<commit_before>package scrum

import (
	"errors"
	"fmt"
	"strings"
	"time"

	colorful "github.com/lucasb-eyer/go-colorful"
	"github.com/nlopes/slack"
	"github.com/robfig/cron"
	log "github.com/sirupsen/logrus"
)

var SlackParams = slack.PostMessageParameters{AsUser: true, LinkNames: 1}

type Service interface {
	DeleteLastReport(username string) bool
	GetTeamByName(teamName string) (*TeamState, error)
	GetTeamsForUser(username string) []string
	GetQuestionSetsForTeam(team string) []*QuestionSet
	SaveReport(report *Report, qs *QuestionSet)
	AddToOutOfOffice(team string, username string)
	RemoveFromOutOfOffice(team string, username string)
}

type service struct {
	configurationProvider ConfigurationProvider
	teamStates            map[string]*TeamState
	slackBotAPI           *slack.Client
	lastEnteredReport     map[string]*Report
}

type TeamState struct {
	*Team
	*cron.Cron
	*service

	questionSetStates map[*QuestionSet]*questionSetState
}

type questionSetState struct {
	*QuestionSet
	enteredReports map[string]*Report
	sent           bool
}

type Report struct {
	User    string
	Team    string
	Skipped bool
	// questions / answers
	Answers map[string]string
}

func emptyQuestionSetState(qs *QuestionSet) *questionSetState {
	return &questionSetState{qs, map[string]*Report{}, false}
}

func isMemberOutOfOffice(ts *TeamState, member string) bool {
	isOutOfOffice := false
	for _, outOfOfficeMember := range ts.OutOfOffice {
		if outOfOfficeMember == member {
			isOutOfOffice = true
			break
		}
	}
	return isOutOfOffice
}

func (ts *TeamState) postMessageToSlack(channel string, message string, params slack.PostMessageParameters) {
	_, _, err := ts.service.slackBotAPI.PostMessage(channel, message, params)
	if err != nil {
		log.WithFields(log.Fields{
			"team":    ts.Team.Name,
			"channel": channel,
			"error":   err,
		}).Warn("Error while posting message to slack")
	}
}

func (ts *TeamState) sendReportForTeam(qs *QuestionSet) {
	qsstate := ts.questionSetStates[qs]
	if qsstate.sent == true {
		return
	}
	qsstate.sent = true

	if len(qsstate.enteredReports) == 0 {
		ts.postMessageToSlack(ts.Channel, "I'd like to take time to :shame: everyone for not reporting", SlackParams)
		return
	}

	attachments := []slack.Attachment{}
	didNotDoReport := []string{}
	outOfOffice := []string{}

	for _, member := range ts.Members {
		report, ok := qsstate.enteredReports[member]
		if !ok {
			if isMemberOutOfOffice(ts, member) {
				outOfOffice = append(outOfOffice, member)
			} else {
				didNotDoReport = append(didNotDoReport, member)
			}
		} else if report.Skipped {
			attachment := slack.Attachment{
				Color:      colorful.FastHappyColor().Hex(),
				MarkdownIn: []string{"text", "pretext"},
				Pretext:    "@" + member,
				Text:       "Has nothing to declare (most probably :bee:cause he did nothing :troll:)",
			}
			attachments = append(attachments, attachment)
		} else {
			message := ""
			for idx, q := range qsstate.QuestionSet.Questions {
				message += q + "\n" + report.Answers[q]

				if idx < len(qsstate.QuestionSet.Questions)-1 {
					message += "\n\n"
				}
			}

			attachment := slack.Attachment{
				Color:      colorful.FastHappyColor().Hex(),
				MarkdownIn: []string{"text", "pretext"},
				Pretext:    "@" + member,
				Text:       message,
			}
			attachments = append(attachments, attachment)
		}
	}

	if len(outOfOffice) > 0 {
		persons := outOfOffice[0]
		verb := "is"

		if len(outOfOffice) > 1 {
			persons = strings.Join(outOfOffice[0:(len(outOfOffice)-2)], ",") + " and " + outOfOffice[(len(outOfOffice)-1)]
			verb = "are"
		}

		attachment := slack.Attachment{
			Color:      colorful.FastHappyColor().Hex(),
			MarkdownIn: []string{"text", "pretext"},
			Pretext:    "Currently out of office",
			Text:       persons + " " + verb + " currently out of office :sunglasses: :palm_tree:",
		}

		attachments = append(attachments, attachment)
	}

	if ts.SplitReport {
		ts.postMessageToSlack(ts.Channel, ":parrotcop: Alrighty! Here's the scrum report for today!", slack.PostMessageParameters{AsUser: true})
		for i := 0; i < len(attachments); i++ {
			params := slack.PostMessageParameters{
				AsUser:      true,
				LinkNames:   1,
				Attachments: []slack.Attachment{attachments[i]},
			}
			ts.postMessageToSlack(ts.Channel, "*Scrum by:*", params)
		}
	} else {
		params := slack.PostMessageParameters{
			AsUser:      true,
			LinkNames:   1,
			Attachments: attachments,
		}
		ts.postMessageToSlack(ts.Channel, ":parrotcop: Alrighty! Here's the scrum report for today!", params)
	}

	if len(didNotDoReport) > 0 {
		ts.postMessageToSlack(ts.Channel, fmt.Sprintln("And lastly we should take a little time to shame", didNotDoReport), SlackParams)
	}

	log.WithFields(log.Fields{
		"team":    ts.Team.Name,
		"channel": ts.Channel,
	}).Info("Sent scrum report.")
}

func (ts *TeamState) sendFirstReminder(qs *QuestionSet) {
	qsstate := ts.questionSetStates[qs]

	log.WithFields(log.Fields{
		"team":    ts.Team.Name,
		"channel": ts.Channel,
	}).Info("Sending first reminder.")

	for _, member := range ts.Members {
		if !isMemberOutOfOffice(ts, member) {
			_, ok := qsstate.enteredReports[member]
			if !ok {
				_, _, err := ts.service.slackBotAPI.PostMessage("@"+member, "Hey! Don't forget to fill your report! `start` to do it or `skip` if you have nothing to say", SlackParams)
				if err != nil {
					log.WithFields(log.Fields{
						"team":    ts.Team.Name,
						"member":  member,
						"channel": ts.Channel,
						"error":   err,
					}).Warn("Could not send first reminder.")
				}
			}
		} else {
			log.WithFields(log.Fields{
				"team":    ts.Team.Name,
				"member":  member,
				"channel": ts.Channel,
			}).Info("Member out of office, not sending reminder.")
		}
	}
}

func (ts *TeamState) sendLastReminder(qs *QuestionSet) {
	qsstate := ts.questionSetStates[qs]
	didNotDoReport := []string{}

	log.WithFields(log.Fields{
		"team":    ts.Team.Name,
		"channel": ts.Channel,
	}).Info("Sending last reminder.")

	for _, member := range ts.Members {
		if !isMemberOutOfOffice(ts, member) {
			_, ok := qsstate.enteredReports[member]
			if !ok {
				didNotDoReport = append(didNotDoReport, "@"+member)
			}
		}
	}

	if len(didNotDoReport) == 0 {
		return
	}

	memberThatDidNotDoReport := strings.Join(didNotDoReport, ", ")
	ts.postMessageToSlack(ts.Channel, fmt.Sprintf("Last chance to fill report! :shame: to: %s", memberThatDidNotDoReport), SlackParams)
}

type ScrumReportJob struct {
	*TeamState
	*QuestionSet
}

func (job *ScrumReportJob) Run() {
	job.TeamState.sendReportForTeam(job.QuestionSet)
	// Reset the questionSetState
	job.TeamState.questionSetStates[job.QuestionSet] = emptyQuestionSetState(job.QuestionSet)
}

type iteration uint8

const (
	First iteration = iota
	Last
)

type ScrumReminderJob struct {
	iteration iteration
	*TeamState
	*QuestionSet
}

func (job *ScrumReminderJob) Run() {
	// Post to slack things
	if job.iteration == First {
		job.TeamState.sendFirstReminder(job.QuestionSet)
		return
	}
	if job.iteration == Last {
		job.TeamState.sendLastReminder(job.QuestionSet)
		return
	}
}

func NewService(configurationProvider ConfigurationProvider, slackBotAPI *slack.Client) Service {
	mod := &service{
		configurationProvider: configurationProvider,
		slackBotAPI:           slackBotAPI,
		teamStates:            map[string]*TeamState{},
		lastEnteredReport:     map[string]*Report{},
	}

	// initial *refresh
	mod.refresh(configurationProvider.Config())

	configurationProvider.OnChange(func(cfg *Config) {
		log.Println("Configuration File Changed refreshing state")
		mod.refresh(cfg)
	})

	return mod
}

func (mod *service) refresh(config *Config) {
	teams := config.ToTeams()

	log.Info("Refreshing teams.")

	globalLocation := time.Local
	if config.Timezone != "" {
		l, err := time.LoadLocation(config.Timezone)
		if err == nil {
			globalLocation = l
		} else {
			log.WithFields(log.Fields{
				"error": err,
			}).Warn("Error loading global location, using default.")
		}
	}

	for _, team := range teams {
		state, ok := mod.teamStates[team.Name]
		if !ok {
			log.WithFields(log.Fields{
				"team": team.Name,
			}).Info("Initializing team.")
		} else {
			// FIXME: Check if team changed before doing that
			log.WithFields(log.Fields{
				"team": team.Name,
			}).Info("Refreshing team.")
			state.Cron.Stop()
		}
		state = initTeamState(team, globalLocation, mod)
		mod.teamStates[team.Name] = state
	}
}

func initTeamState(team *Team, globalLocation *time.Location, mod *service) *TeamState {
	state := &TeamState{
		Team:              team,
		service:           mod,
		questionSetStates: map[*QuestionSet]*questionSetState{},
	}

	loc := globalLocation
	if team.Timezone != nil {
		loc = team.Timezone
	}
	state.Cron = cron.NewWithLocation(loc)

	for _, qs := range team.QuestionsSets {
		state.questionSetStates[qs] = emptyQuestionSetState(qs)
		state.Cron.Schedule(qs.ReportSchedule, &ScrumReportJob{state, qs})
		state.Cron.Schedule(newScheduleDependentSchedule(qs.ReportSchedule, qs.FirstReminderBeforeReport), &ScrumReminderJob{First, state, qs})
		state.Cron.Schedule(newScheduleDependentSchedule(qs.ReportSchedule, qs.LastReminderBeforeReport), &ScrumReminderJob{Last, state, qs})
	}

	state.Cron.Start()

	return state
}

// scheduleDependentSchedule is a schedule that depends on another one to trigger.
type scheduleDependentSchedule struct {
	cron.Schedule
	time.Duration

	depNext time.Time
}

func newScheduleDependentSchedule(s cron.Schedule, t time.Duration) *scheduleDependentSchedule {
	return &scheduleDependentSchedule{s, t, time.Time{}}
}

func (s *scheduleDependentSchedule) Next(t time.Time) time.Time {
	// On init, this is not done in the constructor because Next is only called when the Cron is started
	if s.depNext.IsZero() {
		s.depNext = s.Schedule.Next(t)
	}

	if s.depNext.Add(s.Duration).Before(t) {
		s.depNext = s.Schedule.Next(s.depNext)
	}

	return s.depNext.Add(s.Duration)
}

func (m *service) GetTeamsForUser(username string) []string {
	teams := []string{}
	for _, ts := range m.teamStates {
		for _, member := range ts.Members {
			if username == member {
				teams = append(teams, ts.Name)
			}
		}
	}

	return teams
}

func (m *service) GetTeamByName(teamName string) (*TeamState, error) {
	for _, ts := range m.teamStates {
		if teamName == ts.Team.Name {
			return ts, nil
		}
	}
	return nil, errors.New("Team " + teamName + " does not exist")
}

func (m *service) GetQuestionSetsForTeam(team string) []*QuestionSet {
	return m.teamStates[team].QuestionsSets
}

func (m *service) SaveReport(report *Report, qs *QuestionSet) {
	m.lastEnteredReport[report.User] = report
	m.teamStates[report.Team].questionSetStates[qs].enteredReports[report.User] = report

	// if done launch report answers
	if len(m.teamStates[report.Team].Members) == len(m.teamStates[report.Team].questionSetStates[qs].enteredReports) {
		m.teamStates[report.Team].sendReportForTeam(qs)
	}
}

func (m *service) DeleteLastReport(user string) bool {

	r, ok := m.lastEnteredReport[user]
	if !ok {
		return false
	}
	delete(m.lastEnteredReport, user)

	ts, ok := m.teamStates[r.Team]
	if !ok {
		return false
	}

	for _, qs := range ts.questionSetStates {
		report, ok := qs.enteredReports[r.User]
		if ok && r == report {
			delete(qs.enteredReports, r.User)
			return true
		}
	}

	return false
}

func (m *service) AddToOutOfOffice(team string, username string) {
	m.teamStates[team].OutOfOffice = append(m.teamStates[team].OutOfOffice, username)
}

func (m *service) RemoveFromOutOfOffice(team string, username string) {
	var ooof []string
	for _, outOfOfficeMember := range m.teamStates[team].OutOfOffice {
		if outOfOfficeMember != username {
			ooof = append(ooof, outOfOfficeMember)
		}
	}
	m.teamStates[team].OutOfOffice = ooof
}
<commit_msg>Do not link names, its a pain<commit_after>package scrum

import (
	"errors"
	"fmt"
	"strings"
	"time"

	colorful "github.com/lucasb-eyer/go-colorful"
	"github.com/nlopes/slack"
	"github.com/robfig/cron"
	log "github.com/sirupsen/logrus"
)

var SlackParams = slack.PostMessageParameters{AsUser: true, LinkNames: 1}

type Service interface {
	DeleteLastReport(username string) bool
	GetTeamByName(teamName string) (*TeamState, error)
	GetTeamsForUser(username string) []string
	GetQuestionSetsForTeam(team string) []*QuestionSet
	SaveReport(report *Report, qs *QuestionSet)
	AddToOutOfOffice(team string, username string)
	RemoveFromOutOfOffice(team string, username string)
}

type service struct {
	configurationProvider ConfigurationProvider
	teamStates            map[string]*TeamState
	slackBotAPI           *slack.Client
	lastEnteredReport     map[string]*Report
}

type TeamState struct {
	*Team
	*cron.Cron
	*service

	questionSetStates map[*QuestionSet]*questionSetState
}

type questionSetState struct {
	*QuestionSet
	enteredReports map[string]*Report
	sent           bool
}

type Report struct {
	User    string
	Team    string
	Skipped bool
	// questions / answers
	Answers map[string]string
}

func emptyQuestionSetState(qs *QuestionSet) *questionSetState {
	return &questionSetState{qs, map[string]*Report{}, false}
}

func isMemberOutOfOffice(ts *TeamState, member string) bool {
	isOutOfOffice := false
	for _, outOfOfficeMember := range ts.OutOfOffice {
		if outOfOfficeMember == member {
			isOutOfOffice = true
			break
		}
	}
	return isOutOfOffice
}

func (ts *TeamState) postMessageToSlack(channel string, message string, params slack.PostMessageParameters) {
	_, _, err := ts.service.slackBotAPI.PostMessage(channel, message, params)
	if err != nil {
		log.WithFields(log.Fields{
			"team":    ts.Team.Name,
			"channel": channel,
			"error":   err,
		}).Warn("Error while posting message to slack")
	}
}

func (ts *TeamState) sendReportForTeam(qs *QuestionSet) {
	qsstate := ts.questionSetStates[qs]
	if qsstate.sent == true {
		return
	}
	qsstate.sent = true

	if len(qsstate.enteredReports) == 0 {
		ts.postMessageToSlack(ts.Channel, "I'd like to take time to :shame: everyone for not reporting", SlackParams)
		return
	}

	attachments := []slack.Attachment{}
	didNotDoReport := []string{}
	outOfOffice := []string{}

	for _, member := range ts.Members {
		report, ok := qsstate.enteredReports[member]
		if !ok {
			if isMemberOutOfOffice(ts, member) {
				outOfOffice = append(outOfOffice, member)
			} else {
				didNotDoReport = append(didNotDoReport, member)
			}
		} else if report.Skipped {
			attachment := slack.Attachment{
				Color:      colorful.FastHappyColor().Hex(),
				MarkdownIn: []string{"text", "pretext"},
				Pretext:    "@" + member,
				Text:       "Has nothing to declare (most probably :bee:cause he did nothing :troll:)",
			}
			attachments = append(attachments, attachment)
		} else {
			message := ""
			for idx, q := range qsstate.QuestionSet.Questions {
				message += q + "\n" + report.Answers[q]

				if idx < len(qsstate.QuestionSet.Questions)-1 {
					message += "\n\n"
				}
			}

			attachment := slack.Attachment{
				Color:      colorful.FastHappyColor().Hex(),
				MarkdownIn: []string{"text", "pretext"},
				Pretext:    "@" + member,
				Text:       message,
			}
			attachments = append(attachments, attachment)
		}
	}

	if len(outOfOffice) > 0 {
		persons := outOfOffice[0]
		verb := "is"

		if len(outOfOffice) > 1 {
			persons = strings.Join(outOfOffice[0:(len(outOfOffice)-2)], ", ") + " and " + outOfOffice[(len(outOfOffice)-1)]
			verb = "are"
		}

		attachment := slack.Attachment{
			Color:      colorful.FastHappyColor().Hex(),
			MarkdownIn: []string{"text", "pretext"},
			Pretext:    "Currently out of office",
			Text:       persons + " " + verb + " currently out of office :sunglasses: :palm_tree:",
		}

		attachments = append(attachments, attachment)
	}

	if ts.SplitReport {
		ts.postMessageToSlack(ts.Channel, ":parrotcop: Alrighty! Here's the scrum report for today!", slack.PostMessageParameters{AsUser: true})
		for i := 0; i < len(attachments); i++ {
			params := slack.PostMessageParameters{
				AsUser:      true,
				Attachments: []slack.Attachment{attachments[i]},
			}
			ts.postMessageToSlack(ts.Channel, "*Scrum by:*", params)
		}
	} else {
		params := slack.PostMessageParameters{
			AsUser:      true,
			Attachments: attachments,
		}
		ts.postMessageToSlack(ts.Channel, ":parrotcop: Alrighty! Here's the scrum report for today!", params)
	}

	if len(didNotDoReport) > 0 {
		ts.postMessageToSlack(ts.Channel, fmt.Sprintln("And lastly we should take a little time to shame", didNotDoReport), SlackParams)
	}

	log.WithFields(log.Fields{
		"team":    ts.Team.Name,
		"channel": ts.Channel,
	}).Info("Sent scrum report.")
}

func (ts *TeamState) sendFirstReminder(qs *QuestionSet) {
	qsstate := ts.questionSetStates[qs]

	log.WithFields(log.Fields{
		"team":    ts.Team.Name,
		"channel": ts.Channel,
	}).Info("Sending first reminder.")

	for _, member := range ts.Members {
		if !isMemberOutOfOffice(ts, member) {
			_, ok := qsstate.enteredReports[member]
			if !ok {
				_, _, err := ts.service.slackBotAPI.PostMessage("@"+member, "Hey! Don't forget to fill your report! `start` to do it or `skip` if you have nothing to say", SlackParams)
				if err != nil {
					log.WithFields(log.Fields{
						"team":    ts.Team.Name,
						"member":  member,
						"channel": ts.Channel,
						"error":   err,
					}).Warn("Could not send first reminder.")
				}
			}
		} else {
			log.WithFields(log.Fields{
				"team":    ts.Team.Name,
				"member":  member,
				"channel": ts.Channel,
			}).Info("Member out of office, not sending reminder.")
		}
	}
}

func (ts *TeamState) sendLastReminder(qs *QuestionSet) {
	qsstate := ts.questionSetStates[qs]
	didNotDoReport := []string{}

	log.WithFields(log.Fields{
		"team":    ts.Team.Name,
		"channel": ts.Channel,
	}).Info("Sending last reminder.")

	for _, member := range ts.Members {
		if !isMemberOutOfOffice(ts, member) {
			_, ok := qsstate.enteredReports[member]
			if !ok {
				didNotDoReport = append(didNotDoReport, "@"+member)
			}
		}
	}

	if len(didNotDoReport) == 0 {
		return
	}

	memberThatDidNotDoReport := strings.Join(didNotDoReport, ", ")
	ts.postMessageToSlack(ts.Channel, fmt.Sprintf("Last chance to fill report! :shame: to: %s", memberThatDidNotDoReport), SlackParams)
}

type ScrumReportJob struct {
	*TeamState
	*QuestionSet
}

func (job *ScrumReportJob) Run() {
	job.TeamState.sendReportForTeam(job.QuestionSet)
	// Reset the questionSetState
	job.TeamState.questionSetStates[job.QuestionSet] = emptyQuestionSetState(job.QuestionSet)
}

type iteration uint8

const (
	First iteration = iota
	Last
)

type ScrumReminderJob struct {
	iteration iteration
	*TeamState
	*QuestionSet
}

func (job *ScrumReminderJob) Run() {
	// Post to slack things
	if job.iteration == First {
		job.TeamState.sendFirstReminder(job.QuestionSet)
		return
	}
	if job.iteration == Last {
		job.TeamState.sendLastReminder(job.QuestionSet)
		return
	}
}

func NewService(configurationProvider ConfigurationProvider, slackBotAPI *slack.Client) Service {
	mod := &service{
		configurationProvider: configurationProvider,
		slackBotAPI:           slackBotAPI,
		teamStates:            map[string]*TeamState{},
		lastEnteredReport:     map[string]*Report{},
	}

	// initial *refresh
	mod.refresh(configurationProvider.Config())

	configurationProvider.OnChange(func(cfg *Config) {
		log.Println("Configuration File Changed refreshing state")
		mod.refresh(cfg)
	})

	return mod
}

func (mod *service) refresh(config *Config) {
	teams := config.ToTeams()

	log.Info("Refreshing teams.")

	globalLocation := time.Local
	if config.Timezone != "" {
		l, err := time.LoadLocation(config.Timezone)
		if err == nil {
			globalLocation = l
		} else {
			log.WithFields(log.Fields{
				"error": err,
			}).Warn("Error loading global location, using default.")
		}
	}

	for _, team := range teams {
		state, ok := mod.teamStates[team.Name]
		if !ok {
			log.WithFields(log.Fields{
				"team": team.Name,
			}).Info("Initializing team.")
		} else {
			// FIXME: Check if team changed before doing that
			log.WithFields(log.Fields{
				"team": team.Name,
			}).Info("Refreshing team.")
			state.Cron.Stop()
		}
		state = initTeamState(team, globalLocation, mod)
		mod.teamStates[team.Name] = state
	}
}

func initTeamState(team *Team, globalLocation *time.Location, mod *service) *TeamState {
	state := &TeamState{
		Team:              team,
		service:           mod,
		questionSetStates: map[*QuestionSet]*questionSetState{},
	}

	loc := globalLocation
	if team.Timezone != nil {
		loc = team.Timezone
	}
	state.Cron = cron.NewWithLocation(loc)

	for _, qs := range team.QuestionsSets {
		state.questionSetStates[qs] = emptyQuestionSetState(qs)
		state.Cron.Schedule(qs.ReportSchedule, &ScrumReportJob{state, qs})
		state.Cron.Schedule(newScheduleDependentSchedule(qs.ReportSchedule, qs.FirstReminderBeforeReport), &ScrumReminderJob{First, state, qs})
		state.Cron.Schedule(newScheduleDependentSchedule(qs.ReportSchedule, qs.LastReminderBeforeReport), &ScrumReminderJob{Last, state, qs})
	}

	state.Cron.Start()

	return state
}

// scheduleDependentSchedule is a schedule that depends on another one to trigger.
type scheduleDependentSchedule struct {
	cron.Schedule
	time.Duration

	depNext time.Time
}

func newScheduleDependentSchedule(s cron.Schedule, t time.Duration) *scheduleDependentSchedule {
	return &scheduleDependentSchedule{s, t, time.Time{}}
}

func (s *scheduleDependentSchedule) Next(t time.Time) time.Time {
	// On init, this is not done in the constructor because Next is only called when the Cron is started
	if s.depNext.IsZero() {
		s.depNext = s.Schedule.Next(t)
	}

	if s.depNext.Add(s.Duration).Before(t) {
		s.depNext = s.Schedule.Next(s.depNext)
	}

	return s.depNext.Add(s.Duration)
}

func (m *service) GetTeamsForUser(username string) []string {
	teams := []string{}
	for _, ts := range m.teamStates {
		for _, member := range ts.Members {
			if username == member {
				teams = append(teams, ts.Name)
			}
		}
	}

	return teams
}

func (m *service) GetTeamByName(teamName string) (*TeamState, error) {
	for _, ts := range m.teamStates {
		if teamName == ts.Team.Name {
			return ts, nil
		}
	}
	return nil, errors.New("Team " + teamName + " does not exist")
}

func (m *service) GetQuestionSetsForTeam(team string) []*QuestionSet {
	return m.teamStates[team].QuestionsSets
}

func (m *service) SaveReport(report *Report, qs *QuestionSet) {
	m.lastEnteredReport[report.User] = report
	m.teamStates[report.Team].questionSetStates[qs].enteredReports[report.User] = report

	// if done launch report answers
	if len(m.teamStates[report.Team].Members) == len(m.teamStates[report.Team].questionSetStates[qs].enteredReports) {
		m.teamStates[report.Team].sendReportForTeam(qs)
	}
}

func (m *service) DeleteLastReport(user string) bool {

	r, ok := m.lastEnteredReport[user]
	if !ok {
		return false
	}
	delete(m.lastEnteredReport, user)

	ts, ok := m.teamStates[r.Team]
	if !ok {
		return false
	}

	for _, qs := range ts.questionSetStates {
		report, ok := qs.enteredReports[r.User]
		if ok && r == report {
			delete(qs.enteredReports, r.User)
			return true
		}
	}

	return false
}

func (m *service) AddToOutOfOffice(team string, username string) {
	m.teamStates[team].OutOfOffice = append(m.teamStates[team].OutOfOffice, username)
}

func (m *service) RemoveFromOutOfOffice(team string, username string) {
	var ooof []string
	for _, outOfOfficeMember := range m.teamStates[team].OutOfOffice {
		if outOfOfficeMember != username {
			ooof = append(ooof, outOfOfficeMember)
		}
	}
	m.teamStates[team].OutOfOffice = ooof
}
<|endoftext|>


<commit_before>// The MIT License (MIT)
//
// Copyright (c) 2015 Arnaud Vazard
//
// See LICENSE file.
package search

import (
	"encoding/json"
	"fmt"
	"github.com/romainletendart/goxxx/core"
	"github.com/thoj/go-ircevent"
	"io/ioutil"
	"log"
	"net/http"
	"regexp"
	"strings"
)

//  --- --- --- Constants --- --- ---
const (
	URL_DUCKDUCKGO       string = "https://duckduckgo.com/html/?q=%s"
	URL_WIKIPEDIA        string = "https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts|info&exintro=&explaintext=&inprop=url&titles=%s"
	URL_URBANDICTIONNARY string = "http://api.urbandictionary.com/v0/define?term=%s"

	HELP_DUCKDUCKGO       string = "!d <terms to search> \t\t=> Search on DuckduckGo"
	HELP_WIKIPEDIA        string = "!w <terms to search> \t\t=> Search on Wikipedia"
	HELP_URBANDICTIONNARY string = "!u <terms to search> \t\t=> Search on Urban Dictionnary"
)

// --- --- --- Types  --- --- ---
type wikipedia struct {
	Query struct {
		Pages map[string]struct {
			Extract string `json:"extract"`
			Fullurl string `json:"fullurl"`
			Title   string `json:"title"`
		} `json:"pages"`
	} `json:"query"`
}

type urbanDictionnary struct {
	List []struct {
		Definition string `json:"definition"`
		Example    string `json:"example"`
		Permalink  string `json:"permalink"`
	} `json:"list"`
}

type searchData struct {
	getUrl         func(string) []string
	text_result    [2]string
	text_no_result string
}

// --- --- --- Global variable --- --- ---
var searchMap = make(map[string]searchData)

func Init() {
	searchMap["!dg"] = searchData{
		getDuckduckgoSearchResult,
		[2]string{
			"DuckDuckGo: Best result for %q => %s"},
		"DuckDuckGo: No result for %q"}
	searchMap["!w"] = searchData{
		getWikipediaSearchResult,
		[2]string{"Wikipedia result for %q => %s"},
		"Wikipedia: No result for %q"}
	searchMap["!u"] = searchData{
		getUrbanDictionnarySearchResult,
		[2]string{"Urban Dictionnary: Best result for %q => %s", "Definition: %s"},
		"Urban Dictionnary: No result for %q"}
}

func HandleSearchCmd(event *irc.Event, callback func(*core.ReplyCallbackData)) bool {
	if callback == nil {
		log.Println("Callback nil for the HandleSearchCmd function")
		return false
	}

	fields := strings.Fields(event.Message())
	// fields[0]  => Command
	// fields[1:] => terms to search for
	if len(fields) == 0 {
		return false
	}

	search, present := searchMap[fields[0]]
	if !present {
		return false
	}

	message := strings.Join(fields[1:], " ")
	if message == "" {
		callback(&core.ReplyCallbackData{Message: fmt.Sprintf("Search usage: %s \"terms to search for\"", fields[0])})
		return false
	}

	results := search.getUrl(message)
	if results == nil {
		callback(&core.ReplyCallbackData{Message: fmt.Sprintf(search.text_no_result, message)})
		return true
	}
	for i, item := range results {
		if i == 0 && search.text_result[i] != "" {
			callback(&core.ReplyCallbackData{Message: fmt.Sprintf(search.text_result[i], message, item)})
		} else if i == 1 && search.text_result[i] != "" {
			callback(&core.ReplyCallbackData{Message: fmt.Sprintf(search.text_result[i], item)})
		} else {
			callback(&core.ReplyCallbackData{Message: item})
		}
	}
	return true
}

// --- --- --- HTTP Functions --- --- ---
func getResponseAsText(url string, searchTerms *string) []byte {
	response, err := http.Get(fmt.Sprintf(url, *searchTerms))
	if err != nil {
		log.Println(err)
		return nil
	}
	defer response.Body.Close()

	text, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Println(err)
		return nil
	}
	response.Body.Close()
	return text
}

// --- --- --- Search provider functions --- --- ---

// --- Duckduckgo
func getDuckduckgoResultFromHtml(page []byte) string {
	re := regexp.MustCompile(`<a rel="nofollow" href="(.[^"]*)">`)
	if result := re.FindSubmatch(page); result != nil && len(result) == 2 {
		return string(result[1])
	}
	return ""
}

func getDuckduckgoSearchResult(searchTerms string) []string {
	HtmlPageFromHttp := getResponseAsText(URL_DUCKDUCKGO, &searchTerms)
	if HtmlPageFromHttp == nil {
		return nil
	}
	result := getDuckduckgoResultFromHtml(HtmlPageFromHttp)
	if result == "" {
		return nil
	}
	return []string{result}
}

// --- Wikipedia
func getWikipediaResultFromJson(jsonDataFromHttp []byte) []string {
	var result wikipedia
	err := json.Unmarshal(jsonDataFromHttp, &result)
	if err != nil {
		log.Println(err)
		return nil
	}

	var returnValues []string
	for key, value := range result.Query.Pages {
		if key != "-1" {
			returnValues = append(returnValues, value.Fullurl)
			returnValues = append(returnValues, strings.Split(value.Extract, ". ")...)
		}
	}
	return returnValues
}

func getWikipediaSearchResult(searchTerms string) []string {
	searchTerms = strings.Replace(strings.Title(searchTerms), " ", "%20", -1)
	jsonDataFromHttp := getResponseAsText(URL_WIKIPEDIA, &searchTerms)
	if jsonDataFromHttp == nil {
		return nil
	}
	result := getWikipediaResultFromJson(jsonDataFromHttp)
	if result == nil {
		return nil
	}
	return result
}

// --- Urban Dictionnary
func getUrbanDictionnaryResultFromJson(jsonDataFromHttp []byte) []string {
	var result urbanDictionnary
	err := json.Unmarshal(jsonDataFromHttp, &result)
	if err != nil {
		log.Println(err)
		return nil
	}
	if len(result.List) == 0 {
		return nil
	}
	returnValues := []string{result.List[0].Permalink}
	returnValues = append(returnValues, strings.Split(result.List[0].Definition, ". ")...)

	return returnValues
}

func getUrbanDictionnarySearchResult(searchTerms string) []string {
	searchTerms = strings.Replace(strings.Title(searchTerms), " ", "%20", -1)
	jsonDataFromHttp := getResponseAsText(URL_URBANDICTIONNARY, &searchTerms)
	if jsonDataFromHttp == nil {
		return nil
	}
	result := getUrbanDictionnaryResultFromJson(jsonDataFromHttp)
	if result == nil {
		return nil
	}
	return result
}
<commit_msg>Search: changed the way the different parts of the return message are processed<commit_after>// The MIT License (MIT)
//
// Copyright (c) 2015 Arnaud Vazard
//
// See LICENSE file.
package search

import (
	"encoding/json"
	"fmt"
	"github.com/romainletendart/goxxx/core"
	"github.com/thoj/go-ircevent"
	"io/ioutil"
	"log"
	"net/http"
	"regexp"
	"strings"
)

//  --- --- --- Constants --- --- ---
const (
	URL_DUCKDUCKGO       string = "https://duckduckgo.com/html/?q=%s"
	URL_WIKIPEDIA        string = "https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts|info&exintro=&explaintext=&inprop=url&titles=%s"
	URL_URBANDICTIONNARY string = "http://api.urbandictionary.com/v0/define?term=%s"

	HELP_DUCKDUCKGO       string = "!d/!dg/!ddg <terms to search> \t\t=> Search on DuckduckGo"
	HELP_WIKIPEDIA        string = "!w <terms to search> \t\t=> Search on Wikipedia"
	HELP_URBANDICTIONNARY string = "!u/!ud <terms to search> \t\t=> Search on Urban Dictionnary"
)

// --- --- --- Types  --- --- ---
type wikipedia struct {
	Query struct {
		Pages map[string]struct {
			Extract string `json:"extract"`
			Fullurl string `json:"fullurl"`
			Title   string `json:"title"`
		} `json:"pages"`
	} `json:"query"`
}

type urbanDictionnary struct {
	List []struct {
		Definition string `json:"definition"`
		Example    string `json:"example"`
		Permalink  string `json:"permalink"`
	} `json:"list"`
}

type searchData struct {
	getUrl func(string) []string
	// First string is for the first result (URL), second string for the second result (Details/Definition/...)
	text_result    [2]string
	text_no_result string
}

// --- --- --- Global variable --- --- ---
var searchMap = make(map[string]*searchData)

func Init() {
	ddg := &searchData{getDuckduckgoSearchResult, [2]string{"DuckDuckGo: Best result for %q => %s"}, "DuckDuckGo: No result for %q"}
	searchMap["!d"] = ddg
	searchMap["!dg"] = ddg
	searchMap["!ddg"] = ddg

	searchMap["!w"] = &searchData{getWikipediaSearchResult, [2]string{"Wikipedia result for %q => %s"}, "Wikipedia: No result for %q"}

	ud := &searchData{
		getUrbanDictionnarySearchResult,
		[2]string{"Urban Dictionnary: Best result for %q => %s", "Definition: %s"},
		"Urban Dictionnary: No result for %q"}
	searchMap["!u"] = ud
	searchMap["!ud"] = ud
}

func HandleSearchCmd(event *irc.Event, callback func(*core.ReplyCallbackData)) bool {
	if callback == nil {
		log.Println("Callback nil for the HandleSearchCmd function")
		return false
	}

	fields := strings.Fields(event.Message())
	// fields[0]  => Command
	// fields[1:] => terms to search for
	if len(fields) == 0 {
		return false
	}

	search, present := searchMap[fields[0]]
	if !present {
		return false
	}

	message := strings.Join(fields[1:], " ")
	if message == "" {
		callback(&core.ReplyCallbackData{Message: fmt.Sprintf("Search usage: %s \"terms to search for\"", fields[0])})
		return false
	}

	results := search.getUrl(message)
	if results == nil {
		callback(&core.ReplyCallbackData{Message: fmt.Sprintf(search.text_no_result, message)})
		return true
	}
	for index, item := range results {
		switch index {
		// First part of the result is sent to everyone, no nick is sent
		case 0:
			if search.text_result[index] != "" {
				callback(&core.ReplyCallbackData{Message: fmt.Sprintf(search.text_result[index], message, item)})
			} else {
				callback(&core.ReplyCallbackData{Message: item})
			}
		// Second and following parts of the result are sent directly to the user
		case 1:
			if search.text_result[index] != "" {
				callback(&core.ReplyCallbackData{Nick: event.Nick, Message: fmt.Sprintf(search.text_result[index], item)})
			} else {
				callback(&core.ReplyCallbackData{Nick: event.Nick, Message: item})
			}
		default:
			callback(&core.ReplyCallbackData{Nick: event.Nick, Message: item})
		}
	}
	return true
}

// --- --- --- HTTP Functions --- --- ---
func getResponseAsText(url string, searchTerms *string) []byte {
	response, err := http.Get(fmt.Sprintf(url, *searchTerms))
	if err != nil {
		log.Println(err)
		return nil
	}
	defer response.Body.Close()

	text, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Println(err)
		return nil
	}
	response.Body.Close()
	return text
}

// --- --- --- Search provider functions --- --- ---

// --- Duckduckgo
func getDuckduckgoResultFromHtml(page []byte) string {
	re := regexp.MustCompile(`<a rel="nofollow" href="(.[^"]*)">`)
	if result := re.FindSubmatch(page); result != nil && len(result) == 2 {
		return string(result[1])
	}
	return ""
}

func getDuckduckgoSearchResult(searchTerms string) []string {
	HtmlPageFromHttp := getResponseAsText(URL_DUCKDUCKGO, &searchTerms)
	if HtmlPageFromHttp == nil {
		return nil
	}
	result := getDuckduckgoResultFromHtml(HtmlPageFromHttp)
	if result == "" {
		return nil
	}
	return []string{result}
}

// --- Wikipedia
func getWikipediaResultFromJson(jsonDataFromHttp []byte) []string {
	var result wikipedia
	err := json.Unmarshal(jsonDataFromHttp, &result)
	if err != nil {
		log.Println(err)
		return nil
	}

	var returnValues []string
	for key, value := range result.Query.Pages {
		if key != "-1" {
			returnValues = append(returnValues, value.Fullurl)
			returnValues = append(returnValues, strings.Split(value.Extract, ". ")...)
		}
	}
	return returnValues
}

func getWikipediaSearchResult(searchTerms string) []string {
	searchTerms = strings.Replace(strings.Title(searchTerms), " ", "%20", -1)
	jsonDataFromHttp := getResponseAsText(URL_WIKIPEDIA, &searchTerms)
	if jsonDataFromHttp == nil {
		return nil
	}
	result := getWikipediaResultFromJson(jsonDataFromHttp)
	if result == nil {
		return nil
	}
	return result
}

// --- Urban Dictionnary
func getUrbanDictionnaryResultFromJson(jsonDataFromHttp []byte) []string {
	var result urbanDictionnary
	err := json.Unmarshal(jsonDataFromHttp, &result)
	if err != nil {
		log.Println(err)
		return nil
	}
	if len(result.List) == 0 {
		return nil
	}
	returnValues := []string{result.List[0].Permalink}
	returnValues = append(returnValues, strings.Split(result.List[0].Definition, ". ")...)

	return returnValues
}

func getUrbanDictionnarySearchResult(searchTerms string) []string {
	searchTerms = strings.Replace(strings.Title(searchTerms), " ", "%20", -1)
	jsonDataFromHttp := getResponseAsText(URL_URBANDICTIONNARY, &searchTerms)
	if jsonDataFromHttp == nil {
		return nil
	}
	result := getUrbanDictionnaryResultFromJson(jsonDataFromHttp)
	if result == nil {
		return nil
	}
	return result
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013-2014, Jeremy Bingham (<jbingham@gmail.com>)
 *
 * 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 search provides search and index capabilities for goiardi.
package search

import (
	"fmt"
	"github.com/ctdk/goas/v2/logger"
	"github.com/ctdk/goiardi/client"
	"github.com/ctdk/goiardi/databag"
	"github.com/ctdk/goiardi/environment"
	"github.com/ctdk/goiardi/indexer"
	"github.com/ctdk/goiardi/node"
	"github.com/ctdk/goiardi/role"
	"github.com/ctdk/goiardi/util"
	"net/url"
	"reflect"
	"strings"
	"sort"
)

// Searcher is an interface that any search backend needs to implement. It's
// up to the Searcher to use whatever backend it wants to return the desired
// results.
type Searcher interface {
	Search(string, string, int, string, int) ([]map[string]interface{}, error)
	GetEndpoints() []string
}

type results struct {
	res     []map[string]interface{}
	sortKey string
}

func (r results) Len() int      { return len(r.res) }
func (r results) Swap(i, j int) { r.res[i], r.res[j] = r.res[j], r.res[i] }
func (r results) Less(i, j int) bool {
	ibase := r.res[i][r.sortKey]
	jbase := r.res[j][r.sortKey]
	ival := reflect.ValueOf(ibase)
	jval := reflect.ValueOf(jbase)
	if (!ival.IsValid() && !jval.IsValid()) || ival.IsValid() && !jval.IsValid() {
		return true
	} else if !ival.IsValid() && jval.IsValid() {
		return false
	}
	// don't try and compare different types for now. If this ever becomes
	// an issue in practice, though, it should be revisited
	if ival.Type() == jval.Type() {
		switch ibase.(type) {
		case int, int8, int32, int64:
			return ival.Int() < jval.Int()
		case uint, uint8, uint32, uint64:
			return ival.Uint() < jval.Uint()
		case float32, float64:
			return ival.Float() < jval.Float()
		case string:
			return ival.String() < jval.String()
		}
	}

	return false
}

// SolrQuery holds a parsed query and query chain to run against the index. It's
// called SolrQuery because the search queries use a subset of Solr's syntax.
type SolrQuery struct {
	queryChain Queryable
	idxName    string
	docs       map[string]*indexer.IdxDoc
}

// Search parses the given query string and search the given index for any
// matching results.
func Search(idx string, q string, rows int, sortOrder string, start int, partialData map[string]interface{}) ([]map[string]interface{}, error) {
	/* Eventually we'll want more prep. To start, look right in the index */
	query, qerr := url.QueryUnescape(q)
	if qerr != nil {
		return nil, qerr
	}
	qq := &Tokenizer{Buffer: query}
	qq.Init()
	if err := qq.Parse(); err != nil {
		return nil, err
	}
	qq.Execute()
	qchain := qq.Evaluate()
	d := make(map[string]*indexer.IdxDoc)
	solrQ := &SolrQuery{queryChain: qchain, idxName: idx, docs: d}

	_, err := solrQ.execute()
	if err != nil {
		return nil, err
	}
	qresults := solrQ.results()
	objs := getResults(idx, qresults)
	res := make([]map[string]interface{}, len(objs))
	for i, r := range objs {
		switch r := r.(type) {
		case *client.Client:
			jc := map[string]interface{}{
				"name":       r.Name,
				"chef_type":  r.ChefType,
				"json_class": r.JSONClass,
				"admin":      r.Admin,
				"public_key": r.PublicKey(),
				"validator":  r.Validator,
			}
			res[i] = jc
		default:
			res[i] = util.MapifyObject(r)
		}
	}

	/* If we're doing partial search, tease out the fields we want. */
	if partialData != nil {
		res, err = PartialSearchFormat(res, partialData)
		if err != nil {
			return nil, err
		}
		for x, z := range res {
			tmpRes := make(map[string]interface{})
			switch ro := objs[x].(type) {
			case *databag.DataBagItem:
				dbiURL := fmt.Sprintf("/data/%s/%s", ro.DataBagName, ro.RawData["id"].(string))
				tmpRes["url"] = util.CustomURL(dbiURL)
			default:
				tmpRes["url"] = util.ObjURL(objs[x].(util.GoiardiObj))
			}
			tmpRes["data"] = z

			res[x] = tmpRes
		}
	}

	// and at long last, sort
	ss := strings.Split(sortOrder, " ")
	sortKey := ss[0]
	if sortKey == "id" {
		sortKey = "name"
	}
	var ordering string
	if len(ss) > 1 {
		ordering = strings.ToLower(ss[1])
	} else {
		ordering = "asc"
	}
	sortResults := results{res, sortKey}
	if ordering == "desc" {
		sort.Sort(sort.Reverse(sortResults))
	} else {
		sort.Sort(sortResults)
	}
	res = sortResults.res

	end := start + rows
	if end > len(res) {
		end = len(res)
	}
	res = res[start:end]
	return res, nil
}

func (sq *SolrQuery) execute() (map[string]*indexer.IdxDoc, error) {
	s := sq.queryChain
	curOp := OpNotAnOp
	for s != nil {
		var r map[string]*indexer.IdxDoc
		var err error
		switch c := s.(type) {
		case *SubQuery:
			_ = c
			newq, nend, nerr := extractSubQuery(s)
			if nerr != nil {
				return nil, err
			}
			s = nend
			var d map[string]*indexer.IdxDoc
			if curOp == OpBinAnd {
				d = sq.docs
			} else {
				d = make(map[string]*indexer.IdxDoc)
			}
			nsq := &SolrQuery{queryChain: newq, idxName: sq.idxName, docs: d}
			r, err = nsq.execute()
		default:
			if curOp == OpBinAnd {
				r, err = s.SearchResults(sq.docs)
			} else {
				r, err = s.SearchIndex(sq.idxName)
			}
		}
		if err != nil {
			return nil, err
		}
		if len(sq.docs) == 0 || curOp == OpBinAnd { // nothing in place yet
			sq.docs = r
		} else if curOp == OpBinOr {
			for k, v := range r {
				sq.docs[k] = v
			}
		} else {
			logger.Debugf("Somehow we got to what should have been an impossible state with search")
		}

		curOp = s.Op()
		s = s.Next()
	}
	return sq.docs, nil
}

func extractSubQuery(s Queryable) (Queryable, Queryable, error) {
	n := 1
	prev := s
	s = s.Next()
	top := s
	for {
		switch q := s.(type) {
		case *SubQuery:
			if q.start {
				n++
			} else {
				n--
			}
		}
		if n == 0 {
			// we've followed this subquery chain to its end
			prev.SetNext(nil) // snip this chain off at the end
			return top, s, nil
		}
		prev = s
		s = s.Next()
		if s == nil {
			break
		}
	}
	err := fmt.Errorf("Yikes! Somehow we weren't able to finish the subquery.")
	return nil, nil, err
}

func (sq *SolrQuery) results() []string {
	results := make([]string, len(sq.docs))
	n := 0
	for k := range sq.docs {
		results[n] = k
		n++
	}
	return results
}

// GetEndpoints gets a list from the indexer of all the endpoints available to
// search, namely the defaults (node, role, client, environment) and any data
// bags.
func GetEndpoints() []string {
	endpoints := indexer.Endpoints()
	return endpoints
}

func getResults(variety string, toGet []string) []indexer.Indexable {
	var results []indexer.Indexable
	switch variety {
	case "node":
		for _, n := range toGet {
			if node, _ := node.Get(n); node != nil {
				results = append(results, node)
			}
		}
	case "role":
		for _, r := range toGet {
			if role, _ := role.Get(r); role != nil {
				results = append(results, role)
			}
		}
	case "client":
		for _, c := range toGet {
			if client, _ := client.Get(c); client != nil {
				results = append(results, client)
			}
		}
	case "environment":
		for _, e := range toGet {
			if environment, _ := environment.Get(e); environment != nil {
				results = append(results, environment)
			}
		}
	default: // It's a data bag
		/* These may require further processing later. */
		dbag, _ := databag.Get(variety)
		if dbag != nil {
			for _, k := range toGet {
				dbi, err := dbag.GetDBItem(k)
				if err != nil {
					// at least log the error for
					// now
					logger.Errorf(err.Error())
				}
				results = append(results, dbi)
			}
		}
	}
	return results
}

func PartialSearchFormat(results []map[string]interface{}, partialFormat map[string]interface{}) ([]map[string]interface{}, error) {
	/* regularize partial search keys */
	psearchKeys := make(map[string][]string, len(partialFormat))
	for k, v := range partialFormat {
		switch v := v.(type) {
		case []interface{}:
			psearchKeys[k] = make([]string, len(v))
			for i, j := range v {
				switch j := j.(type) {
				case string:
					psearchKeys[k][i] = j
				default:
					err := fmt.Errorf("Partial search key %s badly formatted: %T %v", k, j, j)
					return nil, err
				}
			}
		case []string:
			psearchKeys[k] = make([]string, len(v))
			for i, j := range v {
				psearchKeys[k][i] = j
			}
		default:
			err := fmt.Errorf("Partial search key %s badly formatted: %T %v", k, v, v)
			return nil, err
		}
	}
	newResults := make([]map[string]interface{}, len(results))

	for i, j := range results {
		newResults[i] = make(map[string]interface{})
		for key, vals := range psearchKeys {
			var pval interface{}
			/* The first key can either be top or first level.
			 * Annoying, but that's how it is. */
			if len(vals) > 0 {
				if step, found := j[vals[0]]; found {
					if len(vals) > 1 {
						pval = walk(step, vals[1:])
					} else {
						pval = step
					}
				} else {
					if len(vals) > 0 {
						// bear in mind precedence. We need to
						// overwrite later values with earlier
						// ones.
						keyRange := []string{"raw_data", "default", "default_attributes", "normal", "override", "override_attributes", "automatic"}
						for _, r := range keyRange {
							tval := walk(j[r], vals[0:])
							if tval != nil {
								switch pv := pval.(type) {
								case map[string]interface{}:
									// only merge if tval is also a map[string]interface{}
									switch tval := tval.(type) {
									case map[string]interface{}:
										for g, h := range tval {
											pv[g] = h
										}
										pval = pv
									}
								default:
									pval = tval
								}
							}
						}
					}
				}
			}
			newResults[i][key] = pval
		}
	}
	return newResults, nil
}

func walk(v interface{}, keys []string) interface{} {
	switch v := v.(type) {
	case map[string]interface{}:
		if _, found := v[keys[0]]; found {
			if len(keys) > 1 {
				return walk(v[keys[0]], keys[1:])
			}
			return v[keys[0]]
		}
		return nil
	case map[string]string:
		return v[keys[0]]
	case map[string][]string:
		return v[keys[0]]
	default:
		if len(keys) == 1 {
			return v
		}
		return nil
	}
}
<commit_msg>go fmt, update Searcher interface for things to come<commit_after>/*
 * Copyright (c) 2013-2014, Jeremy Bingham (<jbingham@gmail.com>)
 *
 * 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 search provides search and index capabilities for goiardi.
package search

import (
	"fmt"
	"github.com/ctdk/goas/v2/logger"
	"github.com/ctdk/goiardi/client"
	"github.com/ctdk/goiardi/databag"
	"github.com/ctdk/goiardi/environment"
	"github.com/ctdk/goiardi/indexer"
	"github.com/ctdk/goiardi/node"
	"github.com/ctdk/goiardi/role"
	"github.com/ctdk/goiardi/util"
	"net/url"
	"reflect"
	"sort"
	"strings"
)

// Searcher is an interface that any search backend needs to implement. It's
// up to the Searcher to use whatever backend it wants to return the desired
// results.
type Searcher interface {
	Search(string, string, int, string, int, map[string]interface{}) ([]map[string]interface{}, error)
	GetEndpoints() []string
}

type results struct {
	res     []map[string]interface{}
	sortKey string
}

func (r results) Len() int      { return len(r.res) }
func (r results) Swap(i, j int) { r.res[i], r.res[j] = r.res[j], r.res[i] }
func (r results) Less(i, j int) bool {
	ibase := r.res[i][r.sortKey]
	jbase := r.res[j][r.sortKey]
	ival := reflect.ValueOf(ibase)
	jval := reflect.ValueOf(jbase)
	if (!ival.IsValid() && !jval.IsValid()) || ival.IsValid() && !jval.IsValid() {
		return true
	} else if !ival.IsValid() && jval.IsValid() {
		return false
	}
	// don't try and compare different types for now. If this ever becomes
	// an issue in practice, though, it should be revisited
	if ival.Type() == jval.Type() {
		switch ibase.(type) {
		case int, int8, int32, int64:
			return ival.Int() < jval.Int()
		case uint, uint8, uint32, uint64:
			return ival.Uint() < jval.Uint()
		case float32, float64:
			return ival.Float() < jval.Float()
		case string:
			return ival.String() < jval.String()
		}
	}

	return false
}

// SolrQuery holds a parsed query and query chain to run against the index. It's
// called SolrQuery because the search queries use a subset of Solr's syntax.
type SolrQuery struct {
	queryChain Queryable
	idxName    string
	docs       map[string]*indexer.IdxDoc
}

// Search parses the given query string and search the given index for any
// matching results.
func Search(idx string, q string, rows int, sortOrder string, start int, partialData map[string]interface{}) ([]map[string]interface{}, error) {
	/* Eventually we'll want more prep. To start, look right in the index */
	query, qerr := url.QueryUnescape(q)
	if qerr != nil {
		return nil, qerr
	}
	qq := &Tokenizer{Buffer: query}
	qq.Init()
	if err := qq.Parse(); err != nil {
		return nil, err
	}
	qq.Execute()
	qchain := qq.Evaluate()
	d := make(map[string]*indexer.IdxDoc)
	solrQ := &SolrQuery{queryChain: qchain, idxName: idx, docs: d}

	_, err := solrQ.execute()
	if err != nil {
		return nil, err
	}
	qresults := solrQ.results()
	objs := getResults(idx, qresults)
	res := make([]map[string]interface{}, len(objs))
	for i, r := range objs {
		switch r := r.(type) {
		case *client.Client:
			jc := map[string]interface{}{
				"name":       r.Name,
				"chef_type":  r.ChefType,
				"json_class": r.JSONClass,
				"admin":      r.Admin,
				"public_key": r.PublicKey(),
				"validator":  r.Validator,
			}
			res[i] = jc
		default:
			res[i] = util.MapifyObject(r)
		}
	}

	/* If we're doing partial search, tease out the fields we want. */
	if partialData != nil {
		res, err = PartialSearchFormat(res, partialData)
		if err != nil {
			return nil, err
		}
		for x, z := range res {
			tmpRes := make(map[string]interface{})
			switch ro := objs[x].(type) {
			case *databag.DataBagItem:
				dbiURL := fmt.Sprintf("/data/%s/%s", ro.DataBagName, ro.RawData["id"].(string))
				tmpRes["url"] = util.CustomURL(dbiURL)
			default:
				tmpRes["url"] = util.ObjURL(objs[x].(util.GoiardiObj))
			}
			tmpRes["data"] = z

			res[x] = tmpRes
		}
	}

	// and at long last, sort
	ss := strings.Split(sortOrder, " ")
	sortKey := ss[0]
	if sortKey == "id" {
		sortKey = "name"
	}
	var ordering string
	if len(ss) > 1 {
		ordering = strings.ToLower(ss[1])
	} else {
		ordering = "asc"
	}
	sortResults := results{res, sortKey}
	if ordering == "desc" {
		sort.Sort(sort.Reverse(sortResults))
	} else {
		sort.Sort(sortResults)
	}
	res = sortResults.res

	end := start + rows
	if end > len(res) {
		end = len(res)
	}
	res = res[start:end]
	return res, nil
}

func (sq *SolrQuery) execute() (map[string]*indexer.IdxDoc, error) {
	s := sq.queryChain
	curOp := OpNotAnOp
	for s != nil {
		var r map[string]*indexer.IdxDoc
		var err error
		switch c := s.(type) {
		case *SubQuery:
			_ = c
			newq, nend, nerr := extractSubQuery(s)
			if nerr != nil {
				return nil, err
			}
			s = nend
			var d map[string]*indexer.IdxDoc
			if curOp == OpBinAnd {
				d = sq.docs
			} else {
				d = make(map[string]*indexer.IdxDoc)
			}
			nsq := &SolrQuery{queryChain: newq, idxName: sq.idxName, docs: d}
			r, err = nsq.execute()
		default:
			if curOp == OpBinAnd {
				r, err = s.SearchResults(sq.docs)
			} else {
				r, err = s.SearchIndex(sq.idxName)
			}
		}
		if err != nil {
			return nil, err
		}
		if len(sq.docs) == 0 || curOp == OpBinAnd { // nothing in place yet
			sq.docs = r
		} else if curOp == OpBinOr {
			for k, v := range r {
				sq.docs[k] = v
			}
		} else {
			logger.Debugf("Somehow we got to what should have been an impossible state with search")
		}

		curOp = s.Op()
		s = s.Next()
	}
	return sq.docs, nil
}

func extractSubQuery(s Queryable) (Queryable, Queryable, error) {
	n := 1
	prev := s
	s = s.Next()
	top := s
	for {
		switch q := s.(type) {
		case *SubQuery:
			if q.start {
				n++
			} else {
				n--
			}
		}
		if n == 0 {
			// we've followed this subquery chain to its end
			prev.SetNext(nil) // snip this chain off at the end
			return top, s, nil
		}
		prev = s
		s = s.Next()
		if s == nil {
			break
		}
	}
	err := fmt.Errorf("Yikes! Somehow we weren't able to finish the subquery.")
	return nil, nil, err
}

func (sq *SolrQuery) results() []string {
	results := make([]string, len(sq.docs))
	n := 0
	for k := range sq.docs {
		results[n] = k
		n++
	}
	return results
}

// GetEndpoints gets a list from the indexer of all the endpoints available to
// search, namely the defaults (node, role, client, environment) and any data
// bags.
func GetEndpoints() []string {
	endpoints := indexer.Endpoints()
	return endpoints
}

func getResults(variety string, toGet []string) []indexer.Indexable {
	var results []indexer.Indexable
	switch variety {
	case "node":
		for _, n := range toGet {
			if node, _ := node.Get(n); node != nil {
				results = append(results, node)
			}
		}
	case "role":
		for _, r := range toGet {
			if role, _ := role.Get(r); role != nil {
				results = append(results, role)
			}
		}
	case "client":
		for _, c := range toGet {
			if client, _ := client.Get(c); client != nil {
				results = append(results, client)
			}
		}
	case "environment":
		for _, e := range toGet {
			if environment, _ := environment.Get(e); environment != nil {
				results = append(results, environment)
			}
		}
	default: // It's a data bag
		/* These may require further processing later. */
		dbag, _ := databag.Get(variety)
		if dbag != nil {
			for _, k := range toGet {
				dbi, err := dbag.GetDBItem(k)
				if err != nil {
					// at least log the error for
					// now
					logger.Errorf(err.Error())
				}
				results = append(results, dbi)
			}
		}
	}
	return results
}

func PartialSearchFormat(results []map[string]interface{}, partialFormat map[string]interface{}) ([]map[string]interface{}, error) {
	/* regularize partial search keys */
	psearchKeys := make(map[string][]string, len(partialFormat))
	for k, v := range partialFormat {
		switch v := v.(type) {
		case []interface{}:
			psearchKeys[k] = make([]string, len(v))
			for i, j := range v {
				switch j := j.(type) {
				case string:
					psearchKeys[k][i] = j
				default:
					err := fmt.Errorf("Partial search key %s badly formatted: %T %v", k, j, j)
					return nil, err
				}
			}
		case []string:
			psearchKeys[k] = make([]string, len(v))
			for i, j := range v {
				psearchKeys[k][i] = j
			}
		default:
			err := fmt.Errorf("Partial search key %s badly formatted: %T %v", k, v, v)
			return nil, err
		}
	}
	newResults := make([]map[string]interface{}, len(results))

	for i, j := range results {
		newResults[i] = make(map[string]interface{})
		for key, vals := range psearchKeys {
			var pval interface{}
			/* The first key can either be top or first level.
			 * Annoying, but that's how it is. */
			if len(vals) > 0 {
				if step, found := j[vals[0]]; found {
					if len(vals) > 1 {
						pval = walk(step, vals[1:])
					} else {
						pval = step
					}
				} else {
					if len(vals) > 0 {
						// bear in mind precedence. We need to
						// overwrite later values with earlier
						// ones.
						keyRange := []string{"raw_data", "default", "default_attributes", "normal", "override", "override_attributes", "automatic"}
						for _, r := range keyRange {
							tval := walk(j[r], vals[0:])
							if tval != nil {
								switch pv := pval.(type) {
								case map[string]interface{}:
									// only merge if tval is also a map[string]interface{}
									switch tval := tval.(type) {
									case map[string]interface{}:
										for g, h := range tval {
											pv[g] = h
										}
										pval = pv
									}
								default:
									pval = tval
								}
							}
						}
					}
				}
			}
			newResults[i][key] = pval
		}
	}
	return newResults, nil
}

func walk(v interface{}, keys []string) interface{} {
	switch v := v.(type) {
	case map[string]interface{}:
		if _, found := v[keys[0]]; found {
			if len(keys) > 1 {
				return walk(v[keys[0]], keys[1:])
			}
			return v[keys[0]]
		}
		return nil
	case map[string]string:
		return v[keys[0]]
	case map[string][]string:
		return v[keys[0]]
	default:
		if len(keys) == 1 {
			return v
		}
		return nil
	}
}
<|endoftext|>


<commit_before>package search

import (
	"errors"
	"strings"
)

import "fmt"

//Result describes the results of a search
type Result struct {
	Hits    int
	Lines   []int
	Pattern string
	index   int //the current index i to iterate Lines
}

//NewSearch searches in a multiline string for lines that match the given pattern,
//it returns:
//* the number of hits (lines)
//* the line index
func NewSearch(text [][]rune, pattern string) (*Result, error) {
	if text != nil {
		sr := &Result{Pattern: pattern, index: -1}
		for i, l := range text {
			line := string(l)
			if strings.Contains(line, pattern) {
				sr.Hits++
				sr.Lines = append(sr.Lines, i)
			}
		}
		return sr, nil
	}
	return nil, errors.New("Nothing to search in an empty text.")
}

func (result *Result) String() string {
	if result.Hits > 0 {
		return fmt.Sprintf("Pattern %s found %d times", result.Pattern, result.Hits)
	}
	return "Pattern not found"
}

//InitialLine sets the position for iterating the search results on the first line
//that is either has the same value or is the closest from 0 to the given line number.
//So, for a result that found that lines (1, 3, 5) were a hit:
// * InitialLine(-1) will set the internal iteration index at 0 (the default starting index)
// * InitialLine(3) will set the internal iteration index at 1.
// * InitialLine(4) will set the internal iteration index at 1.
// * InitialLine(10) will set the internal iteration index at 2.
func (result *Result) InitialLine(lineNumber int) (int, error) {
	if result.Lines == nil {
		return -1, nohitsError()
	}
	candidate := 0
	for i, line := range result.Lines {
		if line < lineNumber {
			candidate = i
		} else {
			break
		}
	}
	result.index = candidate
	return candidate, nil
}

//NextLine returns the previous line while iterating the search results.
//So, for a result that found that lines (1, 3, 5) were a hit:
// *NextLine() should give 1
// *NextLine() should give 3
// *NextLine() should give 5
// *NextLine() should give 5
func (result *Result) NextLine() (int, error) {
	if result.Lines == nil {
		return -1, nohitsError()
	}
	if result.index < len(result.Lines)-1 {
		result.index++
	}
	return result.Lines[result.index], nil
}

//PreviousLine returns the previous line while iterating the search results.
//So, for a result that found that lines (1, 3, 5) were a hit:
// * NextLine() should give 1
// * NextLine() should give 3
// * PreviousLine() should give 1
// * PreviousLine() should give 1
func (result *Result) PreviousLine() (int, error) {
	if result.Lines == nil {
		return -1, nohitsError()
	}
	if result.index > 0 {
		result.index--
	}
	return result.Lines[result.index], nil
}

func nohitsError() error {
	return errors.New("Trying to iterate through the search result when there are no hits")
}
<commit_msg>Minor<commit_after>package search

import (
	"errors"
	"strings"
)

import "fmt"

//Result describes the results of a search
type Result struct {
	Hits    int
	Lines   []int
	Pattern string
	index   int //the current index i to iterate Lines
}

//NewSearch searches in a multiline string for lines that match the given pattern,
//it returns:
//* the number of hits (lines)
//* the line index
func NewSearch(text [][]rune, pattern string) (*Result, error) {
	if text != nil {
		sr := &Result{Pattern: pattern, index: -1}
		for i, l := range text {
			line := string(l)
			if strings.Contains(line, pattern) {
				sr.Hits++
				sr.Lines = append(sr.Lines, i)
			}
		}
		return sr, nil
	}
	return nil, errors.New("Nothing to search in an empty text.")
}

func (result *Result) String() string {
	if result.Hits > 0 {
		return fmt.Sprintf("Pattern %s found %d times", result.Pattern, result.Hits)
	}
	return fmt.Sprintf("Pattern %s not found", result.Pattern)
}

//InitialLine sets the position for iterating the search results on the first line
//that is either has the same value or is the closest from 0 to the given line number.
//So, for a result that found that lines (1, 3, 5) were a hit:
// * InitialLine(-1) will set the internal iteration index at 0 (the default starting index)
// * InitialLine(3) will set the internal iteration index at 1.
// * InitialLine(4) will set the internal iteration index at 1.
// * InitialLine(10) will set the internal iteration index at 2.
func (result *Result) InitialLine(lineNumber int) (int, error) {
	if result.Lines == nil {
		return -1, nohitsError()
	}
	candidate := 0
	for i, line := range result.Lines {
		if line < lineNumber {
			candidate = i
		} else {
			break
		}
	}
	result.index = candidate
	return candidate, nil
}

//NextLine returns the previous line while iterating the search results.
//So, for a result that found that lines (1, 3, 5) were a hit:
// *NextLine() should give 1
// *NextLine() should give 3
// *NextLine() should give 5
// *NextLine() should give 5
func (result *Result) NextLine() (int, error) {
	if result.Lines == nil {
		return -1, nohitsError()
	}
	if result.index < len(result.Lines)-1 {
		result.index++
	}
	return result.Lines[result.index], nil
}

//PreviousLine returns the previous line while iterating the search results.
//So, for a result that found that lines (1, 3, 5) were a hit:
// * NextLine() should give 1
// * NextLine() should give 3
// * PreviousLine() should give 1
// * PreviousLine() should give 1
func (result *Result) PreviousLine() (int, error) {
	if result.Lines == nil {
		return -1, nohitsError()
	}
	if result.index > 0 {
		result.index--
	}
	return result.Lines[result.index], nil
}

func nohitsError() error {
	return errors.New("Trying to iterate through the search result when there are no hits")
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	docker "github.com/fsouza/go-dockerclient"
)

type Volume struct {
	container *docker.APIContainers
	path      string
}

func (v *Volume) getBareFilename() string {
	str, err := getTrueName(v.container)
	if err != nil {
		log.Fatal(err.Error())
	}

	// remove leading backslash
	str = str[1:]

	// remove the "-volumes" suffix used to identify the container
	// as a data-only container in the first place
	if strings.HasSuffix(str, "-volumes") {
		str = str[0 : len(str)-7]
	}

	// Add the volume path with backslashes replaced by minuses.
	// Accidentally, this also give double minus
	// between the container name and the path.
	str += strings.Replace(v.path, "/", "-", -1)

	return str
}

func backupVolume(cli *docker.Client, directory string, v Volume, keepFailed bool) (string, error) {
	filename := v.getBareFilename() + ".tar"
	path := "/backup/" + filename
	hostpath := directory + filename

	user := strconv.Itoa(os.Geteuid())

	//cmd := "tar zcvf " + path + " " + v.path
	//log.Println(cmd)

	vols := make(map[string]struct{})
	vols["/backup/"] = struct{}{}

	binds := []string{
		directory + ":/backup/",
	}

	conf := &docker.Config{
		Cmd:         []string{"tar", "cvf", path, v.path},
		Image:       "busybox",
		User:        user,
		VolumesFrom: v.container.ID,
		Volumes:     vols,
	}
	hostconf := &docker.HostConfig{
		Binds:       binds,
		VolumesFrom: []string{v.container.ID},
	}
	opts := docker.CreateContainerOptions{
		Config:     conf,
		HostConfig: hostconf,
	}

	cont, err := cli.CreateContainer(opts)
	if err != nil {
		return "", err
	}

	if keepFailed == false {
		defer cli.RemoveContainer(docker.RemoveContainerOptions{ID: cont.ID, Force: true})
	}

	err = cli.StartContainer(cont.ID, hostconf)
	if err != nil {
		return "", err
	}

	retcode, err := cli.WaitContainer(cont.ID)
	if err != nil {
		os.Remove(hostpath)
		return "", err
	}

	if retcode != 0 {
		os.Remove(hostpath)
		str := fmt.Sprintf("Failed to create volume backup - tar return code is %d", retcode)
		return "", errors.New(str)
	}

	fileinfo, err := os.Stat(hostpath)
	if err != nil {
		return "", err
	}

	if false == fileinfo.Mode().IsRegular() {
		str := fmt.Sprintf("Failed to create volume backup - created file %s is not regular", hostpath)
		return "", errors.New(str)
	}

	if keepFailed == true {
		cli.RemoveContainer(docker.RemoveContainerOptions{ID: cont.ID, Force: true})
	}

	return hostpath, nil
}

//func createVolumeFilename
<commit_msg>make modular busybox cmd and use to to chown archives<commit_after>package main

import (
	"errors"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	docker "github.com/fsouza/go-dockerclient"
)

type Volume struct {
	container *docker.APIContainers
	path      string
}

func (v *Volume) getBareFilename() string {
	str, err := getTrueName(v.container)
	if err != nil {
		log.Fatal(err.Error())
	}

	// remove leading backslash
	str = str[1:]

	// remove the "-volumes" suffix used to identify the container
	// as a data-only container in the first place
	if strings.HasSuffix(str, "-volumes") {
		str = str[0 : len(str)-7]
	}

	// Add the volume path with backslashes replaced by minuses.
	// Accidentally, this also give double minus
	// between the container name and the path.
	str += strings.Replace(v.path, "/", "-", -1)

	return str
}

func backupVolume(cli *docker.Client, directory string, v Volume, keepFailed bool) (string, error) {
	filename := v.getBareFilename() + ".tar"
	path := "/backup/" + filename
	hostpath := directory + filename

	user := strconv.Itoa(os.Geteuid())

	binds := []string{
		directory + ":/backup/",
	}

	err := volumeBusybox(cli, []string{"tar", "cvf", path, v.path}, []string{v.container.ID}, binds, keepFailed)
	if err != nil {
		return "", err
	}

	err = volumeBusybox(cli, []string{"chown", user, path}, nil, binds, keepFailed)
	if err != nil {
		return "", err
	}

	fileinfo, err := os.Stat(hostpath)
	if err != nil {
		return "", err
	}

	if false == fileinfo.Mode().IsRegular() {
		str := fmt.Sprintf("Failed to create volume backup - created file %s is not regular", hostpath)
		return "", errors.New(str)
	}

	return hostpath, nil
}

func volumeBusybox(cli *docker.Client, cmd []string, volumesFrom []string, volumes []string, keepFailed bool) error {

	vols := make(map[string]struct{})

	for _, v := range volumes {
		point := strings.Split(v, ":")[1]
		vols[point] = struct{}{}
	}

	conf := &docker.Config{
		Cmd:     cmd,
		Image:   "busybox",
		Volumes: vols,
	}
	hostconf := &docker.HostConfig{
		Binds:       volumes,
		VolumesFrom: volumesFrom,
	}
	opts := docker.CreateContainerOptions{
		Config:     conf,
		HostConfig: hostconf,
	}

	cont, err := cli.CreateContainer(opts)
	if err != nil {
		return err
	}

	if keepFailed == false {
		defer cli.RemoveContainer(docker.RemoveContainerOptions{ID: cont.ID, Force: true})
	}

	err = cli.StartContainer(cont.ID, hostconf)
	if err != nil {
		return err
	}

	retcode, err := cli.WaitContainer(cont.ID)
	if err != nil {
		return err
	}

	if retcode != 0 {
		str := fmt.Sprintf("Command failed with code %d", retcode)
		return errors.New(str)
	}

	if keepFailed == true {
		cli.RemoveContainer(docker.RemoveContainerOptions{ID: cont.ID, Force: true})
	}

	return nil
}

//func createVolumeFilename
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"syscall"

	docker "github.com/fsouza/go-dockerclient"
)

type DockerContainer struct {
}

// based off of https://github.com/dotcloud/docker/blob/2a711d16e05b69328f2636f88f8eac035477f7e4/utils/utils.go
func parseHost(addr string) (string, string, error) {

	var (
		proto string
		host  string
		port  int
	)
	addr = strings.TrimSpace(addr)
	switch {
	case addr == "tcp://":
		return "", "", fmt.Errorf("Invalid bind address format: %s", addr)
	case strings.HasPrefix(addr, "unix://"):
		proto = "unix"
		addr = strings.TrimPrefix(addr, "unix://")
		if addr == "" {
			addr = "/var/run/docker.sock"
		}
	case strings.HasPrefix(addr, "tcp://"):
		proto = "tcp"
		addr = strings.TrimPrefix(addr, "tcp://")
	case strings.HasPrefix(addr, "fd://"):
		return "fd", addr, nil
	case addr == "":
		proto = "unix"
		addr = "/var/run/docker.sock"
	default:
		if strings.Contains(addr, "://") {
			return "", "", fmt.Errorf("Invalid bind address protocol: %s", addr)
		}
		proto = "tcp"
	}

	if proto != "unix" && strings.Contains(addr, ":") {
		hostParts := strings.Split(addr, ":")
		if len(hostParts) != 2 {
			return "", "", fmt.Errorf("Invalid bind address format: %s", addr)
		}
		if hostParts[0] != "" {
			host = hostParts[0]
		} else {
			host = "127.0.0.1"
		}

		if p, err := strconv.Atoi(hostParts[1]); err == nil && p != 0 {
			port = p
		} else {
			return "", "", fmt.Errorf("Invalid bind address format: %s", addr)
		}

	} else if proto == "tcp" && !strings.Contains(addr, ":") {
		return "", "", fmt.Errorf("Invalid bind address format: %s", addr)
	} else {
		host = addr
	}
	if proto == "unix" {
		return proto, host, nil

	}
	return proto, fmt.Sprintf("%s:%d", host, port), nil
}

func splitDockerImage(img string) (string, string, string) {
	index := 0
	repository := img
	var registry, tag string
	if strings.Contains(img, "/") {
		separator := strings.Index(img, "/")
		registry = img[index:separator]
		index = separator + 1
		repository = img[index:]
	}

	if strings.Contains(repository, ":") {
		separator := strings.Index(repository, ":")
		tag = repository[separator+1:]
		repository = repository[0:separator]
	}

	return registry, repository, tag
}

func newConn() (*httputil.ClientConn, error) {
	endpoint := getEndpoint()

	proto, addr, err := parseHost(endpoint)
	if err != nil {
		return nil, err
	}

	conn, err := net.Dial(proto, addr)
	if err != nil {
		return nil, err
	}
	return httputil.NewClientConn(conn, nil), nil
}

func getEvents() chan *Event {
	eventChan := make(chan *Event, 100)
	go func() {
		defer close(eventChan)

	restart:

		c, err := newConn()
		if err != nil {
			log.Printf("cannot connect to docker: %s\n", err)
			return
		}
		defer c.Close()

		req, err := http.NewRequest("GET", "/events", nil)
		if err != nil {
			log.Printf("bad request for events: %s\n", err)
			return
		}

		resp, err := c.Do(req)
		if err != nil {
			log.Printf("cannot connect to events endpoint: %s\n", err)
			return
		}
		defer resp.Body.Close()

		// handle signals to stop the socket
		sigChan := make(chan os.Signal, 1)
		signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
		go func() {
			for sig := range sigChan {
				log.Printf("received signal '%v', exiting\n", sig)

				c.Close()
				close(eventChan)
				os.Exit(0)
			}
		}()

		dec := json.NewDecoder(resp.Body)
		for {
			var event *Event
			if err := dec.Decode(&event); err != nil {
				if err == io.EOF {
					break
				}
				log.Printf("cannot decode json: %s\n", err)
				goto restart
			}
			eventChan <- event
		}
		log.Printf("closing event channel\n")
	}()
	return eventChan
}

func getContainers(client *docker.Client) ([]*RuntimeContainer, error) {

	apiContainers, err := client.ListContainers(docker.ListContainersOptions{
		All: false,
	})
	if err != nil {
		return nil, err
	}

	containers := []*RuntimeContainer{}
	for _, apiContainer := range apiContainers {
		container, err := client.InspectContainer(apiContainer.ID)
		if err != nil {
			log.Printf("error inspecting container: %s: %s\n", apiContainer.ID, err)
			continue
		}

		registry, repository, tag := splitDockerImage(container.Config.Image)
		runtimeContainer := &RuntimeContainer{
			ID: container.ID,
			Image: DockerImage{
				Registry:   registry,
				Repository: repository,
				Tag:        tag,
			},
			Name:      strings.TrimLeft(container.Name, "/"),
			Gateway:   container.NetworkSettings.Gateway,
			Addresses: []Address{},
			Env:       make(map[string]string),
		}
		for k, v := range container.NetworkSettings.Ports {
			address := Address{
				IP:   container.NetworkSettings.IPAddress,
				Port: k.Port(),
			}
			if len(v) > 0 {
				address.HostPort = v[0].HostPort
			}
			runtimeContainer.Addresses = append(runtimeContainer.Addresses,
				address)

		}

		for _, entry := range container.Config.Env {
			parts := strings.Split(entry, "=")
			runtimeContainer.Env[parts[0]] = parts[1]
		}

		containers = append(containers, runtimeContainer)
	}
	return containers, nil

}
<commit_msg>Don't exit out of watch loop if can't connect to docker<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"syscall"
	"time"

	docker "github.com/fsouza/go-dockerclient"
)

type DockerContainer struct {
}

// based off of https://github.com/dotcloud/docker/blob/2a711d16e05b69328f2636f88f8eac035477f7e4/utils/utils.go
func parseHost(addr string) (string, string, error) {

	var (
		proto string
		host  string
		port  int
	)
	addr = strings.TrimSpace(addr)
	switch {
	case addr == "tcp://":
		return "", "", fmt.Errorf("Invalid bind address format: %s", addr)
	case strings.HasPrefix(addr, "unix://"):
		proto = "unix"
		addr = strings.TrimPrefix(addr, "unix://")
		if addr == "" {
			addr = "/var/run/docker.sock"
		}
	case strings.HasPrefix(addr, "tcp://"):
		proto = "tcp"
		addr = strings.TrimPrefix(addr, "tcp://")
	case strings.HasPrefix(addr, "fd://"):
		return "fd", addr, nil
	case addr == "":
		proto = "unix"
		addr = "/var/run/docker.sock"
	default:
		if strings.Contains(addr, "://") {
			return "", "", fmt.Errorf("Invalid bind address protocol: %s", addr)
		}
		proto = "tcp"
	}

	if proto != "unix" && strings.Contains(addr, ":") {
		hostParts := strings.Split(addr, ":")
		if len(hostParts) != 2 {
			return "", "", fmt.Errorf("Invalid bind address format: %s", addr)
		}
		if hostParts[0] != "" {
			host = hostParts[0]
		} else {
			host = "127.0.0.1"
		}

		if p, err := strconv.Atoi(hostParts[1]); err == nil && p != 0 {
			port = p
		} else {
			return "", "", fmt.Errorf("Invalid bind address format: %s", addr)
		}

	} else if proto == "tcp" && !strings.Contains(addr, ":") {
		return "", "", fmt.Errorf("Invalid bind address format: %s", addr)
	} else {
		host = addr
	}
	if proto == "unix" {
		return proto, host, nil

	}
	return proto, fmt.Sprintf("%s:%d", host, port), nil
}

func splitDockerImage(img string) (string, string, string) {
	index := 0
	repository := img
	var registry, tag string
	if strings.Contains(img, "/") {
		separator := strings.Index(img, "/")
		registry = img[index:separator]
		index = separator + 1
		repository = img[index:]
	}

	if strings.Contains(repository, ":") {
		separator := strings.Index(repository, ":")
		tag = repository[separator+1:]
		repository = repository[0:separator]
	}

	return registry, repository, tag
}

func newConn() (*httputil.ClientConn, error) {
	endpoint := getEndpoint()

	proto, addr, err := parseHost(endpoint)
	if err != nil {
		return nil, err
	}

	conn, err := net.Dial(proto, addr)
	if err != nil {
		return nil, err
	}
	return httputil.NewClientConn(conn, nil), nil
}

func getEvents() chan *Event {
	eventChan := make(chan *Event, 100)
	go func() {
		defer close(eventChan)

	restart:

		c, err := newConn()
		if err != nil {
			log.Printf("cannot connect to docker: %s\n", err)
			time.Sleep(10 * time.Second)
			goto restart
		}
		defer c.Close()

		req, err := http.NewRequest("GET", "/events", nil)
		if err != nil {
			log.Printf("bad request for events: %s\n", err)
			c.Close()
			time.Sleep(10 * time.Second)
			goto restart
		}

		resp, err := c.Do(req)
		if err != nil {
			log.Printf("cannot connect to events endpoint: %s\n", err)
			c.Close()
			time.Sleep(10 * time.Second)
			goto restart
		}
		defer resp.Body.Close()

		// handle signals to stop the socket
		sigChan := make(chan os.Signal, 1)
		signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
		go func() {
			for sig := range sigChan {
				log.Printf("received signal '%v', exiting\n", sig)

				c.Close()
				close(eventChan)
				os.Exit(0)
			}
		}()

		dec := json.NewDecoder(resp.Body)
		for {
			var event *Event
			if err := dec.Decode(&event); err != nil {
				if err == io.EOF {
					break
				}
				log.Printf("cannot decode json: %s\n", err)
				goto restart
			}
			eventChan <- event
		}
		log.Printf("closing event channel\n")
	}()
	return eventChan
}

func getContainers(client *docker.Client) ([]*RuntimeContainer, error) {

	apiContainers, err := client.ListContainers(docker.ListContainersOptions{
		All: false,
	})
	if err != nil {
		return nil, err
	}

	containers := []*RuntimeContainer{}
	for _, apiContainer := range apiContainers {
		container, err := client.InspectContainer(apiContainer.ID)
		if err != nil {
			log.Printf("error inspecting container: %s: %s\n", apiContainer.ID, err)
			continue
		}

		registry, repository, tag := splitDockerImage(container.Config.Image)
		runtimeContainer := &RuntimeContainer{
			ID: container.ID,
			Image: DockerImage{
				Registry:   registry,
				Repository: repository,
				Tag:        tag,
			},
			Name:      strings.TrimLeft(container.Name, "/"),
			Gateway:   container.NetworkSettings.Gateway,
			Addresses: []Address{},
			Env:       make(map[string]string),
		}
		for k, v := range container.NetworkSettings.Ports {
			address := Address{
				IP:   container.NetworkSettings.IPAddress,
				Port: k.Port(),
			}
			if len(v) > 0 {
				address.HostPort = v[0].HostPort
			}
			runtimeContainer.Addresses = append(runtimeContainer.Addresses,
				address)

		}

		for _, entry := range container.Config.Env {
			parts := strings.Split(entry, "=")
			runtimeContainer.Env[parts[0]] = parts[1]
		}

		containers = append(containers, runtimeContainer)
	}
	return containers, nil

}
<|endoftext|>


<commit_before>package webdoc

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/zenazn/goji/web"
)

type Doc struct {
	Title       string            `json:"title,omitempty"`
	Description string            `json:"description,omitempty"`
	In          interface{}       `json:"in,omitempty"`
	Out         interface{}       `json:"out,omitempty"`
	FormValue   map[string]string `json:"form_value,omitempty"`
	URLParams   map[string]string `json:"url_params,omitempty"`
}

type Info struct {
	Routes        map[string]Info `json:"routes,omitempty"`
	Method        string          `json:"method,omitempty"`
	Documentation *Doc            `json:"documentation,omitempty"`
}

type Mux struct {
	webmux *web.Mux        // goji's web.Mux
	DocMap map[string]Info // Doc Map
}

//New creates a new webdoc.Mux
func New() *Mux {
	return &Mux{
		webmux: web.New(),
		DocMap: make(map[string]Info),
	}
}

//Mux returns the goji's *web.Mux
func (m *Mux) Mux() *web.Mux {
	return m.webmux
}

// muxMap adds the route to the webdoc.DocMap
func muxMap(m *Mux, method string, pattern web.PatternType, handler web.HandlerType, doc Doc) {
	sPattern := fmt.Sprintf("%+v", pattern)

	// now we need an string to work with.
	// this string should not contain leading or trailing slashes.
	workString := sPattern
	workString = strings.Trim(workString, "/")

	// it the workstring is empty we are on root.
	if workString == "" {
		workString = "/"
		// make the new root groop if it doesnt exists.
		if _, ok := m.DocMap[workString]; !ok {
			m.DocMap[workString] = Info{
				Method:        strings.ToUpper(method),
				Documentation: &doc,
			}
		}

		return
	}

	parts := strings.Split(workString, "/")
	_ = addParts(m.DocMap, parts, Info{
		Method:        strings.ToUpper(method),
		Documentation: &doc,
	})

}

// muxMap adds the nested routes to the webdoc.DocMap
func handleMap(m *Mux, submux *Mux, pattern web.PatternType) {
	sPattern := fmt.Sprintf("%+v", pattern)

	// check if the group string ends with `/*`
	// if it does, remove that part.
	sPattern = strings.TrimRight(sPattern, "/*")

	// now we need an string to work with.
	// this string should not contain leading or trailing slashes.
	workString := sPattern
	workString = strings.Trim(workString, "/")

	// it the workstring is empty we are on root.
	if workString == "" {
		workString = "/"
		// make the new root groop if it doesnt exists.
		if _, ok := m.DocMap[workString]; !ok {
			m.DocMap[workString] = Info{
				Routes: make(map[string]Info),
			}
		}

		// append the children
		for k, v := range submux.DocMap {
			m.DocMap[workString].Routes[k] = v
		}

		return
	}

	parts := strings.Split(workString, "/")
	target := addParts(m.DocMap, parts, Info{})

	for k, v := range submux.DocMap {
		target.Routes[k] = v
	}
}

func addParts(target map[string]Info, parts []string, info Info) Info {
	key := "/" + parts[0]

	if _, ok := target[key]; !ok {
		if len(parts[1:]) == 0 {
			target[key] = Info{
				Routes:        make(map[string]Info),
				Method:        info.Method,
				Documentation: info.Documentation,
			}
		} else {
			target[key] = Info{
				Routes: make(map[string]Info),
			}
		}
	}

	if len(parts[1:]) > 0 {
		return addParts(target[key].Routes, parts[1:], info)
	}

	return target[key]
}

func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	m.webmux.ServeHTTP(w, r)
}

func (m *Mux) ServeHTTPC(c web.C, w http.ResponseWriter, r *http.Request) {
	m.webmux.ServeHTTPC(c, w, r)
}

func (m *Mux) Use(middleware web.MiddlewareType) {
	m.webmux.Use(middleware)
}

func (m *Mux) Insert(middleware, before web.MiddlewareType) error {
	return m.webmux.Insert(middleware, before)
}

func (m *Mux) Abandon(middleware web.MiddlewareType) error {
	return m.webmux.Abandon(middleware)
}

func (m *Mux) Router(c *web.C, h http.Handler) http.Handler {
	return m.webmux.Router(c, h)
}

func (m *Mux) Handle(pattern web.PatternType, submux *Mux) {
	handleMap(m, submux, pattern)
	m.webmux.Handle(pattern, submux.Mux())
}

func (m *Mux) Connect(pattern web.PatternType, handler web.HandlerType, doc Doc) {
	muxMap(m, "connect", pattern, handler, doc)
	m.webmux.Connect(pattern, handler)
}

func (m *Mux) Delete(pattern web.PatternType, handler web.HandlerType, doc Doc) {
	muxMap(m, "delete", pattern, handler, doc)
	m.webmux.Delete(pattern, handler)
}

func (m *Mux) Get(pattern web.PatternType, handler web.HandlerType, doc Doc) {
	muxMap(m, "get", pattern, handler, doc)
	m.webmux.Get(pattern, handler)
}

func (m *Mux) Head(pattern web.PatternType, handler web.HandlerType, doc Doc) {
	muxMap(m, "head", pattern, handler, doc)
	m.webmux.Head(pattern, handler)
}

func (m *Mux) Options(pattern web.PatternType, handler web.HandlerType, doc Doc) {
	muxMap(m, "options", pattern, handler, doc)
	m.webmux.Options(pattern, handler)
}

func (m *Mux) Patch(pattern web.PatternType, handler web.HandlerType, doc Doc) {
	muxMap(m, "patch", pattern, handler, doc)
	m.webmux.Patch(pattern, handler)
}

func (m *Mux) Post(pattern web.PatternType, handler web.HandlerType, doc Doc) {
	muxMap(m, "post", pattern, handler, doc)
	m.webmux.Post(pattern, handler)
}

func (m *Mux) Put(pattern web.PatternType, handler web.HandlerType, doc Doc) {
	muxMap(m, "put", pattern, handler, doc)
	m.webmux.Put(pattern, handler)
}

func (m *Mux) Trace(pattern web.PatternType, handler web.HandlerType, doc Doc) {
	muxMap(m, "trace", pattern, handler, doc)
	m.webmux.Trace(pattern, handler)
}
<commit_msg>Allow multiple methods per route<commit_after>package webdoc

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/zenazn/goji/web"
)

type Doc struct {
	Title       string            `json:"title,omitempty"`
	Description string            `json:"description,omitempty"`
	In          interface{}       `json:"in,omitempty"`
	Out         interface{}       `json:"out,omitempty"`
	FormValue   map[string]string `json:"form_value,omitempty"`
	URLParams   map[string]string `json:"url_params,omitempty"`
}

type Info struct {
	Routes  map[string]Info `json:"routes,omitempty"`
	Methods map[string]*Doc `json:"methods,omitempty"`
}

type Mux struct {
	webmux *web.Mux        // goji's web.Mux
	DocMap map[string]Info // Doc Map
}

//New creates a new webdoc.Mux
func New() *Mux {
	return &Mux{
		webmux: web.New(),
		DocMap: make(map[string]Info),
	}
}

//Mux returns the goji's *web.Mux
func (m *Mux) Mux() *web.Mux {
	return m.webmux
}

// muxMap adds the route to the webdoc.DocMap
func muxMap(m *Mux, method string, pattern web.PatternType, handler web.HandlerType, doc *Doc) {
	sPattern := fmt.Sprintf("%+v", pattern)

	// now we need an string to work with.
	// this string should not contain leading or trailing slashes.
	workString := sPattern
	workString = strings.Trim(workString, "/")

	// it the workstring is empty we are on root.
	if workString == "" {
		workString = "/"
		// make the new root groop if it doesnt exists.
		if _, ok := m.DocMap[workString]; !ok {
			m.DocMap[workString] = Info{
				Methods: map[string]*Doc{strings.ToUpper(method): doc},
			}
		} else {
			ma := make(map[string]*Doc)
			for k, v := range m.DocMap[workString].Methods {
				ma[k] = v
			}
			ma[strings.ToUpper(method)] = doc

			m.DocMap[workString] = Info{
				Methods: ma,
			}
		}

		return
	}

	parts := strings.Split(workString, "/")

	// Add missing URL Param to doc.
	// This will save some typing.
	for _, v := range parts {
		if string(v[0]) == ":" {
			if doc == nil {
				doc = &Doc{
					URLParams: make(map[string]string),
				}
			}

			if doc.URLParams == nil {
				doc.URLParams = make(map[string]string)
			}

			if "" == doc.URLParams[v] {
				doc.URLParams[v[1:]] = "string"
			}
		}
	}

	_ = addParts(m.DocMap, parts, strings.ToUpper(method), doc)
}

// muxMap adds the nested routes to the webdoc.DocMap
func handleMap(m *Mux, submux *Mux, pattern web.PatternType) {
	sPattern := fmt.Sprintf("%+v", pattern)

	// check if the group string ends with `/*`
	// if it does, remove that part.
	sPattern = strings.TrimRight(sPattern, "/*")

	// now we need an string to work with.
	// this string should not contain leading or trailing slashes.
	workString := sPattern
	workString = strings.Trim(workString, "/")

	// it the workstring is empty we are on root.
	if workString == "" {
		workString = "/"

		// make the new root groop if it doesnt exists.
		if _, ok := m.DocMap[workString]; !ok {
			m.DocMap[workString] = Info{
				Routes: make(map[string]Info),
			}
		}

		// append the children
		for k, v := range submux.DocMap {
			m.DocMap[workString].Routes[k] = v
		}

		return
	}

	parts := strings.Split(workString, "/")
	target := addParts(m.DocMap, parts, "", nil)

	for k, v := range submux.DocMap {
		target.Routes[k] = v
	}
}

func addParts(target map[string]Info, parts []string, method string, doc *Doc) Info {
	key := "/" + parts[0]

	if _, ok := target[key]; !ok {
		if len(parts[1:]) == 0 {
			target[key] = Info{
				Routes:  make(map[string]Info),
				Methods: map[string]*Doc{strings.ToUpper(method): doc},
			}
		} else {
			target[key] = Info{
				Routes: make(map[string]Info),
			}
		}
	} else {
		if len(parts[1:]) == 0 {
			ma := make(map[string]*Doc)
			for k, v := range target[key].Methods {
				ma[k] = v
			}

			ma[method] = doc

			target[key] = Info{
				Routes:  make(map[string]Info),
				Methods: ma,
			}
		}
	}

	if len(parts[1:]) > 0 {
		return addParts(target[key].Routes, parts[1:], method, doc)
	}

	return target[key]
}

func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	m.webmux.ServeHTTP(w, r)
}

func (m *Mux) ServeHTTPC(c web.C, w http.ResponseWriter, r *http.Request) {
	m.webmux.ServeHTTPC(c, w, r)
}

func (m *Mux) Use(middleware web.MiddlewareType) {
	m.webmux.Use(middleware)
}

func (m *Mux) Insert(middleware, before web.MiddlewareType) error {
	return m.webmux.Insert(middleware, before)
}

func (m *Mux) Abandon(middleware web.MiddlewareType) error {
	return m.webmux.Abandon(middleware)
}

func (m *Mux) Router(c *web.C, h http.Handler) http.Handler {
	return m.webmux.Router(c, h)
}

func (m *Mux) Handle(pattern web.PatternType, submux *Mux) {
	handleMap(m, submux, pattern)
	m.webmux.Handle(pattern, submux.Mux())
}

func (m *Mux) Connect(pattern web.PatternType, handler web.HandlerType, doc *Doc) {
	muxMap(m, "connect", pattern, handler, doc)
	m.webmux.Connect(pattern, handler)
}

func (m *Mux) Delete(pattern web.PatternType, handler web.HandlerType, doc *Doc) {
	muxMap(m, "delete", pattern, handler, doc)
	m.webmux.Delete(pattern, handler)
}

func (m *Mux) Get(pattern web.PatternType, handler web.HandlerType, doc *Doc) {
	muxMap(m, "get", pattern, handler, doc)
	m.webmux.Get(pattern, handler)
}

func (m *Mux) Head(pattern web.PatternType, handler web.HandlerType, doc *Doc) {
	muxMap(m, "head", pattern, handler, doc)
	m.webmux.Head(pattern, handler)
}

func (m *Mux) Options(pattern web.PatternType, handler web.HandlerType, doc *Doc) {
	muxMap(m, "options", pattern, handler, doc)
	m.webmux.Options(pattern, handler)
}

func (m *Mux) Patch(pattern web.PatternType, handler web.HandlerType, doc *Doc) {
	muxMap(m, "patch", pattern, handler, doc)
	m.webmux.Patch(pattern, handler)
}

func (m *Mux) Post(pattern web.PatternType, handler web.HandlerType, doc *Doc) {
	muxMap(m, "post", pattern, handler, doc)
	m.webmux.Post(pattern, handler)
}

func (m *Mux) Put(pattern web.PatternType, handler web.HandlerType, doc *Doc) {
	muxMap(m, "put", pattern, handler, doc)
	m.webmux.Put(pattern, handler)
}

func (m *Mux) Trace(pattern web.PatternType, handler web.HandlerType, doc *Doc) {
	muxMap(m, "trace", pattern, handler, doc)
	m.webmux.Trace(pattern, handler)
}
<|endoftext|>


<commit_before>package main

var authTopic = &Topic{
	Name:        "auth",
	Description: "authentication (login/logout)",
}

var whoamiCmd = &Command{
	Topic:       "auth",
	Command:     "whoami",
	Description: "display your Heroku login",
	Default:     true,
	Help: `Example:

  $ heroku auth:whoami
	email@example.com`,
	Run: func(ctx *Context) {
		// don't use needsToken since this should fail if
		// not logged in. Should not show a login prompt.
		ctx.APIToken = apiToken()

		if ctx.APIToken == "" {
			Println("not logged in")
			Exit(100)
		}

		user := getUserFromToken(ctx.APIToken)
		if user == "" {
			Println("not logged in")
			Exit(100)
		}
		Println(user)
	},
}
<commit_msg>show warning if HEROKU_API_KEY is set<commit_after>package main

import "os"

var authTopic = &Topic{
	Name:        "auth",
	Description: "authentication (login/logout)",
}

var whoamiCmd = &Command{
	Topic:       "auth",
	Command:     "whoami",
	Description: "display your Heroku login",
	Default:     true,
	Help: `Example:

  $ heroku auth:whoami
	email@example.com`,
	Run: func(ctx *Context) {
		if os.Getenv("HEROKU_API_KEY") != "" {
			Warn("HEROKU_API_KEY is set")
		}

		// don't use needsToken since this should fail if
		// not logged in. Should not show a login prompt.
		ctx.APIToken = apiToken()

		if ctx.APIToken == "" {
			Println("not logged in")
			Exit(100)
		}

		user := getUserFromToken(ctx.APIToken)
		if user == "" {
			Println("not logged in")
			Exit(100)
		}
		Println(user)
	},
}
<|endoftext|>


<commit_before>package circuit

import (
	"container/ring"
	"sync/atomic"
	"time"
	"unsafe"
)

var (
	DefaultWindowTime    = time.Millisecond * 10000
	DefaultWindowBuckets = 10
)

type bucket struct {
	failure int64
	success int64
}

func (b *bucket) Reset() {
	b.failure = 0
	b.success = 0
}

func (b *bucket) Fail() {
	b.failure += 1
}

func (b *bucket) Success() {
	b.success += 1
}

type window struct {
	buckets     *ring.Ring
	bucketTime  time.Duration
	_lastAccess unsafe.Pointer
}

func NewWindow(windowTime time.Duration, windowBuckets int) *window {
	buckets := ring.New(windowBuckets)
	for i := 0; i < buckets.Len(); i++ {
		buckets.Value = &bucket{}
		buckets = buckets.Next()
	}

	bucketTime := time.Duration(windowTime.Nanoseconds() / int64(windowBuckets))
	window := &window{buckets: buckets, bucketTime: bucketTime}

	now := time.Now()
	atomic.StorePointer(&window._lastAccess, unsafe.Pointer(&now))

	return window
}

func (w *window) Fail() {
	var b *bucket
	b = w.buckets.Value.(*bucket)

	if time.Since(w.lastAccess()) > w.bucketTime {
		w.buckets = w.buckets.Next()
		b = w.buckets.Value.(*bucket)
		b.Reset()
	}

	b.Fail()
}

func (w *window) Success() {
	var b *bucket
	b = w.buckets.Value.(*bucket)

	if time.Since(w.lastAccess()) > w.bucketTime {
		w.buckets = w.buckets.Next()
		b = w.buckets.Value.(*bucket)
		b.Reset()
	}

	b.Success()
}

func (w *window) Failures() int64 {
	var failures int64
	w.buckets.Do(func(x interface{}) {
		b := x.(*bucket)
		failures += b.failure
	})
	return failures
}

func (w *window) Successes() int64 {
	var successes int64
	w.buckets.Do(func(x interface{}) {
		b := x.(*bucket)
		successes += b.success
	})
	return successes
}

func (w *window) Reset() {
	w.buckets.Do(func(x interface{}) {
		x.(*bucket).Reset()
	})
}

func (w *window) ErrorRate() float64 {
	var total int64
	var failures int64

	w.buckets.Do(func(x interface{}) {
		b := x.(*bucket)
		total += b.failure + b.success
		failures += b.failure
	})

	if total == 0 {
		return 0.0
	}

	return float64(failures) / float64(total)
}

func (w *window) lastAccess() time.Time {
	ptr := atomic.LoadPointer(&w._lastAccess)
	return *(*time.Time)(ptr)
}
<commit_msg>Add locks around window buckets<commit_after>package circuit

import (
	"container/ring"
	"sync"
	"time"
)

var (
	DefaultWindowTime    = time.Millisecond * 10000
	DefaultWindowBuckets = 10
)

type bucket struct {
	failure int64
	success int64
}

func (b *bucket) Reset() {
	b.failure = 0
	b.success = 0
}

func (b *bucket) Fail() {
	b.failure += 1
}

func (b *bucket) Success() {
	b.success += 1
}

type window struct {
	buckets    *ring.Ring
	bucketTime time.Duration
	bucketLock sync.RWMutex
	lastAccess time.Time
}

func NewWindow(windowTime time.Duration, windowBuckets int) *window {
	buckets := ring.New(windowBuckets)
	for i := 0; i < buckets.Len(); i++ {
		buckets.Value = &bucket{}
		buckets = buckets.Next()
	}

	bucketTime := time.Duration(windowTime.Nanoseconds() / int64(windowBuckets))
	return &window{buckets: buckets, bucketTime: bucketTime, lastAccess: time.Now()}
}

func (w *window) Fail() {
	var b *bucket
	w.bucketLock.Lock()
	defer w.bucketLock.Unlock()

	b = w.buckets.Value.(*bucket)

	if time.Since(w.lastAccess) > w.bucketTime {
		w.buckets = w.buckets.Next()
		b = w.buckets.Value.(*bucket)
		b.Reset()
	}
	w.lastAccess = time.Now()

	b.Fail()
}

func (w *window) Success() {
	var b *bucket
	w.bucketLock.Lock()
	defer w.bucketLock.Unlock()

	b = w.buckets.Value.(*bucket)

	if time.Since(w.lastAccess) > w.bucketTime {
		w.buckets = w.buckets.Next()
		b = w.buckets.Value.(*bucket)
		b.Reset()
	}
	w.lastAccess = time.Now()

	b.Success()
}

func (w *window) Failures() int64 {
	w.bucketLock.RLock()
	defer w.bucketLock.RUnlock()

	var failures int64
	w.buckets.Do(func(x interface{}) {
		b := x.(*bucket)
		failures += b.failure
	})
	return failures
}

func (w *window) Successes() int64 {
	w.bucketLock.RLock()
	defer w.bucketLock.RUnlock()

	var successes int64
	w.buckets.Do(func(x interface{}) {
		b := x.(*bucket)
		successes += b.success
	})
	return successes
}

func (w *window) Reset() {
	w.bucketLock.Lock()
	defer w.bucketLock.Unlock()

	w.buckets.Do(func(x interface{}) {
		x.(*bucket).Reset()
	})
}

func (w *window) ErrorRate() float64 {
	w.bucketLock.RLock()
	defer w.bucketLock.RUnlock()

	var total int64
	var failures int64

	w.buckets.Do(func(x interface{}) {
		b := x.(*bucket)
		total += b.failure + b.success
		failures += b.failure
	})

	if total == 0 {
		return 0.0
	}

	return float64(failures) / float64(total)
}
<|endoftext|>