<commit_before>package main

import (
    "bytes"
    "strconv"
    "strings"
    "time"

    "labix.org/v2/mgo/bson"
)

type RedisMessage struct {
    Ts            time.Time
    NumberOfBulks int64
    Bulks         []string

    Stream_id    uint32
    Tuple        *IpPortTuple
    CmdlineTuple *CmdlineTuple
    Direction    uint8

    IsRequest bool
    Message   string
}

type RedisStream struct {
    tcpStream *TcpStream

    data []byte

    parseOffset   int
    parseState    int
    bytesReceived int

    message *RedisMessage
}

type RedisTransaction struct {
    Type         string
    tuple        TcpTuple
    Src          DbEndpoint
    Dst          DbEndpoint
    ResponseTime int32
    Ts           int64
    JsTs         time.Time
    ts           time.Time
    cmdline      *CmdlineTuple

    Redis bson.M

    Request_raw  string
    Response_raw string

    timer *time.Timer
}

var redisTransactionsMap = make(map[TcpTuple]*RedisTransaction, TransactionsHashSize)

func (stream *RedisStream) PrepareForNewMessage() {
    stream.message.NumberOfBulks = 0
    stream.message.Bulks = []string{}
    stream.message.IsRequest = false
}

func redisMessageParser(s *RedisStream) (bool, bool) {

    var err error
    var value string
    m := s.message

    for s.parseOffset < len(s.data) {

        if s.data[s.parseOffset] == '*' {
            //Multi Bulk Message

            line, off := readLine(s.data, s.parseOffset)

            if len(line) == 3 && line[1] == '-' && line[2] == '1' {
                //NULL Multi Bulk
                s.parseOffset = off
                value = "nil"
            } else {

                m.NumberOfBulks, err = strconv.ParseInt(line[1:], 10, 64)

                if err != nil {
                    ERR("Failed to read number of bulk messages: %s", err)
                    return false, false
                }
                s.parseOffset = off
                m.Bulks = []string{}

                continue
            }

        } else if s.data[s.parseOffset] == '$' {
            // Bulk Reply

            line, off := readLine(s.data, s.parseOffset)

            if len(line) == 3 && line[1] == '-' && line[2] == '1' {
                // NULL Bulk Reply
                value = "nil"
                s.parseOffset = off
            } else {
                length, err := strconv.ParseInt(line[1:], 10, 64)
                if err != nil {
                    ERR("Failed to read bulk message: %s", err)
                    return false, false
                }

                s.parseOffset = off

                line, off = readLine(s.data, s.parseOffset)

                if int64(len(line)) != length {
                    ERR("Wrong length of data: %d instead of %d", len(line), length)
                    return false, false
                }
                value = line
                s.parseOffset = off
            }

        } else if s.data[s.parseOffset] == ':' {
            // Integer reply
            line, off := readLine(s.data, s.parseOffset)
            n, err := strconv.ParseInt(line[1:], 10, 64)

            if err != nil {
                ERR("Failed to read integer reply: %s", err)
                return false, false
            }
            value = string(n)
            s.parseOffset = off

        } else if s.data[s.parseOffset] == '+' {
            // Status Reply
            line, off := readLine(s.data, s.parseOffset)

            value = line[1:]
            s.parseOffset = off
        } else if s.data[s.parseOffset] == '-' {
            // Error Reply
            line, off := readLine(s.data, s.parseOffset)

            value = line[1:]
            s.parseOffset = off
        } else {
            DEBUG("redis", "Unexpected message starting with %s", s.data[s.parseOffset:])
            return false, false
        }

        // add value
        if m.NumberOfBulks > 0 {
            m.NumberOfBulks = m.NumberOfBulks - 1
            m.Bulks = append(m.Bulks, value)

            if len(m.Bulks) == 1 {
                // check if it's a command
                if isRedisCommand(value) {
                    m.IsRequest = true
                }
            }

            if m.NumberOfBulks == 0 {
                // the last bulk received
                m.Message = strings.Join(m.Bulks, " ")
                return true, true
            }
        } else {
            m.Message = value
            return true, true
        }

    }   //end for

    return true, false
}

func readLine(data []byte, offset int) (string, int) {
    q := bytes.Index(data[offset:], []byte("\r\n"))
    return string(data[offset : offset+q]), offset + q + 2
}

func ParseRedis(pkt *Packet, tcp *TcpStream, dir uint8) {
    defer RECOVER("ParseRedis exception")

    if tcp.redisData[dir] == nil {
        tcp.redisData[dir] = &RedisStream{
            tcpStream: tcp,
            data:      pkt.payload,
            message:   &RedisMessage{Ts: pkt.ts},
        }
    } else {
        // concatenate bytes
        tcp.redisData[dir].data = append(tcp.redisData[dir].data, pkt.payload...)
    }

    stream := tcp.redisData[dir]
    if stream.message == nil {
        stream.message = &RedisMessage{Ts: pkt.ts}
    }

    ok, complete := redisMessageParser(tcp.redisData[dir])
    if !ok {
        // drop this tcp stream. Will retry parsing with the next
        // segment in it
        tcp.redisData[dir] = nil
        return
    }

    if !ok {
        // drop this tcp stream. Will retry parsing with the next
        // segment in it
        tcp.redisData[dir] = nil
        return
    }

    if complete {

        if stream.message.IsRequest {
            DEBUG("redis", "REDIS request message: %s", stream.message.Message)
        } else {
            DEBUG("redis", "REDIS response message: %s", stream.message.Message)
        }

        // all ok, go to next level
        handleRedis(stream.message, tcp, dir)

        // and reset message
        stream.PrepareForNewMessage()
    }

}

func isRedisCommand(key string) bool {

    RedisCommands := map[string]int{
        "APPEND":           1,
        "AUTH":             1,
        "BGREWRITEAOF":     1,
        "BGSAVE":           1,
        "BITCOUNT":         1,
        "BITOP":            1,
        "BLPOP":            1,
        "BRPOP":            1,
        "BRPOPLPUSH":       1,
        "CLIENT KILL":      1,
        "CLIENT LIST":      1,
        "CLIENT GETNAME":   1,
        "CLIENT SETNAME":   1,
        "CONFIG GET":       1,
        "CONFIG REWRITE":   1,
        "CONFIG SET":       1,
        "CONFIG REsETSTAT": 1,
        "DBSIZE":           1,
        "DEBUG OBJECT":     1,
        "DEBUG SEGFAULT":   1,
        "DECR":             1,
        "DECRBY":           1,
        "DEL":              1,
        "DISCARD":          1,
        "DUMP":             1,
        "ECHO":             1,
        "EVAL":             1,
        "EVALSHA":          1,
        "EXEC":             1,
        "EXISTS":           1,
        "EXPIRE":           1,
        "EXPIREAT":         1,
        "FLUSHALL":         1,
        "GET":              1,
        "GETBIT":           1,
        "GETRANGE":         1,
        "GETSET":           1,
        "HDEL":             1,
        "HEXISTS":          1,
        "HGET":             1,
        "HGETALL":          1,
        "HINCRBY":          1,
        "HINCRBYFLOAT":     1,
        "HKEYS":            1,
        "HLEN":             1,
        "HMGET":            1,
        "HMSET":            1,
        "HSET":             1,
        "HSETINX":          1,
        "HVALS":            1,
        "INCR":             1,
        "INCRBY":           1,
        "INCRBYFLOAT":      1,
        "INFO":             1,
        "KEYS":             1,
        "LASTSAVE":         1,
        "LINDEX":           1,
        "LINSERT":          1,
        "LLEN":             1,
        "LPOP":             1,
        "LPUSH":            1,
        "LPUSHX":           1,
        "LRANGE":           1,
        "LREM":             1,
        "LSET":             1,
        "LTRIM":            1,
        "MGET":             1,
        "MIGRATE":          1,
        "MONITOR":          1,
        "MOVE":             1,
        "MSET":             1,
        "MSETNX":           1,
        "MULTI":            1,
        "OBJECT":           1,
        "PERSIST":          1,
        "PEXPIRE":          1,
        "PEXPIREAT":        1,
        "PING":             1,
        "PSETEX":           1,
        "PSUBSCRIBE":       1,
        "PUBSUB":           1,
        "PTTL":             1,
        "PUBLISH":          1,
        "PUNSUBSCRIBE":     1,
        "QUIT":             1,
        "RANDOMKEY":        1,
        "RENAME":           1,
        "RENAMENX":         1,
        "RESTORE":          1,
        "RPOP":             1,
        "RPOPLPUSH":        1,
        "RPUSH":            1,
        "RPUSHX":           1,
        "SADD":             1,
        "SAVE":             1,
        "SCARD":            1,
        "SCRIPT EXISTS":    1,
        "SCRIPT FLUSH":     1,
        "SCRIPT KILL":      1,
        "SCRIPT LOAD":      1,
        "SDIFF":            1,
        "SDIFFSTORE":       1,
        "SELECT":           1,
        "SET":              1,
        "SETBIT":           1,
        "SETEX":            1,
        "SETNX":            1,
        "SETRANGE":         1,
        "SHUTDOWN":         1,
        "SINTER":           1,
        "SINTERSTORE":      1,
        "SISMEMBER":        1,
        "SLAVEOF":          1,
        "SLOWLOG":          1,
        "SMEMBERS":         1,
        "SMOVE":            1,
        "SORT":             1,
        "SPOP":             1,
        "SRANDMEMBER":      1,
        "SREM":             1,
        "STRLEN":           1,
        "SUBSCRIBE":        1,
        "SUNION":           1,
        "SUNIONSTORE":      1,
        "SYNC":             1,
        "TIME":             1,
        "TTL":              1,
        "TYPE":             1,
        "UNSUBSCRIBE":      1,
        "UNWATCH":          1,
        "WATCH":            1,
        "ZADD":             1,
        "ZCARD":            1,
        "ZCOUNT":           1,
        "ZINCRBY":          1,
        "ZINTERSTORE":      1,
        "ZRANGE":           1,
        "ZRANGEBYSCORE":    1,
        "ZRANK":            1,
        "ZREM":             1,
        "ZREMRANGEBYRANK":  1,
        "ZREMRANGEBYSCORE": 1,
        "ZREVRANGE":        1,
        "ZREVRANGEBYSCORE": 1,
        "ZREVRANK":         1,
        "ZSCORE":           1,
        "ZUNIONSTORE":      1,
        "SCAN":             1,
        "SSCAN":            1,
        "HSCAN":            1,
        "ZSCAN":            1,
    }

    if RedisCommands[key] > 0 {
        return true
    }
    return false
}

func handleRedis(m *RedisMessage, tcp *TcpStream,
    dir uint8) {

    m.Stream_id = tcp.id
    m.Tuple = tcp.tuple
    m.Direction = dir
    m.CmdlineTuple = procWatcher.FindProcessesTuple(tcp.tuple)

    if m.IsRequest {
        receivedRedisRequest(m)
    } else {
        receivedRedisResponse(m)
    }
}

func receivedRedisRequest(msg *RedisMessage) {
    // Add it to the HT
    tuple := TcpTuple{
        Src_ip:    msg.Tuple.Src_ip,
        Dst_ip:    msg.Tuple.Dst_ip,
        Src_port:  msg.Tuple.Src_port,
        Dst_port:  msg.Tuple.Dst_port,
        stream_id: msg.Stream_id,
    }

    trans := redisTransactionsMap[tuple]
    if trans != nil {
        if len(trans.Redis) != 0 {
            WARN("Two requests without a Response. Dropping old request")
        }
    } else {
        trans = &RedisTransaction{Type: "redis", tuple: tuple}
        redisTransactionsMap[tuple] = trans
    }

    var redis bson.M

    DEBUG("redis", "Receive request: %s", redis)

    trans.Redis = bson.M{
        "request": msg.Message,
    }
    trans.Request_raw = msg.Message

    trans.cmdline = msg.CmdlineTuple
    trans.ts = msg.Ts
    trans.Ts = int64(trans.ts.UnixNano() / 1000) // transactions have microseconds resolution
    trans.JsTs = msg.Ts
    trans.Src = DbEndpoint{
        Ip:   Ipv4_Ntoa(tuple.Src_ip),
        Port: tuple.Src_port,
        Proc: string(msg.CmdlineTuple.Src),
    }
    trans.Dst = DbEndpoint{
        Ip:   Ipv4_Ntoa(tuple.Dst_ip),
        Port: tuple.Dst_port,
        Proc: string(msg.CmdlineTuple.Dst),
    }

    if trans.timer != nil {
        trans.timer.Stop()
    }
    trans.timer = time.AfterFunc(TransactionTimeout, func() { trans.Expire() })

}

func (trans *RedisTransaction) Expire() {

    // remove from map
    delete(redisTransactionsMap, trans.tuple)
}

