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

     1  // Package scriptpack is used to work with "ScriptPacks" which are
     2  // packages of shell scripts that app types can define and use to get things
     3  // done on the remote machine, whether its for development or deployment.
     4  //
     5  // ScriptPacks are 100% pure shell scripting. Any inputs must be received from
     6  // environment variables. They aren't allowed to template at all. This is
     7  // all done to ensure testability of the ScriptPacks.
     8  //
     9  // These are treated as first class elements within Otto to assist with
    10  // testing.
    11  //
    12  // To create your own scriptpack, see the "template" folder within this
    13  // directory. The folder structure and contents are important for scriptpacks
    14  // to function correctly.
    15  package scriptpack
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"io/ioutil"
    21  	"os"
    22  	"path/filepath"
    23  	"text/template"
    24  
    25  	"github.com/hashicorp/atlas-go/archive"
    26  	"github.com/hashicorp/otto/helper/bindata"
    27  )
    28  
    29  // ScriptPack is a struct representing a single ScriptPack. This is exported
    30  // from the various scriptpacks.
    31  type ScriptPack struct {
    32  	// Name is an identifying name used to name the environment variables.
    33  	// For example the root of where the files are will always be
    34  	// SCRIPTPACK_<NAME>_ROOT.
    35  	Name string
    36  
    37  	// Data is the compiled bindata for this ScriptPack. The entire
    38  	// AssetDirectory will be copied starting from "/data"
    39  	Data bindata.Data
    40  
    41  	// Dependencies are a list of other ScriptPacks that will always be
    42  	// unpacked alongside this ScriptPack. By referencing actual ScriptPack
    43  	// pointers, the dependencies will also be statically compiled into
    44  	// the Go binaries that contain them.
    45  	//
    46  	// Dependencies can be accessed at the path specified by
    47  	// SCRIPTPACK_<DEP>_ROOT. Note that if you depend on a ScriptPack
    48  	// which itself has a conflicting named dependency, then the first
    49  	// one loaded will win. Be careful about this.
    50  	Dependencies []*ScriptPack
    51  }
    52  
    53  // Env returns the environment variables that should be set for this
    54  // ScriptPack when it is executed.
    55  //
    56  // path is the path to the root of the directory where Write was called
    57  // to write the ScriptPack output.
    58  func (s *ScriptPack) Env(path string) map[string]string {
    59  	result := make(map[string]string)
    60  	result[fmt.Sprintf("SCRIPTPACK_%s_ROOT", s.Name)] = filepath.Join(path, s.Name)
    61  	for _, dep := range s.Dependencies {
    62  		for k, v := range dep.Env(path) {
    63  			result[k] = v
    64  		}
    65  	}
    66  
    67  	return result
    68  }
    69  
    70  // Write writes the contents of the ScriptPack and any dependencies into
    71  // the given directory.
    72  func (s *ScriptPack) Write(dst string) error {
    73  	// Build the names of all scriptpacks
    74  	spNames := make([]string, 1, len(s.Dependencies)+1)
    75  	spNames[0] = s.Name
    76  	for _, dep := range s.Dependencies {
    77  		spNames = append(spNames, dep.Name)
    78  	}
    79  
    80  	// Deps
    81  	for _, dep := range s.Dependencies {
    82  		if err := dep.Write(dst); err != nil {
    83  			return err
    84  		}
    85  	}
    86  
    87  	// Our own
    88  	if err := s.Data.CopyDir(filepath.Join(dst, s.Name), "data"); err != nil {
    89  		return err
    90  	}
    91  
    92  	// Write the main file which has the env vars in it
    93  	f, err := os.OpenFile(
    94  		filepath.Join(dst, "main.sh"),
    95  		os.O_CREATE|os.O_TRUNC|os.O_WRONLY,
    96  		0755)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	defer f.Close()
   101  	tpl := template.Must(template.New("root").Parse(mainShTpl))
   102  	return tpl.Execute(f, map[string]interface{}{
   103  		"root":        s.Name,
   104  		"scriptpacks": spNames,
   105  	})
   106  }
   107  
   108  // WriteArchive writes the contents of the ScriptPack as a tar gzip to the
   109  // given path.
   110  func (s *ScriptPack) WriteArchive(dst string) error {
   111  	// Let's just open the file we're going to write to first to verify
   112  	// we can write there since everything else is pointless if we can't.
   113  	f, err := os.Create(dst)
   114  	if err != nil {
   115  		return err
   116  	}
   117  	defer f.Close()
   118  
   119  	// Create a temporary directory to store the raw ScriptPack data
   120  	td, err := ioutil.TempDir("", "otto")
   121  	if err != nil {
   122  		return err
   123  	}
   124  	defer os.RemoveAll(td)
   125  
   126  	// Write the ScriptPack
   127  	if err := s.Write(td); err != nil {
   128  		return err
   129  	}
   130  
   131  	// Archive this ScriptPack
   132  	a, err := archive.CreateArchive(td, &archive.ArchiveOpts{
   133  		VCS: false,
   134  	})
   135  	if err != nil {
   136  		return err
   137  	}
   138  	defer a.Close()
   139  
   140  	// Write the archive to final path
   141  	_, err = io.Copy(f, a)
   142  	return err
   143  }
   144  
   145  const mainShTpl = `
   146  #!/bin/bash
   147  
   148  # Determine the directory of this script
   149  SOURCE="${BASH_SOURCE[0]}"
   150  while [ -h "$SOURCE" ]; do
   151    DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
   152    SOURCE="$(readlink "$SOURCE")"
   153    [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
   154  done
   155  DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
   156  
   157  # Set the env vars
   158  {{range .scriptpacks}}
   159  export SCRIPTPACK_{{ . }}_ROOT="${DIR}/{{ . }}"
   160  {{end}}
   161  
   162  # Load the main of our entrypoint
   163  . ${SCRIPTPACK_{{.root}}_ROOT}/main.sh
   164  `