mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-06-27 02:59:24 +00:00
Compare commits
122 Commits
Author | SHA1 | Date | |
---|---|---|---|
6f131250f1 | |||
221a63d980 | |||
d02eda147c | |||
9b25716136 | |||
6628a47f23 | |||
ec0e6bc3f8 | |||
d2c02be3a0 | |||
594492fbdd | |||
bd9ea7a88d | |||
51327a4056 | |||
33bd60528b | |||
7e54474111 | |||
e307069d62 | |||
91db63294c | |||
fd04e08c9c | |||
6576409d60 | |||
045cb2058c | |||
d03afc12fd | |||
48799a3cff | |||
dba259e9f1 | |||
07885f5810 | |||
696c518550 | |||
411ef2691c | |||
fc6074ea9f | |||
ab1670e2ce | |||
9142a33bbf | |||
f6eefa4ecc | |||
f1db166ac4 | |||
887c2bc56d | |||
f0738a93c3 | |||
75381c2c6e | |||
bf0b9959d1 | |||
406a54b597 | |||
be04d1a862 | |||
85b2d5a124 | |||
521a7ed7b0 | |||
529b188164 | |||
8d307d8134 | |||
8c675b52bc | |||
aa51aa2aa0 | |||
86865c6da5 | |||
45296100df | |||
1605fbc012 | |||
c6c92e273d | |||
467b373c43 | |||
72ce7f06e9 | |||
346a7284f7 | |||
ee4ac67081 | |||
5a93d14d75 | |||
96a47a60ad | |||
b24a47ad7f | |||
cd1fd1bb7c | |||
d44df7b6e6 | |||
9d1ac0c84b | |||
76af9cba5a | |||
b69fc30902 | |||
c3174f4de9 | |||
99ce68e9ba | |||
0cf73673a9 | |||
08f442dc7b | |||
8a8b95228c | |||
31a752fa21 | |||
a83831e68d | |||
a12a8d4fe2 | |||
e57f3a7e6c | |||
68fbed9281 | |||
8bfaa007d5 | |||
76360f89c1 | |||
d525230abd | |||
b4aa637d41 | |||
7c4334d0de | |||
062be8d7c9 | |||
db25ee59c5 | |||
4b0bc6d0bf | |||
8c0b04b995 | |||
e5989adf92 | |||
9e5da2f9d7 | |||
a284a228a3 | |||
2133e0d1be | |||
a6f37f1d61 | |||
9de9151826 | |||
fdd5ada98c | |||
80fcf18e24 | |||
ab94b5ca7a | |||
8d2ce56c37 | |||
1ec324354b | |||
16be6601c8 | |||
98027446c8 | |||
f2f1d874e1 | |||
25a72113b1 | |||
79c4ad5015 | |||
e24f1c7c87 | |||
dbf8a326d5 | |||
0bc9c70c66 | |||
594d2155e3 | |||
20dbd71306 | |||
6a727b9723 | |||
02a5bc096f | |||
2110db6f0c | |||
2bac867382 | |||
5fbd8a3be0 | |||
ad6440b603 | |||
064b6a915f | |||
1578ebb0e2 | |||
73525a4bbc | |||
d62f49d1fc | |||
63b88e77f2 | |||
3d8f15c20b | |||
cac5d56d60 | |||
bd2a672c14 | |||
82396e73f5 | |||
ba928b169d | |||
4fed720f97 | |||
78238c85d4 | |||
4f2ae7b73f | |||
f82a9cc7ac | |||
cce7624ab8 | |||
c5ecd09172 | |||
7b21c1c2f4 | |||
f8714d81f5 | |||
8622656005 | |||
52237fadb6 |
26
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve. (Check the FAQ on the wiki first)
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots/debug logs**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
Use logs from running `matterbridge -debug` if possible.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- OS: [e.g. linux]
|
||||
- Matterbridge version: output of `matterbridge -version`
|
||||
- If self compiled: output of `git rev-parse HEAD`
|
||||
|
||||
**Additional context**
|
||||
Please add your configuration file (be sure to exclude or anonymize private data (tokens/passwords))
|
17
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
@ -1,7 +1,7 @@
|
||||
language: go
|
||||
go:
|
||||
#- 1.7.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
# - tip
|
||||
|
||||
# we have everything vendored
|
||||
@ -36,13 +36,15 @@ before_script:
|
||||
script:
|
||||
#- test -z $(gofmt -s -l $GO_FILES) # Fail if a .go file hasn't been formatted with gofmt
|
||||
- go test -v -race $PKGS # Run all the tests with the race detector enabled
|
||||
- go vet $PKGS # go vet is the official Go static analyzer
|
||||
# - go vet $PKGS # go vet is the official Go static analyzer
|
||||
- megacheck $PKGS # "go vet on steroids" + linter
|
||||
- /bin/bash ci/bintray.sh
|
||||
#- golint -set_exit_status $PKGS # one last linter
|
||||
|
||||
deploy:
|
||||
provider: bintray
|
||||
edge:
|
||||
branch: v1.8.47
|
||||
file: ci/deploy.json
|
||||
user: 42wim
|
||||
key:
|
||||
|
24
README.md
24
README.md
@ -1,16 +1,19 @@
|
||||
# matterbridge
|
||||
Click on one of the badges below to join the chat
|
||||
|
||||
[](https://gitter.im/42wim/matterbridge) [](https://webchat.freenode.net/?channels=matterbridgechat) [](https://discord.gg/AkKPtrQ) [](https://riot.im/app/#/room/#matterbridge:matrix.org) [](https://join.slack.com/matterbridgechat/shared_invite/MjEwODMxNjU1NDMwLTE0OTk2MTU3NTMtMzZkZmRiNDZhOA) [](https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e) [](https://inverse.chat) [](https://www.twitch.tv/matterbridge)
|
||||
[](https://gitter.im/42wim/matterbridge) [](https://webchat.freenode.net/?channels=matterbridgechat) [](https://discord.gg/AkKPtrQ) [](https://riot.im/app/#/room/#matterbridge:matrix.org) [](https://join.slack.com/matterbridgechat/shared_invite/MjEwODMxNjU1NDMwLTE0OTk2MTU3NTMtMzZkZmRiNDZhOA) [](https://framateam.org/signup_user_complete/?id=tfqm33ggop8x3qgu4boeieta6e) [](https://inverse.chat) [](https://www.twitch.tv/matterbridge) [](https://matterbridge.zulipchat.com/register/)
|
||||
|
||||
[](https://github.com/42wim/matterbridge/releases/latest) [](https://bintray.com/42wim/nightly/Matterbridge/_latestVersion)
|
||||
|
||||

|
||||

|
||||
|
||||
Simple bridge between Mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat, Hipchat(via xmpp), Matrix, Steam and ssh-chat
|
||||
Has a REST API.
|
||||
Simple bridge between IRC, XMPP, Gitter, Mattermost, Slack, Discord, Telegram, Rocket.Chat, Hipchat(via xmpp), Matrix, Steam, ssh-chat and Zulip
|
||||
Has a REST API.
|
||||
Minecraft server chat support via [MatterLink](https://github.com/elytra/MatterLink)
|
||||
|
||||
**Mattermost isn't required to run matterbridge. It bridges between any supported protocol.**
|
||||
(The name matterbridge is a remnant when it was only bridging mattermost)
|
||||
|
||||
# Table of Contents
|
||||
* [Features](https://github.com/42wim/matterbridge/wiki/Features)
|
||||
* [Requirements](#requirements)
|
||||
@ -46,7 +49,7 @@ Used by at least 2 projects. Feel free to make a PR to add your project to this
|
||||
|
||||
# Requirements
|
||||
Accounts to one of the supported bridges
|
||||
* [Mattermost](https://github.com/mattermost/platform/) 3.8.x - 3.10.x, 4.x
|
||||
* [Mattermost](https://github.com/mattermost/platform/) 3.8.x - 3.10.x, 4.x, 5.x
|
||||
* [IRC](http://www.mirc.com/servers.html)
|
||||
* [XMPP](https://jabber.org)
|
||||
* [Gitter](https://gitter.im)
|
||||
@ -59,17 +62,20 @@ Accounts to one of the supported bridges
|
||||
* [Steam](https://store.steampowered.com/)
|
||||
* [Twitch](https://twitch.tv)
|
||||
* [Ssh-chat](https://github.com/shazow/ssh-chat)
|
||||
* [Zulip](https://zulipchat.com)
|
||||
|
||||
# Screenshots
|
||||
See https://github.com/42wim/matterbridge/wiki
|
||||
|
||||
# Installing
|
||||
## Binaries
|
||||
* Latest stable release [v1.8.0](https://github.com/42wim/matterbridge/releases/latest)
|
||||
* Latest stable release [v1.11.0](https://github.com/42wim/matterbridge/releases/latest)
|
||||
* Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
|
||||
|
||||
## Building
|
||||
Go 1.8+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed, including setting up your [GOPATH] (https://golang.org/doc/code.html#GOPATH)
|
||||
Go 1.8+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed, including setting up your [GOPATH](https://golang.org/doc/code.html#GOPATH).
|
||||
|
||||
After Go is setup, download matterbridge to your $GOPATH directory.
|
||||
|
||||
```
|
||||
cd $GOPATH
|
||||
@ -181,11 +187,14 @@ Want to tip ?
|
||||
* btc: 1N7cKHj5SfqBHBzDJ6kad4BzeqUBBS2zhs
|
||||
|
||||
# Thanks
|
||||
[](https://www.digitalocean.com/) for sponsoring demo/testing droplets.
|
||||
|
||||
Matterbridge wouldn't exist without these libraries:
|
||||
* discord - https://github.com/bwmarrin/discordgo
|
||||
* echo - https://github.com/labstack/echo
|
||||
* gitter - https://github.com/sromku/go-gitter
|
||||
* gops - https://github.com/google/gops
|
||||
* gozulipbot - https://github.com/ifo/gozulipbot
|
||||
* irc - https://github.com/lrstanley/girc
|
||||
* mattermost - https://github.com/mattermost/platform
|
||||
* matrix - https://github.com/matrix-org/gomatrix
|
||||
@ -193,3 +202,4 @@ Matterbridge wouldn't exist without these libraries:
|
||||
* steam - https://github.com/Philipp15b/go-steam
|
||||
* telegram - https://github.com/go-telegram-bot-api/telegram-bot-api
|
||||
* xmpp - https://github.com/mattn/go-xmpp
|
||||
* zulip - https://github.com/ifo/gozulipbot
|
||||
|
@ -2,20 +2,21 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/middleware"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/zfjagann/golang-ring"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/middleware"
|
||||
"github.com/zfjagann/golang-ring"
|
||||
)
|
||||
|
||||
type Api struct {
|
||||
Messages ring.Ring
|
||||
sync.RWMutex
|
||||
*config.BridgeConfig
|
||||
*bridge.Config
|
||||
}
|
||||
|
||||
type ApiMessage struct {
|
||||
@ -26,34 +27,27 @@ type ApiMessage struct {
|
||||
Gateway string `json:"gateway"`
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "api"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": protocol})
|
||||
}
|
||||
|
||||
func New(cfg *config.BridgeConfig) *Api {
|
||||
b := &Api{BridgeConfig: cfg}
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
b := &Api{Config: cfg}
|
||||
e := echo.New()
|
||||
e.HideBanner = true
|
||||
e.HidePort = true
|
||||
b.Messages = ring.Ring{}
|
||||
b.Messages.SetCapacity(b.Config.Buffer)
|
||||
if b.Config.Token != "" {
|
||||
b.Messages.SetCapacity(b.GetInt("Buffer"))
|
||||
if b.GetString("Token") != "" {
|
||||
e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) {
|
||||
return key == b.Config.Token, nil
|
||||
return key == b.GetString("Token"), nil
|
||||
}))
|
||||
}
|
||||
e.GET("/api/messages", b.handleMessages)
|
||||
e.GET("/api/stream", b.handleStream)
|
||||
e.POST("/api/message", b.handlePostMessage)
|
||||
go func() {
|
||||
if b.Config.BindAddress == "" {
|
||||
flog.Fatalf("No BindAddress configured.")
|
||||
if b.GetString("BindAddress") == "" {
|
||||
b.Log.Fatalf("No BindAddress configured.")
|
||||
}
|
||||
flog.Infof("Listening on %s", b.Config.BindAddress)
|
||||
flog.Fatal(e.Start(b.Config.BindAddress))
|
||||
b.Log.Infof("Listening on %s", b.GetString("BindAddress"))
|
||||
b.Log.Fatal(e.Start(b.GetString("BindAddress")))
|
||||
}()
|
||||
return b
|
||||
}
|
||||
@ -92,7 +86,7 @@ func (b *Api) handlePostMessage(c echo.Context) error {
|
||||
message.Account = b.Account
|
||||
message.ID = ""
|
||||
message.Timestamp = time.Now()
|
||||
flog.Debugf("Sending message from %s on %s to gateway", message.Username, "api")
|
||||
b.Log.Debugf("Sending message from %s on %s to gateway", message.Username, "api")
|
||||
b.Remote <- message
|
||||
return c.JSON(http.StatusOK, message)
|
||||
}
|
||||
|
110
bridge/bridge.go
110
bridge/bridge.go
@ -1,19 +1,7 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"github.com/42wim/matterbridge/bridge/api"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/discord"
|
||||
"github.com/42wim/matterbridge/bridge/gitter"
|
||||
"github.com/42wim/matterbridge/bridge/irc"
|
||||
"github.com/42wim/matterbridge/bridge/matrix"
|
||||
"github.com/42wim/matterbridge/bridge/mattermost"
|
||||
"github.com/42wim/matterbridge/bridge/rocketchat"
|
||||
"github.com/42wim/matterbridge/bridge/slack"
|
||||
"github.com/42wim/matterbridge/bridge/sshchat"
|
||||
"github.com/42wim/matterbridge/bridge/steam"
|
||||
"github.com/42wim/matterbridge/bridge/telegram"
|
||||
"github.com/42wim/matterbridge/bridge/xmpp"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"strings"
|
||||
@ -27,22 +15,28 @@ type Bridger interface {
|
||||
}
|
||||
|
||||
type Bridge struct {
|
||||
Config config.Protocol
|
||||
Bridger
|
||||
Name string
|
||||
Account string
|
||||
Protocol string
|
||||
Channels map[string]config.ChannelInfo
|
||||
Joined map[string]bool
|
||||
Log *log.Entry
|
||||
Config *config.Config
|
||||
General *config.Protocol
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": "bridge"})
|
||||
type Config struct {
|
||||
// General *config.Protocol
|
||||
Remote chan config.Message
|
||||
Log *log.Entry
|
||||
*Bridge
|
||||
}
|
||||
|
||||
func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Bridge {
|
||||
// Factory is the factory function to create a bridge
|
||||
type Factory func(*Config) Bridger
|
||||
|
||||
func New(bridge *config.Bridge) *Bridge {
|
||||
b := new(Bridge)
|
||||
b.Channels = make(map[string]config.ChannelInfo)
|
||||
accInfo := strings.Split(bridge.Account, ".")
|
||||
@ -52,49 +46,6 @@ func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Brid
|
||||
b.Protocol = protocol
|
||||
b.Account = bridge.Account
|
||||
b.Joined = make(map[string]bool)
|
||||
bridgeConfig := &config.BridgeConfig{General: &cfg.General, Account: bridge.Account, Remote: c}
|
||||
|
||||
// override config from environment
|
||||
config.OverrideCfgFromEnv(cfg, protocol, name)
|
||||
switch protocol {
|
||||
case "mattermost":
|
||||
bridgeConfig.Config = cfg.Mattermost[name]
|
||||
b.Bridger = bmattermost.New(bridgeConfig)
|
||||
case "irc":
|
||||
bridgeConfig.Config = cfg.IRC[name]
|
||||
b.Bridger = birc.New(bridgeConfig)
|
||||
case "gitter":
|
||||
bridgeConfig.Config = cfg.Gitter[name]
|
||||
b.Bridger = bgitter.New(bridgeConfig)
|
||||
case "slack":
|
||||
bridgeConfig.Config = cfg.Slack[name]
|
||||
b.Bridger = bslack.New(bridgeConfig)
|
||||
case "xmpp":
|
||||
bridgeConfig.Config = cfg.Xmpp[name]
|
||||
b.Bridger = bxmpp.New(bridgeConfig)
|
||||
case "discord":
|
||||
bridgeConfig.Config = cfg.Discord[name]
|
||||
b.Bridger = bdiscord.New(bridgeConfig)
|
||||
case "telegram":
|
||||
bridgeConfig.Config = cfg.Telegram[name]
|
||||
b.Bridger = btelegram.New(bridgeConfig)
|
||||
case "rocketchat":
|
||||
bridgeConfig.Config = cfg.Rocketchat[name]
|
||||
b.Bridger = brocketchat.New(bridgeConfig)
|
||||
case "matrix":
|
||||
bridgeConfig.Config = cfg.Matrix[name]
|
||||
b.Bridger = bmatrix.New(bridgeConfig)
|
||||
case "steam":
|
||||
bridgeConfig.Config = cfg.Steam[name]
|
||||
b.Bridger = bsteam.New(bridgeConfig)
|
||||
case "sshchat":
|
||||
bridgeConfig.Config = cfg.Sshchat[name]
|
||||
b.Bridger = bsshchat.New(bridgeConfig)
|
||||
case "api":
|
||||
bridgeConfig.Config = cfg.Api[name]
|
||||
b.Bridger = api.New(bridgeConfig)
|
||||
}
|
||||
b.Config = bridgeConfig.Config
|
||||
return b
|
||||
}
|
||||
|
||||
@ -106,7 +57,7 @@ func (b *Bridge) JoinChannels() error {
|
||||
func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map[string]bool) error {
|
||||
for ID, channel := range channels {
|
||||
if !exists[ID] {
|
||||
flog.Infof("%s: joining %s (ID: %s)", b.Account, channel.Name, ID)
|
||||
b.Log.Infof("%s: joining %s (ID: %s)", b.Account, channel.Name, ID)
|
||||
err := b.JoinChannel(channel)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -116,3 +67,38 @@ func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bridge) GetBool(key string) bool {
|
||||
if b.Config.GetBool(b.Account + "." + key) {
|
||||
return b.Config.GetBool(b.Account + "." + key)
|
||||
}
|
||||
return b.Config.GetBool("general." + key)
|
||||
}
|
||||
|
||||
func (b *Bridge) GetInt(key string) int {
|
||||
if b.Config.GetInt(b.Account+"."+key) != 0 {
|
||||
return b.Config.GetInt(b.Account + "." + key)
|
||||
}
|
||||
return b.Config.GetInt("general." + key)
|
||||
}
|
||||
|
||||
func (b *Bridge) GetString(key string) string {
|
||||
if b.Config.GetString(b.Account+"."+key) != "" {
|
||||
return b.Config.GetString(b.Account + "." + key)
|
||||
}
|
||||
return b.Config.GetString("general." + key)
|
||||
}
|
||||
|
||||
func (b *Bridge) GetStringSlice(key string) []string {
|
||||
if len(b.Config.GetStringSlice(b.Account+"."+key)) != 0 {
|
||||
return b.Config.GetStringSlice(b.Account + "." + key)
|
||||
}
|
||||
return b.Config.GetStringSlice("general." + key)
|
||||
}
|
||||
|
||||
func (b *Bridge) GetStringSlice2D(key string) [][]string {
|
||||
if len(b.Config.GetStringSlice2D(b.Account+"."+key)) != 0 {
|
||||
return b.Config.GetStringSlice2D(b.Account + "." + key)
|
||||
}
|
||||
return b.Config.GetStringSlice2D("general." + key)
|
||||
}
|
||||
|
@ -1,12 +1,16 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/BurntSushi/toml"
|
||||
"log"
|
||||
"bytes"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
prefixed "github.com/x-cray/logrus-prefixed-formatter"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -59,7 +63,9 @@ type Protocol struct {
|
||||
BindAddress string // mattermost, slack // DEPRECATED
|
||||
Buffer int // api
|
||||
Charset string // irc
|
||||
ColorNicks bool // only irc for now
|
||||
Debug bool // general
|
||||
DebugLevel int // only for irc now
|
||||
EditSuffix string // mattermost, slack, discord, telegram, gitter
|
||||
EditDisable bool // mattermost, slack, discord, telegram, gitter
|
||||
IconURL string // mattermost, slack
|
||||
@ -68,6 +74,8 @@ type Protocol struct {
|
||||
Jid string // xmpp
|
||||
Label string // all protocols
|
||||
Login string // mattermost, matrix
|
||||
MediaDownloadBlackList []string
|
||||
MediaDownloadPath string // Basically MediaServerUpload, but instead of uploading it, just write it to a file on the same server.
|
||||
MediaDownloadSize int // all protocols
|
||||
MediaServerDownload string
|
||||
MediaServerUpload string
|
||||
@ -85,10 +93,13 @@ type Protocol struct {
|
||||
NickServPassword string // IRC
|
||||
NicksPerRow int // mattermost, slack
|
||||
NoHomeServerSuffix bool // matrix
|
||||
NoSendJoinPart bool // all protocols
|
||||
NoTLS bool // mattermost
|
||||
Password string // IRC,mattermost,XMPP,matrix
|
||||
PrefixMessagesWithNick bool // mattemost, slack
|
||||
Protocol string // all protocols
|
||||
QuoteDisable bool // telegram
|
||||
QuoteFormat string // telegram
|
||||
RejoinDelay int // IRC
|
||||
ReplaceMessages [][]string // all protocols
|
||||
ReplaceNicks [][]string // all protocols
|
||||
@ -101,6 +112,7 @@ type Protocol struct {
|
||||
StripNick bool // all protocols
|
||||
Team string // mattermost
|
||||
Token string // gitter, slack, discord, api
|
||||
Topic string // zulip
|
||||
URL string // mattermost, slack // DEPRECATED
|
||||
UseAPI bool // mattermost, slack
|
||||
UseSASL bool // IRC
|
||||
@ -114,7 +126,7 @@ type Protocol struct {
|
||||
}
|
||||
|
||||
type ChannelOptions struct {
|
||||
Key string // irc
|
||||
Key string // irc, xmpp
|
||||
WebhookURL string // discord
|
||||
}
|
||||
|
||||
@ -140,9 +152,9 @@ type SameChannelGateway struct {
|
||||
Accounts []string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
type ConfigValues struct {
|
||||
Api map[string]Protocol
|
||||
IRC map[string]Protocol
|
||||
Irc map[string]Protocol
|
||||
Mattermost map[string]Protocol
|
||||
Matrix map[string]Protocol
|
||||
Slack map[string]Protocol
|
||||
@ -153,90 +165,117 @@ type Config struct {
|
||||
Telegram map[string]Protocol
|
||||
Rocketchat map[string]Protocol
|
||||
Sshchat map[string]Protocol
|
||||
Zulip map[string]Protocol
|
||||
General Protocol
|
||||
Gateway []Gateway
|
||||
SameChannelGateway []SameChannelGateway
|
||||
}
|
||||
|
||||
type BridgeConfig struct {
|
||||
Config Protocol
|
||||
General *Protocol
|
||||
Account string
|
||||
Remote chan Message
|
||||
type Config struct {
|
||||
v *viper.Viper
|
||||
*ConfigValues
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func NewConfig(cfgfile string) *Config {
|
||||
var cfg Config
|
||||
if _, err := toml.DecodeFile(cfgfile, &cfg); err != nil {
|
||||
log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false})
|
||||
flog := log.WithFields(log.Fields{"prefix": "config"})
|
||||
var cfg ConfigValues
|
||||
viper.SetConfigType("toml")
|
||||
viper.SetConfigFile(cfgfile)
|
||||
viper.SetEnvPrefix("matterbridge")
|
||||
viper.AddConfigPath(".")
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
viper.AutomaticEnv()
|
||||
f, err := os.Open(cfgfile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fail := false
|
||||
for k, v := range cfg.Mattermost {
|
||||
res := Deprecated(v, "mattermost."+k)
|
||||
if res {
|
||||
fail = res
|
||||
}
|
||||
err = viper.ReadConfig(f)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for k, v := range cfg.Slack {
|
||||
res := Deprecated(v, "slack."+k)
|
||||
if res {
|
||||
fail = res
|
||||
}
|
||||
}
|
||||
for k, v := range cfg.Rocketchat {
|
||||
res := Deprecated(v, "rocketchat."+k)
|
||||
if res {
|
||||
fail = res
|
||||
}
|
||||
}
|
||||
if fail {
|
||||
log.Fatalf("Fix your config. Please see changelog for more information")
|
||||
err = viper.Unmarshal(&cfg)
|
||||
if err != nil {
|
||||
log.Fatal("blah", err)
|
||||
}
|
||||
mycfg := new(Config)
|
||||
mycfg.v = viper.GetViper()
|
||||
if cfg.General.MediaDownloadSize == 0 {
|
||||
cfg.General.MediaDownloadSize = 1000000
|
||||
}
|
||||
return &cfg
|
||||
viper.WatchConfig()
|
||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||
flog.Println("Config file changed:", e.Name)
|
||||
})
|
||||
|
||||
mycfg.ConfigValues = &cfg
|
||||
return mycfg
|
||||
}
|
||||
|
||||
func OverrideCfgFromEnv(cfg *Config, protocol string, account string) {
|
||||
var protoCfg Protocol
|
||||
val := reflect.ValueOf(cfg).Elem()
|
||||
// loop over the Config struct
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
typeField := val.Type().Field(i)
|
||||
// look for the protocol map (both lowercase)
|
||||
if strings.ToLower(typeField.Name) == protocol {
|
||||
// get the Protocol struct from the map
|
||||
data := val.Field(i).MapIndex(reflect.ValueOf(account))
|
||||
protoCfg = data.Interface().(Protocol)
|
||||
protoStruct := reflect.ValueOf(&protoCfg).Elem()
|
||||
// loop over the found protocol struct
|
||||
for i := 0; i < protoStruct.NumField(); i++ {
|
||||
typeField := protoStruct.Type().Field(i)
|
||||
// build our environment key (eg MATTERBRIDGE_MATTERMOST_WORK_LOGIN)
|
||||
key := "matterbridge_" + protocol + "_" + account + "_" + typeField.Name
|
||||
key = strings.ToUpper(key)
|
||||
// search the environment
|
||||
res := os.Getenv(key)
|
||||
// if it exists and the current field is a string
|
||||
// then update the current field
|
||||
if res != "" {
|
||||
fieldVal := protoStruct.Field(i)
|
||||
if fieldVal.Kind() == reflect.String {
|
||||
log.Printf("config: overriding %s from env with %s\n", key, res)
|
||||
fieldVal.Set(reflect.ValueOf(res))
|
||||
}
|
||||
}
|
||||
}
|
||||
// update the map with the modified Protocol (cfg.Protocol[account] = Protocol)
|
||||
val.Field(i).SetMapIndex(reflect.ValueOf(account), reflect.ValueOf(protoCfg))
|
||||
break
|
||||
}
|
||||
func NewConfigFromString(input []byte) *Config {
|
||||
var cfg ConfigValues
|
||||
viper.SetConfigType("toml")
|
||||
err := viper.ReadConfig(bytes.NewBuffer(input))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = viper.Unmarshal(&cfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
mycfg := new(Config)
|
||||
mycfg.v = viper.GetViper()
|
||||
mycfg.ConfigValues = &cfg
|
||||
return mycfg
|
||||
}
|
||||
|
||||
func GetIconURL(msg *Message, cfg *Protocol) string {
|
||||
iconURL := cfg.IconURL
|
||||
func (c *Config) GetBool(key string) bool {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
// log.Debugf("getting bool %s = %#v", key, c.v.GetBool(key))
|
||||
return c.v.GetBool(key)
|
||||
}
|
||||
|
||||
func (c *Config) GetInt(key string) int {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
// log.Debugf("getting int %s = %d", key, c.v.GetInt(key))
|
||||
return c.v.GetInt(key)
|
||||
}
|
||||
|
||||
func (c *Config) GetString(key string) string {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
// log.Debugf("getting String %s = %s", key, c.v.GetString(key))
|
||||
return c.v.GetString(key)
|
||||
}
|
||||
|
||||
func (c *Config) GetStringSlice(key string) []string {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
// log.Debugf("getting StringSlice %s = %#v", key, c.v.GetStringSlice(key))
|
||||
return c.v.GetStringSlice(key)
|
||||
}
|
||||
|
||||
func (c *Config) GetStringSlice2D(key string) [][]string {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
result := [][]string{}
|
||||
if res, ok := c.v.Get(key).([]interface{}); ok {
|
||||
for _, entry := range res {
|
||||
result2 := []string{}
|
||||
for _, entry2 := range entry.([]interface{}) {
|
||||
result2 = append(result2, entry2.(string))
|
||||
}
|
||||
result = append(result, result2)
|
||||
}
|
||||
return result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func GetIconURL(msg *Message, iconURL string) string {
|
||||
info := strings.Split(msg.Account, ".")
|
||||
protocol := info[0]
|
||||
name := info[1]
|
||||
@ -245,17 +284,3 @@ func GetIconURL(msg *Message, cfg *Protocol) string {
|
||||
iconURL = strings.Replace(iconURL, "{PROTOCOL}", protocol, -1)
|
||||
return iconURL
|
||||
}
|
||||
|
||||
func Deprecated(cfg Protocol, account string) bool {
|
||||
if cfg.BindAddress != "" {
|
||||
log.Printf("ERROR: %s BindAddress is deprecated, you need to change it to WebhookBindAddress.", account)
|
||||
} else if cfg.URL != "" {
|
||||
log.Printf("ERROR: %s URL is deprecated, you need to change it to WebhookURL.", account)
|
||||
} else if cfg.UseAPI {
|
||||
log.Printf("ERROR: %s UseAPI is deprecated, it's enabled by default, please remove it from your config file.", account)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
//log.Fatalf("ERROR: Fix your config: %s", account)
|
||||
}
|
||||
|
@ -2,16 +2,18 @@ package bdiscord
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/bwmarrin/discordgo"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type bdiscord struct {
|
||||
type Bdiscord struct {
|
||||
c *discordgo.Session
|
||||
Channels []*discordgo.Channel
|
||||
Nick string
|
||||
@ -22,82 +24,74 @@ type bdiscord struct {
|
||||
webhookToken string
|
||||
channelInfoMap map[string]*config.ChannelInfo
|
||||
sync.RWMutex
|
||||
*config.BridgeConfig
|
||||
*bridge.Config
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "discord"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": protocol})
|
||||
}
|
||||
|
||||
func New(cfg *config.BridgeConfig) *bdiscord {
|
||||
b := &bdiscord{BridgeConfig: cfg}
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
b := &Bdiscord{Config: cfg}
|
||||
b.userMemberMap = make(map[string]*discordgo.Member)
|
||||
b.channelInfoMap = make(map[string]*config.ChannelInfo)
|
||||
if b.Config.WebhookURL != "" {
|
||||
flog.Debug("Configuring Discord Incoming Webhook")
|
||||
b.webhookID, b.webhookToken = b.splitURL(b.Config.WebhookURL)
|
||||
if b.GetString("WebhookURL") != "" {
|
||||
b.Log.Debug("Configuring Discord Incoming Webhook")
|
||||
b.webhookID, b.webhookToken = b.splitURL(b.GetString("WebhookURL"))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *bdiscord) Connect() error {
|
||||
func (b *Bdiscord) Connect() error {
|
||||
var err error
|
||||
flog.Info("Connecting")
|
||||
if b.Config.WebhookURL == "" {
|
||||
flog.Info("Connecting using token")
|
||||
var token string
|
||||
b.Log.Info("Connecting")
|
||||
if b.GetString("WebhookURL") == "" {
|
||||
b.Log.Info("Connecting using token")
|
||||
} else {
|
||||
flog.Info("Connecting using webhookurl (for posting) and token")
|
||||
b.Log.Info("Connecting using webhookurl (for posting) and token")
|
||||
}
|
||||
if !strings.HasPrefix(b.Config.Token, "Bot ") {
|
||||
b.Config.Token = "Bot " + b.Config.Token
|
||||
if !strings.HasPrefix(b.GetString("Token"), "Bot ") {
|
||||
token = "Bot " + b.GetString("Token")
|
||||
}
|
||||
b.c, err = discordgo.New(b.Config.Token)
|
||||
b.c, err = discordgo.New(token)
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
flog.Info("Connection succeeded")
|
||||
b.Log.Info("Connection succeeded")
|
||||
b.c.AddHandler(b.messageCreate)
|
||||
b.c.AddHandler(b.memberUpdate)
|
||||
b.c.AddHandler(b.messageUpdate)
|
||||
b.c.AddHandler(b.messageDelete)
|
||||
err = b.c.Open()
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
guilds, err := b.c.UserGuilds(100, "", "")
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
userinfo, err := b.c.User("@me")
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
b.Nick = userinfo.Username
|
||||
for _, guild := range guilds {
|
||||
if guild.Name == b.Config.Server {
|
||||
if guild.Name == b.GetString("Server") {
|
||||
b.Channels, err = b.c.GuildChannels(guild.ID)
|
||||
b.guildID = guild.ID
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, channel := range b.Channels {
|
||||
b.Log.Debugf("found channel %#v", channel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bdiscord) Disconnect() error {
|
||||
return nil
|
||||
func (b *Bdiscord) Disconnect() error {
|
||||
return b.c.Close()
|
||||
}
|
||||
|
||||
func (b *bdiscord) JoinChannel(channel config.ChannelInfo) error {
|
||||
func (b *Bdiscord) JoinChannel(channel config.ChannelInfo) error {
|
||||
b.channelInfoMap[channel.ID] = &channel
|
||||
idcheck := strings.Split(channel.Name, "ID:")
|
||||
if len(idcheck) > 1 {
|
||||
@ -106,103 +100,117 @@ func (b *bdiscord) JoinChannel(channel config.ChannelInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bdiscord) Send(msg config.Message) (string, error) {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
||||
b.Log.Debugf("=> Receiving %#v", msg)
|
||||
|
||||
channelID := b.getChannelID(msg.Channel)
|
||||
if channelID == "" {
|
||||
flog.Errorf("Could not find channelID for %v", msg.Channel)
|
||||
return "", nil
|
||||
return "", fmt.Errorf("Could not find channelID for %v", msg.Channel)
|
||||
}
|
||||
|
||||
// Make a action /me of the message
|
||||
if msg.Event == config.EVENT_USER_ACTION {
|
||||
msg.Text = "_" + msg.Text + "_"
|
||||
}
|
||||
|
||||
// use initial webhook
|
||||
wID := b.webhookID
|
||||
wToken := b.webhookToken
|
||||
|
||||
// check if have a channel specific webhook
|
||||
if ci, ok := b.channelInfoMap[msg.Channel+b.Account]; ok {
|
||||
if ci.Options.WebhookURL != "" {
|
||||
wID, wToken = b.splitURL(ci.Options.WebhookURL)
|
||||
}
|
||||
}
|
||||
|
||||
if wID == "" {
|
||||
flog.Debugf("Broadcasting using token (API)")
|
||||
if msg.Event == config.EVENT_MSG_DELETE {
|
||||
if msg.ID == "" {
|
||||
return "", nil
|
||||
}
|
||||
err := b.c.ChannelMessageDelete(channelID, msg.ID)
|
||||
return "", err
|
||||
// Use webhook to send the message
|
||||
if wID != "" {
|
||||
// skip events
|
||||
if msg.Event != "" {
|
||||
return "", nil
|
||||
}
|
||||
if msg.ID != "" {
|
||||
_, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text)
|
||||
return msg.ID, err
|
||||
}
|
||||
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.c.ChannelMessageSend(channelID, rmsg.Username+rmsg.Text)
|
||||
}
|
||||
// check if we have files to upload (from slack, telegram or mattermost)
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
var err error
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
files := []*discordgo.File{}
|
||||
files = append(files, &discordgo.File{fi.Name, "", bytes.NewReader(*fi.Data)})
|
||||
_, err = b.c.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{Content: msg.Username + fi.Comment, Files: files})
|
||||
if err != nil {
|
||||
flog.Errorf("file upload failed: %#v", err)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
b.Log.Debugf("Broadcasting using Webhook")
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.URL != "" {
|
||||
msg.Text += fi.URL + " "
|
||||
}
|
||||
}
|
||||
|
||||
res, err := b.c.ChannelMessageSend(channelID, msg.Username+msg.Text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.ID, err
|
||||
err := b.c.WebhookExecute(
|
||||
wID,
|
||||
wToken,
|
||||
true,
|
||||
&discordgo.WebhookParams{
|
||||
Content: msg.Text,
|
||||
Username: msg.Username,
|
||||
AvatarURL: msg.Avatar,
|
||||
})
|
||||
return "", err
|
||||
}
|
||||
flog.Debugf("Broadcasting using Webhook")
|
||||
err := b.c.WebhookExecute(
|
||||
wID,
|
||||
wToken,
|
||||
true,
|
||||
&discordgo.WebhookParams{
|
||||
Content: msg.Text,
|
||||
Username: msg.Username,
|
||||
AvatarURL: msg.Avatar,
|
||||
})
|
||||
return "", err
|
||||
|
||||
b.Log.Debugf("Broadcasting using token (API)")
|
||||
|
||||
// Delete message
|
||||
if msg.Event == config.EVENT_MSG_DELETE {
|
||||
if msg.ID == "" {
|
||||
return "", nil
|
||||
}
|
||||
err := b.c.ChannelMessageDelete(channelID, msg.ID)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Upload a file if it exists
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.c.ChannelMessageSend(channelID, rmsg.Username+rmsg.Text)
|
||||
}
|
||||
// check if we have files to upload (from slack, telegram or mattermost)
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
return b.handleUploadFile(&msg, channelID)
|
||||
}
|
||||
}
|
||||
|
||||
// Edit message
|
||||
if msg.ID != "" {
|
||||
_, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text)
|
||||
return msg.ID, err
|
||||
}
|
||||
|
||||
// Post normal message
|
||||
res, err := b.c.ChannelMessageSend(channelID, msg.Username+msg.Text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.ID, err
|
||||
}
|
||||
|
||||
func (b *bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) {
|
||||
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) {
|
||||
rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EVENT_MSG_DELETE, Text: config.EVENT_MSG_DELETE}
|
||||
rmsg.Channel = b.getChannelName(m.ChannelID)
|
||||
if b.UseChannelID {
|
||||
rmsg.Channel = "ID:" + m.ChannelID
|
||||
}
|
||||
flog.Debugf("Sending message from %s to gateway", b.Account)
|
||||
flog.Debugf("Message is %#v", rmsg)
|
||||
b.Log.Debugf("<= Sending message from %s to gateway", b.Account)
|
||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||
b.Remote <- rmsg
|
||||
}
|
||||
|
||||
func (b *bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) {
|
||||
if b.Config.EditDisable {
|
||||
func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) {
|
||||
if b.GetBool("EditDisable") {
|
||||
return
|
||||
}
|
||||
// only when message is actually edited
|
||||
if m.Message.EditedTimestamp != "" {
|
||||
flog.Debugf("Sending edit message")
|
||||
m.Content = m.Content + b.Config.EditSuffix
|
||||
b.Log.Debugf("Sending edit message")
|
||||
m.Content = m.Content + b.GetString("EditSuffix")
|
||||
b.messageCreate(s, (*discordgo.MessageCreate)(m))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
var err error
|
||||
|
||||
// not relay our own messages
|
||||
if m.Author.Username == b.Nick {
|
||||
return
|
||||
@ -212,70 +220,73 @@ func (b *bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
|
||||
return
|
||||
}
|
||||
|
||||
// add the url of the attachments to content
|
||||
if len(m.Attachments) > 0 {
|
||||
for _, attach := range m.Attachments {
|
||||
m.Content = m.Content + "\n" + attach.URL
|
||||
}
|
||||
}
|
||||
|
||||
var text string
|
||||
rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg", UserID: m.Author.ID, ID: m.ID}
|
||||
|
||||
if m.Content != "" {
|
||||
flog.Debugf("Receiving message %#v", m.Message)
|
||||
b.Log.Debugf("== Receiving event %#v", m.Message)
|
||||
m.Message.Content = b.stripCustomoji(m.Message.Content)
|
||||
m.Message.Content = b.replaceChannelMentions(m.Message.Content)
|
||||
text, err = m.ContentWithMoreMentionsReplaced(b.c)
|
||||
rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c)
|
||||
if err != nil {
|
||||
flog.Errorf("ContentWithMoreMentionsReplaced failed: %s", err)
|
||||
text = m.ContentWithMentionsReplaced()
|
||||
b.Log.Errorf("ContentWithMoreMentionsReplaced failed: %s", err)
|
||||
rmsg.Text = m.ContentWithMentionsReplaced()
|
||||
}
|
||||
}
|
||||
|
||||
rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg",
|
||||
UserID: m.Author.ID, ID: m.ID}
|
||||
|
||||
// set channel name
|
||||
rmsg.Channel = b.getChannelName(m.ChannelID)
|
||||
if b.UseChannelID {
|
||||
rmsg.Channel = "ID:" + m.ChannelID
|
||||
}
|
||||
|
||||
if !b.Config.UseUserName {
|
||||
// set username
|
||||
if !b.GetBool("UseUserName") {
|
||||
rmsg.Username = b.getNick(m.Author)
|
||||
} else {
|
||||
rmsg.Username = m.Author.Username
|
||||
}
|
||||
|
||||
if b.Config.ShowEmbeds && m.Message.Embeds != nil {
|
||||
// if we have embedded content add it to text
|
||||
if b.GetBool("ShowEmbeds") && m.Message.Embeds != nil {
|
||||
for _, embed := range m.Message.Embeds {
|
||||
text = text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n"
|
||||
rmsg.Text = rmsg.Text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
// no empty messages
|
||||
if text == "" {
|
||||
if rmsg.Text == "" {
|
||||
return
|
||||
}
|
||||
|
||||
text, ok := b.replaceAction(text)
|
||||
// do we have a /me action
|
||||
var ok bool
|
||||
rmsg.Text, ok = b.replaceAction(rmsg.Text)
|
||||
if ok {
|
||||
rmsg.Event = config.EVENT_USER_ACTION
|
||||
}
|
||||
|
||||
rmsg.Text = text
|
||||
flog.Debugf("Sending message from %s on %s to gateway", m.Author.Username, b.Account)
|
||||
flog.Debugf("Message is %#v", rmsg)
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", m.Author.Username, b.Account)
|
||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||
b.Remote <- rmsg
|
||||
}
|
||||
|
||||
func (b *bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) {
|
||||
func (b *Bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) {
|
||||
b.Lock()
|
||||
if _, ok := b.userMemberMap[m.Member.User.ID]; ok {
|
||||
flog.Debugf("%s: memberupdate: user %s (nick %s) changes nick to %s", b.Account, m.Member.User.Username, b.userMemberMap[m.Member.User.ID].Nick, m.Member.Nick)
|
||||
b.Log.Debugf("%s: memberupdate: user %s (nick %s) changes nick to %s", b.Account, m.Member.User.Username, b.userMemberMap[m.Member.User.ID].Nick, m.Member.Nick)
|
||||
}
|
||||
b.userMemberMap[m.Member.User.ID] = m.Member
|
||||
b.Unlock()
|
||||
}
|
||||
|
||||
func (b *bdiscord) getNick(user *discordgo.User) string {
|
||||
func (b *Bdiscord) getNick(user *discordgo.User) string {
|
||||
var err error
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
@ -302,7 +313,7 @@ func (b *bdiscord) getNick(user *discordgo.User) string {
|
||||
return user.Username
|
||||
}
|
||||
|
||||
func (b *bdiscord) getChannelID(name string) string {
|
||||
func (b *Bdiscord) getChannelID(name string) string {
|
||||
idcheck := strings.Split(name, "ID:")
|
||||
if len(idcheck) > 1 {
|
||||
return idcheck[1]
|
||||
@ -315,7 +326,7 @@ func (b *bdiscord) getChannelID(name string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *bdiscord) getChannelName(id string) string {
|
||||
func (b *Bdiscord) getChannelName(id string) string {
|
||||
for _, channel := range b.Channels {
|
||||
if channel.ID == id {
|
||||
return channel.Name
|
||||
@ -324,7 +335,7 @@ func (b *bdiscord) getChannelName(id string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *bdiscord) replaceChannelMentions(text string) string {
|
||||
func (b *Bdiscord) replaceChannelMentions(text string) string {
|
||||
var err error
|
||||
re := regexp.MustCompile("<#[0-9]+>")
|
||||
text = re.ReplaceAllStringFunc(text, func(m string) string {
|
||||
@ -343,31 +354,31 @@ func (b *bdiscord) replaceChannelMentions(text string) string {
|
||||
return text
|
||||
}
|
||||
|
||||
func (b *bdiscord) replaceAction(text string) (string, bool) {
|
||||
func (b *Bdiscord) replaceAction(text string) (string, bool) {
|
||||
if strings.HasPrefix(text, "_") && strings.HasSuffix(text, "_") {
|
||||
return strings.Replace(text, "_", "", -1), true
|
||||
}
|
||||
return text, false
|
||||
}
|
||||
|
||||
func (b *bdiscord) stripCustomoji(text string) string {
|
||||
func (b *Bdiscord) stripCustomoji(text string) string {
|
||||
// <:doge:302803592035958784>
|
||||
re := regexp.MustCompile("<(:.*?:)[0-9]+>")
|
||||
return re.ReplaceAllString(text, `$1`)
|
||||
}
|
||||
|
||||
// splitURL splits a webhookURL and returns the id and token
|
||||
func (b *bdiscord) splitURL(url string) (string, string) {
|
||||
func (b *Bdiscord) splitURL(url string) (string, string) {
|
||||
webhookURLSplit := strings.Split(url, "/")
|
||||
if len(webhookURLSplit) != 7 {
|
||||
log.Fatalf("%s is no correct discord WebhookURL", url)
|
||||
b.Log.Fatalf("%s is no correct discord WebhookURL", url)
|
||||
}
|
||||
return webhookURLSplit[len(webhookURLSplit)-2], webhookURLSplit[len(webhookURLSplit)-1]
|
||||
}
|
||||
|
||||
// useWebhook returns true if we have a webhook defined somewhere
|
||||
func (b *bdiscord) useWebhook() bool {
|
||||
if b.Config.WebhookURL != "" {
|
||||
func (b *Bdiscord) useWebhook() bool {
|
||||
if b.GetString("WebhookURL") != "" {
|
||||
return true
|
||||
}
|
||||
for _, channel := range b.channelInfoMap {
|
||||
@ -379,9 +390,9 @@ func (b *bdiscord) useWebhook() bool {
|
||||
}
|
||||
|
||||
// isWebhookID returns true if the specified id is used in a defined webhook
|
||||
func (b *bdiscord) isWebhookID(id string) bool {
|
||||
if b.Config.WebhookURL != "" {
|
||||
wID, _ := b.splitURL(b.Config.WebhookURL)
|
||||
func (b *Bdiscord) isWebhookID(id string) bool {
|
||||
if b.GetString("WebhookURL") != "" {
|
||||
wID, _ := b.splitURL(b.GetString("WebhookURL"))
|
||||
if wID == id {
|
||||
return true
|
||||
}
|
||||
@ -396,3 +407,18 @@ func (b *bdiscord) isWebhookID(id string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// handleUploadFile handles native upload of files
|
||||
func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (string, error) {
|
||||
var err error
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
files := []*discordgo.File{}
|
||||
files = append(files, &discordgo.File{fi.Name, "", bytes.NewReader(*fi.Data)})
|
||||
_, err = b.c.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{Content: msg.Username + fi.Comment, Files: files})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("file upload failed: %#v", err)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
@ -2,11 +2,12 @@ package bgitter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/42wim/go-gitter"
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Bgitter struct {
|
||||
@ -14,31 +15,26 @@ type Bgitter struct {
|
||||
User *gitter.User
|
||||
Users []gitter.User
|
||||
Rooms []gitter.Room
|
||||
*config.BridgeConfig
|
||||
*bridge.Config
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "gitter"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": protocol})
|
||||
}
|
||||
|
||||
func New(cfg *config.BridgeConfig) *Bgitter {
|
||||
return &Bgitter{BridgeConfig: cfg}
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
return &Bgitter{Config: cfg}
|
||||
}
|
||||
|
||||
func (b *Bgitter) Connect() error {
|
||||
var err error
|
||||
flog.Info("Connecting")
|
||||
b.c = gitter.New(b.Config.Token)
|
||||
b.Log.Info("Connecting")
|
||||
b.c = gitter.New(b.GetString("Token"))
|
||||
b.User, err = b.c.GetUser()
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
flog.Info("Connection succeeded")
|
||||
b.Rooms, _ = b.c.GetRooms()
|
||||
b.Rooms, err = b.c.GetRooms()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Log.Info("Connection succeeded")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -74,8 +70,9 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error {
|
||||
for event := range stream.Event {
|
||||
switch ev := event.Data.(type) {
|
||||
case *gitter.MessageReceived:
|
||||
// ignore message sent from ourselves
|
||||
if ev.Message.From.ID != b.User.ID {
|
||||
flog.Debugf("Sending message from %s on %s to gateway", ev.Message.From.Username, b.Account)
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", ev.Message.From.Username, b.Account)
|
||||
rmsg := config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room,
|
||||
Account: b.Account, Avatar: b.getAvatar(ev.Message.From.Username), UserID: ev.Message.From.ID,
|
||||
ID: ev.Message.ID}
|
||||
@ -83,11 +80,11 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error {
|
||||
rmsg.Event = config.EVENT_USER_ACTION
|
||||
rmsg.Text = strings.Replace(rmsg.Text, "@"+ev.Message.From.Username+" ", "", -1)
|
||||
}
|
||||
flog.Debugf("Message is %#v", rmsg)
|
||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||
b.Remote <- rmsg
|
||||
}
|
||||
case *gitter.GitterConnectionClosed:
|
||||
flog.Errorf("connection with gitter closed for room %s", room)
|
||||
b.Log.Errorf("connection with gitter closed for room %s", room)
|
||||
}
|
||||
}
|
||||
}(stream, room.URI)
|
||||
@ -95,25 +92,39 @@ func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error {
|
||||
}
|
||||
|
||||
func (b *Bgitter) Send(msg config.Message) (string, error) {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
b.Log.Debugf("=> Receiving %#v", msg)
|
||||
roomID := b.getRoomID(msg.Channel)
|
||||
if roomID == "" {
|
||||
flog.Errorf("Could not find roomID for %v", msg.Channel)
|
||||
b.Log.Errorf("Could not find roomID for %v", msg.Channel)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Delete message
|
||||
if msg.Event == config.EVENT_MSG_DELETE {
|
||||
if msg.ID == "" {
|
||||
return "", nil
|
||||
}
|
||||
// gitter has no delete message api
|
||||
// gitter has no delete message api so we edit message to ""
|
||||
_, err := b.c.UpdateMessage(roomID, msg.ID, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Upload a file (in gitter case send the upload URL because gitter has no native upload support)
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.c.SendMessage(roomID, rmsg.Username+rmsg.Text)
|
||||
}
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
return b.handleUploadFile(&msg, roomID)
|
||||
}
|
||||
}
|
||||
|
||||
// Edit message
|
||||
if msg.ID != "" {
|
||||
flog.Debugf("updating message with id %s", msg.ID)
|
||||
b.Log.Debugf("updating message with id %s", msg.ID)
|
||||
_, err := b.c.UpdateMessage(roomID, msg.ID, msg.Username+msg.Text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -121,28 +132,7 @@ func (b *Bgitter) Send(msg config.Message) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.c.SendMessage(roomID, rmsg.Username+rmsg.Text)
|
||||
}
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.Comment != "" {
|
||||
msg.Text += fi.Comment + ": "
|
||||
}
|
||||
if fi.URL != "" {
|
||||
msg.Text = fi.URL
|
||||
}
|
||||
_, err := b.c.SendMessage(roomID, msg.Username+msg.Text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// Post normal message
|
||||
resp, err := b.c.SendMessage(roomID, msg.Username+msg.Text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -170,3 +160,23 @@ func (b *Bgitter) getAvatar(user string) string {
|
||||
}
|
||||
return avatar
|
||||
}
|
||||
|
||||
func (b *Bgitter) handleUploadFile(msg *config.Message, roomID string) (string, error) {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.Comment != "" {
|
||||
msg.Text += fi.Comment + ": "
|
||||
}
|
||||
if fi.URL != "" {
|
||||
msg.Text = fi.URL
|
||||
if fi.Comment != "" {
|
||||
msg.Text = fi.Comment + ": " + fi.URL
|
||||
}
|
||||
}
|
||||
_, err := b.c.SendMessage(roomID, msg.Username+msg.Text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
@ -3,18 +3,29 @@ package helper
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func DownloadFile(url string) (*[]byte, error) {
|
||||
return DownloadFileAuth(url, "")
|
||||
}
|
||||
|
||||
func DownloadFileAuth(url string, auth string) (*[]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 5,
|
||||
}
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if auth != "" {
|
||||
req.Header.Add("Authorization", auth)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -61,3 +72,46 @@ func GetAvatar(av map[string]string, userid string, general *config.Protocol) st
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func HandleDownloadSize(flog *log.Entry, msg *config.Message, name string, size int64, general *config.Protocol) error {
|
||||
// check blacklist here
|
||||
for _, entry := range general.MediaDownloadBlackList {
|
||||
if entry != "" {
|
||||
re, err := regexp.Compile(entry)
|
||||
if err != nil {
|
||||
flog.Errorf("incorrect regexp %s for %s", entry, msg.Account)
|
||||
continue
|
||||
}
|
||||
if re.MatchString(name) {
|
||||
return fmt.Errorf("Matching blacklist %s. Not downloading %s", entry, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
flog.Debugf("Trying to download %#v with size %#v", name, size)
|
||||
if int(size) > general.MediaDownloadSize {
|
||||
msg.Event = config.EVENT_FILE_FAILURE_SIZE
|
||||
msg.Extra[msg.Event] = append(msg.Extra[msg.Event], config.FileInfo{Name: name, Comment: msg.Text, Size: size})
|
||||
return fmt.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", name, size, general.MediaDownloadSize)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func HandleDownloadData(flog *log.Entry, msg *config.Message, name, comment, url string, data *[]byte, general *config.Protocol) {
|
||||
var avatar bool
|
||||
flog.Debugf("Download OK %#v %#v", name, len(*data))
|
||||
if msg.Event == config.EVENT_AVATAR_DOWNLOAD {
|
||||
avatar = true
|
||||
}
|
||||
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data, URL: url, Comment: comment, Avatar: avatar})
|
||||
}
|
||||
|
||||
func RemoveEmptyNewLines(msg string) string {
|
||||
lines := ""
|
||||
for _, line := range strings.Split(msg, "\n") {
|
||||
if line != "" {
|
||||
lines += line + "\n"
|
||||
}
|
||||
}
|
||||
lines = strings.TrimRight(lines, "\n")
|
||||
return lines
|
||||
}
|
||||
|
@ -4,13 +4,7 @@ import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/lrstanley/girc"
|
||||
"github.com/paulrosania/go-charset/charset"
|
||||
_ "github.com/paulrosania/go-charset/data"
|
||||
"github.com/saintfish/chardet"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@ -20,40 +14,49 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/dfordsoft/golib/ic"
|
||||
"github.com/lrstanley/girc"
|
||||
"github.com/paulrosania/go-charset/charset"
|
||||
_ "github.com/paulrosania/go-charset/data"
|
||||
"github.com/saintfish/chardet"
|
||||
)
|
||||
|
||||
type Birc struct {
|
||||
i *girc.Client
|
||||
Nick string
|
||||
names map[string][]string
|
||||
connected chan struct{}
|
||||
Local chan config.Message // local queue for flood control
|
||||
FirstConnection bool
|
||||
i *girc.Client
|
||||
Nick string
|
||||
names map[string][]string
|
||||
connected chan struct{}
|
||||
Local chan config.Message // local queue for flood control
|
||||
FirstConnection bool
|
||||
MessageDelay, MessageQueue, MessageLength int
|
||||
|
||||
*config.BridgeConfig
|
||||
*bridge.Config
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "irc"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": protocol})
|
||||
}
|
||||
|
||||
func New(cfg *config.BridgeConfig) *Birc {
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
b := &Birc{}
|
||||
b.BridgeConfig = cfg
|
||||
b.Nick = b.Config.Nick
|
||||
b.Config = cfg
|
||||
b.Nick = b.GetString("Nick")
|
||||
b.names = make(map[string][]string)
|
||||
b.connected = make(chan struct{})
|
||||
if b.Config.MessageDelay == 0 {
|
||||
b.Config.MessageDelay = 1300
|
||||
if b.GetInt("MessageDelay") == 0 {
|
||||
b.MessageDelay = 1300
|
||||
} else {
|
||||
b.MessageDelay = b.GetInt("MessageDelay")
|
||||
}
|
||||
if b.Config.MessageQueue == 0 {
|
||||
b.Config.MessageQueue = 30
|
||||
if b.GetInt("MessageQueue") == 0 {
|
||||
b.MessageQueue = 30
|
||||
} else {
|
||||
b.MessageQueue = b.GetInt("MessageQueue")
|
||||
}
|
||||
if b.Config.MessageLength == 0 {
|
||||
b.Config.MessageLength = 400
|
||||
if b.GetInt("MessageLength") == 0 {
|
||||
b.MessageLength = 400
|
||||
} else {
|
||||
b.MessageLength = b.GetInt("MessageLength")
|
||||
}
|
||||
b.FirstConnection = true
|
||||
return b
|
||||
@ -70,9 +73,9 @@ func (b *Birc) Command(msg *config.Message) string {
|
||||
}
|
||||
|
||||
func (b *Birc) Connect() error {
|
||||
b.Local = make(chan config.Message, b.Config.MessageQueue+10)
|
||||
flog.Infof("Connecting %s", b.Config.Server)
|
||||
server, portstr, err := net.SplitHostPort(b.Config.Server)
|
||||
b.Local = make(chan config.Message, b.MessageQueue+10)
|
||||
b.Log.Infof("Connecting %s", b.GetString("Server"))
|
||||
server, portstr, err := net.SplitHostPort(b.GetString("Server"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -81,7 +84,7 @@ func (b *Birc) Connect() error {
|
||||
return err
|
||||
}
|
||||
// fix strict user handling of girc
|
||||
user := b.Config.Nick
|
||||
user := b.GetString("Nick")
|
||||
for !girc.IsValidUser(user) {
|
||||
if len(user) == 1 {
|
||||
user = "matterbridge"
|
||||
@ -92,62 +95,65 @@ func (b *Birc) Connect() error {
|
||||
|
||||
i := girc.New(girc.Config{
|
||||
Server: server,
|
||||
ServerPass: b.Config.Password,
|
||||
ServerPass: b.GetString("Password"),
|
||||
Port: port,
|
||||
Nick: b.Config.Nick,
|
||||
Nick: b.GetString("Nick"),
|
||||
User: user,
|
||||
Name: b.Config.Nick,
|
||||
SSL: b.Config.UseTLS,
|
||||
TLSConfig: &tls.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, ServerName: server},
|
||||
Name: b.GetString("Nick"),
|
||||
SSL: b.GetBool("UseTLS"),
|
||||
TLSConfig: &tls.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), ServerName: server},
|
||||
PingDelay: time.Minute,
|
||||
})
|
||||
|
||||
if b.Config.UseSASL {
|
||||
i.Config.SASL = &girc.SASLPlain{b.Config.NickServNick, b.Config.NickServPassword}
|
||||
if b.GetBool("UseSASL") {
|
||||
i.Config.SASL = &girc.SASLPlain{b.GetString("NickServNick"), b.GetString("NickServPassword")}
|
||||
}
|
||||
|
||||
i.Handlers.Add(girc.RPL_WELCOME, b.handleNewConnection)
|
||||
i.Handlers.Add(girc.RPL_ENDOFMOTD, b.handleOtherAuth)
|
||||
i.Handlers.Add("*", b.handleOther)
|
||||
i.Handlers.Add(girc.ALL_EVENTS, b.handleOther)
|
||||
go func() {
|
||||
for {
|
||||
if err := i.Connect(); err != nil {
|
||||
flog.Errorf("error: %s", err)
|
||||
flog.Info("reconnecting in 30 seconds...")
|
||||
time.Sleep(30 * time.Second)
|
||||
i.Handlers.Clear(girc.RPL_WELCOME)
|
||||
i.Handlers.Add(girc.RPL_WELCOME, func(client *girc.Client, event girc.Event) {
|
||||
b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS}
|
||||
// set our correct nick on reconnect if necessary
|
||||
b.Nick = event.Source.Name
|
||||
})
|
||||
b.Log.Errorf("disconnect: error: %s", err)
|
||||
} else {
|
||||
return
|
||||
b.Log.Info("disconnect: client requested quit")
|
||||
}
|
||||
|
||||
b.Log.Info("reconnecting in 30 seconds...")
|
||||
time.Sleep(30 * time.Second)
|
||||
i.Handlers.Clear(girc.RPL_WELCOME)
|
||||
i.Handlers.Add(girc.RPL_WELCOME, func(client *girc.Client, event girc.Event) {
|
||||
b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS}
|
||||
// set our correct nick on reconnect if necessary
|
||||
b.Nick = event.Source.Name
|
||||
})
|
||||
}
|
||||
}()
|
||||
b.i = i
|
||||
select {
|
||||
case <-b.connected:
|
||||
flog.Info("Connection succeeded")
|
||||
b.Log.Info("Connection succeeded")
|
||||
case <-time.After(time.Second * 30):
|
||||
return fmt.Errorf("connection timed out")
|
||||
}
|
||||
//i.Debug = false
|
||||
i.Handlers.Clear("*")
|
||||
if b.GetInt("DebugLevel") == 0 {
|
||||
i.Handlers.Clear(girc.ALL_EVENTS)
|
||||
}
|
||||
go b.doSend()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Birc) Disconnect() error {
|
||||
//b.i.Disconnect()
|
||||
b.i.Close()
|
||||
close(b.Local)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Birc) JoinChannel(channel config.ChannelInfo) error {
|
||||
if channel.Options.Key != "" {
|
||||
flog.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
|
||||
b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
|
||||
b.i.Cmd.JoinKey(channel.Name, channel.Options.Key)
|
||||
} else {
|
||||
b.i.Cmd.Join(channel.Name)
|
||||
@ -160,23 +166,38 @@ func (b *Birc) Send(msg config.Message) (string, error) {
|
||||
if msg.Event == config.EVENT_MSG_DELETE {
|
||||
return "", nil
|
||||
}
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
|
||||
b.Log.Debugf("=> Receiving %#v", msg)
|
||||
|
||||
// we can be in between reconnects #385
|
||||
if !b.i.IsConnected() {
|
||||
b.Log.Error("Not connected to server, dropping message")
|
||||
}
|
||||
|
||||
// Execute a command
|
||||
if strings.HasPrefix(msg.Text, "!") {
|
||||
b.Command(&msg)
|
||||
}
|
||||
|
||||
if b.Config.Charset != "" {
|
||||
buf := new(bytes.Buffer)
|
||||
w, err := charset.NewWriter(b.Config.Charset, buf)
|
||||
if err != nil {
|
||||
flog.Errorf("charset from utf-8 conversion failed: %s", err)
|
||||
return "", err
|
||||
// convert to specified charset
|
||||
if b.GetString("Charset") != "" {
|
||||
switch b.GetString("Charset") {
|
||||
case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp":
|
||||
msg.Text = ic.ConvertString("utf-8", b.GetString("Charset"), msg.Text)
|
||||
default:
|
||||
buf := new(bytes.Buffer)
|
||||
w, err := charset.NewWriter(b.GetString("Charset"), buf)
|
||||
if err != nil {
|
||||
b.Log.Errorf("charset from utf-8 conversion failed: %s", err)
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprint(w, msg.Text)
|
||||
w.Close()
|
||||
msg.Text = buf.String()
|
||||
}
|
||||
fmt.Fprintf(w, msg.Text)
|
||||
w.Close()
|
||||
msg.Text = buf.String()
|
||||
}
|
||||
|
||||
// Handle files
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.Local <- rmsg
|
||||
@ -189,6 +210,9 @@ func (b *Birc) Send(msg config.Message) (string, error) {
|
||||
}
|
||||
if fi.URL != "" {
|
||||
msg.Text = fi.URL
|
||||
if fi.Comment != "" {
|
||||
msg.Text = fi.Comment + ": " + fi.URL
|
||||
}
|
||||
}
|
||||
b.Local <- config.Message{Text: msg.Text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event}
|
||||
}
|
||||
@ -197,38 +221,45 @@ func (b *Birc) Send(msg config.Message) (string, error) {
|
||||
}
|
||||
|
||||
// split long messages on messageLength, to avoid clipped messages #281
|
||||
if b.Config.MessageSplit {
|
||||
msg.Text = helper.SplitStringLength(msg.Text, b.Config.MessageLength)
|
||||
if b.GetBool("MessageSplit") {
|
||||
msg.Text = helper.SplitStringLength(msg.Text, b.MessageLength)
|
||||
}
|
||||
for _, text := range strings.Split(msg.Text, "\n") {
|
||||
if len(text) > b.Config.MessageLength {
|
||||
text = text[:b.Config.MessageLength-len(" <message clipped>")]
|
||||
if len(text) > b.MessageLength {
|
||||
text = text[:b.MessageLength-len(" <message clipped>")]
|
||||
if r, size := utf8.DecodeLastRuneInString(text); r == utf8.RuneError {
|
||||
text = text[:len(text)-size]
|
||||
}
|
||||
text += " <message clipped>"
|
||||
}
|
||||
if len(b.Local) < b.Config.MessageQueue {
|
||||
if len(b.Local) == b.Config.MessageQueue-1 {
|
||||
if len(b.Local) < b.MessageQueue {
|
||||
if len(b.Local) == b.MessageQueue-1 {
|
||||
text = text + " <message clipped>"
|
||||
}
|
||||
b.Local <- config.Message{Text: text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event}
|
||||
} else {
|
||||
flog.Debugf("flooding, dropping message (queue at %d)", len(b.Local))
|
||||
b.Log.Debugf("flooding, dropping message (queue at %d)", len(b.Local))
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (b *Birc) doSend() {
|
||||
rate := time.Millisecond * time.Duration(b.Config.MessageDelay)
|
||||
rate := time.Millisecond * time.Duration(b.MessageDelay)
|
||||
throttle := time.NewTicker(rate)
|
||||
for msg := range b.Local {
|
||||
<-throttle.C
|
||||
username := msg.Username
|
||||
if b.GetBool("Colornicks") {
|
||||
checksum := crc32.ChecksumIEEE([]byte(msg.Username))
|
||||
colorCode := checksum%14 + 2 // quick fix - prevent white or black color codes
|
||||
username = fmt.Sprintf("\x03%02d%s\x0F", colorCode, msg.Username)
|
||||
}
|
||||
if msg.Event == config.EVENT_USER_ACTION {
|
||||
b.i.Cmd.Action(msg.Channel, msg.Username+msg.Text)
|
||||
b.i.Cmd.Action(msg.Channel, username+msg.Text)
|
||||
} else {
|
||||
b.i.Cmd.Message(msg.Channel, msg.Username+msg.Text)
|
||||
b.Log.Debugf("Sending to channel %s", msg.Channel)
|
||||
b.i.Cmd.Message(msg.Channel, username+msg.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -252,7 +283,7 @@ func (b *Birc) endNames(client *girc.Client, event girc.Event) {
|
||||
}
|
||||
|
||||
func (b *Birc) handleNewConnection(client *girc.Client, event girc.Event) {
|
||||
flog.Debug("Registering callbacks")
|
||||
b.Log.Debug("Registering callbacks")
|
||||
i := b.i
|
||||
b.Nick = event.Params[0]
|
||||
|
||||
@ -271,107 +302,137 @@ func (b *Birc) handleNewConnection(client *girc.Client, event girc.Event) {
|
||||
|
||||
func (b *Birc) handleJoinPart(client *girc.Client, event girc.Event) {
|
||||
if len(event.Params) == 0 {
|
||||
flog.Debugf("handleJoinPart: empty Params? %#v", event)
|
||||
b.Log.Debugf("handleJoinPart: empty Params? %#v", event)
|
||||
return
|
||||
}
|
||||
channel := event.Params[0]
|
||||
channel := strings.ToLower(event.Params[0])
|
||||
if event.Command == "KICK" {
|
||||
flog.Infof("Got kicked from %s by %s", channel, event.Source.Name)
|
||||
time.Sleep(time.Duration(b.Config.RejoinDelay) * time.Second)
|
||||
b.Log.Infof("Got kicked from %s by %s", channel, event.Source.Name)
|
||||
time.Sleep(time.Duration(b.GetInt("RejoinDelay")) * time.Second)
|
||||
b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: channel, Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS}
|
||||
return
|
||||
}
|
||||
if event.Command == "QUIT" {
|
||||
if event.Source.Name == b.Nick && strings.Contains(event.Trailing, "Ping timeout") {
|
||||
flog.Infof("%s reconnecting ..", b.Account)
|
||||
b.Log.Infof("%s reconnecting ..", b.Account)
|
||||
b.Remote <- config.Message{Username: "system", Text: "reconnect", Channel: channel, Account: b.Account, Event: config.EVENT_FAILURE}
|
||||
return
|
||||
}
|
||||
}
|
||||
if event.Source.Name != b.Nick {
|
||||
flog.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account)
|
||||
b.Remote <- config.Message{Username: "system", Text: event.Source.Name + " " + strings.ToLower(event.Command) + "s", Channel: channel, Account: b.Account, Event: config.EVENT_JOIN_LEAVE}
|
||||
if b.GetBool("nosendjoinpart") {
|
||||
return
|
||||
}
|
||||
b.Log.Debugf("<= Sending JOIN_LEAVE event from %s to gateway", b.Account)
|
||||
msg := config.Message{Username: "system", Text: event.Source.Name + " " + strings.ToLower(event.Command) + "s", Channel: channel, Account: b.Account, Event: config.EVENT_JOIN_LEAVE}
|
||||
b.Log.Debugf("<= Message is %#v", msg)
|
||||
b.Remote <- msg
|
||||
return
|
||||
}
|
||||
flog.Debugf("handle %#v", event)
|
||||
b.Log.Debugf("handle %#v", event)
|
||||
}
|
||||
|
||||
func (b *Birc) handleNotice(client *girc.Client, event girc.Event) {
|
||||
if strings.Contains(event.String(), "This nickname is registered") && event.Source.Name == b.Config.NickServNick {
|
||||
b.i.Cmd.Message(b.Config.NickServNick, "IDENTIFY "+b.Config.NickServPassword)
|
||||
if strings.Contains(event.String(), "This nickname is registered") && event.Source.Name == b.GetString("NickServNick") {
|
||||
b.i.Cmd.Message(b.GetString("NickServNick"), "IDENTIFY "+b.GetString("NickServPassword"))
|
||||
} else {
|
||||
b.handlePrivMsg(client, event)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Birc) handleOther(client *girc.Client, event girc.Event) {
|
||||
if b.GetInt("DebugLevel") == 1 {
|
||||
if event.Command != "CLIENT_STATE_UPDATED" &&
|
||||
event.Command != "CLIENT_GENERAL_UPDATED" {
|
||||
b.Log.Debugf("%#v", event.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
switch event.Command {
|
||||
case "372", "375", "376", "250", "251", "252", "253", "254", "255", "265", "266", "002", "003", "004", "005":
|
||||
return
|
||||
}
|
||||
flog.Debugf("%#v", event.String())
|
||||
b.Log.Debugf("%#v", event.String())
|
||||
}
|
||||
|
||||
func (b *Birc) handleOtherAuth(client *girc.Client, event girc.Event) {
|
||||
if strings.EqualFold(b.Config.NickServNick, "Q@CServe.quakenet.org") {
|
||||
flog.Debugf("Authenticating %s against %s", b.Config.NickServUsername, b.Config.NickServNick)
|
||||
b.i.Cmd.Message(b.Config.NickServNick, "AUTH "+b.Config.NickServUsername+" "+b.Config.NickServPassword)
|
||||
if strings.EqualFold(b.GetString("NickServNick"), "Q@CServe.quakenet.org") {
|
||||
b.Log.Debugf("Authenticating %s against %s", b.GetString("NickServUsername"), b.GetString("NickServNick"))
|
||||
b.i.Cmd.Message(b.GetString("NickServNick"), "AUTH "+b.GetString("NickServUsername")+" "+b.GetString("NickServPassword"))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
|
||||
func (b *Birc) skipPrivMsg(event girc.Event) bool {
|
||||
// Our nick can be changed
|
||||
b.Nick = b.i.GetNick()
|
||||
|
||||
// freenode doesn't send 001 as first reply
|
||||
if event.Command == "NOTICE" {
|
||||
return
|
||||
return true
|
||||
}
|
||||
// don't forward queries to the bot
|
||||
if event.Params[0] == b.Nick {
|
||||
return
|
||||
return true
|
||||
}
|
||||
// don't forward message from ourself
|
||||
if event.Source.Name == b.Nick {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
|
||||
if b.skipPrivMsg(event) {
|
||||
return
|
||||
}
|
||||
rmsg := config.Message{Username: event.Source.Name, Channel: strings.ToLower(event.Params[0]), Account: b.Account, UserID: event.Source.Ident + "@" + event.Source.Host}
|
||||
flog.Debugf("handlePrivMsg() %s %s %#v", event.Source.Name, event.Trailing, event)
|
||||
msg := ""
|
||||
b.Log.Debugf("== Receiving PRIVMSG: %s %s %#v", event.Source.Name, event.Trailing, event)
|
||||
|
||||
// set action event
|
||||
if event.IsAction() {
|
||||
rmsg.Event = config.EVENT_USER_ACTION
|
||||
}
|
||||
msg += event.StripAction()
|
||||
|
||||
// strip action, we made an event if it was an action
|
||||
rmsg.Text += event.StripAction()
|
||||
|
||||
// strip IRC colors
|
||||
re := regexp.MustCompile(`[[:cntrl:]](?:\d{1,2}(?:,\d{1,2})?)?`)
|
||||
msg = re.ReplaceAllString(msg, "")
|
||||
rmsg.Text = re.ReplaceAllString(rmsg.Text, "")
|
||||
|
||||
// start detecting the charset
|
||||
var r io.Reader
|
||||
var err error
|
||||
mycharset := b.Config.Charset
|
||||
mycharset := b.GetString("Charset")
|
||||
if mycharset == "" {
|
||||
// detect what were sending so that we convert it to utf-8
|
||||
detector := chardet.NewTextDetector()
|
||||
result, err := detector.DetectBest([]byte(msg))
|
||||
result, err := detector.DetectBest([]byte(rmsg.Text))
|
||||
if err != nil {
|
||||
flog.Infof("detection failed for msg: %#v", msg)
|
||||
b.Log.Infof("detection failed for rmsg.Text: %#v", rmsg.Text)
|
||||
return
|
||||
}
|
||||
flog.Debugf("detected %s confidence %#v", result.Charset, result.Confidence)
|
||||
b.Log.Debugf("detected %s confidence %#v", result.Charset, result.Confidence)
|
||||
mycharset = result.Charset
|
||||
// if we're not sure, just pick ISO-8859-1
|
||||
if result.Confidence < 80 {
|
||||
mycharset = "ISO-8859-1"
|
||||
}
|
||||
}
|
||||
r, err = charset.NewReader(mycharset, strings.NewReader(msg))
|
||||
if err != nil {
|
||||
flog.Errorf("charset to utf-8 conversion failed: %s", err)
|
||||
return
|
||||
switch mycharset {
|
||||
case "gbk", "gb18030", "gb2312", "big5", "euc-kr", "euc-jp", "shift-jis", "iso-2022-jp":
|
||||
rmsg.Text = ic.ConvertString("utf-8", b.GetString("Charset"), rmsg.Text)
|
||||
default:
|
||||
r, err = charset.NewReader(mycharset, strings.NewReader(rmsg.Text))
|
||||
if err != nil {
|
||||
b.Log.Errorf("charset to utf-8 conversion failed: %s", err)
|
||||
return
|
||||
}
|
||||
output, _ := ioutil.ReadAll(r)
|
||||
rmsg.Text = string(output)
|
||||
}
|
||||
output, _ := ioutil.ReadAll(r)
|
||||
msg = string(output)
|
||||
|
||||
flog.Debugf("Sending message from %s on %s to gateway", event.Params[0], b.Account)
|
||||
rmsg.Text = msg
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", event.Params[0], b.Account)
|
||||
b.Remote <- rmsg
|
||||
}
|
||||
|
||||
@ -379,13 +440,13 @@ func (b *Birc) handleTopicWhoTime(client *girc.Client, event girc.Event) {
|
||||
parts := strings.Split(event.Params[2], "!")
|
||||
t, err := strconv.ParseInt(event.Params[3], 10, 64)
|
||||
if err != nil {
|
||||
flog.Errorf("Invalid time stamp: %s", event.Params[3])
|
||||
b.Log.Errorf("Invalid time stamp: %s", event.Params[3])
|
||||
}
|
||||
user := parts[0]
|
||||
if len(parts) > 1 {
|
||||
user += " [" + parts[1] + "]"
|
||||
}
|
||||
flog.Debugf("%s: Topic set by %s [%s]", event.Command, user, time.Unix(t, 0))
|
||||
b.Log.Debugf("%s: Topic set by %s [%s]", event.Command, user, time.Unix(t, 0))
|
||||
}
|
||||
|
||||
func (b *Birc) nicksPerRow() int {
|
||||
|
@ -2,14 +2,15 @@ package bmatrix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"mime"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
log "github.com/sirupsen/logrus"
|
||||
matrix "github.com/matterbridge/gomatrix"
|
||||
)
|
||||
|
||||
@ -18,42 +19,33 @@ type Bmatrix struct {
|
||||
UserID string
|
||||
RoomMap map[string]string
|
||||
sync.RWMutex
|
||||
*config.BridgeConfig
|
||||
*bridge.Config
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "matrix"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": protocol})
|
||||
}
|
||||
|
||||
func New(cfg *config.BridgeConfig) *Bmatrix {
|
||||
b := &Bmatrix{BridgeConfig: cfg}
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
b := &Bmatrix{Config: cfg}
|
||||
b.RoomMap = make(map[string]string)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Bmatrix) Connect() error {
|
||||
var err error
|
||||
flog.Infof("Connecting %s", b.Config.Server)
|
||||
b.mc, err = matrix.NewClient(b.Config.Server, "", "")
|
||||
b.Log.Infof("Connecting %s", b.GetString("Server"))
|
||||
b.mc, err = matrix.NewClient(b.GetString("Server"), "", "")
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
resp, err := b.mc.Login(&matrix.ReqLogin{
|
||||
Type: "m.login.password",
|
||||
User: b.Config.Login,
|
||||
Password: b.Config.Password,
|
||||
User: b.GetString("Login"),
|
||||
Password: b.GetString("Password"),
|
||||
})
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
b.mc.SetCredentials(resp.UserID, resp.AccessToken)
|
||||
b.UserID = resp.UserID
|
||||
flog.Info("Connection succeeded")
|
||||
b.Log.Info("Connection succeeded")
|
||||
go b.handlematrix()
|
||||
return nil
|
||||
}
|
||||
@ -74,9 +66,22 @@ func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error {
|
||||
}
|
||||
|
||||
func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
b.Log.Debugf("=> Receiving %#v", msg)
|
||||
|
||||
channel := b.getRoomID(msg.Channel)
|
||||
// ignore delete messages
|
||||
b.Log.Debugf("Channel %s maps to channel id %s", msg.Channel, channel)
|
||||
|
||||
// Make a action /me of the message
|
||||
if msg.Event == config.EVENT_USER_ACTION {
|
||||
resp, err := b.mc.SendMessageEvent(channel, "m.room.message",
|
||||
matrix.TextMessage{"m.emote", msg.Username + msg.Text})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.EventID, err
|
||||
}
|
||||
|
||||
// Delete message
|
||||
if msg.Event == config.EVENT_MSG_DELETE {
|
||||
if msg.ID == "" {
|
||||
return "", nil
|
||||
@ -87,62 +92,22 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
|
||||
}
|
||||
return resp.EventID, err
|
||||
}
|
||||
flog.Debugf("Sending to channel %s", channel)
|
||||
if msg.Event == config.EVENT_USER_ACTION {
|
||||
resp, err := b.mc.SendMessageEvent(channel, "m.room.message",
|
||||
matrix.TextMessage{"m.emote", msg.Username + msg.Text})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.EventID, err
|
||||
}
|
||||
|
||||
// Upload a file if it exists
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.mc.SendText(channel, rmsg.Username+rmsg.Text)
|
||||
}
|
||||
// check if we have files to upload (from slack, telegram or mattermost)
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
content := bytes.NewReader(*fi.Data)
|
||||
sp := strings.Split(fi.Name, ".")
|
||||
mtype := mime.TypeByExtension("." + sp[len(sp)-1])
|
||||
if strings.Contains(mtype, "image") ||
|
||||
strings.Contains(mtype, "video") {
|
||||
if fi.Comment != "" {
|
||||
_, err := b.mc.SendText(channel, msg.Username+fi.Comment)
|
||||
if err != nil {
|
||||
flog.Errorf("file comment failed: %#v", err)
|
||||
}
|
||||
}
|
||||
flog.Debugf("uploading file: %s %s", fi.Name, mtype)
|
||||
res, err := b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data)))
|
||||
if err != nil {
|
||||
flog.Errorf("file upload failed: %#v", err)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(mtype, "video") {
|
||||
flog.Debugf("sendVideo %s", res.ContentURI)
|
||||
_, err = b.mc.SendVideo(channel, fi.Name, res.ContentURI)
|
||||
if err != nil {
|
||||
flog.Errorf("sendVideo failed: %#v", err)
|
||||
}
|
||||
}
|
||||
if strings.Contains(mtype, "image") {
|
||||
flog.Debugf("sendImage %s", res.ContentURI)
|
||||
_, err = b.mc.SendImage(channel, fi.Name, res.ContentURI)
|
||||
if err != nil {
|
||||
flog.Errorf("sendImage failed: %#v", err)
|
||||
}
|
||||
}
|
||||
flog.Debugf("result: %#v", res)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
return b.handleUploadFile(&msg, channel)
|
||||
}
|
||||
}
|
||||
|
||||
// Edit message if we have an ID
|
||||
// matrix has no editing support
|
||||
|
||||
// Post normal message
|
||||
resp, err := b.mc.SendText(channel, msg.Username+msg.Text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -168,7 +133,7 @@ func (b *Bmatrix) handlematrix() error {
|
||||
go func() {
|
||||
for {
|
||||
if err := b.mc.Sync(); err != nil {
|
||||
flog.Println("Sync() returned ", err)
|
||||
b.Log.Println("Sync() returned ", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@ -176,24 +141,34 @@ func (b *Bmatrix) handlematrix() error {
|
||||
}
|
||||
|
||||
func (b *Bmatrix) handleEvent(ev *matrix.Event) {
|
||||
flog.Debugf("Received: %#v", ev)
|
||||
b.Log.Debugf("== Receiving event: %#v", ev)
|
||||
if ev.Sender != b.UserID {
|
||||
b.RLock()
|
||||
channel, ok := b.RoomMap[ev.RoomID]
|
||||
b.RUnlock()
|
||||
if !ok {
|
||||
flog.Debugf("Unknown room %s", ev.RoomID)
|
||||
b.Log.Debugf("Unknown room %s", ev.RoomID)
|
||||
return
|
||||
}
|
||||
username := ev.Sender[1:]
|
||||
if b.Config.NoHomeServerSuffix {
|
||||
re := regexp.MustCompile("(.*?):.*")
|
||||
username = re.ReplaceAllString(username, `$1`)
|
||||
|
||||
// TODO download avatar
|
||||
|
||||
// Create our message
|
||||
rmsg := config.Message{Username: ev.Sender[1:], Channel: channel, Account: b.Account, UserID: ev.Sender, ID: ev.ID}
|
||||
|
||||
// Text must be a string
|
||||
if rmsg.Text, ok = ev.Content["body"].(string); !ok {
|
||||
b.Log.Errorf("Content[body] wasn't a %T ?", rmsg.Text)
|
||||
return
|
||||
}
|
||||
var text string
|
||||
text, _ = ev.Content["body"].(string)
|
||||
rmsg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: ev.Sender}
|
||||
rmsg.ID = ev.ID
|
||||
|
||||
// Remove homeserver suffix if configured
|
||||
if b.GetBool("NoHomeServerSuffix") {
|
||||
re := regexp.MustCompile("(.*?):.*")
|
||||
rmsg.Username = re.ReplaceAllString(rmsg.Username, `$1`)
|
||||
}
|
||||
|
||||
// Delete event
|
||||
if ev.Type == "m.room.redaction" {
|
||||
rmsg.Event = config.EVENT_MSG_DELETE
|
||||
rmsg.ID = ev.Redacts
|
||||
@ -201,50 +176,137 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) {
|
||||
b.Remote <- rmsg
|
||||
return
|
||||
}
|
||||
|
||||
// Do we have a /me action
|
||||
if ev.Content["msgtype"].(string) == "m.emote" {
|
||||
rmsg.Event = config.EVENT_USER_ACTION
|
||||
}
|
||||
if ev.Content["msgtype"] != nil && ev.Content["msgtype"].(string) == "m.image" ||
|
||||
ev.Content["msgtype"].(string) == "m.video" ||
|
||||
ev.Content["msgtype"].(string) == "m.file" {
|
||||
flog.Debugf("ev: %#v", ev)
|
||||
rmsg.Extra = make(map[string][]interface{})
|
||||
url := ev.Content["url"].(string)
|
||||
url = strings.Replace(url, "mxc://", b.Config.Server+"/_matrix/media/v1/download/", -1)
|
||||
info := ev.Content["info"].(map[string]interface{})
|
||||
size := info["size"].(float64)
|
||||
name := ev.Content["body"].(string)
|
||||
// check if we have an image uploaded without extension
|
||||
if !strings.Contains(name, ".") {
|
||||
if ev.Content["msgtype"].(string) == "m.image" {
|
||||
if mtype, ok := ev.Content["mimetype"].(string); ok {
|
||||
mext, _ := mime.ExtensionsByType(mtype)
|
||||
if len(mext) > 0 {
|
||||
name = name + mext[0]
|
||||
}
|
||||
} else {
|
||||
// just a default .png extension if we don't have mime info
|
||||
name = name + ".png"
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have attachments
|
||||
if b.containsAttachment(ev.Content) {
|
||||
err := b.handleDownloadFile(&rmsg, ev.Content)
|
||||
if err != nil {
|
||||
b.Log.Errorf("download failed: %#v", err)
|
||||
}
|
||||
flog.Debugf("trying to download %#v with size %#v", name, size)
|
||||
if size <= float64(b.General.MediaDownloadSize) {
|
||||
data, err := helper.DownloadFile(url)
|
||||
if err != nil {
|
||||
flog.Errorf("download %s failed %#v", url, err)
|
||||
} else {
|
||||
flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url))
|
||||
rmsg.Extra["file"] = append(rmsg.Extra["file"], config.FileInfo{Name: name, Data: data})
|
||||
}
|
||||
} else {
|
||||
flog.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", name, size, b.General.MediaDownloadSize)
|
||||
rmsg.Event = config.EVENT_FILE_FAILURE_SIZE
|
||||
rmsg.Extra[rmsg.Event] = append(rmsg.Extra[rmsg.Event], config.FileInfo{Name: name, Size: int64(size)})
|
||||
}
|
||||
rmsg.Text = ""
|
||||
}
|
||||
flog.Debugf("Sending message from %s on %s to gateway", ev.Sender, b.Account)
|
||||
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", ev.Sender, b.Account)
|
||||
b.Remote <- rmsg
|
||||
}
|
||||
}
|
||||
|
||||
// handleDownloadFile handles file download
|
||||
func (b *Bmatrix) handleDownloadFile(rmsg *config.Message, content map[string]interface{}) error {
|
||||
var (
|
||||
ok bool
|
||||
url, name, msgtype, mtype string
|
||||
info map[string]interface{}
|
||||
size float64
|
||||
)
|
||||
|
||||
rmsg.Extra = make(map[string][]interface{})
|
||||
if url, ok = content["url"].(string); !ok {
|
||||
return fmt.Errorf("url isn't a %T", url)
|
||||
}
|
||||
url = strings.Replace(url, "mxc://", b.GetString("Server")+"/_matrix/media/v1/download/", -1)
|
||||
|
||||
if info, ok = content["info"].(map[string]interface{}); !ok {
|
||||
return fmt.Errorf("info isn't a %T", info)
|
||||
}
|
||||
if size, ok = info["size"].(float64); !ok {
|
||||
return fmt.Errorf("size isn't a %T", size)
|
||||
}
|
||||
if name, ok = content["body"].(string); !ok {
|
||||
return fmt.Errorf("name isn't a %T", name)
|
||||
}
|
||||
if msgtype, ok = content["msgtype"].(string); !ok {
|
||||
return fmt.Errorf("msgtype isn't a %T", msgtype)
|
||||
}
|
||||
if mtype, ok = info["mimetype"].(string); !ok {
|
||||
return fmt.Errorf("mtype isn't a %T", mtype)
|
||||
}
|
||||
|
||||
// check if we have an image uploaded without extension
|
||||
if !strings.Contains(name, ".") {
|
||||
if msgtype == "m.image" {
|
||||
mext, _ := mime.ExtensionsByType(mtype)
|
||||
if len(mext) > 0 {
|
||||
name = name + mext[0]
|
||||
}
|
||||
} else {
|
||||
// just a default .png extension if we don't have mime info
|
||||
name = name + ".png"
|
||||
}
|
||||
}
|
||||
|
||||
// check if the size is ok
|
||||
err := helper.HandleDownloadSize(b.Log, rmsg, name, int64(size), b.General)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// actually download the file
|
||||
data, err := helper.DownloadFile(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("download %s failed %#v", url, err)
|
||||
}
|
||||
// add the downloaded data to the message
|
||||
helper.HandleDownloadData(b.Log, rmsg, name, "", url, data, b.General)
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleUploadFile handles native upload of files
|
||||
func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string) (string, error) {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
content := bytes.NewReader(*fi.Data)
|
||||
sp := strings.Split(fi.Name, ".")
|
||||
mtype := mime.TypeByExtension("." + sp[len(sp)-1])
|
||||
if strings.Contains(mtype, "image") ||
|
||||
strings.Contains(mtype, "video") {
|
||||
if fi.Comment != "" {
|
||||
_, err := b.mc.SendText(channel, msg.Username+fi.Comment)
|
||||
if err != nil {
|
||||
b.Log.Errorf("file comment failed: %#v", err)
|
||||
}
|
||||
}
|
||||
b.Log.Debugf("uploading file: %s %s", fi.Name, mtype)
|
||||
res, err := b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data)))
|
||||
if err != nil {
|
||||
b.Log.Errorf("file upload failed: %#v", err)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(mtype, "video") {
|
||||
b.Log.Debugf("sendVideo %s", res.ContentURI)
|
||||
_, err = b.mc.SendVideo(channel, fi.Name, res.ContentURI)
|
||||
if err != nil {
|
||||
b.Log.Errorf("sendVideo failed: %#v", err)
|
||||
}
|
||||
}
|
||||
if strings.Contains(mtype, "image") {
|
||||
b.Log.Debugf("sendImage %s", res.ContentURI)
|
||||
_, err = b.mc.SendImage(channel, fi.Name, res.ContentURI)
|
||||
if err != nil {
|
||||
b.Log.Errorf("sendImage failed: %#v", err)
|
||||
}
|
||||
}
|
||||
b.Log.Debugf("result: %#v", res)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// skipMessages returns true if this message should not be handled
|
||||
func (b *Bmatrix) containsAttachment(content map[string]interface{}) bool {
|
||||
// Skip empty messages
|
||||
if content["msgtype"] == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Only allow image,video or file msgtypes
|
||||
if !(content["msgtype"].(string) == "m.image" ||
|
||||
content["msgtype"].(string) == "m.video" ||
|
||||
content["msgtype"].(string) == "m.file") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -3,51 +3,28 @@ package bmattermost
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/42wim/matterbridge/matterclient"
|
||||
"github.com/42wim/matterbridge/matterhook"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"strings"
|
||||
"github.com/rs/xid"
|
||||
)
|
||||
|
||||
type MMhook struct {
|
||||
mh *matterhook.Client
|
||||
}
|
||||
|
||||
type MMapi struct {
|
||||
mc *matterclient.MMClient
|
||||
mmMap map[string]string
|
||||
}
|
||||
|
||||
type MMMessage struct {
|
||||
Text string
|
||||
Channel string
|
||||
Username string
|
||||
UserID string
|
||||
ID string
|
||||
Event string
|
||||
Extra map[string][]interface{}
|
||||
}
|
||||
|
||||
type Bmattermost struct {
|
||||
MMhook
|
||||
MMapi
|
||||
TeamId string
|
||||
*config.BridgeConfig
|
||||
mh *matterhook.Client
|
||||
mc *matterclient.MMClient
|
||||
uuid string
|
||||
TeamID string
|
||||
*bridge.Config
|
||||
avatarMap map[string]string
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "mattermost"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": protocol})
|
||||
}
|
||||
|
||||
func New(cfg *config.BridgeConfig) *Bmattermost {
|
||||
b := &Bmattermost{BridgeConfig: cfg, avatarMap: make(map[string]string)}
|
||||
b.mmMap = make(map[string]string)
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
b := &Bmattermost{Config: cfg, avatarMap: make(map[string]string)}
|
||||
b.uuid = xid.New().String()
|
||||
return b
|
||||
}
|
||||
|
||||
@ -56,47 +33,47 @@ func (b *Bmattermost) Command(cmd string) string {
|
||||
}
|
||||
|
||||
func (b *Bmattermost) Connect() error {
|
||||
if b.Config.WebhookBindAddress != "" {
|
||||
if b.Config.WebhookURL != "" {
|
||||
flog.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
|
||||
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||
BindAddress: b.Config.WebhookBindAddress})
|
||||
} else if b.Config.Token != "" {
|
||||
flog.Info("Connecting using token (sending)")
|
||||
if b.GetString("WebhookBindAddress") != "" {
|
||||
if b.GetString("WebhookURL") != "" {
|
||||
b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
|
||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||
BindAddress: b.GetString("WebhookBindAddress")})
|
||||
} else if b.GetString("Token") != "" {
|
||||
b.Log.Info("Connecting using token (sending)")
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if b.Config.Login != "" {
|
||||
flog.Info("Connecting using login/password (sending)")
|
||||
} else if b.GetString("Login") != "" {
|
||||
b.Log.Info("Connecting using login/password (sending)")
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
flog.Info("Connecting using webhookbindaddress (receiving)")
|
||||
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||
BindAddress: b.Config.WebhookBindAddress})
|
||||
b.Log.Info("Connecting using webhookbindaddress (receiving)")
|
||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||
BindAddress: b.GetString("WebhookBindAddress")})
|
||||
}
|
||||
go b.handleMatter()
|
||||
return nil
|
||||
}
|
||||
if b.Config.WebhookURL != "" {
|
||||
flog.Info("Connecting using webhookurl (sending)")
|
||||
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||
if b.GetString("WebhookURL") != "" {
|
||||
b.Log.Info("Connecting using webhookurl (sending)")
|
||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||
DisableServer: true})
|
||||
if b.Config.Token != "" {
|
||||
flog.Info("Connecting using token (receiving)")
|
||||
if b.GetString("Token") != "" {
|
||||
b.Log.Info("Connecting using token (receiving)")
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go b.handleMatter()
|
||||
} else if b.Config.Login != "" {
|
||||
flog.Info("Connecting using login/password (receiving)")
|
||||
} else if b.GetString("Login") != "" {
|
||||
b.Log.Info("Connecting using login/password (receiving)")
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -104,23 +81,23 @@ func (b *Bmattermost) Connect() error {
|
||||
go b.handleMatter()
|
||||
}
|
||||
return nil
|
||||
} else if b.Config.Token != "" {
|
||||
flog.Info("Connecting using token (sending and receiving)")
|
||||
} else if b.GetString("Token") != "" {
|
||||
b.Log.Info("Connecting using token (sending and receiving)")
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go b.handleMatter()
|
||||
} else if b.Config.Login != "" {
|
||||
flog.Info("Connecting using login/password (sending and receiving)")
|
||||
} else if b.GetString("Login") != "" {
|
||||
b.Log.Info("Connecting using login/password (sending and receiving)")
|
||||
err := b.apiLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go b.handleMatter()
|
||||
}
|
||||
if b.Config.WebhookBindAddress == "" && b.Config.WebhookURL == "" && b.Config.Login == "" && b.Config.Token == "" {
|
||||
return errors.New("No connection method found. See that you have WebhookBindAddress, WebhookURL or Token/Login/Password/Server/Team configured.")
|
||||
if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") == "" && b.GetString("Login") == "" && b.GetString("Token") == "" {
|
||||
return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Token/Login/Password/Server/Team configured")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -131,7 +108,7 @@ func (b *Bmattermost) Disconnect() error {
|
||||
|
||||
func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error {
|
||||
// we can only join channels using the API
|
||||
if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" {
|
||||
if b.GetString("WebhookURL") == "" && b.GetString("WebhookBindAddress") == "" {
|
||||
id := b.mc.GetChannelId(channel.Name, "")
|
||||
if id == "" {
|
||||
return fmt.Errorf("Could not find channel ID for channel %s", channel.Name)
|
||||
@ -142,136 +119,88 @@ func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error {
|
||||
}
|
||||
|
||||
func (b *Bmattermost) Send(msg config.Message) (string, error) {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
b.Log.Debugf("=> Receiving %#v", msg)
|
||||
|
||||
// Make a action /me of the message
|
||||
if msg.Event == config.EVENT_USER_ACTION {
|
||||
msg.Text = "*" + msg.Text + "*"
|
||||
}
|
||||
nick := msg.Username
|
||||
message := msg.Text
|
||||
channel := msg.Channel
|
||||
|
||||
// map the file SHA to our user (caches the avatar)
|
||||
if msg.Event == config.EVENT_AVATAR_DOWNLOAD {
|
||||
fi := msg.Extra["file"][0].(config.FileInfo)
|
||||
/* if we have a sha we have successfully uploaded the file to the media server,
|
||||
so we can now cache the sha */
|
||||
if fi.SHA != "" {
|
||||
flog.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID)
|
||||
b.avatarMap[msg.UserID] = fi.SHA
|
||||
}
|
||||
return "", nil
|
||||
return b.cacheAvatar(&msg)
|
||||
}
|
||||
|
||||
if b.Config.PrefixMessagesWithNick {
|
||||
message = nick + message
|
||||
// Use webhook to send the message
|
||||
if b.GetString("WebhookURL") != "" {
|
||||
return b.sendWebhook(msg)
|
||||
}
|
||||
if b.Config.WebhookURL != "" {
|
||||
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL, Channel: channel, UserName: rmsg.Username,
|
||||
Text: rmsg.Text, Props: make(map[string]interface{})}
|
||||
matterMessage.Props["matterbridge"] = true
|
||||
b.mh.Send(matterMessage)
|
||||
}
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.URL != "" {
|
||||
message += fi.URL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
|
||||
matterMessage.IconURL = msg.Avatar
|
||||
matterMessage.Channel = channel
|
||||
matterMessage.UserName = nick
|
||||
matterMessage.Type = ""
|
||||
matterMessage.Text = message
|
||||
matterMessage.Props = make(map[string]interface{})
|
||||
matterMessage.Props["matterbridge"] = true
|
||||
err := b.mh.Send(matterMessage)
|
||||
if err != nil {
|
||||
flog.Info(err)
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
// Delete message
|
||||
if msg.Event == config.EVENT_MSG_DELETE {
|
||||
if msg.ID == "" {
|
||||
return "", nil
|
||||
}
|
||||
return msg.ID, b.mc.DeleteMessage(msg.ID)
|
||||
}
|
||||
|
||||
// Upload a file if it exists
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.mc.PostMessage(b.mc.GetChannelId(channel, ""), rmsg.Username+rmsg.Text)
|
||||
b.mc.PostMessage(b.mc.GetChannelId(rmsg.Channel, ""), rmsg.Username+rmsg.Text)
|
||||
}
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
var err error
|
||||
var res, id string
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
id, err = b.mc.UploadFile(*fi.Data, b.mc.GetChannelId(channel, ""), fi.Name)
|
||||
if err != nil {
|
||||
flog.Debugf("ERROR %#v", err)
|
||||
return "", err
|
||||
}
|
||||
message = fi.Comment
|
||||
if b.Config.PrefixMessagesWithNick {
|
||||
message = nick + fi.Comment
|
||||
}
|
||||
res, err = b.mc.PostMessageWithFiles(b.mc.GetChannelId(channel, ""), message, []string{id})
|
||||
}
|
||||
return res, err
|
||||
return b.handleUploadFile(&msg)
|
||||
}
|
||||
}
|
||||
if msg.ID != "" {
|
||||
return b.mc.EditMessage(msg.ID, message)
|
||||
|
||||
// Prepend nick if configured
|
||||
if b.GetBool("PrefixMessagesWithNick") {
|
||||
msg.Text = msg.Username + msg.Text
|
||||
}
|
||||
return b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message)
|
||||
|
||||
// Edit message if we have an ID
|
||||
if msg.ID != "" {
|
||||
return b.mc.EditMessage(msg.ID, msg.Text)
|
||||
}
|
||||
|
||||
// Post normal message
|
||||
return b.mc.PostMessage(b.mc.GetChannelId(msg.Channel, ""), msg.Text)
|
||||
}
|
||||
|
||||
func (b *Bmattermost) handleMatter() {
|
||||
mchan := make(chan *MMMessage)
|
||||
if b.Config.WebhookBindAddress != "" {
|
||||
flog.Debugf("Choosing webhooks based receiving")
|
||||
go b.handleMatterHook(mchan)
|
||||
messages := make(chan *config.Message)
|
||||
if b.GetString("WebhookBindAddress") != "" {
|
||||
b.Log.Debugf("Choosing webhooks based receiving")
|
||||
go b.handleMatterHook(messages)
|
||||
} else {
|
||||
if b.Config.Token != "" {
|
||||
flog.Debugf("Choosing token based receiving")
|
||||
if b.GetString("Token") != "" {
|
||||
b.Log.Debugf("Choosing token based receiving")
|
||||
} else {
|
||||
flog.Debugf("Choosing login/password based receiving")
|
||||
b.Log.Debugf("Choosing login/password based receiving")
|
||||
}
|
||||
go b.handleMatterClient(mchan)
|
||||
go b.handleMatterClient(messages)
|
||||
}
|
||||
for message := range mchan {
|
||||
avatar := helper.GetAvatar(b.avatarMap, message.UserID, b.General)
|
||||
rmsg := config.Message{Username: message.Username, Channel: message.Channel, Account: b.Account, UserID: message.UserID, ID: message.ID, Event: message.Event, Extra: message.Extra, Avatar: avatar}
|
||||
text, ok := b.replaceAction(message.Text)
|
||||
var ok bool
|
||||
for message := range messages {
|
||||
message.Avatar = helper.GetAvatar(b.avatarMap, message.UserID, b.General)
|
||||
message.Account = b.Account
|
||||
message.Text, ok = b.replaceAction(message.Text)
|
||||
if ok {
|
||||
rmsg.Event = config.EVENT_USER_ACTION
|
||||
message.Event = config.EVENT_USER_ACTION
|
||||
}
|
||||
rmsg.Text = text
|
||||
flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account)
|
||||
flog.Debugf("Message is %#v", rmsg)
|
||||
b.Remote <- rmsg
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account)
|
||||
b.Log.Debugf("<= Message is %#v", message)
|
||||
b.Remote <- *message
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) {
|
||||
func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
|
||||
for message := range b.mc.MessageChan {
|
||||
flog.Debugf("%#v", message.Raw.Data)
|
||||
if message.Type == "system_join_leave" ||
|
||||
message.Type == "system_join_channel" ||
|
||||
message.Type == "system_leave_channel" {
|
||||
flog.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account)
|
||||
b.Remote <- config.Message{Username: "system", Text: message.Text, Channel: message.Channel, Account: b.Account, Event: config.EVENT_JOIN_LEAVE}
|
||||
continue
|
||||
}
|
||||
if (message.Raw.Event == "post_edited") && b.Config.EditDisable {
|
||||
b.Log.Debugf("%#v", message.Raw.Data)
|
||||
|
||||
if b.skipMessage(message) {
|
||||
b.Log.Debugf("Skipped message: %#v", message)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -280,107 +209,87 @@ func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) {
|
||||
b.handleDownloadAvatar(message.UserID, message.Channel)
|
||||
}
|
||||
|
||||
m := &MMMessage{Extra: make(map[string][]interface{})}
|
||||
b.Log.Debugf("== Receiving event %#v", message)
|
||||
|
||||
rmsg := &config.Message{Username: message.Username, UserID: message.UserID, Channel: message.Channel, Text: message.Text, ID: message.Post.Id, Extra: make(map[string][]interface{})}
|
||||
|
||||
// handle mattermost post properties (override username and attachments)
|
||||
props := message.Post.Props
|
||||
if props != nil {
|
||||
if _, ok := props["matterbridge"].(bool); ok {
|
||||
flog.Debugf("sent by matterbridge, ignoring")
|
||||
continue
|
||||
}
|
||||
if _, ok := props["override_username"].(string); ok {
|
||||
message.Username = props["override_username"].(string)
|
||||
rmsg.Username = props["override_username"].(string)
|
||||
}
|
||||
if _, ok := props["attachments"].([]interface{}); ok {
|
||||
m.Extra["attachments"] = props["attachments"].([]interface{})
|
||||
}
|
||||
}
|
||||
// do not post our own messages back to irc
|
||||
// only listen to message from our team
|
||||
if (message.Raw.Event == "posted" || message.Raw.Event == "post_edited" || message.Raw.Event == "post_deleted") &&
|
||||
b.mc.User.Username != message.Username && message.Raw.Data["team_id"].(string) == b.TeamId {
|
||||
// if the message has reactions don't repost it (for now, until we can correlate reaction with message)
|
||||
if message.Post.HasReactions {
|
||||
continue
|
||||
}
|
||||
flog.Debugf("Receiving from matterclient %#v", message)
|
||||
m.UserID = message.UserID
|
||||
m.Username = message.Username
|
||||
m.Channel = message.Channel
|
||||
m.Text = message.Text
|
||||
m.ID = message.Post.Id
|
||||
if message.Raw.Event == "post_edited" && !b.Config.EditDisable {
|
||||
m.Text = message.Text + b.Config.EditSuffix
|
||||
}
|
||||
if message.Raw.Event == "post_deleted" {
|
||||
m.Event = config.EVENT_MSG_DELETE
|
||||
}
|
||||
if len(message.Post.FileIds) > 0 {
|
||||
for _, id := range message.Post.FileIds {
|
||||
url, _ := b.mc.Client.GetFileLink(id)
|
||||
finfo, resp := b.mc.Client.GetFileInfo(id)
|
||||
if resp.Error != nil {
|
||||
continue
|
||||
rmsg.Extra["attachments"] = props["attachments"].([]interface{})
|
||||
if rmsg.Text == "" {
|
||||
for _, attachment := range rmsg.Extra["attachments"] {
|
||||
attach := attachment.(map[string]interface{})
|
||||
if attach["text"].(string) != "" {
|
||||
rmsg.Text += attach["text"].(string)
|
||||
continue
|
||||
}
|
||||
if attach["fallback"].(string) != "" {
|
||||
rmsg.Text += attach["fallback"].(string)
|
||||
}
|
||||
}
|
||||
flog.Debugf("trying to download %#v fileid %#v with size %#v", finfo.Name, finfo.Id, finfo.Size)
|
||||
if int(finfo.Size) > b.General.MediaDownloadSize {
|
||||
flog.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", finfo.Name, finfo.Size, b.General.MediaDownloadSize)
|
||||
m.Event = config.EVENT_FILE_FAILURE_SIZE
|
||||
m.Extra[m.Event] = append(m.Extra[m.Event], config.FileInfo{Name: finfo.Name, Comment: message.Text, Size: int64(finfo.Size)})
|
||||
continue
|
||||
}
|
||||
data, resp := b.mc.Client.DownloadFile(id, true)
|
||||
if resp.Error != nil {
|
||||
flog.Errorf("download %s failed %#v", finfo.Name, resp.Error)
|
||||
continue
|
||||
}
|
||||
flog.Debugf("download OK %#v %#v", finfo.Name, len(data))
|
||||
m.Extra["file"] = append(m.Extra["file"], config.FileInfo{Name: finfo.Name, Data: &data, URL: url, Comment: message.Text})
|
||||
}
|
||||
}
|
||||
mchan <- m
|
||||
}
|
||||
|
||||
// create a text for bridges that don't support native editing
|
||||
if message.Raw.Event == "post_edited" && !b.GetBool("EditDisable") {
|
||||
rmsg.Text = message.Text + b.GetString("EditSuffix")
|
||||
}
|
||||
|
||||
if message.Raw.Event == "post_deleted" {
|
||||
rmsg.Event = config.EVENT_MSG_DELETE
|
||||
}
|
||||
|
||||
if len(message.Post.FileIds) > 0 {
|
||||
for _, id := range message.Post.FileIds {
|
||||
err := b.handleDownloadFile(rmsg, id)
|
||||
if err != nil {
|
||||
b.Log.Errorf("download failed: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
messages <- rmsg
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bmattermost) handleMatterHook(mchan chan *MMMessage) {
|
||||
func (b *Bmattermost) handleMatterHook(messages chan *config.Message) {
|
||||
for {
|
||||
message := b.mh.Receive()
|
||||
flog.Debugf("Receiving from matterhook %#v", message)
|
||||
m := &MMMessage{}
|
||||
m.UserID = message.UserID
|
||||
m.Username = message.UserName
|
||||
m.Text = message.Text
|
||||
m.Channel = message.ChannelName
|
||||
mchan <- m
|
||||
b.Log.Debugf("Receiving from matterhook %#v", message)
|
||||
messages <- &config.Message{UserID: message.UserID, Username: message.UserName, Text: message.Text, Channel: message.ChannelName}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bmattermost) apiLogin() error {
|
||||
password := b.Config.Password
|
||||
if b.Config.Token != "" {
|
||||
password = "MMAUTHTOKEN=" + b.Config.Token
|
||||
password := b.GetString("Password")
|
||||
if b.GetString("Token") != "" {
|
||||
password = "MMAUTHTOKEN=" + b.GetString("Token")
|
||||
}
|
||||
|
||||
b.mc = matterclient.New(b.Config.Login, password,
|
||||
b.Config.Team, b.Config.Server)
|
||||
if b.General.Debug {
|
||||
b.mc = matterclient.New(b.GetString("Login"), password, b.GetString("Team"), b.GetString("Server"))
|
||||
if b.GetBool("debug") {
|
||||
b.mc.SetLogLevel("debug")
|
||||
}
|
||||
b.mc.SkipTLSVerify = b.Config.SkipTLSVerify
|
||||
b.mc.NoTLS = b.Config.NoTLS
|
||||
flog.Infof("Connecting %s (team: %s) on %s", b.Config.Login, b.Config.Team, b.Config.Server)
|
||||
b.mc.SkipTLSVerify = b.GetBool("SkipTLSVerify")
|
||||
b.mc.NoTLS = b.GetBool("NoTLS")
|
||||
b.Log.Infof("Connecting %s (team: %s) on %s", b.GetString("Login"), b.GetString("Team"), b.GetString("Server"))
|
||||
err := b.mc.Login()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flog.Info("Connection succeeded")
|
||||
b.TeamId = b.mc.GetTeamId()
|
||||
b.Log.Info("Connection succeeded")
|
||||
b.TeamID = b.mc.GetTeamId()
|
||||
go b.mc.WsReceiver()
|
||||
go b.mc.StatusLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
// replaceAction replace the message with the correct action (/me) code
|
||||
func (b *Bmattermost) replaceAction(text string) (string, bool) {
|
||||
if strings.HasPrefix(text, "*") && strings.HasSuffix(text, "*") {
|
||||
return strings.Replace(text, "*", "", -1), true
|
||||
@ -388,26 +297,166 @@ func (b *Bmattermost) replaceAction(text string) (string, bool) {
|
||||
return text, false
|
||||
}
|
||||
|
||||
func (b *Bmattermost) cacheAvatar(msg *config.Message) (string, error) {
|
||||
fi := msg.Extra["file"][0].(config.FileInfo)
|
||||
/* if we have a sha we have successfully uploaded the file to the media server,
|
||||
so we can now cache the sha */
|
||||
if fi.SHA != "" {
|
||||
b.Log.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID)
|
||||
b.avatarMap[msg.UserID] = fi.SHA
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// handleDownloadAvatar downloads the avatar of userid from channel
|
||||
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
|
||||
// logs an error message if it fails
|
||||
func (b *Bmattermost) handleDownloadAvatar(userid string, channel string) {
|
||||
var name string
|
||||
msg := config.Message{Username: "system", Text: "avatar", Channel: channel, Account: b.Account, UserID: userid, Event: config.EVENT_AVATAR_DOWNLOAD, Extra: make(map[string][]interface{})}
|
||||
rmsg := config.Message{Username: "system", Text: "avatar", Channel: channel, Account: b.Account, UserID: userid, Event: config.EVENT_AVATAR_DOWNLOAD, Extra: make(map[string][]interface{})}
|
||||
if _, ok := b.avatarMap[userid]; !ok {
|
||||
data, resp := b.mc.Client.GetProfileImage(userid, "")
|
||||
if resp.Error != nil {
|
||||
flog.Errorf("ProfileImage download failed for %#v %s", userid, resp.Error)
|
||||
b.Log.Errorf("ProfileImage download failed for %#v %s", userid, resp.Error)
|
||||
return
|
||||
}
|
||||
if len(data) <= b.General.MediaDownloadSize {
|
||||
name = userid + ".png"
|
||||
flog.Debugf("download OK %#v %#v", name, len(data))
|
||||
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: &data, Avatar: true})
|
||||
flog.Debugf("Sending avatar download message from %#v on %s to gateway", userid, b.Account)
|
||||
flog.Debugf("Message is %#v", msg)
|
||||
b.Remote <- msg
|
||||
} else {
|
||||
flog.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", name, len(data), b.General.MediaDownloadSize)
|
||||
err := helper.HandleDownloadSize(b.Log, &rmsg, userid+".png", int64(len(data)), b.General)
|
||||
if err != nil {
|
||||
b.Log.Error(err)
|
||||
return
|
||||
}
|
||||
helper.HandleDownloadData(b.Log, &rmsg, userid+".png", rmsg.Text, "", &data, b.General)
|
||||
b.Remote <- rmsg
|
||||
}
|
||||
}
|
||||
|
||||
// handleDownloadFile handles file download
|
||||
func (b *Bmattermost) handleDownloadFile(rmsg *config.Message, id string) error {
|
||||
url, _ := b.mc.Client.GetFileLink(id)
|
||||
finfo, resp := b.mc.Client.GetFileInfo(id)
|
||||
if resp.Error != nil {
|
||||
return resp.Error
|
||||
}
|
||||
err := helper.HandleDownloadSize(b.Log, rmsg, finfo.Name, finfo.Size, b.General)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, resp := b.mc.Client.DownloadFile(id, true)
|
||||
if resp.Error != nil {
|
||||
return resp.Error
|
||||
}
|
||||
helper.HandleDownloadData(b.Log, rmsg, finfo.Name, rmsg.Text, url, &data, b.General)
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleUploadFile handles native upload of files
|
||||
func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) {
|
||||
var err error
|
||||
var res, id string
|
||||
channelID := b.mc.GetChannelId(msg.Channel, "")
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
id, err = b.mc.UploadFile(*fi.Data, channelID, fi.Name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
msg.Text = fi.Comment
|
||||
if b.GetBool("PrefixMessagesWithNick") {
|
||||
msg.Text = msg.Username + msg.Text
|
||||
}
|
||||
res, err = b.mc.PostMessageWithFiles(channelID, msg.Text, []string{id})
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
// sendWebhook uses the configured WebhookURL to send the message
|
||||
func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
|
||||
// skip events
|
||||
if msg.Event != "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if b.GetBool("PrefixMessagesWithNick") {
|
||||
msg.Text = msg.Username + msg.Text
|
||||
}
|
||||
if msg.Extra != nil {
|
||||
// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl"))
|
||||
matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: rmsg.Channel, UserName: rmsg.Username, Text: rmsg.Text, Props: make(map[string]interface{})}
|
||||
matterMessage.Props["matterbridge_"+b.uuid] = true
|
||||
b.mh.Send(matterMessage)
|
||||
}
|
||||
|
||||
// webhook doesn't support file uploads, so we add the url manually
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.URL != "" {
|
||||
msg.Text += fi.URL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iconURL := config.GetIconURL(&msg, b.GetString("iconurl"))
|
||||
matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: msg.Channel, UserName: msg.Username, Text: msg.Text, Props: make(map[string]interface{})}
|
||||
if msg.Avatar != "" {
|
||||
matterMessage.IconURL = msg.Avatar
|
||||
}
|
||||
matterMessage.Props["matterbridge_"+b.uuid] = true
|
||||
err := b.mh.Send(matterMessage)
|
||||
if err != nil {
|
||||
b.Log.Info(err)
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// skipMessages returns true if this message should not be handled
|
||||
func (b *Bmattermost) skipMessage(message *matterclient.Message) bool {
|
||||
// Handle join/leave
|
||||
if message.Type == "system_join_leave" ||
|
||||
message.Type == "system_join_channel" ||
|
||||
message.Type == "system_leave_channel" {
|
||||
if b.GetBool("nosendjoinpart") {
|
||||
return true
|
||||
}
|
||||
b.Log.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account)
|
||||
b.Remote <- config.Message{Username: "system", Text: message.Text, Channel: message.Channel, Account: b.Account, Event: config.EVENT_JOIN_LEAVE}
|
||||
return true
|
||||
}
|
||||
|
||||
// Handle edited messages
|
||||
if (message.Raw.Event == "post_edited") && b.GetBool("EditDisable") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Ignore messages sent from matterbridge
|
||||
if message.Post.Props != nil {
|
||||
if _, ok := message.Post.Props["matterbridge_"+b.uuid].(bool); ok {
|
||||
b.Log.Debugf("sent by matterbridge, ignoring")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore messages sent from a user logged in as the bot
|
||||
if b.mc.User.Username == message.Username {
|
||||
return true
|
||||
}
|
||||
|
||||
// if the message has reactions don't repost it (for now, until we can correlate reaction with message)
|
||||
if message.Post.HasReactions {
|
||||
return true
|
||||
}
|
||||
|
||||
// ignore messages from other teams than ours
|
||||
if message.Raw.Data["team_id"].(string) != b.TeamID {
|
||||
return true
|
||||
}
|
||||
|
||||
// only handle posted, edited or deleted events
|
||||
if !(message.Raw.Event == "posted" || message.Raw.Event == "post_edited" || message.Raw.Event == "post_deleted") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
package brocketchat
|
||||
|
||||
import (
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/42wim/matterbridge/hook/rockethook"
|
||||
"github.com/42wim/matterbridge/matterhook"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type MMhook struct {
|
||||
@ -15,18 +15,11 @@ type MMhook struct {
|
||||
|
||||
type Brocketchat struct {
|
||||
MMhook
|
||||
*config.BridgeConfig
|
||||
*bridge.Config
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "rocketchat"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": protocol})
|
||||
}
|
||||
|
||||
func New(cfg *config.BridgeConfig) *Brocketchat {
|
||||
return &Brocketchat{BridgeConfig: cfg}
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
return &Brocketchat{Config: cfg}
|
||||
}
|
||||
|
||||
func (b *Brocketchat) Command(cmd string) string {
|
||||
@ -34,11 +27,11 @@ func (b *Brocketchat) Command(cmd string) string {
|
||||
}
|
||||
|
||||
func (b *Brocketchat) Connect() error {
|
||||
flog.Info("Connecting webhooks")
|
||||
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||
b.Log.Info("Connecting webhooks")
|
||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||
DisableServer: true})
|
||||
b.rh = rockethook.New(b.Config.WebhookURL, rockethook.Config{BindAddress: b.Config.WebhookBindAddress})
|
||||
b.rh = rockethook.New(b.GetString("WebhookURL"), rockethook.Config{BindAddress: b.GetString("WebhookBindAddress")})
|
||||
go b.handleRocketHook()
|
||||
return nil
|
||||
}
|
||||
@ -57,11 +50,11 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) {
|
||||
if msg.Event == config.EVENT_MSG_DELETE {
|
||||
return "", nil
|
||||
}
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
b.Log.Debugf("=> Receiving %#v", msg)
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL, Channel: rmsg.Channel, UserName: rmsg.Username,
|
||||
Text: rmsg.Text}
|
||||
iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl"))
|
||||
matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: rmsg.Channel, UserName: rmsg.Username, Text: rmsg.Text}
|
||||
b.mh.Send(matterMessage)
|
||||
}
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
@ -74,14 +67,15 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
|
||||
iconURL := config.GetIconURL(&msg, b.GetString("iconurl"))
|
||||
matterMessage := matterhook.OMessage{IconURL: iconURL}
|
||||
matterMessage.Channel = msg.Channel
|
||||
matterMessage.UserName = msg.Username
|
||||
matterMessage.Type = ""
|
||||
matterMessage.Text = msg.Text
|
||||
err := b.mh.Send(matterMessage)
|
||||
if err != nil {
|
||||
flog.Info(err)
|
||||
b.Log.Info(err)
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
@ -90,12 +84,12 @@ func (b *Brocketchat) Send(msg config.Message) (string, error) {
|
||||
func (b *Brocketchat) handleRocketHook() {
|
||||
for {
|
||||
message := b.rh.Receive()
|
||||
flog.Debugf("Receiving from rockethook %#v", message)
|
||||
b.Log.Debugf("Receiving from rockethook %#v", message)
|
||||
// do not loop
|
||||
if message.UserName == b.Config.Nick {
|
||||
if message.UserName == b.GetString("Nick") {
|
||||
continue
|
||||
}
|
||||
flog.Debugf("Sending message from %s on %s to gateway", message.UserName, b.Account)
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", message.UserName, b.Account)
|
||||
b.Remote <- config.Message{Text: message.Text, Username: message.UserName, Channel: message.ChannelName, Account: b.Account, UserID: message.UserID}
|
||||
}
|
||||
}
|
||||
|
@ -4,47 +4,37 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/42wim/matterbridge/matterhook"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/nlopes/slack"
|
||||
"html"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"github.com/rs/xid"
|
||||
)
|
||||
|
||||
type MMMessage struct {
|
||||
Text string
|
||||
Channel string
|
||||
Username string
|
||||
UserID string
|
||||
Raw *slack.MessageEvent
|
||||
}
|
||||
|
||||
type Bslack struct {
|
||||
mh *matterhook.Client
|
||||
sc *slack.Client
|
||||
rtm *slack.RTM
|
||||
Plus bool
|
||||
Users []slack.User
|
||||
si *slack.Info
|
||||
channels []slack.Channel
|
||||
*config.BridgeConfig
|
||||
mh *matterhook.Client
|
||||
sc *slack.Client
|
||||
rtm *slack.RTM
|
||||
Users []slack.User
|
||||
Usergroups []slack.UserGroup
|
||||
si *slack.Info
|
||||
channels []slack.Channel
|
||||
uuid string
|
||||
*bridge.Config
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "slack"
|
||||
const messageDeleted = "message_deleted"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": protocol})
|
||||
}
|
||||
|
||||
func New(cfg *config.BridgeConfig) *Bslack {
|
||||
return &Bslack{BridgeConfig: cfg}
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
return &Bslack{Config: cfg, uuid: xid.New().String()}
|
||||
}
|
||||
|
||||
func (b *Bslack) Command(cmd string) string {
|
||||
@ -52,71 +42,76 @@ func (b *Bslack) Command(cmd string) string {
|
||||
}
|
||||
|
||||
func (b *Bslack) Connect() error {
|
||||
if b.Config.WebhookBindAddress != "" {
|
||||
if b.Config.WebhookURL != "" {
|
||||
flog.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
|
||||
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||
BindAddress: b.Config.WebhookBindAddress})
|
||||
} else if b.Config.Token != "" {
|
||||
flog.Info("Connecting using token (sending)")
|
||||
b.sc = slack.New(b.Config.Token)
|
||||
b.RLock()
|
||||
defer b.RUnlock()
|
||||
if b.GetString("WebhookBindAddress") != "" {
|
||||
if b.GetString("WebhookURL") != "" {
|
||||
b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
|
||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||
BindAddress: b.GetString("WebhookBindAddress")})
|
||||
} else if b.GetString("Token") != "" {
|
||||
b.Log.Info("Connecting using token (sending)")
|
||||
b.sc = slack.New(b.GetString("Token"))
|
||||
b.rtm = b.sc.NewRTM()
|
||||
go b.rtm.ManageConnection()
|
||||
flog.Info("Connecting using webhookbindaddress (receiving)")
|
||||
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||
BindAddress: b.Config.WebhookBindAddress})
|
||||
b.Log.Info("Connecting using webhookbindaddress (receiving)")
|
||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||
BindAddress: b.GetString("WebhookBindAddress")})
|
||||
} else {
|
||||
flog.Info("Connecting using webhookbindaddress (receiving)")
|
||||
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||
BindAddress: b.Config.WebhookBindAddress})
|
||||
b.Log.Info("Connecting using webhookbindaddress (receiving)")
|
||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||
BindAddress: b.GetString("WebhookBindAddress")})
|
||||
}
|
||||
go b.handleSlack()
|
||||
return nil
|
||||
}
|
||||
if b.Config.WebhookURL != "" {
|
||||
flog.Info("Connecting using webhookurl (sending)")
|
||||
b.mh = matterhook.New(b.Config.WebhookURL,
|
||||
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||
if b.GetString("WebhookURL") != "" {
|
||||
b.Log.Info("Connecting using webhookurl (sending)")
|
||||
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
||||
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
||||
DisableServer: true})
|
||||
if b.Config.Token != "" {
|
||||
flog.Info("Connecting using token (receiving)")
|
||||
b.sc = slack.New(b.Config.Token)
|
||||
if b.GetString("Token") != "" {
|
||||
b.Log.Info("Connecting using token (receiving)")
|
||||
b.sc = slack.New(b.GetString("Token"))
|
||||
b.rtm = b.sc.NewRTM()
|
||||
go b.rtm.ManageConnection()
|
||||
go b.handleSlack()
|
||||
}
|
||||
} else if b.Config.Token != "" {
|
||||
flog.Info("Connecting using token (sending and receiving)")
|
||||
b.sc = slack.New(b.Config.Token)
|
||||
} else if b.GetString("Token") != "" {
|
||||
b.Log.Info("Connecting using token (sending and receiving)")
|
||||
b.sc = slack.New(b.GetString("Token"))
|
||||
b.rtm = b.sc.NewRTM()
|
||||
go b.rtm.ManageConnection()
|
||||
go b.handleSlack()
|
||||
}
|
||||
if b.Config.WebhookBindAddress == "" && b.Config.WebhookURL == "" && b.Config.Token == "" {
|
||||
return errors.New("No connection method found. See that you have WebhookBindAddress, WebhookURL or Token configured.")
|
||||
if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") == "" && b.GetString("Token") == "" {
|
||||
return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Token configured")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bslack) Disconnect() error {
|
||||
return nil
|
||||
|
||||
return b.rtm.Disconnect()
|
||||
}
|
||||
|
||||
func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
|
||||
// we can only join channels using the API
|
||||
if b.sc != nil {
|
||||
if strings.HasPrefix(b.Config.Token, "xoxb") {
|
||||
if strings.HasPrefix(b.GetString("Token"), "xoxb") {
|
||||
// TODO check if bot has already joined channel
|
||||
return nil
|
||||
}
|
||||
_, err := b.sc.JoinChannel(channel.Name)
|
||||
if err != nil {
|
||||
if err.Error() != "name_taken" {
|
||||
return err
|
||||
switch err.Error() {
|
||||
case "name_taken", "restricted_action":
|
||||
case "default":
|
||||
{
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -124,64 +119,25 @@ func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
|
||||
}
|
||||
|
||||
func (b *Bslack) Send(msg config.Message) (string, error) {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
b.Log.Debugf("=> Receiving %#v", msg)
|
||||
|
||||
// Make a action /me of the message
|
||||
if msg.Event == config.EVENT_USER_ACTION {
|
||||
msg.Text = "_" + msg.Text + "_"
|
||||
}
|
||||
nick := msg.Username
|
||||
message := msg.Text
|
||||
channel := msg.Channel
|
||||
if b.Config.PrefixMessagesWithNick {
|
||||
message = nick + " " + message
|
||||
}
|
||||
if b.Config.WebhookURL != "" {
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL, Channel: channel, UserName: rmsg.Username,
|
||||
Text: rmsg.Text}
|
||||
b.mh.Send(matterMessage)
|
||||
}
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.URL != "" {
|
||||
message += fi.URL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
|
||||
matterMessage.Channel = channel
|
||||
matterMessage.UserName = nick
|
||||
matterMessage.Type = ""
|
||||
matterMessage.Text = message
|
||||
err := b.mh.Send(matterMessage)
|
||||
if err != nil {
|
||||
flog.Info(err)
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
// Use webhook to send the message
|
||||
if b.GetString("WebhookURL") != "" {
|
||||
return b.sendWebhook(msg)
|
||||
}
|
||||
schannel, err := b.getChannelByName(channel)
|
||||
|
||||
// get the slack channel
|
||||
schannel, err := b.getChannelByName(msg.Channel)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
np := slack.NewPostMessageParameters()
|
||||
if b.Config.PrefixMessagesWithNick {
|
||||
np.AsUser = true
|
||||
}
|
||||
np.Username = nick
|
||||
np.IconURL = config.GetIconURL(&msg, &b.Config)
|
||||
if msg.Avatar != "" {
|
||||
np.IconURL = msg.Avatar
|
||||
}
|
||||
np.Attachments = append(np.Attachments, slack.Attachment{CallbackID: "matterbridge"})
|
||||
np.Attachments = append(np.Attachments, b.createAttach(msg.Extra)...)
|
||||
|
||||
// replace mentions
|
||||
np.LinkNames = 1
|
||||
|
||||
// Delete message
|
||||
if msg.Event == config.EVENT_MSG_DELETE {
|
||||
// some protocols echo deletes, but with empty ID
|
||||
if msg.ID == "" {
|
||||
@ -189,45 +145,73 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
|
||||
}
|
||||
// we get a "slack <ID>", split it
|
||||
ts := strings.Fields(msg.ID)
|
||||
b.sc.DeleteMessage(schannel.ID, ts[1])
|
||||
return "", nil
|
||||
}
|
||||
// if we have no ID it means we're creating a new message, not updating an existing one
|
||||
if msg.ID != "" {
|
||||
ts := strings.Fields(msg.ID)
|
||||
b.sc.UpdateMessage(schannel.ID, ts[1], message)
|
||||
return "", nil
|
||||
_, _, err := b.sc.DeleteMessage(schannel.ID, ts[1])
|
||||
if err != nil {
|
||||
return msg.ID, err
|
||||
}
|
||||
return msg.ID, nil
|
||||
}
|
||||
|
||||
// Prepend nick if configured
|
||||
if b.GetBool("PrefixMessagesWithNick") {
|
||||
msg.Text = msg.Username + msg.Text
|
||||
}
|
||||
|
||||
// Edit message if we have an ID
|
||||
if msg.ID != "" {
|
||||
ts := strings.Fields(msg.ID)
|
||||
_, _, _, err := b.sc.UpdateMessage(schannel.ID, ts[1], msg.Text)
|
||||
if err != nil {
|
||||
return msg.ID, err
|
||||
}
|
||||
return msg.ID, nil
|
||||
}
|
||||
|
||||
// create slack new post parameters
|
||||
np := slack.NewPostMessageParameters()
|
||||
if b.GetBool("PrefixMessagesWithNick") {
|
||||
np.AsUser = true
|
||||
}
|
||||
np.Username = msg.Username
|
||||
np.LinkNames = 1 // replace mentions
|
||||
np.IconURL = config.GetIconURL(&msg, b.GetString("iconurl"))
|
||||
if msg.Avatar != "" {
|
||||
np.IconURL = msg.Avatar
|
||||
}
|
||||
// add a callback ID so we can see we created it
|
||||
np.Attachments = append(np.Attachments, slack.Attachment{CallbackID: "matterbridge_" + b.uuid})
|
||||
// add file attachments
|
||||
np.Attachments = append(np.Attachments, b.createAttach(msg.Extra)...)
|
||||
// add slack attachments (from another slack bridge)
|
||||
if len(msg.Extra["slack_attachment"]) > 0 {
|
||||
for _, attach := range msg.Extra["slack_attachment"] {
|
||||
np.Attachments = append(np.Attachments, attach.([]slack.Attachment)...)
|
||||
}
|
||||
}
|
||||
|
||||
// Upload a file if it exists
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.sc.PostMessage(schannel.ID, rmsg.Username+rmsg.Text, np)
|
||||
}
|
||||
// check if we have files to upload (from slack, telegram or mattermost)
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
var err error
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
_, err = b.sc.UploadFile(slack.FileUploadParameters{
|
||||
Reader: bytes.NewReader(*fi.Data),
|
||||
Filename: fi.Name,
|
||||
Channels: []string{schannel.ID},
|
||||
InitialComment: fi.Comment,
|
||||
})
|
||||
if err != nil {
|
||||
flog.Errorf("uploadfile %#v", err)
|
||||
}
|
||||
}
|
||||
b.handleUploadFile(&msg, schannel.ID)
|
||||
}
|
||||
}
|
||||
|
||||
_, id, err := b.sc.PostMessage(schannel.ID, message, np)
|
||||
// Post normal message
|
||||
_, id, err := b.sc.PostMessage(schannel.ID, msg.Text, np)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "slack " + id, nil
|
||||
}
|
||||
|
||||
func (b *Bslack) Reload(cfg *bridge.Config) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (b *Bslack) getAvatar(user string) string {
|
||||
var avatar string
|
||||
if b.Users != nil {
|
||||
@ -265,163 +249,61 @@ func (b *Bslack) getChannelByID(ID string) (*slack.Channel, error) {
|
||||
}
|
||||
|
||||
func (b *Bslack) handleSlack() {
|
||||
mchan := make(chan *MMMessage)
|
||||
if b.Config.WebhookBindAddress != "" {
|
||||
flog.Debugf("Choosing webhooks based receiving")
|
||||
go b.handleMatterHook(mchan)
|
||||
messages := make(chan *config.Message)
|
||||
if b.GetString("WebhookBindAddress") != "" {
|
||||
b.Log.Debugf("Choosing webhooks based receiving")
|
||||
go b.handleMatterHook(messages)
|
||||
} else {
|
||||
flog.Debugf("Choosing token based receiving")
|
||||
go b.handleSlackClient(mchan)
|
||||
b.Log.Debugf("Choosing token based receiving")
|
||||
go b.handleSlackClient(messages)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
flog.Debug("Start listening for Slack messages")
|
||||
for message := range mchan {
|
||||
// do not send messages from ourself
|
||||
if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" && message.Username == b.si.User.Name {
|
||||
continue
|
||||
}
|
||||
if (message.Text == "" || message.Username == "") && message.Raw.SubType != "message_deleted" {
|
||||
continue
|
||||
}
|
||||
text := message.Text
|
||||
text = b.replaceURL(text)
|
||||
text = html.UnescapeString(text)
|
||||
flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account)
|
||||
msg := config.Message{Text: text, Username: message.Username, Channel: message.Channel, Account: b.Account, Avatar: b.getAvatar(message.Username), UserID: message.UserID, ID: "slack " + message.Raw.Timestamp, Extra: make(map[string][]interface{})}
|
||||
if message.Raw.SubType == "me_message" {
|
||||
msg.Event = config.EVENT_USER_ACTION
|
||||
}
|
||||
if message.Raw.SubType == "channel_leave" || message.Raw.SubType == "channel_join" {
|
||||
msg.Username = "system"
|
||||
msg.Event = config.EVENT_JOIN_LEAVE
|
||||
}
|
||||
// edited messages have a submessage, use this timestamp
|
||||
if message.Raw.SubMessage != nil {
|
||||
msg.ID = "slack " + message.Raw.SubMessage.Timestamp
|
||||
}
|
||||
if message.Raw.SubType == "message_deleted" {
|
||||
msg.Text = config.EVENT_MSG_DELETE
|
||||
msg.Event = config.EVENT_MSG_DELETE
|
||||
msg.ID = "slack " + message.Raw.DeletedTimestamp
|
||||
}
|
||||
if message.Raw.SubType == "channel_topic" || message.Raw.SubType == "channel_purpose" {
|
||||
msg.Event = config.EVENT_TOPIC_CHANGE
|
||||
}
|
||||
b.Log.Debug("Start listening for Slack messages")
|
||||
for message := range messages {
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account)
|
||||
|
||||
// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
|
||||
if message.Raw.File != nil {
|
||||
// limit to 1MB for now
|
||||
comment := ""
|
||||
results := regexp.MustCompile(`.*?commented: (.*)`).FindAllStringSubmatch(msg.Text, -1)
|
||||
if len(results) > 0 {
|
||||
comment = results[0][1]
|
||||
}
|
||||
// cleanup the message
|
||||
message.Text = b.replaceMention(message.Text)
|
||||
message.Text = b.replaceVariable(message.Text)
|
||||
message.Text = b.replaceChannel(message.Text)
|
||||
message.Text = b.replaceURL(message.Text)
|
||||
message.Text = html.UnescapeString(message.Text)
|
||||
|
||||
if message.Raw.File.Size > b.General.MediaDownloadSize {
|
||||
flog.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", message.Raw.File.Name, message.Raw.File.Size, b.General.MediaDownloadSize)
|
||||
msg.Event = config.EVENT_FILE_FAILURE_SIZE
|
||||
msg.Extra[msg.Event] = append(msg.Extra[msg.Event], config.FileInfo{Name: message.Raw.File.Name, Comment: comment, Size: int64(message.Raw.File.Size)})
|
||||
} else {
|
||||
data, err := b.downloadFile(message.Raw.File.URLPrivateDownload)
|
||||
if err != nil {
|
||||
flog.Errorf("download %s failed %#v", message.Raw.File.URLPrivateDownload, err)
|
||||
} else {
|
||||
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: message.Raw.File.Name, Data: data, Comment: comment})
|
||||
}
|
||||
}
|
||||
}
|
||||
flog.Debugf("Message is %#v", msg)
|
||||
b.Remote <- msg
|
||||
// Add the avatar
|
||||
message.Avatar = b.getAvatar(strings.ToLower(message.Username))
|
||||
|
||||
b.Log.Debugf("<= Message is %#v", message)
|
||||
b.Remote <- *message
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
|
||||
func (b *Bslack) handleSlackClient(messages chan *config.Message) {
|
||||
for msg := range b.rtm.IncomingEvents {
|
||||
if msg.Type != "user_typing" && msg.Type != "latency_report" {
|
||||
flog.Debugf("Receiving from slackclient %#v", msg.Data)
|
||||
b.Log.Debugf("== Receiving event %#v", msg.Data)
|
||||
}
|
||||
switch ev := msg.Data.(type) {
|
||||
case *slack.MessageEvent:
|
||||
if ev.SubType == "pinned_item" || ev.SubType == "unpinned_item" {
|
||||
if b.skipMessageEvent(ev) {
|
||||
b.Log.Debugf("Skipped message: %#v", ev)
|
||||
continue
|
||||
}
|
||||
if len(ev.Attachments) > 0 {
|
||||
// skip messages we made ourselves
|
||||
if ev.Attachments[0].CallbackID == "matterbridge" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !b.Config.EditDisable && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp {
|
||||
flog.Debugf("SubMessage %#v", ev.SubMessage)
|
||||
ev.User = ev.SubMessage.User
|
||||
ev.Text = ev.SubMessage.Text + b.Config.EditSuffix
|
||||
|
||||
// it seems ev.SubMessage.Edited == nil when slack unfurls
|
||||
// do not forward these messages #266
|
||||
if ev.SubMessage.Edited == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// use our own func because rtm.GetChannelInfo doesn't work for private channels
|
||||
channel, err := b.getChannelByID(ev.Channel)
|
||||
rmsg, err := b.handleMessageEvent(ev)
|
||||
if err != nil {
|
||||
b.Log.Errorf("%#v", err)
|
||||
continue
|
||||
}
|
||||
m := &MMMessage{}
|
||||
if ev.BotID == "" && ev.SubType != "message_deleted" && ev.SubType != "file_comment" {
|
||||
user, err := b.rtm.GetUserInfo(ev.User)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
m.UserID = user.ID
|
||||
m.Username = user.Name
|
||||
if user.Profile.DisplayName != "" {
|
||||
m.Username = user.Profile.DisplayName
|
||||
}
|
||||
}
|
||||
m.Channel = channel.Name
|
||||
m.Text = ev.Text
|
||||
if m.Text == "" {
|
||||
for _, attach := range ev.Attachments {
|
||||
if attach.Text != "" {
|
||||
m.Text = attach.Text
|
||||
} else {
|
||||
m.Text = attach.Fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
m.Raw = ev
|
||||
m.Text = b.replaceMention(m.Text)
|
||||
m.Text = b.replaceVariable(m.Text)
|
||||
m.Text = b.replaceChannel(m.Text)
|
||||
// when using webhookURL we can't check if it's our webhook or not for now
|
||||
if ev.BotID != "" && b.Config.WebhookURL == "" {
|
||||
bot, err := b.rtm.GetBotInfo(ev.BotID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if bot.Name != "" {
|
||||
m.Username = bot.Name
|
||||
if ev.Username != "" {
|
||||
m.Username = ev.Username
|
||||
}
|
||||
m.UserID = bot.ID
|
||||
}
|
||||
}
|
||||
|
||||
if ev.SubType == "file_comment" {
|
||||
m.Username = "system"
|
||||
}
|
||||
|
||||
mchan <- m
|
||||
messages <- rmsg
|
||||
case *slack.OutgoingErrorEvent:
|
||||
flog.Debugf("%#v", ev.Error())
|
||||
b.Log.Debugf("%#v", ev.Error())
|
||||
case *slack.ChannelJoinedEvent:
|
||||
b.Users, _ = b.sc.GetUsers()
|
||||
b.Usergroups, _ = b.sc.GetUserGroups()
|
||||
case *slack.ConnectedEvent:
|
||||
b.channels = ev.Info.Channels
|
||||
b.si = ev.Info
|
||||
b.Users, _ = b.sc.GetUsers()
|
||||
b.Usergroups, _ = b.sc.GetUserGroups()
|
||||
// add private channels
|
||||
groups, _ := b.sc.GetGroups(true)
|
||||
for _, g := range groups {
|
||||
@ -431,29 +313,22 @@ func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
|
||||
b.channels = append(b.channels, *channel)
|
||||
}
|
||||
case *slack.InvalidAuthEvent:
|
||||
flog.Fatalf("Invalid Token %#v", ev)
|
||||
b.Log.Fatalf("Invalid Token %#v", ev)
|
||||
case *slack.ConnectionErrorEvent:
|
||||
flog.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
|
||||
b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bslack) handleMatterHook(mchan chan *MMMessage) {
|
||||
func (b *Bslack) handleMatterHook(messages chan *config.Message) {
|
||||
for {
|
||||
message := b.mh.Receive()
|
||||
flog.Debugf("receiving from matterhook (slack) %#v", message)
|
||||
m := &MMMessage{}
|
||||
m.Username = message.UserName
|
||||
m.Text = message.Text
|
||||
m.Text = b.replaceMention(m.Text)
|
||||
m.Text = b.replaceVariable(m.Text)
|
||||
m.Text = b.replaceChannel(m.Text)
|
||||
m.Channel = message.ChannelName
|
||||
if m.Username == "slackbot" {
|
||||
b.Log.Debugf("receiving from matterhook (slack) %#v", message)
|
||||
if message.UserName == "slackbot" {
|
||||
continue
|
||||
}
|
||||
mchan <- m
|
||||
messages <- &config.Message{Username: message.UserName, Text: message.Text, Channel: message.ChannelName}
|
||||
}
|
||||
}
|
||||
|
||||
@ -469,9 +344,20 @@ func (b *Bslack) userName(id string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
/*
|
||||
func (b *Bslack) userGroupName(id string) string {
|
||||
for _, u := range b.Usergroups {
|
||||
if u.ID == id {
|
||||
return u.Name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
*/
|
||||
|
||||
// @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users
|
||||
func (b *Bslack) replaceMention(text string) string {
|
||||
results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1)
|
||||
results := regexp.MustCompile(`<@([a-zA-Z0-9]+)>`).FindAllStringSubmatch(text, -1)
|
||||
for _, r := range results {
|
||||
text = strings.Replace(text, "<@"+r[1]+">", "@"+b.userName(r[1]), -1)
|
||||
}
|
||||
@ -489,9 +375,13 @@ func (b *Bslack) replaceChannel(text string) string {
|
||||
|
||||
// @see https://api.slack.com/docs/message-formatting#variables
|
||||
func (b *Bslack) replaceVariable(text string) string {
|
||||
results := regexp.MustCompile(`<!([a-zA-Z0-9]+)(\|.+?)?>`).FindAllStringSubmatch(text, -1)
|
||||
results := regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`).FindAllStringSubmatch(text, -1)
|
||||
for _, r := range results {
|
||||
text = strings.Replace(text, r[0], "@"+r[1], -1)
|
||||
if r[2] != "" {
|
||||
text = strings.Replace(text, r[0], "@"+r[2], -1)
|
||||
} else {
|
||||
text = strings.Replace(text, r[0], "@"+r[1], -1)
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
@ -500,7 +390,11 @@ func (b *Bslack) replaceVariable(text string) string {
|
||||
func (b *Bslack) replaceURL(text string) string {
|
||||
results := regexp.MustCompile(`<(.*?)(\|.*?)?>`).FindAllStringSubmatch(text, -1)
|
||||
for _, r := range results {
|
||||
text = strings.Replace(text, r[0], r[1], -1)
|
||||
if len(strings.TrimSpace(r[2])) == 1 { // A display text separator was found, but the text was blank
|
||||
text = strings.Replace(text, r[0], "", -1)
|
||||
} else {
|
||||
text = strings.Replace(text, r[0], r[1], -1)
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
@ -528,23 +422,263 @@ func (b *Bslack) createAttach(extra map[string][]interface{}) []slack.Attachment
|
||||
return attachs
|
||||
}
|
||||
|
||||
func (b *Bslack) downloadFile(url string) (*[]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 5,
|
||||
// handleDownloadFile handles file download
|
||||
func (b *Bslack) handleDownloadFile(rmsg *config.Message, file *slack.File) error {
|
||||
// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
|
||||
// limit to 1MB for now
|
||||
comment := ""
|
||||
results := regexp.MustCompile(`.*?commented: (.*)`).FindAllStringSubmatch(rmsg.Text, -1)
|
||||
if len(results) > 0 {
|
||||
comment = results[0][1]
|
||||
}
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
|
||||
err := helper.HandleDownloadSize(b.Log, rmsg, file.Name, int64(file.Size), b.General)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
req.Header.Add("Authorization", "Bearer "+b.Config.Token)
|
||||
resp, err := client.Do(req)
|
||||
// actually download the file
|
||||
data, err := helper.DownloadFileAuth(file.URLPrivateDownload, "Bearer "+b.GetString("Token"))
|
||||
if err != nil {
|
||||
resp.Body.Close()
|
||||
return nil, err
|
||||
return fmt.Errorf("download %s failed %#v", file.URLPrivateDownload, err)
|
||||
}
|
||||
io.Copy(&buf, resp.Body)
|
||||
data := buf.Bytes()
|
||||
resp.Body.Close()
|
||||
return &data, nil
|
||||
// add the downloaded data to the message
|
||||
helper.HandleDownloadData(b.Log, rmsg, file.Name, comment, file.URLPrivateDownload, data, b.General)
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleUploadFile handles native upload of files
|
||||
func (b *Bslack) handleUploadFile(msg *config.Message, channelID string) (string, error) {
|
||||
var err error
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
_, err = b.sc.UploadFile(slack.FileUploadParameters{
|
||||
Reader: bytes.NewReader(*fi.Data),
|
||||
Filename: fi.Name,
|
||||
Channels: []string{channelID},
|
||||
InitialComment: fi.Comment,
|
||||
})
|
||||
if err != nil {
|
||||
b.Log.Errorf("uploadfile %#v", err)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// handleMessageEvent handles the message events
|
||||
func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, error) {
|
||||
// update the userlist on a channel_join
|
||||
if ev.SubType == "channel_join" {
|
||||
b.Users, _ = b.sc.GetUsers()
|
||||
}
|
||||
|
||||
// Edit message
|
||||
if !b.GetBool("EditDisable") && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp {
|
||||
b.Log.Debugf("SubMessage %#v", ev.SubMessage)
|
||||
ev.User = ev.SubMessage.User
|
||||
ev.Text = ev.SubMessage.Text + b.GetString("EditSuffix")
|
||||
}
|
||||
|
||||
// use our own func because rtm.GetChannelInfo doesn't work for private channels
|
||||
channel, err := b.getChannelByID(ev.Channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rmsg := config.Message{Text: ev.Text, Channel: channel.Name, Account: b.Account, ID: "slack " + ev.Timestamp, Extra: make(map[string][]interface{})}
|
||||
|
||||
// find the user id and name
|
||||
if ev.User != "" && ev.SubType != messageDeleted && ev.SubType != "file_comment" {
|
||||
user, err := b.rtm.GetUserInfo(ev.User)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rmsg.UserID = user.ID
|
||||
rmsg.Username = user.Name
|
||||
if user.Profile.DisplayName != "" {
|
||||
rmsg.Username = user.Profile.DisplayName
|
||||
}
|
||||
}
|
||||
|
||||
// See if we have some text in the attachments
|
||||
if rmsg.Text == "" {
|
||||
for _, attach := range ev.Attachments {
|
||||
if attach.Text != "" {
|
||||
if attach.Title != "" {
|
||||
rmsg.Text = attach.Title + "\n"
|
||||
}
|
||||
rmsg.Text += attach.Text
|
||||
} else {
|
||||
rmsg.Text = attach.Fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// when using webhookURL we can't check if it's our webhook or not for now
|
||||
if rmsg.Username == "" && ev.BotID != "" && b.GetString("WebhookURL") == "" {
|
||||
bot, err := b.rtm.GetBotInfo(ev.BotID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bot.Name != "" {
|
||||
rmsg.Username = bot.Name
|
||||
if ev.Username != "" {
|
||||
rmsg.Username = ev.Username
|
||||
}
|
||||
rmsg.UserID = bot.ID
|
||||
}
|
||||
|
||||
// fixes issues with matterircd users
|
||||
if bot.Name == "Slack API Tester" {
|
||||
user, err := b.rtm.GetUserInfo(ev.User)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rmsg.UserID = user.ID
|
||||
rmsg.Username = user.Name
|
||||
if user.Profile.DisplayName != "" {
|
||||
rmsg.Username = user.Profile.DisplayName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// file comments are set by the system (because there is no username given)
|
||||
if ev.SubType == "file_comment" {
|
||||
rmsg.Username = "system"
|
||||
}
|
||||
|
||||
// do we have a /me action
|
||||
if ev.SubType == "me_message" {
|
||||
rmsg.Event = config.EVENT_USER_ACTION
|
||||
}
|
||||
|
||||
// Handle join/leave
|
||||
if ev.SubType == "channel_leave" || ev.SubType == "channel_join" {
|
||||
rmsg.Username = "system"
|
||||
rmsg.Event = config.EVENT_JOIN_LEAVE
|
||||
}
|
||||
|
||||
// edited messages have a submessage, use this timestamp
|
||||
if ev.SubMessage != nil {
|
||||
rmsg.ID = "slack " + ev.SubMessage.Timestamp
|
||||
}
|
||||
|
||||
// deleted message event
|
||||
if ev.SubType == messageDeleted {
|
||||
rmsg.Text = config.EVENT_MSG_DELETE
|
||||
rmsg.Event = config.EVENT_MSG_DELETE
|
||||
rmsg.ID = "slack " + ev.DeletedTimestamp
|
||||
}
|
||||
|
||||
// topic change event
|
||||
if ev.SubType == "channel_topic" || ev.SubType == "channel_purpose" {
|
||||
rmsg.Event = config.EVENT_TOPIC_CHANGE
|
||||
}
|
||||
|
||||
// Only deleted messages can have a empty username and text
|
||||
if (rmsg.Text == "" || rmsg.Username == "") && ev.SubType != messageDeleted {
|
||||
// this is probably a webhook we couldn't resolve
|
||||
if ev.BotID != "" {
|
||||
return nil, fmt.Errorf("probably an incoming webhook we couldn't resolve (maybe ourselves)")
|
||||
}
|
||||
return nil, fmt.Errorf("empty message and not a deleted message")
|
||||
}
|
||||
|
||||
// save the attachments, so that we can send them to other slack (compatible) bridges
|
||||
if len(ev.Attachments) > 0 {
|
||||
rmsg.Extra["slack_attachment"] = append(rmsg.Extra["slack_attachment"], ev.Attachments)
|
||||
}
|
||||
|
||||
// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
|
||||
if ev.File != nil {
|
||||
err := b.handleDownloadFile(&rmsg, ev.File)
|
||||
if err != nil {
|
||||
b.Log.Errorf("download failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &rmsg, nil
|
||||
}
|
||||
|
||||
// sendWebhook uses the configured WebhookURL to send the message
|
||||
func (b *Bslack) sendWebhook(msg config.Message) (string, error) {
|
||||
// skip events
|
||||
if msg.Event != "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if b.GetBool("PrefixMessagesWithNick") {
|
||||
msg.Text = msg.Username + msg.Text
|
||||
}
|
||||
|
||||
if msg.Extra != nil {
|
||||
// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl"))
|
||||
matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: msg.Channel, UserName: rmsg.Username, Text: rmsg.Text}
|
||||
b.mh.Send(matterMessage)
|
||||
}
|
||||
|
||||
// webhook doesn't support file uploads, so we add the url manually
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.URL != "" {
|
||||
msg.Text += " " + fi.URL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we have native slack_attachments add them
|
||||
var attachs []slack.Attachment
|
||||
if len(msg.Extra["slack_attachment"]) > 0 {
|
||||
for _, attach := range msg.Extra["slack_attachment"] {
|
||||
attachs = append(attachs, attach.([]slack.Attachment)...)
|
||||
}
|
||||
}
|
||||
|
||||
iconURL := config.GetIconURL(&msg, b.GetString("iconurl"))
|
||||
matterMessage := matterhook.OMessage{IconURL: iconURL, Attachments: attachs, Channel: msg.Channel, UserName: msg.Username, Text: msg.Text}
|
||||
if msg.Avatar != "" {
|
||||
matterMessage.IconURL = msg.Avatar
|
||||
}
|
||||
err := b.mh.Send(matterMessage)
|
||||
if err != nil {
|
||||
b.Log.Error(err)
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// skipMessageEvent skips event that need to be skipped :-)
|
||||
func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
|
||||
if ev.SubType == "channel_leave" || ev.SubType == "channel_join" {
|
||||
return b.GetBool("nosendjoinpart")
|
||||
}
|
||||
|
||||
// ignore pinned items
|
||||
if ev.SubType == "pinned_item" || ev.SubType == "unpinned_item" {
|
||||
return true
|
||||
}
|
||||
|
||||
// do not send messages from ourself
|
||||
if b.GetString("WebhookURL") == "" && b.GetString("WebhookBindAddress") == "" && ev.Username == b.si.User.Name {
|
||||
return true
|
||||
}
|
||||
|
||||
// skip messages we made ourselves
|
||||
if len(ev.Attachments) > 0 {
|
||||
if ev.Attachments[0].CallbackID == "matterbridge_"+b.uuid {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if !b.GetBool("EditDisable") && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp {
|
||||
// it seems ev.SubMessage.Edited == nil when slack unfurls
|
||||
// do not forward these messages #266
|
||||
if ev.SubMessage.Edited == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -2,36 +2,31 @@ package bsshchat
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/shazow/ssh-chat/sshd"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/shazow/ssh-chat/sshd"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Bsshchat struct {
|
||||
r *bufio.Scanner
|
||||
w io.WriteCloser
|
||||
*config.BridgeConfig
|
||||
*bridge.Config
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "sshchat"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": protocol})
|
||||
}
|
||||
|
||||
func New(cfg *config.BridgeConfig) *Bsshchat {
|
||||
return &Bsshchat{BridgeConfig: cfg}
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
return &Bsshchat{Config: cfg}
|
||||
}
|
||||
|
||||
func (b *Bsshchat) Connect() error {
|
||||
var err error
|
||||
flog.Infof("Connecting %s", b.Config.Server)
|
||||
b.Log.Infof("Connecting %s", b.GetString("Server"))
|
||||
go func() {
|
||||
err = sshd.ConnectShell(b.Config.Server, b.Config.Nick, func(r io.Reader, w io.WriteCloser) error {
|
||||
err = sshd.ConnectShell(b.GetString("Server"), b.GetString("Nick"), func(r io.Reader, w io.WriteCloser) error {
|
||||
b.r = bufio.NewScanner(r)
|
||||
b.w = w
|
||||
b.r.Scan()
|
||||
@ -41,10 +36,10 @@ func (b *Bsshchat) Connect() error {
|
||||
})
|
||||
}()
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
b.Log.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
flog.Info("Connection succeeded")
|
||||
b.Log.Info("Connection succeeded")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -61,7 +56,7 @@ func (b *Bsshchat) Send(msg config.Message) (string, error) {
|
||||
if msg.Event == config.EVENT_MSG_DELETE {
|
||||
return "", nil
|
||||
}
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
b.Log.Debugf("=> Receiving %#v", msg)
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.w.Write([]byte(rmsg.Username + rmsg.Text + "\r\n"))
|
||||
@ -74,6 +69,9 @@ func (b *Bsshchat) Send(msg config.Message) (string, error) {
|
||||
}
|
||||
if fi.URL != "" {
|
||||
msg.Text = fi.URL
|
||||
if fi.Comment != "" {
|
||||
msg.Text = fi.Comment + ": " + fi.URL
|
||||
}
|
||||
}
|
||||
b.w.Write([]byte(msg.Username + msg.Text))
|
||||
}
|
||||
@ -93,10 +91,10 @@ func (b *Bsshchat) sshchatKeepAlive() chan bool {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
flog.Debugf("PING")
|
||||
b.Log.Debugf("PING")
|
||||
err := b.xc.PingC2S("", "")
|
||||
if err != nil {
|
||||
flog.Debugf("PING failed %#v", err)
|
||||
b.Log.Debugf("PING failed %#v", err)
|
||||
}
|
||||
case <-done:
|
||||
return
|
||||
@ -123,6 +121,10 @@ func (b *Bsshchat) handleSshChat() error {
|
||||
wait := true
|
||||
for {
|
||||
if b.r.Scan() {
|
||||
// ignore messages from ourselves
|
||||
if !strings.Contains(b.r.Text(), "\033[K") {
|
||||
continue
|
||||
}
|
||||
res := strings.Split(stripPrompt(b.r.Text()), ":")
|
||||
if res[0] == "-> Set theme" {
|
||||
wait = false
|
||||
@ -130,7 +132,7 @@ func (b *Bsshchat) handleSshChat() error {
|
||||
continue
|
||||
}
|
||||
if !wait {
|
||||
flog.Debugf("message %#v", res)
|
||||
b.Log.Debugf("<= Message %#v", res)
|
||||
rmsg := config.Message{Username: res[0], Text: strings.Join(res[1:], ":"), Channel: "sshchat", Account: b.Account, UserID: "nick"}
|
||||
b.Remote <- rmsg
|
||||
}
|
||||
|
@ -2,11 +2,13 @@ package bsteam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/Philipp15b/go-steam"
|
||||
"github.com/Philipp15b/go-steam/protocol/steamlang"
|
||||
"github.com/Philipp15b/go-steam/steamid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
//"io/ioutil"
|
||||
"strconv"
|
||||
"sync"
|
||||
@ -18,31 +20,24 @@ type Bsteam struct {
|
||||
connected chan struct{}
|
||||
userMap map[steamid.SteamId]string
|
||||
sync.RWMutex
|
||||
*config.BridgeConfig
|
||||
*bridge.Config
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "steam"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": protocol})
|
||||
}
|
||||
|
||||
func New(cfg *config.BridgeConfig) *Bsteam {
|
||||
b := &Bsteam{BridgeConfig: cfg}
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
b := &Bsteam{Config: cfg}
|
||||
b.userMap = make(map[steamid.SteamId]string)
|
||||
b.connected = make(chan struct{})
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Bsteam) Connect() error {
|
||||
flog.Info("Connecting")
|
||||
b.Log.Info("Connecting")
|
||||
b.c = steam.NewClient()
|
||||
go b.handleEvents()
|
||||
go b.c.Connect()
|
||||
select {
|
||||
case <-b.connected:
|
||||
flog.Info("Connection succeeded")
|
||||
b.Log.Info("Connection succeeded")
|
||||
case <-time.After(time.Second * 30):
|
||||
return fmt.Errorf("connection timed out")
|
||||
}
|
||||
@ -73,6 +68,30 @@ func (b *Bsteam) Send(msg config.Message) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Handle files
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, rmsg.Username+rmsg.Text)
|
||||
}
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.Comment != "" {
|
||||
msg.Text += fi.Comment + ": "
|
||||
}
|
||||
if fi.URL != "" {
|
||||
msg.Text = fi.URL
|
||||
if fi.Comment != "" {
|
||||
msg.Text = fi.Comment + ": " + fi.URL
|
||||
}
|
||||
}
|
||||
b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text)
|
||||
return "", nil
|
||||
}
|
||||
@ -88,18 +107,18 @@ func (b *Bsteam) getNick(id steamid.SteamId) string {
|
||||
|
||||
func (b *Bsteam) handleEvents() {
|
||||
myLoginInfo := new(steam.LogOnDetails)
|
||||
myLoginInfo.Username = b.Config.Login
|
||||
myLoginInfo.Password = b.Config.Password
|
||||
myLoginInfo.AuthCode = b.Config.AuthCode
|
||||
myLoginInfo.Username = b.GetString("Login")
|
||||
myLoginInfo.Password = b.GetString("Password")
|
||||
myLoginInfo.AuthCode = b.GetString("AuthCode")
|
||||
// Attempt to read existing auth hash to avoid steam guard.
|
||||
// Maybe works
|
||||
//myLoginInfo.SentryFileHash, _ = ioutil.ReadFile("sentry")
|
||||
for event := range b.c.Events() {
|
||||
//flog.Info(event)
|
||||
//b.Log.Info(event)
|
||||
switch e := event.(type) {
|
||||
case *steam.ChatMsgEvent:
|
||||
flog.Debugf("Receiving ChatMsgEvent: %#v", e)
|
||||
flog.Debugf("Sending message from %s on %s to gateway", b.getNick(e.ChatterId), b.Account)
|
||||
b.Log.Debugf("Receiving ChatMsgEvent: %#v", e)
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", b.getNick(e.ChatterId), b.Account)
|
||||
var channel int64
|
||||
if e.ChatRoomId == 0 {
|
||||
channel = int64(e.ChatterId)
|
||||
@ -110,7 +129,7 @@ func (b *Bsteam) handleEvents() {
|
||||
msg := config.Message{Username: b.getNick(e.ChatterId), Text: e.Message, Channel: strconv.FormatInt(channel, 10), Account: b.Account, UserID: strconv.FormatInt(int64(e.ChatterId), 10)}
|
||||
b.Remote <- msg
|
||||
case *steam.PersonaStateEvent:
|
||||
flog.Debugf("PersonaStateEvent: %#v\n", e)
|
||||
b.Log.Debugf("PersonaStateEvent: %#v\n", e)
|
||||
b.Lock()
|
||||
b.userMap[e.FriendId] = e.Name
|
||||
b.Unlock()
|
||||
@ -118,47 +137,47 @@ func (b *Bsteam) handleEvents() {
|
||||
b.c.Auth.LogOn(myLoginInfo)
|
||||
case *steam.MachineAuthUpdateEvent:
|
||||
/*
|
||||
flog.Info("authupdate", e)
|
||||
flog.Info("hash", e.Hash)
|
||||
b.Log.Info("authupdate", e)
|
||||
b.Log.Info("hash", e.Hash)
|
||||
ioutil.WriteFile("sentry", e.Hash, 0666)
|
||||
*/
|
||||
case *steam.LogOnFailedEvent:
|
||||
flog.Info("Logon failed", e)
|
||||
b.Log.Info("Logon failed", e)
|
||||
switch e.Result {
|
||||
case steamlang.EResult_AccountLogonDeniedNeedTwoFactorCode:
|
||||
{
|
||||
flog.Info("Steam guard isn't letting me in! Enter 2FA code:")
|
||||
b.Log.Info("Steam guard isn't letting me in! Enter 2FA code:")
|
||||
var code string
|
||||
fmt.Scanf("%s", &code)
|
||||
myLoginInfo.TwoFactorCode = code
|
||||
}
|
||||
case steamlang.EResult_AccountLogonDenied:
|
||||
{
|
||||
flog.Info("Steam guard isn't letting me in! Enter auth code:")
|
||||
b.Log.Info("Steam guard isn't letting me in! Enter auth code:")
|
||||
var code string
|
||||
fmt.Scanf("%s", &code)
|
||||
myLoginInfo.AuthCode = code
|
||||
}
|
||||
default:
|
||||
log.Errorf("LogOnFailedEvent: %#v ", e.Result)
|
||||
b.Log.Errorf("LogOnFailedEvent: %#v ", e.Result)
|
||||
// TODO: Handle EResult_InvalidLoginAuthCode
|
||||
return
|
||||
}
|
||||
case *steam.LoggedOnEvent:
|
||||
flog.Debugf("LoggedOnEvent: %#v", e)
|
||||
b.Log.Debugf("LoggedOnEvent: %#v", e)
|
||||
b.connected <- struct{}{}
|
||||
flog.Debugf("setting online")
|
||||
b.Log.Debugf("setting online")
|
||||
b.c.Social.SetPersonaState(steamlang.EPersonaState_Online)
|
||||
case *steam.DisconnectedEvent:
|
||||
flog.Info("Disconnected")
|
||||
flog.Info("Attempting to reconnect...")
|
||||
b.Log.Info("Disconnected")
|
||||
b.Log.Info("Attempting to reconnect...")
|
||||
b.c.Connect()
|
||||
case steam.FatalErrorEvent:
|
||||
flog.Error(e)
|
||||
b.Log.Error(e)
|
||||
case error:
|
||||
flog.Error(e)
|
||||
b.Log.Error(e)
|
||||
default:
|
||||
flog.Debugf("unknown event %#v", e)
|
||||
b.Log.Debugf("unknown event %#v", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,16 @@ package btelegram
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/russross/blackfriday"
|
||||
"html"
|
||||
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
|
||||
type customHtml struct {
|
||||
type customHTML struct {
|
||||
blackfriday.Renderer
|
||||
}
|
||||
|
||||
func (options *customHtml) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||
func (options *customHTML) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||
marker := out.Len()
|
||||
|
||||
if !text() {
|
||||
@ -20,32 +21,32 @@ func (options *customHtml) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||
out.WriteString("\n")
|
||||
}
|
||||
|
||||
func (options *customHtml) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||
func (options *customHTML) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||
out.WriteString("<pre>")
|
||||
|
||||
out.WriteString(html.EscapeString(string(text)))
|
||||
out.WriteString("</pre>\n")
|
||||
}
|
||||
|
||||
func (options *customHtml) Header(out *bytes.Buffer, text func() bool, level int, id string) {
|
||||
func (options *customHTML) Header(out *bytes.Buffer, text func() bool, level int, id string) {
|
||||
options.Paragraph(out, text)
|
||||
}
|
||||
|
||||
func (options *customHtml) HRule(out *bytes.Buffer) {
|
||||
func (options *customHTML) HRule(out *bytes.Buffer) {
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (options *customHtml) BlockQuote(out *bytes.Buffer, text []byte) {
|
||||
func (options *customHTML) BlockQuote(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("> ")
|
||||
out.Write(text)
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (options *customHtml) List(out *bytes.Buffer, text func() bool, flags int) {
|
||||
func (options *customHTML) List(out *bytes.Buffer, text func() bool, flags int) {
|
||||
options.Paragraph(out, text)
|
||||
}
|
||||
|
||||
func (options *customHtml) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||
func (options *customHTML) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||
out.WriteString("- ")
|
||||
out.Write(text)
|
||||
out.WriteByte('\n')
|
||||
@ -53,7 +54,7 @@ func (options *customHtml) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||
|
||||
func makeHTML(input string) string {
|
||||
return string(blackfriday.Markdown([]byte(input),
|
||||
&customHtml{blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML|blackfriday.HTML_SKIP_IMAGES, "", "")},
|
||||
&customHTML{blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML|blackfriday.HTML_SKIP_IMAGES, "", "")},
|
||||
blackfriday.EXTENSION_NO_INTRA_EMPHASIS|
|
||||
blackfriday.EXTENSION_FENCED_CODE|
|
||||
blackfriday.EXTENSION_AUTOLINK|
|
||||
|
@ -1,56 +1,49 @@
|
||||
package btelegram
|
||||
|
||||
import (
|
||||
"html"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Btelegram struct {
|
||||
c *tgbotapi.BotAPI
|
||||
*config.BridgeConfig
|
||||
*bridge.Config
|
||||
avatarMap map[string]string // keep cache of userid and avatar sha
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "telegram"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": protocol})
|
||||
}
|
||||
|
||||
func New(cfg *config.BridgeConfig) *Btelegram {
|
||||
return &Btelegram{BridgeConfig: cfg, avatarMap: make(map[string]string)}
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
return &Btelegram{Config: cfg, avatarMap: make(map[string]string)}
|
||||
}
|
||||
|
||||
func (b *Btelegram) Connect() error {
|
||||
var err error
|
||||
flog.Info("Connecting")
|
||||
b.c, err = tgbotapi.NewBotAPI(b.Config.Token)
|
||||
b.Log.Info("Connecting")
|
||||
b.c, err = tgbotapi.NewBotAPI(b.GetString("Token"))
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
b.Log.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
u := tgbotapi.NewUpdate(0)
|
||||
u.Timeout = 60
|
||||
updates, err := b.c.GetUpdatesChan(u)
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
b.Log.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
flog.Info("Connection succeeded")
|
||||
b.Log.Info("Connection succeeded")
|
||||
go b.handleRecv(updates)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Btelegram) Disconnect() error {
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
|
||||
@ -58,7 +51,9 @@ func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
|
||||
}
|
||||
|
||||
func (b *Btelegram) Send(msg config.Message) (string, error) {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
b.Log.Debugf("=> Receiving %#v", msg)
|
||||
|
||||
// get the chatid
|
||||
chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -66,20 +61,14 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
|
||||
|
||||
// map the file SHA to our user (caches the avatar)
|
||||
if msg.Event == config.EVENT_AVATAR_DOWNLOAD {
|
||||
fi := msg.Extra["file"][0].(config.FileInfo)
|
||||
/* if we have a sha we have successfully uploaded the file to the media server,
|
||||
so we can now cache the sha */
|
||||
if fi.SHA != "" {
|
||||
flog.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID)
|
||||
b.avatarMap[msg.UserID] = fi.SHA
|
||||
}
|
||||
return "", nil
|
||||
return b.cacheAvatar(&msg)
|
||||
}
|
||||
|
||||
if b.Config.MessageFormat == "HTML" {
|
||||
if b.GetString("MessageFormat") == "HTML" {
|
||||
msg.Text = makeHTML(msg.Text)
|
||||
}
|
||||
|
||||
// Delete message
|
||||
if msg.Event == config.EVENT_MSG_DELETE {
|
||||
if msg.ID == "" {
|
||||
return "", nil
|
||||
@ -92,21 +81,40 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Upload a file if it exists
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.sendMessage(chatid, rmsg.Username, rmsg.Text)
|
||||
}
|
||||
// check if we have files to upload (from slack, telegram or mattermost)
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
b.handleUploadFile(&msg, chatid)
|
||||
}
|
||||
}
|
||||
|
||||
// edit the message if we have a msg ID
|
||||
if msg.ID != "" {
|
||||
msgid, err := strconv.Atoi(msg.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.ToLower(b.GetString("MessageFormat")) == "htmlnick" {
|
||||
b.Log.Debug("Using mode HTML - nick only")
|
||||
msg.Text = html.EscapeString(msg.Text)
|
||||
}
|
||||
m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
|
||||
if b.Config.MessageFormat == "HTML" {
|
||||
flog.Debug("Using mode HTML")
|
||||
if b.GetString("MessageFormat") == "HTML" {
|
||||
b.Log.Debug("Using mode HTML")
|
||||
m.ParseMode = tgbotapi.ModeHTML
|
||||
}
|
||||
if b.Config.MessageFormat == "Markdown" {
|
||||
flog.Debug("Using mode markdown")
|
||||
if b.GetString("MessageFormat") == "Markdown" {
|
||||
b.Log.Debug("Using mode markdown")
|
||||
m.ParseMode = tgbotapi.ModeMarkdown
|
||||
}
|
||||
if strings.ToLower(b.GetString("MessageFormat")) == "htmlnick" {
|
||||
b.Log.Debug("Using mode HTML - nick only")
|
||||
m.ParseMode = tgbotapi.ModeHTML
|
||||
}
|
||||
_, err = b.c.Send(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -114,114 +122,84 @@ func (b *Btelegram) Send(msg config.Message) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.sendMessage(chatid, rmsg.Username+rmsg.Text)
|
||||
}
|
||||
// check if we have files to upload (from slack, telegram or mattermost)
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
var c tgbotapi.Chattable
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
file := tgbotapi.FileBytes{fi.Name, *fi.Data}
|
||||
re := regexp.MustCompile(".(jpg|png)$")
|
||||
if re.MatchString(fi.Name) {
|
||||
c = tgbotapi.NewPhotoUpload(chatid, file)
|
||||
} else {
|
||||
c = tgbotapi.NewDocumentUpload(chatid, file)
|
||||
}
|
||||
_, err := b.c.Send(c)
|
||||
if err != nil {
|
||||
log.Errorf("file upload failed: %#v", err)
|
||||
}
|
||||
if fi.Comment != "" {
|
||||
b.sendMessage(chatid, msg.Username+fi.Comment)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
return b.sendMessage(chatid, msg.Username+msg.Text)
|
||||
// Post normal message
|
||||
return b.sendMessage(chatid, msg.Username, msg.Text)
|
||||
}
|
||||
|
||||
func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
||||
for update := range updates {
|
||||
flog.Debugf("Receiving from telegram: %#v", update.Message)
|
||||
if update.Message == nil {
|
||||
flog.Error("Getting nil messages, this shouldn't happen.")
|
||||
b.Log.Debugf("== Receiving event: %#v", update.Message)
|
||||
|
||||
if update.Message == nil && update.ChannelPost == nil && update.EditedMessage == nil && update.EditedChannelPost == nil {
|
||||
b.Log.Error("Getting nil messages, this shouldn't happen.")
|
||||
continue
|
||||
}
|
||||
var message *tgbotapi.Message
|
||||
username := ""
|
||||
channel := ""
|
||||
text := ""
|
||||
|
||||
fmsg := config.Message{Extra: make(map[string][]interface{})}
|
||||
var message *tgbotapi.Message
|
||||
|
||||
rmsg := config.Message{Account: b.Account, Extra: make(map[string][]interface{})}
|
||||
|
||||
// handle channels
|
||||
if update.ChannelPost != nil {
|
||||
message = update.ChannelPost
|
||||
rmsg.Text = message.Text
|
||||
}
|
||||
if update.EditedChannelPost != nil && !b.Config.EditDisable {
|
||||
|
||||
// edited channel message
|
||||
if update.EditedChannelPost != nil && !b.GetBool("EditDisable") {
|
||||
message = update.EditedChannelPost
|
||||
message.Text = message.Text + b.Config.EditSuffix
|
||||
rmsg.Text = rmsg.Text + message.Text + b.GetString("EditSuffix")
|
||||
}
|
||||
|
||||
// handle groups
|
||||
if update.Message != nil {
|
||||
message = update.Message
|
||||
rmsg.Text = message.Text
|
||||
}
|
||||
if update.EditedMessage != nil && !b.Config.EditDisable {
|
||||
|
||||
// edited group message
|
||||
if update.EditedMessage != nil && !b.GetBool("EditDisable") {
|
||||
message = update.EditedMessage
|
||||
message.Text = message.Text + b.Config.EditSuffix
|
||||
rmsg.Text = rmsg.Text + message.Text + b.GetString("EditSuffix")
|
||||
}
|
||||
|
||||
// set the ID's from the channel or group message
|
||||
rmsg.ID = strconv.Itoa(message.MessageID)
|
||||
rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10)
|
||||
|
||||
// handle username
|
||||
if message.From != nil {
|
||||
if b.Config.UseFirstName {
|
||||
username = message.From.FirstName
|
||||
rmsg.UserID = strconv.Itoa(message.From.ID)
|
||||
if b.GetBool("UseFirstName") {
|
||||
rmsg.Username = message.From.FirstName
|
||||
}
|
||||
if username == "" {
|
||||
username = message.From.UserName
|
||||
if username == "" {
|
||||
username = message.From.FirstName
|
||||
if rmsg.Username == "" {
|
||||
rmsg.Username = message.From.UserName
|
||||
if rmsg.Username == "" {
|
||||
rmsg.Username = message.From.FirstName
|
||||
}
|
||||
}
|
||||
text = message.Text
|
||||
channel = strconv.FormatInt(message.Chat.ID, 10)
|
||||
// only download avatars if we have a place to upload them (configured mediaserver)
|
||||
if b.General.MediaServerUpload != "" {
|
||||
b.handleDownloadAvatar(message.From.ID, channel)
|
||||
b.handleDownloadAvatar(message.From.ID, rmsg.Channel)
|
||||
}
|
||||
}
|
||||
|
||||
if username == "" {
|
||||
username = "unknown"
|
||||
}
|
||||
if message.Sticker != nil {
|
||||
b.handleDownload(message.Sticker, message.Caption, &fmsg)
|
||||
}
|
||||
if message.Video != nil {
|
||||
b.handleDownload(message.Video, message.Caption, &fmsg)
|
||||
}
|
||||
if message.Photo != nil {
|
||||
b.handleDownload(message.Photo, message.Caption, &fmsg)
|
||||
}
|
||||
if message.Document != nil {
|
||||
b.handleDownload(message.Document, message.Caption, &fmsg)
|
||||
}
|
||||
if message.Voice != nil {
|
||||
b.handleDownload(message.Voice, message.Caption, &fmsg)
|
||||
}
|
||||
if message.Audio != nil {
|
||||
b.handleDownload(message.Audio, message.Caption, &fmsg)
|
||||
// if we really didn't find a username, set it to unknown
|
||||
if rmsg.Username == "" {
|
||||
rmsg.Username = "unknown"
|
||||
}
|
||||
|
||||
// If UseInsecureURL is used we'll have a text in fmsg.Text
|
||||
if fmsg.Text != "" {
|
||||
text = text + fmsg.Text
|
||||
// handle any downloads
|
||||
err := b.handleDownload(message, &rmsg)
|
||||
if err != nil {
|
||||
b.Log.Errorf("download failed: %s", err)
|
||||
}
|
||||
|
||||
// handle forwarded messages
|
||||
if message.ForwardFrom != nil {
|
||||
usernameForward := ""
|
||||
if b.Config.UseFirstName {
|
||||
if b.GetBool("UseFirstName") {
|
||||
usernameForward = message.ForwardFrom.FirstName
|
||||
}
|
||||
if usernameForward == "" {
|
||||
@ -233,14 +211,14 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
||||
if usernameForward == "" {
|
||||
usernameForward = "unknown"
|
||||
}
|
||||
text = "Forwarded from " + usernameForward + ": " + text
|
||||
rmsg.Text = "Forwarded from " + usernameForward + ": " + rmsg.Text
|
||||
}
|
||||
|
||||
// quote the previous message
|
||||
if message.ReplyToMessage != nil {
|
||||
usernameReply := ""
|
||||
if message.ReplyToMessage.From != nil {
|
||||
if b.Config.UseFirstName {
|
||||
if b.GetBool("UseFirstName") {
|
||||
usernameReply = message.ReplyToMessage.From.FirstName
|
||||
}
|
||||
if usernameReply == "" {
|
||||
@ -253,15 +231,21 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
||||
if usernameReply == "" {
|
||||
usernameReply = "unknown"
|
||||
}
|
||||
text = text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")"
|
||||
if !b.GetBool("QuoteDisable") {
|
||||
rmsg.Text = b.handleQuote(rmsg.Text, usernameReply, message.ReplyToMessage.Text)
|
||||
}
|
||||
}
|
||||
|
||||
if text != "" || len(fmsg.Extra) > 0 {
|
||||
avatar := helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General)
|
||||
flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
|
||||
msg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID), Extra: fmsg.Extra, Avatar: avatar}
|
||||
flog.Debugf("Message is %#v", msg)
|
||||
b.Remote <- msg
|
||||
if rmsg.Text != "" || len(rmsg.Extra) > 0 {
|
||||
rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text)
|
||||
// channels don't have (always?) user information. see #410
|
||||
if message.From != nil {
|
||||
rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.Itoa(message.From.ID), b.General)
|
||||
}
|
||||
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
|
||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||
b.Remote <- rmsg
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -278,60 +262,42 @@ func (b *Btelegram) getFileDirectURL(id string) string {
|
||||
// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
|
||||
// logs an error message if it fails
|
||||
func (b *Btelegram) handleDownloadAvatar(userid int, channel string) {
|
||||
msg := config.Message{Username: "system", Text: "avatar", Channel: channel, Account: b.Account, UserID: strconv.Itoa(userid), Event: config.EVENT_AVATAR_DOWNLOAD, Extra: make(map[string][]interface{})}
|
||||
rmsg := config.Message{Username: "system", Text: "avatar", Channel: channel, Account: b.Account, UserID: strconv.Itoa(userid), Event: config.EVENT_AVATAR_DOWNLOAD, Extra: make(map[string][]interface{})}
|
||||
if _, ok := b.avatarMap[strconv.Itoa(userid)]; !ok {
|
||||
photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1})
|
||||
if err != nil {
|
||||
flog.Errorf("Userprofile download failed for %#v %s", userid, err)
|
||||
b.Log.Errorf("Userprofile download failed for %#v %s", userid, err)
|
||||
}
|
||||
|
||||
if len(photos.Photos) > 0 {
|
||||
photo := photos.Photos[0][0]
|
||||
url := b.getFileDirectURL(photo.FileID)
|
||||
name := strconv.Itoa(userid) + ".png"
|
||||
flog.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize)
|
||||
if photo.FileSize <= b.General.MediaDownloadSize {
|
||||
data, err := helper.DownloadFile(url)
|
||||
if err != nil {
|
||||
flog.Errorf("download %s failed %#v", url, err)
|
||||
} else {
|
||||
flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url))
|
||||
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data, Avatar: true})
|
||||
flog.Debugf("Sending avatar download message from %#v on %s to gateway", userid, b.Account)
|
||||
flog.Debugf("Message is %#v", msg)
|
||||
b.Remote <- msg
|
||||
}
|
||||
} else {
|
||||
flog.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", name, photo.FileSize, b.General.MediaDownloadSize)
|
||||
b.Log.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize)
|
||||
|
||||
err := helper.HandleDownloadSize(b.Log, &rmsg, name, int64(photo.FileSize), b.General)
|
||||
if err != nil {
|
||||
b.Log.Error(err)
|
||||
return
|
||||
}
|
||||
data, err := helper.DownloadFile(url)
|
||||
if err != nil {
|
||||
b.Log.Errorf("download %s failed %#v", url, err)
|
||||
return
|
||||
}
|
||||
helper.HandleDownloadData(b.Log, &rmsg, name, rmsg.Text, "", data, b.General)
|
||||
b.Remote <- rmsg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Btelegram) handleDownload(file interface{}, comment string, msg *config.Message) {
|
||||
// handleDownloadFile handles file download
|
||||
func (b *Btelegram) handleDownload(message *tgbotapi.Message, rmsg *config.Message) error {
|
||||
size := 0
|
||||
url := ""
|
||||
name := ""
|
||||
text := ""
|
||||
fileid := ""
|
||||
switch v := file.(type) {
|
||||
case *tgbotapi.Audio:
|
||||
size = v.FileSize
|
||||
url = b.getFileDirectURL(v.FileID)
|
||||
urlPart := strings.Split(url, "/")
|
||||
name = urlPart[len(urlPart)-1]
|
||||
text = " " + url
|
||||
fileid = v.FileID
|
||||
case *tgbotapi.Voice:
|
||||
size = v.FileSize
|
||||
url = b.getFileDirectURL(v.FileID)
|
||||
urlPart := strings.Split(url, "/")
|
||||
name = urlPart[len(urlPart)-1]
|
||||
text = " " + url
|
||||
if !strings.HasSuffix(name, ".ogg") {
|
||||
name = name + ".ogg"
|
||||
}
|
||||
fileid = v.FileID
|
||||
case *tgbotapi.Sticker:
|
||||
var url, name, text string
|
||||
|
||||
if message.Sticker != nil {
|
||||
v := message.Sticker
|
||||
size = v.FileSize
|
||||
url = b.getFileDirectURL(v.FileID)
|
||||
urlPart := strings.Split(url, "/")
|
||||
@ -340,63 +306,136 @@ func (b *Btelegram) handleDownload(file interface{}, comment string, msg *config
|
||||
name = name + ".webp"
|
||||
}
|
||||
text = " " + url
|
||||
fileid = v.FileID
|
||||
case *tgbotapi.Video:
|
||||
}
|
||||
if message.Video != nil {
|
||||
v := message.Video
|
||||
size = v.FileSize
|
||||
url = b.getFileDirectURL(v.FileID)
|
||||
urlPart := strings.Split(url, "/")
|
||||
name = urlPart[len(urlPart)-1]
|
||||
text = " " + url
|
||||
fileid = v.FileID
|
||||
case *[]tgbotapi.PhotoSize:
|
||||
photos := *v
|
||||
}
|
||||
if message.Photo != nil {
|
||||
photos := *message.Photo
|
||||
size = photos[len(photos)-1].FileSize
|
||||
url = b.getFileDirectURL(photos[len(photos)-1].FileID)
|
||||
urlPart := strings.Split(url, "/")
|
||||
name = urlPart[len(urlPart)-1]
|
||||
text = " " + url
|
||||
case *tgbotapi.Document:
|
||||
}
|
||||
if message.Document != nil {
|
||||
v := message.Document
|
||||
size = v.FileSize
|
||||
url = b.getFileDirectURL(v.FileID)
|
||||
name = v.FileName
|
||||
text = " " + v.FileName + " : " + url
|
||||
fileid = v.FileID
|
||||
}
|
||||
if b.Config.UseInsecureURL {
|
||||
flog.Debugf("Setting message text to :%s", text)
|
||||
msg.Text = text
|
||||
return
|
||||
if message.Voice != nil {
|
||||
v := message.Voice
|
||||
size = v.FileSize
|
||||
url = b.getFileDirectURL(v.FileID)
|
||||
urlPart := strings.Split(url, "/")
|
||||
name = urlPart[len(urlPart)-1]
|
||||
text = " " + url
|
||||
if !strings.HasSuffix(name, ".ogg") {
|
||||
name = name + ".ogg"
|
||||
}
|
||||
}
|
||||
if message.Audio != nil {
|
||||
v := message.Audio
|
||||
size = v.FileSize
|
||||
url = b.getFileDirectURL(v.FileID)
|
||||
urlPart := strings.Split(url, "/")
|
||||
name = urlPart[len(urlPart)-1]
|
||||
text = " " + url
|
||||
}
|
||||
// if name is empty we didn't match a thing to download
|
||||
if name == "" {
|
||||
return nil
|
||||
}
|
||||
// use the URL instead of native upload
|
||||
if b.GetBool("UseInsecureURL") {
|
||||
b.Log.Debugf("Setting message text to :%s", text)
|
||||
rmsg.Text = rmsg.Text + text
|
||||
return nil
|
||||
}
|
||||
// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
|
||||
flog.Debugf("trying to download %#v fileid %#v with size %#v", name, fileid, size)
|
||||
if size <= b.General.MediaDownloadSize {
|
||||
data, err := helper.DownloadFile(url)
|
||||
if err != nil {
|
||||
flog.Errorf("download %s failed %#v", url, err)
|
||||
} else {
|
||||
flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url))
|
||||
msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data, Comment: comment})
|
||||
}
|
||||
} else {
|
||||
flog.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", name, size, b.General.MediaDownloadSize)
|
||||
msg.Event = config.EVENT_FILE_FAILURE_SIZE
|
||||
msg.Extra[msg.Event] = append(msg.Extra[msg.Event], config.FileInfo{Name: name, Comment: comment, Size: int64(size)})
|
||||
err := helper.HandleDownloadSize(b.Log, rmsg, name, int64(size), b.General)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := helper.DownloadFile(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Btelegram) sendMessage(chatid int64, text string) (string, error) {
|
||||
m := tgbotapi.NewMessage(chatid, text)
|
||||
if b.Config.MessageFormat == "HTML" {
|
||||
flog.Debug("Using mode HTML")
|
||||
// handleUploadFile handles native upload of files
|
||||
func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64) (string, error) {
|
||||
var c tgbotapi.Chattable
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
file := tgbotapi.FileBytes{fi.Name, *fi.Data}
|
||||
re := regexp.MustCompile(".(jpg|png)$")
|
||||
if re.MatchString(fi.Name) {
|
||||
c = tgbotapi.NewPhotoUpload(chatid, file)
|
||||
} else {
|
||||
c = tgbotapi.NewDocumentUpload(chatid, file)
|
||||
}
|
||||
_, err := b.c.Send(c)
|
||||
if err != nil {
|
||||
b.Log.Errorf("file upload failed: %#v", err)
|
||||
}
|
||||
if fi.Comment != "" {
|
||||
b.sendMessage(chatid, msg.Username, fi.Comment)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (b *Btelegram) sendMessage(chatid int64, username, text string) (string, error) {
|
||||
m := tgbotapi.NewMessage(chatid, "")
|
||||
m.Text = username + text
|
||||
if b.GetString("MessageFormat") == "HTML" {
|
||||
b.Log.Debug("Using mode HTML")
|
||||
m.ParseMode = tgbotapi.ModeHTML
|
||||
}
|
||||
if b.Config.MessageFormat == "Markdown" {
|
||||
flog.Debug("Using mode markdown")
|
||||
if b.GetString("MessageFormat") == "Markdown" {
|
||||
b.Log.Debug("Using mode markdown")
|
||||
m.ParseMode = tgbotapi.ModeMarkdown
|
||||
}
|
||||
if strings.ToLower(b.GetString("MessageFormat")) == "htmlnick" {
|
||||
b.Log.Debug("Using mode HTML - nick only")
|
||||
m.Text = username + html.EscapeString(text)
|
||||
m.ParseMode = tgbotapi.ModeHTML
|
||||
}
|
||||
res, err := b.c.Send(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strconv.Itoa(res.MessageID), nil
|
||||
}
|
||||
|
||||
func (b *Btelegram) cacheAvatar(msg *config.Message) (string, error) {
|
||||
fi := msg.Extra["file"][0].(config.FileInfo)
|
||||
/* if we have a sha we have successfully uploaded the file to the media server,
|
||||
so we can now cache the sha */
|
||||
if fi.SHA != "" {
|
||||
b.Log.Debugf("Added %s to %s in avatarMap", fi.SHA, msg.UserID)
|
||||
b.avatarMap[msg.UserID] = fi.SHA
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string {
|
||||
format := b.GetString("quoteformat")
|
||||
if format == "" {
|
||||
format = "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
|
||||
}
|
||||
format = strings.Replace(format, "{MESSAGE}", message, -1)
|
||||
format = strings.Replace(format, "{QUOTENICK}", quoteNick, -1)
|
||||
format = strings.Replace(format, "{QUOTEMESSAGE}", quoteMessage, -1)
|
||||
return format
|
||||
}
|
||||
|
@ -2,44 +2,38 @@ package bxmpp
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/jpillora/backoff"
|
||||
"github.com/mattn/go-xmpp"
|
||||
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
"github.com/jpillora/backoff"
|
||||
"github.com/matterbridge/go-xmpp"
|
||||
"github.com/rs/xid"
|
||||
)
|
||||
|
||||
type Bxmpp struct {
|
||||
xc *xmpp.Client
|
||||
xmppMap map[string]string
|
||||
*config.BridgeConfig
|
||||
*bridge.Config
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "xmpp"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": protocol})
|
||||
}
|
||||
|
||||
func New(cfg *config.BridgeConfig) *Bxmpp {
|
||||
b := &Bxmpp{BridgeConfig: cfg}
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
b := &Bxmpp{Config: cfg}
|
||||
b.xmppMap = make(map[string]string)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Bxmpp) Connect() error {
|
||||
var err error
|
||||
flog.Infof("Connecting %s", b.Config.Server)
|
||||
b.Log.Infof("Connecting %s", b.GetString("Server"))
|
||||
b.xc, err = b.createXMPP()
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
b.Log.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
flog.Info("Connection succeeded")
|
||||
b.Log.Info("Connection succeeded")
|
||||
go func() {
|
||||
initial := true
|
||||
bf := &backoff.Backoff{
|
||||
@ -49,16 +43,16 @@ func (b *Bxmpp) Connect() error {
|
||||
}
|
||||
for {
|
||||
if initial {
|
||||
b.handleXmpp()
|
||||
b.handleXMPP()
|
||||
initial = false
|
||||
}
|
||||
d := bf.Duration()
|
||||
flog.Infof("Disconnected. Reconnecting in %s", d)
|
||||
b.Log.Infof("Disconnected. Reconnecting in %s", d)
|
||||
time.Sleep(d)
|
||||
b.xc, err = b.createXMPP()
|
||||
if err == nil {
|
||||
b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS}
|
||||
b.handleXmpp()
|
||||
b.handleXMPP()
|
||||
bf.Reset()
|
||||
}
|
||||
}
|
||||
@ -71,59 +65,65 @@ func (b *Bxmpp) Disconnect() error {
|
||||
}
|
||||
|
||||
func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error {
|
||||
b.xc.JoinMUCNoHistory(channel.Name+"@"+b.Config.Muc, b.Config.Nick)
|
||||
if channel.Options.Key != "" {
|
||||
b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
|
||||
b.xc.JoinProtectedMUC(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"), channel.Options.Key, xmpp.NoHistory, 0, nil)
|
||||
} else {
|
||||
b.xc.JoinMUCNoHistory(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bxmpp) Send(msg config.Message) (string, error) {
|
||||
var msgid = ""
|
||||
var msgreplaceid = ""
|
||||
// ignore delete messages
|
||||
if msg.Event == config.EVENT_MSG_DELETE {
|
||||
return "", nil
|
||||
}
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
b.Log.Debugf("=> Receiving %#v", msg)
|
||||
|
||||
// Upload a file (in xmpp case send the upload URL because xmpp has no native upload support)
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: rmsg.Channel + "@" + b.Config.Muc, Text: rmsg.Username + rmsg.Text})
|
||||
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: rmsg.Channel + "@" + b.GetString("Muc"), Text: rmsg.Username + rmsg.Text})
|
||||
}
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.Comment != "" {
|
||||
msg.Text += fi.Comment + ": "
|
||||
}
|
||||
if fi.URL != "" {
|
||||
msg.Text += fi.URL
|
||||
}
|
||||
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text})
|
||||
}
|
||||
return "", nil
|
||||
return b.handleUploadFile(&msg)
|
||||
}
|
||||
}
|
||||
|
||||
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text})
|
||||
return "", nil
|
||||
msgid = xid.New().String()
|
||||
if msg.ID != "" {
|
||||
msgid = msg.ID
|
||||
msgreplaceid = msg.ID
|
||||
}
|
||||
// Post normal message
|
||||
_, err := b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.GetString("Muc"), Text: msg.Username + msg.Text, ID: msgid, ReplaceID: msgreplaceid})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return msgid, nil
|
||||
}
|
||||
|
||||
func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {
|
||||
tc := new(tls.Config)
|
||||
tc.InsecureSkipVerify = b.Config.SkipTLSVerify
|
||||
tc.ServerName = strings.Split(b.Config.Server, ":")[0]
|
||||
tc.InsecureSkipVerify = b.GetBool("SkipTLSVerify")
|
||||
tc.ServerName = strings.Split(b.GetString("Server"), ":")[0]
|
||||
options := xmpp.Options{
|
||||
Host: b.Config.Server,
|
||||
User: b.Config.Jid,
|
||||
Password: b.Config.Password,
|
||||
NoTLS: true,
|
||||
StartTLS: true,
|
||||
TLSConfig: tc,
|
||||
|
||||
//StartTLS: false,
|
||||
Debug: b.General.Debug,
|
||||
Host: b.GetString("Server"),
|
||||
User: b.GetString("Jid"),
|
||||
Password: b.GetString("Password"),
|
||||
NoTLS: true,
|
||||
StartTLS: true,
|
||||
TLSConfig: tc,
|
||||
Debug: b.GetBool("debug"),
|
||||
Logger: b.Log.Writer(),
|
||||
Session: true,
|
||||
Status: "",
|
||||
StatusMessage: "",
|
||||
Resource: "",
|
||||
InsecureAllowUnencryptedAuth: false,
|
||||
//InsecureAllowUnencryptedAuth: true,
|
||||
}
|
||||
var err error
|
||||
b.xc, err = options.NewClient()
|
||||
@ -138,10 +138,10 @@ func (b *Bxmpp) xmppKeepAlive() chan bool {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
flog.Debugf("PING")
|
||||
b.Log.Debugf("PING")
|
||||
err := b.xc.PingC2S("", "")
|
||||
if err != nil {
|
||||
flog.Debugf("PING failed %#v", err)
|
||||
b.Log.Debugf("PING failed %#v", err)
|
||||
}
|
||||
case <-done:
|
||||
return
|
||||
@ -151,11 +151,11 @@ func (b *Bxmpp) xmppKeepAlive() chan bool {
|
||||
return done
|
||||
}
|
||||
|
||||
func (b *Bxmpp) handleXmpp() error {
|
||||
func (b *Bxmpp) handleXMPP() error {
|
||||
var ok bool
|
||||
var msgid string
|
||||
done := b.xmppKeepAlive()
|
||||
defer close(done)
|
||||
nodelay := time.Time{}
|
||||
for {
|
||||
m, err := b.xc.Recv()
|
||||
if err != nil {
|
||||
@ -163,25 +163,26 @@ func (b *Bxmpp) handleXmpp() error {
|
||||
}
|
||||
switch v := m.(type) {
|
||||
case xmpp.Chat:
|
||||
var channel, nick string
|
||||
if v.Type == "groupchat" {
|
||||
s := strings.Split(v.Remote, "@")
|
||||
if len(s) >= 2 {
|
||||
channel = s[0]
|
||||
b.Log.Debugf("== Receiving %#v", v)
|
||||
// skip invalid messages
|
||||
if b.skipMessage(v) {
|
||||
continue
|
||||
}
|
||||
s = strings.Split(s[1], "/")
|
||||
if len(s) == 2 {
|
||||
nick = s[1]
|
||||
msgid = v.ID
|
||||
if v.ReplaceID != "" {
|
||||
msgid = v.ReplaceID
|
||||
}
|
||||
if nick != b.Config.Nick && v.Stamp == nodelay && v.Text != "" && !strings.Contains(v.Text, "</subject>") {
|
||||
rmsg := config.Message{Username: nick, Text: v.Text, Channel: channel, Account: b.Account, UserID: v.Remote}
|
||||
rmsg.Text, ok = b.replaceAction(rmsg.Text)
|
||||
if ok {
|
||||
rmsg.Event = config.EVENT_USER_ACTION
|
||||
}
|
||||
flog.Debugf("Sending message from %s on %s to gateway", nick, b.Account)
|
||||
b.Remote <- rmsg
|
||||
rmsg := config.Message{Username: b.parseNick(v.Remote), Text: v.Text, Channel: b.parseChannel(v.Remote), Account: b.Account, UserID: v.Remote, ID: msgid}
|
||||
|
||||
// check if we have an action event
|
||||
rmsg.Text, ok = b.replaceAction(rmsg.Text)
|
||||
if ok {
|
||||
rmsg.Event = config.EVENT_USER_ACTION
|
||||
}
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
|
||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||
b.Remote <- rmsg
|
||||
}
|
||||
case xmpp.Presence:
|
||||
// do nothing
|
||||
@ -195,3 +196,71 @@ func (b *Bxmpp) replaceAction(text string) (string, bool) {
|
||||
}
|
||||
return text, false
|
||||
}
|
||||
|
||||
// handleUploadFile handles native upload of files
|
||||
func (b *Bxmpp) handleUploadFile(msg *config.Message) (string, error) {
|
||||
var urldesc = ""
|
||||
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.Comment != "" {
|
||||
msg.Text += fi.Comment + ": "
|
||||
}
|
||||
if fi.URL != "" {
|
||||
msg.Text = fi.URL
|
||||
if fi.Comment != "" {
|
||||
msg.Text = fi.Comment + ": " + fi.URL
|
||||
urldesc = fi.Comment
|
||||
}
|
||||
}
|
||||
_, err := b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.GetString("Muc"), Text: msg.Username + msg.Text})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if fi.URL != "" {
|
||||
b.xc.SendOOB(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.GetString("Muc"), Ooburl: fi.URL, Oobdesc: urldesc})
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (b *Bxmpp) parseNick(remote string) string {
|
||||
s := strings.Split(remote, "@")
|
||||
if len(s) > 0 {
|
||||
s = strings.Split(s[1], "/")
|
||||
if len(s) == 2 {
|
||||
return s[1] // nick
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *Bxmpp) parseChannel(remote string) string {
|
||||
s := strings.Split(remote, "@")
|
||||
if len(s) >= 2 {
|
||||
return s[0] // channel
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// skipMessage skips messages that need to be skipped
|
||||
func (b *Bxmpp) skipMessage(message xmpp.Chat) bool {
|
||||
// skip messages from ourselves
|
||||
if b.parseNick(message.Remote) == b.GetString("Nick") {
|
||||
return true
|
||||
}
|
||||
|
||||
// skip empty messages
|
||||
if message.Text == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
// skip subject messages
|
||||
if strings.Contains(message.Text, "</subject>") {
|
||||
return true
|
||||
}
|
||||
|
||||
// skip delayed messages
|
||||
t := time.Time{}
|
||||
return message.Stamp != t
|
||||
}
|
||||
|
170
bridge/zulip/zulip.go
Normal file
170
bridge/zulip/zulip.go
Normal file
@ -0,0 +1,170 @@
|
||||
package bzulip
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/bridge/helper"
|
||||
gzb "github.com/matterbridge/gozulipbot"
|
||||
)
|
||||
|
||||
type Bzulip struct {
|
||||
q *gzb.Queue
|
||||
bot *gzb.Bot
|
||||
streams map[int]string
|
||||
*bridge.Config
|
||||
}
|
||||
|
||||
func New(cfg *bridge.Config) bridge.Bridger {
|
||||
return &Bzulip{Config: cfg, streams: make(map[int]string)}
|
||||
}
|
||||
|
||||
func (b *Bzulip) Connect() error {
|
||||
bot := gzb.Bot{APIKey: b.GetString("token"), APIURL: b.GetString("server") + "/api/v1/", Email: b.GetString("login")}
|
||||
bot.Init()
|
||||
q, err := bot.RegisterAll()
|
||||
b.q = q
|
||||
b.bot = &bot
|
||||
if err != nil {
|
||||
b.Log.Errorf("Connect() %#v", err)
|
||||
return err
|
||||
}
|
||||
// init stream
|
||||
b.getChannel(0)
|
||||
b.Log.Info("Connection succeeded")
|
||||
go b.handleQueue()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bzulip) Disconnect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bzulip) JoinChannel(channel config.ChannelInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bzulip) Send(msg config.Message) (string, error) {
|
||||
b.Log.Debugf("=> Receiving %#v", msg)
|
||||
|
||||
// Delete message
|
||||
if msg.Event == config.EVENT_MSG_DELETE {
|
||||
if msg.ID == "" {
|
||||
return "", nil
|
||||
}
|
||||
_, err := b.bot.UpdateMessage(msg.ID, "")
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Upload a file if it exists
|
||||
if msg.Extra != nil {
|
||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||
b.sendMessage(rmsg)
|
||||
}
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
return b.handleUploadFile(&msg)
|
||||
}
|
||||
}
|
||||
|
||||
// edit the message if we have a msg ID
|
||||
if msg.ID != "" {
|
||||
_, err := b.bot.UpdateMessage(msg.ID, msg.Username+msg.Text)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Post normal message
|
||||
return b.sendMessage(msg)
|
||||
}
|
||||
|
||||
func (b *Bzulip) getChannel(id int) string {
|
||||
if name, ok := b.streams[id]; ok {
|
||||
return name
|
||||
}
|
||||
streams, err := b.bot.GetRawStreams()
|
||||
if err != nil {
|
||||
b.Log.Errorf("getChannel: %#v", err)
|
||||
return ""
|
||||
}
|
||||
for _, stream := range streams.Streams {
|
||||
b.streams[stream.StreamID] = stream.Name
|
||||
}
|
||||
if name, ok := b.streams[id]; ok {
|
||||
return name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *Bzulip) handleQueue() error {
|
||||
for {
|
||||
messages, _ := b.q.GetEvents()
|
||||
for _, m := range messages {
|
||||
b.Log.Debugf("== Receiving %#v", m)
|
||||
// ignore our own messages
|
||||
if m.SenderEmail == b.GetString("login") {
|
||||
continue
|
||||
}
|
||||
rmsg := config.Message{Username: m.SenderFullName, Text: m.Content, Channel: b.getChannel(m.StreamID), Account: b.Account, UserID: strconv.Itoa(m.SenderID), Avatar: m.AvatarURL}
|
||||
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
|
||||
b.Log.Debugf("<= Message is %#v", rmsg)
|
||||
b.Remote <- rmsg
|
||||
b.q.LastEventID = m.ID
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bzulip) sendMessage(msg config.Message) (string, error) {
|
||||
topic := "matterbridge"
|
||||
if b.GetString("topic") != "" {
|
||||
topic = b.GetString("topic")
|
||||
}
|
||||
m := gzb.Message{
|
||||
Stream: msg.Channel,
|
||||
Topic: topic,
|
||||
Content: msg.Username + msg.Text,
|
||||
}
|
||||
resp, err := b.bot.Message(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp != nil {
|
||||
defer resp.Body.Close()
|
||||
res, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var jr struct {
|
||||
ID int `json:"id"`
|
||||
}
|
||||
err = json.Unmarshal(res, &jr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strconv.Itoa(jr.ID), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (b *Bzulip) handleUploadFile(msg *config.Message) (string, error) {
|
||||
for _, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
if fi.Comment != "" {
|
||||
msg.Text += fi.Comment + ": "
|
||||
}
|
||||
if fi.URL != "" {
|
||||
msg.Text = fi.URL
|
||||
if fi.Comment != "" {
|
||||
msg.Text = fi.Comment + ": " + fi.URL
|
||||
}
|
||||
}
|
||||
_, err := b.sendMessage(*msg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
79
changelog.md
79
changelog.md
@ -1,3 +1,82 @@
|
||||
# v1.11.0
|
||||
|
||||
## New features
|
||||
* general: Add config option MediaDownloadPath (#443). See `MediaDownloadPath` in matterbridge.toml.sample
|
||||
* general: Add MediaDownloadBlacklist option. Closes #442. See `MediaDownloadBlacklist` in matterbridge.toml.sample
|
||||
* xmpp: Add channel password support for XMPP (#451)
|
||||
* xmpp: Add message correction support for XMPP (#437)
|
||||
* telegram: Add support for MessageFormat=htmlnick (telegram). #444
|
||||
* mattermost: Add support for mattermost 5.x
|
||||
|
||||
## Enhancements
|
||||
* slack: Add Title from attachment slack message (#446)
|
||||
* irc: Prevent white or black color codes (irc) (#434)
|
||||
|
||||
## Bugfix
|
||||
* slack: Fix regexp in replaceMention (slack). (#435)
|
||||
* irc: Reconnect on quit. (irc) See #431 (#445)
|
||||
* sshchat: Ignore messages from ourself. (sshchat) Closes #439
|
||||
|
||||
# v1.10.1
|
||||
## New features
|
||||
* irc: Colorize username sent to IRC using its crc32 IEEE checksum (#423). See `ColorNicks` in matterbridge.toml.sample
|
||||
* irc: Add support for CJK to/from utf-8 (irc). #400
|
||||
* telegram: Add QuoteFormat option (telegram). Closes #413. See `QuoteFormat` in matterbridge.toml.sample
|
||||
* xmpp: Send attached files to XMPP in different message with OOB data and without body (#421)
|
||||
|
||||
## Bugfix
|
||||
* general: updated irc/xmpp/telegram libraries
|
||||
* mattermost/slack/rocketchat: Fix iconurl regression. Closes #430
|
||||
* mattermost/slack: Use uuid instead of userid. Fixes #429
|
||||
* slack: Avatar spoofing from Slack to Discord with uppercase in nick doesn't work (#433)
|
||||
* irc: Fix format string bug (irc) (#428)
|
||||
|
||||
# v1.10.0
|
||||
## New features
|
||||
* general: Add support for reloading all settings automatically after changing config except connection and gateway configuration. Closes #373
|
||||
* zulip: New protocol support added (https://zulipchat.com)
|
||||
|
||||
## Enhancements
|
||||
* general: Handle file comment better
|
||||
* steam: Handle file uploads to mediaserver (steam)
|
||||
* slack: Properly set Slack user who initiated slash command (#394)
|
||||
|
||||
## Bugfix
|
||||
* general: Use only alphanumeric for file uploads to mediaserver. Closes #416
|
||||
* general: Fix crash on invalid filenames
|
||||
* general: Fix regression in ReplaceMessages and ReplaceNicks. Closes #407
|
||||
* telegram: Fix possible nil when using channels (telegram). #410
|
||||
* telegram: Fix panic (telegram). Closes #410
|
||||
* telegram: Handle channel posts correctly
|
||||
* mattermost: Update GetFileLinks to API_V4
|
||||
|
||||
# v1.9.1
|
||||
## New features
|
||||
* telegram: Add QuoteDisable option (telegram). Closes #399. See QuoteDisable in matterbridge.toml.sample
|
||||
## Enhancements
|
||||
* discord: Send mediaserver link to Discord in Webhook mode (discord) (#405)
|
||||
* mattermost: Print list of valid team names when team not found (#390)
|
||||
* slack: Strip markdown URLs with blank text (slack) (#392)
|
||||
## Bugfix
|
||||
* slack/mattermost: Make our callbackid more unique. Fixes issue with running multiple matterbridge on the same channel (slack,mattermost)
|
||||
* telegram: fix newlines in multiline messages #399
|
||||
* telegram: Revert #378
|
||||
|
||||
# v1.9.0 (the refactor release)
|
||||
## New features
|
||||
* general: better debug messages
|
||||
* general: better support for environment variables override
|
||||
* general: Ability to disable sending join/leave messages to other gateways. #382
|
||||
* slack: Allow Slack @usergroups to be parsed as human-friendly names #379
|
||||
* slack: Provide better context for shared posts from Slack<=>Slack enhancement #369
|
||||
* telegram: Convert nicks automatically into HTML when MessageFormat is set to HTML #378
|
||||
* irc: Add DebugLevel option
|
||||
|
||||
## Bugfix
|
||||
* slack: Ignore restricted_action on channel join (slack). Closes #387
|
||||
* slack: Add slack attachment support to matterhook
|
||||
* slack: Update userlist on join (slack). Closes #372
|
||||
|
||||
# v1.8.0
|
||||
## New features
|
||||
* general: Send chat notification if media is too big to be re-uploaded to MediaServer. See #359
|
||||
|
@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
go version |grep go1.9 || exit
|
||||
go version |grep go1.10 || exit
|
||||
VERSION=$(git describe --tags)
|
||||
mkdir ci/binaries
|
||||
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-windows-amd64.exe
|
||||
|
@ -3,17 +3,35 @@ package gateway
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/api"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
bdiscord "github.com/42wim/matterbridge/bridge/discord"
|
||||
bgitter "github.com/42wim/matterbridge/bridge/gitter"
|
||||
birc "github.com/42wim/matterbridge/bridge/irc"
|
||||
bmatrix "github.com/42wim/matterbridge/bridge/matrix"
|
||||
bmattermost "github.com/42wim/matterbridge/bridge/mattermost"
|
||||
brocketchat "github.com/42wim/matterbridge/bridge/rocketchat"
|
||||
bslack "github.com/42wim/matterbridge/bridge/slack"
|
||||
bsshchat "github.com/42wim/matterbridge/bridge/sshchat"
|
||||
bsteam "github.com/42wim/matterbridge/bridge/steam"
|
||||
btelegram "github.com/42wim/matterbridge/bridge/telegram"
|
||||
bxmpp "github.com/42wim/matterbridge/bridge/xmpp"
|
||||
bzulip "github.com/42wim/matterbridge/bridge/zulip"
|
||||
"github.com/hashicorp/golang-lru"
|
||||
log "github.com/sirupsen/logrus"
|
||||
// "github.com/davecgh/go-spew/spew"
|
||||
"crypto/sha1"
|
||||
"github.com/hashicorp/golang-lru"
|
||||
"github.com/peterhellberg/emojilib"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/peterhellberg/emojilib"
|
||||
)
|
||||
|
||||
type Gateway struct {
|
||||
@ -36,6 +54,22 @@ type BrMsgID struct {
|
||||
|
||||
var flog *log.Entry
|
||||
|
||||
var bridgeMap = map[string]bridge.Factory{
|
||||
"api": api.New,
|
||||
"discord": bdiscord.New,
|
||||
"gitter": bgitter.New,
|
||||
"irc": birc.New,
|
||||
"mattermost": bmattermost.New,
|
||||
"matrix": bmatrix.New,
|
||||
"rocketchat": brocketchat.New,
|
||||
"slack": bslack.New,
|
||||
"sshchat": bsshchat.New,
|
||||
"steam": bsteam.New,
|
||||
"telegram": btelegram.New,
|
||||
"xmpp": bxmpp.New,
|
||||
"zulip": bzulip.New,
|
||||
}
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"prefix": "gateway"})
|
||||
}
|
||||
@ -52,7 +86,14 @@ func New(cfg config.Gateway, r *Router) *Gateway {
|
||||
func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
|
||||
br := gw.Router.getBridge(cfg.Account)
|
||||
if br == nil {
|
||||
br = bridge.New(gw.Config, cfg, gw.Message)
|
||||
br = bridge.New(cfg)
|
||||
br.Config = gw.Router.Config
|
||||
br.General = &gw.General
|
||||
// set logging
|
||||
br.Log = log.WithFields(log.Fields{"prefix": "bridge"})
|
||||
brconfig := &bridge.Config{Remote: gw.Message, Log: log.WithFields(log.Fields{"prefix": br.Protocol}), Bridge: br}
|
||||
// add the actual bridger for this protocol to this bridge using the bridgeMap
|
||||
br.Bridger = bridgeMap[br.Protocol](brconfig)
|
||||
}
|
||||
gw.mapChannelsToBridge(br)
|
||||
gw.Bridges[cfg.Account] = br
|
||||
@ -168,17 +209,9 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con
|
||||
func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrMsgID {
|
||||
var brMsgIDs []*BrMsgID
|
||||
|
||||
// TODO refactor
|
||||
// only slack now, check will have to be done in the different bridges.
|
||||
// we need to check if we can't use fallback or text in other bridges
|
||||
// if we have an attached file, or other info
|
||||
if msg.Extra != nil {
|
||||
if dest.Protocol != "discord" &&
|
||||
dest.Protocol != "slack" &&
|
||||
dest.Protocol != "mattermost" &&
|
||||
dest.Protocol != "telegram" &&
|
||||
dest.Protocol != "matrix" &&
|
||||
dest.Protocol != "xmpp" &&
|
||||
len(msg.Extra[config.EVENT_FILE_FAILURE_SIZE]) == 0 {
|
||||
if len(msg.Extra[config.EVENT_FILE_FAILURE_SIZE]) != 0 {
|
||||
if msg.Text == "" {
|
||||
return brMsgIDs
|
||||
}
|
||||
@ -192,11 +225,14 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM
|
||||
return brMsgIDs
|
||||
}
|
||||
}
|
||||
// only relay join/part when configged
|
||||
if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart {
|
||||
|
||||
// only relay join/part when configured
|
||||
if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].GetBool("ShowJoinPart") {
|
||||
return brMsgIDs
|
||||
}
|
||||
if msg.Event == config.EVENT_TOPIC_CHANGE && !gw.Bridges[dest.Account].Config.ShowTopicChange {
|
||||
|
||||
// only relay topic change when configured
|
||||
if msg.Event == config.EVENT_TOPIC_CHANGE && !gw.Bridges[dest.Account].GetBool("ShowTopicChange") {
|
||||
return brMsgIDs
|
||||
}
|
||||
|
||||
@ -205,6 +241,7 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM
|
||||
flog.Debug("empty channel")
|
||||
return brMsgIDs
|
||||
}
|
||||
|
||||
originchannel := msg.Channel
|
||||
origmsg := msg
|
||||
channels := gw.getDestChannel(&msg, *dest)
|
||||
@ -220,7 +257,7 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM
|
||||
continue
|
||||
}
|
||||
}
|
||||
flog.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, originchannel, dest.Account, channel.Name)
|
||||
flog.Debugf("=> Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, originchannel, dest.Account, channel.Name)
|
||||
msg.Channel = channel.Name
|
||||
msg.Avatar = gw.modifyAvatar(origmsg, dest)
|
||||
msg.Username = gw.modifyUsername(origmsg, dest)
|
||||
@ -241,10 +278,11 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrM
|
||||
}
|
||||
mID, err := dest.Send(msg)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
flog.Error(err)
|
||||
}
|
||||
// append the message ID (mID) from this bridge (dest) to our brMsgIDs slice
|
||||
if mID != "" {
|
||||
flog.Debugf("mID %s: %s", dest.Account, mID)
|
||||
brMsgIDs = append(brMsgIDs, &BrMsgID{dest, mID, channel.ID})
|
||||
}
|
||||
}
|
||||
@ -256,8 +294,10 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
|
||||
if _, ok := gw.Bridges[msg.Account]; !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// check if we need to ignore a empty message
|
||||
if msg.Text == "" {
|
||||
// we have an attachment or actual bytes
|
||||
// we have an attachment or actual bytes, do not ignore
|
||||
if msg.Extra != nil &&
|
||||
(msg.Extra["attachments"] != nil ||
|
||||
len(msg.Extra["file"]) > 0 ||
|
||||
@ -267,14 +307,18 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
|
||||
flog.Debugf("ignoring empty message %#v from %s", msg, msg.Account)
|
||||
return true
|
||||
}
|
||||
for _, entry := range strings.Fields(gw.Bridges[msg.Account].Config.IgnoreNicks) {
|
||||
|
||||
// is the username in IgnoreNicks field
|
||||
for _, entry := range strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreNicks")) {
|
||||
if msg.Username == entry {
|
||||
flog.Debugf("ignoring %s from %s", msg.Username, msg.Account)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// does the message match regex in IgnoreMessages field
|
||||
// TODO do not compile regexps everytime
|
||||
for _, entry := range strings.Fields(gw.Bridges[msg.Account].Config.IgnoreMessages) {
|
||||
for _, entry := range strings.Fields(gw.Bridges[msg.Account].GetString("IgnoreMessages")) {
|
||||
if entry != "" {
|
||||
re, err := regexp.Compile(entry)
|
||||
if err != nil {
|
||||
@ -293,17 +337,17 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
|
||||
func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) string {
|
||||
br := gw.Bridges[msg.Account]
|
||||
msg.Protocol = br.Protocol
|
||||
if gw.Config.General.StripNick || dest.Config.StripNick {
|
||||
if gw.Config.General.StripNick || dest.GetBool("StripNick") {
|
||||
re := regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||
msg.Username = re.ReplaceAllString(msg.Username, "")
|
||||
}
|
||||
nick := dest.Config.RemoteNickFormat
|
||||
nick := dest.GetString("RemoteNickFormat")
|
||||
if nick == "" {
|
||||
nick = gw.Config.General.RemoteNickFormat
|
||||
}
|
||||
|
||||
// loop to replace nicks
|
||||
for _, outer := range br.Config.ReplaceNicks {
|
||||
for _, outer := range br.GetStringSlice2D("ReplaceNicks") {
|
||||
search := outer[0]
|
||||
replace := outer[1]
|
||||
// TODO move compile to bridge init somewhere
|
||||
@ -327,9 +371,10 @@ func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) strin
|
||||
}
|
||||
nick = strings.Replace(nick, "{NOPINGNICK}", msg.Username[:i]+""+msg.Username[i:], -1)
|
||||
}
|
||||
|
||||
nick = strings.Replace(nick, "{BRIDGE}", br.Name, -1)
|
||||
nick = strings.Replace(nick, "{PROTOCOL}", br.Protocol, -1)
|
||||
nick = strings.Replace(nick, "{LABEL}", br.Config.Label, -1)
|
||||
nick = strings.Replace(nick, "{LABEL}", br.GetString("Label"), -1)
|
||||
nick = strings.Replace(nick, "{NICK}", msg.Username, -1)
|
||||
return nick
|
||||
}
|
||||
@ -337,7 +382,7 @@ func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) strin
|
||||
func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string {
|
||||
iconurl := gw.Config.General.IconURL
|
||||
if iconurl == "" {
|
||||
iconurl = dest.Config.IconURL
|
||||
iconurl = dest.GetString("IconURL")
|
||||
}
|
||||
iconurl = strings.Replace(iconurl, "{NICK}", msg.Username, -1)
|
||||
if msg.Avatar == "" {
|
||||
@ -349,9 +394,10 @@ func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string
|
||||
func (gw *Gateway) modifyMessage(msg *config.Message) {
|
||||
// replace :emoji: to unicode
|
||||
msg.Text = emojilib.Replace(msg.Text)
|
||||
|
||||
br := gw.Bridges[msg.Account]
|
||||
// loop to replace messages
|
||||
for _, outer := range br.Config.ReplaceMessages {
|
||||
for _, outer := range br.GetStringSlice2D("ReplaceMessages") {
|
||||
search := outer[0]
|
||||
replace := outer[1]
|
||||
// TODO move compile to bridge init somewhere
|
||||
@ -369,45 +415,94 @@ func (gw *Gateway) modifyMessage(msg *config.Message) {
|
||||
}
|
||||
}
|
||||
|
||||
// handleFiles uploads or places all files on the given msg to the MediaServer and
|
||||
// adds the new URL of the file on the MediaServer onto the given msg.
|
||||
func (gw *Gateway) handleFiles(msg *config.Message) {
|
||||
if msg.Extra == nil || gw.Config.General.MediaServerUpload == "" {
|
||||
reg := regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||
|
||||
// If we don't have a attachfield or we don't have a mediaserver configured return
|
||||
if msg.Extra == nil || (gw.Config.General.MediaServerUpload == "" && gw.Config.General.MediaDownloadPath == "") {
|
||||
return
|
||||
}
|
||||
if len(msg.Extra["file"]) > 0 {
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 5,
|
||||
}
|
||||
for i, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
sha1sum := fmt.Sprintf("%x", sha1.Sum(*fi.Data))
|
||||
reader := bytes.NewReader(*fi.Data)
|
||||
|
||||
// If we don't have files, nothing to upload.
|
||||
if len(msg.Extra["file"]) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 5,
|
||||
}
|
||||
|
||||
for i, f := range msg.Extra["file"] {
|
||||
fi := f.(config.FileInfo)
|
||||
ext := filepath.Ext(fi.Name)
|
||||
fi.Name = fi.Name[0 : len(fi.Name)-len(ext)]
|
||||
fi.Name = reg.ReplaceAllString(fi.Name, "_")
|
||||
fi.Name = fi.Name + ext
|
||||
|
||||
sha1sum := fmt.Sprintf("%x", sha1.Sum(*fi.Data))[:8]
|
||||
|
||||
if gw.Config.General.MediaServerUpload != "" {
|
||||
// Use MediaServerUpload. Upload using a PUT HTTP request and basicauth.
|
||||
|
||||
url := gw.Config.General.MediaServerUpload + "/" + sha1sum + "/" + fi.Name
|
||||
durl := gw.Config.General.MediaServerDownload + "/" + sha1sum + "/" + fi.Name
|
||||
extra := msg.Extra["file"][i].(config.FileInfo)
|
||||
extra.URL = durl
|
||||
req, _ := http.NewRequest("PUT", url, reader)
|
||||
req.Header.Set("Content-Type", "binary/octet-stream")
|
||||
_, err := client.Do(req)
|
||||
|
||||
req, err := http.NewRequest("PUT", url, bytes.NewReader(*fi.Data))
|
||||
if err != nil {
|
||||
flog.Errorf("mediaserver upload failed: %#v", err)
|
||||
flog.Errorf("mediaserver upload failed, could not create request: %#v", err)
|
||||
continue
|
||||
}
|
||||
flog.Debugf("mediaserver download URL = %s", durl)
|
||||
// we uploaded the file successfully. Add the SHA
|
||||
extra.SHA = sha1sum
|
||||
msg.Extra["file"][i] = extra
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getChannelID(msg config.Message) string {
|
||||
return msg.Channel + msg.Account
|
||||
flog.Debugf("mediaserver upload url: %s", url)
|
||||
|
||||
req.Header.Set("Content-Type", "binary/octet-stream")
|
||||
_, err = client.Do(req)
|
||||
if err != nil {
|
||||
flog.Errorf("mediaserver upload failed, could not Do request: %#v", err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// Use MediaServerPath. Place the file on the current filesystem.
|
||||
|
||||
dir := gw.Config.General.MediaDownloadPath + "/" + sha1sum
|
||||
err := os.Mkdir(dir, os.ModePerm)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
flog.Errorf("mediaserver path failed, could not mkdir: %s %#v", err, err)
|
||||
continue
|
||||
}
|
||||
|
||||
path := dir + "/" + fi.Name
|
||||
flog.Debugf("mediaserver path placing file: %s", path)
|
||||
|
||||
err = ioutil.WriteFile(path, *fi.Data, os.ModePerm)
|
||||
if err != nil {
|
||||
flog.Errorf("mediaserver path failed, could not writefile: %s %#v", err, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Download URL.
|
||||
durl := gw.Config.General.MediaServerDownload + "/" + sha1sum + "/" + fi.Name
|
||||
|
||||
flog.Debugf("mediaserver download URL = %s", durl)
|
||||
|
||||
// We uploaded/placed the file successfully. Add the SHA and URL.
|
||||
extra := msg.Extra["file"][i].(config.FileInfo)
|
||||
extra.URL = durl
|
||||
extra.SHA = sha1sum
|
||||
msg.Extra["file"][i] = extra
|
||||
}
|
||||
}
|
||||
|
||||
func (gw *Gateway) validGatewayDest(msg *config.Message, channel *config.ChannelInfo) bool {
|
||||
return msg.Gateway == gw.Name
|
||||
}
|
||||
|
||||
func getChannelID(msg config.Message) string {
|
||||
return msg.Channel + msg.Account
|
||||
}
|
||||
|
||||
func isApi(account string) bool {
|
||||
return strings.HasPrefix(account, "api.")
|
||||
}
|
||||
|
@ -2,15 +2,15 @@ package gateway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"strconv"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testconfig = `
|
||||
var testconfig = []byte(`
|
||||
[irc.freenode]
|
||||
[mattermost.test]
|
||||
[gitter.42wim]
|
||||
@ -37,9 +37,9 @@ var testconfig = `
|
||||
[[gateway.inout]]
|
||||
account="slack.test"
|
||||
channel="testing"
|
||||
`
|
||||
`)
|
||||
|
||||
var testconfig2 = `
|
||||
var testconfig2 = []byte(`
|
||||
[irc.freenode]
|
||||
[mattermost.test]
|
||||
[gitter.42wim]
|
||||
@ -80,8 +80,9 @@ var testconfig2 = `
|
||||
[[gateway.out]]
|
||||
account = "discord.test"
|
||||
channel = "general2"
|
||||
`
|
||||
var testconfig3 = `
|
||||
`)
|
||||
|
||||
var testconfig3 = []byte(`
|
||||
[irc.zzz]
|
||||
[telegram.zzz]
|
||||
[slack.zzz]
|
||||
@ -149,13 +150,10 @@ enable=true
|
||||
[[gateway.inout]]
|
||||
account="telegram.zzz"
|
||||
channel="--333333333333"
|
||||
`
|
||||
`)
|
||||
|
||||
func maketestRouter(input string) *Router {
|
||||
var cfg *config.Config
|
||||
if _, err := toml.Decode(input, &cfg); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
func maketestRouter(input []byte) *Router {
|
||||
cfg := config.NewConfigFromString(input)
|
||||
r, err := NewRouter(cfg)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
@ -163,14 +161,7 @@ func maketestRouter(input string) *Router {
|
||||
return r
|
||||
}
|
||||
func TestNewRouter(t *testing.T) {
|
||||
var cfg *config.Config
|
||||
if _, err := toml.Decode(testconfig, &cfg); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
r, err := NewRouter(cfg)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
r := maketestRouter(testconfig)
|
||||
assert.Equal(t, 1, len(r.Gateways))
|
||||
assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges))
|
||||
assert.Equal(t, 4, len(r.Gateways["bridge1"].Channels))
|
||||
|
@ -2,10 +2,10 @@ package gateway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge"
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/gateway/samechannel"
|
||||
//log "github.com/sirupsen/logrus"
|
||||
samechannelgateway "github.com/42wim/matterbridge/gateway/samechannel"
|
||||
// "github.com/davecgh/go-spew/spew"
|
||||
"time"
|
||||
)
|
||||
@ -17,10 +17,7 @@ type Router struct {
|
||||
}
|
||||
|
||||
func NewRouter(cfg *config.Config) (*Router, error) {
|
||||
r := &Router{}
|
||||
r.Config = cfg
|
||||
r.Message = make(chan config.Message)
|
||||
r.Gateways = make(map[string]*Gateway)
|
||||
r := &Router{Message: make(chan config.Message), Gateways: make(map[string]*Gateway), Config: cfg}
|
||||
sgw := samechannelgateway.New(cfg)
|
||||
gwconfigs := sgw.GetConfig()
|
||||
|
||||
|
@ -2,6 +2,7 @@ package samechannelgateway
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
BIN
img/matterbridge.gif
Normal file
BIN
img/matterbridge.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 77 KiB |
@ -3,17 +3,18 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/gateway"
|
||||
"github.com/google/gops/agent"
|
||||
log "github.com/sirupsen/logrus"
|
||||
prefixed "github.com/x-cray/logrus-prefixed-formatter"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
version = "1.8.0"
|
||||
version = "1.11.0"
|
||||
githash string
|
||||
)
|
||||
|
||||
@ -34,7 +35,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
if *flagDebug || os.Getenv("DEBUG") == "1" {
|
||||
log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false})
|
||||
log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false, ForceFormatting: true})
|
||||
flog.Info("Enabling debug")
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
@ -64,6 +64,9 @@ NickServPassword="secret"
|
||||
#OPTIONAL only used for quakenet auth
|
||||
NickServUsername="username"
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
#Flood control
|
||||
#Delay in milliseconds between each message send to the IRC server
|
||||
#OPTIONAL (default 1300)
|
||||
@ -89,6 +92,10 @@ MessageSplit=false
|
||||
#OPTIONAL (default 0)
|
||||
RejoinDelay=0
|
||||
|
||||
#ColorNicks will show each nickname in a different color.
|
||||
#Only works in IRC right now.
|
||||
ColorNicks=false
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
@ -135,6 +142,11 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
#Do not send joins/parts to other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack
|
||||
#OPTIONAL (default false)
|
||||
NoSendJoinPart=false
|
||||
|
||||
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
|
||||
#It will strip other characters from the nick
|
||||
#OPTIONAL (default false)
|
||||
@ -179,6 +191,9 @@ Nick="xmppbot"
|
||||
#OPTIONAL (default false)
|
||||
SkipTLSVerify=true
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
@ -260,6 +275,9 @@ Muc="conf.hipchat.com"
|
||||
#REQUIRED
|
||||
Nick="yourlogin"
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
@ -377,6 +395,9 @@ IconURL="http://youricon.png"
|
||||
#OPTIONAL (default false)
|
||||
SkipTLSVerify=true
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
#how to format the list of IRC nicks when displayed in mattermost.
|
||||
#Possible options are "table" and "plain"
|
||||
#OPTIONAL (default plain)
|
||||
@ -446,6 +467,11 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
#Do not send joins/parts to other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack
|
||||
#OPTIONAL (default false)
|
||||
NoSendJoinPart=false
|
||||
|
||||
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
|
||||
#It will strip other characters from the nick
|
||||
#OPTIONAL (default false)
|
||||
@ -472,6 +498,9 @@ ShowTopicChange=false
|
||||
#REQUIRED
|
||||
Token="Yourtokenhere"
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
@ -567,6 +596,9 @@ WebhookBindAddress="0.0.0.0:9999"
|
||||
#OPTIONAL
|
||||
IconURL="https://robohash.org/{NICK}.png?size=48x48"
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
#how to format the list of IRC nicks when displayed in slack
|
||||
#Possible options are "table" and "plain"
|
||||
#OPTIONAL (default plain)
|
||||
@ -636,6 +668,11 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
#Do not send joins/parts to other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack
|
||||
#OPTIONAL (default false)
|
||||
NoSendJoinPart=false
|
||||
|
||||
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
|
||||
#It will strip other characters from the nick
|
||||
#OPTIONAL (default false)
|
||||
@ -665,6 +702,9 @@ Token="Yourtokenhere"
|
||||
#REQUIRED
|
||||
Server="yourservername"
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
#Shows title, description and URL of embedded messages (sent by other bots)
|
||||
#OPTIONAL (default false)
|
||||
ShowEmbeds=false
|
||||
@ -755,9 +795,14 @@ ShowTopicChange=false
|
||||
#REQUIRED
|
||||
Token="Yourtokenhere"
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
#OPTIONAL (default empty)
|
||||
#Only supported format is "HTML", messages will be sent in html parsemode.
|
||||
#Supported formats are "HTML", "Markdown" and "HTMLNick"
|
||||
#See https://core.telegram.org/bots/api#html-style
|
||||
#See https://core.telegram.org/bots/api#markdown-style
|
||||
#HTMLNick only allows HTML for the nick, the message itself will be html-escaped
|
||||
MessageFormat=""
|
||||
|
||||
#If enabled use the "First Name" as username. If this is empty use the Username
|
||||
@ -772,6 +817,14 @@ UseFirstName=false
|
||||
#OPTIONAL (default false)
|
||||
UseInsecureURL=false
|
||||
|
||||
#Disable quoted/reply messages
|
||||
#OPTIONAL (default false)
|
||||
QuoteDisable=false
|
||||
|
||||
#Format quoted/reply messages
|
||||
#OPTIONAL (default "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})")
|
||||
QuoteFormat="{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
|
||||
|
||||
#Disable sending of edits to other bridges
|
||||
#OPTIONAL (default false)
|
||||
EditDisable=false
|
||||
@ -817,6 +870,11 @@ Label=""
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#The string "{LABEL}" (case sensitive) will be replaced by label= field of the sending bridge
|
||||
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||
#
|
||||
#WARNING: if you have set MessageFormat="HTML" be sure that this format matches the guidelines
|
||||
#on https://core.telegram.org/bots/api#html-style otherwise the message will not go through to
|
||||
#telegram! eg <{NICK}> should be <{NICK}>
|
||||
#
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
@ -868,6 +926,9 @@ NoTLS=false
|
||||
#OPTIONAL (default false)
|
||||
SkipTLSVerify=true
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
#Whether to prefix messages from other bridges to rocketchat with the sender's nick.
|
||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||
#rocketchat server. If you set PrefixMessagesWithNick to true, each message
|
||||
@ -955,6 +1016,9 @@ Password="yourpass"
|
||||
#OPTIONAL (default false)
|
||||
NoHomeServerSuffix=false
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
#Whether to prefix messages from other bridges to matrix with the sender's nick.
|
||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||
#matrix server. If you set PrefixMessagesWithNick to true, each message
|
||||
@ -1036,6 +1100,9 @@ Password="yourpass"
|
||||
#OPTIONAL
|
||||
Authcode="ABCE12"
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
#Whether to prefix messages from other bridges to matrix with the sender's nick.
|
||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||
#matrix server. If you set PrefixMessagesWithNick to true, each message
|
||||
@ -1098,6 +1165,90 @@ StripNick=false
|
||||
#OPTIONAL (default false)
|
||||
ShowTopicChange=false
|
||||
|
||||
###################################################################
|
||||
#zulip section
|
||||
###################################################################
|
||||
[zulip]
|
||||
#You can configure multiple servers "[zulip.name]" or "[zulip.name2]"
|
||||
#In this example we use [zulip.streamchat]
|
||||
#REQUIRED
|
||||
|
||||
[zulip.streamchat]
|
||||
#Token to connect with zulip API (called bot API key in Settings - Your bots)
|
||||
#REQUIRED
|
||||
Token="Yourtokenhere"
|
||||
|
||||
#Username of the bot, normally called yourbot-bot@yourserver.zulipchat.com
|
||||
#See username in Settings - Your bots
|
||||
#REQUIRED
|
||||
Login="yourbot-bot@yourserver.zulipchat.com"
|
||||
|
||||
#Servername of your zulip instance
|
||||
#REQUIRED
|
||||
Server="https://yourserver.zulipchat.com"
|
||||
|
||||
#Topic of the messages matterbridge will use
|
||||
#OPTIONAL (default "matterbridge")
|
||||
Topic="matterbridge"
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="spammer1 spammer2"
|
||||
|
||||
#Messages you want to ignore.
|
||||
#Messages matching these regexp will be ignored and not sent to other bridges
|
||||
#See https://regex-golang.appspot.com/assets/html/index.html for more regex info
|
||||
#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword
|
||||
IgnoreMessages="^~~ badword"
|
||||
|
||||
#messages you want to replace.
|
||||
#it replaces outgoing messages from the bridge.
|
||||
#so you need to place it by the sending bridge definition.
|
||||
#regular expressions supported
|
||||
#some examples:
|
||||
#this replaces cat => dog and sleep => awake
|
||||
#replacemessages=[ ["cat","dog"], ["sleep","awake"] ]
|
||||
#this replaces every number with number. 123 => numbernumbernumber
|
||||
#replacemessages=[ ["[0-9]","number"] ]
|
||||
#optional (default empty)
|
||||
ReplaceMessages=[ ["cat","dog"] ]
|
||||
|
||||
#nicks you want to replace.
|
||||
#see replacemessages for syntaxa
|
||||
#optional (default empty)
|
||||
ReplaceNicks=[ ["user--","user"] ]
|
||||
|
||||
#extra label that can be used in the RemoteNickFormat
|
||||
#optional (default empty)
|
||||
Label=""
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#The string "{LABEL}" (case sensitive) will be replaced by label= field of the sending bridge
|
||||
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges
|
||||
#Currently works for messages from the following bridges: irc, mattermost, slack
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
#StripNick only allows alphanumerical nicks. See https://github.com/42wim/matterbridge/issues/285
|
||||
#It will strip other characters from the nick
|
||||
#OPTIONAL (default false)
|
||||
StripNick=false
|
||||
|
||||
#Enable to show topic changes from other bridges
|
||||
#Only works hiding/show topic changes from slack bridge for now
|
||||
#OPTIONAL (default false)
|
||||
ShowTopicChange=false
|
||||
|
||||
###################################################################
|
||||
#API
|
||||
###################################################################
|
||||
@ -1138,6 +1289,10 @@ RemoteNickFormat="{NICK}"
|
||||
###################################################################
|
||||
# Settings here are defaults that each protocol can override
|
||||
[general]
|
||||
|
||||
## RELOADABLE SETTINGS
|
||||
## Settings below can be reloaded by editing the file
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
@ -1152,10 +1307,13 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
StripNick=false
|
||||
|
||||
|
||||
#MediaServerUpload and MediaServerDownload are used for uploading images/files/video to
|
||||
#a remote "mediaserver" (a webserver like caddy for example).
|
||||
#When configured images/files uploaded on bridges like mattermost,slack, telegram will be downloaded
|
||||
#and uploaded again to MediaServerUpload URL
|
||||
#MediaServerUpload (or MediaDownloadPath) and MediaServerDownload are used for uploading
|
||||
#images/files/video to a remote "mediaserver" (a webserver like caddy for example).
|
||||
#When configured images/files uploaded on bridges like mattermost, slack, telegram will be
|
||||
#downloaded and uploaded again to MediaServerUpload URL
|
||||
#MediaDownloadPath is the filesystem path where the media file will be placed, instead of uploaded,
|
||||
#for if Matterbridge has write access to the directory your webserver is serving.
|
||||
#It is an alternative to MediaServerUpload.
|
||||
#The MediaServerDownload will be used so that bridges without native uploading support:
|
||||
#gitter, irc and xmpp will be shown links to the files on MediaServerDownload
|
||||
#
|
||||
@ -1163,6 +1321,8 @@ StripNick=false
|
||||
#OPTIONAL (default empty)
|
||||
MediaServerUpload="https://user:pass@yourserver.com/upload"
|
||||
#OPTIONAL (default empty)
|
||||
MediaDownloadPath="/srv/http/yourserver.com/public/download"
|
||||
#OPTIONAL (default empty)
|
||||
MediaServerDownload="https://youserver.com/download"
|
||||
|
||||
#MediaDownloadSize is the maximum size of attachments, videos, images
|
||||
@ -1172,9 +1332,15 @@ MediaServerDownload="https://youserver.com/download"
|
||||
#It will only download from bridges that don't have public links available, which are for the moment
|
||||
#slack, telegram, matrix and mattermost
|
||||
#
|
||||
#Optional (default 1000000 (1 megabyte))
|
||||
#OPTIONAL (default 1000000 (1 megabyte))
|
||||
MediaDownloadSize=1000000
|
||||
|
||||
#MediaDownloadBlacklist allows you to blacklist specific files from being downloaded.
|
||||
#Filenames matching these regexp will not be download/uploaded to the mediaserver
|
||||
#You can use regex for this, see https://regex-golang.appspot.com/assets/html/index.html for more regex info
|
||||
#OPTIONAL (default empty)
|
||||
MediaDownloadBlacklist=[".html$",".htm$"]
|
||||
|
||||
###################################################################
|
||||
#Gateway configuration
|
||||
###################################################################
|
||||
@ -1222,13 +1388,14 @@ enable=true
|
||||
# - encrypted rooms are not supported in matrix
|
||||
#steam - chatid (a large number).
|
||||
# The number in the URL when you click "enter chat room" in the browser
|
||||
#zulip - stream (without the #)
|
||||
#
|
||||
#REQUIRED
|
||||
channel="#testing"
|
||||
|
||||
#OPTIONAL - only used for IRC protocol at the moment
|
||||
#OPTIONAL - only used for IRC and XMPP protocols at the moment
|
||||
[gateway.in.options]
|
||||
#OPTIONAL - your irc channel key
|
||||
#OPTIONAL - your irc / xmpp channel key
|
||||
key="yourkey"
|
||||
|
||||
|
||||
@ -1237,9 +1404,9 @@ enable=true
|
||||
account="irc.freenode"
|
||||
channel="#testing"
|
||||
|
||||
#OPTIONAL - only used for IRC protocol at the moment
|
||||
#OPTIONAL - only used for IRC and XMPP protocols at the moment
|
||||
[gateway.out.options]
|
||||
#OPTIONAL - your irc channel key
|
||||
#OPTIONAL - your irc / xmpp channel key
|
||||
key="yourkey"
|
||||
|
||||
#[[gateway.inout]] can be used when then channel will be used to receive from
|
||||
@ -1248,9 +1415,9 @@ enable=true
|
||||
account="mattermost.work"
|
||||
channel="off-topic"
|
||||
|
||||
#OPTIONAL - only used for IRC protocol at the moment
|
||||
#OPTIONAL - only used for IRC and XMPP protocols at the moment
|
||||
[gateway.inout.options]
|
||||
#OPTIONAL - your irc channel key
|
||||
#OPTIONAL - your irc / xmpp channel key
|
||||
key="yourkey"
|
||||
|
||||
[[gateway.inout]]
|
||||
|
@ -81,7 +81,7 @@ func New(login, pass, team, server string) *MMClient {
|
||||
}
|
||||
|
||||
func (m *MMClient) SetDebugLog() {
|
||||
log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false})
|
||||
log.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false, ForceFormatting: true})
|
||||
}
|
||||
|
||||
func (m *MMClient) SetLogLevel(level string) {
|
||||
@ -190,7 +190,11 @@ func (m *MMClient) Login() error {
|
||||
}
|
||||
|
||||
if m.Team == nil {
|
||||
return errors.New("team not found")
|
||||
validTeamNames := make([]string, len(m.OtherTeams))
|
||||
for i, t := range m.OtherTeams {
|
||||
validTeamNames[i] = t.Team.Name
|
||||
}
|
||||
return fmt.Errorf("Team '%s' not found in %v", m.Credentials.Team, validTeamNames)
|
||||
}
|
||||
|
||||
m.wsConnect()
|
||||
@ -570,7 +574,7 @@ func (m *MMClient) GetFileLinks(filenames []string) []string {
|
||||
res, resp := m.Client.GetFileLink(f)
|
||||
if resp.Error != nil {
|
||||
// public links is probably disabled, create the link ourselves
|
||||
output = append(output, uriScheme+m.Credentials.Server+model.API_URL_SUFFIX_V3+"/files/"+f+"/get")
|
||||
output = append(output, uriScheme+m.Credentials.Server+model.API_URL_SUFFIX_V4+"/files/"+f)
|
||||
continue
|
||||
}
|
||||
output = append(output, res)
|
||||
@ -768,6 +772,14 @@ func (m *MMClient) GetStatus(userId string) string {
|
||||
return "offline"
|
||||
}
|
||||
|
||||
func (m *MMClient) UpdateStatus(userId string, status string) error {
|
||||
_, resp := m.Client.UpdateUserStatus(userId, &model.Status{Status: status})
|
||||
if resp.Error != nil {
|
||||
return resp.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MMClient) GetStatuses() map[string]string {
|
||||
var ids []string
|
||||
statuses := make(map[string]string)
|
||||
@ -905,7 +917,8 @@ func supportedVersion(version string) bool {
|
||||
if strings.HasPrefix(version, "3.8.0") ||
|
||||
strings.HasPrefix(version, "3.9.0") ||
|
||||
strings.HasPrefix(version, "3.10.0") ||
|
||||
strings.HasPrefix(version, "4.") {
|
||||
strings.HasPrefix(version, "4.") ||
|
||||
strings.HasPrefix(version, "5.") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -6,13 +6,15 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gorilla/schema"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/nlopes/slack"
|
||||
)
|
||||
|
||||
// OMessage for mattermost incoming webhook. (send to mattermost)
|
||||
@ -22,7 +24,7 @@ type OMessage struct {
|
||||
IconEmoji string `json:"icon_emoji,omitempty"`
|
||||
UserName string `json:"username,omitempty"`
|
||||
Text string `json:"text"`
|
||||
Attachments interface{} `json:"attachments,omitempty"`
|
||||
Attachments []slack.Attachment `json:"attachments,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Props map[string]interface{} `json:"props"`
|
||||
}
|
||||
|
362
vendor/github.com/armon/consul-api/LICENSE
generated
vendored
Normal file
362
vendor/github.com/armon/consul-api/LICENSE
generated
vendored
Normal file
@ -0,0 +1,362 @@
|
||||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. "Contributor"
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the terms of
|
||||
a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
|
||||
means a work that combines Covered Software with other material, in a
|
||||
separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether
|
||||
at the time of the initial grant or subsequently, any and all of the
|
||||
rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the License,
|
||||
by the making, using, selling, offering for sale, having made, import,
|
||||
or transfer of either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, "control" means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights to
|
||||
grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter the
|
||||
recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty, or
|
||||
limitations of liability) contained within the Source Code Form of the
|
||||
Covered Software, except that You may alter any license notices to the
|
||||
extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute,
|
||||
judicial order, or regulation then You must: (a) comply with the terms of
|
||||
this License to the maximum extent possible; and (b) describe the
|
||||
limitations and the code they affect. Such description must be placed in a
|
||||
text file included with all distributions of the Covered Software under
|
||||
this License. Except to the extent prohibited by statute or regulation,
|
||||
such description must be sufficiently detailed for a recipient of ordinary
|
||||
skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing
|
||||
basis, if such Contributor fails to notify You of the non-compliance by
|
||||
some reasonable means prior to 60 days after You have come back into
|
||||
compliance. Moreover, Your grants from a particular Contributor are
|
||||
reinstated on an ongoing basis if such Contributor notifies You of the
|
||||
non-compliance by some reasonable means, this is the first time You have
|
||||
received notice of non-compliance with this License from such
|
||||
Contributor, and You become compliant prior to 30 days after Your receipt
|
||||
of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an "as is" basis,
|
||||
without warranty of any kind, either expressed, implied, or statutory,
|
||||
including, without limitation, warranties that the Covered Software is free
|
||||
of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
The entire risk as to the quality and performance of the Covered Software
|
||||
is with You. Should any Covered Software prove defective in any respect,
|
||||
You (not any Contributor) assume the cost of any necessary servicing,
|
||||
repair, or correction. This disclaimer of warranty constitutes an essential
|
||||
part of this License. No use of any Covered Software is authorized under
|
||||
this License except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from
|
||||
such party's negligence to the extent applicable law prohibits such
|
||||
limitation. Some jurisdictions do not allow the exclusion or limitation of
|
||||
incidental or consequential damages, so this exclusion and limitation may
|
||||
not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts
|
||||
of a jurisdiction where the defendant maintains its principal place of
|
||||
business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions. Nothing
|
||||
in this Section shall prevent a party's ability to bring cross-claims or
|
||||
counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides that
|
||||
the language of a contract shall be construed against the drafter shall not
|
||||
be used to construe this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses If You choose to distribute Source Code Form that is
|
||||
Incompatible With Secondary Licenses under the terms of this version of
|
||||
the License, the notice described in Exhibit B of this License must be
|
||||
attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file,
|
||||
then You may include the notice in a location (such as a LICENSE file in a
|
||||
relevant directory) where a recipient would be likely to look for such a
|
||||
notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
This Source Code Form is "Incompatible
|
||||
With Secondary Licenses", as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
140
vendor/github.com/armon/consul-api/acl.go
generated
vendored
Normal file
140
vendor/github.com/armon/consul-api/acl.go
generated
vendored
Normal file
@ -0,0 +1,140 @@
|
||||
package consulapi
|
||||
|
||||
const (
|
||||
// ACLCLientType is the client type token
|
||||
ACLClientType = "client"
|
||||
|
||||
// ACLManagementType is the management type token
|
||||
ACLManagementType = "management"
|
||||
)
|
||||
|
||||
// ACLEntry is used to represent an ACL entry
|
||||
type ACLEntry struct {
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
ID string
|
||||
Name string
|
||||
Type string
|
||||
Rules string
|
||||
}
|
||||
|
||||
// ACL can be used to query the ACL endpoints
|
||||
type ACL struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// ACL returns a handle to the ACL endpoints
|
||||
func (c *Client) ACL() *ACL {
|
||||
return &ACL{c}
|
||||
}
|
||||
|
||||
// Create is used to generate a new token with the given parameters
|
||||
func (a *ACL) Create(acl *ACLEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
r := a.c.newRequest("PUT", "/v1/acl/create")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = acl
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
var out struct{ ID string }
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return out.ID, wm, nil
|
||||
}
|
||||
|
||||
// Update is used to update the rules of an existing token
|
||||
func (a *ACL) Update(acl *ACLEntry, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := a.c.newRequest("PUT", "/v1/acl/update")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = acl
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Destroy is used to destroy a given ACL token ID
|
||||
func (a *ACL) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := a.c.newRequest("PUT", "/v1/acl/destroy/"+id)
|
||||
r.setWriteOptions(q)
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Clone is used to return a new token cloned from an existing one
|
||||
func (a *ACL) Clone(id string, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
r := a.c.newRequest("PUT", "/v1/acl/clone/"+id)
|
||||
r.setWriteOptions(q)
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
var out struct{ ID string }
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return out.ID, wm, nil
|
||||
}
|
||||
|
||||
// Info is used to query for information about an ACL token
|
||||
func (a *ACL) Info(id string, q *QueryOptions) (*ACLEntry, *QueryMeta, error) {
|
||||
r := a.c.newRequest("GET", "/v1/acl/info/"+id)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*ACLEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
return entries[0], qm, nil
|
||||
}
|
||||
return nil, qm, nil
|
||||
}
|
||||
|
||||
// List is used to get all the ACL tokens
|
||||
func (a *ACL) List(q *QueryOptions) ([]*ACLEntry, *QueryMeta, error) {
|
||||
r := a.c.newRequest("GET", "/v1/acl/list")
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*ACLEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
272
vendor/github.com/armon/consul-api/agent.go
generated
vendored
Normal file
272
vendor/github.com/armon/consul-api/agent.go
generated
vendored
Normal file
@ -0,0 +1,272 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// AgentCheck represents a check known to the agent
|
||||
type AgentCheck struct {
|
||||
Node string
|
||||
CheckID string
|
||||
Name string
|
||||
Status string
|
||||
Notes string
|
||||
Output string
|
||||
ServiceID string
|
||||
ServiceName string
|
||||
}
|
||||
|
||||
// AgentService represents a service known to the agent
|
||||
type AgentService struct {
|
||||
ID string
|
||||
Service string
|
||||
Tags []string
|
||||
Port int
|
||||
}
|
||||
|
||||
// AgentMember represents a cluster member known to the agent
|
||||
type AgentMember struct {
|
||||
Name string
|
||||
Addr string
|
||||
Port uint16
|
||||
Tags map[string]string
|
||||
Status int
|
||||
ProtocolMin uint8
|
||||
ProtocolMax uint8
|
||||
ProtocolCur uint8
|
||||
DelegateMin uint8
|
||||
DelegateMax uint8
|
||||
DelegateCur uint8
|
||||
}
|
||||
|
||||
// AgentServiceRegistration is used to register a new service
|
||||
type AgentServiceRegistration struct {
|
||||
ID string `json:",omitempty"`
|
||||
Name string `json:",omitempty"`
|
||||
Tags []string `json:",omitempty"`
|
||||
Port int `json:",omitempty"`
|
||||
Check *AgentServiceCheck
|
||||
}
|
||||
|
||||
// AgentCheckRegistration is used to register a new check
|
||||
type AgentCheckRegistration struct {
|
||||
ID string `json:",omitempty"`
|
||||
Name string `json:",omitempty"`
|
||||
Notes string `json:",omitempty"`
|
||||
AgentServiceCheck
|
||||
}
|
||||
|
||||
// AgentServiceCheck is used to create an associated
|
||||
// check for a service
|
||||
type AgentServiceCheck struct {
|
||||
Script string `json:",omitempty"`
|
||||
Interval string `json:",omitempty"`
|
||||
TTL string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Agent can be used to query the Agent endpoints
|
||||
type Agent struct {
|
||||
c *Client
|
||||
|
||||
// cache the node name
|
||||
nodeName string
|
||||
}
|
||||
|
||||
// Agent returns a handle to the agent endpoints
|
||||
func (c *Client) Agent() *Agent {
|
||||
return &Agent{c: c}
|
||||
}
|
||||
|
||||
// Self is used to query the agent we are speaking to for
|
||||
// information about itself
|
||||
func (a *Agent) Self() (map[string]map[string]interface{}, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/self")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out map[string]map[string]interface{}
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// NodeName is used to get the node name of the agent
|
||||
func (a *Agent) NodeName() (string, error) {
|
||||
if a.nodeName != "" {
|
||||
return a.nodeName, nil
|
||||
}
|
||||
info, err := a.Self()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := info["Config"]["NodeName"].(string)
|
||||
a.nodeName = name
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// Checks returns the locally registered checks
|
||||
func (a *Agent) Checks() (map[string]*AgentCheck, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/checks")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out map[string]*AgentCheck
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Services returns the locally registered services
|
||||
func (a *Agent) Services() (map[string]*AgentService, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/services")
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out map[string]*AgentService
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Members returns the known gossip members. The WAN
|
||||
// flag can be used to query a server for WAN members.
|
||||
func (a *Agent) Members(wan bool) ([]*AgentMember, error) {
|
||||
r := a.c.newRequest("GET", "/v1/agent/members")
|
||||
if wan {
|
||||
r.params.Set("wan", "1")
|
||||
}
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out []*AgentMember
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ServiceRegister is used to register a new service with
|
||||
// the local agent
|
||||
func (a *Agent) ServiceRegister(service *AgentServiceRegistration) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/service/register")
|
||||
r.obj = service
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServiceDeregister is used to deregister a service with
|
||||
// the local agent
|
||||
func (a *Agent) ServiceDeregister(serviceID string) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/service/deregister/"+serviceID)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// PassTTL is used to set a TTL check to the passing state
|
||||
func (a *Agent) PassTTL(checkID, note string) error {
|
||||
return a.UpdateTTL(checkID, note, "pass")
|
||||
}
|
||||
|
||||
// WarnTTL is used to set a TTL check to the warning state
|
||||
func (a *Agent) WarnTTL(checkID, note string) error {
|
||||
return a.UpdateTTL(checkID, note, "warn")
|
||||
}
|
||||
|
||||
// FailTTL is used to set a TTL check to the failing state
|
||||
func (a *Agent) FailTTL(checkID, note string) error {
|
||||
return a.UpdateTTL(checkID, note, "fail")
|
||||
}
|
||||
|
||||
// UpdateTTL is used to update the TTL of a check
|
||||
func (a *Agent) UpdateTTL(checkID, note, status string) error {
|
||||
switch status {
|
||||
case "pass":
|
||||
case "warn":
|
||||
case "fail":
|
||||
default:
|
||||
return fmt.Errorf("Invalid status: %s", status)
|
||||
}
|
||||
endpoint := fmt.Sprintf("/v1/agent/check/%s/%s", status, checkID)
|
||||
r := a.c.newRequest("PUT", endpoint)
|
||||
r.params.Set("note", note)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckRegister is used to register a new check with
|
||||
// the local agent
|
||||
func (a *Agent) CheckRegister(check *AgentCheckRegistration) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/check/register")
|
||||
r.obj = check
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckDeregister is used to deregister a check with
|
||||
// the local agent
|
||||
func (a *Agent) CheckDeregister(checkID string) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/check/deregister/"+checkID)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Join is used to instruct the agent to attempt a join to
|
||||
// another cluster member
|
||||
func (a *Agent) Join(addr string, wan bool) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/join/"+addr)
|
||||
if wan {
|
||||
r.params.Set("wan", "1")
|
||||
}
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForceLeave is used to have the agent eject a failed node
|
||||
func (a *Agent) ForceLeave(node string) error {
|
||||
r := a.c.newRequest("PUT", "/v1/agent/force-leave/"+node)
|
||||
_, resp, err := requireOK(a.c.doRequest(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
323
vendor/github.com/armon/consul-api/api.go
generated
vendored
Normal file
323
vendor/github.com/armon/consul-api/api.go
generated
vendored
Normal file
@ -0,0 +1,323 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// QueryOptions are used to parameterize a query
|
||||
type QueryOptions struct {
|
||||
// Providing a datacenter overwrites the DC provided
|
||||
// by the Config
|
||||
Datacenter string
|
||||
|
||||
// AllowStale allows any Consul server (non-leader) to service
|
||||
// a read. This allows for lower latency and higher throughput
|
||||
AllowStale bool
|
||||
|
||||
// RequireConsistent forces the read to be fully consistent.
|
||||
// This is more expensive but prevents ever performing a stale
|
||||
// read.
|
||||
RequireConsistent bool
|
||||
|
||||
// WaitIndex is used to enable a blocking query. Waits
|
||||
// until the timeout or the next index is reached
|
||||
WaitIndex uint64
|
||||
|
||||
// WaitTime is used to bound the duration of a wait.
|
||||
// Defaults to that of the Config, but can be overriden.
|
||||
WaitTime time.Duration
|
||||
|
||||
// Token is used to provide a per-request ACL token
|
||||
// which overrides the agent's default token.
|
||||
Token string
|
||||
}
|
||||
|
||||
// WriteOptions are used to parameterize a write
|
||||
type WriteOptions struct {
|
||||
// Providing a datacenter overwrites the DC provided
|
||||
// by the Config
|
||||
Datacenter string
|
||||
|
||||
// Token is used to provide a per-request ACL token
|
||||
// which overrides the agent's default token.
|
||||
Token string
|
||||
}
|
||||
|
||||
// QueryMeta is used to return meta data about a query
|
||||
type QueryMeta struct {
|
||||
// LastIndex. This can be used as a WaitIndex to perform
|
||||
// a blocking query
|
||||
LastIndex uint64
|
||||
|
||||
// Time of last contact from the leader for the
|
||||
// server servicing the request
|
||||
LastContact time.Duration
|
||||
|
||||
// Is there a known leader
|
||||
KnownLeader bool
|
||||
|
||||
// How long did the request take
|
||||
RequestTime time.Duration
|
||||
}
|
||||
|
||||
// WriteMeta is used to return meta data about a write
|
||||
type WriteMeta struct {
|
||||
// How long did the request take
|
||||
RequestTime time.Duration
|
||||
}
|
||||
|
||||
// HttpBasicAuth is used to authenticate http client with HTTP Basic Authentication
|
||||
type HttpBasicAuth struct {
|
||||
// Username to use for HTTP Basic Authentication
|
||||
Username string
|
||||
|
||||
// Password to use for HTTP Basic Authentication
|
||||
Password string
|
||||
}
|
||||
|
||||
// Config is used to configure the creation of a client
|
||||
type Config struct {
|
||||
// Address is the address of the Consul server
|
||||
Address string
|
||||
|
||||
// Scheme is the URI scheme for the Consul server
|
||||
Scheme string
|
||||
|
||||
// Datacenter to use. If not provided, the default agent datacenter is used.
|
||||
Datacenter string
|
||||
|
||||
// HttpClient is the client to use. Default will be
|
||||
// used if not provided.
|
||||
HttpClient *http.Client
|
||||
|
||||
// HttpAuth is the auth info to use for http access.
|
||||
HttpAuth *HttpBasicAuth
|
||||
|
||||
// WaitTime limits how long a Watch will block. If not provided,
|
||||
// the agent default values will be used.
|
||||
WaitTime time.Duration
|
||||
|
||||
// Token is used to provide a per-request ACL token
|
||||
// which overrides the agent's default token.
|
||||
Token string
|
||||
}
|
||||
|
||||
// DefaultConfig returns a default configuration for the client
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Address: "127.0.0.1:8500",
|
||||
Scheme: "http",
|
||||
HttpClient: http.DefaultClient,
|
||||
}
|
||||
}
|
||||
|
||||
// Client provides a client to the Consul API
|
||||
type Client struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
// NewClient returns a new client
|
||||
func NewClient(config *Config) (*Client, error) {
|
||||
// bootstrap the config
|
||||
defConfig := DefaultConfig()
|
||||
|
||||
if len(config.Address) == 0 {
|
||||
config.Address = defConfig.Address
|
||||
}
|
||||
|
||||
if len(config.Scheme) == 0 {
|
||||
config.Scheme = defConfig.Scheme
|
||||
}
|
||||
|
||||
if config.HttpClient == nil {
|
||||
config.HttpClient = defConfig.HttpClient
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
config: *config,
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// request is used to help build up a request
|
||||
type request struct {
|
||||
config *Config
|
||||
method string
|
||||
url *url.URL
|
||||
params url.Values
|
||||
body io.Reader
|
||||
obj interface{}
|
||||
}
|
||||
|
||||
// setQueryOptions is used to annotate the request with
|
||||
// additional query options
|
||||
func (r *request) setQueryOptions(q *QueryOptions) {
|
||||
if q == nil {
|
||||
return
|
||||
}
|
||||
if q.Datacenter != "" {
|
||||
r.params.Set("dc", q.Datacenter)
|
||||
}
|
||||
if q.AllowStale {
|
||||
r.params.Set("stale", "")
|
||||
}
|
||||
if q.RequireConsistent {
|
||||
r.params.Set("consistent", "")
|
||||
}
|
||||
if q.WaitIndex != 0 {
|
||||
r.params.Set("index", strconv.FormatUint(q.WaitIndex, 10))
|
||||
}
|
||||
if q.WaitTime != 0 {
|
||||
r.params.Set("wait", durToMsec(q.WaitTime))
|
||||
}
|
||||
if q.Token != "" {
|
||||
r.params.Set("token", q.Token)
|
||||
}
|
||||
}
|
||||
|
||||
// durToMsec converts a duration to a millisecond specified string
|
||||
func durToMsec(dur time.Duration) string {
|
||||
return fmt.Sprintf("%dms", dur/time.Millisecond)
|
||||
}
|
||||
|
||||
// setWriteOptions is used to annotate the request with
|
||||
// additional write options
|
||||
func (r *request) setWriteOptions(q *WriteOptions) {
|
||||
if q == nil {
|
||||
return
|
||||
}
|
||||
if q.Datacenter != "" {
|
||||
r.params.Set("dc", q.Datacenter)
|
||||
}
|
||||
if q.Token != "" {
|
||||
r.params.Set("token", q.Token)
|
||||
}
|
||||
}
|
||||
|
||||
// toHTTP converts the request to an HTTP request
|
||||
func (r *request) toHTTP() (*http.Request, error) {
|
||||
// Encode the query parameters
|
||||
r.url.RawQuery = r.params.Encode()
|
||||
|
||||
// Get the url sring
|
||||
urlRaw := r.url.String()
|
||||
|
||||
// Check if we should encode the body
|
||||
if r.body == nil && r.obj != nil {
|
||||
if b, err := encodeBody(r.obj); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
r.body = b
|
||||
}
|
||||
}
|
||||
|
||||
// Create the HTTP request
|
||||
req, err := http.NewRequest(r.method, urlRaw, r.body)
|
||||
|
||||
// Setup auth
|
||||
if err == nil && r.config.HttpAuth != nil {
|
||||
req.SetBasicAuth(r.config.HttpAuth.Username, r.config.HttpAuth.Password)
|
||||
}
|
||||
|
||||
return req, err
|
||||
}
|
||||
|
||||
// newRequest is used to create a new request
|
||||
func (c *Client) newRequest(method, path string) *request {
|
||||
r := &request{
|
||||
config: &c.config,
|
||||
method: method,
|
||||
url: &url.URL{
|
||||
Scheme: c.config.Scheme,
|
||||
Host: c.config.Address,
|
||||
Path: path,
|
||||
},
|
||||
params: make(map[string][]string),
|
||||
}
|
||||
if c.config.Datacenter != "" {
|
||||
r.params.Set("dc", c.config.Datacenter)
|
||||
}
|
||||
if c.config.WaitTime != 0 {
|
||||
r.params.Set("wait", durToMsec(r.config.WaitTime))
|
||||
}
|
||||
if c.config.Token != "" {
|
||||
r.params.Set("token", r.config.Token)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// doRequest runs a request with our client
|
||||
func (c *Client) doRequest(r *request) (time.Duration, *http.Response, error) {
|
||||
req, err := r.toHTTP()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
start := time.Now()
|
||||
resp, err := c.config.HttpClient.Do(req)
|
||||
diff := time.Now().Sub(start)
|
||||
return diff, resp, err
|
||||
}
|
||||
|
||||
// parseQueryMeta is used to help parse query meta-data
|
||||
func parseQueryMeta(resp *http.Response, q *QueryMeta) error {
|
||||
header := resp.Header
|
||||
|
||||
// Parse the X-Consul-Index
|
||||
index, err := strconv.ParseUint(header.Get("X-Consul-Index"), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse X-Consul-Index: %v", err)
|
||||
}
|
||||
q.LastIndex = index
|
||||
|
||||
// Parse the X-Consul-LastContact
|
||||
last, err := strconv.ParseUint(header.Get("X-Consul-LastContact"), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse X-Consul-LastContact: %v", err)
|
||||
}
|
||||
q.LastContact = time.Duration(last) * time.Millisecond
|
||||
|
||||
// Parse the X-Consul-KnownLeader
|
||||
switch header.Get("X-Consul-KnownLeader") {
|
||||
case "true":
|
||||
q.KnownLeader = true
|
||||
default:
|
||||
q.KnownLeader = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeBody is used to JSON decode a body
|
||||
func decodeBody(resp *http.Response, out interface{}) error {
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
return dec.Decode(out)
|
||||
}
|
||||
|
||||
// encodeBody is used to encode a request body
|
||||
func encodeBody(obj interface{}) (io.Reader, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
enc := json.NewEncoder(buf)
|
||||
if err := enc.Encode(obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// requireOK is used to wrap doRequest and check for a 200
|
||||
func requireOK(d time.Duration, resp *http.Response, e error) (time.Duration, *http.Response, error) {
|
||||
if e != nil {
|
||||
return d, resp, e
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, resp.Body)
|
||||
return d, resp, fmt.Errorf("Unexpected response code: %d (%s)", resp.StatusCode, buf.Bytes())
|
||||
}
|
||||
return d, resp, e
|
||||
}
|
181
vendor/github.com/armon/consul-api/catalog.go
generated
vendored
Normal file
181
vendor/github.com/armon/consul-api/catalog.go
generated
vendored
Normal file
@ -0,0 +1,181 @@
|
||||
package consulapi
|
||||
|
||||
type Node struct {
|
||||
Node string
|
||||
Address string
|
||||
}
|
||||
|
||||
type CatalogService struct {
|
||||
Node string
|
||||
Address string
|
||||
ServiceID string
|
||||
ServiceName string
|
||||
ServiceTags []string
|
||||
ServicePort int
|
||||
}
|
||||
|
||||
type CatalogNode struct {
|
||||
Node *Node
|
||||
Services map[string]*AgentService
|
||||
}
|
||||
|
||||
type CatalogRegistration struct {
|
||||
Node string
|
||||
Address string
|
||||
Datacenter string
|
||||
Service *AgentService
|
||||
Check *AgentCheck
|
||||
}
|
||||
|
||||
type CatalogDeregistration struct {
|
||||
Node string
|
||||
Address string
|
||||
Datacenter string
|
||||
ServiceID string
|
||||
CheckID string
|
||||
}
|
||||
|
||||
// Catalog can be used to query the Catalog endpoints
|
||||
type Catalog struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Catalog returns a handle to the catalog endpoints
|
||||
func (c *Client) Catalog() *Catalog {
|
||||
return &Catalog{c}
|
||||
}
|
||||
|
||||
func (c *Catalog) Register(reg *CatalogRegistration, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := c.c.newRequest("PUT", "/v1/catalog/register")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = reg
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{}
|
||||
wm.RequestTime = rtt
|
||||
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
func (c *Catalog) Deregister(dereg *CatalogDeregistration, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := c.c.newRequest("PUT", "/v1/catalog/deregister")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = dereg
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{}
|
||||
wm.RequestTime = rtt
|
||||
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Datacenters is used to query for all the known datacenters
|
||||
func (c *Catalog) Datacenters() ([]string, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/datacenters")
|
||||
_, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var out []string
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Nodes is used to query all the known nodes
|
||||
func (c *Catalog) Nodes(q *QueryOptions) ([]*Node, *QueryMeta, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/nodes")
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*Node
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Services is used to query for all known services
|
||||
func (c *Catalog) Services(q *QueryOptions) (map[string][]string, *QueryMeta, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/services")
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out map[string][]string
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Service is used to query catalog entries for a given service
|
||||
func (c *Catalog) Service(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/service/"+service)
|
||||
r.setQueryOptions(q)
|
||||
if tag != "" {
|
||||
r.params.Set("tag", tag)
|
||||
}
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*CatalogService
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Node is used to query for service information about a single node
|
||||
func (c *Catalog) Node(node string, q *QueryOptions) (*CatalogNode, *QueryMeta, error) {
|
||||
r := c.c.newRequest("GET", "/v1/catalog/node/"+node)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out *CatalogNode
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
104
vendor/github.com/armon/consul-api/event.go
generated
vendored
Normal file
104
vendor/github.com/armon/consul-api/event.go
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Event can be used to query the Event endpoints
|
||||
type Event struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// UserEvent represents an event that was fired by the user
|
||||
type UserEvent struct {
|
||||
ID string
|
||||
Name string
|
||||
Payload []byte
|
||||
NodeFilter string
|
||||
ServiceFilter string
|
||||
TagFilter string
|
||||
Version int
|
||||
LTime uint64
|
||||
}
|
||||
|
||||
// Event returns a handle to the event endpoints
|
||||
func (c *Client) Event() *Event {
|
||||
return &Event{c}
|
||||
}
|
||||
|
||||
// Fire is used to fire a new user event. Only the Name, Payload and Filters
|
||||
// are respected. This returns the ID or an associated error. Cross DC requests
|
||||
// are supported.
|
||||
func (e *Event) Fire(params *UserEvent, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
r := e.c.newRequest("PUT", "/v1/event/fire/"+params.Name)
|
||||
r.setWriteOptions(q)
|
||||
if params.NodeFilter != "" {
|
||||
r.params.Set("node", params.NodeFilter)
|
||||
}
|
||||
if params.ServiceFilter != "" {
|
||||
r.params.Set("service", params.ServiceFilter)
|
||||
}
|
||||
if params.TagFilter != "" {
|
||||
r.params.Set("tag", params.TagFilter)
|
||||
}
|
||||
if params.Payload != nil {
|
||||
r.body = bytes.NewReader(params.Payload)
|
||||
}
|
||||
|
||||
rtt, resp, err := requireOK(e.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
var out UserEvent
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return out.ID, wm, nil
|
||||
}
|
||||
|
||||
// List is used to get the most recent events an agent has received.
|
||||
// This list can be optionally filtered by the name. This endpoint supports
|
||||
// quasi-blocking queries. The index is not monotonic, nor does it provide provide
|
||||
// LastContact or KnownLeader.
|
||||
func (e *Event) List(name string, q *QueryOptions) ([]*UserEvent, *QueryMeta, error) {
|
||||
r := e.c.newRequest("GET", "/v1/event/list")
|
||||
r.setQueryOptions(q)
|
||||
if name != "" {
|
||||
r.params.Set("name", name)
|
||||
}
|
||||
rtt, resp, err := requireOK(e.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*UserEvent
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
||||
|
||||
// IDToIndex is a bit of a hack. This simulates the index generation to
|
||||
// convert an event ID into a WaitIndex.
|
||||
func (e *Event) IDToIndex(uuid string) uint64 {
|
||||
lower := uuid[0:8] + uuid[9:13] + uuid[14:18]
|
||||
upper := uuid[19:23] + uuid[24:36]
|
||||
lowVal, err := strconv.ParseUint(lower, 16, 64)
|
||||
if err != nil {
|
||||
panic("Failed to convert " + lower)
|
||||
}
|
||||
highVal, err := strconv.ParseUint(upper, 16, 64)
|
||||
if err != nil {
|
||||
panic("Failed to convert " + upper)
|
||||
}
|
||||
return lowVal ^ highVal
|
||||
}
|
136
vendor/github.com/armon/consul-api/health.go
generated
vendored
Normal file
136
vendor/github.com/armon/consul-api/health.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// HealthCheck is used to represent a single check
|
||||
type HealthCheck struct {
|
||||
Node string
|
||||
CheckID string
|
||||
Name string
|
||||
Status string
|
||||
Notes string
|
||||
Output string
|
||||
ServiceID string
|
||||
ServiceName string
|
||||
}
|
||||
|
||||
// ServiceEntry is used for the health service endpoint
|
||||
type ServiceEntry struct {
|
||||
Node *Node
|
||||
Service *AgentService
|
||||
Checks []*HealthCheck
|
||||
}
|
||||
|
||||
// Health can be used to query the Health endpoints
|
||||
type Health struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Health returns a handle to the health endpoints
|
||||
func (c *Client) Health() *Health {
|
||||
return &Health{c}
|
||||
}
|
||||
|
||||
// Node is used to query for checks belonging to a given node
|
||||
func (h *Health) Node(node string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
||||
r := h.c.newRequest("GET", "/v1/health/node/"+node)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*HealthCheck
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Checks is used to return the checks associated with a service
|
||||
func (h *Health) Checks(service string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
||||
r := h.c.newRequest("GET", "/v1/health/checks/"+service)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*HealthCheck
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// Service is used to query health information along with service info
|
||||
// for a given service. It can optionally do server-side filtering on a tag
|
||||
// or nodes with passing health checks only.
|
||||
func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
|
||||
r := h.c.newRequest("GET", "/v1/health/service/"+service)
|
||||
r.setQueryOptions(q)
|
||||
if tag != "" {
|
||||
r.params.Set("tag", tag)
|
||||
}
|
||||
if passingOnly {
|
||||
r.params.Set("passing", "1")
|
||||
}
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*ServiceEntry
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
||||
|
||||
// State is used to retrieve all the checks in a given state.
|
||||
// The wildcard "any" state can also be used for all checks.
|
||||
func (h *Health) State(state string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) {
|
||||
switch state {
|
||||
case "any":
|
||||
case "warning":
|
||||
case "critical":
|
||||
case "passing":
|
||||
case "unknown":
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("Unsupported state: %v", state)
|
||||
}
|
||||
r := h.c.newRequest("GET", "/v1/health/state/"+state)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var out []*HealthCheck
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return out, qm, nil
|
||||
}
|
219
vendor/github.com/armon/consul-api/kv.go
generated
vendored
Normal file
219
vendor/github.com/armon/consul-api/kv.go
generated
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// KVPair is used to represent a single K/V entry
|
||||
type KVPair struct {
|
||||
Key string
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
LockIndex uint64
|
||||
Flags uint64
|
||||
Value []byte
|
||||
Session string
|
||||
}
|
||||
|
||||
// KVPairs is a list of KVPair objects
|
||||
type KVPairs []*KVPair
|
||||
|
||||
// KV is used to manipulate the K/V API
|
||||
type KV struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// KV is used to return a handle to the K/V apis
|
||||
func (c *Client) KV() *KV {
|
||||
return &KV{c}
|
||||
}
|
||||
|
||||
// Get is used to lookup a single key
|
||||
func (k *KV) Get(key string, q *QueryOptions) (*KVPair, *QueryMeta, error) {
|
||||
resp, qm, err := k.getInternal(key, nil, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, qm, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entries []*KVPair
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
return entries[0], qm, nil
|
||||
}
|
||||
return nil, qm, nil
|
||||
}
|
||||
|
||||
// List is used to lookup all keys under a prefix
|
||||
func (k *KV) List(prefix string, q *QueryOptions) (KVPairs, *QueryMeta, error) {
|
||||
resp, qm, err := k.getInternal(prefix, map[string]string{"recurse": ""}, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, qm, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entries []*KVPair
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
||||
|
||||
// Keys is used to list all the keys under a prefix. Optionally,
|
||||
// a separator can be used to limit the responses.
|
||||
func (k *KV) Keys(prefix, separator string, q *QueryOptions) ([]string, *QueryMeta, error) {
|
||||
params := map[string]string{"keys": ""}
|
||||
if separator != "" {
|
||||
params["separator"] = separator
|
||||
}
|
||||
resp, qm, err := k.getInternal(prefix, params, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, qm, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var entries []string
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
||||
|
||||
func (k *KV) getInternal(key string, params map[string]string, q *QueryOptions) (*http.Response, *QueryMeta, error) {
|
||||
r := k.c.newRequest("GET", "/v1/kv/"+key)
|
||||
r.setQueryOptions(q)
|
||||
for param, val := range params {
|
||||
r.params.Set(param, val)
|
||||
}
|
||||
rtt, resp, err := k.c.doRequest(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
if resp.StatusCode == 404 {
|
||||
resp.Body.Close()
|
||||
return nil, qm, nil
|
||||
} else if resp.StatusCode != 200 {
|
||||
resp.Body.Close()
|
||||
return nil, nil, fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// Put is used to write a new value. Only the
|
||||
// Key, Flags and Value is respected.
|
||||
func (k *KV) Put(p *KVPair, q *WriteOptions) (*WriteMeta, error) {
|
||||
params := make(map[string]string, 1)
|
||||
if p.Flags != 0 {
|
||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
||||
}
|
||||
_, wm, err := k.put(p.Key, params, p.Value, q)
|
||||
return wm, err
|
||||
}
|
||||
|
||||
// CAS is used for a Check-And-Set operation. The Key,
|
||||
// ModifyIndex, Flags and Value are respected. Returns true
|
||||
// on success or false on failures.
|
||||
func (k *KV) CAS(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
params := make(map[string]string, 2)
|
||||
if p.Flags != 0 {
|
||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
||||
}
|
||||
params["cas"] = strconv.FormatUint(p.ModifyIndex, 10)
|
||||
return k.put(p.Key, params, p.Value, q)
|
||||
}
|
||||
|
||||
// Acquire is used for a lock acquisiiton operation. The Key,
|
||||
// Flags, Value and Session are respected. Returns true
|
||||
// on success or false on failures.
|
||||
func (k *KV) Acquire(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
params := make(map[string]string, 2)
|
||||
if p.Flags != 0 {
|
||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
||||
}
|
||||
params["acquire"] = p.Session
|
||||
return k.put(p.Key, params, p.Value, q)
|
||||
}
|
||||
|
||||
// Release is used for a lock release operation. The Key,
|
||||
// Flags, Value and Session are respected. Returns true
|
||||
// on success or false on failures.
|
||||
func (k *KV) Release(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
params := make(map[string]string, 2)
|
||||
if p.Flags != 0 {
|
||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
||||
}
|
||||
params["release"] = p.Session
|
||||
return k.put(p.Key, params, p.Value, q)
|
||||
}
|
||||
|
||||
func (k *KV) put(key string, params map[string]string, body []byte, q *WriteOptions) (bool, *WriteMeta, error) {
|
||||
r := k.c.newRequest("PUT", "/v1/kv/"+key)
|
||||
r.setWriteOptions(q)
|
||||
for param, val := range params {
|
||||
r.params.Set(param, val)
|
||||
}
|
||||
r.body = bytes.NewReader(body)
|
||||
rtt, resp, err := requireOK(k.c.doRequest(r))
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &WriteMeta{}
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
||||
return false, nil, fmt.Errorf("Failed to read response: %v", err)
|
||||
}
|
||||
res := strings.Contains(string(buf.Bytes()), "true")
|
||||
return res, qm, nil
|
||||
}
|
||||
|
||||
// Delete is used to delete a single key
|
||||
func (k *KV) Delete(key string, w *WriteOptions) (*WriteMeta, error) {
|
||||
return k.deleteInternal(key, nil, w)
|
||||
}
|
||||
|
||||
// DeleteTree is used to delete all keys under a prefix
|
||||
func (k *KV) DeleteTree(prefix string, w *WriteOptions) (*WriteMeta, error) {
|
||||
return k.deleteInternal(prefix, []string{"recurse"}, w)
|
||||
}
|
||||
|
||||
func (k *KV) deleteInternal(key string, params []string, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := k.c.newRequest("DELETE", "/v1/kv/"+key)
|
||||
r.setWriteOptions(q)
|
||||
for _, param := range params {
|
||||
r.params.Set(param, "")
|
||||
}
|
||||
rtt, resp, err := requireOK(k.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
qm := &WriteMeta{}
|
||||
qm.RequestTime = rtt
|
||||
return qm, nil
|
||||
}
|
204
vendor/github.com/armon/consul-api/session.go
generated
vendored
Normal file
204
vendor/github.com/armon/consul-api/session.go
generated
vendored
Normal file
@ -0,0 +1,204 @@
|
||||
package consulapi
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// SessionEntry represents a session in consul
|
||||
type SessionEntry struct {
|
||||
CreateIndex uint64
|
||||
ID string
|
||||
Name string
|
||||
Node string
|
||||
Checks []string
|
||||
LockDelay time.Duration
|
||||
Behavior string
|
||||
TTL string
|
||||
}
|
||||
|
||||
// Session can be used to query the Session endpoints
|
||||
type Session struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Session returns a handle to the session endpoints
|
||||
func (c *Client) Session() *Session {
|
||||
return &Session{c}
|
||||
}
|
||||
|
||||
// CreateNoChecks is like Create but is used specifically to create
|
||||
// a session with no associated health checks.
|
||||
func (s *Session) CreateNoChecks(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
body := make(map[string]interface{})
|
||||
body["Checks"] = []string{}
|
||||
if se != nil {
|
||||
if se.Name != "" {
|
||||
body["Name"] = se.Name
|
||||
}
|
||||
if se.Node != "" {
|
||||
body["Node"] = se.Node
|
||||
}
|
||||
if se.LockDelay != 0 {
|
||||
body["LockDelay"] = durToMsec(se.LockDelay)
|
||||
}
|
||||
if se.Behavior != "" {
|
||||
body["Behavior"] = se.Behavior
|
||||
}
|
||||
if se.TTL != "" {
|
||||
body["TTL"] = se.TTL
|
||||
}
|
||||
}
|
||||
return s.create(body, q)
|
||||
|
||||
}
|
||||
|
||||
// Create makes a new session. Providing a session entry can
|
||||
// customize the session. It can also be nil to use defaults.
|
||||
func (s *Session) Create(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
var obj interface{}
|
||||
if se != nil {
|
||||
body := make(map[string]interface{})
|
||||
obj = body
|
||||
if se.Name != "" {
|
||||
body["Name"] = se.Name
|
||||
}
|
||||
if se.Node != "" {
|
||||
body["Node"] = se.Node
|
||||
}
|
||||
if se.LockDelay != 0 {
|
||||
body["LockDelay"] = durToMsec(se.LockDelay)
|
||||
}
|
||||
if len(se.Checks) > 0 {
|
||||
body["Checks"] = se.Checks
|
||||
}
|
||||
if se.Behavior != "" {
|
||||
body["Behavior"] = se.Behavior
|
||||
}
|
||||
if se.TTL != "" {
|
||||
body["TTL"] = se.TTL
|
||||
}
|
||||
}
|
||||
return s.create(obj, q)
|
||||
}
|
||||
|
||||
func (s *Session) create(obj interface{}, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
r := s.c.newRequest("PUT", "/v1/session/create")
|
||||
r.setWriteOptions(q)
|
||||
r.obj = obj
|
||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
var out struct{ ID string }
|
||||
if err := decodeBody(resp, &out); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return out.ID, wm, nil
|
||||
}
|
||||
|
||||
// Destroy invalides a given session
|
||||
func (s *Session) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
|
||||
r := s.c.newRequest("PUT", "/v1/session/destroy/"+id)
|
||||
r.setWriteOptions(q)
|
||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Renew renews the TTL on a given session
|
||||
func (s *Session) Renew(id string, q *WriteOptions) (*SessionEntry, *WriteMeta, error) {
|
||||
r := s.c.newRequest("PUT", "/v1/session/renew/"+id)
|
||||
r.setWriteOptions(q)
|
||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wm := &WriteMeta{RequestTime: rtt}
|
||||
|
||||
var entries []*SessionEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, wm, err
|
||||
}
|
||||
|
||||
if len(entries) > 0 {
|
||||
return entries[0], wm, nil
|
||||
}
|
||||
return nil, wm, nil
|
||||
}
|
||||
|
||||
// Info looks up a single session
|
||||
func (s *Session) Info(id string, q *QueryOptions) (*SessionEntry, *QueryMeta, error) {
|
||||
r := s.c.newRequest("GET", "/v1/session/info/"+id)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*SessionEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(entries) > 0 {
|
||||
return entries[0], qm, nil
|
||||
}
|
||||
return nil, qm, nil
|
||||
}
|
||||
|
||||
// List gets sessions for a node
|
||||
func (s *Session) Node(node string, q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
|
||||
r := s.c.newRequest("GET", "/v1/session/node/"+node)
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*SessionEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
||||
|
||||
// List gets all active sessions
|
||||
func (s *Session) List(q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
|
||||
r := s.c.newRequest("GET", "/v1/session/list")
|
||||
r.setQueryOptions(q)
|
||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
qm := &QueryMeta{}
|
||||
parseQueryMeta(resp, qm)
|
||||
qm.RequestTime = rtt
|
||||
|
||||
var entries []*SessionEntry
|
||||
if err := decodeBody(resp, &entries); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return entries, qm, nil
|
||||
}
|
43
vendor/github.com/armon/consul-api/status.go
generated
vendored
Normal file
43
vendor/github.com/armon/consul-api/status.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
package consulapi
|
||||
|
||||
// Status can be used to query the Status endpoints
|
||||
type Status struct {
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Status returns a handle to the status endpoints
|
||||
func (c *Client) Status() *Status {
|
||||
return &Status{c}
|
||||
}
|
||||
|
||||
// Leader is used to query for a known leader
|
||||
func (s *Status) Leader() (string, error) {
|
||||
r := s.c.newRequest("GET", "/v1/status/leader")
|
||||
_, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var leader string
|
||||
if err := decodeBody(resp, &leader); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return leader, nil
|
||||
}
|
||||
|
||||
// Peers is used to query for a known raft peers
|
||||
func (s *Status) Peers() ([]string, error) {
|
||||
r := s.c.newRequest("GET", "/v1/status/peers")
|
||||
_, resp, err := requireOK(s.c.doRequest(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var peers []string
|
||||
if err := decodeBody(resp, &peers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return peers, nil
|
||||
}
|
202
vendor/github.com/coreos/etcd/client/LICENSE
generated
vendored
Normal file
202
vendor/github.com/coreos/etcd/client/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
236
vendor/github.com/coreos/etcd/client/auth_role.go
generated
vendored
Normal file
236
vendor/github.com/coreos/etcd/client/auth_role.go
generated
vendored
Normal file
@ -0,0 +1,236 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Role struct {
|
||||
Role string `json:"role"`
|
||||
Permissions Permissions `json:"permissions"`
|
||||
Grant *Permissions `json:"grant,omitempty"`
|
||||
Revoke *Permissions `json:"revoke,omitempty"`
|
||||
}
|
||||
|
||||
type Permissions struct {
|
||||
KV rwPermission `json:"kv"`
|
||||
}
|
||||
|
||||
type rwPermission struct {
|
||||
Read []string `json:"read"`
|
||||
Write []string `json:"write"`
|
||||
}
|
||||
|
||||
type PermissionType int
|
||||
|
||||
const (
|
||||
ReadPermission PermissionType = iota
|
||||
WritePermission
|
||||
ReadWritePermission
|
||||
)
|
||||
|
||||
// NewAuthRoleAPI constructs a new AuthRoleAPI that uses HTTP to
|
||||
// interact with etcd's role creation and modification features.
|
||||
func NewAuthRoleAPI(c Client) AuthRoleAPI {
|
||||
return &httpAuthRoleAPI{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
type AuthRoleAPI interface {
|
||||
// AddRole adds a role.
|
||||
AddRole(ctx context.Context, role string) error
|
||||
|
||||
// RemoveRole removes a role.
|
||||
RemoveRole(ctx context.Context, role string) error
|
||||
|
||||
// GetRole retrieves role details.
|
||||
GetRole(ctx context.Context, role string) (*Role, error)
|
||||
|
||||
// GrantRoleKV grants a role some permission prefixes for the KV store.
|
||||
GrantRoleKV(ctx context.Context, role string, prefixes []string, permType PermissionType) (*Role, error)
|
||||
|
||||
// RevokeRoleKV revokes some permission prefixes for a role on the KV store.
|
||||
RevokeRoleKV(ctx context.Context, role string, prefixes []string, permType PermissionType) (*Role, error)
|
||||
|
||||
// ListRoles lists roles.
|
||||
ListRoles(ctx context.Context) ([]string, error)
|
||||
}
|
||||
|
||||
type httpAuthRoleAPI struct {
|
||||
client httpClient
|
||||
}
|
||||
|
||||
type authRoleAPIAction struct {
|
||||
verb string
|
||||
name string
|
||||
role *Role
|
||||
}
|
||||
|
||||
type authRoleAPIList struct{}
|
||||
|
||||
func (list *authRoleAPIList) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "roles", "")
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func (l *authRoleAPIAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "roles", l.name)
|
||||
if l.role == nil {
|
||||
req, _ := http.NewRequest(l.verb, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
b, err := json.Marshal(l.role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
body := bytes.NewReader(b)
|
||||
req, _ := http.NewRequest(l.verb, u.String(), body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) ListRoles(ctx context.Context) ([]string, error) {
|
||||
resp, body, err := r.client.Do(ctx, &authRoleAPIList{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var roleList struct {
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
if err = json.Unmarshal(body, &roleList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make([]string, 0, len(roleList.Roles))
|
||||
for _, r := range roleList.Roles {
|
||||
ret = append(ret, r.Role)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) AddRole(ctx context.Context, rolename string) error {
|
||||
role := &Role{
|
||||
Role: rolename,
|
||||
}
|
||||
return r.addRemoveRole(ctx, &authRoleAPIAction{
|
||||
verb: "PUT",
|
||||
name: rolename,
|
||||
role: role,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) RemoveRole(ctx context.Context, rolename string) error {
|
||||
return r.addRemoveRole(ctx, &authRoleAPIAction{
|
||||
verb: "DELETE",
|
||||
name: rolename,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) addRemoveRole(ctx context.Context, req *authRoleAPIAction) error {
|
||||
resp, body, err := r.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
||||
var sec authError
|
||||
err := json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) GetRole(ctx context.Context, rolename string) (*Role, error) {
|
||||
return r.modRole(ctx, &authRoleAPIAction{
|
||||
verb: "GET",
|
||||
name: rolename,
|
||||
})
|
||||
}
|
||||
|
||||
func buildRWPermission(prefixes []string, permType PermissionType) rwPermission {
|
||||
var out rwPermission
|
||||
switch permType {
|
||||
case ReadPermission:
|
||||
out.Read = prefixes
|
||||
case WritePermission:
|
||||
out.Write = prefixes
|
||||
case ReadWritePermission:
|
||||
out.Read = prefixes
|
||||
out.Write = prefixes
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) GrantRoleKV(ctx context.Context, rolename string, prefixes []string, permType PermissionType) (*Role, error) {
|
||||
rwp := buildRWPermission(prefixes, permType)
|
||||
role := &Role{
|
||||
Role: rolename,
|
||||
Grant: &Permissions{
|
||||
KV: rwp,
|
||||
},
|
||||
}
|
||||
return r.modRole(ctx, &authRoleAPIAction{
|
||||
verb: "PUT",
|
||||
name: rolename,
|
||||
role: role,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) RevokeRoleKV(ctx context.Context, rolename string, prefixes []string, permType PermissionType) (*Role, error) {
|
||||
rwp := buildRWPermission(prefixes, permType)
|
||||
role := &Role{
|
||||
Role: rolename,
|
||||
Revoke: &Permissions{
|
||||
KV: rwp,
|
||||
},
|
||||
}
|
||||
return r.modRole(ctx, &authRoleAPIAction{
|
||||
verb: "PUT",
|
||||
name: rolename,
|
||||
role: role,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) modRole(ctx context.Context, req *authRoleAPIAction) (*Role, error) {
|
||||
resp, body, err := r.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, sec
|
||||
}
|
||||
var role Role
|
||||
if err = json.Unmarshal(body, &role); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
}
|
319
vendor/github.com/coreos/etcd/client/auth_user.go
generated
vendored
Normal file
319
vendor/github.com/coreos/etcd/client/auth_user.go
generated
vendored
Normal file
@ -0,0 +1,319 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultV2AuthPrefix = "/v2/auth"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
User string `json:"user"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Roles []string `json:"roles"`
|
||||
Grant []string `json:"grant,omitempty"`
|
||||
Revoke []string `json:"revoke,omitempty"`
|
||||
}
|
||||
|
||||
// userListEntry is the user representation given by the server for ListUsers
|
||||
type userListEntry struct {
|
||||
User string `json:"user"`
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
|
||||
type UserRoles struct {
|
||||
User string `json:"user"`
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
|
||||
func v2AuthURL(ep url.URL, action string, name string) *url.URL {
|
||||
if name != "" {
|
||||
ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action, name)
|
||||
return &ep
|
||||
}
|
||||
ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action)
|
||||
return &ep
|
||||
}
|
||||
|
||||
// NewAuthAPI constructs a new AuthAPI that uses HTTP to
|
||||
// interact with etcd's general auth features.
|
||||
func NewAuthAPI(c Client) AuthAPI {
|
||||
return &httpAuthAPI{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
type AuthAPI interface {
|
||||
// Enable auth.
|
||||
Enable(ctx context.Context) error
|
||||
|
||||
// Disable auth.
|
||||
Disable(ctx context.Context) error
|
||||
}
|
||||
|
||||
type httpAuthAPI struct {
|
||||
client httpClient
|
||||
}
|
||||
|
||||
func (s *httpAuthAPI) Enable(ctx context.Context) error {
|
||||
return s.enableDisable(ctx, &authAPIAction{"PUT"})
|
||||
}
|
||||
|
||||
func (s *httpAuthAPI) Disable(ctx context.Context) error {
|
||||
return s.enableDisable(ctx, &authAPIAction{"DELETE"})
|
||||
}
|
||||
|
||||
func (s *httpAuthAPI) enableDisable(ctx context.Context, req httpAction) error {
|
||||
resp, body, err := s.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type authAPIAction struct {
|
||||
verb string
|
||||
}
|
||||
|
||||
func (l *authAPIAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "enable", "")
|
||||
req, _ := http.NewRequest(l.verb, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type authError struct {
|
||||
Message string `json:"message"`
|
||||
Code int `json:"-"`
|
||||
}
|
||||
|
||||
func (e authError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// NewAuthUserAPI constructs a new AuthUserAPI that uses HTTP to
|
||||
// interact with etcd's user creation and modification features.
|
||||
func NewAuthUserAPI(c Client) AuthUserAPI {
|
||||
return &httpAuthUserAPI{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
type AuthUserAPI interface {
|
||||
// AddUser adds a user.
|
||||
AddUser(ctx context.Context, username string, password string) error
|
||||
|
||||
// RemoveUser removes a user.
|
||||
RemoveUser(ctx context.Context, username string) error
|
||||
|
||||
// GetUser retrieves user details.
|
||||
GetUser(ctx context.Context, username string) (*User, error)
|
||||
|
||||
// GrantUser grants a user some permission roles.
|
||||
GrantUser(ctx context.Context, username string, roles []string) (*User, error)
|
||||
|
||||
// RevokeUser revokes some permission roles from a user.
|
||||
RevokeUser(ctx context.Context, username string, roles []string) (*User, error)
|
||||
|
||||
// ChangePassword changes the user's password.
|
||||
ChangePassword(ctx context.Context, username string, password string) (*User, error)
|
||||
|
||||
// ListUsers lists the users.
|
||||
ListUsers(ctx context.Context) ([]string, error)
|
||||
}
|
||||
|
||||
type httpAuthUserAPI struct {
|
||||
client httpClient
|
||||
}
|
||||
|
||||
type authUserAPIAction struct {
|
||||
verb string
|
||||
username string
|
||||
user *User
|
||||
}
|
||||
|
||||
type authUserAPIList struct{}
|
||||
|
||||
func (list *authUserAPIList) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "users", "")
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func (l *authUserAPIAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "users", l.username)
|
||||
if l.user == nil {
|
||||
req, _ := http.NewRequest(l.verb, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
b, err := json.Marshal(l.user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
body := bytes.NewReader(b)
|
||||
req, _ := http.NewRequest(l.verb, u.String(), body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) ListUsers(ctx context.Context) ([]string, error) {
|
||||
resp, body, err := u.client.Do(ctx, &authUserAPIList{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, sec
|
||||
}
|
||||
|
||||
var userList struct {
|
||||
Users []userListEntry `json:"users"`
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(body, &userList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make([]string, 0, len(userList.Users))
|
||||
for _, u := range userList.Users {
|
||||
ret = append(ret, u.User)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) AddUser(ctx context.Context, username string, password string) error {
|
||||
user := &User{
|
||||
User: username,
|
||||
Password: password,
|
||||
}
|
||||
return u.addRemoveUser(ctx, &authUserAPIAction{
|
||||
verb: "PUT",
|
||||
username: username,
|
||||
user: user,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) RemoveUser(ctx context.Context, username string) error {
|
||||
return u.addRemoveUser(ctx, &authUserAPIAction{
|
||||
verb: "DELETE",
|
||||
username: username,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) addRemoveUser(ctx context.Context, req *authUserAPIAction) error {
|
||||
resp, body, err := u.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) GetUser(ctx context.Context, username string) (*User, error) {
|
||||
return u.modUser(ctx, &authUserAPIAction{
|
||||
verb: "GET",
|
||||
username: username,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) GrantUser(ctx context.Context, username string, roles []string) (*User, error) {
|
||||
user := &User{
|
||||
User: username,
|
||||
Grant: roles,
|
||||
}
|
||||
return u.modUser(ctx, &authUserAPIAction{
|
||||
verb: "PUT",
|
||||
username: username,
|
||||
user: user,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) RevokeUser(ctx context.Context, username string, roles []string) (*User, error) {
|
||||
user := &User{
|
||||
User: username,
|
||||
Revoke: roles,
|
||||
}
|
||||
return u.modUser(ctx, &authUserAPIAction{
|
||||
verb: "PUT",
|
||||
username: username,
|
||||
user: user,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) ChangePassword(ctx context.Context, username string, password string) (*User, error) {
|
||||
user := &User{
|
||||
User: username,
|
||||
Password: password,
|
||||
}
|
||||
return u.modUser(ctx, &authUserAPIAction{
|
||||
verb: "PUT",
|
||||
username: username,
|
||||
user: user,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) modUser(ctx context.Context, req *authUserAPIAction) (*User, error) {
|
||||
resp, body, err := u.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, sec
|
||||
}
|
||||
var user User
|
||||
if err = json.Unmarshal(body, &user); err != nil {
|
||||
var userR UserRoles
|
||||
if urerr := json.Unmarshal(body, &userR); urerr != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.User = userR.User
|
||||
for _, r := range userR.Roles {
|
||||
user.Roles = append(user.Roles, r.Role)
|
||||
}
|
||||
}
|
||||
return &user, nil
|
||||
}
|
18
vendor/github.com/coreos/etcd/client/cancelreq.go
generated
vendored
Normal file
18
vendor/github.com/coreos/etcd/client/cancelreq.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// borrowed from golang/net/context/ctxhttp/cancelreq.go
|
||||
|
||||
package client
|
||||
|
||||
import "net/http"
|
||||
|
||||
func requestCanceler(tr CancelableTransport, req *http.Request) func() {
|
||||
ch := make(chan struct{})
|
||||
req.Cancel = ch
|
||||
|
||||
return func() {
|
||||
close(ch)
|
||||
}
|
||||
}
|
710
vendor/github.com/coreos/etcd/client/client.go
generated
vendored
Normal file
710
vendor/github.com/coreos/etcd/client/client.go
generated
vendored
Normal file
@ -0,0 +1,710 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/version"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoEndpoints = errors.New("client: no endpoints available")
|
||||
ErrTooManyRedirects = errors.New("client: too many redirects")
|
||||
ErrClusterUnavailable = errors.New("client: etcd cluster is unavailable or misconfigured")
|
||||
ErrNoLeaderEndpoint = errors.New("client: no leader endpoint available")
|
||||
errTooManyRedirectChecks = errors.New("client: too many redirect checks")
|
||||
|
||||
// oneShotCtxValue is set on a context using WithValue(&oneShotValue) so
|
||||
// that Do() will not retry a request
|
||||
oneShotCtxValue interface{}
|
||||
)
|
||||
|
||||
var DefaultRequestTimeout = 5 * time.Second
|
||||
|
||||
var DefaultTransport CancelableTransport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
type EndpointSelectionMode int
|
||||
|
||||
const (
|
||||
// EndpointSelectionRandom is the default value of the 'SelectionMode'.
|
||||
// As the name implies, the client object will pick a node from the members
|
||||
// of the cluster in a random fashion. If the cluster has three members, A, B,
|
||||
// and C, the client picks any node from its three members as its request
|
||||
// destination.
|
||||
EndpointSelectionRandom EndpointSelectionMode = iota
|
||||
|
||||
// If 'SelectionMode' is set to 'EndpointSelectionPrioritizeLeader',
|
||||
// requests are sent directly to the cluster leader. This reduces
|
||||
// forwarding roundtrips compared to making requests to etcd followers
|
||||
// who then forward them to the cluster leader. In the event of a leader
|
||||
// failure, however, clients configured this way cannot prioritize among
|
||||
// the remaining etcd followers. Therefore, when a client sets 'SelectionMode'
|
||||
// to 'EndpointSelectionPrioritizeLeader', it must use 'client.AutoSync()' to
|
||||
// maintain its knowledge of current cluster state.
|
||||
//
|
||||
// This mode should be used with Client.AutoSync().
|
||||
EndpointSelectionPrioritizeLeader
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// Endpoints defines a set of URLs (schemes, hosts and ports only)
|
||||
// that can be used to communicate with a logical etcd cluster. For
|
||||
// example, a three-node cluster could be provided like so:
|
||||
//
|
||||
// Endpoints: []string{
|
||||
// "http://node1.example.com:2379",
|
||||
// "http://node2.example.com:2379",
|
||||
// "http://node3.example.com:2379",
|
||||
// }
|
||||
//
|
||||
// If multiple endpoints are provided, the Client will attempt to
|
||||
// use them all in the event that one or more of them are unusable.
|
||||
//
|
||||
// If Client.Sync is ever called, the Client may cache an alternate
|
||||
// set of endpoints to continue operation.
|
||||
Endpoints []string
|
||||
|
||||
// Transport is used by the Client to drive HTTP requests. If not
|
||||
// provided, DefaultTransport will be used.
|
||||
Transport CancelableTransport
|
||||
|
||||
// CheckRedirect specifies the policy for handling HTTP redirects.
|
||||
// If CheckRedirect is not nil, the Client calls it before
|
||||
// following an HTTP redirect. The sole argument is the number of
|
||||
// requests that have already been made. If CheckRedirect returns
|
||||
// an error, Client.Do will not make any further requests and return
|
||||
// the error back it to the caller.
|
||||
//
|
||||
// If CheckRedirect is nil, the Client uses its default policy,
|
||||
// which is to stop after 10 consecutive requests.
|
||||
CheckRedirect CheckRedirectFunc
|
||||
|
||||
// Username specifies the user credential to add as an authorization header
|
||||
Username string
|
||||
|
||||
// Password is the password for the specified user to add as an authorization header
|
||||
// to the request.
|
||||
Password string
|
||||
|
||||
// HeaderTimeoutPerRequest specifies the time limit to wait for response
|
||||
// header in a single request made by the Client. The timeout includes
|
||||
// connection time, any redirects, and header wait time.
|
||||
//
|
||||
// For non-watch GET request, server returns the response body immediately.
|
||||
// For PUT/POST/DELETE request, server will attempt to commit request
|
||||
// before responding, which is expected to take `100ms + 2 * RTT`.
|
||||
// For watch request, server returns the header immediately to notify Client
|
||||
// watch start. But if server is behind some kind of proxy, the response
|
||||
// header may be cached at proxy, and Client cannot rely on this behavior.
|
||||
//
|
||||
// Especially, wait request will ignore this timeout.
|
||||
//
|
||||
// One API call may send multiple requests to different etcd servers until it
|
||||
// succeeds. Use context of the API to specify the overall timeout.
|
||||
//
|
||||
// A HeaderTimeoutPerRequest of zero means no timeout.
|
||||
HeaderTimeoutPerRequest time.Duration
|
||||
|
||||
// SelectionMode is an EndpointSelectionMode enum that specifies the
|
||||
// policy for choosing the etcd cluster node to which requests are sent.
|
||||
SelectionMode EndpointSelectionMode
|
||||
}
|
||||
|
||||
func (cfg *Config) transport() CancelableTransport {
|
||||
if cfg.Transport == nil {
|
||||
return DefaultTransport
|
||||
}
|
||||
return cfg.Transport
|
||||
}
|
||||
|
||||
func (cfg *Config) checkRedirect() CheckRedirectFunc {
|
||||
if cfg.CheckRedirect == nil {
|
||||
return DefaultCheckRedirect
|
||||
}
|
||||
return cfg.CheckRedirect
|
||||
}
|
||||
|
||||
// CancelableTransport mimics net/http.Transport, but requires that
|
||||
// the object also support request cancellation.
|
||||
type CancelableTransport interface {
|
||||
http.RoundTripper
|
||||
CancelRequest(req *http.Request)
|
||||
}
|
||||
|
||||
type CheckRedirectFunc func(via int) error
|
||||
|
||||
// DefaultCheckRedirect follows up to 10 redirects, but no more.
|
||||
var DefaultCheckRedirect CheckRedirectFunc = func(via int) error {
|
||||
if via > 10 {
|
||||
return ErrTooManyRedirects
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
// Sync updates the internal cache of the etcd cluster's membership.
|
||||
Sync(context.Context) error
|
||||
|
||||
// AutoSync periodically calls Sync() every given interval.
|
||||
// The recommended sync interval is 10 seconds to 1 minute, which does
|
||||
// not bring too much overhead to server and makes client catch up the
|
||||
// cluster change in time.
|
||||
//
|
||||
// The example to use it:
|
||||
//
|
||||
// for {
|
||||
// err := client.AutoSync(ctx, 10*time.Second)
|
||||
// if err == context.DeadlineExceeded || err == context.Canceled {
|
||||
// break
|
||||
// }
|
||||
// log.Print(err)
|
||||
// }
|
||||
AutoSync(context.Context, time.Duration) error
|
||||
|
||||
// Endpoints returns a copy of the current set of API endpoints used
|
||||
// by Client to resolve HTTP requests. If Sync has ever been called,
|
||||
// this may differ from the initial Endpoints provided in the Config.
|
||||
Endpoints() []string
|
||||
|
||||
// SetEndpoints sets the set of API endpoints used by Client to resolve
|
||||
// HTTP requests. If the given endpoints are not valid, an error will be
|
||||
// returned
|
||||
SetEndpoints(eps []string) error
|
||||
|
||||
// GetVersion retrieves the current etcd server and cluster version
|
||||
GetVersion(ctx context.Context) (*version.Versions, error)
|
||||
|
||||
httpClient
|
||||
}
|
||||
|
||||
func New(cfg Config) (Client, error) {
|
||||
c := &httpClusterClient{
|
||||
clientFactory: newHTTPClientFactory(cfg.transport(), cfg.checkRedirect(), cfg.HeaderTimeoutPerRequest),
|
||||
rand: rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
|
||||
selectionMode: cfg.SelectionMode,
|
||||
}
|
||||
if cfg.Username != "" {
|
||||
c.credentials = &credentials{
|
||||
username: cfg.Username,
|
||||
password: cfg.Password,
|
||||
}
|
||||
}
|
||||
if err := c.SetEndpoints(cfg.Endpoints); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type httpClient interface {
|
||||
Do(context.Context, httpAction) (*http.Response, []byte, error)
|
||||
}
|
||||
|
||||
func newHTTPClientFactory(tr CancelableTransport, cr CheckRedirectFunc, headerTimeout time.Duration) httpClientFactory {
|
||||
return func(ep url.URL) httpClient {
|
||||
return &redirectFollowingHTTPClient{
|
||||
checkRedirect: cr,
|
||||
client: &simpleHTTPClient{
|
||||
transport: tr,
|
||||
endpoint: ep,
|
||||
headerTimeout: headerTimeout,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type credentials struct {
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
type httpClientFactory func(url.URL) httpClient
|
||||
|
||||
type httpAction interface {
|
||||
HTTPRequest(url.URL) *http.Request
|
||||
}
|
||||
|
||||
type httpClusterClient struct {
|
||||
clientFactory httpClientFactory
|
||||
endpoints []url.URL
|
||||
pinned int
|
||||
credentials *credentials
|
||||
sync.RWMutex
|
||||
rand *rand.Rand
|
||||
selectionMode EndpointSelectionMode
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) getLeaderEndpoint(ctx context.Context, eps []url.URL) (string, error) {
|
||||
ceps := make([]url.URL, len(eps))
|
||||
copy(ceps, eps)
|
||||
|
||||
// To perform a lookup on the new endpoint list without using the current
|
||||
// client, we'll copy it
|
||||
clientCopy := &httpClusterClient{
|
||||
clientFactory: c.clientFactory,
|
||||
credentials: c.credentials,
|
||||
rand: c.rand,
|
||||
|
||||
pinned: 0,
|
||||
endpoints: ceps,
|
||||
}
|
||||
|
||||
mAPI := NewMembersAPI(clientCopy)
|
||||
leader, err := mAPI.Leader(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(leader.ClientURLs) == 0 {
|
||||
return "", ErrNoLeaderEndpoint
|
||||
}
|
||||
|
||||
return leader.ClientURLs[0], nil // TODO: how to handle multiple client URLs?
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) parseEndpoints(eps []string) ([]url.URL, error) {
|
||||
if len(eps) == 0 {
|
||||
return []url.URL{}, ErrNoEndpoints
|
||||
}
|
||||
|
||||
neps := make([]url.URL, len(eps))
|
||||
for i, ep := range eps {
|
||||
u, err := url.Parse(ep)
|
||||
if err != nil {
|
||||
return []url.URL{}, err
|
||||
}
|
||||
neps[i] = *u
|
||||
}
|
||||
return neps, nil
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) SetEndpoints(eps []string) error {
|
||||
neps, err := c.parseEndpoints(eps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.endpoints = shuffleEndpoints(c.rand, neps)
|
||||
// We're not doing anything for PrioritizeLeader here. This is
|
||||
// due to not having a context meaning we can't call getLeaderEndpoint
|
||||
// However, if you're using PrioritizeLeader, you've already been told
|
||||
// to regularly call sync, where we do have a ctx, and can figure the
|
||||
// leader. PrioritizeLeader is also quite a loose guarantee, so deal
|
||||
// with it
|
||||
c.pinned = 0
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
|
||||
action := act
|
||||
c.RLock()
|
||||
leps := len(c.endpoints)
|
||||
eps := make([]url.URL, leps)
|
||||
n := copy(eps, c.endpoints)
|
||||
pinned := c.pinned
|
||||
|
||||
if c.credentials != nil {
|
||||
action = &authedAction{
|
||||
act: act,
|
||||
credentials: *c.credentials,
|
||||
}
|
||||
}
|
||||
c.RUnlock()
|
||||
|
||||
if leps == 0 {
|
||||
return nil, nil, ErrNoEndpoints
|
||||
}
|
||||
|
||||
if leps != n {
|
||||
return nil, nil, errors.New("unable to pick endpoint: copy failed")
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
var body []byte
|
||||
var err error
|
||||
cerr := &ClusterError{}
|
||||
isOneShot := ctx.Value(&oneShotCtxValue) != nil
|
||||
|
||||
for i := pinned; i < leps+pinned; i++ {
|
||||
k := i % leps
|
||||
hc := c.clientFactory(eps[k])
|
||||
resp, body, err = hc.Do(ctx, action)
|
||||
if err != nil {
|
||||
cerr.Errors = append(cerr.Errors, err)
|
||||
if err == ctx.Err() {
|
||||
return nil, nil, ctx.Err()
|
||||
}
|
||||
if err == context.Canceled || err == context.DeadlineExceeded {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else if resp.StatusCode/100 == 5 {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusInternalServerError, http.StatusServiceUnavailable:
|
||||
// TODO: make sure this is a no leader response
|
||||
cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s has no leader", eps[k].String()))
|
||||
default:
|
||||
cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s returns server error [%s]", eps[k].String(), http.StatusText(resp.StatusCode)))
|
||||
}
|
||||
err = cerr.Errors[0]
|
||||
}
|
||||
if err != nil {
|
||||
if !isOneShot {
|
||||
continue
|
||||
}
|
||||
c.Lock()
|
||||
c.pinned = (k + 1) % leps
|
||||
c.Unlock()
|
||||
return nil, nil, err
|
||||
}
|
||||
if k != pinned {
|
||||
c.Lock()
|
||||
c.pinned = k
|
||||
c.Unlock()
|
||||
}
|
||||
return resp, body, nil
|
||||
}
|
||||
|
||||
return nil, nil, cerr
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) Endpoints() []string {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
eps := make([]string, len(c.endpoints))
|
||||
for i, ep := range c.endpoints {
|
||||
eps[i] = ep.String()
|
||||
}
|
||||
|
||||
return eps
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) Sync(ctx context.Context) error {
|
||||
mAPI := NewMembersAPI(c)
|
||||
ms, err := mAPI.List(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var eps []string
|
||||
for _, m := range ms {
|
||||
eps = append(eps, m.ClientURLs...)
|
||||
}
|
||||
|
||||
neps, err := c.parseEndpoints(eps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
npin := 0
|
||||
|
||||
switch c.selectionMode {
|
||||
case EndpointSelectionRandom:
|
||||
c.RLock()
|
||||
eq := endpointsEqual(c.endpoints, neps)
|
||||
c.RUnlock()
|
||||
|
||||
if eq {
|
||||
return nil
|
||||
}
|
||||
// When items in the endpoint list changes, we choose a new pin
|
||||
neps = shuffleEndpoints(c.rand, neps)
|
||||
case EndpointSelectionPrioritizeLeader:
|
||||
nle, err := c.getLeaderEndpoint(ctx, neps)
|
||||
if err != nil {
|
||||
return ErrNoLeaderEndpoint
|
||||
}
|
||||
|
||||
for i, n := range neps {
|
||||
if n.String() == nle {
|
||||
npin = i
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("invalid endpoint selection mode: %d", c.selectionMode)
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.endpoints = neps
|
||||
c.pinned = npin
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) AutoSync(ctx context.Context, interval time.Duration) error {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
err := c.Sync(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) GetVersion(ctx context.Context) (*version.Versions, error) {
|
||||
act := &getAction{Prefix: "/version"}
|
||||
|
||||
resp, body, err := c.Do(ctx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
if len(body) == 0 {
|
||||
return nil, ErrEmptyBody
|
||||
}
|
||||
var vresp version.Versions
|
||||
if err := json.Unmarshal(body, &vresp); err != nil {
|
||||
return nil, ErrInvalidJSON
|
||||
}
|
||||
return &vresp, nil
|
||||
default:
|
||||
var etcdErr Error
|
||||
if err := json.Unmarshal(body, &etcdErr); err != nil {
|
||||
return nil, ErrInvalidJSON
|
||||
}
|
||||
return nil, etcdErr
|
||||
}
|
||||
}
|
||||
|
||||
type roundTripResponse struct {
|
||||
resp *http.Response
|
||||
err error
|
||||
}
|
||||
|
||||
type simpleHTTPClient struct {
|
||||
transport CancelableTransport
|
||||
endpoint url.URL
|
||||
headerTimeout time.Duration
|
||||
}
|
||||
|
||||
func (c *simpleHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
|
||||
req := act.HTTPRequest(c.endpoint)
|
||||
|
||||
if err := printcURL(req); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
isWait := false
|
||||
if req != nil && req.URL != nil {
|
||||
ws := req.URL.Query().Get("wait")
|
||||
if len(ws) != 0 {
|
||||
var err error
|
||||
isWait, err = strconv.ParseBool(ws)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("wrong wait value %s (%v for %+v)", ws, err, req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hctx context.Context
|
||||
var hcancel context.CancelFunc
|
||||
if !isWait && c.headerTimeout > 0 {
|
||||
hctx, hcancel = context.WithTimeout(ctx, c.headerTimeout)
|
||||
} else {
|
||||
hctx, hcancel = context.WithCancel(ctx)
|
||||
}
|
||||
defer hcancel()
|
||||
|
||||
reqcancel := requestCanceler(c.transport, req)
|
||||
|
||||
rtchan := make(chan roundTripResponse, 1)
|
||||
go func() {
|
||||
resp, err := c.transport.RoundTrip(req)
|
||||
rtchan <- roundTripResponse{resp: resp, err: err}
|
||||
close(rtchan)
|
||||
}()
|
||||
|
||||
var resp *http.Response
|
||||
var err error
|
||||
|
||||
select {
|
||||
case rtresp := <-rtchan:
|
||||
resp, err = rtresp.resp, rtresp.err
|
||||
case <-hctx.Done():
|
||||
// cancel and wait for request to actually exit before continuing
|
||||
reqcancel()
|
||||
rtresp := <-rtchan
|
||||
resp = rtresp.resp
|
||||
switch {
|
||||
case ctx.Err() != nil:
|
||||
err = ctx.Err()
|
||||
case hctx.Err() != nil:
|
||||
err = fmt.Errorf("client: endpoint %s exceeded header timeout", c.endpoint.String())
|
||||
default:
|
||||
panic("failed to get error from context")
|
||||
}
|
||||
}
|
||||
|
||||
// always check for resp nil-ness to deal with possible
|
||||
// race conditions between channels above
|
||||
defer func() {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var body []byte
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
resp.Body.Close()
|
||||
<-done
|
||||
return nil, nil, ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
|
||||
return resp, body, err
|
||||
}
|
||||
|
||||
type authedAction struct {
|
||||
act httpAction
|
||||
credentials credentials
|
||||
}
|
||||
|
||||
func (a *authedAction) HTTPRequest(url url.URL) *http.Request {
|
||||
r := a.act.HTTPRequest(url)
|
||||
r.SetBasicAuth(a.credentials.username, a.credentials.password)
|
||||
return r
|
||||
}
|
||||
|
||||
type redirectFollowingHTTPClient struct {
|
||||
client httpClient
|
||||
checkRedirect CheckRedirectFunc
|
||||
}
|
||||
|
||||
func (r *redirectFollowingHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
|
||||
next := act
|
||||
for i := 0; i < 100; i++ {
|
||||
if i > 0 {
|
||||
if err := r.checkRedirect(i); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
resp, body, err := r.client.Do(ctx, next)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode/100 == 3 {
|
||||
hdr := resp.Header.Get("Location")
|
||||
if hdr == "" {
|
||||
return nil, nil, fmt.Errorf("Location header not set")
|
||||
}
|
||||
loc, err := url.Parse(hdr)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Location header not valid URL: %s", hdr)
|
||||
}
|
||||
next = &redirectedHTTPAction{
|
||||
action: act,
|
||||
location: *loc,
|
||||
}
|
||||
continue
|
||||
}
|
||||
return resp, body, nil
|
||||
}
|
||||
|
||||
return nil, nil, errTooManyRedirectChecks
|
||||
}
|
||||
|
||||
type redirectedHTTPAction struct {
|
||||
action httpAction
|
||||
location url.URL
|
||||
}
|
||||
|
||||
func (r *redirectedHTTPAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
orig := r.action.HTTPRequest(ep)
|
||||
orig.URL = &r.location
|
||||
return orig
|
||||
}
|
||||
|
||||
func shuffleEndpoints(r *rand.Rand, eps []url.URL) []url.URL {
|
||||
// copied from Go 1.9<= rand.Rand.Perm
|
||||
n := len(eps)
|
||||
p := make([]int, n)
|
||||
for i := 0; i < n; i++ {
|
||||
j := r.Intn(i + 1)
|
||||
p[i] = p[j]
|
||||
p[j] = i
|
||||
}
|
||||
neps := make([]url.URL, n)
|
||||
for i, k := range p {
|
||||
neps[i] = eps[k]
|
||||
}
|
||||
return neps
|
||||
}
|
||||
|
||||
func endpointsEqual(left, right []url.URL) bool {
|
||||
if len(left) != len(right) {
|
||||
return false
|
||||
}
|
||||
|
||||
sLeft := make([]string, len(left))
|
||||
sRight := make([]string, len(right))
|
||||
for i, l := range left {
|
||||
sLeft[i] = l.String()
|
||||
}
|
||||
for i, r := range right {
|
||||
sRight[i] = r.String()
|
||||
}
|
||||
|
||||
sort.Strings(sLeft)
|
||||
sort.Strings(sRight)
|
||||
for i := range sLeft {
|
||||
if sLeft[i] != sRight[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
37
vendor/github.com/coreos/etcd/client/cluster_error.go
generated
vendored
Normal file
37
vendor/github.com/coreos/etcd/client/cluster_error.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ClusterError struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func (ce *ClusterError) Error() string {
|
||||
s := ErrClusterUnavailable.Error()
|
||||
for i, e := range ce.Errors {
|
||||
s += fmt.Sprintf("; error #%d: %s\n", i, e)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (ce *ClusterError) Detail() string {
|
||||
s := ""
|
||||
for i, e := range ce.Errors {
|
||||
s += fmt.Sprintf("error #%d: %s\n", i, e)
|
||||
}
|
||||
return s
|
||||
}
|
70
vendor/github.com/coreos/etcd/client/curl.go
generated
vendored
Normal file
70
vendor/github.com/coreos/etcd/client/curl.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
cURLDebug = false
|
||||
)
|
||||
|
||||
func EnablecURLDebug() {
|
||||
cURLDebug = true
|
||||
}
|
||||
|
||||
func DisablecURLDebug() {
|
||||
cURLDebug = false
|
||||
}
|
||||
|
||||
// printcURL prints the cURL equivalent request to stderr.
|
||||
// It returns an error if the body of the request cannot
|
||||
// be read.
|
||||
// The caller MUST cancel the request if there is an error.
|
||||
func printcURL(req *http.Request) error {
|
||||
if !cURLDebug {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
command string
|
||||
b []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if req.URL != nil {
|
||||
command = fmt.Sprintf("curl -X %s %s", req.Method, req.URL.String())
|
||||
}
|
||||
|
||||
if req.Body != nil {
|
||||
b, err = ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
command += fmt.Sprintf(" -d %q", string(b))
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "cURL Command: %s\n", command)
|
||||
|
||||
// reset body
|
||||
body := bytes.NewBuffer(b)
|
||||
req.Body = ioutil.NopCloser(body)
|
||||
|
||||
return nil
|
||||
}
|
40
vendor/github.com/coreos/etcd/client/discover.go
generated
vendored
Normal file
40
vendor/github.com/coreos/etcd/client/discover.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/pkg/srv"
|
||||
)
|
||||
|
||||
// Discoverer is an interface that wraps the Discover method.
|
||||
type Discoverer interface {
|
||||
// Discover looks up the etcd servers for the domain.
|
||||
Discover(domain string) ([]string, error)
|
||||
}
|
||||
|
||||
type srvDiscover struct{}
|
||||
|
||||
// NewSRVDiscover constructs a new Discoverer that uses the stdlib to lookup SRV records.
|
||||
func NewSRVDiscover() Discoverer {
|
||||
return &srvDiscover{}
|
||||
}
|
||||
|
||||
func (d *srvDiscover) Discover(domain string) ([]string, error) {
|
||||
srvs, err := srv.GetClient("etcd-client", domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return srvs.Endpoints, nil
|
||||
}
|
73
vendor/github.com/coreos/etcd/client/doc.go
generated
vendored
Normal file
73
vendor/github.com/coreos/etcd/client/doc.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package client provides bindings for the etcd APIs.
|
||||
|
||||
Create a Config and exchange it for a Client:
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"context"
|
||||
|
||||
"github.com/coreos/etcd/client"
|
||||
)
|
||||
|
||||
cfg := client.Config{
|
||||
Endpoints: []string{"http://127.0.0.1:2379"},
|
||||
Transport: DefaultTransport,
|
||||
}
|
||||
|
||||
c, err := client.New(cfg)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
Clients are safe for concurrent use by multiple goroutines.
|
||||
|
||||
Create a KeysAPI using the Client, then use it to interact with etcd:
|
||||
|
||||
kAPI := client.NewKeysAPI(c)
|
||||
|
||||
// create a new key /foo with the value "bar"
|
||||
_, err = kAPI.Create(context.Background(), "/foo", "bar")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// delete the newly created key only if the value is still "bar"
|
||||
_, err = kAPI.Delete(context.Background(), "/foo", &DeleteOptions{PrevValue: "bar"})
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
Use a custom context to set timeouts on your operations:
|
||||
|
||||
import "time"
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// set a new key, ignoring its previous state
|
||||
_, err := kAPI.Set(ctx, "/ping", "pong", nil)
|
||||
if err != nil {
|
||||
if err == context.DeadlineExceeded {
|
||||
// request took longer than 5s
|
||||
} else {
|
||||
// handle error
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
package client
|
17
vendor/github.com/coreos/etcd/client/integration/doc.go
generated
vendored
Normal file
17
vendor/github.com/coreos/etcd/client/integration/doc.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package integration implements tests built upon embedded etcd, focusing on
|
||||
// the correctness of the etcd v2 client.
|
||||
package integration
|
5218
vendor/github.com/coreos/etcd/client/keys.generated.go
generated
vendored
Normal file
5218
vendor/github.com/coreos/etcd/client/keys.generated.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
681
vendor/github.com/coreos/etcd/client/keys.go
generated
vendored
Normal file
681
vendor/github.com/coreos/etcd/client/keys.go
generated
vendored
Normal file
@ -0,0 +1,681 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
//go:generate codecgen -d 1819 -r "Node|Response|Nodes" -o keys.generated.go keys.go
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/pkg/pathutil"
|
||||
"github.com/ugorji/go/codec"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrorCodeKeyNotFound = 100
|
||||
ErrorCodeTestFailed = 101
|
||||
ErrorCodeNotFile = 102
|
||||
ErrorCodeNotDir = 104
|
||||
ErrorCodeNodeExist = 105
|
||||
ErrorCodeRootROnly = 107
|
||||
ErrorCodeDirNotEmpty = 108
|
||||
ErrorCodeUnauthorized = 110
|
||||
|
||||
ErrorCodePrevValueRequired = 201
|
||||
ErrorCodeTTLNaN = 202
|
||||
ErrorCodeIndexNaN = 203
|
||||
ErrorCodeInvalidField = 209
|
||||
ErrorCodeInvalidForm = 210
|
||||
|
||||
ErrorCodeRaftInternal = 300
|
||||
ErrorCodeLeaderElect = 301
|
||||
|
||||
ErrorCodeWatcherCleared = 400
|
||||
ErrorCodeEventIndexCleared = 401
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Code int `json:"errorCode"`
|
||||
Message string `json:"message"`
|
||||
Cause string `json:"cause"`
|
||||
Index uint64 `json:"index"`
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("%v: %v (%v) [%v]", e.Code, e.Message, e.Cause, e.Index)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidJSON = errors.New("client: response is invalid json. The endpoint is probably not valid etcd cluster endpoint.")
|
||||
ErrEmptyBody = errors.New("client: response body is empty")
|
||||
)
|
||||
|
||||
// PrevExistType is used to define an existence condition when setting
|
||||
// or deleting Nodes.
|
||||
type PrevExistType string
|
||||
|
||||
const (
|
||||
PrevIgnore = PrevExistType("")
|
||||
PrevExist = PrevExistType("true")
|
||||
PrevNoExist = PrevExistType("false")
|
||||
)
|
||||
|
||||
var (
|
||||
defaultV2KeysPrefix = "/v2/keys"
|
||||
)
|
||||
|
||||
// NewKeysAPI builds a KeysAPI that interacts with etcd's key-value
|
||||
// API over HTTP.
|
||||
func NewKeysAPI(c Client) KeysAPI {
|
||||
return NewKeysAPIWithPrefix(c, defaultV2KeysPrefix)
|
||||
}
|
||||
|
||||
// NewKeysAPIWithPrefix acts like NewKeysAPI, but allows the caller
|
||||
// to provide a custom base URL path. This should only be used in
|
||||
// very rare cases.
|
||||
func NewKeysAPIWithPrefix(c Client, p string) KeysAPI {
|
||||
return &httpKeysAPI{
|
||||
client: c,
|
||||
prefix: p,
|
||||
}
|
||||
}
|
||||
|
||||
type KeysAPI interface {
|
||||
// Get retrieves a set of Nodes from etcd
|
||||
Get(ctx context.Context, key string, opts *GetOptions) (*Response, error)
|
||||
|
||||
// Set assigns a new value to a Node identified by a given key. The caller
|
||||
// may define a set of conditions in the SetOptions. If SetOptions.Dir=true
|
||||
// then value is ignored.
|
||||
Set(ctx context.Context, key, value string, opts *SetOptions) (*Response, error)
|
||||
|
||||
// Delete removes a Node identified by the given key, optionally destroying
|
||||
// all of its children as well. The caller may define a set of required
|
||||
// conditions in an DeleteOptions object.
|
||||
Delete(ctx context.Context, key string, opts *DeleteOptions) (*Response, error)
|
||||
|
||||
// Create is an alias for Set w/ PrevExist=false
|
||||
Create(ctx context.Context, key, value string) (*Response, error)
|
||||
|
||||
// CreateInOrder is used to atomically create in-order keys within the given directory.
|
||||
CreateInOrder(ctx context.Context, dir, value string, opts *CreateInOrderOptions) (*Response, error)
|
||||
|
||||
// Update is an alias for Set w/ PrevExist=true
|
||||
Update(ctx context.Context, key, value string) (*Response, error)
|
||||
|
||||
// Watcher builds a new Watcher targeted at a specific Node identified
|
||||
// by the given key. The Watcher may be configured at creation time
|
||||
// through a WatcherOptions object. The returned Watcher is designed
|
||||
// to emit events that happen to a Node, and optionally to its children.
|
||||
Watcher(key string, opts *WatcherOptions) Watcher
|
||||
}
|
||||
|
||||
type WatcherOptions struct {
|
||||
// AfterIndex defines the index after-which the Watcher should
|
||||
// start emitting events. For example, if a value of 5 is
|
||||
// provided, the first event will have an index >= 6.
|
||||
//
|
||||
// Setting AfterIndex to 0 (default) means that the Watcher
|
||||
// should start watching for events starting at the current
|
||||
// index, whatever that may be.
|
||||
AfterIndex uint64
|
||||
|
||||
// Recursive specifies whether or not the Watcher should emit
|
||||
// events that occur in children of the given keyspace. If set
|
||||
// to false (default), events will be limited to those that
|
||||
// occur for the exact key.
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
type CreateInOrderOptions struct {
|
||||
// TTL defines a period of time after-which the Node should
|
||||
// expire and no longer exist. Values <= 0 are ignored. Given
|
||||
// that the zero-value is ignored, TTL cannot be used to set
|
||||
// a TTL of 0.
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
type SetOptions struct {
|
||||
// PrevValue specifies what the current value of the Node must
|
||||
// be in order for the Set operation to succeed.
|
||||
//
|
||||
// Leaving this field empty means that the caller wishes to
|
||||
// ignore the current value of the Node. This cannot be used
|
||||
// to compare the Node's current value to an empty string.
|
||||
//
|
||||
// PrevValue is ignored if Dir=true
|
||||
PrevValue string
|
||||
|
||||
// PrevIndex indicates what the current ModifiedIndex of the
|
||||
// Node must be in order for the Set operation to succeed.
|
||||
//
|
||||
// If PrevIndex is set to 0 (default), no comparison is made.
|
||||
PrevIndex uint64
|
||||
|
||||
// PrevExist specifies whether the Node must currently exist
|
||||
// (PrevExist) or not (PrevNoExist). If the caller does not
|
||||
// care about existence, set PrevExist to PrevIgnore, or simply
|
||||
// leave it unset.
|
||||
PrevExist PrevExistType
|
||||
|
||||
// TTL defines a period of time after-which the Node should
|
||||
// expire and no longer exist. Values <= 0 are ignored. Given
|
||||
// that the zero-value is ignored, TTL cannot be used to set
|
||||
// a TTL of 0.
|
||||
TTL time.Duration
|
||||
|
||||
// Refresh set to true means a TTL value can be updated
|
||||
// without firing a watch or changing the node value. A
|
||||
// value must not be provided when refreshing a key.
|
||||
Refresh bool
|
||||
|
||||
// Dir specifies whether or not this Node should be created as a directory.
|
||||
Dir bool
|
||||
|
||||
// NoValueOnSuccess specifies whether the response contains the current value of the Node.
|
||||
// If set, the response will only contain the current value when the request fails.
|
||||
NoValueOnSuccess bool
|
||||
}
|
||||
|
||||
type GetOptions struct {
|
||||
// Recursive defines whether or not all children of the Node
|
||||
// should be returned.
|
||||
Recursive bool
|
||||
|
||||
// Sort instructs the server whether or not to sort the Nodes.
|
||||
// If true, the Nodes are sorted alphabetically by key in
|
||||
// ascending order (A to z). If false (default), the Nodes will
|
||||
// not be sorted and the ordering used should not be considered
|
||||
// predictable.
|
||||
Sort bool
|
||||
|
||||
// Quorum specifies whether it gets the latest committed value that
|
||||
// has been applied in quorum of members, which ensures external
|
||||
// consistency (or linearizability).
|
||||
Quorum bool
|
||||
}
|
||||
|
||||
type DeleteOptions struct {
|
||||
// PrevValue specifies what the current value of the Node must
|
||||
// be in order for the Delete operation to succeed.
|
||||
//
|
||||
// Leaving this field empty means that the caller wishes to
|
||||
// ignore the current value of the Node. This cannot be used
|
||||
// to compare the Node's current value to an empty string.
|
||||
PrevValue string
|
||||
|
||||
// PrevIndex indicates what the current ModifiedIndex of the
|
||||
// Node must be in order for the Delete operation to succeed.
|
||||
//
|
||||
// If PrevIndex is set to 0 (default), no comparison is made.
|
||||
PrevIndex uint64
|
||||
|
||||
// Recursive defines whether or not all children of the Node
|
||||
// should be deleted. If set to true, all children of the Node
|
||||
// identified by the given key will be deleted. If left unset
|
||||
// or explicitly set to false, only a single Node will be
|
||||
// deleted.
|
||||
Recursive bool
|
||||
|
||||
// Dir specifies whether or not this Node should be removed as a directory.
|
||||
Dir bool
|
||||
}
|
||||
|
||||
type Watcher interface {
|
||||
// Next blocks until an etcd event occurs, then returns a Response
|
||||
// representing that event. The behavior of Next depends on the
|
||||
// WatcherOptions used to construct the Watcher. Next is designed to
|
||||
// be called repeatedly, each time blocking until a subsequent event
|
||||
// is available.
|
||||
//
|
||||
// If the provided context is cancelled, Next will return a non-nil
|
||||
// error. Any other failures encountered while waiting for the next
|
||||
// event (connection issues, deserialization failures, etc) will
|
||||
// also result in a non-nil error.
|
||||
Next(context.Context) (*Response, error)
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// Action is the name of the operation that occurred. Possible values
|
||||
// include get, set, delete, update, create, compareAndSwap,
|
||||
// compareAndDelete and expire.
|
||||
Action string `json:"action"`
|
||||
|
||||
// Node represents the state of the relevant etcd Node.
|
||||
Node *Node `json:"node"`
|
||||
|
||||
// PrevNode represents the previous state of the Node. PrevNode is non-nil
|
||||
// only if the Node existed before the action occurred and the action
|
||||
// caused a change to the Node.
|
||||
PrevNode *Node `json:"prevNode"`
|
||||
|
||||
// Index holds the cluster-level index at the time the Response was generated.
|
||||
// This index is not tied to the Node(s) contained in this Response.
|
||||
Index uint64 `json:"-"`
|
||||
|
||||
// ClusterID holds the cluster-level ID reported by the server. This
|
||||
// should be different for different etcd clusters.
|
||||
ClusterID string `json:"-"`
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
// Key represents the unique location of this Node (e.g. "/foo/bar").
|
||||
Key string `json:"key"`
|
||||
|
||||
// Dir reports whether node describes a directory.
|
||||
Dir bool `json:"dir,omitempty"`
|
||||
|
||||
// Value is the current data stored on this Node. If this Node
|
||||
// is a directory, Value will be empty.
|
||||
Value string `json:"value"`
|
||||
|
||||
// Nodes holds the children of this Node, only if this Node is a directory.
|
||||
// This slice of will be arbitrarily deep (children, grandchildren, great-
|
||||
// grandchildren, etc.) if a recursive Get or Watch request were made.
|
||||
Nodes Nodes `json:"nodes"`
|
||||
|
||||
// CreatedIndex is the etcd index at-which this Node was created.
|
||||
CreatedIndex uint64 `json:"createdIndex"`
|
||||
|
||||
// ModifiedIndex is the etcd index at-which this Node was last modified.
|
||||
ModifiedIndex uint64 `json:"modifiedIndex"`
|
||||
|
||||
// Expiration is the server side expiration time of the key.
|
||||
Expiration *time.Time `json:"expiration,omitempty"`
|
||||
|
||||
// TTL is the time to live of the key in second.
|
||||
TTL int64 `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
return fmt.Sprintf("{Key: %s, CreatedIndex: %d, ModifiedIndex: %d, TTL: %d}", n.Key, n.CreatedIndex, n.ModifiedIndex, n.TTL)
|
||||
}
|
||||
|
||||
// TTLDuration returns the Node's TTL as a time.Duration object
|
||||
func (n *Node) TTLDuration() time.Duration {
|
||||
return time.Duration(n.TTL) * time.Second
|
||||
}
|
||||
|
||||
type Nodes []*Node
|
||||
|
||||
// interfaces for sorting
|
||||
|
||||
func (ns Nodes) Len() int { return len(ns) }
|
||||
func (ns Nodes) Less(i, j int) bool { return ns[i].Key < ns[j].Key }
|
||||
func (ns Nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
|
||||
|
||||
type httpKeysAPI struct {
|
||||
client httpClient
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Set(ctx context.Context, key, val string, opts *SetOptions) (*Response, error) {
|
||||
act := &setAction{
|
||||
Prefix: k.prefix,
|
||||
Key: key,
|
||||
Value: val,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.PrevValue = opts.PrevValue
|
||||
act.PrevIndex = opts.PrevIndex
|
||||
act.PrevExist = opts.PrevExist
|
||||
act.TTL = opts.TTL
|
||||
act.Refresh = opts.Refresh
|
||||
act.Dir = opts.Dir
|
||||
act.NoValueOnSuccess = opts.NoValueOnSuccess
|
||||
}
|
||||
|
||||
doCtx := ctx
|
||||
if act.PrevExist == PrevNoExist {
|
||||
doCtx = context.WithValue(doCtx, &oneShotCtxValue, &oneShotCtxValue)
|
||||
}
|
||||
resp, body, err := k.client.Do(doCtx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Create(ctx context.Context, key, val string) (*Response, error) {
|
||||
return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevNoExist})
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) CreateInOrder(ctx context.Context, dir, val string, opts *CreateInOrderOptions) (*Response, error) {
|
||||
act := &createInOrderAction{
|
||||
Prefix: k.prefix,
|
||||
Dir: dir,
|
||||
Value: val,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.TTL = opts.TTL
|
||||
}
|
||||
|
||||
resp, body, err := k.client.Do(ctx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Update(ctx context.Context, key, val string) (*Response, error) {
|
||||
return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevExist})
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Delete(ctx context.Context, key string, opts *DeleteOptions) (*Response, error) {
|
||||
act := &deleteAction{
|
||||
Prefix: k.prefix,
|
||||
Key: key,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.PrevValue = opts.PrevValue
|
||||
act.PrevIndex = opts.PrevIndex
|
||||
act.Dir = opts.Dir
|
||||
act.Recursive = opts.Recursive
|
||||
}
|
||||
|
||||
doCtx := context.WithValue(ctx, &oneShotCtxValue, &oneShotCtxValue)
|
||||
resp, body, err := k.client.Do(doCtx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Get(ctx context.Context, key string, opts *GetOptions) (*Response, error) {
|
||||
act := &getAction{
|
||||
Prefix: k.prefix,
|
||||
Key: key,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.Recursive = opts.Recursive
|
||||
act.Sorted = opts.Sort
|
||||
act.Quorum = opts.Quorum
|
||||
}
|
||||
|
||||
resp, body, err := k.client.Do(ctx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Watcher(key string, opts *WatcherOptions) Watcher {
|
||||
act := waitAction{
|
||||
Prefix: k.prefix,
|
||||
Key: key,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.Recursive = opts.Recursive
|
||||
if opts.AfterIndex > 0 {
|
||||
act.WaitIndex = opts.AfterIndex + 1
|
||||
}
|
||||
}
|
||||
|
||||
return &httpWatcher{
|
||||
client: k.client,
|
||||
nextWait: act,
|
||||
}
|
||||
}
|
||||
|
||||
type httpWatcher struct {
|
||||
client httpClient
|
||||
nextWait waitAction
|
||||
}
|
||||
|
||||
func (hw *httpWatcher) Next(ctx context.Context) (*Response, error) {
|
||||
for {
|
||||
httpresp, body, err := hw.client.Do(ctx, &hw.nextWait)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := unmarshalHTTPResponse(httpresp.StatusCode, httpresp.Header, body)
|
||||
if err != nil {
|
||||
if err == ErrEmptyBody {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hw.nextWait.WaitIndex = resp.Node.ModifiedIndex + 1
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
// v2KeysURL forms a URL representing the location of a key.
|
||||
// The endpoint argument represents the base URL of an etcd
|
||||
// server. The prefix is the path needed to route from the
|
||||
// provided endpoint's path to the root of the keys API
|
||||
// (typically "/v2/keys").
|
||||
func v2KeysURL(ep url.URL, prefix, key string) *url.URL {
|
||||
// We concatenate all parts together manually. We cannot use
|
||||
// path.Join because it does not reserve trailing slash.
|
||||
// We call CanonicalURLPath to further cleanup the path.
|
||||
if prefix != "" && prefix[0] != '/' {
|
||||
prefix = "/" + prefix
|
||||
}
|
||||
if key != "" && key[0] != '/' {
|
||||
key = "/" + key
|
||||
}
|
||||
ep.Path = pathutil.CanonicalURLPath(ep.Path + prefix + key)
|
||||
return &ep
|
||||
}
|
||||
|
||||
type getAction struct {
|
||||
Prefix string
|
||||
Key string
|
||||
Recursive bool
|
||||
Sorted bool
|
||||
Quorum bool
|
||||
}
|
||||
|
||||
func (g *getAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, g.Prefix, g.Key)
|
||||
|
||||
params := u.Query()
|
||||
params.Set("recursive", strconv.FormatBool(g.Recursive))
|
||||
params.Set("sorted", strconv.FormatBool(g.Sorted))
|
||||
params.Set("quorum", strconv.FormatBool(g.Quorum))
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type waitAction struct {
|
||||
Prefix string
|
||||
Key string
|
||||
WaitIndex uint64
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
func (w *waitAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, w.Prefix, w.Key)
|
||||
|
||||
params := u.Query()
|
||||
params.Set("wait", "true")
|
||||
params.Set("waitIndex", strconv.FormatUint(w.WaitIndex, 10))
|
||||
params.Set("recursive", strconv.FormatBool(w.Recursive))
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type setAction struct {
|
||||
Prefix string
|
||||
Key string
|
||||
Value string
|
||||
PrevValue string
|
||||
PrevIndex uint64
|
||||
PrevExist PrevExistType
|
||||
TTL time.Duration
|
||||
Refresh bool
|
||||
Dir bool
|
||||
NoValueOnSuccess bool
|
||||
}
|
||||
|
||||
func (a *setAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, a.Prefix, a.Key)
|
||||
|
||||
params := u.Query()
|
||||
form := url.Values{}
|
||||
|
||||
// we're either creating a directory or setting a key
|
||||
if a.Dir {
|
||||
params.Set("dir", strconv.FormatBool(a.Dir))
|
||||
} else {
|
||||
// These options are only valid for setting a key
|
||||
if a.PrevValue != "" {
|
||||
params.Set("prevValue", a.PrevValue)
|
||||
}
|
||||
form.Add("value", a.Value)
|
||||
}
|
||||
|
||||
// Options which apply to both setting a key and creating a dir
|
||||
if a.PrevIndex != 0 {
|
||||
params.Set("prevIndex", strconv.FormatUint(a.PrevIndex, 10))
|
||||
}
|
||||
if a.PrevExist != PrevIgnore {
|
||||
params.Set("prevExist", string(a.PrevExist))
|
||||
}
|
||||
if a.TTL > 0 {
|
||||
form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10))
|
||||
}
|
||||
|
||||
if a.Refresh {
|
||||
form.Add("refresh", "true")
|
||||
}
|
||||
if a.NoValueOnSuccess {
|
||||
params.Set("noValueOnSuccess", strconv.FormatBool(a.NoValueOnSuccess))
|
||||
}
|
||||
|
||||
u.RawQuery = params.Encode()
|
||||
body := strings.NewReader(form.Encode())
|
||||
|
||||
req, _ := http.NewRequest("PUT", u.String(), body)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
type deleteAction struct {
|
||||
Prefix string
|
||||
Key string
|
||||
PrevValue string
|
||||
PrevIndex uint64
|
||||
Dir bool
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
func (a *deleteAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, a.Prefix, a.Key)
|
||||
|
||||
params := u.Query()
|
||||
if a.PrevValue != "" {
|
||||
params.Set("prevValue", a.PrevValue)
|
||||
}
|
||||
if a.PrevIndex != 0 {
|
||||
params.Set("prevIndex", strconv.FormatUint(a.PrevIndex, 10))
|
||||
}
|
||||
if a.Dir {
|
||||
params.Set("dir", "true")
|
||||
}
|
||||
if a.Recursive {
|
||||
params.Set("recursive", "true")
|
||||
}
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
req, _ := http.NewRequest("DELETE", u.String(), nil)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
type createInOrderAction struct {
|
||||
Prefix string
|
||||
Dir string
|
||||
Value string
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
func (a *createInOrderAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, a.Prefix, a.Dir)
|
||||
|
||||
form := url.Values{}
|
||||
form.Add("value", a.Value)
|
||||
if a.TTL > 0 {
|
||||
form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10))
|
||||
}
|
||||
body := strings.NewReader(form.Encode())
|
||||
|
||||
req, _ := http.NewRequest("POST", u.String(), body)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
return req
|
||||
}
|
||||
|
||||
func unmarshalHTTPResponse(code int, header http.Header, body []byte) (res *Response, err error) {
|
||||
switch code {
|
||||
case http.StatusOK, http.StatusCreated:
|
||||
if len(body) == 0 {
|
||||
return nil, ErrEmptyBody
|
||||
}
|
||||
res, err = unmarshalSuccessfulKeysResponse(header, body)
|
||||
default:
|
||||
err = unmarshalFailedKeysResponse(body)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func unmarshalSuccessfulKeysResponse(header http.Header, body []byte) (*Response, error) {
|
||||
var res Response
|
||||
err := codec.NewDecoderBytes(body, new(codec.JsonHandle)).Decode(&res)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidJSON
|
||||
}
|
||||
if header.Get("X-Etcd-Index") != "" {
|
||||
res.Index, err = strconv.ParseUint(header.Get("X-Etcd-Index"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
res.ClusterID = header.Get("X-Etcd-Cluster-ID")
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func unmarshalFailedKeysResponse(body []byte) error {
|
||||
var etcdErr Error
|
||||
if err := json.Unmarshal(body, &etcdErr); err != nil {
|
||||
return ErrInvalidJSON
|
||||
}
|
||||
return etcdErr
|
||||
}
|
303
vendor/github.com/coreos/etcd/client/members.go
generated
vendored
Normal file
303
vendor/github.com/coreos/etcd/client/members.go
generated
vendored
Normal file
@ -0,0 +1,303 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultV2MembersPrefix = "/v2/members"
|
||||
defaultLeaderSuffix = "/leader"
|
||||
)
|
||||
|
||||
type Member struct {
|
||||
// ID is the unique identifier of this Member.
|
||||
ID string `json:"id"`
|
||||
|
||||
// Name is a human-readable, non-unique identifier of this Member.
|
||||
Name string `json:"name"`
|
||||
|
||||
// PeerURLs represents the HTTP(S) endpoints this Member uses to
|
||||
// participate in etcd's consensus protocol.
|
||||
PeerURLs []string `json:"peerURLs"`
|
||||
|
||||
// ClientURLs represents the HTTP(S) endpoints on which this Member
|
||||
// serves its client-facing APIs.
|
||||
ClientURLs []string `json:"clientURLs"`
|
||||
}
|
||||
|
||||
type memberCollection []Member
|
||||
|
||||
func (c *memberCollection) UnmarshalJSON(data []byte) error {
|
||||
d := struct {
|
||||
Members []Member
|
||||
}{}
|
||||
|
||||
if err := json.Unmarshal(data, &d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.Members == nil {
|
||||
*c = make([]Member, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
*c = d.Members
|
||||
return nil
|
||||
}
|
||||
|
||||
type memberCreateOrUpdateRequest struct {
|
||||
PeerURLs types.URLs
|
||||
}
|
||||
|
||||
func (m *memberCreateOrUpdateRequest) MarshalJSON() ([]byte, error) {
|
||||
s := struct {
|
||||
PeerURLs []string `json:"peerURLs"`
|
||||
}{
|
||||
PeerURLs: make([]string, len(m.PeerURLs)),
|
||||
}
|
||||
|
||||
for i, u := range m.PeerURLs {
|
||||
s.PeerURLs[i] = u.String()
|
||||
}
|
||||
|
||||
return json.Marshal(&s)
|
||||
}
|
||||
|
||||
// NewMembersAPI constructs a new MembersAPI that uses HTTP to
|
||||
// interact with etcd's membership API.
|
||||
func NewMembersAPI(c Client) MembersAPI {
|
||||
return &httpMembersAPI{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
type MembersAPI interface {
|
||||
// List enumerates the current cluster membership.
|
||||
List(ctx context.Context) ([]Member, error)
|
||||
|
||||
// Add instructs etcd to accept a new Member into the cluster.
|
||||
Add(ctx context.Context, peerURL string) (*Member, error)
|
||||
|
||||
// Remove demotes an existing Member out of the cluster.
|
||||
Remove(ctx context.Context, mID string) error
|
||||
|
||||
// Update instructs etcd to update an existing Member in the cluster.
|
||||
Update(ctx context.Context, mID string, peerURLs []string) error
|
||||
|
||||
// Leader gets current leader of the cluster
|
||||
Leader(ctx context.Context) (*Member, error)
|
||||
}
|
||||
|
||||
type httpMembersAPI struct {
|
||||
client httpClient
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) List(ctx context.Context) ([]Member, error) {
|
||||
req := &membersAPIActionList{}
|
||||
resp, body, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mCollection memberCollection
|
||||
if err := json.Unmarshal(body, &mCollection); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []Member(mCollection), nil
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) Add(ctx context.Context, peerURL string) (*Member, error) {
|
||||
urls, err := types.NewURLs([]string{peerURL})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &membersAPIActionAdd{peerURLs: urls}
|
||||
resp, body, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusCreated, http.StatusConflict); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
var merr membersError
|
||||
if err := json.Unmarshal(body, &merr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, merr
|
||||
}
|
||||
|
||||
var memb Member
|
||||
if err := json.Unmarshal(body, &memb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &memb, nil
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) Update(ctx context.Context, memberID string, peerURLs []string) error {
|
||||
urls, err := types.NewURLs(peerURLs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &membersAPIActionUpdate{peerURLs: urls, memberID: memberID}
|
||||
resp, body, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusNotFound, http.StatusConflict); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
var merr membersError
|
||||
if err := json.Unmarshal(body, &merr); err != nil {
|
||||
return err
|
||||
}
|
||||
return merr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) Remove(ctx context.Context, memberID string) error {
|
||||
req := &membersAPIActionRemove{memberID: memberID}
|
||||
resp, _, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusGone)
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) Leader(ctx context.Context) (*Member, error) {
|
||||
req := &membersAPIActionLeader{}
|
||||
resp, body, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var leader Member
|
||||
if err := json.Unmarshal(body, &leader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &leader, nil
|
||||
}
|
||||
|
||||
type membersAPIActionList struct{}
|
||||
|
||||
func (l *membersAPIActionList) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type membersAPIActionRemove struct {
|
||||
memberID string
|
||||
}
|
||||
|
||||
func (d *membersAPIActionRemove) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
u.Path = path.Join(u.Path, d.memberID)
|
||||
req, _ := http.NewRequest("DELETE", u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type membersAPIActionAdd struct {
|
||||
peerURLs types.URLs
|
||||
}
|
||||
|
||||
func (a *membersAPIActionAdd) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
m := memberCreateOrUpdateRequest{PeerURLs: a.peerURLs}
|
||||
b, _ := json.Marshal(&m)
|
||||
req, _ := http.NewRequest("POST", u.String(), bytes.NewReader(b))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
type membersAPIActionUpdate struct {
|
||||
memberID string
|
||||
peerURLs types.URLs
|
||||
}
|
||||
|
||||
func (a *membersAPIActionUpdate) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
m := memberCreateOrUpdateRequest{PeerURLs: a.peerURLs}
|
||||
u.Path = path.Join(u.Path, a.memberID)
|
||||
b, _ := json.Marshal(&m)
|
||||
req, _ := http.NewRequest("PUT", u.String(), bytes.NewReader(b))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func assertStatusCode(got int, want ...int) (err error) {
|
||||
for _, w := range want {
|
||||
if w == got {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unexpected status code %d", got)
|
||||
}
|
||||
|
||||
type membersAPIActionLeader struct{}
|
||||
|
||||
func (l *membersAPIActionLeader) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
u.Path = path.Join(u.Path, defaultLeaderSuffix)
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
// v2MembersURL add the necessary path to the provided endpoint
|
||||
// to route requests to the default v2 members API.
|
||||
func v2MembersURL(ep url.URL) *url.URL {
|
||||
ep.Path = path.Join(ep.Path, defaultV2MembersPrefix)
|
||||
return &ep
|
||||
}
|
||||
|
||||
type membersError struct {
|
||||
Message string `json:"message"`
|
||||
Code int `json:"-"`
|
||||
}
|
||||
|
||||
func (e membersError) Error() string {
|
||||
return e.Message
|
||||
}
|
53
vendor/github.com/coreos/etcd/client/util.go
generated
vendored
Normal file
53
vendor/github.com/coreos/etcd/client/util.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
roleNotFoundRegExp *regexp.Regexp
|
||||
userNotFoundRegExp *regexp.Regexp
|
||||
)
|
||||
|
||||
func init() {
|
||||
roleNotFoundRegExp = regexp.MustCompile("auth: Role .* does not exist.")
|
||||
userNotFoundRegExp = regexp.MustCompile("auth: User .* does not exist.")
|
||||
}
|
||||
|
||||
// IsKeyNotFound returns true if the error code is ErrorCodeKeyNotFound.
|
||||
func IsKeyNotFound(err error) bool {
|
||||
if cErr, ok := err.(Error); ok {
|
||||
return cErr.Code == ErrorCodeKeyNotFound
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsRoleNotFound returns true if the error means role not found of v2 API.
|
||||
func IsRoleNotFound(err error) bool {
|
||||
if ae, ok := err.(authError); ok {
|
||||
return roleNotFoundRegExp.MatchString(ae.Message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUserNotFound returns true if the error means user not found of v2 API.
|
||||
func IsUserNotFound(err error) bool {
|
||||
if ae, ok := err.(authError); ok {
|
||||
return userNotFoundRegExp.MatchString(ae.Message)
|
||||
}
|
||||
return false
|
||||
}
|
202
vendor/github.com/coreos/etcd/pkg/pathutil/LICENSE
generated
vendored
Normal file
202
vendor/github.com/coreos/etcd/pkg/pathutil/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
31
vendor/github.com/coreos/etcd/pkg/pathutil/path.go
generated
vendored
Normal file
31
vendor/github.com/coreos/etcd/pkg/pathutil/path.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package pathutil implements utility functions for handling slash-separated
|
||||
// paths.
|
||||
package pathutil
|
||||
|
||||
import "path"
|
||||
|
||||
// CanonicalURLPath returns the canonical url path for p, which follows the rules:
|
||||
// 1. the path always starts with "/"
|
||||
// 2. replace multiple slashes with a single slash
|
||||
// 3. replace each '.' '..' path name element with equivalent one
|
||||
// 4. keep the trailing slash
|
||||
// The function is borrowed from stdlib http.cleanPath in server.go.
|
||||
func CanonicalURLPath(p string) string {
|
||||
if p == "" {
|
||||
return "/"
|
||||
}
|
||||
if p[0] != '/' {
|
||||
p = "/" + p
|
||||
}
|
||||
np := path.Clean(p)
|
||||
// path.Clean removes trailing slash except for root,
|
||||
// put the trailing slash back if necessary.
|
||||
if p[len(p)-1] == '/' && np != "/" {
|
||||
np += "/"
|
||||
}
|
||||
return np
|
||||
}
|
202
vendor/github.com/coreos/etcd/pkg/srv/LICENSE
generated
vendored
Normal file
202
vendor/github.com/coreos/etcd/pkg/srv/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
130
vendor/github.com/coreos/etcd/pkg/srv/srv.go
generated
vendored
Normal file
130
vendor/github.com/coreos/etcd/pkg/srv/srv.go
generated
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package srv looks up DNS SRV records.
|
||||
package srv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// indirection for testing
|
||||
lookupSRV = net.LookupSRV // net.DefaultResolver.LookupSRV when ctxs don't conflict
|
||||
resolveTCPAddr = net.ResolveTCPAddr
|
||||
)
|
||||
|
||||
// GetCluster gets the cluster information via DNS discovery.
|
||||
// Also sees each entry as a separate instance.
|
||||
func GetCluster(serviceScheme, service, name, dns string, apurls types.URLs) ([]string, error) {
|
||||
tempName := int(0)
|
||||
tcp2ap := make(map[string]url.URL)
|
||||
|
||||
// First, resolve the apurls
|
||||
for _, url := range apurls {
|
||||
tcpAddr, err := resolveTCPAddr("tcp", url.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tcp2ap[tcpAddr.String()] = url
|
||||
}
|
||||
|
||||
stringParts := []string{}
|
||||
updateNodeMap := func(service, scheme string) error {
|
||||
_, addrs, err := lookupSRV(service, "tcp", dns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, srv := range addrs {
|
||||
port := fmt.Sprintf("%d", srv.Port)
|
||||
host := net.JoinHostPort(srv.Target, port)
|
||||
tcpAddr, terr := resolveTCPAddr("tcp", host)
|
||||
if terr != nil {
|
||||
err = terr
|
||||
continue
|
||||
}
|
||||
n := ""
|
||||
url, ok := tcp2ap[tcpAddr.String()]
|
||||
if ok {
|
||||
n = name
|
||||
}
|
||||
if n == "" {
|
||||
n = fmt.Sprintf("%d", tempName)
|
||||
tempName++
|
||||
}
|
||||
// SRV records have a trailing dot but URL shouldn't.
|
||||
shortHost := strings.TrimSuffix(srv.Target, ".")
|
||||
urlHost := net.JoinHostPort(shortHost, port)
|
||||
if ok && url.Scheme != scheme {
|
||||
err = fmt.Errorf("bootstrap at %s from DNS for %s has scheme mismatch with expected peer %s", scheme+"://"+urlHost, service, url.String())
|
||||
} else {
|
||||
stringParts = append(stringParts, fmt.Sprintf("%s=%s://%s", n, scheme, urlHost))
|
||||
}
|
||||
}
|
||||
if len(stringParts) == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := updateNodeMap(service, serviceScheme)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error querying DNS SRV records for _%s %s", service, err)
|
||||
}
|
||||
return stringParts, nil
|
||||
}
|
||||
|
||||
type SRVClients struct {
|
||||
Endpoints []string
|
||||
SRVs []*net.SRV
|
||||
}
|
||||
|
||||
// GetClient looks up the client endpoints for a service and domain.
|
||||
func GetClient(service, domain string) (*SRVClients, error) {
|
||||
var urls []*url.URL
|
||||
var srvs []*net.SRV
|
||||
|
||||
updateURLs := func(service, scheme string) error {
|
||||
_, addrs, err := lookupSRV(service, "tcp", domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, srv := range addrs {
|
||||
urls = append(urls, &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: net.JoinHostPort(srv.Target, fmt.Sprintf("%d", srv.Port)),
|
||||
})
|
||||
}
|
||||
srvs = append(srvs, addrs...)
|
||||
return nil
|
||||
}
|
||||
|
||||
errHTTPS := updateURLs(service+"-ssl", "https")
|
||||
errHTTP := updateURLs(service, "http")
|
||||
|
||||
if errHTTPS != nil && errHTTP != nil {
|
||||
return nil, fmt.Errorf("dns lookup errors: %s and %s", errHTTPS, errHTTP)
|
||||
}
|
||||
|
||||
endpoints := make([]string, len(urls))
|
||||
for i := range urls {
|
||||
endpoints[i] = urls[i].String()
|
||||
}
|
||||
return &SRVClients{Endpoints: endpoints, SRVs: srvs}, nil
|
||||
}
|
202
vendor/github.com/coreos/etcd/pkg/types/LICENSE
generated
vendored
Normal file
202
vendor/github.com/coreos/etcd/pkg/types/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
17
vendor/github.com/coreos/etcd/pkg/types/doc.go
generated
vendored
Normal file
17
vendor/github.com/coreos/etcd/pkg/types/doc.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package types declares various data types and implements type-checking
|
||||
// functions.
|
||||
package types
|
41
vendor/github.com/coreos/etcd/pkg/types/id.go
generated
vendored
Normal file
41
vendor/github.com/coreos/etcd/pkg/types/id.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ID represents a generic identifier which is canonically
|
||||
// stored as a uint64 but is typically represented as a
|
||||
// base-16 string for input/output
|
||||
type ID uint64
|
||||
|
||||
func (i ID) String() string {
|
||||
return strconv.FormatUint(uint64(i), 16)
|
||||
}
|
||||
|
||||
// IDFromString attempts to create an ID from a base-16 string.
|
||||
func IDFromString(s string) (ID, error) {
|
||||
i, err := strconv.ParseUint(s, 16, 64)
|
||||
return ID(i), err
|
||||
}
|
||||
|
||||
// IDSlice implements the sort interface
|
||||
type IDSlice []ID
|
||||
|
||||
func (p IDSlice) Len() int { return len(p) }
|
||||
func (p IDSlice) Less(i, j int) bool { return uint64(p[i]) < uint64(p[j]) }
|
||||
func (p IDSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
178
vendor/github.com/coreos/etcd/pkg/types/set.go
generated
vendored
Normal file
178
vendor/github.com/coreos/etcd/pkg/types/set.go
generated
vendored
Normal file
@ -0,0 +1,178 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Set interface {
|
||||
Add(string)
|
||||
Remove(string)
|
||||
Contains(string) bool
|
||||
Equals(Set) bool
|
||||
Length() int
|
||||
Values() []string
|
||||
Copy() Set
|
||||
Sub(Set) Set
|
||||
}
|
||||
|
||||
func NewUnsafeSet(values ...string) *unsafeSet {
|
||||
set := &unsafeSet{make(map[string]struct{})}
|
||||
for _, v := range values {
|
||||
set.Add(v)
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
func NewThreadsafeSet(values ...string) *tsafeSet {
|
||||
us := NewUnsafeSet(values...)
|
||||
return &tsafeSet{us, sync.RWMutex{}}
|
||||
}
|
||||
|
||||
type unsafeSet struct {
|
||||
d map[string]struct{}
|
||||
}
|
||||
|
||||
// Add adds a new value to the set (no-op if the value is already present)
|
||||
func (us *unsafeSet) Add(value string) {
|
||||
us.d[value] = struct{}{}
|
||||
}
|
||||
|
||||
// Remove removes the given value from the set
|
||||
func (us *unsafeSet) Remove(value string) {
|
||||
delete(us.d, value)
|
||||
}
|
||||
|
||||
// Contains returns whether the set contains the given value
|
||||
func (us *unsafeSet) Contains(value string) (exists bool) {
|
||||
_, exists = us.d[value]
|
||||
return exists
|
||||
}
|
||||
|
||||
// ContainsAll returns whether the set contains all given values
|
||||
func (us *unsafeSet) ContainsAll(values []string) bool {
|
||||
for _, s := range values {
|
||||
if !us.Contains(s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Equals returns whether the contents of two sets are identical
|
||||
func (us *unsafeSet) Equals(other Set) bool {
|
||||
v1 := sort.StringSlice(us.Values())
|
||||
v2 := sort.StringSlice(other.Values())
|
||||
v1.Sort()
|
||||
v2.Sort()
|
||||
return reflect.DeepEqual(v1, v2)
|
||||
}
|
||||
|
||||
// Length returns the number of elements in the set
|
||||
func (us *unsafeSet) Length() int {
|
||||
return len(us.d)
|
||||
}
|
||||
|
||||
// Values returns the values of the Set in an unspecified order.
|
||||
func (us *unsafeSet) Values() (values []string) {
|
||||
values = make([]string, 0)
|
||||
for val := range us.d {
|
||||
values = append(values, val)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// Copy creates a new Set containing the values of the first
|
||||
func (us *unsafeSet) Copy() Set {
|
||||
cp := NewUnsafeSet()
|
||||
for val := range us.d {
|
||||
cp.Add(val)
|
||||
}
|
||||
|
||||
return cp
|
||||
}
|
||||
|
||||
// Sub removes all elements in other from the set
|
||||
func (us *unsafeSet) Sub(other Set) Set {
|
||||
oValues := other.Values()
|
||||
result := us.Copy().(*unsafeSet)
|
||||
|
||||
for _, val := range oValues {
|
||||
if _, ok := result.d[val]; !ok {
|
||||
continue
|
||||
}
|
||||
delete(result.d, val)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
type tsafeSet struct {
|
||||
us *unsafeSet
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Add(value string) {
|
||||
ts.m.Lock()
|
||||
defer ts.m.Unlock()
|
||||
ts.us.Add(value)
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Remove(value string) {
|
||||
ts.m.Lock()
|
||||
defer ts.m.Unlock()
|
||||
ts.us.Remove(value)
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Contains(value string) (exists bool) {
|
||||
ts.m.RLock()
|
||||
defer ts.m.RUnlock()
|
||||
return ts.us.Contains(value)
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Equals(other Set) bool {
|
||||
ts.m.RLock()
|
||||
defer ts.m.RUnlock()
|
||||
return ts.us.Equals(other)
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Length() int {
|
||||
ts.m.RLock()
|
||||
defer ts.m.RUnlock()
|
||||
return ts.us.Length()
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Values() (values []string) {
|
||||
ts.m.RLock()
|
||||
defer ts.m.RUnlock()
|
||||
return ts.us.Values()
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Copy() Set {
|
||||
ts.m.RLock()
|
||||
defer ts.m.RUnlock()
|
||||
usResult := ts.us.Copy().(*unsafeSet)
|
||||
return &tsafeSet{usResult, sync.RWMutex{}}
|
||||
}
|
||||
|
||||
func (ts *tsafeSet) Sub(other Set) Set {
|
||||
ts.m.RLock()
|
||||
defer ts.m.RUnlock()
|
||||
usResult := ts.us.Sub(other).(*unsafeSet)
|
||||
return &tsafeSet{usResult, sync.RWMutex{}}
|
||||
}
|
22
vendor/github.com/coreos/etcd/pkg/types/slice.go
generated
vendored
Normal file
22
vendor/github.com/coreos/etcd/pkg/types/slice.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
// Uint64Slice implements sort interface
|
||||
type Uint64Slice []uint64
|
||||
|
||||
func (p Uint64Slice) Len() int { return len(p) }
|
||||
func (p Uint64Slice) Less(i, j int) bool { return p[i] < p[j] }
|
||||
func (p Uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
82
vendor/github.com/coreos/etcd/pkg/types/urls.go
generated
vendored
Normal file
82
vendor/github.com/coreos/etcd/pkg/types/urls.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type URLs []url.URL
|
||||
|
||||
func NewURLs(strs []string) (URLs, error) {
|
||||
all := make([]url.URL, len(strs))
|
||||
if len(all) == 0 {
|
||||
return nil, errors.New("no valid URLs given")
|
||||
}
|
||||
for i, in := range strs {
|
||||
in = strings.TrimSpace(in)
|
||||
u, err := url.Parse(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "unix" && u.Scheme != "unixs" {
|
||||
return nil, fmt.Errorf("URL scheme must be http, https, unix, or unixs: %s", in)
|
||||
}
|
||||
if _, _, err := net.SplitHostPort(u.Host); err != nil {
|
||||
return nil, fmt.Errorf(`URL address does not have the form "host:port": %s`, in)
|
||||
}
|
||||
if u.Path != "" {
|
||||
return nil, fmt.Errorf("URL must not contain a path: %s", in)
|
||||
}
|
||||
all[i] = *u
|
||||
}
|
||||
us := URLs(all)
|
||||
us.Sort()
|
||||
|
||||
return us, nil
|
||||
}
|
||||
|
||||
func MustNewURLs(strs []string) URLs {
|
||||
urls, err := NewURLs(strs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return urls
|
||||
}
|
||||
|
||||
func (us URLs) String() string {
|
||||
return strings.Join(us.StringSlice(), ",")
|
||||
}
|
||||
|
||||
func (us *URLs) Sort() {
|
||||
sort.Sort(us)
|
||||
}
|
||||
func (us URLs) Len() int { return len(us) }
|
||||
func (us URLs) Less(i, j int) bool { return us[i].String() < us[j].String() }
|
||||
func (us URLs) Swap(i, j int) { us[i], us[j] = us[j], us[i] }
|
||||
|
||||
func (us URLs) StringSlice() []string {
|
||||
out := make([]string, len(us))
|
||||
for i := range us {
|
||||
out[i] = us[i].String()
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
107
vendor/github.com/coreos/etcd/pkg/types/urlsmap.go
generated
vendored
Normal file
107
vendor/github.com/coreos/etcd/pkg/types/urlsmap.go
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// URLsMap is a map from a name to its URLs.
|
||||
type URLsMap map[string]URLs
|
||||
|
||||
// NewURLsMap returns a URLsMap instantiated from the given string,
|
||||
// which consists of discovery-formatted names-to-URLs, like:
|
||||
// mach0=http://1.1.1.1:2380,mach0=http://2.2.2.2::2380,mach1=http://3.3.3.3:2380,mach2=http://4.4.4.4:2380
|
||||
func NewURLsMap(s string) (URLsMap, error) {
|
||||
m := parse(s)
|
||||
|
||||
cl := URLsMap{}
|
||||
for name, urls := range m {
|
||||
us, err := NewURLs(urls)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cl[name] = us
|
||||
}
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
// NewURLsMapFromStringMap takes a map of strings and returns a URLsMap. The
|
||||
// string values in the map can be multiple values separated by the sep string.
|
||||
func NewURLsMapFromStringMap(m map[string]string, sep string) (URLsMap, error) {
|
||||
var err error
|
||||
um := URLsMap{}
|
||||
for k, v := range m {
|
||||
um[k], err = NewURLs(strings.Split(v, sep))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return um, nil
|
||||
}
|
||||
|
||||
// String turns URLsMap into discovery-formatted name-to-URLs sorted by name.
|
||||
func (c URLsMap) String() string {
|
||||
var pairs []string
|
||||
for name, urls := range c {
|
||||
for _, url := range urls {
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%s", name, url.String()))
|
||||
}
|
||||
}
|
||||
sort.Strings(pairs)
|
||||
return strings.Join(pairs, ",")
|
||||
}
|
||||
|
||||
// URLs returns a list of all URLs.
|
||||
// The returned list is sorted in ascending lexicographical order.
|
||||
func (c URLsMap) URLs() []string {
|
||||
var urls []string
|
||||
for _, us := range c {
|
||||
for _, u := range us {
|
||||
urls = append(urls, u.String())
|
||||
}
|
||||
}
|
||||
sort.Strings(urls)
|
||||
return urls
|
||||
}
|
||||
|
||||
// Len returns the size of URLsMap.
|
||||
func (c URLsMap) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
// parse parses the given string and returns a map listing the values specified for each key.
|
||||
func parse(s string) map[string][]string {
|
||||
m := make(map[string][]string)
|
||||
for s != "" {
|
||||
key := s
|
||||
if i := strings.IndexAny(key, ","); i >= 0 {
|
||||
key, s = key[:i], key[i+1:]
|
||||
} else {
|
||||
s = ""
|
||||
}
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
value := ""
|
||||
if i := strings.Index(key, "="); i >= 0 {
|
||||
key, value = key[:i], key[i+1:]
|
||||
}
|
||||
m[key] = append(m[key], value)
|
||||
}
|
||||
return m
|
||||
}
|
202
vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/LICENSE
generated
vendored
Normal file
202
vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
268
vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/semver.go
generated
vendored
Normal file
268
vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/semver.go
generated
vendored
Normal file
@ -0,0 +1,268 @@
|
||||
// Copyright 2013-2015 CoreOS, 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.
|
||||
|
||||
// Semantic Versions http://semver.org
|
||||
package semver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Version struct {
|
||||
Major int64
|
||||
Minor int64
|
||||
Patch int64
|
||||
PreRelease PreRelease
|
||||
Metadata string
|
||||
}
|
||||
|
||||
type PreRelease string
|
||||
|
||||
func splitOff(input *string, delim string) (val string) {
|
||||
parts := strings.SplitN(*input, delim, 2)
|
||||
|
||||
if len(parts) == 2 {
|
||||
*input = parts[0]
|
||||
val = parts[1]
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func New(version string) *Version {
|
||||
return Must(NewVersion(version))
|
||||
}
|
||||
|
||||
func NewVersion(version string) (*Version, error) {
|
||||
v := Version{}
|
||||
|
||||
if err := v.Set(version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
// Must is a helper for wrapping NewVersion and will panic if err is not nil.
|
||||
func Must(v *Version, err error) *Version {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Set parses and updates v from the given version string. Implements flag.Value
|
||||
func (v *Version) Set(version string) error {
|
||||
metadata := splitOff(&version, "+")
|
||||
preRelease := PreRelease(splitOff(&version, "-"))
|
||||
dotParts := strings.SplitN(version, ".", 3)
|
||||
|
||||
if len(dotParts) != 3 {
|
||||
return fmt.Errorf("%s is not in dotted-tri format", version)
|
||||
}
|
||||
|
||||
parsed := make([]int64, 3, 3)
|
||||
|
||||
for i, v := range dotParts[:3] {
|
||||
val, err := strconv.ParseInt(v, 10, 64)
|
||||
parsed[i] = val
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
v.Metadata = metadata
|
||||
v.PreRelease = preRelease
|
||||
v.Major = parsed[0]
|
||||
v.Minor = parsed[1]
|
||||
v.Patch = parsed[2]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
fmt.Fprintf(&buffer, "%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
|
||||
if v.PreRelease != "" {
|
||||
fmt.Fprintf(&buffer, "-%s", v.PreRelease)
|
||||
}
|
||||
|
||||
if v.Metadata != "" {
|
||||
fmt.Fprintf(&buffer, "+%s", v.Metadata)
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (v *Version) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var data string
|
||||
if err := unmarshal(&data); err != nil {
|
||||
return err
|
||||
}
|
||||
return v.Set(data)
|
||||
}
|
||||
|
||||
func (v Version) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + v.String() + `"`), nil
|
||||
}
|
||||
|
||||
func (v *Version) UnmarshalJSON(data []byte) error {
|
||||
l := len(data)
|
||||
if l == 0 || string(data) == `""` {
|
||||
return nil
|
||||
}
|
||||
if l < 2 || data[0] != '"' || data[l-1] != '"' {
|
||||
return errors.New("invalid semver string")
|
||||
}
|
||||
return v.Set(string(data[1 : l-1]))
|
||||
}
|
||||
|
||||
// Compare tests if v is less than, equal to, or greater than versionB,
|
||||
// returning -1, 0, or +1 respectively.
|
||||
func (v Version) Compare(versionB Version) int {
|
||||
if cmp := recursiveCompare(v.Slice(), versionB.Slice()); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
return preReleaseCompare(v, versionB)
|
||||
}
|
||||
|
||||
// Equal tests if v is equal to versionB.
|
||||
func (v Version) Equal(versionB Version) bool {
|
||||
return v.Compare(versionB) == 0
|
||||
}
|
||||
|
||||
// LessThan tests if v is less than versionB.
|
||||
func (v Version) LessThan(versionB Version) bool {
|
||||
return v.Compare(versionB) < 0
|
||||
}
|
||||
|
||||
// Slice converts the comparable parts of the semver into a slice of integers.
|
||||
func (v Version) Slice() []int64 {
|
||||
return []int64{v.Major, v.Minor, v.Patch}
|
||||
}
|
||||
|
||||
func (p PreRelease) Slice() []string {
|
||||
preRelease := string(p)
|
||||
return strings.Split(preRelease, ".")
|
||||
}
|
||||
|
||||
func preReleaseCompare(versionA Version, versionB Version) int {
|
||||
a := versionA.PreRelease
|
||||
b := versionB.PreRelease
|
||||
|
||||
/* Handle the case where if two versions are otherwise equal it is the
|
||||
* one without a PreRelease that is greater */
|
||||
if len(a) == 0 && (len(b) > 0) {
|
||||
return 1
|
||||
} else if len(b) == 0 && (len(a) > 0) {
|
||||
return -1
|
||||
}
|
||||
|
||||
// If there is a prerelease, check and compare each part.
|
||||
return recursivePreReleaseCompare(a.Slice(), b.Slice())
|
||||
}
|
||||
|
||||
func recursiveCompare(versionA []int64, versionB []int64) int {
|
||||
if len(versionA) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
a := versionA[0]
|
||||
b := versionB[0]
|
||||
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
return -1
|
||||
}
|
||||
|
||||
return recursiveCompare(versionA[1:], versionB[1:])
|
||||
}
|
||||
|
||||
func recursivePreReleaseCompare(versionA []string, versionB []string) int {
|
||||
// A larger set of pre-release fields has a higher precedence than a smaller set,
|
||||
// if all of the preceding identifiers are equal.
|
||||
if len(versionA) == 0 {
|
||||
if len(versionB) > 0 {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
} else if len(versionB) == 0 {
|
||||
// We're longer than versionB so return 1.
|
||||
return 1
|
||||
}
|
||||
|
||||
a := versionA[0]
|
||||
b := versionB[0]
|
||||
|
||||
aInt := false
|
||||
bInt := false
|
||||
|
||||
aI, err := strconv.Atoi(versionA[0])
|
||||
if err == nil {
|
||||
aInt = true
|
||||
}
|
||||
|
||||
bI, err := strconv.Atoi(versionB[0])
|
||||
if err == nil {
|
||||
bInt = true
|
||||
}
|
||||
|
||||
// Handle Integer Comparison
|
||||
if aInt && bInt {
|
||||
if aI > bI {
|
||||
return 1
|
||||
} else if aI < bI {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// Handle String Comparison
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
return -1
|
||||
}
|
||||
|
||||
return recursivePreReleaseCompare(versionA[1:], versionB[1:])
|
||||
}
|
||||
|
||||
// BumpMajor increments the Major field by 1 and resets all other fields to their default values
|
||||
func (v *Version) BumpMajor() {
|
||||
v.Major += 1
|
||||
v.Minor = 0
|
||||
v.Patch = 0
|
||||
v.PreRelease = PreRelease("")
|
||||
v.Metadata = ""
|
||||
}
|
||||
|
||||
// BumpMinor increments the Minor field by 1 and resets all other fields to their default values
|
||||
func (v *Version) BumpMinor() {
|
||||
v.Minor += 1
|
||||
v.Patch = 0
|
||||
v.PreRelease = PreRelease("")
|
||||
v.Metadata = ""
|
||||
}
|
||||
|
||||
// BumpPatch increments the Patch field by 1 and resets all other fields to their default values
|
||||
func (v *Version) BumpPatch() {
|
||||
v.Patch += 1
|
||||
v.PreRelease = PreRelease("")
|
||||
v.Metadata = ""
|
||||
}
|
38
vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/sort.go
generated
vendored
Normal file
38
vendor/github.com/coreos/etcd/vendor/github.com/coreos/go-semver/semver/sort.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2013-2015 CoreOS, 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 semver
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
type Versions []*Version
|
||||
|
||||
func (s Versions) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s Versions) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s Versions) Less(i, j int) bool {
|
||||
return s[i].LessThan(*s[j])
|
||||
}
|
||||
|
||||
// Sort sorts the given slice of Version
|
||||
func Sort(versions []*Version) {
|
||||
sort.Sort(Versions(versions))
|
||||
}
|
185
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/0doc.go
generated
vendored
Normal file
185
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/0doc.go
generated
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
/*
|
||||
High Performance, Feature-Rich Idiomatic Go 1.4+ codec/encoding library for
|
||||
binc, msgpack, cbor, json
|
||||
|
||||
Supported Serialization formats are:
|
||||
|
||||
- msgpack: https://github.com/msgpack/msgpack
|
||||
- binc: http://github.com/ugorji/binc
|
||||
- cbor: http://cbor.io http://tools.ietf.org/html/rfc7049
|
||||
- json: http://json.org http://tools.ietf.org/html/rfc7159
|
||||
- simple:
|
||||
|
||||
To install:
|
||||
|
||||
go get github.com/ugorji/go/codec
|
||||
|
||||
This package will carefully use 'unsafe' for performance reasons in specific places.
|
||||
You can build without unsafe use by passing the safe or appengine tag
|
||||
i.e. 'go install -tags=safe ...'. Note that unsafe is only supported for the last 3
|
||||
go sdk versions e.g. current go release is go 1.9, so we support unsafe use only from
|
||||
go 1.7+ . This is because supporting unsafe requires knowledge of implementation details.
|
||||
|
||||
For detailed usage information, read the primer at http://ugorji.net/blog/go-codec-primer .
|
||||
|
||||
The idiomatic Go support is as seen in other encoding packages in
|
||||
the standard library (ie json, xml, gob, etc).
|
||||
|
||||
Rich Feature Set includes:
|
||||
|
||||
- Simple but extremely powerful and feature-rich API
|
||||
- Support for go1.4 and above, while selectively using newer APIs for later releases
|
||||
- Good code coverage ( > 70% )
|
||||
- Very High Performance.
|
||||
Our extensive benchmarks show us outperforming Gob, Json, Bson, etc by 2-4X.
|
||||
- Careful selected use of 'unsafe' for targeted performance gains.
|
||||
100% mode exists where 'unsafe' is not used at all.
|
||||
- Lock-free (sans mutex) concurrency for scaling to 100's of cores
|
||||
- Multiple conversions:
|
||||
Package coerces types where appropriate
|
||||
e.g. decode an int in the stream into a float, etc.
|
||||
- Corner Cases:
|
||||
Overflows, nil maps/slices, nil values in streams are handled correctly
|
||||
- Standard field renaming via tags
|
||||
- Support for omitting empty fields during an encoding
|
||||
- Encoding from any value and decoding into pointer to any value
|
||||
(struct, slice, map, primitives, pointers, interface{}, etc)
|
||||
- Extensions to support efficient encoding/decoding of any named types
|
||||
- Support encoding.(Binary|Text)(M|Unm)arshaler interfaces
|
||||
- Decoding without a schema (into a interface{}).
|
||||
Includes Options to configure what specific map or slice type to use
|
||||
when decoding an encoded list or map into a nil interface{}
|
||||
- Encode a struct as an array, and decode struct from an array in the data stream
|
||||
- Comprehensive support for anonymous fields
|
||||
- Fast (no-reflection) encoding/decoding of common maps and slices
|
||||
- Code-generation for faster performance.
|
||||
- Support binary (e.g. messagepack, cbor) and text (e.g. json) formats
|
||||
- Support indefinite-length formats to enable true streaming
|
||||
(for formats which support it e.g. json, cbor)
|
||||
- Support canonical encoding, where a value is ALWAYS encoded as same sequence of bytes.
|
||||
This mostly applies to maps, where iteration order is non-deterministic.
|
||||
- NIL in data stream decoded as zero value
|
||||
- Never silently skip data when decoding.
|
||||
User decides whether to return an error or silently skip data when keys or indexes
|
||||
in the data stream do not map to fields in the struct.
|
||||
- Detect and error when encoding a cyclic reference (instead of stack overflow shutdown)
|
||||
- Encode/Decode from/to chan types (for iterative streaming support)
|
||||
- Drop-in replacement for encoding/json. `json:` key in struct tag supported.
|
||||
- Provides a RPC Server and Client Codec for net/rpc communication protocol.
|
||||
- Handle unique idiosyncrasies of codecs e.g.
|
||||
- For messagepack, configure how ambiguities in handling raw bytes are resolved
|
||||
- For messagepack, provide rpc server/client codec to support
|
||||
msgpack-rpc protocol defined at:
|
||||
https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md
|
||||
|
||||
Extension Support
|
||||
|
||||
Users can register a function to handle the encoding or decoding of
|
||||
their custom types.
|
||||
|
||||
There are no restrictions on what the custom type can be. Some examples:
|
||||
|
||||
type BisSet []int
|
||||
type BitSet64 uint64
|
||||
type UUID string
|
||||
type MyStructWithUnexportedFields struct { a int; b bool; c []int; }
|
||||
type GifImage struct { ... }
|
||||
|
||||
As an illustration, MyStructWithUnexportedFields would normally be
|
||||
encoded as an empty map because it has no exported fields, while UUID
|
||||
would be encoded as a string. However, with extension support, you can
|
||||
encode any of these however you like.
|
||||
|
||||
RPC
|
||||
|
||||
RPC Client and Server Codecs are implemented, so the codecs can be used
|
||||
with the standard net/rpc package.
|
||||
|
||||
Usage
|
||||
|
||||
The Handle is SAFE for concurrent READ, but NOT SAFE for concurrent modification.
|
||||
|
||||
The Encoder and Decoder are NOT safe for concurrent use.
|
||||
|
||||
Consequently, the usage model is basically:
|
||||
|
||||
- Create and initialize the Handle before any use.
|
||||
Once created, DO NOT modify it.
|
||||
- Multiple Encoders or Decoders can now use the Handle concurrently.
|
||||
They only read information off the Handle (never write).
|
||||
- However, each Encoder or Decoder MUST not be used concurrently
|
||||
- To re-use an Encoder/Decoder, call Reset(...) on it first.
|
||||
This allows you use state maintained on the Encoder/Decoder.
|
||||
|
||||
Sample usage model:
|
||||
|
||||
// create and configure Handle
|
||||
var (
|
||||
bh codec.BincHandle
|
||||
mh codec.MsgpackHandle
|
||||
ch codec.CborHandle
|
||||
)
|
||||
|
||||
mh.MapType = reflect.TypeOf(map[string]interface{}(nil))
|
||||
|
||||
// configure extensions
|
||||
// e.g. for msgpack, define functions and enable Time support for tag 1
|
||||
// mh.SetExt(reflect.TypeOf(time.Time{}), 1, myExt)
|
||||
|
||||
// create and use decoder/encoder
|
||||
var (
|
||||
r io.Reader
|
||||
w io.Writer
|
||||
b []byte
|
||||
h = &bh // or mh to use msgpack
|
||||
)
|
||||
|
||||
dec = codec.NewDecoder(r, h)
|
||||
dec = codec.NewDecoderBytes(b, h)
|
||||
err = dec.Decode(&v)
|
||||
|
||||
enc = codec.NewEncoder(w, h)
|
||||
enc = codec.NewEncoderBytes(&b, h)
|
||||
err = enc.Encode(v)
|
||||
|
||||
//RPC Server
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
rpcCodec := codec.GoRpc.ServerCodec(conn, h)
|
||||
//OR rpcCodec := codec.MsgpackSpecRpc.ServerCodec(conn, h)
|
||||
rpc.ServeCodec(rpcCodec)
|
||||
}
|
||||
}()
|
||||
|
||||
//RPC Communication (client side)
|
||||
conn, err = net.Dial("tcp", "localhost:5555")
|
||||
rpcCodec := codec.GoRpc.ClientCodec(conn, h)
|
||||
//OR rpcCodec := codec.MsgpackSpecRpc.ClientCodec(conn, h)
|
||||
client := rpc.NewClientWithCodec(rpcCodec)
|
||||
|
||||
Running Tests
|
||||
|
||||
To run tests, use the following:
|
||||
|
||||
go test
|
||||
|
||||
To run the full suite of tests, use the following:
|
||||
|
||||
go test -tags alltests -run Suite
|
||||
|
||||
You can run the tag 'safe' to run tests or build in safe mode. e.g.
|
||||
|
||||
go test -tags safe -run Json
|
||||
go test -tags "alltests safe" -run Suite
|
||||
|
||||
Running Benchmarks
|
||||
|
||||
Please see http://github.com/ugorji/go-codec-bench .
|
||||
|
||||
*/
|
||||
package codec
|
||||
|
202
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/LICENSE
generated
vendored
Normal file
202
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
946
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/binc.go
generated
vendored
Normal file
946
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/binc.go
generated
vendored
Normal file
@ -0,0 +1,946 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
const bincDoPrune = true // No longer needed. Needed before as C lib did not support pruning.
|
||||
|
||||
// vd as low 4 bits (there are 16 slots)
|
||||
const (
|
||||
bincVdSpecial byte = iota
|
||||
bincVdPosInt
|
||||
bincVdNegInt
|
||||
bincVdFloat
|
||||
|
||||
bincVdString
|
||||
bincVdByteArray
|
||||
bincVdArray
|
||||
bincVdMap
|
||||
|
||||
bincVdTimestamp
|
||||
bincVdSmallInt
|
||||
bincVdUnicodeOther
|
||||
bincVdSymbol
|
||||
|
||||
bincVdDecimal
|
||||
_ // open slot
|
||||
_ // open slot
|
||||
bincVdCustomExt = 0x0f
|
||||
)
|
||||
|
||||
const (
|
||||
bincSpNil byte = iota
|
||||
bincSpFalse
|
||||
bincSpTrue
|
||||
bincSpNan
|
||||
bincSpPosInf
|
||||
bincSpNegInf
|
||||
bincSpZeroFloat
|
||||
bincSpZero
|
||||
bincSpNegOne
|
||||
)
|
||||
|
||||
const (
|
||||
bincFlBin16 byte = iota
|
||||
bincFlBin32
|
||||
_ // bincFlBin32e
|
||||
bincFlBin64
|
||||
_ // bincFlBin64e
|
||||
// others not currently supported
|
||||
)
|
||||
|
||||
type bincEncDriver struct {
|
||||
e *Encoder
|
||||
w encWriter
|
||||
m map[string]uint16 // symbols
|
||||
b [scratchByteArrayLen]byte
|
||||
s uint16 // symbols sequencer
|
||||
// encNoSeparator
|
||||
encDriverNoopContainerWriter
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) IsBuiltinType(rt uintptr) bool {
|
||||
return rt == timeTypId
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) EncodeBuiltin(rt uintptr, v interface{}) {
|
||||
if rt == timeTypId {
|
||||
var bs []byte
|
||||
switch x := v.(type) {
|
||||
case time.Time:
|
||||
bs = encodeTime(x)
|
||||
case *time.Time:
|
||||
bs = encodeTime(*x)
|
||||
default:
|
||||
e.e.errorf("binc error encoding builtin: expect time.Time, received %T", v)
|
||||
}
|
||||
e.w.writen1(bincVdTimestamp<<4 | uint8(len(bs)))
|
||||
e.w.writeb(bs)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) EncodeNil() {
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpNil)
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) EncodeBool(b bool) {
|
||||
if b {
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpTrue)
|
||||
} else {
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpFalse)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) EncodeFloat32(f float32) {
|
||||
if f == 0 {
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpZeroFloat)
|
||||
return
|
||||
}
|
||||
e.w.writen1(bincVdFloat<<4 | bincFlBin32)
|
||||
bigenHelper{e.b[:4], e.w}.writeUint32(math.Float32bits(f))
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) EncodeFloat64(f float64) {
|
||||
if f == 0 {
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpZeroFloat)
|
||||
return
|
||||
}
|
||||
bigen.PutUint64(e.b[:8], math.Float64bits(f))
|
||||
if bincDoPrune {
|
||||
i := 7
|
||||
for ; i >= 0 && (e.b[i] == 0); i-- {
|
||||
}
|
||||
i++
|
||||
if i <= 6 {
|
||||
e.w.writen1(bincVdFloat<<4 | 0x8 | bincFlBin64)
|
||||
e.w.writen1(byte(i))
|
||||
e.w.writeb(e.b[:i])
|
||||
return
|
||||
}
|
||||
}
|
||||
e.w.writen1(bincVdFloat<<4 | bincFlBin64)
|
||||
e.w.writeb(e.b[:8])
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encIntegerPrune(bd byte, pos bool, v uint64, lim uint8) {
|
||||
if lim == 4 {
|
||||
bigen.PutUint32(e.b[:lim], uint32(v))
|
||||
} else {
|
||||
bigen.PutUint64(e.b[:lim], v)
|
||||
}
|
||||
if bincDoPrune {
|
||||
i := pruneSignExt(e.b[:lim], pos)
|
||||
e.w.writen1(bd | lim - 1 - byte(i))
|
||||
e.w.writeb(e.b[i:lim])
|
||||
} else {
|
||||
e.w.writen1(bd | lim - 1)
|
||||
e.w.writeb(e.b[:lim])
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) EncodeInt(v int64) {
|
||||
const nbd byte = bincVdNegInt << 4
|
||||
if v >= 0 {
|
||||
e.encUint(bincVdPosInt<<4, true, uint64(v))
|
||||
} else if v == -1 {
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpNegOne)
|
||||
} else {
|
||||
e.encUint(bincVdNegInt<<4, false, uint64(-v))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) EncodeUint(v uint64) {
|
||||
e.encUint(bincVdPosInt<<4, true, v)
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encUint(bd byte, pos bool, v uint64) {
|
||||
if v == 0 {
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpZero)
|
||||
} else if pos && v >= 1 && v <= 16 {
|
||||
e.w.writen1(bincVdSmallInt<<4 | byte(v-1))
|
||||
} else if v <= math.MaxUint8 {
|
||||
e.w.writen2(bd|0x0, byte(v))
|
||||
} else if v <= math.MaxUint16 {
|
||||
e.w.writen1(bd | 0x01)
|
||||
bigenHelper{e.b[:2], e.w}.writeUint16(uint16(v))
|
||||
} else if v <= math.MaxUint32 {
|
||||
e.encIntegerPrune(bd, pos, v, 4)
|
||||
} else {
|
||||
e.encIntegerPrune(bd, pos, v, 8)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) EncodeExt(rv interface{}, xtag uint64, ext Ext, _ *Encoder) {
|
||||
bs := ext.WriteExt(rv)
|
||||
if bs == nil {
|
||||
e.EncodeNil()
|
||||
return
|
||||
}
|
||||
e.encodeExtPreamble(uint8(xtag), len(bs))
|
||||
e.w.writeb(bs)
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) EncodeRawExt(re *RawExt, _ *Encoder) {
|
||||
e.encodeExtPreamble(uint8(re.Tag), len(re.Data))
|
||||
e.w.writeb(re.Data)
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeExtPreamble(xtag byte, length int) {
|
||||
e.encLen(bincVdCustomExt<<4, uint64(length))
|
||||
e.w.writen1(xtag)
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) WriteArrayStart(length int) {
|
||||
e.encLen(bincVdArray<<4, uint64(length))
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) WriteMapStart(length int) {
|
||||
e.encLen(bincVdMap<<4, uint64(length))
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) EncodeString(c charEncoding, v string) {
|
||||
l := uint64(len(v))
|
||||
e.encBytesLen(c, l)
|
||||
if l > 0 {
|
||||
e.w.writestr(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) EncodeSymbol(v string) {
|
||||
// if WriteSymbolsNoRefs {
|
||||
// e.encodeString(c_UTF8, v)
|
||||
// return
|
||||
// }
|
||||
|
||||
//symbols only offer benefit when string length > 1.
|
||||
//This is because strings with length 1 take only 2 bytes to store
|
||||
//(bd with embedded length, and single byte for string val).
|
||||
|
||||
l := len(v)
|
||||
if l == 0 {
|
||||
e.encBytesLen(c_UTF8, 0)
|
||||
return
|
||||
} else if l == 1 {
|
||||
e.encBytesLen(c_UTF8, 1)
|
||||
e.w.writen1(v[0])
|
||||
return
|
||||
}
|
||||
if e.m == nil {
|
||||
e.m = make(map[string]uint16, 16)
|
||||
}
|
||||
ui, ok := e.m[v]
|
||||
if ok {
|
||||
if ui <= math.MaxUint8 {
|
||||
e.w.writen2(bincVdSymbol<<4, byte(ui))
|
||||
} else {
|
||||
e.w.writen1(bincVdSymbol<<4 | 0x8)
|
||||
bigenHelper{e.b[:2], e.w}.writeUint16(ui)
|
||||
}
|
||||
} else {
|
||||
e.s++
|
||||
ui = e.s
|
||||
//ui = uint16(atomic.AddUint32(&e.s, 1))
|
||||
e.m[v] = ui
|
||||
var lenprec uint8
|
||||
if l <= math.MaxUint8 {
|
||||
// lenprec = 0
|
||||
} else if l <= math.MaxUint16 {
|
||||
lenprec = 1
|
||||
} else if int64(l) <= math.MaxUint32 {
|
||||
lenprec = 2
|
||||
} else {
|
||||
lenprec = 3
|
||||
}
|
||||
if ui <= math.MaxUint8 {
|
||||
e.w.writen2(bincVdSymbol<<4|0x0|0x4|lenprec, byte(ui))
|
||||
} else {
|
||||
e.w.writen1(bincVdSymbol<<4 | 0x8 | 0x4 | lenprec)
|
||||
bigenHelper{e.b[:2], e.w}.writeUint16(ui)
|
||||
}
|
||||
if lenprec == 0 {
|
||||
e.w.writen1(byte(l))
|
||||
} else if lenprec == 1 {
|
||||
bigenHelper{e.b[:2], e.w}.writeUint16(uint16(l))
|
||||
} else if lenprec == 2 {
|
||||
bigenHelper{e.b[:4], e.w}.writeUint32(uint32(l))
|
||||
} else {
|
||||
bigenHelper{e.b[:8], e.w}.writeUint64(uint64(l))
|
||||
}
|
||||
e.w.writestr(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) EncodeStringBytes(c charEncoding, v []byte) {
|
||||
l := uint64(len(v))
|
||||
e.encBytesLen(c, l)
|
||||
if l > 0 {
|
||||
e.w.writeb(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encBytesLen(c charEncoding, length uint64) {
|
||||
//TODO: support bincUnicodeOther (for now, just use string or bytearray)
|
||||
if c == c_RAW {
|
||||
e.encLen(bincVdByteArray<<4, length)
|
||||
} else {
|
||||
e.encLen(bincVdString<<4, length)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encLen(bd byte, l uint64) {
|
||||
if l < 12 {
|
||||
e.w.writen1(bd | uint8(l+4))
|
||||
} else {
|
||||
e.encLenNumber(bd, l)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encLenNumber(bd byte, v uint64) {
|
||||
if v <= math.MaxUint8 {
|
||||
e.w.writen2(bd, byte(v))
|
||||
} else if v <= math.MaxUint16 {
|
||||
e.w.writen1(bd | 0x01)
|
||||
bigenHelper{e.b[:2], e.w}.writeUint16(uint16(v))
|
||||
} else if v <= math.MaxUint32 {
|
||||
e.w.writen1(bd | 0x02)
|
||||
bigenHelper{e.b[:4], e.w}.writeUint32(uint32(v))
|
||||
} else {
|
||||
e.w.writen1(bd | 0x03)
|
||||
bigenHelper{e.b[:8], e.w}.writeUint64(uint64(v))
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
|
||||
type bincDecSymbol struct {
|
||||
s string
|
||||
b []byte
|
||||
i uint16
|
||||
}
|
||||
|
||||
type bincDecDriver struct {
|
||||
d *Decoder
|
||||
h *BincHandle
|
||||
r decReader
|
||||
br bool // bytes reader
|
||||
bdRead bool
|
||||
bd byte
|
||||
vd byte
|
||||
vs byte
|
||||
// noStreamingCodec
|
||||
// decNoSeparator
|
||||
b [scratchByteArrayLen]byte
|
||||
|
||||
// linear searching on this slice is ok,
|
||||
// because we typically expect < 32 symbols in each stream.
|
||||
s []bincDecSymbol
|
||||
decDriverNoopContainerReader
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) readNextBd() {
|
||||
d.bd = d.r.readn1()
|
||||
d.vd = d.bd >> 4
|
||||
d.vs = d.bd & 0x0f
|
||||
d.bdRead = true
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) uncacheRead() {
|
||||
if d.bdRead {
|
||||
d.r.unreadn1()
|
||||
d.bdRead = false
|
||||
}
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) ContainerType() (vt valueType) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.vd == bincVdSpecial && d.vs == bincSpNil {
|
||||
return valueTypeNil
|
||||
} else if d.vd == bincVdByteArray {
|
||||
return valueTypeBytes
|
||||
} else if d.vd == bincVdString {
|
||||
return valueTypeString
|
||||
} else if d.vd == bincVdArray {
|
||||
return valueTypeArray
|
||||
} else if d.vd == bincVdMap {
|
||||
return valueTypeMap
|
||||
} else {
|
||||
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
|
||||
}
|
||||
return valueTypeUnset
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) TryDecodeAsNil() bool {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == bincVdSpecial<<4|bincSpNil {
|
||||
d.bdRead = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) IsBuiltinType(rt uintptr) bool {
|
||||
return rt == timeTypId
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) DecodeBuiltin(rt uintptr, v interface{}) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if rt == timeTypId {
|
||||
if d.vd != bincVdTimestamp {
|
||||
d.d.errorf("Invalid d.vd. Expecting 0x%x. Received: 0x%x", bincVdTimestamp, d.vd)
|
||||
return
|
||||
}
|
||||
tt, err := decodeTime(d.r.readx(int(d.vs)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var vt *time.Time = v.(*time.Time)
|
||||
*vt = tt
|
||||
d.bdRead = false
|
||||
}
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decFloatPre(vs, defaultLen byte) {
|
||||
if vs&0x8 == 0 {
|
||||
d.r.readb(d.b[0:defaultLen])
|
||||
} else {
|
||||
l := d.r.readn1()
|
||||
if l > 8 {
|
||||
d.d.errorf("At most 8 bytes used to represent float. Received: %v bytes", l)
|
||||
return
|
||||
}
|
||||
for i := l; i < 8; i++ {
|
||||
d.b[i] = 0
|
||||
}
|
||||
d.r.readb(d.b[0:l])
|
||||
}
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decFloat() (f float64) {
|
||||
//if true { f = math.Float64frombits(bigen.Uint64(d.r.readx(8))); break; }
|
||||
if x := d.vs & 0x7; x == bincFlBin32 {
|
||||
d.decFloatPre(d.vs, 4)
|
||||
f = float64(math.Float32frombits(bigen.Uint32(d.b[0:4])))
|
||||
} else if x == bincFlBin64 {
|
||||
d.decFloatPre(d.vs, 8)
|
||||
f = math.Float64frombits(bigen.Uint64(d.b[0:8]))
|
||||
} else {
|
||||
d.d.errorf("only float32 and float64 are supported. d.vd: 0x%x, d.vs: 0x%x", d.vd, d.vs)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decUint() (v uint64) {
|
||||
// need to inline the code (interface conversion and type assertion expensive)
|
||||
switch d.vs {
|
||||
case 0:
|
||||
v = uint64(d.r.readn1())
|
||||
case 1:
|
||||
d.r.readb(d.b[6:8])
|
||||
v = uint64(bigen.Uint16(d.b[6:8]))
|
||||
case 2:
|
||||
d.b[4] = 0
|
||||
d.r.readb(d.b[5:8])
|
||||
v = uint64(bigen.Uint32(d.b[4:8]))
|
||||
case 3:
|
||||
d.r.readb(d.b[4:8])
|
||||
v = uint64(bigen.Uint32(d.b[4:8]))
|
||||
case 4, 5, 6:
|
||||
lim := int(7 - d.vs)
|
||||
d.r.readb(d.b[lim:8])
|
||||
for i := 0; i < lim; i++ {
|
||||
d.b[i] = 0
|
||||
}
|
||||
v = uint64(bigen.Uint64(d.b[:8]))
|
||||
case 7:
|
||||
d.r.readb(d.b[:8])
|
||||
v = uint64(bigen.Uint64(d.b[:8]))
|
||||
default:
|
||||
d.d.errorf("unsigned integers with greater than 64 bits of precision not supported")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decCheckInteger() (ui uint64, neg bool) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
vd, vs := d.vd, d.vs
|
||||
if vd == bincVdPosInt {
|
||||
ui = d.decUint()
|
||||
} else if vd == bincVdNegInt {
|
||||
ui = d.decUint()
|
||||
neg = true
|
||||
} else if vd == bincVdSmallInt {
|
||||
ui = uint64(d.vs) + 1
|
||||
} else if vd == bincVdSpecial {
|
||||
if vs == bincSpZero {
|
||||
//i = 0
|
||||
} else if vs == bincSpNegOne {
|
||||
neg = true
|
||||
ui = 1
|
||||
} else {
|
||||
d.d.errorf("numeric decode fails for special value: d.vs: 0x%x", d.vs)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
d.d.errorf("number can only be decoded from uint or int values. d.bd: 0x%x, d.vd: 0x%x", d.bd, d.vd)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) DecodeInt(bitsize uint8) (i int64) {
|
||||
ui, neg := d.decCheckInteger()
|
||||
i, overflow := chkOvf.SignedInt(ui)
|
||||
if overflow {
|
||||
d.d.errorf("simple: overflow converting %v to signed integer", ui)
|
||||
return
|
||||
}
|
||||
if neg {
|
||||
i = -i
|
||||
}
|
||||
if chkOvf.Int(i, bitsize) {
|
||||
d.d.errorf("binc: overflow integer: %v for num bits: %v", i, bitsize)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) DecodeUint(bitsize uint8) (ui uint64) {
|
||||
ui, neg := d.decCheckInteger()
|
||||
if neg {
|
||||
d.d.errorf("Assigning negative signed value to unsigned type")
|
||||
return
|
||||
}
|
||||
if chkOvf.Uint(ui, bitsize) {
|
||||
d.d.errorf("binc: overflow integer: %v", ui)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
vd, vs := d.vd, d.vs
|
||||
if vd == bincVdSpecial {
|
||||
d.bdRead = false
|
||||
if vs == bincSpNan {
|
||||
return math.NaN()
|
||||
} else if vs == bincSpPosInf {
|
||||
return math.Inf(1)
|
||||
} else if vs == bincSpZeroFloat || vs == bincSpZero {
|
||||
return
|
||||
} else if vs == bincSpNegInf {
|
||||
return math.Inf(-1)
|
||||
} else {
|
||||
d.d.errorf("Invalid d.vs decoding float where d.vd=bincVdSpecial: %v", d.vs)
|
||||
return
|
||||
}
|
||||
} else if vd == bincVdFloat {
|
||||
f = d.decFloat()
|
||||
} else {
|
||||
f = float64(d.DecodeInt(64))
|
||||
}
|
||||
if chkOverflow32 && chkOvf.Float32(f) {
|
||||
d.d.errorf("binc: float32 overflow: %v", f)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
// bool can be decoded from bool only (single byte).
|
||||
func (d *bincDecDriver) DecodeBool() (b bool) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if bd := d.bd; bd == (bincVdSpecial | bincSpFalse) {
|
||||
// b = false
|
||||
} else if bd == (bincVdSpecial | bincSpTrue) {
|
||||
b = true
|
||||
} else {
|
||||
d.d.errorf("Invalid single-byte value for bool: %s: %x", msgBadDesc, d.bd)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) ReadMapStart() (length int) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.vd != bincVdMap {
|
||||
d.d.errorf("Invalid d.vd for map. Expecting 0x%x. Got: 0x%x", bincVdMap, d.vd)
|
||||
return
|
||||
}
|
||||
length = d.decLen()
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) ReadArrayStart() (length int) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.vd != bincVdArray {
|
||||
d.d.errorf("Invalid d.vd for array. Expecting 0x%x. Got: 0x%x", bincVdArray, d.vd)
|
||||
return
|
||||
}
|
||||
length = d.decLen()
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decLen() int {
|
||||
if d.vs > 3 {
|
||||
return int(d.vs - 4)
|
||||
}
|
||||
return int(d.decLenNumber())
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decLenNumber() (v uint64) {
|
||||
if x := d.vs; x == 0 {
|
||||
v = uint64(d.r.readn1())
|
||||
} else if x == 1 {
|
||||
d.r.readb(d.b[6:8])
|
||||
v = uint64(bigen.Uint16(d.b[6:8]))
|
||||
} else if x == 2 {
|
||||
d.r.readb(d.b[4:8])
|
||||
v = uint64(bigen.Uint32(d.b[4:8]))
|
||||
} else {
|
||||
d.r.readb(d.b[:8])
|
||||
v = bigen.Uint64(d.b[:8])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decStringAndBytes(bs []byte, withString, zerocopy bool) (bs2 []byte, s string) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == bincVdSpecial<<4|bincSpNil {
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
var slen int = -1
|
||||
// var ok bool
|
||||
switch d.vd {
|
||||
case bincVdString, bincVdByteArray:
|
||||
slen = d.decLen()
|
||||
if zerocopy {
|
||||
if d.br {
|
||||
bs2 = d.r.readx(slen)
|
||||
} else if len(bs) == 0 {
|
||||
bs2 = decByteSlice(d.r, slen, d.d.h.MaxInitLen, d.b[:])
|
||||
} else {
|
||||
bs2 = decByteSlice(d.r, slen, d.d.h.MaxInitLen, bs)
|
||||
}
|
||||
} else {
|
||||
bs2 = decByteSlice(d.r, slen, d.d.h.MaxInitLen, bs)
|
||||
}
|
||||
if withString {
|
||||
s = string(bs2)
|
||||
}
|
||||
case bincVdSymbol:
|
||||
// zerocopy doesn't apply for symbols,
|
||||
// as the values must be stored in a table for later use.
|
||||
//
|
||||
//from vs: extract numSymbolBytes, containsStringVal, strLenPrecision,
|
||||
//extract symbol
|
||||
//if containsStringVal, read it and put in map
|
||||
//else look in map for string value
|
||||
var symbol uint16
|
||||
vs := d.vs
|
||||
if vs&0x8 == 0 {
|
||||
symbol = uint16(d.r.readn1())
|
||||
} else {
|
||||
symbol = uint16(bigen.Uint16(d.r.readx(2)))
|
||||
}
|
||||
if d.s == nil {
|
||||
d.s = make([]bincDecSymbol, 0, 16)
|
||||
}
|
||||
|
||||
if vs&0x4 == 0 {
|
||||
for i := range d.s {
|
||||
j := &d.s[i]
|
||||
if j.i == symbol {
|
||||
bs2 = j.b
|
||||
if withString {
|
||||
if j.s == "" && bs2 != nil {
|
||||
j.s = string(bs2)
|
||||
}
|
||||
s = j.s
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch vs & 0x3 {
|
||||
case 0:
|
||||
slen = int(d.r.readn1())
|
||||
case 1:
|
||||
slen = int(bigen.Uint16(d.r.readx(2)))
|
||||
case 2:
|
||||
slen = int(bigen.Uint32(d.r.readx(4)))
|
||||
case 3:
|
||||
slen = int(bigen.Uint64(d.r.readx(8)))
|
||||
}
|
||||
// since using symbols, do not store any part of
|
||||
// the parameter bs in the map, as it might be a shared buffer.
|
||||
// bs2 = decByteSlice(d.r, slen, bs)
|
||||
bs2 = decByteSlice(d.r, slen, d.d.h.MaxInitLen, nil)
|
||||
if withString {
|
||||
s = string(bs2)
|
||||
}
|
||||
d.s = append(d.s, bincDecSymbol{i: symbol, s: s, b: bs2})
|
||||
}
|
||||
default:
|
||||
d.d.errorf("Invalid d.vd. Expecting string:0x%x, bytearray:0x%x or symbol: 0x%x. Got: 0x%x",
|
||||
bincVdString, bincVdByteArray, bincVdSymbol, d.vd)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) DecodeString() (s string) {
|
||||
// DecodeBytes does not accommodate symbols, whose impl stores string version in map.
|
||||
// Use decStringAndBytes directly.
|
||||
// return string(d.DecodeBytes(d.b[:], true, true))
|
||||
_, s = d.decStringAndBytes(d.b[:], true, true)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) DecodeStringAsBytes() (s []byte) {
|
||||
s, _ = d.decStringAndBytes(d.b[:], false, true)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == bincVdSpecial<<4|bincSpNil {
|
||||
d.bdRead = false
|
||||
return nil
|
||||
}
|
||||
var clen int
|
||||
if d.vd == bincVdString || d.vd == bincVdByteArray {
|
||||
clen = d.decLen()
|
||||
} else {
|
||||
d.d.errorf("Invalid d.vd for bytes. Expecting string:0x%x or bytearray:0x%x. Got: 0x%x",
|
||||
bincVdString, bincVdByteArray, d.vd)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
if zerocopy {
|
||||
if d.br {
|
||||
return d.r.readx(clen)
|
||||
} else if len(bs) == 0 {
|
||||
bs = d.b[:]
|
||||
}
|
||||
}
|
||||
return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs)
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) {
|
||||
if xtag > 0xff {
|
||||
d.d.errorf("decodeExt: tag must be <= 0xff; got: %v", xtag)
|
||||
return
|
||||
}
|
||||
realxtag1, xbs := d.decodeExtV(ext != nil, uint8(xtag))
|
||||
realxtag = uint64(realxtag1)
|
||||
if ext == nil {
|
||||
re := rv.(*RawExt)
|
||||
re.Tag = realxtag
|
||||
re.Data = detachZeroCopyBytes(d.br, re.Data, xbs)
|
||||
} else {
|
||||
ext.ReadExt(rv, xbs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decodeExtV(verifyTag bool, tag byte) (xtag byte, xbs []byte) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.vd == bincVdCustomExt {
|
||||
l := d.decLen()
|
||||
xtag = d.r.readn1()
|
||||
if verifyTag && xtag != tag {
|
||||
d.d.errorf("Wrong extension tag. Got %b. Expecting: %v", xtag, tag)
|
||||
return
|
||||
}
|
||||
xbs = d.r.readx(l)
|
||||
} else if d.vd == bincVdByteArray {
|
||||
xbs = d.DecodeBytes(nil, true)
|
||||
} else {
|
||||
d.d.errorf("Invalid d.vd for extensions (Expecting extensions or byte array). Got: 0x%x", d.vd)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) DecodeNaked() {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
|
||||
n := d.d.n
|
||||
var decodeFurther bool
|
||||
|
||||
switch d.vd {
|
||||
case bincVdSpecial:
|
||||
switch d.vs {
|
||||
case bincSpNil:
|
||||
n.v = valueTypeNil
|
||||
case bincSpFalse:
|
||||
n.v = valueTypeBool
|
||||
n.b = false
|
||||
case bincSpTrue:
|
||||
n.v = valueTypeBool
|
||||
n.b = true
|
||||
case bincSpNan:
|
||||
n.v = valueTypeFloat
|
||||
n.f = math.NaN()
|
||||
case bincSpPosInf:
|
||||
n.v = valueTypeFloat
|
||||
n.f = math.Inf(1)
|
||||
case bincSpNegInf:
|
||||
n.v = valueTypeFloat
|
||||
n.f = math.Inf(-1)
|
||||
case bincSpZeroFloat:
|
||||
n.v = valueTypeFloat
|
||||
n.f = float64(0)
|
||||
case bincSpZero:
|
||||
n.v = valueTypeUint
|
||||
n.u = uint64(0) // int8(0)
|
||||
case bincSpNegOne:
|
||||
n.v = valueTypeInt
|
||||
n.i = int64(-1) // int8(-1)
|
||||
default:
|
||||
d.d.errorf("decodeNaked: Unrecognized special value 0x%x", d.vs)
|
||||
}
|
||||
case bincVdSmallInt:
|
||||
n.v = valueTypeUint
|
||||
n.u = uint64(int8(d.vs)) + 1 // int8(d.vs) + 1
|
||||
case bincVdPosInt:
|
||||
n.v = valueTypeUint
|
||||
n.u = d.decUint()
|
||||
case bincVdNegInt:
|
||||
n.v = valueTypeInt
|
||||
n.i = -(int64(d.decUint()))
|
||||
case bincVdFloat:
|
||||
n.v = valueTypeFloat
|
||||
n.f = d.decFloat()
|
||||
case bincVdSymbol:
|
||||
n.v = valueTypeSymbol
|
||||
n.s = d.DecodeString()
|
||||
case bincVdString:
|
||||
n.v = valueTypeString
|
||||
n.s = d.DecodeString()
|
||||
case bincVdByteArray:
|
||||
n.v = valueTypeBytes
|
||||
n.l = d.DecodeBytes(nil, false)
|
||||
case bincVdTimestamp:
|
||||
n.v = valueTypeTimestamp
|
||||
tt, err := decodeTime(d.r.readx(int(d.vs)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
n.t = tt
|
||||
case bincVdCustomExt:
|
||||
n.v = valueTypeExt
|
||||
l := d.decLen()
|
||||
n.u = uint64(d.r.readn1())
|
||||
n.l = d.r.readx(l)
|
||||
case bincVdArray:
|
||||
n.v = valueTypeArray
|
||||
decodeFurther = true
|
||||
case bincVdMap:
|
||||
n.v = valueTypeMap
|
||||
decodeFurther = true
|
||||
default:
|
||||
d.d.errorf("decodeNaked: Unrecognized d.vd: 0x%x", d.vd)
|
||||
}
|
||||
|
||||
if !decodeFurther {
|
||||
d.bdRead = false
|
||||
}
|
||||
if n.v == valueTypeUint && d.h.SignedInteger {
|
||||
n.v = valueTypeInt
|
||||
n.i = int64(n.u)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
|
||||
//BincHandle is a Handle for the Binc Schema-Free Encoding Format
|
||||
//defined at https://github.com/ugorji/binc .
|
||||
//
|
||||
//BincHandle currently supports all Binc features with the following EXCEPTIONS:
|
||||
// - only integers up to 64 bits of precision are supported.
|
||||
// big integers are unsupported.
|
||||
// - Only IEEE 754 binary32 and binary64 floats are supported (ie Go float32 and float64 types).
|
||||
// extended precision and decimal IEEE 754 floats are unsupported.
|
||||
// - Only UTF-8 strings supported.
|
||||
// Unicode_Other Binc types (UTF16, UTF32) are currently unsupported.
|
||||
//
|
||||
//Note that these EXCEPTIONS are temporary and full support is possible and may happen soon.
|
||||
type BincHandle struct {
|
||||
BasicHandle
|
||||
binaryEncodingType
|
||||
noElemSeparators
|
||||
}
|
||||
|
||||
func (h *BincHandle) SetBytesExt(rt reflect.Type, tag uint64, ext BytesExt) (err error) {
|
||||
return h.SetExt(rt, tag, &setExtWrapper{b: ext})
|
||||
}
|
||||
|
||||
func (h *BincHandle) newEncDriver(e *Encoder) encDriver {
|
||||
return &bincEncDriver{e: e, w: e.w}
|
||||
}
|
||||
|
||||
func (h *BincHandle) newDecDriver(d *Decoder) decDriver {
|
||||
return &bincDecDriver{d: d, h: h, r: d.r, br: d.bytes}
|
||||
}
|
||||
|
||||
func (_ *BincHandle) IsBuiltinType(rt uintptr) bool {
|
||||
return rt == timeTypId
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) reset() {
|
||||
e.w = e.e.w
|
||||
e.s = 0
|
||||
e.m = nil
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) reset() {
|
||||
d.r, d.br = d.d.r, d.d.bytes
|
||||
d.s = nil
|
||||
d.bd, d.bdRead, d.vd, d.vs = 0, false, 0, 0
|
||||
}
|
||||
|
||||
var _ decDriver = (*bincDecDriver)(nil)
|
||||
var _ encDriver = (*bincEncDriver)(nil)
|
631
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/cbor.go
generated
vendored
Normal file
631
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/cbor.go
generated
vendored
Normal file
@ -0,0 +1,631 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
cborMajorUint byte = iota
|
||||
cborMajorNegInt
|
||||
cborMajorBytes
|
||||
cborMajorText
|
||||
cborMajorArray
|
||||
cborMajorMap
|
||||
cborMajorTag
|
||||
cborMajorOther
|
||||
)
|
||||
|
||||
const (
|
||||
cborBdFalse byte = 0xf4 + iota
|
||||
cborBdTrue
|
||||
cborBdNil
|
||||
cborBdUndefined
|
||||
cborBdExt
|
||||
cborBdFloat16
|
||||
cborBdFloat32
|
||||
cborBdFloat64
|
||||
)
|
||||
|
||||
const (
|
||||
cborBdIndefiniteBytes byte = 0x5f
|
||||
cborBdIndefiniteString = 0x7f
|
||||
cborBdIndefiniteArray = 0x9f
|
||||
cborBdIndefiniteMap = 0xbf
|
||||
cborBdBreak = 0xff
|
||||
)
|
||||
|
||||
const (
|
||||
CborStreamBytes byte = 0x5f
|
||||
CborStreamString = 0x7f
|
||||
CborStreamArray = 0x9f
|
||||
CborStreamMap = 0xbf
|
||||
CborStreamBreak = 0xff
|
||||
)
|
||||
|
||||
const (
|
||||
cborBaseUint byte = 0x00
|
||||
cborBaseNegInt = 0x20
|
||||
cborBaseBytes = 0x40
|
||||
cborBaseString = 0x60
|
||||
cborBaseArray = 0x80
|
||||
cborBaseMap = 0xa0
|
||||
cborBaseTag = 0xc0
|
||||
cborBaseSimple = 0xe0
|
||||
)
|
||||
|
||||
// -------------------
|
||||
|
||||
type cborEncDriver struct {
|
||||
noBuiltInTypes
|
||||
encDriverNoopContainerWriter
|
||||
// encNoSeparator
|
||||
e *Encoder
|
||||
w encWriter
|
||||
h *CborHandle
|
||||
x [8]byte
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) EncodeNil() {
|
||||
e.w.writen1(cborBdNil)
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) EncodeBool(b bool) {
|
||||
if b {
|
||||
e.w.writen1(cborBdTrue)
|
||||
} else {
|
||||
e.w.writen1(cborBdFalse)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) EncodeFloat32(f float32) {
|
||||
e.w.writen1(cborBdFloat32)
|
||||
bigenHelper{e.x[:4], e.w}.writeUint32(math.Float32bits(f))
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) EncodeFloat64(f float64) {
|
||||
e.w.writen1(cborBdFloat64)
|
||||
bigenHelper{e.x[:8], e.w}.writeUint64(math.Float64bits(f))
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) encUint(v uint64, bd byte) {
|
||||
if v <= 0x17 {
|
||||
e.w.writen1(byte(v) + bd)
|
||||
} else if v <= math.MaxUint8 {
|
||||
e.w.writen2(bd+0x18, uint8(v))
|
||||
} else if v <= math.MaxUint16 {
|
||||
e.w.writen1(bd + 0x19)
|
||||
bigenHelper{e.x[:2], e.w}.writeUint16(uint16(v))
|
||||
} else if v <= math.MaxUint32 {
|
||||
e.w.writen1(bd + 0x1a)
|
||||
bigenHelper{e.x[:4], e.w}.writeUint32(uint32(v))
|
||||
} else { // if v <= math.MaxUint64 {
|
||||
e.w.writen1(bd + 0x1b)
|
||||
bigenHelper{e.x[:8], e.w}.writeUint64(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) EncodeInt(v int64) {
|
||||
if v < 0 {
|
||||
e.encUint(uint64(-1-v), cborBaseNegInt)
|
||||
} else {
|
||||
e.encUint(uint64(v), cborBaseUint)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) EncodeUint(v uint64) {
|
||||
e.encUint(v, cborBaseUint)
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) encLen(bd byte, length int) {
|
||||
e.encUint(uint64(length), bd)
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) EncodeExt(rv interface{}, xtag uint64, ext Ext, en *Encoder) {
|
||||
e.encUint(uint64(xtag), cborBaseTag)
|
||||
if v := ext.ConvertExt(rv); v == nil {
|
||||
e.EncodeNil()
|
||||
} else {
|
||||
en.encode(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) EncodeRawExt(re *RawExt, en *Encoder) {
|
||||
e.encUint(uint64(re.Tag), cborBaseTag)
|
||||
if false && re.Data != nil {
|
||||
en.encode(re.Data)
|
||||
} else if re.Value != nil {
|
||||
en.encode(re.Value)
|
||||
} else {
|
||||
e.EncodeNil()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) WriteArrayStart(length int) {
|
||||
if e.h.IndefiniteLength {
|
||||
e.w.writen1(cborBdIndefiniteArray)
|
||||
} else {
|
||||
e.encLen(cborBaseArray, length)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) WriteMapStart(length int) {
|
||||
if e.h.IndefiniteLength {
|
||||
e.w.writen1(cborBdIndefiniteMap)
|
||||
} else {
|
||||
e.encLen(cborBaseMap, length)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) WriteMapEnd() {
|
||||
if e.h.IndefiniteLength {
|
||||
e.w.writen1(cborBdBreak)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) WriteArrayEnd() {
|
||||
if e.h.IndefiniteLength {
|
||||
e.w.writen1(cborBdBreak)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) EncodeString(c charEncoding, v string) {
|
||||
e.encLen(cborBaseString, len(v))
|
||||
e.w.writestr(v)
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) EncodeSymbol(v string) {
|
||||
e.EncodeString(c_UTF8, v)
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) EncodeStringBytes(c charEncoding, v []byte) {
|
||||
if c == c_RAW {
|
||||
e.encLen(cborBaseBytes, len(v))
|
||||
} else {
|
||||
e.encLen(cborBaseString, len(v))
|
||||
}
|
||||
e.w.writeb(v)
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
|
||||
type cborDecDriver struct {
|
||||
d *Decoder
|
||||
h *CborHandle
|
||||
r decReader
|
||||
b [scratchByteArrayLen]byte
|
||||
br bool // bytes reader
|
||||
bdRead bool
|
||||
bd byte
|
||||
noBuiltInTypes
|
||||
// decNoSeparator
|
||||
decDriverNoopContainerReader
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) readNextBd() {
|
||||
d.bd = d.r.readn1()
|
||||
d.bdRead = true
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) uncacheRead() {
|
||||
if d.bdRead {
|
||||
d.r.unreadn1()
|
||||
d.bdRead = false
|
||||
}
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) ContainerType() (vt valueType) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == cborBdNil {
|
||||
return valueTypeNil
|
||||
} else if d.bd == cborBdIndefiniteBytes || (d.bd >= cborBaseBytes && d.bd < cborBaseString) {
|
||||
return valueTypeBytes
|
||||
} else if d.bd == cborBdIndefiniteString || (d.bd >= cborBaseString && d.bd < cborBaseArray) {
|
||||
return valueTypeString
|
||||
} else if d.bd == cborBdIndefiniteArray || (d.bd >= cborBaseArray && d.bd < cborBaseMap) {
|
||||
return valueTypeArray
|
||||
} else if d.bd == cborBdIndefiniteMap || (d.bd >= cborBaseMap && d.bd < cborBaseTag) {
|
||||
return valueTypeMap
|
||||
} else {
|
||||
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
|
||||
}
|
||||
return valueTypeUnset
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) TryDecodeAsNil() bool {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
// treat Nil and Undefined as nil values
|
||||
if d.bd == cborBdNil || d.bd == cborBdUndefined {
|
||||
d.bdRead = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) CheckBreak() bool {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == cborBdBreak {
|
||||
d.bdRead = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) decUint() (ui uint64) {
|
||||
v := d.bd & 0x1f
|
||||
if v <= 0x17 {
|
||||
ui = uint64(v)
|
||||
} else {
|
||||
if v == 0x18 {
|
||||
ui = uint64(d.r.readn1())
|
||||
} else if v == 0x19 {
|
||||
ui = uint64(bigen.Uint16(d.r.readx(2)))
|
||||
} else if v == 0x1a {
|
||||
ui = uint64(bigen.Uint32(d.r.readx(4)))
|
||||
} else if v == 0x1b {
|
||||
ui = uint64(bigen.Uint64(d.r.readx(8)))
|
||||
} else {
|
||||
d.d.errorf("decUint: Invalid descriptor: %v", d.bd)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) decCheckInteger() (neg bool) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
major := d.bd >> 5
|
||||
if major == cborMajorUint {
|
||||
} else if major == cborMajorNegInt {
|
||||
neg = true
|
||||
} else {
|
||||
d.d.errorf("invalid major: %v (bd: %v)", major, d.bd)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) DecodeInt(bitsize uint8) (i int64) {
|
||||
neg := d.decCheckInteger()
|
||||
ui := d.decUint()
|
||||
// check if this number can be converted to an int without overflow
|
||||
var overflow bool
|
||||
if neg {
|
||||
if i, overflow = chkOvf.SignedInt(ui + 1); overflow {
|
||||
d.d.errorf("cbor: overflow converting %v to signed integer", ui+1)
|
||||
return
|
||||
}
|
||||
i = -i
|
||||
} else {
|
||||
if i, overflow = chkOvf.SignedInt(ui); overflow {
|
||||
d.d.errorf("cbor: overflow converting %v to signed integer", ui)
|
||||
return
|
||||
}
|
||||
}
|
||||
if chkOvf.Int(i, bitsize) {
|
||||
d.d.errorf("cbor: overflow integer: %v", i)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) DecodeUint(bitsize uint8) (ui uint64) {
|
||||
if d.decCheckInteger() {
|
||||
d.d.errorf("Assigning negative signed value to unsigned type")
|
||||
return
|
||||
}
|
||||
ui = d.decUint()
|
||||
if chkOvf.Uint(ui, bitsize) {
|
||||
d.d.errorf("cbor: overflow integer: %v", ui)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if bd := d.bd; bd == cborBdFloat16 {
|
||||
f = float64(math.Float32frombits(halfFloatToFloatBits(bigen.Uint16(d.r.readx(2)))))
|
||||
} else if bd == cborBdFloat32 {
|
||||
f = float64(math.Float32frombits(bigen.Uint32(d.r.readx(4))))
|
||||
} else if bd == cborBdFloat64 {
|
||||
f = math.Float64frombits(bigen.Uint64(d.r.readx(8)))
|
||||
} else if bd >= cborBaseUint && bd < cborBaseBytes {
|
||||
f = float64(d.DecodeInt(64))
|
||||
} else {
|
||||
d.d.errorf("Float only valid from float16/32/64: Invalid descriptor: %v", bd)
|
||||
return
|
||||
}
|
||||
if chkOverflow32 && chkOvf.Float32(f) {
|
||||
d.d.errorf("cbor: float32 overflow: %v", f)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
// bool can be decoded from bool only (single byte).
|
||||
func (d *cborDecDriver) DecodeBool() (b bool) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if bd := d.bd; bd == cborBdTrue {
|
||||
b = true
|
||||
} else if bd == cborBdFalse {
|
||||
} else {
|
||||
d.d.errorf("Invalid single-byte value for bool: %s: %x", msgBadDesc, d.bd)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) ReadMapStart() (length int) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
d.bdRead = false
|
||||
if d.bd == cborBdIndefiniteMap {
|
||||
return -1
|
||||
}
|
||||
return d.decLen()
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) ReadArrayStart() (length int) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
d.bdRead = false
|
||||
if d.bd == cborBdIndefiniteArray {
|
||||
return -1
|
||||
}
|
||||
return d.decLen()
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) decLen() int {
|
||||
return int(d.decUint())
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) decAppendIndefiniteBytes(bs []byte) []byte {
|
||||
d.bdRead = false
|
||||
for {
|
||||
if d.CheckBreak() {
|
||||
break
|
||||
}
|
||||
if major := d.bd >> 5; major != cborMajorBytes && major != cborMajorText {
|
||||
d.d.errorf("cbor: expect bytes or string major type in indefinite string/bytes; got: %v, byte: %v", major, d.bd)
|
||||
return nil
|
||||
}
|
||||
n := d.decLen()
|
||||
oldLen := len(bs)
|
||||
newLen := oldLen + n
|
||||
if newLen > cap(bs) {
|
||||
bs2 := make([]byte, newLen, 2*cap(bs)+n)
|
||||
copy(bs2, bs)
|
||||
bs = bs2
|
||||
} else {
|
||||
bs = bs[:newLen]
|
||||
}
|
||||
d.r.readb(bs[oldLen:newLen])
|
||||
// bs = append(bs, d.r.readn()...)
|
||||
d.bdRead = false
|
||||
}
|
||||
d.bdRead = false
|
||||
return bs
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == cborBdNil || d.bd == cborBdUndefined {
|
||||
d.bdRead = false
|
||||
return nil
|
||||
}
|
||||
if d.bd == cborBdIndefiniteBytes || d.bd == cborBdIndefiniteString {
|
||||
if bs == nil {
|
||||
return d.decAppendIndefiniteBytes(nil)
|
||||
}
|
||||
return d.decAppendIndefiniteBytes(bs[:0])
|
||||
}
|
||||
clen := d.decLen()
|
||||
d.bdRead = false
|
||||
if zerocopy {
|
||||
if d.br {
|
||||
return d.r.readx(clen)
|
||||
} else if len(bs) == 0 {
|
||||
bs = d.b[:]
|
||||
}
|
||||
}
|
||||
return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs)
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) DecodeString() (s string) {
|
||||
return string(d.DecodeBytes(d.b[:], true))
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) DecodeStringAsBytes() (s []byte) {
|
||||
return d.DecodeBytes(d.b[:], true)
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
u := d.decUint()
|
||||
d.bdRead = false
|
||||
realxtag = u
|
||||
if ext == nil {
|
||||
re := rv.(*RawExt)
|
||||
re.Tag = realxtag
|
||||
d.d.decode(&re.Value)
|
||||
} else if xtag != realxtag {
|
||||
d.d.errorf("Wrong extension tag. Got %b. Expecting: %v", realxtag, xtag)
|
||||
return
|
||||
} else {
|
||||
var v interface{}
|
||||
d.d.decode(&v)
|
||||
ext.UpdateExt(rv, v)
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) DecodeNaked() {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
|
||||
n := d.d.n
|
||||
var decodeFurther bool
|
||||
|
||||
switch d.bd {
|
||||
case cborBdNil:
|
||||
n.v = valueTypeNil
|
||||
case cborBdFalse:
|
||||
n.v = valueTypeBool
|
||||
n.b = false
|
||||
case cborBdTrue:
|
||||
n.v = valueTypeBool
|
||||
n.b = true
|
||||
case cborBdFloat16, cborBdFloat32:
|
||||
n.v = valueTypeFloat
|
||||
n.f = d.DecodeFloat(true)
|
||||
case cborBdFloat64:
|
||||
n.v = valueTypeFloat
|
||||
n.f = d.DecodeFloat(false)
|
||||
case cborBdIndefiniteBytes:
|
||||
n.v = valueTypeBytes
|
||||
n.l = d.DecodeBytes(nil, false)
|
||||
case cborBdIndefiniteString:
|
||||
n.v = valueTypeString
|
||||
n.s = d.DecodeString()
|
||||
case cborBdIndefiniteArray:
|
||||
n.v = valueTypeArray
|
||||
decodeFurther = true
|
||||
case cborBdIndefiniteMap:
|
||||
n.v = valueTypeMap
|
||||
decodeFurther = true
|
||||
default:
|
||||
switch {
|
||||
case d.bd >= cborBaseUint && d.bd < cborBaseNegInt:
|
||||
if d.h.SignedInteger {
|
||||
n.v = valueTypeInt
|
||||
n.i = d.DecodeInt(64)
|
||||
} else {
|
||||
n.v = valueTypeUint
|
||||
n.u = d.DecodeUint(64)
|
||||
}
|
||||
case d.bd >= cborBaseNegInt && d.bd < cborBaseBytes:
|
||||
n.v = valueTypeInt
|
||||
n.i = d.DecodeInt(64)
|
||||
case d.bd >= cborBaseBytes && d.bd < cborBaseString:
|
||||
n.v = valueTypeBytes
|
||||
n.l = d.DecodeBytes(nil, false)
|
||||
case d.bd >= cborBaseString && d.bd < cborBaseArray:
|
||||
n.v = valueTypeString
|
||||
n.s = d.DecodeString()
|
||||
case d.bd >= cborBaseArray && d.bd < cborBaseMap:
|
||||
n.v = valueTypeArray
|
||||
decodeFurther = true
|
||||
case d.bd >= cborBaseMap && d.bd < cborBaseTag:
|
||||
n.v = valueTypeMap
|
||||
decodeFurther = true
|
||||
case d.bd >= cborBaseTag && d.bd < cborBaseSimple:
|
||||
n.v = valueTypeExt
|
||||
n.u = d.decUint()
|
||||
n.l = nil
|
||||
// d.bdRead = false
|
||||
// d.d.decode(&re.Value) // handled by decode itself.
|
||||
// decodeFurther = true
|
||||
default:
|
||||
d.d.errorf("decodeNaked: Unrecognized d.bd: 0x%x", d.bd)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !decodeFurther {
|
||||
d.bdRead = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
|
||||
// CborHandle is a Handle for the CBOR encoding format,
|
||||
// defined at http://tools.ietf.org/html/rfc7049 and documented further at http://cbor.io .
|
||||
//
|
||||
// CBOR is comprehensively supported, including support for:
|
||||
// - indefinite-length arrays/maps/bytes/strings
|
||||
// - (extension) tags in range 0..0xffff (0 .. 65535)
|
||||
// - half, single and double-precision floats
|
||||
// - all numbers (1, 2, 4 and 8-byte signed and unsigned integers)
|
||||
// - nil, true, false, ...
|
||||
// - arrays and maps, bytes and text strings
|
||||
//
|
||||
// None of the optional extensions (with tags) defined in the spec are supported out-of-the-box.
|
||||
// Users can implement them as needed (using SetExt), including spec-documented ones:
|
||||
// - timestamp, BigNum, BigFloat, Decimals, Encoded Text (e.g. URL, regexp, base64, MIME Message), etc.
|
||||
//
|
||||
// To encode with indefinite lengths (streaming), users will use
|
||||
// (Must)Encode methods of *Encoder, along with writing CborStreamXXX constants.
|
||||
//
|
||||
// For example, to encode "one-byte" as an indefinite length string:
|
||||
// var buf bytes.Buffer
|
||||
// e := NewEncoder(&buf, new(CborHandle))
|
||||
// buf.WriteByte(CborStreamString)
|
||||
// e.MustEncode("one-")
|
||||
// e.MustEncode("byte")
|
||||
// buf.WriteByte(CborStreamBreak)
|
||||
// encodedBytes := buf.Bytes()
|
||||
// var vv interface{}
|
||||
// NewDecoderBytes(buf.Bytes(), new(CborHandle)).MustDecode(&vv)
|
||||
// // Now, vv contains the same string "one-byte"
|
||||
//
|
||||
type CborHandle struct {
|
||||
binaryEncodingType
|
||||
noElemSeparators
|
||||
BasicHandle
|
||||
|
||||
// IndefiniteLength=true, means that we encode using indefinitelength
|
||||
IndefiniteLength bool
|
||||
}
|
||||
|
||||
func (h *CborHandle) SetInterfaceExt(rt reflect.Type, tag uint64, ext InterfaceExt) (err error) {
|
||||
return h.SetExt(rt, tag, &setExtWrapper{i: ext})
|
||||
}
|
||||
|
||||
func (h *CborHandle) newEncDriver(e *Encoder) encDriver {
|
||||
return &cborEncDriver{e: e, w: e.w, h: h}
|
||||
}
|
||||
|
||||
func (h *CborHandle) newDecDriver(d *Decoder) decDriver {
|
||||
return &cborDecDriver{d: d, h: h, r: d.r, br: d.bytes}
|
||||
}
|
||||
|
||||
func (e *cborEncDriver) reset() {
|
||||
e.w = e.e.w
|
||||
}
|
||||
|
||||
func (d *cborDecDriver) reset() {
|
||||
d.r, d.br = d.d.r, d.d.bytes
|
||||
d.bd, d.bdRead = 0, false
|
||||
}
|
||||
|
||||
var _ decDriver = (*cborDecDriver)(nil)
|
||||
var _ encDriver = (*cborEncDriver)(nil)
|
2520
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/decode.go
generated
vendored
Normal file
2520
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/decode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1414
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/encode.go
generated
vendored
Normal file
1414
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/encode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
33034
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/fast-path.generated.go
generated
vendored
Normal file
33034
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/fast-path.generated.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
35
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/fast-path.not.go
generated
vendored
Normal file
35
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/fast-path.not.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// +build notfastpath
|
||||
|
||||
package codec
|
||||
|
||||
import "reflect"
|
||||
|
||||
const fastpathEnabled = false
|
||||
|
||||
// The generated fast-path code is very large, and adds a few seconds to the build time.
|
||||
// This causes test execution, execution of small tools which use codec, etc
|
||||
// to take a long time.
|
||||
//
|
||||
// To mitigate, we now support the notfastpath tag.
|
||||
// This tag disables fastpath during build, allowing for faster build, test execution,
|
||||
// short-program runs, etc.
|
||||
|
||||
func fastpathDecodeTypeSwitch(iv interface{}, d *Decoder) bool { return false }
|
||||
func fastpathEncodeTypeSwitch(iv interface{}, e *Encoder) bool { return false }
|
||||
func fastpathEncodeTypeSwitchSlice(iv interface{}, e *Encoder) bool { return false }
|
||||
func fastpathEncodeTypeSwitchMap(iv interface{}, e *Encoder) bool { return false }
|
||||
func fastpathDecodeSetZeroTypeSwitch(iv interface{}, d *Decoder) bool { return false }
|
||||
|
||||
type fastpathT struct{}
|
||||
type fastpathE struct {
|
||||
rtid uintptr
|
||||
rt reflect.Type
|
||||
encfn func(*Encoder, *codecFnInfo, reflect.Value)
|
||||
decfn func(*Decoder, *codecFnInfo, reflect.Value)
|
||||
}
|
||||
type fastpathA [0]fastpathE
|
||||
|
||||
func (x fastpathA) index(rtid uintptr) int { return -1 }
|
||||
|
||||
var fastpathAV fastpathA
|
||||
var fastpathTV fastpathT
|
250
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen-helper.generated.go
generated
vendored
Normal file
250
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen-helper.generated.go
generated
vendored
Normal file
@ -0,0 +1,250 @@
|
||||
/* // +build ignore */
|
||||
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
// ************************************************************
|
||||
// DO NOT EDIT.
|
||||
// THIS FILE IS AUTO-GENERATED from gen-helper.go.tmpl
|
||||
// ************************************************************
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// GenVersion is the current version of codecgen.
|
||||
const GenVersion = 8
|
||||
|
||||
// This file is used to generate helper code for codecgen.
|
||||
// The values here i.e. genHelper(En|De)coder are not to be used directly by
|
||||
// library users. They WILL change continuously and without notice.
|
||||
//
|
||||
// To help enforce this, we create an unexported type with exported members.
|
||||
// The only way to get the type is via the one exported type that we control (somewhat).
|
||||
//
|
||||
// When static codecs are created for types, they will use this value
|
||||
// to perform encoding or decoding of primitives or known slice or map types.
|
||||
|
||||
// GenHelperEncoder is exported so that it can be used externally by codecgen.
|
||||
//
|
||||
// Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE.
|
||||
func GenHelperEncoder(e *Encoder) (genHelperEncoder, encDriver) {
|
||||
return genHelperEncoder{e: e}, e.e
|
||||
}
|
||||
|
||||
// GenHelperDecoder is exported so that it can be used externally by codecgen.
|
||||
//
|
||||
// Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE.
|
||||
func GenHelperDecoder(d *Decoder) (genHelperDecoder, decDriver) {
|
||||
return genHelperDecoder{d: d}, d.d
|
||||
}
|
||||
|
||||
// Library users: DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE.
|
||||
func BasicHandleDoNotUse(h Handle) *BasicHandle {
|
||||
return h.getBasicHandle()
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
type genHelperEncoder struct {
|
||||
e *Encoder
|
||||
F fastpathT
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
type genHelperDecoder struct {
|
||||
d *Decoder
|
||||
F fastpathT
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperEncoder) EncBasicHandle() *BasicHandle {
|
||||
return f.e.h
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperEncoder) EncBinary() bool {
|
||||
return f.e.cf.be // f.e.hh.isBinaryEncoding()
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperEncoder) EncFallback(iv interface{}) {
|
||||
// println(">>>>>>>>> EncFallback")
|
||||
// f.e.encodeI(iv, false, false)
|
||||
f.e.encodeValue(reflect.ValueOf(iv), nil, false)
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperEncoder) EncTextMarshal(iv encoding.TextMarshaler) {
|
||||
bs, fnerr := iv.MarshalText()
|
||||
f.e.marshal(bs, fnerr, false, c_UTF8)
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperEncoder) EncJSONMarshal(iv jsonMarshaler) {
|
||||
bs, fnerr := iv.MarshalJSON()
|
||||
f.e.marshal(bs, fnerr, true, c_UTF8)
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperEncoder) EncBinaryMarshal(iv encoding.BinaryMarshaler) {
|
||||
bs, fnerr := iv.MarshalBinary()
|
||||
f.e.marshal(bs, fnerr, false, c_RAW)
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperEncoder) EncRaw(iv Raw) {
|
||||
f.e.rawBytes(iv)
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperEncoder) TimeRtidIfBinc() uintptr {
|
||||
if _, ok := f.e.hh.(*BincHandle); ok {
|
||||
return timeTypId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperEncoder) IsJSONHandle() bool {
|
||||
return f.e.cf.js
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperEncoder) HasExtensions() bool {
|
||||
return len(f.e.h.extHandle) != 0
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperEncoder) EncExt(v interface{}) (r bool) {
|
||||
rt := reflect.TypeOf(v)
|
||||
if rt.Kind() == reflect.Ptr {
|
||||
rt = rt.Elem()
|
||||
}
|
||||
rtid := rt2id(rt)
|
||||
if xfFn := f.e.h.getExt(rtid); xfFn != nil {
|
||||
f.e.e.EncodeExt(v, xfFn.tag, xfFn.ext, f.e)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ---------------- DECODER FOLLOWS -----------------
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecBasicHandle() *BasicHandle {
|
||||
return f.d.h
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecBinary() bool {
|
||||
return f.d.be // f.d.hh.isBinaryEncoding()
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecSwallow() {
|
||||
f.d.swallow()
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecScratchBuffer() []byte {
|
||||
return f.d.b[:]
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecFallback(iv interface{}, chkPtr bool) {
|
||||
// println(">>>>>>>>> DecFallback")
|
||||
rv := reflect.ValueOf(iv)
|
||||
if chkPtr {
|
||||
rv = f.d.ensureDecodeable(rv)
|
||||
}
|
||||
f.d.decodeValue(rv, nil, false, false)
|
||||
// f.d.decodeValueFallback(rv)
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecSliceHelperStart() (decSliceHelper, int) {
|
||||
return f.d.decSliceHelperStart()
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecStructFieldNotFound(index int, name string) {
|
||||
f.d.structFieldNotFound(index, name)
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecArrayCannotExpand(sliceLen, streamLen int) {
|
||||
f.d.arrayCannotExpand(sliceLen, streamLen)
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecTextUnmarshal(tm encoding.TextUnmarshaler) {
|
||||
fnerr := tm.UnmarshalText(f.d.d.DecodeStringAsBytes())
|
||||
if fnerr != nil {
|
||||
panic(fnerr)
|
||||
}
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecJSONUnmarshal(tm jsonUnmarshaler) {
|
||||
// bs := f.dd.DecodeStringAsBytes()
|
||||
// grab the bytes to be read, as UnmarshalJSON needs the full JSON so as to unmarshal it itself.
|
||||
fnerr := tm.UnmarshalJSON(f.d.nextValueBytes())
|
||||
if fnerr != nil {
|
||||
panic(fnerr)
|
||||
}
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecBinaryUnmarshal(bm encoding.BinaryUnmarshaler) {
|
||||
fnerr := bm.UnmarshalBinary(f.d.d.DecodeBytes(nil, true))
|
||||
if fnerr != nil {
|
||||
panic(fnerr)
|
||||
}
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecRaw() []byte {
|
||||
return f.d.rawBytes()
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) TimeRtidIfBinc() uintptr {
|
||||
if _, ok := f.d.hh.(*BincHandle); ok {
|
||||
return timeTypId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) IsJSONHandle() bool {
|
||||
return f.d.js
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) HasExtensions() bool {
|
||||
return len(f.d.h.extHandle) != 0
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecExt(v interface{}) (r bool) {
|
||||
rt := reflect.TypeOf(v).Elem()
|
||||
rtid := rt2id(rt)
|
||||
if xfFn := f.d.h.getExt(rtid); xfFn != nil {
|
||||
f.d.d.DecodeExt(v, xfFn.tag, xfFn.ext)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) DecInferLen(clen, maxlen, unit int) (rvlen int) {
|
||||
return decInferLen(clen, maxlen, unit)
|
||||
}
|
||||
|
||||
// FOR USE BY CODECGEN ONLY. IT *WILL* CHANGE WITHOUT NOTICE. *DO NOT USE*
|
||||
func (f genHelperDecoder) StringView(v []byte) string {
|
||||
return stringView(v)
|
||||
}
|
132
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen.generated.go
generated
vendored
Normal file
132
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen.generated.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
// DO NOT EDIT. THIS FILE IS AUTO-GENERATED FROM gen-dec-(map|array).go.tmpl
|
||||
|
||||
const genDecMapTmpl = `
|
||||
{{var "v"}} := *{{ .Varname }}
|
||||
{{var "l"}} := r.ReadMapStart()
|
||||
{{var "bh"}} := z.DecBasicHandle()
|
||||
if {{var "v"}} == nil {
|
||||
{{var "rl"}} := z.DecInferLen({{var "l"}}, {{var "bh"}}.MaxInitLen, {{ .Size }})
|
||||
{{var "v"}} = make(map[{{ .KTyp }}]{{ .Typ }}, {{var "rl"}})
|
||||
*{{ .Varname }} = {{var "v"}}
|
||||
}
|
||||
var {{var "mk"}} {{ .KTyp }}
|
||||
var {{var "mv"}} {{ .Typ }}
|
||||
var {{var "mg"}}, {{var "mdn"}} {{if decElemKindPtr}}, {{var "ms"}}, {{var "mok"}}{{end}} bool
|
||||
if {{var "bh"}}.MapValueReset {
|
||||
{{if decElemKindPtr}}{{var "mg"}} = true
|
||||
{{else if decElemKindIntf}}if !{{var "bh"}}.InterfaceReset { {{var "mg"}} = true }
|
||||
{{else if not decElemKindImmutable}}{{var "mg"}} = true
|
||||
{{end}} }
|
||||
if {{var "l"}} != 0 {
|
||||
{{var "hl"}} := {{var "l"}} > 0
|
||||
for {{var "j"}} := 0; ({{var "hl"}} && {{var "j"}} < {{var "l"}}) || !({{var "hl"}} || r.CheckBreak()); {{var "j"}}++ {
|
||||
r.ReadMapElemKey() {{/* z.DecSendContainerState(codecSelfer_containerMapKey{{ .Sfx }}) */}}
|
||||
{{ $x := printf "%vmk%v" .TempVar .Rand }}{{ decLineVarK $x }}
|
||||
{{ if eq .KTyp "interface{}" }}{{/* // special case if a byte array. */}}if {{var "bv"}}, {{var "bok"}} := {{var "mk"}}.([]byte); {{var "bok"}} {
|
||||
{{var "mk"}} = string({{var "bv"}})
|
||||
}{{ end }}{{if decElemKindPtr}}
|
||||
{{var "ms"}} = true{{end}}
|
||||
if {{var "mg"}} {
|
||||
{{if decElemKindPtr}}{{var "mv"}}, {{var "mok"}} = {{var "v"}}[{{var "mk"}}]
|
||||
if {{var "mok"}} {
|
||||
{{var "ms"}} = false
|
||||
} {{else}}{{var "mv"}} = {{var "v"}}[{{var "mk"}}] {{end}}
|
||||
} {{if not decElemKindImmutable}}else { {{var "mv"}} = {{decElemZero}} }{{end}}
|
||||
r.ReadMapElemValue() {{/* z.DecSendContainerState(codecSelfer_containerMapValue{{ .Sfx }}) */}}
|
||||
{{var "mdn"}} = false
|
||||
{{ $x := printf "%vmv%v" .TempVar .Rand }}{{ $y := printf "%vmdn%v" .TempVar .Rand }}{{ decLineVar $x $y }}
|
||||
if {{var "mdn"}} {
|
||||
if {{ var "bh" }}.DeleteOnNilMapValue { delete({{var "v"}}, {{var "mk"}}) } else { {{var "v"}}[{{var "mk"}}] = {{decElemZero}} }
|
||||
} else if {{if decElemKindPtr}} {{var "ms"}} && {{end}} {{var "v"}} != nil {
|
||||
{{var "v"}}[{{var "mk"}}] = {{var "mv"}}
|
||||
}
|
||||
}
|
||||
} // else len==0: TODO: Should we clear map entries?
|
||||
r.ReadMapEnd() {{/* z.DecSendContainerState(codecSelfer_containerMapEnd{{ .Sfx }}) */}}
|
||||
`
|
||||
|
||||
const genDecListTmpl = `
|
||||
{{var "v"}} := {{if not isArray}}*{{end}}{{ .Varname }}
|
||||
{{var "h"}}, {{var "l"}} := z.DecSliceHelperStart() {{/* // helper, containerLenS */}}{{if not isArray}}
|
||||
var {{var "c"}} bool {{/* // changed */}}
|
||||
_ = {{var "c"}}{{end}}
|
||||
if {{var "l"}} == 0 {
|
||||
{{if isSlice }}if {{var "v"}} == nil {
|
||||
{{var "v"}} = []{{ .Typ }}{}
|
||||
{{var "c"}} = true
|
||||
} else if len({{var "v"}}) != 0 {
|
||||
{{var "v"}} = {{var "v"}}[:0]
|
||||
{{var "c"}} = true
|
||||
} {{end}} {{if isChan }}if {{var "v"}} == nil {
|
||||
{{var "v"}} = make({{ .CTyp }}, 0)
|
||||
{{var "c"}} = true
|
||||
} {{end}}
|
||||
} else {
|
||||
{{var "hl"}} := {{var "l"}} > 0
|
||||
var {{var "rl"}} int; _ = {{var "rl"}}
|
||||
{{if isSlice }} if {{var "hl"}} {
|
||||
if {{var "l"}} > cap({{var "v"}}) {
|
||||
{{var "rl"}} = z.DecInferLen({{var "l"}}, z.DecBasicHandle().MaxInitLen, {{ .Size }})
|
||||
if {{var "rl"}} <= cap({{var "v"}}) {
|
||||
{{var "v"}} = {{var "v"}}[:{{var "rl"}}]
|
||||
} else {
|
||||
{{var "v"}} = make([]{{ .Typ }}, {{var "rl"}})
|
||||
}
|
||||
{{var "c"}} = true
|
||||
} else if {{var "l"}} != len({{var "v"}}) {
|
||||
{{var "v"}} = {{var "v"}}[:{{var "l"}}]
|
||||
{{var "c"}} = true
|
||||
}
|
||||
} {{end}}
|
||||
var {{var "j"}} int
|
||||
// var {{var "dn"}} bool
|
||||
for ; ({{var "hl"}} && {{var "j"}} < {{var "l"}}) || !({{var "hl"}} || r.CheckBreak()); {{var "j"}}++ {
|
||||
{{if not isArray}} if {{var "j"}} == 0 && len({{var "v"}}) == 0 {
|
||||
if {{var "hl"}} {
|
||||
{{var "rl"}} = z.DecInferLen({{var "l"}}, z.DecBasicHandle().MaxInitLen, {{ .Size }})
|
||||
} else {
|
||||
{{var "rl"}} = 8
|
||||
}
|
||||
{{var "v"}} = make([]{{ .Typ }}, {{var "rl"}})
|
||||
{{var "c"}} = true
|
||||
}{{end}}
|
||||
{{var "h"}}.ElemContainerState({{var "j"}})
|
||||
// {{var "dn"}} = r.TryDecodeAsNil()
|
||||
{{if isChan}}{{ $x := printf "%[1]vv%[2]v" .TempVar .Rand }}var {{var $x}} {{ .Typ }}
|
||||
{{ decLineVar $x }}
|
||||
{{var "v"}} <- {{ $x }}
|
||||
{{else}}
|
||||
// if indefinite, etc, then expand the slice if necessary
|
||||
var {{var "db"}} bool
|
||||
if {{var "j"}} >= len({{var "v"}}) {
|
||||
{{if isSlice }} {{var "v"}} = append({{var "v"}}, {{ zero }}); {{var "c"}} = true
|
||||
{{else}} z.DecArrayCannotExpand(len(v), {{var "j"}}+1); {{var "db"}} = true
|
||||
{{end}}
|
||||
}
|
||||
if {{var "db"}} {
|
||||
z.DecSwallow()
|
||||
} else {
|
||||
{{ $x := printf "%[1]vv%[2]v[%[1]vj%[2]v]" .TempVar .Rand }}{{ decLineVar $x }}
|
||||
}
|
||||
{{end}}
|
||||
}
|
||||
{{if isSlice}} if {{var "j"}} < len({{var "v"}}) {
|
||||
{{var "v"}} = {{var "v"}}[:{{var "j"}}]
|
||||
{{var "c"}} = true
|
||||
} else if {{var "j"}} == 0 && {{var "v"}} == nil {
|
||||
{{var "v"}} = make([]{{ .Typ }}, 0)
|
||||
{{var "c"}} = true
|
||||
} {{end}}
|
||||
}
|
||||
{{var "h"}}.End()
|
||||
{{if not isArray }}if {{var "c"}} {
|
||||
*{{ .Varname }} = {{var "v"}}
|
||||
}{{end}}
|
||||
|
||||
`
|
||||
|
2014
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen.go
generated
vendored
Normal file
2014
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/gen.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
14
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_arrayof_gte_go15.go
generated
vendored
Normal file
14
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_arrayof_gte_go15.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package codec
|
||||
|
||||
import "reflect"
|
||||
|
||||
const reflectArrayOfSupported = true
|
||||
|
||||
func reflectArrayOf(count int, elem reflect.Type) reflect.Type {
|
||||
return reflect.ArrayOf(count, elem)
|
||||
}
|
14
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_arrayof_lt_go15.go
generated
vendored
Normal file
14
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_arrayof_lt_go15.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package codec
|
||||
|
||||
import "reflect"
|
||||
|
||||
const reflectArrayOfSupported = false
|
||||
|
||||
func reflectArrayOf(count int, elem reflect.Type) reflect.Type {
|
||||
panic("codec: reflect.ArrayOf unsupported in this go version")
|
||||
}
|
15
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_makemap_gte_go19.go
generated
vendored
Normal file
15
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_makemap_gte_go19.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
// +build go1.9
|
||||
|
||||
package codec
|
||||
|
||||
import "reflect"
|
||||
|
||||
func makeMapReflect(t reflect.Type, size int) reflect.Value {
|
||||
if size < 0 {
|
||||
return reflect.MakeMapWithSize(t, 4)
|
||||
}
|
||||
return reflect.MakeMapWithSize(t, size)
|
||||
}
|
12
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_makemap_lt_go19.go
generated
vendored
Normal file
12
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_makemap_lt_go19.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
// +build !go1.9
|
||||
|
||||
package codec
|
||||
|
||||
import "reflect"
|
||||
|
||||
func makeMapReflect(t reflect.Type, size int) reflect.Value {
|
||||
return reflect.MakeMap(t)
|
||||
}
|
17
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_unsupported_lt_go14.go
generated
vendored
Normal file
17
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_unsupported_lt_go14.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
// +build !go1.4
|
||||
|
||||
package codec
|
||||
|
||||
// This codec package will only work for go1.4 and above.
|
||||
// This is for the following reasons:
|
||||
// - go 1.4 was released in 2014
|
||||
// - go runtime is written fully in go
|
||||
// - interface only holds pointers
|
||||
// - reflect.Value is stabilized as 3 words
|
||||
|
||||
func init() {
|
||||
panic("codec: go 1.3 and below are not supported")
|
||||
}
|
10
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go15.go
generated
vendored
Normal file
10
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go15.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
// +build go1.5,!go1.6
|
||||
|
||||
package codec
|
||||
|
||||
import "os"
|
||||
|
||||
var genCheckVendor = os.Getenv("GO15VENDOREXPERIMENT") == "1"
|
10
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go16.go
generated
vendored
Normal file
10
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go16.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
// +build go1.6,!go1.7
|
||||
|
||||
package codec
|
||||
|
||||
import "os"
|
||||
|
||||
var genCheckVendor = os.Getenv("GO15VENDOREXPERIMENT") != "0"
|
8
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_gte_go17.go
generated
vendored
Normal file
8
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_gte_go17.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package codec
|
||||
|
||||
const genCheckVendor = true
|
8
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_lt_go15.go
generated
vendored
Normal file
8
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/goversion_vendor_lt_go15.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package codec
|
||||
|
||||
var genCheckVendor = false
|
1944
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper.go
generated
vendored
Normal file
1944
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
221
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper_internal.go
generated
vendored
Normal file
221
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper_internal.go
generated
vendored
Normal file
@ -0,0 +1,221 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
// All non-std package dependencies live in this file,
|
||||
// so porting to different environment is easy (just update functions).
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func panicValToErr(panicVal interface{}, err *error) {
|
||||
if panicVal == nil {
|
||||
return
|
||||
}
|
||||
// case nil
|
||||
switch xerr := panicVal.(type) {
|
||||
case error:
|
||||
*err = xerr
|
||||
case string:
|
||||
*err = errors.New(xerr)
|
||||
default:
|
||||
*err = fmt.Errorf("%v", panicVal)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func hIsEmptyValue(v reflect.Value, deref, checkStruct bool) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Invalid:
|
||||
return true
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
if deref {
|
||||
if v.IsNil() {
|
||||
return true
|
||||
}
|
||||
return hIsEmptyValue(v.Elem(), deref, checkStruct)
|
||||
} else {
|
||||
return v.IsNil()
|
||||
}
|
||||
case reflect.Struct:
|
||||
if !checkStruct {
|
||||
return false
|
||||
}
|
||||
// return true if all fields are empty. else return false.
|
||||
// we cannot use equality check, because some fields may be maps/slices/etc
|
||||
// and consequently the structs are not comparable.
|
||||
// return v.Interface() == reflect.Zero(v.Type()).Interface()
|
||||
for i, n := 0, v.NumField(); i < n; i++ {
|
||||
if !hIsEmptyValue(v.Field(i), deref, checkStruct) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isEmptyValue(v reflect.Value, deref, checkStruct bool) bool {
|
||||
return hIsEmptyValue(v, deref, checkStruct)
|
||||
}
|
||||
|
||||
func pruneSignExt(v []byte, pos bool) (n int) {
|
||||
if len(v) < 2 {
|
||||
} else if pos && v[0] == 0 {
|
||||
for ; v[n] == 0 && n+1 < len(v) && (v[n+1]&(1<<7) == 0); n++ {
|
||||
}
|
||||
} else if !pos && v[0] == 0xff {
|
||||
for ; v[n] == 0xff && n+1 < len(v) && (v[n+1]&(1<<7) != 0); n++ {
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func implementsIntf(typ, iTyp reflect.Type) (success bool, indir int8) {
|
||||
if typ == nil {
|
||||
return
|
||||
}
|
||||
rt := typ
|
||||
// The type might be a pointer and we need to keep
|
||||
// dereferencing to the base type until we find an implementation.
|
||||
for {
|
||||
if rt.Implements(iTyp) {
|
||||
return true, indir
|
||||
}
|
||||
if p := rt; p.Kind() == reflect.Ptr {
|
||||
indir++
|
||||
if indir >= math.MaxInt8 { // insane number of indirections
|
||||
return false, 0
|
||||
}
|
||||
rt = p.Elem()
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
// No luck yet, but if this is a base type (non-pointer), the pointer might satisfy.
|
||||
if typ.Kind() != reflect.Ptr {
|
||||
// Not a pointer, but does the pointer work?
|
||||
if reflect.PtrTo(typ).Implements(iTyp) {
|
||||
return true, -1
|
||||
}
|
||||
}
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// validate that this function is correct ...
|
||||
// culled from OGRE (Object-Oriented Graphics Rendering Engine)
|
||||
// function: halfToFloatI (http://stderr.org/doc/ogre-doc/api/OgreBitwise_8h-source.html)
|
||||
func halfFloatToFloatBits(yy uint16) (d uint32) {
|
||||
y := uint32(yy)
|
||||
s := (y >> 15) & 0x01
|
||||
e := (y >> 10) & 0x1f
|
||||
m := y & 0x03ff
|
||||
|
||||
if e == 0 {
|
||||
if m == 0 { // plu or minus 0
|
||||
return s << 31
|
||||
} else { // Denormalized number -- renormalize it
|
||||
for (m & 0x00000400) == 0 {
|
||||
m <<= 1
|
||||
e -= 1
|
||||
}
|
||||
e += 1
|
||||
const zz uint32 = 0x0400
|
||||
m &= ^zz
|
||||
}
|
||||
} else if e == 31 {
|
||||
if m == 0 { // Inf
|
||||
return (s << 31) | 0x7f800000
|
||||
} else { // NaN
|
||||
return (s << 31) | 0x7f800000 | (m << 13)
|
||||
}
|
||||
}
|
||||
e = e + (127 - 15)
|
||||
m = m << 13
|
||||
return (s << 31) | (e << 23) | m
|
||||
}
|
||||
|
||||
// GrowCap will return a new capacity for a slice, given the following:
|
||||
// - oldCap: current capacity
|
||||
// - unit: in-memory size of an element
|
||||
// - num: number of elements to add
|
||||
func growCap(oldCap, unit, num int) (newCap int) {
|
||||
// appendslice logic (if cap < 1024, *2, else *1.25):
|
||||
// leads to many copy calls, especially when copying bytes.
|
||||
// bytes.Buffer model (2*cap + n): much better for bytes.
|
||||
// smarter way is to take the byte-size of the appended element(type) into account
|
||||
|
||||
// maintain 3 thresholds:
|
||||
// t1: if cap <= t1, newcap = 2x
|
||||
// t2: if cap <= t2, newcap = 1.75x
|
||||
// t3: if cap <= t3, newcap = 1.5x
|
||||
// else newcap = 1.25x
|
||||
//
|
||||
// t1, t2, t3 >= 1024 always.
|
||||
// i.e. if unit size >= 16, then always do 2x or 1.25x (ie t1, t2, t3 are all same)
|
||||
//
|
||||
// With this, appending for bytes increase by:
|
||||
// 100% up to 4K
|
||||
// 75% up to 8K
|
||||
// 50% up to 16K
|
||||
// 25% beyond that
|
||||
|
||||
// unit can be 0 e.g. for struct{}{}; handle that appropriately
|
||||
var t1, t2, t3 int // thresholds
|
||||
if unit <= 1 {
|
||||
t1, t2, t3 = 4*1024, 8*1024, 16*1024
|
||||
} else if unit < 16 {
|
||||
t3 = 16 / unit * 1024
|
||||
t1 = t3 * 1 / 4
|
||||
t2 = t3 * 2 / 4
|
||||
} else {
|
||||
t1, t2, t3 = 1024, 1024, 1024
|
||||
}
|
||||
|
||||
var x int // temporary variable
|
||||
|
||||
// x is multiplier here: one of 5, 6, 7 or 8; incr of 25%, 50%, 75% or 100% respectively
|
||||
if oldCap <= t1 { // [0,t1]
|
||||
x = 8
|
||||
} else if oldCap > t3 { // (t3,infinity]
|
||||
x = 5
|
||||
} else if oldCap <= t2 { // (t1,t2]
|
||||
x = 7
|
||||
} else { // (t2,t3]
|
||||
x = 6
|
||||
}
|
||||
newCap = x * oldCap / 4
|
||||
|
||||
if num > 0 {
|
||||
newCap += num
|
||||
}
|
||||
|
||||
// ensure newCap is a multiple of 64 (if it is > 64) or 16.
|
||||
if newCap > 64 {
|
||||
if x = newCap % 64; x != 0 {
|
||||
x = newCap / 64
|
||||
newCap = 64 * (x + 1)
|
||||
}
|
||||
} else {
|
||||
if x = newCap % 16; x != 0 {
|
||||
x = newCap / 16
|
||||
newCap = 16 * (x + 1)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
156
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper_not_unsafe.go
generated
vendored
Normal file
156
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper_not_unsafe.go
generated
vendored
Normal file
@ -0,0 +1,156 @@
|
||||
// +build !go1.7 safe appengine
|
||||
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// stringView returns a view of the []byte as a string.
|
||||
// In unsafe mode, it doesn't incur allocation and copying caused by conversion.
|
||||
// In regular safe mode, it is an allocation and copy.
|
||||
//
|
||||
// Usage: Always maintain a reference to v while result of this call is in use,
|
||||
// and call keepAlive4BytesView(v) at point where done with view.
|
||||
func stringView(v []byte) string {
|
||||
return string(v)
|
||||
}
|
||||
|
||||
// bytesView returns a view of the string as a []byte.
|
||||
// In unsafe mode, it doesn't incur allocation and copying caused by conversion.
|
||||
// In regular safe mode, it is an allocation and copy.
|
||||
//
|
||||
// Usage: Always maintain a reference to v while result of this call is in use,
|
||||
// and call keepAlive4BytesView(v) at point where done with view.
|
||||
func bytesView(v string) []byte {
|
||||
return []byte(v)
|
||||
}
|
||||
|
||||
func definitelyNil(v interface{}) bool {
|
||||
return false
|
||||
// rv := reflect.ValueOf(v)
|
||||
// switch rv.Kind() {
|
||||
// case reflect.Invalid:
|
||||
// return true
|
||||
// case reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Slice, reflect.Map, reflect.Func:
|
||||
// return rv.IsNil()
|
||||
// default:
|
||||
// return false
|
||||
// }
|
||||
}
|
||||
|
||||
// // keepAlive4BytesView maintains a reference to the input parameter for bytesView.
|
||||
// //
|
||||
// // Usage: call this at point where done with the bytes view.
|
||||
// func keepAlive4BytesView(v string) {}
|
||||
|
||||
// // keepAlive4BytesView maintains a reference to the input parameter for stringView.
|
||||
// //
|
||||
// // Usage: call this at point where done with the string view.
|
||||
// func keepAlive4StringView(v []byte) {}
|
||||
|
||||
func rv2i(rv reflect.Value) interface{} {
|
||||
return rv.Interface()
|
||||
}
|
||||
|
||||
func rt2id(rt reflect.Type) uintptr {
|
||||
return reflect.ValueOf(rt).Pointer()
|
||||
}
|
||||
|
||||
func rv2rtid(rv reflect.Value) uintptr {
|
||||
return reflect.ValueOf(rv.Type()).Pointer()
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// type ptrToRvMap struct{}
|
||||
|
||||
// func (_ *ptrToRvMap) init() {}
|
||||
// func (_ *ptrToRvMap) get(i interface{}) reflect.Value {
|
||||
// return reflect.ValueOf(i).Elem()
|
||||
// }
|
||||
|
||||
// --------------------------
|
||||
type atomicTypeInfoSlice struct {
|
||||
v atomic.Value
|
||||
}
|
||||
|
||||
func (x *atomicTypeInfoSlice) load() *[]rtid2ti {
|
||||
i := x.v.Load()
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
return i.(*[]rtid2ti)
|
||||
}
|
||||
|
||||
func (x *atomicTypeInfoSlice) store(p *[]rtid2ti) {
|
||||
x.v.Store(p)
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
func (d *Decoder) raw(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetBytes(d.rawBytes())
|
||||
}
|
||||
|
||||
func (d *Decoder) kString(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetString(d.d.DecodeString())
|
||||
}
|
||||
|
||||
func (d *Decoder) kBool(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetBool(d.d.DecodeBool())
|
||||
}
|
||||
|
||||
func (d *Decoder) kFloat32(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetFloat(d.d.DecodeFloat(true))
|
||||
}
|
||||
|
||||
func (d *Decoder) kFloat64(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetFloat(d.d.DecodeFloat(false))
|
||||
}
|
||||
|
||||
func (d *Decoder) kInt(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetInt(d.d.DecodeInt(intBitsize))
|
||||
}
|
||||
|
||||
func (d *Decoder) kInt8(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetInt(d.d.DecodeInt(8))
|
||||
}
|
||||
|
||||
func (d *Decoder) kInt16(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetInt(d.d.DecodeInt(16))
|
||||
}
|
||||
|
||||
func (d *Decoder) kInt32(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetInt(d.d.DecodeInt(32))
|
||||
}
|
||||
|
||||
func (d *Decoder) kInt64(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetInt(d.d.DecodeInt(64))
|
||||
}
|
||||
|
||||
func (d *Decoder) kUint(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetUint(d.d.DecodeUint(uintBitsize))
|
||||
}
|
||||
|
||||
func (d *Decoder) kUintptr(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetUint(d.d.DecodeUint(uintBitsize))
|
||||
}
|
||||
|
||||
func (d *Decoder) kUint8(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetUint(d.d.DecodeUint(8))
|
||||
}
|
||||
|
||||
func (d *Decoder) kUint16(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetUint(d.d.DecodeUint(16))
|
||||
}
|
||||
|
||||
func (d *Decoder) kUint32(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetUint(d.d.DecodeUint(32))
|
||||
}
|
||||
|
||||
func (d *Decoder) kUint64(f *codecFnInfo, rv reflect.Value) {
|
||||
rv.SetUint(d.d.DecodeUint(64))
|
||||
}
|
418
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper_unsafe.go
generated
vendored
Normal file
418
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/helper_unsafe.go
generated
vendored
Normal file
@ -0,0 +1,418 @@
|
||||
// +build !safe
|
||||
// +build !appengine
|
||||
// +build go1.7
|
||||
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// This file has unsafe variants of some helper methods.
|
||||
// NOTE: See helper_not_unsafe.go for the usage information.
|
||||
|
||||
// var zeroRTv [4]uintptr
|
||||
|
||||
const unsafeFlagIndir = 1 << 7 // keep in sync with GO_ROOT/src/reflect/value.go
|
||||
|
||||
type unsafeString struct {
|
||||
Data uintptr
|
||||
Len int
|
||||
}
|
||||
|
||||
type unsafeSlice struct {
|
||||
Data uintptr
|
||||
Len int
|
||||
Cap int
|
||||
}
|
||||
|
||||
type unsafeIntf struct {
|
||||
typ unsafe.Pointer
|
||||
word unsafe.Pointer
|
||||
}
|
||||
|
||||
type unsafeReflectValue struct {
|
||||
typ unsafe.Pointer
|
||||
ptr unsafe.Pointer
|
||||
flag uintptr
|
||||
}
|
||||
|
||||
func stringView(v []byte) string {
|
||||
if len(v) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
bx := (*unsafeSlice)(unsafe.Pointer(&v))
|
||||
sx := unsafeString{bx.Data, bx.Len}
|
||||
return *(*string)(unsafe.Pointer(&sx))
|
||||
}
|
||||
|
||||
func bytesView(v string) []byte {
|
||||
if len(v) == 0 {
|
||||
return zeroByteSlice
|
||||
}
|
||||
|
||||
sx := (*unsafeString)(unsafe.Pointer(&v))
|
||||
bx := unsafeSlice{sx.Data, sx.Len, sx.Len}
|
||||
return *(*[]byte)(unsafe.Pointer(&bx))
|
||||
}
|
||||
|
||||
func definitelyNil(v interface{}) bool {
|
||||
return (*unsafeIntf)(unsafe.Pointer(&v)).word == nil
|
||||
}
|
||||
|
||||
// func keepAlive4BytesView(v string) {
|
||||
// runtime.KeepAlive(v)
|
||||
// }
|
||||
|
||||
// func keepAlive4StringView(v []byte) {
|
||||
// runtime.KeepAlive(v)
|
||||
// }
|
||||
|
||||
// TODO: consider a more generally-known optimization for reflect.Value ==> Interface
|
||||
//
|
||||
// Currently, we use this fragile method that taps into implememtation details from
|
||||
// the source go stdlib reflect/value.go,
|
||||
// and trims the implementation.
|
||||
func rv2i(rv reflect.Value) interface{} {
|
||||
if false {
|
||||
return rv.Interface()
|
||||
}
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
// references that are single-words (map, ptr) may be double-referenced as flagIndir
|
||||
kk := urv.flag & (1<<5 - 1)
|
||||
if (kk == uintptr(reflect.Map) || kk == uintptr(reflect.Ptr)) && urv.flag&unsafeFlagIndir != 0 {
|
||||
return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ}))
|
||||
}
|
||||
return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ}))
|
||||
}
|
||||
|
||||
func rt2id(rt reflect.Type) uintptr {
|
||||
return uintptr(((*unsafeIntf)(unsafe.Pointer(&rt))).word)
|
||||
}
|
||||
|
||||
func rv2rtid(rv reflect.Value) uintptr {
|
||||
return uintptr((*unsafeReflectValue)(unsafe.Pointer(&rv)).typ)
|
||||
}
|
||||
|
||||
// func rv0t(rt reflect.Type) reflect.Value {
|
||||
// ut := (*unsafeIntf)(unsafe.Pointer(&rt))
|
||||
// // we need to determine whether ifaceIndir, and then whether to just pass 0 as the ptr
|
||||
// uv := unsafeReflectValue{ut.word, &zeroRTv, flag(rt.Kind())}
|
||||
// return *(*reflect.Value)(unsafe.Pointer(&uv})
|
||||
// }
|
||||
|
||||
// --------------------------
|
||||
type atomicTypeInfoSlice struct {
|
||||
v unsafe.Pointer
|
||||
}
|
||||
|
||||
func (x *atomicTypeInfoSlice) load() *[]rtid2ti {
|
||||
return (*[]rtid2ti)(atomic.LoadPointer(&x.v))
|
||||
}
|
||||
|
||||
func (x *atomicTypeInfoSlice) store(p *[]rtid2ti) {
|
||||
atomic.StorePointer(&x.v, unsafe.Pointer(p))
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
func (d *Decoder) raw(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
// if urv.flag&unsafeFlagIndir != 0 {
|
||||
// urv.ptr = *(*unsafe.Pointer)(urv.ptr)
|
||||
// }
|
||||
*(*[]byte)(urv.ptr) = d.rawBytes()
|
||||
}
|
||||
|
||||
func (d *Decoder) kString(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*string)(urv.ptr) = d.d.DecodeString()
|
||||
}
|
||||
|
||||
func (d *Decoder) kBool(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*bool)(urv.ptr) = d.d.DecodeBool()
|
||||
}
|
||||
|
||||
func (d *Decoder) kFloat32(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*float32)(urv.ptr) = float32(d.d.DecodeFloat(true))
|
||||
}
|
||||
|
||||
func (d *Decoder) kFloat64(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*float64)(urv.ptr) = d.d.DecodeFloat(false)
|
||||
}
|
||||
|
||||
func (d *Decoder) kInt(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*int)(urv.ptr) = int(d.d.DecodeInt(intBitsize))
|
||||
}
|
||||
|
||||
func (d *Decoder) kInt8(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*int8)(urv.ptr) = int8(d.d.DecodeInt(8))
|
||||
}
|
||||
|
||||
func (d *Decoder) kInt16(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*int16)(urv.ptr) = int16(d.d.DecodeInt(16))
|
||||
}
|
||||
|
||||
func (d *Decoder) kInt32(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*int32)(urv.ptr) = int32(d.d.DecodeInt(32))
|
||||
}
|
||||
|
||||
func (d *Decoder) kInt64(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*int64)(urv.ptr) = d.d.DecodeInt(64)
|
||||
}
|
||||
|
||||
func (d *Decoder) kUint(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*uint)(urv.ptr) = uint(d.d.DecodeUint(uintBitsize))
|
||||
}
|
||||
|
||||
func (d *Decoder) kUintptr(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*uintptr)(urv.ptr) = uintptr(d.d.DecodeUint(uintBitsize))
|
||||
}
|
||||
|
||||
func (d *Decoder) kUint8(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*uint8)(urv.ptr) = uint8(d.d.DecodeUint(8))
|
||||
}
|
||||
|
||||
func (d *Decoder) kUint16(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*uint16)(urv.ptr) = uint16(d.d.DecodeUint(16))
|
||||
}
|
||||
|
||||
func (d *Decoder) kUint32(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*uint32)(urv.ptr) = uint32(d.d.DecodeUint(32))
|
||||
}
|
||||
|
||||
func (d *Decoder) kUint64(f *codecFnInfo, rv reflect.Value) {
|
||||
urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
*(*uint64)(urv.ptr) = d.d.DecodeUint(64)
|
||||
}
|
||||
|
||||
// ------------
|
||||
|
||||
// func rt2id(rt reflect.Type) uintptr {
|
||||
// return uintptr(((*unsafeIntf)(unsafe.Pointer(&rt))).word)
|
||||
// // var i interface{} = rt
|
||||
// // // ui := (*unsafeIntf)(unsafe.Pointer(&i))
|
||||
// // return ((*unsafeIntf)(unsafe.Pointer(&i))).word
|
||||
// }
|
||||
|
||||
// func rv2i(rv reflect.Value) interface{} {
|
||||
// urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
// // non-reference type: already indir
|
||||
// // reference type: depend on flagIndir property ('cos maybe was double-referenced)
|
||||
// // const (unsafeRvFlagKindMask = 1<<5 - 1 , unsafeRvFlagIndir = 1 << 7 )
|
||||
// // rvk := reflect.Kind(urv.flag & (1<<5 - 1))
|
||||
// // if (rvk == reflect.Chan ||
|
||||
// // rvk == reflect.Func ||
|
||||
// // rvk == reflect.Interface ||
|
||||
// // rvk == reflect.Map ||
|
||||
// // rvk == reflect.Ptr ||
|
||||
// // rvk == reflect.UnsafePointer) && urv.flag&(1<<8) != 0 {
|
||||
// // fmt.Printf(">>>>> ---- double indirect reference: %v, %v\n", rvk, rv.Type())
|
||||
// // return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ}))
|
||||
// // }
|
||||
// if urv.flag&(1<<5-1) == uintptr(reflect.Map) && urv.flag&(1<<7) != 0 {
|
||||
// // fmt.Printf(">>>>> ---- double indirect reference: %v, %v\n", rvk, rv.Type())
|
||||
// return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ}))
|
||||
// }
|
||||
// // fmt.Printf(">>>>> ++++ direct reference: %v, %v\n", rvk, rv.Type())
|
||||
// return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ}))
|
||||
// }
|
||||
|
||||
// const (
|
||||
// unsafeRvFlagKindMask = 1<<5 - 1
|
||||
// unsafeRvKindDirectIface = 1 << 5
|
||||
// unsafeRvFlagIndir = 1 << 7
|
||||
// unsafeRvFlagAddr = 1 << 8
|
||||
// unsafeRvFlagMethod = 1 << 9
|
||||
|
||||
// _USE_RV_INTERFACE bool = false
|
||||
// _UNSAFE_RV_DEBUG = true
|
||||
// )
|
||||
|
||||
// type unsafeRtype struct {
|
||||
// _ [2]uintptr
|
||||
// _ uint32
|
||||
// _ uint8
|
||||
// _ uint8
|
||||
// _ uint8
|
||||
// kind uint8
|
||||
// _ [2]uintptr
|
||||
// _ int32
|
||||
// }
|
||||
|
||||
// func _rv2i(rv reflect.Value) interface{} {
|
||||
// // Note: From use,
|
||||
// // - it's never an interface
|
||||
// // - the only calls here are for ifaceIndir types.
|
||||
// // (though that conditional is wrong)
|
||||
// // To know for sure, we need the value of t.kind (which is not exposed).
|
||||
// //
|
||||
// // Need to validate the path: type is indirect ==> only value is indirect ==> default (value is direct)
|
||||
// // - Type indirect, Value indirect: ==> numbers, boolean, slice, struct, array, string
|
||||
// // - Type Direct, Value indirect: ==> map???
|
||||
// // - Type Direct, Value direct: ==> pointers, unsafe.Pointer, func, chan, map
|
||||
// //
|
||||
// // TRANSLATES TO:
|
||||
// // if typeIndirect { } else if valueIndirect { } else { }
|
||||
// //
|
||||
// // Since we don't deal with funcs, then "flagNethod" is unset, and can be ignored.
|
||||
|
||||
// if _USE_RV_INTERFACE {
|
||||
// return rv.Interface()
|
||||
// }
|
||||
// urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
|
||||
// // if urv.flag&unsafeRvFlagMethod != 0 || urv.flag&unsafeRvFlagKindMask == uintptr(reflect.Interface) {
|
||||
// // println("***** IS flag method or interface: delegating to rv.Interface()")
|
||||
// // return rv.Interface()
|
||||
// // }
|
||||
|
||||
// // if urv.flag&unsafeRvFlagKindMask == uintptr(reflect.Interface) {
|
||||
// // println("***** IS Interface: delegate to rv.Interface")
|
||||
// // return rv.Interface()
|
||||
// // }
|
||||
// // if urv.flag&unsafeRvFlagKindMask&unsafeRvKindDirectIface == 0 {
|
||||
// // if urv.flag&unsafeRvFlagAddr == 0 {
|
||||
// // println("***** IS ifaceIndir typ")
|
||||
// // // ui := unsafeIntf{word: urv.ptr, typ: urv.typ}
|
||||
// // // return *(*interface{})(unsafe.Pointer(&ui))
|
||||
// // // return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ}))
|
||||
// // }
|
||||
// // } else if urv.flag&unsafeRvFlagIndir != 0 {
|
||||
// // println("***** IS flagindir")
|
||||
// // // return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ}))
|
||||
// // } else {
|
||||
// // println("***** NOT flagindir")
|
||||
// // return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ}))
|
||||
// // }
|
||||
// // println("***** default: delegate to rv.Interface")
|
||||
|
||||
// urt := (*unsafeRtype)(unsafe.Pointer(urv.typ))
|
||||
// if _UNSAFE_RV_DEBUG {
|
||||
// fmt.Printf(">>>> start: %v: ", rv.Type())
|
||||
// fmt.Printf("%v - %v\n", *urv, *urt)
|
||||
// }
|
||||
// if urt.kind&unsafeRvKindDirectIface == 0 {
|
||||
// if _UNSAFE_RV_DEBUG {
|
||||
// fmt.Printf("**** +ifaceIndir type: %v\n", rv.Type())
|
||||
// }
|
||||
// // println("***** IS ifaceIndir typ")
|
||||
// // if true || urv.flag&unsafeRvFlagAddr == 0 {
|
||||
// // // println(" ***** IS NOT addr")
|
||||
// return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ}))
|
||||
// // }
|
||||
// } else if urv.flag&unsafeRvFlagIndir != 0 {
|
||||
// if _UNSAFE_RV_DEBUG {
|
||||
// fmt.Printf("**** +flagIndir type: %v\n", rv.Type())
|
||||
// }
|
||||
// // println("***** IS flagindir")
|
||||
// return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ}))
|
||||
// } else {
|
||||
// if _UNSAFE_RV_DEBUG {
|
||||
// fmt.Printf("**** -flagIndir type: %v\n", rv.Type())
|
||||
// }
|
||||
// // println("***** NOT flagindir")
|
||||
// return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ}))
|
||||
// }
|
||||
// // println("***** default: delegating to rv.Interface()")
|
||||
// // return rv.Interface()
|
||||
// }
|
||||
|
||||
// var staticM0 = make(map[string]uint64)
|
||||
// var staticI0 = (int32)(-5)
|
||||
|
||||
// func staticRv2iTest() {
|
||||
// i0 := (int32)(-5)
|
||||
// m0 := make(map[string]uint16)
|
||||
// m0["1"] = 1
|
||||
// for _, i := range []interface{}{
|
||||
// (int)(7),
|
||||
// (uint)(8),
|
||||
// (int16)(-9),
|
||||
// (uint16)(19),
|
||||
// (uintptr)(77),
|
||||
// (bool)(true),
|
||||
// float32(-32.7),
|
||||
// float64(64.9),
|
||||
// complex(float32(19), 5),
|
||||
// complex(float64(-32), 7),
|
||||
// [4]uint64{1, 2, 3, 4},
|
||||
// (chan<- int)(nil), // chan,
|
||||
// rv2i, // func
|
||||
// io.Writer(ioutil.Discard),
|
||||
// make(map[string]uint),
|
||||
// (map[string]uint)(nil),
|
||||
// staticM0,
|
||||
// m0,
|
||||
// &m0,
|
||||
// i0,
|
||||
// &i0,
|
||||
// &staticI0,
|
||||
// &staticM0,
|
||||
// []uint32{6, 7, 8},
|
||||
// "abc",
|
||||
// Raw{},
|
||||
// RawExt{},
|
||||
// &Raw{},
|
||||
// &RawExt{},
|
||||
// unsafe.Pointer(&i0),
|
||||
// } {
|
||||
// i2 := rv2i(reflect.ValueOf(i))
|
||||
// eq := reflect.DeepEqual(i, i2)
|
||||
// fmt.Printf(">>>> %v == %v? %v\n", i, i2, eq)
|
||||
// }
|
||||
// // os.Exit(0)
|
||||
// }
|
||||
|
||||
// func init() {
|
||||
// staticRv2iTest()
|
||||
// }
|
||||
|
||||
// func rv2i(rv reflect.Value) interface{} {
|
||||
// if _USE_RV_INTERFACE || rv.Kind() == reflect.Interface || rv.CanAddr() {
|
||||
// return rv.Interface()
|
||||
// }
|
||||
// // var i interface{}
|
||||
// // ui := (*unsafeIntf)(unsafe.Pointer(&i))
|
||||
// var ui unsafeIntf
|
||||
// urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
|
||||
// // fmt.Printf("urv: flag: %b, typ: %b, ptr: %b\n", urv.flag, uintptr(urv.typ), uintptr(urv.ptr))
|
||||
// if (urv.flag&unsafeRvFlagKindMask)&unsafeRvKindDirectIface == 0 {
|
||||
// if urv.flag&unsafeRvFlagAddr != 0 {
|
||||
// println("***** indirect and addressable! Needs typed move - delegate to rv.Interface()")
|
||||
// return rv.Interface()
|
||||
// }
|
||||
// println("****** indirect type/kind")
|
||||
// ui.word = urv.ptr
|
||||
// } else if urv.flag&unsafeRvFlagIndir != 0 {
|
||||
// println("****** unsafe rv flag indir")
|
||||
// ui.word = *(*unsafe.Pointer)(urv.ptr)
|
||||
// } else {
|
||||
// println("****** default: assign prt to word directly")
|
||||
// ui.word = urv.ptr
|
||||
// }
|
||||
// // ui.word = urv.ptr
|
||||
// ui.typ = urv.typ
|
||||
// // fmt.Printf("(pointers) ui.typ: %p, word: %p\n", ui.typ, ui.word)
|
||||
// // fmt.Printf("(binary) ui.typ: %b, word: %b\n", uintptr(ui.typ), uintptr(ui.word))
|
||||
// return *(*interface{})(unsafe.Pointer(&ui))
|
||||
// // return i
|
||||
// }
|
1172
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/json.go
generated
vendored
Normal file
1172
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/json.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
899
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/msgpack.go
generated
vendored
Normal file
899
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/msgpack.go
generated
vendored
Normal file
@ -0,0 +1,899 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
/*
|
||||
MSGPACK
|
||||
|
||||
Msgpack-c implementation powers the c, c++, python, ruby, etc libraries.
|
||||
We need to maintain compatibility with it and how it encodes integer values
|
||||
without caring about the type.
|
||||
|
||||
For compatibility with behaviour of msgpack-c reference implementation:
|
||||
- Go intX (>0) and uintX
|
||||
IS ENCODED AS
|
||||
msgpack +ve fixnum, unsigned
|
||||
- Go intX (<0)
|
||||
IS ENCODED AS
|
||||
msgpack -ve fixnum, signed
|
||||
|
||||
*/
|
||||
package codec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/rpc"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
mpPosFixNumMin byte = 0x00
|
||||
mpPosFixNumMax = 0x7f
|
||||
mpFixMapMin = 0x80
|
||||
mpFixMapMax = 0x8f
|
||||
mpFixArrayMin = 0x90
|
||||
mpFixArrayMax = 0x9f
|
||||
mpFixStrMin = 0xa0
|
||||
mpFixStrMax = 0xbf
|
||||
mpNil = 0xc0
|
||||
_ = 0xc1
|
||||
mpFalse = 0xc2
|
||||
mpTrue = 0xc3
|
||||
mpFloat = 0xca
|
||||
mpDouble = 0xcb
|
||||
mpUint8 = 0xcc
|
||||
mpUint16 = 0xcd
|
||||
mpUint32 = 0xce
|
||||
mpUint64 = 0xcf
|
||||
mpInt8 = 0xd0
|
||||
mpInt16 = 0xd1
|
||||
mpInt32 = 0xd2
|
||||
mpInt64 = 0xd3
|
||||
|
||||
// extensions below
|
||||
mpBin8 = 0xc4
|
||||
mpBin16 = 0xc5
|
||||
mpBin32 = 0xc6
|
||||
mpExt8 = 0xc7
|
||||
mpExt16 = 0xc8
|
||||
mpExt32 = 0xc9
|
||||
mpFixExt1 = 0xd4
|
||||
mpFixExt2 = 0xd5
|
||||
mpFixExt4 = 0xd6
|
||||
mpFixExt8 = 0xd7
|
||||
mpFixExt16 = 0xd8
|
||||
|
||||
mpStr8 = 0xd9 // new
|
||||
mpStr16 = 0xda
|
||||
mpStr32 = 0xdb
|
||||
|
||||
mpArray16 = 0xdc
|
||||
mpArray32 = 0xdd
|
||||
|
||||
mpMap16 = 0xde
|
||||
mpMap32 = 0xdf
|
||||
|
||||
mpNegFixNumMin = 0xe0
|
||||
mpNegFixNumMax = 0xff
|
||||
)
|
||||
|
||||
// MsgpackSpecRpcMultiArgs is a special type which signifies to the MsgpackSpecRpcCodec
|
||||
// that the backend RPC service takes multiple arguments, which have been arranged
|
||||
// in sequence in the slice.
|
||||
//
|
||||
// The Codec then passes it AS-IS to the rpc service (without wrapping it in an
|
||||
// array of 1 element).
|
||||
type MsgpackSpecRpcMultiArgs []interface{}
|
||||
|
||||
// A MsgpackContainer type specifies the different types of msgpackContainers.
|
||||
type msgpackContainerType struct {
|
||||
fixCutoff int
|
||||
bFixMin, b8, b16, b32 byte
|
||||
hasFixMin, has8, has8Always bool
|
||||
}
|
||||
|
||||
var (
|
||||
msgpackContainerStr = msgpackContainerType{32, mpFixStrMin, mpStr8, mpStr16, mpStr32, true, true, false}
|
||||
msgpackContainerBin = msgpackContainerType{0, 0, mpBin8, mpBin16, mpBin32, false, true, true}
|
||||
msgpackContainerList = msgpackContainerType{16, mpFixArrayMin, 0, mpArray16, mpArray32, true, false, false}
|
||||
msgpackContainerMap = msgpackContainerType{16, mpFixMapMin, 0, mpMap16, mpMap32, true, false, false}
|
||||
)
|
||||
|
||||
//---------------------------------------------
|
||||
|
||||
type msgpackEncDriver struct {
|
||||
noBuiltInTypes
|
||||
encDriverNoopContainerWriter
|
||||
// encNoSeparator
|
||||
e *Encoder
|
||||
w encWriter
|
||||
h *MsgpackHandle
|
||||
x [8]byte
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) EncodeNil() {
|
||||
e.w.writen1(mpNil)
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) EncodeInt(i int64) {
|
||||
if i >= 0 {
|
||||
e.EncodeUint(uint64(i))
|
||||
} else if i >= -32 {
|
||||
e.w.writen1(byte(i))
|
||||
} else if i >= math.MinInt8 {
|
||||
e.w.writen2(mpInt8, byte(i))
|
||||
} else if i >= math.MinInt16 {
|
||||
e.w.writen1(mpInt16)
|
||||
bigenHelper{e.x[:2], e.w}.writeUint16(uint16(i))
|
||||
} else if i >= math.MinInt32 {
|
||||
e.w.writen1(mpInt32)
|
||||
bigenHelper{e.x[:4], e.w}.writeUint32(uint32(i))
|
||||
} else {
|
||||
e.w.writen1(mpInt64)
|
||||
bigenHelper{e.x[:8], e.w}.writeUint64(uint64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) EncodeUint(i uint64) {
|
||||
if i <= math.MaxInt8 {
|
||||
e.w.writen1(byte(i))
|
||||
} else if i <= math.MaxUint8 {
|
||||
e.w.writen2(mpUint8, byte(i))
|
||||
} else if i <= math.MaxUint16 {
|
||||
e.w.writen1(mpUint16)
|
||||
bigenHelper{e.x[:2], e.w}.writeUint16(uint16(i))
|
||||
} else if i <= math.MaxUint32 {
|
||||
e.w.writen1(mpUint32)
|
||||
bigenHelper{e.x[:4], e.w}.writeUint32(uint32(i))
|
||||
} else {
|
||||
e.w.writen1(mpUint64)
|
||||
bigenHelper{e.x[:8], e.w}.writeUint64(uint64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) EncodeBool(b bool) {
|
||||
if b {
|
||||
e.w.writen1(mpTrue)
|
||||
} else {
|
||||
e.w.writen1(mpFalse)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) EncodeFloat32(f float32) {
|
||||
e.w.writen1(mpFloat)
|
||||
bigenHelper{e.x[:4], e.w}.writeUint32(math.Float32bits(f))
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) EncodeFloat64(f float64) {
|
||||
e.w.writen1(mpDouble)
|
||||
bigenHelper{e.x[:8], e.w}.writeUint64(math.Float64bits(f))
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) EncodeExt(v interface{}, xtag uint64, ext Ext, _ *Encoder) {
|
||||
bs := ext.WriteExt(v)
|
||||
if bs == nil {
|
||||
e.EncodeNil()
|
||||
return
|
||||
}
|
||||
if e.h.WriteExt {
|
||||
e.encodeExtPreamble(uint8(xtag), len(bs))
|
||||
e.w.writeb(bs)
|
||||
} else {
|
||||
e.EncodeStringBytes(c_RAW, bs)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) EncodeRawExt(re *RawExt, _ *Encoder) {
|
||||
e.encodeExtPreamble(uint8(re.Tag), len(re.Data))
|
||||
e.w.writeb(re.Data)
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) encodeExtPreamble(xtag byte, l int) {
|
||||
if l == 1 {
|
||||
e.w.writen2(mpFixExt1, xtag)
|
||||
} else if l == 2 {
|
||||
e.w.writen2(mpFixExt2, xtag)
|
||||
} else if l == 4 {
|
||||
e.w.writen2(mpFixExt4, xtag)
|
||||
} else if l == 8 {
|
||||
e.w.writen2(mpFixExt8, xtag)
|
||||
} else if l == 16 {
|
||||
e.w.writen2(mpFixExt16, xtag)
|
||||
} else if l < 256 {
|
||||
e.w.writen2(mpExt8, byte(l))
|
||||
e.w.writen1(xtag)
|
||||
} else if l < 65536 {
|
||||
e.w.writen1(mpExt16)
|
||||
bigenHelper{e.x[:2], e.w}.writeUint16(uint16(l))
|
||||
e.w.writen1(xtag)
|
||||
} else {
|
||||
e.w.writen1(mpExt32)
|
||||
bigenHelper{e.x[:4], e.w}.writeUint32(uint32(l))
|
||||
e.w.writen1(xtag)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) WriteArrayStart(length int) {
|
||||
e.writeContainerLen(msgpackContainerList, length)
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) WriteMapStart(length int) {
|
||||
e.writeContainerLen(msgpackContainerMap, length)
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) EncodeString(c charEncoding, s string) {
|
||||
slen := len(s)
|
||||
if c == c_RAW && e.h.WriteExt {
|
||||
e.writeContainerLen(msgpackContainerBin, slen)
|
||||
} else {
|
||||
e.writeContainerLen(msgpackContainerStr, slen)
|
||||
}
|
||||
if slen > 0 {
|
||||
e.w.writestr(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) EncodeSymbol(v string) {
|
||||
e.EncodeString(c_UTF8, v)
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) EncodeStringBytes(c charEncoding, bs []byte) {
|
||||
slen := len(bs)
|
||||
if c == c_RAW && e.h.WriteExt {
|
||||
e.writeContainerLen(msgpackContainerBin, slen)
|
||||
} else {
|
||||
e.writeContainerLen(msgpackContainerStr, slen)
|
||||
}
|
||||
if slen > 0 {
|
||||
e.w.writeb(bs)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) writeContainerLen(ct msgpackContainerType, l int) {
|
||||
if ct.hasFixMin && l < ct.fixCutoff {
|
||||
e.w.writen1(ct.bFixMin | byte(l))
|
||||
} else if ct.has8 && l < 256 && (ct.has8Always || e.h.WriteExt) {
|
||||
e.w.writen2(ct.b8, uint8(l))
|
||||
} else if l < 65536 {
|
||||
e.w.writen1(ct.b16)
|
||||
bigenHelper{e.x[:2], e.w}.writeUint16(uint16(l))
|
||||
} else {
|
||||
e.w.writen1(ct.b32)
|
||||
bigenHelper{e.x[:4], e.w}.writeUint32(uint32(l))
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------
|
||||
|
||||
type msgpackDecDriver struct {
|
||||
d *Decoder
|
||||
r decReader // *Decoder decReader decReaderT
|
||||
h *MsgpackHandle
|
||||
b [scratchByteArrayLen]byte
|
||||
bd byte
|
||||
bdRead bool
|
||||
br bool // bytes reader
|
||||
noBuiltInTypes
|
||||
// noStreamingCodec
|
||||
// decNoSeparator
|
||||
decDriverNoopContainerReader
|
||||
}
|
||||
|
||||
// Note: This returns either a primitive (int, bool, etc) for non-containers,
|
||||
// or a containerType, or a specific type denoting nil or extension.
|
||||
// It is called when a nil interface{} is passed, leaving it up to the DecDriver
|
||||
// to introspect the stream and decide how best to decode.
|
||||
// It deciphers the value by looking at the stream first.
|
||||
func (d *msgpackDecDriver) DecodeNaked() {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
bd := d.bd
|
||||
n := d.d.n
|
||||
var decodeFurther bool
|
||||
|
||||
switch bd {
|
||||
case mpNil:
|
||||
n.v = valueTypeNil
|
||||
d.bdRead = false
|
||||
case mpFalse:
|
||||
n.v = valueTypeBool
|
||||
n.b = false
|
||||
case mpTrue:
|
||||
n.v = valueTypeBool
|
||||
n.b = true
|
||||
|
||||
case mpFloat:
|
||||
n.v = valueTypeFloat
|
||||
n.f = float64(math.Float32frombits(bigen.Uint32(d.r.readx(4))))
|
||||
case mpDouble:
|
||||
n.v = valueTypeFloat
|
||||
n.f = math.Float64frombits(bigen.Uint64(d.r.readx(8)))
|
||||
|
||||
case mpUint8:
|
||||
n.v = valueTypeUint
|
||||
n.u = uint64(d.r.readn1())
|
||||
case mpUint16:
|
||||
n.v = valueTypeUint
|
||||
n.u = uint64(bigen.Uint16(d.r.readx(2)))
|
||||
case mpUint32:
|
||||
n.v = valueTypeUint
|
||||
n.u = uint64(bigen.Uint32(d.r.readx(4)))
|
||||
case mpUint64:
|
||||
n.v = valueTypeUint
|
||||
n.u = uint64(bigen.Uint64(d.r.readx(8)))
|
||||
|
||||
case mpInt8:
|
||||
n.v = valueTypeInt
|
||||
n.i = int64(int8(d.r.readn1()))
|
||||
case mpInt16:
|
||||
n.v = valueTypeInt
|
||||
n.i = int64(int16(bigen.Uint16(d.r.readx(2))))
|
||||
case mpInt32:
|
||||
n.v = valueTypeInt
|
||||
n.i = int64(int32(bigen.Uint32(d.r.readx(4))))
|
||||
case mpInt64:
|
||||
n.v = valueTypeInt
|
||||
n.i = int64(int64(bigen.Uint64(d.r.readx(8))))
|
||||
|
||||
default:
|
||||
switch {
|
||||
case bd >= mpPosFixNumMin && bd <= mpPosFixNumMax:
|
||||
// positive fixnum (always signed)
|
||||
n.v = valueTypeInt
|
||||
n.i = int64(int8(bd))
|
||||
case bd >= mpNegFixNumMin && bd <= mpNegFixNumMax:
|
||||
// negative fixnum
|
||||
n.v = valueTypeInt
|
||||
n.i = int64(int8(bd))
|
||||
case bd == mpStr8, bd == mpStr16, bd == mpStr32, bd >= mpFixStrMin && bd <= mpFixStrMax:
|
||||
if d.h.RawToString {
|
||||
n.v = valueTypeString
|
||||
n.s = d.DecodeString()
|
||||
} else {
|
||||
n.v = valueTypeBytes
|
||||
n.l = d.DecodeBytes(nil, false)
|
||||
}
|
||||
case bd == mpBin8, bd == mpBin16, bd == mpBin32:
|
||||
n.v = valueTypeBytes
|
||||
n.l = d.DecodeBytes(nil, false)
|
||||
case bd == mpArray16, bd == mpArray32, bd >= mpFixArrayMin && bd <= mpFixArrayMax:
|
||||
n.v = valueTypeArray
|
||||
decodeFurther = true
|
||||
case bd == mpMap16, bd == mpMap32, bd >= mpFixMapMin && bd <= mpFixMapMax:
|
||||
n.v = valueTypeMap
|
||||
decodeFurther = true
|
||||
case bd >= mpFixExt1 && bd <= mpFixExt16, bd >= mpExt8 && bd <= mpExt32:
|
||||
n.v = valueTypeExt
|
||||
clen := d.readExtLen()
|
||||
n.u = uint64(d.r.readn1())
|
||||
n.l = d.r.readx(clen)
|
||||
default:
|
||||
d.d.errorf("Nil-Deciphered DecodeValue: %s: hex: %x, dec: %d", msgBadDesc, bd, bd)
|
||||
}
|
||||
}
|
||||
if !decodeFurther {
|
||||
d.bdRead = false
|
||||
}
|
||||
if n.v == valueTypeUint && d.h.SignedInteger {
|
||||
n.v = valueTypeInt
|
||||
n.i = int64(n.u)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// int can be decoded from msgpack type: intXXX or uintXXX
|
||||
func (d *msgpackDecDriver) DecodeInt(bitsize uint8) (i int64) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
switch d.bd {
|
||||
case mpUint8:
|
||||
i = int64(uint64(d.r.readn1()))
|
||||
case mpUint16:
|
||||
i = int64(uint64(bigen.Uint16(d.r.readx(2))))
|
||||
case mpUint32:
|
||||
i = int64(uint64(bigen.Uint32(d.r.readx(4))))
|
||||
case mpUint64:
|
||||
i = int64(bigen.Uint64(d.r.readx(8)))
|
||||
case mpInt8:
|
||||
i = int64(int8(d.r.readn1()))
|
||||
case mpInt16:
|
||||
i = int64(int16(bigen.Uint16(d.r.readx(2))))
|
||||
case mpInt32:
|
||||
i = int64(int32(bigen.Uint32(d.r.readx(4))))
|
||||
case mpInt64:
|
||||
i = int64(bigen.Uint64(d.r.readx(8)))
|
||||
default:
|
||||
switch {
|
||||
case d.bd >= mpPosFixNumMin && d.bd <= mpPosFixNumMax:
|
||||
i = int64(int8(d.bd))
|
||||
case d.bd >= mpNegFixNumMin && d.bd <= mpNegFixNumMax:
|
||||
i = int64(int8(d.bd))
|
||||
default:
|
||||
d.d.errorf("Unhandled single-byte unsigned integer value: %s: %x", msgBadDesc, d.bd)
|
||||
return
|
||||
}
|
||||
}
|
||||
// check overflow (logic adapted from std pkg reflect/value.go OverflowUint()
|
||||
if bitsize > 0 {
|
||||
if trunc := (i << (64 - bitsize)) >> (64 - bitsize); i != trunc {
|
||||
d.d.errorf("Overflow int value: %v", i)
|
||||
return
|
||||
}
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
// uint can be decoded from msgpack type: intXXX or uintXXX
|
||||
func (d *msgpackDecDriver) DecodeUint(bitsize uint8) (ui uint64) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
switch d.bd {
|
||||
case mpUint8:
|
||||
ui = uint64(d.r.readn1())
|
||||
case mpUint16:
|
||||
ui = uint64(bigen.Uint16(d.r.readx(2)))
|
||||
case mpUint32:
|
||||
ui = uint64(bigen.Uint32(d.r.readx(4)))
|
||||
case mpUint64:
|
||||
ui = bigen.Uint64(d.r.readx(8))
|
||||
case mpInt8:
|
||||
if i := int64(int8(d.r.readn1())); i >= 0 {
|
||||
ui = uint64(i)
|
||||
} else {
|
||||
d.d.errorf("Assigning negative signed value: %v, to unsigned type", i)
|
||||
return
|
||||
}
|
||||
case mpInt16:
|
||||
if i := int64(int16(bigen.Uint16(d.r.readx(2)))); i >= 0 {
|
||||
ui = uint64(i)
|
||||
} else {
|
||||
d.d.errorf("Assigning negative signed value: %v, to unsigned type", i)
|
||||
return
|
||||
}
|
||||
case mpInt32:
|
||||
if i := int64(int32(bigen.Uint32(d.r.readx(4)))); i >= 0 {
|
||||
ui = uint64(i)
|
||||
} else {
|
||||
d.d.errorf("Assigning negative signed value: %v, to unsigned type", i)
|
||||
return
|
||||
}
|
||||
case mpInt64:
|
||||
if i := int64(bigen.Uint64(d.r.readx(8))); i >= 0 {
|
||||
ui = uint64(i)
|
||||
} else {
|
||||
d.d.errorf("Assigning negative signed value: %v, to unsigned type", i)
|
||||
return
|
||||
}
|
||||
default:
|
||||
switch {
|
||||
case d.bd >= mpPosFixNumMin && d.bd <= mpPosFixNumMax:
|
||||
ui = uint64(d.bd)
|
||||
case d.bd >= mpNegFixNumMin && d.bd <= mpNegFixNumMax:
|
||||
d.d.errorf("Assigning negative signed value: %v, to unsigned type", int(d.bd))
|
||||
return
|
||||
default:
|
||||
d.d.errorf("Unhandled single-byte unsigned integer value: %s: %x", msgBadDesc, d.bd)
|
||||
return
|
||||
}
|
||||
}
|
||||
// check overflow (logic adapted from std pkg reflect/value.go OverflowUint()
|
||||
if bitsize > 0 {
|
||||
if trunc := (ui << (64 - bitsize)) >> (64 - bitsize); ui != trunc {
|
||||
d.d.errorf("Overflow uint value: %v", ui)
|
||||
return
|
||||
}
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
// float can either be decoded from msgpack type: float, double or intX
|
||||
func (d *msgpackDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == mpFloat {
|
||||
f = float64(math.Float32frombits(bigen.Uint32(d.r.readx(4))))
|
||||
} else if d.bd == mpDouble {
|
||||
f = math.Float64frombits(bigen.Uint64(d.r.readx(8)))
|
||||
} else {
|
||||
f = float64(d.DecodeInt(0))
|
||||
}
|
||||
if chkOverflow32 && chkOvf.Float32(f) {
|
||||
d.d.errorf("msgpack: float32 overflow: %v", f)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
// bool can be decoded from bool, fixnum 0 or 1.
|
||||
func (d *msgpackDecDriver) DecodeBool() (b bool) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == mpFalse || d.bd == 0 {
|
||||
// b = false
|
||||
} else if d.bd == mpTrue || d.bd == 1 {
|
||||
b = true
|
||||
} else {
|
||||
d.d.errorf("Invalid single-byte value for bool: %s: %x", msgBadDesc, d.bd)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
|
||||
// DecodeBytes could be from: bin str fixstr fixarray array ...
|
||||
var clen int
|
||||
vt := d.ContainerType()
|
||||
switch vt {
|
||||
case valueTypeBytes:
|
||||
// valueTypeBytes may be a mpBin or an mpStr container
|
||||
if bd := d.bd; bd == mpBin8 || bd == mpBin16 || bd == mpBin32 {
|
||||
clen = d.readContainerLen(msgpackContainerBin)
|
||||
} else {
|
||||
clen = d.readContainerLen(msgpackContainerStr)
|
||||
}
|
||||
case valueTypeString:
|
||||
clen = d.readContainerLen(msgpackContainerStr)
|
||||
case valueTypeArray:
|
||||
clen = d.readContainerLen(msgpackContainerList)
|
||||
// ensure everything after is one byte each
|
||||
for i := 0; i < clen; i++ {
|
||||
d.readNextBd()
|
||||
if d.bd == mpNil {
|
||||
bs = append(bs, 0)
|
||||
} else if d.bd == mpUint8 {
|
||||
bs = append(bs, d.r.readn1())
|
||||
} else {
|
||||
d.d.errorf("cannot read non-byte into a byte array")
|
||||
return
|
||||
}
|
||||
}
|
||||
d.bdRead = false
|
||||
return bs
|
||||
default:
|
||||
d.d.errorf("invalid container type: expecting bin|str|array")
|
||||
return
|
||||
}
|
||||
|
||||
// these are (bin|str)(8|16|32)
|
||||
// println("DecodeBytes: clen: ", clen)
|
||||
d.bdRead = false
|
||||
// bytes may be nil, so handle it. if nil, clen=-1.
|
||||
if clen < 0 {
|
||||
return nil
|
||||
}
|
||||
if zerocopy {
|
||||
if d.br {
|
||||
return d.r.readx(clen)
|
||||
} else if len(bs) == 0 {
|
||||
bs = d.b[:]
|
||||
}
|
||||
}
|
||||
return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs)
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) DecodeString() (s string) {
|
||||
return string(d.DecodeBytes(d.b[:], true))
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) DecodeStringAsBytes() (s []byte) {
|
||||
return d.DecodeBytes(d.b[:], true)
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) readNextBd() {
|
||||
d.bd = d.r.readn1()
|
||||
d.bdRead = true
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) uncacheRead() {
|
||||
if d.bdRead {
|
||||
d.r.unreadn1()
|
||||
d.bdRead = false
|
||||
}
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) ContainerType() (vt valueType) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
bd := d.bd
|
||||
if bd == mpNil {
|
||||
return valueTypeNil
|
||||
} else if bd == mpBin8 || bd == mpBin16 || bd == mpBin32 ||
|
||||
(!d.h.RawToString &&
|
||||
(bd == mpStr8 || bd == mpStr16 || bd == mpStr32 || (bd >= mpFixStrMin && bd <= mpFixStrMax))) {
|
||||
return valueTypeBytes
|
||||
} else if d.h.RawToString &&
|
||||
(bd == mpStr8 || bd == mpStr16 || bd == mpStr32 || (bd >= mpFixStrMin && bd <= mpFixStrMax)) {
|
||||
return valueTypeString
|
||||
} else if bd == mpArray16 || bd == mpArray32 || (bd >= mpFixArrayMin && bd <= mpFixArrayMax) {
|
||||
return valueTypeArray
|
||||
} else if bd == mpMap16 || bd == mpMap32 || (bd >= mpFixMapMin && bd <= mpFixMapMax) {
|
||||
return valueTypeMap
|
||||
} else {
|
||||
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
|
||||
}
|
||||
return valueTypeUnset
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) TryDecodeAsNil() (v bool) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == mpNil {
|
||||
d.bdRead = false
|
||||
v = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) readContainerLen(ct msgpackContainerType) (clen int) {
|
||||
bd := d.bd
|
||||
if bd == mpNil {
|
||||
clen = -1 // to represent nil
|
||||
} else if bd == ct.b8 {
|
||||
clen = int(d.r.readn1())
|
||||
} else if bd == ct.b16 {
|
||||
clen = int(bigen.Uint16(d.r.readx(2)))
|
||||
} else if bd == ct.b32 {
|
||||
clen = int(bigen.Uint32(d.r.readx(4)))
|
||||
} else if (ct.bFixMin & bd) == ct.bFixMin {
|
||||
clen = int(ct.bFixMin ^ bd)
|
||||
} else {
|
||||
d.d.errorf("readContainerLen: %s: hex: %x, decimal: %d", msgBadDesc, bd, bd)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) ReadMapStart() int {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
return d.readContainerLen(msgpackContainerMap)
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) ReadArrayStart() int {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
return d.readContainerLen(msgpackContainerList)
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) readExtLen() (clen int) {
|
||||
switch d.bd {
|
||||
case mpNil:
|
||||
clen = -1 // to represent nil
|
||||
case mpFixExt1:
|
||||
clen = 1
|
||||
case mpFixExt2:
|
||||
clen = 2
|
||||
case mpFixExt4:
|
||||
clen = 4
|
||||
case mpFixExt8:
|
||||
clen = 8
|
||||
case mpFixExt16:
|
||||
clen = 16
|
||||
case mpExt8:
|
||||
clen = int(d.r.readn1())
|
||||
case mpExt16:
|
||||
clen = int(bigen.Uint16(d.r.readx(2)))
|
||||
case mpExt32:
|
||||
clen = int(bigen.Uint32(d.r.readx(4)))
|
||||
default:
|
||||
d.d.errorf("decoding ext bytes: found unexpected byte: %x", d.bd)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) {
|
||||
if xtag > 0xff {
|
||||
d.d.errorf("decodeExt: tag must be <= 0xff; got: %v", xtag)
|
||||
return
|
||||
}
|
||||
realxtag1, xbs := d.decodeExtV(ext != nil, uint8(xtag))
|
||||
realxtag = uint64(realxtag1)
|
||||
if ext == nil {
|
||||
re := rv.(*RawExt)
|
||||
re.Tag = realxtag
|
||||
re.Data = detachZeroCopyBytes(d.br, re.Data, xbs)
|
||||
} else {
|
||||
ext.ReadExt(rv, xbs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) decodeExtV(verifyTag bool, tag byte) (xtag byte, xbs []byte) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
xbd := d.bd
|
||||
if xbd == mpBin8 || xbd == mpBin16 || xbd == mpBin32 {
|
||||
xbs = d.DecodeBytes(nil, true)
|
||||
} else if xbd == mpStr8 || xbd == mpStr16 || xbd == mpStr32 ||
|
||||
(xbd >= mpFixStrMin && xbd <= mpFixStrMax) {
|
||||
xbs = d.DecodeStringAsBytes()
|
||||
} else {
|
||||
clen := d.readExtLen()
|
||||
xtag = d.r.readn1()
|
||||
if verifyTag && xtag != tag {
|
||||
d.d.errorf("Wrong extension tag. Got %b. Expecting: %v", xtag, tag)
|
||||
return
|
||||
}
|
||||
xbs = d.r.readx(clen)
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
//MsgpackHandle is a Handle for the Msgpack Schema-Free Encoding Format.
|
||||
type MsgpackHandle struct {
|
||||
BasicHandle
|
||||
|
||||
// RawToString controls how raw bytes are decoded into a nil interface{}.
|
||||
RawToString bool
|
||||
|
||||
// WriteExt flag supports encoding configured extensions with extension tags.
|
||||
// It also controls whether other elements of the new spec are encoded (ie Str8).
|
||||
//
|
||||
// With WriteExt=false, configured extensions are serialized as raw bytes
|
||||
// and Str8 is not encoded.
|
||||
//
|
||||
// A stream can still be decoded into a typed value, provided an appropriate value
|
||||
// is provided, but the type cannot be inferred from the stream. If no appropriate
|
||||
// type is provided (e.g. decoding into a nil interface{}), you get back
|
||||
// a []byte or string based on the setting of RawToString.
|
||||
WriteExt bool
|
||||
binaryEncodingType
|
||||
noElemSeparators
|
||||
}
|
||||
|
||||
func (h *MsgpackHandle) SetBytesExt(rt reflect.Type, tag uint64, ext BytesExt) (err error) {
|
||||
return h.SetExt(rt, tag, &setExtWrapper{b: ext})
|
||||
}
|
||||
|
||||
func (h *MsgpackHandle) newEncDriver(e *Encoder) encDriver {
|
||||
return &msgpackEncDriver{e: e, w: e.w, h: h}
|
||||
}
|
||||
|
||||
func (h *MsgpackHandle) newDecDriver(d *Decoder) decDriver {
|
||||
return &msgpackDecDriver{d: d, h: h, r: d.r, br: d.bytes}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) reset() {
|
||||
e.w = e.e.w
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) reset() {
|
||||
d.r, d.br = d.d.r, d.d.bytes
|
||||
d.bd, d.bdRead = 0, false
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
type msgpackSpecRpcCodec struct {
|
||||
rpcCodec
|
||||
}
|
||||
|
||||
// /////////////// Spec RPC Codec ///////////////////
|
||||
func (c *msgpackSpecRpcCodec) WriteRequest(r *rpc.Request, body interface{}) error {
|
||||
// WriteRequest can write to both a Go service, and other services that do
|
||||
// not abide by the 1 argument rule of a Go service.
|
||||
// We discriminate based on if the body is a MsgpackSpecRpcMultiArgs
|
||||
var bodyArr []interface{}
|
||||
if m, ok := body.(MsgpackSpecRpcMultiArgs); ok {
|
||||
bodyArr = ([]interface{})(m)
|
||||
} else {
|
||||
bodyArr = []interface{}{body}
|
||||
}
|
||||
r2 := []interface{}{0, uint32(r.Seq), r.ServiceMethod, bodyArr}
|
||||
return c.write(r2, nil, false, true)
|
||||
}
|
||||
|
||||
func (c *msgpackSpecRpcCodec) WriteResponse(r *rpc.Response, body interface{}) error {
|
||||
var moe interface{}
|
||||
if r.Error != "" {
|
||||
moe = r.Error
|
||||
}
|
||||
if moe != nil && body != nil {
|
||||
body = nil
|
||||
}
|
||||
r2 := []interface{}{1, uint32(r.Seq), moe, body}
|
||||
return c.write(r2, nil, false, true)
|
||||
}
|
||||
|
||||
func (c *msgpackSpecRpcCodec) ReadResponseHeader(r *rpc.Response) error {
|
||||
return c.parseCustomHeader(1, &r.Seq, &r.Error)
|
||||
}
|
||||
|
||||
func (c *msgpackSpecRpcCodec) ReadRequestHeader(r *rpc.Request) error {
|
||||
return c.parseCustomHeader(0, &r.Seq, &r.ServiceMethod)
|
||||
}
|
||||
|
||||
func (c *msgpackSpecRpcCodec) ReadRequestBody(body interface{}) error {
|
||||
if body == nil { // read and discard
|
||||
return c.read(nil)
|
||||
}
|
||||
bodyArr := []interface{}{body}
|
||||
return c.read(&bodyArr)
|
||||
}
|
||||
|
||||
func (c *msgpackSpecRpcCodec) parseCustomHeader(expectTypeByte byte, msgid *uint64, methodOrError *string) (err error) {
|
||||
|
||||
if c.isClosed() {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
// We read the response header by hand
|
||||
// so that the body can be decoded on its own from the stream at a later time.
|
||||
|
||||
const fia byte = 0x94 //four item array descriptor value
|
||||
// Not sure why the panic of EOF is swallowed above.
|
||||
// if bs1 := c.dec.r.readn1(); bs1 != fia {
|
||||
// err = fmt.Errorf("Unexpected value for array descriptor: Expecting %v. Received %v", fia, bs1)
|
||||
// return
|
||||
// }
|
||||
var b byte
|
||||
b, err = c.br.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if b != fia {
|
||||
err = fmt.Errorf("Unexpected value for array descriptor: Expecting %v. Received %v", fia, b)
|
||||
return
|
||||
}
|
||||
|
||||
if err = c.read(&b); err != nil {
|
||||
return
|
||||
}
|
||||
if b != expectTypeByte {
|
||||
err = fmt.Errorf("Unexpected byte descriptor in header. Expecting %v. Received %v", expectTypeByte, b)
|
||||
return
|
||||
}
|
||||
if err = c.read(msgid); err != nil {
|
||||
return
|
||||
}
|
||||
if err = c.read(methodOrError); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
// msgpackSpecRpc is the implementation of Rpc that uses custom communication protocol
|
||||
// as defined in the msgpack spec at https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md
|
||||
type msgpackSpecRpc struct{}
|
||||
|
||||
// MsgpackSpecRpc implements Rpc using the communication protocol defined in
|
||||
// the msgpack spec at https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md .
|
||||
// Its methods (ServerCodec and ClientCodec) return values that implement RpcCodecBuffered.
|
||||
var MsgpackSpecRpc msgpackSpecRpc
|
||||
|
||||
func (x msgpackSpecRpc) ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec {
|
||||
return &msgpackSpecRpcCodec{newRPCCodec(conn, h)}
|
||||
}
|
||||
|
||||
func (x msgpackSpecRpc) ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec {
|
||||
return &msgpackSpecRpcCodec{newRPCCodec(conn, h)}
|
||||
}
|
||||
|
||||
var _ decDriver = (*msgpackDecDriver)(nil)
|
||||
var _ encDriver = (*msgpackEncDriver)(nil)
|
214
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/noop.go
generated
vendored
Normal file
214
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/noop.go
generated
vendored
Normal file
@ -0,0 +1,214 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NoopHandle returns a no-op handle. It basically does nothing.
|
||||
// It is only useful for benchmarking, as it gives an idea of the
|
||||
// overhead from the codec framework.
|
||||
//
|
||||
// LIBRARY USERS: *** DO NOT USE ***
|
||||
func NoopHandle(slen int) *noopHandle {
|
||||
h := noopHandle{}
|
||||
h.rand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
h.B = make([][]byte, slen)
|
||||
h.S = make([]string, slen)
|
||||
for i := 0; i < len(h.S); i++ {
|
||||
b := make([]byte, i+1)
|
||||
for j := 0; j < len(b); j++ {
|
||||
b[j] = 'a' + byte(i)
|
||||
}
|
||||
h.B[i] = b
|
||||
h.S[i] = string(b)
|
||||
}
|
||||
return &h
|
||||
}
|
||||
|
||||
// noopHandle does nothing.
|
||||
// It is used to simulate the overhead of the codec framework.
|
||||
type noopHandle struct {
|
||||
BasicHandle
|
||||
binaryEncodingType
|
||||
noopDrv // noopDrv is unexported here, so we can get a copy of it when needed.
|
||||
}
|
||||
|
||||
type noopDrv struct {
|
||||
d *Decoder
|
||||
e *Encoder
|
||||
i int
|
||||
S []string
|
||||
B [][]byte
|
||||
mks []bool // stack. if map (true), else if array (false)
|
||||
mk bool // top of stack. what container are we on? map or array?
|
||||
ct valueType // last response for IsContainerType.
|
||||
cb int // counter for ContainerType
|
||||
rand *rand.Rand
|
||||
}
|
||||
|
||||
func (h *noopDrv) r(v int) int { return h.rand.Intn(v) }
|
||||
func (h *noopDrv) m(v int) int { h.i++; return h.i % v }
|
||||
|
||||
func (h *noopDrv) newEncDriver(e *Encoder) encDriver { h.e = e; return h }
|
||||
func (h *noopDrv) newDecDriver(d *Decoder) decDriver { h.d = d; return h }
|
||||
|
||||
func (h *noopDrv) reset() {}
|
||||
func (h *noopDrv) uncacheRead() {}
|
||||
|
||||
// --- encDriver
|
||||
|
||||
// stack functions (for map and array)
|
||||
func (h *noopDrv) start(b bool) {
|
||||
// println("start", len(h.mks)+1)
|
||||
h.mks = append(h.mks, b)
|
||||
h.mk = b
|
||||
}
|
||||
func (h *noopDrv) end() {
|
||||
// println("end: ", len(h.mks)-1)
|
||||
h.mks = h.mks[:len(h.mks)-1]
|
||||
if len(h.mks) > 0 {
|
||||
h.mk = h.mks[len(h.mks)-1]
|
||||
} else {
|
||||
h.mk = false
|
||||
}
|
||||
}
|
||||
|
||||
func (h *noopDrv) EncodeBuiltin(rt uintptr, v interface{}) {}
|
||||
func (h *noopDrv) EncodeNil() {}
|
||||
func (h *noopDrv) EncodeInt(i int64) {}
|
||||
func (h *noopDrv) EncodeUint(i uint64) {}
|
||||
func (h *noopDrv) EncodeBool(b bool) {}
|
||||
func (h *noopDrv) EncodeFloat32(f float32) {}
|
||||
func (h *noopDrv) EncodeFloat64(f float64) {}
|
||||
func (h *noopDrv) EncodeRawExt(re *RawExt, e *Encoder) {}
|
||||
func (h *noopDrv) EncodeArrayStart(length int) { h.start(true) }
|
||||
func (h *noopDrv) EncodeMapStart(length int) { h.start(false) }
|
||||
func (h *noopDrv) EncodeEnd() { h.end() }
|
||||
|
||||
func (h *noopDrv) EncodeString(c charEncoding, v string) {}
|
||||
func (h *noopDrv) EncodeSymbol(v string) {}
|
||||
func (h *noopDrv) EncodeStringBytes(c charEncoding, v []byte) {}
|
||||
|
||||
func (h *noopDrv) EncodeExt(rv interface{}, xtag uint64, ext Ext, e *Encoder) {}
|
||||
|
||||
// ---- decDriver
|
||||
func (h *noopDrv) initReadNext() {}
|
||||
func (h *noopDrv) CheckBreak() bool { return false }
|
||||
func (h *noopDrv) IsBuiltinType(rt uintptr) bool { return false }
|
||||
func (h *noopDrv) DecodeBuiltin(rt uintptr, v interface{}) {}
|
||||
func (h *noopDrv) DecodeInt(bitsize uint8) (i int64) { return int64(h.m(15)) }
|
||||
func (h *noopDrv) DecodeUint(bitsize uint8) (ui uint64) { return uint64(h.m(35)) }
|
||||
func (h *noopDrv) DecodeFloat(chkOverflow32 bool) (f float64) { return float64(h.m(95)) }
|
||||
func (h *noopDrv) DecodeBool() (b bool) { return h.m(2) == 0 }
|
||||
func (h *noopDrv) DecodeString() (s string) { return h.S[h.m(8)] }
|
||||
func (h *noopDrv) DecodeStringAsBytes() []byte { return h.DecodeBytes(nil, true) }
|
||||
|
||||
func (h *noopDrv) DecodeBytes(bs []byte, zerocopy bool) []byte { return h.B[h.m(len(h.B))] }
|
||||
|
||||
func (h *noopDrv) ReadEnd() { h.end() }
|
||||
|
||||
// toggle map/slice
|
||||
func (h *noopDrv) ReadMapStart() int { h.start(true); return h.m(10) }
|
||||
func (h *noopDrv) ReadArrayStart() int { h.start(false); return h.m(10) }
|
||||
|
||||
func (h *noopDrv) ContainerType() (vt valueType) {
|
||||
// return h.m(2) == 0
|
||||
// handle kStruct, which will bomb is it calls this and doesn't get back a map or array.
|
||||
// consequently, if the return value is not map or array, reset it to one of them based on h.m(7) % 2
|
||||
// for kstruct: at least one out of every 2 times, return one of valueTypeMap or Array (else kstruct bombs)
|
||||
// however, every 10th time it is called, we just return something else.
|
||||
var vals = [...]valueType{valueTypeArray, valueTypeMap}
|
||||
// ------------ TAKE ------------
|
||||
// if h.cb%2 == 0 {
|
||||
// if h.ct == valueTypeMap || h.ct == valueTypeArray {
|
||||
// } else {
|
||||
// h.ct = vals[h.m(2)]
|
||||
// }
|
||||
// } else if h.cb%5 == 0 {
|
||||
// h.ct = valueType(h.m(8))
|
||||
// } else {
|
||||
// h.ct = vals[h.m(2)]
|
||||
// }
|
||||
// ------------ TAKE ------------
|
||||
// if h.cb%16 == 0 {
|
||||
// h.ct = valueType(h.cb % 8)
|
||||
// } else {
|
||||
// h.ct = vals[h.cb%2]
|
||||
// }
|
||||
h.ct = vals[h.cb%2]
|
||||
h.cb++
|
||||
return h.ct
|
||||
|
||||
// if h.ct == valueTypeNil || h.ct == valueTypeString || h.ct == valueTypeBytes {
|
||||
// return h.ct
|
||||
// }
|
||||
// return valueTypeUnset
|
||||
// TODO: may need to tweak this so it works.
|
||||
// if h.ct == valueTypeMap && vt == valueTypeArray || h.ct == valueTypeArray && vt == valueTypeMap {
|
||||
// h.cb = !h.cb
|
||||
// h.ct = vt
|
||||
// return h.cb
|
||||
// }
|
||||
// // go in a loop and check it.
|
||||
// h.ct = vt
|
||||
// h.cb = h.m(7) == 0
|
||||
// return h.cb
|
||||
}
|
||||
func (h *noopDrv) TryDecodeAsNil() bool {
|
||||
if h.mk {
|
||||
return false
|
||||
} else {
|
||||
return h.m(8) == 0
|
||||
}
|
||||
}
|
||||
func (h *noopDrv) DecodeExt(rv interface{}, xtag uint64, ext Ext) uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (h *noopDrv) DecodeNaked() {
|
||||
// use h.r (random) not h.m() because h.m() could cause the same value to be given.
|
||||
var sk int
|
||||
if h.mk {
|
||||
// if mapkey, do not support values of nil OR bytes, array, map or rawext
|
||||
sk = h.r(7) + 1
|
||||
} else {
|
||||
sk = h.r(12)
|
||||
}
|
||||
n := &h.d.n
|
||||
switch sk {
|
||||
case 0:
|
||||
n.v = valueTypeNil
|
||||
case 1:
|
||||
n.v, n.b = valueTypeBool, false
|
||||
case 2:
|
||||
n.v, n.b = valueTypeBool, true
|
||||
case 3:
|
||||
n.v, n.i = valueTypeInt, h.DecodeInt(64)
|
||||
case 4:
|
||||
n.v, n.u = valueTypeUint, h.DecodeUint(64)
|
||||
case 5:
|
||||
n.v, n.f = valueTypeFloat, h.DecodeFloat(true)
|
||||
case 6:
|
||||
n.v, n.f = valueTypeFloat, h.DecodeFloat(false)
|
||||
case 7:
|
||||
n.v, n.s = valueTypeString, h.DecodeString()
|
||||
case 8:
|
||||
n.v, n.l = valueTypeBytes, h.B[h.m(len(h.B))]
|
||||
case 9:
|
||||
n.v = valueTypeArray
|
||||
case 10:
|
||||
n.v = valueTypeMap
|
||||
default:
|
||||
n.v = valueTypeExt
|
||||
n.u = h.DecodeUint(64)
|
||||
n.l = h.B[h.m(len(h.B))]
|
||||
}
|
||||
h.ct = n.v
|
||||
return
|
||||
}
|
187
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/rpc.go
generated
vendored
Normal file
187
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/rpc.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"net/rpc"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// // rpcEncodeTerminator allows a handler specify a []byte terminator to send after each Encode.
|
||||
// //
|
||||
// // Some codecs like json need to put a space after each encoded value, to serve as a
|
||||
// // delimiter for things like numbers (else json codec will continue reading till EOF).
|
||||
// type rpcEncodeTerminator interface {
|
||||
// rpcEncodeTerminate() []byte
|
||||
// }
|
||||
|
||||
// Rpc provides a rpc Server or Client Codec for rpc communication.
|
||||
type Rpc interface {
|
||||
ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec
|
||||
ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec
|
||||
}
|
||||
|
||||
// RpcCodecBuffered allows access to the underlying bufio.Reader/Writer
|
||||
// used by the rpc connection. It accommodates use-cases where the connection
|
||||
// should be used by rpc and non-rpc functions, e.g. streaming a file after
|
||||
// sending an rpc response.
|
||||
type RpcCodecBuffered interface {
|
||||
BufferedReader() *bufio.Reader
|
||||
BufferedWriter() *bufio.Writer
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
|
||||
// rpcCodec defines the struct members and common methods.
|
||||
type rpcCodec struct {
|
||||
rwc io.ReadWriteCloser
|
||||
dec *Decoder
|
||||
enc *Encoder
|
||||
bw *bufio.Writer
|
||||
br *bufio.Reader
|
||||
mu sync.Mutex
|
||||
h Handle
|
||||
|
||||
cls bool
|
||||
clsmu sync.RWMutex
|
||||
}
|
||||
|
||||
func newRPCCodec(conn io.ReadWriteCloser, h Handle) rpcCodec {
|
||||
bw := bufio.NewWriter(conn)
|
||||
br := bufio.NewReader(conn)
|
||||
|
||||
// defensive: ensure that jsonH has TermWhitespace turned on.
|
||||
if jsonH, ok := h.(*JsonHandle); ok && !jsonH.TermWhitespace {
|
||||
panic(errors.New("rpc requires a JsonHandle with TermWhitespace set to true"))
|
||||
}
|
||||
|
||||
return rpcCodec{
|
||||
rwc: conn,
|
||||
bw: bw,
|
||||
br: br,
|
||||
enc: NewEncoder(bw, h),
|
||||
dec: NewDecoder(br, h),
|
||||
h: h,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *rpcCodec) BufferedReader() *bufio.Reader {
|
||||
return c.br
|
||||
}
|
||||
|
||||
func (c *rpcCodec) BufferedWriter() *bufio.Writer {
|
||||
return c.bw
|
||||
}
|
||||
|
||||
func (c *rpcCodec) write(obj1, obj2 interface{}, writeObj2, doFlush bool) (err error) {
|
||||
if c.isClosed() {
|
||||
return io.EOF
|
||||
}
|
||||
if err = c.enc.Encode(obj1); err != nil {
|
||||
return
|
||||
}
|
||||
// t, tOk := c.h.(rpcEncodeTerminator)
|
||||
// if tOk {
|
||||
// c.bw.Write(t.rpcEncodeTerminate())
|
||||
// }
|
||||
if writeObj2 {
|
||||
if err = c.enc.Encode(obj2); err != nil {
|
||||
return
|
||||
}
|
||||
// if tOk {
|
||||
// c.bw.Write(t.rpcEncodeTerminate())
|
||||
// }
|
||||
}
|
||||
if doFlush {
|
||||
return c.bw.Flush()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *rpcCodec) read(obj interface{}) (err error) {
|
||||
if c.isClosed() {
|
||||
return io.EOF
|
||||
}
|
||||
//If nil is passed in, we should still attempt to read content to nowhere.
|
||||
if obj == nil {
|
||||
var obj2 interface{}
|
||||
return c.dec.Decode(&obj2)
|
||||
}
|
||||
return c.dec.Decode(obj)
|
||||
}
|
||||
|
||||
func (c *rpcCodec) isClosed() bool {
|
||||
c.clsmu.RLock()
|
||||
x := c.cls
|
||||
c.clsmu.RUnlock()
|
||||
return x
|
||||
}
|
||||
|
||||
func (c *rpcCodec) Close() error {
|
||||
if c.isClosed() {
|
||||
return io.EOF
|
||||
}
|
||||
c.clsmu.Lock()
|
||||
c.cls = true
|
||||
c.clsmu.Unlock()
|
||||
return c.rwc.Close()
|
||||
}
|
||||
|
||||
func (c *rpcCodec) ReadResponseBody(body interface{}) error {
|
||||
return c.read(body)
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
|
||||
type goRpcCodec struct {
|
||||
rpcCodec
|
||||
}
|
||||
|
||||
func (c *goRpcCodec) WriteRequest(r *rpc.Request, body interface{}) error {
|
||||
// Must protect for concurrent access as per API
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.write(r, body, true, true)
|
||||
}
|
||||
|
||||
func (c *goRpcCodec) WriteResponse(r *rpc.Response, body interface{}) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.write(r, body, true, true)
|
||||
}
|
||||
|
||||
func (c *goRpcCodec) ReadResponseHeader(r *rpc.Response) error {
|
||||
return c.read(r)
|
||||
}
|
||||
|
||||
func (c *goRpcCodec) ReadRequestHeader(r *rpc.Request) error {
|
||||
return c.read(r)
|
||||
}
|
||||
|
||||
func (c *goRpcCodec) ReadRequestBody(body interface{}) error {
|
||||
return c.read(body)
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
|
||||
// goRpc is the implementation of Rpc that uses the communication protocol
|
||||
// as defined in net/rpc package.
|
||||
type goRpc struct{}
|
||||
|
||||
// GoRpc implements Rpc using the communication protocol defined in net/rpc package.
|
||||
// Its methods (ServerCodec and ClientCodec) return values that implement RpcCodecBuffered.
|
||||
var GoRpc goRpc
|
||||
|
||||
func (x goRpc) ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec {
|
||||
return &goRpcCodec{newRPCCodec(conn, h)}
|
||||
}
|
||||
|
||||
func (x goRpc) ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec {
|
||||
return &goRpcCodec{newRPCCodec(conn, h)}
|
||||
}
|
||||
|
||||
var _ RpcCodecBuffered = (*rpcCodec)(nil) // ensure *rpcCodec implements RpcCodecBuffered
|
541
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/simple.go
generated
vendored
Normal file
541
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/simple.go
generated
vendored
Normal file
@ -0,0 +1,541 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
_ uint8 = iota
|
||||
simpleVdNil = 1
|
||||
simpleVdFalse = 2
|
||||
simpleVdTrue = 3
|
||||
simpleVdFloat32 = 4
|
||||
simpleVdFloat64 = 5
|
||||
|
||||
// each lasts for 4 (ie n, n+1, n+2, n+3)
|
||||
simpleVdPosInt = 8
|
||||
simpleVdNegInt = 12
|
||||
|
||||
// containers: each lasts for 4 (ie n, n+1, n+2, ... n+7)
|
||||
simpleVdString = 216
|
||||
simpleVdByteArray = 224
|
||||
simpleVdArray = 232
|
||||
simpleVdMap = 240
|
||||
simpleVdExt = 248
|
||||
)
|
||||
|
||||
type simpleEncDriver struct {
|
||||
noBuiltInTypes
|
||||
encDriverNoopContainerWriter
|
||||
// encNoSeparator
|
||||
e *Encoder
|
||||
h *SimpleHandle
|
||||
w encWriter
|
||||
b [8]byte
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) EncodeNil() {
|
||||
e.w.writen1(simpleVdNil)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) EncodeBool(b bool) {
|
||||
if b {
|
||||
e.w.writen1(simpleVdTrue)
|
||||
} else {
|
||||
e.w.writen1(simpleVdFalse)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) EncodeFloat32(f float32) {
|
||||
e.w.writen1(simpleVdFloat32)
|
||||
bigenHelper{e.b[:4], e.w}.writeUint32(math.Float32bits(f))
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) EncodeFloat64(f float64) {
|
||||
e.w.writen1(simpleVdFloat64)
|
||||
bigenHelper{e.b[:8], e.w}.writeUint64(math.Float64bits(f))
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) EncodeInt(v int64) {
|
||||
if v < 0 {
|
||||
e.encUint(uint64(-v), simpleVdNegInt)
|
||||
} else {
|
||||
e.encUint(uint64(v), simpleVdPosInt)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) EncodeUint(v uint64) {
|
||||
e.encUint(v, simpleVdPosInt)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encUint(v uint64, bd uint8) {
|
||||
if v <= math.MaxUint8 {
|
||||
e.w.writen2(bd, uint8(v))
|
||||
} else if v <= math.MaxUint16 {
|
||||
e.w.writen1(bd + 1)
|
||||
bigenHelper{e.b[:2], e.w}.writeUint16(uint16(v))
|
||||
} else if v <= math.MaxUint32 {
|
||||
e.w.writen1(bd + 2)
|
||||
bigenHelper{e.b[:4], e.w}.writeUint32(uint32(v))
|
||||
} else { // if v <= math.MaxUint64 {
|
||||
e.w.writen1(bd + 3)
|
||||
bigenHelper{e.b[:8], e.w}.writeUint64(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encLen(bd byte, length int) {
|
||||
if length == 0 {
|
||||
e.w.writen1(bd)
|
||||
} else if length <= math.MaxUint8 {
|
||||
e.w.writen1(bd + 1)
|
||||
e.w.writen1(uint8(length))
|
||||
} else if length <= math.MaxUint16 {
|
||||
e.w.writen1(bd + 2)
|
||||
bigenHelper{e.b[:2], e.w}.writeUint16(uint16(length))
|
||||
} else if int64(length) <= math.MaxUint32 {
|
||||
e.w.writen1(bd + 3)
|
||||
bigenHelper{e.b[:4], e.w}.writeUint32(uint32(length))
|
||||
} else {
|
||||
e.w.writen1(bd + 4)
|
||||
bigenHelper{e.b[:8], e.w}.writeUint64(uint64(length))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) EncodeExt(rv interface{}, xtag uint64, ext Ext, _ *Encoder) {
|
||||
bs := ext.WriteExt(rv)
|
||||
if bs == nil {
|
||||
e.EncodeNil()
|
||||
return
|
||||
}
|
||||
e.encodeExtPreamble(uint8(xtag), len(bs))
|
||||
e.w.writeb(bs)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) EncodeRawExt(re *RawExt, _ *Encoder) {
|
||||
e.encodeExtPreamble(uint8(re.Tag), len(re.Data))
|
||||
e.w.writeb(re.Data)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeExtPreamble(xtag byte, length int) {
|
||||
e.encLen(simpleVdExt, length)
|
||||
e.w.writen1(xtag)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) WriteArrayStart(length int) {
|
||||
e.encLen(simpleVdArray, length)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) WriteMapStart(length int) {
|
||||
e.encLen(simpleVdMap, length)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) EncodeString(c charEncoding, v string) {
|
||||
e.encLen(simpleVdString, len(v))
|
||||
e.w.writestr(v)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) EncodeSymbol(v string) {
|
||||
e.EncodeString(c_UTF8, v)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) EncodeStringBytes(c charEncoding, v []byte) {
|
||||
e.encLen(simpleVdByteArray, len(v))
|
||||
e.w.writeb(v)
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
|
||||
type simpleDecDriver struct {
|
||||
d *Decoder
|
||||
h *SimpleHandle
|
||||
r decReader
|
||||
bdRead bool
|
||||
bd byte
|
||||
br bool // bytes reader
|
||||
b [scratchByteArrayLen]byte
|
||||
noBuiltInTypes
|
||||
// noStreamingCodec
|
||||
decDriverNoopContainerReader
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) readNextBd() {
|
||||
d.bd = d.r.readn1()
|
||||
d.bdRead = true
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) uncacheRead() {
|
||||
if d.bdRead {
|
||||
d.r.unreadn1()
|
||||
d.bdRead = false
|
||||
}
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) ContainerType() (vt valueType) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == simpleVdNil {
|
||||
return valueTypeNil
|
||||
} else if d.bd == simpleVdByteArray || d.bd == simpleVdByteArray+1 ||
|
||||
d.bd == simpleVdByteArray+2 || d.bd == simpleVdByteArray+3 || d.bd == simpleVdByteArray+4 {
|
||||
return valueTypeBytes
|
||||
} else if d.bd == simpleVdString || d.bd == simpleVdString+1 ||
|
||||
d.bd == simpleVdString+2 || d.bd == simpleVdString+3 || d.bd == simpleVdString+4 {
|
||||
return valueTypeString
|
||||
} else if d.bd == simpleVdArray || d.bd == simpleVdArray+1 ||
|
||||
d.bd == simpleVdArray+2 || d.bd == simpleVdArray+3 || d.bd == simpleVdArray+4 {
|
||||
return valueTypeArray
|
||||
} else if d.bd == simpleVdMap || d.bd == simpleVdMap+1 ||
|
||||
d.bd == simpleVdMap+2 || d.bd == simpleVdMap+3 || d.bd == simpleVdMap+4 {
|
||||
return valueTypeMap
|
||||
} else {
|
||||
// d.d.errorf("isContainerType: unsupported parameter: %v", vt)
|
||||
}
|
||||
return valueTypeUnset
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) TryDecodeAsNil() bool {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == simpleVdNil {
|
||||
d.bdRead = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) decCheckInteger() (ui uint64, neg bool) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
switch d.bd {
|
||||
case simpleVdPosInt:
|
||||
ui = uint64(d.r.readn1())
|
||||
case simpleVdPosInt + 1:
|
||||
ui = uint64(bigen.Uint16(d.r.readx(2)))
|
||||
case simpleVdPosInt + 2:
|
||||
ui = uint64(bigen.Uint32(d.r.readx(4)))
|
||||
case simpleVdPosInt + 3:
|
||||
ui = uint64(bigen.Uint64(d.r.readx(8)))
|
||||
case simpleVdNegInt:
|
||||
ui = uint64(d.r.readn1())
|
||||
neg = true
|
||||
case simpleVdNegInt + 1:
|
||||
ui = uint64(bigen.Uint16(d.r.readx(2)))
|
||||
neg = true
|
||||
case simpleVdNegInt + 2:
|
||||
ui = uint64(bigen.Uint32(d.r.readx(4)))
|
||||
neg = true
|
||||
case simpleVdNegInt + 3:
|
||||
ui = uint64(bigen.Uint64(d.r.readx(8)))
|
||||
neg = true
|
||||
default:
|
||||
d.d.errorf("decIntAny: Integer only valid from pos/neg integer1..8. Invalid descriptor: %v", d.bd)
|
||||
return
|
||||
}
|
||||
// don't do this check, because callers may only want the unsigned value.
|
||||
// if ui > math.MaxInt64 {
|
||||
// d.d.errorf("decIntAny: Integer out of range for signed int64: %v", ui)
|
||||
// return
|
||||
// }
|
||||
return
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) DecodeInt(bitsize uint8) (i int64) {
|
||||
ui, neg := d.decCheckInteger()
|
||||
i, overflow := chkOvf.SignedInt(ui)
|
||||
if overflow {
|
||||
d.d.errorf("simple: overflow converting %v to signed integer", ui)
|
||||
return
|
||||
}
|
||||
if neg {
|
||||
i = -i
|
||||
}
|
||||
if chkOvf.Int(i, bitsize) {
|
||||
d.d.errorf("simple: overflow integer: %v", i)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) DecodeUint(bitsize uint8) (ui uint64) {
|
||||
ui, neg := d.decCheckInteger()
|
||||
if neg {
|
||||
d.d.errorf("Assigning negative signed value to unsigned type")
|
||||
return
|
||||
}
|
||||
if chkOvf.Uint(ui, bitsize) {
|
||||
d.d.errorf("simple: overflow integer: %v", ui)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) DecodeFloat(chkOverflow32 bool) (f float64) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == simpleVdFloat32 {
|
||||
f = float64(math.Float32frombits(bigen.Uint32(d.r.readx(4))))
|
||||
} else if d.bd == simpleVdFloat64 {
|
||||
f = math.Float64frombits(bigen.Uint64(d.r.readx(8)))
|
||||
} else {
|
||||
if d.bd >= simpleVdPosInt && d.bd <= simpleVdNegInt+3 {
|
||||
f = float64(d.DecodeInt(64))
|
||||
} else {
|
||||
d.d.errorf("Float only valid from float32/64: Invalid descriptor: %v", d.bd)
|
||||
return
|
||||
}
|
||||
}
|
||||
if chkOverflow32 && chkOvf.Float32(f) {
|
||||
d.d.errorf("msgpack: float32 overflow: %v", f)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
// bool can be decoded from bool only (single byte).
|
||||
func (d *simpleDecDriver) DecodeBool() (b bool) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == simpleVdTrue {
|
||||
b = true
|
||||
} else if d.bd == simpleVdFalse {
|
||||
} else {
|
||||
d.d.errorf("Invalid single-byte value for bool: %s: %x", msgBadDesc, d.bd)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) ReadMapStart() (length int) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
d.bdRead = false
|
||||
return d.decLen()
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) ReadArrayStart() (length int) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
d.bdRead = false
|
||||
return d.decLen()
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) decLen() int {
|
||||
switch d.bd % 8 {
|
||||
case 0:
|
||||
return 0
|
||||
case 1:
|
||||
return int(d.r.readn1())
|
||||
case 2:
|
||||
return int(bigen.Uint16(d.r.readx(2)))
|
||||
case 3:
|
||||
ui := uint64(bigen.Uint32(d.r.readx(4)))
|
||||
if chkOvf.Uint(ui, intBitsize) {
|
||||
d.d.errorf("simple: overflow integer: %v", ui)
|
||||
return 0
|
||||
}
|
||||
return int(ui)
|
||||
case 4:
|
||||
ui := bigen.Uint64(d.r.readx(8))
|
||||
if chkOvf.Uint(ui, intBitsize) {
|
||||
d.d.errorf("simple: overflow integer: %v", ui)
|
||||
return 0
|
||||
}
|
||||
return int(ui)
|
||||
}
|
||||
d.d.errorf("decLen: Cannot read length: bd%%8 must be in range 0..4. Got: %d", d.bd%8)
|
||||
return -1
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) DecodeString() (s string) {
|
||||
return string(d.DecodeBytes(d.b[:], true))
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) DecodeStringAsBytes() (s []byte) {
|
||||
return d.DecodeBytes(d.b[:], true)
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) DecodeBytes(bs []byte, zerocopy bool) (bsOut []byte) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
if d.bd == simpleVdNil {
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
clen := d.decLen()
|
||||
d.bdRead = false
|
||||
if zerocopy {
|
||||
if d.br {
|
||||
return d.r.readx(clen)
|
||||
} else if len(bs) == 0 {
|
||||
bs = d.b[:]
|
||||
}
|
||||
}
|
||||
return decByteSlice(d.r, clen, d.d.h.MaxInitLen, bs)
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) DecodeExt(rv interface{}, xtag uint64, ext Ext) (realxtag uint64) {
|
||||
if xtag > 0xff {
|
||||
d.d.errorf("decodeExt: tag must be <= 0xff; got: %v", xtag)
|
||||
return
|
||||
}
|
||||
realxtag1, xbs := d.decodeExtV(ext != nil, uint8(xtag))
|
||||
realxtag = uint64(realxtag1)
|
||||
if ext == nil {
|
||||
re := rv.(*RawExt)
|
||||
re.Tag = realxtag
|
||||
re.Data = detachZeroCopyBytes(d.br, re.Data, xbs)
|
||||
} else {
|
||||
ext.ReadExt(rv, xbs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) decodeExtV(verifyTag bool, tag byte) (xtag byte, xbs []byte) {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
switch d.bd {
|
||||
case simpleVdExt, simpleVdExt + 1, simpleVdExt + 2, simpleVdExt + 3, simpleVdExt + 4:
|
||||
l := d.decLen()
|
||||
xtag = d.r.readn1()
|
||||
if verifyTag && xtag != tag {
|
||||
d.d.errorf("Wrong extension tag. Got %b. Expecting: %v", xtag, tag)
|
||||
return
|
||||
}
|
||||
xbs = d.r.readx(l)
|
||||
case simpleVdByteArray, simpleVdByteArray + 1, simpleVdByteArray + 2, simpleVdByteArray + 3, simpleVdByteArray + 4:
|
||||
xbs = d.DecodeBytes(nil, true)
|
||||
default:
|
||||
d.d.errorf("Invalid d.bd for extensions (Expecting extensions or byte array). Got: 0x%x", d.bd)
|
||||
return
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) DecodeNaked() {
|
||||
if !d.bdRead {
|
||||
d.readNextBd()
|
||||
}
|
||||
|
||||
n := d.d.n
|
||||
var decodeFurther bool
|
||||
|
||||
switch d.bd {
|
||||
case simpleVdNil:
|
||||
n.v = valueTypeNil
|
||||
case simpleVdFalse:
|
||||
n.v = valueTypeBool
|
||||
n.b = false
|
||||
case simpleVdTrue:
|
||||
n.v = valueTypeBool
|
||||
n.b = true
|
||||
case simpleVdPosInt, simpleVdPosInt + 1, simpleVdPosInt + 2, simpleVdPosInt + 3:
|
||||
if d.h.SignedInteger {
|
||||
n.v = valueTypeInt
|
||||
n.i = d.DecodeInt(64)
|
||||
} else {
|
||||
n.v = valueTypeUint
|
||||
n.u = d.DecodeUint(64)
|
||||
}
|
||||
case simpleVdNegInt, simpleVdNegInt + 1, simpleVdNegInt + 2, simpleVdNegInt + 3:
|
||||
n.v = valueTypeInt
|
||||
n.i = d.DecodeInt(64)
|
||||
case simpleVdFloat32:
|
||||
n.v = valueTypeFloat
|
||||
n.f = d.DecodeFloat(true)
|
||||
case simpleVdFloat64:
|
||||
n.v = valueTypeFloat
|
||||
n.f = d.DecodeFloat(false)
|
||||
case simpleVdString, simpleVdString + 1, simpleVdString + 2, simpleVdString + 3, simpleVdString + 4:
|
||||
n.v = valueTypeString
|
||||
n.s = d.DecodeString()
|
||||
case simpleVdByteArray, simpleVdByteArray + 1, simpleVdByteArray + 2, simpleVdByteArray + 3, simpleVdByteArray + 4:
|
||||
n.v = valueTypeBytes
|
||||
n.l = d.DecodeBytes(nil, false)
|
||||
case simpleVdExt, simpleVdExt + 1, simpleVdExt + 2, simpleVdExt + 3, simpleVdExt + 4:
|
||||
n.v = valueTypeExt
|
||||
l := d.decLen()
|
||||
n.u = uint64(d.r.readn1())
|
||||
n.l = d.r.readx(l)
|
||||
case simpleVdArray, simpleVdArray + 1, simpleVdArray + 2, simpleVdArray + 3, simpleVdArray + 4:
|
||||
n.v = valueTypeArray
|
||||
decodeFurther = true
|
||||
case simpleVdMap, simpleVdMap + 1, simpleVdMap + 2, simpleVdMap + 3, simpleVdMap + 4:
|
||||
n.v = valueTypeMap
|
||||
decodeFurther = true
|
||||
default:
|
||||
d.d.errorf("decodeNaked: Unrecognized d.bd: 0x%x", d.bd)
|
||||
}
|
||||
|
||||
if !decodeFurther {
|
||||
d.bdRead = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
|
||||
// SimpleHandle is a Handle for a very simple encoding format.
|
||||
//
|
||||
// simple is a simplistic codec similar to binc, but not as compact.
|
||||
// - Encoding of a value is always preceded by the descriptor byte (bd)
|
||||
// - True, false, nil are encoded fully in 1 byte (the descriptor)
|
||||
// - Integers (intXXX, uintXXX) are encoded in 1, 2, 4 or 8 bytes (plus a descriptor byte).
|
||||
// There are positive (uintXXX and intXXX >= 0) and negative (intXXX < 0) integers.
|
||||
// - Floats are encoded in 4 or 8 bytes (plus a descriptor byte)
|
||||
// - Lenght of containers (strings, bytes, array, map, extensions)
|
||||
// are encoded in 0, 1, 2, 4 or 8 bytes.
|
||||
// Zero-length containers have no length encoded.
|
||||
// For others, the number of bytes is given by pow(2, bd%3)
|
||||
// - maps are encoded as [bd] [length] [[key][value]]...
|
||||
// - arrays are encoded as [bd] [length] [value]...
|
||||
// - extensions are encoded as [bd] [length] [tag] [byte]...
|
||||
// - strings/bytearrays are encoded as [bd] [length] [byte]...
|
||||
//
|
||||
// The full spec will be published soon.
|
||||
type SimpleHandle struct {
|
||||
BasicHandle
|
||||
binaryEncodingType
|
||||
noElemSeparators
|
||||
}
|
||||
|
||||
func (h *SimpleHandle) SetBytesExt(rt reflect.Type, tag uint64, ext BytesExt) (err error) {
|
||||
return h.SetExt(rt, tag, &setExtWrapper{b: ext})
|
||||
}
|
||||
|
||||
func (h *SimpleHandle) newEncDriver(e *Encoder) encDriver {
|
||||
return &simpleEncDriver{e: e, w: e.w, h: h}
|
||||
}
|
||||
|
||||
func (h *SimpleHandle) newDecDriver(d *Decoder) decDriver {
|
||||
return &simpleDecDriver{d: d, h: h, r: d.r, br: d.bytes}
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) reset() {
|
||||
e.w = e.e.w
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) reset() {
|
||||
d.r, d.br = d.d.r, d.d.bytes
|
||||
d.bd, d.bdRead = 0, false
|
||||
}
|
||||
|
||||
var _ decDriver = (*simpleDecDriver)(nil)
|
||||
var _ encDriver = (*simpleEncDriver)(nil)
|
220
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/time.go
generated
vendored
Normal file
220
vendor/github.com/coreos/etcd/vendor/github.com/ugorji/go/codec/time.go
generated
vendored
Normal file
@ -0,0 +1,220 @@
|
||||
// Copyright (c) 2012-2015 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a MIT license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
var timeDigits = [...]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
|
||||
|
||||
type timeExt struct{}
|
||||
|
||||
func (x timeExt) WriteExt(v interface{}) (bs []byte) {
|
||||
switch v2 := v.(type) {
|
||||
case time.Time:
|
||||
bs = encodeTime(v2)
|
||||
case *time.Time:
|
||||
bs = encodeTime(*v2)
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported format for time conversion: expecting time.Time; got %T", v2))
|
||||
}
|
||||
return
|
||||
}
|
||||
func (x timeExt) ReadExt(v interface{}, bs []byte) {
|
||||
tt, err := decodeTime(bs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
*(v.(*time.Time)) = tt
|
||||
}
|
||||
|
||||
func (x timeExt) ConvertExt(v interface{}) interface{} {
|
||||
return x.WriteExt(v)
|
||||
}
|
||||
func (x timeExt) UpdateExt(v interface{}, src interface{}) {
|
||||
x.ReadExt(v, src.([]byte))
|
||||
}
|
||||
|
||||
// EncodeTime encodes a time.Time as a []byte, including
|
||||
// information on the instant in time and UTC offset.
|
||||
//
|
||||
// Format Description
|
||||
//
|
||||
// A timestamp is composed of 3 components:
|
||||
//
|
||||
// - secs: signed integer representing seconds since unix epoch
|
||||
// - nsces: unsigned integer representing fractional seconds as a
|
||||
// nanosecond offset within secs, in the range 0 <= nsecs < 1e9
|
||||
// - tz: signed integer representing timezone offset in minutes east of UTC,
|
||||
// and a dst (daylight savings time) flag
|
||||
//
|
||||
// When encoding a timestamp, the first byte is the descriptor, which
|
||||
// defines which components are encoded and how many bytes are used to
|
||||
// encode secs and nsecs components. *If secs/nsecs is 0 or tz is UTC, it
|
||||
// is not encoded in the byte array explicitly*.
|
||||
//
|
||||
// Descriptor 8 bits are of the form `A B C DDD EE`:
|
||||
// A: Is secs component encoded? 1 = true
|
||||
// B: Is nsecs component encoded? 1 = true
|
||||
// C: Is tz component encoded? 1 = true
|
||||
// DDD: Number of extra bytes for secs (range 0-7).
|
||||
// If A = 1, secs encoded in DDD+1 bytes.
|
||||
// If A = 0, secs is not encoded, and is assumed to be 0.
|
||||
// If A = 1, then we need at least 1 byte to encode secs.
|
||||
// DDD says the number of extra bytes beyond that 1.
|
||||
// E.g. if DDD=0, then secs is represented in 1 byte.
|
||||
// if DDD=2, then secs is represented in 3 bytes.
|
||||
// EE: Number of extra bytes for nsecs (range 0-3).
|
||||
// If B = 1, nsecs encoded in EE+1 bytes (similar to secs/DDD above)
|
||||
//
|
||||
// Following the descriptor bytes, subsequent bytes are:
|
||||
//
|
||||
// secs component encoded in `DDD + 1` bytes (if A == 1)
|
||||
// nsecs component encoded in `EE + 1` bytes (if B == 1)
|
||||
// tz component encoded in 2 bytes (if C == 1)
|
||||
//
|
||||
// secs and nsecs components are integers encoded in a BigEndian
|
||||
// 2-complement encoding format.
|
||||
//
|
||||
// tz component is encoded as 2 bytes (16 bits). Most significant bit 15 to
|
||||
// Least significant bit 0 are described below:
|
||||
//
|
||||
// Timezone offset has a range of -12:00 to +14:00 (ie -720 to +840 minutes).
|
||||
// Bit 15 = have\_dst: set to 1 if we set the dst flag.
|
||||
// Bit 14 = dst\_on: set to 1 if dst is in effect at the time, or 0 if not.
|
||||
// Bits 13..0 = timezone offset in minutes. It is a signed integer in Big Endian format.
|
||||
//
|
||||
func encodeTime(t time.Time) []byte {
|
||||
//t := rv.Interface().(time.Time)
|
||||
tsecs, tnsecs := t.Unix(), t.Nanosecond()
|
||||
var (
|
||||
bd byte
|
||||
btmp [8]byte
|
||||
bs [16]byte
|
||||
i int = 1
|
||||
)
|
||||
l := t.Location()
|
||||
if l == time.UTC {
|
||||
l = nil
|
||||
}
|
||||
if tsecs != 0 {
|
||||
bd = bd | 0x80
|
||||
bigen.PutUint64(btmp[:], uint64(tsecs))
|
||||
f := pruneSignExt(btmp[:], tsecs >= 0)
|
||||
bd = bd | (byte(7-f) << 2)
|
||||
copy(bs[i:], btmp[f:])
|
||||
i = i + (8 - f)
|
||||
}
|
||||
if tnsecs != 0 {
|
||||
bd = bd | 0x40
|
||||
bigen.PutUint32(btmp[:4], uint32(tnsecs))
|
||||
f := pruneSignExt(btmp[:4], true)
|
||||
bd = bd | byte(3-f)
|
||||
copy(bs[i:], btmp[f:4])
|
||||
i = i + (4 - f)
|
||||
}
|
||||
if l != nil {
|
||||
bd = bd | 0x20
|
||||
// Note that Go Libs do not give access to dst flag.
|
||||
_, zoneOffset := t.Zone()
|
||||
//zoneName, zoneOffset := t.Zone()
|
||||
zoneOffset /= 60
|
||||
z := uint16(zoneOffset)
|
||||
bigen.PutUint16(btmp[:2], z)
|
||||
// clear dst flags
|
||||
bs[i] = btmp[0] & 0x3f
|
||||
bs[i+1] = btmp[1]
|
||||
i = i + 2
|
||||
}
|
||||
bs[0] = bd
|
||||
return bs[0:i]
|
||||
}
|
||||
|
||||
// DecodeTime decodes a []byte into a time.Time.
|
||||
func decodeTime(bs []byte) (tt time.Time, err error) {
|
||||
bd := bs[0]
|
||||
var (
|
||||
tsec int64
|
||||
tnsec uint32
|
||||
tz uint16
|
||||
i byte = 1
|
||||
i2 byte
|
||||
n byte
|
||||
)
|
||||
if bd&(1<<7) != 0 {
|
||||
var btmp [8]byte
|
||||
n = ((bd >> 2) & 0x7) + 1
|
||||
i2 = i + n
|
||||
copy(btmp[8-n:], bs[i:i2])
|
||||
//if first bit of bs[i] is set, then fill btmp[0..8-n] with 0xff (ie sign extend it)
|
||||
if bs[i]&(1<<7) != 0 {
|
||||
copy(btmp[0:8-n], bsAll0xff)
|
||||
//for j,k := byte(0), 8-n; j < k; j++ { btmp[j] = 0xff }
|
||||
}
|
||||
i = i2
|
||||
tsec = int64(bigen.Uint64(btmp[:]))
|
||||
}
|
||||
if bd&(1<<6) != 0 {
|
||||
var btmp [4]byte
|
||||
n = (bd & 0x3) + 1
|
||||
i2 = i + n
|
||||
copy(btmp[4-n:], bs[i:i2])
|
||||
i = i2
|
||||
tnsec = bigen.Uint32(btmp[:])
|
||||
}
|
||||
if bd&(1<<5) == 0 {
|
||||
tt = time.Unix(tsec, int64(tnsec)).UTC()
|
||||
return
|
||||
}
|
||||
// In stdlib time.Parse, when a date is parsed without a zone name, it uses "" as zone name.
|
||||
// However, we need name here, so it can be shown when time is printed.
|
||||
// Zone name is in form: UTC-08:00.
|
||||
// Note that Go Libs do not give access to dst flag, so we ignore dst bits
|
||||
|
||||
i2 = i + 2
|
||||
tz = bigen.Uint16(bs[i:i2])
|
||||
i = i2
|
||||
// sign extend sign bit into top 2 MSB (which were dst bits):
|
||||
if tz&(1<<13) == 0 { // positive
|
||||
tz = tz & 0x3fff //clear 2 MSBs: dst bits
|
||||
} else { // negative
|
||||
tz = tz | 0xc000 //set 2 MSBs: dst bits
|
||||
//tzname[3] = '-' (TODO: verify. this works here)
|
||||
}
|
||||
tzint := int16(tz)
|
||||
if tzint == 0 {
|
||||
tt = time.Unix(tsec, int64(tnsec)).UTC()
|
||||
} else {
|
||||
// For Go Time, do not use a descriptive timezone.
|
||||
// It's unnecessary, and makes it harder to do a reflect.DeepEqual.
|
||||
// The Offset already tells what the offset should be, if not on UTC and unknown zone name.
|
||||
// var zoneName = timeLocUTCName(tzint)
|
||||
tt = time.Unix(tsec, int64(tnsec)).In(time.FixedZone("", int(tzint)*60))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// func timeLocUTCName(tzint int16) string {
|
||||
// if tzint == 0 {
|
||||
// return "UTC"
|
||||
// }
|
||||
// var tzname = []byte("UTC+00:00")
|
||||
// //tzname := fmt.Sprintf("UTC%s%02d:%02d", tzsign, tz/60, tz%60) //perf issue using Sprintf. inline below.
|
||||
// //tzhr, tzmin := tz/60, tz%60 //faster if u convert to int first
|
||||
// var tzhr, tzmin int16
|
||||
// if tzint < 0 {
|
||||
// tzname[3] = '-' // (TODO: verify. this works here)
|
||||
// tzhr, tzmin = -tzint/60, (-tzint)%60
|
||||
// } else {
|
||||
// tzhr, tzmin = tzint/60, tzint%60
|
||||
// }
|
||||
// tzname[4] = timeDigits[tzhr/10]
|
||||
// tzname[5] = timeDigits[tzhr%10]
|
||||
// tzname[7] = timeDigits[tzmin/10]
|
||||
// tzname[8] = timeDigits[tzmin%10]
|
||||
// return string(tzname)
|
||||
// //return time.FixedZone(string(tzname), int(tzint)*60)
|
||||
// }
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user