| package installer |
| |
| import ( |
| "bytes" |
| _ "embed" |
| "encoding/json" |
| "fmt" |
| template "html/template" |
| "net" |
| "net/netip" |
| "strings" |
| |
| "cuelang.org/go/cue" |
| "cuelang.org/go/cue/build" |
| "cuelang.org/go/cue/cuecontext" |
| "cuelang.org/go/cue/errors" |
| "cuelang.org/go/cue/load" |
| cueyaml "cuelang.org/go/encoding/yaml" |
| helmv2 "github.com/fluxcd/helm-controller/api/v2" |
| ) |
| |
| //go:embed app_configs/dodo_app.cue |
| var dodoAppCue []byte |
| |
| //go:embed app_configs/app_base.cue |
| var cueBaseConfig []byte |
| |
| //go:embed app_configs/app_global_env.cue |
| var cueEnvAppGlobal []byte |
| |
| //go:embed app_configs/app_global_infra.cue |
| var cueInfraAppGlobal []byte |
| |
| type Access struct { |
| Type string `json:"type"` |
| Name string `json:"name"` |
| HTTPS *AccessHTTPS |
| SSH *AccessSSH |
| TCP *AccessTCP |
| UDP *AccessUDP |
| PostgreSQL *AccessPostgreSQL |
| MongoDB *AccessMongoDB |
| EnvVar *AccessEnvVar |
| } |
| |
| func (a Access) MarshalJSON() ([]byte, error) { |
| var buf bytes.Buffer |
| switch a.Type { |
| case "https": |
| if err := json.NewEncoder(&buf).Encode(struct { |
| AccessHTTPS |
| Type string `json:"type"` |
| Name string `json:"name"` |
| }{*a.HTTPS, a.Type, a.Name}); err != nil { |
| return nil, err |
| } |
| case "ssh": |
| if err := json.NewEncoder(&buf).Encode(struct { |
| AccessSSH |
| Type string `json:"type"` |
| Name string `json:"name"` |
| }{*a.SSH, a.Type, a.Name}); err != nil { |
| return nil, err |
| } |
| case "tcp": |
| if err := json.NewEncoder(&buf).Encode(struct { |
| AccessTCP |
| Type string `json:"type"` |
| Name string `json:"name"` |
| }{*a.TCP, a.Type, a.Name}); err != nil { |
| return nil, err |
| } |
| case "udp": |
| if err := json.NewEncoder(&buf).Encode(struct { |
| AccessUDP |
| Type string `json:"type"` |
| Name string `json:"name"` |
| }{*a.UDP, a.Type, a.Name}); err != nil { |
| return nil, err |
| } |
| case "postgresql": |
| if err := json.NewEncoder(&buf).Encode(struct { |
| AccessPostgreSQL |
| Type string `json:"type"` |
| Name string `json:"name"` |
| }{*a.PostgreSQL, a.Type, a.Name}); err != nil { |
| return nil, err |
| } |
| case "mongodb": |
| if err := json.NewEncoder(&buf).Encode(struct { |
| AccessMongoDB |
| Type string `json:"type"` |
| Name string `json:"name"` |
| }{*a.MongoDB, a.Type, a.Name}); err != nil { |
| return nil, err |
| } |
| case "env_var": |
| if err := json.NewEncoder(&buf).Encode(struct { |
| AccessEnvVar |
| Type string `json:"type"` |
| Name string `json:"name"` |
| }{*a.EnvVar, a.Type, a.Name}); err != nil { |
| return nil, err |
| } |
| default: |
| panic("MUST NOT REACH!") |
| } |
| return buf.Bytes(), nil |
| } |
| |
| type AccessHTTPS struct { |
| Address string `json:"address"` |
| AgentName string `json:"agentName,omitempty"` |
| } |
| |
| type AccessSSH struct { |
| Host string `json:"host"` |
| Port int `json:"port"` |
| } |
| |
| type AccessTCP struct { |
| Host string `json:"host"` |
| Port int `json:"port"` |
| } |
| |
| type AccessUDP struct { |
| Host string `json:"host"` |
| Port int `json:"port"` |
| } |
| |
| type AccessPostgreSQL struct { |
| Host string `json:"host"` |
| Port int `json:"port"` |
| Database string `json:"database"` |
| Username string `json:"username"` |
| Password string `json:"password"` |
| } |
| |
| type AccessMongoDB struct { |
| Host string `json:"host"` |
| Port int `json:"port"` |
| Database string `json:"database"` |
| Username string `json:"username"` |
| Password string `json:"password"` |
| } |
| |
| type AccessEnvVar struct { |
| Var string `json:"var"` |
| } |
| |
| type EnvVar struct { |
| Name string `json:"name"` |
| Value string `json:"value"` |
| } |
| |
| type rendered struct { |
| Name string |
| Readme string |
| Cluster string |
| Namespaces []Namespace |
| Resources CueAppData |
| HelmCharts HelmCharts |
| ContainerImages map[string]ContainerImage |
| Ports []PortForward |
| ClusterProxies map[string]ClusterProxy |
| Data CueAppData |
| URL string |
| Help []HelpDocument |
| Icon string |
| Access []Access |
| EnvVars []EnvVar |
| Raw []byte |
| } |
| |
| type Namespace struct { |
| Name string `json:"name"` |
| Kubeconfig string `json:"kubeconfig,omitempty"` |
| } |
| |
| type HelpDocument struct { |
| Title string |
| Contents string |
| Children []HelpDocument |
| } |
| |
| type ContainerImage struct { |
| Registry string `json:"registry"` |
| Repository string `json:"repository"` |
| Name string `json:"name"` |
| Tag string `json:"tag"` |
| } |
| |
| type helmChartRef struct { |
| Kind string `json:"kind"` |
| } |
| |
| type HelmCharts struct { |
| Git map[string]HelmChartGitRepo |
| } |
| |
| type HelmChartGitRepo struct { |
| Address string `json:"address"` |
| Branch string `json:"branch"` |
| Path string `json:"path"` |
| } |
| |
| type EnvAppRendered struct { |
| rendered |
| Config AppInstanceConfig |
| } |
| |
| type InfraAppRendered struct { |
| rendered |
| Config InfraAppInstanceConfig |
| } |
| |
| type ClusterProxy struct { |
| From string `json:"from"` |
| To string `json:"to"` |
| } |
| |
| type PortForward struct { |
| Cluster string `json:"clusterName,omitempty"` |
| Network Network `json:"network"` |
| Protocol string `json:"protocol"` |
| Port int `json:"port"` |
| Service struct { |
| Name string `json:"name"` |
| Namespace string `json:"namespace,omitempty"` |
| Port int `json:"port"` |
| } `json:"service"` |
| } |
| |
| type AppType int |
| |
| const ( |
| AppTypeInfra AppType = iota |
| AppTypeEnv |
| ) |
| |
| type App interface { |
| Name() string |
| Type() AppType |
| Slug() string |
| Description() string |
| Icon() template.HTML |
| Schema() Schema |
| Namespace() string |
| } |
| |
| type InfraConfig struct { |
| Name string `json:"pcloudEnvName,omitempty"` // #TODO(gio): change to name |
| PublicIP []net.IP `json:"publicIP,omitempty"` |
| InfraNamespacePrefix string `json:"namespacePrefix,omitempty"` |
| InfraAdminPublicKey []byte `json:"infraAdminPublicKey,omitempty"` |
| } |
| |
| type InfraNetwork struct { |
| Name string `json:"name,omitempty"` |
| IngressClass string `json:"ingressClass,omitempty"` |
| CertificateIssuer string `json:"certificateIssuer,omitempty"` |
| AllocatePortAddr string `json:"allocatePortAddr,omitempty"` |
| ReservePortAddr string `json:"reservePortAddr,omitempty"` |
| DeallocatePortAddr string `json:"deallocatePortAddr,omitempty"` |
| } |
| |
| type InfraApp interface { |
| App |
| Render(release Release, infra InfraConfig, networks []InfraNetwork, values map[string]any, charts map[string]helmv2.HelmChartTemplateSpec) (InfraAppRendered, error) |
| } |
| |
| type EnvNetwork struct { |
| DNS net.IP `json:"dns,omitempty"` |
| DNSInClusterIP net.IP `json:"dnsInClusterIP,omitempty"` |
| Ingress net.IP `json:"ingress,omitempty"` |
| Headscale net.IP `json:"headscale,omitempty"` |
| ServicesFrom net.IP `json:"servicesFrom,omitempty"` |
| ServicesTo net.IP `json:"servicesTo,omitempty"` |
| } |
| |
| func NewEnvNetwork(subnet net.IP) (EnvNetwork, error) { |
| addr, err := netip.ParseAddr(subnet.String()) |
| if err != nil { |
| return EnvNetwork{}, err |
| } |
| if !addr.Is4() { |
| return EnvNetwork{}, fmt.Errorf("Expected IPv4, got %s instead", addr) |
| } |
| dns := addr.Next() |
| ingress := dns.Next() |
| headscale := ingress.Next() |
| b := addr.AsSlice() |
| if b[3] != 0 { |
| return EnvNetwork{}, fmt.Errorf("Expected last byte to be zero, got %d instead", b[3]) |
| } |
| b[3] = 10 |
| servicesFrom, ok := netip.AddrFromSlice(b) |
| if !ok { |
| return EnvNetwork{}, fmt.Errorf("Must not reach") |
| } |
| b[3] = 254 |
| servicesTo, ok := netip.AddrFromSlice(b) |
| if !ok { |
| return EnvNetwork{}, fmt.Errorf("Must not reach") |
| } |
| b[3] = b[2] |
| b[2] = b[1] |
| b[0] = 10 |
| b[1] = 44 |
| dnsInClusterIP, ok := netip.AddrFromSlice(b) |
| if !ok { |
| return EnvNetwork{}, fmt.Errorf("Must not reach") |
| } |
| return EnvNetwork{ |
| DNS: net.ParseIP(dns.String()), |
| DNSInClusterIP: net.ParseIP(dnsInClusterIP.String()), |
| Ingress: net.ParseIP(ingress.String()), |
| Headscale: net.ParseIP(headscale.String()), |
| ServicesFrom: net.ParseIP(servicesFrom.String()), |
| ServicesTo: net.ParseIP(servicesTo.String()), |
| }, nil |
| } |
| |
| type EnvConfig struct { |
| Id string `json:"id,omitempty"` |
| InfraName string `json:"pcloudEnvName,omitempty"` |
| Domain string `json:"domain,omitempty"` |
| PrivateDomain string `json:"privateDomain,omitempty"` |
| ContactEmail string `json:"contactEmail,omitempty"` |
| AdminPublicKey string `json:"adminPublicKey,omitempty"` |
| PublicIP []net.IP `json:"publicIP,omitempty"` |
| NameserverIP []net.IP `json:"nameserverIP,omitempty"` |
| NamespacePrefix string `json:"namespacePrefix,omitempty"` |
| Network EnvNetwork `json:"network,omitempty"` |
| } |
| |
| type EnvApp interface { |
| App |
| Render( |
| release Release, |
| env EnvConfig, |
| networks []Network, |
| clusters []Cluster, |
| values map[string]any, |
| charts map[string]helmv2.HelmChartTemplateSpec, |
| vpnKeyGen VPNAPIClient, |
| ) (EnvAppRendered, error) |
| } |
| |
| type cueApp struct { |
| name string |
| description string |
| icon template.HTML |
| namespace string |
| schema Schema |
| cfg cue.Value |
| data CueAppData |
| } |
| |
| type CueAppData map[string][]byte |
| |
| func ParseCueAppConfig(data CueAppData) (cue.Value, error) { |
| ctx := cuecontext.New() |
| buildCtx := build.NewContext() |
| cfg := &load.Config{ |
| Context: buildCtx, |
| Overlay: map[string]load.Source{}, |
| } |
| names := make([]string, 0) |
| for n, b := range data { |
| a := fmt.Sprintf("/%s", n) |
| names = append(names, a) |
| cfg.Overlay[a] = load.FromString("package main\n\n" + string(b)) |
| } |
| instances := load.Instances(names, cfg) |
| for _, inst := range instances { |
| if inst.Err != nil { |
| return cue.Value{}, inst.Err |
| } |
| } |
| if len(instances) != 1 { |
| return cue.Value{}, fmt.Errorf("invalid") |
| } |
| ret := ctx.BuildInstance(instances[0]) |
| if err := ret.Err(); err != nil { |
| return cue.Value{}, err |
| } |
| if err := ret.Validate(); err != nil { |
| return cue.Value{}, err |
| } |
| return ret, nil |
| } |
| |
| func newCueApp(config cue.Value, data CueAppData) (cueApp, error) { |
| cfg := struct { |
| Name string `json:"name"` |
| Namespace string `json:"namespace"` |
| Description string `json:"description"` |
| Icon string `json:"icon"` |
| }{} |
| if err := config.Decode(&cfg); err != nil { |
| return cueApp{}, err |
| } |
| schema, err := NewCueSchema("input", config.LookupPath(cue.ParsePath("input"))) |
| if err != nil { |
| return cueApp{}, err |
| } |
| return cueApp{ |
| name: cfg.Name, |
| description: cfg.Description, |
| icon: template.HTML(cfg.Icon), |
| namespace: cfg.Namespace, |
| schema: schema, |
| cfg: config, |
| data: data, |
| }, nil |
| } |
| |
| func ParseAndCreateNewCueApp(data CueAppData) (cueApp, error) { |
| config, err := ParseCueAppConfig(data) |
| if err != nil { |
| return cueApp{}, fmt.Errorf(errors.Details(err, nil)) |
| } |
| if err := config.Err(); err != nil { |
| return cueApp{}, fmt.Errorf(errors.Details(err, nil)) |
| } |
| return newCueApp(config, data) |
| } |
| |
| func (a cueApp) Name() string { |
| return a.name |
| } |
| |
| func (a cueApp) Slug() string { |
| return strings.ReplaceAll(strings.ToLower(a.name), " ", "-") |
| } |
| |
| func (a cueApp) Description() string { |
| return a.description |
| } |
| |
| func (a cueApp) Icon() template.HTML { |
| return a.icon |
| } |
| |
| func (a cueApp) Schema() Schema { |
| return a.schema |
| } |
| |
| func (a cueApp) Namespace() string { |
| return a.namespace |
| } |
| |
| func (a cueApp) render(values map[string]any) (rendered, error) { |
| ret := rendered{ |
| Name: a.Slug(), |
| Resources: make(CueAppData), |
| HelmCharts: HelmCharts{ |
| Git: make(map[string]HelmChartGitRepo), |
| }, |
| ContainerImages: make(map[string]ContainerImage), |
| Ports: make([]PortForward, 0), |
| Data: a.data, |
| } |
| var buf bytes.Buffer |
| if err := json.NewEncoder(&buf).Encode(values); err != nil { |
| return rendered{}, err |
| } |
| ctx := a.cfg.Context() |
| d := ctx.CompileBytes(buf.Bytes()) |
| res := a.cfg.Unify(d).Eval() |
| if err := res.Err(); err != nil { |
| return rendered{}, fmt.Errorf(errors.Details(err, nil)) |
| } |
| if err := res.Validate(); err != nil { |
| return rendered{}, err |
| } |
| full, err := json.MarshalIndent(res, "", "\t") |
| if err != nil { |
| return rendered{}, err |
| } |
| ret.Raw = full |
| ret.Data["rendered.json"] = full |
| readme, err := res.LookupPath(cue.ParsePath("readme")).String() |
| if err != nil { |
| return rendered{}, err |
| } |
| ret.Readme = readme |
| res.LookupPath(cue.ParsePath("input.cluster.name")).Decode(&ret.Cluster) |
| if err := res.LookupPath(cue.ParsePath("output.clusterProxy")).Decode(&ret.ClusterProxies); err != nil { |
| return rendered{}, err |
| } |
| if err := res.LookupPath(cue.ParsePath("namespaces")).Decode(&ret.Namespaces); err != nil { |
| return rendered{}, err |
| } |
| if err := res.LookupPath(cue.ParsePath("output.openPort")).Decode(&ret.Ports); err != nil { |
| return rendered{}, err |
| } |
| { |
| envVars := []string{} |
| if err := res.LookupPath(cue.ParsePath("envVars")).Decode(&envVars); err != nil { |
| return rendered{}, err |
| } |
| for _, ev := range envVars { |
| items := strings.SplitN(ev, "=", 2) |
| if len(items) != 2 { |
| panic(ev) |
| } |
| ret.EnvVars = append(ret.EnvVars, EnvVar{items[0], items[1]}) |
| } |
| } |
| { |
| charts := res.LookupPath(cue.ParsePath("output.charts")) |
| i, err := charts.Fields() |
| if err != nil { |
| return rendered{}, err |
| } |
| for i.Next() { |
| var chartRef helmChartRef |
| if err := i.Value().Decode(&chartRef); err != nil { |
| return rendered{}, err |
| } |
| if chartRef.Kind == "GitRepository" { |
| var chart HelmChartGitRepo |
| if err := i.Value().Decode(&chart); err != nil { |
| return rendered{}, err |
| } |
| ret.HelmCharts.Git[cleanName(i.Selector().String())] = chart |
| } |
| } |
| } |
| { |
| images := res.LookupPath(cue.ParsePath("output.images")) |
| i, err := images.Fields() |
| if err != nil { |
| return rendered{}, err |
| } |
| for i.Next() { |
| var img ContainerImage |
| if err := i.Value().Decode(&img); err != nil { |
| return rendered{}, err |
| } |
| ret.ContainerImages[cleanName(i.Selector().String())] = img |
| } |
| } |
| { |
| helm := res.LookupPath(cue.ParsePath("output.helm")) |
| i, err := helm.Fields() |
| if err != nil { |
| return rendered{}, err |
| } |
| for i.Next() { |
| if contents, err := cueyaml.Encode(i.Value()); err != nil { |
| return rendered{}, err |
| } else { |
| name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String())) |
| ret.Resources[name] = contents |
| } |
| } |
| } |
| { |
| resources := res.LookupPath(cue.ParsePath("resources")) |
| i, err := resources.Fields() |
| if err != nil { |
| return rendered{}, err |
| } |
| for i.Next() { |
| if contents, err := cueyaml.Encode(i.Value()); err != nil { |
| return rendered{}, err |
| } else { |
| name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String())) |
| ret.Resources[name] = contents |
| } |
| } |
| } |
| helpValue := res.LookupPath(cue.ParsePath("help")) |
| if helpValue.Exists() { |
| if err := helpValue.Decode(&ret.Help); err != nil { |
| return rendered{}, err |
| } |
| } |
| url, err := res.LookupPath(cue.ParsePath("url")).String() |
| if err != nil { |
| return rendered{}, err |
| } |
| ret.URL = url |
| icon, err := res.LookupPath(cue.ParsePath("icon")).String() |
| if err != nil { |
| return rendered{}, err |
| } |
| ret.Icon = icon |
| access, err := extractAccess(res.LookupPath(cue.ParsePath("outs"))) |
| if err != nil { |
| return rendered{}, err |
| } |
| ret.Access = access |
| return ret, nil |
| } |
| |
| func extractAccessInternal(v cue.Value) ([]Access, error) { |
| ret := []Access{} |
| a := v.LookupPath(cue.ParsePath("access")) |
| if err := a.Err(); err != nil { |
| return nil, err |
| } |
| i, err := a.List() |
| if err != nil { |
| return nil, err |
| } |
| for i.Next() { |
| n := i.Value().LookupPath(cue.ParsePath("name")) |
| if err := n.Err(); err != nil { |
| return nil, err |
| } |
| nn, err := n.String() |
| if err != nil { |
| return nil, err |
| } |
| t := i.Value().LookupPath(cue.ParsePath("type")) |
| if err := t.Err(); err != nil { |
| return nil, err |
| } |
| d, err := t.String() |
| if err != nil { |
| return nil, err |
| } |
| switch d { |
| case "https": |
| { |
| var q AccessHTTPS |
| if err := i.Value().Decode(&q); err != nil { |
| return nil, err |
| } |
| ret = append(ret, Access{Type: "https", Name: nn, HTTPS: &q}) |
| } |
| case "ssh": |
| { |
| var q AccessSSH |
| if err := i.Value().Decode(&q); err != nil { |
| return nil, err |
| } |
| ret = append(ret, Access{Type: "ssh", Name: nn, SSH: &q}) |
| } |
| case "tcp": |
| { |
| var q AccessTCP |
| if err := i.Value().Decode(&q); err != nil { |
| return nil, err |
| } |
| ret = append(ret, Access{Type: "tcp", Name: nn, TCP: &q}) |
| } |
| case "udp": |
| { |
| var q AccessUDP |
| if err := i.Value().Decode(&q); err != nil { |
| return nil, err |
| } |
| ret = append(ret, Access{Type: "udp", Name: nn, UDP: &q}) |
| } |
| case "postgresql": |
| { |
| var q AccessPostgreSQL |
| if err := i.Value().Decode(&q); err != nil { |
| return nil, err |
| } |
| ret = append(ret, Access{Type: "postgresql", Name: nn, PostgreSQL: &q}) |
| } |
| case "mongodb": |
| { |
| var q AccessMongoDB |
| if err := i.Value().Decode(&q); err != nil { |
| return nil, err |
| } |
| ret = append(ret, Access{Type: "mongodb", Name: nn, MongoDB: &q}) |
| } |
| case "env_var": |
| { |
| var q AccessEnvVar |
| if err := i.Value().Decode(&q); err != nil { |
| return nil, err |
| } |
| ret = append(ret, Access{Type: "env_var", Name: nn, EnvVar: &q}) |
| } |
| } |
| } |
| for _, sub := range []string{"ingress", "postgresql", "mongodb", "services", "vm"} { |
| subout := v.LookupPath(cue.ParsePath(sub)) |
| if subout.Err() != nil { |
| continue |
| } |
| if a, err := extractAccess(subout); err != nil { |
| return nil, err |
| } else { |
| ret = append(ret, a...) |
| } |
| } |
| return ret, nil |
| } |
| |
| func extractAccess(v cue.Value) ([]Access, error) { |
| if err := v.Err(); err != nil { |
| return nil, err |
| } |
| i, err := v.Fields() |
| if err != nil { |
| return nil, err |
| } |
| ret := []Access{} |
| for i.Next() { |
| if a, err := extractAccessInternal(i.Value()); err != nil { |
| return nil, fmt.Errorf(errors.Details(err, nil)) |
| } else { |
| ret = append(ret, a...) |
| } |
| } |
| return ret, nil |
| } |
| |
| type cueEnvApp struct { |
| cueApp |
| } |
| |
| func NewCueEnvApp(data CueAppData) (EnvApp, error) { |
| app, err := ParseAndCreateNewCueApp(data) |
| if err != nil { |
| return nil, err |
| } |
| return cueEnvApp{app}, nil |
| } |
| |
| func NewDodoApp(appCfg []byte) (EnvApp, error) { |
| return NewCueEnvApp(CueAppData{ |
| "app.cue": appCfg, |
| "base.cue": cueBaseConfig, |
| "dodo.cue": dodoAppCue, |
| "env.cue": cueEnvAppGlobal, |
| }) |
| } |
| |
| func (a cueEnvApp) Type() AppType { |
| return AppTypeEnv |
| } |
| |
| func merge(d map[string]any, v map[string]any) map[string]any { |
| ret := map[string]any{} |
| for k, val := range d { |
| if vv, ok := v[k]; ok && vv != nil { |
| if mv, ok := val.(map[string]any); ok { |
| // TODO(gio): check that it is actually map |
| mm, ok := vv.(map[string]any) |
| if !ok { |
| // TODO(gio): handle #Network and others |
| ret[k] = vv |
| } else { |
| ret[k] = merge(mv, mm) |
| } |
| } else { |
| ret[k] = vv |
| } |
| } else { |
| ret[k] = val |
| } |
| } |
| for k, v := range v { |
| if v != nil { |
| if _, ok := d[k]; !ok { |
| ret[k] = v |
| } |
| } |
| } |
| return ret |
| } |
| |
| func (a cueEnvApp) Render( |
| release Release, |
| env EnvConfig, |
| networks []Network, |
| clusters []Cluster, |
| values map[string]any, |
| charts map[string]helmv2.HelmChartTemplateSpec, |
| vpnKeyGen VPNAPIClient, |
| ) (EnvAppRendered, error) { |
| dv, err := ExtractDefaultValues(a.cueApp.cfg.LookupPath(cue.ParsePath("input"))) |
| if err != nil { |
| return EnvAppRendered{}, err |
| } |
| if dv == nil { |
| dv = map[string]any{} |
| } |
| mv := merge(dv.(map[string]any), values) |
| derived, err := deriveValues(mv, mv, a.Schema(), networks, clusters, vpnKeyGen) |
| if err != nil { |
| return EnvAppRendered{}, err |
| } |
| if charts == nil { |
| charts = make(map[string]helmv2.HelmChartTemplateSpec) |
| } |
| if clusters == nil { |
| clusters = []Cluster{} |
| } |
| ret, err := a.cueApp.render(map[string]any{ |
| "global": env, |
| "release": release, |
| "input": derived, |
| "localCharts": charts, |
| "networks": NetworkMap(networks), |
| "clusters": clusters, |
| }) |
| if err != nil { |
| return EnvAppRendered{}, err |
| } |
| return EnvAppRendered{ |
| rendered: ret, |
| Config: AppInstanceConfig{ |
| AppId: a.Slug(), |
| Env: env, |
| Release: release, |
| Values: values, |
| Input: derived, |
| URL: ret.URL, |
| Help: ret.Help, |
| Icon: ret.Icon, |
| }, |
| }, nil |
| } |
| |
| type cueInfraApp struct { |
| cueApp |
| } |
| |
| func NewCueInfraApp(data CueAppData) (InfraApp, error) { |
| app, err := ParseAndCreateNewCueApp(data) |
| if err != nil { |
| return nil, err |
| } |
| return cueInfraApp{app}, nil |
| } |
| |
| func (a cueInfraApp) Type() AppType { |
| return AppTypeInfra |
| } |
| |
| func (a cueInfraApp) Render(release Release, infra InfraConfig, networks []InfraNetwork, values map[string]any, charts map[string]helmv2.HelmChartTemplateSpec) (InfraAppRendered, error) { |
| if charts == nil { |
| charts = make(map[string]helmv2.HelmChartTemplateSpec) |
| } |
| ret, err := a.cueApp.render(map[string]any{ |
| "global": infra, |
| "release": release, |
| "input": values, |
| "localCharts": charts, |
| "networks": InfraNetworkMap(networks), |
| }) |
| if err != nil { |
| return InfraAppRendered{}, err |
| } |
| return InfraAppRendered{ |
| rendered: ret, |
| Config: InfraAppInstanceConfig{ |
| AppId: a.Slug(), |
| Infra: infra, |
| Release: release, |
| Values: values, |
| Input: values, |
| URL: ret.URL, |
| Help: ret.Help, |
| }, |
| }, nil |
| } |
| |
| func cleanName(s string) string { |
| return strings.ReplaceAll(strings.ReplaceAll(s, "\"", ""), "'", "") |
| } |
| |
| func join[T fmt.Stringer](items []T, sep string) string { |
| var tmp []string |
| for _, i := range items { |
| tmp = append(tmp, i.String()) |
| } |
| return strings.Join(tmp, ",") |
| } |
| |
| func NetworkMap(networks []Network) map[string]Network { |
| ret := make(map[string]Network) |
| for _, n := range networks { |
| ret[strings.ToLower(n.Name)] = n |
| } |
| return ret |
| } |
| |
| func InfraNetworkMap(networks []InfraNetwork) map[string]InfraNetwork { |
| ret := make(map[string]InfraNetwork) |
| for _, n := range networks { |
| ret[strings.ToLower(n.Name)] = n |
| } |
| return ret |
| } |