Skip to content

Conversation

@adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented Aug 22, 2025

Summary by CodeRabbit

  • New Features

    • Workspace-level variable resolution now aggregates deployment- and resource-scoped variables and returns resolved key→value maps.
    • Added support for deployment-scoped variables with resolution and priority/value types.
  • Refactor

    • Simplified variable handling and repository interfaces for consistent resolution flows.
    • Introduced pluggable variable managers for layered resolution (resource, deployment, workspace).
  • Tests

    • Removed legacy repository tests tied to the previous implementation.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 22, 2025

Walkthrough

Adds deployment- and resource-level variable models, in-memory repositories (skeleton implementations), and pluggable variable managers (workspace, deployment, resource) for resolution. Removes older model Variable/ResourceVariable interfaces and deletes the resource variable repository test suite.

Changes

Cohort / File(s) Summary
Deployment variable model
apps/workspace-engine/pkg/model/variable/deployment_variable.go
Adds DeploymentVariable, DirectDeploymentVariableValue, ReferenceDeploymentVariableValue types with getters and selector helpers; JSON tags and selector type checks implemented.
Deployment variable repository
apps/workspace-engine/pkg/engine/variable/deployment_variable_repository.go
Adds DeploymentVariable interface and DeploymentVariableRepository (thread-safe, in-memory) with constructor and CRUD-like methods; methods are skeletons (no persistent logic). Includes compile-time assertion to model.Repository.
Resource variable repository (refactor)
apps/workspace-engine/pkg/engine/variable/resource_variable_repository.go
Introduces ResourceVariable interface, changes internal storage to map[string]*ResourceVariable, updates signatures to use the interface, removes uniqueness helper; methods are skeletons (no-op implementations).
Variable managers and orchestration
apps/workspace-engine/pkg/engine/variable/manager_variable.go
apps/workspace-engine/pkg/engine/variable/manager_deployment_variable.go
apps/workspace-engine/pkg/engine/variable/manager_resource_variable.go
Adds VariableManager interface, WorkspaceVariableManager aggregator with ResolveDeploymentVariables, and concrete DeploymentVariableManager / ResourceVariableManager implementing Resolve by delegating to repositories and variable.Resolve. Error wrapping on failures included.
Model removals / API changes
apps/workspace-engine/pkg/model/variable/variable.go
apps/workspace-engine/pkg/model/variable/resource_variable.go
Removes the prior public Variable interface and the ResourceVariable interface (old definitions), and removes DirectResourceVariable.Resolve method; trims related imports.
Tests removed
apps/workspace-engine/pkg/engine/variable/resource_variable_repository_test.go
Deletes the entire test suite for ResourceVariableRepository (creation/update/delete, duplicates, error paths, nil handling).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant WVM as WorkspaceVariableManager
  participant DVM as DeploymentVariableManager
  participant RVM as ResourceVariableManager
  participant DRepo as DeploymentVariableRepository
  participant RRepo as ResourceVariableRepository
  Caller->>WVM: ResolveDeploymentVariables(ctx, resource, deploymentID)
  note right of WVM: obtain keys via provided getKeys()
  WVM->>WVM: for each key
  alt try Resource manager first
    WVM->>RVM: Resolve(ctx, resource, deploymentID, key)
    RVM->>RRepo: GetByResourceIDAndKey(resourceID, key)
    RRepo-->>RVM: *ResourceVariable
    RVM->>RVM: variable.Resolve(ctx)
    RVM-->>WVM: *string or nil / error
  else try Deployment manager
    WVM->>DVM: Resolve(ctx, resource, deploymentID, key)
    DVM->>DRepo: GetByDeploymentIDAndKey(deploymentID, key)
    DRepo-->>DVM: *DeploymentVariable
    DVM->>DVM: variable.Resolve(ctx, resource)
    DVM-->>WVM: *string or nil / error
  end
  alt resolved value
    WVM->>WVM: store result[key]=value
  else error
    WVM-->>Caller: wrapped error (includes key/resource)
  end
  WVM-->>Caller: map[string]string
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I twitch my whiskers, scent the code,
New vars sprout up along the road.
Repos hush in mutex rows,
Managers chase the keys they know.
I hop, I stash, a carrot logged—configuration snug, neatly catalogged. 🥕

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch variable-manager-template

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
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: 4

🧹 Nitpick comments (8)
apps/workspace-engine/pkg/engine/variable/manager_deployment_variable.go (2)

9-19: Add doc comments for exported types/ctors

Please add short package-style docs for DeploymentVariableManager and NewDeploymentVariableManager to clarify ownership and precedence when multiple managers are registered.

Example (non-functional guidance):

// DeploymentVariableManager resolves variables defined at the deployment scope.
// It looks up variables by (deploymentID, key) and delegates value computation
// to the underlying DeploymentVariable implementation.

21-35: *Prefer (string, bool, error) over string for presence semantics

Returning a pointer to string to signal “not found” forces double indirection across the codebase and invites pointer-to-interface patterns downstream. A more idiomatic API is (value string, ok bool, err error).

Minimal diff to show intent (interface change will ripple):

-type VariableManager interface {
-    Resolve(ctx context.Context, resource *resource.Resource, deploymentID string, key string) (*string, error)
-}
+type VariableManager interface {
+    Resolve(ctx context.Context, resource *resource.Resource, deploymentID string, key string) (string, bool, error)
+}

And here:

- resolved, err := deploymentVariable.Resolve(ctx, resource)
- if err != nil {
-     return nil, fmt.Errorf("failed to resolve deployment variable for key: %s, deploymentID: %s, err: %w", key, deploymentID, err)
- }
- return &resolved, nil
+ resolved, err := deploymentVariable.Resolve(ctx, resource)
+ if err != nil {
+     return "", false, fmt.Errorf("failed to resolve deployment variable for key: %s, deploymentID: %s, err: %w", key, deploymentID, err)
+ }
+ return resolved, true, nil
apps/workspace-engine/pkg/engine/variable/manager_resource_variable.go (1)

9-19: Add doc comments

Please document ResourceVariableManager and its constructor, including how it participates in precedence with other VariableManager implementations.

apps/workspace-engine/pkg/engine/variable/manager_variable.go (3)

28-40: Guard against nil repository and pre-allocate keys capacity

If v.deploymentVariables is nil, getKeys will panic. Also, pre-allocating keys improves perf slightly.

 func (v *WorkspaceVariableManager) getKeys(ctx context.Context, deploymentID string) []string {
-    deploymentVariables := v.deploymentVariables.GetAllByDeploymentID(ctx, deploymentID)
-    keys := make([]string, 0)
+    if v.deploymentVariables == nil {
+        return nil
+    }
+    deploymentVariables := v.deploymentVariables.GetAllByDeploymentID(ctx, deploymentID)
+    keys := make([]string, 0, len(deploymentVariables))

13-26: Document precedence and failure semantics

Since resolution stops at the first manager that returns a value, the order of managers defines precedence. Please document this on WorkspaceVariableManager and NewWorkspaceVariableManager so callers know how to construct the slice.


42-61: Consider parallelizing per-key resolution (optional)

If keys/managers grow, serial resolution could become a bottleneck. An errgroup with bounded concurrency per key can keep latency predictable while retaining short-circuit-on-first-hit semantics.

If helpful, I can sketch a safe pattern respecting early-exit per key.

apps/workspace-engine/pkg/engine/variable/deployment_variable_repository.go (1)

19-26: Pointer-to-interface storage increases complexity

variables is map[string]*DeploymentVariable (pointer to interface). This creates double-nil states, forces awkward checks (ptr nil vs interface nil), and rarely adds value in Go. Prefer storing the interface value (map[string]DeploymentVariable) or, better, a concrete entity type that implements DeploymentVariable.

If generics in model.Repository require pointer returns, consider returning a concrete pointer type (e.g., *deploymentVariableImpl) instead of *DeploymentVariable.

apps/workspace-engine/pkg/engine/variable/resource_variable_repository.go (1)

18-25: Avoid pointer-to-interface in storage

Similar to the deployment repo, map[string]*ResourceVariable invites double-nil states and awkward checks. Prefer storing interface values or concrete types and returning concrete pointers.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bd845ae and 7858f7b.

📒 Files selected for processing (8)
  • apps/workspace-engine/pkg/engine/variable/deployment_variable_repository.go (1 hunks)
  • apps/workspace-engine/pkg/engine/variable/manager_deployment_variable.go (1 hunks)
  • apps/workspace-engine/pkg/engine/variable/manager_resource_variable.go (1 hunks)
  • apps/workspace-engine/pkg/engine/variable/manager_variable.go (1 hunks)
  • apps/workspace-engine/pkg/engine/variable/resource_variable_repository.go (1 hunks)
  • apps/workspace-engine/pkg/engine/variable/resource_variable_repository_test.go (0 hunks)
  • apps/workspace-engine/pkg/model/variable/resource_variable.go (0 hunks)
  • apps/workspace-engine/pkg/model/variable/variable.go (0 hunks)
💤 Files with no reviewable changes (3)
  • apps/workspace-engine/pkg/engine/variable/resource_variable_repository_test.go
  • apps/workspace-engine/pkg/model/variable/variable.go
  • apps/workspace-engine/pkg/model/variable/resource_variable.go
🧰 Additional context used
📓 Path-based instructions (1)
apps/workspace-engine/**/*.go

📄 CodeRabbit inference engine (apps/workspace-engine/CLAUDE.md)

apps/workspace-engine/**/*.go: Do not add extraneous inline comments that state the obvious
Do not add comments that simply restate what the code does
Do not add comments for standard Go patterns (e.g., noting WaitGroup or semaphore usage)
Write comments that explain why, document complex logic/algorithms, provide non-obvious context, include TODO/FIXME, and document exported functions/types/methods

Files:

  • apps/workspace-engine/pkg/engine/variable/manager_variable.go
  • apps/workspace-engine/pkg/engine/variable/manager_deployment_variable.go
  • apps/workspace-engine/pkg/engine/variable/manager_resource_variable.go
  • apps/workspace-engine/pkg/engine/variable/deployment_variable_repository.go
  • apps/workspace-engine/pkg/engine/variable/resource_variable_repository.go
🧬 Code graph analysis (1)
apps/workspace-engine/pkg/engine/variable/resource_variable_repository.go (1)
apps/workspace-engine/pkg/model/repository.go (1)
  • Repository (7-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Lint
  • GitHub Check: Typecheck
🔇 Additional comments (1)
apps/workspace-engine/pkg/engine/variable/manager_deployment_variable.go (1)

21-35: Core resolve flow looks correct

Nil-safe fetch, deref, resolve, and error wrapping are all reasonable for the intended template.

Comment on lines +28 to +82
func (r *DeploymentVariableRepository) GetAll(ctx context.Context) []*DeploymentVariable {
r.mu.RLock()
defer r.mu.RUnlock()

return nil
}

func (r *DeploymentVariableRepository) GetAllByDeploymentID(ctx context.Context, deploymentID string) []*DeploymentVariable {
r.mu.RLock()
defer r.mu.RUnlock()

return nil
}

func (r *DeploymentVariableRepository) GetByDeploymentIDAndKey(ctx context.Context, deploymentID, key string) *DeploymentVariable {
r.mu.RLock()
defer r.mu.RUnlock()

return nil
}

func (r *DeploymentVariableRepository) Get(ctx context.Context, id string) *DeploymentVariable {
r.mu.RLock()
defer r.mu.RUnlock()

return nil
}

func (r *DeploymentVariableRepository) Create(ctx context.Context, variable *DeploymentVariable) error {
r.mu.Lock()
defer r.mu.Unlock()

return nil
}

func (r *DeploymentVariableRepository) Update(ctx context.Context, variable *DeploymentVariable) error {
r.mu.Lock()
defer r.mu.Unlock()

return nil
}

func (r *DeploymentVariableRepository) Delete(ctx context.Context, id string) error {
r.mu.Lock()
defer r.mu.Unlock()

return nil
}

func (r *DeploymentVariableRepository) Exists(ctx context.Context, id string) bool {
r.mu.RLock()
defer r.mu.RUnlock()

return false
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Repository methods are no-ops; managers will never resolve anything

Every method currently returns zero values. As a result, getKeys returns no keys and Resolve paths will never find variables. Provide a minimal in-memory implementation to make the template usable and testable.

Apply a minimal implementation:

@@
-import (
-    "context"
-    "sync"
-    "workspace-engine/pkg/model"
-    "workspace-engine/pkg/model/resource"
-)
+import (
+    "context"
+    "fmt"
+    "sync"
+    "workspace-engine/pkg/model"
+    "workspace-engine/pkg/model/resource"
+)
@@
 func (r *DeploymentVariableRepository) GetAll(ctx context.Context) []*DeploymentVariable {
     r.mu.RLock()
     defer r.mu.RUnlock()
-
-    return nil
+    out := make([]*DeploymentVariable, 0, len(r.variables))
+    for _, v := range r.variables {
+        out = append(out, v)
+    }
+    return out
 }
@@
 func (r *DeploymentVariableRepository) GetAllByDeploymentID(ctx context.Context, deploymentID string) []*DeploymentVariable {
     r.mu.RLock()
     defer r.mu.RUnlock()
-
-    return nil
+    out := make([]*DeploymentVariable, 0)
+    for _, vptr := range r.variables {
+        if vptr == nil || *vptr == nil {
+            continue
+        }
+        v := *vptr
+        if v.GetDeploymentID() == deploymentID {
+            out = append(out, vptr)
+        }
+    }
+    return out
 }
@@
 func (r *DeploymentVariableRepository) GetByDeploymentIDAndKey(ctx context.Context, deploymentID, key string) *DeploymentVariable {
     r.mu.RLock()
     defer r.mu.RUnlock()
-
-    return nil
+    for _, vptr := range r.variables {
+        if vptr == nil || *vptr == nil {
+            continue
+        }
+        v := *vptr
+        if v.GetDeploymentID() == deploymentID && v.GetKey() == key {
+            return vptr
+        }
+    }
+    return nil
 }
@@
 func (r *DeploymentVariableRepository) Get(ctx context.Context, id string) *DeploymentVariable {
     r.mu.RLock()
     defer r.mu.RUnlock()
-
-    return nil
+    return r.variables[id]
 }
@@
 func (r *DeploymentVariableRepository) Create(ctx context.Context, variable *DeploymentVariable) error {
     r.mu.Lock()
     defer r.mu.Unlock()
-
-    return nil
+    if variable == nil || *variable == nil {
+        return fmt.Errorf("variable is nil")
+    }
+    id := (*variable).GetID()
+    if id == "" {
+        return fmt.Errorf("id cannot be empty")
+    }
+    if r.variables == nil {
+        r.variables = make(map[string]*DeploymentVariable)
+    }
+    if _, exists := r.variables[id]; exists {
+        return fmt.Errorf("deployment variable %q already exists", id)
+    }
+    r.variables[id] = variable
+    return nil
 }
@@
 func (r *DeploymentVariableRepository) Update(ctx context.Context, variable *DeploymentVariable) error {
     r.mu.Lock()
     defer r.mu.Unlock()
-
-    return nil
+    if variable == nil || *variable == nil {
+        return fmt.Errorf("variable is nil")
+    }
+    id := (*variable).GetID()
+    if id == "" {
+        return fmt.Errorf("id cannot be empty")
+    }
+    r.variables[id] = variable
+    return nil
 }
@@
 func (r *DeploymentVariableRepository) Delete(ctx context.Context, id string) error {
     r.mu.Lock()
     defer r.mu.Unlock()
-
-    return nil
+    delete(r.variables, id)
+    return nil
 }
@@
 func (r *DeploymentVariableRepository) Exists(ctx context.Context, id string) bool {
     r.mu.RLock()
     defer r.mu.RUnlock()
-
-    return false
+    _, ok := r.variables[id]
+    return ok
 }
📝 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 *DeploymentVariableRepository) GetAll(ctx context.Context) []*DeploymentVariable {
r.mu.RLock()
defer r.mu.RUnlock()
return nil
}
func (r *DeploymentVariableRepository) GetAllByDeploymentID(ctx context.Context, deploymentID string) []*DeploymentVariable {
r.mu.RLock()
defer r.mu.RUnlock()
return nil
}
func (r *DeploymentVariableRepository) GetByDeploymentIDAndKey(ctx context.Context, deploymentID, key string) *DeploymentVariable {
r.mu.RLock()
defer r.mu.RUnlock()
return nil
}
func (r *DeploymentVariableRepository) Get(ctx context.Context, id string) *DeploymentVariable {
r.mu.RLock()
defer r.mu.RUnlock()
return nil
}
func (r *DeploymentVariableRepository) Create(ctx context.Context, variable *DeploymentVariable) error {
r.mu.Lock()
defer r.mu.Unlock()
return nil
}
func (r *DeploymentVariableRepository) Update(ctx context.Context, variable *DeploymentVariable) error {
r.mu.Lock()
defer r.mu.Unlock()
return nil
}
func (r *DeploymentVariableRepository) Delete(ctx context.Context, id string) error {
r.mu.Lock()
defer r.mu.Unlock()
return nil
}
func (r *DeploymentVariableRepository) Exists(ctx context.Context, id string) bool {
r.mu.RLock()
defer r.mu.RUnlock()
return false
}
import (
"context"
"fmt"
"sync"
"workspace-engine/pkg/model"
"workspace-engine/pkg/model/resource"
)
func (r *DeploymentVariableRepository) GetAll(ctx context.Context) []*DeploymentVariable {
r.mu.RLock()
defer r.mu.RUnlock()
out := make([]*DeploymentVariable, 0, len(r.variables))
for _, v := range r.variables {
out = append(out, v)
}
return out
}
func (r *DeploymentVariableRepository) GetAllByDeploymentID(ctx context.Context, deploymentID string) []*DeploymentVariable {
r.mu.RLock()
defer r.mu.RUnlock()
out := make([]*DeploymentVariable, 0)
for _, vptr := range r.variables {
if vptr == nil || *vptr == nil {
continue
}
v := *vptr
if v.GetDeploymentID() == deploymentID {
out = append(out, vptr)
}
}
return out
}
func (r *DeploymentVariableRepository) GetByDeploymentIDAndKey(ctx context.Context, deploymentID, key string) *DeploymentVariable {
r.mu.RLock()
defer r.mu.RUnlock()
for _, vptr := range r.variables {
if vptr == nil || *vptr == nil {
continue
}
v := *vptr
if v.GetDeploymentID() == deploymentID && v.GetKey() == key {
return vptr
}
}
return nil
}
func (r *DeploymentVariableRepository) Get(ctx context.Context, id string) *DeploymentVariable {
r.mu.RLock()
defer r.mu.RUnlock()
return r.variables[id]
}
func (r *DeploymentVariableRepository) Create(ctx context.Context, variable *DeploymentVariable) error {
r.mu.Lock()
defer r.mu.Unlock()
if variable == nil || *variable == nil {
return fmt.Errorf("variable is nil")
}
id := (*variable).GetID()
if id == "" {
return fmt.Errorf("id cannot be empty")
}
if r.variables == nil {
r.variables = make(map[string]*DeploymentVariable)
}
if _, exists := r.variables[id]; exists {
return fmt.Errorf("deployment variable %q already exists", id)
}
r.variables[id] = variable
return nil
}
func (r *DeploymentVariableRepository) Update(ctx context.Context, variable *DeploymentVariable) error {
r.mu.Lock()
defer r.mu.Unlock()
if variable == nil || *variable == nil {
return fmt.Errorf("variable is nil")
}
id := (*variable).GetID()
if id == "" {
return fmt.Errorf("id cannot be empty")
}
r.variables[id] = variable
return nil
}
func (r *DeploymentVariableRepository) Delete(ctx context.Context, id string) error {
r.mu.Lock()
defer r.mu.Unlock()
delete(r.variables, id)
return nil
}
func (r *DeploymentVariableRepository) Exists(ctx context.Context, id string) bool {
r.mu.RLock()
defer r.mu.RUnlock()
_, ok := r.variables[id]
return ok
}
🤖 Prompt for AI Agents
In apps/workspace-engine/pkg/engine/variable/deployment_variable_repository.go
around lines 28 to 82, all repository methods are stubs returning zero values;
implement a minimal in-memory backing store using the existing r.mu (read/write
mutex) and a map field (e.g., r.store map[string]*DeploymentVariable) so
managers can resolve variables. Implement GetAll to return all values,
GetAllByDeploymentID to filter by DeploymentID, GetByDeploymentIDAndKey to find
the first matching variable by deploymentID and key, Get to return the entry by
id, Create to add a new variable (error if id already exists), Update to replace
an existing variable (error if not found), Delete to remove by id, and Exists to
check presence; use RLock for read methods and Lock for write methods and ensure
any returned slices contain the stored pointers (or copies if desired) and that
Create/Update/Delete return appropriate errors.

Comment on lines +21 to +35
func (m *ResourceVariableManager) Resolve(ctx context.Context, resource *resource.Resource, deploymentID string, key string) (*string, error) {
resourceVariablePtr := m.resourceVariables.GetByResourceIDAndKey(ctx, resource.GetID(), key)
if resourceVariablePtr == nil || *resourceVariablePtr == nil {
return nil, nil
}

resourceVariable := *resourceVariablePtr

resolved, err := resourceVariable.Resolve(ctx)
if err != nil {
return nil, fmt.Errorf("failed to resolve resource variable for key: %s, resource: %s, err: %w", key, resource.GetID(), err)
}

return &resolved, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Nil deref risk when resource is nil

Resolve dereferences resource twice: once to fetch resource.GetID() and again in the error message. This will panic if callers pass a nil resource.

Apply this guard and make the error path nil-safe:

 func (m *ResourceVariableManager) Resolve(ctx context.Context, resource *resource.Resource, deploymentID string, key string) (*string, error) {
-    resourceVariablePtr := m.resourceVariables.GetByResourceIDAndKey(ctx, resource.GetID(), key)
+    if resource == nil {
+        return nil, fmt.Errorf("resource is nil while resolving key: %s", key)
+    }
+    resourceVariablePtr := m.resourceVariables.GetByResourceIDAndKey(ctx, resource.GetID(), key)
@@
-    if err != nil {
-        return nil, fmt.Errorf("failed to resolve resource variable for key: %s, resource: %s, err: %w", key, resource.GetID(), err)
-    }
+    if err != nil {
+        return nil, fmt.Errorf("failed to resolve resource variable for key: %s, resource: %s, err: %w", key, resource.GetID(), err)
+    }
📝 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 (m *ResourceVariableManager) Resolve(ctx context.Context, resource *resource.Resource, deploymentID string, key string) (*string, error) {
resourceVariablePtr := m.resourceVariables.GetByResourceIDAndKey(ctx, resource.GetID(), key)
if resourceVariablePtr == nil || *resourceVariablePtr == nil {
return nil, nil
}
resourceVariable := *resourceVariablePtr
resolved, err := resourceVariable.Resolve(ctx)
if err != nil {
return nil, fmt.Errorf("failed to resolve resource variable for key: %s, resource: %s, err: %w", key, resource.GetID(), err)
}
return &resolved, nil
}
func (m *ResourceVariableManager) Resolve(ctx context.Context, resource *resource.Resource, deploymentID string, key string) (*string, error) {
if resource == nil {
return nil, fmt.Errorf("resource is nil while resolving key: %s", key)
}
resourceVariablePtr := m.resourceVariables.GetByResourceIDAndKey(ctx, resource.GetID(), key)
if resourceVariablePtr == nil || *resourceVariablePtr == nil {
return nil, nil
}
resourceVariable := *resourceVariablePtr
resolved, err := resourceVariable.Resolve(ctx)
if err != nil {
return nil, fmt.Errorf("failed to resolve resource variable for key: %s, resource: %s, err: %w", key, resource.GetID(), err)
}
return &resolved, nil
}
🤖 Prompt for AI Agents
In apps/workspace-engine/pkg/engine/variable/manager_resource_variable.go around
lines 21-35, the code dereferences resource (resource.GetID()) before checking
for nil which can panic; add a nil guard at the top (if resource == nil) and
handle that case immediately (either return a clear error or nil result as
appropriate for callers). Also capture resourceID into a local variable only
after confirming resource != nil and use that variable in the error message; if
you choose to keep an error path when resource is nil, ensure the error message
uses a safe placeholder like "<nil>" or the deploymentID instead of
dereferencing resource.

Comment on lines +49 to +51
if err != nil {
return nil, fmt.Errorf("failed to resolve for key: %s, resource: %s, err: %w", key, resource.GetID(), err)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Nil deref risk in error formatting

The error path unconditionally calls resource.GetID(). If Resolve was invoked with a nil resource, this will panic before the error can be returned.

Apply a nil-safe resource ID for error messages:

-                return nil, fmt.Errorf("failed to resolve for key: %s, resource: %s, err: %w", key, resource.GetID(), err)
+                rid := "<nil>"
+                if resource != nil {
+                    rid = resource.GetID()
+                }
+                return nil, fmt.Errorf("failed to resolve for key: %s, resource: %s, err: %w", key, rid, err)
📝 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
if err != nil {
return nil, fmt.Errorf("failed to resolve for key: %s, resource: %s, err: %w", key, resource.GetID(), err)
}
if err != nil {
rid := "<nil>"
if resource != nil {
rid = resource.GetID()
}
return nil, fmt.Errorf("failed to resolve for key: %s, resource: %s, err: %w", key, rid, err)
}
🤖 Prompt for AI Agents
In apps/workspace-engine/pkg/engine/variable/manager_variable.go around lines 49
to 51, the error path unconditionally calls resource.GetID(), which will panic
if resource is nil; change the error construction to be nil-safe by computing an
ID string first (e.g. id := "<nil>"; if resource != nil { id = resource.GetID()
}) and then use that id in fmt.Errorf so the error path never dereferences a nil
resource.

Comment on lines +27 to 81
func (r *ResourceVariableRepository) GetAll(ctx context.Context) []*ResourceVariable {
r.mu.RLock()
defer r.mu.RUnlock()

var variables []*modelvariable.ResourceVariable
for _, resourceVariables := range r.variables {
for _, variable := range resourceVariables {
if variable == nil || *variable == nil {
continue
}

variableCopy := *variable
variables = append(variables, &variableCopy)
}
}

return variables
return nil
}

func (r *ResourceVariableRepository) Get(ctx context.Context, id string) *modelvariable.ResourceVariable {
func (r *ResourceVariableRepository) GetAllByResourceID(ctx context.Context, resourceID string) []*ResourceVariable {
r.mu.RLock()
defer r.mu.RUnlock()

for _, resourceVariables := range r.variables {
for _, variable := range resourceVariables {
if variable == nil || *variable == nil {
continue
}
variableCopy := *variable
if variableCopy.GetID() == id {
return &variableCopy
}
}
}

return nil
}

func (r *ResourceVariableRepository) GetAllByResourceID(ctx context.Context, resourceID string) []*modelvariable.ResourceVariable {
func (r *ResourceVariableRepository) GetByResourceIDAndKey(ctx context.Context, resourceID, key string) *ResourceVariable {
r.mu.RLock()
defer r.mu.RUnlock()

var variables []*modelvariable.ResourceVariable
if resourceVariables, ok := r.variables[resourceID]; ok {
for _, variable := range resourceVariables {
if variable == nil || *variable == nil {
continue
}
variableCopy := *variable
variables = append(variables, &variableCopy)
}
}

return variables
return nil
}

func (r *ResourceVariableRepository) GetByResourceIDAndKey(ctx context.Context, resourceID, key string) *modelvariable.ResourceVariable {
func (r *ResourceVariableRepository) Get(ctx context.Context, id string) *ResourceVariable {
r.mu.RLock()
defer r.mu.RUnlock()

if resourceVariables, ok := r.variables[resourceID]; ok {
if variable, ok := resourceVariables[key]; ok {
if variable == nil || *variable == nil {
return nil
}
variableCopy := *variable
return &variableCopy
}
}

return nil
}

func (r *ResourceVariableRepository) ensureVariableUniqueness(resourceID string, variable *modelvariable.ResourceVariable) error {
variableCopy := *variable
if _, ok := r.variables[resourceID][variableCopy.GetKey()]; ok {
return fmt.Errorf("variable already exists for resource %s and key %s", resourceID, variableCopy.GetKey())
}

for _, resourceVariables := range r.variables {
for _, existingVariable := range resourceVariables {
if existingVariable == nil || *existingVariable == nil {
continue
}
existingVariableCopy := *existingVariable
if existingVariableCopy.GetID() == variableCopy.GetID() {
return fmt.Errorf("variable already exists with ID %s", variableCopy.GetID())
}
}
}

return nil
}

func (r *ResourceVariableRepository) Create(ctx context.Context, variable *modelvariable.ResourceVariable) error {
func (r *ResourceVariableRepository) Create(ctx context.Context, variable *ResourceVariable) error {
r.mu.Lock()
defer r.mu.Unlock()

if variable == nil || *variable == nil {
return errors.New("variable is nil")
}

variableCopy := *variable
resourceID := variableCopy.GetResourceID()
key := variableCopy.GetKey()

if _, ok := r.variables[resourceID]; !ok {
r.variables[resourceID] = make(map[string]*modelvariable.ResourceVariable)
}

if err := r.ensureVariableUniqueness(resourceID, variable); err != nil {
return err
}

r.variables[resourceID][key] = &variableCopy

return nil
}

func (r *ResourceVariableRepository) Update(ctx context.Context, variable *modelvariable.ResourceVariable) error {
func (r *ResourceVariableRepository) Update(ctx context.Context, variable *ResourceVariable) error {
r.mu.Lock()
defer r.mu.Unlock()

if variable == nil || *variable == nil {
return errors.New("variable is nil")
}

variableCopy := *variable
resourceID := variableCopy.GetResourceID()
key := variableCopy.GetKey()

if _, ok := r.variables[resourceID]; !ok {
return errors.New("resource not found")
}

current, ok := r.variables[resourceID][key]
if !ok {
return errors.New("variable key not found")
}
currentCopy := *current
if currentCopy == nil {
return errors.New("current variable is nil")
}

if currentCopy.GetID() != variableCopy.GetID() {
return errors.New("variable ID mismatch")
}

r.variables[resourceID][key] = &variableCopy

return nil
}

func (r *ResourceVariableRepository) Delete(ctx context.Context, id string) error {
r.mu.Lock()
defer r.mu.Unlock()

for _, resourceVariables := range r.variables {
for _, variable := range resourceVariables {
if variable == nil || *variable == nil {
continue
}
variableCopy := *variable
if variableCopy.GetID() == id {
delete(resourceVariables, variableCopy.GetKey())
}
}
}

return nil
}

func (r *ResourceVariableRepository) Exists(ctx context.Context, id string) bool {
r.mu.RLock()
defer r.mu.RUnlock()

for _, resourceVariables := range r.variables {
for _, variable := range resourceVariables {
if variable == nil || *variable == nil {
continue
}
variableCopy := *variable
if variableCopy.GetID() == id {
return true
}
}
}
return false
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Repository is a stub; implement minimal in-memory behavior

As written, all methods return zero values; the ResourceVariableManager will never resolve anything. Provide a basic implementation to make the template functional.

Suggested minimal implementation:

@@
-import (
-    "context"
-    "sync"
-    "workspace-engine/pkg/model"
-)
+import (
+    "context"
+    "fmt"
+    "sync"
+    "workspace-engine/pkg/model"
+)
@@
 func (r *ResourceVariableRepository) GetAll(ctx context.Context) []*ResourceVariable {
     r.mu.RLock()
     defer r.mu.RUnlock()
-
-    return nil
+    out := make([]*ResourceVariable, 0, len(r.variables))
+    for _, v := range r.variables {
+        out = append(out, v)
+    }
+    return out
 }
@@
 func (r *ResourceVariableRepository) GetAllByResourceID(ctx context.Context, resourceID string) []*ResourceVariable {
     r.mu.RLock()
     defer r.mu.RUnlock()
 
-    return nil
+    out := make([]*ResourceVariable, 0)
+    for _, vptr := range r.variables {
+        if vptr == nil || *vptr == nil {
+            continue
+        }
+        v := *vptr
+        if v.GetResourceID() == resourceID {
+            out = append(out, vptr)
+        }
+    }
+    return out
 }
@@
 func (r *ResourceVariableRepository) GetByResourceIDAndKey(ctx context.Context, resourceID, key string) *ResourceVariable {
     r.mu.RLock()
     defer r.mu.RUnlock()
 
-    return nil
+    for _, vptr := range r.variables {
+        if vptr == nil || *vptr == nil {
+            continue
+        }
+        v := *vptr
+        if v.GetResourceID() == resourceID && v.GetKey() == key {
+            return vptr
+        }
+    }
+    return nil
 }
@@
 func (r *ResourceVariableRepository) Get(ctx context.Context, id string) *ResourceVariable {
     r.mu.RLock()
     defer r.mu.RUnlock()
 
-    return nil
+    return r.variables[id]
 }
@@
 func (r *ResourceVariableRepository) Create(ctx context.Context, variable *ResourceVariable) error {
     r.mu.Lock()
     defer r.mu.Unlock()
 
-    return nil
+    if variable == nil || *variable == nil {
+        return fmt.Errorf("variable is nil")
+    }
+    id := (*variable).GetID()
+    if id == "" {
+        return fmt.Errorf("id cannot be empty")
+    }
+    if r.variables == nil {
+        r.variables = make(map[string]*ResourceVariable)
+    }
+    if _, exists := r.variables[id]; exists {
+        return fmt.Errorf("resource variable %q already exists", id)
+    }
+    r.variables[id] = variable
+    return nil
 }
@@
 func (r *ResourceVariableRepository) Update(ctx context.Context, variable *ResourceVariable) error {
     r.mu.Lock()
     defer r.mu.Unlock()
 
-    return nil
+    if variable == nil || *variable == nil {
+        return fmt.Errorf("variable is nil")
+    }
+    id := (*variable).GetID()
+    if id == "" {
+        return fmt.Errorf("id cannot be empty")
+    }
+    r.variables[id] = variable
+    return nil
 }
@@
 func (r *ResourceVariableRepository) Delete(ctx context.Context, id string) error {
     r.mu.Lock()
     defer r.mu.Unlock()
 
-    return nil
+    delete(r.variables, id)
+    return nil
 }
@@
 func (r *ResourceVariableRepository) Exists(ctx context.Context, id string) bool {
     r.mu.RLock()
     defer r.mu.RUnlock()
 
-    return false
+    _, ok := r.variables[id]
+    return ok
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/workspace-engine/pkg/engine/variable/resource_variable_repository.go
around lines 27 to 81 the repository methods are stubs returning zero values;
implement a minimal in-memory store using a map and the existing mutex to make
the repository functional. Add a field like store map[string]*ResourceVariable
(keyed by ID) if not present, then implement: GetAll returns a slice of all
values; GetAllByResourceID filters by ResourceID; GetByResourceIDAndKey searches
store for matching resourceID and key and returns the first match; Get returns
store[id]; Create adds the variable to store (generate or validate ID) and
returns nil; Update replaces the entry if it exists (return error on missing if
desired) and Delete removes the key; Exists checks presence in the map. Use
r.mu.RLock/RUnlock for read paths and r.mu.Lock/Unlock for write paths as
already structured.

@adityachoudhari26 adityachoudhari26 merged commit 12bcff9 into main Aug 22, 2025
4 of 5 checks passed
@adityachoudhari26 adityachoudhari26 deleted the variable-manager-template branch August 22, 2025 17:43
Copy link
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: 2

♻️ Duplicate comments (1)
apps/workspace-engine/pkg/engine/variable/manager_variable.go (1)

35-37: Fix panic when resource is nil in error path

Dereferencing resource.GetID() will panic when Resolve is called with a nil resource (workspace-level scenarios). Make the error construction nil-safe and include deploymentID for context.

-            if err != nil {
-                return nil, fmt.Errorf("failed to resolve for key: %s, resource: %s, err: %w", key, resource.GetID(), err)
-            }
+            if err != nil {
+                rid := "<nil>"
+                if resource != nil {
+                    rid = resource.GetID()
+                }
+                return nil, fmt.Errorf("failed to resolve for key: %s, deployment: %s, resource: %s, err: %w", key, deploymentID, rid, err)
+            }
🧹 Nitpick comments (6)
apps/workspace-engine/pkg/engine/variable/manager_variable.go (4)

22-25: Defensive copy of managers to prevent external mutation

The managers slice is stored by reference; callers can mutate it after construction. Make a defensive copy in the constructor.

 return &WorkspaceVariableManager{
-    getKeys:  getKeys,
-    managers: managers,
+    getKeys:  getKeys,
+    managers: append([]VariableManager(nil), managers...),
 }

31-31: Guard against nil getKeys

The struct is exported and can be instantiated without the constructor, making getKeys nil and causing a panic here. Add a simple guard.

-    keys := v.getKeys(ctx, deploymentID)
+    if v.getKeys == nil {
+        return nil, fmt.Errorf("workspace variable manager misconfigured: getKeys is nil")
+    }
+    keys := v.getKeys(ctx, deploymentID)

9-11: Add doc comments for exported API and clarify manager precedence

Public symbols lack Go doc comments. Please add brief comments noting that managers are evaluated in order and the first non-nil value wins.

+// VariableManager resolves a value for a key within a deployment/resource context.
+// Return a nil value (with nil error) to indicate "not handled".
 type VariableManager interface {
     Resolve(ctx context.Context, resource *resource.Resource, deploymentID string, key string) (*string, error)
 }
 
+// WorkspaceVariableManager fans out key resolution to a prioritized set of VariableManagers.
 type WorkspaceVariableManager struct {
     getKeys  func(ctx context.Context, deploymentID string) []string
     managers []VariableManager
 }
 
+// NewWorkspaceVariableManager constructs a manager; managers are evaluated in order.
 func NewWorkspaceVariableManager(
     getKeys func(ctx context.Context, deploymentID string) []string,
     managers []VariableManager,
 ) *WorkspaceVariableManager {
     return &WorkspaceVariableManager{
         getKeys:  getKeys,
         managers: managers,
     }
 }
 
+// ResolveDeploymentVariables resolves all keys returned by getKeys.
 func (v *WorkspaceVariableManager) ResolveDeploymentVariables(ctx context.Context, resource *resource.Resource, deploymentID string) (map[string]string, error) {

Also applies to: 13-16, 18-26, 28-28


31-44: Confirm behavior for unresolved keys

If no manager returns a value for a key, it’s omitted from the result. Is this intended, or should unresolved keys be returned with defaults or an explicit error/list? If needed, we can collect and return an "unresolved" slice for observability.

apps/workspace-engine/pkg/model/variable/deployment_variable.go (2)

10-18: Add concise doc comments for exported types

Exported types lack documentation. Add minimal comments to satisfy linters and communicate intent (especially around selector semantics and sensitivity).

+// DeploymentVariable models a deployment-scoped variable and its candidate values.
 type DeploymentVariable struct {
@@
+// DirectDeploymentVariableValue represents a literal value with optional resource selector.
 type DirectDeploymentVariableValue struct {
@@
+// ReferenceDeploymentVariableValue represents an indirect value (reference + path) with optional selector.
 type ReferenceDeploymentVariableValue struct {

Also applies to: 36-43, 76-83


69-74: Reduce duplication in Selector implementations

Consider factoring the entity-kind check into a small helper to keep both Selectors consistent and easy to maintain.

Example (outside selected lines, for illustration only):

func isResourceEntity(e model.MatchableEntity) bool {
    switch e.(type) {
    case resource.Resource, *resource.Resource:
        return true
    default:
        return false
    }
}

Then both Selectors can call isResourceEntity(entity).

Also applies to: 109-114

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7858f7b and 3db02ca.

📒 Files selected for processing (2)
  • apps/workspace-engine/pkg/engine/variable/manager_variable.go (1 hunks)
  • apps/workspace-engine/pkg/model/variable/deployment_variable.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
apps/workspace-engine/**/*.go

📄 CodeRabbit inference engine (apps/workspace-engine/CLAUDE.md)

apps/workspace-engine/**/*.go: Do not add extraneous inline comments that state the obvious
Do not add comments that simply restate what the code does
Do not add comments for standard Go patterns (e.g., noting WaitGroup or semaphore usage)
Write comments that explain why, document complex logic/algorithms, provide non-obvious context, include TODO/FIXME, and document exported functions/types/methods

Files:

  • apps/workspace-engine/pkg/engine/variable/manager_variable.go
  • apps/workspace-engine/pkg/model/variable/deployment_variable.go
🧬 Code graph analysis (1)
apps/workspace-engine/pkg/model/variable/deployment_variable.go (2)
apps/workspace-engine/pkg/model/conditions/condition.go (1)
  • JSONCondition (53-59)
apps/workspace-engine/pkg/model/types.go (1)
  • MatchableEntity (17-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
🔇 Additional comments (1)
apps/workspace-engine/pkg/model/variable/deployment_variable.go (1)

65-67: Please confirm nil‐selector matching semantics across all SelectorEntity implementations

I noticed that for most entities (DirectDeploymentVariableValue, ReferenceDeploymentVariableValue, Environment, Deployment), MatchAllIfNullSelector always returns false, whereas PolicyTarget returns true when its ResourceSelector is nil. To ensure consistency and avoid surprises in the selector engine:

• Direct/ReferenceDeploymentVariableValue.MatchAllIfNullSelector (lines 65–67 and 105–107 of deployment_variable.go)

func (v *DirectDeploymentVariableValue) MatchAllIfNullSelector(entity model.MatchableEntity) bool {
    return false
}

• Environment.MatchAllIfNullSelector (environment.go:24–26) returns false
• Deployment.MatchAllIfNullSelector (deployment.go:28–30) returns false
• PolicyTarget.MatchAllIfNullSelector (policy_target.go:26–28) returns true

Please verify the intended contract:

  • Should variable values (Direct/ReferenceDeploymentVariableValue) treat a nil ResourceSelector as “match none” (current) or “match all” (policy style)?
  • If “match all” is desired, update MatchAllIfNullSelector to return true when ResourceSelector == nil.
  • Otherwise, consider adding comments to each implementation to document why semantics differ.

Comment on lines +69 to +74
func (v *DirectDeploymentVariableValue) Selector(entity model.MatchableEntity) (*conditions.JSONCondition, error) {
if _, ok := entity.(resource.Resource); ok {
return v.ResourceSelector, nil
}
return nil, fmt.Errorf("entity is not a supported selector option")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Selector rejects pointer-to-resource; support both pointer and value

Type assertion checks for resource.Resource (value). Call sites typically pass pointers (*resource.Resource), so this will incorrectly error. Support both forms.

-func (v *DirectDeploymentVariableValue) Selector(entity model.MatchableEntity) (*conditions.JSONCondition, error) {
-    if _, ok := entity.(resource.Resource); ok {
-        return v.ResourceSelector, nil
-    }
-    return nil, fmt.Errorf("entity is not a supported selector option")
-}
+func (v *DirectDeploymentVariableValue) Selector(entity model.MatchableEntity) (*conditions.JSONCondition, error) {
+    switch entity.(type) {
+    case resource.Resource, *resource.Resource:
+        return v.ResourceSelector, nil
+    default:
+        return nil, fmt.Errorf("entity is not a supported selector option")
+    }
+}
📝 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 (v *DirectDeploymentVariableValue) Selector(entity model.MatchableEntity) (*conditions.JSONCondition, error) {
if _, ok := entity.(resource.Resource); ok {
return v.ResourceSelector, nil
}
return nil, fmt.Errorf("entity is not a supported selector option")
}
func (v *DirectDeploymentVariableValue) Selector(entity model.MatchableEntity) (*conditions.JSONCondition, error) {
switch entity.(type) {
case resource.Resource, *resource.Resource:
return v.ResourceSelector, nil
default:
return nil, fmt.Errorf("entity is not a supported selector option")
}
}
🤖 Prompt for AI Agents
In apps/workspace-engine/pkg/model/variable/deployment_variable.go around lines
69 to 74, the Selector method only type-asserts entity as the value type
resource.Resource so it fails when callers pass a pointer; update the check to
accept both resource.Resource and *resource.Resource (e.g., use a type switch or
two assertions for the value and pointer types) and return v.ResourceSelector
for either case, otherwise return the same error.

Comment on lines +109 to +114
func (v *ReferenceDeploymentVariableValue) Selector(entity model.MatchableEntity) (*conditions.JSONCondition, error) {
if _, ok := entity.(resource.Resource); ok {
return v.ResourceSelector, nil
}
return nil, fmt.Errorf("entity is not a supported selector option")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Same pointer vs value issue for ReferenceDeploymentVariableValue.Selector

Mirror the fix here to avoid false negatives when a pointer is provided.

-func (v *ReferenceDeploymentVariableValue) Selector(entity model.MatchableEntity) (*conditions.JSONCondition, error) {
-    if _, ok := entity.(resource.Resource); ok {
-        return v.ResourceSelector, nil
-    }
-    return nil, fmt.Errorf("entity is not a supported selector option")
-}
+func (v *ReferenceDeploymentVariableValue) Selector(entity model.MatchableEntity) (*conditions.JSONCondition, error) {
+    switch entity.(type) {
+    case resource.Resource, *resource.Resource:
+        return v.ResourceSelector, nil
+    default:
+        return nil, fmt.Errorf("entity is not a supported selector option")
+    }
+}
📝 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 (v *ReferenceDeploymentVariableValue) Selector(entity model.MatchableEntity) (*conditions.JSONCondition, error) {
if _, ok := entity.(resource.Resource); ok {
return v.ResourceSelector, nil
}
return nil, fmt.Errorf("entity is not a supported selector option")
}
func (v *ReferenceDeploymentVariableValue) Selector(entity model.MatchableEntity) (*conditions.JSONCondition, error) {
switch entity.(type) {
case resource.Resource, *resource.Resource:
return v.ResourceSelector, nil
default:
return nil, fmt.Errorf("entity is not a supported selector option")
}
}
🤖 Prompt for AI Agents
In apps/workspace-engine/pkg/model/variable/deployment_variable.go around lines
109 to 114, the current type assertion only checks the dynamic type as-is and
returns false negatives when entity is a pointer to a type that implements
resource.Resource; change the check to detect and accept both pointer and value
receivers by using reflection to inspect and, if entity is a pointer,
dereference it and test whether the underlying value implements
resource.Resource (return v.ResourceSelector if so), otherwise return the
existing error.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants