Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
894da3c
refactor: [#301] add PortDerivation trait in domain topology
josecelano Jan 26, 2026
b8db37f
refactor: [#301] implement PortDerivation for PrometheusConfig
josecelano Jan 26, 2026
cdbbda6
refactor: [#301] implement PortDerivation for GrafanaConfig
josecelano Jan 26, 2026
62e02ff
refactor: [#301] implement PortDerivation for TrackerConfig
josecelano Jan 26, 2026
15ae4c9
refactor: [#301] add fixed port functions for Caddy and MySQL in domain
josecelano Jan 26, 2026
9da19b6
refactor: [#301] complete DDD migration - remove TrackerServiceConfig…
josecelano Jan 26, 2026
17a526e
refactor: [#301] rename PrometheusServiceConfig and GrafanaServiceCon…
josecelano Jan 26, 2026
30eb4a1
refactor(domain): [#301] add NetworkDerivation trait and EnabledServi…
josecelano Jan 26, 2026
49a9eef
docs: [#301] update Phase 4 progress - all main goals complete
josecelano Jan 26, 2026
d98df1e
docs: [#301] mark Phase 4 acceptance criteria complete
josecelano Jan 26, 2026
1e28b43
refactor(domain): [#301] replace fixed_ports.rs with domain types for…
josecelano Jan 26, 2026
7a9d035
refactor(infrastructure): [#301] rename service configs to *Context
josecelano Jan 26, 2026
8452fb5
refactor(infrastructure): [#301] remove TrackerPorts type alias
josecelano Jan 26, 2026
f47326f
docs: [#301] update Phase 4 progress - Step 7.5 complete
josecelano Jan 26, 2026
e87b7ae
refactor(infrastructure): [#301] remove legacy fields from TrackerSer…
josecelano Jan 26, 2026
195c80e
refactor(infrastructure): [#301] remove unused fields from Grafana/Pr…
josecelano Jan 26, 2026
1962386
refactor(infrastructure): [#301] extract ServiceTopology shared type
josecelano Jan 26, 2026
2f3314a
fix(domain): [#301] re-enable aggregate tests with clippy allow attri…
josecelano Jan 26, 2026
14a27e4
fix: [#304] add crate-level allow for clippy::large_stack_arrays fals…
josecelano Jan 26, 2026
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
193 changes: 124 additions & 69 deletions docs/issues/301-phase-4-service-topology-ddd-alignment.md

Large diffs are not rendered by default.

114 changes: 114 additions & 0 deletions docs/issues/304-clippy-large-stack-arrays-false-positive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Issue #304: Clippy `large_stack_arrays` False Positive with `vec![]` Macro

**GitHub Issue**: <https://github.com/torrust/torrust-tracker-deployer/issues/304>

## Problem

Clippy reports a false positive `large_stack_arrays` lint when using the `vec![]` macro
to create vectors of `ServiceTopology` items in tests.

### Error Message

```text
error: allocating a local array larger than 16384 bytes
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#large_stack_arrays
= note: `-D clippy::large-stack-arrays` implied by `-D clippy::pedantic`
= help: to override `-D clippy::pedantic` add `#[allow(clippy::large_stack_arrays)]`
```

### Root Cause

This is a known clippy bug where the `vec![]` macro is incorrectly flagged for creating
a large stack array. The `vec![]` macro creates a `Vec<T>` which allocates on the heap,
not the stack. The lint is a false positive.

**Upstream Issue**: <https://github.com/rust-lang/rust-clippy/issues/12586>

### Affected Code

Tests in `src/domain/topology/aggregate.rs` that create vectors of `ServiceTopology`:

```rust
let topology = DockerComposeTopology::new(vec![
ServiceTopology::with_networks(Service::Tracker, vec![Network::Database]),
ServiceTopology::with_networks(Service::MySQL, vec![Network::Database]),
])
.unwrap();
```

### Workarounds Attempted (Did Not Work)

1. **Outer attribute on module**: `#[allow(clippy::large_stack_arrays)]` on `mod tests`
2. **Inner attribute on submodule**: `#![allow(clippy::large_stack_arrays)]` inside submodules
3. **Outer attribute on test function**: `#[allow(clippy::large_stack_arrays)]` on `#[test]` functions
4. **Inner attribute in function body**: `#![allow(clippy::large_stack_arrays)]` inside function

None of these local suppression methods worked because the lint fires during macro
expansion before the allow attributes are processed.

## Solution

Add a **crate-level allow attribute** in `src/lib.rs`:

```rust
// False positive: clippy reports large_stack_arrays for vec![] macro with ServiceTopology
// This is a known issue: https://github.com/rust-lang/rust-clippy/issues/12586
#![allow(clippy::large_stack_arrays)]
```

This is the only approach that successfully suppresses the false positive.

### Trade-offs

- **Downside**: Suppresses the lint crate-wide, potentially hiding legitimate issues
- **Mitigation**: This lint is specifically for stack allocations. Since we use `Vec<T>`
(heap-allocated) throughout the codebase, legitimate triggers are unlikely
- **Future**: Remove this allow once the upstream clippy issue is fixed

## Affected Rust Versions

- Rust 1.93.0 stable (confirmed on GitHub Actions CI)
- Local nightly 1.95.0 does **not** reproduce the issue

### Fix Timeline

| Event | Date | Version |
| ---------------------------------------------------------------------------------------------- | -------------- | ---------------------------------- |
| Fix merged to clippy master ([PR #12624](https://github.com/rust-lang/rust-clippy/pull/12624)) | April 27, 2024 | - |
| Rust 1.79.0 released | June 13, 2024 | Nightly at merge time |
| Rust 1.80.0 released | July 25, 2024 | **Expected first stable with fix** |

### Potential Regression

The fix was merged in April 2024 and should be in Rust 1.80.0+. However, we're still
seeing this error on Rust 1.93.0 (January 2026). This suggests either:

1. **Regression**: The bug may have regressed in a later clippy version
2. **Different code pattern**: Our `vec![]` with `ServiceTopology` (large struct with
`EnumSet` fields) might trigger a variant not covered by the original fix
3. **CI environment**: Some discrepancy in the clippy version used in CI

Consider reporting this as a potential regression if the issue persists after verifying
the clippy version matches the expected behavior.

## Related Clippy Issues

Other `large_stack_arrays` issues (not directly related to our problem):

- [#13774](https://github.com/rust-lang/rust-clippy/issues/13774) - No span for error (closed)
- [#13529](https://github.com/rust-lang/rust-clippy/issues/13529) - Nested const items (closed)
- [#9460](https://github.com/rust-lang/rust-clippy/issues/9460) - Static struct (closed)
- [#4520](https://github.com/rust-lang/rust-clippy/issues/4520) - Original lint creation (open)

## References

- Upstream clippy issue: <https://github.com/rust-lang/rust-clippy/issues/12586>
- Related PR: #303 (Phase 4 Service Topology DDD Alignment)

## Labels

- `bug`
- `workaround`
- `clippy`
- `technical-debt`
85 changes: 25 additions & 60 deletions src/application/steps/rendering/docker_compose_templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ use std::sync::Arc;

use tracing::{info, instrument};

use crate::domain::environment::user_inputs::UserInputs;
use crate::domain::environment::Environment;
use crate::domain::template::TemplateManager;
use crate::domain::tracker::{DatabaseConfig, TrackerConfig};
use crate::domain::topology::EnabledServices;
use crate::domain::tracker::DatabaseConfig;
use crate::infrastructure::templating::docker_compose::template::wrappers::docker_compose::{
DockerComposeContext, DockerComposeContextBuilder, MysqlSetupConfig, TrackerServiceConfig,
DockerComposeContext, DockerComposeContextBuilder, MysqlSetupConfig, TrackerServiceContext,
};
use crate::infrastructure::templating::docker_compose::template::wrappers::env::EnvContext;
use crate::infrastructure::templating::docker_compose::{
Expand Down Expand Up @@ -155,12 +155,8 @@ impl<S> RenderDockerComposeTemplatesStep<S> {
self.environment.admin_token().to_string()
}

fn build_tracker_config(&self) -> TrackerServiceConfig {
fn build_tracker_config(&self) -> TrackerServiceContext {
let tracker_config = self.environment.tracker_config();
let user_inputs = &self.environment.context().user_inputs;

let (udp_tracker_ports, http_tracker_ports_without_tls, http_api_port, http_api_has_tls) =
Self::extract_tracker_ports(tracker_config, user_inputs);

// Determine which features are enabled (affects tracker networks)
let has_prometheus = self.environment.prometheus_config().is_some();
Expand All @@ -169,16 +165,26 @@ impl<S> RenderDockerComposeTemplatesStep<S> {
DatabaseConfig::Mysql(..)
);
let has_caddy = self.has_caddy_enabled();
let has_grafana = self.environment.grafana_config().is_some();

TrackerServiceConfig::new(
udp_tracker_ports,
http_tracker_ports_without_tls,
http_api_port,
http_api_has_tls,
has_prometheus,
has_mysql,
has_caddy,
)
// Build list of enabled services for topology context
let mut enabled_services = Vec::new();
if has_prometheus {
enabled_services.push(crate::domain::topology::Service::Prometheus);
}
if has_grafana {
enabled_services.push(crate::domain::topology::Service::Grafana);
}
if has_mysql {
enabled_services.push(crate::domain::topology::Service::MySQL);
}
if has_caddy {
enabled_services.push(crate::domain::topology::Service::Caddy);
}

let topology_context = EnabledServices::from(&enabled_services);

TrackerServiceContext::from_domain_config(tracker_config, &topology_context)
}

/// Check if Caddy is enabled (HTTPS with at least one TLS-configured service)
Expand All @@ -205,7 +211,7 @@ impl<S> RenderDockerComposeTemplatesStep<S> {

fn create_sqlite_contexts(
admin_token: String,
tracker: TrackerServiceConfig,
tracker: TrackerServiceContext,
) -> (EnvContext, DockerComposeContextBuilder) {
let env_context = EnvContext::new(admin_token);
let builder = DockerComposeContext::builder(tracker);
Expand All @@ -215,7 +221,7 @@ impl<S> RenderDockerComposeTemplatesStep<S> {

fn create_mysql_contexts(
admin_token: String,
tracker: TrackerServiceConfig,
tracker: TrackerServiceContext,
port: u16,
database_name: String,
username: String,
Expand Down Expand Up @@ -312,47 +318,6 @@ impl<S> RenderDockerComposeTemplatesStep<S> {
env_context
}
}

fn extract_tracker_ports(
tracker_config: &TrackerConfig,
user_inputs: &UserInputs,
) -> (Vec<u16>, Vec<u16>, u16, bool) {
// Extract UDP tracker ports (always exposed - no TLS termination via Caddy)
let udp_ports: Vec<u16> = tracker_config
.udp_trackers()
.iter()
.map(|tracker| tracker.bind_address().port())
.collect();

// Get the set of HTTP tracker ports that have TLS enabled
let tls_enabled_ports: std::collections::HashSet<u16> = user_inputs
.tracker()
.http_trackers_with_tls()
.iter()
.map(|(_, port)| *port)
.collect();

// Extract HTTP tracker ports WITHOUT TLS (these need to be exposed)
let http_ports_without_tls: Vec<u16> = tracker_config
.http_trackers()
.iter()
.map(|tracker| tracker.bind_address().port())
.filter(|port| !tls_enabled_ports.contains(port))
.collect();

// Extract HTTP API port
let api_port = tracker_config.http_api().bind_address().port();

// Check if HTTP API has TLS enabled
let http_api_has_tls = user_inputs.tracker().http_api_tls_domain().is_some();

(
udp_ports,
http_ports_without_tls,
api_port,
http_api_has_tls,
)
}
}

#[cfg(test)]
Expand Down
Loading
Loading