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  `