<commit_before>package govcr

import (
	"bytes"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

// VCRControlPanel holds the parts of a VCR that can be interacted with.
// Client is the HTTP client associated with the VCR.
type VCRControlPanel struct {
	Client *http.Client
}

// Stats returns Stats about the cassette and VCR session.
func (vcr *VCRControlPanel) Stats() Stats {
	vcrT := vcr.Client.Transport.(*vcrTransport)
	return vcrT.Cassette.Stats()
}

const defaultCassettePath = "./govcr-fixtures/"

// VCRConfig holds a set of options for the VCR.
type VCRConfig struct {
	Client                *http.Client
	ExcludeHeaderFunc     ExcludeHeaderFunc
	RequestBodyFilterFunc BodyFilterFunc
	DisableRecording      bool
	Logging               bool
	CassettePath          string
}

// PCB stands for Printed Circuit Board. It is a structure that holds some
// facilities that are passed to the VCR machine to modify its internals.
type pcb struct {
	Transport             http.RoundTripper
	ExcludeHeaderFunc     ExcludeHeaderFunc
	RequestBodyFilterFunc BodyFilterFunc
	Logger                *log.Logger
	DisableRecording      bool
	CassettePath          string
}

const trackNotFound = -1

func (pcbr *pcb) seekTrack(cassette *cassette, req *http.Request) int {
	for idx := range cassette.Tracks {
		if pcbr.trackMatches(cassette, idx, req) {
			pcbr.Logger.Printf("INFO - Cassette '%s' - Found a matching track for %s %s\n", cassette.Name, req.Method, req.URL.String())
			return idx
		}
	}

	return trackNotFound
}

// Matches checks whether the track is a match for the supplied request.
func (pcbr *pcb) trackMatches(cassette *cassette, trackNumber int, req *http.Request) bool {
	if req == nil {
		return false
	}

	// get body data safely
	bodyData, err := readRequestBody(req)
	if err != nil {
		pcbr.Logger.Println(err)
		return false
	}

	track := cassette.Tracks[trackNumber]

	return !track.replayed &&
		track.Request.Method == req.Method &&
		track.Request.URL.String() == req.URL.String() &&
		pcbr.headerResembles(track.Request.Header, req.Header) &&
		pcbr.bodyResembles(track.Request.Body, bodyData)
}

// Resembles compares HTTP headers for equivalence.
func (pcbr *pcb) headerResembles(header1 http.Header, header2 http.Header) bool {
	for k, v1 := range header1 {
		for _, v2 := range v1 {
			if header2.Get(k) != v2 && !pcbr.ExcludeHeaderFunc(k) {
				return false
			}
		}
	}

	// finally assert the number of headers match
	// TODO: perhaps should count how manypcb.ExcludeHeaderFunc() returned true and removes that count from the len to compare?
	return len(header1) == len(header2)
}

// Resembles compares HTTP bodies for equivalence.
func (pcbr *pcb) bodyResembles(body1 string, body2 string) bool {
	return *pcbr.RequestBodyFilterFunc(body1) == *pcbr.RequestBodyFilterFunc(body2)
}

// NewVCR creates a new VCR and loads a cassette.
// A RoundTripper can be provided when a custom Transport is needed (for example to provide
// certificates, etc)
func NewVCR(cassetteName string, vcrConfig *VCRConfig) *VCRControlPanel {
	if vcrConfig == nil {
		vcrConfig = &VCRConfig{}
	}

	// set up logging
	logger := log.New(os.Stderr, "", log.LstdFlags)
	if !vcrConfig.Logging {
		out, _ := os.OpenFile(os.DevNull, os.O_WRONLY|os.O_APPEND, 0600)
		logger.SetOutput(out)
	}

	// use a default client if none provided
	if vcrConfig.Client == nil {
		vcrConfig.Client = http.DefaultClient
	}

	// use a default transport if none provided
	if vcrConfig.Client.Transport == nil {
		vcrConfig.Client.Transport = http.DefaultTransport
	}

	// use a default set of FilterFunc's
	if vcrConfig.ExcludeHeaderFunc == nil {
		vcrConfig.ExcludeHeaderFunc = func(key string) bool {
			return true
		}
	}

	if vcrConfig.RequestBodyFilterFunc == nil {
		vcrConfig.RequestBodyFilterFunc = func(body string) *string {
			return &body
		}
	}

	// load cassette
	cassette, err := loadCassette(cassetteName, vcrConfig.CassettePath)
	if err != nil {
		logger.Fatal(err)
	}

	// create PCB
	pcbr := &pcb{
		// TODO: create appropriate test!
		DisableRecording:      vcrConfig.DisableRecording,
		Transport:             vcrConfig.Client.Transport,
		ExcludeHeaderFunc:     vcrConfig.ExcludeHeaderFunc,
		RequestBodyFilterFunc: vcrConfig.RequestBodyFilterFunc,
		Logger:                logger,
		CassettePath:          vcrConfig.CassettePath,
	}

	// create VCR's HTTP client
	vcrClient := &http.Client{
		Transport: &vcrTransport{
			PCB:      pcbr,
			Cassette: cassette,
		},
	}

	// copy the attributes of the original http.Client
	vcrClient.CheckRedirect = vcrConfig.Client.CheckRedirect
	vcrClient.Jar = vcrConfig.Client.Jar
	vcrClient.Timeout = vcrConfig.Client.Timeout

	// return
	return &VCRControlPanel{
		Client: vcrClient,
	}
}

// ExcludeHeaderFunc is a hook function that is used to filter the Header.
//
// Typically this can be used to remove / amend undesirable custom headers from the request.
//
// For instance, if your application sends requests with a timestamp held in a custom header,
// you likely want to exclude it from the comparison to ensure that the request headers are
// considered a match with those saved on the cassette's track.
type ExcludeHeaderFunc func(key string) bool

// BodyFilterFunc is a hook function that is used to filter the Body.
//
// Typically this can be used to remove / amend undesirable body elements from the request.
//
// For instance, if your application sends requests with a timestamp held in a part of the body,
// you likely want to remove it or force a static timestamp via BodyFilterFunc to
// ensure that the request body matches those saved on the cassette's track.
type BodyFilterFunc func(string) *string

// vcrTransport is the heart of VCR. It provides
// an http.RoundTripper that wraps over the default
// one provided by Go's http package or a custom one
// if specified when calling NewVCR.
type vcrTransport struct {
	PCB               *pcb
	Cassette          *cassette
	ExcludeHeaderFunc ExcludeHeaderFunc
	RequestBodyFilter BodyFilterFunc
}

// RoundTrip is an implementation of http.RoundTripper.
func (t *vcrTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	var (
		// Note: by convention resp should be nil if an error occurs with HTTP
		resp *http.Response

		requestMatched bool
		copiedReq      *http.Request
	)

	// copy the request before the body is closed by the HTTP server.
	copiedReq, err := copyRequest(req)
	if err != nil {
		t.PCB.Logger.Println(err)
		return nil, err
	}

	// attempt to use a track from the cassette that matches
	// the request if one exists.
	if trackNumber := t.PCB.seekTrack(t.Cassette, copiedReq); trackNumber != trackNotFound {
		resp = t.Cassette.replayResponse(trackNumber, copiedReq)
		requestMatched = true
	}

	if !requestMatched {
		// no recorded track was found so execute the request live
		t.PCB.Logger.Printf("INFO - Cassette '%s' - Executing request to live server for %s %s\n", t.Cassette.Name, req.Method, req.URL.String())

		resp, err = t.PCB.Transport.RoundTrip(req)

		if !t.PCB.DisableRecording {
			// the VCR is not in read-only mode so
			// record the HTTP traffic into a new track on the cassette
			t.PCB.Logger.Printf("INFO - Cassette '%s' - Recording new track for %s %s\n", t.Cassette.Name, req.Method, req.URL.String())
			if err := recordNewTrackToCassette(t.Cassette, copiedReq, resp, err); err != nil {
				t.PCB.Logger.Println(err)
			}
		}
	}

	return resp, err
}

// copyRequest makes a copy an HTTP request.
// It ensures that the original request Body stream is restored to its original state
// and can be read from again.
// TODO: should perform a deep copy of the TLS property as with URL
func copyRequest(req *http.Request) (*http.Request, error) {
	if req == nil {
		return nil, nil
	}

	// get a deep copy without body considerations
	copiedReq := copyRequestWithoutBody(req)

	// deal with the Body
	bodyCopy, err := readRequestBody(req)
	if err != nil {
		return nil, err
	}

	// restore Body stream state
	req.Body = toReadCloser(bodyCopy)
	copiedReq.Body = toReadCloser(bodyCopy)

	return copiedReq, nil
}

// copyRequestWithoutBody makes a copy an HTTP request but not the Body (set to nil).
// TODO: should perform a deep copy of the TLS property as with URL
func copyRequestWithoutBody(req *http.Request) *http.Request {
	if req == nil {
		return nil
	}

	// get a shallow copy
	copiedReq := *req

	// remove the channel reference
	copiedReq.Cancel = nil

	// deal with the body
	copiedReq.Body = nil

	// deal with the URL (BEWARE obj == &*obj in Go, with obj being a pointer)
	if req.URL != nil {
		url := *req.URL
		if req.URL.User != nil {
			userInfo := *req.URL.User
			url.User = &userInfo
		}
		copiedReq.URL = &url
	}

	return &copiedReq
}

// readRequestBody reads the Body data stream and restores its states.
// It ensures the stream is restored to its original state and can be read from again.
func readRequestBody(req *http.Request) (string, error) {
	if req == nil || req.Body == nil {
		return "", nil
	}

	// dump the data
	bodyWriter := bytes.NewBuffer(nil)

	_, err := io.Copy(bodyWriter, req.Body)
	if err != nil {
		return "", err
	}

	bodyData := bodyWriter.String()

	// restore original state of the Body source stream
	req.Body.Close()
	req.Body = toReadCloser(bodyData)

	return bodyData, nil
}

// readResponseBody reads the Body data stream and restores its states.
// It ensures the stream is restored to its original state and can be read from again.
func readResponseBody(resp *http.Response) (string, error) {
	if resp == nil || resp.Body == nil {
		return "", nil
	}

	// dump the data
	bodyWriter := bytes.NewBuffer(nil)

	_, err := io.Copy(bodyWriter, resp.Body)
	if err != nil {
		return "", err
	}
	resp.Body.Close()

	bodyData := bodyWriter.String()

	// restore original state of the Body source stream
	resp.Body = toReadCloser(bodyData)

	return bodyData, nil
}

func toReadCloser(body string) io.ReadCloser {
	return ioutil.NopCloser(bytes.NewReader([]byte(body)))
}
<commit_msg>[Fix-Typos] Some minor typos<commit_after>package govcr

import (
	"bytes"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

// VCRControlPanel holds the parts of a VCR that can be interacted with.
// Client is the HTTP client associated with the VCR.
type VCRControlPanel struct {
	Client *http.Client
}

// Stats returns Stats about the cassette and VCR session.
func (vcr *VCRControlPanel) Stats() Stats {
	vcrT := vcr.Client.Transport.(*vcrTransport)
	return vcrT.Cassette.Stats()
}

const defaultCassettePath = "./govcr-fixtures/"

// VCRConfig holds a set of options for the VCR.
type VCRConfig struct {
	Client                *http.Client
	ExcludeHeaderFunc     ExcludeHeaderFunc
	RequestBodyFilterFunc BodyFilterFunc
	DisableRecording      bool
	Logging               bool
	CassettePath          string
}

// PCB stands for Printed Circuit Board. It is a structure that holds some
// facilities that are passed to the VCR machine to modify its internals.
type pcb struct {
	Transport             http.RoundTripper
	ExcludeHeaderFunc     ExcludeHeaderFunc
	RequestBodyFilterFunc BodyFilterFunc
	Logger                *log.Logger
	DisableRecording      bool
	CassettePath          string
}

const trackNotFound = -1

func (pcbr *pcb) seekTrack(cassette *cassette, req *http.Request) int {
	for idx := range cassette.Tracks {
		if pcbr.trackMatches(cassette, idx, req) {
			pcbr.Logger.Printf("INFO - Cassette '%s' - Found a matching track for %s %s\n", cassette.Name, req.Method, req.URL.String())
			return idx
		}
	}

	return trackNotFound
}

// Matches checks whether the track is a match for the supplied request.
func (pcbr *pcb) trackMatches(cassette *cassette, trackNumber int, req *http.Request) bool {
	if req == nil {
		return false
	}

	// get body data safely
	bodyData, err := readRequestBody(req)
	if err != nil {
		pcbr.Logger.Println(err)
		return false
	}

	track := cassette.Tracks[trackNumber]

	return !track.replayed &&
		track.Request.Method == req.Method &&
		track.Request.URL.String() == req.URL.String() &&
		pcbr.headerResembles(track.Request.Header, req.Header) &&
		pcbr.bodyResembles(track.Request.Body, bodyData)
}

// headerResembles compares HTTP headers for equivalence.
func (pcbr *pcb) headerResembles(header1 http.Header, header2 http.Header) bool {
	for k, v1 := range header1 {
		for _, v2 := range v1 {
			if header2.Get(k) != v2 && !pcbr.ExcludeHeaderFunc(k) {
				return false
			}
		}
	}

	// finally assert the number of headers match
	// TODO: perhaps should count how many pcb.ExcludeHeaderFunc() returned true and remove that count from the len to compare?
	return len(header1) == len(header2)
}

// bodyResembles compares HTTP bodies for equivalence.
func (pcbr *pcb) bodyResembles(body1 string, body2 string) bool {
	return *pcbr.RequestBodyFilterFunc(body1) == *pcbr.RequestBodyFilterFunc(body2)
}

// NewVCR creates a new VCR and loads a cassette.
// A RoundTripper can be provided when a custom Transport is needed (for example to provide
// certificates, etc)
func NewVCR(cassetteName string, vcrConfig *VCRConfig) *VCRControlPanel {
	if vcrConfig == nil {
		vcrConfig = &VCRConfig{}
	}

	// set up logging
	logger := log.New(os.Stderr, "", log.LstdFlags)
	if !vcrConfig.Logging {
		out, _ := os.OpenFile(os.DevNull, os.O_WRONLY|os.O_APPEND, 0600)
		logger.SetOutput(out)
	}

	// use a default client if none provided
	if vcrConfig.Client == nil {
		vcrConfig.Client = http.DefaultClient
	}

	// use a default transport if none provided
	if vcrConfig.Client.Transport == nil {
		vcrConfig.Client.Transport = http.DefaultTransport
	}

	// use a default set of FilterFunc's
	if vcrConfig.ExcludeHeaderFunc == nil {
		vcrConfig.ExcludeHeaderFunc = func(key string) bool {
			return true
		}
	}

	if vcrConfig.RequestBodyFilterFunc == nil {
		vcrConfig.RequestBodyFilterFunc = func(body string) *string {
			return &body
		}
	}

	// load cassette
	cassette, err := loadCassette(cassetteName, vcrConfig.CassettePath)
	if err != nil {
		logger.Fatal(err)
	}

	// create PCB
	pcbr := &pcb{
		// TODO: create appropriate test!
		DisableRecording:      vcrConfig.DisableRecording,
		Transport:             vcrConfig.Client.Transport,
		ExcludeHeaderFunc:     vcrConfig.ExcludeHeaderFunc,
		RequestBodyFilterFunc: vcrConfig.RequestBodyFilterFunc,
		Logger:                logger,
		CassettePath:          vcrConfig.CassettePath,
	}

	// create VCR's HTTP client
	vcrClient := &http.Client{
		Transport: &vcrTransport{
			PCB:      pcbr,
			Cassette: cassette,
		},
	}

	// copy the attributes of the original http.Client
	vcrClient.CheckRedirect = vcrConfig.Client.CheckRedirect
	vcrClient.Jar = vcrConfig.Client.Jar
	vcrClient.Timeout = vcrConfig.Client.Timeout

	// return
	return &VCRControlPanel{
		Client: vcrClient,
	}
}

// ExcludeHeaderFunc is a hook function that is used to filter the Header.
//
// Typically this can be used to remove / amend undesirable custom headers from the request.
//
// For instance, if your application sends requests with a timestamp held in a custom header,
// you likely want to exclude it from the comparison to ensure that the request headers are
// considered a match with those saved on the cassette's track.
type ExcludeHeaderFunc func(key string) bool

// BodyFilterFunc is a hook function that is used to filter the Body.
//
// Typically this can be used to remove / amend undesirable body elements from the request.
//
// For instance, if your application sends requests with a timestamp held in a part of the body,
// you likely want to remove it or force a static timestamp via BodyFilterFunc to
// ensure that the request body matches those saved on the cassette's track.
type BodyFilterFunc func(string) *string

// vcrTransport is the heart of VCR. It provides
// an http.RoundTripper that wraps over the default
// one provided by Go's http package or a custom one
// if specified when calling NewVCR.
type vcrTransport struct {
	PCB               *pcb
	Cassette          *cassette
	ExcludeHeaderFunc ExcludeHeaderFunc
	RequestBodyFilter BodyFilterFunc
}

// RoundTrip is an implementation of http.RoundTripper.
func (t *vcrTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	var (
		// Note: by convention resp should be nil if an error occurs with HTTP
		resp *http.Response

		requestMatched bool
		copiedReq      *http.Request
	)

	// copy the request before the body is closed by the HTTP server.
	copiedReq, err := copyRequest(req)
	if err != nil {
		t.PCB.Logger.Println(err)
		return nil, err
	}

	// attempt to use a track from the cassette that matches
	// the request if one exists.
	if trackNumber := t.PCB.seekTrack(t.Cassette, copiedReq); trackNumber != trackNotFound {
		resp = t.Cassette.replayResponse(trackNumber, copiedReq)
		requestMatched = true
	}

	if !requestMatched {
		// no recorded track was found so execute the request live
		t.PCB.Logger.Printf("INFO - Cassette '%s' - Executing request to live server for %s %s\n", t.Cassette.Name, req.Method, req.URL.String())

		resp, err = t.PCB.Transport.RoundTrip(req)

		if !t.PCB.DisableRecording {
			// the VCR is not in read-only mode so
			// record the HTTP traffic into a new track on the cassette
			t.PCB.Logger.Printf("INFO - Cassette '%s' - Recording new track for %s %s\n", t.Cassette.Name, req.Method, req.URL.String())
			if err := recordNewTrackToCassette(t.Cassette, copiedReq, resp, err); err != nil {
				t.PCB.Logger.Println(err)
			}
		}
	}

	return resp, err
}

// copyRequest makes a copy an HTTP request.
// It ensures that the original request Body stream is restored to its original state
// and can be read from again.
// TODO: should perform a deep copy of the TLS property as with URL
func copyRequest(req *http.Request) (*http.Request, error) {
	if req == nil {
		return nil, nil
	}

	// get a deep copy without body considerations
	copiedReq := copyRequestWithoutBody(req)

	// deal with the Body
	bodyCopy, err := readRequestBody(req)
	if err != nil {
		return nil, err
	}

	// restore Body stream state
	req.Body = toReadCloser(bodyCopy)
	copiedReq.Body = toReadCloser(bodyCopy)

	return copiedReq, nil
}

// copyRequestWithoutBody makes a copy an HTTP request but not the Body (set to nil).
// TODO: should perform a deep copy of the TLS property as with URL
func copyRequestWithoutBody(req *http.Request) *http.Request {
	if req == nil {
		return nil
	}

	// get a shallow copy
	copiedReq := *req

	// remove the channel reference
	copiedReq.Cancel = nil

	// deal with the body
	copiedReq.Body = nil

	// deal with the URL (BEWARE obj == &*obj in Go, with obj being a pointer)
	if req.URL != nil {
		url := *req.URL
		if req.URL.User != nil {
			userInfo := *req.URL.User
			url.User = &userInfo
		}
		copiedReq.URL = &url
	}

	return &copiedReq
}

// readRequestBody reads the Body data stream and restores its states.
// It ensures the stream is restored to its original state and can be read from again.
func readRequestBody(req *http.Request) (string, error) {
	if req == nil || req.Body == nil {
		return "", nil
	}

	// dump the data
	bodyWriter := bytes.NewBuffer(nil)

	_, err := io.Copy(bodyWriter, req.Body)
	if err != nil {
		return "", err
	}

	bodyData := bodyWriter.String()

	// restore original state of the Body source stream
	req.Body.Close()
	req.Body = toReadCloser(bodyData)

	return bodyData, nil
}

// readResponseBody reads the Body data stream and restores its states.
// It ensures the stream is restored to its original state and can be read from again.
func readResponseBody(resp *http.Response) (string, error) {
	if resp == nil || resp.Body == nil {
		return "", nil
	}

	// dump the data
	bodyWriter := bytes.NewBuffer(nil)

	_, err := io.Copy(bodyWriter, resp.Body)
	if err != nil {
		return "", err
	}
	resp.Body.Close()

	bodyData := bodyWriter.String()

	// restore original state of the Body source stream
	resp.Body = toReadCloser(bodyData)

	return bodyData, nil
}

func toReadCloser(body string) io.ReadCloser {
	return ioutil.NopCloser(bytes.NewReader([]byte(body)))
}
<|endoftext|>


<commit_before>package facebook

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

type (
	// Graph is a core of this package as its methods allow to communicate with
	// Facebook Graph API
	Graph struct {
		// Register your app at https://developers.facebook.com to get AppID and Secret
		AppID       string
		Secret      string
		AccessToken string
		Expire      time.Duration
		// Full list of scope options here:
		// https://developers.facebook.com/docs/facebook-login/permissions/
		Scope []string

		requestTokenURL *url.URL
		accessTokenURL  *url.URL
		callbackURL     *url.URL
	}
)

// New initializes Graph instance
func New(appID, secret, callback string, scope []string) *Graph {
	var reqTok, accessTok, callbackURL *url.URL
	reqTok, _ = url.Parse("https://www.facebook.com/dialog/oauth")
	accessTok, _ = url.Parse("https://graph.facebook.com/oauth/access_token")
	callbackURL, _ = url.Parse(callback)
	return &Graph{
		AppID:  appID,
		Secret: secret,
		Scope:  scope,
		// AccessToken: "",
		// Expire: time.Time{},
		requestTokenURL: reqTok,
		accessTokenURL:  accessTok,
		callbackURL:     callbackURL,
	}
}

// AuthURL generates URL to redirect to. User will give permission and you'll recieve
// request token. You can pass state parameter to protect from the CSRF
func (g *Graph) AuthURL(state string) string {

	query := g.requestTokenURL.Query()
	query.Set("client_id", g.AppID)
	query.Set("redirect_uri", g.callbackURL.String())
	query.Set("scope", strings.Join(g.Scope, ","))
	query.Set("response_type", "code")
	if state != "" {
		query.Set("state", state)
	}
	g.requestTokenURL.RawQuery = query.Encode()
	return g.requestTokenURL.String()
}

// GetAccessToken parses request for code and retrieve access token from
// response and expiration. In case of errors returns error which you can
// handle on your own (i.e. redirect with error message or return 500 page or
// else.
func (g *Graph) GetAccessToken(r *http.Request) error {
	var err error
	var resp *http.Response
	var result []byte
	var expire time.Duration
	var values url.Values

	query := g.accessTokenURL.Query()
	query.Set("client_id", g.AppID)
	query.Set("redirect_uri", g.callbackURL.String())
	query.Set("client_secret", g.Secret)
	query.Set("code", r.URL.Query().Get("code"))
	g.accessTokenURL.RawQuery = query.Encode()

	if resp, err = http.Get(g.accessTokenURL.String()); err != nil {
		return err
	}
	defer resp.Body.Close()

	if result, err = ioutil.ReadAll(resp.Body); err != nil {
		return err
	}
	if values, err = url.ParseQuery(string(result)); err != nil {
		return err
	}
	if expire, err = time.ParseDuration(values.Get("expires") + "s"); err != nil {
		return err
	}

	g.AccessToken = values.Get("access_token")
	g.Expire = expire
	return nil
}
<commit_msg>remove "s" for time.ParseDuration<commit_after>package facebook

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

type (
	// Graph is a core of this package as its methods allow to communicate with
	// Facebook Graph API
	Graph struct {
		// Register your app at https://developers.facebook.com to get AppID and Secret
		AppID       string
		Secret      string
		AccessToken string
		Expire      time.Duration
		// Full list of scope options here:
		// https://developers.facebook.com/docs/facebook-login/permissions/
		Scope []string

		requestTokenURL *url.URL
		accessTokenURL  *url.URL
		callbackURL     *url.URL
	}
)

// New initializes Graph instance
func New(appID, secret, callback string, scope []string) *Graph {
	var reqTok, accessTok, callbackURL *url.URL
	reqTok, _ = url.Parse("https://www.facebook.com/dialog/oauth")
	accessTok, _ = url.Parse("https://graph.facebook.com/oauth/access_token")
	callbackURL, _ = url.Parse(callback)
	return &Graph{
		AppID:  appID,
		Secret: secret,
		Scope:  scope,
		// AccessToken: "",
		// Expire: time.Time{},
		requestTokenURL: reqTok,
		accessTokenURL:  accessTok,
		callbackURL:     callbackURL,
	}
}

// AuthURL generates URL to redirect to. User will give permission and you'll recieve
// request token. You can pass state parameter to protect from the CSRF
func (g *Graph) AuthURL(state string) string {

	query := g.requestTokenURL.Query()
	query.Set("client_id", g.AppID)
	query.Set("redirect_uri", g.callbackURL.String())
	query.Set("scope", strings.Join(g.Scope, ","))
	query.Set("response_type", "code")
	if state != "" {
		query.Set("state", state)
	}
	g.requestTokenURL.RawQuery = query.Encode()
	return g.requestTokenURL.String()
}

// GetAccessToken parses request for code and retrieve access token from
// response and expiration. In case of errors returns error which you can
// handle on your own (i.e. redirect with error message or return 500 page or
// else.
func (g *Graph) GetAccessToken(r *http.Request) error {
	var err error
	var resp *http.Response
	var result []byte
	var expire time.Duration
	var values url.Values

	query := g.accessTokenURL.Query()
	query.Set("client_id", g.AppID)
	query.Set("redirect_uri", g.callbackURL.String())
	query.Set("client_secret", g.Secret)
	query.Set("code", r.URL.Query().Get("code"))
	g.accessTokenURL.RawQuery = query.Encode()

	if resp, err = http.Get(g.accessTokenURL.String()); err != nil {
		return err
	}
	defer resp.Body.Close()

	if result, err = ioutil.ReadAll(resp.Body); err != nil {
		return err
	}
	if values, err = url.ParseQuery(string(result)); err != nil {
		return err
	}
	if expire, err = time.ParseDuration(values.Get("expires")); err != nil {
		return err
	}

	g.AccessToken = values.Get("access_token")
	g.Expire = expire
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"go/build"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"golang.org/x/tools/go/loader"

	"sourcegraph.com/sourcegraph/srclib-go/gog"
	"sourcegraph.com/sourcegraph/srclib-go/gog/definfo"
	defpkg "sourcegraph.com/sourcegraph/srclib-go/golang_def"
	"sourcegraph.com/sourcegraph/srclib/graph"
	"sourcegraph.com/sourcegraph/srclib/unit"
)

func init() {
	_, err := parser.AddCommand("graph",
		"graph a Go package",
		"Graph a Go package, producing all defs, refs, and docs.",
		&graphCmd,
	)
	if err != nil {
		log.Fatal(err)
	}

	// Check that we have the '-i' flag.
	cmd := exec.Command("go", "help", "build")
	o, err := cmd.Output()
	if err != nil {
		log.Fatal(err)
	}
	usage := strings.Split(string(o), "\n")[0] // The usage is on the first line.
	matched, err := regexp.MatchString("-i", usage)
	if err != nil {
		log.Fatal(err)
	}
	if !matched {
		log.Fatal("'go build' does not have the '-i' flag. Please upgrade to go1.3+.")
	}
}

type GraphCmd struct{}

var graphCmd GraphCmd

// allowErrorsInGoGet is whether the grapher should continue after
// if `go get` fails.
var allowErrorsInGoGet = true

func (c *GraphCmd) Execute(args []string) error {
	inputBytes, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		return err
	}
	var units unit.SourceUnits
	if err := json.NewDecoder(bytes.NewReader(inputBytes)).Decode(&units); err != nil {
		// Legacy API: try parsing input as a single source unit
		var u *unit.SourceUnit
		if err := json.NewDecoder(bytes.NewReader(inputBytes)).Decode(&u); err != nil {
			return err
		}
		units = unit.SourceUnits{u}
	}
	if err := os.Stdin.Close(); err != nil {
		return err
	}

	if len(units) == 0 {
		log.Fatal("Input contains no source unit data.")
	}

	// HACK: fix this. Is this required? We only seem to be setting
	// GOROOT and GOPATH
	if err := unmarshalTypedConfig(units[0].Config); err != nil {
		return err
	}
	if err := config.apply(); err != nil {
		return err
	}

	out, err := Graph(units)
	if err != nil {
		return err
	}

	// Make paths relative to repo.
	for _, gs := range out.Defs {
		if gs.File == "" {
			log.Printf("no file %+v", gs)
		}
		if gs.File != "" {
			gs.File = relPath(cwd, gs.File)
		}
	}
	for _, gr := range out.Refs {
		if gr.File != "" {
			gr.File = relPath(cwd, gr.File)
		}
	}
	for _, gd := range out.Docs {
		if gd.File != "" {
			gd.File = relPath(cwd, gd.File)
		}
	}

	if err := json.NewEncoder(os.Stdout).Encode(out); err != nil {
		return err
	}
	return nil
}

func relPath(base, path string) string {
	rp, err := filepath.Rel(evalSymlinks(base), evalSymlinks(path))
	if err != nil {
		log.Fatalf("Failed to make path %q relative to %q: %s", path, base, err)
	}
	return filepath.ToSlash(rp)
}

func Graph(units unit.SourceUnits) (*graph.Output, error) {
	var pkgs []*build.Package
	for _, u := range units {
		pkg, err := UnitDataAsBuildPackage(u)
		if err != nil {
			log.Printf("Ignoring unit %q due to error in converting to build pkg: %s.", u.Name, err)
			continue
		}
		pkgs = append(pkgs, pkg)
	}

	o, err := doGraph(pkgs)
	if err != nil {
		return nil, err
	}

	o2 := graph.Output{}

	for _, gs := range o.Defs {
		d, err := convertGoDef(gs)
		if err != nil {
			log.Printf("Ignoring def %v due to error in converting to GoDef: %s.", gs, err)
			continue
		}
		if d != nil {
			o2.Defs = append(o2.Defs, d)
		}
	}
	for _, gr := range o.Refs {
		r, err := convertGoRef(gr)
		if err != nil {
			log.Printf("Ignoring ref %v due to error in converting to GoRef: %s.", gr, err)
			continue
		}
		if r != nil {
			o2.Refs = append(o2.Refs, r)
		}
	}
	for _, gd := range o.Docs {
		d, err := convertGoDoc(gd)
		if err != nil {
			log.Printf("Ignoring doc %v due to error in converting to GoDoc: %s.", gd, err)
			continue
		}
		if d != nil {
			o2.Docs = append(o2.Docs, d)
		}
	}

	return &o2, nil
}

func convertGoDef(gs *gog.Def) (*graph.Def, error) {
	resolvedTarget, err := ResolveDep(gs.DefKey.PackageImportPath)
	if err != nil {
		return nil, err
	}
	path := filepath.ToSlash(pathOrDot(filepath.Join(gs.Path...)))
	treePath := treePath(strings.Replace(string(path), ".go", "", -1))
	if !graph.IsValidTreePath(treePath) {
		return nil, fmt.Errorf("'%s' is not a valid tree-path", treePath)
	}

	def := &graph.Def{
		DefKey: graph.DefKey{
			Unit:     resolvedTarget.ToUnit,
			UnitType: resolvedTarget.ToUnitType,
			Path:     path,
		},
		TreePath: treePath,

		Name: gs.Name,
		Kind: definfo.GeneralKindMap[gs.Kind],

		File:     filepath.ToSlash(gs.File),
		DefStart: gs.DeclSpan[0],
		DefEnd:   gs.DeclSpan[1],

		Exported: gs.DefInfo.Exported,
		Local:    !gs.DefInfo.Exported && !gs.DefInfo.PkgScope,
		Test:     strings.HasSuffix(gs.File, "_test.go"),
	}

	d := defpkg.DefData{
		PackageImportPath: gs.DefKey.PackageImportPath,
		DefInfo:           gs.DefInfo,
	}
	def.Data, err = json.Marshal(d)
	if err != nil {
		return nil, err
	}

	if def.File == "" {
		// some cgo defs have empty File; omit them
		return nil, nil
	}

	return def, nil
}

func convertGoRef(gr *gog.Ref) (*graph.Ref, error) {
	resolvedTarget, err := ResolveDep(gr.Def.PackageImportPath)
	if err != nil {
		return nil, err
	}
	if resolvedTarget == nil {
		return nil, nil
	}

	resolvedRefUnit, err := ResolveDep(gr.Unit)
	if err != nil {
		return nil, err
	}
	if resolvedRefUnit == nil {
		return nil, nil
	}

	return &graph.Ref{
		DefRepo:     filepath.ToSlash(uriOrEmpty(resolvedTarget.ToRepoCloneURL)),
		DefPath:     filepath.ToSlash(pathOrDot(filepath.Join(gr.Def.Path...))),
		DefUnit:     resolvedTarget.ToUnit,
		DefUnitType: resolvedTarget.ToUnitType,
		Def:         gr.IsDef,
		Unit:        resolvedRefUnit.ToUnit,
		File:        filepath.ToSlash(gr.File),
		Start:       gr.Span[0],
		End:         gr.Span[1],
	}, nil
}

func convertGoDoc(gd *gog.Doc) (*graph.Doc, error) {
	var key graph.DefKey
	if gd.DefKey != nil {
		resolvedTarget, err := ResolveDep(gd.PackageImportPath)
		if err != nil {
			return nil, err
		}
		key = graph.DefKey{
			Path:     filepath.ToSlash(pathOrDot(filepath.Join(gd.Path...))),
			Unit:     resolvedTarget.ToUnit,
			UnitType: resolvedTarget.ToUnitType,
		}
	}

	resolvedDocUnit, err := ResolveDep(gd.Unit)
	if err != nil {
		return nil, err
	}
	if resolvedDocUnit == nil {
		return nil, nil
	}

	return &graph.Doc{
		DefKey:  key,
		Format:  gd.Format,
		Data:    gd.Data,
		File:    filepath.ToSlash(gd.File),
		Start:   gd.Span[0],
		End:     gd.Span[1],
		DocUnit: resolvedDocUnit.ToUnit,
	}, nil
}

func uriOrEmpty(cloneURL string) string {
	if cloneURL == "" {
		return ""
	}
	return graph.MakeURI(cloneURL)
}

func pathOrDot(path string) string {
	if path == "" {
		return "."
	}
	return path
}

func treePath(path string) string {
	if path == "" || path == "." {
		return string(".")
	}
	return "./" + path
}

func doGraph(pkgs []*build.Package) (*gog.Output, error) {
	// Special-case: if this is a Cgo package, treat the CgoFiles as GoFiles or
	// else the character offsets will be junk.
	//
	// See https://codereview.appspot.com/86140043.
	loaderConfig.Build.CgoEnabled = false
	build.Default = *loaderConfig.Build

	for _, pkg := range pkgs {
		importPath := pkg.ImportPath
		importUnsafe := importPath == "unsafe"

		if len(pkg.CgoFiles) > 0 {
			var allGoFiles []string
			allGoFiles = append(allGoFiles, pkg.GoFiles...)
			allGoFiles = append(allGoFiles, pkg.CgoFiles...)
			allGoFiles = append(allGoFiles, pkg.TestGoFiles...)
			for i, f := range allGoFiles {
				allGoFiles[i] = filepath.Join(cwd, pkg.Dir, f)
			}
			loaderConfig.CreateFromFilenames(pkg.ImportPath, allGoFiles...)
		} else {
			// Normal import
			loaderConfig.ImportWithTests(importPath)
		}

		if importUnsafe {
			// Special-case "unsafe" because go/loader does not let you load it
			// directly.
			if loaderConfig.ImportPkgs == nil {
				loaderConfig.ImportPkgs = make(map[string]bool)
			}
			loaderConfig.ImportPkgs["unsafe"] = true
		}
	}

	prog, err := loaderConfig.Load()
	if err != nil {
		log.Println("XXX", err)
		return nil, err
	}

	g := gog.New(prog)

	var pkgInfos []*loader.PackageInfo
	for _, pkg := range prog.Created {
		if strings.HasSuffix(pkg.Pkg.Name(), "_test") {
			// ignore xtest packages
			continue
		}
		pkgInfos = append(pkgInfos, pkg)
	}
	for _, pkg := range prog.Imported {
		pkgInfos = append(pkgInfos, pkg)
	}

	for _, pkg := range pkgInfos {
		if err := g.Graph(pkg); err != nil {
			log.Printf("Ignoring pkg %q due to error in gog.Graph: %s.", pkg.Pkg.Name(), err)
		}
	}

	return &g.Output, nil
}
<commit_msg>remove debug log message<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"go/build"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"golang.org/x/tools/go/loader"

	"sourcegraph.com/sourcegraph/srclib-go/gog"
	"sourcegraph.com/sourcegraph/srclib-go/gog/definfo"
	defpkg "sourcegraph.com/sourcegraph/srclib-go/golang_def"
	"sourcegraph.com/sourcegraph/srclib/graph"
	"sourcegraph.com/sourcegraph/srclib/unit"
)

func init() {
	_, err := parser.AddCommand("graph",
		"graph a Go package",
		"Graph a Go package, producing all defs, refs, and docs.",
		&graphCmd,
	)
	if err != nil {
		log.Fatal(err)
	}

	// Check that we have the '-i' flag.
	cmd := exec.Command("go", "help", "build")
	o, err := cmd.Output()
	if err != nil {
		log.Fatal(err)
	}
	usage := strings.Split(string(o), "\n")[0] // The usage is on the first line.
	matched, err := regexp.MatchString("-i", usage)
	if err != nil {
		log.Fatal(err)
	}
	if !matched {
		log.Fatal("'go build' does not have the '-i' flag. Please upgrade to go1.3+.")
	}
}

type GraphCmd struct{}

var graphCmd GraphCmd

// allowErrorsInGoGet is whether the grapher should continue after
// if `go get` fails.
var allowErrorsInGoGet = true

func (c *GraphCmd) Execute(args []string) error {
	inputBytes, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		return err
	}
	var units unit.SourceUnits
	if err := json.NewDecoder(bytes.NewReader(inputBytes)).Decode(&units); err != nil {
		// Legacy API: try parsing input as a single source unit
		var u *unit.SourceUnit
		if err := json.NewDecoder(bytes.NewReader(inputBytes)).Decode(&u); err != nil {
			return err
		}
		units = unit.SourceUnits{u}
	}
	if err := os.Stdin.Close(); err != nil {
		return err
	}

	if len(units) == 0 {
		log.Fatal("Input contains no source unit data.")
	}

	// HACK: fix this. Is this required? We only seem to be setting
	// GOROOT and GOPATH
	if err := unmarshalTypedConfig(units[0].Config); err != nil {
		return err
	}
	if err := config.apply(); err != nil {
		return err
	}

	out, err := Graph(units)
	if err != nil {
		return err
	}

	// Make paths relative to repo.
	for _, gs := range out.Defs {
		if gs.File == "" {
			log.Printf("no file %+v", gs)
		}
		if gs.File != "" {
			gs.File = relPath(cwd, gs.File)
		}
	}
	for _, gr := range out.Refs {
		if gr.File != "" {
			gr.File = relPath(cwd, gr.File)
		}
	}
	for _, gd := range out.Docs {
		if gd.File != "" {
			gd.File = relPath(cwd, gd.File)
		}
	}

	if err := json.NewEncoder(os.Stdout).Encode(out); err != nil {
		return err
	}
	return nil
}

