Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 12 additions & 3 deletions docs/orchestration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1252,9 +1252,18 @@ wildcard :P both bind. The one listener that is not a squatter is the
service's own kept container: a Docker store with `keep-running` leaves its
container up for the next run to adopt, still publishing the service's port.
An address is let through only when the container listening there is the one
the service's agent will adopt — its name in this run's workspace and naming
scope, codefly-owned, labelled with this run's recovery scope, running, not
ephemeral, and publishing that port on that loopback family; any other holder,
the service owns: Core's exact parent-service label must match the loaded
module/service, and the logical name label must match an actual Docker name.
The container must also be codefly-owned, labelled with this run's recovery
scope and a compatible namespace, running, not ephemeral, and publishing that
port on that loopback family. Agents bind each Docker environment through
Core's `WithServiceIdentity`; CLI does not infer parent identity from suffixes.
For older unlabelled containers, the existing exact bare name in this run's
workspace and naming scope remains the only compatibility exception. A present
conflicting or malformed parent label refuses even that exception. Old suffixed
containers need owner-directed recovery preserving their data; the CLI does not
relabel or delete them. Parent attribution proves ownership, while the agent
owns actual reuse of its concrete containers. Any other holder,
including one on the other family beside it, is refused. Not covered: a
process that binds a loopback address on the port after that probe, and a
routed module that crashes or is withdrawn mid-run, whose port the hop keeps
Expand Down
34 changes: 7 additions & 27 deletions docs/supported-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,13 @@ When `go.mod` moves to a new core release line, update the `core` field in
`go test ./pkg/conformance/...` before the full coverage and race gates.
`TestMatrixCoreMatchesGoMod` refuses a stale declared version.

The current pin is a `v0.19.7` pseudo-version of core: `257e6b23`, branch
`chore/envelope-on-0.19.6`, which merges `v0.19.6` (with the git source
declaration, codefly-dev/core#832) into `5522c4da`, the commit carrying the
module envelope that `pkg/delivery/signing` uses. `v0.19.6` carries
`b81d92e6`, the merge of codefly-dev/core#812 — the module manifest
(`solutionhost.ModuleManifest`, one signed document per module, whose
workload is `(service, container)` and which names no build) and
`workloadidentity`, which a native run attests its workloads with. Its
companion is `github.com/codefly-dev/sdk-go/workcontext` at `70dc7a54`
(codefly-dev/sdk-go#80, a credential carries no build seal). Releasing a CLI
on it requires core `v0.19.6` and a `workcontext` release past `v0.1.0` to
be tagged first. The previous pin, a `v0.19.5` pseudo-version, carried the
`runtime-started-artifact/v1` contract (codefly-dev/core#796,
codefly-dev/cli#989), which this CLI no longer reads. On top of `v0.19.2`, `v0.19.4` adds
the issuer's non-consuming `Authority.ReadBack` and moves kustomize back to
`v0.21.1`, whose kube-openapi does not pull `structured-merge-diff/v7` (the
`v0.21.3` that `v0.19.2` and `v0.19.3` carried broke every build compiling
client-go). `v0.19.2` added a
solution manifest's scoped `api.consumes` and its consumption contract (#784)
and an issuer-assigned Work Context session (#783). `v0.19.1`
verifies delivered carriers
against pinned platform keys (#773), projects a service account entirely on a
caller-supplied filesystem (#774, #777), and carries ToolProjection and slot-only authority (#755), ciguard (#750), caller tokens,
image-lock/publish evidence (#759), and configuration-origin evidence (#746),
in addition to #752's composition-provenance contract. This source matrix does
not qualify released agents for the new
`deployment-composition-provenance/v1` capability.
The current pin targets Core's `v0.19.8` release line. The exact source
revision is declared in `go.mod`; the CLI consumes Core's container parent
attribution and release acquisition APIs. These source gates qualify the CLI
against that selected revision. They do not qualify separately compiled agents
or imply that the target Core release has been published. Agents creating
sidecar containers must adopt the attribution API in their own source and
qualify their lifecycle before consumers select the resulting release.

A missing receipt can be a consequence of an earlier failure: coverage and race
run `go test -failfast`, so a failed package can stop the npm tests in
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261026023425-000d44495d93
github.com/codefly-dev/authority/libraries/fixture-composition/go v0.0.0-20261027171336-5178f01696e3
github.com/codefly-dev/authority/libraries/hop/go v0.0.0-20261026175541-3afad7224855
github.com/codefly-dev/core v0.19.7-0.20261026194550-a8599bd3991a
github.com/codefly-dev/core v0.19.8-0.20261027203111-6eadd92dd156
github.com/codefly-dev/golor v0.1.3
github.com/codefly-dev/llm v0.1.7
github.com/codefly-dev/sdk-go/workcontext v0.1.1-0.20261018052022-70dc7a546458
Expand Down Expand Up @@ -175,7 +175,7 @@ require (
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/buildkit v0.32.2 // indirect
github.com/moby/buildkit v0.34.1 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
Expand All @@ -195,7 +195,7 @@ require (
github.com/sergi/go-diff v1.4.0 // indirect
github.com/shoenig/go-m1cpu v0.2.2 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.10.0 // indirect
github.com/sirupsen/logrus v1.10.2 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/standard-webhooks/standard-webhooks/libraries v0.0.1 // indirect
Expand Down Expand Up @@ -243,7 +243,7 @@ require (
golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect
golang.org/x/net v0.60.0 // indirect
golang.org/x/text v0.42.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/time v0.16.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20261012220534-4341edf3f941 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261026023425-00
github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261026023425-000d44495d93/go.mod h1:1rmjrVyr0h7zOuwOHS9i+BoeS9dr3XNvtupLZFwY6As=
github.com/codefly-dev/core v0.19.7-0.20261026194550-a8599bd3991a h1:ar8ERpN9n9ZL29LZ63mqBZNLPZ7OpcnQ1+D1g89DbPY=
github.com/codefly-dev/core v0.19.7-0.20261026194550-a8599bd3991a/go.mod h1:1ina3kP+YSAL4tlZvnEYd4P8bFGnJECfkP86ojeeF1A=
github.com/codefly-dev/core v0.19.8-0.20261027203111-6eadd92dd156 h1:OLQ/LG4+KGRz3crTc8zgQlxTS/Ta3Yh7OUFu+12N5u4=
github.com/codefly-dev/core v0.19.8-0.20261027203111-6eadd92dd156/go.mod h1:NB7x1UZ01rYTfEZEl4vUFD1A+fgOXhsWbgA3M8x4ThA=
github.com/codefly-dev/golor v0.1.3 h1:xmo+ceyJFRYZdvpWE2fNd0jeaadp/Ibm1BnganiGKOc=
github.com/codefly-dev/golor v0.1.3/go.mod h1:sl/u/K1l7J0Pr3xyVZp8fOJYQItKKst1No9JqgzLLoY=
github.com/codefly-dev/gortk v0.2.0 h1:7bOlS5valYz2zil+fZctQNcPCYBcPj86abcw9N8h1hQ=
Expand All @@ -133,6 +135,7 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/log v0.2.0 h1:BewD/umNgVnoczglOpX8eRMyEy5t5iPlu5AIpnWDONc=
github.com/containerd/typeurl/v2 v2.3.0 h1:HZHPhRWo5XMy3QGQoPrUzbW/2ckwjfweHmOwlkIrPAQ=
github.com/containerd/typeurl/v2 v2.3.0/go.mod h1:Qk+PAdUYArVj41TnGi6rJ+48RF0PkcTc4i/taoBcK0w=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
Expand Down Expand Up @@ -332,6 +335,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/buildkit v0.32.2 h1:Sfy7+u6dUv/2yuBc9KCoK70Re8atuV8aPZ5UOC068Vc=
github.com/moby/buildkit v0.32.2/go.mod h1:0GB/EJ1d+4VIVqIAgy3asaoGkVXy7IrDfVy7mPhOvg8=
github.com/moby/buildkit v0.34.1 h1:jVdxsXX0UpU0q1Et+9lFezPmh9sGE6vF0TrKZaGlfy4=
github.com/moby/buildkit v0.34.1/go.mod h1:e+Za70y2a3nL1U2Y0P8TvhPyKp2Ok2NS+Q4siSArDiM=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
Expand Down Expand Up @@ -406,6 +411,8 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.10.0 h1:T8MxJJXVZkfcC5zSRMRAg2F8+lxjmUCGGWPzFxO+Msc=
github.com/sirupsen/logrus v1.10.0/go.mod h1:FXZFonkDAnFozmO+5hGAFvB0Yg9/j2SIhA/QuIkP180=
github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q=
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
Expand Down Expand Up @@ -514,6 +521,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0 h1:UFpxO
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0/go.mod h1:YBGjxe3lt0jtXoEXxGrGhv/jM7oUBUDVK7zhAvNyEjc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 h1:QBajQ2SrwQijzHyZbQlPsuIzpl/ll8DY6wPWsajeGcI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0/go.mod h1:08ZQLjrPLQ6R4kAXvuOvODEer5Yh4CoFvll5qB2BCI8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0 h1:KrC1YrQeSt46ITMWAbgQx1M1eV1/1TKzttrBzymPmss=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.47.0 h1:N3YQCxjxQ/bMjyc3heladfRm9t9RTksGQH8z4w6yU/0=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.47.0/go.mod h1:Mp8HOFqcaUyypCuGv9IhDdTHnJ56lSudSHMd+pVSCEA=
go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI=
Expand Down Expand Up @@ -581,6 +589,8 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE=
golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI=
golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA=
Expand Down
2 changes: 1 addition & 1 deletion pkg/conformance/matrix.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schema_version": 1,
"cli": "source",
"core": "v0.19.7",
"core": "v0.19.8",
"rows": [
{
"id": "linux-amd64-source",
Expand Down
50 changes: 32 additions & 18 deletions pkg/orchestration/adopted_container_ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"time"

"github.com/codefly-dev/core/resources"
"github.com/codefly-dev/core/runners/dockerrun"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
Expand Down Expand Up @@ -40,22 +41,16 @@ var listOwnedContainers = func(ctx context.Context, scopeID string) ([]container

// adoptedContainerAddresses answers which loopback addresses the first-Init
// port guard must not treat as a squatter: the ones published by the
// container this service's agent will adopt. A Docker store with
// container attributed to this service. A Docker store with
// keep-running leaves its container up so the next run reattaches to it by
// name, and that container still publishes the service's deterministic port —
// the one this Init proposes.
// name, and that container still publishes the service's deterministic port.
// The agent decides actual reuse during Init; this check establishes ownership.
//
// The container is identified the way the agent's runner reuses it: the name
// every Core service agent gives it (dockerrun.ContainerName over the
// service's unique name in the run's workspace and naming scope, as
// Base.UniqueWithWorkspace spells it), owned by codefly,
// labelled with this flow's exact recovery scope and, when it carries one, its
// namespace (the rule dockerrun applies before reusing a container), running,
// and not an ephemeral one, which is never reused across sessions. An address
// is excused only for the loopback family the container publishes on: a
// 127.0.0.1 or 0.0.0.0 publication covers 127.0.0.1, a ::1 or :: one covers
// ::1, so a squatter on the other family is still refused. Anything this
// cannot establish — no recovery identity, no Docker engine — excuses nothing.
// Core supplies exact parent-service attribution for containers with arbitrary
// agent-owned names. Scope, namespace, lifecycle, the concrete name and the
// published loopback family are checked separately. Unlabelled containers retain
// only the existing exact-bare-name compatibility path; suffixes prove nothing.
// A present conflicting or malformed parent label always refuses the exception.
func (runner *Runner) adoptedContainerAddresses(ctx context.Context) func(address string) bool {
scopeID, namespace, ok := strings.Cut(runner.containerRecoveryIdentity, ":")
if !ok || scopeID == "" || runner.instance == nil || runner.instance.Identity == nil ||
Expand All @@ -78,21 +73,40 @@ func (runner *Runner) adoptedContainerAddresses(ctx context.Context) func(addres
containers, _ = listOwnedContainers(ctx, scopeID)
}
return slices.ContainsFunc(containers, func(c container.Summary) bool {
return adoptableContainer(&c, name, scopeID, namespace) && publishesAddress(&c, address)
return adoptableContainer(&c, identity, name, scopeID, namespace) && publishesAddress(&c, address)
})
}
}

func adoptableContainer(c *container.Summary, name, scopeID, namespace string) bool {
func adoptableContainer(c *container.Summary, service *resources.ServiceIdentity, name, scopeID, namespace string) bool {
labels := c.Labels
if labels[dockerrun.LabelCodeflyOwner] != "true" || labels[dockerrun.LabelCodeflyRecoveryScope] != scopeID ||
labels[dockerrun.LabelCodeflyName] != name || labels[dockerrun.LabelCodeflyEphemeral] == "true" || c.State != "running" {
labels[dockerrun.LabelCodeflyEphemeral] == "true" || c.State != "running" {
return false
}
if found := labels[dockerrun.LabelCodeflyRecoveryNamespace]; found != "" && found != namespace {
return false
}
return slices.Contains(c.Names, "/"+name)
parent, err := dockerrun.ContainerServiceIdentity(labels)
if err != nil || service == nil {
return false
}
logicalName := labels[dockerrun.LabelCodeflyName]
if logicalName == "" {
return false
}
if parent != nil {
if parent.Unique() != service.Unique() {
return false
}
} else if logicalName != name {
// Compatibility for the pre-existing exact-name guard only. An absent
// label cannot extend ownership to a suffixed container.
return false
}
return slices.ContainsFunc(c.Names, func(dockerName string) bool {
return strings.TrimPrefix(dockerName, "/") == logicalName
})
}

// publishesAddress reports whether c publishes the loopback host:port address
Expand Down
47 changes: 47 additions & 0 deletions pkg/orchestration/adopted_container_ports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func keptContainer(name string, port uint16) container.Summary {
State: "running",
Labels: map[string]string{
dockerrun.LabelCodeflyOwner: "true",
dockerrun.LabelCodeflyService: "web/gateway",
dockerrun.LabelCodeflyName: name,
dockerrun.LabelCodeflyRecoveryScope: testRecoveryScope,
dockerrun.LabelCodeflyRecoveryNamespace: testRecoveryNamespace,
Expand Down Expand Up @@ -52,6 +53,44 @@ func TestFirstInitAdoptsItsOwnKeptContainerAndRefusesAnyOtherHolder(t *testing.T
{name: "another service's name", mutate: func(c *container.Summary) {
c.Names = []string{"/codefly-other"}
c.Labels[dockerrun.LabelCodeflyName] = "codefly-other"
c.Labels[dockerrun.LabelCodeflyService] = "other/service"
}, refused: true},
// An agent may run several containers for one service and names each
// after itself: service-object-storage publishes the service's own
// grpc port from `<unique>-gateway`, beside `<unique>-minio` and
// a separate `<unique>-network` Docker network. Each container belongs
// to this service.
{name: "its agent's gateway container", mutate: func(c *container.Summary) {
suffixed(c, "-gateway")
}, refused: false},
{name: "its agent's minio container", mutate: func(c *container.Summary) {
suffixed(c, "-minio")
}, refused: false},
// A sibling service is still not this one, suffix or no suffix.
{name: "a sibling service's container", mutate: func(c *container.Summary) {
c.Names = []string{"/codefly-other-web-gateway-minio"}
c.Labels[dockerrun.LabelCodeflyName] = "codefly-other-web-gateway-minio"
c.Labels[dockerrun.LabelCodeflyService] = "other/gateway"
}, refused: true},
{name: "distinct sibling sharing the name prefix", mutate: func(c *container.Summary) {
suffixed(c, "-admin")
c.Labels[dockerrun.LabelCodeflyService] = "web/gateway-admin"
}, refused: true},
{name: "label and concrete name disagree", mutate: func(c *container.Summary) {
c.Names[0] += "-other"
}, refused: true},
{name: "legacy exact bare name", mutate: func(c *container.Summary) {
delete(c.Labels, dockerrun.LabelCodeflyService)
}},
{name: "legacy suffix proves no parent", mutate: func(c *container.Summary) {
suffixed(c, "-gateway")
delete(c.Labels, dockerrun.LabelCodeflyService)
}, refused: true},
{name: "malformed parent on exact bare name", mutate: func(c *container.Summary) {
c.Labels[dockerrun.LabelCodeflyService] = "web/gateway/other"
}, refused: true},
{name: "conflicting parent on exact bare name", mutate: func(c *container.Summary) {
c.Labels[dockerrun.LabelCodeflyService] = "web/other"
}, refused: true},
{name: "ephemeral", mutate: func(c *container.Summary) { c.Labels[dockerrun.LabelCodeflyEphemeral] = "true" }, refused: true},
{name: "not running", mutate: func(c *container.Summary) { c.State = "exited" }, refused: true},
Expand Down Expand Up @@ -108,6 +147,14 @@ func TestAdoptedContainerAddressesExcuseNothingWithoutARecoveryIdentity(t *testi
require.False(t, runner.adoptedContainerAddresses(context.Background())("127.0.0.1:5432"))
}

// suffixed renames a kept container the way an agent that runs more than one
// container for a service does, through Core's own ContainerName.
func suffixed(c *container.Summary, suffix string) {
name := c.Labels[dockerrun.LabelCodeflyName] + suffix
c.Names = []string{"/" + name}
c.Labels[dockerrun.LabelCodeflyName] = name
}

// A publication covers only its own loopback family.
func TestPublishesAddressIsPerLoopbackFamily(t *testing.T) {
c := container.Summary{Ports: []container.Port{{IP: "0.0.0.0", PublicPort: 5432, Type: "tcp"}}}
Expand Down
Loading
Loading