github.com/holochain/holochain-proto@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/cmd/hcdev/hcdev.go (about) 1 // Copyright (C) 2013-2018, The MetaCurrency Project (Eric Harris-Braun, Arthur Brock, et. al.) 2 // Use of this source code is governed by GPLv3 found in the LICENSE file 3 //--------------------------------------------------------------------------------------- 4 // command line interface to developing and testing holochain applications 5 6 package main 7 8 import ( 9 "bytes" 10 "fmt" 11 "io/ioutil" 12 "os" 13 "os/exec" 14 "os/user" 15 "path/filepath" 16 "regexp" 17 "strconv" 18 "time" 19 20 holo "github.com/holochain/holochain-proto" 21 . "github.com/holochain/holochain-proto/apptest" 22 "github.com/holochain/holochain-proto/cmd" 23 "github.com/holochain/holochain-proto/ui" 24 "github.com/urfave/cli" 25 // fsnotify "github.com/fsnotify/fsnotify" 26 //spew "github.com/davecgh/go-spew/spew" 27 ) 28 29 const ( 30 defaultUIPort = "4141" 31 scenarioStartDelay = 1 32 33 defaultSpecsFile = "bridge_specs.json" 34 ) 35 36 var debug, appInitialized, verbose, keepalive bool 37 var keepaliveCleanup func() 38 var rootPath, devPath, name string 39 var bridgeSpecsFile string 40 var scenarioConfig *holo.TestConfig 41 42 // flags for holochain config generation 43 var dhtPort, logPrefix, bootstrapServer string 44 var mdns bool = true 45 var upnp bool 46 47 // meta flags for program flow control 48 var syncPausePath string 49 var syncPauseUntil int 50 51 type MutableContext struct { 52 str map[string]string 53 obj map[string]interface{} 54 } 55 56 var mutableContext MutableContext 57 58 var lastRunContext *cli.Context 59 60 var sysUser *user.User 61 62 // TODO: move these into cmd module 63 64 func appCheck(devPath string) error { 65 if !appInitialized { 66 return cmd.MakeErr(nil, fmt.Sprintf("%s doesn't look like a holochain app (missing dna). See 'hcdev init -h' for help on initializing an app.", devPath)) 67 } 68 return nil 69 } 70 func setupApp() (app *cli.App) { 71 72 // set default values so we can call this multiple time for testing 73 debug = false 74 appInitialized = false 75 rootPath = "" 76 devPath = "" 77 name = "" 78 mutableContext = MutableContext{map[string]string{}, map[string]interface{}{}} 79 80 var err error 81 sysUser, err = user.Current() 82 if err != nil { 83 panic(err) 84 } 85 86 app = cli.NewApp() 87 app.Name = "hcdev" 88 app.Usage = "holochain dev command line tool" 89 app.Version = fmt.Sprintf("0.0.6 (holochain %s)", holo.VersionStr) 90 91 var service *holo.Service 92 var serverID, agentID, identity string 93 94 var scenarioTmpDir = "hcdev_scenario_test_nodes_" + sysUser.Username 95 96 var dumpScenario string 97 var dumpTest bool 98 var start int 99 100 var bridgeAppTmpFilePath string 101 102 app.Flags = []cli.Flag{ 103 cli.BoolFlag{ 104 Name: "debug", 105 Usage: "debugging output", 106 Destination: &debug, 107 }, 108 cli.BoolFlag{ 109 Name: "verbose", 110 Usage: "verbose output", 111 Destination: &verbose, 112 }, 113 cli.BoolFlag{ 114 Name: "keepalive", 115 Usage: "don't end hcdev process upon completion of work", 116 Destination: &keepalive, 117 }, 118 cli.StringFlag{ 119 Name: "execpath", 120 Usage: "path to holochain dev execution directory (default: ~/.holochaindev)", 121 Destination: &rootPath, 122 }, 123 cli.StringFlag{ 124 Name: "path", 125 Usage: "path to chain source definition directory (default: current working dir)", 126 Destination: &devPath, 127 }, 128 cli.StringFlag{ 129 Name: "DHTport", 130 Usage: fmt.Sprintf("port to use for the holochain DHT and node-to-node communication (defaut: %d)", holo.DefaultDHTPort), 131 Destination: &dhtPort, 132 }, 133 cli.BoolTFlag{ 134 Name: "mdns", 135 Usage: "whether to use mdns for local peer discovery (default: true)", 136 Destination: &mdns, 137 }, 138 cli.BoolFlag{ 139 Name: "upnp", 140 Usage: "whether to use UPnP for creating a NAT port mapping (default: false)", 141 Destination: &upnp, 142 }, 143 cli.StringFlag{ 144 Name: "logPrefix", 145 Usage: "the prefix to put at the front of log messages", 146 Destination: &logPrefix, 147 }, 148 cli.StringFlag{ 149 Name: "bootstrapServer", 150 Usage: "url of bootstrap server or '_' for none", 151 Destination: &bootstrapServer, 152 }, 153 cli.StringFlag{ 154 Name: "bridgeSpecs", 155 Usage: fmt.Sprintf("path to bridge specs file (default: %s)", defaultSpecsFile), 156 Destination: &bridgeSpecsFile, 157 }, 158 cli.StringFlag{ 159 Name: "serverID", 160 Usage: "server identifier for multi-server scenario testing", 161 Destination: &serverID, 162 }, 163 cli.StringFlag{ 164 Name: "agentID", 165 Usage: "value to use for the agent identity (automatically set in scenario testing)", 166 Destination: &agentID, 167 }, 168 } 169 170 var dumpChain, dumpDHT, initTest, fromDevelop, benchmarks, json bool 171 var clonePath, appPackagePath, cloneExample, outputDir, fromBranch, dumpFormat string 172 173 app.Commands = []cli.Command{ 174 { 175 Name: "init", 176 Aliases: []string{"i"}, 177 Usage: "initialize a holochain app directory: use default, from an appPackage file or clone from another app", 178 Flags: []cli.Flag{ 179 cli.BoolFlag{ 180 Name: "test", 181 Usage: "initialize built-in testing app", 182 Destination: &initTest, 183 }, 184 cli.StringFlag{ 185 Name: "clone", 186 Usage: "path from which to clone the app", 187 Destination: &clonePath, 188 }, 189 cli.StringFlag{ 190 Name: "package", 191 Usage: "path to an app package file from which to initialize the app", 192 Destination: &appPackagePath, 193 }, 194 cli.StringFlag{ 195 Name: "cloneExample", 196 Usage: "example from github.com/holochain to clone from", 197 Destination: &cloneExample, 198 }, 199 cli.StringFlag{ 200 Name: "fromBranch", 201 Usage: "specify branch to use with cloneExample", 202 Destination: &fromBranch, 203 }, 204 cli.BoolFlag{ 205 Name: "fromDevelop", 206 Usage: "specify that cloneExample should use the 'develop' branch", 207 Destination: &fromDevelop, 208 }, 209 }, 210 ArgsUsage: "<name>", 211 Action: func(c *cli.Context) error { 212 var name string 213 args := c.Args() 214 if len(args) != 1 { 215 if cloneExample != "" { 216 name = cloneExample 217 } else { 218 return cmd.MakeErr(c, "expecting app name as single argument") 219 } 220 } 221 flags := 0 222 if clonePath != "" { 223 flags += 1 224 } 225 if appPackagePath != "" { 226 flags += 1 227 } 228 if initTest { 229 flags += 1 230 } 231 if flags > 1 { 232 return cmd.MakeErr(c, " options are mutually exclusive, please choose just one.") 233 } 234 if name == "" { 235 name = args[0] 236 } 237 if filepath.IsAbs(name) { 238 devPath = name 239 name = filepath.Base(name) 240 } else { 241 devPath = filepath.Join(devPath, name) 242 } 243 244 info, err := os.Stat(devPath) 245 if err == nil && info.Mode().IsDir() { 246 return cmd.MakeErr(c, fmt.Sprintf("%s already exists", devPath)) 247 } 248 249 encodingFormat := "json" 250 if initTest { 251 fmt.Printf("initializing test app as %s\n", name) 252 format := "json" 253 if len(c.Args()) == 2 { 254 format = c.Args()[1] 255 if !(format == "json" || format == "yaml" || format == "toml") { 256 return cmd.MakeErr(c, "format must be one of yaml,toml,json") 257 258 } 259 } 260 _, err := service.MakeTestingApp(devPath, "json", holo.SkipInitializeDB, holo.CloneWithNewUUID, nil) 261 if err != nil { 262 return cmd.MakeErrFromErr(c, err) 263 } 264 } else if clonePath != "" { 265 266 // build the app by cloning from another app 267 info, err := os.Stat(clonePath) 268 if err != nil { 269 dir, _ := cmd.GetCurrentDirectory() 270 return cmd.MakeErr(c, fmt.Sprintf("ClonePath:%s/'%s' %s", dir, clonePath, err.Error())) 271 } 272 273 if !info.Mode().IsDir() { 274 return cmd.MakeErr(c, "-clone flag expects a directory to clone from") 275 } 276 fmt.Printf("cloning %s from %s\n", name, clonePath) 277 err = doClone(service, clonePath, devPath) 278 if err != nil { 279 return cmd.MakeErrFromErr(c, err) 280 } 281 } else if cloneExample != "" { 282 tmpCopyDir, err := ioutil.TempDir("", fmt.Sprintf("holochain.example.%s", cloneExample)) 283 if err != nil { 284 return cmd.MakeErrFromErr(c, err) 285 } 286 defer os.RemoveAll(tmpCopyDir) 287 err = os.Chdir(tmpCopyDir) 288 if err != nil { 289 return cmd.MakeErrFromErr(c, err) 290 } 291 if fromDevelop { 292 fromBranch = "develop" 293 } 294 command := exec.Command("git", "clone", fmt.Sprintf("git://github.com/holochain/%s.git", cloneExample)) 295 out, err := command.CombinedOutput() 296 fmt.Printf("git: %s\n", string(out)) 297 if err != nil { 298 return cmd.MakeErrFromErr(c, err) 299 } 300 301 if fromBranch != "" { 302 err = os.Chdir(filepath.Join(tmpCopyDir, cloneExample)) 303 if err != nil { 304 return cmd.MakeErrFromErr(c, err) 305 } 306 command := exec.Command("git", "checkout", fromBranch) 307 out, err := command.CombinedOutput() 308 fmt.Printf("git: %s\n", string(out)) 309 if err != nil { 310 return cmd.MakeErrFromErr(c, err) 311 } 312 } 313 314 clonePath := filepath.Join(tmpCopyDir, cloneExample) 315 fmt.Printf("cloning %s from github.com/holochain/%s\n", name, cloneExample) 316 err = doClone(service, clonePath, devPath) 317 if err != nil { 318 return cmd.MakeErrFromErr(c, err) 319 } 320 321 } else if appPackagePath != "" { 322 // build the app from the appPackage 323 _, err := cmd.UpackageAppPackage(service, appPackagePath, devPath, name, encodingFormat) 324 if err != nil { 325 return cmd.MakeErrFromErr(c, err) 326 } 327 328 fmt.Printf("initialized %s from appPackage:%s\n", devPath, appPackagePath) 329 } else { 330 331 // build empty app template 332 err := holo.MakeDirs(devPath) 333 if err != nil { 334 return cmd.MakeErrFromErr(c, err) 335 } 336 appPackageReader := bytes.NewBuffer([]byte(holo.BasicTemplateAppPackage)) 337 338 var agent holo.Agent 339 agent, err = holo.LoadAgent(rootPath) 340 if err != nil { 341 return cmd.MakeErrFromErr(c, err) 342 } 343 344 var appPackage *holo.AppPackage 345 appPackage, err = service.SaveFromAppPackage(appPackageReader, devPath, name, agent, holo.BasicTemplateAppPackageFormat, encodingFormat, true) 346 347 if err != nil { 348 return cmd.MakeErrFromErr(c, err) 349 } 350 fmt.Printf("initialized empty application to %s with new UUID:%v\n", devPath, appPackage.DNA.UUID) 351 } 352 353 err = os.Chdir(devPath) 354 if err != nil { 355 return cmd.MakeErrFromErr(c, err) 356 } 357 358 return nil 359 }, 360 }, 361 { 362 Name: "test", 363 Aliases: []string{"t"}, 364 ArgsUsage: "no args run's all stand-alone | [test file prefix] | [scenario] [role]", 365 Usage: "run chain's stand-alone or scenario tests", 366 Flags: []cli.Flag{ 367 cli.StringFlag{ 368 Name: "syncPausePath", 369 Usage: "path to wait for multinode test sync", 370 Destination: &syncPausePath, 371 }, 372 cli.IntFlag{ 373 Name: "syncPauseUntil", 374 Usage: "unix timestamp - sync tests to run at this time", 375 Destination: &syncPauseUntil, 376 }, 377 cli.BoolFlag{ 378 Name: "benchmarks", 379 Usage: "calculate benchmarks during test", 380 Destination: &benchmarks, 381 }, 382 cli.StringFlag{ 383 Name: "bridgeAppsFile", 384 Usage: "path to live bridging Apps (used internally when scenario testing)", 385 Destination: &bridgeAppTmpFilePath, 386 }, 387 }, 388 Action: func(c *cli.Context) error { 389 holo.Debug("test: start") 390 391 var err error 392 if err = appCheck(devPath); err != nil { 393 return cmd.MakeErrFromErr(c, err) 394 } 395 396 args := c.Args() 397 var errs []error 398 399 var h *holo.Holochain 400 h, err = getHolochain(c, service, identity) 401 if err != nil { 402 return cmd.MakeErrFromErr(c, err) 403 } 404 holo.Debug("test: initialised holochain\n") 405 406 if len(args) < 2 { 407 var bridgeApps []BridgeAppForTests 408 409 bridgeApps, err = getBridgeAppForTests(service, h.Agent()) 410 if err != nil { 411 return cmd.MakeErrFromErr(c, err) 412 } 413 414 if len(args) == 1 { 415 errs = TestOne(h, args[0], bridgeApps, benchmarks) 416 } else if len(args) == 0 { 417 errs = Test(h, bridgeApps, benchmarks) 418 } else { 419 return cmd.MakeErr(c, "expected 0 args (run all stand-alone tests), 1 arg (a single stand-alone test) or 2 args (scenario and role)") 420 } 421 } else { 422 var bridgeApps []holo.BridgeApp 423 if bridgeAppTmpFilePath != "" { 424 bridgeApps, err = getBridgeAppsFromTmpFile(bridgeAppTmpFilePath) 425 if err != nil { 426 return cmd.MakeErrFromErr(c, err) 427 } 428 } 429 430 holo.Debug("test: scenario") 431 432 scenario := args[0] 433 role := args[1] 434 holo.Debugf("test: scenario(%v, %v)\n", scenario, role) 435 436 holo.Debugf("test: scenario(%v, %v): paused at: %v\n", scenario, role, time.Now()) 437 438 if syncPauseUntil != 0 { 439 // IntFlag converts the string into int64 anyway. This explicit conversion is valid 440 time.Sleep(cmd.GetDuration_fromUnixTimestamp(int64(syncPauseUntil))) 441 } 442 holo.Debugf("test: scenario(%v, %v): continuing at: %v\n", scenario, role, time.Now()) 443 pairs := map[string]string{"%server%": serverID} 444 445 // The clone id is put into the identity by scenario call so we get 446 // out with this regex 447 re := regexp.MustCompile(`.*.([0-9]+)@.*`) 448 x := re.FindStringSubmatch(string(h.Agent().Identity())) 449 var clone string 450 if len(x) > 0 { 451 clone = x[1] 452 pairs["%clone%"] = clone 453 } 454 455 host := getHostName(serverID) 456 err = addRolesToPairs(h, scenario, host, pairs) 457 if err != nil { 458 return cmd.MakeErrFromErr(c, err) 459 } 460 461 err, errs = TestScenario(h, scenario, role, pairs, benchmarks, bridgeApps) 462 if err != nil { 463 return cmd.MakeErrFromErr(c, err) 464 } 465 //holo.Debugf("testScenario: h: %v\n", spew.Sdump(h)) 466 467 } 468 469 var s string 470 for _, e := range errs { 471 s += e.Error() 472 } 473 if s != "" { 474 return cmd.MakeErr(c, s) 475 } 476 return nil 477 }, 478 }, 479 { 480 Name: "scenario", 481 Aliases: []string{"s"}, 482 Usage: "run a scenario test", 483 ArgsUsage: "scenario-name", 484 Flags: []cli.Flag{ 485 cli.StringFlag{ 486 Name: "outputDir", 487 Usage: "directory to send output", 488 Destination: &outputDir, 489 }, 490 cli.BoolFlag{ 491 Name: "benchmarks", 492 Usage: "calculate benchmarks during scenario test", 493 Destination: &benchmarks, 494 }, 495 }, 496 Action: func(c *cli.Context) error { 497 mutableContext.str["command"] = "scenario" 498 499 if err := appCheck(devPath); err != nil { 500 return err 501 } 502 503 args := c.Args() 504 if len(args) != 1 { 505 return cmd.MakeErr(c, "missing scenario name argument") 506 } 507 scenarioName := args[0] 508 509 // get the holochain from the source that we are supposed to be testing 510 h, err := getHolochain(c, service, identity) 511 if err != nil { 512 return cmd.MakeErrFromErr(c, err) 513 } 514 515 // get the bridgeApps 516 var bridgeApps []BridgeAppForTests 517 bridgeApps, err = getBridgeAppForTests(service, h.Agent()) 518 if err != nil { 519 return cmd.MakeErrFromErr(c, err) 520 } 521 522 var bridgeAppsTmpfileName string 523 if len(bridgeApps) > 0 { 524 bridgeAppsTmpfileName, err = saveBridgeAppsToTmpFile(bridgeApps) 525 if err != nil { 526 return cmd.MakeErrFromErr(c, err) 527 } 528 } 529 530 //Spin up the bridgeApps 531 var bridgeAppServers []*ui.WebServer 532 for _, app := range bridgeApps { 533 var bridgeAppServer *ui.WebServer 534 bridgeAppServer, err = StartBridgeApp(app.H, app.BridgeApp.Port) 535 if err != nil { 536 return cmd.MakeErrFromErr(c, err) 537 } 538 bridgeAppServers = append(bridgeAppServers, bridgeAppServer) 539 } 540 541 // mutableContext.obj["initialHolochain"] = h 542 testScenarioList, err := holo.GetTestScenarios(h) 543 if err != nil { 544 return cmd.MakeErrFromErr(c, err) 545 } 546 mutableContext.obj["testScenarioList"] = &testScenarioList 547 548 // confirm the user chosen scenario name 549 // TODO add this to code completion 550 if _, ok := testScenarioList[scenarioName]; !ok { 551 return cmd.MakeErr(c, "source argument is not directory in /test. scenario name must match directory name") 552 } 553 mutableContext.str["testScenarioName"] = scenarioName 554 555 // get list of roles 556 roleList, err := holo.GetTestScenarioRoles(h, scenarioName) 557 if err != nil { 558 return cmd.MakeErrFromErr(c, err) 559 } 560 mutableContext.obj["testScenarioRoleList"] = &roleList 561 562 // run a bunch of hcdev test processes. Separate temp folder by username in case 563 // multiple users on the same machine are running tests 564 rootExecDir, err := cmd.MakeTmpDir(scenarioTmpDir) 565 if err != nil { 566 return cmd.MakeErrFromErr(c, err) 567 } 568 secondsFromNowPlusDelay := cmd.GetUnixTimestamp_secondsFromNow(scenarioStartDelay) 569 570 scenarioPath := filepath.Join(h.TestPath(), scenarioName) 571 572 scenarioConfig, err = holo.LoadTestConfig(scenarioPath) 573 if err != nil { 574 return cmd.MakeErrFromErr(c, err) 575 } 576 577 if outputDir != "" { 578 err = os.MkdirAll(outputDir, os.ModePerm) 579 if err != nil { 580 return cmd.MakeErrFromErr(c, err) 581 } 582 } 583 584 for roleIndex, roleName := range roleList { 585 holo.Debugf("scenario: forRole(%v): start\n\n", roleName) 586 587 // HOLOCHAINCONFIG_DHTPORT = FindSomeAvailablePort 588 // HOLOCHAINCONFIG_ENABLEMDNS = "true" or HOLOCHAINCONFIG_BOOTSTRAP = "ip[localhost]:port[3142] 589 // HCLOG_PREFIX = role 590 591 clones := 1 592 593 for _, clone := range scenarioConfig.Clone { 594 if clone.Role == roleName { 595 clones = clone.Number 596 break 597 } 598 } 599 600 // if the bootstrapServer flag isn't set we assume this is a local scenario 601 // test so we set the flag "_" the use no bootstrap 602 if bootstrapServer == "" { 603 bootstrapServer = "_" 604 } 605 606 // check to see if there's a bridge config for the role 607 scenarioBridgeSpecs := filepath.Join(scenarioPath, "_"+roleName+"_"+defaultSpecsFile) 608 holo.Debugf("scenario: looking for bridgeSpecs:%v", scenarioBridgeSpecs) 609 if !holo.FileExists(scenarioBridgeSpecs) { 610 scenarioBridgeSpecs = bridgeSpecsFile 611 } 612 613 originalRoleName := roleName 614 for count := 0; count < clones; count++ { 615 freePort, err := cmd.GetFreePort() 616 if err != nil { 617 return cmd.MakeErrFromErr(c, err) 618 } 619 620 if clones > 1 { 621 roleName = fmt.Sprintf("%s.%d", originalRoleName, count) 622 623 } 624 agentID = roleName 625 if serverID != "" { 626 roleName = serverID + "." + roleName 627 } 628 holo.Debugf("scenario: forRole(%v): port: %v\n\n", roleName, freePort) 629 630 colorByNumbers := []string{"green", "blue", "yellow", "cyan", "magenta", "red"} 631 632 logPrefix := "%{color:" + colorByNumbers[roleIndex%6] + "}" + roleName + ": " 633 /* time doesn't work in prefix yet 634 if outputDir != "" { 635 logPrefix = "%{time}" + logPrefix 636 }*/ 637 638 var upnpnat string 639 if bootstrapServer == "_" { 640 upnpnat = "false" 641 } else { 642 upnpnat = "true" 643 } 644 testCommand := exec.Command( 645 "hcdev", 646 "-path="+devPath, 647 "-execpath="+filepath.Join(rootExecDir, roleName), 648 "-DHTport="+strconv.Itoa(freePort), 649 fmt.Sprintf("-mdns=%v", mdns), 650 "-upnp="+upnpnat, 651 "-logPrefix="+logPrefix, 652 "-serverID="+serverID, 653 "-agentID="+agentID, 654 fmt.Sprintf("-bootstrapServer=%v", bootstrapServer), 655 fmt.Sprintf("-keepalive=%v", keepalive), 656 657 "test", 658 fmt.Sprintf("-bridgeAppsFile=%v", bridgeAppsTmpfileName), 659 fmt.Sprintf("-benchmarks=%v", benchmarks), 660 fmt.Sprintf("-syncPauseUntil=%v", secondsFromNowPlusDelay), 661 scenarioName, 662 originalRoleName, 663 ) 664 665 mutableContext.obj["testCommand."+roleName] = &testCommand 666 667 holo.Debugf("scenario: forRole(%v): testCommandPrepared: %v\n", roleName, testCommand) 668 669 if outputDir != "" { 670 f := filepath.Join(outputDir, roleName) 671 df, err := os.Create(f) 672 if err != nil { 673 return cmd.MakeErrFromErr(c, err) 674 } 675 defer df.Close() 676 testCommand.Stdout = df 677 testCommand.Stderr = df 678 } else { 679 680 testCommand.Stdout = os.Stdout 681 testCommand.Stderr = os.Stderr 682 } 683 testCommand.Start() 684 685 holo.Debugf("scenario: forRole(%v): testCommandStarted\n", roleName) 686 } 687 } 688 keepalive = true 689 if len(bridgeApps) > 0 { 690 keepaliveCleanup = func() { 691 StopBridgeApps(bridgeAppServers) 692 os.Remove(bridgeAppsTmpfileName) 693 } 694 } 695 return nil 696 }, 697 }, 698 { 699 Name: "web", 700 Aliases: []string{"serve", "w"}, 701 ArgsUsage: "[ui-port]", 702 Usage: fmt.Sprintf("serve a chain to the web on localhost:<ui-port> (default: %s)", defaultUIPort), 703 Action: func(c *cli.Context) error { 704 if err := appCheck(devPath); err != nil { 705 return cmd.MakeErrFromErr(c, err) 706 } 707 708 h, err := getHolochain(c, service, agentID) 709 if err != nil { 710 return cmd.MakeErrFromErr(c, err) 711 } 712 713 bridgeApps, err := getBridgeAppForTests(service, h.Agent()) 714 if err != nil { 715 return cmd.MakeErrFromErr(c, err) 716 } 717 718 h.Close() 719 h, err = service.GenChain(name) 720 if err != nil { 721 return cmd.MakeErrFromErr(c, err) 722 } 723 724 var port string 725 if len(c.Args()) == 0 { 726 port = defaultUIPort 727 } else { 728 port = c.Args()[0] 729 } 730 731 var ws *ui.WebServer 732 ws, err = activate(h, port) 733 if err != nil { 734 return cmd.MakeErrFromErr(c, err) 735 } 736 737 var bridgeAppServers []*ui.WebServer 738 bridgeAppServers, err = BuildBridges(h, port, bridgeApps) 739 if err != nil { 740 return cmd.MakeErrFromErr(c, err) 741 } 742 ws.Wait() 743 // TODO call StopBridgeApps instead???? 744 for _, server := range bridgeAppServers { 745 server.Stop() 746 } 747 748 return nil 749 }, 750 }, 751 752 { 753 Name: "package", 754 Aliases: []string{"p"}, 755 ArgsUsage: "[output file]", 756 Usage: fmt.Sprintf("writes a package file of the dev path to file or stdout"), 757 Action: func(c *cli.Context) error { 758 759 var old *os.File 760 if len(c.Args()) == 0 { 761 old = os.Stdout // keep backup of the real stdout 762 _, w, _ := os.Pipe() 763 os.Stdout = w 764 } 765 766 if err := appCheck(devPath); err != nil { 767 return err 768 } 769 h, err := getHolochain(c, service, identity) 770 if err != nil { 771 return cmd.MakeErrFromErr(c, err) 772 } 773 appPackage, err := service.MakeAppPackage(h) 774 if err != nil { 775 return cmd.MakeErrFromErr(c, err) 776 } 777 778 if len(c.Args()) == 0 { 779 os.Stdout = old 780 fmt.Print(string(appPackage)) 781 } else { 782 err = holo.WriteFile(appPackage, c.Args().First()) 783 } 784 if err != nil { 785 return cmd.MakeErrFromErr(c, err) 786 } 787 return nil 788 }, 789 }, 790 791 { 792 Name: "dump", 793 Aliases: []string{"d"}, 794 ArgsUsage: "holochain-name", 795 Usage: "display a text dump of a chain after last 'web', 'test', or 'scenario'", 796 Flags: []cli.Flag{ 797 cli.BoolFlag{ 798 Name: "chain", 799 Destination: &dumpChain, 800 }, 801 cli.BoolFlag{ 802 Name: "dht", 803 Destination: &dumpDHT, 804 }, 805 cli.BoolFlag{ 806 Name: "json", 807 Destination: &json, 808 Usage: "Dump chain or dht as JSON string", 809 }, 810 cli.IntFlag{ 811 Name: "index", 812 Destination: &start, 813 Usage: "starting index for dump (zero based)", 814 }, 815 cli.BoolFlag{ 816 Name: "test", 817 Destination: &dumpTest, 818 }, 819 cli.StringFlag{ 820 Name: "scenario", 821 Destination: &dumpScenario, 822 }, 823 cli.StringFlag{ 824 Name: "format", 825 Destination: &dumpFormat, 826 Usage: "Dump format (string, json, dot)", 827 Value: "string", 828 }, 829 }, 830 Action: func(c *cli.Context) error { 831 832 if !dumpChain && !dumpDHT { 833 dumpChain = true 834 } 835 836 var h *holo.Holochain 837 var s *holo.Service 838 var err error 839 if dumpTest { 840 panic("not implemented") 841 } else if dumpScenario != "" { 842 // the value is the role name which has it's own service for that role 843 var d string 844 d, err = cmd.GetTmpDir(scenarioTmpDir) 845 if err == nil { 846 s, err = holo.LoadService(filepath.Join(d, dumpScenario)) 847 } 848 } else { 849 // use default service 850 s = service 851 } 852 if err == nil { 853 h, err = s.Load(name) 854 if err == nil { 855 err = h.Prepare() 856 } 857 } 858 if err != nil { 859 return cmd.MakeErrFromErr(c, err) 860 } 861 862 if !h.Started() { 863 return cmd.MakeErr(c, "No data to dump, chain not yet initialized.") 864 } 865 866 dnaHash := h.DNAHash() 867 if dumpChain { 868 if json { 869 dump, _ := h.Chain().JSON(start) 870 fmt.Println(dump) 871 } else if dumpFormat != "" { 872 switch dumpFormat { 873 case "string": 874 fmt.Printf("Chain for: %s\n%v", dnaHash, h.Chain().Dump(start)) 875 case "dot": 876 dump, _ := h.Chain().Dot(start) 877 fmt.Println(dump) 878 case "json": 879 dump, _ := h.Chain().JSON(start) 880 fmt.Println(dump) 881 default: 882 return cmd.MakeErr(c, "format must be one of dot,json,string") 883 } 884 } else { 885 fmt.Printf("Chain for: %s\n%v", dnaHash, h.Chain().Dump(start)) 886 } 887 } 888 if dumpDHT { 889 if json { 890 dump, _ := h.DHT().JSON() 891 fmt.Println(dump) 892 } else { 893 fmt.Printf("DHT for: %s\n%v", dnaHash, h.DHT().String()) 894 } 895 } 896 897 return nil 898 }, 899 }, 900 } 901 902 app.Before = func(c *cli.Context) error { 903 lastRunContext = c 904 905 var err error 906 907 if dhtPort != "" { 908 err = os.Setenv("HOLOCHAINCONFIG_DHTPORT", dhtPort) 909 if err != nil { 910 return err 911 } 912 } 913 if mdns { 914 err = os.Setenv("HOLOCHAINCONFIG_ENABLEMDNS", "true") 915 if err != nil { 916 return err 917 } 918 } 919 if upnp != true { 920 err = os.Setenv("HOLOCHAINCONFIG_ENABLENATUPNP", "false") 921 if err != nil { 922 return err 923 } 924 } 925 if logPrefix != "" { 926 os.Setenv("HCLOG_PREFIX", logPrefix) 927 if err != nil { 928 return err 929 } 930 } 931 if bootstrapServer != "" { 932 os.Setenv("HOLOCHAINCONFIG_BOOTSTRAP", bootstrapServer) 933 if err != nil { 934 return err 935 } 936 } 937 938 holo.Debugf("args:%v\n", c.Args()) 939 940 // hcdev always enables the app debugging, and the -debug flag enables the holochain debugging 941 os.Setenv("HCLOG_APP_ENABLE", "1") 942 if debug { 943 os.Setenv("HCLOG_DHT_ENABLE", "1") 944 os.Setenv("HCLOG_GOSSIP_ENABLE", "1") 945 os.Setenv("HCLOG_DEBUG_ENABLE", "1") 946 } 947 holo.InitializeHolochain() 948 949 if devPath == "" { 950 devPath, err = os.Getwd() 951 if err != nil { 952 return err 953 } 954 } 955 name = filepath.Base(devPath) 956 957 if cmd.IsAppDir(devPath) == nil { 958 appInitialized = true 959 } 960 961 if rootPath == "" { 962 rootPath = os.Getenv("HOLOPATHDEV") 963 if rootPath == "" { 964 userPath := sysUser.HomeDir 965 rootPath = filepath.Join(userPath, holo.DefaultDirectoryName+"dev") 966 } 967 } 968 identity = getIdentity(agentID, serverID) 969 if !holo.IsInitialized(rootPath) { 970 service, err = holo.Init(rootPath, holo.AgentIdentity(identity), holo.MakeTestSeed(identity)) 971 if err != nil { 972 return err 973 } 974 fmt.Println("Holochain dev service initialized:") 975 fmt.Printf(" %s directory created\n", rootPath) 976 fmt.Printf(" defaults stored to %s\n", holo.SysFileName) 977 fmt.Println(" key-pair generated") 978 fmt.Printf(" using %s as default agent identity (stored to %s)\n", identity, holo.AgentFileName) 979 980 } else { 981 service, err = holo.LoadService(rootPath) 982 } 983 return err 984 } 985 986 app.Action = func(c *cli.Context) error { 987 cli.ShowAppHelp(c) 988 989 return nil 990 } 991 return 992 993 } 994 995 func main() { 996 app := setupApp() 997 err := app.Run(os.Args) 998 var stop chan bool 999 if keepalive { 1000 stop = make(chan bool, 1) 1001 } 1002 if keepalive && scenarioConfig != nil { 1003 go func() { 1004 time.Sleep(time.Second*(scenarioStartDelay+time.Duration(scenarioConfig.Duration)) + time.Second*scenarioStartDelay) 1005 stop <- true 1006 }() 1007 } 1008 if keepalive { 1009 <-stop 1010 } 1011 if keepaliveCleanup != nil { 1012 keepaliveCleanup() 1013 } 1014 if verbose { 1015 fmt.Printf("hcdev complete!\n") 1016 } 1017 if err != nil { 1018 fmt.Printf("Error: %v\n", err) 1019 os.Exit(1) 1020 } 1021 } 1022 1023 func getHolochain(c *cli.Context, service *holo.Service, identity string) (h *holo.Holochain, err error) { 1024 // clear out the previous chain data that was copied from the last test/run 1025 err = os.RemoveAll(filepath.Join(rootPath, name)) 1026 if err != nil { 1027 return 1028 } 1029 var agent holo.Agent 1030 agent, err = holo.LoadAgent(rootPath) 1031 if err != nil { 1032 return 1033 } 1034 1035 if identity != "" { 1036 holo.SetAgentIdentity(agent, holo.AgentIdentity(identity)) 1037 } 1038 1039 fmt.Printf("Copying chain to: %s\n", rootPath) 1040 h, err = service.Clone(devPath, filepath.Join(rootPath, name), agent, holo.CloneWithSameUUID, holo.InitializeDB) 1041 if err != nil { 1042 return 1043 } 1044 h.Close() 1045 1046 h, err = service.Load(name) 1047 if err != nil { 1048 return 1049 } 1050 if verbose { 1051 fmt.Printf("Identity: %s\n", h.Agent().Identity()) 1052 fmt.Printf("NodeID: %s\n", h.NodeIDStr()) 1053 } 1054 return 1055 } 1056 1057 // BridgeSpec describes an app to be bridged for dev 1058 type BridgeSpec struct { 1059 Path string // path to the app to bridge to/from 1060 Side int // what side of the bridge the dev app is (Bridge.Caller or Bridge.Callee) 1061 BridgeGenesisCallerData string // genesis data for the caller side 1062 BridgeGenesisCalleeData string // genesis data for the callee side 1063 Port string // only used if side == BridgeCallee 1064 BridgeZome string // only used if side == BridgeCaller 1065 } 1066 1067 // getBridgeAppsForTests builds up an array of bridged apps based on the dev values for bridging 1068 func getBridgeAppForTests(service *holo.Service, agent holo.Agent) (bridgedApps []BridgeAppForTests, err error) { 1069 if bridgeSpecsFile == "_" { 1070 return 1071 } 1072 var specs []BridgeSpec 1073 specs, err = loadBridgeSpecs() 1074 if err != nil { 1075 return 1076 } 1077 for _, spec := range specs { 1078 var h *holo.Holochain 1079 h, err = setupBridgeApp(service, agent, spec.Path) 1080 if err != nil { 1081 return 1082 } 1083 if spec.Port == "" { 1084 var port int 1085 port, err = cmd.GetFreePort() 1086 if err != nil { 1087 return 1088 } 1089 spec.Port = fmt.Sprintf("%d", port) 1090 } 1091 bridgedApps = append(bridgedApps, 1092 BridgeAppForTests{ 1093 H: h, 1094 BridgeApp: holo.BridgeApp{ 1095 Name: h.Name(), 1096 DNA: h.DNAHash(), 1097 Side: spec.Side, 1098 BridgeGenesisCallerData: spec.BridgeGenesisCallerData, 1099 BridgeGenesisCalleeData: spec.BridgeGenesisCalleeData, 1100 Port: spec.Port, 1101 BridgeZome: spec.BridgeZome, 1102 }, 1103 }) 1104 } 1105 return 1106 } 1107 1108 // setupBridgeApp clones the bridge app from source and loads it in preparation for actual bridging 1109 func setupBridgeApp(service *holo.Service, agent holo.Agent, path string) (bridgeH *holo.Holochain, err error) { 1110 1111 bridgeName := filepath.Base(path) 1112 1113 os.Setenv("HOLOCHAINCONFIG_ENABLEMDNS", "true") 1114 os.Setenv("HOLOCHAINCONFIG_BOOTSTRAP", "_") 1115 os.Setenv("HCLOG_PREFIX", bridgeName+":") 1116 1117 var freePort int 1118 freePort, err = cmd.GetFreePort() 1119 if err != nil { 1120 return 1121 } 1122 1123 os.Setenv("HOLOCHAINCONFIG_DHTPORT", fmt.Sprintf("%d", freePort)) 1124 1125 fmt.Printf("Copying bridge chain %s to: %s\n", bridgeName, rootPath) 1126 // cleanup from previous time 1127 err = os.RemoveAll(filepath.Join(rootPath, bridgeName)) 1128 if err != nil { 1129 return 1130 } 1131 _, err = service.Clone(path, filepath.Join(rootPath, bridgeName), agent, holo.CloneWithSameUUID, holo.InitializeDB) 1132 if err != nil { 1133 return 1134 } 1135 1136 bridgeH, err = service.Load(bridgeName) 1137 if err != nil { 1138 return 1139 } 1140 1141 _, err = bridgeH.GenChain() 1142 if err != nil { 1143 return 1144 } 1145 1146 // clear the log prefix for the next load. 1147 os.Unsetenv("HCLOG_PREFIX") 1148 return 1149 } 1150 1151 func activate(h *holo.Holochain, port string) (ws *ui.WebServer, err error) { 1152 fmt.Printf("Serving holochain with DNA hash:%v on port:%s\n", h.DNAHash(), port) 1153 err = h.Activate() 1154 if err != nil { 1155 return 1156 } 1157 h.StartBackgroundTasks() 1158 ws = ui.NewWebServer(h, port) 1159 ws.Start() 1160 return 1161 } 1162 1163 func GetLastRunContext() (MutableContext, *cli.Context) { 1164 return mutableContext, lastRunContext 1165 } 1166 1167 func doClone(s *holo.Service, clonePath, devPath string) (err error) { 1168 1169 // TODO this is the bogus dev agent, really it should probably be someone else 1170 agent, err := holo.LoadAgent(rootPath) 1171 if err != nil { 1172 return 1173 } 1174 1175 _, err = s.Clone(clonePath, devPath, agent, holo.CloneWithSameUUID, holo.SkipInitializeDB) 1176 if err != nil { 1177 return 1178 } 1179 return 1180 } 1181 1182 func loadBridgeSpecs() (specs []BridgeSpec, err error) { 1183 if bridgeSpecsFile == "" { 1184 holo.Debug("no bridgeSpecs checking for default") 1185 if holo.FileExists(defaultSpecsFile) { 1186 bridgeSpecsFile = defaultSpecsFile 1187 } 1188 } 1189 if bridgeSpecsFile != "" { 1190 holo.Debugf("load bridgeSpecs:%s", bridgeSpecsFile) 1191 err = holo.DecodeFile(&specs, bridgeSpecsFile) 1192 } 1193 return 1194 } 1195 1196 func getHostName(serverID string) (host string) { 1197 if serverID != "" { 1198 host = serverID 1199 } else { 1200 host, _ = os.Hostname() 1201 } 1202 if host == "" { 1203 host = "example.com" 1204 } 1205 return 1206 } 1207 1208 func getIdentity(agentID, serverID string) (identity string) { 1209 var host, username string 1210 host = getHostName(serverID) 1211 1212 if agentID != "" { 1213 username = agentID 1214 } else { 1215 username = sysUser.Username 1216 } 1217 if username == "" { 1218 username = "test" 1219 } 1220 1221 identity = username + "@" + host 1222 return 1223 } 1224 1225 func addRolesToPairs(h *holo.Holochain, scenario string, host string, pairs map[string]string) (err error) { 1226 1227 var roles []string 1228 roles, err = holo.GetTestScenarioRoles(h, scenario) 1229 if err != nil { 1230 return 1231 } 1232 1233 dir := filepath.Join(h.TestPath(), scenario) 1234 var config *holo.TestConfig 1235 config, err = holo.LoadTestConfig(dir) 1236 if err != nil { 1237 return 1238 } 1239 1240 cloneRoles := make(map[string]holo.CloneSpec) 1241 for _, spec := range config.Clone { 1242 cloneRoles[spec.Role] = spec 1243 } 1244 1245 for _, role := range roles { 1246 1247 var testSet holo.TestSet 1248 testSet, err = holo.LoadTestFile(dir, role+".json") 1249 if err != nil { 1250 return 1251 } 1252 spec, isClone := cloneRoles[role] 1253 1254 if testSet.Identity != "" { 1255 if isClone { 1256 err = fmt.Errorf("can't both clone and specify an identity: role %s", role) 1257 return 1258 } 1259 err = addRoleToPairs(h, role, testSet.Identity, pairs) 1260 } else { 1261 if isClone { 1262 origRole := role 1263 for i := 0; i < spec.Number; i++ { 1264 role = fmt.Sprintf("%s.%d", origRole, i) 1265 err = addRoleToPairs(h, role, fmt.Sprintf("%s@%s", role, host), pairs) 1266 } 1267 } else { 1268 err = addRoleToPairs(h, role, role+"@"+host, pairs) 1269 } 1270 } 1271 1272 } 1273 return 1274 } 1275 func addRoleToPairs(h *holo.Holochain, role string, id string, pairs map[string]string) (err error) { 1276 var agent holo.Agent 1277 agent, err = holo.NewAgent(holo.LibP2P, holo.AgentIdentity(id), holo.MakeTestSeed(id)) 1278 if err != nil { 1279 return 1280 } 1281 var hash string 1282 _, hash, err = agent.NodeID() 1283 if err != nil { 1284 return 1285 } 1286 pairs["%"+role+"_str%"] = id 1287 pairs["%"+role+"_key%"] = hash 1288 return 1289 } 1290 1291 func saveBridgeAppsToTmpFile(bridgeAppsForTests []BridgeAppForTests) (bridgeAppsTmpfileName string, err error) { 1292 var bridgeApps []holo.BridgeApp 1293 for _, app := range bridgeAppsForTests { 1294 bridgeApps = append(bridgeApps, app.BridgeApp) 1295 } 1296 var tmpfile *os.File 1297 tmpfile, err = ioutil.TempFile("", "bridgeApp") 1298 if err != nil { 1299 return 1300 } 1301 bridgeAppsTmpfileName = tmpfile.Name() 1302 1303 var data []byte 1304 data, err = holo.ByteEncoder(bridgeApps) 1305 if err != nil { 1306 return 1307 } 1308 _, err = tmpfile.Write(data) 1309 tmpfile.Close() 1310 1311 return 1312 } 1313 1314 func getBridgeAppsFromTmpFile(path string) (bridgeApps []holo.BridgeApp, err error) { 1315 1316 data, err := holo.ReadFile(path) 1317 if err != nil { 1318 return 1319 } 1320 err = holo.ByteDecoder(data, &bridgeApps) 1321 1322 return 1323 }