github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/serverconfig/genconfig.go (about) 1 /* 2 Copyright 2012 Google Inc. 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 package serverconfig 18 19 import ( 20 "errors" 21 "fmt" 22 "net/url" 23 "os" 24 "path/filepath" 25 "strings" 26 27 "camlistore.org/pkg/blob" 28 "camlistore.org/pkg/jsonconfig" 29 "camlistore.org/pkg/jsonsign" 30 "camlistore.org/pkg/osutil" 31 ) 32 33 // various parameters derived from the high-level user config 34 // and needed to set up the low-level config. 35 type configPrefixesParams struct { 36 secretRing string 37 keyId string 38 indexerPath string 39 blobPath string 40 packBlobs bool 41 searchOwner blob.Ref 42 shareHandlerPath string 43 flickr string 44 memoryIndex bool 45 } 46 47 var ( 48 tempDir = os.TempDir 49 noMkdir bool // for tests to not call os.Mkdir 50 ) 51 52 func addPublishedConfig(prefixes jsonconfig.Obj, 53 published jsonconfig.Obj, 54 sourceRoot string) ([]interface{}, error) { 55 pubPrefixes := []interface{}{} 56 for k, v := range published { 57 p, ok := v.(map[string]interface{}) 58 if !ok { 59 return nil, fmt.Errorf("Wrong type for %s; was expecting map[string]interface{}, got %T", k, v) 60 } 61 rootName := strings.Replace(k, "/", "", -1) + "Root" 62 rootPermanode, goTemplate, style, js := "", "", "", "" 63 for pk, pv := range p { 64 val, ok := pv.(string) 65 if !ok { 66 return nil, fmt.Errorf("Was expecting type string for %s, got %T", pk, pv) 67 } 68 switch pk { 69 case "rootPermanode": 70 rootPermanode = val 71 case "goTemplate": 72 goTemplate = val 73 case "style": 74 style = val 75 case "js": 76 js = val 77 default: 78 return nil, fmt.Errorf("Unexpected key %q in config for %s", pk, k) 79 } 80 } 81 if rootPermanode == "" || goTemplate == "" { 82 return nil, fmt.Errorf("Missing key in configuration for %s, need \"rootPermanode\" and \"goTemplate\"", k) 83 } 84 ob := map[string]interface{}{} 85 ob["handler"] = "publish" 86 handlerArgs := map[string]interface{}{ 87 "rootName": rootName, 88 "blobRoot": "/bs-and-maybe-also-index/", 89 "searchRoot": "/my-search/", 90 "cache": "/cache/", 91 "rootPermanode": []interface{}{"/sighelper/", rootPermanode}, 92 } 93 if sourceRoot != "" { 94 handlerArgs["sourceRoot"] = sourceRoot 95 } 96 handlerArgs["goTemplate"] = goTemplate 97 if style != "" { 98 handlerArgs["css"] = []interface{}{style} 99 } 100 if js != "" { 101 handlerArgs["js"] = []interface{}{js} 102 } 103 // TODO(mpl): we'll probably want to use osutil.CacheDir() if thumbnails.kv 104 // contains private info? same for some of the other "camli-cache" ones? 105 thumbsCacheDir := filepath.Join(tempDir(), "camli-cache") 106 handlerArgs["scaledImage"] = map[string]interface{}{ 107 "type": "kv", 108 "file": filepath.Join(thumbsCacheDir, "thumbnails.kv"), 109 } 110 if err := os.MkdirAll(thumbsCacheDir, 0700); err != nil { 111 return nil, fmt.Errorf("Could not create cache dir %s: %v", thumbsCacheDir, err) 112 } 113 ob["handlerArgs"] = handlerArgs 114 prefixes[k] = ob 115 pubPrefixes = append(pubPrefixes, k) 116 } 117 return pubPrefixes, nil 118 } 119 120 func addUIConfig(params *configPrefixesParams, 121 prefixes jsonconfig.Obj, 122 uiPrefix string, 123 published []interface{}, 124 sourceRoot string) { 125 126 args := map[string]interface{}{ 127 "jsonSignRoot": "/sighelper/", 128 "cache": "/cache/", 129 } 130 if len(published) > 0 { 131 args["publishRoots"] = published 132 } 133 if sourceRoot != "" { 134 args["sourceRoot"] = sourceRoot 135 } 136 if params.blobPath != "" { 137 args["scaledImage"] = map[string]interface{}{ 138 "type": "kv", 139 "file": filepath.Join(params.blobPath, "thumbmeta.kv"), 140 } 141 } 142 prefixes[uiPrefix] = map[string]interface{}{ 143 "handler": "ui", 144 "handlerArgs": args, 145 } 146 } 147 148 func addMongoConfig(prefixes jsonconfig.Obj, dbname string, dbinfo string) { 149 fields := strings.Split(dbinfo, "@") 150 if len(fields) != 2 { 151 exitFailure("Malformed mongo config string. Got \"%v\", want: \"user:password@host\"", dbinfo) 152 } 153 host := fields[1] 154 fields = strings.Split(fields[0], ":") 155 if len(fields) != 2 { 156 exitFailure("Malformed mongo config string. Got \"%v\", want: \"user:password\"", fields[0]) 157 } 158 ob := map[string]interface{}{} 159 ob["enabled"] = true 160 ob["handler"] = "storage-mongodbindexer" 161 ob["handlerArgs"] = map[string]interface{}{ 162 "host": host, 163 "user": fields[0], 164 "password": fields[1], 165 "database": dbname, 166 "blobSource": "/bs/", 167 } 168 prefixes["/index-mongo/"] = ob 169 } 170 171 func addSQLConfig(rdbms string, prefixes jsonconfig.Obj, dbname string, dbinfo string) { 172 fields := strings.Split(dbinfo, "@") 173 if len(fields) != 2 { 174 exitFailure("Malformed " + rdbms + " config string. Want: \"user@host:password\"") 175 } 176 user := fields[0] 177 fields = strings.Split(fields[1], ":") 178 if len(fields) != 2 { 179 exitFailure("Malformed " + rdbms + " config string. Want: \"user@host:password\"") 180 } 181 ob := map[string]interface{}{} 182 ob["enabled"] = true 183 ob["handler"] = "storage-" + rdbms + "indexer" 184 ob["handlerArgs"] = map[string]interface{}{ 185 "host": fields[0], 186 "user": user, 187 "password": fields[1], 188 "database": dbname, 189 "blobSource": "/bs/", 190 } 191 prefixes["/index-"+rdbms+"/"] = ob 192 } 193 194 func addPostgresConfig(prefixes jsonconfig.Obj, dbname string, dbinfo string) { 195 addSQLConfig("postgres", prefixes, dbname, dbinfo) 196 } 197 198 func addMySQLConfig(prefixes jsonconfig.Obj, dbname string, dbinfo string) { 199 addSQLConfig("mysql", prefixes, dbname, dbinfo) 200 } 201 202 func addSQLiteConfig(prefixes jsonconfig.Obj, file string) { 203 ob := map[string]interface{}{} 204 ob["handler"] = "storage-sqliteindexer" 205 ob["handlerArgs"] = map[string]interface{}{ 206 "blobSource": "/bs/", 207 "file": file, 208 } 209 prefixes["/index-sqlite/"] = ob 210 } 211 212 func addKVConfig(prefixes jsonconfig.Obj, file string) { 213 prefixes["/index-kv/"] = map[string]interface{}{ 214 "handler": "storage-kvfileindexer", 215 "handlerArgs": map[string]interface{}{ 216 "blobSource": "/bs/", 217 "file": file, 218 }, 219 } 220 } 221 222 func addS3Config(params *configPrefixesParams, prefixes jsonconfig.Obj, s3 string) error { 223 f := strings.SplitN(s3, ":", 4) 224 if len(f) < 3 { 225 return errors.New(`genconfig: expected "s3" field to be of form "access_key_id:secret_access_key:bucket"`) 226 } 227 accessKey, secret, bucket := f[0], f[1], f[2] 228 var hostname string 229 if len(f) == 4 { 230 hostname = f[3] 231 } 232 isPrimary := false 233 if _, ok := prefixes["/bs/"]; !ok { 234 isPrimary = true 235 } 236 s3Prefix := "" 237 if isPrimary { 238 s3Prefix = "/bs/" 239 } else { 240 s3Prefix = "/sto-s3/" 241 } 242 args := map[string]interface{}{ 243 "aws_access_key": accessKey, 244 "aws_secret_access_key": secret, 245 "bucket": bucket, 246 } 247 if hostname != "" { 248 args["hostname"] = hostname 249 } 250 prefixes[s3Prefix] = map[string]interface{}{ 251 "handler": "storage-s3", 252 "handlerArgs": args, 253 } 254 if isPrimary { 255 // TODO(mpl): s3CacheBucket 256 // See http://code.google.com/p/camlistore/issues/detail?id=85 257 prefixes["/cache/"] = map[string]interface{}{ 258 "handler": "storage-filesystem", 259 "handlerArgs": map[string]interface{}{ 260 "path": filepath.Join(tempDir(), "camli-cache"), 261 }, 262 } 263 } else { 264 if params.blobPath == "" { 265 panic("unexpected empty blobpath with sync-to-s3") 266 } 267 prefixes["/sync-to-s3/"] = map[string]interface{}{ 268 "handler": "sync", 269 "handlerArgs": map[string]interface{}{ 270 "from": "/bs/", 271 "to": s3Prefix, 272 "queue": map[string]interface{}{ 273 "type": "kv", 274 "file": filepath.Join(params.blobPath, "sync-to-s3-queue.kv"), 275 }, 276 }, 277 } 278 } 279 return nil 280 } 281 282 func addGoogleDriveConfig(prefixes jsonconfig.Obj, highCfg string) error { 283 f := strings.SplitN(highCfg, ":", 4) 284 if len(f) != 4 { 285 return errors.New(`genconfig: expected "googledrive" field to be of form "client_id:client_secret:refresh_token:parent_id"`) 286 } 287 clientId, secret, refreshToken, parentId := f[0], f[1], f[2], f[3] 288 289 isPrimary := false 290 if _, ok := prefixes["/bs/"]; !ok { 291 isPrimary = true 292 } 293 294 prefix := "" 295 if isPrimary { 296 prefix = "/bs/" 297 } else { 298 prefix = "/sto-googledrive/" 299 } 300 prefixes[prefix] = map[string]interface{}{ 301 "handler": "storage-googledrive", 302 "handlerArgs": map[string]interface{}{ 303 "parent_id": parentId, 304 "auth": map[string]interface{}{ 305 "client_id": clientId, 306 "client_secret": secret, 307 "refresh_token": refreshToken, 308 }, 309 }, 310 } 311 312 if isPrimary { 313 prefixes["/cache/"] = map[string]interface{}{ 314 "handler": "storage-filesystem", 315 "handlerArgs": map[string]interface{}{ 316 "path": filepath.Join(tempDir(), "camli-cache"), 317 }, 318 } 319 } else { 320 prefixes["/sync-to-googledrive/"] = map[string]interface{}{ 321 "handler": "sync", 322 "handlerArgs": map[string]interface{}{ 323 "from": "/bs/", 324 "to": prefix, 325 }, 326 } 327 } 328 329 return nil 330 } 331 332 func addGoogleCloudStorageConfig(prefixes jsonconfig.Obj, highCfg string) error { 333 f := strings.SplitN(highCfg, ":", 4) 334 if len(f) != 4 { 335 return errors.New(`genconfig: expected "googlecloudstorage" field to be of form "client_id:client_secret:refresh_token:bucket"`) 336 } 337 clientId, secret, refreshToken, bucket := f[0], f[1], f[2], f[3] 338 339 isPrimary := false 340 if _, ok := prefixes["/bs/"]; !ok { 341 isPrimary = true 342 } 343 344 gsPrefix := "" 345 if isPrimary { 346 gsPrefix = "/bs/" 347 } else { 348 gsPrefix = "/sto-googlecloudstorage/" 349 } 350 351 prefixes[gsPrefix] = map[string]interface{}{ 352 "handler": "storage-googlecloudstorage", 353 "handlerArgs": map[string]interface{}{ 354 "bucket": bucket, 355 "auth": map[string]interface{}{ 356 "client_id": clientId, 357 "client_secret": secret, 358 "refresh_token": refreshToken, 359 // If high-level config is for the common user then fullSyncOnStart = true 360 // Then the default just works. 361 //"fullSyncOnStart": true, 362 //"blockingFullSyncOnStart": false 363 }, 364 }, 365 } 366 367 if isPrimary { 368 // TODO: cacheBucket like s3CacheBucket? 369 prefixes["/cache/"] = map[string]interface{}{ 370 "handler": "storage-filesystem", 371 "handlerArgs": map[string]interface{}{ 372 "path": filepath.Join(tempDir(), "camli-cache"), 373 }, 374 } 375 } else { 376 prefixes["/sync-to-googlecloudstorage/"] = map[string]interface{}{ 377 "handler": "sync", 378 "handlerArgs": map[string]interface{}{ 379 "from": "/bs/", 380 "to": gsPrefix, 381 }, 382 } 383 } 384 return nil 385 } 386 387 func genLowLevelPrefixes(params *configPrefixesParams, ownerName string) (m jsonconfig.Obj) { 388 m = make(jsonconfig.Obj) 389 390 haveIndex := params.indexerPath != "" 391 root := "/bs/" 392 pubKeyDest := root 393 if haveIndex { 394 root = "/bs-and-maybe-also-index/" 395 pubKeyDest = "/bs-and-index/" 396 } 397 398 rootArgs := map[string]interface{}{ 399 "stealth": false, 400 "blobRoot": root, 401 "statusRoot": "/status/", 402 } 403 if ownerName != "" { 404 rootArgs["ownerName"] = ownerName 405 } 406 m["/"] = map[string]interface{}{ 407 "handler": "root", 408 "handlerArgs": rootArgs, 409 } 410 if haveIndex { 411 setMap(m, "/", "handlerArgs", "searchRoot", "/my-search/") 412 } 413 414 m["/setup/"] = map[string]interface{}{ 415 "handler": "setup", 416 } 417 418 m["/status/"] = map[string]interface{}{ 419 "handler": "status", 420 } 421 422 if params.shareHandlerPath != "" { 423 m[params.shareHandlerPath] = map[string]interface{}{ 424 "handler": "share", 425 "handlerArgs": map[string]interface{}{ 426 "blobRoot": "/bs/", 427 }, 428 } 429 } 430 431 m["/sighelper/"] = map[string]interface{}{ 432 "handler": "jsonsign", 433 "handlerArgs": map[string]interface{}{ 434 "secretRing": params.secretRing, 435 "keyId": params.keyId, 436 "publicKeyDest": pubKeyDest, 437 }, 438 } 439 440 storageType := "filesystem" 441 if params.packBlobs { 442 storageType = "diskpacked" 443 } 444 if params.blobPath != "" { 445 m["/bs/"] = map[string]interface{}{ 446 "handler": "storage-" + storageType, 447 "handlerArgs": map[string]interface{}{ 448 "path": params.blobPath, 449 }, 450 } 451 452 m["/cache/"] = map[string]interface{}{ 453 "handler": "storage-" + storageType, 454 "handlerArgs": map[string]interface{}{ 455 "path": filepath.Join(params.blobPath, "/cache"), 456 }, 457 } 458 } 459 460 if params.flickr != "" { 461 m["/importer-flickr/"] = map[string]interface{}{ 462 "handler": "importer-flickr", 463 "handlerArgs": map[string]interface{}{ 464 "apiKey": params.flickr, 465 }, 466 } 467 } 468 469 if haveIndex { 470 syncArgs := map[string]interface{}{ 471 "from": "/bs/", 472 "to": params.indexerPath, 473 } 474 // TODO(mpl): Brad says the cond should be dest == /index-*. 475 // But what about when dest is index-mem and we have a local disk; 476 // don't we want to have an active synchandler to do the fullSyncOnStart? 477 // Anyway, that condition works for now. 478 if params.blobPath == "" { 479 // When our primary blob store is remote (s3 or google cloud), 480 // i.e not an efficient replication source, we do not want the 481 // synchandler to mirror to the indexer. But we still want a 482 // synchandler to provide the discovery for e.g tools like 483 // camtool sync. See http://camlistore.org/issue/201 484 syncArgs["idle"] = true 485 } else { 486 syncArgs["queue"] = map[string]interface{}{ 487 "type": "kv", 488 "file": filepath.Join(params.blobPath, "sync-to-index-queue.kv"), 489 } 490 } 491 m["/sync/"] = map[string]interface{}{ 492 "handler": "sync", 493 "handlerArgs": syncArgs, 494 } 495 496 m["/bs-and-index/"] = map[string]interface{}{ 497 "handler": "storage-replica", 498 "handlerArgs": map[string]interface{}{ 499 "backends": []interface{}{"/bs/", params.indexerPath}, 500 }, 501 } 502 503 m["/bs-and-maybe-also-index/"] = map[string]interface{}{ 504 "handler": "storage-cond", 505 "handlerArgs": map[string]interface{}{ 506 "write": map[string]interface{}{ 507 "if": "isSchema", 508 "then": "/bs-and-index/", 509 "else": "/bs/", 510 }, 511 "read": "/bs/", 512 }, 513 } 514 515 searchArgs := map[string]interface{}{ 516 "index": params.indexerPath, 517 "owner": params.searchOwner.String(), 518 } 519 if params.memoryIndex { 520 searchArgs["slurpToMemory"] = true 521 } 522 m["/my-search/"] = map[string]interface{}{ 523 "handler": "search", 524 "handlerArgs": searchArgs, 525 } 526 } 527 528 return 529 } 530 531 // genLowLevelConfig returns a low-level config from a high-level config. 532 func genLowLevelConfig(conf *Config) (lowLevelConf *Config, err error) { 533 var ( 534 baseURL = conf.OptionalString("baseURL", "") 535 listen = conf.OptionalString("listen", "") 536 auth = conf.RequiredString("auth") 537 keyId = conf.RequiredString("identity") 538 secretRing = conf.RequiredString("identitySecretRing") 539 tlsOn = conf.OptionalBool("https", false) 540 tlsCert = conf.OptionalString("HTTPSCertFile", "") 541 tlsKey = conf.OptionalString("HTTPSKeyFile", "") 542 543 // Blob storage options 544 blobPath = conf.OptionalString("blobPath", "") 545 packBlobs = conf.OptionalBool("packBlobs", false) // use diskpacked instead of the default filestorage 546 s3 = conf.OptionalString("s3", "") // "access_key_id:secret_access_key:bucket[:hostname]" 547 googlecloudstorage = conf.OptionalString("googlecloudstorage", "") // "clientId:clientSecret:refreshToken:bucket" 548 googledrive = conf.OptionalString("googledrive", "") // "clientId:clientSecret:refreshToken:parentId" 549 // Enable the share handler. If true, and shareHandlerPath is empty, 550 // then shareHandlerPath defaults to "/share/". 551 shareHandler = conf.OptionalBool("shareHandler", false) 552 // URL prefix for the share handler. If set, overrides shareHandler. 553 shareHandlerPath = conf.OptionalString("shareHandlerPath", "") 554 555 // Index options 556 memoryIndex = conf.OptionalBool("memoryIndex", true) // copy disk-based index to memory on start-up 557 runIndex = conf.OptionalBool("runIndex", true) // if false: no search, no UI, etc. 558 dbname = conf.OptionalString("dbname", "") // for mysql, postgres, mongo 559 mysql = conf.OptionalString("mysql", "") 560 postgres = conf.OptionalString("postgres", "") 561 mongo = conf.OptionalString("mongo", "") 562 sqliteFile = conf.OptionalString("sqlite", "") 563 kvFile = conf.OptionalString("kvIndexFile", "") 564 565 // Importer options 566 flickr = conf.OptionalString("flickr", "") 567 568 _ = conf.OptionalList("replicateTo") 569 publish = conf.OptionalObject("publish") 570 // alternative source tree, to override the embedded ui and/or closure resources. 571 // If non empty, the ui files will be expected at 572 // sourceRoot + "/server/camlistored/ui" and the closure library at 573 // sourceRoot + "/third_party/closure/lib" 574 // Also used by the publish handler. 575 sourceRoot = conf.OptionalString("sourceRoot", "") 576 577 ownerName = conf.OptionalString("ownerName", "") 578 ) 579 if err := conf.Validate(); err != nil { 580 return nil, err 581 } 582 583 obj := jsonconfig.Obj{} 584 if tlsOn { 585 if (tlsCert != "") != (tlsKey != "") { 586 return nil, errors.New("Must set both TLSCertFile and TLSKeyFile (or neither to generate a self-signed cert)") 587 } 588 if tlsCert != "" { 589 obj["TLSCertFile"] = tlsCert 590 obj["TLSKeyFile"] = tlsKey 591 } else { 592 obj["TLSCertFile"] = osutil.DefaultTLSCert() 593 obj["TLSKeyFile"] = osutil.DefaultTLSKey() 594 } 595 } 596 597 if baseURL != "" { 598 u, err := url.Parse(baseURL) 599 if err != nil { 600 return nil, fmt.Errorf("Error parsing baseURL %q as a URL: %v", baseURL, err) 601 } 602 if u.Path != "" && u.Path != "/" { 603 return nil, fmt.Errorf("baseURL can't have a path, only a scheme, host, and optional port.") 604 } 605 u.Path = "" 606 obj["baseURL"] = u.String() 607 } 608 if listen != "" { 609 obj["listen"] = listen 610 } 611 obj["https"] = tlsOn 612 obj["auth"] = auth 613 614 username := "" 615 if dbname == "" { 616 username = osutil.Username() 617 if username == "" { 618 return nil, fmt.Errorf("USER (USERNAME on windows) env var not set; needed to define dbname") 619 } 620 dbname = "camli" + username 621 } 622 623 var indexerPath string 624 numIndexers := numSet(mongo, mysql, postgres, sqliteFile, kvFile) 625 switch { 626 case runIndex && numIndexers == 0: 627 return nil, fmt.Errorf("Unless runIndex is set to false, you must specify an index option (kvIndexFile, mongo, mysql, postgres, sqlite).") 628 case runIndex && numIndexers != 1: 629 return nil, fmt.Errorf("With runIndex set true, you can only pick exactly one indexer (mongo, mysql, postgres, sqlite).") 630 case !runIndex && numIndexers != 0: 631 return nil, fmt.Errorf("With runIndex disabled, you can't specify any of mongo, mysql, postgres, sqlite.") 632 case mysql != "": 633 indexerPath = "/index-mysql/" 634 case postgres != "": 635 indexerPath = "/index-postgres/" 636 case mongo != "": 637 indexerPath = "/index-mongo/" 638 case sqliteFile != "": 639 indexerPath = "/index-sqlite/" 640 case kvFile != "": 641 indexerPath = "/index-kv/" 642 } 643 644 entity, err := jsonsign.EntityFromSecring(keyId, secretRing) 645 if err != nil { 646 return nil, err 647 } 648 armoredPublicKey, err := jsonsign.ArmoredPublicKey(entity) 649 if err != nil { 650 return nil, err 651 } 652 653 nolocaldisk := blobPath == "" 654 if nolocaldisk { 655 if s3 == "" && googlecloudstorage == "" { 656 return nil, errors.New("You need at least one of blobPath (for localdisk) or s3 or googlecloudstorage configured for a blobserver.") 657 } 658 if s3 != "" && googlecloudstorage != "" { 659 return nil, errors.New("Using S3 as a primary storage and Google Cloud Storage as a mirror is not supported for now.") 660 } 661 } 662 663 if shareHandler && shareHandlerPath == "" { 664 shareHandlerPath = "/share/" 665 } 666 667 prefixesParams := &configPrefixesParams{ 668 secretRing: secretRing, 669 keyId: keyId, 670 indexerPath: indexerPath, 671 blobPath: blobPath, 672 packBlobs: packBlobs, 673 searchOwner: blob.SHA1FromString(armoredPublicKey), 674 shareHandlerPath: shareHandlerPath, 675 flickr: flickr, 676 memoryIndex: memoryIndex, 677 } 678 679 prefixes := genLowLevelPrefixes(prefixesParams, ownerName) 680 var cacheDir string 681 if nolocaldisk { 682 // Whether camlistored is run from EC2 or not, we use 683 // a temp dir as the cache when primary storage is S3. 684 // TODO(mpl): s3CacheBucket 685 // See http://code.google.com/p/camlistore/issues/detail?id=85 686 cacheDir = filepath.Join(tempDir(), "camli-cache") 687 } else { 688 cacheDir = filepath.Join(blobPath, "cache") 689 } 690 if !noMkdir { 691 if err := os.MkdirAll(cacheDir, 0700); err != nil { 692 return nil, fmt.Errorf("Could not create blobs cache dir %s: %v", cacheDir, err) 693 } 694 } 695 696 published := []interface{}{} 697 if len(publish) > 0 { 698 if !runIndex { 699 return nil, fmt.Errorf("publishing requires an index") 700 } 701 published, err = addPublishedConfig(prefixes, publish, sourceRoot) 702 if err != nil { 703 return nil, fmt.Errorf("Could not generate config for published: %v", err) 704 } 705 } 706 707 if runIndex { 708 addUIConfig(prefixesParams, prefixes, "/ui/", published, sourceRoot) 709 } 710 711 if mysql != "" { 712 addMySQLConfig(prefixes, dbname, mysql) 713 } 714 if postgres != "" { 715 addPostgresConfig(prefixes, dbname, postgres) 716 } 717 if mongo != "" { 718 addMongoConfig(prefixes, dbname, mongo) 719 } 720 if sqliteFile != "" { 721 addSQLiteConfig(prefixes, sqliteFile) 722 } 723 if kvFile != "" { 724 addKVConfig(prefixes, kvFile) 725 } 726 if s3 != "" { 727 if err := addS3Config(prefixesParams, prefixes, s3); err != nil { 728 return nil, err 729 } 730 } 731 if googledrive != "" { 732 if err := addGoogleDriveConfig(prefixes, googledrive); err != nil { 733 return nil, err 734 } 735 } 736 if googlecloudstorage != "" { 737 if err := addGoogleCloudStorageConfig(prefixes, googlecloudstorage); err != nil { 738 return nil, err 739 } 740 } 741 742 obj["prefixes"] = (map[string]interface{})(prefixes) 743 744 lowLevelConf = &Config{ 745 Obj: obj, 746 configPath: conf.configPath, 747 } 748 return lowLevelConf, nil 749 } 750 751 func numSet(vv ...interface{}) (num int) { 752 for _, vi := range vv { 753 switch v := vi.(type) { 754 case string: 755 if v != "" { 756 num++ 757 } 758 case bool: 759 if v { 760 num++ 761 } 762 default: 763 panic("unknown type") 764 } 765 } 766 return 767 } 768 769 func setMap(m map[string]interface{}, v ...interface{}) { 770 if len(v) < 2 { 771 panic("too few args") 772 } 773 if len(v) == 2 { 774 m[v[0].(string)] = v[1] 775 return 776 } 777 setMap(m[v[0].(string)].(map[string]interface{}), v[1:]...) 778 }