func receivedRedisResponse(msg *RedisMessage) {

    tuple := TcpTuple{
        Src_ip:    msg.Tuple.Src_ip,
        Dst_ip:    msg.Tuple.Dst_ip,
        Src_port:  msg.Tuple.Src_port,
        Dst_port:  msg.Tuple.Dst_port,
        stream_id: msg.Stream_id,
    }
    trans := redisTransactionsMap[tuple]
    if trans == nil {
        WARN("Response from unknown transaction. Ignoring.")
        return
    }
    // check if the request was received
    if len(trans.Redis) == 0 {
        WARN("Response from unknown transaction. Ignoring.")
        return

    }

    var redis bson.M

    DEBUG("redis", "Receive response: %s", redis)

    trans.Redis["response"] = msg.Message

    trans.Response_raw = msg.Message

    trans.ResponseTime = int32(msg.Ts.Sub(trans.ts).Nanoseconds() / 1e6) // resp_time in milliseconds

    err := Publisher.PublishRedisTransaction(trans)
    if err != nil {
        WARN("Publish failure: %s", err)
    }

    DEBUG("redis", "Redis transaction completed: %s", trans.Redis)

    // remove from map
    delete(redisTransactionsMap, trans.tuple)
    if trans.timer != nil {
        trans.timer.Stop()
    }

}
<commit_msg>FIX Typo in RedisCommands map.<commit_after>package main

import (
    "bytes"
    "strconv"
    "strings"
    "time"

    "labix.org/v2/mgo/bson"
)

type RedisMessage struct {
    Ts            time.Time
    NumberOfBulks int64
    Bulks         []string

    Stream_id    uint32
    Tuple        *IpPortTuple
    CmdlineTuple *CmdlineTuple
    Direction    uint8

    IsRequest bool
    Message   string
}

type RedisStream struct {
    tcpStream *TcpStream

    data []byte

    parseOffset   int
    parseState    int
    bytesReceived int

    message *RedisMessage
}

type RedisTransaction struct {
    Type         string
    tuple        TcpTuple
    Src          DbEndpoint
    Dst          DbEndpoint
    ResponseTime int32
    Ts           int64
    JsTs         time.Time
    ts           time.Time
    cmdline      *CmdlineTuple

    Redis bson.M

    Request_raw  string
    Response_raw string

    timer *time.Timer
}

var redisTransactionsMap = make(map[TcpTuple]*RedisTransaction, TransactionsHashSize)

func (stream *RedisStream) PrepareForNewMessage() {
    stream.message.NumberOfBulks = 0
    stream.message.Bulks = []string{}
    stream.message.IsRequest = false
}

func redisMessageParser(s *RedisStream) (bool, bool) {

    var err error
    var value string
    m := s.message

    for s.parseOffset < len(s.data) {

        if s.data[s.parseOffset] == '*' {
            //Multi Bulk Message

            line, off := readLine(s.data, s.parseOffset)

            if len(line) == 3 && line[1] == '-' && line[2] == '1' {
                //NULL Multi Bulk
                s.parseOffset = off
                value = "nil"
            } else {

                m.NumberOfBulks, err = strconv.ParseInt(line[1:], 10, 64)

                if err != nil {
                    ERR("Failed to read number of bulk messages: %s", err)
                    return false, false
                }
                s.parseOffset = off
                m.Bulks = []string{}

                continue
            }

        } else if s.data[s.parseOffset] == '$' {
            // Bulk Reply

            line, off := readLine(s.data, s.parseOffset)

            if len(line) == 3 && line[1] == '-' && line[2] == '1' {
                // NULL Bulk Reply
                value = "nil"
                s.parseOffset = off
            } else {
                length, err := strconv.ParseInt(line[1:], 10, 64)
                if err != nil {
                    ERR("Failed to read bulk message: %s", err)
                    return false, false
                }

                s.parseOffset = off

                line, off = readLine(s.data, s.parseOffset)

                if int64(len(line)) != length {
                    ERR("Wrong length of data: %d instead of %d", len(line), length)
                    return false, false
                }
                value = line
                s.parseOffset = off
            }

        } else if s.data[s.parseOffset] == ':' {
            // Integer reply
            line, off := readLine(s.data, s.parseOffset)
            n, err := strconv.ParseInt(line[1:], 10, 64)

            if err != nil {
                ERR("Failed to read integer reply: %s", err)
                return false, false
            }
            value = string(n)
            s.parseOffset = off

        } else if s.data[s.parseOffset] == '+' {
            // Status Reply
            line, off := readLine(s.data, s.parseOffset)

            value = line[1:]
            s.parseOffset = off
        } else if s.data[s.parseOffset] == '-' {
            // Error Reply
            line, off := readLine(s.data, s.parseOffset)

            value = line[1:]
            s.parseOffset = off
        } else {
            DEBUG("redis", "Unexpected message starting with %s", s.data[s.parseOffset:])
            return false, false
        }

        // add value
        if m.NumberOfBulks > 0 {
            m.NumberOfBulks = m.NumberOfBulks - 1
            m.Bulks = append(m.Bulks, value)

            if len(m.Bulks) == 1 {
                // check if it's a command
                if isRedisCommand(value) {
                    m.IsRequest = true
                }
            }

            if m.NumberOfBulks == 0 {
                // the last bulk received
                m.Message = strings.Join(m.Bulks, " ")
                return true, true
            }
        } else {
            m.Message = value
            return true, true
        }

    }   //end for

    return true, false
}

func readLine(data []byte, offset int) (string, int) {
    q := bytes.Index(data[offset:], []byte("\r\n"))
    return string(data[offset : offset+q]), offset + q + 2
}

func ParseRedis(pkt *Packet, tcp *TcpStream, dir uint8) {
    defer RECOVER("ParseRedis exception")

    if tcp.redisData[dir] == nil {
        tcp.redisData[dir] = &RedisStream{
            tcpStream: tcp,
            data:      pkt.payload,
            message:   &RedisMessage{Ts: pkt.ts},
        }
    } else {
        // concatenate bytes
        tcp.redisData[dir].data = append(tcp.redisData[dir].data, pkt.payload...)
    }

    stream := tcp.redisData[dir]
    if stream.message == nil {
        stream.message = &RedisMessage{Ts: pkt.ts}
    }

    ok, complete := redisMessageParser(tcp.redisData[dir])
    if !ok {
        // drop this tcp stream. Will retry parsing with the next
        // segment in it
        tcp.redisData[dir] = nil
        return
    }

    if !ok {
        // drop this tcp stream. Will retry parsing with the next
        // segment in it
        tcp.redisData[dir] = nil
        return
    }

    if complete {

        if stream.message.IsRequest {
            DEBUG("redis", "REDIS request message: %s", stream.message.Message)
        } else {
            DEBUG("redis", "REDIS response message: %s", stream.message.Message)
        }

        // all ok, go to next level
        handleRedis(stream.message, tcp, dir)

        // and reset message
        stream.PrepareForNewMessage()
    }

}

func isRedisCommand(key string) bool {

    RedisCommands := map[string]int{
        "APPEND":           1,
        "AUTH":             1,
        "BGREWRITEAOF":     1,
        "BGSAVE":           1,
        "BITCOUNT":         1,
        "BITOP":            1,
        "BLPOP":            1,
        "BRPOP":            1,
        "BRPOPLPUSH":       1,
        "CLIENT KILL":      1,
        "CLIENT LIST":      1,
        "CLIENT GETNAME":   1,
        "CLIENT SETNAME":   1,
        "CONFIG GET":       1,
        "CONFIG REWRITE":   1,
        "CONFIG SET":       1,
        "CONFIG RESETSTAT": 1,
        "DBSIZE":           1,
        "DEBUG OBJECT":     1,
        "DEBUG SEGFAULT":   1,
        "DECR":             1,
        "DECRBY":           1,
        "DEL":              1,
        "DISCARD":          1,
        "DUMP":             1,
        "ECHO":             1,
        "EVAL":             1,
        "EVALSHA":          1,
        "EXEC":             1,
        "EXISTS":           1,
        "EXPIRE":           1,
        "EXPIREAT":         1,
        "FLUSHALL":         1,
        "GET":              1,
        "GETBIT":           1,
        "GETRANGE":         1,
        "GETSET":           1,
        "HDEL":             1,
        "HEXISTS":          1,
        "HGET":             1,
        "HGETALL":          1,
        "HINCRBY":          1,
        "HINCRBYFLOAT":     1,
        "HKEYS":            1,
        "HLEN":             1,
        "HMGET":            1,
        "HMSET":            1,
        "HSET":             1,
        "HSETINX":          1,
        "HVALS":            1,
        "INCR":             1,
        "INCRBY":           1,
        "INCRBYFLOAT":      1,
        "INFO":             1,
        "KEYS":             1,
        "LASTSAVE":         1,
        "LINDEX":           1,
        "LINSERT":          1,
        "LLEN":             1,
        "LPOP":             1,
        "LPUSH":            1,
        "LPUSHX":           1,
        "LRANGE":           1,
        "LREM":             1,
        "LSET":             1,
        "LTRIM":            1,
        "MGET":             1,
        "MIGRATE":          1,
        "MONITOR":          1,
        "MOVE":             1,
        "MSET":             1,
        "MSETNX":           1,
        "MULTI":            1,
        "OBJECT":           1,
        "PERSIST":          1,
        "PEXPIRE":          1,
        "PEXPIREAT":        1,
        "PING":             1,
        "PSETEX":           1,
        "PSUBSCRIBE":       1,
        "PUBSUB":           1,
        "PTTL":             1,
        "PUBLISH":          1,
        "PUNSUBSCRIBE":     1,
        "QUIT":             1,
        "RANDOMKEY":        1,
        "RENAME":           1,
        "RENAMENX":         1,
        "RESTORE":          1,
        "RPOP":             1,
        "RPOPLPUSH":        1,
        "RPUSH":            1,
        "RPUSHX":           1,
        "SADD":             1,
        "SAVE":             1,
        "SCARD":            1,
        "SCRIPT EXISTS":    1,
        "SCRIPT FLUSH":     1,
        "SCRIPT KILL":      1,
        "SCRIPT LOAD":      1,
        "SDIFF":            1,
        "SDIFFSTORE":       1,
        "SELECT":           1,
        "SET":              1,
        "SETBIT":           1,
        "SETEX":            1,
        "SETNX":            1,
        "SETRANGE":         1,
        "SHUTDOWN":         1,
        "SINTER":           1,
        "SINTERSTORE":      1,
        "SISMEMBER":        1,
        "SLAVEOF":          1,
        "SLOWLOG":          1,
        "SMEMBERS":         1,
        "SMOVE":            1,
        "SORT":             1,
        "SPOP":             1,
        "SRANDMEMBER":      1,
        "SREM":             1,
        "STRLEN":           1,
        "SUBSCRIBE":        1,
        "SUNION":           1,
        "SUNIONSTORE":      1,
        "SYNC":             1,
        "TIME":             1,
        "TTL":              1,
        "TYPE":             1,
        "UNSUBSCRIBE":      1,
        "UNWATCH":          1,
        "WATCH":            1,
        "ZADD":             1,
        "ZCARD":            1,
        "ZCOUNT":           1,
        "ZINCRBY":          1,
        "ZINTERSTORE":      1,
        "ZRANGE":           1,
        "ZRANGEBYSCORE":    1,
        "ZRANK":            1,
        "ZREM":             1,
        "ZREMRANGEBYRANK":  1,
        "ZREMRANGEBYSCORE": 1,
        "ZREVRANGE":        1,
        "ZREVRANGEBYSCORE": 1,
        "ZREVRANK":         1,
        "ZSCORE":           1,
        "ZUNIONSTORE":      1,
        "SCAN":             1,
        "SSCAN":            1,
        "HSCAN":            1,
        "ZSCAN":            1,
    }

    if RedisCommands[key] > 0 {
        return true
    }
    return false
}

func handleRedis(m *RedisMessage, tcp *TcpStream,
    dir uint8) {

    m.Stream_id = tcp.id
    m.Tuple = tcp.tuple
    m.Direction = dir
    m.CmdlineTuple = procWatcher.FindProcessesTuple(tcp.tuple)

    if m.IsRequest {
        receivedRedisRequest(m)
    } else {
        receivedRedisResponse(m)
    }
}

