func newFromConfig(_ blobserver.Loader, config jsonconfig.Obj) (blobserver.Storage, error) { client := &s3.Client{ Auth: &s3.Auth{ AccessKey: config.RequiredString("aws_access_key"), SecretAccessKey: config.RequiredString("aws_secret_access_key"), Hostname: config.OptionalString("hostname", ""), }, HTTPClient: http.DefaultClient, } sto := &s3Storage{ s3Client: client, bucket: config.RequiredString("bucket"), } skipStartupCheck := config.OptionalBool("skipStartupCheck", false) if err := config.Validate(); err != nil { return nil, err } if !skipStartupCheck { // TODO: skip this check if a file // ~/.camli/.configcheck/sha1-("IS GOOD: s3: sha1(access key + // secret key)") exists and has recent time? buckets, err := client.Buckets() if err != nil { return nil, fmt.Errorf("Failed to get bucket list from S3: %v", err) } haveBucket := make(map[string]bool) for _, b := range buckets { haveBucket[b.Name] = true } if !haveBucket[sto.bucket] { return nil, fmt.Errorf("S3 bucket %q doesn't exist. Create it first at https://console.aws.amazon.com/s3/home") } } return sto, nil }
func newFromConfig(_ blobserver.Loader, config jsonconfig.Obj) (blobserver.Storage, error) { client := &swift.Connection{ UserName: config.RequiredString("user_name"), Tenant: config.RequiredString("tenant"), ApiKey: config.RequiredString("secret"), AuthUrl: config.RequiredString("auth_url"), } if err := client.Authenticate(); err != nil { return nil, fmt.Errorf("Authentication failure on swift: %v", err) } sto := &swiftStorage{ client: client, container: config.OptionalString("container", "default"), } skipStartupCheck := config.OptionalBool("skipStartupCheck", false) if err := config.Validate(); err != nil { return nil, err } if !skipStartupCheck { containers, err := client.ContainerNames(nil) if err != nil { return nil, fmt.Errorf("Failed to get container list from swift: %v", err) } haveContainer := make(map[string]bool) for _, c := range containers { haveContainer[c] = true } if !haveContainer[sto.container] { if err := client.ContainerCreate(sto.container, nil); err != nil { return nil, fmt.Errorf("Swift bucket %s doesn't exist and it's impossible to create it: ", sto.container, err) } } } return sto, nil }
func newHandlerFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (http.Handler, error) { indexPrefix := conf.RequiredString("index") // TODO: add optional help tips here? ownerBlobStr := conf.RequiredString("owner") devBlockStartupPrefix := conf.OptionalString("devBlockStartupOn", "") if err := conf.Validate(); err != nil { return nil, err } if devBlockStartupPrefix != "" { _, err := ld.GetHandler(devBlockStartupPrefix) if err != nil { return nil, fmt.Errorf("search handler references bogus devBlockStartupOn handler %s: %v", devBlockStartupPrefix, err) } } indexHandler, err := ld.GetHandler(indexPrefix) if err != nil { return nil, fmt.Errorf("search config references unknown handler %q", indexPrefix) } indexer, ok := indexHandler.(Index) if !ok { return nil, fmt.Errorf("search config references invalid indexer %q (actually a %T)", indexPrefix, indexHandler) } ownerBlobRef, ok := blob.Parse(ownerBlobStr) if !ok { return nil, fmt.Errorf("search 'owner' has malformed blobref %q; expecting e.g. sha1-xxxxxxxxxxxx", ownerBlobStr) } return &Handler{ index: indexer, owner: ownerBlobRef, }, nil }
func newFromConfig(ld blobserver.Loader, config jsonconfig.Obj) (bs blobserver.Storage, err error) { sto := &storage{ SimpleBlobHubPartitionMap: &blobserver.SimpleBlobHubPartitionMap{}, } key := config.OptionalString("key", "") keyFile := config.OptionalString("keyFile", "") switch { case key != "": sto.key = []byte(key) case keyFile != "": // TODO: check that keyFile's unix permissions aren't too permissive. sto.key, err = ioutil.ReadFile(keyFile) if err != nil { return } } sto.blobs, err = ld.GetStorage(config.RequiredString("blobs")) if err != nil { return } sto.meta, err = ld.GetStorage(config.RequiredString("meta")) if err != nil { return } if err := config.Validate(); err != nil { return nil, err } if sto.key == nil { // TODO: add a way to prompt from stdin on start? or keychain support? return nil, errors.New("no encryption key set with 'key' or 'keyFile'") } return sto, nil }
func indexFromConfig(ld blobserver.Loader, config jsonconfig.Obj) (storage blobserver.Storage, err error) { is := &indexStorage{} var ( blobPrefix = config.RequiredString("blobSource") ns = config.OptionalString("namespace", "") ) if err := config.Validate(); err != nil { return nil, err } sto, err := ld.GetStorage(blobPrefix) if err != nil { return nil, err } is.ns, err = sanitizeNamespace(ns) if err != nil { return nil, err } ix, err := index.New(is) if err != nil { return nil, err } ix.BlobSource = sto ix.KeyFetcher = ix.BlobSource // TODO(bradfitz): global search? something else? return ix, nil }
func newFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (storage blobserver.Storage, err error) { sto := &condStorage{} receive := conf.OptionalStringOrObject("write") read := conf.RequiredString("read") remove := conf.OptionalString("remove", "") if err := conf.Validate(); err != nil { return nil, err } if receive != nil { sto.storageForReceive, err = buildStorageForReceive(ld, receive) if err != nil { return } } sto.read, err = ld.GetStorage(read) if err != nil { return } if remove != "" { sto.remove, err = ld.GetStorage(remove) if err != nil { return } } return sto, nil }
func newKeyValueFromJSONConfig(cfg jsonconfig.Obj) (sorted.KeyValue, error) { host := cfg.OptionalString("host", "") dsn := fmt.Sprintf("%s/%s/%s", cfg.RequiredString("database"), cfg.RequiredString("user"), cfg.OptionalString("password", ""), ) if err := cfg.Validate(); err != nil { return nil, err } if host != "" { // TODO(mpl): document that somewhere if !strings.Contains(host, ":") { host = host + ":3306" } dsn = "tcp:" + host + "*" + dsn } db, err := sql.Open("mymysql", dsn) if err != nil { return nil, err } for _, tableSql := range SQLCreateTables() { if _, err := db.Exec(tableSql); err != nil { return nil, fmt.Errorf("error creating table with %q: %v", tableSql, err) } } if _, err := db.Exec(fmt.Sprintf(`REPLACE INTO meta VALUES ('version', '%d')`, SchemaVersion())); err != nil { return nil, fmt.Errorf("error setting schema version: %v", err) } kv := &keyValue{ db: db, KeyValue: &sqlkv.KeyValue{ DB: db, }, } if err := kv.ping(); err != nil { return nil, fmt.Errorf("MySQL db unreachable: %v", err) } version, err := kv.SchemaVersion() if err != nil { return nil, fmt.Errorf("error getting schema version (need to init database?): %v", err) } if version != requiredSchemaVersion { if version == 20 && requiredSchemaVersion == 21 { fmt.Fprintf(os.Stderr, fixSchema20to21) } if os.Getenv("CAMLI_DEV_CAMLI_ROOT") != "" { // Good signal that we're using the devcam server, so help out // the user with a more useful tip: return nil, fmt.Errorf("database schema version is %d; expect %d (run \"devcam server --wipe\" to wipe both your blobs and re-populate the database schema)", version, requiredSchemaVersion) } return nil, fmt.Errorf("database schema version is %d; expect %d (need to re-init/upgrade database?)", version, requiredSchemaVersion) } return kv, nil }
// detectConfigChange returns an informative error if conf contains obsolete keys. func detectConfigChange(conf jsonconfig.Obj) error { oldHTTPSKey, oldHTTPSCert := conf.OptionalString("HTTPSKeyFile", ""), conf.OptionalString("HTTPSCertFile", "") if oldHTTPSKey != "" || oldHTTPSCert != "" { return fmt.Errorf("Config keys %q and %q have respectively been renamed to %q and %q, please fix your server config.", "HTTPSKeyFile", "HTTPSCertFile", "httpsKey", "httpsCert") } return nil }
// NewHandler returns a Handler that proxies requests to an app. Start() on the // Handler starts the app. // The apiHost must end in a slash and is the camlistored API server for the app // process to hit. // The appHandlerPrefix is the URL path prefix on apiHost where the app is mounted. // It must end in a slash, and be at minimum "/". // The conf object has the following members, related to the vars described in // doc/app-environment.txt: // "program", string, required. File name of the app's program executable. Either // an absolute path, or the name of a file located in CAMLI_APP_BINDIR or in PATH. // "backendURL", string, optional. Automatic if absent. It sets CAMLI_APP_BACKEND_URL. // "appConfig", object, optional. Additional configuration that the app can request from Camlistore. func NewHandler(conf jsonconfig.Obj, apiHost, appHandlerPrefix string) (*Handler, error) { // TODO: remove the appHandlerPrefix if/when we change where the app config JSON URL is made available. name := conf.RequiredString("program") backendURL := conf.OptionalString("backendURL", "") appConfig := conf.OptionalObject("appConfig") // TODO(mpl): add an auth token in the extra config of the dev server config, // that the hello app can use to setup a status handler than only responds // to requests with that token. if err := conf.Validate(); err != nil { return nil, err } if apiHost == "" { return nil, fmt.Errorf("app: could not initialize Handler for %q: Camlistore apiHost is unknown", name) } if appHandlerPrefix == "" { return nil, fmt.Errorf("app: could not initialize Handler for %q: empty appHandlerPrefix", name) } if backendURL == "" { var err error // If not specified in the conf, we're dynamically picking the port of the CAMLI_APP_BACKEND_URL // now (instead of letting the app itself do it), because we need to know it in advance in order // to set the app handler's proxy. backendURL, err = randPortBackendURL(apiHost, appHandlerPrefix) if err != nil { return nil, err } } username, password := auth.RandToken(20), auth.RandToken(20) camliAuth := username + ":" + password basicAuth := auth.NewBasicAuth(username, password) envVars := map[string]string{ "CAMLI_API_HOST": apiHost, "CAMLI_AUTH": camliAuth, "CAMLI_APP_BACKEND_URL": backendURL, } if appConfig != nil { envVars["CAMLI_APP_CONFIG_URL"] = apiHost + strings.TrimPrefix(appHandlerPrefix, "/") + "config.json" } proxyURL, err := url.Parse(backendURL) if err != nil { return nil, fmt.Errorf("could not parse backendURL %q: %v", backendURL, err) } return &Handler{ name: name, envVars: envVars, auth: basicAuth, appConfig: appConfig, proxy: httputil.NewSingleHostReverseProxy(proxyURL), backendURL: backendURL, }, nil }
func newJSONSignFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (http.Handler, error) { pubKeyDestPrefix := conf.OptionalString("publicKeyDest", "") // either a short form ("26F5ABDA") or one the longer forms. keyId := conf.RequiredString("keyId") h := &Handler{ secretRing: conf.OptionalString("secretRing", ""), } var err error if err = conf.Validate(); err != nil { return nil, err } h.entity, err = jsonsign.EntityFromSecring(keyId, h.secretRingPath()) if err != nil { return nil, err } armoredPublicKey, err := jsonsign.ArmoredPublicKey(h.entity) ms := new(blobref.MemoryStore) h.pubKeyBlobRef, err = ms.AddBlob(crypto.SHA1, armoredPublicKey) if err != nil { return nil, err } h.pubKeyFetcher = ms if pubKeyDestPrefix != "" { sto, err := ld.GetStorage(pubKeyDestPrefix) if err != nil { return nil, err } h.pubKeyDest = sto if sto != nil { if ctxReq, ok := ld.GetRequestContext(); ok { if w, ok := sto.(blobserver.ContextWrapper); ok { sto = w.WrapContext(ctxReq) } } err := h.uploadPublicKey(sto, armoredPublicKey) if err != nil { return nil, fmt.Errorf("Error seeding self public key in storage: %v", err) } } } h.pubKeyBlobRefServeSuffix = "camli/" + h.pubKeyBlobRef.String() h.pubKeyHandler = &gethandler.Handler{ Fetcher: ms, AllowGlobalAccess: true, // just public keys } return h, nil }
func newJSONSignFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (http.Handler, error) { pubKeyDestPrefix := conf.OptionalString("publicKeyDest", "") // either a short form ("26F5ABDA") or one the longer forms. keyId := conf.RequiredString("keyId") h := &Handler{ secretRing: conf.OptionalString("secretRing", ""), } var err error if err = conf.Validate(); err != nil { return nil, err } h.entity, err = jsonsign.EntityFromSecring(keyId, h.secretRingPath()) if err != nil { return nil, err } armoredPublicKey, err := jsonsign.ArmoredPublicKey(h.entity) ms := new(blob.MemoryStore) h.pubKeyBlobRef, err = ms.AddBlob(crypto.SHA1, armoredPublicKey) if err != nil { return nil, err } h.pubKeyFetcher = ms if pubKeyDestPrefix != "" { sto, err := ld.GetStorage(pubKeyDestPrefix) if err != nil { return nil, err } h.pubKeyDest = sto if sto != nil { err := h.uploadPublicKey(sto, armoredPublicKey) if err != nil { return nil, fmt.Errorf("Error seeding self public key in storage: %v", err) } } } h.pubKeyBlobRefServeSuffix = "camli/" + h.pubKeyBlobRef.String() h.pubKeyHandler = &gethandler.Handler{ Fetcher: ms, } h.signer, err = schema.NewSigner(h.pubKeyBlobRef, strings.NewReader(armoredPublicKey), h.entity) if err != nil { return nil, err } return h, nil }
func indexFromConfig(ld blobserver.Loader, config jsonconfig.Obj) (storage blobserver.Storage, err os.Error) { sto := &appengineIndex{} ns := config.OptionalString("namespace", "") if err := config.Validate(); err != nil { return nil, err } sto.namespace, err = sanitizeNamespace(ns) if err != nil { return nil, err } return sto, nil }
// ConfigFromJSON populates Config from config, and validates // config. It returns an error if config fails to validate. func ConfigFromJSON(config jsonconfig.Obj) (Config, error) { conf := Config{ Host: config.OptionalString("host", "localhost"), User: config.RequiredString("user"), Password: config.OptionalString("password", ""), Database: config.RequiredString("database"), } if err := config.Validate(); err != nil { return Config{}, err } return conf, nil }
func newFromConfig(ld blobserver.Loader, config jsonconfig.Obj) (storage blobserver.Storage, err error) { sto := &appengineStorage{ namespace: config.OptionalString("namespace", ""), } if err := config.Validate(); err != nil { return nil, err } sto.namespace, err = sanitizeNamespace(sto.namespace) if err != nil { return nil, err } return sto, nil }
// convertToMultiServers takes an old style single-server client configuration and maps it to new a multi-servers configuration that is returned. func convertToMultiServers(conf jsonconfig.Obj) (jsonconfig.Obj, error) { server := conf.OptionalString("server", "") if server == "" { return nil, errors.New("Could not convert config to multi-servers style: no \"server\" key found.") } newConf := jsonconfig.Obj{ "servers": map[string]interface{}{ server: map[string]interface{}{ "auth": conf.OptionalString("auth", ""), "default": true, "server": server, }, }, "identity": conf.OptionalString("identity", ""), "identitySecretRing": conf.OptionalString("identitySecretRing", osutil.IdentitySecretRing()), } if ignoredFiles := conf.OptionalList("ignoredFiles"); ignoredFiles != nil { var list []interface{} for _, v := range ignoredFiles { list = append(list, v) } newConf["ignoredFiles"] = list } return newConf, nil }
func newFromConfig(ld blobserver.Loader, config jsonconfig.Obj) (storage blobserver.Storage, err os.Error) { sto := &appengineStorage{ SimpleBlobHubPartitionMap: &blobserver.SimpleBlobHubPartitionMap{}, } sto.namespace = config.OptionalString("namespace", "") if err := config.Validate(); err != nil { return nil, err } sto.namespace, err = sanitizeNamespace(sto.namespace) if err != nil { return nil, err } return sto, nil }
func newJSONSignFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (http.Handler, error) { var ( // either a short form ("26F5ABDA") or one the longer forms. keyId = conf.RequiredString("keyId") pubKeyDestPrefix = conf.OptionalString("publicKeyDest", "") secretRing = conf.OptionalString("secretRing", "") ) if err := conf.Validate(); err != nil { return nil, err } h := &Handler{ secretRing: secretRing, } var err error h.entity, err = jsonsign.EntityFromSecring(keyId, h.secretRingPath()) if err != nil { return nil, err } h.pubKey, err = jsonsign.ArmoredPublicKey(h.entity) ms := &memory.Storage{} h.pubKeyBlobRef = blob.SHA1FromString(h.pubKey) if _, err := ms.ReceiveBlob(h.pubKeyBlobRef, strings.NewReader(h.pubKey)); err != nil { return nil, fmt.Errorf("could not store pub key blob: %v", err) } h.pubKeyFetcher = ms if pubKeyDestPrefix != "" { sto, err := ld.GetStorage(pubKeyDestPrefix) if err != nil { return nil, err } h.pubKeyDest = sto } h.pubKeyBlobRefServeSuffix = "camli/" + h.pubKeyBlobRef.String() h.pubKeyHandler = &gethandler.Handler{ Fetcher: ms, } h.signer, err = schema.NewSigner(h.pubKeyBlobRef, strings.NewReader(h.pubKey), h.entity) if err != nil { return nil, err } return h, nil }
func newFromConfig(ld blobserver.Loader, config jsonconfig.Obj) (blobserver.Storage, error) { var ( blobPrefix = config.RequiredString("blobSource") host = config.OptionalString("host", "localhost") user = config.RequiredString("user") password = config.OptionalString("password", "") database = config.RequiredString("database") ) if err := config.Validate(); err != nil { return nil, err } sto, err := ld.GetStorage(blobPrefix) if err != nil { return nil, err } isto, err := NewStorage(host, user, password, database) if err != nil { return nil, err } is := isto.(*myIndexStorage) if err := is.ping(); err != nil { return nil, err } version, err := is.SchemaVersion() if err != nil { return nil, fmt.Errorf("error getting schema version (need to init database?): %v", err) } if version != requiredSchemaVersion { if version == 20 && requiredSchemaVersion == 21 { fmt.Fprintf(os.Stderr, fixSchema20to21) } if os.Getenv("CAMLI_DEV_CAMLI_ROOT") != "" { // Good signal that we're using the devcam server, so help out // the user with a more useful tip: return nil, fmt.Errorf("database schema version is %d; expect %d (run \"devcam server --wipe\" to wipe both your blobs and re-populate the database schema)", version, requiredSchemaVersion) } return nil, fmt.Errorf("database schema version is %d; expect %d (need to re-init/upgrade database?)", version, requiredSchemaVersion) } ix := index.New(is) ix.BlobSource = sto // Good enough, for now: ix.KeyFetcher = ix.BlobSource return ix, nil }
// ConfigFromJSON populates Config from cfg, and validates // cfg. It returns an error if cfg fails to validate. func configFromJSON(cfg jsonconfig.Obj) (config, error) { conf := config{ server: cfg.OptionalString("host", "localhost"), database: cfg.RequiredString("database"), collection: cfg.OptionalString("collection", "blobs"), user: cfg.OptionalString("user", ""), password: cfg.OptionalString("password", ""), } if err := cfg.Validate(); err != nil { return config{}, err } return conf, nil }
func newHandlerFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (http.Handler, error) { indexPrefix := conf.RequiredString("index") // TODO: add optional help tips here? ownerBlobStr := conf.RequiredString("owner") devBlockStartupPrefix := conf.OptionalString("devBlockStartupOn", "") slurpToMemory := conf.OptionalBool("slurpToMemory", false) if err := conf.Validate(); err != nil { return nil, err } if devBlockStartupPrefix != "" { _, err := ld.GetHandler(devBlockStartupPrefix) if err != nil { return nil, fmt.Errorf("search handler references bogus devBlockStartupOn handler %s: %v", devBlockStartupPrefix, err) } } indexHandler, err := ld.GetHandler(indexPrefix) if err != nil { return nil, fmt.Errorf("search config references unknown handler %q", indexPrefix) } indexer, ok := indexHandler.(index.Interface) if !ok { return nil, fmt.Errorf("search config references invalid indexer %q (actually a %T)", indexPrefix, indexHandler) } ownerBlobRef, ok := blob.Parse(ownerBlobStr) if !ok { return nil, fmt.Errorf("search 'owner' has malformed blobref %q; expecting e.g. sha1-xxxxxxxxxxxx", ownerBlobStr) } h := NewHandler(indexer, ownerBlobRef) if slurpToMemory { ii := indexer.(*index.Index) corpus, err := ii.KeepInMemory() if err != nil { return nil, fmt.Errorf("error slurping index to memory: %v", err) } h.corpus = corpus } return h, nil }
func newRootFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) { u, err := user.Current() if err != nil { return } root := &RootHandler{ BlobRoot: conf.OptionalString("blobRoot", ""), SearchRoot: conf.OptionalString("searchRoot", ""), OwnerName: conf.OptionalString("ownerName", u.Name), } root.Stealth = conf.OptionalBool("stealth", false) if err = conf.Validate(); err != nil { return } if root.BlobRoot != "" { bs, err := ld.GetStorage(root.BlobRoot) if err != nil { return nil, fmt.Errorf("Root handler's blobRoot of %q error: %v", root.BlobRoot, err) } root.Storage = bs } if root.SearchRoot != "" { h, _ := ld.GetHandler(root.SearchRoot) root.Search = h.(*search.Handler) } return root, nil }
func newMongoIndexFromConfig(ld blobserver.Loader, config jsonconfig.Obj) (blobserver.Storage, error) { blobPrefix := config.RequiredString("blobSource") mgw := &MongoWrapper{ Servers: config.OptionalString("host", "localhost"), Database: config.RequiredString("database"), User: config.OptionalString("user", ""), Password: config.OptionalString("password", ""), Collection: collectionName, } if err := config.Validate(); err != nil { return nil, err } sto, err := ld.GetStorage(blobPrefix) if err != nil { return nil, err } ix, err := newMongoIndex(mgw) if err != nil { return nil, err } ix.BlobSource = sto // Good enough, for now: ix.KeyFetcher = ix.BlobSource if wipe, _ := strconv.ParseBool(os.Getenv("CAMLI_MONGO_WIPE")); wipe { err = ix.Storage().Delete("") if err != nil { return nil, err } } return ix, err }
func newFromConfig(ld blobserver.Loader, config jsonconfig.Obj) (bs blobserver.Storage, err error) { sto := &storage{ SimpleBlobHubPartitionMap: &blobserver.SimpleBlobHubPartitionMap{}, index: index.NewMemoryStorage(), // TODO: temporary for development; let be configurable (mysql, etc) } agreement := config.OptionalString("I_AGREE", "") const wantAgreement = "that encryption support hasn't been peer-reviewed, isn't finished, and its format might change." if agreement != wantAgreement { return nil, errors.New("Use of the 'encrypt' target without the proper I_AGREE value.") } key := config.OptionalString("key", "") keyFile := config.OptionalString("keyFile", "") var keyb []byte switch { case key != "": keyb, err = hex.DecodeString(key) if err != nil || len(keyb) != 16 { return nil, fmt.Errorf("The 'key' parameter must be 16 bytes of 32 hex digits. (currently fixed at AES-128)") } case keyFile != "": // TODO: check that keyFile's unix permissions aren't too permissive. keyb, err = ioutil.ReadFile(keyFile) if err != nil { return nil, fmt.Errorf("Reading key file %v: %v", keyFile, err) } } blobStorage := config.RequiredString("blobs") metaStorage := config.RequiredString("meta") if err := config.Validate(); err != nil { return nil, err } sto.blobs, err = ld.GetStorage(blobStorage) if err != nil { return } sto.meta, err = ld.GetStorage(metaStorage) if err != nil { return } if keyb == nil { // TODO: add a way to prompt from stdin on start? or keychain support? return nil, errors.New("no encryption key set with 'key' or 'keyFile'") } if err := sto.setKey(keyb); err != nil { return nil, err } log.Printf("Reading encryption metadata...") if err := sto.readAllMetaBlobs(); err != nil { return nil, fmt.Errorf("Error scanning metadata on start-up: %v", err) } log.Printf("Read all encryption metadata.") return sto, nil }
func newKeyValueFromJSONConfig(cfg jsonconfig.Obj) (sorted.KeyValue, error) { conninfo := fmt.Sprintf("user=%s dbname=%s host=%s password=%s sslmode=%s", cfg.RequiredString("user"), cfg.RequiredString("database"), cfg.OptionalString("host", "localhost"), cfg.OptionalString("password", ""), cfg.OptionalString("sslmode", "require"), ) if err := cfg.Validate(); err != nil { return nil, err } db, err := sql.Open("postgres", conninfo) if err != nil { return nil, err } for _, tableSql := range SQLCreateTables() { if _, err := db.Exec(tableSql); err != nil { return nil, fmt.Errorf("error creating table with %q: %v", tableSql, err) } } for _, statement := range SQLDefineReplace() { if _, err := db.Exec(statement); err != nil { return nil, fmt.Errorf("error setting up replace statement with %q: %v", statement, err) } } r, err := db.Query(fmt.Sprintf(`SELECT replaceintometa('version', '%d')`, SchemaVersion())) if err != nil { return nil, fmt.Errorf("error setting schema version: %v", err) } r.Close() kv := &keyValue{ db: db, KeyValue: &sqlkv.KeyValue{ DB: db, SetFunc: altSet, BatchSetFunc: altBatchSet, PlaceHolderFunc: replacePlaceHolders, }, } if err := kv.ping(); err != nil { return nil, fmt.Errorf("PostgreSQL db unreachable: %v", err) } version, err := kv.SchemaVersion() if err != nil { return nil, fmt.Errorf("error getting schema version (need to init database?): %v", err) } if version != requiredSchemaVersion { if env.IsDev() { // Good signal that we're using the devcam server, so help out // the user with a more useful tip: return nil, fmt.Errorf("database schema version is %d; expect %d (run \"devcam server --wipe\" to wipe both your blobs and re-populate the database schema)", version, requiredSchemaVersion) } return nil, fmt.Errorf("database schema version is %d; expect %d (need to re-init/upgrade database?)", version, requiredSchemaVersion) } return kv, nil }
func newRootFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) { username, _ := getUserName() root := &RootHandler{ BlobRoot: conf.OptionalString("blobRoot", ""), SearchRoot: conf.OptionalString("searchRoot", ""), OwnerName: conf.OptionalString("ownerName", username), Username: osutil.Username(), Prefix: ld.MyPrefix(), } root.Stealth = conf.OptionalBool("stealth", false) root.statusRoot = conf.OptionalString("statusRoot", "") if err = conf.Validate(); err != nil { return } if root.BlobRoot != "" { bs, err := ld.GetStorage(root.BlobRoot) if err != nil { return nil, fmt.Errorf("Root handler's blobRoot of %q error: %v", root.BlobRoot, err) } root.Storage = bs } root.searchInit = func() {} if root.SearchRoot != "" { prefix := root.SearchRoot if t := ld.GetHandlerType(prefix); t != "search" { if t == "" { return nil, fmt.Errorf("root handler's searchRoot of %q is invalid and doesn't refer to a declared handler", prefix) } return nil, fmt.Errorf("root handler's searchRoot of %q is of type %q, not %q", prefix, t, "search") } root.searchInit = func() { h, err := ld.GetHandler(prefix) if err != nil { log.Fatalf("Error fetching SearchRoot at %q: %v", prefix, err) } root.searchHandler = h.(*search.Handler) root.searchInit = nil } } if pfx, _, _ := ld.FindHandlerByType("importer"); err == nil { root.importerRoot = pfx } return root, nil }
func newKeyValueFromJSONConfig(cfg jsonconfig.Obj) (sorted.KeyValue, error) { ins := &instance{ server: cfg.OptionalString("host", "localhost"), database: cfg.RequiredString("database"), user: cfg.OptionalString("user", ""), password: cfg.OptionalString("password", ""), } if err := cfg.Validate(); err != nil { return nil, err } db, err := ins.getCollection() if err != nil { return nil, err } return &keyValue{db: db, session: ins.session}, nil }
func newPublishFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) { ph := &PublishHandler{ bsLoader: ld, } ph.RootName = conf.RequiredString("rootName") ph.JSFiles = conf.OptionalList("js") ph.CSSFiles = conf.OptionalList("css") blobRoot := conf.RequiredString("blobRoot") searchRoot := conf.RequiredString("searchRoot") cachePrefix := conf.OptionalString("cache", "") scType := conf.OptionalString("scaledImage", "") bootstrapSignRoot := conf.OptionalString("devBootstrapPermanodeUsing", "") rootNode := conf.OptionalList("rootPermanode") if err = conf.Validate(); err != nil { return } if ph.RootName == "" { return nil, errors.New("invalid empty rootName") } bs, err := ld.GetStorage(blobRoot) if err != nil { return nil, fmt.Errorf("publish handler's blobRoot of %q error: %v", blobRoot, err) } ph.Storage = bs si, err := ld.GetHandler(searchRoot) if err != nil { return nil, fmt.Errorf("publish handler's searchRoot of %q error: %v", searchRoot, err) } var ok bool ph.Search, ok = si.(*search.Handler) if !ok { return nil, fmt.Errorf("publish handler's searchRoot of %q is of type %T, expecting a search handler", searchRoot, si) } if rootNode != nil { if len(rootNode) != 2 { return nil, fmt.Errorf("rootPermanode config must contain the jsonSignerHandler and the permanode hash") } if t := ld.GetHandlerType(rootNode[0]); t != "jsonsign" { return nil, fmt.Errorf("publish handler's rootPermanode first value not a jsonsign") } h, _ := ld.GetHandler(rootNode[0]) jsonSign := h.(*JSONSignHandler) pn := blobref.Parse(rootNode[1]) if err := ph.setRootNode(jsonSign, pn); err != nil { return nil, fmt.Errorf("error setting publish root permanode: %v", err) } } else { if bootstrapSignRoot != "" { if t := ld.GetHandlerType(bootstrapSignRoot); t != "jsonsign" { return nil, fmt.Errorf("publish handler's devBootstrapPermanodeUsing must be of type jsonsign") } h, _ := ld.GetHandler(bootstrapSignRoot) jsonSign := h.(*JSONSignHandler) if err := ph.bootstrapPermanode(jsonSign); err != nil { return nil, fmt.Errorf("error bootstrapping permanode: %v", err) } } } if cachePrefix != "" { bs, err := ld.GetStorage(cachePrefix) if err != nil { return nil, fmt.Errorf("publish handler's cache of %q error: %v", cachePrefix, err) } ph.Cache = bs switch scType { case "lrucache": ph.sc = NewScaledImageLru() case "": default: return nil, fmt.Errorf("unsupported publish handler's scType: %q ", scType) } } ph.staticHandler = http.FileServer(uiFiles) return ph, nil }
func newPublishFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) { ph := &PublishHandler{ handlerFinder: ld, } ph.RootName = conf.RequiredString("rootName") ph.JSFiles = conf.OptionalList("js") ph.CSSFiles = conf.OptionalList("css") blobRoot := conf.RequiredString("blobRoot") searchRoot := conf.RequiredString("searchRoot") cachePrefix := conf.OptionalString("cache", "") scType := conf.OptionalString("scaledImage", "") bootstrapSignRoot := conf.OptionalString("devBootstrapPermanodeUsing", "") rootNode := conf.OptionalList("rootPermanode") ph.sourceRoot = conf.OptionalString("sourceRoot", "") if err = conf.Validate(); err != nil { return } if ph.RootName == "" { return nil, errors.New("invalid empty rootName") } bs, err := ld.GetStorage(blobRoot) if err != nil { return nil, fmt.Errorf("publish handler's blobRoot of %q error: %v", blobRoot, err) } ph.Storage = bs si, err := ld.GetHandler(searchRoot) if err != nil { return nil, fmt.Errorf("publish handler's searchRoot of %q error: %v", searchRoot, err) } var ok bool ph.Search, ok = si.(*search.Handler) if !ok { return nil, fmt.Errorf("publish handler's searchRoot of %q is of type %T, expecting a search handler", searchRoot, si) } if rootNode != nil { if len(rootNode) != 2 { return nil, fmt.Errorf("rootPermanode config must contain the jsonSignerHandler and the permanode hash") } if t := ld.GetHandlerType(rootNode[0]); t != "jsonsign" { return nil, fmt.Errorf("publish handler's rootPermanode first value not a jsonsign") } h, _ := ld.GetHandler(rootNode[0]) jsonSign := h.(*signhandler.Handler) pn, ok := blob.Parse(rootNode[1]) if !ok { return nil, fmt.Errorf("Invalid \"rootPermanode\" value; was expecting a blobRef, got %q.", rootNode[1]) } if err := ph.setRootNode(jsonSign, pn); err != nil { return nil, fmt.Errorf("error setting publish root permanode: %v", err) } } else { if bootstrapSignRoot != "" { if t := ld.GetHandlerType(bootstrapSignRoot); t != "jsonsign" { return nil, fmt.Errorf("publish handler's devBootstrapPermanodeUsing must be of type jsonsign") } h, _ := ld.GetHandler(bootstrapSignRoot) jsonSign := h.(*signhandler.Handler) if err := ph.bootstrapPermanode(jsonSign); err != nil { return nil, fmt.Errorf("error bootstrapping permanode: %v", err) } } } if cachePrefix != "" { bs, err := ld.GetStorage(cachePrefix) if err != nil { return nil, fmt.Errorf("publish handler's cache of %q error: %v", cachePrefix, err) } ph.Cache = bs switch scType { case "lrucache": ph.sc = NewScaledImageLRU() case "": default: return nil, fmt.Errorf("unsupported publish handler's scType: %q ", scType) } } // TODO(mpl): check that it works on appengine too. if ph.sourceRoot == "" { ph.sourceRoot = os.Getenv("CAMLI_DEV_CAMLI_ROOT") } if ph.sourceRoot != "" { ph.uiDir = filepath.Join(ph.sourceRoot, filepath.FromSlash("server/camlistored/ui")) // Ignore any fileembed files: Files = &fileembed.Files{ DirFallback: filepath.Join(ph.sourceRoot, filepath.FromSlash("pkg/server")), } uistatic.Files = &fileembed.Files{ DirFallback: ph.uiDir, } } ph.closureHandler, err = ph.makeClosureHandler(ph.sourceRoot) if err != nil { return nil, fmt.Errorf(`Invalid "sourceRoot" value of %q: %v"`, ph.sourceRoot, err) } return ph, nil }
func newFromConfig(_ blobserver.Loader, config jsonconfig.Obj) (blobserver.Storage, error) { hostname := config.OptionalString("hostname", "s3.amazonaws.com") cacheSize := config.OptionalInt64("cacheSize", 32<<20) client := &s3.Client{ Auth: &s3.Auth{ AccessKey: config.RequiredString("aws_access_key"), SecretAccessKey: config.RequiredString("aws_secret_access_key"), Hostname: hostname, }, } sto := &s3Storage{ s3Client: client, bucket: config.RequiredString("bucket"), hostname: hostname, } skipStartupCheck := config.OptionalBool("skipStartupCheck", false) if err := config.Validate(); err != nil { return nil, err } if cacheSize != 0 { sto.cache = memory.NewCache(cacheSize) } if !skipStartupCheck { _, err := client.ListBucket(sto.bucket, "", 1) if serr, ok := err.(*s3.Error); ok { if serr.AmazonCode == "NoSuchBucket" { return nil, fmt.Errorf("Bucket %q doesn't exist.", sto.bucket) } // This code appears when the hostname has dots in it: if serr.AmazonCode == "PermanentRedirect" { loc, lerr := client.BucketLocation(sto.bucket) if lerr != nil { return nil, fmt.Errorf("Wrong server for bucket %q; and error determining bucket's location: %v", sto.bucket, lerr) } client.Auth.Hostname = loc _, err = client.ListBucket(sto.bucket, "", 1) if err == nil { log.Printf("Warning: s3 server should be %q, not %q. Change config file to avoid start-up latency.", client.Auth.Hostname, hostname) } } // This path occurs when the user set the // wrong server, or didn't set one at all, but // the bucket doesn't have dots in it: if serr.UseEndpoint != "" { // UseEndpoint will be e.g. "brads3test-ca.s3-us-west-1.amazonaws.com" // But we only want the "s3-us-west-1.amazonaws.com" part. client.Auth.Hostname = strings.TrimPrefix(serr.UseEndpoint, sto.bucket+".") _, err = client.ListBucket(sto.bucket, "", 1) if err == nil { log.Printf("Warning: s3 server should be %q, not %q. Change config file to avoid start-up latency.", client.Auth.Hostname, hostname) } } } if err != nil { return nil, fmt.Errorf("Error listing bucket %s: %v", sto.bucket, err) } } return sto, nil }
func uiFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) { ui := &UIHandler{ prefix: ld.MyPrefix(), JSONSignRoot: conf.OptionalString("jsonSignRoot", ""), sourceRoot: conf.OptionalString("sourceRoot", ""), } pubRoots := conf.OptionalList("publishRoots") cachePrefix := conf.OptionalString("cache", "") scType := conf.OptionalString("scaledImage", "") if err = conf.Validate(); err != nil { return } if ui.JSONSignRoot != "" { h, _ := ld.GetHandler(ui.JSONSignRoot) if sigh, ok := h.(*signhandler.Handler); ok { ui.sigh = sigh } } ui.PublishRoots = make(map[string]*PublishHandler) for _, pubRoot := range pubRoots { h, err := ld.GetHandler(pubRoot) if err != nil { return nil, fmt.Errorf("UI handler's publishRoots references invalid %q", pubRoot) } pubh, ok := h.(*PublishHandler) if !ok { return nil, fmt.Errorf("UI handler's publishRoots references invalid %q; not a PublishHandler", pubRoot) } ui.PublishRoots[pubRoot] = pubh } checkType := func(key string, htype string) { v := conf.OptionalString(key, "") if v == "" { return } ct := ld.GetHandlerType(v) if ct == "" { err = fmt.Errorf("UI handler's %q references non-existant %q", key, v) } else if ct != htype { err = fmt.Errorf("UI handler's %q references %q of type %q; expected type %q", key, v, ct, htype) } } checkType("searchRoot", "search") checkType("jsonSignRoot", "jsonsign") if err != nil { return } if cachePrefix != "" { bs, err := ld.GetStorage(cachePrefix) if err != nil { return nil, fmt.Errorf("UI handler's cache of %q error: %v", cachePrefix, err) } ui.Cache = bs switch scType { case "lrucache": ui.sc = NewScaledImageLRU() default: return nil, fmt.Errorf("unsupported ui handler's scType: %q ", scType) } } if ui.sourceRoot == "" { ui.sourceRoot = os.Getenv("CAMLI_DEV_CAMLI_ROOT") if uistatic.IsAppEngine { if _, err = os.Stat(filepath.Join(uistatic.GaeSourceRoot, filepath.FromSlash("server/camlistored/ui/index.html"))); err != nil { hint := fmt.Sprintf("\"sourceRoot\" was not specified in the config,"+ " and the default sourceRoot dir %v does not exist or does not contain"+ " \"server/camlistored/ui/index.html\". devcam appengine can do that for you.", uistatic.GaeSourceRoot) log.Print(hint) return nil, errors.New("No sourceRoot found; UI not available.") } log.Printf("Using the default \"%v\" as the sourceRoot for AppEngine", uistatic.GaeSourceRoot) ui.sourceRoot = uistatic.GaeSourceRoot } } if ui.sourceRoot != "" { ui.uiDir = filepath.Join(ui.sourceRoot, filepath.FromSlash("server/camlistored/ui")) // Ignore any fileembed files: Files = &fileembed.Files{ DirFallback: filepath.Join(ui.sourceRoot, filepath.FromSlash("pkg/server")), } uistatic.Files = &fileembed.Files{ DirFallback: ui.uiDir, Listable: true, // In dev_appserver, allow edit-and-reload without // restarting. In production, though, it's faster to just // slurp it in. SlurpToMemory: uistatic.IsProdAppEngine, } } ui.closureHandler, err = ui.makeClosureHandler(ui.sourceRoot) if err != nil { return nil, fmt.Errorf(`Invalid "sourceRoot" value of %q: %v"`, ui.sourceRoot, err) } rootPrefix, _, err := ld.FindHandlerByType("root") if err != nil { return nil, errors.New("No root handler configured, which is necessary for the ui handler") } if h, err := ld.GetHandler(rootPrefix); err == nil { ui.root = h.(*RootHandler) ui.root.registerUIHandler(ui) } else { return nil, errors.New("failed to find the 'root' handler") } return ui, nil }