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 }