Skip to content

feat: add studioctl apps search#18824

Open
martinothamar-agent wants to merge 7 commits into
Altinn:mainfrom
martinothamar-agent:feat/studioctl-apps-search
Open

feat: add studioctl apps search#18824
martinothamar-agent wants to merge 7 commits into
Altinn:mainfrom
martinothamar-agent:feat/studioctl-apps-search

Conversation

@martinothamar-agent
Copy link
Copy Markdown
Contributor

@martinothamar-agent martinothamar-agent commented May 19, 2026

Description

Adds studioctl apps search for finding app repositories visible to the authenticated user.

The CLI uses the existing stored Designer API key and calls the Designer API endpoint /designer/api/repos/search rather than calling the Gitea API directly. To support that, the Designer repository search endpoint now allows API-key authentication with [AllowApiKey].

Verification

  • Related issues are connected (if applicable)
  • Your code builds clean without any errors or warnings
  • Manual testing done (required)
  • Relevant automated test added (if you find this hard, leave it and we'll help out)

Automated checks run:

$ cd src/cli && make fmt && make test && make lint && make build && make user-install
"/data/home/code/altinn-studio/src/cli/bin/golangci-lint" fmt
go test -race -count=1 ./...
ok  	altinn.studio/studioctl/internal/cmd	1.185s
ok  	altinn.studio/studioctl/internal/cmd/apps	1.014s
ok  	altinn.studio/studioctl/internal/studio	1.011s
"/data/home/code/altinn-studio/src/cli/bin/golangci-lint" run ./...
0 issues.
go build -ldflags "-X altinn.studio/studioctl/internal/cmd.version=0.1.0-preview.0" -o build/studioctl ./cmd/studioctl
[ok] Installation completed
$ dotnet build src/Designer/backend/src/Designer/Designer.csproj --no-restore
Build succeeded.
    0 Warning(s)
    0 Error(s)

Manual testing against local Designer backend:

$ curl -fsS http://studio.localhost:5000/health
Healthy
$ /data/home/.local/bin/studioctl apps search --env local ttd
APP ID           DESCRIPTION
ttd/ttd-content  -
$ /data/home/.local/bin/studioctl apps search --env local --limit 1 --json ttd
{"query":"ttd","apps":[{"appId":"ttd/ttd-content","cloneUrl":"http://studio.localhost/repos/ttd/ttd-content.git","htmlUrl":"http://studio.localhost/repos/ttd/ttd-content"}],"totalCount":1,"totalPages":1,"page":1,"limit":1}

Note: node ./development/setup.js started the local Postgres/Gitea/fake-Ansattporten containers, but the full studio_designer Docker image build failed during frontend yarn --immutable due Puppeteer/Cypress postinstall build failures. I therefore ran the Designer backend from source against those local containers for the manual API/CLI test.

cc @martinothamar

Summary by CodeRabbit

Release Notes

  • New Features

    • Added apps search command to the CLI for discovering app repositories in Altinn Studio with filtering, sorting, and pagination options. Results can be displayed as a table or JSON.
  • Documentation

    • Updated CLI documentation and changelog to document the new search functionality.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Warning

Rate limit exceeded

@martinothamar-agent has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 34 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5103df30-f3ba-48ca-acff-7130f86eb136

📥 Commits

Reviewing files that changed from the base of the PR and between 07b02ae and 92ad979.

📒 Files selected for processing (1)
  • src/cli/internal/cmd/apps.go
📝 Walkthrough

Walkthrough

The pull request adds API-key authorisation for the Designer repository search endpoint and implements a complete CLI interface for searching app repositories. Changes span backend authorisation, HTTP client implementation, service abstraction, CLI command handling, and comprehensive test coverage with documentation.

Changes

App Repository Search Implementation

Layer / File(s) Summary
Backend API Key Auth for Search Endpoint
src/Designer/backend/src/Designer/Controllers/RepositoryController.cs, src/Designer/backend/tests/Designer.Tests/Controllers/RepositoryController/SearchAuthMetadataTests.cs, src/Designer/backend/tests/Designer.Tests/Services/RepositoryServiceTests.cs
RepositoryController.Search gains an [AllowApiKey] attribute (alongside the controller-level [Authorize]), overriding to permit API key access. New reflective test verifies the attribute is present. Existing test predicate tightened to match directories by exact basename rather than substring.
User-Facing Documentation
src/cli/CHANGELOG.md, src/cli/README.md
Announces new apps search command in changelog and documents it under core CLI commands.
Studio HTTP Client Search
src/cli/internal/studio/client.go, src/cli/internal/studio/client_test.go
Adds SearchAppsRequest and SearchAppsResult types for Designer API interaction. Client.SearchApps constructs GET to /designer/api/repos/search, conditionally adds query parameters, handles 401 as ErrUnauthorized, decodes JSON response. Tests verify request structure and authorisation error path.
App Service Layer
src/cli/internal/cmd/app/service.go
Defines SearchRequest (environment, query, pagination, sorting) and SearchResult (repositories, totals). Service.Search loads credentials, creates an environment-scoped studio client, calls SearchApps, maps "not logged in" errors, and returns populated results.
CLI Apps Command: Parsing, Validation, and Output
src/cli/internal/cmd/apps.go
Introduces AppsCommand with search subcommand dispatcher. Flag parsing validates pagination (positive), sort field, and order (asc/desc). Maps auth errors with auth login --env ... guidance. Result mapper computes AppID from repository FullName, owner login, or name. Output renderer handles JSON and two-column table modes, including "no results" message.
CLI Apps Command Tests
src/cli/internal/cmd/apps_internal_test.go
Covers table output with mocked HTTP server validation, JSON unmarshalling with field assertions, missing-query error case, and test helper creating temporary config with credentials pointing to mocked Designer search endpoint.
CLI Root Registration and Help
src/cli/internal/cmd/root.go, src/cli/internal/cmd/root_test.go
Registers NewAppsCommand in CLI dispatcher and updates help text ordering. Help invocation test added to command discovery suite.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

The PR spans multiple layers (backend, HTTP client, service, CLI command, tests, docs) with mixed complexity. Backend changes are minimal and focused (two-line authorisation attribute addition). CLI implementation is substantial (281 lines in apps.go) with flag parsing, output formatting, and error handling, but follows established patterns. Client implementation is straightforward HTTP request/response handling. Test coverage is comprehensive and uses standard Go idioms. No logic density issues, but variety across file types and functional areas requires contextual understanding of each layer's role in the end-to-end feature.

Suggested reviewers

  • martinothamar

Poem

🐰 A search for apps, now swift and keen,
Through CLI queries, API screen,
With keys that auth, and flags that flow,
The repositories start to show!
Table and JSON, both take their place—
Discovery quickens the builder's pace. 🔍

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarises the main change: adding a new CLI command 'studioctl apps search' for app repository discovery.
Description check ✅ Passed The description follows the template structure, includes a clear explanation of changes, and comprehensively documents verification steps with evidence of manual testing and automated checks.
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

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.

@github-actions github-actions Bot added skip-releasenotes Issues that do not make sense to list in our release notes backend solution/studio/designer labels May 19, 2026
@martinothamar
Copy link
Copy Markdown
Contributor

get a review from a subagent on this

@github-actions github-actions Bot added the quality/testing Tests that are missing, needs to be created or could be improved. label May 19, 2026
@martinothamar-agent
Copy link
Copy Markdown
Contributor Author

Subagent review completed.

Findings:

  • No blocking issues found.
  • Verified the CLI path, Designer endpoint change, JSON field mapping, and API-key auth flow.
  • Focused checks from the reviewer passed: go test ./internal/cmd ./internal/studio and git diff --check origin/main...HEAD.

Residual risk noted:

  • The main gap was backend regression coverage for RepositoryController.Search being part of the API-key surface.
  • ApiKeyScopeFilter allows any valid Designer API key for endpoints marked [AllowApiKey], which matches the current auth model but is worth keeping in mind from a product/security perspective.

Follow-up done:

  • Added SearchAuthMetadataTests.Search_AllowsApiKeyAuthentication to lock the [AllowApiKey] metadata on /designer/api/repos/search.
  • Ran: dotnet test src/Designer/backend/tests/Designer.Tests/Designer.Tests.csproj --no-restore --filter FullyQualifiedName~SearchAuthMetadataTests --logger "console;verbosity=normal".
  • Ran: dotnet build src/Designer/backend/src/Designer/Designer.csproj --no-restore.

Pushed follow-up commit: 7920077e67 test: cover repository search api key metadata.

@martinothamar-agent
Copy link
Copy Markdown
Contributor Author

Follow-up on the red Designer backend CI run:

  • The failed assertion was in RepositoryServiceTests.CopyRepository_TargetExistsLocally_InitialCloneMoved, outside the new studioctl apps search path.
  • The production code renames an existing local clone to *_SCHEDULED_FOR_DELETE_* and deletes it in a background task before cloning the replacement. The test counted any directory containing the target repo name, so it could see both the replacement clone and the transient scheduled-delete directory.
  • Updated the assertion to count only the exact target clone directory name.

Verification run locally:

dotnet test src/Designer/backend/tests/Designer.Tests/Designer.Tests.csproj --no-restore --filter FullyQualifiedName~RepositoryServiceTests.CopyRepository_TargetExistsLocally_InitialCloneMoved --logger "console;verbosity=normal"
# Passed: 1

dotnet test src/Designer/backend/Designer.sln --filter "(Category!=GiteaIntegrationTest)&(Category!=StudioOidcGiteaIntegrationTest)&(Category!=DbIntegrationTest)&(FullyQualifiedName!~AppDevelopmentController)&(FullyQualifiedName!~PreviewController)&(FullyQualifiedName!~PolicyControllerTests)&(FullyQualifiedName!~DataModelsController)&(FullyQualifiedName!~ResourceAdminController)&(FullyQualifiedName!~TextController)&(FullyQualifiedName!~RepositoryController)" -v m --no-build
# Passed: 831, Skipped: 1

@martinothamar
Copy link
Copy Markdown
Contributor

Hmm we should discuss the design here. Because this kind of search just lists actual apps, if we want to support search through code, e.g. references to som .NET symbol for example across apps, where would we put that?

@martinothamar-agent
Copy link
Copy Markdown
Contributor Author

I agree this is worth separating conceptually. My read is:

  • studioctl apps search <query> should mean catalog/discovery search for app repositories, like "find apps whose owner/name/description matches this query". It returns app identities, not file matches.
  • Searching inside app source code is a different operation and probably should not reuse this command shape. I would expect something like studioctl apps grep <pattern> for a local-or-cloned-apps workflow, or studioctl code search <query> / studioctl repos search-code <query> if it is backed by a server-side index across repositories.

So I think the current command is still defensible if we treat apps as the plural resource, similar to kubectl noun-first commands. But if we want to reserve the word search for code/content search, then the safer alternative for this PR would be to rename this to something more catalog-specific, e.g. studioctl apps find <query> or studioctl apps list --search <query>.

My preference for this PR: keep apps search as app catalog search and document/describe it explicitly as "Search for apps in Altinn Studio". Future code search should get a separate command because the output shape, backend API, permissions, pagination, and likely local/remote behavior will be different.

@martinothamar
Copy link
Copy Markdown
Contributor

apps grep sounds OK actually so lets proceed with this. Also update cli changelog.md file since this is a user-facing change, use "Added" section

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/cli/internal/cmd/apps_internal_test.go`:
- Around line 23-36: The httptest handler currently calls t.Fatalf from its
goroutine (unsafe); instead create an error channel (e.g. errCh := make(chan
error, 1) before creating the server), replace each t.Fatalf inside the
http.HandlerFunc with sending an error (errCh <- fmt.Errorf(...)) and return,
and after the test finishes (after server.Close or after the request is made)
check the channel and call t.Fatalf or t.Error from the test goroutine if an
error was received; update references to appsSearchTestQuery and the handler
function accordingly and ensure the handler returns immediately after sending to
errCh to avoid further processing.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 133de500-dc10-44db-8847-c70e6f608d39

📥 Commits

Reviewing files that changed from the base of the PR and between 3f26985 and fc0fc49.

📒 Files selected for processing (12)
  • src/Designer/backend/src/Designer/Controllers/RepositoryController.cs
  • src/Designer/backend/tests/Designer.Tests/Controllers/RepositoryController/SearchAuthMetadataTests.cs
  • src/Designer/backend/tests/Designer.Tests/Services/RepositoryServiceTests.cs
  • src/cli/CHANGELOG.md
  • src/cli/README.md
  • src/cli/internal/cmd/app/service.go
  • src/cli/internal/cmd/apps.go
  • src/cli/internal/cmd/apps_internal_test.go
  • src/cli/internal/cmd/root.go
  • src/cli/internal/cmd/root_test.go
  • src/cli/internal/studio/client.go
  • src/cli/internal/studio/client_test.go

Comment thread src/cli/internal/cmd/apps_internal_test.go
@martinothamar-agent martinothamar-agent force-pushed the feat/studioctl-apps-search branch 3 times, most recently from 21e0eaa to f626768 Compare May 20, 2026 15:51
@martinothamar-agent martinothamar-agent force-pushed the feat/studioctl-apps-search branch from f626768 to 7b94be4 Compare May 20, 2026 15:59
@martinothamar-agent martinothamar-agent force-pushed the feat/studioctl-apps-search branch from 7b94be4 to 07b02ae Compare May 20, 2026 16:20
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/cli/internal/cmd/apps.go (1)

177-185: ⚡ Quick win

Normalise accepted --sort/--order values before sending the request.

Line 177 and Line 183 validate case-insensitively, but raw values are forwarded later. Lowercasing after validation avoids downstream case-sensitivity surprises.

Proposed patch
 	if !isSupportedAppsSearchSort(flags.sort) {
 		return flags, "", false, fmt.Errorf(
 			"%w: --sort must be one of alpha, created, updated, size, or id",
 			ErrInvalidFlagValue,
 		)
 	}
 	if !isSupportedAppsSearchOrder(flags.order) {
 		return flags, "", false, fmt.Errorf("%w: --order must be asc or desc", ErrInvalidFlagValue)
 	}
+	flags.sort = strings.ToLower(flags.sort)
+	flags.order = strings.ToLower(flags.order)
 
 	query := strings.TrimSpace(strings.Join(fs.Args(), " "))
 	return flags, query, false, nil
 }
🤖 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 `@src/cli/internal/cmd/apps.go` around lines 177 - 185, After validating
flags.sort with isSupportedAppsSearchSort and flags.order with
isSupportedAppsSearchOrder, normalize the accepted values by lowercasing
flags.sort and flags.order before they are forwarded in the request; update the
code around the validation block that reads flags.sort and flags.order (the
variables validated by isSupportedAppsSearchSort and isSupportedAppsSearchOrder)
so the values are reassigned to their lowercase equivalents (e.g., flags.sort =
strings.ToLower(flags.sort) and flags.order = strings.ToLower(flags.order))
immediately after the corresponding validations to prevent downstream
case-sensitivity issues.
🤖 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.

Nitpick comments:
In `@src/cli/internal/cmd/apps.go`:
- Around line 177-185: After validating flags.sort with
isSupportedAppsSearchSort and flags.order with isSupportedAppsSearchOrder,
normalize the accepted values by lowercasing flags.sort and flags.order before
they are forwarded in the request; update the code around the validation block
that reads flags.sort and flags.order (the variables validated by
isSupportedAppsSearchSort and isSupportedAppsSearchOrder) so the values are
reassigned to their lowercase equivalents (e.g., flags.sort =
strings.ToLower(flags.sort) and flags.order = strings.ToLower(flags.order))
immediately after the corresponding validations to prevent downstream
case-sensitivity issues.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 80e13b1e-3adf-46f7-837e-65654a210cd0

📥 Commits

Reviewing files that changed from the base of the PR and between f626768 and 07b02ae.

📒 Files selected for processing (12)
  • src/Designer/backend/src/Designer/Controllers/RepositoryController.cs
  • src/Designer/backend/tests/Designer.Tests/Controllers/RepositoryController/SearchAuthMetadataTests.cs
  • src/Designer/backend/tests/Designer.Tests/Services/RepositoryServiceTests.cs
  • src/cli/CHANGELOG.md
  • src/cli/README.md
  • src/cli/internal/cmd/app/service.go
  • src/cli/internal/cmd/apps.go
  • src/cli/internal/cmd/apps_internal_test.go
  • src/cli/internal/cmd/root.go
  • src/cli/internal/cmd/root_test.go
  • src/cli/internal/studio/client.go
  • src/cli/internal/studio/client_test.go
✅ Files skipped from review due to trivial changes (1)
  • src/cli/CHANGELOG.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend quality/testing Tests that are missing, needs to be created or could be improved. skip-releasenotes Issues that do not make sense to list in our release notes solution/studio/designer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants