github.com/bhameyie/otto@v0.2.1-0.20160406174117-16052efa52ec/command/meta.go (about) 1 package command 2 3 import ( 4 "bufio" 5 "flag" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 11 "github.com/hashicorp/otto/appfile" 12 "github.com/hashicorp/otto/directory" 13 "github.com/hashicorp/otto/otto" 14 "github.com/hashicorp/otto/plugin" 15 "github.com/hashicorp/otto/ui" 16 "github.com/mitchellh/cli" 17 "github.com/mitchellh/go-homedir" 18 ) 19 20 const ( 21 // DefaultAppfile is the default filename for the Appfile 22 DefaultAppfile = "Appfile" 23 24 // DefaultLocalDataDir is the default path to the local data 25 // directory. 26 DefaultLocalDataDir = "~/.otto.d" 27 DefaultLocalDataDetectorDir = "detect" 28 DefaultLocalDataPluginsDir = "plugins" 29 30 // DefaultOutputDir is the default filename for the output directory 31 DefaultOutputDir = ".otto" 32 DefaultOutputDirCompiledAppfile = "appfile" 33 DefaultOutputDirCompiledData = "compiled" 34 DefaultOutputDirLocalData = "data" 35 36 // DefaultDataDir is the default directory for the directory 37 // data if a directory in the Appfile isn't specified. 38 DefaultDataDir = "otto-data" 39 ) 40 41 var ( 42 // AltAppfiles is the list of alternative names for an Appfile that Otto can 43 // detect and load automatically 44 AltAppfiles = []string{"appfile.hcl"} 45 ) 46 47 // FlagSetFlags is an enum to define what flags are present in the 48 // default FlagSet returned by Meta.FlagSet 49 type FlagSetFlags uint 50 51 const ( 52 FlagSetNone FlagSetFlags = 0 53 ) 54 55 // Meta are the meta-options that are available on all or most commands. 56 type Meta struct { 57 CoreConfig *otto.CoreConfig 58 Ui cli.Ui 59 PluginMap plugin.ServeMuxMap 60 61 pluginManager *PluginManager 62 } 63 64 // Appfile loads the compiled Appfile. If the Appfile isn't compiled yet, 65 // then an error will be returned. 66 func (m *Meta) Appfile() (*appfile.Compiled, error) { 67 // Find the root directory 68 startDir, err := os.Getwd() 69 if err != nil { 70 return nil, err 71 } 72 rootDir, err := m.RootDir(startDir) 73 if err != nil { 74 return nil, err 75 } 76 77 return appfile.LoadCompiled(filepath.Join( 78 rootDir, DefaultOutputDir, DefaultOutputDirCompiledAppfile)) 79 } 80 81 // Core returns the core for the given Appfile. The file where the 82 // Appfile was loaded from should be set in appfile.File.Path. This 83 // root appfile path will be used as the default output directory 84 // for Otto. 85 func (m *Meta) Core(f *appfile.Compiled) (*otto.Core, error) { 86 if f.File == nil || f.File.Path == "" { 87 return nil, fmt.Errorf("Could not determine Appfile dir") 88 } 89 90 rootDir, err := m.RootDir(filepath.Dir(f.File.Path)) 91 if err != nil { 92 return nil, err 93 } 94 95 rootDir, err = filepath.Abs(rootDir) 96 if err != nil { 97 return nil, err 98 } 99 100 dataDir, err := m.DataDir() 101 if err != nil { 102 return nil, err 103 } 104 105 pluginMgr, err := m.PluginManager() 106 if err != nil { 107 return nil, err 108 } 109 if len(pluginMgr.Plugins()) == 0 { 110 // We haven't loaded any plugins. Look for them in the 111 // used directory and load them. 112 usedPath, err := m.AppfilePluginsPath(f) 113 if err != nil { 114 return nil, err 115 } 116 if _, err := os.Stat(usedPath); err == nil { 117 if err := pluginMgr.LoadUsed(usedPath); err != nil { 118 return nil, err 119 } 120 } 121 } 122 123 // Configure the core with what we have from the plugin manager. 124 if err := pluginMgr.ConfigureCore(m.CoreConfig); err != nil { 125 return nil, err 126 } 127 128 config := *m.CoreConfig 129 config.Appfile = f 130 config.DataDir = dataDir 131 config.LocalDir = filepath.Join( 132 rootDir, DefaultOutputDir, DefaultOutputDirLocalData) 133 config.CompileDir = filepath.Join( 134 rootDir, DefaultOutputDir, DefaultOutputDirCompiledData) 135 config.Ui = m.OttoUi() 136 137 config.Directory, err = m.Directory(&config) 138 if err != nil { 139 return nil, err 140 } 141 142 return otto.NewCore(&config) 143 } 144 145 // AppfilePluginsPath returns the path where the used plugins data 146 // should be stored based on an Appfile. 147 func (m *Meta) AppfilePluginsPath(f *appfile.Compiled) (string, error) { 148 rootDir, err := m.RootDir(filepath.Dir(f.File.Path)) 149 if err != nil { 150 return "", err 151 } 152 153 rootDir, err = filepath.Abs(rootDir) 154 if err != nil { 155 return "", err 156 } 157 158 return filepath.Join( 159 rootDir, DefaultOutputDir, DefaultOutputDirCompiledAppfile, "plugins.json"), nil 160 } 161 162 // DataDir returns the user-local data directory for Otto. 163 func (m *Meta) DataDir() (string, error) { 164 return homedir.Expand(DefaultLocalDataDir) 165 } 166 167 // RootDir finds the "root" directory. This is the working directory of 168 // the Appfile and Otto itself. To find the root directory, we traverse 169 // upwards until we find the ".otto" directory and assume that is where 170 // it is. 171 func (m *Meta) RootDir(startDir string) (string, error) { 172 current := startDir 173 174 // Traverse upwards until we find the directory. We also protect this 175 // loop with a basic infinite loop guard. 176 i := 0 177 prev := "" 178 for prev != current && i < 1000 { 179 if _, err := os.Stat(filepath.Join(current, DefaultOutputDir)); err == nil { 180 // Found it 181 return current, nil 182 } 183 184 prev = current 185 current = filepath.Dir(current) 186 i++ 187 } 188 189 return "", fmt.Errorf( 190 "Otto doesn't appear to have compiled your Appfile yet!\n\n" + 191 "Run `otto compile` in the directory with the Appfile or\n" + 192 "with the `-appfile` flag in order to compile the files for\n" + 193 "developing, building, and deploying your application.\n\n" + 194 "Once the Appfile is compiled, you can run `otto` in any\n" + 195 "subdirectory.") 196 } 197 198 // Directory returns the Otto directory backend for the given 199 // Appfile. If no directory backend is specified, a local folder 200 // will be used. 201 func (m *Meta) Directory(config *otto.CoreConfig) (directory.Backend, error) { 202 return &directory.BoltBackend{ 203 Dir: filepath.Join(config.DataDir, "directory"), 204 }, nil 205 } 206 207 // FlagSet returns a FlagSet with the common flags that every 208 // command implements. The exact behavior of FlagSet can be configured 209 // using the flags as the second parameter. 210 func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet { 211 f := flag.NewFlagSet(n, flag.ContinueOnError) 212 213 // Create an io.Writer that writes to our Ui properly for errors. 214 // This is kind of a hack, but it does the job. Basically: create 215 // a pipe, use a scanner to break it into lines, and output each line 216 // to the UI. Do this forever. 217 errR, errW := io.Pipe() 218 errScanner := bufio.NewScanner(errR) 219 go func() { 220 for errScanner.Scan() { 221 m.Ui.Error(errScanner.Text()) 222 } 223 }() 224 f.SetOutput(errW) 225 226 return f 227 } 228 229 // PluginManager returns the PluginManager configured with the proper 230 // directories for this command invocation. 231 // 232 // This is a singleton for each Meta, so multiple calls will return the 233 // same object. 234 func (m *Meta) PluginManager() (*PluginManager, error) { 235 if m.pluginManager != nil { 236 return m.pluginManager, nil 237 } 238 239 // Get the root directory to look for plugins. If we can't get it, 240 // then assume we're in the pwd for compilation. 241 startDir, err := os.Getwd() 242 if err != nil { 243 return nil, err 244 } 245 rootDir, err := m.RootDir(startDir) 246 if err != nil { 247 rootDir = startDir 248 } 249 250 // Data directory where plugins can be 251 dataDir, err := m.DataDir() 252 if err != nil { 253 return nil, err 254 } 255 256 m.pluginManager = &PluginManager{ 257 PluginMap: m.PluginMap, 258 PluginDirs: []string{ 259 rootDir, 260 filepath.Join(dataDir, DefaultLocalDataPluginsDir), 261 filepath.Dir(pluginExePath), 262 }, 263 } 264 return m.pluginManager, nil 265 } 266 267 // OttoUi returns the ui.Ui object. 268 func (m *Meta) OttoUi() ui.Ui { 269 return NewUi(m.Ui) 270 } 271 272 // confirmDestroy is a little helper that will ask the user to confirm a 273 // destroy action using the provided msg, unless -force is included in args it 274 // returns true if the destroy should be considered confirmed, and false if 275 // the destroy should be aborted. 276 func (m *Meta) confirmDestroy(msg string, args []string) bool { 277 destroyForce := false 278 for _, arg := range args { 279 if arg == "-force" { 280 destroyForce = true 281 } 282 } 283 284 if !destroyForce { 285 v, err := m.OttoUi().Input(&ui.InputOpts{ 286 Id: "destroy", 287 Query: "Do you really want to destroy?", 288 Description: fmt.Sprintf("%s\n"+ 289 "There is no undo. Only 'yes' will be accepted to confirm.", msg), 290 }) 291 if err != nil { 292 m.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err)) 293 return false 294 } 295 if v != "yes" { 296 m.Ui.Output("Destroy cancelled.") 297 return false 298 } 299 } 300 301 return true 302 }