ROSAENG-63260 | refactor: Create reusable request/response conventions - #3452
ROSAENG-63260 | refactor: Create reusable request/response conventions#3452markirish wants to merge 1 commit into
Conversation
|
Skipping CI for Draft Pull Request. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: markirish The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
📝 WalkthroughWalkthroughThe change documents workflow 🚥 Pre-merge checks | ✅ 13 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (13 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
pkg/iamserviceaccount/client.go (1)
40-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the unused producer-side interface abstraction.
IAMServiceAccountServicehas only one implementation and no production caller uses it. Move this to a concreteServiceinpkg/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
📒 Files selected for processing (6)
AGENTS.mdguidelines/ARCHITECTURE.mdguidelines/workflow-conventions.mdpkg/iamserviceaccount/client.gopkg/iamserviceaccount/create.gopkg/iamserviceaccount/create_test.go
| 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 |
There was a problem hiding this comment.
🗄️ 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.
| 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.
- 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
b13666e to
79b1c0a
Compare
|
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. |
|
|
||
| // Validate checks that the request contains all required fields and that | ||
| // service account names and namespaces are syntactically valid. | ||
| func (r *CreateIAMServiceAccountRequest) Validate() error { |
There was a problem hiding this comment.
@coderabbitai Does CreateIAMServiceAccountRequest.Validate() cover the same cases the CLI currently checks (e.g. ARN format validation is still CLI-side today)?
There was a problem hiding this comment.
🧩 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/iamserviceaccountLength 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.
There was a problem hiding this comment.
Something to keep in mind once the command call this.
There was a problem hiding this comment.
@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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
🧩 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 goLength 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.
There was a problem hiding this comment.
@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 |
There was a problem hiding this comment.
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{} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
🧩 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 500Length 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
RequestandResult. - 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
IAMServiceAccountServiceby default. - Consider a package-level
CreateIAMServiceAccountfunction instead ofServicewhile the workflow has no state or injected dependencies. - Introduce a shared
Serviceonly 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.
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
@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.
| // 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 == "" { |
There was a problem hiding this comment.
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...)
PR Summary
Detailed Description of the Issue
Related Issues and PRs
#Type of Change
Previous Behavior
Behavior After This Change
How to Test (Step-by-Step)
Preconditions
Test Steps
Expected Results
Proof of the Fix
Breaking Changes
Breaking Change Details / Migration Plan
Developer Verification Checklist
[JIRA-TICKET] | [TYPE]: <MESSAGE>.make install-hookshas been run in this clone.make testpasses.make lintpasses.make rosapasses.Summary by CodeRabbit
New Features
Documentation
Tests