github.com/bhameyie/otto@v0.2.1-0.20160406174117-16052efa52ec/helper/compile/app.go (about) 1 package compile 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/hashicorp/otto/app" 11 "github.com/hashicorp/otto/foundation" 12 "github.com/hashicorp/otto/helper/bindata" 13 "github.com/hashicorp/otto/helper/oneline" 14 "github.com/hashicorp/otto/scriptpack" 15 ) 16 17 // AppOptions are the options for compiling an application. 18 // 19 // These options may be modified during customization processing, and 20 // in fact that is an intended use case and common pattern. To do this, 21 // use the AppCustomizationFunc method. See some of the builtin types for 22 // examples. 23 type AppOptions struct { 24 // Ctx is the app context of this compilation. 25 Ctx *app.Context 26 27 // Result is the base CompileResult that will be used to return the result. 28 // You can set this if you want to override some settings. 29 Result *app.CompileResult 30 31 // FoundationConfig is the configuration for the foundation that 32 // will be returned as the compilation result. 33 FoundationConfig foundation.Config 34 35 // Bindata is the data that is used for templating. This must be set. 36 // Template data should also be set on this. This will be modified with 37 // default template data if those keys are not set. 38 Bindata *bindata.Data 39 40 // ScriptPacks are a list of ScriptPacks that this app wants available 41 // to it. Each of these scriptpacks and all of the dependencies will be 42 // expanded into the compiled directory and the paths to them will be 43 // available in the Bindata context. 44 // 45 // The uploaded ScriptPacks are tar.gzipped. 46 ScriptPacks []*scriptpack.ScriptPack 47 48 // Customization is used to configure the customizations for this 49 // application. See the Customization type docs for more info. 50 Customization *Customization 51 52 // Callbacks are called just prior to compilation completing. 53 Callbacks []CompileCallback 54 } 55 56 // CompileCallback is a callback that can be registered to be run after 57 // compilation. To access any data within this callback, it should be created 58 // as a closure around the AppOptions. 59 type CompileCallback func() error 60 61 // App is an opinionated compilation function to help implement 62 // app.App.Compile. 63 // 64 // AppOptions may be modified by this function during this call. 65 func App(opts *AppOptions) (*app.CompileResult, error) { 66 // Write the test data in case we're running tests right now 67 testLock.RLock() 68 defer testLock.RUnlock() 69 testAppOpts = opts 70 71 ctx := opts.Ctx 72 73 // Prepare bindata context 74 if err := opts.prepareBindata(); err != nil { 75 return nil, err 76 } 77 78 // Process the customizations! 79 err := processCustomizations( 80 ctx.Appfile.Customization, 81 opts.Customization) 82 if err != nil { 83 return nil, err 84 } 85 86 // Upload the ScriptPacks 87 if err := opts.compileScriptPacks(); err != nil { 88 return nil, err 89 } 90 91 // Create the directory list that we'll copy from, and copy those 92 // directly into the compilation directory. 93 bindirs := []string{ 94 "data/common", 95 fmt.Sprintf("data/%s-%s", ctx.Tuple.Infra, ctx.Tuple.InfraFlavor), 96 } 97 for _, dir := range bindirs { 98 // Copy all the common files that exist 99 if err := opts.Bindata.CopyDir(ctx.Dir, dir); err != nil { 100 // Ignore any directories that don't exist 101 if strings.Contains(err.Error(), "not found") { 102 continue 103 } 104 105 return nil, err 106 } 107 } 108 109 if err := appFoundations(opts); err != nil { 110 return nil, err 111 } 112 113 // Callbacks 114 for _, cb := range opts.Callbacks { 115 if err := cb(); err != nil { 116 return nil, err 117 } 118 } 119 120 // If the DevDep fragment exists, then use it 121 fragmentPath := filepath.Join(ctx.Dir, "dev-dep", "Vagrantfile.fragment") 122 if _, err := os.Stat(fragmentPath); err != nil { 123 fragmentPath = "" 124 } 125 126 // Set some defaults here 127 if opts.FoundationConfig.ServiceName == "" { 128 opts.FoundationConfig.ServiceName = opts.Ctx.Application.Name 129 } 130 131 result := opts.Result 132 if result == nil { 133 result = new(app.CompileResult) 134 } 135 result.FoundationConfig = opts.FoundationConfig 136 result.DevDepFragmentPath = fragmentPath 137 return result, nil 138 } 139 140 // appFoundations compiles the app-specific foundation files. 141 func appFoundations(opts *AppOptions) error { 142 // Setup the bindata for rendering 143 dataCopy := Data 144 data := &dataCopy 145 data.Context = make(map[string]interface{}) 146 for k, v := range opts.Bindata.Context { 147 data.Context[k] = v 148 } 149 150 // Go through each foundation and setup the layers 151 log.Printf("[INFO] compile: looking for foundation layers for dev") 152 for i, dir := range opts.Ctx.FoundationDirs { 153 devDir := filepath.Join(dir, "app-dev") 154 log.Printf("[DEBUG] compile: checking foundation dir: %s", devDir) 155 156 _, err := os.Stat(filepath.Join(devDir, "layer.sh")) 157 if err != nil { 158 // If the file doesn't exist then this foundation just 159 // doesn't have a layer. Not a big deal. 160 if os.IsNotExist(err) { 161 log.Printf("[DEBUG] compile: dir %s has no layers", devDir) 162 continue 163 } 164 165 // The error is something else, return it... 166 return err 167 } 168 169 log.Printf("[DEBUG] compile: dir %s has a layer!", devDir) 170 171 // We have a layer! Read the ID. 172 id, err := oneline.Read(filepath.Join(devDir, "layer.id")) 173 if err != nil { 174 return err 175 } 176 177 // Setup the data for this render 178 data.Context["foundation_id"] = id 179 data.Context["foundation_dir"] = devDir 180 181 // Create the directory where this will be stored 182 renderDir := filepath.Join( 183 opts.Ctx.Dir, "foundation-layers", fmt.Sprintf("%d-%s", i, id)) 184 if err := os.MkdirAll(renderDir, 0755); err != nil { 185 return err 186 } 187 188 // Render our standard template for a foundation layer 189 err = data.RenderAsset( 190 filepath.Join(renderDir, "Vagrantfile"), 191 "data/internal/foundation-layer.Vagrantfile.tpl") 192 if err != nil { 193 return err 194 } 195 } 196 197 return nil 198 } 199 200 func (a *AppOptions) prepareBindata() error { 201 ctx := a.Ctx 202 203 // Setup the basic templating data. We put this into the "data" local 204 // var just so that it is easier to reference. 205 data := a.Bindata 206 if data.Context == nil { 207 data.Context = make(map[string]interface{}) 208 a.Bindata = data 209 } 210 211 data.Context["app_type"] = ctx.Appfile.Application.Type 212 data.Context["name"] = ctx.Appfile.Application.Name 213 data.Context["dev_fragments"] = ctx.DevDepFragments 214 data.Context["dev_ip_address"] = ctx.DevIPAddress 215 216 if data.Context["path"] == nil { 217 data.Context["path"] = make(map[string]string) 218 } 219 pathMap := data.Context["path"].(map[string]string) 220 pathMap["cache"] = ctx.CacheDir 221 pathMap["compiled"] = ctx.Dir 222 pathMap["working"] = filepath.Dir(ctx.Appfile.Path) 223 foundationDirsContext := map[string][]string{ 224 "dev": make([]string, len(ctx.FoundationDirs)), 225 "dev_dep": make([]string, len(ctx.FoundationDirs)), 226 "build": make([]string, len(ctx.FoundationDirs)), 227 "deploy": make([]string, len(ctx.FoundationDirs)), 228 } 229 for i, dir := range ctx.FoundationDirs { 230 foundationDirsContext["dev"][i] = filepath.Join(dir, "app-dev") 231 foundationDirsContext["dev_dep"][i] = filepath.Join(dir, "app-dev-dep") 232 foundationDirsContext["build"][i] = filepath.Join(dir, "app-build") 233 foundationDirsContext["deploy"][i] = filepath.Join(dir, "app-deploy") 234 } 235 data.Context["foundation_dirs"] = foundationDirsContext 236 237 // ScriptPack paths 238 spPaths := make([]map[string]interface{}, len(a.ScriptPacks)) 239 for i, sp := range a.ScriptPacks { 240 spPaths[i] = map[string]interface{}{ 241 "name": sp.Name, 242 "path": filepath.Join( 243 ctx.Dir, "scriptpacks", fmt.Sprintf("%s.tar.gz", sp.Name)), 244 } 245 } 246 data.Context["scriptpacks"] = spPaths 247 248 // Setup the shared data 249 if data.SharedExtends == nil { 250 data.SharedExtends = make(map[string]*bindata.Data) 251 } 252 data.SharedExtends["compile"] = &bindata.Data{ 253 Asset: Asset, 254 AssetDir: AssetDir, 255 } 256 257 return nil 258 } 259 260 func (a *AppOptions) compileScriptPacks() error { 261 // Create the directory to hold the scriptpacks 262 root := filepath.Join(a.Ctx.Dir, "scriptpacks") 263 if _, err := os.Stat(root); err != nil { 264 if os.IsNotExist(err) { 265 err = os.MkdirAll(root, 0755) 266 } 267 268 if err != nil { 269 return err 270 } 271 } 272 273 // Go through each and write it out 274 for _, sp := range a.ScriptPacks { 275 path := filepath.Join(root, fmt.Sprintf("%s.tar.gz", sp.Name)) 276 if err := sp.WriteArchive(path); err != nil { 277 return fmt.Errorf( 278 "Error writing ScriptPack '%s': %s", sp.Name, err) 279 } 280 } 281 282 return nil 283 }