package gatewaykcp import ( "cmp" "fmt" "os" "slices" "strconv" "strings" "time" "sigs.k8s.io/yaml" "github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1" "github.com/johnandersen777/socialweb-computer-kcp/internal/app/compose" appgateway "github.com/johnandersen777/socialweb-computer-kcp/internal/app/gateway" "github.com/johnandersen777/socialweb-computer-kcp/internal/providermain" ) const ( DefaultWebAppFront = "127.0.0.1:8788" PortZeroWebAppFront = "127.0.0.1:0" OptionGatewayAudience = "gateway-audience" OptionKcpPublicHost = "kcp-public-host" ) func ReadSpec(path string) (v1alpha1.GatewaySpec, error) { var s v1alpha1.GatewaySpec if path == "" { return s, nil } b, err := os.ReadFile(path) if err != nil { return s, fmt.Errorf("gatewaykcp: the Gateway spec file: %w", err) } if err := yaml.UnmarshalStrict(b, &s); err != nil { return s, fmt.Errorf("gatewaykcp: the Gateway spec file %s: %w", path, err) } return s, nil } func ClusterProviderEnv(c *v1alpha1.GatewayClusterProvider) map[string]string { out := map[string]string{} if c == nil { return out } if c.ScaleHold != "" { out[providermain.EnvScaleHold] = c.ScaleHold } if c.ScaleMaxNodes != 0 { out[providermain.EnvScaleMaxNodes] = strconv.Itoa(int(c.ScaleMaxNodes)) } if c.ScalePodsPerNode != 0 { out[providermain.EnvScalePodsPerNode] = strconv.Itoa(int(c.ScalePodsPerNode)) } return out } func ClusterProviderFromEnv(lookup func(string) (string, bool), have *v1alpha1.GatewayClusterProvider) (*v1alpha1.GatewayClusterProvider, error) { out := v1alpha1.GatewayClusterProvider{} if have != nil { out = *have } get := func(k string) string { v, _ := lookup(k) return strings.TrimSpace(v) } if v := get(providermain.EnvScaleHold); v != "" && out.ScaleHold == "" { if _, err := time.ParseDuration(v); err != nil { return nil, fmt.Errorf("%s is not a duration: %w", providermain.EnvScaleHold, err) } out.ScaleHold = v } for _, f := range []struct { env string dst *int32 }{{providermain.EnvScaleMaxNodes, &out.ScaleMaxNodes}, {providermain.EnvScalePodsPerNode, &out.ScalePodsPerNode}} { v := get(f.env) if v == "" || *f.dst != 0 { continue } n, err := strconv.ParseInt(v, 10, 32) if err != nil || n < 1 { return nil, fmt.Errorf("%s is %q, not a whole number of at least 1", f.env, v) } *f.dst = int32(n) } if out == (v1alpha1.GatewayClusterProvider{}) { return nil, nil } return &out, nil } func hasOption(s v1alpha1.GatewaySpec, name string) bool { return slices.ContainsFunc(s.Options, func(o v1alpha1.ApplicationOption) bool { return o.Name == name }) } func Spec(s v1alpha1.GatewaySpec, profile string, environ []string) v1alpha1.GatewaySpec { e := newEnv(environ) s.Profile = profile n := v1alpha1.MarketNetworkValue{} if s.Network != nil { n = *s.Network } if n.PLCDirectoryURL == "" { n.PLCDirectoryURL = e.get("SOCIALWEB_GATEWAY_PLC_URL") } n.PLCDirectoryURL = cmp.Or(n.PLCDirectoryURL, DefaultPLC) if n.IngressProxyHost == "" { n.IngressProxyHost = e.get("SOCIALWEB_GATEWAY_INGRESS_PROXY_HOST") } n.IngressProxyHost = cmp.Or(n.IngressProxyHost, DefaultIngress) s.Network = &n if s.Ingress == nil && profile == appgateway.ProfileGatewayProd { s.Ingress = compose.DefaultIngressConfig() if n.IngressProxyHost != "" { s.Ingress.Xrpc.IngressProxyHost = n.IngressProxyHost } } if s.WebAppFront == "" { s.WebAppFront = PortZeroWebAppFront if profile == appgateway.ProfileGatewayProd { s.WebAppFront = DefaultWebAppFront } } if len(s.Identity.ApprovedAssociations) == 0 { for did := range strings.SplitSeq(e.get("SOCIALWEB_GATEWAY_APPROVED_ASSOCIATIONS"), ",") { if did = strings.TrimSpace(did); did != "" && !slices.Contains(s.Identity.ApprovedAssociations, did) { s.Identity.ApprovedAssociations = append(s.Identity.ApprovedAssociations, did) } } } s.Options = slices.Clone(s.Options) if !hasOption(s, OptionGatewayAudience) { first, _, _ := strings.Cut(e.get("SOCIALWEB_GATEWAY_AUDIENCES"), ",") if first = strings.TrimSpace(first); first == "" { first = DefaultAudience } s.Options = append(s.Options, v1alpha1.ApplicationOption{Name: OptionGatewayAudience, Value: first}) } if h := e.get("SOCIALWEB_GATEWAY_KCP_PUBLIC_HOST"); h != "" && !hasOption(s, OptionKcpPublicHost) { s.Options = append(s.Options, v1alpha1.ApplicationOption{Name: OptionKcpPublicHost, Value: h}) } return s }