Skip to content

Commit

Permalink
Merge pull request #47 from NYTimes/max_versions
Browse files Browse the repository at this point in the history
Adding "max_versions" feature to clean up older GAE versions post-deploy
  • Loading branch information
jprobinson authored Feb 20, 2019
2 parents 5263cad + 89903dc commit dbf4282
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 5 deletions.
29 changes: 24 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"text/template"

Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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{}
Expand Down Expand Up @@ -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
}

Expand Down
81 changes: 81 additions & 0 deletions remove.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit dbf4282

Please sign in to comment.