github.com/bhameyie/otto@v0.2.1-0.20160406174117-16052efa52ec/command/compile.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"path/filepath"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/otto/appfile"
    12  	"github.com/hashicorp/otto/appfile/detect"
    13  	appfileLoad "github.com/hashicorp/otto/appfile/load"
    14  	"github.com/hashicorp/otto/ui"
    15  )
    16  
    17  // CompileCommand is the command that is responsible for "compiling" the
    18  // Appfile into a set of data that is used by the other commands for
    19  // execution.
    20  type CompileCommand struct {
    21  	Meta
    22  
    23  	// Detectors to use for compilation. These will be overridden by any
    24  	// plugins.
    25  	Detectors []*detect.Detector
    26  }
    27  
    28  func (c *CompileCommand) Run(args []string) int {
    29  	var flagAppfile string
    30  	fs := c.FlagSet("compile", FlagSetNone)
    31  	fs.Usage = func() { c.Ui.Error(c.Help()) }
    32  	fs.StringVar(&flagAppfile, "appfile", "", "")
    33  	if err := fs.Parse(args); err != nil {
    34  		return 1
    35  	}
    36  
    37  	// Load all the plugins, we use all the plugins for compilation only
    38  	// so we have full access to detectors and app types.
    39  	pluginMgr, err := c.PluginManager()
    40  	if err != nil {
    41  		c.Ui.Error(fmt.Sprintf(
    42  			"Error initializing plugin manager: %s", err))
    43  		return 1
    44  	}
    45  	if err := pluginMgr.LoadAll(); err != nil {
    46  		c.Ui.Error(fmt.Sprintf(
    47  			"Error loading plugins: %s", err))
    48  		return 1
    49  	}
    50  
    51  	// Load the detectors from the plugins
    52  	detectors := make([]*detect.Detector, 0, 20)
    53  	detectors = append(detectors, c.Detectors...)
    54  	for _, p := range pluginMgr.Plugins() {
    55  		detectors = append(detectors, p.AppMeta.Detectors...)
    56  	}
    57  
    58  	// Sort the detectors so we try highest priority first
    59  	sort.Sort(detect.DetectorList(detectors))
    60  
    61  	// Parse the detectors
    62  	dataDir, err := c.DataDir()
    63  	if err != nil {
    64  		c.Ui.Error(err.Error())
    65  		return 1
    66  	}
    67  	detectorDir := filepath.Join(dataDir, DefaultLocalDataDetectorDir)
    68  	log.Printf("[DEBUG] loading detectors from: %s", detectorDir)
    69  	detectConfig, err := detect.ParseDir(detectorDir)
    70  	if err != nil {
    71  		c.Ui.Error(err.Error())
    72  		return 1
    73  	}
    74  	if detectConfig == nil {
    75  		detectConfig = &detect.Config{}
    76  	}
    77  	err = detectConfig.Merge(&detect.Config{Detectors: detectors})
    78  	if err != nil {
    79  		c.Ui.Error(err.Error())
    80  		return 1
    81  	}
    82  
    83  	// Load a UI
    84  	ui := c.OttoUi()
    85  	ui.Header("Loading Appfile...")
    86  
    87  	app, appPath, err := loadAppfile(flagAppfile)
    88  	if err != nil {
    89  		c.Ui.Error(err.Error())
    90  		return 1
    91  	}
    92  
    93  	// Tell the user what is happening if they have no Appfile
    94  	if app == nil {
    95  		ui.Header("No Appfile found! Detecting project information...")
    96  		ui.Message(fmt.Sprintf(
    97  			"No Appfile was found. If there is no Appfile, Otto will do its best\n" +
    98  				"to detect the type of application this is and set reasonable defaults.\n" +
    99  				"This is a good way to get started with Otto, but over time we recommend\n" +
   100  				"writing a real Appfile since this will allow more complex customizations,\n" +
   101  				"the ability to reference dependencies, versioning, and more."))
   102  	}
   103  
   104  	// Build the appfile compiler
   105  	var loader appfileLoad.Loader
   106  	compiler, err := appfile.NewCompiler(&appfile.CompileOpts{
   107  		Dir: filepath.Join(
   108  			appPath, DefaultOutputDir, DefaultOutputDirCompiledAppfile),
   109  		Loader:   loader.Load,
   110  		Callback: c.compileCallback(ui),
   111  	})
   112  	if err != nil {
   113  		c.Ui.Error(fmt.Sprintf(
   114  			"Error initializing Appfile compiler: %s", err))
   115  		return 1
   116  	}
   117  
   118  	// Create the Appfile loader
   119  	if err := pluginMgr.ConfigureCore(c.CoreConfig); err != nil {
   120  		panic(err)
   121  	}
   122  	loader.Detector = detectConfig
   123  	loader.Compiler = compiler
   124  	loader.Apps = c.CoreConfig.Apps
   125  
   126  	// Load the complete Appfile
   127  	app, err = loader.Load(app, appPath)
   128  	if err != nil {
   129  		c.Ui.Error(err.Error())
   130  		return 1
   131  	}
   132  
   133  	// If there was no loaded Appfile and we don't have an application
   134  	// type then we weren't able to detect the type. Error.
   135  	if app == nil || app.Application.Type == "" {
   136  		c.Ui.Error(strings.TrimSpace(errCantDetectType))
   137  		return 1
   138  	}
   139  
   140  	// Compile the Appfile
   141  	ui.Header("Fetching all Appfile dependencies...")
   142  	capp, err := compiler.Compile(app)
   143  	if err != nil {
   144  		c.Ui.Error(fmt.Sprintf(
   145  			"Error compiling Appfile: %s", err))
   146  		return 1
   147  	}
   148  
   149  	// Get a core
   150  	core, err := c.Core(capp)
   151  	if err != nil {
   152  		c.Ui.Error(fmt.Sprintf(
   153  			"Error loading core: %s", err))
   154  		return 1
   155  	}
   156  
   157  	// Get the active infrastructure just for UI reasons
   158  	infra := app.ActiveInfrastructure()
   159  
   160  	// Before the compilation, output to the user what is going on
   161  	ui.Header("Compiling...")
   162  	ui.Message(fmt.Sprintf(
   163  		"Application:    %s (%s)",
   164  		app.Application.Name,
   165  		app.Application.Type))
   166  	ui.Message(fmt.Sprintf("Project:        %s", app.Project.Name))
   167  	ui.Message(fmt.Sprintf(
   168  		"Infrastructure: %s (%s)",
   169  		infra.Type,
   170  		infra.Flavor))
   171  	ui.Message("")
   172  
   173  	// Compile!
   174  	if err := core.Compile(); err != nil {
   175  		c.Ui.Error(fmt.Sprintf(
   176  			"Error compiling: %s", err))
   177  		return 1
   178  	}
   179  
   180  	// Store the used plugins so later calls don't have to load everything
   181  	usedPath, err := c.AppfilePluginsPath(capp)
   182  	if err != nil {
   183  		c.Ui.Error(fmt.Sprintf(
   184  			"Error compiling: %s", err))
   185  		return 1
   186  	}
   187  	if err := pluginMgr.StoreUsed(usedPath); err != nil {
   188  		c.Ui.Error(fmt.Sprintf(
   189  			"Error compiling plugin data: %s", err))
   190  		return 1
   191  	}
   192  
   193  	// Success!
   194  	ui.Header("[green]Compilation success!")
   195  	ui.Message(fmt.Sprintf(
   196  		"[green]This means that Otto is now ready to start a development environment,\n" +
   197  			"deploy this application, build the supporting infrastructure, and\n" +
   198  			"more. See the help for more information.\n\n" +
   199  			"Supporting files to enable Otto to manage your application from\n" +
   200  			"development to deployment have been placed in the output directory.\n" +
   201  			"These files can be manually inspected to determine what Otto will do."))
   202  
   203  	return 0
   204  }
   205  
   206  func (c *CompileCommand) Synopsis() string {
   207  	return "Prepares your project for being run"
   208  }
   209  
   210  func (c *CompileCommand) Help() string {
   211  	helpText := `
   212  Usage: otto [options] [path]
   213  
   214    Compiles the Appfile into the set of supporting files used for
   215    development, deploy, etc. If path is not specified, the current directory
   216    is assumed.
   217  
   218    This command will download and update any dependencies as well as
   219    the import statements in your Appfile. This process only happens during
   220    compilation so that every other Otto operation begins executing much
   221    more quickly.
   222  
   223  `
   224  
   225  	return strings.TrimSpace(helpText)
   226  }
   227  
   228  func (c *CompileCommand) compileCallback(ui ui.Ui) func(appfile.CompileEvent) {
   229  	return func(raw appfile.CompileEvent) {
   230  		switch e := raw.(type) {
   231  		case *appfile.CompileEventDep:
   232  			ui.Message(fmt.Sprintf(
   233  				"Fetching dependency: %s", e.Source))
   234  		case *appfile.CompileEventImport:
   235  			ui.Message(fmt.Sprintf(
   236  				"Fetching import: %s", e.Source))
   237  		}
   238  	}
   239  }
   240  
   241  // Returns a loaded copy of any appfile.File we find, otherwise returns nil,
   242  // which is valid, since Otto can detect app type and calculate defaults.
   243  // Also returns the base dir of the appfile, which is the current WD in the
   244  // case of a nil appfile.
   245  func loadAppfile(flagAppfile string) (*appfile.File, string, error) {
   246  	appfilePath, err := findAppfile(flagAppfile)
   247  	if err != nil {
   248  		return nil, "", err
   249  	}
   250  	if appfilePath == "" {
   251  		wd, err := os.Getwd()
   252  		if err != nil {
   253  			return nil, "", err
   254  		}
   255  		return nil, wd, nil
   256  	}
   257  	app, err := appfile.ParseFile(appfilePath)
   258  	if err != nil {
   259  		return nil, "", err
   260  	}
   261  	return app, filepath.Dir(app.Path), nil
   262  }
   263  
   264  // findAppfile returns the path to an existing Appfile by checking the optional
   265  // flag value and the current directory. It returns blank if it does not find
   266  // any Appfiles
   267  func findAppfile(flag string) (string, error) {
   268  	// First, if an Appfile was specified on the command-line, it must
   269  	// exist so we validate that it exists.
   270  	if flag != "" {
   271  		fi, err := os.Stat(flag)
   272  		if err != nil {
   273  			return "", fmt.Errorf("Error loading Appfile: %s", err)
   274  		}
   275  
   276  		if fi.IsDir() {
   277  			return findAppfileInDir(flag), nil
   278  		} else {
   279  			return flag, nil
   280  		}
   281  	}
   282  
   283  	// Otherwise we search through our current directory
   284  	wd, err := os.Getwd()
   285  	if err != nil {
   286  		return "", fmt.Errorf("Error loading working directory: %s", err)
   287  	}
   288  	return findAppfileInDir(wd), nil
   289  }
   290  
   291  // findAppfileInDir takes a path to a directory returns the path to the first
   292  // existing Appfile by first looking for the DefaultAppfile and then looking
   293  // for any AltAppfiles in the dir
   294  func findAppfileInDir(path string) string {
   295  	if fi, err := os.Stat(filepath.Join(path, DefaultAppfile)); err == nil && !fi.IsDir() {
   296  		return filepath.Join(path, DefaultAppfile)
   297  	}
   298  	for _, aaf := range AltAppfiles {
   299  		if fi, err := os.Stat(filepath.Join(path, aaf)); err == nil && !fi.IsDir() {
   300  			return filepath.Join(path, aaf)
   301  		}
   302  	}
   303  	return ""
   304  }
   305  
   306  const errCantDetectType = `
   307  No Appfile is present and Otto couldn't detect the project type automatically.
   308  Otto does its best without an Appfile to detect what kind of project this is
   309  automatically, but sometimes this fails if the project is in a structure
   310  Otto doesn't recognize or its a project type that Otto doesn't yet support.
   311  
   312  Please create an Appfile and specify at a minimum the project name and type. Below
   313  is an example minimal Appfile specifying the "my-app" application name and "go"
   314  project type:
   315  
   316      application {
   317  	name = "my-app"
   318  	type = "go"
   319      }
   320  
   321  If you believe Otto should've been able to automatically detect your
   322  project type, then please open an issue with the Otto project.
   323  `