Skip to content
This repository was archived by the owner on Sep 9, 2026. It is now read-only.

OSAC-1551: Add tenant scoping to the CLI - #810

Closed
DakCrowder wants to merge 1 commit into
osac-project:mainfrom
DakCrowder:OSAC-1551-tenant-scoped-cli
Closed

DakCrowder wants to merge 1 commit into
osac-project:mainfrom
DakCrowder:OSAC-1551-tenant-scoped-cli

Conversation

@DakCrowder

@DakCrowder DakCrowder commented Jun 30, 2026 •

Copy link
Copy Markdown
Contributor

Add multi-tenant support to the osac CLI, enabling users to scope operations to a specific tenant via a saved setting or a per-command flag.

Core changes:

  • Add osac tenant command to view, set, and clear the active tenant (persisted in the OS keyring alongside credentials)
  • Add global --tenant flag with precedence over the saved setting
  • Add TENANT column into CLI display tables when no tenant is selected and the resource is tenant-scoped

Summary by CodeRabbit

  • New Features

    • Added a tenant selector in the CLI to scope operations and save a default tenant for future commands.
    • Added tenant management commands and messages for viewing, setting, clearing, and resolving the current tenant.
    • Tenant-aware creation and list views now better reflect the selected tenant, including clearer table output when no tenant is set.
  • Bug Fixes

    • Objects created from the CLI now consistently carry tenant information where applicable, improving tenant-scoped resource handling.

Add multi-tenant support to the osac CLI, enabling users to scope
operations to a specific tenant via a saved setting or a per-command
flag.

Core changes:
- Add `osac tenant` command to view, set, and clear the active tenant
  (persisted in the OS keyring alongside credentials)
- Add global `--tenant` flag with precedence over the saved setting
- Add TENANT column into CLI display tables when no tenant is selected and the resource is tenant-scoped

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Dakota Crowder <dcrowder@redhat.com>
@openshift-ci-robot

openshift-ci-robot commented Jun 30, 2026 •

Copy link
Copy Markdown

@DakCrowder: This pull request references OSAC-1551 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the epic to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Add multi-tenant support to the osac CLI, enabling users to scope operations to a specific tenant via a saved setting or a per-command flag.

Core changes:

  • Add osac tenant command to view, set, and clear the active tenant (persisted in the OS keyring alongside credentials)
  • Add global --tenant flag with precedence over the saved setting
  • Add TENANT column into CLI display tables when no tenant is selected and the resource is tenant-scoped

Assisted-by: Claude Code noreply@anthropic.com

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci

openshift-ci Bot commented Jun 30, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: DakCrowder
Once this PR has been reviewed and has the lgtm label, please assign tzvatot for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jun 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds tenant scoping across the CLI: new tenant settings persistence and context helpers in internal/config, a new tenant cobra subcommand with --clear/--output flags, a --tenant persistent root flag, tenant filter injection in the reflection helper's List, a TENANT column in table rendering for unscoped contexts, and tenant metadata propagation in all create commands.

Changes

Tenant Scoping for CLI

