<commit_before>package telegrambot

import (
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/go-telegram-bot-api/telegram-bot-api"
	"github.com/yamnikov-oleg/avamon-bot/monitor"

	_ "github.com/jinzhu/gorm/dialects/sqlite"
)

var (
	// Green clover
	okStatusEmoji = string([]rune{0x2618, 0xfe0f})
	// Red alarm light
	errorStatusEmoji = string([]rune{0x1f6a8})
)

func replaceHTML(input string) string {
	input = strings.Replace(input, "<", "<", -1)
	input = strings.Replace(input, ">", ">", -1)
	return input
}

type Bot struct {
	AdminNickname string
	DB            *TargetsDB
	TgBot         *tgbotapi.BotAPI
	Monitor       *monitor.Monitor
	sessionMap    map[int64]*session
}

func (b *Bot) formatStatusUpdate(target monitor.Target, status monitor.Status) string {
	var output string
	var sign string

	if status.Type == monitor.StatusOK {
		sign = strings.Repeat(okStatusEmoji, 10) + "\n"
	} else {
		sign = strings.Repeat(errorStatusEmoji, 10) + "\n"
	}

	output += sign
	output += fmt.Sprintf("%v: %v\n\n", replaceHTML(target.Title), status.Type)
	output += fmt.Sprintf("URL: %v\n", replaceHTML(target.URL))
	output += fmt.Sprintf("Response time: %v\n", status.ResponseTime)

	if status.Type != monitor.StatusOK {
		output += fmt.Sprintf("Error msg: %v\n", replaceHTML(status.Err.Error()))
	}
	if status.Type == monitor.StatusHTTPError {
		output += fmt.Sprintf("HTTP Status: %v %v\n", status.HTTPStatusCode, http.StatusText(status.HTTPStatusCode))
	}
	output += sign

	return output
}

func (b *Bot) SendMessage(chatID int64, message string) {
	msg := tgbotapi.NewMessage(chatID, message)
	msg.ParseMode = tgbotapi.ModeHTML
	msg.DisableWebPagePreview = true
	b.TgBot.Send(msg)
}

func (b *Bot) SendDialogMessage(replyTo *tgbotapi.Message, message string) {
	msg := tgbotapi.NewMessage(replyTo.Chat.ID, message)
	msg.ReplyToMessageID = replyTo.MessageID
	msg.ReplyMarkup = tgbotapi.ForceReply{
		ForceReply: true,
		Selective:  true,
	}
	msg.ParseMode = tgbotapi.ModeHTML
	msg.DisableWebPagePreview = true
	b.TgBot.Send(msg)
}

func (b *Bot) MonitorStart() {
	go func() {
		for upd := range b.Monitor.Updates {
			rec, err := b.DB.GetTarget(int(upd.Target.ID))
			if err != nil {
				fmt.Println(err)
				continue
			}
			b.SendMessage(
				rec.ChatID,
				b.formatStatusUpdate(upd.Target, upd.Status))
		}
	}()

	go func() {
		for err := range b.Monitor.Errors() {
			fmt.Println(err)
		}
	}()

	go b.Monitor.Run(nil)
}

type session struct {
	Stage  int
	Dialog dialog
}

type dialog interface {
	ContinueDialog(stepNumber int, update tgbotapi.Update, bot *tgbotapi.BotAPI) (int, bool)
}

type addNewTarget struct {
	Title string
	URL   string
	bot   *Bot
}

func (t *addNewTarget) ContinueDialog(stepNumber int, update tgbotapi.Update, bot *tgbotapi.BotAPI) (int, bool) {
	if stepNumber == 1 {
		t.bot.SendDialogMessage(
			update.Message,
			"Enter the title for the target. Send /cancel if you've changed your mind.")
		return 2, true
	}
	if stepNumber == 2 {
		t.Title = update.Message.Text
		t.bot.SendDialogMessage(update.Message, "Enter the url for the target")
		return 3, true
	}
	if stepNumber == 3 {
		if _, err := url.Parse(update.Message.Text); err != nil {
			t.bot.SendDialogMessage(update.Message, "Error while parsing url, please try again")
			return 3, true
		}
		t.URL = update.Message.Text
		err := t.bot.DB.CreateTarget(Record{
			ChatID: update.Message.Chat.ID,
			Title:  t.Title,
			URL:    t.URL,
		})
		if err != nil {
			t.bot.SendMessage(
				update.Message.Chat.ID,
				fmt.Sprintf(
					"Error while adding the target, please contact the administrator: %v",
					t.bot.AdminNickname))
			return 0, false
		}
		t.bot.SendMessage(update.Message.Chat.ID, "Target was successfully added")
		return 0, false
	}
	return 0, false
}

type deleteTarget struct {
	bot *Bot
}

func (t *deleteTarget) ContinueDialog(stepNumber int, update tgbotapi.Update, bot *tgbotapi.BotAPI) (int, bool) {
	if stepNumber == 1 {
		targs, err := t.bot.DB.GetCurrentTargets(update.Message.Chat.ID)
		if err != nil {
			t.bot.SendMessage(
				update.Message.Chat.ID,
				fmt.Sprintf(
					"Error while retrieving the targets, please contact the administrator: %v",
					t.bot.AdminNickname))
			return 0, false
		}
		if len(targs) == 0 {
			t.bot.SendMessage(update.Message.Chat.ID, "You have no targets added! Use /add to add one")
			return 0, false
		}
		var targetStrings []string
		targetStrings = append(targetStrings, "Enter the ID of a target to delete it. Send /cancel if you've changed your mind.\n")
		for _, target := range targs {
			targetStrings = append(
				targetStrings,
				fmt.Sprintf(
					"%v: %v",
					target.ID,
					replaceHTML(target.URL),
					replaceHTML(target.Title),
				),
			)
		}
		message := strings.Join(targetStrings, "\n")
		t.bot.SendDialogMessage(update.Message, message)
		return 2, true
	}
	if stepNumber == 2 {
		target, err := strconv.Atoi(update.Message.Text)
		if err != nil {
			t.bot.SendDialogMessage(update.Message, "Invalid ID, please try again")
			return 2, true
		}
		targetFromDB, err := t.bot.DB.GetTarget(target)
		if err != nil || targetFromDB.ChatID != update.Message.Chat.ID {
			t.bot.SendMessage(update.Message.Chat.ID, "No target with such ID found")
			return 0, false
		}
		err = t.bot.DB.DeleteTarget(target)
		if err != nil {
			t.bot.SendMessage(
				update.Message.Chat.ID,
				fmt.Sprintf(
					"Error while deleting the target, please contact the administrator: %v",
					t.bot.AdminNickname))
			return 0, false
		}
		t.bot.SendMessage(update.Message.Chat.ID, "Target was successfully deleted!")
		return 0, false
	}
	return 0, false
}

func (b *Bot) Dispatch(update *tgbotapi.Update) {
	if update.Message == nil {
		return
	}
	if _, ok := b.sessionMap[update.Message.Chat.ID]; !ok {
		b.sessionMap[update.Message.Chat.ID] = &session{}
		b.sessionMap[update.Message.Chat.ID].Stage = 1
		b.sessionMap[update.Message.Chat.ID].Dialog = nil
	}
	sess := b.sessionMap[update.Message.Chat.ID]
	if sess.Dialog != nil {
		var ok bool
		sess.Stage, ok = sess.Dialog.ContinueDialog(sess.Stage, *update, b.TgBot)
		if !ok {
			sess.Dialog = nil
		}
		return
	}
	if update.Message.Command() == "start" {
		b.SendMessage(
			update.Message.Chat.ID,
			"Hi!\nI'm a bot which can monitor sites' availability and notify you when a site goes down or up again.\n")
		return
	}
	if update.Message.Command() == "add" {
		b.StartDialog(update, &addNewTarget{
			bot: b,
		})
	}
	if update.Message.Command() == "targets" {
		targs, err := b.DB.GetCurrentTargets(update.Message.Chat.ID)
		if err != nil {
			b.SendMessage(
				update.Message.Chat.ID,
				fmt.Sprintf(
					"Error while retrieving the targets, please contact the administrator: %v",
					b.AdminNickname))
			return
		}
		if len(targs) == 0 {
			b.SendMessage(update.Message.Chat.ID, "No targets! Use /add to add one.")
			return
		}
		var targetStrings []string
		for _, target := range targs {
			status, ok, err := b.Monitor.StatusStore.GetStatus(target.ToTarget())
			if err != nil {
				b.SendMessage(
					update.Message.Chat.ID,
					fmt.Sprintf(
						"Error while retrieving the target's status, please contact the administrator: %v",
						b.AdminNickname))
				continue
			}

			var header string
			header = fmt.Sprintf(
				"%v",
				replaceHTML(target.URL), replaceHTML(target.Title))

			var statusText string
			if ok {
				var emoji string
				if status.Type == monitor.StatusOK {
					emoji = okStatusEmoji
				} else {
					emoji = errorStatusEmoji
				}

				statusText = fmt.Sprintf(
					"%v %v (%v ms)",
					emoji, status.Type, int64(status.ResponseTime/time.Millisecond))
			} else {
				statusText = "N/A"
			}

			targetStrings = append(
				targetStrings, fmt.Sprintf("%v: %v", header, statusText))
		}
		message := strings.Join(targetStrings, "\n")
		b.SendMessage(update.Message.Chat.ID, message)
		return
	}
	if update.Message.Command() == "delete" {
		b.StartDialog(update, &deleteTarget{
			bot: b,
		})
	}
	if update.Message.Command() == "cancel" {
		if sess.Dialog != nil {
			sess.Dialog = nil
			sess.Stage = 0
			b.SendMessage(update.Message.Chat.ID, "Action has been canceled")
		} else {
			b.SendMessage(update.Message.Chat.ID, "No action in process")
		}
		return
	}
}

func (b *Bot) StartDialog(update *tgbotapi.Update, dialog dialog) {
	var ok bool
	b.sessionMap[update.Message.Chat.ID].Dialog = dialog
	b.sessionMap[update.Message.Chat.ID].Stage, ok = dialog.ContinueDialog(1, *update, b.TgBot)
	if !ok {
		b.sessionMap[update.Message.Chat.ID].Dialog = nil
	}
	return
}

func (b *Bot) Run() error {
	b.sessionMap = map[int64]*session{}

	u := tgbotapi.NewUpdate(0)
	u.Timeout = 0

	updates, err := b.TgBot.GetUpdatesChan(u)
	if err != nil {
		return err
	}

	for update := range updates {
		b.Dispatch(&update)
	}

	return nil
}
<commit_msg>Make /cancel command top-priority in the dispatching switch<commit_after>package telegrambot

import (
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/go-telegram-bot-api/telegram-bot-api"
	"github.com/yamnikov-oleg/avamon-bot/monitor"

	_ "github.com/jinzhu/gorm/dialects/sqlite"
)

var (
	// Green clover
	okStatusEmoji = string([]rune{0x2618, 0xfe0f})
	// Red alarm light
	errorStatusEmoji = string([]rune{0x1f6a8})
)

func replaceHTML(input string) string {
	input = strings.Replace(input, "<", "<", -1)
	input = strings.Replace(input, ">", ">", -1)
	return input
}

type Bot struct {
	AdminNickname string
	DB            *TargetsDB
	TgBot         *tgbotapi.BotAPI
	Monitor       *monitor.Monitor
	sessionMap    map[int64]*session
}

func (b *Bot) formatStatusUpdate(target monitor.Target, status monitor.Status) string {
	var output string
	var sign string

	if status.Type == monitor.StatusOK {
		sign = strings.Repeat(okStatusEmoji, 10) + "\n"
	} else {
		sign = strings.Repeat(errorStatusEmoji, 10) + "\n"
	}

	output += sign
	output += fmt.Sprintf("%v: %v\n\n", replaceHTML(target.Title), status.Type)
	output += fmt.Sprintf("URL: %v\n", replaceHTML(target.URL))
	output += fmt.Sprintf("Response time: %v\n", status.ResponseTime)

	if status.Type != monitor.StatusOK {
		output += fmt.Sprintf("Error msg: %v\n", replaceHTML(status.Err.Error()))
	}
	if status.Type == monitor.StatusHTTPError {
		output += fmt.Sprintf("HTTP Status: %v %v\n", status.HTTPStatusCode, http.StatusText(status.HTTPStatusCode))
	}
	output += sign

	return output
}

func (b *Bot) SendMessage(chatID int64, message string) {
	msg := tgbotapi.NewMessage(chatID, message)
	msg.ParseMode = tgbotapi.ModeHTML
	msg.DisableWebPagePreview = true
	b.TgBot.Send(msg)
}

func (b *Bot) SendDialogMessage(replyTo *tgbotapi.Message, message string) {
	msg := tgbotapi.NewMessage(replyTo.Chat.ID, message)
	msg.ReplyToMessageID = replyTo.MessageID
	msg.ReplyMarkup = tgbotapi.ForceReply{
		ForceReply: true,
		Selective:  true,
	}
	msg.ParseMode = tgbotapi.ModeHTML
	msg.DisableWebPagePreview = true
	b.TgBot.Send(msg)
}

func (b *Bot) MonitorStart() {
	go func() {
		for upd := range b.Monitor.Updates {
			rec, err := b.DB.GetTarget(int(upd.Target.ID))
			if err != nil {
				fmt.Println(err)
				continue
			}
			b.SendMessage(
				rec.ChatID,
				b.formatStatusUpdate(upd.Target, upd.Status))
		}
	}()

	go func() {
		for err := range b.Monitor.Errors() {
			fmt.Println(err)
		}
	}()

	go b.Monitor.Run(nil)
}

type session struct {
	Stage  int
	Dialog dialog
}

type dialog interface {
	ContinueDialog(stepNumber int, update tgbotapi.Update, bot *tgbotapi.BotAPI) (int, bool)
}

type addNewTarget struct {
	Title string
	URL   string
	bot   *Bot
}

func (t *addNewTarget) ContinueDialog(stepNumber int, update tgbotapi.Update, bot *tgbotapi.BotAPI) (int, bool) {
	if stepNumber == 1 {
		t.bot.SendDialogMessage(
			update.Message,
			"Enter the title for the target. Send /cancel if you've changed your mind.")
		return 2, true
	}
	if stepNumber == 2 {
		t.Title = update.Message.Text
		t.bot.SendDialogMessage(update.Message, "Enter the url for the target")
		return 3, true
	}
	if stepNumber == 3 {
		if _, err := url.Parse(update.Message.Text); err != nil {
			t.bot.SendDialogMessage(update.Message, "Error while parsing url, please try again")
			return 3, true
		}
		t.URL = update.Message.Text
		err := t.bot.DB.CreateTarget(Record{
			ChatID: update.Message.Chat.ID,
			Title:  t.Title,
			URL:    t.URL,
		})
		if err != nil {
			t.bot.SendMessage(
				update.Message.Chat.ID,
				fmt.Sprintf(
					"Error while adding the target, please contact the administrator: %v",
					t.bot.AdminNickname))
			return 0, false
		}
		t.bot.SendMessage(update.Message.Chat.ID, "Target was successfully added")
		return 0, false
	}
	return 0, false
}

type deleteTarget struct {
	bot *Bot
}

func (t *deleteTarget) ContinueDialog(stepNumber int, update tgbotapi.Update, bot *tgbotapi.BotAPI) (int, bool) {
	if stepNumber == 1 {
		targs, err := t.bot.DB.GetCurrentTargets(update.Message.Chat.ID)
		if err != nil {
			t.bot.SendMessage(
				update.Message.Chat.ID,
				fmt.Sprintf(
					"Error while retrieving the targets, please contact the administrator: %v",
					t.bot.AdminNickname))
			return 0, false
		}
		if len(targs) == 0 {
			t.bot.SendMessage(update.Message.Chat.ID, "You have no targets added! Use /add to add one")
			return 0, false
		}
		var targetStrings []string
		targetStrings = append(targetStrings, "Enter the ID of a target to delete it. Send /cancel if you've changed your mind.\n")
		for _, target := range targs {
			targetStrings = append(
				targetStrings,
				fmt.Sprintf(
					"%v: %v",
					target.ID,
					replaceHTML(target.URL),
					replaceHTML(target.Title),
				),
			)
		}
		message := strings.Join(targetStrings, "\n")
		t.bot.SendDialogMessage(update.Message, message)
		return 2, true
	}
	if stepNumber == 2 {
		target, err := strconv.Atoi(update.Message.Text)
		if err != nil {
			t.bot.SendDialogMessage(update.Message, "Invalid ID, please try again")
			return 2, true
		}
		targetFromDB, err := t.bot.DB.GetTarget(target)
		if err != nil || targetFromDB.ChatID != update.Message.Chat.ID {
			t.bot.SendMessage(update.Message.Chat.ID, "No target with such ID found")
			return 0, false
		}
		err = t.bot.DB.DeleteTarget(target)
		if err != nil {
			t.bot.SendMessage(
				update.Message.Chat.ID,
				fmt.Sprintf(
					"Error while deleting the target, please contact the administrator: %v",
					t.bot.AdminNickname))
			return 0, false
		}
		t.bot.SendMessage(update.Message.Chat.ID, "Target was successfully deleted!")
		return 0, false
	}
	return 0, false
}

func (b *Bot) Dispatch(update *tgbotapi.Update) {
	if update.Message == nil {
		return
	}
	if _, ok := b.sessionMap[update.Message.Chat.ID]; !ok {
		b.sessionMap[update.Message.Chat.ID] = &session{}
		b.sessionMap[update.Message.Chat.ID].Stage = 1
		b.sessionMap[update.Message.Chat.ID].Dialog = nil
	}
	sess := b.sessionMap[update.Message.Chat.ID]
	if update.Message.Command() == "cancel" {
		if sess.Dialog != nil {
			sess.Dialog = nil
			sess.Stage = 0
			b.SendMessage(update.Message.Chat.ID, "Action has been canceled")
		} else {
			b.SendMessage(update.Message.Chat.ID, "No action in process")
		}
		return
	}
	if sess.Dialog != nil {
		var ok bool
		sess.Stage, ok = sess.Dialog.ContinueDialog(sess.Stage, *update, b.TgBot)
		if !ok {
			sess.Dialog = nil
		}
		return
	}
	if update.Message.Command() == "start" {
		b.SendMessage(
			update.Message.Chat.ID,
			"Hi!\nI'm a bot which can monitor sites' availability and notify you when a site goes down or up again.\n")
		return
	}
	if update.Message.Command() == "add" {
		b.StartDialog(update, &addNewTarget{
			bot: b,
		})
	}
	if update.Message.Command() == "targets" {
		targs, err := b.DB.GetCurrentTargets(update.Message.Chat.ID)
		if err != nil {
			b.SendMessage(
				update.Message.Chat.ID,
				fmt.Sprintf(
					"Error while retrieving the targets, please contact the administrator: %v",
					b.AdminNickname))
			return
		}
		if len(targs) == 0 {
			b.SendMessage(update.Message.Chat.ID, "No targets! Use /add to add one.")
			return
		}
		var targetStrings []string
		for _, target := range targs {
			status, ok, err := b.Monitor.StatusStore.GetStatus(target.ToTarget())
			if err != nil {
				b.SendMessage(
					update.Message.Chat.ID,
					fmt.Sprintf(
						"Error while retrieving the target's status, please contact the administrator: %v",
						b.AdminNickname))
				continue
			}

			var header string
			header = fmt.Sprintf(
				"%v",
				replaceHTML(target.URL), replaceHTML(target.Title))

			var statusText string
			if ok {
				var emoji string
				if status.Type == monitor.StatusOK {
					emoji = okStatusEmoji
				} else {
					emoji = errorStatusEmoji
				}

				statusText = fmt.Sprintf(
					"%v %v (%v ms)",
					emoji, status.Type, int64(status.ResponseTime/time.Millisecond))
			} else {
				statusText = "N/A"
			}

			targetStrings = append(
				targetStrings, fmt.Sprintf("%v: %v", header, statusText))
		}
		message := strings.Join(targetStrings, "\n")
		b.SendMessage(update.Message.Chat.ID, message)
		return
	}
	if update.Message.Command() == "delete" {
		b.StartDialog(update, &deleteTarget{
			bot: b,
		})
	}
}

func (b *Bot) StartDialog(update *tgbotapi.Update, dialog dialog) {
	var ok bool
	b.sessionMap[update.Message.Chat.ID].Dialog = dialog
	b.sessionMap[update.Message.Chat.ID].Stage, ok = dialog.ContinueDialog(1, *update, b.TgBot)
	if !ok {
		b.sessionMap[update.Message.Chat.ID].Dialog = nil
	}
	return
}

func (b *Bot) Run() error {
	b.sessionMap = map[int64]*session{}

	u := tgbotapi.NewUpdate(0)
	u.Timeout = 0

	updates, err := b.TgBot.GetUpdatesChan(u)
	if err != nil {
		return err
	}

	for update := range updates {
		b.Dispatch(&update)
	}

	return nil
}
<|endoftext|>


<commit_before>package esa

import (
	"fmt"
	"net/url"
	"strconv"
	"strings"

	"github.com/kyokomi/slackbot/plugins"
	"github.com/upamune/go-esa/esa"
)

var urlMarksReplacer = strings.NewReplacer("<", "", ">", "")

type plugin struct {
	teamName  string
	esaClient *esa.Client
}

// NewPlugin esa plugin
func NewPlugin(teamName, token string) plugins.BotMessagePlugin {
	return &plugin{
		teamName:  teamName,
		esaClient: esa.NewClient(token),
	}
}

// CheckMessage esa.io url is ok
func (p *plugin) CheckMessage(_ plugins.BotEvent, message string) (bool, string) {
	// esaのURLが見つかったら1件目を返す
	fields := plugins.DefaultUtils.QuotationOrSpaceFields(message)
	for _, val := range fields {
		u, err := url.Parse(urlMarksReplacer.Replace(val))
		if err != nil || !strings.HasSuffix(u.Host, "esa.io") {
			continue
		}
		return true, u.String()
	}
	return false, message
}

// DoAction is replay url detail message
func (p *plugin) DoAction(event plugins.BotEvent, message string) bool {
	u, _ := url.Parse(message)

	var postNumber int
	paths := strings.Split(u.Path, "/")
	for i := range paths {
		if paths[i] == "posts" && len(paths) >= i+1 {
			postNumber, _ = strconv.Atoi(paths[i+1])
			break
		}
	}
	resp, err := p.esaClient.Post.GetPost(p.teamName, postNumber)
	if err != nil {
		event.Reply(fmt.Sprintf("GetPost error %s", err.Error()))
		return true
	}
	event.Reply(fmt.Sprintf("```\n%s\n```", resp.FullName+"\n"+resp.CreatedAt+"\n\n"+resp.BodyMd))
	return true // next ok
}

// Help print help
func (p *plugin) Help() string {
	return `esa:
	URLを貼ると詳細を展開します
	`
}

var _ plugins.BotMessagePlugin = (*plugin)(nil)
<commit_msg>Fix esa_test.go<commit_after>package esa

import (
	"fmt"
	"net/url"
	"strconv"
	"strings"

	"github.com/kyokomi/slackbot/plugins"
	"github.com/upamune/go-esa/esa"
)

var urlMarksReplacer = strings.NewReplacer("<", "", ">", "")

type plugin struct {
	teamName  string
	esaClient *esa.Client
}

// NewPlugin esa plugin
func NewPlugin(teamName, token string) plugins.BotMessagePlugin {
	return &plugin{
		teamName:  teamName,
		esaClient: esa.NewClient(token),
	}
}

// CheckMessage esa.io url is ok
func (p *plugin) CheckMessage(_ plugins.BotEvent, message string) (bool, string) {
	// esaのURLが見つかったら1件目を返す
	fields := plugins.DefaultUtils.QuotationOrSpaceFields(message)
	for _, val := range fields {
		u, err := url.Parse(urlMarksReplacer.Replace(val))
		if err != nil || !strings.HasSuffix(u.Host, "esa.io") {
			continue
		}
		return true, u.String()
	}
	return false, message
}

// DoAction is replay url detail message
func (p *plugin) DoAction(event plugins.BotEvent, message string) bool {
	u, err := url.Parse(message)
	if err != nil {
		event.Reply(fmt.Sprintf("url.Parse error %s", err.Error()))
		return true
	}

	var postNumber int
	paths := strings.Split(u.Path, "/")
	for i := range paths {
		if paths[i] == "posts" && len(paths) >= i+1 {
			postNumber, _ = strconv.Atoi(paths[i+1])
			break
		}
	}
	resp, err := p.esaClient.Post.GetPost(p.teamName, postNumber)
	if err != nil {
		event.Reply(fmt.Sprintf("GetPost error %s", err.Error()))
		return true
	}
	event.Reply(fmt.Sprintf("```\n%s\n```", resp.FullName+"\n"+resp.CreatedAt+"\n\n"+resp.BodyMd))
	return true // next ok
}

// Help print help
func (p *plugin) Help() string {
	return `esa:
	URLを貼ると詳細を展開します
	`
}

var _ plugins.BotMessagePlugin = (*plugin)(nil)
<|endoftext|>


<commit_before>// This file is subject to a 1-clause BSD license.
// Its contents can be found in the enclosed LICENSE file.

// This plugin detects webpage urls embedded in incoming messages.
// It extracts the urls, fetches their contents from the web and finds the
// page title element. The title is then posted to the channel/user from wence
// the message came.
package url
<commit_msg>Amends documentation for URL plugin.<commit_after>// This file is subject to a 1-clause BSD license.
// Its contents can be found in the enclosed LICENSE file.

// This plugin detects webpage urls embedded in incoming messages.
// It extracts the urls, fetches their contents from the web and finds the
// page title element. The title is then posted to the channel/user from wence
// the message came.
//
// This plugin has no commands. It simply hooks into PRIVMSG inputs
// and scans them for URLs.
package url
<|endoftext|>


<commit_before>// Copyright 2013 errnoh. All rights reserved.
// Use of this source code is governed by a BSD-style (2-Clause)
// license that can be found in the LICENSE file.

package main

import (
	_ "bitbucket.org/errnoh/wde.canvas"
	"fmt"
	"github.com/skelterjohn/go.wde"
	"image"
	"image/color"
	"image/draw"
	"math/rand"
	"time"
)

const (
	WIDTH, HEIGHT = 600, 600
)

var (
	mousex, mousey int
	posx, posy     int
	radius         int
	r, g, b, a     uint8

	dw   wde.Window
	done = make(chan struct{})
)

func main() {
	go run()
	wde.Run()
}

func run() {
	var (
		err error
	)
	radius = 10
	r, g, b, a = 0, 0, 170, 255

	dw, err = wde.NewWindow(WIDTH, HEIGHT)
	if err != nil {
		fmt.Println(err)
		return
	}
	dw.Show()

	events := dw.EventChan()
	go listen(events)
	render()
}

func listen(c <-chan interface{}) {
loop:
	for ev := range c {
		switch e := ev.(type) {
		case wde.MouseDownEvent:
			if e.Which == 8 {
				radius++
			} else if e.Which == 16 {
				radius--
			} else {
				randomize()
			}
		case wde.MouseMovedEvent:
			mousex, mousey = e.Where.X, e.Where.Y
		case wde.CloseEvent:
			fmt.Println("closed")
			dw.Close()
			break loop
		}
	}
	done <- struct{}{}
}

func render() {
	for {
		draw.DrawMask(dw.Screen(), dw.Screen().Bounds(), &image.Uniform{color.RGBA{r, g, b, a}}, image.ZP, &circle{image.Point{mousex, mousey}, radius}, image.ZP, draw.Over)
		dw.FlushImage()
		select {
		case <-time.After(time.Second / 25):
		case <-done:
			return
		}
	}
}

func randomize() {
	r, g, b = uint8(rand.Uint32()), uint8(rand.Uint32()), uint8(rand.Uint32())
}

type circle struct {
	p image.Point
	r int
}

func (c *circle) ColorModel() color.Model {
	return color.AlphaModel
}

func (c *circle) Bounds() image.Rectangle {
	return image.Rect(c.p.X-c.r, c.p.Y-c.r, c.p.X+c.r, c.p.Y+c.r)
}

func (c *circle) At(x, y int) color.Color {
	xx, yy, rr := float64(x-c.p.X)+0.5, float64(y-c.p.Y)+0.5, float64(c.r)
	if xx*xx+yy*yy < rr*rr {
		return color.Alpha{255}
	}
	return color.Alpha{0}
}
<commit_msg>fixed correct import path for example<commit_after>// Copyright 2013 errnoh. All rights reserved.
// Use of this source code is governed by a BSD-style (2-Clause)
// license that can be found in the LICENSE file.

package main

import (
	_ "github.com/errnoh/wde.canvas"
	"fmt"
	"github.com/skelterjohn/go.wde"
	"image"
	"image/color"
	"image/draw"
	"math/rand"
	"time"
)

const (
	WIDTH, HEIGHT = 600, 600
)

var (
	mousex, mousey int
	posx, posy     int
	radius         int
	r, g, b, a     uint8

	dw   wde.Window
	done = make(chan struct{})
)

func main() {
	go run()
	wde.Run()
}

func run() {
	var (
		err error
	)
	radius = 10
	r, g, b, a = 0, 0, 170, 255

	dw, err = wde.NewWindow(WIDTH, HEIGHT)
	if err != nil {
		fmt.Println(err)
		return
	}
	dw.Show()

	events := dw.EventChan()
	go listen(events)
	render()
}

func listen(c <-chan interface{}) {
loop:
	for ev := range c {
		switch e := ev.(type) {
		case wde.MouseDownEvent:
			if e.Which == 8 {
				radius++
			} else if e.Which == 16 {
				radius--
			} else {
				randomize()
			}
		case wde.MouseMovedEvent:
			mousex, mousey = e.Where.X, e.Where.Y
		case wde.CloseEvent:
			fmt.Println("closed")
			dw.Close()
			break loop
		}
	}
	done <- struct{}{}
}

func render() {
	for {
		draw.DrawMask(dw.Screen(), dw.Screen().Bounds(), &image.Uniform{color.RGBA{r, g, b, a}}, image.ZP, &circle{image.Point{mousex, mousey}, radius}, image.ZP, draw.Over)
		dw.FlushImage()
		select {
		case <-time.After(time.Second / 25):
		case <-done:
			return
		}
	}
}

func randomize() {
	r, g, b = uint8(rand.Uint32()), uint8(rand.Uint32()), uint8(rand.Uint32())
}

type circle struct {
	p image.Point
	r int
}

func (c *circle) ColorModel() color.Model {
	return color.AlphaModel
}

func (c *circle) Bounds() image.Rectangle {
	return image.Rect(c.p.X-c.r, c.p.Y-c.r, c.p.X+c.r, c.p.Y+c.r)
}

func (c *circle) At(x, y int) color.Color {
	xx, yy, rr := float64(x-c.p.X)+0.5, float64(y-c.p.Y)+0.5, float64(c.r)
	if xx*xx+yy*yy < rr*rr {
		return color.Alpha{255}
	}
	return color.Alpha{0}
}
<|endoftext|>


<commit_before>package server

import (
	"context"
	"crypto/subtle"
	"fmt"
	"log"
	"net/http"
	"os"
	"text/template"
	"time"

	"github.com/99designs/gqlgen/handler"
	"github.com/emwalker/digraph/loaders"
	"github.com/emwalker/digraph/models"
	"github.com/emwalker/digraph/resolvers"
	"github.com/go-webpack/webpack"
	"github.com/gorilla/handlers"
	// Load the PQ drivers
	_ "github.com/lib/pq"
	"github.com/markbates/goth/gothic"
	"github.com/rs/cors"
	"github.com/volatiletech/sqlboiler/queries/qm"
)

const (
	userSessionKey = "userSessionKey"
)

func must(err error) {
	if err != nil {
		log.Fatal("there was a problem: ", err)
	}
}

func (s *Server) basicAuthRequired(r *http.Request) bool {
	if s.BasicAuthUsername == "" && s.BasicAuthPassword == "" {
		return false
	}

	user, pass, ok := r.BasicAuth()
	return !ok ||
		subtle.ConstantTimeCompare([]byte(user), []byte(s.BasicAuthUsername)) != 1 ||
		subtle.ConstantTimeCompare([]byte(pass), []byte(s.BasicAuthPassword)) != 1
}

// https://stackoverflow.com/a/39591234/61048
func (s *Server) withBasicAuth(next http.Handler) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if s.basicAuthRequired(r) {
			w.Header().Set("WWW-Authenticate", `Basic realm="Digraph"`)
			w.WriteHeader(401)
			w.Write([]byte("Unauthorized.\n"))
			return
		}

		next.ServeHTTP(w, r)
	})
}

// https://github.com/vektah/gqlgen-tutorials/blob/master/dataloader/graph.go
func (s *Server) withLoaders(next http.Handler) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		ctx = loaders.AddToContext(ctx, s.db, 1*time.Millisecond)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

func (s *Server) withSession(next http.Handler) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		sessionID, err := gothic.GetFromSession(userSessionKey, r)
		if err != nil {
			log.Printf("No user session found: %s", err)
			next.ServeHTTP(w, r)
			return
		}

		ctx := r.Context()

		log.Printf("A session id found, looking up session: %s", sessionID)
		session, err := models.Sessions(
			qm.Load("User"),
			qm.Where("session_id = decode(?, 'hex')", sessionID),
		).One(ctx, s.db)

		if err != nil {
			log.Printf("Session not found for session id %s", sessionID)
			next.ServeHTTP(w, r)
			return
		}

		// Figure out a way to avoid mutating the resolver after the fact
		user := session.R.User
		s.resolver.Actor = user

		log.Printf("Adding user %s to context", user.Name)
		ctx = context.WithValue(ctx, resolvers.CurrentUserKey, user)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

const homepageTemplate = `<!doctype html>

    <meta charset="utf-8">
    <meta http-equiv="Content-Language" content="en">
    <title>Digraph</title>
    {{ asset "main.css" }}

    {{ asset "vendors.js" }}
    {{ asset "main.js" }}

`

func (s *Server) handleRoot() http.Handler {
	funcMap := map[string]interface{}{"asset": webpack.AssetHelper}
	t := template.New("homepage").Funcs(funcMap)
	template.Must(t.Parse(homepageTemplate))

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Execute(w, nil)
	})
}

func (s *Server) handleGraphqlRequest() http.Handler {
	handler := cors.Default().Handler(handler.GraphQL(s.schema))
	handler = handlers.CompressHandler(handler)
	if s.LogLevel > 0 {
		handler = handlers.CombinedLoggingHandler(os.Stdout, handler)
	}
	return s.withLoaders(handler)
}

func (s *Server) handleGraphqlPlayground() http.Handler {
	return handler.Playground("GraphQL playground", "/graphql")
}

func (s *Server) handleHealthCheck() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "ok")
	})
}

func (s *Server) handleStaticFiles() http.Handler {
	fs := http.FileServer(http.Dir("public/webpack"))
	return http.StripPrefix("/static", fs)
}
<commit_msg>Include favicon in production template<commit_after>package server

import (
	"context"
	"crypto/subtle"
	"fmt"
	"log"
	"net/http"
	"os"
	"text/template"
	"time"

	"github.com/99designs/gqlgen/handler"
	"github.com/emwalker/digraph/loaders"
	"github.com/emwalker/digraph/models"
	"github.com/emwalker/digraph/resolvers"
	"github.com/go-webpack/webpack"
	"github.com/gorilla/handlers"
	// Load the PQ drivers
	_ "github.com/lib/pq"
	"github.com/markbates/goth/gothic"
	"github.com/rs/cors"
	"github.com/volatiletech/sqlboiler/queries/qm"
)

const (
	userSessionKey = "userSessionKey"
)

func must(err error) {
	if err != nil {
		log.Fatal("there was a problem: ", err)
	}
}

func (s *Server) basicAuthRequired(r *http.Request) bool {
	if s.BasicAuthUsername == "" && s.BasicAuthPassword == "" {
		return false
	}

	user, pass, ok := r.BasicAuth()
	return !ok ||
		subtle.ConstantTimeCompare([]byte(user), []byte(s.BasicAuthUsername)) != 1 ||
		subtle.ConstantTimeCompare([]byte(pass), []byte(s.BasicAuthPassword)) != 1
}

// https://stackoverflow.com/a/39591234/61048
func (s *Server) withBasicAuth(next http.Handler) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if s.basicAuthRequired(r) {
			w.Header().Set("WWW-Authenticate", `Basic realm="Digraph"`)
			w.WriteHeader(401)
			w.Write([]byte("Unauthorized.\n"))
			return
		}

		next.ServeHTTP(w, r)
	})
}

// https://github.com/vektah/gqlgen-tutorials/blob/master/dataloader/graph.go
func (s *Server) withLoaders(next http.Handler) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		ctx = loaders.AddToContext(ctx, s.db, 1*time.Millisecond)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

func (s *Server) withSession(next http.Handler) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		sessionID, err := gothic.GetFromSession(userSessionKey, r)
		if err != nil {
			log.Printf("No user session found: %s", err)
			next.ServeHTTP(w, r)
			return
		}

		ctx := r.Context()

		log.Printf("A session id found, looking up session: %s", sessionID)
		session, err := models.Sessions(
			qm.Load("User"),
			qm.Where("session_id = decode(?, 'hex')", sessionID),
		).One(ctx, s.db)

		if err != nil {
			log.Printf("Session not found for session id %s", sessionID)
			next.ServeHTTP(w, r)
			return
		}

		// Figure out a way to avoid mutating the resolver after the fact
		user := session.R.User
		s.resolver.Actor = user

		log.Printf("Adding user %s to context", user.Name)
		ctx = context.WithValue(ctx, resolvers.CurrentUserKey, user)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

const homepageTemplate = `<!doctype html>

    <meta charset="utf-8">
    <meta http-equiv="Content-Language" content="en">
    <title>Digraph</title>
    <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
    {{ asset "main.css" }}

    {{ asset "vendors.js" }}
    {{ asset "main.js" }}

`

func (s *Server) handleRoot() http.Handler {
	funcMap := map[string]interface{}{"asset": webpack.AssetHelper}
	t := template.New("homepage").Funcs(funcMap)
	template.Must(t.Parse(homepageTemplate))

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Execute(w, nil)
	})
}

func (s *Server) handleGraphqlRequest() http.Handler {
	handler := cors.Default().Handler(handler.GraphQL(s.schema))
	handler = handlers.CompressHandler(handler)
	if s.LogLevel > 0 {
		handler = handlers.CombinedLoggingHandler(os.Stdout, handler)
	}
	return s.withLoaders(handler)
}

func (s *Server) handleGraphqlPlayground() http.Handler {
	return handler.Playground("GraphQL playground", "/graphql")
}

func (s *Server) handleHealthCheck() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "ok")
	})
}

func (s *Server) handleStaticFiles() http.Handler {
	fs := http.FileServer(http.Dir("public/webpack"))
	return http.StripPrefix("/static", fs)
}
<|endoftext|>


<commit_before>package kateway

import (
	"bytes"
	"fmt"
	"os"
	"sync"
	"time"

	"github.com/Shopify/sarama"
	"github.com/funkygao/gafka/cmd/kguard/monitor"
	"github.com/funkygao/gafka/zk"
	"github.com/funkygao/go-metrics"
	log "github.com/funkygao/log4go"
)

var (
	javaSdkPubErr       = []byte("send msg error")
	javaSdkPubStatusErr = []byte("StatusLine is null")
)

func init() {
	monitor.RegisterWatcher("kateway.apperr", func() monitor.Watcher {
		return &WatchAppError{}
	})
}

// WatchAppError monitors app err log to find all pubsub related err.
type WatchAppError struct {
	Zkzone *zk.ZkZone
	Stop   <-chan struct{}
	Wg     *sync.WaitGroup

	startedAt time.Time
	seq       int

	pubLatency, subLatency metrics.Histogram
}

func (this *WatchAppError) Init(ctx monitor.Context) {
	this.Zkzone = ctx.ZkZone()
	this.Stop = ctx.StopChan()
	this.Wg = ctx.Inflight()
}

func (this *WatchAppError) Run() {
	defer this.Wg.Done()

	appError := metrics.NewRegisteredCounter("kateway.apperr", nil)
	msgChan := make(chan *sarama.ConsumerMessage, 2000)

	if err := this.consumeAppErrLogs(msgChan); err != nil {
		close(msgChan)

		log.Error("%v", err)
		return
	}

	for {
		select {
		case <-this.Stop:
			log.Info("kateway.apperr stopped")
			return

		case msg, ok := <-msgChan:
			if !ok {
				return
			}

			appError.Inc(1)
			log.Warn("%d/%d %s", msg.Partition, msg.Offset, string(msg.Value))
		}
	}
}

func (this *WatchAppError) consumeAppErrLogs(msgChan chan<- *sarama.ConsumerMessage) error {
	var (
		cluster = os.Getenv("APPLOG_CLUSTER")
		topic   = os.Getenv("APPLOG_TOPIC")
	)

	if cluster == "" || topic == "" {
		return fmt.Errorf("empty cluster/topic params provided, kateway.apperr disabled")
	}

	zkcluster := this.Zkzone.NewCluster(cluster)
	brokerList := zkcluster.BrokerList()
	if len(brokerList) == 0 {
		return fmt.Errorf("cluster[%s] has empty brokers", cluster)
	}
	kfk, err := sarama.NewClient(brokerList, sarama.NewConfig())
	if err != nil {
		return err
	}
	defer kfk.Close()

	consumer, err := sarama.NewConsumerFromClient(kfk)
	if err != nil {
		return err
	}
	defer consumer.Close()

	partitions, err := kfk.Partitions(topic)
	if err != nil {
		return err
	}

	for _, p := range partitions {
		go this.consumePartition(zkcluster, consumer, topic, p, sarama.OffsetOldest, msgChan)
	}

	return nil
}

func (this *WatchAppError) consumePartition(zkcluster *zk.ZkCluster, consumer sarama.Consumer,
	topic string, partitionId int32, offset int64, msgCh chan<- *sarama.ConsumerMessage) {
	p, err := consumer.ConsumePartition(topic, partitionId, offset)
	if err != nil {
		log.Error("%s %s/%d: offset=%d %v", zkcluster.Name(), topic, partitionId, offset, err)
		return
	}
	defer p.Close()

	for {
		select {
		case <-this.Stop:
			return

		case msg := <-p.Messages():
			if this.predicate(msg.Value) {
				msgCh <- msg
			}

		}
	}
}

func (this *WatchAppError) predicate(msg []byte) bool {
	switch {
	case bytes.Contains(msg, javaSdkPubErr):
		return true

	case bytes.Contains(msg, javaSdkPubStatusErr):
		return true

	default:
		return false
	}
}
<commit_msg>fix  bug: kafka tried to use a client that was closed<commit_after>package kateway

import (
	"bytes"
	"fmt"
	"os"
	"sync"

	"github.com/Shopify/sarama"
	"github.com/funkygao/gafka/cmd/kguard/monitor"
	"github.com/funkygao/gafka/zk"
	"github.com/funkygao/go-metrics"
	log "github.com/funkygao/log4go"
)

var (
	javaSdkPubErr       = []byte("send msg error")
	javaSdkPubStatusErr = []byte("StatusLine is null")
)

func init() {
	monitor.RegisterWatcher("kateway.apperr", func() monitor.Watcher {
		return &WatchAppError{}
	})
}

// WatchAppError monitors app err log to find all pubsub related err.
type WatchAppError struct {
	Zkzone *zk.ZkZone
	Stop   <-chan struct{}
	Wg     *sync.WaitGroup
}

func (this *WatchAppError) Init(ctx monitor.Context) {
	this.Zkzone = ctx.ZkZone()
	this.Stop = ctx.StopChan()
	this.Wg = ctx.Inflight()
}

func (this *WatchAppError) Run() {
	defer this.Wg.Done()

	appError := metrics.NewRegisteredCounter("kateway.apperr", nil)
	msgChan := make(chan *sarama.ConsumerMessage, 2000)

	if err := this.consumeAppErrLogs(msgChan); err != nil {
		close(msgChan)

		log.Error("%v", err)
		return
	}

	for {
		select {
		case <-this.Stop:
			log.Info("kateway.apperr stopped")
			return

		case msg, ok := <-msgChan:
			if !ok {
				return
			}

			appError.Inc(1)
			log.Warn("%d/%d %s", msg.Partition, msg.Offset, string(msg.Value))
		}
	}
}

func (this *WatchAppError) consumeAppErrLogs(msgChan chan<- *sarama.ConsumerMessage) error {
	var (
		cluster = os.Getenv("APPLOG_CLUSTER")
		topic   = os.Getenv("APPLOG_TOPIC")
	)

	if cluster == "" || topic == "" {
		return fmt.Errorf("empty cluster/topic params provided, kateway.apperr disabled")
	}

	zkcluster := this.Zkzone.NewCluster(cluster)
	brokerList := zkcluster.BrokerList()
	if len(brokerList) == 0 {
		return fmt.Errorf("cluster[%s] has empty brokers", cluster)
	}
	kfk, err := sarama.NewClient(brokerList, sarama.NewConfig())
	if err != nil {
		return err
	}
	defer kfk.Close()

	consumer, err := sarama.NewConsumerFromClient(kfk)
	if err != nil {
		return err
	}
	defer consumer.Close()

	partitions, err := kfk.Partitions(topic)
	if err != nil {
		return err
	}

	var wg sync.WaitGroup
	for _, p := range partitions {
		wg.Add(1)
		go this.consumePartition(zkcluster, consumer, topic, p, sarama.OffsetOldest, msgChan, &wg)
	}

	wg.Wait()
	return nil
}

func (this *WatchAppError) consumePartition(zkcluster *zk.ZkCluster, consumer sarama.Consumer,
	topic string, partitionId int32, offset int64, msgCh chan<- *sarama.ConsumerMessage, wg *sync.WaitGroup) {
	defer wg.Done()

	p, err := consumer.ConsumePartition(topic, partitionId, offset)
	if err != nil {
		log.Error("%s %s/%d: offset=%d %v", zkcluster.Name(), topic, partitionId, offset, err)
		return
	}
	defer p.Close()

	for {
		select {
		case <-this.Stop:
			return

		case msg := <-p.Messages():
			if this.predicate(msg.Value) {
				msgCh <- msg
			}

		}
	}
}

func (this *WatchAppError) predicate(msg []byte) bool {
	switch {
	case bytes.Contains(msg, javaSdkPubErr):
		return true

	case bytes.Contains(msg, javaSdkPubStatusErr):
		return true

	default:
		return false
	}
}
<|endoftext|>


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

package main

// Install command from a go source file.
//
// Synopsis:
//     SYMLINK [ARGS...]
//     installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]
//
// Description:
//     u-root commands are lazily compiled. Uncompiled commands in the /bin
//     directory are symbolic links to installcommand. When executed through
//     the symbolic link, installcommand will build the command from source and
//     exec it.
//
//     The second form allows commands to be installed and exec'ed without a
//     symbolic link. In this form additional arguments such as `-v` and
//     `-ludicrous` can be passed into installcommand.
//
// Options:
//     -lowpri:    the scheduler priority to lowered before starting
//     -exec:      build and exec the command
//     -force:     do not build if a file already exists at the destination
//     -v:         print all build commands
import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/u-root/u-root/pkg/golang"
	"github.com/u-root/u-root/pkg/uroot/util"
)

var (
	lowpri = flag.Bool("lowpri", false, "the scheduler priority is lowered before starting")
	exe    = flag.Bool("exec", true, "build AND execute the command")
	force  = flag.Bool("force", false, "build even if a file already exists at the destination")

	verbose = flag.Bool("v", false, "print all build commands")
	debug   = func(string, ...interface{}) {}
	r = util.UrootPath
)

type form struct {
	// Name of the command, ex: "ls"
	cmdName string
	// Args passed to the command, ex: {"-l", "-R"}
	cmdArgs []string

	// Args intended for installcommand
	lowPri  bool
	exec    bool
	force   bool
	verbose bool
}

func usage() {
	fmt.Fprintf(os.Stderr, "Usage: installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]\n")
	os.Exit(2)
}

// Parse the command line to determine the form.
func parseCommandLine() form {
	// First form:
	//     SYMLINK [ARGS...]
	if !strings.HasSuffix(os.Args[0], "installcommand") {
		return form{
			cmdName: filepath.Base(os.Args[0]),
			cmdArgs: os.Args[1:],
			lowPri:  *lowpri,
			exec:    *exe,
			force:   *force,
			verbose: *verbose,
		}
	}

	// Second form:
	//     installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]
	flag.Parse()
	if flag.NArg() < 1 {
		log.Println("Second form requires a COMMAND argument")
		usage()
	}
	return form{
		cmdName: flag.Arg(0),
		cmdArgs: flag.Args()[1:],
		lowPri:  *lowpri,
		exec:    *exe,
		force:   *force,
		verbose: *verbose,
	}
}

// run runs the command with the information from form.
// Since run can potentially never return, since it can use Exec,
// it should never return in any other case. Hence, if all goes well
// at the end, we os.Exit(0)
func run(n string, form form) {
	cmd := exec.Command(n, form.cmdArgs...)
	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	if err := cmd.Run(); err != nil {
		exitErr, ok := err.(*exec.ExitError)
		if !ok {
			log.Fatal(err)
		}
		exitWithStatus(exitErr)
	}
	os.Exit(0)
}

func main() {
	form := parseCommandLine()

	if form.lowPri {
		if err := syscall.Setpriority(syscall.PRIO_PROCESS, 0, 20); err != nil {
			log.Printf("Cannot set low priority: %v", err)
		}
	}

	if form.verbose {
		debug = log.Printf
	}

	debug("Command name: %v\n", form.cmdName)
	destFile := filepath.Join(r("/ubin"), form.cmdName)

	// Is the command there? This covers a race condition
	// in that some other process may have caused it to be
	// built.
	if _, err := os.Stat(destFile); err == nil {
		if !form.exec {
			os.Exit(0)
		}
		run(destFile, form)
	}

	env := golang.Default()
	env.Context.GOROOT = r("/go")
	env.Context.GOPATH = r("/")

	var srcDir string
	err := filepath.Walk(r("/src"), func(p string, fi os.FileInfo, err error) error {
		if err != nil {
			return nil
		}

		if fi.IsDir() && filepath.Base(p) == form.cmdName {
			// Make sure it's an actual Go command.
			pkg, err := env.PackageByPath(p)
			if err == nil && pkg.IsCommand() {
				srcDir = p
			}
		}
		return nil
	})
	if err != nil {
		log.Fatal(err)
	}
	if len(srcDir) == 0 {
		log.Fatalf("Can not find source code for %q", form.cmdName)
	}

	if err := env.BuildDir(srcDir, destFile, golang.BuildOpts{}); err != nil {
		log.Fatalf("Couldn't compile %q: %v", form.cmdName, err)
	}

	if form.exec {
		run(destFile, form)
	}
}
<commit_msg>installcommand: avoid using cgo<commit_after>// Copyright 2012-2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

// Install command from a go source file.
//
// Synopsis:
//     SYMLINK [ARGS...]
//     installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]
//
// Description:
//     u-root commands are lazily compiled. Uncompiled commands in the /bin
//     directory are symbolic links to installcommand. When executed through
//     the symbolic link, installcommand will build the command from source and
//     exec it.
//
//     The second form allows commands to be installed and exec'ed without a
//     symbolic link. In this form additional arguments such as `-v` and
//     `-ludicrous` can be passed into installcommand.
//
// Options:
//     -lowpri:    the scheduler priority to lowered before starting
//     -exec:      build and exec the command
//     -force:     do not build if a file already exists at the destination
//     -v:         print all build commands
import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/u-root/u-root/pkg/golang"
	"github.com/u-root/u-root/pkg/uroot/util"
)

var (
	lowpri = flag.Bool("lowpri", false, "the scheduler priority is lowered before starting")
	exe    = flag.Bool("exec", true, "build AND execute the command")
	force  = flag.Bool("force", false, "build even if a file already exists at the destination")

	verbose = flag.Bool("v", false, "print all build commands")
	debug   = func(string, ...interface{}) {}
	r = util.UrootPath
)

type form struct {
	// Name of the command, ex: "ls"
	cmdName string
	// Args passed to the command, ex: {"-l", "-R"}
	cmdArgs []string

	// Args intended for installcommand
	lowPri  bool
	exec    bool
	force   bool
	verbose bool
}

func usage() {
	fmt.Fprintf(os.Stderr, "Usage: installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]\n")
	os.Exit(2)
}

// Parse the command line to determine the form.
func parseCommandLine() form {
	// First form:
	//     SYMLINK [ARGS...]
	if !strings.HasSuffix(os.Args[0], "installcommand") {
		return form{
			cmdName: filepath.Base(os.Args[0]),
			cmdArgs: os.Args[1:],
			lowPri:  *lowpri,
			exec:    *exe,
			force:   *force,
			verbose: *verbose,
		}
	}

	// Second form:
	//     installcommand [INSTALLCOMMAND_ARGS...] COMMAND [ARGS...]
	flag.Parse()
	if flag.NArg() < 1 {
		log.Println("Second form requires a COMMAND argument")
		usage()
	}
	return form{
		cmdName: flag.Arg(0),
		cmdArgs: flag.Args()[1:],
		lowPri:  *lowpri,
		exec:    *exe,
		force:   *force,
		verbose: *verbose,
	}
}

// run runs the command with the information from form.
// Since run can potentially never return, since it can use Exec,
// it should never return in any other case. Hence, if all goes well
// at the end, we os.Exit(0)
func run(n string, form form) {
	cmd := exec.Command(n, form.cmdArgs...)
	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	if err := cmd.Run(); err != nil {
		exitErr, ok := err.(*exec.ExitError)
		if !ok {
			log.Fatal(err)
		}
		exitWithStatus(exitErr)
	}
	os.Exit(0)
}

func main() {
	form := parseCommandLine()

	if form.lowPri {
		if err := syscall.Setpriority(syscall.PRIO_PROCESS, 0, 20); err != nil {
			log.Printf("Cannot set low priority: %v", err)
		}
	}

	if form.verbose {
		debug = log.Printf
	}

	debug("Command name: %v\n", form.cmdName)
	destFile := filepath.Join(r("/ubin"), form.cmdName)

	// Is the command there? This covers a race condition
	// in that some other process may have caused it to be
	// built.
	if _, err := os.Stat(destFile); err == nil {
		if !form.exec {
			os.Exit(0)
		}
		run(destFile, form)
	}

	env := golang.Default()
	env.Context.GOROOT = r("/go")
	env.Context.GOPATH = r("/")
	env.Context.CgoEnabled = false

	var srcDir string
	err := filepath.Walk(r("/src"), func(p string, fi os.FileInfo, err error) error {
		if err != nil {
			return nil
		}

		if fi.IsDir() && filepath.Base(p) == form.cmdName {
			// Make sure it's an actual Go command.
			pkg, err := env.PackageByPath(p)
			if err == nil && pkg.IsCommand() {
				srcDir = p
			}
		}
		return nil
	})
	if err != nil {
		log.Fatal(err)
	}
	if len(srcDir) == 0 {
		log.Fatalf("Can not find source code for %q", form.cmdName)
	}

	if err := env.BuildDir(srcDir, destFile, golang.BuildOpts{}); err != nil {
		log.Fatalf("Couldn't compile %q: %v", form.cmdName, err)
	}

	if form.exec {
		run(destFile, form)
	}
}
<|endoftext|>


<commit_before>package server

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"encoding/hex"
	"fmt"
	"io"
	"time"

	"gopkg.in/square/go-jose.v2"

	"github.com/Sirupsen/logrus"
	"github.com/coreos/dex/storage"
)

// rotationStrategy describes a strategy for generating cryptographic keys, how
// often to rotate them, and how long they can validate signatures after rotation.
type rotationStrategy struct {
	// Time between rotations.
	rotationFrequency time.Duration

	// After being rotated how long should the key be kept around for validating
	// signatues?
	idTokenValidFor time.Duration

	// Keys are always RSA keys. Though cryptopasta recommends ECDSA keys, not every
	// client may support these (e.g. github.com/coreos/go-oidc/oidc).
	key func() (*rsa.PrivateKey, error)
}

// staticRotationStrategy returns a strategy which never rotates keys.
func staticRotationStrategy(key *rsa.PrivateKey) rotationStrategy {
	return rotationStrategy{
		// Setting these values to 100 years is easier than having a flag indicating no rotation.
		rotationFrequency: time.Hour * 8760 * 100,
		idTokenValidFor:   time.Hour * 8760 * 100,
		key:               func() (*rsa.PrivateKey, error) { return key, nil },
	}
}

// defaultRotationStrategy returns a strategy which rotates keys every provided period,
// holding onto the public parts for some specified amount of time.
func defaultRotationStrategy(rotationFrequency, idTokenValidFor time.Duration) rotationStrategy {
	return rotationStrategy{
		rotationFrequency: rotationFrequency,
		idTokenValidFor:   idTokenValidFor,
		key: func() (*rsa.PrivateKey, error) {
			return rsa.GenerateKey(rand.Reader, 2048)
		},
	}
}

type keyRotater struct {
	storage.Storage

	strategy rotationStrategy
	now      func() time.Time

	logger logrus.FieldLogger
}

// startKeyRotation begins key rotation in a new goroutine, closing once the context is canceled.
//
// The method blocks until after the first attempt to rotate keys has completed. That way
// healthy storages will return from this call with valid keys.
func (s *Server) startKeyRotation(ctx context.Context, strategy rotationStrategy, now func() time.Time) {
	rotater := keyRotater{s.storage, strategy, now, s.logger}

	// Try to rotate immediately so properly configured storages will have keys.
	if err := rotater.rotate(); err != nil {
		s.logger.Errorf("failed to rotate keys: %v", err)
	}

	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			case <-time.After(time.Second * 30):
				if err := rotater.rotate(); err != nil {
					s.logger.Errorf("failed to rotate keys: %v", err)
				}
			}
		}
	}()
	return
}

func (k keyRotater) rotate() error {
	keys, err := k.GetKeys()
	if err != nil && err != storage.ErrNotFound {
		return fmt.Errorf("get keys: %v", err)
	}
	if k.now().Before(keys.NextRotation) {
		return nil
	}
	k.logger.Infof("keys expired, rotating")

	// Generate the key outside of a storage transaction.
	key, err := k.strategy.key()
	if err != nil {
		return fmt.Errorf("generate key: %v", err)
	}
	b := make([]byte, 20)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		panic(err)
	}
	keyID := hex.EncodeToString(b)
	priv := &jose.JSONWebKey{
		Key:       key,
		KeyID:     keyID,
		Algorithm: "RS256",
		Use:       "sig",
	}
	pub := &jose.JSONWebKey{
		Key:       key.Public(),
		KeyID:     keyID,
		Algorithm: "RS256",
		Use:       "sig",
	}

	var nextRotation time.Time
	err = k.Storage.UpdateKeys(func(keys storage.Keys) (storage.Keys, error) {
		tNow := k.now()

		// if you are running multiple instances of dex, another instance
		// could have already rotated the keys.
		if tNow.Before(keys.NextRotation) {
			return storage.Keys{}, nil
		}

		expired := func(key storage.VerificationKey) bool {
			return tNow.After(key.Expiry)
		}

		// Remove any verification keys that have expired.
		i := 0
		for _, key := range keys.VerificationKeys {
			if !expired(key) {
				keys.VerificationKeys[i] = key
				i++
			}
		}
		keys.VerificationKeys = keys.VerificationKeys[:i]

		if keys.SigningKeyPub != nil {
			// Move current signing key to a verification only key, throwing
			// away the private part.
			verificationKey := storage.VerificationKey{
				PublicKey: keys.SigningKeyPub,
				// After demoting the signing key, keep the token around for at least
				// the amount of time an ID Token is valid for. This ensures the
				// verification key won't expire until all ID Tokens it's signed
				// expired as well.
				Expiry: tNow.Add(k.strategy.idTokenValidFor),
			}
			keys.VerificationKeys = append(keys.VerificationKeys, verificationKey)
		}

		nextRotation = k.now().Add(k.strategy.rotationFrequency)
		keys.SigningKey = priv
		keys.SigningKeyPub = pub
		keys.NextRotation = nextRotation
		return keys, nil
	})
	if err != nil {
		return err
	}
	k.logger.Infof("keys rotated, next rotation: %s", nextRotation)
	return nil
}
<commit_msg>server/rotation.go: Fix key rotation with multiple dex instances.<commit_after>package server

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"time"

	"gopkg.in/square/go-jose.v2"

	"github.com/Sirupsen/logrus"
	"github.com/coreos/dex/storage"
)

var errAlreadyRotated = errors.New("keys already rotated by another server instance")

// rotationStrategy describes a strategy for generating cryptographic keys, how
// often to rotate them, and how long they can validate signatures after rotation.
type rotationStrategy struct {
	// Time between rotations.
	rotationFrequency time.Duration

	// After being rotated how long should the key be kept around for validating
	// signatues?
	idTokenValidFor time.Duration

	// Keys are always RSA keys. Though cryptopasta recommends ECDSA keys, not every
	// client may support these (e.g. github.com/coreos/go-oidc/oidc).
	key func() (*rsa.PrivateKey, error)
}

// staticRotationStrategy returns a strategy which never rotates keys.
func staticRotationStrategy(key *rsa.PrivateKey) rotationStrategy {
	return rotationStrategy{
		// Setting these values to 100 years is easier than having a flag indicating no rotation.
		rotationFrequency: time.Hour * 8760 * 100,
		idTokenValidFor:   time.Hour * 8760 * 100,
		key:               func() (*rsa.PrivateKey, error) { return key, nil },
	}
}

// defaultRotationStrategy returns a strategy which rotates keys every provided period,
// holding onto the public parts for some specified amount of time.
func defaultRotationStrategy(rotationFrequency, idTokenValidFor time.Duration) rotationStrategy {
	return rotationStrategy{
		rotationFrequency: rotationFrequency,
		idTokenValidFor:   idTokenValidFor,
		key: func() (*rsa.PrivateKey, error) {
			return rsa.GenerateKey(rand.Reader, 2048)
		},
	}
}

type keyRotater struct {
	storage.Storage

	strategy rotationStrategy
	now      func() time.Time

	logger logrus.FieldLogger
}

// startKeyRotation begins key rotation in a new goroutine, closing once the context is canceled.
//
// The method blocks until after the first attempt to rotate keys has completed. That way
// healthy storages will return from this call with valid keys.
func (s *Server) startKeyRotation(ctx context.Context, strategy rotationStrategy, now func() time.Time) {
	rotater := keyRotater{s.storage, strategy, now, s.logger}

	// Try to rotate immediately so properly configured storages will have keys.
	if err := rotater.rotate(); err != nil {
		if err == errAlreadyRotated {
			s.logger.Infof("Key rotation not needed: %v", err)
		} else {
			s.logger.Errorf("failed to rotate keys: %v", err)
		}
	}

	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			case <-time.After(time.Second * 30):
				if err := rotater.rotate(); err != nil {
					s.logger.Errorf("failed to rotate keys: %v", err)
				}
			}
		}
	}()
	return
}

func (k keyRotater) rotate() error {
	keys, err := k.GetKeys()
	if err != nil && err != storage.ErrNotFound {
		return fmt.Errorf("get keys: %v", err)
	}
	if k.now().Before(keys.NextRotation) {
		return nil
	}
	k.logger.Infof("keys expired, rotating")

	// Generate the key outside of a storage transaction.
	key, err := k.strategy.key()
	if err != nil {
		return fmt.Errorf("generate key: %v", err)
	}
	b := make([]byte, 20)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		panic(err)
	}
	keyID := hex.EncodeToString(b)
	priv := &jose.JSONWebKey{
		Key:       key,
		KeyID:     keyID,
		Algorithm: "RS256",
		Use:       "sig",
	}
	pub := &jose.JSONWebKey{
		Key:       key.Public(),
		KeyID:     keyID,
		Algorithm: "RS256",
		Use:       "sig",
	}

	var nextRotation time.Time
	err = k.Storage.UpdateKeys(func(keys storage.Keys) (storage.Keys, error) {
		tNow := k.now()

		// if you are running multiple instances of dex, another instance
		// could have already rotated the keys.
		if tNow.Before(keys.NextRotation) {
			return storage.Keys{}, errAlreadyRotated
		}

		expired := func(key storage.VerificationKey) bool {
			return tNow.After(key.Expiry)
		}

		// Remove any verification keys that have expired.
		i := 0
		for _, key := range keys.VerificationKeys {
			if !expired(key) {
				keys.VerificationKeys[i] = key
				i++
			}
		}
		keys.VerificationKeys = keys.VerificationKeys[:i]

		if keys.SigningKeyPub != nil {
			// Move current signing key to a verification only key, throwing
			// away the private part.
			verificationKey := storage.VerificationKey{
				PublicKey: keys.SigningKeyPub,
				// After demoting the signing key, keep the token around for at least
				// the amount of time an ID Token is valid for. This ensures the
				// verification key won't expire until all ID Tokens it's signed
				// expired as well.
				Expiry: tNow.Add(k.strategy.idTokenValidFor),
			}
			keys.VerificationKeys = append(keys.VerificationKeys, verificationKey)
		}

		nextRotation = k.now().Add(k.strategy.rotationFrequency)
		keys.SigningKey = priv
		keys.SigningKeyPub = pub
		keys.NextRotation = nextRotation
		return keys, nil
	})
	if err != nil {
		return err
	}
	k.logger.Infof("keys rotated, next rotation: %s", nextRotation)
	return nil
}
<|endoftext|>


<commit_before>package bitbucket

import (
	"context"
	"crypto/sha256"
	"fmt"
	"net/http"
	"path/filepath"
	"strings"
	"sync"

	"github.com/reviewdog/reviewdog"
	"github.com/reviewdog/reviewdog/proto/rdf"
	"github.com/reviewdog/reviewdog/service/bitbucket/openapi"
)

var _ reviewdog.CommentService = &ReportAnnotator{}

const (
	// avatar from https://github.com/apps/reviewdog
	logoURL  = "https://avatars1.githubusercontent.com/in/12131"
	reporter = "reviewdog"
)

// ReportAnnotator is a comment service for Bitbucket Code Insights reports.
//
// API:
//  https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Bworkspace%7D/%7Brepo_slug%7D/commit/%7Bcommit%7D/reports/%7BreportId%7D/annotations#post
//  POST /2.0/repositories/{username}/{repo_slug}/commit/{commit}/reports/{reportId}/annotations
type ReportAnnotator struct {
	cli         *openapi.APIClient
	sha         string
	owner, repo string

	muAnnotations sync.Mutex
	annotations   map[string][]openapi.ReportAnnotation
	severityMap   map[rdf.Severity]string

	// wd is working directory relative to root of repository.
	wd string
}

// NewReportAnnotator creates new Bitbucket Report Annotator
func NewReportAnnotator(cli *openapi.APIClient, owner, repo, sha string) *ReportAnnotator {
	return &ReportAnnotator{
		cli:         cli,
		sha:         sha,
		owner:       owner,
		repo:        repo,
		annotations: make(map[string][]openapi.ReportAnnotation),
		severityMap: map[rdf.Severity]string{
			rdf.Severity_INFO:    annotationSeverityLow,
			rdf.Severity_WARNING: annotationSeverityMedium,
			rdf.Severity_ERROR:   annotationSeverityHigh,
		},
	}
}

// Post accepts a comment and holds it. Flush method actually posts comments to
// Bitbucket in batch.
func (r *ReportAnnotator) Post(_ context.Context, c *reviewdog.Comment) error {
	c.Result.Diagnostic.GetLocation().Path = filepath.ToSlash(
		filepath.Join(r.wd, c.Result.Diagnostic.GetLocation().GetPath()))
	r.muAnnotations.Lock()
	defer r.muAnnotations.Unlock()

	r.annotations[c.ToolName] = append(r.annotations[c.ToolName], r.annotationFromReviewDogComment(*c))

	return nil
}

// Flush posts comments which has not been posted yet.
func (r *ReportAnnotator) Flush(ctx context.Context) error {
	r.muAnnotations.Lock()
	defer r.muAnnotations.Unlock()

	// create/update/annotate report per tool
	for tool, annotations := range r.annotations {
		reportID := reportID(reporter, tool)
		title := fmt.Sprintf("[%s] %s report", tool, reporter)
		if len(r.annotations) == 0 {
			// if no annotation, create Passed report
			if err := r.createOrUpdateReport(ctx, reportID, title, reportResultPassed); err != nil {
				return err
			}
			// and move one
			continue
		}

		// create report or update report first, with the failed status
		if err := r.createOrUpdateReport(ctx, reportID, title, reportResultFailed); err != nil {
			return err
		}

		// add annotations to the report
		_, resp, err := r.cli.ReportsApi.BulkCreateOrUpdateAnnotations(
			ctx, r.owner, r.repo, r.sha, reportID,
		).Body(annotations).Execute()

		if err := checkAPIError(err, resp, http.StatusOK); err != nil {
			return fmt.Errorf("bitbucket.BulkCreateOrUpdateAnnotations: %s", err)
		}
	}

	return nil
}

func (r *ReportAnnotator) annotationFromReviewDogComment(c reviewdog.Comment) openapi.ReportAnnotation {
	a := openapi.NewReportAnnotation()
	switch c.ToolName {
	// TODO: different type of annotation based on tool?
	default:
		a.SetAnnotationType(annotationTypeCodeSmell)
	}

	// hash the output of linter and use it as external id
	a.SetExternalId(hashString(c.Result.Diagnostic.OriginalOutput))
	a.SetSummary(c.Result.Diagnostic.GetMessage())
	a.SetDetails(fmt.Sprintf(`[%s] %s`, c.ToolName, c.Result.Diagnostic.GetMessage()))
	a.SetLine(c.Result.Diagnostic.GetLocation().GetRange().GetStart().GetLine())
	a.SetPath(c.Result.Diagnostic.GetLocation().GetPath())
	if v, ok := r.severityMap[c.Result.Diagnostic.GetSeverity()]; ok {
		a.SetSeverity(v)
	}
	if link := c.Result.Diagnostic.GetCode().GetUrl(); link != "" {
		a.SetLink(link)
	}

	return *a
}

func (r *ReportAnnotator) createOrUpdateReport(ctx context.Context, redportID, title, reportStatus string) error {
	var report = openapi.NewReport()
	report.SetTitle(title)
	// TODO: different report types?
	report.SetReportType(reportTypeBug)
	report.SetReporter(reporter)
	report.SetLogoUrl(logoURL)
	report.SetResult(reportStatus)
	report.SetDetails("Woof-Woof! This report generated for you by reviewdog")

	_, resp, err := r.cli.ReportsApi.CreateOrUpdateReport(
		ctx, r.owner, r.repo, r.sha, redportID,
	).Body(*report).Execute()

	if err := checkAPIError(err, resp, http.StatusOK); err != nil {
		return fmt.Errorf("bitbucket.CreateOrUpdateReport: %s", err)
	}

	return nil
}

func hashString(str string) string {
	h := sha256.New()
	_, _ = h.Write([]byte(str))
	return fmt.Sprintf("%x", h.Sum(nil))
}

func reportID(ids ...string) string {
	return strings.ReplaceAll(strings.ToLower(strings.Join(ids, "-")), " ", "_")
}
<commit_msg>batch annotations send<commit_after>package bitbucket

import (
	"context"
	"crypto/sha256"
	"fmt"
	"net/http"
	"path/filepath"
	"strings"
	"sync"

	"github.com/reviewdog/reviewdog"
	"github.com/reviewdog/reviewdog/proto/rdf"
	"github.com/reviewdog/reviewdog/service/bitbucket/openapi"
)

var _ reviewdog.CommentService = &ReportAnnotator{}

const (
	// avatar from https://github.com/apps/reviewdog
	logoURL  = "https://avatars1.githubusercontent.com/in/12131"
	reporter = "reviewdog"
	// max amount of annotations in one batch call
	annotationsBatchSize = 100
)

// ReportAnnotator is a comment service for Bitbucket Code Insights reports.
//
// API:
//  https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Bworkspace%7D/%7Brepo_slug%7D/commit/%7Bcommit%7D/reports/%7BreportId%7D/annotations#post
//  POST /2.0/repositories/{username}/{repo_slug}/commit/{commit}/reports/{reportId}/annotations
type ReportAnnotator struct {
	cli         *openapi.APIClient
	sha         string
	owner, repo string

	muAnnotations sync.Mutex
	annotations   map[string][]openapi.ReportAnnotation
	severityMap   map[rdf.Severity]string

	// wd is working directory relative to root of repository.
	wd string
}

// NewReportAnnotator creates new Bitbucket Report Annotator
func NewReportAnnotator(cli *openapi.APIClient, owner, repo, sha string) *ReportAnnotator {
	return &ReportAnnotator{
		cli:         cli,
		sha:         sha,
		owner:       owner,
		repo:        repo,
		annotations: make(map[string][]openapi.ReportAnnotation),
		severityMap: map[rdf.Severity]string{
			rdf.Severity_INFO:    annotationSeverityLow,
			rdf.Severity_WARNING: annotationSeverityMedium,
			rdf.Severity_ERROR:   annotationSeverityHigh,
		},
	}
}

// Post accepts a comment and holds it. Flush method actually posts comments to
// Bitbucket in batch.
func (r *ReportAnnotator) Post(_ context.Context, c *reviewdog.Comment) error {
	c.Result.Diagnostic.GetLocation().Path = filepath.ToSlash(
		filepath.Join(r.wd, c.Result.Diagnostic.GetLocation().GetPath()))
	r.muAnnotations.Lock()
	defer r.muAnnotations.Unlock()

	r.annotations[c.ToolName] = append(r.annotations[c.ToolName], r.annotationFromReviewDogComment(*c))

	return nil
}

// Flush posts comments which has not been posted yet.
func (r *ReportAnnotator) Flush(ctx context.Context) error {
	r.muAnnotations.Lock()
	defer r.muAnnotations.Unlock()

	// create/update/annotate report per tool
	for tool, annotations := range r.annotations {
		reportID := reportID(reporter, tool)
		title := fmt.Sprintf("[%s] %s report", tool, reporter)
		if len(r.annotations) == 0 {
			// if no annotation, create Passed report
			if err := r.createOrUpdateReport(ctx, reportID, title, reportResultPassed); err != nil {
				return err
			}
			// and move one
			continue
		}

		// create report or update report first, with the failed status
		if err := r.createOrUpdateReport(ctx, reportID, title, reportResultFailed); err != nil {
			return err
		}

		// send annotations in batches, because of the api max payload size limit
		for start, annCount := 0, len(annotations); start < annCount; start += annotationsBatchSize {
			end := start + annotationsBatchSize

			if end > annCount {
				end = annCount
			}

			// add annotations to the report
			_, resp, err := r.cli.ReportsApi.BulkCreateOrUpdateAnnotations(
				ctx, r.owner, r.repo, r.sha, reportID,
			).Body(annotations[start:end]).Execute()

			if err := checkAPIError(err, resp, http.StatusOK); err != nil {
				return fmt.Errorf("bitbucket.BulkCreateOrUpdateAnnotations: %s", err)
			}
		}
	}

	return nil
}

func (r *ReportAnnotator) annotationFromReviewDogComment(c reviewdog.Comment) openapi.ReportAnnotation {
	a := openapi.NewReportAnnotation()
	switch c.ToolName {
	// TODO: different type of annotation based on tool?
	default:
		a.SetAnnotationType(annotationTypeCodeSmell)
	}

	// hash the output of linter and use it as external id
	a.SetExternalId(hashString(c.Result.Diagnostic.OriginalOutput))
	a.SetSummary(c.Result.Diagnostic.GetMessage())
	a.SetDetails(fmt.Sprintf(`[%s] %s`, c.ToolName, c.Result.Diagnostic.GetMessage()))
	a.SetLine(c.Result.Diagnostic.GetLocation().GetRange().GetStart().GetLine())
	a.SetPath(c.Result.Diagnostic.GetLocation().GetPath())
	if v, ok := r.severityMap[c.Result.Diagnostic.GetSeverity()]; ok {
		a.SetSeverity(v)
	}
	if link := c.Result.Diagnostic.GetCode().GetUrl(); link != "" {
		a.SetLink(link)
	}

	return *a
}

func (r *ReportAnnotator) createOrUpdateReport(ctx context.Context, redportID, title, reportStatus string) error {
	var report = openapi.NewReport()
	report.SetTitle(title)
	// TODO: different report types?
	report.SetReportType(reportTypeBug)
	report.SetReporter(reporter)
	report.SetLogoUrl(logoURL)
	report.SetResult(reportStatus)
	report.SetDetails("Woof-Woof! This report generated for you by reviewdog")

	_, resp, err := r.cli.ReportsApi.CreateOrUpdateReport(
		ctx, r.owner, r.repo, r.sha, redportID,
	).Body(*report).Execute()

	if err := checkAPIError(err, resp, http.StatusOK); err != nil {
		return fmt.Errorf("bitbucket.CreateOrUpdateReport: %s", err)
	}

	return nil
}

func hashString(str string) string {
	h := sha256.New()
	_, _ = h.Write([]byte(str))
	return fmt.Sprintf("%x", h.Sum(nil))
}

func reportID(ids ...string) string {
	return strings.ReplaceAll(strings.ToLower(strings.Join(ids, "-")), " ", "_")
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes Authors.

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 apimachinery

import (
	"context"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/kubernetes/test/e2e/framework"

	"github.com/onsi/ginkgo"
)

var _ = SIGDescribe("apigroup preferred version", func() {
	f := framework.NewDefaultFramework("apigroup-preferred-version")
	ginkgo.It("should validate PreferredVersion for each APIGroup", func() {

		// get list of APIGroup endpoints
		list := &metav1.APIGroupList{}
		err := f.ClientSet.Discovery().RESTClient().Get().AbsPath("/apis/").Do(context.TODO()).Into(list)

		framework.ExpectNoError(err, "Failed to find /apis/")

		for _, group := range list.Groups {
			framework.Logf("Checking APIGroup: %v", group.Name)

			// locate APIGroup endpoint
			checkGroup := &metav1.APIGroup{}
			apiPath := "/apis/" + group.Name + "/"
			err = f.ClientSet.Discovery().RESTClient().Get().AbsPath(apiPath).Do(context.TODO()).Into(checkGroup)
			framework.ExpectNoError(err, "Fail to access: %s", apiPath)

			framework.Logf("PreferredVersion.GroupVersion: %s", checkGroup.PreferredVersion.GroupVersion)
			framework.Logf("Versions found %v", checkGroup.Versions)

			// confirm that the PreferredVersion is a valid version
			match := false
			for _, version := range checkGroup.Versions {
				if version.GroupVersion == checkGroup.PreferredVersion.GroupVersion {
					framework.Logf("%s matches %s", version.GroupVersion, checkGroup.PreferredVersion.GroupVersion)
					match = true
					break
				}
			}
			framework.ExpectEqual(true, match, "failed to find a valid version for PreferredVersion")
		}
	})
})
<commit_msg>Check that empty sets are passed through the test<commit_after>/*
Copyright 2020 The Kubernetes Authors.

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 apimachinery

import (
	"context"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/kubernetes/test/e2e/framework"

	"github.com/onsi/ginkgo"
)

var _ = SIGDescribe("apigroup preferred version", func() {
	f := framework.NewDefaultFramework("apigroup-preferred-version")
	ginkgo.It("should validate PreferredVersion for each APIGroup", func() {

		// get list of APIGroup endpoints
		list := &metav1.APIGroupList{}
		err := f.ClientSet.Discovery().RESTClient().Get().AbsPath("/apis/").Do(context.TODO()).Into(list)
		framework.ExpectNoError(err, "Failed to find /apis/")
		framework.ExpectNotEqual(len(list.Groups), 0, "Missing APIGroups")

		for _, group := range list.Groups {
			framework.Logf("Checking APIGroup: %v", group.Name)

			// locate APIGroup endpoint
			checkGroup := &metav1.APIGroup{}
			apiPath := "/apis/" + group.Name + "/"
			err = f.ClientSet.Discovery().RESTClient().Get().AbsPath(apiPath).Do(context.TODO()).Into(checkGroup)
			framework.ExpectNoError(err, "Fail to access: %s", apiPath)
			framework.ExpectNotEqual(len(checkGroup.Versions), 0, "No version found for %v", group.Name)
			framework.Logf("PreferredVersion.GroupVersion: %s", checkGroup.PreferredVersion.GroupVersion)
			framework.Logf("Versions found %v", checkGroup.Versions)

			// confirm that the PreferredVersion is a valid version
			match := false
			for _, version := range checkGroup.Versions {
				if version.GroupVersion == checkGroup.PreferredVersion.GroupVersion {
					framework.Logf("%s matches %s", version.GroupVersion, checkGroup.PreferredVersion.GroupVersion)
					match = true
					break
				}
			}
			framework.ExpectEqual(true, match, "failed to find a valid version for PreferredVersion")
		}
	})
})
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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 framework

import (
	"fmt"
	"testing"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
	clustercontroller "k8s.io/kubernetes/federation/pkg/federation-controller/cluster"
	"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
	"k8s.io/kubernetes/pkg/master"
	"k8s.io/kubernetes/test/integration/framework"
)

type MemberCluster struct {
	CloseFn framework.CloseFunc
	Config  *master.Config
	Client  clientset.Interface
	Host    string
}

// FederationFixture manages a federation api server and a set of member clusters
type FederationFixture struct {
	APIFixture          *FederationAPIFixture
	DesiredClusterCount int
	Clusters            []*MemberCluster
	ClusterClients      []clientset.Interface
	ClusterController   *clustercontroller.ClusterController
	stopChan            chan struct{}
}

func (f *FederationFixture) SetUp(t *testing.T) {
	if f.APIFixture != nil {
		t.Fatal("Fixture already started")
	}
	if f.DesiredClusterCount < 1 {
		f.DesiredClusterCount = 1
	}
	defer TearDownOnPanic(t, f)

	t.Logf("Starting a federation of %d clusters", f.DesiredClusterCount)

	f.APIFixture = &FederationAPIFixture{}
	f.APIFixture.SetUp(t)

	f.stopChan = make(chan struct{})
	monitorPeriod := 1 * time.Second
	clustercontroller.StartClusterController(f.APIFixture.NewConfig(), f.stopChan, monitorPeriod)

	f.startClusters()
}

func (f *FederationFixture) startClusters() {
	fedClient := f.APIFixture.NewClient("federation-fixture")
	for i := 0; i < f.DesiredClusterCount; i++ {
		config := framework.NewMasterConfig()
		_, _, closeFn := framework.RunAMaster(config)
		host := config.GenericConfig.LoopbackClientConfig.Host

		// Use fmt to ensure the output will be visible when run with go test -v
		fmt.Printf("Federated cluster %d serving on %s", i, host)

		clusterClient := clientset.NewForConfigOrDie(config.GenericConfig.LoopbackClientConfig)
		f.Clusters = append(f.Clusters, &MemberCluster{
			CloseFn: closeFn,
			Config:  config,
			Client:  clusterClient,
			Host:    host,
		})

		f.ClusterClients = append(f.ClusterClients, clusterClient)

		cluster := &federationapi.Cluster{
			ObjectMeta: metav1.ObjectMeta{
				Name: fmt.Sprintf("cluster-%d", i),
			},
			Spec: federationapi.ClusterSpec{
				ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{
					{
						ClientCIDR:    "0.0.0.0/0",
						ServerAddress: host,
					},
				},
				// Use insecure access
				SecretRef: nil,
			},
		}
		fedClient.FederationV1beta1().Clusters().Create(cluster)
	}
}

func (f *FederationFixture) TearDown(t *testing.T) {
	if f.stopChan != nil {
		close(f.stopChan)
		f.stopChan = nil
	}
	for _, cluster := range f.Clusters {
		cluster.CloseFn()
	}
	f.Clusters = nil
	if f.APIFixture != nil {
		f.APIFixture.TearDown(t)
		f.APIFixture = nil
	}
}
<commit_msg>fed: Refactor integ test fixture to support cluster addition by test<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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 framework

import (
	"fmt"
	"testing"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
	federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
	clustercontroller "k8s.io/kubernetes/federation/pkg/federation-controller/cluster"
	"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
	"k8s.io/kubernetes/pkg/master"
	"k8s.io/kubernetes/test/integration/framework"
)

type MemberCluster struct {
	CloseFn framework.CloseFunc
	Config  *master.Config
	Client  clientset.Interface
	Host    string
}

// FederationFixture manages a federation api server and a set of member clusters
type FederationFixture struct {
	APIFixture          *FederationAPIFixture
	DesiredClusterCount int
	Clusters            []*MemberCluster
	ClusterClients      []clientset.Interface
	ClusterController   *clustercontroller.ClusterController
	fedClient           federationclientset.Interface
	stopChan            chan struct{}
}

func (f *FederationFixture) SetUp(t *testing.T) {
	if f.APIFixture != nil {
		t.Fatal("Fixture already started")
	}
	if f.DesiredClusterCount < 1 {
		f.DesiredClusterCount = 1
	}
	defer TearDownOnPanic(t, f)

	t.Logf("Starting a federation of %d clusters", f.DesiredClusterCount)

	f.APIFixture = &FederationAPIFixture{}
	f.APIFixture.SetUp(t)

	f.stopChan = make(chan struct{})
	monitorPeriod := 1 * time.Second
	clustercontroller.StartClusterController(f.APIFixture.NewConfig(), f.stopChan, monitorPeriod)

	f.fedClient = f.APIFixture.NewClient("federation-fixture")
	for i := 0; i < f.DesiredClusterCount; i++ {
		f.StartCluster(t)
	}
}

func (f *FederationFixture) StartCluster(t *testing.T) {
	config := framework.NewMasterConfig()
	_, _, closeFn := framework.RunAMaster(config)
	host := config.GenericConfig.LoopbackClientConfig.Host

	clusterClient := clientset.NewForConfigOrDie(config.GenericConfig.LoopbackClientConfig)
	f.ClusterClients = append(f.ClusterClients, clusterClient)
	f.Clusters = append(f.Clusters, &MemberCluster{
		CloseFn: closeFn,
		Config:  config,
		Client:  clusterClient,
		Host:    host,
	})

	clusterId := len(f.ClusterClients)

	t.Logf("Federated cluster %d serving on %s", clusterId, host)

	cluster := &federationapi.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name: fmt.Sprintf("cluster-%d", clusterId),
		},
		Spec: federationapi.ClusterSpec{
			ServerAddressByClientCIDRs: []federationapi.ServerAddressByClientCIDR{
				{
					ClientCIDR:    "0.0.0.0/0",
					ServerAddress: host,
				},
			},
			// Use insecure access
			SecretRef: nil,
		},
	}
	f.fedClient.FederationV1beta1().Clusters().Create(cluster)
}

func (f *FederationFixture) TearDown(t *testing.T) {
	if f.stopChan != nil {
		close(f.stopChan)
		f.stopChan = nil
	}
	for _, cluster := range f.Clusters {
		cluster.CloseFn()
	}
	f.Clusters = nil
	if f.APIFixture != nil {
		f.APIFixture.TearDown(t)
		f.APIFixture = nil
	}
}
<|endoftext|>


<commit_before>package mailgun

import (
	"errors"
	"github.com/mbanzon/simplehttp"
)

type Message struct {
	from    string
	to      []string
	cc      []string
	bcc     []string
	subject string
	text    string
	html    string
}

type sendMessageResponse struct {
	Message string `json:"message"`
	Id      string `json:"id"`
}

func NewMessage(from string, subject string, text string, to ...string) *Message {
	return &Message{ from: from, subject: subject, text: text, to: to }
}

func (m *Message) AddRecipient(recipient string) {
	m.to = append(m.to, recipient)
}

func (m *Message) AddCC(recipient string) {
	m.cc = append(m.cc, recipient)
}

func (m *Message) AddBCC(recipient string) {
	m.bcc = append(m.bcc, recipient)
}

func (m *Message) SetHtml(html string) {
	m.html = html
}

func (m *mailgunImpl) Send(message *Message) (mes string, id string, err error) {
	if !message.validateMessage() {
		err = errors.New("Message not valid")
	} else {
		r := simplehttp.NewPostRequest(generateApiUrl(m, messagesEndpoint))
		r.AddFormValue("from", message.from)
		r.AddFormValue("subject", message.subject)
		r.AddFormValue("text", message.text)
		for _, to := range message.to {
			r.AddFormValue("to", to)
		}
		for _, cc := range message.cc {
			r.AddFormValue("cc", cc)
		}
		for _, bcc := range message.bcc {
			r.AddFormValue("bcc", bcc)
		}
		if message.html != "" {
			r.AddFormValue("html", message.html)
		}
		r.SetBasicAuth(basicAuthUser, m.ApiKey())

		var response sendMessageResponse
		err = r.MakeJSONRequest(&response)
		if err == nil {
			mes = response.Message
			id = response.Id
		}
	}

	return
}

func (m *Message) validateMessage() bool {
	if m == nil {
		return false
	}

	if m.from == "" {
		return false
	}

	if !validateAddressList(m.to, true) {
		return false
	}

	if !validateAddressList(m.cc, false) {
		return false
	}

	if !validateAddressList(m.bcc, false) {
		return false
	}

	if m.text == "" {
		return false
	}

	return true
}

func validateAddressList(list []string, requireOne bool) bool {
	hasOne := false

	if list == nil {
		return !requireOne
	} else {
		for _, a := range list {
			if a == "" {
				return false
			} else {
				hasOne = hasOne || true
			}
		}
	}

	return hasOne
}
<commit_msg>Added tags to messages.<commit_after>package mailgun

import (
	"errors"
	"github.com/mbanzon/simplehttp"
)

type Message struct {
	from    string
	to      []string
	cc      []string
	bcc     []string
	subject string
	text    string
	html    string
	tags	[]string
}

type sendMessageResponse struct {
	Message string `json:"message"`
	Id      string `json:"id"`
}

func NewMessage(from string, subject string, text string, to ...string) *Message {
	return &Message{ from: from, subject: subject, text: text, to: to }
}

func (m *Message) AddRecipient(recipient string) {
	m.to = append(m.to, recipient)
}

func (m *Message) AddCC(recipient string) {
	m.cc = append(m.cc, recipient)
}

func (m *Message) AddBCC(recipient string) {
	m.bcc = append(m.bcc, recipient)
}

func (m *Message) SetHtml(html string) {
	m.html = html
}
func (m *Message) AddTag(tag string) {
	m.tags = append(m.tags, tag)
}

func (m *mailgunImpl) Send(message *Message) (mes string, id string, err error) {
	if !message.validateMessage() {
		err = errors.New("Message not valid")
	} else {
		r := simplehttp.NewPostRequest(generateApiUrl(m, messagesEndpoint))
		r.AddFormValue("from", message.from)
		r.AddFormValue("subject", message.subject)
		r.AddFormValue("text", message.text)
		for _, to := range message.to {
			r.AddFormValue("to", to)
		}
		for _, cc := range message.cc {
			r.AddFormValue("cc", cc)
		}
		for _, bcc := range message.bcc {
			r.AddFormValue("bcc", bcc)
		}
		for _, tag := range message.tags {
			r.AddFormValue("o:tag", tag)
		}
		if message.html != "" {
			r.AddFormValue("html", message.html)
		}
		r.SetBasicAuth(basicAuthUser, m.ApiKey())

		var response sendMessageResponse
		err = r.MakeJSONRequest(&response)
		if err == nil {
			mes = response.Message
			id = response.Id
		}
	}

	return
}

func (m *Message) validateMessage() bool {
	if m == nil {
		return false
	}

	if m.from == "" {
		return false
	}

	if !validateAddressList(m.to, true) {
		return false
	}

	if !validateAddressList(m.cc, false) {
		return false
	}

	if !validateAddressList(m.bcc, false) {
		return false
	}

	if m.text == "" {
		return false
	}

	return true
}

func validateAddressList(list []string, requireOne bool) bool {
	hasOne := false

	if list == nil {
		return !requireOne
	} else {
		for _, a := range list {
			if a == "" {
				return false
			} else {
				hasOne = hasOne || true
			}
		}
	}

	return hasOne
}
<|endoftext|>