diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 9caa890517..bca6609301 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -5,8 +5,18 @@
# Files in the root directory
**/go.mod @radius-project/on-call @radius-project/maintainers-radius @radius-project/approvers-radius
**/go.sum @radius-project/on-call @radius-project/maintainers-radius @radius-project/approvers-radius
+
# Devcontainer files
-.devcontainer/devcontainer.json @radius-project/on-call @radius-project/maintainers-radius @radius-project/approvers-radius
+.devcontainer/** @radius-project/on-call @radius-project/maintainers-radius @radius-project/approvers-radius
+
+# Allows on-call members to respond to changes in .github
+.github/workflows/*.yaml @radius-project/on-call @radius-project/maintainers-radius @radius-project/approvers-radius
+.github/workflows/*.yml @radius-project/on-call @radius-project/maintainers-radius @radius-project/approvers-radius
+
+# GitHub configs
+.github/*.yaml @radius-project/on-call @radius-project/maintainers-radius @radius-project/approvers-radius
+.github/*.yml @radius-project/on-call @radius-project/maintainers-radius @radius-project/approvers-radius
+
# Dockerfile
test/*/Dockerfile @radius-project/on-call @radius-project/maintainers-radius @radius-project/approvers-radius
deploy/images/*/Dockerfile @radius-project/on-call @radius-project/maintainers-radius @radius-project/approvers-radius
diff --git a/.github/agents/issue-investigator.agent.md b/.github/agents/issue-investigator.agent.md
new file mode 100644
index 0000000000..cd4dd7175a
--- /dev/null
+++ b/.github/agents/issue-investigator.agent.md
@@ -0,0 +1,141 @@
+---
+name: issue-investigator
+description: Review and analyze issues to provide focused, detailed technical context that helps developers understand and evaluate each issue efficiently.
+tools: ["read", "search", "edit", "web", "shell"]
+---
+
+# Dynamic inputs provided when invoking the agent.
+inputs:
+ - name: issue
+ type: integer
+ required: true
+ description: Numeric GitHub issue number (e.g. 345)
+
+You are a technical investigation agent for the Radius Project. Your role is to analyze the specified issues and provide in-depth technical context to help developers understand and resolve them efficiently.
+
+The audience for the results of your investigation is an experienced Radius developer, so you do not need to provide an overview of Radius, its functionality, or architecture.
+
+Focus on the specified issue and bring together only that information that will help the agent or developer assigned the issue understand the issue quickly.
+
+For each issue, perform the following technical investigation:
+
+## 1. Code Exploration and Problem Localization
+- Identify the functionality or feature area described in the issue
+- Search the codebase for:
+ - Functions, classes, or modules likely involved
+ - Entry points where the issue might manifest
+ - Data flow paths that could be affected
+- List potential problem locations with brief explanations:
+
+## 2. Reference Material Gathering
+Find and document relevant resources:
+- **Code references:**
+ - Related functions and their locations
+ - Similar implementations elsewhere in the codebase
+ - Test files that cover this functionality
+- **Documentation:**
+ - API documentation for the affected endpoints
+ - Architecture decision records (ADRs) related to this area
+ - README sections or wiki pages
+ - Related issues or pull requests (both open and closed)
+- **External resources:**
+ - Dependencies that might be involved
+ - Discussions about similar problems and known issues
+ - Official documentation for frameworks/libraries used
+
+## 3. Behavior Analysis
+Document the discrepancy between expected and actual behavior:
+- **Expected behavior:**
+ - What should happen according to documentation
+ - What the user reasonably expects
+ - What the tests indicate should occur
+- **Current behavior:**
+ - What actually happens
+ - Error messages or unexpected outputs
+ - Side effects observed
+- **Behavior delta:**
+ - Specific differences
+ - Conditions under which the problem occurs
+ - Edge cases that might trigger the issue
+
+## 4. Impact Assessment
+Analyze the scope and criticality:
+- **Scope:**
+ - How many users/use cases are affected
+ - Which features depend on this functionality
+ - Whether this blocks other functionality
+- **Criticality factors:**
+ - Data integrity risks
+ - Security implications
+ - Performance impact
+ - User experience degradation
+- **Severity rating:** [Critical/High/Medium/Low] with justification
+
+## 5. Cross-Cutting Concerns
+Identify related issues elsewhere:
+- **Similar patterns:**
+ - Search for similar code patterns that might have the same issue
+ - List other components using the same approach
+- **Dependency analysis:**
+ - Other modules that depend on the affected code
+ - Downstream effects of potential fixes
+- **Related bugs:**
+ - Past issues in the same area
+ - Known limitations or technical debt
+
+## Investigation Report Template:
+Technical Investigation Summary
+Issue: [Issue Title]
+1. Problem Localization
+The issue appears to originate from:
+Primary location: [file:line] - [brief explanation]
+Secondary locations: [list other relevant code areas]
+
+2. Root Cause Hypothesis
+Based on code inspection, the likely cause is:
+[Technical explanation of what might be going wrong]
+
+3. Expected vs Actual Behavior
+Expected: [What should happen]
+Actual: [What currently happens]
+Trigger conditions: [When this occurs]
+
+4. Relevant References
+Code:
+[Link to function/class]
+[Link to tests]
+Documentation:
+[Link to relevant docs]
+Related issues:
+[Links to similar problems]
+
+5. Impact Analysis
+Severity: [Critical/High/Medium/Low]
+Scope: [Number of affected features/users]
+Risk areas: [What could break]
+
+6. Similar Patterns Found
+[List other code areas with similar implementation]
+[Potential for same issue elsewhere]
+
+7. Technical Context for Developers
+Key functions to review: [List]
+Relevant design patterns: [Explain]
+Potential gotchas: [Warn about tricky aspects]
+Suggested investigation steps: [Next steps for assigned developer]
+
+## Guidelines:
+- Do not try to solve the issue
+- Do not create a plan to solve the issue
+- Do not change any code or product documentation
+- Do not provide summaries or overviews of Radius as a whole, its functionality or architecture.
+- Do not provide summaries or overviews of the purpse, structure, or content of the current repo.
+- Focus on providing relevant and actionable technical information
+- Include code snippets when relevant
+- Link to specific lines of code in the repository
+- Highlight architectural implications
+- Note any technical debt that might complicate fixes
+- Identify opportunities for broader improvements
+- Consider backward compatibility implications
+
+Analyze the submitted issue and provide your technical investigation following this framework.
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 6b99f46c37..cc8c604f9e 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -35,6 +35,12 @@ The following instruction files are available:
- **[Make](instructions/make.instructions.md)** - Best practices for GNU Make Makefiles
- **[Shell Scripts](instructions/shell.instructions.md)** - Guidelines for Bash/Shell script development
+## Skills
+
+These skills are available for specific Radius tasks under the `.github/skills/` directory.
+
+- **[Architecture Documentation](skills/architecture-documenter/SKILL.md)** - Document and diagram application architecture
+
## How to Use
When working on files that match the patterns defined in instruction files (e.g., `*.sh`, `.github/workflows/*.yml`), Copilot will automatically apply the relevant guidelines from the corresponding instruction file.
diff --git a/.github/skills/architecture-documenter/SKILL.md b/.github/skills/architecture-documenter/SKILL.md
new file mode 100644
index 0000000000..c29ad6ead5
--- /dev/null
+++ b/.github/skills/architecture-documenter/SKILL.md
@@ -0,0 +1,172 @@
+---
+name: architecture-documenter
+description: 'Document application architectures with Mermaid diagrams. Use for: generating architecture overviews, component diagrams, sequence diagrams from code, explaining complex Go codebases, answering architecture questions, suggesting architectural improvements, producing entity-relationship diagrams, and distilling code into human-readable descriptions.'
+argument-hint: 'Describe what part of the architecture to document or ask an architecture question'
+---
+
+# Architecture Documenter
+
+Expert skill for analyzing codebases, documenting application architectures, and generating accurate Mermaid diagrams grounded in actual source code.
+
+## When to Use
+
+- Generate a high-level architecture overview of the system or a subsystem
+- Produce component diagrams showing entity relationships
+- Create sequence diagrams that are true-to-code (reflect actual call chains)
+- Explain how a complex subsystem works in plain language
+- Answer questions about the existing architecture
+- Suggest architectural improvements that would simplify the code
+- Onboard new contributors by explaining system structure
+
+## Core Principles
+
+1. **Code-grounded**: Every diagram and explanation must be derived from actual source code, not assumptions. Read the code before documenting it.
+2. **Progressive depth**: Start with high-level overviews, then drill into details only when asked.
+3. **Accuracy over aesthetics**: A correct simple diagram beats an elaborate wrong one.
+4. **Human-readable output**: Distill complex code concepts into clear, jargon-minimal prose. Use diagrams to complement text, not replace it.
+
+## Procedure
+
+### Step 1: Scope the Request
+
+Determine what the user wants documented:
+
+| Request Type | Output |
+|---|---|
+| "How does X work?" | Prose explanation + optional diagram |
+| "Show me the architecture of X" | Component diagram + brief description |
+| "Show me the flow when X happens" | Sequence diagram + step-by-step narrative |
+| "What are the relationships between X, Y, Z?" | Entity-relationship / component diagram |
+| "How could X be improved?" | Current-state diagram + improvement suggestions |
+| "Give me an overview" | High-level system diagram + component summary |
+
+### Step 2: Gather Context from Code
+
+This is the most critical step. **Do not generate diagrams from memory or assumptions.**
+
+1. **Identify entry points**: Find `main()` functions, server setup, route registration, or handler initialization relevant to the scope.
+2. **Trace the call chain**: Follow function calls from entry points through layers (frontend → backend → data). Read interfaces and their implementations.
+3. **Map package structure**: Understand how packages relate to each other. Pay attention to `doc.go` files for package-level documentation.
+4. **Identify key types**: Find the core structs, interfaces, and their methods that define the architecture.
+5. **Note patterns**: Identify design patterns in use (controller pattern, resource provider pattern, middleware chains, async operations, etc.).
+
+#### Go-Specific Investigation Techniques
+
+- **Find interface implementations**: Search for methods matching interface signatures. Use `grep` for receiver types.
+- **Trace dependency injection**: Look at constructor functions (`New...()`) and `setup` packages to understand how components are wired together.
+- **Follow the handler chain**: For HTTP services, start at route registration and follow middleware → handler → controller → backend flow.
+- **Check for code generation**: Look for generated files (`zz_generated_*.go`, files with generation comments) to understand what is hand-written vs. generated.
+- **Read test files**: Tests often reveal the expected behavior and interaction patterns between components.
+
+### Step 3: Generate the Diagram
+
+Choose the appropriate Mermaid diagram type based on the request. See [Mermaid Diagram Reference](./references/mermaid-patterns.md) for templates.
+
+| Situation | Diagram Type |
+|---|---|
+| System / subsystem overview | `graph TD` (top-down flowchart) |
+| Request/response flow | `sequenceDiagram` |
+| Entity relationships | `classDiagram` or `erDiagram` |
+| State transitions | `stateDiagram-v2` |
+| Component dependencies | `graph LR` (left-right flowchart) |
+| Deployment topology | `graph TD` with subgraphs |
+
+#### Diagram Quality Checklist
+
+- [ ] Every node in the diagram corresponds to a real package, type, or component in the code
+- [ ] Relationships reflect actual code dependencies (imports, function calls, interface implementations)
+- [ ] Labels use the actual names from the codebase (type names, package names, function names)
+- [ ] The diagram is not overcrowded — split into multiple diagrams if >15 nodes
+- [ ] Subgraphs are used to group related components
+- [ ] Arrow labels describe the nature of the relationship (e.g., "implements", "calls", "sends")
+
+### Step 4: Write the Explanation
+
+Pair every diagram with a prose explanation that:
+
+1. **Summarizes** what the diagram shows in 1-2 sentences
+2. **Walks through** the key components and their responsibilities
+3. **Highlights** important architectural decisions or patterns
+4. **Notes** any non-obvious aspects (error handling paths, async behavior, retries)
+
+#### Writing Style
+
+- Use short paragraphs (3-4 sentences max)
+- Lead with the "what" and "why" before the "how"
+- Use bullet lists for component responsibilities
+- Bold key terms on first use
+- Reference specific file paths so readers can find the code
+
+### Step 5: Suggest Improvements (When Asked)
+
+When the user asks for architectural improvements:
+
+1. **Identify pain points**: Look for code smells — excessive coupling, god packages, circular dependencies, duplicated patterns, inconsistent abstractions.
+2. **Propose specific changes**: Name the packages/types involved and describe the refactoring.
+3. **Show before/after**: Use a current-state diagram and a proposed-state diagram to illustrate the improvement.
+4. **Assess trade-offs**: Every change has a cost. Note migration effort, risk, and what gets simpler vs. more complex.
+
+## Radius Project Context
+
+This skill is tailored for the Radius project. Key architectural knowledge:
+
+### High-Level Components
+
+| Component | Location | Purpose |
+|---|---|---|
+| UCP (Universal Control Plane) | `pkg/ucp/`, `cmd/ucpd/` | Core control plane, resource routing, proxy |
+| Applications RP (Applications.Core) | `pkg/corerp/`, `cmd/applications-rp/` | Resource provider for core Radius resources (environments, applications, containers, gateways) |
+| Dynamic RP | `pkg/dynamicrp/`, `cmd/dynamic-rp/` | Resource provider for user-defined resource types that have no dedicated RP implementation |
+| Dapr RP | `pkg/daprrp/` | Resource provider for Dapr portable resources (state stores, pub/sub, secret stores) |
+| Datastores RP | `pkg/datastoresrp/` | Resource provider for datastore portable resources (MongoDB, Redis, SQL) |
+| Messaging RP | `pkg/messagingrp/` | Resource provider for messaging portable resources (RabbitMQ) |
+| Portable Resources (shared) | `pkg/portableresources/` | Shared backend, handlers, processors, and renderers used by Dapr/Datastores/Messaging RPs |
+| Controller | `pkg/controller/`, `cmd/controller/` | Kubernetes controller for deployment reconciliation |
+| CLI (rad) | `pkg/cli/`, `cmd/rad/` | Command-line interface |
+| ARM RPC Framework | `pkg/armrpc/` | Shared framework for building ARM-compatible resource providers |
+| Recipes Engine | `pkg/recipes/` | Recipe execution engine for provisioning infrastructure via Terraform and Bicep |
+| SDK | `pkg/sdk/` | Client SDK for connecting to and interacting with the Radius control plane |
+| Shared Components | `pkg/components/` | Shared infrastructure: database, message queue, secrets, metrics, tracing |
+| RP Commons | `pkg/rp/` | Shared packages used by corerp and the portable resource providers |
+
+### Common Patterns
+
+- **Frontend/Backend split**: Resource providers have `frontend/` (HTTP handlers, API validation) and `backend/` (async operations, deployment) packages.
+- **Data models**: Each RP defines data models in `datamodel/` with versioned API types in `api/`.
+- **ARM RPC controllers**: HTTP handlers implement the `armrpc` controller interfaces.
+- **Async operations**: Long-running operations use the async operation framework in `pkg/armrpc/asyncoperation/`.
+- **Recipes**: Infrastructure provisioning via Terraform/Bicep recipes in `pkg/recipes/`.
+
+### Design Notes
+
+Architecture design documents are available in the `design-notes/architecture/` directory of the `radius-project/design-notes` repository. Reference these for historical context on architectural decisions.
+
+### Output Directory
+
+When generating architecture documentation files, place them in `docs/architecture/` within the Radius repository. This folder is for living architecture documentation derived from the current codebase.
+
+## Output Format
+
+Always structure output as:
+
+````markdown
+## [Title — what is being documented]
+
+[1-2 sentence summary]
+
+```mermaid
+[diagram]
+```
+
+### Key Components
+
+[Bulleted list of components and responsibilities]
+
+### How It Works
+
+[Prose walkthrough of the flow/architecture]
+
+### Notable Details
+
+[Any non-obvious aspects worth calling out]
+````
diff --git a/.github/skills/architecture-documenter/references/mermaid-patterns.md b/.github/skills/architecture-documenter/references/mermaid-patterns.md
new file mode 100644
index 0000000000..91918dcc65
--- /dev/null
+++ b/.github/skills/architecture-documenter/references/mermaid-patterns.md
@@ -0,0 +1,174 @@
+# Mermaid Diagram Patterns
+
+Reusable Mermaid templates for architecture documentation. Copy and adapt these patterns.
+
+## High-Level System Overview (Top-Down Flowchart)
+
+```mermaid
+graph TD
+ subgraph External["External Clients"]
+ CLI["rad CLI"]
+ API["API Clients"]
+ end
+
+ subgraph ControlPlane["Control Plane"]
+ UCP["UCP
Universal Control Plane"]
+ end
+
+ subgraph ResourceProviders["Resource Providers"]
+ AppRP["Applications RP"]
+ DynRP["Dynamic RP"]
+ end
+
+ subgraph Infrastructure["Infrastructure"]
+ K8s["Kubernetes"]
+ Azure["Azure"]
+ AWS["AWS"]
+ end
+
+ CLI --> UCP
+ API --> UCP
+ UCP -->|routes requests| AppRP
+ UCP -->|routes requests| DynRP
+ AppRP -->|deploys to| K8s
+ AppRP -->|deploys to| Azure
+ DynRP -->|deploys to| K8s
+```
+
+## Sequence Diagram (Request Flow)
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant Frontend as Frontend
(HTTP Handler)
+ participant Controller as ARM RPC
Controller
+ participant Backend as Backend
(Async Worker)
+ participant Store as Data Store
+
+ Client->>Frontend: PUT /resource
+ Frontend->>Frontend: Validate request
+ Frontend->>Controller: CreateOrUpdate()
+ Controller->>Store: Save resource (Queued)
+ Controller-->>Frontend: 201 Created (async)
+ Frontend-->>Client: 201 + Operation-Location
+
+ Note over Backend: Async processing
+ Backend->>Store: Get resource
+ Backend->>Backend: Execute operation
+ Backend->>Store: Update resource (Succeeded/Failed)
+```
+
+## Component Diagram (Entity Relationships)
+
+```mermaid
+classDiagram
+ class Interface {
+ <>
+ +Method() error
+ }
+ class ConcreteImpl {
+ -field Type
+ +Method() error
+ }
+ class Dependency {
+ +HelperMethod() Result
+ }
+
+ Interface <|.. ConcreteImpl : implements
+ ConcreteImpl --> Dependency : uses
+```
+
+## Package Dependency Diagram
+
+```mermaid
+graph LR
+ subgraph cmd["cmd/"]
+ main["main.go"]
+ end
+
+ subgraph pkg["pkg/"]
+ frontend["frontend/"]
+ backend["backend/"]
+ datamodel["datamodel/"]
+ api["api/"]
+ end
+
+ main --> frontend
+ main --> backend
+ frontend --> datamodel
+ frontend --> api
+ backend --> datamodel
+ api --> datamodel
+```
+
+## State Diagram (Resource Lifecycle)
+
+```mermaid
+stateDiagram-v2
+ [*] --> Provisioning: Create
+ Provisioning --> Succeeded: Operation complete
+ Provisioning --> Failed: Error
+
+ Succeeded --> Updating: Update
+ Updating --> Succeeded: Operation complete
+ Updating --> Failed: Error
+
+ Succeeded --> Deleting: Delete
+ Failed --> Deleting: Delete
+ Deleting --> [*]: Removed
+ Deleting --> Failed: Error
+```
+
+## Deployment Topology (Subgraphs)
+
+```mermaid
+graph TD
+ subgraph Namespace["radius-system namespace"]
+ UCP["UCP Pod"]
+ AppRP["Applications RP Pod"]
+ DynRP["Dynamic RP Pod"]
+ Controller["Controller Pod"]
+ end
+
+ subgraph UserNS["user namespace"]
+ App["Application Resources"]
+ end
+
+ subgraph External["External"]
+ Cloud["Cloud Providers"]
+ end
+
+ Controller -->|watches| App
+ UCP -->|proxy| AppRP
+ UCP -->|proxy| DynRP
+ AppRP -->|manages| App
+ AppRP -->|provisions| Cloud
+```
+
+## Tips for Effective Diagrams
+
+### Keep It Readable
+
+- **Max ~15 nodes** per diagram. Split complex systems into multiple diagrams.
+- Use **subgraphs** to group related components and reduce visual clutter.
+- Use **short labels** on arrows — one or two words.
+- Prefer **top-down** (`TD`) for hierarchical relationships and **left-right** (`LR`) for data flows.
+
+### Make It Accurate
+
+- Use **actual names** from the code (package names, type names, function names).
+- Show **real relationships** — don't invent connections that don't exist in the code.
+- Include a **legend** or note if using non-obvious conventions.
+
+### Sequence Diagram Tips
+
+- Name participants after their **actual role** in the code (e.g., "FrontendController" not "Server").
+- Use `Note over` to explain non-obvious steps.
+- Use `activate`/`deactivate` to show which participant is processing.
+- Show **error paths** with `alt`/`else` blocks when they are architecturally significant.
+
+### Class Diagram Tips
+
+- Use `<>` stereotypes for Go interfaces.
+- Show only **architecturally significant** fields and methods, not every field.
+- Use composition (`*--`) vs. aggregation (`o--`) vs. dependency (`-->`) appropriately.
diff --git a/.github/workflows/__changes.yml b/.github/workflows/__changes.yml
index 938665fcdd..d201dd3966 100644
--- a/.github/workflows/__changes.yml
+++ b/.github/workflows/__changes.yml
@@ -62,7 +62,7 @@ jobs:
- name: Filter
id: filter
- uses: tj-actions/changed-files@8cba46e29c11878d930bca7870bb54394d3e8b21 # v47.0.2
+ uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
with:
json: true
files: ${{ inputs.files }}
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index a8d5d02ac6..0c69a5d3c6 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -109,7 +109,7 @@ jobs:
- name: Setup Go
if: needs.changes.outputs.only_changed != 'true'
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
+ uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: go.mod
cache-dependency-path: go.sum
@@ -143,7 +143,7 @@ jobs:
- name: Upload CLI binary
if: needs.changes.outputs.only_changed != 'true'
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: rad_cli_${{ matrix.target_os}}_${{ matrix.target_arch}}
path: ${{ env.RELEASE_PATH }}
@@ -221,7 +221,7 @@ jobs:
run: python ./.github/scripts/get_release_version.py
- name: Setup Go
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
+ uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: go.mod
cache-dependency-path: go.sum
@@ -261,7 +261,7 @@ jobs:
- name: Upload container image artifacts (PR)
if: github.event_name == 'pull_request'
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: container-images-${{ env.REL_VERSION }}
path: ./dist/images/
@@ -296,7 +296,7 @@ jobs:
- name: Upload build metrics
if: always()
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: build-metrics-${{ github.job }}
path: dist/metrics/
@@ -457,7 +457,7 @@ jobs:
run: python ./.github/scripts/get_release_version.py
- name: Download release artifacts
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
+ uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
pattern: rad_cli_*
merge-multiple: true
diff --git a/.github/workflows/close-stale-prs.yml b/.github/workflows/close-stale-prs.yml
index 3ff23f5413..0baeab2dbe 100644
--- a/.github/workflows/close-stale-prs.yml
+++ b/.github/workflows/close-stale-prs.yml
@@ -20,7 +20,7 @@ jobs:
permissions:
pull-requests: write
steps:
- - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
+ - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
with:
repo-token: ${{ github.token }}
stale-pr-message: |
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 04a71fd725..fb99f636db 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -41,7 +41,7 @@ jobs:
- name: Filter
id: filter
- uses: tj-actions/changed-files@8cba46e29c11878d930bca7870bb54394d3e8b21 # v47.0.2
+ uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
with:
json: true
escape_json: false
@@ -96,7 +96,7 @@ jobs:
- name: Setup Go
if: matrix.language == 'go'
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
+ uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: go.mod
cache: true
@@ -109,7 +109,7 @@ jobs:
- name: Initialize CodeQL
if: ${{ !startsWith(matrix.language, 'custom-') }}
- uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
+ uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
with:
config-file: .github/configs/.codeql.yml
languages: ${{ matrix.language }}
@@ -117,7 +117,7 @@ jobs:
- name: Auto build
if: matrix.build-mode == 'autobuild'
- uses: github/codeql-action/autobuild@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
+ uses: github/codeql-action/autobuild@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
with:
working-directory: ${{ matrix.working-directory }}
@@ -129,21 +129,21 @@ jobs:
- name: Perform GoSec Analysis
if: matrix.language == 'custom-gosec'
- uses: securego/gosec@398ad549bbf1a51dc978fd966169f660c59774de # v2.23.0
+ uses: securego/gosec@271492bcd930ef72dfb9d00e5bb9544b3b407fb5 # v2.24.0
with:
args: -no-fail -fmt sarif -out gosec-results.sarif ./...
continue-on-error: true
- name: Upload GoSec result
if: ${{ always() && matrix.language == 'custom-gosec' }}
- uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
+ uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
with:
sarif_file: gosec-results.sarif
wait-for-processing: true
- name: Perform CodeQL Analysis
if: ${{ !startsWith(matrix.language, 'custom-') }}
- uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
+ uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
id: codeql-analyze
with:
category: /language:${{matrix.language}}
@@ -152,7 +152,7 @@ jobs:
- name: Upload CodeQL result
if: ${{ always() && !startsWith(matrix.language, 'custom-') }}
- uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
+ uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
with:
sarif_file: ${{ format('{0}/{1}.sarif', steps.codeql-analyze.outputs.sarif-output, matrix.language) }}
wait-for-processing: true
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index 3f8640dcc2..38451cbff7 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -32,6 +32,6 @@ jobs:
persist-credentials: false
- name: Run Dependency Review
- uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
+ uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3
with:
comment-summary-in-pr: on-failure
diff --git a/.github/workflows/functional-test-cloud.yaml b/.github/workflows/functional-test-cloud.yaml
index fe5c8586e9..8ab1dfd8d0 100644
--- a/.github/workflows/functional-test-cloud.yaml
+++ b/.github/workflows/functional-test-cloud.yaml
@@ -272,7 +272,7 @@ jobs:
persist-credentials: false
- name: Setup Go
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
+ uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: go.mod
cache-dependency-path: go.sum
@@ -344,7 +344,7 @@ jobs:
DOCKER_TAG_VERSION: ${{ env.REL_VERSION }}
- name: Upload CLI binary
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: ${{ env.RAD_CLI_ARTIFACT_NAME }}
path: |
@@ -565,14 +565,14 @@ jobs:
persist-credentials: false
- name: Setup Go
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
+ uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: go.mod
cache-dependency-path: go.sum
cache: true
- name: Download rad CLI
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
+ uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: ${{ env.RAD_CLI_ARTIFACT_NAME }}
path: ${{ runner.temp }}/rad-cli-artifact
@@ -915,7 +915,7 @@ jobs:
- name: Upload container logs
if: always()
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: ${{ matrix.name }}_container_logs
path: ./${{ env.RADIUS_CONTAINER_LOG_BASE }}
@@ -937,7 +937,7 @@ jobs:
kubectl get events -n $namespace > recipes/pod-logs/events.txt
- name: Upload Terraform recipe publishing logs
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: always()
with:
name: ${{ matrix.name }}_recipes-pod-logs
diff --git a/.github/workflows/functional-test-noncloud.yaml b/.github/workflows/functional-test-noncloud.yaml
index af96a7ea2f..f279452004 100644
--- a/.github/workflows/functional-test-noncloud.yaml
+++ b/.github/workflows/functional-test-noncloud.yaml
@@ -217,7 +217,7 @@ jobs:
persist-credentials: false
- name: Setup Go
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
+ uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: go.mod
cache-dependency-path: go.sum
@@ -233,7 +233,7 @@ jobs:
make generate-bicep-types VERSION=${{ env.REL_VERSION == 'edge' && 'latest' || env.REL_VERSION }}
- name: Upload Radius Bicep types artifacts
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: ${{ matrix.name }}_radius_bicep_types
path: ./hack/bicep-types-radius/generated
@@ -532,7 +532,7 @@ jobs:
kubectl get events -n $namespace > func-nc/radius-logs-events/${{ matrix.name }}/events.txt
- name: Upload Pod logs for failed tests
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: always() && steps.radius-logs-events.outcome == 'success'
with:
name: ${{ matrix.name }}-radius-pod-logs
@@ -554,7 +554,7 @@ jobs:
- name: Upload container logs
if: always()
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: ${{ matrix.name }}_container_logs
path: ./${{ env.RADIUS_CONTAINER_LOG_BASE }}
@@ -576,7 +576,7 @@ jobs:
kubectl get events -n $namespace > recipes/pod-logs/events.txt
- name: Upload Terraform recipe publishing logs
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: always()
with:
name: ${{ matrix.name }}_recipes-pod-logs
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
index 868974c987..9fb0a9d9e0 100644
--- a/.github/workflows/lint.yaml
+++ b/.github/workflows/lint.yaml
@@ -58,7 +58,7 @@ jobs:
persist-credentials: false
- name: Setup Go
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
+ uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: go.mod
cache-dependency-path: go.sum
diff --git a/.github/workflows/long-running-azure.yaml b/.github/workflows/long-running-azure.yaml
index a6c8c7113a..c0cd47698f 100644
--- a/.github/workflows/long-running-azure.yaml
+++ b/.github/workflows/long-running-azure.yaml
@@ -168,7 +168,7 @@ jobs:
./.github/scripts/checkout-release-codebase.sh
- name: Setup Go
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
+ uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: ${{ steps.checkout-release-codebase.outputs.release-dir }}/go.mod
cache-dependency-path: ${{ steps.checkout-release-codebase.outputs.release-dir }}/go.sum
@@ -426,7 +426,7 @@ jobs:
- name: Upload container logs
if: always()
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: all_container_logs
path: ./${{ env.RADIUS_CONTAINER_LOG_BASE }}
diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml
index ce84aeb40a..90021bae6a 100644
--- a/.github/workflows/publish-docs.yaml
+++ b/.github/workflows/publish-docs.yaml
@@ -77,7 +77,7 @@ jobs:
# Setup dependencies
- name: Setup Go
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
+ uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: radius/go.mod
cache-dependency-path: radius/go.sum
@@ -109,7 +109,7 @@ jobs:
run: python docs/.github/scripts/generate_resource_references.py ./radius/hack/bicep-types-radius/generated/ ./docs/docs/content/reference/resources
- name: Upload resource reference docs
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: resource-docs
path: docs/docs/content/reference/resources
@@ -122,7 +122,7 @@ jobs:
go run cmd/docgen/main.go ../docs/docs/content/reference/cli
- name: Upload CLI docs
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: cli-docs
path: docs/docs/content/reference/cli
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 01c9d2b304..d4f9ee51ef 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -54,7 +54,7 @@ jobs:
- name: Get supported versions from versions.yaml
id: get-supported-versions
- uses: mikefarah/yq@2be0094729a1006f61e8339ce9934bfb3cbb549f # v4.52.2
+ uses: mikefarah/yq@5a7e72a743649b1b3a47d1a1d8214f3453173c51 # v4.52.4
with:
# Get a comma-separated list of supported versions
cmd: yq '.supported[].version' versions.yaml | tr '\n' ',' | sed 's/,$//'
@@ -194,7 +194,7 @@ jobs:
- name: Get supported versions from versions.yaml
id: get-supported-versions
- uses: mikefarah/yq@2be0094729a1006f61e8339ce9934bfb3cbb549f # v4.52.2
+ uses: mikefarah/yq@5a7e72a743649b1b3a47d1a1d8214f3453173c51 # v4.52.4
with:
# Get a comma-separated list of supported versions
cmd: yq '.supported[].version' ./radius/versions.yaml | tr '\n' ',' | sed 's/,$//'
diff --git a/.github/workflows/scorecard.yaml b/.github/workflows/scorecard.yaml
index c78cb126aa..72d9a5137d 100644
--- a/.github/workflows/scorecard.yaml
+++ b/.github/workflows/scorecard.yaml
@@ -60,7 +60,7 @@ jobs:
# uploads of run results in SARIF format to the repository Actions tab.
# https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts
- name: Upload artifact
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: OpenSSF scan results
path: results.sarif
@@ -68,6 +68,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: Upload to code-scanning
- uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
+ uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
with:
sarif_file: results.sarif
diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml
index 8d5fbc6e20..bfb7233d94 100644
--- a/.github/workflows/unit-tests.yaml
+++ b/.github/workflows/unit-tests.yaml
@@ -26,10 +26,19 @@ env:
GOTESTSUMVERSION: 1.13.0
jobs:
+ changes:
+ name: Changes
+ uses: ./.github/workflows/__changes.yml
+ permissions:
+ contents: read
+ pull-requests: read
+
unit-tests:
name: Run Unit Tests
runs-on: ubuntu-24.04
timeout-minutes: 30
+ needs: [changes]
+ if: needs.changes.outputs.only_changed != 'true'
permissions:
contents: read
checks: write
@@ -42,7 +51,7 @@ jobs:
persist-credentials: false
- name: Setup Go
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
+ uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: go.mod
cache-dependency-path: go.sum
@@ -62,13 +71,20 @@ jobs:
go install gotest.tools/gotestsum@v${{ env.GOTESTSUMVERSION }}
make test
+ - name: Run make test-validate-cli (CLI integration tests)
+ env:
+ GOTESTSUM_OPTS: --junitfile ./dist/unit_test/cli_results.xml
+ GOTEST_OPTS: -race -coverprofile ./dist/unit_test/cli_coverage.out
+ run: |
+ make test-validate-cli
+
- name: Upload coverage to Codecov
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
with:
# This secret is unavailable for fork PRs; Codecov falls back to tokenless upload.
token: ${{ secrets.CODECOV_TOKEN }}
codecov_yml_path: ./.codecov.yml
- files: ./dist/unit_test/ut_coverage.out
+ files: ./dist/unit_test/ut_coverage.out,./dist/unit_test/cli_coverage.out
fail_ci_if_error: false
verbose: true
diff --git a/.github/workflows/validate-bicep.yaml b/.github/workflows/validate-bicep.yaml
index 4b44139a13..1e53a14eb5 100644
--- a/.github/workflows/validate-bicep.yaml
+++ b/.github/workflows/validate-bicep.yaml
@@ -64,7 +64,7 @@ jobs:
persist-credentials: false
- name: Setup Go
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
+ uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: go.mod
cache: true
diff --git a/build/test.mk b/build/test.mk
index c308ff38ac..c11f4a3f3f 100644
--- a/build/test.mk
+++ b/build/test.mk
@@ -56,7 +56,7 @@ endif
.PHONY: test
test: test-get-envtools test-helm ## Runs unit tests, excluding kubernetes controller tests
- KUBEBUILDER_ASSETS="$(shell $(ENV_SETUP) use -p path ${K8S_VERSION} --arch amd64)" CGO_ENABLED=1 $(GOTEST_TOOL) -v ./pkg/... $(GOTEST_OPTS)
+ KUBEBUILDER_ASSETS="$(shell $(ENV_SETUP) use -p path ${K8S_VERSION} --arch amd64)" CGO_ENABLED=1 $(GOTEST_TOOL) ./pkg/... $(GOTEST_OPTS)
.PHONY: test-compile
test-compile: test-get-envtools ## Compiles all tests without running them
@@ -73,7 +73,7 @@ test-get-envtools:
.PHONY: test-validate-cli
test-validate-cli: ## Run cli integration tests
- CGO_ENABLED=1 $(GOTEST_TOOL) -coverpkg= ./pkg/cli/cmd/... ./cmd/rad/... -timeout ${TEST_TIMEOUT} -v -parallel 5 $(GOTEST_OPTS)
+ CGO_ENABLED=1 $(GOTEST_TOOL) ./pkg/cli/cmd/... ./cmd/rad/... -timeout ${TEST_TIMEOUT} $(GOTEST_OPTS)
.PHONY: test-functional-all
test-functional-all: test-functional-ucp test-functional-kubernetes test-functional-corerp test-functional-cli test-functional-msgrp test-functional-daprrp test-functional-datastoresrp test-functional-samples test-functional-dynamicrp-noncloud ## Runs all functional tests
diff --git a/pkg/cli/manifest/registermanifest.go b/pkg/cli/manifest/registermanifest.go
index b8cf9bf65e..ffdc0c95a6 100644
--- a/pkg/cli/manifest/registermanifest.go
+++ b/pkg/cli/manifest/registermanifest.go
@@ -34,7 +34,13 @@ import (
const (
initialBackoff = 2 * time.Second
- maxRetries = 5
+ // maxRetries is the maximum number of retry attempts for 409 Conflict errors.
+ // With exponential backoff starting at 2s, this allows approximately 1022 seconds
+ // (~17 minutes) of retry time (2s + 4s + 8s + 16s + 32s + 64s + 128s + 256s + 512s).
+ // This is necessary to handle async operations that may take several minutes
+ // to complete, especially during UCP initialization when multiple resources
+ // are being created sequentially. See: https://github.com/radius-project/radius/issues/11017
+ maxRetries = 10
)
// RegisterFile registers a manifest file
diff --git a/pkg/controller/reconciler/deployment_reconciler_test.go b/pkg/controller/reconciler/deployment_reconciler_test.go
index 4316ea762f..cf50889fe6 100644
--- a/pkg/controller/reconciler/deployment_reconciler_test.go
+++ b/pkg/controller/reconciler/deployment_reconciler_test.go
@@ -17,6 +17,8 @@ limitations under the License.
package reconciler
import (
+ "context"
+ "errors"
"fmt"
"testing"
"time"
@@ -42,7 +44,7 @@ import (
const (
deploymentTestWaitDuration = time.Second * 10
- deploymentTestWaitInterval = time.Second * 1
+ deploymentTestWaitInterval = time.Millisecond * 200
deploymentTestControllerDelayInterval = time.Millisecond * 100
)
@@ -85,8 +87,10 @@ func SetupDeploymentTest(t *testing.T) (*mockRadiusClient, client.Client) {
require.NoError(t, err)
go func() {
- err := mgr.Start(ctx)
- require.NoError(t, err)
+ // Cannot use require/assert here - accessing testing.T from a non-test goroutine causes a data race.
+ if err := mgr.Start(ctx); err != nil && !errors.Is(err, context.Canceled) {
+ panic(fmt.Sprintf("manager exited with error: %v", err))
+ }
}()
return radius, mgr.GetClient()
diff --git a/pkg/controller/reconciler/deploymentresource_reconciler_test.go b/pkg/controller/reconciler/deploymentresource_reconciler_test.go
index 2908088b98..781e2c24d4 100644
--- a/pkg/controller/reconciler/deploymentresource_reconciler_test.go
+++ b/pkg/controller/reconciler/deploymentresource_reconciler_test.go
@@ -17,6 +17,8 @@ limitations under the License.
package reconciler
import (
+ "context"
+ "errors"
"fmt"
"testing"
"time"
@@ -38,7 +40,7 @@ import (
const (
DeploymentResourceTestWaitDuration = time.Second * 10
- DeploymentResourceTestWaitInterval = time.Second * 1
+ DeploymentResourceTestWaitInterval = time.Millisecond * 200
DeploymentResourceTestControllerDelayInterval = time.Millisecond * 100
TestDeploymentResourceNamespace = "deploymentresource-basic"
@@ -93,8 +95,10 @@ func SetupDeploymentResourceTest(t *testing.T) (*mockRadiusClient, *sdkclients.M
require.NoError(t, err)
go func() {
- err := mgr.Start(ctx)
- require.NoError(t, err)
+ // Cannot use require/assert here - accessing testing.T from a non-test goroutine causes a data race.
+ if err := mgr.Start(ctx); err != nil && !errors.Is(err, context.Canceled) {
+ panic(fmt.Sprintf("manager exited with error: %v", err))
+ }
}()
return mockRadiusClient, mockResourceDeploymentsClient, mgr.GetClient()
diff --git a/pkg/controller/reconciler/deploymenttemplate_reconciler_test.go b/pkg/controller/reconciler/deploymenttemplate_reconciler_test.go
index 0c63a38473..68bb63aa05 100644
--- a/pkg/controller/reconciler/deploymenttemplate_reconciler_test.go
+++ b/pkg/controller/reconciler/deploymenttemplate_reconciler_test.go
@@ -17,8 +17,10 @@ limitations under the License.
package reconciler
import (
+ "context"
"encoding/json"
"errors"
+ "fmt"
"os"
"path"
"testing"
@@ -42,7 +44,7 @@ import (
const (
deploymentTemplateTestWaitDuration = time.Second * 10
- deploymentTemplateTestWaitInterval = time.Second * 1
+ deploymentTemplateTestWaitInterval = time.Millisecond * 200
deploymentTemplateTestControllerDelayInterval = time.Millisecond * 100
)
@@ -101,8 +103,10 @@ func SetupDeploymentTemplateTest(t *testing.T) (*mockRadiusClient, *sdkclients.M
require.NoError(t, err)
go func() {
- err := mgr.Start(ctx)
- require.NoError(t, err)
+ // Cannot use require/assert here - accessing testing.T from a non-test goroutine causes a data race.
+ if err := mgr.Start(ctx); err != nil && !errors.Is(err, context.Canceled) {
+ panic(fmt.Sprintf("manager exited with error: %v", err))
+ }
}()
return mockRadiusClient, mockResourceDeploymentsClient, mgr.GetClient()
diff --git a/pkg/controller/reconciler/flux_controller_test.go b/pkg/controller/reconciler/flux_controller_test.go
index 53435ced83..4268af4904 100644
--- a/pkg/controller/reconciler/flux_controller_test.go
+++ b/pkg/controller/reconciler/flux_controller_test.go
@@ -431,7 +431,7 @@ func makeGitRepository(namespacedName types.NamespacedName, url string) sourcev1
func waitForGitRepositoryToExistWithGeneration(ctx context.Context, k8sClient k8sclient.Client, key k8sclient.ObjectKey, obj k8sclient.Object, generation int64) error {
timeout := 10 * time.Second
- interval := 1 * time.Second
+ interval := 200 * time.Millisecond
deadlineCtx, deadlineCancel := context.WithTimeout(ctx, timeout)
defer deadlineCancel()
@@ -462,7 +462,7 @@ func waitForGitRepositoryToExistWithGeneration(ctx context.Context, k8sClient k8
func waitForDeploymentTemplateToExistWithGeneration(ctx context.Context, k8sClient k8sclient.Client, key types.NamespacedName, obj k8sclient.Object, generation int64) error {
timeout := 10 * time.Second
- interval := 1 * time.Second
+ interval := 200 * time.Millisecond
deadlineCtx, deadlineCancel := context.WithTimeout(ctx, timeout)
defer deadlineCancel()
diff --git a/pkg/controller/reconciler/recipe_reconciler_test.go b/pkg/controller/reconciler/recipe_reconciler_test.go
index 1910b7d836..a66944d318 100644
--- a/pkg/controller/reconciler/recipe_reconciler_test.go
+++ b/pkg/controller/reconciler/recipe_reconciler_test.go
@@ -17,7 +17,9 @@ limitations under the License.
package reconciler
import (
+ "context"
"errors"
+ "fmt"
"testing"
"github.com/radius-project/radius/pkg/cli/clients_new/generated"
@@ -73,8 +75,10 @@ func SetupRecipeTest(t *testing.T) (*mockRadiusClient, client.Client) {
require.NoError(t, err)
go func() {
- err := mgr.Start(ctx)
- require.NoError(t, err)
+ // Cannot use require/assert here - accessing testing.T from a non-test goroutine causes a data race.
+ if err := mgr.Start(ctx); err != nil && !errors.Is(err, context.Canceled) {
+ panic(fmt.Sprintf("manager exited with error: %v", err))
+ }
}()
return radius, mgr.GetClient()
diff --git a/pkg/controller/reconciler/recipe_webhook_test.go b/pkg/controller/reconciler/recipe_webhook_test.go
index 311c074989..a0231dc233 100644
--- a/pkg/controller/reconciler/recipe_webhook_test.go
+++ b/pkg/controller/reconciler/recipe_webhook_test.go
@@ -17,7 +17,9 @@ limitations under the License.
package reconciler
import (
+ "context"
"crypto/tls"
+ "errors"
"fmt"
"net"
"net/http"
@@ -287,8 +289,10 @@ func setupWebhookTest(t *testing.T) (*mockRadiusClient, client.Client) {
require.NoError(t, err)
go func() {
- err := mgr.Start(ctx)
- require.NoError(t, err)
+ // Cannot use require/assert here - accessing testing.T from a non-test goroutine causes a data race.
+ if err := mgr.Start(ctx); err != nil && !errors.Is(err, context.Canceled) {
+ panic(fmt.Sprintf("manager exited with error: %v", err))
+ }
}()
// wait for the webhook server to get ready
diff --git a/pkg/controller/reconciler/shared_test.go b/pkg/controller/reconciler/shared_test.go
index 6990f7adbe..e9155bd592 100644
--- a/pkg/controller/reconciler/shared_test.go
+++ b/pkg/controller/reconciler/shared_test.go
@@ -39,7 +39,7 @@ import (
const (
recipeTestWaitDuration = time.Second * 10
- recipeTestWaitInterval = time.Second * 1
+ recipeTestWaitInterval = time.Millisecond * 200
recipeTestControllerDelayInterval = time.Millisecond * 100
)
diff --git a/pkg/ucp/integrationtests/radius/proxy_test.go b/pkg/ucp/integrationtests/radius/proxy_test.go
index 5c93754d61..dc21c0d1fd 100644
--- a/pkg/ucp/integrationtests/radius/proxy_test.go
+++ b/pkg/ucp/integrationtests/radius/proxy_test.go
@@ -275,7 +275,8 @@ func Test_RadiusPlane_ResourceAsync(t *testing.T) {
err := json.Unmarshal(response.Body.Bytes(), resource)
require.NoError(t, err)
require.Equal(t, message, *resource.Properties.Message)
- require.Equal(t, string(v1.ProvisioningStateAccepted), *resource.Properties.ProvisioningState)
+ // Updating or Accepted are valid states, so test for not being Terminal.
+ require.False(t, v1.ProvisioningState(*resource.Properties.ProvisioningState).IsTerminal())
})
t.Run("Complete PUT", func(t *testing.T) {
diff --git a/pkg/upgrade/preflight/config_check_test.go b/pkg/upgrade/preflight/config_check_test.go
index 573c8f647d..f7d01f0477 100644
--- a/pkg/upgrade/preflight/config_check_test.go
+++ b/pkg/upgrade/preflight/config_check_test.go
@@ -21,6 +21,8 @@ import (
"errors"
"fmt"
"io/fs"
+ "os"
+ "path/filepath"
"testing"
"github.com/radius-project/radius/pkg/cli/filesystem"
@@ -115,11 +117,11 @@ func TestCustomConfigValidationCheck_BasicValidation(t *testing.T) {
func TestCustomConfigValidationCheck_ChartValidation(t *testing.T) {
t.Parallel()
- // For chart validation tests, we'll check if real chart exists using default filesystem
+ // For chart validation tests, we'll check if real chart exists using the OS filesystem
// since this is just for the skip check
- defaultFS := filesystem.NewMemMapFileSystem()
+ osFS := filesystem.NewOSFS()
realChartPath := DefaultChartPath
- if _, err := defaultFS.Stat(realChartPath); errors.Is(err, fs.ErrNotExist) {
+ if _, err := osFS.Stat(realChartPath); errors.Is(err, fs.ErrNotExist) {
t.Skipf("Radius chart not found at %s, skipping chart validation tests", realChartPath)
}
@@ -157,9 +159,10 @@ func TestCustomConfigValidationCheck_ChartValidation(t *testing.T) {
},
{
name: "valid set-file with chart",
- setupFiles: func(t *testing.T, fs filesystem.FileSystem) []string {
- tmpFile := "/tmp/config.yaml"
- err := fs.WriteFile(tmpFile, []byte("custom-config-content"), 0644)
+ setupFiles: func(t *testing.T, _ filesystem.FileSystem) []string {
+ tmpDir := t.TempDir()
+ tmpFile := filepath.Join(tmpDir, "config.yaml")
+ err := os.WriteFile(tmpFile, []byte("custom-config-content"), 0644)
require.NoError(t, err)
return []string{fmt.Sprintf("global.rootCA.cert=%s", tmpFile)}
},
@@ -196,20 +199,14 @@ func TestCustomConfigValidationCheck_ChartValidation(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
- // For chart validation, we need to use the real filesystem
- // since the chart is on disk
- memFS := filesystem.NewMemMapFileSystem()
-
- // Setup files if needed
+ // Setup files if needed (writes real temp files to disk)
setFileParams := tt.setFileParams
if tt.setupFiles != nil {
- setFileParams = tt.setupFiles(t, memFS)
+ setFileParams = tt.setupFiles(t, osFS)
}
- // Use the real chart path
+ // Use the real chart path and OS filesystem for chart loading
check := NewCustomConfigValidationCheck(tt.setParams, setFileParams, realChartPath, nil)
- // Use memFS for file parameters but default FS will be used for chart loading
- check.fs = memFS
pass, msg, err := check.Run(context.Background())
diff --git a/pkg/upgrade/preflight/version_check.go b/pkg/upgrade/preflight/version_check.go
index 5742e23e94..1442eb806e 100644
--- a/pkg/upgrade/preflight/version_check.go
+++ b/pkg/upgrade/preflight/version_check.go
@@ -133,6 +133,13 @@ func (v *VersionCompatibilityCheck) isValidUpgradeVersion(currentVersion, target
return false, fmt.Sprintf("Skipping multiple major versions not supported. Expected next major version: %d.0.0", current.Major()+1), nil
}
+ // Allow upgrades within the same minor version (patch bumps, prerelease upgrades)
+ // e.g., 0.55.0-rc4 -> 0.55.0-rc5, 0.55.0-rc5 -> 0.55.0, 0.55.0 -> 0.55.1
+ // Same-version case (e.g., 0.55.0 -> 0.55.0) is already rejected by the Equal check above.
+ if target.Major() == current.Major() && target.Minor() == current.Minor() {
+ return true, "", nil
+ }
+
// Allow increment of minor version by exactly 1
if target.Major() == current.Major() && target.Minor() == current.Minor()+1 {
return true, "", nil
diff --git a/pkg/upgrade/preflight/version_check_test.go b/pkg/upgrade/preflight/version_check_test.go
index aeea4f3cc7..918703a856 100644
--- a/pkg/upgrade/preflight/version_check_test.go
+++ b/pkg/upgrade/preflight/version_check_test.go
@@ -67,6 +67,27 @@ func TestVersionCompatibilityCheck_Run(t *testing.T) {
expectSuccess: false,
expectMessage: "Only incremental version upgrades are supported. Expected next version: 0.41.0",
},
+ {
+ name: "valid prerelease upgrade same version",
+ currentVersion: "0.55.0-rc4",
+ targetVersion: "0.55.0-rc5",
+ expectSuccess: true,
+ expectMessage: "Upgrade from 0.55.0-rc4 to 0.55.0-rc5 is valid",
+ },
+ {
+ name: "valid prerelease to release upgrade",
+ currentVersion: "v0.55.0-rc5",
+ targetVersion: "v0.55.0",
+ expectSuccess: true,
+ expectMessage: "Upgrade from v0.55.0-rc5 to v0.55.0 is valid",
+ },
+ {
+ name: "valid patch version upgrade",
+ currentVersion: "v0.55.0",
+ targetVersion: "v0.55.1",
+ expectSuccess: true,
+ expectMessage: "Upgrade from v0.55.0 to v0.55.1 is valid",
+ },
}
for _, tt := range tests {
@@ -117,6 +138,31 @@ func TestValidateVersionJump(t *testing.T) {
expectValid: false,
expectMessage: "Downgrading is not supported",
},
+ {
+ name: "safe prerelease upgrade",
+ currentVersion: "0.55.0-rc4",
+ targetVersion: "0.55.0-rc5",
+ expectValid: true,
+ },
+ {
+ name: "safe prerelease to release",
+ currentVersion: "0.55.0-rc5",
+ targetVersion: "0.55.0",
+ expectValid: true,
+ },
+ {
+ name: "safe patch bump",
+ currentVersion: "v0.55.0",
+ targetVersion: "v0.55.1",
+ expectValid: true,
+ },
+ {
+ name: "same version rejected",
+ currentVersion: "v0.55.0",
+ targetVersion: "v0.55.0",
+ expectValid: false,
+ expectMessage: "Target version is the same as current version",
+ },
}
for _, tt := range tests {
diff --git a/versions.yaml b/versions.yaml
index 2eb752d993..7881bd1069 100644
--- a/versions.yaml
+++ b/versions.yaml
@@ -1,6 +1,6 @@
supported:
- channel: '0.55'
- version: 'v0.55.0-rc5'
+ version: 'v0.55.0-rc6'
- channel: '0.54'
version: 'v0.54.0'
deprecated: