Skip to content

Improve Devportal CLI commands#2192

Draft
ShakyaPr wants to merge 2 commits into
wso2:mainfrom
ShakyaPr:impr/devportal_cli
Draft

Improve Devportal CLI commands#2192
ShakyaPr wants to merge 2 commits into
wso2:mainfrom
ShakyaPr:impr/devportal_cli

Conversation

@ShakyaPr

Copy link
Copy Markdown
Contributor

Purpose

  • This PR updates the devportal endpoint invocation urls, as the devportal openapi spec was changed recently through the PR Improve Devportal OpenAPI Spec  #2117
  • Add the Integration tests for Devportal related CLI commands
  • Add step to run devportal it when perform a release to ensure the new release doesn't break the existing behavior

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary

This PR updates the DevPortal CLI commands to align with recent OpenAPI specification changes and introduces comprehensive integration tests for DevPortal functionality in the CLI release pipeline.

Endpoint Path Updates

All DevPortal CLI commands have been refactored to use a new endpoint URL scheme. The old /devportal/organizations/{org}/... path pattern is replaced with /o/{org}/devportal/v1/... across all DevPortal resource operations:

  • API management: Organization listing/get/add/edit/delete operations
  • REST APIs: Publishing, listing, retrieving, and deleting APIs
  • API Keys: Generating, listing, regenerating, and revoking API keys
  • Subscriptions: Creating, editing, retrieving, and deleting subscriptions
  • Subscription Plans: Publishing and managing subscription policies
  • Applications: Creating, listing, retrieving, updating, and deleting applications

A new centralized OrgScopedPath() helper function in cli/src/internal/devportal/helpers.go standardizes the construction of these versioned organization-scoped paths across all commands, removing redundant path-formatting logic and improving maintainability.

Integration Test Framework

Comprehensive Gherkin-based integration tests have been added for DevPortal functionality:

  • New feature specifications cover DevPortal management, organizations, APIs, API keys, subscriptions, applications, and end-to-end publish flows
  • Test infrastructure now supports multiple suite types (gateway and devportal) controllable via the IT_SUITES environment variable
  • New DevPortalSteps helper provides step implementations for configuring DevPortal, scaffolding API projects, publishing, and managing API artifacts
  • Test configuration extends to define DevPortal infrastructure requirements and per-command test enablement via cli/it/test-config.yaml

Test Infrastructure Updates

The CLI integration test framework has been enhanced to orchestrate both gateway and DevPortal infrastructure:

  • New make test-devportal target builds and runs DevPortal-specific integration tests
  • New build-devportal-image target builds the DevPortal test Docker image
  • CheckPortsAvailable() now accepts a list of required infrastructure to validate only relevant ports
  • Setup infrastructure now conditionally manages DevPortal Docker Compose stack alongside gateway infrastructure
  • Health checking for DevPortal added via dedicated endpoints

Release Pipeline Integration

The release workflow now enforces integration test execution:

  • New integration-tests job builds required infrastructure and runs the full CLI integration suite (both gateway and DevPortal)
  • The release job depends on successful completion of integration-tests, preventing releases that fail integration tests
  • Codecov workflow updated to build DevPortal test image before running integration tests

Documentation

Updated CLI integration test README documents the DevPortal test suite, infrastructure requirements, authentication details, and the ordered end-to-end publish flow steps.

Walkthrough

The PR makes two coordinated changes. First, it introduces an OrgScopedPath helper function and APIVersion constant in the internal devportal package, then migrates all DevPortal CLI command subpackages (org, restapi, apikey, application, subplan, subscription) from inline fmt.Sprintf/url.PathEscape path construction to this centralized helper. Organization endpoints move to /organizations/... while org-scoped resource endpoints move to /o/{org}/devportal/v1/.... All unit test URL path assertions are updated accordingly.

Second, a complete DevPortal IT suite is added: new Docker Compose override, InfraDevPortal infrastructure support in setup.go, suite-selection via IT_SUITES, DevPortalSteps step implementation, Gherkin feature files for management/org/api/apikey/application/subscription/e2e scenarios, test fixture YAML resources, test-devportal and build-devportal-image Makefile targets, and a gated integration-tests CI job that must succeed before release.

Suggested Reviewers

  • renuka-fernando
  • RakhithaRR
  • Tharsanan1
  • VirajSalaka
  • malinthaprasan
  • AnuGayan
  • chamilaadhi
  • Arshardh
  • tgtshanika
  • CrowleyRajapakse
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description addresses the core purpose and objectives but omits several required sections from the template (Goals, Approach, User stories, Documentation, Automation tests, Security checks, Samples, Related PRs, and Test environment). Complete the PR description by filling in all required template sections: Goals, Approach, User stories, Documentation, Automation tests details, Security checks, Samples, Related PRs, and Test environment information.
Docstring Coverage ⚠️ Warning Docstring coverage is 23.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Improve Devportal CLI commands' is vague and generic. It does not clearly convey the specific nature of the changes, which include endpoint URL updates, integration tests, and release workflow modifications. Consider a more specific title such as 'Update devportal CLI endpoint URLs and add integration tests' to better reflect the primary changes in the PR.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (1)
cli/src/cmd/devportal/apikey/get.go (1)

96-97: ⚡ Quick win

Keep query parameters outside OrgScopedPath for clearer helper contracts.

Passing the query string inside the resource argument works, but separating path and query keeps OrgScopedPath path-only and reduces future coupling.

