1package domain
2
3import (
4 "crypto/sha256"
5 "fmt"
6 "strings"
7)
8
9// DefaultNetworkPrefix applies when no installation prefix is configured.
10const DefaultNetworkPrefix = "gordon"
11
12// Network roles identifying what a Gordon-managed network is for.
13const (
14 // AppNetworkRolePrivate marks the incarnation-owned network of one app.
15 // Containers of different apps never share it.
16 AppNetworkRolePrivate = "private"
17 // AppNetworkRoleShared marks a declared cross-app shared network.
18 AppNetworkRoleShared = "shared"
19)
20
21// Network ownership label keys. Management follows the same fail-closed
22// rule as volumes: an existing network is reused only when its labels
23// prove Gordon ownership; anything else is refused, never adopted.
24const (
25 // LabelAppNetworkRole carries AppNetworkRolePrivate/AppNetworkRoleShared.
26 LabelAppNetworkRole = "gordon.app.network.role"
27 // LabelAppNetworkName carries the declared shared-network name, so a
28 // derived runtime name can be tied back to exactly one declaration.
29 LabelAppNetworkName = "gordon.app.network.name"
30)
31
32// NormalizeNetworkPrefix returns the configured prefix or the default.
33func NormalizeNetworkPrefix(prefix string) string {
34 if strings.TrimSpace(prefix) == "" {
35 return DefaultNetworkPrefix
36 }
37 return prefix
38}
39
40// AppPrivateNetworkName derives the incarnation-owned network name from
41// the app's stable internal UUID. The name is deterministic so recovery
42// reattaches the same network, and it never derives from the public name:
43// removing and re-adding an app under the same name yields a different
44// incarnation UUID and therefore a different network.
45func AppPrivateNetworkName(prefix, appID string) string {
46 sum := sha256.Sum256([]byte(appID))
47 return fmt.Sprintf("%s-app-%x", NormalizeNetworkPrefix(prefix), sum[:6])
48}
49
50// AppSharedNetworkName derives the Gordon-managed name of a declared
51// shared network. Deriving from the declaration means two apps that
52// declare the same name join the same network, while a foreign network
53// that happens to use the name is never mistaken for it.
54func AppSharedNetworkName(prefix, declared string) string {
55 sum := sha256.Sum256([]byte(declared))
56 return fmt.Sprintf("%s-shared-%x", NormalizeNetworkPrefix(prefix), sum[:6])
57}
58
59// AppPrivateNetworkLabels is the ownership label set stamped on an
60// incarnation-owned network.
61func AppPrivateNetworkLabels(app, appID string) map[string]string {
62 labels := map[string]string{
63 LabelManaged: "true",
64 LabelApp: app,
65 LabelAppNetworkRole: AppNetworkRolePrivate,
66 }
67 if appID != "" {
68 labels[LabelAppID] = appID
69 }
70 return labels
71}
72
73// AppSharedNetworkLabels is the ownership label set stamped on a declared
74// shared network. A shared network belongs to the installation, not to a
75// single app, so it carries no app identity.
76func AppSharedNetworkLabels(declared string) map[string]string {
77 return map[string]string{
78 LabelManaged: "true",
79 LabelAppNetworkRole: AppNetworkRoleShared,
80 LabelAppNetworkName: declared,
81 }
82}
83
84// NetworkOwnedBy reports whether an observed network's labels prove the
85// exact expected Gordon ownership. Extra labels are tolerated; every
86// expected key must be present and equal, so a network created with
87// different ownership (or none) is never adopted.
88func NetworkOwnedBy(observed, expected map[string]string) bool {
89 if len(expected) == 0 {
90 return false
91 }
92 for key, want := range expected {
93 if observed[key] != want {
94 return false
95 }
96 }
97 return true
98}
99
100// AppServiceSharedNetworks returns the declared shared-network memberships
101// that the named service joins, preserving declaration order and
102// de-duplicating by network name.
103func AppServiceSharedNetworks(spec AppSpec, service string) []AppSharedNetwork {
104 var memberships []AppSharedNetwork
105 seen := map[string]struct{}{}
106 for _, network := range spec.Networks {
107 if _, ok := seen[network.Network]; ok {
108 continue
109 }
110 for _, member := range network.Services {
111 if member != service {
112 continue
113 }
114 seen[network.Network] = struct{}{}
115 memberships = append(memberships, network)
116 break
117 }
118 }
119 return memberships
120}