Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
900f00b
fix: clear pending VCS status when silence flags enabled and no proje…
jamengual Aug 15, 2025
4245005
style: run go fmt on modified files
jamengual Aug 15, 2025
0859d4a
Merge branch 'main' into fix/silence-vcs-status-stuck-pending
jamengual Aug 16, 2025
2850deb
Merge branch 'main' into fix/silence-vcs-status-stuck-pending
jamengual Aug 19, 2025
7311a82
Merge branch 'main' into fix/silence-vcs-status-stuck-pending
jamengual Aug 20, 2025
702f0e3
Merge branch 'main' into fix/silence-vcs-status-stuck-pending
jamengual Aug 21, 2025
cba4c78
Merge branch 'main' into fix/silence-vcs-status-stuck-pending
jamengual Aug 25, 2025
12c20d0
Merge branch 'main' into fix/silence-vcs-status-stuck-pending
jamengual Aug 26, 2025
9d6e555
fix: apply silence VCS status logic to API controller
jamengual Aug 26, 2025
07e6c37
fix: format code with gofmt to resolve linting issue
jamengual Aug 26, 2025
c372411
fix: prevent VCS status when silence enabled and no projects found
jamengual Aug 27, 2025
a848d7e
fix: prevent pending VCS status when silence enabled at source
jamengual Aug 27, 2025
8c4cac0
feat: implement StatusManager architecture for centralized status han…
jamengual Aug 28, 2025
f63e346
Complete StatusManager TODOs: fork PR detection, status querying, cle…
jamengual Aug 28, 2025
b84a842
feat: implement StatusManager as single source of truth for VCS statu…
jamengual Aug 28, 2025
775fbd2
docs: move StatusManager documentation to runatlantis.io folder
jamengual Aug 28, 2025
5a4204f
Merge upstream/main into feature/status-manager - resolve conflicts
jamengual Sep 6, 2025
ac9d896
Merge remote-tracking branch 'origin/main' into feature/status-manager
jamengual Sep 6, 2025
7bed5a0
fix: add StatusManager to E2E test to fix CI failures
jamengual Sep 6, 2025
c6e1de7
fix: resolve linting issues (gofmt and staticcheck)
jamengual Sep 6, 2025
002c6b4
fix: update internal tests to use StatusManager for ApplyCommandRunner
jamengual Sep 6, 2025
ed535cf
fix: update ApplyCommandRunner tests to work with StatusManager refac…
jamengual Sep 6, 2025
13a6104
fix: add StatusManager mock to TestRunAutoplanCommand_FailedPreWorkfl…
jamengual Sep 6, 2025
eac695b
fix: run gofmt -s to fix linting issues
jamengual Sep 6, 2025
b48acb0
fix: address linting errors
jamengual Sep 6, 2025
afd285d
Merge branch 'main' into feature/status-manager
jamengual Sep 7, 2025
0c24644
Merge branch 'main' into feature/status-manager
jamengual Sep 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 272 additions & 0 deletions runatlantis.io/docs/status-manager.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
# StatusManager Architecture

## Overview

The StatusManager is the centralized system for managing VCS (Version Control System) status checks in Atlantis. It provides policy-driven decisions about when to set, clear, or silence status checks, ensuring consistent behavior across all command types while respecting user configuration preferences.

## Architecture

### Core Components

```

Check failure on line 11 in runatlantis.io/docs/status-manager.md

View workflow job for this annotation

GitHub Actions / Website Check

Fenced code blocks should have a language specified

runatlantis.io/docs/status-manager.md:11 MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md040.md
┌─────────────────┐ ┌──────────────────┐ ┌────────────────────┐
│ Command Runners │───▶│ StatusManager │───▶│ CommitStatusUpdater│
└─────────────────┘ └──────────────────┘ └────────────────────┘
┌──────────────┐
│ StatusPolicy │
└──────────────┘
```

### Components Description

#### StatusManager

Check failure on line 24 in runatlantis.io/docs/status-manager.md

View workflow job for this annotation

GitHub Actions / Website Check

Headings should be surrounded by blank lines

runatlantis.io/docs/status-manager.md:24 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "#### StatusManager"] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md022.md
- **Purpose**: Central orchestrator for all status updates

Check failure on line 25 in runatlantis.io/docs/status-manager.md

View workflow job for this annotation

GitHub Actions / Website Check

Lists should be surrounded by blank lines

runatlantis.io/docs/status-manager.md:25 MD032/blanks-around-lists Lists should be surrounded by blank lines [Context: "- **Purpose**: Central orchest..."] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md032.md
- **Responsibilities**:

Check failure on line 26 in runatlantis.io/docs/status-manager.md

View workflow job for this annotation

GitHub Actions / Website Check

Trailing spaces

runatlantis.io/docs/status-manager.md:26:24 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md009.md
- Receives status requests from command runners
- Delegates decision-making to StatusPolicy
- Executes approved status operations via CommitStatusUpdater
- **Interface**: Provides methods for command lifecycle events (start, end, no projects found)

#### StatusPolicy

Check failure on line 32 in runatlantis.io/docs/status-manager.md

View workflow job for this annotation

GitHub Actions / Website Check

Headings should be surrounded by blank lines

runatlantis.io/docs/status-manager.md:32 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "#### StatusPolicy"] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md022.md
- **Purpose**: Encapsulates business logic for status decisions

Check failure on line 33 in runatlantis.io/docs/status-manager.md

View workflow job for this annotation

GitHub Actions / Website Check

Lists should be surrounded by blank lines

runatlantis.io/docs/status-manager.md:33 MD032/blanks-around-lists Lists should be surrounded by blank lines [Context: "- **Purpose**: Encapsulates bu..."] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md032.md
- **Implementation**: `SilencePolicy` - handles all silence flag combinations
- **Responsibilities**:
- Evaluates silence flags
- Detects fork PRs
- Returns status decisions with clear reasoning

#### CommitStatusUpdater

Check failure on line 40 in runatlantis.io/docs/status-manager.md

View workflow job for this annotation

GitHub Actions / Website Check

Headings should be surrounded by blank lines

runatlantis.io/docs/status-manager.md:40 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "#### CommitStatusUpdater"] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md022.md
- **Purpose**: Low-level interface for VCS status updates

Check failure on line 41 in runatlantis.io/docs/status-manager.md

View workflow job for this annotation

GitHub Actions / Website Check

Lists should be surrounded by blank lines

runatlantis.io/docs/status-manager.md:41 MD032/blanks-around-lists Lists should be surrounded by blank lines [Context: "- **Purpose**: Low-level inter..."] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md032.md
- **Responsibilities**: Direct communication with VCS providers (GitHub, GitLab, etc.)

## Status Operations

### Operation Types

1. **OperationSet**: Set a specific status (pending, success, failed)
2. **OperationClear**: Clear/reset existing status

Check failure on line 49 in runatlantis.io/docs/status-manager.md

View workflow job for this annotation

GitHub Actions / Website Check

Trailing spaces

runatlantis.io/docs/status-manager.md:49:51 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md009.md
3. **OperationSilence**: Do nothing (no VCS interaction)

### Decision Flow

```mermaid
graph TD
A[Command Event] --> B[StatusManager]
B --> C[StatusPolicy.DecideOn*]
C --> D{Fork PR?}
D -->|Yes + SilenceForkPRErrors| E[OperationSilence]
D -->|No| F{Silence Flags?}
F -->|Enabled| G[OperationSilence]
F -->|Disabled| H[OperationSet]
E --> I[StatusManager.executeDecision]
G --> I
H --> I
I --> J{Operation Type}
J -->|Set| K[CommitStatusUpdater.Update*]
J -->|Clear| L[CommitStatusUpdater.UpdateCombinedCount]
J -->|Silence| M[No Action]
```

## Command Integration

### Command Lifecycle Events

#### 1. Command Start (`HandleCommandStart`)

Check failure on line 76 in runatlantis.io/docs/status-manager.md

View workflow job for this annotation

GitHub Actions / Website Check

Headings should be surrounded by blank lines

runatlantis.io/docs/status-manager.md:76 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "#### 1. Command Start (`HandleCommandStart`)"] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md022.md
Called when any command begins execution.

**Triggers**:
- Plan commands via autoplan
- Manual commands via comments

**Behavior**:
- **Normal**: Sets status to `PendingCommitStatus`
- **Fork PR + SilenceForkPRErrors**: No status set
- **SilenceVCSStatusNoProjects enabled**: No status set

#### 2. Command End (`HandleCommandEnd`)
Called when command execution completes.

**Parameters**: Command result with success/failure information

**Behavior**:
- **Has Errors**: Sets `FailedCommitStatus`
- **Success**: Sets `SuccessCommitStatus` with project counts
- **Fork PR + SilenceForkPRErrors**: No status set
- **Silence flags enabled**: No status set

#### 3. No Projects Found (`HandleNoProjectsFound`)
Called when no projects match the command criteria.

**Behavior**:
- **SilenceNoProjects**: No comment, may set status based on other flags
- **SilenceVCSStatusNoPlans** (plan commands): No status set
- **SilenceVCSStatusNoProjects**: No status set
- **Default**: Sets `SuccessCommitStatus` with 0/0 counts

## Silence Flags Configuration

### Available Flags

| Flag | Environment Variable | Default | Purpose |
|------|---------------------|---------|---------|
| `SilenceNoProjects` | `ATLANTIS_SILENCE_NO_PROJECTS` | `false` | Suppress PR comments when no projects found |
| `SilenceVCSStatusNoPlans` | `ATLANTIS_SILENCE_VCS_STATUS_NO_PLANS` | `false` | Suppress VCS status for plan commands with no projects |
| `SilenceVCSStatusNoProjects` | `ATLANTIS_SILENCE_VCS_STATUS_NO_PROJECTS` | `false` | Suppress VCS status for all commands with no projects |
| `SilenceForkPRErrors` | `ATLANTIS_SILENCE_FORK_PR_ERRORS` | `false` | Suppress status updates for fork PRs |

### Flag Combinations

#### Complete Silence
```yaml
# atlantis.yaml or CLI flags
silence-no-projects: true
silence-vcs-status-no-plans: true
silence-vcs-status-no-projects: true
silence-fork-pr-errors: true
```
**Result**: No VCS status checks appear when no projects are found or for fork PRs.

#### Selective Silence
```yaml
# Only silence plan commands when no projects found
silence-vcs-status-no-plans: true
```
**Result**: Plan commands don't set status when no projects found, but apply commands still do.

## Fork PR Handling

### Detection
Fork PRs are identified using: `ctx.HeadRepo.Owner != ctx.Pull.BaseRepo.Owner`

### Behavior
When `SilenceForkPRErrors` is enabled:
- No status checks are set for any command on fork PRs
- Prevents permission errors when Atlantis can't update status on forks
- Overrides all other status logic

## Migration from Legacy Status Logic

### Before StatusManager
- Status updates scattered across command runners
- Inconsistent silence flag handling
- Duplicate status setting logic
- Difficult to maintain and extend

### After StatusManager
- Single source of truth for all status decisions
- Consistent policy application across commands
- Clean separation of concerns
- Easy to extend with new status policies

### Breaking Changes
**None** - The StatusManager maintains full backward compatibility with existing configurations.

## Implementation Details

### Key Interfaces

```go
// StatusManager - Main interface for status operations
type StatusManager interface {
HandleCommandStart(ctx *command.Context, cmdName command.Name) error
HandleCommandEnd(ctx *command.Context, cmdName command.Name, result *command.Result) error
HandleNoProjectsFound(ctx *command.Context, cmdName command.Name) error

// Direct status methods (bypass policy)
SetPending(ctx *command.Context, cmdName command.Name) error
SetSuccess(ctx *command.Context, cmdName command.Name, numSuccess, numTotal int) error
SetFailure(ctx *command.Context, cmdName command.Name, err error) error
}

// StatusPolicy - Decision making interface
type StatusPolicy interface {
DecideOnStart(ctx *command.Context, cmdName command.Name) StatusDecision
DecideOnEnd(ctx *command.Context, cmdName command.Name, result *command.Result) StatusDecision
DecideOnNoProjects(ctx *command.Context, cmdName command.Name) StatusDecision
}
```

### Command Runner Integration

All command runners now use StatusManager instead of calling CommitStatusUpdater directly:

```go
// Before
if err := c.commitStatusUpdater.UpdateCombined(..., models.PendingCommitStatus, ...); err != nil {
// handle error
}

// After
if err := c.StatusManager.HandleCommandStart(ctx, command.Plan); err != nil {
// StatusManager handles policy decisions automatically
}
```

