github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/google.golang.org/appengine/cmd/aedeploy/aedeploy.go (about)

     1  // Copyright 2015 Google Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  // Program aedeploy assists with deploying Go Managed VM apps to production.
     6  // A temporary directory is created; the app, its subdirectories, and all its
     7  // dependencies from $GOPATH are copied into the directory; then the app
     8  // is deployed to production with the provided command.
     9  //
    10  // The app must be in "package main".
    11  //
    12  // This command must be issued from within the root directory of the app
    13  // (where the app.yaml file is located).
    14  package main
    15  
    16  import (
    17  	"flag"
    18  	"fmt"
    19  	"go/build"
    20  	"io"
    21  	"io/ioutil"
    22  	"os"
    23  	"os/exec"
    24  	"path/filepath"
    25  	"strings"
    26  )
    27  
    28  var (
    29  	skipFiles = map[string]bool{
    30  		".git":        true,
    31  		".gitconfig":  true,
    32  		".hg":         true,
    33  		".travis.yml": true,
    34  	}
    35  
    36  	gopathCache = map[string]string{}
    37  )
    38  
    39  func usage() {
    40  	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
    41  	fmt.Fprintf(os.Stderr, "\t%s gcloud --verbosity debug preview app deploy --version myversion ./app.yaml\tDeploy app to production\n", os.Args[0])
    42  }
    43  
    44  func main() {
    45  	flag.Usage = usage
    46  	flag.Parse()
    47  	if flag.NArg() < 1 {
    48  		usage()
    49  		os.Exit(1)
    50  	}
    51  
    52  	if err := aedeploy(); err != nil {
    53  		fmt.Fprintf(os.Stderr, os.Args[0]+": Error: %v\n", err)
    54  		os.Exit(1)
    55  	}
    56  }
    57  
    58  func aedeploy() error {
    59  	tags := []string{"appenginevm"}
    60  	app, err := analyze(tags)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	tmpDir, err := app.bundle()
    66  	if tmpDir != "" {
    67  		defer os.RemoveAll(tmpDir)
    68  	}
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	if err := os.Chdir(tmpDir); err != nil {
    74  		return fmt.Errorf("unable to chdir to %v: %v", tmpDir, err)
    75  	}
    76  	return deploy()
    77  }
    78  
    79  // deploy calls the provided command to deploy the app from the temporary directory.
    80  func deploy() error {
    81  	cmd := exec.Command(flag.Arg(0), flag.Args()[1:]...)
    82  	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
    83  	if err := cmd.Run(); err != nil {
    84  		return fmt.Errorf("unable to run %q: %v", strings.Join(flag.Args(), " "), err)
    85  	}
    86  	return nil
    87  }
    88  
    89  type app struct {
    90  	appFiles []string
    91  	imports  map[string]string
    92  }
    93  
    94  // analyze checks the app for building with the given build tags and returns
    95  // app files, and a map of full directory import names to original import names.
    96  func analyze(tags []string) (*app, error) {
    97  	ctxt := buildContext(tags)
    98  	appFiles, err := appFiles(ctxt)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	gopath := filepath.SplitList(ctxt.GOPATH)
   103  	im, err := imports(ctxt, ".", gopath)
   104  	return &app{
   105  		appFiles: appFiles,
   106  		imports:  im,
   107  	}, err
   108  }
   109  
   110  // buildContext returns the context for building the source.
   111  func buildContext(tags []string) *build.Context {
   112  	return &build.Context{
   113  		GOARCH:    "amd64",
   114  		GOOS:      "linux",
   115  		GOROOT:    build.Default.GOROOT,
   116  		GOPATH:    build.Default.GOPATH,
   117  		Compiler:  build.Default.Compiler,
   118  		BuildTags: append(build.Default.BuildTags, tags...),
   119  	}
   120  }
   121  
   122  // bundle bundles the app into a temporary directory.
   123  func (s *app) bundle() (tmpdir string, err error) {
   124  	workDir, err := ioutil.TempDir("", "aedeploy")
   125  	if err != nil {
   126  		return "", fmt.Errorf("unable to create tmpdir: %v", err)
   127  	}
   128  
   129  	for srcDir, importName := range s.imports {
   130  		dstDir := "_gopath/src/" + importName
   131  		if err := copyTree(workDir, dstDir, srcDir); err != nil {
   132  			return workDir, fmt.Errorf("unable to copy directory %v to %v: %v", srcDir, dstDir, err)
   133  		}
   134  	}
   135  	if err := copyTree(workDir, ".", "."); err != nil {
   136  		return workDir, fmt.Errorf("unable to copy root directory to /app: %v", err)
   137  	}
   138  	return workDir, nil
   139  }
   140  
   141  // imports returns a map of all import directories (recursively) used by the app.
   142  // The return value maps full directory names to original import names.
   143  func imports(ctxt *build.Context, srcDir string, gopath []string) (map[string]string, error) {
   144  	pkg, err := ctxt.ImportDir(srcDir, 0)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	// Resolve all non-standard-library imports
   150  	result := make(map[string]string)
   151  	for _, v := range pkg.Imports {
   152  		if !strings.Contains(v, ".") {
   153  			continue
   154  		}
   155  		src, err := findInGopath(v, gopath)
   156  		if err != nil {
   157  			return nil, fmt.Errorf("unable to find import %v in gopath %v: %v", v, gopath, err)
   158  		}
   159  		if _, ok := result[src]; ok { // Already processed
   160  			continue
   161  		}
   162  		result[src] = v
   163  		im, err := imports(ctxt, src, gopath)
   164  		if err != nil {
   165  			return nil, fmt.Errorf("unable to parse package %v: %v", src, err)
   166  		}
   167  		for k, v := range im {
   168  			result[k] = v
   169  		}
   170  	}
   171  	return result, nil
   172  }
   173  
   174  // findInGopath searches the gopath for the named import directory.
   175  func findInGopath(dir string, gopath []string) (string, error) {
   176  	if v, ok := gopathCache[dir]; ok {
   177  		return v, nil
   178  	}
   179  	for _, v := range gopath {
   180  		dst := filepath.Join(v, "src", dir)
   181  		if _, err := os.Stat(dst); err == nil {
   182  			gopathCache[dir] = dst
   183  			return dst, nil
   184  		}
   185  	}
   186  	return "", fmt.Errorf("unable to find package %v in gopath %v", dir, gopath)
   187  }
   188  
   189  // copyTree copies srcDir to dstDir relative to dstRoot, ignoring skipFiles.
   190  func copyTree(dstRoot, dstDir, srcDir string) error {
   191  	d := filepath.Join(dstRoot, dstDir)
   192  	if err := os.MkdirAll(d, 0755); err != nil {
   193  		return fmt.Errorf("unable to create directory %q: %v", d, err)
   194  	}
   195  
   196  	entries, err := ioutil.ReadDir(srcDir)
   197  	if err != nil {
   198  		return fmt.Errorf("unable to read dir %q: %v", srcDir, err)
   199  	}
   200  	for _, entry := range entries {
   201  		n := entry.Name()
   202  		if skipFiles[n] {
   203  			continue
   204  		}
   205  		s := filepath.Join(srcDir, n)
   206  		if entry.Mode()&os.ModeSymlink == os.ModeSymlink {
   207  			if entry, err = os.Stat(s); err != nil {
   208  				return fmt.Errorf("unable to stat %v: %v", s, err)
   209  			}
   210  		}
   211  		d := filepath.Join(dstDir, n)
   212  		if entry.IsDir() {
   213  			if err := copyTree(dstRoot, d, s); err != nil {
   214  				return fmt.Errorf("unable to copy dir %q to %q: %v", s, d, err)
   215  			}
   216  			continue
   217  		}
   218  		if err := copyFile(dstRoot, d, s); err != nil {
   219  			return fmt.Errorf("unable to copy dir %q to %q: %v", s, d, err)
   220  		}
   221  	}
   222  	return nil
   223  }
   224  
   225  // copyFile copies src to dst relative to dstRoot.
   226  func copyFile(dstRoot, dst, src string) error {
   227  	s, err := os.Open(src)
   228  	if err != nil {
   229  		return fmt.Errorf("unable to open %q: %v", src, err)
   230  	}
   231  	defer s.Close()
   232  
   233  	dst = filepath.Join(dstRoot, dst)
   234  	d, err := os.Create(dst)
   235  	if err != nil {
   236  		return fmt.Errorf("unable to create %q: %v", dst, err)
   237  	}
   238  	_, err = io.Copy(d, s)
   239  	if err != nil {
   240  		d.Close() // ignore error, copy already failed.
   241  		return fmt.Errorf("unable to copy %q to %q: %v", src, dst, err)
   242  	}
   243  	if err := d.Close(); err != nil {
   244  		return fmt.Errorf("unable to close %q: %v", dst, err)
   245  	}
   246  	return nil
   247  }
   248  
   249  // appFiles returns a list of all Go source files in the app.
   250  func appFiles(ctxt *build.Context) ([]string, error) {
   251  	pkg, err := ctxt.ImportDir(".", 0)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  	if !pkg.IsCommand() {
   256  		return nil, fmt.Errorf(`the root of your app needs to be package "main" (currently %q). Please see https://cloud.google.com/appengine/docs/go/managed-vms for more details on structuring your app.`, pkg.Name)
   257  	}
   258  	var appFiles []string
   259  	for _, f := range pkg.GoFiles {
   260  		n := filepath.Join(".", f)
   261  		appFiles = append(appFiles, n)
   262  	}
   263  	return appFiles, nil
   264  }