<commit_before>package plugins

import (
    "github.com/bwmarrin/discordgo"
    "strings"
    "github.com/Seklfreak/Robyul2/helpers"
    rethink "github.com/gorethink/gorethink"
    "math"
    "time"
    "math/rand"
    "sync"
    "errors"
    "github.com/Seklfreak/Robyul2/ratelimits"
    "fmt"
    "github.com/bradfitz/slice"
    "strconv"
)

type Levels struct {
    sync.RWMutex

    buckets map[string]int8
}

var (
    LevelsBucket = &ratelimits.BucketContainer{}

    // How many keys a bucket may contain when created
    BUCKET_INITIAL_FILL int8 = 1

    // The maximum amount of keys a user may possess
    BUCKET_UPPER_BOUND int8 = 1

    // How often new keys drip into the buckets
    DROP_INTERVAL = 60 * time.Second

    // How many keys may drop at a time
    DROP_SIZE int8 = 1
)

func (m *Levels) Commands() []string {
    return []string{
        "level",
    }
}

type DB_Levels_ServerUser struct {
    ID      string  `gorethink:"id,omitempty"`
    UserID  string  `gorethink:"userid"`
    GuildID string  `gorethink:"guildid"`
    Exp     int64   `gorethink:"exp"`
}

func (m *Levels) Init(session *discordgo.Session) {
    m.BucketInit()
}

func (m *Levels) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) {
    switch command {
    case "level": // [p]level <user> or [p]level top
        session.ChannelTyping(msg.ChannelID)
        targetUser, err := session.User(msg.Author.ID)
        helpers.Relax(err)
        args := strings.Split(content, " ")

        channel, err := session.Channel(msg.ChannelID)
        helpers.Relax(err)

        if len(args) >= 1 && args[0] != "" {
            switch args[0] {
            case "leaderboard", "top": // [p]level top
                var levelsServersUsers []DB_Levels_ServerUser
                listCursor, err := rethink.Table("levels_serverusers").Filter(
                    rethink.Row.Field("guildid").Eq(channel.GuildID),
                ).Run(helpers.GetDB())
                helpers.Relax(err)
                defer listCursor.Close()
                err = listCursor.All(&levelsServersUsers)

                if err == rethink.ErrEmptyResult || len(levelsServersUsers) <= 0 {
                    _, err := session.ChannelMessageSend(msg.ChannelID, helpers.GetText("plugins.levels.top-server-no-stats"))
                    helpers.Relax(err)
                    return
                } else if err != nil {
                    helpers.Relax(err)
                }

                slice.Sort(levelsServersUsers, func(i, j int) bool {
                    return levelsServersUsers[i].Exp > levelsServersUsers[j].Exp
                })

                topLevelEmbed := &discordgo.MessageEmbed{
                    Color: 0x0FADED,
                    Title: helpers.GetText("plugins.levels.top-server-embed-title"),
                    //Description: "",
                    //Footer: &discordgo.MessageEmbedFooter{Text: helpers.GetText("plugins.stats.voicestats-embed-footer")},
                    Fields: []*discordgo.MessageEmbedField{},
                }

                i := 0
                for _, levelsServersUser := range levelsServersUsers {
                    currentMember, err := session.GuildMember(channel.GuildID, levelsServersUser.UserID)
                    fullUsername := currentMember.User.Username
                    if currentMember.Nick != "" {
                        fullUsername += " ~ " + currentMember.Nick
                    }
                    helpers.Relax(err)
                    topLevelEmbed.Fields = append(topLevelEmbed.Fields, &discordgo.MessageEmbedField{
                        Name:   fmt.Sprintf("#%d: %s", i+1, fullUsername),
                        Value:  fmt.Sprintf("Level: %d, Experience: %d EXP", m.getLevelFromExp(levelsServersUser.Exp), levelsServersUser.Exp),
                        Inline: false,
                    })
                    i++
                    if i >= 10 {
                        break
                    }
                }

                _, err = session.ChannelMessageSendEmbed(msg.ChannelID, topLevelEmbed)
                helpers.Relax(err)
                return
            }
            targetUser, err = helpers.GetUserFromMention(args[0])
            if targetUser == nil || targetUser.ID == "" {
                _, err := session.ChannelMessageSend(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
                helpers.Relax(err)
                return
            }
        }

        var levelsServersUser []DB_Levels_ServerUser
        listCursor, err := rethink.Table("levels_serverusers").Filter(
            rethink.Row.Field("userid").Eq(targetUser.ID),
        ).Run(helpers.GetDB())
        helpers.Relax(err)
        defer listCursor.Close()
        err = listCursor.All(&levelsServersUser)

        if err == rethink.ErrEmptyResult {
            _, err := session.ChannelMessageSend(msg.ChannelID, helpers.GetText("plugins.levels.level-no-stats"))
            helpers.Relax(err)
            return
        } else if err != nil {
            helpers.Relax(err)
        }

        var levelThisServerUser DB_Levels_ServerUser
        var totalExp int64
        for _, levelsServerUser := range levelsServersUser {
            if levelsServerUser.GuildID == channel.GuildID {
                levelThisServerUser = levelsServerUser
            }
            totalExp += levelsServerUser.Exp
        }

        if totalExp <= 0 {
            _, err := session.ChannelMessageSend(msg.ChannelID, helpers.GetText("plugins.levels.level-no-stats"))
            helpers.Relax(err)
            return
        }

        currentMember, err := session.GuildMember(channel.GuildID, levelThisServerUser.UserID)
        fullUsername := currentMember.User.Username
        if currentMember.Nick != "" {
            fullUsername += " ~ " + currentMember.Nick
        }

        userLevelEmbed := &discordgo.MessageEmbed{
            Color: 0x0FADED,
            Title: helpers.GetTextF("plugins.levels.user-embed-title", fullUsername),
            //Description: "",
            //Footer: &discordgo.MessageEmbedFooter{Text: helpers.GetText("plugins.stats.voicestats-embed-footer")},
            Fields: []*discordgo.MessageEmbedField{
                &discordgo.MessageEmbedField{
                    Name:   "Level",
                    Value:  strconv.Itoa(m.getLevelFromExp(levelThisServerUser.Exp)),
                    Inline: true,
                },
                &discordgo.MessageEmbedField{
                    Name:   "Next Level Progress",
                    Value:  strconv.Itoa(m.getProgressToNextLevelFromExp(levelThisServerUser.Exp)) + " %",
                    Inline: true,
                },
                &discordgo.MessageEmbedField{
                    Name:   ":white_circle:",
                    Value:  ":white_circle:",
                    Inline: true,
                },
                &discordgo.MessageEmbedField{
                    Name:   "Global Level",
                    Value:  strconv.Itoa(m.getLevelFromExp(totalExp)),
                    Inline: true,
                },
                &discordgo.MessageEmbedField{
                    Name:   "Next Global Level Progress",
                    Value:  strconv.Itoa(m.getProgressToNextLevelFromExp(totalExp)) + " %",
                    Inline: true,
                },
                &discordgo.MessageEmbedField{
                    Name:   ":white_circle:",
                    Value:  ":white_circle:",
                    Inline: true,
                },
            },
        }

        _, err = session.ChannelMessageSendEmbed(msg.ChannelID, userLevelEmbed)
        helpers.Relax(err)
        return
    }

}

func (m *Levels) OnMessage(content string, msg *discordgo.Message, session *discordgo.Session) {
    channel, err := session.Channel(msg.ChannelID)
    helpers.Relax(err)
    // ignore bot messages
    if msg.Author.Bot == true {
        return
    }
    // ignore commands
    prefix := helpers.GetPrefixForServer(channel.GuildID)
    if prefix != "" {
        if strings.HasPrefix(content, prefix) {
            return
        }
    }
    // check if bucket is empty
    if !m.BucketHasKeys(channel.GuildID + msg.Author.ID) {
        //m.BucketSet(channel.GuildID+msg.Author.ID, -1)
        return
    }

    err = m.BucketDrain(1, channel.GuildID+msg.Author.ID)
    helpers.Relax(err)

    levelsServerUser := m.getLevelsServerUserOrCreateNew(channel.GuildID, msg.Author.ID)
    levelsServerUser.Exp += m.getRandomExpForMessage()
    m.setLevelsServerUser(levelsServerUser)
}

func (m *Levels) OnGuildMemberAdd(member *discordgo.Member, session *discordgo.Session) {

}

func (m *Levels) OnGuildMemberRemove(member *discordgo.Member, session *discordgo.Session) {

}

func (m *Levels) getLevelsServerUserOrCreateNew(guildid string, userid string) DB_Levels_ServerUser {
    var levelsServerUser DB_Levels_ServerUser
    listCursor, err := rethink.Table("levels_serverusers").Filter(
        rethink.Row.Field("guildid").Eq(guildid),
    ).Filter(
        rethink.Row.Field("userid").Eq(userid),
    ).Run(helpers.GetDB())
    helpers.Relax(err)
    defer listCursor.Close()
    err = listCursor.One(&levelsServerUser)

    if err == rethink.ErrEmptyResult {
        insert := rethink.Table("levels_serverusers").Insert(DB_Levels_ServerUser{GuildID: guildid, UserID: userid})
        _, e := insert.RunWrite(helpers.GetDB())
        if e != nil {
            panic(e)
        } else {
            return m.getLevelsServerUserOrCreateNew(guildid, userid)
        }
    } else if err != nil {
        panic(err)
    }

    return levelsServerUser
}

func (m *Levels) setLevelsServerUser(entry DB_Levels_ServerUser) {
    _, err := rethink.Table("levels_serverusers").Update(entry).Run(helpers.GetDB())
    helpers.Relax(err)
}

func (m *Levels) getLevelFromExp(exp int64) int {
    calculatedLevel := 0.1 * math.Sqrt(float64(exp))

    return int(math.Floor(calculatedLevel))
}

func (m *Levels) getExpForLevel(level int) int64 {
    if level <= 0 {
        return 0
    }

    calculatedExp := math.Pow(float64(level)/0.1, 2)
    return int64(calculatedExp)
}

func (m *Levels) getProgressToNextLevelFromExp(exp int64) int {
    expLevelCurrently := exp - m.getExpForLevel(m.getLevelFromExp(exp))
    expLevelNext := m.getExpForLevel((m.getLevelFromExp(exp) + 1)) - m.getExpForLevel(m.getLevelFromExp(exp))
    return int(expLevelCurrently / (expLevelNext / 100))
}

func (m *Levels) getRandomExpForMessage() int64 {
    min := 10
    max := 15
    rand.Seed(time.Now().Unix())
    return int64(rand.Intn(max-min) + min)
}

func (b *Levels) BucketInit() {
    b.Lock()
    b.buckets = make(map[string]int8)
    b.Unlock()

    go b.BucketRefiller()
}

// Refills user buckets in a set interval
func (b *Levels) BucketRefiller() {
    for {
        b.Lock()
        for user, keys := range b.buckets {
            // Chill zone
            if keys == -1 {
                b.buckets[user]++
                continue
            }

            // Chill zone exit
            if keys == 0 {
                b.buckets[user] = BUCKET_INITIAL_FILL
                continue
            }

            // More free keys for nice users :3
            if keys < BUCKET_UPPER_BOUND {
                b.buckets[user] += DROP_SIZE
                continue
            }
        }
        b.Unlock()

        time.Sleep(DROP_INTERVAL)
    }
}

// Check if the user has a bucket. If not create one
func (b *Levels) CreateBucketIfNotExists(user string) {
    b.RLock()
    _, e := b.buckets[user]
    b.RUnlock()

    if !e {
        b.Lock()
        b.buckets[user] = BUCKET_INITIAL_FILL
        b.Unlock()
    }
}

// Drains $amount from $user if he has enough keys left
func (b *Levels) BucketDrain(amount int8, user string) error {
    b.CreateBucketIfNotExists(user)

    // Check if there are enough keys left
    b.RLock()
    userAmount := b.buckets[user]
    b.RUnlock()

    if amount > userAmount {
        return errors.New("No keys left")
    }

    // Remove keys from bucket
    b.Lock()
    b.buckets[user] -= amount
    b.Unlock()

    return nil
}

// Check if the user still has keys
func (b *Levels) BucketHasKeys(user string) bool {
    b.CreateBucketIfNotExists(user)

    b.RLock()
    defer b.RUnlock()

    return b.buckets[user] > 0
}

func (b *Levels) BucketGet(user string) int8 {
    b.RLock()
    defer b.RUnlock()

    return b.buckets[user]
}

func (b *Levels) BucketSet(user string, value int8) {
    b.Lock()
    b.buckets[user] = value
    b.Unlock()
}
<commit_msg>[levels] hide EXP from levels top<commit_after>package plugins

import (
    "github.com/bwmarrin/discordgo"
    "strings"
    "github.com/Seklfreak/Robyul2/helpers"
    rethink "github.com/gorethink/gorethink"
    "math"
    "time"
    "math/rand"
    "sync"
    "errors"
    "github.com/Seklfreak/Robyul2/ratelimits"
    "fmt"
    "github.com/bradfitz/slice"
    "strconv"
)

type Levels struct {
    sync.RWMutex

    buckets map[string]int8
}

var (
    LevelsBucket = &ratelimits.BucketContainer{}

    // How many keys a bucket may contain when created
    BUCKET_INITIAL_FILL int8 = 1

    // The maximum amount of keys a user may possess
    BUCKET_UPPER_BOUND int8 = 1

    // How often new keys drip into the buckets
    DROP_INTERVAL = 60 * time.Second

    // How many keys may drop at a time
    DROP_SIZE int8 = 1
)

func (m *Levels) Commands() []string {
    return []string{
        "level",
    }
}

type DB_Levels_ServerUser struct {
    ID      string  `gorethink:"id,omitempty"`
    UserID  string  `gorethink:"userid"`
    GuildID string  `gorethink:"guildid"`
    Exp     int64   `gorethink:"exp"`
}

func (m *Levels) Init(session *discordgo.Session) {
    m.BucketInit()
}

func (m *Levels) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) {
    switch command {
    case "level": // [p]level <user> or [p]level top
        session.ChannelTyping(msg.ChannelID)
        targetUser, err := session.User(msg.Author.ID)
        helpers.Relax(err)
        args := strings.Split(content, " ")

        channel, err := session.Channel(msg.ChannelID)
        helpers.Relax(err)

        if len(args) >= 1 && args[0] != "" {
            switch args[0] {
            case "leaderboard", "top": // [p]level top
                var levelsServersUsers []DB_Levels_ServerUser
                listCursor, err := rethink.Table("levels_serverusers").Filter(
                    rethink.Row.Field("guildid").Eq(channel.GuildID),
                ).Run(helpers.GetDB())
                helpers.Relax(err)
                defer listCursor.Close()
                err = listCursor.All(&levelsServersUsers)

                if err == rethink.ErrEmptyResult || len(levelsServersUsers) <= 0 {
                    _, err := session.ChannelMessageSend(msg.ChannelID, helpers.GetText("plugins.levels.top-server-no-stats"))
                    helpers.Relax(err)
                    return
                } else if err != nil {
                    helpers.Relax(err)
                }

                slice.Sort(levelsServersUsers, func(i, j int) bool {
                    return levelsServersUsers[i].Exp > levelsServersUsers[j].Exp
                })

                topLevelEmbed := &discordgo.MessageEmbed{
                    Color: 0x0FADED,
                    Title: helpers.GetText("plugins.levels.top-server-embed-title"),
                    //Description: "",
                    //Footer: &discordgo.MessageEmbedFooter{Text: helpers.GetText("plugins.stats.voicestats-embed-footer")},
                    Fields: []*discordgo.MessageEmbedField{},
                }

                i := 0
                for _, levelsServersUser := range levelsServersUsers {
                    currentMember, err := session.GuildMember(channel.GuildID, levelsServersUser.UserID)
                    fullUsername := currentMember.User.Username
                    if currentMember.Nick != "" {
                        fullUsername += " ~ " + currentMember.Nick
                    }
                    helpers.Relax(err)
                    topLevelEmbed.Fields = append(topLevelEmbed.Fields, &discordgo.MessageEmbedField{
                        Name:   fmt.Sprintf("#%d: %s", i+1, fullUsername),
                        Value:  fmt.Sprintf("Level: %d", m.getLevelFromExp(levelsServersUser.Exp)),
                        Inline: false,
                    })
                    i++
                    if i >= 10 {
                        break
                    }
                }

                _, err = session.ChannelMessageSendEmbed(msg.ChannelID, topLevelEmbed)
                helpers.Relax(err)
                return
            }
            targetUser, err = helpers.GetUserFromMention(args[0])
            if targetUser == nil || targetUser.ID == "" {
                _, err := session.ChannelMessageSend(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
                helpers.Relax(err)
                return
            }
        }

        var levelsServersUser []DB_Levels_ServerUser
        listCursor, err := rethink.Table("levels_serverusers").Filter(
            rethink.Row.Field("userid").Eq(targetUser.ID),
        ).Run(helpers.GetDB())
        helpers.Relax(err)
        defer listCursor.Close()
        err = listCursor.All(&levelsServersUser)

        if err == rethink.ErrEmptyResult {
            _, err := session.ChannelMessageSend(msg.ChannelID, helpers.GetText("plugins.levels.level-no-stats"))
            helpers.Relax(err)
            return
        } else if err != nil {
            helpers.Relax(err)
        }

        var levelThisServerUser DB_Levels_ServerUser
        var totalExp int64
        for _, levelsServerUser := range levelsServersUser {
            if levelsServerUser.GuildID == channel.GuildID {
                levelThisServerUser = levelsServerUser
            }
            totalExp += levelsServerUser.Exp
        }

        if totalExp <= 0 {
            _, err := session.ChannelMessageSend(msg.ChannelID, helpers.GetText("plugins.levels.level-no-stats"))
            helpers.Relax(err)
            return
        }

        currentMember, err := session.GuildMember(channel.GuildID, levelThisServerUser.UserID)
        fullUsername := currentMember.User.Username
        if currentMember.Nick != "" {
            fullUsername += " ~ " + currentMember.Nick
        }

        userLevelEmbed := &discordgo.MessageEmbed{
            Color: 0x0FADED,
            Title: helpers.GetTextF("plugins.levels.user-embed-title", fullUsername),
            //Description: "",
            //Footer: &discordgo.MessageEmbedFooter{Text: helpers.GetText("plugins.stats.voicestats-embed-footer")},
            Fields: []*discordgo.MessageEmbedField{
                &discordgo.MessageEmbedField{
                    Name:   "Level",
                    Value:  strconv.Itoa(m.getLevelFromExp(levelThisServerUser.Exp)),
                    Inline: true,
                },
                &discordgo.MessageEmbedField{
                    Name:   "Next Level Progress",
                    Value:  strconv.Itoa(m.getProgressToNextLevelFromExp(levelThisServerUser.Exp)) + " %",
                    Inline: true,
                },
                &discordgo.MessageEmbedField{
                    Name:   ":white_circle:",
                    Value:  ":white_circle:",
                    Inline: true,
                },
                &discordgo.MessageEmbedField{
                    Name:   "Global Level",
                    Value:  strconv.Itoa(m.getLevelFromExp(totalExp)),
                    Inline: true,
                },
                &discordgo.MessageEmbedField{
                    Name:   "Next Global Level Progress",
                    Value:  strconv.Itoa(m.getProgressToNextLevelFromExp(totalExp)) + " %",
                    Inline: true,
                },
                &discordgo.MessageEmbedField{
                    Name:   ":white_circle:",
                    Value:  ":white_circle:",
                    Inline: true,
                },
            },
        }

        _, err = session.ChannelMessageSendEmbed(msg.ChannelID, userLevelEmbed)
        helpers.Relax(err)
        return
    }

}

func (m *Levels) OnMessage(content string, msg *discordgo.Message, session *discordgo.Session) {
    channel, err := session.Channel(msg.ChannelID)
    helpers.Relax(err)
    // ignore bot messages
    if msg.Author.Bot == true {
        return
    }
    // ignore commands
    prefix := helpers.GetPrefixForServer(channel.GuildID)
    if prefix != "" {
        if strings.HasPrefix(content, prefix) {
            return
        }
    }
    // check if bucket is empty
    if !m.BucketHasKeys(channel.GuildID + msg.Author.ID) {
        //m.BucketSet(channel.GuildID+msg.Author.ID, -1)
        return
    }

    err = m.BucketDrain(1, channel.GuildID+msg.Author.ID)
    helpers.Relax(err)

    levelsServerUser := m.getLevelsServerUserOrCreateNew(channel.GuildID, msg.Author.ID)
    levelsServerUser.Exp += m.getRandomExpForMessage()
    m.setLevelsServerUser(levelsServerUser)
}

func (m *Levels) OnGuildMemberAdd(member *discordgo.Member, session *discordgo.Session) {

}

func (m *Levels) OnGuildMemberRemove(member *discordgo.Member, session *discordgo.Session) {

}

func (m *Levels) getLevelsServerUserOrCreateNew(guildid string, userid string) DB_Levels_ServerUser {
    var levelsServerUser DB_Levels_ServerUser
    listCursor, err := rethink.Table("levels_serverusers").Filter(
        rethink.Row.Field("guildid").Eq(guildid),
    ).Filter(
        rethink.Row.Field("userid").Eq(userid),
    ).Run(helpers.GetDB())
    helpers.Relax(err)
    defer listCursor.Close()
    err = listCursor.One(&levelsServerUser)

    if err == rethink.ErrEmptyResult {
        insert := rethink.Table("levels_serverusers").Insert(DB_Levels_ServerUser{GuildID: guildid, UserID: userid})
        _, e := insert.RunWrite(helpers.GetDB())
        if e != nil {
            panic(e)
        } else {
            return m.getLevelsServerUserOrCreateNew(guildid, userid)
        }
    } else if err != nil {
        panic(err)
    }

    return levelsServerUser
}

func (m *Levels) setLevelsServerUser(entry DB_Levels_ServerUser) {
    _, err := rethink.Table("levels_serverusers").Update(entry).Run(helpers.GetDB())
    helpers.Relax(err)
}

func (m *Levels) getLevelFromExp(exp int64) int {
    calculatedLevel := 0.1 * math.Sqrt(float64(exp))

    return int(math.Floor(calculatedLevel))
}

func (m *Levels) getExpForLevel(level int) int64 {
    if level <= 0 {
        return 0
    }

    calculatedExp := math.Pow(float64(level)/0.1, 2)
    return int64(calculatedExp)
}

func (m *Levels) getProgressToNextLevelFromExp(exp int64) int {
    expLevelCurrently := exp - m.getExpForLevel(m.getLevelFromExp(exp))
    expLevelNext := m.getExpForLevel((m.getLevelFromExp(exp) + 1)) - m.getExpForLevel(m.getLevelFromExp(exp))
    return int(expLevelCurrently / (expLevelNext / 100))
}

func (m *Levels) getRandomExpForMessage() int64 {
    min := 10
    max := 15
    rand.Seed(time.Now().Unix())
    return int64(rand.Intn(max-min) + min)
}

func (b *Levels) BucketInit() {
    b.Lock()
    b.buckets = make(map[string]int8)
    b.Unlock()

    go b.BucketRefiller()
}

// Refills user buckets in a set interval
func (b *Levels) BucketRefiller() {
    for {
        b.Lock()
        for user, keys := range b.buckets {
            // Chill zone
            if keys == -1 {
                b.buckets[user]++
                continue
            }

            // Chill zone exit
            if keys == 0 {
                b.buckets[user] = BUCKET_INITIAL_FILL
                continue
            }

            // More free keys for nice users :3
            if keys < BUCKET_UPPER_BOUND {
                b.buckets[user] += DROP_SIZE
                continue
            }
        }
        b.Unlock()

        time.Sleep(DROP_INTERVAL)
    }
}

// Check if the user has a bucket. If not create one
func (b *Levels) CreateBucketIfNotExists(user string) {
    b.RLock()
    _, e := b.buckets[user]
    b.RUnlock()

    if !e {
        b.Lock()
        b.buckets[user] = BUCKET_INITIAL_FILL
        b.Unlock()
    }
}

// Drains $amount from $user if he has enough keys left
func (b *Levels) BucketDrain(amount int8, user string) error {
    b.CreateBucketIfNotExists(user)

    // Check if there are enough keys left
    b.RLock()
    userAmount := b.buckets[user]
    b.RUnlock()

    if amount > userAmount {
        return errors.New("No keys left")
    }

    // Remove keys from bucket
    b.Lock()
    b.buckets[user] -= amount
    b.Unlock()

    return nil
}

// Check if the user still has keys
func (b *Levels) BucketHasKeys(user string) bool {
    b.CreateBucketIfNotExists(user)

    b.RLock()
    defer b.RUnlock()

    return b.buckets[user] > 0
}

func (b *Levels) BucketGet(user string) int8 {
    b.RLock()
    defer b.RUnlock()

    return b.buckets[user]
}

func (b *Levels) BucketSet(user string, value int8) {
    b.Lock()
    b.buckets[user] = value
    b.Unlock()
}
<|endoftext|>


<commit_before>//go:build clang

/*
Parses the libclang AST and collects functions which are a target for requirements tracking.
*/

package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/daedaleanai/reqtraq/repos"
	"github.com/go-clang/clang-v14/clang"
)

