github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/dev/devcam/server.go (about)

     1  /*
     2  Copyright 2013 The Camlistore Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // This file adds the "server" subcommand to devcam, to run camlistored.
    18  
    19  package main
    20  
    21  import (
    22  	"errors"
    23  	"flag"
    24  	"fmt"
    25  	"log"
    26  	"os"
    27  	"os/exec"
    28  	"path/filepath"
    29  	"strconv"
    30  	"strings"
    31  
    32  	"camlistore.org/pkg/cmdmain"
    33  	"camlistore.org/pkg/osutil"
    34  )
    35  
    36  type serverCmd struct {
    37  	// start of flag vars
    38  	all      bool
    39  	hostname string
    40  	port     string
    41  	tls      bool
    42  	wipe     bool
    43  	debug    bool
    44  
    45  	mongo    bool
    46  	mysql    bool
    47  	postgres bool
    48  	sqlite   bool
    49  
    50  	slow     bool
    51  	throttle int
    52  	latency  int
    53  
    54  	fullClosure bool
    55  	mini        bool
    56  	publish     bool
    57  
    58  	openBrowser      bool
    59  	flickrAPIKey     string
    60  	foursquareAPIKey string
    61  	extraArgs        string // passed to camlistored
    62  	// end of flag vars
    63  
    64  	listen string // address + port to listen on
    65  	root   string // the temp dir where blobs are stored
    66  	env    *Env
    67  }
    68  
    69  func init() {
    70  	cmdmain.RegisterCommand("server", func(flags *flag.FlagSet) cmdmain.CommandRunner {
    71  		cmd := &serverCmd{
    72  			env: NewCopyEnv(),
    73  		}
    74  		flags.BoolVar(&cmd.all, "all", false, "Listen on all interfaces.")
    75  		flags.StringVar(&cmd.hostname, "hostname", "", "Hostname to advertise, defaults to the hostname reported by the kernel.")
    76  		flags.StringVar(&cmd.port, "port", "3179", "Port to listen on.")
    77  		flags.BoolVar(&cmd.tls, "tls", false, "Use TLS.")
    78  		flags.BoolVar(&cmd.wipe, "wipe", false, "Wipe the blobs on disk and the indexer.")
    79  		flags.BoolVar(&cmd.debug, "debug", false, "Enable http debugging.")
    80  		flags.BoolVar(&cmd.publish, "publish", true, "Enable publish handlers")
    81  		flags.BoolVar(&cmd.mini, "mini", false, "Enable minimal mode, where all optional features are disabled. (Currently just publishing)")
    82  
    83  		flags.BoolVar(&cmd.mongo, "mongo", false, "Use mongodb as the indexer. Excludes -mysql, -postgres, -sqlite.")
    84  		flags.BoolVar(&cmd.mysql, "mysql", false, "Use mysql as the indexer. Excludes -mongo, -postgres, -sqlite.")
    85  		flags.BoolVar(&cmd.postgres, "postgres", false, "Use postgres as the indexer. Excludes -mongo, -mysql, -sqlite.")
    86  		flags.BoolVar(&cmd.sqlite, "sqlite", false, "Use sqlite as the indexer. Excludes -mongo, -mysql, -postgres.")
    87  
    88  		flags.BoolVar(&cmd.slow, "slow", false, "Add artificial latency.")
    89  		flags.IntVar(&cmd.throttle, "throttle", 150, "If -slow, this is the rate in kBps, to which we should throttle.")
    90  		flags.IntVar(&cmd.latency, "latency", 90, "If -slow, this is the added latency, in ms.")
    91  
    92  		flags.BoolVar(&cmd.fullClosure, "fullclosure", false, "Use the ondisk closure library.")
    93  
    94  		flags.BoolVar(&cmd.openBrowser, "openbrowser", false, "Open the start page on startup.")
    95  		flags.StringVar(&cmd.flickrAPIKey, "flickrapikey", "", "The key and secret to use with the Flickr importer. Formatted as '<key>:<secret>'.")
    96  		flags.StringVar(&cmd.foursquareAPIKey, "foursquareapikey", "", "The key and secret to use with the Foursquare importer. Formatted as '<clientID>:<clientSecret>'.")
    97  		flags.StringVar(&cmd.root, "root", "", "A directory to store data in. Defaults to a location in the OS temp directory.")
    98  		flags.StringVar(&cmd.extraArgs, "extraargs", "",
    99  			"List of comma separated options that will be passed to camlistored")
   100  		return cmd
   101  	})
   102  }
   103  
   104  func (c *serverCmd) Usage() {
   105  	fmt.Fprintf(cmdmain.Stderr, "Usage: devcam [globalopts] server [serveropts]\n")
   106  }
   107  
   108  func (c *serverCmd) Examples() []string {
   109  	return []string{
   110  		"-wipe -mysql -fullclosure",
   111  	}
   112  }
   113  
   114  func (c *serverCmd) Describe() string {
   115  	return "run the stand-alone camlistored in dev mode."
   116  }
   117  
   118  func (c *serverCmd) checkFlags(args []string) error {
   119  	if len(args) != 0 {
   120  		c.Usage()
   121  	}
   122  	nindex := 0
   123  	for _, v := range []bool{c.mongo, c.mysql, c.postgres, c.sqlite} {
   124  		if v {
   125  			nindex++
   126  		}
   127  	}
   128  	if nindex > 1 {
   129  		return fmt.Errorf("Only one index option allowed")
   130  	}
   131  
   132  	if _, err := strconv.ParseInt(c.port, 0, 0); err != nil {
   133  		return fmt.Errorf("Invalid -port value: %q", c.port)
   134  	}
   135  	return nil
   136  }
   137  
   138  func (c *serverCmd) setRoot() error {
   139  	if c.root == "" {
   140  		user := osutil.Username()
   141  		if user == "" {
   142  			return errors.New("Could not get username from environment")
   143  		}
   144  		c.root = filepath.Join(os.TempDir(), "camliroot-"+user, "port"+c.port)
   145  	}
   146  	log.Printf("Temp dir root is %v", c.root)
   147  	if c.wipe {
   148  		log.Printf("Wiping %v", c.root)
   149  		if err := os.RemoveAll(c.root); err != nil {
   150  			return fmt.Errorf("Could not wipe %v: %v", c.root, err)
   151  		}
   152  	}
   153  	return nil
   154  }
   155  
   156  func (c *serverCmd) makeSuffixdir(fullpath string) {
   157  	if err := os.MkdirAll(fullpath, 0755); err != nil {
   158  		log.Fatalf("Could not create %v: %v", fullpath, err)
   159  	}
   160  }
   161  
   162  func (c *serverCmd) setEnvVars() error {
   163  	c.env.SetCamdevVars(false)
   164  	setenv := func(k, v string) {
   165  		c.env.Set(k, v)
   166  	}
   167  	if c.slow {
   168  		setenv("DEV_THROTTLE_KBPS", fmt.Sprintf("%d", c.throttle))
   169  		setenv("DEV_THROTTLE_LATENCY_MS", fmt.Sprintf("%d", c.latency))
   170  	}
   171  	if c.debug {
   172  		setenv("CAMLI_HTTP_DEBUG", "1")
   173  	}
   174  	user := osutil.Username()
   175  	if user == "" {
   176  		return errors.New("Could not get username from environment")
   177  	}
   178  	setenv("CAMLI_FULL_INDEX_SYNC_ON_START", "false") // TODO: option to make this true
   179  	setenv("CAMLI_DBNAME", "devcamli"+user)
   180  	setenv("CAMLI_MYSQL_ENABLED", "false")
   181  	setenv("CAMLI_MONGO_ENABLED", "false")
   182  	setenv("CAMLI_POSTGRES_ENABLED", "false")
   183  	setenv("CAMLI_SQLITE_ENABLED", "false")
   184  	setenv("CAMLI_KVINDEX_ENABLED", "false")
   185  
   186  	setenv("CAMLI_PUBLISH_ENABLED", strconv.FormatBool(c.publish))
   187  	switch {
   188  	case c.mongo:
   189  		setenv("CAMLI_MONGO_ENABLED", "true")
   190  		setenv("CAMLI_INDEXER_PATH", "/index-mongo/")
   191  	case c.postgres:
   192  		setenv("CAMLI_POSTGRES_ENABLED", "true")
   193  		setenv("CAMLI_INDEXER_PATH", "/index-postgres/")
   194  	case c.mysql:
   195  		setenv("CAMLI_MYSQL_ENABLED", "true")
   196  		setenv("CAMLI_INDEXER_PATH", "/index-mysql/")
   197  	case c.sqlite:
   198  		setenv("CAMLI_SQLITE_ENABLED", "true")
   199  		setenv("CAMLI_INDEXER_PATH", "/index-sqlite/")
   200  		if c.root == "" {
   201  			panic("no root set")
   202  		}
   203  		setenv("CAMLI_DBNAME", filepath.Join(c.root, "sqliteindex.db"))
   204  	default:
   205  		setenv("CAMLI_KVINDEX_ENABLED", "true")
   206  		setenv("CAMLI_INDEXER_PATH", "/index-kv/")
   207  		if c.root == "" {
   208  			panic("no root set")
   209  		}
   210  		setenv("CAMLI_DBNAME", filepath.Join(c.root, "kvindex.db"))
   211  	}
   212  
   213  	base := "http://localhost:" + c.port
   214  	c.listen = "127.0.0.1:" + c.port
   215  	if c.all {
   216  		c.listen = "0.0.0.0:" + c.port
   217  		if c.hostname == "" {
   218  			hostname, err := os.Hostname()
   219  			if err != nil {
   220  				return fmt.Errorf("Could not get system hostname: %v", err)
   221  			}
   222  			base = "http://" + hostname + ":" + c.port
   223  		} else {
   224  			base = "http://" + c.hostname + ":" + c.port
   225  		}
   226  	}
   227  	setenv("CAMLI_TLS", "false")
   228  	if c.tls {
   229  		base = strings.Replace(base, "http://", "https://", 1)
   230  		setenv("CAMLI_TLS", "true")
   231  	}
   232  	setenv("CAMLI_BASEURL", base)
   233  
   234  	setenv("CAMLI_DEV_CAMLI_ROOT", camliSrcRoot)
   235  	setenv("CAMLI_AUTH", "devauth:pass3179")
   236  	fullSuffix := func(name string) string {
   237  		return filepath.Join(c.root, name)
   238  	}
   239  	suffixes := map[string]string{
   240  		"CAMLI_ROOT":          fullSuffix("bs"),
   241  		"CAMLI_ROOT_SHARD1":   fullSuffix("s1"),
   242  		"CAMLI_ROOT_SHARD2":   fullSuffix("s2"),
   243  		"CAMLI_ROOT_REPLICA1": fullSuffix("r1"),
   244  		"CAMLI_ROOT_REPLICA2": fullSuffix("r2"),
   245  		"CAMLI_ROOT_REPLICA3": fullSuffix("r3"),
   246  		"CAMLI_ROOT_CACHE":    fullSuffix("cache"),
   247  		"CAMLI_ROOT_ENCMETA":  fullSuffix("encmeta"),
   248  		"CAMLI_ROOT_ENCBLOB":  fullSuffix("encblob"),
   249  	}
   250  	for k, v := range suffixes {
   251  		c.makeSuffixdir(v)
   252  		setenv(k, v)
   253  	}
   254  	setenv("CAMLI_PORT", c.port)
   255  	if c.flickrAPIKey != "" {
   256  		setenv("CAMLI_FLICKR_ENABLED", "true")
   257  		setenv("CAMLI_FLICKR_API_KEY", c.flickrAPIKey)
   258  	}
   259  	if c.foursquareAPIKey != "" {
   260  		setenv("CAMLI_FOURSQUARE_ENABLED", "true")
   261  		setenv("CAMLI_FOURSQUARE_API_KEY", c.foursquareAPIKey)
   262  	}
   263  	setenv("CAMLI_CONFIG_DIR", "config")
   264  	return nil
   265  }
   266  
   267  func (c *serverCmd) setupIndexer() error {
   268  	args := []string{"dbinit"}
   269  	switch {
   270  	case c.postgres:
   271  		args = append(args,
   272  			"-dbtype=postgres",
   273  			"-user=postgres",
   274  			"-password=postgres",
   275  			"-host=localhost",
   276  			"-dbname="+c.env.m["CAMLI_DBNAME"])
   277  	case c.mysql:
   278  		args = append(args,
   279  			"-user=root",
   280  			"-password=root",
   281  			"-host=localhost",
   282  			"-dbname="+c.env.m["CAMLI_DBNAME"])
   283  	case c.sqlite:
   284  		args = append(args,
   285  			"-dbtype=sqlite",
   286  			"-dbname="+c.env.m["CAMLI_DBNAME"])
   287  	case c.mongo:
   288  		args = append(args,
   289  			"-dbtype=mongo",
   290  			"-host=localhost",
   291  			"-dbname="+c.env.m["CAMLI_DBNAME"])
   292  	default:
   293  		return nil
   294  	}
   295  	if c.wipe {
   296  		args = append(args, "-wipe")
   297  	} else {
   298  		args = append(args, "-ignoreexists")
   299  	}
   300  	binPath := filepath.Join("bin", "camtool")
   301  	cmd := exec.Command(binPath, args...)
   302  	cmd.Stdout = os.Stdout
   303  	cmd.Stderr = os.Stderr
   304  	if err := cmd.Run(); err != nil {
   305  		return fmt.Errorf("Could not run camtool dbinit: %v", err)
   306  	}
   307  	return nil
   308  }
   309  
   310  func (c *serverCmd) syncTemplateBlobs() error {
   311  	if c.wipe {
   312  		templateDir := "dev-server-template"
   313  		if _, err := os.Stat(templateDir); err != nil {
   314  			if os.IsNotExist(err) {
   315  				return nil
   316  			}
   317  			return err
   318  		}
   319  		blobsDir := filepath.Join(c.root, "sha1")
   320  		if err := cpDir(templateDir, blobsDir, nil); err != nil {
   321  			return fmt.Errorf("Could not cp template blobs: %v", err)
   322  		}
   323  	}
   324  	return nil
   325  }
   326  
   327  func (c *serverCmd) setFullClosure() error {
   328  	if c.fullClosure {
   329  		oldsvn := filepath.Join(c.root, filepath.FromSlash("tmp/closure-lib/.svn"))
   330  		if err := os.RemoveAll(oldsvn); err != nil {
   331  			return fmt.Errorf("Could not remove svn checkout of closure-lib %v: %v",
   332  				oldsvn, err)
   333  		}
   334  		log.Println("Updating closure library...")
   335  		args := []string{"run", "third_party/closure/updatelibrary.go", "-verbose"}
   336  		cmd := exec.Command("go", args...)
   337  		cmd.Stdout = os.Stdout
   338  		cmd.Stderr = os.Stderr
   339  		if err := cmd.Run(); err != nil {
   340  			return fmt.Errorf("Could not run updatelibrary.go: %v", err)
   341  		}
   342  		c.env.Set("CAMLI_DEV_CLOSURE_DIR", "third_party/closure/lib/closure")
   343  	}
   344  	return nil
   345  }
   346  
   347  func (c *serverCmd) RunCommand(args []string) error {
   348  	if c.mini {
   349  		c.publish = false
   350  	}
   351  	err := c.checkFlags(args)
   352  	if err != nil {
   353  		return cmdmain.UsageError(fmt.Sprint(err))
   354  	}
   355  	if !*noBuild {
   356  		withSqlite = c.sqlite
   357  		for _, name := range []string{
   358  			filepath.Join("server", "camlistored"),
   359  			filepath.Join("cmd", "camtool"),
   360  		} {
   361  			err := build(name)
   362  			if err != nil {
   363  				return fmt.Errorf("Could not build %v: %v", name, err)
   364  			}
   365  		}
   366  	}
   367  	if err := c.setRoot(); err != nil {
   368  		return fmt.Errorf("Could not setup the camli root: %v", err)
   369  	}
   370  	if err := c.setEnvVars(); err != nil {
   371  		return fmt.Errorf("Could not setup the env vars: %v", err)
   372  	}
   373  	if err := c.setupIndexer(); err != nil {
   374  		return fmt.Errorf("Could not setup the indexer: %v", err)
   375  	}
   376  	if err := c.syncTemplateBlobs(); err != nil {
   377  		return fmt.Errorf("Could not copy the template blobs: %v", err)
   378  	}
   379  	if err := c.setFullClosure(); err != nil {
   380  		return fmt.Errorf("Could not setup the closure lib: %v", err)
   381  	}
   382  
   383  	log.Printf("Starting dev server on %v/ui/ with password \"pass3179\"\n",
   384  		c.env.m["CAMLI_BASEURL"])
   385  
   386  	camliBin := filepath.Join("bin", "camlistored")
   387  	cmdArgs := []string{
   388  		"-configfile=" + filepath.Join(camliSrcRoot, "config", "dev-server-config.json"),
   389  		"-listen=" + c.listen,
   390  		"-openbrowser=" + strconv.FormatBool(c.openBrowser),
   391  	}
   392  	if c.extraArgs != "" {
   393  		cmdArgs = append(cmdArgs, strings.Split(c.extraArgs, ",")...)
   394  	}
   395  	return runExec(camliBin, cmdArgs, c.env)
   396  }