<commit_before>package auth

import (
	"fmt"
	"time"

	"github.com/pkg/errors"
)

var errEmailVerifyHashExists = errors.New("DB: Email verify hash already exists")
var errInvalidEmailVerifyHash = errors.New("DB: Invalid verify code")
var errInvalidRenewTimeUTC = errors.New("DB: Invalid RenewTimeUTC")
var errInvalidSessionHash = errors.New("DB: Invalid SessionHash")
var errRememberMeSelectorExists = errors.New("DB: RememberMe selector already exists")
var errUserNotFound = errors.New("DB: User not found")
var errLoginNotFound = errors.New("DB: Login not found")
var errInvalidCredentials = errors.New("DB: Invalid Credentials")
var errSessionNotFound = errors.New("DB: Session not found")
var errSessionAlreadyExists = errors.New("DB: Session already exists")
var errRememberMeNotFound = errors.New("DB: RememberMe not found")
var errRememberMeNeedsRenew = errors.New("DB: RememberMe needs to be renewed")
var errRememberMeExpired = errors.New("DB: RememberMe is expired")
var errUserAlreadyExists = errors.New("DB: User already exists")

// Backender interface contains all the methods needed to read and write users, sessions and logins
type Backender interface {
	userBackender
	sessionBackender
	backendCloser
	Clone() Backender
}

type backendCloser interface {
	Close() error
}

// UserBackender interface holds methods for user management
type UserBackender interface {
	Clone() UserBackender
	userBackender
	backendCloser
}

type userBackender interface {
	AddUser(email string, info map[string]interface{}) (string, error)
	AddUserFull(email, password string, info map[string]interface{}) (*User, error)
	GetUser(email string) (*User, error)
	UpdateUser(userID, password string, info map[string]interface{}) error
	UpdateInfo(userID string, info map[string]interface{}) error
	UpdatePassword(userID, newPassword string) error

	Login(email, password string) error
	LoginAndGetUser(email, password string) (*User, error)
	AddSecondaryEmail(userID, secondaryEmail string) error
	UpdatePrimaryEmail(userID, newPrimaryEmail string) error
}

// SessionBackender interface holds methods for session management
type SessionBackender interface {
	Clone() SessionBackender
	sessionBackender
	backendCloser
}

type sessionBackender interface {
	CreateEmailSession(email string, info map[string]interface{}, emailVerifyHash, csrfToken string) error
	GetEmailSession(verifyHash string) (*emailSession, error)
	UpdateEmailSession(verifyHash string, userID string) error
	DeleteEmailSession(verifyHash string) error

	CreateSession(userID, email string, info map[string]interface{}, sessionHash, csrfToken string, sessionRenewTimeUTC, sessionExpireTimeUTC time.Time) (*LoginSession, error)
	GetSession(sessionHash string) (*LoginSession, error)
	UpdateSession(sessionHash string, renewTimeUTC, expireTimeUTC time.Time) error
	DeleteSession(sessionHash string) error
	InvalidateSessions(email string) error

	CreateRememberMe(userID, email string, rememberMeSelector, rememberMeTokenHash string, renewTimeUTC, expireTimeUTC time.Time) (*rememberMeSession, error)
	GetRememberMe(selector string) (*rememberMeSession, error)
	UpdateRememberMe(selector string, renewTimeUTC time.Time) error
	DeleteRememberMe(selector string) error
}

type emailSession struct {
	UserID          string                 `bson:"userID"    json:"userID"`
	Email           string                 `bson:"email"     json:"email"`
	Info            map[string]interface{} `bson:"info"      json:"info"`
	EmailVerifyHash string                 `bson:"_id"       json:"emailVerifyHash"`
	CSRFToken       string                 `bson:"csrfToken" json:"csrfToken"`
}

type user struct {
	UserID            string
	PrimaryEmail      string
	PasswordHash      string
	Info              map[string]interface{}
	LockoutEndTimeUTC *time.Time
	AccessFailedCount int
}

// User is the struct which holds user information
type User struct {
	UserID string                 `json:"userID"`
	Email  string                 `json:"email"`
	Info   map[string]interface{} `json:"info"`
}

// LoginSession is the struct which holds session information
type LoginSession struct {
	UserID        string                 `bson:"userID"        json:"userID"`
	Email         string                 `bson:"email"         json:"email"`
	Info          map[string]interface{} `bson:"info"          json:"info"`
	SessionHash   string                 `bson:"_id"           json:"sessionHash"`
	CSRFToken     string                 `bson:"csrfToken"     json:"csrfToken"`
	RenewTimeUTC  time.Time              `bson:"renewTimeUTC"  json:"renewTimeUTC"`
	ExpireTimeUTC time.Time              `bson:"expireTimeUTC" json:"expireTimeUTC"`
}

// GetInfo will return the named info as an interface{}
func (l *LoginSession) GetInfo(name string) interface{} {
	if l == nil {
		return nil
	}
	return GetInfo(l.Info, name)
}

// GetInfoString will return the named info as a string
func (l *LoginSession) GetInfoString(name string) string {
	if l == nil {
		return ""
	}
	return GetInfoString(l.Info, name)
}

// GetInfoStrings will return the named info as an array of strings
func (l *LoginSession) GetInfoStrings(name string) []string {
	if l == nil {
		return nil
	}
	return GetInfoStrings(l.Info, name)
}

// GetInfo will return the named info as an interface{}
func GetInfo(info map[string]interface{}, name string) interface{} {
	if info == nil {
		return nil
	}
	return info[name]
}

// GetInfoString will return the named info as a string
func GetInfoString(info map[string]interface{}, name string) string {
	v := GetInfo(info, name)
	if v == nil {
		return ""
	}
	if i, ok := v.(string); ok {
		return i
	}
	return fmt.Sprint(v)
}

// GetInfoStrings will return the named info as an array of strings
func GetInfoStrings(info map[string]interface{}, name string) []string {
	i := GetInfo(info, name)
	switch v := i.(type) {
	case []string:
		return v
	case []interface{}:
		strArr := make([]string, len(v))
		for i, str := range v {
			if s, ok := str.(string); ok {
				strArr[i] = s
			} else {
				strArr[i] = fmt.Sprint(str)
			}
		}
		return strArr
	}
	return nil
}

type rememberMeSession struct {
	UserID        string    `bson:"userID"        json:"userID"`
	Email         string    `bson:"email"         json:"email"`
	Selector      string    `bson:"_id"           json:"selector"`
	TokenHash     string    `bson:"tokenHash"     json:"tokenHash"`
	RenewTimeUTC  time.Time `bson:"renewTimeUTC"  json:"renewTimeUTC"`
	ExpireTimeUTC time.Time `bson:"expireTimeUTC" json:"expireTimeUTC"`
}

type loginProvider struct {
	LoginProviderID   int
	Name              string
	OAuthClientID     string
	OAuthClientSecret string
	OAuthURL          string
}

// AuthError struct holds detailed auth error info
type AuthError struct {
	message    string
	innerError error
	shouldLog  bool
	error
}

func newLoggedError(message string, innerError error) *AuthError {
	return &AuthError{message: message, innerError: innerError, shouldLog: true}
}

func newAuthError(message string, innerError error) *AuthError {
	return &AuthError{message: message, innerError: innerError}
}

func (a *AuthError) Error() string {
	return a.message
}

func (a *AuthError) Trace() string {
	trace := a.message + "\n"
	indent := "  "
	inner := a.innerError
	for inner != nil {
		trace += indent + inner.Error() + "\n"
		e, ok := inner.(*AuthError)
		if !ok {
			break
		}
		indent += "  "
		inner = e.innerError
	}
	return trace
}

type backend struct {
	u UserBackender
	s SessionBackender
	backendCloser
}

// NewBackend returns a Backender from a UserBackender, LoginBackender and SessionBackender
func NewBackend(u UserBackender, s SessionBackender) Backender {
	return &backend{u: u, s: s}
}

func (b *backend) Clone() Backender {
	return &backend{u: b.u.Clone(), s: b.s.Clone()}
}

func (b *backend) Login(email, password string) error {
	return b.u.Login(email, password)
}

func (b *backend) LoginAndGetUser(email, password string) (*User, error) {
	return b.u.LoginAndGetUser(email, password)
}

func (b *backend) CreateSession(userID, email string, info map[string]interface{}, sessionHash, csrfToken string, sessionRenewTimeUTC, sessionExpireTimeUTC time.Time) (*LoginSession, error) {
	return b.s.CreateSession(userID, email, info, sessionHash, csrfToken, sessionRenewTimeUTC, sessionExpireTimeUTC)
}

func (b *backend) GetSession(sessionHash string) (*LoginSession, error) {
	return b.s.GetSession(sessionHash)
}

func (b *backend) UpdateSession(sessionHash string, renewTimeUTC, expireTimeUTC time.Time) error {
	return b.s.UpdateSession(sessionHash, renewTimeUTC, expireTimeUTC)
}

func (b *backend) CreateRememberMe(userID, email string, rememberMeSelector, rememberMeTokenHash string, renewTimeUTC, expireTimeUTC time.Time) (*rememberMeSession, error) {
	return b.s.CreateRememberMe(userID, email, rememberMeSelector, rememberMeTokenHash, renewTimeUTC, expireTimeUTC)
}

func (b *backend) GetRememberMe(selector string) (*rememberMeSession, error) {
	return b.s.GetRememberMe(selector)
}

func (b *backend) UpdateRememberMe(selector string, renewTimeUTC time.Time) error {
	return b.s.UpdateRememberMe(selector, renewTimeUTC)
}

func (b *backend) CreateEmailSession(email string, info map[string]interface{}, emailVerifyHash, csrfToken string) error {
	return b.s.CreateEmailSession(email, info, emailVerifyHash, csrfToken)
}

func (b *backend) GetEmailSession(emailVerifyHash string) (*emailSession, error) {
	return b.s.GetEmailSession(emailVerifyHash)
}

func (b *backend) UpdateEmailSession(emailVerifyHash string, userID string) error {
	return b.s.UpdateEmailSession(emailVerifyHash, userID)
}

func (b *backend) DeleteEmailSession(emailVerifyHash string) error {
	return b.s.DeleteEmailSession(emailVerifyHash)
}

func (b *backend) AddUser(email string, info map[string]interface{}) (string, error) {
	return b.u.AddUser(email, info)
}

func (b *backend) AddUserFull(email, password string, info map[string]interface{}) (*User, error) {
	return b.u.AddUserFull(email, password, info)
}

func (b *backend) GetUser(email string) (*User, error) {
	return b.u.GetUser(email)
}

func (b *backend) UpdateUser(userID, password string, info map[string]interface{}) error {
	return b.u.UpdateUser(userID, password, info)
}

func (b *backend) UpdateInfo(userID string, info map[string]interface{}) error {
	return b.u.UpdateInfo(userID, info)
}

func (b *backend) AddSecondaryEmail(userID string, secondaryEmail string) error {
	return b.u.AddSecondaryEmail(userID, secondaryEmail)
}

func (b *backend) UpdatePrimaryEmail(userID, secondaryEmail string) error {
	return b.u.UpdatePrimaryEmail(userID, secondaryEmail)
}

func (b *backend) UpdatePassword(userID, password string) error {
	return b.u.UpdatePassword(userID, password)
}

func (b *backend) DeleteSession(sessionHash string) error {
	return b.s.DeleteSession(sessionHash)
}

func (b *backend) InvalidateSessions(email string) error {
	return b.s.InvalidateSessions(email)
}

func (b *backend) DeleteRememberMe(selector string) error {
	return b.s.DeleteRememberMe(selector)
}

func (b *backend) Close() error {
	if err := b.s.Close(); err != nil {
		return err
	}
	return b.u.Close()
}
<commit_msg>Clone is a no-op for backend<commit_after>package auth

import (
	"fmt"
	"time"

	"github.com/pkg/errors"
)

var errEmailVerifyHashExists = errors.New("DB: Email verify hash already exists")
var errInvalidEmailVerifyHash = errors.New("DB: Invalid verify code")
var errInvalidRenewTimeUTC = errors.New("DB: Invalid RenewTimeUTC")
var errInvalidSessionHash = errors.New("DB: Invalid SessionHash")
var errRememberMeSelectorExists = errors.New("DB: RememberMe selector already exists")
var errUserNotFound = errors.New("DB: User not found")
var errLoginNotFound = errors.New("DB: Login not found")
var errInvalidCredentials = errors.New("DB: Invalid Credentials")
var errSessionNotFound = errors.New("DB: Session not found")
var errSessionAlreadyExists = errors.New("DB: Session already exists")
var errRememberMeNotFound = errors.New("DB: RememberMe not found")
var errRememberMeNeedsRenew = errors.New("DB: RememberMe needs to be renewed")
var errRememberMeExpired = errors.New("DB: RememberMe is expired")
var errUserAlreadyExists = errors.New("DB: User already exists")

// Backender interface contains all the methods needed to read and write users, sessions and logins
type Backender interface {
	userBackender
	sessionBackender
	backendCloser
	Clone() Backender
}

type backendCloser interface {
	Close() error
}

// UserBackender interface holds methods for user management
type UserBackender interface {
	userBackender
	backendCloser
}

type userBackender interface {
	AddUser(email string, info map[string]interface{}) (string, error)
	AddUserFull(email, password string, info map[string]interface{}) (*User, error)
	GetUser(email string) (*User, error)
	UpdateUser(userID, password string, info map[string]interface{}) error
	UpdateInfo(userID string, info map[string]interface{}) error
	UpdatePassword(userID, newPassword string) error

	Login(email, password string) error
	LoginAndGetUser(email, password string) (*User, error)
	AddSecondaryEmail(userID, secondaryEmail string) error
	UpdatePrimaryEmail(userID, newPrimaryEmail string) error
}

// SessionBackender interface holds methods for session management
type SessionBackender interface {
	sessionBackender
	backendCloser
}

type sessionBackender interface {
	CreateEmailSession(email string, info map[string]interface{}, emailVerifyHash, csrfToken string) error
	GetEmailSession(verifyHash string) (*emailSession, error)
	UpdateEmailSession(verifyHash string, userID string) error
	DeleteEmailSession(verifyHash string) error

	CreateSession(userID, email string, info map[string]interface{}, sessionHash, csrfToken string, sessionRenewTimeUTC, sessionExpireTimeUTC time.Time) (*LoginSession, error)
	GetSession(sessionHash string) (*LoginSession, error)
	UpdateSession(sessionHash string, renewTimeUTC, expireTimeUTC time.Time) error
	DeleteSession(sessionHash string) error
	InvalidateSessions(email string) error

	CreateRememberMe(userID, email string, rememberMeSelector, rememberMeTokenHash string, renewTimeUTC, expireTimeUTC time.Time) (*rememberMeSession, error)
	GetRememberMe(selector string) (*rememberMeSession, error)
	UpdateRememberMe(selector string, renewTimeUTC time.Time) error
	DeleteRememberMe(selector string) error
}

type emailSession struct {
	UserID          string                 `bson:"userID"    json:"userID"`
	Email           string                 `bson:"email"     json:"email"`
	Info            map[string]interface{} `bson:"info"      json:"info"`
	EmailVerifyHash string                 `bson:"_id"       json:"emailVerifyHash"`
	CSRFToken       string                 `bson:"csrfToken" json:"csrfToken"`
}

type user struct {
	UserID            string
	PrimaryEmail      string
	PasswordHash      string
	Info              map[string]interface{}
	LockoutEndTimeUTC *time.Time
	AccessFailedCount int
}

// User is the struct which holds user information
type User struct {
	UserID string                 `json:"userID"`
	Email  string                 `json:"email"`
	Info   map[string]interface{} `json:"info"`
}

// LoginSession is the struct which holds session information
type LoginSession struct {
	UserID        string                 `bson:"userID"        json:"userID"`
	Email         string                 `bson:"email"         json:"email"`
	Info          map[string]interface{} `bson:"info"          json:"info"`
	SessionHash   string                 `bson:"_id"           json:"sessionHash"`
	CSRFToken     string                 `bson:"csrfToken"     json:"csrfToken"`
	RenewTimeUTC  time.Time              `bson:"renewTimeUTC"  json:"renewTimeUTC"`
	ExpireTimeUTC time.Time              `bson:"expireTimeUTC" json:"expireTimeUTC"`
}

// GetInfo will return the named info as an interface{}
func (l *LoginSession) GetInfo(name string) interface{} {
	if l == nil {
		return nil
	}
	return GetInfo(l.Info, name)
}

// GetInfoString will return the named info as a string
func (l *LoginSession) GetInfoString(name string) string {
	if l == nil {
		return ""
	}
	return GetInfoString(l.Info, name)
}

// GetInfoStrings will return the named info as an array of strings
func (l *LoginSession) GetInfoStrings(name string) []string {
	if l == nil {
		return nil
	}
	return GetInfoStrings(l.Info, name)
}

// GetInfo will return the named info as an interface{}
func GetInfo(info map[string]interface{}, name string) interface{} {
	if info == nil {
		return nil
	}
	return info[name]
}

// GetInfoString will return the named info as a string
func GetInfoString(info map[string]interface{}, name string) string {
	v := GetInfo(info, name)
	if v == nil {
		return ""
	}
	if i, ok := v.(string); ok {
		return i
	}
	return fmt.Sprint(v)
}

// GetInfoStrings will return the named info as an array of strings
func GetInfoStrings(info map[string]interface{}, name string) []string {
	i := GetInfo(info, name)
	switch v := i.(type) {
	case []string:
		return v
	case []interface{}:
		strArr := make([]string, len(v))
		for i, str := range v {
			if s, ok := str.(string); ok {
				strArr[i] = s
			} else {
				strArr[i] = fmt.Sprint(str)
			}
		}
		return strArr
	}
	return nil
}

type rememberMeSession struct {
	UserID        string    `bson:"userID"        json:"userID"`
	Email         string    `bson:"email"         json:"email"`
	Selector      string    `bson:"_id"           json:"selector"`
	TokenHash     string    `bson:"tokenHash"     json:"tokenHash"`
	RenewTimeUTC  time.Time `bson:"renewTimeUTC"  json:"renewTimeUTC"`
	ExpireTimeUTC time.Time `bson:"expireTimeUTC" json:"expireTimeUTC"`
}

type loginProvider struct {
	LoginProviderID   int
	Name              string
	OAuthClientID     string
	OAuthClientSecret string
	OAuthURL          string
}

// AuthError struct holds detailed auth error info
type AuthError struct {
	message    string
	innerError error
	shouldLog  bool
	error
}

func newLoggedError(message string, innerError error) *AuthError {
	return &AuthError{message: message, innerError: innerError, shouldLog: true}
}

func newAuthError(message string, innerError error) *AuthError {
	return &AuthError{message: message, innerError: innerError}
}

func (a *AuthError) Error() string {
	return a.message
}

func (a *AuthError) Trace() string {
	trace := a.message + "\n"
	indent := "  "
	inner := a.innerError
	for inner != nil {
		trace += indent + inner.Error() + "\n"
		e, ok := inner.(*AuthError)
		if !ok {
			break
		}
		indent += "  "
		inner = e.innerError
	}
	return trace
}

type backend struct {
	u UserBackender
	s SessionBackender
	backendCloser
}

// NewBackend returns a Backender from a UserBackender, LoginBackender and SessionBackender
func NewBackend(u UserBackender, s SessionBackender) Backender {
	return &backend{u: u, s: s}
}

func (b *backend) Clone() Backender {
	return b
}

func (b *backend) Login(email, password string) error {
	return b.u.Login(email, password)
}

func (b *backend) LoginAndGetUser(email, password string) (*User, error) {
	return b.u.LoginAndGetUser(email, password)
}

func (b *backend) CreateSession(userID, email string, info map[string]interface{}, sessionHash, csrfToken string, sessionRenewTimeUTC, sessionExpireTimeUTC time.Time) (*LoginSession, error) {
	return b.s.CreateSession(userID, email, info, sessionHash, csrfToken, sessionRenewTimeUTC, sessionExpireTimeUTC)
}

func (b *backend) GetSession(sessionHash string) (*LoginSession, error) {
	return b.s.GetSession(sessionHash)
}

func (b *backend) UpdateSession(sessionHash string, renewTimeUTC, expireTimeUTC time.Time) error {
	return b.s.UpdateSession(sessionHash, renewTimeUTC, expireTimeUTC)
}

func (b *backend) CreateRememberMe(userID, email string, rememberMeSelector, rememberMeTokenHash string, renewTimeUTC, expireTimeUTC time.Time) (*rememberMeSession, error) {
	return b.s.CreateRememberMe(userID, email, rememberMeSelector, rememberMeTokenHash, renewTimeUTC, expireTimeUTC)
}

func (b *backend) GetRememberMe(selector string) (*rememberMeSession, error) {
	return b.s.GetRememberMe(selector)
}

func (b *backend) UpdateRememberMe(selector string, renewTimeUTC time.Time) error {
	return b.s.UpdateRememberMe(selector, renewTimeUTC)
}

func (b *backend) CreateEmailSession(email string, info map[string]interface{}, emailVerifyHash, csrfToken string) error {
	return b.s.CreateEmailSession(email, info, emailVerifyHash, csrfToken)
}

func (b *backend) GetEmailSession(emailVerifyHash string) (*emailSession, error) {
	return b.s.GetEmailSession(emailVerifyHash)
}

func (b *backend) UpdateEmailSession(emailVerifyHash string, userID string) error {
	return b.s.UpdateEmailSession(emailVerifyHash, userID)
}

func (b *backend) DeleteEmailSession(emailVerifyHash string) error {
	return b.s.DeleteEmailSession(emailVerifyHash)
}

func (b *backend) AddUser(email string, info map[string]interface{}) (string, error) {
	return b.u.AddUser(email, info)
}

func (b *backend) AddUserFull(email, password string, info map[string]interface{}) (*User, error) {
	return b.u.AddUserFull(email, password, info)
}

func (b *backend) GetUser(email string) (*User, error) {
	return b.u.GetUser(email)
}

func (b *backend) UpdateUser(userID, password string, info map[string]interface{}) error {
	return b.u.UpdateUser(userID, password, info)
}

func (b *backend) UpdateInfo(userID string, info map[string]interface{}) error {
	return b.u.UpdateInfo(userID, info)
}

func (b *backend) AddSecondaryEmail(userID string, secondaryEmail string) error {
	return b.u.AddSecondaryEmail(userID, secondaryEmail)
}

func (b *backend) UpdatePrimaryEmail(userID, secondaryEmail string) error {
	return b.u.UpdatePrimaryEmail(userID, secondaryEmail)
}

func (b *backend) UpdatePassword(userID, password string) error {
	return b.u.UpdatePassword(userID, password)
}

func (b *backend) DeleteSession(sessionHash string) error {
	return b.s.DeleteSession(sessionHash)
}

func (b *backend) InvalidateSessions(email string) error {
	return b.s.InvalidateSessions(email)
}

func (b *backend) DeleteRememberMe(selector string) error {
	return b.s.DeleteRememberMe(selector)
}

func (b *backend) Close() error {
	if err := b.s.Close(); err != nil {
		return err
	}
	return b.u.Close()
}
<|endoftext|>


<commit_before>package monitor

// Target is a URL, which has to be polled for availability.
type Target struct {
	// Unique identifier of this target. Targets' IDs cannot intercept. Target's
	// ID must be constant between GetTargets() calls.
	ID uint
	// User-supplied target title, used purely for display.
	Title string
	// The HTTP URL to poll.
	URL string
}

// TargetsGetter is an interface of targets source. Monitor uses it to retrieve
// list targets on every polling iteration. External frontend may implement
// this interface to store targets in a DB or in a configuration file.
type TargetsGetter interface {
	GetTargets() ([]Target, error)
}
<commit_msg>:pencil2: Implement Stringer to monitor.Target<commit_after>package monitor

import "fmt"

// Target is a URL, which has to be polled for availability.
type Target struct {
	// Unique identifier of this target. Targets' IDs cannot intercept. Target's
	// ID must be constant between GetTargets() calls.
	ID uint
	// User-supplied target title, used purely for display.
	Title string
	// The HTTP URL to poll.
	URL string
}

func (t Target) String() string {
	return fmt.Sprintf("Target %v { %q, %q }", t.ID, t.Title, t.URL)
}

// TargetsGetter is an interface of targets source. Monitor uses it to retrieve
// list targets on every polling iteration. External frontend may implement
// this interface to store targets in a DB or in a configuration file.
type TargetsGetter interface {
	GetTargets() ([]Target, error)
}
<|endoftext|>


<commit_before>//: ----------------------------------------------------------------------------
//: Copyright (C) 2017 Verizon.  All Rights Reserved.
//: All Rights Reserved
//:
//: file:    monitor.go
//: details: TODO
//: author:  Mehrdad Arshad Rad
//: date:    02/01/2017
//:
//: Licensed under the Apache License, Version 2.0 (the "License");
//: you may not use this file except in compliance with the License.
//: You may obtain a copy of the License at
//:
//:     http://www.apache.org/licenses/LICENSE-2.0
//:
//: Unless required by applicable law or agreed to in writing, software
//: distributed under the License is distributed on an "AS IS" BASIS,
//: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//: See the License for the specific language governing permissions and
//: limitations under the License.
//: ----------------------------------------------------------------------------
package main

import (
	"flag"
	"log"

	"github.com/VerizonDigital/vflow/monitor/store"
)

type options struct {
	DBType       string
	VFlowHost    string
	InfluxDBAPI  string
	InfluxDBName string
	TSDBAPI      string
}

var opts = options{
	DBType:       "influxdb",
	VFlowHost:    "http://localhost:8080",
	InfluxDBAPI:  "http://localhost:8086",
	TSDBAPI:      "http://localhost:4242",
	InfluxDBName: "vflow",
}

func init() {

	flag.StringVar(&opts.DBType, "db-type", opts.DBType, "database type name to ingest")
	flag.StringVar(&opts.VFlowHost, "vflow-host", opts.VFlowHost, "vflow host address and port")
	flag.StringVar(&opts.InfluxDBAPI, "influxdb-api-addr", opts.InfluxDBAPI, "influxdb api address")
	flag.StringVar(&opts.InfluxDBName, "influxdb-db-name", opts.InfluxDBName, "influxdb database name")
	flag.StringVar(&opts.TSDBAPI, "tsdb-api-addr", opts.TSDBAPI, "tsdb api address")

	flag.Parse()
}

func main() {
	var m = make(map[string]store.Monitor)

	m["influxdb"] = store.InfluxDB{
		API:   opts.InfluxDBAPI,
		DB:    opts.InfluxDBName,
		VHost: opts.VFlowHost,
	}

	m["tsdb"] = store.TSDB{
		API:   opts.TSDBAPI,
		VHost: opts.VFlowHost,
	}

	if _, ok := m[opts.DBType]; !ok {
		log.Fatalf("the storage: %s is not available", opts.DBType)
	}

	if err := m[opts.DBType].Netflow(); err != nil {
		log.Println(err)
	}
	if err := m[opts.DBType].System(); err != nil {
		log.Println(err)
	}
}
<commit_msg>change vflow stats port to 8081<commit_after>//: ----------------------------------------------------------------------------
//: Copyright (C) 2017 Verizon.  All Rights Reserved.
//: All Rights Reserved
//:
//: file:    monitor.go
//: details: TODO
//: author:  Mehrdad Arshad Rad
//: date:    02/01/2017
//:
//: Licensed under the Apache License, Version 2.0 (the "License");
//: you may not use this file except in compliance with the License.
//: You may obtain a copy of the License at
//:
//:     http://www.apache.org/licenses/LICENSE-2.0
//:
//: Unless required by applicable law or agreed to in writing, software
//: distributed under the License is distributed on an "AS IS" BASIS,
//: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//: See the License for the specific language governing permissions and
//: limitations under the License.
//: ----------------------------------------------------------------------------
package main

import (
	"flag"
	"log"

	"github.com/VerizonDigital/vflow/monitor/store"
)

type options struct {
	DBType       string
	VFlowHost    string
	InfluxDBAPI  string
	InfluxDBName string
	TSDBAPI      string
}

var opts = options{
	DBType:       "influxdb",
	VFlowHost:    "http://localhost:8081",
	InfluxDBAPI:  "http://localhost:8086",
	TSDBAPI:      "http://localhost:4242",
	InfluxDBName: "vflow",
}

func init() {

	flag.StringVar(&opts.DBType, "db-type", opts.DBType, "database type name to ingest")
	flag.StringVar(&opts.VFlowHost, "vflow-host", opts.VFlowHost, "vflow host address and port")
	flag.StringVar(&opts.InfluxDBAPI, "influxdb-api-addr", opts.InfluxDBAPI, "influxdb api address")
	flag.StringVar(&opts.InfluxDBName, "influxdb-db-name", opts.InfluxDBName, "influxdb database name")
	flag.StringVar(&opts.TSDBAPI, "tsdb-api-addr", opts.TSDBAPI, "tsdb api address")

	flag.Parse()
}

func main() {
	var m = make(map[string]store.Monitor)

	m["influxdb"] = store.InfluxDB{
		API:   opts.InfluxDBAPI,
		DB:    opts.InfluxDBName,
		VHost: opts.VFlowHost,
	}

	m["tsdb"] = store.TSDB{
		API:   opts.TSDBAPI,
		VHost: opts.VFlowHost,
	}

	if _, ok := m[opts.DBType]; !ok {
		log.Fatalf("the storage: %s is not available", opts.DBType)
	}

	if err := m[opts.DBType].Netflow(); err != nil {
		log.Println(err)
	}
	if err := m[opts.DBType].System(); err != nil {
		log.Println(err)
	}
}
<|endoftext|>


<commit_before>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package cron

import (
	"bytes"
	"io"
	"strings"
)

var (
	dot6str   = "\n" + `......` + "\n"
	dot6bytes = []byte(dot6str)
)

type OutputWriter struct {
	io.Writer
	String() string
	Bytes() []byte
}

func NewCmdRec(max uint64) *cmdRec {
	return &cmdRec{
		buf:  new(bytes.Buffer),
		max:  max / 2,
		last: []byte{},
	}
}

type cmdRec struct {
	buf    *bytes.Buffer
	max    uint64
	start  uint64
	end    uint64
	last   []byte
	ignore bool
}

func (c *cmdRec) Write(p []byte) (n int, err error) {
	if c.ignore {
		return
	}
	if c.start == 0 && strings.HasPrefix(string(p), NotRecordPrefixFlag) {
		c.ignore = true
		n, err = c.buf.Write(p)
		c.start += uint64(n)
		return
	}
	if c.start < c.max {
		n, err = c.buf.Write(p)
		c.start += uint64(n)
		return
	}
	n = len(p)
	size := uint64(n)
	if c.end > c.max {
		if c.max > size {
			c.last = append(c.last[0:c.max-size], p...)
		} else if c.max == size {
			c.last = p
		} else {
			start := size - c.max
			c.last = p[start:]
		}
		c.end = uint64(len(c.last))
		return
	}
	c.end += size
	c.last = append(c.last, p...)
	return
}

// String returns the contents of the unread portion of the buffer
// as a string. If the Buffer is a nil pointer, it returns "<nil>".
func (c *cmdRec) String() string {
	if c.buf == nil {
		// Special case, useful in debugging.
		return string(c.last)
	}
	s := c.buf.String()
	if len(s) > 0 && len(c.last) > 0 {
		s += dot6str + string(c.last)
	}
	return s
}

func (c *cmdRec) Bytes() []byte {
	if c.buf == nil {
		// Special case, useful in debugging.
		return c.last
	}
	b := c.buf.Bytes()
	if len(b) > 0 && len(c.last) > 0 {
		b = append(b, dot6bytes...)
	}
	return append(b, c.last...)
}
<commit_msg>update<commit_after>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package cron

import (
	"bytes"
	"io"
	"strings"
)

var (
	dot6str   = "\n" + `......` + "\n"
	dot6bytes = []byte(dot6str)
)

type OutputWriter interface {
	io.Writer
	String() string
	Bytes() []byte
}

func NewCmdRec(max uint64) *cmdRec {
	return &cmdRec{
		buf:  new(bytes.Buffer),
		max:  max / 2,
		last: []byte{},
	}
}

type cmdRec struct {
	buf    *bytes.Buffer
	max    uint64
	start  uint64
	end    uint64
	last   []byte
	ignore bool
}

func (c *cmdRec) Write(p []byte) (n int, err error) {
	if c.ignore {
		return
	}
	if c.start == 0 && strings.HasPrefix(string(p), NotRecordPrefixFlag) {
		c.ignore = true
		n, err = c.buf.Write(p)
		c.start += uint64(n)
		return
	}
	if c.start < c.max {
		n, err = c.buf.Write(p)
		c.start += uint64(n)
		return
	}
	n = len(p)
	size := uint64(n)
	if c.end > c.max {
		if c.max > size {
			c.last = append(c.last[0:c.max-size], p...)
		} else if c.max == size {
			c.last = p
		} else {
			start := size - c.max
			c.last = p[start:]
		}
		c.end = uint64(len(c.last))
		return
	}
	c.end += size
	c.last = append(c.last, p...)
	return
}

// String returns the contents of the unread portion of the buffer
// as a string. If the Buffer is a nil pointer, it returns "<nil>".
func (c *cmdRec) String() string {
	if c.buf == nil {
		// Special case, useful in debugging.
		return string(c.last)
	}
	s := c.buf.String()
	if len(s) > 0 && len(c.last) > 0 {
		s += dot6str + string(c.last)
	}
	return s
}

func (c *cmdRec) Bytes() []byte {
	if c.buf == nil {
		// Special case, useful in debugging.
		return c.last
	}
	b := c.buf.Bytes()
	if len(b) > 0 && len(c.last) > 0 {
		b = append(b, dot6bytes...)
	}
	return append(b, c.last...)
}
<|endoftext|>


<commit_before>package dashboard

import (
	"log"
	"os"
	"strings"

	gh "github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

const accessTokenEnvVar = "GITHUB_ACCESS_TOKEN"

var githubClient *gh.Client

type GitHub struct {
	CommitsThisWeek           int    `json:"commits_this_week"`
	OpenPRs                   int    `json:"open_prs"`
	OpenIssues                int    `json:"open_issues"`
	CommitsSinceLatestRelease int    `json:"commits_since_latest_release"`
	LatestReleaseTag          string `json"latest_release_tag"`
}

func init() {
	githubClient = newGitHubClient()
}

func gitHubToken() string {
	return os.Getenv(accessTokenEnvVar)
}

func newGitHubClient() *gh.Client {
	if token := gitHubToken(); token != "" {
		return gh.NewClient(oauth2.NewClient(
			oauth2.NoContext,
			oauth2.StaticTokenSource(
				&oauth2.Token{AccessToken: token},
			),
		))
	} else {
		log.Printf("%s required for GitHub", accessTokenEnvVar)
		return nil
	}
}

func github(nwo string) chan *GitHub {
	githubChan := make(chan *GitHub, 1)

	go func() {
		if nwo == "" || githubClient == nil {
			githubChan <- nil
			return
		}
		pieces := strings.Split(nwo, "/")
		owner := pieces[0]
		repo := pieces[1]

		commits, tag := commitsSinceLatestRelease(owner, repo)
		githubChan <- &GitHub{
			CommitsThisWeek:           commitsThisWeek(owner, repo),
			OpenPRs:                   openPRs(nwo),
			OpenIssues:                openIssues(owner, repo),
			CommitsSinceLatestRelease: commits,
			LatestReleaseTag:          tag,
		}
	}()

	return githubChan
}

func openIssues(owner, repo string) int {
	repoData, _, err := githubClient.Repositories.Get(owner, repo)
	if err != nil {
		log.Printf("error fetching repo %s/%s: %v", owner, repo, err)
		return -1
	}
	return *repoData.OpenIssuesCount
}

func openPRs(nwo string) int {
	result, _, err := githubClient.Search.Issues(
		"state:open type:pr repo:"+nwo,
		&gh.SearchOptions{Sort: "created", Order: "asc"},
	)
	if err != nil {
		log.Printf("error searching for pr's for %s: %v", nwo, err)
		return -1
	}
	return *result.Total
}

func commitsThisWeek(owner, repo string) int {
	activities, _, err := githubClient.Repositories.ListCommitActivity(owner, repo)
	if err != nil {
		log.Printf("error fetching commits this week for %s/%s: %v", owner, repo, err)
		return -1
	}
	if len(activities) < 1 {
		log.Printf("error fetching commits this week for %s/%s: no results", owner, repo)
		return -1
	}
	return *activities[len(activities)-1].Total
}

func commitsSinceLatestRelease(owner, repo string) (int, string) {
	release, _, err := githubClient.Repositories.GetLatestRelease(owner, repo)
	if err != nil {
		log.Printf("error fetching commits since latest release for %s/%s: %v", owner, repo, err)
		return -1, ""
	}
	comparison, _, err := githubClient.Repositories.CompareCommits(
		owner, repo,
		*release.TagName, "master",
	)
	if err != nil {
		log.Printf("error fetching commit comparison for %s...master for %s/%s: %v", *release.TagName, owner, repo, err)
		return -1, *release.TagName
	}
	return *comparison.TotalCommits, *release.TagName
}
<commit_msg>issue count is apparently issues+pr's<commit_after>package dashboard

import (
	"log"
	"os"
	"strings"

	gh "github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

const accessTokenEnvVar = "GITHUB_ACCESS_TOKEN"

var githubClient *gh.Client

type GitHub struct {
	CommitsThisWeek           int    `json:"commits_this_week"`
	OpenPRs                   int    `json:"open_prs"`
	OpenIssues                int    `json:"open_issues"`
	CommitsSinceLatestRelease int    `json:"commits_since_latest_release"`
	LatestReleaseTag          string `json"latest_release_tag"`
}

func init() {
	githubClient = newGitHubClient()
}

func gitHubToken() string {
	return os.Getenv(accessTokenEnvVar)
}

func newGitHubClient() *gh.Client {
	if token := gitHubToken(); token != "" {
		return gh.NewClient(oauth2.NewClient(
			oauth2.NoContext,
			oauth2.StaticTokenSource(
				&oauth2.Token{AccessToken: token},
			),
		))
	} else {
		log.Printf("%s required for GitHub", accessTokenEnvVar)
		return nil
	}
}

func github(nwo string) chan *GitHub {
	githubChan := make(chan *GitHub, 1)

	go func() {
		if nwo == "" || githubClient == nil {
			githubChan <- nil
			return
		}
		pieces := strings.Split(nwo, "/")
		owner := pieces[0]
		repo := pieces[1]

		commits, tag := commitsSinceLatestRelease(owner, repo)
		openIssueAndPRCount := openIssues(owner, repo)
		openPRCount := openPRs(nwo)
		githubChan <- &GitHub{
			CommitsThisWeek:           commitsThisWeek(owner, repo),
			OpenPRs:                   openPRCount,
			OpenIssues:                openIssueAndPRCount-openPRCount,
			CommitsSinceLatestRelease: commits,
			LatestReleaseTag:          tag,
		}
	}()

	return githubChan
}

func openIssues(owner, repo string) int {
	repoData, _, err := githubClient.Repositories.Get(owner, repo)
	if err != nil {
		log.Printf("error fetching repo %s/%s: %v", owner, repo, err)
		return -1
	}
	return *repoData.OpenIssuesCount
}

func openPRs(nwo string) int {
	result, _, err := githubClient.Search.Issues(
		"state:open type:pr repo:"+nwo,
		&gh.SearchOptions{Sort: "created", Order: "asc"},
	)
	if err != nil {
		log.Printf("error searching for pr's for %s: %v", nwo, err)
		return -1
	}
	return *result.Total
}

func commitsThisWeek(owner, repo string) int {
	activities, _, err := githubClient.Repositories.ListCommitActivity(owner, repo)
	if err != nil {
		log.Printf("error fetching commits this week for %s/%s: %v", owner, repo, err)
		return -1
	}
	if len(activities) < 1 {
		log.Printf("error fetching commits this week for %s/%s: no results", owner, repo)
		return -1
	}
	return *activities[len(activities)-1].Total
}

func commitsSinceLatestRelease(owner, repo string) (int, string) {
	release, _, err := githubClient.Repositories.GetLatestRelease(owner, repo)
	if err != nil {
		log.Printf("error fetching commits since latest release for %s/%s: %v", owner, repo, err)
		return -1, ""
	}
	comparison, _, err := githubClient.Repositories.CompareCommits(
		owner, repo,
		*release.TagName, "master",
	)
	if err != nil {
		log.Printf("error fetching commit comparison for %s...master for %s/%s: %v", *release.TagName, owner, repo, err)
		return -1, *release.TagName
	}
	return *comparison.TotalCommits, *release.TagName
}
<|endoftext|>


<commit_before>package reviewdog

import (
	"context"
	"fmt"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"

	"golang.org/x/sync/errgroup"

	"github.com/google/go-github/github"
)

var _ = github.ScopeAdminOrg

var _ CommentService = &GitHubPullRequest{}
var _ DiffService = &GitHubPullRequest{}

// `path` to `position`(Lnum for new file) to comment `body`s
type postedcomments map[string]map[int][]string

// IsPosted returns true if a given comment has been posted in GitHub already,
// otherwise returns false. It sees comments with same path, same position,
// and same body as same comments.
func (p postedcomments) IsPosted(c *Comment) bool {
	if _, ok := p[c.Path]; !ok {
		return false
	}
	bodys, ok := p[c.Path][c.LnumDiff]
	if !ok {
		return false
	}
	for _, body := range bodys {
		if body == commentBody(c) {
			return true
		}
	}
	return false
}

// GitHubPullRequest is a comment and diff service for GitHub PullRequest.
//
// API:
//	https://developer.github.com/v3/pulls/comments/#create-a-comment
// 	POST /repos/:owner/:repo/pulls/:number/comments
type GitHubPullRequest struct {
	cli   *github.Client
	owner string
	repo  string
	pr    int
	sha   string

	muComments   sync.Mutex
	postComments []*Comment

	postedcs postedcomments

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

// NewGitHubPullReqest returns a new GitHubPullRequest service.
// GitHubPullRequest service needs git command in $PATH.
func NewGitHubPullReqest(cli *github.Client, owner, repo string, pr int, sha string) (*GitHubPullRequest, error) {
	workDir, err := gitRelWorkdir()
	if err != nil {
		return nil, fmt.Errorf("GitHubPullRequest needs 'git' command: %v", err)
	}
	return &GitHubPullRequest{
		cli:   cli,
		owner: owner,
		repo:  repo,
		pr:    pr,
		sha:   sha,
		wd:    workDir,
	}, nil
}

// Post accepts a comment and holds it. Flash method actually posts comments to
// GitHub in parallel.
func (g *GitHubPullRequest) Post(_ context.Context, c *Comment) error {
	c.Path = filepath.Join(g.wd, c.Path)
	g.muComments.Lock()
	defer g.muComments.Unlock()
	g.postComments = append(g.postComments, c)
	return nil
}

const bodyPrefix = `<sub>reported by [reviewdog](https://github.com/haya14busa/reviewdog) :dog:</sub>`

func commentBody(c *Comment) string {
	tool := ""
	if c.ToolName != "" {
		tool = fmt.Sprintf("**[%s]** ", c.ToolName)
	}
	return tool + bodyPrefix + "\n" + c.Body
}

var githubAPIHost = "api.github.com"

// Flash posts comments which has not been posted yet.
func (g *GitHubPullRequest) Flash(ctx context.Context) error {
	g.muComments.Lock()
	defer g.muComments.Unlock()

	if err := g.setPostedComment(ctx); err != nil {
		return err
	}
	// TODO(haya14busa,#58): remove host check when GitHub Enterprise supports
	// Pull Request API.
	if g.cli.BaseURL.Host == githubAPIHost {
		return g.postAsReviewComment(ctx)
	}
	return g.postCommentsForEach(ctx)
}

func (g *GitHubPullRequest) postAsReviewComment(ctx context.Context) error {
	comments := make([]*github.DraftReviewComment, 0, len(g.postComments))
	for _, c := range g.postComments {
		if g.postedcs.IsPosted(c) {
			continue
		}
		cbody := commentBody(c)
		comments = append(comments, &github.DraftReviewComment{
			Path:     &c.Path,
			Position: &c.LnumDiff,
			Body:     &cbody,
		})
	}

	if len(comments) == 0 {
		return nil
	}

	// TODO(haya14busa): it might be useful to report overview results by "body"
	// field.
	review := &github.PullRequestReviewRequest{
		Event:    github.String("COMMENT"),
		Comments: comments,
	}
	_, _, err := g.cli.PullRequests.CreateReview(ctx, g.owner, g.repo, g.pr, review)
	return err
}

func (g *GitHubPullRequest) postCommentsForEach(ctx context.Context) error {
	var eg errgroup.Group
	for _, c := range g.postComments {
		comment := c
		if g.postedcs.IsPosted(comment) {
			continue
		}
		eg.Go(func() error {
			body := commentBody(comment)
			prcomment := &github.PullRequestComment{
				CommitID: &g.sha,
				Body:     &body,
				Path:     &comment.Path,
				Position: &comment.LnumDiff,
			}
			_, _, err := g.cli.PullRequests.CreateComment(ctx, g.owner, g.repo, g.pr, prcomment)
			return err
		})
	}
	return eg.Wait()
}

func (g *GitHubPullRequest) setPostedComment(ctx context.Context) error {
	g.postedcs = make(postedcomments)
	cs, err := g.comment(ctx)
	if err != nil {
		return err
	}
	for _, c := range cs {
		if c.Position == nil || c.Path == nil || c.Body == nil {
			// skip resolved comments. Or comments which do not have "path" nor
			// "body".
			continue
		}
		path := *c.Path
		pos := *c.Position
		body := *c.Body
		if _, ok := g.postedcs[path]; !ok {
			g.postedcs[path] = make(map[int][]string)
		}
		if _, ok := g.postedcs[path][pos]; !ok {
			g.postedcs[path][pos] = make([]string, 0)
		}
		g.postedcs[path][pos] = append(g.postedcs[path][pos], body)
	}
	return nil
}

// Diff returns a diff of PullRequest. It runs `git diff` locally instead of
// diff_url of GitHub Pull Request because diff of diff_url is not suited for
// comment API in a sense that diff of diff_url is equivalent to
// `git diff --no-renames`, we want diff which is equivalent to
// `git diff --find-renames`.
func (g *GitHubPullRequest) Diff(ctx context.Context) ([]byte, error) {
	pr, _, err := g.cli.PullRequests.Get(ctx, g.owner, g.repo, g.pr)
	if err != nil {
		return nil, err
	}
	b, err := exec.Command("git", "merge-base", g.sha, *pr.Base.SHA).Output()
	if err != nil {
		return nil, fmt.Errorf("failed to get merge-base commit: %v", err)
	}
	mergeBase := strings.Trim(string(b), "\n")
	return exec.Command("git", "diff", "--relative", g.wd, "--find-renames", mergeBase, g.sha).Output()
}

// Strip returns 1 as a strip of git diff.
func (g *GitHubPullRequest) Strip() int {
	return 1
}

func (g *GitHubPullRequest) comment(ctx context.Context) ([]*github.PullRequestComment, error) {
	comments, _, err := g.cli.PullRequests.ListComments(ctx, g.owner, g.repo, g.pr, nil)
	if err != nil {
		return nil, err
	}
	return comments, nil
}

func gitRelWorkdir() (string, error) {
	b, err := exec.Command("git", "rev-parse", "--show-prefix").Output()
	if err != nil {
		return "", fmt.Errorf("failed to run 'git rev-parse --show-prefix': %v", err)
	}
	return strings.Trim(string(b), "\n"), nil
}
<commit_msg>github: fix "git diff" cmd args and improve error message<commit_after>package reviewdog

import (
	"context"
	"fmt"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"

	"golang.org/x/sync/errgroup"

	"github.com/google/go-github/github"
)

var _ = github.ScopeAdminOrg

var _ CommentService = &GitHubPullRequest{}
var _ DiffService = &GitHubPullRequest{}

// `path` to `position`(Lnum for new file) to comment `body`s
type postedcomments map[string]map[int][]string

// IsPosted returns true if a given comment has been posted in GitHub already,
// otherwise returns false. It sees comments with same path, same position,
// and same body as same comments.
func (p postedcomments) IsPosted(c *Comment) bool {
	if _, ok := p[c.Path]; !ok {
		return false
	}
	bodys, ok := p[c.Path][c.LnumDiff]
	if !ok {
		return false
	}
	for _, body := range bodys {
		if body == commentBody(c) {
			return true
		}
	}
	return false
}

// GitHubPullRequest is a comment and diff service for GitHub PullRequest.
//
// API:
//	https://developer.github.com/v3/pulls/comments/#create-a-comment
// 	POST /repos/:owner/:repo/pulls/:number/comments
type GitHubPullRequest struct {
	cli   *github.Client
	owner string
	repo  string
	pr    int
	sha   string

	muComments   sync.Mutex
	postComments []*Comment

	postedcs postedcomments

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

// NewGitHubPullReqest returns a new GitHubPullRequest service.
// GitHubPullRequest service needs git command in $PATH.
func NewGitHubPullReqest(cli *github.Client, owner, repo string, pr int, sha string) (*GitHubPullRequest, error) {
	workDir, err := gitRelWorkdir()
	if err != nil {
		return nil, fmt.Errorf("GitHubPullRequest needs 'git' command: %v", err)
	}
	return &GitHubPullRequest{
		cli:   cli,
		owner: owner,
		repo:  repo,
		pr:    pr,
		sha:   sha,
		wd:    workDir,
	}, nil
}

// Post accepts a comment and holds it. Flash method actually posts comments to
// GitHub in parallel.
func (g *GitHubPullRequest) Post(_ context.Context, c *Comment) error {
	c.Path = filepath.Join(g.wd, c.Path)
	g.muComments.Lock()
	defer g.muComments.Unlock()
	g.postComments = append(g.postComments, c)
	return nil
}

const bodyPrefix = `<sub>reported by [reviewdog](https://github.com/haya14busa/reviewdog) :dog:</sub>`

func commentBody(c *Comment) string {
	tool := ""
	if c.ToolName != "" {
		tool = fmt.Sprintf("**[%s]** ", c.ToolName)
	}
	return tool + bodyPrefix + "\n" + c.Body
}

var githubAPIHost = "api.github.com"

// Flash posts comments which has not been posted yet.
func (g *GitHubPullRequest) Flash(ctx context.Context) error {
	g.muComments.Lock()
	defer g.muComments.Unlock()

	if err := g.setPostedComment(ctx); err != nil {
		return err
	}
	// TODO(haya14busa,#58): remove host check when GitHub Enterprise supports
	// Pull Request API.
	if g.cli.BaseURL.Host == githubAPIHost {
		return g.postAsReviewComment(ctx)
	}
	return g.postCommentsForEach(ctx)
}

func (g *GitHubPullRequest) postAsReviewComment(ctx context.Context) error {
	comments := make([]*github.DraftReviewComment, 0, len(g.postComments))
	for _, c := range g.postComments {
		if g.postedcs.IsPosted(c) {
			continue
		}
		cbody := commentBody(c)
		comments = append(comments, &github.DraftReviewComment{
			Path:     &c.Path,
			Position: &c.LnumDiff,
			Body:     &cbody,
		})
	}

	if len(comments) == 0 {
		return nil
	}

	// TODO(haya14busa): it might be useful to report overview results by "body"
	// field.
	review := &github.PullRequestReviewRequest{
		Event:    github.String("COMMENT"),
		Comments: comments,
	}
	_, _, err := g.cli.PullRequests.CreateReview(ctx, g.owner, g.repo, g.pr, review)
	return err
}

func (g *GitHubPullRequest) postCommentsForEach(ctx context.Context) error {
	var eg errgroup.Group
	for _, c := range g.postComments {
		comment := c
		if g.postedcs.IsPosted(comment) {
			continue
		}
		eg.Go(func() error {
			body := commentBody(comment)
			prcomment := &github.PullRequestComment{
				CommitID: &g.sha,
				Body:     &body,
				Path:     &comment.Path,
				Position: &comment.LnumDiff,
			}
			_, _, err := g.cli.PullRequests.CreateComment(ctx, g.owner, g.repo, g.pr, prcomment)
			return err
		})
	}
	return eg.Wait()
}

func (g *GitHubPullRequest) setPostedComment(ctx context.Context) error {
	g.postedcs = make(postedcomments)
	cs, err := g.comment(ctx)
	if err != nil {
		return err
	}
	for _, c := range cs {
		if c.Position == nil || c.Path == nil || c.Body == nil {
			// skip resolved comments. Or comments which do not have "path" nor
			// "body".
			continue
		}
		path := *c.Path
		pos := *c.Position
		body := *c.Body
		if _, ok := g.postedcs[path]; !ok {
			g.postedcs[path] = make(map[int][]string)
		}
		if _, ok := g.postedcs[path][pos]; !ok {
			g.postedcs[path][pos] = make([]string, 0)
		}
		g.postedcs[path][pos] = append(g.postedcs[path][pos], body)
	}
	return nil
}

// Diff returns a diff of PullRequest. It runs `git diff` locally instead of
// diff_url of GitHub Pull Request because diff of diff_url is not suited for
// comment API in a sense that diff of diff_url is equivalent to
// `git diff --no-renames`, we want diff which is equivalent to
// `git diff --find-renames`.
func (g *GitHubPullRequest) Diff(ctx context.Context) ([]byte, error) {
	pr, _, err := g.cli.PullRequests.Get(ctx, g.owner, g.repo, g.pr)
	if err != nil {
		return nil, err
	}
	b, err := exec.Command("git", "merge-base", g.sha, *pr.Base.SHA).Output()
	if err != nil {
		return nil, fmt.Errorf("failed to get merge-base commit: %v", err)
	}
	mergeBase := strings.Trim(string(b), "\n")
	relArg := fmt.Sprintf("--relative=%s", g.wd)
	bytes, err := exec.Command("git", "diff", relArg, "--find-renames", mergeBase, g.sha).Output()
	if err != nil {
		return nil, fmt.Errorf("failed to run git diff: %v", err)
	}
	return bytes, nil
}

// Strip returns 1 as a strip of git diff.
func (g *GitHubPullRequest) Strip() int {
	return 1
}

func (g *GitHubPullRequest) comment(ctx context.Context) ([]*github.PullRequestComment, error) {
	comments, _, err := g.cli.PullRequests.ListComments(ctx, g.owner, g.repo, g.pr, nil)
	if err != nil {
		return nil, err
	}
	return comments, nil
}

func gitRelWorkdir() (string, error) {
	b, err := exec.Command("git", "rev-parse", "--show-prefix").Output()
	if err != nil {
		return "", fmt.Errorf("failed to run 'git rev-parse --show-prefix': %v", err)
	}
	return strings.Trim(string(b), "\n"), nil
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"github.com/thoj/go-ircevent"
	"hash"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

type GitHubAdapter struct {
	ircbot   *irc.Connection
	config   *GitHubConfig
	channels map[string]*GitHubRepositoryConfig
}

type GithubCreate struct {
	Ref          string
	RefType      string `json:"ref_type"`
	MasterBranch string
	Description  string
	PusherType   string
	Repository   GithubRepository
	Sender       GithubUser
}

type GithubDelete struct {
        Ref             string
        RefType         string `json:"ref_type"`
        PusherType      string
        Repository      GithubRepository
        Sender          GithubUser
}

type GithubRepository struct {
	Id               uint64
	Name             string
	FullName         string `json:"full_name"`
	Owner            GithubUser
	Private          bool
	HtmlUrl          string
	Description      string
	Fork             bool
	Url              string
	ForksUrl         string
	KeysUrl          string
	CollaboratorsUrl string
	TeamsUrl         string
	HooksUrl         string
	IssueEventsUrl   string
	EventsUrl        string
	AssigneesUrl     string
	BranchesUrl      string
	TagsUrl          string
	BlobsUrl         string
	GitTagsUrl       string
	GitRefsUrl       string
	TreesUrl         string
	StatusesUrl      string
	LanguagesUrl     string
	StargazersUrl    string
	ContributorsUrl  string
	SubscribersUrl   string
	SubscriptionUrl  string
	CommitsUrl       string
	GitCommitsUrl    string
	CommentsUrl      string
	CompareUrl       string
	MergesUrl        string
	ArchiveUrl       string
	DownloadsUrl     string
	IssuesUrl        string
	PullsUrl         string
	MilestonesUrl    string
	NotificationsUrl string
	LabelsUrl        string
	ReleasesUrl      string
	CreatedAt        string
	UpdatedAt        string
	PushedAt         string
	GitUrl           string
	SshUrl           string
	CloneUrl         string
	SvnUrl           string
	Homepage         string
	Size             uint64
	StargazersCount  uint64
	WatchersCount    uint64
	Language         string
	HasIssues        bool
	HasDownloads     bool
	HasWiki          bool
	ForksCount       uint64
	MirrorUrl        string
	OpenIssuesCount  uint64
	Forks            uint64
	OpenIssues       uint64
	Watchers         uint64
	DefaultBranch    string
}

type GithubUser struct {
	Login             string
	Id                uint64
	AvatarURL         string
	GravatarId        string
	Url               string
	HtmlUrl           string
	FollowersUrl      string
	FollowingUrl      string
	GistsUrl          string
	StarredUrl        string
	SubscriptionsUrl  string
	OrganizationsUrl  string
	ReposUrl          string
	EventsUrl         string
	ReceivedEventsUrl string
	Type              string
	SiteAdmin         bool
	Name              string
	Email             string
}

type GithubPush struct {
	Ref        string
	After      string
	Before     string
	Created    bool
	Deleted    bool
	Forced     bool
	Compare    string
	Commits    []GithubCommit
	HeadCommit GithubCommit `json:"head_commit"`
	Repository GithubRepository
	Pusher     GithubUser
}

type GithubCommit struct {
	Id        string
	Distinct  bool
	Message   string
	Timestamp string
	Url       string
	Author    GithubUser
	Committer GithubUser
	Added     []string
	Removed   []string
	Modified  []string
}

// CheckMAC returns true if messageMAC is a valid HMAC tag for message.
func CheckMAC(message []byte, messageMAC string, key string) bool {
	var err error
	var mac hash.Hash
	var macdata []byte
	var macparts = strings.Split(messageMAC, "=")
	macdata, err = hex.DecodeString(macparts[1])
	if err != nil {
		log.Print("Error decoding hex digest: ", err)
		return false
	}
	switch macparts[0] {
	case "md5":
		mac = hmac.New(md5.New, []byte(key))
	case "sha1":
		mac = hmac.New(sha1.New, []byte(key))
	case "sha256":
		mac = hmac.New(sha256.New, []byte(key))
	case "sha512":
		mac = hmac.New(sha512.New, []byte(key))
	default:
		log.Print("Unsupported hash: ", macparts[0])
		return false
	}
	mac.Write(message)
	expectedMAC := mac.Sum(nil)
	return hmac.Equal(macdata, expectedMAC)
}

func (g *GithubUser) String() string {
	if len(g.Name) > 0 && len(g.Email) > 0 {
		return g.Name + " <" + g.Email + ">"
	} else if len(g.Name) > 0 {
		return g.Name
	} else if len(g.Email) > 0 {
		return g.Email
	}
	return g.Login
}

func (g *GithubRepository) String() string {
	if len(g.FullName) > 0 {
		return g.FullName
	}
	return g.Name
}

func (g *GithubCommit) String() string {
	var lines []string = strings.Split(g.Message, "\n")                // Commit message
	var text string = g.Author.String() + " \x02" + g.Id[0:7] + "\x0f" // First 7 characters

	if len(g.Added) > 0 {
		text += " \x0303" + strings.Join(g.Added, " ") + "\x0f"
	}

	if len(g.Removed) > 0 {
		text += " \x0304" + strings.Join(g.Removed, " ") + "\x0f"
	}

	if len(g.Modified) > 0 {
		text += " \x0310" + strings.Join(g.Modified, " ") + "\x0f"
	}
	if len(lines) > 0 {
		text += " " + lines[0]
	}
	return text
}

func (g *GithubPush) Strings() []string {
	var refs []string = strings.Split(g.Ref, "/")
	var prefix string = "\x0303" + g.Repository.String() + "\x0f \x0305" + refs[len(refs)-1] + "\x0f"
	var pushes []string = make([]string, 0)

	for _, commit := range g.Commits {
		pushes = append(pushes, prefix+" "+commit.String())
		pushes = append(pushes, prefix+" "+commit.Url)
	}
	return pushes
}

func (g *GithubCreate) String() string {
	return "\x0303" + g.Sender.String() + "\x0f has pushed a new " + g.RefType + " \x0305" + g.Ref + "\x0f to \x0303" + g.Repository.String() + "\x0f"
}

func (g *GithubDelete) String() string {
	return "\x0303" + g.Sender.String() + "\x0f has deleted a " + g.RefType + " \x0305" + g.Ref + "\x0f from \x0303" + g.Repository.String() + "\x0f"
}

func NewGitHubAdapter(ircbot *irc.Connection, config *GitHubConfig) *GitHubAdapter {
	var channels = make(map[string]*GitHubRepositoryConfig)
	for _, repo := range config.Repo {
		channels[repo.GetName()] = repo
	}
	return &GitHubAdapter{
		ircbot:   ircbot,
		config:   config,
		channels: channels,
	}
}

func (g *GitHubAdapter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	var body []byte
	var err error
	body, err = ioutil.ReadAll(req.Body)
	if err != nil {
		log.Print("Error reading body: ", err)
		return
	}

	switch req.Header.Get("X-GitHub-Event") {
	case "create":
		var create GithubCreate
		var githubconf *GitHubRepositoryConfig
		var ok bool
		var err error
		err = json.Unmarshal(body, &create)
		if err != nil {
			log.Print("Error decoding github create: ", err)
			return
		}
		log.Print(create.String())
		githubconf, ok = g.channels[create.Repository.String()]
		if !ok {
			log.Print("Repository ", create.Repository.String(), " not configured.")
			return
		}
		if !CheckMAC(body, req.Header.Get("X-Hub-Signature"), githubconf.GetSecret()) {
			log.Print("DEBUG Spam, spam spam")
			return
		}
		for _, channel := range githubconf.GetIrcChannel() {
			g.ircbot.Privmsg(channel, create.String())
		}
        case "delete":
                var del GithubDelete
                var githubconf *GitHubRepositoryConfig
                var ok bool
                var err error
                err = json.Unmarshal(body, &del)
                if err != nil {
                        log.Print("Error decoding github delete: ", err)
                        return
                }
                log.Print(del.String())
                githubconf, ok = g.channels[del.Repository.String()]
                if !ok {
                        log.Print("Repository ", del.Repository.String(), " not configured.")
                        return
                }
		if !CheckMAC(body, req.Header.Get("X-Hub-Signature"), githubconf.GetSecret()) {
			log.Print("DEBUG Spam, spam spam")
			return
		}
		for _, channel := range githubconf.GetIrcChannel() {
			g.ircbot.Privmsg(channel, del.String())
		}
	case "push":
		var push GithubPush
		var ok bool
		var githubconf *GitHubRepositoryConfig
		var err error

		err = json.Unmarshal(body, &push)

		if err != nil {
			log.Print("Error decoding github push: ", err)
			return
		}

		githubconf, ok = g.channels[push.Repository.String()]
		if !ok {
			log.Print("Repository ", push.Repository.String(), " not configured.")
			return
		}
		for _, channel := range githubconf.GetIrcChannel() {
			for _, commit := range push.Strings() {
				g.ircbot.Privmsg(channel, commit)
			}
		}
	default:
		log.Print("Unknown GitHub event.", req.Header.Get("X-GitHub-Event"))
	}
}
<commit_msg>Added forgotten check to github push notifications.<commit_after>package main

import (
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"github.com/thoj/go-ircevent"
	"hash"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

type GitHubAdapter struct {
	ircbot   *irc.Connection
	config   *GitHubConfig
	channels map[string]*GitHubRepositoryConfig
}

type GithubCreate struct {
	Ref          string
	RefType      string `json:"ref_type"`
	MasterBranch string
	Description  string
	PusherType   string
	Repository   GithubRepository
	Sender       GithubUser
}

type GithubDelete struct {
        Ref             string
        RefType         string `json:"ref_type"`
        PusherType      string
        Repository      GithubRepository
        Sender          GithubUser
}

type GithubRepository struct {
	Id               uint64
	Name             string
	FullName         string `json:"full_name"`
	Owner            GithubUser
	Private          bool
	HtmlUrl          string
	Description      string
	Fork             bool
	Url              string
	ForksUrl         string
	KeysUrl          string
	CollaboratorsUrl string
	TeamsUrl         string
	HooksUrl         string
	IssueEventsUrl   string
	EventsUrl        string
	AssigneesUrl     string
	BranchesUrl      string
	TagsUrl          string
	BlobsUrl         string
	GitTagsUrl       string
	GitRefsUrl       string
	TreesUrl         string
	StatusesUrl      string
	LanguagesUrl     string
	StargazersUrl    string
	ContributorsUrl  string
	SubscribersUrl   string
	SubscriptionUrl  string
	CommitsUrl       string
	GitCommitsUrl    string
	CommentsUrl      string
	CompareUrl       string
	MergesUrl        string
	ArchiveUrl       string
	DownloadsUrl     string
	IssuesUrl        string
	PullsUrl         string
	MilestonesUrl    string
	NotificationsUrl string
	LabelsUrl        string
	ReleasesUrl      string
	CreatedAt        string
	UpdatedAt        string
	PushedAt         string
	GitUrl           string
	SshUrl           string
	CloneUrl         string
	SvnUrl           string
	Homepage         string
	Size             uint64
	StargazersCount  uint64
	WatchersCount    uint64
	Language         string
	HasIssues        bool
	HasDownloads     bool
	HasWiki          bool
	ForksCount       uint64
	MirrorUrl        string
	OpenIssuesCount  uint64
	Forks            uint64
	OpenIssues       uint64
	Watchers         uint64
	DefaultBranch    string
}

type GithubUser struct {
	Login             string
	Id                uint64
	AvatarURL         string
	GravatarId        string
	Url               string
	HtmlUrl           string
	FollowersUrl      string
	FollowingUrl      string
	GistsUrl          string
	StarredUrl        string
	SubscriptionsUrl  string
	OrganizationsUrl  string
	ReposUrl          string
	EventsUrl         string
	ReceivedEventsUrl string
	Type              string
	SiteAdmin         bool
	Name              string
	Email             string
}

type GithubPush struct {
	Ref        string
	After      string
	Before     string
	Created    bool
	Deleted    bool
	Forced     bool
	Compare    string
	Commits    []GithubCommit
	HeadCommit GithubCommit `json:"head_commit"`
	Repository GithubRepository
	Pusher     GithubUser
}

type GithubCommit struct {
	Id        string
	Distinct  bool
	Message   string
	Timestamp string
	Url       string
	Author    GithubUser
	Committer GithubUser
	Added     []string
	Removed   []string
	Modified  []string
}

// CheckMAC returns true if messageMAC is a valid HMAC tag for message.
func CheckMAC(message []byte, messageMAC string, key string) bool {
	var err error
	var mac hash.Hash
	var macdata []byte
	var macparts = strings.Split(messageMAC, "=")
	macdata, err = hex.DecodeString(macparts[1])
	if err != nil {
		log.Print("Error decoding hex digest: ", err)
		return false
	}
	switch macparts[0] {
	case "md5":
		mac = hmac.New(md5.New, []byte(key))
	case "sha1":
		mac = hmac.New(sha1.New, []byte(key))
	case "sha256":
		mac = hmac.New(sha256.New, []byte(key))
	case "sha512":
		mac = hmac.New(sha512.New, []byte(key))
	default:
		log.Print("Unsupported hash: ", macparts[0])
		return false
	}
	mac.Write(message)
	expectedMAC := mac.Sum(nil)
	return hmac.Equal(macdata, expectedMAC)
}

func (g *GithubUser) String() string {
	if len(g.Name) > 0 && len(g.Email) > 0 {
		return g.Name + " <" + g.Email + ">"
	} else if len(g.Name) > 0 {
		return g.Name
	} else if len(g.Email) > 0 {
		return g.Email
	}
	return g.Login
}

func (g *GithubRepository) String() string {
	if len(g.FullName) > 0 {
		return g.FullName
	}
	return g.Name
}

func (g *GithubCommit) String() string {
	var lines []string = strings.Split(g.Message, "\n")                // Commit message
	var text string = g.Author.String() + " \x02" + g.Id[0:7] + "\x0f" // First 7 characters

	if len(g.Added) > 0 {
		text += " \x0303" + strings.Join(g.Added, " ") + "\x0f"
	}

	if len(g.Removed) > 0 {
		text += " \x0304" + strings.Join(g.Removed, " ") + "\x0f"
	}

	if len(g.Modified) > 0 {
		text += " \x0310" + strings.Join(g.Modified, " ") + "\x0f"
	}
	if len(lines) > 0 {
		text += " " + lines[0]
	}
	return text
}

func (g *GithubPush) Strings() []string {
	var refs []string = strings.Split(g.Ref, "/")
	var prefix string = "\x0303" + g.Repository.String() + "\x0f \x0305" + refs[len(refs)-1] + "\x0f"
	var pushes []string = make([]string, 0)

	for _, commit := range g.Commits {
		pushes = append(pushes, prefix+" "+commit.String())
		pushes = append(pushes, prefix+" "+commit.Url)
	}
	return pushes
}

func (g *GithubCreate) String() string {
	return "\x0303" + g.Sender.String() + "\x0f has pushed a new " + g.RefType + " \x0305" + g.Ref + "\x0f to \x0303" + g.Repository.String() + "\x0f"
}

func (g *GithubDelete) String() string {
	return "\x0303" + g.Sender.String() + "\x0f has deleted a " + g.RefType + " \x0305" + g.Ref + "\x0f from \x0303" + g.Repository.String() + "\x0f"
}

func NewGitHubAdapter(ircbot *irc.Connection, config *GitHubConfig) *GitHubAdapter {
	var channels = make(map[string]*GitHubRepositoryConfig)
	for _, repo := range config.Repo {
		channels[repo.GetName()] = repo
	}
	return &GitHubAdapter{
		ircbot:   ircbot,
		config:   config,
		channels: channels,
	}
}

func (g *GitHubAdapter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	var body []byte
	var err error
	body, err = ioutil.ReadAll(req.Body)
	if err != nil {
		log.Print("Error reading body: ", err)
		return
	}

	switch req.Header.Get("X-GitHub-Event") {
	case "create":
		var create GithubCreate
		var githubconf *GitHubRepositoryConfig
		var ok bool
		var err error

		err = json.Unmarshal(body, &create)
		if err != nil {
			log.Print("Error decoding github create: ", err)
			return
		}
		log.Print(create.String())
		githubconf, ok = g.channels[create.Repository.String()]
		if !ok {
			log.Print("Repository ", create.Repository.String(), " not configured.")
			return
		}
		if !CheckMAC(body, req.Header.Get("X-Hub-Signature"), githubconf.GetSecret()) {
			log.Print("DEBUG Spam, spam spam")
			return
		}
		for _, channel := range githubconf.GetIrcChannel() {
			g.ircbot.Privmsg(channel, create.String())
		}
        case "delete":
                var del GithubDelete
                var githubconf *GitHubRepositoryConfig
                var ok bool
                var err error

                err = json.Unmarshal(body, &del)
                if err != nil {
                        log.Print("Error decoding github delete: ", err)
                        return
                }
                log.Print(del.String())
                githubconf, ok = g.channels[del.Repository.String()]
                if !ok {
                        log.Print("Repository ", del.Repository.String(), " not configured.")
                        return
                }
		if !CheckMAC(body, req.Header.Get("X-Hub-Signature"), githubconf.GetSecret()) {
			log.Print("DEBUG Spam, spam spam")
			return
		}
		for _, channel := range githubconf.GetIrcChannel() {
			g.ircbot.Privmsg(channel, del.String())
		}
	case "push":
		var push GithubPush
		var ok bool
		var githubconf *GitHubRepositoryConfig
		var err error

		err = json.Unmarshal(body, &push)

		if err != nil {
			log.Print("Error decoding github push: ", err)
			return
		}

		githubconf, ok = g.channels[push.Repository.String()]
		if !ok {
			log.Print("Repository ", push.Repository.String(), " not configured.")
			return
		}
		if !CheckMAC(body, req.Header.Get("X-Hub-Signature"), githubconf.GetSecret()) {
			log.Print("DEBUG Spam, spam spam")
			return
		}
		for _, channel := range githubconf.GetIrcChannel() {
			for _, commit := range push.Strings() {
				g.ircbot.Privmsg(channel, commit)
			}
		}
	default:
		log.Print("Unknown GitHub event.", req.Header.Get("X-GitHub-Event"))
	}
}
<|endoftext|>


<commit_before>package pulls

import (
	"bufio"
	"encoding/json"
	"fmt"
	gh "github.com/crosbymichael/octokat"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

// Top level type that manages a repository
type Maintainer struct {
	repo           gh.Repo
	client         *gh.Client
	email          string
	directoriesMap *MaintainerDirectoriesMap
}

type MaintainerDirectoriesMap struct {
	paths []string
}

type Config struct {
	Token string
}

const MaintainersFileName = "MAINTAINERS"

var configPath = path.Join(os.Getenv("HOME"), ".maintainercfg")

var maintainerDirectoriesMap = MaintainerDirectoriesMap{}

func LoadConfig() (*Config, error) {
	var config Config
	f, err := os.Open(configPath)
	if err != nil {
		if !os.IsNotExist(err) {
			return &config, err
		}
	} else {
		defer f.Close()

		dec := json.NewDecoder(f)
		if err := dec.Decode(&config); err != nil {
			return &config, err
		}
	}
	return &config, err
}

func SaveConfig(config Config) error {
	f, err := os.OpenFile(configPath, os.O_CREATE|os.O_RDWR, 0600)
	if err != nil {
		return nil
	}
	defer f.Close()

	enc := json.NewEncoder(f)
	if err := enc.Encode(config); err != nil {
		return err
	}
	return nil
}

func getEmailFromLine(str string) (string, bool, error) {
	exp, err := regexp.Compile(`([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})`)
	if err != nil {
		return "", false, err
	}
	return exp.FindString(str), exp.MatchString(str), err
}

func getRepoPath(pth, org string) string {
	flag := false
	i := 0
	repoPath := path.Dir("/")
	for _, dir := range strings.Split(pth, "/") {
		if strings.EqualFold(dir, org) {
			flag = true
		}
		if flag {
			if i >= 2 {
				repoPath = path.Join(repoPath, dir)
			}
			i++
		}
	}
	return repoPath
}

func getMaintainersEmails(pth string) (*[]string, error) {
	maintainersFileMap := []string{}
	file, _ := os.Open(pth)
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		strEmail, isEmail, err := getEmailFromLine(scanner.Text())
		if err != nil {
			return nil, err
		}
		if isEmail {
			email := []string{strEmail}
			maintainersFileMap = append(maintainersFileMap, email...)
		}
	}
	sort.Strings(maintainersFileMap)

	return &maintainersFileMap, nil
}

func createMaintainerDirectoriesMap(pth, cpth, maintainerEmail string, belongsToOthers bool) error {
	names, err := ioutil.ReadDir(pth)
	if err != nil {
		return err
	}
	// Look for the Maintainer File
	foundMaintainersFile := false
	iAmOneOfTheMaintainers := false
	belongsToOtherMaintainers := false
	for _, name := range names {
		if strings.EqualFold(name.Name(), MaintainersFileName) {
			foundMaintainersFile = true
			emails, err := getMaintainersEmails(path.Join(pth, name.Name()))
			if err != nil {
				return err
			}
			i := sort.SearchStrings(*emails, maintainerEmail)
			if i < len(*emails) && (*emails)[i] == maintainerEmail {
				iAmOneOfTheMaintainers = true
			}
		}
	}
	// Check if we need to add the directory to the maintainer's  directories mapping tree
	if (!foundMaintainersFile && !belongsToOthers) || iAmOneOfTheMaintainers {
		tmpcpth := cpth
		if cpth == "" {
			tmpcpth = "."
		}
		currentPath := []string{tmpcpth}
		maintainerDirectoriesMap.paths = append(maintainerDirectoriesMap.paths, currentPath...)
	} else if foundMaintainersFile || belongsToOthers {
		belongsToOtherMaintainers = true
	}
	for _, name := range names {
		if name.IsDir() && name.Name()[0] != '.' {
			tmpcpth := path.Join(cpth, name.Name())
			newPath := path.Join(pth, name.Name())
			createMaintainerDirectoriesMap(newPath, tmpcpth, maintainerEmail, belongsToOtherMaintainers)
		}
	}

	return err
}

func getOriginPath(org string) (string, error) {
	currentPath, err := os.Getwd()
	if err != nil {
		return "", err
	}

	originPath := path.Dir("/")
	for _, dir := range strings.Split(currentPath, "/") {
		originPath = path.Join(originPath, dir)
		if strings.EqualFold(dir, org) {
			break
		}
	}
	return originPath, err
}

func NewMaintainer(client *gh.Client, org, repo string) (*Maintainer, error) {

	config, err := LoadConfig()
	if err == nil {
		client.WithToken(config.Token)
	}

	originPath, err := getOriginPath(org)
	if err != nil {
		return nil, err
	}
	originPath = path.Join(originPath, repo)

	email, err := GetMaintainerEmail()
	if err != nil {
		return nil, err
	}

	err = createMaintainerDirectoriesMap(originPath, "", email, false)
	if err != nil {
		return nil, err
	}

	return &Maintainer{
		repo:           gh.Repo{Name: repo, UserName: org},
		client:         client,
		directoriesMap: &maintainerDirectoriesMap,
		email:          email,
	}, nil
}

func (m *Maintainer) Repository() (*gh.Repository, error) {
	return m.client.Repository(m.repo, nil)
}

// Return all the pull requests that I care about
func (m *Maintainer) GetPullRequestsThatICareAbout(showAll bool, state string) ([]*gh.PullRequest, error) {

	if showAll {
		return m.GetPullRequests(state)
	}

	filteredPrs := []*gh.PullRequest{}
	prs, err := m.GetPullRequests(state)
	if err != nil {
		return nil, err
	}
	for _, p := range prs {
		prfs, err := m.GetPullRequestFiles(strconv.Itoa(p.Number))
		if err != nil {
			return nil, err
		}
		for _, prf := range prfs {
			dirPath := filepath.Dir(prf.FileName)
			i := sort.SearchStrings((*m.directoriesMap).paths, dirPath)
			if i < len(m.directoriesMap.paths) && (*m.directoriesMap).paths[i] == dirPath {
				pr := []*gh.PullRequest{p}
				filteredPrs = append(filteredPrs, pr...)
				break
			}
		}
		fmt.Printf(".")

	}
	return filteredPrs, nil
}

// Return all pull requests
func (m *Maintainer) GetPullRequests(state string) ([]*gh.PullRequest, error) {
	o := &gh.Options{}
	o.QueryParams = map[string]string{
		"sort":      "updated",
		"direction": "asc",
		"state":     state,
		"per_page":  "100",
	}
	prevSize := -1
	page := 1
	allPRs := []*gh.PullRequest{}
	for len(allPRs) != prevSize {
		o.QueryParams["page"] = strconv.Itoa(page)
		if prs, err := m.client.PullRequests(m.repo, o); err != nil {
			return nil, err
		} else {
			prevSize = len(allPRs)
			allPRs = append(allPRs, prs...)
			page += 1
		}
		fmt.Printf(".")
	}
	return allPRs, nil
}

// Return all pull request Files
func (m *Maintainer) GetPullRequestFiles(number string) ([]*gh.PullRequestFile, error) {
	o := &gh.Options{}
	o.QueryParams = map[string]string{}
	allPrFiles := []*gh.PullRequestFile{}

	if prfs, err := m.client.PullRequestFiles(m.repo, number, o); err != nil {
		return nil, err
	} else {
		allPrFiles = append(allPrFiles, prfs...)

	}
	return allPrFiles, nil
}

func (m *Maintainer) GetFirstPullRequest(state, sortBy string) (*gh.PullRequest, error) {
	o := &gh.Options{}
	o.QueryParams = map[string]string{
		"state":     state,
		"per_page":  "1",
		"page":      "1",
		"sort":      sortBy,
		"direction": "asc",
	}
	prs, err := m.client.PullRequests(m.repo, o)
	if err != nil {
		return nil, err
	}
	if len(prs) == 0 {
		return nil, fmt.Errorf("No matching pull request")
	}
	return prs[0], nil
}

// Return a single pull request
// Return pr's comments if requested
func (m *Maintainer) GetPullRequest(number string, comments bool) (*gh.PullRequest, []gh.Comment, error) {
	var c []gh.Comment
	pr, err := m.client.PullRequest(m.repo, number, nil)
	if err != nil {
		return nil, nil, err
	}
	if comments {
		c, err = m.GetComments(number)
		if err != nil {
			return nil, nil, err
		}
	}
	return pr, c, nil
}

// Return all comments for an issue or pull request
func (m *Maintainer) GetComments(number string) ([]gh.Comment, error) {
	return m.client.Comments(m.repo, number, nil)
}

// Add a comment to an existing pull request
func (m *Maintainer) AddComment(number, comment string) (gh.Comment, error) {
	return m.client.AddComment(m.repo, number, comment)
}

// Merge a pull request
// If no LGTMs are in the comments require force to be true
func (m *Maintainer) MergePullRequest(number, comment string, force bool) (gh.Merge, error) {
	comments, err := m.GetComments(number)
	if err != nil {
		return gh.Merge{}, err
	}
	isApproved := false
	for _, c := range comments {
		// FIXME: Again should check for LGTM from a maintainer
		if strings.Contains(c.Body, "LGTM") {
			isApproved = true
			break
		}
	}
	if !isApproved && !force {
		return gh.Merge{}, fmt.Errorf("Pull request %s has not been approved", number)
	}
	o := &gh.Options{}
	o.Params = map[string]string{
		"commit_message": comment,
	}
	return m.client.MergePullRequest(m.repo, number, o)
}

// Checkout the pull request into the working tree of
// the users repository.
// This will mimic the operations on the manual merge view
func (m *Maintainer) Checkout(pr *gh.PullRequest) error {
	var (
		userBranch        = fmt.Sprintf("%s-%s", pr.User.Login, pr.Head.Ref)
		destinationBranch = pr.Base.Ref
	)

	// Checkout a new branch locally before pulling the changes
	if err := Git("checkout", "-b", userBranch, destinationBranch); err != nil {
		return err
	}

	if err := Git("pull", pr.Head.Repo.CloneURL, pr.Head.Ref); err != nil {
		return err
	}
	return nil
}

func (m *Maintainer) GetFirstIssue(state, sortBy string) (*gh.Issue, error) {
	o := &gh.Options{}
	o.QueryParams = map[string]string{
		"state":     state,
		"per_page":  "1",
		"page":      "1",
		"sort":      sortBy,
		"direction": "asc",
	}
	issues, err := m.client.Issues(m.repo, o)
	if err != nil {
		return &gh.Issue{}, err
	}
	if len(issues) == 0 {
		return &gh.Issue{}, fmt.Errorf("No matching issues")
	}
	return issues[0], nil
}

// GetIssues queries the GithubAPI for all issues matching the state `state` and the
// assignee `assignee`.
// See http://developer.github.com/v3/issues/#list-issues-for-a-repository
func (m *Maintainer) GetIssues(state, assignee string) ([]*gh.Issue, error) {
	o := &gh.Options{}
	o.QueryParams = map[string]string{
		"sort":      "updated",
		"direction": "asc",
		"state":     state,
		"per_page":  "100",
	}
	// If assignee == "", don't add it to the params.
	// This will show all issues, assigned or not.
	if assignee != "" {
		o.QueryParams["assignee"] = assignee
	}
	prevSize := -1
	page := 1
	all := []*gh.Issue{}
	for len(all) != prevSize {
		o.QueryParams["page"] = strconv.Itoa(page)
		if issues, err := m.client.Issues(m.repo, o); err != nil {
			return nil, err
		} else {
			prevSize = len(all)
			all = append(all, issues...)
			page += 1
		}
		fmt.Printf(".")
	}
	return all, nil
}
<commit_msg>- Added error checking<commit_after>package pulls

import (
	"bufio"
	"encoding/json"
	"fmt"
	gh "github.com/crosbymichael/octokat"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

// Top level type that manages a repository
type Maintainer struct {
	repo           gh.Repo
	client         *gh.Client
	email          string
	directoriesMap *MaintainerDirectoriesMap
}

type MaintainerDirectoriesMap struct {
	paths []string
}

type Config struct {
	Token string
}

const MaintainersFileName = "MAINTAINERS"

var configPath = path.Join(os.Getenv("HOME"), ".maintainercfg")

var maintainerDirectoriesMap = MaintainerDirectoriesMap{}

func LoadConfig() (*Config, error) {
	var config Config
	f, err := os.Open(configPath)
	if err != nil {
		if !os.IsNotExist(err) {
			return &config, err
		}
	} else {
		defer f.Close()

		dec := json.NewDecoder(f)
		if err := dec.Decode(&config); err != nil {
			return &config, err
		}
	}
	return &config, err
}

func SaveConfig(config Config) error {
	f, err := os.OpenFile(configPath, os.O_CREATE|os.O_RDWR, 0600)
	if err != nil {
		return nil
	}
	defer f.Close()

	enc := json.NewEncoder(f)
	if err := enc.Encode(config); err != nil {
		return err
	}
	return nil
}

func getEmailFromLine(str string) (string, bool, error) {
	exp, err := regexp.Compile(`([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})`)
	if err != nil {
		return "", false, err
	}
	return exp.FindString(str), exp.MatchString(str), err
}

func getRepoPath(pth, org string) string {
	flag := false
	i := 0
	repoPath := path.Dir("/")
	for _, dir := range strings.Split(pth, "/") {
		if strings.EqualFold(dir, org) {
			flag = true
		}
		if flag {
			if i >= 2 {
				repoPath = path.Join(repoPath, dir)
			}
			i++
		}
	}
	return repoPath
}

func getMaintainersEmails(pth string) (*[]string, error) {
	maintainersFileMap := []string{}
	file, err := os.Open(pth)
	if err != nil {
		if !os.IsNotExist(err) {
			return &maintainersFileMap, err
		}
	}
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		strEmail, isEmail, err := getEmailFromLine(scanner.Text())
		if err != nil {
			return nil, err
		}
		if isEmail {
			email := []string{strEmail}
			maintainersFileMap = append(maintainersFileMap, email...)
		}
	}
	sort.Strings(maintainersFileMap)

	return &maintainersFileMap, nil
}

func createMaintainerDirectoriesMap(pth, cpth, maintainerEmail string, belongsToOthers bool) error {
	names, err := ioutil.ReadDir(pth)
	if err != nil {
		return err
	}
	// Look for the Maintainer File
	foundMaintainersFile := false
	iAmOneOfTheMaintainers := false
	belongsToOtherMaintainers := false
	for _, name := range names {
		if strings.EqualFold(name.Name(), MaintainersFileName) {
			foundMaintainersFile = true
			emails, err := getMaintainersEmails(path.Join(pth, name.Name()))
			if err != nil {
				return err
			}
			i := sort.SearchStrings(*emails, maintainerEmail)
			if i < len(*emails) && (*emails)[i] == maintainerEmail {
				iAmOneOfTheMaintainers = true
			}
		}
	}
	// Check if we need to add the directory to the maintainer's  directories mapping tree
	if (!foundMaintainersFile && !belongsToOthers) || iAmOneOfTheMaintainers {
		tmpcpth := cpth
		if cpth == "" {
			tmpcpth = "."
		}
		currentPath := []string{tmpcpth}
		maintainerDirectoriesMap.paths = append(maintainerDirectoriesMap.paths, currentPath...)
	} else if foundMaintainersFile || belongsToOthers {
		belongsToOtherMaintainers = true
	}
	for _, name := range names {
		if name.IsDir() && name.Name()[0] != '.' {
			tmpcpth := path.Join(cpth, name.Name())
			newPath := path.Join(pth, name.Name())
			createMaintainerDirectoriesMap(newPath, tmpcpth, maintainerEmail, belongsToOtherMaintainers)
		}
	}

	return err
}

func getOriginPath(org string) (string, error) {
	currentPath, err := os.Getwd()
	if err != nil {
		return "", err
	}

	originPath := path.Dir("/")
	for _, dir := range strings.Split(currentPath, "/") {
		originPath = path.Join(originPath, dir)
		if strings.EqualFold(dir, org) {
			break
		}
	}
	return originPath, err
}

func NewMaintainer(client *gh.Client, org, repo string) (*Maintainer, error) {

	config, err := LoadConfig()
	if err == nil {
		client.WithToken(config.Token)
	}

	originPath, err := getOriginPath(org)
	if err != nil {
		return nil, err
	}
	originPath = path.Join(originPath, repo)

	email, err := GetMaintainerEmail()
	if err != nil {
		return nil, err
	}

	err = createMaintainerDirectoriesMap(originPath, "", email, false)
	if err != nil {
		return nil, err
	}

	return &Maintainer{
		repo:           gh.Repo{Name: repo, UserName: org},
		client:         client,
		directoriesMap: &maintainerDirectoriesMap,
		email:          email,
	}, nil
}

func (m *Maintainer) Repository() (*gh.Repository, error) {
	return m.client.Repository(m.repo, nil)
}

// Return all the pull requests that I care about
func (m *Maintainer) GetPullRequestsThatICareAbout(showAll bool, state string) ([]*gh.PullRequest, error) {

	if showAll {
		return m.GetPullRequests(state)
	}

	filteredPrs := []*gh.PullRequest{}
	prs, err := m.GetPullRequests(state)
	if err != nil {
		return nil, err
	}
	for _, p := range prs {
		prfs, err := m.GetPullRequestFiles(strconv.Itoa(p.Number))
		if err != nil {
			return nil, err
		}
		for _, prf := range prfs {
			dirPath := filepath.Dir(prf.FileName)
			i := sort.SearchStrings((*m.directoriesMap).paths, dirPath)
			if i < len(m.directoriesMap.paths) && (*m.directoriesMap).paths[i] == dirPath {
				pr := []*gh.PullRequest{p}
				filteredPrs = append(filteredPrs, pr...)
				break
			}
		}
		fmt.Printf(".")

	}
	return filteredPrs, nil
}

// Return all pull requests
func (m *Maintainer) GetPullRequests(state string) ([]*gh.PullRequest, error) {
	o := &gh.Options{}
	o.QueryParams = map[string]string{
		"sort":      "updated",
		"direction": "asc",
		"state":     state,
		"per_page":  "100",
	}
	prevSize := -1
	page := 1
	allPRs := []*gh.PullRequest{}
	for len(allPRs) != prevSize {
		o.QueryParams["page"] = strconv.Itoa(page)
		if prs, err := m.client.PullRequests(m.repo, o); err != nil {
			return nil, err
		} else {
			prevSize = len(allPRs)
			allPRs = append(allPRs, prs...)
			page += 1
		}
		fmt.Printf(".")
	}
	return allPRs, nil
}

// Return all pull request Files
func (m *Maintainer) GetPullRequestFiles(number string) ([]*gh.PullRequestFile, error) {
	o := &gh.Options{}
	o.QueryParams = map[string]string{}
	allPrFiles := []*gh.PullRequestFile{}

	if prfs, err := m.client.PullRequestFiles(m.repo, number, o); err != nil {
		return nil, err
	} else {
		allPrFiles = append(allPrFiles, prfs...)

	}
	return allPrFiles, nil
}

func (m *Maintainer) GetFirstPullRequest(state, sortBy string) (*gh.PullRequest, error) {
	o := &gh.Options{}
	o.QueryParams = map[string]string{
		"state":     state,
		"per_page":  "1",
		"page":      "1",
		"sort":      sortBy,
		"direction": "asc",
	}
	prs, err := m.client.PullRequests(m.repo, o)
	if err != nil {
		return nil, err
	}
	if len(prs) == 0 {
		return nil, fmt.Errorf("No matching pull request")
	}
	return prs[0], nil
}

// Return a single pull request
// Return pr's comments if requested
func (m *Maintainer) GetPullRequest(number string, comments bool) (*gh.PullRequest, []gh.Comment, error) {
	var c []gh.Comment
	pr, err := m.client.PullRequest(m.repo, number, nil)
	if err != nil {
		return nil, nil, err
	}
	if comments {
		c, err = m.GetComments(number)
		if err != nil {
			return nil, nil, err
		}
	}
	return pr, c, nil
}

// Return all comments for an issue or pull request
func (m *Maintainer) GetComments(number string) ([]gh.Comment, error) {
	return m.client.Comments(m.repo, number, nil)
}

// Add a comment to an existing pull request
func (m *Maintainer) AddComment(number, comment string) (gh.Comment, error) {
	return m.client.AddComment(m.repo, number, comment)
}

// Merge a pull request
// If no LGTMs are in the comments require force to be true
func (m *Maintainer) MergePullRequest(number, comment string, force bool) (gh.Merge, error) {
	comments, err := m.GetComments(number)
	if err != nil {
		return gh.Merge{}, err
	}
	isApproved := false
	for _, c := range comments {
		// FIXME: Again should check for LGTM from a maintainer
		if strings.Contains(c.Body, "LGTM") {
			isApproved = true
			break
		}
	}
	if !isApproved && !force {
		return gh.Merge{}, fmt.Errorf("Pull request %s has not been approved", number)
	}
	o := &gh.Options{}
	o.Params = map[string]string{
		"commit_message": comment,
	}
	return m.client.MergePullRequest(m.repo, number, o)
}

// Checkout the pull request into the working tree of
// the users repository.
// This will mimic the operations on the manual merge view
func (m *Maintainer) Checkout(pr *gh.PullRequest) error {
	var (
		userBranch        = fmt.Sprintf("%s-%s", pr.User.Login, pr.Head.Ref)
		destinationBranch = pr.Base.Ref
	)

	// Checkout a new branch locally before pulling the changes
	if err := Git("checkout", "-b", userBranch, destinationBranch); err != nil {
		return err
	}

	if err := Git("pull", pr.Head.Repo.CloneURL, pr.Head.Ref); err != nil {
		return err
	}
	return nil
}

func (m *Maintainer) GetFirstIssue(state, sortBy string) (*gh.Issue, error) {
	o := &gh.Options{}
	o.QueryParams = map[string]string{
		"state":     state,
		"per_page":  "1",
		"page":      "1",
		"sort":      sortBy,
		"direction": "asc",
	}
	issues, err := m.client.Issues(m.repo, o)
	if err != nil {
		return &gh.Issue{}, err
	}
	if len(issues) == 0 {
		return &gh.Issue{}, fmt.Errorf("No matching issues")
	}
	return issues[0], nil
}

// GetIssues queries the GithubAPI for all issues matching the state `state` and the
// assignee `assignee`.
// See http://developer.github.com/v3/issues/#list-issues-for-a-repository
func (m *Maintainer) GetIssues(state, assignee string) ([]*gh.Issue, error) {
	o := &gh.Options{}
	o.QueryParams = map[string]string{
		"sort":      "updated",
		"direction": "asc",
		"state":     state,
		"per_page":  "100",
	}
	// If assignee == "", don't add it to the params.
	// This will show all issues, assigned or not.
	if assignee != "" {
		o.QueryParams["assignee"] = assignee
	}
	prevSize := -1
	page := 1
	all := []*gh.Issue{}
	for len(all) != prevSize {
		o.QueryParams["page"] = strconv.Itoa(page)
		if issues, err := m.client.Issues(m.repo, o); err != nil {
			return nil, err
		} else {
			prevSize = len(all)
			all = append(all, issues...)
			page += 1
		}
		fmt.Printf(".")
	}
	return all, nil
}
<|endoftext|>