// Finds a matching compile command for a file in the repository
// @llr REQ-TRAQ-SWL-61
func findMatchingCommand(pathInRepo string, commands clang.CompileCommands) *clang.CompileCommand {
	for i := uint32(0); i < commands.Size(); i++ {
		command := commands.Command(i)
		if filepath.IsAbs(command.Filename()) {
			if command.Filename() == pathInRepo {
				return &command
			}
		} else {
			absPath, err := filepath.Abs(filepath.Join(command.Directory(), command.Filename()))
			if err != nil {
				return nil
			}
			if absPath == pathInRepo {
				return &command
			}
		}
	}
	return nil
}

// Returns true if the cursor is pointing to a possibly public entity
// @llr REQ-TRAQ-SWL-63
func IsPublic(cursor clang.Cursor) bool {
	if cursor.IsNull() {
		return true
	}

	if cursor.AccessSpecifier() != clang.AccessSpecifier_Public && cursor.AccessSpecifier() != clang.AccessSpecifier_Invalid {
		return false
	}

	return IsPublic(cursor.SemanticParent())
}

// Returns true if the cursor is part of an anonymous (or detail) namespace or class
// @llr REQ-TRAQ-SWL-62
func IsInAnonymousOrDetailNamespaceOrClass(cursor clang.Cursor) bool {
	if cursor.IsNull() {
		return false
	}

	if cursor.Kind() == clang.Cursor_Namespace && (cursor.Spelling() == "" || cursor.Spelling() == "detail") {
		return true
	}

	if ((cursor.Kind() == clang.Cursor_ClassDecl) ||
		(cursor.Kind() == clang.Cursor_StructDecl) ||
		(cursor.Kind() == clang.Cursor_ClassTemplate) ||
		(cursor.Kind() == clang.Cursor_ClassTemplatePartialSpecialization)) && (cursor.Spelling() == "") {
		return true
	}

	return IsInAnonymousOrDetailNamespaceOrClass(cursor.SemanticParent())
}

// Returns true if the given cursor is a deleted member method. Libclang does not provide a nicer way to do this.
// @llr REQ-TRAQ-SWL-61
func IsDeleted(cursor clang.Cursor) bool {
	// There is no actual way to check for deleted functions, but this should be close enough...
	return cursor.IsFunctionInlined() && cursor.Definition().IsNull() && !cursor.CXXMethod_IsDefaulted()
}

// Traverses the AST obtained from libclang to find any code and returns a map of files to a map of lines to code tags
// @llr REQ-TRAQ-SWL-61, REQ-TRAQ-SWL-62, REQ-TRAQ-SWL-63, REQ-TRAQ-SWL-69
func visitAstNodes(cursor clang.Cursor, repoName repos.RepoName, repoPath string, path string, fileMap map[string]CodeFile) map[string]map[uint]*Code {
	code := map[string]map[uint]*Code{}

	storeTag := func(cursor clang.Cursor, optional bool) {
		if strings.TrimSpace(cursor.Spelling()) == "" {
			// Ignore empty symbols
			return
		}

		file, line, _, _ := cursor.Location().FileLocation()

		// Try to get relative path to the repo
		relativePath, err := filepath.Rel(repoPath, file.TryGetRealPathName())
		if err != nil {
			// Path not in repo, continue
			return
		}

		var codeFile CodeFile
		if file, ok := fileMap[relativePath]; ok {
			codeFile = file
		} else {
			// file is not in fileMap, therefore it shall be ignored
			return
		}

		if _, ok := code[relativePath]; !ok {
			code[relativePath] = make(map[uint]*Code)
		}

		code[relativePath][uint(line)] = &Code{
			CodeFile: codeFile,
			Tag:      cursor.Spelling(),
			Symbol:   cursor.USR(),
			Line:     int(line),
			Optional: optional,
		}
	}

	cursor.Visit(func(cursor, parent clang.Cursor) clang.ChildVisitResult {
		if cursor.IsNull() {
			return clang.ChildVisit_Continue
		}

		switch cursor.Kind() {
		case clang.Cursor_UnexposedDecl:
			// libclang exposes concepts via Cursor_UnexposedDecl
			// concepts CAN have parent requirements but DO NOT HAVE TO.
			storeTag(cursor, true)

			return clang.ChildVisit_Recurse

		case clang.Cursor_ClassDecl, clang.Cursor_EnumDecl, clang.Cursor_StructDecl, clang.Cursor_ClassTemplate, clang.Cursor_ClassTemplatePartialSpecialization:
			if !IsPublic(cursor) {
				return clang.ChildVisit_Continue
			}

			// Classes, Enums, and Structs CAN have parent requirements but DO NOT HAVE TO.
			storeTag(cursor, true)

			// Only recurse if there is a chance that we can find something of interest.
			// In practice if the element is not public, there is no point in recursing
			return clang.ChildVisit_Recurse

		case clang.Cursor_Namespace:
			if cursor.Spelling() == "" || cursor.Spelling() == "detail" {
				// These namespaces are excluded from the requirements, they are considered to have
				// private access specifiers
				return clang.ChildVisit_Continue
			}
			return clang.ChildVisit_Recurse

		case clang.Cursor_TypeAliasDecl, clang.Cursor_TypeAliasTemplateDecl:
			if !IsPublic(cursor) || IsInAnonymousOrDetailNamespaceOrClass(cursor) {
				return clang.ChildVisit_Continue
			}

			// type alias CAN have parent requirements but DO NOT HAVE TO.
			storeTag(cursor, true)

		case clang.Cursor_CXXMethod, clang.Cursor_FunctionDecl, clang.Cursor_FunctionTemplate, clang.Cursor_Constructor, clang.Cursor_ConversionFunction:
			if !IsPublic(cursor) || IsInAnonymousOrDetailNamespaceOrClass(cursor) || IsDeleted(cursor) || cursor.CXXMethod_IsPureVirtual() {
				return clang.ChildVisit_Continue
			}

			if strings.HasPrefix(cursor.Spelling(), "<deduction guide for ") {
				return clang.ChildVisit_Continue
			}

			// Regular functions are never optional
			storeTag(cursor, false)
		}

		return clang.ChildVisit_Continue
	})

	return code
}

// Parses a single file as a translation unit, providing tags from all included files that are listed in the file map
// @llr REQ-TRAQ-SWL-61, REQ-TRAQ-SWL-62, REQ-TRAQ-SWL-63
func parseSingleFile(index *clang.Index, codeFile CodeFile, commands clang.CompileCommands, compilerArgs []string, fileMap map[string]CodeFile) (map[string]map[uint]*Code, error) {
	repoPath, err := repos.GetRepoPathByName(codeFile.RepoName)
	if err != nil {
		return map[string]map[uint]*Code{}, err
	}
	absRepoPath, err := filepath.Abs(string(repoPath))
	if err != nil {
		return map[string]map[uint]*Code{}, err
	}

	pathInRepo, err := repos.PathInRepo(codeFile.RepoName, codeFile.Path)
	if err != nil {
		return map[string]map[uint]*Code{}, err
	}

	pathInRepo, err = filepath.Abs(pathInRepo)
	if err != nil {
		return map[string]map[uint]*Code{}, err
	}

	cmdline := []string{}
	command := findMatchingCommand(pathInRepo, commands)
	buildDir := absRepoPath
	if command != nil {
		for i := uint32(0); i < command.NumArgs(); i++ {
			cmdline = append(cmdline, command.Arg(i))
		}
		buildDir = command.Directory()
	}

	// We need to check the translation unit in the build directory
	originalDir, err := os.Getwd()
	if err != nil {
		return map[string]map[uint]*Code{}, err
	}
	err = os.Chdir(buildDir)
	if err != nil {
		return map[string]map[uint]*Code{}, err
	}
	defer os.Chdir(originalDir)

	var tu clang.TranslationUnit
	var clangErr clang.ErrorCode
	if len(cmdline) != 0 {
		clangErr = index.ParseTranslationUnit2FullArgv("", cmdline, nil, 0, &tu)
	} else {
		clangErr = index.ParseTranslationUnit2(pathInRepo, compilerArgs, nil, 0, &tu)
	}
	if clangErr != clang.Error_Success {
		return map[string]map[uint]*Code{}, fmt.Errorf("Error parsing translation unit `%s`, %v\n", codeFile.Path, clangErr)
	}
	defer tu.Dispose()

	for _, d := range tu.Diagnostics() {
		fmt.Printf("Diagnostic for file %s: %s\n", codeFile.Path, d.Spelling())
	}

	return visitAstNodes(tu.TranslationUnitCursor(), codeFile.RepoName, absRepoPath, codeFile.Path, fileMap), nil

}

// Code parser that uses Clang to parse code
type ClangCodeParser struct{}

// Tags the code in the given repository using libclang. The compilationDatabase path and clang arguments are optional
// and used to provide libclang as much information as possible when parsing the code. This function will parse each file individually,
// but collect tagged data from all included files. This helps to tag code from header files that normally is
// not found in the compilation database (because it is only part of a translation unit as a result of being included from other files)
// @llr REQ-TRAQ-SWL-61, REQ-TRAQ-SWL-62, REQ-TRAQ-SWL-63
func (ClangCodeParser) tagCode(repoName repos.RepoName, codeFiles []CodeFile, compilationDatabase string, compilerArgs []string) (map[CodeFile][]*Code, error) {
	codeMap := make(map[string]map[uint]*Code)
	code := make(map[CodeFile][]*Code)

	index := clang.NewIndex(1, 0)
	defer index.Dispose()

	var compDb clang.CompilationDatabase
	if compilationDatabase != "" {
		pathInRepo, err := repos.PathInRepo(repoName, compilationDatabase)
		if err != nil {
			fmt.Printf("Warning: compilation database not found in path `%s`: `%v`\n", compilationDatabase, err)
		} else {
			var dbErr clang.CompilationDatabase_Error
			dbErr, compDb = clang.FromDirectory(filepath.Dir(pathInRepo))
			if dbErr != clang.CompilationDatabase_NoError {
				fmt.Printf("Warning: could not parse compilation database in path `%s`: `%v`\n", compilationDatabase, dbErr)
			} else {
				defer compDb.Dispose()
			}
		}
	}

	commands := compDb.AllCompileCommands()
	defer commands.Dispose()

	fileMap := make(map[string]CodeFile)
	for _, codeFile := range codeFiles {
		fileMap[codeFile.Path] = codeFile
	}

	for _, codeFile := range codeFiles {
		codeFromFile, err := parseSingleFile(&index, codeFile, commands, compilerArgs, fileMap)
		if err != nil {
			return code, err
		}

		// Merge with the existing code tags
		for filePath := range codeFromFile {
			if _, ok := codeMap[filePath]; !ok {
				codeMap[filePath] = make(map[uint]*Code)
			}

			for fileLine, tag := range codeFromFile[filePath] {
				codeMap[filePath][fileLine] = tag
			}
		}
	}

	// Turn the map into an array now that we cannot have duplicates
	for filePath := range codeMap {
		codeFile := fileMap[filePath]

		codeForCurrentPath := []*Code{}
		for _, tag := range codeMap[filePath] {
			codeForCurrentPath = append(codeForCurrentPath, tag)
		}
		code[codeFile] = codeForCurrentPath
	}

	return code, nil
}

// Registers libclang as a code parser
// @llr REQ-TRAQ-SWL-61, REQ-TRAQ-SWL-65
func init() {
	registerCodeParser("clang", ClangCodeParser{})
}
<commit_msg>Make clang diagnostics result in reqtraq errors<commit_after>//go:build clang

/*
Parses the libclang AST and collects functions which are a target for requirements tracking.
*/

package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/daedaleanai/reqtraq/repos"
	"github.com/go-clang/clang-v14/clang"
)

// Finds a matching compile command for a file in the repository
// @llr REQ-TRAQ-SWL-61
func findMatchingCommand(pathInRepo string, commands clang.CompileCommands) *clang.CompileCommand {
	for i := uint32(0); i < commands.Size(); i++ {
		command := commands.Command(i)
		if filepath.IsAbs(command.Filename()) {
			if command.Filename() == pathInRepo {
				return &command
			}
		} else {
			absPath, err := filepath.Abs(filepath.Join(command.Directory(), command.Filename()))
			if err != nil {
				return nil
			}
			if absPath == pathInRepo {
				return &command
			}
		}
	}
	return nil
}

// Returns true if the cursor is pointing to a possibly public entity
// @llr REQ-TRAQ-SWL-63
func IsPublic(cursor clang.Cursor) bool {
	if cursor.IsNull() {
		return true
	}

	if cursor.AccessSpecifier() != clang.AccessSpecifier_Public && cursor.AccessSpecifier() != clang.AccessSpecifier_Invalid {
		return false
	}

	return IsPublic(cursor.SemanticParent())
}

// Returns true if the cursor is part of an anonymous (or detail) namespace or class
// @llr REQ-TRAQ-SWL-62
func IsInAnonymousOrDetailNamespaceOrClass(cursor clang.Cursor) bool {
	if cursor.IsNull() {
		return false
	}

	if cursor.Kind() == clang.Cursor_Namespace && (cursor.Spelling() == "" || cursor.Spelling() == "detail") {
		return true
	}

	if ((cursor.Kind() == clang.Cursor_ClassDecl) ||
		(cursor.Kind() == clang.Cursor_StructDecl) ||
		(cursor.Kind() == clang.Cursor_ClassTemplate) ||
		(cursor.Kind() == clang.Cursor_ClassTemplatePartialSpecialization)) && (cursor.Spelling() == "") {
		return true
	}

	return IsInAnonymousOrDetailNamespaceOrClass(cursor.SemanticParent())
}

// Returns true if the given cursor is a deleted member method. Libclang does not provide a nicer way to do this.
// @llr REQ-TRAQ-SWL-61
func IsDeleted(cursor clang.Cursor) bool {
	// There is no actual way to check for deleted functions, but this should be close enough...
	return cursor.IsFunctionInlined() && cursor.Definition().IsNull() && !cursor.CXXMethod_IsDefaulted()
}

// Traverses the AST obtained from libclang to find any code and returns a map of files to a map of lines to code tags
// @llr REQ-TRAQ-SWL-61, REQ-TRAQ-SWL-62, REQ-TRAQ-SWL-63, REQ-TRAQ-SWL-69
func visitAstNodes(cursor clang.Cursor, repoName repos.RepoName, repoPath string, path string, fileMap map[string]CodeFile) map[string]map[uint]*Code {
	code := map[string]map[uint]*Code{}

	storeTag := func(cursor clang.Cursor, optional bool) {
		if strings.TrimSpace(cursor.Spelling()) == "" {
			// Ignore empty symbols
			return
		}

		file, line, _, _ := cursor.Location().FileLocation()

		// Try to get relative path to the repo
		relativePath, err := filepath.Rel(repoPath, file.TryGetRealPathName())
		if err != nil {
			// Path not in repo, continue
			return
		}

		var codeFile CodeFile
		if file, ok := fileMap[relativePath]; ok {
			codeFile = file
		} else {
			// file is not in fileMap, therefore it shall be ignored
			return
		}

		if _, ok := code[relativePath]; !ok {
			code[relativePath] = make(map[uint]*Code)
		}

		code[relativePath][uint(line)] = &Code{
			CodeFile: codeFile,
			Tag:      cursor.Spelling(),
			Symbol:   cursor.USR(),
			Line:     int(line),
			Optional: optional,
		}
	}

	cursor.Visit(func(cursor, parent clang.Cursor) clang.ChildVisitResult {
		if cursor.IsNull() {
			return clang.ChildVisit_Continue
		}

		switch cursor.Kind() {
		case clang.Cursor_UnexposedDecl:
			// libclang exposes concepts via Cursor_UnexposedDecl
			// concepts CAN have parent requirements but DO NOT HAVE TO.
			storeTag(cursor, true)

			return clang.ChildVisit_Recurse

		case clang.Cursor_ClassDecl, clang.Cursor_EnumDecl, clang.Cursor_StructDecl, clang.Cursor_ClassTemplate, clang.Cursor_ClassTemplatePartialSpecialization:
			if !IsPublic(cursor) {
				return clang.ChildVisit_Continue
			}

			// Classes, Enums, and Structs CAN have parent requirements but DO NOT HAVE TO.
			storeTag(cursor, true)

			// Only recurse if there is a chance that we can find something of interest.
			// In practice if the element is not public, there is no point in recursing
			return clang.ChildVisit_Recurse

		case clang.Cursor_Namespace:
			if cursor.Spelling() == "" || cursor.Spelling() == "detail" {
				// These namespaces are excluded from the requirements, they are considered to have
				// private access specifiers
				return clang.ChildVisit_Continue
			}
			return clang.ChildVisit_Recurse

		case clang.Cursor_TypeAliasDecl, clang.Cursor_TypeAliasTemplateDecl:
			if !IsPublic(cursor) || IsInAnonymousOrDetailNamespaceOrClass(cursor) {
				return clang.ChildVisit_Continue
			}

			// type alias CAN have parent requirements but DO NOT HAVE TO.
			storeTag(cursor, true)

		case clang.Cursor_CXXMethod, clang.Cursor_FunctionDecl, clang.Cursor_FunctionTemplate, clang.Cursor_Constructor, clang.Cursor_ConversionFunction:
			if !IsPublic(cursor) || IsInAnonymousOrDetailNamespaceOrClass(cursor) || IsDeleted(cursor) || cursor.CXXMethod_IsPureVirtual() {
				return clang.ChildVisit_Continue
			}

			if strings.HasPrefix(cursor.Spelling(), "<deduction guide for ") {
				return clang.ChildVisit_Continue
			}

			// Regular functions are never optional
			storeTag(cursor, false)
		}

		return clang.ChildVisit_Continue
	})

	return code
}

// Parses a single file as a translation unit, providing tags from all included files that are listed in the file map
// @llr REQ-TRAQ-SWL-61, REQ-TRAQ-SWL-62, REQ-TRAQ-SWL-63
func parseSingleFile(index *clang.Index, codeFile CodeFile, commands clang.CompileCommands, compilerArgs []string, fileMap map[string]CodeFile) (map[string]map[uint]*Code, error) {
	repoPath, err := repos.GetRepoPathByName(codeFile.RepoName)
	if err != nil {
		return map[string]map[uint]*Code{}, err
	}
	absRepoPath, err := filepath.Abs(string(repoPath))
	if err != nil {
		return map[string]map[uint]*Code{}, err
	}

	pathInRepo, err := repos.PathInRepo(codeFile.RepoName, codeFile.Path)
	if err != nil {
		return map[string]map[uint]*Code{}, err
	}

	pathInRepo, err = filepath.Abs(pathInRepo)
	if err != nil {
		return map[string]map[uint]*Code{}, err
	}

	cmdline := []string{}
	command := findMatchingCommand(pathInRepo, commands)
	buildDir := absRepoPath
	if command != nil {
		for i := uint32(0); i < command.NumArgs(); i++ {
			cmdline = append(cmdline, command.Arg(i))
		}
		buildDir = command.Directory()
	}

	// We need to check the translation unit in the build directory
	originalDir, err := os.Getwd()
	if err != nil {
		return map[string]map[uint]*Code{}, err
	}
	err = os.Chdir(buildDir)
	if err != nil {
		return map[string]map[uint]*Code{}, err
	}
	defer os.Chdir(originalDir)

	var tu clang.TranslationUnit
	var clangErr clang.ErrorCode
	if len(cmdline) != 0 {
		clangErr = index.ParseTranslationUnit2FullArgv("", cmdline, nil, 0, &tu)
	} else {
		clangErr = index.ParseTranslationUnit2(pathInRepo, compilerArgs, nil, 0, &tu)
	}
	if clangErr != clang.Error_Success {
		return map[string]map[uint]*Code{}, fmt.Errorf("Error parsing translation unit `%s`, %v\n", codeFile.Path, clangErr)
	}
	defer tu.Dispose()

	for _, d := range tu.Diagnostics() {
		fmt.Printf("Diagnostic for file %s: %s\n", codeFile.Path, d.Spelling())
	}
	if len(tu.Diagnostics()) != 0 {
		return map[string]map[uint]*Code{}, fmt.Errorf("Diagnostic errors parsing translation unit `%s`\n", codeFile.Path)
	}

	return visitAstNodes(tu.TranslationUnitCursor(), codeFile.RepoName, absRepoPath, codeFile.Path, fileMap), nil

}

// Code parser that uses Clang to parse code
type ClangCodeParser struct{}

// Tags the code in the given repository using libclang. The compilationDatabase path and clang arguments are optional
// and used to provide libclang as much information as possible when parsing the code. This function will parse each file individually,
// but collect tagged data from all included files. This helps to tag code from header files that normally is
// not found in the compilation database (because it is only part of a translation unit as a result of being included from other files)
// @llr REQ-TRAQ-SWL-61, REQ-TRAQ-SWL-62, REQ-TRAQ-SWL-63
func (ClangCodeParser) tagCode(repoName repos.RepoName, codeFiles []CodeFile, compilationDatabase string, compilerArgs []string) (map[CodeFile][]*Code, error) {
	codeMap := make(map[string]map[uint]*Code)
	code := make(map[CodeFile][]*Code)

	index := clang.NewIndex(1, 0)
	defer index.Dispose()

	var compDb clang.CompilationDatabase
	if compilationDatabase != "" {
		pathInRepo, err := repos.PathInRepo(repoName, compilationDatabase)
		if err != nil {
			fmt.Printf("Warning: compilation database not found in path `%s`: `%v`\n", compilationDatabase, err)
		} else {
			var dbErr clang.CompilationDatabase_Error
			dbErr, compDb = clang.FromDirectory(filepath.Dir(pathInRepo))
			if dbErr != clang.CompilationDatabase_NoError {
				fmt.Printf("Warning: could not parse compilation database in path `%s`: `%v`\n", compilationDatabase, dbErr)
			} else {
				defer compDb.Dispose()
			}
		}
	}

	commands := compDb.AllCompileCommands()
	defer commands.Dispose()

	fileMap := make(map[string]CodeFile)
	for _, codeFile := range codeFiles {
		fileMap[codeFile.Path] = codeFile
	}

	for _, codeFile := range codeFiles {
		codeFromFile, err := parseSingleFile(&index, codeFile, commands, compilerArgs, fileMap)
		if err != nil {
			return code, err
		}

		// Merge with the existing code tags
		for filePath := range codeFromFile {
			if _, ok := codeMap[filePath]; !ok {
				codeMap[filePath] = make(map[uint]*Code)
			}

			for fileLine, tag := range codeFromFile[filePath] {
				codeMap[filePath][fileLine] = tag
			}
		}
	}

	// Turn the map into an array now that we cannot have duplicates
	for filePath := range codeMap {
		codeFile := fileMap[filePath]

		codeForCurrentPath := []*Code{}
		for _, tag := range codeMap[filePath] {
			codeForCurrentPath = append(codeForCurrentPath, tag)
		}
		code[codeFile] = codeForCurrentPath
	}

	return code, nil
}

// Registers libclang as a code parser
// @llr REQ-TRAQ-SWL-61, REQ-TRAQ-SWL-65
func init() {
	registerCodeParser("clang", ClangCodeParser{})
}
<|endoftext|>


<commit_before>package configurator

import (
	"encoding/json"
	"github.com/iankronquist/senior-project-experiment/src/monitor"
	"github.com/iankronquist/senior-project-experiment/src/filter"
	"io"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
)

type Config struct {
	DockerComposeName string   `json:"docker compose name"`
	MonitorName       string   `json:"monitor process name"`
	Containers        []string `json:"container names"`
}

func StartContainers(c Config) error {

	for _, containerName := range c.Containers {
		containerProc := exec.Command(c.DockerComposeName, "up", "-d", containerName)

		fmt.Println(c.DockerComposeName, "up", "-d", containerName)

		// Copy docker-compose stdout/stderr to configurator's
		compose_stderr, err := containerProc.StderrPipe()
		if err != nil {
			panic(err)
		}
		compose_stdout, err := containerProc.StdoutPipe()
		if err != nil {
			panic(err)
		}
		go io.Copy(os.Stdout, compose_stdout)
		go io.Copy(os.Stderr, compose_stderr)

		containerProc.Start()

		err = containerProc.Wait()
		if err != nil {
			fmt.Println(err)
			return err
		}
	}
	fmt.Println("Containers started!")
	return nil
}

func ReadConfig(fileName string) (Config, error) {
	config := Config{}
	data, err := ioutil.ReadFile(fileName)
	if err != nil {
		return config, err
	}
	err = json.Unmarshal(data, &config)
	if err != nil {
		return config, err
	}
	return config, nil
}

func StartMonitor(c Config) {
	FSMessages := make(chan []byte)
	FSMessagesOut := make(chan []byte)
	//networkMessages := make(chan string)
	//execMessages := make(chan string)
	fsMonitor := monitor.FSMonitor{MonitorName: c.MonitorName}
	go fsMonitor.Start(FSMessages, c.DockerComposeName)
	go filter.StartFilterStream(FSMessages, FSMessagesOut)
	for {
		select {
			case message := <-FSMessages:
				fmt.Println("Message received: ", message)
		}
	}
	/*
		go monitor.StartNetWorkMonitor(networkMessages)
		go monitor.StartExecMonitor(execMessages)
	*/
}
<commit_msg>Fix order of arguments<commit_after>package configurator

import (
	"encoding/json"
	"github.com/iankronquist/senior-project-experiment/src/monitor"
	"github.com/iankronquist/senior-project-experiment/src/filter"
	"io"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
)

type Config struct {
	DockerComposeName string   `json:"docker compose name"`
	MonitorName       string   `json:"monitor process name"`
	Containers        []string `json:"container names"`
}

func StartContainers(c Config) error {

	for _, containerName := range c.Containers {
		containerProc := exec.Command(c.DockerComposeName, "up", "-d", containerName)

		fmt.Println(c.DockerComposeName, "up", "-d", containerName)

		// Copy docker-compose stdout/stderr to configurator's
		compose_stderr, err := containerProc.StderrPipe()
		if err != nil {
			panic(err)
		}
		compose_stdout, err := containerProc.StdoutPipe()
		if err != nil {
			panic(err)
		}
		go io.Copy(os.Stdout, compose_stdout)
		go io.Copy(os.Stderr, compose_stderr)

		containerProc.Start()

		err = containerProc.Wait()
		if err != nil {
			fmt.Println(err)
			return err
		}
	}
	fmt.Println("Containers started!")
	return nil
}

func ReadConfig(fileName string) (Config, error) {
	config := Config{}
	data, err := ioutil.ReadFile(fileName)
	if err != nil {
		return config, err
	}
	err = json.Unmarshal(data, &config)
	if err != nil {
		return config, err
	}
	return config, nil
}

func StartMonitor(c Config) {
	FSMessages := make(chan []byte)
	FSMessagesOut := make(chan []byte)
	//networkMessages := make(chan string)
	//execMessages := make(chan string)
	fsMonitor := monitor.FSMonitor{MonitorName: c.MonitorName}
	go fsMonitor.Start(FSMessages, c.DockerComposeName)
	go filter.StartFilterStream(FSMessagesOut, FSMessages)
	for {
		message := <-FSMessagesOut
		fmt.Println("Message received: ", string(message))
	}
	/*
		go monitor.StartNetWorkMonitor(networkMessages)
		go monitor.StartExecMonitor(execMessages)
	*/
}
<|endoftext|>


<commit_before>package device

import (
	"context"
	"sync"
	"testing"
	"time"

	"github.com/funjack/launchcontrol/protocol"
)

var testScript = []protocol.TimedAction{
	{
		Action: protocol.Action{
			Position: 5,
			Speed:    50,
		},
		Time: time.Millisecond * 50,
	},
	{
		Action: protocol.Action{
			Position: 50,
			Speed:    40,
		},
		Time: time.Millisecond * 100,
	},
	{
		Action: protocol.Action{
			Position: 90,
			Speed:    90,
		},
		Time: time.Millisecond * 150,
	},
	{
		Action: protocol.Action{
			Position: 30,
			Speed:    30,
		},
		Time: time.Millisecond * 200,
	},
}

type fakeLaunch struct {
	sync.Mutex

	DisFunc func()

	MoveCount       int
	ConnectCount    int
	DisconnectCount int
}

func (f *fakeLaunch) Move(position, speed int) {
	f.Lock()
	defer f.Unlock()
	f.MoveCount++
}
func (f *fakeLaunch) Connect(ctx context.Context) error {
	f.Lock()
	defer f.Unlock()
	f.ConnectCount++
	return nil
}
func (f *fakeLaunch) Disconnect() {
	f.Lock()
	defer f.Unlock()
	f.DisconnectCount++
}
func (f *fakeLaunch) HandleDisconnect(fnc func()) {
	f.Lock()
	defer f.Unlock()
	f.DisFunc = fnc
}

// TestManager is a basic test running through most of managers functions.
func TestManager(t *testing.T) {
	fake := &fakeLaunch{}
	lm := NewLaunchManager(fake)
	p := protocol.NewTimedActionsPlayer()
	p.Script = testScript
	lm.SetScriptPlayer(p)

	if err := lm.Play(); err != nil {
		t.Error(err)
	}
	if err := lm.Skip(time.Millisecond * 5); err != nil {
		t.Error(err)
	}
	if err := lm.Pause(); err != nil {
		t.Error(err)
	}
	if err := lm.Resume(); err != nil {
		t.Error(err)
	}

	// Fake a disconnect
	fake.DisFunc()
	if err := lm.Pause(); err == nil {
		t.Error("player should have stopped on disconnect")
	}

	<-time.After(time.Millisecond * 500)
	if err := lm.Play(); err != nil {
		t.Error(err)
	}

	// Give some time for the scenario to play
	<-time.After(time.Millisecond * 500)

	fake.Lock()
	defer fake.Unlock()
	if fake.MoveCount != len(p.Script) {
		t.Errorf("launch did not move enough")
	}
	if fake.DisconnectCount > 0 {
		t.Errorf("launch disconnected")
	}
	if fake.ConnectCount != 2 {
		t.Errorf("launch did not connect enough")
	}
}

func TestTrace(t *testing.T) {
	fake := &fakeLaunch{}
	lm := NewLaunchManager(fake)
	p := protocol.NewTimedActionsPlayer()
	p.Script = testScript
	lm.SetScriptPlayer(p)

	trace := lm.Trace()
	if err := lm.Play(); err != nil {
		t.Error(err)
	}

	breakAtAction := 2
	breakTimer := time.After(testScript[breakAtAction-1].Time +
		time.Millisecond*25)
	traced := make([]protocol.Action, 0, len(testScript))
STOP:
	for {
		select {
		case <-breakTimer:
			break STOP
		case a := <-trace:
			traced = append(traced, a)
		}
	}
	// Make sure at least one more action is written to the unread channel
	time.Sleep(testScript[len(testScript)-1].Time - testScript[breakAtAction].Time)

	if breakAtAction != len(traced) {
		t.Errorf("did not trace all actions: want %d, got %d",
			breakAtAction, len(traced))
	}
}

func TestStop(t *testing.T) {
	fake := &fakeLaunch{}
	lm := NewLaunchManager(fake)
	p := protocol.NewTimedActionsPlayer()
	p.Script = testScript
	lm.SetScriptPlayer(p)

	if err := lm.Play(); err != nil {
		t.Error(err)
	}

	stopAtAction := 2
	time.Sleep(testScript[stopAtAction-1].Time + time.Millisecond*25)
	if err := lm.Stop(); err != nil {
		t.Error(err)
	}
	if fake.MoveCount > stopAtAction {
		t.Error("manager did not stop playing")
	}
}

func TestDump(t *testing.T) {
	fake := &fakeLaunch{}
	lm := NewLaunchManager(fake)

	if _, err := lm.Dump(); err != ErrNotSupported {
		t.Errorf("dump on empty player did not return error")
	}

	p := protocol.NewTimedActionsPlayer()
	p.Script = testScript
	lm.SetScriptPlayer(p)

	dump, err := lm.Dump()
	if err != nil {
		t.Error(err)
	}

	if len(testScript) != len(dump) {
		t.Errorf("dump is not complete: want %d, got %d",
			len(testScript), len(dump))
	}

}
<commit_msg>Fix race in device test<commit_after>package device

import (
	"context"
	"sync"
	"testing"
	"time"

	"github.com/funjack/launchcontrol/protocol"
)

var testScript = []protocol.TimedAction{
	{
		Action: protocol.Action{
			Position: 5,
			Speed:    50,
		},
		Time: time.Millisecond * 50,
	},
	{
		Action: protocol.Action{
			Position: 50,
			Speed:    40,
		},
		Time: time.Millisecond * 100,
	},
	{
		Action: protocol.Action{
			Position: 90,
			Speed:    90,
		},
		Time: time.Millisecond * 150,
	},
	{
		Action: protocol.Action{
			Position: 30,
			Speed:    30,
		},
		Time: time.Millisecond * 200,
	},
}

type fakeLaunch struct {
	sync.Mutex

	DisFunc func()

	MoveCount       int
	ConnectCount    int
	DisconnectCount int
}

func (f *fakeLaunch) Move(position, speed int) {
	f.Lock()
	defer f.Unlock()
	f.MoveCount++
}
func (f *fakeLaunch) Connect(ctx context.Context) error {
	f.Lock()
	defer f.Unlock()
	f.ConnectCount++
	return nil
}
func (f *fakeLaunch) Disconnect() {
	f.Lock()
	defer f.Unlock()
	f.DisconnectCount++
}
func (f *fakeLaunch) HandleDisconnect(fnc func()) {
	f.Lock()
	defer f.Unlock()
	f.DisFunc = fnc
}

// TestManager is a basic test running through most of managers functions.
func TestManager(t *testing.T) {
	fake := &fakeLaunch{}
	lm := NewLaunchManager(fake)
	p := protocol.NewTimedActionsPlayer()
	p.Script = testScript
	lm.SetScriptPlayer(p)

	if err := lm.Play(); err != nil {
		t.Error(err)
	}
	if err := lm.Skip(time.Millisecond * 5); err != nil {
		t.Error(err)
	}
	if err := lm.Pause(); err != nil {
		t.Error(err)
	}
	if err := lm.Resume(); err != nil {
		t.Error(err)
	}

	// Fake a disconnect
	fake.DisFunc()
	if err := lm.Pause(); err == nil {
		t.Error("player should have stopped on disconnect")
	}

	<-time.After(time.Millisecond * 500)
	if err := lm.Play(); err != nil {
		t.Error(err)
	}

	// Give some time for the scenario to play
	<-time.After(time.Millisecond * 500)

	fake.Lock()
	defer fake.Unlock()
	if fake.MoveCount != len(p.Script) {
		t.Errorf("launch did not move enough")
	}
	if fake.DisconnectCount > 0 {
		t.Errorf("launch disconnected")
	}
	if fake.ConnectCount != 2 {
		t.Errorf("launch did not connect enough")
	}
}

func TestTrace(t *testing.T) {
	fake := &fakeLaunch{}
	lm := NewLaunchManager(fake)
	p := protocol.NewTimedActionsPlayer()
	p.Script = testScript
	lm.SetScriptPlayer(p)

	trace := lm.Trace()
	if err := lm.Play(); err != nil {
		t.Error(err)
	}

	breakAtAction := 2
	breakTimer := time.After(testScript[breakAtAction-1].Time +
		time.Millisecond*25)
	traced := make([]protocol.Action, 0, len(testScript))
STOP:
	for {
		select {
		case <-breakTimer:
			break STOP
		case a := <-trace:
			traced = append(traced, a)
		}
	}
	// Make sure at least one more action is written to the unread channel
	time.Sleep(testScript[len(testScript)-1].Time - testScript[breakAtAction].Time)

	if breakAtAction != len(traced) {
		t.Errorf("did not trace all actions: want %d, got %d",
			breakAtAction, len(traced))
	}
}

func TestStop(t *testing.T) {
	fake := &fakeLaunch{}
	lm := NewLaunchManager(fake)
	if err := lm.Stop(); err != nil {
		t.Errorf("stop on empty player did return an error")
	}
	p := protocol.NewTimedActionsPlayer()
	p.Script = testScript
	lm.SetScriptPlayer(p)

	if err := lm.Play(); err != nil {
		t.Error(err)
	}

	stopAtAction := 2
	time.Sleep(testScript[stopAtAction-1].Time + time.Millisecond*25)
	if err := lm.Stop(); err != nil {
		t.Error(err)
	}
	fake.Lock()
	defer fake.Unlock()
	if fake.MoveCount > stopAtAction {
		t.Error("manager did not stop playing")
	}
}

func TestDump(t *testing.T) {
	fake := &fakeLaunch{}
	lm := NewLaunchManager(fake)

	if _, err := lm.Dump(); err != ErrNotSupported {
		t.Errorf("dump on empty player did not return error")
	}

	p := protocol.NewTimedActionsPlayer()
	p.Script = testScript
	lm.SetScriptPlayer(p)

	dump, err := lm.Dump()
	if err != nil {
		t.Error(err)
	}

	if len(testScript) != len(dump) {
		t.Errorf("dump is not complete: want %d, got %d",
			len(testScript), len(dump))
	}

}
<|endoftext|>


<commit_before>// Copyright 2009 The Go 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 os

import (
	"os";
	"syscall";
)

var Args []string;	// provided by runtime
var Envs []string;	// provided by runtime

// Exit causes the current program to exit with the given status code.
// Conventionally, code zero indicates success, non-zero an error.
// returning exit status n.
func Exit(code int) {
	syscall.Syscall(syscall.SYS_EXIT, int64(code), 0, 0)
}

<commit_msg>fix comment<commit_after>// Copyright 2009 The Go 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 os

import (
	"os";
	"syscall";
)

var Args []string;	// provided by runtime
var Envs []string;	// provided by runtime

// Exit causes the current program to exit with the given status code.
// Conventionally, code zero indicates success, non-zero an error.
func Exit(code int) {
	syscall.Syscall(syscall.SYS_EXIT, int64(code), 0, 0)
}

<|endoftext|>


<commit_before>// Copyright 2009 The Go 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 strings

// Count UTF-8 sequences in s.
// Assumes s is well-formed.
export func utflen(s string) int {
	n := 0;
	for i := 0; i < len(s); i++ {
		if s[i]&0xC0 != 0x80 {
			n++
		}
	}
	return n
}

// Split string into array of UTF-8 sequences (still strings)
export func explode(s string) *[]string {
	a := new([]string, utflen(s));
	j := 0;
	for i := 0; i < len(a); i++ {
		ej := j;
		ej++;
		for ej < len(s) && (s[ej]&0xC0) == 0x80 {
			ej++
		}
		a[i] = s[j:ej];
		j = ej
	}
	return a
}

// Count non-overlapping instances of sep in s.
export func count(s, sep string) int {
	if sep == "" {
		return utflen(s)+1
	}
	c := sep[0];
	n := 0;
	for i := 0; i+len(sep) <= len(s); i++ {
		if s[i] == c && (len(sep) == 1 || s[i:i+len(sep)] == sep) {
			n++;
			i += len(sep)-1
		}
	}
	return n
}

// Return index of first instance of sep in s.
export func index(s, sep string) int {
	if sep == "" {
		return 0
	}
	c := sep[0];
	for i := 0; i+len(sep) <= len(s); i++ {
		if s[i] == c && (len(sep) == 1 || s[i:i+len(sep)] == sep) {
			return i
		}
	}
	return -1
}

// Split string into list of strings at separators
export func split(s, sep string) *[]string {
	if sep == "" {
		return explode(s)
	}
	c := sep[0];
	start := 0;
	n := count(s, sep)+1;
	a := new([]string, n);
	na := 0;
	for i := 0; i+len(sep) <= len(s); i++ {
		if s[i] == c && (len(sep) == 1 || s[i:i+len(sep)] == sep) {
			a[na] = s[start:i];
			na++;
			start = i+len(sep);
			i += len(sep)-1
		}
	}
	a[na] = s[start:len(s)];
	return a
}

// Join list of strings with separators between them.
export func join(a *[]string, sep string) string {
	if len(a) == 0 {
		return 0
	}
	if len(a) == 1 {
		return a[0]
	}
	n := len(sep) * (len(a)-1);
	for i := 0; i < len(a); i++ {
		n += len(a[i])
	}

	b := new([]byte, n);
	bp := 0;
	for i := 0; i < len(a); i++ {
		s := a[i];
		for j := 0; j < len(s); j++ {
			b[bp] = s[j];
			bp++
		}
		if i + 1 < len(a) {
			s = sep;
			for j := 0; j < len(s); j++ {
				b[bp] = s[j];
				bp++
			}
		}
	}
	return string(b)
}

// Convert decimal string to integer.
// TODO: Doesn't check for overflow.
export func atoi(s string) (i int, ok bool) {
	// empty string bad
	if len(s) == 0 {
		return 0, false
	}

	// pick off leading sign
	neg := false;
	if s[0] == '+' {
		s = s[1:len(s)]
	} else if s[0] == '-' {
		neg = true;
		s = s[1:len(s)]
	}

	// empty string bad
	if len(s) == 0 {
		return 0, false
	}

	// pick off zero
	if s == "0" {
		return 0, true
	}

	// otherwise, leading zero bad
	if s[0] == '0' {
		return 0, false
	}

	// parse number
	n := 0;
	for i := 0; i < len(s); i++ {
		if s[i] < '0' || s[i] > '9' {
			return 0, false
		}
		n = n*10 + int(s[i] - '0')
	}
	if neg {
		n = -n
	}
	return n, true
}

export func itoa(i int) string {
	if i == 0 {
		return "0"
	}

	neg := false;	// negative
	bigneg := false;	// largest negative number
	if i < 0 {
		neg = true;
		i = -i;
		if i < 0 {
			bigneg = true;	// is largest negative int
			i--	// now positive
		}
	}

	// Assemble decimal in reverse order.
	var b [32]byte;
	bp := len(b);
	for ; i > 0; i /= 10 {
		bp--;
		b[bp] = byte(i%10) + '0'
	}
	if neg {	// add sign
		bp--;
		b[bp] = '-'
	}
	if bigneg {	// account for i-- above
		b[len(b)-1]++
	}

	// BUG return string(b[bp:len(b)])
	return string((&b)[bp:len(b)])
}
<commit_msg>To get an empty string, return an empty string, not 0.<commit_after>// Copyright 2009 The Go 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 strings

// Count UTF-8 sequences in s.
// Assumes s is well-formed.
export func utflen(s string) int {
	n := 0;
	for i := 0; i < len(s); i++ {
		if s[i]&0xC0 != 0x80 {
			n++
		}
	}
	return n
}

// Split string into array of UTF-8 sequences (still strings)
export func explode(s string) *[]string {
	a := new([]string, utflen(s));
	j := 0;
	for i := 0; i < len(a); i++ {
		ej := j;
		ej++;
		for ej < len(s) && (s[ej]&0xC0) == 0x80 {
			ej++
		}
		a[i] = s[j:ej];
		j = ej
	}
	return a
}

// Count non-overlapping instances of sep in s.
export func count(s, sep string) int {
	if sep == "" {
		return utflen(s)+1
	}
	c := sep[0];
	n := 0;
	for i := 0; i+len(sep) <= len(s); i++ {
		if s[i] == c && (len(sep) == 1 || s[i:i+len(sep)] == sep) {
			n++;
			i += len(sep)-1
		}
	}
	return n
}

// Return index of first instance of sep in s.
export func index(s, sep string) int {
	if sep == "" {
		return 0
	}
	c := sep[0];
	for i := 0; i+len(sep) <= len(s); i++ {
		if s[i] == c && (len(sep) == 1 || s[i:i+len(sep)] == sep) {
			return i
		}
	}
	return -1
}

// Split string into list of strings at separators
export func split(s, sep string) *[]string {
	if sep == "" {
		return explode(s)
	}
	c := sep[0];
	start := 0;
	n := count(s, sep)+1;
	a := new([]string, n);
	na := 0;
	for i := 0; i+len(sep) <= len(s); i++ {
		if s[i] == c && (len(sep) == 1 || s[i:i+len(sep)] == sep) {
			a[na] = s[start:i];
			na++;
			start = i+len(sep);
			i += len(sep)-1
		}
	}
	a[na] = s[start:len(s)];
	return a
}

// Join list of strings with separators between them.
export func join(a *[]string, sep string) string {
	if len(a) == 0 {
		return ""
	}
	if len(a) == 1 {
		return a[0]
	}
	n := len(sep) * (len(a)-1);
	for i := 0; i < len(a); i++ {
		n += len(a[i])
	}

	b := new([]byte, n);
	bp := 0;
	for i := 0; i < len(a); i++ {
		s := a[i];
		for j := 0; j < len(s); j++ {
			b[bp] = s[j];
			bp++
		}
		if i + 1 < len(a) {
			s = sep;
			for j := 0; j < len(s); j++ {
				b[bp] = s[j];
				bp++
			}
		}
	}
	return string(b)
}

// Convert decimal string to integer.
// TODO: Doesn't check for overflow.
export func atoi(s string) (i int, ok bool) {
	// empty string bad
	if len(s) == 0 {
		return 0, false
	}

	// pick off leading sign
	neg := false;
	if s[0] == '+' {
		s = s[1:len(s)]
	} else if s[0] == '-' {
		neg = true;
		s = s[1:len(s)]
	}

	// empty string bad
	if len(s) == 0 {
		return 0, false
	}

	// pick off zero
	if s == "0" {
		return 0, true
	}

	// otherwise, leading zero bad
	if s[0] == '0' {
		return 0, false
	}

	// parse number
	n := 0;
	for i := 0; i < len(s); i++ {
		if s[i] < '0' || s[i] > '9' {
			return 0, false
		}
		n = n*10 + int(s[i] - '0')
	}
	if neg {
		n = -n
	}
	return n, true
}

export func itoa(i int) string {
	if i == 0 {
		return "0"
	}

	neg := false;	// negative
	bigneg := false;	// largest negative number
	if i < 0 {
		neg = true;
		i = -i;
		if i < 0 {
			bigneg = true;	// is largest negative int
			i--	// now positive
		}
	}

	// Assemble decimal in reverse order.
	var b [32]byte;
	bp := len(b);
	for ; i > 0; i /= 10 {
		bp--;
		b[bp] = byte(i%10) + '0'
	}
	if neg {	// add sign
		bp--;
		b[bp] = '-'
	}
	if bigneg {	// account for i-- above
		b[len(b)-1]++
	}

	// BUG return string(b[bp:len(b)])
	return string((&b)[bp:len(b)])
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go 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 testing

import (
	"fmt";
	"flag";
)

var chatty bool;
func init() {
	flag.Bool("chatty", false, &chatty, "chatty");
}

export type T struct {
	errors	string;
	failed	bool;
	ch	*chan *T;
}

func (t *T) Fail() {
	t.failed = true
}

func (t *T) FailNow() {
	t.Fail();
	t.ch <- t;
	sys.goexit();
}

func (t *T) Log(args ...) {
	t.errors += "\t" + fmt.sprintln(args);
}

func (t *T) Logf(format string, args ...) {
	t.errors += fmt.sprintf("\t" + format, args);
	l := len(t.errors);
	if l > 0 && t.errors[l-1] != '\n' {
		t.errors += "\n"
	}
}

func (t *T) Error(args ...) {
	t.Log(args);
	t.Fail();
}

func (t *T) Errorf(format string, args ...) {
	t.Logf(format, args);
	t.Fail();
}

func (t *T) Fatal(args ...) {
	t.Log(args);
	t.FailNow();
}

func (t *T) Fatalf(format string, args ...) {
	t.Logf(format, args);
	t.FailNow();
}

export type Test struct {
	name string;
	f *(*T);
}

func TRunner(t *T, test *Test) {
	test.f(t);
	t.ch <- t;
}

export func Main(tests *[]Test) {
	ok := true;
	if len(tests) == 0 {
		println("gotest: warning: no tests to run");
	}
	for i := 0; i < len(tests); i++ {
		if chatty {
			println("=== RUN ", tests[i].name);
		}
		t := new(T);
		t.ch = new(chan *T);
		go TRunner(t, &tests[i]);
		<-t.ch;
		if t.failed {
			println("--- FAIL:", tests[i].name);
			print(t.errors);
			ok = false;
		} else if chatty {
			println("--- PASS:", tests[i].name);
			print(t.errors);
		}
	}
	if !ok {
		println("FAIL");
		sys.exit(1);
	}
	println("PASS");
}
<commit_msg>automatically add tabs after newlines<commit_after>// Copyright 2009 The Go 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 testing

import (
	"fmt";
	"flag";
)

var chatty bool;
func init() {
	flag.Bool("chatty", false, &chatty, "chatty");
}

// Insert tabs after newlines - but not the last one
func Tabify(s string) string {
	for i := 0; i < len(s) - 1; i++ {	// -1 because if last char is newline, don't bother
		if s[i] == '\n' {
			return s[0:i+1] + "\t" + Tabify(s[i+1:len(s)]);
		}
	}
	return s
}

export type T struct {
	errors	string;
	failed	bool;
	ch	*chan *T;
}

func (t *T) Fail() {
	t.failed = true
}

func (t *T) FailNow() {
	t.Fail();
	t.ch <- t;
	sys.goexit();
}

func (t *T) Log(args ...) {
	t.errors += "\t" + Tabify(fmt.sprintln(args));
}

func (t *T) Logf(format string, args ...) {
	t.errors += Tabify(fmt.sprintf("\t" + format, args));
	l := len(t.errors);
	if l > 0 && t.errors[l-1] != '\n' {
		t.errors += "\n"
	}
}

func (t *T) Error(args ...) {
	t.Log(args);
	t.Fail();
}

func (t *T) Errorf(format string, args ...) {
	t.Logf(format, args);
	t.Fail();
}

func (t *T) Fatal(args ...) {
	t.Log(args);
	t.FailNow();
}

func (t *T) Fatalf(format string, args ...) {
	t.Logf(format, args);
	t.FailNow();
}

export type Test struct {
	name string;
	f *(*T);
}

func TRunner(t *T, test *Test) {
	test.f(t);
	t.ch <- t;
}

export func Main(tests *[]Test) {
	ok := true;
	if len(tests) == 0 {
		println("gotest: warning: no tests to run");
	}
	for i := 0; i < len(tests); i++ {
		if chatty {
			println("=== RUN ", tests[i].name);
		}
		t := new(T);
		t.ch = new(chan *T);
		go TRunner(t, &tests[i]);
		<-t.ch;
		if t.failed {
			println("--- FAIL:", tests[i].name);
			print(t.errors);
			ok = false;
		} else if chatty {
			println("--- PASS:", tests[i].name);
			print(t.errors);
		}
	}
	if !ok {
		println("FAIL");
		sys.exit(1);
	}
	println("PASS");
}
<|endoftext|>


<commit_before>package dispatchers

import (
	"fmt"
	"github.com/whosonfirst/go-webhookd"
	"gopkg.in/redis.v1"
)

type PubSubDispatcher struct {
	webhookd.WebhookDispatcher
	client  *redis.Client
	channel string
}

func NewPubSubDispatcher(host string, port int, channel string) (PubSubDispatcher, error) {

	endpoint := fmt.Sprintf("%s:%d", host, port)

	client := redis.NewTCPClient(&redis.Options{
		Addr: endpoint,
	})

	// defer client.Close()

	dispatcher := PubSubDispatcher{
		client:  client,
		channel: channel,
	}

	return dispatcher, nil
}

func (dispatcher PubSubDispatcher) Dispatch(body []byte) *webhookd.WebhookError {

	rsp := dispatcher.client.Publish(dispatcher.channel, string(body))

	_, err := rsp.Result()

	if err != nil {

		code := 999
		message := err.Error()

		err := &webhookd.WebhookError{Code: code, Message: message}
		return err
	}

	return nil
}
<commit_msg>ping pubsub client before assigning to dispatcher<commit_after>package dispatchers

import (
	"fmt"
	"github.com/whosonfirst/go-webhookd"
	"gopkg.in/redis.v1"
)

type PubSubDispatcher struct {
	webhookd.WebhookDispatcher
	client  *redis.Client
	channel string
}

func NewPubSubDispatcher(host string, port int, channel string) (*PubSubDispatcher, error) {

	endpoint := fmt.Sprintf("%s:%d", host, port)

	client := redis.NewTCPClient(&redis.Options{
		Addr: endpoint,
	})

	// defer client.Close()

	_, err := client.Ping().Result()

	if err != nil {
		return nil, err
	}

	dispatcher := PubSubDispatcher{
		client:  client,
		channel: channel,
	}

	return &dispatcher, nil
}

func (dispatcher *PubSubDispatcher) Dispatch(body []byte) *webhookd.WebhookError {

	rsp := dispatcher.client.Publish(dispatcher.channel, string(body))

	_, err := rsp.Result()

	if err != nil {

		code := 999
		message := err.Error()

		err := &webhookd.WebhookError{Code: code, Message: message}
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2018 Authors of Cilium
//
// 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 clustermesh

import (
	"fmt"
	"path"
	"time"

	"github.com/cilium/cilium/pkg/controller"
	"github.com/cilium/cilium/pkg/identity/cache"
	"github.com/cilium/cilium/pkg/ipcache"
	"github.com/cilium/cilium/pkg/kvstore"
	"github.com/cilium/cilium/pkg/kvstore/allocator"
	"github.com/cilium/cilium/pkg/kvstore/store"
	"github.com/cilium/cilium/pkg/lock"
	nodeStore "github.com/cilium/cilium/pkg/node/store"
	"github.com/cilium/cilium/pkg/service"

	"github.com/sirupsen/logrus"
)

// remoteCluster represents another cluster other than the cluster the agent is
// running in
type remoteCluster struct {
	// name is the name of the cluster
	name string

	// configPath is the path to the etcd configuration to be used to
	// connect to the etcd cluster of the remote cluster
	configPath string

	// changed receives an event when the remote cluster configuration has
	// changed and is closed when the configuration file was removed
	changed chan bool

	// mesh is the cluster mesh this remote cluster belongs to
	mesh *ClusterMesh

	controllers *controller.Manager

	// remoteConnectionControllerName is the name of the backing controller
	// that maintains the remote connection
	remoteConnectionControllerName string

	// mutex protects the following variables
	// - store
	// - remoteNodes
	// - ipCacheWatcher
	// - remoteIdentityCache
	mutex lock.RWMutex

	// store is the shared store representing all nodes in the remote cluster
	remoteNodes *store.SharedStore

	// remoteServices is the shared store representing services in remote
	// clusters
	remoteServices *store.SharedStore

	// ipCacheWatcher is the watcher that notifies about IP<->identity
	// changes in the remote cluster
	ipCacheWatcher *ipcache.IPIdentityWatcher

	// remoteIdentityCache is a locally cached copy of the identity
	// allocations in the remote cluster
	remoteIdentityCache *allocator.RemoteCache

	// backend is the kvstore backend being used
	backend kvstore.BackendOperations
}

var (
	// skipKvstoreConnection skips the etcd connection, used for testing
	skipKvstoreConnection bool
)

func (rc *remoteCluster) getLogger() *logrus.Entry {
	var (
		status string
		err    error
	)

	if rc.backend != nil {
		status, err = rc.backend.Status()
	}

	return log.WithFields(logrus.Fields{
		fieldClusterName:   rc.name,
		fieldConfig:        rc.configPath,
		fieldKVStoreStatus: status,
		fieldKVStoreErr:    err,
	})
}

func (rc *remoteCluster) restartRemoteConnection() {
	rc.controllers.UpdateController(rc.remoteConnectionControllerName,
		controller.ControllerParams{
			DoFunc: func() error {
				backend, err := kvstore.NewClient(kvstore.EtcdBackendName,
					map[string]string{
						kvstore.EtcdOptionConfig: rc.configPath,
					})
				if err != nil {
					return err
				}

				remoteNodes, err := store.JoinSharedStore(store.Configuration{
					Prefix:                  path.Join(nodeStore.NodeStorePrefix, rc.name),
					KeyCreator:              rc.mesh.conf.NodeKeyCreator,
					SynchronizationInterval: time.Minute,
					Backend:                 backend,
					Observer:                rc.mesh.conf.NodeObserver(),
				})
				if err != nil {
					backend.Close()
					return err
				}

				remoteServices, err := store.JoinSharedStore(store.Configuration{
					Prefix: path.Join(service.ServiceStorePrefix, rc.name),
					KeyCreator: func() store.Key {
						svc := service.ClusterService{}
						return &svc
					},
					SynchronizationInterval: time.Minute,
					Backend:                 backend,
					Observer: &remoteServiceObserver{
						remoteCluster: rc,
					},
				})
				if err != nil {
					remoteNodes.Close()
					backend.Close()
					return err
				}

				ipCacheWatcher := ipcache.NewIPIdentityWatcher(backend)
				go ipCacheWatcher.Watch()

				remoteIdentityCache := cache.WatchRemoteIdentities(backend)

				rc.mutex.Lock()
				rc.remoteNodes = remoteNodes
				rc.remoteServices = remoteServices
				rc.backend = backend
				rc.ipCacheWatcher = ipCacheWatcher
				rc.remoteIdentityCache = remoteIdentityCache
				rc.mutex.Unlock()

				rc.getLogger().Info("Established connection to remote etcd")

				return nil
			},
			StopFunc: func() error {
				rc.mutex.Lock()
				if rc.ipCacheWatcher != nil {
					rc.ipCacheWatcher.Close()
				}

				if rc.remoteNodes != nil {
					rc.remoteNodes.Close()
				}
				if rc.remoteIdentityCache != nil {
					rc.remoteIdentityCache.Close()
				}
				if rc.remoteServices != nil {
					rc.remoteServices.Close()
				}
				if rc.backend != nil {
					rc.backend.Close()
				}
				rc.mutex.Unlock()

				rc.getLogger().Info("All resources of remote cluster cleaned up")

				return nil
			},
		},
	)
}

func (rc *remoteCluster) onInsert() {
	rc.getLogger().Info("New remote cluster discovered")

	if skipKvstoreConnection {
		return
	}

	rc.remoteConnectionControllerName = fmt.Sprintf("remote-etcd-%s", rc.name)
	rc.restartRemoteConnection()

	go func() {
		for {
			val := <-rc.changed
			if val {
				rc.getLogger().Info("etcd configuration has changed, re-creating connection")
				rc.restartRemoteConnection()
			} else {
				rc.getLogger().Info("Closing connection to remote etcd")
				return
			}
		}
	}()
}

func (rc *remoteCluster) onRemove() {
	rc.controllers.RemoveAllAndWait()
	close(rc.changed)

	rc.getLogger().Info("Remote cluster disconnected")
}

func (rc *remoteCluster) isReady() bool {
	rc.mutex.RLock()
	defer rc.mutex.RUnlock()

	return rc.backend != nil && rc.remoteNodes != nil && rc.ipCacheWatcher != nil
}
<commit_msg>clustermesh: Release old connection on restart<commit_after>// Copyright 2018 Authors of Cilium
//
// 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 clustermesh

import (
	"fmt"
	"path"
	"time"

	"github.com/cilium/cilium/pkg/controller"
	"github.com/cilium/cilium/pkg/identity/cache"
	"github.com/cilium/cilium/pkg/ipcache"
	"github.com/cilium/cilium/pkg/kvstore"
	"github.com/cilium/cilium/pkg/kvstore/allocator"
	"github.com/cilium/cilium/pkg/kvstore/store"
	"github.com/cilium/cilium/pkg/lock"
	nodeStore "github.com/cilium/cilium/pkg/node/store"
	"github.com/cilium/cilium/pkg/service"

	"github.com/sirupsen/logrus"
)

// remoteCluster represents another cluster other than the cluster the agent is
// running in
type remoteCluster struct {
	// name is the name of the cluster
	name string

	// configPath is the path to the etcd configuration to be used to
	// connect to the etcd cluster of the remote cluster
	configPath string

	// changed receives an event when the remote cluster configuration has
	// changed and is closed when the configuration file was removed
	changed chan bool

	// mesh is the cluster mesh this remote cluster belongs to
	mesh *ClusterMesh

	controllers *controller.Manager

	// remoteConnectionControllerName is the name of the backing controller
	// that maintains the remote connection
	remoteConnectionControllerName string

	// mutex protects the following variables
	// - store
	// - remoteNodes
	// - ipCacheWatcher
	// - remoteIdentityCache
	mutex lock.RWMutex

	// store is the shared store representing all nodes in the remote cluster
	remoteNodes *store.SharedStore

	// remoteServices is the shared store representing services in remote
	// clusters
	remoteServices *store.SharedStore

	// ipCacheWatcher is the watcher that notifies about IP<->identity
	// changes in the remote cluster
	ipCacheWatcher *ipcache.IPIdentityWatcher

	// remoteIdentityCache is a locally cached copy of the identity
	// allocations in the remote cluster
	remoteIdentityCache *allocator.RemoteCache

	// backend is the kvstore backend being used
	backend kvstore.BackendOperations
}

var (
	// skipKvstoreConnection skips the etcd connection, used for testing
	skipKvstoreConnection bool
)

func (rc *remoteCluster) getLogger() *logrus.Entry {
	var (
		status string
		err    error
	)

	if rc.backend != nil {
		status, err = rc.backend.Status()
	}

	return log.WithFields(logrus.Fields{
		fieldClusterName:   rc.name,
		fieldConfig:        rc.configPath,
		fieldKVStoreStatus: status,
		fieldKVStoreErr:    err,
	})
}

func (rc *remoteCluster) releaseOldConnection() {
	if rc.ipCacheWatcher != nil {
		rc.ipCacheWatcher.Close()
		rc.ipCacheWatcher = nil
	}

	if rc.remoteNodes != nil {
		rc.remoteNodes.Close()
		rc.remoteNodes = nil
	}
	if rc.remoteIdentityCache != nil {
		rc.remoteIdentityCache.Close()
		rc.remoteIdentityCache = nil
	}
	if rc.remoteServices != nil {
		rc.remoteServices.Close()
		rc.remoteServices = nil
	}
	if rc.backend != nil {
		rc.backend.Close()
		rc.backend = nil
	}
}

func (rc *remoteCluster) restartRemoteConnection() {
	rc.controllers.UpdateController(rc.remoteConnectionControllerName,
		controller.ControllerParams{
			DoFunc: func() error {
				rc.mutex.Lock()
				if rc.backend != nil {
					rc.releaseOldConnection()
				}
				rc.mutex.Unlock()

				backend, err := kvstore.NewClient(kvstore.EtcdBackendName,
					map[string]string{
						kvstore.EtcdOptionConfig: rc.configPath,
					})
				if err != nil {
					return err
				}

				remoteNodes, err := store.JoinSharedStore(store.Configuration{
					Prefix:                  path.Join(nodeStore.NodeStorePrefix, rc.name),
					KeyCreator:              rc.mesh.conf.NodeKeyCreator,
					SynchronizationInterval: time.Minute,
					Backend:                 backend,
					Observer:                rc.mesh.conf.NodeObserver(),
				})
				if err != nil {
					backend.Close()
					return err
				}

				remoteServices, err := store.JoinSharedStore(store.Configuration{
					Prefix: path.Join(service.ServiceStorePrefix, rc.name),
					KeyCreator: func() store.Key {
						svc := service.ClusterService{}
						return &svc
					},
					SynchronizationInterval: time.Minute,
					Backend:                 backend,
					Observer: &remoteServiceObserver{
						remoteCluster: rc,
					},
				})
				if err != nil {
					remoteNodes.Close()
					backend.Close()
					return err
				}

				ipCacheWatcher := ipcache.NewIPIdentityWatcher(backend)
				go ipCacheWatcher.Watch()

				remoteIdentityCache := cache.WatchRemoteIdentities(backend)

				rc.mutex.Lock()
				rc.remoteNodes = remoteNodes
				rc.remoteServices = remoteServices
				rc.backend = backend
				rc.ipCacheWatcher = ipCacheWatcher
				rc.remoteIdentityCache = remoteIdentityCache
				rc.mutex.Unlock()

				rc.getLogger().Info("Established connection to remote etcd")

				return nil
			},
			StopFunc: func() error {
				rc.mutex.Lock()
				rc.releaseOldConnection()
				rc.mutex.Unlock()

				rc.getLogger().Info("All resources of remote cluster cleaned up")

				return nil
			},
		},
	)
}

func (rc *remoteCluster) onInsert() {
	rc.getLogger().Info("New remote cluster discovered")

	if skipKvstoreConnection {
		return
	}

	rc.remoteConnectionControllerName = fmt.Sprintf("remote-etcd-%s", rc.name)
	rc.restartRemoteConnection()

	go func() {
		for {
			val := <-rc.changed
			if val {
				rc.getLogger().Info("etcd configuration has changed, re-creating connection")
				rc.restartRemoteConnection()
			} else {
				rc.getLogger().Info("Closing connection to remote etcd")
				return
			}
		}
	}()
}

func (rc *remoteCluster) onRemove() {
	rc.controllers.RemoveAllAndWait()
	close(rc.changed)

	rc.getLogger().Info("Remote cluster disconnected")
}

func (rc *remoteCluster) isReady() bool {
	rc.mutex.RLock()
	defer rc.mutex.RUnlock()

	return rc.backend != nil && rc.remoteNodes != nil && rc.ipCacheWatcher != nil
}
<|endoftext|>


<commit_before>//
// Last.Backend LLC CONFIDENTIAL
// __________________
//
// [2014] - [2018] Last.Backend LLC
// All Rights Reserved.
//
// NOTICE:  All information contained herein is, and remains
// the property of Last.Backend LLC and its suppliers,
// if any.  The intellectual and technical concepts contained
// herein are proprietary to Last.Backend LLC
// and its suppliers and may be covered by Russian Federation and Foreign Patents,
// patents in process, and are protected by trade secret or copyright law.
// Dissemination of this information or reproduction of this material
// is strictly forbidden unless prior written permission is obtained
// from Last.Backend LLC.
//

package errors

import (
	"errors"
	"net/http"
	"strings"
)

const (
	StatusBadParameter  = "Bad Parameter"
	StatusBadRequest    = "Bad Request"
	StatusUnknown       = "Unknown"
	StatusIncorrectXml  = "Incorrect Xml"
	StatusIncorrectJson = "Incorrect Json"
	StatusNotUnique     = "Not Unique"
	StatusForbidden     = "Forbidden"
	StatusNotAllowed    = "Not Allowed"
	ArgumentIsEmpty     = "ArgumentIsEmpty"
)

type Err struct {
	Code   string
	Attr   string
	origin error
	http   *Http
}

func BadParameter(attr string, e ...error) *Err {
	return &Err{
		Code:   StatusBadParameter,
		Attr:   attr,
		origin: getError(attr+": bad parameter", e...),
		http:   HTTP.getBadParameter(attr),
	}
}

func IncorrectJSON(e ...error) *Err {
	return &Err{
		Code:   StatusIncorrectJson,
		origin: getError("incorrect json", e...),
		http:   HTTP.getIncorrectJSON(),
	}
}

func IncorrectXML(e ...error) *Err {
	return &Err{
		Code:   StatusIncorrectXml,
		origin: getError("incorrect xml", e...),
		http:   HTTP.getIncorrectJSON(),
	}
}

func Forbidden(e ...error) *Err {
	return &Err{
		Code:   StatusForbidden,
		origin: getError("forbidden", e...),
		http:   HTTP.getForbidden(),
	}
}

func NotAllowed(e ...error) *Err {
	return &Err{
		Code:   StatusNotAllowed,
		origin: getError("not allowed", e...),
		http:   HTTP.getNotAllowed(),
	}
}

func Unknown(e ...error) *Err {
	return &Err{
		Code:   StatusUnknown,
		origin: getError("unknown error", e...),
		http:   HTTP.getUnknown(),
	}
}

func (e *Err) Err() error {
	return e.origin
}

func (e *Err) Http(w http.ResponseWriter) {
	e.http.send(w)
}

func (e *Err) Message(s string) {
	e.http.Message = s
}

type err struct {
	s string
}

func New(text string) *err {
	return &err{text}
}

func (e *err) Error() string {
	return e.s
}

func (e *err) Unauthorized(err ...error) *Err {
	return &Err{
		Code:   http.StatusText(http.StatusUnauthorized),
		origin: getError(joinNameAndMessage(e.s, "access denied"), err...),
		http:   HTTP.getUnauthorized(),
	}
}

func (e *err) NotFound(err ...error) *Err {
	return &Err{
		Code:   http.StatusText(http.StatusNotFound),
		origin: getError(joinNameAndMessage(e.s, "not found"), err...),
		http:   HTTP.getNotFound(e.s),
	}
}

func (e *err) NotUnique(attr string, err ...error) *Err {
	return &Err{
		Code:   StatusNotUnique,
		origin: getError(joinNameAndMessage(e.s, strings.ToLower(attr)+" not unique"), err...),
		http:   HTTP.getNotUnique(strings.ToLower(attr)),
	}
}

func (e *err) BadParameter(attr string, err ...error) *Err {
	return &Err{
		Code:   StatusBadParameter,
		Attr:   attr,
		origin: getError(joinNameAndMessage(e.s, "bad parameter "+strings.ToLower(attr)), err...),
		http:   HTTP.getBadParameter(attr),
	}
}

func (e *err) BadRequest(msg string, err ...error) *Err {
	return &Err{
		Code:   StatusBadParameter,
		origin: getError(joinNameAndMessage(e.s, msg), err...),
		http:   HTTP.getBadRequest(msg),
	}
}

func (e *err) IncorrectJSON(err ...error) *Err {
	return &Err{
		Code:   StatusIncorrectJson,
		origin: getError(joinNameAndMessage(e.s, "incorrect json"), err...),
		http:   HTTP.getIncorrectJSON(),
	}
}

func (e *err) IncorrectXML(err ...error) *Err {
	return &Err{
		Code:   StatusIncorrectJson,
		origin: getError(joinNameAndMessage(e.s, "incorrect xml"), err...),
		http:   HTTP.getIncorrectXML(),
	}
}

func (e *err) Forbidden(err ...error) *Err {
	return &Err{
		Code:   StatusForbidden,
		origin: getError(joinNameAndMessage(e.s, "forbidden"), err...),
		http:   HTTP.getForbidden(),
	}
}

func (e *err) NotAllowed(err ...error) *Err {
	return &Err{
		Code:   StatusNotAllowed,
		origin: getError(joinNameAndMessage(e.s, "not allowed"), err...),
		http:   HTTP.getNotAllowed(),
	}
}

func (e *err) Unknown(err ...error) *Err {
	return &Err{
		Code:   StatusUnknown,
		origin: getError(joinNameAndMessage(e.s, "unknown error"), err...),
		http:   HTTP.getUnknown(),
	}
}

func getError(msg string, err ...error) error {
	if len(err) == 0 {
		return errors.New(msg)
	} else {
		return err[0]
	}
}

func joinNameAndMessage(name, message string) string {
	return toUpperFirstChar(name) + ": " + message
}

func toUpperFirstChar(srt string) string {
	return strings.ToUpper(srt[0:1]) + srt[1:]
}
<commit_msg>update erros package<commit_after>//
// Last.Backend LLC CONFIDENTIAL
// __________________
//
// [2014] - [2018] Last.Backend LLC
// All Rights Reserved.
//
// NOTICE:  All information contained herein is, and remains
// the property of Last.Backend LLC and its suppliers,
// if any.  The intellectual and technical concepts contained
// herein are proprietary to Last.Backend LLC
// and its suppliers and may be covered by Russian Federation and Foreign Patents,
// patents in process, and are protected by trade secret or copyright law.
// Dissemination of this information or reproduction of this material
// is strictly forbidden unless prior written permission is obtained
// from Last.Backend LLC.
//

package errors

import (
	"errors"
	"net/http"
	"strings"
)

const (
	StatusBadParameter  = "Bad Parameter"
	StatusBadRequest    = "Bad Request"
	StatusUnknown       = "Unknown"
	StatusIncorrectXml  = "Incorrect Xml"
	StatusIncorrectJson = "Incorrect Json"
	StatusNotUnique     = "Not Unique"
	StatusForbidden     = "Forbidden"
	StatusNotAllowed    = "Not Allowed"
	ArgumentIsEmpty     = "ArgumentIsEmpty"
)

type Err struct {
	Code   string
	Attr   string
	origin error
	http   *Http
}

func BadParameter(attr string, e ...error) *Err {
	return &Err{
		Code:   StatusBadParameter,
		Attr:   attr,
		origin: getError(attr+": bad parameter", e...),
		http:   HTTP.getBadParameter(attr),
	}
}

func IncorrectJSON(e ...error) *Err {
	return &Err{
		Code:   StatusIncorrectJson,
		origin: getError("incorrect json", e...),
		http:   HTTP.getIncorrectJSON(),
	}
}

func IncorrectXML(e ...error) *Err {
	return &Err{
		Code:   StatusIncorrectXml,
		origin: getError("incorrect xml", e...),
		http:   HTTP.getIncorrectJSON(),
	}
}

func Forbidden(e ...error) *Err {
	return &Err{
		Code:   StatusForbidden,
		origin: getError("forbidden", e...),
		http:   HTTP.getForbidden(),
	}
}

func NotAllowed(e ...error) *Err {
	return &Err{
		Code:   StatusNotAllowed,
		origin: getError("not allowed", e...),
		http:   HTTP.getNotAllowed(),
	}
}

func Unknown(e ...error) *Err {
	return &Err{
		Code:   StatusUnknown,
		origin: getError("unknown error", e...),
		http:   HTTP.getUnknown(),
	}
}

func (e *Err) Err() error {
	return e.origin
}

func (e *Err) Http(w http.ResponseWriter) {
	e.http.send(w)
}

func (e *Err) SetMessage(s string) *Err {
	e.http.Message = s
	return e
}

type err struct {
	s string
}

func New(text string) *err {
	return &err{text}
}

func (e *err) Error() string {
	return e.s
}

func (e *err) Unauthorized(err ...error) *Err {
	return &Err{
		Code:   http.StatusText(http.StatusUnauthorized),
		origin: getError(joinNameAndMessage(e.s, "access denied"), err...),
		http:   HTTP.getUnauthorized(),
	}
}

func (e *err) NotFound(err ...error) *Err {
	return &Err{
		Code:   http.StatusText(http.StatusNotFound),
		origin: getError(joinNameAndMessage(e.s, "not found"), err...),
		http:   HTTP.getNotFound(e.s),
	}
}

func (e *err) NotUnique(attr string, err ...error) *Err {
	return &Err{
		Code:   StatusNotUnique,
		origin: getError(joinNameAndMessage(e.s, strings.ToLower(attr)+" not unique"), err...),
		http:   HTTP.getNotUnique(strings.ToLower(attr)),
	}
}

func (e *err) BadParameter(attr string, err ...error) *Err {
	return &Err{
		Code:   StatusBadParameter,
		Attr:   attr,
		origin: getError(joinNameAndMessage(e.s, "bad parameter "+strings.ToLower(attr)), err...),
		http:   HTTP.getBadParameter(attr),
	}
}

func (e *err) BadRequest(msg string, err ...error) *Err {
	return &Err{
		Code:   StatusBadParameter,
		origin: getError(joinNameAndMessage(e.s, msg), err...),
		http:   HTTP.getBadRequest(msg),
	}
}

func (e *err) IncorrectJSON(err ...error) *Err {
	return &Err{
		Code:   StatusIncorrectJson,
		origin: getError(joinNameAndMessage(e.s, "incorrect json"), err...),
		http:   HTTP.getIncorrectJSON(),
	}
}

func (e *err) IncorrectXML(err ...error) *Err {
	return &Err{
		Code:   StatusIncorrectJson,
		origin: getError(joinNameAndMessage(e.s, "incorrect xml"), err...),
		http:   HTTP.getIncorrectXML(),
	}
}

func (e *err) Forbidden(err ...error) *Err {
	return &Err{
		Code:   StatusForbidden,
		origin: getError(joinNameAndMessage(e.s, "forbidden"), err...),
		http:   HTTP.getForbidden(),
	}
}

func (e *err) NotAllowed(err ...error) *Err {
	return &Err{
		Code:   StatusNotAllowed,
		origin: getError(joinNameAndMessage(e.s, "not allowed"), err...),
		http:   HTTP.getNotAllowed(),
	}
}

func (e *err) Unknown(err ...error) *Err {
	return &Err{
		Code:   StatusUnknown,
		origin: getError(joinNameAndMessage(e.s, "unknown error"), err...),
		http:   HTTP.getUnknown(),
	}
}

func getError(msg string, err ...error) error {
	if len(err) == 0 {
		return errors.New(msg)
	} else {
		return err[0]
	}
}

func joinNameAndMessage(name, message string) string {
	return toUpperFirstChar(name) + ": " + message
}

func toUpperFirstChar(srt string) string {
	return strings.ToUpper(srt[0:1]) + srt[1:]
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 components

import (
	"fmt"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/loader"
)

// KubeAPIServerOptionsBuilder adds options for the apiserver to the model
type KubeAPIServerOptionsBuilder struct {
	Context *OptionsContext
}

var _ loader.OptionsBuilder = &KubeAPIServerOptionsBuilder{}

func (b *KubeAPIServerOptionsBuilder) BuildOptions(o interface{}) error {
	options := o.(*kops.ClusterSpec)
	if options.KubeAPIServer == nil {
		options.KubeAPIServer = &kops.KubeAPIServerConfig{}
	}

	if options.KubeAPIServer.APIServerCount == nil {
		count := b.buildAPIServerCount()
		if count == 0 {
			return fmt.Errorf("no instance groups found")
		}
		options.KubeAPIServer.APIServerCount = fi.Int32(int32(count))
	}

	if options.KubeAPIServer.StorageBackend == nil {
		// For the moment, we continue to use etcd2
		options.KubeAPIServer.StorageBackend = fi.String("etcd2")
	}

	return nil
}

func (b *KubeAPIServerOptionsBuilder) buildAPIServerCount() int {
	// The --apiserver-count flag is (generally agreed) to be something we need to get rid of in k8s

	// We should do something like this:

	//count := 0
	//for _, ig := range b.InstanceGroups {
	//	if !ig.IsMaster() {
	//		continue
	//	}
	//	size := fi.IntValue(ig.Spec.MaxSize)
	//	if size == 0 {
	//		size = fi.IntValue(ig.Spec.MinSize)
	//	}
	//	count += size
	//}

	// But if we do, we end up with a weird dependency on InstanceGroups.  We actually could tolerate
	// that in kops, but we don't really want to.

	// So instead, we assume that the etcd cluster size is the API Server Count.
	// We can re-examine this when we allow separate etcd clusters - at which time hopefully
	// the flag won't exist

	counts := make(map[string]int)
	for _, etcdCluster := range b.Context.Cluster.Spec.EtcdClusters {
		counts[etcdCluster.Name] = len(etcdCluster.Members)
	}

	count := counts["main"]

	return count
}
<commit_msg>Resolve nodes by IP before trying by name<commit_after>/*
Copyright 2016 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 components

import (
	"fmt"
	"github.com/blang/semver"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/loader"
	"k8s.io/kubernetes/pkg/api"
)

// KubeAPIServerOptionsBuilder adds options for the apiserver to the model
type KubeAPIServerOptionsBuilder struct {
	Context *OptionsContext
}

var _ loader.OptionsBuilder = &KubeAPIServerOptionsBuilder{}

func (b *KubeAPIServerOptionsBuilder) BuildOptions(o interface{}) error {
	options := o.(*kops.ClusterSpec)
	if options.KubeAPIServer == nil {
		options.KubeAPIServer = &kops.KubeAPIServerConfig{}
	}

	if options.KubeAPIServer.APIServerCount == nil {
		count := b.buildAPIServerCount()
		if count == 0 {
			return fmt.Errorf("no instance groups found")
		}
		options.KubeAPIServer.APIServerCount = fi.Int32(int32(count))
	}

	if options.KubeAPIServer.StorageBackend == nil {
		// For the moment, we continue to use etcd2
		options.KubeAPIServer.StorageBackend = fi.String("etcd2")
	}

	k8sVersion, err := b.Context.KubernetesVersion()
	if err != nil {
		return err
	}
	if options.KubeAPIServer.KubeletPreferredAddressTypes == nil {
		if k8sVersion.GTE(semver.MustParse("1.5.0")) {
			// Default precedence
			//options.KubeAPIServer.KubeletPreferredAddressTypes = []string {
			//	string(api.NodeHostName),
			//	string(api.NodeInternalIP),
			//	string(api.NodeExternalIP),
			//	string(api.NodeLegacyHostIP),
			//}

			// We prioritize the internal IP above the hostname
			options.KubeAPIServer.KubeletPreferredAddressTypes = []string{
				string(api.NodeInternalIP),
				string(api.NodeHostName),
				string(api.NodeExternalIP),
				string(api.NodeLegacyHostIP),
			}
		}
	}

	return nil
}

func (b *KubeAPIServerOptionsBuilder) buildAPIServerCount() int {
	// The --apiserver-count flag is (generally agreed) to be something we need to get rid of in k8s

	// We should do something like this:

	//count := 0
	//for _, ig := range b.InstanceGroups {
	//	if !ig.IsMaster() {
	//		continue
	//	}
	//	size := fi.IntValue(ig.Spec.MaxSize)
	//	if size == 0 {
	//		size = fi.IntValue(ig.Spec.MinSize)
	//	}
	//	count += size
	//}

	// But if we do, we end up with a weird dependency on InstanceGroups.  We actually could tolerate
	// that in kops, but we don't really want to.

	// So instead, we assume that the etcd cluster size is the API Server Count.
	// We can re-examine this when we allow separate etcd clusters - at which time hopefully
	// the flag won't exist

	counts := make(map[string]int)
	for _, etcdCluster := range b.Context.Cluster.Spec.EtcdClusters {
		counts[etcdCluster.Name] = len(etcdCluster.Members)
	}

	count := counts["main"]

	return count
}
<|endoftext|>