From e061883c32ec31a68642ba19c8e023b7cda94239 Mon Sep 17 00:00:00 2001 From: Waldemar Date: Thu, 2 Oct 2025 13:50:28 +0200 Subject: [PATCH 01/40] fix(cli): Run tests before release --- .github/workflows/release.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f29aebf..d2a754b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -17,6 +17,8 @@ jobs: uses: actions/setup-go@v6 with: go-version: ">=1.25.1" + - name: Test code + run: make test - name: Run GoReleaser uses: https://github.com/goreleaser/goreleaser-action@v6 env: From 393977c7fcf0d2eee7437757be90cd850a97f48c Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Thu, 2 Oct 2025 14:52:40 +0200 Subject: [PATCH 02/40] feat(cli): Added an auto approve flag for apply --- cmd/apply.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/apply.go b/cmd/apply.go index 22c26d8..41e94e9 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -16,8 +16,9 @@ import ( ) var ( - configFile string - dryRun bool + configFile string + dryRun bool + autoApprove bool ) var applyCmd = &cobra.Command{ @@ -33,14 +34,14 @@ the necessary changes to deploy your applications across multiple cloudlets.`, os.Exit(1) } - if err := runApply(configFile, dryRun); err != nil { + if err := runApply(configFile, dryRun, autoApprove); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } }, } -func runApply(configPath string, isDryRun bool) error { +func runApply(configPath string, isDryRun bool, autoApprove bool) error { // Step 1: Validate and resolve config file path absPath, err := filepath.Abs(configPath) if err != nil { @@ -112,7 +113,7 @@ func runApply(configPath string, isDryRun bool) error { fmt.Printf("\nThis will perform %d actions. Estimated time: %v\n", result.Plan.TotalActions, result.Plan.EstimatedDuration) - if !confirmDeployment() { + if !autoApprove && !confirmDeployment() { fmt.Println("Deployment cancelled.") return nil } @@ -170,6 +171,7 @@ func init() { applyCmd.Flags().StringVarP(&configFile, "file", "f", "", "configuration file path (required)") applyCmd.Flags().BoolVar(&dryRun, "dry-run", false, "preview changes without applying them") + applyCmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "automatically approve the deployment plan") applyCmd.MarkFlagRequired("file") } From 6de170f6cfb2f26bdd6ff9401b053aab3ece1c4e Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 6 Oct 2025 16:45:53 +0200 Subject: [PATCH 03/40] feat(cli): Added output of diff when updating outboundConnections in the desired manifest --- internal/apply/planner.go | 35 +++++++++++++++++++++-------------- internal/apply/types.go | 33 ++++++++++++++++++++++----------- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/internal/apply/planner.go b/internal/apply/planner.go index 93caf18..f247e16 100644 --- a/internal/apply/planner.go +++ b/internal/apply/planner.go @@ -384,17 +384,20 @@ func (p *EdgeConnectPlanner) compareAppStates(current, desired *AppState) ([]str } // Compare outbound connections - if !p.compareOutboundConnections(current.OutboundConnections, desired.OutboundConnections) { - changes = append(changes, "Outbound connections changed") + outboundChanges := p.compareOutboundConnections(current.OutboundConnections, desired.OutboundConnections) + if len(outboundChanges) > 0 { + changes = append(changes, "Outbound connections changed:") + changes = append(changes, outboundChanges...) } return changes, manifestChanged } // compareOutboundConnections compares two sets of outbound connections for equality -func (p *EdgeConnectPlanner) compareOutboundConnections(current, desired []SecurityRule) bool { - makeMap := func(rules []SecurityRule) map[string]struct{} { - m := make(map[string]struct{}, len(rules)) +func (p *EdgeConnectPlanner) compareOutboundConnections(current, desired []SecurityRule) []string { + var changes []string + makeMap := func(rules []SecurityRule) map[string]SecurityRule { + m := make(map[string]SecurityRule, len(rules)) for _, r := range rules { key := fmt.Sprintf("%s:%d-%d:%s", strings.ToLower(r.Protocol), @@ -402,7 +405,7 @@ func (p *EdgeConnectPlanner) compareOutboundConnections(current, desired []Secur r.PortRangeMax, r.RemoteCIDR, ) - m[key] = struct{}{} + m[key] = r } return m } @@ -410,17 +413,21 @@ func (p *EdgeConnectPlanner) compareOutboundConnections(current, desired []Secur currentMap := makeMap(current) desiredMap := makeMap(desired) - if len(currentMap) != len(desiredMap) { - return false - } - - for k := range currentMap { - if _, exists := desiredMap[k]; !exists { - return false + // Find added and modified rules + for key, rule := range desiredMap { + if _, exists := currentMap[key]; !exists { + changes = append(changes, fmt.Sprintf(" - Added outbound connection: %s %d-%d to %s", rule.Protocol, rule.PortRangeMin, rule.PortRangeMax, rule.RemoteCIDR)) } } - return true + // Find removed rules + for key, rule := range currentMap { + if _, exists := desiredMap[key]; !exists { + changes = append(changes, fmt.Sprintf(" - Removed outbound connection: %s %d-%d to %s", rule.Protocol, rule.PortRangeMin, rule.PortRangeMax, rule.RemoteCIDR)) + } + } + + return changes } // compareInstanceStates compares current and desired instance states and returns changes diff --git a/internal/apply/types.go b/internal/apply/types.go index cd2a93a..6f7ef4e 100644 --- a/internal/apply/types.go +++ b/internal/apply/types.go @@ -4,6 +4,7 @@ package apply import ( "fmt" + "strings" "time" "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" @@ -353,40 +354,50 @@ func (dp *DeploymentPlan) GenerateSummary() string { return "No changes required - configuration matches current state" } - summary := fmt.Sprintf("Deployment plan for '%s':\n", dp.ConfigName) + var sb strings.Builder + sb.WriteString(fmt.Sprintf("Deployment plan for '%s':\n", dp.ConfigName)) // App actions if dp.AppAction.Type != ActionNone { - summary += fmt.Sprintf("- %s application '%s'\n", dp.AppAction.Type, dp.AppAction.Desired.Name) + sb.WriteString(fmt.Sprintf("- %s application '%s'\n", dp.AppAction.Type, dp.AppAction.Desired.Name)) if len(dp.AppAction.Changes) > 0 { for _, change := range dp.AppAction.Changes { - summary += fmt.Sprintf(" - %s\n", change) + sb.WriteString(fmt.Sprintf(" - %s\n", change)) } } } // Instance actions createCount := 0 - updateCount := 0 + updateActions := []InstanceAction{} for _, action := range dp.InstanceActions { switch action.Type { case ActionCreate: createCount++ case ActionUpdate: - updateCount++ + updateActions = append(updateActions, action) } } if createCount > 0 { - summary += fmt.Sprintf("- CREATE %d instance(s) across %d cloudlet(s)\n", createCount, len(dp.GetTargetCloudlets())) - } - if updateCount > 0 { - summary += fmt.Sprintf("- UPDATE %d instance(s)\n", updateCount) + sb.WriteString(fmt.Sprintf("- CREATE %d instance(s) across %d cloudlet(s)\n", createCount, len(dp.GetTargetCloudlets()))) } - summary += fmt.Sprintf("Estimated duration: %s", dp.EstimatedDuration.String()) + if len(updateActions) > 0 { + sb.WriteString(fmt.Sprintf("- UPDATE %d instance(s)\n", len(updateActions))) + for _, action := range updateActions { + if len(action.Changes) > 0 { + sb.WriteString(fmt.Sprintf(" - Instance '%s':\n", action.InstanceName)) + for _, change := range action.Changes { + sb.WriteString(fmt.Sprintf(" - %s\n", change)) + } + } + } + } - return summary + sb.WriteString(fmt.Sprintf("Estimated duration: %s", dp.EstimatedDuration.String())) + + return sb.String() } // Validate checks if the deployment plan is valid and safe to execute From e092f352f8484a44fa9e2179c17108f014392562 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 6 Oct 2025 17:08:33 +0200 Subject: [PATCH 04/40] feat(cli): Added hash compare between current and desired manifest state without using annotation. instead the current hash is calculated from the showapp() app.deploymentmanifest field --- internal/apply/planner.go | 5 +++++ sdk/examples/comprehensive/EdgeConnectConfig.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/apply/planner.go b/internal/apply/planner.go index f247e16..a9219ea 100644 --- a/internal/apply/planner.go +++ b/internal/apply/planner.go @@ -304,6 +304,11 @@ func (p *EdgeConnectPlanner) getCurrentAppState(ctx context.Context, desired *Ap LastUpdated: time.Now(), // EdgeConnect doesn't provide this, so use current time } + // Calculate current manifest hash + hasher := sha256.New() + hasher.Write([]byte(app.DeploymentManifest)) + current.ManifestHash = fmt.Sprintf("%x", hasher.Sum(nil)) + // Note: EdgeConnect API doesn't currently support annotations for manifest hash tracking // This would be implemented when the API supports it diff --git a/sdk/examples/comprehensive/EdgeConnectConfig.yaml b/sdk/examples/comprehensive/EdgeConnectConfig.yaml index 37bde30..842494a 100644 --- a/sdk/examples/comprehensive/EdgeConnectConfig.yaml +++ b/sdk/examples/comprehensive/EdgeConnectConfig.yaml @@ -10,7 +10,7 @@ spec: # image: "https://registry-1.docker.io/library/nginx:latest" k8sApp: appVersion: "1.0.0" - manifestFile: "./k8s-deployment.yaml" # store hash of the manifest file in annotation field. Annotations is a comma separated map of arbitrary key value pairs, + manifestFile: "./k8s-deployment.yaml" infraTemplate: - organization: "edp2" region: "EU" From f635157d675ef275f1937b796a179112e755bea4 Mon Sep 17 00:00:00 2001 From: Patrick Sy Date: Tue, 7 Oct 2025 14:37:54 +0200 Subject: [PATCH 05/40] chore: Added flake --- .envrc.example | 1 + .gitignore | 4 ++++ flake.lock | 25 +++++++++++++++++++++++++ flake.nix | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 .envrc.example create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.envrc.example b/.envrc.example new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc.example @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 7cf5941..c08c1df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ edge-connect # Added by goreleaser init: dist/ + +### direnv ### +.direnv +.envrc diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..eb9d74d --- /dev/null +++ b/flake.lock @@ -0,0 +1,25 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1759733170, + "narHash": "sha256-TXnlsVb5Z8HXZ6mZoeOAIwxmvGHp1g4Dw89eLvIwKVI=", + "rev": "8913c168d1c56dc49a7718685968f38752171c3b", + "revCount": 873256, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.873256%2Brev-8913c168d1c56dc49a7718685968f38752171c3b/0199bd36-8ae7-7817-b019-8688eb4f61ff/source.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://flakehub.com/f/NixOS/nixpkgs/0.1" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..2536eb7 --- /dev/null +++ b/flake.nix @@ -0,0 +1,38 @@ +{ + description = "A Nix-flake-based Go development environment"; + + inputs.nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1"; + + outputs = inputs: + let + goVersion = 25; # Change this to update the whole stack + + supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; + forEachSupportedSystem = f: inputs.nixpkgs.lib.genAttrs supportedSystems (system: f { + pkgs = import inputs.nixpkgs { + inherit system; + overlays = [ inputs.self.overlays.default ]; + }; + }); + in + { + overlays.default = final: prev: { + go = final."go_1_${toString goVersion}"; + }; + + devShells = forEachSupportedSystem ({ pkgs }: { + default = pkgs.mkShell { + packages = with pkgs; [ + # go (version is specified by overlay) + go + + # goimports, godoc, etc. + gotools + + # https://github.com/golangci/golangci-lint + golangci-lint + ]; + }; + }); + }; +} From cc8b9e791b83665ec6375702e567d6aa73d5760a Mon Sep 17 00:00:00 2001 From: Patrick Sy Date: Tue, 7 Oct 2025 15:40:27 +0200 Subject: [PATCH 06/40] fix(cli): Fixed tests after outputting plan diff --- internal/apply/planner.go | 9 +++++++-- internal/apply/planner_test.go | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/apply/planner.go b/internal/apply/planner.go index a9219ea..4e2a3e0 100644 --- a/internal/apply/planner.go +++ b/internal/apply/planner.go @@ -391,8 +391,13 @@ func (p *EdgeConnectPlanner) compareAppStates(current, desired *AppState) ([]str // Compare outbound connections outboundChanges := p.compareOutboundConnections(current.OutboundConnections, desired.OutboundConnections) if len(outboundChanges) > 0 { - changes = append(changes, "Outbound connections changed:") - changes = append(changes, outboundChanges...) + sb:= strings.Builder{} + sb.WriteString("Outbound connections changed:\n") + for _, change := range outboundChanges { + sb.WriteString(change) + sb.WriteString("\n") + } + changes = append(changes, sb.String()) } return changes, manifestChanged diff --git a/internal/apply/planner_test.go b/internal/apply/planner_test.go index cd9ef31..358ae41 100644 --- a/internal/apply/planner_test.go +++ b/internal/apply/planner_test.go @@ -185,6 +185,7 @@ func TestPlanExistingDeploymentNoChanges(t *testing.T) { // Note: We would calculate expected manifest hash here when API supports it // Mock existing app with same manifest hash and outbound connections + manifestContent := "apiVersion: v1\nkind: Pod\nmetadata:\n name: test\n" existingApp := &edgeconnect.App{ Key: edgeconnect.AppKey{ Organization: "testorg", @@ -192,6 +193,7 @@ func TestPlanExistingDeploymentNoChanges(t *testing.T) { Version: "1.0.0", }, Deployment: "kubernetes", + DeploymentManifest: manifestContent, RequiredOutboundConnections: []edgeconnect.SecurityRule{ { Protocol: "tcp", From 06f921963a0af4c6030ef032eeaf7f72a923bcba Mon Sep 17 00:00:00 2001 From: Patrick Sy Date: Tue, 7 Oct 2025 16:01:38 +0200 Subject: [PATCH 07/40] refactor(yaml): moved AppVersion into metadata --- internal/apply/manager_test.go | 4 +- internal/apply/planner.go | 7 +-- internal/apply/planner_test.go | 4 +- internal/apply/strategy_recreate.go | 2 +- internal/config/example_test.go | 5 +- internal/config/parser_test.go | 54 ++++++++++++------- internal/config/types.go | 43 ++++----------- .../comprehensive/EdgeConnectConfig.yaml | 2 +- 8 files changed, 57 insertions(+), 64 deletions(-) diff --git a/internal/apply/manager_test.go b/internal/apply/manager_test.go index 17fd027..539e7e0 100644 --- a/internal/apply/manager_test.go +++ b/internal/apply/manager_test.go @@ -137,11 +137,11 @@ func createTestManagerConfig(t *testing.T) *config.EdgeConnectConfig { return &config.EdgeConnectConfig{ Kind: "edgeconnect-deployment", Metadata: config.Metadata{ - Name: "test-app", + Name: "test-app", + AppVersion: "1.0.0", }, Spec: config.Spec{ K8sApp: &config.K8sApp{ - AppVersion: "1.0.0", ManifestFile: manifestFile, }, InfraTemplate: []config.InfraTemplate{ diff --git a/internal/apply/planner.go b/internal/apply/planner.go index 4e2a3e0..c0a9f70 100644 --- a/internal/apply/planner.go +++ b/internal/apply/planner.go @@ -134,7 +134,7 @@ func (p *EdgeConnectPlanner) planAppAction(ctx context.Context, config *config.E // Build desired app state desired := &AppState{ Name: config.Metadata.Name, - Version: config.Spec.GetAppVersion(), + Version: config.Metadata.AppVersion, Organization: config.Spec.InfraTemplate[0].Organization, // Use first infra template for org Region: config.Spec.InfraTemplate[0].Region, // Use first infra template for region Exists: false, // Will be set based on current state @@ -204,6 +204,7 @@ func (p *EdgeConnectPlanner) planAppAction(ctx context.Context, config *config.E action.Type = ActionUpdate action.Changes = changes action.Reason = "Application configuration has changed" + fmt.Printf("Changes: %v\n", changes) if manifestChanged { warnings = append(warnings, "Manifest file has changed - instances may need to be recreated") @@ -219,11 +220,11 @@ func (p *EdgeConnectPlanner) planInstanceActions(ctx context.Context, config *co var warnings []string for _, infra := range config.Spec.InfraTemplate { - instanceName := getInstanceName(config.Metadata.Name, config.Spec.GetAppVersion()) + instanceName := getInstanceName(config.Metadata.Name, config.Metadata.AppVersion) desired := &InstanceState{ Name: instanceName, - AppVersion: config.Spec.GetAppVersion(), + AppVersion: config.Metadata.AppVersion, Organization: infra.Organization, Region: infra.Region, CloudletOrg: infra.CloudletOrg, diff --git a/internal/apply/planner_test.go b/internal/apply/planner_test.go index 358ae41..5568dea 100644 --- a/internal/apply/planner_test.go +++ b/internal/apply/planner_test.go @@ -112,11 +112,11 @@ func createTestConfig(t *testing.T) *config.EdgeConnectConfig { return &config.EdgeConnectConfig{ Kind: "edgeconnect-deployment", Metadata: config.Metadata{ - Name: "test-app", + Name: "test-app", + AppVersion: "1.0.0", }, Spec: config.Spec{ K8sApp: &config.K8sApp{ - AppVersion: "1.0.0", ManifestFile: manifestFile, }, InfraTemplate: []config.InfraTemplate{ diff --git a/internal/apply/strategy_recreate.go b/internal/apply/strategy_recreate.go index da88664..3ac1502 100644 --- a/internal/apply/strategy_recreate.go +++ b/internal/apply/strategy_recreate.go @@ -440,7 +440,7 @@ func (r *RecreateStrategy) createInstance(ctx context.Context, action InstanceAc AppKey: edgeconnect.AppKey{ Organization: action.Target.Organization, Name: config.Metadata.Name, - Version: config.Spec.GetAppVersion(), + Version: config.Metadata.AppVersion, }, Flavor: edgeconnect.Flavor{ Name: action.Target.FlavorName, diff --git a/internal/config/example_test.go b/internal/config/example_test.go index 7219412..7316430 100644 --- a/internal/config/example_test.go +++ b/internal/config/example_test.go @@ -28,7 +28,7 @@ func TestParseExampleConfig(t *testing.T) { // Check k8s app configuration require.NotNil(t, config.Spec.K8sApp) - assert.Equal(t, "1.0.0", config.Spec.K8sApp.AppVersion) + assert.Equal(t, "1.0.0", config.Metadata.AppVersion) // Note: ManifestFile path should be resolved to absolute path assert.Contains(t, config.Spec.K8sApp.ManifestFile, "k8s-deployment.yaml") @@ -59,7 +59,6 @@ func TestParseExampleConfig(t *testing.T) { // Test utility methods assert.Equal(t, "edge-app-demo", config.Metadata.Name) - assert.Equal(t, "1.0.0", config.Spec.GetAppVersion()) assert.Contains(t, config.Spec.GetManifestFile(), "k8s-deployment.yaml") assert.True(t, config.Spec.IsK8sApp()) assert.False(t, config.Spec.IsDockerApp()) @@ -73,10 +72,10 @@ func TestValidateExampleStructure(t *testing.T) { Kind: "edgeconnect-deployment", Metadata: Metadata{ Name: "edge-app-demo", + AppVersion: "1.0.0", }, Spec: Spec{ DockerApp: &DockerApp{ // Use DockerApp to avoid manifest file validation - AppVersion: "1.0.0", Image: "nginx:latest", }, InfraTemplate: []InfraTemplate{ diff --git a/internal/config/parser_test.go b/internal/config/parser_test.go index d5e0865..c713ff5 100644 --- a/internal/config/parser_test.go +++ b/internal/config/parser_test.go @@ -32,9 +32,9 @@ func TestConfigParser_ParseBytes(t *testing.T) { kind: edgeconnect-deployment metadata: name: "test-app" + appVersion: "1.0.0" spec: k8sApp: - appVersion: "1.0.0" manifestFile: "./test-manifest.yaml" infraTemplate: - organization: "testorg" @@ -52,9 +52,9 @@ spec: kind: edgeconnect-deployment metadata: name: "test-app" + appVersion: "1.0.0" spec: dockerApp: - appVersion: "1.0.0" image: "nginx:latest" infraTemplate: - organization: "testorg" @@ -70,9 +70,9 @@ spec: yaml: ` metadata: name: "test-app" + appVersion: "1.0.0" spec: k8sApp: - appVersion: "1.0.0" manifestFile: "./test-manifest.yaml" infraTemplate: - organization: "testorg" @@ -90,9 +90,9 @@ spec: kind: invalid-kind metadata: name: "test-app" + appVersion: "1.0.0" spec: dockerApp: - appVersion: "1.0.0" image: "nginx:latest" infraTemplate: - organization: "testorg" @@ -110,6 +110,7 @@ spec: kind: edgeconnect-deployment metadata: name: "test-app" + appVersion: "1.0.0" spec: infraTemplate: - organization: "testorg" @@ -127,13 +128,12 @@ spec: kind: edgeconnect-deployment metadata: name: "test-app" + appVersion: "1.0.0" spec: k8sApp: - appVersion: "1.0.0" manifestFile: "./test-manifest.yaml" dockerApp: appName: "test-app" - appVersion: "1.0.0" image: "nginx:latest" infraTemplate: - organization: "testorg" @@ -151,9 +151,9 @@ spec: kind: edgeconnect-deployment metadata: name: "test-app" + appVersion: "1.0.0" spec: dockerApp: - appVersion: "1.0.0" image: "nginx:latest" infraTemplate: [] `, @@ -166,9 +166,9 @@ spec: kind: edgeconnect-deployment metadata: name: "test-app" + appVersion: "1.0.0" spec: dockerApp: - appVersion: "1.0.0" image: "nginx:latest" infraTemplate: - organization: "testorg" @@ -222,9 +222,9 @@ func TestConfigParser_ParseFile(t *testing.T) { kind: edgeconnect-deployment metadata: name: "test-app" + appVersion: "1.0.0" spec: dockerApp: - appVersion: "1.0.0" image: "nginx:latest" infraTemplate: - organization: "testorg" @@ -284,9 +284,9 @@ func TestConfigParser_RelativePathResolution(t *testing.T) { kind: edgeconnect-deployment metadata: name: "test-app" + appVersion: "1.0.0" spec: k8sApp: - appVersion: "1.0.0" manifestFile: "./manifest.yaml" infraTemplate: - organization: "testorg" @@ -322,10 +322,10 @@ func TestEdgeConnectConfig_Validate(t *testing.T) { Kind: "edgeconnect-deployment", Metadata: Metadata{ Name: "test-app", + AppVersion: "1.0.0", }, Spec: Spec{ DockerApp: &DockerApp{ - AppVersion: "1.0.0", Image: "nginx:latest", }, InfraTemplate: []InfraTemplate{ @@ -385,24 +385,42 @@ func TestMetadata_Validate(t *testing.T) { }{ { name: "valid metadata", - metadata: Metadata{Name: "test-app"}, + metadata: Metadata{Name: "test-app", AppVersion: "1.0.0"}, wantErr: false, }, { name: "empty name", - metadata: Metadata{Name: ""}, + metadata: Metadata{Name: "", AppVersion: "1.0.0"}, wantErr: true, errMsg: "metadata.name is required", }, { name: "name with leading whitespace", - metadata: Metadata{Name: " test-app"}, + metadata: Metadata{Name: " test-app", AppVersion: "1.0.0"}, wantErr: true, errMsg: "cannot have leading/trailing whitespace", }, { name: "name with trailing whitespace", - metadata: Metadata{Name: "test-app "}, + metadata: Metadata{Name: "test-app ", AppVersion: "1.0.0"}, + wantErr: true, + errMsg: "cannot have leading/trailing whitespace", + }, + { + name: "empty app version", + metadata: Metadata{Name: "test-app", AppVersion: ""}, + wantErr: true, + errMsg: "metadata.appVersion is required", + }, + { + name: "app version with leading whitespace", + metadata: Metadata{Name: "test-app", AppVersion: " 1.0.0"}, + wantErr: true, + errMsg: "cannot have leading/trailing whitespace", + }, + { + name: "app version with trailing whitespace", + metadata: Metadata{Name: "test-app", AppVersion: "1.0.0 "}, wantErr: true, errMsg: "cannot have leading/trailing whitespace", }, @@ -526,24 +544,20 @@ func TestOutboundConnection_Validate(t *testing.T) { func TestSpec_GetMethods(t *testing.T) { k8sSpec := &Spec{ K8sApp: &K8sApp{ - AppVersion: "1.0.0", ManifestFile: "k8s.yaml", }, } dockerSpec := &Spec{ DockerApp: &DockerApp{ - AppVersion: "2.0.0", ManifestFile: "docker.yaml", }, } - assert.Equal(t, "1.0.0", k8sSpec.GetAppVersion()) assert.Equal(t, "k8s.yaml", k8sSpec.GetManifestFile()) assert.True(t, k8sSpec.IsK8sApp()) assert.False(t, k8sSpec.IsDockerApp()) - assert.Equal(t, "2.0.0", dockerSpec.GetAppVersion()) assert.Equal(t, "docker.yaml", dockerSpec.GetManifestFile()) assert.False(t, dockerSpec.IsK8sApp()) assert.True(t, dockerSpec.IsDockerApp()) @@ -564,9 +578,9 @@ func TestReadManifestFile(t *testing.T) { kind: edgeconnect-deployment metadata: name: "test-app" + appVersion: "1.0.0" spec: k8sApp: - appVersion: "1.0.0" manifestFile: "./manifest.yaml" infraTemplate: - organization: "testorg" diff --git a/internal/config/types.go b/internal/config/types.go index 71388d0..88b52b7 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -18,7 +18,8 @@ type EdgeConnectConfig struct { // Metadata contains configuration metadata type Metadata struct { - Name string `yaml:"name"` + Name string `yaml:"name"` + AppVersion string `yaml:"appVersion"` } // Spec defines the application and infrastructure specification @@ -32,13 +33,11 @@ type Spec struct { // K8sApp defines Kubernetes application configuration type K8sApp struct { - AppVersion string `yaml:"appVersion"` ManifestFile string `yaml:"manifestFile"` } // DockerApp defines Docker application configuration type DockerApp struct { - AppVersion string `yaml:"appVersion"` ManifestFile string `yaml:"manifestFile"` Image string `yaml:"image"` } @@ -113,6 +112,15 @@ func (m *Metadata) Validate() error { return fmt.Errorf("metadata.name cannot have leading/trailing whitespace") } + if m.AppVersion == "" { + return fmt.Errorf("metadata.appVersion is required") + } + + // Validate version format + if strings.TrimSpace(m.AppVersion) != m.AppVersion { + return fmt.Errorf("metadata.appVersion cannot have leading/trailing whitespace") + } + return nil } @@ -171,10 +179,6 @@ func (s *Spec) Validate() error { // Validate validates k8s app configuration func (k *K8sApp) Validate() error { - if k.AppVersion == "" { - return fmt.Errorf("appVersion is required") - } - if k.ManifestFile == "" { return fmt.Errorf("manifestFile is required") } @@ -184,29 +188,15 @@ func (k *K8sApp) Validate() error { return fmt.Errorf("manifestFile does not exist: %s", k.ManifestFile) } - // Validate version format - if strings.TrimSpace(k.AppVersion) != k.AppVersion { - return fmt.Errorf("appVersion cannot have leading/trailing whitespace") - } - return nil } // Validate validates docker app configuration func (d *DockerApp) Validate() error { - if d.AppVersion == "" { - return fmt.Errorf("appVersion is required") - } - if d.Image == "" { return fmt.Errorf("image is required") } - // Validate version format - if strings.TrimSpace(d.AppVersion) != d.AppVersion { - return fmt.Errorf("appVersion cannot have leading/trailing whitespace") - } - // Check if manifest file exists if specified if d.ManifestFile != "" { if _, err := os.Stat(d.ManifestFile); os.IsNotExist(err) { @@ -326,17 +316,6 @@ func (d *DockerApp) GetManifestPath(configDir string) string { return filepath.Join(configDir, d.ManifestFile) } -// GetAppVersion returns the application version from the active app type -func (s *Spec) GetAppVersion() string { - if s.K8sApp != nil { - return s.K8sApp.AppVersion - } - if s.DockerApp != nil { - return s.DockerApp.AppVersion - } - return "" -} - // GetManifestFile returns the manifest file path from the active app type func (s *Spec) GetManifestFile() string { if s.K8sApp != nil { diff --git a/sdk/examples/comprehensive/EdgeConnectConfig.yaml b/sdk/examples/comprehensive/EdgeConnectConfig.yaml index 842494a..1430b33 100644 --- a/sdk/examples/comprehensive/EdgeConnectConfig.yaml +++ b/sdk/examples/comprehensive/EdgeConnectConfig.yaml @@ -3,13 +3,13 @@ kind: edgeconnect-deployment metadata: name: "edge-app-demo" # name could be used for appName + appVersion: "1.0.0" spec: # dockerApp: # Docker is OBSOLETE # appVersion: "1.0.0" # manifestFile: "./docker-compose.yaml" # image: "https://registry-1.docker.io/library/nginx:latest" k8sApp: - appVersion: "1.0.0" manifestFile: "./k8s-deployment.yaml" infraTemplate: - organization: "edp2" From 0f3cc90b01d895a4e816449eca924aab3c519db6 Mon Sep 17 00:00:00 2001 From: Patrick Sy Date: Tue, 7 Oct 2025 16:08:48 +0200 Subject: [PATCH 08/40] ci: Added test workflow running on each push except tags --- .github/workflows/test.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..5c1cefa --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,23 @@ +name: test + +on: + push: + branches: + - '*' + tags-ignore: + - '*' + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: ">=1.25.1" + - name: Test code + run: make test From bc524c3b0e8b8006282cc954a3aa0e9aebe1f499 Mon Sep 17 00:00:00 2001 From: Patrick Sy Date: Tue, 7 Oct 2025 16:30:57 +0200 Subject: [PATCH 09/40] refactor(yaml): Moved organisation to metadata --- internal/apply/manager.go | 2 +- internal/apply/planner.go | 4 ++-- internal/apply/strategy_recreate.go | 6 +++--- internal/config/types.go | 16 ++++++++-------- .../comprehensive/EdgeConnectConfig.yaml | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/apply/manager.go b/internal/apply/manager.go index 9951ada..45477ab 100644 --- a/internal/apply/manager.go +++ b/internal/apply/manager.go @@ -265,7 +265,7 @@ func (rm *EdgeConnectResourceManager) rollbackInstance(ctx context.Context, acti for _, instanceAction := range plan.InstanceActions { if instanceAction.InstanceName == action.Target { instanceKey := edgeconnect.AppInstanceKey{ - Organization: instanceAction.Target.Organization, + Organization: plan.AppAction.Desired.Organization, Name: instanceAction.InstanceName, CloudletKey: edgeconnect.CloudletKey{ Organization: instanceAction.Target.CloudletOrg, diff --git a/internal/apply/planner.go b/internal/apply/planner.go index c0a9f70..1cbc58d 100644 --- a/internal/apply/planner.go +++ b/internal/apply/planner.go @@ -135,7 +135,7 @@ func (p *EdgeConnectPlanner) planAppAction(ctx context.Context, config *config.E desired := &AppState{ Name: config.Metadata.Name, Version: config.Metadata.AppVersion, - Organization: config.Spec.InfraTemplate[0].Organization, // Use first infra template for org + Organization: config.Metadata.Organization, // Use first infra template for org Region: config.Spec.InfraTemplate[0].Region, // Use first infra template for region Exists: false, // Will be set based on current state } @@ -225,7 +225,7 @@ func (p *EdgeConnectPlanner) planInstanceActions(ctx context.Context, config *co desired := &InstanceState{ Name: instanceName, AppVersion: config.Metadata.AppVersion, - Organization: infra.Organization, + Organization: config.Metadata.Organization, Region: infra.Region, CloudletOrg: infra.CloudletOrg, CloudletName: infra.CloudletName, diff --git a/internal/apply/strategy_recreate.go b/internal/apply/strategy_recreate.go index 3ac1502..b2302ca 100644 --- a/internal/apply/strategy_recreate.go +++ b/internal/apply/strategy_recreate.go @@ -408,7 +408,7 @@ func (r *RecreateStrategy) executeAppActionWithRetry(ctx context.Context, action // deleteInstance deletes an instance (reuse existing logic from manager.go) func (r *RecreateStrategy) deleteInstance(ctx context.Context, action InstanceAction) (bool, error) { instanceKey := edgeconnect.AppInstanceKey{ - Organization: action.Target.Organization, + Organization: action.Desired.Organization, Name: action.InstanceName, CloudletKey: edgeconnect.CloudletKey{ Organization: action.Target.CloudletOrg, @@ -430,7 +430,7 @@ func (r *RecreateStrategy) createInstance(ctx context.Context, action InstanceAc Region: action.Target.Region, AppInst: edgeconnect.AppInstance{ Key: edgeconnect.AppInstanceKey{ - Organization: action.Target.Organization, + Organization: action.Desired.Organization, Name: action.InstanceName, CloudletKey: edgeconnect.CloudletKey{ Organization: action.Target.CloudletOrg, @@ -438,7 +438,7 @@ func (r *RecreateStrategy) createInstance(ctx context.Context, action InstanceAc }, }, AppKey: edgeconnect.AppKey{ - Organization: action.Target.Organization, + Organization: action.Desired.Organization, Name: config.Metadata.Name, Version: config.Metadata.AppVersion, }, diff --git a/internal/config/types.go b/internal/config/types.go index 88b52b7..05d84b7 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -18,8 +18,9 @@ type EdgeConnectConfig struct { // Metadata contains configuration metadata type Metadata struct { - Name string `yaml:"name"` - AppVersion string `yaml:"appVersion"` + Name string `yaml:"name"` + AppVersion string `yaml:"appVersion"` + Organization string `yaml:"organization"` } // Spec defines the application and infrastructure specification @@ -44,7 +45,6 @@ type DockerApp struct { // InfraTemplate defines infrastructure deployment targets type InfraTemplate struct { - Organization string `yaml:"organization"` Region string `yaml:"region"` CloudletOrg string `yaml:"cloudletOrg"` CloudletName string `yaml:"cloudletName"` @@ -121,6 +121,11 @@ func (m *Metadata) Validate() error { return fmt.Errorf("metadata.appVersion cannot have leading/trailing whitespace") } + if m.Organization == "" { + return fmt.Errorf("organization is required") + } + + return nil } @@ -209,10 +214,6 @@ func (d *DockerApp) Validate() error { // Validate validates infrastructure template configuration func (i *InfraTemplate) Validate() error { - if i.Organization == "" { - return fmt.Errorf("organization is required") - } - if i.Region == "" { return fmt.Errorf("region is required") } @@ -231,7 +232,6 @@ func (i *InfraTemplate) Validate() error { // Validate no leading/trailing whitespace fields := map[string]string{ - "organization": i.Organization, "region": i.Region, "cloudletOrg": i.CloudletOrg, "cloudletName": i.CloudletName, diff --git a/sdk/examples/comprehensive/EdgeConnectConfig.yaml b/sdk/examples/comprehensive/EdgeConnectConfig.yaml index 1430b33..b45abc4 100644 --- a/sdk/examples/comprehensive/EdgeConnectConfig.yaml +++ b/sdk/examples/comprehensive/EdgeConnectConfig.yaml @@ -4,6 +4,7 @@ kind: edgeconnect-deployment metadata: name: "edge-app-demo" # name could be used for appName appVersion: "1.0.0" + organization: "edp2" spec: # dockerApp: # Docker is OBSOLETE # appVersion: "1.0.0" @@ -12,8 +13,7 @@ spec: k8sApp: manifestFile: "./k8s-deployment.yaml" infraTemplate: - - organization: "edp2" - region: "EU" + - region: "EU" cloudletOrg: "TelekomOP" cloudletName: "Munich" flavorName: "EU.small" From a72341356b97c9fc61789f3e27031d74f834a0f7 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Tue, 7 Oct 2025 17:05:35 +0200 Subject: [PATCH 10/40] fix(test): started fixing tests --- internal/config/example_test.go | 3 +-- internal/config/parser_test.go | 48 ++++++++++++++++----------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/internal/config/example_test.go b/internal/config/example_test.go index 7316430..dfa3840 100644 --- a/internal/config/example_test.go +++ b/internal/config/example_test.go @@ -35,7 +35,6 @@ func TestParseExampleConfig(t *testing.T) { // Check infrastructure template require.Len(t, config.Spec.InfraTemplate, 1) infra := config.Spec.InfraTemplate[0] - assert.Equal(t, "edp2", infra.Organization) assert.Equal(t, "EU", infra.Region) assert.Equal(t, "TelekomOP", infra.CloudletOrg) assert.Equal(t, "Munich", infra.CloudletName) @@ -73,6 +72,7 @@ func TestValidateExampleStructure(t *testing.T) { Metadata: Metadata{ Name: "edge-app-demo", AppVersion: "1.0.0", + Organization: "edp2", }, Spec: Spec{ DockerApp: &DockerApp{ // Use DockerApp to avoid manifest file validation @@ -80,7 +80,6 @@ func TestValidateExampleStructure(t *testing.T) { }, InfraTemplate: []InfraTemplate{ { - Organization: "edp2", Region: "EU", CloudletOrg: "TelekomOP", CloudletName: "Munich", diff --git a/internal/config/parser_test.go b/internal/config/parser_test.go index c713ff5..cb672ae 100644 --- a/internal/config/parser_test.go +++ b/internal/config/parser_test.go @@ -33,12 +33,12 @@ kind: edgeconnect-deployment metadata: name: "test-app" appVersion: "1.0.0" + organization: "testorg" spec: k8sApp: manifestFile: "./test-manifest.yaml" infraTemplate: - - organization: "testorg" - region: "US" + - region: "US" cloudletOrg: "TestOP" cloudletName: "TestCloudlet" flavorName: "small" @@ -53,12 +53,12 @@ kind: edgeconnect-deployment metadata: name: "test-app" appVersion: "1.0.0" + organization: "testorg" spec: dockerApp: image: "nginx:latest" infraTemplate: - - organization: "testorg" - region: "US" + - region: "US" cloudletOrg: "TestOP" cloudletName: "TestCloudlet" flavorName: "small" @@ -71,12 +71,12 @@ spec: metadata: name: "test-app" appVersion: "1.0.0" + organization: "testorg" spec: k8sApp: manifestFile: "./test-manifest.yaml" infraTemplate: - - organization: "testorg" - region: "US" + - region: "US" cloudletOrg: "TestOP" cloudletName: "TestCloudlet" flavorName: "small" @@ -91,12 +91,12 @@ kind: invalid-kind metadata: name: "test-app" appVersion: "1.0.0" + organization: "testorg" spec: dockerApp: image: "nginx:latest" infraTemplate: - - organization: "testorg" - region: "US" + - region: "US" cloudletOrg: "TestOP" cloudletName: "TestCloudlet" flavorName: "small" @@ -111,10 +111,10 @@ kind: edgeconnect-deployment metadata: name: "test-app" appVersion: "1.0.0" + organization: "testorg" spec: infraTemplate: - - organization: "testorg" - region: "US" + - region: "US" cloudletOrg: "TestOP" cloudletName: "TestCloudlet" flavorName: "small" @@ -129,6 +129,7 @@ kind: edgeconnect-deployment metadata: name: "test-app" appVersion: "1.0.0" + organization: "testorg" spec: k8sApp: manifestFile: "./test-manifest.yaml" @@ -136,8 +137,7 @@ spec: appName: "test-app" image: "nginx:latest" infraTemplate: - - organization: "testorg" - region: "US" + - region: "US" cloudletOrg: "TestOP" cloudletName: "TestCloudlet" flavorName: "small" @@ -167,12 +167,12 @@ kind: edgeconnect-deployment metadata: name: "test-app" appVersion: "1.0.0" + organization: "testorg" spec: dockerApp: image: "nginx:latest" infraTemplate: - - organization: "testorg" - region: "US" + - region: "US" cloudletOrg: "TestOP" cloudletName: "TestCloudlet" flavorName: "small" @@ -223,12 +223,12 @@ kind: edgeconnect-deployment metadata: name: "test-app" appVersion: "1.0.0" + organization: "testorg" spec: dockerApp: image: "nginx:latest" infraTemplate: - - organization: "testorg" - region: "US" + - region: "US" cloudletOrg: "TestOP" cloudletName: "TestCloudlet" flavorName: "small" @@ -285,12 +285,12 @@ kind: edgeconnect-deployment metadata: name: "test-app" appVersion: "1.0.0" + organization: "testorg" spec: k8sApp: manifestFile: "./manifest.yaml" infraTemplate: - - organization: "testorg" - region: "US" + - region: "US" cloudletOrg: "TestOP" cloudletName: "TestCloudlet" flavorName: "small" @@ -321,16 +321,16 @@ func TestEdgeConnectConfig_Validate(t *testing.T) { config: EdgeConnectConfig{ Kind: "edgeconnect-deployment", Metadata: Metadata{ - Name: "test-app", - AppVersion: "1.0.0", + Name: "test-app", + AppVersion: "1.0.0", + Organization: "testorg", }, Spec: Spec{ DockerApp: &DockerApp{ - Image: "nginx:latest", + Image: "nginx:latest", }, InfraTemplate: []InfraTemplate{ { - Organization: "testorg", Region: "US", CloudletOrg: "TestOP", CloudletName: "TestCloudlet", @@ -579,12 +579,12 @@ kind: edgeconnect-deployment metadata: name: "test-app" appVersion: "1.0.0" + organization: "testorg" spec: k8sApp: manifestFile: "./manifest.yaml" infraTemplate: - - organization: "testorg" - region: "US" + - region: "US" cloudletOrg: "TestOP" cloudletName: "TestCloudlet" flavorName: "small" From f32479aaf8b9a01851d63d0201f59317ec537a9f Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Tue, 7 Oct 2025 17:09:36 +0200 Subject: [PATCH 11/40] fix(test): fixed compile errors --- internal/apply/manager_test.go | 9 +++------ internal/apply/planner_test.go | 9 ++++----- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/internal/apply/manager_test.go b/internal/apply/manager_test.go index 539e7e0..6060a37 100644 --- a/internal/apply/manager_test.go +++ b/internal/apply/manager_test.go @@ -110,7 +110,6 @@ func createTestDeploymentPlan() *DeploymentPlan { { Type: ActionCreate, Target: config.InfraTemplate{ - Organization: "testorg", Region: "US", CloudletOrg: "cloudletorg", CloudletName: "cloudlet1", @@ -137,8 +136,9 @@ func createTestManagerConfig(t *testing.T) *config.EdgeConnectConfig { return &config.EdgeConnectConfig{ Kind: "edgeconnect-deployment", Metadata: config.Metadata{ - Name: "test-app", - AppVersion: "1.0.0", + Name: "test-app", + AppVersion: "1.0.0", + Organization: "testorg", }, Spec: config.Spec{ K8sApp: &config.K8sApp{ @@ -146,7 +146,6 @@ func createTestManagerConfig(t *testing.T) *config.EdgeConnectConfig { }, InfraTemplate: []config.InfraTemplate{ { - Organization: "testorg", Region: "US", CloudletOrg: "cloudletorg", CloudletName: "cloudlet1", @@ -309,7 +308,6 @@ func TestApplyDeploymentMultipleInstances(t *testing.T) { { Type: ActionCreate, Target: config.InfraTemplate{ - Organization: "testorg", Region: "US", CloudletOrg: "cloudletorg1", CloudletName: "cloudlet1", @@ -321,7 +319,6 @@ func TestApplyDeploymentMultipleInstances(t *testing.T) { { Type: ActionCreate, Target: config.InfraTemplate{ - Organization: "testorg", Region: "EU", CloudletOrg: "cloudletorg2", CloudletName: "cloudlet2", diff --git a/internal/apply/planner_test.go b/internal/apply/planner_test.go index 5568dea..d946a14 100644 --- a/internal/apply/planner_test.go +++ b/internal/apply/planner_test.go @@ -112,8 +112,9 @@ func createTestConfig(t *testing.T) *config.EdgeConnectConfig { return &config.EdgeConnectConfig{ Kind: "edgeconnect-deployment", Metadata: config.Metadata{ - Name: "test-app", - AppVersion: "1.0.0", + Name: "test-app", + AppVersion: "1.0.0", + Organization: "testorg", }, Spec: config.Spec{ K8sApp: &config.K8sApp{ @@ -121,7 +122,6 @@ func createTestConfig(t *testing.T) *config.EdgeConnectConfig { }, InfraTemplate: []config.InfraTemplate{ { - Organization: "testorg", Region: "US", CloudletOrg: "TestCloudletOrg", CloudletName: "TestCloudlet", @@ -192,7 +192,7 @@ func TestPlanExistingDeploymentNoChanges(t *testing.T) { Name: "test-app", Version: "1.0.0", }, - Deployment: "kubernetes", + Deployment: "kubernetes", DeploymentManifest: manifestContent, RequiredOutboundConnections: []edgeconnect.SecurityRule{ { @@ -286,7 +286,6 @@ func TestPlanMultipleInfrastructures(t *testing.T) { // Add a second infrastructure target testConfig.Spec.InfraTemplate = append(testConfig.Spec.InfraTemplate, config.InfraTemplate{ - Organization: "testorg", Region: "EU", CloudletOrg: "EUCloudletOrg", CloudletName: "EUCloudlet", From 921822239b397229a98b2861154d87eb678cb924 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Tue, 7 Oct 2025 17:19:52 +0200 Subject: [PATCH 12/40] fix(test): finish fixing organisation refactoring tests failures --- internal/config/parser_test.go | 33 ++++++++++++++++++++++++++------- internal/config/types.go | 5 ++++- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/internal/config/parser_test.go b/internal/config/parser_test.go index cb672ae..7e9cd61 100644 --- a/internal/config/parser_test.go +++ b/internal/config/parser_test.go @@ -152,6 +152,7 @@ kind: edgeconnect-deployment metadata: name: "test-app" appVersion: "1.0.0" + organization: "testorg" spec: dockerApp: image: "nginx:latest" @@ -385,42 +386,60 @@ func TestMetadata_Validate(t *testing.T) { }{ { name: "valid metadata", - metadata: Metadata{Name: "test-app", AppVersion: "1.0.0"}, + metadata: Metadata{Name: "test-app", AppVersion: "1.0.0", Organization: "testorg"}, wantErr: false, }, { name: "empty name", - metadata: Metadata{Name: "", AppVersion: "1.0.0"}, + metadata: Metadata{Name: "", AppVersion: "1.0.0", Organization: "testorg"}, wantErr: true, errMsg: "metadata.name is required", }, { name: "name with leading whitespace", - metadata: Metadata{Name: " test-app", AppVersion: "1.0.0"}, + metadata: Metadata{Name: " test-app", AppVersion: "1.0.0", Organization: "testorg"}, wantErr: true, errMsg: "cannot have leading/trailing whitespace", }, { name: "name with trailing whitespace", - metadata: Metadata{Name: "test-app ", AppVersion: "1.0.0"}, + metadata: Metadata{Name: "test-app ", AppVersion: "1.0.0", Organization: "testorg"}, wantErr: true, errMsg: "cannot have leading/trailing whitespace", }, { name: "empty app version", - metadata: Metadata{Name: "test-app", AppVersion: ""}, + metadata: Metadata{Name: "test-app", AppVersion: "", Organization: "testorg"}, wantErr: true, errMsg: "metadata.appVersion is required", }, { name: "app version with leading whitespace", - metadata: Metadata{Name: "test-app", AppVersion: " 1.0.0"}, + metadata: Metadata{Name: "test-app", AppVersion: " 1.0.0", Organization: "testorg"}, wantErr: true, errMsg: "cannot have leading/trailing whitespace", }, { name: "app version with trailing whitespace", - metadata: Metadata{Name: "test-app", AppVersion: "1.0.0 "}, + metadata: Metadata{Name: "test-app", AppVersion: "1.0.0 ", Organization: "testorg"}, + wantErr: true, + errMsg: "cannot have leading/trailing whitespace", + }, + { + name: "empty organization", + metadata: Metadata{Name: "test-app", AppVersion: "1.0.0", Organization: ""}, + wantErr: true, + errMsg: "metadata.organization is required", + }, + { + name: "organization with leading whitespace", + metadata: Metadata{Name: "test-app", AppVersion: "1.0.0", Organization: " testorg"}, + wantErr: true, + errMsg: "cannot have leading/trailing whitespace", + }, + { + name: "organization with trailing whitespace", + metadata: Metadata{Name: "test-app", AppVersion: "1.0.0", Organization: "testorg "}, wantErr: true, errMsg: "cannot have leading/trailing whitespace", }, diff --git a/internal/config/types.go b/internal/config/types.go index 05d84b7..665d873 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -122,9 +122,12 @@ func (m *Metadata) Validate() error { } if m.Organization == "" { - return fmt.Errorf("organization is required") + return fmt.Errorf("metadata.organization is required") } + if strings.TrimSpace(m.Organization) != m.Organization { + return fmt.Errorf("metadata.Organization cannot have leading/trailing whitespace") + } return nil } From c7b12846063275ff95e9702c763b27417aa2871f Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Tue, 7 Oct 2025 17:21:38 +0200 Subject: [PATCH 13/40] fix(test): finish fixing organisation refactoring tests failures --- internal/config/types.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/config/types.go b/internal/config/types.go index 665d873..9b365dd 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -116,7 +116,6 @@ func (m *Metadata) Validate() error { return fmt.Errorf("metadata.appVersion is required") } - // Validate version format if strings.TrimSpace(m.AppVersion) != m.AppVersion { return fmt.Errorf("metadata.appVersion cannot have leading/trailing whitespace") } From 4ded2e193e12c9b31aa8ea631602654697ad6673 Mon Sep 17 00:00:00 2001 From: Stephan Lo Date: Mon, 13 Oct 2025 10:10:16 +0200 Subject: [PATCH 14/40] feat(ec-api): new swagger from EC (Alex) with changes update app and appinstances. They call it 2.0 which already was delivered. we discussed in Teams: Malashevich, Alex (ext) Freitag 10.10.25 17:19 Updated spec is available. It's relevant for Orca cluster you'll be added next week I hope Swagger UI https://swagger.edge.platform.mg3.mdb.osc.live/#/ Stephan Lo, , Montag 13.10.25 09:37 hey alex ... this is great news! just a quick question: We still see version '2.0' - does this mean that there were no changes? Malashevich, Alex (ext) Montag 13.10.25 09:49 yes, it's just relevant update of current state of things for external teams to integrate with us (FE, Developer Framework, AI, etc). So the spec you've seen before is our internal so to say --- api/swagger.json | 13204 +++------------------------------------------ 1 file changed, 678 insertions(+), 12526 deletions(-) diff --git a/api/swagger.json b/api/swagger.json index 9a9aa56..31cb7fa 100644 --- a/api/swagger.json +++ b/api/swagger.json @@ -1,12716 +1,868 @@ { - "consumes": ["application/json"], - "produces": ["application/json"], - "schemes": ["https"], - "swagger": "2.0", - "host": "hub.apps.edge.platform.mg3.mdb.osc.live", + "basePath": "/api/v1", + "definitions": { + "handler.App": { + "properties": { + "access_ports": { + "description": "Application port to be exposed with ingress in format .\nNecessary only when manifest is generated automatically. Otherwise, all\nthe ports has to be set up manually in YAML manifest.", + "example": "tcp:80", + "type": "string" + }, + "allow_serverless": { + "type": "boolean" + }, + "created_at": { + "description": "Timestamp, format RFC3339.", + "type": "string" + }, + "defaultFlavor": { + "allOf": [ + { + "$ref": "#/definitions/handler.Flavor" + } + ], + "description": "Default resource config to be used." + }, + "deployment": { + "allOf": [ + { + "$ref": "#/definitions/handler.DeploymentType" + } + ], + "example": "kubernetes" + }, + "global_id": { + "description": "Combination of key fields (local-).", + "type": "string" + }, + "image_path": { + "description": "Docker registry URL (e.g. docker.io/library/nginx:latest)", + "type": "string" + }, + "image_type": { + "$ref": "#/definitions/handler.ImageType" + }, + "key": { + "$ref": "#/definitions/handler.AppKey" + }, + "serverless_config": { + "$ref": "#/definitions/handler.ServerlessConfig" + }, + "updated_at": { + "description": "Timestamp, format RFC3339.", + "type": "string" + } + }, + "type": "object" + }, + "handler.AppInst": { + "properties": { + "app_key": { + "$ref": "#/definitions/handler.AppKey" + }, + "cloudlet_loc": { + "$ref": "#/definitions/handler.CloudletLoc" + }, + "created_at": { + "type": "string" + }, + "flavor": { + "$ref": "#/definitions/handler.Flavor" + }, + "ingress_url": { + "type": "string" + }, + "key": { + "$ref": "#/definitions/handler.AppInstKey" + }, + "state": { + "$ref": "#/definitions/handler.AppInstState" + }, + "unique_id": { + "description": "Combination of key fields (---).", + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "type": "object" + }, + "handler.AppInstKey": { + "properties": { + "cloudlet_key": { + "$ref": "#/definitions/handler.CloudletKey" + }, + "name": { + "type": "string" + }, + "organization": { + "type": "string" + } + }, + "type": "object" + }, + "handler.AppInstState": { + "enum": [ + "Ready" + ], + "type": "string", + "x-enum-varnames": [ + "AppInstStateReady" + ] + }, + "handler.AppKey": { + "description": "is a unique identifier.", + "properties": { + "name": { + "type": "string" + }, + "organization": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "handler.CloudletKey": { + "properties": { + "name": { + "type": "string" + }, + "organization": { + "type": "string" + } + }, + "type": "object" + }, + "handler.CloudletLoc": { + "properties": { + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + } + }, + "type": "object" + }, + "handler.DeploymentType": { + "enum": [ + "kubernetes" + ], + "type": "string", + "x-enum-varnames": [ + "DeploymentTypeKubernetes" + ] + }, + "handler.Flavor": { + "description": "is a default configuration is applied to app if no configuration\nis provided (e.g. in serverless config). Configuration can be checked at /auth/ctrl/CreateApp", + "properties": { + "name": { + "$ref": "#/definitions/handler.FlavorName" + } + }, + "type": "object" + }, + "handler.FlavorName": { + "enum": [ + "EU.small", + "EU.medium", + "EU.big", + "EU.large" + ], + "type": "string", + "x-enum-varnames": [ + "DefaultFlavorNameEUSmall", + "DefaultFlavorNameEUMedium", + "DefaultFlavorNameEUBig", + "DefaultFlavorNameEULarge" + ] + }, + "handler.ImageType": { + "enum": [ + "docker" + ], + "type": "string", + "x-enum-varnames": [ + "ImageTypeDocker" + ] + }, + "handler.Region": { + "enum": [ + "EU" + ], + "type": "string", + "x-enum-varnames": [ + "RegionEU" + ] + }, + "handler.RequestCreateApp": { + "description": "necessary App details to create an entity.", + "properties": { + "app": { + "properties": { + "access_ports": { + "description": "Application port to be exposed with ingress in format .\nNecessary only when manifest is generated automatically. Otherwise,\nall the ports has to be set up manually in YAML manifest.", + "example": "tcp:80", + "type": "string" + }, + "allow_serverless": { + "type": "boolean" + }, + "defaultFlavor": { + "allOf": [ + { + "$ref": "#/definitions/handler.Flavor" + } + ], + "description": "Default resource config to be used." + }, + "deployment": { + "allOf": [ + { + "$ref": "#/definitions/handler.DeploymentType" + } + ], + "example": "kubernetes" + }, + "deployment_generator": { + "description": "Technical field. Required for providing custom manifest", + "type": "string" + }, + "deployment_manifest": { + "description": "Kubernetes manifest. ACCEPTS ONLY DEPLOYMENTS AND SERVICES.", + "type": "string" + }, + "image_path": { + "description": "Docker registry URL (e.g. docker.io/library/nginx:latest)", + "type": "string" + }, + "image_type": { + "$ref": "#/definitions/handler.ImageType" + }, + "key": { + "$ref": "#/definitions/handler.AppKey" + }, + "serverless_config": { + "$ref": "#/definitions/handler.ServerlessConfig" + } + }, + "type": "object" + }, + "region": { + "allOf": [ + { + "$ref": "#/definitions/handler.Region" + } + ], + "description": "Region to create instance at.", + "example": "EU" + } + }, + "type": "object", + "example": { + "appWithoutManifest": { + "region": "EU", + "app": { + "key": { + "organization": "DeveloperOrg", + "name": "test-app-without-manifest", + "version": "1.0" + }, + "access_ports": "tcp:80", + "serverless_config": {}, + "deployment": "kubernetes", + "image_type": "Docker", + "image_path": "docker.io/library/nginx:latest", + "allow_serverless": true, + "defaultFlavor": { + "name": "EU.small" + } + } + }, + "appWitManifest": { + "region": "EU", + "app": { + "key": { + "organization": "DeveloperOrg", + "name": "test-app-without-manifest", + "version": "1.0" + }, + "serverless_config": {}, + "deployment": "kubernetes", + "image_type": "Docker", + "image_path": "docker.io/library/nginx:latest", + "allow_serverless": true, + "defaultFlavor": { + "name": "EU.small" + }, + "deployment_manifest": "apiVersion: v1\nkind: Service\nmetadata:\n name: example-app-tcp\n labels:\n run: example-app\nspec:\n type: LoadBalancer\n ports:\n - name: tcp8080\n protocol: TCP\n port: 80\n targetPort: 80\n selector:\n run: example-app\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: example-app-deployment\nspec:\n replicas: 1\n selector:\n matchLabels:\n run: example-app\n template:\n metadata:\n labels:\n run: example-app\n mexDeployGen: kubernetes-basic\n spec:\n volumes:\n imagePullSecrets:\n - name: mtr.devops.telekom.de\n containers:\n - name: example-app\n image: docker.io/library/nginx:latest\n imagePullPolicy: Always\n ports:\n - containerPort: 80\n protocol: TCP\n", + "deployment_generator": "kubernetes-basic" + } + } + } + }, + "handler.RequestCreateAppInst": { + "properties": { + "appinst": { + "properties": { + "app_key": { + "$ref": "#/definitions/handler.AppKey" + }, + "key": { + "$ref": "#/definitions/handler.AppInstKey" + } + }, + "type": "object" + }, + "region": { + "allOf": [ + { + "$ref": "#/definitions/handler.Region" + } + ], + "description": "Region to create instance at.", + "example": "EU" + } + }, + "type": "object" + }, + "handler.RequestCreateAppInstMessage": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + }, + "handler.RequestDeleteApp": { + "properties": { + "key": { + "$ref": "#/definitions/handler.AppKey" + }, + "region": { + "allOf": [ + { + "$ref": "#/definitions/handler.Region" + } + ], + "example": "EU" + } + }, + "type": "object" + }, + "handler.RequestDeleteAppInst": { + "properties": { + "key": { + "$ref": "#/definitions/handler.AppInstKey" + } + }, + "type": "object" + }, + "handler.RequestShowApp": { + "properties": { + "access_ports": { + "description": "Application port to be exposed with ingress. Necessary only when\nmanifest is generated automatically. Otherwise, all the ports has to be\nset up manually in YAML manifest", + "example": "tcp:80", + "type": "string" + }, + "defaultFlavor": { + "allOf": [ + { + "$ref": "#/definitions/handler.Flavor" + } + ], + "description": "Default resource config to be used." + }, + "deployment": { + "$ref": "#/definitions/handler.DeploymentType" + }, + "image_path": { + "description": "Docker registry URL (e.g. docker.io/library/nginx:latest)", + "type": "string" + }, + "image_type": { + "$ref": "#/definitions/handler.ImageType" + }, + "key": { + "$ref": "#/definitions/handler.AppKey" + }, + "region": { + "allOf": [ + { + "$ref": "#/definitions/handler.Region" + } + ], + "example": "EU" + } + }, + "type": "object" + }, + "handler.RequestShowAppInst": { + "properties": { + "app_key": { + "$ref": "#/definitions/handler.AppKey" + }, + "key": { + "$ref": "#/definitions/handler.AppInstKey" + }, + "region": { + "allOf": [ + { + "$ref": "#/definitions/handler.Region" + } + ], + "description": "Region to create instance at.", + "example": "EU" + }, + "state": { + "$ref": "#/definitions/handler.AppInstState" + } + }, + "type": "object" + }, + "handler.RequestUpdateApp": { + "properties": { + "access_ports": { + "description": "Can be updated only if manifest is generated by EdgeXR.", + "example": "tcp:80", + "type": "string" + }, + "defaultFlavor": { + "$ref": "#/definitions/handler.Flavor" + }, + "image_path": { + "description": "Docker registry URL (e.g. docker.io/library/nginx:latest)", + "type": "string" + }, + "key": { + "allOf": [ + { + "$ref": "#/definitions/handler.AppKey" + } + ], + "description": "Immutable." + }, + "region": { + "allOf": [ + { + "$ref": "#/definitions/handler.Region" + } + ], + "description": "Immutable.", + "example": "EU" + } + }, + "type": "object" + }, + "handler.RequestUpdateAppInst": { + "properties": { + "flavor": { + "$ref": "#/definitions/handler.Flavor" + }, + "key": { + "$ref": "#/definitions/handler.AppInstKey" + } + }, + "type": "object" + }, + "handler.ServerlessConfig": { + "description": "is a default configuration is applied to app if no configuration\nis provided (e.g. in serverless config). Configuration can be checked at /auth/ctrl/CreateApp", + "properties": { + "min_replicas": { + "description": "number of replicas (at least 1).", + "type": "integer" + }, + "ram": { + "description": "RAM in MB.", + "type": "integer" + }, + "vcpus": { + "description": "Virtual CPUs.", + "type": "integer" + } + }, + "type": "object" + } + }, + "externalDocs": { + "description": "OpenAPI", + "url": "https://swagger.io/resources/open-api/" + }, "info": { - "description": "# Introduction\nThe Master Controller (MC) serves as the central gateway for orchestrating edge applications and provides several services to both application developers and operators. For application developers, these APIs allow the management and monitoring of deployments for edge applications. For infrastructure operators, these APIs provide ways to manage and monitor the usage of cloudlet infrastructures. Both developers and operators can take advantage of these APIS to manage users within the Organization.\n\nYou can leverage these functionalities and services on our easy-to-use MobiledgeX Console. If you prefer to manage these services programmatically, the available APIs and their resources are accessible from the left navigational menu.", - "title": "Master Controller (MC) API Documentation", + "contact": {}, + "description": "# Introduction\n The Master Controller (MC) serves as the central\ngateway for orchestrating edge applications and provides several services to both\napplication developers and operators. For application developers, these APIs allow\nthe management and monitoring of deployments for edge applications. For infrastructure\noperators, these APIs provide ways to manage and monitor the usage of cloudlet\ninfrastructures. Both developers and operators can take advantage of these APIS\nto manage users within the Organization. ## Important note.\n API can return more\nfields than provided in the specification. Specification is a main source of truth.", + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "title": "Edge Connect API", "version": "2.0" }, - "basePath": "/api/v1", "paths": { - "/auth/alertreceiver/create": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Create Alert Receiver\nCreate alert receiver.", - "tags": ["AlertReceiver"], - "operationId": "CreateAlertReceiver", - "parameters": [ - { - "name": "Body", - "in": "body", + "/": { + "get": { + "description": "Returns OK when server is set-up and running.", + "responses": { + "200": { + "description": "OK\" \"ok", "schema": { - "$ref": "#/definitions/AlertReceiver" + "type": "string" } } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/alertreceiver/delete": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Delete Alert Receiver\nDelete alert receiver.", - "tags": ["AlertReceiver"], - "operationId": "DeleteAlertReceiver", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/AlertReceiver" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/alertreceiver/show": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Show Alert Receiver\nShow alert receiver.", - "tags": ["AlertReceiver"], - "operationId": "ShowAlertReceiver", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/AlertReceiver" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/billingorg/addchild": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Adds an Organization to an existing parent BillingOrganization.", - "tags": ["BillingOrganization"], - "summary": "Add Child to BillingOrganization", - "operationId": "AddChildOrg", - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/billingorg/delete": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Deletes an existing BillingOrganization.", - "tags": ["BillingOrganization"], - "summary": "Delete BillingOrganization", - "operationId": "DeleteBillingOrg", - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/billingorg/removechild": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Removes an Organization from an existing parent BillingOrganization.", - "tags": ["BillingOrganization"], - "summary": "Remove Child from BillingOrganization", - "operationId": "RemoveChildOrg", - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/billingorg/show": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Displays existing BillingOrganizations in which you are authorized to access.", - "tags": ["BillingOrganization"], - "summary": "Show BillingOrganizations", - "operationId": "ShowBillingOrg", - "responses": { - "200": { - "$ref": "#/responses/listBillingOrgs" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/billingorg/update": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "API to update an existing BillingOrganization.", - "tags": ["BillingOrganization"], - "summary": "Update BillingOrganization", - "operationId": "UpdateBillingOrg", - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/AccessCloudlet": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["ExecRequest"], - "summary": "Access Cloudlet VM", - "operationId": "AccessCloudlet", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionExecRequest" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/AddAppAlertPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AppAlertPolicy"], - "summary": "Add an AlertPolicy to the App", - "operationId": "AddAppAlertPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAppAlertPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/AddAppAutoProvPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AppAutoProvPolicy"], - "summary": "Add an AutoProvPolicy to the App", - "operationId": "AddAppAutoProvPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAppAutoProvPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/AddAutoProvPolicyCloudlet": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AutoProvPolicyCloudlet"], - "summary": "Add a Cloudlet to the Auto Provisioning Policy", - "operationId": "AddAutoProvPolicyCloudlet", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAutoProvPolicyCloudlet" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/AddCloudletAllianceOrg": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletAllianceOrg"], - "summary": "Add alliance organization to the cloudlet", - "operationId": "AddCloudletAllianceOrg", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletAllianceOrg" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/AddCloudletPoolMember": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletPoolMember"], - "summary": "Add a Cloudlet to a CloudletPool", - "operationId": "AddCloudletPoolMember", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletPoolMember" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/AddCloudletResMapping": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletResMap"], - "summary": "Add Optional Resource tag table", - "operationId": "AddCloudletResMapping", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletResMap" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/AddFlavorRes": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Flavor"], - "summary": "Add Optional Resource", - "operationId": "AddFlavorRes", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionFlavor" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/AddGPUDriverBuild": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Adds new build to GPU driver.", - "tags": ["GPUDriverBuildMember"], - "summary": "Add GPU Driver Build", - "operationId": "AddGPUDriverBuild", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionGPUDriverBuildMember" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/AddResTag": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["ResTagTable"], - "summary": "Add new tag(s) to TagTable", - "operationId": "AddResTag", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionResTagTable" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/AddVMPoolMember": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Adds a VM to existing VM Pool.", - "tags": ["VMPoolMember"], - "summary": "Add VMPoolMember", - "operationId": "AddVMPoolMember", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionVMPoolMember" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateAlertPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AlertPolicy"], - "summary": "Create an Alert Policy", - "operationId": "CreateAlertPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAlertPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } + }, + "summary": "Show server is running", + "tags": [ + "monitoring" + ] } }, "/auth/ctrl/CreateApp": { "post": { - "security": [ - { - "Bearer": [] - } + "consumes": [ + "application/json" ], - "description": "Creates a definition for an application instance for Cloudlet deployment.", - "tags": ["App"], - "summary": "Create Application", - "operationId": "CreateApp", + "description": "Creates App specification, validating of the params. Please, read\ndescription of the fields since not every one is required in every configuration.", "parameters": [ { - "name": "Body", + "description": "body", "in": "body", + "name": "_", + "required": true, "schema": { - "$ref": "#/definitions/RegionApp" + "$ref": "#/definitions/handler.RequestCreateApp" } } ], + "produces": [ + "application/json" + ], "responses": { "200": { - "$ref": "#/responses/success" + "description": "OK" }, "400": { - "$ref": "#/responses/badRequest" + "description": "Bad Request" }, "403": { - "$ref": "#/responses/forbidden" + "description": "Forbidden" }, "404": { - "$ref": "#/responses/notFound" + "description": "Not Found" } - } + }, + "summary": "Creates App specification.", + "tags": [ + "App" + ] } }, "/auth/ctrl/CreateAppInst": { "post": { - "security": [ - { - "Bearer": [] - } + "consumes": [ + "application/json" ], - "description": "Creates an instance of an App on a Cloudlet where it is defined by an App plus a ClusterInst key. Many of the fields here are inherited from the App definition.", - "tags": ["AppInst"], - "summary": "Create Application Instance", - "operationId": "CreateAppInst", + "description": "Create App instance on the cloudlet. Requests can be done as web\nsocket or regular http request returning all the steps as array of json messages.", "parameters": [ { - "name": "Body", + "description": "body", "in": "body", + "name": "_", + "required": true, "schema": { - "$ref": "#/definitions/RegionAppInst" + "$ref": "#/definitions/handler.RequestCreateAppInst" } } ], + "produces": [ + "application/json" + ], "responses": { "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateAutoProvPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AutoProvPolicy"], - "summary": "Create an Auto Provisioning Policy", - "operationId": "CreateAutoProvPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", + "description": "OK", "schema": { - "$ref": "#/definitions/RegionAutoProvPolicy" + "items": { + "$ref": "#/definitions/handler.RequestCreateAppInstMessage" + }, + "type": "array" } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" }, "400": { - "$ref": "#/responses/badRequest" + "description": "Bad Request" }, "403": { - "$ref": "#/responses/forbidden" + "description": "Forbidden" }, "404": { - "$ref": "#/responses/notFound" + "description": "Not Found" } - } - } - }, - "/auth/ctrl/CreateAutoScalePolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AutoScalePolicy"], - "summary": "Create an Auto Scale Policy", - "operationId": "CreateAutoScalePolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAutoScalePolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateCloudlet": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Sets up Cloudlet services on the Operators compute resources, and integrated as part of EdgeCloud edge resource portfolio. These resources are managed from the Edge Controller.", - "tags": ["Cloudlet"], - "summary": "Create Cloudlet", - "operationId": "CreateCloudlet", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudlet" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateCloudletPool": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletPool"], - "summary": "Create a CloudletPool", - "operationId": "CreateCloudletPool", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletPool" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateClusterInst": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Creates an instance of a Cluster on a Cloudlet, defined by a Cluster Key and a Cloudlet Key. ClusterInst is a collection of compute resources on a Cloudlet on which AppInsts are deployed.", - "tags": ["ClusterInst"], - "summary": "Create Cluster Instance", - "operationId": "CreateClusterInst", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionClusterInst" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateFlavor": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Flavor"], - "summary": "Create a Flavor", - "operationId": "CreateFlavor", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionFlavor" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateFlowRateLimitSettings": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["FlowRateLimitSettings"], - "summary": "Create Flow RateLimit settings for an API endpoint and target", - "operationId": "CreateFlowRateLimitSettings", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionFlowRateLimitSettings" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateGPUDriver": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Creates GPU driver with all the config required to install it.", - "tags": ["GPUDriver"], - "summary": "Create GPU Driver", - "operationId": "CreateGPUDriver", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionGPUDriver" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateMaxReqsRateLimitSettings": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["MaxReqsRateLimitSettings"], - "summary": "Create MaxReqs RateLimit settings for an API endpoint and target", - "operationId": "CreateMaxReqsRateLimitSettings", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionMaxReqsRateLimitSettings" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateNetwork": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Network"], - "summary": "Create a Network", - "operationId": "CreateNetwork", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionNetwork" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateOperatorCode": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Create a code for an Operator.", - "tags": ["OperatorCode"], - "summary": "Create Operator Code", - "operationId": "CreateOperatorCode", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionOperatorCode" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateResTagTable": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["ResTagTable"], - "summary": "Create TagTable", - "operationId": "CreateResTagTable", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionResTagTable" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateTrustPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["TrustPolicy"], - "summary": "Create a Trust Policy", - "operationId": "CreateTrustPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionTrustPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateTrustPolicyException": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["TrustPolicyException"], - "summary": "Create a Trust Policy Exception, by App Developer Organization", - "operationId": "CreateTrustPolicyException", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionTrustPolicyException" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/CreateVMPool": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Creates VM pool which will have VMs defined.", - "tags": ["VMPool"], - "summary": "Create VMPool", - "operationId": "CreateVMPool", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionVMPool" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteAlertPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AlertPolicy"], - "summary": "Delete an Alert Policy", - "operationId": "DeleteAlertPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAlertPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } + }, + "summary": "Create app instance.", + "tags": [ + "AppInst" + ] } }, "/auth/ctrl/DeleteApp": { "post": { - "security": [ - { - "Bearer": [] - } + "consumes": [ + "application/json" ], - "description": "Deletes a definition of an Application instance. Make sure no other application instances exist with that definition. If they do exist, you must delete those Application instances first.", - "tags": ["App"], - "summary": "Delete Application", - "operationId": "DeleteApp", + "description": "Update app specification with limitation to the key.", "parameters": [ { - "name": "Body", + "description": "body", "in": "body", + "name": "_", + "required": true, "schema": { - "$ref": "#/definitions/RegionApp" + "$ref": "#/definitions/handler.RequestDeleteApp" } } ], + "produces": [ + "application/json" + ], "responses": { "200": { - "$ref": "#/responses/success" + "description": "OK" }, "400": { - "$ref": "#/responses/badRequest" + "description": "Bad Request" }, "403": { - "$ref": "#/responses/forbidden" + "description": "Forbidden" }, "404": { - "$ref": "#/responses/notFound" + "description": "Not Found" } - } + }, + "summary": "Update app specs.", + "tags": [ + "App" + ] } }, "/auth/ctrl/DeleteAppInst": { "post": { - "security": [ - { - "Bearer": [] - } + "consumes": [ + "application/json" ], - "description": "Deletes an instance of the App from the Cloudlet.", - "tags": ["AppInst"], - "summary": "Delete Application Instance", - "operationId": "DeleteAppInst", + "description": "Deletes app instance at the specified cloudlet.", "parameters": [ { - "name": "Body", + "description": "body", "in": "body", + "name": "_", + "required": true, "schema": { - "$ref": "#/definitions/RegionAppInst" + "$ref": "#/definitions/handler.RequestDeleteAppInst" } } ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteAutoProvPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AutoProvPolicy"], - "summary": "Delete an Auto Provisioning Policy", - "operationId": "DeleteAutoProvPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAutoProvPolicy" - } - } + "produces": [ + "application/json" ], "responses": { "200": { - "$ref": "#/responses/success" + "description": "OK" }, "400": { - "$ref": "#/responses/badRequest" + "description": "Bad Request" }, "403": { - "$ref": "#/responses/forbidden" + "description": "Forbidden" }, "404": { - "$ref": "#/responses/notFound" + "description": "Not Found" } - } - } - }, - "/auth/ctrl/DeleteAutoScalePolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AutoScalePolicy"], - "summary": "Delete an Auto Scale Policy", - "operationId": "DeleteAutoScalePolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAutoScalePolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteCloudlet": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Removes the Cloudlet services where they are no longer managed from the Edge Controller.", - "tags": ["Cloudlet"], - "summary": "Delete Cloudlet", - "operationId": "DeleteCloudlet", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudlet" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteCloudletPool": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletPool"], - "summary": "Delete a CloudletPool", - "operationId": "DeleteCloudletPool", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletPool" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteClusterInst": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Deletes an instance of a Cluster deployed on a Cloudlet.", - "tags": ["ClusterInst"], - "summary": "Delete Cluster Instance", - "operationId": "DeleteClusterInst", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionClusterInst" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteFlavor": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Flavor"], - "summary": "Delete a Flavor", - "operationId": "DeleteFlavor", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionFlavor" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteFlowRateLimitSettings": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["FlowRateLimitSettings"], - "summary": "Delete Flow RateLimit settings for an API endpoint and target", - "operationId": "DeleteFlowRateLimitSettings", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionFlowRateLimitSettings" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteGPUDriver": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Deletes GPU driver given that it is not used by any cloudlet.", - "tags": ["GPUDriver"], - "summary": "Delete GPU Driver", - "operationId": "DeleteGPUDriver", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionGPUDriver" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteIdleReservableClusterInsts": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Deletes reservable cluster instances that are not in use.", - "tags": ["IdleReservableClusterInsts"], - "summary": "Cleanup Reservable Cluster Instances", - "operationId": "DeleteIdleReservableClusterInsts", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionIdleReservableClusterInsts" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteMaxReqsRateLimitSettings": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["MaxReqsRateLimitSettings"], - "summary": "Delete MaxReqs RateLimit settings for an API endpoint and target", - "operationId": "DeleteMaxReqsRateLimitSettings", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionMaxReqsRateLimitSettings" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteNetwork": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Network"], - "summary": "Delete a Network", - "operationId": "DeleteNetwork", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionNetwork" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteOperatorCode": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Delete a code for an Operator.", - "tags": ["OperatorCode"], - "summary": "Delete Operator Code", - "operationId": "DeleteOperatorCode", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionOperatorCode" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteResTagTable": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["ResTagTable"], - "summary": "Delete TagTable", - "operationId": "DeleteResTagTable", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionResTagTable" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteTrustPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["TrustPolicy"], - "summary": "Delete a Trust policy", - "operationId": "DeleteTrustPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionTrustPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteTrustPolicyException": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["TrustPolicyException"], - "summary": "Delete a Trust Policy Exception, by App Developer Organization", - "operationId": "DeleteTrustPolicyException", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionTrustPolicyException" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DeleteVMPool": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Deletes VM pool given that none of VMs part of this pool is used.", - "tags": ["VMPool"], - "summary": "Delete VMPool", - "operationId": "DeleteVMPool", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionVMPool" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/DisableDebugLevels": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["DebugRequest"], - "summary": "Disable debug log levels", - "operationId": "DisableDebugLevels", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionDebugRequest" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/EnableDebugLevels": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["DebugRequest"], - "summary": "Enable debug log levels", - "operationId": "EnableDebugLevels", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionDebugRequest" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/EvictCloudletInfo": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletInfo"], - "summary": "Evict (delete) a CloudletInfo for regression testing", - "operationId": "EvictCloudletInfo", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletInfo" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/EvictDevice": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Device"], - "summary": "Evict a device", - "operationId": "EvictDevice", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionDevice" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/FindFlavorMatch": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["FlavorMatch"], - "summary": "Discover if flavor produces a matching platform flavor", - "operationId": "FindFlavorMatch", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionFlavorMatch" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/GenerateAccessKey": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletKey"], - "summary": "Generate new crm access key", - "operationId": "GenerateAccessKey", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletKey" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/GetCloudletGPUDriverLicenseConfig": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Returns the license config associated with the cloudlet", - "tags": ["CloudletKey"], - "summary": "Get Cloudlet Specific GPU Driver License Config", - "operationId": "GetCloudletGPUDriverLicenseConfig", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletKey" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/GetCloudletManifest": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Shows deployment manifest required to setup cloudlet", - "tags": ["CloudletKey"], - "summary": "Get Cloudlet Manifest", - "operationId": "GetCloudletManifest", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletKey" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/GetCloudletProps": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Shows all the infra properties used to setup cloudlet", - "tags": ["CloudletProps"], - "summary": "Get Cloudlet Properties", - "operationId": "GetCloudletProps", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletProps" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/GetCloudletResourceQuotaProps": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Shows all the resource quota properties of the cloudlet", - "tags": ["CloudletResourceQuotaProps"], - "summary": "Get Cloudlet Resource Quota Properties", - "operationId": "GetCloudletResourceQuotaProps", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletResourceQuotaProps" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/GetCloudletResourceUsage": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Shows cloudlet resources used and their limits", - "tags": ["CloudletResourceUsage"], - "summary": "Get Cloudlet resource information", - "operationId": "GetCloudletResourceUsage", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletResourceUsage" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/GetGPUDriverBuildURL": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Returns a time-limited signed URL to download GPU driver.", - "tags": ["GPUDriverBuildMember"], - "summary": "Get GPU Driver Build URL", - "operationId": "GetGPUDriverBuildURL", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionGPUDriverBuildMember" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/GetGPUDriverLicenseConfig": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Returns the license config specific to GPU driver", - "tags": ["GPUDriverKey"], - "summary": "Get GPU Driver License Config", - "operationId": "GetGPUDriverLicenseConfig", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionGPUDriverKey" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/GetOrganizationsOnCloudlet": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletKey"], - "summary": "Get organizations of ClusterInsts and AppInsts on cloudlet", - "operationId": "GetOrganizationsOnCloudlet", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletKey" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/GetResTagTable": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["ResTagTableKey"], - "summary": "Fetch a copy of the TagTable", - "operationId": "GetResTagTable", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionResTagTableKey" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/InjectCloudletInfo": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletInfo"], - "summary": "Inject (create) a CloudletInfo for regression testing", - "operationId": "InjectCloudletInfo", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletInfo" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/InjectDevice": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Device"], - "summary": "Inject a device", - "operationId": "InjectDevice", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionDevice" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RefreshAppInst": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Restarts an App instance with new App settings or image.", - "tags": ["AppInst"], - "summary": "Refresh Application Instance", - "operationId": "RefreshAppInst", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAppInst" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RemoveAppAlertPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AppAlertPolicy"], - "summary": "Remove an AlertPolicy from the App", - "operationId": "RemoveAppAlertPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAppAlertPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RemoveAppAutoProvPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AppAutoProvPolicy"], - "summary": "Remove an AutoProvPolicy from the App", - "operationId": "RemoveAppAutoProvPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAppAutoProvPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RemoveAutoProvPolicyCloudlet": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AutoProvPolicyCloudlet"], - "summary": "Remove a Cloudlet from the Auto Provisioning Policy", - "operationId": "RemoveAutoProvPolicyCloudlet", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAutoProvPolicyCloudlet" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RemoveCloudletAllianceOrg": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletAllianceOrg"], - "summary": "Remove alliance organization from the cloudlet", - "operationId": "RemoveCloudletAllianceOrg", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletAllianceOrg" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RemoveCloudletPoolMember": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletPoolMember"], - "summary": "Remove a Cloudlet from a CloudletPool", - "operationId": "RemoveCloudletPoolMember", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletPoolMember" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RemoveCloudletResMapping": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletResMap"], - "summary": "Remove Optional Resource tag table", - "operationId": "RemoveCloudletResMapping", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletResMap" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RemoveFlavorRes": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Flavor"], - "summary": "Remove Optional Resource", - "operationId": "RemoveFlavorRes", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionFlavor" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RemoveGPUDriverBuild": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Removes build from GPU driver.", - "tags": ["GPUDriverBuildMember"], - "summary": "Remove GPU Driver Build", - "operationId": "RemoveGPUDriverBuild", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionGPUDriverBuildMember" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RemoveResTag": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["ResTagTable"], - "summary": "Remove existing tag(s) from TagTable", - "operationId": "RemoveResTag", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionResTagTable" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RemoveVMPoolMember": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Removes a VM from existing VM Pool.", - "tags": ["VMPoolMember"], - "summary": "Remove VMPoolMember", - "operationId": "RemoveVMPoolMember", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionVMPoolMember" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RequestAppInstLatency": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AppInstLatency"], - "summary": "Request Latency measurements for clients connected to AppInst", - "operationId": "RequestAppInstLatency", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAppInstLatency" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ResetSettings": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Settings"], - "summary": "Reset all settings to their defaults", - "operationId": "ResetSettings", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionSettings" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RevokeAccessKey": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletKey"], - "summary": "Revoke crm access key", - "operationId": "RevokeAccessKey", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletKey" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RunCommand": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["ExecRequest"], - "summary": "Run a Command or Shell on a container", - "operationId": "RunCommand", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionExecRequest" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RunConsole": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["ExecRequest"], - "summary": "Run console on a VM", - "operationId": "RunConsole", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionExecRequest" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/RunDebug": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["DebugRequest"], - "summary": "Run debug command", - "operationId": "RunDebug", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionDebugRequest" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowAlert": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Alert"], - "summary": "Show alerts", - "operationId": "ShowAlert", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAlert" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowAlertPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Any fields specified will be used to filter results.", - "tags": ["AlertPolicy"], - "summary": "Show Alert Policies", - "operationId": "ShowAlertPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAlertPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } + }, + "summary": "Deletes app instance by key.", + "tags": [ + "AppInst" + ] } }, "/auth/ctrl/ShowApp": { "post": { - "security": [ - { - "Bearer": [] - } + "consumes": [ + "application/json" ], - "description": "Lists all Application definitions managed from the Edge Controller. Any fields specified will be used to filter results.", - "tags": ["App"], - "summary": "Show Applications", - "operationId": "ShowApp", + "description": "returns app specifications for provided region. Filter is done\nwith providing app fields from the model.", "parameters": [ { - "name": "Body", + "description": "body", "in": "body", + "name": "_", + "required": true, "schema": { - "$ref": "#/definitions/RegionApp" + "$ref": "#/definitions/handler.RequestShowApp" } } ], + "produces": [ + "application/json" + ], "responses": { "200": { - "$ref": "#/responses/success" + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/handler.App" + }, + "type": "array" + } }, "400": { - "$ref": "#/responses/badRequest" + "description": "Bad Request" }, "403": { - "$ref": "#/responses/forbidden" + "description": "Forbidden" }, "404": { - "$ref": "#/responses/notFound" + "description": "Not Found" } - } + }, + "summary": "Shows list of apps for the region.", + "tags": [ + "App" + ] } }, "/auth/ctrl/ShowAppInst": { "post": { - "security": [ - { - "Bearer": [] - } + "consumes": [ + "application/json" ], - "description": "Lists all the Application instances managed by the Edge Controller. Any fields specified will be used to filter results.", - "tags": ["AppInst"], - "summary": "Show Application Instances", - "operationId": "ShowAppInst", + "description": "Returns app instances for provided region. Filter is done with\nproviding app instances fields from the model.", "parameters": [ { - "name": "Body", + "description": "body", "in": "body", + "name": "_", + "required": true, "schema": { - "$ref": "#/definitions/RegionAppInst" + "$ref": "#/definitions/handler.RequestShowAppInst" } } ], + "produces": [ + "application/json" + ], "responses": { "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowAppInstClient": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AppInstClientKey"], - "summary": "Show application instance clients", - "operationId": "ShowAppInstClient", - "parameters": [ - { - "name": "Body", - "in": "body", + "description": "OK", "schema": { - "$ref": "#/definitions/RegionAppInstClientKey" + "items": { + "$ref": "#/definitions/handler.AppInst" + }, + "type": "array" } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" }, "400": { - "$ref": "#/responses/badRequest" + "description": "Bad Request" }, "403": { - "$ref": "#/responses/forbidden" + "description": "Forbidden" }, "404": { - "$ref": "#/responses/notFound" + "description": "Not Found" } - } - } - }, - "/auth/ctrl/ShowAppInstRefs": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AppInstRefs"], - "summary": "Show AppInstRefs (debug only)", - "operationId": "ShowAppInstRefs", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAppInstRefs" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowAutoProvPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Any fields specified will be used to filter results.", - "tags": ["AutoProvPolicy"], - "summary": "Show Auto Provisioning Policies", - "operationId": "ShowAutoProvPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAutoProvPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowAutoScalePolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Any fields specified will be used to filter results.", - "tags": ["AutoScalePolicy"], - "summary": "Show Auto Scale Policies", - "operationId": "ShowAutoScalePolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAutoScalePolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowCloudlet": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Lists all the cloudlets managed from Edge Controller.", - "tags": ["Cloudlet"], - "summary": "Show Cloudlets", - "operationId": "ShowCloudlet", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudlet" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowCloudletInfo": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletInfo"], - "summary": "Show CloudletInfos", - "operationId": "ShowCloudletInfo", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletInfo" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowCloudletPool": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletPool"], - "summary": "Show CloudletPools", - "operationId": "ShowCloudletPool", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletPool" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowCloudletRefs": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletRefs"], - "summary": "Show CloudletRefs (debug only)", - "operationId": "ShowCloudletRefs", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletRefs" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowCloudletsForAppDeployment": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "DefaultFlavor", - "tags": ["DeploymentCloudletRequest"], - "summary": "Discover cloudlets supporting deployments of App", - "operationId": "ShowCloudletsForAppDeployment", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionDeploymentCloudletRequest" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowClusterInst": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Lists all the cluster instances managed by Edge Controller.", - "tags": ["ClusterInst"], - "summary": "Show Cluster Instances", - "operationId": "ShowClusterInst", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionClusterInst" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowClusterRefs": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["ClusterRefs"], - "summary": "Show ClusterRefs (debug only)", - "operationId": "ShowClusterRefs", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionClusterRefs" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowDebugLevels": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["DebugRequest"], - "summary": "Show debug log levels", - "operationId": "ShowDebugLevels", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionDebugRequest" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowDevice": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Device"], - "summary": "Show devices", - "operationId": "ShowDevice", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionDevice" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowDeviceReport": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["DeviceReport"], - "summary": "Device Reports API", - "operationId": "ShowDeviceReport", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionDeviceReport" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowFlavor": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Flavor"], - "summary": "Show Flavors", - "operationId": "ShowFlavor", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionFlavor" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowFlavorsForCloudlet": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletKey"], - "summary": "Find all meta flavors viable on cloudlet", - "operationId": "ShowFlavorsForCloudlet", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletKey" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowFlowRateLimitSettings": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["FlowRateLimitSettings"], - "summary": "Show Flow RateLimit settings for an API endpoint and target", - "operationId": "ShowFlowRateLimitSettings", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionFlowRateLimitSettings" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowGPUDriver": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Lists all the EdgeCloud created GPU drivers and operator created GPU drivers.", - "tags": ["GPUDriver"], - "summary": "Show GPU Drivers", - "operationId": "ShowGPUDriver", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionGPUDriver" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowLogs": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["ExecRequest"], - "summary": "View logs for AppInst", - "operationId": "ShowLogs", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionExecRequest" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowMaxReqsRateLimitSettings": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["MaxReqsRateLimitSettings"], - "summary": "Show MaxReqs RateLimit settings for an API endpoint and target", - "operationId": "ShowMaxReqsRateLimitSettings", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionMaxReqsRateLimitSettings" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowNetwork": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Any fields specified will be used to filter results.", - "tags": ["Network"], - "summary": "Show Networks", - "operationId": "ShowNetwork", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionNetwork" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowNode": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Node"], - "summary": "Show all Nodes connected to all Controllers", - "operationId": "ShowNode", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionNode" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowOperatorCode": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Show Codes for an Operator.", - "tags": ["OperatorCode"], - "summary": "Show Operator Code", - "operationId": "ShowOperatorCode", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionOperatorCode" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowRateLimitSettings": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["RateLimitSettings"], - "summary": "Show RateLimit settings for an API endpoint and target", - "operationId": "ShowRateLimitSettings", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionRateLimitSettings" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowResTagTable": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["ResTagTable"], - "summary": "Show TagTable", - "operationId": "ShowResTagTable", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionResTagTable" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowSettings": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["Settings"], - "summary": "Show settings", - "operationId": "ShowSettings", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionSettings" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowTrustPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Any fields specified will be used to filter results.", - "tags": ["TrustPolicy"], - "summary": "Show Trust Policies", - "operationId": "ShowTrustPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionTrustPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowTrustPolicyException": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Any fields specified will be used to filter results.", - "tags": ["TrustPolicyException"], - "summary": "Show Trust Policy Exceptions", - "operationId": "ShowTrustPolicyException", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionTrustPolicyException" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/ShowVMPool": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Lists all the VMs part of the VM pool.", - "tags": ["VMPool"], - "summary": "Show VMPools", - "operationId": "ShowVMPool", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionVMPool" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/StreamAppInst": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["AppInstKey"], - "summary": "Stream Application Instance current progress", - "operationId": "StreamAppInst", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAppInstKey" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/StreamCloudlet": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["CloudletKey"], - "summary": "Stream Cloudlet current progress", - "operationId": "StreamCloudlet", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletKey" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/StreamClusterInst": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["ClusterInstKey"], - "summary": "Stream Cluster Instance current progress", - "operationId": "StreamClusterInst", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionClusterInstKey" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/StreamGPUDriver": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "tags": ["GPUDriverKey"], - "summary": "Stream GPU driver current progress", - "operationId": "StreamGPUDriver", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionGPUDriverKey" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateAlertPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "The following values should be added to `AlertPolicy.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nCpuUtilizationLimit: 3\nMemUtilizationLimit: 4\nDiskUtilizationLimit: 5\nActiveConnLimit: 6\nSeverity: 7\nTriggerTime: 8\nLabels: 9\nLabelsKey: 9.1\nLabelsValue: 9.2\nAnnotations: 10\nAnnotationsKey: 10.1\nAnnotationsValue: 10.2\nDescription: 11\nDeletePrepare: 12\n```", - "tags": ["AlertPolicy"], - "summary": "Update an Alert Policy", - "operationId": "UpdateAlertPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAlertPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } + }, + "summary": "Shows list of app instances for the region.", + "tags": [ + "AppInst" + ] } }, "/auth/ctrl/UpdateApp": { "post": { - "security": [ - { - "Bearer": [] - } + "consumes": [ + "application/json" ], - "description": "Updates the definition of an Application instance.\nThe following values should be added to `App.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nKeyVersion: 2.3\nImagePath: 4\nImageType: 5\nAccessPorts: 7\nDefaultFlavor: 9\nDefaultFlavorName: 9.1\nAuthPublicKey: 12\nCommand: 13\nAnnotations: 14\nDeployment: 15\nDeploymentManifest: 16\nDeploymentGenerator: 17\nAndroidPackageName: 18\nDelOpt: 20\nConfigs: 21\nConfigsKind: 21.1\nConfigsConfig: 21.2\nScaleWithCluster: 22\nInternalPorts: 23\nRevision: 24\nOfficialFqdn: 25\nMd5Sum: 26\nAutoProvPolicy: 28\nAccessType: 29\nDeletePrepare: 31\nAutoProvPolicies: 32\nTemplateDelimiter: 33\nSkipHcPorts: 34\nCreatedAt: 35\nCreatedAtSeconds: 35.1\nCreatedAtNanos: 35.2\nUpdatedAt: 36\nUpdatedAtSeconds: 36.1\nUpdatedAtNanos: 36.2\nTrusted: 37\nRequiredOutboundConnections: 38\nRequiredOutboundConnectionsProtocol: 38.1\nRequiredOutboundConnectionsPortRangeMin: 38.2\nRequiredOutboundConnectionsPortRangeMax: 38.3\nRequiredOutboundConnectionsRemoteCidr: 38.4\nAllowServerless: 39\nServerlessConfig: 40\nServerlessConfigVcpus: 40.1\nServerlessConfigVcpusWhole: 40.1.1\nServerlessConfigVcpusNanos: 40.1.2\nServerlessConfigRam: 40.2\nServerlessConfigMinReplicas: 40.3\nVmAppOsType: 41\nAlertPolicies: 42\nQosSessionProfile: 43\nQosSessionDuration: 44\n```", - "tags": ["App"], - "summary": "Update Application", - "operationId": "UpdateApp", + "description": "Update app specification with limitation to the key.", "parameters": [ { - "name": "Body", + "description": "body", "in": "body", + "name": "_", + "required": true, "schema": { - "$ref": "#/definitions/RegionApp" + "$ref": "#/definitions/handler.RequestUpdateApp" } } ], + "produces": [ + "application/json" + ], "responses": { "200": { - "$ref": "#/responses/success" + "description": "OK" }, "400": { - "$ref": "#/responses/badRequest" + "description": "Bad Request" }, "403": { - "$ref": "#/responses/forbidden" + "description": "Forbidden" }, "404": { - "$ref": "#/responses/notFound" + "description": "Not Found" } - } + }, + "summary": "Update app specs.", + "tags": [ + "App" + ] } }, "/auth/ctrl/UpdateAppInst": { "post": { - "security": [ - { - "Bearer": [] - } + "consumes": [ + "application/json" ], - "description": "Updates an Application instance and then refreshes it.\nThe following values should be added to `AppInst.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyAppKey: 2.1\nKeyAppKeyOrganization: 2.1.1\nKeyAppKeyName: 2.1.2\nKeyAppKeyVersion: 2.1.3\nKeyClusterInstKey: 2.4\nKeyClusterInstKeyClusterKey: 2.4.1\nKeyClusterInstKeyClusterKeyName: 2.4.1.1\nKeyClusterInstKeyCloudletKey: 2.4.2\nKeyClusterInstKeyCloudletKeyOrganization: 2.4.2.1\nKeyClusterInstKeyCloudletKeyName: 2.4.2.2\nKeyClusterInstKeyCloudletKeyFederatedOrganization: 2.4.2.3\nKeyClusterInstKeyOrganization: 2.4.3\nCloudletLoc: 3\nCloudletLocLatitude: 3.1\nCloudletLocLongitude: 3.2\nCloudletLocHorizontalAccuracy: 3.3\nCloudletLocVerticalAccuracy: 3.4\nCloudletLocAltitude: 3.5\nCloudletLocCourse: 3.6\nCloudletLocSpeed: 3.7\nCloudletLocTimestamp: 3.8\nCloudletLocTimestampSeconds: 3.8.1\nCloudletLocTimestampNanos: 3.8.2\nUri: 4\nLiveness: 6\nMappedPorts: 9\nMappedPortsProto: 9.1\nMappedPortsInternalPort: 9.2\nMappedPortsPublicPort: 9.3\nMappedPortsFqdnPrefix: 9.5\nMappedPortsEndPort: 9.6\nMappedPortsTls: 9.7\nMappedPortsNginx: 9.8\nMappedPortsMaxPktSize: 9.9\nFlavor: 12\nFlavorName: 12.1\nState: 14\nErrors: 15\nCrmOverride: 16\nRuntimeInfo: 17\nRuntimeInfoContainerIds: 17.1\nCreatedAt: 21\nCreatedAtSeconds: 21.1\nCreatedAtNanos: 21.2\nAutoClusterIpAccess: 22\nRevision: 24\nForceUpdate: 25\nUpdateMultiple: 26\nConfigs: 27\nConfigsKind: 27.1\nConfigsConfig: 27.2\nHealthCheck: 29\nPowerState: 31\nExternalVolumeSize: 32\nAvailabilityZone: 33\nVmFlavor: 34\nOptRes: 35\nUpdatedAt: 36\nUpdatedAtSeconds: 36.1\nUpdatedAtNanos: 36.2\nRealClusterName: 37\nInternalPortToLbIp: 38\nInternalPortToLbIpKey: 38.1\nInternalPortToLbIpValue: 38.2\nDedicatedIp: 39\nUniqueId: 40\nDnsLabel: 41\n```", - "tags": ["AppInst"], - "summary": "Update Application Instance", - "operationId": "UpdateAppInst", + "description": "Update app instance by key with limited set of fields.", "parameters": [ { - "name": "Body", + "description": "body", "in": "body", + "name": "_", + "required": true, "schema": { - "$ref": "#/definitions/RegionAppInst" + "$ref": "#/definitions/handler.RequestUpdateAppInst" } } ], + "produces": [ + "application/json" + ], "responses": { "200": { - "$ref": "#/responses/success" + "description": "OK" }, "400": { - "$ref": "#/responses/badRequest" + "description": "Bad Request" }, "403": { - "$ref": "#/responses/forbidden" + "description": "Forbidden" }, "404": { - "$ref": "#/responses/notFound" + "description": "Not Found" } - } - } - }, - "/auth/ctrl/UpdateAutoProvPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "The following values should be added to `AutoProvPolicy.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nDeployClientCount: 3\nDeployIntervalCount: 4\nCloudlets: 5\nCloudletsKey: 5.1\nCloudletsKeyOrganization: 5.1.1\nCloudletsKeyName: 5.1.2\nCloudletsKeyFederatedOrganization: 5.1.3\nCloudletsLoc: 5.2\nCloudletsLocLatitude: 5.2.1\nCloudletsLocLongitude: 5.2.2\nCloudletsLocHorizontalAccuracy: 5.2.3\nCloudletsLocVerticalAccuracy: 5.2.4\nCloudletsLocAltitude: 5.2.5\nCloudletsLocCourse: 5.2.6\nCloudletsLocSpeed: 5.2.7\nCloudletsLocTimestamp: 5.2.8\nCloudletsLocTimestampSeconds: 5.2.8.1\nCloudletsLocTimestampNanos: 5.2.8.2\nMinActiveInstances: 6\nMaxInstances: 7\nUndeployClientCount: 8\nUndeployIntervalCount: 9\nDeletePrepare: 10\n```", - "tags": ["AutoProvPolicy"], - "summary": "Update an Auto Provisioning Policy", - "operationId": "UpdateAutoProvPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAutoProvPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateAutoScalePolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "The following values should be added to `AutoScalePolicy.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nMinNodes: 3\nMaxNodes: 4\nScaleUpCpuThresh: 5\nScaleDownCpuThresh: 6\nTriggerTimeSec: 7\nStabilizationWindowSec: 8\nTargetCpu: 9\nTargetMem: 10\nTargetActiveConnections: 11\nDeletePrepare: 12\n```", - "tags": ["AutoScalePolicy"], - "summary": "Update an Auto Scale Policy", - "operationId": "UpdateAutoScalePolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAutoScalePolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateCloudlet": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Updates the Cloudlet configuration and manages the upgrade of Cloudlet services.\nThe following values should be added to `Cloudlet.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nKeyFederatedOrganization: 2.3\nLocation: 5\nLocationLatitude: 5.1\nLocationLongitude: 5.2\nLocationHorizontalAccuracy: 5.3\nLocationVerticalAccuracy: 5.4\nLocationAltitude: 5.5\nLocationCourse: 5.6\nLocationSpeed: 5.7\nLocationTimestamp: 5.8\nLocationTimestampSeconds: 5.8.1\nLocationTimestampNanos: 5.8.2\nIpSupport: 6\nStaticIps: 7\nNumDynamicIps: 8\nTimeLimits: 9\nTimeLimitsCreateClusterInstTimeout: 9.1\nTimeLimitsUpdateClusterInstTimeout: 9.2\nTimeLimitsDeleteClusterInstTimeout: 9.3\nTimeLimitsCreateAppInstTimeout: 9.4\nTimeLimitsUpdateAppInstTimeout: 9.5\nTimeLimitsDeleteAppInstTimeout: 9.6\nErrors: 10\nState: 12\nCrmOverride: 13\nDeploymentLocal: 14\nPlatformType: 15\nNotifySrvAddr: 16\nFlavor: 17\nFlavorName: 17.1\nPhysicalName: 18\nEnvVar: 19\nEnvVarKey: 19.1\nEnvVarValue: 19.2\nContainerVersion: 20\nConfig: 21\nConfigContainerRegistryPath: 21.1\nConfigCloudletVmImagePath: 21.2\nConfigNotifyCtrlAddrs: 21.3\nConfigTlsCertFile: 21.5\nConfigTlsKeyFile: 21.20\nConfigTlsCaFile: 21.21\nConfigEnvVar: 21.6\nConfigEnvVarKey: 21.6.1\nConfigEnvVarValue: 21.6.2\nConfigPlatformTag: 21.8\nConfigTestMode: 21.9\nConfigSpan: 21.10\nConfigCleanupMode: 21.11\nConfigRegion: 21.12\nConfigCommercialCerts: 21.13\nConfigUseVaultPki: 21.14\nConfigAppDnsRoot: 21.16\nConfigChefServerPath: 21.17\nConfigChefClientInterval: 21.18\nConfigDeploymentTag: 21.19\nConfigCrmAccessPrivateKey: 21.22\nConfigAccessApiAddr: 21.23\nConfigCacheDir: 21.24\nConfigSecondaryCrmAccessPrivateKey: 21.25\nConfigThanosRecvAddr: 21.26\nResTagMap: 22\nResTagMapKey: 22.1\nResTagMapValue: 22.2\nResTagMapValueName: 22.2.1\nResTagMapValueOrganization: 22.2.2\nAccessVars: 23\nAccessVarsKey: 23.1\nAccessVarsValue: 23.2\nVmImageVersion: 24\nDeployment: 26\nInfraApiAccess: 27\nInfraConfig: 28\nInfraConfigExternalNetworkName: 28.1\nInfraConfigFlavorName: 28.2\nChefClientKey: 29\nChefClientKeyKey: 29.1\nChefClientKeyValue: 29.2\nMaintenanceState: 30\nOverridePolicyContainerVersion: 31\nVmPool: 32\nCrmAccessPublicKey: 33\nCrmAccessKeyUpgradeRequired: 34\nCreatedAt: 35\nCreatedAtSeconds: 35.1\nCreatedAtNanos: 35.2\nUpdatedAt: 36\nUpdatedAtSeconds: 36.1\nUpdatedAtNanos: 36.2\nTrustPolicy: 37\nTrustPolicyState: 38\nResourceQuotas: 39\nResourceQuotasName: 39.1\nResourceQuotasValue: 39.2\nResourceQuotasAlertThreshold: 39.3\nDefaultResourceAlertThreshold: 40\nHostController: 41\nKafkaCluster: 42\nKafkaUser: 43\nKafkaPassword: 44\nGpuConfig: 45\nGpuConfigDriver: 45.1\nGpuConfigDriverName: 45.1.1\nGpuConfigDriverOrganization: 45.1.2\nGpuConfigProperties: 45.2\nGpuConfigPropertiesKey: 45.2.1\nGpuConfigPropertiesValue: 45.2.2\nGpuConfigLicenseConfig: 45.3\nGpuConfigLicenseConfigMd5Sum: 45.4\nEnableDefaultServerlessCluster: 46\nAllianceOrgs: 47\nSingleKubernetesClusterOwner: 48\nDeletePrepare: 49\nPlatformHighAvailability: 50\nSecondaryCrmAccessPublicKey: 51\nSecondaryCrmAccessKeyUpgradeRequired: 52\nSecondaryNotifySrvAddr: 53\nDnsLabel: 54\nRootLbFqdn: 55\nFederationConfig: 56\nFederationConfigFederationName: 56.1\nFederationConfigSelfFederationId: 56.2\nFederationConfigPartnerFederationId: 56.3\nFederationConfigZoneCountryCode: 56.4\nFederationConfigPartnerFederationAddr: 56.5\nLicenseConfigStoragePath: 57\n```", - "tags": ["Cloudlet"], - "summary": "Update Cloudlet", - "operationId": "UpdateCloudlet", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudlet" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateCloudletPool": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "The following values should be added to `CloudletPool.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nCloudlets: 3\nCloudletsOrganization: 3.1\nCloudletsName: 3.2\nCloudletsFederatedOrganization: 3.3\nCreatedAt: 4\nCreatedAtSeconds: 4.1\nCreatedAtNanos: 4.2\nUpdatedAt: 5\nUpdatedAtSeconds: 5.1\nUpdatedAtNanos: 5.2\nDeletePrepare: 6\n```", - "tags": ["CloudletPool"], - "summary": "Update a CloudletPool", - "operationId": "UpdateCloudletPool", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletPool" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateClusterInst": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Updates an instance of a Cluster deployed on a Cloudlet.\nThe following values should be added to `ClusterInst.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyClusterKey: 2.1\nKeyClusterKeyName: 2.1.1\nKeyCloudletKey: 2.2\nKeyCloudletKeyOrganization: 2.2.1\nKeyCloudletKeyName: 2.2.2\nKeyCloudletKeyFederatedOrganization: 2.2.3\nKeyOrganization: 2.3\nFlavor: 3\nFlavorName: 3.1\nLiveness: 9\nAuto: 10\nState: 4\nErrors: 5\nCrmOverride: 6\nIpAccess: 7\nAllocatedIp: 8\nNodeFlavor: 11\nDeployment: 15\nNumMasters: 13\nNumNodes: 14\nExternalVolumeSize: 17\nAutoScalePolicy: 18\nAvailabilityZone: 19\nImageName: 20\nReservable: 21\nReservedBy: 22\nSharedVolumeSize: 23\nMasterNodeFlavor: 25\nSkipCrmCleanupOnFailure: 26\nOptRes: 27\nResources: 28\nResourcesVms: 28.1\nResourcesVmsName: 28.1.1\nResourcesVmsType: 28.1.2\nResourcesVmsStatus: 28.1.3\nResourcesVmsInfraFlavor: 28.1.4\nResourcesVmsIpaddresses: 28.1.5\nResourcesVmsIpaddressesExternalIp: 28.1.5.1\nResourcesVmsIpaddressesInternalIp: 28.1.5.2\nResourcesVmsContainers: 28.1.6\nResourcesVmsContainersName: 28.1.6.1\nResourcesVmsContainersType: 28.1.6.2\nResourcesVmsContainersStatus: 28.1.6.3\nResourcesVmsContainersClusterip: 28.1.6.4\nResourcesVmsContainersRestarts: 28.1.6.5\nCreatedAt: 29\nCreatedAtSeconds: 29.1\nCreatedAtNanos: 29.2\nUpdatedAt: 30\nUpdatedAtSeconds: 30.1\nUpdatedAtNanos: 30.2\nReservationEndedAt: 31\nReservationEndedAtSeconds: 31.1\nReservationEndedAtNanos: 31.2\nMultiTenant: 32\nNetworks: 33\nDeletePrepare: 34\nDnsLabel: 35\nFqdn: 36\n```", - "tags": ["ClusterInst"], - "summary": "Update Cluster Instance", - "operationId": "UpdateClusterInst", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionClusterInst" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateFlavor": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "The following values should be added to `Flavor.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyName: 2.1\nRam: 3\nVcpus: 4\nDisk: 5\nOptResMap: 6\nOptResMapKey: 6.1\nOptResMapValue: 6.2\nDeletePrepare: 7\n```", - "tags": ["Flavor"], - "summary": "Update a Flavor", - "operationId": "UpdateFlavor", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionFlavor" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateFlowRateLimitSettings": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "The following values should be added to `FlowRateLimitSettings.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyFlowSettingsName: 2.1\nKeyRateLimitKey: 2.2\nKeyRateLimitKeyApiName: 2.2.1\nKeyRateLimitKeyApiEndpointType: 2.2.2\nKeyRateLimitKeyRateLimitTarget: 2.2.3\nSettings: 3\nSettingsFlowAlgorithm: 3.1\nSettingsReqsPerSecond: 3.2\nSettingsBurstSize: 3.3\n```", - "tags": ["FlowRateLimitSettings"], - "summary": "Update Flow RateLimit settings for an API endpoint and target", - "operationId": "UpdateFlowRateLimitSettings", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionFlowRateLimitSettings" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateGPUDriver": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Updates GPU driver config.\nThe following values should be added to `GPUDriver.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyName: 2.1\nKeyOrganization: 2.2\nBuilds: 3\nBuildsName: 3.1\nBuildsDriverPath: 3.2\nBuildsDriverPathCreds: 3.3\nBuildsOperatingSystem: 3.4\nBuildsKernelVersion: 3.5\nBuildsHypervisorInfo: 3.6\nBuildsMd5Sum: 3.7\nBuildsStoragePath: 3.8\nLicenseConfig: 4\nLicenseConfigMd5Sum: 5\nProperties: 6\nPropertiesKey: 6.1\nPropertiesValue: 6.2\nState: 7\nIgnoreState: 8\nDeletePrepare: 9\nStorageBucketName: 10\nLicenseConfigStoragePath: 11\n```", - "tags": ["GPUDriver"], - "summary": "Update GPU Driver", - "operationId": "UpdateGPUDriver", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionGPUDriver" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateMaxReqsRateLimitSettings": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "The following values should be added to `MaxReqsRateLimitSettings.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyMaxReqsSettingsName: 2.1\nKeyRateLimitKey: 2.2\nKeyRateLimitKeyApiName: 2.2.1\nKeyRateLimitKeyApiEndpointType: 2.2.2\nKeyRateLimitKeyRateLimitTarget: 2.2.3\nSettings: 3\nSettingsMaxReqsAlgorithm: 3.1\nSettingsMaxRequests: 3.2\nSettingsInterval: 3.3\n```", - "tags": ["MaxReqsRateLimitSettings"], - "summary": "Update MaxReqs RateLimit settings for an API endpoint and target", - "operationId": "UpdateMaxReqsRateLimitSettings", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionMaxReqsRateLimitSettings" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateNetwork": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "The following values should be added to `Network.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyCloudletKey: 2.1\nKeyCloudletKeyOrganization: 2.1.1\nKeyCloudletKeyName: 2.1.2\nKeyCloudletKeyFederatedOrganization: 2.1.3\nKeyName: 2.2\nRoutes: 3\nRoutesDestinationCidr: 3.1\nRoutesNextHopIp: 3.2\nConnectionType: 4\nDeletePrepare: 5\n```", - "tags": ["Network"], - "summary": "Update a Network", - "operationId": "UpdateNetwork", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionNetwork" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateResTagTable": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "The following values should be added to `ResTagTable.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyName: 2.1\nKeyOrganization: 2.2\nTags: 3\nTagsKey: 3.1\nTagsValue: 3.2\nAzone: 4\nDeletePrepare: 5\n```", - "tags": ["ResTagTable"], - "summary": "Update TagTable", - "operationId": "UpdateResTagTable", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionResTagTable" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateSettings": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "The following values should be added to `Settings.fields` field array to specify which fields will be updated.\n```\nShepherdMetricsCollectionInterval: 2\nShepherdAlertEvaluationInterval: 20\nShepherdMetricsScrapeInterval: 40\nShepherdHealthCheckRetries: 3\nShepherdHealthCheckInterval: 4\nAutoDeployIntervalSec: 5\nAutoDeployOffsetSec: 6\nAutoDeployMaxIntervals: 7\nCreateAppInstTimeout: 8\nUpdateAppInstTimeout: 9\nDeleteAppInstTimeout: 10\nCreateClusterInstTimeout: 11\nUpdateClusterInstTimeout: 12\nDeleteClusterInstTimeout: 13\nMasterNodeFlavor: 14\nMaxTrackedDmeClients: 16\nChefClientInterval: 17\nInfluxDbMetricsRetention: 18\nCloudletMaintenanceTimeout: 19\nUpdateVmPoolTimeout: 21\nUpdateTrustPolicyTimeout: 22\nDmeApiMetricsCollectionInterval: 23\nEdgeEventsMetricsCollectionInterval: 24\nCleanupReservableAutoClusterIdletime: 25\nInfluxDbCloudletUsageMetricsRetention: 26\nCreateCloudletTimeout: 27\nUpdateCloudletTimeout: 28\nLocationTileSideLengthKm: 29\nEdgeEventsMetricsContinuousQueriesCollectionIntervals: 30\nEdgeEventsMetricsContinuousQueriesCollectionIntervalsInterval: 30.1\nEdgeEventsMetricsContinuousQueriesCollectionIntervalsRetention: 30.2\nInfluxDbDownsampledMetricsRetention: 31\nInfluxDbEdgeEventsMetricsRetention: 32\nAppinstClientCleanupInterval: 33\nClusterAutoScaleAveragingDurationSec: 34\nClusterAutoScaleRetryDelay: 35\nAlertPolicyMinTriggerTime: 36\nDisableRateLimit: 37\nRateLimitMaxTrackedIps: 39\nResourceSnapshotThreadInterval: 41\nPlatformHaInstancePollInterval: 42\nPlatformHaInstanceActiveExpireTime: 43\n```", - "tags": ["Settings"], - "summary": "Update settings", - "operationId": "UpdateSettings", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionSettings" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateTrustPolicy": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "The following values should be added to `TrustPolicy.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nOutboundSecurityRules: 3\nOutboundSecurityRulesProtocol: 3.1\nOutboundSecurityRulesPortRangeMin: 3.2\nOutboundSecurityRulesPortRangeMax: 3.3\nOutboundSecurityRulesRemoteCidr: 3.4\nDeletePrepare: 4\n```", - "tags": ["TrustPolicy"], - "summary": "Update a Trust policy", - "operationId": "UpdateTrustPolicy", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionTrustPolicy" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateTrustPolicyException": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "The following values should be added to `TrustPolicyException.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyAppKey: 2.1\nKeyAppKeyOrganization: 2.1.1\nKeyAppKeyName: 2.1.2\nKeyAppKeyVersion: 2.1.3\nKeyCloudletPoolKey: 2.2\nKeyCloudletPoolKeyOrganization: 2.2.1\nKeyCloudletPoolKeyName: 2.2.2\nKeyName: 2.3\nState: 3\nOutboundSecurityRules: 4\nOutboundSecurityRulesProtocol: 4.1\nOutboundSecurityRulesPortRangeMin: 4.2\nOutboundSecurityRulesPortRangeMax: 4.3\nOutboundSecurityRulesRemoteCidr: 4.4\n```", - "tags": ["TrustPolicyException"], - "summary": "Update a Trust Policy Exception, by Operator Organization", - "operationId": "UpdateTrustPolicyException", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionTrustPolicyException" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/ctrl/UpdateVMPool": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Updates a VM pools VMs.\nThe following values should be added to `VMPool.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nVms: 3\nVmsName: 3.1\nVmsNetInfo: 3.2\nVmsNetInfoExternalIp: 3.2.1\nVmsNetInfoInternalIp: 3.2.2\nVmsGroupName: 3.3\nVmsState: 3.4\nVmsUpdatedAt: 3.5\nVmsUpdatedAtSeconds: 3.5.1\nVmsUpdatedAtNanos: 3.5.2\nVmsInternalName: 3.6\nVmsFlavor: 3.7\nVmsFlavorName: 3.7.1\nVmsFlavorVcpus: 3.7.2\nVmsFlavorRam: 3.7.3\nVmsFlavorDisk: 3.7.4\nVmsFlavorPropMap: 3.7.5\nVmsFlavorPropMapKey: 3.7.5.1\nVmsFlavorPropMapValue: 3.7.5.2\nState: 4\nErrors: 5\nCrmOverride: 7\nDeletePrepare: 8\n```", - "tags": ["VMPool"], - "summary": "Update VMPool", - "operationId": "UpdateVMPool", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionVMPool" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/events/find": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Find events\nDisplay events based on find filter.", - "tags": ["Events"], - "operationId": "FindEvents", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/EventSearch" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/events/show": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Search events\nDisplay events based on search filter.", - "tags": ["Events"], - "operationId": "SearchEvents", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/EventSearch" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/events/terms": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Terms Events\nDisplay events terms.", - "tags": ["Events"], - "operationId": "TermsEvents", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/EventTerms" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/metrics/app": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Display app related metrics.", - "tags": ["DeveloperMetrics"], - "summary": "App related metrics", - "operationId": "AppMetrics", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAppInstMetrics" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/metrics/clientapiusage": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Display client api usage related metrics.", - "tags": ["DeveloperMetrics"], - "summary": "Client api usage related metrics", - "operationId": "ClientApiUsageMetrics", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionClientApiUsageMetrics" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/metrics/clientappusage": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Display client app usage related metrics.", - "tags": ["DeveloperMetrics"], - "summary": "Client app usage related metrics", - "operationId": "ClientAppUsageMetrics", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionClientAppUsageMetrics" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/metrics/clientcloudletusage": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Display client cloudlet usage related metrics.", - "tags": ["DeveloperMetrics"], - "summary": "Client cloudlet usage related metrics", - "operationId": "ClientCloudletUsageMetrics", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionClientCloudletUsageMetrics" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/metrics/cloudlet": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Display cloudlet related metrics.", - "tags": ["OperatorMetrics"], - "summary": "Cloudlet related metrics", - "operationId": "CloudletMetrics", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletMetrics" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/metrics/cloudlet/usage": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Display cloudlet usage related metrics.", - "tags": ["OperatorMetrics"], - "summary": "Cloudlet usage related metrics", - "operationId": "CloudletUsageMetrics", - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/metrics/cluster": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Display cluster related metrics.", - "tags": ["DeveloperMetrics"], - "summary": "Cluster related metrics", - "operationId": "ClusterMetrics", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionClusterInstMetrics" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/org/create": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Create an Organization to access operator/cloudlet APIs.", - "tags": ["Organization"], - "summary": "Create Organization", - "operationId": "CreateOrg", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/Organization" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/org/delete": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Deletes an existing Organization.", - "tags": ["Organization"], - "summary": "Delete Organization", - "operationId": "DeleteOrg", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/Organization" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/org/show": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Displays existing Organizations in which you are authorized to access.", - "tags": ["Organization"], - "summary": "Show Organizations", - "operationId": "ShowOrg", - "responses": { - "200": { - "$ref": "#/responses/listOrgs" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/org/update": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "API to update an existing Organization.", - "tags": ["Organization"], - "summary": "Update Organization", - "operationId": "UpdateOrg", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/Organization" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/role/adduser": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Add a role for the organization to the user.", - "tags": ["Role"], - "summary": "Add User Role", - "operationId": "AddUserRole", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/Role" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/role/assignment/show": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Show roles for the current user.", - "tags": ["Role"], - "summary": "Show Role Assignment", - "operationId": "ShowRoleAssignment", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/Role" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/listRoles" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/role/perms/show": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Show permissions associated with each role.", - "tags": ["Role"], - "summary": "Show Role Permissions", - "operationId": "ShowRolePerm", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RolePerm" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/listPerms" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/role/removeuser": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Remove the role for the organization from the user.", - "tags": ["Role"], - "summary": "Remove User Role", - "operationId": "RemoveUserRole", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/Role" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/role/show": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Show role names.", - "tags": ["Role"], - "summary": "Show Role Names", - "operationId": "ShowRoleNames", - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/role/showuser": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Show roles for the organizations the current user can add or remove roles to", - "tags": ["Role"], - "summary": "Show User Role", - "operationId": "ShowUserRole", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/Role" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/listRoles" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/usage/app": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "App Usage\nDisplay app usage.", - "tags": ["DeveloperUsage"], - "operationId": "AppUsage", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionAppInstUsage" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/usage/cloudletpool": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "CloudletPool Usage\nDisplay cloudletpool usage.", - "tags": ["OperatorUsage"], - "operationId": "CloudletPoolUsage", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionCloudletPoolUsage" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/usage/cluster": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Cluster Usage\nDisplay cluster usage.", - "tags": ["DeveloperUsage"], - "operationId": "ClusterUsage", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/RegionClusterInstUsage" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/user/delete": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Deletes existing user.", - "tags": ["User"], - "summary": "Delete User", - "operationId": "DeleteUser", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/User" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/user/show": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Displays existing users to which you are authorized to access.", - "tags": ["User"], - "summary": "Show Users", - "operationId": "ShowUser", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/Organization" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/listUsers" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/auth/user/update": { - "post": { - "security": [ - { - "Bearer": [] - } - ], - "description": "Updates current user.", - "tags": ["User"], - "summary": "Update User", - "operationId": "UpdateUser", - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/login": { - "post": { - "description": "Log in to the MC to acquire a temporary bearer token for access to other APIs.\nAuthentication can be via a username and password, or an API key ID and API key if created. If two-factor authentication (2FA) is enabled on the account, an additional temporary one-time password (TOTP) from a mobile authenticator will also be required.\n", - "tags": ["Security"], - "summary": "Login", - "operationId": "Login", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/UserLogin" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/authToken" - }, - "400": { - "$ref": "#/responses/loginBadRequest" - } - } - } - }, - "/passwordreset": { - "post": { - "description": "This resets your login password.", - "tags": ["Security"], - "summary": "Reset Login Password", - "operationId": "PasswdReset", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/PasswordReset" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - } - } - } - }, - "/publicconfig": { - "post": { - "description": "Show Public Configuration for UI", - "tags": ["Config"], - "summary": "Show Public Configuration", - "operationId": "PublicConfig", - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, - "/usercreate": { - "post": { - "description": "Creates a new user and allows them to access and manage resources.", - "tags": ["User"], - "summary": "Create User", - "operationId": "CreateUser", - "parameters": [ - { - "name": "Body", - "in": "body", - "schema": { - "$ref": "#/definitions/CreateUser" - } - } - ], - "responses": { - "200": { - "$ref": "#/responses/success" - }, - "400": { - "$ref": "#/responses/badRequest" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - } - }, - "definitions": { - "AccessType": { - "description": "AccessType indicates how to access the app\n\n0: `ACCESS_TYPE_DEFAULT_FOR_DEPLOYMENT`\n1: `ACCESS_TYPE_DIRECT`\n2: `ACCESS_TYPE_LOAD_BALANCER`", - "type": "integer", - "format": "int32", - "title": "(Deprecated) AccessType", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AggrVal": { - "type": "object", - "properties": { - "count": { - "type": "integer", - "format": "int64", - "x-go-name": "DocCount" - }, - "key": { - "type": "string", - "x-go-name": "Key" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/cloudcommon/node" - }, - "Alert": { - "type": "object", - "properties": { - "active_at": { - "$ref": "#/definitions/Timestamp" - }, - "annotations": { - "description": "Annotations are extra information about the alert", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "Annotations" - }, - "controller": { - "description": "Connected controller unique id", - "type": "string", - "x-go-name": "Controller" - }, - "labels": { - "description": "Labels uniquely define the alert", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "Labels" - }, - "notify_id": { - "description": "Id of client assigned by server (internal use only)", - "type": "integer", - "format": "int64", - "x-go-name": "NotifyId" - }, - "state": { - "description": "State of the alert", - "type": "string", - "x-go-name": "State" - }, - "value": { - "description": "Any value associated with alert", - "type": "number", - "format": "double", - "x-go-name": "Value" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AlertPolicy": { - "type": "object", - "properties": { - "active_conn_limit": { - "description": "Active Connections alert threshold. Valid values 1-4294967295", - "type": "integer", - "format": "uint32", - "x-go-name": "ActiveConnLimit" - }, - "annotations": { - "description": "Additional Annotations for extra information about the alert", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "Annotations" - }, - "cpu_utilization_limit": { - "description": "Container or pod CPU utilization rate(percentage) across all nodes. Valid values 1-100", - "type": "integer", - "format": "uint32", - "x-go-name": "CpuUtilizationLimit" - }, - "delete_prepare": { - "description": "Preparing to be deleted", - "type": "boolean", - "x-go-name": "DeletePrepare" - }, - "description": { - "description": "Description of the alert policy", - "type": "string", - "x-go-name": "Description" - }, - "disk_utilization_limit": { - "description": "Container or pod disk utilization rate(percentage) across all nodes. Valid values 1-100", - "type": "integer", - "format": "uint32", - "x-go-name": "DiskUtilizationLimit" - }, - "fields": { - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "key": { - "$ref": "#/definitions/AlertPolicyKey" - }, - "labels": { - "description": "Additional Labels", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "Labels" - }, - "mem_utilization_limit": { - "description": "Container or pod memory utilization rate(percentage) across all nodes. Valid values 1-100", - "type": "integer", - "format": "uint32", - "x-go-name": "MemUtilizationLimit" - }, - "severity": { - "description": "Alert severity level - one of \"info\", \"warning\", \"error\"", - "type": "string", - "x-go-name": "Severity" - }, - "trigger_time": { - "$ref": "#/definitions/Duration" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AlertPolicyKey": { - "type": "object", - "properties": { - "name": { - "description": "Alert Policy name", - "type": "string", - "x-go-name": "Name" - }, - "organization": { - "description": "Name of the organization for the app that this alert can be applied to", - "type": "string", - "x-go-name": "Organization" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AlertReceiver": { - "description": "Configurable part of AlertManager Receiver", - "type": "object", - "properties": { - "AppInst": { - "$ref": "#/definitions/AppInstKey" - }, - "Cloudlet": { - "$ref": "#/definitions/CloudletKey" - }, - "Email": { - "description": "Custom receiving email", - "type": "string" - }, - "Name": { - "description": "Receiver Name", - "type": "string" - }, - "PagerDutyApiVersion": { - "description": "PagerDuty API version", - "type": "string" - }, - "PagerDutyIntegrationKey": { - "description": "PagerDuty integration key", - "type": "string" - }, - "Region": { - "description": "Region for the alert receiver", - "type": "string" - }, - "Severity": { - "description": "Alert severity filter", - "type": "string" - }, - "SlackChannel": { - "description": "Custom slack channel", - "type": "string" - }, - "SlackWebhook": { - "description": "Custom slack webhook", - "type": "string" - }, - "Type": { - "description": "Receiver type. Eg. email, slack, pagerduty", - "type": "string" - }, - "User": { - "description": "User that created this receiver", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "ApiEndpointType": { - "type": "integer", - "format": "int32", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "App": { - "description": "App belongs to developer organizations and is used to provide information about their application.", - "type": "object", - "title": "Application", - "required": ["key"], - "properties": { - "access_ports": { - "description": "Comma separated list of protocol:port pairs that the App listens on.\nEx: \"tcp:80,udp:10002\".\nAlso supports additional configurations per port:\n(1) tls (tcp-only) - Enables TLS on specified port. Ex: \"tcp:443:tls\".\n(2) nginx (udp-only) - Use NGINX LB instead of envoy for specified port. Ex: \"udp:10001:nginx\".\n(3) maxpktsize (udp-only) - Configures maximum UDP datagram size allowed on port for both upstream/downstream traffic. Ex: \"udp:10001:maxpktsize=8000\".", - "type": "string", - "x-go-name": "AccessPorts" - }, - "access_type": { - "$ref": "#/definitions/AccessType" - }, - "alert_policies": { - "description": "Alert Policies", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "AlertPolicies" - }, - "allow_serverless": { - "description": "App is allowed to deploy as serverless containers", - "type": "boolean", - "x-go-name": "AllowServerless" - }, - "android_package_name": { - "description": "Android package name used to match the App name from the Android package", - "type": "string", - "x-go-name": "AndroidPackageName" - }, - "annotations": { - "description": "Annotations is a comma separated map of arbitrary key value pairs,", - "type": "string", - "x-go-name": "Annotations", - "example": "key1=val1,key2=val2,key3=\"val 3\"" - }, - "auth_public_key": { - "description": "Public key used for authentication", - "type": "string", - "x-go-name": "AuthPublicKey" - }, - "auto_prov_policies": { - "description": "Auto provisioning policy names, may be specified multiple times", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "AutoProvPolicies" - }, - "auto_prov_policy": { - "description": "(_deprecated_) Auto provisioning policy name", - "type": "string", - "x-go-name": "AutoProvPolicy" - }, - "command": { - "description": "Command that the container runs to start service", - "type": "string", - "x-go-name": "Command" - }, - "configs": { - "description": "Customization files passed through to implementing services", - "type": "array", - "items": { - "$ref": "#/definitions/ConfigFile" - }, - "x-go-name": "Configs" - }, - "created_at": { - "$ref": "#/definitions/Timestamp" - }, - "default_flavor": { - "$ref": "#/definitions/FlavorKey" - }, - "del_opt": { - "$ref": "#/definitions/DeleteType" - }, - "delete_prepare": { - "description": "Preparing to be deleted", - "type": "boolean", - "x-go-name": "DeletePrepare" - }, - "deployment": { - "description": "Deployment type (kubernetes, docker, or vm)", - "type": "string", - "x-go-name": "Deployment" - }, - "deployment_generator": { - "description": "Deployment generator target to generate a basic deployment manifest", - "type": "string", - "x-go-name": "DeploymentGenerator" - }, - "deployment_manifest": { - "description": "Deployment manifest is the deployment specific manifest file/config.\nFor docker deployment, this can be a docker-compose or docker run file.\nFor kubernetes deployment, this can be a kubernetes yaml or helm chart file.", - "type": "string", - "x-go-name": "DeploymentManifest" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "image_path": { - "description": "URI of where image resides", - "type": "string", - "x-go-name": "ImagePath" - }, - "image_type": { - "$ref": "#/definitions/ImageType" - }, - "internal_ports": { - "description": "Should this app have access to outside world?", - "type": "boolean", - "x-go-name": "InternalPorts" - }, - "key": { - "$ref": "#/definitions/AppKey" - }, - "md5sum": { - "description": "MD5Sum of the VM-based app image", - "type": "string", - "x-go-name": "Md5Sum" - }, - "official_fqdn": { - "description": "Official FQDN is the FQDN that the app uses to connect by default", - "type": "string", - "x-go-name": "OfficialFqdn" - }, - "qos_session_duration": { - "$ref": "#/definitions/Duration" - }, - "qos_session_profile": { - "$ref": "#/definitions/QosSessionProfile" - }, - "required_outbound_connections": { - "description": "Connections this app require to determine if the app is compatible with a trust policy", - "type": "array", - "items": { - "$ref": "#/definitions/SecurityRule" - }, - "x-go-name": "RequiredOutboundConnections" - }, - "revision": { - "description": "Revision can be specified or defaults to current timestamp when app is updated", - "type": "string", - "x-go-name": "Revision" - }, - "scale_with_cluster": { - "description": "Option to run App on all nodes of the cluster", - "type": "boolean", - "x-go-name": "ScaleWithCluster" - }, - "serverless_config": { - "$ref": "#/definitions/ServerlessConfig" - }, - "skip_hc_ports": { - "description": "Comma separated list of protocol:port pairs that we should not run health check on.\nShould be configured in case app does not always listen on these ports.\n\"all\" can be specified if no health check to be run for this app.\nNumerical values must be decimal format.\ni.e. tcp:80,udp:10002", - "type": "string", - "x-go-name": "SkipHcPorts" - }, - "template_delimiter": { - "description": "Delimiter to be used for template parsing, defaults to \"[[ ]]\"", - "type": "string", - "x-go-name": "TemplateDelimiter" - }, - "trusted": { - "description": "Indicates that an instance of this app can be started on a trusted cloudlet", - "type": "boolean", - "x-go-name": "Trusted" - }, - "updated_at": { - "$ref": "#/definitions/Timestamp" - }, - "vm_app_os_type": { - "$ref": "#/definitions/VmAppOsType" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AppAlertPolicy": { - "type": "object", - "properties": { - "alert_policy": { - "description": "Alert name", - "type": "string", - "x-go-name": "AlertPolicy" - }, - "app_key": { - "$ref": "#/definitions/AppKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AppAutoProvPolicy": { - "description": "AutoProvPolicy belonging to an app", - "type": "object", - "properties": { - "app_key": { - "$ref": "#/definitions/AppKey" - }, - "auto_prov_policy": { - "description": "Auto provisioning policy name", - "type": "string", - "x-go-name": "AutoProvPolicy" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AppInst": { - "description": "AppInst is an instance of an App on a Cloudlet where it is defined by an App plus a ClusterInst key.\nMany of the fields here are inherited from the App definition.", - "type": "object", - "title": "Application Instance", - "required": ["key"], - "properties": { - "auto_cluster_ip_access": { - "$ref": "#/definitions/IpAccess" - }, - "availability_zone": { - "description": "Optional Availability Zone if any", - "type": "string", - "x-go-name": "AvailabilityZone" - }, - "cloudlet_loc": { - "$ref": "#/definitions/Loc" - }, - "configs": { - "description": "Customization files passed through to implementing services", - "type": "array", - "items": { - "$ref": "#/definitions/ConfigFile" - }, - "x-go-name": "Configs" - }, - "created_at": { - "$ref": "#/definitions/Timestamp" - }, - "crm_override": { - "$ref": "#/definitions/CRMOverride" - }, - "dedicated_ip": { - "description": "Dedicated IP assigns an IP for this AppInst but requires platform support", - "type": "boolean", - "x-go-name": "DedicatedIp" - }, - "dns_label": { - "description": "DNS label that is unique within the cloudlet and among other AppInsts/ClusterInsts", - "type": "string", - "x-go-name": "DnsLabel" - }, - "errors": { - "description": "Any errors trying to create, update, or delete the AppInst on the Cloudlet", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Errors" - }, - "external_volume_size": { - "description": "Size of external volume to be attached to nodes. This is for the root partition", - "type": "integer", - "format": "uint64", - "x-go-name": "ExternalVolumeSize" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "flavor": { - "$ref": "#/definitions/FlavorKey" - }, - "force_update": { - "description": "Force Appinst refresh even if revision number matches App revision number.", - "type": "boolean", - "x-go-name": "ForceUpdate" - }, - "health_check": { - "$ref": "#/definitions/HealthCheck" - }, - "internal_port_to_lb_ip": { - "description": "mapping of ports to load balancer IPs", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "InternalPortToLbIp" - }, - "key": { - "$ref": "#/definitions/AppInstKey" - }, - "liveness": { - "$ref": "#/definitions/Liveness" - }, - "mapped_ports": { - "description": "For instances accessible via a shared load balancer, defines the external\nports on the shared load balancer that map to the internal ports\nExternal ports should be appended to the Uri for L4 access.", - "type": "array", - "items": { - "$ref": "#/definitions/AppPort" - }, - "x-go-name": "MappedPorts" - }, - "opt_res": { - "description": "Optional Resources required by OS flavor if any", - "type": "string", - "x-go-name": "OptRes" - }, - "power_state": { - "$ref": "#/definitions/PowerState" - }, - "real_cluster_name": { - "description": "Real ClusterInst name", - "type": "string", - "x-go-name": "RealClusterName" - }, - "revision": { - "description": "Revision changes each time the App is updated. Refreshing the App Instance will sync the revision with that of the App", - "type": "string", - "x-go-name": "Revision" - }, - "runtime_info": { - "$ref": "#/definitions/AppInstRuntime" - }, - "state": { - "$ref": "#/definitions/TrackedState" - }, - "unique_id": { - "description": "A unique id for the AppInst within the region to be used by platforms", - "type": "string", - "x-go-name": "UniqueId" - }, - "update_multiple": { - "description": "Allow multiple instances to be updated at once", - "type": "boolean", - "x-go-name": "UpdateMultiple" - }, - "updated_at": { - "$ref": "#/definitions/Timestamp" - }, - "uri": { - "description": "Base FQDN (not really URI) for the App. See Service FQDN for endpoint access.", - "type": "string", - "x-go-name": "Uri" - }, - "vm_flavor": { - "description": "OS node flavor to use", - "type": "string", - "x-go-name": "VmFlavor" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AppInstClientKey": { - "type": "object", - "properties": { - "app_inst_key": { - "$ref": "#/definitions/AppInstKey" - }, - "unique_id": { - "description": "AppInstClient Unique Id", - "type": "string", - "x-go-name": "UniqueId" - }, - "unique_id_type": { - "description": "AppInstClient Unique Id Type", - "type": "string", - "x-go-name": "UniqueIdType" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AppInstKey": { - "description": "AppInstKey uniquely identifies an Application Instance (AppInst) or Application Instance state (AppInstInfo).", - "type": "object", - "title": "App Instance Unique Key", - "properties": { - "app_key": { - "$ref": "#/definitions/AppKey" - }, - "cluster_inst_key": { - "$ref": "#/definitions/VirtualClusterInstKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AppInstLatency": { - "type": "object", - "properties": { - "key": { - "$ref": "#/definitions/AppInstKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AppInstRefKey": { - "description": "AppInstRefKey is app instance key without cloudlet key.", - "type": "object", - "title": "AppInst Ref Key", - "properties": { - "app_key": { - "$ref": "#/definitions/AppKey" - }, - "cluster_inst_key": { - "$ref": "#/definitions/ClusterInstRefKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AppInstRefs": { - "type": "object", - "properties": { - "delete_requested_insts": { - "description": "AppInsts being deleted (key is JSON of AppInst Key)", - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "uint32" - }, - "x-go-name": "DeleteRequestedInsts" - }, - "insts": { - "description": "AppInsts for App (key is JSON of AppInst Key)", - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "uint32" - }, - "x-go-name": "Insts" - }, - "key": { - "$ref": "#/definitions/AppKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AppInstRuntime": { - "description": "Runtime information of active AppInsts", - "type": "object", - "title": "AppInst Runtime Info", - "properties": { - "container_ids": { - "description": "List of container names", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "ContainerIds" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AppKey": { - "description": "AppKey uniquely identifies an App", - "type": "object", - "title": "Application unique key", - "properties": { - "name": { - "description": "App name", - "type": "string", - "x-go-name": "Name" - }, - "organization": { - "description": "App developer organization", - "type": "string", - "x-go-name": "Organization" - }, - "version": { - "description": "App version", - "type": "string", - "x-go-name": "Version" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AppPort": { - "description": "AppPort describes an L4 or L7 public access port/path mapping. This is used to track external to internal mappings for access via a shared load balancer or reverse proxy.", - "type": "object", - "title": "Application Port", - "properties": { - "end_port": { - "description": "A non-zero end port indicates a port range from internal port to end port, inclusive.", - "type": "integer", - "format": "int32", - "x-go-name": "EndPort" - }, - "fqdn_prefix": { - "description": "skip 4 to preserve the numbering. 4 was path_prefix but was removed since we dont need it after removed http\nFQDN prefix to append to base FQDN in FindCloudlet response. May be empty.", - "type": "string", - "x-go-name": "FqdnPrefix" - }, - "internal_port": { - "description": "Container port", - "type": "integer", - "format": "int32", - "x-go-name": "InternalPort" - }, - "max_pkt_size": { - "description": "Maximum datagram size (udp only)", - "type": "integer", - "format": "int64", - "x-go-name": "MaxPktSize" - }, - "nginx": { - "description": "Use nginx proxy for this port if you really need a transparent proxy (udp only)", - "type": "boolean", - "x-go-name": "Nginx" - }, - "proto": { - "$ref": "#/definitions/LProto" - }, - "public_port": { - "description": "Public facing port for TCP/UDP (may be mapped on shared LB reverse proxy)", - "type": "integer", - "format": "int32", - "x-go-name": "PublicPort" - }, - "tls": { - "description": "TLS termination for this port", - "type": "boolean", - "x-go-name": "Tls" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/dme-proto" - }, - "AutoProvCloudlet": { - "description": "AutoProvCloudlet stores the potential cloudlet and location for DME lookup", - "type": "object", - "properties": { - "key": { - "$ref": "#/definitions/CloudletKey" - }, - "loc": { - "$ref": "#/definitions/Loc" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AutoProvPolicy": { - "description": "AutoProvPolicy defines the automated provisioning policy", - "type": "object", - "properties": { - "cloudlets": { - "description": "Allowed deployment locations", - "type": "array", - "items": { - "$ref": "#/definitions/AutoProvCloudlet" - }, - "x-go-name": "Cloudlets" - }, - "delete_prepare": { - "description": "Preparing to be deleted", - "type": "boolean", - "x-go-name": "DeletePrepare" - }, - "deploy_client_count": { - "description": "Minimum number of clients within the auto deploy interval to trigger deployment", - "type": "integer", - "format": "uint32", - "x-go-name": "DeployClientCount" - }, - "deploy_interval_count": { - "description": "Number of intervals to check before triggering deployment", - "type": "integer", - "format": "uint32", - "x-go-name": "DeployIntervalCount" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "key": { - "$ref": "#/definitions/PolicyKey" - }, - "max_instances": { - "description": "Maximum number of instances (active or not)", - "type": "integer", - "format": "uint32", - "x-go-name": "MaxInstances" - }, - "min_active_instances": { - "description": "Minimum number of active instances for High-Availability", - "type": "integer", - "format": "uint32", - "x-go-name": "MinActiveInstances" - }, - "undeploy_client_count": { - "description": "Number of active clients for the undeploy interval below which trigers undeployment, 0 (default) disables auto undeploy", - "type": "integer", - "format": "uint32", - "x-go-name": "UndeployClientCount" - }, - "undeploy_interval_count": { - "description": "Number of intervals to check before triggering undeployment", - "type": "integer", - "format": "uint32", - "x-go-name": "UndeployIntervalCount" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AutoProvPolicyCloudlet": { - "description": "AutoProvPolicyCloudlet is used to add and remove Cloudlets from the Auto Provisioning Policy", - "type": "object", - "properties": { - "cloudlet_key": { - "$ref": "#/definitions/CloudletKey" - }, - "key": { - "$ref": "#/definitions/PolicyKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "AutoScalePolicy": { - "description": "AutoScalePolicy defines when and how cluster instances will have their\nnodes scaled up or down.", - "type": "object", - "properties": { - "delete_prepare": { - "description": "Preparing to be deleted", - "type": "boolean", - "x-go-name": "DeletePrepare" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "key": { - "$ref": "#/definitions/PolicyKey" - }, - "max_nodes": { - "description": "Maximum number of cluster nodes", - "type": "integer", - "format": "uint32", - "x-go-name": "MaxNodes" - }, - "min_nodes": { - "description": "Minimum number of cluster nodes", - "type": "integer", - "format": "uint32", - "x-go-name": "MinNodes" - }, - "scale_down_cpu_thresh": { - "description": "(Deprecated) Scale down cpu threshold (percentage 1 to 100), 0 means disabled", - "type": "integer", - "format": "uint32", - "x-go-name": "ScaleDownCpuThresh" - }, - "scale_up_cpu_thresh": { - "description": "(Deprecated) Scale up cpu threshold (percentage 1 to 100), 0 means disabled", - "type": "integer", - "format": "uint32", - "x-go-name": "ScaleUpCpuThresh" - }, - "stabilization_window_sec": { - "description": "Stabilization window is the time for which past triggers are considered; the largest scale factor is always taken.", - "type": "integer", - "format": "uint32", - "x-go-name": "StabilizationWindowSec" - }, - "target_active_connections": { - "description": "Target per-node number of active connections, 0 means disabled", - "type": "integer", - "format": "uint64", - "x-go-name": "TargetActiveConnections" - }, - "target_cpu": { - "description": "Target per-node cpu utilization (percentage 1 to 100), 0 means disabled", - "type": "integer", - "format": "uint32", - "x-go-name": "TargetCpu" - }, - "target_mem": { - "description": "Target per-node memory utilization (percentage 1 to 100), 0 means disabled", - "type": "integer", - "format": "uint32", - "x-go-name": "TargetMem" - }, - "trigger_time_sec": { - "description": "(Deprecated) Trigger time defines how long the target must be satified in seconds before acting upon it.", - "type": "integer", - "format": "uint32", - "x-go-name": "TriggerTimeSec" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "BillingOrganization": { - "type": "object", - "required": ["Name"], - "properties": { - "Address": { - "description": "Organization address", - "type": "string" - }, - "Address2": { - "description": "Organization address2", - "type": "string" - }, - "Children": { - "description": "Children belonging to this BillingOrganization", - "type": "string" - }, - "City": { - "description": "Organization city", - "type": "string" - }, - "Country": { - "description": "Organization country", - "type": "string" - }, - "CreatedAt": { - "type": "string", - "format": "date-time", - "readOnly": true - }, - "DeleteInProgress": { - "description": "Delete of this BillingOrganization is in progress", - "type": "boolean", - "readOnly": true - }, - "Email": { - "description": "Organization email", - "type": "string" - }, - "FirstName": { - "description": "Billing info first name", - "type": "string" - }, - "LastName": { - "description": "Billing info last name", - "type": "string" - }, - "Name": { - "description": "BillingOrganization name. Can only contain letters, digits, underscore, period, hyphen. It cannot have leading or trailing spaces or period. It cannot start with hyphen", - "type": "string" - }, - "Phone": { - "description": "Organization phone number", - "type": "string" - }, - "PostalCode": { - "description": "Organization postal code", - "type": "string" - }, - "State": { - "description": "Organization state", - "type": "string" - }, - "Type": { - "description": "Organization type: \"parent\" or \"self\"", - "type": "string" - }, - "UpdatedAt": { - "type": "string", - "format": "date-time", - "readOnly": true - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "CRMOverride": { - "description": "CRMOverride can be applied to commands that issue requests to the CRM.\nIt should only be used by administrators when bugs have caused the\nController and CRM to get out of sync. It allows commands from the\nController to ignore errors from the CRM, or ignore the CRM completely\n(messages will not be sent to CRM).\n\n0: `NO_OVERRIDE`\n1: `IGNORE_CRM_ERRORS`\n2: `IGNORE_CRM`\n3: `IGNORE_TRANSIENT_STATE`\n4: `IGNORE_CRM_AND_TRANSIENT_STATE`", - "type": "integer", - "format": "int32", - "title": "Overrides default CRM behaviour", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "Cloudlet": { - "description": "A Cloudlet is a set of compute resources at a particular location, provided by an Operator.", - "type": "object", - "title": "Cloudlet", - "required": ["key"], - "properties": { - "HostController": { - "description": "Address of the controller hosting the cloudlet services if it is running locally", - "type": "string" - }, - "access_vars": { - "description": "Variables required to access cloudlet", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "AccessVars" - }, - "alliance_orgs": { - "description": "This cloudlet will be treated as directly connected to these additional operator organizations for the purposes of FindCloudlet", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "AllianceOrgs" - }, - "chef_client_key": { - "description": "Chef client key", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "ChefClientKey" - }, - "config": { - "$ref": "#/definitions/PlatformConfig" - }, - "container_version": { - "description": "Cloudlet container version", - "type": "string", - "x-go-name": "ContainerVersion" - }, - "created_at": { - "$ref": "#/definitions/Timestamp" - }, - "crm_access_key_upgrade_required": { - "description": "CRM access key upgrade required", - "type": "boolean", - "x-go-name": "CrmAccessKeyUpgradeRequired" - }, - "crm_access_public_key": { - "description": "CRM access public key", - "type": "string", - "x-go-name": "CrmAccessPublicKey" - }, - "crm_override": { - "$ref": "#/definitions/CRMOverride" - }, - "default_resource_alert_threshold": { - "description": "Default resource alert threshold percentage", - "type": "integer", - "format": "int32", - "x-go-name": "DefaultResourceAlertThreshold" - }, - "delete_prepare": { - "description": "Preparing to be deleted", - "type": "boolean", - "x-go-name": "DeletePrepare" - }, - "deployment": { - "description": "Deployment type to bring up CRM services (docker, kubernetes)", - "type": "string", - "x-go-name": "Deployment" - }, - "deployment_local": { - "description": "Deploy cloudlet services locally", - "type": "boolean", - "x-go-name": "DeploymentLocal" - }, - "dns_label": { - "description": "DNS label that is unique within the region", - "type": "string", - "x-go-name": "DnsLabel" - }, - "enable_default_serverless_cluster": { - "description": "Enable experimental default multitenant (serverless) cluster", - "type": "boolean", - "x-go-name": "EnableDefaultServerlessCluster" - }, - "env_var": { - "description": "Single Key-Value pair of env var to be passed to CRM", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "EnvVar" - }, - "errors": { - "description": "Any errors trying to create, update, or delete the Cloudlet.", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Errors" - }, - "federation_config": { - "$ref": "#/definitions/FederationConfig" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "flavor": { - "$ref": "#/definitions/FlavorKey" - }, - "gpu_config": { - "$ref": "#/definitions/GPUConfig" - }, - "infra_api_access": { - "$ref": "#/definitions/InfraApiAccess" - }, - "infra_config": { - "$ref": "#/definitions/InfraConfig" - }, - "ip_support": { - "$ref": "#/definitions/IpSupport" - }, - "kafka_cluster": { - "description": "Operator provided kafka cluster endpoint to push events to", - "type": "string", - "x-go-name": "KafkaCluster" - }, - "kafka_password": { - "description": "Password for kafka SASL/PLAIN authentification, stored securely in secret storage and never visible externally", - "type": "string", - "x-go-name": "KafkaPassword" - }, - "kafka_user": { - "description": "Username for kafka SASL/PLAIN authentification, stored securely in secret storage and never visible externally", - "type": "string", - "x-go-name": "KafkaUser" - }, - "key": { - "$ref": "#/definitions/CloudletKey" - }, - "license_config_storage_path": { - "description": "GPU driver license config storage path", - "type": "string", - "x-go-name": "LicenseConfigStoragePath" - }, - "location": { - "$ref": "#/definitions/Loc" - }, - "maintenance_state": { - "$ref": "#/definitions/MaintenanceState" - }, - "notify_srv_addr": { - "description": "Address for the CRM notify listener to run on", - "type": "string", - "x-go-name": "NotifySrvAddr" - }, - "num_dynamic_ips": { - "description": "Number of dynamic IPs available for dynamic IP support", - "type": "integer", - "format": "int32", - "x-go-name": "NumDynamicIps" - }, - "override_policy_container_version": { - "description": "Override container version from policy file", - "type": "boolean", - "x-go-name": "OverridePolicyContainerVersion" - }, - "physical_name": { - "description": "Physical infrastructure cloudlet name", - "type": "string", - "x-go-name": "PhysicalName" - }, - "platform_high_availability": { - "description": "Enable platform H/A", - "type": "boolean", - "x-go-name": "PlatformHighAvailability" - }, - "platform_type": { - "$ref": "#/definitions/PlatformType" - }, - "res_tag_map": { - "description": "Optional resource to restagtbl key map key values = [gpu, nas, nic]", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/ResTagTableKey" - }, - "x-go-name": "ResTagMap" - }, - "resource_quotas": { - "description": "Resource quotas", - "type": "array", - "items": { - "$ref": "#/definitions/ResourceQuota" - }, - "x-go-name": "ResourceQuotas" - }, - "root_lb_fqdn": { - "description": "Root LB FQDN which is globally unique", - "type": "string", - "x-go-name": "RootLbFqdn" - }, - "secondary_crm_access_key_upgrade_required": { - "description": "CRM secondary access key upgrade required for H/A", - "type": "boolean", - "x-go-name": "SecondaryCrmAccessKeyUpgradeRequired" - }, - "secondary_crm_access_public_key": { - "description": "CRM secondary access public key for H/A", - "type": "string", - "x-go-name": "SecondaryCrmAccessPublicKey" - }, - "secondary_notify_srv_addr": { - "description": "Address for the secondary CRM notify listener to run on", - "type": "string", - "x-go-name": "SecondaryNotifySrvAddr" - }, - "single_kubernetes_cluster_owner": { - "description": "For single kubernetes cluster cloudlet platforms, cluster is owned by this organization instead of multi-tenant", - "type": "string", - "x-go-name": "SingleKubernetesClusterOwner" - }, - "state": { - "$ref": "#/definitions/TrackedState" - }, - "static_ips": { - "description": "List of static IPs for static IP support", - "type": "string", - "x-go-name": "StaticIps" - }, - "time_limits": { - "$ref": "#/definitions/OperationTimeLimits" - }, - "trust_policy": { - "description": "Optional Trust Policy", - "type": "string", - "x-go-name": "TrustPolicy" - }, - "trust_policy_state": { - "$ref": "#/definitions/TrackedState" - }, - "updated_at": { - "$ref": "#/definitions/Timestamp" - }, - "vm_image_version": { - "description": "EdgeCloud baseimage version where CRM services reside", - "type": "string", - "x-go-name": "VmImageVersion" - }, - "vm_pool": { - "description": "VM Pool", - "type": "string", - "x-go-name": "VmPool" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CloudletAllianceOrg": { - "type": "object", - "properties": { - "key": { - "$ref": "#/definitions/CloudletKey" - }, - "organization": { - "description": "Alliance organization", - "type": "string", - "x-go-name": "Organization" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CloudletInfo": { - "type": "object", - "title": "CloudletInfo provides information from the Cloudlet Resource Manager about the state of the Cloudlet.", - "properties": { - "active_crm_instance": { - "description": "Active HA instance", - "type": "string", - "x-go-name": "ActiveCrmInstance" - }, - "availability_zones": { - "description": "Availability Zones if any", - "type": "array", - "items": { - "$ref": "#/definitions/OSAZone" - }, - "x-go-name": "AvailabilityZones" - }, - "compatibility_version": { - "description": "Version for compatibility tracking", - "type": "integer", - "format": "uint32", - "x-go-name": "CompatibilityVersion" - }, - "container_version": { - "description": "Cloudlet container version", - "type": "string", - "x-go-name": "ContainerVersion" - }, - "controller": { - "description": "Connected controller unique id", - "type": "string", - "x-go-name": "Controller" - }, - "controller_cache_received": { - "description": "Indicates all controller data has been sent to CRM", - "type": "boolean", - "x-go-name": "ControllerCacheReceived" - }, - "errors": { - "description": "Any errors encountered while making changes to the Cloudlet", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Errors" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "flavors": { - "description": "Supported flavors by the Cloudlet", - "type": "array", - "items": { - "$ref": "#/definitions/FlavorInfo" - }, - "x-go-name": "Flavors" - }, - "key": { - "$ref": "#/definitions/CloudletKey" - }, - "maintenance_state": { - "$ref": "#/definitions/MaintenanceState" - }, - "node_infos": { - "description": "Cluster node info for serverless platforms (k8s multi-tenant cluster)", - "type": "array", - "items": { - "$ref": "#/definitions/NodeInfo" - }, - "x-go-name": "NodeInfos" - }, - "notify_id": { - "description": "Id of client assigned by server (internal use only)", - "type": "integer", - "format": "int64", - "x-go-name": "NotifyId" - }, - "os_images": { - "description": "Local Images availble to cloudlet", - "type": "array", - "items": { - "$ref": "#/definitions/OSImage" - }, - "x-go-name": "OsImages" - }, - "os_max_ram": { - "description": "Maximum Ram in MB on the Cloudlet", - "type": "integer", - "format": "uint64", - "x-go-name": "OsMaxRam" - }, - "os_max_vcores": { - "description": "Maximum number of VCPU cores on the Cloudlet", - "type": "integer", - "format": "uint64", - "x-go-name": "OsMaxVcores" - }, - "os_max_vol_gb": { - "description": "Maximum amount of disk in GB on the Cloudlet", - "type": "integer", - "format": "uint64", - "x-go-name": "OsMaxVolGb" - }, - "properties": { - "description": "Cloudlet properties", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "Properties" - }, - "release_version": { - "description": "Cloudlet release version", - "type": "string", - "x-go-name": "ReleaseVersion" - }, - "resources_snapshot": { - "$ref": "#/definitions/InfraResourcesSnapshot" - }, - "standby_crm": { - "description": "Denotes if info was reported by inactive", - "type": "boolean", - "x-go-name": "StandbyCrm" - }, - "state": { - "$ref": "#/definitions/CloudletState" - }, - "status": { - "$ref": "#/definitions/StatusInfo" - }, - "trust_policy_state": { - "$ref": "#/definitions/TrackedState" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CloudletKey": { - "type": "object", - "title": "CloudletKey uniquely identifies a Cloudlet.", - "properties": { - "federated_organization": { - "description": "Federated operator organization who shared this cloudlet", - "type": "string", - "x-go-name": "FederatedOrganization" - }, - "name": { - "description": "Name of the cloudlet", - "type": "string", - "x-go-name": "Name" - }, - "organization": { - "description": "Organization of the cloudlet site", - "type": "string", - "x-go-name": "Organization" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CloudletMgmtNode": { - "type": "object", - "properties": { - "name": { - "description": "Name of Cloudlet Mgmt Node", - "type": "string", - "x-go-name": "Name" - }, - "type": { - "description": "Type of Cloudlet Mgmt Node", - "type": "string", - "x-go-name": "Type" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CloudletPool": { - "description": "CloudletPool defines a pool of Cloudlets that have restricted access", - "type": "object", - "properties": { - "cloudlets": { - "description": "Cloudlets part of the pool", - "type": "array", - "items": { - "$ref": "#/definitions/CloudletKey" - }, - "x-go-name": "Cloudlets" - }, - "created_at": { - "$ref": "#/definitions/Timestamp" - }, - "delete_prepare": { - "description": "Preparing to be deleted", - "type": "boolean", - "x-go-name": "DeletePrepare" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "key": { - "$ref": "#/definitions/CloudletPoolKey" - }, - "updated_at": { - "$ref": "#/definitions/Timestamp" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CloudletPoolKey": { - "description": "CloudletPoolKey uniquely identifies a CloudletPool.", - "type": "object", - "title": "CloudletPool unique key", - "properties": { - "name": { - "description": "CloudletPool Name", - "type": "string", - "x-go-name": "Name" - }, - "organization": { - "description": "Name of the organization this pool belongs to", - "type": "string", - "x-go-name": "Organization" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CloudletPoolMember": { - "description": "CloudletPoolMember is used to add and remove a Cloudlet from a CloudletPool", - "type": "object", - "properties": { - "cloudlet": { - "$ref": "#/definitions/CloudletKey" - }, - "key": { - "$ref": "#/definitions/CloudletPoolKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CloudletProps": { - "description": "Infra properties used to set up cloudlet", - "type": "object", - "properties": { - "organization": { - "description": "Organization", - "type": "string", - "x-go-name": "Organization" - }, - "platform_type": { - "$ref": "#/definitions/PlatformType" - }, - "properties": { - "description": "Single Key-Value pair of env var to be passed to CRM", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/PropertyInfo" - }, - "x-go-name": "Properties" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CloudletRefs": { - "type": "object", - "title": "CloudletRefs track used resources and Clusters instantiated on a Cloudlet. Used resources are compared against max resources for a Cloudlet to determine if resources are available for a new Cluster to be instantiated on the Cloudlet.", - "properties": { - "cluster_insts": { - "description": "Clusters instantiated on the Cloudlet", - "type": "array", - "items": { - "$ref": "#/definitions/ClusterInstRefKey" - }, - "x-go-name": "ClusterInsts" - }, - "k8s_app_insts": { - "description": "K8s apps instantiated on the Cloudlet", - "type": "array", - "items": { - "$ref": "#/definitions/AppInstRefKey" - }, - "x-go-name": "K8SAppInsts" - }, - "key": { - "$ref": "#/definitions/CloudletKey" - }, - "opt_res_used_map": { - "description": "Used Optional Resources", - "type": "object", - "additionalProperties": { - "type": "integer", - "format": "uint32" - }, - "x-go-name": "OptResUsedMap" - }, - "reserved_auto_cluster_ids": { - "description": "Track reservable autoclusterinsts ids in use. This is a bitmap.", - "type": "integer", - "format": "uint64", - "x-go-name": "ReservedAutoClusterIds" - }, - "root_lb_ports": { - "description": "Used ports on root load balancer. Map key is public port, value is a bitmap for the protocol\nbitmap: bit 0: tcp, bit 1: udp", - "x-go-name": "RootLbPorts" - }, - "used_dynamic_ips": { - "description": "Used dynamic IPs", - "type": "integer", - "format": "int32", - "x-go-name": "UsedDynamicIps" - }, - "used_static_ips": { - "description": "Used static IPs", - "type": "string", - "x-go-name": "UsedStaticIps" - }, - "vm_app_insts": { - "description": "VM apps instantiated on the Cloudlet", - "type": "array", - "items": { - "$ref": "#/definitions/AppInstRefKey" - }, - "x-go-name": "VmAppInsts" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CloudletResMap": { - "description": "Optional resource input consists of a resource specifier and clouldkey name", - "type": "object", - "properties": { - "key": { - "$ref": "#/definitions/CloudletKey" - }, - "mapping": { - "description": "Resource mapping info", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "Mapping" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CloudletResourceQuotaProps": { - "type": "object", - "properties": { - "organization": { - "description": "Organization", - "type": "string", - "x-go-name": "Organization" - }, - "platform_type": { - "$ref": "#/definitions/PlatformType" - }, - "properties": { - "description": "Cloudlet resource properties", - "type": "array", - "items": { - "$ref": "#/definitions/InfraResource" - }, - "x-go-name": "Properties" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CloudletResourceUsage": { - "type": "object", - "properties": { - "info": { - "description": "Infra Resource information", - "type": "array", - "items": { - "$ref": "#/definitions/InfraResource" - }, - "x-go-name": "Info" - }, - "infra_usage": { - "description": "Show Infra based usage", - "type": "boolean", - "x-go-name": "InfraUsage" - }, - "key": { - "$ref": "#/definitions/CloudletKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CloudletState": { - "type": "integer", - "format": "int32", - "title": "CloudletState is the state of the Cloudlet.", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/dme-proto" - }, - "ClusterInst": { - "description": "ClusterInst is an instance of a Cluster on a Cloudlet.\nIt is defined by a Cluster, Cloudlet, and Developer key.", - "type": "object", - "title": "Cluster Instance", - "required": ["key"], - "properties": { - "allocated_ip": { - "description": "Allocated IP for dedicated access", - "type": "string", - "x-go-name": "AllocatedIp" - }, - "auto": { - "description": "Auto is set to true when automatically created by back-end (internal use only)", - "type": "boolean", - "x-go-name": "Auto" - }, - "auto_scale_policy": { - "description": "Auto scale policy name", - "type": "string", - "x-go-name": "AutoScalePolicy" - }, - "availability_zone": { - "description": "Optional Resource AZ if any", - "type": "string", - "x-go-name": "AvailabilityZone" - }, - "created_at": { - "$ref": "#/definitions/Timestamp" - }, - "crm_override": { - "$ref": "#/definitions/CRMOverride" - }, - "delete_prepare": { - "description": "Preparing to be deleted", - "type": "boolean", - "x-go-name": "DeletePrepare" - }, - "deployment": { - "description": "Deployment type (kubernetes or docker)", - "type": "string", - "x-go-name": "Deployment" - }, - "dns_label": { - "description": "DNS label that is unique within the cloudlet and among other AppInsts/ClusterInsts", - "type": "string", - "x-go-name": "DnsLabel" - }, - "errors": { - "description": "Any errors trying to create, update, or delete the ClusterInst on the Cloudlet.", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Errors" - }, - "external_volume_size": { - "description": "Size of external volume to be attached to nodes. This is for the root partition", - "type": "integer", - "format": "uint64", - "x-go-name": "ExternalVolumeSize" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "flavor": { - "$ref": "#/definitions/FlavorKey" - }, - "fqdn": { - "description": "FQDN is a globally unique DNS id for the ClusterInst", - "type": "string", - "x-go-name": "Fqdn" - }, - "image_name": { - "description": "Optional resource specific image to launch", - "type": "string", - "x-go-name": "ImageName" - }, - "ip_access": { - "$ref": "#/definitions/IpAccess" - }, - "key": { - "$ref": "#/definitions/ClusterInstKey" - }, - "liveness": { - "$ref": "#/definitions/Liveness" - }, - "master_node_flavor": { - "description": "Generic flavor for k8s master VM when worker nodes \u003e 0", - "type": "string", - "x-go-name": "MasterNodeFlavor" - }, - "multi_tenant": { - "description": "Multi-tenant kubernetes cluster", - "type": "boolean", - "x-go-name": "MultiTenant" - }, - "networks": { - "description": "networks to connect to", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Networks" - }, - "node_flavor": { - "description": "Cloudlet specific node flavor", - "type": "string", - "x-go-name": "NodeFlavor" - }, - "num_masters": { - "description": "Number of k8s masters (In case of docker deployment, this field is not required)", - "type": "integer", - "format": "uint32", - "x-go-name": "NumMasters" - }, - "num_nodes": { - "description": "Number of k8s nodes (In case of docker deployment, this field is not required)", - "type": "integer", - "format": "uint32", - "x-go-name": "NumNodes" - }, - "opt_res": { - "description": "Optional Resources required by OS flavor if any", - "type": "string", - "x-go-name": "OptRes" - }, - "reservable": { - "description": "If ClusterInst is reservable", - "type": "boolean", - "x-go-name": "Reservable" - }, - "reservation_ended_at": { - "$ref": "#/definitions/Timestamp" - }, - "reserved_by": { - "description": "For reservable EdgeCloud ClusterInsts, the current developer tenant", - "type": "string", - "x-go-name": "ReservedBy" - }, - "resources": { - "$ref": "#/definitions/InfraResources" - }, - "shared_volume_size": { - "description": "Size of an optional shared volume to be mounted on the master", - "type": "integer", - "format": "uint64", - "x-go-name": "SharedVolumeSize" - }, - "skip_crm_cleanup_on_failure": { - "description": "Prevents cleanup of resources on failure within CRM, used for diagnostic purposes", - "type": "boolean", - "x-go-name": "SkipCrmCleanupOnFailure" - }, - "state": { - "$ref": "#/definitions/TrackedState" - }, - "updated_at": { - "$ref": "#/definitions/Timestamp" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "ClusterInstKey": { - "description": "ClusterInstKey uniquely identifies a Cluster Instance (ClusterInst) or Cluster Instance state (ClusterInstInfo).", - "type": "object", - "title": "Cluster Instance unique key", - "properties": { - "cloudlet_key": { - "$ref": "#/definitions/CloudletKey" - }, - "cluster_key": { - "$ref": "#/definitions/ClusterKey" - }, - "organization": { - "description": "Name of Developer organization that this cluster belongs to", - "type": "string", - "x-go-name": "Organization" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "ClusterInstRefKey": { - "description": "ClusterInstRefKey is cluster instance key without cloudlet key.", - "type": "object", - "title": "ClusterInst Ref Key", - "properties": { - "cluster_key": { - "$ref": "#/definitions/ClusterKey" - }, - "organization": { - "description": "Name of Developer organization that this cluster belongs to", - "type": "string", - "x-go-name": "Organization" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "ClusterKey": { - "type": "object", - "title": "ClusterKey uniquely identifies a Cluster.", - "properties": { - "name": { - "description": "Cluster name", - "type": "string", - "x-go-name": "Name" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "ClusterRefs": { - "type": "object", - "title": "ClusterRefs track used resources within a ClusterInst. Each AppInst specifies a set of required resources (Flavor), so tracking resources used by Apps within a Cluster is necessary to determine if enough resources are available for another AppInst to be instantiated on a ClusterInst.", - "properties": { - "apps": { - "description": "App instances in the Cluster Instance", - "type": "array", - "items": { - "$ref": "#/definitions/ClusterRefsAppInstKey" - }, - "x-go-name": "Apps" - }, - "key": { - "$ref": "#/definitions/ClusterInstKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "ClusterRefsAppInstKey": { - "description": "ClusterRefsAppInstKey is an app instance key without the cluster inst key,\nbut including the virtual cluster name. This is used by the ClusterRefs\nto track AppInsts instantiated in the cluster.", - "type": "object", - "title": "ClusterRefs AppInst Key", - "properties": { - "app_key": { - "$ref": "#/definitions/AppKey" - }, - "v_cluster_name": { - "description": "Virtual cluster name", - "type": "string", - "x-go-name": "VClusterName" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CollectionInterval": { - "description": "Collection interval for Influxdb (Specifically used for cq intervals, because cannot gogoproto.casttype to Duration for repeated fields otherwise)", - "type": "object", - "properties": { - "interval": { - "$ref": "#/definitions/Duration" - }, - "retention": { - "$ref": "#/definitions/Duration" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "ConfigFile": { - "description": "ConfigFile", - "type": "object", - "properties": { - "config": { - "description": "Config file contents or URI reference", - "type": "string", - "x-go-name": "Config" - }, - "kind": { - "description": "Kind (type) of config, i.e. envVarsYaml, helmCustomizationYaml", - "type": "string", - "x-go-name": "Kind" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "ContainerInfo": { - "description": "ContainerInfo is infomation about containers running on a VM,", - "type": "object", - "title": "ContainerInfo", - "properties": { - "clusterip": { - "description": "IP within the CNI and is applicable to kubernetes only", - "type": "string", - "x-go-name": "Clusterip" - }, - "name": { - "description": "Name of the container", - "type": "string", - "x-go-name": "Name" - }, - "restarts": { - "description": "Restart count, applicable to kubernetes only", - "type": "integer", - "format": "int64", - "x-go-name": "Restarts" - }, - "status": { - "description": "Runtime status of the container", - "type": "string", - "x-go-name": "Status" - }, - "type": { - "description": "Type can be docker or kubernetes", - "type": "string", - "x-go-name": "Type" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "CreateUser": { - "type": "object", - "required": ["Name"], - "properties": { - "CreatedAt": { - "type": "string", - "format": "date-time", - "readOnly": true - }, - "Email": { - "description": "User email", - "type": "string" - }, - "EmailVerified": { - "description": "Email address has been verified", - "type": "boolean", - "readOnly": true - }, - "EnableTOTP": { - "description": "Enable or disable temporary one-time passwords for the account", - "type": "boolean" - }, - "FailedLogins": { - "description": "Number of failed login attempts since last successful login", - "type": "integer", - "format": "int64" - }, - "FamilyName": { - "description": "Family Name", - "type": "string" - }, - "GivenName": { - "description": "Given Name", - "type": "string" - }, - "Iter": { - "type": "integer", - "format": "int64", - "readOnly": true - }, - "LastFailedLogin": { - "description": "Last failed login time", - "type": "string", - "format": "date-time", - "readOnly": true - }, - "LastLogin": { - "description": "Last successful login time", - "type": "string", - "format": "date-time", - "readOnly": true - }, - "Locked": { - "description": "Account is locked", - "type": "boolean", - "readOnly": true - }, - "Metadata": { - "description": "Metadata", - "type": "string" - }, - "Name": { - "description": "User name. Can only contain letters, digits, underscore, period, hyphen. It cannot have leading or trailing spaces or period. It cannot start with hyphen", - "type": "string" - }, - "Nickname": { - "description": "Nick Name", - "type": "string" - }, - "PassCrackTimeSec": { - "type": "number", - "format": "double", - "readOnly": true - }, - "Passhash": { - "type": "string", - "readOnly": true - }, - "Picture": { - "type": "string", - "readOnly": true - }, - "Salt": { - "type": "string", - "readOnly": true - }, - "TOTPSharedKey": { - "type": "string", - "readOnly": true - }, - "UpdatedAt": { - "type": "string", - "format": "date-time", - "readOnly": true - }, - "verify": { - "$ref": "#/definitions/EmailRequest" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "DateTime": { - "description": "DateTime is a time but it serializes to ISO8601 format with millis\nIt knows how to read 3 different variations of a RFC3339 date time.\nMost APIs we encounter want either millisecond or second precision times.\nThis just tries to make it worry-free.", - "type": "string", - "format": "date-time", - "x-go-package": "github.com/go-openapi/strfmt" - }, - "DebugRequest": { - "type": "object", - "title": "DebugRequest. Keep everything in one struct to make it easy to send commands without having to change the code.", - "properties": { - "args": { - "description": "Additional arguments for cmd", - "type": "string", - "x-go-name": "Args" - }, - "cmd": { - "description": "Debug command (use \"help\" to see available commands)", - "type": "string", - "x-go-name": "Cmd" - }, - "id": { - "description": "Id used internally", - "type": "integer", - "format": "uint64", - "x-go-name": "Id" - }, - "levels": { - "description": "Comma separated list of debug level names: etcd,api,notify,dmereq,locapi,infra,metrics,upgrade,info,sampled,fedapi", - "type": "string", - "x-go-name": "Levels" - }, - "node": { - "$ref": "#/definitions/NodeKey" - }, - "pretty": { - "description": "if possible, make output pretty", - "type": "boolean", - "x-go-name": "Pretty" - }, - "timeout": { - "$ref": "#/definitions/Duration" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "DeleteType": { - "description": "DeleteType specifies if AppInst can be auto deleted or not\n\n0: `NO_AUTO_DELETE`\n1: `AUTO_DELETE`", - "type": "integer", - "format": "int32", - "title": "DeleteType", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "DeploymentCloudletRequest": { - "type": "object", - "properties": { - "app": { - "$ref": "#/definitions/App" - }, - "dry_run_deploy": { - "description": "Attempt to qualify cloudlet resources for deployment", - "type": "boolean", - "x-go-name": "DryRunDeploy" - }, - "num_nodes": { - "description": "Optional number of worker VMs in dry run K8s Cluster, default = 2", - "type": "integer", - "format": "uint32", - "x-go-name": "NumNodes" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "Device": { - "description": "Device represents a device on the EdgeCloud platform\nWe record when this device first showed up on our platform", - "type": "object", - "properties": { - "fields": { - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "first_seen": { - "$ref": "#/definitions/Timestamp" - }, - "key": { - "$ref": "#/definitions/DeviceKey" - }, - "last_seen": { - "$ref": "#/definitions/Timestamp" - }, - "notify_id": { - "description": "Id of client assigned by server (internal use only)", - "type": "integer", - "format": "int64", - "x-go-name": "NotifyId" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "DeviceKey": { - "description": "DeviceKey is an identifier for a given device on the EdgeCloud platform\nIt is defined by a unique id and unique id type\nAnd example of such a device is a MEL device that hosts several applications", - "type": "object", - "properties": { - "unique_id": { - "description": "Unique identification of the client device or user. May be overridden by the server.", - "type": "string", - "x-go-name": "UniqueId" - }, - "unique_id_type": { - "description": "Type of unique ID provided by the client", - "type": "string", - "x-go-name": "UniqueIdType" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "DeviceReport": { - "description": "DeviceReport is a reporting message. It takes a begining and end time\nfor the report", - "type": "object", - "properties": { - "begin": { - "$ref": "#/definitions/Timestamp" - }, - "end": { - "$ref": "#/definitions/Timestamp" - }, - "key": { - "$ref": "#/definitions/DeviceKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "Duration": { - "type": "integer", - "format": "int64", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "EmailRequest": { - "description": "Email request is used for password reset and to resend welcome\nverification email.", - "type": "object", - "properties": { - "email": { - "description": "User's email address", - "type": "string", - "x-go-name": "Email", - "readOnly": true - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "EventMatch": { - "type": "object", - "properties": { - "error": { - "description": "Error substring to match", - "type": "string", - "x-go-name": "Error" - }, - "failed": { - "description": "Failure status on event to match", - "type": "boolean", - "x-go-name": "Failed" - }, - "names": { - "description": "Names of events to match", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Names" - }, - "orgs": { - "description": "Organizations on events to match", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Orgs" - }, - "regions": { - "description": "Regions on events to match", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Regions" - }, - "tags": { - "description": "Tags on events to match", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "Tags" - }, - "types": { - "description": "Types of events to match", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Types" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/cloudcommon/node" - }, - "EventSearch": { - "type": "object", - "properties": { - "allowedorgs": { - "description": "Organizations allowed to access the event", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "AllowedOrgs" - }, - "endage": { - "$ref": "#/definitions/Duration" - }, - "endtime": { - "description": "End time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "EndTime" - }, - "from": { - "description": "Start offset if paging through results", - "type": "integer", - "format": "int64", - "x-go-name": "From" - }, - "limit": { - "description": "Display the last X events", - "type": "integer", - "format": "int64", - "x-go-name": "Limit" - }, - "match": { - "$ref": "#/definitions/EventMatch" - }, - "notmatch": { - "$ref": "#/definitions/EventMatch" - }, - "startage": { - "$ref": "#/definitions/Duration" - }, - "starttime": { - "description": "Start time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "StartTime" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/cloudcommon/node" - }, - "EventTerms": { - "type": "object", - "properties": { - "names": { - "description": "Names of events", - "type": "array", - "items": { - "$ref": "#/definitions/AggrVal" - }, - "x-go-name": "Names" - }, - "orgs": { - "description": "Organizations on events", - "type": "array", - "items": { - "$ref": "#/definitions/AggrVal" - }, - "x-go-name": "Orgs" - }, - "regions": { - "description": "Regions on events", - "type": "array", - "items": { - "$ref": "#/definitions/AggrVal" - }, - "x-go-name": "Regions" - }, - "tagkeys": { - "description": "Tag keys on events", - "type": "array", - "items": { - "$ref": "#/definitions/AggrVal" - }, - "x-go-name": "TagKeys" - }, - "types": { - "description": "Types of events", - "type": "array", - "items": { - "$ref": "#/definitions/AggrVal" - }, - "x-go-name": "Types" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/cloudcommon/node" - }, - "ExecRequest": { - "description": "ExecRequest is a common struct for enabling a connection to execute some work on a container", - "type": "object", - "properties": { - "access_url": { - "description": "Access URL", - "type": "string", - "x-go-name": "AccessUrl" - }, - "answer": { - "description": "Answer", - "type": "string", - "x-go-name": "Answer" - }, - "app_inst_key": { - "$ref": "#/definitions/AppInstKey" - }, - "cmd": { - "$ref": "#/definitions/RunCmd" - }, - "console": { - "$ref": "#/definitions/RunVMConsole" - }, - "container_id": { - "description": "ContainerId is the name or ID of the target container, if applicable", - "type": "string", - "x-go-name": "ContainerId" - }, - "edge_turn_addr": { - "description": "EdgeTurn Server Address", - "type": "string", - "x-go-name": "EdgeTurnAddr" - }, - "err": { - "description": "Any error message", - "type": "string", - "x-go-name": "Err" - }, - "log": { - "$ref": "#/definitions/ShowLog" - }, - "offer": { - "description": "Offer", - "type": "string", - "x-go-name": "Offer" - }, - "timeout": { - "$ref": "#/definitions/Duration" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "FederationConfig": { - "description": "Federation config associated with the cloudlet", - "type": "object", - "properties": { - "federation_name": { - "description": "Federation name", - "type": "string", - "x-go-name": "FederationName" - }, - "partner_federation_addr": { - "description": "Partner federation address", - "type": "string", - "x-go-name": "PartnerFederationAddr" - }, - "partner_federation_id": { - "description": "Partner federation ID", - "type": "string", - "x-go-name": "PartnerFederationId" - }, - "self_federation_id": { - "description": "Self federation ID", - "type": "string", - "x-go-name": "SelfFederationId" - }, - "zone_country_code": { - "description": "Cloudlet zone country code", - "type": "string", - "x-go-name": "ZoneCountryCode" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "Flavor": { - "description": "To put it simply, a flavor is an available hardware configuration for a server.\nIt defines the size of a virtual server that can be launched.", - "type": "object", - "title": "Flavors define the compute, memory, and storage capacity of computing instances.", - "properties": { - "delete_prepare": { - "description": "Preparing to be deleted", - "type": "boolean", - "x-go-name": "DeletePrepare" - }, - "disk": { - "description": "Amount of disk space in gigabytes", - "type": "integer", - "format": "uint64", - "x-go-name": "Disk" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "key": { - "$ref": "#/definitions/FlavorKey" - }, - "opt_res_map": { - "description": "Optional Resources request, key = gpu\nform: $resource=$kind:[$alias]$count ex: optresmap=gpu=vgpu:nvidia-63:1", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "OptResMap" - }, - "ram": { - "description": "RAM in megabytes", - "type": "integer", - "format": "uint64", - "x-go-name": "Ram" - }, - "vcpus": { - "description": "Number of virtual CPUs", - "type": "integer", - "format": "uint64", - "x-go-name": "Vcpus" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "FlavorInfo": { - "description": "Flavor details from the Cloudlet", - "type": "object", - "properties": { - "disk": { - "description": "Amount of disk in GB on the Cloudlet", - "type": "integer", - "format": "uint64", - "x-go-name": "Disk" - }, - "name": { - "description": "Name of the flavor on the Cloudlet", - "type": "string", - "x-go-name": "Name" - }, - "prop_map": { - "description": "OS Flavor Properties, if any", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "PropMap" - }, - "ram": { - "description": "Ram in MB on the Cloudlet", - "type": "integer", - "format": "uint64", - "x-go-name": "Ram" - }, - "vcpus": { - "description": "Number of VCPU cores on the Cloudlet", - "type": "integer", - "format": "uint64", - "x-go-name": "Vcpus" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "FlavorKey": { - "description": "FlavorKey uniquely identifies a Flavor.", - "type": "object", - "title": "Flavor", - "properties": { - "name": { - "description": "Flavor name", - "type": "string", - "x-go-name": "Name" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "FlavorMatch": { - "type": "object", - "properties": { - "availability_zone": { - "description": "availability zone for optional resources if any", - "type": "string", - "x-go-name": "AvailabilityZone" - }, - "flavor_name": { - "description": "Flavor name to lookup", - "type": "string", - "x-go-name": "FlavorName" - }, - "key": { - "$ref": "#/definitions/CloudletKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "FlowRateLimitAlgorithm": { - "type": "integer", - "format": "int32", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "FlowRateLimitSettings": { - "type": "object", - "required": ["key"], - "properties": { - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "key": { - "$ref": "#/definitions/FlowRateLimitSettingsKey" - }, - "settings": { - "$ref": "#/definitions/FlowSettings" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "FlowRateLimitSettingsKey": { - "type": "object", - "properties": { - "flow_settings_name": { - "description": "Unique name for FlowRateLimitSettings (there can be multiple FlowSettings per RateLimitSettingsKey)", - "type": "string", - "x-go-name": "FlowSettingsName" - }, - "rate_limit_key": { - "$ref": "#/definitions/RateLimitSettingsKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "FlowSettings": { - "type": "object", - "properties": { - "burst_size": { - "description": "Burst size for flow rate limiting (required for TokenBucketAlgorithm)", - "type": "integer", - "format": "int64", - "x-go-name": "BurstSize" - }, - "flow_algorithm": { - "$ref": "#/definitions/FlowRateLimitAlgorithm" - }, - "reqs_per_second": { - "description": "Requests per second for flow rate limiting", - "type": "number", - "format": "double", - "x-go-name": "ReqsPerSecond" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "GPUConfig": { - "type": "object", - "properties": { - "driver": { - "$ref": "#/definitions/GPUDriverKey" - }, - "license_config": { - "description": "Cloudlet specific license config to setup license (will be stored in secure storage)", - "type": "string", - "x-go-name": "LicenseConfig" - }, - "license_config_md5sum": { - "description": "Cloudlet specific license config md5sum, to ensure integrity of license config", - "type": "string", - "x-go-name": "LicenseConfigMd5Sum" - }, - "properties": { - "description": "Properties to identify specifics of GPU", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "Properties" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "GPUDriver": { - "type": "object", - "properties": { - "builds": { - "description": "List of GPU driver build", - "type": "array", - "items": { - "$ref": "#/definitions/GPUDriverBuild" - }, - "x-go-name": "Builds" - }, - "delete_prepare": { - "description": "Preparing to be deleted", - "type": "boolean", - "x-go-name": "DeletePrepare" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "ignore_state": { - "description": "Ignore state will ignore any action in-progress on the GPU driver", - "type": "boolean", - "x-go-name": "IgnoreState" - }, - "key": { - "$ref": "#/definitions/GPUDriverKey" - }, - "license_config": { - "description": "License config to setup license (will be stored in secure storage)", - "type": "string", - "x-go-name": "LicenseConfig" - }, - "license_config_md5sum": { - "description": "License config md5sum, to ensure integrity of license config", - "type": "string", - "x-go-name": "LicenseConfigMd5Sum" - }, - "license_config_storage_path": { - "description": "GPU driver license config storage path", - "type": "string", - "x-go-name": "LicenseConfigStoragePath" - }, - "properties": { - "description": "Additional properties associated with GPU driver build", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "Properties", - "example": "license server information, driver release date, etc" - }, - "state": { - "description": "State to figure out if any action on the GPU driver is in-progress", - "type": "string", - "x-go-name": "State" - }, - "storage_bucket_name": { - "description": "GPU driver storage bucket name", - "type": "string", - "x-go-name": "StorageBucketName" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "GPUDriverBuild": { - "type": "object", - "properties": { - "driver_path": { - "description": "Path where the driver package is located, if it is authenticated path,\nthen credentials must be passed as part of URL (one-time download path)", - "type": "string", - "x-go-name": "DriverPath" - }, - "driver_path_creds": { - "description": "Optional credentials (username:password) to access driver path", - "type": "string", - "x-go-name": "DriverPathCreds" - }, - "hypervisor_info": { - "description": "Info on hypervisor supported by vGPU driver", - "type": "string", - "x-go-name": "HypervisorInfo" - }, - "kernel_version": { - "description": "Kernel Version supported by GPU driver build", - "type": "string", - "x-go-name": "KernelVersion" - }, - "md5sum": { - "description": "Driver package md5sum to ensure package is not corrupted", - "type": "string", - "x-go-name": "Md5Sum" - }, - "name": { - "description": "Unique identifier key", - "type": "string", - "x-go-name": "Name" - }, - "operating_system": { - "$ref": "#/definitions/OSType" - }, - "storage_path": { - "description": "GPU driver build storage path", - "type": "string", - "x-go-name": "StoragePath" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "GPUDriverBuildMember": { - "type": "object", - "properties": { - "build": { - "$ref": "#/definitions/GPUDriverBuild" - }, - "ignore_state": { - "description": "Ignore state will ignore any action in-progress on the GPU driver", - "type": "boolean", - "x-go-name": "IgnoreState" - }, - "key": { - "$ref": "#/definitions/GPUDriverKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "GPUDriverKey": { - "description": "GPUDriverKey uniquely identifies a GPU driver", - "type": "object", - "title": "GPU Driver Key", - "properties": { - "name": { - "description": "Name of the driver", - "type": "string", - "x-go-name": "Name" - }, - "organization": { - "description": "Organization to which the driver belongs to", - "type": "string", - "x-go-name": "Organization" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "HealthCheck": { - "description": "Health check status gets set by external, or rootLB health check", - "type": "integer", - "format": "int32", - "title": "Health check status", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/dme-proto" - }, - "IdleReservableClusterInsts": { - "description": "Parameters for selecting reservable ClusterInsts to delete", - "type": "object", - "properties": { - "idle_time": { - "$ref": "#/definitions/Duration" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "ImageType": { - "type": "integer", - "format": "int32", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "InfraApiAccess": { - "description": "InfraApiAccess is the type of access available to Infra API endpoint\n\n0: `DIRECT_ACCESS`\n1: `RESTRICTED_ACCESS`", - "type": "integer", - "format": "int32", - "title": "Infra API Access", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "InfraConfig": { - "description": "Infra specific configuration used for Cloudlet deployments", - "type": "object", - "properties": { - "external_network_name": { - "description": "Infra specific external network name", - "type": "string", - "x-go-name": "ExternalNetworkName" - }, - "flavor_name": { - "description": "Infra specific flavor name", - "type": "string", - "x-go-name": "FlavorName" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "InfraResource": { - "description": "InfraResource is information about cloudlet infra resource.", - "type": "object", - "title": "InfraResource", - "properties": { - "alert_threshold": { - "description": "Generate alert when more than threshold percentage of resource is used", - "type": "integer", - "format": "int32", - "x-go-name": "AlertThreshold" - }, - "description": { - "description": "Resource description", - "type": "string", - "x-go-name": "Description" - }, - "infra_max_value": { - "description": "Resource infra max value", - "type": "integer", - "format": "uint64", - "x-go-name": "InfraMaxValue" - }, - "name": { - "description": "Resource name", - "type": "string", - "x-go-name": "Name" - }, - "quota_max_value": { - "description": "Resource quota max value", - "type": "integer", - "format": "uint64", - "x-go-name": "QuotaMaxValue" - }, - "units": { - "description": "Resource units", - "type": "string", - "x-go-name": "Units" - }, - "value": { - "description": "Resource value", - "type": "integer", - "format": "uint64", - "x-go-name": "Value" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "InfraResources": { - "description": "InfraResources is infomation about infrastructure resources.", - "type": "object", - "title": "InfraResources", - "properties": { - "vms": { - "description": "Virtual machine resources info", - "type": "array", - "items": { - "$ref": "#/definitions/VmInfo" - }, - "x-go-name": "Vms" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "InfraResourcesSnapshot": { - "description": "InfraResourcesSnapshot is snapshot of information about cloudlet infra resources.", - "type": "object", - "title": "InfraResourcesSnapshot", - "properties": { - "cluster_insts": { - "description": "List of clusterinsts this resources snapshot represent", - "type": "array", - "items": { - "$ref": "#/definitions/ClusterInstRefKey" - }, - "x-go-name": "ClusterInsts" - }, - "info": { - "description": "Infra Resource information", - "type": "array", - "items": { - "$ref": "#/definitions/InfraResource" - }, - "x-go-name": "Info" - }, - "k8s_app_insts": { - "description": "List of k8s appinsts this resources snapshot represent", - "type": "array", - "items": { - "$ref": "#/definitions/AppInstRefKey" - }, - "x-go-name": "K8SAppInsts" - }, - "platform_vms": { - "description": "Virtual machine resources info", - "type": "array", - "items": { - "$ref": "#/definitions/VmInfo" - }, - "x-go-name": "PlatformVms" - }, - "vm_app_insts": { - "description": "List of vm appinsts this resources snapshot represent", - "type": "array", - "items": { - "$ref": "#/definitions/AppInstRefKey" - }, - "x-go-name": "VmAppInsts" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "IpAccess": { - "description": "IpAccess indicates the type of RootLB that Developer requires for their App\n\n0: `IP_ACCESS_UNKNOWN`\n1: `IP_ACCESS_DEDICATED`\n3: `IP_ACCESS_SHARED`", - "type": "integer", - "format": "int32", - "title": "IpAccess Options", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "IpAddr": { - "description": "IpAddr is an address for a VM which may have an external and\ninternal component. Internal and external is with respect to the VM\nand are are often the same unless a natted or floating IP is used. If\ninternalIP is not reported it is the same as the ExternalIP.", - "type": "object", - "properties": { - "externalIp": { - "description": "External IP address", - "type": "string", - "x-go-name": "ExternalIp" - }, - "internalIp": { - "description": "Internal IP address", - "type": "string", - "x-go-name": "InternalIp" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "IpSupport": { - "description": "Static IP support indicates a set of static public IPs are available for use, and managed by the Controller. Dynamic indicates the Cloudlet uses a DHCP server to provide public IP addresses, and the controller has no control over which IPs are assigned.\n\n0: `IP_SUPPORT_UNKNOWN`\n1: `IP_SUPPORT_STATIC`\n2: `IP_SUPPORT_DYNAMIC`", - "type": "integer", - "format": "int32", - "title": "Type of public IP support", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "LProto": { - "description": "LProto indicates which protocol to use for accessing an application on a particular port. This is required by Kubernetes for port mapping.\n\n0: `L_PROTO_UNKNOWN`\n1: `L_PROTO_TCP`\n2: `L_PROTO_UDP`", - "type": "integer", - "format": "int32", - "title": "Layer4 Protocol", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/dme-proto" - }, - "Liveness": { - "description": "Liveness indicates if an object was created statically via an external API call, or dynamically via an internal algorithm.\n\n0: `LIVENESS_UNKNOWN`\n1: `LIVENESS_STATIC`\n2: `LIVENESS_DYNAMIC`\n3: `LIVENESS_AUTOPROV`", - "type": "integer", - "format": "int32", - "title": "Liveness Options", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "Loc": { - "description": "GPS Location", - "type": "object", - "properties": { - "altitude": { - "description": "On android only lat and long are guaranteed to be supplied\nAltitude in meters", - "type": "number", - "format": "double", - "x-go-name": "Altitude" - }, - "course": { - "description": "Course (IOS) / bearing (Android) (degrees east relative to true north)", - "type": "number", - "format": "double", - "x-go-name": "Course" - }, - "horizontal_accuracy": { - "description": "Horizontal accuracy (radius in meters)", - "type": "number", - "format": "double", - "x-go-name": "HorizontalAccuracy" - }, - "latitude": { - "description": "Latitude in WGS 84 coordinates", - "type": "number", - "format": "double", - "x-go-name": "Latitude" - }, - "longitude": { - "description": "Longitude in WGS 84 coordinates", - "type": "number", - "format": "double", - "x-go-name": "Longitude" - }, - "speed": { - "description": "Speed (IOS) / velocity (Android) (meters/sec)", - "type": "number", - "format": "double", - "x-go-name": "Speed" - }, - "timestamp": { - "$ref": "#/definitions/Timestamp" - }, - "vertical_accuracy": { - "description": "Vertical accuracy (meters)", - "type": "number", - "format": "double", - "x-go-name": "VerticalAccuracy" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/dme-proto" - }, - "MaintenanceState": { - "description": "Maintenance allows for planned downtimes of Cloudlets.\nThese states involve message exchanges between the Controller,\nthe AutoProv service, and the CRM. Certain states are only set\nby certain actors.", - "type": "integer", - "format": "int32", - "title": "Cloudlet Maintenance States", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/dme-proto" - }, - "MaxReqsRateLimitAlgorithm": { - "type": "integer", - "format": "int32", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "MaxReqsRateLimitSettings": { - "type": "object", - "required": ["key"], - "properties": { - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "key": { - "$ref": "#/definitions/MaxReqsRateLimitSettingsKey" - }, - "settings": { - "$ref": "#/definitions/MaxReqsSettings" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "MaxReqsRateLimitSettingsKey": { - "type": "object", - "properties": { - "max_reqs_settings_name": { - "description": "Unique name for MaxReqsRateLimitSettings (there can be multiple MaxReqsSettings per RateLimitSettingsKey)", - "type": "string", - "x-go-name": "MaxReqsSettingsName" - }, - "rate_limit_key": { - "$ref": "#/definitions/RateLimitSettingsKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "MaxReqsSettings": { - "type": "object", - "properties": { - "interval": { - "$ref": "#/definitions/Duration" - }, - "max_reqs_algorithm": { - "$ref": "#/definitions/MaxReqsRateLimitAlgorithm" - }, - "max_requests": { - "description": "Maximum number of requests for the given Interval", - "type": "integer", - "format": "int64", - "x-go-name": "MaxRequests" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "Network": { - "description": "Network defines additional networks which can be optionally assigned to a cloudlet key and used on a cluster instance", - "type": "object", - "properties": { - "connection_type": { - "$ref": "#/definitions/NetworkConnectionType" - }, - "delete_prepare": { - "description": "Preparing to be deleted", - "type": "boolean", - "x-go-name": "DeletePrepare" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "key": { - "$ref": "#/definitions/NetworkKey" - }, - "routes": { - "description": "List of routes", - "type": "array", - "items": { - "$ref": "#/definitions/Route" - }, - "x-go-name": "Routes" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "NetworkConnectionType": { - "description": "NetworkConnectionType is the supported list of network types to be optionally added to a cluster instance\n\n0: `UNDEFINED`\n1: `CONNECT_TO_LOAD_BALANCER`\n2: `CONNECT_TO_CLUSTER_NODES`\n3: `CONNECT_TO_ALL`", - "type": "integer", - "format": "int32", - "title": "Network Connection Type", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "NetworkKey": { - "type": "object", - "properties": { - "cloudlet_key": { - "$ref": "#/definitions/CloudletKey" - }, - "name": { - "description": "Network Name", - "type": "string", - "x-go-name": "Name" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "Node": { - "type": "object", - "title": "Node identifies an Edge Cloud service.", - "properties": { - "build_author": { - "description": "Build Author", - "type": "string", - "x-go-name": "BuildAuthor" - }, - "build_date": { - "description": "Build Date", - "type": "string", - "x-go-name": "BuildDate" - }, - "build_head": { - "description": "Build Head Version", - "type": "string", - "x-go-name": "BuildHead" - }, - "build_master": { - "description": "Build Master Version", - "type": "string", - "x-go-name": "BuildMaster" - }, - "container_version": { - "description": "Docker edge-cloud container version which node instance use", - "type": "string", - "x-go-name": "ContainerVersion" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "hostname": { - "description": "Hostname", - "type": "string", - "x-go-name": "Hostname" - }, - "internal_pki": { - "description": "Internal PKI Config", - "type": "string", - "x-go-name": "InternalPki" - }, - "key": { - "$ref": "#/definitions/NodeKey" - }, - "notify_id": { - "description": "Id of client assigned by server (internal use only)", - "type": "integer", - "format": "int64", - "x-go-name": "NotifyId" - }, - "properties": { - "description": "Additional properties", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "Properties" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "NodeInfo": { - "description": "NodeInfo is information about a Kubernetes node", - "type": "object", - "title": "NodeInfo", - "properties": { - "allocatable": { - "description": "Maximum allocatable resources on the node (capacity - overhead)", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Udec64" - }, - "x-go-name": "Allocatable" - }, - "capacity": { - "description": "Capacity of underlying resources on the node", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Udec64" - }, - "x-go-name": "Capacity" - }, - "name": { - "description": "Node name", - "type": "string", - "x-go-name": "Name" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "NodeKey": { - "description": "NodeKey uniquely identifies a DME or CRM node", - "type": "object", - "properties": { - "cloudlet_key": { - "$ref": "#/definitions/CloudletKey" - }, - "name": { - "description": "Name or hostname of node", - "type": "string", - "x-go-name": "Name" - }, - "region": { - "description": "Region the node is in", - "type": "string", - "x-go-name": "Region" - }, - "type": { - "description": "Node type", - "type": "string", - "x-go-name": "Type" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "OSAZone": { - "type": "object", - "properties": { - "name": { - "description": "OpenStack availability zone name", - "type": "string", - "x-go-name": "Name" - }, - "status": { - "description": "OpenStack availability zone status", - "type": "string", - "x-go-name": "Status" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "OSImage": { - "type": "object", - "properties": { - "disk_format": { - "description": "format qcow2, img, etc", - "type": "string", - "x-go-name": "DiskFormat" - }, - "name": { - "description": "image name", - "type": "string", - "x-go-name": "Name" - }, - "properties": { - "description": "image properties/metadata", - "type": "string", - "x-go-name": "Properties" - }, - "tags": { - "description": "optional tags present on image", - "type": "string", - "x-go-name": "Tags" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "OSType": { - "description": "OSType is the type of the Operator System\n\n0: `Linux`\n1: `Windows`\n20: `Others`", - "type": "integer", - "format": "int32", - "title": "Operating System Type", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "OperationTimeLimits": { - "description": "Time limits for cloudlet create, update and delete operations", - "type": "object", - "title": "Operation time limits", - "properties": { - "create_app_inst_timeout": { - "$ref": "#/definitions/Duration" - }, - "create_cluster_inst_timeout": { - "$ref": "#/definitions/Duration" - }, - "delete_app_inst_timeout": { - "$ref": "#/definitions/Duration" - }, - "delete_cluster_inst_timeout": { - "$ref": "#/definitions/Duration" - }, - "update_app_inst_timeout": { - "$ref": "#/definitions/Duration" - }, - "update_cluster_inst_timeout": { - "$ref": "#/definitions/Duration" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "OperatorCode": { - "description": "OperatorCode maps a carrier code to an Operator organization name", - "type": "object", - "properties": { - "code": { - "description": "MCC plus MNC code, or custom carrier code designation.", - "type": "string", - "x-go-name": "Code" - }, - "organization": { - "description": "Operator Organization name", - "type": "string", - "x-go-name": "Organization" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "Organization": { - "type": "object", - "required": ["Name"], - "properties": { - "Address": { - "description": "Organization address", - "type": "string" - }, - "CreatedAt": { - "type": "string", - "format": "date-time", - "readOnly": true - }, - "DeleteInProgress": { - "description": "Delete of this organization is in progress", - "type": "boolean", - "readOnly": true - }, - "EdgeboxOnly": { - "description": "Edgebox only operator organization", - "type": "boolean", - "readOnly": true - }, - "Name": { - "description": "Organization name. Can only contain letters, digits, underscore, period, hyphen. It cannot have leading or trailing spaces or period. It cannot start with hyphen", - "type": "string" - }, - "Parent": { - "type": "string", - "readOnly": true - }, - "Phone": { - "description": "Organization phone number", - "type": "string" - }, - "PublicImages": { - "description": "Images are made available to other organization", - "type": "boolean", - "readOnly": true - }, - "Type": { - "description": "Organization type: \"developer\" or \"operator\"", - "type": "string" - }, - "UpdatedAt": { - "type": "string", - "format": "date-time", - "readOnly": true - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "PasswordReset": { - "type": "object", - "required": ["token", "password"], - "properties": { - "password": { - "description": "User's new password", - "type": "string", - "x-go-name": "Password" - }, - "token": { - "description": "Authentication token", - "type": "string", - "x-go-name": "Token" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "PlatformConfig": { - "description": "Platform specific configuration required for Cloudlet management", - "type": "object", - "properties": { - "access_api_addr": { - "description": "controller access API address", - "type": "string", - "x-go-name": "AccessApiAddr" - }, - "app_dns_root": { - "description": "App domain name root", - "type": "string", - "x-go-name": "AppDnsRoot" - }, - "cache_dir": { - "description": "cache dir", - "type": "string", - "x-go-name": "CacheDir" - }, - "chef_client_interval": { - "$ref": "#/definitions/Duration" - }, - "chef_server_path": { - "description": "Path to Chef Server", - "type": "string", - "x-go-name": "ChefServerPath" - }, - "cleanup_mode": { - "description": "Internal cleanup flag", - "type": "boolean", - "x-go-name": "CleanupMode" - }, - "cloudlet_vm_image_path": { - "description": "Path to platform base image", - "type": "string", - "x-go-name": "CloudletVmImagePath" - }, - "commercial_certs": { - "description": "Get certs from vault or generate your own for the root load balancer", - "type": "boolean", - "x-go-name": "CommercialCerts" - }, - "container_registry_path": { - "description": "Path to Docker registry holding edge-cloud image", - "type": "string", - "x-go-name": "ContainerRegistryPath" - }, - "crm_access_private_key": { - "description": "crm access private key", - "type": "string", - "x-go-name": "CrmAccessPrivateKey" - }, - "deployment_tag": { - "description": "Deployment Tag", - "type": "string", - "x-go-name": "DeploymentTag" - }, - "env_var": { - "description": "Environment variables", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "EnvVar" - }, - "notify_ctrl_addrs": { - "description": "Address of controller notify port (can be multiple of these)", - "type": "string", - "x-go-name": "NotifyCtrlAddrs" - }, - "platform_tag": { - "description": "Tag of edge-cloud image", - "type": "string", - "x-go-name": "PlatformTag" - }, - "region": { - "description": "Region", - "type": "string", - "x-go-name": "Region" - }, - "secondary_crm_access_private_key": { - "description": "secondary crm access private key", - "type": "string", - "x-go-name": "SecondaryCrmAccessPrivateKey" - }, - "span": { - "description": "Span string", - "type": "string", - "x-go-name": "Span" - }, - "test_mode": { - "description": "Internal Test flag", - "type": "boolean", - "x-go-name": "TestMode" - }, - "thanos_recv_addr": { - "description": "Thanos Receive remote write address", - "type": "string", - "x-go-name": "ThanosRecvAddr" - }, - "tls_ca_file": { - "description": "TLS ca file", - "type": "string", - "x-go-name": "TlsCaFile" - }, - "tls_cert_file": { - "description": "TLS cert file", - "type": "string", - "x-go-name": "TlsCertFile" - }, - "tls_key_file": { - "description": "TLS key file", - "type": "string", - "x-go-name": "TlsKeyFile" - }, - "use_vault_pki": { - "description": "Use Vault certs and CAs for internal TLS communication", - "type": "boolean", - "x-go-name": "UseVaultPki" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "PlatformType": { - "description": "PlatformType is the supported list of cloudlet types\n\n0: `PLATFORM_TYPE_FAKE`\n1: `PLATFORM_TYPE_DIND`\n2: `PLATFORM_TYPE_OPENSTACK`\n3: `PLATFORM_TYPE_AZURE`\n4: `PLATFORM_TYPE_GCP`\n5: `PLATFORM_TYPE_EDGEBOX`\n6: `PLATFORM_TYPE_FAKEINFRA`\n7: `PLATFORM_TYPE_VSPHERE`\n8: `PLATFORM_TYPE_AWS_EKS`\n9: `PLATFORM_TYPE_VM_POOL`\n10: `PLATFORM_TYPE_AWS_EC2`\n11: `PLATFORM_TYPE_VCD`\n12: `PLATFORM_TYPE_K8S_BARE_METAL`\n13: `PLATFORM_TYPE_KIND`\n14: `PLATFORM_TYPE_KINDINFRA`\n15: `PLATFORM_TYPE_FAKE_SINGLE_CLUSTER`\n16: `PLATFORM_TYPE_FEDERATION`\n17: `PLATFORM_TYPE_FAKE_VM_POOL`", - "type": "integer", - "format": "int32", - "title": "Platform Type", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "PolicyKey": { - "type": "object", - "properties": { - "name": { - "description": "Policy name", - "type": "string", - "x-go-name": "Name" - }, - "organization": { - "description": "Name of the organization for the cluster that this policy will apply to", - "type": "string", - "x-go-name": "Organization" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "PowerState": { - "description": "Power State of the AppInst\n\n0: `POWER_STATE_UNKNOWN`\n1: `POWER_ON_REQUESTED`\n2: `POWERING_ON`\n3: `POWER_ON`\n4: `POWER_OFF_REQUESTED`\n5: `POWERING_OFF`\n6: `POWER_OFF`\n7: `REBOOT_REQUESTED`\n8: `REBOOTING`\n9: `REBOOT`\n10: `POWER_STATE_ERROR`", - "type": "integer", - "format": "int32", - "title": "Power State", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "PropertyInfo": { - "type": "object", - "properties": { - "description": { - "description": "Description of the property", - "type": "string", - "x-go-name": "Description" - }, - "internal": { - "description": "Is the property internal, not to be set by Operator", - "type": "boolean", - "x-go-name": "Internal" - }, - "mandatory": { - "description": "Is the property mandatory", - "type": "boolean", - "x-go-name": "Mandatory" - }, - "name": { - "description": "Name of the property", - "type": "string", - "x-go-name": "Name" - }, - "secret": { - "description": "Is the property a secret value, will be hidden", - "type": "boolean", - "x-go-name": "Secret" - }, - "value": { - "description": "Default value of the property", - "type": "string", - "x-go-name": "Value" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "QosSessionProfile": { - "description": "The selected profile name will be included\nas the \"qos\" value in the qos-senf/v1/sessions POST.", - "type": "integer", - "format": "int32", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "RateLimitSettings": { - "type": "object", - "properties": { - "flow_settings": { - "description": "Map of FlowSettings (key: FlowSettingsName, value: FlowSettings)", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/FlowSettings" - }, - "x-go-name": "FlowSettings" - }, - "key": { - "$ref": "#/definitions/RateLimitSettingsKey" - }, - "max_reqs_settings": { - "description": "Map of MaxReqsSettings (key: MaxReqsSettingsName, value: MaxReqsSettings)", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/MaxReqsSettings" - }, - "x-go-name": "MaxReqsSettings" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "RateLimitSettingsKey": { - "type": "object", - "properties": { - "api_endpoint_type": { - "$ref": "#/definitions/ApiEndpointType" - }, - "api_name": { - "description": "Name of API (eg. CreateApp or RegisterClient) (Use \"Global\" if not a specific API)", - "type": "string", - "x-go-name": "ApiName" - }, - "rate_limit_target": { - "$ref": "#/definitions/RateLimitTarget" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "RateLimitTarget": { - "type": "integer", - "format": "int32", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "RegionAlert": { - "type": "object", - "required": ["Region"], - "properties": { - "Alert": { - "$ref": "#/definitions/Alert" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionAlertPolicy": { - "type": "object", - "required": ["Region"], - "properties": { - "AlertPolicy": { - "$ref": "#/definitions/AlertPolicy" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionApp": { - "type": "object", - "required": ["Region"], - "properties": { - "App": { - "$ref": "#/definitions/App" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionAppAlertPolicy": { - "type": "object", - "required": ["Region"], - "properties": { - "AppAlertPolicy": { - "$ref": "#/definitions/AppAlertPolicy" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionAppAutoProvPolicy": { - "type": "object", - "required": ["Region"], - "properties": { - "AppAutoProvPolicy": { - "$ref": "#/definitions/AppAutoProvPolicy" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionAppInst": { - "type": "object", - "required": ["Region"], - "properties": { - "AppInst": { - "$ref": "#/definitions/AppInst" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionAppInstClientKey": { - "type": "object", - "required": ["Region"], - "properties": { - "AppInstClientKey": { - "$ref": "#/definitions/AppInstClientKey" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionAppInstKey": { - "type": "object", - "required": ["Region"], - "properties": { - "AppInstKey": { - "$ref": "#/definitions/AppInstKey" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionAppInstLatency": { - "type": "object", - "required": ["Region"], - "properties": { - "AppInstLatency": { - "$ref": "#/definitions/AppInstLatency" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionAppInstMetrics": { - "type": "object", - "properties": { - "AppInst": { - "$ref": "#/definitions/AppInstKey" - }, - "AppInsts": { - "description": "Application instances to filter for metrics", - "type": "array", - "items": { - "$ref": "#/definitions/AppInstKey" - } - }, - "Limit": { - "description": "Display the last X metrics", - "type": "integer", - "format": "int64" - }, - "NumSamples": { - "description": "Display X samples spaced out evenly over start and end times", - "type": "integer", - "format": "int64" - }, - "Region": { - "description": "Region name", - "type": "string" - }, - "Selector": { - "description": "Comma separated list of metrics to view. Available metrics: utilization, network, ipusage", - "type": "string" - }, - "endage": { - "$ref": "#/definitions/Duration" - }, - "endtime": { - "description": "End time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "EndTime" - }, - "startage": { - "$ref": "#/definitions/Duration" - }, - "starttime": { - "description": "Start time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "StartTime" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionAppInstRefs": { - "type": "object", - "required": ["Region"], - "properties": { - "AppInstRefs": { - "$ref": "#/definitions/AppInstRefs" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionAppInstUsage": { - "type": "object", - "properties": { - "AppInst": { - "$ref": "#/definitions/AppInstKey" - }, - "EndTime": { - "description": "Time up to which to display stats", - "type": "string", - "format": "date-time" - }, - "Region": { - "description": "Region name", - "type": "string" - }, - "StartTime": { - "description": "Time to start displaying stats from", - "type": "string", - "format": "date-time" - }, - "VmOnly": { - "description": "Show only VM-based apps", - "type": "boolean" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionAutoProvPolicy": { - "type": "object", - "required": ["Region"], - "properties": { - "AutoProvPolicy": { - "$ref": "#/definitions/AutoProvPolicy" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionAutoProvPolicyCloudlet": { - "type": "object", - "required": ["Region"], - "properties": { - "AutoProvPolicyCloudlet": { - "$ref": "#/definitions/AutoProvPolicyCloudlet" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionAutoScalePolicy": { - "type": "object", - "required": ["Region"], - "properties": { - "AutoScalePolicy": { - "$ref": "#/definitions/AutoScalePolicy" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionClientApiUsageMetrics": { - "type": "object", - "properties": { - "AppInst": { - "$ref": "#/definitions/AppInstKey" - }, - "DmeCloudlet": { - "description": "Cloudlet name where DME is running", - "type": "string" - }, - "DmeCloudletOrg": { - "description": "Operator organization where DME is running", - "type": "string" - }, - "Limit": { - "description": "Display the last X metrics", - "type": "integer", - "format": "int64" - }, - "Method": { - "description": "API call method, one of: FindCloudlet, PlatformFindCloudlet, RegisterClient, VerifyLocation", - "type": "string" - }, - "NumSamples": { - "description": "Display X samples spaced out evenly over start and end times", - "type": "integer", - "format": "int64" - }, - "Region": { - "description": "Region name", - "type": "string" - }, - "Selector": { - "description": "Comma separated list of metrics to view. Available metrics: utilization, network, ipusage", - "type": "string" - }, - "endage": { - "$ref": "#/definitions/Duration" - }, - "endtime": { - "description": "End time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "EndTime" - }, - "startage": { - "$ref": "#/definitions/Duration" - }, - "starttime": { - "description": "Start time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "StartTime" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionClientAppUsageMetrics": { - "type": "object", - "properties": { - "AppInst": { - "$ref": "#/definitions/AppInstKey" - }, - "DataNetworkType": { - "description": "Data network type used by client device. Can be used for selectors: latency", - "type": "string" - }, - "DeviceCarrier": { - "description": "Device carrier. Can be used for selectors: latency, deviceinfo", - "type": "string" - }, - "DeviceModel": { - "description": "Device model. Can be used for selectors: deviceinfo", - "type": "string" - }, - "DeviceOs": { - "description": "Device operating system. Can be used for selectors: deviceinfo", - "type": "string" - }, - "Limit": { - "description": "Display the last X metrics", - "type": "integer", - "format": "int64" - }, - "LocationTile": { - "description": "Provides the range of GPS coordinates for the location tile/square.\nFormat is: 'LocationUnderLongitude,LocationUnderLatitude_LocationOverLongitude,LocationOverLatitude_LocationTileLength'.\nLocationUnder are the GPS coordinates of the corner closest to (0,0) of the location tile.\nLocationOver are the GPS coordinates of the corner farthest from (0,0) of the location tile.\nLocationTileLength is the length (in kilometers) of one side of the location tile square", - "type": "string" - }, - "NumSamples": { - "description": "Display X samples spaced out evenly over start and end times", - "type": "integer", - "format": "int64" - }, - "Region": { - "description": "Region name", - "type": "string" - }, - "Selector": { - "description": "Comma separated list of metrics to view. Available metrics: utilization, network, ipusage", - "type": "string" - }, - "SignalStrength": { - "type": "string" - }, - "endage": { - "$ref": "#/definitions/Duration" - }, - "endtime": { - "description": "End time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "EndTime" - }, - "startage": { - "$ref": "#/definitions/Duration" - }, - "starttime": { - "description": "Start time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "StartTime" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionClientCloudletUsageMetrics": { - "type": "object", - "properties": { - "Cloudlet": { - "$ref": "#/definitions/CloudletKey" - }, - "DataNetworkType": { - "description": "Data network type used by client device. Can be used for selectors: latency", - "type": "string" - }, - "DeviceCarrier": { - "description": "Device carrier. Can be used for selectors: latency, deviceinfo", - "type": "string" - }, - "DeviceModel": { - "description": "Device model. Can be used for selectors: deviceinfo", - "type": "string" - }, - "DeviceOs": { - "description": "Device operating system. Can be used for selectors: deviceinfo", - "type": "string" - }, - "Limit": { - "description": "Display the last X metrics", - "type": "integer", - "format": "int64" - }, - "LocationTile": { - "description": "Provides the range of GPS coordinates for the location tile/square.\nFormat is: 'LocationUnderLongitude,LocationUnderLatitude_LocationOverLongitude,LocationOverLatitude_LocationTileLength'.\nLocationUnder are the GPS coordinates of the corner closest to (0,0) of the location tile.\nLocationOver are the GPS coordinates of the corner farthest from (0,0) of the location tile.\nLocationTileLength is the length (in kilometers) of one side of the location tile square", - "type": "string" - }, - "NumSamples": { - "description": "Display X samples spaced out evenly over start and end times", - "type": "integer", - "format": "int64" - }, - "Region": { - "description": "Region name", - "type": "string" - }, - "Selector": { - "description": "Comma separated list of metrics to view. Available metrics: utilization, network, ipusage", - "type": "string" - }, - "SignalStrength": { - "type": "string" - }, - "endage": { - "$ref": "#/definitions/Duration" - }, - "endtime": { - "description": "End time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "EndTime" - }, - "startage": { - "$ref": "#/definitions/Duration" - }, - "starttime": { - "description": "Start time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "StartTime" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionCloudlet": { - "type": "object", - "required": ["Region"], - "properties": { - "Cloudlet": { - "$ref": "#/definitions/Cloudlet" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionCloudletAllianceOrg": { - "type": "object", - "required": ["Region"], - "properties": { - "CloudletAllianceOrg": { - "$ref": "#/definitions/CloudletAllianceOrg" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionCloudletInfo": { - "type": "object", - "required": ["Region"], - "properties": { - "CloudletInfo": { - "$ref": "#/definitions/CloudletInfo" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionCloudletKey": { - "type": "object", - "required": ["Region"], - "properties": { - "CloudletKey": { - "$ref": "#/definitions/CloudletKey" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionCloudletMetrics": { - "type": "object", - "properties": { - "Cloudlet": { - "$ref": "#/definitions/CloudletKey" - }, - "Cloudlets": { - "description": "Cloudlet keys for metrics", - "type": "array", - "items": { - "$ref": "#/definitions/CloudletKey" - } - }, - "Limit": { - "description": "Display the last X metrics", - "type": "integer", - "format": "int64" - }, - "NumSamples": { - "description": "Display X samples spaced out evenly over start and end times", - "type": "integer", - "format": "int64" - }, - "PlatformType": { - "type": "string" - }, - "Region": { - "description": "Region name", - "type": "string" - }, - "Selector": { - "description": "Comma separated list of metrics to view. Available metrics: utilization, network, ipusage", - "type": "string" - }, - "endage": { - "$ref": "#/definitions/Duration" - }, - "endtime": { - "description": "End time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "EndTime" - }, - "startage": { - "$ref": "#/definitions/Duration" - }, - "starttime": { - "description": "Start time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "StartTime" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionCloudletPool": { - "type": "object", - "required": ["Region"], - "properties": { - "CloudletPool": { - "$ref": "#/definitions/CloudletPool" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionCloudletPoolMember": { - "type": "object", - "required": ["Region"], - "properties": { - "CloudletPoolMember": { - "$ref": "#/definitions/CloudletPoolMember" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionCloudletPoolUsage": { - "type": "object", - "properties": { - "CloudletPool": { - "$ref": "#/definitions/CloudletPoolKey" - }, - "EndTime": { - "description": "Time up to which to display stats", - "type": "string", - "format": "date-time" - }, - "Region": { - "description": "Region name", - "type": "string" - }, - "ShowVmAppsOnly": { - "description": "Show only VM-based apps", - "type": "boolean" - }, - "StartTime": { - "description": "Time to start displaying stats from", - "type": "string", - "format": "date-time" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionCloudletProps": { - "type": "object", - "required": ["Region"], - "properties": { - "CloudletProps": { - "$ref": "#/definitions/CloudletProps" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionCloudletRefs": { - "type": "object", - "required": ["Region"], - "properties": { - "CloudletRefs": { - "$ref": "#/definitions/CloudletRefs" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionCloudletResMap": { - "type": "object", - "required": ["Region"], - "properties": { - "CloudletResMap": { - "$ref": "#/definitions/CloudletResMap" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionCloudletResourceQuotaProps": { - "type": "object", - "required": ["Region"], - "properties": { - "CloudletResourceQuotaProps": { - "$ref": "#/definitions/CloudletResourceQuotaProps" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionCloudletResourceUsage": { - "type": "object", - "required": ["Region"], - "properties": { - "CloudletResourceUsage": { - "$ref": "#/definitions/CloudletResourceUsage" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionClusterInst": { - "type": "object", - "required": ["Region"], - "properties": { - "ClusterInst": { - "$ref": "#/definitions/ClusterInst" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionClusterInstKey": { - "type": "object", - "required": ["Region"], - "properties": { - "ClusterInstKey": { - "$ref": "#/definitions/ClusterInstKey" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionClusterInstMetrics": { - "type": "object", - "properties": { - "ClusterInst": { - "$ref": "#/definitions/ClusterInstKey" - }, - "ClusterInsts": { - "description": "Cluster instance keys for metrics", - "type": "array", - "items": { - "$ref": "#/definitions/ClusterInstKey" - } - }, - "Limit": { - "description": "Display the last X metrics", - "type": "integer", - "format": "int64" - }, - "NumSamples": { - "description": "Display X samples spaced out evenly over start and end times", - "type": "integer", - "format": "int64" - }, - "Region": { - "description": "Region name", - "type": "string" - }, - "Selector": { - "description": "Comma separated list of metrics to view. Available metrics: utilization, network, ipusage", - "type": "string" - }, - "endage": { - "$ref": "#/definitions/Duration" - }, - "endtime": { - "description": "End time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "EndTime" - }, - "startage": { - "$ref": "#/definitions/Duration" - }, - "starttime": { - "description": "Start time of the time range", - "type": "string", - "format": "date-time", - "x-go-name": "StartTime" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionClusterInstUsage": { - "type": "object", - "properties": { - "ClusterInst": { - "$ref": "#/definitions/ClusterInstKey" - }, - "EndTime": { - "description": "Time up to which to display stats", - "type": "string", - "format": "date-time" - }, - "Region": { - "description": "Region name", - "type": "string" - }, - "StartTime": { - "description": "Time to start displaying stats from", - "type": "string", - "format": "date-time" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionClusterRefs": { - "type": "object", - "required": ["Region"], - "properties": { - "ClusterRefs": { - "$ref": "#/definitions/ClusterRefs" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionDebugRequest": { - "type": "object", - "required": ["Region"], - "properties": { - "DebugRequest": { - "$ref": "#/definitions/DebugRequest" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionDeploymentCloudletRequest": { - "type": "object", - "required": ["Region"], - "properties": { - "DeploymentCloudletRequest": { - "$ref": "#/definitions/DeploymentCloudletRequest" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionDevice": { - "type": "object", - "required": ["Region"], - "properties": { - "Device": { - "$ref": "#/definitions/Device" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionDeviceReport": { - "type": "object", - "required": ["Region"], - "properties": { - "DeviceReport": { - "$ref": "#/definitions/DeviceReport" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionExecRequest": { - "type": "object", - "required": ["Region"], - "properties": { - "ExecRequest": { - "$ref": "#/definitions/ExecRequest" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionFlavor": { - "type": "object", - "required": ["Region"], - "properties": { - "Flavor": { - "$ref": "#/definitions/Flavor" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionFlavorMatch": { - "type": "object", - "required": ["Region"], - "properties": { - "FlavorMatch": { - "$ref": "#/definitions/FlavorMatch" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionFlowRateLimitSettings": { - "type": "object", - "required": ["Region"], - "properties": { - "FlowRateLimitSettings": { - "$ref": "#/definitions/FlowRateLimitSettings" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionGPUDriver": { - "type": "object", - "required": ["Region"], - "properties": { - "GPUDriver": { - "$ref": "#/definitions/GPUDriver" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionGPUDriverBuildMember": { - "type": "object", - "required": ["Region"], - "properties": { - "GPUDriverBuildMember": { - "$ref": "#/definitions/GPUDriverBuildMember" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionGPUDriverKey": { - "type": "object", - "required": ["Region"], - "properties": { - "GPUDriverKey": { - "$ref": "#/definitions/GPUDriverKey" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionIdleReservableClusterInsts": { - "type": "object", - "required": ["Region"], - "properties": { - "IdleReservableClusterInsts": { - "$ref": "#/definitions/IdleReservableClusterInsts" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionMaxReqsRateLimitSettings": { - "type": "object", - "required": ["Region"], - "properties": { - "MaxReqsRateLimitSettings": { - "$ref": "#/definitions/MaxReqsRateLimitSettings" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionNetwork": { - "type": "object", - "required": ["Region"], - "properties": { - "Network": { - "$ref": "#/definitions/Network" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionNode": { - "type": "object", - "required": ["Region"], - "properties": { - "Node": { - "$ref": "#/definitions/Node" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionOperatorCode": { - "type": "object", - "required": ["Region"], - "properties": { - "OperatorCode": { - "$ref": "#/definitions/OperatorCode" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionRateLimitSettings": { - "type": "object", - "required": ["Region"], - "properties": { - "RateLimitSettings": { - "$ref": "#/definitions/RateLimitSettings" - }, - "Region": { - "description": "Region name", - "type": "string" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionResTagTable": { - "type": "object", - "required": ["Region"], - "properties": { - "Region": { - "description": "Region name", - "type": "string" - }, - "ResTagTable": { - "$ref": "#/definitions/ResTagTable" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionResTagTableKey": { - "type": "object", - "required": ["Region"], - "properties": { - "Region": { - "description": "Region name", - "type": "string" - }, - "ResTagTableKey": { - "$ref": "#/definitions/ResTagTableKey" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionSettings": { - "type": "object", - "required": ["Region"], - "properties": { - "Region": { - "description": "Region name", - "type": "string" - }, - "Settings": { - "$ref": "#/definitions/Settings" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionTrustPolicy": { - "type": "object", - "required": ["Region"], - "properties": { - "Region": { - "description": "Region name", - "type": "string" - }, - "TrustPolicy": { - "$ref": "#/definitions/TrustPolicy" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionTrustPolicyException": { - "type": "object", - "required": ["Region"], - "properties": { - "Region": { - "description": "Region name", - "type": "string" - }, - "TrustPolicyException": { - "$ref": "#/definitions/TrustPolicyException" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionVMPool": { - "type": "object", - "required": ["Region"], - "properties": { - "Region": { - "description": "Region name", - "type": "string" - }, - "VMPool": { - "$ref": "#/definitions/VMPool" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RegionVMPoolMember": { - "type": "object", - "required": ["Region"], - "properties": { - "Region": { - "description": "Region name", - "type": "string" - }, - "VMPoolMember": { - "$ref": "#/definitions/VMPoolMember" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "ResTagTable": { - "type": "object", - "properties": { - "azone": { - "description": "Availability zone(s) of resource if required", - "type": "string", - "x-go-name": "Azone" - }, - "delete_prepare": { - "description": "Preparing to be deleted", - "type": "boolean", - "x-go-name": "DeletePrepare" - }, - "fields": { - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "key": { - "$ref": "#/definitions/ResTagTableKey" - }, - "tags": { - "description": "One or more string tags", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "Tags" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "ResTagTableKey": { - "type": "object", - "properties": { - "name": { - "description": "Resource Table Name", - "type": "string", - "x-go-name": "Name" - }, - "organization": { - "description": "Operator organization of the cloudlet site.", - "type": "string", - "x-go-name": "Organization" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "ResourceQuota": { - "description": "Resource Quota", - "type": "object", - "properties": { - "alert_threshold": { - "description": "Generate alert when more than threshold percentage of resource is used", - "type": "integer", - "format": "int32", - "x-go-name": "AlertThreshold" - }, - "name": { - "description": "Resource name on which to set quota", - "type": "string", - "x-go-name": "Name" - }, - "value": { - "description": "Quota value of the resource", - "type": "integer", - "format": "uint64", - "x-go-name": "Value" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "Result": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int64", - "x-go-name": "Code" - }, - "message": { - "type": "string", - "x-go-name": "Message" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "Role": { - "type": "object", - "properties": { - "org": { - "description": "Organization name", - "type": "string", - "x-go-name": "Org" - }, - "role": { - "description": "Role which defines the set of permissions", - "type": "string", - "x-go-name": "Role" - }, - "username": { - "description": "User name", - "type": "string", - "x-go-name": "Username" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "RolePerm": { - "type": "object", - "properties": { - "action": { - "description": "Action defines what type of action can be performed on a resource", - "type": "string", - "x-go-name": "Action" - }, - "resource": { - "description": "Resource defines a resource to act upon", - "type": "string", - "x-go-name": "Resource" - }, - "role": { - "description": "Role defines a collection of permissions, which are resource-action pairs", - "type": "string", - "x-go-name": "Role" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "Route": { - "type": "object", - "properties": { - "destination_cidr": { - "description": "Destination CIDR", - "type": "string", - "x-go-name": "DestinationCidr" - }, - "next_hop_ip": { - "description": "Next hop IP", - "type": "string", - "x-go-name": "NextHopIp" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "RunCmd": { - "type": "object", - "properties": { - "cloudlet_mgmt_node": { - "$ref": "#/definitions/CloudletMgmtNode" - }, - "command": { - "description": "Command or Shell", - "type": "string", - "x-go-name": "Command" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "RunVMConsole": { - "type": "object", - "properties": { - "url": { - "description": "VM Console URL", - "type": "string", - "x-go-name": "Url" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "SecurityRule": { - "type": "object", - "properties": { - "port_range_max": { - "description": "TCP or UDP port range end", - "type": "integer", - "format": "uint32", - "x-go-name": "PortRangeMax" - }, - "port_range_min": { - "description": "TCP or UDP port range start", - "type": "integer", - "format": "uint32", - "x-go-name": "PortRangeMin" - }, - "protocol": { - "description": "TCP, UDP, ICMP", - "type": "string", - "x-go-name": "Protocol" - }, - "remote_cidr": { - "description": "Remote CIDR X.X.X.X/X", - "type": "string", - "x-go-name": "RemoteCidr" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "ServerlessConfig": { - "type": "object", - "properties": { - "min_replicas": { - "description": "Minimum number of replicas when serverless", - "type": "integer", - "format": "uint32", - "x-go-name": "MinReplicas" - }, - "ram": { - "description": "RAM allocation in megabytes per container when serverless", - "type": "integer", - "format": "uint64", - "x-go-name": "Ram" - }, - "vcpus": { - "$ref": "#/definitions/Udec64" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "Settings": { - "description": "Global settings", - "type": "object", - "properties": { - "alert_policy_min_trigger_time": { - "$ref": "#/definitions/Duration" - }, - "appinst_client_cleanup_interval": { - "$ref": "#/definitions/Duration" - }, - "auto_deploy_interval_sec": { - "description": "Auto Provisioning Stats push and analysis interval (seconds)", - "type": "number", - "format": "double", - "x-go-name": "AutoDeployIntervalSec" - }, - "auto_deploy_max_intervals": { - "description": "Auto Provisioning Policy max allowed intervals", - "type": "integer", - "format": "uint32", - "x-go-name": "AutoDeployMaxIntervals" - }, - "auto_deploy_offset_sec": { - "description": "Auto Provisioning analysis offset from interval (seconds)", - "type": "number", - "format": "double", - "x-go-name": "AutoDeployOffsetSec" - }, - "chef_client_interval": { - "$ref": "#/definitions/Duration" - }, - "cleanup_reservable_auto_cluster_idletime": { - "$ref": "#/definitions/Duration" - }, - "cloudlet_maintenance_timeout": { - "$ref": "#/definitions/Duration" - }, - "cluster_auto_scale_averaging_duration_sec": { - "description": "Cluster auto scale averaging duration for stats to avoid spikes (seconds), avoid setting below 30s or it will not capture any measurements to average", - "type": "integer", - "format": "int64", - "x-go-name": "ClusterAutoScaleAveragingDurationSec" - }, - "cluster_auto_scale_retry_delay": { - "$ref": "#/definitions/Duration" - }, - "create_app_inst_timeout": { - "$ref": "#/definitions/Duration" - }, - "create_cloudlet_timeout": { - "$ref": "#/definitions/Duration" - }, - "create_cluster_inst_timeout": { - "$ref": "#/definitions/Duration" - }, - "delete_app_inst_timeout": { - "$ref": "#/definitions/Duration" - }, - "delete_cluster_inst_timeout": { - "$ref": "#/definitions/Duration" - }, - "disable_rate_limit": { - "description": "Disable rate limiting for APIs (default is false)", - "type": "boolean", - "x-go-name": "DisableRateLimit" - }, - "dme_api_metrics_collection_interval": { - "$ref": "#/definitions/Duration" - }, - "edge_events_metrics_collection_interval": { - "$ref": "#/definitions/Duration" - }, - "edge_events_metrics_continuous_queries_collection_intervals": { - "description": "List of collection intervals for Continuous Queries for EdgeEvents metrics", - "type": "array", - "items": { - "$ref": "#/definitions/CollectionInterval" - }, - "x-go-name": "EdgeEventsMetricsContinuousQueriesCollectionIntervals" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "influx_db_cloudlet_usage_metrics_retention": { - "$ref": "#/definitions/Duration" - }, - "influx_db_downsampled_metrics_retention": { - "$ref": "#/definitions/Duration" - }, - "influx_db_edge_events_metrics_retention": { - "$ref": "#/definitions/Duration" - }, - "influx_db_metrics_retention": { - "$ref": "#/definitions/Duration" - }, - "location_tile_side_length_km": { - "description": "Length of location tiles side for latency metrics (km)", - "type": "integer", - "format": "int64", - "x-go-name": "LocationTileSideLengthKm" - }, - "master_node_flavor": { - "description": "Default flavor for k8s master VM and \u003e 0 workers", - "type": "string", - "x-go-name": "MasterNodeFlavor" - }, - "max_tracked_dme_clients": { - "description": "Max DME clients to be tracked at the same time.", - "type": "integer", - "format": "int32", - "x-go-name": "MaxTrackedDmeClients" - }, - "platform_ha_instance_active_expire_time": { - "$ref": "#/definitions/Duration" - }, - "platform_ha_instance_poll_interval": { - "$ref": "#/definitions/Duration" - }, - "rate_limit_max_tracked_ips": { - "description": "Maximum number of IPs to track for rate limiting", - "type": "integer", - "format": "int64", - "x-go-name": "RateLimitMaxTrackedIps" - }, - "resource_snapshot_thread_interval": { - "$ref": "#/definitions/Duration" - }, - "shepherd_alert_evaluation_interval": { - "$ref": "#/definitions/Duration" - }, - "shepherd_health_check_interval": { - "$ref": "#/definitions/Duration" - }, - "shepherd_health_check_retries": { - "description": "Number of times Shepherd Health Check fails before we mark appInst down", - "type": "integer", - "format": "int32", - "x-go-name": "ShepherdHealthCheckRetries" - }, - "shepherd_metrics_collection_interval": { - "$ref": "#/definitions/Duration" - }, - "shepherd_metrics_scrape_interval": { - "$ref": "#/definitions/Duration" - }, - "update_app_inst_timeout": { - "$ref": "#/definitions/Duration" - }, - "update_cloudlet_timeout": { - "$ref": "#/definitions/Duration" - }, - "update_cluster_inst_timeout": { - "$ref": "#/definitions/Duration" - }, - "update_trust_policy_timeout": { - "$ref": "#/definitions/Duration" - }, - "update_vm_pool_timeout": { - "$ref": "#/definitions/Duration" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "ShowLog": { - "type": "object", - "properties": { - "follow": { - "description": "Stream data", - "type": "boolean", - "x-go-name": "Follow" - }, - "since": { - "description": "Show logs since either a duration ago (5s, 2m, 3h) or a timestamp (RFC3339)", - "type": "string", - "x-go-name": "Since" - }, - "tail": { - "description": "Show only a recent number of lines", - "type": "integer", - "format": "int32", - "x-go-name": "Tail" - }, - "timestamps": { - "description": "Show timestamps", - "type": "boolean", - "x-go-name": "Timestamps" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "StatusInfo": { - "description": "Used to track status of create/delete/update for resources that are being modified\nby the controller via the CRM. Tasks are the high level jobs that are to be completed.\nSteps are work items within a task. Within the clusterinst and appinst objects this\nis converted to a string", - "type": "object", - "title": "Status Information", - "properties": { - "max_tasks": { - "type": "integer", - "format": "uint32", - "x-go-name": "MaxTasks" - }, - "msg_count": { - "type": "integer", - "format": "uint32", - "x-go-name": "MsgCount" - }, - "msgs": { - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Msgs" - }, - "step_name": { - "type": "string", - "x-go-name": "StepName" - }, - "task_name": { - "type": "string", - "x-go-name": "TaskName" - }, - "task_number": { - "type": "integer", - "format": "uint32", - "x-go-name": "TaskNumber" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "Timestamp": { - "description": "All minutes are 60 seconds long. Leap seconds are \"smeared\" so that no leap\nsecond table is needed for interpretation, using a [24-hour linear\nsmear](https://developers.google.com/time/smear).\n\nThe range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By\nrestricting to that range, we ensure that we can convert to and from [RFC\n3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.\n\n# Examples\n\nExample 1: Compute Timestamp from POSIX `time()`.\n\nTimestamp timestamp;\ntimestamp.set_seconds(time(NULL));\ntimestamp.set_nanos(0);\n\nExample 2: Compute Timestamp from POSIX `gettimeofday()`.\n\nstruct timeval tv;\ngettimeofday(\u0026tv, NULL);\n\nTimestamp timestamp;\ntimestamp.set_seconds(tv.tv_sec);\ntimestamp.set_nanos(tv.tv_usec * 1000);\n\nExample 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.\n\nFILETIME ft;\nGetSystemTimeAsFileTime(\u0026ft);\nUINT64 ticks = (((UINT64)ft.dwHighDateTime) \u003c\u003c 32) | ft.dwLowDateTime;\n\nA Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z\nis 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.\nTimestamp timestamp;\ntimestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));\ntimestamp.set_nanos((INT32) ((ticks % 10000000) * 100));\n\nExample 4: Compute Timestamp from Java `System.currentTimeMillis()`.\n\nlong millis = System.currentTimeMillis();\n\nTimestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)\n.setNanos((int) ((millis % 1000) * 1000000)).build();\n\n\nExample 5: Compute Timestamp from current time in Python.\n\ntimestamp = Timestamp()\ntimestamp.GetCurrentTime()\n\n# JSON Mapping\n\nIn JSON format, the Timestamp type is encoded as a string in the\n[RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the\nformat is \"{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z\"\nwhere {year} is always expressed using four digits while {month}, {day},\n{hour}, {min}, and {sec} are zero-padded to two digits each. The fractional\nseconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),\nare optional. The \"Z\" suffix indicates the timezone (\"UTC\"); the timezone\nis required. A proto3 JSON serializer should always use UTC (as indicated by\n\"Z\") when printing the Timestamp type and a proto3 JSON parser should be\nable to accept both UTC and other timezones (as indicated by an offset).\n\nFor example, \"2017-01-15T01:30:15.01Z\" encodes 15.01 seconds past\n01:30 UTC on January 15, 2017.\n\nIn JavaScript, one can convert a Date object to this format using the\nstandard\n[toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)\nmethod. In Python, a standard `datetime.datetime` object can be converted\nto this format using\n[`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with\nthe time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use\nthe Joda Time's [`ISODateTimeFormat.dateTime()`](\nhttp://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D\n) to obtain a formatter capable of generating timestamps in this format.", - "type": "object", - "title": "A Timestamp represents a point in time independent of any time zone or local\ncalendar, encoded as a count of seconds and fractions of seconds at\nnanosecond resolution. The count is relative to an epoch at UTC midnight on\nJanuary 1, 1970, in the proleptic Gregorian calendar which extends the\nGregorian calendar backwards to year one.", - "properties": { - "nanos": { - "description": "Non-negative fractions of a second at nanosecond resolution. Negative\nsecond values with fractions must still have non-negative nanos values\nthat count forward in time. Must be from 0 to 999,999,999\ninclusive.", - "type": "integer", - "format": "int32", - "x-go-name": "Nanos" - }, - "seconds": { - "description": "Represents seconds of UTC time since Unix epoch\n1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n9999-12-31T23:59:59Z inclusive.", - "type": "integer", - "format": "int64", - "x-go-name": "Seconds" - } - }, - "x-go-package": "github.com/gogo/protobuf/types" - }, - "Token": { - "type": "object", - "properties": { - "token": { - "description": "Authentication token", - "type": "string", - "x-go-name": "Token" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "TrackedState": { - "description": "TrackedState is used to track the state of an object on a remote node,\ni.e. track the state of a ClusterInst object on the CRM (Cloudlet).\n\n0: `TRACKED_STATE_UNKNOWN`\n1: `NOT_PRESENT`\n2: `CREATE_REQUESTED`\n3: `CREATING`\n4: `CREATE_ERROR`\n5: `READY`\n6: `UPDATE_REQUESTED`\n7: `UPDATING`\n8: `UPDATE_ERROR`\n9: `DELETE_REQUESTED`\n10: `DELETING`\n11: `DELETE_ERROR`\n12: `DELETE_PREPARE`\n13: `CRM_INITOK`\n14: `CREATING_DEPENDENCIES`\n15: `DELETE_DONE`", - "type": "integer", - "format": "int32", - "title": "Tracked States", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "TrustPolicy": { - "description": "TrustPolicy defines security restrictions for cluster instances\nnodes scaled up or down.", - "type": "object", - "properties": { - "delete_prepare": { - "description": "Preparing to be deleted", - "type": "boolean", - "x-go-name": "DeletePrepare" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "key": { - "$ref": "#/definitions/PolicyKey" - }, - "outbound_security_rules": { - "description": "List of outbound security rules for whitelisting traffic", - "type": "array", - "items": { - "$ref": "#/definitions/SecurityRule" - }, - "x-go-name": "OutboundSecurityRules" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "TrustPolicyException": { - "type": "object", - "properties": { - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "key": { - "$ref": "#/definitions/TrustPolicyExceptionKey" - }, - "outbound_security_rules": { - "description": "List of outbound security rules for whitelisting traffic", - "type": "array", - "items": { - "$ref": "#/definitions/SecurityRule" - }, - "x-go-name": "OutboundSecurityRules" - }, - "state": { - "$ref": "#/definitions/TrustPolicyExceptionState" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "TrustPolicyExceptionKey": { - "type": "object", - "properties": { - "app_key": { - "$ref": "#/definitions/AppKey" - }, - "cloudlet_pool_key": { - "$ref": "#/definitions/CloudletPoolKey" - }, - "name": { - "description": "TrustPolicyExceptionKey name", - "type": "string", - "x-go-name": "Name" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "TrustPolicyExceptionState": { - "type": "integer", - "format": "int32", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "Udec64": { - "description": "Udec64 is an unsigned decimal with whole number values\nas uint64, and decimal values in nanos.", - "type": "object", - "title": "Udec64", - "properties": { - "nanos": { - "description": "Decimal value in nanos", - "type": "integer", - "format": "uint32", - "x-go-name": "Nanos" - }, - "whole": { - "description": "Whole number value", - "type": "integer", - "format": "uint64", - "x-go-name": "Whole" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "User": { - "type": "object", - "required": ["Name"], - "properties": { - "CreatedAt": { - "type": "string", - "format": "date-time", - "readOnly": true - }, - "Email": { - "description": "User email", - "type": "string" - }, - "EmailVerified": { - "description": "Email address has been verified", - "type": "boolean", - "readOnly": true - }, - "EnableTOTP": { - "description": "Enable or disable temporary one-time passwords for the account", - "type": "boolean" - }, - "FailedLogins": { - "description": "Number of failed login attempts since last successful login", - "type": "integer", - "format": "int64" - }, - "FamilyName": { - "description": "Family Name", - "type": "string" - }, - "GivenName": { - "description": "Given Name", - "type": "string" - }, - "Iter": { - "type": "integer", - "format": "int64", - "readOnly": true - }, - "LastFailedLogin": { - "description": "Last failed login time", - "type": "string", - "format": "date-time", - "readOnly": true - }, - "LastLogin": { - "description": "Last successful login time", - "type": "string", - "format": "date-time", - "readOnly": true - }, - "Locked": { - "description": "Account is locked", - "type": "boolean", - "readOnly": true - }, - "Metadata": { - "description": "Metadata", - "type": "string" - }, - "Name": { - "description": "User name. Can only contain letters, digits, underscore, period, hyphen. It cannot have leading or trailing spaces or period. It cannot start with hyphen", - "type": "string" - }, - "Nickname": { - "description": "Nick Name", - "type": "string" - }, - "PassCrackTimeSec": { - "type": "number", - "format": "double", - "readOnly": true - }, - "Passhash": { - "type": "string", - "readOnly": true - }, - "Picture": { - "type": "string", - "readOnly": true - }, - "Salt": { - "type": "string", - "readOnly": true - }, - "TOTPSharedKey": { - "type": "string", - "readOnly": true - }, - "UpdatedAt": { - "type": "string", - "format": "date-time", - "readOnly": true - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "UserLogin": { - "type": "object", - "required": ["username", "password"], - "properties": { - "apikey": { - "description": "API key if logging in using API key", - "type": "string", - "x-go-name": "ApiKey" - }, - "apikeyid": { - "description": "API key ID if logging in using API key", - "type": "string", - "x-go-name": "ApiKeyId" - }, - "password": { - "description": "User's password", - "type": "string", - "x-go-name": "Password" - }, - "totp": { - "description": "Temporary one-time password if 2-factor authentication is enabled", - "type": "string", - "x-go-name": "TOTP" - }, - "username": { - "description": "User's name or email address", - "type": "string", - "x-go-name": "Username" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" - }, - "VM": { - "type": "object", - "properties": { - "flavor": { - "$ref": "#/definitions/FlavorInfo" - }, - "group_name": { - "description": "VM Group Name", - "type": "string", - "x-go-name": "GroupName" - }, - "internal_name": { - "description": "VM Internal Name", - "type": "string", - "x-go-name": "InternalName" - }, - "name": { - "description": "VM Name", - "type": "string", - "x-go-name": "Name" - }, - "net_info": { - "$ref": "#/definitions/VMNetInfo" - }, - "state": { - "$ref": "#/definitions/VMState" - }, - "updated_at": { - "$ref": "#/definitions/Timestamp" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "VMNetInfo": { - "type": "object", - "properties": { - "external_ip": { - "description": "External IP", - "type": "string", - "x-go-name": "ExternalIp" - }, - "internal_ip": { - "description": "Internal IP", - "type": "string", - "x-go-name": "InternalIp" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "VMPool": { - "description": "VMPool defines a pool of VMs to be part of a Cloudlet", - "type": "object", - "properties": { - "crm_override": { - "$ref": "#/definitions/CRMOverride" - }, - "delete_prepare": { - "description": "Preparing to be deleted", - "type": "boolean", - "x-go-name": "DeletePrepare" - }, - "errors": { - "description": "Any errors trying to add/remove VM to/from VM Pool", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Errors" - }, - "fields": { - "description": "Fields are used for the Update API to specify which fields to apply", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "Fields" - }, - "key": { - "$ref": "#/definitions/VMPoolKey" - }, - "state": { - "$ref": "#/definitions/TrackedState" - }, - "vms": { - "description": "list of VMs to be part of VM pool", - "type": "array", - "items": { - "$ref": "#/definitions/VM" - }, - "x-go-name": "Vms" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "VMPoolKey": { - "description": "VMPoolKey uniquely identifies a VMPool.", - "type": "object", - "title": "VMPool unique key", - "properties": { - "name": { - "description": "Name of the vmpool", - "type": "string", - "x-go-name": "Name" - }, - "organization": { - "description": "Organization of the vmpool", - "type": "string", - "x-go-name": "Organization" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "VMPoolMember": { - "description": "VMPoolMember is used to add and remove VM from VM Pool", - "type": "object", - "properties": { - "crm_override": { - "$ref": "#/definitions/CRMOverride" - }, - "key": { - "$ref": "#/definitions/VMPoolKey" - }, - "vm": { - "$ref": "#/definitions/VM" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "VMState": { - "description": "VMState is the state of the VM\n\n0: `VM_FREE`\n1: `VM_IN_PROGRESS`\n2: `VM_IN_USE`\n3: `VM_ADD`\n4: `VM_REMOVE`\n5: `VM_UPDATE`\n6: `VM_FORCE_FREE`", - "type": "integer", - "format": "int32", - "title": "VM State", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "VirtualClusterInstKey": { - "description": "Virtual ClusterInstKey", - "type": "object", - "properties": { - "cloudlet_key": { - "$ref": "#/definitions/CloudletKey" - }, - "cluster_key": { - "$ref": "#/definitions/ClusterKey" - }, - "organization": { - "description": "Name of Developer organization that this cluster belongs to", - "type": "string", - "x-go-name": "Organization" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "VmAppOsType": { - "type": "integer", - "format": "int32", - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "VmInfo": { - "description": "VmInfo is information about Virtual Machine resources.", - "type": "object", - "title": "VmInfo", - "properties": { - "containers": { - "description": "Information about containers running in the VM", - "type": "array", - "items": { - "$ref": "#/definitions/ContainerInfo" - }, - "x-go-name": "Containers" - }, - "infraFlavor": { - "description": "Flavor allocated within the cloudlet infrastructure, distinct from the control plane flavor", - "type": "string", - "x-go-name": "InfraFlavor" - }, - "ipaddresses": { - "description": "IP addresses allocated to the VM", - "type": "array", - "items": { - "$ref": "#/definitions/IpAddr" - }, - "x-go-name": "Ipaddresses" - }, - "name": { - "description": "Virtual machine name", - "type": "string", - "x-go-name": "Name" - }, - "status": { - "description": "Runtime status of the VM", - "type": "string", - "x-go-name": "Status" - }, - "type": { - "description": "Type can be platformvm, platform-cluster-master, platform-cluster-primary-node, platform-cluster-secondary-node, sharedrootlb, dedicatedrootlb, cluster-master, cluster-k8s-node, cluster-docker-node, appvm", - "type": "string", - "x-go-name": "Type" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" - }, - "alert": { - "description": "Alert alert", - "type": "object", - "required": ["labels"], - "properties": { - "generatorURL": { - "description": "generator URL\nFormat: uri", - "type": "string", - "format": "uri", - "x-go-name": "GeneratorURL" - }, - "labels": { - "$ref": "#/definitions/labelSet" - } - }, - "x-go-name": "Alert", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "alertGroup": { - "description": "AlertGroup alert group", - "type": "object", - "required": ["alerts", "labels", "receiver"], - "properties": { - "alerts": { - "description": "alerts", - "type": "array", - "items": { - "$ref": "#/definitions/gettableAlert" - }, - "x-go-name": "Alerts" - }, - "labels": { - "$ref": "#/definitions/labelSet" - }, - "receiver": { - "$ref": "#/definitions/receiver" - } - }, - "x-go-name": "AlertGroup", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "alertGroups": { - "description": "AlertGroups alert groups", - "type": "array", - "items": { - "$ref": "#/definitions/alertGroup" - }, - "x-go-name": "AlertGroups", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "alertStatus": { - "description": "AlertStatus alert status", - "type": "object", - "required": ["inhibitedBy", "silencedBy", "state"], - "properties": { - "inhibitedBy": { - "description": "inhibited by", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "InhibitedBy" - }, - "silencedBy": { - "description": "silenced by", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "SilencedBy" - }, - "state": { - "description": "state", - "type": "string", - "enum": ["[unprocessed active suppressed]"], - "x-go-name": "State" - } - }, - "x-go-name": "AlertStatus", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "alertmanagerConfig": { - "description": "AlertmanagerConfig alertmanager config", - "type": "object", - "required": ["original"], - "properties": { - "original": { - "description": "original", - "type": "string", - "x-go-name": "Original" - } - }, - "x-go-name": "AlertmanagerConfig", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "alertmanagerStatus": { - "description": "AlertmanagerStatus alertmanager status", - "type": "object", - "required": ["cluster", "config", "uptime", "versionInfo"], - "properties": { - "cluster": { - "$ref": "#/definitions/clusterStatus" - }, - "config": { - "$ref": "#/definitions/alertmanagerConfig" - }, - "uptime": { - "description": "uptime", - "type": "string", - "format": "date-time", - "x-go-name": "Uptime" - }, - "versionInfo": { - "$ref": "#/definitions/versionInfo" - } - }, - "x-go-name": "AlertmanagerStatus", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "clusterStatus": { - "description": "ClusterStatus cluster status", - "type": "object", - "required": ["status"], - "properties": { - "name": { - "description": "name", - "type": "string", - "x-go-name": "Name" - }, - "peers": { - "description": "peers", - "type": "array", - "items": { - "$ref": "#/definitions/peerStatus" - }, - "x-go-name": "Peers" - }, - "status": { - "description": "status", - "type": "string", - "enum": ["[ready settling disabled]"], - "x-go-name": "Status" - } - }, - "x-go-name": "ClusterStatus", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "gettableAlert": { - "description": "GettableAlert gettable alert", - "type": "object", - "required": [ - "labels", - "annotations", - "endsAt", - "fingerprint", - "receivers", - "startsAt", - "status", - "updatedAt" - ], - "properties": { - "annotations": { - "$ref": "#/definitions/labelSet" - }, - "endsAt": { - "description": "ends at", - "type": "string", - "format": "date-time", - "x-go-name": "EndsAt" - }, - "fingerprint": { - "description": "fingerprint", - "type": "string", - "x-go-name": "Fingerprint" - }, - "generatorURL": { - "description": "generator URL\nFormat: uri", - "type": "string", - "format": "uri", - "x-go-name": "GeneratorURL" - }, - "labels": { - "$ref": "#/definitions/labelSet" - }, - "receivers": { - "description": "receivers", - "type": "array", - "items": { - "$ref": "#/definitions/receiver" - }, - "x-go-name": "Receivers" - }, - "startsAt": { - "description": "starts at", - "type": "string", - "format": "date-time", - "x-go-name": "StartsAt" - }, - "status": { - "$ref": "#/definitions/alertStatus" - }, - "updatedAt": { - "description": "updated at", - "type": "string", - "format": "date-time", - "x-go-name": "UpdatedAt" - } - }, - "x-go-name": "GettableAlert", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "gettableAlerts": { - "description": "GettableAlerts gettable alerts", - "type": "array", - "items": { - "$ref": "#/definitions/gettableAlert" - }, - "x-go-name": "GettableAlerts", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "gettableSilence": { - "description": "GettableSilence gettable silence", - "type": "object", - "required": [ - "comment", - "createdBy", - "endsAt", - "matchers", - "startsAt", - "id", - "status", - "updatedAt" - ], - "properties": { - "comment": { - "description": "comment", - "type": "string", - "x-go-name": "Comment" - }, - "createdBy": { - "description": "created by", - "type": "string", - "x-go-name": "CreatedBy" - }, - "endsAt": { - "description": "ends at", - "type": "string", - "format": "date-time", - "x-go-name": "EndsAt" - }, - "id": { - "description": "id", - "type": "string", - "x-go-name": "ID" - }, - "matchers": { - "$ref": "#/definitions/matchers" - }, - "startsAt": { - "description": "starts at", - "type": "string", - "format": "date-time", - "x-go-name": "StartsAt" - }, - "status": { - "$ref": "#/definitions/silenceStatus" - }, - "updatedAt": { - "description": "updated at", - "type": "string", - "format": "date-time", - "x-go-name": "UpdatedAt" - } - }, - "x-go-name": "GettableSilence", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "gettableSilences": { - "description": "GettableSilences gettable silences", - "type": "array", - "items": { - "$ref": "#/definitions/gettableSilence" - }, - "x-go-name": "GettableSilences", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "labelSet": { - "description": "LabelSet label set", - "type": "object", - "additionalProperties": { - "type": "string" - }, - "x-go-name": "LabelSet", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "matcher": { - "description": "Matcher matcher", - "type": "object", - "required": ["isRegex", "name", "value"], - "properties": { - "isRegex": { - "description": "is regex", - "type": "boolean", - "x-go-name": "IsRegex" - }, - "name": { - "description": "name", - "type": "string", - "x-go-name": "Name" - }, - "value": { - "description": "value", - "type": "string", - "x-go-name": "Value" - } - }, - "x-go-name": "Matcher", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "matchers": { - "description": "Matchers matchers", - "type": "array", - "items": { - "$ref": "#/definitions/matcher" - }, - "x-go-name": "Matchers", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "peerStatus": { - "description": "PeerStatus peer status", - "type": "object", - "required": ["address", "name"], - "properties": { - "address": { - "description": "address", - "type": "string", - "x-go-name": "Address" - }, - "name": { - "description": "name", - "type": "string", - "x-go-name": "Name" - } - }, - "x-go-name": "PeerStatus", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "postableAlert": { - "description": "PostableAlert postable alert", - "type": "object", - "required": ["labels"], - "properties": { - "annotations": { - "$ref": "#/definitions/labelSet" - }, - "endsAt": { - "description": "ends at\nFormat: date-time", - "type": "string", - "format": "date-time", - "x-go-name": "EndsAt" - }, - "generatorURL": { - "description": "generator URL\nFormat: uri", - "type": "string", - "format": "uri", - "x-go-name": "GeneratorURL" - }, - "labels": { - "$ref": "#/definitions/labelSet" - }, - "startsAt": { - "description": "starts at\nFormat: date-time", - "type": "string", - "format": "date-time", - "x-go-name": "StartsAt" - } - }, - "x-go-name": "PostableAlert", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "postableAlerts": { - "description": "PostableAlerts postable alerts", - "type": "array", - "items": { - "$ref": "#/definitions/postableAlert" - }, - "x-go-name": "PostableAlerts", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "postableSilence": { - "description": "PostableSilence postable silence", - "type": "object", - "required": ["comment", "createdBy", "endsAt", "matchers", "startsAt"], - "properties": { - "comment": { - "description": "comment", - "type": "string", - "x-go-name": "Comment" - }, - "createdBy": { - "description": "created by", - "type": "string", - "x-go-name": "CreatedBy" - }, - "endsAt": { - "description": "ends at", - "type": "string", - "format": "date-time", - "x-go-name": "EndsAt" - }, - "id": { - "description": "id", - "type": "string", - "x-go-name": "ID" - }, - "matchers": { - "$ref": "#/definitions/matchers" - }, - "startsAt": { - "description": "starts at", - "type": "string", - "format": "date-time", - "x-go-name": "StartsAt" - } - }, - "x-go-name": "PostableSilence", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "receiver": { - "description": "Receiver receiver", - "type": "object", - "required": ["name"], - "properties": { - "name": { - "description": "name", - "type": "string", - "x-go-name": "Name" - } - }, - "x-go-name": "Receiver", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "silence": { - "description": "Silence silence", - "type": "object", - "required": ["comment", "createdBy", "endsAt", "matchers", "startsAt"], - "properties": { - "comment": { - "description": "comment", - "type": "string", - "x-go-name": "Comment" - }, - "createdBy": { - "description": "created by", - "type": "string", - "x-go-name": "CreatedBy" - }, - "endsAt": { - "description": "ends at", - "type": "string", - "format": "date-time", - "x-go-name": "EndsAt" - }, - "matchers": { - "$ref": "#/definitions/matchers" - }, - "startsAt": { - "description": "starts at", - "type": "string", - "format": "date-time", - "x-go-name": "StartsAt" - } - }, - "x-go-name": "Silence", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "silenceStatus": { - "description": "SilenceStatus silence status", - "type": "object", - "required": ["state"], - "properties": { - "state": { - "description": "state", - "type": "string", - "enum": ["[expired active pending]"], - "x-go-name": "State" - } - }, - "x-go-name": "SilenceStatus", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - }, - "swaggerHttpResponse": { - "type": "object", - "properties": { - "message": { - "type": "string", - "x-go-name": "Message" - } - }, - "x-go-package": "github.com/edgexr/edge-cloud-platform/doc/swagger" - }, - "versionInfo": { - "description": "VersionInfo version info", - "type": "object", - "required": [ - "branch", - "buildDate", - "buildUser", - "goVersion", - "revision", - "version" - ], - "properties": { - "branch": { - "description": "branch", - "type": "string", - "x-go-name": "Branch" - }, - "buildDate": { - "description": "build date", - "type": "string", - "x-go-name": "BuildDate" - }, - "buildUser": { - "description": "build user", - "type": "string", - "x-go-name": "BuildUser" - }, - "goVersion": { - "description": "go version", - "type": "string", - "x-go-name": "GoVersion" - }, - "revision": { - "description": "revision", - "type": "string", - "x-go-name": "Revision" }, - "version": { - "description": "version", - "type": "string", - "x-go-name": "Version" - } - }, - "x-go-name": "VersionInfo", - "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" - } - }, - "responses": { - "authToken": { - "description": "Authentication Token", - "schema": { - "$ref": "#/definitions/Token" - } - }, - "badRequest": { - "description": "Status Bad Request", - "schema": { - "$ref": "#/definitions/Result" - } - }, - "forbidden": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/Result" - } - }, - "listBillingOrgs": { - "description": "List of BillingOrgs", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/BillingOrganization" - } - } - }, - "listOrgs": { - "description": "List of Orgs", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Organization" - } - } - }, - "listPerms": { - "description": "List of Permissions", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/RolePerm" - } - } - }, - "listRoles": { - "description": "List of Roles", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Role" - } - } - }, - "listUsers": { - "description": "List of Users", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/User" - } - } - }, - "loginBadRequest": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/swaggerHttpResponse" - } - }, - "notFound": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Result" - } - }, - "success": { - "description": "Success", - "schema": { - "$ref": "#/definitions/swaggerHttpResponse" + "summary": "Update app instance by key.", + "tags": [ + "AppInst" + ] } } }, "securityDefinitions": { "Bearer": { - "description": "Use [login API](#operation/Login) to generate bearer token (JWT) for authorization. Usage format - `Bearer \u003cJWT\u003e`", - "type": "apiKey", - "name": "Authorization", - "in": "header" + "type": "basic" } }, - "tags": [ - { - "description": "Authentication is done by a user name or email plus a password. The login function returns a JSON Web Token (JWT) once authenticated, that should be included with subsequent API calls to authenticate the user. The JWT will expire after a while for security, at which point you may need to log in again.", - "name": "Security" - }, - { - "description": "Users can be assigned roles for Organizations, allowing them to view or manage resources associated with the Organizations.", - "name": "User" - }, - { - "description": "Roles can be assigned to users for Organizations, allowing the users to view or manage resources associated with the Organizations.", - "name": "Role" - }, - { - "description": "Organizations group a set of resources together, for example Apps, App Instances, or Cloudlets. Users given a role in an Organization can operate on those resources in the scope provided by their role.", - "name": "Organization" - }, - { - "description": "OperatorCode maps a carrier code to an Operator organization name.", - "name": "OperatorCode" - }, - { - "description": "Flavors define the compute, memory and storage capacity of computing instances. To put it simply, a flavor is an available hardware configuration for a server. It defines the size of a virtual server that can be launched.", - "name": "Flavor" - }, - { - "description": "AutoProvPolicy defines the automated provisioning policy.", - "name": "AutoProvPolicy" - }, - { - "description": "AutoProvPolicy belonging to an app.", - "name": "AppAutoProvPolicy" - }, - { - "description": "AutoScalePolicy defines when and how ClusterInsts will have their nodes scaled up or down.", - "name": "AutoScalePolicy" - }, - { - "description": "PrivacyPolicy defines security restrictions for cluster instances nodes scaled up or down.", - "name": "PrivacyPolicy" - }, - { - "description": "AutoProvPolicyCloudlet belong to a cloudlet.", - "name": "AutoProvPolicyCloudlet" - }, - { - "description": "Pool of VMs to be part of a Cloudlet.", - "name": "VMPool" - }, - { - "description": "Members belong to a VMPool.", - "name": "VMPoolMember" - }, - { - "description": "Cloudlet is a set of compute resources at a particular location, provided by an Operator.", - "name": "Cloudlet" - }, - { - "description": "CloudletPool defines a pool of Cloudlets that have restricted access.", - "name": "CloudletPool" - }, - { - "description": "Member belong to a cloudlet pool.", - "name": "CloudletPoolMember" - }, - { - "description": "ClusterInst is an instance of a Cluster on a Cloudlet. It is defined by a Cluster, Cloudlet, and Developer key.", - "name": "ClusterInst" - }, - { - "description": "Provides information about the developer's application.", - "name": "App" - }, - { - "description": "AppInst is an instance of an App on a Cloudlet where it is defined by an App plus a ClusterInst key.", - "name": "AppInst" - }, - { - "description": "Infra properties used to setup cloudlet.", - "name": "CloudletProps" - }, - { - "description": "Cloudlet resouce mapping.", - "name": "CloudletResMap" - }, - { - "description": "To match a flavor with platform flavor.", - "name": "FlavorMatch" - }, - { - "description": "Client is an AppInst client that called FindCloudlet DME Api.", - "name": "AppInstClientKey" - }, - { - "description": "ExecRequest is a common struct for enabling a connection to execute some work on a container.", - "name": "ExecRequest" - }, - { - "description": "Collection of statistics related to Client/App/Cluster.", - "name": "DeveloperMetrics" - }, - { - "description": "Collection of statistics related to Cloudlet.", - "name": "OperatorMetrics" - }, - { - "description": "Collection of event/audit logs from edge services.", - "name": "Events" - }, - { - "description": "Usage details of App/Cluster.", - "name": "DeveloperUsage" - }, - { - "description": "Usage details of Cloudlet.", - "name": "OperatorUsage" - }, - { - "description": "Manage receiver for alerts from edge services.", - "name": "AlertReceiver" - }, - { - "description": "Manage additional networks which can be added to Cluster Instances.", - "name": "Network" - } - ], - "x-tagGroups": [ - { - "name": "Auth \u0026 User Management API", - "tags": ["Security", "User", "Role", "Organization"] - }, - { - "name": "Operator API", - "tags": [ - "Cloudlet", - "OperatorCode", - "Flavor", - "CloudletProps", - "CloudletResMap", - "FlavorMatch", - "CloudletPool", - "CloudletPoolMember", - "VMPool", - "VMPoolMember", - "OperatorMetrics", - "Events", - "OperatorUsage", - "AlertReceiver", - "Network" - ] - }, - { - "name": "Developer API", - "tags": [ - "ClusterInst", - "App", - "AppInst", - "AutoProvPolicy", - "AppAutoProvPolicy", - "AutoScalePolicy", - "PrivacyPolicy", - "AutoProvPolicyCloudlet", - "AppInstClientKey", - "ExecRequest", - "DeveloperMetrics", - "Events", - "DeveloperUsage", - "AlertReceiver" - ] - } - ] + "swagger": "2.0" } From 8f6fd944425fc2bcb11ea559af414331a9dfd227 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Wed, 15 Oct 2025 16:00:38 +0200 Subject: [PATCH 15/40] feat(edge-connect): Added Forgejo Runner Deployment in Edge Connect Example --- .../forgejo-runner/EdgeConnectConfig.yaml | 29 +++++ sdk/examples/forgejo-runner/README.md | 7 ++ .../forgejo-runner-deployment.yaml | 107 ++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 sdk/examples/forgejo-runner/EdgeConnectConfig.yaml create mode 100644 sdk/examples/forgejo-runner/README.md create mode 100644 sdk/examples/forgejo-runner/forgejo-runner-deployment.yaml diff --git a/sdk/examples/forgejo-runner/EdgeConnectConfig.yaml b/sdk/examples/forgejo-runner/EdgeConnectConfig.yaml new file mode 100644 index 0000000..caee349 --- /dev/null +++ b/sdk/examples/forgejo-runner/EdgeConnectConfig.yaml @@ -0,0 +1,29 @@ +# Is there a swagger file for the new EdgeConnect API? +# How does it differ from the EdgeXR API? +kind: edgeconnect-deployment +metadata: + name: "forgejo-runner-test" # name could be used for appName + appVersion: "1.0.0" + organization: "edp2" +spec: + # dockerApp: # Docker is OBSOLETE + # appVersion: "1.0.0" + # manifestFile: "./docker-compose.yaml" + # image: "https://registry-1.docker.io/library/nginx:latest" + k8sApp: + manifestFile: "./forgejo-runner-deployment.yaml" + infraTemplate: + - region: "EU" + cloudletOrg: "TelekomOP" + cloudletName: "Munich" + flavorName: "EU.small" + network: + outboundConnections: + - protocol: "tcp" + portRangeMin: 80 + portRangeMax: 80 + remoteCIDR: "0.0.0.0/0" + - protocol: "tcp" + portRangeMin: 443 + portRangeMax: 443 + remoteCIDR: "0.0.0.0/0" diff --git a/sdk/examples/forgejo-runner/README.md b/sdk/examples/forgejo-runner/README.md new file mode 100644 index 0000000..7a03a0f --- /dev/null +++ b/sdk/examples/forgejo-runner/README.md @@ -0,0 +1,7 @@ +# Forgejo Runner in Edge Connect Example + +Execute in the projects main directory: + +``` +go run . apply -f forgejo-runner/EdgeConnectConfig.yaml +``` diff --git a/sdk/examples/forgejo-runner/forgejo-runner-deployment.yaml b/sdk/examples/forgejo-runner/forgejo-runner-deployment.yaml new file mode 100644 index 0000000..93cd593 --- /dev/null +++ b/sdk/examples/forgejo-runner/forgejo-runner-deployment.yaml @@ -0,0 +1,107 @@ +apiVersion: v1 +kind: Service +metadata: + name: edgeconnect-coder-tcp + labels: + app: forgejo-runner +spec: + type: LoadBalancer + ports: + - name: tcp80 + protocol: TCP + port: 80 + targetPort: 80 + selector: + app: forgejo-runner +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: forgejo-runner + name: forgejo-runner +spec: + # Two replicas means that if one is busy, the other can pick up jobs. + replicas: 3 + selector: + matchLabels: + app: forgejo-runner + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: forgejo-runner + spec: + restartPolicy: Always + volumes: + - name: docker-certs + emptyDir: {} + - name: runner-data + emptyDir: {} + # Initialise our configuration file using offline registration + # https://forgejo.org/docs/v1.21/admin/actions/#offline-registration + initContainers: + - name: runner-register + image: code.forgejo.org/forgejo/runner:6.4.0 + command: + - "sh" + - "-c" + - | + forgejo-runner \ + register \ + --no-interactive \ + --token "#####RUNNER_REGISTRATION_TOKEN#####" \ + --name "edge-test" \ + --instance "https://garm-provider-test.t09.de" \ + --labels docker:docker://node:20-bookworm,ubuntu-22.04:docker://ghcr.io/catthehacker/ubuntu:act-22.04,ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:act-22.04 + volumeMounts: + - name: runner-data + mountPath: /data + containers: + - name: runner + image: code.forgejo.org/forgejo/runner:6.4.0 + command: + - "sh" + - "-c" + - | + while ! nc -z 127.0.0.1 2376 config.yml ; + sed -i -e "s|privileged: .*|privileged: true|" config.yml + sed -i -e "s|network: .*|network: host|" config.yml ; + sed -i -e "s|^ envs:$$| envs:\n DOCKER_HOST: tcp://127.0.0.1:2376\n DOCKER_TLS_VERIFY: 1\n DOCKER_CERT_PATH: /certs/client|" config.yml ; + sed -i -e "s|^ options:| options: -v /certs/client:/certs/client|" config.yml ; + sed -i -e "s| valid_volumes: \[\]$$| valid_volumes:\n - /certs/client|" config.yml ; + /bin/forgejo-runner --config config.yml daemon + securityContext: + allowPrivilegeEscalation: true + privileged: true + readOnlyRootFilesystem: false + runAsGroup: 0 + runAsNonRoot: false + runAsUser: 0 + env: + - name: DOCKER_HOST + value: tcp://localhost:2376 + - name: DOCKER_CERT_PATH + value: /certs/client + - name: DOCKER_TLS_VERIFY + value: "1" + volumeMounts: + - name: docker-certs + mountPath: /certs + - name: runner-data + mountPath: /data + - name: daemon + image: docker:28.0.4-dind + env: + - name: DOCKER_TLS_CERTDIR + value: /certs + securityContext: + privileged: true + volumeMounts: + - name: docker-certs + mountPath: /certs From 0b31409b26a57caac6be2bb0a8b5eadac723e94c Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Thu, 16 Oct 2025 11:12:57 +0200 Subject: [PATCH 16/40] feat(parser): add result parser of createappinstance and added a configurable timeout for that function --- cmd/app.go | 6 ++ cmd/root.go | 12 ++-- internal/apply/strategy_recreate.go | 43 ++++++++++++ sdk/edgeconnect/appinstance.go | 70 ++++++++++++++----- sdk/edgeconnect/appinstance_test.go | 55 +++++++++++++++ sdk/edgeconnect/client.go | 36 ++++++---- sdk/edgeconnect/types.go | 20 ++++++ .../comprehensive/EdgeConnectConfig.yaml | 4 +- .../forgejo-runner/EdgeConnectConfig.yaml | 4 +- .../forgejo-runner-deployment.yaml | 15 ++-- 10 files changed, 218 insertions(+), 47 deletions(-) diff --git a/cmd/app.go b/cmd/app.go index a9f187f..98914c6 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -53,6 +53,7 @@ func newSDKClient() *edgeconnect.Client { baseURL := viper.GetString("base_url") username := viper.GetString("username") password := viper.GetString("password") + createAppInstanceTimeout := viper.GetInt("create_app_instance_timeout") err := validateBaseURL(baseURL) if err != nil { @@ -60,15 +61,20 @@ func newSDKClient() *edgeconnect.Client { os.Exit(1) } + // Convert timeout from minutes to duration + timeout := time.Duration(createAppInstanceTimeout) * time.Minute + if username != "" && password != "" { return edgeconnect.NewClientWithCredentials(baseURL, username, password, edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), + edgeconnect.WithCreateAppInstanceTimeout(timeout), ) } // Fallback to no auth for now - in production should require auth return edgeconnect.NewClient(baseURL, edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), + edgeconnect.WithCreateAppInstanceTimeout(timeout), ) } diff --git a/cmd/root.go b/cmd/root.go index 480d8f5..7817622 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,10 +9,11 @@ import ( ) var ( - cfgFile string - baseURL string - username string - password string + cfgFile string + baseURL string + username string + password string + createAppInstanceTimeout int // timeout in minutes ) // rootCmd represents the base command when called without any subcommands @@ -39,10 +40,12 @@ func init() { rootCmd.PersistentFlags().StringVar(&baseURL, "base-url", "", "base URL for the Edge Connect API") rootCmd.PersistentFlags().StringVar(&username, "username", "", "username for authentication") rootCmd.PersistentFlags().StringVar(&password, "password", "", "password for authentication") + rootCmd.PersistentFlags().IntVar(&createAppInstanceTimeout, "create-app-instance-timeout", 10, "timeout in minutes for CreateAppInstance operations") viper.BindPFlag("base_url", rootCmd.PersistentFlags().Lookup("base-url")) viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username")) viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password")) + viper.BindPFlag("create_app_instance_timeout", rootCmd.PersistentFlags().Lookup("create-app-instance-timeout")) } func initConfig() { @@ -51,6 +54,7 @@ func initConfig() { viper.BindEnv("base_url", "EDGE_CONNECT_BASE_URL") viper.BindEnv("username", "EDGE_CONNECT_USERNAME") viper.BindEnv("password", "EDGE_CONNECT_PASSWORD") + viper.BindEnv("create_app_instance_timeout", "EDGE_CONNECT_CREATE_APP_INSTANCE_TIMEOUT") if cfgFile != "" { viper.SetConfigFile(cfgFile) diff --git a/internal/apply/strategy_recreate.go b/internal/apply/strategy_recreate.go index b2302ca..4e69e7d 100644 --- a/internal/apply/strategy_recreate.go +++ b/internal/apply/strategy_recreate.go @@ -4,6 +4,7 @@ package apply import ( "context" + "errors" "fmt" "strings" "sync" @@ -355,6 +356,15 @@ func (r *RecreateStrategy) executeInstanceActionWithRetry(ctx context.Context, a } lastErr = err + + // Check if error is retryable (don't retry 4xx client errors) + if !isRetryableError(err) { + r.logf("Failed to %s instance %s: %v (non-retryable error, giving up)", operation, action.InstanceName, err) + result.Error = fmt.Errorf("non-retryable error: %w", err) + result.Duration = time.Since(startTime) + return result + } + if attempt < r.config.MaxRetries { r.logf("Failed to %s instance %s: %v (will retry)", operation, action.InstanceName, err) } @@ -395,6 +405,15 @@ func (r *RecreateStrategy) executeAppActionWithRetry(ctx context.Context, action } lastErr = err + + // Check if error is retryable (don't retry 4xx client errors) + if !isRetryableError(err) { + r.logf("Failed to update app: %v (non-retryable error, giving up)", err) + result.Error = fmt.Errorf("non-retryable error: %w", err) + result.Duration = time.Since(startTime) + return result + } + if attempt < r.config.MaxRetries { r.logf("Failed to update app: %v (will retry)", err) } @@ -503,3 +522,27 @@ func (r *RecreateStrategy) logf(format string, v ...interface{}) { r.logger.Printf("[RecreateStrategy] "+format, v...) } } + +// isRetryableError determines if an error should be retried +// Returns false for client errors (4xx), true for server errors (5xx) and other transient errors +func isRetryableError(err error) bool { + if err == nil { + return false + } + + // Check if it's an APIError with a status code + var apiErr *edgeconnect.APIError + if errors.As(err, &apiErr) { + // Don't retry client errors (4xx) + if apiErr.StatusCode >= 400 && apiErr.StatusCode < 500 { + return false + } + // Retry server errors (5xx) + if apiErr.StatusCode >= 500 { + return true + } + } + + // Retry all other errors (network issues, timeouts, etc.) + return true +} diff --git a/sdk/edgeconnect/appinstance.go b/sdk/edgeconnect/appinstance.go index 8d568a8..a02d9f4 100644 --- a/sdk/edgeconnect/appinstance.go +++ b/sdk/edgeconnect/appinstance.go @@ -15,10 +15,14 @@ import ( // 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 { + // Apply CreateAppInstance-specific timeout + timeoutCtx, cancel := context.WithTimeout(ctx, c.CreateAppInstanceTimeout) + defer cancel() + transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/CreateAppInst" - resp, err := transport.Call(ctx, "POST", url, input) + resp, err := transport.Call(timeoutCtx, "POST", url, input) if err != nil { return fmt.Errorf("CreateAppInstance failed: %w", err) } @@ -28,6 +32,12 @@ func (c *Client) CreateAppInstance(ctx context.Context, input *NewAppInstanceInp return c.handleErrorResponse(resp, "CreateAppInstance") } + // Parse streaming JSON response + var appInstances []AppInstance + if err := c.parseStreamingAppInstanceResponse(resp, &appInstances); err != nil { + return fmt.Errorf("ShowAppInstance failed to parse response: %w", err) + } + c.logf("CreateAppInstance: %s/%s created successfully", input.AppInst.Key.Organization, input.AppInst.Key.Name) @@ -187,14 +197,41 @@ func (c *Client) DeleteAppInstance(ctx context.Context, appInstKey AppInstanceKe // 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] + var appInstances []AppInstance + var messages []string + var hasError bool + var errorCode int + var errorMessage string parseErr := sdkhttp.ParseJSONLines(resp.Body, 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 nil + } + + // Try parsing as Response[AppInstance] var response Response[AppInstance] if err := json.Unmarshal(line, &response); err != nil { return err } - responses = append(responses, response) + + if response.HasData() { + appInstances = append(appInstances, response.Data) + } + 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 }) @@ -202,25 +239,20 @@ func (c *Client) parseStreamingAppInstanceResponse(resp *http.Response, result i 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{ + // 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 } // Set result based on type diff --git a/sdk/edgeconnect/appinstance_test.go b/sdk/edgeconnect/appinstance_test.go index fc8bfc4..ac9c1eb 100644 --- a/sdk/edgeconnect/appinstance_test.go +++ b/sdk/edgeconnect/appinstance_test.go @@ -22,6 +22,7 @@ func TestCreateAppInstance(t *testing.T) { mockStatusCode int mockResponse string expectError bool + errorContains string }{ { name: "successful creation", @@ -63,6 +64,57 @@ func TestCreateAppInstance(t *testing.T) { mockResponse: `{"message": "organization is required"}`, expectError: true, }, + { + name: "HTTP 200 with CreateError message", + input: &NewAppInstanceInput{ + Region: "us-west", + AppInst: AppInstance{ + Key: AppInstanceKey{ + Organization: "testorg", + Name: "testinst", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + Flavor: Flavor{Name: "m4.small"}, + }, + }, + mockStatusCode: 200, + mockResponse: `{"data":{"message":"Creating"}} +{"data":{"message":"a service has been configured"}} +{"data":{"message":"CreateError"}} +{"data":{"message":"Deleting AppInst due to failure"}} +{"data":{"message":"Deleted AppInst successfully"}}`, + expectError: true, + errorContains: "CreateError", + }, + { + name: "HTTP 200 with result error code", + input: &NewAppInstanceInput{ + Region: "us-west", + AppInst: AppInstance{ + Key: AppInstanceKey{ + Organization: "testorg", + Name: "testinst", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + Flavor: Flavor{Name: "m4.small"}, + }, + }, + mockStatusCode: 200, + mockResponse: `{"data":{"message":"Creating"}} +{"data":{"message":"a service has been configured"}} +{"data":{"message":"CreateError"}} +{"data":{"message":"Deleting AppInst due to failure"}} +{"data":{"message":"Deleted AppInst successfully"}} +{"result":{"message":"Encountered failures: Create App Inst failed: deployments.apps is forbidden: User \"system:serviceaccount:edgexr:crm-telekomop-munich\" cannot create resource \"deployments\" in API group \"apps\" in the namespace \"gitea\"","code":400}}`, + expectError: true, + errorContains: "deployments.apps is forbidden", + }, } for _, tt := range tests { @@ -91,6 +143,9 @@ func TestCreateAppInstance(t *testing.T) { // Verify results if tt.expectError { assert.Error(t, err) + if tt.errorContains != "" { + assert.Contains(t, err.Error(), tt.errorContains) + } } else { assert.NoError(t, err) } diff --git a/sdk/edgeconnect/client.go b/sdk/edgeconnect/client.go index 2a79cff..bcac042 100644 --- a/sdk/edgeconnect/client.go +++ b/sdk/edgeconnect/client.go @@ -11,11 +11,12 @@ import ( // Client represents the EdgeXR Master Controller SDK client type Client struct { - BaseURL string - HTTPClient *http.Client - AuthProvider AuthProvider - RetryOpts RetryOptions - Logger Logger + BaseURL string + HTTPClient *http.Client + AuthProvider AuthProvider + RetryOpts RetryOptions + Logger Logger + CreateAppInstanceTimeout time.Duration } // RetryOptions configures retry behavior for API calls @@ -81,13 +82,21 @@ func WithLogger(logger Logger) Option { } } +// WithCreateAppInstanceTimeout sets the timeout for CreateAppInstance operations +func WithCreateAppInstanceTimeout(timeout time.Duration) Option { + return func(c *Client) { + c.CreateAppInstanceTimeout = timeout + } +} + // NewClient creates a new EdgeXR SDK client func NewClient(baseURL string, options ...Option) *Client { client := &Client{ - BaseURL: strings.TrimRight(baseURL, "/"), - HTTPClient: &http.Client{Timeout: 30 * time.Second}, - AuthProvider: NewNoAuthProvider(), - RetryOpts: DefaultRetryOptions(), + BaseURL: strings.TrimRight(baseURL, "/"), + HTTPClient: &http.Client{Timeout: 30 * time.Second}, + AuthProvider: NewNoAuthProvider(), + RetryOpts: DefaultRetryOptions(), + CreateAppInstanceTimeout: 10 * time.Minute, } for _, opt := range options { @@ -101,10 +110,11 @@ func NewClient(baseURL string, options ...Option) *Client { // This matches the existing client pattern from client/client.go func NewClientWithCredentials(baseURL, username, password string, options ...Option) *Client { client := &Client{ - BaseURL: strings.TrimRight(baseURL, "/"), - HTTPClient: &http.Client{Timeout: 30 * time.Second}, - AuthProvider: NewUsernamePasswordProvider(baseURL, username, password, nil), - RetryOpts: DefaultRetryOptions(), + BaseURL: strings.TrimRight(baseURL, "/"), + HTTPClient: &http.Client{Timeout: 30 * time.Second}, + AuthProvider: NewUsernamePasswordProvider(baseURL, username, password, nil), + RetryOpts: DefaultRetryOptions(), + CreateAppInstanceTimeout: 10 * time.Minute, } for _, opt := range options { diff --git a/sdk/edgeconnect/types.go b/sdk/edgeconnect/types.go index 6f82d51..7fd39fc 100644 --- a/sdk/edgeconnect/types.go +++ b/sdk/edgeconnect/types.go @@ -271,6 +271,26 @@ func (res *Response[T]) IsMessage() bool { return res.Data.GetMessage() != "" } +// ResultResponse represents an API result with error code +type ResultResponse struct { + Result struct { + Message string `json:"message"` + Code int `json:"code"` + } `json:"result"` +} + +func (r *ResultResponse) IsError() bool { + return r.Result.Code >= 400 +} + +func (r *ResultResponse) GetMessage() string { + return r.Result.Message +} + +func (r *ResultResponse) GetCode() int { + return r.Result.Code +} + // Responses wraps multiple API responses with metadata type Responses[T Message] struct { Responses []Response[T] `json:"responses,omitempty"` diff --git a/sdk/examples/comprehensive/EdgeConnectConfig.yaml b/sdk/examples/comprehensive/EdgeConnectConfig.yaml index b45abc4..fc24729 100644 --- a/sdk/examples/comprehensive/EdgeConnectConfig.yaml +++ b/sdk/examples/comprehensive/EdgeConnectConfig.yaml @@ -2,7 +2,7 @@ # How does it differ from the EdgeXR API? kind: edgeconnect-deployment metadata: - name: "edge-app-demo" # name could be used for appName + name: "edge-app-test" # name could be used for appName appVersion: "1.0.0" organization: "edp2" spec: @@ -15,7 +15,7 @@ spec: infraTemplate: - region: "EU" cloudletOrg: "TelekomOP" - cloudletName: "Munich" + cloudletName: "Hamburg" flavorName: "EU.small" network: outboundConnections: diff --git a/sdk/examples/forgejo-runner/EdgeConnectConfig.yaml b/sdk/examples/forgejo-runner/EdgeConnectConfig.yaml index caee349..d9a35aa 100644 --- a/sdk/examples/forgejo-runner/EdgeConnectConfig.yaml +++ b/sdk/examples/forgejo-runner/EdgeConnectConfig.yaml @@ -2,7 +2,7 @@ # How does it differ from the EdgeXR API? kind: edgeconnect-deployment metadata: - name: "forgejo-runner-test" # name could be used for appName + name: "forgejo-runner-edge" # name could be used for appName appVersion: "1.0.0" organization: "edp2" spec: @@ -15,7 +15,7 @@ spec: infraTemplate: - region: "EU" cloudletOrg: "TelekomOP" - cloudletName: "Munich" + cloudletName: "Hamburg" flavorName: "EU.small" network: outboundConnections: diff --git a/sdk/examples/forgejo-runner/forgejo-runner-deployment.yaml b/sdk/examples/forgejo-runner/forgejo-runner-deployment.yaml index 93cd593..199f969 100644 --- a/sdk/examples/forgejo-runner/forgejo-runner-deployment.yaml +++ b/sdk/examples/forgejo-runner/forgejo-runner-deployment.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: edgeconnect-coder-tcp + name: forgejo-runner-test-tcp labels: - app: forgejo-runner + app: forgejo-runner-test spec: type: LoadBalancer ports: @@ -12,26 +12,27 @@ spec: port: 80 targetPort: 80 selector: - app: forgejo-runner + app: forgejo-runner-test --- apiVersion: apps/v1 kind: Deployment metadata: labels: - app: forgejo-runner - name: forgejo-runner + app: forgejo-runner-test + name: forgejo-runner-test + #namespace: gitea spec: # Two replicas means that if one is busy, the other can pick up jobs. replicas: 3 selector: matchLabels: - app: forgejo-runner + app: forgejo-runner-test strategy: {} template: metadata: creationTimestamp: null labels: - app: forgejo-runner + app: forgejo-runner-test spec: restartPolicy: Always volumes: From 5f54082813cd50957a437c6a029b9965d8b1c8ce Mon Sep 17 00:00:00 2001 From: Stephan Lo Date: Thu, 16 Oct 2025 17:42:29 +0200 Subject: [PATCH 17/40] doc(create-appinstance): added documentation of the correct parsing of an errorneous app instance creation response --- sdk/examples/forgejo-runner/README.md | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/sdk/examples/forgejo-runner/README.md b/sdk/examples/forgejo-runner/README.md index 7a03a0f..afab91d 100644 --- a/sdk/examples/forgejo-runner/README.md +++ b/sdk/examples/forgejo-runner/README.md @@ -5,3 +5,49 @@ Execute in the projects main directory: ``` go run . apply -f forgejo-runner/EdgeConnectConfig.yaml ``` + +## Improvement: 'create app instance' with full respone body analysis (feature/parsing_createappinstance) + +When we have an errorneous deployment (example: "namespace: gitea" within the manifest) EdgeConnect will reject the deployment at some stage in its creation workflow. +Now we grab the error correctly in the workflow-response-array by parsing the whole response body and don't think the deployment worked just of only reading the first successfull step (which was the creation of the app instance): + +```bash +(devbox) stl@ubuntu-vpn:~/git/mms/ipcei-cis/edge-connect-client$ go run . apply -f sdk/examples/forgejo-runner/EdgeConnectConfig.yaml +📄 Loading configuration from: /home/stl/git/mms/ipcei-cis/edge-connect-client/sdk/examples/forgejo-runner/EdgeConnectConfig.yaml +✅ Configuration loaded successfully: forgejo-runner-edge +🔍 Analyzing current state and generating deployment plan... + +📋 Deployment Plan: +================================================== +Deployment plan for 'forgejo-runner-edge': +- CREATE application 'forgejo-runner-edge' + - Create new application +- CREATE 1 instance(s) across 1 cloudlet(s) +Estimated duration: 3m0s +================================================== + +This will perform 2 actions. Estimated time: 3m0s +Do you want to proceed? (yes/no): yes + +🚀 Starting deployment... +2025/10/16 16:58:08 [ResourceManager] Starting deployment: forgejo-runner-edge +2025/10/16 16:58:08 [ResourceManager] Validating deployment prerequisites for: forgejo-runner-edge +2025/10/16 16:58:08 [ResourceManager] Prerequisites validation passed +2025/10/16 16:58:08 [ResourceManager] Using deployment strategy: recreate +2025/10/16 16:58:08 [ResourceManager] Estimated deployment duration: 8m20s +2025/10/16 16:58:08 [RecreateStrategy] Starting recreate deployment strategy for: forgejo-runner-edge +2025/10/16 16:58:08 [RecreateStrategy] Phase 1: Deleting existing instances +2025/10/16 16:58:08 [RecreateStrategy] No existing instances to delete +2025/10/16 16:58:08 [RecreateStrategy] Phase 2: No app deletion needed (new app) +2025/10/16 16:58:08 [RecreateStrategy] Phase 3: Creating application +2025/10/16 16:58:10 [RecreateStrategy] Successfully created application: edp2/forgejo-runner-edge version 1.0.0 +2025/10/16 16:58:10 [RecreateStrategy] Phase 3 complete: app created successfully +2025/10/16 16:58:10 [RecreateStrategy] Phase 4: Creating new instances +2025/10/16 16:58:11 [RecreateStrategy] Failed to create instance forgejo-runner-edge-1.0.0-instance: failed to create instance: ShowAppInstance failed to parse response: API error: {"status_code":400,"code":"400","messages":["Encountered failures: Create App Inst failed: deployments.apps is forbidden: User \"system:serviceaccount:edgexr:crm-telekomop-hamburg\" cannot create resource \"deployments\" in API group \"apps\" in the namespace \"gitea\"","Creating","a service has been configured","your application is accessiable via https://forgejo-runner-test-tcp.apps.edge.platform.mg3.mdb.osc.live","CreateError","Deleting AppInst due to failure","Deleted AppInst successfully"]} (non-retryable error, giving up) +2025/10/16 16:58:11 [ResourceManager] Deployment failed, attempting rollback... +2025/10/16 16:58:11 [ResourceManager] Starting rollback for deployment: forgejo-runner-edge +2025/10/16 16:58:11 [ResourceManager] Successfully rolled back: forgejo-runner-edge +2025/10/16 16:58:11 [ResourceManager] Rollback completed successfully +Error: deployment failed: failed to create instance forgejo-runner-edge-1.0.0-instance: non-retryable error: failed to create instance: ShowAppInstance failed to parse response: API error: {"status_code":400,"code":"400","messages":["Encountered failures: Create App Inst failed: deployments.apps is forbidden: User \"system:serviceaccount:edgexr:crm-telekomop-hamburg\" cannot create resource \"deployments\" in API group \"apps\" in the namespace \"gitea\"","Creating","a service has been configured","your application is accessiable via https://forgejo-runner-test-tcp.apps.edge.platform.mg3.mdb.osc.live","CreateError","Deleting AppInst due to failure","Deleted AppInst successfully"]} +exit status 1 +``` \ No newline at end of file From dbf7ccb0d68fe4054bcfb9219e969b0fd98e2fe4 Mon Sep 17 00:00:00 2001 From: Stephan Lo Date: Fri, 17 Oct 2025 12:01:47 +0200 Subject: [PATCH 18/40] chore(http-timeout): removed timeout functionality when calling the API as it was not needed and malfunctional --- cmd/app.go | 6 ---- cmd/root.go | 12 +++---- sdk/edgeconnect/appinstance.go | 5 +-- sdk/edgeconnect/client.go | 36 +++++++------------ .../comprehensive/EdgeConnectConfig.yaml | 4 +-- 5 files changed, 20 insertions(+), 43 deletions(-) diff --git a/cmd/app.go b/cmd/app.go index 98914c6..a9f187f 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -53,7 +53,6 @@ func newSDKClient() *edgeconnect.Client { baseURL := viper.GetString("base_url") username := viper.GetString("username") password := viper.GetString("password") - createAppInstanceTimeout := viper.GetInt("create_app_instance_timeout") err := validateBaseURL(baseURL) if err != nil { @@ -61,20 +60,15 @@ func newSDKClient() *edgeconnect.Client { os.Exit(1) } - // Convert timeout from minutes to duration - timeout := time.Duration(createAppInstanceTimeout) * time.Minute - if username != "" && password != "" { return edgeconnect.NewClientWithCredentials(baseURL, username, password, edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), - edgeconnect.WithCreateAppInstanceTimeout(timeout), ) } // Fallback to no auth for now - in production should require auth return edgeconnect.NewClient(baseURL, edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), - edgeconnect.WithCreateAppInstanceTimeout(timeout), ) } diff --git a/cmd/root.go b/cmd/root.go index 7817622..480d8f5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,11 +9,10 @@ import ( ) var ( - cfgFile string - baseURL string - username string - password string - createAppInstanceTimeout int // timeout in minutes + cfgFile string + baseURL string + username string + password string ) // rootCmd represents the base command when called without any subcommands @@ -40,12 +39,10 @@ func init() { rootCmd.PersistentFlags().StringVar(&baseURL, "base-url", "", "base URL for the Edge Connect API") rootCmd.PersistentFlags().StringVar(&username, "username", "", "username for authentication") rootCmd.PersistentFlags().StringVar(&password, "password", "", "password for authentication") - rootCmd.PersistentFlags().IntVar(&createAppInstanceTimeout, "create-app-instance-timeout", 10, "timeout in minutes for CreateAppInstance operations") viper.BindPFlag("base_url", rootCmd.PersistentFlags().Lookup("base-url")) viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username")) viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password")) - viper.BindPFlag("create_app_instance_timeout", rootCmd.PersistentFlags().Lookup("create-app-instance-timeout")) } func initConfig() { @@ -54,7 +51,6 @@ func initConfig() { viper.BindEnv("base_url", "EDGE_CONNECT_BASE_URL") viper.BindEnv("username", "EDGE_CONNECT_USERNAME") viper.BindEnv("password", "EDGE_CONNECT_PASSWORD") - viper.BindEnv("create_app_instance_timeout", "EDGE_CONNECT_CREATE_APP_INSTANCE_TIMEOUT") if cfgFile != "" { viper.SetConfigFile(cfgFile) diff --git a/sdk/edgeconnect/appinstance.go b/sdk/edgeconnect/appinstance.go index a02d9f4..a26f45c 100644 --- a/sdk/edgeconnect/appinstance.go +++ b/sdk/edgeconnect/appinstance.go @@ -15,14 +15,11 @@ import ( // 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 { - // Apply CreateAppInstance-specific timeout - timeoutCtx, cancel := context.WithTimeout(ctx, c.CreateAppInstanceTimeout) - defer cancel() transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/CreateAppInst" - resp, err := transport.Call(timeoutCtx, "POST", url, input) + resp, err := transport.Call(ctx, "POST", url, input) if err != nil { return fmt.Errorf("CreateAppInstance failed: %w", err) } diff --git a/sdk/edgeconnect/client.go b/sdk/edgeconnect/client.go index bcac042..2a79cff 100644 --- a/sdk/edgeconnect/client.go +++ b/sdk/edgeconnect/client.go @@ -11,12 +11,11 @@ import ( // Client represents the EdgeXR Master Controller SDK client type Client struct { - BaseURL string - HTTPClient *http.Client - AuthProvider AuthProvider - RetryOpts RetryOptions - Logger Logger - CreateAppInstanceTimeout time.Duration + BaseURL string + HTTPClient *http.Client + AuthProvider AuthProvider + RetryOpts RetryOptions + Logger Logger } // RetryOptions configures retry behavior for API calls @@ -82,21 +81,13 @@ func WithLogger(logger Logger) Option { } } -// WithCreateAppInstanceTimeout sets the timeout for CreateAppInstance operations -func WithCreateAppInstanceTimeout(timeout time.Duration) Option { - return func(c *Client) { - c.CreateAppInstanceTimeout = timeout - } -} - // NewClient creates a new EdgeXR SDK client func NewClient(baseURL string, options ...Option) *Client { client := &Client{ - BaseURL: strings.TrimRight(baseURL, "/"), - HTTPClient: &http.Client{Timeout: 30 * time.Second}, - AuthProvider: NewNoAuthProvider(), - RetryOpts: DefaultRetryOptions(), - CreateAppInstanceTimeout: 10 * time.Minute, + BaseURL: strings.TrimRight(baseURL, "/"), + HTTPClient: &http.Client{Timeout: 30 * time.Second}, + AuthProvider: NewNoAuthProvider(), + RetryOpts: DefaultRetryOptions(), } for _, opt := range options { @@ -110,11 +101,10 @@ func NewClient(baseURL string, options ...Option) *Client { // This matches the existing client pattern from client/client.go func NewClientWithCredentials(baseURL, username, password string, options ...Option) *Client { client := &Client{ - BaseURL: strings.TrimRight(baseURL, "/"), - HTTPClient: &http.Client{Timeout: 30 * time.Second}, - AuthProvider: NewUsernamePasswordProvider(baseURL, username, password, nil), - RetryOpts: DefaultRetryOptions(), - CreateAppInstanceTimeout: 10 * time.Minute, + BaseURL: strings.TrimRight(baseURL, "/"), + HTTPClient: &http.Client{Timeout: 30 * time.Second}, + AuthProvider: NewUsernamePasswordProvider(baseURL, username, password, nil), + RetryOpts: DefaultRetryOptions(), } for _, opt := range options { diff --git a/sdk/examples/comprehensive/EdgeConnectConfig.yaml b/sdk/examples/comprehensive/EdgeConnectConfig.yaml index fc24729..b45abc4 100644 --- a/sdk/examples/comprehensive/EdgeConnectConfig.yaml +++ b/sdk/examples/comprehensive/EdgeConnectConfig.yaml @@ -2,7 +2,7 @@ # How does it differ from the EdgeXR API? kind: edgeconnect-deployment metadata: - name: "edge-app-test" # name could be used for appName + name: "edge-app-demo" # name could be used for appName appVersion: "1.0.0" organization: "edp2" spec: @@ -15,7 +15,7 @@ spec: infraTemplate: - region: "EU" cloudletOrg: "TelekomOP" - cloudletName: "Hamburg" + cloudletName: "Munich" flavorName: "EU.small" network: outboundConnections: From 0f71239db67681a4a5e43e5e521b3f736cfbbdd2 Mon Sep 17 00:00:00 2001 From: Stephan Lo Date: Mon, 20 Oct 2025 10:05:24 +0200 Subject: [PATCH 19/40] doc(api): rename current swagger to _v2, add old swagger as _v1 --- api/swagger_v1.json | 12716 ++++++++++++++++++++++++ api/{swagger.json => swagger_v2.json} | 0 2 files changed, 12716 insertions(+) create mode 100644 api/swagger_v1.json rename api/{swagger.json => swagger_v2.json} (100%) diff --git a/api/swagger_v1.json b/api/swagger_v1.json new file mode 100644 index 0000000..9a9aa56 --- /dev/null +++ b/api/swagger_v1.json @@ -0,0 +1,12716 @@ +{ + "consumes": ["application/json"], + "produces": ["application/json"], + "schemes": ["https"], + "swagger": "2.0", + "host": "hub.apps.edge.platform.mg3.mdb.osc.live", + "info": { + "description": "# Introduction\nThe Master Controller (MC) serves as the central gateway for orchestrating edge applications and provides several services to both application developers and operators. For application developers, these APIs allow the management and monitoring of deployments for edge applications. For infrastructure operators, these APIs provide ways to manage and monitor the usage of cloudlet infrastructures. Both developers and operators can take advantage of these APIS to manage users within the Organization.\n\nYou can leverage these functionalities and services on our easy-to-use MobiledgeX Console. If you prefer to manage these services programmatically, the available APIs and their resources are accessible from the left navigational menu.", + "title": "Master Controller (MC) API Documentation", + "version": "2.0" + }, + "basePath": "/api/v1", + "paths": { + "/auth/alertreceiver/create": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Create Alert Receiver\nCreate alert receiver.", + "tags": ["AlertReceiver"], + "operationId": "CreateAlertReceiver", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/AlertReceiver" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/alertreceiver/delete": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Delete Alert Receiver\nDelete alert receiver.", + "tags": ["AlertReceiver"], + "operationId": "DeleteAlertReceiver", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/AlertReceiver" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/alertreceiver/show": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Show Alert Receiver\nShow alert receiver.", + "tags": ["AlertReceiver"], + "operationId": "ShowAlertReceiver", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/AlertReceiver" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/billingorg/addchild": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Adds an Organization to an existing parent BillingOrganization.", + "tags": ["BillingOrganization"], + "summary": "Add Child to BillingOrganization", + "operationId": "AddChildOrg", + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/billingorg/delete": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Deletes an existing BillingOrganization.", + "tags": ["BillingOrganization"], + "summary": "Delete BillingOrganization", + "operationId": "DeleteBillingOrg", + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/billingorg/removechild": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Removes an Organization from an existing parent BillingOrganization.", + "tags": ["BillingOrganization"], + "summary": "Remove Child from BillingOrganization", + "operationId": "RemoveChildOrg", + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/billingorg/show": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Displays existing BillingOrganizations in which you are authorized to access.", + "tags": ["BillingOrganization"], + "summary": "Show BillingOrganizations", + "operationId": "ShowBillingOrg", + "responses": { + "200": { + "$ref": "#/responses/listBillingOrgs" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/billingorg/update": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API to update an existing BillingOrganization.", + "tags": ["BillingOrganization"], + "summary": "Update BillingOrganization", + "operationId": "UpdateBillingOrg", + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/AccessCloudlet": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["ExecRequest"], + "summary": "Access Cloudlet VM", + "operationId": "AccessCloudlet", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionExecRequest" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/AddAppAlertPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AppAlertPolicy"], + "summary": "Add an AlertPolicy to the App", + "operationId": "AddAppAlertPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppAlertPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/AddAppAutoProvPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AppAutoProvPolicy"], + "summary": "Add an AutoProvPolicy to the App", + "operationId": "AddAppAutoProvPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppAutoProvPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/AddAutoProvPolicyCloudlet": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AutoProvPolicyCloudlet"], + "summary": "Add a Cloudlet to the Auto Provisioning Policy", + "operationId": "AddAutoProvPolicyCloudlet", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAutoProvPolicyCloudlet" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/AddCloudletAllianceOrg": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletAllianceOrg"], + "summary": "Add alliance organization to the cloudlet", + "operationId": "AddCloudletAllianceOrg", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletAllianceOrg" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/AddCloudletPoolMember": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletPoolMember"], + "summary": "Add a Cloudlet to a CloudletPool", + "operationId": "AddCloudletPoolMember", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletPoolMember" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/AddCloudletResMapping": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletResMap"], + "summary": "Add Optional Resource tag table", + "operationId": "AddCloudletResMapping", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletResMap" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/AddFlavorRes": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Flavor"], + "summary": "Add Optional Resource", + "operationId": "AddFlavorRes", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionFlavor" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/AddGPUDriverBuild": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Adds new build to GPU driver.", + "tags": ["GPUDriverBuildMember"], + "summary": "Add GPU Driver Build", + "operationId": "AddGPUDriverBuild", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionGPUDriverBuildMember" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/AddResTag": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["ResTagTable"], + "summary": "Add new tag(s) to TagTable", + "operationId": "AddResTag", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionResTagTable" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/AddVMPoolMember": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Adds a VM to existing VM Pool.", + "tags": ["VMPoolMember"], + "summary": "Add VMPoolMember", + "operationId": "AddVMPoolMember", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionVMPoolMember" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateAlertPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AlertPolicy"], + "summary": "Create an Alert Policy", + "operationId": "CreateAlertPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAlertPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateApp": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Creates a definition for an application instance for Cloudlet deployment.", + "tags": ["App"], + "summary": "Create Application", + "operationId": "CreateApp", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionApp" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateAppInst": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Creates an instance of an App on a Cloudlet where it is defined by an App plus a ClusterInst key. Many of the fields here are inherited from the App definition.", + "tags": ["AppInst"], + "summary": "Create Application Instance", + "operationId": "CreateAppInst", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppInst" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateAutoProvPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AutoProvPolicy"], + "summary": "Create an Auto Provisioning Policy", + "operationId": "CreateAutoProvPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAutoProvPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateAutoScalePolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AutoScalePolicy"], + "summary": "Create an Auto Scale Policy", + "operationId": "CreateAutoScalePolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAutoScalePolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateCloudlet": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Sets up Cloudlet services on the Operators compute resources, and integrated as part of EdgeCloud edge resource portfolio. These resources are managed from the Edge Controller.", + "tags": ["Cloudlet"], + "summary": "Create Cloudlet", + "operationId": "CreateCloudlet", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudlet" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateCloudletPool": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletPool"], + "summary": "Create a CloudletPool", + "operationId": "CreateCloudletPool", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletPool" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateClusterInst": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Creates an instance of a Cluster on a Cloudlet, defined by a Cluster Key and a Cloudlet Key. ClusterInst is a collection of compute resources on a Cloudlet on which AppInsts are deployed.", + "tags": ["ClusterInst"], + "summary": "Create Cluster Instance", + "operationId": "CreateClusterInst", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionClusterInst" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateFlavor": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Flavor"], + "summary": "Create a Flavor", + "operationId": "CreateFlavor", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionFlavor" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateFlowRateLimitSettings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["FlowRateLimitSettings"], + "summary": "Create Flow RateLimit settings for an API endpoint and target", + "operationId": "CreateFlowRateLimitSettings", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionFlowRateLimitSettings" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateGPUDriver": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Creates GPU driver with all the config required to install it.", + "tags": ["GPUDriver"], + "summary": "Create GPU Driver", + "operationId": "CreateGPUDriver", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionGPUDriver" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateMaxReqsRateLimitSettings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["MaxReqsRateLimitSettings"], + "summary": "Create MaxReqs RateLimit settings for an API endpoint and target", + "operationId": "CreateMaxReqsRateLimitSettings", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionMaxReqsRateLimitSettings" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateNetwork": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Network"], + "summary": "Create a Network", + "operationId": "CreateNetwork", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionNetwork" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateOperatorCode": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Create a code for an Operator.", + "tags": ["OperatorCode"], + "summary": "Create Operator Code", + "operationId": "CreateOperatorCode", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionOperatorCode" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateResTagTable": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["ResTagTable"], + "summary": "Create TagTable", + "operationId": "CreateResTagTable", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionResTagTable" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateTrustPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["TrustPolicy"], + "summary": "Create a Trust Policy", + "operationId": "CreateTrustPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionTrustPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateTrustPolicyException": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["TrustPolicyException"], + "summary": "Create a Trust Policy Exception, by App Developer Organization", + "operationId": "CreateTrustPolicyException", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionTrustPolicyException" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/CreateVMPool": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Creates VM pool which will have VMs defined.", + "tags": ["VMPool"], + "summary": "Create VMPool", + "operationId": "CreateVMPool", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionVMPool" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteAlertPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AlertPolicy"], + "summary": "Delete an Alert Policy", + "operationId": "DeleteAlertPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAlertPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteApp": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Deletes a definition of an Application instance. Make sure no other application instances exist with that definition. If they do exist, you must delete those Application instances first.", + "tags": ["App"], + "summary": "Delete Application", + "operationId": "DeleteApp", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionApp" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteAppInst": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Deletes an instance of the App from the Cloudlet.", + "tags": ["AppInst"], + "summary": "Delete Application Instance", + "operationId": "DeleteAppInst", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppInst" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteAutoProvPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AutoProvPolicy"], + "summary": "Delete an Auto Provisioning Policy", + "operationId": "DeleteAutoProvPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAutoProvPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteAutoScalePolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AutoScalePolicy"], + "summary": "Delete an Auto Scale Policy", + "operationId": "DeleteAutoScalePolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAutoScalePolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteCloudlet": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Removes the Cloudlet services where they are no longer managed from the Edge Controller.", + "tags": ["Cloudlet"], + "summary": "Delete Cloudlet", + "operationId": "DeleteCloudlet", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudlet" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteCloudletPool": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletPool"], + "summary": "Delete a CloudletPool", + "operationId": "DeleteCloudletPool", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletPool" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteClusterInst": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Deletes an instance of a Cluster deployed on a Cloudlet.", + "tags": ["ClusterInst"], + "summary": "Delete Cluster Instance", + "operationId": "DeleteClusterInst", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionClusterInst" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteFlavor": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Flavor"], + "summary": "Delete a Flavor", + "operationId": "DeleteFlavor", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionFlavor" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteFlowRateLimitSettings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["FlowRateLimitSettings"], + "summary": "Delete Flow RateLimit settings for an API endpoint and target", + "operationId": "DeleteFlowRateLimitSettings", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionFlowRateLimitSettings" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteGPUDriver": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Deletes GPU driver given that it is not used by any cloudlet.", + "tags": ["GPUDriver"], + "summary": "Delete GPU Driver", + "operationId": "DeleteGPUDriver", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionGPUDriver" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteIdleReservableClusterInsts": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Deletes reservable cluster instances that are not in use.", + "tags": ["IdleReservableClusterInsts"], + "summary": "Cleanup Reservable Cluster Instances", + "operationId": "DeleteIdleReservableClusterInsts", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionIdleReservableClusterInsts" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteMaxReqsRateLimitSettings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["MaxReqsRateLimitSettings"], + "summary": "Delete MaxReqs RateLimit settings for an API endpoint and target", + "operationId": "DeleteMaxReqsRateLimitSettings", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionMaxReqsRateLimitSettings" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteNetwork": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Network"], + "summary": "Delete a Network", + "operationId": "DeleteNetwork", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionNetwork" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteOperatorCode": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Delete a code for an Operator.", + "tags": ["OperatorCode"], + "summary": "Delete Operator Code", + "operationId": "DeleteOperatorCode", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionOperatorCode" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteResTagTable": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["ResTagTable"], + "summary": "Delete TagTable", + "operationId": "DeleteResTagTable", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionResTagTable" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteTrustPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["TrustPolicy"], + "summary": "Delete a Trust policy", + "operationId": "DeleteTrustPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionTrustPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteTrustPolicyException": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["TrustPolicyException"], + "summary": "Delete a Trust Policy Exception, by App Developer Organization", + "operationId": "DeleteTrustPolicyException", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionTrustPolicyException" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DeleteVMPool": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Deletes VM pool given that none of VMs part of this pool is used.", + "tags": ["VMPool"], + "summary": "Delete VMPool", + "operationId": "DeleteVMPool", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionVMPool" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/DisableDebugLevels": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["DebugRequest"], + "summary": "Disable debug log levels", + "operationId": "DisableDebugLevels", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionDebugRequest" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/EnableDebugLevels": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["DebugRequest"], + "summary": "Enable debug log levels", + "operationId": "EnableDebugLevels", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionDebugRequest" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/EvictCloudletInfo": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletInfo"], + "summary": "Evict (delete) a CloudletInfo for regression testing", + "operationId": "EvictCloudletInfo", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletInfo" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/EvictDevice": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Device"], + "summary": "Evict a device", + "operationId": "EvictDevice", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionDevice" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/FindFlavorMatch": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["FlavorMatch"], + "summary": "Discover if flavor produces a matching platform flavor", + "operationId": "FindFlavorMatch", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionFlavorMatch" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/GenerateAccessKey": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletKey"], + "summary": "Generate new crm access key", + "operationId": "GenerateAccessKey", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletKey" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/GetCloudletGPUDriverLicenseConfig": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Returns the license config associated with the cloudlet", + "tags": ["CloudletKey"], + "summary": "Get Cloudlet Specific GPU Driver License Config", + "operationId": "GetCloudletGPUDriverLicenseConfig", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletKey" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/GetCloudletManifest": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Shows deployment manifest required to setup cloudlet", + "tags": ["CloudletKey"], + "summary": "Get Cloudlet Manifest", + "operationId": "GetCloudletManifest", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletKey" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/GetCloudletProps": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Shows all the infra properties used to setup cloudlet", + "tags": ["CloudletProps"], + "summary": "Get Cloudlet Properties", + "operationId": "GetCloudletProps", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletProps" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/GetCloudletResourceQuotaProps": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Shows all the resource quota properties of the cloudlet", + "tags": ["CloudletResourceQuotaProps"], + "summary": "Get Cloudlet Resource Quota Properties", + "operationId": "GetCloudletResourceQuotaProps", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletResourceQuotaProps" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/GetCloudletResourceUsage": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Shows cloudlet resources used and their limits", + "tags": ["CloudletResourceUsage"], + "summary": "Get Cloudlet resource information", + "operationId": "GetCloudletResourceUsage", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletResourceUsage" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/GetGPUDriverBuildURL": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Returns a time-limited signed URL to download GPU driver.", + "tags": ["GPUDriverBuildMember"], + "summary": "Get GPU Driver Build URL", + "operationId": "GetGPUDriverBuildURL", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionGPUDriverBuildMember" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/GetGPUDriverLicenseConfig": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Returns the license config specific to GPU driver", + "tags": ["GPUDriverKey"], + "summary": "Get GPU Driver License Config", + "operationId": "GetGPUDriverLicenseConfig", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionGPUDriverKey" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/GetOrganizationsOnCloudlet": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletKey"], + "summary": "Get organizations of ClusterInsts and AppInsts on cloudlet", + "operationId": "GetOrganizationsOnCloudlet", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletKey" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/GetResTagTable": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["ResTagTableKey"], + "summary": "Fetch a copy of the TagTable", + "operationId": "GetResTagTable", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionResTagTableKey" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/InjectCloudletInfo": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletInfo"], + "summary": "Inject (create) a CloudletInfo for regression testing", + "operationId": "InjectCloudletInfo", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletInfo" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/InjectDevice": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Device"], + "summary": "Inject a device", + "operationId": "InjectDevice", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionDevice" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RefreshAppInst": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Restarts an App instance with new App settings or image.", + "tags": ["AppInst"], + "summary": "Refresh Application Instance", + "operationId": "RefreshAppInst", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppInst" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RemoveAppAlertPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AppAlertPolicy"], + "summary": "Remove an AlertPolicy from the App", + "operationId": "RemoveAppAlertPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppAlertPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RemoveAppAutoProvPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AppAutoProvPolicy"], + "summary": "Remove an AutoProvPolicy from the App", + "operationId": "RemoveAppAutoProvPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppAutoProvPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RemoveAutoProvPolicyCloudlet": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AutoProvPolicyCloudlet"], + "summary": "Remove a Cloudlet from the Auto Provisioning Policy", + "operationId": "RemoveAutoProvPolicyCloudlet", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAutoProvPolicyCloudlet" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RemoveCloudletAllianceOrg": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletAllianceOrg"], + "summary": "Remove alliance organization from the cloudlet", + "operationId": "RemoveCloudletAllianceOrg", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletAllianceOrg" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RemoveCloudletPoolMember": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletPoolMember"], + "summary": "Remove a Cloudlet from a CloudletPool", + "operationId": "RemoveCloudletPoolMember", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletPoolMember" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RemoveCloudletResMapping": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletResMap"], + "summary": "Remove Optional Resource tag table", + "operationId": "RemoveCloudletResMapping", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletResMap" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RemoveFlavorRes": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Flavor"], + "summary": "Remove Optional Resource", + "operationId": "RemoveFlavorRes", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionFlavor" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RemoveGPUDriverBuild": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Removes build from GPU driver.", + "tags": ["GPUDriverBuildMember"], + "summary": "Remove GPU Driver Build", + "operationId": "RemoveGPUDriverBuild", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionGPUDriverBuildMember" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RemoveResTag": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["ResTagTable"], + "summary": "Remove existing tag(s) from TagTable", + "operationId": "RemoveResTag", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionResTagTable" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RemoveVMPoolMember": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Removes a VM from existing VM Pool.", + "tags": ["VMPoolMember"], + "summary": "Remove VMPoolMember", + "operationId": "RemoveVMPoolMember", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionVMPoolMember" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RequestAppInstLatency": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AppInstLatency"], + "summary": "Request Latency measurements for clients connected to AppInst", + "operationId": "RequestAppInstLatency", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppInstLatency" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ResetSettings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Settings"], + "summary": "Reset all settings to their defaults", + "operationId": "ResetSettings", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionSettings" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RevokeAccessKey": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletKey"], + "summary": "Revoke crm access key", + "operationId": "RevokeAccessKey", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletKey" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RunCommand": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["ExecRequest"], + "summary": "Run a Command or Shell on a container", + "operationId": "RunCommand", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionExecRequest" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RunConsole": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["ExecRequest"], + "summary": "Run console on a VM", + "operationId": "RunConsole", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionExecRequest" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/RunDebug": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["DebugRequest"], + "summary": "Run debug command", + "operationId": "RunDebug", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionDebugRequest" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowAlert": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Alert"], + "summary": "Show alerts", + "operationId": "ShowAlert", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAlert" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowAlertPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Any fields specified will be used to filter results.", + "tags": ["AlertPolicy"], + "summary": "Show Alert Policies", + "operationId": "ShowAlertPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAlertPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowApp": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Lists all Application definitions managed from the Edge Controller. Any fields specified will be used to filter results.", + "tags": ["App"], + "summary": "Show Applications", + "operationId": "ShowApp", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionApp" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowAppInst": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Lists all the Application instances managed by the Edge Controller. Any fields specified will be used to filter results.", + "tags": ["AppInst"], + "summary": "Show Application Instances", + "operationId": "ShowAppInst", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppInst" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowAppInstClient": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AppInstClientKey"], + "summary": "Show application instance clients", + "operationId": "ShowAppInstClient", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppInstClientKey" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowAppInstRefs": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AppInstRefs"], + "summary": "Show AppInstRefs (debug only)", + "operationId": "ShowAppInstRefs", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppInstRefs" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowAutoProvPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Any fields specified will be used to filter results.", + "tags": ["AutoProvPolicy"], + "summary": "Show Auto Provisioning Policies", + "operationId": "ShowAutoProvPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAutoProvPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowAutoScalePolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Any fields specified will be used to filter results.", + "tags": ["AutoScalePolicy"], + "summary": "Show Auto Scale Policies", + "operationId": "ShowAutoScalePolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAutoScalePolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowCloudlet": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Lists all the cloudlets managed from Edge Controller.", + "tags": ["Cloudlet"], + "summary": "Show Cloudlets", + "operationId": "ShowCloudlet", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudlet" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowCloudletInfo": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletInfo"], + "summary": "Show CloudletInfos", + "operationId": "ShowCloudletInfo", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletInfo" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowCloudletPool": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletPool"], + "summary": "Show CloudletPools", + "operationId": "ShowCloudletPool", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletPool" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowCloudletRefs": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletRefs"], + "summary": "Show CloudletRefs (debug only)", + "operationId": "ShowCloudletRefs", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletRefs" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowCloudletsForAppDeployment": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "DefaultFlavor", + "tags": ["DeploymentCloudletRequest"], + "summary": "Discover cloudlets supporting deployments of App", + "operationId": "ShowCloudletsForAppDeployment", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionDeploymentCloudletRequest" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowClusterInst": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Lists all the cluster instances managed by Edge Controller.", + "tags": ["ClusterInst"], + "summary": "Show Cluster Instances", + "operationId": "ShowClusterInst", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionClusterInst" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowClusterRefs": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["ClusterRefs"], + "summary": "Show ClusterRefs (debug only)", + "operationId": "ShowClusterRefs", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionClusterRefs" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowDebugLevels": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["DebugRequest"], + "summary": "Show debug log levels", + "operationId": "ShowDebugLevels", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionDebugRequest" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowDevice": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Device"], + "summary": "Show devices", + "operationId": "ShowDevice", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionDevice" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowDeviceReport": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["DeviceReport"], + "summary": "Device Reports API", + "operationId": "ShowDeviceReport", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionDeviceReport" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowFlavor": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Flavor"], + "summary": "Show Flavors", + "operationId": "ShowFlavor", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionFlavor" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowFlavorsForCloudlet": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletKey"], + "summary": "Find all meta flavors viable on cloudlet", + "operationId": "ShowFlavorsForCloudlet", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletKey" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowFlowRateLimitSettings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["FlowRateLimitSettings"], + "summary": "Show Flow RateLimit settings for an API endpoint and target", + "operationId": "ShowFlowRateLimitSettings", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionFlowRateLimitSettings" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowGPUDriver": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Lists all the EdgeCloud created GPU drivers and operator created GPU drivers.", + "tags": ["GPUDriver"], + "summary": "Show GPU Drivers", + "operationId": "ShowGPUDriver", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionGPUDriver" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowLogs": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["ExecRequest"], + "summary": "View logs for AppInst", + "operationId": "ShowLogs", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionExecRequest" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowMaxReqsRateLimitSettings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["MaxReqsRateLimitSettings"], + "summary": "Show MaxReqs RateLimit settings for an API endpoint and target", + "operationId": "ShowMaxReqsRateLimitSettings", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionMaxReqsRateLimitSettings" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowNetwork": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Any fields specified will be used to filter results.", + "tags": ["Network"], + "summary": "Show Networks", + "operationId": "ShowNetwork", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionNetwork" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowNode": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Node"], + "summary": "Show all Nodes connected to all Controllers", + "operationId": "ShowNode", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionNode" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowOperatorCode": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Show Codes for an Operator.", + "tags": ["OperatorCode"], + "summary": "Show Operator Code", + "operationId": "ShowOperatorCode", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionOperatorCode" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowRateLimitSettings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["RateLimitSettings"], + "summary": "Show RateLimit settings for an API endpoint and target", + "operationId": "ShowRateLimitSettings", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionRateLimitSettings" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowResTagTable": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["ResTagTable"], + "summary": "Show TagTable", + "operationId": "ShowResTagTable", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionResTagTable" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowSettings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["Settings"], + "summary": "Show settings", + "operationId": "ShowSettings", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionSettings" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowTrustPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Any fields specified will be used to filter results.", + "tags": ["TrustPolicy"], + "summary": "Show Trust Policies", + "operationId": "ShowTrustPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionTrustPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowTrustPolicyException": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Any fields specified will be used to filter results.", + "tags": ["TrustPolicyException"], + "summary": "Show Trust Policy Exceptions", + "operationId": "ShowTrustPolicyException", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionTrustPolicyException" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/ShowVMPool": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Lists all the VMs part of the VM pool.", + "tags": ["VMPool"], + "summary": "Show VMPools", + "operationId": "ShowVMPool", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionVMPool" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/StreamAppInst": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["AppInstKey"], + "summary": "Stream Application Instance current progress", + "operationId": "StreamAppInst", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppInstKey" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/StreamCloudlet": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["CloudletKey"], + "summary": "Stream Cloudlet current progress", + "operationId": "StreamCloudlet", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletKey" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/StreamClusterInst": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["ClusterInstKey"], + "summary": "Stream Cluster Instance current progress", + "operationId": "StreamClusterInst", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionClusterInstKey" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/StreamGPUDriver": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "tags": ["GPUDriverKey"], + "summary": "Stream GPU driver current progress", + "operationId": "StreamGPUDriver", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionGPUDriverKey" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateAlertPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "The following values should be added to `AlertPolicy.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nCpuUtilizationLimit: 3\nMemUtilizationLimit: 4\nDiskUtilizationLimit: 5\nActiveConnLimit: 6\nSeverity: 7\nTriggerTime: 8\nLabels: 9\nLabelsKey: 9.1\nLabelsValue: 9.2\nAnnotations: 10\nAnnotationsKey: 10.1\nAnnotationsValue: 10.2\nDescription: 11\nDeletePrepare: 12\n```", + "tags": ["AlertPolicy"], + "summary": "Update an Alert Policy", + "operationId": "UpdateAlertPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAlertPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateApp": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Updates the definition of an Application instance.\nThe following values should be added to `App.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nKeyVersion: 2.3\nImagePath: 4\nImageType: 5\nAccessPorts: 7\nDefaultFlavor: 9\nDefaultFlavorName: 9.1\nAuthPublicKey: 12\nCommand: 13\nAnnotations: 14\nDeployment: 15\nDeploymentManifest: 16\nDeploymentGenerator: 17\nAndroidPackageName: 18\nDelOpt: 20\nConfigs: 21\nConfigsKind: 21.1\nConfigsConfig: 21.2\nScaleWithCluster: 22\nInternalPorts: 23\nRevision: 24\nOfficialFqdn: 25\nMd5Sum: 26\nAutoProvPolicy: 28\nAccessType: 29\nDeletePrepare: 31\nAutoProvPolicies: 32\nTemplateDelimiter: 33\nSkipHcPorts: 34\nCreatedAt: 35\nCreatedAtSeconds: 35.1\nCreatedAtNanos: 35.2\nUpdatedAt: 36\nUpdatedAtSeconds: 36.1\nUpdatedAtNanos: 36.2\nTrusted: 37\nRequiredOutboundConnections: 38\nRequiredOutboundConnectionsProtocol: 38.1\nRequiredOutboundConnectionsPortRangeMin: 38.2\nRequiredOutboundConnectionsPortRangeMax: 38.3\nRequiredOutboundConnectionsRemoteCidr: 38.4\nAllowServerless: 39\nServerlessConfig: 40\nServerlessConfigVcpus: 40.1\nServerlessConfigVcpusWhole: 40.1.1\nServerlessConfigVcpusNanos: 40.1.2\nServerlessConfigRam: 40.2\nServerlessConfigMinReplicas: 40.3\nVmAppOsType: 41\nAlertPolicies: 42\nQosSessionProfile: 43\nQosSessionDuration: 44\n```", + "tags": ["App"], + "summary": "Update Application", + "operationId": "UpdateApp", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionApp" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateAppInst": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Updates an Application instance and then refreshes it.\nThe following values should be added to `AppInst.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyAppKey: 2.1\nKeyAppKeyOrganization: 2.1.1\nKeyAppKeyName: 2.1.2\nKeyAppKeyVersion: 2.1.3\nKeyClusterInstKey: 2.4\nKeyClusterInstKeyClusterKey: 2.4.1\nKeyClusterInstKeyClusterKeyName: 2.4.1.1\nKeyClusterInstKeyCloudletKey: 2.4.2\nKeyClusterInstKeyCloudletKeyOrganization: 2.4.2.1\nKeyClusterInstKeyCloudletKeyName: 2.4.2.2\nKeyClusterInstKeyCloudletKeyFederatedOrganization: 2.4.2.3\nKeyClusterInstKeyOrganization: 2.4.3\nCloudletLoc: 3\nCloudletLocLatitude: 3.1\nCloudletLocLongitude: 3.2\nCloudletLocHorizontalAccuracy: 3.3\nCloudletLocVerticalAccuracy: 3.4\nCloudletLocAltitude: 3.5\nCloudletLocCourse: 3.6\nCloudletLocSpeed: 3.7\nCloudletLocTimestamp: 3.8\nCloudletLocTimestampSeconds: 3.8.1\nCloudletLocTimestampNanos: 3.8.2\nUri: 4\nLiveness: 6\nMappedPorts: 9\nMappedPortsProto: 9.1\nMappedPortsInternalPort: 9.2\nMappedPortsPublicPort: 9.3\nMappedPortsFqdnPrefix: 9.5\nMappedPortsEndPort: 9.6\nMappedPortsTls: 9.7\nMappedPortsNginx: 9.8\nMappedPortsMaxPktSize: 9.9\nFlavor: 12\nFlavorName: 12.1\nState: 14\nErrors: 15\nCrmOverride: 16\nRuntimeInfo: 17\nRuntimeInfoContainerIds: 17.1\nCreatedAt: 21\nCreatedAtSeconds: 21.1\nCreatedAtNanos: 21.2\nAutoClusterIpAccess: 22\nRevision: 24\nForceUpdate: 25\nUpdateMultiple: 26\nConfigs: 27\nConfigsKind: 27.1\nConfigsConfig: 27.2\nHealthCheck: 29\nPowerState: 31\nExternalVolumeSize: 32\nAvailabilityZone: 33\nVmFlavor: 34\nOptRes: 35\nUpdatedAt: 36\nUpdatedAtSeconds: 36.1\nUpdatedAtNanos: 36.2\nRealClusterName: 37\nInternalPortToLbIp: 38\nInternalPortToLbIpKey: 38.1\nInternalPortToLbIpValue: 38.2\nDedicatedIp: 39\nUniqueId: 40\nDnsLabel: 41\n```", + "tags": ["AppInst"], + "summary": "Update Application Instance", + "operationId": "UpdateAppInst", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppInst" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateAutoProvPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "The following values should be added to `AutoProvPolicy.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nDeployClientCount: 3\nDeployIntervalCount: 4\nCloudlets: 5\nCloudletsKey: 5.1\nCloudletsKeyOrganization: 5.1.1\nCloudletsKeyName: 5.1.2\nCloudletsKeyFederatedOrganization: 5.1.3\nCloudletsLoc: 5.2\nCloudletsLocLatitude: 5.2.1\nCloudletsLocLongitude: 5.2.2\nCloudletsLocHorizontalAccuracy: 5.2.3\nCloudletsLocVerticalAccuracy: 5.2.4\nCloudletsLocAltitude: 5.2.5\nCloudletsLocCourse: 5.2.6\nCloudletsLocSpeed: 5.2.7\nCloudletsLocTimestamp: 5.2.8\nCloudletsLocTimestampSeconds: 5.2.8.1\nCloudletsLocTimestampNanos: 5.2.8.2\nMinActiveInstances: 6\nMaxInstances: 7\nUndeployClientCount: 8\nUndeployIntervalCount: 9\nDeletePrepare: 10\n```", + "tags": ["AutoProvPolicy"], + "summary": "Update an Auto Provisioning Policy", + "operationId": "UpdateAutoProvPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAutoProvPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateAutoScalePolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "The following values should be added to `AutoScalePolicy.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nMinNodes: 3\nMaxNodes: 4\nScaleUpCpuThresh: 5\nScaleDownCpuThresh: 6\nTriggerTimeSec: 7\nStabilizationWindowSec: 8\nTargetCpu: 9\nTargetMem: 10\nTargetActiveConnections: 11\nDeletePrepare: 12\n```", + "tags": ["AutoScalePolicy"], + "summary": "Update an Auto Scale Policy", + "operationId": "UpdateAutoScalePolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAutoScalePolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateCloudlet": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Updates the Cloudlet configuration and manages the upgrade of Cloudlet services.\nThe following values should be added to `Cloudlet.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nKeyFederatedOrganization: 2.3\nLocation: 5\nLocationLatitude: 5.1\nLocationLongitude: 5.2\nLocationHorizontalAccuracy: 5.3\nLocationVerticalAccuracy: 5.4\nLocationAltitude: 5.5\nLocationCourse: 5.6\nLocationSpeed: 5.7\nLocationTimestamp: 5.8\nLocationTimestampSeconds: 5.8.1\nLocationTimestampNanos: 5.8.2\nIpSupport: 6\nStaticIps: 7\nNumDynamicIps: 8\nTimeLimits: 9\nTimeLimitsCreateClusterInstTimeout: 9.1\nTimeLimitsUpdateClusterInstTimeout: 9.2\nTimeLimitsDeleteClusterInstTimeout: 9.3\nTimeLimitsCreateAppInstTimeout: 9.4\nTimeLimitsUpdateAppInstTimeout: 9.5\nTimeLimitsDeleteAppInstTimeout: 9.6\nErrors: 10\nState: 12\nCrmOverride: 13\nDeploymentLocal: 14\nPlatformType: 15\nNotifySrvAddr: 16\nFlavor: 17\nFlavorName: 17.1\nPhysicalName: 18\nEnvVar: 19\nEnvVarKey: 19.1\nEnvVarValue: 19.2\nContainerVersion: 20\nConfig: 21\nConfigContainerRegistryPath: 21.1\nConfigCloudletVmImagePath: 21.2\nConfigNotifyCtrlAddrs: 21.3\nConfigTlsCertFile: 21.5\nConfigTlsKeyFile: 21.20\nConfigTlsCaFile: 21.21\nConfigEnvVar: 21.6\nConfigEnvVarKey: 21.6.1\nConfigEnvVarValue: 21.6.2\nConfigPlatformTag: 21.8\nConfigTestMode: 21.9\nConfigSpan: 21.10\nConfigCleanupMode: 21.11\nConfigRegion: 21.12\nConfigCommercialCerts: 21.13\nConfigUseVaultPki: 21.14\nConfigAppDnsRoot: 21.16\nConfigChefServerPath: 21.17\nConfigChefClientInterval: 21.18\nConfigDeploymentTag: 21.19\nConfigCrmAccessPrivateKey: 21.22\nConfigAccessApiAddr: 21.23\nConfigCacheDir: 21.24\nConfigSecondaryCrmAccessPrivateKey: 21.25\nConfigThanosRecvAddr: 21.26\nResTagMap: 22\nResTagMapKey: 22.1\nResTagMapValue: 22.2\nResTagMapValueName: 22.2.1\nResTagMapValueOrganization: 22.2.2\nAccessVars: 23\nAccessVarsKey: 23.1\nAccessVarsValue: 23.2\nVmImageVersion: 24\nDeployment: 26\nInfraApiAccess: 27\nInfraConfig: 28\nInfraConfigExternalNetworkName: 28.1\nInfraConfigFlavorName: 28.2\nChefClientKey: 29\nChefClientKeyKey: 29.1\nChefClientKeyValue: 29.2\nMaintenanceState: 30\nOverridePolicyContainerVersion: 31\nVmPool: 32\nCrmAccessPublicKey: 33\nCrmAccessKeyUpgradeRequired: 34\nCreatedAt: 35\nCreatedAtSeconds: 35.1\nCreatedAtNanos: 35.2\nUpdatedAt: 36\nUpdatedAtSeconds: 36.1\nUpdatedAtNanos: 36.2\nTrustPolicy: 37\nTrustPolicyState: 38\nResourceQuotas: 39\nResourceQuotasName: 39.1\nResourceQuotasValue: 39.2\nResourceQuotasAlertThreshold: 39.3\nDefaultResourceAlertThreshold: 40\nHostController: 41\nKafkaCluster: 42\nKafkaUser: 43\nKafkaPassword: 44\nGpuConfig: 45\nGpuConfigDriver: 45.1\nGpuConfigDriverName: 45.1.1\nGpuConfigDriverOrganization: 45.1.2\nGpuConfigProperties: 45.2\nGpuConfigPropertiesKey: 45.2.1\nGpuConfigPropertiesValue: 45.2.2\nGpuConfigLicenseConfig: 45.3\nGpuConfigLicenseConfigMd5Sum: 45.4\nEnableDefaultServerlessCluster: 46\nAllianceOrgs: 47\nSingleKubernetesClusterOwner: 48\nDeletePrepare: 49\nPlatformHighAvailability: 50\nSecondaryCrmAccessPublicKey: 51\nSecondaryCrmAccessKeyUpgradeRequired: 52\nSecondaryNotifySrvAddr: 53\nDnsLabel: 54\nRootLbFqdn: 55\nFederationConfig: 56\nFederationConfigFederationName: 56.1\nFederationConfigSelfFederationId: 56.2\nFederationConfigPartnerFederationId: 56.3\nFederationConfigZoneCountryCode: 56.4\nFederationConfigPartnerFederationAddr: 56.5\nLicenseConfigStoragePath: 57\n```", + "tags": ["Cloudlet"], + "summary": "Update Cloudlet", + "operationId": "UpdateCloudlet", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudlet" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateCloudletPool": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "The following values should be added to `CloudletPool.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nCloudlets: 3\nCloudletsOrganization: 3.1\nCloudletsName: 3.2\nCloudletsFederatedOrganization: 3.3\nCreatedAt: 4\nCreatedAtSeconds: 4.1\nCreatedAtNanos: 4.2\nUpdatedAt: 5\nUpdatedAtSeconds: 5.1\nUpdatedAtNanos: 5.2\nDeletePrepare: 6\n```", + "tags": ["CloudletPool"], + "summary": "Update a CloudletPool", + "operationId": "UpdateCloudletPool", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletPool" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateClusterInst": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Updates an instance of a Cluster deployed on a Cloudlet.\nThe following values should be added to `ClusterInst.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyClusterKey: 2.1\nKeyClusterKeyName: 2.1.1\nKeyCloudletKey: 2.2\nKeyCloudletKeyOrganization: 2.2.1\nKeyCloudletKeyName: 2.2.2\nKeyCloudletKeyFederatedOrganization: 2.2.3\nKeyOrganization: 2.3\nFlavor: 3\nFlavorName: 3.1\nLiveness: 9\nAuto: 10\nState: 4\nErrors: 5\nCrmOverride: 6\nIpAccess: 7\nAllocatedIp: 8\nNodeFlavor: 11\nDeployment: 15\nNumMasters: 13\nNumNodes: 14\nExternalVolumeSize: 17\nAutoScalePolicy: 18\nAvailabilityZone: 19\nImageName: 20\nReservable: 21\nReservedBy: 22\nSharedVolumeSize: 23\nMasterNodeFlavor: 25\nSkipCrmCleanupOnFailure: 26\nOptRes: 27\nResources: 28\nResourcesVms: 28.1\nResourcesVmsName: 28.1.1\nResourcesVmsType: 28.1.2\nResourcesVmsStatus: 28.1.3\nResourcesVmsInfraFlavor: 28.1.4\nResourcesVmsIpaddresses: 28.1.5\nResourcesVmsIpaddressesExternalIp: 28.1.5.1\nResourcesVmsIpaddressesInternalIp: 28.1.5.2\nResourcesVmsContainers: 28.1.6\nResourcesVmsContainersName: 28.1.6.1\nResourcesVmsContainersType: 28.1.6.2\nResourcesVmsContainersStatus: 28.1.6.3\nResourcesVmsContainersClusterip: 28.1.6.4\nResourcesVmsContainersRestarts: 28.1.6.5\nCreatedAt: 29\nCreatedAtSeconds: 29.1\nCreatedAtNanos: 29.2\nUpdatedAt: 30\nUpdatedAtSeconds: 30.1\nUpdatedAtNanos: 30.2\nReservationEndedAt: 31\nReservationEndedAtSeconds: 31.1\nReservationEndedAtNanos: 31.2\nMultiTenant: 32\nNetworks: 33\nDeletePrepare: 34\nDnsLabel: 35\nFqdn: 36\n```", + "tags": ["ClusterInst"], + "summary": "Update Cluster Instance", + "operationId": "UpdateClusterInst", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionClusterInst" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateFlavor": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "The following values should be added to `Flavor.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyName: 2.1\nRam: 3\nVcpus: 4\nDisk: 5\nOptResMap: 6\nOptResMapKey: 6.1\nOptResMapValue: 6.2\nDeletePrepare: 7\n```", + "tags": ["Flavor"], + "summary": "Update a Flavor", + "operationId": "UpdateFlavor", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionFlavor" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateFlowRateLimitSettings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "The following values should be added to `FlowRateLimitSettings.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyFlowSettingsName: 2.1\nKeyRateLimitKey: 2.2\nKeyRateLimitKeyApiName: 2.2.1\nKeyRateLimitKeyApiEndpointType: 2.2.2\nKeyRateLimitKeyRateLimitTarget: 2.2.3\nSettings: 3\nSettingsFlowAlgorithm: 3.1\nSettingsReqsPerSecond: 3.2\nSettingsBurstSize: 3.3\n```", + "tags": ["FlowRateLimitSettings"], + "summary": "Update Flow RateLimit settings for an API endpoint and target", + "operationId": "UpdateFlowRateLimitSettings", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionFlowRateLimitSettings" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateGPUDriver": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Updates GPU driver config.\nThe following values should be added to `GPUDriver.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyName: 2.1\nKeyOrganization: 2.2\nBuilds: 3\nBuildsName: 3.1\nBuildsDriverPath: 3.2\nBuildsDriverPathCreds: 3.3\nBuildsOperatingSystem: 3.4\nBuildsKernelVersion: 3.5\nBuildsHypervisorInfo: 3.6\nBuildsMd5Sum: 3.7\nBuildsStoragePath: 3.8\nLicenseConfig: 4\nLicenseConfigMd5Sum: 5\nProperties: 6\nPropertiesKey: 6.1\nPropertiesValue: 6.2\nState: 7\nIgnoreState: 8\nDeletePrepare: 9\nStorageBucketName: 10\nLicenseConfigStoragePath: 11\n```", + "tags": ["GPUDriver"], + "summary": "Update GPU Driver", + "operationId": "UpdateGPUDriver", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionGPUDriver" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateMaxReqsRateLimitSettings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "The following values should be added to `MaxReqsRateLimitSettings.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyMaxReqsSettingsName: 2.1\nKeyRateLimitKey: 2.2\nKeyRateLimitKeyApiName: 2.2.1\nKeyRateLimitKeyApiEndpointType: 2.2.2\nKeyRateLimitKeyRateLimitTarget: 2.2.3\nSettings: 3\nSettingsMaxReqsAlgorithm: 3.1\nSettingsMaxRequests: 3.2\nSettingsInterval: 3.3\n```", + "tags": ["MaxReqsRateLimitSettings"], + "summary": "Update MaxReqs RateLimit settings for an API endpoint and target", + "operationId": "UpdateMaxReqsRateLimitSettings", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionMaxReqsRateLimitSettings" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateNetwork": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "The following values should be added to `Network.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyCloudletKey: 2.1\nKeyCloudletKeyOrganization: 2.1.1\nKeyCloudletKeyName: 2.1.2\nKeyCloudletKeyFederatedOrganization: 2.1.3\nKeyName: 2.2\nRoutes: 3\nRoutesDestinationCidr: 3.1\nRoutesNextHopIp: 3.2\nConnectionType: 4\nDeletePrepare: 5\n```", + "tags": ["Network"], + "summary": "Update a Network", + "operationId": "UpdateNetwork", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionNetwork" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateResTagTable": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "The following values should be added to `ResTagTable.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyName: 2.1\nKeyOrganization: 2.2\nTags: 3\nTagsKey: 3.1\nTagsValue: 3.2\nAzone: 4\nDeletePrepare: 5\n```", + "tags": ["ResTagTable"], + "summary": "Update TagTable", + "operationId": "UpdateResTagTable", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionResTagTable" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateSettings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "The following values should be added to `Settings.fields` field array to specify which fields will be updated.\n```\nShepherdMetricsCollectionInterval: 2\nShepherdAlertEvaluationInterval: 20\nShepherdMetricsScrapeInterval: 40\nShepherdHealthCheckRetries: 3\nShepherdHealthCheckInterval: 4\nAutoDeployIntervalSec: 5\nAutoDeployOffsetSec: 6\nAutoDeployMaxIntervals: 7\nCreateAppInstTimeout: 8\nUpdateAppInstTimeout: 9\nDeleteAppInstTimeout: 10\nCreateClusterInstTimeout: 11\nUpdateClusterInstTimeout: 12\nDeleteClusterInstTimeout: 13\nMasterNodeFlavor: 14\nMaxTrackedDmeClients: 16\nChefClientInterval: 17\nInfluxDbMetricsRetention: 18\nCloudletMaintenanceTimeout: 19\nUpdateVmPoolTimeout: 21\nUpdateTrustPolicyTimeout: 22\nDmeApiMetricsCollectionInterval: 23\nEdgeEventsMetricsCollectionInterval: 24\nCleanupReservableAutoClusterIdletime: 25\nInfluxDbCloudletUsageMetricsRetention: 26\nCreateCloudletTimeout: 27\nUpdateCloudletTimeout: 28\nLocationTileSideLengthKm: 29\nEdgeEventsMetricsContinuousQueriesCollectionIntervals: 30\nEdgeEventsMetricsContinuousQueriesCollectionIntervalsInterval: 30.1\nEdgeEventsMetricsContinuousQueriesCollectionIntervalsRetention: 30.2\nInfluxDbDownsampledMetricsRetention: 31\nInfluxDbEdgeEventsMetricsRetention: 32\nAppinstClientCleanupInterval: 33\nClusterAutoScaleAveragingDurationSec: 34\nClusterAutoScaleRetryDelay: 35\nAlertPolicyMinTriggerTime: 36\nDisableRateLimit: 37\nRateLimitMaxTrackedIps: 39\nResourceSnapshotThreadInterval: 41\nPlatformHaInstancePollInterval: 42\nPlatformHaInstanceActiveExpireTime: 43\n```", + "tags": ["Settings"], + "summary": "Update settings", + "operationId": "UpdateSettings", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionSettings" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateTrustPolicy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "The following values should be added to `TrustPolicy.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nOutboundSecurityRules: 3\nOutboundSecurityRulesProtocol: 3.1\nOutboundSecurityRulesPortRangeMin: 3.2\nOutboundSecurityRulesPortRangeMax: 3.3\nOutboundSecurityRulesRemoteCidr: 3.4\nDeletePrepare: 4\n```", + "tags": ["TrustPolicy"], + "summary": "Update a Trust policy", + "operationId": "UpdateTrustPolicy", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionTrustPolicy" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateTrustPolicyException": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "The following values should be added to `TrustPolicyException.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyAppKey: 2.1\nKeyAppKeyOrganization: 2.1.1\nKeyAppKeyName: 2.1.2\nKeyAppKeyVersion: 2.1.3\nKeyCloudletPoolKey: 2.2\nKeyCloudletPoolKeyOrganization: 2.2.1\nKeyCloudletPoolKeyName: 2.2.2\nKeyName: 2.3\nState: 3\nOutboundSecurityRules: 4\nOutboundSecurityRulesProtocol: 4.1\nOutboundSecurityRulesPortRangeMin: 4.2\nOutboundSecurityRulesPortRangeMax: 4.3\nOutboundSecurityRulesRemoteCidr: 4.4\n```", + "tags": ["TrustPolicyException"], + "summary": "Update a Trust Policy Exception, by Operator Organization", + "operationId": "UpdateTrustPolicyException", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionTrustPolicyException" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/ctrl/UpdateVMPool": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Updates a VM pools VMs.\nThe following values should be added to `VMPool.fields` field array to specify which fields will be updated.\n```\nKey: 2\nKeyOrganization: 2.1\nKeyName: 2.2\nVms: 3\nVmsName: 3.1\nVmsNetInfo: 3.2\nVmsNetInfoExternalIp: 3.2.1\nVmsNetInfoInternalIp: 3.2.2\nVmsGroupName: 3.3\nVmsState: 3.4\nVmsUpdatedAt: 3.5\nVmsUpdatedAtSeconds: 3.5.1\nVmsUpdatedAtNanos: 3.5.2\nVmsInternalName: 3.6\nVmsFlavor: 3.7\nVmsFlavorName: 3.7.1\nVmsFlavorVcpus: 3.7.2\nVmsFlavorRam: 3.7.3\nVmsFlavorDisk: 3.7.4\nVmsFlavorPropMap: 3.7.5\nVmsFlavorPropMapKey: 3.7.5.1\nVmsFlavorPropMapValue: 3.7.5.2\nState: 4\nErrors: 5\nCrmOverride: 7\nDeletePrepare: 8\n```", + "tags": ["VMPool"], + "summary": "Update VMPool", + "operationId": "UpdateVMPool", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionVMPool" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/events/find": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Find events\nDisplay events based on find filter.", + "tags": ["Events"], + "operationId": "FindEvents", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/EventSearch" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/events/show": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Search events\nDisplay events based on search filter.", + "tags": ["Events"], + "operationId": "SearchEvents", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/EventSearch" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/events/terms": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Terms Events\nDisplay events terms.", + "tags": ["Events"], + "operationId": "TermsEvents", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/EventTerms" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/metrics/app": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Display app related metrics.", + "tags": ["DeveloperMetrics"], + "summary": "App related metrics", + "operationId": "AppMetrics", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppInstMetrics" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/metrics/clientapiusage": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Display client api usage related metrics.", + "tags": ["DeveloperMetrics"], + "summary": "Client api usage related metrics", + "operationId": "ClientApiUsageMetrics", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionClientApiUsageMetrics" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/metrics/clientappusage": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Display client app usage related metrics.", + "tags": ["DeveloperMetrics"], + "summary": "Client app usage related metrics", + "operationId": "ClientAppUsageMetrics", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionClientAppUsageMetrics" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/metrics/clientcloudletusage": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Display client cloudlet usage related metrics.", + "tags": ["DeveloperMetrics"], + "summary": "Client cloudlet usage related metrics", + "operationId": "ClientCloudletUsageMetrics", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionClientCloudletUsageMetrics" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/metrics/cloudlet": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Display cloudlet related metrics.", + "tags": ["OperatorMetrics"], + "summary": "Cloudlet related metrics", + "operationId": "CloudletMetrics", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletMetrics" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/metrics/cloudlet/usage": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Display cloudlet usage related metrics.", + "tags": ["OperatorMetrics"], + "summary": "Cloudlet usage related metrics", + "operationId": "CloudletUsageMetrics", + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/metrics/cluster": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Display cluster related metrics.", + "tags": ["DeveloperMetrics"], + "summary": "Cluster related metrics", + "operationId": "ClusterMetrics", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionClusterInstMetrics" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/org/create": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Create an Organization to access operator/cloudlet APIs.", + "tags": ["Organization"], + "summary": "Create Organization", + "operationId": "CreateOrg", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/Organization" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/org/delete": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Deletes an existing Organization.", + "tags": ["Organization"], + "summary": "Delete Organization", + "operationId": "DeleteOrg", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/Organization" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/org/show": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Displays existing Organizations in which you are authorized to access.", + "tags": ["Organization"], + "summary": "Show Organizations", + "operationId": "ShowOrg", + "responses": { + "200": { + "$ref": "#/responses/listOrgs" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/org/update": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API to update an existing Organization.", + "tags": ["Organization"], + "summary": "Update Organization", + "operationId": "UpdateOrg", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/Organization" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/role/adduser": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Add a role for the organization to the user.", + "tags": ["Role"], + "summary": "Add User Role", + "operationId": "AddUserRole", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/Role" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/role/assignment/show": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Show roles for the current user.", + "tags": ["Role"], + "summary": "Show Role Assignment", + "operationId": "ShowRoleAssignment", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/Role" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/listRoles" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/role/perms/show": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Show permissions associated with each role.", + "tags": ["Role"], + "summary": "Show Role Permissions", + "operationId": "ShowRolePerm", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RolePerm" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/listPerms" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/role/removeuser": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Remove the role for the organization from the user.", + "tags": ["Role"], + "summary": "Remove User Role", + "operationId": "RemoveUserRole", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/Role" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/role/show": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Show role names.", + "tags": ["Role"], + "summary": "Show Role Names", + "operationId": "ShowRoleNames", + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/role/showuser": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Show roles for the organizations the current user can add or remove roles to", + "tags": ["Role"], + "summary": "Show User Role", + "operationId": "ShowUserRole", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/Role" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/listRoles" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/usage/app": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "App Usage\nDisplay app usage.", + "tags": ["DeveloperUsage"], + "operationId": "AppUsage", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionAppInstUsage" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/usage/cloudletpool": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "CloudletPool Usage\nDisplay cloudletpool usage.", + "tags": ["OperatorUsage"], + "operationId": "CloudletPoolUsage", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionCloudletPoolUsage" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/usage/cluster": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Cluster Usage\nDisplay cluster usage.", + "tags": ["DeveloperUsage"], + "operationId": "ClusterUsage", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/RegionClusterInstUsage" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/user/delete": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Deletes existing user.", + "tags": ["User"], + "summary": "Delete User", + "operationId": "DeleteUser", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/user/show": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Displays existing users to which you are authorized to access.", + "tags": ["User"], + "summary": "Show Users", + "operationId": "ShowUser", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/Organization" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/listUsers" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/auth/user/update": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Updates current user.", + "tags": ["User"], + "summary": "Update User", + "operationId": "UpdateUser", + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/login": { + "post": { + "description": "Log in to the MC to acquire a temporary bearer token for access to other APIs.\nAuthentication can be via a username and password, or an API key ID and API key if created. If two-factor authentication (2FA) is enabled on the account, an additional temporary one-time password (TOTP) from a mobile authenticator will also be required.\n", + "tags": ["Security"], + "summary": "Login", + "operationId": "Login", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/UserLogin" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/authToken" + }, + "400": { + "$ref": "#/responses/loginBadRequest" + } + } + } + }, + "/passwordreset": { + "post": { + "description": "This resets your login password.", + "tags": ["Security"], + "summary": "Reset Login Password", + "operationId": "PasswdReset", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/PasswordReset" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + } + } + } + }, + "/publicconfig": { + "post": { + "description": "Show Public Configuration for UI", + "tags": ["Config"], + "summary": "Show Public Configuration", + "operationId": "PublicConfig", + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/usercreate": { + "post": { + "description": "Creates a new user and allows them to access and manage resources.", + "tags": ["User"], + "summary": "Create User", + "operationId": "CreateUser", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/CreateUser" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/success" + }, + "400": { + "$ref": "#/responses/badRequest" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + } + }, + "definitions": { + "AccessType": { + "description": "AccessType indicates how to access the app\n\n0: `ACCESS_TYPE_DEFAULT_FOR_DEPLOYMENT`\n1: `ACCESS_TYPE_DIRECT`\n2: `ACCESS_TYPE_LOAD_BALANCER`", + "type": "integer", + "format": "int32", + "title": "(Deprecated) AccessType", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AggrVal": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "format": "int64", + "x-go-name": "DocCount" + }, + "key": { + "type": "string", + "x-go-name": "Key" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/cloudcommon/node" + }, + "Alert": { + "type": "object", + "properties": { + "active_at": { + "$ref": "#/definitions/Timestamp" + }, + "annotations": { + "description": "Annotations are extra information about the alert", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "Annotations" + }, + "controller": { + "description": "Connected controller unique id", + "type": "string", + "x-go-name": "Controller" + }, + "labels": { + "description": "Labels uniquely define the alert", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "Labels" + }, + "notify_id": { + "description": "Id of client assigned by server (internal use only)", + "type": "integer", + "format": "int64", + "x-go-name": "NotifyId" + }, + "state": { + "description": "State of the alert", + "type": "string", + "x-go-name": "State" + }, + "value": { + "description": "Any value associated with alert", + "type": "number", + "format": "double", + "x-go-name": "Value" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AlertPolicy": { + "type": "object", + "properties": { + "active_conn_limit": { + "description": "Active Connections alert threshold. Valid values 1-4294967295", + "type": "integer", + "format": "uint32", + "x-go-name": "ActiveConnLimit" + }, + "annotations": { + "description": "Additional Annotations for extra information about the alert", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "Annotations" + }, + "cpu_utilization_limit": { + "description": "Container or pod CPU utilization rate(percentage) across all nodes. Valid values 1-100", + "type": "integer", + "format": "uint32", + "x-go-name": "CpuUtilizationLimit" + }, + "delete_prepare": { + "description": "Preparing to be deleted", + "type": "boolean", + "x-go-name": "DeletePrepare" + }, + "description": { + "description": "Description of the alert policy", + "type": "string", + "x-go-name": "Description" + }, + "disk_utilization_limit": { + "description": "Container or pod disk utilization rate(percentage) across all nodes. Valid values 1-100", + "type": "integer", + "format": "uint32", + "x-go-name": "DiskUtilizationLimit" + }, + "fields": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "key": { + "$ref": "#/definitions/AlertPolicyKey" + }, + "labels": { + "description": "Additional Labels", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "Labels" + }, + "mem_utilization_limit": { + "description": "Container or pod memory utilization rate(percentage) across all nodes. Valid values 1-100", + "type": "integer", + "format": "uint32", + "x-go-name": "MemUtilizationLimit" + }, + "severity": { + "description": "Alert severity level - one of \"info\", \"warning\", \"error\"", + "type": "string", + "x-go-name": "Severity" + }, + "trigger_time": { + "$ref": "#/definitions/Duration" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AlertPolicyKey": { + "type": "object", + "properties": { + "name": { + "description": "Alert Policy name", + "type": "string", + "x-go-name": "Name" + }, + "organization": { + "description": "Name of the organization for the app that this alert can be applied to", + "type": "string", + "x-go-name": "Organization" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AlertReceiver": { + "description": "Configurable part of AlertManager Receiver", + "type": "object", + "properties": { + "AppInst": { + "$ref": "#/definitions/AppInstKey" + }, + "Cloudlet": { + "$ref": "#/definitions/CloudletKey" + }, + "Email": { + "description": "Custom receiving email", + "type": "string" + }, + "Name": { + "description": "Receiver Name", + "type": "string" + }, + "PagerDutyApiVersion": { + "description": "PagerDuty API version", + "type": "string" + }, + "PagerDutyIntegrationKey": { + "description": "PagerDuty integration key", + "type": "string" + }, + "Region": { + "description": "Region for the alert receiver", + "type": "string" + }, + "Severity": { + "description": "Alert severity filter", + "type": "string" + }, + "SlackChannel": { + "description": "Custom slack channel", + "type": "string" + }, + "SlackWebhook": { + "description": "Custom slack webhook", + "type": "string" + }, + "Type": { + "description": "Receiver type. Eg. email, slack, pagerduty", + "type": "string" + }, + "User": { + "description": "User that created this receiver", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "ApiEndpointType": { + "type": "integer", + "format": "int32", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "App": { + "description": "App belongs to developer organizations and is used to provide information about their application.", + "type": "object", + "title": "Application", + "required": ["key"], + "properties": { + "access_ports": { + "description": "Comma separated list of protocol:port pairs that the App listens on.\nEx: \"tcp:80,udp:10002\".\nAlso supports additional configurations per port:\n(1) tls (tcp-only) - Enables TLS on specified port. Ex: \"tcp:443:tls\".\n(2) nginx (udp-only) - Use NGINX LB instead of envoy for specified port. Ex: \"udp:10001:nginx\".\n(3) maxpktsize (udp-only) - Configures maximum UDP datagram size allowed on port for both upstream/downstream traffic. Ex: \"udp:10001:maxpktsize=8000\".", + "type": "string", + "x-go-name": "AccessPorts" + }, + "access_type": { + "$ref": "#/definitions/AccessType" + }, + "alert_policies": { + "description": "Alert Policies", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "AlertPolicies" + }, + "allow_serverless": { + "description": "App is allowed to deploy as serverless containers", + "type": "boolean", + "x-go-name": "AllowServerless" + }, + "android_package_name": { + "description": "Android package name used to match the App name from the Android package", + "type": "string", + "x-go-name": "AndroidPackageName" + }, + "annotations": { + "description": "Annotations is a comma separated map of arbitrary key value pairs,", + "type": "string", + "x-go-name": "Annotations", + "example": "key1=val1,key2=val2,key3=\"val 3\"" + }, + "auth_public_key": { + "description": "Public key used for authentication", + "type": "string", + "x-go-name": "AuthPublicKey" + }, + "auto_prov_policies": { + "description": "Auto provisioning policy names, may be specified multiple times", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "AutoProvPolicies" + }, + "auto_prov_policy": { + "description": "(_deprecated_) Auto provisioning policy name", + "type": "string", + "x-go-name": "AutoProvPolicy" + }, + "command": { + "description": "Command that the container runs to start service", + "type": "string", + "x-go-name": "Command" + }, + "configs": { + "description": "Customization files passed through to implementing services", + "type": "array", + "items": { + "$ref": "#/definitions/ConfigFile" + }, + "x-go-name": "Configs" + }, + "created_at": { + "$ref": "#/definitions/Timestamp" + }, + "default_flavor": { + "$ref": "#/definitions/FlavorKey" + }, + "del_opt": { + "$ref": "#/definitions/DeleteType" + }, + "delete_prepare": { + "description": "Preparing to be deleted", + "type": "boolean", + "x-go-name": "DeletePrepare" + }, + "deployment": { + "description": "Deployment type (kubernetes, docker, or vm)", + "type": "string", + "x-go-name": "Deployment" + }, + "deployment_generator": { + "description": "Deployment generator target to generate a basic deployment manifest", + "type": "string", + "x-go-name": "DeploymentGenerator" + }, + "deployment_manifest": { + "description": "Deployment manifest is the deployment specific manifest file/config.\nFor docker deployment, this can be a docker-compose or docker run file.\nFor kubernetes deployment, this can be a kubernetes yaml or helm chart file.", + "type": "string", + "x-go-name": "DeploymentManifest" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "image_path": { + "description": "URI of where image resides", + "type": "string", + "x-go-name": "ImagePath" + }, + "image_type": { + "$ref": "#/definitions/ImageType" + }, + "internal_ports": { + "description": "Should this app have access to outside world?", + "type": "boolean", + "x-go-name": "InternalPorts" + }, + "key": { + "$ref": "#/definitions/AppKey" + }, + "md5sum": { + "description": "MD5Sum of the VM-based app image", + "type": "string", + "x-go-name": "Md5Sum" + }, + "official_fqdn": { + "description": "Official FQDN is the FQDN that the app uses to connect by default", + "type": "string", + "x-go-name": "OfficialFqdn" + }, + "qos_session_duration": { + "$ref": "#/definitions/Duration" + }, + "qos_session_profile": { + "$ref": "#/definitions/QosSessionProfile" + }, + "required_outbound_connections": { + "description": "Connections this app require to determine if the app is compatible with a trust policy", + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRule" + }, + "x-go-name": "RequiredOutboundConnections" + }, + "revision": { + "description": "Revision can be specified or defaults to current timestamp when app is updated", + "type": "string", + "x-go-name": "Revision" + }, + "scale_with_cluster": { + "description": "Option to run App on all nodes of the cluster", + "type": "boolean", + "x-go-name": "ScaleWithCluster" + }, + "serverless_config": { + "$ref": "#/definitions/ServerlessConfig" + }, + "skip_hc_ports": { + "description": "Comma separated list of protocol:port pairs that we should not run health check on.\nShould be configured in case app does not always listen on these ports.\n\"all\" can be specified if no health check to be run for this app.\nNumerical values must be decimal format.\ni.e. tcp:80,udp:10002", + "type": "string", + "x-go-name": "SkipHcPorts" + }, + "template_delimiter": { + "description": "Delimiter to be used for template parsing, defaults to \"[[ ]]\"", + "type": "string", + "x-go-name": "TemplateDelimiter" + }, + "trusted": { + "description": "Indicates that an instance of this app can be started on a trusted cloudlet", + "type": "boolean", + "x-go-name": "Trusted" + }, + "updated_at": { + "$ref": "#/definitions/Timestamp" + }, + "vm_app_os_type": { + "$ref": "#/definitions/VmAppOsType" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AppAlertPolicy": { + "type": "object", + "properties": { + "alert_policy": { + "description": "Alert name", + "type": "string", + "x-go-name": "AlertPolicy" + }, + "app_key": { + "$ref": "#/definitions/AppKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AppAutoProvPolicy": { + "description": "AutoProvPolicy belonging to an app", + "type": "object", + "properties": { + "app_key": { + "$ref": "#/definitions/AppKey" + }, + "auto_prov_policy": { + "description": "Auto provisioning policy name", + "type": "string", + "x-go-name": "AutoProvPolicy" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AppInst": { + "description": "AppInst is an instance of an App on a Cloudlet where it is defined by an App plus a ClusterInst key.\nMany of the fields here are inherited from the App definition.", + "type": "object", + "title": "Application Instance", + "required": ["key"], + "properties": { + "auto_cluster_ip_access": { + "$ref": "#/definitions/IpAccess" + }, + "availability_zone": { + "description": "Optional Availability Zone if any", + "type": "string", + "x-go-name": "AvailabilityZone" + }, + "cloudlet_loc": { + "$ref": "#/definitions/Loc" + }, + "configs": { + "description": "Customization files passed through to implementing services", + "type": "array", + "items": { + "$ref": "#/definitions/ConfigFile" + }, + "x-go-name": "Configs" + }, + "created_at": { + "$ref": "#/definitions/Timestamp" + }, + "crm_override": { + "$ref": "#/definitions/CRMOverride" + }, + "dedicated_ip": { + "description": "Dedicated IP assigns an IP for this AppInst but requires platform support", + "type": "boolean", + "x-go-name": "DedicatedIp" + }, + "dns_label": { + "description": "DNS label that is unique within the cloudlet and among other AppInsts/ClusterInsts", + "type": "string", + "x-go-name": "DnsLabel" + }, + "errors": { + "description": "Any errors trying to create, update, or delete the AppInst on the Cloudlet", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Errors" + }, + "external_volume_size": { + "description": "Size of external volume to be attached to nodes. This is for the root partition", + "type": "integer", + "format": "uint64", + "x-go-name": "ExternalVolumeSize" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "flavor": { + "$ref": "#/definitions/FlavorKey" + }, + "force_update": { + "description": "Force Appinst refresh even if revision number matches App revision number.", + "type": "boolean", + "x-go-name": "ForceUpdate" + }, + "health_check": { + "$ref": "#/definitions/HealthCheck" + }, + "internal_port_to_lb_ip": { + "description": "mapping of ports to load balancer IPs", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "InternalPortToLbIp" + }, + "key": { + "$ref": "#/definitions/AppInstKey" + }, + "liveness": { + "$ref": "#/definitions/Liveness" + }, + "mapped_ports": { + "description": "For instances accessible via a shared load balancer, defines the external\nports on the shared load balancer that map to the internal ports\nExternal ports should be appended to the Uri for L4 access.", + "type": "array", + "items": { + "$ref": "#/definitions/AppPort" + }, + "x-go-name": "MappedPorts" + }, + "opt_res": { + "description": "Optional Resources required by OS flavor if any", + "type": "string", + "x-go-name": "OptRes" + }, + "power_state": { + "$ref": "#/definitions/PowerState" + }, + "real_cluster_name": { + "description": "Real ClusterInst name", + "type": "string", + "x-go-name": "RealClusterName" + }, + "revision": { + "description": "Revision changes each time the App is updated. Refreshing the App Instance will sync the revision with that of the App", + "type": "string", + "x-go-name": "Revision" + }, + "runtime_info": { + "$ref": "#/definitions/AppInstRuntime" + }, + "state": { + "$ref": "#/definitions/TrackedState" + }, + "unique_id": { + "description": "A unique id for the AppInst within the region to be used by platforms", + "type": "string", + "x-go-name": "UniqueId" + }, + "update_multiple": { + "description": "Allow multiple instances to be updated at once", + "type": "boolean", + "x-go-name": "UpdateMultiple" + }, + "updated_at": { + "$ref": "#/definitions/Timestamp" + }, + "uri": { + "description": "Base FQDN (not really URI) for the App. See Service FQDN for endpoint access.", + "type": "string", + "x-go-name": "Uri" + }, + "vm_flavor": { + "description": "OS node flavor to use", + "type": "string", + "x-go-name": "VmFlavor" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AppInstClientKey": { + "type": "object", + "properties": { + "app_inst_key": { + "$ref": "#/definitions/AppInstKey" + }, + "unique_id": { + "description": "AppInstClient Unique Id", + "type": "string", + "x-go-name": "UniqueId" + }, + "unique_id_type": { + "description": "AppInstClient Unique Id Type", + "type": "string", + "x-go-name": "UniqueIdType" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AppInstKey": { + "description": "AppInstKey uniquely identifies an Application Instance (AppInst) or Application Instance state (AppInstInfo).", + "type": "object", + "title": "App Instance Unique Key", + "properties": { + "app_key": { + "$ref": "#/definitions/AppKey" + }, + "cluster_inst_key": { + "$ref": "#/definitions/VirtualClusterInstKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AppInstLatency": { + "type": "object", + "properties": { + "key": { + "$ref": "#/definitions/AppInstKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AppInstRefKey": { + "description": "AppInstRefKey is app instance key without cloudlet key.", + "type": "object", + "title": "AppInst Ref Key", + "properties": { + "app_key": { + "$ref": "#/definitions/AppKey" + }, + "cluster_inst_key": { + "$ref": "#/definitions/ClusterInstRefKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AppInstRefs": { + "type": "object", + "properties": { + "delete_requested_insts": { + "description": "AppInsts being deleted (key is JSON of AppInst Key)", + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "uint32" + }, + "x-go-name": "DeleteRequestedInsts" + }, + "insts": { + "description": "AppInsts for App (key is JSON of AppInst Key)", + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "uint32" + }, + "x-go-name": "Insts" + }, + "key": { + "$ref": "#/definitions/AppKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AppInstRuntime": { + "description": "Runtime information of active AppInsts", + "type": "object", + "title": "AppInst Runtime Info", + "properties": { + "container_ids": { + "description": "List of container names", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "ContainerIds" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AppKey": { + "description": "AppKey uniquely identifies an App", + "type": "object", + "title": "Application unique key", + "properties": { + "name": { + "description": "App name", + "type": "string", + "x-go-name": "Name" + }, + "organization": { + "description": "App developer organization", + "type": "string", + "x-go-name": "Organization" + }, + "version": { + "description": "App version", + "type": "string", + "x-go-name": "Version" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AppPort": { + "description": "AppPort describes an L4 or L7 public access port/path mapping. This is used to track external to internal mappings for access via a shared load balancer or reverse proxy.", + "type": "object", + "title": "Application Port", + "properties": { + "end_port": { + "description": "A non-zero end port indicates a port range from internal port to end port, inclusive.", + "type": "integer", + "format": "int32", + "x-go-name": "EndPort" + }, + "fqdn_prefix": { + "description": "skip 4 to preserve the numbering. 4 was path_prefix but was removed since we dont need it after removed http\nFQDN prefix to append to base FQDN in FindCloudlet response. May be empty.", + "type": "string", + "x-go-name": "FqdnPrefix" + }, + "internal_port": { + "description": "Container port", + "type": "integer", + "format": "int32", + "x-go-name": "InternalPort" + }, + "max_pkt_size": { + "description": "Maximum datagram size (udp only)", + "type": "integer", + "format": "int64", + "x-go-name": "MaxPktSize" + }, + "nginx": { + "description": "Use nginx proxy for this port if you really need a transparent proxy (udp only)", + "type": "boolean", + "x-go-name": "Nginx" + }, + "proto": { + "$ref": "#/definitions/LProto" + }, + "public_port": { + "description": "Public facing port for TCP/UDP (may be mapped on shared LB reverse proxy)", + "type": "integer", + "format": "int32", + "x-go-name": "PublicPort" + }, + "tls": { + "description": "TLS termination for this port", + "type": "boolean", + "x-go-name": "Tls" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/dme-proto" + }, + "AutoProvCloudlet": { + "description": "AutoProvCloudlet stores the potential cloudlet and location for DME lookup", + "type": "object", + "properties": { + "key": { + "$ref": "#/definitions/CloudletKey" + }, + "loc": { + "$ref": "#/definitions/Loc" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AutoProvPolicy": { + "description": "AutoProvPolicy defines the automated provisioning policy", + "type": "object", + "properties": { + "cloudlets": { + "description": "Allowed deployment locations", + "type": "array", + "items": { + "$ref": "#/definitions/AutoProvCloudlet" + }, + "x-go-name": "Cloudlets" + }, + "delete_prepare": { + "description": "Preparing to be deleted", + "type": "boolean", + "x-go-name": "DeletePrepare" + }, + "deploy_client_count": { + "description": "Minimum number of clients within the auto deploy interval to trigger deployment", + "type": "integer", + "format": "uint32", + "x-go-name": "DeployClientCount" + }, + "deploy_interval_count": { + "description": "Number of intervals to check before triggering deployment", + "type": "integer", + "format": "uint32", + "x-go-name": "DeployIntervalCount" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "key": { + "$ref": "#/definitions/PolicyKey" + }, + "max_instances": { + "description": "Maximum number of instances (active or not)", + "type": "integer", + "format": "uint32", + "x-go-name": "MaxInstances" + }, + "min_active_instances": { + "description": "Minimum number of active instances for High-Availability", + "type": "integer", + "format": "uint32", + "x-go-name": "MinActiveInstances" + }, + "undeploy_client_count": { + "description": "Number of active clients for the undeploy interval below which trigers undeployment, 0 (default) disables auto undeploy", + "type": "integer", + "format": "uint32", + "x-go-name": "UndeployClientCount" + }, + "undeploy_interval_count": { + "description": "Number of intervals to check before triggering undeployment", + "type": "integer", + "format": "uint32", + "x-go-name": "UndeployIntervalCount" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AutoProvPolicyCloudlet": { + "description": "AutoProvPolicyCloudlet is used to add and remove Cloudlets from the Auto Provisioning Policy", + "type": "object", + "properties": { + "cloudlet_key": { + "$ref": "#/definitions/CloudletKey" + }, + "key": { + "$ref": "#/definitions/PolicyKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "AutoScalePolicy": { + "description": "AutoScalePolicy defines when and how cluster instances will have their\nnodes scaled up or down.", + "type": "object", + "properties": { + "delete_prepare": { + "description": "Preparing to be deleted", + "type": "boolean", + "x-go-name": "DeletePrepare" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "key": { + "$ref": "#/definitions/PolicyKey" + }, + "max_nodes": { + "description": "Maximum number of cluster nodes", + "type": "integer", + "format": "uint32", + "x-go-name": "MaxNodes" + }, + "min_nodes": { + "description": "Minimum number of cluster nodes", + "type": "integer", + "format": "uint32", + "x-go-name": "MinNodes" + }, + "scale_down_cpu_thresh": { + "description": "(Deprecated) Scale down cpu threshold (percentage 1 to 100), 0 means disabled", + "type": "integer", + "format": "uint32", + "x-go-name": "ScaleDownCpuThresh" + }, + "scale_up_cpu_thresh": { + "description": "(Deprecated) Scale up cpu threshold (percentage 1 to 100), 0 means disabled", + "type": "integer", + "format": "uint32", + "x-go-name": "ScaleUpCpuThresh" + }, + "stabilization_window_sec": { + "description": "Stabilization window is the time for which past triggers are considered; the largest scale factor is always taken.", + "type": "integer", + "format": "uint32", + "x-go-name": "StabilizationWindowSec" + }, + "target_active_connections": { + "description": "Target per-node number of active connections, 0 means disabled", + "type": "integer", + "format": "uint64", + "x-go-name": "TargetActiveConnections" + }, + "target_cpu": { + "description": "Target per-node cpu utilization (percentage 1 to 100), 0 means disabled", + "type": "integer", + "format": "uint32", + "x-go-name": "TargetCpu" + }, + "target_mem": { + "description": "Target per-node memory utilization (percentage 1 to 100), 0 means disabled", + "type": "integer", + "format": "uint32", + "x-go-name": "TargetMem" + }, + "trigger_time_sec": { + "description": "(Deprecated) Trigger time defines how long the target must be satified in seconds before acting upon it.", + "type": "integer", + "format": "uint32", + "x-go-name": "TriggerTimeSec" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "BillingOrganization": { + "type": "object", + "required": ["Name"], + "properties": { + "Address": { + "description": "Organization address", + "type": "string" + }, + "Address2": { + "description": "Organization address2", + "type": "string" + }, + "Children": { + "description": "Children belonging to this BillingOrganization", + "type": "string" + }, + "City": { + "description": "Organization city", + "type": "string" + }, + "Country": { + "description": "Organization country", + "type": "string" + }, + "CreatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "DeleteInProgress": { + "description": "Delete of this BillingOrganization is in progress", + "type": "boolean", + "readOnly": true + }, + "Email": { + "description": "Organization email", + "type": "string" + }, + "FirstName": { + "description": "Billing info first name", + "type": "string" + }, + "LastName": { + "description": "Billing info last name", + "type": "string" + }, + "Name": { + "description": "BillingOrganization name. Can only contain letters, digits, underscore, period, hyphen. It cannot have leading or trailing spaces or period. It cannot start with hyphen", + "type": "string" + }, + "Phone": { + "description": "Organization phone number", + "type": "string" + }, + "PostalCode": { + "description": "Organization postal code", + "type": "string" + }, + "State": { + "description": "Organization state", + "type": "string" + }, + "Type": { + "description": "Organization type: \"parent\" or \"self\"", + "type": "string" + }, + "UpdatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "CRMOverride": { + "description": "CRMOverride can be applied to commands that issue requests to the CRM.\nIt should only be used by administrators when bugs have caused the\nController and CRM to get out of sync. It allows commands from the\nController to ignore errors from the CRM, or ignore the CRM completely\n(messages will not be sent to CRM).\n\n0: `NO_OVERRIDE`\n1: `IGNORE_CRM_ERRORS`\n2: `IGNORE_CRM`\n3: `IGNORE_TRANSIENT_STATE`\n4: `IGNORE_CRM_AND_TRANSIENT_STATE`", + "type": "integer", + "format": "int32", + "title": "Overrides default CRM behaviour", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "Cloudlet": { + "description": "A Cloudlet is a set of compute resources at a particular location, provided by an Operator.", + "type": "object", + "title": "Cloudlet", + "required": ["key"], + "properties": { + "HostController": { + "description": "Address of the controller hosting the cloudlet services if it is running locally", + "type": "string" + }, + "access_vars": { + "description": "Variables required to access cloudlet", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "AccessVars" + }, + "alliance_orgs": { + "description": "This cloudlet will be treated as directly connected to these additional operator organizations for the purposes of FindCloudlet", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "AllianceOrgs" + }, + "chef_client_key": { + "description": "Chef client key", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "ChefClientKey" + }, + "config": { + "$ref": "#/definitions/PlatformConfig" + }, + "container_version": { + "description": "Cloudlet container version", + "type": "string", + "x-go-name": "ContainerVersion" + }, + "created_at": { + "$ref": "#/definitions/Timestamp" + }, + "crm_access_key_upgrade_required": { + "description": "CRM access key upgrade required", + "type": "boolean", + "x-go-name": "CrmAccessKeyUpgradeRequired" + }, + "crm_access_public_key": { + "description": "CRM access public key", + "type": "string", + "x-go-name": "CrmAccessPublicKey" + }, + "crm_override": { + "$ref": "#/definitions/CRMOverride" + }, + "default_resource_alert_threshold": { + "description": "Default resource alert threshold percentage", + "type": "integer", + "format": "int32", + "x-go-name": "DefaultResourceAlertThreshold" + }, + "delete_prepare": { + "description": "Preparing to be deleted", + "type": "boolean", + "x-go-name": "DeletePrepare" + }, + "deployment": { + "description": "Deployment type to bring up CRM services (docker, kubernetes)", + "type": "string", + "x-go-name": "Deployment" + }, + "deployment_local": { + "description": "Deploy cloudlet services locally", + "type": "boolean", + "x-go-name": "DeploymentLocal" + }, + "dns_label": { + "description": "DNS label that is unique within the region", + "type": "string", + "x-go-name": "DnsLabel" + }, + "enable_default_serverless_cluster": { + "description": "Enable experimental default multitenant (serverless) cluster", + "type": "boolean", + "x-go-name": "EnableDefaultServerlessCluster" + }, + "env_var": { + "description": "Single Key-Value pair of env var to be passed to CRM", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "EnvVar" + }, + "errors": { + "description": "Any errors trying to create, update, or delete the Cloudlet.", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Errors" + }, + "federation_config": { + "$ref": "#/definitions/FederationConfig" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "flavor": { + "$ref": "#/definitions/FlavorKey" + }, + "gpu_config": { + "$ref": "#/definitions/GPUConfig" + }, + "infra_api_access": { + "$ref": "#/definitions/InfraApiAccess" + }, + "infra_config": { + "$ref": "#/definitions/InfraConfig" + }, + "ip_support": { + "$ref": "#/definitions/IpSupport" + }, + "kafka_cluster": { + "description": "Operator provided kafka cluster endpoint to push events to", + "type": "string", + "x-go-name": "KafkaCluster" + }, + "kafka_password": { + "description": "Password for kafka SASL/PLAIN authentification, stored securely in secret storage and never visible externally", + "type": "string", + "x-go-name": "KafkaPassword" + }, + "kafka_user": { + "description": "Username for kafka SASL/PLAIN authentification, stored securely in secret storage and never visible externally", + "type": "string", + "x-go-name": "KafkaUser" + }, + "key": { + "$ref": "#/definitions/CloudletKey" + }, + "license_config_storage_path": { + "description": "GPU driver license config storage path", + "type": "string", + "x-go-name": "LicenseConfigStoragePath" + }, + "location": { + "$ref": "#/definitions/Loc" + }, + "maintenance_state": { + "$ref": "#/definitions/MaintenanceState" + }, + "notify_srv_addr": { + "description": "Address for the CRM notify listener to run on", + "type": "string", + "x-go-name": "NotifySrvAddr" + }, + "num_dynamic_ips": { + "description": "Number of dynamic IPs available for dynamic IP support", + "type": "integer", + "format": "int32", + "x-go-name": "NumDynamicIps" + }, + "override_policy_container_version": { + "description": "Override container version from policy file", + "type": "boolean", + "x-go-name": "OverridePolicyContainerVersion" + }, + "physical_name": { + "description": "Physical infrastructure cloudlet name", + "type": "string", + "x-go-name": "PhysicalName" + }, + "platform_high_availability": { + "description": "Enable platform H/A", + "type": "boolean", + "x-go-name": "PlatformHighAvailability" + }, + "platform_type": { + "$ref": "#/definitions/PlatformType" + }, + "res_tag_map": { + "description": "Optional resource to restagtbl key map key values = [gpu, nas, nic]", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ResTagTableKey" + }, + "x-go-name": "ResTagMap" + }, + "resource_quotas": { + "description": "Resource quotas", + "type": "array", + "items": { + "$ref": "#/definitions/ResourceQuota" + }, + "x-go-name": "ResourceQuotas" + }, + "root_lb_fqdn": { + "description": "Root LB FQDN which is globally unique", + "type": "string", + "x-go-name": "RootLbFqdn" + }, + "secondary_crm_access_key_upgrade_required": { + "description": "CRM secondary access key upgrade required for H/A", + "type": "boolean", + "x-go-name": "SecondaryCrmAccessKeyUpgradeRequired" + }, + "secondary_crm_access_public_key": { + "description": "CRM secondary access public key for H/A", + "type": "string", + "x-go-name": "SecondaryCrmAccessPublicKey" + }, + "secondary_notify_srv_addr": { + "description": "Address for the secondary CRM notify listener to run on", + "type": "string", + "x-go-name": "SecondaryNotifySrvAddr" + }, + "single_kubernetes_cluster_owner": { + "description": "For single kubernetes cluster cloudlet platforms, cluster is owned by this organization instead of multi-tenant", + "type": "string", + "x-go-name": "SingleKubernetesClusterOwner" + }, + "state": { + "$ref": "#/definitions/TrackedState" + }, + "static_ips": { + "description": "List of static IPs for static IP support", + "type": "string", + "x-go-name": "StaticIps" + }, + "time_limits": { + "$ref": "#/definitions/OperationTimeLimits" + }, + "trust_policy": { + "description": "Optional Trust Policy", + "type": "string", + "x-go-name": "TrustPolicy" + }, + "trust_policy_state": { + "$ref": "#/definitions/TrackedState" + }, + "updated_at": { + "$ref": "#/definitions/Timestamp" + }, + "vm_image_version": { + "description": "EdgeCloud baseimage version where CRM services reside", + "type": "string", + "x-go-name": "VmImageVersion" + }, + "vm_pool": { + "description": "VM Pool", + "type": "string", + "x-go-name": "VmPool" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CloudletAllianceOrg": { + "type": "object", + "properties": { + "key": { + "$ref": "#/definitions/CloudletKey" + }, + "organization": { + "description": "Alliance organization", + "type": "string", + "x-go-name": "Organization" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CloudletInfo": { + "type": "object", + "title": "CloudletInfo provides information from the Cloudlet Resource Manager about the state of the Cloudlet.", + "properties": { + "active_crm_instance": { + "description": "Active HA instance", + "type": "string", + "x-go-name": "ActiveCrmInstance" + }, + "availability_zones": { + "description": "Availability Zones if any", + "type": "array", + "items": { + "$ref": "#/definitions/OSAZone" + }, + "x-go-name": "AvailabilityZones" + }, + "compatibility_version": { + "description": "Version for compatibility tracking", + "type": "integer", + "format": "uint32", + "x-go-name": "CompatibilityVersion" + }, + "container_version": { + "description": "Cloudlet container version", + "type": "string", + "x-go-name": "ContainerVersion" + }, + "controller": { + "description": "Connected controller unique id", + "type": "string", + "x-go-name": "Controller" + }, + "controller_cache_received": { + "description": "Indicates all controller data has been sent to CRM", + "type": "boolean", + "x-go-name": "ControllerCacheReceived" + }, + "errors": { + "description": "Any errors encountered while making changes to the Cloudlet", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Errors" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "flavors": { + "description": "Supported flavors by the Cloudlet", + "type": "array", + "items": { + "$ref": "#/definitions/FlavorInfo" + }, + "x-go-name": "Flavors" + }, + "key": { + "$ref": "#/definitions/CloudletKey" + }, + "maintenance_state": { + "$ref": "#/definitions/MaintenanceState" + }, + "node_infos": { + "description": "Cluster node info for serverless platforms (k8s multi-tenant cluster)", + "type": "array", + "items": { + "$ref": "#/definitions/NodeInfo" + }, + "x-go-name": "NodeInfos" + }, + "notify_id": { + "description": "Id of client assigned by server (internal use only)", + "type": "integer", + "format": "int64", + "x-go-name": "NotifyId" + }, + "os_images": { + "description": "Local Images availble to cloudlet", + "type": "array", + "items": { + "$ref": "#/definitions/OSImage" + }, + "x-go-name": "OsImages" + }, + "os_max_ram": { + "description": "Maximum Ram in MB on the Cloudlet", + "type": "integer", + "format": "uint64", + "x-go-name": "OsMaxRam" + }, + "os_max_vcores": { + "description": "Maximum number of VCPU cores on the Cloudlet", + "type": "integer", + "format": "uint64", + "x-go-name": "OsMaxVcores" + }, + "os_max_vol_gb": { + "description": "Maximum amount of disk in GB on the Cloudlet", + "type": "integer", + "format": "uint64", + "x-go-name": "OsMaxVolGb" + }, + "properties": { + "description": "Cloudlet properties", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "Properties" + }, + "release_version": { + "description": "Cloudlet release version", + "type": "string", + "x-go-name": "ReleaseVersion" + }, + "resources_snapshot": { + "$ref": "#/definitions/InfraResourcesSnapshot" + }, + "standby_crm": { + "description": "Denotes if info was reported by inactive", + "type": "boolean", + "x-go-name": "StandbyCrm" + }, + "state": { + "$ref": "#/definitions/CloudletState" + }, + "status": { + "$ref": "#/definitions/StatusInfo" + }, + "trust_policy_state": { + "$ref": "#/definitions/TrackedState" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CloudletKey": { + "type": "object", + "title": "CloudletKey uniquely identifies a Cloudlet.", + "properties": { + "federated_organization": { + "description": "Federated operator organization who shared this cloudlet", + "type": "string", + "x-go-name": "FederatedOrganization" + }, + "name": { + "description": "Name of the cloudlet", + "type": "string", + "x-go-name": "Name" + }, + "organization": { + "description": "Organization of the cloudlet site", + "type": "string", + "x-go-name": "Organization" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CloudletMgmtNode": { + "type": "object", + "properties": { + "name": { + "description": "Name of Cloudlet Mgmt Node", + "type": "string", + "x-go-name": "Name" + }, + "type": { + "description": "Type of Cloudlet Mgmt Node", + "type": "string", + "x-go-name": "Type" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CloudletPool": { + "description": "CloudletPool defines a pool of Cloudlets that have restricted access", + "type": "object", + "properties": { + "cloudlets": { + "description": "Cloudlets part of the pool", + "type": "array", + "items": { + "$ref": "#/definitions/CloudletKey" + }, + "x-go-name": "Cloudlets" + }, + "created_at": { + "$ref": "#/definitions/Timestamp" + }, + "delete_prepare": { + "description": "Preparing to be deleted", + "type": "boolean", + "x-go-name": "DeletePrepare" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "key": { + "$ref": "#/definitions/CloudletPoolKey" + }, + "updated_at": { + "$ref": "#/definitions/Timestamp" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CloudletPoolKey": { + "description": "CloudletPoolKey uniquely identifies a CloudletPool.", + "type": "object", + "title": "CloudletPool unique key", + "properties": { + "name": { + "description": "CloudletPool Name", + "type": "string", + "x-go-name": "Name" + }, + "organization": { + "description": "Name of the organization this pool belongs to", + "type": "string", + "x-go-name": "Organization" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CloudletPoolMember": { + "description": "CloudletPoolMember is used to add and remove a Cloudlet from a CloudletPool", + "type": "object", + "properties": { + "cloudlet": { + "$ref": "#/definitions/CloudletKey" + }, + "key": { + "$ref": "#/definitions/CloudletPoolKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CloudletProps": { + "description": "Infra properties used to set up cloudlet", + "type": "object", + "properties": { + "organization": { + "description": "Organization", + "type": "string", + "x-go-name": "Organization" + }, + "platform_type": { + "$ref": "#/definitions/PlatformType" + }, + "properties": { + "description": "Single Key-Value pair of env var to be passed to CRM", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PropertyInfo" + }, + "x-go-name": "Properties" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CloudletRefs": { + "type": "object", + "title": "CloudletRefs track used resources and Clusters instantiated on a Cloudlet. Used resources are compared against max resources for a Cloudlet to determine if resources are available for a new Cluster to be instantiated on the Cloudlet.", + "properties": { + "cluster_insts": { + "description": "Clusters instantiated on the Cloudlet", + "type": "array", + "items": { + "$ref": "#/definitions/ClusterInstRefKey" + }, + "x-go-name": "ClusterInsts" + }, + "k8s_app_insts": { + "description": "K8s apps instantiated on the Cloudlet", + "type": "array", + "items": { + "$ref": "#/definitions/AppInstRefKey" + }, + "x-go-name": "K8SAppInsts" + }, + "key": { + "$ref": "#/definitions/CloudletKey" + }, + "opt_res_used_map": { + "description": "Used Optional Resources", + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "uint32" + }, + "x-go-name": "OptResUsedMap" + }, + "reserved_auto_cluster_ids": { + "description": "Track reservable autoclusterinsts ids in use. This is a bitmap.", + "type": "integer", + "format": "uint64", + "x-go-name": "ReservedAutoClusterIds" + }, + "root_lb_ports": { + "description": "Used ports on root load balancer. Map key is public port, value is a bitmap for the protocol\nbitmap: bit 0: tcp, bit 1: udp", + "x-go-name": "RootLbPorts" + }, + "used_dynamic_ips": { + "description": "Used dynamic IPs", + "type": "integer", + "format": "int32", + "x-go-name": "UsedDynamicIps" + }, + "used_static_ips": { + "description": "Used static IPs", + "type": "string", + "x-go-name": "UsedStaticIps" + }, + "vm_app_insts": { + "description": "VM apps instantiated on the Cloudlet", + "type": "array", + "items": { + "$ref": "#/definitions/AppInstRefKey" + }, + "x-go-name": "VmAppInsts" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CloudletResMap": { + "description": "Optional resource input consists of a resource specifier and clouldkey name", + "type": "object", + "properties": { + "key": { + "$ref": "#/definitions/CloudletKey" + }, + "mapping": { + "description": "Resource mapping info", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "Mapping" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CloudletResourceQuotaProps": { + "type": "object", + "properties": { + "organization": { + "description": "Organization", + "type": "string", + "x-go-name": "Organization" + }, + "platform_type": { + "$ref": "#/definitions/PlatformType" + }, + "properties": { + "description": "Cloudlet resource properties", + "type": "array", + "items": { + "$ref": "#/definitions/InfraResource" + }, + "x-go-name": "Properties" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CloudletResourceUsage": { + "type": "object", + "properties": { + "info": { + "description": "Infra Resource information", + "type": "array", + "items": { + "$ref": "#/definitions/InfraResource" + }, + "x-go-name": "Info" + }, + "infra_usage": { + "description": "Show Infra based usage", + "type": "boolean", + "x-go-name": "InfraUsage" + }, + "key": { + "$ref": "#/definitions/CloudletKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CloudletState": { + "type": "integer", + "format": "int32", + "title": "CloudletState is the state of the Cloudlet.", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/dme-proto" + }, + "ClusterInst": { + "description": "ClusterInst is an instance of a Cluster on a Cloudlet.\nIt is defined by a Cluster, Cloudlet, and Developer key.", + "type": "object", + "title": "Cluster Instance", + "required": ["key"], + "properties": { + "allocated_ip": { + "description": "Allocated IP for dedicated access", + "type": "string", + "x-go-name": "AllocatedIp" + }, + "auto": { + "description": "Auto is set to true when automatically created by back-end (internal use only)", + "type": "boolean", + "x-go-name": "Auto" + }, + "auto_scale_policy": { + "description": "Auto scale policy name", + "type": "string", + "x-go-name": "AutoScalePolicy" + }, + "availability_zone": { + "description": "Optional Resource AZ if any", + "type": "string", + "x-go-name": "AvailabilityZone" + }, + "created_at": { + "$ref": "#/definitions/Timestamp" + }, + "crm_override": { + "$ref": "#/definitions/CRMOverride" + }, + "delete_prepare": { + "description": "Preparing to be deleted", + "type": "boolean", + "x-go-name": "DeletePrepare" + }, + "deployment": { + "description": "Deployment type (kubernetes or docker)", + "type": "string", + "x-go-name": "Deployment" + }, + "dns_label": { + "description": "DNS label that is unique within the cloudlet and among other AppInsts/ClusterInsts", + "type": "string", + "x-go-name": "DnsLabel" + }, + "errors": { + "description": "Any errors trying to create, update, or delete the ClusterInst on the Cloudlet.", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Errors" + }, + "external_volume_size": { + "description": "Size of external volume to be attached to nodes. This is for the root partition", + "type": "integer", + "format": "uint64", + "x-go-name": "ExternalVolumeSize" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "flavor": { + "$ref": "#/definitions/FlavorKey" + }, + "fqdn": { + "description": "FQDN is a globally unique DNS id for the ClusterInst", + "type": "string", + "x-go-name": "Fqdn" + }, + "image_name": { + "description": "Optional resource specific image to launch", + "type": "string", + "x-go-name": "ImageName" + }, + "ip_access": { + "$ref": "#/definitions/IpAccess" + }, + "key": { + "$ref": "#/definitions/ClusterInstKey" + }, + "liveness": { + "$ref": "#/definitions/Liveness" + }, + "master_node_flavor": { + "description": "Generic flavor for k8s master VM when worker nodes \u003e 0", + "type": "string", + "x-go-name": "MasterNodeFlavor" + }, + "multi_tenant": { + "description": "Multi-tenant kubernetes cluster", + "type": "boolean", + "x-go-name": "MultiTenant" + }, + "networks": { + "description": "networks to connect to", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Networks" + }, + "node_flavor": { + "description": "Cloudlet specific node flavor", + "type": "string", + "x-go-name": "NodeFlavor" + }, + "num_masters": { + "description": "Number of k8s masters (In case of docker deployment, this field is not required)", + "type": "integer", + "format": "uint32", + "x-go-name": "NumMasters" + }, + "num_nodes": { + "description": "Number of k8s nodes (In case of docker deployment, this field is not required)", + "type": "integer", + "format": "uint32", + "x-go-name": "NumNodes" + }, + "opt_res": { + "description": "Optional Resources required by OS flavor if any", + "type": "string", + "x-go-name": "OptRes" + }, + "reservable": { + "description": "If ClusterInst is reservable", + "type": "boolean", + "x-go-name": "Reservable" + }, + "reservation_ended_at": { + "$ref": "#/definitions/Timestamp" + }, + "reserved_by": { + "description": "For reservable EdgeCloud ClusterInsts, the current developer tenant", + "type": "string", + "x-go-name": "ReservedBy" + }, + "resources": { + "$ref": "#/definitions/InfraResources" + }, + "shared_volume_size": { + "description": "Size of an optional shared volume to be mounted on the master", + "type": "integer", + "format": "uint64", + "x-go-name": "SharedVolumeSize" + }, + "skip_crm_cleanup_on_failure": { + "description": "Prevents cleanup of resources on failure within CRM, used for diagnostic purposes", + "type": "boolean", + "x-go-name": "SkipCrmCleanupOnFailure" + }, + "state": { + "$ref": "#/definitions/TrackedState" + }, + "updated_at": { + "$ref": "#/definitions/Timestamp" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "ClusterInstKey": { + "description": "ClusterInstKey uniquely identifies a Cluster Instance (ClusterInst) or Cluster Instance state (ClusterInstInfo).", + "type": "object", + "title": "Cluster Instance unique key", + "properties": { + "cloudlet_key": { + "$ref": "#/definitions/CloudletKey" + }, + "cluster_key": { + "$ref": "#/definitions/ClusterKey" + }, + "organization": { + "description": "Name of Developer organization that this cluster belongs to", + "type": "string", + "x-go-name": "Organization" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "ClusterInstRefKey": { + "description": "ClusterInstRefKey is cluster instance key without cloudlet key.", + "type": "object", + "title": "ClusterInst Ref Key", + "properties": { + "cluster_key": { + "$ref": "#/definitions/ClusterKey" + }, + "organization": { + "description": "Name of Developer organization that this cluster belongs to", + "type": "string", + "x-go-name": "Organization" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "ClusterKey": { + "type": "object", + "title": "ClusterKey uniquely identifies a Cluster.", + "properties": { + "name": { + "description": "Cluster name", + "type": "string", + "x-go-name": "Name" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "ClusterRefs": { + "type": "object", + "title": "ClusterRefs track used resources within a ClusterInst. Each AppInst specifies a set of required resources (Flavor), so tracking resources used by Apps within a Cluster is necessary to determine if enough resources are available for another AppInst to be instantiated on a ClusterInst.", + "properties": { + "apps": { + "description": "App instances in the Cluster Instance", + "type": "array", + "items": { + "$ref": "#/definitions/ClusterRefsAppInstKey" + }, + "x-go-name": "Apps" + }, + "key": { + "$ref": "#/definitions/ClusterInstKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "ClusterRefsAppInstKey": { + "description": "ClusterRefsAppInstKey is an app instance key without the cluster inst key,\nbut including the virtual cluster name. This is used by the ClusterRefs\nto track AppInsts instantiated in the cluster.", + "type": "object", + "title": "ClusterRefs AppInst Key", + "properties": { + "app_key": { + "$ref": "#/definitions/AppKey" + }, + "v_cluster_name": { + "description": "Virtual cluster name", + "type": "string", + "x-go-name": "VClusterName" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CollectionInterval": { + "description": "Collection interval for Influxdb (Specifically used for cq intervals, because cannot gogoproto.casttype to Duration for repeated fields otherwise)", + "type": "object", + "properties": { + "interval": { + "$ref": "#/definitions/Duration" + }, + "retention": { + "$ref": "#/definitions/Duration" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "ConfigFile": { + "description": "ConfigFile", + "type": "object", + "properties": { + "config": { + "description": "Config file contents or URI reference", + "type": "string", + "x-go-name": "Config" + }, + "kind": { + "description": "Kind (type) of config, i.e. envVarsYaml, helmCustomizationYaml", + "type": "string", + "x-go-name": "Kind" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "ContainerInfo": { + "description": "ContainerInfo is infomation about containers running on a VM,", + "type": "object", + "title": "ContainerInfo", + "properties": { + "clusterip": { + "description": "IP within the CNI and is applicable to kubernetes only", + "type": "string", + "x-go-name": "Clusterip" + }, + "name": { + "description": "Name of the container", + "type": "string", + "x-go-name": "Name" + }, + "restarts": { + "description": "Restart count, applicable to kubernetes only", + "type": "integer", + "format": "int64", + "x-go-name": "Restarts" + }, + "status": { + "description": "Runtime status of the container", + "type": "string", + "x-go-name": "Status" + }, + "type": { + "description": "Type can be docker or kubernetes", + "type": "string", + "x-go-name": "Type" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "CreateUser": { + "type": "object", + "required": ["Name"], + "properties": { + "CreatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "Email": { + "description": "User email", + "type": "string" + }, + "EmailVerified": { + "description": "Email address has been verified", + "type": "boolean", + "readOnly": true + }, + "EnableTOTP": { + "description": "Enable or disable temporary one-time passwords for the account", + "type": "boolean" + }, + "FailedLogins": { + "description": "Number of failed login attempts since last successful login", + "type": "integer", + "format": "int64" + }, + "FamilyName": { + "description": "Family Name", + "type": "string" + }, + "GivenName": { + "description": "Given Name", + "type": "string" + }, + "Iter": { + "type": "integer", + "format": "int64", + "readOnly": true + }, + "LastFailedLogin": { + "description": "Last failed login time", + "type": "string", + "format": "date-time", + "readOnly": true + }, + "LastLogin": { + "description": "Last successful login time", + "type": "string", + "format": "date-time", + "readOnly": true + }, + "Locked": { + "description": "Account is locked", + "type": "boolean", + "readOnly": true + }, + "Metadata": { + "description": "Metadata", + "type": "string" + }, + "Name": { + "description": "User name. Can only contain letters, digits, underscore, period, hyphen. It cannot have leading or trailing spaces or period. It cannot start with hyphen", + "type": "string" + }, + "Nickname": { + "description": "Nick Name", + "type": "string" + }, + "PassCrackTimeSec": { + "type": "number", + "format": "double", + "readOnly": true + }, + "Passhash": { + "type": "string", + "readOnly": true + }, + "Picture": { + "type": "string", + "readOnly": true + }, + "Salt": { + "type": "string", + "readOnly": true + }, + "TOTPSharedKey": { + "type": "string", + "readOnly": true + }, + "UpdatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "verify": { + "$ref": "#/definitions/EmailRequest" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "DateTime": { + "description": "DateTime is a time but it serializes to ISO8601 format with millis\nIt knows how to read 3 different variations of a RFC3339 date time.\nMost APIs we encounter want either millisecond or second precision times.\nThis just tries to make it worry-free.", + "type": "string", + "format": "date-time", + "x-go-package": "github.com/go-openapi/strfmt" + }, + "DebugRequest": { + "type": "object", + "title": "DebugRequest. Keep everything in one struct to make it easy to send commands without having to change the code.", + "properties": { + "args": { + "description": "Additional arguments for cmd", + "type": "string", + "x-go-name": "Args" + }, + "cmd": { + "description": "Debug command (use \"help\" to see available commands)", + "type": "string", + "x-go-name": "Cmd" + }, + "id": { + "description": "Id used internally", + "type": "integer", + "format": "uint64", + "x-go-name": "Id" + }, + "levels": { + "description": "Comma separated list of debug level names: etcd,api,notify,dmereq,locapi,infra,metrics,upgrade,info,sampled,fedapi", + "type": "string", + "x-go-name": "Levels" + }, + "node": { + "$ref": "#/definitions/NodeKey" + }, + "pretty": { + "description": "if possible, make output pretty", + "type": "boolean", + "x-go-name": "Pretty" + }, + "timeout": { + "$ref": "#/definitions/Duration" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "DeleteType": { + "description": "DeleteType specifies if AppInst can be auto deleted or not\n\n0: `NO_AUTO_DELETE`\n1: `AUTO_DELETE`", + "type": "integer", + "format": "int32", + "title": "DeleteType", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "DeploymentCloudletRequest": { + "type": "object", + "properties": { + "app": { + "$ref": "#/definitions/App" + }, + "dry_run_deploy": { + "description": "Attempt to qualify cloudlet resources for deployment", + "type": "boolean", + "x-go-name": "DryRunDeploy" + }, + "num_nodes": { + "description": "Optional number of worker VMs in dry run K8s Cluster, default = 2", + "type": "integer", + "format": "uint32", + "x-go-name": "NumNodes" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "Device": { + "description": "Device represents a device on the EdgeCloud platform\nWe record when this device first showed up on our platform", + "type": "object", + "properties": { + "fields": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "first_seen": { + "$ref": "#/definitions/Timestamp" + }, + "key": { + "$ref": "#/definitions/DeviceKey" + }, + "last_seen": { + "$ref": "#/definitions/Timestamp" + }, + "notify_id": { + "description": "Id of client assigned by server (internal use only)", + "type": "integer", + "format": "int64", + "x-go-name": "NotifyId" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "DeviceKey": { + "description": "DeviceKey is an identifier for a given device on the EdgeCloud platform\nIt is defined by a unique id and unique id type\nAnd example of such a device is a MEL device that hosts several applications", + "type": "object", + "properties": { + "unique_id": { + "description": "Unique identification of the client device or user. May be overridden by the server.", + "type": "string", + "x-go-name": "UniqueId" + }, + "unique_id_type": { + "description": "Type of unique ID provided by the client", + "type": "string", + "x-go-name": "UniqueIdType" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "DeviceReport": { + "description": "DeviceReport is a reporting message. It takes a begining and end time\nfor the report", + "type": "object", + "properties": { + "begin": { + "$ref": "#/definitions/Timestamp" + }, + "end": { + "$ref": "#/definitions/Timestamp" + }, + "key": { + "$ref": "#/definitions/DeviceKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "Duration": { + "type": "integer", + "format": "int64", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "EmailRequest": { + "description": "Email request is used for password reset and to resend welcome\nverification email.", + "type": "object", + "properties": { + "email": { + "description": "User's email address", + "type": "string", + "x-go-name": "Email", + "readOnly": true + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "EventMatch": { + "type": "object", + "properties": { + "error": { + "description": "Error substring to match", + "type": "string", + "x-go-name": "Error" + }, + "failed": { + "description": "Failure status on event to match", + "type": "boolean", + "x-go-name": "Failed" + }, + "names": { + "description": "Names of events to match", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Names" + }, + "orgs": { + "description": "Organizations on events to match", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Orgs" + }, + "regions": { + "description": "Regions on events to match", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Regions" + }, + "tags": { + "description": "Tags on events to match", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "Tags" + }, + "types": { + "description": "Types of events to match", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Types" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/cloudcommon/node" + }, + "EventSearch": { + "type": "object", + "properties": { + "allowedorgs": { + "description": "Organizations allowed to access the event", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "AllowedOrgs" + }, + "endage": { + "$ref": "#/definitions/Duration" + }, + "endtime": { + "description": "End time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "EndTime" + }, + "from": { + "description": "Start offset if paging through results", + "type": "integer", + "format": "int64", + "x-go-name": "From" + }, + "limit": { + "description": "Display the last X events", + "type": "integer", + "format": "int64", + "x-go-name": "Limit" + }, + "match": { + "$ref": "#/definitions/EventMatch" + }, + "notmatch": { + "$ref": "#/definitions/EventMatch" + }, + "startage": { + "$ref": "#/definitions/Duration" + }, + "starttime": { + "description": "Start time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "StartTime" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/cloudcommon/node" + }, + "EventTerms": { + "type": "object", + "properties": { + "names": { + "description": "Names of events", + "type": "array", + "items": { + "$ref": "#/definitions/AggrVal" + }, + "x-go-name": "Names" + }, + "orgs": { + "description": "Organizations on events", + "type": "array", + "items": { + "$ref": "#/definitions/AggrVal" + }, + "x-go-name": "Orgs" + }, + "regions": { + "description": "Regions on events", + "type": "array", + "items": { + "$ref": "#/definitions/AggrVal" + }, + "x-go-name": "Regions" + }, + "tagkeys": { + "description": "Tag keys on events", + "type": "array", + "items": { + "$ref": "#/definitions/AggrVal" + }, + "x-go-name": "TagKeys" + }, + "types": { + "description": "Types of events", + "type": "array", + "items": { + "$ref": "#/definitions/AggrVal" + }, + "x-go-name": "Types" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/cloudcommon/node" + }, + "ExecRequest": { + "description": "ExecRequest is a common struct for enabling a connection to execute some work on a container", + "type": "object", + "properties": { + "access_url": { + "description": "Access URL", + "type": "string", + "x-go-name": "AccessUrl" + }, + "answer": { + "description": "Answer", + "type": "string", + "x-go-name": "Answer" + }, + "app_inst_key": { + "$ref": "#/definitions/AppInstKey" + }, + "cmd": { + "$ref": "#/definitions/RunCmd" + }, + "console": { + "$ref": "#/definitions/RunVMConsole" + }, + "container_id": { + "description": "ContainerId is the name or ID of the target container, if applicable", + "type": "string", + "x-go-name": "ContainerId" + }, + "edge_turn_addr": { + "description": "EdgeTurn Server Address", + "type": "string", + "x-go-name": "EdgeTurnAddr" + }, + "err": { + "description": "Any error message", + "type": "string", + "x-go-name": "Err" + }, + "log": { + "$ref": "#/definitions/ShowLog" + }, + "offer": { + "description": "Offer", + "type": "string", + "x-go-name": "Offer" + }, + "timeout": { + "$ref": "#/definitions/Duration" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "FederationConfig": { + "description": "Federation config associated with the cloudlet", + "type": "object", + "properties": { + "federation_name": { + "description": "Federation name", + "type": "string", + "x-go-name": "FederationName" + }, + "partner_federation_addr": { + "description": "Partner federation address", + "type": "string", + "x-go-name": "PartnerFederationAddr" + }, + "partner_federation_id": { + "description": "Partner federation ID", + "type": "string", + "x-go-name": "PartnerFederationId" + }, + "self_federation_id": { + "description": "Self federation ID", + "type": "string", + "x-go-name": "SelfFederationId" + }, + "zone_country_code": { + "description": "Cloudlet zone country code", + "type": "string", + "x-go-name": "ZoneCountryCode" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "Flavor": { + "description": "To put it simply, a flavor is an available hardware configuration for a server.\nIt defines the size of a virtual server that can be launched.", + "type": "object", + "title": "Flavors define the compute, memory, and storage capacity of computing instances.", + "properties": { + "delete_prepare": { + "description": "Preparing to be deleted", + "type": "boolean", + "x-go-name": "DeletePrepare" + }, + "disk": { + "description": "Amount of disk space in gigabytes", + "type": "integer", + "format": "uint64", + "x-go-name": "Disk" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "key": { + "$ref": "#/definitions/FlavorKey" + }, + "opt_res_map": { + "description": "Optional Resources request, key = gpu\nform: $resource=$kind:[$alias]$count ex: optresmap=gpu=vgpu:nvidia-63:1", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "OptResMap" + }, + "ram": { + "description": "RAM in megabytes", + "type": "integer", + "format": "uint64", + "x-go-name": "Ram" + }, + "vcpus": { + "description": "Number of virtual CPUs", + "type": "integer", + "format": "uint64", + "x-go-name": "Vcpus" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "FlavorInfo": { + "description": "Flavor details from the Cloudlet", + "type": "object", + "properties": { + "disk": { + "description": "Amount of disk in GB on the Cloudlet", + "type": "integer", + "format": "uint64", + "x-go-name": "Disk" + }, + "name": { + "description": "Name of the flavor on the Cloudlet", + "type": "string", + "x-go-name": "Name" + }, + "prop_map": { + "description": "OS Flavor Properties, if any", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "PropMap" + }, + "ram": { + "description": "Ram in MB on the Cloudlet", + "type": "integer", + "format": "uint64", + "x-go-name": "Ram" + }, + "vcpus": { + "description": "Number of VCPU cores on the Cloudlet", + "type": "integer", + "format": "uint64", + "x-go-name": "Vcpus" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "FlavorKey": { + "description": "FlavorKey uniquely identifies a Flavor.", + "type": "object", + "title": "Flavor", + "properties": { + "name": { + "description": "Flavor name", + "type": "string", + "x-go-name": "Name" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "FlavorMatch": { + "type": "object", + "properties": { + "availability_zone": { + "description": "availability zone for optional resources if any", + "type": "string", + "x-go-name": "AvailabilityZone" + }, + "flavor_name": { + "description": "Flavor name to lookup", + "type": "string", + "x-go-name": "FlavorName" + }, + "key": { + "$ref": "#/definitions/CloudletKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "FlowRateLimitAlgorithm": { + "type": "integer", + "format": "int32", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "FlowRateLimitSettings": { + "type": "object", + "required": ["key"], + "properties": { + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "key": { + "$ref": "#/definitions/FlowRateLimitSettingsKey" + }, + "settings": { + "$ref": "#/definitions/FlowSettings" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "FlowRateLimitSettingsKey": { + "type": "object", + "properties": { + "flow_settings_name": { + "description": "Unique name for FlowRateLimitSettings (there can be multiple FlowSettings per RateLimitSettingsKey)", + "type": "string", + "x-go-name": "FlowSettingsName" + }, + "rate_limit_key": { + "$ref": "#/definitions/RateLimitSettingsKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "FlowSettings": { + "type": "object", + "properties": { + "burst_size": { + "description": "Burst size for flow rate limiting (required for TokenBucketAlgorithm)", + "type": "integer", + "format": "int64", + "x-go-name": "BurstSize" + }, + "flow_algorithm": { + "$ref": "#/definitions/FlowRateLimitAlgorithm" + }, + "reqs_per_second": { + "description": "Requests per second for flow rate limiting", + "type": "number", + "format": "double", + "x-go-name": "ReqsPerSecond" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "GPUConfig": { + "type": "object", + "properties": { + "driver": { + "$ref": "#/definitions/GPUDriverKey" + }, + "license_config": { + "description": "Cloudlet specific license config to setup license (will be stored in secure storage)", + "type": "string", + "x-go-name": "LicenseConfig" + }, + "license_config_md5sum": { + "description": "Cloudlet specific license config md5sum, to ensure integrity of license config", + "type": "string", + "x-go-name": "LicenseConfigMd5Sum" + }, + "properties": { + "description": "Properties to identify specifics of GPU", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "Properties" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "GPUDriver": { + "type": "object", + "properties": { + "builds": { + "description": "List of GPU driver build", + "type": "array", + "items": { + "$ref": "#/definitions/GPUDriverBuild" + }, + "x-go-name": "Builds" + }, + "delete_prepare": { + "description": "Preparing to be deleted", + "type": "boolean", + "x-go-name": "DeletePrepare" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "ignore_state": { + "description": "Ignore state will ignore any action in-progress on the GPU driver", + "type": "boolean", + "x-go-name": "IgnoreState" + }, + "key": { + "$ref": "#/definitions/GPUDriverKey" + }, + "license_config": { + "description": "License config to setup license (will be stored in secure storage)", + "type": "string", + "x-go-name": "LicenseConfig" + }, + "license_config_md5sum": { + "description": "License config md5sum, to ensure integrity of license config", + "type": "string", + "x-go-name": "LicenseConfigMd5Sum" + }, + "license_config_storage_path": { + "description": "GPU driver license config storage path", + "type": "string", + "x-go-name": "LicenseConfigStoragePath" + }, + "properties": { + "description": "Additional properties associated with GPU driver build", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "Properties", + "example": "license server information, driver release date, etc" + }, + "state": { + "description": "State to figure out if any action on the GPU driver is in-progress", + "type": "string", + "x-go-name": "State" + }, + "storage_bucket_name": { + "description": "GPU driver storage bucket name", + "type": "string", + "x-go-name": "StorageBucketName" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "GPUDriverBuild": { + "type": "object", + "properties": { + "driver_path": { + "description": "Path where the driver package is located, if it is authenticated path,\nthen credentials must be passed as part of URL (one-time download path)", + "type": "string", + "x-go-name": "DriverPath" + }, + "driver_path_creds": { + "description": "Optional credentials (username:password) to access driver path", + "type": "string", + "x-go-name": "DriverPathCreds" + }, + "hypervisor_info": { + "description": "Info on hypervisor supported by vGPU driver", + "type": "string", + "x-go-name": "HypervisorInfo" + }, + "kernel_version": { + "description": "Kernel Version supported by GPU driver build", + "type": "string", + "x-go-name": "KernelVersion" + }, + "md5sum": { + "description": "Driver package md5sum to ensure package is not corrupted", + "type": "string", + "x-go-name": "Md5Sum" + }, + "name": { + "description": "Unique identifier key", + "type": "string", + "x-go-name": "Name" + }, + "operating_system": { + "$ref": "#/definitions/OSType" + }, + "storage_path": { + "description": "GPU driver build storage path", + "type": "string", + "x-go-name": "StoragePath" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "GPUDriverBuildMember": { + "type": "object", + "properties": { + "build": { + "$ref": "#/definitions/GPUDriverBuild" + }, + "ignore_state": { + "description": "Ignore state will ignore any action in-progress on the GPU driver", + "type": "boolean", + "x-go-name": "IgnoreState" + }, + "key": { + "$ref": "#/definitions/GPUDriverKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "GPUDriverKey": { + "description": "GPUDriverKey uniquely identifies a GPU driver", + "type": "object", + "title": "GPU Driver Key", + "properties": { + "name": { + "description": "Name of the driver", + "type": "string", + "x-go-name": "Name" + }, + "organization": { + "description": "Organization to which the driver belongs to", + "type": "string", + "x-go-name": "Organization" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "HealthCheck": { + "description": "Health check status gets set by external, or rootLB health check", + "type": "integer", + "format": "int32", + "title": "Health check status", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/dme-proto" + }, + "IdleReservableClusterInsts": { + "description": "Parameters for selecting reservable ClusterInsts to delete", + "type": "object", + "properties": { + "idle_time": { + "$ref": "#/definitions/Duration" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "ImageType": { + "type": "integer", + "format": "int32", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "InfraApiAccess": { + "description": "InfraApiAccess is the type of access available to Infra API endpoint\n\n0: `DIRECT_ACCESS`\n1: `RESTRICTED_ACCESS`", + "type": "integer", + "format": "int32", + "title": "Infra API Access", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "InfraConfig": { + "description": "Infra specific configuration used for Cloudlet deployments", + "type": "object", + "properties": { + "external_network_name": { + "description": "Infra specific external network name", + "type": "string", + "x-go-name": "ExternalNetworkName" + }, + "flavor_name": { + "description": "Infra specific flavor name", + "type": "string", + "x-go-name": "FlavorName" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "InfraResource": { + "description": "InfraResource is information about cloudlet infra resource.", + "type": "object", + "title": "InfraResource", + "properties": { + "alert_threshold": { + "description": "Generate alert when more than threshold percentage of resource is used", + "type": "integer", + "format": "int32", + "x-go-name": "AlertThreshold" + }, + "description": { + "description": "Resource description", + "type": "string", + "x-go-name": "Description" + }, + "infra_max_value": { + "description": "Resource infra max value", + "type": "integer", + "format": "uint64", + "x-go-name": "InfraMaxValue" + }, + "name": { + "description": "Resource name", + "type": "string", + "x-go-name": "Name" + }, + "quota_max_value": { + "description": "Resource quota max value", + "type": "integer", + "format": "uint64", + "x-go-name": "QuotaMaxValue" + }, + "units": { + "description": "Resource units", + "type": "string", + "x-go-name": "Units" + }, + "value": { + "description": "Resource value", + "type": "integer", + "format": "uint64", + "x-go-name": "Value" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "InfraResources": { + "description": "InfraResources is infomation about infrastructure resources.", + "type": "object", + "title": "InfraResources", + "properties": { + "vms": { + "description": "Virtual machine resources info", + "type": "array", + "items": { + "$ref": "#/definitions/VmInfo" + }, + "x-go-name": "Vms" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "InfraResourcesSnapshot": { + "description": "InfraResourcesSnapshot is snapshot of information about cloudlet infra resources.", + "type": "object", + "title": "InfraResourcesSnapshot", + "properties": { + "cluster_insts": { + "description": "List of clusterinsts this resources snapshot represent", + "type": "array", + "items": { + "$ref": "#/definitions/ClusterInstRefKey" + }, + "x-go-name": "ClusterInsts" + }, + "info": { + "description": "Infra Resource information", + "type": "array", + "items": { + "$ref": "#/definitions/InfraResource" + }, + "x-go-name": "Info" + }, + "k8s_app_insts": { + "description": "List of k8s appinsts this resources snapshot represent", + "type": "array", + "items": { + "$ref": "#/definitions/AppInstRefKey" + }, + "x-go-name": "K8SAppInsts" + }, + "platform_vms": { + "description": "Virtual machine resources info", + "type": "array", + "items": { + "$ref": "#/definitions/VmInfo" + }, + "x-go-name": "PlatformVms" + }, + "vm_app_insts": { + "description": "List of vm appinsts this resources snapshot represent", + "type": "array", + "items": { + "$ref": "#/definitions/AppInstRefKey" + }, + "x-go-name": "VmAppInsts" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "IpAccess": { + "description": "IpAccess indicates the type of RootLB that Developer requires for their App\n\n0: `IP_ACCESS_UNKNOWN`\n1: `IP_ACCESS_DEDICATED`\n3: `IP_ACCESS_SHARED`", + "type": "integer", + "format": "int32", + "title": "IpAccess Options", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "IpAddr": { + "description": "IpAddr is an address for a VM which may have an external and\ninternal component. Internal and external is with respect to the VM\nand are are often the same unless a natted or floating IP is used. If\ninternalIP is not reported it is the same as the ExternalIP.", + "type": "object", + "properties": { + "externalIp": { + "description": "External IP address", + "type": "string", + "x-go-name": "ExternalIp" + }, + "internalIp": { + "description": "Internal IP address", + "type": "string", + "x-go-name": "InternalIp" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "IpSupport": { + "description": "Static IP support indicates a set of static public IPs are available for use, and managed by the Controller. Dynamic indicates the Cloudlet uses a DHCP server to provide public IP addresses, and the controller has no control over which IPs are assigned.\n\n0: `IP_SUPPORT_UNKNOWN`\n1: `IP_SUPPORT_STATIC`\n2: `IP_SUPPORT_DYNAMIC`", + "type": "integer", + "format": "int32", + "title": "Type of public IP support", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "LProto": { + "description": "LProto indicates which protocol to use for accessing an application on a particular port. This is required by Kubernetes for port mapping.\n\n0: `L_PROTO_UNKNOWN`\n1: `L_PROTO_TCP`\n2: `L_PROTO_UDP`", + "type": "integer", + "format": "int32", + "title": "Layer4 Protocol", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/dme-proto" + }, + "Liveness": { + "description": "Liveness indicates if an object was created statically via an external API call, or dynamically via an internal algorithm.\n\n0: `LIVENESS_UNKNOWN`\n1: `LIVENESS_STATIC`\n2: `LIVENESS_DYNAMIC`\n3: `LIVENESS_AUTOPROV`", + "type": "integer", + "format": "int32", + "title": "Liveness Options", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "Loc": { + "description": "GPS Location", + "type": "object", + "properties": { + "altitude": { + "description": "On android only lat and long are guaranteed to be supplied\nAltitude in meters", + "type": "number", + "format": "double", + "x-go-name": "Altitude" + }, + "course": { + "description": "Course (IOS) / bearing (Android) (degrees east relative to true north)", + "type": "number", + "format": "double", + "x-go-name": "Course" + }, + "horizontal_accuracy": { + "description": "Horizontal accuracy (radius in meters)", + "type": "number", + "format": "double", + "x-go-name": "HorizontalAccuracy" + }, + "latitude": { + "description": "Latitude in WGS 84 coordinates", + "type": "number", + "format": "double", + "x-go-name": "Latitude" + }, + "longitude": { + "description": "Longitude in WGS 84 coordinates", + "type": "number", + "format": "double", + "x-go-name": "Longitude" + }, + "speed": { + "description": "Speed (IOS) / velocity (Android) (meters/sec)", + "type": "number", + "format": "double", + "x-go-name": "Speed" + }, + "timestamp": { + "$ref": "#/definitions/Timestamp" + }, + "vertical_accuracy": { + "description": "Vertical accuracy (meters)", + "type": "number", + "format": "double", + "x-go-name": "VerticalAccuracy" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/dme-proto" + }, + "MaintenanceState": { + "description": "Maintenance allows for planned downtimes of Cloudlets.\nThese states involve message exchanges between the Controller,\nthe AutoProv service, and the CRM. Certain states are only set\nby certain actors.", + "type": "integer", + "format": "int32", + "title": "Cloudlet Maintenance States", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/dme-proto" + }, + "MaxReqsRateLimitAlgorithm": { + "type": "integer", + "format": "int32", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "MaxReqsRateLimitSettings": { + "type": "object", + "required": ["key"], + "properties": { + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "key": { + "$ref": "#/definitions/MaxReqsRateLimitSettingsKey" + }, + "settings": { + "$ref": "#/definitions/MaxReqsSettings" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "MaxReqsRateLimitSettingsKey": { + "type": "object", + "properties": { + "max_reqs_settings_name": { + "description": "Unique name for MaxReqsRateLimitSettings (there can be multiple MaxReqsSettings per RateLimitSettingsKey)", + "type": "string", + "x-go-name": "MaxReqsSettingsName" + }, + "rate_limit_key": { + "$ref": "#/definitions/RateLimitSettingsKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "MaxReqsSettings": { + "type": "object", + "properties": { + "interval": { + "$ref": "#/definitions/Duration" + }, + "max_reqs_algorithm": { + "$ref": "#/definitions/MaxReqsRateLimitAlgorithm" + }, + "max_requests": { + "description": "Maximum number of requests for the given Interval", + "type": "integer", + "format": "int64", + "x-go-name": "MaxRequests" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "Network": { + "description": "Network defines additional networks which can be optionally assigned to a cloudlet key and used on a cluster instance", + "type": "object", + "properties": { + "connection_type": { + "$ref": "#/definitions/NetworkConnectionType" + }, + "delete_prepare": { + "description": "Preparing to be deleted", + "type": "boolean", + "x-go-name": "DeletePrepare" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "key": { + "$ref": "#/definitions/NetworkKey" + }, + "routes": { + "description": "List of routes", + "type": "array", + "items": { + "$ref": "#/definitions/Route" + }, + "x-go-name": "Routes" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "NetworkConnectionType": { + "description": "NetworkConnectionType is the supported list of network types to be optionally added to a cluster instance\n\n0: `UNDEFINED`\n1: `CONNECT_TO_LOAD_BALANCER`\n2: `CONNECT_TO_CLUSTER_NODES`\n3: `CONNECT_TO_ALL`", + "type": "integer", + "format": "int32", + "title": "Network Connection Type", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "NetworkKey": { + "type": "object", + "properties": { + "cloudlet_key": { + "$ref": "#/definitions/CloudletKey" + }, + "name": { + "description": "Network Name", + "type": "string", + "x-go-name": "Name" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "Node": { + "type": "object", + "title": "Node identifies an Edge Cloud service.", + "properties": { + "build_author": { + "description": "Build Author", + "type": "string", + "x-go-name": "BuildAuthor" + }, + "build_date": { + "description": "Build Date", + "type": "string", + "x-go-name": "BuildDate" + }, + "build_head": { + "description": "Build Head Version", + "type": "string", + "x-go-name": "BuildHead" + }, + "build_master": { + "description": "Build Master Version", + "type": "string", + "x-go-name": "BuildMaster" + }, + "container_version": { + "description": "Docker edge-cloud container version which node instance use", + "type": "string", + "x-go-name": "ContainerVersion" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "hostname": { + "description": "Hostname", + "type": "string", + "x-go-name": "Hostname" + }, + "internal_pki": { + "description": "Internal PKI Config", + "type": "string", + "x-go-name": "InternalPki" + }, + "key": { + "$ref": "#/definitions/NodeKey" + }, + "notify_id": { + "description": "Id of client assigned by server (internal use only)", + "type": "integer", + "format": "int64", + "x-go-name": "NotifyId" + }, + "properties": { + "description": "Additional properties", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "Properties" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "NodeInfo": { + "description": "NodeInfo is information about a Kubernetes node", + "type": "object", + "title": "NodeInfo", + "properties": { + "allocatable": { + "description": "Maximum allocatable resources on the node (capacity - overhead)", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Udec64" + }, + "x-go-name": "Allocatable" + }, + "capacity": { + "description": "Capacity of underlying resources on the node", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Udec64" + }, + "x-go-name": "Capacity" + }, + "name": { + "description": "Node name", + "type": "string", + "x-go-name": "Name" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "NodeKey": { + "description": "NodeKey uniquely identifies a DME or CRM node", + "type": "object", + "properties": { + "cloudlet_key": { + "$ref": "#/definitions/CloudletKey" + }, + "name": { + "description": "Name or hostname of node", + "type": "string", + "x-go-name": "Name" + }, + "region": { + "description": "Region the node is in", + "type": "string", + "x-go-name": "Region" + }, + "type": { + "description": "Node type", + "type": "string", + "x-go-name": "Type" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "OSAZone": { + "type": "object", + "properties": { + "name": { + "description": "OpenStack availability zone name", + "type": "string", + "x-go-name": "Name" + }, + "status": { + "description": "OpenStack availability zone status", + "type": "string", + "x-go-name": "Status" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "OSImage": { + "type": "object", + "properties": { + "disk_format": { + "description": "format qcow2, img, etc", + "type": "string", + "x-go-name": "DiskFormat" + }, + "name": { + "description": "image name", + "type": "string", + "x-go-name": "Name" + }, + "properties": { + "description": "image properties/metadata", + "type": "string", + "x-go-name": "Properties" + }, + "tags": { + "description": "optional tags present on image", + "type": "string", + "x-go-name": "Tags" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "OSType": { + "description": "OSType is the type of the Operator System\n\n0: `Linux`\n1: `Windows`\n20: `Others`", + "type": "integer", + "format": "int32", + "title": "Operating System Type", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "OperationTimeLimits": { + "description": "Time limits for cloudlet create, update and delete operations", + "type": "object", + "title": "Operation time limits", + "properties": { + "create_app_inst_timeout": { + "$ref": "#/definitions/Duration" + }, + "create_cluster_inst_timeout": { + "$ref": "#/definitions/Duration" + }, + "delete_app_inst_timeout": { + "$ref": "#/definitions/Duration" + }, + "delete_cluster_inst_timeout": { + "$ref": "#/definitions/Duration" + }, + "update_app_inst_timeout": { + "$ref": "#/definitions/Duration" + }, + "update_cluster_inst_timeout": { + "$ref": "#/definitions/Duration" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "OperatorCode": { + "description": "OperatorCode maps a carrier code to an Operator organization name", + "type": "object", + "properties": { + "code": { + "description": "MCC plus MNC code, or custom carrier code designation.", + "type": "string", + "x-go-name": "Code" + }, + "organization": { + "description": "Operator Organization name", + "type": "string", + "x-go-name": "Organization" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "Organization": { + "type": "object", + "required": ["Name"], + "properties": { + "Address": { + "description": "Organization address", + "type": "string" + }, + "CreatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "DeleteInProgress": { + "description": "Delete of this organization is in progress", + "type": "boolean", + "readOnly": true + }, + "EdgeboxOnly": { + "description": "Edgebox only operator organization", + "type": "boolean", + "readOnly": true + }, + "Name": { + "description": "Organization name. Can only contain letters, digits, underscore, period, hyphen. It cannot have leading or trailing spaces or period. It cannot start with hyphen", + "type": "string" + }, + "Parent": { + "type": "string", + "readOnly": true + }, + "Phone": { + "description": "Organization phone number", + "type": "string" + }, + "PublicImages": { + "description": "Images are made available to other organization", + "type": "boolean", + "readOnly": true + }, + "Type": { + "description": "Organization type: \"developer\" or \"operator\"", + "type": "string" + }, + "UpdatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "PasswordReset": { + "type": "object", + "required": ["token", "password"], + "properties": { + "password": { + "description": "User's new password", + "type": "string", + "x-go-name": "Password" + }, + "token": { + "description": "Authentication token", + "type": "string", + "x-go-name": "Token" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "PlatformConfig": { + "description": "Platform specific configuration required for Cloudlet management", + "type": "object", + "properties": { + "access_api_addr": { + "description": "controller access API address", + "type": "string", + "x-go-name": "AccessApiAddr" + }, + "app_dns_root": { + "description": "App domain name root", + "type": "string", + "x-go-name": "AppDnsRoot" + }, + "cache_dir": { + "description": "cache dir", + "type": "string", + "x-go-name": "CacheDir" + }, + "chef_client_interval": { + "$ref": "#/definitions/Duration" + }, + "chef_server_path": { + "description": "Path to Chef Server", + "type": "string", + "x-go-name": "ChefServerPath" + }, + "cleanup_mode": { + "description": "Internal cleanup flag", + "type": "boolean", + "x-go-name": "CleanupMode" + }, + "cloudlet_vm_image_path": { + "description": "Path to platform base image", + "type": "string", + "x-go-name": "CloudletVmImagePath" + }, + "commercial_certs": { + "description": "Get certs from vault or generate your own for the root load balancer", + "type": "boolean", + "x-go-name": "CommercialCerts" + }, + "container_registry_path": { + "description": "Path to Docker registry holding edge-cloud image", + "type": "string", + "x-go-name": "ContainerRegistryPath" + }, + "crm_access_private_key": { + "description": "crm access private key", + "type": "string", + "x-go-name": "CrmAccessPrivateKey" + }, + "deployment_tag": { + "description": "Deployment Tag", + "type": "string", + "x-go-name": "DeploymentTag" + }, + "env_var": { + "description": "Environment variables", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "EnvVar" + }, + "notify_ctrl_addrs": { + "description": "Address of controller notify port (can be multiple of these)", + "type": "string", + "x-go-name": "NotifyCtrlAddrs" + }, + "platform_tag": { + "description": "Tag of edge-cloud image", + "type": "string", + "x-go-name": "PlatformTag" + }, + "region": { + "description": "Region", + "type": "string", + "x-go-name": "Region" + }, + "secondary_crm_access_private_key": { + "description": "secondary crm access private key", + "type": "string", + "x-go-name": "SecondaryCrmAccessPrivateKey" + }, + "span": { + "description": "Span string", + "type": "string", + "x-go-name": "Span" + }, + "test_mode": { + "description": "Internal Test flag", + "type": "boolean", + "x-go-name": "TestMode" + }, + "thanos_recv_addr": { + "description": "Thanos Receive remote write address", + "type": "string", + "x-go-name": "ThanosRecvAddr" + }, + "tls_ca_file": { + "description": "TLS ca file", + "type": "string", + "x-go-name": "TlsCaFile" + }, + "tls_cert_file": { + "description": "TLS cert file", + "type": "string", + "x-go-name": "TlsCertFile" + }, + "tls_key_file": { + "description": "TLS key file", + "type": "string", + "x-go-name": "TlsKeyFile" + }, + "use_vault_pki": { + "description": "Use Vault certs and CAs for internal TLS communication", + "type": "boolean", + "x-go-name": "UseVaultPki" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "PlatformType": { + "description": "PlatformType is the supported list of cloudlet types\n\n0: `PLATFORM_TYPE_FAKE`\n1: `PLATFORM_TYPE_DIND`\n2: `PLATFORM_TYPE_OPENSTACK`\n3: `PLATFORM_TYPE_AZURE`\n4: `PLATFORM_TYPE_GCP`\n5: `PLATFORM_TYPE_EDGEBOX`\n6: `PLATFORM_TYPE_FAKEINFRA`\n7: `PLATFORM_TYPE_VSPHERE`\n8: `PLATFORM_TYPE_AWS_EKS`\n9: `PLATFORM_TYPE_VM_POOL`\n10: `PLATFORM_TYPE_AWS_EC2`\n11: `PLATFORM_TYPE_VCD`\n12: `PLATFORM_TYPE_K8S_BARE_METAL`\n13: `PLATFORM_TYPE_KIND`\n14: `PLATFORM_TYPE_KINDINFRA`\n15: `PLATFORM_TYPE_FAKE_SINGLE_CLUSTER`\n16: `PLATFORM_TYPE_FEDERATION`\n17: `PLATFORM_TYPE_FAKE_VM_POOL`", + "type": "integer", + "format": "int32", + "title": "Platform Type", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "PolicyKey": { + "type": "object", + "properties": { + "name": { + "description": "Policy name", + "type": "string", + "x-go-name": "Name" + }, + "organization": { + "description": "Name of the organization for the cluster that this policy will apply to", + "type": "string", + "x-go-name": "Organization" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "PowerState": { + "description": "Power State of the AppInst\n\n0: `POWER_STATE_UNKNOWN`\n1: `POWER_ON_REQUESTED`\n2: `POWERING_ON`\n3: `POWER_ON`\n4: `POWER_OFF_REQUESTED`\n5: `POWERING_OFF`\n6: `POWER_OFF`\n7: `REBOOT_REQUESTED`\n8: `REBOOTING`\n9: `REBOOT`\n10: `POWER_STATE_ERROR`", + "type": "integer", + "format": "int32", + "title": "Power State", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "PropertyInfo": { + "type": "object", + "properties": { + "description": { + "description": "Description of the property", + "type": "string", + "x-go-name": "Description" + }, + "internal": { + "description": "Is the property internal, not to be set by Operator", + "type": "boolean", + "x-go-name": "Internal" + }, + "mandatory": { + "description": "Is the property mandatory", + "type": "boolean", + "x-go-name": "Mandatory" + }, + "name": { + "description": "Name of the property", + "type": "string", + "x-go-name": "Name" + }, + "secret": { + "description": "Is the property a secret value, will be hidden", + "type": "boolean", + "x-go-name": "Secret" + }, + "value": { + "description": "Default value of the property", + "type": "string", + "x-go-name": "Value" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "QosSessionProfile": { + "description": "The selected profile name will be included\nas the \"qos\" value in the qos-senf/v1/sessions POST.", + "type": "integer", + "format": "int32", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "RateLimitSettings": { + "type": "object", + "properties": { + "flow_settings": { + "description": "Map of FlowSettings (key: FlowSettingsName, value: FlowSettings)", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/FlowSettings" + }, + "x-go-name": "FlowSettings" + }, + "key": { + "$ref": "#/definitions/RateLimitSettingsKey" + }, + "max_reqs_settings": { + "description": "Map of MaxReqsSettings (key: MaxReqsSettingsName, value: MaxReqsSettings)", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MaxReqsSettings" + }, + "x-go-name": "MaxReqsSettings" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "RateLimitSettingsKey": { + "type": "object", + "properties": { + "api_endpoint_type": { + "$ref": "#/definitions/ApiEndpointType" + }, + "api_name": { + "description": "Name of API (eg. CreateApp or RegisterClient) (Use \"Global\" if not a specific API)", + "type": "string", + "x-go-name": "ApiName" + }, + "rate_limit_target": { + "$ref": "#/definitions/RateLimitTarget" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "RateLimitTarget": { + "type": "integer", + "format": "int32", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "RegionAlert": { + "type": "object", + "required": ["Region"], + "properties": { + "Alert": { + "$ref": "#/definitions/Alert" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionAlertPolicy": { + "type": "object", + "required": ["Region"], + "properties": { + "AlertPolicy": { + "$ref": "#/definitions/AlertPolicy" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionApp": { + "type": "object", + "required": ["Region"], + "properties": { + "App": { + "$ref": "#/definitions/App" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionAppAlertPolicy": { + "type": "object", + "required": ["Region"], + "properties": { + "AppAlertPolicy": { + "$ref": "#/definitions/AppAlertPolicy" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionAppAutoProvPolicy": { + "type": "object", + "required": ["Region"], + "properties": { + "AppAutoProvPolicy": { + "$ref": "#/definitions/AppAutoProvPolicy" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionAppInst": { + "type": "object", + "required": ["Region"], + "properties": { + "AppInst": { + "$ref": "#/definitions/AppInst" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionAppInstClientKey": { + "type": "object", + "required": ["Region"], + "properties": { + "AppInstClientKey": { + "$ref": "#/definitions/AppInstClientKey" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionAppInstKey": { + "type": "object", + "required": ["Region"], + "properties": { + "AppInstKey": { + "$ref": "#/definitions/AppInstKey" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionAppInstLatency": { + "type": "object", + "required": ["Region"], + "properties": { + "AppInstLatency": { + "$ref": "#/definitions/AppInstLatency" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionAppInstMetrics": { + "type": "object", + "properties": { + "AppInst": { + "$ref": "#/definitions/AppInstKey" + }, + "AppInsts": { + "description": "Application instances to filter for metrics", + "type": "array", + "items": { + "$ref": "#/definitions/AppInstKey" + } + }, + "Limit": { + "description": "Display the last X metrics", + "type": "integer", + "format": "int64" + }, + "NumSamples": { + "description": "Display X samples spaced out evenly over start and end times", + "type": "integer", + "format": "int64" + }, + "Region": { + "description": "Region name", + "type": "string" + }, + "Selector": { + "description": "Comma separated list of metrics to view. Available metrics: utilization, network, ipusage", + "type": "string" + }, + "endage": { + "$ref": "#/definitions/Duration" + }, + "endtime": { + "description": "End time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "EndTime" + }, + "startage": { + "$ref": "#/definitions/Duration" + }, + "starttime": { + "description": "Start time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "StartTime" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionAppInstRefs": { + "type": "object", + "required": ["Region"], + "properties": { + "AppInstRefs": { + "$ref": "#/definitions/AppInstRefs" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionAppInstUsage": { + "type": "object", + "properties": { + "AppInst": { + "$ref": "#/definitions/AppInstKey" + }, + "EndTime": { + "description": "Time up to which to display stats", + "type": "string", + "format": "date-time" + }, + "Region": { + "description": "Region name", + "type": "string" + }, + "StartTime": { + "description": "Time to start displaying stats from", + "type": "string", + "format": "date-time" + }, + "VmOnly": { + "description": "Show only VM-based apps", + "type": "boolean" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionAutoProvPolicy": { + "type": "object", + "required": ["Region"], + "properties": { + "AutoProvPolicy": { + "$ref": "#/definitions/AutoProvPolicy" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionAutoProvPolicyCloudlet": { + "type": "object", + "required": ["Region"], + "properties": { + "AutoProvPolicyCloudlet": { + "$ref": "#/definitions/AutoProvPolicyCloudlet" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionAutoScalePolicy": { + "type": "object", + "required": ["Region"], + "properties": { + "AutoScalePolicy": { + "$ref": "#/definitions/AutoScalePolicy" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionClientApiUsageMetrics": { + "type": "object", + "properties": { + "AppInst": { + "$ref": "#/definitions/AppInstKey" + }, + "DmeCloudlet": { + "description": "Cloudlet name where DME is running", + "type": "string" + }, + "DmeCloudletOrg": { + "description": "Operator organization where DME is running", + "type": "string" + }, + "Limit": { + "description": "Display the last X metrics", + "type": "integer", + "format": "int64" + }, + "Method": { + "description": "API call method, one of: FindCloudlet, PlatformFindCloudlet, RegisterClient, VerifyLocation", + "type": "string" + }, + "NumSamples": { + "description": "Display X samples spaced out evenly over start and end times", + "type": "integer", + "format": "int64" + }, + "Region": { + "description": "Region name", + "type": "string" + }, + "Selector": { + "description": "Comma separated list of metrics to view. Available metrics: utilization, network, ipusage", + "type": "string" + }, + "endage": { + "$ref": "#/definitions/Duration" + }, + "endtime": { + "description": "End time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "EndTime" + }, + "startage": { + "$ref": "#/definitions/Duration" + }, + "starttime": { + "description": "Start time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "StartTime" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionClientAppUsageMetrics": { + "type": "object", + "properties": { + "AppInst": { + "$ref": "#/definitions/AppInstKey" + }, + "DataNetworkType": { + "description": "Data network type used by client device. Can be used for selectors: latency", + "type": "string" + }, + "DeviceCarrier": { + "description": "Device carrier. Can be used for selectors: latency, deviceinfo", + "type": "string" + }, + "DeviceModel": { + "description": "Device model. Can be used for selectors: deviceinfo", + "type": "string" + }, + "DeviceOs": { + "description": "Device operating system. Can be used for selectors: deviceinfo", + "type": "string" + }, + "Limit": { + "description": "Display the last X metrics", + "type": "integer", + "format": "int64" + }, + "LocationTile": { + "description": "Provides the range of GPS coordinates for the location tile/square.\nFormat is: 'LocationUnderLongitude,LocationUnderLatitude_LocationOverLongitude,LocationOverLatitude_LocationTileLength'.\nLocationUnder are the GPS coordinates of the corner closest to (0,0) of the location tile.\nLocationOver are the GPS coordinates of the corner farthest from (0,0) of the location tile.\nLocationTileLength is the length (in kilometers) of one side of the location tile square", + "type": "string" + }, + "NumSamples": { + "description": "Display X samples spaced out evenly over start and end times", + "type": "integer", + "format": "int64" + }, + "Region": { + "description": "Region name", + "type": "string" + }, + "Selector": { + "description": "Comma separated list of metrics to view. Available metrics: utilization, network, ipusage", + "type": "string" + }, + "SignalStrength": { + "type": "string" + }, + "endage": { + "$ref": "#/definitions/Duration" + }, + "endtime": { + "description": "End time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "EndTime" + }, + "startage": { + "$ref": "#/definitions/Duration" + }, + "starttime": { + "description": "Start time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "StartTime" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionClientCloudletUsageMetrics": { + "type": "object", + "properties": { + "Cloudlet": { + "$ref": "#/definitions/CloudletKey" + }, + "DataNetworkType": { + "description": "Data network type used by client device. Can be used for selectors: latency", + "type": "string" + }, + "DeviceCarrier": { + "description": "Device carrier. Can be used for selectors: latency, deviceinfo", + "type": "string" + }, + "DeviceModel": { + "description": "Device model. Can be used for selectors: deviceinfo", + "type": "string" + }, + "DeviceOs": { + "description": "Device operating system. Can be used for selectors: deviceinfo", + "type": "string" + }, + "Limit": { + "description": "Display the last X metrics", + "type": "integer", + "format": "int64" + }, + "LocationTile": { + "description": "Provides the range of GPS coordinates for the location tile/square.\nFormat is: 'LocationUnderLongitude,LocationUnderLatitude_LocationOverLongitude,LocationOverLatitude_LocationTileLength'.\nLocationUnder are the GPS coordinates of the corner closest to (0,0) of the location tile.\nLocationOver are the GPS coordinates of the corner farthest from (0,0) of the location tile.\nLocationTileLength is the length (in kilometers) of one side of the location tile square", + "type": "string" + }, + "NumSamples": { + "description": "Display X samples spaced out evenly over start and end times", + "type": "integer", + "format": "int64" + }, + "Region": { + "description": "Region name", + "type": "string" + }, + "Selector": { + "description": "Comma separated list of metrics to view. Available metrics: utilization, network, ipusage", + "type": "string" + }, + "SignalStrength": { + "type": "string" + }, + "endage": { + "$ref": "#/definitions/Duration" + }, + "endtime": { + "description": "End time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "EndTime" + }, + "startage": { + "$ref": "#/definitions/Duration" + }, + "starttime": { + "description": "Start time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "StartTime" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionCloudlet": { + "type": "object", + "required": ["Region"], + "properties": { + "Cloudlet": { + "$ref": "#/definitions/Cloudlet" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionCloudletAllianceOrg": { + "type": "object", + "required": ["Region"], + "properties": { + "CloudletAllianceOrg": { + "$ref": "#/definitions/CloudletAllianceOrg" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionCloudletInfo": { + "type": "object", + "required": ["Region"], + "properties": { + "CloudletInfo": { + "$ref": "#/definitions/CloudletInfo" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionCloudletKey": { + "type": "object", + "required": ["Region"], + "properties": { + "CloudletKey": { + "$ref": "#/definitions/CloudletKey" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionCloudletMetrics": { + "type": "object", + "properties": { + "Cloudlet": { + "$ref": "#/definitions/CloudletKey" + }, + "Cloudlets": { + "description": "Cloudlet keys for metrics", + "type": "array", + "items": { + "$ref": "#/definitions/CloudletKey" + } + }, + "Limit": { + "description": "Display the last X metrics", + "type": "integer", + "format": "int64" + }, + "NumSamples": { + "description": "Display X samples spaced out evenly over start and end times", + "type": "integer", + "format": "int64" + }, + "PlatformType": { + "type": "string" + }, + "Region": { + "description": "Region name", + "type": "string" + }, + "Selector": { + "description": "Comma separated list of metrics to view. Available metrics: utilization, network, ipusage", + "type": "string" + }, + "endage": { + "$ref": "#/definitions/Duration" + }, + "endtime": { + "description": "End time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "EndTime" + }, + "startage": { + "$ref": "#/definitions/Duration" + }, + "starttime": { + "description": "Start time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "StartTime" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionCloudletPool": { + "type": "object", + "required": ["Region"], + "properties": { + "CloudletPool": { + "$ref": "#/definitions/CloudletPool" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionCloudletPoolMember": { + "type": "object", + "required": ["Region"], + "properties": { + "CloudletPoolMember": { + "$ref": "#/definitions/CloudletPoolMember" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionCloudletPoolUsage": { + "type": "object", + "properties": { + "CloudletPool": { + "$ref": "#/definitions/CloudletPoolKey" + }, + "EndTime": { + "description": "Time up to which to display stats", + "type": "string", + "format": "date-time" + }, + "Region": { + "description": "Region name", + "type": "string" + }, + "ShowVmAppsOnly": { + "description": "Show only VM-based apps", + "type": "boolean" + }, + "StartTime": { + "description": "Time to start displaying stats from", + "type": "string", + "format": "date-time" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionCloudletProps": { + "type": "object", + "required": ["Region"], + "properties": { + "CloudletProps": { + "$ref": "#/definitions/CloudletProps" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionCloudletRefs": { + "type": "object", + "required": ["Region"], + "properties": { + "CloudletRefs": { + "$ref": "#/definitions/CloudletRefs" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionCloudletResMap": { + "type": "object", + "required": ["Region"], + "properties": { + "CloudletResMap": { + "$ref": "#/definitions/CloudletResMap" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionCloudletResourceQuotaProps": { + "type": "object", + "required": ["Region"], + "properties": { + "CloudletResourceQuotaProps": { + "$ref": "#/definitions/CloudletResourceQuotaProps" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionCloudletResourceUsage": { + "type": "object", + "required": ["Region"], + "properties": { + "CloudletResourceUsage": { + "$ref": "#/definitions/CloudletResourceUsage" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionClusterInst": { + "type": "object", + "required": ["Region"], + "properties": { + "ClusterInst": { + "$ref": "#/definitions/ClusterInst" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionClusterInstKey": { + "type": "object", + "required": ["Region"], + "properties": { + "ClusterInstKey": { + "$ref": "#/definitions/ClusterInstKey" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionClusterInstMetrics": { + "type": "object", + "properties": { + "ClusterInst": { + "$ref": "#/definitions/ClusterInstKey" + }, + "ClusterInsts": { + "description": "Cluster instance keys for metrics", + "type": "array", + "items": { + "$ref": "#/definitions/ClusterInstKey" + } + }, + "Limit": { + "description": "Display the last X metrics", + "type": "integer", + "format": "int64" + }, + "NumSamples": { + "description": "Display X samples spaced out evenly over start and end times", + "type": "integer", + "format": "int64" + }, + "Region": { + "description": "Region name", + "type": "string" + }, + "Selector": { + "description": "Comma separated list of metrics to view. Available metrics: utilization, network, ipusage", + "type": "string" + }, + "endage": { + "$ref": "#/definitions/Duration" + }, + "endtime": { + "description": "End time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "EndTime" + }, + "startage": { + "$ref": "#/definitions/Duration" + }, + "starttime": { + "description": "Start time of the time range", + "type": "string", + "format": "date-time", + "x-go-name": "StartTime" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionClusterInstUsage": { + "type": "object", + "properties": { + "ClusterInst": { + "$ref": "#/definitions/ClusterInstKey" + }, + "EndTime": { + "description": "Time up to which to display stats", + "type": "string", + "format": "date-time" + }, + "Region": { + "description": "Region name", + "type": "string" + }, + "StartTime": { + "description": "Time to start displaying stats from", + "type": "string", + "format": "date-time" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionClusterRefs": { + "type": "object", + "required": ["Region"], + "properties": { + "ClusterRefs": { + "$ref": "#/definitions/ClusterRefs" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionDebugRequest": { + "type": "object", + "required": ["Region"], + "properties": { + "DebugRequest": { + "$ref": "#/definitions/DebugRequest" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionDeploymentCloudletRequest": { + "type": "object", + "required": ["Region"], + "properties": { + "DeploymentCloudletRequest": { + "$ref": "#/definitions/DeploymentCloudletRequest" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionDevice": { + "type": "object", + "required": ["Region"], + "properties": { + "Device": { + "$ref": "#/definitions/Device" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionDeviceReport": { + "type": "object", + "required": ["Region"], + "properties": { + "DeviceReport": { + "$ref": "#/definitions/DeviceReport" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionExecRequest": { + "type": "object", + "required": ["Region"], + "properties": { + "ExecRequest": { + "$ref": "#/definitions/ExecRequest" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionFlavor": { + "type": "object", + "required": ["Region"], + "properties": { + "Flavor": { + "$ref": "#/definitions/Flavor" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionFlavorMatch": { + "type": "object", + "required": ["Region"], + "properties": { + "FlavorMatch": { + "$ref": "#/definitions/FlavorMatch" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionFlowRateLimitSettings": { + "type": "object", + "required": ["Region"], + "properties": { + "FlowRateLimitSettings": { + "$ref": "#/definitions/FlowRateLimitSettings" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionGPUDriver": { + "type": "object", + "required": ["Region"], + "properties": { + "GPUDriver": { + "$ref": "#/definitions/GPUDriver" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionGPUDriverBuildMember": { + "type": "object", + "required": ["Region"], + "properties": { + "GPUDriverBuildMember": { + "$ref": "#/definitions/GPUDriverBuildMember" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionGPUDriverKey": { + "type": "object", + "required": ["Region"], + "properties": { + "GPUDriverKey": { + "$ref": "#/definitions/GPUDriverKey" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionIdleReservableClusterInsts": { + "type": "object", + "required": ["Region"], + "properties": { + "IdleReservableClusterInsts": { + "$ref": "#/definitions/IdleReservableClusterInsts" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionMaxReqsRateLimitSettings": { + "type": "object", + "required": ["Region"], + "properties": { + "MaxReqsRateLimitSettings": { + "$ref": "#/definitions/MaxReqsRateLimitSettings" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionNetwork": { + "type": "object", + "required": ["Region"], + "properties": { + "Network": { + "$ref": "#/definitions/Network" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionNode": { + "type": "object", + "required": ["Region"], + "properties": { + "Node": { + "$ref": "#/definitions/Node" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionOperatorCode": { + "type": "object", + "required": ["Region"], + "properties": { + "OperatorCode": { + "$ref": "#/definitions/OperatorCode" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionRateLimitSettings": { + "type": "object", + "required": ["Region"], + "properties": { + "RateLimitSettings": { + "$ref": "#/definitions/RateLimitSettings" + }, + "Region": { + "description": "Region name", + "type": "string" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionResTagTable": { + "type": "object", + "required": ["Region"], + "properties": { + "Region": { + "description": "Region name", + "type": "string" + }, + "ResTagTable": { + "$ref": "#/definitions/ResTagTable" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionResTagTableKey": { + "type": "object", + "required": ["Region"], + "properties": { + "Region": { + "description": "Region name", + "type": "string" + }, + "ResTagTableKey": { + "$ref": "#/definitions/ResTagTableKey" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionSettings": { + "type": "object", + "required": ["Region"], + "properties": { + "Region": { + "description": "Region name", + "type": "string" + }, + "Settings": { + "$ref": "#/definitions/Settings" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionTrustPolicy": { + "type": "object", + "required": ["Region"], + "properties": { + "Region": { + "description": "Region name", + "type": "string" + }, + "TrustPolicy": { + "$ref": "#/definitions/TrustPolicy" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionTrustPolicyException": { + "type": "object", + "required": ["Region"], + "properties": { + "Region": { + "description": "Region name", + "type": "string" + }, + "TrustPolicyException": { + "$ref": "#/definitions/TrustPolicyException" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionVMPool": { + "type": "object", + "required": ["Region"], + "properties": { + "Region": { + "description": "Region name", + "type": "string" + }, + "VMPool": { + "$ref": "#/definitions/VMPool" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RegionVMPoolMember": { + "type": "object", + "required": ["Region"], + "properties": { + "Region": { + "description": "Region name", + "type": "string" + }, + "VMPoolMember": { + "$ref": "#/definitions/VMPoolMember" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "ResTagTable": { + "type": "object", + "properties": { + "azone": { + "description": "Availability zone(s) of resource if required", + "type": "string", + "x-go-name": "Azone" + }, + "delete_prepare": { + "description": "Preparing to be deleted", + "type": "boolean", + "x-go-name": "DeletePrepare" + }, + "fields": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "key": { + "$ref": "#/definitions/ResTagTableKey" + }, + "tags": { + "description": "One or more string tags", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "Tags" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "ResTagTableKey": { + "type": "object", + "properties": { + "name": { + "description": "Resource Table Name", + "type": "string", + "x-go-name": "Name" + }, + "organization": { + "description": "Operator organization of the cloudlet site.", + "type": "string", + "x-go-name": "Organization" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "ResourceQuota": { + "description": "Resource Quota", + "type": "object", + "properties": { + "alert_threshold": { + "description": "Generate alert when more than threshold percentage of resource is used", + "type": "integer", + "format": "int32", + "x-go-name": "AlertThreshold" + }, + "name": { + "description": "Resource name on which to set quota", + "type": "string", + "x-go-name": "Name" + }, + "value": { + "description": "Quota value of the resource", + "type": "integer", + "format": "uint64", + "x-go-name": "Value" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "Result": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int64", + "x-go-name": "Code" + }, + "message": { + "type": "string", + "x-go-name": "Message" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "Role": { + "type": "object", + "properties": { + "org": { + "description": "Organization name", + "type": "string", + "x-go-name": "Org" + }, + "role": { + "description": "Role which defines the set of permissions", + "type": "string", + "x-go-name": "Role" + }, + "username": { + "description": "User name", + "type": "string", + "x-go-name": "Username" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "RolePerm": { + "type": "object", + "properties": { + "action": { + "description": "Action defines what type of action can be performed on a resource", + "type": "string", + "x-go-name": "Action" + }, + "resource": { + "description": "Resource defines a resource to act upon", + "type": "string", + "x-go-name": "Resource" + }, + "role": { + "description": "Role defines a collection of permissions, which are resource-action pairs", + "type": "string", + "x-go-name": "Role" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "Route": { + "type": "object", + "properties": { + "destination_cidr": { + "description": "Destination CIDR", + "type": "string", + "x-go-name": "DestinationCidr" + }, + "next_hop_ip": { + "description": "Next hop IP", + "type": "string", + "x-go-name": "NextHopIp" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "RunCmd": { + "type": "object", + "properties": { + "cloudlet_mgmt_node": { + "$ref": "#/definitions/CloudletMgmtNode" + }, + "command": { + "description": "Command or Shell", + "type": "string", + "x-go-name": "Command" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "RunVMConsole": { + "type": "object", + "properties": { + "url": { + "description": "VM Console URL", + "type": "string", + "x-go-name": "Url" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "SecurityRule": { + "type": "object", + "properties": { + "port_range_max": { + "description": "TCP or UDP port range end", + "type": "integer", + "format": "uint32", + "x-go-name": "PortRangeMax" + }, + "port_range_min": { + "description": "TCP or UDP port range start", + "type": "integer", + "format": "uint32", + "x-go-name": "PortRangeMin" + }, + "protocol": { + "description": "TCP, UDP, ICMP", + "type": "string", + "x-go-name": "Protocol" + }, + "remote_cidr": { + "description": "Remote CIDR X.X.X.X/X", + "type": "string", + "x-go-name": "RemoteCidr" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "ServerlessConfig": { + "type": "object", + "properties": { + "min_replicas": { + "description": "Minimum number of replicas when serverless", + "type": "integer", + "format": "uint32", + "x-go-name": "MinReplicas" + }, + "ram": { + "description": "RAM allocation in megabytes per container when serverless", + "type": "integer", + "format": "uint64", + "x-go-name": "Ram" + }, + "vcpus": { + "$ref": "#/definitions/Udec64" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "Settings": { + "description": "Global settings", + "type": "object", + "properties": { + "alert_policy_min_trigger_time": { + "$ref": "#/definitions/Duration" + }, + "appinst_client_cleanup_interval": { + "$ref": "#/definitions/Duration" + }, + "auto_deploy_interval_sec": { + "description": "Auto Provisioning Stats push and analysis interval (seconds)", + "type": "number", + "format": "double", + "x-go-name": "AutoDeployIntervalSec" + }, + "auto_deploy_max_intervals": { + "description": "Auto Provisioning Policy max allowed intervals", + "type": "integer", + "format": "uint32", + "x-go-name": "AutoDeployMaxIntervals" + }, + "auto_deploy_offset_sec": { + "description": "Auto Provisioning analysis offset from interval (seconds)", + "type": "number", + "format": "double", + "x-go-name": "AutoDeployOffsetSec" + }, + "chef_client_interval": { + "$ref": "#/definitions/Duration" + }, + "cleanup_reservable_auto_cluster_idletime": { + "$ref": "#/definitions/Duration" + }, + "cloudlet_maintenance_timeout": { + "$ref": "#/definitions/Duration" + }, + "cluster_auto_scale_averaging_duration_sec": { + "description": "Cluster auto scale averaging duration for stats to avoid spikes (seconds), avoid setting below 30s or it will not capture any measurements to average", + "type": "integer", + "format": "int64", + "x-go-name": "ClusterAutoScaleAveragingDurationSec" + }, + "cluster_auto_scale_retry_delay": { + "$ref": "#/definitions/Duration" + }, + "create_app_inst_timeout": { + "$ref": "#/definitions/Duration" + }, + "create_cloudlet_timeout": { + "$ref": "#/definitions/Duration" + }, + "create_cluster_inst_timeout": { + "$ref": "#/definitions/Duration" + }, + "delete_app_inst_timeout": { + "$ref": "#/definitions/Duration" + }, + "delete_cluster_inst_timeout": { + "$ref": "#/definitions/Duration" + }, + "disable_rate_limit": { + "description": "Disable rate limiting for APIs (default is false)", + "type": "boolean", + "x-go-name": "DisableRateLimit" + }, + "dme_api_metrics_collection_interval": { + "$ref": "#/definitions/Duration" + }, + "edge_events_metrics_collection_interval": { + "$ref": "#/definitions/Duration" + }, + "edge_events_metrics_continuous_queries_collection_intervals": { + "description": "List of collection intervals for Continuous Queries for EdgeEvents metrics", + "type": "array", + "items": { + "$ref": "#/definitions/CollectionInterval" + }, + "x-go-name": "EdgeEventsMetricsContinuousQueriesCollectionIntervals" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "influx_db_cloudlet_usage_metrics_retention": { + "$ref": "#/definitions/Duration" + }, + "influx_db_downsampled_metrics_retention": { + "$ref": "#/definitions/Duration" + }, + "influx_db_edge_events_metrics_retention": { + "$ref": "#/definitions/Duration" + }, + "influx_db_metrics_retention": { + "$ref": "#/definitions/Duration" + }, + "location_tile_side_length_km": { + "description": "Length of location tiles side for latency metrics (km)", + "type": "integer", + "format": "int64", + "x-go-name": "LocationTileSideLengthKm" + }, + "master_node_flavor": { + "description": "Default flavor for k8s master VM and \u003e 0 workers", + "type": "string", + "x-go-name": "MasterNodeFlavor" + }, + "max_tracked_dme_clients": { + "description": "Max DME clients to be tracked at the same time.", + "type": "integer", + "format": "int32", + "x-go-name": "MaxTrackedDmeClients" + }, + "platform_ha_instance_active_expire_time": { + "$ref": "#/definitions/Duration" + }, + "platform_ha_instance_poll_interval": { + "$ref": "#/definitions/Duration" + }, + "rate_limit_max_tracked_ips": { + "description": "Maximum number of IPs to track for rate limiting", + "type": "integer", + "format": "int64", + "x-go-name": "RateLimitMaxTrackedIps" + }, + "resource_snapshot_thread_interval": { + "$ref": "#/definitions/Duration" + }, + "shepherd_alert_evaluation_interval": { + "$ref": "#/definitions/Duration" + }, + "shepherd_health_check_interval": { + "$ref": "#/definitions/Duration" + }, + "shepherd_health_check_retries": { + "description": "Number of times Shepherd Health Check fails before we mark appInst down", + "type": "integer", + "format": "int32", + "x-go-name": "ShepherdHealthCheckRetries" + }, + "shepherd_metrics_collection_interval": { + "$ref": "#/definitions/Duration" + }, + "shepherd_metrics_scrape_interval": { + "$ref": "#/definitions/Duration" + }, + "update_app_inst_timeout": { + "$ref": "#/definitions/Duration" + }, + "update_cloudlet_timeout": { + "$ref": "#/definitions/Duration" + }, + "update_cluster_inst_timeout": { + "$ref": "#/definitions/Duration" + }, + "update_trust_policy_timeout": { + "$ref": "#/definitions/Duration" + }, + "update_vm_pool_timeout": { + "$ref": "#/definitions/Duration" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "ShowLog": { + "type": "object", + "properties": { + "follow": { + "description": "Stream data", + "type": "boolean", + "x-go-name": "Follow" + }, + "since": { + "description": "Show logs since either a duration ago (5s, 2m, 3h) or a timestamp (RFC3339)", + "type": "string", + "x-go-name": "Since" + }, + "tail": { + "description": "Show only a recent number of lines", + "type": "integer", + "format": "int32", + "x-go-name": "Tail" + }, + "timestamps": { + "description": "Show timestamps", + "type": "boolean", + "x-go-name": "Timestamps" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "StatusInfo": { + "description": "Used to track status of create/delete/update for resources that are being modified\nby the controller via the CRM. Tasks are the high level jobs that are to be completed.\nSteps are work items within a task. Within the clusterinst and appinst objects this\nis converted to a string", + "type": "object", + "title": "Status Information", + "properties": { + "max_tasks": { + "type": "integer", + "format": "uint32", + "x-go-name": "MaxTasks" + }, + "msg_count": { + "type": "integer", + "format": "uint32", + "x-go-name": "MsgCount" + }, + "msgs": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Msgs" + }, + "step_name": { + "type": "string", + "x-go-name": "StepName" + }, + "task_name": { + "type": "string", + "x-go-name": "TaskName" + }, + "task_number": { + "type": "integer", + "format": "uint32", + "x-go-name": "TaskNumber" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "Timestamp": { + "description": "All minutes are 60 seconds long. Leap seconds are \"smeared\" so that no leap\nsecond table is needed for interpretation, using a [24-hour linear\nsmear](https://developers.google.com/time/smear).\n\nThe range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By\nrestricting to that range, we ensure that we can convert to and from [RFC\n3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.\n\n# Examples\n\nExample 1: Compute Timestamp from POSIX `time()`.\n\nTimestamp timestamp;\ntimestamp.set_seconds(time(NULL));\ntimestamp.set_nanos(0);\n\nExample 2: Compute Timestamp from POSIX `gettimeofday()`.\n\nstruct timeval tv;\ngettimeofday(\u0026tv, NULL);\n\nTimestamp timestamp;\ntimestamp.set_seconds(tv.tv_sec);\ntimestamp.set_nanos(tv.tv_usec * 1000);\n\nExample 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.\n\nFILETIME ft;\nGetSystemTimeAsFileTime(\u0026ft);\nUINT64 ticks = (((UINT64)ft.dwHighDateTime) \u003c\u003c 32) | ft.dwLowDateTime;\n\nA Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z\nis 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.\nTimestamp timestamp;\ntimestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));\ntimestamp.set_nanos((INT32) ((ticks % 10000000) * 100));\n\nExample 4: Compute Timestamp from Java `System.currentTimeMillis()`.\n\nlong millis = System.currentTimeMillis();\n\nTimestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)\n.setNanos((int) ((millis % 1000) * 1000000)).build();\n\n\nExample 5: Compute Timestamp from current time in Python.\n\ntimestamp = Timestamp()\ntimestamp.GetCurrentTime()\n\n# JSON Mapping\n\nIn JSON format, the Timestamp type is encoded as a string in the\n[RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the\nformat is \"{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z\"\nwhere {year} is always expressed using four digits while {month}, {day},\n{hour}, {min}, and {sec} are zero-padded to two digits each. The fractional\nseconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),\nare optional. The \"Z\" suffix indicates the timezone (\"UTC\"); the timezone\nis required. A proto3 JSON serializer should always use UTC (as indicated by\n\"Z\") when printing the Timestamp type and a proto3 JSON parser should be\nable to accept both UTC and other timezones (as indicated by an offset).\n\nFor example, \"2017-01-15T01:30:15.01Z\" encodes 15.01 seconds past\n01:30 UTC on January 15, 2017.\n\nIn JavaScript, one can convert a Date object to this format using the\nstandard\n[toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)\nmethod. In Python, a standard `datetime.datetime` object can be converted\nto this format using\n[`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with\nthe time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use\nthe Joda Time's [`ISODateTimeFormat.dateTime()`](\nhttp://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D\n) to obtain a formatter capable of generating timestamps in this format.", + "type": "object", + "title": "A Timestamp represents a point in time independent of any time zone or local\ncalendar, encoded as a count of seconds and fractions of seconds at\nnanosecond resolution. The count is relative to an epoch at UTC midnight on\nJanuary 1, 1970, in the proleptic Gregorian calendar which extends the\nGregorian calendar backwards to year one.", + "properties": { + "nanos": { + "description": "Non-negative fractions of a second at nanosecond resolution. Negative\nsecond values with fractions must still have non-negative nanos values\nthat count forward in time. Must be from 0 to 999,999,999\ninclusive.", + "type": "integer", + "format": "int32", + "x-go-name": "Nanos" + }, + "seconds": { + "description": "Represents seconds of UTC time since Unix epoch\n1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n9999-12-31T23:59:59Z inclusive.", + "type": "integer", + "format": "int64", + "x-go-name": "Seconds" + } + }, + "x-go-package": "github.com/gogo/protobuf/types" + }, + "Token": { + "type": "object", + "properties": { + "token": { + "description": "Authentication token", + "type": "string", + "x-go-name": "Token" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "TrackedState": { + "description": "TrackedState is used to track the state of an object on a remote node,\ni.e. track the state of a ClusterInst object on the CRM (Cloudlet).\n\n0: `TRACKED_STATE_UNKNOWN`\n1: `NOT_PRESENT`\n2: `CREATE_REQUESTED`\n3: `CREATING`\n4: `CREATE_ERROR`\n5: `READY`\n6: `UPDATE_REQUESTED`\n7: `UPDATING`\n8: `UPDATE_ERROR`\n9: `DELETE_REQUESTED`\n10: `DELETING`\n11: `DELETE_ERROR`\n12: `DELETE_PREPARE`\n13: `CRM_INITOK`\n14: `CREATING_DEPENDENCIES`\n15: `DELETE_DONE`", + "type": "integer", + "format": "int32", + "title": "Tracked States", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "TrustPolicy": { + "description": "TrustPolicy defines security restrictions for cluster instances\nnodes scaled up or down.", + "type": "object", + "properties": { + "delete_prepare": { + "description": "Preparing to be deleted", + "type": "boolean", + "x-go-name": "DeletePrepare" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "key": { + "$ref": "#/definitions/PolicyKey" + }, + "outbound_security_rules": { + "description": "List of outbound security rules for whitelisting traffic", + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRule" + }, + "x-go-name": "OutboundSecurityRules" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "TrustPolicyException": { + "type": "object", + "properties": { + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "key": { + "$ref": "#/definitions/TrustPolicyExceptionKey" + }, + "outbound_security_rules": { + "description": "List of outbound security rules for whitelisting traffic", + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRule" + }, + "x-go-name": "OutboundSecurityRules" + }, + "state": { + "$ref": "#/definitions/TrustPolicyExceptionState" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "TrustPolicyExceptionKey": { + "type": "object", + "properties": { + "app_key": { + "$ref": "#/definitions/AppKey" + }, + "cloudlet_pool_key": { + "$ref": "#/definitions/CloudletPoolKey" + }, + "name": { + "description": "TrustPolicyExceptionKey name", + "type": "string", + "x-go-name": "Name" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "TrustPolicyExceptionState": { + "type": "integer", + "format": "int32", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "Udec64": { + "description": "Udec64 is an unsigned decimal with whole number values\nas uint64, and decimal values in nanos.", + "type": "object", + "title": "Udec64", + "properties": { + "nanos": { + "description": "Decimal value in nanos", + "type": "integer", + "format": "uint32", + "x-go-name": "Nanos" + }, + "whole": { + "description": "Whole number value", + "type": "integer", + "format": "uint64", + "x-go-name": "Whole" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "User": { + "type": "object", + "required": ["Name"], + "properties": { + "CreatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "Email": { + "description": "User email", + "type": "string" + }, + "EmailVerified": { + "description": "Email address has been verified", + "type": "boolean", + "readOnly": true + }, + "EnableTOTP": { + "description": "Enable or disable temporary one-time passwords for the account", + "type": "boolean" + }, + "FailedLogins": { + "description": "Number of failed login attempts since last successful login", + "type": "integer", + "format": "int64" + }, + "FamilyName": { + "description": "Family Name", + "type": "string" + }, + "GivenName": { + "description": "Given Name", + "type": "string" + }, + "Iter": { + "type": "integer", + "format": "int64", + "readOnly": true + }, + "LastFailedLogin": { + "description": "Last failed login time", + "type": "string", + "format": "date-time", + "readOnly": true + }, + "LastLogin": { + "description": "Last successful login time", + "type": "string", + "format": "date-time", + "readOnly": true + }, + "Locked": { + "description": "Account is locked", + "type": "boolean", + "readOnly": true + }, + "Metadata": { + "description": "Metadata", + "type": "string" + }, + "Name": { + "description": "User name. Can only contain letters, digits, underscore, period, hyphen. It cannot have leading or trailing spaces or period. It cannot start with hyphen", + "type": "string" + }, + "Nickname": { + "description": "Nick Name", + "type": "string" + }, + "PassCrackTimeSec": { + "type": "number", + "format": "double", + "readOnly": true + }, + "Passhash": { + "type": "string", + "readOnly": true + }, + "Picture": { + "type": "string", + "readOnly": true + }, + "Salt": { + "type": "string", + "readOnly": true + }, + "TOTPSharedKey": { + "type": "string", + "readOnly": true + }, + "UpdatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "UserLogin": { + "type": "object", + "required": ["username", "password"], + "properties": { + "apikey": { + "description": "API key if logging in using API key", + "type": "string", + "x-go-name": "ApiKey" + }, + "apikeyid": { + "description": "API key ID if logging in using API key", + "type": "string", + "x-go-name": "ApiKeyId" + }, + "password": { + "description": "User's password", + "type": "string", + "x-go-name": "Password" + }, + "totp": { + "description": "Temporary one-time password if 2-factor authentication is enabled", + "type": "string", + "x-go-name": "TOTP" + }, + "username": { + "description": "User's name or email address", + "type": "string", + "x-go-name": "Username" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/ormapi" + }, + "VM": { + "type": "object", + "properties": { + "flavor": { + "$ref": "#/definitions/FlavorInfo" + }, + "group_name": { + "description": "VM Group Name", + "type": "string", + "x-go-name": "GroupName" + }, + "internal_name": { + "description": "VM Internal Name", + "type": "string", + "x-go-name": "InternalName" + }, + "name": { + "description": "VM Name", + "type": "string", + "x-go-name": "Name" + }, + "net_info": { + "$ref": "#/definitions/VMNetInfo" + }, + "state": { + "$ref": "#/definitions/VMState" + }, + "updated_at": { + "$ref": "#/definitions/Timestamp" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "VMNetInfo": { + "type": "object", + "properties": { + "external_ip": { + "description": "External IP", + "type": "string", + "x-go-name": "ExternalIp" + }, + "internal_ip": { + "description": "Internal IP", + "type": "string", + "x-go-name": "InternalIp" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "VMPool": { + "description": "VMPool defines a pool of VMs to be part of a Cloudlet", + "type": "object", + "properties": { + "crm_override": { + "$ref": "#/definitions/CRMOverride" + }, + "delete_prepare": { + "description": "Preparing to be deleted", + "type": "boolean", + "x-go-name": "DeletePrepare" + }, + "errors": { + "description": "Any errors trying to add/remove VM to/from VM Pool", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Errors" + }, + "fields": { + "description": "Fields are used for the Update API to specify which fields to apply", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Fields" + }, + "key": { + "$ref": "#/definitions/VMPoolKey" + }, + "state": { + "$ref": "#/definitions/TrackedState" + }, + "vms": { + "description": "list of VMs to be part of VM pool", + "type": "array", + "items": { + "$ref": "#/definitions/VM" + }, + "x-go-name": "Vms" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "VMPoolKey": { + "description": "VMPoolKey uniquely identifies a VMPool.", + "type": "object", + "title": "VMPool unique key", + "properties": { + "name": { + "description": "Name of the vmpool", + "type": "string", + "x-go-name": "Name" + }, + "organization": { + "description": "Organization of the vmpool", + "type": "string", + "x-go-name": "Organization" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "VMPoolMember": { + "description": "VMPoolMember is used to add and remove VM from VM Pool", + "type": "object", + "properties": { + "crm_override": { + "$ref": "#/definitions/CRMOverride" + }, + "key": { + "$ref": "#/definitions/VMPoolKey" + }, + "vm": { + "$ref": "#/definitions/VM" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "VMState": { + "description": "VMState is the state of the VM\n\n0: `VM_FREE`\n1: `VM_IN_PROGRESS`\n2: `VM_IN_USE`\n3: `VM_ADD`\n4: `VM_REMOVE`\n5: `VM_UPDATE`\n6: `VM_FORCE_FREE`", + "type": "integer", + "format": "int32", + "title": "VM State", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "VirtualClusterInstKey": { + "description": "Virtual ClusterInstKey", + "type": "object", + "properties": { + "cloudlet_key": { + "$ref": "#/definitions/CloudletKey" + }, + "cluster_key": { + "$ref": "#/definitions/ClusterKey" + }, + "organization": { + "description": "Name of Developer organization that this cluster belongs to", + "type": "string", + "x-go-name": "Organization" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "VmAppOsType": { + "type": "integer", + "format": "int32", + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "VmInfo": { + "description": "VmInfo is information about Virtual Machine resources.", + "type": "object", + "title": "VmInfo", + "properties": { + "containers": { + "description": "Information about containers running in the VM", + "type": "array", + "items": { + "$ref": "#/definitions/ContainerInfo" + }, + "x-go-name": "Containers" + }, + "infraFlavor": { + "description": "Flavor allocated within the cloudlet infrastructure, distinct from the control plane flavor", + "type": "string", + "x-go-name": "InfraFlavor" + }, + "ipaddresses": { + "description": "IP addresses allocated to the VM", + "type": "array", + "items": { + "$ref": "#/definitions/IpAddr" + }, + "x-go-name": "Ipaddresses" + }, + "name": { + "description": "Virtual machine name", + "type": "string", + "x-go-name": "Name" + }, + "status": { + "description": "Runtime status of the VM", + "type": "string", + "x-go-name": "Status" + }, + "type": { + "description": "Type can be platformvm, platform-cluster-master, platform-cluster-primary-node, platform-cluster-secondary-node, sharedrootlb, dedicatedrootlb, cluster-master, cluster-k8s-node, cluster-docker-node, appvm", + "type": "string", + "x-go-name": "Type" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/api/edgeproto" + }, + "alert": { + "description": "Alert alert", + "type": "object", + "required": ["labels"], + "properties": { + "generatorURL": { + "description": "generator URL\nFormat: uri", + "type": "string", + "format": "uri", + "x-go-name": "GeneratorURL" + }, + "labels": { + "$ref": "#/definitions/labelSet" + } + }, + "x-go-name": "Alert", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "alertGroup": { + "description": "AlertGroup alert group", + "type": "object", + "required": ["alerts", "labels", "receiver"], + "properties": { + "alerts": { + "description": "alerts", + "type": "array", + "items": { + "$ref": "#/definitions/gettableAlert" + }, + "x-go-name": "Alerts" + }, + "labels": { + "$ref": "#/definitions/labelSet" + }, + "receiver": { + "$ref": "#/definitions/receiver" + } + }, + "x-go-name": "AlertGroup", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "alertGroups": { + "description": "AlertGroups alert groups", + "type": "array", + "items": { + "$ref": "#/definitions/alertGroup" + }, + "x-go-name": "AlertGroups", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "alertStatus": { + "description": "AlertStatus alert status", + "type": "object", + "required": ["inhibitedBy", "silencedBy", "state"], + "properties": { + "inhibitedBy": { + "description": "inhibited by", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "InhibitedBy" + }, + "silencedBy": { + "description": "silenced by", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "SilencedBy" + }, + "state": { + "description": "state", + "type": "string", + "enum": ["[unprocessed active suppressed]"], + "x-go-name": "State" + } + }, + "x-go-name": "AlertStatus", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "alertmanagerConfig": { + "description": "AlertmanagerConfig alertmanager config", + "type": "object", + "required": ["original"], + "properties": { + "original": { + "description": "original", + "type": "string", + "x-go-name": "Original" + } + }, + "x-go-name": "AlertmanagerConfig", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "alertmanagerStatus": { + "description": "AlertmanagerStatus alertmanager status", + "type": "object", + "required": ["cluster", "config", "uptime", "versionInfo"], + "properties": { + "cluster": { + "$ref": "#/definitions/clusterStatus" + }, + "config": { + "$ref": "#/definitions/alertmanagerConfig" + }, + "uptime": { + "description": "uptime", + "type": "string", + "format": "date-time", + "x-go-name": "Uptime" + }, + "versionInfo": { + "$ref": "#/definitions/versionInfo" + } + }, + "x-go-name": "AlertmanagerStatus", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "clusterStatus": { + "description": "ClusterStatus cluster status", + "type": "object", + "required": ["status"], + "properties": { + "name": { + "description": "name", + "type": "string", + "x-go-name": "Name" + }, + "peers": { + "description": "peers", + "type": "array", + "items": { + "$ref": "#/definitions/peerStatus" + }, + "x-go-name": "Peers" + }, + "status": { + "description": "status", + "type": "string", + "enum": ["[ready settling disabled]"], + "x-go-name": "Status" + } + }, + "x-go-name": "ClusterStatus", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "gettableAlert": { + "description": "GettableAlert gettable alert", + "type": "object", + "required": [ + "labels", + "annotations", + "endsAt", + "fingerprint", + "receivers", + "startsAt", + "status", + "updatedAt" + ], + "properties": { + "annotations": { + "$ref": "#/definitions/labelSet" + }, + "endsAt": { + "description": "ends at", + "type": "string", + "format": "date-time", + "x-go-name": "EndsAt" + }, + "fingerprint": { + "description": "fingerprint", + "type": "string", + "x-go-name": "Fingerprint" + }, + "generatorURL": { + "description": "generator URL\nFormat: uri", + "type": "string", + "format": "uri", + "x-go-name": "GeneratorURL" + }, + "labels": { + "$ref": "#/definitions/labelSet" + }, + "receivers": { + "description": "receivers", + "type": "array", + "items": { + "$ref": "#/definitions/receiver" + }, + "x-go-name": "Receivers" + }, + "startsAt": { + "description": "starts at", + "type": "string", + "format": "date-time", + "x-go-name": "StartsAt" + }, + "status": { + "$ref": "#/definitions/alertStatus" + }, + "updatedAt": { + "description": "updated at", + "type": "string", + "format": "date-time", + "x-go-name": "UpdatedAt" + } + }, + "x-go-name": "GettableAlert", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "gettableAlerts": { + "description": "GettableAlerts gettable alerts", + "type": "array", + "items": { + "$ref": "#/definitions/gettableAlert" + }, + "x-go-name": "GettableAlerts", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "gettableSilence": { + "description": "GettableSilence gettable silence", + "type": "object", + "required": [ + "comment", + "createdBy", + "endsAt", + "matchers", + "startsAt", + "id", + "status", + "updatedAt" + ], + "properties": { + "comment": { + "description": "comment", + "type": "string", + "x-go-name": "Comment" + }, + "createdBy": { + "description": "created by", + "type": "string", + "x-go-name": "CreatedBy" + }, + "endsAt": { + "description": "ends at", + "type": "string", + "format": "date-time", + "x-go-name": "EndsAt" + }, + "id": { + "description": "id", + "type": "string", + "x-go-name": "ID" + }, + "matchers": { + "$ref": "#/definitions/matchers" + }, + "startsAt": { + "description": "starts at", + "type": "string", + "format": "date-time", + "x-go-name": "StartsAt" + }, + "status": { + "$ref": "#/definitions/silenceStatus" + }, + "updatedAt": { + "description": "updated at", + "type": "string", + "format": "date-time", + "x-go-name": "UpdatedAt" + } + }, + "x-go-name": "GettableSilence", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "gettableSilences": { + "description": "GettableSilences gettable silences", + "type": "array", + "items": { + "$ref": "#/definitions/gettableSilence" + }, + "x-go-name": "GettableSilences", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "labelSet": { + "description": "LabelSet label set", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "x-go-name": "LabelSet", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "matcher": { + "description": "Matcher matcher", + "type": "object", + "required": ["isRegex", "name", "value"], + "properties": { + "isRegex": { + "description": "is regex", + "type": "boolean", + "x-go-name": "IsRegex" + }, + "name": { + "description": "name", + "type": "string", + "x-go-name": "Name" + }, + "value": { + "description": "value", + "type": "string", + "x-go-name": "Value" + } + }, + "x-go-name": "Matcher", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "matchers": { + "description": "Matchers matchers", + "type": "array", + "items": { + "$ref": "#/definitions/matcher" + }, + "x-go-name": "Matchers", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "peerStatus": { + "description": "PeerStatus peer status", + "type": "object", + "required": ["address", "name"], + "properties": { + "address": { + "description": "address", + "type": "string", + "x-go-name": "Address" + }, + "name": { + "description": "name", + "type": "string", + "x-go-name": "Name" + } + }, + "x-go-name": "PeerStatus", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "postableAlert": { + "description": "PostableAlert postable alert", + "type": "object", + "required": ["labels"], + "properties": { + "annotations": { + "$ref": "#/definitions/labelSet" + }, + "endsAt": { + "description": "ends at\nFormat: date-time", + "type": "string", + "format": "date-time", + "x-go-name": "EndsAt" + }, + "generatorURL": { + "description": "generator URL\nFormat: uri", + "type": "string", + "format": "uri", + "x-go-name": "GeneratorURL" + }, + "labels": { + "$ref": "#/definitions/labelSet" + }, + "startsAt": { + "description": "starts at\nFormat: date-time", + "type": "string", + "format": "date-time", + "x-go-name": "StartsAt" + } + }, + "x-go-name": "PostableAlert", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "postableAlerts": { + "description": "PostableAlerts postable alerts", + "type": "array", + "items": { + "$ref": "#/definitions/postableAlert" + }, + "x-go-name": "PostableAlerts", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "postableSilence": { + "description": "PostableSilence postable silence", + "type": "object", + "required": ["comment", "createdBy", "endsAt", "matchers", "startsAt"], + "properties": { + "comment": { + "description": "comment", + "type": "string", + "x-go-name": "Comment" + }, + "createdBy": { + "description": "created by", + "type": "string", + "x-go-name": "CreatedBy" + }, + "endsAt": { + "description": "ends at", + "type": "string", + "format": "date-time", + "x-go-name": "EndsAt" + }, + "id": { + "description": "id", + "type": "string", + "x-go-name": "ID" + }, + "matchers": { + "$ref": "#/definitions/matchers" + }, + "startsAt": { + "description": "starts at", + "type": "string", + "format": "date-time", + "x-go-name": "StartsAt" + } + }, + "x-go-name": "PostableSilence", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "receiver": { + "description": "Receiver receiver", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "name", + "type": "string", + "x-go-name": "Name" + } + }, + "x-go-name": "Receiver", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "silence": { + "description": "Silence silence", + "type": "object", + "required": ["comment", "createdBy", "endsAt", "matchers", "startsAt"], + "properties": { + "comment": { + "description": "comment", + "type": "string", + "x-go-name": "Comment" + }, + "createdBy": { + "description": "created by", + "type": "string", + "x-go-name": "CreatedBy" + }, + "endsAt": { + "description": "ends at", + "type": "string", + "format": "date-time", + "x-go-name": "EndsAt" + }, + "matchers": { + "$ref": "#/definitions/matchers" + }, + "startsAt": { + "description": "starts at", + "type": "string", + "format": "date-time", + "x-go-name": "StartsAt" + } + }, + "x-go-name": "Silence", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "silenceStatus": { + "description": "SilenceStatus silence status", + "type": "object", + "required": ["state"], + "properties": { + "state": { + "description": "state", + "type": "string", + "enum": ["[expired active pending]"], + "x-go-name": "State" + } + }, + "x-go-name": "SilenceStatus", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + }, + "swaggerHttpResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "x-go-name": "Message" + } + }, + "x-go-package": "github.com/edgexr/edge-cloud-platform/doc/swagger" + }, + "versionInfo": { + "description": "VersionInfo version info", + "type": "object", + "required": [ + "branch", + "buildDate", + "buildUser", + "goVersion", + "revision", + "version" + ], + "properties": { + "branch": { + "description": "branch", + "type": "string", + "x-go-name": "Branch" + }, + "buildDate": { + "description": "build date", + "type": "string", + "x-go-name": "BuildDate" + }, + "buildUser": { + "description": "build user", + "type": "string", + "x-go-name": "BuildUser" + }, + "goVersion": { + "description": "go version", + "type": "string", + "x-go-name": "GoVersion" + }, + "revision": { + "description": "revision", + "type": "string", + "x-go-name": "Revision" + }, + "version": { + "description": "version", + "type": "string", + "x-go-name": "Version" + } + }, + "x-go-name": "VersionInfo", + "x-go-package": "github.com/edgexr/edge-cloud-platform/pkg/mc/orm/alertmgr/prometheus_structs/models" + } + }, + "responses": { + "authToken": { + "description": "Authentication Token", + "schema": { + "$ref": "#/definitions/Token" + } + }, + "badRequest": { + "description": "Status Bad Request", + "schema": { + "$ref": "#/definitions/Result" + } + }, + "forbidden": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/Result" + } + }, + "listBillingOrgs": { + "description": "List of BillingOrgs", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/BillingOrganization" + } + } + }, + "listOrgs": { + "description": "List of Orgs", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Organization" + } + } + }, + "listPerms": { + "description": "List of Permissions", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/RolePerm" + } + } + }, + "listRoles": { + "description": "List of Roles", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Role" + } + } + }, + "listUsers": { + "description": "List of Users", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + }, + "loginBadRequest": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/swaggerHttpResponse" + } + }, + "notFound": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/Result" + } + }, + "success": { + "description": "Success", + "schema": { + "$ref": "#/definitions/swaggerHttpResponse" + } + } + }, + "securityDefinitions": { + "Bearer": { + "description": "Use [login API](#operation/Login) to generate bearer token (JWT) for authorization. Usage format - `Bearer \u003cJWT\u003e`", + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + }, + "tags": [ + { + "description": "Authentication is done by a user name or email plus a password. The login function returns a JSON Web Token (JWT) once authenticated, that should be included with subsequent API calls to authenticate the user. The JWT will expire after a while for security, at which point you may need to log in again.", + "name": "Security" + }, + { + "description": "Users can be assigned roles for Organizations, allowing them to view or manage resources associated with the Organizations.", + "name": "User" + }, + { + "description": "Roles can be assigned to users for Organizations, allowing the users to view or manage resources associated with the Organizations.", + "name": "Role" + }, + { + "description": "Organizations group a set of resources together, for example Apps, App Instances, or Cloudlets. Users given a role in an Organization can operate on those resources in the scope provided by their role.", + "name": "Organization" + }, + { + "description": "OperatorCode maps a carrier code to an Operator organization name.", + "name": "OperatorCode" + }, + { + "description": "Flavors define the compute, memory and storage capacity of computing instances. To put it simply, a flavor is an available hardware configuration for a server. It defines the size of a virtual server that can be launched.", + "name": "Flavor" + }, + { + "description": "AutoProvPolicy defines the automated provisioning policy.", + "name": "AutoProvPolicy" + }, + { + "description": "AutoProvPolicy belonging to an app.", + "name": "AppAutoProvPolicy" + }, + { + "description": "AutoScalePolicy defines when and how ClusterInsts will have their nodes scaled up or down.", + "name": "AutoScalePolicy" + }, + { + "description": "PrivacyPolicy defines security restrictions for cluster instances nodes scaled up or down.", + "name": "PrivacyPolicy" + }, + { + "description": "AutoProvPolicyCloudlet belong to a cloudlet.", + "name": "AutoProvPolicyCloudlet" + }, + { + "description": "Pool of VMs to be part of a Cloudlet.", + "name": "VMPool" + }, + { + "description": "Members belong to a VMPool.", + "name": "VMPoolMember" + }, + { + "description": "Cloudlet is a set of compute resources at a particular location, provided by an Operator.", + "name": "Cloudlet" + }, + { + "description": "CloudletPool defines a pool of Cloudlets that have restricted access.", + "name": "CloudletPool" + }, + { + "description": "Member belong to a cloudlet pool.", + "name": "CloudletPoolMember" + }, + { + "description": "ClusterInst is an instance of a Cluster on a Cloudlet. It is defined by a Cluster, Cloudlet, and Developer key.", + "name": "ClusterInst" + }, + { + "description": "Provides information about the developer's application.", + "name": "App" + }, + { + "description": "AppInst is an instance of an App on a Cloudlet where it is defined by an App plus a ClusterInst key.", + "name": "AppInst" + }, + { + "description": "Infra properties used to setup cloudlet.", + "name": "CloudletProps" + }, + { + "description": "Cloudlet resouce mapping.", + "name": "CloudletResMap" + }, + { + "description": "To match a flavor with platform flavor.", + "name": "FlavorMatch" + }, + { + "description": "Client is an AppInst client that called FindCloudlet DME Api.", + "name": "AppInstClientKey" + }, + { + "description": "ExecRequest is a common struct for enabling a connection to execute some work on a container.", + "name": "ExecRequest" + }, + { + "description": "Collection of statistics related to Client/App/Cluster.", + "name": "DeveloperMetrics" + }, + { + "description": "Collection of statistics related to Cloudlet.", + "name": "OperatorMetrics" + }, + { + "description": "Collection of event/audit logs from edge services.", + "name": "Events" + }, + { + "description": "Usage details of App/Cluster.", + "name": "DeveloperUsage" + }, + { + "description": "Usage details of Cloudlet.", + "name": "OperatorUsage" + }, + { + "description": "Manage receiver for alerts from edge services.", + "name": "AlertReceiver" + }, + { + "description": "Manage additional networks which can be added to Cluster Instances.", + "name": "Network" + } + ], + "x-tagGroups": [ + { + "name": "Auth \u0026 User Management API", + "tags": ["Security", "User", "Role", "Organization"] + }, + { + "name": "Operator API", + "tags": [ + "Cloudlet", + "OperatorCode", + "Flavor", + "CloudletProps", + "CloudletResMap", + "FlavorMatch", + "CloudletPool", + "CloudletPoolMember", + "VMPool", + "VMPoolMember", + "OperatorMetrics", + "Events", + "OperatorUsage", + "AlertReceiver", + "Network" + ] + }, + { + "name": "Developer API", + "tags": [ + "ClusterInst", + "App", + "AppInst", + "AutoProvPolicy", + "AppAutoProvPolicy", + "AutoScalePolicy", + "PrivacyPolicy", + "AutoProvPolicyCloudlet", + "AppInstClientKey", + "ExecRequest", + "DeveloperMetrics", + "Events", + "DeveloperUsage", + "AlertReceiver" + ] + } + ] +} diff --git a/api/swagger.json b/api/swagger_v2.json similarity index 100% rename from api/swagger.json rename to api/swagger_v2.json From 1413836b6808b1da4c2d2814ec28a484821d3290 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 20 Oct 2025 13:05:36 +0200 Subject: [PATCH 20/40] feat(swagger_v2): added support for the orca staging environment --- cmd/app.go | 20 ++++-- cmd/root.go | 2 + internal/config/types.go | 69 ++++++++++++++++++- sdk/edgeconnect/appinstance.go | 29 ++++++-- sdk/edgeconnect/apps.go | 32 ++++++--- sdk/edgeconnect/types.go | 40 +++++++++-- .../comprehensive/EdgeConnectConfig.yaml | 12 ++-- .../comprehensive/k8s-deployment.yaml | 2 +- sdk/internal/http/transport.go | 19 +++-- 9 files changed, 186 insertions(+), 39 deletions(-) diff --git a/cmd/app.go b/cmd/app.go index a9f187f..0273896 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -3,6 +3,7 @@ package cmd import ( "context" "fmt" + "log" "net/http" "net/url" "os" @@ -60,16 +61,23 @@ func newSDKClient() *edgeconnect.Client { os.Exit(1) } + // Build options + opts := []edgeconnect.Option{ + edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), + } + + // Add logger only if debug flag is set + if debug { + logger := log.New(os.Stderr, "[DEBUG] ", log.LstdFlags) + opts = append(opts, edgeconnect.WithLogger(logger)) + } + if username != "" && password != "" { - return edgeconnect.NewClientWithCredentials(baseURL, username, password, - edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), - ) + return edgeconnect.NewClientWithCredentials(baseURL, username, password, opts...) } // Fallback to no auth for now - in production should require auth - return edgeconnect.NewClient(baseURL, - edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), - ) + return edgeconnect.NewClient(baseURL, opts...) } var appCmd = &cobra.Command{ diff --git a/cmd/root.go b/cmd/root.go index 480d8f5..6fa2dd6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,6 +13,7 @@ var ( baseURL string username string password string + debug bool ) // rootCmd represents the base command when called without any subcommands @@ -39,6 +40,7 @@ func init() { rootCmd.PersistentFlags().StringVar(&baseURL, "base-url", "", "base URL for the Edge Connect API") rootCmd.PersistentFlags().StringVar(&username, "username", "", "username for authentication") rootCmd.PersistentFlags().StringVar(&password, "password", "", "password for authentication") + rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "enable debug logging") viper.BindPFlag("base_url", rootCmd.PersistentFlags().Lookup("base-url")) viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username")) diff --git a/internal/config/types.go b/internal/config/types.go index 9b365dd..60128d4 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -7,6 +7,8 @@ import ( "os" "path/filepath" "strings" + + "gopkg.in/yaml.v3" ) // EdgeConnectConfig represents the top-level configuration structure @@ -98,10 +100,75 @@ func (c *EdgeConnectConfig) GetImagePath() string { if c.Spec.IsDockerApp() && c.Spec.DockerApp.Image != "" { return c.Spec.DockerApp.Image } - // Default for kubernetes apps + + // For kubernetes apps, extract image from manifest + if c.Spec.IsK8sApp() && c.Spec.K8sApp.ManifestFile != "" { + if image, err := extractImageFromK8sManifest(c.Spec.K8sApp.ManifestFile); err == nil && image != "" { + return image + } + } + + // Fallback default for kubernetes apps return "https://registry-1.docker.io/library/nginx:latest" } +// extractImageFromK8sManifest extracts the container image from a Kubernetes manifest +func extractImageFromK8sManifest(manifestPath string) (string, error) { + data, err := os.ReadFile(manifestPath) + if err != nil { + return "", fmt.Errorf("failed to read manifest: %w", err) + } + + // Parse multi-document YAML + decoder := yaml.NewDecoder(strings.NewReader(string(data))) + + for { + var doc map[string]interface{} + if err := decoder.Decode(&doc); err != nil { + break // End of documents or error + } + + // Check if this is a Deployment + kind, ok := doc["kind"].(string) + if !ok || kind != "Deployment" { + continue + } + + // Navigate to spec.template.spec.containers[0].image + spec, ok := doc["spec"].(map[string]interface{}) + if !ok { + continue + } + + template, ok := spec["template"].(map[string]interface{}) + if !ok { + continue + } + + templateSpec, ok := template["spec"].(map[string]interface{}) + if !ok { + continue + } + + containers, ok := templateSpec["containers"].([]interface{}) + if !ok || len(containers) == 0 { + continue + } + + firstContainer, ok := containers[0].(map[string]interface{}) + if !ok { + continue + } + + image, ok := firstContainer["image"].(string) + if ok && image != "" { + return image, nil + } + } + + return "", fmt.Errorf("no image found in Deployment manifest") +} + // Validate validates metadata fields func (m *Metadata) Validate() error { if m.Name == "" { diff --git a/sdk/edgeconnect/appinstance.go b/sdk/edgeconnect/appinstance.go index a26f45c..ec4751a 100644 --- a/sdk/edgeconnect/appinstance.go +++ b/sdk/edgeconnect/appinstance.go @@ -4,9 +4,11 @@ package edgeconnect import ( + "bytes" "context" "encoding/json" "fmt" + "io" "net/http" sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/internal/http" @@ -164,18 +166,17 @@ func (c *Client) RefreshAppInstance(ctx context.Context, appInstKey AppInstanceK return nil } -// DeleteAppInstance removes an application instance from the specified region +// DeleteAppInstance removes an application instance // 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, + input := DeleteAppInstanceInput{ + Key: appInstKey, } - resp, err := transport.Call(ctx, "POST", url, filter) + resp, err := transport.Call(ctx, "POST", url, input) if err != nil { return fmt.Errorf("DeleteAppInstance failed: %w", err) } @@ -194,13 +195,29 @@ func (c *Client) DeleteAppInstance(ctx context.Context, appInstKey AppInstanceKe // parseStreamingAppInstanceResponse parses the EdgeXR streaming JSON response format for app instances func (c *Client) parseStreamingAppInstanceResponse(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 *[]AppInstance: + var appInstances []AppInstance + if err := json.Unmarshal(bodyBytes, &appInstances); err == nil { + *v = appInstances + return nil + } + } + + // Fall back to streaming format (v1 API format) var appInstances []AppInstance var messages []string var hasError bool var errorCode int var errorMessage string - parseErr := sdkhttp.ParseJSONLines(resp.Body, func(line []byte) error { + 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 != "" { diff --git a/sdk/edgeconnect/apps.go b/sdk/edgeconnect/apps.go index 70f5dea..7010070 100644 --- a/sdk/edgeconnect/apps.go +++ b/sdk/edgeconnect/apps.go @@ -4,6 +4,7 @@ package edgeconnect import ( + "bytes" "context" "encoding/json" "fmt" @@ -142,12 +143,12 @@ func (c *Client) DeleteApp(ctx context.Context, appKey AppKey, region string) er transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/DeleteApp" - filter := AppFilter{ - App: App{Key: appKey}, + input := DeleteAppInput{ + Key: appKey, Region: region, } - resp, err := transport.Call(ctx, "POST", url, filter) + resp, err := transport.Call(ctx, "POST", url, input) if err != nil { return fmt.Errorf("DeleteApp failed: %w", err) } @@ -166,9 +167,27 @@ func (c *Client) DeleteApp(ctx context.Context, appKey AppKey, region string) er // parseStreamingResponse parses the EdgeXR streaming JSON response format func (c *Client) parseStreamingResponse(resp *http.Response, result interface{}) error { - var responses []Response[App] + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } - parseErr := sdkhttp.ParseJSONLines(resp.Body, func(line []byte) error { + // 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 @@ -182,9 +201,6 @@ func (c *Client) parseStreamingResponse(resp *http.Response, result interface{}) } // Extract data from responses - var apps []App - var messages []string - for _, response := range responses { if response.HasData() { apps = append(apps, response.Data) diff --git a/sdk/edgeconnect/types.go b/sdk/edgeconnect/types.go index 7fd39fc..ffd5550 100644 --- a/sdk/edgeconnect/types.go +++ b/sdk/edgeconnect/types.go @@ -184,24 +184,33 @@ type App struct { Deployment string `json:"deployment,omitempty"` ImageType string `json:"image_type,omitempty"` ImagePath string `json:"image_path,omitempty"` + AccessPorts string `json:"access_ports,omitempty"` AllowServerless bool `json:"allow_serverless,omitempty"` DefaultFlavor Flavor `json:"defaultFlavor,omitempty"` ServerlessConfig interface{} `json:"serverless_config,omitempty"` DeploymentGenerator string `json:"deployment_generator,omitempty"` DeploymentManifest string `json:"deployment_manifest,omitempty"` RequiredOutboundConnections []SecurityRule `json:"required_outbound_connections"` + GlobalID string `json:"global_id,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` Fields []string `json:"fields,omitempty"` } // AppInstance represents a deployed application instance type AppInstance struct { - msg `json:",inline"` - Key AppInstanceKey `json:"key"` - AppKey AppKey `json:"app_key,omitempty"` - Flavor Flavor `json:"flavor,omitempty"` - State string `json:"state,omitempty"` - PowerState string `json:"power_state,omitempty"` - Fields []string `json:"fields,omitempty"` + msg `json:",inline"` + Key AppInstanceKey `json:"key"` + AppKey AppKey `json:"app_key,omitempty"` + CloudletLoc CloudletLoc `json:"cloudlet_loc,omitempty"` + Flavor Flavor `json:"flavor,omitempty"` + State string `json:"state,omitempty"` + IngressURL string `json:"ingress_url,omitempty"` + UniqueID string `json:"unique_id,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + PowerState string `json:"power_state,omitempty"` + Fields []string `json:"fields,omitempty"` } // Cloudlet represents edge infrastructure @@ -224,6 +233,12 @@ type Location struct { Longitude float64 `json:"longitude"` } +// CloudletLoc represents geographical coordinates for cloudlets +type CloudletLoc struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` +} + // Input types for API operations // NewAppInput represents input for creating an application @@ -256,6 +271,17 @@ type UpdateAppInstanceInput struct { AppInst AppInstance `json:"appinst"` } +// DeleteAppInput represents input for deleting an application +type DeleteAppInput struct { + Key AppKey `json:"key"` + Region string `json:"region"` +} + +// DeleteAppInstanceInput represents input for deleting an app instance +type DeleteAppInstanceInput struct { + Key AppInstanceKey `json:"key"` +} + // Response wrapper types // Response wraps a single API response diff --git a/sdk/examples/comprehensive/EdgeConnectConfig.yaml b/sdk/examples/comprehensive/EdgeConnectConfig.yaml index b45abc4..af41eaf 100644 --- a/sdk/examples/comprehensive/EdgeConnectConfig.yaml +++ b/sdk/examples/comprehensive/EdgeConnectConfig.yaml @@ -3,8 +3,8 @@ kind: edgeconnect-deployment metadata: name: "edge-app-demo" # name could be used for appName - appVersion: "1.0.0" - organization: "edp2" + appVersion: "1" + organization: "edp2-orca" spec: # dockerApp: # Docker is OBSOLETE # appVersion: "1.0.0" @@ -13,10 +13,10 @@ spec: k8sApp: manifestFile: "./k8s-deployment.yaml" infraTemplate: - - region: "EU" - cloudletOrg: "TelekomOP" - cloudletName: "Munich" - flavorName: "EU.small" + - region: "US" + cloudletOrg: "TelekomOp" + cloudletName: "gardener-shepherd-test" + flavorName: "defualt" network: outboundConnections: - protocol: "tcp" diff --git a/sdk/examples/comprehensive/k8s-deployment.yaml b/sdk/examples/comprehensive/k8s-deployment.yaml index 348b6f8..2a0a741 100644 --- a/sdk/examples/comprehensive/k8s-deployment.yaml +++ b/sdk/examples/comprehensive/k8s-deployment.yaml @@ -32,7 +32,7 @@ spec: volumes: containers: - name: edgeconnect-coder - image: nginx:latest + image: edp.buildth.ing/devfw-cicd/fibonacci_pipeline:edge_platform_demo imagePullPolicy: Always ports: - containerPort: 80 diff --git a/sdk/internal/http/transport.go b/sdk/internal/http/transport.go index 54e853c..c3bbab1 100644 --- a/sdk/internal/http/transport.go +++ b/sdk/internal/http/transport.go @@ -98,10 +98,12 @@ func NewTransport(opts RetryOptions, auth AuthProvider, logger Logger) *Transpor // Call executes an HTTP request with retry logic and returns typed response func (t *Transport) Call(ctx context.Context, method, url string, body interface{}) (*http.Response, error) { var reqBody io.Reader + var jsonData []byte // Marshal request body if provided if body != nil { - jsonData, err := json.Marshal(body) + var err error + jsonData, err = json.Marshal(body) if err != nil { return nil, fmt.Errorf("failed to marshal request body: %w", err) } @@ -127,8 +129,16 @@ func (t *Transport) Call(ctx context.Context, method, url string, body interface // Log request if t.logger != nil { - t.logger.Printf("HTTP %s %s", method, url) - t.logger.Printf("BODY %s", reqBody) + t.logger.Printf("=== HTTP REQUEST ===") + t.logger.Printf("%s %s", method, url) + if len(jsonData) > 0 { + var prettyJSON bytes.Buffer + if err := json.Indent(&prettyJSON, jsonData, "", " "); err == nil { + t.logger.Printf("Request Body:\n%s", prettyJSON.String()) + } else { + t.logger.Printf("Request Body: %s", string(jsonData)) + } + } } // Execute request @@ -139,7 +149,8 @@ func (t *Transport) Call(ctx context.Context, method, url string, body interface // Log response if t.logger != nil { - t.logger.Printf("HTTP %s %s -> %d", method, url, resp.StatusCode) + t.logger.Printf("=== HTTP RESPONSE ===") + t.logger.Printf("%s %s -> %d", method, url, resp.StatusCode) } return resp, nil From 3486b2228d1acacc02018e463b6aacb121c9bdd0 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 20 Oct 2025 13:34:22 +0200 Subject: [PATCH 21/40] refactor(sdk): restructure to follow Go module versioning conventions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorganize SDK to support both v1 and v2 APIs following Go conventions: - sdk/edgeconnect/ now contains v1 SDK (from revision/v1 branch) - sdk/edgeconnect/v2/ contains v2 SDK with package v2 - Update all CLI and internal imports to use v2 path - Update SDK examples and documentation for v2 import path 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- cmd/app.go | 26 +- cmd/instance.go | 26 +- internal/apply/manager.go | 8 +- internal/apply/manager_test.go | 42 +- internal/apply/planner.go | 24 +- internal/apply/planner_test.go | 86 ++-- internal/apply/strategy_recreate.go | 30 +- internal/apply/types.go | 10 +- sdk/README.md | 56 +-- sdk/edgeconnect/appinstance.go | 29 +- sdk/edgeconnect/apps.go | 30 +- sdk/edgeconnect/types.go | 40 +- sdk/edgeconnect/v2/appinstance.go | 281 +++++++++++++ sdk/edgeconnect/v2/appinstance_test.go | 524 +++++++++++++++++++++++++ sdk/edgeconnect/v2/apps.go | 267 +++++++++++++ sdk/edgeconnect/v2/apps_test.go | 419 ++++++++++++++++++++ sdk/edgeconnect/v2/auth.go | 184 +++++++++ sdk/edgeconnect/v2/auth_test.go | 226 +++++++++++ sdk/edgeconnect/v2/client.go | 122 ++++++ sdk/edgeconnect/v2/cloudlet.go | 271 +++++++++++++ sdk/edgeconnect/v2/cloudlet_test.go | 408 +++++++++++++++++++ sdk/edgeconnect/v2/types.go | 407 +++++++++++++++++++ sdk/examples/comprehensive/main.go | 58 +-- sdk/examples/deploy_app.go | 32 +- 24 files changed, 3328 insertions(+), 278 deletions(-) create mode 100644 sdk/edgeconnect/v2/appinstance.go create mode 100644 sdk/edgeconnect/v2/appinstance_test.go create mode 100644 sdk/edgeconnect/v2/apps.go create mode 100644 sdk/edgeconnect/v2/apps_test.go create mode 100644 sdk/edgeconnect/v2/auth.go create mode 100644 sdk/edgeconnect/v2/auth_test.go create mode 100644 sdk/edgeconnect/v2/client.go create mode 100644 sdk/edgeconnect/v2/cloudlet.go create mode 100644 sdk/edgeconnect/v2/cloudlet_test.go create mode 100644 sdk/edgeconnect/v2/types.go diff --git a/cmd/app.go b/cmd/app.go index 0273896..a96f599 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -9,7 +9,7 @@ import ( "os" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -50,7 +50,7 @@ func validateBaseURL(baseURL string) error { return nil } -func newSDKClient() *edgeconnect.Client { +func newSDKClient() *v2.Client { baseURL := viper.GetString("base_url") username := viper.GetString("username") password := viper.GetString("password") @@ -62,22 +62,22 @@ func newSDKClient() *edgeconnect.Client { } // Build options - opts := []edgeconnect.Option{ - edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), + opts := []v2.Option{ + v2.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), } // Add logger only if debug flag is set if debug { logger := log.New(os.Stderr, "[DEBUG] ", log.LstdFlags) - opts = append(opts, edgeconnect.WithLogger(logger)) + opts = append(opts, v2.WithLogger(logger)) } if username != "" && password != "" { - return edgeconnect.NewClientWithCredentials(baseURL, username, password, opts...) + return v2.NewClientWithCredentials(baseURL, username, password, opts...) } // Fallback to no auth for now - in production should require auth - return edgeconnect.NewClient(baseURL, opts...) + return v2.NewClient(baseURL, opts...) } var appCmd = &cobra.Command{ @@ -91,10 +91,10 @@ var createAppCmd = &cobra.Command{ Short: "Create a new Edge Connect application", Run: func(cmd *cobra.Command, args []string) { c := newSDKClient() - input := &edgeconnect.NewAppInput{ + input := &v2.NewAppInput{ Region: region, - App: edgeconnect.App{ - Key: edgeconnect.AppKey{ + App: v2.App{ + Key: v2.AppKey{ Organization: organization, Name: appName, Version: appVersion, @@ -116,7 +116,7 @@ var showAppCmd = &cobra.Command{ Short: "Show details of an Edge Connect application", Run: func(cmd *cobra.Command, args []string) { c := newSDKClient() - appKey := edgeconnect.AppKey{ + appKey := v2.AppKey{ Organization: organization, Name: appName, Version: appVersion, @@ -136,7 +136,7 @@ var listAppsCmd = &cobra.Command{ Short: "List Edge Connect applications", Run: func(cmd *cobra.Command, args []string) { c := newSDKClient() - appKey := edgeconnect.AppKey{ + appKey := v2.AppKey{ Organization: organization, Name: appName, Version: appVersion, @@ -159,7 +159,7 @@ var deleteAppCmd = &cobra.Command{ Short: "Delete an Edge Connect application", Run: func(cmd *cobra.Command, args []string) { c := newSDKClient() - appKey := edgeconnect.AppKey{ + appKey := v2.AppKey{ Organization: organization, Name: appName, Version: appVersion, diff --git a/cmd/instance.go b/cmd/instance.go index de22062..30194ab 100644 --- a/cmd/instance.go +++ b/cmd/instance.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" "github.com/spf13/cobra" ) @@ -27,23 +27,23 @@ var createInstanceCmd = &cobra.Command{ Short: "Create a new Edge Connect application instance", Run: func(cmd *cobra.Command, args []string) { c := newSDKClient() - input := &edgeconnect.NewAppInstanceInput{ + input := &v2.NewAppInstanceInput{ Region: region, - AppInst: edgeconnect.AppInstance{ - Key: edgeconnect.AppInstanceKey{ + AppInst: v2.AppInstance{ + Key: v2.AppInstanceKey{ Organization: organization, Name: instanceName, - CloudletKey: edgeconnect.CloudletKey{ + CloudletKey: v2.CloudletKey{ Organization: cloudletOrg, Name: cloudletName, }, }, - AppKey: edgeconnect.AppKey{ + AppKey: v2.AppKey{ Organization: organization, Name: appName, Version: appVersion, }, - Flavor: edgeconnect.Flavor{ + Flavor: v2.Flavor{ Name: flavorName, }, }, @@ -63,10 +63,10 @@ var showInstanceCmd = &cobra.Command{ Short: "Show details of an Edge Connect application instance", Run: func(cmd *cobra.Command, args []string) { c := newSDKClient() - instanceKey := edgeconnect.AppInstanceKey{ + instanceKey := v2.AppInstanceKey{ Organization: organization, Name: instanceName, - CloudletKey: edgeconnect.CloudletKey{ + CloudletKey: v2.CloudletKey{ Organization: cloudletOrg, Name: cloudletName, }, @@ -86,10 +86,10 @@ var listInstancesCmd = &cobra.Command{ Short: "List Edge Connect application instances", Run: func(cmd *cobra.Command, args []string) { c := newSDKClient() - instanceKey := edgeconnect.AppInstanceKey{ + instanceKey := v2.AppInstanceKey{ Organization: organization, Name: instanceName, - CloudletKey: edgeconnect.CloudletKey{ + CloudletKey: v2.CloudletKey{ Organization: cloudletOrg, Name: cloudletName, }, @@ -112,10 +112,10 @@ var deleteInstanceCmd = &cobra.Command{ Short: "Delete an Edge Connect application instance", Run: func(cmd *cobra.Command, args []string) { c := newSDKClient() - instanceKey := edgeconnect.AppInstanceKey{ + instanceKey := v2.AppInstanceKey{ Organization: organization, Name: instanceName, - CloudletKey: edgeconnect.CloudletKey{ + CloudletKey: v2.CloudletKey{ Organization: cloudletOrg, Name: cloudletName, }, diff --git a/internal/apply/manager.go b/internal/apply/manager.go index 45477ab..3e6d837 100644 --- a/internal/apply/manager.go +++ b/internal/apply/manager.go @@ -8,7 +8,7 @@ import ( "time" "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" ) // ResourceManagerInterface defines the interface for resource management @@ -250,7 +250,7 @@ func (rm *EdgeConnectResourceManager) rollbackCreateAction(ctx context.Context, // rollbackApp deletes an application that was created func (rm *EdgeConnectResourceManager) rollbackApp(ctx context.Context, action ActionResult, plan *DeploymentPlan) error { - appKey := edgeconnect.AppKey{ + appKey := v2.AppKey{ Organization: plan.AppAction.Desired.Organization, Name: plan.AppAction.Desired.Name, Version: plan.AppAction.Desired.Version, @@ -264,10 +264,10 @@ func (rm *EdgeConnectResourceManager) rollbackInstance(ctx context.Context, acti // Find the instance action to get the details for _, instanceAction := range plan.InstanceActions { if instanceAction.InstanceName == action.Target { - instanceKey := edgeconnect.AppInstanceKey{ + instanceKey := v2.AppInstanceKey{ Organization: plan.AppAction.Desired.Organization, Name: instanceAction.InstanceName, - CloudletKey: edgeconnect.CloudletKey{ + CloudletKey: v2.CloudletKey{ Organization: instanceAction.Target.CloudletOrg, Name: instanceAction.Target.CloudletName, }, diff --git a/internal/apply/manager_test.go b/internal/apply/manager_test.go index 6060a37..f2135b5 100644 --- a/internal/apply/manager_test.go +++ b/internal/apply/manager_test.go @@ -11,7 +11,7 @@ import ( "time" "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -22,32 +22,32 @@ type MockResourceClient struct { MockEdgeConnectClient } -func (m *MockResourceClient) CreateApp(ctx context.Context, input *edgeconnect.NewAppInput) error { +func (m *MockResourceClient) CreateApp(ctx context.Context, input *v2.NewAppInput) error { args := m.Called(ctx, input) return args.Error(0) } -func (m *MockResourceClient) CreateAppInstance(ctx context.Context, input *edgeconnect.NewAppInstanceInput) error { +func (m *MockResourceClient) CreateAppInstance(ctx context.Context, input *v2.NewAppInstanceInput) error { args := m.Called(ctx, input) return args.Error(0) } -func (m *MockResourceClient) DeleteApp(ctx context.Context, appKey edgeconnect.AppKey, region string) error { +func (m *MockResourceClient) DeleteApp(ctx context.Context, appKey v2.AppKey, region string) error { args := m.Called(ctx, appKey, region) return args.Error(0) } -func (m *MockResourceClient) UpdateApp(ctx context.Context, input *edgeconnect.UpdateAppInput) error { +func (m *MockResourceClient) UpdateApp(ctx context.Context, input *v2.UpdateAppInput) error { args := m.Called(ctx, input) return args.Error(0) } -func (m *MockResourceClient) UpdateAppInstance(ctx context.Context, input *edgeconnect.UpdateAppInstanceInput) error { +func (m *MockResourceClient) UpdateAppInstance(ctx context.Context, input *v2.UpdateAppInstanceInput) error { args := m.Called(ctx, input) return args.Error(0) } -func (m *MockResourceClient) DeleteAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) error { +func (m *MockResourceClient) DeleteAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) error { args := m.Called(ctx, instanceKey, region) return args.Error(0) } @@ -185,9 +185,9 @@ func TestApplyDeploymentSuccess(t *testing.T) { config := createTestManagerConfig(t) // Mock successful operations - mockClient.On("CreateApp", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInput")). + mockClient.On("CreateApp", mock.Anything, mock.AnythingOfType("*v2.NewAppInput")). Return(nil) - mockClient.On("CreateAppInstance", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInstanceInput")). + mockClient.On("CreateAppInstance", mock.Anything, mock.AnythingOfType("*v2.NewAppInstanceInput")). Return(nil) ctx := context.Background() @@ -216,8 +216,8 @@ func TestApplyDeploymentAppFailure(t *testing.T) { config := createTestManagerConfig(t) // Mock app creation failure - deployment should stop here - mockClient.On("CreateApp", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInput")). - Return(&edgeconnect.APIError{StatusCode: 500, Messages: []string{"Server error"}}) + mockClient.On("CreateApp", mock.Anything, mock.AnythingOfType("*v2.NewAppInput")). + Return(&v2.APIError{StatusCode: 500, Messages: []string{"Server error"}}) ctx := context.Background() result, err := manager.ApplyDeployment(ctx, plan, config, "test manifest content") @@ -241,13 +241,13 @@ func TestApplyDeploymentInstanceFailureWithRollback(t *testing.T) { config := createTestManagerConfig(t) // Mock successful app creation but failed instance creation - mockClient.On("CreateApp", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInput")). + mockClient.On("CreateApp", mock.Anything, mock.AnythingOfType("*v2.NewAppInput")). Return(nil) - mockClient.On("CreateAppInstance", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInstanceInput")). - Return(&edgeconnect.APIError{StatusCode: 500, Messages: []string{"Instance creation failed"}}) + mockClient.On("CreateAppInstance", mock.Anything, mock.AnythingOfType("*v2.NewAppInstanceInput")). + Return(&v2.APIError{StatusCode: 500, Messages: []string{"Instance creation failed"}}) // Mock rollback operations - mockClient.On("DeleteApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). + mockClient.On("DeleteApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US"). Return(nil) ctx := context.Background() @@ -333,9 +333,9 @@ func TestApplyDeploymentMultipleInstances(t *testing.T) { config := createTestManagerConfig(t) // Mock successful operations - mockClient.On("CreateApp", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInput")). + mockClient.On("CreateApp", mock.Anything, mock.AnythingOfType("*v2.NewAppInput")). Return(nil) - mockClient.On("CreateAppInstance", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInstanceInput")). + mockClient.On("CreateAppInstance", mock.Anything, mock.AnythingOfType("*v2.NewAppInstanceInput")). Return(nil) ctx := context.Background() @@ -421,9 +421,9 @@ func TestRollbackDeployment(t *testing.T) { } // Mock rollback operations - mockClient.On("DeleteAppInstance", mock.Anything, mock.AnythingOfType("edgeconnect.AppInstanceKey"), "US"). + mockClient.On("DeleteAppInstance", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "US"). Return(nil) - mockClient.On("DeleteApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). + mockClient.On("DeleteApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US"). Return(nil) ctx := context.Background() @@ -453,8 +453,8 @@ func TestRollbackDeploymentFailure(t *testing.T) { } // Mock rollback failure - mockClient.On("DeleteApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). - Return(&edgeconnect.APIError{StatusCode: 500, Messages: []string{"Delete failed"}}) + mockClient.On("DeleteApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US"). + Return(&v2.APIError{StatusCode: 500, Messages: []string{"Delete failed"}}) ctx := context.Background() err := manager.RollbackDeployment(ctx, result) diff --git a/internal/apply/planner.go b/internal/apply/planner.go index 1cbc58d..d4f3e82 100644 --- a/internal/apply/planner.go +++ b/internal/apply/planner.go @@ -12,19 +12,19 @@ import ( "time" "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" ) // EdgeConnectClientInterface defines the methods needed for deployment planning type EdgeConnectClientInterface interface { - ShowApp(ctx context.Context, appKey edgeconnect.AppKey, region string) (edgeconnect.App, error) - CreateApp(ctx context.Context, input *edgeconnect.NewAppInput) error - UpdateApp(ctx context.Context, input *edgeconnect.UpdateAppInput) error - DeleteApp(ctx context.Context, appKey edgeconnect.AppKey, region string) error - ShowAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) (edgeconnect.AppInstance, error) - CreateAppInstance(ctx context.Context, input *edgeconnect.NewAppInstanceInput) error - UpdateAppInstance(ctx context.Context, input *edgeconnect.UpdateAppInstanceInput) error - DeleteAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) error + ShowApp(ctx context.Context, appKey v2.AppKey, region string) (v2.App, error) + CreateApp(ctx context.Context, input *v2.NewAppInput) error + UpdateApp(ctx context.Context, input *v2.UpdateAppInput) error + DeleteApp(ctx context.Context, appKey v2.AppKey, region string) error + ShowAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) (v2.AppInstance, error) + CreateAppInstance(ctx context.Context, input *v2.NewAppInstanceInput) error + UpdateAppInstance(ctx context.Context, input *v2.UpdateAppInstanceInput) error + DeleteAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) error } // Planner defines the interface for deployment planning @@ -285,7 +285,7 @@ func (p *EdgeConnectPlanner) getCurrentAppState(ctx context.Context, desired *Ap timeoutCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - appKey := edgeconnect.AppKey{ + appKey := v2.AppKey{ Organization: desired.Organization, Name: desired.Name, Version: desired.Version, @@ -339,10 +339,10 @@ func (p *EdgeConnectPlanner) getCurrentInstanceState(ctx context.Context, desire timeoutCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - instanceKey := edgeconnect.AppInstanceKey{ + instanceKey := v2.AppInstanceKey{ Organization: desired.Organization, Name: desired.Name, - CloudletKey: edgeconnect.CloudletKey{ + CloudletKey: v2.CloudletKey{ Organization: desired.CloudletOrg, Name: desired.CloudletName, }, diff --git a/internal/apply/planner_test.go b/internal/apply/planner_test.go index d946a14..6f7c39b 100644 --- a/internal/apply/planner_test.go +++ b/internal/apply/planner_test.go @@ -10,7 +10,7 @@ import ( "time" "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -21,66 +21,66 @@ type MockEdgeConnectClient struct { mock.Mock } -func (m *MockEdgeConnectClient) ShowApp(ctx context.Context, appKey edgeconnect.AppKey, region string) (edgeconnect.App, error) { +func (m *MockEdgeConnectClient) ShowApp(ctx context.Context, appKey v2.AppKey, region string) (v2.App, error) { args := m.Called(ctx, appKey, region) if args.Get(0) == nil { - return edgeconnect.App{}, args.Error(1) + return v2.App{}, args.Error(1) } - return args.Get(0).(edgeconnect.App), args.Error(1) + return args.Get(0).(v2.App), args.Error(1) } -func (m *MockEdgeConnectClient) ShowAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) (edgeconnect.AppInstance, error) { +func (m *MockEdgeConnectClient) ShowAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) (v2.AppInstance, error) { args := m.Called(ctx, instanceKey, region) if args.Get(0) == nil { - return edgeconnect.AppInstance{}, args.Error(1) + return v2.AppInstance{}, args.Error(1) } - return args.Get(0).(edgeconnect.AppInstance), args.Error(1) + return args.Get(0).(v2.AppInstance), args.Error(1) } -func (m *MockEdgeConnectClient) CreateApp(ctx context.Context, input *edgeconnect.NewAppInput) error { +func (m *MockEdgeConnectClient) CreateApp(ctx context.Context, input *v2.NewAppInput) error { args := m.Called(ctx, input) return args.Error(0) } -func (m *MockEdgeConnectClient) CreateAppInstance(ctx context.Context, input *edgeconnect.NewAppInstanceInput) error { +func (m *MockEdgeConnectClient) CreateAppInstance(ctx context.Context, input *v2.NewAppInstanceInput) error { args := m.Called(ctx, input) return args.Error(0) } -func (m *MockEdgeConnectClient) DeleteApp(ctx context.Context, appKey edgeconnect.AppKey, region string) error { +func (m *MockEdgeConnectClient) DeleteApp(ctx context.Context, appKey v2.AppKey, region string) error { args := m.Called(ctx, appKey, region) return args.Error(0) } -func (m *MockEdgeConnectClient) UpdateApp(ctx context.Context, input *edgeconnect.UpdateAppInput) error { +func (m *MockEdgeConnectClient) UpdateApp(ctx context.Context, input *v2.UpdateAppInput) error { args := m.Called(ctx, input) return args.Error(0) } -func (m *MockEdgeConnectClient) UpdateAppInstance(ctx context.Context, input *edgeconnect.UpdateAppInstanceInput) error { +func (m *MockEdgeConnectClient) UpdateAppInstance(ctx context.Context, input *v2.UpdateAppInstanceInput) error { args := m.Called(ctx, input) return args.Error(0) } -func (m *MockEdgeConnectClient) DeleteAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) error { +func (m *MockEdgeConnectClient) DeleteAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) error { args := m.Called(ctx, instanceKey, region) return args.Error(0) } -func (m *MockEdgeConnectClient) ShowApps(ctx context.Context, appKey edgeconnect.AppKey, region string) ([]edgeconnect.App, error) { +func (m *MockEdgeConnectClient) ShowApps(ctx context.Context, appKey v2.AppKey, region string) ([]v2.App, error) { args := m.Called(ctx, appKey, region) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).([]edgeconnect.App), args.Error(1) + return args.Get(0).([]v2.App), args.Error(1) } -func (m *MockEdgeConnectClient) ShowAppInstances(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) ([]edgeconnect.AppInstance, error) { +func (m *MockEdgeConnectClient) ShowAppInstances(ctx context.Context, instanceKey v2.AppInstanceKey, region string) ([]v2.AppInstance, error) { args := m.Called(ctx, instanceKey, region) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).([]edgeconnect.AppInstance), args.Error(1) + return args.Get(0).([]v2.AppInstance), args.Error(1) } func TestNewPlanner(t *testing.T) { @@ -148,11 +148,11 @@ func TestPlanNewDeployment(t *testing.T) { testConfig := createTestConfig(t) // Mock API calls to return "not found" errors - mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). - Return(nil, &edgeconnect.APIError{StatusCode: 404, Messages: []string{"App not found"}}) + mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US"). + Return(nil, &v2.APIError{StatusCode: 404, Messages: []string{"App not found"}}) - mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("edgeconnect.AppInstanceKey"), "US"). - Return(nil, &edgeconnect.APIError{StatusCode: 404, Messages: []string{"Instance not found"}}) + mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "US"). + Return(nil, &v2.APIError{StatusCode: 404, Messages: []string{"Instance not found"}}) ctx := context.Background() result, err := planner.Plan(ctx, testConfig) @@ -186,15 +186,15 @@ func TestPlanExistingDeploymentNoChanges(t *testing.T) { // Mock existing app with same manifest hash and outbound connections manifestContent := "apiVersion: v1\nkind: Pod\nmetadata:\n name: test\n" - existingApp := &edgeconnect.App{ - Key: edgeconnect.AppKey{ + existingApp := &v2.App{ + Key: v2.AppKey{ Organization: "testorg", Name: "test-app", Version: "1.0.0", }, Deployment: "kubernetes", DeploymentManifest: manifestContent, - RequiredOutboundConnections: []edgeconnect.SecurityRule{ + RequiredOutboundConnections: []v2.SecurityRule{ { Protocol: "tcp", PortRangeMin: 80, @@ -206,31 +206,31 @@ func TestPlanExistingDeploymentNoChanges(t *testing.T) { } // Mock existing instance - existingInstance := &edgeconnect.AppInstance{ - Key: edgeconnect.AppInstanceKey{ + existingInstance := &v2.AppInstance{ + Key: v2.AppInstanceKey{ Organization: "testorg", Name: "test-app-1.0.0-instance", - CloudletKey: edgeconnect.CloudletKey{ + CloudletKey: v2.CloudletKey{ Organization: "TestCloudletOrg", Name: "TestCloudlet", }, }, - AppKey: edgeconnect.AppKey{ + AppKey: v2.AppKey{ Organization: "testorg", Name: "test-app", Version: "1.0.0", }, - Flavor: edgeconnect.Flavor{ + Flavor: v2.Flavor{ Name: "small", }, State: "Ready", PowerState: "PowerOn", } - mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). + mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US"). Return(*existingApp, nil) - mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("edgeconnect.AppInstanceKey"), "US"). + mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "US"). Return(*existingInstance, nil) ctx := context.Background() @@ -293,14 +293,14 @@ func TestPlanMultipleInfrastructures(t *testing.T) { }) // Mock API calls to return "not found" errors - mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). - Return(nil, &edgeconnect.APIError{StatusCode: 404, Messages: []string{"App not found"}}) + mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US"). + Return(nil, &v2.APIError{StatusCode: 404, Messages: []string{"App not found"}}) - mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("edgeconnect.AppInstanceKey"), "US"). - Return(nil, &edgeconnect.APIError{StatusCode: 404, Messages: []string{"Instance not found"}}) + mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "US"). + Return(nil, &v2.APIError{StatusCode: 404, Messages: []string{"Instance not found"}}) - mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("edgeconnect.AppInstanceKey"), "EU"). - Return(nil, &edgeconnect.APIError{StatusCode: 404, Messages: []string{"Instance not found"}}) + mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "EU"). + Return(nil, &v2.APIError{StatusCode: 404, Messages: []string{"Instance not found"}}) ctx := context.Background() result, err := planner.Plan(ctx, testConfig) @@ -628,10 +628,10 @@ func TestIsResourceNotFoundError(t *testing.T) { expected bool }{ {"nil error", nil, false}, - {"not found error", &edgeconnect.APIError{StatusCode: 404, Messages: []string{"Resource not found"}}, true}, - {"does not exist error", &edgeconnect.APIError{Messages: []string{"App does not exist"}}, true}, - {"404 in message", &edgeconnect.APIError{Messages: []string{"HTTP 404 error"}}, true}, - {"other error", &edgeconnect.APIError{StatusCode: 500, Messages: []string{"Server error"}}, false}, + {"not found error", &v2.APIError{StatusCode: 404, Messages: []string{"Resource not found"}}, true}, + {"does not exist error", &v2.APIError{Messages: []string{"App does not exist"}}, true}, + {"404 in message", &v2.APIError{Messages: []string{"HTTP 404 error"}}, true}, + {"other error", &v2.APIError{StatusCode: 500, Messages: []string{"Server error"}}, false}, } for _, tt := range tests { @@ -648,8 +648,8 @@ func TestPlanErrorHandling(t *testing.T) { testConfig := createTestConfig(t) // Mock API call to return a non-404 error - mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). - Return(nil, &edgeconnect.APIError{StatusCode: 500, Messages: []string{"Server error"}}) + mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US"). + Return(nil, &v2.APIError{StatusCode: 500, Messages: []string{"Server error"}}) ctx := context.Background() result, err := planner.Plan(ctx, testConfig) diff --git a/internal/apply/strategy_recreate.go b/internal/apply/strategy_recreate.go index 4e69e7d..dc44784 100644 --- a/internal/apply/strategy_recreate.go +++ b/internal/apply/strategy_recreate.go @@ -11,7 +11,7 @@ import ( "time" "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" ) // RecreateStrategy implements the recreate deployment strategy @@ -184,7 +184,7 @@ func (r *RecreateStrategy) deleteAppPhase(ctx context.Context, plan *DeploymentP r.logf("Phase 2: Deleting existing application") - appKey := edgeconnect.AppKey{ + appKey := v2.AppKey{ Organization: plan.AppAction.Desired.Organization, Name: plan.AppAction.Desired.Name, Version: plan.AppAction.Desired.Version, @@ -426,10 +426,10 @@ func (r *RecreateStrategy) executeAppActionWithRetry(ctx context.Context, action // deleteInstance deletes an instance (reuse existing logic from manager.go) func (r *RecreateStrategy) deleteInstance(ctx context.Context, action InstanceAction) (bool, error) { - instanceKey := edgeconnect.AppInstanceKey{ + instanceKey := v2.AppInstanceKey{ Organization: action.Desired.Organization, Name: action.InstanceName, - CloudletKey: edgeconnect.CloudletKey{ + CloudletKey: v2.CloudletKey{ Organization: action.Target.CloudletOrg, Name: action.Target.CloudletName, }, @@ -445,23 +445,23 @@ func (r *RecreateStrategy) deleteInstance(ctx context.Context, action InstanceAc // createInstance creates an instance (extracted from manager.go logic) func (r *RecreateStrategy) createInstance(ctx context.Context, action InstanceAction, config *config.EdgeConnectConfig) (bool, error) { - instanceInput := &edgeconnect.NewAppInstanceInput{ + instanceInput := &v2.NewAppInstanceInput{ Region: action.Target.Region, - AppInst: edgeconnect.AppInstance{ - Key: edgeconnect.AppInstanceKey{ + AppInst: v2.AppInstance{ + Key: v2.AppInstanceKey{ Organization: action.Desired.Organization, Name: action.InstanceName, - CloudletKey: edgeconnect.CloudletKey{ + CloudletKey: v2.CloudletKey{ Organization: action.Target.CloudletOrg, Name: action.Target.CloudletName, }, }, - AppKey: edgeconnect.AppKey{ + AppKey: v2.AppKey{ Organization: action.Desired.Organization, Name: config.Metadata.Name, Version: config.Metadata.AppVersion, }, - Flavor: edgeconnect.Flavor{ + Flavor: v2.Flavor{ Name: action.Target.FlavorName, }, }, @@ -481,10 +481,10 @@ func (r *RecreateStrategy) createInstance(ctx context.Context, action InstanceAc // updateApplication creates/recreates an application (always uses CreateApp since we delete first) func (r *RecreateStrategy) updateApplication(ctx context.Context, action AppAction, config *config.EdgeConnectConfig, manifestContent string) (bool, error) { // Build the app create input - always create since recreate strategy deletes first - appInput := &edgeconnect.NewAppInput{ + appInput := &v2.NewAppInput{ Region: action.Desired.Region, - App: edgeconnect.App{ - Key: edgeconnect.AppKey{ + App: v2.App{ + Key: v2.AppKey{ Organization: action.Desired.Organization, Name: action.Desired.Name, Version: action.Desired.Version, @@ -493,7 +493,7 @@ func (r *RecreateStrategy) updateApplication(ctx context.Context, action AppActi ImageType: "ImageTypeDocker", ImagePath: config.GetImagePath(), AllowServerless: true, - DefaultFlavor: edgeconnect.Flavor{Name: config.Spec.InfraTemplate[0].FlavorName}, + DefaultFlavor: v2.Flavor{Name: config.Spec.InfraTemplate[0].FlavorName}, ServerlessConfig: struct{}{}, DeploymentManifest: manifestContent, DeploymentGenerator: "kubernetes-basic", @@ -531,7 +531,7 @@ func isRetryableError(err error) bool { } // Check if it's an APIError with a status code - var apiErr *edgeconnect.APIError + var apiErr *v2.APIError if errors.As(err, &apiErr) { // Don't retry client errors (4xx) if apiErr.StatusCode >= 400 && apiErr.StatusCode < 500 { diff --git a/internal/apply/types.go b/internal/apply/types.go index 6f7ef4e..279832a 100644 --- a/internal/apply/types.go +++ b/internal/apply/types.go @@ -8,11 +8,11 @@ import ( "time" "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" ) // SecurityRule defines network access rules (alias to SDK type for consistency) -type SecurityRule = edgeconnect.SecurityRule +type SecurityRule = v2.SecurityRule // ActionType represents the type of action to be performed type ActionType string @@ -446,11 +446,11 @@ func (dp *DeploymentPlan) Clone() *DeploymentPlan { } // convertNetworkRules converts config network rules to EdgeConnect SecurityRules -func convertNetworkRules(network *config.NetworkConfig) []edgeconnect.SecurityRule { - rules := make([]edgeconnect.SecurityRule, len(network.OutboundConnections)) +func convertNetworkRules(network *config.NetworkConfig) []v2.SecurityRule { + rules := make([]v2.SecurityRule, len(network.OutboundConnections)) for i, conn := range network.OutboundConnections { - rules[i] = edgeconnect.SecurityRule{ + rules[i] = v2.SecurityRule{ Protocol: conn.Protocol, PortRangeMin: conn.PortRangeMin, PortRangeMax: conn.PortRangeMax, diff --git a/sdk/README.md b/sdk/README.md index 0f16b12..89dc673 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -16,18 +16,18 @@ A comprehensive Go SDK for the EdgeXR Master Controller API, providing typed int ### Installation ```go -import "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" +import v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" ``` ### Authentication ```go // Username/password (recommended) -client := client.NewClientWithCredentials(baseURL, username, password) +client := v2.NewClientWithCredentials(baseURL, username, password) // Static Bearer token -client := client.NewClient(baseURL, - client.WithAuthProvider(client.NewStaticTokenProvider(token))) +client := v2.NewClient(baseURL, + v2.WithAuthProvider(v2.NewStaticTokenProvider(token))) ``` ### Basic Usage @@ -36,10 +36,10 @@ client := client.NewClient(baseURL, ctx := context.Background() // Create an application -app := &client.NewAppInput{ +app := &v2.NewAppInput{ Region: "us-west", - App: client.App{ - Key: client.AppKey{ + App: v2.App{ + Key: v2.AppKey{ Organization: "myorg", Name: "my-app", Version: "1.0.0", @@ -49,28 +49,28 @@ app := &client.NewAppInput{ }, } -if err := client.CreateApp(ctx, app); err != nil { +if err := v2.CreateApp(ctx, app); err != nil { log.Fatal(err) } // Deploy an application instance -instance := &client.NewAppInstanceInput{ +instance := &v2.NewAppInstanceInput{ Region: "us-west", - AppInst: client.AppInstance{ - Key: client.AppInstanceKey{ + AppInst: v2.AppInstance{ + Key: v2.AppInstanceKey{ Organization: "myorg", Name: "my-instance", - CloudletKey: client.CloudletKey{ + CloudletKey: v2.CloudletKey{ Organization: "cloudlet-provider", Name: "edge-cloudlet", }, }, AppKey: app.App.Key, - Flavor: client.Flavor{Name: "m4.small"}, + Flavor: v2.Flavor{Name: "m4.small"}, }, } -if err := client.CreateAppInstance(ctx, instance); err != nil { +if err := v2.CreateAppInstance(ctx, instance); err != nil { log.Fatal(err) } ``` @@ -101,22 +101,22 @@ if err := client.CreateAppInstance(ctx, instance); err != nil { ## Configuration Options ```go -client := client.NewClient(baseURL, +client := v2.NewClient(baseURL, // Custom HTTP client with timeout - client.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), + v2.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), // Authentication provider - client.WithAuthProvider(client.NewStaticTokenProvider(token)), + v2.WithAuthProvider(v2.NewStaticTokenProvider(token)), // Retry configuration - client.WithRetryOptions(client.RetryOptions{ + v2.WithRetryOptions(v2.RetryOptions{ MaxRetries: 5, InitialDelay: 1 * time.Second, MaxDelay: 30 * time.Second, }), // Request logging - client.WithLogger(log.Default()), + v2.WithLogger(log.Default()), ) ``` @@ -141,7 +141,7 @@ EDGEXR_USERNAME=user EDGEXR_PASSWORD=pass go run main.go Uses the existing `/api/v1/login` endpoint with automatic token caching: ```go -client := client.NewClientWithCredentials(baseURL, username, password) +client := v2.NewClientWithCredentials(baseURL, username, password) ``` **Features:** @@ -154,23 +154,23 @@ client := client.NewClientWithCredentials(baseURL, username, password) For pre-obtained tokens: ```go -client := client.NewClient(baseURL, - client.WithAuthProvider(client.NewStaticTokenProvider(token))) +client := v2.NewClient(baseURL, + v2.WithAuthProvider(v2.NewStaticTokenProvider(token))) ``` ## Error Handling ```go -app, err := client.ShowApp(ctx, appKey, region) +app, err := v2.ShowApp(ctx, appKey, region) if err != nil { // Check for specific error types - if errors.Is(err, client.ErrResourceNotFound) { + if errors.Is(err, v2.ErrResourceNotFound) { fmt.Println("App not found") return } // Check for API errors - var apiErr *client.APIError + var apiErr *v2.APIError if errors.As(err, &apiErr) { fmt.Printf("API Error %d: %s\n", apiErr.StatusCode, apiErr.Messages[0]) return @@ -213,13 +213,13 @@ The SDK provides a drop-in replacement with enhanced features: ```go // Old approach -oldClient := &client.EdgeConnect{ +oldClient := &v2.EdgeConnect{ BaseURL: baseURL, - Credentials: client.Credentials{Username: user, Password: pass}, + Credentials: v2.Credentials{Username: user, Password: pass}, } // New SDK approach -newClient := client.NewClientWithCredentials(baseURL, user, pass) +newClient := v2.NewClientWithCredentials(baseURL, user, pass) // Same method calls, enhanced reliability err := newClient.CreateApp(ctx, input) diff --git a/sdk/edgeconnect/appinstance.go b/sdk/edgeconnect/appinstance.go index ec4751a..a26f45c 100644 --- a/sdk/edgeconnect/appinstance.go +++ b/sdk/edgeconnect/appinstance.go @@ -4,11 +4,9 @@ package edgeconnect import ( - "bytes" "context" "encoding/json" "fmt" - "io" "net/http" sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/internal/http" @@ -166,17 +164,18 @@ func (c *Client) RefreshAppInstance(ctx context.Context, appInstKey AppInstanceK return nil } -// DeleteAppInstance removes an application instance +// 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" - input := DeleteAppInstanceInput{ - Key: appInstKey, + filter := AppInstanceFilter{ + AppInstance: AppInstance{Key: appInstKey}, + Region: region, } - resp, err := transport.Call(ctx, "POST", url, input) + resp, err := transport.Call(ctx, "POST", url, filter) if err != nil { return fmt.Errorf("DeleteAppInstance failed: %w", err) } @@ -195,29 +194,13 @@ func (c *Client) DeleteAppInstance(ctx context.Context, appInstKey AppInstanceKe // parseStreamingAppInstanceResponse parses the EdgeXR streaming JSON response format for app instances func (c *Client) parseStreamingAppInstanceResponse(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 *[]AppInstance: - var appInstances []AppInstance - if err := json.Unmarshal(bodyBytes, &appInstances); err == nil { - *v = appInstances - return nil - } - } - - // Fall back to streaming format (v1 API format) var appInstances []AppInstance var messages []string var hasError bool var errorCode int var errorMessage string - parseErr := sdkhttp.ParseJSONLines(io.NopCloser(bytes.NewReader(bodyBytes)), func(line []byte) error { + parseErr := sdkhttp.ParseJSONLines(resp.Body, 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 != "" { diff --git a/sdk/edgeconnect/apps.go b/sdk/edgeconnect/apps.go index 7010070..70f5dea 100644 --- a/sdk/edgeconnect/apps.go +++ b/sdk/edgeconnect/apps.go @@ -4,7 +4,6 @@ package edgeconnect import ( - "bytes" "context" "encoding/json" "fmt" @@ -143,12 +142,12 @@ func (c *Client) DeleteApp(ctx context.Context, appKey AppKey, region string) er transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/DeleteApp" - input := DeleteAppInput{ - Key: appKey, + filter := AppFilter{ + App: App{Key: appKey}, Region: region, } - resp, err := transport.Call(ctx, "POST", url, input) + resp, err := transport.Call(ctx, "POST", url, filter) if err != nil { return fmt.Errorf("DeleteApp failed: %w", err) } @@ -167,27 +166,9 @@ func (c *Client) DeleteApp(ctx context.Context, appKey AppKey, region string) er // 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 { + parseErr := sdkhttp.ParseJSONLines(resp.Body, func(line []byte) error { var response Response[App] if err := json.Unmarshal(line, &response); err != nil { return err @@ -201,6 +182,9 @@ func (c *Client) parseStreamingResponse(resp *http.Response, result interface{}) } // Extract data from responses + var apps []App + var messages []string + for _, response := range responses { if response.HasData() { apps = append(apps, response.Data) diff --git a/sdk/edgeconnect/types.go b/sdk/edgeconnect/types.go index ffd5550..7fd39fc 100644 --- a/sdk/edgeconnect/types.go +++ b/sdk/edgeconnect/types.go @@ -184,33 +184,24 @@ type App struct { Deployment string `json:"deployment,omitempty"` ImageType string `json:"image_type,omitempty"` ImagePath string `json:"image_path,omitempty"` - AccessPorts string `json:"access_ports,omitempty"` AllowServerless bool `json:"allow_serverless,omitempty"` DefaultFlavor Flavor `json:"defaultFlavor,omitempty"` ServerlessConfig interface{} `json:"serverless_config,omitempty"` DeploymentGenerator string `json:"deployment_generator,omitempty"` DeploymentManifest string `json:"deployment_manifest,omitempty"` RequiredOutboundConnections []SecurityRule `json:"required_outbound_connections"` - GlobalID string `json:"global_id,omitempty"` - CreatedAt string `json:"created_at,omitempty"` - UpdatedAt string `json:"updated_at,omitempty"` Fields []string `json:"fields,omitempty"` } // AppInstance represents a deployed application instance type AppInstance struct { - msg `json:",inline"` - Key AppInstanceKey `json:"key"` - AppKey AppKey `json:"app_key,omitempty"` - CloudletLoc CloudletLoc `json:"cloudlet_loc,omitempty"` - Flavor Flavor `json:"flavor,omitempty"` - State string `json:"state,omitempty"` - IngressURL string `json:"ingress_url,omitempty"` - UniqueID string `json:"unique_id,omitempty"` - CreatedAt string `json:"created_at,omitempty"` - UpdatedAt string `json:"updated_at,omitempty"` - PowerState string `json:"power_state,omitempty"` - Fields []string `json:"fields,omitempty"` + msg `json:",inline"` + Key AppInstanceKey `json:"key"` + AppKey AppKey `json:"app_key,omitempty"` + Flavor Flavor `json:"flavor,omitempty"` + State string `json:"state,omitempty"` + PowerState string `json:"power_state,omitempty"` + Fields []string `json:"fields,omitempty"` } // Cloudlet represents edge infrastructure @@ -233,12 +224,6 @@ type Location struct { Longitude float64 `json:"longitude"` } -// CloudletLoc represents geographical coordinates for cloudlets -type CloudletLoc struct { - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` -} - // Input types for API operations // NewAppInput represents input for creating an application @@ -271,17 +256,6 @@ type UpdateAppInstanceInput struct { AppInst AppInstance `json:"appinst"` } -// DeleteAppInput represents input for deleting an application -type DeleteAppInput struct { - Key AppKey `json:"key"` - Region string `json:"region"` -} - -// DeleteAppInstanceInput represents input for deleting an app instance -type DeleteAppInstanceInput struct { - Key AppInstanceKey `json:"key"` -} - // Response wrapper types // Response wraps a single API response diff --git a/sdk/edgeconnect/v2/appinstance.go b/sdk/edgeconnect/v2/appinstance.go new file mode 100644 index 0000000..57e6b3c --- /dev/null +++ b/sdk/edgeconnect/v2/appinstance.go @@ -0,0 +1,281 @@ +// ABOUTME: Application instance lifecycle management APIs for EdgeXR Master Controller +// ABOUTME: Provides typed methods for creating, querying, and deleting application instances + +package v2 + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "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") + } + + // Parse streaming JSON response + var appInstances []AppInstance + if err := c.parseStreamingAppInstanceResponse(resp, &appInstances); err != nil { + return fmt.Errorf("ShowAppInstance failed to parse response: %w", err) + } + + 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 +// 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" + + input := DeleteAppInstanceInput{ + Key: appInstKey, + } + + resp, err := transport.Call(ctx, "POST", url, input) + 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 { + 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 *[]AppInstance: + var appInstances []AppInstance + if err := json.Unmarshal(bodyBytes, &appInstances); err == nil { + *v = appInstances + return nil + } + } + + // Fall back to streaming format (v1 API format) + var appInstances []AppInstance + var messages []string + var hasError bool + var errorCode int + var errorMessage string + + 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 nil + } + + // Try parsing as Response[AppInstance] + var response Response[AppInstance] + if err := json.Unmarshal(line, &response); err != nil { + return err + } + + if response.HasData() { + appInstances = append(appInstances, response.Data) + } + 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 parseErr != nil { + return parseErr + } + + // 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 + } + + // Set result based on type + switch v := result.(type) { + case *[]AppInstance: + *v = appInstances + default: + return fmt.Errorf("unsupported result type: %T", result) + } + + return nil +} diff --git a/sdk/edgeconnect/v2/appinstance_test.go b/sdk/edgeconnect/v2/appinstance_test.go new file mode 100644 index 0000000..e1c3d5e --- /dev/null +++ b/sdk/edgeconnect/v2/appinstance_test.go @@ -0,0 +1,524 @@ +// ABOUTME: Unit tests for AppInstance management APIs using httptest mock server +// ABOUTME: Tests create, show, list, refresh, and delete operations with error conditions + +package v2 + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCreateAppInstance(t *testing.T) { + tests := []struct { + name string + input *NewAppInstanceInput + mockStatusCode int + mockResponse string + expectError bool + errorContains string + }{ + { + name: "successful creation", + input: &NewAppInstanceInput{ + Region: "us-west", + AppInst: AppInstance{ + Key: AppInstanceKey{ + Organization: "testorg", + Name: "testinst", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + AppKey: AppKey{ + Organization: "testorg", + Name: "testapp", + Version: "1.0.0", + }, + Flavor: Flavor{Name: "m4.small"}, + }, + }, + mockStatusCode: 200, + mockResponse: `{"message": "success"}`, + expectError: false, + }, + { + name: "validation error", + input: &NewAppInstanceInput{ + Region: "us-west", + AppInst: AppInstance{ + Key: AppInstanceKey{ + Organization: "", + Name: "testinst", + }, + }, + }, + mockStatusCode: 400, + mockResponse: `{"message": "organization is required"}`, + expectError: true, + }, + { + name: "HTTP 200 with CreateError message", + input: &NewAppInstanceInput{ + Region: "us-west", + AppInst: AppInstance{ + Key: AppInstanceKey{ + Organization: "testorg", + Name: "testinst", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + Flavor: Flavor{Name: "m4.small"}, + }, + }, + mockStatusCode: 200, + mockResponse: `{"data":{"message":"Creating"}} +{"data":{"message":"a service has been configured"}} +{"data":{"message":"CreateError"}} +{"data":{"message":"Deleting AppInst due to failure"}} +{"data":{"message":"Deleted AppInst successfully"}}`, + expectError: true, + errorContains: "CreateError", + }, + { + name: "HTTP 200 with result error code", + input: &NewAppInstanceInput{ + Region: "us-west", + AppInst: AppInstance{ + Key: AppInstanceKey{ + Organization: "testorg", + Name: "testinst", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + Flavor: Flavor{Name: "m4.small"}, + }, + }, + mockStatusCode: 200, + mockResponse: `{"data":{"message":"Creating"}} +{"data":{"message":"a service has been configured"}} +{"data":{"message":"CreateError"}} +{"data":{"message":"Deleting AppInst due to failure"}} +{"data":{"message":"Deleted AppInst successfully"}} +{"result":{"message":"Encountered failures: Create App Inst failed: deployments.apps is forbidden: User \"system:serviceaccount:edgexr:crm-telekomop-munich\" cannot create resource \"deployments\" in API group \"apps\" in the namespace \"gitea\"","code":400}}`, + expectError: true, + errorContains: "deployments.apps is forbidden", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/CreateAppInst", r.URL.Path) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + w.WriteHeader(tt.mockStatusCode) + w.Write([]byte(tt.mockResponse)) + })) + defer server.Close() + + // Create client + client := NewClient(server.URL, + WithHTTPClient(&http.Client{Timeout: 5 * time.Second}), + WithAuthProvider(NewStaticTokenProvider("test-token")), + ) + + // Execute test + ctx := context.Background() + err := client.CreateAppInstance(ctx, tt.input) + + // Verify results + if tt.expectError { + assert.Error(t, err) + if tt.errorContains != "" { + assert.Contains(t, err.Error(), tt.errorContains) + } + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestShowAppInstance(t *testing.T) { + tests := []struct { + name string + appInstKey AppInstanceKey + region string + mockStatusCode int + mockResponse string + expectError bool + expectNotFound bool + }{ + { + name: "successful show", + appInstKey: AppInstanceKey{ + Organization: "testorg", + Name: "testinst", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + region: "us-west", + mockStatusCode: 200, + mockResponse: `{"data": {"key": {"organization": "testorg", "name": "testinst", "cloudlet_key": {"organization": "cloudletorg", "name": "testcloudlet"}}, "state": "Ready"}} +`, + expectError: false, + expectNotFound: false, + }, + { + name: "instance not found", + appInstKey: AppInstanceKey{ + Organization: "testorg", + Name: "nonexistent", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + region: "us-west", + mockStatusCode: 404, + mockResponse: "", + expectError: true, + expectNotFound: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/ShowAppInst", r.URL.Path) + + w.WriteHeader(tt.mockStatusCode) + if tt.mockResponse != "" { + w.Write([]byte(tt.mockResponse)) + } + })) + defer server.Close() + + // Create client + client := NewClient(server.URL, + WithHTTPClient(&http.Client{Timeout: 5 * time.Second}), + ) + + // Execute test + ctx := context.Background() + appInst, err := client.ShowAppInstance(ctx, tt.appInstKey, tt.region) + + // Verify results + if tt.expectError { + assert.Error(t, err) + if tt.expectNotFound { + assert.Contains(t, err.Error(), "resource not found") + } + } else { + require.NoError(t, err) + assert.Equal(t, tt.appInstKey.Organization, appInst.Key.Organization) + assert.Equal(t, tt.appInstKey.Name, appInst.Key.Name) + assert.Equal(t, "Ready", appInst.State) + } + }) + } +} + +func TestShowAppInstances(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/ShowAppInst", r.URL.Path) + + // Verify request body + var filter AppInstanceFilter + err := json.NewDecoder(r.Body).Decode(&filter) + require.NoError(t, err) + assert.Equal(t, "testorg", filter.AppInstance.Key.Organization) + assert.Equal(t, "us-west", filter.Region) + + // Return multiple app instances + response := `{"data": {"key": {"organization": "testorg", "name": "inst1"}, "state": "Ready"}} +{"data": {"key": {"organization": "testorg", "name": "inst2"}, "state": "Creating"}} +` + w.WriteHeader(200) + w.Write([]byte(response)) + })) + defer server.Close() + + client := NewClient(server.URL) + ctx := context.Background() + + appInstances, err := client.ShowAppInstances(ctx, AppInstanceKey{Organization: "testorg"}, "us-west") + + require.NoError(t, err) + assert.Len(t, appInstances, 2) + assert.Equal(t, "inst1", appInstances[0].Key.Name) + assert.Equal(t, "Ready", appInstances[0].State) + assert.Equal(t, "inst2", appInstances[1].Key.Name) + assert.Equal(t, "Creating", appInstances[1].State) +} + +func TestUpdateAppInstance(t *testing.T) { + tests := []struct { + name string + input *UpdateAppInstanceInput + mockStatusCode int + mockResponse string + expectError bool + }{ + { + name: "successful update", + input: &UpdateAppInstanceInput{ + Region: "us-west", + AppInst: AppInstance{ + Key: AppInstanceKey{ + Organization: "testorg", + Name: "testinst", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + AppKey: AppKey{ + Organization: "testorg", + Name: "testapp", + Version: "1.0.0", + }, + Flavor: Flavor{Name: "m4.medium"}, + PowerState: "PowerOn", + }, + }, + mockStatusCode: 200, + mockResponse: `{"message": "success"}`, + expectError: false, + }, + { + name: "validation error", + input: &UpdateAppInstanceInput{ + Region: "us-west", + AppInst: AppInstance{ + Key: AppInstanceKey{ + Organization: "", + Name: "testinst", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + }, + }, + mockStatusCode: 400, + mockResponse: `{"message": "organization is required"}`, + expectError: true, + }, + { + name: "instance not found", + input: &UpdateAppInstanceInput{ + Region: "us-west", + AppInst: AppInstance{ + Key: AppInstanceKey{ + Organization: "testorg", + Name: "nonexistent", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + }, + }, + mockStatusCode: 404, + mockResponse: `{"message": "app instance not found"}`, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/UpdateAppInst", r.URL.Path) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + // Verify request body + var input UpdateAppInstanceInput + err := json.NewDecoder(r.Body).Decode(&input) + require.NoError(t, err) + assert.Equal(t, tt.input.Region, input.Region) + assert.Equal(t, tt.input.AppInst.Key.Organization, input.AppInst.Key.Organization) + + w.WriteHeader(tt.mockStatusCode) + w.Write([]byte(tt.mockResponse)) + })) + defer server.Close() + + // Create client + client := NewClient(server.URL, + WithHTTPClient(&http.Client{Timeout: 5 * time.Second}), + WithAuthProvider(NewStaticTokenProvider("test-token")), + ) + + // Execute test + ctx := context.Background() + err := client.UpdateAppInstance(ctx, tt.input) + + // Verify results + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestRefreshAppInstance(t *testing.T) { + tests := []struct { + name string + appInstKey AppInstanceKey + region string + mockStatusCode int + expectError bool + }{ + { + name: "successful refresh", + appInstKey: AppInstanceKey{ + Organization: "testorg", + Name: "testinst", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + region: "us-west", + mockStatusCode: 200, + expectError: false, + }, + { + name: "server error", + appInstKey: AppInstanceKey{ + Organization: "testorg", + Name: "testinst", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + region: "us-west", + mockStatusCode: 500, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/RefreshAppInst", r.URL.Path) + + w.WriteHeader(tt.mockStatusCode) + })) + defer server.Close() + + client := NewClient(server.URL) + ctx := context.Background() + + err := client.RefreshAppInstance(ctx, tt.appInstKey, tt.region) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestDeleteAppInstance(t *testing.T) { + tests := []struct { + name string + appInstKey AppInstanceKey + region string + mockStatusCode int + expectError bool + }{ + { + name: "successful deletion", + appInstKey: AppInstanceKey{ + Organization: "testorg", + Name: "testinst", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + region: "us-west", + mockStatusCode: 200, + expectError: false, + }, + { + name: "already deleted (404 ok)", + appInstKey: AppInstanceKey{ + Organization: "testorg", + Name: "testinst", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + region: "us-west", + mockStatusCode: 404, + expectError: false, + }, + { + name: "server error", + appInstKey: AppInstanceKey{ + Organization: "testorg", + Name: "testinst", + CloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + }, + region: "us-west", + mockStatusCode: 500, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/DeleteAppInst", r.URL.Path) + + w.WriteHeader(tt.mockStatusCode) + })) + defer server.Close() + + client := NewClient(server.URL) + ctx := context.Background() + + err := client.DeleteAppInstance(ctx, tt.appInstKey, tt.region) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/sdk/edgeconnect/v2/apps.go b/sdk/edgeconnect/v2/apps.go new file mode 100644 index 0000000..ce5bb76 --- /dev/null +++ b/sdk/edgeconnect/v2/apps.go @@ -0,0 +1,267 @@ +// ABOUTME: Application lifecycle management APIs for EdgeXR Master Controller +// ABOUTME: Provides typed methods for creating, querying, and deleting applications + +package v2 + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/internal/http" +) + +var ( + // ErrResourceNotFound indicates the requested resource was not found + ErrResourceNotFound = fmt.Errorf("resource not found") +) + +// CreateApp creates a new application in the specified region +// Maps to POST /auth/ctrl/CreateApp +func (c *Client) CreateApp(ctx context.Context, input *NewAppInput) error { + transport := c.getTransport() + url := c.BaseURL + "/api/v1/auth/ctrl/CreateApp" + + resp, err := transport.Call(ctx, "POST", url, input) + if err != nil { + return fmt.Errorf("CreateApp failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return c.handleErrorResponse(resp, "CreateApp") + } + + c.logf("CreateApp: %s/%s version %s created successfully", + input.App.Key.Organization, input.App.Key.Name, input.App.Key.Version) + + return nil +} + +// ShowApp retrieves a single application by key and region +// Maps to POST /auth/ctrl/ShowApp +func (c *Client) ShowApp(ctx context.Context, appKey AppKey, region string) (App, error) { + transport := c.getTransport() + url := c.BaseURL + "/api/v1/auth/ctrl/ShowApp" + + filter := AppFilter{ + App: App{Key: appKey}, + Region: region, + } + + resp, err := transport.Call(ctx, "POST", url, filter) + if err != nil { + return App{}, fmt.Errorf("ShowApp failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return App{}, fmt.Errorf("app %s/%s version %s in region %s: %w", + appKey.Organization, appKey.Name, appKey.Version, region, ErrResourceNotFound) + } + + if resp.StatusCode >= 400 { + return App{}, c.handleErrorResponse(resp, "ShowApp") + } + + // Parse streaming JSON response + var apps []App + if err := c.parseStreamingResponse(resp, &apps); err != nil { + return App{}, fmt.Errorf("ShowApp failed to parse response: %w", err) + } + + if len(apps) == 0 { + return App{}, fmt.Errorf("app %s/%s version %s in region %s: %w", + appKey.Organization, appKey.Name, appKey.Version, region, ErrResourceNotFound) + } + + return apps[0], nil +} + +// ShowApps retrieves all applications matching the filter criteria +// Maps to POST /auth/ctrl/ShowApp +func (c *Client) ShowApps(ctx context.Context, appKey AppKey, region string) ([]App, error) { + transport := c.getTransport() + url := c.BaseURL + "/api/v1/auth/ctrl/ShowApp" + + filter := AppFilter{ + App: App{Key: appKey}, + Region: region, + } + + resp, err := transport.Call(ctx, "POST", url, filter) + if err != nil { + return nil, fmt.Errorf("ShowApps failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { + return nil, c.handleErrorResponse(resp, "ShowApps") + } + + var apps []App + if resp.StatusCode == http.StatusNotFound { + return apps, nil // Return empty slice for not found + } + + if err := c.parseStreamingResponse(resp, &apps); err != nil { + return nil, fmt.Errorf("ShowApps failed to parse response: %w", err) + } + + c.logf("ShowApps: found %d apps matching criteria", len(apps)) + return apps, nil +} + +// UpdateApp updates the definition of an application +// Maps to POST /auth/ctrl/UpdateApp +func (c *Client) UpdateApp(ctx context.Context, input *UpdateAppInput) error { + transport := c.getTransport() + url := c.BaseURL + "/api/v1/auth/ctrl/UpdateApp" + + resp, err := transport.Call(ctx, "POST", url, input) + if err != nil { + return fmt.Errorf("UpdateApp failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return c.handleErrorResponse(resp, "UpdateApp") + } + + c.logf("UpdateApp: %s/%s version %s updated successfully", + input.App.Key.Organization, input.App.Key.Name, input.App.Key.Version) + + return nil +} + +// DeleteApp removes an application from the specified region +// Maps to POST /auth/ctrl/DeleteApp +func (c *Client) DeleteApp(ctx context.Context, appKey AppKey, region string) error { + transport := c.getTransport() + url := c.BaseURL + "/api/v1/auth/ctrl/DeleteApp" + + input := DeleteAppInput{ + Key: appKey, + Region: region, + } + + resp, err := transport.Call(ctx, "POST", url, input) + if err != nil { + return fmt.Errorf("DeleteApp 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, "DeleteApp") + } + + c.logf("DeleteApp: %s/%s version %s deleted successfully", + appKey.Organization, appKey.Name, appKey.Version) + + 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( + sdkhttp.RetryOptions{ + MaxRetries: c.RetryOpts.MaxRetries, + InitialDelay: c.RetryOpts.InitialDelay, + MaxDelay: c.RetryOpts.MaxDelay, + Multiplier: c.RetryOpts.Multiplier, + RetryableHTTPStatusCodes: c.RetryOpts.RetryableHTTPStatusCodes, + }, + c.AuthProvider, + c.Logger, + ) +} + +// handleErrorResponse creates an appropriate error from HTTP error response +func (c *Client) handleErrorResponse(resp *http.Response, operation string) error { + + messages := []string{ + fmt.Sprintf("%s failed with status %d", operation, resp.StatusCode), + } + + bodyBytes := []byte{} + + if resp.Body != nil { + defer resp.Body.Close() + bodyBytes, _ = io.ReadAll(resp.Body) + messages = append(messages, string(bodyBytes)) + } + + return &APIError{ + StatusCode: resp.StatusCode, + Messages: messages, + Body: bodyBytes, + } +} diff --git a/sdk/edgeconnect/v2/apps_test.go b/sdk/edgeconnect/v2/apps_test.go new file mode 100644 index 0000000..4ea757c --- /dev/null +++ b/sdk/edgeconnect/v2/apps_test.go @@ -0,0 +1,419 @@ +// ABOUTME: Unit tests for App management APIs using httptest mock server +// ABOUTME: Tests create, show, list, and delete operations with error conditions + +package v2 + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCreateApp(t *testing.T) { + tests := []struct { + name string + input *NewAppInput + mockStatusCode int + mockResponse string + expectError bool + }{ + { + name: "successful creation", + input: &NewAppInput{ + Region: "us-west", + App: App{ + Key: AppKey{ + Organization: "testorg", + Name: "testapp", + Version: "1.0.0", + }, + Deployment: "kubernetes", + }, + }, + mockStatusCode: 200, + mockResponse: `{"message": "success"}`, + expectError: false, + }, + { + name: "validation error", + input: &NewAppInput{ + Region: "us-west", + App: App{ + Key: AppKey{ + Organization: "", + Name: "testapp", + Version: "1.0.0", + }, + }, + }, + mockStatusCode: 400, + mockResponse: `{"message": "organization is required"}`, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/CreateApp", r.URL.Path) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + w.WriteHeader(tt.mockStatusCode) + w.Write([]byte(tt.mockResponse)) + })) + defer server.Close() + + // Create client + client := NewClient(server.URL, + WithHTTPClient(&http.Client{Timeout: 5 * time.Second}), + WithAuthProvider(NewStaticTokenProvider("test-token")), + ) + + // Execute test + ctx := context.Background() + err := client.CreateApp(ctx, tt.input) + + // Verify results + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestShowApp(t *testing.T) { + tests := []struct { + name string + appKey AppKey + region string + mockStatusCode int + mockResponse string + expectError bool + expectNotFound bool + }{ + { + name: "successful show", + appKey: AppKey{ + Organization: "testorg", + Name: "testapp", + Version: "1.0.0", + }, + region: "us-west", + mockStatusCode: 200, + mockResponse: `{"data": {"key": {"organization": "testorg", "name": "testapp", "version": "1.0.0"}, "deployment": "kubernetes"}} +`, + expectError: false, + expectNotFound: false, + }, + { + name: "app not found", + appKey: AppKey{ + Organization: "testorg", + Name: "nonexistent", + Version: "1.0.0", + }, + region: "us-west", + mockStatusCode: 404, + mockResponse: "", + expectError: true, + expectNotFound: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/ShowApp", r.URL.Path) + + w.WriteHeader(tt.mockStatusCode) + if tt.mockResponse != "" { + w.Write([]byte(tt.mockResponse)) + } + })) + defer server.Close() + + // Create client + client := NewClient(server.URL, + WithHTTPClient(&http.Client{Timeout: 5 * time.Second}), + ) + + // Execute test + ctx := context.Background() + app, err := client.ShowApp(ctx, tt.appKey, tt.region) + + // Verify results + if tt.expectError { + assert.Error(t, err) + if tt.expectNotFound { + assert.Contains(t, err.Error(), "resource not found") + } + } else { + require.NoError(t, err) + assert.Equal(t, tt.appKey.Organization, app.Key.Organization) + assert.Equal(t, tt.appKey.Name, app.Key.Name) + assert.Equal(t, tt.appKey.Version, app.Key.Version) + } + }) + } +} + +func TestShowApps(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/ShowApp", r.URL.Path) + + // Verify request body + var filter AppFilter + err := json.NewDecoder(r.Body).Decode(&filter) + require.NoError(t, err) + assert.Equal(t, "testorg", filter.App.Key.Organization) + assert.Equal(t, "us-west", filter.Region) + + // Return multiple apps + response := `{"data": {"key": {"organization": "testorg", "name": "app1", "version": "1.0.0"}, "deployment": "kubernetes"}} +{"data": {"key": {"organization": "testorg", "name": "app2", "version": "1.0.0"}, "deployment": "docker"}} +` + w.WriteHeader(200) + w.Write([]byte(response)) + })) + defer server.Close() + + client := NewClient(server.URL) + ctx := context.Background() + + apps, err := client.ShowApps(ctx, AppKey{Organization: "testorg"}, "us-west") + + require.NoError(t, err) + assert.Len(t, apps, 2) + assert.Equal(t, "app1", apps[0].Key.Name) + assert.Equal(t, "app2", apps[1].Key.Name) +} + +func TestUpdateApp(t *testing.T) { + tests := []struct { + name string + input *UpdateAppInput + mockStatusCode int + mockResponse string + expectError bool + }{ + { + name: "successful update", + input: &UpdateAppInput{ + Region: "us-west", + App: App{ + Key: AppKey{ + Organization: "testorg", + Name: "testapp", + Version: "1.0.0", + }, + Deployment: "kubernetes", + ImagePath: "nginx:latest", + }, + }, + mockStatusCode: 200, + mockResponse: `{"message": "success"}`, + expectError: false, + }, + { + name: "validation error", + input: &UpdateAppInput{ + Region: "us-west", + App: App{ + Key: AppKey{ + Organization: "", + Name: "testapp", + Version: "1.0.0", + }, + }, + }, + mockStatusCode: 400, + mockResponse: `{"message": "organization is required"}`, + expectError: true, + }, + { + name: "app not found", + input: &UpdateAppInput{ + Region: "us-west", + App: App{ + Key: AppKey{ + Organization: "testorg", + Name: "nonexistent", + Version: "1.0.0", + }, + }, + }, + mockStatusCode: 404, + mockResponse: `{"message": "app not found"}`, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/UpdateApp", r.URL.Path) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + // Verify request body + var input UpdateAppInput + err := json.NewDecoder(r.Body).Decode(&input) + require.NoError(t, err) + assert.Equal(t, tt.input.Region, input.Region) + assert.Equal(t, tt.input.App.Key.Organization, input.App.Key.Organization) + + w.WriteHeader(tt.mockStatusCode) + w.Write([]byte(tt.mockResponse)) + })) + defer server.Close() + + // Create client + client := NewClient(server.URL, + WithHTTPClient(&http.Client{Timeout: 5 * time.Second}), + WithAuthProvider(NewStaticTokenProvider("test-token")), + ) + + // Execute test + ctx := context.Background() + err := client.UpdateApp(ctx, tt.input) + + // Verify results + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestDeleteApp(t *testing.T) { + tests := []struct { + name string + appKey AppKey + region string + mockStatusCode int + expectError bool + }{ + { + name: "successful deletion", + appKey: AppKey{ + Organization: "testorg", + Name: "testapp", + Version: "1.0.0", + }, + region: "us-west", + mockStatusCode: 200, + expectError: false, + }, + { + name: "already deleted (404 ok)", + appKey: AppKey{ + Organization: "testorg", + Name: "testapp", + Version: "1.0.0", + }, + region: "us-west", + mockStatusCode: 404, + expectError: false, + }, + { + name: "server error", + appKey: AppKey{ + Organization: "testorg", + Name: "testapp", + Version: "1.0.0", + }, + region: "us-west", + mockStatusCode: 500, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/DeleteApp", r.URL.Path) + + w.WriteHeader(tt.mockStatusCode) + })) + defer server.Close() + + client := NewClient(server.URL) + ctx := context.Background() + + err := client.DeleteApp(ctx, tt.appKey, tt.region) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestClientOptions(t *testing.T) { + t.Run("with auth provider", func(t *testing.T) { + authProvider := NewStaticTokenProvider("test-token") + client := NewClient("https://example.com", + WithAuthProvider(authProvider), + ) + + assert.Equal(t, authProvider, client.AuthProvider) + }) + + t.Run("with custom HTTP client", func(t *testing.T) { + httpClient := &http.Client{Timeout: 10 * time.Second} + client := NewClient("https://example.com", + WithHTTPClient(httpClient), + ) + + assert.Equal(t, httpClient, client.HTTPClient) + }) + + t.Run("with retry options", func(t *testing.T) { + retryOpts := RetryOptions{MaxRetries: 5} + client := NewClient("https://example.com", + WithRetryOptions(retryOpts), + ) + + assert.Equal(t, 5, client.RetryOpts.MaxRetries) + }) +} + +func TestAPIError(t *testing.T) { + err := &APIError{ + StatusCode: 400, + Messages: []string{"validation failed", "name is required"}, + } + + assert.Contains(t, err.Error(), "validation failed") + assert.Equal(t, 400, err.StatusCode) + assert.Len(t, err.Messages, 2) +} + +// Helper function to create a test server that handles streaming JSON responses +func createStreamingJSONServer(responses []string, statusCode int) *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(statusCode) + for _, response := range responses { + w.Write([]byte(response + "\n")) + } + })) +} diff --git a/sdk/edgeconnect/v2/auth.go b/sdk/edgeconnect/v2/auth.go new file mode 100644 index 0000000..a1f33a2 --- /dev/null +++ b/sdk/edgeconnect/v2/auth.go @@ -0,0 +1,184 @@ +// ABOUTME: Authentication providers for EdgeXR Master Controller API +// ABOUTME: Supports Bearer token authentication with pluggable provider interface + +package v2 + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "sync" + "time" +) + +// AuthProvider interface for attaching authentication to requests +type AuthProvider interface { + // Attach adds authentication headers to the request + Attach(ctx context.Context, req *http.Request) error +} + +// StaticTokenProvider implements Bearer token authentication with a fixed token +type StaticTokenProvider struct { + Token string +} + +// NewStaticTokenProvider creates a new static token provider +func NewStaticTokenProvider(token string) *StaticTokenProvider { + return &StaticTokenProvider{Token: token} +} + +// Attach adds the Bearer token to the request Authorization header +func (s *StaticTokenProvider) Attach(ctx context.Context, req *http.Request) error { + if s.Token != "" { + req.Header.Set("Authorization", "Bearer "+s.Token) + } + return nil +} + +// UsernamePasswordProvider implements dynamic token retrieval using username/password +// This matches the existing client/client.go RetrieveToken implementation +type UsernamePasswordProvider struct { + BaseURL string + Username string + Password string + HTTPClient *http.Client + + // Token caching + mu sync.RWMutex + cachedToken string + tokenExpiry time.Time +} + +// NewUsernamePasswordProvider creates a new username/password auth provider +func NewUsernamePasswordProvider(baseURL, username, password string, httpClient *http.Client) *UsernamePasswordProvider { + if httpClient == nil { + httpClient = &http.Client{Timeout: 30 * time.Second} + } + + return &UsernamePasswordProvider{ + BaseURL: strings.TrimRight(baseURL, "/"), + Username: username, + Password: password, + HTTPClient: httpClient, + } +} + +// Attach retrieves a token (with caching) and adds it to the Authorization header +func (u *UsernamePasswordProvider) Attach(ctx context.Context, req *http.Request) error { + token, err := u.getToken(ctx) + if err != nil { + return fmt.Errorf("failed to get token: %w", err) + } + + if token != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + + return nil +} + +// getToken retrieves a token, using cache if valid +func (u *UsernamePasswordProvider) getToken(ctx context.Context) (string, error) { + // Check cache first + u.mu.RLock() + if u.cachedToken != "" && time.Now().Before(u.tokenExpiry) { + token := u.cachedToken + u.mu.RUnlock() + return token, nil + } + u.mu.RUnlock() + + // Need to retrieve new token + u.mu.Lock() + defer u.mu.Unlock() + + // Double-check after acquiring write lock + if u.cachedToken != "" && time.Now().Before(u.tokenExpiry) { + return u.cachedToken, nil + } + + // Retrieve token using existing RetrieveToken logic + token, err := u.retrieveToken(ctx) + if err != nil { + return "", err + } + + // Cache token with reasonable expiry (assume 1 hour, can be configurable) + u.cachedToken = token + u.tokenExpiry = time.Now().Add(1 * time.Hour) + + return token, nil +} + +// retrieveToken implements the same logic as the existing client/client.go RetrieveToken method +func (u *UsernamePasswordProvider) retrieveToken(ctx context.Context) (string, error) { + // Marshal credentials - same as existing implementation + jsonData, err := json.Marshal(map[string]string{ + "username": u.Username, + "password": u.Password, + }) + if err != nil { + return "", err + } + + // Create request - same as existing implementation + loginURL := u.BaseURL + "/api/v1/login" + request, err := http.NewRequestWithContext(ctx, "POST", loginURL, bytes.NewBuffer(jsonData)) + if err != nil { + return "", err + } + request.Header.Set("Content-Type", "application/json") + + // Execute request + resp, err := u.HTTPClient.Do(request) + if err != nil { + return "", err + } + defer resp.Body.Close() + + // Read response body - same as existing implementation + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("error reading response body: %v", err) + } + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("login failed with status %d: %s", resp.StatusCode, string(body)) + } + + // Parse JSON response - same as existing implementation + var respData struct { + Token string `json:"token"` + } + err = json.Unmarshal(body, &respData) + if err != nil { + return "", fmt.Errorf("error parsing JSON (status %d): %v", resp.StatusCode, err) + } + + return respData.Token, nil +} + +// InvalidateToken clears the cached token, forcing a new login on next request +func (u *UsernamePasswordProvider) InvalidateToken() { + u.mu.Lock() + defer u.mu.Unlock() + u.cachedToken = "" + u.tokenExpiry = time.Time{} +} + +// NoAuthProvider implements no authentication (for testing or public endpoints) +type NoAuthProvider struct{} + +// NewNoAuthProvider creates a new no-auth provider +func NewNoAuthProvider() *NoAuthProvider { + return &NoAuthProvider{} +} + +// Attach does nothing (no authentication) +func (n *NoAuthProvider) Attach(ctx context.Context, req *http.Request) error { + return nil +} diff --git a/sdk/edgeconnect/v2/auth_test.go b/sdk/edgeconnect/v2/auth_test.go new file mode 100644 index 0000000..0fc5b24 --- /dev/null +++ b/sdk/edgeconnect/v2/auth_test.go @@ -0,0 +1,226 @@ +// ABOUTME: Unit tests for authentication providers including username/password token flow +// ABOUTME: Tests token caching, login flow, and error conditions with mock servers + +package v2 + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStaticTokenProvider(t *testing.T) { + provider := NewStaticTokenProvider("test-token-123") + + req, _ := http.NewRequest("GET", "https://example.com", nil) + ctx := context.Background() + + err := provider.Attach(ctx, req) + + require.NoError(t, err) + assert.Equal(t, "Bearer test-token-123", req.Header.Get("Authorization")) +} + +func TestStaticTokenProvider_EmptyToken(t *testing.T) { + provider := NewStaticTokenProvider("") + + req, _ := http.NewRequest("GET", "https://example.com", nil) + ctx := context.Background() + + err := provider.Attach(ctx, req) + + require.NoError(t, err) + assert.Empty(t, req.Header.Get("Authorization")) +} + +func TestUsernamePasswordProvider_Success(t *testing.T) { + // Mock login server + loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/login", r.URL.Path) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + // Verify request body + var creds map[string]string + err := json.NewDecoder(r.Body).Decode(&creds) + require.NoError(t, err) + assert.Equal(t, "testuser", creds["username"]) + assert.Equal(t, "testpass", creds["password"]) + + // Return token + response := map[string]string{"token": "dynamic-token-456"} + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + })) + defer loginServer.Close() + + provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil) + + req, _ := http.NewRequest("GET", "https://api.example.com", nil) + ctx := context.Background() + + err := provider.Attach(ctx, req) + + require.NoError(t, err) + assert.Equal(t, "Bearer dynamic-token-456", req.Header.Get("Authorization")) +} + +func TestUsernamePasswordProvider_LoginFailure(t *testing.T) { + // Mock login server that returns error + loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Invalid credentials")) + })) + defer loginServer.Close() + + provider := NewUsernamePasswordProvider(loginServer.URL, "baduser", "badpass", nil) + + req, _ := http.NewRequest("GET", "https://api.example.com", nil) + ctx := context.Background() + + err := provider.Attach(ctx, req) + + require.Error(t, err) + assert.Contains(t, err.Error(), "login failed with status 401") + assert.Contains(t, err.Error(), "Invalid credentials") +} + +func TestUsernamePasswordProvider_TokenCaching(t *testing.T) { + callCount := 0 + + // Mock login server that tracks calls + loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callCount++ + response := map[string]string{"token": "cached-token-789"} + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + })) + defer loginServer.Close() + + provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil) + ctx := context.Background() + + // First request should call login + req1, _ := http.NewRequest("GET", "https://api.example.com", nil) + err1 := provider.Attach(ctx, req1) + require.NoError(t, err1) + assert.Equal(t, "Bearer cached-token-789", req1.Header.Get("Authorization")) + assert.Equal(t, 1, callCount) + + // Second request should use cached token (no additional login call) + req2, _ := http.NewRequest("GET", "https://api.example.com", nil) + err2 := provider.Attach(ctx, req2) + require.NoError(t, err2) + assert.Equal(t, "Bearer cached-token-789", req2.Header.Get("Authorization")) + assert.Equal(t, 1, callCount) // Still only 1 call +} + +func TestUsernamePasswordProvider_TokenExpiry(t *testing.T) { + callCount := 0 + + loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callCount++ + response := map[string]string{"token": "refreshed-token-999"} + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + })) + defer loginServer.Close() + + provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil) + + // Manually set expired token + provider.mu.Lock() + provider.cachedToken = "expired-token" + provider.tokenExpiry = time.Now().Add(-1 * time.Hour) // Already expired + provider.mu.Unlock() + + ctx := context.Background() + req, _ := http.NewRequest("GET", "https://api.example.com", nil) + + err := provider.Attach(ctx, req) + + require.NoError(t, err) + assert.Equal(t, "Bearer refreshed-token-999", req.Header.Get("Authorization")) + assert.Equal(t, 1, callCount) // New token retrieved +} + +func TestUsernamePasswordProvider_InvalidateToken(t *testing.T) { + callCount := 0 + + loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callCount++ + response := map[string]string{"token": "new-token-after-invalidation"} + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + })) + defer loginServer.Close() + + provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil) + ctx := context.Background() + + // First request to get token + req1, _ := http.NewRequest("GET", "https://api.example.com", nil) + err1 := provider.Attach(ctx, req1) + require.NoError(t, err1) + assert.Equal(t, 1, callCount) + + // Invalidate token + provider.InvalidateToken() + + // Next request should get new token + req2, _ := http.NewRequest("GET", "https://api.example.com", nil) + err2 := provider.Attach(ctx, req2) + require.NoError(t, err2) + assert.Equal(t, "Bearer new-token-after-invalidation", req2.Header.Get("Authorization")) + assert.Equal(t, 2, callCount) // New login call made +} + +func TestUsernamePasswordProvider_BadJSONResponse(t *testing.T) { + // Mock server returning invalid JSON + loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte("invalid json response")) + })) + defer loginServer.Close() + + provider := NewUsernamePasswordProvider(loginServer.URL, "testuser", "testpass", nil) + + req, _ := http.NewRequest("GET", "https://api.example.com", nil) + ctx := context.Background() + + err := provider.Attach(ctx, req) + + require.Error(t, err) + assert.Contains(t, err.Error(), "error parsing JSON") +} + +func TestNoAuthProvider(t *testing.T) { + provider := NewNoAuthProvider() + + req, _ := http.NewRequest("GET", "https://example.com", nil) + ctx := context.Background() + + err := provider.Attach(ctx, req) + + require.NoError(t, err) + assert.Empty(t, req.Header.Get("Authorization")) +} + +func TestNewClientWithCredentials(t *testing.T) { + client := NewClientWithCredentials("https://example.com", "testuser", "testpass") + + assert.Equal(t, "https://example.com", client.BaseURL) + + // Check that auth provider is UsernamePasswordProvider + authProvider, ok := client.AuthProvider.(*UsernamePasswordProvider) + require.True(t, ok, "AuthProvider should be UsernamePasswordProvider") + assert.Equal(t, "testuser", authProvider.Username) + assert.Equal(t, "testpass", authProvider.Password) + assert.Equal(t, "https://example.com", authProvider.BaseURL) +} diff --git a/sdk/edgeconnect/v2/client.go b/sdk/edgeconnect/v2/client.go new file mode 100644 index 0000000..6846b83 --- /dev/null +++ b/sdk/edgeconnect/v2/client.go @@ -0,0 +1,122 @@ +// ABOUTME: Core EdgeXR Master Controller SDK client with HTTP transport and auth +// ABOUTME: Provides typed APIs for app, instance, and cloudlet management operations + +package v2 + +import ( + "net/http" + "strings" + "time" +) + +// Client represents the EdgeXR Master Controller SDK client +type Client struct { + BaseURL string + HTTPClient *http.Client + AuthProvider AuthProvider + RetryOpts RetryOptions + Logger Logger +} + +// RetryOptions configures retry behavior for API calls +type RetryOptions struct { + MaxRetries int + InitialDelay time.Duration + MaxDelay time.Duration + Multiplier float64 + RetryableHTTPStatusCodes []int +} + +// Logger interface for optional logging +type Logger interface { + Printf(format string, v ...interface{}) +} + +// DefaultRetryOptions returns sensible default retry configuration +func DefaultRetryOptions() RetryOptions { + return RetryOptions{ + MaxRetries: 3, + InitialDelay: 1 * time.Second, + MaxDelay: 30 * time.Second, + Multiplier: 2.0, + RetryableHTTPStatusCodes: []int{ + http.StatusRequestTimeout, + http.StatusTooManyRequests, + http.StatusInternalServerError, + http.StatusBadGateway, + http.StatusServiceUnavailable, + http.StatusGatewayTimeout, + }, + } +} + +// Option represents a configuration option for the client +type Option func(*Client) + +// WithHTTPClient sets a custom HTTP client +func WithHTTPClient(client *http.Client) Option { + return func(c *Client) { + c.HTTPClient = client + } +} + +// WithAuthProvider sets the authentication provider +func WithAuthProvider(auth AuthProvider) Option { + return func(c *Client) { + c.AuthProvider = auth + } +} + +// WithRetryOptions sets retry configuration +func WithRetryOptions(opts RetryOptions) Option { + return func(c *Client) { + c.RetryOpts = opts + } +} + +// WithLogger sets a logger for debugging +func WithLogger(logger Logger) Option { + return func(c *Client) { + c.Logger = logger + } +} + +// NewClient creates a new EdgeXR SDK client +func NewClient(baseURL string, options ...Option) *Client { + client := &Client{ + BaseURL: strings.TrimRight(baseURL, "/"), + HTTPClient: &http.Client{Timeout: 30 * time.Second}, + AuthProvider: NewNoAuthProvider(), + RetryOpts: DefaultRetryOptions(), + } + + for _, opt := range options { + opt(client) + } + + return client +} + +// NewClientWithCredentials creates a new EdgeXR SDK client with username/password authentication +// This matches the existing client pattern from client/client.go +func NewClientWithCredentials(baseURL, username, password string, options ...Option) *Client { + client := &Client{ + BaseURL: strings.TrimRight(baseURL, "/"), + HTTPClient: &http.Client{Timeout: 30 * time.Second}, + AuthProvider: NewUsernamePasswordProvider(baseURL, username, password, nil), + RetryOpts: DefaultRetryOptions(), + } + + for _, opt := range options { + opt(client) + } + + return client +} + +// logf logs a message if a logger is configured +func (c *Client) logf(format string, v ...interface{}) { + if c.Logger != nil { + c.Logger.Printf(format, v...) + } +} diff --git a/sdk/edgeconnect/v2/cloudlet.go b/sdk/edgeconnect/v2/cloudlet.go new file mode 100644 index 0000000..85ef522 --- /dev/null +++ b/sdk/edgeconnect/v2/cloudlet.go @@ -0,0 +1,271 @@ +// ABOUTME: Cloudlet management APIs for EdgeXR Master Controller +// ABOUTME: Provides typed methods for creating, querying, and managing edge cloudlets + +package v2 + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/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 +} diff --git a/sdk/edgeconnect/v2/cloudlet_test.go b/sdk/edgeconnect/v2/cloudlet_test.go new file mode 100644 index 0000000..8f2cc06 --- /dev/null +++ b/sdk/edgeconnect/v2/cloudlet_test.go @@ -0,0 +1,408 @@ +// ABOUTME: Unit tests for Cloudlet management APIs using httptest mock server +// ABOUTME: Tests create, show, list, delete, manifest, and resource usage operations + +package v2 + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCreateCloudlet(t *testing.T) { + tests := []struct { + name string + input *NewCloudletInput + mockStatusCode int + mockResponse string + expectError bool + }{ + { + name: "successful creation", + input: &NewCloudletInput{ + Region: "us-west", + Cloudlet: Cloudlet{ + Key: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + Location: Location{ + Latitude: 37.7749, + Longitude: -122.4194, + }, + IpSupport: "IpSupportDynamic", + NumDynamicIps: 10, + }, + }, + mockStatusCode: 200, + mockResponse: `{"message": "success"}`, + expectError: false, + }, + { + name: "validation error", + input: &NewCloudletInput{ + Region: "us-west", + Cloudlet: Cloudlet{ + Key: CloudletKey{ + Organization: "", + Name: "testcloudlet", + }, + }, + }, + mockStatusCode: 400, + mockResponse: `{"message": "organization is required"}`, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/CreateCloudlet", r.URL.Path) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + w.WriteHeader(tt.mockStatusCode) + w.Write([]byte(tt.mockResponse)) + })) + defer server.Close() + + // Create client + client := NewClient(server.URL, + WithHTTPClient(&http.Client{Timeout: 5 * time.Second}), + WithAuthProvider(NewStaticTokenProvider("test-token")), + ) + + // Execute test + ctx := context.Background() + err := client.CreateCloudlet(ctx, tt.input) + + // Verify results + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestShowCloudlet(t *testing.T) { + tests := []struct { + name string + cloudletKey CloudletKey + region string + mockStatusCode int + mockResponse string + expectError bool + expectNotFound bool + }{ + { + name: "successful show", + cloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + region: "us-west", + mockStatusCode: 200, + mockResponse: `{"data": {"key": {"organization": "cloudletorg", "name": "testcloudlet"}, "state": "Ready", "location": {"latitude": 37.7749, "longitude": -122.4194}}} +`, + expectError: false, + expectNotFound: false, + }, + { + name: "cloudlet not found", + cloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "nonexistent", + }, + region: "us-west", + mockStatusCode: 404, + mockResponse: "", + expectError: true, + expectNotFound: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/ShowCloudlet", r.URL.Path) + + w.WriteHeader(tt.mockStatusCode) + if tt.mockResponse != "" { + w.Write([]byte(tt.mockResponse)) + } + })) + defer server.Close() + + // Create client + client := NewClient(server.URL, + WithHTTPClient(&http.Client{Timeout: 5 * time.Second}), + ) + + // Execute test + ctx := context.Background() + cloudlet, err := client.ShowCloudlet(ctx, tt.cloudletKey, tt.region) + + // Verify results + if tt.expectError { + assert.Error(t, err) + if tt.expectNotFound { + assert.Contains(t, err.Error(), "resource not found") + } + } else { + require.NoError(t, err) + assert.Equal(t, tt.cloudletKey.Organization, cloudlet.Key.Organization) + assert.Equal(t, tt.cloudletKey.Name, cloudlet.Key.Name) + assert.Equal(t, "Ready", cloudlet.State) + } + }) + } +} + +func TestShowCloudlets(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/ShowCloudlet", r.URL.Path) + + // Verify request body + var filter CloudletFilter + err := json.NewDecoder(r.Body).Decode(&filter) + require.NoError(t, err) + assert.Equal(t, "cloudletorg", filter.Cloudlet.Key.Organization) + assert.Equal(t, "us-west", filter.Region) + + // Return multiple cloudlets + response := `{"data": {"key": {"organization": "cloudletorg", "name": "cloudlet1"}, "state": "Ready"}} +{"data": {"key": {"organization": "cloudletorg", "name": "cloudlet2"}, "state": "Creating"}} +` + w.WriteHeader(200) + w.Write([]byte(response)) + })) + defer server.Close() + + client := NewClient(server.URL) + ctx := context.Background() + + cloudlets, err := client.ShowCloudlets(ctx, CloudletKey{Organization: "cloudletorg"}, "us-west") + + require.NoError(t, err) + assert.Len(t, cloudlets, 2) + assert.Equal(t, "cloudlet1", cloudlets[0].Key.Name) + assert.Equal(t, "Ready", cloudlets[0].State) + assert.Equal(t, "cloudlet2", cloudlets[1].Key.Name) + assert.Equal(t, "Creating", cloudlets[1].State) +} + +func TestDeleteCloudlet(t *testing.T) { + tests := []struct { + name string + cloudletKey CloudletKey + region string + mockStatusCode int + expectError bool + }{ + { + name: "successful deletion", + cloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + region: "us-west", + mockStatusCode: 200, + expectError: false, + }, + { + name: "already deleted (404 ok)", + cloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + region: "us-west", + mockStatusCode: 404, + expectError: false, + }, + { + name: "server error", + cloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + region: "us-west", + mockStatusCode: 500, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/DeleteCloudlet", r.URL.Path) + + w.WriteHeader(tt.mockStatusCode) + })) + defer server.Close() + + client := NewClient(server.URL) + ctx := context.Background() + + err := client.DeleteCloudlet(ctx, tt.cloudletKey, tt.region) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestGetCloudletManifest(t *testing.T) { + tests := []struct { + name string + cloudletKey CloudletKey + region string + mockStatusCode int + mockResponse string + expectError bool + expectNotFound bool + }{ + { + name: "successful manifest retrieval", + cloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + region: "us-west", + mockStatusCode: 200, + mockResponse: `{"manifest": "apiVersion: v1\nkind: Deployment\nmetadata:\n name: test", "last_modified": "2024-01-01T00:00:00Z"}`, + expectError: false, + expectNotFound: false, + }, + { + name: "manifest not found", + cloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "nonexistent", + }, + region: "us-west", + mockStatusCode: 404, + mockResponse: "", + expectError: true, + expectNotFound: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/GetCloudletManifest", r.URL.Path) + + w.WriteHeader(tt.mockStatusCode) + if tt.mockResponse != "" { + w.Write([]byte(tt.mockResponse)) + } + })) + defer server.Close() + + client := NewClient(server.URL) + ctx := context.Background() + + manifest, err := client.GetCloudletManifest(ctx, tt.cloudletKey, tt.region) + + if tt.expectError { + assert.Error(t, err) + if tt.expectNotFound { + assert.Contains(t, err.Error(), "resource not found") + } + } else { + require.NoError(t, err) + assert.NotNil(t, manifest) + assert.Contains(t, manifest.Manifest, "apiVersion: v1") + } + }) + } +} + +func TestGetCloudletResourceUsage(t *testing.T) { + tests := []struct { + name string + cloudletKey CloudletKey + region string + mockStatusCode int + mockResponse string + expectError bool + expectNotFound bool + }{ + { + name: "successful usage retrieval", + cloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "testcloudlet", + }, + region: "us-west", + mockStatusCode: 200, + mockResponse: `{"cloudlet_key": {"organization": "cloudletorg", "name": "testcloudlet"}, "region": "us-west", "usage": {"cpu": "50%", "memory": "30%", "disk": "20%"}}`, + expectError: false, + expectNotFound: false, + }, + { + name: "usage not found", + cloudletKey: CloudletKey{ + Organization: "cloudletorg", + Name: "nonexistent", + }, + region: "us-west", + mockStatusCode: 404, + mockResponse: "", + expectError: true, + expectNotFound: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/auth/ctrl/GetCloudletResourceUsage", r.URL.Path) + + w.WriteHeader(tt.mockStatusCode) + if tt.mockResponse != "" { + w.Write([]byte(tt.mockResponse)) + } + })) + defer server.Close() + + client := NewClient(server.URL) + ctx := context.Background() + + usage, err := client.GetCloudletResourceUsage(ctx, tt.cloudletKey, tt.region) + + if tt.expectError { + assert.Error(t, err) + if tt.expectNotFound { + assert.Contains(t, err.Error(), "resource not found") + } + } else { + require.NoError(t, err) + assert.NotNil(t, usage) + assert.Equal(t, "cloudletorg", usage.CloudletKey.Organization) + assert.Equal(t, "testcloudlet", usage.CloudletKey.Name) + assert.Equal(t, "us-west", usage.Region) + assert.Contains(t, usage.Usage, "cpu") + } + }) + } +} diff --git a/sdk/edgeconnect/v2/types.go b/sdk/edgeconnect/v2/types.go new file mode 100644 index 0000000..82995e0 --- /dev/null +++ b/sdk/edgeconnect/v2/types.go @@ -0,0 +1,407 @@ +// ABOUTME: Core type definitions for EdgeXR Master Controller SDK +// ABOUTME: These types are based on the swagger API specification and existing client patterns + +package v2 + +import ( + "encoding/json" + "fmt" + "time" +) + +// App field constants for partial updates (based on EdgeXR API specification) +const ( + AppFieldKey = "2" + AppFieldKeyOrganization = "2.1" + AppFieldKeyName = "2.2" + AppFieldKeyVersion = "2.3" + AppFieldImagePath = "4" + AppFieldImageType = "5" + AppFieldAccessPorts = "7" + AppFieldDefaultFlavor = "9" + AppFieldDefaultFlavorName = "9.1" + AppFieldAuthPublicKey = "12" + AppFieldCommand = "13" + AppFieldAnnotations = "14" + AppFieldDeployment = "15" + AppFieldDeploymentManifest = "16" + AppFieldDeploymentGenerator = "17" + AppFieldAndroidPackageName = "18" + AppFieldDelOpt = "20" + AppFieldConfigs = "21" + AppFieldConfigsKind = "21.1" + AppFieldConfigsConfig = "21.2" + AppFieldScaleWithCluster = "22" + AppFieldInternalPorts = "23" + AppFieldRevision = "24" + AppFieldOfficialFqdn = "25" + AppFieldMd5Sum = "26" + AppFieldAutoProvPolicy = "28" + AppFieldAccessType = "29" + AppFieldDeletePrepare = "31" + AppFieldAutoProvPolicies = "32" + AppFieldTemplateDelimiter = "33" + AppFieldSkipHcPorts = "34" + AppFieldCreatedAt = "35" + AppFieldCreatedAtSeconds = "35.1" + AppFieldCreatedAtNanos = "35.2" + AppFieldUpdatedAt = "36" + AppFieldUpdatedAtSeconds = "36.1" + AppFieldUpdatedAtNanos = "36.2" + AppFieldTrusted = "37" + AppFieldRequiredOutboundConnections = "38" + AppFieldAllowServerless = "39" + AppFieldServerlessConfig = "40" + AppFieldVmAppOsType = "41" + AppFieldAlertPolicies = "42" + AppFieldQosSessionProfile = "43" + AppFieldQosSessionDuration = "44" +) + +// 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" + 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" +) + +// Message interface for types that can provide error messages +type Message interface { + GetMessage() string +} + +// Base message type for API responses +type msg struct { + Message string `json:"message,omitempty"` +} + +func (m msg) GetMessage() string { + return m.Message +} + +// AppKey uniquely identifies an application +type AppKey struct { + Organization string `json:"organization"` + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` +} + +// CloudletKey uniquely identifies a cloudlet +type CloudletKey struct { + Organization string `json:"organization"` + Name string `json:"name"` +} + +// AppInstanceKey uniquely identifies an application instance +type AppInstanceKey struct { + Organization string `json:"organization"` + Name string `json:"name"` + CloudletKey CloudletKey `json:"cloudlet_key"` +} + +// Flavor defines resource allocation for instances +type Flavor struct { + Name string `json:"name"` +} + +// SecurityRule defines network access rules +type SecurityRule struct { + PortRangeMax int `json:"port_range_max"` + PortRangeMin int `json:"port_range_min"` + Protocol string `json:"protocol"` + RemoteCIDR string `json:"remote_cidr"` +} + +// App represents an application definition +type App struct { + msg `json:",inline"` + Key AppKey `json:"key"` + Deployment string `json:"deployment,omitempty"` + ImageType string `json:"image_type,omitempty"` + ImagePath string `json:"image_path,omitempty"` + AccessPorts string `json:"access_ports,omitempty"` + AllowServerless bool `json:"allow_serverless,omitempty"` + DefaultFlavor Flavor `json:"defaultFlavor,omitempty"` + ServerlessConfig interface{} `json:"serverless_config,omitempty"` + DeploymentGenerator string `json:"deployment_generator,omitempty"` + DeploymentManifest string `json:"deployment_manifest,omitempty"` + RequiredOutboundConnections []SecurityRule `json:"required_outbound_connections"` + GlobalID string `json:"global_id,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + Fields []string `json:"fields,omitempty"` +} + +// AppInstance represents a deployed application instance +type AppInstance struct { + msg `json:",inline"` + Key AppInstanceKey `json:"key"` + AppKey AppKey `json:"app_key,omitempty"` + CloudletLoc CloudletLoc `json:"cloudlet_loc,omitempty"` + Flavor Flavor `json:"flavor,omitempty"` + State string `json:"state,omitempty"` + IngressURL string `json:"ingress_url,omitempty"` + UniqueID string `json:"unique_id,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + PowerState string `json:"power_state,omitempty"` + Fields []string `json:"fields,omitempty"` +} + +// Cloudlet represents edge infrastructure +type Cloudlet struct { + msg `json:",inline"` + Key CloudletKey `json:"key"` + Location Location `json:"location"` + IpSupport string `json:"ip_support,omitempty"` + NumDynamicIps int32 `json:"num_dynamic_ips,omitempty"` + State string `json:"state,omitempty"` + Flavor Flavor `json:"flavor,omitempty"` + PhysicalName string `json:"physical_name,omitempty"` + Region string `json:"region,omitempty"` + NotifySrvAddr string `json:"notify_srv_addr,omitempty"` +} + +// Location represents geographical coordinates +type Location struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` +} + +// CloudletLoc represents geographical coordinates for cloudlets +type CloudletLoc struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` +} + +// Input types for API operations + +// NewAppInput represents input for creating an application +type NewAppInput struct { + Region string `json:"region"` + App App `json:"app"` +} + +// NewAppInstanceInput represents input for creating an app instance +type NewAppInstanceInput struct { + Region string `json:"region"` + AppInst AppInstance `json:"appinst"` +} + +// NewCloudletInput represents input for creating a cloudlet +type NewCloudletInput struct { + Region string `json:"region"` + Cloudlet Cloudlet `json:"cloudlet"` +} + +// UpdateAppInput represents input for updating an application +type UpdateAppInput struct { + Region string `json:"region"` + App App `json:"app"` +} + +// UpdateAppInstanceInput represents input for updating an app instance +type UpdateAppInstanceInput struct { + Region string `json:"region"` + AppInst AppInstance `json:"appinst"` +} + +// DeleteAppInput represents input for deleting an application +type DeleteAppInput struct { + Key AppKey `json:"key"` + Region string `json:"region"` +} + +// DeleteAppInstanceInput represents input for deleting an app instance +type DeleteAppInstanceInput struct { + Key AppInstanceKey `json:"key"` +} + +// Response wrapper types + +// Response wraps a single API response +type Response[T Message] struct { + Data T `json:"data"` +} + +func (res *Response[T]) HasData() bool { + return !res.IsMessage() +} + +func (res *Response[T]) IsMessage() bool { + return res.Data.GetMessage() != "" +} + +// ResultResponse represents an API result with error code +type ResultResponse struct { + Result struct { + Message string `json:"message"` + Code int `json:"code"` + } `json:"result"` +} + +func (r *ResultResponse) IsError() bool { + return r.Result.Code >= 400 +} + +func (r *ResultResponse) GetMessage() string { + return r.Result.Message +} + +func (r *ResultResponse) GetCode() int { + return r.Result.Code +} + +// Responses wraps multiple API responses with metadata +type Responses[T Message] struct { + Responses []Response[T] `json:"responses,omitempty"` + StatusCode int `json:"-"` +} + +func (r *Responses[T]) GetData() []T { + var data []T + for _, v := range r.Responses { + if v.HasData() { + data = append(data, v.Data) + } + } + return data +} + +func (r *Responses[T]) GetMessages() []string { + var messages []string + for _, v := range r.Responses { + if v.IsMessage() { + messages = append(messages, v.Data.GetMessage()) + } + } + return messages +} + +func (r *Responses[T]) IsSuccessful() bool { + return r.StatusCode >= 200 && r.StatusCode < 400 +} + +func (r *Responses[T]) Error() error { + if r.IsSuccessful() { + return nil + } + return &APIError{ + StatusCode: r.StatusCode, + Messages: r.GetMessages(), + } +} + +// APIError represents an API error with details +type APIError struct { + StatusCode int `json:"status_code"` + Code string `json:"code,omitempty"` + Messages []string `json:"messages,omitempty"` + Body []byte `json:"-"` +} + +func (e *APIError) Error() string { + jsonErr, err := json.Marshal(e) + if err != nil { + return fmt.Sprintf("API error: %v", err) + } + return fmt.Sprintf("API error: %s", jsonErr) +} + +// Filter types for querying + +// AppFilter represents filters for app queries +type AppFilter struct { + App App `json:"app"` + Region string `json:"region"` +} + +// AppInstanceFilter represents filters for app instance queries +type AppInstanceFilter struct { + AppInstance AppInstance `json:"appinst"` + Region string `json:"region"` +} + +// CloudletFilter represents filters for cloudlet queries +type CloudletFilter struct { + Cloudlet Cloudlet `json:"cloudlet"` + Region string `json:"region"` +} + +// CloudletManifest represents cloudlet deployment manifest +type CloudletManifest struct { + Manifest string `json:"manifest"` + LastModified time.Time `json:"last_modified,omitempty"` +} + +// CloudletResourceUsage represents cloudlet resource utilization +type CloudletResourceUsage struct { + CloudletKey CloudletKey `json:"cloudlet_key"` + Region string `json:"region"` + Usage map[string]interface{} `json:"usage"` +} diff --git a/sdk/examples/comprehensive/main.go b/sdk/examples/comprehensive/main.go index 616279f..d3fb922 100644 --- a/sdk/examples/comprehensive/main.go +++ b/sdk/examples/comprehensive/main.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" ) func main() { @@ -24,20 +24,20 @@ func main() { username := getEnvOrDefault("EDGEXR_USERNAME", "") password := getEnvOrDefault("EDGEXR_PASSWORD", "") - var client *edgeconnect.Client + var client *v2.Client if token != "" { fmt.Println("🔐 Using Bearer token authentication") - client = edgeconnect.NewClient(baseURL, - edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), - edgeconnect.WithAuthProvider(edgeconnect.NewStaticTokenProvider(token)), - edgeconnect.WithLogger(log.Default()), + client = v2.NewClient(baseURL, + v2.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), + v2.WithAuthProvider(v2.NewStaticTokenProvider(token)), + v2.WithLogger(log.Default()), ) } else if username != "" && password != "" { fmt.Println("🔐 Using username/password authentication") - client = edgeconnect.NewClientWithCredentials(baseURL, username, password, - edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), - edgeconnect.WithLogger(log.Default()), + client = v2.NewClientWithCredentials(baseURL, username, password, + v2.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), + v2.WithLogger(log.Default()), ) } else { log.Fatal("Authentication required: Set either EDGEXR_TOKEN or both EDGEXR_USERNAME and EDGEXR_PASSWORD") @@ -85,15 +85,15 @@ type WorkflowConfig struct { FlavorName string } -func runComprehensiveWorkflow(ctx context.Context, c *edgeconnect.Client, config WorkflowConfig) error { +func runComprehensiveWorkflow(ctx context.Context, c *v2.Client, config WorkflowConfig) error { fmt.Println("═══ Phase 1: Application Management ═══") // 1. Create Application fmt.Println("\n1️⃣ Creating application...") - app := &edgeconnect.NewAppInput{ + app := &v2.NewAppInput{ Region: config.Region, - App: edgeconnect.App{ - Key: edgeconnect.AppKey{ + App: v2.App{ + Key: v2.AppKey{ Organization: config.Organization, Name: config.AppName, Version: config.AppVersion, @@ -101,10 +101,10 @@ func runComprehensiveWorkflow(ctx context.Context, c *edgeconnect.Client, config Deployment: "kubernetes", ImageType: "ImageTypeDocker", // field is ignored ImagePath: "https://registry-1.docker.io/library/nginx:latest", // must be set. Even for kubernetes - DefaultFlavor: edgeconnect.Flavor{Name: config.FlavorName}, + DefaultFlavor: v2.Flavor{Name: config.FlavorName}, ServerlessConfig: struct{}{}, // must be set AllowServerless: true, // must be set to true for kubernetes - RequiredOutboundConnections: []edgeconnect.SecurityRule{ + RequiredOutboundConnections: []v2.SecurityRule{ { Protocol: "tcp", PortRangeMin: 80, @@ -128,7 +128,7 @@ func runComprehensiveWorkflow(ctx context.Context, c *edgeconnect.Client, config // 2. Show Application Details fmt.Println("\n2️⃣ Querying application details...") - appKey := edgeconnect.AppKey{ + appKey := v2.AppKey{ Organization: config.Organization, Name: config.AppName, Version: config.AppVersion, @@ -146,7 +146,7 @@ func runComprehensiveWorkflow(ctx context.Context, c *edgeconnect.Client, config // 3. List Applications in Organization fmt.Println("\n3️⃣ Listing applications in organization...") - filter := edgeconnect.AppKey{Organization: config.Organization} + filter := v2.AppKey{Organization: config.Organization} apps, err := c.ShowApps(ctx, filter, config.Region) if err != nil { return fmt.Errorf("failed to list apps: %w", err) @@ -160,19 +160,19 @@ func runComprehensiveWorkflow(ctx context.Context, c *edgeconnect.Client, config // 4. Create Application Instance fmt.Println("\n4️⃣ Creating application instance...") - instance := &edgeconnect.NewAppInstanceInput{ + instance := &v2.NewAppInstanceInput{ Region: config.Region, - AppInst: edgeconnect.AppInstance{ - Key: edgeconnect.AppInstanceKey{ + AppInst: v2.AppInstance{ + Key: v2.AppInstanceKey{ Organization: config.Organization, Name: config.InstanceName, - CloudletKey: edgeconnect.CloudletKey{ + CloudletKey: v2.CloudletKey{ Organization: config.CloudletOrg, Name: config.CloudletName, }, }, AppKey: appKey, - Flavor: edgeconnect.Flavor{Name: config.FlavorName}, + Flavor: v2.Flavor{Name: config.FlavorName}, }, } @@ -184,10 +184,10 @@ func runComprehensiveWorkflow(ctx context.Context, c *edgeconnect.Client, config // 5. Wait for Application Instance to be Ready fmt.Println("\n5️⃣ Waiting for application instance to be ready...") - instanceKey := edgeconnect.AppInstanceKey{ + instanceKey := v2.AppInstanceKey{ Organization: config.Organization, Name: config.InstanceName, - CloudletKey: edgeconnect.CloudletKey{ + CloudletKey: v2.CloudletKey{ Organization: config.CloudletOrg, Name: config.CloudletName, }, @@ -207,7 +207,7 @@ func runComprehensiveWorkflow(ctx context.Context, c *edgeconnect.Client, config // 6. List Application Instances fmt.Println("\n6️⃣ Listing application instances...") - instances, err := c.ShowAppInstances(ctx, edgeconnect.AppInstanceKey{Organization: config.Organization}, config.Region) + instances, err := c.ShowAppInstances(ctx, v2.AppInstanceKey{Organization: config.Organization}, config.Region) if err != nil { return fmt.Errorf("failed to list app instances: %w", err) } @@ -228,7 +228,7 @@ func runComprehensiveWorkflow(ctx context.Context, c *edgeconnect.Client, config // 8. Show Cloudlet Details fmt.Println("\n8️⃣ Querying cloudlet information...") - cloudletKey := edgeconnect.CloudletKey{ + cloudletKey := v2.CloudletKey{ Organization: config.CloudletOrg, Name: config.CloudletName, } @@ -287,7 +287,7 @@ func runComprehensiveWorkflow(ctx context.Context, c *edgeconnect.Client, config // 13. Verify Cleanup fmt.Println("\n1️⃣3️⃣ Verifying cleanup...") _, err = c.ShowApp(ctx, appKey, config.Region) - if err != nil && fmt.Sprintf("%v", err) == edgeconnect.ErrResourceNotFound.Error() { + if err != nil && fmt.Sprintf("%v", err) == v2.ErrResourceNotFound.Error() { fmt.Printf("✅ Cleanup verified - app no longer exists\n") } else if err != nil { fmt.Printf("✅ Cleanup appears successful (verification returned: %v)\n", err) @@ -306,7 +306,7 @@ func getEnvOrDefault(key, defaultValue string) string { } // waitForInstanceReady polls the instance status until it's no longer "Creating" or timeout -func waitForInstanceReady(ctx context.Context, c *edgeconnect.Client, instanceKey edgeconnect.AppInstanceKey, region string, timeout time.Duration) (edgeconnect.AppInstance, error) { +func waitForInstanceReady(ctx context.Context, c *v2.Client, instanceKey v2.AppInstanceKey, region string, timeout time.Duration) (v2.AppInstance, error) { timeoutCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() @@ -318,7 +318,7 @@ func waitForInstanceReady(ctx context.Context, c *edgeconnect.Client, instanceKe for { select { case <-timeoutCtx.Done(): - return edgeconnect.AppInstance{}, fmt.Errorf("timeout waiting for instance to be ready after %v", timeout) + return v2.AppInstance{}, fmt.Errorf("timeout waiting for instance to be ready after %v", timeout) case <-ticker.C: instance, err := c.ShowAppInstance(timeoutCtx, instanceKey, region) diff --git a/sdk/examples/deploy_app.go b/sdk/examples/deploy_app.go index b413886..84297dc 100644 --- a/sdk/examples/deploy_app.go +++ b/sdk/examples/deploy_app.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" ) func main() { @@ -24,22 +24,22 @@ func main() { username := getEnvOrDefault("EDGEXR_USERNAME", "") password := getEnvOrDefault("EDGEXR_PASSWORD", "") - var edgeClient *edgeconnect.Client + var edgeClient *v2.Client if token != "" { // Use static token authentication fmt.Println("🔐 Using Bearer token authentication") - edgeClient = edgeconnect.NewClient(baseURL, - edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), - edgeconnect.WithAuthProvider(edgeconnect.NewStaticTokenProvider(token)), - edgeconnect.WithLogger(log.Default()), + edgeClient = v2.NewClient(baseURL, + v2.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), + v2.WithAuthProvider(v2.NewStaticTokenProvider(token)), + v2.WithLogger(log.Default()), ) } else if username != "" && password != "" { // Use username/password authentication (matches existing client pattern) fmt.Println("🔐 Using username/password authentication") - edgeClient = edgeconnect.NewClientWithCredentials(baseURL, username, password, - edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), - edgeconnect.WithLogger(log.Default()), + edgeClient = v2.NewClientWithCredentials(baseURL, username, password, + v2.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), + v2.WithLogger(log.Default()), ) } else { log.Fatal("Authentication required: Set either EDGEXR_TOKEN or both EDGEXR_USERNAME and EDGEXR_PASSWORD") @@ -48,10 +48,10 @@ func main() { ctx := context.Background() // Example application to deploy - app := &edgeconnect.NewAppInput{ + app := &v2.NewAppInput{ Region: "EU", - App: edgeconnect.App{ - Key: edgeconnect.AppKey{ + App: v2.App{ + Key: v2.AppKey{ Organization: "edp2", Name: "my-edge-app", Version: "1.0.0", @@ -59,7 +59,7 @@ func main() { Deployment: "docker", ImageType: "ImageTypeDocker", ImagePath: "https://registry-1.docker.io/library/nginx:latest", - DefaultFlavor: edgeconnect.Flavor{Name: "EU.small"}, + DefaultFlavor: v2.Flavor{Name: "EU.small"}, ServerlessConfig: struct{}{}, AllowServerless: false, }, @@ -73,7 +73,7 @@ func main() { fmt.Println("✅ SDK example completed successfully!") } -func demonstrateAppLifecycle(ctx context.Context, edgeClient *edgeconnect.Client, input *edgeconnect.NewAppInput) error { +func demonstrateAppLifecycle(ctx context.Context, edgeClient *v2.Client, input *v2.NewAppInput) error { appKey := input.App.Key region := input.Region @@ -98,7 +98,7 @@ func demonstrateAppLifecycle(ctx context.Context, edgeClient *edgeconnect.Client // Step 3: List applications in the organization fmt.Println("\n3. Listing applications...") - filter := edgeconnect.AppKey{Organization: appKey.Organization} + filter := v2.AppKey{Organization: appKey.Organization} apps, err := edgeClient.ShowApps(ctx, filter, region) if err != nil { return fmt.Errorf("failed to list apps: %w", err) @@ -116,7 +116,7 @@ func demonstrateAppLifecycle(ctx context.Context, edgeClient *edgeconnect.Client fmt.Println("\n5. Verifying deletion...") _, err = edgeClient.ShowApp(ctx, appKey, region) if err != nil { - if strings.Contains(fmt.Sprintf("%v", err), edgeconnect.ErrResourceNotFound.Error()) { + if strings.Contains(fmt.Sprintf("%v", err), v2.ErrResourceNotFound.Error()) { fmt.Printf("✅ App successfully deleted (not found)\n") } else { return fmt.Errorf("unexpected error verifying deletion: %w", err) From 2a8e99eb6366420ad5a499dd95f854dee3c83eac Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 20 Oct 2025 13:41:50 +0200 Subject: [PATCH 22/40] feat(config): add API version selector for v1 and v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add configurable API version selection with three methods: - Config file: api_version: "v1" or "v2" in .edge-connect.yaml - CLI flag: --api-version v1/v2 - Environment variable: EDGE_CONNECT_API_VERSION=v1/v2 Changes: - Update root.go to add api_version config and env var support - Update app.go and instance.go to support both v1 and v2 clients - Add example config file with api_version documentation - Default to v2 for backward compatibility - Apply command always uses v2 (advanced feature) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .edge-connect.yaml.example | 14 +++ cmd/app.go | 190 ++++++++++++++++++++++++++-------- cmd/apply.go | 4 +- cmd/instance.go | 205 ++++++++++++++++++++++++++----------- cmd/root.go | 14 ++- 5 files changed, 319 insertions(+), 108 deletions(-) create mode 100644 .edge-connect.yaml.example diff --git a/.edge-connect.yaml.example b/.edge-connect.yaml.example new file mode 100644 index 0000000..694ed1e --- /dev/null +++ b/.edge-connect.yaml.example @@ -0,0 +1,14 @@ +# Example EdgeConnect CLI Configuration File +# Place this file at ~/.edge-connect.yaml or specify with --config flag + +# Base URL for the EdgeConnect API +base_url: "https://hub.apps.edge.platform.mg3.mdb.osc.live" + +# Authentication credentials +username: "your-username@example.com" +password: "your-password" + +# API version to use (v1 or v2) +# Default: v2 +# Set via config, --api-version flag, or EDGE_CONNECT_API_VERSION env var +api_version: "v2" diff --git a/cmd/app.go b/cmd/app.go index a96f599..79fc2c5 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -7,8 +7,10 @@ import ( "net/http" "net/url" "os" + "strings" "time" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -50,7 +52,45 @@ func validateBaseURL(baseURL string) error { return nil } -func newSDKClient() *v2.Client { +func getAPIVersion() string { + version := viper.GetString("api_version") + if version == "" { + version = "v2" // default to v2 + } + return strings.ToLower(version) +} + +func newSDKClientV1() *edgeconnect.Client { + baseURL := viper.GetString("base_url") + username := viper.GetString("username") + password := viper.GetString("password") + + err := validateBaseURL(baseURL) + if err != nil { + fmt.Printf("Error parsing baseURL: '%s' with error: %s\n", baseURL, err.Error()) + os.Exit(1) + } + + // Build options + opts := []edgeconnect.Option{ + edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}), + } + + // Add logger only if debug flag is set + if debug { + logger := log.New(os.Stderr, "[DEBUG] ", log.LstdFlags) + opts = append(opts, edgeconnect.WithLogger(logger)) + } + + if username != "" && password != "" { + return edgeconnect.NewClientWithCredentials(baseURL, username, password, opts...) + } + + // Fallback to no auth for now - in production should require auth + return edgeconnect.NewClient(baseURL, opts...) +} + +func newSDKClientV2() *v2.Client { baseURL := viper.GetString("base_url") username := viper.GetString("username") password := viper.GetString("password") @@ -90,19 +130,37 @@ var createAppCmd = &cobra.Command{ Use: "create", Short: "Create a new Edge Connect application", Run: func(cmd *cobra.Command, args []string) { - c := newSDKClient() - input := &v2.NewAppInput{ - Region: region, - App: v2.App{ - Key: v2.AppKey{ - Organization: organization, - Name: appName, - Version: appVersion, + apiVersion := getAPIVersion() + var err error + + if apiVersion == "v1" { + c := newSDKClientV1() + input := &edgeconnect.NewAppInput{ + Region: region, + App: edgeconnect.App{ + Key: edgeconnect.AppKey{ + Organization: organization, + Name: appName, + Version: appVersion, + }, }, - }, + } + err = c.CreateApp(context.Background(), input) + } else { + c := newSDKClientV2() + input := &v2.NewAppInput{ + Region: region, + App: v2.App{ + Key: v2.AppKey{ + Organization: organization, + Name: appName, + Version: appVersion, + }, + }, + } + err = c.CreateApp(context.Background(), input) } - err := c.CreateApp(context.Background(), input) if err != nil { fmt.Printf("Error creating app: %v\n", err) os.Exit(1) @@ -115,19 +173,35 @@ var showAppCmd = &cobra.Command{ Use: "show", Short: "Show details of an Edge Connect application", Run: func(cmd *cobra.Command, args []string) { - c := newSDKClient() - appKey := v2.AppKey{ - Organization: organization, - Name: appName, - Version: appVersion, - } + apiVersion := getAPIVersion() - app, err := c.ShowApp(context.Background(), appKey, region) - if err != nil { - fmt.Printf("Error showing app: %v\n", err) - os.Exit(1) + if apiVersion == "v1" { + c := newSDKClientV1() + appKey := edgeconnect.AppKey{ + Organization: organization, + Name: appName, + Version: appVersion, + } + app, err := c.ShowApp(context.Background(), appKey, region) + if err != nil { + fmt.Printf("Error showing app: %v\n", err) + os.Exit(1) + } + fmt.Printf("Application details:\n%+v\n", app) + } else { + c := newSDKClientV2() + appKey := v2.AppKey{ + Organization: organization, + Name: appName, + Version: appVersion, + } + app, err := c.ShowApp(context.Background(), appKey, region) + if err != nil { + fmt.Printf("Error showing app: %v\n", err) + os.Exit(1) + } + fmt.Printf("Application details:\n%+v\n", app) } - fmt.Printf("Application details:\n%+v\n", app) }, } @@ -135,21 +209,40 @@ var listAppsCmd = &cobra.Command{ Use: "list", Short: "List Edge Connect applications", Run: func(cmd *cobra.Command, args []string) { - c := newSDKClient() - appKey := v2.AppKey{ - Organization: organization, - Name: appName, - Version: appVersion, - } + apiVersion := getAPIVersion() - apps, err := c.ShowApps(context.Background(), appKey, region) - if err != nil { - fmt.Printf("Error listing apps: %v\n", err) - os.Exit(1) - } - fmt.Println("Applications:") - for _, app := range apps { - fmt.Printf("%+v\n", app) + if apiVersion == "v1" { + c := newSDKClientV1() + appKey := edgeconnect.AppKey{ + Organization: organization, + Name: appName, + Version: appVersion, + } + apps, err := c.ShowApps(context.Background(), appKey, region) + if err != nil { + fmt.Printf("Error listing apps: %v\n", err) + os.Exit(1) + } + fmt.Println("Applications:") + for _, app := range apps { + fmt.Printf("%+v\n", app) + } + } else { + c := newSDKClientV2() + appKey := v2.AppKey{ + Organization: organization, + Name: appName, + Version: appVersion, + } + apps, err := c.ShowApps(context.Background(), appKey, region) + if err != nil { + fmt.Printf("Error listing apps: %v\n", err) + os.Exit(1) + } + fmt.Println("Applications:") + for _, app := range apps { + fmt.Printf("%+v\n", app) + } } }, } @@ -158,14 +251,27 @@ var deleteAppCmd = &cobra.Command{ Use: "delete", Short: "Delete an Edge Connect application", Run: func(cmd *cobra.Command, args []string) { - c := newSDKClient() - appKey := v2.AppKey{ - Organization: organization, - Name: appName, - Version: appVersion, + apiVersion := getAPIVersion() + var err error + + if apiVersion == "v1" { + c := newSDKClientV1() + appKey := edgeconnect.AppKey{ + Organization: organization, + Name: appName, + Version: appVersion, + } + err = c.DeleteApp(context.Background(), appKey, region) + } else { + c := newSDKClientV2() + appKey := v2.AppKey{ + Organization: organization, + Name: appName, + Version: appVersion, + } + err = c.DeleteApp(context.Background(), appKey, region) } - err := c.DeleteApp(context.Background(), appKey, region) if err != nil { fmt.Printf("Error deleting app: %v\n", err) os.Exit(1) diff --git a/cmd/apply.go b/cmd/apply.go index 41e94e9..311f64b 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -67,8 +67,8 @@ func runApply(configPath string, isDryRun bool, autoApprove bool) error { fmt.Printf("✅ Configuration loaded successfully: %s\n", cfg.Metadata.Name) - // Step 3: Create EdgeConnect client - client := newSDKClient() + // Step 3: Create EdgeConnect client (apply always uses v2) + client := newSDKClientV2() // Step 4: Create deployment planner planner := apply.NewPlanner(client) diff --git a/cmd/instance.go b/cmd/instance.go index 30194ab..1eb6cb6 100644 --- a/cmd/instance.go +++ b/cmd/instance.go @@ -5,6 +5,7 @@ import ( "fmt" "os" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" "github.com/spf13/cobra" ) @@ -26,30 +27,59 @@ var createInstanceCmd = &cobra.Command{ Use: "create", Short: "Create a new Edge Connect application instance", Run: func(cmd *cobra.Command, args []string) { - c := newSDKClient() - input := &v2.NewAppInstanceInput{ - Region: region, - AppInst: v2.AppInstance{ - Key: v2.AppInstanceKey{ - Organization: organization, - Name: instanceName, - CloudletKey: v2.CloudletKey{ - Organization: cloudletOrg, - Name: cloudletName, + apiVersion := getAPIVersion() + var err error + + if apiVersion == "v1" { + c := newSDKClientV1() + input := &edgeconnect.NewAppInstanceInput{ + Region: region, + AppInst: edgeconnect.AppInstance{ + Key: edgeconnect.AppInstanceKey{ + Organization: organization, + Name: instanceName, + CloudletKey: edgeconnect.CloudletKey{ + Organization: cloudletOrg, + Name: cloudletName, + }, + }, + AppKey: edgeconnect.AppKey{ + Organization: organization, + Name: appName, + Version: appVersion, + }, + Flavor: edgeconnect.Flavor{ + Name: flavorName, }, }, - AppKey: v2.AppKey{ - Organization: organization, - Name: appName, - Version: appVersion, + } + err = c.CreateAppInstance(context.Background(), input) + } else { + c := newSDKClientV2() + input := &v2.NewAppInstanceInput{ + Region: region, + AppInst: v2.AppInstance{ + Key: v2.AppInstanceKey{ + Organization: organization, + Name: instanceName, + CloudletKey: v2.CloudletKey{ + Organization: cloudletOrg, + Name: cloudletName, + }, + }, + AppKey: v2.AppKey{ + Organization: organization, + Name: appName, + Version: appVersion, + }, + Flavor: v2.Flavor{ + Name: flavorName, + }, }, - Flavor: v2.Flavor{ - Name: flavorName, - }, - }, + } + err = c.CreateAppInstance(context.Background(), input) } - err := c.CreateAppInstance(context.Background(), input) if err != nil { fmt.Printf("Error creating app instance: %v\n", err) os.Exit(1) @@ -62,22 +92,41 @@ var showInstanceCmd = &cobra.Command{ Use: "show", Short: "Show details of an Edge Connect application instance", Run: func(cmd *cobra.Command, args []string) { - c := newSDKClient() - instanceKey := v2.AppInstanceKey{ - Organization: organization, - Name: instanceName, - CloudletKey: v2.CloudletKey{ - Organization: cloudletOrg, - Name: cloudletName, - }, - } + apiVersion := getAPIVersion() - instance, err := c.ShowAppInstance(context.Background(), instanceKey, region) - if err != nil { - fmt.Printf("Error showing app instance: %v\n", err) - os.Exit(1) + if apiVersion == "v1" { + c := newSDKClientV1() + instanceKey := edgeconnect.AppInstanceKey{ + Organization: organization, + Name: instanceName, + CloudletKey: edgeconnect.CloudletKey{ + Organization: cloudletOrg, + Name: cloudletName, + }, + } + instance, err := c.ShowAppInstance(context.Background(), instanceKey, region) + if err != nil { + fmt.Printf("Error showing app instance: %v\n", err) + os.Exit(1) + } + fmt.Printf("Application instance details:\n%+v\n", instance) + } else { + c := newSDKClientV2() + instanceKey := v2.AppInstanceKey{ + Organization: organization, + Name: instanceName, + CloudletKey: v2.CloudletKey{ + Organization: cloudletOrg, + Name: cloudletName, + }, + } + instance, err := c.ShowAppInstance(context.Background(), instanceKey, region) + if err != nil { + fmt.Printf("Error showing app instance: %v\n", err) + os.Exit(1) + } + fmt.Printf("Application instance details:\n%+v\n", instance) } - fmt.Printf("Application instance details:\n%+v\n", instance) }, } @@ -85,24 +134,46 @@ var listInstancesCmd = &cobra.Command{ Use: "list", Short: "List Edge Connect application instances", Run: func(cmd *cobra.Command, args []string) { - c := newSDKClient() - instanceKey := v2.AppInstanceKey{ - Organization: organization, - Name: instanceName, - CloudletKey: v2.CloudletKey{ - Organization: cloudletOrg, - Name: cloudletName, - }, - } + apiVersion := getAPIVersion() - instances, err := c.ShowAppInstances(context.Background(), instanceKey, region) - if err != nil { - fmt.Printf("Error listing app instances: %v\n", err) - os.Exit(1) - } - fmt.Println("Application instances:") - for _, instance := range instances { - fmt.Printf("%+v\n", instance) + if apiVersion == "v1" { + c := newSDKClientV1() + instanceKey := edgeconnect.AppInstanceKey{ + Organization: organization, + Name: instanceName, + CloudletKey: edgeconnect.CloudletKey{ + Organization: cloudletOrg, + Name: cloudletName, + }, + } + instances, err := c.ShowAppInstances(context.Background(), instanceKey, region) + if err != nil { + fmt.Printf("Error listing app instances: %v\n", err) + os.Exit(1) + } + fmt.Println("Application instances:") + for _, instance := range instances { + fmt.Printf("%+v\n", instance) + } + } else { + c := newSDKClientV2() + instanceKey := v2.AppInstanceKey{ + Organization: organization, + Name: instanceName, + CloudletKey: v2.CloudletKey{ + Organization: cloudletOrg, + Name: cloudletName, + }, + } + instances, err := c.ShowAppInstances(context.Background(), instanceKey, region) + if err != nil { + fmt.Printf("Error listing app instances: %v\n", err) + os.Exit(1) + } + fmt.Println("Application instances:") + for _, instance := range instances { + fmt.Printf("%+v\n", instance) + } } }, } @@ -111,17 +182,33 @@ var deleteInstanceCmd = &cobra.Command{ Use: "delete", Short: "Delete an Edge Connect application instance", Run: func(cmd *cobra.Command, args []string) { - c := newSDKClient() - instanceKey := v2.AppInstanceKey{ - Organization: organization, - Name: instanceName, - CloudletKey: v2.CloudletKey{ - Organization: cloudletOrg, - Name: cloudletName, - }, + apiVersion := getAPIVersion() + var err error + + if apiVersion == "v1" { + c := newSDKClientV1() + instanceKey := edgeconnect.AppInstanceKey{ + Organization: organization, + Name: instanceName, + CloudletKey: edgeconnect.CloudletKey{ + Organization: cloudletOrg, + Name: cloudletName, + }, + } + err = c.DeleteAppInstance(context.Background(), instanceKey, region) + } else { + c := newSDKClientV2() + instanceKey := v2.AppInstanceKey{ + Organization: organization, + Name: instanceName, + CloudletKey: v2.CloudletKey{ + Organization: cloudletOrg, + Name: cloudletName, + }, + } + err = c.DeleteAppInstance(context.Background(), instanceKey, region) } - err := c.DeleteAppInstance(context.Background(), instanceKey, region) if err != nil { fmt.Printf("Error deleting app instance: %v\n", err) os.Exit(1) diff --git a/cmd/root.go b/cmd/root.go index 6fa2dd6..dd22f72 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,11 +9,12 @@ import ( ) var ( - cfgFile string - baseURL string - username string - password string - debug bool + cfgFile string + baseURL string + username string + password string + debug bool + apiVersion string ) // rootCmd represents the base command when called without any subcommands @@ -40,11 +41,13 @@ func init() { rootCmd.PersistentFlags().StringVar(&baseURL, "base-url", "", "base URL for the Edge Connect API") rootCmd.PersistentFlags().StringVar(&username, "username", "", "username for authentication") rootCmd.PersistentFlags().StringVar(&password, "password", "", "password for authentication") + rootCmd.PersistentFlags().StringVar(&apiVersion, "api-version", "v2", "API version to use (v1 or v2)") rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "enable debug logging") viper.BindPFlag("base_url", rootCmd.PersistentFlags().Lookup("base-url")) viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username")) viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password")) + viper.BindPFlag("api_version", rootCmd.PersistentFlags().Lookup("api-version")) } func initConfig() { @@ -53,6 +56,7 @@ func initConfig() { viper.BindEnv("base_url", "EDGE_CONNECT_BASE_URL") viper.BindEnv("username", "EDGE_CONNECT_USERNAME") viper.BindEnv("password", "EDGE_CONNECT_PASSWORD") + viper.BindEnv("api_version", "EDGE_CONNECT_API_VERSION") if cfgFile != "" { viper.SetConfigFile(cfgFile) From 59ba5ffb02661572067adf52fe8f669f34c5d3b3 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 20 Oct 2025 13:49:09 +0200 Subject: [PATCH 23/40] fix(apply): add validation to reject v1 API version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The apply command requires v2 API features and cannot work with v1. Add early validation to provide a clear error message when users try to use apply with --api-version v1, instead of failing with a cryptic 403 Forbidden error. Error message explains that apply only supports v2 and guides users to use --api-version v2 or remove the api_version setting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- cmd/apply.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cmd/apply.go b/cmd/apply.go index 311f64b..3a50ddf 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -67,13 +67,19 @@ func runApply(configPath string, isDryRun bool, autoApprove bool) error { fmt.Printf("✅ Configuration loaded successfully: %s\n", cfg.Metadata.Name) - // Step 3: Create EdgeConnect client (apply always uses v2) + // Step 3: Validate API version (apply only supports v2) + apiVersion := getAPIVersion() + if apiVersion == "v1" { + return fmt.Errorf("apply command only supports API v2. The v1 API does not support the advanced deployment features required by this command. Please use --api-version v2 or remove the api_version setting") + } + + // Step 4: Create EdgeConnect client (v2 only) client := newSDKClientV2() - // Step 4: Create deployment planner + // Step 5: Create deployment planner planner := apply.NewPlanner(client) - // Step 5: Generate deployment plan + // Step 6: Generate deployment plan fmt.Println("🔍 Analyzing current state and generating deployment plan...") planOptions := apply.DefaultPlanOptions() From 98a8c4db4a04b20dec8083a7281d08e1f2a63b46 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 20 Oct 2025 13:57:57 +0200 Subject: [PATCH 24/40] feat(apply): add v1 API support to apply command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor apply command to support both v1 and v2 APIs: - Split internal/apply into v1 and v2 subdirectories - v1: Uses sdk/edgeconnect (from revision/v1 branch) - v2: Uses sdk/edgeconnect/v2 - Update cmd/apply.go to route to appropriate version based on api_version config - Both versions now fully functional with their respective API endpoints Changes: - Created internal/apply/v1/ with v1 SDK implementation - Created internal/apply/v2/ with v2 SDK implementation - Updated cmd/apply.go with runApplyV1() and runApplyV2() functions - Removed validation error that rejected v1 - Apply command now respects --api-version flag and config setting Testing: - V1 with edge.platform: ✅ Generates deployment plan correctly - V2 with orca.platform: ✅ Works as before 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- cmd/apply.go | 156 ++++- internal/apply/v1/manager.go | 286 ++++++++ internal/apply/v1/manager_test.go | 497 ++++++++++++++ internal/apply/v1/planner.go | 555 ++++++++++++++++ internal/apply/v1/planner_test.go | 663 +++++++++++++++++++ internal/apply/{ => v1}/strategy.go | 2 +- internal/apply/v1/strategy_recreate.go | 548 +++++++++++++++ internal/apply/v1/types.go | 462 +++++++++++++ internal/apply/{ => v2}/manager.go | 2 +- internal/apply/{ => v2}/manager_test.go | 2 +- internal/apply/{ => v2}/planner.go | 2 +- internal/apply/{ => v2}/planner_test.go | 2 +- internal/apply/v2/strategy.go | 106 +++ internal/apply/{ => v2}/strategy_recreate.go | 2 +- internal/apply/{ => v2}/types.go | 2 +- 15 files changed, 3265 insertions(+), 22 deletions(-) create mode 100644 internal/apply/v1/manager.go create mode 100644 internal/apply/v1/manager_test.go create mode 100644 internal/apply/v1/planner.go create mode 100644 internal/apply/v1/planner_test.go rename internal/apply/{ => v1}/strategy.go (99%) create mode 100644 internal/apply/v1/strategy_recreate.go create mode 100644 internal/apply/v1/types.go rename internal/apply/{ => v2}/manager.go (99%) rename internal/apply/{ => v2}/manager_test.go (99%) rename internal/apply/{ => v2}/planner.go (99%) rename internal/apply/{ => v2}/planner_test.go (99%) create mode 100644 internal/apply/v2/strategy.go rename internal/apply/{ => v2}/strategy_recreate.go (99%) rename internal/apply/{ => v2}/types.go (99%) diff --git a/cmd/apply.go b/cmd/apply.go index 3a50ddf..1493841 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -10,7 +10,8 @@ import ( "path/filepath" "strings" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/apply" + applyv1 "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/apply/v1" + applyv2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/apply/v2" "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" "github.com/spf13/cobra" ) @@ -67,22 +68,27 @@ func runApply(configPath string, isDryRun bool, autoApprove bool) error { fmt.Printf("✅ Configuration loaded successfully: %s\n", cfg.Metadata.Name) - // Step 3: Validate API version (apply only supports v2) + // Step 3: Determine API version and create appropriate client apiVersion := getAPIVersion() + + // Step 4-6: Execute deployment based on API version if apiVersion == "v1" { - return fmt.Errorf("apply command only supports API v2. The v1 API does not support the advanced deployment features required by this command. Please use --api-version v2 or remove the api_version setting") + return runApplyV1(cfg, manifestContent, isDryRun, autoApprove) } + return runApplyV2(cfg, manifestContent, isDryRun, autoApprove) +} - // Step 4: Create EdgeConnect client (v2 only) - client := newSDKClientV2() +func runApplyV1(cfg *config.EdgeConnectConfig, manifestContent string, isDryRun bool, autoApprove bool) error { + // Create v1 client + client := newSDKClientV1() - // Step 5: Create deployment planner - planner := apply.NewPlanner(client) + // Create deployment planner + planner := applyv1.NewPlanner(client) - // Step 6: Generate deployment plan + // Generate deployment plan fmt.Println("🔍 Analyzing current state and generating deployment plan...") - planOptions := apply.DefaultPlanOptions() + planOptions := applyv1.DefaultPlanOptions() planOptions.DryRun = isDryRun result, err := planner.PlanWithOptions(context.Background(), cfg, planOptions) @@ -90,7 +96,7 @@ func runApply(configPath string, isDryRun bool, autoApprove bool) error { return fmt.Errorf("failed to generate deployment plan: %w", err) } - // Step 6: Display plan summary + // Display plan summary fmt.Println("\n📋 Deployment Plan:") fmt.Println(strings.Repeat("=", 50)) fmt.Println(result.Plan.Summary) @@ -104,13 +110,13 @@ func runApply(configPath string, isDryRun bool, autoApprove bool) error { } } - // Step 7: If dry-run, stop here + // If dry-run, stop here if isDryRun { fmt.Println("\n🔍 Dry-run complete. No changes were made.") return nil } - // Step 8: Confirm deployment (in non-dry-run mode) + // Confirm deployment if result.Plan.TotalActions == 0 { fmt.Println("\n✅ No changes needed. Resources are already in desired state.") return nil @@ -124,16 +130,112 @@ func runApply(configPath string, isDryRun bool, autoApprove bool) error { return nil } - // Step 9: Execute deployment + // Execute deployment fmt.Println("\n🚀 Starting deployment...") - manager := apply.NewResourceManager(client, apply.WithLogger(log.Default())) + manager := applyv1.NewResourceManager(client, applyv1.WithLogger(log.Default())) deployResult, err := manager.ApplyDeployment(context.Background(), result.Plan, cfg, manifestContent) if err != nil { return fmt.Errorf("deployment failed: %w", err) } - // Step 10: Display results + // Display results + return displayDeploymentResults(deployResult) +} + +func runApplyV2(cfg *config.EdgeConnectConfig, manifestContent string, isDryRun bool, autoApprove bool) error { + // Create v2 client + client := newSDKClientV2() + + // Create deployment planner + planner := applyv2.NewPlanner(client) + + // Generate deployment plan + fmt.Println("🔍 Analyzing current state and generating deployment plan...") + + planOptions := applyv2.DefaultPlanOptions() + planOptions.DryRun = isDryRun + + result, err := planner.PlanWithOptions(context.Background(), cfg, planOptions) + if err != nil { + return fmt.Errorf("failed to generate deployment plan: %w", err) + } + + // Display plan summary + fmt.Println("\n📋 Deployment Plan:") + fmt.Println(strings.Repeat("=", 50)) + fmt.Println(result.Plan.Summary) + fmt.Println(strings.Repeat("=", 50)) + + // Display warnings if any + if len(result.Warnings) > 0 { + fmt.Println("\n⚠️ Warnings:") + for _, warning := range result.Warnings { + fmt.Printf(" • %s\n", warning) + } + } + + // If dry-run, stop here + if isDryRun { + fmt.Println("\n🔍 Dry-run complete. No changes were made.") + return nil + } + + // Confirm deployment + if result.Plan.TotalActions == 0 { + fmt.Println("\n✅ No changes needed. Resources are already in desired state.") + return nil + } + + fmt.Printf("\nThis will perform %d actions. Estimated time: %v\n", + result.Plan.TotalActions, result.Plan.EstimatedDuration) + + if !autoApprove && !confirmDeployment() { + fmt.Println("Deployment cancelled.") + return nil + } + + // Execute deployment + fmt.Println("\n🚀 Starting deployment...") + + manager := applyv2.NewResourceManager(client, applyv2.WithLogger(log.Default())) + deployResult, err := manager.ApplyDeployment(context.Background(), result.Plan, cfg, manifestContent) + if err != nil { + return fmt.Errorf("deployment failed: %w", err) + } + + // Display results + return displayDeploymentResults(deployResult) +} + +type deploymentResult interface { + IsSuccess() bool + GetDuration() string + GetCompletedActions() []actionResult + GetFailedActions() []actionResult + GetError() error +} + +type actionResult interface { + GetType() string + GetTarget() string + GetError() error +} + +func displayDeploymentResults(result interface{}) error { + // Use reflection or type assertion to handle both v1 and v2 result types + // For now, we'll use a simple approach that works with both + switch r := result.(type) { + case *applyv1.ExecutionResult: + return displayDeploymentResultsV1(r) + case *applyv2.ExecutionResult: + return displayDeploymentResultsV2(r) + default: + return fmt.Errorf("unknown deployment result type") + } +} + +func displayDeploymentResultsV1(deployResult *applyv1.ExecutionResult) error { if deployResult.Success { fmt.Printf("\n✅ Deployment completed successfully in %v\n", deployResult.Duration) if len(deployResult.CompletedActions) > 0 { @@ -155,7 +257,31 @@ func runApply(configPath string, isDryRun bool, autoApprove bool) error { } return fmt.Errorf("deployment failed with %d failed actions", len(deployResult.FailedActions)) } + return nil +} +func displayDeploymentResultsV2(deployResult *applyv2.ExecutionResult) error { + if deployResult.Success { + fmt.Printf("\n✅ Deployment completed successfully in %v\n", deployResult.Duration) + if len(deployResult.CompletedActions) > 0 { + fmt.Println("\nCompleted actions:") + for _, action := range deployResult.CompletedActions { + fmt.Printf(" ✅ %s %s\n", action.Type, action.Target) + } + } + } else { + fmt.Printf("\n❌ Deployment failed after %v\n", deployResult.Duration) + if deployResult.Error != nil { + fmt.Printf("Error: %v\n", deployResult.Error) + } + if len(deployResult.FailedActions) > 0 { + fmt.Println("\nFailed actions:") + for _, action := range deployResult.FailedActions { + fmt.Printf(" ❌ %s %s: %v\n", action.Type, action.Target, action.Error) + } + } + return fmt.Errorf("deployment failed with %d failed actions", len(deployResult.FailedActions)) + } return nil } diff --git a/internal/apply/v1/manager.go b/internal/apply/v1/manager.go new file mode 100644 index 0000000..a0668e8 --- /dev/null +++ b/internal/apply/v1/manager.go @@ -0,0 +1,286 @@ +// ABOUTME: Resource management for EdgeConnect apply command with deployment execution and rollback +// ABOUTME: Handles actual deployment operations, manifest processing, and error recovery with parallel execution +package v1 + +import ( + "context" + "fmt" + "time" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" +) + +// ResourceManagerInterface defines the interface for resource management +type ResourceManagerInterface interface { + // ApplyDeployment executes a deployment plan + ApplyDeployment(ctx context.Context, plan *DeploymentPlan, config *config.EdgeConnectConfig, manifestContent string) (*ExecutionResult, error) + + // RollbackDeployment attempts to rollback a failed deployment + RollbackDeployment(ctx context.Context, result *ExecutionResult) error + + // ValidatePrerequisites checks if deployment prerequisites are met + ValidatePrerequisites(ctx context.Context, plan *DeploymentPlan) error +} + +// EdgeConnectResourceManager implements resource management for EdgeConnect +type EdgeConnectResourceManager struct { + client EdgeConnectClientInterface + parallelLimit int + rollbackOnFail bool + logger Logger + strategyConfig StrategyConfig +} + +// Logger interface for deployment logging +type Logger interface { + Printf(format string, v ...interface{}) +} + +// ResourceManagerOptions configures the resource manager behavior +type ResourceManagerOptions struct { + // ParallelLimit controls how many operations run concurrently + ParallelLimit int + + // RollbackOnFail automatically rolls back on deployment failure + RollbackOnFail bool + + // Logger for deployment operations + Logger Logger + + // Timeout for individual operations + OperationTimeout time.Duration + + // StrategyConfig for deployment strategies + StrategyConfig StrategyConfig +} + +// DefaultResourceManagerOptions returns sensible defaults +func DefaultResourceManagerOptions() ResourceManagerOptions { + return ResourceManagerOptions{ + ParallelLimit: 5, // Conservative parallel limit + RollbackOnFail: true, + OperationTimeout: 2 * time.Minute, + StrategyConfig: DefaultStrategyConfig(), + } +} + +// NewResourceManager creates a new EdgeConnect resource manager +func NewResourceManager(client EdgeConnectClientInterface, opts ...func(*ResourceManagerOptions)) ResourceManagerInterface { + options := DefaultResourceManagerOptions() + for _, opt := range opts { + opt(&options) + } + + return &EdgeConnectResourceManager{ + client: client, + parallelLimit: options.ParallelLimit, + rollbackOnFail: options.RollbackOnFail, + logger: options.Logger, + strategyConfig: options.StrategyConfig, + } +} + +// WithParallelLimit sets the parallel execution limit +func WithParallelLimit(limit int) func(*ResourceManagerOptions) { + return func(opts *ResourceManagerOptions) { + opts.ParallelLimit = limit + } +} + +// WithRollbackOnFail enables/disables automatic rollback +func WithRollbackOnFail(rollback bool) func(*ResourceManagerOptions) { + return func(opts *ResourceManagerOptions) { + opts.RollbackOnFail = rollback + } +} + +// WithLogger sets a logger for deployment operations +func WithLogger(logger Logger) func(*ResourceManagerOptions) { + return func(opts *ResourceManagerOptions) { + opts.Logger = logger + } +} + +// WithStrategyConfig sets the strategy configuration +func WithStrategyConfig(config StrategyConfig) func(*ResourceManagerOptions) { + return func(opts *ResourceManagerOptions) { + opts.StrategyConfig = config + } +} + +// ApplyDeployment executes a deployment plan using deployment strategies +func (rm *EdgeConnectResourceManager) ApplyDeployment(ctx context.Context, plan *DeploymentPlan, config *config.EdgeConnectConfig, manifestContent string) (*ExecutionResult, error) { + rm.logf("Starting deployment: %s", plan.ConfigName) + + // Step 1: Validate prerequisites + if err := rm.ValidatePrerequisites(ctx, plan); err != nil { + result := &ExecutionResult{ + Plan: plan, + CompletedActions: []ActionResult{}, + FailedActions: []ActionResult{}, + Error: fmt.Errorf("prerequisites validation failed: %w", err), + Duration: 0, + } + return result, err + } + + // Step 2: Determine deployment strategy + strategyName := DeploymentStrategy(config.Spec.GetDeploymentStrategy()) + rm.logf("Using deployment strategy: %s", strategyName) + + // Step 3: Create strategy executor + strategyConfig := rm.strategyConfig + strategyConfig.ParallelOperations = rm.parallelLimit > 1 + + factory := NewStrategyFactory(rm.client, strategyConfig, rm.logger) + strategy, err := factory.CreateStrategy(strategyName) + if err != nil { + result := &ExecutionResult{ + Plan: plan, + CompletedActions: []ActionResult{}, + FailedActions: []ActionResult{}, + Error: fmt.Errorf("failed to create deployment strategy: %w", err), + Duration: 0, + } + return result, err + } + + // Step 4: Validate strategy can handle this deployment + if err := strategy.Validate(plan); err != nil { + result := &ExecutionResult{ + Plan: plan, + CompletedActions: []ActionResult{}, + FailedActions: []ActionResult{}, + Error: fmt.Errorf("strategy validation failed: %w", err), + Duration: 0, + } + return result, err + } + + // Step 5: Execute the deployment strategy + rm.logf("Estimated deployment duration: %v", strategy.EstimateDuration(plan)) + result, err := strategy.Execute(ctx, plan, config, manifestContent) + + // Step 6: Handle rollback if needed + if err != nil && rm.rollbackOnFail && result != nil { + rm.logf("Deployment failed, attempting rollback...") + if rollbackErr := rm.RollbackDeployment(ctx, result); rollbackErr != nil { + rm.logf("Rollback failed: %v", rollbackErr) + } else { + result.RollbackPerformed = true + result.RollbackSuccess = true + } + } + + if result != nil && result.Success { + rm.logf("Deployment completed successfully in %v", result.Duration) + } + + return result, err +} + +// ValidatePrerequisites checks if deployment prerequisites are met +func (rm *EdgeConnectResourceManager) ValidatePrerequisites(ctx context.Context, plan *DeploymentPlan) error { + rm.logf("Validating deployment prerequisites for: %s", plan.ConfigName) + + // Check if we have any actions to perform + if plan.IsEmpty() { + return fmt.Errorf("deployment plan is empty - no actions to perform") + } + + // Validate that we have required client capabilities + if rm.client == nil { + return fmt.Errorf("EdgeConnect client is not configured") + } + + rm.logf("Prerequisites validation passed") + return nil +} + +// RollbackDeployment attempts to rollback a failed deployment +func (rm *EdgeConnectResourceManager) RollbackDeployment(ctx context.Context, result *ExecutionResult) error { + rm.logf("Starting rollback for deployment: %s", result.Plan.ConfigName) + + rollbackErrors := []error{} + + // Rollback completed instances (in reverse order) + for i := len(result.CompletedActions) - 1; i >= 0; i-- { + action := result.CompletedActions[i] + + switch action.Type { + case ActionCreate: + if err := rm.rollbackCreateAction(ctx, action, result.Plan); err != nil { + rollbackErrors = append(rollbackErrors, fmt.Errorf("failed to rollback %s: %w", action.Target, err)) + } else { + rm.logf("Successfully rolled back: %s", action.Target) + } + } + } + + if len(rollbackErrors) > 0 { + return fmt.Errorf("rollback encountered %d errors: %v", len(rollbackErrors), rollbackErrors) + } + + rm.logf("Rollback completed successfully") + return nil +} + +// rollbackCreateAction rolls back a CREATE action by deleting the resource +func (rm *EdgeConnectResourceManager) rollbackCreateAction(ctx context.Context, action ActionResult, plan *DeploymentPlan) error { + if action.Type != ActionCreate { + return nil + } + + // Determine if this is an app or instance rollback based on the target name + isInstance := false + for _, instanceAction := range plan.InstanceActions { + if instanceAction.InstanceName == action.Target { + isInstance = true + break + } + } + + if isInstance { + return rm.rollbackInstance(ctx, action, plan) + } else { + return rm.rollbackApp(ctx, action, plan) + } +} + +// rollbackApp deletes an application that was created +func (rm *EdgeConnectResourceManager) rollbackApp(ctx context.Context, action ActionResult, plan *DeploymentPlan) error { + appKey := edgeconnect.AppKey{ + Organization: plan.AppAction.Desired.Organization, + Name: plan.AppAction.Desired.Name, + Version: plan.AppAction.Desired.Version, + } + + return rm.client.DeleteApp(ctx, appKey, plan.AppAction.Desired.Region) +} + +// rollbackInstance deletes an instance that was created +func (rm *EdgeConnectResourceManager) rollbackInstance(ctx context.Context, action ActionResult, plan *DeploymentPlan) error { + // Find the instance action to get the details + for _, instanceAction := range plan.InstanceActions { + if instanceAction.InstanceName == action.Target { + instanceKey := edgeconnect.AppInstanceKey{ + Organization: plan.AppAction.Desired.Organization, + Name: instanceAction.InstanceName, + CloudletKey: edgeconnect.CloudletKey{ + Organization: instanceAction.Target.CloudletOrg, + Name: instanceAction.Target.CloudletName, + }, + } + return rm.client.DeleteAppInstance(ctx, instanceKey, instanceAction.Target.Region) + } + } + return fmt.Errorf("instance action not found for rollback: %s", action.Target) +} + +// logf logs a message if a logger is configured +func (rm *EdgeConnectResourceManager) logf(format string, v ...interface{}) { + if rm.logger != nil { + rm.logger.Printf("[ResourceManager] "+format, v...) + } +} diff --git a/internal/apply/v1/manager_test.go b/internal/apply/v1/manager_test.go new file mode 100644 index 0000000..9ed3cac --- /dev/null +++ b/internal/apply/v1/manager_test.go @@ -0,0 +1,497 @@ +// ABOUTME: Comprehensive tests for EdgeConnect resource manager with deployment scenarios +// ABOUTME: Tests deployment execution, rollback functionality, and error handling with mock clients +package v1 + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" + "time" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +// MockResourceClient extends MockEdgeConnectClient with resource management methods +type MockResourceClient struct { + MockEdgeConnectClient +} + +func (m *MockResourceClient) CreateApp(ctx context.Context, input *edgeconnect.NewAppInput) error { + args := m.Called(ctx, input) + return args.Error(0) +} + +func (m *MockResourceClient) CreateAppInstance(ctx context.Context, input *edgeconnect.NewAppInstanceInput) error { + args := m.Called(ctx, input) + return args.Error(0) +} + +func (m *MockResourceClient) DeleteApp(ctx context.Context, appKey edgeconnect.AppKey, region string) error { + args := m.Called(ctx, appKey, region) + return args.Error(0) +} + +func (m *MockResourceClient) UpdateApp(ctx context.Context, input *edgeconnect.UpdateAppInput) error { + args := m.Called(ctx, input) + return args.Error(0) +} + +func (m *MockResourceClient) UpdateAppInstance(ctx context.Context, input *edgeconnect.UpdateAppInstanceInput) error { + args := m.Called(ctx, input) + return args.Error(0) +} + +func (m *MockResourceClient) DeleteAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) error { + args := m.Called(ctx, instanceKey, region) + return args.Error(0) +} + +// TestLogger implements Logger interface for testing +type TestLogger struct { + messages []string +} + +func (l *TestLogger) Printf(format string, v ...interface{}) { + l.messages = append(l.messages, fmt.Sprintf(format, v...)) +} + +func TestNewResourceManager(t *testing.T) { + mockClient := &MockResourceClient{} + manager := NewResourceManager(mockClient) + + assert.NotNil(t, manager) + assert.IsType(t, &EdgeConnectResourceManager{}, manager) +} + +func TestDefaultResourceManagerOptions(t *testing.T) { + opts := DefaultResourceManagerOptions() + + assert.Equal(t, 5, opts.ParallelLimit) + assert.True(t, opts.RollbackOnFail) + assert.Equal(t, 2*time.Minute, opts.OperationTimeout) +} + +func TestWithOptions(t *testing.T) { + mockClient := &MockResourceClient{} + logger := &TestLogger{} + + manager := NewResourceManager(mockClient, + WithParallelLimit(10), + WithRollbackOnFail(false), + WithLogger(logger), + ) + + // Cast to implementation to check options were applied + impl := manager.(*EdgeConnectResourceManager) + assert.Equal(t, 10, impl.parallelLimit) + assert.False(t, impl.rollbackOnFail) + assert.Equal(t, logger, impl.logger) +} + +func createTestDeploymentPlan() *DeploymentPlan { + return &DeploymentPlan{ + ConfigName: "test-deployment", + AppAction: AppAction{ + Type: ActionCreate, + Desired: &AppState{ + Name: "test-app", + Version: "1.0.0", + Organization: "testorg", + Region: "US", + }, + }, + InstanceActions: []InstanceAction{ + { + Type: ActionCreate, + Target: config.InfraTemplate{ + Region: "US", + CloudletOrg: "cloudletorg", + CloudletName: "cloudlet1", + FlavorName: "small", + }, + Desired: &InstanceState{ + Name: "test-app-1.0.0-instance", + AppName: "test-app", + }, + InstanceName: "test-app-1.0.0-instance", + }, + }, + } +} + +func createTestManagerConfig(t *testing.T) *config.EdgeConnectConfig { + // Create temporary manifest file + tempDir := t.TempDir() + manifestFile := filepath.Join(tempDir, "test-manifest.yaml") + manifestContent := "apiVersion: v1\nkind: Pod\nmetadata:\n name: test\n" + err := os.WriteFile(manifestFile, []byte(manifestContent), 0644) + require.NoError(t, err) + + return &config.EdgeConnectConfig{ + Kind: "edgeconnect-deployment", + Metadata: config.Metadata{ + Name: "test-app", + AppVersion: "1.0.0", + Organization: "testorg", + }, + Spec: config.Spec{ + K8sApp: &config.K8sApp{ + ManifestFile: manifestFile, + }, + InfraTemplate: []config.InfraTemplate{ + { + Region: "US", + CloudletOrg: "cloudletorg", + CloudletName: "cloudlet1", + FlavorName: "small", + }, + }, + Network: &config.NetworkConfig{ + OutboundConnections: []config.OutboundConnection{ + { + Protocol: "tcp", + PortRangeMin: 80, + PortRangeMax: 80, + RemoteCIDR: "0.0.0.0/0", + }, + }, + }, + }, + } +} + +// createTestStrategyConfig returns a fast configuration for tests +func createTestStrategyConfig() StrategyConfig { + return StrategyConfig{ + MaxRetries: 0, // No retries for fast tests + HealthCheckTimeout: 1 * time.Millisecond, + ParallelOperations: false, // Sequential for predictable tests + RetryDelay: 0, // No delay + } +} + +func TestApplyDeploymentSuccess(t *testing.T) { + mockClient := &MockResourceClient{} + logger := &TestLogger{} + manager := NewResourceManager(mockClient, WithLogger(logger), WithStrategyConfig(createTestStrategyConfig())) + + plan := createTestDeploymentPlan() + config := createTestManagerConfig(t) + + // Mock successful operations + mockClient.On("CreateApp", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInput")). + Return(nil) + mockClient.On("CreateAppInstance", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInstanceInput")). + Return(nil) + + ctx := context.Background() + result, err := manager.ApplyDeployment(ctx, plan, config, "test manifest content") + + require.NoError(t, err) + require.NotNil(t, result) + assert.True(t, result.Success) + assert.Len(t, result.CompletedActions, 2) // 1 app + 1 instance + assert.Len(t, result.FailedActions, 0) + assert.False(t, result.RollbackPerformed) + assert.Greater(t, result.Duration, time.Duration(0)) + + // Check that operations were logged + assert.Greater(t, len(logger.messages), 0) + + mockClient.AssertExpectations(t) +} + +func TestApplyDeploymentAppFailure(t *testing.T) { + mockClient := &MockResourceClient{} + logger := &TestLogger{} + manager := NewResourceManager(mockClient, WithLogger(logger), WithStrategyConfig(createTestStrategyConfig())) + + plan := createTestDeploymentPlan() + config := createTestManagerConfig(t) + + // Mock app creation failure - deployment should stop here + mockClient.On("CreateApp", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInput")). + Return(&edgeconnect.APIError{StatusCode: 500, Messages: []string{"Server error"}}) + + ctx := context.Background() + result, err := manager.ApplyDeployment(ctx, plan, config, "test manifest content") + + require.Error(t, err) + require.NotNil(t, result) + assert.False(t, result.Success) + assert.Len(t, result.CompletedActions, 0) + assert.Len(t, result.FailedActions, 1) + assert.Contains(t, err.Error(), "Server error") + + mockClient.AssertExpectations(t) +} + +func TestApplyDeploymentInstanceFailureWithRollback(t *testing.T) { + mockClient := &MockResourceClient{} + logger := &TestLogger{} + manager := NewResourceManager(mockClient, WithLogger(logger), WithRollbackOnFail(true), WithStrategyConfig(createTestStrategyConfig())) + + plan := createTestDeploymentPlan() + config := createTestManagerConfig(t) + + // Mock successful app creation but failed instance creation + mockClient.On("CreateApp", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInput")). + Return(nil) + mockClient.On("CreateAppInstance", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInstanceInput")). + Return(&edgeconnect.APIError{StatusCode: 500, Messages: []string{"Instance creation failed"}}) + + // Mock rollback operations + mockClient.On("DeleteApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). + Return(nil) + + ctx := context.Background() + result, err := manager.ApplyDeployment(ctx, plan, config, "test manifest content") + + require.Error(t, err) + require.NotNil(t, result) + assert.False(t, result.Success) + assert.Len(t, result.CompletedActions, 1) // App was created + assert.Len(t, result.FailedActions, 1) // Instance failed + assert.True(t, result.RollbackPerformed) + assert.True(t, result.RollbackSuccess) + assert.Contains(t, err.Error(), "failed to create instance") + + mockClient.AssertExpectations(t) +} + +func TestApplyDeploymentNoActions(t *testing.T) { + mockClient := &MockResourceClient{} + manager := NewResourceManager(mockClient) + + // Create empty plan + plan := &DeploymentPlan{ + ConfigName: "empty-plan", + AppAction: AppAction{Type: ActionNone}, + } + config := createTestManagerConfig(t) + + ctx := context.Background() + result, err := manager.ApplyDeployment(ctx, plan, config, "test manifest content") + + require.Error(t, err) + require.NotNil(t, result) + assert.Contains(t, err.Error(), "deployment plan is empty") + + mockClient.AssertNotCalled(t, "CreateApp") + mockClient.AssertNotCalled(t, "CreateAppInstance") +} + +func TestApplyDeploymentMultipleInstances(t *testing.T) { + mockClient := &MockResourceClient{} + logger := &TestLogger{} + manager := NewResourceManager(mockClient, WithLogger(logger), WithParallelLimit(2), WithStrategyConfig(createTestStrategyConfig())) + + // Create plan with multiple instances + plan := &DeploymentPlan{ + ConfigName: "multi-instance", + AppAction: AppAction{ + Type: ActionCreate, + Desired: &AppState{ + Name: "test-app", + Version: "1.0.0", + Organization: "testorg", + Region: "US", + }, + }, + InstanceActions: []InstanceAction{ + { + Type: ActionCreate, + Target: config.InfraTemplate{ + Region: "US", + CloudletOrg: "cloudletorg1", + CloudletName: "cloudlet1", + FlavorName: "small", + }, + Desired: &InstanceState{Name: "instance1"}, + InstanceName: "instance1", + }, + { + Type: ActionCreate, + Target: config.InfraTemplate{ + Region: "EU", + CloudletOrg: "cloudletorg2", + CloudletName: "cloudlet2", + FlavorName: "medium", + }, + Desired: &InstanceState{Name: "instance2"}, + InstanceName: "instance2", + }, + }, + } + + config := createTestManagerConfig(t) + + // Mock successful operations + mockClient.On("CreateApp", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInput")). + Return(nil) + mockClient.On("CreateAppInstance", mock.Anything, mock.AnythingOfType("*edgeconnect.NewAppInstanceInput")). + Return(nil) + + ctx := context.Background() + result, err := manager.ApplyDeployment(ctx, plan, config, "test manifest content") + + require.NoError(t, err) + require.NotNil(t, result) + assert.True(t, result.Success) + assert.Len(t, result.CompletedActions, 3) // 1 app + 2 instances + assert.Len(t, result.FailedActions, 0) + + mockClient.AssertExpectations(t) +} + +func TestValidatePrerequisites(t *testing.T) { + mockClient := &MockResourceClient{} + manager := NewResourceManager(mockClient) + + tests := []struct { + name string + plan *DeploymentPlan + wantErr bool + errMsg string + }{ + { + name: "valid plan", + plan: &DeploymentPlan{ + ConfigName: "test", + AppAction: AppAction{Type: ActionCreate, Desired: &AppState{}}, + }, + wantErr: false, + }, + { + name: "empty plan", + plan: &DeploymentPlan{ + ConfigName: "test", + AppAction: AppAction{Type: ActionNone}, + }, + wantErr: true, + errMsg: "deployment plan is empty", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + err := manager.ValidatePrerequisites(ctx, tt.plan) + + if tt.wantErr { + assert.Error(t, err) + if tt.errMsg != "" { + assert.Contains(t, err.Error(), tt.errMsg) + } + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestRollbackDeployment(t *testing.T) { + mockClient := &MockResourceClient{} + logger := &TestLogger{} + manager := NewResourceManager(mockClient, WithLogger(logger), WithStrategyConfig(createTestStrategyConfig())) + + // Create result with completed actions + plan := createTestDeploymentPlan() + result := &ExecutionResult{ + Plan: plan, + CompletedActions: []ActionResult{ + { + Type: ActionCreate, + Target: "test-app", + Success: true, + }, + { + Type: ActionCreate, + Target: "test-app-1.0.0-instance", + Success: true, + }, + }, + FailedActions: []ActionResult{}, + } + + // Mock rollback operations + mockClient.On("DeleteAppInstance", mock.Anything, mock.AnythingOfType("edgeconnect.AppInstanceKey"), "US"). + Return(nil) + mockClient.On("DeleteApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). + Return(nil) + + ctx := context.Background() + err := manager.RollbackDeployment(ctx, result) + + require.NoError(t, err) + mockClient.AssertExpectations(t) + + // Check rollback was logged + assert.Greater(t, len(logger.messages), 0) +} + +func TestRollbackDeploymentFailure(t *testing.T) { + mockClient := &MockResourceClient{} + manager := NewResourceManager(mockClient) + + plan := createTestDeploymentPlan() + result := &ExecutionResult{ + Plan: plan, + CompletedActions: []ActionResult{ + { + Type: ActionCreate, + Target: "test-app", + Success: true, + }, + }, + } + + // Mock rollback failure + mockClient.On("DeleteApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). + Return(&edgeconnect.APIError{StatusCode: 500, Messages: []string{"Delete failed"}}) + + ctx := context.Background() + err := manager.RollbackDeployment(ctx, result) + + require.Error(t, err) + assert.Contains(t, err.Error(), "rollback encountered") + mockClient.AssertExpectations(t) +} + +func TestConvertNetworkRules(t *testing.T) { + network := &config.NetworkConfig{ + OutboundConnections: []config.OutboundConnection{ + { + Protocol: "tcp", + PortRangeMin: 80, + PortRangeMax: 80, + RemoteCIDR: "0.0.0.0/0", + }, + { + Protocol: "tcp", + PortRangeMin: 443, + PortRangeMax: 443, + RemoteCIDR: "10.0.0.0/8", + }, + }, + } + + rules := convertNetworkRules(network) + require.Len(t, rules, 2) + + assert.Equal(t, "tcp", rules[0].Protocol) + assert.Equal(t, 80, rules[0].PortRangeMin) + assert.Equal(t, 80, rules[0].PortRangeMax) + assert.Equal(t, "0.0.0.0/0", rules[0].RemoteCIDR) + + assert.Equal(t, "tcp", rules[1].Protocol) + assert.Equal(t, 443, rules[1].PortRangeMin) + assert.Equal(t, 443, rules[1].PortRangeMax) + assert.Equal(t, "10.0.0.0/8", rules[1].RemoteCIDR) +} diff --git a/internal/apply/v1/planner.go b/internal/apply/v1/planner.go new file mode 100644 index 0000000..33b8d9c --- /dev/null +++ b/internal/apply/v1/planner.go @@ -0,0 +1,555 @@ +// ABOUTME: Deployment planner for EdgeConnect apply command with intelligent state comparison +// ABOUTME: Analyzes desired vs current state to generate optimal deployment plans with minimal API calls +package v1 + +import ( + "context" + "crypto/sha256" + "fmt" + "io" + "os" + "strings" + "time" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" +) + +// EdgeConnectClientInterface defines the methods needed for deployment planning +type EdgeConnectClientInterface interface { + ShowApp(ctx context.Context, appKey edgeconnect.AppKey, region string) (edgeconnect.App, error) + CreateApp(ctx context.Context, input *edgeconnect.NewAppInput) error + UpdateApp(ctx context.Context, input *edgeconnect.UpdateAppInput) error + DeleteApp(ctx context.Context, appKey edgeconnect.AppKey, region string) error + ShowAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) (edgeconnect.AppInstance, error) + CreateAppInstance(ctx context.Context, input *edgeconnect.NewAppInstanceInput) error + UpdateAppInstance(ctx context.Context, input *edgeconnect.UpdateAppInstanceInput) error + DeleteAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) error +} + +// Planner defines the interface for deployment planning +type Planner interface { + // Plan analyzes the configuration and current state to generate a deployment plan + Plan(ctx context.Context, config *config.EdgeConnectConfig) (*PlanResult, error) + + // PlanWithOptions allows customization of planning behavior + PlanWithOptions(ctx context.Context, config *config.EdgeConnectConfig, opts PlanOptions) (*PlanResult, error) +} + +// PlanOptions provides configuration for the planning process +type PlanOptions struct { + // DryRun indicates this is a planning-only operation + DryRun bool + + // Force indicates to proceed even with warnings + Force bool + + // SkipStateCheck bypasses current state queries (useful for testing) + SkipStateCheck bool + + // ParallelQueries enables parallel state fetching + ParallelQueries bool + + // Timeout for API operations + Timeout time.Duration +} + +// DefaultPlanOptions returns sensible default planning options +func DefaultPlanOptions() PlanOptions { + return PlanOptions{ + DryRun: false, + Force: false, + SkipStateCheck: false, + ParallelQueries: true, + Timeout: 30 * time.Second, + } +} + +// EdgeConnectPlanner implements the Planner interface for EdgeConnect +type EdgeConnectPlanner struct { + client EdgeConnectClientInterface +} + +// NewPlanner creates a new EdgeConnect deployment planner +func NewPlanner(client EdgeConnectClientInterface) Planner { + return &EdgeConnectPlanner{ + client: client, + } +} + +// Plan analyzes the configuration and generates a deployment plan +func (p *EdgeConnectPlanner) Plan(ctx context.Context, config *config.EdgeConnectConfig) (*PlanResult, error) { + return p.PlanWithOptions(ctx, config, DefaultPlanOptions()) +} + +// PlanWithOptions generates a deployment plan with custom options +func (p *EdgeConnectPlanner) PlanWithOptions(ctx context.Context, config *config.EdgeConnectConfig, opts PlanOptions) (*PlanResult, error) { + startTime := time.Now() + var warnings []string + + // Create the deployment plan structure + plan := &DeploymentPlan{ + ConfigName: config.Metadata.Name, + CreatedAt: startTime, + DryRun: opts.DryRun, + } + + // Step 1: Plan application state + appAction, appWarnings, err := p.planAppAction(ctx, config, opts) + if err != nil { + return &PlanResult{Error: err}, err + } + plan.AppAction = *appAction + warnings = append(warnings, appWarnings...) + + // Step 2: Plan instance actions + instanceActions, instanceWarnings, err := p.planInstanceActions(ctx, config, opts) + if err != nil { + return &PlanResult{Error: err}, err + } + plan.InstanceActions = instanceActions + warnings = append(warnings, instanceWarnings...) + + // Step 3: Calculate plan metadata + p.calculatePlanMetadata(plan) + + // Step 4: Generate summary + plan.Summary = plan.GenerateSummary() + + // Step 5: Validate the plan + if err := plan.Validate(); err != nil { + return &PlanResult{Error: fmt.Errorf("invalid deployment plan: %w", err)}, err + } + + return &PlanResult{ + Plan: plan, + Warnings: warnings, + }, nil +} + +// planAppAction determines what action needs to be taken for the application +func (p *EdgeConnectPlanner) planAppAction(ctx context.Context, config *config.EdgeConnectConfig, opts PlanOptions) (*AppAction, []string, error) { + var warnings []string + + // Build desired app state + desired := &AppState{ + Name: config.Metadata.Name, + Version: config.Metadata.AppVersion, + Organization: config.Metadata.Organization, // Use first infra template for org + Region: config.Spec.InfraTemplate[0].Region, // Use first infra template for region + Exists: false, // Will be set based on current state + } + + if config.Spec.IsK8sApp() { + desired.AppType = AppTypeK8s + } else { + desired.AppType = AppTypeDocker + } + + // Extract outbound connections from config + if config.Spec.Network != nil { + desired.OutboundConnections = make([]SecurityRule, len(config.Spec.Network.OutboundConnections)) + for i, conn := range config.Spec.Network.OutboundConnections { + desired.OutboundConnections[i] = SecurityRule{ + Protocol: conn.Protocol, + PortRangeMin: conn.PortRangeMin, + PortRangeMax: conn.PortRangeMax, + RemoteCIDR: conn.RemoteCIDR, + } + } + } + + // Calculate manifest hash + manifestHash, err := p.calculateManifestHash(config.Spec.GetManifestFile()) + if err != nil { + return nil, warnings, fmt.Errorf("failed to calculate manifest hash: %w", err) + } + desired.ManifestHash = manifestHash + + action := &AppAction{ + Type: ActionNone, + Desired: desired, + ManifestHash: manifestHash, + Reason: "No action needed", + } + + // Skip state check if requested (useful for testing) + if opts.SkipStateCheck { + action.Type = ActionCreate + action.Reason = "Creating app (state check skipped)" + action.Changes = []string{"Create new application"} + return action, warnings, nil + } + + // Query current app state + current, err := p.getCurrentAppState(ctx, desired, opts.Timeout) + if err != nil { + // If app doesn't exist, we need to create it + if isResourceNotFoundError(err) { + action.Type = ActionCreate + action.Reason = "Application does not exist" + action.Changes = []string{"Create new application"} + return action, warnings, nil + } + return nil, warnings, fmt.Errorf("failed to query current app state: %w", err) + } + + action.Current = current + + // Compare current vs desired state + changes, manifestChanged := p.compareAppStates(current, desired) + action.ManifestChanged = manifestChanged + + if len(changes) > 0 { + action.Type = ActionUpdate + action.Changes = changes + action.Reason = "Application configuration has changed" + fmt.Printf("Changes: %v\n", changes) + + if manifestChanged { + warnings = append(warnings, "Manifest file has changed - instances may need to be recreated") + } + } + + return action, warnings, nil +} + +// planInstanceActions determines what actions need to be taken for instances +func (p *EdgeConnectPlanner) planInstanceActions(ctx context.Context, config *config.EdgeConnectConfig, opts PlanOptions) ([]InstanceAction, []string, error) { + var actions []InstanceAction + var warnings []string + + for _, infra := range config.Spec.InfraTemplate { + instanceName := getInstanceName(config.Metadata.Name, config.Metadata.AppVersion) + + desired := &InstanceState{ + Name: instanceName, + AppVersion: config.Metadata.AppVersion, + Organization: config.Metadata.Organization, + Region: infra.Region, + CloudletOrg: infra.CloudletOrg, + CloudletName: infra.CloudletName, + FlavorName: infra.FlavorName, + Exists: false, + } + + action := &InstanceAction{ + Type: ActionNone, + Target: infra, + Desired: desired, + InstanceName: instanceName, + Reason: "No action needed", + } + + // Skip state check if requested + if opts.SkipStateCheck { + action.Type = ActionCreate + action.Reason = "Creating instance (state check skipped)" + action.Changes = []string{"Create new instance"} + actions = append(actions, *action) + continue + } + + // Query current instance state + current, err := p.getCurrentInstanceState(ctx, desired, opts.Timeout) + if err != nil { + // If instance doesn't exist, we need to create it + if isResourceNotFoundError(err) { + action.Type = ActionCreate + action.Reason = "Instance does not exist" + action.Changes = []string{"Create new instance"} + actions = append(actions, *action) + continue + } + return nil, warnings, fmt.Errorf("failed to query current instance state: %w", err) + } + + action.Current = current + + // Compare current vs desired state + changes := p.compareInstanceStates(current, desired) + if len(changes) > 0 { + action.Type = ActionUpdate + action.Changes = changes + action.Reason = "Instance configuration has changed" + } + + actions = append(actions, *action) + } + + return actions, warnings, nil +} + +// getCurrentAppState queries the current state of an application +func (p *EdgeConnectPlanner) getCurrentAppState(ctx context.Context, desired *AppState, timeout time.Duration) (*AppState, error) { + timeoutCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + appKey := edgeconnect.AppKey{ + Organization: desired.Organization, + Name: desired.Name, + Version: desired.Version, + } + + app, err := p.client.ShowApp(timeoutCtx, appKey, desired.Region) + if err != nil { + return nil, err + } + + current := &AppState{ + Name: app.Key.Name, + Version: app.Key.Version, + Organization: app.Key.Organization, + Region: desired.Region, + Exists: true, + LastUpdated: time.Now(), // EdgeConnect doesn't provide this, so use current time + } + + // Calculate current manifest hash + hasher := sha256.New() + hasher.Write([]byte(app.DeploymentManifest)) + current.ManifestHash = fmt.Sprintf("%x", hasher.Sum(nil)) + + // Note: EdgeConnect API doesn't currently support annotations for manifest hash tracking + // This would be implemented when the API supports it + + // Determine app type based on deployment type + if app.Deployment == "kubernetes" { + current.AppType = AppTypeK8s + } else { + current.AppType = AppTypeDocker + } + + // Extract outbound connections from the app + current.OutboundConnections = make([]SecurityRule, len(app.RequiredOutboundConnections)) + for i, conn := range app.RequiredOutboundConnections { + current.OutboundConnections[i] = SecurityRule{ + Protocol: conn.Protocol, + PortRangeMin: conn.PortRangeMin, + PortRangeMax: conn.PortRangeMax, + RemoteCIDR: conn.RemoteCIDR, + } + } + + return current, nil +} + +// getCurrentInstanceState queries the current state of an application instance +func (p *EdgeConnectPlanner) getCurrentInstanceState(ctx context.Context, desired *InstanceState, timeout time.Duration) (*InstanceState, error) { + timeoutCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + instanceKey := edgeconnect.AppInstanceKey{ + Organization: desired.Organization, + Name: desired.Name, + CloudletKey: edgeconnect.CloudletKey{ + Organization: desired.CloudletOrg, + Name: desired.CloudletName, + }, + } + + instance, err := p.client.ShowAppInstance(timeoutCtx, instanceKey, desired.Region) + if err != nil { + return nil, err + } + + current := &InstanceState{ + Name: instance.Key.Name, + AppName: instance.AppKey.Name, + AppVersion: instance.AppKey.Version, + Organization: instance.Key.Organization, + Region: desired.Region, + CloudletOrg: instance.Key.CloudletKey.Organization, + CloudletName: instance.Key.CloudletKey.Name, + FlavorName: instance.Flavor.Name, + State: instance.State, + PowerState: instance.PowerState, + Exists: true, + LastUpdated: time.Now(), // EdgeConnect doesn't provide this + } + + return current, nil +} + +// compareAppStates compares current and desired app states and returns changes +func (p *EdgeConnectPlanner) compareAppStates(current, desired *AppState) ([]string, bool) { + var changes []string + manifestChanged := false + + // Compare manifest hash - only if both states have hash values + // Since EdgeConnect API doesn't support annotations yet, skip manifest hash comparison for now + // This would be implemented when the API supports manifest hash tracking + if current.ManifestHash != "" && desired.ManifestHash != "" && current.ManifestHash != desired.ManifestHash { + changes = append(changes, fmt.Sprintf("Manifest hash changed: %s -> %s", current.ManifestHash, desired.ManifestHash)) + manifestChanged = true + } + + // Compare app type + if current.AppType != desired.AppType { + changes = append(changes, fmt.Sprintf("App type changed: %s -> %s", current.AppType, desired.AppType)) + } + + // Compare outbound connections + outboundChanges := p.compareOutboundConnections(current.OutboundConnections, desired.OutboundConnections) + if len(outboundChanges) > 0 { + sb:= strings.Builder{} + sb.WriteString("Outbound connections changed:\n") + for _, change := range outboundChanges { + sb.WriteString(change) + sb.WriteString("\n") + } + changes = append(changes, sb.String()) + } + + return changes, manifestChanged +} + +// compareOutboundConnections compares two sets of outbound connections for equality +func (p *EdgeConnectPlanner) compareOutboundConnections(current, desired []SecurityRule) []string { + var changes []string + makeMap := func(rules []SecurityRule) map[string]SecurityRule { + m := make(map[string]SecurityRule, len(rules)) + for _, r := range rules { + key := fmt.Sprintf("%s:%d-%d:%s", + strings.ToLower(r.Protocol), + r.PortRangeMin, + r.PortRangeMax, + r.RemoteCIDR, + ) + m[key] = r + } + return m + } + + currentMap := makeMap(current) + desiredMap := makeMap(desired) + + // Find added and modified rules + for key, rule := range desiredMap { + if _, exists := currentMap[key]; !exists { + changes = append(changes, fmt.Sprintf(" - Added outbound connection: %s %d-%d to %s", rule.Protocol, rule.PortRangeMin, rule.PortRangeMax, rule.RemoteCIDR)) + } + } + + // Find removed rules + for key, rule := range currentMap { + if _, exists := desiredMap[key]; !exists { + changes = append(changes, fmt.Sprintf(" - Removed outbound connection: %s %d-%d to %s", rule.Protocol, rule.PortRangeMin, rule.PortRangeMax, rule.RemoteCIDR)) + } + } + + return changes +} + +// compareInstanceStates compares current and desired instance states and returns changes +func (p *EdgeConnectPlanner) compareInstanceStates(current, desired *InstanceState) []string { + var changes []string + + if current.FlavorName != desired.FlavorName { + changes = append(changes, fmt.Sprintf("Flavor changed: %s -> %s", current.FlavorName, desired.FlavorName)) + } + + if current.CloudletName != desired.CloudletName { + changes = append(changes, fmt.Sprintf("Cloudlet changed: %s -> %s", current.CloudletName, desired.CloudletName)) + } + + if current.CloudletOrg != desired.CloudletOrg { + changes = append(changes, fmt.Sprintf("Cloudlet org changed: %s -> %s", current.CloudletOrg, desired.CloudletOrg)) + } + + return changes +} + +// calculateManifestHash computes the SHA256 hash of a manifest file +func (p *EdgeConnectPlanner) calculateManifestHash(manifestPath string) (string, error) { + if manifestPath == "" { + return "", nil + } + + file, err := os.Open(manifestPath) + if err != nil { + return "", fmt.Errorf("failed to open manifest file: %w", err) + } + defer file.Close() + + hasher := sha256.New() + if _, err := io.Copy(hasher, file); err != nil { + return "", fmt.Errorf("failed to hash manifest file: %w", err) + } + + return fmt.Sprintf("%x", hasher.Sum(nil)), nil +} + +// calculatePlanMetadata computes metadata for the deployment plan +func (p *EdgeConnectPlanner) calculatePlanMetadata(plan *DeploymentPlan) { + totalActions := 0 + + if plan.AppAction.Type != ActionNone { + totalActions++ + } + + for _, action := range plan.InstanceActions { + if action.Type != ActionNone { + totalActions++ + } + } + + plan.TotalActions = totalActions + + // Estimate duration based on action types and counts + plan.EstimatedDuration = p.estimateDeploymentDuration(plan) +} + +// estimateDeploymentDuration provides a rough estimate of deployment time +func (p *EdgeConnectPlanner) estimateDeploymentDuration(plan *DeploymentPlan) time.Duration { + var duration time.Duration + + // App operations + if plan.AppAction.Type == ActionCreate { + duration += 30 * time.Second + } else if plan.AppAction.Type == ActionUpdate { + duration += 15 * time.Second + } + + // Instance operations (can be done in parallel) + instanceDuration := time.Duration(0) + for _, action := range plan.InstanceActions { + if action.Type == ActionCreate { + instanceDuration = max(instanceDuration, 2*time.Minute) + } else if action.Type == ActionUpdate { + instanceDuration = max(instanceDuration, 1*time.Minute) + } + } + + duration += instanceDuration + + // Add buffer time + duration += 30 * time.Second + + return duration +} + +// isResourceNotFoundError checks if an error indicates a resource was not found +func isResourceNotFoundError(err error) bool { + if err == nil { + return false + } + + errStr := strings.ToLower(err.Error()) + return strings.Contains(errStr, "not found") || + strings.Contains(errStr, "does not exist") || + strings.Contains(errStr, "404") +} + +// max returns the larger of two durations +func max(a, b time.Duration) time.Duration { + if a > b { + return a + } + return b +} + +// getInstanceName generates the instance name following the pattern: appName-appVersion-instance +func getInstanceName(appName, appVersion string) string { + return fmt.Sprintf("%s-%s-instance", appName, appVersion) +} diff --git a/internal/apply/v1/planner_test.go b/internal/apply/v1/planner_test.go new file mode 100644 index 0000000..8c1e48a --- /dev/null +++ b/internal/apply/v1/planner_test.go @@ -0,0 +1,663 @@ +// ABOUTME: Comprehensive tests for EdgeConnect deployment planner with mock scenarios +// ABOUTME: Tests planning logic, state comparison, and various deployment scenarios +package v1 + +import ( + "context" + "os" + "path/filepath" + "testing" + "time" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +// MockEdgeConnectClient is a mock implementation of the EdgeConnect client +type MockEdgeConnectClient struct { + mock.Mock +} + +func (m *MockEdgeConnectClient) ShowApp(ctx context.Context, appKey edgeconnect.AppKey, region string) (edgeconnect.App, error) { + args := m.Called(ctx, appKey, region) + if args.Get(0) == nil { + return edgeconnect.App{}, args.Error(1) + } + return args.Get(0).(edgeconnect.App), args.Error(1) +} + +func (m *MockEdgeConnectClient) ShowAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) (edgeconnect.AppInstance, error) { + args := m.Called(ctx, instanceKey, region) + if args.Get(0) == nil { + return edgeconnect.AppInstance{}, args.Error(1) + } + return args.Get(0).(edgeconnect.AppInstance), args.Error(1) +} + +func (m *MockEdgeConnectClient) CreateApp(ctx context.Context, input *edgeconnect.NewAppInput) error { + args := m.Called(ctx, input) + return args.Error(0) +} + +func (m *MockEdgeConnectClient) CreateAppInstance(ctx context.Context, input *edgeconnect.NewAppInstanceInput) error { + args := m.Called(ctx, input) + return args.Error(0) +} + +func (m *MockEdgeConnectClient) DeleteApp(ctx context.Context, appKey edgeconnect.AppKey, region string) error { + args := m.Called(ctx, appKey, region) + return args.Error(0) +} + +func (m *MockEdgeConnectClient) UpdateApp(ctx context.Context, input *edgeconnect.UpdateAppInput) error { + args := m.Called(ctx, input) + return args.Error(0) +} + +func (m *MockEdgeConnectClient) UpdateAppInstance(ctx context.Context, input *edgeconnect.UpdateAppInstanceInput) error { + args := m.Called(ctx, input) + return args.Error(0) +} + +func (m *MockEdgeConnectClient) DeleteAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) error { + args := m.Called(ctx, instanceKey, region) + return args.Error(0) +} + +func (m *MockEdgeConnectClient) ShowApps(ctx context.Context, appKey edgeconnect.AppKey, region string) ([]edgeconnect.App, error) { + args := m.Called(ctx, appKey, region) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]edgeconnect.App), args.Error(1) +} + +func (m *MockEdgeConnectClient) ShowAppInstances(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) ([]edgeconnect.AppInstance, error) { + args := m.Called(ctx, instanceKey, region) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]edgeconnect.AppInstance), args.Error(1) +} + +func TestNewPlanner(t *testing.T) { + mockClient := &MockEdgeConnectClient{} + planner := NewPlanner(mockClient) + + assert.NotNil(t, planner) + assert.IsType(t, &EdgeConnectPlanner{}, planner) +} + +func TestDefaultPlanOptions(t *testing.T) { + opts := DefaultPlanOptions() + + assert.False(t, opts.DryRun) + assert.False(t, opts.Force) + assert.False(t, opts.SkipStateCheck) + assert.True(t, opts.ParallelQueries) + assert.Equal(t, 30*time.Second, opts.Timeout) +} + +func createTestConfig(t *testing.T) *config.EdgeConnectConfig { + // Create temporary manifest file + tempDir := t.TempDir() + manifestFile := filepath.Join(tempDir, "test-manifest.yaml") + manifestContent := "apiVersion: v1\nkind: Pod\nmetadata:\n name: test\n" + err := os.WriteFile(manifestFile, []byte(manifestContent), 0644) + require.NoError(t, err) + + return &config.EdgeConnectConfig{ + Kind: "edgeconnect-deployment", + Metadata: config.Metadata{ + Name: "test-app", + AppVersion: "1.0.0", + Organization: "testorg", + }, + Spec: config.Spec{ + K8sApp: &config.K8sApp{ + ManifestFile: manifestFile, + }, + InfraTemplate: []config.InfraTemplate{ + { + Region: "US", + CloudletOrg: "TestCloudletOrg", + CloudletName: "TestCloudlet", + FlavorName: "small", + }, + }, + Network: &config.NetworkConfig{ + OutboundConnections: []config.OutboundConnection{ + { + Protocol: "tcp", + PortRangeMin: 80, + PortRangeMax: 80, + RemoteCIDR: "0.0.0.0/0", + }, + }, + }, + }, + } +} + +func TestPlanNewDeployment(t *testing.T) { + mockClient := &MockEdgeConnectClient{} + planner := NewPlanner(mockClient) + testConfig := createTestConfig(t) + + // Mock API calls to return "not found" errors + mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). + Return(nil, &edgeconnect.APIError{StatusCode: 404, Messages: []string{"App not found"}}) + + mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("edgeconnect.AppInstanceKey"), "US"). + Return(nil, &edgeconnect.APIError{StatusCode: 404, Messages: []string{"Instance not found"}}) + + ctx := context.Background() + result, err := planner.Plan(ctx, testConfig) + + require.NoError(t, err) + require.NotNil(t, result) + require.NotNil(t, result.Plan) + require.NoError(t, result.Error) + + plan := result.Plan + assert.Equal(t, "test-app", plan.ConfigName) + assert.Equal(t, ActionCreate, plan.AppAction.Type) + assert.Equal(t, "Application does not exist", plan.AppAction.Reason) + + require.Len(t, plan.InstanceActions, 1) + assert.Equal(t, ActionCreate, plan.InstanceActions[0].Type) + assert.Equal(t, "Instance does not exist", plan.InstanceActions[0].Reason) + + assert.Equal(t, 2, plan.TotalActions) // 1 app + 1 instance + assert.False(t, plan.IsEmpty()) + + mockClient.AssertExpectations(t) +} + +func TestPlanExistingDeploymentNoChanges(t *testing.T) { + mockClient := &MockEdgeConnectClient{} + planner := NewPlanner(mockClient) + testConfig := createTestConfig(t) + + // Note: We would calculate expected manifest hash here when API supports it + + // Mock existing app with same manifest hash and outbound connections + manifestContent := "apiVersion: v1\nkind: Pod\nmetadata:\n name: test\n" + existingApp := &edgeconnect.App{ + Key: edgeconnect.AppKey{ + Organization: "testorg", + Name: "test-app", + Version: "1.0.0", + }, + Deployment: "kubernetes", + DeploymentManifest: manifestContent, + RequiredOutboundConnections: []edgeconnect.SecurityRule{ + { + Protocol: "tcp", + PortRangeMin: 80, + PortRangeMax: 80, + RemoteCIDR: "0.0.0.0/0", + }, + }, + // Note: Manifest hash tracking would be implemented when API supports annotations + } + + // Mock existing instance + existingInstance := &edgeconnect.AppInstance{ + Key: edgeconnect.AppInstanceKey{ + Organization: "testorg", + Name: "test-app-1.0.0-instance", + CloudletKey: edgeconnect.CloudletKey{ + Organization: "TestCloudletOrg", + Name: "TestCloudlet", + }, + }, + AppKey: edgeconnect.AppKey{ + Organization: "testorg", + Name: "test-app", + Version: "1.0.0", + }, + Flavor: edgeconnect.Flavor{ + Name: "small", + }, + State: "Ready", + PowerState: "PowerOn", + } + + mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). + Return(*existingApp, nil) + + mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("edgeconnect.AppInstanceKey"), "US"). + Return(*existingInstance, nil) + + ctx := context.Background() + result, err := planner.Plan(ctx, testConfig) + + require.NoError(t, err) + require.NotNil(t, result) + require.NotNil(t, result.Plan) + + plan := result.Plan + assert.Equal(t, ActionNone, plan.AppAction.Type) + assert.Len(t, plan.InstanceActions, 1) + assert.Equal(t, ActionNone, plan.InstanceActions[0].Type) + assert.Equal(t, 0, plan.TotalActions) + assert.True(t, plan.IsEmpty()) + assert.Contains(t, plan.Summary, "No changes required") + + mockClient.AssertExpectations(t) +} + +func TestPlanWithOptions(t *testing.T) { + mockClient := &MockEdgeConnectClient{} + planner := NewPlanner(mockClient) + testConfig := createTestConfig(t) + + opts := PlanOptions{ + DryRun: true, + SkipStateCheck: true, + Timeout: 10 * time.Second, + } + + ctx := context.Background() + result, err := planner.PlanWithOptions(ctx, testConfig, opts) + + require.NoError(t, err) + require.NotNil(t, result) + require.NotNil(t, result.Plan) + + plan := result.Plan + assert.True(t, plan.DryRun) + assert.Equal(t, ActionCreate, plan.AppAction.Type) + assert.Contains(t, plan.AppAction.Reason, "state check skipped") + + // No API calls should be made when SkipStateCheck is true + mockClient.AssertNotCalled(t, "ShowApp") + mockClient.AssertNotCalled(t, "ShowAppInstance") +} + +func TestPlanMultipleInfrastructures(t *testing.T) { + mockClient := &MockEdgeConnectClient{} + planner := NewPlanner(mockClient) + testConfig := createTestConfig(t) + + // Add a second infrastructure target + testConfig.Spec.InfraTemplate = append(testConfig.Spec.InfraTemplate, config.InfraTemplate{ + Region: "EU", + CloudletOrg: "EUCloudletOrg", + CloudletName: "EUCloudlet", + FlavorName: "medium", + }) + + // Mock API calls to return "not found" errors + mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). + Return(nil, &edgeconnect.APIError{StatusCode: 404, Messages: []string{"App not found"}}) + + mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("edgeconnect.AppInstanceKey"), "US"). + Return(nil, &edgeconnect.APIError{StatusCode: 404, Messages: []string{"Instance not found"}}) + + mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("edgeconnect.AppInstanceKey"), "EU"). + Return(nil, &edgeconnect.APIError{StatusCode: 404, Messages: []string{"Instance not found"}}) + + ctx := context.Background() + result, err := planner.Plan(ctx, testConfig) + + require.NoError(t, err) + require.NotNil(t, result) + require.NotNil(t, result.Plan) + + plan := result.Plan + assert.Equal(t, ActionCreate, plan.AppAction.Type) + + // Should have 2 instance actions, one for each infrastructure + require.Len(t, plan.InstanceActions, 2) + assert.Equal(t, ActionCreate, plan.InstanceActions[0].Type) + assert.Equal(t, ActionCreate, plan.InstanceActions[1].Type) + + assert.Equal(t, 3, plan.TotalActions) // 1 app + 2 instances + + // Test cloudlet and region aggregation + cloudlets := plan.GetTargetCloudlets() + regions := plan.GetTargetRegions() + assert.Len(t, cloudlets, 2) + assert.Len(t, regions, 2) + + mockClient.AssertExpectations(t) +} + +func TestCalculateManifestHash(t *testing.T) { + planner := &EdgeConnectPlanner{} + tempDir := t.TempDir() + + // Create test file + testFile := filepath.Join(tempDir, "test.yaml") + content := "test content for hashing" + err := os.WriteFile(testFile, []byte(content), 0644) + require.NoError(t, err) + + hash1, err := planner.calculateManifestHash(testFile) + require.NoError(t, err) + assert.NotEmpty(t, hash1) + assert.Len(t, hash1, 64) // SHA256 hex string length + + // Same content should produce same hash + hash2, err := planner.calculateManifestHash(testFile) + require.NoError(t, err) + assert.Equal(t, hash1, hash2) + + // Different content should produce different hash + err = os.WriteFile(testFile, []byte("different content"), 0644) + require.NoError(t, err) + + hash3, err := planner.calculateManifestHash(testFile) + require.NoError(t, err) + assert.NotEqual(t, hash1, hash3) + + // Empty file path should return empty hash + hash4, err := planner.calculateManifestHash("") + require.NoError(t, err) + assert.Empty(t, hash4) + + // Non-existent file should return error + _, err = planner.calculateManifestHash("/non/existent/file") + assert.Error(t, err) +} + +func TestCompareAppStates(t *testing.T) { + planner := &EdgeConnectPlanner{} + + current := &AppState{ + Name: "test-app", + Version: "1.0.0", + AppType: AppTypeK8s, + ManifestHash: "old-hash", + } + + desired := &AppState{ + Name: "test-app", + Version: "1.0.0", + AppType: AppTypeK8s, + ManifestHash: "new-hash", + } + + changes, manifestChanged := planner.compareAppStates(current, desired) + assert.Len(t, changes, 1) + assert.True(t, manifestChanged) + assert.Contains(t, changes[0], "Manifest hash changed") + + // Test no changes + desired.ManifestHash = "old-hash" + changes, manifestChanged = planner.compareAppStates(current, desired) + assert.Empty(t, changes) + assert.False(t, manifestChanged) + + // Test app type change + desired.AppType = AppTypeDocker + changes, manifestChanged = planner.compareAppStates(current, desired) + assert.Len(t, changes, 1) + assert.False(t, manifestChanged) + assert.Contains(t, changes[0], "App type changed") +} + +func TestCompareAppStatesOutboundConnections(t *testing.T) { + planner := &EdgeConnectPlanner{} + + // Test with no outbound connections + current := &AppState{ + Name: "test-app", + Version: "1.0.0", + AppType: AppTypeK8s, + OutboundConnections: nil, + } + + desired := &AppState{ + Name: "test-app", + Version: "1.0.0", + AppType: AppTypeK8s, + OutboundConnections: nil, + } + + changes, _ := planner.compareAppStates(current, desired) + assert.Empty(t, changes, "No changes expected when both have no outbound connections") + + // Test adding outbound connections + desired.OutboundConnections = []SecurityRule{ + { + Protocol: "tcp", + PortRangeMin: 80, + PortRangeMax: 80, + RemoteCIDR: "0.0.0.0/0", + }, + } + + changes, _ = planner.compareAppStates(current, desired) + assert.Len(t, changes, 1) + assert.Contains(t, changes[0], "Outbound connections changed") + + // Test identical outbound connections + current.OutboundConnections = []SecurityRule{ + { + Protocol: "tcp", + PortRangeMin: 80, + PortRangeMax: 80, + RemoteCIDR: "0.0.0.0/0", + }, + } + + changes, _ = planner.compareAppStates(current, desired) + assert.Empty(t, changes, "No changes expected when outbound connections are identical") + + // Test different outbound connections (different port) + desired.OutboundConnections[0].PortRangeMin = 443 + desired.OutboundConnections[0].PortRangeMax = 443 + + changes, _ = planner.compareAppStates(current, desired) + assert.Len(t, changes, 1) + assert.Contains(t, changes[0], "Outbound connections changed") + + // Test same connections but different order (should be considered equal) + current.OutboundConnections = []SecurityRule{ + { + Protocol: "tcp", + PortRangeMin: 80, + PortRangeMax: 80, + RemoteCIDR: "0.0.0.0/0", + }, + { + Protocol: "tcp", + PortRangeMin: 443, + PortRangeMax: 443, + RemoteCIDR: "0.0.0.0/0", + }, + } + + desired.OutboundConnections = []SecurityRule{ + { + Protocol: "tcp", + PortRangeMin: 443, + PortRangeMax: 443, + RemoteCIDR: "0.0.0.0/0", + }, + { + Protocol: "tcp", + PortRangeMin: 80, + PortRangeMax: 80, + RemoteCIDR: "0.0.0.0/0", + }, + } + + changes, _ = planner.compareAppStates(current, desired) + assert.Empty(t, changes, "No changes expected when outbound connections are same but in different order") + + // Test removing outbound connections + desired.OutboundConnections = nil + + changes, _ = planner.compareAppStates(current, desired) + assert.Len(t, changes, 1) + assert.Contains(t, changes[0], "Outbound connections changed") +} + +func TestCompareInstanceStates(t *testing.T) { + planner := &EdgeConnectPlanner{} + + current := &InstanceState{ + Name: "test-instance", + FlavorName: "small", + CloudletName: "oldcloudlet", + CloudletOrg: "oldorg", + } + + desired := &InstanceState{ + Name: "test-instance", + FlavorName: "medium", + CloudletName: "newcloudlet", + CloudletOrg: "neworg", + } + + changes := planner.compareInstanceStates(current, desired) + assert.Len(t, changes, 3) + assert.Contains(t, changes[0], "Flavor changed") + assert.Contains(t, changes[1], "Cloudlet changed") + assert.Contains(t, changes[2], "Cloudlet org changed") + + // Test no changes + desired.FlavorName = "small" + desired.CloudletName = "oldcloudlet" + desired.CloudletOrg = "oldorg" + changes = planner.compareInstanceStates(current, desired) + assert.Empty(t, changes) +} + +func TestDeploymentPlanMethods(t *testing.T) { + plan := &DeploymentPlan{ + ConfigName: "test-plan", + AppAction: AppAction{ + Type: ActionCreate, + Desired: &AppState{Name: "test-app"}, + }, + InstanceActions: []InstanceAction{ + { + Type: ActionCreate, + Target: config.InfraTemplate{ + CloudletOrg: "org1", + CloudletName: "cloudlet1", + Region: "US", + }, + InstanceName: "instance1", + Desired: &InstanceState{Name: "instance1"}, + }, + { + Type: ActionUpdate, + Target: config.InfraTemplate{ + CloudletOrg: "org2", + CloudletName: "cloudlet2", + Region: "EU", + }, + InstanceName: "instance2", + Desired: &InstanceState{Name: "instance2"}, + }, + }, + } + + // Test IsEmpty + assert.False(t, plan.IsEmpty()) + + // Test GetTargetCloudlets + cloudlets := plan.GetTargetCloudlets() + assert.Len(t, cloudlets, 2) + assert.Contains(t, cloudlets, "org1:cloudlet1") + assert.Contains(t, cloudlets, "org2:cloudlet2") + + // Test GetTargetRegions + regions := plan.GetTargetRegions() + assert.Len(t, regions, 2) + assert.Contains(t, regions, "US") + assert.Contains(t, regions, "EU") + + // Test GenerateSummary + summary := plan.GenerateSummary() + assert.Contains(t, summary, "test-plan") + assert.Contains(t, summary, "CREATE application") + assert.Contains(t, summary, "CREATE 1 instance") + assert.Contains(t, summary, "UPDATE 1 instance") + + // Test Validate + err := plan.Validate() + assert.NoError(t, err) + + // Test validation failure + plan.AppAction.Desired = nil + err = plan.Validate() + assert.Error(t, err) + assert.Contains(t, err.Error(), "must have desired state") +} + +func TestEstimateDeploymentDuration(t *testing.T) { + planner := &EdgeConnectPlanner{} + + plan := &DeploymentPlan{ + AppAction: AppAction{Type: ActionCreate}, + InstanceActions: []InstanceAction{ + {Type: ActionCreate}, + {Type: ActionUpdate}, + }, + } + + duration := planner.estimateDeploymentDuration(plan) + assert.Greater(t, duration, time.Duration(0)) + assert.Less(t, duration, 10*time.Minute) // Reasonable upper bound + + // Test with no actions + emptyPlan := &DeploymentPlan{ + AppAction: AppAction{Type: ActionNone}, + InstanceActions: []InstanceAction{}, + } + + emptyDuration := planner.estimateDeploymentDuration(emptyPlan) + assert.Greater(t, emptyDuration, time.Duration(0)) + assert.Less(t, emptyDuration, duration) // Should be less than plan with actions +} + +func TestIsResourceNotFoundError(t *testing.T) { + tests := []struct { + name string + err error + expected bool + }{ + {"nil error", nil, false}, + {"not found error", &edgeconnect.APIError{StatusCode: 404, Messages: []string{"Resource not found"}}, true}, + {"does not exist error", &edgeconnect.APIError{Messages: []string{"App does not exist"}}, true}, + {"404 in message", &edgeconnect.APIError{Messages: []string{"HTTP 404 error"}}, true}, + {"other error", &edgeconnect.APIError{StatusCode: 500, Messages: []string{"Server error"}}, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isResourceNotFoundError(tt.err) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestPlanErrorHandling(t *testing.T) { + mockClient := &MockEdgeConnectClient{} + planner := NewPlanner(mockClient) + testConfig := createTestConfig(t) + + // Mock API call to return a non-404 error + mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("edgeconnect.AppKey"), "US"). + Return(nil, &edgeconnect.APIError{StatusCode: 500, Messages: []string{"Server error"}}) + + ctx := context.Background() + result, err := planner.Plan(ctx, testConfig) + + assert.Error(t, err) + assert.NotNil(t, result) + assert.NotNil(t, result.Error) + assert.Contains(t, err.Error(), "failed to query current app state") + + mockClient.AssertExpectations(t) +} diff --git a/internal/apply/strategy.go b/internal/apply/v1/strategy.go similarity index 99% rename from internal/apply/strategy.go rename to internal/apply/v1/strategy.go index 8d32d2e..44f2471 100644 --- a/internal/apply/strategy.go +++ b/internal/apply/v1/strategy.go @@ -1,6 +1,6 @@ // ABOUTME: Deployment strategy framework for EdgeConnect apply command // ABOUTME: Defines interfaces and types for different deployment strategies (recreate, blue-green, rolling) -package apply +package v1 import ( "context" diff --git a/internal/apply/v1/strategy_recreate.go b/internal/apply/v1/strategy_recreate.go new file mode 100644 index 0000000..1f6f121 --- /dev/null +++ b/internal/apply/v1/strategy_recreate.go @@ -0,0 +1,548 @@ +// ABOUTME: Recreate deployment strategy implementation for EdgeConnect +// ABOUTME: Handles delete-all, update-app, create-all deployment pattern with retries and parallel execution +package v1 + +import ( + "context" + "errors" + "fmt" + "strings" + "sync" + "time" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" +) + +// RecreateStrategy implements the recreate deployment strategy +type RecreateStrategy struct { + client EdgeConnectClientInterface + config StrategyConfig + logger Logger +} + +// NewRecreateStrategy creates a new recreate strategy executor +func NewRecreateStrategy(client EdgeConnectClientInterface, config StrategyConfig, logger Logger) *RecreateStrategy { + return &RecreateStrategy{ + client: client, + config: config, + logger: logger, + } +} + +// GetName returns the strategy name +func (r *RecreateStrategy) GetName() DeploymentStrategy { + return StrategyRecreate +} + +// Validate checks if the recreate strategy can be used for this deployment +func (r *RecreateStrategy) Validate(plan *DeploymentPlan) error { + // Recreate strategy can be used for any deployment + // No specific constraints for recreate + return nil +} + +// EstimateDuration estimates the time needed for recreate deployment +func (r *RecreateStrategy) EstimateDuration(plan *DeploymentPlan) time.Duration { + var duration time.Duration + + // Delete phase - estimate based on number of instances + instanceCount := len(plan.InstanceActions) + if instanceCount > 0 { + deleteTime := time.Duration(instanceCount) * 30 * time.Second + if r.config.ParallelOperations { + deleteTime = 30 * time.Second // Parallel deletion + } + duration += deleteTime + } + + // App update phase + if plan.AppAction.Type == ActionUpdate { + duration += 30 * time.Second + } + + // Create phase - estimate based on number of instances + if instanceCount > 0 { + createTime := time.Duration(instanceCount) * 2 * time.Minute + if r.config.ParallelOperations { + createTime = 2 * time.Minute // Parallel creation + } + duration += createTime + } + + // Health check time + duration += r.config.HealthCheckTimeout + + // Add retry buffer (potential retries) + retryBuffer := time.Duration(r.config.MaxRetries) * r.config.RetryDelay + duration += retryBuffer + + return duration +} + +// Execute runs the recreate deployment strategy +func (r *RecreateStrategy) Execute(ctx context.Context, plan *DeploymentPlan, config *config.EdgeConnectConfig, manifestContent string) (*ExecutionResult, error) { + startTime := time.Now() + r.logf("Starting recreate deployment strategy for: %s", plan.ConfigName) + + result := &ExecutionResult{ + Plan: plan, + CompletedActions: []ActionResult{}, + FailedActions: []ActionResult{}, + } + + // Phase 1: Delete all existing instances + if err := r.deleteInstancesPhase(ctx, plan, config, result); err != nil { + result.Error = err + result.Duration = time.Since(startTime) + return result, err + } + + // Phase 2: Delete existing app (if updating) + if err := r.deleteAppPhase(ctx, plan, config, result); err != nil { + result.Error = err + result.Duration = time.Since(startTime) + return result, err + } + + // Phase 3: Create/recreate application + if err := r.createAppPhase(ctx, plan, config, manifestContent, result); err != nil { + result.Error = err + result.Duration = time.Since(startTime) + return result, err + } + + // Phase 4: Create new instances + if err := r.createInstancesPhase(ctx, plan, config, result); err != nil { + result.Error = err + result.Duration = time.Since(startTime) + return result, err + } + + // Phase 5: Health check (wait for instances to be ready) + if err := r.healthCheckPhase(ctx, plan, result); err != nil { + result.Error = err + result.Duration = time.Since(startTime) + return result, err + } + + result.Success = len(result.FailedActions) == 0 + result.Duration = time.Since(startTime) + + if result.Success { + r.logf("Recreate deployment completed successfully in %v", result.Duration) + } else { + r.logf("Recreate deployment failed with %d failed actions", len(result.FailedActions)) + } + + return result, result.Error +} + +// deleteInstancesPhase deletes all existing instances +func (r *RecreateStrategy) deleteInstancesPhase(ctx context.Context, plan *DeploymentPlan, config *config.EdgeConnectConfig, result *ExecutionResult) error { + r.logf("Phase 1: Deleting existing instances") + + // Only delete instances that exist (have ActionUpdate or ActionNone type) + instancesToDelete := []InstanceAction{} + for _, action := range plan.InstanceActions { + if action.Type == ActionUpdate || action.Type == ActionNone { + // Convert to delete action + deleteAction := action + deleteAction.Type = ActionDelete + deleteAction.Reason = "Recreate strategy: deleting for recreation" + instancesToDelete = append(instancesToDelete, deleteAction) + } + } + + if len(instancesToDelete) == 0 { + r.logf("No existing instances to delete") + return nil + } + + deleteResults := r.executeInstanceActionsWithRetry(ctx, instancesToDelete, "delete", config) + + for _, deleteResult := range deleteResults { + if deleteResult.Success { + result.CompletedActions = append(result.CompletedActions, deleteResult) + r.logf("Deleted instance: %s", deleteResult.Target) + } else { + result.FailedActions = append(result.FailedActions, deleteResult) + return fmt.Errorf("failed to delete instance %s: %w", deleteResult.Target, deleteResult.Error) + } + } + + r.logf("Phase 1 complete: deleted %d instances", len(deleteResults)) + return nil +} + +// deleteAppPhase deletes the existing app (if updating) +func (r *RecreateStrategy) deleteAppPhase(ctx context.Context, plan *DeploymentPlan, config *config.EdgeConnectConfig, result *ExecutionResult) error { + if plan.AppAction.Type != ActionUpdate { + r.logf("Phase 2: No app deletion needed (new app)") + return nil + } + + r.logf("Phase 2: Deleting existing application") + + appKey := edgeconnect.AppKey{ + Organization: plan.AppAction.Desired.Organization, + Name: plan.AppAction.Desired.Name, + Version: plan.AppAction.Desired.Version, + } + + if err := r.client.DeleteApp(ctx, appKey, plan.AppAction.Desired.Region); err != nil { + result.FailedActions = append(result.FailedActions, ActionResult{ + Type: ActionDelete, + Target: plan.AppAction.Desired.Name, + Success: false, + Error: err, + }) + return fmt.Errorf("failed to delete app: %w", err) + } + + result.CompletedActions = append(result.CompletedActions, ActionResult{ + Type: ActionDelete, + Target: plan.AppAction.Desired.Name, + Success: true, + Details: fmt.Sprintf("Deleted app %s", plan.AppAction.Desired.Name), + }) + + r.logf("Phase 2 complete: deleted existing application") + return nil +} + +// createAppPhase creates the application (always create since we deleted it first) +func (r *RecreateStrategy) createAppPhase(ctx context.Context, plan *DeploymentPlan, config *config.EdgeConnectConfig, manifestContent string, result *ExecutionResult) error { + if plan.AppAction.Type == ActionNone { + r.logf("Phase 3: No app creation needed") + return nil + } + + r.logf("Phase 3: Creating application") + + // Always use create since recreate strategy deletes first + createAction := plan.AppAction + createAction.Type = ActionCreate + createAction.Reason = "Recreate strategy: creating app" + + appResult := r.executeAppActionWithRetry(ctx, createAction, config, manifestContent) + + if appResult.Success { + result.CompletedActions = append(result.CompletedActions, appResult) + r.logf("Phase 3 complete: app created successfully") + return nil + } else { + result.FailedActions = append(result.FailedActions, appResult) + return fmt.Errorf("failed to create app: %w", appResult.Error) + } +} + +// createInstancesPhase creates new instances +func (r *RecreateStrategy) createInstancesPhase(ctx context.Context, plan *DeploymentPlan, config *config.EdgeConnectConfig, result *ExecutionResult) error { + r.logf("Phase 4: Creating new instances") + + // Convert all instance actions to create + instancesToCreate := []InstanceAction{} + for _, action := range plan.InstanceActions { + createAction := action + createAction.Type = ActionCreate + createAction.Reason = "Recreate strategy: creating new instance" + instancesToCreate = append(instancesToCreate, createAction) + } + + if len(instancesToCreate) == 0 { + r.logf("No instances to create") + return nil + } + + createResults := r.executeInstanceActionsWithRetry(ctx, instancesToCreate, "create", config) + + for _, createResult := range createResults { + if createResult.Success { + result.CompletedActions = append(result.CompletedActions, createResult) + r.logf("Created instance: %s", createResult.Target) + } else { + result.FailedActions = append(result.FailedActions, createResult) + return fmt.Errorf("failed to create instance %s: %w", createResult.Target, createResult.Error) + } + } + + r.logf("Phase 4 complete: created %d instances", len(createResults)) + return nil +} + +// healthCheckPhase waits for instances to become ready +func (r *RecreateStrategy) healthCheckPhase(ctx context.Context, plan *DeploymentPlan, result *ExecutionResult) error { + if len(plan.InstanceActions) == 0 { + return nil + } + + r.logf("Phase 5: Performing health checks") + + // TODO: Implement actual health checks by querying instance status + // For now, skip waiting in tests/mock environments + r.logf("Phase 5 complete: health check passed (no wait)") + return nil +} + +// executeInstanceActionsWithRetry executes instance actions with retry logic +func (r *RecreateStrategy) executeInstanceActionsWithRetry(ctx context.Context, actions []InstanceAction, operation string, config *config.EdgeConnectConfig) []ActionResult { + results := make([]ActionResult, len(actions)) + + if r.config.ParallelOperations && len(actions) > 1 { + // Parallel execution + var wg sync.WaitGroup + semaphore := make(chan struct{}, 5) // Limit concurrency + + for i, action := range actions { + wg.Add(1) + go func(index int, instanceAction InstanceAction) { + defer wg.Done() + semaphore <- struct{}{} + defer func() { <-semaphore }() + + results[index] = r.executeInstanceActionWithRetry(ctx, instanceAction, operation, config) + }(i, action) + } + wg.Wait() + } else { + // Sequential execution + for i, action := range actions { + results[i] = r.executeInstanceActionWithRetry(ctx, action, operation, config) + } + } + + return results +} + +// executeInstanceActionWithRetry executes a single instance action with retry logic +func (r *RecreateStrategy) executeInstanceActionWithRetry(ctx context.Context, action InstanceAction, operation string, config *config.EdgeConnectConfig) ActionResult { + startTime := time.Now() + result := ActionResult{ + Type: action.Type, + Target: action.InstanceName, + } + + var lastErr error + for attempt := 0; attempt <= r.config.MaxRetries; attempt++ { + if attempt > 0 { + r.logf("Retrying %s for instance %s (attempt %d/%d)", operation, action.InstanceName, attempt, r.config.MaxRetries) + select { + case <-time.After(r.config.RetryDelay): + case <-ctx.Done(): + result.Error = ctx.Err() + result.Duration = time.Since(startTime) + return result + } + } + + var success bool + var err error + + switch action.Type { + case ActionDelete: + success, err = r.deleteInstance(ctx, action) + case ActionCreate: + success, err = r.createInstance(ctx, action, config) + default: + err = fmt.Errorf("unsupported action type: %s", action.Type) + } + + if success { + result.Success = true + result.Details = fmt.Sprintf("Successfully %sd instance %s", strings.ToLower(string(action.Type)), action.InstanceName) + result.Duration = time.Since(startTime) + return result + } + + lastErr = err + + // Check if error is retryable (don't retry 4xx client errors) + if !isRetryableError(err) { + r.logf("Failed to %s instance %s: %v (non-retryable error, giving up)", operation, action.InstanceName, err) + result.Error = fmt.Errorf("non-retryable error: %w", err) + result.Duration = time.Since(startTime) + return result + } + + if attempt < r.config.MaxRetries { + r.logf("Failed to %s instance %s: %v (will retry)", operation, action.InstanceName, err) + } + } + + result.Error = fmt.Errorf("failed after %d attempts: %w", r.config.MaxRetries+1, lastErr) + result.Duration = time.Since(startTime) + return result +} + +// executeAppActionWithRetry executes app action with retry logic +func (r *RecreateStrategy) executeAppActionWithRetry(ctx context.Context, action AppAction, config *config.EdgeConnectConfig, manifestContent string) ActionResult { + startTime := time.Now() + result := ActionResult{ + Type: action.Type, + Target: action.Desired.Name, + } + + var lastErr error + for attempt := 0; attempt <= r.config.MaxRetries; attempt++ { + if attempt > 0 { + r.logf("Retrying app update (attempt %d/%d)", attempt, r.config.MaxRetries) + select { + case <-time.After(r.config.RetryDelay): + case <-ctx.Done(): + result.Error = ctx.Err() + result.Duration = time.Since(startTime) + return result + } + } + + success, err := r.updateApplication(ctx, action, config, manifestContent) + if success { + result.Success = true + result.Details = fmt.Sprintf("Successfully updated application %s", action.Desired.Name) + result.Duration = time.Since(startTime) + return result + } + + lastErr = err + + // Check if error is retryable (don't retry 4xx client errors) + if !isRetryableError(err) { + r.logf("Failed to update app: %v (non-retryable error, giving up)", err) + result.Error = fmt.Errorf("non-retryable error: %w", err) + result.Duration = time.Since(startTime) + return result + } + + if attempt < r.config.MaxRetries { + r.logf("Failed to update app: %v (will retry)", err) + } + } + + result.Error = fmt.Errorf("failed after %d attempts: %w", r.config.MaxRetries+1, lastErr) + result.Duration = time.Since(startTime) + return result +} + +// deleteInstance deletes an instance (reuse existing logic from manager.go) +func (r *RecreateStrategy) deleteInstance(ctx context.Context, action InstanceAction) (bool, error) { + instanceKey := edgeconnect.AppInstanceKey{ + Organization: action.Desired.Organization, + Name: action.InstanceName, + CloudletKey: edgeconnect.CloudletKey{ + Organization: action.Target.CloudletOrg, + Name: action.Target.CloudletName, + }, + } + + err := r.client.DeleteAppInstance(ctx, instanceKey, action.Target.Region) + if err != nil { + return false, fmt.Errorf("failed to delete instance: %w", err) + } + + return true, nil +} + +// createInstance creates an instance (extracted from manager.go logic) +func (r *RecreateStrategy) createInstance(ctx context.Context, action InstanceAction, config *config.EdgeConnectConfig) (bool, error) { + instanceInput := &edgeconnect.NewAppInstanceInput{ + Region: action.Target.Region, + AppInst: edgeconnect.AppInstance{ + Key: edgeconnect.AppInstanceKey{ + Organization: action.Desired.Organization, + Name: action.InstanceName, + CloudletKey: edgeconnect.CloudletKey{ + Organization: action.Target.CloudletOrg, + Name: action.Target.CloudletName, + }, + }, + AppKey: edgeconnect.AppKey{ + Organization: action.Desired.Organization, + Name: config.Metadata.Name, + Version: config.Metadata.AppVersion, + }, + Flavor: edgeconnect.Flavor{ + Name: action.Target.FlavorName, + }, + }, + } + + // Create the instance + if err := r.client.CreateAppInstance(ctx, instanceInput); err != nil { + return false, fmt.Errorf("failed to create instance: %w", err) + } + + r.logf("Successfully created instance: %s on %s:%s", + action.InstanceName, action.Target.CloudletOrg, action.Target.CloudletName) + + return true, nil +} + +// updateApplication creates/recreates an application (always uses CreateApp since we delete first) +func (r *RecreateStrategy) updateApplication(ctx context.Context, action AppAction, config *config.EdgeConnectConfig, manifestContent string) (bool, error) { + // Build the app create input - always create since recreate strategy deletes first + appInput := &edgeconnect.NewAppInput{ + Region: action.Desired.Region, + App: edgeconnect.App{ + Key: edgeconnect.AppKey{ + Organization: action.Desired.Organization, + Name: action.Desired.Name, + Version: action.Desired.Version, + }, + Deployment: config.GetDeploymentType(), + ImageType: "ImageTypeDocker", + ImagePath: config.GetImagePath(), + AllowServerless: true, + DefaultFlavor: edgeconnect.Flavor{Name: config.Spec.InfraTemplate[0].FlavorName}, + ServerlessConfig: struct{}{}, + DeploymentManifest: manifestContent, + DeploymentGenerator: "kubernetes-basic", + }, + } + + // Add network configuration if specified + if config.Spec.Network != nil { + appInput.App.RequiredOutboundConnections = convertNetworkRules(config.Spec.Network) + } + + // Create the application (recreate strategy always creates from scratch) + if err := r.client.CreateApp(ctx, appInput); err != nil { + return false, fmt.Errorf("failed to create application: %w", err) + } + + r.logf("Successfully created application: %s/%s version %s", + action.Desired.Organization, action.Desired.Name, action.Desired.Version) + + return true, nil +} + +// logf logs a message if a logger is configured +func (r *RecreateStrategy) logf(format string, v ...interface{}) { + if r.logger != nil { + r.logger.Printf("[RecreateStrategy] "+format, v...) + } +} + +// isRetryableError determines if an error should be retried +// Returns false for client errors (4xx), true for server errors (5xx) and other transient errors +func isRetryableError(err error) bool { + if err == nil { + return false + } + + // Check if it's an APIError with a status code + var apiErr *edgeconnect.APIError + if errors.As(err, &apiErr) { + // Don't retry client errors (4xx) + if apiErr.StatusCode >= 400 && apiErr.StatusCode < 500 { + return false + } + // Retry server errors (5xx) + if apiErr.StatusCode >= 500 { + return true + } + } + + // Retry all other errors (network issues, timeouts, etc.) + return true +} diff --git a/internal/apply/v1/types.go b/internal/apply/v1/types.go new file mode 100644 index 0000000..223fa74 --- /dev/null +++ b/internal/apply/v1/types.go @@ -0,0 +1,462 @@ +// ABOUTME: Deployment planning types for EdgeConnect apply command with state management +// ABOUTME: Defines structures for deployment plans, actions, and state comparison results +package v1 + +import ( + "fmt" + "strings" + "time" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" +) + +// SecurityRule defines network access rules (alias to SDK type for consistency) +type SecurityRule = edgeconnect.SecurityRule + +// ActionType represents the type of action to be performed +type ActionType string + +const ( + // ActionCreate indicates a resource needs to be created + ActionCreate ActionType = "CREATE" + // ActionUpdate indicates a resource needs to be updated + ActionUpdate ActionType = "UPDATE" + // ActionNone indicates no action is needed + ActionNone ActionType = "NONE" + // ActionDelete indicates a resource needs to be deleted (for rollback scenarios) + ActionDelete ActionType = "DELETE" +) + +// String returns the string representation of ActionType +func (a ActionType) String() string { + return string(a) +} + +// DeploymentPlan represents the complete deployment plan for a configuration +type DeploymentPlan struct { + // ConfigName is the name from metadata + ConfigName string + + // AppAction defines what needs to be done with the application + AppAction AppAction + + // InstanceActions defines what needs to be done with each instance + InstanceActions []InstanceAction + + // Summary provides a human-readable summary of the plan + Summary string + + // TotalActions is the count of all actions that will be performed + TotalActions int + + // EstimatedDuration is the estimated time to complete the deployment + EstimatedDuration time.Duration + + // CreatedAt timestamp when the plan was created + CreatedAt time.Time + + // DryRun indicates if this is a dry-run plan + DryRun bool +} + +// AppAction represents an action to be performed on an application +type AppAction struct { + // Type of action to perform + Type ActionType + + // Current state of the app (nil if doesn't exist) + Current *AppState + + // Desired state of the app + Desired *AppState + + // Changes describes what will change + Changes []string + + // Reason explains why this action is needed + Reason string + + // ManifestHash is the hash of the current manifest file + ManifestHash string + + // ManifestChanged indicates if the manifest content has changed + ManifestChanged bool +} + +// InstanceAction represents an action to be performed on an application instance +type InstanceAction struct { + // Type of action to perform + Type ActionType + + // Target infrastructure where the instance will be deployed + Target config.InfraTemplate + + // Current state of the instance (nil if doesn't exist) + Current *InstanceState + + // Desired state of the instance + Desired *InstanceState + + // Changes describes what will change + Changes []string + + // Reason explains why this action is needed + Reason string + + // InstanceName is the generated name for this instance + InstanceName string + + // Dependencies lists other instances this depends on + Dependencies []string +} + +// AppState represents the current state of an application +type AppState struct { + // Name of the application + Name string + + // Version of the application + Version string + + // Organization that owns the app + Organization string + + // Region where the app is deployed + Region string + + // ManifestHash is the stored hash of the manifest file + ManifestHash string + + // LastUpdated timestamp when the app was last modified + LastUpdated time.Time + + // Exists indicates if the app currently exists + Exists bool + + // AppType indicates whether this is a k8s or docker app + AppType AppType + + // OutboundConnections contains the required outbound network connections + OutboundConnections []SecurityRule +} + +// InstanceState represents the current state of an application instance +type InstanceState struct { + // Name of the instance + Name string + + // AppName that this instance belongs to + AppName string + + // AppVersion of the associated app + AppVersion string + + // Organization that owns the instance + Organization string + + // Region where the instance is deployed + Region string + + // CloudletOrg that hosts the cloudlet + CloudletOrg string + + // CloudletName where the instance is running + CloudletName string + + // FlavorName used for the instance + FlavorName string + + // State of the instance (e.g., "Ready", "Pending", "Error") + State string + + // PowerState of the instance + PowerState string + + // LastUpdated timestamp when the instance was last modified + LastUpdated time.Time + + // Exists indicates if the instance currently exists + Exists bool +} + +// AppType represents the type of application +type AppType string + +const ( + // AppTypeK8s represents a Kubernetes application + AppTypeK8s AppType = "k8s" + // AppTypeDocker represents a Docker application + AppTypeDocker AppType = "docker" +) + +// String returns the string representation of AppType +func (a AppType) String() string { + return string(a) +} + +// DeploymentSummary provides a high-level overview of the deployment plan +type DeploymentSummary struct { + // TotalActions is the total number of actions to be performed + TotalActions int + + // ActionCounts breaks down actions by type + ActionCounts map[ActionType]int + + // EstimatedDuration for the entire deployment + EstimatedDuration time.Duration + + // ResourceSummary describes the resources involved + ResourceSummary ResourceSummary + + // Warnings about potential issues + Warnings []string +} + +// ResourceSummary provides details about resources in the deployment +type ResourceSummary struct { + // AppsToCreate number of apps that will be created + AppsToCreate int + + // AppsToUpdate number of apps that will be updated + AppsToUpdate int + + // InstancesToCreate number of instances that will be created + InstancesToCreate int + + // InstancesToUpdate number of instances that will be updated + InstancesToUpdate int + + // CloudletsAffected number of unique cloudlets involved + CloudletsAffected int + + // RegionsAffected number of unique regions involved + RegionsAffected int +} + +// PlanResult represents the result of a deployment planning operation +type PlanResult struct { + // Plan is the generated deployment plan + Plan *DeploymentPlan + + // Error if planning failed + Error error + + // Warnings encountered during planning + Warnings []string +} + +// ExecutionResult represents the result of executing a deployment plan +type ExecutionResult struct { + // Plan that was executed + Plan *DeploymentPlan + + // Success indicates if the deployment was successful + Success bool + + // CompletedActions lists actions that were successfully completed + CompletedActions []ActionResult + + // FailedActions lists actions that failed + FailedActions []ActionResult + + // Error that caused the deployment to fail (if any) + Error error + + // Duration taken to execute the plan + Duration time.Duration + + // RollbackPerformed indicates if rollback was executed + RollbackPerformed bool + + // RollbackSuccess indicates if rollback was successful + RollbackSuccess bool +} + +// ActionResult represents the result of executing a single action +type ActionResult struct { + // Type of action that was attempted + Type ActionType + + // Target describes what was being acted upon + Target string + + // Success indicates if the action succeeded + Success bool + + // Error if the action failed + Error error + + // Duration taken to complete the action + Duration time.Duration + + // Details provides additional information about the action + Details string +} + +// IsEmpty returns true if the deployment plan has no actions to perform +func (dp *DeploymentPlan) IsEmpty() bool { + if dp.AppAction.Type != ActionNone { + return false + } + + for _, action := range dp.InstanceActions { + if action.Type != ActionNone { + return false + } + } + + return true +} + +// HasErrors returns true if the plan contains any error conditions +func (dp *DeploymentPlan) HasErrors() bool { + // Check for conflicting actions or invalid states + return false // Implementation would check for various error conditions +} + +// GetTargetCloudlets returns a list of unique cloudlets that will be affected +func (dp *DeploymentPlan) GetTargetCloudlets() []string { + cloudletSet := make(map[string]bool) + var cloudlets []string + + for _, action := range dp.InstanceActions { + if action.Type != ActionNone { + key := fmt.Sprintf("%s:%s", action.Target.CloudletOrg, action.Target.CloudletName) + if !cloudletSet[key] { + cloudletSet[key] = true + cloudlets = append(cloudlets, key) + } + } + } + + return cloudlets +} + +// GetTargetRegions returns a list of unique regions that will be affected +func (dp *DeploymentPlan) GetTargetRegions() []string { + regionSet := make(map[string]bool) + var regions []string + + for _, action := range dp.InstanceActions { + if action.Type != ActionNone && !regionSet[action.Target.Region] { + regionSet[action.Target.Region] = true + regions = append(regions, action.Target.Region) + } + } + + return regions +} + +// GenerateSummary creates a human-readable summary of the deployment plan +func (dp *DeploymentPlan) GenerateSummary() string { + if dp.IsEmpty() { + return "No changes required - configuration matches current state" + } + + var sb strings.Builder + sb.WriteString(fmt.Sprintf("Deployment plan for '%s':\n", dp.ConfigName)) + + // App actions + if dp.AppAction.Type != ActionNone { + sb.WriteString(fmt.Sprintf("- %s application '%s'\n", dp.AppAction.Type, dp.AppAction.Desired.Name)) + if len(dp.AppAction.Changes) > 0 { + for _, change := range dp.AppAction.Changes { + sb.WriteString(fmt.Sprintf(" - %s\n", change)) + } + } + } + + // Instance actions + createCount := 0 + updateActions := []InstanceAction{} + for _, action := range dp.InstanceActions { + switch action.Type { + case ActionCreate: + createCount++ + case ActionUpdate: + updateActions = append(updateActions, action) + } + } + + if createCount > 0 { + sb.WriteString(fmt.Sprintf("- CREATE %d instance(s) across %d cloudlet(s)\n", createCount, len(dp.GetTargetCloudlets()))) + } + + if len(updateActions) > 0 { + sb.WriteString(fmt.Sprintf("- UPDATE %d instance(s)\n", len(updateActions))) + for _, action := range updateActions { + if len(action.Changes) > 0 { + sb.WriteString(fmt.Sprintf(" - Instance '%s':\n", action.InstanceName)) + for _, change := range action.Changes { + sb.WriteString(fmt.Sprintf(" - %s\n", change)) + } + } + } + } + + sb.WriteString(fmt.Sprintf("Estimated duration: %s", dp.EstimatedDuration.String())) + + return sb.String() +} + +// Validate checks if the deployment plan is valid and safe to execute +func (dp *DeploymentPlan) Validate() error { + if dp.ConfigName == "" { + return fmt.Errorf("deployment plan must have a config name") + } + + // Validate app action + if dp.AppAction.Type != ActionNone && dp.AppAction.Desired == nil { + return fmt.Errorf("app action of type %s must have desired state", dp.AppAction.Type) + } + + // Validate instance actions + for i, action := range dp.InstanceActions { + if action.Type != ActionNone { + if action.Desired == nil { + return fmt.Errorf("instance action %d of type %s must have desired state", i, action.Type) + } + if action.InstanceName == "" { + return fmt.Errorf("instance action %d must have an instance name", i) + } + } + } + + return nil +} + +// Clone creates a deep copy of the deployment plan +func (dp *DeploymentPlan) Clone() *DeploymentPlan { + clone := &DeploymentPlan{ + ConfigName: dp.ConfigName, + Summary: dp.Summary, + TotalActions: dp.TotalActions, + EstimatedDuration: dp.EstimatedDuration, + CreatedAt: dp.CreatedAt, + DryRun: dp.DryRun, + AppAction: dp.AppAction, // Struct copy is sufficient for this use case + } + + // Deep copy instance actions + clone.InstanceActions = make([]InstanceAction, len(dp.InstanceActions)) + copy(clone.InstanceActions, dp.InstanceActions) + + return clone +} + +// convertNetworkRules converts config network rules to EdgeConnect SecurityRules +func convertNetworkRules(network *config.NetworkConfig) []edgeconnect.SecurityRule { + rules := make([]edgeconnect.SecurityRule, len(network.OutboundConnections)) + + for i, conn := range network.OutboundConnections { + rules[i] = edgeconnect.SecurityRule{ + Protocol: conn.Protocol, + PortRangeMin: conn.PortRangeMin, + PortRangeMax: conn.PortRangeMax, + RemoteCIDR: conn.RemoteCIDR, + } + } + + return rules +} diff --git a/internal/apply/manager.go b/internal/apply/v2/manager.go similarity index 99% rename from internal/apply/manager.go rename to internal/apply/v2/manager.go index 3e6d837..fc1b483 100644 --- a/internal/apply/manager.go +++ b/internal/apply/v2/manager.go @@ -1,6 +1,6 @@ // ABOUTME: Resource management for EdgeConnect apply command with deployment execution and rollback // ABOUTME: Handles actual deployment operations, manifest processing, and error recovery with parallel execution -package apply +package v2 import ( "context" diff --git a/internal/apply/manager_test.go b/internal/apply/v2/manager_test.go similarity index 99% rename from internal/apply/manager_test.go rename to internal/apply/v2/manager_test.go index f2135b5..68c60fd 100644 --- a/internal/apply/manager_test.go +++ b/internal/apply/v2/manager_test.go @@ -1,6 +1,6 @@ // ABOUTME: Comprehensive tests for EdgeConnect resource manager with deployment scenarios // ABOUTME: Tests deployment execution, rollback functionality, and error handling with mock clients -package apply +package v2 import ( "context" diff --git a/internal/apply/planner.go b/internal/apply/v2/planner.go similarity index 99% rename from internal/apply/planner.go rename to internal/apply/v2/planner.go index d4f3e82..52de1ee 100644 --- a/internal/apply/planner.go +++ b/internal/apply/v2/planner.go @@ -1,6 +1,6 @@ // ABOUTME: Deployment planner for EdgeConnect apply command with intelligent state comparison // ABOUTME: Analyzes desired vs current state to generate optimal deployment plans with minimal API calls -package apply +package v2 import ( "context" diff --git a/internal/apply/planner_test.go b/internal/apply/v2/planner_test.go similarity index 99% rename from internal/apply/planner_test.go rename to internal/apply/v2/planner_test.go index 6f7c39b..fe56871 100644 --- a/internal/apply/planner_test.go +++ b/internal/apply/v2/planner_test.go @@ -1,6 +1,6 @@ // ABOUTME: Comprehensive tests for EdgeConnect deployment planner with mock scenarios // ABOUTME: Tests planning logic, state comparison, and various deployment scenarios -package apply +package v2 import ( "context" diff --git a/internal/apply/v2/strategy.go b/internal/apply/v2/strategy.go new file mode 100644 index 0000000..6a1661a --- /dev/null +++ b/internal/apply/v2/strategy.go @@ -0,0 +1,106 @@ +// ABOUTME: Deployment strategy framework for EdgeConnect apply command +// ABOUTME: Defines interfaces and types for different deployment strategies (recreate, blue-green, rolling) +package v2 + +import ( + "context" + "fmt" + "time" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" +) + +// DeploymentStrategy represents the type of deployment strategy +type DeploymentStrategy string + +const ( + // StrategyRecreate deletes all instances, updates app, then creates new instances + StrategyRecreate DeploymentStrategy = "recreate" + + // StrategyBlueGreen creates new instances alongside old ones, then switches traffic (future) + StrategyBlueGreen DeploymentStrategy = "blue-green" + + // StrategyRolling updates instances one by one with health checks (future) + StrategyRolling DeploymentStrategy = "rolling" +) + +// DeploymentStrategyExecutor defines the interface that all deployment strategies must implement +type DeploymentStrategyExecutor interface { + // Execute runs the deployment strategy + Execute(ctx context.Context, plan *DeploymentPlan, config *config.EdgeConnectConfig, manifestContent string) (*ExecutionResult, error) + + // Validate checks if the strategy can be used for this deployment + Validate(plan *DeploymentPlan) error + + // EstimateDuration provides time estimate for this strategy + EstimateDuration(plan *DeploymentPlan) time.Duration + + // GetName returns the strategy name + GetName() DeploymentStrategy +} + +// StrategyConfig holds configuration for deployment strategies +type StrategyConfig struct { + // MaxRetries is the number of times to retry failed operations + MaxRetries int + + // HealthCheckTimeout is the maximum time to wait for health checks + HealthCheckTimeout time.Duration + + // ParallelOperations enables parallel execution of operations + ParallelOperations bool + + // RetryDelay is the delay between retry attempts + RetryDelay time.Duration +} + +// DefaultStrategyConfig returns sensible defaults for strategy configuration +func DefaultStrategyConfig() StrategyConfig { + return StrategyConfig{ + MaxRetries: 5, // Retry 5 times + HealthCheckTimeout: 5 * time.Minute, // Max 5 mins health check + ParallelOperations: true, // Parallel execution + RetryDelay: 10 * time.Second, // 10s between retries + } +} + +// StrategyFactory creates deployment strategy executors +type StrategyFactory struct { + config StrategyConfig + client EdgeConnectClientInterface + logger Logger +} + +// NewStrategyFactory creates a new strategy factory +func NewStrategyFactory(client EdgeConnectClientInterface, config StrategyConfig, logger Logger) *StrategyFactory { + return &StrategyFactory{ + config: config, + client: client, + logger: logger, + } +} + +// CreateStrategy creates the appropriate strategy executor based on the deployment strategy +func (f *StrategyFactory) CreateStrategy(strategy DeploymentStrategy) (DeploymentStrategyExecutor, error) { + switch strategy { + case StrategyRecreate: + return NewRecreateStrategy(f.client, f.config, f.logger), nil + case StrategyBlueGreen: + // TODO: Implement blue-green strategy + return nil, fmt.Errorf("blue-green strategy not yet implemented") + case StrategyRolling: + // TODO: Implement rolling strategy + return nil, fmt.Errorf("rolling strategy not yet implemented") + default: + return nil, fmt.Errorf("unknown deployment strategy: %s", strategy) + } +} + +// GetAvailableStrategies returns a list of all available strategies +func (f *StrategyFactory) GetAvailableStrategies() []DeploymentStrategy { + return []DeploymentStrategy{ + StrategyRecreate, + // StrategyBlueGreen, // TODO: Enable when implemented + // StrategyRolling, // TODO: Enable when implemented + } +} diff --git a/internal/apply/strategy_recreate.go b/internal/apply/v2/strategy_recreate.go similarity index 99% rename from internal/apply/strategy_recreate.go rename to internal/apply/v2/strategy_recreate.go index dc44784..739a454 100644 --- a/internal/apply/strategy_recreate.go +++ b/internal/apply/v2/strategy_recreate.go @@ -1,6 +1,6 @@ // ABOUTME: Recreate deployment strategy implementation for EdgeConnect // ABOUTME: Handles delete-all, update-app, create-all deployment pattern with retries and parallel execution -package apply +package v2 import ( "context" diff --git a/internal/apply/types.go b/internal/apply/v2/types.go similarity index 99% rename from internal/apply/types.go rename to internal/apply/v2/types.go index 279832a..90b7956 100644 --- a/internal/apply/types.go +++ b/internal/apply/v2/types.go @@ -1,6 +1,6 @@ // ABOUTME: Deployment planning types for EdgeConnect apply command with state management // ABOUTME: Defines structures for deployment plans, actions, and state comparison results -package apply +package v2 import ( "fmt" From f921169351417e5003d0a51a4695ca0b48a4a4d3 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 20 Oct 2025 14:29:45 +0200 Subject: [PATCH 25/40] feat(examples): added edge connect v1 and v2 examples --- .../comprehensive/EdgeConnectConfig_v1.yaml | 29 +++++++++++++++++++ ...tConfig.yaml => EdgeConnectConfig_v2.yaml} | 0 2 files changed, 29 insertions(+) create mode 100644 sdk/examples/comprehensive/EdgeConnectConfig_v1.yaml rename sdk/examples/comprehensive/{EdgeConnectConfig.yaml => EdgeConnectConfig_v2.yaml} (100%) diff --git a/sdk/examples/comprehensive/EdgeConnectConfig_v1.yaml b/sdk/examples/comprehensive/EdgeConnectConfig_v1.yaml new file mode 100644 index 0000000..b45abc4 --- /dev/null +++ b/sdk/examples/comprehensive/EdgeConnectConfig_v1.yaml @@ -0,0 +1,29 @@ +# Is there a swagger file for the new EdgeConnect API? +# How does it differ from the EdgeXR API? +kind: edgeconnect-deployment +metadata: + name: "edge-app-demo" # name could be used for appName + appVersion: "1.0.0" + organization: "edp2" +spec: + # dockerApp: # Docker is OBSOLETE + # appVersion: "1.0.0" + # manifestFile: "./docker-compose.yaml" + # image: "https://registry-1.docker.io/library/nginx:latest" + k8sApp: + manifestFile: "./k8s-deployment.yaml" + infraTemplate: + - region: "EU" + cloudletOrg: "TelekomOP" + cloudletName: "Munich" + flavorName: "EU.small" + network: + outboundConnections: + - protocol: "tcp" + portRangeMin: 80 + portRangeMax: 80 + remoteCIDR: "0.0.0.0/0" + - protocol: "tcp" + portRangeMin: 443 + portRangeMax: 443 + remoteCIDR: "0.0.0.0/0" diff --git a/sdk/examples/comprehensive/EdgeConnectConfig.yaml b/sdk/examples/comprehensive/EdgeConnectConfig_v2.yaml similarity index 100% rename from sdk/examples/comprehensive/EdgeConnectConfig.yaml rename to sdk/examples/comprehensive/EdgeConnectConfig_v2.yaml From df697c0ff6ad6c888050c246e4d1d54a7631783a Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 20 Oct 2025 15:15:23 +0200 Subject: [PATCH 26/40] fix(sdk): correct delete payload structure for v2 API and add delete command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v2 API requires a different JSON payload structure than what was being sent. Both DeleteApp and DeleteAppInstance needed to wrap their parameters properly. SDK Changes: - Update DeleteAppInput to use {region, app: {key}} structure - Update DeleteAppInstanceInput to use {region, appinst: {key}} structure - Fix DeleteApp method to populate new payload structure - Fix DeleteAppInstance method to populate new payload structure CLI Changes: - Add delete command with -f flag for config file specification - Support --dry-run to preview deletions - Support --auto-approve to skip confirmation - Implement v1 and v2 API support following same pattern as apply - Add deletion planner to discover resources matching config - Add resource manager to execute deletions (instances first, then app) Test Changes: - Update example_test.go to use EdgeConnectConfig_v1.yaml - All tests passing including comprehensive delete test coverage Verified working with manual API testing against live endpoint. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- cmd/delete.go | 294 +++++++++++++++++++++++++++++ internal/config/example_test.go | 2 +- internal/delete/v1/manager.go | 166 ++++++++++++++++ internal/delete/v1/planner.go | 228 ++++++++++++++++++++++ internal/delete/v1/types.go | 157 +++++++++++++++ internal/delete/v2/manager.go | 166 ++++++++++++++++ internal/delete/v2/manager_test.go | 200 ++++++++++++++++++++ internal/delete/v2/planner.go | 228 ++++++++++++++++++++++ internal/delete/v2/planner_test.go | 219 +++++++++++++++++++++ internal/delete/v2/types.go | 157 +++++++++++++++ internal/delete/v2/types_test.go | 95 ++++++++++ sdk/edgeconnect/v2/appinstance.go | 3 +- sdk/edgeconnect/v2/apps.go | 2 +- sdk/edgeconnect/v2/types.go | 9 +- 14 files changed, 1921 insertions(+), 5 deletions(-) create mode 100644 cmd/delete.go create mode 100644 internal/delete/v1/manager.go create mode 100644 internal/delete/v1/planner.go create mode 100644 internal/delete/v1/types.go create mode 100644 internal/delete/v2/manager.go create mode 100644 internal/delete/v2/manager_test.go create mode 100644 internal/delete/v2/planner.go create mode 100644 internal/delete/v2/planner_test.go create mode 100644 internal/delete/v2/types.go create mode 100644 internal/delete/v2/types_test.go diff --git a/cmd/delete.go b/cmd/delete.go new file mode 100644 index 0000000..912741b --- /dev/null +++ b/cmd/delete.go @@ -0,0 +1,294 @@ +// ABOUTME: CLI command for deleting EdgeConnect applications from YAML configuration +// ABOUTME: Removes applications and their instances based on configuration file specification +package cmd + +import ( + "context" + "fmt" + "log" + "os" + "path/filepath" + "strings" + + deletev1 "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/delete/v1" + deletev2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/delete/v2" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + "github.com/spf13/cobra" +) + +var ( + deleteConfigFile string + deleteDryRun bool + deleteAutoApprove bool +) + +var deleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete EdgeConnect applications from configuration files", + Long: `Delete EdgeConnect applications and their instances based on YAML configuration files. +This command reads a configuration file, finds matching resources, and deletes them. +Instances are always deleted before the application.`, + Run: func(cmd *cobra.Command, args []string) { + if deleteConfigFile == "" { + fmt.Fprintf(os.Stderr, "Error: configuration file is required\n") + cmd.Usage() + os.Exit(1) + } + + if err := runDelete(deleteConfigFile, deleteDryRun, deleteAutoApprove); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + }, +} + +func runDelete(configPath string, isDryRun bool, autoApprove bool) error { + // Step 1: Validate and resolve config file path + absPath, err := filepath.Abs(configPath) + if err != nil { + return fmt.Errorf("failed to resolve config file path: %w", err) + } + + if _, err := os.Stat(absPath); os.IsNotExist(err) { + return fmt.Errorf("configuration file not found: %s", absPath) + } + + fmt.Printf("📄 Loading configuration from: %s\n", absPath) + + // Step 2: Parse and validate configuration + parser := config.NewParser() + cfg, _, err := parser.ParseFile(absPath) + if err != nil { + return fmt.Errorf("failed to parse configuration: %w", err) + } + + if err := parser.Validate(cfg); err != nil { + return fmt.Errorf("configuration validation failed: %w", err) + } + + fmt.Printf("✅ Configuration loaded successfully: %s\n", cfg.Metadata.Name) + + // Step 3: Determine API version and create appropriate client + apiVersion := getAPIVersion() + + // Step 4: Execute deletion based on API version + if apiVersion == "v1" { + return runDeleteV1(cfg, isDryRun, autoApprove) + } + return runDeleteV2(cfg, isDryRun, autoApprove) +} + +func runDeleteV1(cfg *config.EdgeConnectConfig, isDryRun bool, autoApprove bool) error { + // Create v1 client + client := newSDKClientV1() + + // Create deletion planner + planner := deletev1.NewPlanner(client) + + // Generate deletion plan + fmt.Println("🔍 Analyzing current state and generating deletion plan...") + + planOptions := deletev1.DefaultPlanOptions() + planOptions.DryRun = isDryRun + + result, err := planner.PlanWithOptions(context.Background(), cfg, planOptions) + if err != nil { + return fmt.Errorf("failed to generate deletion plan: %w", err) + } + + // Display plan summary + fmt.Println("\n📋 Deletion Plan:") + fmt.Println(strings.Repeat("=", 50)) + fmt.Println(result.Plan.Summary) + fmt.Println(strings.Repeat("=", 50)) + + // Display warnings if any + if len(result.Warnings) > 0 { + fmt.Println("\n⚠️ Warnings:") + for _, warning := range result.Warnings { + fmt.Printf(" • %s\n", warning) + } + } + + // If dry-run, stop here + if isDryRun { + fmt.Println("\n🔍 Dry-run complete. No changes were made.") + return nil + } + + // Check if there's anything to delete + if result.Plan.TotalActions == 0 { + fmt.Println("\n✅ No resources found to delete.") + return nil + } + + fmt.Printf("\nThis will delete %d resource(s). Estimated time: %v\n", + result.Plan.TotalActions, result.Plan.EstimatedDuration) + + if !autoApprove && !confirmDeletion() { + fmt.Println("Deletion cancelled.") + return nil + } + + // Execute deletion + fmt.Println("\n🗑️ Starting deletion...") + + manager := deletev1.NewResourceManager(client, deletev1.WithLogger(log.Default())) + deleteResult, err := manager.ExecuteDeletion(context.Background(), result.Plan) + if err != nil { + return fmt.Errorf("deletion failed: %w", err) + } + + // Display results + return displayDeletionResults(deleteResult) +} + +func runDeleteV2(cfg *config.EdgeConnectConfig, isDryRun bool, autoApprove bool) error { + // Create v2 client + client := newSDKClientV2() + + // Create deletion planner + planner := deletev2.NewPlanner(client) + + // Generate deletion plan + fmt.Println("🔍 Analyzing current state and generating deletion plan...") + + planOptions := deletev2.DefaultPlanOptions() + planOptions.DryRun = isDryRun + + result, err := planner.PlanWithOptions(context.Background(), cfg, planOptions) + if err != nil { + return fmt.Errorf("failed to generate deletion plan: %w", err) + } + + // Display plan summary + fmt.Println("\n📋 Deletion Plan:") + fmt.Println(strings.Repeat("=", 50)) + fmt.Println(result.Plan.Summary) + fmt.Println(strings.Repeat("=", 50)) + + // Display warnings if any + if len(result.Warnings) > 0 { + fmt.Println("\n⚠️ Warnings:") + for _, warning := range result.Warnings { + fmt.Printf(" • %s\n", warning) + } + } + + // If dry-run, stop here + if isDryRun { + fmt.Println("\n🔍 Dry-run complete. No changes were made.") + return nil + } + + // Check if there's anything to delete + if result.Plan.TotalActions == 0 { + fmt.Println("\n✅ No resources found to delete.") + return nil + } + + fmt.Printf("\nThis will delete %d resource(s). Estimated time: %v\n", + result.Plan.TotalActions, result.Plan.EstimatedDuration) + + if !autoApprove && !confirmDeletion() { + fmt.Println("Deletion cancelled.") + return nil + } + + // Execute deletion + fmt.Println("\n🗑️ Starting deletion...") + + manager := deletev2.NewResourceManager(client, deletev2.WithLogger(log.Default())) + deleteResult, err := manager.ExecuteDeletion(context.Background(), result.Plan) + if err != nil { + return fmt.Errorf("deletion failed: %w", err) + } + + // Display results + return displayDeletionResults(deleteResult) +} + +func displayDeletionResults(result interface{}) error { + // Use type assertion to handle both v1 and v2 result types + switch r := result.(type) { + case *deletev1.DeletionResult: + return displayDeletionResultsV1(r) + case *deletev2.DeletionResult: + return displayDeletionResultsV2(r) + default: + return fmt.Errorf("unknown deletion result type") + } +} + +func displayDeletionResultsV1(deleteResult *deletev1.DeletionResult) error { + if deleteResult.Success { + fmt.Printf("\n✅ Deletion completed successfully in %v\n", deleteResult.Duration) + if len(deleteResult.CompletedActions) > 0 { + fmt.Println("\nDeleted resources:") + for _, action := range deleteResult.CompletedActions { + fmt.Printf(" ✅ %s %s\n", action.Type, action.Target) + } + } + } else { + fmt.Printf("\n❌ Deletion failed after %v\n", deleteResult.Duration) + if deleteResult.Error != nil { + fmt.Printf("Error: %v\n", deleteResult.Error) + } + if len(deleteResult.FailedActions) > 0 { + fmt.Println("\nFailed actions:") + for _, action := range deleteResult.FailedActions { + fmt.Printf(" ❌ %s %s: %v\n", action.Type, action.Target, action.Error) + } + } + return fmt.Errorf("deletion failed with %d failed actions", len(deleteResult.FailedActions)) + } + return nil +} + +func displayDeletionResultsV2(deleteResult *deletev2.DeletionResult) error { + if deleteResult.Success { + fmt.Printf("\n✅ Deletion completed successfully in %v\n", deleteResult.Duration) + if len(deleteResult.CompletedActions) > 0 { + fmt.Println("\nDeleted resources:") + for _, action := range deleteResult.CompletedActions { + fmt.Printf(" ✅ %s %s\n", action.Type, action.Target) + } + } + } else { + fmt.Printf("\n❌ Deletion failed after %v\n", deleteResult.Duration) + if deleteResult.Error != nil { + fmt.Printf("Error: %v\n", deleteResult.Error) + } + if len(deleteResult.FailedActions) > 0 { + fmt.Println("\nFailed actions:") + for _, action := range deleteResult.FailedActions { + fmt.Printf(" ❌ %s %s: %v\n", action.Type, action.Target, action.Error) + } + } + return fmt.Errorf("deletion failed with %d failed actions", len(deleteResult.FailedActions)) + } + return nil +} + +func confirmDeletion() bool { + fmt.Print("Do you want to proceed with deletion? (yes/no): ") + var response string + fmt.Scanln(&response) + + switch response { + case "yes", "y", "YES", "Y": + return true + default: + return false + } +} + +func init() { + rootCmd.AddCommand(deleteCmd) + + deleteCmd.Flags().StringVarP(&deleteConfigFile, "file", "f", "", "configuration file path (required)") + deleteCmd.Flags().BoolVar(&deleteDryRun, "dry-run", false, "preview deletion without actually deleting resources") + deleteCmd.Flags().BoolVar(&deleteAutoApprove, "auto-approve", false, "automatically approve the deletion plan") + + deleteCmd.MarkFlagRequired("file") +} diff --git a/internal/config/example_test.go b/internal/config/example_test.go index dfa3840..536399f 100644 --- a/internal/config/example_test.go +++ b/internal/config/example_test.go @@ -14,7 +14,7 @@ func TestParseExampleConfig(t *testing.T) { parser := NewParser() // Parse the actual example file (now that we've created the manifest file) - examplePath := filepath.Join("../../sdk/examples/comprehensive/EdgeConnectConfig.yaml") + examplePath := filepath.Join("../../sdk/examples/comprehensive/EdgeConnectConfig_v1.yaml") config, parsedManifest, err := parser.ParseFile(examplePath) // This should now succeed with full validation diff --git a/internal/delete/v1/manager.go b/internal/delete/v1/manager.go new file mode 100644 index 0000000..470ac37 --- /dev/null +++ b/internal/delete/v1/manager.go @@ -0,0 +1,166 @@ +// ABOUTME: Resource management for EdgeConnect delete command with deletion execution +// ABOUTME: Handles actual deletion operations with proper ordering (instances first, then app) +package v1 + +import ( + "context" + "fmt" + "time" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" +) + +// ResourceManagerInterface defines the interface for resource management +type ResourceManagerInterface interface { + // ExecuteDeletion executes a deletion plan + ExecuteDeletion(ctx context.Context, plan *DeletionPlan) (*DeletionResult, error) +} + +// EdgeConnectResourceManager implements resource management for EdgeConnect +type EdgeConnectResourceManager struct { + client EdgeConnectClientInterface + logger Logger +} + +// Logger interface for deletion logging +type Logger interface { + Printf(format string, v ...interface{}) +} + +// ResourceManagerOptions configures the resource manager behavior +type ResourceManagerOptions struct { + // Logger for deletion operations + Logger Logger +} + +// DefaultResourceManagerOptions returns sensible defaults +func DefaultResourceManagerOptions() ResourceManagerOptions { + return ResourceManagerOptions{ + Logger: nil, + } +} + +// NewResourceManager creates a new EdgeConnect resource manager +func NewResourceManager(client EdgeConnectClientInterface, opts ...func(*ResourceManagerOptions)) ResourceManagerInterface { + options := DefaultResourceManagerOptions() + for _, opt := range opts { + opt(&options) + } + + return &EdgeConnectResourceManager{ + client: client, + logger: options.Logger, + } +} + +// WithLogger sets a logger for deletion operations +func WithLogger(logger Logger) func(*ResourceManagerOptions) { + return func(opts *ResourceManagerOptions) { + opts.Logger = logger + } +} + +// ExecuteDeletion executes a deletion plan +// Important: Instances must be deleted before the app +func (rm *EdgeConnectResourceManager) ExecuteDeletion(ctx context.Context, plan *DeletionPlan) (*DeletionResult, error) { + startTime := time.Now() + rm.logf("Starting deletion: %s", plan.ConfigName) + + result := &DeletionResult{ + Plan: plan, + Success: true, + CompletedActions: []DeletionActionResult{}, + FailedActions: []DeletionActionResult{}, + } + + // If plan is empty, return success immediately + if plan.IsEmpty() { + rm.logf("No resources to delete") + result.Duration = time.Since(startTime) + return result, nil + } + + // Step 1: Delete all instances first + for _, instance := range plan.InstancesToDelete { + actionStart := time.Now() + rm.logf("Deleting instance: %s", instance.Name) + + instanceKey := edgeconnect.AppInstanceKey{ + Organization: instance.Organization, + Name: instance.Name, + CloudletKey: edgeconnect.CloudletKey{ + Organization: instance.CloudletOrg, + Name: instance.CloudletName, + }, + } + + err := rm.client.DeleteAppInstance(ctx, instanceKey, instance.Region) + actionResult := DeletionActionResult{ + Type: "instance", + Target: instance.Name, + Duration: time.Since(actionStart), + } + + if err != nil { + rm.logf("Failed to delete instance %s: %v", instance.Name, err) + actionResult.Success = false + actionResult.Error = err + result.FailedActions = append(result.FailedActions, actionResult) + result.Success = false + result.Error = fmt.Errorf("failed to delete instance %s: %w", instance.Name, err) + result.Duration = time.Since(startTime) + return result, result.Error + } + + rm.logf("Successfully deleted instance: %s", instance.Name) + actionResult.Success = true + result.CompletedActions = append(result.CompletedActions, actionResult) + } + + // Step 2: Delete the app (only after all instances are deleted) + if plan.AppToDelete != nil { + actionStart := time.Now() + app := plan.AppToDelete + rm.logf("Deleting app: %s version %s", app.Name, app.Version) + + appKey := edgeconnect.AppKey{ + Organization: app.Organization, + Name: app.Name, + Version: app.Version, + } + + err := rm.client.DeleteApp(ctx, appKey, app.Region) + actionResult := DeletionActionResult{ + Type: "app", + Target: fmt.Sprintf("%s:%s", app.Name, app.Version), + Duration: time.Since(actionStart), + } + + if err != nil { + rm.logf("Failed to delete app %s: %v", app.Name, err) + actionResult.Success = false + actionResult.Error = err + result.FailedActions = append(result.FailedActions, actionResult) + result.Success = false + result.Error = fmt.Errorf("failed to delete app %s: %w", app.Name, err) + result.Duration = time.Since(startTime) + return result, result.Error + } + + rm.logf("Successfully deleted app: %s", app.Name) + actionResult.Success = true + result.CompletedActions = append(result.CompletedActions, actionResult) + } + + result.Duration = time.Since(startTime) + rm.logf("Deletion completed successfully in %v", result.Duration) + + return result, nil +} + +// logf logs a message if a logger is configured +func (rm *EdgeConnectResourceManager) logf(format string, v ...interface{}) { + if rm.logger != nil { + rm.logger.Printf(format, v...) + } +} diff --git a/internal/delete/v1/planner.go b/internal/delete/v1/planner.go new file mode 100644 index 0000000..d436057 --- /dev/null +++ b/internal/delete/v1/planner.go @@ -0,0 +1,228 @@ +// ABOUTME: Deletion planner for EdgeConnect delete command +// ABOUTME: Analyzes current state to identify resources for deletion +package v1 + +import ( + "context" + "fmt" + "time" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" +) + +// EdgeConnectClientInterface defines the methods needed for deletion planning +type EdgeConnectClientInterface interface { + ShowApp(ctx context.Context, appKey edgeconnect.AppKey, region string) (edgeconnect.App, error) + ShowAppInstances(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) ([]edgeconnect.AppInstance, error) + DeleteApp(ctx context.Context, appKey edgeconnect.AppKey, region string) error + DeleteAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) error +} + +// Planner defines the interface for deletion planning +type Planner interface { + // Plan analyzes the configuration and current state to generate a deletion plan + Plan(ctx context.Context, config *config.EdgeConnectConfig) (*PlanResult, error) + + // PlanWithOptions allows customization of planning behavior + PlanWithOptions(ctx context.Context, config *config.EdgeConnectConfig, opts PlanOptions) (*PlanResult, error) +} + +// PlanOptions provides configuration for the planning process +type PlanOptions struct { + // DryRun indicates this is a planning-only operation + DryRun bool + + // Timeout for API operations + Timeout time.Duration +} + +// DefaultPlanOptions returns sensible default planning options +func DefaultPlanOptions() PlanOptions { + return PlanOptions{ + DryRun: false, + Timeout: 30 * time.Second, + } +} + +// EdgeConnectPlanner implements the Planner interface for EdgeConnect +type EdgeConnectPlanner struct { + client EdgeConnectClientInterface +} + +// NewPlanner creates a new EdgeConnect deletion planner +func NewPlanner(client EdgeConnectClientInterface) Planner { + return &EdgeConnectPlanner{ + client: client, + } +} + +// Plan analyzes the configuration and generates a deletion plan +func (p *EdgeConnectPlanner) Plan(ctx context.Context, config *config.EdgeConnectConfig) (*PlanResult, error) { + return p.PlanWithOptions(ctx, config, DefaultPlanOptions()) +} + +// PlanWithOptions generates a deletion plan with custom options +func (p *EdgeConnectPlanner) PlanWithOptions(ctx context.Context, config *config.EdgeConnectConfig, opts PlanOptions) (*PlanResult, error) { + startTime := time.Now() + var warnings []string + + // Create the deletion plan structure + plan := &DeletionPlan{ + ConfigName: config.Metadata.Name, + CreatedAt: startTime, + DryRun: opts.DryRun, + } + + // Get the region from the first infra template + region := config.Spec.InfraTemplate[0].Region + + // Step 1: Check if instances exist + instancesResult := p.findInstancesToDelete(ctx, config, region) + plan.InstancesToDelete = instancesResult.instances + if instancesResult.err != nil { + warnings = append(warnings, fmt.Sprintf("Error querying instances: %v", instancesResult.err)) + } + + // Step 2: Check if app exists + appResult := p.findAppToDelete(ctx, config, region) + plan.AppToDelete = appResult.app + if appResult.err != nil && !isNotFoundError(appResult.err) { + warnings = append(warnings, fmt.Sprintf("Error querying app: %v", appResult.err)) + } + + // Step 3: Calculate plan metadata + p.calculatePlanMetadata(plan) + + // Step 4: Generate summary + plan.Summary = plan.GenerateSummary() + + return &PlanResult{ + Plan: plan, + Warnings: warnings, + }, nil +} + +type appQueryResult struct { + app *AppDeletion + err error +} + +type instancesQueryResult struct { + instances []InstanceDeletion + err error +} + +// findAppToDelete checks if the app exists and should be deleted +func (p *EdgeConnectPlanner) findAppToDelete(ctx context.Context, config *config.EdgeConnectConfig, region string) appQueryResult { + appKey := edgeconnect.AppKey{ + Organization: config.Metadata.Organization, + Name: config.Metadata.Name, + Version: config.Metadata.AppVersion, + } + + app, err := p.client.ShowApp(ctx, appKey, region) + if err != nil { + if isNotFoundError(err) { + return appQueryResult{app: nil, err: nil} + } + return appQueryResult{app: nil, err: err} + } + + return appQueryResult{ + app: &AppDeletion{ + Name: app.Key.Name, + Version: app.Key.Version, + Organization: app.Key.Organization, + Region: region, + }, + err: nil, + } +} + +// findInstancesToDelete finds all instances that match the config +func (p *EdgeConnectPlanner) findInstancesToDelete(ctx context.Context, config *config.EdgeConnectConfig, region string) instancesQueryResult { + var allInstances []InstanceDeletion + + // Query instances for each infra template + for _, infra := range config.Spec.InfraTemplate { + instanceKey := edgeconnect.AppInstanceKey{ + Organization: config.Metadata.Organization, + Name: generateInstanceName(config.Metadata.Name, config.Metadata.AppVersion), + CloudletKey: edgeconnect.CloudletKey{ + Organization: infra.CloudletOrg, + Name: infra.CloudletName, + }, + } + + instances, err := p.client.ShowAppInstances(ctx, instanceKey, infra.Region) + if err != nil { + // If it's a not found error, just continue + if isNotFoundError(err) { + continue + } + return instancesQueryResult{instances: nil, err: err} + } + + // Add found instances to the list + for _, inst := range instances { + allInstances = append(allInstances, InstanceDeletion{ + Name: inst.Key.Name, + Organization: inst.Key.Organization, + Region: infra.Region, + CloudletOrg: inst.Key.CloudletKey.Organization, + CloudletName: inst.Key.CloudletKey.Name, + }) + } + } + + return instancesQueryResult{ + instances: allInstances, + err: nil, + } +} + +// calculatePlanMetadata calculates the total actions and estimated duration +func (p *EdgeConnectPlanner) calculatePlanMetadata(plan *DeletionPlan) { + totalActions := 0 + + if plan.AppToDelete != nil { + totalActions++ + } + + totalActions += len(plan.InstancesToDelete) + + plan.TotalActions = totalActions + + // Estimate duration: ~5 seconds per instance, ~3 seconds for app + estimatedSeconds := len(plan.InstancesToDelete) * 5 + if plan.AppToDelete != nil { + estimatedSeconds += 3 + } + plan.EstimatedDuration = time.Duration(estimatedSeconds) * time.Second +} + +// generateInstanceName creates an instance name from app name and version +func generateInstanceName(appName, appVersion string) string { + return fmt.Sprintf("%s-%s-instance", appName, appVersion) +} + +// isNotFoundError checks if an error is a 404 not found error +func isNotFoundError(err error) bool { + if apiErr, ok := err.(*edgeconnect.APIError); ok { + return apiErr.StatusCode == 404 + } + return false +} + +// PlanResult represents the result of a deletion planning operation +type PlanResult struct { + // Plan is the generated deletion plan + Plan *DeletionPlan + + // Error if planning failed + Error error + + // Warnings encountered during planning + Warnings []string +} diff --git a/internal/delete/v1/types.go b/internal/delete/v1/types.go new file mode 100644 index 0000000..a4d491c --- /dev/null +++ b/internal/delete/v1/types.go @@ -0,0 +1,157 @@ +// ABOUTME: Deletion planning types for EdgeConnect delete command +// ABOUTME: Defines structures for deletion plans and deletion results +package v1 + +import ( + "fmt" + "strings" + "time" +) + +// DeletionPlan represents the complete deletion plan for a configuration +type DeletionPlan struct { + // ConfigName is the name from metadata + ConfigName string + + // AppToDelete defines the app that will be deleted (nil if app doesn't exist) + AppToDelete *AppDeletion + + // InstancesToDelete defines the instances that will be deleted + InstancesToDelete []InstanceDeletion + + // Summary provides a human-readable summary of the plan + Summary string + + // TotalActions is the count of all actions that will be performed + TotalActions int + + // EstimatedDuration is the estimated time to complete the deletion + EstimatedDuration time.Duration + + // CreatedAt timestamp when the plan was created + CreatedAt time.Time + + // DryRun indicates if this is a dry-run plan + DryRun bool +} + +// AppDeletion represents an application to be deleted +type AppDeletion struct { + // Name of the application + Name string + + // Version of the application + Version string + + // Organization that owns the app + Organization string + + // Region where the app is deployed + Region string +} + +// InstanceDeletion represents an application instance to be deleted +type InstanceDeletion struct { + // Name of the instance + Name string + + // Organization that owns the instance + Organization string + + // Region where the instance is deployed + Region string + + // CloudletOrg that hosts the cloudlet + CloudletOrg string + + // CloudletName where the instance is running + CloudletName string +} + +// DeletionResult represents the result of a deletion operation +type DeletionResult struct { + // Plan that was executed + Plan *DeletionPlan + + // Success indicates if the deletion was successful + Success bool + + // CompletedActions lists actions that were successfully completed + CompletedActions []DeletionActionResult + + // FailedActions lists actions that failed + FailedActions []DeletionActionResult + + // Error that caused the deletion to fail (if any) + Error error + + // Duration taken to execute the plan + Duration time.Duration +} + +// DeletionActionResult represents the result of executing a single deletion action +type DeletionActionResult struct { + // Type of resource that was deleted ("app" or "instance") + Type string + + // Target describes what was being deleted + Target string + + // Success indicates if the action succeeded + Success bool + + // Error if the action failed + Error error + + // Duration taken to complete the action + Duration time.Duration +} + +// IsEmpty returns true if the deletion plan has no actions to perform +func (dp *DeletionPlan) IsEmpty() bool { + return dp.AppToDelete == nil && len(dp.InstancesToDelete) == 0 +} + +// GenerateSummary creates a human-readable summary of the deletion plan +func (dp *DeletionPlan) GenerateSummary() string { + if dp.IsEmpty() { + return "No resources found to delete" + } + + var sb strings.Builder + sb.WriteString(fmt.Sprintf("Deletion plan for '%s':\n", dp.ConfigName)) + + // Instance actions + if len(dp.InstancesToDelete) > 0 { + sb.WriteString(fmt.Sprintf("- DELETE %d instance(s)\n", len(dp.InstancesToDelete))) + cloudletSet := make(map[string]bool) + for _, inst := range dp.InstancesToDelete { + key := fmt.Sprintf("%s:%s", inst.CloudletOrg, inst.CloudletName) + cloudletSet[key] = true + } + sb.WriteString(fmt.Sprintf(" Across %d cloudlet(s)\n", len(cloudletSet))) + } + + // App action + if dp.AppToDelete != nil { + sb.WriteString(fmt.Sprintf("- DELETE application '%s' version %s\n", + dp.AppToDelete.Name, dp.AppToDelete.Version)) + } + + sb.WriteString(fmt.Sprintf("Estimated duration: %s", dp.EstimatedDuration.String())) + + return sb.String() +} + +// Validate checks if the deletion plan is valid +func (dp *DeletionPlan) Validate() error { + if dp.ConfigName == "" { + return fmt.Errorf("deletion plan must have a config name") + } + + if dp.IsEmpty() { + return fmt.Errorf("deletion plan has no resources to delete") + } + + return nil +} diff --git a/internal/delete/v2/manager.go b/internal/delete/v2/manager.go new file mode 100644 index 0000000..a644f32 --- /dev/null +++ b/internal/delete/v2/manager.go @@ -0,0 +1,166 @@ +// ABOUTME: Resource management for EdgeConnect delete command with deletion execution +// ABOUTME: Handles actual deletion operations with proper ordering (instances first, then app) +package v2 + +import ( + "context" + "fmt" + "time" + + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" +) + +// ResourceManagerInterface defines the interface for resource management +type ResourceManagerInterface interface { + // ExecuteDeletion executes a deletion plan + ExecuteDeletion(ctx context.Context, plan *DeletionPlan) (*DeletionResult, error) +} + +// EdgeConnectResourceManager implements resource management for EdgeConnect +type EdgeConnectResourceManager struct { + client EdgeConnectClientInterface + logger Logger +} + +// Logger interface for deletion logging +type Logger interface { + Printf(format string, v ...interface{}) +} + +// ResourceManagerOptions configures the resource manager behavior +type ResourceManagerOptions struct { + // Logger for deletion operations + Logger Logger +} + +// DefaultResourceManagerOptions returns sensible defaults +func DefaultResourceManagerOptions() ResourceManagerOptions { + return ResourceManagerOptions{ + Logger: nil, + } +} + +// NewResourceManager creates a new EdgeConnect resource manager +func NewResourceManager(client EdgeConnectClientInterface, opts ...func(*ResourceManagerOptions)) ResourceManagerInterface { + options := DefaultResourceManagerOptions() + for _, opt := range opts { + opt(&options) + } + + return &EdgeConnectResourceManager{ + client: client, + logger: options.Logger, + } +} + +// WithLogger sets a logger for deletion operations +func WithLogger(logger Logger) func(*ResourceManagerOptions) { + return func(opts *ResourceManagerOptions) { + opts.Logger = logger + } +} + +// ExecuteDeletion executes a deletion plan +// Important: Instances must be deleted before the app +func (rm *EdgeConnectResourceManager) ExecuteDeletion(ctx context.Context, plan *DeletionPlan) (*DeletionResult, error) { + startTime := time.Now() + rm.logf("Starting deletion: %s", plan.ConfigName) + + result := &DeletionResult{ + Plan: plan, + Success: true, + CompletedActions: []DeletionActionResult{}, + FailedActions: []DeletionActionResult{}, + } + + // If plan is empty, return success immediately + if plan.IsEmpty() { + rm.logf("No resources to delete") + result.Duration = time.Since(startTime) + return result, nil + } + + // Step 1: Delete all instances first + for _, instance := range plan.InstancesToDelete { + actionStart := time.Now() + rm.logf("Deleting instance: %s", instance.Name) + + instanceKey := v2.AppInstanceKey{ + Organization: instance.Organization, + Name: instance.Name, + CloudletKey: v2.CloudletKey{ + Organization: instance.CloudletOrg, + Name: instance.CloudletName, + }, + } + + err := rm.client.DeleteAppInstance(ctx, instanceKey, instance.Region) + actionResult := DeletionActionResult{ + Type: "instance", + Target: instance.Name, + Duration: time.Since(actionStart), + } + + if err != nil { + rm.logf("Failed to delete instance %s: %v", instance.Name, err) + actionResult.Success = false + actionResult.Error = err + result.FailedActions = append(result.FailedActions, actionResult) + result.Success = false + result.Error = fmt.Errorf("failed to delete instance %s: %w", instance.Name, err) + result.Duration = time.Since(startTime) + return result, result.Error + } + + rm.logf("Successfully deleted instance: %s", instance.Name) + actionResult.Success = true + result.CompletedActions = append(result.CompletedActions, actionResult) + } + + // Step 2: Delete the app (only after all instances are deleted) + if plan.AppToDelete != nil { + actionStart := time.Now() + app := plan.AppToDelete + rm.logf("Deleting app: %s version %s", app.Name, app.Version) + + appKey := v2.AppKey{ + Organization: app.Organization, + Name: app.Name, + Version: app.Version, + } + + err := rm.client.DeleteApp(ctx, appKey, app.Region) + actionResult := DeletionActionResult{ + Type: "app", + Target: fmt.Sprintf("%s:%s", app.Name, app.Version), + Duration: time.Since(actionStart), + } + + if err != nil { + rm.logf("Failed to delete app %s: %v", app.Name, err) + actionResult.Success = false + actionResult.Error = err + result.FailedActions = append(result.FailedActions, actionResult) + result.Success = false + result.Error = fmt.Errorf("failed to delete app %s: %w", app.Name, err) + result.Duration = time.Since(startTime) + return result, result.Error + } + + rm.logf("Successfully deleted app: %s", app.Name) + actionResult.Success = true + result.CompletedActions = append(result.CompletedActions, actionResult) + } + + result.Duration = time.Since(startTime) + rm.logf("Deletion completed successfully in %v", result.Duration) + + return result, nil +} + +// logf logs a message if a logger is configured +func (rm *EdgeConnectResourceManager) logf(format string, v ...interface{}) { + if rm.logger != nil { + rm.logger.Printf(format, v...) + } +} diff --git a/internal/delete/v2/manager_test.go b/internal/delete/v2/manager_test.go new file mode 100644 index 0000000..fd098af --- /dev/null +++ b/internal/delete/v2/manager_test.go @@ -0,0 +1,200 @@ +// ABOUTME: Tests for EdgeConnect deletion manager with mock scenarios +// ABOUTME: Tests deletion execution and error handling with mock clients +package v2 + +import ( + "context" + "fmt" + "testing" + "time" + + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +// MockResourceClient for testing deletion manager +type MockResourceClient struct { + mock.Mock +} + +func (m *MockResourceClient) ShowApp(ctx context.Context, appKey v2.AppKey, region string) (v2.App, error) { + args := m.Called(ctx, appKey, region) + if args.Get(0) == nil { + return v2.App{}, args.Error(1) + } + return args.Get(0).(v2.App), args.Error(1) +} + +func (m *MockResourceClient) ShowAppInstances(ctx context.Context, instanceKey v2.AppInstanceKey, region string) ([]v2.AppInstance, error) { + args := m.Called(ctx, instanceKey, region) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]v2.AppInstance), args.Error(1) +} + +func (m *MockResourceClient) DeleteApp(ctx context.Context, appKey v2.AppKey, region string) error { + args := m.Called(ctx, appKey, region) + return args.Error(0) +} + +func (m *MockResourceClient) DeleteAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) error { + args := m.Called(ctx, instanceKey, region) + return args.Error(0) +} + +// TestLogger implements Logger interface for testing +type TestLogger struct { + messages []string +} + +func (l *TestLogger) Printf(format string, v ...interface{}) { + l.messages = append(l.messages, fmt.Sprintf(format, v...)) +} + +func TestNewResourceManager(t *testing.T) { + mockClient := &MockResourceClient{} + manager := NewResourceManager(mockClient) + + assert.NotNil(t, manager) +} + +func TestWithLogger(t *testing.T) { + mockClient := &MockResourceClient{} + logger := &TestLogger{} + + manager := NewResourceManager(mockClient, WithLogger(logger)) + + // Cast to implementation to check logger was set + impl := manager.(*EdgeConnectResourceManager) + assert.Equal(t, logger, impl.logger) +} + +func createTestDeletionPlan() *DeletionPlan { + return &DeletionPlan{ + ConfigName: "test-deletion", + AppToDelete: &AppDeletion{ + Name: "test-app", + Version: "1.0.0", + Organization: "testorg", + Region: "US", + }, + InstancesToDelete: []InstanceDeletion{ + { + Name: "test-app-1.0.0-instance", + Organization: "testorg", + Region: "US", + CloudletOrg: "cloudletorg", + CloudletName: "cloudlet1", + }, + }, + TotalActions: 2, + EstimatedDuration: 10 * time.Second, + } +} + +func TestExecuteDeletion_Success(t *testing.T) { + mockClient := &MockResourceClient{} + logger := &TestLogger{} + manager := NewResourceManager(mockClient, WithLogger(logger)) + + plan := createTestDeletionPlan() + + // Mock successful deletion operations + mockClient.On("DeleteAppInstance", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "US"). + Return(nil) + mockClient.On("DeleteApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US"). + Return(nil) + + ctx := context.Background() + result, err := manager.ExecuteDeletion(ctx, plan) + + require.NoError(t, err) + require.NotNil(t, result) + assert.True(t, result.Success) + assert.Len(t, result.CompletedActions, 2) // 1 instance + 1 app + assert.Len(t, result.FailedActions, 0) + + mockClient.AssertExpectations(t) +} + +func TestExecuteDeletion_InstanceDeleteFails(t *testing.T) { + mockClient := &MockResourceClient{} + logger := &TestLogger{} + manager := NewResourceManager(mockClient, WithLogger(logger)) + + plan := createTestDeletionPlan() + + // Mock instance deletion failure + mockClient.On("DeleteAppInstance", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "US"). + Return(fmt.Errorf("instance deletion failed")) + + ctx := context.Background() + result, err := manager.ExecuteDeletion(ctx, plan) + + require.Error(t, err) + require.NotNil(t, result) + assert.False(t, result.Success) + assert.Len(t, result.FailedActions, 1) + + mockClient.AssertExpectations(t) +} + +func TestExecuteDeletion_OnlyInstances(t *testing.T) { + mockClient := &MockResourceClient{} + logger := &TestLogger{} + manager := NewResourceManager(mockClient, WithLogger(logger)) + + plan := &DeletionPlan{ + ConfigName: "test-deletion", + AppToDelete: nil, // No app to delete + InstancesToDelete: []InstanceDeletion{ + { + Name: "test-app-1.0.0-instance", + Organization: "testorg", + Region: "US", + CloudletOrg: "cloudletorg", + CloudletName: "cloudlet1", + }, + }, + TotalActions: 1, + EstimatedDuration: 5 * time.Second, + } + + // Mock successful instance deletion + mockClient.On("DeleteAppInstance", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "US"). + Return(nil) + + ctx := context.Background() + result, err := manager.ExecuteDeletion(ctx, plan) + + require.NoError(t, err) + require.NotNil(t, result) + assert.True(t, result.Success) + assert.Len(t, result.CompletedActions, 1) + + mockClient.AssertExpectations(t) +} + +func TestExecuteDeletion_EmptyPlan(t *testing.T) { + mockClient := &MockResourceClient{} + manager := NewResourceManager(mockClient) + + plan := &DeletionPlan{ + ConfigName: "test-deletion", + AppToDelete: nil, + InstancesToDelete: []InstanceDeletion{}, + TotalActions: 0, + } + + ctx := context.Background() + result, err := manager.ExecuteDeletion(ctx, plan) + + require.NoError(t, err) + require.NotNil(t, result) + assert.True(t, result.Success) + assert.Len(t, result.CompletedActions, 0) + assert.Len(t, result.FailedActions, 0) +} diff --git a/internal/delete/v2/planner.go b/internal/delete/v2/planner.go new file mode 100644 index 0000000..e77cd9e --- /dev/null +++ b/internal/delete/v2/planner.go @@ -0,0 +1,228 @@ +// ABOUTME: Deletion planner for EdgeConnect delete command +// ABOUTME: Analyzes current state to identify resources for deletion +package v2 + +import ( + "context" + "fmt" + "time" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" +) + +// EdgeConnectClientInterface defines the methods needed for deletion planning +type EdgeConnectClientInterface interface { + ShowApp(ctx context.Context, appKey v2.AppKey, region string) (v2.App, error) + ShowAppInstances(ctx context.Context, instanceKey v2.AppInstanceKey, region string) ([]v2.AppInstance, error) + DeleteApp(ctx context.Context, appKey v2.AppKey, region string) error + DeleteAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) error +} + +// Planner defines the interface for deletion planning +type Planner interface { + // Plan analyzes the configuration and current state to generate a deletion plan + Plan(ctx context.Context, config *config.EdgeConnectConfig) (*PlanResult, error) + + // PlanWithOptions allows customization of planning behavior + PlanWithOptions(ctx context.Context, config *config.EdgeConnectConfig, opts PlanOptions) (*PlanResult, error) +} + +// PlanOptions provides configuration for the planning process +type PlanOptions struct { + // DryRun indicates this is a planning-only operation + DryRun bool + + // Timeout for API operations + Timeout time.Duration +} + +// DefaultPlanOptions returns sensible default planning options +func DefaultPlanOptions() PlanOptions { + return PlanOptions{ + DryRun: false, + Timeout: 30 * time.Second, + } +} + +// EdgeConnectPlanner implements the Planner interface for EdgeConnect +type EdgeConnectPlanner struct { + client EdgeConnectClientInterface +} + +// NewPlanner creates a new EdgeConnect deletion planner +func NewPlanner(client EdgeConnectClientInterface) Planner { + return &EdgeConnectPlanner{ + client: client, + } +} + +// Plan analyzes the configuration and generates a deletion plan +func (p *EdgeConnectPlanner) Plan(ctx context.Context, config *config.EdgeConnectConfig) (*PlanResult, error) { + return p.PlanWithOptions(ctx, config, DefaultPlanOptions()) +} + +// PlanWithOptions generates a deletion plan with custom options +func (p *EdgeConnectPlanner) PlanWithOptions(ctx context.Context, config *config.EdgeConnectConfig, opts PlanOptions) (*PlanResult, error) { + startTime := time.Now() + var warnings []string + + // Create the deletion plan structure + plan := &DeletionPlan{ + ConfigName: config.Metadata.Name, + CreatedAt: startTime, + DryRun: opts.DryRun, + } + + // Get the region from the first infra template + region := config.Spec.InfraTemplate[0].Region + + // Step 1: Check if instances exist + instancesResult := p.findInstancesToDelete(ctx, config, region) + plan.InstancesToDelete = instancesResult.instances + if instancesResult.err != nil { + warnings = append(warnings, fmt.Sprintf("Error querying instances: %v", instancesResult.err)) + } + + // Step 2: Check if app exists + appResult := p.findAppToDelete(ctx, config, region) + plan.AppToDelete = appResult.app + if appResult.err != nil && !isNotFoundError(appResult.err) { + warnings = append(warnings, fmt.Sprintf("Error querying app: %v", appResult.err)) + } + + // Step 3: Calculate plan metadata + p.calculatePlanMetadata(plan) + + // Step 4: Generate summary + plan.Summary = plan.GenerateSummary() + + return &PlanResult{ + Plan: plan, + Warnings: warnings, + }, nil +} + +type appQueryResult struct { + app *AppDeletion + err error +} + +type instancesQueryResult struct { + instances []InstanceDeletion + err error +} + +// findAppToDelete checks if the app exists and should be deleted +func (p *EdgeConnectPlanner) findAppToDelete(ctx context.Context, config *config.EdgeConnectConfig, region string) appQueryResult { + appKey := v2.AppKey{ + Organization: config.Metadata.Organization, + Name: config.Metadata.Name, + Version: config.Metadata.AppVersion, + } + + app, err := p.client.ShowApp(ctx, appKey, region) + if err != nil { + if isNotFoundError(err) { + return appQueryResult{app: nil, err: nil} + } + return appQueryResult{app: nil, err: err} + } + + return appQueryResult{ + app: &AppDeletion{ + Name: app.Key.Name, + Version: app.Key.Version, + Organization: app.Key.Organization, + Region: region, + }, + err: nil, + } +} + +// findInstancesToDelete finds all instances that match the config +func (p *EdgeConnectPlanner) findInstancesToDelete(ctx context.Context, config *config.EdgeConnectConfig, region string) instancesQueryResult { + var allInstances []InstanceDeletion + + // Query instances for each infra template + for _, infra := range config.Spec.InfraTemplate { + instanceKey := v2.AppInstanceKey{ + Organization: config.Metadata.Organization, + Name: generateInstanceName(config.Metadata.Name, config.Metadata.AppVersion), + CloudletKey: v2.CloudletKey{ + Organization: infra.CloudletOrg, + Name: infra.CloudletName, + }, + } + + instances, err := p.client.ShowAppInstances(ctx, instanceKey, infra.Region) + if err != nil { + // If it's a not found error, just continue + if isNotFoundError(err) { + continue + } + return instancesQueryResult{instances: nil, err: err} + } + + // Add found instances to the list + for _, inst := range instances { + allInstances = append(allInstances, InstanceDeletion{ + Name: inst.Key.Name, + Organization: inst.Key.Organization, + Region: infra.Region, + CloudletOrg: inst.Key.CloudletKey.Organization, + CloudletName: inst.Key.CloudletKey.Name, + }) + } + } + + return instancesQueryResult{ + instances: allInstances, + err: nil, + } +} + +// calculatePlanMetadata calculates the total actions and estimated duration +func (p *EdgeConnectPlanner) calculatePlanMetadata(plan *DeletionPlan) { + totalActions := 0 + + if plan.AppToDelete != nil { + totalActions++ + } + + totalActions += len(plan.InstancesToDelete) + + plan.TotalActions = totalActions + + // Estimate duration: ~5 seconds per instance, ~3 seconds for app + estimatedSeconds := len(plan.InstancesToDelete) * 5 + if plan.AppToDelete != nil { + estimatedSeconds += 3 + } + plan.EstimatedDuration = time.Duration(estimatedSeconds) * time.Second +} + +// generateInstanceName creates an instance name from app name and version +func generateInstanceName(appName, appVersion string) string { + return fmt.Sprintf("%s-%s-instance", appName, appVersion) +} + +// isNotFoundError checks if an error is a 404 not found error +func isNotFoundError(err error) bool { + if apiErr, ok := err.(*v2.APIError); ok { + return apiErr.StatusCode == 404 + } + return false +} + +// PlanResult represents the result of a deletion planning operation +type PlanResult struct { + // Plan is the generated deletion plan + Plan *DeletionPlan + + // Error if planning failed + Error error + + // Warnings encountered during planning + Warnings []string +} diff --git a/internal/delete/v2/planner_test.go b/internal/delete/v2/planner_test.go new file mode 100644 index 0000000..c37a318 --- /dev/null +++ b/internal/delete/v2/planner_test.go @@ -0,0 +1,219 @@ +// ABOUTME: Tests for EdgeConnect deletion planner with mock scenarios +// ABOUTME: Tests deletion planning logic and resource discovery +package v2 + +import ( + "context" + "os" + "path/filepath" + "testing" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +// MockEdgeConnectClient is a mock implementation of the EdgeConnect client +type MockEdgeConnectClient struct { + mock.Mock +} + +func (m *MockEdgeConnectClient) ShowApp(ctx context.Context, appKey v2.AppKey, region string) (v2.App, error) { + args := m.Called(ctx, appKey, region) + if args.Get(0) == nil { + return v2.App{}, args.Error(1) + } + return args.Get(0).(v2.App), args.Error(1) +} + +func (m *MockEdgeConnectClient) ShowAppInstances(ctx context.Context, instanceKey v2.AppInstanceKey, region string) ([]v2.AppInstance, error) { + args := m.Called(ctx, instanceKey, region) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]v2.AppInstance), args.Error(1) +} + +func (m *MockEdgeConnectClient) DeleteApp(ctx context.Context, appKey v2.AppKey, region string) error { + args := m.Called(ctx, appKey, region) + return args.Error(0) +} + +func (m *MockEdgeConnectClient) DeleteAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) error { + args := m.Called(ctx, instanceKey, region) + return args.Error(0) +} + +func createTestConfig(t *testing.T) *config.EdgeConnectConfig { + // Create temporary manifest file + tempDir := t.TempDir() + manifestFile := filepath.Join(tempDir, "test-manifest.yaml") + manifestContent := "apiVersion: v1\nkind: Pod\nmetadata:\n name: test\n" + err := os.WriteFile(manifestFile, []byte(manifestContent), 0644) + require.NoError(t, err) + + return &config.EdgeConnectConfig{ + Kind: "edgeconnect-deployment", + Metadata: config.Metadata{ + Name: "test-app", + AppVersion: "1.0.0", + Organization: "testorg", + }, + Spec: config.Spec{ + K8sApp: &config.K8sApp{ + ManifestFile: manifestFile, + }, + InfraTemplate: []config.InfraTemplate{ + { + Region: "US", + CloudletOrg: "TestCloudletOrg", + CloudletName: "TestCloudlet", + FlavorName: "small", + }, + }, + }, + } +} + +func TestNewPlanner(t *testing.T) { + mockClient := &MockEdgeConnectClient{} + planner := NewPlanner(mockClient) + + assert.NotNil(t, planner) +} + +func TestPlanDeletion_WithExistingResources(t *testing.T) { + mockClient := &MockEdgeConnectClient{} + planner := NewPlanner(mockClient) + testConfig := createTestConfig(t) + + // Mock existing app + existingApp := v2.App{ + Key: v2.AppKey{ + Organization: "testorg", + Name: "test-app", + Version: "1.0.0", + }, + Deployment: "kubernetes", + } + + // Mock existing instances + existingInstances := []v2.AppInstance{ + { + Key: v2.AppInstanceKey{ + Organization: "testorg", + Name: "test-app-1.0.0-instance", + CloudletKey: v2.CloudletKey{ + Organization: "TestCloudletOrg", + Name: "TestCloudlet", + }, + }, + AppKey: v2.AppKey{ + Organization: "testorg", + Name: "test-app", + Version: "1.0.0", + }, + }, + } + + mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US"). + Return(existingApp, nil) + + mockClient.On("ShowAppInstances", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "US"). + Return(existingInstances, nil) + + ctx := context.Background() + result, err := planner.Plan(ctx, testConfig) + + require.NoError(t, err) + require.NotNil(t, result) + require.NotNil(t, result.Plan) + + plan := result.Plan + assert.Equal(t, "test-app", plan.ConfigName) + assert.NotNil(t, plan.AppToDelete) + assert.Equal(t, "test-app", plan.AppToDelete.Name) + assert.Equal(t, "1.0.0", plan.AppToDelete.Version) + assert.Equal(t, "testorg", plan.AppToDelete.Organization) + + require.Len(t, plan.InstancesToDelete, 1) + assert.Equal(t, "test-app-1.0.0-instance", plan.InstancesToDelete[0].Name) + assert.Equal(t, "testorg", plan.InstancesToDelete[0].Organization) + + assert.Equal(t, 2, plan.TotalActions) // 1 app + 1 instance + assert.False(t, plan.IsEmpty()) + + mockClient.AssertExpectations(t) +} + +func TestPlanDeletion_NoResourcesExist(t *testing.T) { + mockClient := &MockEdgeConnectClient{} + planner := NewPlanner(mockClient) + testConfig := createTestConfig(t) + + // Mock API calls to return "not found" errors + mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US"). + Return(v2.App{}, &v2.APIError{StatusCode: 404, Messages: []string{"App not found"}}) + + mockClient.On("ShowAppInstances", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "US"). + Return([]v2.AppInstance{}, nil) + + ctx := context.Background() + result, err := planner.Plan(ctx, testConfig) + + require.NoError(t, err) + require.NotNil(t, result) + require.NotNil(t, result.Plan) + + plan := result.Plan + assert.Equal(t, "test-app", plan.ConfigName) + assert.Nil(t, plan.AppToDelete) + assert.Len(t, plan.InstancesToDelete, 0) + assert.Equal(t, 0, plan.TotalActions) + assert.True(t, plan.IsEmpty()) + + mockClient.AssertExpectations(t) +} + +func TestPlanDeletion_OnlyInstancesExist(t *testing.T) { + mockClient := &MockEdgeConnectClient{} + planner := NewPlanner(mockClient) + testConfig := createTestConfig(t) + + // Mock existing instances but no app + existingInstances := []v2.AppInstance{ + { + Key: v2.AppInstanceKey{ + Organization: "testorg", + Name: "test-app-1.0.0-instance", + CloudletKey: v2.CloudletKey{ + Organization: "TestCloudletOrg", + Name: "TestCloudlet", + }, + }, + }, + } + + mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US"). + Return(v2.App{}, &v2.APIError{StatusCode: 404, Messages: []string{"App not found"}}) + + mockClient.On("ShowAppInstances", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "US"). + Return(existingInstances, nil) + + ctx := context.Background() + result, err := planner.Plan(ctx, testConfig) + + require.NoError(t, err) + require.NotNil(t, result) + require.NotNil(t, result.Plan) + + plan := result.Plan + assert.Nil(t, plan.AppToDelete) + assert.Len(t, plan.InstancesToDelete, 1) + assert.Equal(t, 1, plan.TotalActions) + assert.False(t, plan.IsEmpty()) + + mockClient.AssertExpectations(t) +} diff --git a/internal/delete/v2/types.go b/internal/delete/v2/types.go new file mode 100644 index 0000000..de50a68 --- /dev/null +++ b/internal/delete/v2/types.go @@ -0,0 +1,157 @@ +// ABOUTME: Deletion planning types for EdgeConnect delete command +// ABOUTME: Defines structures for deletion plans and deletion results +package v2 + +import ( + "fmt" + "strings" + "time" +) + +// DeletionPlan represents the complete deletion plan for a configuration +type DeletionPlan struct { + // ConfigName is the name from metadata + ConfigName string + + // AppToDelete defines the app that will be deleted (nil if app doesn't exist) + AppToDelete *AppDeletion + + // InstancesToDelete defines the instances that will be deleted + InstancesToDelete []InstanceDeletion + + // Summary provides a human-readable summary of the plan + Summary string + + // TotalActions is the count of all actions that will be performed + TotalActions int + + // EstimatedDuration is the estimated time to complete the deletion + EstimatedDuration time.Duration + + // CreatedAt timestamp when the plan was created + CreatedAt time.Time + + // DryRun indicates if this is a dry-run plan + DryRun bool +} + +// AppDeletion represents an application to be deleted +type AppDeletion struct { + // Name of the application + Name string + + // Version of the application + Version string + + // Organization that owns the app + Organization string + + // Region where the app is deployed + Region string +} + +// InstanceDeletion represents an application instance to be deleted +type InstanceDeletion struct { + // Name of the instance + Name string + + // Organization that owns the instance + Organization string + + // Region where the instance is deployed + Region string + + // CloudletOrg that hosts the cloudlet + CloudletOrg string + + // CloudletName where the instance is running + CloudletName string +} + +// DeletionResult represents the result of a deletion operation +type DeletionResult struct { + // Plan that was executed + Plan *DeletionPlan + + // Success indicates if the deletion was successful + Success bool + + // CompletedActions lists actions that were successfully completed + CompletedActions []DeletionActionResult + + // FailedActions lists actions that failed + FailedActions []DeletionActionResult + + // Error that caused the deletion to fail (if any) + Error error + + // Duration taken to execute the plan + Duration time.Duration +} + +// DeletionActionResult represents the result of executing a single deletion action +type DeletionActionResult struct { + // Type of resource that was deleted ("app" or "instance") + Type string + + // Target describes what was being deleted + Target string + + // Success indicates if the action succeeded + Success bool + + // Error if the action failed + Error error + + // Duration taken to complete the action + Duration time.Duration +} + +// IsEmpty returns true if the deletion plan has no actions to perform +func (dp *DeletionPlan) IsEmpty() bool { + return dp.AppToDelete == nil && len(dp.InstancesToDelete) == 0 +} + +// GenerateSummary creates a human-readable summary of the deletion plan +func (dp *DeletionPlan) GenerateSummary() string { + if dp.IsEmpty() { + return "No resources found to delete" + } + + var sb strings.Builder + sb.WriteString(fmt.Sprintf("Deletion plan for '%s':\n", dp.ConfigName)) + + // Instance actions + if len(dp.InstancesToDelete) > 0 { + sb.WriteString(fmt.Sprintf("- DELETE %d instance(s)\n", len(dp.InstancesToDelete))) + cloudletSet := make(map[string]bool) + for _, inst := range dp.InstancesToDelete { + key := fmt.Sprintf("%s:%s", inst.CloudletOrg, inst.CloudletName) + cloudletSet[key] = true + } + sb.WriteString(fmt.Sprintf(" Across %d cloudlet(s)\n", len(cloudletSet))) + } + + // App action + if dp.AppToDelete != nil { + sb.WriteString(fmt.Sprintf("- DELETE application '%s' version %s\n", + dp.AppToDelete.Name, dp.AppToDelete.Version)) + } + + sb.WriteString(fmt.Sprintf("Estimated duration: %s", dp.EstimatedDuration.String())) + + return sb.String() +} + +// Validate checks if the deletion plan is valid +func (dp *DeletionPlan) Validate() error { + if dp.ConfigName == "" { + return fmt.Errorf("deletion plan must have a config name") + } + + if dp.IsEmpty() { + return fmt.Errorf("deletion plan has no resources to delete") + } + + return nil +} diff --git a/internal/delete/v2/types_test.go b/internal/delete/v2/types_test.go new file mode 100644 index 0000000..8dfa6b0 --- /dev/null +++ b/internal/delete/v2/types_test.go @@ -0,0 +1,95 @@ +package v2 + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestDeletionPlan_IsEmpty(t *testing.T) { + tests := []struct { + name string + plan *DeletionPlan + expected bool + }{ + { + name: "empty plan with no resources", + plan: &DeletionPlan{ + ConfigName: "test-config", + AppToDelete: nil, + InstancesToDelete: []InstanceDeletion{}, + }, + expected: true, + }, + { + name: "plan with app deletion", + plan: &DeletionPlan{ + ConfigName: "test-config", + AppToDelete: &AppDeletion{ + Name: "test-app", + Organization: "test-org", + Version: "1.0", + Region: "US", + }, + InstancesToDelete: []InstanceDeletion{}, + }, + expected: false, + }, + { + name: "plan with instance deletion", + plan: &DeletionPlan{ + ConfigName: "test-config", + AppToDelete: nil, + InstancesToDelete: []InstanceDeletion{ + { + Name: "test-instance", + Organization: "test-org", + }, + }, + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.plan.IsEmpty() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestDeletionPlan_GenerateSummary(t *testing.T) { + plan := &DeletionPlan{ + ConfigName: "test-config", + AppToDelete: &AppDeletion{ + Name: "test-app", + Organization: "test-org", + Version: "1.0", + Region: "US", + }, + InstancesToDelete: []InstanceDeletion{ + { + Name: "test-instance-1", + Organization: "test-org", + CloudletName: "cloudlet-1", + CloudletOrg: "cloudlet-org", + }, + { + Name: "test-instance-2", + Organization: "test-org", + CloudletName: "cloudlet-2", + CloudletOrg: "cloudlet-org", + }, + }, + TotalActions: 3, + EstimatedDuration: 30 * time.Second, + } + + summary := plan.GenerateSummary() + + assert.Contains(t, summary, "test-config") + assert.Contains(t, summary, "DELETE application 'test-app'") + assert.Contains(t, summary, "DELETE 2 instance(s)") +} diff --git a/sdk/edgeconnect/v2/appinstance.go b/sdk/edgeconnect/v2/appinstance.go index 57e6b3c..4fb7204 100644 --- a/sdk/edgeconnect/v2/appinstance.go +++ b/sdk/edgeconnect/v2/appinstance.go @@ -173,8 +173,9 @@ func (c *Client) DeleteAppInstance(ctx context.Context, appInstKey AppInstanceKe url := c.BaseURL + "/api/v1/auth/ctrl/DeleteAppInst" input := DeleteAppInstanceInput{ - Key: appInstKey, + Region: region, } + input.AppInst.Key = appInstKey resp, err := transport.Call(ctx, "POST", url, input) if err != nil { diff --git a/sdk/edgeconnect/v2/apps.go b/sdk/edgeconnect/v2/apps.go index ce5bb76..06d529f 100644 --- a/sdk/edgeconnect/v2/apps.go +++ b/sdk/edgeconnect/v2/apps.go @@ -144,9 +144,9 @@ func (c *Client) DeleteApp(ctx context.Context, appKey AppKey, region string) er url := c.BaseURL + "/api/v1/auth/ctrl/DeleteApp" input := DeleteAppInput{ - Key: appKey, Region: region, } + input.App.Key = appKey resp, err := transport.Call(ctx, "POST", url, input) if err != nil { diff --git a/sdk/edgeconnect/v2/types.go b/sdk/edgeconnect/v2/types.go index 82995e0..0bb6875 100644 --- a/sdk/edgeconnect/v2/types.go +++ b/sdk/edgeconnect/v2/types.go @@ -273,13 +273,18 @@ type UpdateAppInstanceInput struct { // DeleteAppInput represents input for deleting an application type DeleteAppInput struct { - Key AppKey `json:"key"` Region string `json:"region"` + App struct { + Key AppKey `json:"key"` + } `json:"app"` } // DeleteAppInstanceInput represents input for deleting an app instance type DeleteAppInstanceInput struct { - Key AppInstanceKey `json:"key"` + Region string `json:"region"` + AppInst struct { + Key AppInstanceKey `json:"key"` + } `json:"appinst"` } // Response wrapper types From a70e107a3fef8c58052ab421d176f1b5fc9519db Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 20 Oct 2025 15:55:58 +0200 Subject: [PATCH 27/40] feat(signing): added goreleaser signing --- .github/workflows/release.yaml | 7 +++++++ .goreleaser.yaml | 21 ++++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d2a754b..3040258 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -19,9 +19,16 @@ jobs: go-version: ">=1.25.1" - name: Test code run: make test + - name: Import GPG key + id: import_gpg + uses: https://github.com/crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} - name: Run GoReleaser uses: https://github.com/goreleaser/goreleaser-action@v6 env: GITEA_TOKEN: ${{ secrets.PACKAGES_TOKEN }} + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} with: args: release --clean diff --git a/.goreleaser.yaml b/.goreleaser.yaml index e92295f..9d098eb 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -10,11 +10,11 @@ builds: - CGO_ENABLED=0 goos: - linux - - darwin - - windows + #- darwin + #- windows goarch: - amd64 - - arm64 + #- arm64 archives: - formats: [tar.gz] @@ -31,6 +31,21 @@ archives: - goos: windows formats: [zip] +signs: + - artifacts: checksum + cmd: gpg2 + args: + - "--batch" + - "-u" + - "{{ .Env.GPG_FINGERPRINT }}" + - "--output" + - "${signature}" + - "--detach-sign" + - "${artifact}" + +#binary_signs: +# - {} + changelog: abbrev: 10 filters: From 318af7baff86a3c3f8214ca47c0cea217c35ec52 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 20 Oct 2025 15:59:05 +0200 Subject: [PATCH 28/40] feat(signing): added goreleaser signing --- .goreleaser.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 9d098eb..4731016 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -33,7 +33,7 @@ archives: signs: - artifacts: checksum - cmd: gpg2 + cmd: gpg args: - "--batch" - "-u" From 65e018506475d71f8808c739ac126cb639122341 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 20 Oct 2025 16:47:00 +0200 Subject: [PATCH 29/40] feat(signing): added public key --- public.gpg | Bin 0 -> 2298 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public.gpg diff --git a/public.gpg b/public.gpg new file mode 100644 index 0000000000000000000000000000000000000000..32d15f1381b15652b515672192b0264a83eccbea GIT binary patch literal 2298 zcmajf`9Bj51HkcZj?7$5?mI&6FF8W)p|HsnBi9@ob7UKHMOvsizOIQxwJ3?oy%KUv zjUsaw5<+t1E9aW;bG)9{^ZfSw0iWMKuXhQEpA)@vt`H~$XgoQp{V}`l6eGfWvYl&{ zGjDMvvd^_0bdUAI$Et!AQ=O7Jood4~o)J+Xtfnxu4snk(9^sUZUEY<615RDe-t$?% z!;o7&e!AX}x&8cO@SINDAZ|lS5KYQMkU{`-qh?;nc@lu^Q4mLyz9 z8mcY8PW1Fu<6hL%yWwO8z8D1Q#H-BFuKU<*CqNvhvg@7c{A}f7Fei=(FG@g@|WgBlwFO9cCr}Zll4#rX>FG(z4r+3YJ-P`TRDqMH2UWPjaAO%}dSl9rbj2 zh68sF61Ta4KLCYYKw7kIX8$_VXZIpwgU5w=jyl~w5F$HE`Xc?HuQ@8uj&iZg`c>2P z7V_d3v0a$r=TyV^3c~eX@Dj1`Q!r(N6f@4GCEFvOsnm9sqbW{vpD=66PCdiFb7|07S z6II}JwTD;6CJ5_4qIfLr^nyG#l~o8_pno0*lH%Y3b5ToExgcPkV<2u3UN8>?#3>5m z0D@$JAhA>cFIeb5N##>PwxS~fnUSymIf+URQX5`6vP(*d)nBS3E_IfLqU}poM$45e zHS4`Fu5DI~7$xjSt1fd!q6M5O87#ps5Odw9`ii@%PUj)9J$;mAa{!!{G@|&#%7)pT z#eJRNyE#!oQ!L@7VPM1enR9`@9GwuUr_J&FhC^T&l7ZxTK&IO)nYYAl| z-Axv;h8_=}3E`)v&Y;aV<^-Q?=@$0Qmb{J~3Y1NCqqie_CwDF%{#9EbA}%_A3({6uJE2yj{I|8uBM)^~!k9L!6rG@3zjyd}f#>)5ldfST*Vi{1sld zg*{#hVYL5J^@c>LJM2n+JTy3YV=8X8%r$i!m^AllEK|-0wZn8)vu+ftw1}FQJ-_$N zz^SLnogE%vuAXb52;n0x=V*?AcBp&kx%rH`OWXSSOCae|VzI-iZW^JpW&WfbcH3c$ zgF22HRObE|mbT!x$cPd&VrGs@N2f%QunnhSr6I~^m0tMi^7$+OmWe_jNN&6WgPH&( zJMFm2{Exjn?P5&F229W-&a~EZ#UL>&TE#IjMar-t?;B;r_v$LIuHvKZrjst|FMVdD zWfJ%Ff4grFbeMFlJbeOdoY{|@jF6naUSvvkQZ2nFcbFQHSk)?*$pH*^8dxU;Orm66` ze(i1$Z)4XFR%fQy&4p_Z>~mNk9nr}~;uRNQy~8mxPX?KSh%Td*xRRld8|AJU4kk98 zat_~$z_N>Ie^#nYJA)>p!D~+t5CJUX?%8)WKf2=S-0Sqr8g#pOPPls0k^rh*Zlc)Itw0_?!GBS8 z6J*LM{tsr9{|_^K|7PZYIVpWBcw{j#BSdacN1KS{(tR>5??$$$ONcC}i-%cKsrYm6 z?t9+o!*u-GaVeDe6O&9WSgR;csEK==-3m$6ZNB`_u$>wyA3X4LE%E>WAi*V{fc4ycTj#}$57*%x@B?>)D)p_>w(5oBmR%1` z_GYvvDSGNmyeW6V84Kj+#k7J{TjH{CCW~5zS1VZFK_4o98 z$)xd|NLdS1d{6sJA_bM%LbH7JxaMZ7%F&dLS*WGA2;d4r38&s+bZ7x=Rl2VHg2p$r zMlv~OqWT$KOsOfBnv`1C`e~46kmyKUA4f7=-GJ+epxqETYzuGXI5AVQ(B8#>gHWj5 zoI-SAHmw~MmpgL!Zmtl9CzoukpN043w)`d&Ps;hE^WH+vuaF;n7nYDNdH9EF(7n!^ zPgFdd*EGlp4_XW)1iX)qWCj*u>`73B22Q-6IIvK8UN&_b=p2u_z(qh_9Vw ztf#skLCh>$1Q1!5G73Etba?!(6KVKF QHln&}=F?@5RMy$Q0gR196#xJL literal 0 HcmV?d00001 From 9cb9f97a1f1ed5e0f5d47082b4d01c2170e76915 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 20 Oct 2025 16:49:41 +0200 Subject: [PATCH 30/40] feat(signing): added multi arch build --- .goreleaser.yaml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 4731016..248c94f 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -10,11 +10,11 @@ builds: - CGO_ENABLED=0 goos: - linux - #- darwin - #- windows + - darwin + - windows goarch: - amd64 - #- arm64 + - arm64 archives: - formats: [tar.gz] @@ -43,9 +43,6 @@ signs: - "--detach-sign" - "${artifact}" -#binary_signs: -# - {} - changelog: abbrev: 10 filters: From 716c8e79e415f641ed123e89f61db1dffb184611 Mon Sep 17 00:00:00 2001 From: Martin McCaffery Date: Tue, 21 Oct 2025 11:40:35 +0200 Subject: [PATCH 31/40] fix(version): update imports and go.mod to allow v2 --- cmd/app.go | 4 ++-- cmd/apply.go | 6 +++--- cmd/delete.go | 6 +++--- cmd/instance.go | 4 ++-- go.mod | 2 +- internal/apply/v1/manager.go | 4 ++-- internal/apply/v1/manager_test.go | 4 ++-- internal/apply/v1/planner.go | 12 ++++++------ internal/apply/v1/planner_test.go | 4 ++-- internal/apply/v1/strategy.go | 2 +- internal/apply/v1/strategy_recreate.go | 4 ++-- internal/apply/v1/types.go | 4 ++-- internal/apply/v2/manager.go | 4 ++-- internal/apply/v2/manager_test.go | 4 ++-- internal/apply/v2/planner.go | 12 ++++++------ internal/apply/v2/planner_test.go | 4 ++-- internal/apply/v2/strategy.go | 2 +- internal/apply/v2/strategy_recreate.go | 4 ++-- internal/apply/v2/types.go | 4 ++-- internal/delete/v1/manager.go | 2 +- internal/delete/v1/planner.go | 4 ++-- internal/delete/v2/manager.go | 2 +- internal/delete/v2/manager_test.go | 2 +- internal/delete/v2/planner.go | 4 ++-- internal/delete/v2/planner_test.go | 4 ++-- main.go | 2 +- sdk/README.md | 2 +- sdk/edgeconnect/appinstance.go | 2 +- sdk/edgeconnect/apps.go | 2 +- sdk/edgeconnect/cloudlet.go | 2 +- sdk/edgeconnect/v2/appinstance.go | 2 +- sdk/edgeconnect/v2/apps.go | 2 +- sdk/edgeconnect/v2/cloudlet.go | 2 +- sdk/examples/comprehensive/main.go | 2 +- sdk/examples/deploy_app.go | 2 +- 35 files changed, 64 insertions(+), 64 deletions(-) diff --git a/cmd/app.go b/cmd/app.go index 79fc2c5..02125fc 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -10,8 +10,8 @@ import ( "strings" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" "github.com/spf13/cobra" "github.com/spf13/viper" ) diff --git a/cmd/apply.go b/cmd/apply.go index 1493841..e2affd0 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -10,9 +10,9 @@ import ( "path/filepath" "strings" - applyv1 "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/apply/v1" - applyv2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/apply/v2" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + applyv1 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/apply/v1" + applyv2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/apply/v2" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" "github.com/spf13/cobra" ) diff --git a/cmd/delete.go b/cmd/delete.go index 912741b..7124e61 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -10,9 +10,9 @@ import ( "path/filepath" "strings" - deletev1 "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/delete/v1" - deletev2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/delete/v2" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + deletev1 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/delete/v1" + deletev2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/delete/v2" "github.com/spf13/cobra" ) diff --git a/cmd/instance.go b/cmd/instance.go index 1eb6cb6..0b78986 100644 --- a/cmd/instance.go +++ b/cmd/instance.go @@ -5,8 +5,8 @@ import ( "fmt" "os" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" "github.com/spf13/cobra" ) diff --git a/go.mod b/go.mod index dd77621..e88a974 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module edp.buildth.ing/DevFW-CICD/edge-connect-client +module edp.buildth.ing/DevFW-CICD/edge-connect-client/v2 go 1.25.1 diff --git a/internal/apply/v1/manager.go b/internal/apply/v1/manager.go index a0668e8..048e85e 100644 --- a/internal/apply/v1/manager.go +++ b/internal/apply/v1/manager.go @@ -7,8 +7,8 @@ import ( "fmt" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect" ) // ResourceManagerInterface defines the interface for resource management diff --git a/internal/apply/v1/manager_test.go b/internal/apply/v1/manager_test.go index 9ed3cac..d4b4744 100644 --- a/internal/apply/v1/manager_test.go +++ b/internal/apply/v1/manager_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/apply/v1/planner.go b/internal/apply/v1/planner.go index 33b8d9c..001076c 100644 --- a/internal/apply/v1/planner.go +++ b/internal/apply/v1/planner.go @@ -11,8 +11,8 @@ import ( "strings" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect" ) // EdgeConnectClientInterface defines the methods needed for deployment planning @@ -135,9 +135,9 @@ func (p *EdgeConnectPlanner) planAppAction(ctx context.Context, config *config.E desired := &AppState{ Name: config.Metadata.Name, Version: config.Metadata.AppVersion, - Organization: config.Metadata.Organization, // Use first infra template for org - Region: config.Spec.InfraTemplate[0].Region, // Use first infra template for region - Exists: false, // Will be set based on current state + Organization: config.Metadata.Organization, // Use first infra template for org + Region: config.Spec.InfraTemplate[0].Region, // Use first infra template for region + Exists: false, // Will be set based on current state } if config.Spec.IsK8sApp() { @@ -392,7 +392,7 @@ func (p *EdgeConnectPlanner) compareAppStates(current, desired *AppState) ([]str // Compare outbound connections outboundChanges := p.compareOutboundConnections(current.OutboundConnections, desired.OutboundConnections) if len(outboundChanges) > 0 { - sb:= strings.Builder{} + sb := strings.Builder{} sb.WriteString("Outbound connections changed:\n") for _, change := range outboundChanges { sb.WriteString(change) diff --git a/internal/apply/v1/planner_test.go b/internal/apply/v1/planner_test.go index 8c1e48a..7761365 100644 --- a/internal/apply/v1/planner_test.go +++ b/internal/apply/v1/planner_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/apply/v1/strategy.go b/internal/apply/v1/strategy.go index 44f2471..db2f90f 100644 --- a/internal/apply/v1/strategy.go +++ b/internal/apply/v1/strategy.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" ) // DeploymentStrategy represents the type of deployment strategy diff --git a/internal/apply/v1/strategy_recreate.go b/internal/apply/v1/strategy_recreate.go index 1f6f121..b8cc736 100644 --- a/internal/apply/v1/strategy_recreate.go +++ b/internal/apply/v1/strategy_recreate.go @@ -10,8 +10,8 @@ import ( "sync" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect" ) // RecreateStrategy implements the recreate deployment strategy diff --git a/internal/apply/v1/types.go b/internal/apply/v1/types.go index 223fa74..4863716 100644 --- a/internal/apply/v1/types.go +++ b/internal/apply/v1/types.go @@ -7,8 +7,8 @@ import ( "strings" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect" ) // SecurityRule defines network access rules (alias to SDK type for consistency) diff --git a/internal/apply/v2/manager.go b/internal/apply/v2/manager.go index fc1b483..4866129 100644 --- a/internal/apply/v2/manager.go +++ b/internal/apply/v2/manager.go @@ -7,8 +7,8 @@ import ( "fmt" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" ) // ResourceManagerInterface defines the interface for resource management diff --git a/internal/apply/v2/manager_test.go b/internal/apply/v2/manager_test.go index 68c60fd..dd2fc55 100644 --- a/internal/apply/v2/manager_test.go +++ b/internal/apply/v2/manager_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/apply/v2/planner.go b/internal/apply/v2/planner.go index 52de1ee..52a5e18 100644 --- a/internal/apply/v2/planner.go +++ b/internal/apply/v2/planner.go @@ -11,8 +11,8 @@ import ( "strings" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" ) // EdgeConnectClientInterface defines the methods needed for deployment planning @@ -135,9 +135,9 @@ func (p *EdgeConnectPlanner) planAppAction(ctx context.Context, config *config.E desired := &AppState{ Name: config.Metadata.Name, Version: config.Metadata.AppVersion, - Organization: config.Metadata.Organization, // Use first infra template for org - Region: config.Spec.InfraTemplate[0].Region, // Use first infra template for region - Exists: false, // Will be set based on current state + Organization: config.Metadata.Organization, // Use first infra template for org + Region: config.Spec.InfraTemplate[0].Region, // Use first infra template for region + Exists: false, // Will be set based on current state } if config.Spec.IsK8sApp() { @@ -392,7 +392,7 @@ func (p *EdgeConnectPlanner) compareAppStates(current, desired *AppState) ([]str // Compare outbound connections outboundChanges := p.compareOutboundConnections(current.OutboundConnections, desired.OutboundConnections) if len(outboundChanges) > 0 { - sb:= strings.Builder{} + sb := strings.Builder{} sb.WriteString("Outbound connections changed:\n") for _, change := range outboundChanges { sb.WriteString(change) diff --git a/internal/apply/v2/planner_test.go b/internal/apply/v2/planner_test.go index fe56871..20d3dab 100644 --- a/internal/apply/v2/planner_test.go +++ b/internal/apply/v2/planner_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/apply/v2/strategy.go b/internal/apply/v2/strategy.go index 6a1661a..78e3df4 100644 --- a/internal/apply/v2/strategy.go +++ b/internal/apply/v2/strategy.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" ) // DeploymentStrategy represents the type of deployment strategy diff --git a/internal/apply/v2/strategy_recreate.go b/internal/apply/v2/strategy_recreate.go index 739a454..89c9c56 100644 --- a/internal/apply/v2/strategy_recreate.go +++ b/internal/apply/v2/strategy_recreate.go @@ -10,8 +10,8 @@ import ( "sync" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" ) // RecreateStrategy implements the recreate deployment strategy diff --git a/internal/apply/v2/types.go b/internal/apply/v2/types.go index 90b7956..ae52420 100644 --- a/internal/apply/v2/types.go +++ b/internal/apply/v2/types.go @@ -7,8 +7,8 @@ import ( "strings" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" ) // SecurityRule defines network access rules (alias to SDK type for consistency) diff --git a/internal/delete/v1/manager.go b/internal/delete/v1/manager.go index 470ac37..e20eba9 100644 --- a/internal/delete/v1/manager.go +++ b/internal/delete/v1/manager.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect" ) // ResourceManagerInterface defines the interface for resource management diff --git a/internal/delete/v1/planner.go b/internal/delete/v1/planner.go index d436057..10f41c5 100644 --- a/internal/delete/v1/planner.go +++ b/internal/delete/v1/planner.go @@ -7,8 +7,8 @@ import ( "fmt" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect" ) // EdgeConnectClientInterface defines the methods needed for deletion planning diff --git a/internal/delete/v2/manager.go b/internal/delete/v2/manager.go index a644f32..35518a2 100644 --- a/internal/delete/v2/manager.go +++ b/internal/delete/v2/manager.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" ) // ResourceManagerInterface defines the interface for resource management diff --git a/internal/delete/v2/manager_test.go b/internal/delete/v2/manager_test.go index fd098af..fa2b7c9 100644 --- a/internal/delete/v2/manager_test.go +++ b/internal/delete/v2/manager_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/delete/v2/planner.go b/internal/delete/v2/planner.go index e77cd9e..752fe3b 100644 --- a/internal/delete/v2/planner.go +++ b/internal/delete/v2/planner.go @@ -7,8 +7,8 @@ import ( "fmt" "time" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" ) // EdgeConnectClientInterface defines the methods needed for deletion planning diff --git a/internal/delete/v2/planner_test.go b/internal/delete/v2/planner_test.go index c37a318..2ec9eae 100644 --- a/internal/delete/v2/planner_test.go +++ b/internal/delete/v2/planner_test.go @@ -8,8 +8,8 @@ import ( "path/filepath" "testing" - "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/main.go b/main.go index 9bc902d..2d198e9 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,6 @@ package main -import "edp.buildth.ing/DevFW-CICD/edge-connect-client/cmd" +import "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/cmd" func main() { cmd.Execute() diff --git a/sdk/README.md b/sdk/README.md index 89dc673..be2374f 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -16,7 +16,7 @@ A comprehensive Go SDK for the EdgeXR Master Controller API, providing typed int ### Installation ```go -import v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" +import v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" ``` ### Authentication diff --git a/sdk/edgeconnect/appinstance.go b/sdk/edgeconnect/appinstance.go index a26f45c..f655c98 100644 --- a/sdk/edgeconnect/appinstance.go +++ b/sdk/edgeconnect/appinstance.go @@ -9,7 +9,7 @@ import ( "fmt" "net/http" - sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/internal/http" + sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/internal/http" ) // CreateAppInstance creates a new application instance in the specified region diff --git a/sdk/edgeconnect/apps.go b/sdk/edgeconnect/apps.go index 70f5dea..8973862 100644 --- a/sdk/edgeconnect/apps.go +++ b/sdk/edgeconnect/apps.go @@ -10,7 +10,7 @@ import ( "io" "net/http" - sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/internal/http" + sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/internal/http" ) var ( diff --git a/sdk/edgeconnect/cloudlet.go b/sdk/edgeconnect/cloudlet.go index e3f4b7d..0ed6e71 100644 --- a/sdk/edgeconnect/cloudlet.go +++ b/sdk/edgeconnect/cloudlet.go @@ -9,7 +9,7 @@ import ( "fmt" "net/http" - sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/internal/http" + sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/internal/http" ) // CreateCloudlet creates a new cloudlet in the specified region diff --git a/sdk/edgeconnect/v2/appinstance.go b/sdk/edgeconnect/v2/appinstance.go index 4fb7204..d38821e 100644 --- a/sdk/edgeconnect/v2/appinstance.go +++ b/sdk/edgeconnect/v2/appinstance.go @@ -11,7 +11,7 @@ import ( "io" "net/http" - sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/internal/http" + sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/internal/http" ) // CreateAppInstance creates a new application instance in the specified region diff --git a/sdk/edgeconnect/v2/apps.go b/sdk/edgeconnect/v2/apps.go index 06d529f..8f5410e 100644 --- a/sdk/edgeconnect/v2/apps.go +++ b/sdk/edgeconnect/v2/apps.go @@ -11,7 +11,7 @@ import ( "io" "net/http" - sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/internal/http" + sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/internal/http" ) var ( diff --git a/sdk/edgeconnect/v2/cloudlet.go b/sdk/edgeconnect/v2/cloudlet.go index 85ef522..415584a 100644 --- a/sdk/edgeconnect/v2/cloudlet.go +++ b/sdk/edgeconnect/v2/cloudlet.go @@ -9,7 +9,7 @@ import ( "fmt" "net/http" - sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/internal/http" + sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/internal/http" ) // CreateCloudlet creates a new cloudlet in the specified region diff --git a/sdk/examples/comprehensive/main.go b/sdk/examples/comprehensive/main.go index d3fb922..f932a75 100644 --- a/sdk/examples/comprehensive/main.go +++ b/sdk/examples/comprehensive/main.go @@ -12,7 +12,7 @@ import ( "strings" "time" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" ) func main() { diff --git a/sdk/examples/deploy_app.go b/sdk/examples/deploy_app.go index 84297dc..d35ff9c 100644 --- a/sdk/examples/deploy_app.go +++ b/sdk/examples/deploy_app.go @@ -12,7 +12,7 @@ import ( "strings" "time" - v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2" + v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/edgeconnect/v2" ) func main() { From 26ba07200e190e15b15ae438721ba8bb2b608de4 Mon Sep 17 00:00:00 2001 From: Stephan Lo Date: Tue, 21 Oct 2025 13:44:33 +0200 Subject: [PATCH 32/40] test(orca-forgjo-runner): added v2 example to deploy forgejo runner in orca --- ...tConfig.yaml => EdgeConnectConfig_v1.yaml} | 0 .../forgejo-runner/EdgeConnectConfig_v2.yaml | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+) rename sdk/examples/forgejo-runner/{EdgeConnectConfig.yaml => EdgeConnectConfig_v1.yaml} (100%) create mode 100644 sdk/examples/forgejo-runner/EdgeConnectConfig_v2.yaml diff --git a/sdk/examples/forgejo-runner/EdgeConnectConfig.yaml b/sdk/examples/forgejo-runner/EdgeConnectConfig_v1.yaml similarity index 100% rename from sdk/examples/forgejo-runner/EdgeConnectConfig.yaml rename to sdk/examples/forgejo-runner/EdgeConnectConfig_v1.yaml diff --git a/sdk/examples/forgejo-runner/EdgeConnectConfig_v2.yaml b/sdk/examples/forgejo-runner/EdgeConnectConfig_v2.yaml new file mode 100644 index 0000000..5afcf4b --- /dev/null +++ b/sdk/examples/forgejo-runner/EdgeConnectConfig_v2.yaml @@ -0,0 +1,29 @@ +# Is there a swagger file for the new EdgeConnect API? +# How does it differ from the EdgeXR API? +kind: edgeconnect-deployment +metadata: + name: "forgejo-runner-orca" # name could be used for appName + appVersion: "1" + organization: "edp2-orca" +spec: + # dockerApp: # Docker is OBSOLETE + # appVersion: "1.0.0" + # manifestFile: "./docker-compose.yaml" + # image: "https://registry-1.docker.io/library/nginx:latest" + k8sApp: + manifestFile: "./forgejo-runner-deployment.yaml" + infraTemplate: + - region: "US" + cloudletOrg: "TelekomOp" + cloudletName: "gardener-shepherd-test" + flavorName: "defualt" + network: + outboundConnections: + - protocol: "tcp" + portRangeMin: 80 + portRangeMax: 80 + remoteCIDR: "0.0.0.0/0" + - protocol: "tcp" + portRangeMin: 443 + portRangeMax: 443 + remoteCIDR: "0.0.0.0/0" From f3cbfa3723f68e1073e271bb23a658001d056367 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Wed, 22 Oct 2025 10:31:03 +0200 Subject: [PATCH 33/40] fix(deploy): Fixed glitch when updating an app inst with an invalid manifest --- internal/apply/v2/manager.go | 150 +++++++++++++++++- internal/apply/v2/manager_test.go | 106 +++++++++++++ internal/apply/v2/strategy_recreate.go | 91 +++++++++++ internal/apply/v2/types.go | 27 ++++ .../comprehensive/k8s-deployment.yaml | 1 + 5 files changed, 374 insertions(+), 1 deletion(-) diff --git a/internal/apply/v2/manager.go b/internal/apply/v2/manager.go index 4866129..f43e933 100644 --- a/internal/apply/v2/manager.go +++ b/internal/apply/v2/manager.go @@ -4,7 +4,9 @@ package v2 import ( "context" + "errors" "fmt" + "strings" "time" "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/internal/config" @@ -204,7 +206,8 @@ func (rm *EdgeConnectResourceManager) RollbackDeployment(ctx context.Context, re rollbackErrors := []error{} - // Rollback completed instances (in reverse order) + // Phase 1: Delete resources that were created in this deployment attempt (in reverse order) + rm.logf("Phase 1: Rolling back created resources") for i := len(result.CompletedActions) - 1; i >= 0; i-- { action := result.CompletedActions[i] @@ -218,6 +221,32 @@ func (rm *EdgeConnectResourceManager) RollbackDeployment(ctx context.Context, re } } + // Phase 2: Restore resources that were deleted before the failed deployment + // This is critical for RecreateStrategy which deletes everything before recreating + if result.DeletedAppBackup != nil || len(result.DeletedInstancesBackup) > 0 { + rm.logf("Phase 2: Restoring deleted resources") + + // Restore app first (must exist before instances can be created) + if result.DeletedAppBackup != nil { + if err := rm.restoreApp(ctx, result.DeletedAppBackup); err != nil { + rollbackErrors = append(rollbackErrors, fmt.Errorf("failed to restore app: %w", err)) + rm.logf("Failed to restore app: %v", err) + } else { + rm.logf("Successfully restored app: %s", result.DeletedAppBackup.App.Key.Name) + } + } + + // Restore instances + for _, backup := range result.DeletedInstancesBackup { + if err := rm.restoreInstance(ctx, &backup); err != nil { + rollbackErrors = append(rollbackErrors, fmt.Errorf("failed to restore instance %s: %w", backup.Instance.Key.Name, err)) + rm.logf("Failed to restore instance %s: %v", backup.Instance.Key.Name, err) + } else { + rm.logf("Successfully restored instance: %s", backup.Instance.Key.Name) + } + } + } + if len(rollbackErrors) > 0 { return fmt.Errorf("rollback encountered %d errors: %v", len(rollbackErrors), rollbackErrors) } @@ -278,6 +307,125 @@ func (rm *EdgeConnectResourceManager) rollbackInstance(ctx context.Context, acti return fmt.Errorf("instance action not found for rollback: %s", action.Target) } +// restoreApp recreates an app that was deleted during deployment +func (rm *EdgeConnectResourceManager) restoreApp(ctx context.Context, backup *AppBackup) error { + rm.logf("Restoring app: %s/%s version %s", + backup.App.Key.Organization, backup.App.Key.Name, backup.App.Key.Version) + + // Build a clean app input with only creation-safe fields + // We must exclude read-only fields like CreatedAt, UpdatedAt, etc. + 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, + // Explicitly omit read-only fields like CreatedAt, UpdatedAt, Fields, etc. + }, + } + + if err := rm.client.CreateApp(ctx, appInput); err != nil { + return fmt.Errorf("failed to restore app: %w", err) + } + + rm.logf("Successfully restored app: %s", backup.App.Key.Name) + return nil +} + +// restoreInstance recreates an instance that was deleted during deployment +func (rm *EdgeConnectResourceManager) restoreInstance(ctx context.Context, backup *InstanceBackup) error { + rm.logf("Restoring instance: %s on %s:%s", + backup.Instance.Key.Name, + backup.Instance.Key.CloudletKey.Organization, + backup.Instance.Key.CloudletKey.Name) + + // Build a clean instance input with only creation-safe fields + // We must exclude read-only fields like CloudletLoc, CreatedAt, etc. + instanceInput := &v2.NewAppInstanceInput{ + Region: backup.Region, + AppInst: v2.AppInstance{ + Key: backup.Instance.Key, + AppKey: backup.Instance.AppKey, + Flavor: backup.Instance.Flavor, + // Explicitly omit read-only fields like CloudletLoc, State, PowerState, CreatedAt, etc. + }, + } + + // Retry logic to handle namespace termination race conditions + maxRetries := 5 + retryDelay := 10 * time.Second + + var lastErr error + for attempt := 0; attempt <= maxRetries; attempt++ { + if attempt > 0 { + rm.logf("Retrying instance restore %s (attempt %d/%d)", backup.Instance.Key.Name, attempt, maxRetries) + select { + case <-time.After(retryDelay): + case <-ctx.Done(): + return ctx.Err() + } + } + + err := rm.client.CreateAppInstance(ctx, instanceInput) + if err == nil { + rm.logf("Successfully restored instance: %s", backup.Instance.Key.Name) + return nil + } + + lastErr = err + + // Check if error is retryable + if !rm.isRetryableError(err) { + rm.logf("Failed to restore instance %s: %v (non-retryable error, giving up)", backup.Instance.Key.Name, err) + return fmt.Errorf("failed to restore instance: %w", err) + } + + if attempt < maxRetries { + rm.logf("Failed to restore instance %s: %v (will retry)", backup.Instance.Key.Name, err) + } + } + + return fmt.Errorf("failed to restore instance after %d attempts: %w", maxRetries+1, lastErr) +} + +// isRetryableError determines if an error should be retried +func (rm *EdgeConnectResourceManager) isRetryableError(err error) bool { + if err == nil { + return false + } + + errStr := strings.ToLower(err.Error()) + + // Special case: Kubernetes namespace termination race condition + // This is a transient 400 error that should be retried + if strings.Contains(errStr, "being terminated") || strings.Contains(errStr, "is being terminated") { + return true + } + + // Check if it's an APIError with a status code + var apiErr *v2.APIError + if errors.As(err, &apiErr) { + // Don't retry client errors (4xx) + if apiErr.StatusCode >= 400 && apiErr.StatusCode < 500 { + return false + } + // Retry server errors (5xx) + if apiErr.StatusCode >= 500 { + return true + } + } + + // Retry all other errors (network issues, timeouts, etc.) + return true +} + // logf logs a message if a logger is configured func (rm *EdgeConnectResourceManager) logf(format string, v ...interface{}) { if rm.logger != nil { diff --git a/internal/apply/v2/manager_test.go b/internal/apply/v2/manager_test.go index dd2fc55..6d5ef18 100644 --- a/internal/apply/v2/manager_test.go +++ b/internal/apply/v2/manager_test.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "testing" "time" @@ -464,6 +465,111 @@ func TestRollbackDeploymentFailure(t *testing.T) { mockClient.AssertExpectations(t) } +func TestRollbackDeploymentWithRestore(t *testing.T) { + mockClient := &MockResourceClient{} + logger := &TestLogger{} + manager := NewResourceManager(mockClient, WithLogger(logger), WithStrategyConfig(createTestStrategyConfig())) + + plan := createTestDeploymentPlan() + + // Simulate a RecreateStrategy scenario: + // 1. Old app and instance were deleted and backed up + // 2. New app was created successfully + // 3. New instance creation failed + // 4. Rollback should: delete new app, restore old app, restore old instance + oldApp := v2.App{ + Key: v2.AppKey{ + Organization: "test-org", + Name: "test-app", + Version: "1.0.0", + }, + Deployment: "kubernetes", + DeploymentManifest: "old-manifest-content", + } + + oldInstance := v2.AppInstance{ + Key: v2.AppInstanceKey{ + Organization: "test-org", + Name: "test-app-1.0.0-instance", + CloudletKey: v2.CloudletKey{ + Organization: "test-cloudlet-org", + Name: "test-cloudlet", + }, + }, + AppKey: v2.AppKey{ + Organization: "test-org", + Name: "test-app", + Version: "1.0.0", + }, + Flavor: v2.Flavor{Name: "small"}, + } + + result := &ExecutionResult{ + Plan: plan, + // Completed actions: new app was created before failure + CompletedActions: []ActionResult{ + { + Type: ActionCreate, + Target: "test-app", + Success: true, + }, + }, + // Failed action: new instance creation failed + FailedActions: []ActionResult{ + { + Type: ActionCreate, + Target: "test-app-1.0.0-instance", + Success: false, + }, + }, + // Backup of deleted resources + DeletedAppBackup: &AppBackup{ + App: oldApp, + Region: "US", + ManifestContent: "old-manifest-content", + }, + DeletedInstancesBackup: []InstanceBackup{ + { + Instance: oldInstance, + Region: "US", + }, + }, + } + + // Mock rollback operations in order: + // 1. Delete newly created app (rollback create) + mockClient.On("DeleteApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US"). + Return(nil).Once() + + // 2. Restore old app (from backup) + mockClient.On("CreateApp", mock.Anything, mock.MatchedBy(func(input *v2.NewAppInput) bool { + return input.App.Key.Name == "test-app" && input.App.DeploymentManifest == "old-manifest-content" + })).Return(nil).Once() + + // 3. Restore old instance (from backup) + mockClient.On("CreateAppInstance", mock.Anything, mock.MatchedBy(func(input *v2.NewAppInstanceInput) bool { + return input.AppInst.Key.Name == "test-app-1.0.0-instance" + })).Return(nil).Once() + + ctx := context.Background() + err := manager.RollbackDeployment(ctx, result) + + require.NoError(t, err) + mockClient.AssertExpectations(t) + + // Verify rollback was logged + assert.Greater(t, len(logger.messages), 0) + // Should have messages about rolling back created resources and restoring deleted resources + hasRestoreLog := false + for _, msg := range logger.messages { + if strings.Contains(msg, "Restoring deleted resources") { + hasRestoreLog = true + break + } + } + assert.True(t, hasRestoreLog, "Should log restoration of deleted resources") +} + func TestConvertNetworkRules(t *testing.T) { network := &config.NetworkConfig{ OutboundConnections: []config.OutboundConnection{ diff --git a/internal/apply/v2/strategy_recreate.go b/internal/apply/v2/strategy_recreate.go index 89c9c56..4d81029 100644 --- a/internal/apply/v2/strategy_recreate.go +++ b/internal/apply/v2/strategy_recreate.go @@ -159,6 +159,19 @@ func (r *RecreateStrategy) deleteInstancesPhase(ctx context.Context, plan *Deplo return nil } + // Backup instances before deleting them (for rollback restoration) + r.logf("Backing up %d existing instances before deletion", len(instancesToDelete)) + for _, action := range instancesToDelete { + backup, err := r.backupInstance(ctx, action, config) + if err != nil { + r.logf("Warning: failed to backup instance %s before deletion: %v", action.InstanceName, err) + // Continue with deletion even if backup fails - this is best effort + } else { + result.DeletedInstancesBackup = append(result.DeletedInstancesBackup, *backup) + r.logf("Backed up instance: %s", action.InstanceName) + } + } + deleteResults := r.executeInstanceActionsWithRetry(ctx, instancesToDelete, "delete", config) for _, deleteResult := range deleteResults { @@ -172,6 +185,19 @@ func (r *RecreateStrategy) deleteInstancesPhase(ctx context.Context, plan *Deplo } r.logf("Phase 1 complete: deleted %d instances", len(deleteResults)) + + // Wait for Kubernetes namespace termination to complete + // This prevents "namespace is being terminated" errors when recreating instances + if len(deleteResults) > 0 { + waitTime := 5 * time.Second + r.logf("Waiting %v for namespace termination to complete...", waitTime) + select { + case <-time.After(waitTime): + case <-ctx.Done(): + return ctx.Err() + } + } + return nil } @@ -184,6 +210,17 @@ func (r *RecreateStrategy) deleteAppPhase(ctx context.Context, plan *DeploymentP r.logf("Phase 2: Deleting existing application") + // Backup app before deleting it (for rollback restoration) + r.logf("Backing up existing app before deletion") + backup, err := r.backupApp(ctx, plan, config) + if err != nil { + r.logf("Warning: failed to backup app before deletion: %v", err) + // Continue with deletion even if backup fails - this is best effort + } else { + result.DeletedAppBackup = backup + r.logf("Backed up app: %s", plan.AppAction.Desired.Name) + } + appKey := v2.AppKey{ Organization: plan.AppAction.Desired.Organization, Name: plan.AppAction.Desired.Name, @@ -516,6 +553,52 @@ func (r *RecreateStrategy) updateApplication(ctx context.Context, action AppActi return true, nil } +// backupApp fetches and stores the current app state before deletion +func (r *RecreateStrategy) backupApp(ctx context.Context, plan *DeploymentPlan, config *config.EdgeConnectConfig) (*AppBackup, error) { + appKey := v2.AppKey{ + Organization: plan.AppAction.Desired.Organization, + Name: plan.AppAction.Desired.Name, + Version: plan.AppAction.Desired.Version, + } + + app, err := r.client.ShowApp(ctx, appKey, plan.AppAction.Desired.Region) + if err != nil { + return nil, fmt.Errorf("failed to fetch app for backup: %w", err) + } + + backup := &AppBackup{ + App: app, + Region: plan.AppAction.Desired.Region, + ManifestContent: app.DeploymentManifest, + } + + return backup, nil +} + +// backupInstance fetches and stores the current instance state before deletion +func (r *RecreateStrategy) backupInstance(ctx context.Context, action InstanceAction, config *config.EdgeConnectConfig) (*InstanceBackup, error) { + instanceKey := v2.AppInstanceKey{ + Organization: action.Desired.Organization, + Name: action.InstanceName, + CloudletKey: v2.CloudletKey{ + Organization: action.Target.CloudletOrg, + Name: action.Target.CloudletName, + }, + } + + instance, err := r.client.ShowAppInstance(ctx, instanceKey, action.Target.Region) + if err != nil { + return nil, fmt.Errorf("failed to fetch instance for backup: %w", err) + } + + backup := &InstanceBackup{ + Instance: instance, + Region: action.Target.Region, + } + + return backup, nil +} + // logf logs a message if a logger is configured func (r *RecreateStrategy) logf(format string, v ...interface{}) { if r.logger != nil { @@ -530,6 +613,14 @@ func isRetryableError(err error) bool { return false } + errStr := strings.ToLower(err.Error()) + + // Special case: Kubernetes namespace termination race condition + // This is a transient 400 error that should be retried + if strings.Contains(errStr, "being terminated") || strings.Contains(errStr, "is being terminated") { + return true + } + // Check if it's an APIError with a status code var apiErr *v2.APIError if errors.As(err, &apiErr) { diff --git a/internal/apply/v2/types.go b/internal/apply/v2/types.go index ae52420..26d998e 100644 --- a/internal/apply/v2/types.go +++ b/internal/apply/v2/types.go @@ -271,6 +271,12 @@ type ExecutionResult struct { // RollbackSuccess indicates if rollback was successful RollbackSuccess bool + + // DeletedAppBackup stores the app that was deleted (for rollback restoration) + DeletedAppBackup *AppBackup + + // DeletedInstancesBackup stores instances that were deleted (for rollback restoration) + DeletedInstancesBackup []InstanceBackup } // ActionResult represents the result of executing a single action @@ -294,6 +300,27 @@ type ActionResult struct { Details string } +// AppBackup stores a deleted app's complete state for rollback restoration +type AppBackup struct { + // App is the full app object that was deleted + App v2.App + + // Region where the app was deployed + Region string + + // ManifestContent is the deployment manifest content + ManifestContent string +} + +// InstanceBackup stores a deleted instance's complete state for rollback restoration +type InstanceBackup struct { + // Instance is the full instance object that was deleted + Instance v2.AppInstance + + // Region where the instance was deployed + Region string +} + // IsEmpty returns true if the deployment plan has no actions to perform func (dp *DeploymentPlan) IsEmpty() bool { if dp.AppAction.Type != ActionNone { diff --git a/sdk/examples/comprehensive/k8s-deployment.yaml b/sdk/examples/comprehensive/k8s-deployment.yaml index 2a0a741..dff3649 100644 --- a/sdk/examples/comprehensive/k8s-deployment.yaml +++ b/sdk/examples/comprehensive/k8s-deployment.yaml @@ -18,6 +18,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: edgeconnect-coder-deployment + #namespace: gitea spec: replicas: 1 selector: From 9772a072e8b223af95448c008904b8eb6479f2b1 Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Wed, 22 Oct 2025 12:47:15 +0200 Subject: [PATCH 34/40] chore(linting): Fixed all linter errors --- cmd/app.go | 14 +++++++---- cmd/apply.go | 22 ++++-------------- cmd/delete.go | 8 ++++--- cmd/instance.go | 28 ++++++++++++++++------ cmd/root.go | 32 +++++++++++++++++++------- internal/apply/v1/planner.go | 21 ++++++++--------- internal/apply/v2/planner.go | 21 ++++++++--------- sdk/edgeconnect/appinstance.go | 24 ++++++++++++++----- sdk/edgeconnect/appinstance_test.go | 8 +++---- sdk/edgeconnect/apps.go | 24 ++++++++++++++----- sdk/edgeconnect/apps_test.go | 18 ++++----------- sdk/edgeconnect/auth.go | 4 +++- sdk/edgeconnect/auth_test.go | 12 +++++----- sdk/edgeconnect/cloudlet.go | 24 ++++++++++++++----- sdk/edgeconnect/cloudlet_test.go | 10 ++++---- sdk/edgeconnect/v2/appinstance.go | 24 ++++++++++++++----- sdk/edgeconnect/v2/appinstance_test.go | 8 +++---- sdk/edgeconnect/v2/apps.go | 24 ++++++++++++++----- sdk/edgeconnect/v2/apps_test.go | 18 ++++----------- sdk/edgeconnect/v2/auth.go | 4 +++- sdk/edgeconnect/v2/auth_test.go | 12 +++++----- sdk/edgeconnect/v2/cloudlet.go | 24 ++++++++++++++----- sdk/edgeconnect/v2/cloudlet_test.go | 10 ++++---- sdk/internal/http/transport.go | 4 +++- 24 files changed, 240 insertions(+), 158 deletions(-) diff --git a/cmd/app.go b/cmd/app.go index 02125fc..37218bf 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -37,7 +37,7 @@ func validateBaseURL(baseURL string) error { return fmt.Errorf("user and or password should not be set") } - if !(url.Path == "" || url.Path == "/") { + if url.Path != "" && url.Path != "/" { return fmt.Errorf("should not contain any path '%s'", url.Path) } @@ -291,12 +291,18 @@ func init() { cmd.Flags().StringVarP(&appName, "name", "n", "", "application name") cmd.Flags().StringVarP(&appVersion, "version", "v", "", "application version") cmd.Flags().StringVarP(®ion, "region", "r", "", "region (required)") - cmd.MarkFlagRequired("org") - cmd.MarkFlagRequired("region") + if err := cmd.MarkFlagRequired("org"); err != nil { + panic(err) + } + if err := cmd.MarkFlagRequired("region"); err != nil { + panic(err) + } } // Add required name flag for specific commands for _, cmd := range []*cobra.Command{createAppCmd, showAppCmd, deleteAppCmd} { - cmd.MarkFlagRequired("name") + if err := cmd.MarkFlagRequired("name"); err != nil { + panic(err) + } } } diff --git a/cmd/apply.go b/cmd/apply.go index e2affd0..cf2b37f 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -31,7 +31,7 @@ the necessary changes to deploy your applications across multiple cloudlets.`, Run: func(cmd *cobra.Command, args []string) { if configFile == "" { fmt.Fprintf(os.Stderr, "Error: configuration file is required\n") - cmd.Usage() + _ = cmd.Usage() os.Exit(1) } @@ -208,20 +208,6 @@ func runApplyV2(cfg *config.EdgeConnectConfig, manifestContent string, isDryRun return displayDeploymentResults(deployResult) } -type deploymentResult interface { - IsSuccess() bool - GetDuration() string - GetCompletedActions() []actionResult - GetFailedActions() []actionResult - GetError() error -} - -type actionResult interface { - GetType() string - GetTarget() string - GetError() error -} - func displayDeploymentResults(result interface{}) error { // Use reflection or type assertion to handle both v1 and v2 result types // For now, we'll use a simple approach that works with both @@ -288,7 +274,7 @@ func displayDeploymentResultsV2(deployResult *applyv2.ExecutionResult) error { func confirmDeployment() bool { fmt.Print("Do you want to proceed? (yes/no): ") var response string - fmt.Scanln(&response) + _, _ = fmt.Scanln(&response) switch response { case "yes", "y", "YES", "Y": @@ -305,5 +291,7 @@ func init() { applyCmd.Flags().BoolVar(&dryRun, "dry-run", false, "preview changes without applying them") applyCmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "automatically approve the deployment plan") - applyCmd.MarkFlagRequired("file") + if err := applyCmd.MarkFlagRequired("file"); err != nil { + panic(err) + } } diff --git a/cmd/delete.go b/cmd/delete.go index 7124e61..dcc1614 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -31,7 +31,7 @@ Instances are always deleted before the application.`, Run: func(cmd *cobra.Command, args []string) { if deleteConfigFile == "" { fmt.Fprintf(os.Stderr, "Error: configuration file is required\n") - cmd.Usage() + _ = cmd.Usage() os.Exit(1) } @@ -273,7 +273,7 @@ func displayDeletionResultsV2(deleteResult *deletev2.DeletionResult) error { func confirmDeletion() bool { fmt.Print("Do you want to proceed with deletion? (yes/no): ") var response string - fmt.Scanln(&response) + _, _ = fmt.Scanln(&response) switch response { case "yes", "y", "YES", "Y": @@ -290,5 +290,7 @@ func init() { deleteCmd.Flags().BoolVar(&deleteDryRun, "dry-run", false, "preview deletion without actually deleting resources") deleteCmd.Flags().BoolVar(&deleteAutoApprove, "auto-approve", false, "automatically approve the deletion plan") - deleteCmd.MarkFlagRequired("file") + if err := deleteCmd.MarkFlagRequired("file"); err != nil { + panic(err) + } } diff --git a/cmd/instance.go b/cmd/instance.go index 0b78986..75868ce 100644 --- a/cmd/instance.go +++ b/cmd/instance.go @@ -230,17 +230,31 @@ func init() { cmd.Flags().StringVarP(&cloudletOrg, "cloudlet-org", "", "", "cloudlet organization (required)") cmd.Flags().StringVarP(®ion, "region", "r", "", "region (required)") - cmd.MarkFlagRequired("org") - cmd.MarkFlagRequired("name") - cmd.MarkFlagRequired("cloudlet") - cmd.MarkFlagRequired("cloudlet-org") - cmd.MarkFlagRequired("region") + if err := cmd.MarkFlagRequired("org"); err != nil { + panic(err) + } + if err := cmd.MarkFlagRequired("name"); err != nil { + panic(err) + } + if err := cmd.MarkFlagRequired("cloudlet"); err != nil { + panic(err) + } + if err := cmd.MarkFlagRequired("cloudlet-org"); err != nil { + panic(err) + } + if err := cmd.MarkFlagRequired("region"); err != nil { + panic(err) + } } // Add additional flags for create command createInstanceCmd.Flags().StringVarP(&appName, "app", "a", "", "application name (required)") createInstanceCmd.Flags().StringVarP(&appVersion, "version", "v", "", "application version") createInstanceCmd.Flags().StringVarP(&flavorName, "flavor", "f", "", "flavor name (required)") - createInstanceCmd.MarkFlagRequired("app") - createInstanceCmd.MarkFlagRequired("flavor") + if err := createInstanceCmd.MarkFlagRequired("app"); err != nil { + panic(err) + } + if err := createInstanceCmd.MarkFlagRequired("flavor"); err != nil { + panic(err) + } } diff --git a/cmd/root.go b/cmd/root.go index dd22f72..52ae3ca 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -44,19 +44,35 @@ func init() { rootCmd.PersistentFlags().StringVar(&apiVersion, "api-version", "v2", "API version to use (v1 or v2)") rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "enable debug logging") - viper.BindPFlag("base_url", rootCmd.PersistentFlags().Lookup("base-url")) - viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username")) - viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password")) - viper.BindPFlag("api_version", rootCmd.PersistentFlags().Lookup("api-version")) + if err := viper.BindPFlag("base_url", rootCmd.PersistentFlags().Lookup("base-url")); err != nil { + panic(err) + } + if err := viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username")); err != nil { + panic(err) + } + if err := viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password")); err != nil { + panic(err) + } + if err := viper.BindPFlag("api_version", rootCmd.PersistentFlags().Lookup("api-version")); err != nil { + panic(err) + } } func initConfig() { viper.AutomaticEnv() viper.SetEnvPrefix("EDGE_CONNECT") - viper.BindEnv("base_url", "EDGE_CONNECT_BASE_URL") - viper.BindEnv("username", "EDGE_CONNECT_USERNAME") - viper.BindEnv("password", "EDGE_CONNECT_PASSWORD") - viper.BindEnv("api_version", "EDGE_CONNECT_API_VERSION") + if err := viper.BindEnv("base_url", "EDGE_CONNECT_BASE_URL"); err != nil { + panic(err) + } + if err := viper.BindEnv("username", "EDGE_CONNECT_USERNAME"); err != nil { + panic(err) + } + if err := viper.BindEnv("password", "EDGE_CONNECT_PASSWORD"); err != nil { + panic(err) + } + if err := viper.BindEnv("api_version", "EDGE_CONNECT_API_VERSION"); err != nil { + panic(err) + } if cfgFile != "" { viper.SetConfigFile(cfgFile) diff --git a/internal/apply/v1/planner.go b/internal/apply/v1/planner.go index 001076c..bcfd043 100644 --- a/internal/apply/v1/planner.go +++ b/internal/apply/v1/planner.go @@ -323,12 +323,7 @@ func (p *EdgeConnectPlanner) getCurrentAppState(ctx context.Context, desired *Ap // Extract outbound connections from the app current.OutboundConnections = make([]SecurityRule, len(app.RequiredOutboundConnections)) for i, conn := range app.RequiredOutboundConnections { - current.OutboundConnections[i] = SecurityRule{ - Protocol: conn.Protocol, - PortRangeMin: conn.PortRangeMin, - PortRangeMax: conn.PortRangeMax, - RemoteCIDR: conn.RemoteCIDR, - } + current.OutboundConnections[i] = SecurityRule(conn) } return current, nil @@ -470,7 +465,9 @@ func (p *EdgeConnectPlanner) calculateManifestHash(manifestPath string) (string, if err != nil { return "", fmt.Errorf("failed to open manifest file: %w", err) } - defer file.Close() + defer func() { + _ = file.Close() + }() hasher := sha256.New() if _, err := io.Copy(hasher, file); err != nil { @@ -505,18 +502,20 @@ func (p *EdgeConnectPlanner) estimateDeploymentDuration(plan *DeploymentPlan) ti var duration time.Duration // App operations - if plan.AppAction.Type == ActionCreate { + switch plan.AppAction.Type { + case ActionCreate: duration += 30 * time.Second - } else if plan.AppAction.Type == ActionUpdate { + case ActionUpdate: duration += 15 * time.Second } // Instance operations (can be done in parallel) instanceDuration := time.Duration(0) for _, action := range plan.InstanceActions { - if action.Type == ActionCreate { + switch action.Type { + case ActionCreate: instanceDuration = max(instanceDuration, 2*time.Minute) - } else if action.Type == ActionUpdate { + case ActionUpdate: instanceDuration = max(instanceDuration, 1*time.Minute) } } diff --git a/internal/apply/v2/planner.go b/internal/apply/v2/planner.go index 52a5e18..61f15cd 100644 --- a/internal/apply/v2/planner.go +++ b/internal/apply/v2/planner.go @@ -323,12 +323,7 @@ func (p *EdgeConnectPlanner) getCurrentAppState(ctx context.Context, desired *Ap // Extract outbound connections from the app current.OutboundConnections = make([]SecurityRule, len(app.RequiredOutboundConnections)) for i, conn := range app.RequiredOutboundConnections { - current.OutboundConnections[i] = SecurityRule{ - Protocol: conn.Protocol, - PortRangeMin: conn.PortRangeMin, - PortRangeMax: conn.PortRangeMax, - RemoteCIDR: conn.RemoteCIDR, - } + current.OutboundConnections[i] = SecurityRule(conn) } return current, nil @@ -470,7 +465,9 @@ func (p *EdgeConnectPlanner) calculateManifestHash(manifestPath string) (string, if err != nil { return "", fmt.Errorf("failed to open manifest file: %w", err) } - defer file.Close() + defer func() { + _ = file.Close() + }() hasher := sha256.New() if _, err := io.Copy(hasher, file); err != nil { @@ -505,18 +502,20 @@ func (p *EdgeConnectPlanner) estimateDeploymentDuration(plan *DeploymentPlan) ti var duration time.Duration // App operations - if plan.AppAction.Type == ActionCreate { + switch plan.AppAction.Type { + case ActionCreate: duration += 30 * time.Second - } else if plan.AppAction.Type == ActionUpdate { + case ActionUpdate: duration += 15 * time.Second } // Instance operations (can be done in parallel) instanceDuration := time.Duration(0) for _, action := range plan.InstanceActions { - if action.Type == ActionCreate { + switch action.Type { + case ActionCreate: instanceDuration = max(instanceDuration, 2*time.Minute) - } else if action.Type == ActionUpdate { + case ActionUpdate: instanceDuration = max(instanceDuration, 1*time.Minute) } } diff --git a/sdk/edgeconnect/appinstance.go b/sdk/edgeconnect/appinstance.go index f655c98..4a1bda9 100644 --- a/sdk/edgeconnect/appinstance.go +++ b/sdk/edgeconnect/appinstance.go @@ -23,7 +23,9 @@ func (c *Client) CreateAppInstance(ctx context.Context, input *NewAppInstanceInp if err != nil { return fmt.Errorf("CreateAppInstance failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "CreateAppInstance") @@ -56,7 +58,9 @@ func (c *Client) ShowAppInstance(ctx context.Context, appInstKey AppInstanceKey, if err != nil { return AppInstance{}, fmt.Errorf("ShowAppInstance failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode == http.StatusNotFound { return AppInstance{}, fmt.Errorf("app instance %s/%s: %w", @@ -96,7 +100,9 @@ func (c *Client) ShowAppInstances(ctx context.Context, appInstKey AppInstanceKey if err != nil { return nil, fmt.Errorf("ShowAppInstances failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { return nil, c.handleErrorResponse(resp, "ShowAppInstances") @@ -125,7 +131,9 @@ func (c *Client) UpdateAppInstance(ctx context.Context, input *UpdateAppInstance if err != nil { return fmt.Errorf("UpdateAppInstance failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "UpdateAppInstance") @@ -152,7 +160,9 @@ func (c *Client) RefreshAppInstance(ctx context.Context, appInstKey AppInstanceK if err != nil { return fmt.Errorf("RefreshAppInstance failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "RefreshAppInstance") @@ -179,7 +189,9 @@ func (c *Client) DeleteAppInstance(ctx context.Context, appInstKey AppInstanceKe if err != nil { return fmt.Errorf("DeleteAppInstance failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() // 404 is acceptable for delete operations (already deleted) if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { diff --git a/sdk/edgeconnect/appinstance_test.go b/sdk/edgeconnect/appinstance_test.go index ac9c1eb..003f024 100644 --- a/sdk/edgeconnect/appinstance_test.go +++ b/sdk/edgeconnect/appinstance_test.go @@ -126,7 +126,7 @@ func TestCreateAppInstance(t *testing.T) { assert.Equal(t, "application/json", r.Header.Get("Content-Type")) w.WriteHeader(tt.mockStatusCode) - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) })) defer server.Close() @@ -207,7 +207,7 @@ func TestShowAppInstance(t *testing.T) { w.WriteHeader(tt.mockStatusCode) if tt.mockResponse != "" { - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) } })) defer server.Close() @@ -254,7 +254,7 @@ func TestShowAppInstances(t *testing.T) { {"data": {"key": {"organization": "testorg", "name": "inst2"}, "state": "Creating"}} ` w.WriteHeader(200) - w.Write([]byte(response)) + _, _ = w.Write([]byte(response)) })) defer server.Close() @@ -361,7 +361,7 @@ func TestUpdateAppInstance(t *testing.T) { assert.Equal(t, tt.input.AppInst.Key.Organization, input.AppInst.Key.Organization) w.WriteHeader(tt.mockStatusCode) - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) })) defer server.Close() diff --git a/sdk/edgeconnect/apps.go b/sdk/edgeconnect/apps.go index 8973862..f197a68 100644 --- a/sdk/edgeconnect/apps.go +++ b/sdk/edgeconnect/apps.go @@ -28,7 +28,9 @@ func (c *Client) CreateApp(ctx context.Context, input *NewAppInput) error { if err != nil { return fmt.Errorf("CreateApp failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "CreateApp") @@ -55,7 +57,9 @@ func (c *Client) ShowApp(ctx context.Context, appKey AppKey, region string) (App if err != nil { return App{}, fmt.Errorf("ShowApp failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode == http.StatusNotFound { return App{}, fmt.Errorf("app %s/%s version %s in region %s: %w", @@ -95,7 +99,9 @@ func (c *Client) ShowApps(ctx context.Context, appKey AppKey, region string) ([] if err != nil { return nil, fmt.Errorf("ShowApps failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { return nil, c.handleErrorResponse(resp, "ShowApps") @@ -124,7 +130,9 @@ func (c *Client) UpdateApp(ctx context.Context, input *UpdateAppInput) error { if err != nil { return fmt.Errorf("UpdateApp failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "UpdateApp") @@ -151,7 +159,9 @@ func (c *Client) DeleteApp(ctx context.Context, appKey AppKey, region string) er if err != nil { return fmt.Errorf("DeleteApp failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() // 404 is acceptable for delete operations (already deleted) if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { @@ -238,7 +248,9 @@ func (c *Client) handleErrorResponse(resp *http.Response, operation string) erro bodyBytes := []byte{} if resp.Body != nil { - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() bodyBytes, _ = io.ReadAll(resp.Body) messages = append(messages, string(bodyBytes)) } diff --git a/sdk/edgeconnect/apps_test.go b/sdk/edgeconnect/apps_test.go index 30531f6..88437ca 100644 --- a/sdk/edgeconnect/apps_test.go +++ b/sdk/edgeconnect/apps_test.go @@ -67,7 +67,7 @@ func TestCreateApp(t *testing.T) { assert.Equal(t, "application/json", r.Header.Get("Content-Type")) w.WriteHeader(tt.mockStatusCode) - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) })) defer server.Close() @@ -139,7 +139,7 @@ func TestShowApp(t *testing.T) { w.WriteHeader(tt.mockStatusCode) if tt.mockResponse != "" { - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) } })) defer server.Close() @@ -186,7 +186,7 @@ func TestShowApps(t *testing.T) { {"data": {"key": {"organization": "testorg", "name": "app2", "version": "1.0.0"}, "deployment": "docker"}} ` w.WriteHeader(200) - w.Write([]byte(response)) + _, _ = w.Write([]byte(response)) })) defer server.Close() @@ -277,7 +277,7 @@ func TestUpdateApp(t *testing.T) { assert.Equal(t, tt.input.App.Key.Organization, input.App.Key.Organization) w.WriteHeader(tt.mockStatusCode) - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) })) defer server.Close() @@ -407,13 +407,3 @@ func TestAPIError(t *testing.T) { assert.Equal(t, 400, err.StatusCode) assert.Len(t, err.Messages, 2) } - -// Helper function to create a test server that handles streaming JSON responses -func createStreamingJSONServer(responses []string, statusCode int) *httptest.Server { - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(statusCode) - for _, response := range responses { - w.Write([]byte(response + "\n")) - } - })) -} diff --git a/sdk/edgeconnect/auth.go b/sdk/edgeconnect/auth.go index eab24b9..cf6067b 100644 --- a/sdk/edgeconnect/auth.go +++ b/sdk/edgeconnect/auth.go @@ -138,7 +138,9 @@ func (u *UsernamePasswordProvider) retrieveToken(ctx context.Context) (string, e if err != nil { return "", err } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() // Read response body - same as existing implementation body, err := io.ReadAll(resp.Body) diff --git a/sdk/edgeconnect/auth_test.go b/sdk/edgeconnect/auth_test.go index 8ea3176..8e68dc4 100644 --- a/sdk/edgeconnect/auth_test.go +++ b/sdk/edgeconnect/auth_test.go @@ -56,7 +56,7 @@ func TestUsernamePasswordProvider_Success(t *testing.T) { // Return token response := map[string]string{"token": "dynamic-token-456"} w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) })) defer loginServer.Close() @@ -75,7 +75,7 @@ func TestUsernamePasswordProvider_LoginFailure(t *testing.T) { // Mock login server that returns error loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte("Invalid credentials")) + _, _ = w.Write([]byte("Invalid credentials")) })) defer loginServer.Close() @@ -99,7 +99,7 @@ func TestUsernamePasswordProvider_TokenCaching(t *testing.T) { callCount++ response := map[string]string{"token": "cached-token-789"} w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) })) defer loginServer.Close() @@ -128,7 +128,7 @@ func TestUsernamePasswordProvider_TokenExpiry(t *testing.T) { callCount++ response := map[string]string{"token": "refreshed-token-999"} w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) })) defer loginServer.Close() @@ -157,7 +157,7 @@ func TestUsernamePasswordProvider_InvalidateToken(t *testing.T) { callCount++ response := map[string]string{"token": "new-token-after-invalidation"} w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) })) defer loginServer.Close() @@ -185,7 +185,7 @@ func TestUsernamePasswordProvider_BadJSONResponse(t *testing.T) { // Mock server returning invalid JSON loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - w.Write([]byte("invalid json response")) + _, _ = w.Write([]byte("invalid json response")) })) defer loginServer.Close() diff --git a/sdk/edgeconnect/cloudlet.go b/sdk/edgeconnect/cloudlet.go index 0ed6e71..142b9d6 100644 --- a/sdk/edgeconnect/cloudlet.go +++ b/sdk/edgeconnect/cloudlet.go @@ -22,7 +22,9 @@ func (c *Client) CreateCloudlet(ctx context.Context, input *NewCloudletInput) er if err != nil { return fmt.Errorf("CreateCloudlet failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "CreateCloudlet") @@ -49,7 +51,9 @@ func (c *Client) ShowCloudlet(ctx context.Context, cloudletKey CloudletKey, regi if err != nil { return Cloudlet{}, fmt.Errorf("ShowCloudlet failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode == http.StatusNotFound { return Cloudlet{}, fmt.Errorf("cloudlet %s/%s in region %s: %w", @@ -89,7 +93,9 @@ func (c *Client) ShowCloudlets(ctx context.Context, cloudletKey CloudletKey, reg if err != nil { return nil, fmt.Errorf("ShowCloudlets failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { return nil, c.handleErrorResponse(resp, "ShowCloudlets") @@ -123,7 +129,9 @@ func (c *Client) DeleteCloudlet(ctx context.Context, cloudletKey CloudletKey, re if err != nil { return fmt.Errorf("DeleteCloudlet failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() // 404 is acceptable for delete operations (already deleted) if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { @@ -151,7 +159,9 @@ func (c *Client) GetCloudletManifest(ctx context.Context, cloudletKey CloudletKe if err != nil { return nil, fmt.Errorf("GetCloudletManifest failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode == http.StatusNotFound { return nil, fmt.Errorf("cloudlet manifest %s/%s in region %s: %w", @@ -189,7 +199,9 @@ func (c *Client) GetCloudletResourceUsage(ctx context.Context, cloudletKey Cloud if err != nil { return nil, fmt.Errorf("GetCloudletResourceUsage failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode == http.StatusNotFound { return nil, fmt.Errorf("cloudlet resource usage %s/%s in region %s: %w", diff --git a/sdk/edgeconnect/cloudlet_test.go b/sdk/edgeconnect/cloudlet_test.go index 7d129bb..b029f17 100644 --- a/sdk/edgeconnect/cloudlet_test.go +++ b/sdk/edgeconnect/cloudlet_test.go @@ -70,7 +70,7 @@ func TestCreateCloudlet(t *testing.T) { assert.Equal(t, "application/json", r.Header.Get("Content-Type")) w.WriteHeader(tt.mockStatusCode) - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) })) defer server.Close() @@ -140,7 +140,7 @@ func TestShowCloudlet(t *testing.T) { w.WriteHeader(tt.mockStatusCode) if tt.mockResponse != "" { - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) } })) defer server.Close() @@ -187,7 +187,7 @@ func TestShowCloudlets(t *testing.T) { {"data": {"key": {"organization": "cloudletorg", "name": "cloudlet2"}, "state": "Creating"}} ` w.WriteHeader(200) - w.Write([]byte(response)) + _, _ = w.Write([]byte(response)) })) defer server.Close() @@ -312,7 +312,7 @@ func TestGetCloudletManifest(t *testing.T) { w.WriteHeader(tt.mockStatusCode) if tt.mockResponse != "" { - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) } })) defer server.Close() @@ -380,7 +380,7 @@ func TestGetCloudletResourceUsage(t *testing.T) { w.WriteHeader(tt.mockStatusCode) if tt.mockResponse != "" { - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) } })) defer server.Close() diff --git a/sdk/edgeconnect/v2/appinstance.go b/sdk/edgeconnect/v2/appinstance.go index d38821e..f7b04bb 100644 --- a/sdk/edgeconnect/v2/appinstance.go +++ b/sdk/edgeconnect/v2/appinstance.go @@ -25,7 +25,9 @@ func (c *Client) CreateAppInstance(ctx context.Context, input *NewAppInstanceInp if err != nil { return fmt.Errorf("CreateAppInstance failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "CreateAppInstance") @@ -58,7 +60,9 @@ func (c *Client) ShowAppInstance(ctx context.Context, appInstKey AppInstanceKey, if err != nil { return AppInstance{}, fmt.Errorf("ShowAppInstance failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode == http.StatusNotFound { return AppInstance{}, fmt.Errorf("app instance %s/%s: %w", @@ -98,7 +102,9 @@ func (c *Client) ShowAppInstances(ctx context.Context, appInstKey AppInstanceKey if err != nil { return nil, fmt.Errorf("ShowAppInstances failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { return nil, c.handleErrorResponse(resp, "ShowAppInstances") @@ -127,7 +133,9 @@ func (c *Client) UpdateAppInstance(ctx context.Context, input *UpdateAppInstance if err != nil { return fmt.Errorf("UpdateAppInstance failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "UpdateAppInstance") @@ -154,7 +162,9 @@ func (c *Client) RefreshAppInstance(ctx context.Context, appInstKey AppInstanceK if err != nil { return fmt.Errorf("RefreshAppInstance failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "RefreshAppInstance") @@ -181,7 +191,9 @@ func (c *Client) DeleteAppInstance(ctx context.Context, appInstKey AppInstanceKe if err != nil { return fmt.Errorf("DeleteAppInstance failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() // 404 is acceptable for delete operations (already deleted) if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { diff --git a/sdk/edgeconnect/v2/appinstance_test.go b/sdk/edgeconnect/v2/appinstance_test.go index e1c3d5e..dd0bc45 100644 --- a/sdk/edgeconnect/v2/appinstance_test.go +++ b/sdk/edgeconnect/v2/appinstance_test.go @@ -126,7 +126,7 @@ func TestCreateAppInstance(t *testing.T) { assert.Equal(t, "application/json", r.Header.Get("Content-Type")) w.WriteHeader(tt.mockStatusCode) - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) })) defer server.Close() @@ -207,7 +207,7 @@ func TestShowAppInstance(t *testing.T) { w.WriteHeader(tt.mockStatusCode) if tt.mockResponse != "" { - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) } })) defer server.Close() @@ -254,7 +254,7 @@ func TestShowAppInstances(t *testing.T) { {"data": {"key": {"organization": "testorg", "name": "inst2"}, "state": "Creating"}} ` w.WriteHeader(200) - w.Write([]byte(response)) + _, _ = w.Write([]byte(response)) })) defer server.Close() @@ -361,7 +361,7 @@ func TestUpdateAppInstance(t *testing.T) { assert.Equal(t, tt.input.AppInst.Key.Organization, input.AppInst.Key.Organization) w.WriteHeader(tt.mockStatusCode) - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) })) defer server.Close() diff --git a/sdk/edgeconnect/v2/apps.go b/sdk/edgeconnect/v2/apps.go index 8f5410e..80c3981 100644 --- a/sdk/edgeconnect/v2/apps.go +++ b/sdk/edgeconnect/v2/apps.go @@ -29,7 +29,9 @@ func (c *Client) CreateApp(ctx context.Context, input *NewAppInput) error { if err != nil { return fmt.Errorf("CreateApp failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "CreateApp") @@ -56,7 +58,9 @@ func (c *Client) ShowApp(ctx context.Context, appKey AppKey, region string) (App if err != nil { return App{}, fmt.Errorf("ShowApp failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode == http.StatusNotFound { return App{}, fmt.Errorf("app %s/%s version %s in region %s: %w", @@ -96,7 +100,9 @@ func (c *Client) ShowApps(ctx context.Context, appKey AppKey, region string) ([] if err != nil { return nil, fmt.Errorf("ShowApps failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { return nil, c.handleErrorResponse(resp, "ShowApps") @@ -125,7 +131,9 @@ func (c *Client) UpdateApp(ctx context.Context, input *UpdateAppInput) error { if err != nil { return fmt.Errorf("UpdateApp failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "UpdateApp") @@ -152,7 +160,9 @@ func (c *Client) DeleteApp(ctx context.Context, appKey AppKey, region string) er if err != nil { return fmt.Errorf("DeleteApp failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() // 404 is acceptable for delete operations (already deleted) if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { @@ -254,7 +264,9 @@ func (c *Client) handleErrorResponse(resp *http.Response, operation string) erro bodyBytes := []byte{} if resp.Body != nil { - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() bodyBytes, _ = io.ReadAll(resp.Body) messages = append(messages, string(bodyBytes)) } diff --git a/sdk/edgeconnect/v2/apps_test.go b/sdk/edgeconnect/v2/apps_test.go index 4ea757c..a4c202f 100644 --- a/sdk/edgeconnect/v2/apps_test.go +++ b/sdk/edgeconnect/v2/apps_test.go @@ -67,7 +67,7 @@ func TestCreateApp(t *testing.T) { assert.Equal(t, "application/json", r.Header.Get("Content-Type")) w.WriteHeader(tt.mockStatusCode) - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) })) defer server.Close() @@ -139,7 +139,7 @@ func TestShowApp(t *testing.T) { w.WriteHeader(tt.mockStatusCode) if tt.mockResponse != "" { - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) } })) defer server.Close() @@ -186,7 +186,7 @@ func TestShowApps(t *testing.T) { {"data": {"key": {"organization": "testorg", "name": "app2", "version": "1.0.0"}, "deployment": "docker"}} ` w.WriteHeader(200) - w.Write([]byte(response)) + _, _ = w.Write([]byte(response)) })) defer server.Close() @@ -277,7 +277,7 @@ func TestUpdateApp(t *testing.T) { assert.Equal(t, tt.input.App.Key.Organization, input.App.Key.Organization) w.WriteHeader(tt.mockStatusCode) - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) })) defer server.Close() @@ -407,13 +407,3 @@ func TestAPIError(t *testing.T) { assert.Equal(t, 400, err.StatusCode) assert.Len(t, err.Messages, 2) } - -// Helper function to create a test server that handles streaming JSON responses -func createStreamingJSONServer(responses []string, statusCode int) *httptest.Server { - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(statusCode) - for _, response := range responses { - w.Write([]byte(response + "\n")) - } - })) -} diff --git a/sdk/edgeconnect/v2/auth.go b/sdk/edgeconnect/v2/auth.go index a1f33a2..f428f64 100644 --- a/sdk/edgeconnect/v2/auth.go +++ b/sdk/edgeconnect/v2/auth.go @@ -138,7 +138,9 @@ func (u *UsernamePasswordProvider) retrieveToken(ctx context.Context) (string, e if err != nil { return "", err } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() // Read response body - same as existing implementation body, err := io.ReadAll(resp.Body) diff --git a/sdk/edgeconnect/v2/auth_test.go b/sdk/edgeconnect/v2/auth_test.go index 0fc5b24..34ebcaf 100644 --- a/sdk/edgeconnect/v2/auth_test.go +++ b/sdk/edgeconnect/v2/auth_test.go @@ -56,7 +56,7 @@ func TestUsernamePasswordProvider_Success(t *testing.T) { // Return token response := map[string]string{"token": "dynamic-token-456"} w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) })) defer loginServer.Close() @@ -75,7 +75,7 @@ func TestUsernamePasswordProvider_LoginFailure(t *testing.T) { // Mock login server that returns error loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte("Invalid credentials")) + _, _ = w.Write([]byte("Invalid credentials")) })) defer loginServer.Close() @@ -99,7 +99,7 @@ func TestUsernamePasswordProvider_TokenCaching(t *testing.T) { callCount++ response := map[string]string{"token": "cached-token-789"} w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) })) defer loginServer.Close() @@ -128,7 +128,7 @@ func TestUsernamePasswordProvider_TokenExpiry(t *testing.T) { callCount++ response := map[string]string{"token": "refreshed-token-999"} w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) })) defer loginServer.Close() @@ -157,7 +157,7 @@ func TestUsernamePasswordProvider_InvalidateToken(t *testing.T) { callCount++ response := map[string]string{"token": "new-token-after-invalidation"} w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + _ = json.NewEncoder(w).Encode(response) })) defer loginServer.Close() @@ -185,7 +185,7 @@ func TestUsernamePasswordProvider_BadJSONResponse(t *testing.T) { // Mock server returning invalid JSON loginServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - w.Write([]byte("invalid json response")) + _, _ = w.Write([]byte("invalid json response")) })) defer loginServer.Close() diff --git a/sdk/edgeconnect/v2/cloudlet.go b/sdk/edgeconnect/v2/cloudlet.go index 415584a..c877486 100644 --- a/sdk/edgeconnect/v2/cloudlet.go +++ b/sdk/edgeconnect/v2/cloudlet.go @@ -22,7 +22,9 @@ func (c *Client) CreateCloudlet(ctx context.Context, input *NewCloudletInput) er if err != nil { return fmt.Errorf("CreateCloudlet failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 { return c.handleErrorResponse(resp, "CreateCloudlet") @@ -49,7 +51,9 @@ func (c *Client) ShowCloudlet(ctx context.Context, cloudletKey CloudletKey, regi if err != nil { return Cloudlet{}, fmt.Errorf("ShowCloudlet failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode == http.StatusNotFound { return Cloudlet{}, fmt.Errorf("cloudlet %s/%s in region %s: %w", @@ -89,7 +93,9 @@ func (c *Client) ShowCloudlets(ctx context.Context, cloudletKey CloudletKey, reg if err != nil { return nil, fmt.Errorf("ShowCloudlets failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { return nil, c.handleErrorResponse(resp, "ShowCloudlets") @@ -123,7 +129,9 @@ func (c *Client) DeleteCloudlet(ctx context.Context, cloudletKey CloudletKey, re if err != nil { return fmt.Errorf("DeleteCloudlet failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() // 404 is acceptable for delete operations (already deleted) if resp.StatusCode >= 400 && resp.StatusCode != http.StatusNotFound { @@ -151,7 +159,9 @@ func (c *Client) GetCloudletManifest(ctx context.Context, cloudletKey CloudletKe if err != nil { return nil, fmt.Errorf("GetCloudletManifest failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode == http.StatusNotFound { return nil, fmt.Errorf("cloudlet manifest %s/%s in region %s: %w", @@ -189,7 +199,9 @@ func (c *Client) GetCloudletResourceUsage(ctx context.Context, cloudletKey Cloud if err != nil { return nil, fmt.Errorf("GetCloudletResourceUsage failed: %w", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode == http.StatusNotFound { return nil, fmt.Errorf("cloudlet resource usage %s/%s in region %s: %w", diff --git a/sdk/edgeconnect/v2/cloudlet_test.go b/sdk/edgeconnect/v2/cloudlet_test.go index 8f2cc06..d8ffb75 100644 --- a/sdk/edgeconnect/v2/cloudlet_test.go +++ b/sdk/edgeconnect/v2/cloudlet_test.go @@ -70,7 +70,7 @@ func TestCreateCloudlet(t *testing.T) { assert.Equal(t, "application/json", r.Header.Get("Content-Type")) w.WriteHeader(tt.mockStatusCode) - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) })) defer server.Close() @@ -140,7 +140,7 @@ func TestShowCloudlet(t *testing.T) { w.WriteHeader(tt.mockStatusCode) if tt.mockResponse != "" { - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) } })) defer server.Close() @@ -187,7 +187,7 @@ func TestShowCloudlets(t *testing.T) { {"data": {"key": {"organization": "cloudletorg", "name": "cloudlet2"}, "state": "Creating"}} ` w.WriteHeader(200) - w.Write([]byte(response)) + _, _ = w.Write([]byte(response)) })) defer server.Close() @@ -312,7 +312,7 @@ func TestGetCloudletManifest(t *testing.T) { w.WriteHeader(tt.mockStatusCode) if tt.mockResponse != "" { - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) } })) defer server.Close() @@ -380,7 +380,7 @@ func TestGetCloudletResourceUsage(t *testing.T) { w.WriteHeader(tt.mockStatusCode) if tt.mockResponse != "" { - w.Write([]byte(tt.mockResponse)) + _, _ = w.Write([]byte(tt.mockResponse)) } })) defer server.Close() diff --git a/sdk/internal/http/transport.go b/sdk/internal/http/transport.go index c3bbab1..35b71b8 100644 --- a/sdk/internal/http/transport.go +++ b/sdk/internal/http/transport.go @@ -162,7 +162,9 @@ func (t *Transport) CallJSON(ctx context.Context, method, url string, body inter if err != nil { return resp, err } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() // Read response body respBody, err := io.ReadAll(resp.Body) From ece3dddfe6e014529efd86c59fdbb2c5d0193e4c Mon Sep 17 00:00:00 2001 From: Richard Robert Reitz Date: Mon, 27 Oct 2025 16:32:57 +0100 Subject: [PATCH 35/40] feat(edge): Added ubuntu buildkit edge v1 (running) and v2 (not running) example --- .../ubuntu-buildkit/EdgeConnectConfig_v1.yaml | 29 ++++++++++ .../ubuntu-buildkit/EdgeConnectConfig_v2.yaml | 29 ++++++++++ .../ubuntu-buildkit/k8s-deployment.yaml | 57 +++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 sdk/examples/ubuntu-buildkit/EdgeConnectConfig_v1.yaml create mode 100644 sdk/examples/ubuntu-buildkit/EdgeConnectConfig_v2.yaml create mode 100644 sdk/examples/ubuntu-buildkit/k8s-deployment.yaml diff --git a/sdk/examples/ubuntu-buildkit/EdgeConnectConfig_v1.yaml b/sdk/examples/ubuntu-buildkit/EdgeConnectConfig_v1.yaml new file mode 100644 index 0000000..9710327 --- /dev/null +++ b/sdk/examples/ubuntu-buildkit/EdgeConnectConfig_v1.yaml @@ -0,0 +1,29 @@ +# Is there a swagger file for the new EdgeConnect API? +# How does it differ from the EdgeXR API? +kind: edgeconnect-deployment +metadata: + name: "edge-ubuntu-buildkit" # name could be used for appName + appVersion: "1.0.0" + organization: "edp2" +spec: + # dockerApp: # Docker is OBSOLETE + # appVersion: "1.0.0" + # manifestFile: "./docker-compose.yaml" + # image: "https://registry-1.docker.io/library/nginx:latest" + k8sApp: + manifestFile: "./k8s-deployment.yaml" + infraTemplate: + - region: "EU" + cloudletOrg: "TelekomOP" + cloudletName: "Munich" + flavorName: "EU.small" + network: + outboundConnections: + - protocol: "tcp" + portRangeMin: 80 + portRangeMax: 80 + remoteCIDR: "0.0.0.0/0" + - protocol: "tcp" + portRangeMin: 443 + portRangeMax: 443 + remoteCIDR: "0.0.0.0/0" diff --git a/sdk/examples/ubuntu-buildkit/EdgeConnectConfig_v2.yaml b/sdk/examples/ubuntu-buildkit/EdgeConnectConfig_v2.yaml new file mode 100644 index 0000000..9fb80df --- /dev/null +++ b/sdk/examples/ubuntu-buildkit/EdgeConnectConfig_v2.yaml @@ -0,0 +1,29 @@ +# Is there a swagger file for the new EdgeConnect API? +# How does it differ from the EdgeXR API? +kind: edgeconnect-deployment +metadata: + name: "edge-ubuntu-buildkit" # name could be used for appName + appVersion: "1" + organization: "edp2-orca" +spec: + # dockerApp: # Docker is OBSOLETE + # appVersion: "1.0.0" + # manifestFile: "./docker-compose.yaml" + # image: "https://registry-1.docker.io/library/nginx:latest" + k8sApp: + manifestFile: "./k8s-deployment.yaml" + infraTemplate: + - region: "US" + cloudletOrg: "TelekomOp" + cloudletName: "gardener-shepherd-test" + flavorName: "defualt" + network: + outboundConnections: + - protocol: "tcp" + portRangeMin: 80 + portRangeMax: 80 + remoteCIDR: "0.0.0.0/0" + - protocol: "tcp" + portRangeMin: 443 + portRangeMax: 443 + remoteCIDR: "0.0.0.0/0" diff --git a/sdk/examples/ubuntu-buildkit/k8s-deployment.yaml b/sdk/examples/ubuntu-buildkit/k8s-deployment.yaml new file mode 100644 index 0000000..d4d3dd8 --- /dev/null +++ b/sdk/examples/ubuntu-buildkit/k8s-deployment.yaml @@ -0,0 +1,57 @@ +# Add remote buildx builder: +# docker buildx create --use --name sidecar tcp://127.0.0.1:1234 + +# Run build: +# docker buildx build . + +apiVersion: v1 +kind: Service +metadata: + name: ubuntu-runner + labels: + run: ubuntu-runner +spec: + type: LoadBalancer + ports: + - name: tcp80 + protocol: TCP + port: 80 + targetPort: 80 + selector: + run: ubuntu-runner +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app: ubuntu-runner + name: ubuntu-runner +spec: + replicas: 1 + selector: + matchLabels: + app: ubuntu-runner + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: ubuntu-runner + annotations: + container.apparmor.security.beta.kubernetes.io/buildkitd: unconfined + spec: + containers: + - name: ubuntu + image: edp.buildth.ing/devfw-cicd/catthehacker/ubuntu:act-22.04-amd64 + command: + - sleep + - 7d + - args: + - --allow-insecure-entitlement=network.host + - --oci-worker-no-process-sandbox + - --addr + - tcp://127.0.0.1:1234 + image: moby/buildkit:v0.25.1-rootless + imagePullPolicy: IfNotPresent + name: buildkitd From a51e2ae4541a72df74bd814c4984936e89812448 Mon Sep 17 00:00:00 2001 From: Patrick Sy Date: Thu, 13 Nov 2025 15:40:36 +0100 Subject: [PATCH 36/40] feat(api): Added AppKey property to ShowAppInstances --- cmd/instance.go | 8 ++++++-- internal/apply/v1/planner.go | 7 +++++-- internal/apply/v1/planner_test.go | 10 +--------- internal/apply/v2/planner.go | 7 +++++-- internal/apply/v2/planner_test.go | 2 +- internal/apply/v2/strategy_recreate.go | 4 +++- sdk/edgeconnect/appinstance.go | 4 ++-- sdk/edgeconnect/appinstance_test.go | 5 ++++- sdk/edgeconnect/v2/appinstance.go | 2 +- sdk/edgeconnect/v2/appinstance_test.go | 5 ++++- sdk/examples/comprehensive/main.go | 6 +++--- 11 files changed, 35 insertions(+), 25 deletions(-) diff --git a/cmd/instance.go b/cmd/instance.go index 75868ce..68c8f5b 100644 --- a/cmd/instance.go +++ b/cmd/instance.go @@ -15,6 +15,7 @@ var ( cloudletOrg string instanceName string flavorName string + appId string ) var appInstanceCmd = &cobra.Command{ @@ -104,7 +105,8 @@ var showInstanceCmd = &cobra.Command{ Name: cloudletName, }, } - instance, err := c.ShowAppInstance(context.Background(), instanceKey, region) + appkey := edgeconnect.AppKey{Name: appId} + instance, err := c.ShowAppInstance(context.Background(), instanceKey, appkey, region) if err != nil { fmt.Printf("Error showing app instance: %v\n", err) os.Exit(1) @@ -120,7 +122,8 @@ var showInstanceCmd = &cobra.Command{ Name: cloudletName, }, } - instance, err := c.ShowAppInstance(context.Background(), instanceKey, region) + appkey := v2.AppKey{Name: appId} + instance, err := c.ShowAppInstance(context.Background(), instanceKey, appkey, region) if err != nil { fmt.Printf("Error showing app instance: %v\n", err) os.Exit(1) @@ -229,6 +232,7 @@ func init() { cmd.Flags().StringVarP(&cloudletName, "cloudlet", "c", "", "cloudlet name (required)") cmd.Flags().StringVarP(&cloudletOrg, "cloudlet-org", "", "", "cloudlet organization (required)") cmd.Flags().StringVarP(®ion, "region", "r", "", "region (required)") + cmd.Flags().StringVarP(&appId, "app-id", "i", "", "application id") if err := cmd.MarkFlagRequired("org"); err != nil { panic(err) diff --git a/internal/apply/v1/planner.go b/internal/apply/v1/planner.go index bcfd043..e1a1449 100644 --- a/internal/apply/v1/planner.go +++ b/internal/apply/v1/planner.go @@ -21,7 +21,7 @@ type EdgeConnectClientInterface interface { CreateApp(ctx context.Context, input *edgeconnect.NewAppInput) error UpdateApp(ctx context.Context, input *edgeconnect.UpdateAppInput) error DeleteApp(ctx context.Context, appKey edgeconnect.AppKey, region string) error - ShowAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) (edgeconnect.AppInstance, error) + ShowAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, appKey edgeconnect.AppKey, region string) (edgeconnect.AppInstance, error) CreateAppInstance(ctx context.Context, input *edgeconnect.NewAppInstanceInput) error UpdateAppInstance(ctx context.Context, input *edgeconnect.UpdateAppInstanceInput) error DeleteAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) error @@ -342,8 +342,11 @@ func (p *EdgeConnectPlanner) getCurrentInstanceState(ctx context.Context, desire Name: desired.CloudletName, }, } + appKey := edgeconnect.AppKey{ + Name: desired.AppName, + } - instance, err := p.client.ShowAppInstance(timeoutCtx, instanceKey, desired.Region) + instance, err := p.client.ShowAppInstance(timeoutCtx, instanceKey, appKey, desired.Region) if err != nil { return nil, err } diff --git a/internal/apply/v1/planner_test.go b/internal/apply/v1/planner_test.go index 7761365..6530d8e 100644 --- a/internal/apply/v1/planner_test.go +++ b/internal/apply/v1/planner_test.go @@ -29,7 +29,7 @@ func (m *MockEdgeConnectClient) ShowApp(ctx context.Context, appKey edgeconnect. return args.Get(0).(edgeconnect.App), args.Error(1) } -func (m *MockEdgeConnectClient) ShowAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) (edgeconnect.AppInstance, error) { +func (m *MockEdgeConnectClient) ShowAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, appKey edgeconnect.AppKey, region string) (edgeconnect.AppInstance, error) { args := m.Called(ctx, instanceKey, region) if args.Get(0) == nil { return edgeconnect.AppInstance{}, args.Error(1) @@ -75,14 +75,6 @@ func (m *MockEdgeConnectClient) ShowApps(ctx context.Context, appKey edgeconnect return args.Get(0).([]edgeconnect.App), args.Error(1) } -func (m *MockEdgeConnectClient) ShowAppInstances(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) ([]edgeconnect.AppInstance, error) { - args := m.Called(ctx, instanceKey, region) - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).([]edgeconnect.AppInstance), args.Error(1) -} - func TestNewPlanner(t *testing.T) { mockClient := &MockEdgeConnectClient{} planner := NewPlanner(mockClient) diff --git a/internal/apply/v2/planner.go b/internal/apply/v2/planner.go index 61f15cd..33a809e 100644 --- a/internal/apply/v2/planner.go +++ b/internal/apply/v2/planner.go @@ -21,7 +21,7 @@ type EdgeConnectClientInterface interface { CreateApp(ctx context.Context, input *v2.NewAppInput) error UpdateApp(ctx context.Context, input *v2.UpdateAppInput) error DeleteApp(ctx context.Context, appKey v2.AppKey, region string) error - ShowAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) (v2.AppInstance, error) + ShowAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, appKey v2.AppKey, region string) (v2.AppInstance, error) CreateAppInstance(ctx context.Context, input *v2.NewAppInstanceInput) error UpdateAppInstance(ctx context.Context, input *v2.UpdateAppInstanceInput) error DeleteAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) error @@ -343,7 +343,10 @@ func (p *EdgeConnectPlanner) getCurrentInstanceState(ctx context.Context, desire }, } - instance, err := p.client.ShowAppInstance(timeoutCtx, instanceKey, desired.Region) + appKey := v2.AppKey{ Name: desired.AppName} + + + instance, err := p.client.ShowAppInstance(timeoutCtx, instanceKey, appKey, desired.Region) if err != nil { return nil, err } diff --git a/internal/apply/v2/planner_test.go b/internal/apply/v2/planner_test.go index 20d3dab..3fbdbc3 100644 --- a/internal/apply/v2/planner_test.go +++ b/internal/apply/v2/planner_test.go @@ -29,7 +29,7 @@ func (m *MockEdgeConnectClient) ShowApp(ctx context.Context, appKey v2.AppKey, r return args.Get(0).(v2.App), args.Error(1) } -func (m *MockEdgeConnectClient) ShowAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) (v2.AppInstance, error) { +func (m *MockEdgeConnectClient) ShowAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, appKey v2.AppKey, region string) (v2.AppInstance, error) { args := m.Called(ctx, instanceKey, region) if args.Get(0) == nil { return v2.AppInstance{}, args.Error(1) diff --git a/internal/apply/v2/strategy_recreate.go b/internal/apply/v2/strategy_recreate.go index 4d81029..17ea3a7 100644 --- a/internal/apply/v2/strategy_recreate.go +++ b/internal/apply/v2/strategy_recreate.go @@ -586,7 +586,9 @@ func (r *RecreateStrategy) backupInstance(ctx context.Context, action InstanceAc }, } - instance, err := r.client.ShowAppInstance(ctx, instanceKey, action.Target.Region) + appKey := v2.AppKey{ Name: action.Desired.AppName } + + instance, err := r.client.ShowAppInstance(ctx, instanceKey, appKey, action.Target.Region) if err != nil { return nil, fmt.Errorf("failed to fetch instance for backup: %w", err) } diff --git a/sdk/edgeconnect/appinstance.go b/sdk/edgeconnect/appinstance.go index 4a1bda9..9e73511 100644 --- a/sdk/edgeconnect/appinstance.go +++ b/sdk/edgeconnect/appinstance.go @@ -45,12 +45,12 @@ func (c *Client) CreateAppInstance(ctx context.Context, input *NewAppInstanceInp // 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) { +func (c *Client) ShowAppInstance(ctx context.Context, appInstKey AppInstanceKey, appKey AppKey, region string) (AppInstance, error) { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/ShowAppInst" filter := AppInstanceFilter{ - AppInstance: AppInstance{Key: appInstKey}, + AppInstance: AppInstance{AppKey: appKey, Key: appInstKey}, Region: region, } diff --git a/sdk/edgeconnect/appinstance_test.go b/sdk/edgeconnect/appinstance_test.go index 003f024..210c5e7 100644 --- a/sdk/edgeconnect/appinstance_test.go +++ b/sdk/edgeconnect/appinstance_test.go @@ -156,6 +156,7 @@ func TestCreateAppInstance(t *testing.T) { func TestShowAppInstance(t *testing.T) { tests := []struct { name string + appKey AppKey appInstKey AppInstanceKey region string mockStatusCode int @@ -173,6 +174,7 @@ func TestShowAppInstance(t *testing.T) { Name: "testcloudlet", }, }, + appKey: AppKey{Name: "test-app-id"}, region: "us-west", mockStatusCode: 200, mockResponse: `{"data": {"key": {"organization": "testorg", "name": "testinst", "cloudlet_key": {"organization": "cloudletorg", "name": "testcloudlet"}}, "state": "Ready"}} @@ -190,6 +192,7 @@ func TestShowAppInstance(t *testing.T) { Name: "testcloudlet", }, }, + appKey: AppKey{Name: "test-app-id"}, region: "us-west", mockStatusCode: 404, mockResponse: "", @@ -219,7 +222,7 @@ func TestShowAppInstance(t *testing.T) { // Execute test ctx := context.Background() - appInst, err := client.ShowAppInstance(ctx, tt.appInstKey, tt.region) + appInst, err := client.ShowAppInstance(ctx, tt.appInstKey, tt.appKey, tt.region) // Verify results if tt.expectError { diff --git a/sdk/edgeconnect/v2/appinstance.go b/sdk/edgeconnect/v2/appinstance.go index f7b04bb..bd27be7 100644 --- a/sdk/edgeconnect/v2/appinstance.go +++ b/sdk/edgeconnect/v2/appinstance.go @@ -47,7 +47,7 @@ func (c *Client) CreateAppInstance(ctx context.Context, input *NewAppInstanceInp // 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) { +func (c *Client) ShowAppInstance(ctx context.Context, appInstKey AppInstanceKey, appKey AppKey, region string) (AppInstance, error) { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/ShowAppInst" diff --git a/sdk/edgeconnect/v2/appinstance_test.go b/sdk/edgeconnect/v2/appinstance_test.go index dd0bc45..ce4e758 100644 --- a/sdk/edgeconnect/v2/appinstance_test.go +++ b/sdk/edgeconnect/v2/appinstance_test.go @@ -157,6 +157,7 @@ func TestShowAppInstance(t *testing.T) { tests := []struct { name string appInstKey AppInstanceKey + appKey AppKey region string mockStatusCode int mockResponse string @@ -173,6 +174,7 @@ func TestShowAppInstance(t *testing.T) { Name: "testcloudlet", }, }, + appKey: AppKey{ Name: "testapp" }, region: "us-west", mockStatusCode: 200, mockResponse: `{"data": {"key": {"organization": "testorg", "name": "testinst", "cloudlet_key": {"organization": "cloudletorg", "name": "testcloudlet"}}, "state": "Ready"}} @@ -190,6 +192,7 @@ func TestShowAppInstance(t *testing.T) { Name: "testcloudlet", }, }, + appKey: AppKey{ Name: "testapp" }, region: "us-west", mockStatusCode: 404, mockResponse: "", @@ -219,7 +222,7 @@ func TestShowAppInstance(t *testing.T) { // Execute test ctx := context.Background() - appInst, err := client.ShowAppInstance(ctx, tt.appInstKey, tt.region) + appInst, err := client.ShowAppInstance(ctx, tt.appInstKey, tt.appKey, tt.region) // Verify results if tt.expectError { diff --git a/sdk/examples/comprehensive/main.go b/sdk/examples/comprehensive/main.go index f932a75..0bc6e51 100644 --- a/sdk/examples/comprehensive/main.go +++ b/sdk/examples/comprehensive/main.go @@ -193,7 +193,7 @@ func runComprehensiveWorkflow(ctx context.Context, c *v2.Client, config Workflow }, } - instanceDetails, err := waitForInstanceReady(ctx, c, instanceKey, config.Region, 5*time.Minute) + instanceDetails, err := waitForInstanceReady(ctx, c, instanceKey, v2.AppKey{}, config.Region, 5*time.Minute) if err != nil { return fmt.Errorf("failed to wait for instance ready: %w", err) } @@ -306,7 +306,7 @@ func getEnvOrDefault(key, defaultValue string) string { } // waitForInstanceReady polls the instance status until it's no longer "Creating" or timeout -func waitForInstanceReady(ctx context.Context, c *v2.Client, instanceKey v2.AppInstanceKey, region string, timeout time.Duration) (v2.AppInstance, error) { +func waitForInstanceReady(ctx context.Context, c *v2.Client, instanceKey v2.AppInstanceKey, appKey v2.AppKey, region string, timeout time.Duration) (v2.AppInstance, error) { timeoutCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() @@ -321,7 +321,7 @@ func waitForInstanceReady(ctx context.Context, c *v2.Client, instanceKey v2.AppI return v2.AppInstance{}, fmt.Errorf("timeout waiting for instance to be ready after %v", timeout) case <-ticker.C: - instance, err := c.ShowAppInstance(timeoutCtx, instanceKey, region) + instance, err := c.ShowAppInstance(timeoutCtx, instanceKey, appKey, region) if err != nil { // Log error but continue polling fmt.Printf(" ⚠️ Error checking instance state: %v\n", err) From ece2955a2a427e85caaf739f837468e2cb128e2b Mon Sep 17 00:00:00 2001 From: Patrick Sy Date: Thu, 13 Nov 2025 16:59:38 +0100 Subject: [PATCH 37/40] feat(api): Added AppKey to ShowAppInstances --- cmd/instance.go | 6 ++++-- internal/delete/v1/planner.go | 5 +++-- internal/delete/v2/manager_test.go | 2 +- internal/delete/v2/planner.go | 5 +++-- internal/delete/v2/planner_test.go | 2 +- sdk/edgeconnect/appinstance.go | 4 ++-- sdk/edgeconnect/appinstance_test.go | 2 +- sdk/edgeconnect/v2/appinstance.go | 4 ++-- sdk/edgeconnect/v2/appinstance_test.go | 2 +- sdk/examples/comprehensive/main.go | 2 +- 10 files changed, 19 insertions(+), 15 deletions(-) diff --git a/cmd/instance.go b/cmd/instance.go index 68c8f5b..d856dea 100644 --- a/cmd/instance.go +++ b/cmd/instance.go @@ -149,7 +149,8 @@ var listInstancesCmd = &cobra.Command{ Name: cloudletName, }, } - instances, err := c.ShowAppInstances(context.Background(), instanceKey, region) + appKey := edgeconnect.AppKey{Name: appId} + instances, err := c.ShowAppInstances(context.Background(), instanceKey, appKey, region) if err != nil { fmt.Printf("Error listing app instances: %v\n", err) os.Exit(1) @@ -168,7 +169,8 @@ var listInstancesCmd = &cobra.Command{ Name: cloudletName, }, } - instances, err := c.ShowAppInstances(context.Background(), instanceKey, region) + appKey := v2.AppKey{Name: appId} + instances, err := c.ShowAppInstances(context.Background(), instanceKey, appKey, region) if err != nil { fmt.Printf("Error listing app instances: %v\n", err) os.Exit(1) diff --git a/internal/delete/v1/planner.go b/internal/delete/v1/planner.go index 10f41c5..ca97b84 100644 --- a/internal/delete/v1/planner.go +++ b/internal/delete/v1/planner.go @@ -14,7 +14,7 @@ import ( // EdgeConnectClientInterface defines the methods needed for deletion planning type EdgeConnectClientInterface interface { ShowApp(ctx context.Context, appKey edgeconnect.AppKey, region string) (edgeconnect.App, error) - ShowAppInstances(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) ([]edgeconnect.AppInstance, error) + ShowAppInstances(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, appKey edgeconnect.AppKey, region string) ([]edgeconnect.AppInstance, error) DeleteApp(ctx context.Context, appKey edgeconnect.AppKey, region string) error DeleteAppInstance(ctx context.Context, instanceKey edgeconnect.AppInstanceKey, region string) error } @@ -154,8 +154,9 @@ func (p *EdgeConnectPlanner) findInstancesToDelete(ctx context.Context, config * Name: infra.CloudletName, }, } + appKey := edgeconnect.AppKey{Name: config.Metadata.Name} - instances, err := p.client.ShowAppInstances(ctx, instanceKey, infra.Region) + instances, err := p.client.ShowAppInstances(ctx, instanceKey, appKey, infra.Region) if err != nil { // If it's a not found error, just continue if isNotFoundError(err) { diff --git a/internal/delete/v2/manager_test.go b/internal/delete/v2/manager_test.go index fa2b7c9..d021f20 100644 --- a/internal/delete/v2/manager_test.go +++ b/internal/delete/v2/manager_test.go @@ -27,7 +27,7 @@ func (m *MockResourceClient) ShowApp(ctx context.Context, appKey v2.AppKey, regi return args.Get(0).(v2.App), args.Error(1) } -func (m *MockResourceClient) ShowAppInstances(ctx context.Context, instanceKey v2.AppInstanceKey, region string) ([]v2.AppInstance, error) { +func (m *MockResourceClient) ShowAppInstances(ctx context.Context, instanceKey v2.AppInstanceKey, appKey v2.AppKey, region string) ([]v2.AppInstance, error) { args := m.Called(ctx, instanceKey, region) if args.Get(0) == nil { return nil, args.Error(1) diff --git a/internal/delete/v2/planner.go b/internal/delete/v2/planner.go index 752fe3b..76ec1c6 100644 --- a/internal/delete/v2/planner.go +++ b/internal/delete/v2/planner.go @@ -14,7 +14,7 @@ import ( // EdgeConnectClientInterface defines the methods needed for deletion planning type EdgeConnectClientInterface interface { ShowApp(ctx context.Context, appKey v2.AppKey, region string) (v2.App, error) - ShowAppInstances(ctx context.Context, instanceKey v2.AppInstanceKey, region string) ([]v2.AppInstance, error) + ShowAppInstances(ctx context.Context, instanceKey v2.AppInstanceKey, appKey v2.AppKey, region string) ([]v2.AppInstance, error) DeleteApp(ctx context.Context, appKey v2.AppKey, region string) error DeleteAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) error } @@ -154,8 +154,9 @@ func (p *EdgeConnectPlanner) findInstancesToDelete(ctx context.Context, config * Name: infra.CloudletName, }, } + appKey := v2.AppKey{Name: config.Metadata.Name} - instances, err := p.client.ShowAppInstances(ctx, instanceKey, infra.Region) + instances, err := p.client.ShowAppInstances(ctx, instanceKey, appKey, infra.Region) if err != nil { // If it's a not found error, just continue if isNotFoundError(err) { diff --git a/internal/delete/v2/planner_test.go b/internal/delete/v2/planner_test.go index 2ec9eae..292cecc 100644 --- a/internal/delete/v2/planner_test.go +++ b/internal/delete/v2/planner_test.go @@ -28,7 +28,7 @@ func (m *MockEdgeConnectClient) ShowApp(ctx context.Context, appKey v2.AppKey, r return args.Get(0).(v2.App), args.Error(1) } -func (m *MockEdgeConnectClient) ShowAppInstances(ctx context.Context, instanceKey v2.AppInstanceKey, region string) ([]v2.AppInstance, error) { +func (m *MockEdgeConnectClient) ShowAppInstances(ctx context.Context, instanceKey v2.AppInstanceKey, appKey v2.AppKey, region string) ([]v2.AppInstance, error) { args := m.Called(ctx, instanceKey, region) if args.Get(0) == nil { return nil, args.Error(1) diff --git a/sdk/edgeconnect/appinstance.go b/sdk/edgeconnect/appinstance.go index 9e73511..2a6673c 100644 --- a/sdk/edgeconnect/appinstance.go +++ b/sdk/edgeconnect/appinstance.go @@ -87,12 +87,12 @@ func (c *Client) ShowAppInstance(ctx context.Context, appInstKey AppInstanceKey, // 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) { +func (c *Client) ShowAppInstances(ctx context.Context, appInstKey AppInstanceKey, appKey AppKey, region string) ([]AppInstance, error) { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/ShowAppInst" filter := AppInstanceFilter{ - AppInstance: AppInstance{Key: appInstKey}, + AppInstance: AppInstance{Key: appInstKey, AppKey: appKey}, Region: region, } diff --git a/sdk/edgeconnect/appinstance_test.go b/sdk/edgeconnect/appinstance_test.go index 210c5e7..3545904 100644 --- a/sdk/edgeconnect/appinstance_test.go +++ b/sdk/edgeconnect/appinstance_test.go @@ -264,7 +264,7 @@ func TestShowAppInstances(t *testing.T) { client := NewClient(server.URL) ctx := context.Background() - appInstances, err := client.ShowAppInstances(ctx, AppInstanceKey{Organization: "testorg"}, "us-west") + appInstances, err := client.ShowAppInstances(ctx, AppInstanceKey{Organization: "testorg"}, AppKey{}, "us-west") require.NoError(t, err) assert.Len(t, appInstances, 2) diff --git a/sdk/edgeconnect/v2/appinstance.go b/sdk/edgeconnect/v2/appinstance.go index bd27be7..013d053 100644 --- a/sdk/edgeconnect/v2/appinstance.go +++ b/sdk/edgeconnect/v2/appinstance.go @@ -89,12 +89,12 @@ func (c *Client) ShowAppInstance(ctx context.Context, appInstKey AppInstanceKey, // 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) { +func (c *Client) ShowAppInstances(ctx context.Context, appInstKey AppInstanceKey, appKey AppKey, region string) ([]AppInstance, error) { transport := c.getTransport() url := c.BaseURL + "/api/v1/auth/ctrl/ShowAppInst" filter := AppInstanceFilter{ - AppInstance: AppInstance{Key: appInstKey}, + AppInstance: AppInstance{Key: appInstKey, AppKey: appKey}, Region: region, } diff --git a/sdk/edgeconnect/v2/appinstance_test.go b/sdk/edgeconnect/v2/appinstance_test.go index ce4e758..bf4db81 100644 --- a/sdk/edgeconnect/v2/appinstance_test.go +++ b/sdk/edgeconnect/v2/appinstance_test.go @@ -264,7 +264,7 @@ func TestShowAppInstances(t *testing.T) { client := NewClient(server.URL) ctx := context.Background() - appInstances, err := client.ShowAppInstances(ctx, AppInstanceKey{Organization: "testorg"}, "us-west") + appInstances, err := client.ShowAppInstances(ctx, AppInstanceKey{Organization: "testorg"}, AppKey{}, "us-west") require.NoError(t, err) assert.Len(t, appInstances, 2) diff --git a/sdk/examples/comprehensive/main.go b/sdk/examples/comprehensive/main.go index 0bc6e51..25a4aa5 100644 --- a/sdk/examples/comprehensive/main.go +++ b/sdk/examples/comprehensive/main.go @@ -207,7 +207,7 @@ func runComprehensiveWorkflow(ctx context.Context, c *v2.Client, config Workflow // 6. List Application Instances fmt.Println("\n6️⃣ Listing application instances...") - instances, err := c.ShowAppInstances(ctx, v2.AppInstanceKey{Organization: config.Organization}, config.Region) + instances, err := c.ShowAppInstances(ctx, v2.AppInstanceKey{Organization: config.Organization}, v2.AppKey{}, config.Region) if err != nil { return fmt.Errorf("failed to list app instances: %w", err) } From 2909e0d1b4c0a98d402e87b901b72aa127ccfc28 Mon Sep 17 00:00:00 2001 From: Martin McCaffery Date: Fri, 14 Nov 2025 12:11:24 +0100 Subject: [PATCH 38/40] feat(api): add nicer error message to format issues indicating permission denied --- sdk/edgeconnect/appinstance.go | 4 ++++ sdk/edgeconnect/apps.go | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/sdk/edgeconnect/appinstance.go b/sdk/edgeconnect/appinstance.go index 2a6673c..34e3486 100644 --- a/sdk/edgeconnect/appinstance.go +++ b/sdk/edgeconnect/appinstance.go @@ -213,6 +213,10 @@ 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 f197a68..a086475 100644 --- a/sdk/edgeconnect/apps.go +++ b/sdk/edgeconnect/apps.go @@ -15,7 +15,8 @@ import ( var ( // ErrResourceNotFound indicates the requested resource was not found - ErrResourceNotFound = fmt.Errorf("resource not found") + ErrResourceNotFound = fmt.Errorf("resource not found") + ErrFaultyResponsePerhaps403 = fmt.Errorf("faulty response from API, may indicate permission denied") ) // CreateApp creates a new application in the specified region @@ -179,6 +180,10 @@ 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 From e38d7e84d52b4fb9fc8e8d6ec40b540ef1f81b16 Mon Sep 17 00:00:00 2001 From: Manuel Ganter Date: Fri, 14 Nov 2025 16:00:43 +0100 Subject: [PATCH 39/40] parseStreamingResponse is now unified for all objects under both versions --- .gitignore | 2 + Makefile | 2 +- internal/apply/v2/manager.go | 20 ++-- internal/apply/v2/planner.go | 3 +- internal/apply/v2/strategy_recreate.go | 2 +- internal/config/example_test.go | 6 +- internal/delete/v2/types_test.go | 4 +- sdk/edgeconnect/types.go | 134 ++++++++++----------- sdk/edgeconnect/v2/appinstance.go | 158 ++++++++++++------------- sdk/edgeconnect/v2/appinstance_test.go | 4 +- sdk/edgeconnect/v2/apps.go | 74 +----------- sdk/edgeconnect/v2/types.go | 147 ++++++++++++----------- 12 files changed, 250 insertions(+), 306 deletions(-) diff --git a/.gitignore b/.gitignore index c08c1df..dec973c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ dist/ ### direnv ### .direnv .envrc + +edge-connect-client diff --git a/Makefile b/Makefile index 496876e..a8695c5 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ clean: # Lint the code lint: - golangci-lint run + go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.2 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 f43e933..9bce91f 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 33a809e..797a411 100644 --- a/internal/apply/v2/planner.go +++ b/internal/apply/v2/planner.go @@ -343,8 +343,7 @@ 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 17ea3a7..6af0a68 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 536399f..f7299c2 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 8dfa6b0..225c5ef 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/types.go b/sdk/edgeconnect/types.go index 7fd39fc..307ed52 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 013d053..eda3467 100644 --- a/sdk/edgeconnect/v2/appinstance.go +++ b/sdk/edgeconnect/v2/appinstance.go @@ -10,8 +10,7 @@ import ( "fmt" "io" "net/http" - - sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/v2/sdk/internal/http" + "strings" ) // CreateAppInstance creates a new application instance in the specified region @@ -34,8 +33,7 @@ func (c *Client) CreateAppInstance(ctx context.Context, input *NewAppInstanceInp } // Parse streaming JSON response - var appInstances []AppInstance - if err := c.parseStreamingAppInstanceResponse(resp, &appInstances); err != nil { + if _, err = parseStreamingResponse[AppInstance](resp); err != nil { return fmt.Errorf("ShowAppInstance failed to parse response: %w", err) } @@ -75,7 +73,7 @@ func (c *Client) ShowAppInstance(ctx context.Context, appInstKey AppInstanceKey, // Parse streaming JSON response var appInstances []AppInstance - if err := c.parseStreamingAppInstanceResponse(resp, &appInstances); err != nil { + if appInstances, err = parseStreamingResponse[AppInstance](resp); err != nil { return AppInstance{}, fmt.Errorf("ShowAppInstance failed to parse response: %w", err) } @@ -110,12 +108,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 appInstances, nil // Return empty slice for not found + return []AppInstance{}, nil // Return empty slice for not found } - if err := c.parseStreamingAppInstanceResponse(resp, &appInstances); err != nil { + var appInstances []AppInstance + if appInstances, err = parseStreamingResponse[AppInstance](resp); err != nil { return nil, fmt.Errorf("ShowAppInstances failed to parse response: %w", err) } @@ -207,88 +205,90 @@ func (c *Client) DeleteAppInstance(ctx context.Context, appInstKey AppInstanceKe } // parseStreamingAppInstanceResponse parses the EdgeXR streaming JSON response format for app instances -func (c *Client) parseStreamingAppInstanceResponse(resp *http.Response, result interface{}) error { +func parseStreamingResponse[T Message](resp *http.Response) ([]T, error) { bodyBytes, err := io.ReadAll(resp.Body) if err != nil { - return fmt.Errorf("failed to read response body: %w", err) + return []T{}, 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 *[]AppInstance: - var appInstances []AppInstance - if err := json.Unmarshal(bodyBytes, &appInstances); err == nil { - *v = appInstances - return nil - } + // 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) } - // Fall back to streaming format (v1 API format) - var appInstances []AppInstance - var messages []string - var hasError bool - var errorCode int - var errorMessage string - - 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 nil + if isV2 { + resultV2, err := parseStreamingResponseV2[T](resp.StatusCode, bodyBytes) + if err != nil { + return []T{}, err } - - // Try parsing as Response[AppInstance] - var response Response[AppInstance] - if err := json.Unmarshal(line, &response); err != nil { - return err - } - - if response.HasData() { - appInstances = append(appInstances, response.Data) - } - 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 parseErr != nil { - return parseErr + return resultV2, nil } - // 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 + resultV1, err := parseStreamingResponseV1[T](resp.StatusCode, bodyBytes) + if err != nil { + return nil, err } - // Set result based on type - switch v := result.(type) { - case *[]AppInstance: - *v = appInstances - default: - return fmt.Errorf("unsupported result type: %T", result) + if !resultV1.IsSuccessful() { + return []T{}, resultV1.Error() } - return nil + 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 + + decoder := json.NewDecoder(bytes.NewReader(bodyBytes)) + for { + var d Response[T] + if err := decoder.Decode(&d); err != nil { + if err.Error() == "EOF" { + break + } + return Responses[T]{}, fmt.Errorf("error in parsing json object into Message: %w", err) + } + + if d.Result.Message != "" && d.Result.Code != 0 { + responses.StatusCode = d.Result.Code + } + + if strings.Contains(d.Data.GetMessage(), "CreateError") { + responses.Errors = append(responses.Errors, fmt.Errorf("server responded with: %s", "CreateError")) + } + + if strings.Contains(d.Data.GetMessage(), "UpdateError") { + responses.Errors = append(responses.Errors, fmt.Errorf("server responded with: %s", "UpdateError")) + } + + 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) + } + + return responses, nil +} + +func isV2Response(bodyBytes []byte) (bool, error) { + if len(bodyBytes) == 0 { + return false, fmt.Errorf("malformatted response body") + } + + return bodyBytes[0] == '[', nil +} + +func parseStreamingResponseV2[T Message](statusCode int, bodyBytes []byte) ([]T, error) { + var result []T + // Try parsing as a direct JSON array first (v2 API format) + if err := json.Unmarshal(bodyBytes, &result); err == nil { + return result, fmt.Errorf("failed to read response body: %w", err) + } + + return result, nil } diff --git a/sdk/edgeconnect/v2/appinstance_test.go b/sdk/edgeconnect/v2/appinstance_test.go index bf4db81..04df669 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 80c3981..61c1f4c 100644 --- a/sdk/edgeconnect/v2/apps.go +++ b/sdk/edgeconnect/v2/apps.go @@ -4,9 +4,7 @@ package v2 import ( - "bytes" "context" - "encoding/json" "fmt" "io" "net/http" @@ -73,7 +71,7 @@ func (c *Client) ShowApp(ctx context.Context, appKey AppKey, region string) (App // Parse streaming JSON response var apps []App - if err := c.parseStreamingResponse(resp, &apps); err != nil { + if apps, err = parseStreamingResponse[App](resp); err != nil { return App{}, fmt.Errorf("ShowApp failed to parse response: %w", err) } @@ -108,12 +106,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 apps, nil // Return empty slice for not found + return []App{}, nil // Return empty slice for not found } - if err := c.parseStreamingResponse(resp, &apps); err != nil { + var apps []App + if apps, err = parseStreamingResponse[App](resp); err != nil { return nil, fmt.Errorf("ShowApps failed to parse response: %w", err) } @@ -175,70 +173,6 @@ 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 0bb6875..7dea92e 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,7 +291,8 @@ type DeleteAppInstanceInput struct { // Response wraps a single API response type Response[T Message] struct { - Data T `json:"data"` + ResultResponse `json:",inline"` + Data T `json:"data"` } func (res *Response[T]) HasData() bool { @@ -326,6 +327,7 @@ 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 { @@ -344,12 +346,15 @@ 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 r.StatusCode >= 200 && r.StatusCode < 400 + return len(r.Errors) == 0 && (r.StatusCode >= 200 && r.StatusCode < 400) } func (r *Responses[T]) Error() error { @@ -410,3 +415,7 @@ type CloudletResourceUsage struct { Region string `json:"region"` Usage map[string]interface{} `json:"usage"` } + +type ErrorMessage struct { + Message string +} From 02856be5412c8016b882bc63bd0daae1adf5c7f4 Mon Sep 17 00:00:00 2001 From: Patrick Sy Date: Mon, 17 Nov 2025 15:43:08 +0100 Subject: [PATCH 40/40] fix: Fixed error handling --- sdk/edgeconnect/v2/appinstance.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sdk/edgeconnect/v2/appinstance.go b/sdk/edgeconnect/v2/appinstance.go index eda3467..52dcf1f 100644 --- a/sdk/edgeconnect/v2/appinstance.go +++ b/sdk/edgeconnect/v2/appinstance.go @@ -285,8 +285,7 @@ func isV2Response(bodyBytes []byte) (bool, error) { func parseStreamingResponseV2[T Message](statusCode int, bodyBytes []byte) ([]T, error) { var result []T - // Try parsing as a direct JSON array first (v2 API format) - if err := json.Unmarshal(bodyBytes, &result); err == nil { + if err := json.Unmarshal(bodyBytes, &result); err != nil { return result, fmt.Errorf("failed to read response body: %w", err) }