func relPath(base, path string) string {
	rp, err := filepath.Rel(evalSymlinks(base), evalSymlinks(path))
	if err != nil {
		log.Fatalf("Failed to make path %q relative to %q: %s", path, base, err)
	}
	return filepath.ToSlash(rp)
}

func Graph(units unit.SourceUnits) (*graph.Output, error) {
	var pkgs []*build.Package
	for _, u := range units {
		pkg, err := UnitDataAsBuildPackage(u)
		if err != nil {
			log.Printf("Ignoring unit %q due to error in converting to build pkg: %s.", u.Name, err)
			continue
		}
		pkgs = append(pkgs, pkg)
	}

	o, err := doGraph(pkgs)
	if err != nil {
		return nil, err
	}

	o2 := graph.Output{}

	for _, gs := range o.Defs {
		d, err := convertGoDef(gs)
		if err != nil {
			log.Printf("Ignoring def %v due to error in converting to GoDef: %s.", gs, err)
			continue
		}
		if d != nil {
			o2.Defs = append(o2.Defs, d)
		}
	}
	for _, gr := range o.Refs {
		r, err := convertGoRef(gr)
		if err != nil {
			log.Printf("Ignoring ref %v due to error in converting to GoRef: %s.", gr, err)
			continue
		}
		if r != nil {
			o2.Refs = append(o2.Refs, r)
		}
	}
	for _, gd := range o.Docs {
		d, err := convertGoDoc(gd)
		if err != nil {
			log.Printf("Ignoring doc %v due to error in converting to GoDoc: %s.", gd, err)
			continue
		}
		if d != nil {
			o2.Docs = append(o2.Docs, d)
		}
	}

	return &o2, nil
}

func convertGoDef(gs *gog.Def) (*graph.Def, error) {
	resolvedTarget, err := ResolveDep(gs.DefKey.PackageImportPath)
	if err != nil {
		return nil, err
	}
	path := filepath.ToSlash(pathOrDot(filepath.Join(gs.Path...)))
	treePath := treePath(strings.Replace(string(path), ".go", "", -1))
	if !graph.IsValidTreePath(treePath) {
		return nil, fmt.Errorf("'%s' is not a valid tree-path", treePath)
	}

	def := &graph.Def{
		DefKey: graph.DefKey{
			Unit:     resolvedTarget.ToUnit,
			UnitType: resolvedTarget.ToUnitType,
			Path:     path,
		},
		TreePath: treePath,

		Name: gs.Name,
		Kind: definfo.GeneralKindMap[gs.Kind],

		File:     filepath.ToSlash(gs.File),
		DefStart: gs.DeclSpan[0],
		DefEnd:   gs.DeclSpan[1],

		Exported: gs.DefInfo.Exported,
		Local:    !gs.DefInfo.Exported && !gs.DefInfo.PkgScope,
		Test:     strings.HasSuffix(gs.File, "_test.go"),
	}

	d := defpkg.DefData{
		PackageImportPath: gs.DefKey.PackageImportPath,
		DefInfo:           gs.DefInfo,
	}
	def.Data, err = json.Marshal(d)
	if err != nil {
		return nil, err
	}

	if def.File == "" {
		// some cgo defs have empty File; omit them
		return nil, nil
	}

	return def, nil
}

func convertGoRef(gr *gog.Ref) (*graph.Ref, error) {
	resolvedTarget, err := ResolveDep(gr.Def.PackageImportPath)
	if err != nil {
		return nil, err
	}
	if resolvedTarget == nil {
		return nil, nil
	}

	resolvedRefUnit, err := ResolveDep(gr.Unit)
	if err != nil {
		return nil, err
	}
	if resolvedRefUnit == nil {
		return nil, nil
	}

	return &graph.Ref{
		DefRepo:     filepath.ToSlash(uriOrEmpty(resolvedTarget.ToRepoCloneURL)),
		DefPath:     filepath.ToSlash(pathOrDot(filepath.Join(gr.Def.Path...))),
		DefUnit:     resolvedTarget.ToUnit,
		DefUnitType: resolvedTarget.ToUnitType,
		Def:         gr.IsDef,
		Unit:        resolvedRefUnit.ToUnit,
		File:        filepath.ToSlash(gr.File),
		Start:       gr.Span[0],
		End:         gr.Span[1],
	}, nil
}

func convertGoDoc(gd *gog.Doc) (*graph.Doc, error) {
	var key graph.DefKey
	if gd.DefKey != nil {
		resolvedTarget, err := ResolveDep(gd.PackageImportPath)
		if err != nil {
			return nil, err
		}
		key = graph.DefKey{
			Path:     filepath.ToSlash(pathOrDot(filepath.Join(gd.Path...))),
			Unit:     resolvedTarget.ToUnit,
			UnitType: resolvedTarget.ToUnitType,
		}
	}

	resolvedDocUnit, err := ResolveDep(gd.Unit)
	if err != nil {
		return nil, err
	}
	if resolvedDocUnit == nil {
		return nil, nil
	}

	return &graph.Doc{
		DefKey:  key,
		Format:  gd.Format,
		Data:    gd.Data,
		File:    filepath.ToSlash(gd.File),
		Start:   gd.Span[0],
		End:     gd.Span[1],
		DocUnit: resolvedDocUnit.ToUnit,
	}, nil
}

func uriOrEmpty(cloneURL string) string {
	if cloneURL == "" {
		return ""
	}
	return graph.MakeURI(cloneURL)
}

func pathOrDot(path string) string {
	if path == "" {
		return "."
	}
	return path
}

func treePath(path string) string {
	if path == "" || path == "." {
		return string(".")
	}
	return "./" + path
}

func doGraph(pkgs []*build.Package) (*gog.Output, error) {
	// Special-case: if this is a Cgo package, treat the CgoFiles as GoFiles or
	// else the character offsets will be junk.
	//
	// See https://codereview.appspot.com/86140043.
	loaderConfig.Build.CgoEnabled = false
	build.Default = *loaderConfig.Build

	for _, pkg := range pkgs {
		importPath := pkg.ImportPath
		importUnsafe := importPath == "unsafe"

		if len(pkg.CgoFiles) > 0 {
			var allGoFiles []string
			allGoFiles = append(allGoFiles, pkg.GoFiles...)
			allGoFiles = append(allGoFiles, pkg.CgoFiles...)
			allGoFiles = append(allGoFiles, pkg.TestGoFiles...)
			for i, f := range allGoFiles {
				allGoFiles[i] = filepath.Join(cwd, pkg.Dir, f)
			}
			loaderConfig.CreateFromFilenames(pkg.ImportPath, allGoFiles...)
		} else {
			// Normal import
			loaderConfig.ImportWithTests(importPath)
		}

		if importUnsafe {
			// Special-case "unsafe" because go/loader does not let you load it
			// directly.
			if loaderConfig.ImportPkgs == nil {
				loaderConfig.ImportPkgs = make(map[string]bool)
			}
			loaderConfig.ImportPkgs["unsafe"] = true
		}
	}

	prog, err := loaderConfig.Load()
	if err != nil {
		return nil, err
	}

	g := gog.New(prog)

	var pkgInfos []*loader.PackageInfo
	for _, pkg := range prog.Created {
		if strings.HasSuffix(pkg.Pkg.Name(), "_test") {
			// ignore xtest packages
			continue
		}
		pkgInfos = append(pkgInfos, pkg)
	}
	for _, pkg := range prog.Imported {
		pkgInfos = append(pkgInfos, pkg)
	}

	for _, pkg := range pkgInfos {
		if err := g.Graph(pkg); err != nil {
			log.Printf("Ignoring pkg %q due to error in gog.Graph: %s.", pkg.Pkg.Name(), err)
		}
	}

	return &g.Output, nil
}
<|endoftext|>


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

package main

import (
	"context"
	drghs_v1 "devrel/cloud/devrel-github-service/drghs/v1"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"devrel/cloud/devrel-github-service/drghs-worker/api/v1beta1"
	"devrel/cloud/devrel-github-service/drghs-worker/internal/apiroutes"
	"devrel/cloud/devrel-github-service/drghs-worker/pkg/googlers"

	"cloud.google.com/go/errorreporting"
	"github.com/gorilla/mux"
	"github.com/urfave/negroni"
	"golang.org/x/build/maintner"
	"golang.org/x/build/maintner/maintnerd/gcslog"
	"golang.org/x/time/rate"
	grpc "google.golang.org/grpc"
)

var (
	listen    = flag.String("listen", ":6343", "listen address")
	verbose   = flag.Bool("verbose", false, "enable verbose debug output")
	bucket    = flag.String("bucket", "cdpe-maintner", "Google Cloud Storage bucket to use for log storage")
	token     = flag.String("token", "", "Token to Access GitHub with")
	projectID = flag.String("gcp-project", "", "The GCP Project this is using")
	owner     = flag.String("owner", "", "The owner of the GitHub repository")
	repo      = flag.String("repo", "", "The repository to track")
)

var (
	corpus          = &maintner.Corpus{}
	googlerResolver googlers.GooglersResolver
	errorClient     *errorreporting.Client
)

func main() {
	// Set log to Stdout. Default for log is Stderr
	log.SetOutput(os.Stdout)
	flag.Parse()

	ctx := context.Background()

	if *projectID == "" {
		log.Fatal("must provide --gcp-project")
	}

	var err error
	errorClient, err = errorreporting.NewClient(ctx, *projectID, errorreporting.Config{
		ServiceName: "devrel-github-services",
		OnError: func(err error) {
			log.Printf("Could not report error: %v", err)
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer errorClient.Close()

	if *token == "" {
		err := fmt.Errorf("must provide --token")
		logAndPrintError(err)
		log.Fatal(err)
	}

	if *owner == "" {
		err := fmt.Errorf("must provide --owner")
		logAndPrintError(err)
		log.Fatal(err)
	}

	if *repo == "" {
		err := fmt.Errorf("must provide --repo")
		logAndPrintError(err)
		log.Fatal(err)
	}

	const qps = 1
	limit := rate.Every(time.Second / qps)
	corpus.SetGitHubLimiter(rate.NewLimiter(limit, qps))

	if *bucket == "" {
		err := fmt.Errorf("must provide --bucket")
		logAndPrintError(err)
		log.Fatal(err)
	}

	gl, err := gcslog.NewGCSLog(ctx, fmt.Sprintf("%v/%v/%v", *bucket, *owner, *repo))
	if err != nil {
		err := fmt.Errorf("NewGCSLog: %v", err)
		logAndPrintError(err)
		log.Fatal(err)
	}

	dataDir := filepath.Join("/tmp", "maintnr")
	log.Printf("dataDir: %v", dataDir)
	if err := os.MkdirAll(dataDir, 0755); err != nil {
		log.Fatal(err)
	}
	log.Printf("Storing data in implicit directory %s", dataDir)

	corpus.EnableLeaderMode(gl, dataDir)

	if err := corpus.Initialize(ctx, gl); err != nil {
		err := fmt.Errorf("Initalize: %v", err)
		logAndPrintError(err)
		log.Fatal(err)
	}

	tkn := strings.TrimSpace(*token)
	corpus.TrackGitHub(*owner, *repo, tkn)

	googlerResolver = googlers.NewGooglersStatic()

	go func() {
		// In the golang.org/x/build/maintner syncloop the update loops
		// are done every 30 seconds.
		// We will go for a less agressive schedule and only sync once every
		// 1 minutes.
		ticker := time.NewTicker(10 * time.Minute)
		for t := range ticker.C {
			log.Printf("Corpus.SyncLoop at %d", t)
			// Lock it for writes
			// Sync
			if err := corpus.Sync(ctx); err != nil {
				logAndPrintError(err)
				log.Printf("Error during corpus sync %v", err)
			}
			// Unlock
		}
	}()

	// Add gRPC service for v1beta1
	grpcServer := grpc.NewServer()
	drghs_v1.RegisterIssueServiceServer(grpcServer, v1beta1.NewIssueServiceV1(corpus, googlerResolver))

	// Send everything through Mux
	r := mux.NewRouter()
	apiSR := r.PathPrefix("/api").Subrouter()

	// Keep a handle on our Api Routers
	apis := registerApis(apiSR)
	log.Printf("Registered: %v Api Routes", len(apis))

	r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") {
			grpcServer.ServeHTTP(w, r)
			return
		}
	})

	r.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(200)
		w.Write([]byte("ok"))
	})

	// Add middleware support
	n := negroni.New()
	l := negroni.NewLogger()
	n.Use(l)
	n.Use(negroni.NewRecovery())
	n.UseHandler(r)

	log.Fatal(http.ListenAndServe(*listen, n))
}

