github.com/spg/deis@v1.7.3/router/boot.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "net" 7 "os" 8 "os/exec" 9 "os/signal" 10 "strings" 11 "syscall" 12 "time" 13 14 "github.com/ActiveState/tail" 15 "github.com/Sirupsen/logrus" 16 "github.com/coreos/go-etcd/etcd" 17 18 "github.com/deis/deis/router/logger" 19 ) 20 21 var log = logrus.New() 22 23 const ( 24 timeout time.Duration = 10 * time.Second 25 ttl time.Duration = timeout * 2 26 gitLogFile string = "/opt/nginx/logs/git.log" 27 nginxAccessLog string = "/opt/nginx/logs/access.log" 28 nginxErrorLog string = "/opt/nginx/logs/error.log" 29 ) 30 31 func main() { 32 log.Formatter = new(logger.StdOutFormatter) 33 34 logLevel := getopt("LOG", "info") 35 if level, err := logrus.ParseLevel(logLevel); err == nil { 36 log.Level = level 37 } 38 39 log.Debug("reading environment variables...") 40 host := getopt("HOST", "127.0.0.1") 41 42 etcdPort := getopt("ETCD_PORT", "4001") 43 44 etcdPath := getopt("ETCD_PATH", "/deis/router") 45 46 hostEtcdPath := getopt("HOST_ETCD_PATH", "/deis/router/hosts/"+host) 47 48 externalPort := getopt("EXTERNAL_PORT", "80") 49 50 client := etcd.NewClient([]string{"http://" + host + ":" + etcdPort}) 51 52 // wait until etcd has discarded potentially stale values 53 time.Sleep(timeout + 1) 54 55 log.Debug("creating required defaults in etcd...") 56 mkdirEtcd(client, "/deis/controller") 57 mkdirEtcd(client, "/deis/services") 58 mkdirEtcd(client, "/deis/domains") 59 mkdirEtcd(client, "/deis/builder") 60 mkdirEtcd(client, "/deis/certs") 61 mkdirEtcd(client, "/deis/router/hosts") 62 63 setDefaultEtcd(client, etcdPath+"/gzip", "on") 64 65 log.Info("Starting Nginx...") 66 // tail the logs 67 go tailFile(nginxAccessLog) 68 go tailFile(nginxErrorLog) 69 go tailFile(gitLogFile) 70 71 nginxChan := make(chan bool) 72 go launchNginx(nginxChan) 73 <-nginxChan 74 75 // FIXME: have to launch cron first so generate-certs will generate the files nginx requires 76 go launchCron() 77 78 waitForInitialConfd(host+":"+etcdPort, timeout) 79 80 go launchConfd(host + ":" + etcdPort) 81 82 go publishService(client, hostEtcdPath, host, externalPort, uint64(ttl.Seconds())) 83 84 log.Info("deis-router running...") 85 86 exitChan := make(chan os.Signal, 2) 87 signal.Notify(exitChan, syscall.SIGTERM, syscall.SIGINT) 88 go cleanOnExit(exitChan) 89 <-exitChan 90 } 91 92 func launchCron() { 93 // edit crontab 94 crontab := `(echo "* * * * * generate-certs >> /dev/stdout") | crontab -` 95 cmd := exec.Command("bash", "-c", crontab) 96 if err := cmd.Run(); err != nil { 97 log.Fatalf("could not write to crontab: %v", err) 98 } 99 100 // run cron 101 cmd = exec.Command("crond") 102 if err := cmd.Run(); err != nil { 103 log.Fatalf("cron terminated by error: %v", err) 104 } 105 } 106 107 func cleanOnExit(exitChan chan os.Signal) { 108 for _ = range exitChan { 109 tail.Cleanup() 110 } 111 } 112 113 // Wait until the compilation of the templates 114 func waitForInitialConfd(etcd string, timeout time.Duration) { 115 for { 116 var buffer bytes.Buffer 117 output := bufio.NewWriter(&buffer) 118 log.Debugf("Connecting to etcd server %s", etcd) 119 cmd := exec.Command("confd", "-node", etcd, "--confdir", "/app", "-onetime", "--log-level", "error") 120 cmd.Stdout = output 121 cmd.Stderr = output 122 123 err := cmd.Run() 124 output.Flush() 125 if err == nil { 126 break 127 } 128 129 log.Info("waiting for confd to write initial templates...") 130 log.Debugf("\n%s", buffer.String()) 131 time.Sleep(timeout) 132 } 133 } 134 135 func launchConfd(etcd string) { 136 cmd := exec.Command("confd", "-node", etcd, "--confdir", "/app", "--log-level", "error", "--interval", "5") 137 cmd.Stdout = os.Stdout 138 cmd.Stderr = os.Stderr 139 140 if err := cmd.Start(); err != nil { 141 log.Warn("confd terminated by error: %v", err) 142 } 143 } 144 145 func launchNginx(nginxChan chan bool) { 146 cmd := exec.Command("/opt/nginx/sbin/nginx", "-c", "/opt/nginx/conf/nginx.conf") 147 cmd.Stdout = os.Stdout 148 cmd.Stderr = os.Stderr 149 150 if err := cmd.Start(); err != nil { 151 log.Warn("Nginx terminated by error: %v", err) 152 } 153 154 // Wait until the nginx is available 155 for { 156 _, err := net.DialTimeout("tcp", "127.0.0.1:80", timeout) 157 if err == nil { 158 nginxChan <- true 159 break 160 } 161 } 162 163 if err := cmd.Wait(); err != nil { 164 log.Warnf("Nginx terminated by error: %v", err) 165 } else { 166 log.Info("Reloading nginx (change in configuration)...") 167 } 168 } 169 170 func tailFile(path string) { 171 mkfifo(path) 172 t, _ := tail.TailFile(path, tail.Config{Follow: true}) 173 174 for line := range t.Lines { 175 log.Info(line.Text) 176 } 177 } 178 179 func mkfifo(path string) { 180 os.Remove(path) 181 if err := syscall.Mkfifo(path, syscall.S_IFIFO|0666); err != nil { 182 log.Fatalf("%v", err) 183 } 184 } 185 186 func publishService( 187 client *etcd.Client, 188 etcdPath string, 189 host string, 190 externalPort string, 191 ttl uint64) { 192 193 for { 194 setEtcd(client, etcdPath, host+":"+externalPort, ttl) 195 time.Sleep(timeout) 196 } 197 } 198 199 func setEtcd(client *etcd.Client, key, value string, ttl uint64) { 200 _, err := client.Set(key, value, ttl) 201 if err != nil { 202 log.Warn(err) 203 } 204 } 205 206 func setDefaultEtcd(client *etcd.Client, key, value string) { 207 _, err := client.Set(key, value, 0) 208 if err != nil { 209 log.Warn(err) 210 } 211 } 212 213 func mkdirEtcd(client *etcd.Client, path string) { 214 _, err := client.CreateDir(path, 0) 215 if err != nil && !strings.Contains(err.Error(), "Key already exists") { 216 log.Warn(err) 217 } 218 } 219 220 func getopt(name, dfault string) string { 221 value := os.Getenv(name) 222 if value == "" { 223 value = dfault 224 } 225 return value 226 }