go-ayo/common을 gocommon으로 분리
This commit is contained in:
456
azure/bot.go
Normal file
456
azure/bot.go
Normal file
@ -0,0 +1,456 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ChannelAccount struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
AadObjectId string `json:"aadObjectId,omitempty"`
|
||||
Role string `json:"role,omitempty"`
|
||||
}
|
||||
|
||||
type AttachmentData struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
OriginalBase64 string `json:"originalBase64,omitempty"`
|
||||
ThumbnailBase64 string `json:"thumbnailBase64,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
type MessageActivity struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
LocalTimestamp time.Time `json:"localTimestamp,omitempty"`
|
||||
Id string `json:"id"`
|
||||
ChannelId string `json:"channelId,omitempty"`
|
||||
ServiceUrl string `json:"serviceUrl,omitempty"`
|
||||
Message string
|
||||
Mentions []string
|
||||
From ChannelAccount `json:"from,omitempty"`
|
||||
Conversation *struct {
|
||||
ConversationType string `json:"conversationType,omitempty"`
|
||||
TenantId string `json:"tenantId,omitempty"`
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"conversation,omitempty"`
|
||||
Recipient *struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Id string `json:"id"`
|
||||
} `json:"recipient,omitempty"`
|
||||
Entities []interface{} `json:"entities,omitempty"`
|
||||
ChannelData *struct {
|
||||
TeamsChannelId string `json:"teamsChannelId,omitempty"`
|
||||
TeamsTeamId string `json:"teamsTeamId,omitempty"`
|
||||
Channel *struct {
|
||||
Id string `json:"id"`
|
||||
} `json:"channel,omitempty"`
|
||||
Team *struct {
|
||||
Id string `json:"id"`
|
||||
} `json:"team,omitempty"`
|
||||
Tenant *struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
} `json:"tenant,omitempty"`
|
||||
} `json:"channelData,omitempty"`
|
||||
ReplyToId string `json:"replyToId,omitempty"`
|
||||
Value map[string]string `json:"value,omitempty"`
|
||||
Locale string `json:"Locale,omitempty"`
|
||||
LocalTimezone string `json:"LocalTimezone,omitempty"`
|
||||
}
|
||||
|
||||
type AdaptiveCard struct {
|
||||
Body []interface{}
|
||||
Actions []interface{}
|
||||
}
|
||||
|
||||
type tempMessageActivity struct {
|
||||
MessageActivity
|
||||
RawText string `json:"text,omitempty"`
|
||||
}
|
||||
|
||||
var botFrameworkAuth = accessToken{}
|
||||
|
||||
func ParseMessageActivity(src []byte) (MessageActivity, error) {
|
||||
var message tempMessageActivity
|
||||
err := json.Unmarshal(src, &message)
|
||||
if err != nil {
|
||||
return MessageActivity{}, err
|
||||
}
|
||||
|
||||
for len(message.RawText) > 0 {
|
||||
s := strings.Index(message.RawText, "<at>")
|
||||
if s < 0 {
|
||||
break
|
||||
}
|
||||
message.Message += message.RawText[:s]
|
||||
message.RawText = message.RawText[s+4:]
|
||||
e := strings.Index(message.RawText, "</at>")
|
||||
if e < 0 {
|
||||
break
|
||||
}
|
||||
mention := message.RawText[:e]
|
||||
message.RawText = message.RawText[e+5:]
|
||||
message.Mentions = append(message.Mentions, mention)
|
||||
}
|
||||
message.Message += strings.Trim(message.RawText, " ")
|
||||
message.Message = strings.TrimSpace(message.Message)
|
||||
if !strings.HasSuffix(message.ServiceUrl, "/") {
|
||||
message.ServiceUrl += "/"
|
||||
}
|
||||
return message.MessageActivity, nil
|
||||
}
|
||||
|
||||
func sendRequireMessage(requrl string, method string, stream io.Reader) (string, error) {
|
||||
req, err := http.NewRequest(method, requrl, stream)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
auth, err := botFrameworkAuth.getAuthoizationToken()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Authorization", auth)
|
||||
req.Header.Set("Content-type", "application/json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
bt, _ := io.ReadAll(resp.Body)
|
||||
var resultdoc map[string]interface{}
|
||||
json.Unmarshal(bt, &resultdoc)
|
||||
if len(resultdoc) > 0 {
|
||||
if idraw, ok := resultdoc["id"]; ok {
|
||||
return idraw.(string), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
func putMessage(requrl string, stream io.Reader) (string, error) {
|
||||
return sendRequireMessage(requrl, "PUT", stream)
|
||||
}
|
||||
|
||||
func postMessage(requrl string, stream io.Reader) (string, error) {
|
||||
return sendRequireMessage(requrl, "POST", stream)
|
||||
}
|
||||
|
||||
func unescapeUnicodeCharactersInJSON(src []byte) (string, error) {
|
||||
str, err := strconv.Unquote(strings.Replace(strconv.Quote(string(src)), `\\u`, `\u`, -1))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func (m *MessageActivity) SendStreamOnChannel(reader io.Reader) (string, error) {
|
||||
serviceUrl := m.ServiceUrl
|
||||
|
||||
// mention 없이 여기 왔으면 private 채팅이다. reply를 해야함
|
||||
var conversationId string
|
||||
if m.ChannelData.Channel == nil {
|
||||
conversationId = m.Conversation.Id
|
||||
} else {
|
||||
conversationId = m.ChannelData.Channel.Id
|
||||
}
|
||||
|
||||
requrl := fmt.Sprintf("%sv3/conversations/%s/activities", serviceUrl, conversationId)
|
||||
|
||||
return postMessage(requrl, reader)
|
||||
}
|
||||
|
||||
func (m *MessageActivity) UploadFile(attachment AttachmentData) (string, error) {
|
||||
// POST /v3/conversations/{conversationId}/attachments
|
||||
// Request body An AttachmentData object.
|
||||
// Returns A ResourceResponse object. The id property specifies the attachment ID that can be used with the Get Attachment Info operation and the Get Attachment operation.
|
||||
serviceUrl := m.ServiceUrl
|
||||
conversationId := m.Conversation.Id
|
||||
bt, _ := json.Marshal(attachment)
|
||||
|
||||
requrl := fmt.Sprintf("%sv3/conversations/%s/attachments", serviceUrl, conversationId)
|
||||
return postMessage(requrl, bytes.NewReader([]byte(bt)))
|
||||
}
|
||||
|
||||
func makeAttachmentDocs(attachments []AdaptiveCard) []interface{} {
|
||||
var attachconv []interface{}
|
||||
for _, attachment := range attachments {
|
||||
attachconv = append(attachconv, map[string]interface{}{
|
||||
"contentType": "application/vnd.microsoft.card.adaptive",
|
||||
"content": map[string]interface{}{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.3",
|
||||
"body": attachment.Body,
|
||||
},
|
||||
})
|
||||
}
|
||||
return attachconv
|
||||
}
|
||||
|
||||
// func (m *MessageActivity) update(activityId string, message string, attachments ...AdaptiveCard) (string, error) {
|
||||
// serviceUrl := m.ServiceUrl
|
||||
// conversationId := m.Conversation.Id
|
||||
|
||||
// attachconv := makeAttachmentDocs(attachments)
|
||||
// activity := map[string]interface{}{
|
||||
// "type": "message",
|
||||
// "text": strings.ReplaceAll(message, `"`, `\"`),
|
||||
// }
|
||||
// if len(attachconv) > 0 {
|
||||
// activity["attachments"] = attachconv
|
||||
// }
|
||||
|
||||
// reqdoc, _ := json.Marshal(activity)
|
||||
// unq, _ := unescapeUnicodeCharactersInJSON(reqdoc)
|
||||
|
||||
// requrl := fmt.Sprintf("%sv3/conversations/%s/activities/%s", serviceUrl, conversationId, activityId)
|
||||
// return putMessage(requrl, bytes.NewReader([]byte(unq)))
|
||||
// }
|
||||
|
||||
// func (m *MessageActivity) reply(message string, mention bool, attachments ...AdaptiveCard) (string, error) {
|
||||
// serviceUrl := m.ServiceUrl
|
||||
// conversationId := m.Conversation.Id
|
||||
|
||||
// attachconv := makeAttachmentDocs(attachments)
|
||||
|
||||
// var activity map[string]interface{}
|
||||
// if mention {
|
||||
// mentionText := fmt.Sprintf(`<at>%s</at>`, m.From.Name)
|
||||
// activity = map[string]interface{}{
|
||||
// "type": "message",
|
||||
// "text": mentionText + " " + strings.ReplaceAll(message, `"`, `\"`),
|
||||
// "entities": []interface{}{
|
||||
// map[string]interface{}{
|
||||
// "mentioned": m.From,
|
||||
// "text": mentionText,
|
||||
// "type": "mention",
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// } else {
|
||||
// activity = map[string]interface{}{
|
||||
// "type": "message",
|
||||
// "text": strings.ReplaceAll(message, `"`, `\"`),
|
||||
// }
|
||||
// }
|
||||
|
||||
// activity["replyToId"] = m.Id
|
||||
// if len(attachconv) > 0 {
|
||||
// activity["attachments"] = attachconv
|
||||
// }
|
||||
|
||||
// reqdoc, _ := json.Marshal(activity)
|
||||
// unq, _ := unescapeUnicodeCharactersInJSON(reqdoc)
|
||||
|
||||
// requrl := fmt.Sprintf("%sv3/conversations/%s/activities", serviceUrl, conversationId)
|
||||
// return postMessage(requrl, bytes.NewReader([]byte(unq)))
|
||||
// }
|
||||
|
||||
// func (m *MessageActivity) ReplyWithMentionf(format string, val ...interface{}) (string, error) {
|
||||
// return m.ReplyWithMention(fmt.Sprintf(format, val...))
|
||||
// }
|
||||
|
||||
// func (m *MessageActivity) ReplyWithMention(message string, attachments ...AdaptiveCard) (string, error) {
|
||||
// return m.reply(message, true, attachments...)
|
||||
// }
|
||||
|
||||
// func (m *MessageActivity) Reply(message string, attachments ...AdaptiveCard) (string, error) {
|
||||
// return m.reply(message, false, attachments...)
|
||||
// }
|
||||
|
||||
// func (m *MessageActivity) Replyf(format string, val ...interface{}) (string, error) {
|
||||
// return m.Reply(fmt.Sprintf(format, val...))
|
||||
// }
|
||||
|
||||
// func (m *MessageActivity) Update(activityId string, message string, attachments ...AdaptiveCard) (string, error) {
|
||||
// return m.update(activityId, message, attachments...)
|
||||
// }
|
||||
|
||||
// func (m *MessageActivity) Updatef(activityId string, format string, val ...interface{}) (string, error) {
|
||||
// return m.Update(activityId, fmt.Sprintf(format, val...))
|
||||
// }
|
||||
|
||||
type MessageWrap struct {
|
||||
*MessageActivity
|
||||
Prefix string
|
||||
Mention bool
|
||||
Attachments []AdaptiveCard
|
||||
}
|
||||
|
||||
func (m *MessageActivity) MakeWrap() *MessageWrap {
|
||||
return &MessageWrap{MessageActivity: m}
|
||||
}
|
||||
|
||||
func (m *MessageWrap) WithPrefix(prefix string) *MessageWrap {
|
||||
m.Prefix = prefix
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MessageWrap) WithMention() *MessageWrap {
|
||||
m.Mention = true
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MessageWrap) WithAttachments(attachments ...AdaptiveCard) *MessageWrap {
|
||||
m.Attachments = attachments
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MessageWrap) Reply(text string) (string, error) {
|
||||
serviceUrl := m.ServiceUrl
|
||||
conversationId := m.Conversation.Id
|
||||
attachconv := makeAttachmentDocs(m.Attachments)
|
||||
|
||||
var activity map[string]interface{}
|
||||
if m.Mention {
|
||||
var mentionText string
|
||||
if len(m.Prefix) > 0 {
|
||||
mentionText = fmt.Sprintf(`**[%s]** <at>%s</at>`, m.Prefix, m.From.Name)
|
||||
} else {
|
||||
mentionText = fmt.Sprintf(`<at>%s</at>`, m.From.Name)
|
||||
}
|
||||
activity = map[string]interface{}{
|
||||
"type": "message",
|
||||
"text": mentionText + " " + strings.ReplaceAll(text, `"`, `\"`),
|
||||
"entities": []interface{}{
|
||||
map[string]interface{}{
|
||||
"mentioned": m.From,
|
||||
"text": mentionText,
|
||||
"type": "mention",
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
if len(m.Prefix) > 0 {
|
||||
text = fmt.Sprintf("**[%s]** %s", m.Prefix, text)
|
||||
}
|
||||
|
||||
activity = map[string]interface{}{
|
||||
"type": "message",
|
||||
"text": strings.ReplaceAll(text, `"`, `\"`),
|
||||
}
|
||||
}
|
||||
|
||||
activity["replyToId"] = m.Id
|
||||
if len(attachconv) > 0 {
|
||||
activity["attachments"] = attachconv
|
||||
}
|
||||
|
||||
activity["from"] = m.Recipient
|
||||
activity["channelId"] = m.ChannelId
|
||||
reqdoc, _ := json.Marshal(activity)
|
||||
unq, _ := unescapeUnicodeCharactersInJSON(reqdoc)
|
||||
|
||||
requrl := fmt.Sprintf("%sv3/conversations/%s/activities", serviceUrl, conversationId)
|
||||
return postMessage(requrl, bytes.NewReader([]byte(unq)))
|
||||
}
|
||||
|
||||
func (m *MessageWrap) Replyf(format string, args ...interface{}) (string, error) {
|
||||
return m.Reply(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (m *MessageActivity) Update(reader io.Reader) {
|
||||
serviceUrl := m.ServiceUrl
|
||||
conversationId := m.Conversation.Id
|
||||
activityId := m.ReplyToId
|
||||
|
||||
requrl := fmt.Sprintf("%sv3/conversations/%s/activities/%s", serviceUrl, conversationId, activityId)
|
||||
putMessage(requrl, reader)
|
||||
}
|
||||
|
||||
func (m *MessageWrap) Update(activityId string, text string) (string, error) {
|
||||
serviceUrl := m.ServiceUrl
|
||||
conversationId := m.Conversation.Id
|
||||
if len(m.Prefix) > 0 {
|
||||
text = fmt.Sprintf("**[%s]** %s", m.Prefix, text)
|
||||
}
|
||||
attachconv := makeAttachmentDocs(m.Attachments)
|
||||
activity := map[string]interface{}{
|
||||
"type": "message",
|
||||
"text": strings.ReplaceAll(text, `"`, `\"`),
|
||||
}
|
||||
if len(attachconv) > 0 {
|
||||
activity["attachments"] = attachconv
|
||||
}
|
||||
|
||||
reqdoc, _ := json.Marshal(activity)
|
||||
unq, _ := unescapeUnicodeCharactersInJSON(reqdoc)
|
||||
|
||||
requrl := fmt.Sprintf("%sv3/conversations/%s/activities/%s", serviceUrl, conversationId, activityId)
|
||||
return putMessage(requrl, bytes.NewReader([]byte(unq)))
|
||||
}
|
||||
|
||||
func (m *MessageWrap) Updatef(activityId string, format string, args ...interface{}) (string, error) {
|
||||
return m.Update(activityId, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
type MessageReplyCache struct {
|
||||
sourceActivity *MessageActivity
|
||||
ReplyWrap *MessageWrap
|
||||
Replyaid string
|
||||
}
|
||||
|
||||
func (c *MessageReplyCache) Serialize() string {
|
||||
bt, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
linkfilename := fmt.Sprintf("updatelink_%s.json", time.Now().Format("2006-01-02T15-04-05"))
|
||||
if os.WriteFile(linkfilename, bt, 0666) == nil {
|
||||
return linkfilename
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func DeserializeMessageReplyCache(filename string) *MessageReplyCache {
|
||||
bt, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var out MessageReplyCache
|
||||
if json.Unmarshal(bt, &out) == nil {
|
||||
return &out
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func MakeMessageReplyCache(src *MessageActivity) MessageReplyCache {
|
||||
return MessageReplyCache{
|
||||
sourceActivity: src,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *MessageReplyCache) SourceActivity() *MessageActivity {
|
||||
return ch.sourceActivity
|
||||
}
|
||||
|
||||
func (ch *MessageReplyCache) Reply(text string) {
|
||||
if ch.ReplyWrap == nil {
|
||||
hostname, _ := os.Hostname()
|
||||
ch.ReplyWrap = ch.sourceActivity.MakeWrap().WithPrefix(hostname)
|
||||
}
|
||||
|
||||
if len(ch.Replyaid) == 0 {
|
||||
ch.Replyaid, _ = ch.ReplyWrap.Reply(text)
|
||||
} else {
|
||||
ch.ReplyWrap.Update(ch.Replyaid, text)
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *MessageReplyCache) Replyf(format string, args ...interface{}) {
|
||||
ch.Reply(fmt.Sprintf(format, args...))
|
||||
}
|
||||
199
azure/func.go
Normal file
199
azure/func.go
Normal file
@ -0,0 +1,199 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
)
|
||||
|
||||
type accessToken struct {
|
||||
sync.Mutex
|
||||
typetoken string
|
||||
expireAt time.Time
|
||||
url string
|
||||
values url.Values
|
||||
}
|
||||
|
||||
var jwkCache struct {
|
||||
headerlock sync.Mutex
|
||||
typetoken string
|
||||
expireAt time.Time
|
||||
pks map[string]*rsa.PublicKey
|
||||
}
|
||||
|
||||
func microsoftAppId() string {
|
||||
val := os.Getenv("MICROSOFT_APP_ID")
|
||||
if len(val) == 0 {
|
||||
val = "b5367590-5a94-4df3-bca0-ecd4b693ddf0"
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func microsoftAppPassword() string {
|
||||
val := os.Getenv("MICROSOFT_APP_PASSWORD")
|
||||
if len(val) == 0 {
|
||||
val = "~VG1cf2-~5Fw3Wz9_4.A.XxpZPO8BwJ36y"
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func getOpenIDConfiguration(x5t string) (*rsa.PublicKey, error) {
|
||||
// https://docs.microsoft.com/ko-kr/azure/bot-service/rest-api/bot-framework-rest-connector-authentication?view=azure-bot-service-4.0
|
||||
jwkCache.headerlock.Lock()
|
||||
defer jwkCache.headerlock.Unlock()
|
||||
|
||||
if time.Now().After(jwkCache.expireAt) {
|
||||
resp, err := http.Get("https://login.botframework.com/v1/.well-known/openidconfiguration")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
bt, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var doc map[string]interface{}
|
||||
if err = json.Unmarshal(bt, &doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := doc["jwks_uri"].(string)
|
||||
resp, err = http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
bt, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(bt, &doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := doc["keys"].([]interface{})
|
||||
newPks := make(map[string]*rsa.PublicKey)
|
||||
for _, key := range keys {
|
||||
keydoc := key.(map[string]interface{})
|
||||
x5t := keydoc["x5t"].(string)
|
||||
eb := make([]byte, 4)
|
||||
nb, _ := base64.RawURLEncoding.DecodeString(keydoc["n"].(string))
|
||||
base64.RawURLEncoding.Decode(eb, []byte(keydoc["e"].(string)))
|
||||
n := big.NewInt(0).SetBytes(nb)
|
||||
e := binary.LittleEndian.Uint32(eb)
|
||||
pk := &rsa.PublicKey{
|
||||
N: n,
|
||||
E: int(e),
|
||||
}
|
||||
newPks[x5t] = pk
|
||||
}
|
||||
|
||||
jwkCache.expireAt = time.Now().Add(24 * time.Hour)
|
||||
jwkCache.pks = newPks
|
||||
}
|
||||
|
||||
return jwkCache.pks[x5t], nil
|
||||
}
|
||||
|
||||
func VerifyJWT(header string) error {
|
||||
if !strings.HasPrefix(header, "Bearer ") {
|
||||
return errors.New("invalid token")
|
||||
}
|
||||
tokenString := header[7:]
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return getOpenIDConfiguration(token.Header["x5t"].(string))
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
if claims["iss"].(string) != "https://api.botframework.com" {
|
||||
return errors.New("issuer is not valid")
|
||||
}
|
||||
if claims["aud"].(string) != microsoftAppId() {
|
||||
return errors.New("audience is not valid")
|
||||
}
|
||||
expireAt := int64(claims["exp"].(float64))
|
||||
if math.Abs(float64((expireAt-time.Now().UTC().Unix())/int64(time.Second))) >= 300 {
|
||||
return errors.New("token expired")
|
||||
}
|
||||
} else {
|
||||
return errors.New("VerifyJWT token claims failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (at *accessToken) getAuthoizationToken() (string, error) {
|
||||
at.Lock()
|
||||
defer at.Unlock()
|
||||
|
||||
if len(at.url) == 0 {
|
||||
at.url = "https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token"
|
||||
at.values = url.Values{
|
||||
"grant_type": {"client_credentials"},
|
||||
"client_id": {microsoftAppId()},
|
||||
"scope": {"https://api.botframework.com/.default"},
|
||||
"client_secret": {microsoftAppPassword()},
|
||||
}
|
||||
}
|
||||
|
||||
if time.Now().After(at.expireAt) {
|
||||
resp, err := http.PostForm(at.url, at.values)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
var doc map[string]interface{}
|
||||
err = json.Unmarshal(body, &doc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if v, ok := doc["error"]; ok {
|
||||
if desc, ok := doc["error_description"]; ok {
|
||||
return "", errors.New(desc.(string))
|
||||
}
|
||||
|
||||
return "", errors.New(v.(string))
|
||||
}
|
||||
|
||||
tokenType := doc["token_type"].(string)
|
||||
token := doc["access_token"].(string)
|
||||
expin := doc["expires_in"]
|
||||
|
||||
var tokenDur int
|
||||
switch expin := expin.(type) {
|
||||
case float64:
|
||||
tokenDur = int(expin)
|
||||
case string:
|
||||
tokenDur, _ = strconv.Atoi(expin)
|
||||
}
|
||||
|
||||
at.typetoken = tokenType + " " + token
|
||||
at.expireAt = time.Now().Add(time.Duration(tokenDur) * time.Second)
|
||||
}
|
||||
|
||||
return at.typetoken, nil
|
||||
|
||||
}
|
||||
356
azure/graph.go
Normal file
356
azure/graph.go
Normal file
@ -0,0 +1,356 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Graph struct {
|
||||
*accessToken
|
||||
tenantId string
|
||||
groupId string
|
||||
}
|
||||
|
||||
func NewGraphByEnv() *Graph {
|
||||
tenantId := os.Getenv("GRAPH_TENANT_ID")
|
||||
groupId := os.Getenv("GRAPH_GROUP_ID")
|
||||
return NewGraph(tenantId, groupId)
|
||||
}
|
||||
|
||||
func NewGraph(tenantId string, groupId string) *Graph {
|
||||
return &Graph{
|
||||
accessToken: &accessToken{
|
||||
url: fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantId),
|
||||
values: url.Values{
|
||||
"grant_type": {"client_credentials"},
|
||||
"client_id": {microsoftAppId()},
|
||||
"scope": {"https://graph.microsoft.com/.default"},
|
||||
"client_secret": {microsoftAppPassword()},
|
||||
},
|
||||
},
|
||||
tenantId: tenantId,
|
||||
groupId: groupId,
|
||||
}
|
||||
}
|
||||
|
||||
func (s Graph) GroupId() string {
|
||||
return s.groupId
|
||||
}
|
||||
|
||||
var errGroupIdMissing = errors.New("GRAPH_GROUP_ID is missing")
|
||||
|
||||
func (s Graph) ReadFile(relativePath string) (io.ReadCloser, error) {
|
||||
if len(s.groupId) == 0 {
|
||||
return nil, errGroupIdMissing
|
||||
}
|
||||
|
||||
token, err := s.getAuthoizationToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/groups/%s/drive/root:/%s:/content", s.groupId, relativePath)
|
||||
req, _ := http.NewRequest("GET", requrl, nil)
|
||||
|
||||
req.Header.Add("Authorization", token)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
return nil, errors.New(resp.Status)
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
func (s Graph) GetWebURLOfFile(relativePath string) (string, error) {
|
||||
if len(s.groupId) == 0 {
|
||||
return "", errGroupIdMissing
|
||||
}
|
||||
|
||||
token, err := s.getAuthoizationToken()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
requrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/groups/%s/drive/root:/%s:", s.groupId, relativePath)
|
||||
req, _ := http.NewRequest("GET", requrl, nil)
|
||||
|
||||
req.Header.Add("Authorization", token)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}()
|
||||
|
||||
itemdoc, _ := io.ReadAll(resp.Body)
|
||||
var item map[string]interface{}
|
||||
json.Unmarshal(itemdoc, &item)
|
||||
|
||||
url, ok := item["webUrl"]
|
||||
if ok {
|
||||
return url.(string), nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s Graph) DownloadFile(relativePath string, outputFile string) error {
|
||||
body, err := s.ReadFile(relativePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
// Create the file
|
||||
out, err := os.Create(outputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(out, body)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s Graph) UploadStream(relativePath string, reader io.Reader) (string, error) {
|
||||
if len(s.groupId) == 0 {
|
||||
return "", errGroupIdMissing
|
||||
}
|
||||
|
||||
token, err := s.getAuthoizationToken()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
requrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/groups/%s/drive/root:/%s:/content", s.groupId, relativePath)
|
||||
req, _ := http.NewRequest("PUT", requrl, reader)
|
||||
|
||||
req.Header.Add("Authorization", token)
|
||||
req.Header.Add("Content-Type", "text/plain")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
unquot, _ := unescapeUnicodeCharactersInJSON(body)
|
||||
|
||||
var drive map[string]interface{}
|
||||
err = json.Unmarshal([]byte(unquot), &drive)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if errnode, ok := drive["error"]; ok {
|
||||
errmsg := errnode.(map[string]interface{})["message"].(string)
|
||||
return "", errors.New(errmsg)
|
||||
}
|
||||
|
||||
return drive["webUrl"].(string), nil
|
||||
}
|
||||
|
||||
func (s Graph) UploadBytes(relativePath string, content []byte) (string, error) {
|
||||
return s.UploadStream(relativePath, bytes.NewReader(content))
|
||||
}
|
||||
|
||||
func (s Graph) GetChannelFilesFolderName(channel string) (string, error) {
|
||||
if len(s.groupId) == 0 {
|
||||
return "", errGroupIdMissing
|
||||
}
|
||||
|
||||
token, err := s.getAuthoizationToken()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
requrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/teams/%s/channels/%s/filesFolder", s.groupId, channel)
|
||||
req, _ := http.NewRequest("GET", requrl, nil)
|
||||
req.Header.Add("Authorization", token)
|
||||
req.Header.Add("Accept", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
unquot, _ := unescapeUnicodeCharactersInJSON(body)
|
||||
|
||||
var drive map[string]interface{}
|
||||
err = json.Unmarshal([]byte(unquot), &drive)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if errnode, ok := drive["error"]; ok {
|
||||
errmsg := errnode.(map[string]interface{})["message"].(string)
|
||||
return "", errors.New(errmsg)
|
||||
}
|
||||
|
||||
return drive["name"].(string), nil
|
||||
}
|
||||
|
||||
func (s Graph) listChildren(path string) (map[string]interface{}, error) {
|
||||
if len(s.groupId) == 0 {
|
||||
return nil, errGroupIdMissing
|
||||
}
|
||||
|
||||
token, err := s.getAuthoizationToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// groupId := "612620b7-cd90-4b3f-a9bd-d34cddf52517"
|
||||
var requrl string
|
||||
if len(path) == 0 {
|
||||
requrl = fmt.Sprintf("https://graph.microsoft.com/v1.0/groups/%s/drive/root/children", s.groupId)
|
||||
} else {
|
||||
requrl = fmt.Sprintf("https://graph.microsoft.com/v1.0/groups/%s/drive/root:/%s:/children", s.groupId, path)
|
||||
}
|
||||
req, _ := http.NewRequest("GET", requrl, nil)
|
||||
req.Header.Add("Authorization", token)
|
||||
req.Header.Add("Accept", "application/json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
unquot, _ := unescapeUnicodeCharactersInJSON(body)
|
||||
|
||||
var drive map[string]interface{}
|
||||
err = json.Unmarshal([]byte(unquot), &drive)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if errnode, ok := drive["error"]; ok {
|
||||
errmsg := errnode.(map[string]interface{})["message"].(string)
|
||||
return nil, errors.New(errmsg)
|
||||
}
|
||||
|
||||
return drive, nil
|
||||
}
|
||||
|
||||
func (s Graph) ListFolders(parent string) ([]string, error) {
|
||||
drive, err := s.listChildren(parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var output []string
|
||||
items := drive["value"].([]interface{})
|
||||
for _, item := range items {
|
||||
itemobj := item.(map[string]interface{})
|
||||
if _, ok := itemobj["file"]; !ok {
|
||||
output = append(output, itemobj["name"].(string))
|
||||
}
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
type FileMeta struct {
|
||||
Path string `json:"path"`
|
||||
LastModified time.Time `json:"lastModified"`
|
||||
}
|
||||
|
||||
func (s Graph) ListFiles(parent string) ([]FileMeta, error) {
|
||||
drive, err := s.listChildren(parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var output []FileMeta
|
||||
items := drive["value"].([]interface{})
|
||||
for _, item := range items {
|
||||
itemobj := item.(map[string]interface{})
|
||||
if _, ok := itemobj["file"]; ok {
|
||||
modTime, _ := time.Parse(time.RFC3339, itemobj["lastModifiedDateTime"].(string))
|
||||
|
||||
output = append(output, FileMeta{
|
||||
Path: path.Join(parent, itemobj["name"].(string)),
|
||||
LastModified: modTime,
|
||||
})
|
||||
}
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (s Graph) GetChannels() (map[string]string, error) {
|
||||
if len(s.groupId) == 0 {
|
||||
return nil, errGroupIdMissing
|
||||
}
|
||||
|
||||
token, err := s.getAuthoizationToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
requrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/teams/%s/channels", s.groupId)
|
||||
req, _ := http.NewRequest("GET", requrl, nil)
|
||||
req.Header.Add("Authorization", token)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var raw map[string]interface{}
|
||||
if err = json.Unmarshal(body, &raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if errnode, ok := raw["error"]; ok {
|
||||
return nil, errors.New(errnode.(map[string]interface{})["message"].(string))
|
||||
}
|
||||
|
||||
if r, ok := raw["value"]; ok {
|
||||
valuenode := r.([]interface{})
|
||||
output := make(map[string]string)
|
||||
for _, v := range valuenode {
|
||||
data := v.(map[string]interface{})
|
||||
id := data["id"].(string)
|
||||
displayname := data["displayName"].(string)
|
||||
output[id] = displayname
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("error")
|
||||
}
|
||||
261
azure/misc.go
Normal file
261
azure/misc.go
Normal file
@ -0,0 +1,261 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func SortVersions(versions []string) []string {
|
||||
sort.Slice(versions, func(i, j int) bool {
|
||||
leftnum := 0
|
||||
for _, iv := range strings.Split(versions[i], ".") {
|
||||
n, _ := strconv.Atoi(iv)
|
||||
leftnum += leftnum<<8 + n
|
||||
}
|
||||
|
||||
rightnum := 0
|
||||
for _, iv := range strings.Split(versions[j], ".") {
|
||||
n, _ := strconv.Atoi(iv)
|
||||
rightnum += rightnum<<8 + n
|
||||
}
|
||||
|
||||
return leftnum < rightnum
|
||||
})
|
||||
return versions
|
||||
}
|
||||
|
||||
const (
|
||||
HoustonStatPoolCount = int(10)
|
||||
)
|
||||
|
||||
type HoustonStatReport struct {
|
||||
Region string `json:"region"`
|
||||
FreeMemory float64 `json:"freeMemory"`
|
||||
FreePercent float64 `json:"freePercent"`
|
||||
CPUUsage float64 `json:"cpuUsage"`
|
||||
PlayingUsers int `json:"users"`
|
||||
PlayingSessions int `json:"sessions"`
|
||||
}
|
||||
|
||||
var prefetchptr = flag.Bool("prefetch", false, "")
|
||||
|
||||
func NeedPrefetch() bool {
|
||||
return *prefetchptr
|
||||
}
|
||||
|
||||
var ErrUpdateUnnecessary = errors.New("binary is already latest")
|
||||
|
||||
func SelfUpdateUsingScript(replycache *MessageReplyCache, graph *Graph, patchroot string, force bool) (err error) {
|
||||
// 1. 다운로드
|
||||
var linkfilearg string
|
||||
if replycache != nil {
|
||||
if replycache.ReplyWrap == nil {
|
||||
hostname, _ := os.Hostname()
|
||||
replycache.ReplyWrap = replycache.SourceActivity().MakeWrap().WithPrefix(hostname)
|
||||
}
|
||||
linkfile := replycache.Serialize()
|
||||
linkfilearg = fmt.Sprintf("-updatelink=%s", linkfile)
|
||||
}
|
||||
|
||||
currentexe, _ := os.Executable()
|
||||
currentexe = strings.ReplaceAll(currentexe, "\\", "/")
|
||||
exefile := path.Base(currentexe)
|
||||
if runtime.GOOS == "windows" && !strings.HasSuffix(exefile, ".exe") {
|
||||
exefile += ".exe"
|
||||
}
|
||||
|
||||
newbinary := fmt.Sprintf("%s.%s", exefile, time.Now().Format("2006.01.02-15.04.05"))
|
||||
downloadurl := path.Join(patchroot, exefile)
|
||||
|
||||
err = graph.DownloadFile(downloadurl, newbinary)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = os.Stat(newbinary)
|
||||
if os.IsNotExist(err) {
|
||||
err = errors.New("다운로드 실패 : " + newbinary)
|
||||
return
|
||||
}
|
||||
|
||||
if !force {
|
||||
currentfile, e := os.Open(currentexe)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
defer currentfile.Close()
|
||||
|
||||
hash1 := md5.New()
|
||||
_, err = io.Copy(hash1, currentfile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
currentHash := hex.EncodeToString(hash1.Sum(nil))
|
||||
|
||||
nextfile, e := os.Open(newbinary)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
defer nextfile.Close()
|
||||
hash2 := md5.New()
|
||||
_, err = io.Copy(hash2, nextfile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nextHash := hex.EncodeToString(hash2.Sum(nil))
|
||||
|
||||
if currentHash == nextHash {
|
||||
// 해시가 같으니까 업데이트 할 필요가 없다.
|
||||
return ErrUpdateUnnecessary
|
||||
}
|
||||
}
|
||||
|
||||
var scriptfileName string
|
||||
if runtime.GOOS == "linux" {
|
||||
scriptfileName = "selfupdate.sh"
|
||||
} else if runtime.GOOS == "windows" {
|
||||
scriptfileName = "selfupdate.bat"
|
||||
}
|
||||
|
||||
scripturl := path.Join(patchroot, scriptfileName)
|
||||
if _, err = os.Stat(scriptfileName); os.IsExist(err) {
|
||||
err = os.Remove(scriptfileName)
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 스크립트 다운로드
|
||||
err = graph.DownloadFile(scripturl, scriptfileName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var nextArgs []string
|
||||
for _, arg := range os.Args[1:] {
|
||||
// -updatelink : selfupdate시에
|
||||
if !strings.HasPrefix(arg, "-updatelink=") && arg != "-prefetch" {
|
||||
nextArgs = append(nextArgs, arg)
|
||||
}
|
||||
}
|
||||
pid := strconv.Itoa(os.Getpid())
|
||||
args := append([]string{
|
||||
pid,
|
||||
newbinary,
|
||||
exefile,
|
||||
}, nextArgs...)
|
||||
|
||||
if len(linkfilearg) > 0 {
|
||||
args = append(args, linkfilearg)
|
||||
}
|
||||
|
||||
// 3. 독립 실행
|
||||
if runtime.GOOS == "linux" {
|
||||
// 실행 가능한 권한 부여
|
||||
err = os.Chmod(scriptfileName, 0777)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// 실행
|
||||
env := os.Environ()
|
||||
currentpath := path.Dir(currentexe)
|
||||
argv0 := path.Join(currentpath, scriptfileName)
|
||||
args = append([]string{"/bin/bash", argv0}, args...)
|
||||
err = syscall.Exec(args[0], args, env)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if runtime.GOOS == "windows" {
|
||||
windowsargs := append([]string{
|
||||
"/C",
|
||||
"start",
|
||||
scriptfileName,
|
||||
}, args...)
|
||||
|
||||
cmd := exec.Command("cmd.exe", windowsargs...)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var linkupdate = flag.String("updatelink", "", "")
|
||||
|
||||
func ReplyUpdateComplete() {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
logger.Error(r)
|
||||
}
|
||||
}()
|
||||
|
||||
if len(*linkupdate) > 0 {
|
||||
cache := DeserializeMessageReplyCache(*linkupdate)
|
||||
if cache != nil {
|
||||
os.Remove(*linkupdate)
|
||||
if cache.ReplyWrap != nil {
|
||||
cache.ReplyWrap.Update(cache.Replyaid, "업데이트 완료")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// var objectIDCounter = readRandomUint32()
|
||||
// var processUnique = processUniqueBytes()
|
||||
|
||||
// func processUniqueBytes() [5]byte {
|
||||
// var b [5]byte
|
||||
// _, err := io.ReadFull(rand.Reader, b[:])
|
||||
// if err != nil {
|
||||
// panic(fmt.Errorf("cannot initialize objectid package with crypto.rand.Reader: %v", err))
|
||||
// }
|
||||
|
||||
// return b
|
||||
// }
|
||||
|
||||
// func readRandomUint32() uint32 {
|
||||
// var b [4]byte
|
||||
// _, err := io.ReadFull(rand.Reader, b[:])
|
||||
// if err != nil {
|
||||
// panic(fmt.Errorf("cannot initialize objectid package with crypto.rand.Reader: %v", err))
|
||||
// }
|
||||
|
||||
// return (uint32(b[0]) << 0) | (uint32(b[1]) << 8) | (uint32(b[2]) << 16) | (uint32(b[3]) << 24)
|
||||
// }
|
||||
|
||||
type BsonMarshaler[T any] struct {
|
||||
val T
|
||||
}
|
||||
|
||||
func NewBsonMarshaler[T any](val T) *BsonMarshaler[T] {
|
||||
return &BsonMarshaler[T]{
|
||||
val: val,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *BsonMarshaler[T]) MarshalBinary() (data []byte, err error) {
|
||||
return bson.Marshal(m.val)
|
||||
}
|
||||
202
azure/monitor.go
Normal file
202
azure/monitor.go
Normal file
@ -0,0 +1,202 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type CounterCurcuit struct {
|
||||
curcuit []float64
|
||||
cursor int
|
||||
}
|
||||
|
||||
func (cc *CounterCurcuit) Append(val float64) {
|
||||
cc.curcuit[cc.cursor] = val
|
||||
cc.cursor++
|
||||
}
|
||||
|
||||
func (cc *CounterCurcuit) Stat() (min float64, max float64, sum float64, count int) {
|
||||
if cc.cursor == 0 {
|
||||
return 0, 0, 0, 0
|
||||
}
|
||||
|
||||
min = math.MaxFloat64
|
||||
max = -math.MaxFloat64
|
||||
sum = float64(0)
|
||||
|
||||
if cc.cursor < len(cc.curcuit) {
|
||||
count = cc.cursor
|
||||
for i := 0; i < cc.cursor; i++ {
|
||||
if min > cc.curcuit[i] {
|
||||
min = cc.curcuit[i]
|
||||
}
|
||||
if max < cc.curcuit[i] {
|
||||
max = cc.curcuit[i]
|
||||
}
|
||||
|
||||
sum += cc.curcuit[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
count = len(cc.curcuit)
|
||||
for _, v := range cc.curcuit {
|
||||
if min > v {
|
||||
min = v
|
||||
}
|
||||
if max < v {
|
||||
max = v
|
||||
}
|
||||
|
||||
sum += v
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func MakeCounterCurcuit(length int) *CounterCurcuit {
|
||||
return &CounterCurcuit{
|
||||
curcuit: make([]float64, length),
|
||||
cursor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
type Float64Counter struct {
|
||||
Min float64 `json:"min"`
|
||||
Max float64 `json:"max"`
|
||||
Sum float64 `json:"sum"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
var DefaultFloat64Counter = Float64Counter{
|
||||
Min: math.MaxFloat64,
|
||||
Max: -math.MaxFloat64,
|
||||
Sum: 0,
|
||||
Count: 0,
|
||||
}
|
||||
|
||||
func (c *Float64Counter) Valid() bool {
|
||||
return c.Count > 0
|
||||
}
|
||||
|
||||
func (c *Float64Counter) Add(val float64) {
|
||||
if c.Max < val {
|
||||
c.Max = val
|
||||
}
|
||||
|
||||
if c.Min > val {
|
||||
c.Min = val
|
||||
}
|
||||
|
||||
c.Sum += val
|
||||
c.Count++
|
||||
}
|
||||
|
||||
func (c *Float64Counter) SingleMin() Float64Counter {
|
||||
return Float64Counter{
|
||||
Min: c.Min,
|
||||
Max: c.Min,
|
||||
Sum: c.Min,
|
||||
Count: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Float64Counter) SingleMax() Float64Counter {
|
||||
return Float64Counter{
|
||||
Min: c.Max,
|
||||
Max: c.Max,
|
||||
Sum: c.Max,
|
||||
Count: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Float64Counter) AddInt(val int) {
|
||||
fval := float64(val)
|
||||
if c.Max < fval {
|
||||
c.Max = fval
|
||||
}
|
||||
|
||||
if c.Min > fval {
|
||||
c.Min = fval
|
||||
}
|
||||
|
||||
c.Sum += fval
|
||||
c.Count++
|
||||
}
|
||||
|
||||
type MetricSeries struct {
|
||||
Float64Counter
|
||||
DimValues []string `json:"dimValues"`
|
||||
}
|
||||
|
||||
type MetructBaseData struct {
|
||||
Metric string `json:"metric"`
|
||||
Namespace string `json:"namespace"`
|
||||
DimNames []string `json:"dimNames"`
|
||||
Series []MetricSeries `json:"series"`
|
||||
}
|
||||
|
||||
type MetricData struct {
|
||||
BaseData MetructBaseData `json:"baseData"`
|
||||
}
|
||||
|
||||
type MetricDocument struct {
|
||||
Time string `json:"time"`
|
||||
Data MetricData `json:"data"`
|
||||
}
|
||||
|
||||
func MakeMetricDocument(timestamp string, namespace string, metric string, dimNames []string) MetricDocument {
|
||||
output := MetricDocument{Time: timestamp}
|
||||
output.Data.BaseData.DimNames = dimNames
|
||||
output.Data.BaseData.Metric = metric
|
||||
output.Data.BaseData.Namespace = namespace
|
||||
return output
|
||||
}
|
||||
|
||||
type Monitor struct {
|
||||
*accessToken
|
||||
tenantId string
|
||||
}
|
||||
|
||||
func NewMonitor(tenantId string) *Monitor {
|
||||
return &Monitor{
|
||||
accessToken: &accessToken{
|
||||
url: fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/token", tenantId),
|
||||
values: url.Values{
|
||||
"grant_type": {"client_credentials"},
|
||||
"client_id": {microsoftAppId()},
|
||||
"resource": {"https://monitoring.azure.com/"},
|
||||
"client_secret": {microsoftAppPassword()},
|
||||
},
|
||||
},
|
||||
tenantId: tenantId,
|
||||
}
|
||||
}
|
||||
|
||||
func (m Monitor) PostMetrics(url string, docs ...MetricDocument) error {
|
||||
// https://docs.microsoft.com/ko-kr/rest/api/monitor/metrics%20(data%20plane)/create
|
||||
token, err := m.getAuthoizationToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, doc := range docs {
|
||||
bt, _ := json.Marshal(doc)
|
||||
req, _ := http.NewRequest("POST", url, bytes.NewReader(bt))
|
||||
req.Header.Add("Authorization", token)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user