Layer / File(s) Summary
Tenant context and settings primitives
internal/config/config_context.go, internal/config/config_settings.go, internal/config/config_context_test.go, internal/config/config_settings_test.go
Adds contextTenantKey, TenantFromContext/TenantIntoContext helpers, secretSettings.Tenant field, and Settings.Tenant/SetTenant accessors with round-trip tests.
Reflection helper tenant scoping
internal/reflection/reflection_helper.go, internal/reflection/reflection_metadata.go, internal/reflection/reflection_helper_test.go
Extends ObjectHelper and Metadata interfaces with SetTenant/GetTenant/IsTenantScoped; adds tenantScopedTypes map; injects tenant predicate into List filter; fixes Lookup to return copy pointer.
Regenerated mocks
internal/reflection/reflection_helper_mock.go, internal/reflection/reflection_object_helper_mock.go
Regenerates MockObjectHelper with new tenant methods, migrates proto.Message → protoreflect.ProtoMessage, removes isgomock fields.
New tenant CLI subcommand and root wiring
internal/cmd/cli/tenant/tenant_cmd.go, internal/cmd/cli/tenant/templates/*, internal/cmd/cli/tenant/tenant_cmd_test.go, internal/cmd/cli/tenant/tenant_suite_test.go, internal/cmd/cli/root_cmd.go
Implements tenant cobra command (show/set/clear), embeds response templates, registers --tenant persistent flag on root, and injects effective tenant into command context in persistentPreRun.
Tenant metadata injection in create commands
internal/cmd/cli/create/create_cmd.go, internal/cmd/cli/create/cluster/..., internal/cmd/cli/create/computeinstance/..., internal/cmd/cli/create/publicip/..., internal/cmd/cli/create/securitygroup/..., internal/cmd/cli/create/subnet/..., internal/cmd/cli/create/virtualnetwork/...
Generic create loop calls SetTenant for tenant-scoped types; individual create commands populate Metadata.Tenant from context.
Table renderer tenant column
internal/rendering/table_renderer.go, internal/rendering/table_renderer_test.go, internal/rendering/table_renderer_compute_instance_test.go
Injects a TENANT column for tenant-scoped types when no tenant is active in context; uses empty-tenant context for name lookups; updates tests with IsTenantScoped mock expectations.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant RootCmd
  participant TenantCmd
  participant Settings
  participant ReflectionHelper
  participant gRPC

  User->>RootCmd: run --tenant foo <subcommand>
  RootCmd->>Settings: Tenant()
  Settings-->>RootCmd: saved tenant
  RootCmd->>RootCmd: override with --tenant flag if changed
  RootCmd->>RootCmd: TenantIntoContext(ctx, effective tenant)

  User->>TenantCmd: tenant set NAME
  TenantCmd->>gRPC: connect
  TenantCmd->>ReflectionHelper: Lookup("tenant")
  ReflectionHelper-->>TenantCmd: objectHelper
  TenantCmd->>ReflectionHelper: FindObject(ctx, NAME)
  ReflectionHelper-->>TenantCmd: resolved tenant proto
  TenantCmd->>Settings: SetTenant(NAME) + Save()
  TenantCmd-->>User: "Tenant 'NAME' saved."
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • trewest
  • akshaynadkarni

Poem

A tenant was lost in the context's abyss,
No flag, no save, no column to miss.
Now --tenant foo sets the scope with a grin,
The TENANT column joins tables within.
🏢 Saved, cleared, injected — all tidy and neat!


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
No-Weak-Crypto ❌ Error settingsTokenStore.Save compares AccessToken and RefreshToken with !=, which is a non-constant-time secret/token comparison. Use a constant-time compare (e.g. subtle.ConstantTimeCompare on token bytes) or avoid comparing secret values unless necessary.
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding tenant scoping support across the CLI.
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.
No-Hardcoded-Secrets ✅ Passed No hardcoded secrets, embedded credentials, or long opaque literals were added; searches found no string literals assigned to secret-like identifiers in the touched files.
No-Injection-Vectors ✅ Passed Changed code only threads tenant values through context/proto fields; no sql/shell/eval/pickle/yaml-load or innerHTML sinks appear in the modified paths.
Container-Privileges ✅ Passed PASS: Changed files are CLI/config/template code only; no container manifests or privileged/hostNetwork/allowPrivilegeEscalation/SYS_ADMIN settings were present.
No-Sensitive-Data-In-Logs ✅ Passed No new sensitive data is logged; secret-store debug dumps use !settings, and the logger redacts those fields by default.
Ai-Attribution ✅ Passed PR and HEAD commit include Assisted-by: Claude Code; no Co-Authored-By AI trailer was found.
✨ 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.

@DakCrowder

Copy link
Copy Markdown
Contributor Author

/hold

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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 `@internal/cmd/cli/tenant/templates/no_tenant.txt`:
- Around line 5-9: The tenant help template contains an outdated example
command, since `{{ binary }} get tenants` is not valid for this CLI flow. Update
the example text in the no-tenant template used by the tenant command help to
show the correct positional usage, such as `{{ binary }} tenant` or `{{ binary
}} tenant my-tenant`, and keep the surrounding help copy aligned with the
`tenant` command behavior.

In `@internal/cmd/cli/tenant/tenant_cmd_test.go`:
- Around line 41-44: The spec named “Looks up tenant resource type directly” in
tenant_cmd_test.go is a no-op because it only creates a runnerContext and
discards it without asserting anything. Update this test to actually exercise
the tenant lookup behavior using runnerContext and add real expectations against
the lookup path, or remove the It block entirely if there is no testable
behavior yet. Make sure the fix is tied to the runnerContext setup in this test
so the case provides meaningful coverage instead of always passing.

In `@internal/cmd/cli/tenant/tenant_cmd.go`:
- Around line 105-126: showCurrentTenant is reading the persisted tenant from
settings instead of the effective tenant already resolved by root command
argument handling, so --tenant overrides are ignored. Update showCurrentTenant
to use the tenant value from the command context (the one populated by the
root_cmd path) as the source of truth, and only fall back to settings if that
context value is missing; keep the existing no_tenant/current_tenant/findTenant
rendering flow intact.
- Around line 75-102: In runnerContext.run, add upfront validation for
conflicting tenant command inputs instead of letting the current branching
silently drop them: reject cases where c.args.clear is set together with any
positional NAME, reject more than one positional argument, and reject --output
when a NAME is provided so it is only accepted in the no-arg path. Keep the
existing showCurrentTenant and setTenant flow, but make the command fail fast
with a clear error before calling settings.SetTenant, c.console.Render, or
c.setTenant when the invocation is ambiguous.

In `@internal/reflection/reflection_helper.go`:
- Around line 785-828: Normalize the alignment of the tenantScopedTypes map in
reflection_helper.go so golangci-lint/gofmt stops failing; the map literal in
the tenantScopedTypes declaration has misaligned boolean values, so reformat
that block with gofmt (or by editing the entries around tenantScopedTypes) to
make the true columns consistently aligned across all osac.public.v1 and
osac.private.v1 types.
🪄 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: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 686c4d85-fe00-4b26-a9ec-91a727121de9

📥 Commits

Reviewing files that changed from the base of the PR and between 892bf39 and 2fb12c9.

📒 Files selected for processing (29)
  • internal/cmd/cli/create/cluster/create_cluster_cmd.go
  • internal/cmd/cli/create/computeinstance/create_compute_instance_cmd.go
  • internal/cmd/cli/create/create_cmd.go
  • internal/cmd/cli/create/publicip/create_publicip_cmd.go
  • internal/cmd/cli/create/securitygroup/create_securitygroup_cmd.go
  • internal/cmd/cli/create/subnet/create_subnet_cmd.go
  • internal/cmd/cli/create/virtualnetwork/create_virtualnetwork_cmd.go
  • internal/cmd/cli/root_cmd.go
  • internal/cmd/cli/tenant/templates/current_tenant.txt
  • internal/cmd/cli/tenant/templates/multiple_matches.txt
  • internal/cmd/cli/tenant/templates/no_matches.txt
  • internal/cmd/cli/tenant/templates/no_tenant.txt
  • internal/cmd/cli/tenant/templates/tenant_cleared.txt
  • internal/cmd/cli/tenant/templates/tenant_set.txt
  • internal/cmd/cli/tenant/tenant_cmd.go
  • internal/cmd/cli/tenant/tenant_cmd_test.go
  • internal/cmd/cli/tenant/tenant_suite_test.go
  • internal/config/config_context.go
  • internal/config/config_context_test.go
  • internal/config/config_settings.go
  • internal/config/config_settings_test.go
  • internal/reflection/reflection_helper.go
  • internal/reflection/reflection_helper_mock.go
  • internal/reflection/reflection_helper_test.go
  • internal/reflection/reflection_metadata.go
  • internal/reflection/reflection_object_helper_mock.go
  • internal/rendering/table_renderer.go
  • internal/rendering/table_renderer_compute_instance_test.go
  • internal/rendering/table_renderer_test.go

Comment on lines +5 to +9
{{ binary }} get tenants

To set a tenant:

{{ binary }} tenant <name>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find tenant command Use string and registered resources to confirm help text.
fd -t f 'tenant_cmd.go' internal/cmd/cli | xargs rg -n 'Use:|set-tenant|--clear'
rg -n 'Use:\s*"tenants?' internal/cmd/cli

Repository: osac-project/fulfillment-service

Length of output: 350


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map tenant-related CLI command definitions and help templates.
ast-grep outline internal/cmd/cli/tenant/tenant_cmd.go --view expanded || true
rg -n 'get tenants|tenant --clear|tenant \[FLAG\.\.\.\] \[NAME\]|Use:' internal/cmd/cli -g '!**/*_test.go'
fd -t f . internal/cmd/cli | sed -n '1,200p'

