diff --git a/main.go b/main.go index a5332ef..7a2ba41 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "text/template" @@ -52,6 +53,14 @@ type GAE struct { // and autoscaling configurations. AppFile string `json:"app_file"` + // MaxVersions is an optional value that can be used along with the "deploy" or + // "update" actions. If set to a non-zero value, the plugin will look up the versions + // of the deployed service and delete any older versions beyond the "max" value + // provided. If any of the "older" versions that should be deleted are actually + // serving traffic, they will not be deleted. This may result in the actual version + // count being higher than the max listed here. + MaxVersions int `json:"max_versions"` + // CronFile is the name of the cron.yaml file to use for this deployment. This field // is only required if your cron.yaml file is not named 'cron.yaml' CronFile string `json:"cron_file"` @@ -154,13 +163,23 @@ func wrapMain() error { } // if gcloud app cmd, run it - if found := gcloudCmds[vargs.Action]; found { - return runGcloud(runner, workspace, vargs) + if gcloudCmds[vargs.Action] { + err = runGcloud(runner, workspace, vargs) + } else { + // otherwise, do appcfg.py command + err = runAppCfg(runner, workspace, vargs) + } + + if err != nil { + return err } - // otherwise, do appcfg.py command - return runAppCfg(runner, workspace, vargs) + // check if MaxVersions is supplied + deploy action + if vargs.MaxVersions > 0 && (vargs.Action == "deploy" || vargs.Action == "update") { + return removeOldVersions(runner, workspace, vargs) + } + return nil } func configFromStdin(vargs *GAE, workspace *string) error { @@ -202,6 +221,7 @@ func configFromEnv(vargs *GAE, workspace *string) error { vargs.Dir = os.Getenv("PLUGIN_DIR") vargs.AppCfgCmd = os.Getenv("PLUGIN_APPCFG_CMD") vargs.GCloudCmd = os.Getenv("PLUGIN_GCLOUD_CMD") + vargs.MaxVersions, _ = strconv.Atoi(os.Getenv("PLUGIN_MAX_VERSIONS")) // Maps dummyVargs := dummyGAE{} @@ -357,7 +377,6 @@ func runGcloud(runner *Environ, workspace string, vargs GAE) error { if err != nil { return fmt.Errorf("error: %s\n", err) } - return nil } diff --git a/remove.go b/remove.go new file mode 100644 index 0000000..5a786fe --- /dev/null +++ b/remove.go @@ -0,0 +1,81 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + + "gopkg.in/yaml.v2" +) + +func removeOldVersions(runner *Environ, workspace string, vargs GAE) error { + // read in the app.yaml file to grab the module/service mame we just deployed + appLoc := filepath.Join(workspace, vargs.Dir, "app.yaml") + appFile, err := os.Open(appLoc) + if err != nil { + return fmt.Errorf("error: %s\n", err) + } + defer appFile.Close() + var appStruct struct { + Service string `yaml:"service"` + Module string `yaml:"module"` + } + err = yaml.NewDecoder(appFile).Decode(&appStruct) + if err != nil { + return fmt.Errorf("error: %s\n", err) + } + + service := appStruct.Service + if service == "" { + service = appStruct.Module + } + + // look up existing versions for given service ordered by create time desc + var versionJSON bytes.Buffer + sout := runner.stdout + runner.stdout = &versionJSON + err = runner.Run(vargs.GCloudCmd, "app", "versions", "list", + "--service", service, "--project", vargs.Project, + "--format", "json", "--sort-by", "~version.createTime", "--quiet") + if err != nil { + return fmt.Errorf("error: %s\n", err) + } + + var results []struct { + ID string `json:"id"` + TrafficSplit float64 `json:"traffic_split"` + } + err = json.NewDecoder(&versionJSON).Decode(&results) + if err != nil { + return err + } + + var toDelete []string + for i, res := range results { + // keep newer versions, the newly deployed version or anything that has traffic + if i < vargs.MaxVersions || res.ID == vargs.Version || res.TrafficSplit > 0 { + continue + } + toDelete = append(toDelete, res.ID) + } + + if len(toDelete) == 0 { + return nil + } + + log.Printf("deleting %d versions: %s", len(toDelete), toDelete) + + runner.stdout = sout + args := []string{"app", "versions", "delete", + "--service", service, "--project", vargs.Project, "--quiet"} + args = append(args, toDelete...) + err = runner.Run(vargs.GCloudCmd, args...) + if err != nil { + return fmt.Errorf("error: %s\n", err) + } + + return nil +}