## Debugging and Troubleshooting

### Log Messages

StatusManager provides detailed logging for debugging:

```
DEBUG status decision: set - command starting - setting pending status
DEBUG status decision: silence - silence VCS status enabled (SilenceVCSStatusNoProjects)
DEBUG status decision: clear - resetting status for command completion
```

### Common Issues

#### Status Stuck in Pending
**Cause**: Command execution interrupted before completion
**Solution**: StatusManager prevents this by not setting pending status when silence flags are enabled

#### No Status Appearing
**Cause**: Silence flags enabled
**Check**: Review `ATLANTIS_SILENCE_*` environment variables
**Solution**: Adjust silence flags based on desired behavior

#### Fork PR Permission Errors
**Cause**: Atlantis trying to set status on external fork
**Solution**: Enable `silence-fork-pr-errors: true`

## Best Practices

### Configuration Recommendations

1. **Production Environments**:
```yaml
silence-fork-pr-errors: true # Prevent permission errors
```

2. **High-Traffic Repos**:
```yaml
silence-vcs-status-no-projects: true # Reduce noise
```

3. **Development/Testing**:
```yaml
# Keep defaults for full visibility
```

### Monitoring

Monitor StatusManager behavior through:
- Atlantis debug logs (`--log-level debug`)
- VCS status check history
- PR comment patterns

## Future Enhancements

Potential improvements to the StatusManager architecture:

1. **Custom Status Policies**: Allow plugins for organization-specific logic
2. **Status Aggregation**: Combine multiple project statuses intelligently
3. **Retry Logic**: Handle transient VCS API failures
4. **Status History**: Track status changes for debugging
5. **Conditional Status**: Set status based on file patterns or project types

## API Reference

See the [StatusManager GoDoc](https://pkg.go.dev/github.com/runatlantis/atlantis/server/events/status) for complete API documentation.
Binary file added runtime.test
Binary file not shown.
14 changes: 14 additions & 0 deletions server/controllers/events/events_controller_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/runatlantis/atlantis/server/events/command"
"github.com/runatlantis/atlantis/server/events/mocks"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/status"
"github.com/runatlantis/atlantis/server/events/vcs"
vcsmocks "github.com/runatlantis/atlantis/server/events/vcs/mocks"
"github.com/runatlantis/atlantis/server/events/webhooks"
Expand Down Expand Up @@ -1311,6 +1312,15 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
logging.SuppressDefaultLogging()
logger := logging.NewNoopLogger(t)

// Create StatusManager with policy
statusPolicy := status.NewSilencePolicy(
false, // silenceNoProjects
false, // silenceVCSStatusNoPlans
false, // silenceVCSStatusNoProjects
false, // silenceForkPRErrors
)
e2eStatusManager := status.NewStatusManager(e2eStatusUpdater, statusPolicy, logger)

eventParser := &events.EventParser{
GithubUser: "github-user",
GithubToken: "github-token",
Expand Down Expand Up @@ -1539,6 +1549,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
parallelPoolSize,
false,
userConfig.QuietPolicyChecks,
e2eStatusManager,
)

e2ePullReqStatusFetcher := vcs.NewPullReqStatusFetcher(e2eVCSClient, "atlantis-test", []string{})
Expand Down Expand Up @@ -1579,6 +1590,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
silenceNoProjects,
false,
e2ePullReqStatusFetcher,
e2eStatusManager,
)

approvePoliciesCommandRunner := events.NewApprovePoliciesCommandRunner(
Expand All @@ -1590,6 +1602,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
silenceNoProjects,
false,
e2eVCSClient,
e2eStatusManager,
)

unlockCommandRunner := events.NewUnlockCommandRunner(
Expand Down Expand Up @@ -1648,6 +1661,7 @@ func setupE2E(t *testing.T, repoDir string, opt setupOption) (events_controllers
PullStatusFetcher: backend,
DisableAutoplan: opt.disableAutoplan,
CommitStatusUpdater: commitStatusUpdater,
StatusManager: e2eStatusManager,
}

repoAllowlistChecker, err := events.NewRepoAllowlistChecker("*")
Expand Down
Loading
Loading