// TestAppfile returns a compiled appfile for the given path. This uses // defaults for detectors and such so it is up to you to use a fairly // complete Appfile. func TestAppfile(t TestT, path string) *appfile.Compiled { def, err := appfile.Default(filepath.Dir(path), &detect.Config{ Detectors: []*detect.Detector{ &detect.Detector{ Type: "test", File: []string{"Appfile"}, }, }, }) if err != nil { t.Fatal("err: ", err) } // Default type should be "test" def.Infrastructure[0].Type = "test" def.Infrastructure[0].Flavor = "test" def.Infrastructure[0].Foundations = nil // Parse the raw file f, err := appfile.ParseFile(path) if err != nil { t.Fatal("err: ", err) } // Merge if err := def.Merge(f); err != nil { t.Fatal("err: ", err) } f = def // Create a temporary directory for the compilation data. We don't // delete this now in case we're using any of that data, but the // temp dir should get cleaned up by the system at some point. td, err := ioutil.TempDir("", "otto") if err != nil { t.Fatal("err: ", err) } // Compile it! compiler, err := appfile.NewCompiler(&appfile.CompileOpts{ Dir: td, }) if err != nil { t.Fatal("err: ", err) } result, err := compiler.Compile(f) if err != nil { t.Fatal("err: ", err) } return result }
func (l *Loader) Load(f *appfile.File, dir string) (*appfile.File, error) { realFile := f // Load the "detected" Appfile appDef, err := appfile.Default(dir, l.Detector) if err != nil { return nil, fmt.Errorf("Error detecting app type: %s", err) } // Merge the detected Appfile with the real Appfile var merged appfile.File if err := merged.Merge(appDef); err != nil { return nil, fmt.Errorf("Error loading Appfile: %s", err) } if realFile != nil { if err := merged.Merge(realFile); err != nil { return nil, fmt.Errorf("Error loading Appfile: %s", err) } } realFile = &merged // If we have no application type, there is nothing more to do if realFile == nil || realFile.Application.Type == "" { return realFile, nil } // If we're not configured to do any further detection, return it if !realFile.Application.Detect { return realFile, nil } // Minimally compile the file that we can use to create a core compiled, err := l.Compiler.MinCompile(realFile) if err != nil { return nil, err } // Create a temporary directory we use for the core td, err := ioutil.TempDir("", "otto") if err != nil { return nil, err } defer os.RemoveAll(td) // Create a core core, err := otto.NewCore(&otto.CoreConfig{ DataDir: filepath.Join(td, "data"), LocalDir: filepath.Join(td, "local"), CompileDir: filepath.Join(td, "compile"), Appfile: compiled, Apps: l.Apps, }) if err != nil { return nil, err } // Get the app implementation appImpl, appCtx, err := core.App() if err != nil { return nil, err } defer app.Close(appImpl) // Load the implicit Appfile implicit, err := appImpl.Implicit(appCtx) if err != nil { return nil, err } var final appfile.File if err := final.Merge(appDef); err != nil { return nil, fmt.Errorf("Error loading Appfile: %s", err) } if implicit != nil { if err := final.Merge(implicit); err != nil { return nil, fmt.Errorf("Error loading Appfile: %s", err) } } if f != nil { if err := final.Merge(f); err != nil { return nil, fmt.Errorf("Error loading Appfile: %s", err) } } return &final, nil }
func (c *CompileCommand) Run(args []string) int { var flagAppfile string fs := c.FlagSet("compile", FlagSetNone) fs.Usage = func() { c.Ui.Error(c.Help()) } fs.StringVar(&flagAppfile, "appfile", "", "") if err := fs.Parse(args); err != nil { return 1 } // Load all the plugins, we use all the plugins for compilation only // so we have full access to detectors and app types. pluginMgr, err := c.PluginManager() if err != nil { c.Ui.Error(fmt.Sprintf( "Error initializing plugin manager: %s", err)) return 1 } if err := pluginMgr.LoadAll(); err != nil { c.Ui.Error(fmt.Sprintf( "Error loading plugins: %s", err)) return 1 } // Load the detectors from the plugins detectors := make([]*detect.Detector, 0, 20) detectors = append(detectors, c.Detectors...) for _, p := range pluginMgr.Plugins() { detectors = append(detectors, p.AppMeta.Detectors...) } // Load a UI ui := c.OttoUi() ui.Header("Loading Appfile...") app, appPath, err := loadAppfile(flagAppfile) if err != nil { c.Ui.Error(err.Error()) return 1 } // Tell the user what is happening if they have no Appfile if app == nil { ui.Header("No Appfile found! Detecting project information...") ui.Message(fmt.Sprintf( "No Appfile was found. If there is no Appfile, Otto will do its best\n" + "to detect the type of application this is and set reasonable defaults.\n" + "This is a good way to get started with Otto, but over time we recommend\n" + "writing a real Appfile since this will allow more complex customizations,\n" + "the ability to reference dependencies, versioning, and more.")) } // Parse the detectors dataDir, err := c.DataDir() if err != nil { c.Ui.Error(err.Error()) return 1 } detectorDir := filepath.Join(dataDir, DefaultLocalDataDetectorDir) log.Printf("[DEBUG] loading detectors from: %s", detectorDir) detectConfig, err := detect.ParseDir(detectorDir) if err != nil { c.Ui.Error(err.Error()) return 1 } if detectConfig == nil { detectConfig = &detect.Config{} } err = detectConfig.Merge(&detect.Config{Detectors: detectors}) if err != nil { c.Ui.Error(err.Error()) return 1 } // Load the default Appfile so we can merge in any defaults into // the loaded Appfile (if there is one). appDef, err := appfile.Default(appPath, detectConfig) if err != nil { c.Ui.Error(fmt.Sprintf( "Error loading Appfile: %s", err)) return 1 } // If there was no loaded Appfile and we don't have an application // type then we weren't able to detect the type. Error. if app == nil && appDef.Application.Type == "" { c.Ui.Error(strings.TrimSpace(errCantDetectType)) return 1 } // Merge the appfiles if app != nil { if err := appDef.Merge(app); err != nil { c.Ui.Error(fmt.Sprintf( "Error loading Appfile: %s", err)) return 1 } } app = appDef // Compile the Appfile ui.Header("Fetching all Appfile dependencies...") capp, err := appfile.Compile(app, &appfile.CompileOpts{ Dir: filepath.Join( filepath.Dir(app.Path), DefaultOutputDir, DefaultOutputDirCompiledAppfile), Detect: detectConfig, Callback: c.compileCallback(ui), }) if err != nil { c.Ui.Error(fmt.Sprintf( "Error compiling Appfile: %s", err)) return 1 } // Get a core core, err := c.Core(capp) if err != nil { c.Ui.Error(fmt.Sprintf( "Error loading core: %s", err)) return 1 } // Get the active infrastructure just for UI reasons infra := app.ActiveInfrastructure() // Before the compilation, output to the user what is going on ui.Header("Compiling...") ui.Message(fmt.Sprintf( "Application: %s (%s)", app.Application.Name, app.Application.Type)) ui.Message(fmt.Sprintf("Project: %s", app.Project.Name)) ui.Message(fmt.Sprintf( "Infrastructure: %s (%s)", infra.Type, infra.Flavor)) ui.Message("") // Compile! if err := core.Compile(); err != nil { c.Ui.Error(fmt.Sprintf( "Error compiling: %s", err)) return 1 } // Store the used plugins so later calls don't have to load everything usedPath, err := c.AppfilePluginsPath(capp) if err != nil { c.Ui.Error(fmt.Sprintf( "Error compiling: %s", err)) return 1 } if err := pluginMgr.StoreUsed(usedPath); err != nil { c.Ui.Error(fmt.Sprintf( "Error compiling plugin data: %s", err)) return 1 } // Success! ui.Header("[green]Compilation success!") ui.Message(fmt.Sprintf( "[green]This means that Otto is now ready to start a development environment,\n" + "deploy this application, build the supporting infrastructure, and\n" + "more. See the help for more information.\n\n" + "Supporting files to enable Otto to manage your application from\n" + "development to deployment have been placed in the output directory.\n" + "These files can be manually inspected to determine what Otto will do.")) return 0 }
func (c *CompileCommand) Run(args []string) int { var flagAppfile string fs := c.FlagSet("compile", FlagSetNone) fs.Usage = func() { c.Ui.Error(c.Help()) } fs.StringVar(&flagAppfile, "appfile", "", "") if err := fs.Parse(args); err != nil { return 1 } // Load a UI ui := c.OttoUi() ui.Header("Loading Appfile...") // Determine all the Appfile paths // // First, if an Appfile was specified on the command-line, it must // exist so we validate that it exists. if flagAppfile != "" { fi, err := os.Stat(flagAppfile) if err != nil { c.Ui.Error(fmt.Sprintf( "Error loading Appfile: %s", err)) return 1 } if fi.IsDir() { flagAppfile = filepath.Join(flagAppfile, DefaultAppfile) } } // If the Appfile is still blank, just use our current directory if flagAppfile == "" { var err error flagAppfile, err = os.Getwd() if err != nil { c.Ui.Error(fmt.Sprintf( "Error loading working directory: %s", err)) return 1 } flagAppfile = filepath.Join(flagAppfile, DefaultAppfile) } // If we have the Appfile, then make sure it is an absoute path if flagAppfile != "" { var err error flagAppfile, err = filepath.Abs(flagAppfile) if err != nil { c.Ui.Error(fmt.Sprintf( "Error getting Appfile path: %s", err)) return 1 } } // Load the appfile. This is the only time we ever load the // raw Appfile. All other commands load the compiled Appfile. var app *appfile.File if fi, err := os.Stat(flagAppfile); err == nil && !fi.IsDir() { app, err = appfile.ParseFile(flagAppfile) if err != nil { c.Ui.Error(err.Error()) return 1 } } // Tell the user what is happening if they have no Appfile if app == nil { ui.Header("No Appfile found! Detecting project information...") ui.Message(fmt.Sprintf( "No Appfile was found. If there is no Appfile, Otto will do its best\n" + "to detect the type of application this is and set reasonable defaults.\n" + "This is a good way to get started with Otto, but over time we recommend\n" + "writing a real Appfile since this will allow more complex customizations,\n" + "the ability to reference dependencies, versioning, and more.")) } // Parse the detectors dataDir, err := c.DataDir() if err != nil { c.Ui.Error(err.Error()) return 1 } detectorDir := filepath.Join(dataDir, DefaultLocalDataDetectorDir) log.Printf("[DEBUG] loading detectors from: %s", detectorDir) detectConfig, err := detect.ParseDir(detectorDir) if err != nil { c.Ui.Error(err.Error()) return 1 } if detectConfig == nil { detectConfig = &detect.Config{} } err = detectConfig.Merge(&detect.Config{Detectors: c.Detectors}) if err != nil { c.Ui.Error(err.Error()) return 1 } // Load the default Appfile so we can merge in any defaults into // the loaded Appfile (if there is one). appDef, err := appfile.Default(filepath.Dir(flagAppfile), detectConfig) if err != nil { c.Ui.Error(fmt.Sprintf( "Error loading Appfile: %s", err)) return 1 } // If there was no loaded Appfile and we don't have an application // type then we weren't able to detect the type. Error. if app == nil && appDef.Application.Type == "" { c.Ui.Error(strings.TrimSpace(errCantDetectType)) return 1 } // Merge the appfiles if app != nil { if err := appDef.Merge(app); err != nil { c.Ui.Error(fmt.Sprintf( "Error loading Appfile: %s", err)) return 1 } } app = appDef // Compile the Appfile ui.Header("Fetching all Appfile dependencies...") capp, err := appfile.Compile(app, &appfile.CompileOpts{ Dir: filepath.Join( filepath.Dir(app.Path), DefaultOutputDir, DefaultOutputDirCompiledAppfile), Detect: detectConfig, Callback: c.compileCallback(ui), }) if err != nil { c.Ui.Error(fmt.Sprintf( "Error compiling Appfile: %s", err)) return 1 } // Get a core core, err := c.Core(capp) if err != nil { c.Ui.Error(fmt.Sprintf( "Error loading core: %s", err)) return 1 } // Get the active infrastructure just for UI reasons infra := app.ActiveInfrastructure() // Before the compilation, output to the user what is going on ui.Header("Compiling...") ui.Message(fmt.Sprintf( "Application: %s (%s)", app.Application.Name, app.Application.Type)) ui.Message(fmt.Sprintf("Project: %s", app.Project.Name)) ui.Message(fmt.Sprintf( "Infrastructure: %s (%s)", infra.Type, infra.Flavor)) ui.Message("") // Compile! if err := core.Compile(); err != nil { c.Ui.Error(fmt.Sprintf( "Error compiling: %s", err)) return 1 } // Success! ui.Header("[green]Compilation success!") ui.Message(fmt.Sprintf( "[green]This means that Otto is now ready to start a development environment,\n" + "deploy this application, build the supporting infastructure, and\n" + "more. See the help for more information.\n\n" + "Supporting files to enable Otto to manage your application from\n" + "development to deployment have been placed in the output directory.\n" + "These files can be manually inspected to determine what Otto will do.")) return 0 }
func TestLoader_basic(t *testing.T) { cases := []struct { Path string Input, Expected *appfile.File }{ { "basic", &appfile.File{ Application: &appfile.Application{ Name: "foo", }, }, &appfile.File{ Application: &appfile.Application{ Name: "foo", }, }, }, { "detect", &appfile.File{ Application: &appfile.Application{ Name: "foo", Detect: true, }, }, &appfile.File{ Application: &appfile.Application{ Name: "foo", Type: "test", Dependencies: []*appfile.Dependency{ &appfile.Dependency{Source: "tubes"}, }, }, }, }, { "detect-no-appfile", nil, &appfile.File{ Application: &appfile.Application{ Type: "test", Dependencies: []*appfile.Dependency{ &appfile.Dependency{Source: "tubes"}, }, }, }, }, { "detect-false", &appfile.File{ Application: &appfile.Application{ Name: "foo", Detect: false, }, }, &appfile.File{ Application: &appfile.Application{ Name: "foo", Type: "test", Detect: false, }, }, }, } l, appMock := testLoader(t) appMock.ImplicitResult = &appfile.File{ Application: &appfile.Application{ Dependencies: []*appfile.Dependency{ &appfile.Dependency{Source: "tubes"}, }, }, } for _, tc := range cases { tc.Path = testPath(tc.Path) actual, err := l.Load(tc.Input, tc.Path) if err != nil { t.Fatalf("err: %s", err) } // Load the default and merge it def, err := appfile.Default(tc.Path, nil) if err != nil { t.Fatalf("err %s: %s", tc.Path, err) } if err := def.Merge(tc.Expected); err != nil { t.Fatalf("err %s: %s", tc.Path, err) } tc.Expected = def if !reflect.DeepEqual(actual, tc.Expected) { t.Fatalf("err: %s\n\n%#v", tc.Path, actual) } } }