Suggested change
- path := internaldevportal.OrgScopedPath(orgID, "api-keys?apiId="+url.QueryEscape(apiID))
- resp, err := client.Get(path)
+ path := internaldevportal.OrgScopedPath(orgID, "api-keys")
+ resp, err := client.Get(path + "?apiId=" + url.QueryEscape(apiID))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/cmd/devportal/apikey/get.go` around lines 96 - 97, The query
parameter is being passed inside the resource argument to OrgScopedPath, mixing
path and query concerns. Separate these by calling OrgScopedPath with only the
path portion ("api-keys"), then append the query string separately when
constructing the argument to client.Get(). This keeps OrgScopedPath focused on
path construction and maintains a clearer helper contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/cli-release.yml:
- Around line 9-24: The integration-tests job in the workflow needs to be
hardened for security. Replace the version tag references in the uses field for
each action (actions/checkout@v4, actions/setup-go@v5, and
docker/setup-buildx-action@v3) with pinned commit SHAs instead. Add an explicit
permissions block at the job level specifying only the minimum required
permissions needed for this integration test job to function. Additionally, add
persist-credentials: false to the Checkout code step unless push access is
explicitly required in this job.
- Around line 36-39: The release workflow step "Run CLI integration tests
(gateway + devportal)" is currently executing only the devportal test suite via
the make test-devportal command, which excludes gateway scenario tests. To
properly gate the release on full integration coverage, change the make command
from test-devportal to the appropriate full-suite integration test target that
includes both gateway and devportal scenarios.

In `@cli/it/config.go`:
- Around line 49-52: Add validation to ensure that only known suite names from a
defined list of valid suites are accepted when parsing the IT_SUITES environment
variable. In the loop around lines 49-52 where values are split and normalized,
check each name against the set of valid suites before adding it to the selected
map. Apply the same validation logic at lines 67-69 to handle the feature paths
consistently. This prevents unknown values from being stored in selected and
ensures that if only invalid suite names are provided, both the test selection
and feature paths handling behave consistently without causing a suite/infra
mismatch at runtime.

In `@cli/it/README.md`:
- Around line 96-99: The documentation in the README.md file incorrectly states
that the subscription is created on the `Gold` plan, but the actual test
implementation uses the `it-platinum` plan instead. Update the step 8
description to replace the reference to `Gold` with `it-platinum` to accurately
reflect the actual test scenario flow.

In `@cli/it/setup.go`:
- Around line 500-509: The preflight port check in the `if
needed[InfraMCPServer]` block only adds the MCPServerPort but does not include
the gateway ports (GatewayControllerPort, "8080" for Router HTTP, and "18000"
for xDS) that are also started as part of the MCP infrastructure setup. Modify
the `if needed[InfraMCPServer]` condition to append all three gateway ports
(GatewayControllerPort, "8080", and "18000") in addition to the MCPServerPort
when checking for available ports, ensuring the preflight check catches any port
conflicts from the complete gateway stack that runs in MCP scenarios.

In `@cli/it/steps/devportal_steps.go`:
- Around line 54-71: The EnsureDevPortalConfigured method is not idempotent
because it fails whenever the devportal add command returns a non-zero exit
code. Since the method name suggests it should ensure the configuration exists
(not fail if it already exists), you need to check whether the non-zero exit is
due to the configuration already existing (which should be acceptable) versus a
real error. Modify the error handling logic after checking if
s.state.GetExitCode() != 0 to inspect the error message or output to determine
if this is an "already exists" error; if so, continue with calling
SetCurrentDevPortal rather than returning an error. Only return an error for
actual failures that prevent the devportal from being available.
- Around line 219-223: The issue is that filepath.Base(resourcePath) on line 219
discards the directory components of the resourcePath parameter, passing only
the filename to resources.GetDevPortalResourcePath(). To honor the resourcePath
parameter and allow callers to provide explicit file paths with directory
context, pass the full resourcePath directly to
resources.GetDevPortalResourcePath() instead of using filepath.Base() to extract
only the base filename.

In `@cli/src/cmd/devportal/subscription/edit.go`:
- Line 100: The runEditCommand function in edit.go uses editOrgID directly
without trimming whitespace or validation before passing it to OrgScopedPath,
allowing whitespace-only input to bypass validation and create an invalid
request path. Trim the editOrgID value using strings.TrimSpace and add proper
validation to ensure it is not empty before constructing the path with
OrgScopedPath, matching the validation pattern used in other subscription
commands in the same package.

---

Nitpick comments:
In `@cli/src/cmd/devportal/apikey/get.go`:
- Around line 96-97: The query parameter is being passed inside the resource
argument to OrgScopedPath, mixing path and query concerns. Separate these by
calling OrgScopedPath with only the path portion ("api-keys"), then append the
query string separately when constructing the argument to client.Get(). This
keeps OrgScopedPath focused on path construction and maintains a clearer helper
contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e95b0017-082c-4019-a4e2-e8c93f90c5a6

📥 Commits

Reviewing files that changed from the base of the PR and between 5e92c07 and b4f6ab3.

📒 Files selected for processing (56)
  • .github/workflows/cli-release.yml
  • .github/workflows/codecov.yml
  • cli/it/Makefile
  • cli/it/README.md
  • cli/it/config.go
  • cli/it/docker-compose.devportal.override.yaml
  • cli/it/features/devportal/api.feature
  • cli/it/features/devportal/apikey.feature
  • cli/it/features/devportal/application.feature
  • cli/it/features/devportal/e2e.feature
  • cli/it/features/devportal/management.feature
  • cli/it/features/devportal/organization.feature
  • cli/it/features/devportal/subscription.feature
  • cli/it/resources/devportal/echo-definition.yaml
  • cli/it/resources/devportal/echo-devportal.yaml
  • cli/it/resources/devportal/organization.yaml
  • cli/it/resources/devportal/subscription-plan.yaml
  • cli/it/resources/values.go
  • cli/it/setup.go
  • cli/it/state.go
  • cli/it/steps/cli_steps.go
  • cli/it/steps/devportal_steps.go
  • cli/it/suite_test.go
  • cli/it/test-config.yaml
  • cli/src/cmd/devportal/apikey/commands_test.go
  • cli/src/cmd/devportal/apikey/generate.go
  • cli/src/cmd/devportal/apikey/get.go
  • cli/src/cmd/devportal/apikey/regenerate.go
  • cli/src/cmd/devportal/apikey/revoke.go
  • cli/src/cmd/devportal/application/commands_test.go
  • cli/src/cmd/devportal/application/create.go
  • cli/src/cmd/devportal/application/delete.go
  • cli/src/cmd/devportal/application/get.go
  • cli/src/cmd/devportal/application/update.go
  • cli/src/cmd/devportal/org/add.go
  • cli/src/cmd/devportal/org/commands_test.go
  • cli/src/cmd/devportal/org/delete.go
  • cli/src/cmd/devportal/org/edit.go
  • cli/src/cmd/devportal/org/get.go
  • cli/src/cmd/devportal/org/list.go
  • cli/src/cmd/devportal/restapi/commands_test.go
  • cli/src/cmd/devportal/restapi/delete.go
  • cli/src/cmd/devportal/restapi/edit.go
  • cli/src/cmd/devportal/restapi/get.go
  • cli/src/cmd/devportal/restapi/list.go
  • cli/src/cmd/devportal/restapi/publish.go
  • cli/src/cmd/devportal/subplan/commands_test.go
  • cli/src/cmd/devportal/subplan/delete.go
  • cli/src/cmd/devportal/subplan/get.go
  • cli/src/cmd/devportal/subplan/publish.go
  • cli/src/cmd/devportal/subscription/commands_test.go
  • cli/src/cmd/devportal/subscription/create.go
  • cli/src/cmd/devportal/subscription/delete.go
  • cli/src/cmd/devportal/subscription/edit.go
  • cli/src/cmd/devportal/subscription/get.go
  • cli/src/internal/devportal/helpers.go

Comment on lines +9 to +24
integration-tests:
name: CLI Integration Tests
runs-on: ubuntu-24.04
timeout-minutes: 120
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.26.2'
cache-dependency-path: '**/go.sum'

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Harden the new workflow job with pinned actions and explicit minimal permissions.

For the newly added job, pin uses: references to commit SHAs and set explicit least-privilege permissions. Also set persist-credentials: false on checkout unless push access is required in this job.

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 14-15: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[warning] 9-39: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)


[error] 15-15: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 18-18: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 24-24: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cli-release.yml around lines 9 - 24, The integration-tests
job in the workflow needs to be hardened for security. Replace the version tag
references in the uses field for each action (actions/checkout@v4,
actions/setup-go@v5, and docker/setup-buildx-action@v3) with pinned commit SHAs
instead. Add an explicit permissions block at the job level specifying only the
minimum required permissions needed for this integration test job to function.
Additionally, add persist-credentials: false to the Checkout code step unless
push access is explicitly required in this job.

Source: Linters/SAST tools

Comment on lines +36 to +39
- name: Run CLI integration tests (gateway + devportal)
run: |
cd cli/it
make test-devportal

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The release gate is currently running only the devportal suite, not the full integration suite.

This step calls make test-devportal, which excludes gateway scenarios. If the goal is to gate on full integration coverage, switch this to the full-suite target.

Suggested fix
-      - name: Run CLI integration tests (gateway + devportal)
+      - name: Run CLI integration tests (gateway + devportal)
         run: |
           cd cli/it
-          make test-devportal
+          make test-all
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Run CLI integration tests (gateway + devportal)
run: |
cd cli/it
make test-devportal
- name: Run CLI integration tests (gateway + devportal)
run: |
cd cli/it
make test-all
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 9-39: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cli-release.yml around lines 36 - 39, The release workflow
step "Run CLI integration tests (gateway + devportal)" is currently executing
only the devportal test suite via the make test-devportal command, which
excludes gateway scenario tests. To properly gate the release on full
integration coverage, change the make command from test-devportal to the
appropriate full-suite integration test target that includes both gateway and
devportal scenarios.

Comment thread cli/it/config.go
Comment on lines +49 to +52
for _, part := range strings.Split(raw, ",") {
if name := strings.ToLower(strings.TrimSpace(part)); name != "" {
selected[name] = true
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate IT_SUITES values against known suites and handle invalid-only input consistently.

Unknown values currently propagate from SelectedSuites(), which can make enabled-test selection empty while FeaturePaths() still falls back to "features". That creates a suite/infra mismatch at runtime.

Suggested fix
 func SelectedSuites() map[string]bool {
 	raw := strings.TrimSpace(os.Getenv(EnvSuites))
 	selected := map[string]bool{}
+	valid := map[string]bool{
+		SuiteGateway:   true,
+		SuiteDevPortal: true,
+	}
 	if raw == "" {
 		selected[SuiteGateway] = true
 		selected[SuiteDevPortal] = true
 		return selected
 	}
 	for _, part := range strings.Split(raw, ",") {
-		if name := strings.ToLower(strings.TrimSpace(part)); name != "" {
+		if name := strings.ToLower(strings.TrimSpace(part)); valid[name] {
 			selected[name] = true
 		}
 	}
+	if len(selected) == 0 {
+		selected[SuiteGateway] = true
+		selected[SuiteDevPortal] = true
+	}
 	return selected
 }

Also applies to: 67-69

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/it/config.go` around lines 49 - 52, Add validation to ensure that only
known suite names from a defined list of valid suites are accepted when parsing
the IT_SUITES environment variable. In the loop around lines 49-52 where values
are split and normalized, check each name against the set of valid suites before
adding it to the selected map. Apply the same validation logic at lines 67-69 to
handle the feature paths consistently. This prevents unknown values from being
stored in selected and ensures that if only invalid suite names are provided,
both the test selection and feature paths handling behave consistently without
causing a suite/infra mismatch at runtime.

