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: