github.com/koron/hk@v0.0.0-20150303213137-b8aeaa3ab34c/releases.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"log"
     7  	"os"
     8  	"sort"
     9  	"strings"
    10  	"text/tabwriter"
    11  	"time"
    12  
    13  	"github.com/heroku/hk/Godeps/_workspace/src/github.com/bgentry/heroku-go"
    14  )
    15  
    16  var releaseCount int
    17  
    18  var cmdReleases = &Command{
    19  	Run:      runReleases,
    20  	Usage:    "releases [-n <limit>] [<version>...]",
    21  	NeedsApp: true,
    22  	Category: "release",
    23  	Short:    "list releases",
    24  	Long: `
    25  Lists releases. Shows the version of the release (e.g. v1), who
    26  made the release, time of the release, and description.
    27  
    28  Options:
    29  
    30      -n <limit>  maximum number of recent releases to display
    31  
    32  Examples:
    33  
    34      $ hk releases
    35      v1  bob@test.com  Jun 12 18:28  Deploy 3ae20c2
    36      v2  john@me.com   Jun 13 18:14  Deploy 0fda0ae
    37      v3  john@me.com   Jun 13 18:31  Rollback to v2
    38  
    39      $ hk releases -n 2
    40      v2  john  Jun 13 18:14  Deploy 0fda0ae
    41      v3  john  Jun 13 18:31  Rollback to v2
    42  
    43      $ hk releases 1 3
    44      v1  bob@test.com  Jun 12 18:28  Deploy 3ae20c2
    45      v3  john@me.com   Jun 13 18:31  Rollback to v2
    46  `,
    47  }
    48  
    49  func init() {
    50  	cmdReleases.Flag.IntVarP(&releaseCount, "number", "n", 20, "max number of recent releases to display")
    51  }
    52  
    53  func runReleases(cmd *Command, versions []string) {
    54  	w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
    55  	defer w.Flush()
    56  	listReleases(w, versions)
    57  }
    58  
    59  func listReleases(w io.Writer, versions []string) {
    60  	appname := mustApp()
    61  	if len(versions) == 0 {
    62  		hrels, err := client.ReleaseList(appname, &heroku.ListRange{
    63  			Field:      "version",
    64  			Max:        releaseCount,
    65  			Descending: true,
    66  		})
    67  		must(err)
    68  		rels := make([]*Release, len(hrels))
    69  		for i := range hrels {
    70  			rels[i] = newRelease(&hrels[i])
    71  		}
    72  		sort.Sort(releasesByVersion(rels))
    73  		gitDescribe(rels)
    74  		abbrevEmailReleases(rels)
    75  		for _, r := range rels {
    76  			listRelease(w, r)
    77  		}
    78  		return
    79  	}
    80  
    81  	var rels []*Release
    82  	relch := make(chan *heroku.Release, len(versions))
    83  	errch := make(chan error, len(versions))
    84  	for _, name := range versions {
    85  		if name == "" {
    86  			relch <- nil
    87  		} else {
    88  			go func(relname string) {
    89  				if rel, err := client.ReleaseInfo(appname, relname); err != nil {
    90  					errch <- err
    91  				} else {
    92  					relch <- rel
    93  				}
    94  			}(strings.TrimPrefix(name, "v"))
    95  		}
    96  	}
    97  	for _ = range versions {
    98  		select {
    99  		case err := <-errch:
   100  			printFatal(err.Error())
   101  		case rel := <-relch:
   102  			if rel != nil {
   103  				rels = append(rels, newRelease(rel))
   104  			}
   105  		}
   106  	}
   107  	sort.Sort(releasesByVersion(rels))
   108  	gitDescribe(rels)
   109  	abbrevEmailReleases(rels)
   110  	for _, r := range rels {
   111  		listRelease(w, r)
   112  	}
   113  }
   114  
   115  func abbrevEmailReleases(rels []*Release) {
   116  	domains := make(map[string]int)
   117  	for _, r := range rels {
   118  		r.Who = r.User.Email
   119  		if a := strings.SplitN(r.Who, "@", 2); len(a) == 2 {
   120  			domains["@"+a[1]]++
   121  		}
   122  	}
   123  	smax, nmax := "", 0
   124  	for s, n := range domains {
   125  		if n > nmax {
   126  			smax = s
   127  		}
   128  	}
   129  	for _, r := range rels {
   130  		if strings.HasSuffix(r.Who, smax) {
   131  			r.Who = r.Who[:len(r.Who)-len(smax)]
   132  		}
   133  	}
   134  }
   135  
   136  func listRelease(w io.Writer, r *Release) {
   137  	desc := r.Description
   138  	// add the git tag to the description if it's not a hash (and thus isn't
   139  	// included already)
   140  	if r.Commit != "" && !strings.Contains(r.Description, r.Commit) {
   141  		desc += " (" + abbrev(r.Commit, 12) + ")"
   142  	}
   143  	listRec(w,
   144  		fmt.Sprintf("v%d", r.Version),
   145  		abbrev(r.Who, 10),
   146  		prettyTime{r.CreatedAt},
   147  		desc,
   148  	)
   149  }
   150  
   151  type releasesByVersion []*Release
   152  
   153  func (a releasesByVersion) Len() int           { return len(a) }
   154  func (a releasesByVersion) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   155  func (a releasesByVersion) Less(i, j int) bool { return a[i].Version < a[j].Version }
   156  
   157  func newRelease(rel *heroku.Release) *Release {
   158  	return &Release{*rel, "", ""}
   159  }
   160  
   161  var cmdReleaseInfo = &Command{
   162  	Run:      runReleaseInfo,
   163  	Usage:    "release-info <version>",
   164  	NeedsApp: true,
   165  	Category: "release",
   166  	Short:    "show release info",
   167  	Long: `
   168  release-info shows detailed information about a release.
   169  
   170  Examples:
   171  
   172      $ hk release-info v116
   173      Version:  v116
   174      By:       user@test.com
   175      Change:   Deploy 62b3059
   176      When:     2014-01-13T21:20:57Z
   177      Id:       abcd1234-5678-def0-8190-12347060474d
   178      Slug:     98765432-82ba-10ba-fedc-8d206789d062
   179  `,
   180  }
   181  
   182  func runReleaseInfo(cmd *Command, args []string) {
   183  	appname := mustApp()
   184  	if len(args) != 1 {
   185  		cmd.PrintUsage()
   186  		os.Exit(2)
   187  	}
   188  	ver := strings.TrimPrefix(args[0], "v")
   189  	rel, err := client.ReleaseInfo(appname, ver)
   190  	must(err)
   191  
   192  	fmt.Printf("Version:  v%d\n", rel.Version)
   193  	fmt.Printf("By:       %s\n", rel.User.Email)
   194  	fmt.Printf("Change:   %s\n", rel.Description)
   195  	fmt.Printf("When:     %s\n", rel.CreatedAt.UTC().Format(time.RFC3339))
   196  	fmt.Printf("Id:       %s\n", rel.Id)
   197  	if rel.Slug != nil {
   198  		fmt.Printf("Slug:     %s\n", rel.Slug.Id)
   199  	}
   200  }
   201  
   202  var cmdRollback = &Command{
   203  	Run:      runRollback,
   204  	Usage:    "rollback <version>",
   205  	NeedsApp: true,
   206  	Category: "release",
   207  	Short:    "roll back to a previous release",
   208  	Long: `
   209  Rollback re-releases an app at an older version. This action
   210  creates a new release based on the older release, then restarts
   211  the app's dynos on the new release.
   212  
   213  Examples:
   214  
   215      $ hk rollback v4
   216      Rolled back myapp to v4 as v7.
   217  `,
   218  }
   219  
   220  func runRollback(cmd *Command, args []string) {
   221  	appname := mustApp()
   222  	if len(args) != 1 {
   223  		cmd.PrintUsage()
   224  		os.Exit(2)
   225  	}
   226  	ver := strings.TrimPrefix(args[0], "v")
   227  	rel, err := client.ReleaseRollback(appname, ver)
   228  	must(err)
   229  	log.Printf("Rolled back %s to v%s as v%d.\n", appname, ver, rel.Version)
   230  }