Skip to content

ROSAENG-63260 | refactor: Create reusable request/response conventions - #3452

Open
markirish wants to merge 1 commit into
openshift:masterfrom
markirish:ROSAENG-63260-define-reusable-workflow-conventions
Open

ROSAENG-63260 | refactor: Create reusable request/response conventions#3452
markirish wants to merge 1 commit into
openshift:masterfrom
markirish:ROSAENG-63260-define-reusable-workflow-conventions

Conversation

@markirish

@markirish markirish commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

PR Summary

Detailed Description of the Issue

  • Create a document outlining how requests from CLI to core layers should be structured
  • Outline how responses from core layer to CLI should be structured
  • Implement a minimal request/response workflow when creating an IAMServiceAccount

Related Issues and PRs

Type of Change

  • feat - adds a new user-facing capability.
  • fix - resolves an incorrect behavior or bug.
  • docs - updates documentation only.
  • style - formatting or naming changes with no logic impact.
  • refactor - code restructuring with no behavior change.
  • test - adds or updates tests only.
  • chore - maintenance work (tooling, housekeeping, non-product code).
  • build - changes build system, packaging, or dependencies for build output.
  • ci - changes CI pipelines, jobs, or automation workflows.
  • perf - improves performance without changing intended behavior.

Previous Behavior

Behavior After This Change

How to Test (Step-by-Step)

Preconditions

Test Steps

Expected Results

Proof of the Fix

  • Screenshots:
  • Videos:
  • Logs/CLI output:
  • Other artifacts:

Breaking Changes

  • No breaking changes
  • Yes, this PR introduces a breaking change (describe impact and migration plan below)

Breaking Change Details / Migration Plan

Developer Verification Checklist

  • Commit subject/title follows [JIRA-TICKET] | [TYPE]: <MESSAGE>.
  • PR description clearly explains both what changed and why.
  • Relevant Jira/GitHub issues and related PRs are linked.
  • make install-hooks has been run in this clone.
  • Tests were added/updated where appropriate.
  • I manually tested the change.
  • make test passes.
  • make lint passes.
  • make rosa passes.
  • Documentation or repo-local agent guidance was added/updated where appropriate.
  • Any risk, limitation, or follow-up work is documented.

Summary by CodeRabbit

  • New Features

    • Added IAM service-account creation workflows with role generation, trust policies, managed and inline policies, permissions boundaries, paths, and GovCloud support.
    • Added validation and structured results for IAM service-account operations.
  • Documentation

    • Added workflow conventions covering request/result naming, validation, lifecycle boundaries, optional values, and domain types.
    • Updated architecture guidance and documentation indexes.
  • Tests

    • Added comprehensive coverage for successful creation, configuration options, GovCloud handling, validation, and error scenarios.

@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 6, 2026
@openshift-ci

openshift-ci Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci

openshift-ci Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: markirish
Once this PR has been reviewed and has the lgtm label, please assign jerichokeyne 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 Aug 6, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The change documents workflow Request and Result conventions and updates architecture references. It adds IAM service-account client and service interfaces, request validation, role creation, trust-policy generation, tag handling, policy attachment, GovCloud ARN handling, structured results, and contextual errors. Ginkgo tests cover successful creation, validation failures, client errors, policy options, and stopping after the first attachment failure.

🚥 Pre-merge checks | ✅ 13 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Test Structure And Quality ⚠️ Warning pkg/iamserviceaccount/create_test.go uses 85 Expect assertions, with 82 lacking meaningful failure messages; mock-only tests otherwise need no cluster waits or cleanup. Add diagnostic messages to the assertions, especially error checks and recorded-call checks, such as the operation and relevant role or policy.
Description check ⚠️ Warning The description includes a basic scope summary but leaves the issue context, behavior changes, test instructions, evidence, breaking-change status, and verification checklist incomplete. Complete the required sections with the problem and rationale, behavior before and after, reproducible test steps and results, proof, breaking-change status, issue links, and verification checklist.
✅ Passed checks (13 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Stable And Deterministic Test Names ✅ Passed The added Ginkgo titles are 28 static string literals. They contain no generated identifiers, timestamps, UUIDs, node or namespace values, IPs, or interpolated expressions.
Microshift Test Compatibility ✅ Passed The only new Ginkgo test is pkg/iamserviceaccount/create_test.go, a unit test with a mock IAM client; no tests were added under tests/e2e or OpenShift/MicroShift APIs.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The added Ginkgo tests are unit tests in pkg/iamserviceaccount, using a local IAM mock; they do not access clusters or assume nodes, topology, scheduling, or HA.
Topology-Aware Scheduling Compatibility ✅ Passed The diff adds documentation and IAM service-account workflow code only; it changes no manifests, operators, controllers, replicas, affinities, tolerations, selectors, or disruption budgets.
Ote Binary Stdout Contract ✅ Passed Changed Go files add no main/init/TestMain or suite-setup stdout writes; fmt usage only constructs errors/ARNs, and the new tests use Describe/BeforeEach without output calls.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed The only added Ginkgo test is a package-level unit test using a local IAM mock; no e2e test, IP assumption, or external network operation was added.
No-Weak-Crypto ✅ Passed The added IAM workflow imports only context and fmt; scans of added lines and changed Go code found no MD5, SHA-1, DES, RC4, Blowfish, ECB, custom crypto, or secret comparisons.
Container-Privileges ✅ Passed The PR changes only documentation and Go IAM workflow files. No changed container or Kubernetes manifest contains privileged:true, host namespaces, SYS_ADMIN, root settings, or allowPrivilegeEscala...
No-Sensitive-Data-In-Logs ✅ Passed The PR adds no logging or print calls in the new workflow; it only returns wrapped errors and passes IAM policy data to the client.
Title check ✅ Passed The title clearly identifies the refactoring goal and matches the documented request/response conventions and IAM service-account workflow changes.
✨ 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.

@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: 3

🧹 Nitpick comments (1)
pkg/iamserviceaccount/client.go (1)

40-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused producer-side interface abstraction.

IAMServiceAccountService has only one implementation and no production caller uses it. Move this to a concrete Service in pkg/iamserviceaccount; if an external consumer needs an interface, define a minimal interface at that consumer boundary 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 `@pkg/iamserviceaccount/client.go` around lines 40 - 46, Remove the unused
IAMServiceAccountService interface and define the workflow on a concrete Service
type in pkg/iamserviceaccount, updating CreateIAMServiceAccount references and
construction accordingly. Preserve the existing CreateIAMServiceAccount
signature and behavior; any interface needed by external consumers should be
declared at their boundary.

Source: Coding guidelines

🤖 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 `@pkg/iamserviceaccount/create.go`:
- Around line 114-117: Update the request validation flow in the create
operation to check whether req is nil before calling req.Validate(), and return
an appropriate validation error instead of dereferencing it. Add a focused spec
covering a nil request passed through this call path, while preserving the
existing validation behavior for non-nil requests.
- Around line 141-150: Update the concrete IAM client methods EnsureRole,
AttachRolePolicy, and PutRolePolicy to accept ctx context.Context, then
propagate that context to every AWS SDK IAM request and paginator page call
instead of using context.Background() or context.TODO(). Update all callers,
including the service-account creation flow, to pass the existing caller context
through.
- Around line 51-83: Update CreateIAMServiceAccountRequest.Validate to reject
blank entries in PolicyARNs and validate that a non-empty InlinePolicy contains
valid JSON before returning success. Ensure these checks occur before role
creation through the existing validation path, and add specs verifying invalid
policy inputs fail without making IAM client calls.

---

Nitpick comments:
In `@pkg/iamserviceaccount/client.go`:
- Around line 40-46: Remove the unused IAMServiceAccountService interface and
define the workflow on a concrete Service type in pkg/iamserviceaccount,
updating CreateIAMServiceAccount references and construction accordingly.
Preserve the existing CreateIAMServiceAccount signature and behavior; any
interface needed by external consumers should be declared at their boundary.
🪄 Autofix

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 YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 09987a68-a0a5-4b33-87b5-779254be2e3f

📥 Commits

Reviewing files that changed from the base of the PR and between e82fa7d and b13666e.

📒 Files selected for processing (6)
  • AGENTS.md
  • guidelines/ARCHITECTURE.md
  • guidelines/workflow-conventions.md
  • pkg/iamserviceaccount/client.go
  • pkg/iamserviceaccount/create.go
  • pkg/iamserviceaccount/create_test.go

Comment on lines +51 to +83
func (r *CreateIAMServiceAccountRequest) Validate() error {
if r.ClusterName == "" {
return fmt.Errorf("cluster name is required")
}
if r.OIDCProviderARN == "" {
return fmt.Errorf("OIDC provider ARN is required")
}
if len(r.ServiceAccounts) == 0 {
return fmt.Errorf("at least one service account is required")
}
for _, sa := range r.ServiceAccounts {
if err := ValidateServiceAccountName(sa.Name); err != nil {
return fmt.Errorf("invalid service account name %q: %w", sa.Name, err)
}
if err := ValidateNamespaceName(sa.Namespace); err != nil {
return fmt.Errorf("invalid namespace %q for service account %q: %w", sa.Namespace, sa.Name, err)
}
}
if len(r.PolicyARNs) == 0 && r.InlinePolicy == "" {
return fmt.Errorf("at least one policy ARN or inline policy is required")
}
if r.RoleName == "" && len(r.ServiceAccounts) > 1 {
return fmt.Errorf("role name is required when specifying multiple service accounts")
}
if r.IsGovcloud {
if r.AccountID == "" {
return fmt.Errorf("account ID is required for GovCloud environments")
}
if r.Partition == "" {
return fmt.Errorf("partition is required for GovCloud environments")
}
}
return nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate each policy input before role creation.

PolicyARNs: []string{""} passes validation. A malformed non-empty InlinePolicy also passes validation. The workflow then calls EnsureRole before IAM rejects the policy input. This can leave a created or modified role after the command returns an error.

Reject blank policy ARN entries. Reject invalid inline-policy JSON before calling EnsureRole. Add specs that confirm these inputs make no IAM client calls.

Proposed validation change
 import (
 	"context"
+	"encoding/json"
 	"fmt"
+	"strings"
 )
 
-	if len(r.PolicyARNs) == 0 && r.InlinePolicy == "" {
+	if len(r.PolicyARNs) == 0 && strings.TrimSpace(r.InlinePolicy) == "" {
 		return fmt.Errorf("at least one policy ARN or inline policy is required")
 	}
+	for _, policyARN := range r.PolicyARNs {
+		if strings.TrimSpace(policyARN) == "" {
+			return fmt.Errorf("policy ARN cannot be empty")
+		}
+	}
+	if r.InlinePolicy != "" && !json.Valid([]byte(r.InlinePolicy)) {
+		return fmt.Errorf("inline policy must be valid JSON")
+	}
📝 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 (r *CreateIAMServiceAccountRequest) Validate() error {
if r.ClusterName == "" {
return fmt.Errorf("cluster name is required")
}
if r.OIDCProviderARN == "" {
return fmt.Errorf("OIDC provider ARN is required")
}
if len(r.ServiceAccounts) == 0 {
return fmt.Errorf("at least one service account is required")
}
for _, sa := range r.ServiceAccounts {
if err := ValidateServiceAccountName(sa.Name); err != nil {
return fmt.Errorf("invalid service account name %q: %w", sa.Name, err)
}
if err := ValidateNamespaceName(sa.Namespace); err != nil {
return fmt.Errorf("invalid namespace %q for service account %q: %w", sa.Namespace, sa.Name, err)
}
}
if len(r.PolicyARNs) == 0 && r.InlinePolicy == "" {
return fmt.Errorf("at least one policy ARN or inline policy is required")
}
if r.RoleName == "" && len(r.ServiceAccounts) > 1 {
return fmt.Errorf("role name is required when specifying multiple service accounts")
}
if r.IsGovcloud {
if r.AccountID == "" {
return fmt.Errorf("account ID is required for GovCloud environments")
}
if r.Partition == "" {
return fmt.Errorf("partition is required for GovCloud environments")
}
}
return nil
func (r *CreateIAMServiceAccountRequest) Validate() error {
if r.ClusterName == "" {
return fmt.Errorf("cluster name is required")
}
if r.OIDCProviderARN == "" {
return fmt.Errorf("OIDC provider ARN is required")
}
if len(r.ServiceAccounts) == 0 {
return fmt.Errorf("at least one service account is required")
}
for _, sa := range r.ServiceAccounts {
if err := ValidateServiceAccountName(sa.Name); err != nil {
return fmt.Errorf("invalid service account name %q: %w", sa.Name, err)
}
if err := ValidateNamespaceName(sa.Namespace); err != nil {
return fmt.Errorf("invalid namespace %q for service account %q: %w", sa.Namespace, sa.Name, err)
}
}
if len(r.PolicyARNs) == 0 && strings.TrimSpace(r.InlinePolicy) == "" {
return fmt.Errorf("at least one policy ARN or inline policy is required")
}
for _, policyARN := range r.PolicyARNs {
if strings.TrimSpace(policyARN) == "" {
return fmt.Errorf("policy ARN cannot be empty")
}
}
if r.InlinePolicy != "" && !json.Valid([]byte(r.InlinePolicy)) {
return fmt.Errorf("inline policy must be valid JSON")
}
if r.RoleName == "" && len(r.ServiceAccounts) > 1 {
return fmt.Errorf("role name is required when specifying multiple service accounts")
}
if r.IsGovcloud {
if r.AccountID == "" {
return fmt.Errorf("account ID is required for GovCloud environments")
}
if r.Partition == "" {
return fmt.Errorf("partition is required for GovCloud environments")
}
}
return 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 `@pkg/iamserviceaccount/create.go` around lines 51 - 83, Update
CreateIAMServiceAccountRequest.Validate to reject blank entries in PolicyARNs
and validate that a non-empty InlinePolicy contains valid JSON before returning
success. Ensure these checks occur before role creation through the existing
validation path, and add specs verifying invalid policy inputs fail without
making IAM client calls.

Comment thread pkg/iamserviceaccount/create.go
Comment thread pkg/iamserviceaccount/create.go
- Create a document outlining how requests from CLI to core layers should be structured
- Outline how responses from core layer to CLI should be structured
- Implement a minimal request/response workflow when creating an IAMServiceAccount
@markirish
markirish force-pushed the ROSAENG-63260-define-reusable-workflow-conventions branch from b13666e to 79b1c0a Compare August 6, 2026 16:15
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@markirish
markirish marked this pull request as ready for review August 14, 2026 12:23
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 14, 2026

// Validate checks that the request contains all required fields and that
// service account names and namespaces are syntactically valid.
func (r *CreateIAMServiceAccountRequest) Validate() error {

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.

@coderabbitai Does CreateIAMServiceAccountRequest.Validate() cover the same cases the CLI currently checks (e.g. ARN format validation is still CLI-side today)?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== IAM service account package outline =='
ast-grep outline pkg/iamserviceaccount --items all --type function,method,struct,interface 2>/dev/null || true

echo '== CreateIAMServiceAccountRequest.Validate implementation =='
rg -n -A170 -B8 'func \(r \*CreateIAMServiceAccountRequest\) Validate\(\) error' pkg/iamserviceaccount/create.go

echo '== Candidate CLI commands and validation references =='
rg -n -i -C5 \
  'iam.?service.?account|OIDCProviderARN|OIDC.*ARN|ParseARN|arn\.Parse|Validate.*ARN|IsARN' \
  --type go \
  -g '!pkg/iamserviceaccount/create.go' \
  .

echo '== Direct callers of IAM service-account creation =='
rg -n -C4 \
  'CreateIAMServiceAccount\s*\(|CreateIAMServiceAccountRequest|CreateIAMServiceAccountResult' \
  --type go \
  .

Length of output: 194676


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Current create command validation and request construction =='
sed -n '40,180p' cmd/create/iamserviceaccount/cmd.go

echo '== Core request validation =='
sed -n '45,100p' pkg/iamserviceaccount/create.go

echo '== IAM service-account option package outline =='
ast-grep outline pkg/options/iamserviceaccount --items all --type function,method,struct 2>/dev/null || true

echo '== IAM service-account option validation and flag binding =='
rg -n -A100 -B8 \
  'func .*Validate|func .*BuildIamServiceAccountCreateCommandWithOptions|ARNValidator|PermissionsBoundary|RoleName|InlinePolicy|Path' \
  pkg/options/iamserviceaccount

echo '== aws.ARNValidator definition and tests =='
rg -n -A80 -B8 'func ARNValidator|ARNValidator\s*=' pkg/aws
rg -n -A50 -B5 'ARNValidator' pkg/aws -g '*_test.go'

echo '== OIDC provider ARN validation in the create IAM service-account command =='
rg -n -C4 \
  'getOIDCProviderARN|OIDCProviderARN|arn\.Parse|arn\.IsARN|ARNValidator' \
  cmd/create/iamserviceaccount

Length of output: 48487


@amandahla, no. CreateIAMServiceAccountRequest.Validate() does not yet cover all current CLI validation.

The current CLI parses every managed policy ARN with aws.ARNValidator. Validate() only rejects empty PolicyARNs values. It accepts a non-empty malformed policy ARN. The request validation should retain that ARN syntax check.

The current CLI does not parse OIDCProviderARN. It resolves the provider from the cluster OIDC endpoint through AWS and rejects a missing provider. Validate() only checks that the resolved OIDC provider ARN is non-empty. Therefore, OIDC ARN format validation is not a current CLI parity case.

Validate() adds service-account and namespace syntax checks. These checks are stronger than the current create CLI runner.

The CLI must still validate STS cluster eligibility and resolve the OIDC provider. Those checks require the cluster and AWS client, which are outside the resolved request.


✏️ Learnings added
Learnt from: amandahla
Repo: openshift/rosa PR: 3452
File: pkg/iamserviceaccount/create.go:51-51
Timestamp: 2026-08-17T21:15:51.486Z
Learning: For `cmd/create/iamserviceaccount/cmd.go`, the create IAM service-account CLI validates each managed policy ARN with `pkg/aws.ARNValidator`, which uses `arn.Parse`. It resolves the OIDC provider ARN through AWS in `getOIDCProviderARN` but does not syntactically validate that ARN.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

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.

Something to keep in mind once the command call this.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@amandahla, agreed. When the command calls CreateIAMServiceAccount, it must preserve managed policy ARN validation. The command can keep pkg/aws.ARNValidator, or CreateIAMServiceAccountRequest.Validate() can perform equivalent validation.


🧠 Learnings used
Learnt from: amandahla
Repo: openshift/rosa PR: 3452
File: pkg/iamserviceaccount/create.go:51-51
Timestamp: 2026-08-17T21:15:51.486Z
Learning: For `cmd/create/iamserviceaccount/cmd.go`, the create IAM service-account CLI validates each managed policy ARN with `pkg/aws.ARNValidator`, which uses `arn.Parse`. It resolves the OIDC provider ARN through AWS in `getOIDCProviderARN` but does not syntactically validate that ARN.

You are interacting with an AI system.

// IAMServiceAccountClient defines the IAM operations needed by service
// account workflows.
//
// This is a narrow interface over the subset of AWS IAM operations that

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.

I didnt understand this comment.

Today we have

roleARN, err := r.AWSClient.EnsureRole(
			r.Reporter,
			roleName,
			trustPolicy,
			userOptions.PermissionsBoundary,
			"",
			tags,
			userOptions.Path,
			managedPolicies,
		)

The ideia is to change for iamServiceAccountclient.EnsureRole(ctx,roleName,...etc)?

@coderabbitai help me understand this comment + documentation

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

client_file=$(fd -t f '^client\.go$' pkg/iamserviceaccount | head -n 1)
create_file=$(fd -t f '^create\.go$' pkg/iamserviceaccount | head -n 1)

ast-grep outline "$client_file" --items all
ast-grep outline "$create_file" --items all

printf '\n--- IAM service account interface and workflow ---\n'
sed -n '1,260p' "$client_file"
sed -n '1,320p' "$create_file"

printf '\n--- Existing EnsureRole declarations and call sites ---\n'
rg -n -C 5 '\bEnsureRole\s*\(' --type go

Length of output: 10266


@amandahla, yes, the workflow will call:

client.EnsureRole(ctx, roleName, trustPolicy, permissionsBoundary, "", tags, path, false)

However, this does not mean that the existing r.AWSClient.EnsureRole method must change its signature.

The intended layers are:

CLI command
  -> builds CreateIAMServiceAccountRequest
  -> creates an IAMServiceAccountClient adapter around r.AWSClient and r.Reporter
  -> calls iamserviceaccount.Service.CreateIAMServiceAccount(ctx, adapter, request)

Core workflow
  -> calls adapter.EnsureRole(ctx, ...)

Adapter
  -> calls r.AWSClient.EnsureRole(r.Reporter, ...)

The adapter holds the CLI-specific dependencies, including r.Reporter. The core package only knows the small IAMServiceAccountClient interface and context.Context.

Conceptually, the adapter method is:

func (c *clientAdapter) EnsureRole(
	ctx context.Context,
	roleName string,
	trustPolicy string,
	permissionsBoundary string,
	version string,
	tags map[string]string,
	path string,
	managedPolicies bool,
) (string, error) {
	return c.awsClient.EnsureRole(
		c.reporter,
		roleName,
		trustPolicy,
		permissionsBoundary,
		version,
		tags,
		path,
		managedPolicies,
	)
}

ctx exists at the workflow boundary. The adapter can use it if the AWS client API later supports context. It does not need to pass it to the current AWSClient.EnsureRole method.

The purpose is separation of responsibilities:

  • The CLI owns Reporter, flags, prompts, and command output.
  • The core workflow owns validation, role-name generation, trust-policy generation, and IAM workflow order.
  • The adapter connects the core workflow to the current AWS client API.

One clarification for the documentation: pkg/iamserviceaccount/client.go currently describes this adapter, but this PR does not appear to add the adapter or migrate the existing CLI call site yet. The documentation should say that the CLI layer will provide the adapter when the command is migrated, unless that implementation is included in a later change.

You are interacting with an AI system.

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.

@markirish I think it would be nice clarify that.

display (table/JSON/YAML)
```

The **Request** and **Result** types are the boundary contract. The CLI layer

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.

Given that CLI must support two different runners/api (v1 and v2'Hyperfleet') where both can have same request but different fields/flags/validation, how could we define that?
The verb and resource is the same but behavior will be different.

}

// Service is the default implementation of IAMServiceAccountService.
type Service struct{}

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.

As we add list/describe/delete, do you see these living as methods on one Service, or as separate workflow functions in pkg/iamserviceaccount (create/list/delete each with their own Request/Result)?

I'm concerned about Service becoming a catch-all for every IAM SA operation. Narrow reader/writer interfaces per workflow might scale better.

@coderabbitai evaluate my review, my reference is https://refactoring.guru/design-patterns/facade

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- iamserviceaccount files ---'
fd --type f . pkg/iamserviceaccount | sort

printf '%s\n' '--- iamserviceaccount declarations ---'
ast-grep outline pkg/iamserviceaccount --items all --type class,function,interface --view expanded || true

printf '%s\n' '--- relevant implementation and interface ---'
for f in pkg/iamserviceaccount/client.go pkg/iamserviceaccount/create.go; do
  if [ -f "$f" ]; then
    printf '\n--- %s ---\n' "$f"
    cat -n "$f"
  fi
done

printf '%s\n' '--- comparable Service declarations and workflow signatures in pkg ---'
rg -n -C 3 --type go \
  'type Service struct|func \(s \*Service\) (Create|List|Describe|Delete)|type .*Request struct|type .*Result struct' \
  pkg | head -n 500

Length of output: 14933


@amandahla, your concern is valid. I would keep it as a non-blocking design recommendation for this PR.

Service is not currently a Facade. A Facade provides one simplified interface over subsystem dependencies. Here, Service has no state, and CreateIAMServiceAccount receives IAMServiceAccountClient from its caller. The client interface is the dependency boundary.

Line 40 in pkg/iamserviceaccount/client.go also creates IAMServiceAccountService before there is a second workflow. If list, describe, and delete are added as methods there, that interface can become a broad package-wide contract.

I recommend this direction:

  • Keep each workflow in its own file with its own Request and Result.
  • Define a narrow, consumer-side IAM client interface for each workflow when that workflow is added.
  • Do not add list, describe, or delete methods to IAMServiceAccountService by default.
  • Consider a package-level CreateIAMServiceAccount function instead of Service while the workflow has no state or injected dependencies.
  • Introduce a shared Service only if workflows need shared state, configuration, or coordination logic.

The current IAMServiceAccountClient is already create-specific. A future reader workflow should not need EnsureRole, AttachRolePolicy, or PutRolePolicy. A future delete workflow should not need read or create methods.

I would not require this PR to pre-design the future workflows. I would request that it avoids presenting IAMServiceAccountService as the long-term aggregate API.

You are interacting with an AI system.

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.

@coderabbitai I’m less worried about whether today’s Service is literally a Facade, and more about the direction as list/describe/delete move into pkg/: one type accumulating every IAM SA operation starts to look like an overgrown Facade / god object (refactoring.guru calls out facades growing too broad). I’d prefer we establish one workflow function + Request/Result per operation, and narrow client interfaces per workflow (reader vs writer), rather than growing IAMServiceAccountService. Not asking to solve that in this PR, mainly want alignment before the next extraction.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@amandahla, agreed. This is the right direction for future extractions.

Use one workflow entry point per operation, with operation-specific Request and Result types. Keep client interfaces narrow and owned by the workflow that consumes them.

Do not expand IAMServiceAccountService into a package-wide interface for create, list, describe, and delete. A list or describe workflow should depend on reader operations only. A create or delete workflow should depend on writer operations only.

Service can remain an implementation detail for the current create workflow. Introduce shared service state only when multiple workflows need shared configuration or coordination.

This aligns with the existing IAM service-account package guidance. No change is required in this PR.


🧠 Learnings used
Learnt from: amandahla
Repo: openshift/rosa PR: 3452
File: pkg/iamserviceaccount/create.go:51-51
Timestamp: 2026-08-17T21:48:44.211Z
Learning: For IAM service-account workflows under pkg/iamserviceaccount, use one workflow entry point with operation-specific Request and Result types. Define narrow, workflow-specific client interfaces—for example, reader interfaces for list or describe operations and writer interfaces for create or delete operations—instead of expanding IAMServiceAccountService into a broad interface that accumulates all operations.

You are interacting with an AI system.

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.

@markirish wdyt?

@openshift openshift deleted a comment from coderabbitai Bot Aug 17, 2026
// Validate checks that the request contains all required fields and that
// service account names and namespaces are syntactically valid.
func (r *CreateIAMServiceAccountRequest) Validate() error {
if r.ClusterName == "" {

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.

out of scope but wouldnt be amazing be able to replace this for something like

var errs []error
for _, validate := range createValidators {
    if err := validate(r); err != nil {
        errs = append(errs, err)
    }
}
return errors.Join(errs...)

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants