<commit_before>package bslack

import (
	"context"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/nlopes/slack"
	"github.com/sirupsen/logrus"
)

const minimumRefreshInterval = 10 * time.Second

type users struct {
	log *logrus.Entry
	sc  *slack.Client

	users      map[string]*slack.User
	usersMutex sync.RWMutex

	refreshInProgress bool
	earliestRefresh   time.Time
	refreshMutex      sync.Mutex
}

func newUserManager(log *logrus.Entry, sc *slack.Client) *users {
	return &users{
		log:             log,
		sc:              sc,
		users:           make(map[string]*slack.User),
		earliestRefresh: time.Now(),
	}
}

func (b *users) getUser(id string) *slack.User {
	b.usersMutex.RLock()
	user, ok := b.users[id]
	b.usersMutex.RUnlock()
	if ok {
		return user
	}
	b.populateUser(id)
	b.usersMutex.RLock()
	defer b.usersMutex.RUnlock()

	return b.users[id]
}

func (b *users) getUsername(id string) string {
	if user := b.getUser(id); user != nil {
		if user.Profile.DisplayName != "" {
			return user.Profile.DisplayName
		}
		return user.Name
	}
	b.log.Warnf("Could not find user with ID '%s'", id)
	return ""
}

func (b *users) getAvatar(id string) string {
	if user := b.getUser(id); user != nil {
		return user.Profile.Image48
	}
	return ""
}

func (b *users) populateUser(userID string) {
	b.usersMutex.RLock()
	_, exists := b.users[userID]
	b.usersMutex.RUnlock()
	if exists {
		// already in cache
		return
	}

	user, err := b.sc.GetUserInfo(userID)
	if err != nil {
		b.log.Debugf("GetUserInfo failed for %v: %v", userID, err)
		return
	}

	b.usersMutex.Lock()
	b.users[userID] = user
	b.usersMutex.Unlock()
}

func (b *users) populateUsers(wait bool) {
	b.refreshMutex.Lock()
	if !wait && (time.Now().Before(b.earliestRefresh) || b.refreshInProgress) {
		b.log.Debugf("Not refreshing user list as it was done less than %v ago.",
			minimumRefreshInterval)
		b.refreshMutex.Unlock()

		return
	}
	for b.refreshInProgress {
		b.refreshMutex.Unlock()
		time.Sleep(time.Second)
		b.refreshMutex.Lock()
	}
	b.refreshInProgress = true
	b.refreshMutex.Unlock()

	newUsers := map[string]*slack.User{}
	pagination := b.sc.GetUsersPaginated(slack.GetUsersOptionLimit(200))
	count := 0
	for {
		var err error
		pagination, err = pagination.Next(context.Background())
		time.Sleep(time.Second)
		if err != nil {
			if pagination.Done(err) {
				break
			}

			if err = handleRateLimit(b.log, err); err != nil {
				b.log.Errorf("Could not retrieve users: %#v", err)
				return
			}
			continue
		}

		for i := range pagination.Users {
			newUsers[pagination.Users[i].ID] = &pagination.Users[i]
		}
		b.log.Debugf("getting %d users", len(pagination.Users))
		count++
		// more > 2000 users, slack will complain and ratelimit. break
		if count > 10 {
			b.log.Info("Large slack detected > 2000 users, skipping loading complete userlist.")
			break
		}
	}

	b.usersMutex.Lock()
	defer b.usersMutex.Unlock()
	b.users = newUsers

	b.refreshMutex.Lock()
	defer b.refreshMutex.Unlock()
	b.earliestRefresh = time.Now().Add(minimumRefreshInterval)
	b.refreshInProgress = false
}

type channels struct {
	log *logrus.Entry
	sc  *slack.Client

	channelsByID   map[string]*slack.Channel
	channelsByName map[string]*slack.Channel
	channelsMutex  sync.RWMutex

	channelMembers      map[string][]string
	channelMembersMutex sync.RWMutex

	refreshInProgress bool
	earliestRefresh   time.Time
	refreshMutex      sync.Mutex
}

func newChannelManager(log *logrus.Entry, sc *slack.Client) *channels {
	return &channels{
		log:             log,
		sc:              sc,
		channelsByID:    make(map[string]*slack.Channel),
		channelsByName:  make(map[string]*slack.Channel),
		earliestRefresh: time.Now(),
	}
}

func (b *channels) getChannel(channel string) (*slack.Channel, error) {
	if strings.HasPrefix(channel, "ID:") {
		return b.getChannelByID(strings.TrimPrefix(channel, "ID:"))
	}
	return b.getChannelByName(channel)
}

func (b *channels) getChannelByName(name string) (*slack.Channel, error) {
	return b.getChannelBy(name, b.channelsByName)
}

func (b *channels) getChannelByID(id string) (*slack.Channel, error) {
	return b.getChannelBy(id, b.channelsByID)
}

func (b *channels) getChannelBy(lookupKey string, lookupMap map[string]*slack.Channel) (*slack.Channel, error) {
	b.channelsMutex.RLock()
	defer b.channelsMutex.RUnlock()

	if channel, ok := lookupMap[lookupKey]; ok {
		return channel, nil
	}
	return nil, fmt.Errorf("channel %s not found", lookupKey)
}

func (b *channels) getChannelMembers(users *users) config.ChannelMembers {
	b.channelMembersMutex.RLock()
	defer b.channelMembersMutex.RUnlock()

	membersInfo := config.ChannelMembers{}
	for channelID, members := range b.channelMembers {
		for _, member := range members {
			channelName := ""
			userName := ""
			userNick := ""
			user := users.getUser(member)
			if user != nil {
				userName = user.Name
				userNick = user.Profile.DisplayName
			}
			channel, _ := b.getChannelByID(channelID)
			if channel != nil {
				channelName = channel.Name
			}
			memberInfo := config.ChannelMember{
				Username:    userName,
				Nick:        userNick,
				UserID:      member,
				ChannelID:   channelID,
				ChannelName: channelName,
			}
			membersInfo = append(membersInfo, memberInfo)
		}
	}
	return membersInfo
}

func (b *channels) registerChannel(channel slack.Channel) {
	b.channelsMutex.Lock()
	b.channelsByID[channel.ID] = &channel
	b.channelsByName[channel.Name] = &channel
	b.channelsMutex.Unlock()
}

func (b *channels) populateChannels(wait bool) {
	b.refreshMutex.Lock()
	if !wait && (time.Now().Before(b.earliestRefresh) || b.refreshInProgress) {
		b.log.Debugf("Not refreshing channel list as it was done less than %v seconds ago.", minimumRefreshInterval)
		b.refreshMutex.Unlock()
		return
	}
	for b.refreshInProgress {
		b.refreshMutex.Unlock()
		time.Sleep(time.Second)
		b.refreshMutex.Lock()
	}
	b.refreshInProgress = true
	b.refreshMutex.Unlock()

	newChannelsByID := map[string]*slack.Channel{}
	newChannelsByName := map[string]*slack.Channel{}
	newChannelMembers := make(map[string][]string)

	// We only retrieve public and private channels, not IMs
	// and MPIMs as those do not have a channel name.
	queryParams := &slack.GetConversationsParameters{
		ExcludeArchived: "true",
		Types:           []string{"public_channel,private_channel"},
	}
	for {
		channels, nextCursor, err := b.sc.GetConversations(queryParams)
		if err != nil {
			if err = handleRateLimit(b.log, err); err != nil {
				b.log.Errorf("Could not retrieve channels: %#v", err)
				return
			}
			continue
		}

		for i := range channels {
			newChannelsByID[channels[i].ID] = &channels[i]
			newChannelsByName[channels[i].Name] = &channels[i]
			// also find all the members in every channel
			// comment for now, issues on big slacks
			/*
				members, err := b.getUsersInConversation(channels[i].ID)
				if err != nil {
					if err = b.handleRateLimit(err); err != nil {
						b.Log.Errorf("Could not retrieve channel members: %#v", err)
						return
					}
					continue
				}
				newChannelMembers[channels[i].ID] = members
			*/
		}

		if nextCursor == "" {
			break
		}
		queryParams.Cursor = nextCursor
	}

	b.channelsMutex.Lock()
	defer b.channelsMutex.Unlock()
	b.channelsByID = newChannelsByID
	b.channelsByName = newChannelsByName

	b.channelMembersMutex.Lock()
	defer b.channelMembersMutex.Unlock()
	b.channelMembers = newChannelMembers

	b.refreshMutex.Lock()
	defer b.refreshMutex.Unlock()
	b.earliestRefresh = time.Now().Add(minimumRefreshInterval)
	b.refreshInProgress = false
}
<commit_msg>Fix race-condition in populateUser() (#767)<commit_after>package bslack

import (
	"context"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/nlopes/slack"
	"github.com/sirupsen/logrus"
)

const minimumRefreshInterval = 10 * time.Second

type users struct {
	log *logrus.Entry
	sc  *slack.Client

	users           map[string]*slack.User
	usersMutex      sync.RWMutex
	usersSyncPoints map[string]chan struct{}

	refreshInProgress bool
	earliestRefresh   time.Time
	refreshMutex      sync.Mutex
}

func newUserManager(log *logrus.Entry, sc *slack.Client) *users {
	return &users{
		log:             log,
		sc:              sc,
		users:           make(map[string]*slack.User),
		usersSyncPoints: make(map[string]chan struct{}),
		earliestRefresh: time.Now(),
	}
}

func (b *users) getUser(id string) *slack.User {
	b.usersMutex.RLock()
	user, ok := b.users[id]
	b.usersMutex.RUnlock()
	if ok {
		return user
	}
	b.populateUser(id)
	b.usersMutex.RLock()
	defer b.usersMutex.RUnlock()

	return b.users[id]
}

func (b *users) getUsername(id string) string {
	if user := b.getUser(id); user != nil {
		if user.Profile.DisplayName != "" {
			return user.Profile.DisplayName
		}
		return user.Name
	}
	b.log.Warnf("Could not find user with ID '%s'", id)
	return ""
}

func (b *users) getAvatar(id string) string {
	if user := b.getUser(id); user != nil {
		return user.Profile.Image48
	}
	return ""
}

func (b *users) populateUser(userID string) {
	for {
		b.usersMutex.Lock()
		_, exists := b.users[userID]
		if exists {
			// already in cache
			b.usersMutex.Unlock()
			return
		}

		if syncPoint, ok := b.usersSyncPoints[userID]; ok {
			// Another goroutine is already populating this user for us so wait on it to finish.
			b.usersMutex.Unlock()
			<-syncPoint
			// We do not return and iterate again to check that the entry does indeed exist
			// in case the previous query failed for some reason.
		} else {
			b.usersSyncPoints[userID] = make(chan struct{})
			b.usersMutex.Unlock()
			break
		}
	}

	// Do not hold the lock while fetching information from Slack
	// as this might take an unbounded amount of time.
	b.usersMutex.Unlock()

	user, err := b.sc.GetUserInfo(userID)
	if err != nil {
		b.log.Debugf("GetUserInfo failed for %v: %v", userID, err)
		return
	}

	b.usersMutex.Lock()
	defer b.usersMutex.Unlock()

	// Register user information.
	b.users[userID] = user

	// Wake up any waiting goroutines and remove the synchronization point.
	close(b.usersSyncPoints[userID])
	delete(b.usersSyncPoints, userID)
}

func (b *users) populateUsers(wait bool) {
	b.refreshMutex.Lock()
	if !wait && (time.Now().Before(b.earliestRefresh) || b.refreshInProgress) {
		b.log.Debugf("Not refreshing user list as it was done less than %v ago.", minimumRefreshInterval)
		b.refreshMutex.Unlock()

		return
	}
	for b.refreshInProgress {
		b.refreshMutex.Unlock()
		time.Sleep(time.Second)
		b.refreshMutex.Lock()
	}
	b.refreshInProgress = true
	b.refreshMutex.Unlock()

	newUsers := map[string]*slack.User{}
	pagination := b.sc.GetUsersPaginated(slack.GetUsersOptionLimit(200))
	count := 0
	for {
		var err error
		pagination, err = pagination.Next(context.Background())
		time.Sleep(time.Second)
		if err != nil {
			if pagination.Done(err) {
				break
			}

			if err = handleRateLimit(b.log, err); err != nil {
				b.log.Errorf("Could not retrieve users: %#v", err)
				return
			}
			continue
		}

		for i := range pagination.Users {
			newUsers[pagination.Users[i].ID] = &pagination.Users[i]
		}
		b.log.Debugf("getting %d users", len(pagination.Users))
		count++
		// more > 2000 users, slack will complain and ratelimit. break
		if count > 10 {
			b.log.Info("Large slack detected > 2000 users, skipping loading complete userlist.")
			break
		}
	}

	b.usersMutex.Lock()
	defer b.usersMutex.Unlock()
	b.users = newUsers

	b.refreshMutex.Lock()
	defer b.refreshMutex.Unlock()
	b.earliestRefresh = time.Now().Add(minimumRefreshInterval)
	b.refreshInProgress = false
}

type channels struct {
	log *logrus.Entry
	sc  *slack.Client

	channelsByID   map[string]*slack.Channel
	channelsByName map[string]*slack.Channel
	channelsMutex  sync.RWMutex

	channelMembers      map[string][]string
	channelMembersMutex sync.RWMutex

	refreshInProgress bool
	earliestRefresh   time.Time
	refreshMutex      sync.Mutex
}

func newChannelManager(log *logrus.Entry, sc *slack.Client) *channels {
	return &channels{
		log:             log,
		sc:              sc,
		channelsByID:    make(map[string]*slack.Channel),
		channelsByName:  make(map[string]*slack.Channel),
		earliestRefresh: time.Now(),
	}
}

func (b *channels) getChannel(channel string) (*slack.Channel, error) {
	if strings.HasPrefix(channel, "ID:") {
		return b.getChannelByID(strings.TrimPrefix(channel, "ID:"))
	}
	return b.getChannelByName(channel)
}

func (b *channels) getChannelByName(name string) (*slack.Channel, error) {
	return b.getChannelBy(name, b.channelsByName)
}

func (b *channels) getChannelByID(id string) (*slack.Channel, error) {
	return b.getChannelBy(id, b.channelsByID)
}

func (b *channels) getChannelBy(lookupKey string, lookupMap map[string]*slack.Channel) (*slack.Channel, error) {
	b.channelsMutex.RLock()
	defer b.channelsMutex.RUnlock()

	if channel, ok := lookupMap[lookupKey]; ok {
		return channel, nil
	}
	return nil, fmt.Errorf("channel %s not found", lookupKey)
}

func (b *channels) getChannelMembers(users *users) config.ChannelMembers {
	b.channelMembersMutex.RLock()
	defer b.channelMembersMutex.RUnlock()

	membersInfo := config.ChannelMembers{}
	for channelID, members := range b.channelMembers {
		for _, member := range members {
			channelName := ""
			userName := ""
			userNick := ""
			user := users.getUser(member)
			if user != nil {
				userName = user.Name
				userNick = user.Profile.DisplayName
			}
			channel, _ := b.getChannelByID(channelID)
			if channel != nil {
				channelName = channel.Name
			}
			memberInfo := config.ChannelMember{
				Username:    userName,
				Nick:        userNick,
				UserID:      member,
				ChannelID:   channelID,
				ChannelName: channelName,
			}
			membersInfo = append(membersInfo, memberInfo)
		}
	}
	return membersInfo
}

func (b *channels) registerChannel(channel slack.Channel) {
	b.channelsMutex.Lock()
	defer b.channelsMutex.Unlock()

	b.channelsByID[channel.ID] = &channel
	b.channelsByName[channel.Name] = &channel
}

func (b *channels) populateChannels(wait bool) {
	b.refreshMutex.Lock()
	if !wait && (time.Now().Before(b.earliestRefresh) || b.refreshInProgress) {
		b.log.Debugf("Not refreshing channel list as it was done less than %v seconds ago.", minimumRefreshInterval)
		b.refreshMutex.Unlock()
		return
	}
	for b.refreshInProgress {
		b.refreshMutex.Unlock()
		time.Sleep(time.Second)
		b.refreshMutex.Lock()
	}
	b.refreshInProgress = true
	b.refreshMutex.Unlock()

	newChannelsByID := map[string]*slack.Channel{}
	newChannelsByName := map[string]*slack.Channel{}
	newChannelMembers := make(map[string][]string)

	// We only retrieve public and private channels, not IMs
	// and MPIMs as those do not have a channel name.
	queryParams := &slack.GetConversationsParameters{
		ExcludeArchived: "true",
		Types:           []string{"public_channel,private_channel"},
	}
	for {
		channels, nextCursor, err := b.sc.GetConversations(queryParams)
		if err != nil {
			if err = handleRateLimit(b.log, err); err != nil {
				b.log.Errorf("Could not retrieve channels: %#v", err)
				return
			}
			continue
		}

		for i := range channels {
			newChannelsByID[channels[i].ID] = &channels[i]
			newChannelsByName[channels[i].Name] = &channels[i]
			// also find all the members in every channel
			// comment for now, issues on big slacks
			/*
				members, err := b.getUsersInConversation(channels[i].ID)
				if err != nil {
					if err = b.handleRateLimit(err); err != nil {
						b.Log.Errorf("Could not retrieve channel members: %#v", err)
						return
					}
					continue
				}
				newChannelMembers[channels[i].ID] = members
			*/
		}

		if nextCursor == "" {
			break
		}
		queryParams.Cursor = nextCursor
	}

	b.channelsMutex.Lock()
	defer b.channelsMutex.Unlock()
	b.channelsByID = newChannelsByID
	b.channelsByName = newChannelsByName

	b.channelMembersMutex.Lock()
	defer b.channelMembersMutex.Unlock()
	b.channelMembers = newChannelMembers

	b.refreshMutex.Lock()
	defer b.refreshMutex.Unlock()
	b.earliestRefresh = time.Now().Add(minimumRefreshInterval)
	b.refreshInProgress = false
}
<|endoftext|>


<commit_before>package goredis

import (
	"testing"
)

func TestTransaction(t *testing.T) {
	transaction, err := r.Transaction()
	if err != nil {
		t.Error(err)
	}
	defer transaction.Close()
	transaction.Command("DEL", "key")
	transaction.Command("SET", "key", 1)
	transaction.Command("INCR", "key")
	transaction.Command("GET", "key")
	result, err := transaction.Exec()
	if err != nil {
		t.Error(err)
	}
	if len(result) != 4 {
		t.Fail()
	}
	if s, err := result[3].StringValue(); err != nil || s != "2" {
		t.Fail()
	}
}
<commit_msg>add more transaction test case<commit_after>package goredis

import (
	"testing"
)

func TestTransaction(t *testing.T) {
	transaction, err := r.Transaction()
	if err != nil {
		t.Error(err)
	}
	defer transaction.Close()
	if err := transaction.Command("DEL", "key"); err != nil {
		t.Error(err)
	}
	if err := transaction.Command("SET", "key", 1); err != nil {
		t.Error(err)
	}
	if err := transaction.Command("INCR", "key"); err != nil {
		t.Error(err)
	}
	if err := transaction.Command("GET", "key"); err != nil {
		t.Error(err)
	}
	result, err := transaction.Exec()
	if err != nil {
		t.Error(err)
	}
	if len(result) != 4 {
		t.Fail()
	}
	if s, err := result[3].StringValue(); err != nil || s != "2" {
		t.Fail()
	}
}

func TestWatch(t *testing.T) {
	transaction, err := r.Transaction()
	if err != nil {
		t.Error(err)
	}
	defer transaction.Close()
	if err := transaction.Watch("key"); err != nil {
		t.Error(err)
	}
}

func TestUnWatch(t *testing.T) {
	transaction, err := r.Transaction()
	if err != nil {
		t.Error(err)
	}
	defer transaction.Close()
	transaction.Watch("key")
	if err := transaction.UnWatch(); err != nil {
		t.Error(err)
	}
}

func TestDiscard(t *testing.T) {
	transaction, err := r.Transaction()
	if err != nil {
		t.Error(err)
	}
	defer transaction.Close()
	transaction.Command("SET", "KEY", 1)
	if transaction.Discard(); err != nil {
		t.Error(err)
	}
}
<|endoftext|>


<commit_before>package draw

import (
	"encoding/hex"
	"fmt"
	"image/color"
	"log"
	"math/rand"
	"strconv"
)

// RandomColorFromArray returns a random color from the given array.
func RandomColorFromArray(colors []color.RGBA) color.RGBA {
	r := rand.Intn(len(colors))
	return colors[r]
}

// ColorByPercentage returns a color based on the given percentage and the
// number of colors present in the 'colors' array
func ColorByPercentage(colors []color.RGBA, percentage int) color.RGBA {
	if percentage == 0 {
		percentage = 1
	}
	r := rand.Intn(100)
	cr := rand.Intn(100)

	colorChange := 100 / len(colors)
	frontier := (cr / colorChange)

	if frontier == 0 {
		frontier = 1
	} else if frontier == len(colors) {
		frontier = frontier - 1
	}

	if r < percentage {
		return RandomColorFromArray(colors[0:frontier])
	}
	return RandomColorFromArray(colors[frontier-1:])
}

// FillFromRGBA return a "fill" SVG style from a color.RGBA
func FillFromRGBA(c color.RGBA) string {
	return fmt.Sprintf("fill:rgb(%d,%d,%d)", c.R, c.G, c.B)
}

// PickColor returns a color given a key string, an array of colors and an index.
// key: should be a md5 hash string.
// index: is an index from the key string. Should be in interval [0, 16]
// Algorithm: PickColor converts the key[index] value to a decimal value.
// We pick the ith colors that respects the equality value%numberOfColors == i.
func PickColor(key string, colors []color.RGBA, index int) color.RGBA {
	n := len(colors)
	s := hex.EncodeToString([]byte{key[index]})
	if r, err := strconv.ParseInt(s, 16, 0); err == nil {
		for i := 0; i < n; i++ {
			if int(r)%n == i {
				return colors[i]
			}
		}
	} else {
		log.Println("Error calling ParseInt(%v, 16, 0)", s, err)
	}
	return colors[0]
}
<commit_msg>simplify colorByPercentage<commit_after>package draw

import (
	"encoding/hex"
	"fmt"
	"image/color"
	"log"
	"math"
	"math/rand"
	"strconv"
)

// RandomColorFromArray returns a random color from the given array.
func RandomColorFromArray(colors []color.RGBA) color.RGBA {
	r := rand.Intn(len(colors))
	return colors[r]
}

// ColorByPercentage returns a color based on the given percentage and the
// number of colors present in the 'colors' array in a gradient way.
func ColorByPercentage(colors []color.RGBA, percentage int) color.RGBA {
	r := rand.Intn(100)
	colorChange := 100 / len(colors)
	frontier := 0
	frontier = int(math.Ceil(float64(r) / float64(colorChange)))

	if r < percentage {
		if frontier == 0 {
			frontier++
		}
		return RandomColorFromArray(colors[:frontier])
	}
	if frontier == len(colors) {
		frontier--
	}
	return RandomColorFromArray(colors[frontier:])
}

// FillFromRGBA return a "fill" SVG style from a color.RGBA
func FillFromRGBA(c color.RGBA) string {
	return fmt.Sprintf("fill:rgb(%d,%d,%d)", c.R, c.G, c.B)
}

// PickColor returns a color given a key string, an array of colors and an index.
// key: should be a md5 hash string.
// index: is an index from the key string. Should be in interval [0, 16]
// Algorithm: PickColor converts the key[index] value to a decimal value.
// We pick the ith colors that respects the equality value%numberOfColors == i.
func PickColor(key string, colors []color.RGBA, index int) color.RGBA {
	n := len(colors)
	s := hex.EncodeToString([]byte{key[index]})
	if r, err := strconv.ParseInt(s, 16, 0); err == nil {
		for i := 0; i < n; i++ {
			if int(r)%n == i {
				return colors[i]
			}
		}
	} else {
		log.Println("Error calling ParseInt(%v, 16, 0)", s, err)
	}
	return colors[0]
}
<|endoftext|>


<commit_before>package possum

import (
	"context"
	"net/http"

	"github.com/mikespook/possum/session"
)

// SessionName is the type of name for context
type SessionName string

const sessionCookieName = "possum"
const sessionName SessionName = "session"

// SessionFacotryFunc is the default factory function of session.
var SessionFacotryFunc session.FactoryFunc = session.NewFactory(session.CookieStorage(sessionCookieName, nil))

// Session extracts data from the request and returns session instance.
// It uses SessionFacotryFunc to initialise session if no session has been set yet.
func Session(w http.ResponseWriter, req *http.Request) (session *session.Session, err error) {
	var ok bool
	if session, ok = req.Context().Value(sessionName).(*Session); ok {
		return session, nil
	}

	session, err = SessionFacotryFunc(w, req)
	if err != nil {
		return nil, err
	}
	context.WithValue(req.Context(), sessionName, session)
	return session, nil
}
<commit_msg>new session<commit_after>package possum

import (
	"context"
	"net/http"

	"github.com/mikespook/possum/session"
)

type contextKey string

const sessionCookieName = "possum"
const sessionKey contextKey = "session"

// SessionFacotryFunc is the default factory function of session.
var SessionFacotryFunc = session.NewFactory(session.CookieStorage(sessionCookieName, nil))

// Session extracts data from the request and returns session instance.
// It uses SessionFacotryFunc to initialise session if no session has been set yet.
func Session(w http.ResponseWriter, req *http.Request) (session *session.Session, err error) {
	var ok bool
	if session, ok = req.Context().Value(sessionKey).(*Session); ok {
		return session, nil
	}

	session, err = SessionFacotryFunc(w, req)
	if err != nil {
		return nil, err
	}
	req.WithContext(context.WithValue(req.Context(), sessionKey, session))
	return session, nil
}

func handleSession(w http.ResponseWriter, req *http.Request) {
	ctx := req.Context()
	if session, ok := ctx.Value(sessionKey).(*Session); ok {
		if err := session.Flush(); err != nil {
			panic(Error{http.StatusInternalServerError, err.Error()})
		}
	}
}
<|endoftext|>


<commit_before>package gockle

import (
	"fmt"

	"github.com/gocql/gocql"
	"github.com/maraino/go-mock"
)

// Session wraps *gocql.Session.
type Session interface {
	// Close closes the session.
	Close()

	// Columns returns a map from column names to types for keyspace and table.
	Columns(keyspace, table string) (map[string]gocql.TypeInfo, error)

	// QueryBatch returns a Batch with type kind for batched queries.
	QueryBatch(kind BatchKind) Batch

	// QueryExecute runs statement with arguments.
	QueryExecute(statement string, arguments ...interface{}) error

	// QueryIterate runs statement with arguments and returns an Iterator for
	// the results.
	QueryIterate(statement string, arguments ...interface{}) Iterator

	// QueryScan runs statement with arguments.
	QueryScan(statement string, arguments, results []interface{}) error

	// QueryScanMap runs statement with arguments and puts the first result row
	// in results.
	QueryScanMap(statement string, arguments []interface{}, results map[string]interface{}) error

	// QueryScanMapTransaction runs statement with arguments as a lightweight
	// transaction and puts the first result row in results. It returns whether
	// the transaction succeeded. If not, it puts the old values in results.
	QueryScanMapTransaction(statement string, arguments []interface{}, results map[string]interface{}) (bool, error)

	// QuerySliceMap runs statement with arguments and returns all result rows.
	QuerySliceMap(statement string, arguments ...interface{}) ([]map[string]interface{}, error)

	// Tables returns the table names for keyspace.
	Tables(keyspace string) ([]string, error)
}

var _ Session = session{}

// NewSession returns a new Session for s.
func NewSession(s *gocql.Session) Session {
	return session{s: s}
}

// SessionMock is a mock Session.
type SessionMock struct {
	mock.Mock
}

// Close implements Session.
func (m SessionMock) Close() {
	m.Called()
}

// Columns implements Session.
func (m SessionMock) Columns(keyspace, table string) (map[string]gocql.TypeInfo, error) {
	var r = m.Called(keyspace, table)

	return r.Get(0).(map[string]gocql.TypeInfo), r.Error(1)
}

// QueryBatch implements Session.
func (m SessionMock) QueryBatch(kind BatchKind) Batch {
	return m.Called(kind).Get(0).(Batch)
}

// QueryExecute implements Session.
func (m SessionMock) QueryExecute(statement string, arguments ...interface{}) error {
	return m.Called(statement, arguments).Error(0)
}

// QueryIterate implements Session.
func (m SessionMock) QueryIterate(statement string, arguments ...interface{}) Iterator {
	return m.Called(statement, arguments).Get(0).(Iterator)
}

// QueryScan implements Session.
func (m SessionMock) QueryScan(statement string, arguments, results []interface{}) error {
	return m.Called(statement, arguments, results).Error(0)
}

// QueryScanMap implements Session.
func (m SessionMock) QueryScanMap(statement string, arguments []interface{}, results map[string]interface{}) error {
	return m.Called(statement, arguments, results).Error(0)
}

// QueryScanMapTransaction implements Session.
func (m SessionMock) QueryScanMapTransaction(statement string, arguments []interface{}, results map[string]interface{}) (bool, error) {
	var r = m.Called(statement, arguments, results)

	return r.Bool(0), r.Error(1)
}

// QuerySliceMap implements Session.
func (m SessionMock) QuerySliceMap(statement string, arguments ...interface{}) ([]map[string]interface{}, error) {
	var r = m.Called(statement, arguments)

	return r.Get(0).([]map[string]interface{}), r.Error(1)
}

// Tables implements Session.
func (m SessionMock) Tables(keyspace string) ([]string, error) {
	var r = m.Called(keyspace)

	return r.Get(0).([]string), r.Error(1)
}

type session struct {
	s *gocql.Session
}

func (s session) Close() {
	s.Close()
}

func (s session) Columns(keyspace, table string) (map[string]gocql.TypeInfo, error) {
	var m, err = s.s.KeyspaceMetadata(keyspace)

	if err != nil {
		return nil, err
	}

	var t, ok = m.Tables[table]

	if !ok {
		return nil, fmt.Errorf("table %v.%v invalid", keyspace, table)
	}

	var types = map[string]gocql.TypeInfo{}

	for n, c := range t.Columns {
		types[n] = c.Type
	}

	return types, nil
}

func (s session) QueryBatch(kind BatchKind) Batch {
	return batch{b: s.s.NewBatch(gocql.BatchType(kind)), s: s.s}
}

func (s session) QueryExecute(statement string, arguments ...interface{}) error {
	return s.s.Query(statement, arguments...).Exec()
}

func (s session) QueryIterate(statement string, arguments ...interface{}) Iterator {
	return iterator{i: s.s.Query(statement, arguments...).Iter()}
}

func (s session) QueryScan(statement string, arguments, results []interface{}) error {
	return s.s.Query(statement, arguments...).Scan(results...)
}

func (s session) QueryScanMap(statement string, arguments []interface{}, results map[string]interface{}) error {
	return s.s.Query(statement, arguments...).MapScan(results)
}

func (s session) QueryScanMapTransaction(statement string, arguments []interface{}, results map[string]interface{}) (bool, error) {
	return s.s.Query(statement, arguments...).MapScanCAS(results)
}

func (s session) QuerySliceMap(statement string, arguments ...interface{}) ([]map[string]interface{}, error) {
	var i = s.s.Query(statement, arguments...).Iter()
	var m, err = i.SliceMap()

	if err != nil {
		return nil, err
	}

	if err := i.Close(); err != nil {
		return nil, err
	}

	return m, nil
}

func (s session) Tables(keyspace string) ([]string, error) {
	var m, err = s.s.KeyspaceMetadata(keyspace)

	if err != nil {
		return nil, err
	}

	var ts []string

	for t := range m.Tables {
		ts = append(ts, t)
	}

	return ts, nil
}
<commit_msg>Add package prefix to error<commit_after>package gockle

import (
	"fmt"

	"github.com/gocql/gocql"
	"github.com/maraino/go-mock"
)

// Session wraps *gocql.Session.
type Session interface {
	// Close closes the session.
	Close()

	// Columns returns a map from column names to types for keyspace and table.
	Columns(keyspace, table string) (map[string]gocql.TypeInfo, error)

	// QueryBatch returns a Batch with type kind for batched queries.
	QueryBatch(kind BatchKind) Batch

	// QueryExecute runs statement with arguments.
	QueryExecute(statement string, arguments ...interface{}) error

	// QueryIterate runs statement with arguments and returns an Iterator for
	// the results.
	QueryIterate(statement string, arguments ...interface{}) Iterator

	// QueryScan runs statement with arguments.
	QueryScan(statement string, arguments, results []interface{}) error

	// QueryScanMap runs statement with arguments and puts the first result row
	// in results.
	QueryScanMap(statement string, arguments []interface{}, results map[string]interface{}) error

	// QueryScanMapTransaction runs statement with arguments as a lightweight
	// transaction and puts the first result row in results. It returns whether
	// the transaction succeeded. If not, it puts the old values in results.
	QueryScanMapTransaction(statement string, arguments []interface{}, results map[string]interface{}) (bool, error)

	// QuerySliceMap runs statement with arguments and returns all result rows.
	QuerySliceMap(statement string, arguments ...interface{}) ([]map[string]interface{}, error)

	// Tables returns the table names for keyspace.
	Tables(keyspace string) ([]string, error)
}

var _ Session = session{}

// NewSession returns a new Session for s.
func NewSession(s *gocql.Session) Session {
	return session{s: s}
}

// SessionMock is a mock Session.
type SessionMock struct {
	mock.Mock
}

// Close implements Session.
func (m SessionMock) Close() {
	m.Called()
}

// Columns implements Session.
func (m SessionMock) Columns(keyspace, table string) (map[string]gocql.TypeInfo, error) {
	var r = m.Called(keyspace, table)

	return r.Get(0).(map[string]gocql.TypeInfo), r.Error(1)
}

// QueryBatch implements Session.
func (m SessionMock) QueryBatch(kind BatchKind) Batch {
	return m.Called(kind).Get(0).(Batch)
}

// QueryExecute implements Session.
func (m SessionMock) QueryExecute(statement string, arguments ...interface{}) error {
	return m.Called(statement, arguments).Error(0)
}

// QueryIterate implements Session.
func (m SessionMock) QueryIterate(statement string, arguments ...interface{}) Iterator {
	return m.Called(statement, arguments).Get(0).(Iterator)
}

// QueryScan implements Session.
func (m SessionMock) QueryScan(statement string, arguments, results []interface{}) error {
	return m.Called(statement, arguments, results).Error(0)
}

// QueryScanMap implements Session.
func (m SessionMock) QueryScanMap(statement string, arguments []interface{}, results map[string]interface{}) error {
	return m.Called(statement, arguments, results).Error(0)
}

// QueryScanMapTransaction implements Session.
func (m SessionMock) QueryScanMapTransaction(statement string, arguments []interface{}, results map[string]interface{}) (bool, error) {
	var r = m.Called(statement, arguments, results)

	return r.Bool(0), r.Error(1)
}

// QuerySliceMap implements Session.
func (m SessionMock) QuerySliceMap(statement string, arguments ...interface{}) ([]map[string]interface{}, error) {
	var r = m.Called(statement, arguments)

	return r.Get(0).([]map[string]interface{}), r.Error(1)
}

// Tables implements Session.
func (m SessionMock) Tables(keyspace string) ([]string, error) {
	var r = m.Called(keyspace)

	return r.Get(0).([]string), r.Error(1)
}

type session struct {
	s *gocql.Session
}

func (s session) Close() {
	s.Close()
}

func (s session) Columns(keyspace, table string) (map[string]gocql.TypeInfo, error) {
	var m, err = s.s.KeyspaceMetadata(keyspace)

	if err != nil {
		return nil, err
	}

	var t, ok = m.Tables[table]

	if !ok {
		return nil, fmt.Errorf("gockle: table %v.%v invalid", keyspace, table)
	}

	var types = map[string]gocql.TypeInfo{}

	for n, c := range t.Columns {
		types[n] = c.Type
	}

	return types, nil
}

func (s session) QueryBatch(kind BatchKind) Batch {
	return batch{b: s.s.NewBatch(gocql.BatchType(kind)), s: s.s}
}

func (s session) QueryExecute(statement string, arguments ...interface{}) error {
	return s.s.Query(statement, arguments...).Exec()
}

func (s session) QueryIterate(statement string, arguments ...interface{}) Iterator {
	return iterator{i: s.s.Query(statement, arguments...).Iter()}
}

func (s session) QueryScan(statement string, arguments, results []interface{}) error {
	return s.s.Query(statement, arguments...).Scan(results...)
}

func (s session) QueryScanMap(statement string, arguments []interface{}, results map[string]interface{}) error {
	return s.s.Query(statement, arguments...).MapScan(results)
}

func (s session) QueryScanMapTransaction(statement string, arguments []interface{}, results map[string]interface{}) (bool, error) {
	return s.s.Query(statement, arguments...).MapScanCAS(results)
}

func (s session) QuerySliceMap(statement string, arguments ...interface{}) ([]map[string]interface{}, error) {
	var i = s.s.Query(statement, arguments...).Iter()
	var m, err = i.SliceMap()

	if err != nil {
		return nil, err
	}

	if err := i.Close(); err != nil {
		return nil, err
	}

	return m, nil
}

func (s session) Tables(keyspace string) ([]string, error) {
	var m, err = s.s.KeyspaceMetadata(keyspace)

	if err != nil {
		return nil, err
	}

	var ts []string

	for t := range m.Tables {
		ts = append(ts, t)
	}

	return ts, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package common

import (
	"fmt"
	"reflect"

	"github.com/golang/glog"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/util/diff"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"

	"metacontroller.app/apis/metacontroller/v1alpha1"
	dynamicapply "metacontroller.app/dynamic/apply"
	dynamicclientset "metacontroller.app/dynamic/clientset"
	k8s "metacontroller.app/third_party/kubernetes"
)

func ApplyUpdate(orig, update *unstructured.Unstructured) (*unstructured.Unstructured, error) {
	// The controller only returns a partial object.
	// We compute the full updated object in the style of "kubectl apply".
	lastApplied, err := dynamicapply.GetLastApplied(orig)
	if err != nil {
		return nil, err
	}
	newObj := &unstructured.Unstructured{}
	newObj.Object, err = dynamicapply.Merge(orig.UnstructuredContent(), lastApplied, update.UnstructuredContent())
	if err != nil {
		return nil, err
	}
	// Revert metadata fields that are known to be read-only, system fields,
	// so that attempts to change those fields will never cause a diff to be found
	// by DeepEqual, which would cause needless, no-op updates or recreates.
	// See: https://github.com/GoogleCloudPlatform/metacontroller/issues/76
	if err := revertObjectMetaSystemFields(newObj, orig); err != nil {
		return nil, fmt.Errorf("failed to revert ObjectMeta system fields: %v", err)
	}
	// Revert status because we don't currently support a parent changing status of
	// its children, so we need to ensure no diffs on the children involve status.
	if err := revertField(newObj, orig, "status"); err != nil {
		return nil, fmt.Errorf("failed to revert .status: %v", err)
	}
	dynamicapply.SetLastApplied(newObj, update.UnstructuredContent())
	return newObj, nil
}

// objectMetaSystemFields is a list of JSON field names within ObjectMeta that
// are both read-only and system-populated according to the comments in
// k8s.io/apimachinery/pkg/apis/meta/v1/types.go.
var objectMetaSystemFields = []string{
	"selfLink",
	"uid",
	"resourceVersion",
	"generation",
	"creationTimestamp",
	"deletionTimestamp",
}

// revertObjectMetaSystemFields overwrites the read-only, system-populated
// fields of ObjectMeta in newObj to match what they were in orig.
// If the field existed before, we create it if necessary and set the value.
// If the field was unset before, we delete it if necessary.
func revertObjectMetaSystemFields(newObj, orig *unstructured.Unstructured) error {
	for _, fieldName := range objectMetaSystemFields {
		if err := revertField(newObj, orig, "metadata", fieldName); err != nil {
			return err
		}
	}
	return nil
}

// revertField reverts field in newObj to match what it was in orig.
func revertField(newObj, orig *unstructured.Unstructured, fieldPath ...string) error {
	field, found, err := unstructured.NestedFieldNoCopy(orig.UnstructuredContent(), fieldPath...)
	if err != nil {
		return fmt.Errorf("can't traverse UnstructuredContent to look for field %v: %v", fieldPath, err)
	}
	if found {
		// The original had this field set, so make sure it remains the same.
		// SetNestedField will recursively ensure the field and all its parent
		// fields exist, and then set the value.
		if err := unstructured.SetNestedField(newObj.UnstructuredContent(), field, fieldPath...); err != nil {
			return fmt.Errorf("can't revert field %v: %v", fieldPath, err)
		}
	} else {
		// The original had this field unset, so make sure it remains unset.
		// RemoveNestedField is a no-op if the field or any of its parents
		// don't exist.
		unstructured.RemoveNestedField(newObj.UnstructuredContent(), fieldPath...)
	}
	return nil
}

func MakeControllerRef(parent *unstructured.Unstructured) *metav1.OwnerReference {
	return &metav1.OwnerReference{
		APIVersion:         parent.GetAPIVersion(),
		Kind:               parent.GetKind(),
		Name:               parent.GetName(),
		UID:                parent.GetUID(),
		Controller:         k8s.BoolPtr(true),
		BlockOwnerDeletion: k8s.BoolPtr(true),
	}
}

type ChildUpdateStrategy interface {
	GetMethod(apiGroup, kind string) v1alpha1.ChildUpdateMethod
}

func ManageChildren(dynClient *dynamicclientset.Clientset, updateStrategy ChildUpdateStrategy, parent *unstructured.Unstructured, observedChildren, desiredChildren ChildMap) error {
	// If some operations fail, keep trying others so, for example,
	// we don't block recovery (create new Pod) on a failed delete.
	var errs []error

	// Delete observed, owned objects that are not desired.
	for key, objects := range observedChildren {
		apiVersion, kind := ParseChildMapKey(key)
		client, err := dynClient.Kind(apiVersion, kind)
		if err != nil {
			errs = append(errs, err)
			continue
		}
		if err := deleteChildren(client, parent, objects, desiredChildren[key]); err != nil {
			errs = append(errs, err)
			continue
		}
	}

	// Create or update desired objects.
	for key, objects := range desiredChildren {
		apiVersion, kind := ParseChildMapKey(key)
		client, err := dynClient.Kind(apiVersion, kind)
		if err != nil {
			errs = append(errs, err)
			continue
		}
		if err := updateChildren(client, updateStrategy, parent, observedChildren[key], objects); err != nil {
			errs = append(errs, err)
			continue
		}
	}

	return utilerrors.NewAggregate(errs)
}

func deleteChildren(client *dynamicclientset.ResourceClient, parent *unstructured.Unstructured, observed, desired map[string]*unstructured.Unstructured) error {
	var errs []error
	for name, obj := range observed {
		if obj.GetDeletionTimestamp() != nil {
			// Skip objects that are already pending deletion.
			continue
		}
		if desired == nil || desired[name] == nil {
			// This observed object wasn't listed as desired.
			glog.Infof("%v: deleting %v", describeObject(parent), describeObject(obj))
			uid := obj.GetUID()
			err := client.Namespace(obj.GetNamespace()).Delete(name, &metav1.DeleteOptions{
				Preconditions: &metav1.Preconditions{UID: &uid},
			})
			if err != nil {
				errs = append(errs, fmt.Errorf("can't delete %v: %v", describeObject(obj), err))
				continue
			}
		}
	}
	return utilerrors.NewAggregate(errs)
}

func updateChildren(client *dynamicclientset.ResourceClient, updateStrategy ChildUpdateStrategy, parent *unstructured.Unstructured, observed, desired map[string]*unstructured.Unstructured) error {
	var errs []error
	for name, obj := range desired {
		ns := obj.GetNamespace()
		if ns == "" {
			ns = parent.GetNamespace()
		}
		if oldObj := observed[name]; oldObj != nil {
			// Update
			newObj, err := ApplyUpdate(oldObj, obj)
			if err != nil {
				errs = append(errs, err)
				continue
			}

			// Attempt an update, if the 3-way merge resulted in any changes.
			if reflect.DeepEqual(newObj.UnstructuredContent(), oldObj.UnstructuredContent()) {
				// Nothing changed.
				continue
			}
			if glog.V(5) {
				glog.Infof("reflect diff: a=observed, b=desired:\n%s", diff.ObjectReflectDiff(oldObj.UnstructuredContent(), newObj.UnstructuredContent()))
			}

			// Leave it alone if it's pending deletion.
			if oldObj.GetDeletionTimestamp() != nil {
				glog.Infof("%v: not updating %v (pending deletion)", describeObject(parent), describeObject(obj))
				continue
			}

			// Check the update strategy for this child kind.
			switch method := updateStrategy.GetMethod(client.Group, client.Kind); method {
			case v1alpha1.ChildUpdateOnDelete, "":
				// This means we don't try to update anything unless it gets deleted
				// by someone else (we won't delete it ourselves).
				glog.V(5).Infof("%v: not updating %v (OnDelete update strategy)", describeObject(parent), describeObject(obj))
				continue
			case v1alpha1.ChildUpdateRecreate, v1alpha1.ChildUpdateRollingRecreate:
				// Delete the object (now) and recreate it (on the next sync).
				glog.Infof("%v: deleting %v for update", describeObject(parent), describeObject(obj))
				uid := oldObj.GetUID()
				err := client.Namespace(ns).Delete(name, &metav1.DeleteOptions{
					Preconditions: &metav1.Preconditions{UID: &uid},
				})
				if err != nil {
					errs = append(errs, err)
					continue
				}
			case v1alpha1.ChildUpdateInPlace, v1alpha1.ChildUpdateRollingInPlace:
				// Update the object in-place.
				glog.Infof("%v: updating %v", describeObject(parent), describeObject(obj))
				if _, err := client.Namespace(ns).Update(newObj); err != nil {
					errs = append(errs, err)
					continue
				}
			default:
				errs = append(errs, fmt.Errorf("invalid update strategy for %v: unknown method %q", client.Kind, method))
				continue
			}
		} else {
			// Create
			glog.Infof("%v: creating %v", describeObject(parent), describeObject(obj))

			// The controller should return a partial object containing only the
			// fields it cares about. We save this partial object so we can do
			// a 3-way merge upon update, in the style of "kubectl apply".
			//
			// Make sure this happens before we add anything else to the object.
			if err := dynamicapply.SetLastApplied(obj, obj.UnstructuredContent()); err != nil {
				errs = append(errs, err)
				continue
			}

			// We always claim everything we create.
			controllerRef := MakeControllerRef(parent)
			ownerRefs := obj.GetOwnerReferences()
			ownerRefs = append(ownerRefs, *controllerRef)
			obj.SetOwnerReferences(ownerRefs)

			if _, err := client.Namespace(ns).Create(obj); err != nil {
				errs = append(errs, err)
				continue
			}
		}
	}
	return utilerrors.NewAggregate(errs)
}
<commit_msg>Fix updating a ns-scoped child of a cluster-scoped controller<commit_after>/*
Copyright 2018 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package common

import (
	"fmt"
	"reflect"

	"github.com/golang/glog"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/util/diff"
	utilerrors "k8s.io/apimachinery/pkg/util/errors"

	"metacontroller.app/apis/metacontroller/v1alpha1"
	dynamicapply "metacontroller.app/dynamic/apply"
	dynamicclientset "metacontroller.app/dynamic/clientset"
	k8s "metacontroller.app/third_party/kubernetes"
)

func ApplyUpdate(orig, update *unstructured.Unstructured) (*unstructured.Unstructured, error) {
	// The controller only returns a partial object.
	// We compute the full updated object in the style of "kubectl apply".
	lastApplied, err := dynamicapply.GetLastApplied(orig)
	if err != nil {
		return nil, err
	}
	newObj := &unstructured.Unstructured{}
	newObj.Object, err = dynamicapply.Merge(orig.UnstructuredContent(), lastApplied, update.UnstructuredContent())
	if err != nil {
		return nil, err
	}
	// Revert metadata fields that are known to be read-only, system fields,
	// so that attempts to change those fields will never cause a diff to be found
	// by DeepEqual, which would cause needless, no-op updates or recreates.
	// See: https://github.com/GoogleCloudPlatform/metacontroller/issues/76
	if err := revertObjectMetaSystemFields(newObj, orig); err != nil {
		return nil, fmt.Errorf("failed to revert ObjectMeta system fields: %v", err)
	}
	// Revert status because we don't currently support a parent changing status of
	// its children, so we need to ensure no diffs on the children involve status.
	if err := revertField(newObj, orig, "status"); err != nil {
		return nil, fmt.Errorf("failed to revert .status: %v", err)
	}
	dynamicapply.SetLastApplied(newObj, update.UnstructuredContent())
	return newObj, nil
}

// objectMetaSystemFields is a list of JSON field names within ObjectMeta that
// are both read-only and system-populated according to the comments in
// k8s.io/apimachinery/pkg/apis/meta/v1/types.go.
var objectMetaSystemFields = []string{
	"selfLink",
	"uid",
	"resourceVersion",
	"generation",
	"creationTimestamp",
	"deletionTimestamp",
}

// revertObjectMetaSystemFields overwrites the read-only, system-populated
// fields of ObjectMeta in newObj to match what they were in orig.
// If the field existed before, we create it if necessary and set the value.
// If the field was unset before, we delete it if necessary.
func revertObjectMetaSystemFields(newObj, orig *unstructured.Unstructured) error {
	for _, fieldName := range objectMetaSystemFields {
		if err := revertField(newObj, orig, "metadata", fieldName); err != nil {
			return err
		}
	}
	return nil
}

// revertField reverts field in newObj to match what it was in orig.
func revertField(newObj, orig *unstructured.Unstructured, fieldPath ...string) error {
	field, found, err := unstructured.NestedFieldNoCopy(orig.UnstructuredContent(), fieldPath...)
	if err != nil {
		return fmt.Errorf("can't traverse UnstructuredContent to look for field %v: %v", fieldPath, err)
	}
	if found {
		// The original had this field set, so make sure it remains the same.
		// SetNestedField will recursively ensure the field and all its parent
		// fields exist, and then set the value.
		if err := unstructured.SetNestedField(newObj.UnstructuredContent(), field, fieldPath...); err != nil {
			return fmt.Errorf("can't revert field %v: %v", fieldPath, err)
		}
	} else {
		// The original had this field unset, so make sure it remains unset.
		// RemoveNestedField is a no-op if the field or any of its parents
		// don't exist.
		unstructured.RemoveNestedField(newObj.UnstructuredContent(), fieldPath...)
	}
	return nil
}

func MakeControllerRef(parent *unstructured.Unstructured) *metav1.OwnerReference {
	return &metav1.OwnerReference{
		APIVersion:         parent.GetAPIVersion(),
		Kind:               parent.GetKind(),
		Name:               parent.GetName(),
		UID:                parent.GetUID(),
		Controller:         k8s.BoolPtr(true),
		BlockOwnerDeletion: k8s.BoolPtr(true),
	}
}

type ChildUpdateStrategy interface {
	GetMethod(apiGroup, kind string) v1alpha1.ChildUpdateMethod
}

func ManageChildren(dynClient *dynamicclientset.Clientset, updateStrategy ChildUpdateStrategy, parent *unstructured.Unstructured, observedChildren, desiredChildren ChildMap) error {
	// If some operations fail, keep trying others so, for example,
	// we don't block recovery (create new Pod) on a failed delete.
	var errs []error

	// Delete observed, owned objects that are not desired.
	for key, objects := range observedChildren {
		apiVersion, kind := ParseChildMapKey(key)
		client, err := dynClient.Kind(apiVersion, kind)
		if err != nil {
			errs = append(errs, err)
			continue
		}
		if err := deleteChildren(client, parent, objects, desiredChildren[key]); err != nil {
			errs = append(errs, err)
			continue
		}
	}

	// Create or update desired objects.
	for key, objects := range desiredChildren {
		apiVersion, kind := ParseChildMapKey(key)
		client, err := dynClient.Kind(apiVersion, kind)
		if err != nil {
			errs = append(errs, err)
			continue
		}
		if err := updateChildren(client, updateStrategy, parent, observedChildren[key], objects); err != nil {
			errs = append(errs, err)
			continue
		}
	}

	return utilerrors.NewAggregate(errs)
}

func deleteChildren(client *dynamicclientset.ResourceClient, parent *unstructured.Unstructured, observed, desired map[string]*unstructured.Unstructured) error {
	var errs []error
	for name, obj := range observed {
		if obj.GetDeletionTimestamp() != nil {
			// Skip objects that are already pending deletion.
			continue
		}
		if desired == nil || desired[name] == nil {
			// This observed object wasn't listed as desired.
			glog.Infof("%v: deleting %v", describeObject(parent), describeObject(obj))
			uid := obj.GetUID()
			err := client.Namespace(obj.GetNamespace()).Delete(obj.GetName(), &metav1.DeleteOptions{
				Preconditions: &metav1.Preconditions{UID: &uid},
			})
			if err != nil {
				errs = append(errs, fmt.Errorf("can't delete %v: %v", describeObject(obj), err))
				continue
			}
		}
	}
	return utilerrors.NewAggregate(errs)
}

func updateChildren(client *dynamicclientset.ResourceClient, updateStrategy ChildUpdateStrategy, parent *unstructured.Unstructured, observed, desired map[string]*unstructured.Unstructured) error {
	var errs []error
	for name, obj := range desired {
		ns := obj.GetNamespace()
		if ns == "" {
			ns = parent.GetNamespace()
		}
		if oldObj := observed[name]; oldObj != nil {
			// Update
			newObj, err := ApplyUpdate(oldObj, obj)
			if err != nil {
				errs = append(errs, err)
				continue
			}

			// Attempt an update, if the 3-way merge resulted in any changes.
			if reflect.DeepEqual(newObj.UnstructuredContent(), oldObj.UnstructuredContent()) {
				// Nothing changed.
				continue
			}
			if glog.V(5) {
				glog.Infof("reflect diff: a=observed, b=desired:\n%s", diff.ObjectReflectDiff(oldObj.UnstructuredContent(), newObj.UnstructuredContent()))
			}

			// Leave it alone if it's pending deletion.
			if oldObj.GetDeletionTimestamp() != nil {
				glog.Infof("%v: not updating %v (pending deletion)", describeObject(parent), describeObject(obj))
				continue
			}

			// Check the update strategy for this child kind.
			switch method := updateStrategy.GetMethod(client.Group, client.Kind); method {
			case v1alpha1.ChildUpdateOnDelete, "":
				// This means we don't try to update anything unless it gets deleted
				// by someone else (we won't delete it ourselves).
				glog.V(5).Infof("%v: not updating %v (OnDelete update strategy)", describeObject(parent), describeObject(obj))
				continue
			case v1alpha1.ChildUpdateRecreate, v1alpha1.ChildUpdateRollingRecreate:
				// Delete the object (now) and recreate it (on the next sync).
				glog.Infof("%v: deleting %v for update", describeObject(parent), describeObject(obj))
				uid := oldObj.GetUID()
				err := client.Namespace(ns).Delete(obj.GetName(), &metav1.DeleteOptions{
					Preconditions: &metav1.Preconditions{UID: &uid},
				})
				if err != nil {
					errs = append(errs, err)
					continue
				}
			case v1alpha1.ChildUpdateInPlace, v1alpha1.ChildUpdateRollingInPlace:
				// Update the object in-place.
				glog.Infof("%v: updating %v", describeObject(parent), describeObject(obj))
				if _, err := client.Namespace(ns).Update(newObj); err != nil {
					errs = append(errs, err)
					continue
				}
			default:
				errs = append(errs, fmt.Errorf("invalid update strategy for %v: unknown method %q", client.Kind, method))
				continue
			}
		} else {
			// Create
			glog.Infof("%v: creating %v", describeObject(parent), describeObject(obj))

			// The controller should return a partial object containing only the
			// fields it cares about. We save this partial object so we can do
			// a 3-way merge upon update, in the style of "kubectl apply".
			//
			// Make sure this happens before we add anything else to the object.
			if err := dynamicapply.SetLastApplied(obj, obj.UnstructuredContent()); err != nil {
				errs = append(errs, err)
				continue
			}

			// We always claim everything we create.
			controllerRef := MakeControllerRef(parent)
			ownerRefs := obj.GetOwnerReferences()
			ownerRefs = append(ownerRefs, *controllerRef)
			obj.SetOwnerReferences(ownerRefs)

			if _, err := client.Namespace(ns).Create(obj); err != nil {
				errs = append(errs, err)
				continue
			}
		}
	}
	return utilerrors.NewAggregate(errs)
}
<|endoftext|>


<commit_before>/*-
 * Copyright 2015 Square Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"io"
	"os"
	"os/signal"
	"sync/atomic"
	"syscall"
	"time"
)

// signalHandler. Listens for incoming SIGTERM or SIGUSR1 signals. If we get
// SIGTERM, stop listening for new connections and gracefully terminate the
// process. If we get SIGUSR1, reload certificates.
func signalHandler(proxy *proxy, closeables []io.Closer, context *Context) {
	signals := make(chan os.Signal)
	signal.Notify(signals, syscall.SIGUSR1, syscall.SIGTERM)
	defer signal.Stop(signals)

	for {
		// Wait for a signal
		select {
		case sig := <-signals:
			switch sig {
			case syscall.SIGTERM:
				logger.Printf("received SIGTERM, shutting down")
				time.AfterFunc(*shutdownTimeout, func() {
					logger.Printf("graceful shutdown timeout: exiting")
					exitFunc(1)
				})
				atomic.StoreInt32(&proxy.quit, 1)
				for _, closeable := range closeables {
					closeable.Close()
				}
				logger.Printf("done with signal handler")
				return

			case syscall.SIGUSR1:
				logger.Printf("received SIGUSR1, reloading certificates")
				context.status.Reloading()
				err := context.cert.reload()
				if err != nil {
					logger.Printf("error reloading: %s", err)
				}
				logger.Printf("reloading complete")
				context.status.Listening()
			}
		case _ = <-context.watcher:
			context.status.Reloading()
			err := context.cert.reload()
			if err != nil {
				logger.Printf("error reloading: %s", err)
			}
			logger.Printf("reloading complete")
			context.status.Listening()
		}
	}
}
<commit_msg>Graceful shutdown and cleanup on SIGINT<commit_after>/*-
 * Copyright 2015 Square Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"io"
	"os"
	"os/signal"
	"sync/atomic"
	"syscall"
	"time"
)

// signalHandler. Listens for incoming SIGTERM or SIGUSR1 signals. If we get
// SIGTERM, stop listening for new connections and gracefully terminate the
// process. If we get SIGUSR1, reload certificates.
func signalHandler(proxy *proxy, closeables []io.Closer, context *Context) {
	signals := make(chan os.Signal)
	signal.Notify(signals, syscall.SIGUSR1, syscall.SIGTERM, syscall.SIGINT)
	defer signal.Stop(signals)
	defer cleanup()

	for {
		// Wait for a signal
		select {
		case sig := <-signals:
			switch sig {
			case syscall.SIGINT, syscall.SIGTERM:
				logger.Printf("received SIGTERM, shutting down")
				time.AfterFunc(*shutdownTimeout, func() {
					logger.Printf("graceful shutdown timeout: exiting")
					exitFunc(1)
				})
				atomic.StoreInt32(&proxy.quit, 1)
				for _, closeable := range closeables {
					closeable.Close()
				}
				logger.Printf("done with signal handler")
				return

			case syscall.SIGUSR1:
				logger.Printf("received SIGUSR1, reloading certificates")
				context.status.Reloading()
				err := context.cert.reload()
				if err != nil {
					logger.Printf("error reloading: %s", err)
				}
				logger.Printf("reloading complete")
				context.status.Listening()
			}
		case _ = <-context.watcher:
			context.status.Reloading()
			err := context.cert.reload()
			if err != nil {
				logger.Printf("error reloading: %s", err)
			}
			logger.Printf("reloading complete")
			context.status.Listening()
		}
	}
}
<|endoftext|>


<commit_before>package main

import "fmt"
import "io"
import "net/http"
import "os"
import "path/filepath"
import "regexp"
import "runtime"
import "strings"
import "github.com/codegangsta/cli"
import "github.com/kardianos/osext"

var shell string
var dvmDir string
var debug bool
var silent bool

func main() {
  app := cli.NewApp()
  app.Name = "Docker Version Manager"
  app.Usage = "Manage multiple versions of the Docker client"
  app.EnableBashCompletion = true
  app.Flags = []cli.Flag{
    cli.StringFlag { Name: "dvm-dir", EnvVar: "DVM_DIR", Usage: "Specify an alternate DVM home directory, defaults to the current directory." },
    cli.StringFlag { Name: "shell", EnvVar: "SHELL", Usage: "Specify the shell format in which environment variables should be output, e.g. powershell, cmd or sh/bash. Defaults to sh/bash."},
    cli.BoolFlag { Name: "debug", Usage: "Print additional debug information." },
    cli.BoolFlag{ Name: "silent", EnvVar: "DVM_SILENT", Usage: "Suppress output. Errors will still be displayed."},
  }
  app.Commands = []cli.Command{
      {
          Name:    "install",
          Aliases: []string{"i"},
          Usage:   "Download and install a <version>. Uses $DOCKER_VERSION if available",
          Action: func(c *cli.Context) {
            setGlobalVars(c)
            install(c.Args().First())
          },
      },
      {
          Name:    "use",
          Usage:   "Modify PATH to use <version>. Uses $DOCKER_VERSION if available",
          Action: func(c *cli.Context) {
            setGlobalVars(c)
            use(c.Args().First())
          },
      },
  }

  app.Run(os.Args)
}

func setGlobalVars(c *cli.Context) {
  debug = c.GlobalBool("debug")
  shell = c.GlobalString("shell")
  silent = c.GlobalBool("silent")
  dvmDir = c.GlobalString("dvm-dir")
  if dvmDir == "" {
    var err error
    dvmDir, err = osext.ExecutableFolder()
    if err != nil {
      fmt.Fprintln(os.Stderr, "Unable to determine DVM home directory")
      os.Exit(1)
    }
  }
}

func install(version string) {
  if version == "" {
    version = os.Getenv("DOCKER_VERSION")
  }

  if version == "" {
    fmt.Fprintln(os.Stderr, "The install command requires that a version is specified or the DOCKER_VERSION environment variable is set")
    os.Exit(127)
  }

  if !versionExists(version) {
    fmt.Fprintf(os.Stderr, "Version %s not found - try `dvm ls-remote` to browse available versions.\n", version)
    os.Exit(3)
  }

  versionDir := getVersionDir(version)

  // TODO: Support experimental

  if _, err := os.Stat(versionDir); err == nil {
    if !silent { fmt.Fprintf(os.Stderr, "%s is already installed\n", version) }
    use(version)
    return
  }

  if !silent { fmt.Printf("Installing %s\n", version) }

  url := fmt.Sprintf("https://get.docker.com/builds/%s/%s/%s", getDockerOS(), getDockerArch(), getDockerBinaryName(version))
  tmpPath := filepath.Join(getDvmDir(), ".tmp/docker", version, getBinaryName())
  downloadFile(url, tmpPath)
  binaryPath := filepath.Join(getDvmDir(), "bin/docker", version, getBinaryName())
  ensureParentDirectoryExists(binaryPath)
  err := os.Rename(tmpPath, binaryPath)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to copy %s to %s.\n%s\n", tmpPath, binaryPath, err)
  }

  if debug {
    fmt.Printf("Installed Docker %s to %s", version, binaryPath)
  }
}

func use(version string) {
  if version == "" {
    version = os.Getenv("DOCKER_VERSION")
  }

  if version == "" {
    fmt.Fprintln(os.Stderr, "The use command requires that a version is specified or the DOCKER_VERSION environment variable is set")
    os.Exit(127)
  }

  if !versionExists(version) {
    fmt.Fprintf(os.Stderr, "Version %s not found - try `dvm ls-remote` to browse available versions.\n", version)
    os.Exit(3)
  }

  // TODO: Handle system version

  ensureVersionIsInstalled(version)
  removePreviousDvmVersionFromPath()
  prependDvmVersionToPath(version)

  if !silent {
    fmt.Printf("Now using Docker %s\n", version)
  }
}

func getDvmDir() string {
  return dvmDir
}

func getDockerBinaryName(version string) string {
    if runtime.GOOS == "windows" {
      return fmt.Sprintf("docker-%s.exe", version)
    }
    return fmt.Sprintf("docker-%s", version)
}

func getBinaryName() string {
    if runtime.GOOS == "windows" {
      return "docker.exe"
    }
    return "docker"
}

func prependDvmVersionToPath(version string) {
    if runtime.GOOS == "windows" && shell == "" {
      fmt.Fprintf(os.Stderr, "The --shell flag or SHELL environment variable must be set when running on Windows. Available values are sh, powershell and cmd.")
      os.Exit(127)
    }
    versionDir := getVersionDir(version)
    path := os.Getenv("PATH")

    var shellScript string
    var shellScriptExt string
    if shell == "powershell" {
      shellScript = fmt.Sprintf(`$env:PATH="%s;%s"`, versionDir, path)
      shellScriptExt = "ps1"
    } else if shell == "cmd" {
      shellScript = fmt.Sprintf("PATH=%s;%s", versionDir, path)
      shellScriptExt = "cmd"
    } else { // default to bash
      shellScript = fmt.Sprintf("export PATH=%s:%s", versionDir, path)
      shellScriptExt = "sh"
    }

    writeShellScript(shellScript, shellScriptExt)
}

func writeShellScript(contents string, fileExtension string) {
    // Write to a shell script for the calling wrapper to execute
    scriptPath := filepath.Join(dvmDir, ".tmp", ("dvm-output." + fileExtension))

    if debug {
      fmt.Printf("Writing wrapper shell script to %s\n%s\n", scriptPath, contents)
    }

    ensureParentDirectoryExists(scriptPath)
    scriptFile, err := os.Create(scriptPath)
    if err != nil {
      fmt.Fprintf(os.Stderr, "Unable to create %s\n%s\n", scriptPath, err)
      os.Exit(1)
    }

    _, err = io.WriteString(scriptFile, contents)
    if err != nil {
      fmt.Fprintf(os.Stderr, "Unable to write to %s\n%s\n", scriptPath, err)
      os.Exit(1)
    }

    scriptFile.Close()
}

func removePreviousDvmVersionFromPath() {
  versionDir := getVersionDir("")

  var pathRegex string
  if runtime.GOOS == "windows" {
    escapedVersionDir := strings.Replace(versionDir, `\`, `\\`, -1)
    pathRegex = escapedVersionDir + `\\\d+\.\d+\.\d+;`
  } else {
    pathRegex = versionDir + `\d+\.\d+\.\d+:`
  }

  regex, _ := regexp.Compile(pathRegex)
  path := regex.ReplaceAllString(os.Getenv("PATH"), "")
  os.Setenv("PATH", path)
}

func ensureVersionIsInstalled(version string) {
    versionDir := getVersionDir(version)
    if _, err := os.Stat(versionDir); err == nil {
      return
    }

    if !silent {
      fmt.Printf("%s is not installed. Installing now...\n", version)
      install(version)
    }
}

func downloadFile(url string, destPath string) {
  ensureParentDirectoryExists(destPath)

  destFile, err := os.Create(destPath)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to create to %s.\n%s\n", destPath, err)
    os.Exit(1)
  }
  defer destFile.Close()
  os.Chmod(destPath, 0755)

  if debug { fmt.Printf("Downloading %s\n", url) }

  response, err := http.Get(url)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to download %s.\n%s\n", url, err)
    os.Exit(1)
  }
  if response.StatusCode != 200 {
    fmt.Fprintf(os.Stderr, "Unable to download %s.\nStatus Code: %d\n", url, response.StatusCode)
    os.Exit(1)
  }
  defer response.Body.Close()

  _, err = io.Copy(destFile, response.Body)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to write to %s.\n%s\n", destPath, err)
    os.Exit(1)
  }
}

func versionExists(version string) bool {
  availableVersions := getAvailableVersions(version)

  for _,availableVersion := range availableVersions {
      if version == availableVersion {
          return true
      }
  }
  return false
}

func ensureParentDirectoryExists(filePath string) {
  dir := filepath.Dir(filePath)

  err := os.MkdirAll(dir, 0777)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to create directory %s\n", dir)
    os.Exit(1)
  }
}

func getAvailableVersions(userPattern string) []string {
  return []string {"1.8.2", "1.8.3"}
}

func getVersionDir(version string) string {
  return filepath.Join(dvmDir, "bin", "docker", version)
}

func getDockerOS() string {
  switch runtime.GOOS {
    case "windows":
      return "Windows"
    case "darwin":
      return "Darwin"
    case "linux":
      return "Linux"
    }

  fmt.Fprintf(os.Stderr, "Unsupported OS: %s\n", runtime.GOOS)
  os.Exit(1)
  return ""
}

func getDockerArch() string {
  switch runtime.GOARCH {
    case "amd64":
      return "x86_64"
    case "386":
      return "i386"
    }

  fmt.Fprintf(os.Stderr, "Unsupported ARCH: %s\n", runtime.GOARCH)
  os.Exit(1)
  return ""
}
/*
func help() {
  echo
  echo "Docker Version Manager"
  echo
  echo "Usage:"
  echo "  dvm help                              Show this message"
  echo "  dvm --version                         Print out the latest released version of dvm"
  echo "  dvm install <version>                 Download and install a <version>. Uses \$DOCKER_VERSION if available"
  echo "  dvm uninstall <version>               Uninstall a version"
  echo "  dvm use <version>                     Modify PATH to use <version>. Uses \$DOCKER_VERSION if available"
  echo "  dvm current                           Display currently activated version"
  echo "  dvm ls                                List installed versions"
  echo "  dvm ls <version>                      List versions matching a given description"
  echo "  dvm ls-remote                         List remote versions available for install"
  echo "  dvm deactivate                        Undo effects of \`dvm\` on current shell"
  echo "  dvm alias [<pattern>]                 Show all aliases beginning with <pattern>"
  echo "  dvm alias <name> <version>            Set an alias named <name> pointing to <version>"
  echo "  dvm unalias <name>                    Deletes the alias named <name>"
  echo "  dvm unload                            Unload \`dvm\` from shell"
  echo "  dvm which [<version>]                 Display path to installed docker version."
  echo
  echo "Example:"
  echo "  dvm install 1.8.1                     Install a specific version number"
  echo "  dvm use 1.6                           Use the latest available 1.6.x release"
  echo "  dvm alias default 1.8.1               Set default Docker version on a shell"
  echo
  echo "Note:"
  echo "  to remove, delete, or uninstall dvm - remove ~/.dvm"
  echo
}*/
<commit_msg>dvm ls-remote<commit_after>package main

import "fmt"
import "io"
import "net/http"
import "os"
import "path/filepath"
import "regexp"
import "runtime"
import "sort"
import "strings"
import "github.com/google/go-github/github"
import "github.com/codegangsta/cli"
import "github.com/kardianos/osext"

var shell string
var dvmDir string
var debug bool
var silent bool

func main() {
  app := cli.NewApp()
  app.Name = "Docker Version Manager"
  app.Usage = "Manage multiple versions of the Docker client"
  app.EnableBashCompletion = true
  app.Flags = []cli.Flag{
    cli.StringFlag { Name: "dvm-dir", EnvVar: "DVM_DIR", Usage: "Specify an alternate DVM home directory, defaults to the current directory." },
    cli.StringFlag { Name: "shell", EnvVar: "SHELL", Usage: "Specify the shell format in which environment variables should be output, e.g. powershell, cmd or sh/bash. Defaults to sh/bash."},
    cli.BoolFlag { Name: "debug", Usage: "Print additional debug information." },
    cli.BoolFlag{ Name: "silent", EnvVar: "DVM_SILENT", Usage: "Suppress output. Errors will still be displayed."},
  }
  app.Commands = []cli.Command{
    {
      Name:    "install",
      Aliases: []string{"i"},
      Usage:   "dvm install <version>. Uses $DOCKER_VERSION if available",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        install(c.Args().First())
      },
    },
    {
      Name:    "use",
      Usage:   "dvm use <version>. Uses $DOCKER_VERSION if available",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        use(c.Args().First())
      },
    },
    {
      Name: "list-remote",
      Aliases: []string{"ls-remote"},
      Usage: "dvm list-remote [<pattern>]",
      Action: func(c *cli.Context) {
        setGlobalVars(c)
        listRemote(c.Args().First())
      },
    },
  }

  app.Run(os.Args)
}

func setGlobalVars(c *cli.Context) {
  debug = c.GlobalBool("debug")
  shell = c.GlobalString("shell")
  silent = c.GlobalBool("silent")
  dvmDir = c.GlobalString("dvm-dir")
  if dvmDir == "" {
    var err error
    dvmDir, err = osext.ExecutableFolder()
    if err != nil {
      fmt.Fprintln(os.Stderr, "Unable to determine DVM home directory")
      os.Exit(1)
    }
  }
}

func install(version string) {
  if version == "" {
    version = os.Getenv("DOCKER_VERSION")
  }

  if version == "" {
    fmt.Fprintln(os.Stderr, "The install command requires that a version is specified or the DOCKER_VERSION environment variable is set")
    os.Exit(127)
  }

  if !versionExists(version) {
    fmt.Fprintf(os.Stderr, "Version %s not found - try `dvm ls-remote` to browse available versions.\n", version)
    os.Exit(3)
  }

  versionDir := getVersionDir(version)

  // TODO: Support experimental

  if _, err := os.Stat(versionDir); err == nil {
    if !silent { fmt.Fprintf(os.Stderr, "%s is already installed\n", version) }
    use(version)
    return
  }

  if !silent { fmt.Printf("Installing %s\n", version) }

  url := fmt.Sprintf("https://get.docker.com/builds/%s/%s/%s", getDockerOS(), getDockerArch(), getDockerBinaryName(version))
  tmpPath := filepath.Join(getDvmDir(), ".tmp/docker", version, getBinaryName())
  downloadFile(url, tmpPath)
  binaryPath := filepath.Join(getDvmDir(), "bin/docker", version, getBinaryName())
  ensureParentDirectoryExists(binaryPath)
  err := os.Rename(tmpPath, binaryPath)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to copy %s to %s.\n%s\n", tmpPath, binaryPath, err)
  }

  if debug {
    fmt.Printf("Installed Docker %s to %s", version, binaryPath)
  }
}

func use(version string) {
  if version == "" {
    version = os.Getenv("DOCKER_VERSION")
  }

  if version == "" {
    fmt.Fprintln(os.Stderr, "The use command requires that a version is specified or the DOCKER_VERSION environment variable is set")
    os.Exit(127)
  }

  if !versionExists(version) {
    fmt.Fprintf(os.Stderr, "Version %s not found - try `dvm ls-remote` to browse available versions.\n", version)
    os.Exit(3)
  }

  // TODO: Handle system version

  ensureVersionIsInstalled(version)
  removePreviousDvmVersionFromPath()
  prependDvmVersionToPath(version)

  if !silent {
    fmt.Printf("Now using Docker %s\n", version)
  }
}

func getDvmDir() string {
  return dvmDir
}

func getDockerBinaryName(version string) string {
    if runtime.GOOS == "windows" {
      return fmt.Sprintf("docker-%s.exe", version)
    }
    return fmt.Sprintf("docker-%s", version)
}

func getBinaryName() string {
    if runtime.GOOS == "windows" {
      return "docker.exe"
    }
    return "docker"
}

func prependDvmVersionToPath(version string) {
    if runtime.GOOS == "windows" && shell == "" {
      fmt.Fprintf(os.Stderr, "The --shell flag or SHELL environment variable must be set when running on Windows. Available values are sh, powershell and cmd.")
      os.Exit(127)
    }
    versionDir := getVersionDir(version)
    path := os.Getenv("PATH")

    var shellScript string
    var shellScriptExt string
    if shell == "powershell" {
      shellScript = fmt.Sprintf(`$env:PATH="%s;%s"`, versionDir, path)
      shellScriptExt = "ps1"
    } else if shell == "cmd" {
      shellScript = fmt.Sprintf("PATH=%s;%s", versionDir, path)
      shellScriptExt = "cmd"
    } else { // default to bash
      shellScript = fmt.Sprintf("export PATH=%s:%s", versionDir, path)
      shellScriptExt = "sh"
    }

    writeShellScript(shellScript, shellScriptExt)
}

func writeShellScript(contents string, fileExtension string) {
    // Write to a shell script for the calling wrapper to execute
    scriptPath := filepath.Join(dvmDir, ".tmp", ("dvm-output." + fileExtension))

    if debug {
      fmt.Printf("Writing wrapper shell script to %s\n%s\n", scriptPath, contents)
    }

    ensureParentDirectoryExists(scriptPath)
    scriptFile, err := os.Create(scriptPath)
    if err != nil {
      fmt.Fprintf(os.Stderr, "Unable to create %s\n%s\n", scriptPath, err)
      os.Exit(1)
    }

    _, err = io.WriteString(scriptFile, contents)
    if err != nil {
      fmt.Fprintf(os.Stderr, "Unable to write to %s\n%s\n", scriptPath, err)
      os.Exit(1)
    }

    scriptFile.Close()
}

func removePreviousDvmVersionFromPath() {
  versionDir := getVersionDir("")

  var pathRegex string
  if runtime.GOOS == "windows" {
    escapedVersionDir := strings.Replace(versionDir, `\`, `\\`, -1)
    pathRegex = escapedVersionDir + `\\\d+\.\d+\.\d+;`
  } else {
    pathRegex = versionDir + `\d+\.\d+\.\d+:`
  }

  regex, _ := regexp.Compile(pathRegex)
  path := regex.ReplaceAllString(os.Getenv("PATH"), "")
  os.Setenv("PATH", path)
}

func ensureVersionIsInstalled(version string) {
    versionDir := getVersionDir(version)
    if _, err := os.Stat(versionDir); err == nil {
      return
    }

    if !silent {
      fmt.Printf("%s is not installed. Installing now...\n", version)
      install(version)
    }
}

func downloadFile(url string, destPath string) {
  ensureParentDirectoryExists(destPath)

  destFile, err := os.Create(destPath)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to create to %s.\n%s\n", destPath, err)
    os.Exit(1)
  }
  defer destFile.Close()
  os.Chmod(destPath, 0755)

  if debug { fmt.Printf("Downloading %s\n", url) }

  response, err := http.Get(url)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to download %s.\n%s\n", url, err)
    os.Exit(1)
  }
  if response.StatusCode != 200 {
    fmt.Fprintf(os.Stderr, "Unable to download %s.\nStatus Code: %d\n", url, response.StatusCode)
    os.Exit(1)
  }
  defer response.Body.Close()

  _, err = io.Copy(destFile, response.Body)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to write to %s.\n%s\n", destPath, err)
    os.Exit(1)
  }
}

func versionExists(version string) bool {
  availableVersions := getAvailableVersions(version)

  for _,availableVersion := range availableVersions {
      if version == availableVersion {
          return true
      }
  }
  return false
}

func ensureParentDirectoryExists(filePath string) {
  dir := filepath.Dir(filePath)

  err := os.MkdirAll(dir, 0777)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to create directory %s\n", dir)
    os.Exit(1)
  }
}

func listRemote(pattern string) {
    versions := getAvailableVersions(pattern)
    for _, version := range versions {
      fmt.Println(version)
    }
}

func getAvailableVersions(pattern string) []string {
  gh := github.NewClient(nil)
	tags, response, err := gh.Repositories.ListTags("docker", "docker", nil)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Unable to retrieve list of Docker tags from GitHub.\n%s\n", err)
    os.Exit(1)
  }
  if response.StatusCode != 200 {
    fmt.Fprintf(os.Stderr, "Unable to retrieve list of Docker tags from GitHub.\nStatus Code:%d\n", response.StatusCode)
    os.Exit(1)
  }

  versionRegex := regexp.MustCompile(`^v([1-9]+\.\d+\.\d+)$`)
  patternRegex, err := regexp.Compile(pattern)
  if err != nil {
    fmt.Fprintf(os.Stderr, "Invalid pattern.\n%s\n", err)
    os.Exit(3)
  }

  var results []string
  for _, tag := range tags {
    version := *tag.Name
    match := versionRegex.FindStringSubmatch(version)
    if len(match) > 1 && patternRegex.MatchString(version) {
      results = append(results, match[1])
    }
  }

  sort.Strings(results)
  return results
}

func getVersionDir(version string) string {
  return filepath.Join(dvmDir, "bin", "docker", version)
}

func getDockerOS() string {
  switch runtime.GOOS {
    case "windows":
      return "Windows"
    case "darwin":
      return "Darwin"
    case "linux":
      return "Linux"
    }

  fmt.Fprintf(os.Stderr, "Unsupported OS: %s\n", runtime.GOOS)
  os.Exit(1)
  return ""
}

func getDockerArch() string {
  switch runtime.GOARCH {
    case "amd64":
      return "x86_64"
    case "386":
      return "i386"
    }

  fmt.Fprintf(os.Stderr, "Unsupported ARCH: %s\n", runtime.GOARCH)
  os.Exit(1)
  return ""
}
/*
func help() {
  echo
  echo "Docker Version Manager"
  echo
  echo "Usage:"
  echo "  dvm help                              Show this message"
  echo "  dvm --version                         Print out the latest released version of dvm"
  echo "  dvm install <version>                 Download and install a <version>. Uses \$DOCKER_VERSION if available"
  echo "  dvm uninstall <version>               Uninstall a version"
  echo "  dvm use <version>                     Modify PATH to use <version>. Uses \$DOCKER_VERSION if available"
  echo "  dvm current                           Display currently activated version"
  echo "  dvm ls                                List installed versions"
  echo "  dvm ls <version>                      List versions matching a given description"
  echo "  dvm ls-remote                         List remote versions available for install"
  echo "  dvm deactivate                        Undo effects of \`dvm\` on current shell"
  echo "  dvm alias [<pattern>]                 Show all aliases beginning with <pattern>"
  echo "  dvm alias <name> <version>            Set an alias named <name> pointing to <version>"
  echo "  dvm unalias <name>                    Deletes the alias named <name>"
  echo "  dvm unload                            Unload \`dvm\` from shell"
  echo "  dvm which [<version>]                 Display path to installed docker version."
  echo
  echo "Example:"
  echo "  dvm install 1.8.1                     Install a specific version number"
  echo "  dvm use 1.6                           Use the latest available 1.6.x release"
  echo "  dvm alias default 1.8.1               Set default Docker version on a shell"
  echo
  echo "Note:"
  echo "  to remove, delete, or uninstall dvm - remove ~/.dvm"
  echo
}*/
<|endoftext|>


<commit_before>package lessgo

import (
	"path"
	"reflect"
	"runtime"
	"sort"
	"strings"

	"github.com/lessgo/lessgo/utils"
)

type (
	DynaRouter struct {
		Id          string
		Url         string
		Type        int
		Prefix      string // 允许动态修改
		Name        string // 允许动态修改
		Description string // 允许动态修改
		Methods     []string
		Param       string
		Handler     string
		Middlewares []string // 允许动态修改
		ParentUrl   string   // 允许动态指定父节点
		Parent      *DynaRouter
		Children    []*DynaRouter
	}
	RouterFunc func(name, description string, handler HandlerFunc, param ...string)
)

const (
	ROOT int = iota - 1
	GROUP
	HANDLER
)

var (
	HandlerFuncMap = map[string]HandlerFunc{}
	MiddlewareMap  = map[string]Middleware{}
	dynaRouterMap  = map[string]*DynaRouter{}
	DefDynaRouter  = &DynaRouter{
		Prefix:      "/",
		Type:        ROOT,
		Middlewares: []string{},
		Children:    []*DynaRouter{},
	}
)

func (d *DynaRouter) Tree() []*DynaRouter {
	rs := []*DynaRouter{d}
	for _, node := range d.Children {
		rs = append(rs, node.Tree()...)
	}
	return rs
}

func (d *DynaRouter) SetMiddlewares(middlewares []string) {
	d.Middlewares = middlewares
}

func (d *DynaRouter) init() {
	d.setUrl()
	d.setId()
	for _, node := range d.Children {
		node.init()
	}
	dynaRouterMap[d.Id] = d
}

func (d *DynaRouter) setUrl() {
	var u = path.Join(d.Prefix, d.Param)
	d.ParentUrl = ""
	if d.Parent != nil {
		d.Parent.setUrl()
		d.ParentUrl = d.Parent.Url
		u = path.Join(d.Parent.Url, u)
	}
	d.Url = u
}

func (d *DynaRouter) setId() {
	d.Id = utils.MakeMd5(d.Url + strings.Join(d.Methods, ""))
}

// 返回路由列表
func DynaRouterTree() []*DynaRouter {
	return DefDynaRouter.Tree()
}

// 设置或添加路由
func SetRouter(parentId string, node *DynaRouter) {
	node.Parent = dynaRouterMap[parentId]
	node.init()
	dynaRouterMap[node.Id] = node
	for i, child := range node.Parent.Children {
		if child.Id == node.Id {
			node.Parent.Children[i] = node
			return
		}
	}
	node.Parent.Children = append(node.Parent.Children, node)
}

// 移除路由
func DelRouter(nodeId string) {
	parent := dynaRouterMap[nodeId].Parent
	for i, child := range parent.Children {
		if child.Id == nodeId {
			parent.Children = append(parent.Children[:i], parent.Children[i+1:]...)
			break
		}
	}
	delete(dynaRouterMap, nodeId)
}

// 重建真实路由
func ResetRealRoute() {
	DefLessgo.Echo.lock.Lock()
	defer DefLessgo.Echo.lock.Unlock()
	DefLessgo.Echo.router = NewRouter(DefLessgo.Echo)
	DefLessgo.Echo.middleware = []Middleware{DefLessgo.Echo.router}
	DefLessgo.Echo.head = HandlerFunc(func(c Context) error {
		return c.Handle(c)
	})
	DefLessgo.Echo.pristineHead = DefLessgo.Echo.head
	DefLessgo.Echo.chainMiddleware()
	var parent *Group
	for _, d := range DynaRouterTree() {
		var mws = make([]Middleware, len(d.Middlewares))
		for i, mw := range d.Middlewares {
			mws[i] = MiddlewareMap[mw]
		}

		switch d.Type {
		case ROOT:
			DefLessgo.Echo.Use(mws...)
		case GROUP:
			if parent == nil {
				parent = DefLessgo.Echo.Group(d.Prefix, mws...)
				break
			}
			parent = parent.Group(d.Prefix, mws...)
		case HANDLER:
			if parent == nil {
				DefLessgo.Echo.Match(d.Methods, path.Join(d.Prefix, d.Param), HandlerFuncMap[d.Handler], mws...)
				break
			}
			parent.Match(d.Methods, path.Join(d.Prefix, d.Param), HandlerFuncMap[d.Handler], mws...)
		}
	}
}

// 从根路由开始配置路由
func RootRouter(node ...*DynaRouter) {
	DefDynaRouter.Children = append(DefDynaRouter.Children, node...)
	for _, r := range node {
		r.Parent = DefDynaRouter
	}
	DefDynaRouter.init()
}

// 配置路由分组
func SubRouter(prefix, name, description string, node ...*DynaRouter) *DynaRouter {
	p := &DynaRouter{
		Prefix:      prefix,
		Type:        GROUP,
		Name:        name,
		Description: description,
		Middlewares: []string{},
		Children:    node,
	}
	for _, r := range node {
		r.Parent = p
	}
	return p
}

func Get(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{GET}, name, description, handler, param...)
}
func Head(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{HEAD}, name, description, handler, param...)
}
func Options(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{OPTIONS}, name, description, handler, param...)
}
func Patch(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{PATCH}, name, description, handler, param...)
}
func Post(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{POST}, name, description, handler, param...)
}
func Put(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{PUT}, name, description, handler, param...)
}
func Trace(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{TRACE}, name, description, handler, param...)
}
func Any(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE}, name, description, handler, param...)
}
func Match(methods []string, name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	if len(methods) == 0 {
		DefLessgo.logger.Error("The method can not be empty: %v", name)
	}
	return route(methods, name, description, handler, param...)
}

func route(methods []string, name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	sort.Strings(methods)
	hUri := handleWareUri(handler)
	HandlerFuncMap[hUri] = handler
	if len(param) == 0 {
		param = append(param, "")
	}
	ns := strings.Split(hUri, ".")
	n := strings.TrimSuffix(ns[len(ns)-1], "Handle")
	prefix := "/" + utils.SnakeString(n)
	return &DynaRouter{
		Prefix:      prefix,
		Type:        HANDLER,
		Name:        name,
		Description: description,
		Methods:     methods,
		Param:       param[0],
		Handler:     hUri,
		Middlewares: []string{},
	}
}

func handleWareUri(hw interface{}) string {
	t := reflect.ValueOf(hw).Type()
	if t.Kind() == reflect.Func {
		return runtime.FuncForPC(reflect.ValueOf(hw).Pointer()).Name()
	}
	return t.String()
}
<commit_msg>update<commit_after>package lessgo

import (
	"path"
	"reflect"
	"runtime"
	"sort"
	"strings"

	"github.com/lessgo/lessgo/utils"
)

type (
	DynaRouter struct {
		Id          string
		Url         string
		Type        int
		Prefix      string // 允许动态修改
		Name        string // 允许动态修改
		Description string // 允许动态修改
		Methods     []string
		Param       string
		Handler     string
		Middlewares []string // 允许动态修改
		ParentUrl   string   // 允许动态指定父节点
		Parent      *DynaRouter
		Children    []*DynaRouter
	}
)

const (
	ROOT int = iota - 1
	GROUP
	HANDLER
)

var (
	HandlerFuncMap = map[string]HandlerFunc{}
	MiddlewareMap  = map[string]Middleware{}
	dynaRouterMap  = map[string]*DynaRouter{}
	DefDynaRouter  = &DynaRouter{
		Prefix:      "/",
		Type:        ROOT,
		Middlewares: []string{},
		Children:    []*DynaRouter{},
	}
)

func (d *DynaRouter) Tree() []*DynaRouter {
	rs := []*DynaRouter{d}
	for _, node := range d.Children {
		rs = append(rs, node.Tree()...)
	}
	return rs
}

func (d *DynaRouter) SetMiddlewares(middlewares []string) {
	d.Middlewares = middlewares
}

func (d *DynaRouter) init() {
	d.setUrl()
	d.setId()
	for _, node := range d.Children {
		node.init()
	}
	dynaRouterMap[d.Id] = d
}

func (d *DynaRouter) setUrl() {
	var u = path.Join(d.Prefix, d.Param)
	d.ParentUrl = ""
	if d.Parent != nil {
		d.Parent.setUrl()
		d.ParentUrl = d.Parent.Url
		u = path.Join(d.Parent.Url, u)
	}
	d.Url = u
}

func (d *DynaRouter) setId() {
	d.Id = utils.MakeMd5(d.Url + strings.Join(d.Methods, ""))
}

// 返回路由列表
func DynaRouterTree() []*DynaRouter {
	return DefDynaRouter.Tree()
}

// 设置或添加路由
func SetRouter(parentId string, node *DynaRouter) {
	node.Parent = dynaRouterMap[parentId]
	node.init()
	dynaRouterMap[node.Id] = node
	for i, child := range node.Parent.Children {
		if child.Id == node.Id {
			node.Parent.Children[i] = node
			return
		}
	}
	node.Parent.Children = append(node.Parent.Children, node)
}

// 移除路由
func DelRouter(nodeId string) {
	parent := dynaRouterMap[nodeId].Parent
	for i, child := range parent.Children {
		if child.Id == nodeId {
			parent.Children = append(parent.Children[:i], parent.Children[i+1:]...)
			break
		}
	}
	delete(dynaRouterMap, nodeId)
}

// 重建真实路由
func ResetRealRoute() {
	DefLessgo.Echo.lock.Lock()
	defer DefLessgo.Echo.lock.Unlock()
	DefLessgo.Echo.router = NewRouter(DefLessgo.Echo)
	DefLessgo.Echo.middleware = []Middleware{DefLessgo.Echo.router}
	DefLessgo.Echo.head = HandlerFunc(func(c Context) error {
		return c.Handle(c)
	})
	DefLessgo.Echo.pristineHead = DefLessgo.Echo.head
	DefLessgo.Echo.chainMiddleware()
	var group *Group
	for _, d := range DynaRouterTree() {
		var mws = make([]Middleware, len(d.Middlewares))
		for i, mw := range d.Middlewares {
			mws[i] = MiddlewareMap[mw]
		}

		switch d.Type {
		case ROOT:
			DefLessgo.Echo.Use(mws...)
		case GROUP:
			if group == nil {
				group = DefLessgo.Echo.Group(d.Prefix, mws...)
				break
			}
			group = group.Group(d.Prefix, mws...)
		case HANDLER:
			if group == nil {
				DefLessgo.Echo.Match(d.Methods, path.Join(d.Prefix, d.Param), HandlerFuncMap[d.Handler], mws...)
				break
			}
			group.Match(d.Methods, path.Join(d.Prefix, d.Param), HandlerFuncMap[d.Handler], mws...)
		}
	}
}

// 从根路由开始配置路由
func RootRouter(node ...*DynaRouter) {
	DefDynaRouter.Children = append(DefDynaRouter.Children, node...)
	for _, r := range node {
		r.Parent = DefDynaRouter
	}
	DefDynaRouter.init()
}

// 配置路由分组
func SubRouter(prefix, name, description string, node ...*DynaRouter) *DynaRouter {
	p := &DynaRouter{
		Prefix:      prefix,
		Type:        GROUP,
		Name:        name,
		Description: description,
		Middlewares: []string{},
		Children:    node,
	}
	for _, r := range node {
		r.Parent = p
	}
	return p
}

func Get(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{GET}, name, description, handler, param...)
}
func Head(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{HEAD}, name, description, handler, param...)
}
func Options(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{OPTIONS}, name, description, handler, param...)
}
func Patch(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{PATCH}, name, description, handler, param...)
}
func Post(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{POST}, name, description, handler, param...)
}
func Put(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{PUT}, name, description, handler, param...)
}
func Trace(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{TRACE}, name, description, handler, param...)
}
func Any(name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	return route([]string{CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE}, name, description, handler, param...)
}
func Match(methods []string, name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	if len(methods) == 0 {
		DefLessgo.logger.Error("The method can not be empty: %v", name)
	}
	return route(methods, name, description, handler, param...)
}

func route(methods []string, name, description string, handler HandlerFunc, param ...string) *DynaRouter {
	sort.Strings(methods)
	hUri := handleWareUri(handler)
	HandlerFuncMap[hUri] = handler
	if len(param) == 0 {
		param = append(param, "")
	}
	ns := strings.Split(hUri, ".")
	n := strings.TrimSuffix(ns[len(ns)-1], "Handle")
	prefix := "/" + utils.SnakeString(n)
	return &DynaRouter{
		Prefix:      prefix,
		Type:        HANDLER,
		Name:        name,
		Description: description,
		Methods:     methods,
		Param:       param[0],
		Handler:     hUri,
		Middlewares: []string{},
	}
}

func handleWareUri(hw interface{}) string {
	t := reflect.ValueOf(hw).Type()
	if t.Kind() == reflect.Func {
		return runtime.FuncForPC(reflect.ValueOf(hw).Pointer()).Name()
	}
	return t.String()
}
<|endoftext|>