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 }