Commit ad6725e6 authored by Andy Cobaugh's avatar Andy Cobaugh

Add ci wait subcommand

parent f8c8a884
package cmd
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"os"
"text/template"
"time"
"github.com/apex/log"
"github.com/apex/log/handlers/cli"
"github.com/spf13/cobra"
)
// buildCmd represents the build command
var ciWaitCmd = &cobra.Command{
Use: "wait",
Short: "Wait for a deployment to be deployed by watching the live version endpoint",
RunE: runCiWaitCmd,
}
var ciWaitCommitFields = []string{"scm-commit-id", "Commit", "commit"}
var ciWaitVersionFields = []string{"Version", "version", "commit"}
var ciWaitURLs = []string{
"https://{{.Env}}-{{.Project}}.qa.k8s.psu.edu/{{.Project}}-web/resources/version",
"https://{{.Env}}-{{.Project}}.qa.k8s.psu.edu/{{.Project}}/resources/version",
"https://{{.Env}}-{{.Project}}.qa.k8s.psu.edu/resources/version",
"https://{{.Env}}-{{.Project}}.qa.k8s.psu.edu/version",
}
type ciWaitBaseURLData struct {
Project string
Env string
}
const ciWaitURLMaxTries = 10
const ciWaitURLDelay = 10 * time.Second
const ciWaitUpdateMaxTries = 30
const ciWaitUpdateDelay = 10 * time.Second
const ciWaitTimeout = 10 * time.Second
func init() {
ciCmd.AddCommand(ciWaitCmd)
}
func runCiWaitCmd(cmd *cobra.Command, args []string) error {
data := ciWaitBaseURLData{
Project: os.Getenv("CI_PROJECT_NAME"),
Env: environmentSuffix,
}
newCommit := os.Getenv("CI_COMMIT_SHA")
newVersion := os.Getenv("CI_COMMIT_TAG")
var url string
var initialBody []byte
var newBody []byte
hc := &http.Client{Timeout: ciWaitTimeout}
log.WithFields(log.Fields{
"commit": newCommit,
"version": newVersion,
"project": data.Project,
"env": data.Env,
}).Info("Waiting for new version to be available")
log.WithFields(log.Fields{
"maxtries": ciWaitURLMaxTries,
"delay": ciWaitURLDelay.String(),
}).Info("Looking for version endpoint")
// loop over possible urls
cli.Default.Padding = 2 * InitialPadding
for i := 1; i <= ciWaitURLMaxTries; i++ {
for _, u := range ciWaitURLs {
tmpl, err := template.New("URL").Parse(u)
if err != nil {
log.Fatalf("Could not parse URL template '%s': %s", u, err)
}
var urlBuf bytes.Buffer
err = tmpl.Execute(&urlBuf, data)
if err != nil {
log.Fatalf("Could not execute URL template: %s", err)
}
// check url for 200 response
log.WithField("try", i).Infof("Checking URL: %s", urlBuf.String())
resp, err := hc.Get(urlBuf.String())
if err != nil {
log.WithError(err).Error("Error fetching URL")
continue
}
defer resp.Body.Close()
log.WithFields(log.Fields{"code": resp.StatusCode}).Info("Received response")
if resp.StatusCode == http.StatusOK {
url = urlBuf.String()
initialBody, err = ioutil.ReadAll(resp.Body)
if err != nil {
log.WithError(err).Error("Could not read body")
}
break
}
}
if url == "" {
log.Infof("Sleeping for %s ...", ciWaitURLDelay.String())
time.Sleep(ciWaitURLDelay)
} else {
break
}
}
cli.Default.Padding = InitialPadding
log.WithFields(log.Fields{
"project": data.Project,
"env": data.Env,
"url": url,
"maxtries": ciWaitUpdateMaxTries,
"delay": ciWaitUpdateDelay.String(),
}).Infof("Waiting for version update")
cli.Default.Padding = 2 * InitialPadding
// wait for version to update
for i := 1; i <= ciWaitUpdateMaxTries; i++ {
log.WithField("try", i).Infof("Checking URL: %s", url)
// check version url and compare
resp, err := hc.Get(url)
if err != nil {
log.WithError(err).WithField("url", url).Error("Error fetching URL")
continue
}
defer resp.Body.Close()
log.WithFields(log.Fields{"code": resp.StatusCode}).Info("Received response")
if resp.StatusCode == http.StatusOK {
newBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.WithError(err).Error("Could not read body")
continue
}
if ciWaitVersionCompare(initialBody, newBody, newCommit, newVersion) {
log.Info("New version is live")
return nil
} else {
log.Info("No update to version endpoint")
}
}
log.Infof("Sleeping for %s ...", ciWaitUpdateDelay.String())
time.Sleep(ciWaitUpdateDelay)
}
cli.Default.Padding = InitialPadding
log.WithFields(log.Fields{
"url": url,
"old": initialBody,
"new": newBody,
"commit": newCommit,
"version": newVersion,
}).Error("Did not observe an update to the version endpoint")
return errors.New("Did not observe an update to the version endpoint")
}
// ciWaitVersionCompare returns true if:
// 1) the version element contained in newBody matches vesion
// or 2) the commit element contained in newBody matches commit
// or 3) if all else fails, newBody is different from oldBody
func ciWaitVersionCompare(initialBody []byte, newBody []byte, commit string, version string) bool {
var nb map[string]interface{}
err := json.Unmarshal(newBody, &nb)
if err != nil {
// unable to unmarshal newBody, do a straight string comparison
return string(initialBody) == string(newBody)
}
// look for long commit
for _, k := range ciWaitCommitFields {
if v := nb[k]; v != "" {
l := len(v.(string))
return v == commit[:l]
}
}
// look for version
for _, k := range ciWaitVersionFields {
if v := nb[k]; v != "" {
return v == version
}
}
return false
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment