blob: 30e946b42e031b6057302829b7013774c7d8b59a [file] [log] [blame]
giolekva8aa73e82022-07-09 11:34:39 +04001package installer
giolekva050609f2021-12-29 15:51:40 +04002
giolekva8aa73e82022-07-09 11:34:39 +04003import (
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +04004 "bytes"
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +04005 "encoding/json"
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +04006 "fmt"
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +04007 template "html/template"
gio3cdee592024-04-17 10:15:56 +04008 "net"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +04009 "strings"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040010
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040011 "cuelang.org/go/cue"
gio308105e2024-04-19 13:12:13 +040012 "cuelang.org/go/cue/build"
13 "cuelang.org/go/cue/cuecontext"
14 "cuelang.org/go/cue/load"
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040015 cueyaml "cuelang.org/go/encoding/yaml"
giolekva8aa73e82022-07-09 11:34:39 +040016)
giolekva050609f2021-12-29 15:51:40 +040017
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040018// TODO(gio): import
19const cueBaseConfig = `
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040020name: string | *""
21description: string | *""
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040022readme: string | *""
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040023icon: string | *""
24namespace: string | *""
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040025
gio308105e2024-04-19 13:12:13 +040026#AppType: "infra" | "env"
27appType: #AppType | *"env"
28
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040029#Auth: {
30 enabled: bool | *false // TODO(gio): enabled by default?
31 groups: string | *"" // TODO(gio): []string
32}
33
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040034#Network: {
35 name: string
36 ingressClass: string
37 certificateIssuer: string | *""
38 domain: string
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040039 allocatePortAddr: string
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040040}
41
Giorgi Lekveishvili67383962024-03-22 19:27:34 +040042networks: {
43 public: #Network & {
44 name: "Public"
45 ingressClass: "\(global.pcloudEnvName)-ingress-public"
46 certificateIssuer: "\(global.id)-public"
47 domain: global.domain
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040048 allocatePortAddr: "http://port-allocator.\(global.pcloudEnvName)-ingress-public.svc.cluster.local/api/allocate"
Giorgi Lekveishvili67383962024-03-22 19:27:34 +040049 }
50 private: #Network & {
51 name: "Private"
52 ingressClass: "\(global.id)-ingress-private"
53 domain: global.privateDomain
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040054 allocatePortAddr: "http://port-allocator.\(global.id)-ingress-private.svc.cluster.local/api/allocate"
Giorgi Lekveishvili67383962024-03-22 19:27:34 +040055 }
56}
57
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040058#Image: {
59 registry: string | *"docker.io"
60 repository: string
61 name: string
62 tag: string
63 pullPolicy: string | *"IfNotPresent"
64 imageName: "\(repository)/\(name)"
65 fullName: "\(registry)/\(imageName)"
66 fullNameWithTag: "\(fullName):\(tag)"
67}
68
69#Chart: {
70 chart: string
71 sourceRef: #SourceRef
72}
73
74#SourceRef: {
75 kind: "GitRepository" | "HelmRepository"
76 name: string
77 namespace: string // TODO(gio): default global.id
78}
79
80#Global: {
81 id: string | *""
82 pcloudEnvName: string | *""
83 domain: string | *""
84 privateDomain: string | *""
85 namespacePrefix: string | *""
86 ...
87}
88
89#Release: {
gio3cdee592024-04-17 10:15:56 +040090 appInstanceId: string
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040091 namespace: string
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040092 repoAddr: string
93 appDir: string
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040094}
95
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040096#PortForward: {
97 allocator: string
98 protocol: "TCP" | "UDP" | *"TCP"
99 sourcePort: int
100 targetService: string
101 targetPort: int
102}
103
104portForward: [...#PortForward] | *[]
105
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400106global: #Global
107release: #Release
108
109_ingressPrivate: "\(global.id)-ingress-private"
110_ingressPublic: "\(global.pcloudEnvName)-ingress-public"
111_issuerPrivate: "\(global.id)-private"
112_issuerPublic: "\(global.id)-public"
113
gio1de49582024-04-21 08:33:57 +0400114#Ingress: {
115 auth: #Auth
116 network: #Network
117 subdomain: string
118 service: close({
119 name: string
120 port: close({ name: string }) | close({ number: int & > 0 })
121 })
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400122
gio1de49582024-04-21 08:33:57 +0400123 _domain: "\(subdomain).\(network.domain)"
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400124 _authProxyHTTPPortName: "http"
125
126 out: {
127 images: {
128 authProxy: #Image & {
129 repository: "giolekva"
130 name: "auth-proxy"
131 tag: "latest"
132 pullPolicy: "Always"
133 }
134 }
135 charts: {
136 ingress: #Chart & {
137 chart: "charts/ingress"
138 sourceRef: {
139 kind: "GitRepository"
140 name: "pcloud"
141 namespace: global.id
142 }
143 }
144 authProxy: #Chart & {
145 chart: "charts/auth-proxy"
146 sourceRef: {
147 kind: "GitRepository"
148 name: "pcloud"
149 namespace: global.id
150 }
151 }
152 }
153 helm: {
gio1de49582024-04-21 08:33:57 +0400154 if auth.enabled {
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400155 "auth-proxy": {
156 chart: charts.authProxy
157 values: {
158 image: {
159 repository: images.authProxy.fullName
160 tag: images.authProxy.tag
161 pullPolicy: images.authProxy.pullPolicy
162 }
gio1de49582024-04-21 08:33:57 +0400163 upstream: "\(service.name).\(release.namespace).svc.cluster.local"
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400164 whoAmIAddr: "https://accounts.\(global.domain)/sessions/whoami"
165 loginAddr: "https://accounts-ui.\(global.domain)/login"
Giorgi Lekveishvili329af572024-03-25 20:14:41 +0400166 membershipAddr: "http://memberships-api.\(global.id)-core-auth-memberships.svc.cluster.local/api/user"
gio1de49582024-04-21 08:33:57 +0400167 groups: auth.groups
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400168 portName: _authProxyHTTPPortName
169 }
170 }
171 }
172 ingress: {
173 chart: charts.ingress
gio1de49582024-04-21 08:33:57 +0400174 _service: service
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400175 values: {
176 domain: _domain
gio1de49582024-04-21 08:33:57 +0400177 ingressClassName: network.ingressClass
178 certificateIssuer: network.certificateIssuer
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400179 service: {
gio1de49582024-04-21 08:33:57 +0400180 if auth.enabled {
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400181 name: "auth-proxy"
182 port: name: _authProxyHTTPPortName
183 }
gio1de49582024-04-21 08:33:57 +0400184 if !auth.enabled {
185 name: _service.name
186 if _service.port.name != _|_ {
187 port: name: _service.port.name
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400188 }
gio1de49582024-04-21 08:33:57 +0400189 if _service.port.number != _|_ {
190 port: number: _service.port.number
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400191 }
192 }
193 }
194 }
195 }
196 }
197 }
198}
199
gio1de49582024-04-21 08:33:57 +0400200ingress: {}
201
202_ingressValidate: {
203 for key, value in ingress {
204 "\(key)": #Ingress & value
205 }
206}
207
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400208images: {
209 for key, value in images {
210 "\(key)": #Image & value
211 }
gio1de49582024-04-21 08:33:57 +0400212 for _, value in _ingressValidate {
213 for name, image in value.out.images {
214 "\(name)": #Image & image
215 }
216 }
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400217}
218
219charts: {
220 for key, value in charts {
221 "\(key)": #Chart & value
222 }
gio1de49582024-04-21 08:33:57 +0400223 for _, value in _ingressValidate {
224 for name, chart in value.out.charts {
225 "\(name)": #Chart & chart
226 }
227 }
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400228}
229
230#ResourceReference: {
231 name: string
232 namespace: string
233}
234
235#Helm: {
236 name: string
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400237 dependsOn: [...#ResourceReference] | *[]
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400238 ...
239}
240
gio1de49582024-04-21 08:33:57 +0400241_helmValidate: {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400242 for key, value in helm {
243 "\(key)": #Helm & value & {
244 name: key
245 }
246 }
gio1de49582024-04-21 08:33:57 +0400247 for key, value in _ingressValidate {
248 for ing, ingValue in value.out.helm {
249 // TODO(gio): support multiple ingresses
250 // "\(key)-\(ing)": #Helm & ingValue & {
251 "\(ing)": #Helm & ingValue & {
252 // name: "\(key)-\(ing)"
253 name: ing
254 }
255 }
256 }
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400257}
258
259#HelmRelease: {
260 _name: string
261 _chart: #Chart
262 _values: _
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400263 _dependencies: [...#ResourceReference] | *[]
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400264
265 apiVersion: "helm.toolkit.fluxcd.io/v2beta1"
266 kind: "HelmRelease"
267 metadata: {
268 name: _name
269 namespace: release.namespace
270 }
271 spec: {
272 interval: "1m0s"
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400273 dependsOn: _dependencies
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400274 chart: {
275 spec: _chart
276 }
277 values: _values
278 }
279}
280
281output: {
gio1de49582024-04-21 08:33:57 +0400282 for name, r in _helmValidate {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400283 "\(name)": #HelmRelease & {
284 _name: name
285 _chart: r.chart
286 _values: r.values
287 _dependencies: r.dependsOn
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400288 }
289 }
290}
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400291
292#SSHKey: {
293 public: string
294 private: string
295}
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400296`
297
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400298type Rendered struct {
gio3cdee592024-04-17 10:15:56 +0400299 Name string
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400300 Readme string
gio308105e2024-04-19 13:12:13 +0400301 Resources CueAppData
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400302 Ports []PortForward
gio3cdee592024-04-17 10:15:56 +0400303 Config AppInstanceConfig
gio308105e2024-04-19 13:12:13 +0400304 Data CueAppData
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400305}
306
gio3cdee592024-04-17 10:15:56 +0400307type PortForward struct {
308 Allocator string `json:"allocator"`
309 Protocol string `json:"protocol"`
310 SourcePort int `json:"sourcePort"`
311 TargetService string `json:"targetService"`
312 TargetPort int `json:"targetPort"`
313}
314
315type AppType int
316
317const (
318 AppTypeInfra AppType = iota
319 AppTypeEnv
320)
321
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400322type App interface {
gio3cdee592024-04-17 10:15:56 +0400323 Type() AppType
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400324 Name() string
325 Description() string
326 Icon() template.HTML
327 Schema() Schema
gioef01fbb2024-04-12 16:52:59 +0400328 Namespace() string
gio3cdee592024-04-17 10:15:56 +0400329}
330
331type InfraConfig struct {
332 Name string `json:"pcloudEnvName"` // #TODO(gio): change to name
333 PublicIP []net.IP `json:"publicIP"`
334 InfraNamespacePrefix string `json:"namespacePrefix"`
335 InfraAdminPublicKey []byte `json:"infraAdminPublicKey"`
336}
337
338type InfraApp interface {
339 App
340 Render(release Release, infra InfraConfig, values map[string]any) (Rendered, error)
341}
342
343// TODO(gio): rename to EnvConfig
344type AppEnvConfig struct {
345 Id string `json:"id"`
346 InfraName string `json:"pcloudEnvName"`
347 Domain string `json:"domain"`
348 PrivateDomain string `json:"privateDomain"`
349 ContactEmail string `json:"contactEmail"`
350 PublicIP []net.IP `json:"publicIP"`
351 NamespacePrefix string `json:"namespacePrefix"`
352}
353
354type EnvApp interface {
355 App
356 Render(release Release, env AppEnvConfig, values map[string]any) (Rendered, error)
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400357}
358
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400359type cueApp struct {
360 name string
361 description string
362 icon template.HTML
363 namespace string
364 schema Schema
gio308105e2024-04-19 13:12:13 +0400365 cfg cue.Value
366 data CueAppData
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400367}
368
gio308105e2024-04-19 13:12:13 +0400369type CueAppData map[string][]byte
370
371func ParseCueAppConfig(data CueAppData) (cue.Value, error) {
372 ctx := cuecontext.New()
373 buildCtx := build.NewContext()
374 cfg := &load.Config{
375 Context: buildCtx,
376 Overlay: map[string]load.Source{},
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400377 }
gio308105e2024-04-19 13:12:13 +0400378 names := make([]string, 0)
379 for n, b := range data {
380 a := fmt.Sprintf("/%s", n)
381 names = append(names, a)
382 cfg.Overlay[a] = load.FromString("package main\n\n" + string(b))
383 }
384 instances := load.Instances(names, cfg)
385 for _, inst := range instances {
386 if inst.Err != nil {
387 return cue.Value{}, inst.Err
388 }
389 }
390 if len(instances) != 1 {
391 return cue.Value{}, fmt.Errorf("invalid")
392 }
393 ret := ctx.BuildInstance(instances[0])
394 if ret.Err() != nil {
395 return cue.Value{}, ret.Err()
396 }
397 if err := ret.Validate(); err != nil {
398 return cue.Value{}, err
399 }
400 return ret, nil
401}
402
403func newCueApp(config cue.Value, data CueAppData) (cueApp, error) {
gio3cdee592024-04-17 10:15:56 +0400404 cfg := struct {
405 Name string `json:"name"`
406 Namespace string `json:"namespace"`
407 Description string `json:"description"`
408 Icon string `json:"icon"`
409 }{}
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400410 if err := config.Decode(&cfg); err != nil {
411 return cueApp{}, err
412 }
413 schema, err := NewCueSchema(config.LookupPath(cue.ParsePath("input")))
414 if err != nil {
415 return cueApp{}, err
416 }
417 return cueApp{
418 name: cfg.Name,
419 description: cfg.Description,
420 icon: template.HTML(cfg.Icon),
421 namespace: cfg.Namespace,
422 schema: schema,
423 cfg: config,
gio308105e2024-04-19 13:12:13 +0400424 data: data,
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400425 }, nil
426}
427
gio308105e2024-04-19 13:12:13 +0400428func ParseAndCreateNewCueApp(data CueAppData) (cueApp, error) {
429 config, err := ParseCueAppConfig(data)
430 if err != nil {
431 return cueApp{}, err
432 }
433 return newCueApp(config, data)
434}
435
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400436func (a cueApp) Name() string {
437 return a.name
438}
439
440func (a cueApp) Description() string {
441 return a.description
442}
443
444func (a cueApp) Icon() template.HTML {
445 return a.icon
446}
447
448func (a cueApp) Schema() Schema {
449 return a.schema
450}
451
gioef01fbb2024-04-12 16:52:59 +0400452func (a cueApp) Namespace() string {
453 return a.namespace
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400454}
455
gio3cdee592024-04-17 10:15:56 +0400456func (a cueApp) render(values map[string]any) (Rendered, error) {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400457 ret := Rendered{
gio3cdee592024-04-17 10:15:56 +0400458 Name: a.Name(),
gio308105e2024-04-19 13:12:13 +0400459 Resources: make(CueAppData),
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400460 Ports: make([]PortForward, 0),
gio308105e2024-04-19 13:12:13 +0400461 Data: a.data,
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400462 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400463 var buf bytes.Buffer
gio3cdee592024-04-17 10:15:56 +0400464 if err := json.NewEncoder(&buf).Encode(values); err != nil {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400465 return Rendered{}, err
466 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400467 ctx := a.cfg.Context()
468 d := ctx.CompileBytes(buf.Bytes())
469 res := a.cfg.Unify(d).Eval()
470 if err := res.Err(); err != nil {
471 return Rendered{}, err
472 }
473 if err := res.Validate(); err != nil {
474 return Rendered{}, err
475 }
gio308105e2024-04-19 13:12:13 +0400476 full, err := json.MarshalIndent(res, "", "\t")
477 if err != nil {
478 return Rendered{}, err
479 }
480 ret.Data["rendered.json"] = full
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400481 readme, err := res.LookupPath(cue.ParsePath("readme")).String()
482 if err != nil {
483 return Rendered{}, err
484 }
485 ret.Readme = readme
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400486 if err := res.LookupPath(cue.ParsePath("portForward")).Decode(&ret.Ports); err != nil {
487 return Rendered{}, err
488 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400489 output := res.LookupPath(cue.ParsePath("output"))
490 i, err := output.Fields()
491 if err != nil {
492 return Rendered{}, err
493 }
494 for i.Next() {
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400495 if contents, err := cueyaml.Encode(i.Value()); err != nil {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400496 return Rendered{}, err
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400497 } else {
498 name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
499 ret.Resources[name] = contents
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400500 }
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400501 }
502 return ret, nil
503}
504
gio3cdee592024-04-17 10:15:56 +0400505type cueEnvApp struct {
506 cueApp
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400507}
508
gio308105e2024-04-19 13:12:13 +0400509func NewCueEnvApp(data CueAppData) (EnvApp, error) {
510 app, err := ParseAndCreateNewCueApp(data)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400511 if err != nil {
512 return nil, err
513 }
gio3cdee592024-04-17 10:15:56 +0400514 return cueEnvApp{app}, nil
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400515}
516
gio3cdee592024-04-17 10:15:56 +0400517func (a cueEnvApp) Type() AppType {
518 return AppTypeEnv
519}
520
521func (a cueEnvApp) Render(release Release, env AppEnvConfig, values map[string]any) (Rendered, error) {
522 networks := CreateNetworks(env)
523 derived, err := deriveValues(values, a.Schema(), networks)
524 if err != nil {
525 return Rendered{}, nil
526 }
527 ret, err := a.cueApp.render(map[string]any{
528 "global": env,
529 "release": release,
530 "input": derived,
531 })
532 if err != nil {
533 return Rendered{}, err
534 }
535 ret.Config = AppInstanceConfig{
536 AppId: a.Name(),
537 Env: env,
538 Release: release,
539 Values: values,
540 Input: derived,
541 }
542 return ret, nil
543}
544
545type cueInfraApp struct {
546 cueApp
547}
548
gio308105e2024-04-19 13:12:13 +0400549func NewCueInfraApp(data CueAppData) (InfraApp, error) {
550 app, err := ParseAndCreateNewCueApp(data)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400551 if err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400552 return nil, err
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400553 }
gio3cdee592024-04-17 10:15:56 +0400554 return cueInfraApp{app}, nil
555}
556
557func (a cueInfraApp) Type() AppType {
558 return AppTypeInfra
559}
560
561func (a cueInfraApp) Render(release Release, infra InfraConfig, values map[string]any) (Rendered, error) {
562 return a.cueApp.render(map[string]any{
563 "global": infra,
564 "release": release,
565 "input": values,
566 })
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400567}
568
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400569func cleanName(s string) string {
570 return strings.ReplaceAll(strings.ReplaceAll(s, "\"", ""), "'", "")
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400571}