Comment thread cli/it/README.md
Comment on lines +96 to +99
7. `ap devportal sub-plan publish` creates a new subscription plan
(`resources/devportal/subscription-plan.yaml`).
8. `ap devportal subscription create` subscribes to the API on the `Gold` plan
(the API carries the seeded `Gold`/`Bronze` policies).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align the documented E2E subscription plan with the implemented scenario.

The scenario flow publishes subscription-plan.yaml and then subscribes with it-platinum, but this section still says the subscription is created on Gold. Please update this to match the actual test flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/it/README.md` around lines 96 - 99, The documentation in the README.md
file incorrectly states that the subscription is created on the `Gold` plan, but
the actual test implementation uses the `it-platinum` plan instead. Update the
step 8 description to replace the reference to `Gold` with `it-platinum` to
accurately reflect the actual test scenario flow.

Comment thread cli/it/setup.go
Comment on lines +500 to +509
if needed[InfraGateway] {
ports = append(ports,
GatewayControllerPort,
"8080", // Router HTTP
"18000", // xDS
)
}
if needed[InfraMCPServer] {
ports = append(ports, MCPServerPort)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Include gateway ports in preflight when InfraMCPServer is requested.

SetupInfrastructure() starts the gateway stack for MCP scenarios, but CheckPortsAvailable() currently checks only MCP port for that case. This can miss required gateway port conflicts during preflight.

Suggested fix
 func CheckPortsAvailable(required []InfrastructureID) error {
 	needed := make(map[InfrastructureID]bool)
 	for _, id := range required {
 		needed[id] = true
 	}
+	if needed[InfraMCPServer] {
+		needed[InfraGateway] = true
+	}

 	var ports []string
 	if needed[InfraGateway] {
 		ports = append(ports,
 			GatewayControllerPort,
 			"8080",  // Router HTTP
 			"18000", // xDS
 		)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/it/setup.go` around lines 500 - 509, The preflight port check in the `if
needed[InfraMCPServer]` block only adds the MCPServerPort but does not include
the gateway ports (GatewayControllerPort, "8080" for Router HTTP, and "18000"
for xDS) that are also started as part of the MCP infrastructure setup. Modify
the `if needed[InfraMCPServer]` condition to append all three gateway ports
(GatewayControllerPort, "8080", and "18000") in addition to the MCPServerPort
when checking for available ports, ensuring the preflight check catches any port
conflicts from the complete gateway stack that runs in MCP scenarios.

