-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresolver.go
More file actions
163 lines (148 loc) · 5.48 KB
/
Copy pathresolver.go
File metadata and controls
163 lines (148 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package generate
import (
"fmt"
"strconv"
"strings"
"github.com/open-source-cloud/devstack/internal/config"
)
// secretAttrs are reference attributes whose value is a secret. They are emitted
// as VALUELESS per-service env keys — the value is supplied at runtime via the
// process env (secrets land in M4) and never written into a generated file
// (ARCHITECTURE §7.5). Inline ${ref:...secret} is rejected; secrets must flow
// through env.import so the coupling stays explicit.
var secretAttrs = map[string]bool{
"password": true,
"secretkey": true,
"secret": true,
"token": true,
}
// graphResolver implements config.Resolver against the workspace service graph.
// The per-service context (curProject/curService) is set immediately before each
// service's env values are interpolated.
type graphResolver struct {
model *config.Model
sharedPort map[string]int // shared name → in-network port (from its template's defaultPort)
env map[string]string // host env (injectable for deterministic tests)
profile string
curProject string
curService string
}
func (r *graphResolver) Env(name string) (string, bool) {
v, ok := r.env[name]
return v, ok
}
func (r *graphResolver) Self(attr string) (string, bool) {
switch strings.ToLower(attr) {
case "host", "name", "service":
// A service's in-project-network hostname is its compose service name.
return r.curService, true
case "project":
return r.curProject, true
}
return "", false
}
func (r *graphResolver) Profile() string { return r.profile }
func (r *graphResolver) WorkspaceName() string { return r.model.Workspace.Name }
func (r *graphResolver) Ref(path string) (string, error) {
ref, ok := config.ParseRef(path)
if !ok {
return "", fmt.Errorf("invalid reference %q", path)
}
return r.refAttr(ref)
}
// refAttr resolves a parsed reference's attribute to a concrete value.
func (r *graphResolver) refAttr(ref config.Reference) (string, error) {
attr := strings.ToLower(ref.Attr)
if secretAttrs[attr] {
return "", fmt.Errorf("reference attribute %q is a secret; consume it via env.import, not inline ${ref}", ref.Attr)
}
switch ref.Kind {
case config.RefShared:
return r.sharedAttr(ref.Name, attr)
case config.RefService:
return r.serviceAttr(ref.Project, ref.Name, attr)
default:
return "", fmt.Errorf("unresolvable reference")
}
}
// defaultAWSRegion is the region advertised when a shared AWS-emulation engine
// (LocalStack/ministack) declares no `region` param (spec 28).
const defaultAWSRegion = "us-east-1"
// secondaryPorts maps a shared engine's template name to its non-default
// (secondary) export attrs and the in-network container port each resolves to
// (spec 28 Q-SECONDARY-PORTS). The resolver tracks only one in-network port per
// shared service (sharedPort); these extra admin/monitor/mgmt ports are static
// per the template's declared service command and resolved from this lookup so a
// consumer's ${ref:...<engine>.monitorPort} resolves without a hardcode in the
// generic sharedAttr switch.
var secondaryPorts = map[string]map[string]int{
"nats": {"monitorport": 8222},
"kafka": {"adminport": 9644},
"rabbitmq": {"mgmtport": 15672},
}
// sharedAttr resolves an attribute of a shared service. host/port are stable
// (DNS alias + the engine's default port); user/database default to the CONSUMER
// project name — the per-project role/db provisioned on the shared engine in M2.
// endpoint/region (AWS-emulation engines) and the per-engine secondary admin/
// monitor/mgmt ports are non-secret extras resolved from the alias, the default
// port, the `region` param, and the static secondaryPorts lookup (spec 28).
func (r *graphResolver) sharedAttr(name, attr string) (string, error) {
svc, ok := r.model.Workspace.Shared[name]
if !ok {
return "", fmt.Errorf("shared service %q does not exist%s", name, suggestShared(r.model))
}
switch attr {
case "", "host":
return sharedAlias(name), nil
case "port":
if p := r.sharedPort[name]; p != 0 {
return strconv.Itoa(p), nil
}
return "", fmt.Errorf("shared service %q exposes no default port", name)
case "endpoint":
p := r.sharedPort[name]
if p == 0 {
return "", fmt.Errorf("shared service %q exposes no default port for its endpoint", name)
}
return fmt.Sprintf("http://%s:%d", sharedAlias(name), p), nil
case "region":
if v, ok := svc.Params["region"].(string); ok && v != "" {
return v, nil
}
return defaultAWSRegion, nil
case "user", "accesskey":
return r.curProject, nil
case "database", "db":
return r.curProject, nil
default:
if ports, ok := secondaryPorts[svc.Template]; ok {
if p, ok := ports[attr]; ok {
return strconv.Itoa(p), nil
}
}
return "", fmt.Errorf("unknown attribute %q on shared service %q", attr, name)
}
}
// serviceAttr resolves an attribute of another project's service.
func (r *graphResolver) serviceAttr(project, service, attr string) (string, error) {
p, ok := r.model.Projects[project]
if !ok {
return "", fmt.Errorf("project %q does not exist", project)
}
if _, ok := p.Services[service]; !ok {
return "", fmt.Errorf("service %q does not exist in project %q", service, project)
}
switch attr {
case "", "host", "name":
return service, nil
default:
return "", fmt.Errorf("unknown attribute %q on service %q (cross-project import resolves host only in v1)", attr, service)
}
}
func suggestShared(m *config.Model) string {
names := m.SharedNames()
if len(names) == 0 {
return ""
}
return fmt.Sprintf(" (available shared: %s)", strings.Join(names, ", "))
}