<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
)

type Spotify struct {
	AccessToken  string         `json:"access_token"`
	TokenType    string         `json:"token_type"`
	ExpiresIn    uint           `json:"expires_in"`
	RefreshToken string         `json:"refresh_token"`
	Auth         SpotifyAuth    `json:"auth"`
	Profile      SpotifyProfile `json:"profile"`
}

type SpotifyProfile struct {
	ExternalUrls map[string]string `json:"external_urls"`
	Href         string            `json:"href"`
	Id           string            `json:"id"`
	Type         string            `json:"type"`
	Uri          string            `json:"uri"`
}

type Playlist struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

type Playlists struct {
	Items []Playlist `json:"items"`
}

type NewPlaylist struct {
	Name   string `json:"name"`
	Public bool   `json:"public"`
}

type SearchResult struct {
	Tracks Tracks `json:"tracks"`
}

type Tracks struct {
	Items []Track `json:"items"`
}

type Track struct {
	Id   string `json:"id"`
	Name string `json:"name"`
	Uri  string `json:"uri"`
}

func (spotify *Spotify) update(newToken *Spotify) {
	spotify.AccessToken = newToken.AccessToken
	spotify.TokenType = newToken.TokenType
	spotify.ExpiresIn = newToken.ExpiresIn
}

func (spotify *Spotify) refreshToken() error {
	formData := url.Values{
		"grant_type":    {"refresh_token"},
		"refresh_token": {spotify.RefreshToken},
	}
	url := "https://accounts.spotify.com/api/token"
	client := &http.Client{}
	req, err := http.NewRequest("POST", url,
		bytes.NewBufferString(formData.Encode()))
	req.Header.Set("Authorization", spotify.Auth.authHeader())
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	var newToken Spotify
	if err := json.Unmarshal(body, &newToken); err != nil {
		return err
	}
	spotify.update(&newToken)
	return nil
}

func (spotify *Spotify) authHeader() string {
	return spotify.TokenType + " " + spotify.AccessToken
}

func (spotify *Spotify) doGet(url string) (*http.Response, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", spotify.authHeader())
	if err != nil {
		return nil, err
	}
	return client.Do(req)
}

func (spotify *Spotify) get(url string) ([]byte, error) {
	resp, err := spotify.doGet(url)
	if err != nil {
		return nil, err
	}
	// Check if we need to refresh token
	if resp.StatusCode == 401 {
		if err := spotify.refreshToken(); err != nil {
			return nil, err
		}
		if err := spotify.save(spotify.Auth.TokenFile); err != nil {
			return nil, err
		}
		resp, err = spotify.doGet(url)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return body, err
}

func (spotify *Spotify) doPost(url string, body []byte) (*http.Response,
	error) {
	client := &http.Client{}
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	req.Header.Set("Authorization", spotify.authHeader())
	req.Header.Set("Content-Type", "application/json")
	if err != nil {
		return nil, err
	}
	return client.Do(req)
}

func (spotify *Spotify) post(url string, body []byte) ([]byte, error) {
	resp, err := spotify.doPost(url, body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == 401 {
		if err := spotify.refreshToken(); err != nil {
			return nil, err
		}
		if err := spotify.save(spotify.Auth.TokenFile); err != nil {
			return nil, err
		}
		resp, err = spotify.doPost(url, body)
	}
	if resp.StatusCode/100 != 2 {
		return nil, fmt.Errorf("Request failed, status code: %s",
			resp.StatusCode)
	}
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return data, err
}

func (spotify *Spotify) save(filepath string) error {
	json, err := json.Marshal(spotify)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filepath, json, 0600)
	if err != nil {
		return err
	}
	return nil
}

func ReadToken(filepath string) (*Spotify, error) {
	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return nil, err
	}
	var spotify Spotify
	if err := json.Unmarshal(data, &spotify); err != nil {
		return nil, err
	}
	return &spotify, nil
}

func (spotify *Spotify) currentUser() (*SpotifyProfile, error) {
	url := "https://api.spotify.com/v1/me"
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var profile SpotifyProfile
	if err := json.Unmarshal(body, &profile); err != nil {
		return nil, err
	}
	return &profile, nil
}

func (spotify *Spotify) playlists() ([]Playlist, error) {
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists",
		spotify.Profile.Id)
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var playlists Playlists
	if err := json.Unmarshal(body, &playlists); err != nil {
		return nil, err
	}
	return playlists.Items, nil
}

func (spotify *Spotify) playlist(name string) (*Playlist, error) {
	playlists, err := spotify.playlists()
	if err != nil {
		return nil, err
	}
	for _, playlist := range playlists {
		if playlist.Name == name {
			return &playlist, nil
		}
	}
	return nil, fmt.Errorf("Could not find playlist by name: %s", name)
}

func (spotify *Spotify) getOrCreatePlaylist(name string) (*Playlist, error) {
	playlists, err := spotify.playlists()
	if err != nil {
		return nil, err
	}
	for _, playlist := range playlists {
		if playlist.Name == name {
			return &playlist, nil
		}
	}
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists",
		spotify.Profile.Id)
	newPlaylist, err := json.Marshal(NewPlaylist{
		Name:   name,
		Public: false,
	})
	if err != nil {
		return nil, err
	}
	body, err := spotify.post(url, newPlaylist)
	if err != nil {
		return nil, err
	}
	var playlist Playlist
	if err := json.Unmarshal(body, &playlist); err != nil {
		return nil, err
	}
	return &playlist, err
}

func (spotify *Spotify) search(query string, types string, limit uint) ([]Track,
	error) {
	params := url.Values{
		"q":     {query},
		"type":  {types},
		"limit": {strconv.Itoa(int(limit))},
	}
	url := "https://api.spotify.com/v1/search?" + params.Encode()
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var result SearchResult
	if err := json.Unmarshal(body, &result); err != nil {
		return nil, err
	}
	return result.Tracks.Items, nil
}

func (spotify *Spotify) searchArtistTrack(artist string, track string) ([]Track,
	error) {
	query := fmt.Sprintf("artist:%s track:%s", artist, track)
	return spotify.search(query, "track", 1)
}

func (spotify *Spotify) addTracks(playlist *Playlist, tracks []Track) error {
	url := fmt.Sprintf(
		"https://api.spotify.com/v1/users/%s/playlists/%s/tracks",
		spotify.Profile.Id, playlist.Id)

	uris := make([]string, len(tracks))
	for idx, track := range tracks {
		uris[idx] = track.Uri
	}
	jsonUris, err := json.Marshal(uris)
	if err != nil {
		return err
	}
	if _, err := spotify.post(url, jsonUris); err != nil {
		return err
	}
	return nil

}
<commit_msg>Log response body when status code is non-2xx<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
)

type Spotify struct {
	AccessToken  string         `json:"access_token"`
	TokenType    string         `json:"token_type"`
	ExpiresIn    uint           `json:"expires_in"`
	RefreshToken string         `json:"refresh_token"`
	Auth         SpotifyAuth    `json:"auth"`
	Profile      SpotifyProfile `json:"profile"`
}

type SpotifyProfile struct {
	ExternalUrls map[string]string `json:"external_urls"`
	Href         string            `json:"href"`
	Id           string            `json:"id"`
	Type         string            `json:"type"`
	Uri          string            `json:"uri"`
}

type Playlist struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

type Playlists struct {
	Items []Playlist `json:"items"`
}

type NewPlaylist struct {
	Name   string `json:"name"`
	Public bool   `json:"public"`
}

type SearchResult struct {
	Tracks Tracks `json:"tracks"`
}

type Tracks struct {
	Items []Track `json:"items"`
}

type Track struct {
	Id   string `json:"id"`
	Name string `json:"name"`
	Uri  string `json:"uri"`
}

func (spotify *Spotify) update(newToken *Spotify) {
	spotify.AccessToken = newToken.AccessToken
	spotify.TokenType = newToken.TokenType
	spotify.ExpiresIn = newToken.ExpiresIn
}

func (spotify *Spotify) refreshToken() error {
	formData := url.Values{
		"grant_type":    {"refresh_token"},
		"refresh_token": {spotify.RefreshToken},
	}
	url := "https://accounts.spotify.com/api/token"
	client := &http.Client{}
	req, err := http.NewRequest("POST", url,
		bytes.NewBufferString(formData.Encode()))
	req.Header.Set("Authorization", spotify.Auth.authHeader())
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	var newToken Spotify
	if err := json.Unmarshal(body, &newToken); err != nil {
		return err
	}
	spotify.update(&newToken)
	return nil
}

func (spotify *Spotify) authHeader() string {
	return spotify.TokenType + " " + spotify.AccessToken
}

func (spotify *Spotify) doGet(url string) (*http.Response, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", spotify.authHeader())
	if err != nil {
		return nil, err
	}
	return client.Do(req)
}

func (spotify *Spotify) get(url string) ([]byte, error) {
	resp, err := spotify.doGet(url)
	if err != nil {
		return nil, err
	}
	// Check if we need to refresh token
	if resp.StatusCode == 401 {
		if err := spotify.refreshToken(); err != nil {
			return nil, err
		}
		if err := spotify.save(spotify.Auth.TokenFile); err != nil {
			return nil, err
		}
		resp, err = spotify.doGet(url)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return body, err
}

func (spotify *Spotify) doPost(url string, body []byte) (*http.Response,
	error) {
	client := &http.Client{}
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	req.Header.Set("Authorization", spotify.authHeader())
	req.Header.Set("Content-Type", "application/json")
	if err != nil {
		return nil, err
	}
	return client.Do(req)
}

func (spotify *Spotify) post(url string, body []byte) ([]byte, error) {
	resp, err := spotify.doPost(url, body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == 401 {
		if err := spotify.refreshToken(); err != nil {
			return nil, err
		}
		if err := spotify.save(spotify.Auth.TokenFile); err != nil {
			return nil, err
		}
		resp, err = spotify.doPost(url, body)
	}
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode/100 != 2 {
		return nil, fmt.Errorf("Request failed [%d]: %s",
			resp.StatusCode, data)
	}
	return data, err
}

func (spotify *Spotify) save(filepath string) error {
	json, err := json.Marshal(spotify)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filepath, json, 0600)
	if err != nil {
		return err
	}
	return nil
}

func ReadToken(filepath string) (*Spotify, error) {
	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return nil, err
	}
	var spotify Spotify
	if err := json.Unmarshal(data, &spotify); err != nil {
		return nil, err
	}
	return &spotify, nil
}

func (spotify *Spotify) currentUser() (*SpotifyProfile, error) {
	url := "https://api.spotify.com/v1/me"
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var profile SpotifyProfile
	if err := json.Unmarshal(body, &profile); err != nil {
		return nil, err
	}
	return &profile, nil
}

func (spotify *Spotify) playlists() ([]Playlist, error) {
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists",
		spotify.Profile.Id)
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var playlists Playlists
	if err := json.Unmarshal(body, &playlists); err != nil {
		return nil, err
	}
	return playlists.Items, nil
}

func (spotify *Spotify) playlist(name string) (*Playlist, error) {
	playlists, err := spotify.playlists()
	if err != nil {
		return nil, err
	}
	for _, playlist := range playlists {
		if playlist.Name == name {
			return &playlist, nil
		}
	}
	return nil, fmt.Errorf("Could not find playlist by name: %s", name)
}

func (spotify *Spotify) getOrCreatePlaylist(name string) (*Playlist, error) {
	playlists, err := spotify.playlists()
	if err != nil {
		return nil, err
	}
	for _, playlist := range playlists {
		if playlist.Name == name {
			return &playlist, nil
		}
	}
	url := fmt.Sprintf("https://api.spotify.com/v1/users/%s/playlists",
		spotify.Profile.Id)
	newPlaylist, err := json.Marshal(NewPlaylist{
		Name:   name,
		Public: false,
	})
	if err != nil {
		return nil, err
	}
	body, err := spotify.post(url, newPlaylist)
	if err != nil {
		return nil, err
	}
	var playlist Playlist
	if err := json.Unmarshal(body, &playlist); err != nil {
		return nil, err
	}
	return &playlist, err
}

func (spotify *Spotify) search(query string, types string, limit uint) ([]Track,
	error) {
	params := url.Values{
		"q":     {query},
		"type":  {types},
		"limit": {strconv.Itoa(int(limit))},
	}
	url := "https://api.spotify.com/v1/search?" + params.Encode()
	body, err := spotify.get(url)
	if err != nil {
		return nil, err
	}
	var result SearchResult
	if err := json.Unmarshal(body, &result); err != nil {
		return nil, err
	}
	return result.Tracks.Items, nil
}

func (spotify *Spotify) searchArtistTrack(artist string, track string) ([]Track,
	error) {
	query := fmt.Sprintf("artist:%s track:%s", artist, track)
	return spotify.search(query, "track", 1)
}

func (spotify *Spotify) addTracks(playlist *Playlist, tracks []Track) error {
	url := fmt.Sprintf(
		"https://api.spotify.com/v1/users/%s/playlists/%s/tracks",
		spotify.Profile.Id, playlist.Id)

	uris := make([]string, len(tracks))
	for idx, track := range tracks {
		uris[idx] = track.Uri
	}
	jsonUris, err := json.Marshal(uris)
	if err != nil {
		return err
	}
	if _, err := spotify.post(url, jsonUris); err != nil {
		return err
	}
	return nil

}
<|endoftext|>


<commit_before>package rocserv

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httputil"
	"regexp"
	"runtime"
	"strings"
	"time"

	"gitlab.pri.ibanyu.com/middleware/seaweed/xcontext"
	xprom "gitlab.pri.ibanyu.com/middleware/seaweed/xstat/xmetric/xprometheus"
	"gitlab.pri.ibanyu.com/middleware/seaweed/xtrace"
	"gitlab.pri.ibanyu.com/middleware/util/idl/gen-go/util/thriftutil"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/julienschmidt/httprouter"
	"github.com/uber/jaeger-client-go"
)

const (
	HttpHeaderKeyTraceID = "ipalfish-trace-id"

	WildCharacter = ":"

	RoutePath = "req-simple-path"

	HttpHeaderKeyGroup = "ipalfish-group"
	HttpHeaderKeyHead  = "ipalfish-head"

	CookieNameGroup = "ipalfish_group"
)

// HttpServer is the http server, Create an instance of GinServer, by using NewGinServer()
type HttpServer struct {
	*gin.Engine
}

// Context warp gin Context
type Context struct {
	*gin.Context
}

// HandlerFunc ...
type HandlerFunc func(*Context)

// NewHttpServer create http server with gin
func NewHttpServer() *HttpServer {
	// 实例化gin Server
	router := gin.New()
	router.Use(Recovery(), InjectFromRequest(), Metric(), Trace())

	return &HttpServer{router}
}

// Use attachs a global middleware to the router
func (s *HttpServer) Use(middleware ...HandlerFunc) {
	s.Engine.Use(mutilWrap(middleware...)...)
}

// GET is a shortcut for router.Handle("GET", path, handle).
func (s *HttpServer) GET(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.GET(relativePath, ws...)
}

// WGET is wrap httprouter handle to GET
func (s *HttpServer) WGET(relativePath string, handlers ...httprouter.Handle) {
	s.GET(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// POST is a shortcut for router.Handle("POST", path, handle).
func (s *HttpServer) POST(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.POST(relativePath, ws...)
}

// WPOST is wrap httprouter handle to POST
func (s *HttpServer) WPOST(relativePath string, handlers ...httprouter.Handle) {
	s.POST(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// PUT is a shortcut for router.Handle("PUT", path, handle).
func (s *HttpServer) PUT(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.PUT(relativePath, ws...)
}

// WPUT is wrap httprouter handle to PUT
func (s *HttpServer) WPUT(relativePath string, handlers ...httprouter.Handle) {
	s.PUT(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// Any registers a route that matches all the HTTP methods.
func (s *HttpServer) Any(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.Any(relativePath, ws...)
}

// WAny is wrap httprouter handle to ANY
func (s *HttpServer) WAny(relativePath string, handlers ...httprouter.Handle) {
	s.Any(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle).
func (s *HttpServer) DELETE(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.DELETE(relativePath, ws...)
}

// WDELETE is wrap httprouter handle to DELETE
func (s *HttpServer) WDELETE(relativePath string, handlers ...httprouter.Handle) {
	s.DELETE(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// PATCH is a shortcut for router.Handle("PATCH", path, handle).
func (s *HttpServer) PATCH(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.PATCH(relativePath, ws...)
}

// WPATCH is wrap httprouter handle to PATCH
func (s *HttpServer) WPATCH(relativePath string, handlers ...httprouter.Handle) {
	s.PATCH(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
func (s *HttpServer) OPTIONS(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.OPTIONS(relativePath, ws...)
}

// WOPTIONS is wrap httprouter handle to OPTIONS
func (s *HttpServer) WOPTIONS(relativePath string, handlers ...httprouter.Handle) {
	s.OPTIONS(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// HEAD is a shortcut for router.Handle("HEAD", path, handle).
func (s *HttpServer) HEAD(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.HEAD(relativePath, ws...)
}

// WHEAD is wrap httprouter handle to HEAD
func (s *HttpServer) WHEAD(relativePath string, handlers ...httprouter.Handle) {
	s.HEAD(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// Bind checks the Content-Type to select a binding engine automatically
func (c *Context) Bind(obj interface{}) error {
	b := binding.Default(c.Request.Method, c.ContentType())
	return c.MustBindWith(obj, b)
}

func InjectFromRequest() gin.HandlerFunc {
	return func(c *gin.Context) {
		ctx := c.Request.Context()
		ctx = extractThriftUtilContextControlFromRequest(ctx, c.Request)
		ctx = extractThriftUtilContextHeadFromRequest(ctx, c.Request)
		c.Request = c.Request.WithContext(ctx)
	}
}

// Metric returns a metric middleware
func Metric() gin.HandlerFunc {
	return func(c *gin.Context) {
		now := time.Now()
		c.Next()
		dt := time.Since(now)
		if path, exist := c.Get(RoutePath); exist {
			if fun, ok := path.(string); ok {
				group, serviceName := GetGroupAndService()
				//TODO 先做兼容，后续再补上
				_metricAPIRequestCount.With(xprom.LabelGroupName, group, xprom.LabelServiceName, serviceName, xprom.LabelAPI, fun, xprom.LabelErrCode, "1").Inc()
				_metricAPIRequestTime.With(xprom.LabelGroupName, group, xprom.LabelServiceName, serviceName, xprom.LabelAPI, fun, xprom.LabelErrCode, "1").Observe(float64(dt / time.Millisecond))
			}
		}
	}
}

// Trace returns a trace middleware
func Trace() gin.HandlerFunc {
	return func(c *gin.Context) {
		span := xtrace.SpanFromContext(c.Request.Context())
		if span == nil {
			newSpan, ctx := xtrace.StartSpanFromContext(c.Request.Context(), c.Request.RequestURI)
			c.Request.WithContext(ctx)
			span = newSpan
		}
		defer span.Finish()

		if sc, ok := span.Context().(jaeger.SpanContext); ok {
			c.Writer.Header()[HttpHeaderKeyTraceID] = []string{fmt.Sprint(sc.TraceID())}
		}

		c.Next()
	}
}

// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
func Recovery() gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			var rawReq []byte
			if err := recover(); err != nil {
				const size = 64 << 10
				buf := make([]byte, size)
				buf = buf[:runtime.Stack(buf, false)]
				if c.Request != nil {
					rawReq, _ = httputil.DumpRequest(c.Request, false)
				}
				pl := fmt.Sprintf("http call panic: %s\n%v\n%s\n", string(rawReq), err, buf)
				fmt.Println(pl)
				c.AbortWithStatus(500)
			}
		}()
		c.Next()
	}
}

func pathHook(relativePath string) gin.HandlerFunc {
	return func(c *gin.Context) {
		values := strings.Split(relativePath, WildCharacter)
		c.Set(RoutePath, values[0])
	}
}

func mutilWrap(handlers ...HandlerFunc) []gin.HandlerFunc {
	var h = make([]gin.HandlerFunc, len(handlers))
	for k, v := range handlers {
		h[k] = wrap(v)
	}
	return h
}

func wrap(h HandlerFunc) gin.HandlerFunc {
	return func(c *gin.Context) {
		h(&Context{c})
	}
}

// MutilWrapHttpRouter wrap many httprouter handle to roc httpserver handle
func MutilWrapHttpRouter(handlers ...httprouter.Handle) []HandlerFunc {
	var h = make([]HandlerFunc, len(handlers))
	for k, v := range handlers {
		h[k] = WrapHttpRouter(v)
	}
	return h
}

// WrapHttpRouter wrap httprouter handle to roc httpserver handle
func WrapHttpRouter(handle httprouter.Handle) HandlerFunc {
	return func(c *Context) {
		params := make([]httprouter.Param, 0)
		for param := range c.Params {
			params = append(params, httprouter.Param{
				Key:   c.Params[param].Key,
				Value: c.Params[param].Value,
			})
		}
		handle(c.Writer, c.Request, params)
	}
}

func extractThriftUtilContextControlFromRequest(ctx context.Context, req *http.Request) context.Context {
	var group string
	if group = extractRouteGroupFromHost(req); group != "" {
		return injectRouteGroupToContext(ctx, group)
	}

	if group = extractRouteGroupFromHeader(req); group != "" {
		return injectRouteGroupToContext(ctx, group)
	}

	if group = extractRouteGroupFromCookie(req); group != "" {
		return injectRouteGroupToContext(ctx, group)
	}

	return injectRouteGroupToContext(ctx, xcontext.DefaultGroup)
}

func extractThriftUtilContextHeadFromRequest(ctx context.Context, req *http.Request) context.Context {
	// NOTE: 如果已经有了就先不覆盖
	val := ctx.Value(xcontext.ContextKeyHead)
	if val != nil {
		return ctx
	}

	headJsonString := req.Header.Get(HttpHeaderKeyHead)
	var head thriftutil.Head
	_ = json.Unmarshal([]byte(headJsonString), &head)
	ctx = context.WithValue(ctx, xcontext.ContextKeyHead, &head)
	return ctx
}

var domainRouteRegexp = regexp.MustCompile(`(?P<group>.+)\.group\..+`)

func extractRouteGroupFromHost(r *http.Request) (group string) {
	matches := domainRouteRegexp.FindStringSubmatch(r.Host)
	names := domainRouteRegexp.SubexpNames()
	for i, _ := range matches {
		if names[i] == "group" {
			group = matches[i]
		}
	}
	return
}

func injectRouteGroupToContext(ctx context.Context, group string) context.Context {
	control := thriftutil.NewDefaultControl()
	control.Route.Group = group

	return context.WithValue(ctx, xcontext.ContextKeyControl, control)
}

func extractRouteGroupFromHeader(r *http.Request) (group string) {
	return r.Header.Get(HttpHeaderKeyGroup)
}

func extractRouteGroupFromCookie(r *http.Request) (group string) {
	ck, err := r.Cookie(CookieNameGroup)
	if err == nil {
		group = ck.Value
	}
	return
}
<commit_msg>server_http.go: add errcode metrics (#241)<commit_after>package rocserv

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httputil"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"gitlab.pri.ibanyu.com/middleware/seaweed/xcontext"
	xprom "gitlab.pri.ibanyu.com/middleware/seaweed/xstat/xmetric/xprometheus"
	"gitlab.pri.ibanyu.com/middleware/seaweed/xtrace"
	"gitlab.pri.ibanyu.com/middleware/util/idl/gen-go/util/thriftutil"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/julienschmidt/httprouter"
	"github.com/uber/jaeger-client-go"
)

const (
	HttpHeaderKeyTraceID = "ipalfish-trace-id"

	WildCharacter = ":"

	RoutePath = "req-simple-path"

	HttpHeaderKeyGroup = "ipalfish-group"
	HttpHeaderKeyHead  = "ipalfish-head"

	CookieNameGroup = "ipalfish_group"
)

// HttpServer is the http server, Create an instance of GinServer, by using NewGinServer()
type HttpServer struct {
	*gin.Engine
}

// Context warp gin Context
type Context struct {
	*gin.Context
}

// HandlerFunc ...
type HandlerFunc func(*Context)

// NewHttpServer create http server with gin
func NewHttpServer() *HttpServer {
	// 实例化gin Server
	router := gin.New()
	router.Use(Recovery(), InjectFromRequest(), Metric(), Trace())

	return &HttpServer{router}
}

// Use attachs a global middleware to the router
func (s *HttpServer) Use(middleware ...HandlerFunc) {
	s.Engine.Use(mutilWrap(middleware...)...)
}

// GET is a shortcut for router.Handle("GET", path, handle).
func (s *HttpServer) GET(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.GET(relativePath, ws...)
}

// WGET is wrap httprouter handle to GET
func (s *HttpServer) WGET(relativePath string, handlers ...httprouter.Handle) {
	s.GET(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// POST is a shortcut for router.Handle("POST", path, handle).
func (s *HttpServer) POST(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.POST(relativePath, ws...)
}

// WPOST is wrap httprouter handle to POST
func (s *HttpServer) WPOST(relativePath string, handlers ...httprouter.Handle) {
	s.POST(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// PUT is a shortcut for router.Handle("PUT", path, handle).
func (s *HttpServer) PUT(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.PUT(relativePath, ws...)
}

// WPUT is wrap httprouter handle to PUT
func (s *HttpServer) WPUT(relativePath string, handlers ...httprouter.Handle) {
	s.PUT(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// Any registers a route that matches all the HTTP methods.
func (s *HttpServer) Any(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.Any(relativePath, ws...)
}

// WAny is wrap httprouter handle to ANY
func (s *HttpServer) WAny(relativePath string, handlers ...httprouter.Handle) {
	s.Any(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle).
func (s *HttpServer) DELETE(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.DELETE(relativePath, ws...)
}

// WDELETE is wrap httprouter handle to DELETE
func (s *HttpServer) WDELETE(relativePath string, handlers ...httprouter.Handle) {
	s.DELETE(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// PATCH is a shortcut for router.Handle("PATCH", path, handle).
func (s *HttpServer) PATCH(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.PATCH(relativePath, ws...)
}

// WPATCH is wrap httprouter handle to PATCH
func (s *HttpServer) WPATCH(relativePath string, handlers ...httprouter.Handle) {
	s.PATCH(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
func (s *HttpServer) OPTIONS(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.OPTIONS(relativePath, ws...)
}

// WOPTIONS is wrap httprouter handle to OPTIONS
func (s *HttpServer) WOPTIONS(relativePath string, handlers ...httprouter.Handle) {
	s.OPTIONS(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// HEAD is a shortcut for router.Handle("HEAD", path, handle).
func (s *HttpServer) HEAD(relativePath string, handlers ...HandlerFunc) {
	ws := append([]gin.HandlerFunc{pathHook(relativePath)}, mutilWrap(handlers...)...)
	s.Engine.HEAD(relativePath, ws...)
}

// WHEAD is wrap httprouter handle to HEAD
func (s *HttpServer) WHEAD(relativePath string, handlers ...httprouter.Handle) {
	s.HEAD(relativePath, MutilWrapHttpRouter(handlers...)...)
}

// Bind checks the Content-Type to select a binding engine automatically
func (c *Context) Bind(obj interface{}) error {
	b := binding.Default(c.Request.Method, c.ContentType())
	return c.MustBindWith(obj, b)
}

func InjectFromRequest() gin.HandlerFunc {
	return func(c *gin.Context) {
		ctx := c.Request.Context()
		ctx = extractThriftUtilContextControlFromRequest(ctx, c.Request)
		ctx = extractThriftUtilContextHeadFromRequest(ctx, c.Request)
		c.Request = c.Request.WithContext(ctx)
	}
}

// Metric returns a metric middleware
func Metric() gin.HandlerFunc {
	return func(c *gin.Context) {
		ctx := c.Request.Context()
		ctx = contextWithErrCode(ctx,1)
		c.Request = c.Request.WithContext(ctx)

		now := time.Now()
		c.Next()
		dt := time.Since(now)

		errCode := getErrCodeFromContext(c.Request.Context())
		if path, exist := c.Get(RoutePath); exist {
			if fun, ok := path.(string); ok {
				group, serviceName := GetGroupAndService()
				_metricAPIRequestCount.With(xprom.LabelGroupName, group, xprom.LabelServiceName, serviceName, xprom.LabelAPI, fun, xprom.LabelErrCode, strconv.Itoa(errCode)).Inc()
				_metricAPIRequestTime.With(xprom.LabelGroupName, group, xprom.LabelServiceName, serviceName, xprom.LabelAPI, fun, xprom.LabelErrCode, strconv.Itoa(errCode)).Observe(float64(dt / time.Millisecond))
			}
		}
	}
}

// Trace returns a trace middleware
func Trace() gin.HandlerFunc {
	return func(c *gin.Context) {
		span := xtrace.SpanFromContext(c.Request.Context())
		if span == nil {
			newSpan, ctx := xtrace.StartSpanFromContext(c.Request.Context(), c.Request.RequestURI)
			c.Request.WithContext(ctx)
			span = newSpan
		}
		defer span.Finish()

		if sc, ok := span.Context().(jaeger.SpanContext); ok {
			c.Writer.Header()[HttpHeaderKeyTraceID] = []string{fmt.Sprint(sc.TraceID())}
		}

		c.Next()
	}
}

// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
func Recovery() gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			var rawReq []byte
			if err := recover(); err != nil {
				const size = 64 << 10
				buf := make([]byte, size)
				buf = buf[:runtime.Stack(buf, false)]
				if c.Request != nil {
					rawReq, _ = httputil.DumpRequest(c.Request, false)
				}
				pl := fmt.Sprintf("http call panic: %s\n%v\n%s\n", string(rawReq), err, buf)
				fmt.Println(pl)
				c.AbortWithStatus(500)
			}
		}()
		c.Next()
	}
}

func pathHook(relativePath string) gin.HandlerFunc {
	return func(c *gin.Context) {
		values := strings.Split(relativePath, WildCharacter)
		c.Set(RoutePath, values[0])
	}
}

func mutilWrap(handlers ...HandlerFunc) []gin.HandlerFunc {
	var h = make([]gin.HandlerFunc, len(handlers))
	for k, v := range handlers {
		h[k] = wrap(v)
	}
	return h
}

func wrap(h HandlerFunc) gin.HandlerFunc {
	return func(c *gin.Context) {
		h(&Context{c})
	}
}

// MutilWrapHttpRouter wrap many httprouter handle to roc httpserver handle
func MutilWrapHttpRouter(handlers ...httprouter.Handle) []HandlerFunc {
	var h = make([]HandlerFunc, len(handlers))
	for k, v := range handlers {
		h[k] = WrapHttpRouter(v)
	}
	return h
}

// WrapHttpRouter wrap httprouter handle to roc httpserver handle
func WrapHttpRouter(handle httprouter.Handle) HandlerFunc {
	return func(c *Context) {
		params := make([]httprouter.Param, 0)
		for param := range c.Params {
			params = append(params, httprouter.Param{
				Key:   c.Params[param].Key,
				Value: c.Params[param].Value,
			})
		}
		handle(c.Writer, c.Request, params)
	}
}

func extractThriftUtilContextControlFromRequest(ctx context.Context, req *http.Request) context.Context {
	var group string
	if group = extractRouteGroupFromHost(req); group != "" {
		return injectRouteGroupToContext(ctx, group)
	}

	if group = extractRouteGroupFromHeader(req); group != "" {
		return injectRouteGroupToContext(ctx, group)
	}

	if group = extractRouteGroupFromCookie(req); group != "" {
		return injectRouteGroupToContext(ctx, group)
	}

	return injectRouteGroupToContext(ctx, xcontext.DefaultGroup)
}

func extractThriftUtilContextHeadFromRequest(ctx context.Context, req *http.Request) context.Context {
	// NOTE: 如果已经有了就先不覆盖
	val := ctx.Value(xcontext.ContextKeyHead)
	if val != nil {
		return ctx
	}

	headJsonString := req.Header.Get(HttpHeaderKeyHead)
	var head thriftutil.Head
	_ = json.Unmarshal([]byte(headJsonString), &head)
	ctx = context.WithValue(ctx, xcontext.ContextKeyHead, &head)
	return ctx
}

var domainRouteRegexp = regexp.MustCompile(`(?P<group>.+)\.group\..+`)

func extractRouteGroupFromHost(r *http.Request) (group string) {
	matches := domainRouteRegexp.FindStringSubmatch(r.Host)
	names := domainRouteRegexp.SubexpNames()
	for i, _ := range matches {
		if names[i] == "group" {
			group = matches[i]
		}
	}
	return
}

func injectRouteGroupToContext(ctx context.Context, group string) context.Context {
	control := thriftutil.NewDefaultControl()
	control.Route.Group = group

	return context.WithValue(ctx, xcontext.ContextKeyControl, control)
}

func extractRouteGroupFromHeader(r *http.Request) (group string) {
	return r.Header.Get(HttpHeaderKeyGroup)
}

func extractRouteGroupFromCookie(r *http.Request) (group string) {
	ck, err := r.Cookie(CookieNameGroup)
	if err == nil {
		group = ck.Value
	}
	return
}

type ErrCode struct {
	int
}

const ErrCodeKey = "ErrCode"

func ContextSetErrCode(ctx context.Context, errCode int) {
	errCodeContext, ok := ctx.Value(ErrCodeKey).(*ErrCode)
	if !ok {
		return
	}
	errCodeContext.int = errCode
	if span := xtrace.SpanFromContext(ctx); span != nil {
		span.SetTag("errcode", errCode)
	}
}

func contextWithErrCode(ctx context.Context, errCode int) context.Context {
	if span := xtrace.SpanFromContext(ctx); span != nil {
		span.SetTag("errcode", errCode)
	}
	return context.WithValue(ctx, ErrCodeKey, &ErrCode{errCode})
}

func getErrCodeFromContext(ctx context.Context) int {
	errCode, ok := ctx.Value(ErrCodeKey).(*ErrCode)
	if !ok {
		return 1
	}
	return errCode.int
}
<|endoftext|>


<commit_before>//
// Copyright 2017, Sander van Harmelen
//
// 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 gitlab

import (
	"fmt"
	"time"
)

// DeployKeysService handles communication with the keys related methods
// of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/deploy_keys.html
type DeployKeysService struct {
	client *Client
}

// DeployKey represents a GitLab deploy key.
type DeployKey struct {
	ID        int        `json:"id"`
	Title     string     `json:"title"`
	Key       string     `json:"key"`
	CanPush   *bool      `json:"can_push"`
	CreatedAt *time.Time `json:"created_at"`
}

func (k DeployKey) String() string {
	return Stringify(k)
}

// ListAllDeployKeys gets a list of all deploy keys
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#list-all-deploy-keys
func (s *DeployKeysService) ListAllDeployKeys(options ...RequestOptionFunc) ([]*DeployKey, *Response, error) {
	req, err := s.client.NewRequest("GET", "deploy_keys", nil, options)
	if err != nil {
		return nil, nil, err
	}

	var ks []*DeployKey
	resp, err := s.client.Do(req, &ks)
	if err != nil {
		return nil, resp, err
	}

	return ks, resp, err
}

// ListProjectDeployKeysOptions represents the available ListProjectDeployKeys()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#list-project-deploy-keys
type ListProjectDeployKeysOptions ListOptions

// ListProjectDeployKeys gets a list of a project's deploy keys
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#list-project-deploy-keys
func (s *DeployKeysService) ListProjectDeployKeys(pid interface{}, opt *ListProjectDeployKeysOptions, options ...RequestOptionFunc) ([]*DeployKey, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/deploy_keys", pathEscape(project))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var ks []*DeployKey
	resp, err := s.client.Do(req, &ks)
	if err != nil {
		return nil, resp, err
	}

	return ks, resp, err
}

// GetDeployKey gets a single deploy key.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#single-deploy-key
func (s *DeployKeysService) GetDeployKey(pid interface{}, deployKey int, options ...RequestOptionFunc) (*DeployKey, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/deploy_keys/%d", pathEscape(project), deployKey)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	k := new(DeployKey)
	resp, err := s.client.Do(req, k)
	if err != nil {
		return nil, resp, err
	}

	return k, resp, err
}

// AddDeployKeyOptions represents the available ADDDeployKey() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#add-deploy-key
type AddDeployKeyOptions struct {
	Title   *string `url:"title,omitempty" json:"title,omitempty"`
	Key     *string `url:"key,omitempty" json:"key,omitempty"`
	CanPush *bool   `url:"can_push,omitempty" json:"can_push,omitempty"`
}

// AddDeployKey creates a new deploy key for a project. If deploy key already
// exists in another project - it will be joined to project but only if
// original one was is accessible by same user.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#add-deploy-key
func (s *DeployKeysService) AddDeployKey(pid interface{}, opt *AddDeployKeyOptions, options ...RequestOptionFunc) (*DeployKey, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/deploy_keys", pathEscape(project))

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	k := new(DeployKey)
	resp, err := s.client.Do(req, k)
	if err != nil {
		return nil, resp, err
	}

	return k, resp, err
}

// DeleteDeployKey deletes a deploy key from a project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#delete-deploy-key
func (s *DeployKeysService) DeleteDeployKey(pid interface{}, deployKey int, options ...RequestOptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/deploy_keys/%d", pathEscape(project), deployKey)

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// EnableDeployKey enables a deploy key.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#enable-deploy-key
func (s *DeployKeysService) EnableDeployKey(pid interface{}, deployKey int, options ...RequestOptionFunc) (*DeployKey, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/deploy_keys/%d/enable", pathEscape(project), deployKey)

	req, err := s.client.NewRequest("POST", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	k := new(DeployKey)
	resp, err := s.client.Do(req, k)
	if err != nil {
		return nil, resp, err
	}

	return k, resp, err
}
<commit_msg>Add UpdateDeployKey.<commit_after>//
// Copyright 2017, Sander van Harmelen
//
// 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 gitlab

import (
	"fmt"
	"time"
)

// DeployKeysService handles communication with the keys related methods
// of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/deploy_keys.html
type DeployKeysService struct {
	client *Client
}

// DeployKey represents a GitLab deploy key.
type DeployKey struct {
	ID        int        `json:"id"`
	Title     string     `json:"title"`
	Key       string     `json:"key"`
	CanPush   *bool      `json:"can_push"`
	CreatedAt *time.Time `json:"created_at"`
}

func (k DeployKey) String() string {
	return Stringify(k)
}

// ListAllDeployKeys gets a list of all deploy keys
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#list-all-deploy-keys
func (s *DeployKeysService) ListAllDeployKeys(options ...RequestOptionFunc) ([]*DeployKey, *Response, error) {
	req, err := s.client.NewRequest("GET", "deploy_keys", nil, options)
	if err != nil {
		return nil, nil, err
	}

	var ks []*DeployKey
	resp, err := s.client.Do(req, &ks)
	if err != nil {
		return nil, resp, err
	}

	return ks, resp, err
}

// ListProjectDeployKeysOptions represents the available ListProjectDeployKeys()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#list-project-deploy-keys
type ListProjectDeployKeysOptions ListOptions

// ListProjectDeployKeys gets a list of a project's deploy keys
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#list-project-deploy-keys
func (s *DeployKeysService) ListProjectDeployKeys(pid interface{}, opt *ListProjectDeployKeysOptions, options ...RequestOptionFunc) ([]*DeployKey, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/deploy_keys", pathEscape(project))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var ks []*DeployKey
	resp, err := s.client.Do(req, &ks)
	if err != nil {
		return nil, resp, err
	}

	return ks, resp, err
}

// GetDeployKey gets a single deploy key.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#single-deploy-key
func (s *DeployKeysService) GetDeployKey(pid interface{}, deployKey int, options ...RequestOptionFunc) (*DeployKey, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/deploy_keys/%d", pathEscape(project), deployKey)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	k := new(DeployKey)
	resp, err := s.client.Do(req, k)
	if err != nil {
		return nil, resp, err
	}

	return k, resp, err
}

// AddDeployKeyOptions represents the available ADDDeployKey() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#add-deploy-key
type AddDeployKeyOptions struct {
	Title   *string `url:"title,omitempty" json:"title,omitempty"`
	Key     *string `url:"key,omitempty" json:"key,omitempty"`
	CanPush *bool   `url:"can_push,omitempty" json:"can_push,omitempty"`
}

// AddDeployKey creates a new deploy key for a project. If deploy key already
// exists in another project - it will be joined to project but only if
// original one was is accessible by same user.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#add-deploy-key
func (s *DeployKeysService) AddDeployKey(pid interface{}, opt *AddDeployKeyOptions, options ...RequestOptionFunc) (*DeployKey, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/deploy_keys", pathEscape(project))

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	k := new(DeployKey)
	resp, err := s.client.Do(req, k)
	if err != nil {
		return nil, resp, err
	}

	return k, resp, err
}

// DeleteDeployKey deletes a deploy key from a project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#delete-deploy-key
func (s *DeployKeysService) DeleteDeployKey(pid interface{}, deployKey int, options ...RequestOptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/deploy_keys/%d", pathEscape(project), deployKey)

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// EnableDeployKey enables a deploy key.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#enable-deploy-key
func (s *DeployKeysService) EnableDeployKey(pid interface{}, deployKey int, options ...RequestOptionFunc) (*DeployKey, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/deploy_keys/%d/enable", pathEscape(project), deployKey)

	req, err := s.client.NewRequest("POST", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	k := new(DeployKey)
	resp, err := s.client.Do(req, k)
	if err != nil {
		return nil, resp, err
	}

	return k, resp, err
}

// UpdateDeployKeyOptions represents the available UpdateDeployKey() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#update-deploy-key
type UpdateDeployKeyOptions struct {
	Title   *string `url:"title,omitempty" json:"title,omitempty"`
	CanPush *bool   `url:"can_push,omitempty" json:"can_push,omitempty"`
}

// UpdateDeployKey updates a deploy key for a project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/deploy_keys.html#update-deploy-key
func (s *DeployKeysService) UpdateDeployKey(pid interface{}, deployKey int, opt *UpdateDeployKeyOptions, options ...RequestOptionFunc) (*DeployKey, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/deploy_keys/%d", pathEscape(project), deployKey)

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	k := new(DeployKey)
	resp, err := s.client.Do(req, k)
	if err != nil {
		return nil, resp, err
	}

	return k, resp, err
}
<|endoftext|>


<commit_before>package zeroformatter

import (
	"encoding/binary"
	"errors"
	"fmt"
	"math"
	"reflect"
	"time"
	"unicode/utf16"

	"github.com/shamaton/zeroformatter/char"
	"github.com/shamaton/zeroformatter/datetimeoffset"
)

type deserializer struct {
	data []byte
}

const minStructDataSize = 9

func createDeserializer(data []byte) *deserializer {
	return &deserializer{
		data: data,
	}
}

func Deserialize(holder interface{}, data []byte) error {
	ds := createDeserializer(data)

	t := reflect.ValueOf(holder)
	if t.Kind() != reflect.Ptr {
		return fmt.Errorf("holder must set pointer value. but got: %t", holder)
	}

	t = t.Elem()
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	// byte to Struct
	if t.Kind() == reflect.Struct && !isDateTime(t) && !isDateTimeOffset(t) {
		return ds.deserializeStruct(t)
	}

	// byte to primitive
	_, err := ds.deserialize(t, 0)
	return err
}

func (d *deserializer) deserializeStruct(t reflect.Value) error {
	dataLen := len(d.data)
	if dataLen < minStructDataSize {
		return fmt.Errorf("data size is not enough: %d", dataLen)
	}

	// data lookup
	offset := uint32(0)

	// size
	b, offset := d.read_s4(offset)
	size := binary.LittleEndian.Uint32(b)
	if size != uint32(dataLen) {
		return fmt.Errorf("data size is wrong [ %d : %d ]", size, dataLen)
	}

	// index
	b, offset = d.read_s4(offset)
	dataIndex := binary.LittleEndian.Uint32(b)
	numField := t.NumField()
	if dataIndex != uint32(numField-1) {
		return fmt.Errorf("data index is diffrent [ %d : %d ]", dataIndex, numField-1)
	}

	for i := 0; i < numField; i++ {
		b, offset = d.read_s4(offset)
		dataOffset := binary.LittleEndian.Uint32(b)
		if _, err := d.deserialize(t.Field(i), dataOffset); err != nil {
			return err
		}
	}
	return nil
}

func isDateTime(value reflect.Value) bool {
	i := value.Interface()
	switch i.(type) {
	case time.Time:
		return true
	}
	return false
}

func isDateTimeOffset(value reflect.Value) bool {
	i := value.Interface()
	switch i.(type) {
	case datetimeoffset.DateTimeOffset:
		return true
	}
	return false
}

func isDuration(value reflect.Value) bool {
	// check type
	i := value.Interface()
	switch i.(type) {
	case time.Duration:
		return true
	}
	return false
}

func isChar(value reflect.Value) bool {
	i := value.Interface()
	switch i.(type) {
	case char.Char:
		return true
	}
	return false
}

func (d *deserializer) deserialize(rv reflect.Value, offset uint32) (uint32, error) {
	var err error

	switch rv.Kind() {
	case reflect.Int8:
		b, o := d.read_s1(offset)
		rv.SetInt(int64(b))
		// update
		offset = o

	case reflect.Int16:
		// Int16 [short(2)]
		b, o := d.read_s2(offset)
		_v := binary.LittleEndian.Uint16(b)
		rv.SetInt(int64(_v))
		// update
		offset = o

	case reflect.Int32:
		// char is used instead of rune
		if isChar(rv) {
			// rune [ushort(2)]
			b, o := d.read_s2(offset)
			u16s := []uint16{binary.LittleEndian.Uint16(b)}
			_v := utf16.Decode(u16s)
			v := char.Char(_v[0])
			rv.Set(reflect.ValueOf(v))

			// update
			offset = o
		} else {
			// Int32 [int(4)]
			b, o := d.read_s4(offset)
			_v := binary.LittleEndian.Uint32(b)
			// NOTE : double cast
			rv.SetInt(int64(int32(_v)))
			// update
			offset = o
		}

	case reflect.Int:
		// Int32 [int(4)]
		b, o := d.read_s4(offset)
		_v := binary.LittleEndian.Uint32(b)
		// NOTE : double cast
		rv.SetInt(int64(int32(_v)))
		// update
		offset = o

	case reflect.Int64:
		if isDuration(rv) {
			// todo : NOTE procedure is as same as datetime
			b, o1 := d.read_s8(offset)
			seconds := binary.LittleEndian.Uint64(b)
			b, o2 := d.read_s4(o1)
			nanos := binary.LittleEndian.Uint32(b)
			v := time.Duration(int64(seconds)*1000*1000 + int64(nanos))

			rv.Set(reflect.ValueOf(v))
			// update
			offset = o2
		} else {
			// Int64 [long(8)]
			b, o := d.read_s8(offset)
			v := binary.LittleEndian.Uint64(b)
			rv.SetInt(int64(v))
			// update
			offset = o
		}

	case reflect.Uint8:
		// byte in cSharp
		_v, o := d.read_s1(offset)
		rv.SetUint(uint64(_v))
		// update
		offset = o

	case reflect.Uint16:
		// Uint16 / Char
		b, o := d.read_s2(offset)
		v := binary.LittleEndian.Uint16(b)
		rv.SetUint(uint64(v))
		// update
		offset = o

	case reflect.Uint32:
		b, o := d.read_s4(offset)
		v := binary.LittleEndian.Uint32(b)
		rv.SetUint(uint64(v))
		// update
		offset = o

	case reflect.Uint:
		b, o := d.read_s4(offset)
		v := binary.LittleEndian.Uint32(b)
		rv.SetUint(uint64(v))
		// update
		offset = o

	case reflect.Uint64:
		b, o := d.read_s8(offset)
		v := binary.LittleEndian.Uint64(b)
		rv.SetUint(v)
		// update
		offset = o

	case reflect.Float32:
		// Single
		b, o := d.read_s4(offset)
		_v := binary.LittleEndian.Uint32(b)
		v := math.Float32frombits(_v)
		rv.SetFloat(float64(v))
		// update
		offset = o

	case reflect.Float64:
		// Double
		b, o := d.read_s8(offset)
		_v := binary.LittleEndian.Uint64(b)
		v := math.Float64frombits(_v)
		rv.SetFloat(v)
		// update
		offset = o

	case reflect.Bool:
		b, o := d.read_s1(offset)
		if b == 0x01 {
			rv.SetBool(true)
		} else if b == 0x00 {
			rv.SetBool(false)
		}
		// update
		offset = o

	case reflect.String:
		b, o := d.read_s4(offset)
		l := binary.LittleEndian.Uint32(b)
		v := string(d.data[o : o+l])
		rv.SetString(v)
		// update
		offset = o + l

	case reflect.Struct:
		if isDateTimeOffset(rv) {
			b, o1 := d.read_s8(offset)
			seconds := binary.LittleEndian.Uint64(b)
			b, o2 := d.read_s4(o1)
			nanos := binary.LittleEndian.Uint32(b)
			b, o3 := d.read_s2(o2)
			offMin := binary.LittleEndian.Uint16(b)

			v := datetimeoffset.Unix(int64(seconds)-int64(offMin*60), int64(nanos))
			rv.Set(reflect.ValueOf(v))
			// update
			offset = o3

		} else if isDateTime(rv) {
			b, o1 := d.read_s8(offset)
			seconds := binary.LittleEndian.Uint64(b)
			b, o2 := d.read_s4(o1)
			nanos := binary.LittleEndian.Uint32(b)
			v := time.Unix(int64(seconds), int64(nanos))

			rv.Set(reflect.ValueOf(v))
			// update
			offset = o2
		} else {
			for i := 0; i < rv.NumField(); i++ {
				offset, err = d.deserialize(rv.Field(i), offset)
				if err != nil {
					return 0, err
				}
			}
		}

	case reflect.Slice:
		// element type
		e := rv.Type().Elem()

		// length
		b, offset := d.read_s4(offset)
		l := int(int32(binary.LittleEndian.Uint32(b)))

		// data is null
		if l < 0 {
			return offset, nil
		}

		o := offset
		tmpSlice := reflect.MakeSlice(rv.Type(), l, l)

		for i := 0; i < l; i++ {
			v := reflect.New(e).Elem()
			o, err = d.deserialize(v, o)
			if err != nil {
				return 0, err
			}

			tmpSlice.Index(i).Set(v)
		}
		rv.Set(tmpSlice)

		// update
		offset = o

	case reflect.Array:
		// element type
		e := rv.Type().Elem()

		// length
		b, offset := d.read_s4(offset)
		l := int(int32(binary.LittleEndian.Uint32(b)))

		// data is null
		if l < 0 {
			return offset, nil
		}
		if l != rv.Len() {
			return 0, fmt.Errorf("Array Length is different : data[%d] array[%d]", l, rv.Len())
		}

		o := offset
		for i := 0; i < l; i++ {
			v := reflect.New(e).Elem()
			o, err = d.deserialize(v, o)
			if err != nil {
				return 0, err
			}
			rv.Index(i).Set(v)
		}

		// update
		offset = o

	case reflect.Map:
		key := rv.Type().Key()
		value := rv.Type().Elem()

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

		// map length
		b, offset := d.read_s4(offset)
		l := int(binary.LittleEndian.Uint32(b))

		for i := 0; i < l; i++ {
			k := reflect.New(key).Elem()
			v := reflect.New(value).Elem()
			o, err := d.deserialize(k, offset)
			if err != nil {
				return 0, err
			}
			o, err = d.deserialize(v, o)
			if err != nil {
				return 0, err
			}

			rv.SetMapIndex(k, v)
			offset = o
		}

	case reflect.Ptr:
		e := rv.Type().Elem()
		v := reflect.New(e).Elem()
		offset, err = d.deserialize(v, offset)
		rv.Set(v.Addr())

	default:
		err = errors.New(fmt.Sprint("this type is not supported : ", rv.Type()))
	}

	return offset, err
}
<commit_msg>implementing delay deserializer<commit_after>package zeroformatter

import (
	"encoding/binary"
	"errors"
	"fmt"
	"math"
	"reflect"
	"time"
	"unicode/utf16"

	"github.com/shamaton/zeroformatter/char"
	"github.com/shamaton/zeroformatter/datetimeoffset"
)

type deserializer struct {
	data []byte
}

type delayDeserializer struct {
	*deserializer
	holder       reflect.Value
	processedMap map[uintptr]int
}

const minStructDataSize = 9

func createDeserializer(data []byte) *deserializer {
	return &deserializer{
		data: data,
	}
}

func createDelayDeserialize(data []byte, holder reflect.Value) *delayDeserializer {
	return &delayDeserializer{
		deserializer: createDeserializer(data),
		holder:       holder,
		processedMap: map[uintptr]int{},
	}
}

func Deserialize(holder interface{}, data []byte) error {
	ds := createDeserializer(data)

	t := reflect.ValueOf(holder)
	if t.Kind() != reflect.Ptr {
		return fmt.Errorf("holder must set pointer value. but got: %t", holder)
	}

	t = t.Elem()
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	// byte to Struct
	if t.Kind() == reflect.Struct && !isDateTime(t) && !isDateTimeOffset(t) {
		return ds.deserializeStruct(t)
	}

	// byte to primitive
	_, err := ds.deserialize(t, 0)
	return err
}

func DelayDeserialize(holder interface{}, data []byte) (*delayDeserializer, error) {

	t := reflect.ValueOf(holder)
	if t.Kind() != reflect.Ptr {
		return nil, fmt.Errorf("holder must set pointer value. but got: %t", holder)
	}

	t = t.Elem()
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	fmt.Println("eelele  ", t.CanSet())

	ds := createDelayDeserialize(data, t)

	// size check
	offset := uint32(0)
	b, offset := ds.read_s4(offset)

	dataLen := len(ds.data)
	size := binary.LittleEndian.Uint32(b)
	if size != uint32(dataLen) {
		return nil, fmt.Errorf("data size is wrong [ %d : %d ]", size, dataLen)
	}

	if t.Kind() == reflect.Struct && !isDateTime(t) && !isDateTimeOffset(t) {
		// make deserialized map
		for i := 0; i < t.NumField(); i++ {
			e := t.Field(i)
			ds.processedMap[e.Addr().Pointer()] = i
		}
	} else {
		return nil, fmt.Errorf("only defined struct can delay deserialize: %t", holder)
	}

	return ds, nil
}

func (d *delayDeserializer) DeserializeByElement(elem interface{}) error {

	t := reflect.ValueOf(elem)
	if t.Kind() != reflect.Ptr {
		return fmt.Errorf("element must set pointer value. but got: %t", elem)
	}

	t = t.Elem()
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	index, ok := d.processedMap[t.Addr().Pointer()]
	if !ok {
		fmt.Errorf("not found element: %t", elem)
	}

	// already deserialize
	if index < 0 {
		return nil
	}

	tt := d.holder

	fmt.Println("testes ---> ", tt.Kind(), tt.CanSet())

	// value
	rv := tt.Field(index)
	// offset
	off := 8 + uint32(index)*byte4
	b, _ := d.read_s4(off)
	dataIndex := binary.LittleEndian.Uint32(b)

	fmt.Println("testes ---> ", index, rv.Kind(), rv.CanSet(), d.data)

	//
	d.deserialize(rv, dataIndex)
	return nil
}

func (d *deserializer) deserializeStruct(t reflect.Value) error {
	dataLen := len(d.data)
	if dataLen < minStructDataSize {
		return fmt.Errorf("data size is not enough: %d", dataLen)
	}

	// data lookup
	offset := uint32(0)

	// size
	b, offset := d.read_s4(offset)
	size := binary.LittleEndian.Uint32(b)
	if size != uint32(dataLen) {
		return fmt.Errorf("data size is wrong [ %d : %d ]", size, dataLen)
	}

	// index
	b, offset = d.read_s4(offset)
	dataIndex := binary.LittleEndian.Uint32(b)
	numField := t.NumField()
	if dataIndex != uint32(numField-1) {
		return fmt.Errorf("data index is diffrent [ %d : %d ]", dataIndex, numField-1)
	}

	for i := 0; i < numField; i++ {
		b, offset = d.read_s4(offset)
		dataOffset := binary.LittleEndian.Uint32(b)
		if _, err := d.deserialize(t.Field(i), dataOffset); err != nil {
			return err
		}
	}
	return nil
}

func isDateTime(value reflect.Value) bool {
	i := value.Interface()
	switch i.(type) {
	case time.Time:
		return true
	}
	return false
}

func isDateTimeOffset(value reflect.Value) bool {
	i := value.Interface()
	switch i.(type) {
	case datetimeoffset.DateTimeOffset:
		return true
	}
	return false
}

func isDuration(value reflect.Value) bool {
	// check type
	i := value.Interface()
	switch i.(type) {
	case time.Duration:
		return true
	}
	return false
}

func isChar(value reflect.Value) bool {
	i := value.Interface()
	switch i.(type) {
	case char.Char:
		return true
	}
	return false
}

func (d *deserializer) deserialize(rv reflect.Value, offset uint32) (uint32, error) {
	var err error

	switch rv.Kind() {
	case reflect.Int8:
		b, o := d.read_s1(offset)
		rv.SetInt(int64(b))
		// update
		offset = o

	case reflect.Int16:
		// Int16 [short(2)]
		b, o := d.read_s2(offset)
		_v := binary.LittleEndian.Uint16(b)
		rv.SetInt(int64(_v))
		// update
		offset = o

	case reflect.Int32:
		// char is used instead of rune
		if isChar(rv) {
			// rune [ushort(2)]
			b, o := d.read_s2(offset)
			u16s := []uint16{binary.LittleEndian.Uint16(b)}
			_v := utf16.Decode(u16s)
			v := char.Char(_v[0])
			rv.Set(reflect.ValueOf(v))

			// update
			offset = o
		} else {
			// Int32 [int(4)]
			b, o := d.read_s4(offset)
			_v := binary.LittleEndian.Uint32(b)
			// NOTE : double cast
			rv.SetInt(int64(int32(_v)))
			// update
			offset = o
		}

	case reflect.Int:
		// Int32 [int(4)]
		b, o := d.read_s4(offset)
		_v := binary.LittleEndian.Uint32(b)
		// NOTE : double cast
		rv.SetInt(int64(int32(_v)))
		// update
		offset = o

	case reflect.Int64:
		if isDuration(rv) {
			// todo : NOTE procedure is as same as datetime
			b, o1 := d.read_s8(offset)
			seconds := binary.LittleEndian.Uint64(b)
			b, o2 := d.read_s4(o1)
			nanos := binary.LittleEndian.Uint32(b)
			v := time.Duration(int64(seconds)*1000*1000 + int64(nanos))

			rv.Set(reflect.ValueOf(v))
			// update
			offset = o2
		} else {
			// Int64 [long(8)]
			b, o := d.read_s8(offset)
			v := binary.LittleEndian.Uint64(b)
			rv.SetInt(int64(v))
			// update
			offset = o
		}

	case reflect.Uint8:
		// byte in cSharp
		_v, o := d.read_s1(offset)
		rv.SetUint(uint64(_v))
		// update
		offset = o

	case reflect.Uint16:
		// Uint16 / Char
		b, o := d.read_s2(offset)
		v := binary.LittleEndian.Uint16(b)
		rv.SetUint(uint64(v))
		// update
		offset = o

	case reflect.Uint32:
		b, o := d.read_s4(offset)
		v := binary.LittleEndian.Uint32(b)
		rv.SetUint(uint64(v))
		// update
		offset = o

	case reflect.Uint:
		b, o := d.read_s4(offset)
		v := binary.LittleEndian.Uint32(b)
		rv.SetUint(uint64(v))
		// update
		offset = o

	case reflect.Uint64:
		b, o := d.read_s8(offset)
		v := binary.LittleEndian.Uint64(b)
		rv.SetUint(v)
		// update
		offset = o

	case reflect.Float32:
		// Single
		b, o := d.read_s4(offset)
		_v := binary.LittleEndian.Uint32(b)
		v := math.Float32frombits(_v)
		rv.SetFloat(float64(v))
		// update
		offset = o

	case reflect.Float64:
		// Double
		b, o := d.read_s8(offset)
		_v := binary.LittleEndian.Uint64(b)
		v := math.Float64frombits(_v)
		rv.SetFloat(v)
		// update
		offset = o

	case reflect.Bool:
		b, o := d.read_s1(offset)
		if b == 0x01 {
			rv.SetBool(true)
		} else if b == 0x00 {
			rv.SetBool(false)
		}
		// update
		offset = o

	case reflect.String:
		b, o := d.read_s4(offset)
		l := binary.LittleEndian.Uint32(b)
		v := string(d.data[o : o+l])
		rv.SetString(v)
		// update
		offset = o + l

	case reflect.Struct:
		if isDateTimeOffset(rv) {
			b, o1 := d.read_s8(offset)
			seconds := binary.LittleEndian.Uint64(b)
			b, o2 := d.read_s4(o1)
			nanos := binary.LittleEndian.Uint32(b)
			b, o3 := d.read_s2(o2)
			offMin := binary.LittleEndian.Uint16(b)

			v := datetimeoffset.Unix(int64(seconds)-int64(offMin*60), int64(nanos))
			rv.Set(reflect.ValueOf(v))
			// update
			offset = o3

		} else if isDateTime(rv) {
			b, o1 := d.read_s8(offset)
			seconds := binary.LittleEndian.Uint64(b)
			b, o2 := d.read_s4(o1)
			nanos := binary.LittleEndian.Uint32(b)
			v := time.Unix(int64(seconds), int64(nanos))

			rv.Set(reflect.ValueOf(v))
			// update
			offset = o2
		} else {
			for i := 0; i < rv.NumField(); i++ {
				offset, err = d.deserialize(rv.Field(i), offset)
				if err != nil {
					return 0, err
				}
			}
		}

	case reflect.Slice:
		// element type
		e := rv.Type().Elem()

		// length
		b, offset := d.read_s4(offset)
		l := int(int32(binary.LittleEndian.Uint32(b)))

		// data is null
		if l < 0 {
			return offset, nil
		}

		o := offset
		tmpSlice := reflect.MakeSlice(rv.Type(), l, l)

		for i := 0; i < l; i++ {
			v := reflect.New(e).Elem()
			o, err = d.deserialize(v, o)
			if err != nil {
				return 0, err
			}

			tmpSlice.Index(i).Set(v)
		}
		rv.Set(tmpSlice)

		// update
		offset = o

	case reflect.Array:
		// element type
		e := rv.Type().Elem()

		// length
		b, offset := d.read_s4(offset)
		l := int(int32(binary.LittleEndian.Uint32(b)))

		// data is null
		if l < 0 {
			return offset, nil
		}
		if l != rv.Len() {
			return 0, fmt.Errorf("Array Length is different : data[%d] array[%d]", l, rv.Len())
		}

		o := offset
		for i := 0; i < l; i++ {
			v := reflect.New(e).Elem()
			o, err = d.deserialize(v, o)
			if err != nil {
				return 0, err
			}
			rv.Index(i).Set(v)
		}

		// update
		offset = o

	case reflect.Map:
		key := rv.Type().Key()
		value := rv.Type().Elem()

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

		// map length
		b, offset := d.read_s4(offset)
		l := int(binary.LittleEndian.Uint32(b))

		for i := 0; i < l; i++ {
			k := reflect.New(key).Elem()
			v := reflect.New(value).Elem()
			o, err := d.deserialize(k, offset)
			if err != nil {
				return 0, err
			}
			o, err = d.deserialize(v, o)
			if err != nil {
				return 0, err
			}

			rv.SetMapIndex(k, v)
			offset = o
		}

	case reflect.Ptr:
		e := rv.Type().Elem()
		v := reflect.New(e).Elem()
		offset, err = d.deserialize(v, offset)
		rv.Set(v.Addr())

	default:
		err = errors.New(fmt.Sprint("this type is not supported : ", rv.Type()))
	}

	return offset, err
}
<|endoftext|>


<commit_before>// +build linux darwin freebsd

package command

import (
	"context"
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/filesys/meta_cache"
	"os"
	"os/user"
	"path"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/seaweedfs/fuse"
	"github.com/seaweedfs/fuse/fs"

	"github.com/chrislusf/seaweedfs/weed/filesys"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/chrislusf/seaweedfs/weed/security"
	"github.com/chrislusf/seaweedfs/weed/util"
	"github.com/chrislusf/seaweedfs/weed/util/grace"
)

func runMount(cmd *Command, args []string) bool {

	grace.SetupProfiling(*mountCpuProfile, *mountMemProfile)

	umask, umaskErr := strconv.ParseUint(*mountOptions.umaskString, 8, 64)
	if umaskErr != nil {
		fmt.Printf("can not parse umask %s", *mountOptions.umaskString)
		return false
	}

	if len(args) > 0 {
		return false
	}

	return RunMount(&mountOptions, os.FileMode(umask))
}

func RunMount(option *MountOptions, umask os.FileMode) bool {

	filer := *option.filer
	// parse filer grpc address
	filerGrpcAddress, err := pb.ParseFilerGrpcAddress(filer)
	if err != nil {
		glog.V(0).Infof("ParseFilerGrpcAddress: %v", err)
		return true
	}

	// try to connect to filer, filerBucketsPath may be useful later
	grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client")
	var cipher bool
	err = pb.WithGrpcFilerClient(filerGrpcAddress, grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
		resp, err := client.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
		if err != nil {
			return fmt.Errorf("get filer grpc address %s configuration: %v", filerGrpcAddress, err)
		}
		cipher = resp.Cipher
		return nil
	})
	if err != nil {
		glog.Infof("failed to talk to filer %s: %v", filerGrpcAddress, err)
		return true
	}

	filerMountRootPath := *option.filerMountRootPath
	dir := util.ResolvePath(*option.dir)
	chunkSizeLimitMB := *mountOptions.chunkSizeLimitMB

	util.LoadConfiguration("security", false)

	fmt.Printf("This is SeaweedFS version %s %s %s\n", util.Version(), runtime.GOOS, runtime.GOARCH)
	if dir == "" {
		fmt.Printf("Please specify the mount directory via \"-dir\"")
		return false
	}
	if chunkSizeLimitMB <= 0 {
		fmt.Printf("Please specify a reasonable buffer size.")
		return false
	}

	fuse.Unmount(dir)

	uid, gid := uint32(0), uint32(0)

	// detect mount folder mode
	if *option.dirAutoCreate {
		os.MkdirAll(dir, 0755)
	}
	mountMode := os.ModeDir | 0755
	fileInfo, err := os.Stat(dir)
	if err == nil {
		mountMode = os.ModeDir | fileInfo.Mode()
		uid, gid = util.GetFileUidGid(fileInfo)
		fmt.Printf("mount point owner uid=%d gid=%d mode=%s\n", uid, gid, fileInfo.Mode())
	} else {
		fmt.Printf("can not stat %s\n", dir)
		return false
	}

	if uid == 0 {
		if u, err := user.Current(); err == nil {
			if parsedId, pe := strconv.ParseUint(u.Uid, 10, 32); pe == nil {
				uid = uint32(parsedId)
			}
			if parsedId, pe := strconv.ParseUint(u.Gid, 10, 32); pe == nil {
				gid = uint32(parsedId)
			}
			fmt.Printf("current uid=%d gid=%d\n", uid, gid)
		}
	}

	// mapping uid, gid
	uidGidMapper, err := meta_cache.NewUidGidMapper(*option.uidMap, *option.gidMap)
	if err != nil {
		fmt.Printf("failed to parse %s %s: %v\n", *option.uidMap, *option.gidMap, err)
		return false
	}

	// Ensure target mount point availability
	if isValid := checkMountPointAvailable(dir); !isValid {
		glog.Fatalf("Expected mount to still be active, target mount point: %s, please check!", dir)
		return true
	}

	mountName := path.Base(dir)

	options := []fuse.MountOption{
		fuse.VolumeName(mountName),
		fuse.FSName(filer + ":" + filerMountRootPath),
		fuse.Subtype("seaweedfs"),
		// fuse.NoAppleDouble(), // include .DS_Store, otherwise can not delete non-empty folders
		fuse.NoAppleXattr(),
		fuse.NoBrowse(),
		fuse.AutoXattr(),
		fuse.ExclCreate(),
		fuse.DaemonTimeout("3600"),
		fuse.AllowSUID(),
		fuse.DefaultPermissions(),
		fuse.MaxReadahead(1024 * 128),
		fuse.AsyncRead(),
		fuse.WritebackCache(),
	}

	options = append(options, osSpecificMountOptions()...)
	if *option.allowOthers {
		options = append(options, fuse.AllowOther())
	}
	if *option.nonempty {
		options = append(options, fuse.AllowNonEmptyMount())
	}

	// find mount point
	mountRoot := filerMountRootPath
	if mountRoot != "/" && strings.HasSuffix(mountRoot, "/") {
		mountRoot = mountRoot[0 : len(mountRoot)-1]
	}

	seaweedFileSystem := filesys.NewSeaweedFileSystem(&filesys.Option{
		FilerGrpcAddress:            filerGrpcAddress,
		GrpcDialOption:              grpcDialOption,
		FilerMountRootPath:          mountRoot,
		Collection:                  *option.collection,
		Replication:                 *option.replication,
		TtlSec:                      int32(*option.ttlSec),
		ChunkSizeLimit:              int64(chunkSizeLimitMB) * 1024 * 1024,
		CacheDir:                    *option.cacheDir,
		CacheSizeMB:                 *option.cacheSizeMB,
		DataCenter:                  *option.dataCenter,
		EntryCacheTtl:               3 * time.Second,
		MountUid:                    uid,
		MountGid:                    gid,
		MountMode:                   mountMode,
		MountCtime:                  fileInfo.ModTime(),
		MountMtime:                  time.Now(),
		Umask:                       umask,
		OutsideContainerClusterMode: *mountOptions.outsideContainerClusterMode,
		Cipher:                      cipher,
		UidGidMapper:                uidGidMapper,
	})

	// mount
	c, err := fuse.Mount(dir, options...)
	if err != nil {
		glog.V(0).Infof("mount: %v", err)
		return true
	}
	defer fuse.Unmount(dir)

	grace.OnInterrupt(func() {
		fuse.Unmount(dir)
		c.Close()
	})

	glog.V(0).Infof("mounted %s%s to %s", filer, mountRoot, dir)
	err = fs.Serve(c, seaweedFileSystem)

	// check if the mount process has an error to report
	<-c.Ready
	if err := c.MountError; err != nil {
		glog.V(0).Infof("mount process: %v", err)
		return true
	}

	return true
}
<commit_msg>mount: auto created dir set corrct umask<commit_after>// +build linux darwin freebsd

package command

import (
	"context"
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/filesys/meta_cache"
	"os"
	"os/user"
	"path"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/seaweedfs/fuse"
	"github.com/seaweedfs/fuse/fs"

	"github.com/chrislusf/seaweedfs/weed/filesys"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/chrislusf/seaweedfs/weed/security"
	"github.com/chrislusf/seaweedfs/weed/util"
	"github.com/chrislusf/seaweedfs/weed/util/grace"
)

func runMount(cmd *Command, args []string) bool {

	grace.SetupProfiling(*mountCpuProfile, *mountMemProfile)

	umask, umaskErr := strconv.ParseUint(*mountOptions.umaskString, 8, 64)
	if umaskErr != nil {
		fmt.Printf("can not parse umask %s", *mountOptions.umaskString)
		return false
	}

	if len(args) > 0 {
		return false
	}

	return RunMount(&mountOptions, os.FileMode(umask))
}

func RunMount(option *MountOptions, umask os.FileMode) bool {

	filer := *option.filer
	// parse filer grpc address
	filerGrpcAddress, err := pb.ParseFilerGrpcAddress(filer)
	if err != nil {
		glog.V(0).Infof("ParseFilerGrpcAddress: %v", err)
		return true
	}

	// try to connect to filer, filerBucketsPath may be useful later
	grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client")
	var cipher bool
	err = pb.WithGrpcFilerClient(filerGrpcAddress, grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
		resp, err := client.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
		if err != nil {
			return fmt.Errorf("get filer grpc address %s configuration: %v", filerGrpcAddress, err)
		}
		cipher = resp.Cipher
		return nil
	})
	if err != nil {
		glog.Infof("failed to talk to filer %s: %v", filerGrpcAddress, err)
		return true
	}

	filerMountRootPath := *option.filerMountRootPath
	dir := util.ResolvePath(*option.dir)
	chunkSizeLimitMB := *mountOptions.chunkSizeLimitMB

	util.LoadConfiguration("security", false)

	fmt.Printf("This is SeaweedFS version %s %s %s\n", util.Version(), runtime.GOOS, runtime.GOARCH)
	if dir == "" {
		fmt.Printf("Please specify the mount directory via \"-dir\"")
		return false
	}
	if chunkSizeLimitMB <= 0 {
		fmt.Printf("Please specify a reasonable buffer size.")
		return false
	}

	fuse.Unmount(dir)

	uid, gid := uint32(0), uint32(0)

	// detect mount folder mode
	if *option.dirAutoCreate {
		os.MkdirAll(dir, os.FileMode(0777) &^ umask)
	}
	mountMode := os.ModeDir | 0755
	fileInfo, err := os.Stat(dir)
	if err == nil {
		mountMode = os.ModeDir | fileInfo.Mode()
		uid, gid = util.GetFileUidGid(fileInfo)
		fmt.Printf("mount point owner uid=%d gid=%d mode=%s\n", uid, gid, fileInfo.Mode())
	} else {
		fmt.Printf("can not stat %s\n", dir)
		return false
	}

	if uid == 0 {
		if u, err := user.Current(); err == nil {
			if parsedId, pe := strconv.ParseUint(u.Uid, 10, 32); pe == nil {
				uid = uint32(parsedId)
			}
			if parsedId, pe := strconv.ParseUint(u.Gid, 10, 32); pe == nil {
				gid = uint32(parsedId)
			}
			fmt.Printf("current uid=%d gid=%d\n", uid, gid)
		}
	}

	// mapping uid, gid
	uidGidMapper, err := meta_cache.NewUidGidMapper(*option.uidMap, *option.gidMap)
	if err != nil {
		fmt.Printf("failed to parse %s %s: %v\n", *option.uidMap, *option.gidMap, err)
		return false
	}

	// Ensure target mount point availability
	if isValid := checkMountPointAvailable(dir); !isValid {
		glog.Fatalf("Expected mount to still be active, target mount point: %s, please check!", dir)
		return true
	}

	mountName := path.Base(dir)

	options := []fuse.MountOption{
		fuse.VolumeName(mountName),
		fuse.FSName(filer + ":" + filerMountRootPath),
		fuse.Subtype("seaweedfs"),
		// fuse.NoAppleDouble(), // include .DS_Store, otherwise can not delete non-empty folders
		fuse.NoAppleXattr(),
		fuse.NoBrowse(),
		fuse.AutoXattr(),
		fuse.ExclCreate(),
		fuse.DaemonTimeout("3600"),
		fuse.AllowSUID(),
		fuse.DefaultPermissions(),
		fuse.MaxReadahead(1024 * 128),
		fuse.AsyncRead(),
		fuse.WritebackCache(),
	}

	options = append(options, osSpecificMountOptions()...)
	if *option.allowOthers {
		options = append(options, fuse.AllowOther())
	}
	if *option.nonempty {
		options = append(options, fuse.AllowNonEmptyMount())
	}

	// find mount point
	mountRoot := filerMountRootPath
	if mountRoot != "/" && strings.HasSuffix(mountRoot, "/") {
		mountRoot = mountRoot[0 : len(mountRoot)-1]
	}

	seaweedFileSystem := filesys.NewSeaweedFileSystem(&filesys.Option{
		FilerGrpcAddress:            filerGrpcAddress,
		GrpcDialOption:              grpcDialOption,
		FilerMountRootPath:          mountRoot,
		Collection:                  *option.collection,
		Replication:                 *option.replication,
		TtlSec:                      int32(*option.ttlSec),
		ChunkSizeLimit:              int64(chunkSizeLimitMB) * 1024 * 1024,
		CacheDir:                    *option.cacheDir,
		CacheSizeMB:                 *option.cacheSizeMB,
		DataCenter:                  *option.dataCenter,
		EntryCacheTtl:               3 * time.Second,
		MountUid:                    uid,
		MountGid:                    gid,
		MountMode:                   mountMode,
		MountCtime:                  fileInfo.ModTime(),
		MountMtime:                  time.Now(),
		Umask:                       umask,
		OutsideContainerClusterMode: *mountOptions.outsideContainerClusterMode,
		Cipher:                      cipher,
		UidGidMapper:                uidGidMapper,
	})

	// mount
	c, err := fuse.Mount(dir, options...)
	if err != nil {
		glog.V(0).Infof("mount: %v", err)
		return true
	}
	defer fuse.Unmount(dir)

	grace.OnInterrupt(func() {
		fuse.Unmount(dir)
		c.Close()
	})

	glog.V(0).Infof("mounted %s%s to %s", filer, mountRoot, dir)
	err = fs.Serve(c, seaweedFileSystem)

	// check if the mount process has an error to report
	<-c.Ready
	if err := c.MountError; err != nil {
		glog.V(0).Infof("mount process: %v", err)
		return true
	}

	return true
}
<|endoftext|>


<commit_before>package device

import (
	"encoding/json"
	"fmt"
	"github.com/silenceper/wechat/util"
)

type ReqBind struct {
	Ticket   string `json:"ticket"`
	DeviceId string `json:"device_id"`
	OpenId   string `json:"open_id"`
}
type resBind struct {
	BaseResp util.CommonError `json:"base_resp"`
}

// Bind 设备绑定
func (d *Device) Bind(req ReqBind) (err error) {
	var accessToken string
	if accessToken, err = d.GetAccessToken(); err != nil {
		return
	}
	uri := fmt.Sprintf("%s?access_token=%s", uriBind, accessToken)
	var response []byte
	if response, err = util.PostJSON(uri, req); err != nil {
		return
	}
	var result resBind
	if err = json.Unmarshal(response, result); err != nil {
		return
	}
	if result.BaseResp.ErrCode != 0 {
		err = fmt.Errorf("DeviceBind Error , errcode=%d , errmsg=%s", result.BaseResp.ErrCode, result.BaseResp.ErrMsg)
		return
	}
	return
}

// Bind 设备解绑
func (d *Device) Unbind(req ReqBind) (err error) {
	var accessToken string
	if accessToken, err = d.GetAccessToken(); err != nil {
		return
	}
	uri := fmt.Sprintf("%s?access_token=%s", uriUnbind, accessToken)
	var response []byte
	if response, err = util.PostJSON(uri, req); err != nil {
		return
	}
	var result resBind
	if err = json.Unmarshal(response, result); err != nil {
		return
	}
	if result.BaseResp.ErrCode != 0 {
		err = fmt.Errorf("DeviceBind Error , errcode=%d , errmsg=%s", result.BaseResp.ErrCode, result.BaseResp.ErrMsg)
		return
	}
	return
}
<commit_msg>修改对象类型错误<commit_after>package device

import (
	"encoding/json"
	"fmt"
	"github.com/silenceper/wechat/util"
)

type ReqBind struct {
	Ticket   string `json:"ticket"`
	DeviceId string `json:"device_id"`
	OpenId   string `json:"open_id"`
}
type resBind struct {
	BaseResp util.CommonError `json:"base_resp"`
}

// Bind 设备绑定
func (d *Device) Bind(req ReqBind) (err error) {
	var accessToken string
	if accessToken, err = d.GetAccessToken(); err != nil {
		return
	}
	uri := fmt.Sprintf("%s?access_token=%s", uriBind, accessToken)
	var response []byte
	if response, err = util.PostJSON(uri, req); err != nil {
		return
	}
	var result resBind
	if err = json.Unmarshal(response, &result); err != nil {
		return
	}
	if result.BaseResp.ErrCode != 0 {
		err = fmt.Errorf("DeviceBind Error , errcode=%d , errmsg=%s", result.BaseResp.ErrCode, result.BaseResp.ErrMsg)
		return
	}
	return
}

// Bind 设备解绑
func (d *Device) Unbind(req ReqBind) (err error) {
	var accessToken string
	if accessToken, err = d.GetAccessToken(); err != nil {
		return
	}
	uri := fmt.Sprintf("%s?access_token=%s", uriUnbind, accessToken)
	var response []byte
	if response, err = util.PostJSON(uri, req); err != nil {
		return
	}
	var result resBind
	if err = json.Unmarshal(response, &result); err != nil {
		return
	}
	if result.BaseResp.ErrCode != 0 {
		err = fmt.Errorf("DeviceBind Error , errcode=%d , errmsg=%s", result.BaseResp.ErrCode, result.BaseResp.ErrMsg)
		return
	}
	return
}
<|endoftext|>


<commit_before>package wrap

import (
	"fmt"
	"os"

	"github.com/mackerelio/mackerel-agent/config"
	"github.com/mackerelio/mkr/logger"
	cli "gopkg.in/urfave/cli.v1"
)

// Command is definition of mkr wrap
var Command = cli.Command{
	Name:      "wrap",
	Usage:     "Wrap and monitor batch jobs to run with cron etc",
	ArgsUsage: "[--detail|-d] [--name|-n <name>] [--memo|-m <memo>] [--warning|w] -- /path/to/batch",
	Description: `
    Wrap a batch command with specifying it as arguments. If the command failed
    with non-zero exit code, it sends a report to Mackerel and raises an alert.
    It is useful for cron jobs etc.
`,
	Action: doWrap,
	Flags: []cli.Flag{
		cli.StringFlag{Name: "name, n", Value: "", Usage: "The `check-name` which must be unique on a host. If it is empty it will be automatically derived."},
		cli.BoolFlag{Name: "detail, d", Usage: "send a detailed report contains command output"},
		cli.StringFlag{Name: "memo, m", Value: "", Usage: "`memo` of the job"},
		cli.StringFlag{Name: "host, H", Value: "", Usage: "`hostID`"},
		cli.BoolFlag{Name: "warning, w", Usage: "alerts as warning"},
		cli.BoolFlag{Name: "auto-close, a", Usage: "automatically close an existing alert when the command success"},
		cli.DurationFlag{Name: "notification-interval, I", Usage: "The notification re-sending `interval`. If it is zero, never re-send. (minimum 10 minutes)"},
	},
}

func doWrap(c *cli.Context) error {
	confFile := c.GlobalString("conf")
	conf, err := config.LoadConfig(confFile)
	if err != nil {
		return err
	}
	apibase := c.GlobalString("apibase")
	if apibase == "" {
		apibase = conf.Apibase
	}

	apikey := conf.Apikey
	if apikey == "" {
		apikey = os.Getenv("MACKEREL_APIKEY")
	}
	if apikey == "" {
		logger.Log("error", "[mkr wrap] failed to detect Mackerel APIKey. Try to specify in mackerel-agent.conf or export MACKEREL_APIKEY='<Your apikey>'")
	}
	hostID, _ := conf.LoadHostID()
	if c.String("host") != "" {
		hostID = c.String("host")
	}
	if hostID == "" {
		logger.Log("error", "[mkr wrap] failed to load hostID. Try to specify -host option explicitly")
	}
	// Since command execution has the highest priority, even when apikey or
	// hostID is empty, we don't return errors and only output the log here.

	cmd := c.Args()
	if len(cmd) > 0 && cmd[0] == "--" {
		cmd = cmd[1:]
	}
	if len(cmd) < 1 {
		return fmt.Errorf("no commands specified")
	}

	return (&wrap{
		apibase:              apibase,
		name:                 c.String("name"),
		detail:               c.Bool("detail"),
		memo:                 c.String("memo"),
		warning:              c.Bool("warning"),
		autoClose:            c.Bool("auto-close"),
		notificationInterval: c.Duration("notification-interval"),
		hostID:               hostID,
		apikey:               apikey,
		cmd:                  cmd,
		outStream:            os.Stdout,
		errStream:            os.Stderr,
	}).run()
}
<commit_msg>[wrap] add comment for maxCheckAttempts<commit_after>package wrap

import (
	"fmt"
	"os"

	"github.com/mackerelio/mackerel-agent/config"
	"github.com/mackerelio/mkr/logger"
	cli "gopkg.in/urfave/cli.v1"
)

// Command is definition of mkr wrap
var Command = cli.Command{
	Name:      "wrap",
	Usage:     "Wrap and monitor batch jobs to run with cron etc",
	ArgsUsage: "[--detail|-d] [--name|-n <name>] [--memo|-m <memo>] [--warning|w] -- /path/to/batch",
	Description: `
    Wrap a batch command with specifying it as arguments. If the command failed
    with non-zero exit code, it sends a report to Mackerel and raises an alert.
    It is useful for cron jobs etc.
`,
	Action: doWrap,
	Flags: []cli.Flag{
		cli.StringFlag{Name: "name, n", Value: "", Usage: "The `check-name` which must be unique on a host. If it is empty it will be automatically derived."},
		cli.BoolFlag{Name: "detail, d", Usage: "send a detailed report contains command output"},
		cli.StringFlag{Name: "memo, m", Value: "", Usage: "`memo` of the job"},
		cli.StringFlag{Name: "host, H", Value: "", Usage: "`hostID`"},
		cli.BoolFlag{Name: "warning, w", Usage: "alerts as warning"},
		cli.BoolFlag{Name: "auto-close, a", Usage: "automatically close an existing alert when the command success"},
		cli.DurationFlag{Name: "notification-interval, I", Usage: "The notification re-sending `interval`. If it is zero, never re-send. (minimum 10 minutes)"},
		// XXX Implementation of maxCheckAttempts is difficult because the
		// execution interval of cron or batches are not always one-minute.
		// This is due to the server-side logic of the Mackerel.
	},
}

func doWrap(c *cli.Context) error {
	confFile := c.GlobalString("conf")
	conf, err := config.LoadConfig(confFile)
	if err != nil {
		return err
	}
	apibase := c.GlobalString("apibase")
	if apibase == "" {
		apibase = conf.Apibase
	}

	apikey := conf.Apikey
	if apikey == "" {
		apikey = os.Getenv("MACKEREL_APIKEY")
	}
	if apikey == "" {
		logger.Log("error", "[mkr wrap] failed to detect Mackerel APIKey. Try to specify in mackerel-agent.conf or export MACKEREL_APIKEY='<Your apikey>'")
	}
	hostID, _ := conf.LoadHostID()
	if c.String("host") != "" {
		hostID = c.String("host")
	}
	if hostID == "" {
		logger.Log("error", "[mkr wrap] failed to load hostID. Try to specify -host option explicitly")
	}
	// Since command execution has the highest priority, even when apikey or
	// hostID is empty, we don't return errors and only output the log here.

	cmd := c.Args()
	if len(cmd) > 0 && cmd[0] == "--" {
		cmd = cmd[1:]
	}
	if len(cmd) < 1 {
		return fmt.Errorf("no commands specified")
	}

	return (&wrap{
		apibase:              apibase,
		name:                 c.String("name"),
		detail:               c.Bool("detail"),
		memo:                 c.String("memo"),
		warning:              c.Bool("warning"),
		autoClose:            c.Bool("auto-close"),
		notificationInterval: c.Duration("notification-interval"),
		hostID:               hostID,
		apikey:               apikey,
		cmd:                  cmd,
		outStream:            os.Stdout,
		errStream:            os.Stderr,
	}).run()
}
<|endoftext|>