<commit_before>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
	"sync"

	"github.com/tchaik/tchaik/index"
)

type LibraryAPI struct {
	index.Library

	collections map[string]index.Collection
	filters     map[string][]index.FilterItem
	recent      []index.Path
	searcher    index.Searcher
	players     *players
}

type libraryFileSystem struct {
	http.FileSystem
	index.Library
}

// Open implements http.FileSystem and rewrites TrackID values to their corresponding Location
// values using the index.Library
func (l *libraryFileSystem) Open(path string) (http.File, error) {
	t, ok := l.Library.Track(strings.Trim(path, "/")) // TrackIDs arrive with leading slash
	if !ok {
		return nil, fmt.Errorf("could not find track: %v", path)
	}

	loc := t.GetString("Location")
	if loc == "" {
		return nil, fmt.Errorf("invalid (empty) location for track: %v", path)
	}
	return l.FileSystem.Open(loc)
}

type group struct {
	Name        string
	Key         index.Key
	TotalTime   interface{} `json:",omitempty"`
	Artist      interface{} `json:",omitempty"`
	AlbumArtist interface{} `json:",omitempty"`
	Composer    interface{} `json:",omitempty"`
	BitRate     interface{} `json:",omitempty"`
	DiscNumber  interface{} `json:",omitempty"`
	ListStyle   interface{} `json:",omitempty"`
	TrackID     interface{} `json:",omitempty"`
	Year        interface{} `json:",omitempty"`
	Groups      []group     `json:",omitempty"`
	Tracks      []track     `json:",omitempty"`
}

type track struct {
	TrackID     string `json:",omitempty"`
	Name        string `json:",omitempty"`
	Album       string `json:",omitempty"`
	Artist      string `json:",omitempty"`
	AlbumArtist string `json:",omitempty"`
	Composer    string `json:",omitempty"`
	Year        int    `json:",omitempty"`
	DiscNumber  int    `json:",omitempty"`
	TotalTime   int    `json:",omitempty"`
	BitRate     int    `json:",omitempty"`
}

func buildCollection(h group, c index.Collection) group {
	for _, k := range c.Keys() {
		g := c.Get(k)
		g = index.FirstTrackAttr(index.StringAttr("AlbumArtist"), g)
		g = index.CommonGroupAttr([]index.Attr{index.StringAttr("Artist")}, g)
		h.Groups = append(h.Groups, group{
			Name:        g.Name(),
			Key:         k,
			AlbumArtist: g.Field("AlbumArtist"),
			Artist:      g.Field("Artist"),
		})
	}
	return h
}

func build(g index.Group, key index.Key) group {
	h := group{
		Name:        g.Name(),
		Key:         key,
		TotalTime:   g.Field("TotalTime"),
		Artist:      g.Field("Artist"),
		AlbumArtist: g.Field("AlbumArtist"),
		Composer:    g.Field("Composer"),
		Year:        g.Field("Year"),
		BitRate:     g.Field("BitRate"),
		DiscNumber:  g.Field("DiscNumber"),
		ListStyle:   g.Field("ListStyle"),
		TrackID:     g.Field("TrackID"),
	}

	if c, ok := g.(index.Collection); ok {
		return buildCollection(h, c)
	}

	getString := func(t index.Track, field string) string {
		if g.Field(field) != "" && g.Field(field) == t.GetString(field) {
			return ""
		}
		return t.GetString(field)
	}

	getInt := func(t index.Track, field string) int {
		if g.Field(field) != 0 && g.Field(field) == t.GetInt(field) {
			return 0
		}
		return t.GetInt(field)
	}

	for _, t := range g.Tracks() {
		h.Tracks = append(h.Tracks, track{
			TrackID:   t.GetString("TrackID"),
			Name:      t.GetString("Name"),
			TotalTime: t.GetInt("TotalTime"),
			// Potentially common fields (don't want to re-transmit everything)
			Album:       getString(t, "Album"),
			Artist:      getString(t, "Artist"),
			AlbumArtist: getString(t, "AlbumArtist"),
			Composer:    getString(t, "Composer"),
			Year:        getInt(t, "Year"),
			DiscNumber:  getInt(t, "DiscNumber"),
			BitRate:     getInt(t, "BitRate"),
		})
	}
	return h
}

func (l *LibraryAPI) Fetch(c index.Collection, path []string) (group, error) {
	if len(path) == 0 {
		return build(c, index.Key("Root")), nil
	}

	var g index.Group = c
	k := index.Key(path[0])
	g = c.Get(k)

	if g == nil {
		return group{}, fmt.Errorf("invalid path: near '%v'", path[0])
	}

	index.Sort(g.Tracks(), index.MultiSort(index.SortByInt("DiscNumber"), index.SortByInt("TrackNumber")))
	c = index.Collect(g, index.ByPrefix("Name"))
	g = index.SubTransform(c, index.TrimEnumPrefix)
	g = index.SumGroupIntAttr("TotalTime", g)
	commonFields := []index.Attr{
		index.StringAttr("Album"),
		index.StringAttr("Artist"),
		index.StringAttr("AlbumArtist"),
		index.StringAttr("Composer"),
		index.IntAttr("Year"),
		index.IntAttr("BitRate"),
		index.IntAttr("DiscNumber"),
	}
	g = index.CommonGroupAttr(commonFields, g)
	g = index.RemoveEmptyCollections(g)

	for i, p := range path[1:] {
		var ok bool
		c, ok = g.(index.Collection)
		if !ok {
			return group{}, fmt.Errorf("retrieved Group is not a Collection")
		}
		k = index.Key(p)
		g = c.Get(k)

		if g == nil {
			return group{}, fmt.Errorf("invalid path near '%v'", path[1:][i])
		}

		if _, ok = g.(index.Collection); !ok {
			if i == len(path[1:])-1 {
				break
			}
			return group{}, fmt.Errorf("retrieved Group isn't a Collection: %v", p)
		}
	}
	if g == nil {
		return group{}, fmt.Errorf("could not find group")
	}
	g = index.FirstTrackAttr(index.StringAttr("TrackID"), g)

	return build(g, k), nil
}

func (l *LibraryAPI) FileSystem(fs http.FileSystem) http.FileSystem {
	return &libraryFileSystem{fs, l.Library}
}

type players struct {
	sync.RWMutex
	m map[string]Player
}

func newPlayers() *players {
	return &players{m: make(map[string]Player)}
}

func (s *players) add(p Player) {
	s.Lock()
	defer s.Unlock()

	s.m[p.Key()] = p
}

func (s *players) remove(key string) {
	s.Lock()
	defer s.Unlock()

	delete(s.m, key)
}

func (s *players) get(key string) Player {
	s.RLock()
	defer s.RUnlock()

	return s.m[key]
}

func (s *players) list() []string {
	s.RLock()
	defer s.RUnlock()

	keys := make([]string, 0, len(s.m))
	for k := range s.m {
		keys = append(keys, k)
	}
	return keys
}

func (s *players) MarshalJSON() ([]byte, error) {
	keys := s.list()
	return json.Marshal(struct {
		Keys []string `json:"keys"`
	}{
		Keys: keys,
	})
}

func playersGet(l LibraryAPI, w http.ResponseWriter, r *http.Request) {
	b, err := json.Marshal(l.players)
	if err != nil {
		http.Error(w, fmt.Sprintf("error encoding JSON: %v", err), http.StatusInternalServerError)
		return
	}

	w.Header().Add("Content-Type", "application/json")
	_, err = w.Write(b)
	if err != nil {
		log.Printf("error writing response: %v", err)
	}
}

func createPlayer(l LibraryAPI, w http.ResponseWriter, r *http.Request) {
	dec := json.NewDecoder(r.Body)
	defer r.Body.Close()

	postData := struct {
		Key        string
		PlayerKeys []string
	}{}
	err := dec.Decode(&postData)
	if err != nil {
		http.Error(w, fmt.Sprintf("error parsing JSON: %v", err), http.StatusBadRequest)
		return
	}

	if p := l.players.get(postData.Key); p != nil {
		http.Error(w, "player key already exists", http.StatusBadRequest)
		return
	}

	if postData.PlayerKeys == nil || len(postData.PlayerKeys) == 0 {
		http.Error(w, "no player keys specified", http.StatusBadRequest)
		return
	}

	var players []Player
	for _, pk := range postData.PlayerKeys {
		p := l.players.get(pk)
		if p == nil {
			http.Error(w, fmt.Sprintf("invalid player key: %v", pk), http.StatusBadRequest)
			return
		}
		players = append(players, p)
	}
	l.players.add(MultiPlayer(postData.Key, players...))
	w.WriteHeader(http.StatusCreated)
}

func playerAction(p Player, w http.ResponseWriter, r *http.Request) {
	dec := json.NewDecoder(r.Body)
	defer r.Body.Close()

	putData := struct {
		Action string
		Value  interface{}
	}{}
	err := dec.Decode(&putData)
	if err != nil {
		http.Error(w, fmt.Sprintf("error parsing JSON: %v", err), http.StatusBadRequest)
		return
	}

	switch putData.Action {
	case "play":
		err = p.Play()

	case "pause":
		err = p.Pause()

	case "next":
		err = p.NextTrack()

	case "prev":
		err = p.PreviousTrack()

	case "togglePlayPause":
		err = p.TogglePlayPause()

	case "toggleMute":
		err = p.ToggleMute()

	case "setVolume":
		f, ok := putData.Value.(float64)
		if !ok {
			err = InvalidValueError("invalid volume value: expected float")
			break
		}
		err = p.SetVolume(f)

	case "setMute":
		b, ok := putData.Value.(bool)
		if !ok {
			err = InvalidValueError("invalid mute value: expected boolean")
			break
		}
		err = p.SetMute(b)

	case "setTime":
		f, ok := putData.Value.(float64)
		if !ok {
			err = InvalidValueError("invalid time value: expected float")
			break
		}
		err = p.SetTime(f)

	default:
		err = InvalidValueError("invalid action")
		return
	}

	if err != nil {
		if err, ok := err.(InvalidValueError); ok {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		http.Error(w, fmt.Sprintf("error sending player command: %v", err), http.StatusInternalServerError)
	}
}

func playerView(p Player, w http.ResponseWriter, t *http.Request) {
	enc := json.NewEncoder(w)
	err := enc.Encode(p)
	if err != nil {
		log.Printf("error encoding player data: %v", err)
		return
	}
}

func playersHandler(l LibraryAPI) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "" {
			switch r.Method {
			case "GET":
				playersGet(l, w, r)
			case "POST":
				createPlayer(l, w, r)
			}
			return
		}

		paths := strings.Split(r.URL.Path, "/")
		if len(paths) != 1 {
			http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
			return
		}

		p := l.players.get(paths[0])
		if p == nil {
			http.Error(w, "invalid player key", http.StatusBadRequest)
			return
		}

		switch r.Method {
		case "DELETE":
			l.players.remove(paths[0])
			w.WriteHeader(http.StatusNoContent)
			return

		case "PUT":
			playerAction(p, w, r)

		case "GET":
			playerView(p, w, r)
		}
	})
}
<commit_msg>Only transmit non-common Artist and AlbumArtist.<commit_after>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
	"sync"

	"github.com/tchaik/tchaik/index"
)

type LibraryAPI struct {
	index.Library

	collections map[string]index.Collection
	filters     map[string][]index.FilterItem
	recent      []index.Path
	searcher    index.Searcher
	players     *players
}

type libraryFileSystem struct {
	http.FileSystem
	index.Library
}

// Open implements http.FileSystem and rewrites TrackID values to their corresponding Location
// values using the index.Library
func (l *libraryFileSystem) Open(path string) (http.File, error) {
	t, ok := l.Library.Track(strings.Trim(path, "/")) // TrackIDs arrive with leading slash
	if !ok {
		return nil, fmt.Errorf("could not find track: %v", path)
	}

	loc := t.GetString("Location")
	if loc == "" {
		return nil, fmt.Errorf("invalid (empty) location for track: %v", path)
	}
	return l.FileSystem.Open(loc)
}

type group struct {
	Name        string
	Key         index.Key
	TotalTime   interface{} `json:",omitempty"`
	Artist      interface{} `json:",omitempty"`
	AlbumArtist interface{} `json:",omitempty"`
	Composer    interface{} `json:",omitempty"`
	BitRate     interface{} `json:",omitempty"`
	DiscNumber  interface{} `json:",omitempty"`
	ListStyle   interface{} `json:",omitempty"`
	TrackID     interface{} `json:",omitempty"`
	Year        interface{} `json:",omitempty"`
	Groups      []group     `json:",omitempty"`
	Tracks      []track     `json:",omitempty"`
}

type track struct {
	TrackID     string `json:",omitempty"`
	Name        string `json:",omitempty"`
	Album       string `json:",omitempty"`
	Artist      string `json:",omitempty"`
	AlbumArtist string `json:",omitempty"`
	Composer    string `json:",omitempty"`
	Year        int    `json:",omitempty"`
	DiscNumber  int    `json:",omitempty"`
	TotalTime   int    `json:",omitempty"`
	BitRate     int    `json:",omitempty"`
}

func buildCollection(h group, c index.Collection) group {
	getField := func(f string, g index.Group, c index.Collection) interface{} {
		if g.Field(f) == c.Field(f) {
			return nil
		}
		return g.Field(f)
	}

	for _, k := range c.Keys() {
		g := c.Get(k)
		g = index.FirstTrackAttr(index.StringAttr("AlbumArtist"), g)
		g = index.CommonGroupAttr([]index.Attr{index.StringAttr("Artist")}, g)
		h.Groups = append(h.Groups, group{
			Name:        g.Name(),
			Key:         k,
			AlbumArtist: getField("AlbumArtist", g, c),
			Artist:      getField("Artist", g, c),
		})
	}
	return h
}

func build(g index.Group, key index.Key) group {
	h := group{
		Name:        g.Name(),
		Key:         key,
		TotalTime:   g.Field("TotalTime"),
		Artist:      g.Field("Artist"),
		AlbumArtist: g.Field("AlbumArtist"),
		Composer:    g.Field("Composer"),
		Year:        g.Field("Year"),
		BitRate:     g.Field("BitRate"),
		DiscNumber:  g.Field("DiscNumber"),
		ListStyle:   g.Field("ListStyle"),
		TrackID:     g.Field("TrackID"),
	}

	if c, ok := g.(index.Collection); ok {
		return buildCollection(h, c)
	}

	getString := func(t index.Track, field string) string {
		if g.Field(field) != "" && g.Field(field) == t.GetString(field) {
			return ""
		}
		return t.GetString(field)
	}

	getInt := func(t index.Track, field string) int {
		if g.Field(field) != 0 && g.Field(field) == t.GetInt(field) {
			return 0
		}
		return t.GetInt(field)
	}

	for _, t := range g.Tracks() {
		h.Tracks = append(h.Tracks, track{
			TrackID:   t.GetString("TrackID"),
			Name:      t.GetString("Name"),
			TotalTime: t.GetInt("TotalTime"),
			// Potentially common fields (don't want to re-transmit everything)
			Album:       getString(t, "Album"),
			Artist:      getString(t, "Artist"),
			AlbumArtist: getString(t, "AlbumArtist"),
			Composer:    getString(t, "Composer"),
			Year:        getInt(t, "Year"),
			DiscNumber:  getInt(t, "DiscNumber"),
			BitRate:     getInt(t, "BitRate"),
		})
	}
	return h
}

func (l *LibraryAPI) Fetch(c index.Collection, path []string) (group, error) {
	if len(path) == 0 {
		return build(c, index.Key("Root")), nil
	}

	var g index.Group = c
	k := index.Key(path[0])
	g = c.Get(k)

	if g == nil {
		return group{}, fmt.Errorf("invalid path: near '%v'", path[0])
	}

	index.Sort(g.Tracks(), index.MultiSort(index.SortByInt("DiscNumber"), index.SortByInt("TrackNumber")))
	c = index.Collect(g, index.ByPrefix("Name"))
	g = index.SubTransform(c, index.TrimEnumPrefix)
	g = index.SumGroupIntAttr("TotalTime", g)
	commonFields := []index.Attr{
		index.StringAttr("Album"),
		index.StringAttr("Artist"),
		index.StringAttr("AlbumArtist"),
		index.StringAttr("Composer"),
		index.IntAttr("Year"),
		index.IntAttr("BitRate"),
		index.IntAttr("DiscNumber"),
	}
	g = index.CommonGroupAttr(commonFields, g)
	g = index.RemoveEmptyCollections(g)

	for i, p := range path[1:] {
		var ok bool
		c, ok = g.(index.Collection)
		if !ok {
			return group{}, fmt.Errorf("retrieved Group is not a Collection")
		}
		k = index.Key(p)
		g = c.Get(k)

		if g == nil {
			return group{}, fmt.Errorf("invalid path near '%v'", path[1:][i])
		}

		if _, ok = g.(index.Collection); !ok {
			if i == len(path[1:])-1 {
				break
			}
			return group{}, fmt.Errorf("retrieved Group isn't a Collection: %v", p)
		}
	}
	if g == nil {
		return group{}, fmt.Errorf("could not find group")
	}
	g = index.FirstTrackAttr(index.StringAttr("TrackID"), g)

	return build(g, k), nil
}

func (l *LibraryAPI) FileSystem(fs http.FileSystem) http.FileSystem {
	return &libraryFileSystem{fs, l.Library}
}

type players struct {
	sync.RWMutex
	m map[string]Player
}

func newPlayers() *players {
	return &players{m: make(map[string]Player)}
}

func (s *players) add(p Player) {
	s.Lock()
	defer s.Unlock()

	s.m[p.Key()] = p
}

func (s *players) remove(key string) {
	s.Lock()
	defer s.Unlock()

	delete(s.m, key)
}

func (s *players) get(key string) Player {
	s.RLock()
	defer s.RUnlock()

	return s.m[key]
}

func (s *players) list() []string {
	s.RLock()
	defer s.RUnlock()

	keys := make([]string, 0, len(s.m))
	for k := range s.m {
		keys = append(keys, k)
	}
	return keys
}

func (s *players) MarshalJSON() ([]byte, error) {
	keys := s.list()
	return json.Marshal(struct {
		Keys []string `json:"keys"`
	}{
		Keys: keys,
	})
}

func playersGet(l LibraryAPI, w http.ResponseWriter, r *http.Request) {
	b, err := json.Marshal(l.players)
	if err != nil {
		http.Error(w, fmt.Sprintf("error encoding JSON: %v", err), http.StatusInternalServerError)
		return
	}

	w.Header().Add("Content-Type", "application/json")
	_, err = w.Write(b)
	if err != nil {
		log.Printf("error writing response: %v", err)
	}
}

func createPlayer(l LibraryAPI, w http.ResponseWriter, r *http.Request) {
	dec := json.NewDecoder(r.Body)
	defer r.Body.Close()

	postData := struct {
		Key        string
		PlayerKeys []string
	}{}
	err := dec.Decode(&postData)
	if err != nil {
		http.Error(w, fmt.Sprintf("error parsing JSON: %v", err), http.StatusBadRequest)
		return
	}

	if p := l.players.get(postData.Key); p != nil {
		http.Error(w, "player key already exists", http.StatusBadRequest)
		return
	}

	if postData.PlayerKeys == nil || len(postData.PlayerKeys) == 0 {
		http.Error(w, "no player keys specified", http.StatusBadRequest)
		return
	}

	var players []Player
	for _, pk := range postData.PlayerKeys {
		p := l.players.get(pk)
		if p == nil {
			http.Error(w, fmt.Sprintf("invalid player key: %v", pk), http.StatusBadRequest)
			return
		}
		players = append(players, p)
	}
	l.players.add(MultiPlayer(postData.Key, players...))
	w.WriteHeader(http.StatusCreated)
}

func playerAction(p Player, w http.ResponseWriter, r *http.Request) {
	dec := json.NewDecoder(r.Body)
	defer r.Body.Close()

	putData := struct {
		Action string
		Value  interface{}
	}{}
	err := dec.Decode(&putData)
	if err != nil {
		http.Error(w, fmt.Sprintf("error parsing JSON: %v", err), http.StatusBadRequest)
		return
	}

	switch putData.Action {
	case "play":
		err = p.Play()

	case "pause":
		err = p.Pause()

	case "next":
		err = p.NextTrack()

	case "prev":
		err = p.PreviousTrack()

	case "togglePlayPause":
		err = p.TogglePlayPause()

	case "toggleMute":
		err = p.ToggleMute()

	case "setVolume":
		f, ok := putData.Value.(float64)
		if !ok {
			err = InvalidValueError("invalid volume value: expected float")
			break
		}
		err = p.SetVolume(f)

	case "setMute":
		b, ok := putData.Value.(bool)
		if !ok {
			err = InvalidValueError("invalid mute value: expected boolean")
			break
		}
		err = p.SetMute(b)

	case "setTime":
		f, ok := putData.Value.(float64)
		if !ok {
			err = InvalidValueError("invalid time value: expected float")
			break
		}
		err = p.SetTime(f)

	default:
		err = InvalidValueError("invalid action")
		return
	}

	if err != nil {
		if err, ok := err.(InvalidValueError); ok {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		http.Error(w, fmt.Sprintf("error sending player command: %v", err), http.StatusInternalServerError)
	}
}

func playerView(p Player, w http.ResponseWriter, t *http.Request) {
	enc := json.NewEncoder(w)
	err := enc.Encode(p)
	if err != nil {
		log.Printf("error encoding player data: %v", err)
		return
	}
}

func playersHandler(l LibraryAPI) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "" {
			switch r.Method {
			case "GET":
				playersGet(l, w, r)
			case "POST":
				createPlayer(l, w, r)
			}
			return
		}

		paths := strings.Split(r.URL.Path, "/")
		if len(paths) != 1 {
			http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
			return
		}

		p := l.players.get(paths[0])
		if p == nil {
			http.Error(w, "invalid player key", http.StatusBadRequest)
			return
		}

		switch r.Method {
		case "DELETE":
			l.players.remove(paths[0])
			w.WriteHeader(http.StatusNoContent)
			return

		case "PUT":
			playerAction(p, w, r)

		case "GET":
			playerView(p, w, r)
		}
	})
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"os"
	"os/exec"

	"strings"

	"github.com/cep21/gobuild/internal/golang.org/x/net/context"
	"github.com/cep21/gobuild/internal/golang.org/x/tools/cover"
	"bufio"
)

type goCoverageCheck struct {
	dirs               []string
	cache              *templateCache
	coverProfileOutTo  cmdOutputStreamer
	testStdoutOutputTo cmdOutputStreamer
	testStderrOutputTo cmdOutputStreamer
	requiredCoverage   float64
	verboseLog         logger
	errLog             logger

	fullCoverageOutput io.Writer
}

func (g *goCoverageCheck) Run(ctx context.Context) error {
	allErrs := make([]error, 0, len(g.dirs))
	allCoverProfiles := make([]string, 0, len(g.dirs))
	for _, d := range g.dirs {
		g.verboseLog.Printf("Running test %s", d)
		coverageFilename, err := g.runForDir(d)
		if err != nil {
			g.errLog.Printf("Test failure on %s: %s", d, err.Error())
			allErrs = append(allErrs, err)
		}
		if coverageFilename != "" {
			allCoverProfiles = append(allCoverProfiles, coverageFilename)
		}
	}

	if err := g.combineCoverageProfiles(allCoverProfiles); err != nil {
		allErrs = append(allErrs, err)
	}
	return multiErr(allErrs)
}

type hasName interface {
	Name() string
}

var _ hasName = &os.File{}

func (g *goCoverageCheck) combineCoverageProfiles(filenames []string) error {
	writtenHead := false
	for _, filename := range filenames {
		f, err := os.Open(filename)
		if err != nil {
			return wraperr(err, "cannot open file %s", filename)
		}
		s := bufio.NewScanner(f)
		for s.Scan() {
			curLine := s.Text()
			if strings.HasPrefix(curLine, "mode:") {
				if !writtenHead {
					if _, err := io.WriteString(g.fullCoverageOutput, curLine); err != nil {
						return wraperr(err, "cannot write to coverprofile")
					}
					if _, err := io.WriteString(g.fullCoverageOutput, "\n"); err != nil {
						return wraperr(err, "cannot write to coverprofile")
					}
					writtenHead = true
				}
				continue
			}
			if _, err := io.WriteString(g.fullCoverageOutput, curLine); err != nil {
				return wraperr(err, "cannot write to coverprofile")
			}
			if _, err := io.WriteString(g.fullCoverageOutput, "\n"); err != nil {
				return wraperr(err, "cannot write to coverprofile")
			}
		}
		if err := f.Close(); err != nil {
			return wraperr(err, "cannot close coverprofile file %s", filename)
		}
	}
	return nil
}

func (g *goCoverageCheck) runForDir(dir string) (string, error) {
	template, err := g.cache.loadInDir(dir)
	if err != nil {
		return "", wraperr(err, "unable to load cache for %s", dir)
	}
	coverArgs := append([]string{"test", "-v"}, template.TestCoverageArgs()...)
	cmdName := "go"
	coverprofile, err := g.coverProfileOutTo.GetCmdOutput(dir)
	if err != nil {
		return "", wraperr(err, "coverprofile generation failed for %s", dir)
	}
	stdout, err := g.testStdoutOutputTo.GetCmdOutput(dir)
	if err != nil {
		return "", wraperr(err, "stdout generation failed for %s", dir)
	}
	stderr, err := g.testStderrOutputTo.GetCmdOutput(dir)
	if err != nil {
		return "", wraperr(err, "stderr generation failed for %s", dir)
	}
	for _, s := range []io.Closer{stdout, stderr} {
		defer func(s io.Closer) {
			logIfErr(s.Close(), g.errLog, "could not flush test output file")
		}(s)
	}

	// Note: this panics if the coverprofile doesn't return File types
	coverprofileName := coverprofile.(hasName).Name()
	coverArgs = append(coverArgs, "-coverprofile", coverprofileName, ".")

	if err := coverprofile.Close(); err != nil {
		return "", wraperr(err, "unable to generate coverprofile file")
	}

	cmd := exec.Command(cmdName, coverArgs...)
	cmd.Stdout = stdout
	cmd.Stderr = stderr
	cmd.Dir = dir
	g.verboseLog.Printf("Running [cmd=%s args=%s dir=%s]", cmd.Path, strings.Join(cmd.Args, " "), cmd.Dir)
	err = cmd.Run()
	if err != nil {
		return coverprofileName, wraperr(err, "test failed for %s", dir)
	}

	coverage, err := calculateCoverage(coverprofileName)
	if err != nil {
		return coverprofileName, wraperr(err, "unable to calculate coverage")
	}
	if coverage+.001 < g.requiredCoverage {
		return coverprofileName, fmt.Errorf("code coverage %f < required %f for %s", coverage, g.requiredCoverage, dir)
	}
	return coverprofileName, nil
}

func calculateCoverage(coverprofile string) (float64, error) {
	profiles, err := cover.ParseProfiles(coverprofile)
	if err != nil {
		if os.IsNotExist(err) {
			return 0.0, nil
		}
		return 0.0, wraperr(err, "cannot parse coverage profile file %s", coverprofile)
	}
	total := 0
	covered := 0
	for _, profile := range profiles {
		for _, block := range profile.Blocks {
			total += block.NumStmt
			if block.Count > 0 {
				covered += block.NumStmt
			}
		}
	}
	if total == 0 {
		return 0.0, nil
	}
	return float64(covered) / float64(total) * 100, nil
}
<commit_msg>Pass check command<commit_after>package main

import (
	"fmt"
	"io"
	"os"
	"os/exec"

	"strings"

	"bufio"

	"github.com/cep21/gobuild/internal/golang.org/x/net/context"
	"github.com/cep21/gobuild/internal/golang.org/x/tools/cover"
)

type goCoverageCheck struct {
	dirs               []string
	cache              *templateCache
	coverProfileOutTo  cmdOutputStreamer
	testStdoutOutputTo cmdOutputStreamer
	testStderrOutputTo cmdOutputStreamer
	requiredCoverage   float64
	verboseLog         logger
	errLog             logger

	fullCoverageOutput io.Writer
}

func (g *goCoverageCheck) Run(ctx context.Context) error {
	allErrs := make([]error, 0, len(g.dirs))
	allCoverProfiles := make([]string, 0, len(g.dirs))
	for _, d := range g.dirs {
		g.verboseLog.Printf("Running test %s", d)
		coverageFilename, err := g.runForDir(d)
		if err != nil {
			g.errLog.Printf("Test failure on %s: %s", d, err.Error())
			allErrs = append(allErrs, err)
		}
		if coverageFilename != "" {
			allCoverProfiles = append(allCoverProfiles, coverageFilename)
		}
	}

	if err := g.combineCoverageProfiles(allCoverProfiles); err != nil {
		allErrs = append(allErrs, err)
	}
	return multiErr(allErrs)
}

type hasName interface {
	Name() string
}

var _ hasName = &os.File{}

func writeLine(w io.Writer, line string) error {
	if _, err := io.WriteString(w, line); err != nil {
		return wraperr(err, "cannot write to coverprofile")
	}
	if _, err := io.WriteString(w, "\n"); err != nil {
		return wraperr(err, "cannot write to coverprofile")
	}
	return nil
}

func (g *goCoverageCheck) combineCoverageProfiles(filenames []string) error {
	writtenHead := false
	for _, filename := range filenames {
		f, err := os.Open(filename)
		if err != nil {
			return wraperr(err, "cannot open file %s", filename)
		}
		s := bufio.NewScanner(f)
		for s.Scan() {
			curLine := s.Text()
			if strings.HasPrefix(curLine, "mode:") {
				if !writtenHead {
					if err := writeLine(g.fullCoverageOutput, curLine); err != nil {
						return wraperr(err, "cannot write to coverprofile")
					}
					writtenHead = true
				}
				continue
			}
			if err := writeLine(g.fullCoverageOutput, curLine); err != nil {
				return wraperr(err, "cannot write to coverprofile")
			}
		}
		if err := f.Close(); err != nil {
			return wraperr(err, "cannot close coverprofile file %s", filename)
		}
	}
	return nil
}

func (g *goCoverageCheck) runForDir(dir string) (string, error) {
	template, err := g.cache.loadInDir(dir)
	if err != nil {
		return "", wraperr(err, "unable to load cache for %s", dir)
	}
	coverArgs := append([]string{"test", "-v"}, template.TestCoverageArgs()...)
	cmdName := "go"
	coverprofile, err := g.coverProfileOutTo.GetCmdOutput(dir)
	if err != nil {
		return "", wraperr(err, "coverprofile generation failed for %s", dir)
	}
	stdout, err := g.testStdoutOutputTo.GetCmdOutput(dir)
	if err != nil {
		return "", wraperr(err, "stdout generation failed for %s", dir)
	}
	stderr, err := g.testStderrOutputTo.GetCmdOutput(dir)
	if err != nil {
		return "", wraperr(err, "stderr generation failed for %s", dir)
	}
	for _, s := range []io.Closer{stdout, stderr} {
		defer func(s io.Closer) {
			logIfErr(s.Close(), g.errLog, "could not flush test output file")
		}(s)
	}

	// Note: this panics if the coverprofile doesn't return File types
	coverprofileName := coverprofile.(hasName).Name()
	coverArgs = append(coverArgs, "-coverprofile", coverprofileName, ".")

	if err := coverprofile.Close(); err != nil {
		return "", wraperr(err, "unable to generate coverprofile file")
	}

	cmd := exec.Command(cmdName, coverArgs...)
	cmd.Stdout = stdout
	cmd.Stderr = stderr
	cmd.Dir = dir
	g.verboseLog.Printf("Running [cmd=%s args=%s dir=%s]", cmd.Path, strings.Join(cmd.Args, " "), cmd.Dir)
	err = cmd.Run()
	if err != nil {
		return coverprofileName, wraperr(err, "test failed for %s", dir)
	}

	coverage, err := calculateCoverage(coverprofileName)
	if err != nil {
		return coverprofileName, wraperr(err, "unable to calculate coverage")
	}
	if coverage+.001 < g.requiredCoverage {
		return coverprofileName, fmt.Errorf("code coverage %f < required %f for %s", coverage, g.requiredCoverage, dir)
	}
	return coverprofileName, nil
}

func calculateCoverage(coverprofile string) (float64, error) {
	profiles, err := cover.ParseProfiles(coverprofile)
	if err != nil {
		if os.IsNotExist(err) {
			return 0.0, nil
		}
		return 0.0, wraperr(err, "cannot parse coverage profile file %s", coverprofile)
	}
	total := 0
	covered := 0
	for _, profile := range profiles {
		for _, block := range profile.Blocks {
			total += block.NumStmt
			if block.Count > 0 {
				covered += block.NumStmt
			}
		}
	}
	if total == 0 {
		return 0.0, nil
	}
	return float64(covered) / float64(total) * 100, nil
}
<|endoftext|>


<commit_before>// Copyright 2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Time process execution.
//
// Synopsis:
//     time CMD [ARG]...
//
// Description:
//     After executing CMD, its real, user and system times are printed to
//     stderr in the POSIX format.
//
// Example:
//     $ time sleep 1.23s
//     real 1.23
//     user 0.00
//     sys 0.00
//
// Note:
//     This is different from bash's time command which is built into the shell
//     and can time the entire pipeline.
//
// Bugs:
//     Time is not reported when exiting due to a signal.
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"syscall"
	"time"
)

func printTime(label string, t time.Duration) {
	fmt.Fprintf(os.Stderr, "%s %.03f\n", label, t.Seconds())
}

func main() {
	flag.Parse()
	if flag.NArg() == 0 {
		log.Fatal("too few arguments")
	}

	// Run command
	cmd := exec.Command(flag.Arg(0), flag.Args()[1:]...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	start := time.Now()
	err := cmd.Run()
	real := time.Since(start)

	// Check for io erros.
	exitErr, ok := err.(*exec.ExitError)
	if err != nil && !ok {
		log.Fatal(err)
	}

	// Print usage.
	printTime("real", real)
	printTime("user", cmd.ProcessState.SystemTime())
	printTime("sys", cmd.ProcessState.UserTime())

	// Propagate return value.
	if exitErr != nil {
		os.Exit(exitErr.Sys().(syscall.WaitStatus).ExitStatus())
	}
}
<commit_msg>Fix a few mistakes in the time command<commit_after>// Copyright 2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Time process execution.
//
// Synopsis:
//     time CMD [ARG]...
//
// Description:
//     After executing CMD, its real, user and system times are printed to
//     stderr in the POSIX format.
//
// Example:
//     $ time sleep 1.23s
//     real 1.230
//     user 0.001
//     sys 0.000
//
// Note:
//     This is different from bash's time command which is built into the shell
//     and can time the entire pipeline.
//
// Bugs:
//     Time is not reported when exiting due to a signal.
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"syscall"
	"time"
)

func printTime(label string, t time.Duration) {
	fmt.Fprintf(os.Stderr, "%s %.03f\n", label, t.Seconds())
}

func main() {
	flag.Parse()
	if flag.NArg() == 0 {
		log.Fatal("too few arguments")
	}

	// Run command
	cmd := exec.Command(flag.Arg(0), flag.Args()[1:]...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	start := time.Now()
	err := cmd.Run()
	realTime := time.Since(start)

	// Check for io errors.
	exitErr, ok := err.(*exec.ExitError)
	if err != nil && !ok {
		log.Fatal(err)
	}

	// Print usage.
	printTime("real", realTime)
	printTime("user", cmd.ProcessState.UserTime())
	printTime("sys", cmd.ProcessState.SystemTime())

	// Propagate return value.
	if exitErr != nil {
		os.Exit(exitErr.Sys().(syscall.WaitStatus).ExitStatus())
	}
}
<|endoftext|>


<commit_before>package jetpack

import "crypto/sha512"
import "encoding/json"
import "fmt"
import "io"
import "io/ioutil"
import "os"
import "path"
import "path/filepath"
import "time"

import "code.google.com/p/go-uuid/uuid"
import "github.com/appc/spec/schema"
import "github.com/appc/spec/schema/types"
import "github.com/juju/errors"

import "../run"
import "../zfs"

const imageSnapshotName = "seal"

type Image struct {
	UUID     uuid.UUID            `json:"-"`
	Host     *Host                `json:"-"`
	Manifest schema.ImageManifest `json:"-"`

	Hash      *types.Hash `json:",omitempty"`
	Origin    string
	Timestamp time.Time

	rootfs *zfs.Dataset
}

func NewImage(h *Host, id uuid.UUID) *Image {
	if id == nil {
		id = uuid.NewRandom()
	}
	return &Image{Host: h, UUID: id, Manifest: *schema.BlankImageManifest()}
}

func LoadImage(h *Host, id uuid.UUID) (*Image, error) {
	if id == nil {
		return nil, errors.New("No UUID given")
	}
	img := NewImage(h, id)
	if img.IsEmpty() {
		return nil, ErrNotFound
	}
	if err := img.Load(); err != nil {
		return nil, err
	}
	return img, nil
}

func (img *Image) Path(elem ...string) string {
	return img.Host.Path(append(
		[]string{"images", img.UUID.String()},
		elem...,
	)...)
}

func (img *Image) getRootfs() *zfs.Dataset {
	if img.rootfs == nil {
		ds, err := img.Host.Dataset.GetDataset(path.Join("images", img.UUID.String()))
		if err != nil {
			panic(err)
		}
		img.rootfs = ds
	}
	return img.rootfs
}

func (img *Image) IsEmpty() bool {
	_, err := os.Stat(img.Path("manifest"))
	return os.IsNotExist(err)
}

func (img *Image) Load() error {
	if img.IsEmpty() {
		return errors.New("Image is empty")
	}

	metadataJSON, err := ioutil.ReadFile(img.Path("metadata"))
	if err != nil {
		return errors.Trace(err)
	}

	if err = json.Unmarshal(metadataJSON, img); err != nil {
		return errors.Trace(err)
	}

	if err := img.loadManifest(); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (img *Image) Seal() error {
	// Make sure that manifest exists and validates correctly
	if err := img.loadManifest(); err != nil {
		return errors.Trace(err)
	}

	if img.Hash == nil {
		amiPath := ""
		if img.Host.Properties.GetBool("images.ami.store", false) {
			amiPath = img.Path("ami")
		}

		if hash, err := img.SaveAMI(amiPath, 0400); err != nil {
			return errors.Trace(err)
		} else {
			img.Hash = hash
		}
	}

	// Serialize metadata
	if metadataJSON, err := json.Marshal(img); err != nil {
		return errors.Trace(err)
	} else {
		if err := ioutil.WriteFile(img.Path("metadata"), metadataJSON, 0400); err != nil {
			return errors.Trace(err)
		}
	}

	if err := os.Symlink(img.UUID.String(), img.Path("..", img.Hash.String())); err != nil {
		return errors.Trace(err)
	}

	if _, err := img.getRootfs().Snapshot(imageSnapshotName); err != nil {
		return errors.Trace(err)
	}

	if err := img.getRootfs().Zfs("set", "readonly=on"); err != nil {
		return errors.Trace(err)
	}

	return nil
}

// Save image to an AMI file, return its hash.
//
// If path is an empty string, don't save the image, just return the
// hash. If path is "-", print image to stdout.
func (img *Image) SaveAMI(path string, perm os.FileMode) (*types.Hash, error) {
	archiver := run.Command("tar", "-C", img.Path(), "-c", "-f", "-", "manifest", "rootfs")
	if archive, err := archiver.StdoutPipe(); err != nil {
		return nil, errors.Trace(err)
	} else {
		hash := sha512.New()
		faucet := io.TeeReader(archive, hash)
		sink := ioutil.Discard
		var compressor *run.Cmd = nil

		if path != "" {
			if path == "-" {
				sink = os.Stdout
			} else {
				if f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm); err != nil {
					return nil, errors.Trace(err)
				} else {
					defer f.Close()
					sink = f
				}
			}

			if compression := img.Host.Properties.GetString("images.ami.compression", "no"); compression != "none" {
				switch compression {
				case "xz":
					compressor = run.Command("xz", "-z", "-c")
				case "bzip2":
					compressor = run.Command("bzip2", "-z", "-c")
				case "gz":
				case "gzip":
					compressor = run.Command("gzip", "-c")
				default:
					return nil, errors.Errorf("Invalid setting images.ami.compression=%#v (allowed values: xz, bzip2, gzip, none)", compression)
				}

				compressor.Cmd.Stdout = sink
				if cin, err := compressor.StdinPipe(); err != nil {
					return nil, errors.Trace(err)
				} else {
					sink = cin
				}
			}
		}

		if err := archiver.Start(); err != nil {
			return nil, errors.Trace(err)
		}

		if compressor != nil {
			if err := compressor.Start(); err != nil {
				archiver.Cmd.Process.Kill()
				return nil, errors.Trace(err)
			}
		}

		if _, err := io.Copy(sink, faucet); err != nil {
			return nil, errors.Trace(err)
		}

		if hash, err := types.NewHash(fmt.Sprintf("sha512-%x", hash.Sum(nil))); err != nil {
			// CAN'T HAPPEN, srsly
			return nil, errors.Trace(err)
		} else {
			return hash, nil
		}
	}
}

func (img *Image) loadManifest() error {
	manifestJSON, err := ioutil.ReadFile(img.Path("manifest"))
	if err != nil {
		return errors.Trace(err)
	}

	if err = json.Unmarshal(manifestJSON, &img.Manifest); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (img *Image) Destroy() (err error) {
	err = errors.Trace(img.getRootfs().Destroy("-r"))
	if img.Hash != nil {
		if err2 := os.Remove(img.Path("..", img.Hash.String())); err2 != nil && err == nil {
			err = errors.Trace(err2)
		}
		if err2 := os.RemoveAll(img.Path()); err2 != nil && err == nil {
			err = errors.Trace(err2)
		}
	}
	return
}

func (img *Image) Clone(dest, mountpoint string) (*zfs.Dataset, error) {
	snap, err := img.getRootfs().GetSnapshot(imageSnapshotName)
	if err != nil {
		return nil, errors.Trace(err)
	}

	ds, err := snap.Clone(dest, "-o", "mountpoint="+mountpoint)
	if err != nil {
		return nil, errors.Trace(err)
	}

	return ds, nil
}

func (img *Image) RuntimeApp() schema.RuntimeApp {
	app := schema.RuntimeApp{
		Name:  img.Manifest.Name,
		Image: schema.RuntimeImage{Name: &img.Manifest.Name},
	}
	app.Annotations.Set("jetpack/image-uuid", img.UUID.String())
	if img.Hash != nil {
		app.Image.ID = *img.Hash
	} else {
		// TODO: do we really need to store ACI tarballs to have an image ID on built images?
		app.Image.ID.Set(fmt.Sprintf(
			"sha512-000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000%032x",
			img.UUID,
		))
	}
	return app
}

func (img *Image) buildPodManifest(exec []string) *schema.PodManifest {
	bpm := schema.BlankPodManifest()

	// Figure out working path that doesn't exist in the image's rootfs
	workDir := ".jetpack.build."
	for {
		if _, err := os.Stat(img.getRootfs().Path(workDir)); err != nil {
			if os.IsNotExist(err) {
				break
			}
			panic(err)
		}
		workDir = fmt.Sprintf(".jetpack.build.%v", uuid.NewRandom())
	}

	bprta := img.RuntimeApp()
	bprta.Name.Set("jetpack/build")
	bprta.App = &types.App{
		Exec:             exec,
		WorkingDirectory: "/" + workDir,
		User:             "0",
		Group:            "0",
	}
	bpm.Apps = append(bpm.Apps, bprta)

	// This is needed by freebsd-update at least, should be okay to
	// allow this in builders.
	bpm.Annotations.Set("jetpack/jail.conf/allow.chflags", "true")

	return bpm
}

func (img *Image) Build(buildDir string, addFiles []string, buildExec []string) (*Image, error) {
	buildPod, err := img.Host.CreatePod(img.buildPodManifest(buildExec))
	if err != nil {
		return nil, errors.Trace(err)
	}

	fullWorkDir := buildPod.Path("rootfs", buildPod.Manifest.Apps[0].App.WorkingDirectory)
	if err := os.Mkdir(fullWorkDir, 0700); err != nil {
		return nil, errors.Trace(err)
	}

	if buildDir[len(buildDir)-1] != '/' {
		buildDir += "/"
	}

	cpArgs := []string{"-R", buildDir}
	if addFiles != nil {
		cpArgs = append(cpArgs, addFiles...)
	}
	cpArgs = append(cpArgs, fullWorkDir)

	if err := run.Command("cp", cpArgs...).Run(); err != nil {
		return nil, errors.Trace(err)
	}

	if err := buildPod.RunApp(buildPod.Manifest.Apps[0].Name); err != nil {
		return nil, errors.Trace(err)
	}

	if err := buildPod.Kill(); err != nil {
		return nil, errors.Trace(err)
	}

	manifestBytes, err := ioutil.ReadFile(filepath.Join(fullWorkDir, "manifest.json"))
	if err != nil {
		return nil, errors.Trace(err)
	}

	if err := os.RemoveAll(fullWorkDir); err != nil {
		return nil, errors.Trace(err)
	}

	if err := os.Remove(buildPod.Path("rootfs/etc/resolv.conf")); err != nil && !os.IsNotExist(err) {
		return nil, errors.Trace(err)
	}

	// Pivot pod into an image
	childImage := NewImage(img.Host, buildPod.UUID)

	ds := buildPod.getDataset() // FIXME: getDataset()
	if err := ds.Set("mountpoint", childImage.Path("rootfs")); err != nil {
		return nil, errors.Trace(err)
	}

	if err := buildPod.getDataset().Rename(img.Host.Dataset.ChildName(path.Join("images", childImage.UUID.String()))); err != nil {
		return nil, errors.Trace(err)
	}

	// Construct the child image's manifest

	if err := json.Unmarshal(manifestBytes, &childImage.Manifest); err != nil {
		return nil, errors.Trace(err)
	}

	// We don't need build pod anymore
	if err := os.RemoveAll(buildPod.Path()); err != nil {
		return nil, errors.Trace(err)
	}
	buildPod = nil

	if _, ok := childImage.Manifest.Annotations.Get("timestamp"); !ok {
		if ts, err := time.Now().MarshalText(); err != nil {
			return nil, errors.Trace(err)
		} else {
			childImage.Manifest.Annotations.Set("timestamp", string(ts))
		}
	}

	for _, label := range []string{"os", "arch"} {
		if childValue, ok := childImage.Manifest.GetLabel(label); !ok {
			// if child has no os/arch, copy from parent
			if parentValue, ok := img.Manifest.GetLabel(label); ok {
				childImage.Manifest.Labels = append(childImage.Manifest.Labels,
					types.Label{Name: types.ACName(label), Value: parentValue})
			}
		} else if childValue == "" {
			// if child explicitly set to nil or empty string, remove the
			// label
			for i, l := range childImage.Manifest.Labels {
				if string(l.Name) == label {
					childImage.Manifest.Labels = append(
						childImage.Manifest.Labels[:i],
						childImage.Manifest.Labels[i+1:]...)
					break
				}
			}
		}
	}

	// TODO: should we merge app from parent image?

	childImage.Origin = img.UUID.String()

	if manifestBytes, err := json.Marshal(childImage.Manifest); err != nil {
		return nil, errors.Trace(err)
	} else {
		if err := ioutil.WriteFile(childImage.Path("manifest"), manifestBytes, 0400); err != nil {
			return nil, errors.Trace(err)
		}
	}

	childImage.Timestamp = time.Now()

	if err := childImage.Seal(); err != nil {
		return nil, errors.Trace(err)
	}

	return childImage, nil
}
<commit_msg>Make image building work with per-app rootfs<commit_after>package jetpack

import "crypto/sha512"
import "encoding/json"
import "fmt"
import "io"
import "io/ioutil"
import "os"
import "path"
import "path/filepath"
import "time"

import "code.google.com/p/go-uuid/uuid"
import "github.com/appc/spec/schema"
import "github.com/appc/spec/schema/types"
import "github.com/juju/errors"

import "../run"
import "../zfs"

const imageSnapshotName = "seal"

type Image struct {
	UUID     uuid.UUID            `json:"-"`
	Host     *Host                `json:"-"`
	Manifest schema.ImageManifest `json:"-"`

	Hash      *types.Hash `json:",omitempty"`
	Origin    string
	Timestamp time.Time

	rootfs *zfs.Dataset
}

func NewImage(h *Host, id uuid.UUID) *Image {
	if id == nil {
		id = uuid.NewRandom()
	}
	return &Image{Host: h, UUID: id, Manifest: *schema.BlankImageManifest()}
}

func LoadImage(h *Host, id uuid.UUID) (*Image, error) {
	if id == nil {
		return nil, errors.New("No UUID given")
	}
	img := NewImage(h, id)
	if img.IsEmpty() {
		return nil, ErrNotFound
	}
	if err := img.Load(); err != nil {
		return nil, err
	}
	return img, nil
}

func (img *Image) Path(elem ...string) string {
	return img.Host.Path(append(
		[]string{"images", img.UUID.String()},
		elem...,
	)...)
}

func (img *Image) getRootfs() *zfs.Dataset {
	if img.rootfs == nil {
		ds, err := img.Host.Dataset.GetDataset(path.Join("images", img.UUID.String()))
		if err != nil {
			panic(err)
		}
		img.rootfs = ds
	}
	return img.rootfs
}

func (img *Image) IsEmpty() bool {
	_, err := os.Stat(img.Path("manifest"))
	return os.IsNotExist(err)
}

func (img *Image) Load() error {
	if img.IsEmpty() {
		return errors.New("Image is empty")
	}

	metadataJSON, err := ioutil.ReadFile(img.Path("metadata"))
	if err != nil {
		return errors.Trace(err)
	}

	if err = json.Unmarshal(metadataJSON, img); err != nil {
		return errors.Trace(err)
	}

	if err := img.loadManifest(); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (img *Image) Seal() error {
	// Make sure that manifest exists and validates correctly
	if err := img.loadManifest(); err != nil {
		return errors.Trace(err)
	}

	if img.Hash == nil {
		amiPath := ""
		if img.Host.Properties.GetBool("images.ami.store", false) {
			amiPath = img.Path("ami")
		}

		if hash, err := img.SaveAMI(amiPath, 0400); err != nil {
			return errors.Trace(err)
		} else {
			img.Hash = hash
		}
	}

	// Serialize metadata
	if metadataJSON, err := json.Marshal(img); err != nil {
		return errors.Trace(err)
	} else {
		if err := ioutil.WriteFile(img.Path("metadata"), metadataJSON, 0400); err != nil {
			return errors.Trace(err)
		}
	}

	if err := os.Symlink(img.UUID.String(), img.Path("..", img.Hash.String())); err != nil {
		return errors.Trace(err)
	}

	if _, err := img.getRootfs().Snapshot(imageSnapshotName); err != nil {
		return errors.Trace(err)
	}

	if err := img.getRootfs().Zfs("set", "readonly=on"); err != nil {
		return errors.Trace(err)
	}

	return nil
}

// Save image to an AMI file, return its hash.
//
// If path is an empty string, don't save the image, just return the
// hash. If path is "-", print image to stdout.
func (img *Image) SaveAMI(path string, perm os.FileMode) (*types.Hash, error) {
	archiver := run.Command("tar", "-C", img.Path(), "-c", "-f", "-", "manifest", "rootfs")
	if archive, err := archiver.StdoutPipe(); err != nil {
		return nil, errors.Trace(err)
	} else {
		hash := sha512.New()
		faucet := io.TeeReader(archive, hash)
		sink := ioutil.Discard
		var compressor *run.Cmd = nil

		if path != "" {
			if path == "-" {
				sink = os.Stdout
			} else {
				if f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm); err != nil {
					return nil, errors.Trace(err)
				} else {
					defer f.Close()
					sink = f
				}
			}

			if compression := img.Host.Properties.GetString("images.ami.compression", "no"); compression != "none" {
				switch compression {
				case "xz":
					compressor = run.Command("xz", "-z", "-c")
				case "bzip2":
					compressor = run.Command("bzip2", "-z", "-c")
				case "gz":
				case "gzip":
					compressor = run.Command("gzip", "-c")
				default:
					return nil, errors.Errorf("Invalid setting images.ami.compression=%#v (allowed values: xz, bzip2, gzip, none)", compression)
				}

				compressor.Cmd.Stdout = sink
				if cin, err := compressor.StdinPipe(); err != nil {
					return nil, errors.Trace(err)
				} else {
					sink = cin
				}
			}
		}

		if err := archiver.Start(); err != nil {
			return nil, errors.Trace(err)
		}

		if compressor != nil {
			if err := compressor.Start(); err != nil {
				archiver.Cmd.Process.Kill()
				return nil, errors.Trace(err)
			}
		}

		if _, err := io.Copy(sink, faucet); err != nil {
			return nil, errors.Trace(err)
		}

		if hash, err := types.NewHash(fmt.Sprintf("sha512-%x", hash.Sum(nil))); err != nil {
			// CAN'T HAPPEN, srsly
			return nil, errors.Trace(err)
		} else {
			return hash, nil
		}
	}
}

func (img *Image) loadManifest() error {
	manifestJSON, err := ioutil.ReadFile(img.Path("manifest"))
	if err != nil {
		return errors.Trace(err)
	}

	if err = json.Unmarshal(manifestJSON, &img.Manifest); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (img *Image) Destroy() (err error) {
	err = errors.Trace(img.getRootfs().Destroy("-r"))
	if img.Hash != nil {
		if err2 := os.Remove(img.Path("..", img.Hash.String())); err2 != nil && err == nil {
			err = errors.Trace(err2)
		}
		if err2 := os.RemoveAll(img.Path()); err2 != nil && err == nil {
			err = errors.Trace(err2)
		}
	}
	return
}

func (img *Image) Clone(dest, mountpoint string) (*zfs.Dataset, error) {
	snap, err := img.getRootfs().GetSnapshot(imageSnapshotName)
	if err != nil {
		return nil, errors.Trace(err)
	}

	ds, err := snap.Clone(dest, "-o", "mountpoint="+mountpoint)
	if err != nil {
		return nil, errors.Trace(err)
	}

	return ds, nil
}

func (img *Image) RuntimeApp() schema.RuntimeApp {
	app := schema.RuntimeApp{
		Name:  img.Manifest.Name,
		Image: schema.RuntimeImage{Name: &img.Manifest.Name},
	}
	app.Annotations.Set("jetpack/image-uuid", img.UUID.String())
	if img.Hash != nil {
		app.Image.ID = *img.Hash
	} else {
		// TODO: do we really need to store ACI tarballs to have an image ID on built images?
		app.Image.ID.Set(fmt.Sprintf(
			"sha512-000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000%032x",
			img.UUID,
		))
	}
	return app
}

func (img *Image) buildPodManifest(exec []string) *schema.PodManifest {
	bpm := schema.BlankPodManifest()

	// Figure out working path that doesn't exist in the image's rootfs
	workDir := ".jetpack.build."
	for {
		if _, err := os.Stat(img.getRootfs().Path(workDir)); err != nil {
			if os.IsNotExist(err) {
				break
			}
			panic(err)
		}
		workDir = fmt.Sprintf(".jetpack.build.%v", uuid.NewRandom())
	}

	bprta := img.RuntimeApp()
	bprta.Name.Set("jetpack/build")
	bprta.App = &types.App{
		Exec:             exec,
		WorkingDirectory: "/" + workDir,
		User:             "0",
		Group:            "0",
	}
	bpm.Apps = append(bpm.Apps, bprta)

	// This is needed by freebsd-update at least, should be okay to
	// allow this in builders.
	bpm.Annotations.Set("jetpack/jail.conf/allow.chflags", "true")

	return bpm
}

func (img *Image) Build(buildDir string, addFiles []string, buildExec []string) (*Image, error) {
	buildPod, err := img.Host.CreatePod(img.buildPodManifest(buildExec))
	if err != nil {
		return nil, errors.Trace(err)
	}

	fullWorkDir := buildPod.Path("rootfs/0", buildPod.Manifest.Apps[0].App.WorkingDirectory)
	if err := os.Mkdir(fullWorkDir, 0700); err != nil {
		return nil, errors.Trace(err)
	}

	if buildDir[len(buildDir)-1] != '/' {
		buildDir += "/"
	}

	cpArgs := []string{"-R", buildDir}
	if addFiles != nil {
		cpArgs = append(cpArgs, addFiles...)
	}
	cpArgs = append(cpArgs, fullWorkDir)

	if err := run.Command("cp", cpArgs...).Run(); err != nil {
		return nil, errors.Trace(err)
	}

	if err := buildPod.RunApp(buildPod.Manifest.Apps[0].Name); err != nil {
		return nil, errors.Trace(err)
	}

	if err := buildPod.Kill(); err != nil {
		return nil, errors.Trace(err)
	}

	manifestBytes, err := ioutil.ReadFile(filepath.Join(fullWorkDir, "manifest.json"))
	if err != nil {
		return nil, errors.Trace(err)
	}

	if err := os.RemoveAll(fullWorkDir); err != nil {
		return nil, errors.Trace(err)
	}

	if err := os.Remove(buildPod.Path("rootfs/0/etc/resolv.conf")); err != nil && !os.IsNotExist(err) {
		return nil, errors.Trace(err)
	}

	// Pivot pod into an image
	childImage := NewImage(img.Host, buildPod.UUID)

	ds, err := img.Host.Dataset.GetDataset(path.Join("pods", buildPod.UUID.String(), "rootfs.0"))
	if err != nil {
		return nil, errors.Trace(err)
	}

	if err := ds.Set("mountpoint", childImage.Path("rootfs")); err != nil {
		return nil, errors.Trace(err)
	}

	if err := buildPod.getDataset().Rename(img.Host.Dataset.ChildName(path.Join("images", childImage.UUID.String()))); err != nil {
		return nil, errors.Trace(err)
	}

	// Construct the child image's manifest

	if err := json.Unmarshal(manifestBytes, &childImage.Manifest); err != nil {
		return nil, errors.Trace(err)
	}

	// We don't need build pod anymore
	if err := os.RemoveAll(buildPod.Path()); err != nil {
		return nil, errors.Trace(err)
	}
	buildPod = nil

	if _, ok := childImage.Manifest.Annotations.Get("timestamp"); !ok {
		if ts, err := time.Now().MarshalText(); err != nil {
			return nil, errors.Trace(err)
		} else {
			childImage.Manifest.Annotations.Set("timestamp", string(ts))
		}
	}

	for _, label := range []string{"os", "arch"} {
		if childValue, ok := childImage.Manifest.GetLabel(label); !ok {
			// if child has no os/arch, copy from parent
			if parentValue, ok := img.Manifest.GetLabel(label); ok {
				childImage.Manifest.Labels = append(childImage.Manifest.Labels,
					types.Label{Name: types.ACName(label), Value: parentValue})
			}
		} else if childValue == "" {
			// if child explicitly set to nil or empty string, remove the
			// label
			for i, l := range childImage.Manifest.Labels {
				if string(l.Name) == label {
					childImage.Manifest.Labels = append(
						childImage.Manifest.Labels[:i],
						childImage.Manifest.Labels[i+1:]...)
					break
				}
			}
		}
	}

	// TODO: should we merge app from parent image?

	childImage.Origin = img.UUID.String()

	if manifestBytes, err := json.Marshal(childImage.Manifest); err != nil {
		return nil, errors.Trace(err)
	} else {
		if err := ioutil.WriteFile(childImage.Path("manifest"), manifestBytes, 0400); err != nil {
			return nil, errors.Trace(err)
		}
	}

	childImage.Timestamp = time.Now()

	if err := childImage.Seal(); err != nil {
		return nil, errors.Trace(err)
	}

	return childImage, nil
}
<|endoftext|>


<commit_before>package graceful

import (
	"crypto/tls"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

// Server wraps an http.Server with graceful connection handling.
// It may be used directly in the same way as http.Server, or may
// be constructed with the global functions in this package.
type Server struct {
	// Timeout is the duration to allow outstanding requests to survive
	// before forcefully terminating them.
	Timeout   time.Duration

	// ConnState specifies an optional callback function that is
	// called when a client connection changes state. This is a proxy
	// to the underlying http.Server's ConnState, and the original
	// must not be set directly.
	ConnState func(net.Conn, http.ConnState)

	// interrupt signals the listener to stop serving connections,
	// and the server to shut down.
	interrupt chan os.Signal

	*http.Server
}

// Run serves the http.Handler with graceful shutdown enabled.
//
// timeout is the duration to wait until killing active requests and stopping the server.
// If timeout is 0, the server never times out. It waits for all active requests to finish.
func Run(addr string, timeout time.Duration, n http.Handler) {
	srv := &Server{
		Timeout: timeout,
		Server:  &http.Server{Addr: addr, Handler: n},
	}

	if err := srv.ListenAndServe(); err != nil {
		if opErr, ok := err.(*net.OpError); !ok || (ok && opErr.Op != "accept") {
			logger := log.New(os.Stdout, "[graceful] ", 0)
			logger.Fatal(err)
		}
	}
}

// ListenAndServe is equivalent to http.Server.ListenAndServe with graceful shutdown enabled.
//
// timeout is the duration to wait until killing active requests and stopping the server.
// If timeout is 0, the server never times out. It waits for all active requests to finish.
func ListenAndServe(server *http.Server, timeout time.Duration) error {
	srv := &Server{Timeout: timeout, Server: server}
	return srv.ListenAndServe()
}

// ListenAndServe is equivalent to http.Server.ListenAndServe with graceful shutdown enabled.
func (srv *Server) ListenAndServe() error {
	// Create the listener so we can control their lifetime
	addr := srv.Addr
	if addr == "" {
		addr = ":http"
	}
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	return srv.Serve(l)
}

// ListenAndServeTLS is equivalent to http.Server.ListenAndServeTLS with graceful shutdown enabled.
//
// timeout is the duration to wait until killing active requests and stopping the server.
// If timeout is 0, the server never times out. It waits for all active requests to finish.
func ListenAndServeTLS(server *http.Server, certFile, keyFile string, timeout time.Duration) error {
	// Create the listener ourselves so we can control its lifetime
	srv := &Server{Timeout: timeout, Server: server}
	addr := srv.Addr
	if addr == "" {
		addr = ":https"
	}

	config := &tls.Config{}
	if srv.TLSConfig != nil {
		*config = *srv.TLSConfig
	}
	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}

	var err error
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return err
	}

	conn, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	tlsListener := tls.NewListener(conn, config)
	return srv.Serve(tlsListener)
}

// Serve is equivalent to http.Server.Serve with graceful shutdown enabled.
//
// timeout is the duration to wait until killing active requests and stopping the server.
// If timeout is 0, the server never times out. It waits for all active requests to finish.
func Serve(server *http.Server, l net.Listener, timeout time.Duration) error {
	srv := &Server{Timeout: timeout, Server: server}
	return srv.Serve(l)
}

// Serve is equivalent to http.Server.Serve with graceful shutdown enabled.
func (srv *Server) Serve(listener net.Listener) error {
	// Track connection state
	add := make(chan net.Conn)
	remove := make(chan net.Conn)

	srv.Server.ConnState = func(conn net.Conn, state http.ConnState) {
		switch state {
		case http.StateActive:
			add <- conn
		case http.StateClosed, http.StateIdle:
			remove <- conn
		}

		if hook := srv.ConnState; hook != nil {
			hook(conn, state)
		}
	}

	// Manage open connections
	stop := make(chan chan struct{})
	kill := make(chan struct{})
	go func() {
		var done chan struct{}
		connections := map[net.Conn]struct{}{}
		for {
			select {
			case conn := <-add:
				connections[conn] = struct{}{}
			case conn := <-remove:
				delete(connections, conn)
				if done != nil && len(connections) == 0 {
					done <- struct{}{}
					return
				}
			case done = <-stop:
				if len(connections) == 0 {
					done <- struct{}{}
					return
				}
			case <-kill:
				for k := range connections {
					k.Close()
				}
				return
			}
		}
	}()

	if srv.interrupt == nil {
		srv.interrupt = make(chan os.Signal, 1)
	}

	// Set up the interrupt catch
	signal.Notify(srv.interrupt, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		for _ = range srv.interrupt {
			srv.SetKeepAlivesEnabled(false)
			listener.Close()
			signal.Stop(srv.interrupt)
			close(srv.interrupt)
		}
	}()

	// Serve with graceful listener
	err := srv.Server.Serve(listener)

	// Request done notification
	done := make(chan struct{})
	stop <- done

	if srv.Timeout > 0 {
		select {
		case <-done:
		case <-time.After(srv.Timeout):
			kill <- struct{}{}
		}
	} else {
		<-done
	}
	return err
}
<commit_msg>Add example and run fmt<commit_after>package graceful

import (
	"crypto/tls"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

// Server wraps an http.Server with graceful connection handling.
// It may be used directly in the same way as http.Server, or may
// be constructed with the global functions in this package.
//
// Example:
//	srv := &graceful.Server{
//		Timeout: 5 * time.Second,
//		Server: &http.Server{Addr: ":1234", Handler: handler},
//	}
//	srv.ListenAndServe()
type Server struct {
	// Timeout is the duration to allow outstanding requests to survive
	// before forcefully terminating them.
	Timeout time.Duration

	// ConnState specifies an optional callback function that is
	// called when a client connection changes state. This is a proxy
	// to the underlying http.Server's ConnState, and the original
	// must not be set directly.
	ConnState func(net.Conn, http.ConnState)

	// interrupt signals the listener to stop serving connections,
	// and the server to shut down.
	interrupt chan os.Signal

	*http.Server
}

// Run serves the http.Handler with graceful shutdown enabled.
//
// timeout is the duration to wait until killing active requests and stopping the server.
// If timeout is 0, the server never times out. It waits for all active requests to finish.
func Run(addr string, timeout time.Duration, n http.Handler) {
	srv := &Server{
		Timeout: timeout,
		Server:  &http.Server{Addr: addr, Handler: n},
	}

	if err := srv.ListenAndServe(); err != nil {
		if opErr, ok := err.(*net.OpError); !ok || (ok && opErr.Op != "accept") {
			logger := log.New(os.Stdout, "[graceful] ", 0)
			logger.Fatal(err)
		}
	}
}

// ListenAndServe is equivalent to http.Server.ListenAndServe with graceful shutdown enabled.
//
// timeout is the duration to wait until killing active requests and stopping the server.
// If timeout is 0, the server never times out. It waits for all active requests to finish.
func ListenAndServe(server *http.Server, timeout time.Duration) error {
	srv := &Server{Timeout: timeout, Server: server}
	return srv.ListenAndServe()
}

// ListenAndServe is equivalent to http.Server.ListenAndServe with graceful shutdown enabled.
func (srv *Server) ListenAndServe() error {
	// Create the listener so we can control their lifetime
	addr := srv.Addr
	if addr == "" {
		addr = ":http"
	}
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	return srv.Serve(l)
}

// ListenAndServeTLS is equivalent to http.Server.ListenAndServeTLS with graceful shutdown enabled.
//
// timeout is the duration to wait until killing active requests and stopping the server.
// If timeout is 0, the server never times out. It waits for all active requests to finish.
func ListenAndServeTLS(server *http.Server, certFile, keyFile string, timeout time.Duration) error {
	// Create the listener ourselves so we can control its lifetime
	srv := &Server{Timeout: timeout, Server: server}
	addr := srv.Addr
	if addr == "" {
		addr = ":https"
	}

	config := &tls.Config{}
	if srv.TLSConfig != nil {
		*config = *srv.TLSConfig
	}
	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}

	var err error
	config.Certificates = make([]tls.Certificate, 1)
	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return err
	}

	conn, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	tlsListener := tls.NewListener(conn, config)
	return srv.Serve(tlsListener)
}

// Serve is equivalent to http.Server.Serve with graceful shutdown enabled.
//
// timeout is the duration to wait until killing active requests and stopping the server.
// If timeout is 0, the server never times out. It waits for all active requests to finish.
func Serve(server *http.Server, l net.Listener, timeout time.Duration) error {
	srv := &Server{Timeout: timeout, Server: server}
	return srv.Serve(l)
}

// Serve is equivalent to http.Server.Serve with graceful shutdown enabled.
func (srv *Server) Serve(listener net.Listener) error {
	// Track connection state
	add := make(chan net.Conn)
	remove := make(chan net.Conn)

	srv.Server.ConnState = func(conn net.Conn, state http.ConnState) {
		switch state {
		case http.StateActive:
			add <- conn
		case http.StateClosed, http.StateIdle:
			remove <- conn
		}

		if hook := srv.ConnState; hook != nil {
			hook(conn, state)
		}
	}

	// Manage open connections
	stop := make(chan chan struct{})
	kill := make(chan struct{})
	go func() {
		var done chan struct{}
		connections := map[net.Conn]struct{}{}
		for {
			select {
			case conn := <-add:
				connections[conn] = struct{}{}
			case conn := <-remove:
				delete(connections, conn)
				if done != nil && len(connections) == 0 {
					done <- struct{}{}
					return
				}
			case done = <-stop:
				if len(connections) == 0 {
					done <- struct{}{}
					return
				}
			case <-kill:
				for k := range connections {
					k.Close()
				}
				return
			}
		}
	}()

	if srv.interrupt == nil {
		srv.interrupt = make(chan os.Signal, 1)
	}

	// Set up the interrupt catch
	signal.Notify(srv.interrupt, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		for _ = range srv.interrupt {
			srv.SetKeepAlivesEnabled(false)
			listener.Close()
			signal.Stop(srv.interrupt)
			close(srv.interrupt)
		}
	}()

	// Serve with graceful listener
	err := srv.Server.Serve(listener)

	// Request done notification
	done := make(chan struct{})
	stop <- done

	if srv.Timeout > 0 {
		select {
		case <-done:
		case <-time.After(srv.Timeout):
			kill <- struct{}{}
		}
	} else {
		<-done
	}
	return err
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"os"
	"os/exec"

	"github.com/leancloud/lean-cli/logo"
	"github.com/leancloud/lean-cli/version"
	"github.com/urfave/cli"
)

// Run the command line
func Run(args []string) {
	// add banner text to help text
	cli.AppHelpTemplate = logo.Logo() + cli.AppHelpTemplate
	cli.SubcommandHelpTemplate = logo.Logo() + cli.SubcommandHelpTemplate

	app := cli.NewApp()
	app.Name = "lean"
	app.Version = version.Version
	app.Usage = "Command line to manage and deploy LeanCloud apps"
	app.EnableBashCompletion = true

	app.CommandNotFound = thirdPartyCommand

	app.Commands = []cli.Command{
		{
			Name:      "login",
			Usage:     "Log in to LeanCloud",
			Action:    wrapAction(loginAction),
			ArgsUsage: "[-u username -p password (--region <CN> | <US> | <TAB>)]",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "username,u",
					Usage: "Username",
				},
				cli.StringFlag{
					Name:  "password,p",
					Usage: "Password",
				},
				cli.StringFlag{
					Name:  "region,r",
					Usage: "The LeanCloud region to log in to (e.g., US, CN)",
				},
			},
		},
		{
			Name:      "metric",
			Usage:     "Obtain LeanStorage performance metrics of current project",
			Action:    wrapAction(statusAction),
			ArgsUsage: "[--from fromTime --to toTime --format default|json]",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "from",
					Usage: "Start date, formatted as YYYY-MM-DD，e.g., 1926-08-17",
				},
				cli.StringFlag{
					Name:  "to",
					Usage: "End date formatted as YYYY-MM-DD，e.g., 1926-08-17",
				},
				cli.StringFlag{
					Name:  "format",
					Usage: "Output format，'default' or 'json'",
				},
			},
		},
		{
			Name:   "info",
			Usage:  "Show information about the current user and app",
			Action: wrapAction(infoAction),
		},
		{
			Name:   "up",
			Usage:  "Start a development instance locally",
			Action: wrapAction(upAction),
			Flags: []cli.Flag{
				cli.IntFlag{
					Name:  "port,p",
					Usage: "Local port to listen on",
					Value: 3000,
				},
				cli.IntFlag{
					Name:  "console-port,c",
					Usage: "Port of the debug console",
				},
				cli.StringFlag{
					Name:  "cmd",
					Usage: "Command to start the project, other arguments except --console-port are ignored",
				},
			},
		},
		{
			Name:      "init",
			Usage:     "Initialize a LeanEngine project",
			Action:    wrapAction(initAction),
			ArgsUsage: "[dest]",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "region",
					Usage: "LeanCloud region for the project",
				},
				cli.StringFlag{
					Name:  "group",
					Usage: "LeanEngine group",
				},
			},
		},
		{
			Name:      "switch",
			Usage:     "Change the associated LeanCloud app",
			Action:    wrapAction(switchAction),
			ArgsUsage: "[appID | appName]",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "region",
					Usage: "LeanCloud region",
				},
				cli.StringFlag{
					Name:  "group",
					Usage: "LeanEngine group",
				},
			},
		},
		{
			Name:   "deploy",
			Usage:  "Deploy the project to LeanEngine",
			Action: wrapAction(deployAction),
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "g",
					Usage: "Deploy from git repo",
				},
				cli.BoolFlag{
					Name:  "war",
					Usage: "Deploy .war file for Java project. The first .war file in target/ is used by default",
				},
				cli.BoolFlag{
					Name:  "no-cache",
					Usage: "Force download dependencies",
				},
				cli.StringFlag{
					Name:  "leanignore",
					Usage: "Rule file for ignored files in deployment",
					Value: ".leanignore",
				},
				cli.StringFlag{
					Name:  "message,m",
					Usage: "Comment for this deployment, only applicable when deploying from local files",
				},
				cli.BoolFlag{
					Name: "keep-deploy-file",
				},
				cli.StringFlag{
					Name:  "revision,r",
					Usage: "Git revision or branch. Only applicable when deploying from Git",
					Value: "master",
				},
				cli.StringFlag{
					Name:  "options",
					Usage: "Send additional deploy options to server, in urlencode format(like `--options build-root=app&atomic=true`)",
				},
				cli.StringFlag{
					Name:  "prod",
					Usage: "Deploy to production(`--prod 1`) or staging(`--prod 0`) environment, default to staging if it exists",
				},
			},
		},
		{
			Name:   "publish",
			Usage:  "Publish code from staging to production",
			Action: wrapAction(publishAction),
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "options",
					Usage: "Send additional deploy options to server, in urlencode format(like `--options build-root=app&atomic=true`)",
				},
			},
		},
		{
			Name:      "upload",
			Usage:     "Upload files to the current application (available in the '_File' class)",
			Action:    uploadAction,
			ArgsUsage: "<file-path> <file-path> ...",
		},
		{
			Name:   "logs",
			Usage:  "Show LeanEngine logs",
			Action: wrapAction(logsAction),
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "f",
					Usage: "Wait for and continuously show most recent logs",
				},
				cli.StringFlag{
					Name:  "env,e",
					Usage: "Environment to view (staging / production)",
					Value: "production",
				},
				cli.IntFlag{
					Name:  "limit,l",
					Usage: "Maximum number of lines to show",
					Value: 30,
				},
				cli.StringFlag{
					Name:  "from",
					Usage: "Start date formatted as YYYY-MM-DD (local time) or RFC3339，e.g., 2006-01-02 or 2006-01-02T15:04:05Z",
				},
				cli.StringFlag{
					Name:  "to",
					Usage: "End date formated as YYYY-MM-DD (local time) or RFC3339, e.g., 2006-01-02 or 2006-01-02T15:04:05Z",
				},
				cli.StringFlag{
					Name:  "format",
					Usage: "Format to use ('default' or 'json')",
					Value: "default",
				},
			},
		},
		{
			Name:   "debug",
			Usage:  "Start the debug console without running the project",
			Action: wrapAction(debugAction),
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "remote,r",
					Usage: "URL of target app",
					Value: "http://localhost:3000",
				},
				cli.StringFlag{
					Name:  "app-id",
					Usage: "Target AppID, use the AppID of the current project by default",
				},
				cli.IntFlag{
					Name:  "port,p",
					Usage: "Port to listen on",
					Value: 3001,
				},
			},
		},
		{
			Name:   "env",
			Usage:  "Output environment variables used by the current project",
			Action: wrapAction(envAction),
			Flags: []cli.Flag{
				cli.IntFlag{
					Name:  "port,p",
					Usage: "Local port for the app (affects value of LC_APP_PORT)",
					Value: 3000,
				},
				cli.StringFlag{
					Name:  "template",
					Usage: "Template for output, 'export {{name}}={{value}}' by default",
				},
			},
			Subcommands: []cli.Command{
				{
					Name:      "set",
					Usage:     "Set the value of an environment variable",
					Action:    wrapAction(envSetAction),
					ArgsUsage: "[env-name] [env-value]",
				},
				{
					Name:      "unset",
					Usage:     "Delete an environment variable",
					Action:    wrapAction(envUnsetAction),
					ArgsUsage: "[env-name]",
				},
			},
		},
		{
			Name:   "cache",
			Usage:  "LeanCache shell",
			Action: wrapAction(cacheAction),
			Flags: []cli.Flag{
				cli.IntFlag{
					Name:  "db",
					Usage: "Number of LeanCache DB",
					Value: -1,
				},
				cli.StringFlag{
					Name:  "name",
					Usage: "Name of LeanCache instance",
				},
				cli.StringFlag{
					Name:  "eval",
					Usage: "LeanCache command to run",
				},
			},
			Subcommands: []cli.Command{
				{
					Name:   "list",
					Usage:  "Show LeanCache instances of the current project",
					Action: wrapAction(cacheListAction),
				},
			},
		},
		{
			Name:   "cql",
			Usage:  "Start CQL interactive mode (warn: CQL is deprecated)",
			Action: wrapAction(cqlAction),
			Flags: []cli.Flag{
				cli.StringFlag{Name: "format,f",
					Usage: "CQL result format",
					Value: "table",
				},
				cli.StringFlag{
					Name:  "eval",
					Usage: "CQL command to run",
				},
			},
		},
		{
			Name:    "help",
			Aliases: []string{"h"},
			Usage:   "Show all commands or help info for one command",
			Action: func(c *cli.Context) error {
				args := c.Args()
				if args.Present() {
					_, err := fmt.Printf("Please use `lean %s -h` for subcommand usage.\n", args.First())
					return err
				}
				return cli.ShowAppHelp(c)
			},
		},
	}

	app.Before = func(c *cli.Context) error {
		args := []string{"--_collect-stats"}
		args = append(args, c.Args()...)
		err := exec.Command(os.Args[0], args...).Start()
		_ = err
		return nil
	}

	app.Run(args)
}
<commit_msg>docs: logs --from/to use +08:00 example<commit_after>package commands

import (
	"fmt"
	"os"
	"os/exec"

	"github.com/leancloud/lean-cli/logo"
	"github.com/leancloud/lean-cli/version"
	"github.com/urfave/cli"
)

// Run the command line
func Run(args []string) {
	// add banner text to help text
	cli.AppHelpTemplate = logo.Logo() + cli.AppHelpTemplate
	cli.SubcommandHelpTemplate = logo.Logo() + cli.SubcommandHelpTemplate

	app := cli.NewApp()
	app.Name = "lean"
	app.Version = version.Version
	app.Usage = "Command line to manage and deploy LeanCloud apps"
	app.EnableBashCompletion = true

	app.CommandNotFound = thirdPartyCommand

	app.Commands = []cli.Command{
		{
			Name:      "login",
			Usage:     "Log in to LeanCloud",
			Action:    wrapAction(loginAction),
			ArgsUsage: "[-u username -p password (--region <CN> | <US> | <TAB>)]",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "username,u",
					Usage: "Username",
				},
				cli.StringFlag{
					Name:  "password,p",
					Usage: "Password",
				},
				cli.StringFlag{
					Name:  "region,r",
					Usage: "The LeanCloud region to log in to (e.g., US, CN)",
				},
			},
		},
		{
			Name:      "metric",
			Usage:     "Obtain LeanStorage performance metrics of current project",
			Action:    wrapAction(statusAction),
			ArgsUsage: "[--from fromTime --to toTime --format default|json]",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "from",
					Usage: "Start date, formatted as YYYY-MM-DD，e.g., 1926-08-17",
				},
				cli.StringFlag{
					Name:  "to",
					Usage: "End date formatted as YYYY-MM-DD，e.g., 1926-08-17",
				},
				cli.StringFlag{
					Name:  "format",
					Usage: "Output format，'default' or 'json'",
				},
			},
		},
		{
			Name:   "info",
			Usage:  "Show information about the current user and app",
			Action: wrapAction(infoAction),
		},
		{
			Name:   "up",
			Usage:  "Start a development instance locally",
			Action: wrapAction(upAction),
			Flags: []cli.Flag{
				cli.IntFlag{
					Name:  "port,p",
					Usage: "Local port to listen on",
					Value: 3000,
				},
				cli.IntFlag{
					Name:  "console-port,c",
					Usage: "Port of the debug console",
				},
				cli.StringFlag{
					Name:  "cmd",
					Usage: "Command to start the project, other arguments except --console-port are ignored",
				},
			},
		},
		{
			Name:      "init",
			Usage:     "Initialize a LeanEngine project",
			Action:    wrapAction(initAction),
			ArgsUsage: "[dest]",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "region",
					Usage: "LeanCloud region for the project",
				},
				cli.StringFlag{
					Name:  "group",
					Usage: "LeanEngine group",
				},
			},
		},
		{
			Name:      "switch",
			Usage:     "Change the associated LeanCloud app",
			Action:    wrapAction(switchAction),
			ArgsUsage: "[appID | appName]",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "region",
					Usage: "LeanCloud region",
				},
				cli.StringFlag{
					Name:  "group",
					Usage: "LeanEngine group",
				},
			},
		},
		{
			Name:   "deploy",
			Usage:  "Deploy the project to LeanEngine",
			Action: wrapAction(deployAction),
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "g",
					Usage: "Deploy from git repo",
				},
				cli.BoolFlag{
					Name:  "war",
					Usage: "Deploy .war file for Java project. The first .war file in target/ is used by default",
				},
				cli.BoolFlag{
					Name:  "no-cache",
					Usage: "Force download dependencies",
				},
				cli.StringFlag{
					Name:  "leanignore",
					Usage: "Rule file for ignored files in deployment",
					Value: ".leanignore",
				},
				cli.StringFlag{
					Name:  "message,m",
					Usage: "Comment for this deployment, only applicable when deploying from local files",
				},
				cli.BoolFlag{
					Name: "keep-deploy-file",
				},
				cli.StringFlag{
					Name:  "revision,r",
					Usage: "Git revision or branch. Only applicable when deploying from Git",
					Value: "master",
				},
				cli.StringFlag{
					Name:  "options",
					Usage: "Send additional deploy options to server, in urlencode format(like `--options build-root=app&atomic=true`)",
				},
				cli.StringFlag{
					Name:  "prod",
					Usage: "Deploy to production(`--prod 1`) or staging(`--prod 0`) environment, default to staging if it exists",
				},
			},
		},
		{
			Name:   "publish",
			Usage:  "Publish code from staging to production",
			Action: wrapAction(publishAction),
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "options",
					Usage: "Send additional deploy options to server, in urlencode format(like `--options build-root=app&atomic=true`)",
				},
			},
		},
		{
			Name:      "upload",
			Usage:     "Upload files to the current application (available in the '_File' class)",
			Action:    uploadAction,
			ArgsUsage: "<file-path> <file-path> ...",
		},
		{
			Name:   "logs",
			Usage:  "Show LeanEngine logs",
			Action: wrapAction(logsAction),
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "f",
					Usage: "Wait for and continuously show most recent logs",
				},
				cli.StringFlag{
					Name:  "env,e",
					Usage: "Environment to view (staging / production)",
					Value: "production",
				},
				cli.IntFlag{
					Name:  "limit,l",
					Usage: "Maximum number of lines to show",
					Value: 30,
				},
				cli.StringFlag{
					Name:  "from",
					Usage: "Start date formatted as YYYY-MM-DD (local time) or RFC3339，e.g., 2006-01-02 or 2006-01-02T15:04:05+08:00",
				},
				cli.StringFlag{
					Name:  "to",
					Usage: "End date formated as YYYY-MM-DD (local time) or RFC3339, e.g., 2006-01-02 or 2006-01-02T15:04:05+08:00",
				},
				cli.StringFlag{
					Name:  "format",
					Usage: "Format to use ('default' or 'json')",
					Value: "default",
				},
			},
		},
		{
			Name:   "debug",
			Usage:  "Start the debug console without running the project",
			Action: wrapAction(debugAction),
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "remote,r",
					Usage: "URL of target app",
					Value: "http://localhost:3000",
				},
				cli.StringFlag{
					Name:  "app-id",
					Usage: "Target AppID, use the AppID of the current project by default",
				},
				cli.IntFlag{
					Name:  "port,p",
					Usage: "Port to listen on",
					Value: 3001,
				},
			},
		},
		{
			Name:   "env",
			Usage:  "Output environment variables used by the current project",
			Action: wrapAction(envAction),
			Flags: []cli.Flag{
				cli.IntFlag{
					Name:  "port,p",
					Usage: "Local port for the app (affects value of LC_APP_PORT)",
					Value: 3000,
				},
				cli.StringFlag{
					Name:  "template",
					Usage: "Template for output, 'export {{name}}={{value}}' by default",
				},
			},
			Subcommands: []cli.Command{
				{
					Name:      "set",
					Usage:     "Set the value of an environment variable",
					Action:    wrapAction(envSetAction),
					ArgsUsage: "[env-name] [env-value]",
				},
				{
					Name:      "unset",
					Usage:     "Delete an environment variable",
					Action:    wrapAction(envUnsetAction),
					ArgsUsage: "[env-name]",
				},
			},
		},
		{
			Name:   "cache",
			Usage:  "LeanCache shell",
			Action: wrapAction(cacheAction),
			Flags: []cli.Flag{
				cli.IntFlag{
					Name:  "db",
					Usage: "Number of LeanCache DB",
					Value: -1,
				},
				cli.StringFlag{
					Name:  "name",
					Usage: "Name of LeanCache instance",
				},
				cli.StringFlag{
					Name:  "eval",
					Usage: "LeanCache command to run",
				},
			},
			Subcommands: []cli.Command{
				{
					Name:   "list",
					Usage:  "Show LeanCache instances of the current project",
					Action: wrapAction(cacheListAction),
				},
			},
		},
		{
			Name:   "cql",
			Usage:  "Start CQL interactive mode (warn: CQL is deprecated)",
			Action: wrapAction(cqlAction),
			Flags: []cli.Flag{
				cli.StringFlag{Name: "format,f",
					Usage: "CQL result format",
					Value: "table",
				},
				cli.StringFlag{
					Name:  "eval",
					Usage: "CQL command to run",
				},
			},
		},
		{
			Name:    "help",
			Aliases: []string{"h"},
			Usage:   "Show all commands or help info for one command",
			Action: func(c *cli.Context) error {
				args := c.Args()
				if args.Present() {
					_, err := fmt.Printf("Please use `lean %s -h` for subcommand usage.\n", args.First())
					return err
				}
				return cli.ShowAppHelp(c)
			},
		},
	}

	app.Before = func(c *cli.Context) error {
		args := []string{"--_collect-stats"}
		args = append(args, c.Args()...)
		err := exec.Command(os.Args[0], args...).Start()
		_ = err
		return nil
	}

	app.Run(args)
}
<|endoftext|>