Repository: osac-project/fulfillment-service

Length of output: 15372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' internal/cmd/cli/get/get_cmd.go
printf '\n--- tenant cmd ---\n'
sed -n '1,280p' internal/cmd/cli/tenant/tenant_cmd.go

Repository: osac-project/fulfillment-service

Length of output: 11872


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n '\bget tenants\b|\bget tenant\b|tenant templates/no_(tenant|matches)\.txt|Lookup\("tenant"\)|OBJECT \[ID\|NAME\]' internal/cmd/cli docs README.md

Repository: osac-project/fulfillment-service

Length of output: 505


Replace the stale tenant help example. {{ binary }} get tenants is not a valid command here; the command takes an optional positional name, so the example should use {{ binary }} tenant or {{ binary }} tenant my-tenant instead.

🤖 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 `@internal/cmd/cli/tenant/templates/no_tenant.txt` around lines 5 - 9, The
tenant help template contains an outdated example command, since `{{ binary }}
get tenants` is not valid for this CLI flow. Update the example text in the
no-tenant template used by the tenant command help to show the correct
positional usage, such as `{{ binary }} tenant` or `{{ binary }} tenant
my-tenant`, and keep the surrounding help copy aligned with the `tenant` command
behavior.

Comment on lines +41 to +44
It("Looks up tenant resource type directly", func() {
runner := &runnerContext{}
_ = runner
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

This spec asserts nothing — it's a green light wired to no bulb.

Looks up tenant resource type directly builds a runnerContext and immediately discards it with _ = runner. There's no Expect, so it passes unconditionally and gives false coverage for tenant lookup. Either flesh it out to actually exercise the lookup path or remove it until you can.

🤖 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 `@internal/cmd/cli/tenant/tenant_cmd_test.go` around lines 41 - 44, The spec
named “Looks up tenant resource type directly” in tenant_cmd_test.go is a no-op
because it only creates a runnerContext and discards it without asserting
anything. Update this test to actually exercise the tenant lookup behavior using
runnerContext and add real expectations against the lookup path, or remove the
It block entirely if there is no testable behavior yet. Make sure the fix is
tied to the runnerContext setup in this test so the case provides meaningful
coverage instead of always passing.

Comment on lines +75 to +102
func (c *runnerContext) run(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

c.logger = logging.LoggerFromContext(ctx)
c.console = terminal.ConsoleFromContext(ctx)

err := c.console.AddTemplates(templatesFS, "templates")
if err != nil {
return fmt.Errorf("failed to load templates: %w", err)
}

settings := config.SettingsFromContext(ctx)

if c.args.clear {
settings.SetTenant("")
err = settings.Save(ctx)
if err != nil {
return fmt.Errorf("failed to save settings: %w", err)
}
c.console.Render(ctx, "tenant_cleared.txt", nil)
return nil
}

if len(args) == 0 {
return c.showCurrentTenant(cmd, settings)
}

return c.setTenant(cmd, settings, args[0])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject conflicting tenant invocations instead of silently ignoring inputs.

This path currently clears on --clear even if a NAME is also passed, ignores positional args after args[0], and accepts --output with NAME even though that flag is only honored in the no-arg branch. These combinations should fail fast; otherwise the CLI can do something different from what the user asked for.

🤖 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 `@internal/cmd/cli/tenant/tenant_cmd.go` around lines 75 - 102, In
runnerContext.run, add upfront validation for conflicting tenant command inputs
instead of letting the current branching silently drop them: reject cases where
c.args.clear is set together with any positional NAME, reject more than one
positional argument, and reject --output when a NAME is provided so it is only
accepted in the no-arg path. Keep the existing showCurrentTenant and setTenant
flow, but make the command fail fast with a clear error before calling
settings.SetTenant, c.console.Render, or c.setTenant when the invocation is
ambiguous.

Comment on lines +105 to +126
func (c *runnerContext) showCurrentTenant(cmd *cobra.Command, settings *config.Settings) error {
ctx := cmd.Context()
tenant := settings.Tenant()

if tenant == "" {
c.console.Render(ctx, "no_tenant.txt", nil)
return nil
}

if c.args.format == "" {
c.console.Render(ctx, "current_tenant.txt", map[string]any{
"Tenant": tenant,
})
return nil
}

object, err := c.findTenant(cmd, settings, tenant)
if err != nil {
return err
}

return c.renderObject(cmd, object)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Honor --tenant precedence when showing the current tenant.

Line 107 reads the persisted tenant from settings instead of the effective tenant that internal/cmd/cli/root_cmd.go:209-223 already resolved into the command context. osac --tenant other tenant therefore still shows/serializes the saved tenant, which breaks the advertised override behavior.

🤖 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 `@internal/cmd/cli/tenant/tenant_cmd.go` around lines 105 - 126,
showCurrentTenant is reading the persisted tenant from settings instead of the
effective tenant already resolved by root command argument handling, so --tenant
overrides are ignored. Update showCurrentTenant to use the tenant value from the
command context (the one populated by the root_cmd path) as the source of truth,
and only fall back to settings if that context value is missing; keep the
existing no_tenant/current_tenant/findTenant rendering flow intact.

Comment thread internal/reflection/reflection_helper.go
@DakCrowder DakCrowder closed this Jun 30, 2026

This branch was previously deployed

1 inactive deployment
e2e-test — 2fb12c96 Deployed Jun 30, 2026 by DakCrowder via e2e-vmaas-full-install / e2e #47
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants