github.com/anycable/anycable-go@v1.5.1/config/presets.go (about)

     1  package config
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log/slog"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  
    11  	gonanoid "github.com/matoous/go-nanoid"
    12  )
    13  
    14  func (c *Config) Presets() []string {
    15  	if c.UserPresets != nil {
    16  		return c.UserPresets
    17  	}
    18  
    19  	return detectPresetsFromEnv()
    20  }
    21  
    22  func (c *Config) LoadPresets(logger *slog.Logger) error {
    23  	presets := c.Presets()
    24  
    25  	if len(presets) == 0 {
    26  		return nil
    27  	}
    28  
    29  	logger.With("context", "config").Info("load presets", "presets", strings.Join(presets, ","))
    30  
    31  	defaults := NewConfig()
    32  
    33  	for _, preset := range presets {
    34  		switch preset {
    35  		case "fly":
    36  			if err := c.loadFlyPreset(&defaults); err != nil {
    37  				return err
    38  			}
    39  		case "heroku":
    40  			if err := c.loadHerokuPreset(&defaults); err != nil {
    41  				return err
    42  			}
    43  		case "broker":
    44  			if err := c.loadBrokerPreset(&defaults); err != nil {
    45  				return err
    46  			}
    47  		}
    48  	}
    49  
    50  	return nil
    51  }
    52  
    53  func (c *Config) loadFlyPreset(defaults *Config) error {
    54  	if c.Host == defaults.Host {
    55  		c.Host = "0.0.0.0"
    56  	}
    57  
    58  	region, ok := os.LookupEnv("FLY_REGION")
    59  
    60  	if !ok {
    61  		return errors.New("FLY_REGION env is missing")
    62  	}
    63  
    64  	appName, ok := os.LookupEnv("FLY_APP_NAME")
    65  
    66  	if !ok {
    67  		return errors.New("FLY_APP_NAME env is missing")
    68  	}
    69  
    70  	appId, _ := os.LookupEnv("FLY_ALLOC_ID")
    71  
    72  	redisEnabled := (c.Redis.URL != defaults.Redis.URL)
    73  
    74  	// Use the same port for HTTP broadcasts by default
    75  	if c.HTTPBroadcast.Port == defaults.HTTPBroadcast.Port {
    76  		c.HTTPBroadcast.Port = c.Port
    77  	}
    78  
    79  	if c.EmbeddedNats.Name == defaults.EmbeddedNats.Name && appId != "" {
    80  		// We need to be unique to avoid having duplicates during deployments
    81  		suf, _ := gonanoid.Nanoid(3) // nolint: errcheck
    82  		c.EmbeddedNats.Name = fmt.Sprintf("fly-%s-%s-%s", region, appId, suf)
    83  	}
    84  
    85  	if c.EmbeddedNats.ServiceAddr == defaults.EmbeddedNats.ServiceAddr {
    86  		c.EmbeddedNats.ServiceAddr = "nats://0.0.0.0:4222"
    87  	}
    88  
    89  	if c.EmbeddedNats.ClusterAddr == defaults.EmbeddedNats.ClusterAddr {
    90  		c.EmbeddedNats.ClusterAddr = "nats://0.0.0.0:5222"
    91  	}
    92  
    93  	if c.EmbeddedNats.ClusterName == defaults.EmbeddedNats.ClusterName {
    94  		c.EmbeddedNats.ClusterName = fmt.Sprintf("%s-%s-cluster", appName, region)
    95  	}
    96  
    97  	if c.EmbeddedNats.Routes == nil {
    98  		c.EmbeddedNats.Routes = []string{fmt.Sprintf("nats://%s.%s.internal:5222", region, appName)}
    99  	}
   100  
   101  	if c.EmbeddedNats.GatewayAdvertise == defaults.EmbeddedNats.GatewayAdvertise {
   102  		c.EmbeddedNats.GatewayAdvertise = fmt.Sprintf("%s.%s.internal:7222", region, appName)
   103  	}
   104  
   105  	// Enable embedded NATS by default unless another adapter is set for PubSub
   106  	// or Redis URL is provided
   107  	if c.PubSubAdapter == defaults.PubSubAdapter {
   108  		if redisEnabled {
   109  			c.PubSubAdapter = "redis"
   110  		} else {
   111  			c.PubSubAdapter = "nats"
   112  
   113  			// NATS hasn't been configured, so we can embed it
   114  			if !c.EmbedNats || c.NATS.Servers == defaults.NATS.Servers {
   115  				c.EmbedNats = true
   116  				c.NATS.Servers = c.EmbeddedNats.ServiceAddr
   117  				c.BroadcastAdapter = "http,nats"
   118  			}
   119  		}
   120  	}
   121  
   122  	if !redisEnabled && c.BroadcastAdapter == defaults.BroadcastAdapter {
   123  		c.BroadcastAdapter = "http"
   124  	}
   125  
   126  	if rpcName, ok := os.LookupEnv("ANYCABLE_FLY_RPC_APP_NAME"); ok {
   127  		if c.RPC.Host == defaults.RPC.Host {
   128  			c.RPC.Host = fmt.Sprintf("dns:///%s.%s.internal:50051", region, rpcName)
   129  		}
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  func (c *Config) loadHerokuPreset(defaults *Config) error {
   136  	if c.Host == defaults.Host {
   137  		c.Host = "0.0.0.0"
   138  	}
   139  
   140  	if c.HTTPBroadcast.Port == defaults.HTTPBroadcast.Port {
   141  		if herokuPortStr := os.Getenv("PORT"); herokuPortStr != "" {
   142  			herokuPort, err := strconv.Atoi(herokuPortStr)
   143  			if err != nil {
   144  				return err
   145  			}
   146  
   147  			c.HTTPBroadcast.Port = herokuPort
   148  		}
   149  	}
   150  
   151  	return nil
   152  }
   153  
   154  func (c *Config) loadBrokerPreset(defaults *Config) error {
   155  	redisEnabled := (c.Redis.URL != defaults.Redis.URL)
   156  	enatsEnabled := c.EmbedNats
   157  
   158  	if c.BrokerAdapter == defaults.BrokerAdapter {
   159  		if enatsEnabled {
   160  			c.BrokerAdapter = "nats"
   161  		} else {
   162  			c.BrokerAdapter = "memory"
   163  		}
   164  	}
   165  
   166  	if c.BroadcastAdapter == defaults.BroadcastAdapter {
   167  		switch {
   168  		case enatsEnabled:
   169  			c.BroadcastAdapter = "http,nats"
   170  		case redisEnabled:
   171  			c.BroadcastAdapter = "http,redisx,redis"
   172  		default:
   173  			c.BroadcastAdapter = "http"
   174  		}
   175  	}
   176  
   177  	if c.PubSubAdapter == defaults.PubSubAdapter {
   178  		switch {
   179  		case enatsEnabled:
   180  			c.PubSubAdapter = "nats"
   181  		case redisEnabled:
   182  			c.PubSubAdapter = "redis"
   183  		}
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  func detectPresetsFromEnv() []string {
   190  	presets := []string{}
   191  
   192  	if isFlyEnv() {
   193  		presets = append(presets, "fly")
   194  	}
   195  
   196  	if isHerokuEnv() {
   197  		presets = append(presets, "heroku")
   198  	}
   199  
   200  	return presets
   201  }
   202  
   203  func isFlyEnv() bool {
   204  	if _, ok := os.LookupEnv("FLY_APP_NAME"); !ok {
   205  		return false
   206  	}
   207  
   208  	if _, ok := os.LookupEnv("FLY_ALLOC_ID"); !ok {
   209  		return false
   210  	}
   211  
   212  	if _, ok := os.LookupEnv("FLY_REGION"); !ok {
   213  		return false
   214  	}
   215  
   216  	return true
   217  }
   218  
   219  func isHerokuEnv() bool {
   220  	if _, ok := os.LookupEnv("HEROKU_APP_ID"); !ok {
   221  		return false
   222  	}
   223  
   224  	if _, ok := os.LookupEnv("HEROKU_DYNO_ID"); !ok {
   225  		return false
   226  	}
   227  
   228  	return true
   229  }