github.com/leowmjw/otto@v0.2.1-0.20160126165905-6400716cf085/otto/core.go (about) 1 package otto 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 "os" 8 "path/filepath" 9 "strings" 10 "sync" 11 "sync/atomic" 12 "time" 13 14 "github.com/hashicorp/otto/app" 15 "github.com/hashicorp/otto/appfile" 16 "github.com/hashicorp/otto/context" 17 "github.com/hashicorp/otto/directory" 18 "github.com/hashicorp/otto/foundation" 19 "github.com/hashicorp/otto/helper/localaddr" 20 "github.com/hashicorp/otto/infrastructure" 21 "github.com/hashicorp/otto/ui" 22 "github.com/hashicorp/terraform/dag" 23 "github.com/mitchellh/copystructure" 24 ) 25 26 // Core is the main struct to use to interact with Otto as a library. 27 type Core struct { 28 appfile *appfile.File 29 appfileCompiled *appfile.Compiled 30 apps map[app.Tuple]app.Factory 31 dir directory.Backend 32 infras map[string]infrastructure.Factory 33 foundationMap map[foundation.Tuple]foundation.Factory 34 dataDir string 35 localDir string 36 compileDir string 37 ui ui.Ui 38 39 metadataCache *CompileMetadata 40 } 41 42 // CoreConfig is configuration for creating a new core with NewCore. 43 type CoreConfig struct { 44 // DataDir is the directory where local data will be stored that 45 // is global to all Otto processes. 46 // 47 // LocalDir is the directory where data local to this single Appfile 48 // will be stored. This isn't necessarilly cleared for compilation. 49 // 50 // CompiledDir is the directory where compiled data will be written. 51 // Each compilation will clear this directory. 52 DataDir string 53 LocalDir string 54 CompileDir string 55 56 // Appfile is the appfile that this core will be using for configuration. 57 // This must be a compiled Appfile. 58 Appfile *appfile.Compiled 59 60 // Directory is the directory where data is stored about this Appfile. 61 Directory directory.Backend 62 63 // Apps is the map of available app implementations. 64 Apps map[app.Tuple]app.Factory 65 66 // Infrastructures is the map of available infrastructures. The 67 // value is a factory that can create the infrastructure impl. 68 Infrastructures map[string]infrastructure.Factory 69 70 // Foundations is the map of available foundations. The 71 // value is a factory that can create the impl. 72 Foundations map[foundation.Tuple]foundation.Factory 73 74 // Ui is the Ui that will be used to communicate with the user. 75 Ui ui.Ui 76 } 77 78 // NewCore creates a new core. 79 // 80 // Once this function is called, this CoreConfig should not be used again 81 // or modified, since the Core may use parts of it without deep copying. 82 func NewCore(c *CoreConfig) (*Core, error) { 83 return &Core{ 84 appfile: c.Appfile.File, 85 appfileCompiled: c.Appfile, 86 apps: c.Apps, 87 dir: c.Directory, 88 infras: c.Infrastructures, 89 foundationMap: c.Foundations, 90 dataDir: c.DataDir, 91 localDir: c.LocalDir, 92 compileDir: c.CompileDir, 93 ui: c.Ui, 94 }, nil 95 } 96 97 // App returns the app implementation and context for this configured Core. 98 // 99 // If App implements io.Closer, it is up to the caller to call Close on it. 100 func (c *Core) App() (app.App, *app.Context, error) { 101 root, err := c.appfileCompiled.Graph.Root() 102 if err != nil { 103 return nil, nil, err 104 } 105 rootCtx, err := c.appContext(root.(*appfile.CompiledGraphVertex).File) 106 if err != nil { 107 return nil, nil, fmt.Errorf( 108 "Error loading App: %s", err) 109 } 110 rootApp, err := c.app(rootCtx) 111 if err != nil { 112 return nil, nil, fmt.Errorf( 113 "Error loading App: %s", err) 114 } 115 116 return rootApp, rootCtx, nil 117 } 118 119 // Compile takes the Appfile and compiles all the resulting data. 120 func (c *Core) Compile() error { 121 // md stores the metadata about the compilation. This is only written 122 // on a successful compile. 123 var md CompileMetadata 124 125 // Get the infra implementation for this 126 infra, infraCtx, err := c.infra() 127 if err != nil { 128 return err 129 } 130 defer maybeClose(infra) 131 132 // Get all the foundation implementations (which are tied as singletons 133 // to the infrastructure). 134 foundations, foundationCtxs, err := c.foundations() 135 if err != nil { 136 return err 137 } 138 for _, f := range foundations { 139 defer maybeClose(f) 140 } 141 142 // Delete the prior output directory 143 log.Printf("[INFO] deleting prior compilation contents: %s", c.compileDir) 144 if err := os.RemoveAll(c.compileDir); err != nil { 145 return err 146 } 147 148 // Reset the metadata cache so we don't have that 149 c.resetCompileMetadata() 150 151 // Compile the infrastructure for our application 152 log.Printf("[INFO] running infra compile...") 153 c.ui.Message("Compiling infra...") 154 infraResult, err := infra.Compile(infraCtx) 155 if err != nil { 156 return err 157 } 158 md.Infra = infraResult 159 160 // Compile the foundation (not tied to any app). This compilation 161 // of the foundation is used for `otto infra` to set everything up. 162 log.Printf("[INFO] running foundation compilations") 163 md.Foundations = make(map[string]*foundation.CompileResult, len(foundations)) 164 for i, f := range foundations { 165 ctx := foundationCtxs[i] 166 c.ui.Message(fmt.Sprintf( 167 "Compiling foundation: %s", ctx.Tuple.Type)) 168 result, err := f.Compile(ctx) 169 if err != nil { 170 return err 171 } 172 173 md.Foundations[ctx.Tuple.Type] = result 174 } 175 176 // Walk through the dependencies and compile all of them. 177 // We have to compile every dependency for dev building. 178 var mdLock sync.Mutex 179 md.AppDeps = make(map[string]*app.CompileResult) 180 err = c.walk(func(app app.App, ctx *app.Context, root bool) error { 181 if !root { 182 c.ui.Header(fmt.Sprintf( 183 "Compiling dependency '%s'...", 184 ctx.Appfile.Application.Name)) 185 } else { 186 c.ui.Header(fmt.Sprintf( 187 "Compiling main application...")) 188 } 189 190 // If this is the root, we set the dev dep fragments. 191 if root { 192 // We grab the lock just in case although if we're the 193 // root this should be serialized. 194 mdLock.Lock() 195 ctx.DevDepFragments = make([]string, 0, len(md.AppDeps)) 196 for _, result := range md.AppDeps { 197 if result.DevDepFragmentPath != "" { 198 ctx.DevDepFragments = append( 199 ctx.DevDepFragments, result.DevDepFragmentPath) 200 } 201 } 202 mdLock.Unlock() 203 } 204 205 // Compile the foundations for this app 206 subdirs := []string{"app-dev", "app-dev-dep", "app-build", "app-deploy"} 207 for i, f := range foundations { 208 fCtx := foundationCtxs[i] 209 fCtx.Dir = ctx.FoundationDirs[i] 210 211 if _, err := f.Compile(fCtx); err != nil { 212 return err 213 } 214 215 // Make sure the subdirs exist 216 for _, dir := range subdirs { 217 if err := os.MkdirAll(filepath.Join(fCtx.Dir, dir), 0755); err != nil { 218 return err 219 } 220 } 221 } 222 223 // Compile! 224 result, err := app.Compile(ctx) 225 if err != nil { 226 return err 227 } 228 229 // Compile the foundations for this app 230 for i, f := range foundations { 231 fCtx := foundationCtxs[i] 232 fCtx.Dir = ctx.FoundationDirs[i] 233 if result != nil { 234 fCtx.AppConfig = &result.FoundationConfig 235 } 236 237 if _, err := f.Compile(fCtx); err != nil { 238 return err 239 } 240 241 // Make sure the subdirs exist 242 for _, dir := range subdirs { 243 if err := os.MkdirAll(filepath.Join(fCtx.Dir, dir), 0755); err != nil { 244 return err 245 } 246 } 247 } 248 249 // Store the compilation result in the metadata 250 mdLock.Lock() 251 defer mdLock.Unlock() 252 253 if root { 254 md.App = result 255 } else { 256 // Don't store the result if its nil because it is pointless 257 if result != nil { 258 md.AppDeps[ctx.Appfile.ID] = result 259 } 260 } 261 262 return nil 263 }) 264 if err != nil { 265 return err 266 } 267 268 // We had no compilation errors! Let's save the metadata 269 return c.saveCompileMetadata(&md) 270 } 271 272 func (c *Core) walk(f func(app.App, *app.Context, bool) error) error { 273 root, err := c.appfileCompiled.Graph.Root() 274 if err != nil { 275 return fmt.Errorf( 276 "Error loading app: %s", err) 277 } 278 279 // Walk the appfile graph. 280 var stop int32 = 0 281 return c.appfileCompiled.Graph.Walk(func(raw dag.Vertex) (err error) { 282 // If we're told to stop (something else had an error), then stop early. 283 // Graphs walks by default will complete all disjoint parts of the 284 // graph before failing, but Otto doesn't have to do that. 285 if atomic.LoadInt32(&stop) != 0 { 286 return nil 287 } 288 289 // If we exit with an error, then mark the stop atomic 290 defer func() { 291 if err != nil { 292 atomic.StoreInt32(&stop, 1) 293 } 294 }() 295 296 // Convert to the rich vertex type so that we can access data 297 v := raw.(*appfile.CompiledGraphVertex) 298 299 // Do some logging to help ourselves out 300 log.Printf("[DEBUG] core walking app: %s", v.File.Application.Name) 301 302 // Get the context and app for this appfile 303 appCtx, err := c.appContext(v.File) 304 if err != nil { 305 return fmt.Errorf( 306 "Error loading Appfile for '%s': %s", 307 dag.VertexName(raw), err) 308 } 309 app, err := c.app(appCtx) 310 if err != nil { 311 return fmt.Errorf( 312 "Error loading App implementation for '%s': %s", 313 dag.VertexName(raw), err) 314 } 315 defer maybeClose(app) 316 317 // Call our callback 318 return f(app, appCtx, raw == root) 319 }) 320 } 321 322 // Build builds the deployable artifact for the currently compiled 323 // Appfile. 324 func (c *Core) Build() error { 325 // Get the infra implementation for this 326 infra, infraCtx, err := c.infra() 327 if err != nil { 328 return err 329 } 330 if err := c.creds(infra, infraCtx); err != nil { 331 return err 332 } 333 defer maybeClose(infra) 334 335 // We only use the root application for this task, upstream dependencies 336 // don't have an effect on the build process. 337 root, err := c.appfileCompiled.Graph.Root() 338 if err != nil { 339 return err 340 } 341 rootCtx, err := c.appContext(root.(*appfile.CompiledGraphVertex).File) 342 if err != nil { 343 return fmt.Errorf( 344 "Error loading App: %s", err) 345 } 346 rootApp, err := c.app(rootCtx) 347 if err != nil { 348 return fmt.Errorf( 349 "Error loading App: %s", err) 350 } 351 defer maybeClose(rootApp) 352 353 // Just update our shared data so we get the creds 354 rootCtx.Shared.InfraCreds = infraCtx.Shared.InfraCreds 355 356 return rootApp.Build(rootCtx) 357 } 358 359 // Deploy deploys the application. 360 // 361 // Deploy supports subactions, which can be specified with action and args. 362 // Action can be "" to get the default deploy behavior. 363 func (c *Core) Deploy(action string, args []string) error { 364 // Get the infra implementation for this 365 infra, infraCtx, err := c.infra() 366 if err != nil { 367 return err 368 } 369 defer maybeClose(infra) 370 371 // Special case: don't try to fetch creds during `help` or `info` 372 if action != "help" && action != "info" { 373 if err := c.creds(infra, infraCtx); err != nil { 374 return err 375 } 376 } 377 378 // TODO: Verify that upstream dependencies are deployed 379 380 // We only use the root application for this task, upstream dependencies 381 // don't have an effect on the build process. 382 root, err := c.appfileCompiled.Graph.Root() 383 if err != nil { 384 return err 385 } 386 rootCtx, err := c.appContext(root.(*appfile.CompiledGraphVertex).File) 387 if err != nil { 388 return fmt.Errorf( 389 "Error loading App: %s", err) 390 } 391 rootApp, err := c.app(rootCtx) 392 if err != nil { 393 return fmt.Errorf( 394 "Error loading App: %s", err) 395 } 396 defer maybeClose(rootApp) 397 398 // Update our shared data so we get the creds 399 rootCtx.Shared.InfraCreds = infraCtx.Shared.InfraCreds 400 401 // Pass through the requested action 402 rootCtx.Action = action 403 rootCtx.ActionArgs = args 404 405 return rootApp.Deploy(rootCtx) 406 } 407 408 // Dev starts a dev environment for the current application. For destroying 409 // and other tasks against the dev environment, use the generic `Execute` 410 // method. 411 func (c *Core) Dev() error { 412 // We need to get the root data separately since we need that for 413 // all the function calls into the dependencies. 414 root, err := c.appfileCompiled.Graph.Root() 415 if err != nil { 416 return err 417 } 418 rootCtx, err := c.appContext(root.(*appfile.CompiledGraphVertex).File) 419 if err != nil { 420 return fmt.Errorf( 421 "Error loading App: %s", err) 422 } 423 rootApp, err := c.app(rootCtx) 424 if err != nil { 425 return fmt.Errorf( 426 "Error loading App: %s", err) 427 } 428 defer maybeClose(rootApp) 429 430 // Go through all the dependencies and build their immutable 431 // dev environment pieces for the final configuration. 432 err = c.walk(func(appImpl app.App, ctx *app.Context, root bool) error { 433 // If it is the root, we just return and do nothing else since 434 // the root is a special case where we're building the actual 435 // dev environment. 436 if root { 437 return nil 438 } 439 440 // Get the path to where we'd cache the dependency if we have 441 // cached it... 442 cachePath := filepath.Join(ctx.CacheDir, "dev-dep.json") 443 444 // Check if we've cached this. If so, then use the cache. 445 if _, err := app.ReadDevDep(cachePath); err == nil { 446 ctx.Ui.Header(fmt.Sprintf( 447 "Using cached dev dependency for '%s'", 448 ctx.Appfile.Application.Name)) 449 return nil 450 } 451 452 // Copy the root context so it isn't modified by the call below 453 rootCtxCopy := *rootCtx 454 455 // Build the development dependency 456 log.Printf( 457 "[DEBUG] core: calling DevDep for '%s'", 458 ctx.Appfile.Application.Name) 459 dep, err := appImpl.DevDep(&rootCtxCopy, ctx) 460 if err != nil { 461 return fmt.Errorf( 462 "Error building dependency for dev '%s': %s", 463 ctx.Appfile.Application.Name, 464 err) 465 } 466 467 // If we have a dependency with files, then verify the files 468 // and store it in our cache directory so we can retrieve it 469 // later. 470 if dep != nil && len(dep.Files) > 0 { 471 if err := dep.RelFiles(ctx.CacheDir); err != nil { 472 return fmt.Errorf( 473 "Error caching dependency for dev '%s': %s", 474 ctx.Appfile.Application.Name, 475 err) 476 } 477 478 if err := app.WriteDevDep(cachePath, dep); err != nil { 479 return fmt.Errorf( 480 "Error caching dependency for dev '%s': %s", 481 ctx.Appfile.Application.Name, 482 err) 483 } 484 } 485 486 return nil 487 }) 488 if err != nil { 489 return err 490 } 491 492 // All the development dependencies are built/loaded. We now have 493 // everything we need to build the complete development environment. 494 log.Printf( 495 "[DEBUG] core: calling Dev for root app '%s'", 496 rootCtx.Appfile.Application.Name) 497 return rootApp.Dev(rootCtx) 498 } 499 500 // Infra manages the infrastructure for this Appfile. 501 // 502 // Infra supports subactions, which can be specified with action and args. 503 // Infra recognizes two special actions: "" (blank string) and "destroy". 504 // The former expects to create or update the complete infrastructure, 505 // and the latter will destroy the infrastructure. 506 func (c *Core) Infra(action string, args []string) error { 507 // Get the infra implementation for this 508 infra, infraCtx, err := c.infra() 509 if err != nil { 510 return err 511 } 512 if action == "" || action == "destroy" { 513 if err := c.creds(infra, infraCtx); err != nil { 514 return err 515 } 516 } 517 defer maybeClose(infra) 518 519 // Set the action and action args 520 infraCtx.Action = action 521 infraCtx.ActionArgs = args 522 523 // If we need the foundations, then get them 524 var foundations []foundation.Foundation 525 var foundationCtxs []*foundation.Context 526 if action == "" || action == "destroy" { 527 foundations, foundationCtxs, err = c.foundations() 528 if err != nil { 529 return err 530 } 531 } 532 for _, f := range foundations { 533 defer maybeClose(f) 534 } 535 536 // If we're doing anything other than destroying, then 537 // run the execution now. 538 if action != "destroy" { 539 if err := infra.Execute(infraCtx); err != nil { 540 return err 541 } 542 } 543 544 // If we have any foundations, we now run their infra deployment. 545 // This should only ever execute if action is to deploy or destroy, 546 // since those are the only cases that we load foundations. 547 for i, f := range foundations { 548 ctx := foundationCtxs[i] 549 ctx.Action = action 550 ctx.ActionArgs = args 551 ctx.InfraCreds = infraCtx.InfraCreds 552 553 log.Printf( 554 "[INFO] infra action '%s' on foundation '%s'", 555 action, ctx.Tuple.Type) 556 557 switch action { 558 case "": 559 c.ui.Header(fmt.Sprintf( 560 "Building infrastructure for foundation: %s", 561 ctx.Tuple.Type)) 562 case "destroy": 563 c.ui.Header(fmt.Sprintf( 564 "Destroying infrastructure for foundation: %s", 565 ctx.Tuple.Type)) 566 } 567 568 if err := f.Infra(ctx); err != nil { 569 return err 570 } 571 } 572 573 // If the action is destroy, we run the infrastructure execution 574 // here. We mirror creation above since in the destruction case 575 // we need to first destroy all applications and foundations that 576 // are using this infra. 577 if action == "destroy" { 578 if err := infra.Execute(infraCtx); err != nil { 579 return err 580 } 581 } 582 583 // Output the right thing 584 switch action { 585 case "": 586 infraCtx.Ui.Header("[green]Infrastructure successfully created!") 587 infraCtx.Ui.Message( 588 "[green]The infrastructure necessary to deploy this application\n" + 589 "is now available. You can now deploy using `otto deploy`.") 590 case "destroy": 591 infraCtx.Ui.Header("[green]Infrastructure successfully destroyed!") 592 infraCtx.Ui.Message( 593 "[green]The infrastructure necessary to run this application and\n" + 594 "all other applications in this project has been destroyed.") 595 } 596 597 return nil 598 } 599 600 // Status outputs to the UI the status of all the stages of this application. 601 func (c *Core) Status() error { 602 // Start loading the status info in a goroutine 603 statusCh := make(chan *statusInfo, 1) 604 go c.statusInfo(statusCh) 605 606 // Wait for the status. If this takes longer than a certain amount 607 // of time then we show a loading message. 608 var status *statusInfo 609 select { 610 case status = <-statusCh: 611 case <-time.After(150 * time.Millisecond): 612 c.ui.Header("Loading status...") 613 c.ui.Message(fmt.Sprintf( 614 "Depending on your configured directory backend, this may require\n" + 615 "network operations and can take some time. On a typical broadband\n" + 616 "connection, this shouldn't take more than a few seconds.")) 617 } 618 if status == nil { 619 status = <-statusCh 620 } 621 622 // Create the status texts 623 devStatus := "[reset]NOT CREATED" 624 if status.Dev.IsReady() { 625 devStatus = "[green]CREATED" 626 } 627 buildStatus := "[reset]NOT BUILT" 628 if status.Build != nil { 629 buildStatus = "[green]BUILD READY" 630 } 631 deployStatus := "[reset]NOT DEPLOYED" 632 if status.Deploy.IsDeployed() { 633 deployStatus = "[green]DEPLOYED" 634 } else if status.Deploy.IsFailed() { 635 deployStatus = "[reset]DEPLOY FAILED" 636 } 637 infraStatus := "[reset]NOT CREATED" 638 if status.Infra.IsReady() { 639 infraStatus = "[green]READY" 640 } else if status.Infra.IsPartial() { 641 infraStatus = "[yellow]PARTIAL" 642 } 643 644 // Get the active infra 645 infra := c.appfile.ActiveInfrastructure() 646 647 c.ui.Header("App Info") 648 c.ui.Message(fmt.Sprintf( 649 "Application: %s (%s)", 650 c.appfile.Application.Name, c.appfile.Application.Type)) 651 c.ui.Message(fmt.Sprintf("Project: %s", c.appfile.Project.Name)) 652 c.ui.Message(fmt.Sprintf( 653 "Infrastructure: %s (%s)", 654 infra.Type, infra.Flavor)) 655 656 c.ui.Header("Component Status") 657 c.ui.Message(fmt.Sprintf("Dev environment: %s", devStatus)) 658 c.ui.Message(fmt.Sprintf("Infra: %s", infraStatus)) 659 c.ui.Message(fmt.Sprintf("Build: %s", buildStatus)) 660 c.ui.Message(fmt.Sprintf("Deploy: %s", deployStatus)) 661 662 return nil 663 } 664 665 // Execute executes the given task for this Appfile. 666 func (c *Core) Execute(opts *ExecuteOpts) error { 667 switch opts.Task { 668 case ExecuteTaskDev: 669 return c.executeApp(opts) 670 default: 671 return fmt.Errorf("unknown task: %s", opts.Task) 672 } 673 } 674 675 // creds reads the credentials if we have them, or queries the user 676 // for infrastructure credentials using the infrastructure if we 677 // don't have them. 678 func (c *Core) creds( 679 infra infrastructure.Infrastructure, 680 infraCtx *infrastructure.Context) error { 681 // Output to the user some information about what is about to 682 // happen here... 683 infraCtx.Ui.Header(fmt.Sprintf( 684 "Detecting infrastructure credentials for: %s (%s)", 685 infraCtx.Infra.Name, infraCtx.Infra.Type)) 686 687 // The path to where we put the encrypted creds 688 path := filepath.Join(c.dataDir, "cache", "creds", infraCtx.Infra.Name) 689 690 // Determine whether we believe the creds exist already or not 691 var exists bool 692 if _, err := os.Stat(path); err == nil { 693 exists = true 694 } else { 695 if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { 696 return err 697 } 698 } 699 700 var creds map[string]string 701 if exists { 702 infraCtx.Ui.Message( 703 "Cached and encrypted infrastructure credentials found.\n" + 704 "Otto will now ask you for the password to decrypt these\n" + 705 "credentials.\n\n") 706 707 // If they exist, ask for the password 708 value, err := infraCtx.Ui.Input(&ui.InputOpts{ 709 Id: "creds_password", 710 Query: "Encrypted Credentials Password", 711 Description: strings.TrimSpace(credsQueryPassExists), 712 Hide: true, 713 EnvVars: []string{"OTTO_CREDS_PASSWORD"}, 714 }) 715 if err != nil { 716 return err 717 } 718 719 // If the password is not blank, then just read the credentials 720 if value != "" { 721 plaintext, err := cryptRead(path, value) 722 if err == nil { 723 err = json.Unmarshal(plaintext, &creds) 724 } 725 if err != nil { 726 return fmt.Errorf( 727 "error reading encrypted credentials: %s\n\n"+ 728 "If this error persists, you can force Otto to ask for credentials\n"+ 729 "again by inputting the empty password as the password.", 730 err) 731 } 732 } 733 } 734 735 // If we don't have creds, then we need to query the user via 736 // the infrastructure implementation. 737 if creds == nil { 738 infraCtx.Ui.Message( 739 "Existing infrastructure credentials were not found! Otto will\n" + 740 "now ask you for infrastructure credentials. These will be encrypted\n" + 741 "and saved on disk so this doesn't need to be repeated.\n\n" + 742 "IMPORTANT: If you're re-entering new credentials, make sure the\n" + 743 "credentials are for the same account, otherwise you may lose\n" + 744 "access to your existing infrastructure Otto set up.\n\n") 745 746 var err error 747 creds, err = infra.Creds(infraCtx) 748 if err != nil { 749 return err 750 } 751 752 // Now that we have the credentials, we need to ask for the 753 // password to encrypt and store them. 754 var password string 755 for password == "" { 756 password, err = infraCtx.Ui.Input(&ui.InputOpts{ 757 Id: "creds_password", 758 Query: "Password for Encrypting Credentials", 759 Description: strings.TrimSpace(credsQueryPassNew), 760 Hide: true, 761 EnvVars: []string{"OTTO_CREDS_PASSWORD"}, 762 }) 763 if err != nil { 764 return err 765 } 766 } 767 768 // With the password, encrypt and write the data 769 plaintext, err := json.Marshal(creds) 770 if err != nil { 771 // creds is a map[string]string, so this shouldn't ever fail 772 panic(err) 773 } 774 775 if err := cryptWrite(path, password, plaintext); err != nil { 776 return fmt.Errorf( 777 "error writing encrypted credentials: %s", err) 778 } 779 } 780 781 // Set the credentials 782 infraCtx.InfraCreds = creds 783 784 // Let the infrastructure do whatever it likes to verify that the credentials 785 // are good, so we can fail fast in case there's a problem. 786 if err := infra.VerifyCreds(infraCtx); err != nil { 787 return err 788 } 789 790 return nil 791 } 792 793 func (c *Core) executeApp(opts *ExecuteOpts) error { 794 // Get the infra implementation for this 795 appCtx, err := c.appContext(c.appfile) 796 if err != nil { 797 return err 798 } 799 app, err := c.app(appCtx) 800 if err != nil { 801 return err 802 } 803 defer maybeClose(app) 804 805 // Set the action and action args 806 appCtx.Action = opts.Action 807 appCtx.ActionArgs = opts.Args 808 809 // Build the infrastructure compilation context 810 switch opts.Task { 811 case ExecuteTaskDev: 812 return app.Dev(appCtx) 813 default: 814 panic(fmt.Sprintf("uknown task: %s", opts.Task)) 815 } 816 } 817 818 func (c *Core) appContext(f *appfile.File) (*app.Context, error) { 819 // Whether or not this is the root Appfile 820 root := f.ID == c.appfile.ID 821 822 // We need the configuration for the active infrastructure 823 // so that we can build the tuple below 824 config := f.ActiveInfrastructure() 825 if config == nil { 826 return nil, fmt.Errorf( 827 "infrastructure not found in appfile: %s", 828 f.Project.Infrastructure) 829 } 830 831 // The tuple we're looking for is the application type, the 832 // infrastructure type, and the infrastructure flavor. Build that 833 // tuple. 834 tuple := app.Tuple{ 835 App: f.Application.Type, 836 Infra: config.Type, 837 InfraFlavor: config.Flavor, 838 } 839 840 // The output directory for data. This is either the main app so 841 // it goes directly into "app" or it is a dependency and goes into 842 // a dep folder. 843 outputDir := filepath.Join(c.compileDir, "app") 844 if !root { 845 outputDir = filepath.Join( 846 c.compileDir, fmt.Sprintf("dep-%s", f.ID)) 847 } 848 849 // The cache directory for this app 850 cacheDir := filepath.Join(c.dataDir, "cache", f.ID) 851 if err := os.MkdirAll(cacheDir, 0755); err != nil { 852 return nil, fmt.Errorf( 853 "error making cache directory '%s': %s", 854 cacheDir, err) 855 } 856 857 // The directory for global data 858 globalDir := filepath.Join(c.dataDir, "global-data") 859 if err := os.MkdirAll(globalDir, 0755); err != nil { 860 return nil, fmt.Errorf( 861 "error making global data directory '%s': %s", 862 globalDir, err) 863 } 864 865 // Build the contexts for the foundations. We use this 866 // to also compile the list of foundation dirs. 867 foundationDirs := make([]string, len(config.Foundations)) 868 for i, f := range config.Foundations { 869 foundationDirs[i] = filepath.Join( 870 outputDir, fmt.Sprintf("foundation-%s", f.Name)) 871 } 872 873 // Get the dev IP address 874 ipDB := &localaddr.CachedDB{ 875 DB: &localaddr.DB{Path: filepath.Join(c.dataDir, "ip.db")}, 876 CachePath: filepath.Join(c.localDir, "dev_ip"), 877 } 878 ip, err := ipDB.IP() 879 if err != nil { 880 return nil, fmt.Errorf( 881 "Error retrieving dev IP address: %s", err) 882 } 883 884 // Get the metadata 885 var compileResult *app.CompileResult 886 md, err := c.compileMetadata() 887 if err != nil { 888 return nil, fmt.Errorf( 889 "Error loading compilation metadata: %s", err) 890 } 891 if md != nil { 892 if root { 893 compileResult = md.App 894 } else { 895 compileResult = md.AppDeps[f.ID] 896 } 897 } 898 899 // Get the customizations. If we don't have any at all, we fast-path 900 // this by doing nothing. If we do, we have to make a deep copy in 901 // order to prune out the irrelevant ones. 902 if f.Customization != nil && len(f.Customization.Raw) > 0 { 903 // Perform a deep copy of the Appfile so we can modify it 904 fRaw, err := copystructure.Copy(f) 905 if err != nil { 906 return nil, err 907 } 908 f = fRaw.(*appfile.File) 909 910 // Get the app-only customizations and set it on the Appfile 911 cs := f.Customization.Filter("app") 912 f.Customization = &appfile.CustomizationSet{Raw: cs} 913 } 914 915 return &app.Context{ 916 CompileResult: compileResult, 917 Dir: outputDir, 918 CacheDir: cacheDir, 919 LocalDir: c.localDir, 920 GlobalDir: globalDir, 921 Tuple: tuple, 922 Application: f.Application, 923 DevIPAddress: ip.String(), 924 Shared: context.Shared{ 925 Appfile: f, 926 FoundationDirs: foundationDirs, 927 InstallDir: filepath.Join(c.dataDir, "binaries"), 928 Directory: c.dir, 929 Ui: c.ui, 930 }, 931 }, nil 932 } 933 934 func (c *Core) app(ctx *app.Context) (app.App, error) { 935 log.Printf("[INFO] Loading app implementation for Tuple: %s", ctx.Tuple) 936 937 // Look for the app impl. factory 938 f := app.TupleMap(c.apps).Lookup(ctx.Tuple) 939 if f == nil { 940 return nil, fmt.Errorf( 941 "app implementation for tuple not found: %s", ctx.Tuple) 942 } 943 944 // Start the impl. 945 result, err := f() 946 if err != nil { 947 return nil, fmt.Errorf( 948 "app failed to start properly: %s", err) 949 } 950 951 return result, nil 952 } 953 954 func (c *Core) infra() (infrastructure.Infrastructure, *infrastructure.Context, error) { 955 // Get the infrastructure configuration 956 config := c.appfile.ActiveInfrastructure() 957 if config == nil { 958 return nil, nil, fmt.Errorf( 959 "infrastructure not found in appfile: %s", 960 c.appfile.Project.Infrastructure) 961 } 962 963 // Get the infrastructure factory 964 f, ok := c.infras[config.Type] 965 if !ok { 966 return nil, nil, fmt.Errorf( 967 "infrastructure type not supported: %s", 968 config.Type) 969 } 970 971 // Start the infrastructure implementation 972 infra, err := f() 973 if err != nil { 974 return nil, nil, err 975 } 976 977 // The output directory for data 978 outputDir := filepath.Join( 979 c.compileDir, fmt.Sprintf("infra-%s", c.appfile.Project.Infrastructure)) 980 981 // Build the context 982 return infra, &infrastructure.Context{ 983 Dir: outputDir, 984 Infra: config, 985 Shared: context.Shared{ 986 Appfile: c.appfile, 987 InstallDir: filepath.Join(c.dataDir, "binaries"), 988 Directory: c.dir, 989 Ui: c.ui, 990 }, 991 }, nil 992 } 993 994 func (c *Core) foundations() ([]foundation.Foundation, []*foundation.Context, error) { 995 // Get the infrastructure configuration 996 config := c.appfile.ActiveInfrastructure() 997 if config == nil { 998 return nil, nil, fmt.Errorf( 999 "infrastructure not found in appfile: %s", 1000 c.appfile.Project.Infrastructure) 1001 } 1002 1003 // If there are no foundations, return nothing. 1004 if len(config.Foundations) == 0 { 1005 return nil, nil, nil 1006 } 1007 1008 // Create the arrays for our list 1009 fs := make([]foundation.Foundation, 0, len(config.Foundations)) 1010 ctxs := make([]*foundation.Context, 0, cap(fs)) 1011 for _, f := range config.Foundations { 1012 // The tuple we're looking for is the foundation type, the 1013 // infrastructure type, and the infrastructure flavor. Build that 1014 // tuple. 1015 tuple := foundation.Tuple{ 1016 Type: f.Name, 1017 Infra: config.Type, 1018 InfraFlavor: config.Flavor, 1019 } 1020 1021 // Look for the matching foundation 1022 fun := foundation.TupleMap(c.foundationMap).Lookup(tuple) 1023 if fun == nil { 1024 return nil, nil, fmt.Errorf( 1025 "foundation implementation for tuple not found: %s", 1026 tuple) 1027 } 1028 1029 // Instantiate the implementation 1030 impl, err := fun() 1031 if err != nil { 1032 return nil, nil, err 1033 } 1034 1035 // The output directory for data 1036 outputDir := filepath.Join( 1037 c.compileDir, fmt.Sprintf("foundation-%s", f.Name)) 1038 1039 // Build the context 1040 ctx := &foundation.Context{ 1041 Config: f.Config, 1042 Dir: outputDir, 1043 Tuple: tuple, 1044 Shared: context.Shared{ 1045 Appfile: c.appfile, 1046 InstallDir: filepath.Join(c.dataDir, "binaries"), 1047 Directory: c.dir, 1048 Ui: c.ui, 1049 }, 1050 } 1051 1052 // Add to our results 1053 fs = append(fs, impl) 1054 ctxs = append(ctxs, ctx) 1055 } 1056 1057 return fs, ctxs, nil 1058 } 1059 1060 const credsQueryPassExists = ` 1061 Infrastructure credentials are required for this operation. Otto found 1062 saved credentials that are password protected. Please enter the password 1063 to decrypt these credentials. You may also just hit <enter> and leave 1064 the password blank to force Otto to ask for the credentials again. 1065 ` 1066 1067 const credsQueryPassNew = ` 1068 This password will be used to encrypt and save the credentials so they 1069 don't need to be repeated multiple times. 1070 `