v0.33.7_docker_hostnames
This commit is contained in:
1 parent
f462ca7194
commit
7e625c5876
6 files changed
+90
-73
No files matched your search
@@ -5,6 +5,7 @@
|
||||
|
||||
## 0.33.7
|
||||
|
||||
- Docker agent: suggest container names rather than name-port for newly published vhosts. Append port only for conflicts; preserve stable service keys and existing vhost hostnames.
|
||||
- Replace the redundant **Arrange Overview** visual editor modal with an
|
||||
account-scoped Dashboard preset manager. Save, load/overwrite and delete up
|
||||
to 20 named presets containing Dashboard density, card order/visibility and
|
||||
|
||||
+5
-5
@@ -2,7 +2,7 @@
|
||||
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
|
||||
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
|
||||
c793a8be7f4b76350e04ab36dbeec3792bb2dcf33c87a86f76fa2f330e26dd9f AGENTS.md
|
||||
4d86a27c6b8f8931e4c43bbbd0da1a5880ffafd17bd139bd9ba93fa1e7c02af4 CHANGELOG.md
|
||||
538acee273827bd09041e25fe7ee804284eb66ade85537cf3dcd7b968ec33b43 CHANGELOG.md
|
||||
d5dc2b778af98f52ad52a7515e2d1b4ba8c2e01a04cc6e02564377075b01c501 Cargo.lock
|
||||
7fd5dd1ccae8fac77bbbea4bbf960f5cf762f712ff17a2e629a5dd8629eb7ff9 Cargo.toml
|
||||
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
|
||||
@@ -13,14 +13,14 @@ f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
|
||||
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
|
||||
4834c278430ddf3bf842995735195368f1ac2c3b5c1d749df4f9238caf1e2b25 deploy/rustproxy.service
|
||||
c111f5b20484707b80efb7dd9176acbaa27b63ac78db497261ac234821e1fade dev.sh
|
||||
5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md
|
||||
82fbf6deba6841f843ea5fb419efa03ee820c7fb2af00b1bf4d8f6cddad52b9e docker-agent/README.md
|
||||
7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go
|
||||
5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example
|
||||
bf687a851374d8f1fa59d050f3f76532f7c12f16a6daa7d3c36102c2d9a8ec9c docker-agent/docker.go
|
||||
dcdf8b060d9529ceb8c286d0a592fa4bd876bd7fdcd1a178e4295e13cd88e6c0 docker-agent/docker.go
|
||||
9e9f3f17f38f9b0730f598f65988ce8ef562d2182ef6778fc7292b990e9cf22f docker-agent/go.mod
|
||||
9d77c90767fa929ee8338734d72ce83284d3d965673270f62cb2b1613d587a33 docker-agent/install.sh
|
||||
b68234472bec5cedd67c7cfe1de7d8806a965dfd4101f944aa6320132f18c14e docker-agent/main.go
|
||||
3d4051a1f805d145df3877476e4b87773e74be8e123045065955a5133afdc68c docker-agent/main_test.go
|
||||
34552a436bc88a49d1a82a60875d4bd1c0c0b808759b6a099a7f954f44701ab0 docker-agent/main_test.go
|
||||
68ad007eac216f115809695d3c2c6651e2850ff3d7334685ae6e49bc21b01087 docker-agent/model.go
|
||||
e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/network.go
|
||||
634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service
|
||||
@@ -64,7 +64,7 @@ ec7e32fd19e6ba6ed1732f1f3dd92ac0860609e8c0bbd3c1998840cdfb24bf54 docs/SECURITY.
|
||||
007537ee971463072b2482dfa4fdcf1955f837ced0b711d4eef3b6267d16d4f6 docs/SECURITY_AUDIT_0.31.0.md
|
||||
84d1523a18dc98d266a290ed7fb8fe2624bb37fd54f4f0b991ba37a7624d8a3a docs/SETTINGS.md
|
||||
8fc112cb45a2f35ef5b0dd4371aa6f43aca4df85db62c6ae02e56d74a3b702ae docs/STREAMS.md
|
||||
420d8e3976882ef42c2b7dde409560825db72a22405b023876f9c39055671754 docs/TEST_REPORT.md
|
||||
193704656501643c86470be699d2e1c05b5fbb678f68ffac1b3f5c51172bf433 docs/TEST_REPORT.md
|
||||
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
|
||||
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
|
||||
fe3ec6ca16ff933239c12dacf39dd7b45717c0551a8b9f6ce601bf78136c9e37 docs/TRANSPORT.md
|
||||
|
||||
@@ -44,3 +44,7 @@ go build -trimpath -ldflags='-s -w' -o rustproxy-docker-agent .
|
||||
The inventory endpoint is `GET /v1/inventory` with `Authorization: Bearer <token>`. `/healthz` does not expose Docker inventory. Both are served over HTTPS by default.
|
||||
|
||||
`RUSTPROXY_DOCKER_ADVERTISE_IP` marks a preferred address but does not suppress automatic discovery of the host's other addresses. `RUSTPROXY_DOCKER_PUBLIC_IP_URL` adds the externally observed address when enabled. RustProxy can choose a concrete reported address independently for every Docker-backed vhost.
|
||||
|
||||
## Default vhost hostname suggestions
|
||||
|
||||
The agent reports a DNS-safe Docker container name as `technical_name` (or the Compose service name when the container name is unavailable). RustProxy pre-fills this name for newly published vhosts. For example, `/grafana` suggests `grafana`, not `grafana-3000`. If multiple published services have the same normalized container name, the agent appends the container's internal port; remaining collisions get a deterministic identifier. The stable `service_key` is unchanged. Already saved vhost hostnames are never rewritten by discovery.
|
||||
+43
-6
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -92,20 +93,18 @@ func serviceIdentity(c dockerContainer, p dockerPort) (key, technical string) {
|
||||
project := c.Labels["com.docker.compose.project"]
|
||||
composeService := c.Labels["com.docker.compose.service"]
|
||||
replica := c.Labels["com.docker.compose.container-number"]
|
||||
suffix := strconv.Itoa(int(p.PrivatePort))
|
||||
if project != "" && composeService != "" {
|
||||
if replica == "" {
|
||||
replica = name
|
||||
}
|
||||
key = fmt.Sprintf("compose:%s/%s/%s:%d/%s", project, composeService, replica, p.PrivatePort, p.Type)
|
||||
technicalBase := project + "-" + composeService
|
||||
if replica != "1" {
|
||||
technicalBase += "-" + replica
|
||||
technical = dnsLabel(name)
|
||||
if name == "container" {
|
||||
technical = dnsLabel(composeService)
|
||||
}
|
||||
technical = dnsLabel(technicalBase + "-" + suffix)
|
||||
} else {
|
||||
key = fmt.Sprintf("container:%s:%d/%s", name, p.PrivatePort, p.Type)
|
||||
technical = dnsLabel(name + "-" + suffix)
|
||||
technical = dnsLabel(name)
|
||||
}
|
||||
return key, technical
|
||||
}
|
||||
@@ -163,5 +162,43 @@ func servicesFrom(containers []dockerContainer) []service {
|
||||
out = append(out, *item)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key })
|
||||
// Prefer the Docker container name. Add the internal port only when names
|
||||
// collide; retain stable service keys and prevent any duplicate hostnames.
|
||||
counts := make(map[string]int, len(out))
|
||||
for _, item := range out {
|
||||
counts[item.TechnicalName]++
|
||||
}
|
||||
for i := range out {
|
||||
if counts[out[i].TechnicalName] > 1 {
|
||||
out[i].TechnicalName = dnsLabel(out[i].TechnicalName + "-" + strconv.Itoa(int(out[i].PrivatePort)))
|
||||
}
|
||||
}
|
||||
// Port suffixes can still collide after normalization/truncation.
|
||||
// Resolve these deterministically using a short hash of the stable key.
|
||||
counts = make(map[string]int, len(out))
|
||||
for _, item := range out {
|
||||
counts[item.TechnicalName]++
|
||||
}
|
||||
used := make(map[string]struct{}, len(out))
|
||||
for i := range out {
|
||||
name := out[i].TechnicalName
|
||||
if counts[name] > 1 {
|
||||
digest := sha256.Sum256([]byte(out[i].Key))
|
||||
name = fmt.Sprintf("%s-%x", strings.TrimRight(name[:min(len(name), 48)], "-"), digest[:6])
|
||||
}
|
||||
// Extremely unlikely hash collisions are still resolved without
|
||||
// changing the stable key or depending on Docker listing order.
|
||||
for attempt := 0; ; attempt++ {
|
||||
candidate := name
|
||||
if attempt > 0 {
|
||||
candidate = fmt.Sprintf("%s-%d", name[:min(len(name), 56)], attempt)
|
||||
}
|
||||
if _, exists := used[candidate]; !exists {
|
||||
out[i].TechnicalName = candidate
|
||||
used[candidate] = struct{}{}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func TestServicesFromUsesStableComposeIdentity(t *testing.T) {
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("got %d services", len(got))
|
||||
}
|
||||
if got[0].Key != "compose:lab/web/1:8080/tcp" || got[0].TechnicalName != "lab-web-8080" {
|
||||
if got[0].Key != "compose:lab/web/1:8080/tcp" || got[0].TechnicalName != "web-1" {
|
||||
t.Fatalf("unexpected service %#v", got[0])
|
||||
}
|
||||
if len(got[0].Bindings) != 1 || got[0].Bindings[0].PublicPort != 18080 {
|
||||
@@ -72,6 +72,42 @@ func TestServicesFromKeepsComposeReplicasAndAllBindings(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceNamesPreferContainerAndOnlyAppendPortsForCollisions(t *testing.T) {
|
||||
items := []dockerContainer{
|
||||
{ID: "a", Names: []string{"/grafana"}, Labels: map[string]string{}, Ports: []dockerPort{{PrivatePort: 3000, PublicPort: 3000, Type: "tcp", IP: "0.0.0.0"}}},
|
||||
{ID: "b", Names: []string{"/frontend"}, Labels: map[string]string{"com.docker.compose.project": "example", "com.docker.compose.service": "web", "com.docker.compose.container-number": "1"}, Ports: []dockerPort{{PrivatePort: 80, PublicPort: 8080, Type: "tcp", IP: "0.0.0.0"}}},
|
||||
{ID: "c", Names: []string{"/shared"}, Ports: []dockerPort{{PrivatePort: 80, PublicPort: 8081, Type: "tcp", IP: "0.0.0.0"}, {PrivatePort: 443, PublicPort: 8443, Type: "tcp", IP: "0.0.0.0"}}},
|
||||
}
|
||||
got := servicesFrom(items)
|
||||
names := map[string]string{}
|
||||
for _, item := range got {
|
||||
names[item.Key] = item.TechnicalName
|
||||
}
|
||||
if names["container:grafana:3000/tcp"] != "grafana" || names["compose:example/web/1:80/tcp"] != "frontend" {
|
||||
t.Fatalf("incorrect short names: %#v", names)
|
||||
}
|
||||
if names["container:shared:80/tcp"] != "shared-80" || names["container:shared:443/tcp"] != "shared-443" {
|
||||
t.Fatalf("incorrect collision names: %#v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceNamesRemainUniqueAfterDNSNormalization(t *testing.T) {
|
||||
items := []dockerContainer{
|
||||
{Names: []string{"/A_B"}, Ports: []dockerPort{{PrivatePort: 80, PublicPort: 18080, Type: "tcp"}}},
|
||||
{Names: []string{"/a-b"}, Ports: []dockerPort{{PrivatePort: 80, PublicPort: 18081, Type: "tcp"}}},
|
||||
}
|
||||
got := servicesFrom(items)
|
||||
if len(got) != 2 || got[0].TechnicalName == got[1].TechnicalName {
|
||||
t.Fatalf("names not unique: %#v", got)
|
||||
}
|
||||
reversed := servicesFrom([]dockerContainer{items[1], items[0]})
|
||||
for i := range got {
|
||||
if got[i].TechnicalName != reversed[i].TechnicalName {
|
||||
t.Fatalf("unstable names: %#v vs %#v", got, reversed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServicesFromSkipsLoopbackOnlyBindings(t *testing.T) {
|
||||
items := []dockerContainer{{ID: "abc", Names: []string{"/app"}, Labels: map[string]string{}, Ports: []dockerPort{
|
||||
{IP: "127.0.0.1", PrivatePort: 8080, PublicPort: 18080, Type: "tcp"},
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# RustProxy 0.33.7 - verification report
|
||||
|
||||
## Scope (2026-10-15)
|
||||
|
||||
0.33.7 changes the Dashboard UI without changing proxy traffic handling or
|
||||
telemetry collection:
|
||||
|
||||
- Replace the duplicated **Arrange Overview** editor modal with an account-scoped
|
||||
**Dashboard presets** manager. A named preset stores Dashboard density, card
|
||||
order, visibility and optional custom widths; presets can be saved/updated,
|
||||
loaded and deleted through the existing administrator preferences document.
|
||||
- Keep actual Dashboard editing in the existing inline **Unlock layout** flow and
|
||||
Settings visibility/order controls.
|
||||
- Use the same monospace font family as **CONNECTION ACTIVITY** for Overview chart
|
||||
titles, including compact chart cards and wide charts.
|
||||
- No SQLite schema migration, new telemetry polling loop or proxy-path change is
|
||||
introduced.
|
||||
|
||||
## Checks completed
|
||||
|
||||
- **PASS:** `python scripts/tests/dashboard_presets_0337_browser_test.py` against
|
||||
the real bundled HTML/CSS/JS with a simulated preferences API at 320, 375,
|
||||
768 and 1440px. Covered save/update snapshot data, load, delete, responsive
|
||||
modal layout, removal of the former duplicated editor controls, and chart-title
|
||||
font parity with **CONNECTION ACTIVITY**.
|
||||
- **PASS:** `python scripts/tests/dashboard_modal_0333_browser_test.py` after the
|
||||
0.33.7 preset conversion at 320, 375, 550, 620, 800, 900, 960, 1158, 1440 and
|
||||
1920px with no horizontal viewport overflow.
|
||||
- **PASS:** updated `dashboard_layout_0331_browser_test.py` and
|
||||
`dashboard_resizing_0333_browser_test.py`; inline editing still covers
|
||||
add/hide/reorder/resize, live refresh, cancel/save and persisted custom widths,
|
||||
while opening/closing the preset modal does not mutate the active layout.
|
||||
- **PASS:** all 13 current `scripts/tests/*browser_test.py` simulated-browser
|
||||
regressions, including chart heading/width/fixed CPU/stable scale coverage,
|
||||
progress bars, sidebar, RAM, Web observability and Web Server live metrics.
|
||||
The obsolete CPU autoscale regression was removed because 0.33.5 intentionally
|
||||
replaced that behavior with a fixed 0-100% CPU scale and has its own current
|
||||
regression test.
|
||||
- **PASS:** `node scripts/tests/overview_samples_0330_ui_test.mjs`,
|
||||
`node scripts/tests/dashboard_resume_rate_charts_ui_test.mjs` and
|
||||
`node scripts/tests/dashboard_metric_visuals_ui_test.mjs`.
|
||||
- **PASS:** JSON parsing for `src/admin/openapi.json`, `langs/en_US.json` and
|
||||
`langs/pl_pl.json`; JavaScript syntax checks for the changed preferences/preset
|
||||
files; Python syntax checks for changed browser tests; `python scripts/preflight.py`.
|
||||
- **PASS:** `./dev.sh doctor` reports the source layout as OK and confirms that a
|
||||
C compiler and Python are available.
|
||||
- **PASS:** all eight `migrations/*.sql` applied successfully in two successive
|
||||
cycles to a disposable in-memory SQLite database. This verifies SQL script
|
||||
execution/idempotency in this context, not a production-data upgrade.
|
||||
- **PASS:** parsed `scripts/deploy/upgrade.py --help` and
|
||||
`scripts/migration/migrate_npm.py --help`; no live external upgrade/migration
|
||||
was performed.
|
||||
|
||||
## Not verified in this environment
|
||||
|
||||
- `./dev.sh check` and `./dev.sh test` exit 127 because Cargo/rustc are not
|
||||
installed. Therefore Rust compilation, Rust unit/integration tests, an actual
|
||||
RustProxy process, real SSE traffic, production database upgrade and live load
|
||||
testing are not verified here.
|
||||
- The real-runtime acceptance gate remains open until Rust builds and runs against
|
||||
representative test traffic.
|
||||
Reference in new issue
Block a user