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  }