func receivedRedisRequest(msg *RedisMessage) {
    // Add it to the HT
    tuple := TcpTuple{
        Src_ip:    msg.Tuple.Src_ip,
        Dst_ip:    msg.Tuple.Dst_ip,
        Src_port:  msg.Tuple.Src_port,
        Dst_port:  msg.Tuple.Dst_port,
        stream_id: msg.Stream_id,
    }

    trans := redisTransactionsMap[tuple]
    if trans != nil {
        if len(trans.Redis) != 0 {
            WARN("Two requests without a Response. Dropping old request")
        }
    } else {
        trans = &RedisTransaction{Type: "redis", tuple: tuple}
        redisTransactionsMap[tuple] = trans
    }

    var redis bson.M

    DEBUG("redis", "Receive request: %s", redis)

    trans.Redis = bson.M{
        "request": msg.Message,
    }
    trans.Request_raw = msg.Message

    trans.cmdline = msg.CmdlineTuple
    trans.ts = msg.Ts
    trans.Ts = int64(trans.ts.UnixNano() / 1000) // transactions have microseconds resolution
    trans.JsTs = msg.Ts
    trans.Src = DbEndpoint{
        Ip:   Ipv4_Ntoa(tuple.Src_ip),
        Port: tuple.Src_port,
        Proc: string(msg.CmdlineTuple.Src),
    }
    trans.Dst = DbEndpoint{
        Ip:   Ipv4_Ntoa(tuple.Dst_ip),
        Port: tuple.Dst_port,
        Proc: string(msg.CmdlineTuple.Dst),
    }

    if trans.timer != nil {
        trans.timer.Stop()
    }
    trans.timer = time.AfterFunc(TransactionTimeout, func() { trans.Expire() })

}

func (trans *RedisTransaction) Expire() {

    // remove from map
    delete(redisTransactionsMap, trans.tuple)
}

func receivedRedisResponse(msg *RedisMessage) {

    tuple := TcpTuple{
        Src_ip:    msg.Tuple.Src_ip,
        Dst_ip:    msg.Tuple.Dst_ip,
        Src_port:  msg.Tuple.Src_port,
        Dst_port:  msg.Tuple.Dst_port,
        stream_id: msg.Stream_id,
    }
    trans := redisTransactionsMap[tuple]
    if trans == nil {
        WARN("Response from unknown transaction. Ignoring.")
        return
    }
    // check if the request was received
    if len(trans.Redis) == 0 {
        WARN("Response from unknown transaction. Ignoring.")
        return

    }

    var redis bson.M

    DEBUG("redis", "Receive response: %s", redis)

    trans.Redis["response"] = msg.Message

    trans.Response_raw = msg.Message

    trans.ResponseTime = int32(msg.Ts.Sub(trans.ts).Nanoseconds() / 1e6) // resp_time in milliseconds

    err := Publisher.PublishRedisTransaction(trans)
    if err != nil {
        WARN("Publish failure: %s", err)
    }

    DEBUG("redis", "Redis transaction completed: %s", trans.Redis)

    // remove from map
    delete(redisTransactionsMap, trans.tuple)
    if trans.timer != nil {
        trans.timer.Stop()
    }

}
<|endoftext|>


<commit_before>package shredis

import (
	"strconv"
	"strings"
	"time"
)

// BuildGet is shorthand for Build(key, "GET", key)
func BuildGet(key string) *Cmd {
	return Build(key, "GET", key)
}

// BuildSet is shorthand for Build(key, "SET", key, value)
func BuildSet(key, value string) *Cmd {
	return Build(key, "SET", key, value)
}

// BuildSetEx builds a SET with EX command
func BuildSetEx(key, value string, ttl time.Duration) *Cmd {
	return Build(key, "SET", key, value, "EX", strconv.Itoa(int(ttl.Seconds())))
}

// BuildSetNx builds a SETNX command
func BuildSetNx(key, value string) *Cmd {
	return Build(key, "SETNX", key, value)
}

// BuildSetNX is an alias for BuildSetNx
func BuildSetNX(key, value string) *Cmd {
	return BuildSetNx(key, value)
}

// BuildSetNxEx builds a SET with EX command
func BuildSetNxEx(key, value string, ttl time.Duration) *Cmd {
	return Build(key, "SET", key, value, "NX", "EX", strconv.Itoa(int(ttl.Seconds())))
}

// BuildExpire builds an EXPIRE
func BuildExpire(key string, ttl time.Duration) *Cmd {
	return Build(key, "EXPIRE", key, strconv.Itoa(int(ttl.Seconds())))
}

// BuildDel is shorthand for Build(key, "DEL", key)
func BuildDel(key string) *Cmd {
	return Build(key, "DEL", key)
}

// BuildHget is shorthand for Build(key, "HGET", key, field)
func BuildHget(key, field string) *Cmd {
	return Build(key, "HGET", key, field)
}

// BuildHset is shorthand for Build(key, "HSET", key, field, value)
func BuildHset(key, field, value string) *Cmd {
	return Build(key, "HSET", key, field, value)
}

// RedisInfoStat is returned by ExecInfoStat. It's the [Stats] part of the
// 'INFO' command.
type RedisInfoStat struct {
	TotalConnectionsReceived,
	TotalCommandsProcessed,
	InstantaneousOpsPerSec,
	TotalNetInputBytes,
	TotalNetOutputBytes,
	RejectedConnections,
	SyncFull,
	SyncPartialOk,
	SyncPartialErr,
	ExpiredKeys,
	EvictedKeys,
	KeyspaceHits,
	KeyspaceMisses,
	PubsubChannels,
	PubsubPatterns int
	InstantaneousInputKbps,
	InstantaneousOutputKbps float64
	// LatestForkUsec int
}

// ExecInfoStats calls 'INFO STATS' on every configured server and returns the
// sum.  Or error, if any server gives an error.
func ExecInfoStats(s *Shred) (RedisInfoStat, error) {
	var sum RedisInfoStat
	cmds := s.MapExec("INFO", "STATS")
	for _, c := range cmds {
		v, err := c.GetString()
		if err != nil {
			return sum, err
		}

		// v is a string with lines, with 'key:intvalue\n' lines. And comments.
		for _, line := range strings.Split(v, "\r\n") {
			fields := strings.SplitN(line, ":", 2)
			if len(fields) != 2 {
				continue
			}
			v, err := strconv.ParseFloat(fields[1], 64)
			if err != nil {
				return sum, err
			}
			vi := int(v)
			switch fields[0] {
			case "total_connections_received":
				sum.TotalConnectionsReceived += vi
			case "total_commands_processed":
				sum.TotalCommandsProcessed += vi
			case "instantaneous_ops_per_sec":
				sum.InstantaneousOpsPerSec += vi
			case "total_net_input_bytes":
				sum.TotalNetInputBytes += vi
			case "total_net_output_bytes":
				sum.TotalNetOutputBytes += vi
			case "instantaneous_input_kbps":
				sum.InstantaneousInputKbps += v
			case "instantaneous_output_kbps":
				sum.InstantaneousOutputKbps += v
			case "rejected_connections":
				sum.RejectedConnections += vi
			case "sync_full":
				sum.SyncFull += vi
			case "sync_partial_ok":
				sum.SyncPartialOk += vi
			case "sync_partial_err":
				sum.SyncPartialErr += vi
			case "expired_keys":
				sum.ExpiredKeys += vi
			case "evicted_keys":
				sum.EvictedKeys += vi
			case "keyspace_hits":
				sum.KeyspaceHits += vi
			case "keyspace_misses":
				sum.KeyspaceMisses += vi
			case "pubsub_channels":
				sum.PubsubChannels += vi
			case "pubsub_patterns":
				sum.PubsubPatterns += vi
			}
			// sum.LatestForkUsec = m["latest_fork_usec"]
		}
	}
	return sum, nil
}
<commit_msg>RedisInfoMemory()<commit_after>package shredis

import (
	"strconv"
	"strings"
	"time"
)

// BuildGet is shorthand for Build(key, "GET", key)
func BuildGet(key string) *Cmd {
	return Build(key, "GET", key)
}

// BuildSet is shorthand for Build(key, "SET", key, value)
func BuildSet(key, value string) *Cmd {
	return Build(key, "SET", key, value)
}

// BuildSetEx builds a SET with EX command
func BuildSetEx(key, value string, ttl time.Duration) *Cmd {
	return Build(key, "SET", key, value, "EX", strconv.Itoa(int(ttl.Seconds())))
}

// BuildSetNx builds a SETNX command
func BuildSetNx(key, value string) *Cmd {
	return Build(key, "SETNX", key, value)
}

// BuildSetNX is an alias for BuildSetNx
func BuildSetNX(key, value string) *Cmd {
	return BuildSetNx(key, value)
}

// BuildSetNxEx builds a SET with EX command
func BuildSetNxEx(key, value string, ttl time.Duration) *Cmd {
	return Build(key, "SET", key, value, "NX", "EX", strconv.Itoa(int(ttl.Seconds())))
}

// BuildExpire builds an EXPIRE
func BuildExpire(key string, ttl time.Duration) *Cmd {
	return Build(key, "EXPIRE", key, strconv.Itoa(int(ttl.Seconds())))
}

// BuildDel is shorthand for Build(key, "DEL", key)
func BuildDel(key string) *Cmd {
	return Build(key, "DEL", key)
}

// BuildHget is shorthand for Build(key, "HGET", key, field)
func BuildHget(key, field string) *Cmd {
	return Build(key, "HGET", key, field)
}

// BuildHset is shorthand for Build(key, "HSET", key, field, value)
func BuildHset(key, field, value string) *Cmd {
	return Build(key, "HSET", key, field, value)
}

// RedisInfoStat represents the [Stats] part of the 'INFO' command.
type RedisInfoStat struct {
	TotalConnectionsReceived,
	TotalCommandsProcessed,
	InstantaneousOpsPerSec,
	TotalNetInputBytes,
	TotalNetOutputBytes,
	RejectedConnections,
	SyncFull,
	SyncPartialOk,
	SyncPartialErr,
	ExpiredKeys,
	EvictedKeys,
	KeyspaceHits,
	KeyspaceMisses,
	PubsubChannels,
	PubsubPatterns int
	InstantaneousInputKbps,
	InstantaneousOutputKbps float64
	// LatestForkUsec int
}

// ExecInfoStats calls 'INFO STATS' on every configured server and returns the
// sum.  Or error, if any server gives an error.
func ExecInfoStats(s *Shred) (RedisInfoStat, error) {
	var sum RedisInfoStat
	cmds := s.MapExec("INFO", "STATS")
	for _, c := range cmds {
		s, err := c.GetString()
		if err != nil {
			return sum, err
		}

		for k, v := range parseInfo(s) {
			f, err := strconv.ParseFloat(v, 64)
			if err != nil {
				return sum, err
			}
			fi := int(f)
			switch k {
			case "total_connections_received":
				sum.TotalConnectionsReceived += fi
			case "total_commands_processed":
				sum.TotalCommandsProcessed += fi
			case "instantaneous_ops_per_sec":
				sum.InstantaneousOpsPerSec += fi
			case "total_net_input_bytes":
				sum.TotalNetInputBytes += fi
			case "total_net_output_bytes":
				sum.TotalNetOutputBytes += fi
			case "instantaneous_input_kbps":
				sum.InstantaneousInputKbps += f
			case "instantaneous_output_kbps":
				sum.InstantaneousOutputKbps += f
			case "rejected_connections":
				sum.RejectedConnections += fi
			case "sync_full":
				sum.SyncFull += fi
			case "sync_partial_ok":
				sum.SyncPartialOk += fi
			case "sync_partial_err":
				sum.SyncPartialErr += fi
			case "expired_keys":
				sum.ExpiredKeys += fi
			case "evicted_keys":
				sum.EvictedKeys += fi
			case "keyspace_hits":
				sum.KeyspaceHits += fi
			case "keyspace_misses":
				sum.KeyspaceMisses += fi
			case "pubsub_channels":
				sum.PubsubChannels += fi
			case "pubsub_patterns":
				sum.PubsubPatterns += fi
			}
			// sum.LatestForkUsec = m["latest_fork_usec"]
		}
	}
	return sum, nil
}

// RedisInfoMemory represents the [Memory] part of the 'INFO' command.
type RedisInfoMemory struct {
	UsedMemory,
	UsedMemoryPeak,
	UsedMemoryLua int
}

// ExecInfoMemory calls 'INFO MEMORY' on every configured server and returns the
// sum.
func ExecInfoMemory(s *Shred) (RedisInfoMemory, error) {
	var sum RedisInfoMemory
	cmds := s.MapExec("INFO", "MEMORY")
	for _, c := range cmds {
		v, err := c.GetString()
		if err != nil {
			return sum, err
		}
		for k, v := range parseInfo(v) {
			switch k {
			case "used_memory":
				i, _ := strconv.Atoi(v)
				sum.UsedMemory += i
			case "used_memory_peak":
				i, _ := strconv.Atoi(v)
				sum.UsedMemoryPeak += i
			case "used_memory_lua":
				i, _ := strconv.Atoi(v)
				sum.UsedMemoryLua += i
			}
		}
	}
	return sum, nil
}

// parse strings returned from INFO commands.
func parseInfo(s string) map[string]string {
	r := map[string]string{}
	// s is a string with lines, with 'key:somevalue\n' lines. And comments.
	for _, line := range strings.Split(s, "\r\n") {
		fields := strings.SplitN(line, ":", 2)
		if len(fields) != 2 {
			continue
		}
		r[fields[0]] = fields[1]
	}
	return r
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Zachary Klippenstein

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

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

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

package main

import (
	"flag"
	"fmt"
	"github.com/zach-klippenstein/goregen"
	"math/rand"
	"os"
	"path"
	"time"
)

var NumberOfGenerations = flag.Int("n", 1, "number of strings to generate. A value of 0 will keep generating strings until the process is killed.")

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [args] pattern\n", path.Base(os.Args[0]))
		fmt.Fprintln(os.Stderr, "Generates random strings from regular expressions.")
		flag.PrintDefaults()
	}
	flag.Parse()

	if flag.NArg() != 1 {
		fmt.Fprintln(os.Stderr, "error: must specify a pattern")
		flag.Usage()
	}

	rand.Seed(time.Now().UTC().UnixNano())

	pattern := flag.Arg(0)

	generator, err := regen.NewGenerator(pattern, nil)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err)
		os.Exit(1)
	}

	for i := 0; *NumberOfGenerations == 0 || i < *NumberOfGenerations; i++ {
		fmt.Println(generator.Generate())
	}
}
<commit_msg>Pattern will be read from stdin if not passed on command line.<commit_after>/*
Copyright 2014 Zachary Klippenstein

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

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

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

package main

import (
	"flag"
	"fmt"
	"github.com/zach-klippenstein/goregen"
	"io/ioutil"
	"math/rand"
	"os"
	"path"
	"strings"
	"time"
)

var NumberOfGenerations = flag.Int("n", 1, "number of strings to generate. A value of 0 will keep generating strings until the process is killed.")

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [args] [pattern]\n", path.Base(os.Args[0]))
		fmt.Fprintln(os.Stderr, "Generates random strings from regular expressions.")
		fmt.Fprintln(os.Stderr, "If no pattern is given on the command line, it will be read from stdin. If the last character is \\n, it will be removed.")
		fmt.Fprintln(os.Stderr, "\nargs:")
		flag.PrintDefaults()
	}
	flag.Parse()

	var pattern string

	if flag.NArg() > 1 {
		fmt.Fprintln(os.Stderr, "error: too many arguments")
		flag.Usage()
	} else if flag.NArg() == 1 {
		pattern = flag.Arg(0)
	} else {
		pattern = ReadPatternFromStdin()
	}

	rand.Seed(time.Now().UTC().UnixNano())

	generator, err := regen.NewGenerator(pattern, nil)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err)
		os.Exit(1)
	}

	for i := 0; *NumberOfGenerations == 0 || i < *NumberOfGenerations; i++ {
		fmt.Println(generator.Generate())
	}
}

func ReadPatternFromStdin() string {
	patternBytes, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error reading from stdin: %s\n", err)
		os.Exit(1)
	}
	pattern := string(patternBytes)

	// if entered interactively, there will be an extra \n at the end that
	// is probably not intended to be part of the regex.
	pattern = strings.TrimSuffix(pattern, "\n")

	return pattern
}
<|endoftext|>


<commit_before>package ucfg

import "reflect"

func (c *Config) Unpack(to interface{}) error {
	if c == nil {
		return ErrNilConfig
	}
	if to == nil {
		return ErrNilValue
	}

	vTo := reflect.ValueOf(to)
	if to == nil || (vTo.Kind() != reflect.Ptr && vTo.Kind() != reflect.Map) {
		return ErrPointerRequired
	}
	return reifyInto(vTo, c.fields)
}

func reifyInto(to reflect.Value, from map[string]value) error {
	to = chaseValuePointers(to)

	if to.Type() == tConfig {
		return mergeConfig(to.Addr().Interface().(*Config).fields, from)
	}

	switch to.Kind() {
	case reflect.Map:
		return reifyMap(to, from)
	case reflect.Struct:
		return reifyStruct(to, from)
	}

	return ErrTypeMismatch
}

func reifyMap(to reflect.Value, from map[string]value) error {
	if to.Type().Key().Kind() != reflect.String {
		return ErrTypeMismatch
	}

	if to.IsNil() {
		to.Set(reflect.MakeMap(to.Type()))
	}

	for k, value := range from {
		key := reflect.ValueOf(k)

		old := to.MapIndex(key)
		var v reflect.Value
		var err error

		if !old.IsValid() {
			v, err = reifyValue(to.Type().Elem(), value)
		} else {
			v, err = reifyMergeValue(old, value)
		}

		if err != nil {
			return err
		}
		to.SetMapIndex(key, v)
	}

	return nil
}

func reifyStruct(to reflect.Value, from map[string]value) error {
	to = chaseValuePointers(to)
	numField := to.NumField()

	for i := 0; i < numField; i++ {
		stField := to.Type().Field(i)
		name, _ := parseTags(stField.Tag.Get("config"))
		name = fieldName(name, stField.Name)

		value, ok := from[name]
		if !ok {
			// TODO: handle missing config
			continue
		}

		vField := to.Field(i)
		v, err := reifyMergeValue(vField, value)
		if err != nil {
			return err
		}
		vField.Set(v)
	}

	return nil
}

func reifyValue(t reflect.Type, val value) (reflect.Value, error) {
	if t.Kind() == reflect.Interface && t.NumMethod() == 0 {
		return reflect.ValueOf(val.reify()), nil
	}

	baseType := chaseTypePointers(t)
	if baseType == tConfig {
		if _, ok := val.(cfgSub); !ok {
			return reflect.Value{}, ErrTypeMismatch
		}

		v := val.reflect()
		if t == baseType { // copy config
			v = v.Elem()
		} else {
			v = pointerize(t, baseType, v)
		}
		return v, nil
	}

	if baseType.Kind() == reflect.Struct {
		if _, ok := val.(cfgSub); !ok {
			return reflect.Value{}, ErrTypeMismatch
		}

		newSt := reflect.New(baseType)
		if err := reifyInto(newSt, val.(cfgSub).c.fields); err != nil {
			return reflect.Value{}, err
		}

		if t.Kind() != reflect.Ptr {
			return newSt.Elem(), nil
		}
		return pointerize(t, baseType, newSt), nil
	}

	if baseType.Kind() == reflect.Map {
		if _, ok := val.(cfgSub); !ok {
			return reflect.Value{}, ErrTypeMismatch
		}

		if baseType.Key().Kind() != reflect.String {
			return reflect.Value{}, ErrTypeMismatch
		}

		newMap := reflect.MakeMap(baseType)
		if err := reifyInto(newMap, val.(cfgSub).c.fields); err != nil {
			return reflect.Value{}, err
		}
		return newMap, nil
	}

	if baseType.Kind() == reflect.Slice {
		arr, ok := val.(*cfgArray)
		if !ok {
			arr = &cfgArray{arr: []value{val}}
		}

		v, err := reifySlice(baseType, arr)
		if err != nil {
			return reflect.Value{}, err
		}
		return pointerize(t, baseType, v), nil
	}

	v := val.reflect()
	if v.Type().ConvertibleTo(baseType) {
		v = pointerize(t, baseType, v.Convert(baseType))
		return v, nil
	}

	return reflect.Value{}, ErrTypeMismatch
}

func reifyMergeValue(oldValue reflect.Value, val value) (reflect.Value, error) {
	old := chaseValueInterfaces(oldValue)
	t := old.Type()
	old = chaseValuePointers(old)
	if (old.Kind() == reflect.Ptr || old.Kind() == reflect.Interface) && old.IsNil() {
		return reifyValue(t, val)
	}

	baseType := chaseTypePointers(old.Type())
	if baseType == tConfig {
		sub, ok := val.(cfgSub)
		if !ok {
			return reflect.Value{}, ErrTypeMismatch
		}

		if t == baseType {
			// no pointer -> return type mismatch
			return reflect.Value{}, ErrTypeMismatch
		}

		// check if old is nil -> copy reference only
		if old.Kind() == reflect.Ptr && old.IsNil() {
			return pointerize(t, baseType, val.reflect()), nil
		}

		// check if old == value
		subOld := chaseValuePointers(old).Addr().Interface().(*Config)
		if sub.c == subOld {
			return oldValue, nil
		}

		// old != value -> merge value into old
		err := mergeConfig(subOld.fields, sub.c.fields)
		return oldValue, err
	}

	switch baseType.Kind() {
	case reflect.Map:
		sub, ok := val.(cfgSub)
		if !ok {
			return reflect.Value{}, ErrTypeMismatch
		}
		err := reifyMap(old, sub.c.fields)
		return old, err
	case reflect.Struct:
		sub, ok := val.(cfgSub)
		if !ok {
			return reflect.Value{}, ErrTypeMismatch
		}
		err := reifyStruct(old, sub.c.fields)
		return oldValue, err
	case reflect.Array:
		arr, ok := val.(*cfgArray)
		if !ok {
			// convert single value to array for merging
			arr = &cfgArray{
				arr: []value{val},
			}
		}
		return reifyArray(old, baseType, arr)
	case reflect.Slice:
		arr, ok := val.(*cfgArray)
		if !ok {
			// convert single value to array for merging
			arr = &cfgArray{
				arr: []value{val},
			}
		}
		return reifySlice(baseType, arr)
	}

	// try primitive conversion
	v := val.reflect()
	if v.Type().ConvertibleTo(baseType) {
		return pointerize(t, baseType, v.Convert(baseType)), nil
	}

	return reflect.Value{}, ErrTODO
}

func reifyArray(to reflect.Value, tTo reflect.Type, arr *cfgArray) (reflect.Value, error) {
	if arr.Len() != tTo.Len() {
		return reflect.Value{}, ErrArraySizeMistach
	}
	return reifyDoArray(to, tTo.Elem(), arr)
}

func reifySlice(tTo reflect.Type, arr *cfgArray) (reflect.Value, error) {
	to := reflect.MakeSlice(tTo, arr.Len(), arr.Len())
	return reifyDoArray(to, tTo.Elem(), arr)
}

func reifyDoArray(to reflect.Value, elemT reflect.Type, arr *cfgArray) (reflect.Value, error) {
	for i, from := range arr.arr {
		v, err := reifyValue(elemT, from)
		if err != nil {
			return reflect.Value{}, ErrTODO
		}
		to.Index(i).Set(v)
	}
	return to, nil
}

func pointerize(t, base reflect.Type, v reflect.Value) reflect.Value {
	if t == base {
		return v
	}

	if t.Kind() == reflect.Interface {
		return v
	}

	for t != v.Type() {
		if !v.CanAddr() {
			tmp := reflect.New(v.Type())
			tmp.Elem().Set(v)
			v = tmp
		} else {
			v = v.Addr()
		}
	}
	return v
}
<commit_msg>Add support for options to Unpack<commit_after>package ucfg

import "reflect"

func (c *Config) Unpack(to interface{}, options ...Option) error {
	if c == nil {
		return ErrNilConfig
	}
	if to == nil {
		return ErrNilValue
	}

	opts := makeOptions(options)
	vTo := reflect.ValueOf(to)
	if to == nil || (vTo.Kind() != reflect.Ptr && vTo.Kind() != reflect.Map) {
		return ErrPointerRequired
	}
	return reifyInto(opts, vTo, c.fields)
}

func reifyInto(opts options, to reflect.Value, from map[string]value) error {
	to = chaseValuePointers(to)

	if to.Type() == tConfig {
		return mergeConfig(to.Addr().Interface().(*Config).fields, from)
	}

	switch to.Kind() {
	case reflect.Map:
		return reifyMap(opts, to, from)
	case reflect.Struct:
		return reifyStruct(opts, to, from)
	}

	return ErrTypeMismatch
}

func reifyMap(opts options, to reflect.Value, from map[string]value) error {
	if to.Type().Key().Kind() != reflect.String {
		return ErrTypeMismatch
	}

	if to.IsNil() {
		to.Set(reflect.MakeMap(to.Type()))
	}

	for k, value := range from {
		key := reflect.ValueOf(k)

		old := to.MapIndex(key)
		var v reflect.Value
		var err error

		if !old.IsValid() {
			v, err = reifyValue(opts, to.Type().Elem(), value)
		} else {
			v, err = reifyMergeValue(opts, old, value)
		}

		if err != nil {
			return err
		}
		to.SetMapIndex(key, v)
	}

	return nil
}

func reifyStruct(opts options, to reflect.Value, from map[string]value) error {
	to = chaseValuePointers(to)
	numField := to.NumField()

	for i := 0; i < numField; i++ {
		stField := to.Type().Field(i)
		name, _ := parseTags(stField.Tag.Get(opts.tag))
		name = fieldName(name, stField.Name)

		value, ok := from[name]
		if !ok {
			// TODO: handle missing config
			continue
		}

		vField := to.Field(i)
		v, err := reifyMergeValue(opts, vField, value)
		if err != nil {
			return err
		}
		vField.Set(v)
	}

	return nil
}

func reifyValue(opts options, t reflect.Type, val value) (reflect.Value, error) {
	if t.Kind() == reflect.Interface && t.NumMethod() == 0 {
		return reflect.ValueOf(val.reify()), nil
	}

	baseType := chaseTypePointers(t)
	if baseType == tConfig {
		if _, ok := val.(cfgSub); !ok {
			return reflect.Value{}, ErrTypeMismatch
		}

		v := val.reflect()
		if t == baseType { // copy config
			v = v.Elem()
		} else {
			v = pointerize(t, baseType, v)
		}
		return v, nil
	}

	if baseType.Kind() == reflect.Struct {
		if _, ok := val.(cfgSub); !ok {
			return reflect.Value{}, ErrTypeMismatch
		}

		newSt := reflect.New(baseType)
		if err := reifyInto(opts, newSt, val.(cfgSub).c.fields); err != nil {
			return reflect.Value{}, err
		}

		if t.Kind() != reflect.Ptr {
			return newSt.Elem(), nil
		}
		return pointerize(t, baseType, newSt), nil
	}

	if baseType.Kind() == reflect.Map {
		if _, ok := val.(cfgSub); !ok {
			return reflect.Value{}, ErrTypeMismatch
		}

		if baseType.Key().Kind() != reflect.String {
			return reflect.Value{}, ErrTypeMismatch
		}

		newMap := reflect.MakeMap(baseType)
		if err := reifyInto(opts, newMap, val.(cfgSub).c.fields); err != nil {
			return reflect.Value{}, err
		}
		return newMap, nil
	}

	if baseType.Kind() == reflect.Slice {
		arr, ok := val.(*cfgArray)
		if !ok {
			arr = &cfgArray{arr: []value{val}}
		}

		v, err := reifySlice(opts, baseType, arr)
		if err != nil {
			return reflect.Value{}, err
		}
		return pointerize(t, baseType, v), nil
	}

	v := val.reflect()
	if v.Type().ConvertibleTo(baseType) {
		v = pointerize(t, baseType, v.Convert(baseType))
		return v, nil
	}

	return reflect.Value{}, ErrTypeMismatch
}

func reifyMergeValue(
	opts options,
	oldValue reflect.Value, val value,
) (reflect.Value, error) {
	old := chaseValueInterfaces(oldValue)
	t := old.Type()
	old = chaseValuePointers(old)
	if (old.Kind() == reflect.Ptr || old.Kind() == reflect.Interface) && old.IsNil() {
		return reifyValue(opts, t, val)
	}

	baseType := chaseTypePointers(old.Type())
	if baseType == tConfig {
		sub, ok := val.(cfgSub)
		if !ok {
			return reflect.Value{}, ErrTypeMismatch
		}

		if t == baseType {
			// no pointer -> return type mismatch
			return reflect.Value{}, ErrTypeMismatch
		}

		// check if old is nil -> copy reference only
		if old.Kind() == reflect.Ptr && old.IsNil() {
			return pointerize(t, baseType, val.reflect()), nil
		}

		// check if old == value
		subOld := chaseValuePointers(old).Addr().Interface().(*Config)
		if sub.c == subOld {
			return oldValue, nil
		}

		// old != value -> merge value into old
		err := mergeConfig(subOld.fields, sub.c.fields)
		return oldValue, err
	}

	switch baseType.Kind() {
	case reflect.Map:
		sub, ok := val.(cfgSub)
		if !ok {
			return reflect.Value{}, ErrTypeMismatch
		}
		err := reifyMap(opts, old, sub.c.fields)
		return old, err
	case reflect.Struct:
		sub, ok := val.(cfgSub)
		if !ok {
			return reflect.Value{}, ErrTypeMismatch
		}
		err := reifyStruct(opts, old, sub.c.fields)
		return oldValue, err
	case reflect.Array:
		arr, ok := val.(*cfgArray)
		if !ok {
			// convert single value to array for merging
			arr = &cfgArray{
				arr: []value{val},
			}
		}
		return reifyArray(opts, old, baseType, arr)
	case reflect.Slice:
		arr, ok := val.(*cfgArray)
		if !ok {
			// convert single value to array for merging
			arr = &cfgArray{
				arr: []value{val},
			}
		}
		return reifySlice(opts, baseType, arr)
	}

	// try primitive conversion
	v := val.reflect()
	if v.Type().ConvertibleTo(baseType) {
		return pointerize(t, baseType, v.Convert(baseType)), nil
	}

	return reflect.Value{}, ErrTODO
}

func reifyArray(opts options, to reflect.Value, tTo reflect.Type, arr *cfgArray) (reflect.Value, error) {
	if arr.Len() != tTo.Len() {
		return reflect.Value{}, ErrArraySizeMistach
	}
	return reifyDoArray(opts, to, tTo.Elem(), arr)
}

func reifySlice(opts options, tTo reflect.Type, arr *cfgArray) (reflect.Value, error) {
	to := reflect.MakeSlice(tTo, arr.Len(), arr.Len())
	return reifyDoArray(opts, to, tTo.Elem(), arr)
}

func reifyDoArray(
	opts options,
	to reflect.Value, elemT reflect.Type, arr *cfgArray,
) (reflect.Value, error) {
	for i, from := range arr.arr {
		v, err := reifyValue(opts, elemT, from)
		if err != nil {
			return reflect.Value{}, ErrTODO
		}
		to.Index(i).Set(v)
	}
	return to, nil
}

func pointerize(t, base reflect.Type, v reflect.Value) reflect.Value {
	if t == base {
		return v
	}

	if t.Kind() == reflect.Interface {
		return v
	}

	for t != v.Type() {
		if !v.CanAddr() {
			tmp := reflect.New(v.Type())
			tmp.Elem().Set(v)
			v = tmp
		} else {
			v = v.Addr()
		}
	}
	return v
}
<|endoftext|>


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

package minio

import (
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// MaxRetry is the maximum number of retries before stopping.
var MaxRetry = 10

// MaxJitter will randomize over the full exponential backoff time
const MaxJitter = 1.0

// NoJitter disables the use of jitter for randomizing the exponential backoff time
const NoJitter = 0.0

// DefaultRetryUnit - default unit multiplicative per retry.
// defaults to 1 second.
const DefaultRetryUnit = time.Second

// DefaultRetryCap - Each retry attempt never waits no longer than
// this maximum time duration.
const DefaultRetryCap = time.Second * 30

// newRetryTimer creates a timer with exponentially increasing
// delays until the maximum retry attempts are reached.
func (c Client) newRetryTimer(maxRetry int, unit time.Duration, cap time.Duration, jitter float64, doneCh chan struct{}) <-chan int {
	attemptCh := make(chan int)

	// computes the exponential backoff duration according to
	// https://www.awsarchitectureblog.com/2015/03/backoff.html
	exponentialBackoffWait := func(attempt int) time.Duration {
		// normalize jitter to the range [0, 1.0]
		if jitter < NoJitter {
			jitter = NoJitter
		}
		if jitter > MaxJitter {
			jitter = MaxJitter
		}

		//sleep = random_between(0, min(cap, base * 2 ** attempt))
		sleep := unit * time.Duration(1<<uint(attempt))
		if sleep > cap {
			sleep = cap
		}
		if jitter != NoJitter {
			sleep -= time.Duration(c.random.Float64() * float64(sleep) * jitter)
		}
		return sleep
	}

	go func() {
		defer close(attemptCh)
		for i := 0; i < maxRetry; i++ {
			select {
			// Attempts start from 1.
			case attemptCh <- i + 1:
			case <-doneCh:
				// Stop the routine.
				return
			}
			time.Sleep(exponentialBackoffWait(i))
		}
	}()
	return attemptCh
}

// isNetErrorRetryable - is network error retryable.
func isNetErrorRetryable(err error) bool {
	if err == nil {
		return false
	}
	switch err.(type) {
	case net.Error:
		switch err.(type) {
		case *net.DNSError, *net.OpError, net.UnknownNetworkError:
			return true
		case *url.Error:
			// For a URL error, where it replies back "connection closed"
			// retry again.
			if strings.Contains(err.Error(), "Connection closed by foreign host") {
				return true
			}
		default:
			if strings.Contains(err.Error(), "net/http: TLS handshake timeout") {
				// If error is - tlsHandshakeTimeoutError, retry.
				return true
			} else if strings.Contains(err.Error(), "i/o timeout") {
				// If error is - tcp timeoutError, retry.
				return true
			} else if strings.Contains(err.Error(), "connection timed out") {
				// If err is a net.Dial timeout, retry.
				return true
			}
		}
	}
	return false
}

// List of AWS S3 error codes which are retryable.
var retryableS3Codes = map[string]struct{}{
	"RequestError":          {},
	"RequestTimeout":        {},
	"Throttling":            {},
	"ThrottlingException":   {},
	"RequestLimitExceeded":  {},
	"RequestThrottled":      {},
	"InternalError":         {},
	"ExpiredToken":          {},
	"ExpiredTokenException": {},
	// Add more AWS S3 codes here.
}

// isS3CodeRetryable - is s3 error code retryable.
func isS3CodeRetryable(s3Code string) (ok bool) {
	_, ok = retryableS3Codes[s3Code]
	return ok
}

// List of HTTP status codes which are retryable.
var retryableHTTPStatusCodes = map[int]struct{}{
	429: {}, // http.StatusTooManyRequests is not part of the Go 1.5 library, yet
	http.StatusInternalServerError: {},
	http.StatusBadGateway:          {},
	http.StatusServiceUnavailable:  {},
	// Add more HTTP status codes here.
}

// isHTTPStatusRetryable - is HTTP error code retryable.
func isHTTPStatusRetryable(httpStatusCode int) (ok bool) {
	_, ok = retryableHTTPStatusCodes[httpStatusCode]
	return ok
}
<commit_msg>Add transport connection broken error to retry list (#956)<commit_after>/*
 * Minio Go Library for Amazon S3 Compatible Cloud Storage
 * Copyright 2015-2017 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package minio

import (
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// MaxRetry is the maximum number of retries before stopping.
var MaxRetry = 10

// MaxJitter will randomize over the full exponential backoff time
const MaxJitter = 1.0

// NoJitter disables the use of jitter for randomizing the exponential backoff time
const NoJitter = 0.0

// DefaultRetryUnit - default unit multiplicative per retry.
// defaults to 1 second.
const DefaultRetryUnit = time.Second

// DefaultRetryCap - Each retry attempt never waits no longer than
// this maximum time duration.
const DefaultRetryCap = time.Second * 30

// newRetryTimer creates a timer with exponentially increasing
// delays until the maximum retry attempts are reached.
func (c Client) newRetryTimer(maxRetry int, unit time.Duration, cap time.Duration, jitter float64, doneCh chan struct{}) <-chan int {
	attemptCh := make(chan int)

	// computes the exponential backoff duration according to
	// https://www.awsarchitectureblog.com/2015/03/backoff.html
	exponentialBackoffWait := func(attempt int) time.Duration {
		// normalize jitter to the range [0, 1.0]
		if jitter < NoJitter {
			jitter = NoJitter
		}
		if jitter > MaxJitter {
			jitter = MaxJitter
		}

		//sleep = random_between(0, min(cap, base * 2 ** attempt))
		sleep := unit * time.Duration(1<<uint(attempt))
		if sleep > cap {
			sleep = cap
		}
		if jitter != NoJitter {
			sleep -= time.Duration(c.random.Float64() * float64(sleep) * jitter)
		}
		return sleep
	}

	go func() {
		defer close(attemptCh)
		for i := 0; i < maxRetry; i++ {
			select {
			// Attempts start from 1.
			case attemptCh <- i + 1:
			case <-doneCh:
				// Stop the routine.
				return
			}
			time.Sleep(exponentialBackoffWait(i))
		}
	}()
	return attemptCh
}

// isNetErrorRetryable - is network error retryable.
func isNetErrorRetryable(err error) bool {
	if err == nil {
		return false
	}
	switch err.(type) {
	case net.Error:
		switch err.(type) {
		case *net.DNSError, *net.OpError, net.UnknownNetworkError:
			return true
		case *url.Error:
			// For a URL error, where it replies back "connection closed"
			// retry again.
			if strings.Contains(err.Error(), "Connection closed by foreign host") {
				return true
			}
		default:
			if strings.Contains(err.Error(), "net/http: TLS handshake timeout") {
				// If error is - tlsHandshakeTimeoutError, retry.
				return true
			} else if strings.Contains(err.Error(), "i/o timeout") {
				// If error is - tcp timeoutError, retry.
				return true
			} else if strings.Contains(err.Error(), "connection timed out") {
				// If err is a net.Dial timeout, retry.
				return true
			} else if strings.Contains(err.Error(), "net/http: HTTP/1.x transport connection broken") {
				// If error is transport connection broken, retry.
				return true
			}
		}
	}
	return false
}

// List of AWS S3 error codes which are retryable.
var retryableS3Codes = map[string]struct{}{
	"RequestError":          {},
	"RequestTimeout":        {},
	"Throttling":            {},
	"ThrottlingException":   {},
	"RequestLimitExceeded":  {},
	"RequestThrottled":      {},
	"InternalError":         {},
	"ExpiredToken":          {},
	"ExpiredTokenException": {},
	// Add more AWS S3 codes here.
}

// isS3CodeRetryable - is s3 error code retryable.
func isS3CodeRetryable(s3Code string) (ok bool) {
	_, ok = retryableS3Codes[s3Code]
	return ok
}

// List of HTTP status codes which are retryable.
var retryableHTTPStatusCodes = map[int]struct{}{
	429: {}, // http.StatusTooManyRequests is not part of the Go 1.5 library, yet
	http.StatusInternalServerError: {},
	http.StatusBadGateway:          {},
	http.StatusServiceUnavailable:  {},
	// Add more HTTP status codes here.
}

// isHTTPStatusRetryable - is HTTP error code retryable.
func isHTTPStatusRetryable(httpStatusCode int) (ok bool) {
	_, ok = retryableHTTPStatusCodes[httpStatusCode]
	return ok
}
<|endoftext|>


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

package minio

import (
	"net"
	"net/url"
	"strings"
	"time"
)

// MaxRetry is the maximum number of retries before stopping.
var MaxRetry = 5

// MaxJitter will randomize over the full exponential backoff time
const MaxJitter = 1.0

// NoJitter disables the use of jitter for randomizing the exponential backoff time
const NoJitter = 0.0

// newRetryTimer creates a timer with exponentially increasing delays
// until the maximum retry attempts are reached.
func (c Client) newRetryTimer(maxRetry int, unit time.Duration, cap time.Duration, jitter float64) <-chan int {
	attemptCh := make(chan int)

	// computes the exponential backoff duration according to
	// https://www.awsarchitectureblog.com/2015/03/backoff.html
	exponentialBackoffWait := func(attempt int) time.Duration {
		// normalize jitter to the range [0, 1.0]
		if jitter < NoJitter {
			jitter = NoJitter
		}
		if jitter > MaxJitter {
			jitter = MaxJitter

		}

		//sleep = random_between(0, min(cap, base * 2 ** attempt))
		sleep := unit * time.Duration(1<<uint(attempt))
		if sleep > cap {
			sleep = cap
		}
		if jitter != NoJitter {
			sleep -= time.Duration(c.random.Float64() * float64(sleep) * jitter)
		}
		return sleep
	}

	go func() {
		defer close(attemptCh)
		for i := 0; i < maxRetry; i++ {
			attemptCh <- i + 1 // Attempts start from 1.
			time.Sleep(exponentialBackoffWait(i))
		}
	}()
	return attemptCh
}

// isNetErrorRetryable - is network error retryable.
func isNetErrorRetryable(err error) bool {
	switch err.(type) {
	case *net.DNSError, *net.OpError, net.UnknownNetworkError:
		return true
	case *url.Error:
		// For a URL error, where it replies back "connection closed"
		// retry again.
		if strings.Contains(err.Error(), "Connection closed by foreign host") {
			return true
		}
	}
	return false
}

// isS3CodeRetryable - is s3 error code retryable.
func isS3CodeRetryable(s3Code string) bool {
	switch s3Code {
	case "RequestError", "RequestTimeout", "Throttling", "ThrottlingException":
		fallthrough
	case "RequestLimitExceeded", "RequestThrottled", "InternalError":
		fallthrough
	case "ExpiredToken", "ExpiredTokenException":
		return true
	}
	return false
}
<commit_msg>retry: re-factor isS3CodeRetryable to use map, instead of switch.<commit_after>/*
 * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package minio

import (
	"net"
	"net/url"
	"strings"
	"time"
)

// MaxRetry is the maximum number of retries before stopping.
var MaxRetry = 5

// MaxJitter will randomize over the full exponential backoff time
const MaxJitter = 1.0

// NoJitter disables the use of jitter for randomizing the exponential backoff time
const NoJitter = 0.0

// newRetryTimer creates a timer with exponentially increasing delays
// until the maximum retry attempts are reached.
func (c Client) newRetryTimer(maxRetry int, unit time.Duration, cap time.Duration, jitter float64) <-chan int {
	attemptCh := make(chan int)

	// computes the exponential backoff duration according to
	// https://www.awsarchitectureblog.com/2015/03/backoff.html
	exponentialBackoffWait := func(attempt int) time.Duration {
		// normalize jitter to the range [0, 1.0]
		if jitter < NoJitter {
			jitter = NoJitter
		}
		if jitter > MaxJitter {
			jitter = MaxJitter

		}

		//sleep = random_between(0, min(cap, base * 2 ** attempt))
		sleep := unit * time.Duration(1<<uint(attempt))
		if sleep > cap {
			sleep = cap
		}
		if jitter != NoJitter {
			sleep -= time.Duration(c.random.Float64() * float64(sleep) * jitter)
		}
		return sleep
	}

	go func() {
		defer close(attemptCh)
		for i := 0; i < maxRetry; i++ {
			attemptCh <- i + 1 // Attempts start from 1.
			time.Sleep(exponentialBackoffWait(i))
		}
	}()
	return attemptCh
}

// isNetErrorRetryable - is network error retryable.
func isNetErrorRetryable(err error) bool {
	switch err.(type) {
	case *net.DNSError, *net.OpError, net.UnknownNetworkError:
		return true
	case *url.Error:
		// For a URL error, where it replies back "connection closed"
		// retry again.
		if strings.Contains(err.Error(), "Connection closed by foreign host") {
			return true
		}
	}
	return false
}

// List of S3 codes which are retryable.
var s3CodesRetryable = map[string]struct{}{
	"RequestError":          {},
	"RequestTimeout":        {},
	"Throttling":            {},
	"ThrottlingException":   {},
	"RequestLimitExceeded":  {},
	"RequestThrottled":      {},
	"InternalError":         {},
	"ExpiredToken":          {},
	"ExpiredTokenException": {},
	// Add more s3 codes here.
}

// isS3CodeRetryable - is s3 error code retryable.
func isS3CodeRetryable(s3Code string) (ok bool) {
	_, ok = s3CodesRetryable[s3Code]
	return ok
}
<|endoftext|>


<commit_before>// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package revel

import (
	"go/build"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	// "cmd/vendor/golang.org/x/mod/modfile"
	"github.com/revel/config"
	"golang.org/x/mod/modfile"
)

const (
	// RevelImportPath Revel framework import path
	RevelImportPath = "github.com/revel/revel"
)
const (
	// Event type when templates are going to be refreshed (receivers are registered template engines added to the template.engine conf option)
	TEMPLATE_REFRESH_REQUESTED = iota
	// Event type when templates are refreshed (receivers are registered template engines added to the template.engine conf option)
	TEMPLATE_REFRESH_COMPLETED
	// Event type before all module loads, events thrown to handlers added to AddInitEventHandler

	// Event type before all module loads, events thrown to handlers added to AddInitEventHandler
	REVEL_BEFORE_MODULES_LOADED
	// Event type after all module loads, events thrown to handlers added to AddInitEventHandler
	REVEL_AFTER_MODULES_LOADED

	// Event type before server engine is initialized, receivers are active server engine and handlers added to AddInitEventHandler
	ENGINE_BEFORE_INITIALIZED
	// Event type before server engine is started, receivers are active server engine and handlers added to AddInitEventHandler
	ENGINE_STARTED
	// Event type after server engine is stopped, receivers are active server engine and handlers added to AddInitEventHandler
	ENGINE_SHUTDOWN

	// Called before routes are refreshed
	ROUTE_REFRESH_REQUESTED
	// Called after routes have been refreshed
	ROUTE_REFRESH_COMPLETED
)

type EventHandler func(typeOf int, value interface{}) (responseOf int)

// App details
var (
	AppName    string // e.g. "sample"
	AppRoot    string // e.g. "/app1"
	BasePath   string // e.g. "$GOPATH/src/corp/sample"
	AppPath    string // e.g. "$GOPATH/src/corp/sample/app"
	ViewsPath  string // e.g. "$GOPATH/src/corp/sample/app/views"
	ImportPath string // e.g. "corp/sample"
	SourcePath string // e.g. "$GOPATH/src"
	IsGoModule bool

	Config  *config.Context
	RunMode string // Application-defined (by default, "dev" or "prod")
	DevMode bool   // if true, RunMode is a development mode.

	// Revel installation details
	RevelPath string // e.g. "$GOPATH/src/github.com/revel/revel"

	// Where to look for templates
	// Ordered by priority. (Earlier paths take precedence over later paths.)
	CodePaths     []string // Code base directories, for modules and app
	TemplatePaths []string // Template path directories manually added

	// ConfPaths where to look for configurations
	// Config load order
	// 1. framework (revel/conf/*)
	// 2. application (conf/*)
	// 3. user supplied configs (...) - User configs can override/add any from above
	ConfPaths []string

	// Server config.
	//
	// Alert: This is how the app is configured, which may be different from
	// the current process reality.  For example, if the app is configured for
	// port 9000, HTTPPort will always be 9000, even though in dev mode it is
	// run on a random port and proxied.
	HTTPPort    int    // e.g. 9000
	HTTPAddr    string // e.g. "", "127.0.0.1"
	HTTPSsl     bool   // e.g. true if using ssl
	HTTPSslCert string // e.g. "/path/to/cert.pem"
	HTTPSslKey  string // e.g. "/path/to/key.pem"

	// All cookies dropped by the framework begin with this prefix.
	CookiePrefix string
	// Cookie domain
	CookieDomain string
	// Cookie flags
	CookieSecure bool

	// Revel request access log, not exposed from package.
	// However output settings can be controlled from app.conf

	// True when revel engine has been initialized (Init has returned)
	Initialized bool

	// Private
	secretKey     []byte             // Key used to sign cookies. An empty key disables signing.
	packaged      bool               // If true, this is running from a pre-built package.
	initEventList = []EventHandler{} // Event handler list for receiving events
)

// Init initializes Revel -- it provides paths for getting around the app.
//
// Params:
//   mode - the run mode, which determines which app.conf settings are used.
//   importPath - the Go import path of the application.
//   srcPath - the path to the source directory, containing Revel and the app.
//     If not specified (""), then a functioning Go installation is required.
func Init(mode, importPath, srcPath string) {
	// Ignore trailing slashes.
	ImportPath = strings.TrimRight(importPath, "/")
	SourcePath = srcPath
	RunMode = mode

	// If the SourcePath is not specified, find it using build.Import.
	var revelSourcePath string // may be different from the app source path
	if SourcePath == "" {
		revelSourcePath, SourcePath, IsGoModule = findSrcPaths(importPath)
	} else {
		// If the SourcePath was specified, assume both Revel and the app are within it.
		SourcePath = filepath.Clean(SourcePath)
		revelSourcePath = SourcePath
		packaged = true
	}

	RevelPath = filepath.Join(revelSourcePath, filepath.FromSlash(RevelImportPath))

	if IsGoModule {
		bs, err := ioutil.ReadFile(filepath.Join(SourcePath, "go.mod"))
		if err != nil {
			RevelLog.Fatal("Failed to read mod file:", "error", err)
		}
		modrelatepath := modfile.ModulePath(bs)
		if importPath == modrelatepath {
			BasePath = SourcePath
		} else {
			BasePath = filepath.Join(SourcePath, filepath.FromSlash(importPath[len(modrelatepath)+1:]))
		}
	} else {
		BasePath = filepath.Join(SourcePath, filepath.FromSlash(importPath))
	}

	AppPath = filepath.Join(BasePath, "app")
	ViewsPath = filepath.Join(AppPath, "views")

	CodePaths = []string{AppPath}

	if ConfPaths == nil {
		ConfPaths = []string{}
	}

	// Config load order
	// 1. framework (revel/conf/*)
	// 2. application (conf/*)
	// 3. user supplied configs (...) - User configs can override/add any from above
	ConfPaths = append(
		[]string{
			filepath.Join(RevelPath, "conf"),
			filepath.Join(BasePath, "conf"),
		},
		ConfPaths...)

	TemplatePaths = []string{
		ViewsPath,
		filepath.Join(RevelPath, "templates"),
	}

	// Load app.conf
	var err error
	Config, err = config.LoadContext("app.conf", ConfPaths)
	if err != nil || Config == nil {
		RevelLog.Fatal("Failed to load app.conf:", "error", err)
	}
	// Ensure that the selected runmode appears in app.conf.
	// If empty string is passed as the mode, treat it as "DEFAULT"
	if mode == "" {
		mode = config.DefaultSection
	}
	if !Config.HasSection(mode) {
		log.Fatalln("app.conf: No mode found:", mode)
	}
	Config.SetSection(mode)

	// Configure properties from app.conf
	DevMode = Config.BoolDefault("mode.dev", false)
	HTTPPort = Config.IntDefault("http.port", 9000)
	HTTPAddr = Config.StringDefault("http.addr", "")
	HTTPSsl = Config.BoolDefault("http.ssl", false)
	HTTPSslCert = Config.StringDefault("http.sslcert", "")
	HTTPSslKey = Config.StringDefault("http.sslkey", "")
	if HTTPSsl {
		if HTTPSslCert == "" {
			RevelLog.Fatal("No http.sslcert provided.")
		}
		if HTTPSslKey == "" {
			RevelLog.Fatal("No http.sslkey provided.")
		}
	}

	AppName = Config.StringDefault("app.name", "(not set)")
	AppRoot = Config.StringDefault("app.root", "")
	CookiePrefix = Config.StringDefault("cookie.prefix", "REVEL")
	CookieDomain = Config.StringDefault("cookie.domain", "")
	CookieSecure = Config.BoolDefault("cookie.secure", HTTPSsl)
	if secretStr := Config.StringDefault("app.secret", ""); secretStr != "" {
		SetSecretKey([]byte(secretStr))
	}

	fireEvent(REVEL_BEFORE_MODULES_LOADED, nil)
	loadModules()
	fireEvent(REVEL_AFTER_MODULES_LOADED, nil)

	Initialized = true
	RevelLog.Info("Initialized Revel", "Version", Version, "BuildDate", BuildDate, "MinimumGoVersion", MinimumGoVersion)
}

// Fires system events from revel
func fireEvent(key int, value interface{}) (response int) {
	for _, handler := range initEventList {
		response |= handler(key, value)
	}
	return
}

// Add event handler to listen for all system events
func AddInitEventHandler(handler EventHandler) {
	initEventList = append(initEventList, handler)
	return
}

// Set the secret key
func SetSecretKey(newKey []byte) error {
	secretKey = newKey
	return nil
}

// ResolveImportPath returns the filesystem path for the given import path.
// Returns an error if the import path could not be found.
func ResolveImportPath(importPath string) (string, error) {
	if packaged {
		return filepath.Join(SourcePath, importPath), nil
	}

	modPkg, err := build.Import(importPath, RevelPath, build.FindOnly)
	if err != nil {
		return "", err
	}
	return modPkg.Dir, nil
}

// CheckInit method checks `revel.Initialized` if not initialized it panics
func CheckInit() {
	if !Initialized {
		RevelLog.Panic("CheckInit: Revel has not been initialized!")
	}
}

// findSrcPaths uses the "go/build" package to find the source root for Revel
// and the app.
func findSrcPaths(importPath string) (revelSourcePath, appSourcePath string, isGoModule bool) {
	var (
		gopaths = filepath.SplitList(build.Default.GOPATH)
		goroot  = build.Default.GOROOT
	)

	if ContainsString(gopaths, goroot) {
		RevelLog.Fatalf("GOPATH (%s) must not include your GOROOT (%s). "+
			"Please refer to http://golang.org/doc/code.html to configure your Go environment.",
			gopaths, goroot)
	}

	isGoModule = false
	var srcImportPath string
	var srcRoot string
	for _, pa := range gopaths {
		dir := filepath.Join(pa, "src", importPath)
		st, err := os.Stat(dir)
		if err != nil {
			continue
		}
		if st != nil {
			srcImportPath = dir
			srcRoot = filepath.Join(pa, "src")
			break
		}
	}

	if srcImportPath == "" {
		if os.Getenv("GO111MODULE") == "on" {
			modpath := GetModPath()
			if modpath == "" {
				RevelLog.Fatal("GOPATH environment variable is not set. " +
					"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
			}

			srcImportPath = modpath
			srcRoot = modpath
			isGoModule = true
		} else {
			appPkg, err := build.Import(importPath, "", build.FindOnly)
			if err != nil {
				RevelLog.Panic("Failed to import "+importPath+" with error:", "error", err)
			}
			srcImportPath = appPkg.Dir
			srcRoot = appPkg.SrcRoot
		}
	}

	revelPkg, err := build.Import(RevelImportPath, srcImportPath, build.FindOnly)
	if err != nil {
		RevelLog.Fatal("Failed to find Revel with error:", "error", err)
	}

	return revelPkg.SrcRoot, srcRoot, isGoModule //
}

func GetModPath() string {
	wd, err := os.Getwd()
	if err != nil {
		return ""
	}
	for {
		gomod := filepath.Join(wd, "go.mod")
		if _, err := os.Stat(gomod); err == nil {
			return wd
		}
		parent := filepath.Dir(wd)
		if len(parent) >= len(wd) {
			return ""
		}
		wd = parent
	}
}
<commit_msg>add Event and EventResponse<commit_after>// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package revel

import (
	"go/build"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	// "cmd/vendor/golang.org/x/mod/modfile"
	"github.com/revel/config"
	"golang.org/x/mod/modfile"
)

type Event = int
type EventResponse = int

const (

	// RevelImportPath Revel framework import path
	RevelImportPath = "github.com/revel/revel"
)
const (
	// Event type when templates are going to be refreshed (receivers are registered template engines added to the template.engine conf option)
	TEMPLATE_REFRESH_REQUESTED = iota
	// Event type when templates are refreshed (receivers are registered template engines added to the template.engine conf option)
	TEMPLATE_REFRESH_COMPLETED
	// Event type before all module loads, events thrown to handlers added to AddInitEventHandler

	// Event type before all module loads, events thrown to handlers added to AddInitEventHandler
	REVEL_BEFORE_MODULES_LOADED
	// Event type after all module loads, events thrown to handlers added to AddInitEventHandler
	REVEL_AFTER_MODULES_LOADED

	// Event type before server engine is initialized, receivers are active server engine and handlers added to AddInitEventHandler
	ENGINE_BEFORE_INITIALIZED
	// Event type before server engine is started, receivers are active server engine and handlers added to AddInitEventHandler
	ENGINE_STARTED
	// Event type after server engine is stopped, receivers are active server engine and handlers added to AddInitEventHandler
	ENGINE_SHUTDOWN

	// Called before routes are refreshed
	ROUTE_REFRESH_REQUESTED
	// Called after routes have been refreshed
	ROUTE_REFRESH_COMPLETED
)

type EventHandler func(typeOf int, value interface{}) (responseOf int)

// App details
var (
	AppName    string // e.g. "sample"
	AppRoot    string // e.g. "/app1"
	BasePath   string // e.g. "$GOPATH/src/corp/sample"
	AppPath    string // e.g. "$GOPATH/src/corp/sample/app"
	ViewsPath  string // e.g. "$GOPATH/src/corp/sample/app/views"
	ImportPath string // e.g. "corp/sample"
	SourcePath string // e.g. "$GOPATH/src"
	IsGoModule bool

	Config  *config.Context
	RunMode string // Application-defined (by default, "dev" or "prod")
	DevMode bool   // if true, RunMode is a development mode.

	// Revel installation details
	RevelPath string // e.g. "$GOPATH/src/github.com/revel/revel"

	// Where to look for templates
	// Ordered by priority. (Earlier paths take precedence over later paths.)
	CodePaths     []string // Code base directories, for modules and app
	TemplatePaths []string // Template path directories manually added

	// ConfPaths where to look for configurations
	// Config load order
	// 1. framework (revel/conf/*)
	// 2. application (conf/*)
	// 3. user supplied configs (...) - User configs can override/add any from above
	ConfPaths []string

	// Server config.
	//
	// Alert: This is how the app is configured, which may be different from
	// the current process reality.  For example, if the app is configured for
	// port 9000, HTTPPort will always be 9000, even though in dev mode it is
	// run on a random port and proxied.
	HTTPPort    int    // e.g. 9000
	HTTPAddr    string // e.g. "", "127.0.0.1"
	HTTPSsl     bool   // e.g. true if using ssl
	HTTPSslCert string // e.g. "/path/to/cert.pem"
	HTTPSslKey  string // e.g. "/path/to/key.pem"

	// All cookies dropped by the framework begin with this prefix.
	CookiePrefix string
	// Cookie domain
	CookieDomain string
	// Cookie flags
	CookieSecure bool

	// Revel request access log, not exposed from package.
	// However output settings can be controlled from app.conf

	// True when revel engine has been initialized (Init has returned)
	Initialized bool

	// Private
	secretKey     []byte             // Key used to sign cookies. An empty key disables signing.
	packaged      bool               // If true, this is running from a pre-built package.
	initEventList = []EventHandler{} // Event handler list for receiving events
)

// Init initializes Revel -- it provides paths for getting around the app.
//
// Params:
//   mode - the run mode, which determines which app.conf settings are used.
//   importPath - the Go import path of the application.
//   srcPath - the path to the source directory, containing Revel and the app.
//     If not specified (""), then a functioning Go installation is required.
func Init(mode, importPath, srcPath string) {
	// Ignore trailing slashes.
	ImportPath = strings.TrimRight(importPath, "/")
	SourcePath = srcPath
	RunMode = mode

	// If the SourcePath is not specified, find it using build.Import.
	var revelSourcePath string // may be different from the app source path
	if SourcePath == "" {
		revelSourcePath, SourcePath, IsGoModule = findSrcPaths(importPath)
	} else {
		// If the SourcePath was specified, assume both Revel and the app are within it.
		SourcePath = filepath.Clean(SourcePath)
		revelSourcePath = SourcePath
		packaged = true
	}

	RevelPath = filepath.Join(revelSourcePath, filepath.FromSlash(RevelImportPath))

	if IsGoModule {
		bs, err := ioutil.ReadFile(filepath.Join(SourcePath, "go.mod"))
		if err != nil {
			RevelLog.Fatal("Failed to read mod file:", "error", err)
		}
		modrelatepath := modfile.ModulePath(bs)
		if importPath == modrelatepath {
			BasePath = SourcePath
		} else {
			BasePath = filepath.Join(SourcePath, filepath.FromSlash(importPath[len(modrelatepath)+1:]))
		}
	} else {
		BasePath = filepath.Join(SourcePath, filepath.FromSlash(importPath))
	}

	AppPath = filepath.Join(BasePath, "app")
	ViewsPath = filepath.Join(AppPath, "views")

	CodePaths = []string{AppPath}

	if ConfPaths == nil {
		ConfPaths = []string{}
	}

	// Config load order
	// 1. framework (revel/conf/*)
	// 2. application (conf/*)
	// 3. user supplied configs (...) - User configs can override/add any from above
	ConfPaths = append(
		[]string{
			filepath.Join(RevelPath, "conf"),
			filepath.Join(BasePath, "conf"),
		},
		ConfPaths...)

	TemplatePaths = []string{
		ViewsPath,
		filepath.Join(RevelPath, "templates"),
	}

	// Load app.conf
	var err error
	Config, err = config.LoadContext("app.conf", ConfPaths)
	if err != nil || Config == nil {
		RevelLog.Fatal("Failed to load app.conf:", "error", err)
	}
	// Ensure that the selected runmode appears in app.conf.
	// If empty string is passed as the mode, treat it as "DEFAULT"
	if mode == "" {
		mode = config.DefaultSection
	}
	if !Config.HasSection(mode) {
		log.Fatalln("app.conf: No mode found:", mode)
	}
	Config.SetSection(mode)

	// Configure properties from app.conf
	DevMode = Config.BoolDefault("mode.dev", false)
	HTTPPort = Config.IntDefault("http.port", 9000)
	HTTPAddr = Config.StringDefault("http.addr", "")
	HTTPSsl = Config.BoolDefault("http.ssl", false)
	HTTPSslCert = Config.StringDefault("http.sslcert", "")
	HTTPSslKey = Config.StringDefault("http.sslkey", "")
	if HTTPSsl {
		if HTTPSslCert == "" {
			RevelLog.Fatal("No http.sslcert provided.")
		}
		if HTTPSslKey == "" {
			RevelLog.Fatal("No http.sslkey provided.")
		}
	}

	AppName = Config.StringDefault("app.name", "(not set)")
	AppRoot = Config.StringDefault("app.root", "")
	CookiePrefix = Config.StringDefault("cookie.prefix", "REVEL")
	CookieDomain = Config.StringDefault("cookie.domain", "")
	CookieSecure = Config.BoolDefault("cookie.secure", HTTPSsl)
	if secretStr := Config.StringDefault("app.secret", ""); secretStr != "" {
		SetSecretKey([]byte(secretStr))
	}

	fireEvent(REVEL_BEFORE_MODULES_LOADED, nil)
	loadModules()
	fireEvent(REVEL_AFTER_MODULES_LOADED, nil)

	Initialized = true
	RevelLog.Info("Initialized Revel", "Version", Version, "BuildDate", BuildDate, "MinimumGoVersion", MinimumGoVersion)
}

// Fires system events from revel
func fireEvent(key int, value interface{}) (response int) {
	for _, handler := range initEventList {
		response |= handler(key, value)
	}
	return
}

// Add event handler to listen for all system events
func AddInitEventHandler(handler EventHandler) {
	initEventList = append(initEventList, handler)
	return
}

// Set the secret key
func SetSecretKey(newKey []byte) error {
	secretKey = newKey
	return nil
}

// ResolveImportPath returns the filesystem path for the given import path.
// Returns an error if the import path could not be found.
func ResolveImportPath(importPath string) (string, error) {
	if packaged {
		return filepath.Join(SourcePath, importPath), nil
	}

	modPkg, err := build.Import(importPath, RevelPath, build.FindOnly)
	if err != nil {
		return "", err
	}
	return modPkg.Dir, nil
}

// CheckInit method checks `revel.Initialized` if not initialized it panics
func CheckInit() {
	if !Initialized {
		RevelLog.Panic("CheckInit: Revel has not been initialized!")
	}
}

// findSrcPaths uses the "go/build" package to find the source root for Revel
// and the app.
func findSrcPaths(importPath string) (revelSourcePath, appSourcePath string, isGoModule bool) {
	var (
		gopaths = filepath.SplitList(build.Default.GOPATH)
		goroot  = build.Default.GOROOT
	)

	if ContainsString(gopaths, goroot) {
		RevelLog.Fatalf("GOPATH (%s) must not include your GOROOT (%s). "+
			"Please refer to http://golang.org/doc/code.html to configure your Go environment.",
			gopaths, goroot)
	}

	isGoModule = false
	var srcImportPath string
	var srcRoot string
	for _, pa := range gopaths {
		dir := filepath.Join(pa, "src", importPath)
		st, err := os.Stat(dir)
		if err != nil {
			continue
		}
		if st != nil {
			srcImportPath = dir
			srcRoot = filepath.Join(pa, "src")
			break
		}
	}

	if srcImportPath == "" {
		if os.Getenv("GO111MODULE") == "on" {
			modpath := GetModPath()
			if modpath == "" {
				RevelLog.Fatal("GOPATH environment variable is not set. " +
					"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
			}

			srcImportPath = modpath
			srcRoot = modpath
			isGoModule = true
		} else {
			appPkg, err := build.Import(importPath, "", build.FindOnly)
			if err != nil {
				RevelLog.Panic("Failed to import "+importPath+" with error:", "error", err)
			}
			srcImportPath = appPkg.Dir
			srcRoot = appPkg.SrcRoot
		}
	}

	revelPkg, err := build.Import(RevelImportPath, srcImportPath, build.FindOnly)
	if err != nil {
		RevelLog.Fatal("Failed to find Revel with error:", "error", err)
	}

	return revelPkg.SrcRoot, srcRoot, isGoModule //
}

func GetModPath() string {
	wd, err := os.Getwd()
	if err != nil {
		return ""
	}
	for {
		gomod := filepath.Join(wd, "go.mod")
		if _, err := os.Stat(gomod); err == nil {
			return wd
		}
		parent := filepath.Dir(wd)
		if len(parent) >= len(wd) {
			return ""
		}
		wd = parent
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/pem"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strconv"

	"github.com/flynn/flynn-controller/client"
	"github.com/flynn/strowger/types"
)

var cmdRouteAddHTTP = &Command{
	Run:   runRouteAddHTTP,
	Usage: "route-add-http [-s <service>] [-c <tls-cert>] [-k <tls-key>] <domain>",
	Short: "add a HTTP route",
	Long:  `Add a HTTP route to an app`,
}

var routeHTTPService string
var tlsCertPath string
var tlsKeyPath string

func init() {
	cmdRouteAddHTTP.Flag.StringVarP(&routeHTTPService, "service", "s", "", "service name to route domain to (defaults to APPNAME-web)")
	cmdRouteAddHTTP.Flag.StringVarP(&tlsCertPath, "tls-cert", "c", "", "path to PEM encoded certificate for TLS, - for stdin")
	cmdRouteAddHTTP.Flag.StringVarP(&tlsKeyPath, "tls-key", "k", "", "path to PEM encoded private key for TLS, - for stdin")
}

func runRouteAddHTTP(cmd *Command, args []string, client *controller.Client) error {
	var tlsCert []byte
	var tlsKey []byte

	if len(args) != 1 {
		cmd.printUsage(true)
	}

	if routeHTTPService == "" {
		routeHTTPService = mustApp() + "-web"
	}

	if tlsCertPath != "" && tlsKeyPath != "" {
		var stdin []byte
		var err error

		if tlsCertPath == "-" || tlsKeyPath == "-" {
			stdin, err = ioutil.ReadAll(os.Stdin)
			if err != nil {
				return fmt.Errorf("Failed to read from stdin: %s", err)
			}
		}

		tlsCert, err = readPEM("CERTIFICATE", tlsCertPath, stdin)
		if err != nil {
			return errors.New("Failed to read TLS Cert")
		}
		tlsKey, err = readPEM("PRIVATE KEY", tlsKeyPath, stdin)
		if err != nil {
			return errors.New("Failed to read TLS Key")
		}
	} else if tlsCertPath != "" || tlsKeyPath != "" {
		return errors.New("Both the TLS certificate AND private key need to be specified")
	}

	hr := &strowger.HTTPRoute{
		Service: routeHTTPService,
		Domain:  args[0],
		TLSCert: string(tlsCert),
		TLSKey:  string(tlsKey),
	}
	route := hr.ToRoute()
	if err := client.CreateRoute(mustApp(), route); err != nil {
		return err
	}
	fmt.Println(route.ID)
	return nil
}

func readPEM(typ string, path string, stdin []byte) ([]byte, error) {
	if path == "-" {
		var buf bytes.Buffer
		var block *pem.Block
		for {
			block, stdin = pem.Decode(stdin)
			if block == nil {
				break
			}
			if block.Type == typ {
				pem.Encode(&buf, block)
			}
		}
		if buf.Len() > 0 {
			return buf.Bytes(), nil
		}
		return nil, errors.New("No PEM blocks found in stdin")
	}
	return ioutil.ReadFile(path)
}

var cmdRoutes = &Command{
	Run:   runRoutes,
	Usage: "routes",
	Short: "list routes",
	Long:  `list routes for application"`,
}

func runRoutes(cmd *Command, args []string, client *controller.Client) error {
	if len(args) != 0 {
		cmd.printUsage(true)
	}
	routes, err := client.RouteList(mustApp())
	if err != nil {
		return err
	}

	w := tabWriter()
	defer w.Flush()

	var route, protocol, service string
	listRec(w, "ROUTE", "SERVICE", "ID")
	for _, k := range routes {
		switch k.Type {
		case "tcp":
			protocol = "tcp"
			route = strconv.Itoa(k.TCPRoute().Port)
			service = k.TCPRoute().Service
		case "http":
			route = k.HTTPRoute().Domain
			service = k.TCPRoute().Service
			if k.HTTPRoute().TLSCert == "" {
				protocol = "http"
			} else {
				protocol = "https"
			}
		}
		listRec(w, protocol+":"+route, service, k.ID)
	}
	return nil
}

var cmdRouteRemove = &Command{
	Run:   runRouteRemove,
	Usage: "route-remove <id>",
	Short: "remove a route",
	Long:  "Command route-remove removes a route from the Flynn controller.",
}

func runRouteRemove(cmd *Command, args []string, client *controller.Client) error {
	if len(args) != 1 {
		cmd.printUsage(true)
	}
	routeID := args[0]

	if err := client.DeleteRoute(mustApp(), routeID); err != nil {
		return err
	}
	fmt.Printf("Route %s removed.\n", routeID)
	return nil
}
<commit_msg>cli: Add flag to enable sticky HTTP routing<commit_after>package main

import (
	"bytes"
	"encoding/pem"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strconv"

	"github.com/flynn/flynn-controller/client"
	"github.com/flynn/strowger/types"
)

var cmdRouteAddHTTP = &Command{
	Run:   runRouteAddHTTP,
	Usage: "route-add-http [-s <service>] [-c <tls-cert>] [-k <tls-key>] <domain>",
	Short: "add a HTTP route",
	Long:  `Add a HTTP route to an app`,
}

var routeHTTPService string
var tlsCertPath string
var tlsKeyPath string
var sticky bool

func init() {
	cmdRouteAddHTTP.Flag.StringVarP(&routeHTTPService, "service", "s", "", "service name to route domain to (defaults to APPNAME-web)")
	cmdRouteAddHTTP.Flag.StringVarP(&tlsCertPath, "tls-cert", "c", "", "path to PEM encoded certificate for TLS, - for stdin")
	cmdRouteAddHTTP.Flag.StringVarP(&tlsKeyPath, "tls-key", "k", "", "path to PEM encoded private key for TLS, - for stdin")
	cmdRouteAddHTTP.Flag.BoolVarP(&sticky, "sticky", "s", false, "enable cookie-based sticky routing")
}

func runRouteAddHTTP(cmd *Command, args []string, client *controller.Client) error {
	var tlsCert []byte
	var tlsKey []byte

	if len(args) != 1 {
		cmd.printUsage(true)
	}

	if routeHTTPService == "" {
		routeHTTPService = mustApp() + "-web"
	}

	if tlsCertPath != "" && tlsKeyPath != "" {
		var stdin []byte
		var err error

		if tlsCertPath == "-" || tlsKeyPath == "-" {
			stdin, err = ioutil.ReadAll(os.Stdin)
			if err != nil {
				return fmt.Errorf("Failed to read from stdin: %s", err)
			}
		}

		tlsCert, err = readPEM("CERTIFICATE", tlsCertPath, stdin)
		if err != nil {
			return errors.New("Failed to read TLS Cert")
		}
		tlsKey, err = readPEM("PRIVATE KEY", tlsKeyPath, stdin)
		if err != nil {
			return errors.New("Failed to read TLS Key")
		}
	} else if tlsCertPath != "" || tlsKeyPath != "" {
		return errors.New("Both the TLS certificate AND private key need to be specified")
	}

	hr := &strowger.HTTPRoute{
		Service: routeHTTPService,
		Domain:  args[0],
		TLSCert: string(tlsCert),
		TLSKey:  string(tlsKey),
		Sticky:  sticky,
	}
	route := hr.ToRoute()
	if err := client.CreateRoute(mustApp(), route); err != nil {
		return err
	}
	fmt.Println(route.ID)
	return nil
}

func readPEM(typ string, path string, stdin []byte) ([]byte, error) {
	if path == "-" {
		var buf bytes.Buffer
		var block *pem.Block
		for {
			block, stdin = pem.Decode(stdin)
			if block == nil {
				break
			}
			if block.Type == typ {
				pem.Encode(&buf, block)
			}
		}
		if buf.Len() > 0 {
			return buf.Bytes(), nil
		}
		return nil, errors.New("No PEM blocks found in stdin")
	}
	return ioutil.ReadFile(path)
}

var cmdRoutes = &Command{
	Run:   runRoutes,
	Usage: "routes",
	Short: "list routes",
	Long:  `list routes for application"`,
}

func runRoutes(cmd *Command, args []string, client *controller.Client) error {
	if len(args) != 0 {
		cmd.printUsage(true)
	}
	routes, err := client.RouteList(mustApp())
	if err != nil {
		return err
	}

	w := tabWriter()
	defer w.Flush()

	var route, protocol, service string
	listRec(w, "ROUTE", "SERVICE", "ID")
	for _, k := range routes {
		switch k.Type {
		case "tcp":
			protocol = "tcp"
			route = strconv.Itoa(k.TCPRoute().Port)
			service = k.TCPRoute().Service
		case "http":
			route = k.HTTPRoute().Domain
			service = k.TCPRoute().Service
			if k.HTTPRoute().TLSCert == "" {
				protocol = "http"
			} else {
				protocol = "https"
			}
		}
		listRec(w, protocol+":"+route, service, k.ID)
	}
	return nil
}

var cmdRouteRemove = &Command{
	Run:   runRouteRemove,
	Usage: "route-remove <id>",
	Short: "remove a route",
	Long:  "Command route-remove removes a route from the Flynn controller.",
}

func runRouteRemove(cmd *Command, args []string, client *controller.Client) error {
	if len(args) != 1 {
		cmd.printUsage(true)
	}
	routeID := args[0]

	if err := client.DeleteRoute(mustApp(), routeID); err != nil {
		return err
	}
	fmt.Printf("Route %s removed.\n", routeID)
	return nil
}
<|endoftext|>