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  }