func logAndPrintError(err error) {
	errorClient.Report(errorreporting.Entry{
		Error: err,
	})
	log.Print(err)
}

func registerApis(r *mux.Router) []apiroutes.ApiRoute {
	apis := make([]apiroutes.ApiRoute, 0)
	vzSR := r.PathPrefix("/v0").Subrouter()

	api, err := apiroutes.NewV0Api(corpus, googlerResolver, vzSR)
	if err != nil {
		logAndPrintError(err)
		log.Fatalf("Error registering v0 Api Routes %v", err)
	}
	api.Routes()
	apis = append(apis, api)

	vOSr := r.PathPrefix("/v1").Subrouter()
	api, err = apiroutes.NewV1Api(corpus, googlerResolver, vOSr)
	if err != nil {
		logAndPrintError(err)
		log.Fatalf("Error registering v1 Api Routes %v", err)
	}

	api.Routes()
	apis = append(apis, api)

	return apis
}
<commit_msg>fix(maintner): bucket name pefixed properly (#16)<commit_after>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"context"
	drghs_v1 "devrel/cloud/devrel-github-service/drghs/v1"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"devrel/cloud/devrel-github-service/drghs-worker/api/v1beta1"
	"devrel/cloud/devrel-github-service/drghs-worker/internal/apiroutes"
	"devrel/cloud/devrel-github-service/drghs-worker/pkg/googlers"

	"cloud.google.com/go/errorreporting"
	"github.com/gorilla/mux"
	"github.com/urfave/negroni"
	"golang.org/x/build/maintner"
	"golang.org/x/build/maintner/maintnerd/gcslog"
	"golang.org/x/time/rate"
	grpc "google.golang.org/grpc"
)

var (
	listen    = flag.String("listen", ":6343", "listen address")
	verbose   = flag.Bool("verbose", false, "enable verbose debug output")
	bucket    = flag.String("bucket", "cdpe-maintner", "Google Cloud Storage bucket to use for log storage")
	token     = flag.String("token", "", "Token to Access GitHub with")
	projectID = flag.String("gcp-project", "", "The GCP Project this is using")
	owner     = flag.String("owner", "", "The owner of the GitHub repository")
	repo      = flag.String("repo", "", "The repository to track")
)

var (
	corpus          = &maintner.Corpus{}
	googlerResolver googlers.GooglersResolver
	errorClient     *errorreporting.Client
)

func main() {
	// Set log to Stdout. Default for log is Stderr
	log.SetOutput(os.Stdout)
	flag.Parse()

	ctx := context.Background()

	if *projectID == "" {
		log.Fatal("must provide --gcp-project")
	}

	var err error
	errorClient, err = errorreporting.NewClient(ctx, *projectID, errorreporting.Config{
		ServiceName: "devrel-github-services",
		OnError: func(err error) {
			log.Printf("Could not report error: %v", err)
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer errorClient.Close()

	if *token == "" {
		err := fmt.Errorf("must provide --token")
		logAndPrintError(err)
		log.Fatal(err)
	}

	if *owner == "" {
		err := fmt.Errorf("must provide --owner")
		logAndPrintError(err)
		log.Fatal(err)
	}

	if *repo == "" {
		err := fmt.Errorf("must provide --repo")
		logAndPrintError(err)
		log.Fatal(err)
	}

	const qps = 1
	limit := rate.Every(time.Second / qps)
	corpus.SetGitHubLimiter(rate.NewLimiter(limit, qps))

	if *bucket == "" {
		err := fmt.Errorf("must provide --bucket")
		logAndPrintError(err)
		log.Fatal(err)
	}

	gl, err := gcslog.NewGCSLog(ctx, fmt.Sprintf("%v/%v/%v/", *bucket, *owner, *repo))
	if err != nil {
		err := fmt.Errorf("NewGCSLog: %v", err)
		logAndPrintError(err)
		log.Fatal(err)
	}

	dataDir := filepath.Join("/tmp", "maintnr")
	log.Printf("dataDir: %v", dataDir)
	if err := os.MkdirAll(dataDir, 0755); err != nil {
		log.Fatal(err)
	}
	log.Printf("Storing data in implicit directory %s", dataDir)

	corpus.EnableLeaderMode(gl, dataDir)

	if err := corpus.Initialize(ctx, gl); err != nil {
		err := fmt.Errorf("Initalize: %v", err)
		logAndPrintError(err)
		log.Fatal(err)
	}

	tkn := strings.TrimSpace(*token)
	corpus.TrackGitHub(*owner, *repo, tkn)

	googlerResolver = googlers.NewGooglersStatic()

	go func() {
		// In the golang.org/x/build/maintner syncloop the update loops
		// are done every 30 seconds.
		// We will go for a less agressive schedule and only sync once every
		// 1 minutes.
		ticker := time.NewTicker(10 * time.Minute)
		for t := range ticker.C {
			log.Printf("Corpus.SyncLoop at %d", t)
			// Lock it for writes
			// Sync
			if err := corpus.Sync(ctx); err != nil {
				logAndPrintError(err)
				log.Printf("Error during corpus sync %v", err)
			}
			// Unlock
		}
	}()

	// Add gRPC service for v1beta1
	grpcServer := grpc.NewServer()
	drghs_v1.RegisterIssueServiceServer(grpcServer, v1beta1.NewIssueServiceV1(corpus, googlerResolver))

	// Send everything through Mux
	r := mux.NewRouter()
	apiSR := r.PathPrefix("/api").Subrouter()

	// Keep a handle on our Api Routers
	apis := registerApis(apiSR)
	log.Printf("Registered: %v Api Routes", len(apis))

	r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") {
			grpcServer.ServeHTTP(w, r)
			return
		}
	})

	r.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(200)
		w.Write([]byte("ok"))
	})

	// Add middleware support
	n := negroni.New()
	l := negroni.NewLogger()
	n.Use(l)
	n.Use(negroni.NewRecovery())
	n.UseHandler(r)

	log.Fatal(http.ListenAndServe(*listen, n))
}

func logAndPrintError(err error) {
	errorClient.Report(errorreporting.Entry{
		Error: err,
	})
	log.Print(err)
}

func registerApis(r *mux.Router) []apiroutes.ApiRoute {
	apis := make([]apiroutes.ApiRoute, 0)
	vzSR := r.PathPrefix("/v0").Subrouter()

	api, err := apiroutes.NewV0Api(corpus, googlerResolver, vzSR)
	if err != nil {
		logAndPrintError(err)
		log.Fatalf("Error registering v0 Api Routes %v", err)
	}
	api.Routes()
	apis = append(apis, api)

	vOSr := r.PathPrefix("/v1").Subrouter()
	api, err = apiroutes.NewV1Api(corpus, googlerResolver, vOSr)
	if err != nil {
		logAndPrintError(err)
		log.Fatalf("Error registering v1 Api Routes %v", err)
	}

	api.Routes()
	apis = append(apis, api)

	return apis
}
<|endoftext|>


<commit_before>package decide

import (
	"fmt"
	graphviz "github.com/sjhitchner/go-graphviz"
	"io"
	"sort"
	"strings"
)

// Generate a Graphviz representation of the node tree
func Graph(w io.Writer, node *Node) error {
	graph := graphviz.NewGraph("DecisisonTree")
	GraphWalk(graph, nil, node)
	return graph.Output(w)
}

func GraphWalk(graph *graphviz.Graph, parent, node *Node) {
	graph.AddNode(
		fmt.Sprintf("Node%p", node),
		map[string]string{
			//"label": node.Expression.String(),
			"label": fmt.Sprintf(
				"{<f0>%s|<f1>%s\n\n\n}",
				node.Expression.String(),
				func() string {
					list := make([]string, len(node.Payload))
					for i := range node.Payload {
						list[i] = string(node.Payload[i])
					}
					sort.Strings(list)
					return strings.Join(list, "|")
				}()),
			"shape": "Mrecord",
		})

	if parent != nil {
		graph.AddEdge(
			fmt.Sprintf("Node%p", parent),
			fmt.Sprintf("Node%p", node),
			true,
			map[string]string{
				"label": func() string {
					if node == parent.True {
						return "T"
					}
					return "F"
				}(),
			},
		)
	}

	if node.True != nil {
		GraphWalk(graph, node, node.True)
	}
	if node.False != nil {
		GraphWalk(graph, node, node.False)
	}
}
<commit_msg>updating graphing<commit_after>package decide

import (
	"fmt"
	graphviz "github.com/sjhitchner/go-graphviz"
	"io"
	"sort"
	"strings"
)

// Generate a Graphviz representation of the node tree
func Graph(w io.Writer, node *Node) error {
	graph := graphviz.NewGraph("DecisisonTree")
	GraphWalk(graph, nil, node)
	return graph.Output(w)
}

func GraphWalk(graph *graphviz.Graph, parent, node *Node) {
	graph.AddNode(
		fmt.Sprintf("Node%p", node),
		map[string]string{
			//"label": node.Expression.String(),
			"label": fmt.Sprintf(
				"{<f0>%s|<f1>%s}",
				node.Expression.String(),
				func() string {
					list := make([]string, len(node.Payload))
					for i := range node.Payload {
						list[i] = string(node.Payload[i])
					}
					sort.Strings(list)
					return strings.Join(list, "|")
				}()),
			"shape": "Mrecord",
		})

	if parent != nil {
		graph.AddEdge(
			fmt.Sprintf("Node%p", parent),
			fmt.Sprintf("Node%p", node),
			true,
			map[string]string{
				"label": func() string {
					if node == parent.True {
						return "T"
					}
					return "F"
				}(),
			},
		)
	}

	if node.True != nil {
		GraphWalk(graph, node, node.True)
	}
	if node.False != nil {
		GraphWalk(graph, node, node.False)
	}
}
<|endoftext|>


<commit_before>// Package kitchen implements a driver based on Test Kitchen.
package kitchen

import (
	"errors"
	"fmt"
	"io/ioutil"
	"path"
	"strconv"

	"github.com/mlafeldt/chef-runner/log"
	"github.com/mlafeldt/chef-runner/openssh"
	"github.com/mlafeldt/chef-runner/rsync"
	"github.com/mlafeldt/chef-runner/util"
	"gopkg.in/yaml.v1"
)

// Driver is a driver based on Test Kitchen.
type Driver struct {
	instance    string
	sshClient   *openssh.Client
	rsyncClient *rsync.Client
}

type instanceConfig struct {
	Hostname string `yaml:"hostname"`
	Username string `yaml:"username"`
	SSHKey   string `yaml:"ssh_key"`
	Port     string `yaml:"port"`
}

func readInstanceConfig(instance string) (*instanceConfig, error) {
	configFile := path.Join(".kitchen", instance+".yml")
	log.Debugf("Kitchen config file = %s\n", configFile)

	data, err := ioutil.ReadFile(configFile)
	if err != nil {
		return nil, err
	}

	var config instanceConfig
	if err := yaml.Unmarshal(data, &config); err != nil {
		return nil, err
	}
	log.Debugf("Kitchen config = %+v\n", config)

	if config.Hostname == "" {
		return nil, errors.New(configFile + ": invalid `hostname`")
	}
	if config.Username == "" {
		return nil, errors.New(configFile + ": invalid `username`")
	}
	if config.SSHKey == "" {
		return nil, errors.New(configFile + ": invalid `ssh_key`")
	}
	if _, err := strconv.Atoi(config.Port); err != nil {
		return nil, errors.New(configFile + ": invalid `port`")
	}

	return &config, nil
}

// NewDriver creates a new Test Kitchen driver that communicates with the given
// Test Kitchen instance. Under the hood the instance's YAML configuration is
// parsed to get a working SSH configuration.
func NewDriver(instance string) (*Driver, error) {
	if !util.FileExist(".kitchen.yml") {
		return nil, errors.New("Kitchen YAML file not found")
	}

	config, err := readInstanceConfig(instance)
	if err != nil {
		return nil, err
	}

	// Test Kitchen stores the port as an string
	port, _ := strconv.Atoi(config.Port)

	// This is what `vagrant ssh` uses
	sshOpts := map[string]string{
		"UserKnownHostsFile":     "/dev/null",
		"StrictHostKeyChecking":  "no",
		"PasswordAuthentication": "no",
		"IdentitiesOnly":         "yes",
		"LogLevel":               "FATAL",
	}
	sshClient := &openssh.Client{
		Host:        config.Hostname,
		User:        config.Username,
		Port:        port,
		PrivateKeys: []string{config.SSHKey},
		Options:     sshOpts,
	}

	rsyncClient := rsync.MirrorClient
	rsyncClient.RemoteHost = config.Hostname
	rsyncClient.RemoteShell = sshClient.Shell()

	return &Driver{instance, sshClient, rsyncClient}, nil
}

// RunCommand runs the specified command on the Test Kitchen instance.
func (drv Driver) RunCommand(args []string) error {
	return drv.sshClient.RunCommand(args)
}

// Upload copies files to the Test Kitchen instance.
func (drv Driver) Upload(dst string, src ...string) error {
	return drv.rsyncClient.Copy(dst, src...)
}

// String returns the driver's name.
func (drv Driver) String() string {
	return fmt.Sprintf("Test Kitchen driver (instance: %s)", drv.instance)
}
<commit_msg>Again, don't overwrite rsync.MirrorClient<commit_after>// Package kitchen implements a driver based on Test Kitchen.
package kitchen

import (
	"errors"
	"fmt"
	"io/ioutil"
	"path"
	"strconv"

	"github.com/mlafeldt/chef-runner/log"
	"github.com/mlafeldt/chef-runner/openssh"
	"github.com/mlafeldt/chef-runner/rsync"
	"github.com/mlafeldt/chef-runner/util"
	"gopkg.in/yaml.v1"
)

// Driver is a driver based on Test Kitchen.
type Driver struct {
	instance    string
	sshClient   *openssh.Client
	rsyncClient *rsync.Client
}

type instanceConfig struct {
	Hostname string `yaml:"hostname"`
	Username string `yaml:"username"`
	SSHKey   string `yaml:"ssh_key"`
	Port     string `yaml:"port"`
}

func readInstanceConfig(instance string) (*instanceConfig, error) {
	configFile := path.Join(".kitchen", instance+".yml")
	log.Debugf("Kitchen config file = %s\n", configFile)

	data, err := ioutil.ReadFile(configFile)
	if err != nil {
		return nil, err
	}

	var config instanceConfig
	if err := yaml.Unmarshal(data, &config); err != nil {
		return nil, err
	}
	log.Debugf("Kitchen config = %+v\n", config)

	if config.Hostname == "" {
		return nil, errors.New(configFile + ": invalid `hostname`")
	}
	if config.Username == "" {
		return nil, errors.New(configFile + ": invalid `username`")
	}
	if config.SSHKey == "" {
		return nil, errors.New(configFile + ": invalid `ssh_key`")
	}
	if _, err := strconv.Atoi(config.Port); err != nil {
		return nil, errors.New(configFile + ": invalid `port`")
	}

	return &config, nil
}

// NewDriver creates a new Test Kitchen driver that communicates with the given
// Test Kitchen instance. Under the hood the instance's YAML configuration is
// parsed to get a working SSH configuration.
func NewDriver(instance string) (*Driver, error) {
	if !util.FileExist(".kitchen.yml") {
		return nil, errors.New("Kitchen YAML file not found")
	}

	config, err := readInstanceConfig(instance)
	if err != nil {
		return nil, err
	}

	// Test Kitchen stores the port as an string
	port, _ := strconv.Atoi(config.Port)

	// This is what `vagrant ssh` uses
	sshOpts := map[string]string{
		"UserKnownHostsFile":     "/dev/null",
		"StrictHostKeyChecking":  "no",
		"PasswordAuthentication": "no",
		"IdentitiesOnly":         "yes",
		"LogLevel":               "FATAL",
	}
	sshClient := &openssh.Client{
		Host:        config.Hostname,
		User:        config.Username,
		Port:        port,
		PrivateKeys: []string{config.SSHKey},
		Options:     sshOpts,
	}

	rsyncClient := *rsync.MirrorClient
	rsyncClient.RemoteHost = config.Hostname
	rsyncClient.RemoteShell = sshClient.Shell()

	return &Driver{instance, sshClient, &rsyncClient}, nil
}

// RunCommand runs the specified command on the Test Kitchen instance.
func (drv Driver) RunCommand(args []string) error {
	return drv.sshClient.RunCommand(args)
}

// Upload copies files to the Test Kitchen instance.
func (drv Driver) Upload(dst string, src ...string) error {
	return drv.rsyncClient.Copy(dst, src...)
}

// String returns the driver's name.
func (drv Driver) String() string {
	return fmt.Sprintf("Test Kitchen driver (instance: %s)", drv.instance)
}
<|endoftext|>


<commit_before>package main

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

// group [actions]

type GroupList struct {
	client *Client
}

func (g GroupList) Apply(args []string) {
	switch len(args) {
	case 0:
		g.listGroups("")
	case 1:
		g.listGroups(args[0])
	default:
		Check(false, "expected 0 or 1 argument")
	}
}

func (g GroupList) listGroups(groupid string) {
	path := "/v2/groups"
	if groupid != "" {
		path += "/" + url.QueryEscape(groupid)
	}
	request := g.client.GET(path)
	response, e := g.client.Do(request)
	Check(e == nil, "failed to get response", e)
	defer response.Body.Close()
	dec := json.NewDecoder(response.Body)
	var root Group
	e = dec.Decode(&root)
	Check(e == nil, "failed to unmarshal response", e)
	printGroup(&root)
}

func printGroup(group *Group) {
	title := "GROUPID VERSION GROUPS APPS\n"
	var b bytes.Buffer
	gatherGroup(group, &b)
	text := title + b.String()
	fmt.Println(Columnize(text))
}

func gatherGroup(g *Group, b *bytes.Buffer) {
	b.WriteString(g.GroupID)
	b.WriteString(" ")
	b.WriteString(g.Version)
	b.WriteString(" ")
	b.WriteString(strconv.Itoa(len(g.Groups)))
	b.WriteString(" ")
	b.WriteString(strconv.Itoa(len(g.Apps)))
	b.WriteString("\n")
	for _, group := range g.Groups {
		gatherGroup(group, b)
	}
}

type GroupCreate struct {
	client *Client
}

func (g GroupCreate) Apply(args []string) {
	Check(len(args) == 1, "must supply 1 jsonfile")
	f, e := os.Open(args[0])
	Check(e == nil, "failed to open jsonfile", e)
	defer f.Close()
	request := g.client.POST("/v2/groups", f)
	response, e := g.client.Do(request)
	Check(e == nil, "failed to get response")
	defer response.Body.Close()
	Check(response.StatusCode != 409, "group already exists")

	b, e := ioutil.ReadAll(response.Body)
	Check(e == nil, "error", e)
	fmt.Println(string(b))
	// dec := json.NewDecoder(response.Body)
	// var group Group
	// e = dec.Decode(&group)
	// Check(e == nil, "failed to unmarshal group", e)
	// printGroup(&group)

}

type GroupDestroy struct {
	client *Client
}

func (g GroupDestroy) Apply(args []string) {
	Check(len(args) == 1, "must specify groupid")
	groupid := url.QueryEscape(args[0])
	path := "/v2/groups/" + groupid
	request := g.client.DELETE(path)
	response, e := g.client.Do(request)
	Check(e == nil, "destroy group failed", e)
	defer response.Body.Close()
	c := response.StatusCode
	Check(c != 404, "unknown group")
	Check(c == 200, "destroy group bad status", c)
	dec := json.NewDecoder(response.Body)
	var versionmap map[string]string // ugh
	e = dec.Decode(&versionmap)
	Check(e == nil, "failed to decode response", e)

	v, ok := versionmap["version"]
	Check(ok, "version missing")

	fmt.Println("VERSION\n" + v)
}
<commit_msg>print deployid and version on group create<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/url"
	"os"
	"strconv"
)

// group [actions]

type GroupList struct {
	client *Client
}

func (g GroupList) Apply(args []string) {
	switch len(args) {
	case 0:
		g.listGroups("")
	case 1:
		g.listGroups(args[0])
	default:
		Check(false, "expected 0 or 1 argument")
	}
}

func (g GroupList) listGroups(groupid string) {
	path := "/v2/groups"
	if groupid != "" {
		path += "/" + url.QueryEscape(groupid)
	}
	request := g.client.GET(path)
	response, e := g.client.Do(request)
	Check(e == nil, "failed to get response", e)
	defer response.Body.Close()
	dec := json.NewDecoder(response.Body)
	var root Group
	e = dec.Decode(&root)
	Check(e == nil, "failed to unmarshal response", e)
	printGroup(&root)
}

func printGroup(group *Group) {
	title := "GROUPID VERSION GROUPS APPS\n"
	var b bytes.Buffer
	gatherGroup(group, &b)
	text := title + b.String()
	fmt.Println(Columnize(text))
}

func gatherGroup(g *Group, b *bytes.Buffer) {
	b.WriteString(g.GroupID)
	b.WriteString(" ")
	b.WriteString(g.Version)
	b.WriteString(" ")
	b.WriteString(strconv.Itoa(len(g.Groups)))
	b.WriteString(" ")
	b.WriteString(strconv.Itoa(len(g.Apps)))
	b.WriteString("\n")
	for _, group := range g.Groups {
		gatherGroup(group, b)
	}
}

type GroupCreate struct {
	client *Client
}

func (g GroupCreate) Apply(args []string) {
	Check(len(args) == 1, "must supply 1 jsonfile")
	f, e := os.Open(args[0])
	Check(e == nil, "failed to open jsonfile", e)
	defer f.Close()
	request := g.client.POST("/v2/groups", f)
	response, e := g.client.Do(request)
	Check(e == nil, "failed to get response")
	defer response.Body.Close()
	Check(response.StatusCode != 409, "group already exists")

	dec := json.NewDecoder(response.Body)
	var update Update
	e = dec.Decode(&update)
	Check(e == nil, "failed to decode response", e)
	title := "DEPLOYID VERSION\n"
	text := title + update.DeploymentID + " " + update.Version
	fmt.Println(Columnize(text))
}

type GroupDestroy struct {
	client *Client
}

func (g GroupDestroy) Apply(args []string) {
	Check(len(args) == 1, "must specify groupid")
	groupid := url.QueryEscape(args[0])
	path := "/v2/groups/" + groupid
	request := g.client.DELETE(path)
	response, e := g.client.Do(request)
	Check(e == nil, "destroy group failed", e)
	defer response.Body.Close()
	c := response.StatusCode
	Check(c != 404, "unknown group")
	Check(c == 200, "destroy group bad status", c)
	dec := json.NewDecoder(response.Body)
	var versionmap map[string]string // ugh
	e = dec.Decode(&versionmap)
	Check(e == nil, "failed to decode response", e)

	v, ok := versionmap["version"]
	Check(ok, "version missing")

	fmt.Println("VERSION\n" + v)
}
<|endoftext|>


<commit_before>package proxy

import (
	"io"
	"net"

	"github.com/crunchydata/crunchy-proxy/common"
	"github.com/crunchydata/crunchy-proxy/config"
	"github.com/crunchydata/crunchy-proxy/connect"
	"github.com/crunchydata/crunchy-proxy/pool"
	"github.com/crunchydata/crunchy-proxy/protocol"
	"github.com/crunchydata/crunchy-proxy/util/log"
)

type Proxy struct {
	pools      map[string]*pool.Pool
	writePools chan *pool.Pool
	readPools  chan *pool.Pool
	master     common.Node
	clients    []net.Conn
}

func NewProxy() *Proxy {
	p := &Proxy{}

	p.setupPools()

	return p
}

func (p *Proxy) setupPools() {
	nodes := config.GetNodes()
	capacity := config.GetPoolCapacity()

	/* Initialize pool structures */
	numNodes := len(nodes)
	p.pools = make(map[string]*pool.Pool, numNodes)
	p.writePools = make(chan *pool.Pool, numNodes)
	p.readPools = make(chan *pool.Pool, numNodes)

	for name, node := range nodes {
		/* Create Pool for Node */
		newPool := pool.NewPool(capacity)
		p.pools[name] = newPool

		if node.Role == common.NODE_ROLE_MASTER {
			p.writePools <- newPool
		} else {
			p.readPools <- newPool
		}

		/* Create connections and add to pool. */
		for i := 0; i < capacity; i++ {
			log.Infof("Setting up connection #%d for node '%s'", i, name)
			/* Connect and authenticate */
			log.Infof("Connecting to node '%s' at %s...", name, node.HostPort)
			c, err := connect.Connect(node.HostPort)

			if err != nil {
				log.Errorf("Error establishing connection to node '%s'", name)
				log.Errorf("Error: %s", err.Error())
			} else {
				newPool.Add(c)
			}
		}
	}
}

// Get the next pool. If read is set to true, then a 'read-only' pool will be
// returned. Otherwise, a 'read-write' pool will be returned.
func (p *Proxy) getPool(read bool) *pool.Pool {
	if read {
		return <-p.readPools
	}
	return <-p.writePools
}

// Return the pool. If read is 'true' then, the pool will be returned to the
// 'read-only' collection of pools. Otherwise, it will be returned to the
// 'read-write' collection of pools.
func (p *Proxy) returnPool(pl *pool.Pool, read bool) {
	if read {
		p.readPools <- pl
	} else {
		p.writePools <- pl
	}
}

// HandleConnection handle an incoming connection to the proxy
func (p *Proxy) HandleConnection(client net.Conn) {
	/* Get the client startup message. */
	message, length, err := connect.Receive(client)

	if err != nil {
		log.Error("Error receiving startup message from client.")
		log.Errorf("Error: %s", err.Error())
	}

	/* Get the protocol from the startup message.*/
	version := protocol.GetVersion(message)

	/* Handle the case where the startup message was an SSL request. */
	if version == protocol.SSLRequestCode {
		sslResponse := protocol.NewMessageBuffer([]byte{})

		/* Determine which SSL response to send to client. */
		creds := config.GetCredentials()
		if creds.SSL.Enable {
			sslResponse.WriteByte(protocol.SSLAllowed)
		} else {
			sslResponse.WriteByte(protocol.SSLNotAllowed)
		}

		/*
		 * Send the SSL response back to the client and wait for it to send the
		 * regular startup packet.
		 */
		connect.Send(client, sslResponse.Bytes())

		/* Upgrade the client connection if required. */
		client = connect.UpgradeServerConnection(client)

		/*
		 * Re-read the startup message from the client. It is possible that the
		 * client might not like the response given and as a result it might
		 * close the connection. This is not an 'error' condition as this is an
		 * expected behavior from a client.
		 */
		if message, length, err = connect.Receive(client); err == io.EOF {
			log.Info("The client closed the connection.")
			return
		}
	}

	/*
	 * Validate that the client username and database are the same as that
	 * which is configured for the proxy connections.
	 *
	 * If the the client cannot be validated then send an appropriate PG error
	 * message back to the client.
	 */
	if !connect.ValidateClient(message) {
		pgError := protocol.Error{
			Severity: protocol.ErrorSeverityFatal,
			Code:     protocol.ErrorCodeInvalidAuthorizationSpecification,
			Message:  "could not validate user/database",
		}

		connect.Send(client, pgError.GetMessage())
		log.Error("Could not validate client")
		return
	}

	/* Authenticate the client against the appropriate backend. */
	log.Infof("Authenticating client: %s", client.RemoteAddr())
	authenticated, err := connect.AuthenticateClient(client, message, length)

	/* If the client could not authenticate then go no further. */
	if authenticated {
		log.Infof("Successfully authenticated client: %s", client.RemoteAddr())
	} else {
		log.Error("Client authentication failed.")
		log.Errorf("Error: %s", err.Error())
		return
	}

	/* Process the client messages for the life of the connection. */
	var statementBlock bool
	var cp *pool.Pool    // The connection pool in use
	var backend net.Conn // The backend connection in use
	var read bool
	var end bool

	for {
		var done bool // for message processing loop.

		message, length, err = connect.Receive(client)

		if err != nil {
			switch err {
			case io.EOF:
				log.Infof("Client: %s - closed the connection.", client.RemoteAddr())
			default:
				log.Errorf("Error reading from client connection %s", client.RemoteAddr())
				log.Errorf("Error: %s", err.Error())
			}
			break
		}

		messageType := protocol.GetMessageType(message)

		/*
		 * If the message is a simple query, then it can have read/write
		 * annotations attached to it. Therefore, we need to process it and
		 * determine which backend we need to send it to.
		 */
		if messageType == protocol.QueryMessageType {
			annotations := getAnnotations(message)

			if annotations[StartAnnotation] {
				statementBlock = true
			} else if annotations[EndAnnotation] {
				end = true
				statementBlock = false
			}

			read = annotations[ReadAnnotation]
		}

		/*
		 * If not in a statement block or if the pool or backend are not already
		 * set, then fetch a new backend to receive the message.
		 */
		if !statementBlock && !end || cp == nil || backend == nil {
			cp = p.getPool(read)
			backend = cp.Next()
			p.returnPool(cp, read)
		}

		/* Relay message to client and backend */
		if _, err = connect.Send(backend, message[:length]); err != nil {
			log.Debugf("Error sending message to backend %s", backend.RemoteAddr())
			log.Debugf("Error: %s", err.Error())
		}

		/* */
		for !done {
			if message, length, err = connect.Receive(backend); err != nil {
				log.Debugf("Error receiving response from backend %s", backend.RemoteAddr())
				log.Debugf("Error: %s", err.Error())
				done = true
			}

			messageLength := protocol.GetMessageLength(message)

			/*
			 * Examine all of the messages in the buffer and determine if any of
			 * them are a ReadyForQuery message.
			 */
			for start := 0; start < length; {
				messageType := protocol.GetMessageType(message[start:])
				messageLength = protocol.GetMessageLength(message[start:])

				done = messageType == protocol.ReadyForQueryMessageType

				/*
				 * Calculate the next start position, add '1' to the message
				 * length to account for the message type.
				 */
				start = start + int(messageLength) + 1
			}

			if _, err = connect.Send(client, message[:length]); err != nil {
				log.Debugf("Error sending response to client %s", client.RemoteAddr())
				log.Debugf("Error: %s", err.Error())
				done = true
			}
		}

		/*
		 * If at the end of a statement block or not part of statment block,
		 * then return the connection to the pool.
		 */
		if !statementBlock {
			/*
			 * Toggle 'end' such that a new connection will be fetched on the
			 * next query.
			 */
			if end {
				end = false
			}

			/* Return the backend to the pool it belongs to. */
			cp.Return(backend)
		}
	}
}
<commit_msg>add terminate message handling.<commit_after>package proxy

import (
	"io"
	"net"

	"github.com/crunchydata/crunchy-proxy/common"
	"github.com/crunchydata/crunchy-proxy/config"
	"github.com/crunchydata/crunchy-proxy/connect"
	"github.com/crunchydata/crunchy-proxy/pool"
	"github.com/crunchydata/crunchy-proxy/protocol"
	"github.com/crunchydata/crunchy-proxy/util/log"
)

type Proxy struct {
	pools      map[string]*pool.Pool
	writePools chan *pool.Pool
	readPools  chan *pool.Pool
	master     common.Node
	clients    []net.Conn
}

func NewProxy() *Proxy {
	p := &Proxy{}

	p.setupPools()

	return p
}

func (p *Proxy) setupPools() {
	nodes := config.GetNodes()
	capacity := config.GetPoolCapacity()

	/* Initialize pool structures */
	numNodes := len(nodes)
	p.pools = make(map[string]*pool.Pool, numNodes)
	p.writePools = make(chan *pool.Pool, numNodes)
	p.readPools = make(chan *pool.Pool, numNodes)

	for name, node := range nodes {
		/* Create Pool for Node */
		newPool := pool.NewPool(capacity)
		p.pools[name] = newPool

		if node.Role == common.NODE_ROLE_MASTER {
			p.writePools <- newPool
		} else {
			p.readPools <- newPool
		}

		/* Create connections and add to pool. */
		for i := 0; i < capacity; i++ {
			log.Infof("Setting up connection #%d for node '%s'", i, name)
			/* Connect and authenticate */
			log.Infof("Connecting to node '%s' at %s...", name, node.HostPort)
			c, err := connect.Connect(node.HostPort)

			if err != nil {
				log.Errorf("Error establishing connection to node '%s'", name)
				log.Errorf("Error: %s", err.Error())
			} else {
				newPool.Add(c)
			}
		}
	}
}

// Get the next pool. If read is set to true, then a 'read-only' pool will be
// returned. Otherwise, a 'read-write' pool will be returned.
func (p *Proxy) getPool(read bool) *pool.Pool {
	if read {
		return <-p.readPools
	}
	return <-p.writePools
}

// Return the pool. If read is 'true' then, the pool will be returned to the
// 'read-only' collection of pools. Otherwise, it will be returned to the
// 'read-write' collection of pools.
func (p *Proxy) returnPool(pl *pool.Pool, read bool) {
	if read {
		p.readPools <- pl
	} else {
		p.writePools <- pl
	}
}

// HandleConnection handle an incoming connection to the proxy
func (p *Proxy) HandleConnection(client net.Conn) {
	/* Get the client startup message. */
	message, length, err := connect.Receive(client)

	if err != nil {
		log.Error("Error receiving startup message from client.")
		log.Errorf("Error: %s", err.Error())
	}

	/* Get the protocol from the startup message.*/
	version := protocol.GetVersion(message)

	/* Handle the case where the startup message was an SSL request. */
	if version == protocol.SSLRequestCode {
		sslResponse := protocol.NewMessageBuffer([]byte{})

		/* Determine which SSL response to send to client. */
		creds := config.GetCredentials()
		if creds.SSL.Enable {
			sslResponse.WriteByte(protocol.SSLAllowed)
		} else {
			sslResponse.WriteByte(protocol.SSLNotAllowed)
		}

		/*
		 * Send the SSL response back to the client and wait for it to send the
		 * regular startup packet.
		 */
		connect.Send(client, sslResponse.Bytes())

		/* Upgrade the client connection if required. */
		client = connect.UpgradeServerConnection(client)

		/*
		 * Re-read the startup message from the client. It is possible that the
		 * client might not like the response given and as a result it might
		 * close the connection. This is not an 'error' condition as this is an
		 * expected behavior from a client.
		 */
		if message, length, err = connect.Receive(client); err == io.EOF {
			log.Info("The client closed the connection.")
			return
		}
	}

	/*
	 * Validate that the client username and database are the same as that
	 * which is configured for the proxy connections.
	 *
	 * If the the client cannot be validated then send an appropriate PG error
	 * message back to the client.
	 */
	if !connect.ValidateClient(message) {
		pgError := protocol.Error{
			Severity: protocol.ErrorSeverityFatal,
			Code:     protocol.ErrorCodeInvalidAuthorizationSpecification,
			Message:  "could not validate user/database",
		}

		connect.Send(client, pgError.GetMessage())
		log.Error("Could not validate client")
		return
	}

	/* Authenticate the client against the appropriate backend. */
	log.Infof("Authenticating client: %s", client.RemoteAddr())
	authenticated, err := connect.AuthenticateClient(client, message, length)

	/* If the client could not authenticate then go no further. */
	if authenticated {
		log.Infof("Successfully authenticated client: %s", client.RemoteAddr())
	} else {
		log.Error("Client authentication failed.")
		log.Errorf("Error: %s", err.Error())
		return
	}

	/* Process the client messages for the life of the connection. */
	var statementBlock bool
	var cp *pool.Pool    // The connection pool in use
	var backend net.Conn // The backend connection in use
	var read bool
	var end bool

	for {
		var done bool // for message processing loop.

		message, length, err = connect.Receive(client)

		if err != nil {
			switch err {
			case io.EOF:
				log.Infof("Client: %s - closed the connection.", client.RemoteAddr())
			default:
				log.Errorf("Error reading from client connection %s", client.RemoteAddr())
				log.Errorf("Error: %s", err.Error())
			}
			break
		}

		messageType := protocol.GetMessageType(message)

		/*
		 * If the message is a simple query, then it can have read/write
		 * annotations attached to it. Therefore, we need to process it and
		 * determine which backend we need to send it to.
		 */
		if messageType == protocol.TerminateMessageType {
			log.Infof("Terminate Message Received: %s", client.RemoteAddr())
			return
		} else if messageType == protocol.QueryMessageType {
			annotations := getAnnotations(message)

			if annotations[StartAnnotation] {
				statementBlock = true
			} else if annotations[EndAnnotation] {
				end = true
				statementBlock = false
			}

			read = annotations[ReadAnnotation]
		}

		/*
		 * If not in a statement block or if the pool or backend are not already
		 * set, then fetch a new backend to receive the message.
		 */
		if !statementBlock && !end || cp == nil || backend == nil {
			cp = p.getPool(read)
			backend = cp.Next()
			p.returnPool(cp, read)
		}

		/* Relay message to client and backend */
		if _, err = connect.Send(backend, message[:length]); err != nil {
			log.Debugf("Error sending message to backend %s", backend.RemoteAddr())
			log.Debugf("Error: %s", err.Error())
		}

		/* */
		for !done {
			if message, length, err = connect.Receive(backend); err != nil {
				log.Debugf("Error receiving response from backend %s", backend.RemoteAddr())
				log.Debugf("Error: %s", err.Error())
				done = true
			}

			messageLength := protocol.GetMessageLength(message)

			/*
			 * Examine all of the messages in the buffer and determine if any of
			 * them are a ReadyForQuery message.
			 */
			for start := 0; start < length; {
				messageType := protocol.GetMessageType(message[start:])
				messageLength = protocol.GetMessageLength(message[start:])

				done = messageType == protocol.ReadyForQueryMessageType

				/*
				 * Calculate the next start position, add '1' to the message
				 * length to account for the message type.
				 */
				start = start + int(messageLength) + 1
			}

			if _, err = connect.Send(client, message[:length]); err != nil {
				log.Debugf("Error sending response to client %s", client.RemoteAddr())
				log.Debugf("Error: %s", err.Error())
				done = true
			}
		}

		/*
		 * If at the end of a statement block or not part of statment block,
		 * then return the connection to the pool.
		 */
		if !statementBlock {
			/*
			 * Toggle 'end' such that a new connection will be fetched on the
			 * next query.
			 */
			if end {
				end = false
			}

			/* Return the backend to the pool it belongs to. */
			cp.Return(backend)
		}
	}
}
<|endoftext|>


<commit_before>package proxyclient

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

// 连接
type Conn interface {
	net.Conn
	ProxyClient() ProxyClient // 获得所属的代理
}

// 表示 TCP 连接
type TCPConn interface {
	Conn

	/*
		SetLinger设定当连接中仍有数据等待发送或接受时的Close方法的行为。
		如果sec < 0（默认），Close方法立即返回，操作系统停止后台数据发送；如果 sec == 0，Close立刻返回，操作系统丢弃任何未发送或未接收的数据；如果sec > 0，Close方法阻塞最多sec秒，等待数据发送或者接收，在一些操作系统中，在超时后，任何未发送的数据会被丢弃。
	*/
	SetLinger(sec int) error

	// SetNoDelay设定操作系统是否应该延迟数据包传递，以便发送更少的数据包（Nagle's算法）。默认为真，即数据应该在Write方法后立刻发送。
	SetNoDelay(noDelay bool) error

	//SetReadBuffer设置该连接的系统接收缓冲
	SetReadBuffer(bytes int) error

	//SetWriteBuffer设置该连接的系统发送缓冲
	SetWriteBuffer(bytes int) error
}

// 表示 UDP 连接
type UDPConn interface {
	Conn
}

// 仿 net 库接口
type ProxyClient interface {
	UpProxy() ProxyClient
	SetUpProxy(upProxy ProxyClient) error

	Dial(network, address string) (Conn, error)
	DialTimeout(network, address string, timeout time.Duration) (Conn, error)
	DialTCP(net string, laddr, raddr *net.TCPAddr) (TCPConn, error)
	//ListenTCP在本地TCP地址laddr上声明并返回一个*TCPListener，net参数必须是"tcp"、"tcp4"、"tcp6"，如果laddr的端口字段为0，函数将选择一个当前可用的端口，可以用Listener的Addr方法获得该端口。
	//ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error)
	//DialTCP在网络协议net上连接本地地址laddr和远端地址raddr。net必须是"udp"、"udp4"、"udp6"；如果laddr不是nil，将使用它作为本地地址，否则自动选择一个本地地址。
	DialUDP(net string, laddr, raddr *net.UDPAddr) (UDPConn, error)
}

// 创建代理客户端
// http 代理 http://123.123.123.123:8088
// socks4a 代理 socks4a://123.
// socks5 代理 socks5://123.
// 直连 direct://0.0.0.0:0000/?LocalAddr=123.123.123.123:0
func NewProxyClient(addr string) (ProxyClient, error) {
	u, err := url.Parse(addr)
	if err != nil {
		return nil, errors.New("addr 错误的格式")
	}
	_query, err := url.ParseQuery(u.RawQuery)
	query := make(map[string][]string)
	for k, v := range _query {
		query[strings.ToLower(k)] = v
	}

	switch strings.ToLower(u.Scheme) {
	case "direct":
		if localAddr, ok := query["LocalAddr"]; ok {
			return NewDriectProxyClient(localAddr[0])
		} else {
			return NewDriectProxyClient(":0")
		}
	}
	panic("未完成")
}
<commit_msg>多重代理隧道支持<commit_after>package proxyclient

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

// 连接
type Conn interface {
	net.Conn
	ProxyClient() ProxyClient // 获得所属的代理
}

// 表示 TCP 连接
type TCPConn interface {
	Conn

	/*
		SetLinger设定当连接中仍有数据等待发送或接受时的Close方法的行为。
		如果sec < 0（默认），Close方法立即返回，操作系统停止后台数据发送；如果 sec == 0，Close立刻返回，操作系统丢弃任何未发送或未接收的数据；如果sec > 0，Close方法阻塞最多sec秒，等待数据发送或者接收，在一些操作系统中，在超时后，任何未发送的数据会被丢弃。
	*/
	SetLinger(sec int) error

	// SetNoDelay设定操作系统是否应该延迟数据包传递，以便发送更少的数据包（Nagle's算法）。默认为真，即数据应该在Write方法后立刻发送。
	SetNoDelay(noDelay bool) error

	//SetReadBuffer设置该连接的系统接收缓冲
	SetReadBuffer(bytes int) error

	//SetWriteBuffer设置该连接的系统发送缓冲
	SetWriteBuffer(bytes int) error
}

// 表示 UDP 连接
type UDPConn interface {
	Conn
}

// 仿 net 库接口
type ProxyClient interface {
	UpProxy() ProxyClient
	SetUpProxy(upProxy ProxyClient) error

	Dial(network, address string) (Conn, error)
	DialTimeout(network, address string, timeout time.Duration) (Conn, error)
	DialTCP(net string, laddr, raddr *net.TCPAddr) (TCPConn, error)
	//ListenTCP在本地TCP地址laddr上声明并返回一个*TCPListener，net参数必须是"tcp"、"tcp4"、"tcp6"，如果laddr的端口字段为0，函数将选择一个当前可用的端口，可以用Listener的Addr方法获得该端口。
	//ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error)
	//DialTCP在网络协议net上连接本地地址laddr和远端地址raddr。net必须是"udp"、"udp4"、"udp6"；如果laddr不是nil，将使用它作为本地地址，否则自动选择一个本地地址。
	DialUDP(net string, laddr, raddr *net.UDPAddr) (UDPConn, error)
}

// 创建代理客户端
// http 代理 http://123.123.123.123:8088
// socks4 代理 socks4://123.123.123.123:5050  不支持远端 dns 解析
// socks4a 代理 socks4a://123.123.123.123:5050
// socks5 代理 socks5://123.123.123.123:5050?upProxy=http://145.2.1.3:8080
// 直连 direct://0.0.0.0:0000/?LocalAddr=123.123.123.123:0
func NewProxyClient(addr string) (ProxyClient, error) {
	u, err := url.Parse(addr)
	if err != nil {
		return nil, errors.New("addr 错误的格式")
	}
	_query, err := url.ParseQuery(u.RawQuery)
	query := make(map[string][]string)
	for k, v := range _query {
		query[strings.ToLower(k)] = v
	}

	scheme := strings.ToLower(strings.TrimSpace(u.Scheme))

	var upProxy ProxyClient = nil
	if up, ok := query["upproxy"]; ok == true {
		if upProxy, err = NewDriectProxyClient(up[0]); err != nil {
			return nil, fmt.Errorf("upProxy 创建失败：%v", err)
		}
	}

	switch scheme {
	case "direct":
		if localAddr, ok := query["LocalAddr"]; ok {
			return NewDriectProxyClient(localAddr[0])
		} else {
			return NewDriectProxyClient(":0")
		}
	case "socks4", "socks4a", "socks5":
		return NewSocksProxyClient(scheme, u.Host, upProxy)
	default:
		return nil, fmt.Errorf("未识别的代理类型：%v", scheme)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
)

func removeDirectories(directories []string) {
	for _, directory := range directories {

		if err := os.RemoveAll(directory); err != nil {
			fmt.Println(err)
		}
	}
}

func main() {
	directories := []string{"tmp"}
	removeDirectories(directories)

	fmt.Println("Have cleaned")
}
<commit_msg>read config file<commit_after>package main

import (
	"fmt"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"os"
)

func removeDirectories(directories []string) {
	for _, directory := range directories {

		if err := os.RemoveAll(directory); err != nil {
			fmt.Println(err)
		}
	}
}

func readConfigFile() {
	configFile := os.Getenv("HOME") + "/.houki.yml"

	buf, err := ioutil.ReadFile(configFile)
	if err != nil {
		fmt.Println(err)
		return
	}

	m := make(map[interface{}]interface{})
	err = yaml.Unmarshal(buf, &m)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%s\n", m["Directory"])
}

func main() {
	readConfigFile()
	directories := []string{"tmp"}
	removeDirectories(directories)

	fmt.Println("Have cleaned")
}
<|endoftext|>


<commit_before>package discovery

import (
	"context"
	"time"

	cid "github.com/ipfs/go-cid"
	pstore "github.com/libp2p/go-libp2p-peerstore"
	routing "github.com/libp2p/go-libp2p-routing"
	mh "github.com/multiformats/go-multihash"
)

// RoutingDiscovery is an implementation of discovery using ContentRouting
type RoutingDiscovery struct {
	router routing.ContentRouting
}

func NewRoutingDiscovery(router routing.ContentRouting) Discovery {
	return &RoutingDiscovery{router}
}

func (d *RoutingDiscovery) Advertise(ctx context.Context, ns string, opts ...Option) (time.Duration, error) {
	cid, err := nsToCid(ns)
	if err != nil {
		return 0, err
	}

	err = d.router.Provide(ctx, cid, true)
	if err != nil {
		return 0, err
	}

	// this is the dht provide validity
	return 24 * time.Hour, nil
}

func (d *RoutingDiscovery) FindPeers(ctx context.Context, ns string, opts ...Option) (<-chan pstore.PeerInfo, error) {
	options := &Options{}
	err := options.Apply(opts...)
	if err != nil {
		return nil, err
	}

	limit := options.Limit
	if limit == 0 {
		limit = 100 // that's just arbitrary, but FindProvidersAsync needs a count
	}

	cid, err := nsToCid(ns)
	if err != nil {
		return nil, err
	}

	return d.router.FindProvidersAsync(ctx, cid, limit), nil
}

func nsToCid(ns string) (cid.Cid, error) {
	h, err := mh.Encode([]byte(ns), mh.SHA2_256)
	if err != nil {
		return cid.Undef, err
	}

	return cid.NewCidV1(cid.Raw, h), nil
}
<commit_msg>niceties and best practices<commit_after>package discovery

import (
	"context"
	"time"

	cid "github.com/ipfs/go-cid"
	pstore "github.com/libp2p/go-libp2p-peerstore"
	routing "github.com/libp2p/go-libp2p-routing"
	mh "github.com/multiformats/go-multihash"
)

// RoutingDiscovery is an implementation of discovery using ContentRouting
type RoutingDiscovery struct {
	routing.ContentRouting
}

func NewRoutingDiscovery(router routing.ContentRouting) *RoutingDiscovery {
	return &RoutingDiscovery{router}
}

func (d *RoutingDiscovery) Advertise(ctx context.Context, ns string, opts ...Option) (time.Duration, error) {
	cid, err := nsToCid(ns)
	if err != nil {
		return 0, err
	}

	err = d.Provide(ctx, cid, true)
	if err != nil {
		return 0, err
	}

	// this is the dht provide validity
	return 24 * time.Hour, nil
}

func (d *RoutingDiscovery) FindPeers(ctx context.Context, ns string, opts ...Option) (<-chan pstore.PeerInfo, error) {
	var options Options
	err := options.Apply(opts...)
	if err != nil {
		return nil, err
	}

	limit := options.Limit
	if limit == 0 {
		limit = 100 // that's just arbitrary, but FindProvidersAsync needs a count
	}

	cid, err := nsToCid(ns)
	if err != nil {
		return nil, err
	}

	return d.FindProvidersAsync(ctx, cid, limit), nil
}

func nsToCid(ns string) (cid.Cid, error) {
	h, err := mh.Encode([]byte(ns), mh.SHA2_256)
	if err != nil {
		return cid.Undef, err
	}

	return cid.NewCidV1(cid.Raw, h), nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"image"
	_ "image/gif"
	"image/jpeg"
	_ "image/png"
	"io"
	"math"
	"mime"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strconv"

	_ "golang.org/x/image/bmp"
	"golang.org/x/image/draw"
)

func resizeImage(data io.ReadSeeker, width, height int) ([]byte, error) {
	info, _, err := image.DecodeConfig(data)
	if err != nil {
		return nil, err
	}
	if _, err := data.Seek(0, io.SeekStart); err != nil {
		return nil, err
	}
	img, _, err := image.Decode(data)
	if err != nil {
		return nil, err
	}
	imgRatio := float64(info.Width) / float64(info.Height)
	outRatio := float64(width) / float64(height)
	if imgRatio > outRatio {
		height = int(math.Round(float64(height*info.Height) / float64(info.Width)))
	} else {
		width = int(math.Round(float64(width*info.Width) / float64(info.Height)))
	}
	rect := image.Rect(0, 0, width, height)
	out := image.NewRGBA(rect)
	draw.CatmullRom.Scale(out, rect, img, img.Bounds(), draw.Over, nil)
	outwriter := new(bytes.Buffer)
	opt := jpeg.Options{Quality: 100}
	jpeg.Encode(outwriter, out, &opt)
	return outwriter.Bytes(), nil
}

// ImageHandler returns HTTP handler for image
func ImageHandler(local string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		rpath := filepath.Join(filepath.FromSlash(local), filepath.FromSlash(r.URL.Path))
		i, err := os.Stat(rpath)
		if err != nil {
			http.NotFound(w, r)
			return
		}
		if !modifiedSince(r, i.ModTime()) {
			w.WriteHeader(http.StatusNotModified)
			return
		}
		f, err := os.Open(rpath)
		if err != nil {
			http.NotFound(w, r)
			return
		}
		defer f.Close()
		q := r.URL.Query()
		ws, hs := q.Get("width"), q.Get("height")
		if len(ws) == 0 || len(hs) == 0 {
			w.Header().Add("Cache-Control", "max-age=86400")
			w.Header().Add("Content-Length", strconv.FormatInt(i.Size(), 10))
			w.Header().Add("Content-Type", mime.TypeByExtension(path.Ext(rpath)))
			w.Header().Add("Last-Modified", i.ModTime().Format(http.TimeFormat))
			io.CopyN(w, f, i.Size())
			return
		}
		wi, err := strconv.Atoi(ws)
		if err != nil {
			writeHTTPError(w, http.StatusBadRequest, err)
		}
		hi, err := strconv.Atoi(hs)
		if err != nil {
			writeHTTPError(w, http.StatusBadRequest, err)
		}
		b, err := resizeImage(f, wi, hi)
		if err != nil {
			writeHTTPError(w, http.StatusInternalServerError, err)
		}
		w.Header().Add("Cache-Control", "max-age=86400")
		w.Header().Add("Content-Length", strconv.Itoa(len(b)))
		w.Header().Add("Content-Type", mime.TypeByExtension(path.Ext(rpath)))
		w.Header().Add("Last-Modified", i.ModTime().Format(http.TimeFormat))
		w.Write(b)
	}
}
<commit_msg>remove unused file<commit_after><|endoftext|>