Comment on lines +54 to +71
func (s *DevPortalSteps) EnsureDevPortalConfigured(name string) error {
err := s.state.ExecuteCLI(
"devportal", "add",
"--display-name", name,
"--server", resources.DevPortalURL,
"--auth", resources.DevPortalAuthType,
"--api-key", resources.DevPortalAPIKey,
"--no-interactive",
)
if err != nil {
return err
}
if s.state.GetExitCode() != 0 {
return fmt.Errorf("failed to add devportal %q: %s", name, s.state.GetCombinedOutput())
}

return s.SetCurrentDevPortal(name)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make EnsureDevPortalConfigured idempotent.

On Line 66, any non-zero exit from devportal add fails the step. If the config already exists, this makes a method named “Ensure…” non-repeatable and can break reruns.

Suggested fix
 func (s *DevPortalSteps) EnsureDevPortalConfigured(name string) error {
 	err := s.state.ExecuteCLI(
 		"devportal", "add",
 		"--display-name", name,
 		"--server", resources.DevPortalURL,
 		"--auth", resources.DevPortalAuthType,
 		"--api-key", resources.DevPortalAPIKey,
 		"--no-interactive",
 	)
 	if err != nil {
 		return err
 	}
 	if s.state.GetExitCode() != 0 {
-		return fmt.Errorf("failed to add devportal %q: %s", name, s.state.GetCombinedOutput())
+		out := s.state.GetCombinedOutput()
+		if !strings.Contains(out, "already exists") {
+			return fmt.Errorf("failed to add devportal %q: %s", name, out)
+		}
 	}
 
 	return s.SetCurrentDevPortal(name)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (s *DevPortalSteps) EnsureDevPortalConfigured(name string) error {
err := s.state.ExecuteCLI(
"devportal", "add",
"--display-name", name,
"--server", resources.DevPortalURL,
"--auth", resources.DevPortalAuthType,
"--api-key", resources.DevPortalAPIKey,
"--no-interactive",
)
if err != nil {
return err
}
if s.state.GetExitCode() != 0 {
return fmt.Errorf("failed to add devportal %q: %s", name, s.state.GetCombinedOutput())
}
return s.SetCurrentDevPortal(name)
}
func (s *DevPortalSteps) EnsureDevPortalConfigured(name string) error {
err := s.state.ExecuteCLI(
"devportal", "add",
"--display-name", name,
"--server", resources.DevPortalURL,
"--auth", resources.DevPortalAuthType,
"--api-key", resources.DevPortalAPIKey,
"--no-interactive",
)
if err != nil {
return err
}
if s.state.GetExitCode() != 0 {
out := s.state.GetCombinedOutput()
if !strings.Contains(out, "already exists") {
return fmt.Errorf("failed to add devportal %q: %s", name, out)
}
}
return s.SetCurrentDevPortal(name)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/it/steps/devportal_steps.go` around lines 54 - 71, The
EnsureDevPortalConfigured method is not idempotent because it fails whenever the
devportal add command returns a non-zero exit code. Since the method name
suggests it should ensure the configuration exists (not fail if it already
exists), you need to check whether the non-zero exit is due to the configuration
already existing (which should be acceptable) versus a real error. Modify the
error handling logic after checking if s.state.GetExitCode() != 0 to inspect the
error message or output to determine if this is an "already exists" error; if
so, continue with calling SetCurrentDevPortal rather than returning an error.
Only return an error for actual failures that prevent the devportal from being
available.

Comment on lines +219 to +223
planPath := resources.GetDevPortalResourcePath(filepath.Base(resourcePath))
return s.runOK("sub-plan publish",
"devportal", "sub-plan", "publish", "-f", planPath, "--org", s.orgID,
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Honor the resourcePath parameter without dropping directory context.

On Line 219, filepath.Base(resourcePath) discards directories, so callers cannot reliably provide an explicit file path.

Suggested fix
 func (s *DevPortalSteps) PublishSubscriptionPlan(resourcePath string) error {
 	if s.orgID == "" {
 		return fmt.Errorf("no target organization; run the target organization step first")
 	}
-	planPath := resources.GetDevPortalResourcePath(filepath.Base(resourcePath))
+	planPath := resourcePath
+	if !filepath.IsAbs(resourcePath) && strings.HasPrefix(resourcePath, "resources/devportal/") {
+		rel := strings.TrimPrefix(resourcePath, "resources/devportal/")
+		planPath = resources.GetDevPortalResourcePath(rel)
+	}
 	return s.runOK("sub-plan publish",
 		"devportal", "sub-plan", "publish", "-f", planPath, "--org", s.orgID,
 	)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/it/steps/devportal_steps.go` around lines 219 - 223, The issue is that
filepath.Base(resourcePath) on line 219 discards the directory components of the
resourcePath parameter, passing only the filename to
resources.GetDevPortalResourcePath(). To honor the resourcePath parameter and
allow callers to provide explicit file paths with directory context, pass the
full resourcePath directly to resources.GetDevPortalResourcePath() instead of
using filepath.Base() to extract only the base filename.


client := internaldevportal.NewClientWithOptions(devPortal, editInsecure)
path := fmt.Sprintf("/devportal/organizations/%s/subscriptions/%s", url.PathEscape(editOrgID), url.PathEscape(subscriptionID))
path := internaldevportal.OrgScopedPath(editOrgID, "subscriptions/"+url.PathEscape(subscriptionID))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate and normalize organization ID before building the route.

runEditCommand uses editOrgID directly for OrgScopedPath, so whitespace-only input can bypass clear validation and produce an invalid request path. Align with the other subscription commands by trimming and validating org ID first.

Suggested fix
 func runEditCommand() error {
+	orgID := strings.TrimSpace(editOrgID)
+	if orgID == "" {
+		return fmt.Errorf("organization ID is required")
+	}
+
 	subscriptionID := strings.TrimSpace(editSubscription)
 	if subscriptionID == "" {
 		return fmt.Errorf("subscription ID is required")
 	}
@@
-	path := internaldevportal.OrgScopedPath(editOrgID, "subscriptions/"+url.PathEscape(subscriptionID))
+	path := internaldevportal.OrgScopedPath(orgID, "subscriptions/"+url.PathEscape(subscriptionID))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
path := internaldevportal.OrgScopedPath(editOrgID, "subscriptions/"+url.PathEscape(subscriptionID))
func runEditCommand() error {
orgID := strings.TrimSpace(editOrgID)
if orgID == "" {
return fmt.Errorf("organization ID is required")
}
subscriptionID := strings.TrimSpace(editSubscription)
if subscriptionID == "" {
return fmt.Errorf("subscription ID is required")
}
path := internaldevportal.OrgScopedPath(orgID, "subscriptions/"+url.PathEscape(subscriptionID))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/src/cmd/devportal/subscription/edit.go` at line 100, The runEditCommand
function in edit.go uses editOrgID directly without trimming whitespace or
validation before passing it to OrgScopedPath, allowing whitespace-only input to
bypass validation and create an invalid request path. Trim the editOrgID value
using strings.TrimSpace and add proper validation to ensure it is not empty
before constructing the path with OrgScopedPath, matching the validation pattern
used in other subscription commands in the same package.

@ShakyaPr ShakyaPr marked this pull request as draft June 17, 2026 04:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant