<commit_before>package views

import (
	"fmt"
	"log"

	"github.com/cswank/kcli/internal/colors"
	"github.com/cswank/kcli/internal/kafka"
	ui "github.com/jroimartin/gocui"
)

var (
	pg pages

	head *header
	bod  *body
	foot *footer
	hlp  *help

	currentView string

	c1, c2, c3 colors.Colorer

	//After gets called by main when the gui is closed (if it's not nil)
	After func()
)

func init() {
	c1, c2, c3 = getColors()
	helpMsg = getHelpMsg()
}

type coords struct {
	x1 int
	x2 int
	y1 int
	y2 int
}

type View interface {
	Render(g *ui.Gui, v *ui.View) error
}

func GetLayout(width, height int) func(g *ui.Gui) error {
	head = newHeader(width, height)
	bod = newBody(width, height)
	foot = newFooter(width, height)
	hlp = newHelp(width, height)

	ui.DefaultEditor = foot

	currentView = bod.name

	p, err := getTopics(bod.size, "")
	if err != nil {
		log.Fatal(err)
	}

	pg = pages{
		p: []page{p},
	}

	return func(g *ui.Gui) error {
		w, h := g.Size()
		if h != height || w != width {
			width = w
			height = h
			head.resize(w, h)
			bod.resize(w, h)
			foot.resize(w, h)
		}
		v, err := g.SetView(head.name, head.coords.x1, head.coords.y1, head.coords.x2, head.coords.y2)
		if err != nil && err != ui.ErrUnknownView {
			return err
		}
		v.Frame = false
		if err := head.Render(g, v); err != nil {
			return err
		}

		v, err = g.SetView(bod.name, bod.coords.x1, bod.coords.y1, bod.coords.x2, bod.coords.y2)
		if err != nil && err != ui.ErrUnknownView {
			return err
		}

		v.Frame = false

		if err := bod.Render(g, v); err != nil {
			return err
		}

		v, err = g.SetView(foot.name, foot.coords.x1, foot.coords.y1, foot.coords.x2, foot.coords.y2)
		if err != nil && err != ui.ErrUnknownView {
			return err
		}
		v.Frame = false
		v.Editable = true

		_, err = g.SetCurrentView(currentView)
		return err
	}
}

func next(g *ui.Gui, v *ui.View) error {
	_, cur := v.Cursor()
	if cur < bod.size-1 && cur < len(pg.body())-1 {
		cur++
	}
	return v.SetCursor(0, cur)
}

func prev(g *ui.Gui, v *ui.View) error {
	_, cur := v.Cursor()
	if cur > 0 {
		cur--
	}
	return v.SetCursor(0, cur)
}

func forward(g *ui.Gui, v *ui.View) error {
	if err := pg.forward(); err != nil {
		return err
	}
	return v.SetCursor(0, 0)
}

func back(g *ui.Gui, v *ui.View) error {
	if err := pg.back(); err != nil {
		return err
	}
	return v.SetCursor(0, 0)
}

//sel gets called when the user hits the enter key.
//The item under the cursor is selected and the next()
//func is called to get then next page.
func sel(g *ui.Gui, v *ui.View) error {
	_, cur := v.Cursor()
	_, size := v.Size()

	p, r := pg.sel(cur)
	n, err := p.next(size, r.args)
	if err != nil {
		return err
	}

	pg.add(n)
	return v.SetCursor(0, 0)
}

func popPage(g *ui.Gui, v *ui.View) error {
	pg.pop()
	return v.SetCursor(0, pg.cursor())
}

func jump(g *ui.Gui, v *ui.View) error {
	p := pg.current()
	if p.name != "partition" {
		return nil
	}

	var err error
	currentView = foot.name
	v, err = g.SetCurrentView(foot.name)
	if err != nil {
		return err
	}

	v.Clear()
	v.Write([]byte("jump: "))
	foot.function = "jump"
	return v.SetCursor(6, 0)
}

func search(g *ui.Gui, v *ui.View) error {
	p := pg.current()
	if p.name != "partition" {
		return nil
	}

	var err error
	currentView = foot.name
	v, err = g.SetCurrentView(foot.name)
	if err != nil {
		return err
	}

	v.Clear()
	v.Write([]byte("search: "))
	foot.function = "search"
	return v.SetCursor(8, 0)
}

func dump(g *ui.Gui, v *ui.View) error {
	_, cur := v.Cursor()
	page, r := pg.sel(cur)
	switch page.name {
	case "partition":
		msg := r.args.(kafka.Msg)
		part := msg.Partition
		After = func() {
			kafka.Fetch(part, part.End, func(s string) {
				fmt.Println(s)
			})
		}
	default:
		After = func() {
			fmt.Println(page.header)
			for _, rows := range page.body {
				for _, s := range rows {
					fmt.Println(s.value)
				}
			}
		}
	}

	return ui.ErrQuit
}

func quit(g *ui.Gui, v *ui.View) error {
	return ui.ErrQuit
}
<commit_msg>don't allow select when next page is empty<commit_after>package views

import (
	"fmt"
	"log"

	"github.com/cswank/kcli/internal/colors"
	"github.com/cswank/kcli/internal/kafka"
	ui "github.com/jroimartin/gocui"
)

var (
	pg pages

	head *header
	bod  *body
	foot *footer
	hlp  *help

	currentView string

	c1, c2, c3 colors.Colorer

	//After gets called by main when the gui is closed (if it's not nil)
	After func()
)

func init() {
	c1, c2, c3 = getColors()
	helpMsg = getHelpMsg()
}

type coords struct {
	x1 int
	x2 int
	y1 int
	y2 int
}

type View interface {
	Render(g *ui.Gui, v *ui.View) error
}

func GetLayout(width, height int) func(g *ui.Gui) error {
	head = newHeader(width, height)
	bod = newBody(width, height)
	foot = newFooter(width, height)
	hlp = newHelp(width, height)

	ui.DefaultEditor = foot

	currentView = bod.name

	p, err := getTopics(bod.size, "")
	if err != nil {
		log.Fatal(err)
	}

	pg = pages{
		p: []page{p},
	}

	return func(g *ui.Gui) error {
		w, h := g.Size()
		if h != height || w != width {
			width = w
			height = h
			head.resize(w, h)
			bod.resize(w, h)
			foot.resize(w, h)
		}
		v, err := g.SetView(head.name, head.coords.x1, head.coords.y1, head.coords.x2, head.coords.y2)
		if err != nil && err != ui.ErrUnknownView {
			return err
		}
		v.Frame = false
		if err := head.Render(g, v); err != nil {
			return err
		}

		v, err = g.SetView(bod.name, bod.coords.x1, bod.coords.y1, bod.coords.x2, bod.coords.y2)
		if err != nil && err != ui.ErrUnknownView {
			return err
		}

		v.Frame = false

		if err := bod.Render(g, v); err != nil {
			return err
		}

		v, err = g.SetView(foot.name, foot.coords.x1, foot.coords.y1, foot.coords.x2, foot.coords.y2)
		if err != nil && err != ui.ErrUnknownView {
			return err
		}
		v.Frame = false
		v.Editable = true

		_, err = g.SetCurrentView(currentView)
		return err
	}
}

func next(g *ui.Gui, v *ui.View) error {
	_, cur := v.Cursor()
	if cur < bod.size-1 && cur < len(pg.body())-1 {
		cur++
	}
	return v.SetCursor(0, cur)
}

func prev(g *ui.Gui, v *ui.View) error {
	_, cur := v.Cursor()
	if cur > 0 {
		cur--
	}
	return v.SetCursor(0, cur)
}

func forward(g *ui.Gui, v *ui.View) error {
	if err := pg.forward(); err != nil {
		return err
	}
	return v.SetCursor(0, 0)
}

func back(g *ui.Gui, v *ui.View) error {
	if err := pg.back(); err != nil {
		return err
	}
	return v.SetCursor(0, 0)
}

//sel gets called when the user hits the enter key.
//The item under the cursor is selected and the next()
//func is called to get then next page.
func sel(g *ui.Gui, v *ui.View) error {
	_, cur := v.Cursor()
	_, size := v.Size()

	p, r := pg.sel(cur)
	n, err := p.next(size, r.args)
	if err != nil {
		return err
	}

	if len(n.body) == 0 {
		return nil
	}

	pg.add(n)
	return v.SetCursor(0, 0)
}

func popPage(g *ui.Gui, v *ui.View) error {
	pg.pop()
	return v.SetCursor(0, pg.cursor())
}

func jump(g *ui.Gui, v *ui.View) error {
	p := pg.current()
	if p.name != "partition" {
		return nil
	}

	var err error
	currentView = foot.name
	v, err = g.SetCurrentView(foot.name)
	if err != nil {
		return err
	}

	v.Clear()
	v.Write([]byte("jump: "))
	foot.function = "jump"
	return v.SetCursor(6, 0)
}

func search(g *ui.Gui, v *ui.View) error {
	p := pg.current()
	if p.name != "partition" {
		return nil
	}

	var err error
	currentView = foot.name
	v, err = g.SetCurrentView(foot.name)
	if err != nil {
		return err
	}

	v.Clear()
	v.Write([]byte("search: "))
	foot.function = "search"
	return v.SetCursor(8, 0)
}

func dump(g *ui.Gui, v *ui.View) error {
	_, cur := v.Cursor()
	page, r := pg.sel(cur)
	switch page.name {
	case "partition":
		msg := r.args.(kafka.Msg)
		part := msg.Partition
		After = func() {
			kafka.Fetch(part, part.End, func(s string) {
				fmt.Println(s)
			})
		}
	default:
		After = func() {
			fmt.Println(page.header)
			for _, rows := range page.body {
				for _, s := range rows {
					fmt.Println(s.value)
				}
			}
		}
	}

	return ui.ErrQuit
}

func quit(g *ui.Gui, v *ui.View) error {
	return ui.ErrQuit
}
<|endoftext|>


<commit_before>package dskvs

import (
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
)

type janitor struct {
	DirtyPages         chan *page
	ToDelete           chan *member
	ToCreate           chan *member
	mustDie            chan bool
	blockUntilFinished chan bool
}

func newJanitor() janitor {
	return janitor{
		make(chan *page),
		make(chan *member),
		make(chan *member),
		make(chan bool),
		make(chan bool),
	}
}

func (j *janitor) loadStore(s *Store) error {

	log.Printf("Loading existing data")

	basepath := s.storagePath
	possibleColl, err := ioutil.ReadDir(basepath)
	if os.IsNotExist(err) {
		log.Printf("Store path is empty, starting with fresh persistence.")
		return nil
	} else if err != nil {
		log.Printf("Can't list directory at path %s: %v", basepath, err)
		return err
	}

	log.Printf("... scanning collections")
	var memberPathList []string
	var memberPath string
	for _, file := range possibleColl {
		if file.IsDir() {
			memberPath = filepath.Join(basepath, file.Name())
			memberPathList = append(memberPathList, memberPath)
			s.coll.members[file.Name()] = newMember(basepath, file.Name())
		}
	}

	log.Printf("... loading values into collections")
	var aPage *page
	var pagePath string
	for _, member := range memberPathList {
		possiblePage, err := ioutil.ReadDir(member)
		if err != nil {
			log.Printf("\t... skipping, can't list directory at path <%s>: %v",
				basepath, err)
			continue
		}

		for _, file := range possiblePage {
			if !file.Mode().IsRegular() {
				log.Printf("\t... skipping irregular file <%s>", file.Name())
				continue
			}
			pagePath = filepath.Join(member, file.Name())
			aPage, err = readFromFile(pagePath)
			if err != nil {
				log.Printf("\t... skipping, error reading possible page file: %v",
					err)
				continue
			}
			s.coll.members[aPage.coll].entries[aPage.key] = aPage
		}
	}
	log.Printf("Done loading existing data")
	return nil
}

func (j *janitor) unloadStore(s *Store) error {

	j.die()
	log.Printf("Janitor blocking caller until done writing last changes")
	<-j.blockUntilFinished
	log.Printf("Janitor done writing last changes")

	return nil
}

func (j *janitor) dirtyPageIfNoMember() chan *page {
	if len(j.ToCreate) != 0 {
		return nil
	}
	if len(j.ToDelete) != 0 {
		return nil
	}
	return j.DirtyPages
}

func (j *janitor) shouldDie() chan bool {
	createBacklog := len(j.ToCreate)
	deleteBacklog := len(j.ToDelete)
	pageBacklog := len(j.DirtyPages)

	if createBacklog != 0 ||
		deleteBacklog != 0 ||
		pageBacklog != 0 {
		log.Printf("Janitor has backlog of length %d",
			createBacklog+deleteBacklog+pageBacklog)
		return nil
	}
	return j.mustDie
}

func (j *janitor) run() {
	go func() {
		for {
			select {
			case dirty := <-j.dirtyPageIfNoMember():
				writeToFile(dirty)
			case delete := <-j.ToDelete:
				deleteFolder(delete)
			case create := <-j.ToCreate:
				createFolder(create)
			case <-j.shouldDie():
				j.blockUntilFinished <- false
				return
			}
		}
	}()
}

func (j *janitor) die() {
	log.Printf("Janitor will die")
	j.mustDie <- true
}
<commit_msg>Remove noisy logging.<commit_after>package dskvs

import (
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
)

type janitor struct {
	DirtyPages         chan *page
	ToDelete           chan *member
	ToCreate           chan *member
	mustDie            chan bool
	blockUntilFinished chan bool
}

func newJanitor() janitor {
	return janitor{
		make(chan *page),
		make(chan *member),
		make(chan *member),
		make(chan bool),
		make(chan bool),
	}
}

func (j *janitor) loadStore(s *Store) error {

	basepath := s.storagePath
	possibleColl, err := ioutil.ReadDir(basepath)
	if os.IsNotExist(err) {
		return nil
	} else if err != nil {
		log.Printf("Can't list directory at path %s: %v", basepath, err)
		return err
	}

	var memberPathList []string
	var memberPath string
	for _, file := range possibleColl {
		if file.IsDir() {
			memberPath = filepath.Join(basepath, file.Name())
			memberPathList = append(memberPathList, memberPath)
			s.coll.members[file.Name()] = newMember(basepath, file.Name())
		}
	}

	var aPage *page
	var pagePath string
	for _, member := range memberPathList {
		possiblePage, err := ioutil.ReadDir(member)
		if err != nil {
			log.Printf("\t... skipping, can't list directory at path <%s>: %v",
				basepath, err)
			continue
		}

		for _, file := range possiblePage {
			if !file.Mode().IsRegular() {
				log.Printf("\t... skipping irregular file <%s>", file.Name())
				continue
			}
			pagePath = filepath.Join(member, file.Name())
			aPage, err = readFromFile(pagePath)
			if err != nil {
				log.Printf("\t... skipping, error reading possible page file: %v",
					err)
				continue
			}
			s.coll.members[aPage.coll].entries[aPage.key] = aPage
		}
	}
	return nil
}

func (j *janitor) unloadStore(s *Store) error {

	j.die()
	<-j.blockUntilFinished

	return nil
}

func (j *janitor) dirtyPageIfNoMember() chan *page {
	if len(j.ToCreate) != 0 {
		return nil
	}
	if len(j.ToDelete) != 0 {
		return nil
	}
	return j.DirtyPages
}

func (j *janitor) shouldDie() chan bool {
	createBacklog := len(j.ToCreate)
	deleteBacklog := len(j.ToDelete)
	pageBacklog := len(j.DirtyPages)

	if createBacklog != 0 ||
		deleteBacklog != 0 ||
		pageBacklog != 0 {
		log.Printf("Janitor has backlog of length %d",
			createBacklog+deleteBacklog+pageBacklog)
		return nil
	}
	return j.mustDie
}

func (j *janitor) run() {
	go func() {
		for {
			select {
			case dirty := <-j.dirtyPageIfNoMember():
				writeToFile(dirty)
			case delete := <-j.ToDelete:
				deleteFolder(delete)
			case create := <-j.ToCreate:
				createFolder(create)
			case <-j.shouldDie():
				j.blockUntilFinished <- false
				return
			}
		}
	}()
}

func (j *janitor) die() {
	j.mustDie <- true
}
<|endoftext|>


<commit_before>package router

import (
	"fmt"
	"github.com/gin-gonic/gin"
	. "github.com/orange-jacky/albums/common/util"
	"github.com/orange-jacky/albums/data"
	"github.com/orange-jacky/albums/util"
	"net/http"
)

func AlbumManage(c *gin.Context) {
	user := util.GetUserName(c)
	album := util.GetAlbumName(c)
	mongo_album := util.GetAlbum()

	begin := GetMills()

	//
	resp := &data.Response{}
	action := c.Param("action")

	switch action {
	case "insert":
		var str string
		err := mongo_album.Insert(user, album)
		if err == nil {
			str = fmt.Sprintf("%v create %v success", user, album)
		} else {
			resp.Status = -1
			str = fmt.Sprintf("%v create %v fail, %v", user, album, err)
		}
		resp.StatusDescription = str
	case "delete":
		var str string
		err := mongo_album.Delete(user, album)
		if err == nil {
			str = fmt.Sprintf("%v delete %v success", user, album)
		} else {
			str = fmt.Sprintf("%v delete %v fail, %v", user, album, err)
			resp.Status = -2
		}
		resp.StatusDescription = str
	case "get":
		rets, err := mongo_album.GetAlbums(user)
		if err == nil {
			resp.Data = rets
			resp.Total = len(rets)
			str := fmt.Sprintf("%v get albums success", user)
			resp.StatusDescription = str
		} else {
			str := fmt.Sprintf("%v get album fail, %v", user, err)
			resp.StatusDescription = str
			resp.Status = -3
		}
	}
	resp.Cost = GetMills() - begin
	c.JSON(http.StatusOK, resp)
}
<commit_msg>update<commit_after>package router

import (
	"fmt"
	"github.com/gin-gonic/gin"
	. "github.com/orange-jacky/albums/common/util"
	"github.com/orange-jacky/albums/data"
	"github.com/orange-jacky/albums/util"
	"net/http"
)

func AlbumManage(c *gin.Context) {
	user := util.GetUserName(c)
	album := util.GetAlbumName(c)
	mongo_album := util.GetAlbum()

	begin := GetMills()

	//
	resp := &data.Response{}
	action := c.Param("action")

	switch action {
	case "insert":
		var str string
		err := mongo_album.Insert(user, album)
		if err == nil {
			str = fmt.Sprintf("%v create %v success", user, album)
		} else {
			resp.Status = -1
			str = fmt.Sprintf("%v create %v fail, %v", user, album, err)
		}
		resp.StatusDescription = str
	case "delete":
		var str string
		err := mongo_album.Delete(user, album)
		if err == nil {
			str = fmt.Sprintf("%v delete %v success", user, album)
		} else {
			str = fmt.Sprintf("%v delete %v fail, %v", user, album, err)
			resp.Status = -2
		}
		resp.StatusDescription = str
	case "get":
		rets, err := mongo_album.GetAlbums(user)
		if err == nil {
			resp.Data = rets
			resp.Total = len(rets)
			str := fmt.Sprintf("%v get albums success", user)
			resp.StatusDescription = str
		} else {
			str := fmt.Sprintf("%v get album fail, %v", user, err)
			resp.StatusDescription = str
			resp.Status = -3
		}
	}
	resp.Cost = GetMills() - begin
	if resp.Data == nil {
		resp.Data = make([]string, 0)
	}
	c.JSON(http.StatusOK, resp)
}
<|endoftext|>


<commit_before>/*--------------------------------------------------------*\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: https://hprose.com                     |
|                                                          |
| rpc/core/tag_parser.go                                   |
|                                                          |
| LastModified: Apr 27, 2021                               |
| Author: Ma Bingyao <andot@hprose.com>                    |
|                                                          |
\*________________________________________________________*/

package core

import (
	"reflect"
	"strconv"
	"time"
)

type TagParser struct {
	Name    string
	Context *ClientContext
	tag     reflect.StructTag
}

func (tp *TagParser) parseName() {
	tp.Name = tp.tag.Get("name")
}

func (tp *TagParser) parseTimeout() {
	if s, ok := tp.tag.Lookup("timeout"); ok {
		if timeout, err := strconv.Atoi(s); err == nil {
			tp.Context.Timeout = time.Millisecond * time.Duration(timeout)
		}
	}
}

func (tp *TagParser) parseMapName(tag string) (remain string, name string, c byte) {
	// Skip leading space.
	i := 0
	for i < len(tag) && tag[i] == ' ' {
		i++
	}
	tag = tag[i:]
	if tag == "" {
		return
	}

	// Scan to colon or comma.
	i = 0
	for i < len(tag) && tag[i] != ':' && tag[i] != ',' {
		i++
	}
	if i == len(tag) {
		return "", tag, ','
	}
	c = tag[i]
	name = tag[:i]
	remain = tag[i+1:]
	return
}

func (tp *TagParser) parseMapValue(tag string) (string, string) {
	// Scan to find value.
	i := 0
	c := byte(',')
	if i < len(tag) && tag[i] == '"' {
		i++
		c = '"'
	} else if i < len(tag) && tag[i] == '\'' {
		i++
		c = '\''
	}
	for i < len(tag) && tag[i] != c {
		i++
	}
	if i < len(tag) && tag[i+1] == ',' {
		i++
	}
	value := tag[:i]
	tag = tag[i:]
	return tag, value
}

func (tp *TagParser) parseMap(key string) map[string]interface{} {
	m := make(map[string]interface{})
	tag := tp.tag.Get(key)
	for tag != "" {
		var name string
		var c byte
		tag, name, c = tp.parseMapName(tag)
		if tag == "" && name == "" {
			break
		}
		if c == ',' {
			m[name] = true
			continue
		}
		var value string
		tag, value = tp.parseMapValue(tag)
		if (len(value) >= 2) && (value[0] == '"' && value[len(value)-1] == '"') ||
			(value[0] == '\'' && value[len(value)-1] == '\'') {
			m[name] = value[1 : len(value)-1]
			continue
		}
		if value == "nil" || value == "null" {
			m[name] = nil
			continue
		}
		if intValue, err := strconv.Atoi(value); err == nil {
			m[name] = intValue
			continue
		}
		if floatValue, err := strconv.ParseFloat(value, 64); err == nil {
			m[name] = floatValue
			continue
		}
		if boolValue, err := strconv.ParseBool(value); err == nil {
			m[name] = boolValue
			continue
		}
		m[name] = value
	}
	return m
}

func (tp *TagParser) parseHeader() {
	m := tp.parseMap("header")
	header := tp.Context.RequestHeaders()
	for key, value := range m {
		header.Set(key, value)
	}
}

func (tp *TagParser) parseContext() {
	m := tp.parseMap("context")
	items := tp.Context.Items()
	for key, value := range m {
		items.Set(key, value)
	}
}

func ParseTag(ctx *ClientContext, tag reflect.StructTag) *TagParser {
	parser := &TagParser{Context: ctx, tag: tag}
	parser.parseName()
	parser.parseTimeout()
	parser.parseHeader()
	parser.parseContext()
	return parser
}
<commit_msg>Update tag_parser.go<commit_after>/*--------------------------------------------------------*\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: https://hprose.com                     |
|                                                          |
| rpc/core/tag_parser.go                                   |
|                                                          |
| LastModified: May 16, 2021                               |
| Author: Ma Bingyao <andot@hprose.com>                    |
|                                                          |
\*________________________________________________________*/

package core

import (
	"reflect"
	"strconv"
	"time"
)

type TagParser struct {
	Name    string
	Context *ClientContext
	tag     reflect.StructTag
}

func (tp *TagParser) parseName() {
	tp.Name = tp.tag.Get("name")
}

func (tp *TagParser) parseTimeout() {
	if s, ok := tp.tag.Lookup("timeout"); ok {
		if timeout, err := strconv.Atoi(s); err == nil {
			tp.Context.Timeout = time.Millisecond * time.Duration(timeout)
		}
	}
}

func (tp *TagParser) parseMapName(tag string) (remain string, name string, c byte) {
	// Skip leading space.
	i := 0
	for i < len(tag) && tag[i] == ' ' {
		i++
	}
	tag = tag[i:]
	if tag == "" {
		return
	}

	// Scan to colon or comma.
	i = 0
	for i < len(tag) && tag[i] != ':' && tag[i] != ',' {
		i++
	}
	if i == len(tag) {
		return "", tag, ','
	}
	c = tag[i]
	name = tag[:i]
	remain = tag[i+1:]
	return
}

func (tp *TagParser) parseMapValue(tag string) (string, string) {
	// Scan to find value.
	i := 0
	c := byte(',')
	if i < len(tag) && tag[i] == '"' {
		i++
		c = '"'
	} else if i < len(tag) && tag[i] == '\'' {
		i++
		c = '\''
	}
	for i < len(tag) && tag[i] != c {
		i++
	}
	if i < len(tag) && tag[i+1] == ',' {
		i++
	}
	value := tag[:i]
	tag = tag[i:]
	return tag, value
}

func (tp *TagParser) parseMap(key string) map[string]interface{} {
	m := make(map[string]interface{})
	tag := tp.tag.Get(key)
	for tag != "" {
		var name string
		var c byte
		tag, name, c = tp.parseMapName(tag)
		if tag == "" && name == "" {
			break
		}
		if c == ',' {
			m[name] = true
			continue
		}
		var value string
		tag, value = tp.parseMapValue(tag)
		if (len(value) >= 2) && (value[0] == '"' && value[len(value)-1] == '"') ||
			(value[0] == '\'' && value[len(value)-1] == '\'') {
			m[name] = value[1 : len(value)-1]
			continue
		}
		if value == "nil" || value == "null" {
			m[name] = nil
			continue
		}
		if intValue, err := strconv.Atoi(value); err == nil {
			m[name] = intValue
			continue
		}
		if floatValue, err := strconv.ParseFloat(value, 64); err == nil {
			m[name] = floatValue
			continue
		}
		if boolValue, err := strconv.ParseBool(value); err == nil {
			m[name] = boolValue
			continue
		}
		m[name] = value
	}
	return m
}

func (tp *TagParser) parseHeader() {
	m := tp.parseMap("header")
	header := tp.Context.RequestHeaders()
	for key, value := range m {
		header.Set(key, value)
	}
}

func (tp *TagParser) parseContext() {
	m := tp.parseMap("context")
	items := tp.Context.Items()
	for key, value := range m {
		items.Set(key, value)
	}
}

func ParseTag(ctx *ClientContext, tag reflect.StructTag) *TagParser {
	parser := &TagParser{Context: ctx, tag: tag}
	parser.parseName()
	if ctx != nil {
		parser.parseTimeout()
		parser.parseHeader()
		parser.parseContext()
	}
	return parser
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/miekg/dns"
	"github.com/zettio/weavedns"
	"log"
	"net"
)

var zone = new(weavedns.ZoneDb)

func handleLocal(w dns.ResponseWriter, r *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(r)
	q := r.Question[0]
	ip, err := zone.MatchLocal(q.Name)
	if err == nil {
		hdr := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA,
			Class: dns.ClassINET, Ttl: 3600}
		a := &dns.A{hdr, net.ParseIP(ip)}
		m.Answer = append(m.Answer, a)
		w.WriteMsg(m)
	} else {
		log.Printf("Failed lookup for %s", q.Name)
	}
	return
}

func main() {
	LocalServeMux := dns.NewServeMux()
	LocalServeMux.HandleFunc("local", handleLocal)
	go weavedns.ListenHttp(zone)
	dns.ListenAndServe(":5300", "udp", LocalServeMux)
}
<commit_msg>Extract makeDNSReply function<commit_after>package main

import (
	"github.com/miekg/dns"
	"github.com/zettio/weavedns"
	"log"
	"net"
)

var zone = new(weavedns.ZoneDb)

func makeDNSReply(r *dns.Msg, name string, addr net.IP) *dns.Msg {
	m := new(dns.Msg)
	m.SetReply(r)
	hdr := dns.RR_Header{Name: name, Rrtype: dns.TypeA,
		Class: dns.ClassINET, Ttl: 3600}
	a := &dns.A{hdr, addr}
	m.Answer = append(m.Answer, a)
	return m
}

func handleLocal(w dns.ResponseWriter, r *dns.Msg) {
	q := r.Question[0]
	ip, err := zone.MatchLocal(q.Name)
	if err == nil {
		m := makeDNSReply(r, q.Name, net.ParseIP(ip))
		w.WriteMsg(m)
	} else {
		log.Printf("Failed lookup for %s", q.Name)
	}
	return
}

func main() {
	LocalServeMux := dns.NewServeMux()
	LocalServeMux.HandleFunc("local", handleLocal)
	go weavedns.ListenHttp(zone)
	dns.ListenAndServe(":5300", "udp", LocalServeMux)
}
<|endoftext|>


<commit_before>package middleware

import (
	"net/http"

	"github.com/nicomo/abacaxi/session"
)

// DisallowAnon does not allow anonymous users to access the page
func DisallowAnon(h http.Handler) http.Handler {

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Get session
		sess := session.Instance(r)

		// If user is not authenticated, redirect to login
		if sess.Values["id"] == nil {
			http.Redirect(w, r, "/", http.StatusFound)
			return
		}

		// otherwise, move on with context logged in true
		h.ServeHTTP(w, r)
	})
}

// DisallowAuthed prevents logged in users to access /users/login
func DisallowAuthed(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Get session
		sess := session.Instance(r)

		// If user is authenticated, redirect to home
		if sess.Values["id"] != nil {
			http.Redirect(w, r, "/", http.StatusFound)
			return
		}

		// otherwise, move on with context logged in true
		h.ServeHTTP(w, r)
	})
}
<commit_msg>bugfix: correct redirect to login when user logged out<commit_after>package middleware

import (
	"net/http"

	"github.com/nicomo/abacaxi/session"
)

// DisallowAnon does not allow anonymous users to access the page
func DisallowAnon(h http.Handler) http.Handler {

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Get session
		sess := session.Instance(r)

		// If user is not authenticated, redirect to login
		if sess.Values["id"] == nil {
			http.Redirect(w, r, "/users/login", http.StatusFound)
			return
		}

		// otherwise, move on with context logged in true
		h.ServeHTTP(w, r)
	})
}

// DisallowAuthed prevents logged in users to access /users/login
func DisallowAuthed(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Get session
		sess := session.Instance(r)

		// If user is authenticated, redirect to home
		if sess.Values["id"] != nil {
			http.Redirect(w, r, "/", http.StatusFound)
			return
		}

		// otherwise, move on with context logged in true
		h.ServeHTTP(w, r)
	})
}
<|endoftext|>


<commit_before>package filer

import (
	"bytes"
	"context"
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
)

func (fsw *FilerStoreWrapper) handleUpdateToHardLinks(ctx context.Context, entry *Entry) error {
	if len(entry.HardLinkId) == 0 {
		return nil
	}
	// handle hard links
	if err := fsw.setHardLink(ctx, entry); err != nil {
		return fmt.Errorf("setHardLink %d: %v", entry.HardLinkId, err)
	}

	// check what is existing entry
	glog.V(4).Infof("handleUpdateToHardLinks FindEntry %s", entry.FullPath)
	actualStore := fsw.getActualStore(entry.FullPath)
	existingEntry, err := actualStore.FindEntry(ctx, entry.FullPath)
	if err != nil && err != filer_pb.ErrNotFound {
		return fmt.Errorf("update existing entry %s: %v", entry.FullPath, err)
	}

	// remove old hard link
	if err == nil && len(existingEntry.HardLinkId) != 0 && bytes.Compare(existingEntry.HardLinkId, entry.HardLinkId) != 0 {
		glog.V(4).Infof("handleUpdateToHardLinks DeleteHardLink %s", entry.FullPath)
		if err = fsw.DeleteHardLink(ctx, existingEntry.HardLinkId); err != nil {
			return err
		}
	}
	return nil
}

func (fsw *FilerStoreWrapper) setHardLink(ctx context.Context, entry *Entry) error {
	if len(entry.HardLinkId) == 0 {
		return nil
	}
	key := entry.HardLinkId

	newBlob, encodeErr := entry.EncodeAttributesAndChunks()
	if encodeErr != nil {
		return encodeErr
	}

	return fsw.KvPut(ctx, key, newBlob)
}

func (fsw *FilerStoreWrapper) maybeReadHardLink(ctx context.Context, entry *Entry) error {
	if len(entry.HardLinkId) == 0 {
		return nil
	}
	key := entry.HardLinkId

	glog.V(4).Infof("maybeReadHardLink KvGet %v", key)
	value, err := fsw.KvGet(ctx, key)
	if err != nil {
		glog.Errorf("read %s hardlink %d: %v", entry.FullPath, entry.HardLinkId, err)
		return err
	}

	if err = entry.DecodeAttributesAndChunks(value); err != nil {
		glog.Errorf("decode %s hardlink %d: %v", entry.FullPath, entry.HardLinkId, err)
		return err
	}

	return nil
}

func (fsw *FilerStoreWrapper) DeleteHardLink(ctx context.Context, hardLinkId HardLinkId) error {
	key := hardLinkId
	value, err := fsw.KvGet(ctx, key)
	if err == ErrKvNotFound {
		return nil
	}
	if err != nil {
		return err
	}

	entry := &Entry{}
	if err = entry.DecodeAttributesAndChunks(value); err != nil {
		return err
	}

	entry.HardLinkCounter--
	if entry.HardLinkCounter <= 0 {
		glog.V(4).Infof("DeleteHardLink KvDelete %v", key)
		return fsw.KvDelete(ctx, key)
	}

	newBlob, encodeErr := entry.EncodeAttributesAndChunks()
	if encodeErr != nil {
		return encodeErr
	}

	glog.V(4).Infof("DeleteHardLink KvPut %v", key)
	return fsw.KvPut(ctx, key, newBlob)

}
<commit_msg>rename: handle hard links<commit_after>package filer

import (
	"bytes"
	"context"
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
)

func (fsw *FilerStoreWrapper) handleUpdateToHardLinks(ctx context.Context, entry *Entry) error {

	if entry.IsDirectory() {
		return nil
	}

	if len(entry.HardLinkId) > 0 {
		// handle hard links
		if err := fsw.setHardLink(ctx, entry); err != nil {
			return fmt.Errorf("setHardLink %d: %v", entry.HardLinkId, err)
		}
	}

	// check what is existing entry
	glog.V(4).Infof("handleUpdateToHardLinks FindEntry %s", entry.FullPath)
	actualStore := fsw.getActualStore(entry.FullPath)
	existingEntry, err := actualStore.FindEntry(ctx, entry.FullPath)
	if err != nil && err != filer_pb.ErrNotFound {
		return fmt.Errorf("update existing entry %s: %v", entry.FullPath, err)
	}

	// remove old hard link
	if err == nil && len(existingEntry.HardLinkId) != 0 && bytes.Compare(existingEntry.HardLinkId, entry.HardLinkId) != 0 {
		glog.V(4).Infof("handleUpdateToHardLinks DeleteHardLink %s", entry.FullPath)
		if err = fsw.DeleteHardLink(ctx, existingEntry.HardLinkId); err != nil {
			return err
		}
	}
	return nil
}

func (fsw *FilerStoreWrapper) setHardLink(ctx context.Context, entry *Entry) error {
	if len(entry.HardLinkId) == 0 {
		return nil
	}
	key := entry.HardLinkId

	newBlob, encodeErr := entry.EncodeAttributesAndChunks()
	if encodeErr != nil {
		return encodeErr
	}

	return fsw.KvPut(ctx, key, newBlob)
}

func (fsw *FilerStoreWrapper) maybeReadHardLink(ctx context.Context, entry *Entry) error {
	if len(entry.HardLinkId) == 0 {
		return nil
	}
	key := entry.HardLinkId

	glog.V(4).Infof("maybeReadHardLink KvGet %v", key)
	value, err := fsw.KvGet(ctx, key)
	if err != nil {
		glog.Errorf("read %s hardlink %d: %v", entry.FullPath, entry.HardLinkId, err)
		return err
	}

	if err = entry.DecodeAttributesAndChunks(value); err != nil {
		glog.Errorf("decode %s hardlink %d: %v", entry.FullPath, entry.HardLinkId, err)
		return err
	}

	return nil
}

func (fsw *FilerStoreWrapper) DeleteHardLink(ctx context.Context, hardLinkId HardLinkId) error {
	key := hardLinkId
	value, err := fsw.KvGet(ctx, key)
	if err == ErrKvNotFound {
		return nil
	}
	if err != nil {
		return err
	}

	entry := &Entry{}
	if err = entry.DecodeAttributesAndChunks(value); err != nil {
		return err
	}

	entry.HardLinkCounter--
	if entry.HardLinkCounter <= 0 {
		glog.V(4).Infof("DeleteHardLink KvDelete %v", key)
		return fsw.KvDelete(ctx, key)
	}

	newBlob, encodeErr := entry.EncodeAttributesAndChunks()
	if encodeErr != nil {
		return encodeErr
	}

	glog.V(4).Infof("DeleteHardLink KvPut %v", key)
	return fsw.KvPut(ctx, key, newBlob)

}
<|endoftext|>


<commit_before>package whproxy

import (
	"bytes"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	"sync"

	"github.com/gorilla/websocket"
	log "github.com/sirupsen/logrus"
	"github.com/taskcluster/webhooktunnel/util"
	"github.com/taskcluster/webhooktunnel/wsmux"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  64 * 1024,
	WriteBufferSize: 64 * 1024,
}

func genLogger(fname string) *log.Logger {
	file, err := os.Create(fname)
	if err != nil {
		panic(err)
	}
	logger := &log.Logger{
		Out:       file,
		Formatter: new(log.TextFormatter),
		Level:     log.DebugLevel,
	}
	return logger
}

func TestProxyRegister(t *testing.T) {
	//  start proxy server
	proxy := New(Config{Upgrader: upgrader, Logger: genLogger("register-test")})
	server := httptest.NewServer(proxy.GetHandler())
	defer server.Close()

	// get url
	wsURL := util.MakeWsURL(server.URL)

	// create address to dial
	workerID := "validWorkerID"
	dialAddr := wsURL + "/register/" + workerID

	// dial connection to proxy
	conn1, _, err := websocket.DefaultDialer.Dial(dialAddr, nil)
	if err != nil {
		t.Fatal(err)
	}

	defer func() {
		_ = conn1.Close()
	}()
	// second connection should fail
	conn2, _, err := websocket.DefaultDialer.Dial(dialAddr, nil)
	if err == nil {
		defer func() {
			_ = conn2.Close()
		}()
		t.Fatalf("bad status code: connection should fail")
	}
}

// TestProxyRequest
func TestProxyRequest(t *testing.T) {
	proxy := New(Config{Upgrader: upgrader, Logger: genLogger("request-test")})
	server := httptest.NewServer(proxy.GetHandler())
	defer server.Close()

	// get url
	wsURL := util.MakeWsURL(server.URL)
	// makeshift client
	clientWs, _, err := websocket.DefaultDialer.Dial(wsURL+"/register/workerID", nil)
	if err != nil {
		t.Fatal(err)
	}

	// handler to serve client requests
	clientHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet:
			_, _ = w.Write([]byte("GET successful"))
		case http.MethodPost:
			_, _ = io.Copy(w, r.Body)
		default:
			http.NotFound(w, r)
		}
	})

	// serve client endpoint
	clientServer := &http.Server{Handler: clientHandler}
	go func() {
		_ = clientServer.Serve(wsmux.Client(clientWs, wsmux.Config{}))
	}()
	defer func() {
		_ = clientServer.Close()
	}()

	// make requests
	viewer := &http.Client{}
	servURL := server.URL

	// GET request
	resp, err := viewer.Get(servURL + "/workerID/")
	if err != nil {
		t.Fatal(err)
	}
	if resp.StatusCode != 200 {
		t.Log(resp)
		t.Fatalf("bad status code on get request")
	}
	reply, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(reply, []byte("GET successful")) {
		t.Fatalf("GET failed. Bad message")
	}

	// POST request
	resp, err = viewer.Post(servURL+"/workerID/", "application/text", bytes.NewBuffer([]byte("message")))
	if err != nil {
		t.Fatal(err)
	}
	if resp.StatusCode != 200 {
		t.Fatalf("bad status code on post request")
	}
	reply, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(reply, []byte("message")) {
		t.Fatalf("POST failed. Bad message")
	}

	// GET request to invalid id
	resp, err = viewer.Get(servURL + "/notWorkerID/")
	if resp.StatusCode != 404 {
		t.Fatalf("request should fail with 404")
	}
}

func TestProxyWebsocket(t *testing.T) {
	proxy := New(Config{Upgrader: upgrader})
	server := httptest.NewServer(proxy.GetHandler())
	wsURL := util.MakeWsURL(server.URL)
	defer server.Close()

	clientHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !websocket.IsWebSocketUpgrade(r) {
			http.NotFound(w, r)
		}

		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			t.Fatal(err)
		}

		mt, buf, err := conn.ReadMessage()
		if err != nil {
			t.Fatal(err)
		}

		err = conn.WriteMessage(mt, buf)
		if err != nil {
			t.Fatal(err)
		}
	})

	// register worker and serve http
	clientWs, _, err := websocket.DefaultDialer.Dial(wsURL+"/register/wsWorker/", nil)
	if err != nil {
		t.Fatal(err)
	}

	clientServer := &http.Server{Handler: clientHandler}
	go func() {
		_ = clientServer.Serve(wsmux.Client(clientWs, wsmux.Config{}))
	}()
	defer func() {
		_ = clientServer.Close()
	}()

	// create websocket connection
	conn, _, err := websocket.DefaultDialer.Dial(wsURL+"/wsWorker/", nil)
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		_ = conn.Close()
	}()

	// Generate 1M message
	message := make([]byte, 0)
	for i := 0; i < 1024*1024; i++ {
		message = append(message, byte(i%127))
	}

	err = conn.WriteMessage(websocket.BinaryMessage, message)
	if err != nil {
		t.Fatal(err)
	}

	_, buf, err := conn.ReadMessage()
	if !bytes.Equal(buf, message) {
		t.Fatalf("websocket test failed. Bad message")
	}
}

// ensure control messages are proxied
func TestWebsocketProxyControl(t *testing.T) {
	logger := genLogger("ws-control-test")
	proxy := New(Config{Upgrader: upgrader})
	//serve proxy
	server := httptest.NewServer(proxy.GetHandler())
	wsURL := util.MakeWsURL(server.URL)
	defer server.Close()

	// mechanism to know test has completed
	var wg sync.WaitGroup
	wg.Add(2)
	done := func() chan bool {
		tdone := make(chan bool, 1)
		go func() {
			wg.Wait()
			close(tdone)
		}()
		return tdone
	}

	clientHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !websocket.IsWebSocketUpgrade(r) {
			http.NotFound(w, r)
		}

		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			t.Fatal(err)
		}

		// set ping handler. Decrement wg to ensure ping frame was received
		conn.SetPingHandler(func(appData string) error {
			defer wg.Done()
			return conn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(500*time.Millisecond))
		})

		// Read message to make sure ping was received
		for {
			_, _, err = conn.NextReader()
			if err != nil {
				break
			}
		}
	})

	// register worker and serve http
	clientWs, _, err := websocket.DefaultDialer.Dial(wsURL+"/register/wsWorker/", nil)
	if err != nil {
		t.Fatal(err)
	}

	clientServer := &http.Server{Handler: clientHandler}
	go func() {
		_ = clientServer.Serve(wsmux.Client(clientWs, wsmux.Config{}))
	}()
	defer func() {
		_ = clientServer.Close()
	}()

	// create websocket connection
	conn, _, err := websocket.DefaultDialer.Dial(wsURL+"/wsWorker/", nil)
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		_ = conn.Close()
	}()

	// ****************************
	// Set Pong Handler. Decrement wg when pong handler fires to ensure that
	// pong is called
	conn.SetPongHandler(func(appData string) error {
		defer wg.Done()
		logger.Printf("received pong: %s", appData)
		if appData != "ping" {
			t.Fatal("bad pong")
		}
		return nil
	})

	// set timer for timing out test
	timer := time.NewTimer(3 * time.Second)

	// start reading messages to ensure pong is received
	go func() {
		for {
			_, _, err = conn.NextReader()
			if err != nil {
				break
			}
		}
	}()

	err = conn.WriteControl(websocket.PingMessage, []byte("ping"), time.Now().Add(1*time.Second))
	if err != nil {
		t.Fatal(err)
	}

	select {
	case <-timer.C:
		t.Fatalf("test failed: timeout")
	case <-done():
	}

}

// Ensure websocket close is proxied
func TestWebSocketClosure(t *testing.T) {
	logger := genLogger("ws-closure-test")
	proxy := New(Config{Upgrader: upgrader})
	//serve proxy
	server := httptest.NewServer(proxy.GetHandler())
	wsURL := util.MakeWsURL(server.URL)
	defer server.Close()

	// mechanism to know test has completed
	var wg sync.WaitGroup
	wg.Add(1)
	done := func() chan bool {
		tdone := make(chan bool, 1)
		go func() {
			wg.Wait()
			close(tdone)
		}()
		return tdone
	}

	clientHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !websocket.IsWebSocketUpgrade(r) {
			http.NotFound(w, r)
		}

		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			t.Fatal(err)
		}

		for {
			_, _, err = conn.NextReader()
			if err != nil && websocket.IsCloseError(err, websocket.CloseAbnormalClosure) {
				logger.Printf("closed")
				wg.Done()
				break
			}
			if err != nil {
				t.Fatal(err)
			}
		}

	})

	// register worker and serve http
	clientWs, _, err := websocket.DefaultDialer.Dial(wsURL+"/register/wsWorker/", nil)
	if err != nil {
		t.Fatal(err)
	}

	clientServer := &http.Server{Handler: clientHandler}
	go func() {
		_ = clientServer.Serve(wsmux.Client(clientWs, wsmux.Config{}))
	}()
	defer func() {
		_ = clientServer.Close()
	}()

	// create websocket connection
	conn, _, err := websocket.DefaultDialer.Dial(wsURL+"/wsWorker/", nil)
	if err != nil {
		t.Fatal(err)
	}

	// set timer for timing out test
	timer := time.NewTimer(4 * time.Second)

	// Close connection
	// will cause abnormal closure as Close will cause the underlying connection
	// to close without sending any close frame
	err = conn.Close()
	if err != nil {
		t.Fatal(err)
	}

	select {
	case <-timer.C:
		t.Fatalf("test failed: timeout")
	case <-done():
	}

}

func TestProxySessionRemoved(t *testing.T) {
	done := make(chan bool, 1)
	proxy := New(Config{Upgrader: upgrader, Logger: genLogger("session-remove-test")})
	proxy.SetSessionRemoveHandler(func(id string) {
		close(done)
	})

	server := httptest.NewServer(proxy.GetHandler())
	defer server.Close()

	wsURL := util.MakeWsURL(server.URL)
	conn, _, err := websocket.DefaultDialer.Dial(wsURL+"/register/wsWorker", nil)
	if err != nil {
		t.Fatal(err)
	}

	timeout := time.NewTimer(4 * time.Second)
	err = conn.Close()
	if err != nil {
		t.Fatal(err)
	}

	select {
	case <-done:
	case <-timeout.C:
		t.Fatalf("test timed out")
	}
}
<commit_msg>proxy: simplified closure test<commit_after>package whproxy

import (
	"bytes"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	"sync"

	"github.com/gorilla/websocket"
	log "github.com/sirupsen/logrus"
	"github.com/taskcluster/webhooktunnel/util"
	"github.com/taskcluster/webhooktunnel/wsmux"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  64 * 1024,
	WriteBufferSize: 64 * 1024,
}

func genLogger(fname string) *log.Logger {
	file, err := os.Create(fname)
	if err != nil {
		panic(err)
	}
	logger := &log.Logger{
		Out:       file,
		Formatter: new(log.TextFormatter),
		Level:     log.DebugLevel,
	}
	return logger
}

func TestProxyRegister(t *testing.T) {
	//  start proxy server
	proxy := New(Config{Upgrader: upgrader, Logger: genLogger("register-test")})
	server := httptest.NewServer(proxy.GetHandler())
	defer server.Close()

	// get url
	wsURL := util.MakeWsURL(server.URL)

	// create address to dial
	workerID := "validWorkerID"
	dialAddr := wsURL + "/register/" + workerID

	// dial connection to proxy
	conn1, _, err := websocket.DefaultDialer.Dial(dialAddr, nil)
	if err != nil {
		t.Fatal(err)
	}

	defer func() {
		_ = conn1.Close()
	}()
	// second connection should fail
	conn2, _, err := websocket.DefaultDialer.Dial(dialAddr, nil)
	if err == nil {
		defer func() {
			_ = conn2.Close()
		}()
		t.Fatalf("bad status code: connection should fail")
	}
}

// TestProxyRequest
func TestProxyRequest(t *testing.T) {
	proxy := New(Config{Upgrader: upgrader, Logger: genLogger("request-test")})
	server := httptest.NewServer(proxy.GetHandler())
	defer server.Close()

	// get url
	wsURL := util.MakeWsURL(server.URL)
	// makeshift client
	clientWs, _, err := websocket.DefaultDialer.Dial(wsURL+"/register/workerID", nil)
	if err != nil {
		t.Fatal(err)
	}

	// handler to serve client requests
	clientHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet:
			_, _ = w.Write([]byte("GET successful"))
		case http.MethodPost:
			_, _ = io.Copy(w, r.Body)
		default:
			http.NotFound(w, r)
		}
	})

	// serve client endpoint
	clientServer := &http.Server{Handler: clientHandler}
	go func() {
		_ = clientServer.Serve(wsmux.Client(clientWs, wsmux.Config{}))
	}()
	defer func() {
		_ = clientServer.Close()
	}()

	// make requests
	viewer := &http.Client{}
	servURL := server.URL

	// GET request
	resp, err := viewer.Get(servURL + "/workerID/")
	if err != nil {
		t.Fatal(err)
	}
	if resp.StatusCode != 200 {
		t.Log(resp)
		t.Fatalf("bad status code on get request")
	}
	reply, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(reply, []byte("GET successful")) {
		t.Fatalf("GET failed. Bad message")
	}

	// POST request
	resp, err = viewer.Post(servURL+"/workerID/", "application/text", bytes.NewBuffer([]byte("message")))
	if err != nil {
		t.Fatal(err)
	}
	if resp.StatusCode != 200 {
		t.Fatalf("bad status code on post request")
	}
	reply, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(reply, []byte("message")) {
		t.Fatalf("POST failed. Bad message")
	}

	// GET request to invalid id
	resp, err = viewer.Get(servURL + "/notWorkerID/")
	if resp.StatusCode != 404 {
		t.Fatalf("request should fail with 404")
	}
}

func TestProxyWebsocket(t *testing.T) {
	proxy := New(Config{Upgrader: upgrader})
	server := httptest.NewServer(proxy.GetHandler())
	wsURL := util.MakeWsURL(server.URL)
	defer server.Close()

	clientHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !websocket.IsWebSocketUpgrade(r) {
			http.NotFound(w, r)
		}

		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			t.Fatal(err)
		}

		mt, buf, err := conn.ReadMessage()
		if err != nil {
			t.Fatal(err)
		}

		err = conn.WriteMessage(mt, buf)
		if err != nil {
			t.Fatal(err)
		}
	})

	// register worker and serve http
	clientWs, _, err := websocket.DefaultDialer.Dial(wsURL+"/register/wsWorker/", nil)
	if err != nil {
		t.Fatal(err)
	}

	clientServer := &http.Server{Handler: clientHandler}
	go func() {
		_ = clientServer.Serve(wsmux.Client(clientWs, wsmux.Config{}))
	}()
	defer func() {
		_ = clientServer.Close()
	}()

	// create websocket connection
	conn, _, err := websocket.DefaultDialer.Dial(wsURL+"/wsWorker/", nil)
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		_ = conn.Close()
	}()

	// Generate 1M message
	message := make([]byte, 0)
	for i := 0; i < 1024*1024; i++ {
		message = append(message, byte(i%127))
	}

	err = conn.WriteMessage(websocket.BinaryMessage, message)
	if err != nil {
		t.Fatal(err)
	}

	_, buf, err := conn.ReadMessage()
	if !bytes.Equal(buf, message) {
		t.Fatalf("websocket test failed. Bad message")
	}
}

// ensure control messages are proxied
func TestWebsocketProxyControl(t *testing.T) {
	logger := genLogger("ws-control-test")
	proxy := New(Config{Upgrader: upgrader})
	//serve proxy
	server := httptest.NewServer(proxy.GetHandler())
	wsURL := util.MakeWsURL(server.URL)
	defer server.Close()

	// mechanism to know test has completed
	var wg sync.WaitGroup
	wg.Add(2)
	done := func() chan bool {
		tdone := make(chan bool, 1)
		go func() {
			wg.Wait()
			close(tdone)
		}()
		return tdone
	}

	clientHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !websocket.IsWebSocketUpgrade(r) {
			http.NotFound(w, r)
		}

		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			t.Fatal(err)
		}

		// set ping handler. Decrement wg to ensure ping frame was received
		conn.SetPingHandler(func(appData string) error {
			defer wg.Done()
			return conn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(500*time.Millisecond))
		})

		// Read message to make sure ping was received
		for {
			_, _, err = conn.NextReader()
			if err != nil {
				break
			}
		}
	})

	// register worker and serve http
	clientWs, _, err := websocket.DefaultDialer.Dial(wsURL+"/register/wsWorker/", nil)
	if err != nil {
		t.Fatal(err)
	}

	clientServer := &http.Server{Handler: clientHandler}
	go func() {
		_ = clientServer.Serve(wsmux.Client(clientWs, wsmux.Config{}))
	}()
	defer func() {
		_ = clientServer.Close()
	}()

	// create websocket connection
	conn, _, err := websocket.DefaultDialer.Dial(wsURL+"/wsWorker/", nil)
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		_ = conn.Close()
	}()

	// ****************************
	// Set Pong Handler. Decrement wg when pong handler fires to ensure that
	// pong is called
	conn.SetPongHandler(func(appData string) error {
		defer wg.Done()
		logger.Printf("received pong: %s", appData)
		if appData != "ping" {
			t.Fatal("bad pong")
		}
		return nil
	})

	// set timer for timing out test
	timer := time.NewTimer(3 * time.Second)

	// start reading messages to ensure pong is received
	go func() {
		for {
			_, _, err = conn.NextReader()
			if err != nil {
				break
			}
		}
	}()

	err = conn.WriteControl(websocket.PingMessage, []byte("ping"), time.Now().Add(1*time.Second))
	if err != nil {
		t.Fatal(err)
	}

	select {
	case <-timer.C:
		t.Fatalf("test failed: timeout")
	case <-done():
	}

}

// Ensure websocket close is proxied
func TestWebSocketClosure(t *testing.T) {
	logger := genLogger("ws-closure-test")
	proxy := New(Config{Upgrader: upgrader})
	//serve proxy
	server := httptest.NewServer(proxy.GetHandler())
	wsURL := util.MakeWsURL(server.URL)
	defer server.Close()

	// mechanism to know test has completed
	done := make(chan bool, 1)

	clientHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !websocket.IsWebSocketUpgrade(r) {
			http.NotFound(w, r)
		}

		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			t.Fatal(err)
		}

		for {
			_, _, err = conn.NextReader()
			if err != nil && websocket.IsCloseError(err, websocket.CloseAbnormalClosure) {
				logger.Printf("closed")
				close(done)
				break
			}
			if err != nil {
				t.Fatal(err)
			}
		}

	})

	// register worker and serve http
	clientWs, _, err := websocket.DefaultDialer.Dial(wsURL+"/register/wsWorker/", nil)
	if err != nil {
		t.Fatal(err)
	}

	clientServer := &http.Server{Handler: clientHandler}
	go func() {
		_ = clientServer.Serve(wsmux.Client(clientWs, wsmux.Config{}))
	}()
	defer func() {
		_ = clientServer.Close()
	}()

	// create websocket connection
	conn, _, err := websocket.DefaultDialer.Dial(wsURL+"/wsWorker/", nil)
	if err != nil {
		t.Fatal(err)
	}

	// set timer for timing out test
	timer := time.NewTimer(4 * time.Second)

	// Close connection
	// will cause abnormal closure as Close will cause the underlying connection
	// to close without sending any close frame
	err = conn.Close()
	if err != nil {
		t.Fatal(err)
	}

	select {
	case <-timer.C:
		t.Fatalf("test failed: timeout")
	case <-done:
	}

}

func TestProxySessionRemoved(t *testing.T) {
	done := make(chan bool, 1)
	proxy := New(Config{Upgrader: upgrader, Logger: genLogger("session-remove-test")})
	proxy.SetSessionRemoveHandler(func(id string) {
		close(done)
	})

	server := httptest.NewServer(proxy.GetHandler())
	defer server.Close()

	wsURL := util.MakeWsURL(server.URL)
	conn, _, err := websocket.DefaultDialer.Dial(wsURL+"/register/wsWorker", nil)
	if err != nil {
		t.Fatal(err)
	}

	timeout := time.NewTimer(4 * time.Second)
	err = conn.Close()
	if err != nil {
		t.Fatal(err)
	}

	select {
	case <-done:
	case <-timeout.C:
		t.Fatalf("test timed out")
	}
}
<|endoftext|>


<commit_before>package model

import (
	"fmt"
	"github.com/viant/endly/util"
	"github.com/viant/neatly"
	"github.com/viant/toolbox"
	"github.com/viant/toolbox/data"
	"github.com/viant/toolbox/url"
	"strings"
)

//Template represents inline workflow template to dynamically Expand actions - idea borrowed from neatly format: https://github.com/viant/neatly/
type Template struct {
	SubPath     string            `description:"sub path for dynamic resource template expansion: i.e. use_cases/${index}*"`
	Tag         string            `description:"grouping tag i.e Test"`
	Range       string            `description:"range expression i.e 2..003  where upper bound number drives padding $index variable"`
	Description string            `description:"reference to file containing tagDescription i.e. @use_case,  file reference has to start with @"`
	Data        map[string]string `description:"map of data references, where key is workflow.data target, and value is a file within expanded dynamically subpath or workflow path fallback. Value has to start with @"`
	Template    []interface{}
	inline      *InlineWorkflow
}

func (t *Template) Expand(task *Task, parentTag string, inline *InlineWorkflow) error {
	if t.Tag == "" {
		if t.Tag = task.Name; t.Tag == "" {
			t.Tag = parentTag
		}
	}
	t.inline = inline
	tag := buildTag(t, inline)
	task.multiAction = true
	iterator := tag.Iterator
	var workflowData = data.Map(t.inline.Data)

	for tag.HasActiveIterator() {
		tempTask := NewTask(task.Name, true)
		index := iterator.Index()
		state := t.buildTagState(index, tag)
		tagPath := state.GetString("path")
		t.inline.tagPathURL = tagPath
		if len(t.Data) > 0 {
			if err := t.loadWorkflowData(tagPath, workflowData, state); err != nil {
				return fmt.Errorf("failed to load data: %v", err)
			}
		}
		var err error
		_ = toolbox.ProcessMap(t.Template, func(key, value interface{}) bool {
			if err = inline.buildWorkflowNodes(toolbox.AsString(key), value, tempTask, t.Tag, state); err != nil {
				return false
			}
			return true
		})
		if err != nil {
			return err
		}
		description := ""
		if t.Description != "" {
			_, _ = util.LoadResource(tagPath, t.Description, &description)
		}
		actions := flattenAction(tempTask, tempTask, tag, description)
		task.Actions = append(task.Actions, actions...)
		if !iterator.Next() {
			break
		}
	}
	t.inline.tagPathURL = ""
	return nil
}

func (t *Template) loadWorkflowData(tagPath string, workflowData data.Map, state data.Map) error {
	var baseURLs = []string{tagPath, toolbox.URLPathJoin(t.inline.baseURL, "default"), t.inline.baseURL}
	var err error

	for k, v := range t.Data {
		k = state.ExpandAsText(k)
		hasWildCard := strings.Contains(v, "*")
		var resourceURLs = make([]string, 0)
		if hasWildCard {
			resourceURLs, err = util.ListResource(baseURLs, v)
			if util.IsNotSuchResourceError(err) {
				continue
			}
			if err != nil {
				return err
			}
		}
		if len(resourceURLs) > 0 {
			for _, resourceURL := range resourceURLs {
				base, URI := toolbox.URLSplit(resourceURL)
				loaded, err := util.LoadData([]string{base}, "@"+URI)
				if err != nil {
					return err
				}
				addLoadedData(loaded, state, k, workflowData)
			}
			continue
		}
		loaded, err := util.LoadData(baseURLs, v)
		if util.IsNotSuchResourceError(err) {
			continue
		}
		if err != nil {
			return err
		}
		addLoadedData(loaded, state, k, workflowData)
	}
	return nil
}

func addLoadedData(loaded interface{}, state data.Map, k string, workflowData data.Map) {
	loaded = state.Expand(loaded)
	collectionSignatureCount := strings.Count(k, "[]")
	if collectionSignatureCount > 0 {
		k = strings.Replace(k, "[]", "", collectionSignatureCount)
		var collection *data.Collection
		collectionValue, ok := workflowData.GetValue(k)
		if !ok {
			collection = data.NewCollection()
			workflowData.SetValue(k, collection)
		} else {
			collection, _ = collectionValue.(*data.Collection)
		}
		if collection == nil {
			collection = data.NewCollection()
			workflowData.SetValue(k, collection)
		}
		if toolbox.IsSlice(loaded) {
			for _, item := range toolbox.AsSlice(loaded) {
				collection.Push(item)
			}
		} else {
			collection.Push(loaded)
		}
	} else {
		workflowData.SetValue(k, loaded)
	}
}

func (t *Template) buildTagState(index string, tag *neatly.Tag) data.Map {
	var state = data.NewMap()
	state.Put("index", index)
	if t.SubPath != "" {
		tag.SetSubPath(state.ExpandAsText(t.SubPath))
	}
	tagPath := toolbox.URLPathJoin(t.inline.baseURL, tag.Subpath)
	state.Put("subpath", tag.Subpath)
	state.Put("tagId", tag.TagID())
	state.Put("subPath", tag.Subpath)
	state.Put("pathMatch", tag.PathMatch)
	state.Put("path", tagPath)
	return state
}

func flattenAction(parent *Task, task *Task, tag *neatly.Tag, description string) []*Action {
	var result = make([]*Action, 0)
	isRootTask := parent == task
	if !isRootTask {
		tag.Group = parent.Name
	}
	if len(task.Actions) > 0 {
		result = task.Actions
		for i := range result {
			action := result[i]
			action.TagID = tag.TagID()
			action.TagIndex = tag.Iterator.Index()

			action.Tag = tag.Expand(tag.Name)
			if i == 0 {
				action.TagDescription = description
			}
		}
	}
	if task.TasksNode != nil && len(task.Tasks) > 0 {
		for _, subTask := range task.Tasks {
			actions := flattenAction(task, subTask, tag, description)
			result = append(result, actions...)
		}
	}
	return result
}

func buildTag(t *Template, inline *InlineWorkflow) *neatly.Tag {
	key := t.Tag + "{" + t.Range + "}"
	ownerURL := toolbox.URLPathJoin(inline.baseURL, inline.name+".yaml")
	tag := neatly.NewTag(inline.name, url.NewResource(ownerURL), key, 0)
	return tag
}
<commit_msg>added parent.URL and parent.path to inline workflow scope<commit_after>package model

import (
	"fmt"
	"github.com/viant/endly/util"
	"github.com/viant/neatly"
	"github.com/viant/toolbox"
	"github.com/viant/toolbox/data"
	"github.com/viant/toolbox/url"
	"strings"
)

//Template represents inline workflow template to dynamically Expand actions - idea borrowed from neatly format: https://github.com/viant/neatly/
type Template struct {
	SubPath     string            `description:"sub path for dynamic resource template expansion: i.e. use_cases/${index}*"`
	Tag         string            `description:"grouping tag i.e Test"`
	Range       string            `description:"range expression i.e 2..003  where upper bound number drives padding $index variable"`
	Description string            `description:"reference to file containing tagDescription i.e. @use_case,  file reference has to start with @"`
	Data        map[string]string `description:"map of data references, where key is workflow.data target, and value is a file within expanded dynamically subpath or workflow path fallback. Value has to start with @"`
	Template    []interface{}
	inline      *InlineWorkflow
}

func (t *Template) Expand(task *Task, parentTag string, inline *InlineWorkflow) error {
	if t.Tag == "" {
		if t.Tag = task.Name; t.Tag == "" {
			t.Tag = parentTag
		}
	}
	t.inline = inline
	tag := buildTag(t, inline)
	task.multiAction = true
	iterator := tag.Iterator
	var workflowData = data.Map(t.inline.Data)

	for tag.HasActiveIterator() {
		tempTask := NewTask(task.Name, true)
		index := iterator.Index()
		state := t.buildTagState(index, tag)
		tagPath := state.GetString("path")
		t.inline.tagPathURL = tagPath
		if len(t.Data) > 0 {
			if err := t.loadWorkflowData(tagPath, workflowData, state); err != nil {
				return fmt.Errorf("failed to load data: %v", err)
			}
		}
		var err error
		_ = toolbox.ProcessMap(t.Template, func(key, value interface{}) bool {
			if err = inline.buildWorkflowNodes(toolbox.AsString(key), value, tempTask, t.Tag, state); err != nil {
				return false
			}
			return true
		})
		if err != nil {
			return err
		}
		description := ""
		if t.Description != "" {
			_, _ = util.LoadResource(tagPath, t.Description, &description)
		}
		actions := flattenAction(tempTask, tempTask, tag, description)
		task.Actions = append(task.Actions, actions...)
		if !iterator.Next() {
			break
		}
	}
	t.inline.tagPathURL = ""
	return nil
}

func (t *Template) loadWorkflowData(tagPath string, workflowData data.Map, state data.Map) error {
	var baseURLs = []string{tagPath, toolbox.URLPathJoin(t.inline.baseURL, "default"), t.inline.baseURL}
	var err error

	for k, v := range t.Data {
		k = state.ExpandAsText(k)
		hasWildCard := strings.Contains(v, "*")
		var resourceURLs = make([]string, 0)
		if hasWildCard {
			resourceURLs, err = util.ListResource(baseURLs, v)
			if util.IsNotSuchResourceError(err) {
				continue
			}
			if err != nil {
				return err
			}
		}
		if len(resourceURLs) > 0 {
			for _, resourceURL := range resourceURLs {
				base, URI := toolbox.URLSplit(resourceURL)
				loaded, err := util.LoadData([]string{base}, "@"+URI)
				if err != nil {
					return err
				}
				addLoadedData(loaded, state, k, workflowData)
			}
			continue
		}
		loaded, err := util.LoadData(baseURLs, v)
		if util.IsNotSuchResourceError(err) {
			continue
		}
		if err != nil {
			return err
		}
		addLoadedData(loaded, state, k, workflowData)
	}
	return nil
}

func addLoadedData(loaded interface{}, state data.Map, k string, workflowData data.Map) {
	loaded = state.Expand(loaded)
	collectionSignatureCount := strings.Count(k, "[]")
	if collectionSignatureCount > 0 {
		k = strings.Replace(k, "[]", "", collectionSignatureCount)
		var collection *data.Collection
		collectionValue, ok := workflowData.GetValue(k)
		if !ok {
			collection = data.NewCollection()
			workflowData.SetValue(k, collection)
		} else {
			collection, _ = collectionValue.(*data.Collection)
		}
		if collection == nil {
			collection = data.NewCollection()
			workflowData.SetValue(k, collection)
		}
		if toolbox.IsSlice(loaded) {
			for _, item := range toolbox.AsSlice(loaded) {
				collection.Push(item)
			}
		} else {
			collection.Push(loaded)
		}
	} else {
		workflowData.SetValue(k, loaded)
	}
}

func (t *Template) buildTagState(index string, tag *neatly.Tag) data.Map {
	var state = data.NewMap()
	state.Put("index", index)
	if t.SubPath != "" {
		tag.SetSubPath(state.ExpandAsText(t.SubPath))
	}
	tagPathURL := toolbox.URLPathJoin(t.inline.baseURL, tag.Subpath)
	state.Put("subpath", tag.Subpath)
	state.Put("tagId", tag.TagID())
	state.Put("subPath", tag.Subpath)
	state.Put("pathMatch", tag.PathMatch)
	state.Put("URL", tagPathURL)
	state.Put("path", url.NewResource(tagPathURL).ParsedURL.Path)
	return state
}

func flattenAction(parent *Task, task *Task, tag *neatly.Tag, description string) []*Action {
	var result = make([]*Action, 0)
	isRootTask := parent == task
	if !isRootTask {
		tag.Group = parent.Name
	}
	if len(task.Actions) > 0 {
		result = task.Actions
		for i := range result {
			action := result[i]
			action.TagID = tag.TagID()
			action.TagIndex = tag.Iterator.Index()

			action.Tag = tag.Expand(tag.Name)
			if i == 0 {
				action.TagDescription = description
			}
		}
	}
	if task.TasksNode != nil && len(task.Tasks) > 0 {
		for _, subTask := range task.Tasks {
			actions := flattenAction(task, subTask, tag, description)
			result = append(result, actions...)
		}
	}
	return result
}

func buildTag(t *Template, inline *InlineWorkflow) *neatly.Tag {
	key := t.Tag + "{" + t.Range + "}"
	ownerURL := toolbox.URLPathJoin(inline.baseURL, inline.name+".yaml")
	tag := neatly.NewTag(inline.name, url.NewResource(ownerURL), key, 0)
	return tag
}
<|endoftext|>


<commit_before>package v1

import (
	"fmt"
	log "github.com/sirupsen/logrus"
	"runtime"
	"strings"
	"time"

	"github.com/ivanilves/lstags/api/v1/collection"
	dockerclient "github.com/ivanilves/lstags/docker/client"
	dockerconfig "github.com/ivanilves/lstags/docker/config"
	"github.com/ivanilves/lstags/repository"
	"github.com/ivanilves/lstags/tag"
	"github.com/ivanilves/lstags/tag/local"
	"github.com/ivanilves/lstags/tag/remote"
	"github.com/ivanilves/lstags/util/wait"
)

// Config holds API instance configuration
type Config struct {
	DockerJSONConfigFile string
	ConcurrentRequests   int
	TraceRequests        bool
	RetryRequests        int
	RetryDelay           time.Duration
	InsecureRegistryEx   string
	VerboseLogging       bool
}

// PushConfig holds push-specific configuration
type PushConfig struct {
	Prefix        string
	Registry      string
	UpdateChanged bool
}

// API represents application API instance
type API struct {
	config       Config
	dockerClient *dockerclient.DockerClient
}

// fn gives the name of the calling function (e.g. enriches log.Debugf() output)
// + optionally attaches free form string labels (mainly to identify goroutines)
func fn(labels ...string) string {
	function, _, _, _ := runtime.Caller(1)

	longname := runtime.FuncForPC(function).Name()

	nameparts := strings.Split(longname, ".")
	shortname := nameparts[len(nameparts)-1]

	if labels == nil {
		return fmt.Sprintf("[%s()]", shortname)
	}

	return fmt.Sprintf("[%s():%s]", shortname, strings.Join(labels, ":"))
}

// CollectTags collects information on tags present in remote registry and [local] Docker daemon,
// makes required comparisons between them and spits organized info back as collection.Collection
func (api *API) CollectTags(refs []string) (*collection.Collection, error) {
	if len(refs) == 0 {
		return nil, fmt.Errorf("no image references passed")
	}

	log.Debugf("%s references: %+v", fn(), refs)

	repos, err := repository.ParseRefs(refs)
	if err != nil {
		return nil, err
	}
	for _, repo := range repos {
		log.Debugf("%s repository: %+v", fn(), repo)
	}

	done := make(chan error, len(repos))
	tags := make(map[string][]*tag.Tag)

	for _, repo := range repos {
		go func(repo *repository.Repository, done chan error) {
			log.Infof("ANALYZE %s", repo.Ref())

			username, password, _ := api.dockerClient.Config().GetCredentials(repo.Registry())

			remoteTags, err := remote.FetchTags(repo, username, password)
			if err != nil {
				done <- err
				return
			}
			log.Debugf("%s remote tags: %+v", fn(repo.Ref()), remoteTags)

			localTags, err := local.FetchTags(repo, api.dockerClient)
			if err != nil {
				done <- err
				return
			}
			log.Debugf("%s local tags: %+v", fn(repo.Ref()), localTags)

			sortedKeys, tagNames, joinedTags := tag.Join(
				remoteTags,
				localTags,
				repo.Tags(),
			)
			log.Debugf("%s joined tags: %+v", fn(repo.Ref()), joinedTags)

			tags[repo.Ref()] = tag.Collect(sortedKeys, tagNames, joinedTags)

			done <- nil

			log.Infof("FETCHED %s", repo.Ref())

			return
		}(repo, done)
	}

	if err := wait.Until(done); err != nil {
		return nil, err
	}
	log.Debugf("%s tags: %+v", fn(), tags)

	cn, err := collection.New(refs, tags)
	if err != nil {
		return nil, err
	}
	log.Debugf(
		"%s collection: %+v (%d repos / %d tags)",
		fn(), cn, cn.RepoCount(), cn.TagCount(),
	)

	return cn, nil
}

// CollectPushTags blends passed collection with information fetched from [local] "push" registry,
// makes required comparisons between them and spits organized info back as collection.Collection
func (api *API) CollectPushTags(cn *collection.Collection, push PushConfig) (*collection.Collection, error) {
	log.Debugf(
		"%s collection: %+v (%d repos / %d tags)",
		fn(), cn, cn.RepoCount(), cn.TagCount(),
	)
	log.Debugf("%s push config: %+v", fn(), push)

	refs := make([]string, len(cn.Refs()))
	done := make(chan error, len(cn.Refs()))
	tags := make(map[string][]*tag.Tag)

	for i, repo := range cn.Repos() {
		go func(repo *repository.Repository, i int, done chan error) {
			refs[i] = repo.Ref()

			pushPrefix := push.Prefix
			if pushPrefix == "" {
				pushPrefix = repo.PushPrefix()
			}

			var pushRepoPath string
			pushRepoPath = pushPrefix + "/" + repo.Path()
			pushRepoPath = pushRepoPath[1:] // Leading "/" in prefix should be removed!

			pushRef := fmt.Sprintf("%s/%s~/.*/", push.Registry, pushRepoPath)

			log.Debugf("%s 'push' reference: %+v", fn(repo.Ref()), pushRef)

			pushRepo, err := repository.ParseRef(pushRef)
			if err != nil {
				done <- err
				return
			}

			log.Infof("[PULL/PUSH] ANALYZE %s => %s", repo.Ref(), pushRef)

			username, password, _ := api.dockerClient.Config().GetCredentials(push.Registry)

			pushedTags, err := remote.FetchTags(pushRepo, username, password)
			if err != nil {
				if !strings.Contains(err.Error(), "404 Not Found") {
					done <- err
					return
				}

				log.Warnf("%s repo not found: %+s", fn(repo.Ref()), pushRef)

				pushedTags = make(map[string]*tag.Tag)
			}
			log.Debugf("%s pushed tags: %+v", fn(repo.Ref()), pushedTags)

			remoteTags := cn.TagMap(repo.Ref())
			log.Debugf("%s remote tags: %+v", fn(repo.Ref()), remoteTags)

			sortedKeys, tagNames, joinedTags := tag.Join(
				remoteTags,
				pushedTags,
				repo.Tags(),
			)
			log.Debugf("%s joined tags: %+v", fn(repo.Ref()), joinedTags)

			tagsToPush := make([]*tag.Tag, 0)
			for _, key := range sortedKeys {
				name := tagNames[key]
				tg := joinedTags[name]

				if tg.NeedsPush(push.UpdateChanged) {
					tagsToPush = append(tagsToPush, tg)
				}
			}
			log.Debugf("%s tags to push: %+v", fn(repo.Ref()), tagsToPush)

			tags[repo.Ref()] = tagsToPush

			done <- nil

			return
		}(repo, i, done)
	}

	if err := wait.Until(done); err != nil {
		return nil, err
	}
	log.Debugf("%s 'push' tags: %+v", fn(), tags)

	pn, err := collection.New(refs, tags)
	if err != nil {
		return nil, err
	}
	log.Debugf(
		"%s 'push' collection: %+v (%d repos / %d tags)",
		fn(), cn, cn.RepoCount(), cn.TagCount(),
	)

	return pn, nil
}

// PullTags compares images from remote registry and Docker daemon and pulls
// images that match tag spec passed and are not present in Docker daemon.
func (api *API) PullTags(cn *collection.Collection) error {
	log.Debugf(
		"%s collection: %+v (%d repos / %d tags)",
		fn(), cn, cn.RepoCount(), cn.TagCount(),
	)

	done := make(chan error, cn.TagCount())

	for _, ref := range cn.Refs() {
		repo := cn.Repo(ref)
		tags := cn.Tags(ref)

		log.Debugf("%s repository: %+v", fn(), repo)
		for _, tg := range tags {
			log.Debugf("%s tag: %+v", fn(), tg)
		}

		go func(repo *repository.Repository, tags []*tag.Tag, done chan error) {
			for _, tg := range tags {
				if !tg.NeedsPull() {
					done <- nil
					continue
				}

				ref := repo.Name() + ":" + tg.Name()

				log.Infof("PULLING %s", ref)
				err := api.dockerClient.Pull(ref)
				if err != nil {
					done <- err
					return
				}

				done <- nil
			}
		}(repo, tags, done)
	}

	return wait.Until(done)
}

// PushTags compares images from remote and "push" (usually local) registries,
// pulls images that are present in remote registry, but are not in "push" one
// and then [re-]pushes them to the "push" registry.
func (api *API) PushTags(cn *collection.Collection, push PushConfig) error {
	log.Debugf(
		"%s 'push' collection: %+v (%d repos / %d tags)",
		fn(), cn, cn.RepoCount(), cn.TagCount(),
	)
	log.Debugf("%s push config: %+v", fn(), push)

	done := make(chan error, cn.TagCount())

	if cn.TagCount() == 0 {
		log.Infof("%s No tags to push", fn())
		return nil
	}

	for _, ref := range cn.Refs() {
		repo := cn.Repo(ref)
		tags := cn.Tags(ref)

		log.Debugf("%s repository: %+v", fn(), repo)
		for _, tg := range tags {
			log.Debugf("%s tag: %+v", fn(), tg)
		}

		go func(repo *repository.Repository, tags []*tag.Tag, done chan error) {
			for _, tg := range tags {
				srcRef := repo.Name() + ":" + tg.Name()
				dstRef := push.Registry + push.Prefix + "/" + repo.Path() + ":" + tg.Name()

				log.Infof("[PULL/PUSH] PULLING %s", srcRef)
				if err := api.dockerClient.Pull(srcRef); err != nil {
					done <- err
					return
				}

				log.Infof("[PULL/PUSH] PUSHING %s => %s", srcRef, dstRef)
				if err := api.dockerClient.Tag(srcRef, dstRef); err != nil {
					done <- err
					return
				}
				if err := api.dockerClient.Push(dstRef); err != nil {
					done <- err
					return
				}

				done <- nil
			}
		}(repo, tags, done)
	}

	return wait.Until(done)
}

// New creates new instance of application API
func New(config Config) (*API, error) {
	if config.VerboseLogging {
		log.SetLevel(log.DebugLevel)
	}
	log.Debugf("%s API config: %+v", fn(), config)

	if config.ConcurrentRequests == 0 {
		config.ConcurrentRequests = 1
	}
	remote.ConcurrentRequests = config.ConcurrentRequests
	remote.TraceRequests = config.TraceRequests
	remote.RetryRequests = config.RetryRequests
	remote.RetryDelay = config.RetryDelay

	dockerclient.RetryPulls = config.RetryRequests
	dockerclient.RetryDelay = config.RetryDelay

	if config.InsecureRegistryEx != "" {
		repository.InsecureRegistryEx = config.InsecureRegistryEx
	}

	if config.DockerJSONConfigFile == "" {
		config.DockerJSONConfigFile = dockerconfig.DefaultDockerJSON
	}
	dockerConfig, err := dockerconfig.Load(config.DockerJSONConfigFile)
	if err != nil {
		return nil, err
	}
	dockerClient, err := dockerclient.New(dockerConfig)
	if err != nil {
		return nil, err
	}

	return &API{
		config:       config,
		dockerClient: dockerClient,
	}, nil
}
<commit_msg>NORELEASE: Coverage bump for v1.go #1<commit_after>package v1

import (
	"fmt"
	log "github.com/sirupsen/logrus"
	"runtime"
	"strings"
	"time"

	"github.com/ivanilves/lstags/api/v1/collection"
	dockerclient "github.com/ivanilves/lstags/docker/client"
	dockerconfig "github.com/ivanilves/lstags/docker/config"
	"github.com/ivanilves/lstags/repository"
	"github.com/ivanilves/lstags/tag"
	"github.com/ivanilves/lstags/tag/local"
	"github.com/ivanilves/lstags/tag/remote"
	"github.com/ivanilves/lstags/util/wait"
)

// Config holds API instance configuration
type Config struct {
	DockerJSONConfigFile string
	ConcurrentRequests   int
	TraceRequests        bool
	RetryRequests        int
	RetryDelay           time.Duration
	InsecureRegistryEx   string
	VerboseLogging       bool
}

// PushConfig holds push-specific configuration
type PushConfig struct {
	Prefix        string
	Registry      string
	UpdateChanged bool
}

// API represents application API instance
type API struct {
	config       Config
	dockerClient *dockerclient.DockerClient
}

// fn gives the name of the calling function (e.g. enriches log.Debugf() output)
// + optionally attaches free form string labels (mainly to identify goroutines)
func fn(labels ...string) string {
	function, _, _, _ := runtime.Caller(1)

	longname := runtime.FuncForPC(function).Name()

	nameparts := strings.Split(longname, ".")
	shortname := nameparts[len(nameparts)-1]

	if labels == nil {
		return fmt.Sprintf("[%s()]", shortname)
	}

	return fmt.Sprintf("[%s():%s]", shortname, strings.Join(labels, ":"))
}

// CollectTags collects information on tags present in remote registry and [local] Docker daemon,
// makes required comparisons between them and spits organized info back as collection.Collection
func (api *API) CollectTags(refs []string) (*collection.Collection, error) {
	if len(refs) == 0 {
		return nil, fmt.Errorf("no image references passed")
	}

	log.Debugf("%s references: %+v", fn(), refs)

	repos, err := repository.ParseRefs(refs)
	if err != nil {
		return nil, err
	}
	for _, repo := range repos {
		log.Debugf("%s repository: %+v", fn(), repo)
	}

	done := make(chan error, len(repos))
	tags := make(map[string][]*tag.Tag)

	for _, repo := range repos {
		go func(repo *repository.Repository, done chan error) {
			log.Infof("ANALYZE %s", repo.Ref())

			username, password, _ := api.dockerClient.Config().GetCredentials(repo.Registry())

			remoteTags, err := remote.FetchTags(repo, username, password)
			if err != nil {
				done <- err
				return
			}
			log.Debugf("%s remote tags: %+v", fn(repo.Ref()), remoteTags)

			localTags, _ := local.FetchTags(repo, api.dockerClient)

			log.Debugf("%s local tags: %+v", fn(repo.Ref()), localTags)

			sortedKeys, tagNames, joinedTags := tag.Join(
				remoteTags,
				localTags,
				repo.Tags(),
			)
			log.Debugf("%s joined tags: %+v", fn(repo.Ref()), joinedTags)

			tags[repo.Ref()] = tag.Collect(sortedKeys, tagNames, joinedTags)

			done <- nil

			log.Infof("FETCHED %s", repo.Ref())

			return
		}(repo, done)
	}

	if err := wait.Until(done); err != nil {
		return nil, err
	}
	log.Debugf("%s tags: %+v", fn(), tags)

	cn, err := collection.New(refs, tags)
	if err != nil {
		return nil, err
	}
	log.Debugf(
		"%s collection: %+v (%d repos / %d tags)",
		fn(), cn, cn.RepoCount(), cn.TagCount(),
	)

	return cn, nil
}

// CollectPushTags blends passed collection with information fetched from [local] "push" registry,
// makes required comparisons between them and spits organized info back as collection.Collection
func (api *API) CollectPushTags(cn *collection.Collection, push PushConfig) (*collection.Collection, error) {
	log.Debugf(
		"%s collection: %+v (%d repos / %d tags)",
		fn(), cn, cn.RepoCount(), cn.TagCount(),
	)
	log.Debugf("%s push config: %+v", fn(), push)

	refs := make([]string, len(cn.Refs()))
	done := make(chan error, len(cn.Refs()))
	tags := make(map[string][]*tag.Tag)

	for i, repo := range cn.Repos() {
		go func(repo *repository.Repository, i int, done chan error) {
			refs[i] = repo.Ref()

			pushPrefix := push.Prefix
			if pushPrefix == "" {
				pushPrefix = repo.PushPrefix()
			}

			var pushRepoPath string
			pushRepoPath = pushPrefix + "/" + repo.Path()
			pushRepoPath = pushRepoPath[1:] // Leading "/" in prefix should be removed!

			pushRef := fmt.Sprintf("%s/%s~/.*/", push.Registry, pushRepoPath)

			log.Debugf("%s 'push' reference: %+v", fn(repo.Ref()), pushRef)

			pushRepo, err := repository.ParseRef(pushRef)
			if err != nil {
				done <- err
				return
			}

			log.Infof("[PULL/PUSH] ANALYZE %s => %s", repo.Ref(), pushRef)

			username, password, _ := api.dockerClient.Config().GetCredentials(push.Registry)

			pushedTags, err := remote.FetchTags(pushRepo, username, password)
			if err != nil {
				if !strings.Contains(err.Error(), "404 Not Found") {
					done <- err
					return
				}

				log.Warnf("%s repo not found: %+s", fn(repo.Ref()), pushRef)

				pushedTags = make(map[string]*tag.Tag)
			}
			log.Debugf("%s pushed tags: %+v", fn(repo.Ref()), pushedTags)

			remoteTags := cn.TagMap(repo.Ref())
			log.Debugf("%s remote tags: %+v", fn(repo.Ref()), remoteTags)

			sortedKeys, tagNames, joinedTags := tag.Join(
				remoteTags,
				pushedTags,
				repo.Tags(),
			)
			log.Debugf("%s joined tags: %+v", fn(repo.Ref()), joinedTags)

			tagsToPush := make([]*tag.Tag, 0)
			for _, key := range sortedKeys {
				name := tagNames[key]
				tg := joinedTags[name]

				if tg.NeedsPush(push.UpdateChanged) {
					tagsToPush = append(tagsToPush, tg)
				}
			}
			log.Debugf("%s tags to push: %+v", fn(repo.Ref()), tagsToPush)

			tags[repo.Ref()] = tagsToPush

			done <- nil

			return
		}(repo, i, done)
	}

	if err := wait.Until(done); err != nil {
		return nil, err
	}
	log.Debugf("%s 'push' tags: %+v", fn(), tags)

	pn, err := collection.New(refs, tags)
	if err != nil {
		return nil, err
	}
	log.Debugf(
		"%s 'push' collection: %+v (%d repos / %d tags)",
		fn(), cn, cn.RepoCount(), cn.TagCount(),
	)

	return pn, nil
}

// PullTags compares images from remote registry and Docker daemon and pulls
// images that match tag spec passed and are not present in Docker daemon.
func (api *API) PullTags(cn *collection.Collection) error {
	log.Debugf(
		"%s collection: %+v (%d repos / %d tags)",
		fn(), cn, cn.RepoCount(), cn.TagCount(),
	)

	done := make(chan error, cn.TagCount())

	for _, ref := range cn.Refs() {
		repo := cn.Repo(ref)
		tags := cn.Tags(ref)

		log.Debugf("%s repository: %+v", fn(), repo)
		for _, tg := range tags {
			log.Debugf("%s tag: %+v", fn(), tg)
		}

		go func(repo *repository.Repository, tags []*tag.Tag, done chan error) {
			for _, tg := range tags {
				if !tg.NeedsPull() {
					done <- nil
					continue
				}

				ref := repo.Name() + ":" + tg.Name()

				log.Infof("PULLING %s", ref)
				err := api.dockerClient.Pull(ref)
				if err != nil {
					done <- err
					return
				}

				done <- nil
			}
		}(repo, tags, done)
	}

	return wait.Until(done)
}

// PushTags compares images from remote and "push" (usually local) registries,
// pulls images that are present in remote registry, but are not in "push" one
// and then [re-]pushes them to the "push" registry.
func (api *API) PushTags(cn *collection.Collection, push PushConfig) error {
	log.Debugf(
		"%s 'push' collection: %+v (%d repos / %d tags)",
		fn(), cn, cn.RepoCount(), cn.TagCount(),
	)
	log.Debugf("%s push config: %+v", fn(), push)

	done := make(chan error, cn.TagCount())

	if cn.TagCount() == 0 {
		log.Infof("%s No tags to push", fn())
		return nil
	}

	for _, ref := range cn.Refs() {
		repo := cn.Repo(ref)
		tags := cn.Tags(ref)

		log.Debugf("%s repository: %+v", fn(), repo)
		for _, tg := range tags {
			log.Debugf("%s tag: %+v", fn(), tg)
		}

		go func(repo *repository.Repository, tags []*tag.Tag, done chan error) {
			for _, tg := range tags {
				srcRef := repo.Name() + ":" + tg.Name()
				dstRef := push.Registry + push.Prefix + "/" + repo.Path() + ":" + tg.Name()

				log.Infof("[PULL/PUSH] PULLING %s", srcRef)
				if err := api.dockerClient.Pull(srcRef); err != nil {
					done <- err
					return
				}

				log.Infof("[PULL/PUSH] PUSHING %s => %s", srcRef, dstRef)
				if err := api.dockerClient.Tag(srcRef, dstRef); err != nil {
					done <- err
					return
				}
				if err := api.dockerClient.Push(dstRef); err != nil {
					done <- err
					return
				}

				done <- nil
			}
		}(repo, tags, done)
	}

	return wait.Until(done)
}

// New creates new instance of application API
func New(config Config) (*API, error) {
	if config.VerboseLogging {
		log.SetLevel(log.DebugLevel)
	}
	log.Debugf("%s API config: %+v", fn(), config)

	if config.ConcurrentRequests == 0 {
		config.ConcurrentRequests = 1
	}
	remote.ConcurrentRequests = config.ConcurrentRequests
	remote.TraceRequests = config.TraceRequests
	remote.RetryRequests = config.RetryRequests
	remote.RetryDelay = config.RetryDelay

	dockerclient.RetryPulls = config.RetryRequests
	dockerclient.RetryDelay = config.RetryDelay

	if config.InsecureRegistryEx != "" {
		repository.InsecureRegistryEx = config.InsecureRegistryEx
	}

	if config.DockerJSONConfigFile == "" {
		config.DockerJSONConfigFile = dockerconfig.DefaultDockerJSON
	}
	dockerConfig, err := dockerconfig.Load(config.DockerJSONConfigFile)
	if err != nil {
		return nil, err
	}
	dockerClient, err := dockerclient.New(dockerConfig)
	if err != nil {
		return nil, err
	}

	return &API{
		config:       config,
		dockerClient: dockerClient,
	}, nil
}
<|endoftext|>


<commit_before>package isolated

import (
	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var _ = Describe("proxy", func() {
	var proxyURL string

	BeforeEach(func() {
		proxyURL = "http://127.0.0.1:9999"
	})

	Context("V2", func() {
		It("errors when proxy is not setup properly", func() {
			session := helpers.CFWithEnv(map[string]string{"https_proxy": proxyURL}, "api", apiURL)
			Eventually(session.Err).Should(Say("%s/v2/info.*proxy.*%s", apiURL, proxyURL))
			Eventually(session.Err).Should(Say("TIP: If you are behind a firewall and require an HTTP proxy, verify the https_proxy environment variable is correctly set. Else, check your network connection."))
			Eventually(session).Should(Exit(1))
		})
	})

	Context("V3", func() {
		It("errors when proxy is not setup properly", func() {
			session := helpers.CFWithEnv(map[string]string{"https_proxy": proxyURL}, "run-task", "app", "echo")
			Eventually(session.Err).Should(Say("%s.*proxy.*%s", apiURL, proxyURL))
			Eventually(session.Err).Should(Say("TIP: If you are behind a firewall and require an HTTP proxy, verify the https_proxy environment variable is correctly set. Else, check your network connection."))
			Eventually(session).Should(Exit(1))
		})
	})
})
<commit_msg>don't need the url anymore<commit_after>package isolated

import (
	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var _ = Describe("proxy", func() {
	var proxyURL string

	BeforeEach(func() {
		proxyURL = "127.0.0.1:9999"
	})

	Context("V2", func() {
		It("errors when proxy is not setup properly", func() {
			session := helpers.CFWithEnv(map[string]string{"https_proxy": proxyURL}, "api", apiURL)
			Eventually(session.Err).Should(Say("%s/v2/info.*proxy.*%s", apiURL, proxyURL))
			Eventually(session.Err).Should(Say("TIP: If you are behind a firewall and require an HTTP proxy, verify the https_proxy environment variable is correctly set. Else, check your network connection."))
			Eventually(session).Should(Exit(1))
		})
	})

	Context("V3", func() {
		It("errors when proxy is not setup properly", func() {
			session := helpers.CFWithEnv(map[string]string{"https_proxy": proxyURL}, "run-task", "app", "echo")
			Eventually(session.Err).Should(Say("%s.*proxy.*%s", apiURL, proxyURL))
			Eventually(session.Err).Should(Say("TIP: If you are behind a firewall and require an HTTP proxy, verify the https_proxy environment variable is correctly set. Else, check your network connection."))
			Eventually(session).Should(Exit(1))
		})
	})
})
<|endoftext|>


<commit_before>package isolated

import (
	"code.cloudfoundry.org/cli/integration/helpers"
	"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var _ = Describe("stack command", func() {
	var (
		orgName          string
		spaceName        string
		stackName        string
		stackDescription string
	)

	BeforeEach(func() {
		orgName = helpers.NewOrgName()
		spaceName = helpers.NewSpaceName()
		stackName = helpers.PrefixedRandomName("stack")
		stackDescription = "this is a test stack"
	})

	Describe("help", func() {
		When("--help flag is set", func() {
			It("Displays command usage to output", func() {
				session := helpers.CF("stack", "--help")

				Eventually(session).Should(Say(`NAME:`))
				Eventually(session).Should(Say(`stack - Show information for a stack \(a stack is a pre-built file system, including an operating system, that can run apps\)`))
				Eventually(session).Should(Say("USAGE:"))
				Eventually(session).Should(Say("cf stack STACK_NAME"))

				Eventually(session).Should(Exit(0))
			})
		})
	})

	When("the stack name is not provided", func() {
		It("tells the user that the stack name is required, prints help text, and exits 1", func() {
			session := helpers.CF("stack")

			Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `STACK_NAME` was not provided"))
			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Exit(1))
		})
	})

	When("the environment is not setup correctly", func() {
		It("fails with the appropriate errors", func() {
			helpers.CheckEnvironmentTargetedCorrectly(true, true, ReadOnlyOrg, "stack", stackName)
		})
	})

	When("the environment is set up correctly", func() {
		var username string

		BeforeEach(func() {
			helpers.SetupCF(orgName, spaceName)
			username, _ = helpers.GetCredentials()
		})

		AfterEach(func() {
			helpers.QuickDeleteOrg(orgName)
		})

		When("the input is invalid", func() {
			When("there are not enough arguments", func() {
				It("outputs the usage and exits 1", func() {
					session := helpers.CF("stack")

					Eventually(session.Err).Should(Say("Incorrect Usage:"))
					Eventually(session).Should(Say("NAME:"))
					Eventually(session).Should(Exit(1))
				})
			})

			When("there too many arguments", func() {
				It("ignores the extra arguments", func() {
					session := helpers.CF("stack", stackName, "extra")

					Eventually(session).Should(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
					Eventually(session.Err).Should(Say("Stack %s not found", stackName))
					Eventually(session).Should(Say("FAILED"))
					Eventually(session).Should(Exit(1))
				})
			})
		})

		When("the stack does not exist", func() {
			It("Fails", func() {
				session := helpers.CF("stack", stackName)

				Eventually(session).Should(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
				Eventually(session.Err).Should(Say("Stack %s not found", stackName))
				Eventually(session).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})

		When("the stack exists", func() {
			BeforeEach(func() {
				jsonBody := fmt.Sprintf(`{"name": "%s", "description": "%s"}`, stackName, stackDescription)
				session := helpers.CF("curl", "-d", jsonBody, "-X", "POST", "/v3/stacks")
				Eventually(session).Should(Exit(0))
			})
			It("Shows the details for the stack", func() {
				session := helpers.CF("stack", stackName)

				Eventually(session).Should(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
				Eventually(session).Should(Say(`name:\s+%s`, stackName))
				Eventually(session).Should(Say(`description:\s+%s`, stackDescription))
				Eventually(session).Should(Exit(0))
			})

			When("the stack exists and the --guid flag is passed", func() {
				It("prints nothing but the guid", func() {
					session := helpers.CF("stack", stackName, "--guid")

					Consistently(session).ShouldNot(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
					Consistently(session).ShouldNot(Say(`name:\s+%s`, stackName))
					Consistently(session).ShouldNot(Say(`description:\s+%s`, stackDescription))
					Eventually(session).Should(Say(`^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`))
					Eventually(session).Should(Exit(0))
				})
			})
		})
	})
})
<commit_msg>Fix integration test for stack command<commit_after>package isolated

import (
	"code.cloudfoundry.org/cli/integration/helpers"
	"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var _ = Describe("stack command", func() {
	var (
		orgName          string
		spaceName        string
		stackName        string
		stackDescription string
	)

	BeforeEach(func() {
		orgName = helpers.NewOrgName()
		spaceName = helpers.NewSpaceName()
		stackName = helpers.PrefixedRandomName("stack")
		stackDescription = "this is a test stack"
	})

	Describe("help", func() {
		When("--help flag is set", func() {
			It("Displays command usage to output", func() {
				session := helpers.CF("stack", "--help")

				Eventually(session).Should(Say(`NAME:`))
				Eventually(session).Should(Say(`stack - Show information for a stack \(a stack is a pre-built file system, including an operating system, that can run apps\)`))
				Eventually(session).Should(Say("USAGE:"))
				Eventually(session).Should(Say("cf stack STACK_NAME"))

				Eventually(session).Should(Exit(0))
			})
		})
	})

	When("the stack name is not provided", func() {
		It("tells the user that the stack name is required, prints help text, and exits 1", func() {
			session := helpers.CF("stack")

			Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `STACK_NAME` was not provided"))
			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Exit(1))
		})
	})

	When("the environment is not setup correctly", func() {
		It("fails with the appropriate errors", func() {
			helpers.CheckEnvironmentTargetedCorrectly(true, true, ReadOnlyOrg, "stack", stackName)
		})
	})

	When("the environment is set up correctly", func() {
		var username string

		BeforeEach(func() {
			helpers.SetupCF(orgName, spaceName)
			username, _ = helpers.GetCredentials()
		})

		AfterEach(func() {
			helpers.QuickDeleteOrg(orgName)
		})

		When("the input is invalid", func() {
			When("there are not enough arguments", func() {
				It("outputs the usage and exits 1", func() {
					session := helpers.CF("stack")

					Eventually(session.Err).Should(Say("Incorrect Usage:"))
					Eventually(session).Should(Say("NAME:"))
					Eventually(session).Should(Exit(1))
				})
			})

			When("there too many arguments", func() {
				It("ignores the extra arguments", func() {
					session := helpers.CF("stack", stackName, "extra")

					Eventually(session).Should(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
					Eventually(session.Err).Should(Say("Stack %s not found", stackName))
					Eventually(session).Should(Say("FAILED"))
					Eventually(session).Should(Exit(1))
				})
			})
		})

		When("the stack does not exist", func() {
			It("Fails", func() {
				session := helpers.CF("stack", stackName)

				Eventually(session).Should(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
				Eventually(session.Err).Should(Say("Stack %s not found", stackName))
				Eventually(session).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})

		When("the stack exists", func() {
			var stackGuid string

			BeforeEach(func() {
				jsonBody := fmt.Sprintf(`{"name": "%s", "description": "%s"}`, stackName, stackDescription)
				session := helpers.CF("curl", "-d", jsonBody, "-X", "POST", "/v3/stacks")
				Eventually(session).Should(Exit(0))

				r, _ := regexp.Compile(`[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`)
				stackGuid = string(r.Find(session.Out.Contents()))
			})

			AfterEach(func() {
				session := helpers.CF("curl", "-X", "DELETE", "/v3/stacks/" + stackGuid)
				Eventually(session).Should(Exit(0))
			})

			It("Shows the details for the stack", func() {
				session := helpers.CF("stack", stackName)

				Eventually(session).Should(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
				Eventually(session).Should(Say(`name:\s+%s`, stackName))
				Eventually(session).Should(Say(`description:\s+%s`, stackDescription))
				Eventually(session).Should(Exit(0))
			})

			When("the stack exists and the --guid flag is passed", func() {
				It("prints nothing but the guid", func() {
					session := helpers.CF("stack", stackName, "--guid")

					Consistently(session).ShouldNot(Say(`Getting stack %s in org %s / space %s as %s\.\.\.`, stackName, orgName, spaceName, username))
					Consistently(session).ShouldNot(Say(`name:\s+%s`, stackName))
					Consistently(session).ShouldNot(Say(`description:\s+%s`, stackDescription))
					Eventually(session).Should(Say(`^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}`))
					Eventually(session).Should(Exit(0))
				})
			})
		})
	})
})
<|endoftext|>