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

package client

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"mime"
	"net/http"
	"net/http/httputil"
	"os"
	"path"
	"strings"
	"sync"
	"time"

	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"

	"github.com/go-openapi/runtime"
	"github.com/go-openapi/strfmt"
)

// TLSClientOptions to configure client authentication with mutual TLS
type TLSClientOptions struct {
	Certificate        string
	Key                string
	CA                 string
	ServerName         string
	InsecureSkipVerify bool
	_                  struct{}
}

// TLSClientAuth creates a tls.Config for mutual auth
func TLSClientAuth(opts TLSClientOptions) (*tls.Config, error) {
	// load client cert
	cert, err := tls.LoadX509KeyPair(opts.Certificate, opts.Key)
	if err != nil {
		return nil, fmt.Errorf("tls client cert: %v", err)
	}

	// create client tls config
	cfg := &tls.Config{}
	cfg.Certificates = []tls.Certificate{cert}
	cfg.InsecureSkipVerify = opts.InsecureSkipVerify

	// When no CA certificate is provided, default to the system cert pool
	// that way when a request is made to a server known by the system trust store,
	// the name is still verified
	if opts.CA != "" {
		// load ca cert
		caCert, err := ioutil.ReadFile(opts.CA)
		if err != nil {
			return nil, fmt.Errorf("tls client ca: %v", err)
		}
		caCertPool := x509.NewCertPool()
		caCertPool.AppendCertsFromPEM(caCert)
		cfg.RootCAs = caCertPool
	}

	// apply servername overrride
	if opts.ServerName != "" {
		cfg.InsecureSkipVerify = false
		cfg.ServerName = opts.ServerName
	}

	cfg.BuildNameToCertificate()

	return cfg, nil
}

// TLSTransport creates a http client transport suitable for mutual tls auth
func TLSTransport(opts TLSClientOptions) (http.RoundTripper, error) {
	cfg, err := TLSClientAuth(opts)
	if err != nil {
		return nil, err
	}

	return &http.Transport{TLSClientConfig: cfg}, nil
}

// TLSClient creates a http.Client for mutual auth
func TLSClient(opts TLSClientOptions) (*http.Client, error) {
	transport, err := TLSTransport(opts)
	if err != nil {
		return nil, err
	}
	return &http.Client{Transport: transport}, nil
}

// DefaultTimeout the default request timeout
var DefaultTimeout = 30 * time.Second

// Runtime represents an API client that uses the transport
// to make http requests based on a swagger specification.
type Runtime struct {
	DefaultMediaType      string
	DefaultAuthentication runtime.ClientAuthInfoWriter
	Consumers             map[string]runtime.Consumer
	Producers             map[string]runtime.Producer

	Transport http.RoundTripper
	Jar       http.CookieJar
	//Spec      *spec.Document
	Host     string
	BasePath string
	Formats  strfmt.Registry
	Debug    bool
	Context  context.Context

	clientOnce *sync.Once
	client     *http.Client
	schemes    []string
	do         func(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error)
}

// New creates a new default runtime for a swagger api runtime.Client
func New(host, basePath string, schemes []string) *Runtime {
	var rt Runtime
	rt.DefaultMediaType = runtime.JSONMime

	// TODO: actually infer this stuff from the spec
	rt.Consumers = map[string]runtime.Consumer{
		runtime.JSONMime:    runtime.JSONConsumer(),
		runtime.XMLMime:     runtime.XMLConsumer(),
		runtime.TextMime:    runtime.TextConsumer(),
		runtime.DefaultMime: runtime.ByteStreamConsumer(),
	}
	rt.Producers = map[string]runtime.Producer{
		runtime.JSONMime:    runtime.JSONProducer(),
		runtime.XMLMime:     runtime.XMLProducer(),
		runtime.TextMime:    runtime.TextProducer(),
		runtime.DefaultMime: runtime.ByteStreamProducer(),
	}
	rt.Transport = http.DefaultTransport
	rt.Jar = nil
	rt.Host = host
	rt.BasePath = basePath
	rt.Context = context.Background()
	rt.clientOnce = new(sync.Once)
	if !strings.HasPrefix(rt.BasePath, "/") {
		rt.BasePath = "/" + rt.BasePath
	}
	rt.Debug = len(os.Getenv("DEBUG")) > 0
	if len(schemes) > 0 {
		rt.schemes = schemes
	}
	rt.do = ctxhttp.Do
	return &rt
}

// NewWithClient allows you to create a new transport with a configured http.Client
func NewWithClient(host, basePath string, schemes []string, client *http.Client) *Runtime {
	rt := New(host, basePath, schemes)
	if client != nil {
		rt.clientOnce.Do(func() {
			rt.client = client
		})
	}
	return rt
}

func (r *Runtime) pickScheme(schemes []string) string {
	if v := r.selectScheme(r.schemes); v != "" {
		return v
	}
	if v := r.selectScheme(schemes); v != "" {
		return v
	}
	return "http"
}

func (r *Runtime) selectScheme(schemes []string) string {
	schLen := len(schemes)
	if schLen == 0 {
		return ""
	}

	scheme := schemes[0]
	// prefer https, but skip when not possible
	if scheme != "https" && schLen > 1 {
		for _, sch := range schemes {
			if sch == "https" {
				scheme = sch
				break
			}
		}
	}
	return scheme
}

// Submit a request and when there is a body on success it will turn that into the result
// all other things are turned into an api error for swagger which retains the status code
func (r *Runtime) Submit(operation *runtime.ClientOperation) (interface{}, error) {
	params, readResponse, auth := operation.Params, operation.Reader, operation.AuthInfo

	request, err := newRequest(operation.Method, operation.PathPattern, params)
	if err != nil {
		return nil, err
	}

	var accept []string
	accept = append(accept, operation.ProducesMediaTypes...)
	if err = request.SetHeaderParam(runtime.HeaderAccept, accept...); err != nil {
		return nil, err
	}

	if auth == nil && r.DefaultAuthentication != nil {
		auth = r.DefaultAuthentication
	}
	if auth != nil {
		if err := auth.AuthenticateRequest(request, r.Formats); err != nil {
			return nil, err
		}
	}

	// TODO: pick appropriate media type
	cmt := r.DefaultMediaType
	for _, mediaType := range operation.ConsumesMediaTypes {
		// Pick first non-empty media type
		if mediaType != "" {
			cmt = mediaType
			break
		}
	}

	req, err := request.BuildHTTP(cmt, r.Producers, r.Formats)
	if err != nil {
		return nil, err
	}
	req.URL.Scheme = r.pickScheme(operation.Schemes)
	req.URL.Host = r.Host
	var reinstateSlash bool
	if req.URL.Path != "" && req.URL.Path != "/" && req.URL.Path[len(req.URL.Path)-1] == '/' {
		reinstateSlash = true
	}
	req.URL.Path = path.Join(r.BasePath, req.URL.Path)
	if reinstateSlash {
		req.URL.Path = req.URL.Path + "/"
	}

	r.clientOnce.Do(func() {
		r.client = &http.Client{
			Transport: r.Transport,
			Jar:       r.Jar,
		}
	})

	if r.Debug {
		b, err2 := httputil.DumpRequestOut(req, true)
		if err2 != nil {
			return nil, err2
		}
		fmt.Fprintln(os.Stderr, string(b))
	}

	var hasTimeout bool
	pctx := operation.Context
	if pctx == nil {
		pctx = r.Context
	} else {
		hasTimeout = true
	}
	if pctx == nil {
		pctx = context.Background()
	}
	var ctx context.Context
	var cancel context.CancelFunc
	if hasTimeout {
		ctx, cancel = context.WithCancel(pctx)
	} else {
		ctx, cancel = context.WithTimeout(pctx, request.timeout)
	}
	defer cancel()

	client := operation.Client
	if client == nil {
		client = r.client
	}
	if r.do == nil {
		r.do = ctxhttp.Do
	}
	res, err := r.do(ctx, client, req) // make requests, by default follows 10 redirects before failing
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if r.Debug {
		b, err2 := httputil.DumpResponse(res, true)
		if err2 != nil {
			return nil, err2
		}
		fmt.Fprintln(os.Stderr, string(b))
	}

	ct := res.Header.Get(runtime.HeaderContentType)
	if ct == "" { // this should really really never occur
		ct = r.DefaultMediaType
	}

	mt, _, err := mime.ParseMediaType(ct)
	if err != nil {
		return nil, fmt.Errorf("parse content type: %s", err)
	}

	cons, ok := r.Consumers[mt]
	if !ok {
		// scream about not knowing what to do
		return nil, fmt.Errorf("no consumer: %q", ct)
	}
	return readResponse.ReadResponse(response{res}, cons)
}
<commit_msg>FIX: Allow InsecureSkipVerify to be specified without Certificate<commit_after>// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"mime"
	"net/http"
	"net/http/httputil"
	"os"
	"path"
	"strings"
	"sync"
	"time"

	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"

	"github.com/go-openapi/runtime"
	"github.com/go-openapi/strfmt"
)

// TLSClientOptions to configure client authentication with mutual TLS
type TLSClientOptions struct {
	Certificate        string
	Key                string
	CA                 string
	ServerName         string
	InsecureSkipVerify bool
	_                  struct{}
}

// TLSClientAuth creates a tls.Config for mutual auth
func TLSClientAuth(opts TLSClientOptions) (*tls.Config, error) {
	// create client tls config
	cfg := &tls.Config{}

	// load client cert if specified
	if opts.Certificate != "" {
		cert, err := tls.LoadX509KeyPair(opts.Certificate, opts.Key)
		if err != nil {
			return nil, fmt.Errorf("tls client cert: %v", err)
		}
		cfg.Certificates = []tls.Certificate{cert}
	}

	cfg.InsecureSkipVerify = opts.InsecureSkipVerify

	// When no CA certificate is provided, default to the system cert pool
	// that way when a request is made to a server known by the system trust store,
	// the name is still verified
	if opts.CA != "" {
		// load ca cert
		caCert, err := ioutil.ReadFile(opts.CA)
		if err != nil {
			return nil, fmt.Errorf("tls client ca: %v", err)
		}
		caCertPool := x509.NewCertPool()
		caCertPool.AppendCertsFromPEM(caCert)
		cfg.RootCAs = caCertPool
	}

	// apply servername overrride
	if opts.ServerName != "" {
		cfg.InsecureSkipVerify = false
		cfg.ServerName = opts.ServerName
	}

	cfg.BuildNameToCertificate()

	return cfg, nil
}

// TLSTransport creates a http client transport suitable for mutual tls auth
func TLSTransport(opts TLSClientOptions) (http.RoundTripper, error) {
	cfg, err := TLSClientAuth(opts)
	if err != nil {
		return nil, err
	}

	return &http.Transport{TLSClientConfig: cfg}, nil
}

// TLSClient creates a http.Client for mutual auth
func TLSClient(opts TLSClientOptions) (*http.Client, error) {
	transport, err := TLSTransport(opts)
	if err != nil {
		return nil, err
	}
	return &http.Client{Transport: transport}, nil
}

// DefaultTimeout the default request timeout
var DefaultTimeout = 30 * time.Second

// Runtime represents an API client that uses the transport
// to make http requests based on a swagger specification.
type Runtime struct {
	DefaultMediaType      string
	DefaultAuthentication runtime.ClientAuthInfoWriter
	Consumers             map[string]runtime.Consumer
	Producers             map[string]runtime.Producer

	Transport http.RoundTripper
	Jar       http.CookieJar
	//Spec      *spec.Document
	Host     string
	BasePath string
	Formats  strfmt.Registry
	Debug    bool
	Context  context.Context

	clientOnce *sync.Once
	client     *http.Client
	schemes    []string
	do         func(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error)
}

// New creates a new default runtime for a swagger api runtime.Client
func New(host, basePath string, schemes []string) *Runtime {
	var rt Runtime
	rt.DefaultMediaType = runtime.JSONMime

	// TODO: actually infer this stuff from the spec
	rt.Consumers = map[string]runtime.Consumer{
		runtime.JSONMime:    runtime.JSONConsumer(),
		runtime.XMLMime:     runtime.XMLConsumer(),
		runtime.TextMime:    runtime.TextConsumer(),
		runtime.DefaultMime: runtime.ByteStreamConsumer(),
	}
	rt.Producers = map[string]runtime.Producer{
		runtime.JSONMime:    runtime.JSONProducer(),
		runtime.XMLMime:     runtime.XMLProducer(),
		runtime.TextMime:    runtime.TextProducer(),
		runtime.DefaultMime: runtime.ByteStreamProducer(),
	}
	rt.Transport = http.DefaultTransport
	rt.Jar = nil
	rt.Host = host
	rt.BasePath = basePath
	rt.Context = context.Background()
	rt.clientOnce = new(sync.Once)
	if !strings.HasPrefix(rt.BasePath, "/") {
		rt.BasePath = "/" + rt.BasePath
	}
	rt.Debug = len(os.Getenv("DEBUG")) > 0
	if len(schemes) > 0 {
		rt.schemes = schemes
	}
	rt.do = ctxhttp.Do
	return &rt
}

// NewWithClient allows you to create a new transport with a configured http.Client
func NewWithClient(host, basePath string, schemes []string, client *http.Client) *Runtime {
	rt := New(host, basePath, schemes)
	if client != nil {
		rt.clientOnce.Do(func() {
			rt.client = client
		})
	}
	return rt
}

func (r *Runtime) pickScheme(schemes []string) string {
	if v := r.selectScheme(r.schemes); v != "" {
		return v
	}
	if v := r.selectScheme(schemes); v != "" {
		return v
	}
	return "http"
}

func (r *Runtime) selectScheme(schemes []string) string {
	schLen := len(schemes)
	if schLen == 0 {
		return ""
	}

	scheme := schemes[0]
	// prefer https, but skip when not possible
	if scheme != "https" && schLen > 1 {
		for _, sch := range schemes {
			if sch == "https" {
				scheme = sch
				break
			}
		}
	}
	return scheme
}

// Submit a request and when there is a body on success it will turn that into the result
// all other things are turned into an api error for swagger which retains the status code
func (r *Runtime) Submit(operation *runtime.ClientOperation) (interface{}, error) {
	params, readResponse, auth := operation.Params, operation.Reader, operation.AuthInfo

	request, err := newRequest(operation.Method, operation.PathPattern, params)
	if err != nil {
		return nil, err
	}

	var accept []string
	accept = append(accept, operation.ProducesMediaTypes...)
	if err = request.SetHeaderParam(runtime.HeaderAccept, accept...); err != nil {
		return nil, err
	}

	if auth == nil && r.DefaultAuthentication != nil {
		auth = r.DefaultAuthentication
	}
	if auth != nil {
		if err := auth.AuthenticateRequest(request, r.Formats); err != nil {
			return nil, err
		}
	}

	// TODO: pick appropriate media type
	cmt := r.DefaultMediaType
	for _, mediaType := range operation.ConsumesMediaTypes {
		// Pick first non-empty media type
		if mediaType != "" {
			cmt = mediaType
			break
		}
	}

	req, err := request.BuildHTTP(cmt, r.Producers, r.Formats)
	if err != nil {
		return nil, err
	}
	req.URL.Scheme = r.pickScheme(operation.Schemes)
	req.URL.Host = r.Host
	var reinstateSlash bool
	if req.URL.Path != "" && req.URL.Path != "/" && req.URL.Path[len(req.URL.Path)-1] == '/' {
		reinstateSlash = true
	}
	req.URL.Path = path.Join(r.BasePath, req.URL.Path)
	if reinstateSlash {
		req.URL.Path = req.URL.Path + "/"
	}

	r.clientOnce.Do(func() {
		r.client = &http.Client{
			Transport: r.Transport,
			Jar:       r.Jar,
		}
	})

	if r.Debug {
		b, err2 := httputil.DumpRequestOut(req, true)
		if err2 != nil {
			return nil, err2
		}
		fmt.Fprintln(os.Stderr, string(b))
	}

	var hasTimeout bool
	pctx := operation.Context
	if pctx == nil {
		pctx = r.Context
	} else {
		hasTimeout = true
	}
	if pctx == nil {
		pctx = context.Background()
	}
	var ctx context.Context
	var cancel context.CancelFunc
	if hasTimeout {
		ctx, cancel = context.WithCancel(pctx)
	} else {
		ctx, cancel = context.WithTimeout(pctx, request.timeout)
	}
	defer cancel()

	client := operation.Client
	if client == nil {
		client = r.client
	}
	if r.do == nil {
		r.do = ctxhttp.Do
	}
	res, err := r.do(ctx, client, req) // make requests, by default follows 10 redirects before failing
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if r.Debug {
		b, err2 := httputil.DumpResponse(res, true)
		if err2 != nil {
			return nil, err2
		}
		fmt.Fprintln(os.Stderr, string(b))
	}

	ct := res.Header.Get(runtime.HeaderContentType)
	if ct == "" { // this should really really never occur
		ct = r.DefaultMediaType
	}

	mt, _, err := mime.ParseMediaType(ct)
	if err != nil {
		return nil, fmt.Errorf("parse content type: %s", err)
	}

	cons, ok := r.Consumers[mt]
	if !ok {
		// scream about not knowing what to do
		return nil, fmt.Errorf("no consumer: %q", ct)
	}
	return readResponse.ReadResponse(response{res}, cons)
}
<|endoftext|>


<commit_before>package client

import (
	"fmt"
	"net/http"
	"path/filepath"

	"github.com/go-swagger/go-swagger/client"
	"github.com/go-swagger/go-swagger/httpkit"
	"github.com/go-swagger/go-swagger/spec"
	"github.com/go-swagger/go-swagger/strfmt"
)

// Runtime represents an API client that uses the transport
// to make http requests based on a swagger specification.
type Runtime struct {
	DefaultMediaType string
	Consumers        map[string]httpkit.Consumer
	Producers        map[string]httpkit.Producer
	Transport        http.RoundTripper
	Spec             *spec.Document
	Host             string
	BasePath         string
	Formats          strfmt.Registry

	client          *http.Client
	methodsAndPaths map[string]methodAndPath
}

// New creates a new default runtime for a swagger api client.
func New(swaggerSpec *spec.Document) *Runtime {
	var rt Runtime
	rt.DefaultMediaType = httpkit.JSONMime

	// TODO: actually infer this stuff from the spec
	rt.Consumers = map[string]httpkit.Consumer{
		httpkit.JSONMime: httpkit.JSONConsumer(),
	}
	rt.Producers = map[string]httpkit.Producer{
		httpkit.JSONMime: httpkit.JSONProducer(),
	}
	rt.Spec = swaggerSpec
	rt.Transport = http.DefaultTransport
	rt.client = http.DefaultClient
	rt.client.Transport = rt.Transport
	rt.Host = swaggerSpec.Host()
	rt.BasePath = swaggerSpec.BasePath()
	schemes := swaggerSpec.Spec().Schemes
	if len(schemes) == 0 {
		schemes = append(schemes, "http")
	}
	rt.methodsAndPaths = make(map[string]methodAndPath)
	for mth, pathItem := range rt.Spec.Operations() {
		for pth, op := range pathItem {
			if len(op.Schemes) > 0 {
				rt.methodsAndPaths[op.ID] = methodAndPath{mth, pth, op.Schemes}
			} else {
				rt.methodsAndPaths[op.ID] = methodAndPath{mth, pth, schemes}
			}
		}
	}
	return &rt
}

// Submit a request and when there is a body on success it will turn that into the result
// all other things are turned into an api error for swagger which retains the status code
func (r *Runtime) Submit(operationID string, params client.RequestWriter, readResponse client.ResponseReader) (interface{}, error) {
	mthPth, ok := r.methodsAndPaths[operationID]
	if !ok {
		return nil, fmt.Errorf("unknown operation: %q", operationID)
	}
	request, err := newRequest(mthPth.Method, mthPth.PathPattern, params)
	if err != nil {
		return nil, err
	}

	// TODO: infer most appropriate content type
	request.SetHeaderParam(httpkit.HeaderContentType, r.DefaultMediaType)
	var accept []string
	for k := range r.Consumers {
		accept = append(accept, k)
	}
	request.SetHeaderParam(httpkit.HeaderAccept, accept...)

	req, err := request.BuildHTTP(r.Producers[r.DefaultMediaType], r.Formats)
	req.URL.Scheme = "http"
	if len(mthPth.Schemes) > 0 {
		scheme := mthPth.Schemes[0]
		for _, sch := range mthPth.Schemes {
			if sch == "https" {
				scheme = sch
				break
			}
		}
		req.URL.Scheme = scheme
	}
	req.URL.Host = r.Host
	req.URL.Path = filepath.Join(r.BasePath, req.URL.Path)
	if err != nil {
		return nil, err
	}

	res, err := r.client.Do(req) // make requests, by default follows 10 redirects before failing
	if err != nil {
		return nil, err
	}
	ct := res.Header.Get(httpkit.HeaderContentType)
	if ct == "" { // this should really really never occur
		ct = r.DefaultMediaType
	}

	// TODO: normalize this (ct) and only match on media type,
	// skip the params like charset unless a tie breaker is needed
	cons, ok := r.Consumers[ct]
	if !ok {
		// scream about not knowing what to do
		return nil, fmt.Errorf("no consumer: %q", ct)
	}
	return readResponse.ReadResponse(response{res}, cons)
}
<commit_msg>adds authentication to the client (issues #82, #76)<commit_after>package client

import (
	"fmt"
	"net/http"
	"path/filepath"

	"github.com/go-swagger/go-swagger/client"
	"github.com/go-swagger/go-swagger/httpkit"
	"github.com/go-swagger/go-swagger/spec"
	"github.com/go-swagger/go-swagger/strfmt"
)

// Runtime represents an API client that uses the transport
// to make http requests based on a swagger specification.
type Runtime struct {
	DefaultMediaType string
	Consumers        map[string]httpkit.Consumer
	Producers        map[string]httpkit.Producer

	Transport http.RoundTripper
	Spec      *spec.Document
	Host      string
	BasePath  string
	Formats   strfmt.Registry

	client          *http.Client
	methodsAndPaths map[string]methodAndPath
}

// New creates a new default runtime for a swagger api client.
func New(swaggerSpec *spec.Document) *Runtime {
	var rt Runtime
	rt.DefaultMediaType = httpkit.JSONMime

	// TODO: actually infer this stuff from the spec
	rt.Consumers = map[string]httpkit.Consumer{
		httpkit.JSONMime: httpkit.JSONConsumer(),
	}
	rt.Producers = map[string]httpkit.Producer{
		httpkit.JSONMime: httpkit.JSONProducer(),
	}
	rt.Spec = swaggerSpec
	rt.Transport = http.DefaultTransport
	rt.client = http.DefaultClient
	rt.client.Transport = rt.Transport
	rt.Host = swaggerSpec.Host()
	rt.BasePath = swaggerSpec.BasePath()
	schemes := swaggerSpec.Spec().Schemes
	if len(schemes) == 0 {
		schemes = append(schemes, "http")
	}
	rt.methodsAndPaths = make(map[string]methodAndPath)
	for mth, pathItem := range rt.Spec.Operations() {
		for pth, op := range pathItem {
			if len(op.Schemes) > 0 {
				rt.methodsAndPaths[op.ID] = methodAndPath{mth, pth, op.Schemes}
			} else {
				rt.methodsAndPaths[op.ID] = methodAndPath{mth, pth, schemes}
			}
		}
	}
	return &rt
}

// Submit a request and when there is a body on success it will turn that into the result
// all other things are turned into an api error for swagger which retains the status code
func (r *Runtime) Submit(context *client.Operation) (interface{}, error) {
	operationID, params, readResponse, auth := context.ID, context.Params, context.Reader, context.AuthInfo
	mthPth, ok := r.methodsAndPaths[operationID]
	if !ok {
		return nil, fmt.Errorf("unknown operation: %q", operationID)
	}
	request, err := newRequest(mthPth.Method, mthPth.PathPattern, params)
	if err != nil {
		return nil, err
	}

	// TODO: infer most appropriate content type
	request.SetHeaderParam(httpkit.HeaderContentType, r.DefaultMediaType)
	var accept []string
	for k := range r.Consumers {
		accept = append(accept, k)
	}
	request.SetHeaderParam(httpkit.HeaderAccept, accept...)

	if auth != nil {
		if err := auth.AuthenticateRequest(request, r.Formats); err != nil {
			return nil, err
		}
	}

	req, err := request.BuildHTTP(r.Producers[r.DefaultMediaType], r.Formats)
	req.URL.Scheme = "http"
	if len(mthPth.Schemes) > 0 {
		scheme := mthPth.Schemes[0]
		for _, sch := range mthPth.Schemes {
			if sch == "https" {
				scheme = sch
				break
			}
		}
		req.URL.Scheme = scheme
	}
	req.URL.Host = r.Host
	req.URL.Path = filepath.Join(r.BasePath, req.URL.Path)
	if err != nil {
		return nil, err
	}

	res, err := r.client.Do(req) // make requests, by default follows 10 redirects before failing
	if err != nil {
		return nil, err
	}
	ct := res.Header.Get(httpkit.HeaderContentType)
	if ct == "" { // this should really really never occur
		ct = r.DefaultMediaType
	}

	// TODO: normalize this (ct) and only match on media type,
	// skip the params like charset unless a tie breaker is needed
	cons, ok := r.Consumers[ct]
	if !ok {
		// scream about not knowing what to do
		return nil, fmt.Errorf("no consumer: %q", ct)
	}
	return readResponse.ReadResponse(response{res}, cons)
}
<|endoftext|>


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

package client

import (
	"context"
	"crypto/tls"
	"fmt"
	"io"
	"math/rand"
	"net"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/fatedier/frp/assets"
	"github.com/fatedier/frp/pkg/auth"
	"github.com/fatedier/frp/pkg/config"
	"github.com/fatedier/frp/pkg/msg"
	"github.com/fatedier/frp/pkg/transport"
	"github.com/fatedier/frp/pkg/util/log"
	frpNet "github.com/fatedier/frp/pkg/util/net"
	"github.com/fatedier/frp/pkg/util/util"
	"github.com/fatedier/frp/pkg/util/version"
	"github.com/fatedier/frp/pkg/util/xlog"
	"github.com/fatedier/golib/crypto"
	libdial "github.com/fatedier/golib/net/dial"

	fmux "github.com/hashicorp/yamux"
)

func init() {
	crypto.DefaultSalt = "frp"
	rand.Seed(time.Now().UnixNano())
}

// Service is a client service.
type Service struct {
	// uniq id got from frps, attach it in loginMsg
	runID string

	// manager control connection with server
	ctl   *Control
	ctlMu sync.RWMutex

	// Sets authentication based on selected method
	authSetter auth.Setter

	cfg         config.ClientCommonConf
	pxyCfgs     map[string]config.ProxyConf
	visitorCfgs map[string]config.VisitorConf
	cfgMu       sync.RWMutex

	// The configuration file used to initialize this client, or an empty
	// string if no configuration file was used.
	cfgFile string

	// This is configured by the login response from frps
	serverUDPPort int

	exit uint32 // 0 means not exit

	// service context
	ctx context.Context
	// call cancel to stop service
	cancel context.CancelFunc
}

func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (svr *Service, err error) {

	ctx, cancel := context.WithCancel(context.Background())
	svr = &Service{
		authSetter:  auth.NewAuthSetter(cfg.ClientConfig),
		cfg:         cfg,
		cfgFile:     cfgFile,
		pxyCfgs:     pxyCfgs,
		visitorCfgs: visitorCfgs,
		exit:        0,
		ctx:         xlog.NewContext(ctx, xlog.New()),
		cancel:      cancel,
	}
	return
}

func (svr *Service) GetController() *Control {
	svr.ctlMu.RLock()
	defer svr.ctlMu.RUnlock()
	return svr.ctl
}

func (svr *Service) Run() error {
	xl := xlog.FromContextSafe(svr.ctx)

	// set custom DNSServer
	if svr.cfg.DNSServer != "" {
		dnsAddr := svr.cfg.DNSServer
		if !strings.Contains(dnsAddr, ":") {
			dnsAddr += ":53"
		}
		// Change default dns server for frpc
		net.DefaultResolver = &net.Resolver{
			PreferGo: true,
			Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
				return net.Dial("udp", dnsAddr)
			},
		}
	}

	// login to frps
	for {
		conn, session, err := svr.login()
		if err != nil {
			xl.Warn("login to server failed: %v", err)

			// if login_fail_exit is true, just exit this program
			// otherwise sleep a while and try again to connect to server
			if svr.cfg.LoginFailExit {
				return err
			}
			util.RandomSleep(10*time.Second, 0.9, 1.1)
		} else {
			// login success
			ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
			ctl.Run()
			svr.ctlMu.Lock()
			svr.ctl = ctl
			svr.ctlMu.Unlock()
			break
		}
	}

	go svr.keepControllerWorking()

	if svr.cfg.AdminPort != 0 {
		// Init admin server assets
		assets.Load(svr.cfg.AssetsDir)

		address := net.JoinHostPort(svr.cfg.AdminAddr, strconv.Itoa(svr.cfg.AdminPort))
		err := svr.RunAdminServer(address)
		if err != nil {
			log.Warn("run admin server error: %v", err)
		}
		log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
	}
	<-svr.ctx.Done()
	return nil
}

func (svr *Service) keepControllerWorking() {
	xl := xlog.FromContextSafe(svr.ctx)
	maxDelayTime := 20 * time.Second
	delayTime := time.Second

	// if frpc reconnect frps, we need to limit retry times in 1min
	// current retry logic is sleep 0s, 0s, 0s, 1s, 2s, 4s, 8s, ...
	// when exceed 1min, we will reset delay and counts
	cutoffTime := time.Now().Add(time.Minute)
	reconnectDelay := time.Second
	reconnectCounts := 1

	for {
		<-svr.ctl.ClosedDoneCh()
		if atomic.LoadUint32(&svr.exit) != 0 {
			return
		}

		// the first three retry with no delay
		if reconnectCounts > 3 {
			util.RandomSleep(reconnectDelay, 0.9, 1.1)
			xl.Info("wait %v to reconnect", reconnectDelay)
			reconnectDelay *= 2
		} else {
			util.RandomSleep(time.Second, 0, 0.5)
		}
		reconnectCounts++

		now := time.Now()
		if now.After(cutoffTime) {
			// reset
			cutoffTime = now.Add(time.Minute)
			reconnectDelay = time.Second
			reconnectCounts = 1
		}

		for {
			xl.Info("try to reconnect to server...")
			conn, session, err := svr.login()
			if err != nil {
				xl.Warn("reconnect to server error: %v, wait %v for another retry", err, delayTime)
				util.RandomSleep(delayTime, 0.9, 1.1)

				delayTime = delayTime * 2
				if delayTime > maxDelayTime {
					delayTime = maxDelayTime
				}
				continue
			}
			// reconnect success, init delayTime
			delayTime = time.Second

			ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
			ctl.Run()
			svr.ctlMu.Lock()
			if svr.ctl != nil {
				svr.ctl.Close()
			}
			svr.ctl = ctl
			svr.ctlMu.Unlock()
			break
		}
	}
}

// login creates a connection to frps and registers it self as a client
// conn: control connection
// session: if it's not nil, using tcp mux
func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
	xl := xlog.FromContextSafe(svr.ctx)
	var tlsConfig *tls.Config
	if svr.cfg.TLSEnable {
		sn := svr.cfg.TLSServerName
		if sn == "" {
			sn = svr.cfg.ServerAddr
		}

		tlsConfig, err = transport.NewClientTLSConfig(
			svr.cfg.TLSCertFile,
			svr.cfg.TLSKeyFile,
			svr.cfg.TLSTrustedCaFile,
			sn)
		if err != nil {
			xl.Warn("fail to build tls configuration when service login, err: %v", err)
			return
		}
	}

	proxyType, addr, auth, err := libdial.ParseProxyURL(svr.cfg.HTTPProxy)
	if err != nil {
		xl.Error("fail to parse proxy url")
		return
	}
	dialOptions := []libdial.DialOption{}
	protocol := svr.cfg.Protocol
	if protocol == "websocket" {
		protocol = "tcp"
		dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: frpNet.DialHookWebsocket()}))
	}
	if svr.cfg.ConnectServerLocalIP != "" {
		dialOptions = append(dialOptions, libdial.WithLocalAddr(svr.cfg.ConnectServerLocalIP))
	}
	dialOptions = append(dialOptions,
		libdial.WithProtocol(protocol),
		libdial.WithTimeout(time.Duration(svr.cfg.DialServerTimeout)*time.Second),
		libdial.WithKeepAlive(time.Duration(svr.cfg.DialServerKeepAlive)*time.Second),
		libdial.WithProxy(proxyType, addr),
		libdial.WithProxyAuth(auth),
		libdial.WithTLSConfig(tlsConfig),
		libdial.WithAfterHook(libdial.AfterHook{
			Hook: frpNet.DialHookCustomTLSHeadByte(tlsConfig != nil, svr.cfg.DisableCustomTLSFirstByte),
		}),
	)
	conn, err = libdial.Dial(
		net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort)),
		dialOptions...,
	)
	if err != nil {
		return
	}

	defer func() {
		if err != nil {
			conn.Close()
			if session != nil {
				session.Close()
			}
		}
	}()

	if svr.cfg.TCPMux {
		fmuxCfg := fmux.DefaultConfig()
		fmuxCfg.KeepAliveInterval = time.Duration(svr.cfg.TCPMuxKeepaliveInterval) * time.Second
		fmuxCfg.LogOutput = io.Discard
		session, err = fmux.Client(conn, fmuxCfg)
		if err != nil {
			return
		}
		stream, errRet := session.OpenStream()
		if errRet != nil {
			session.Close()
			err = errRet
			return
		}
		conn = stream
	}

	loginMsg := &msg.Login{
		Arch:      runtime.GOARCH,
		Os:        runtime.GOOS,
		PoolCount: svr.cfg.PoolCount,
		User:      svr.cfg.User,
		Version:   version.Full(),
		Timestamp: time.Now().Unix(),
		RunID:     svr.runID,
		Metas:     svr.cfg.Metas,
	}

	// Add auth
	if err = svr.authSetter.SetLogin(loginMsg); err != nil {
		return
	}

	if err = msg.WriteMsg(conn, loginMsg); err != nil {
		return
	}

	var loginRespMsg msg.LoginResp
	conn.SetReadDeadline(time.Now().Add(10 * time.Second))
	if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
		return
	}
	conn.SetReadDeadline(time.Time{})

	if loginRespMsg.Error != "" {
		err = fmt.Errorf("%s", loginRespMsg.Error)
		xl.Error("%s", loginRespMsg.Error)
		return
	}

	svr.runID = loginRespMsg.RunID
	xl.ResetPrefixes()
	xl.AppendPrefix(svr.runID)

	svr.serverUDPPort = loginRespMsg.ServerUDPPort
	xl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunID, loginRespMsg.ServerUDPPort)
	return
}

func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
	svr.cfgMu.Lock()
	svr.pxyCfgs = pxyCfgs
	svr.visitorCfgs = visitorCfgs
	svr.cfgMu.Unlock()

	svr.ctlMu.RLock()
	ctl := svr.ctl
	svr.ctlMu.RUnlock()

	if ctl != nil {
		return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
	}
	return nil
}

func (svr *Service) Close() {
	svr.GracefulClose(time.Duration(0))
}

func (svr *Service) GracefulClose(d time.Duration) {
	atomic.StoreUint32(&svr.exit, 1)

	svr.ctlMu.RLock()
	if svr.ctl != nil {
		svr.ctl.GracefulClose(d)
	}
	svr.ctlMu.RUnlock()

	svr.cancel()
}
<commit_msg>[client] Fixed a bug where service loops reconnection after disconnection.service is shut down and can not exit goroutine (#3012)<commit_after>// Copyright 2017 fatedier, fatedier@gmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
	"context"
	"crypto/tls"
	"fmt"
	"io"
	"math/rand"
	"net"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/fatedier/frp/assets"
	"github.com/fatedier/frp/pkg/auth"
	"github.com/fatedier/frp/pkg/config"
	"github.com/fatedier/frp/pkg/msg"
	"github.com/fatedier/frp/pkg/transport"
	"github.com/fatedier/frp/pkg/util/log"
	frpNet "github.com/fatedier/frp/pkg/util/net"
	"github.com/fatedier/frp/pkg/util/util"
	"github.com/fatedier/frp/pkg/util/version"
	"github.com/fatedier/frp/pkg/util/xlog"
	"github.com/fatedier/golib/crypto"
	libdial "github.com/fatedier/golib/net/dial"

	fmux "github.com/hashicorp/yamux"
)

func init() {
	crypto.DefaultSalt = "frp"
	rand.Seed(time.Now().UnixNano())
}

// Service is a client service.
type Service struct {
	// uniq id got from frps, attach it in loginMsg
	runID string

	// manager control connection with server
	ctl   *Control
	ctlMu sync.RWMutex

	// Sets authentication based on selected method
	authSetter auth.Setter

	cfg         config.ClientCommonConf
	pxyCfgs     map[string]config.ProxyConf
	visitorCfgs map[string]config.VisitorConf
	cfgMu       sync.RWMutex

	// The configuration file used to initialize this client, or an empty
	// string if no configuration file was used.
	cfgFile string

	// This is configured by the login response from frps
	serverUDPPort int

	exit uint32 // 0 means not exit

	// service context
	ctx context.Context
	// call cancel to stop service
	cancel context.CancelFunc
}

func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (svr *Service, err error) {

	ctx, cancel := context.WithCancel(context.Background())
	svr = &Service{
		authSetter:  auth.NewAuthSetter(cfg.ClientConfig),
		cfg:         cfg,
		cfgFile:     cfgFile,
		pxyCfgs:     pxyCfgs,
		visitorCfgs: visitorCfgs,
		exit:        0,
		ctx:         xlog.NewContext(ctx, xlog.New()),
		cancel:      cancel,
	}
	return
}

func (svr *Service) GetController() *Control {
	svr.ctlMu.RLock()
	defer svr.ctlMu.RUnlock()
	return svr.ctl
}

func (svr *Service) Run() error {
	xl := xlog.FromContextSafe(svr.ctx)

	// set custom DNSServer
	if svr.cfg.DNSServer != "" {
		dnsAddr := svr.cfg.DNSServer
		if !strings.Contains(dnsAddr, ":") {
			dnsAddr += ":53"
		}
		// Change default dns server for frpc
		net.DefaultResolver = &net.Resolver{
			PreferGo: true,
			Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
				return net.Dial("udp", dnsAddr)
			},
		}
	}

	// login to frps
	for {
		conn, session, err := svr.login()
		if err != nil {
			xl.Warn("login to server failed: %v", err)

			// if login_fail_exit is true, just exit this program
			// otherwise sleep a while and try again to connect to server
			if svr.cfg.LoginFailExit {
				return err
			}
			util.RandomSleep(10*time.Second, 0.9, 1.1)
		} else {
			// login success
			ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
			ctl.Run()
			svr.ctlMu.Lock()
			svr.ctl = ctl
			svr.ctlMu.Unlock()
			break
		}
	}

	go svr.keepControllerWorking()

	if svr.cfg.AdminPort != 0 {
		// Init admin server assets
		assets.Load(svr.cfg.AssetsDir)

		address := net.JoinHostPort(svr.cfg.AdminAddr, strconv.Itoa(svr.cfg.AdminPort))
		err := svr.RunAdminServer(address)
		if err != nil {
			log.Warn("run admin server error: %v", err)
		}
		log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
	}
	<-svr.ctx.Done()
	return nil
}

func (svr *Service) keepControllerWorking() {
	xl := xlog.FromContextSafe(svr.ctx)
	maxDelayTime := 20 * time.Second
	delayTime := time.Second

	// if frpc reconnect frps, we need to limit retry times in 1min
	// current retry logic is sleep 0s, 0s, 0s, 1s, 2s, 4s, 8s, ...
	// when exceed 1min, we will reset delay and counts
	cutoffTime := time.Now().Add(time.Minute)
	reconnectDelay := time.Second
	reconnectCounts := 1

	for {
		<-svr.ctl.ClosedDoneCh()
		if atomic.LoadUint32(&svr.exit) != 0 {
			return
		}

		// the first three retry with no delay
		if reconnectCounts > 3 {
			util.RandomSleep(reconnectDelay, 0.9, 1.1)
			xl.Info("wait %v to reconnect", reconnectDelay)
			reconnectDelay *= 2
		} else {
			util.RandomSleep(time.Second, 0, 0.5)
		}
		reconnectCounts++

		now := time.Now()
		if now.After(cutoffTime) {
			// reset
			cutoffTime = now.Add(time.Minute)
			reconnectDelay = time.Second
			reconnectCounts = 1
		}

		for {
			if atomic.LoadUint32(&svr.exit) != 0 {
				return
			}

			xl.Info("try to reconnect to server...")
			conn, session, err := svr.login()
			if err != nil {
				xl.Warn("reconnect to server error: %v, wait %v for another retry", err, delayTime)
				util.RandomSleep(delayTime, 0.9, 1.1)

				delayTime = delayTime * 2
				if delayTime > maxDelayTime {
					delayTime = maxDelayTime
				}
				continue
			}
			// reconnect success, init delayTime
			delayTime = time.Second

			ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter)
			ctl.Run()
			svr.ctlMu.Lock()
			if svr.ctl != nil {
				svr.ctl.Close()
			}
			svr.ctl = ctl
			svr.ctlMu.Unlock()
			break
		}
	}
}

// login creates a connection to frps and registers it self as a client
// conn: control connection
// session: if it's not nil, using tcp mux
func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
	xl := xlog.FromContextSafe(svr.ctx)
	var tlsConfig *tls.Config
	if svr.cfg.TLSEnable {
		sn := svr.cfg.TLSServerName
		if sn == "" {
			sn = svr.cfg.ServerAddr
		}

		tlsConfig, err = transport.NewClientTLSConfig(
			svr.cfg.TLSCertFile,
			svr.cfg.TLSKeyFile,
			svr.cfg.TLSTrustedCaFile,
			sn)
		if err != nil {
			xl.Warn("fail to build tls configuration when service login, err: %v", err)
			return
		}
	}

	proxyType, addr, auth, err := libdial.ParseProxyURL(svr.cfg.HTTPProxy)
	if err != nil {
		xl.Error("fail to parse proxy url")
		return
	}
	dialOptions := []libdial.DialOption{}
	protocol := svr.cfg.Protocol
	if protocol == "websocket" {
		protocol = "tcp"
		dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: frpNet.DialHookWebsocket()}))
	}
	if svr.cfg.ConnectServerLocalIP != "" {
		dialOptions = append(dialOptions, libdial.WithLocalAddr(svr.cfg.ConnectServerLocalIP))
	}
	dialOptions = append(dialOptions,
		libdial.WithProtocol(protocol),
		libdial.WithTimeout(time.Duration(svr.cfg.DialServerTimeout)*time.Second),
		libdial.WithKeepAlive(time.Duration(svr.cfg.DialServerKeepAlive)*time.Second),
		libdial.WithProxy(proxyType, addr),
		libdial.WithProxyAuth(auth),
		libdial.WithTLSConfig(tlsConfig),
		libdial.WithAfterHook(libdial.AfterHook{
			Hook: frpNet.DialHookCustomTLSHeadByte(tlsConfig != nil, svr.cfg.DisableCustomTLSFirstByte),
		}),
	)
	conn, err = libdial.Dial(
		net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort)),
		dialOptions...,
	)
	if err != nil {
		return
	}

	defer func() {
		if err != nil {
			conn.Close()
			if session != nil {
				session.Close()
			}
		}
	}()

	if svr.cfg.TCPMux {
		fmuxCfg := fmux.DefaultConfig()
		fmuxCfg.KeepAliveInterval = time.Duration(svr.cfg.TCPMuxKeepaliveInterval) * time.Second
		fmuxCfg.LogOutput = io.Discard
		session, err = fmux.Client(conn, fmuxCfg)
		if err != nil {
			return
		}
		stream, errRet := session.OpenStream()
		if errRet != nil {
			session.Close()
			err = errRet
			return
		}
		conn = stream
	}

	loginMsg := &msg.Login{
		Arch:      runtime.GOARCH,
		Os:        runtime.GOOS,
		PoolCount: svr.cfg.PoolCount,
		User:      svr.cfg.User,
		Version:   version.Full(),
		Timestamp: time.Now().Unix(),
		RunID:     svr.runID,
		Metas:     svr.cfg.Metas,
	}

	// Add auth
	if err = svr.authSetter.SetLogin(loginMsg); err != nil {
		return
	}

	if err = msg.WriteMsg(conn, loginMsg); err != nil {
		return
	}

	var loginRespMsg msg.LoginResp
	conn.SetReadDeadline(time.Now().Add(10 * time.Second))
	if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil {
		return
	}
	conn.SetReadDeadline(time.Time{})

	if loginRespMsg.Error != "" {
		err = fmt.Errorf("%s", loginRespMsg.Error)
		xl.Error("%s", loginRespMsg.Error)
		return
	}

	svr.runID = loginRespMsg.RunID
	xl.ResetPrefixes()
	xl.AppendPrefix(svr.runID)

	svr.serverUDPPort = loginRespMsg.ServerUDPPort
	xl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunID, loginRespMsg.ServerUDPPort)
	return
}

func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error {
	svr.cfgMu.Lock()
	svr.pxyCfgs = pxyCfgs
	svr.visitorCfgs = visitorCfgs
	svr.cfgMu.Unlock()

	svr.ctlMu.RLock()
	ctl := svr.ctl
	svr.ctlMu.RUnlock()

	if ctl != nil {
		return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
	}
	return nil
}

func (svr *Service) Close() {
	svr.GracefulClose(time.Duration(0))
}

func (svr *Service) GracefulClose(d time.Duration) {
	atomic.StoreUint32(&svr.exit, 1)

	svr.ctlMu.RLock()
	if svr.ctl != nil {
		svr.ctl.GracefulClose(d)
	}
	svr.ctlMu.RUnlock()

	svr.cancel()
}
<|endoftext|>


<commit_before>// Package recovery implements couple of middleware interfaces
//
// package main
//
// import (
//     "github.com/vanng822/r2router"
//     "github.com/vanng822/recovery"
//     "net/http"
// )
//
// func main() {
//     seefor := r2router.NewSeeforRouter()
//     rec := recovery.NewRecovery()
//     rec.PrintStack = true
//     seefor.Before(rec.Handler)
// seefor.Get("/user/keys/:id", func(w http.ResponseWriter, r *http.Request, _ r2router.Params) {
// 	   		panic("This shouldn't crash Seefor")
// 	   })
//
//     http.ListenAndServe(":8080", seefor)
// }
package recovery

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"runtime"
)

type Logger interface {
	Printf(format string, v ...interface{})
}

type Recovery struct {
	Logger     Logger
	StackAll   bool
	StackSize  int
	PrintStack bool
}

func (rec *Recovery) recovery(w http.ResponseWriter) {
	if err := recover(); err != nil {
		w.WriteHeader(http.StatusInternalServerError)

		stack := make([]byte, rec.StackSize)
		stack = stack[:runtime.Stack(stack, rec.StackAll)]
		format := "PANIC: %s\n%s"
		rec.Logger.Printf(format, err, stack)

		if rec.PrintStack {
			fmt.Fprintf(w, format, err, stack)
		} else {
			w.Write([]byte(http.StatusText(http.StatusInternalServerError)))
		}
	}
}

func NewRecovery() *Recovery {
	return &Recovery{
		Logger:     log.New(os.Stdout, "[error] ", 0),
		StackAll:   false,
		StackSize:  1024 * 8,
		PrintStack: false,
	}
}

func (rec *Recovery) Handler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		defer rec.recovery(w)
		next.ServeHTTP(w, req)
	})
}

func (rec *Recovery) HandlerFuncWithNext(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	defer rec.recovery(w)
	next(w, r)
}
<commit_msg>Correct docstring this time?!<commit_after>// Package recovery implements couple of middleware interfaces
//
//	package main
//
//	import (
//		"github.com/vanng822/r2router"
//		"github.com/vanng822/recovery"
//		"net/http"
//	)
//
//	func main() {
//		seefor := r2router.NewSeeforRouter()
//		rec := recovery.NewRecovery()
//		rec.PrintStack = true
//		seefor.Before(rec.Handler)
//		seefor.Before(r2router.WrapBeforeHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//			panic("Middleware panic")
//		})))
//
//		http.ListenAndServe(":8080", seefor)
//	}
package recovery

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"runtime"
)

type Logger interface {
	Printf(format string, v ...interface{})
}

type Recovery struct {
	Logger     Logger
	StackAll   bool
	StackSize  int
	PrintStack bool
}

func (rec *Recovery) recovery(w http.ResponseWriter) {
	if err := recover(); err != nil {
		w.WriteHeader(http.StatusInternalServerError)

		stack := make([]byte, rec.StackSize)
		stack = stack[:runtime.Stack(stack, rec.StackAll)]
		format := "PANIC: %s\n%s"
		rec.Logger.Printf(format, err, stack)

		if rec.PrintStack {
			fmt.Fprintf(w, format, err, stack)
		} else {
			w.Write([]byte(http.StatusText(http.StatusInternalServerError)))
		}
	}
}

func NewRecovery() *Recovery {
	return &Recovery{
		Logger:     log.New(os.Stdout, "[error] ", 0),
		StackAll:   false,
		StackSize:  1024 * 8,
		PrintStack: false,
	}
}

func (rec *Recovery) Handler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		defer rec.recovery(w)
		next.ServeHTTP(w, req)
	})
}

func (rec *Recovery) HandlerFuncWithNext(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	defer rec.recovery(w)
	next(w, r)
}
<|endoftext|>


<commit_before>package modelhelper

import (
	"koding/db/models"
	"strconv"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

const AccountsColl = "jAccounts"

func GetAccountById(id string) (*models.Account, error) {
	account := new(models.Account)
	return account, Mongo.One(AccountsColl, id, account)
}

// GetAccountsByIds fetches all the accounts given by their IDs
func GetAccountsByIds(ids []bson.ObjectId) ([]models.Account, error) {
	accounts := make([]models.Account, 0)

	query := func(c *mgo.Collection) error {
		return c.Find(bson.M{"_id": bson.M{"$in": ids}}).All(&accounts)
	}

	return accounts, Mongo.Run(AccountsColl, query)
}

func GetAccount(username string) (*models.Account, error) {
	account := new(models.Account)
	query := func(c *mgo.Collection) error {
		return c.Find(bson.M{"profile.nickname": username}).One(&account)
	}
	return account, Mongo.Run(AccountsColl, query)
}

func GetAccountBySocialApiId(socialApiId int64) (*models.Account, error) {
	account := new(models.Account)
	query := func(c *mgo.Collection) error {
		return c.Find(bson.M{
			"socialApiId": strconv.FormatInt(socialApiId, 10),
		}).One(&account)
	}
	return account, Mongo.Run(AccountsColl, query)
}

// GetAccountBySocialApiIds fetches the accounts by their socialApiIds
func GetAccountBySocialApiIds(socialApiIds ...int64) ([]models.Account, error) {
	accounts := make([]models.Account, 0)
	query := func(c *mgo.Collection) error {
		return c.Find(bson.M{"socialApiId": bson.M{"$in": socialApiIds}}).All(&accounts)
	}
	return accounts, Mongo.Run(AccountsColl, query)
}

func CheckAccountExistence(id string) (bool, error) {
	var exists bool
	query := checkExistence(id, &exists)
	return exists, Mongo.Run(AccountsColl, query)
}

func UpdateAccount(selector, options Selector) error {
	query := func(c *mgo.Collection) error {
		return c.Update(selector, options)
	}
	return Mongo.Run(AccountsColl, query)
}

// RemoveAccount removes given account
func RemoveAccount(id bson.ObjectId) error {
	return RemoveDocument(AccountsColl, id)
}

func RemoveAccountByUsername(username string) error {
	selector := bson.M{"profile": bson.M{"nickname": username}}

	query := func(c *mgo.Collection) error {
		return c.Remove(selector)
	}

	return Mongo.Run(AccountsColl, query)
}

func RemoveAllAccountByUsername(username string) error {
	selector := bson.M{"profile": bson.M{"nickname": username}}

	query := func(c *mgo.Collection) error {
		_, err := c.RemoveAll(selector)
		return err
	}

	return Mongo.Run(AccountsColl, query)
}

func CreateAccount(a *models.Account) error {
	query := insertQuery(a)
	return Mongo.Run(AccountsColl, query)
}
<commit_msg>socialapi/presence: change int64 parameter as string<commit_after>package modelhelper

import (
	"koding/db/models"
	"strconv"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

const AccountsColl = "jAccounts"

func GetAccountById(id string) (*models.Account, error) {
	account := new(models.Account)
	return account, Mongo.One(AccountsColl, id, account)
}

// GetAccountsByIds fetches all the accounts given by their IDs
func GetAccountsByIds(ids []bson.ObjectId) ([]models.Account, error) {
	accounts := make([]models.Account, 0)

	query := func(c *mgo.Collection) error {
		return c.Find(bson.M{"_id": bson.M{"$in": ids}}).All(&accounts)
	}

	return accounts, Mongo.Run(AccountsColl, query)
}

func GetAccount(username string) (*models.Account, error) {
	account := new(models.Account)
	query := func(c *mgo.Collection) error {
		return c.Find(bson.M{"profile.nickname": username}).One(&account)
	}
	return account, Mongo.Run(AccountsColl, query)
}

func GetAccountBySocialApiId(socialApiId int64) (*models.Account, error) {
	account := new(models.Account)
	query := func(c *mgo.Collection) error {
		return c.Find(bson.M{
			"socialApiId": strconv.FormatInt(socialApiId, 10),
		}).One(&account)
	}
	return account, Mongo.Run(AccountsColl, query)
}

// GetAccountBySocialApiIds fetches the accounts by their socialApiIds
func GetAccountBySocialApiIds(socialApiIds ...string) ([]models.Account, error) {
	accounts := make([]models.Account, 0)
	query := func(c *mgo.Collection) error {
		return c.Find(bson.M{"socialApiId": bson.M{"$in": socialApiIds}}).All(&accounts)
	}
	return accounts, Mongo.Run(AccountsColl, query)
}

func CheckAccountExistence(id string) (bool, error) {
	var exists bool
	query := checkExistence(id, &exists)
	return exists, Mongo.Run(AccountsColl, query)
}

func UpdateAccount(selector, options Selector) error {
	query := func(c *mgo.Collection) error {
		return c.Update(selector, options)
	}
	return Mongo.Run(AccountsColl, query)
}

// RemoveAccount removes given account
func RemoveAccount(id bson.ObjectId) error {
	return RemoveDocument(AccountsColl, id)
}

func RemoveAccountByUsername(username string) error {
	selector := bson.M{"profile": bson.M{"nickname": username}}

	query := func(c *mgo.Collection) error {
		return c.Remove(selector)
	}

	return Mongo.Run(AccountsColl, query)
}

func RemoveAllAccountByUsername(username string) error {
	selector := bson.M{"profile": bson.M{"nickname": username}}

	query := func(c *mgo.Collection) error {
		_, err := c.RemoveAll(selector)
		return err
	}

	return Mongo.Run(AccountsColl, query)
}

func CreateAccount(a *models.Account) error {
	query := insertQuery(a)
	return Mongo.Run(AccountsColl, query)
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"koding/kites/common"
	"koding/kites/kloud/pkg/dnsclient"
	"os"

	"github.com/koding/kite"
	"github.com/koding/logging"
	"github.com/koding/multiconfig"
	"github.com/koding/tunnel"
	"github.com/mitchellh/goamz/aws"
)

type registerResult struct {
	VirtualHost string
	Identifier  string
}

type tunnelServer struct {
	Port            int
	Debug           bool
	BaseVirtualHost string `required:"true"`

	// ServerAddr is public Address of the running server. Like an assigned Elastic IP
	ServerAddr string `required:"true"`

	HostedZone string
	AccessKey  string
	SecretKey  string

	// internal
	Log    logging.Logger
	Server *tunnel.Server
	Dns    *dnsclient.Route53
}

func main() {
	t := new(tunnelServer)
	m := multiconfig.New()
	m.MustLoad(t)
	m.MustValidate(t)

	auth := aws.Auth{
		AccessKey: t.AccessKey,
		SecretKey: t.SecretKey,
	}

	var err error
	t.Server, err = tunnel.NewServer(&tunnel.ServerConfig{
		Debug: t.Debug,
	})
	if err != nil {
		fmt.Fprintf(os.Stderr, err.Error())
		os.Exit(1)
	}

	t.Dns = dnsclient.NewRoute53Client(t.HostedZone, auth)
	t.Log = common.NewLogger("tunnelkite", t.Debug)

	k := kite.New("tunnelserver", "0.0.1")
	k.Config.DisableAuthentication = true
	k.Config.Port = t.Port

	k.HandleFunc("register", t.Register)
	k.HandleHTTP("/{rest:.*}", t.Server)

	k.Run()
}

func (t *tunnelServer) Register(r *kite.Request) (interface{}, error) {
	virtualHost := fmt.Sprintf("%s.%s", r.Username, t.BaseVirtualHost)
	identifier := randomID(32)

	domain := r.Username + "." + t.Dns.HostedZone()

	t.Log.Debug("Adding domain '%s' with to IP Address '%s'", domain, t.ServerAddr)
	if err := t.Dns.Upsert(domain, t.ServerAddr); err != nil {
		return nil, err
	}

	t.Log.Debug("Adding host '%s' with identifer '%s'", virtualHost, identifier)
	t.Server.AddHost(virtualHost, identifier)

	// cleanup domain and delete in memory virtualhost
	t.Server.OnDisconnect(identifier, func() error {
		t.Log.Debug("Deleting host '%s' and domain '%s'", virtualHost, domain)
		t.Server.DeleteHost(virtualHost)
		return t.Dns.Delete(domain)
	})

	return registerResult{
		VirtualHost: virtualHost,
		Identifier:  identifier,
	}, nil
}

// randomID generates a random string of the given length
func randomID(length int) string {
	r := make([]byte, length*6/8)
	rand.Read(r)
	return base64.URLEncoding.EncodeToString(r)
}
<commit_msg>tunnelserver: read public IP from meta-data if not passed from outside<commit_after>package main

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"koding/ec2dynamicdata"
	"koding/kites/common"
	"koding/kites/kloud/pkg/dnsclient"
	"os"

	"github.com/koding/kite"
	"github.com/koding/logging"
	"github.com/koding/multiconfig"
	"github.com/koding/tunnel"
	"github.com/mitchellh/goamz/aws"
)

type registerResult struct {
	VirtualHost string
	Identifier  string
}

type tunnelServer struct {
	Port            int
	Debug           bool
	BaseVirtualHost string `required:"true"`

	// ServerAddr is public Address of the running server. Like an assigned Elastic IP
	ServerAddr string

	HostedZone string
	AccessKey  string
	SecretKey  string

	// internal
	Log    logging.Logger
	Server *tunnel.Server
	Dns    *dnsclient.Route53
}

func main() {
	t := new(tunnelServer)
	m := multiconfig.New()
	m.MustLoad(t)
	m.MustValidate(t)

	auth := aws.Auth{
		AccessKey: t.AccessKey,
		SecretKey: t.SecretKey,
	}

	// get from ec2 meta-data if it's not passed explicitly
	if t.ServerAddr == "" {
		var err error
		t.ServerAddr, err = ec2dynamicdata.GetMetadata(ec2dynamicdata.PublicIPv4)
		if err != nil {
			fmt.Fprintf(os.Stderr, err.Error())
			os.Exit(1)
		}
	}

	var err error
	t.Server, err = tunnel.NewServer(&tunnel.ServerConfig{
		Debug: t.Debug,
	})
	if err != nil {
		fmt.Fprintf(os.Stderr, err.Error())
		os.Exit(1)
	}

	t.Dns = dnsclient.NewRoute53Client(t.HostedZone, auth)
	t.Log = common.NewLogger("tunnelkite", t.Debug)

	k := kite.New("tunnelserver", "0.0.1")
	k.Config.DisableAuthentication = true
	k.Config.Port = t.Port

	k.HandleFunc("register", t.Register)
	k.HandleHTTP("/{rest:.*}", t.Server)

	k.Run()
}

func (t *tunnelServer) Register(r *kite.Request) (interface{}, error) {
	virtualHost := fmt.Sprintf("%s.%s", r.Username, t.BaseVirtualHost)
	identifier := randomID(32)

	domain := r.Username + "." + t.Dns.HostedZone()

	t.Log.Debug("Adding domain '%s' with to IP Address '%s'", domain, t.ServerAddr)
	if err := t.Dns.Upsert(domain, t.ServerAddr); err != nil {
		return nil, err
	}

	t.Log.Debug("Adding host '%s' with identifer '%s'", virtualHost, identifier)
	t.Server.AddHost(virtualHost, identifier)

	// cleanup domain and delete in memory virtualhost
	t.Server.OnDisconnect(identifier, func() error {
		t.Log.Debug("Deleting host '%s' and domain '%s'", virtualHost, domain)
		t.Server.DeleteHost(virtualHost)
		return t.Dns.Delete(domain)
	})

	return registerResult{
		VirtualHost: virtualHost,
		Identifier:  identifier,
	}, nil
}

// randomID generates a random string of the given length
func randomID(length int) string {
	r := make([]byte, length*6/8)
	rand.Read(r)
	return base64.URLEncoding.EncodeToString(r)
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"regexp"

	"github.com/gruntwork-io/terragrunt/config"
	"github.com/gruntwork-io/terragrunt/errors"
	"github.com/gruntwork-io/terragrunt/locks"
	"github.com/gruntwork-io/terragrunt/remote"
	"github.com/gruntwork-io/terragrunt/shell"
	"github.com/gruntwork-io/terragrunt/util"
	"github.com/urfave/cli"
	"github.com/gruntwork-io/terragrunt/options"
)

// Since Terragrunt is just a thin wrapper for Terraform, and we don't want to repeat every single Terraform command
// in its definition, we don't quite fit into the model of any Go CLI library. Fortunately, urfave/cli allows us to
// override the whole template used for the Usage Text.
const CUSTOM_USAGE_TEXT = `DESCRIPTION:
   {{.Name}} - {{.UsageText}}

USAGE:
   {{.Usage}}

COMMANDS:
   apply                Acquire a lock and run 'terraform apply'
   destroy              Acquire a lock and run 'terraform destroy'
   import               Acquire a lock and run 'terraform import'
   refresh              Acquire a lock and run 'terraform refresh'
   remote push          Acquire a lock and run 'terraform remote push'
   release-lock         Release a lock that is left over from some previous command
   *                    Terragrunt forwards all other commands directly to Terraform
{{if .VisibleFlags}}
GLOBAL OPTIONS:
   {{range .VisibleFlags}}{{.}}
   {{end}}{{end}}
VERSION:
   {{.Version}}{{if len .Authors}}

AUTHOR(S):
   {{range .Authors}}{{.}}{{end}}
   {{end}}
`

var MODULE_REGEX = regexp.MustCompile(`module ".+"`)

const TERRAFORM_EXTENSION_GLOB = "*.tf"

const OPT_TERRAGRUNT_CONFIG = "terragrunt-config"
const OPT_NON_INTERACTIVE = "terragrunt-non-interactive"

// Create the Terragrunt CLI App
func CreateTerragruntCli(version string) *cli.App {
	cli.AppHelpTemplate = CUSTOM_USAGE_TEXT

	app := cli.NewApp()

	app.Name = "terragrunt"
	app.Author = "Gruntwork <www.gruntwork.io>"
	app.Version = version
	app.Action = runApp
	app.Usage = "terragrunt <COMMAND>"
	app.UsageText = `Terragrunt is a thin wrapper for [Terraform](https://www.terraform.io/) that supports locking
   via Amazon's DynamoDB and enforces best practices. Terragrunt forwards almost all commands, arguments, and options
   directly to Terraform, using whatever version of Terraform you already have installed. However, before running
   Terraform, Terragrunt will ensure your remote state is configured according to the settings in the .terragrunt file.
   Moreover, for the apply and destroy commands, Terragrunt will first try to acquire a lock using DynamoDB. For
   documentation, see https://github.com/gruntwork-io/terragrunt/.`

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  OPT_TERRAGRUNT_CONFIG,
			EnvVar: "TERRAGRUNT_CONFIG",
			Usage: ".terragrunt file to use",
		},
		cli.BoolFlag{
			Name:  OPT_NON_INTERACTIVE,
			Usage: "Don't show interactive user prompts. This will default the answer for all prompts to 'yes'.",
		},
	}

	return app
}

// The sole action for the app. It forwards all commands directly to Terraform, enforcing a few best practices along
// the way, such as configuring remote state or acquiring a lock.
func runApp(cliContext *cli.Context) (finalErr error) {
	defer errors.Recover(func(cause error) { finalErr = cause })

	// If someone calls us with no args at all, show the help text and exit
	if !cliContext.Args().Present() {
		cli.ShowAppHelp(cliContext)
		return nil
	}

	terragruntOptions := parseTerragruntOptions(cliContext)

	conf, err := config.ReadTerragruntConfig(terragruntOptions)
	if err != nil {
		return err
	}

	if err := downloadModules(cliContext); err != nil {
		return err
	}

	if conf.RemoteState != nil {
		if err := configureRemoteState(cliContext, conf.RemoteState, terragruntOptions); err != nil {
			return err
		}
	}

	if conf.Lock == nil {
		util.Logger.Printf("WARNING: you have not configured locking in your .terragrunt file. Concurrent changes to your .tfstate files may cause conflicts!")
		return runTerraformCommand(cliContext)
	}

	return runTerraformCommandWithLock(cliContext, conf.Lock, terragruntOptions)
}

// Parse command line options that are passed in for Terragrunt
func parseTerragruntOptions(cliContext *cli.Context) options.TerragruntOptions {
	terragruntConfigPath := cliContext.String(OPT_TERRAGRUNT_CONFIG)
	if terragruntConfigPath == "" {
		terragruntConfigPath = config.DefaultTerragruntConfigPath
	}

	nonInteractive := cliContext.Bool(OPT_NON_INTERACTIVE)

	return options.TerragruntOptions{
		TerragruntConfigPath: terragruntConfigPath,
		NonInteractive: nonInteractive,
	}
}

// A quick sanity check that calls `terraform get` to download modules, if they aren't already downloaded.
func downloadModules(cliContext *cli.Context) error {
	switch cliContext.Args().First() {
	case "apply", "destroy", "graph", "output", "plan", "show", "taint", "untaint", "validate":
		shouldDownload, err := shouldDownloadModules()
		if err != nil {
			return err
		}
		if shouldDownload {
			return shell.RunShellCommand("terraform", "get", "-update")
		}
	}

	return nil
}

// Return true if modules aren't already downloaded and the Terraform templates in this project reference modules.
// Note that to keep the logic in this code very simple, this code ONLY detects the case where you haven't downloaded
// modules at all. Detecting if your downloaded modules are out of date (as opposed to missing entirely) is more
// complicated and not something we handle at the moment.
func shouldDownloadModules() (bool, error) {
	if util.FileExists(".terraform/modules") {
		return false, nil
	}

	return util.Grep(MODULE_REGEX, TERRAFORM_EXTENSION_GLOB)
}

// If the user entered a Terraform command that uses state (e.g. plan, apply), make sure remote state is configured
// before running the command.
func configureRemoteState(cliContext *cli.Context, remoteState *remote.RemoteState, terragruntOptions options.TerragruntOptions) error {
	// We only configure remote state for the commands that use the tfstate files. We do not configure it for
	// commands such as "get" or "version".
	switch cliContext.Args().First() {
	case "apply", "destroy", "import", "graph", "output", "plan", "push", "refresh", "show", "taint", "untaint", "validate":
		return remoteState.ConfigureRemoteState(terragruntOptions)
	case "remote":
		if cliContext.Args().Get(1) == "config" {
			// Encourage the user to configure remote state by defining it in .terragrunt and letting
			// Terragrunt handle it for them
			return errors.WithStackTrace(DontManuallyConfigureRemoteState)
		} else {
			// The other "terraform remote" commands explicitly push or pull state, so we shouldn't mess
			// with the configuration
			return nil
		}
	}

	return nil
}

// Run the given Terraform command with the given lock (if the command requires locking)
func runTerraformCommandWithLock(cliContext *cli.Context, lock locks.Lock, terragruntOptions options.TerragruntOptions) error {
	switch cliContext.Args().First() {
	case "apply", "destroy", "import", "refresh":
		return locks.WithLock(lock, func() error { return runTerraformCommand(cliContext) })
	case "remote":
		if cliContext.Args().Get(1) == "push" {
			return locks.WithLock(lock, func() error { return runTerraformCommand(cliContext) })
		} else {
			return runTerraformCommand(cliContext)
		}
	case "release-lock":
		return runReleaseLockCommand(cliContext, lock, terragruntOptions)
	default:
		return runTerraformCommand(cliContext)
	}
}

// Run the given Terraform command
func runTerraformCommand(cliContext *cli.Context) error {
	return shell.RunShellCommand("terraform", cliContext.Args()...)
}

// Release a lock, prompting the user for confirmation first
func runReleaseLockCommand(cliContext *cli.Context, lock locks.Lock, terragruntOptions options.TerragruntOptions) error {
	prompt := fmt.Sprintf("Are you sure you want to release %s?", lock)
	proceed, err := shell.PromptUserForYesNo(prompt, terragruntOptions)
	if err != nil {
		return err
	}

	if proceed {
		return lock.ReleaseLock()
	} else {
		return nil
	}
}

var DontManuallyConfigureRemoteState = fmt.Errorf("Instead of manually using the 'remote config' command, define your remote state settings in .terragrunt and Terragrunt will automatically configure it for you (and all your team members) next time you run it.")
<commit_msg>Strip out all Terragrunt args before running Terraform<commit_after>package cli

import (
	"fmt"
	"regexp"

	"github.com/gruntwork-io/terragrunt/config"
	"github.com/gruntwork-io/terragrunt/errors"
	"github.com/gruntwork-io/terragrunt/locks"
	"github.com/gruntwork-io/terragrunt/remote"
	"github.com/gruntwork-io/terragrunt/shell"
	"github.com/gruntwork-io/terragrunt/util"
	"github.com/urfave/cli"
	"github.com/gruntwork-io/terragrunt/options"
	"strings"
)

// Since Terragrunt is just a thin wrapper for Terraform, and we don't want to repeat every single Terraform command
// in its definition, we don't quite fit into the model of any Go CLI library. Fortunately, urfave/cli allows us to
// override the whole template used for the Usage Text.
const CUSTOM_USAGE_TEXT = `DESCRIPTION:
   {{.Name}} - {{.UsageText}}

USAGE:
   {{.Usage}}

COMMANDS:
   apply                Acquire a lock and run 'terraform apply'
   destroy              Acquire a lock and run 'terraform destroy'
   import               Acquire a lock and run 'terraform import'
   refresh              Acquire a lock and run 'terraform refresh'
   remote push          Acquire a lock and run 'terraform remote push'
   release-lock         Release a lock that is left over from some previous command
   *                    Terragrunt forwards all other commands directly to Terraform
{{if .VisibleFlags}}
GLOBAL OPTIONS:
   {{range .VisibleFlags}}{{.}}
   {{end}}{{end}}
VERSION:
   {{.Version}}{{if len .Authors}}

AUTHOR(S):
   {{range .Authors}}{{.}}{{end}}
   {{end}}
`

var MODULE_REGEX = regexp.MustCompile(`module ".+"`)

const TERRAFORM_EXTENSION_GLOB = "*.tf"

const OPT_TERRAGRUNT_CONFIG = "terragrunt-config"
const OPT_NON_INTERACTIVE = "terragrunt-non-interactive"

// Create the Terragrunt CLI App
func CreateTerragruntCli(version string) *cli.App {
	cli.AppHelpTemplate = CUSTOM_USAGE_TEXT

	app := cli.NewApp()

	app.Name = "terragrunt"
	app.Author = "Gruntwork <www.gruntwork.io>"
	app.Version = version
	app.Action = runApp
	app.Usage = "terragrunt <COMMAND>"
	app.UsageText = `Terragrunt is a thin wrapper for [Terraform](https://www.terraform.io/) that supports locking
   via Amazon's DynamoDB and enforces best practices. Terragrunt forwards almost all commands, arguments, and options
   directly to Terraform, using whatever version of Terraform you already have installed. However, before running
   Terraform, Terragrunt will ensure your remote state is configured according to the settings in the .terragrunt file.
   Moreover, for the apply and destroy commands, Terragrunt will first try to acquire a lock using DynamoDB. For
   documentation, see https://github.com/gruntwork-io/terragrunt/.`

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  OPT_TERRAGRUNT_CONFIG,
			EnvVar: "TERRAGRUNT_CONFIG",
			Usage: ".terragrunt file to use",
		},
		cli.BoolFlag{
			Name:  OPT_NON_INTERACTIVE,
			Usage: "Don't show interactive user prompts. This will default the answer for all prompts to 'yes'.",
		},
	}

	return app
}

// The sole action for the app. It forwards all commands directly to Terraform, enforcing a few best practices along
// the way, such as configuring remote state or acquiring a lock.
func runApp(cliContext *cli.Context) (finalErr error) {
	defer errors.Recover(func(cause error) { finalErr = cause })

	// If someone calls us with no args at all, show the help text and exit
	if !cliContext.Args().Present() {
		cli.ShowAppHelp(cliContext)
		return nil
	}

	terragruntOptions := parseTerragruntOptions(cliContext)

	conf, err := config.ReadTerragruntConfig(terragruntOptions)
	if err != nil {
		return err
	}

	if err := downloadModules(cliContext); err != nil {
		return err
	}

	if conf.RemoteState != nil {
		if err := configureRemoteState(cliContext, conf.RemoteState, terragruntOptions); err != nil {
			return err
		}
	}

	if conf.Lock == nil {
		util.Logger.Printf("WARNING: you have not configured locking in your .terragrunt file. Concurrent changes to your .tfstate files may cause conflicts!")
		return runTerraformCommand(cliContext)
	}

	return runTerraformCommandWithLock(cliContext, conf.Lock, terragruntOptions)
}

// Parse command line options that are passed in for Terragrunt
func parseTerragruntOptions(cliContext *cli.Context) options.TerragruntOptions {
	terragruntConfigPath := cliContext.String(OPT_TERRAGRUNT_CONFIG)
	if terragruntConfigPath == "" {
		terragruntConfigPath = config.DefaultTerragruntConfigPath
	}

	nonInteractive := cliContext.Bool(OPT_NON_INTERACTIVE)

	return options.TerragruntOptions{
		TerragruntConfigPath: terragruntConfigPath,
		NonInteractive: nonInteractive,
	}
}

// A quick sanity check that calls `terraform get` to download modules, if they aren't already downloaded.
func downloadModules(cliContext *cli.Context) error {
	switch cliContext.Args().First() {
	case "apply", "destroy", "graph", "output", "plan", "show", "taint", "untaint", "validate":
		shouldDownload, err := shouldDownloadModules()
		if err != nil {
			return err
		}
		if shouldDownload {
			return shell.RunShellCommand("terraform", "get", "-update")
		}
	}

	return nil
}

// Return true if modules aren't already downloaded and the Terraform templates in this project reference modules.
// Note that to keep the logic in this code very simple, this code ONLY detects the case where you haven't downloaded
// modules at all. Detecting if your downloaded modules are out of date (as opposed to missing entirely) is more
// complicated and not something we handle at the moment.
func shouldDownloadModules() (bool, error) {
	if util.FileExists(".terraform/modules") {
		return false, nil
	}

	return util.Grep(MODULE_REGEX, TERRAFORM_EXTENSION_GLOB)
}

// If the user entered a Terraform command that uses state (e.g. plan, apply), make sure remote state is configured
// before running the command.
func configureRemoteState(cliContext *cli.Context, remoteState *remote.RemoteState, terragruntOptions options.TerragruntOptions) error {
	// We only configure remote state for the commands that use the tfstate files. We do not configure it for
	// commands such as "get" or "version".
	switch cliContext.Args().First() {
	case "apply", "destroy", "import", "graph", "output", "plan", "push", "refresh", "show", "taint", "untaint", "validate":
		return remoteState.ConfigureRemoteState(terragruntOptions)
	case "remote":
		if cliContext.Args().Get(1) == "config" {
			// Encourage the user to configure remote state by defining it in .terragrunt and letting
			// Terragrunt handle it for them
			return errors.WithStackTrace(DontManuallyConfigureRemoteState)
		} else {
			// The other "terraform remote" commands explicitly push or pull state, so we shouldn't mess
			// with the configuration
			return nil
		}
	}

	return nil
}

// Run the given Terraform command with the given lock (if the command requires locking)
func runTerraformCommandWithLock(cliContext *cli.Context, lock locks.Lock, terragruntOptions options.TerragruntOptions) error {
	switch cliContext.Args().First() {
	case "apply", "destroy", "import", "refresh":
		return locks.WithLock(lock, func() error { return runTerraformCommand(cliContext) })
	case "remote":
		if cliContext.Args().Get(1) == "push" {
			return locks.WithLock(lock, func() error { return runTerraformCommand(cliContext) })
		} else {
			return runTerraformCommand(cliContext)
		}
	case "release-lock":
		return runReleaseLockCommand(cliContext, lock, terragruntOptions)
	default:
		return runTerraformCommand(cliContext)
	}
}

// Run the given Terraform command
func runTerraformCommand(cliContext *cli.Context) error {
	return shell.RunShellCommand("terraform", filterOutTerragruntArgs(cliContext)...)
}

// Return the args in teh given CLI Context object, filtering any args that are only meant for Terragrunt itself
func filterOutTerragruntArgs(cliContext *cli.Context) []string {
	args := []string{}

	for _, arg := range cliContext.Args() {
		if !strings.HasPrefix(arg, "--terragrunt") {
			args = append(args, arg)
		}
	}

	return args
}

// Release a lock, prompting the user for confirmation first
func runReleaseLockCommand(cliContext *cli.Context, lock locks.Lock, terragruntOptions options.TerragruntOptions) error {
	prompt := fmt.Sprintf("Are you sure you want to release %s?", lock)
	proceed, err := shell.PromptUserForYesNo(prompt, terragruntOptions)
	if err != nil {
		return err
	}

	if proceed {
		return lock.ReleaseLock()
	} else {
		return nil
	}
}

var DontManuallyConfigureRemoteState = fmt.Errorf("Instead of manually using the 'remote config' command, define your remote state settings in .terragrunt and Terragrunt will automatically configure it for you (and all your team members) next time you run it.")
<|endoftext|>


<commit_before>package agentintegration_test

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/onsi/gomega/gexec"
	"github.com/pivotal-cf/cf-redis-broker/integration/helpers"
	"github.com/pivotal-cf/cf-redis-broker/redisconf"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var redisSession *gexec.Session
var agentSession *gexec.Session

var _ = Describe("DELETE /", func() {

	var (
		redisConn         redis.Conn
		aofPath           string
		originalRedisConf redisconf.Conf
	)

	BeforeEach(func() {
		agentSession = startAgent()
		redisSession, aofPath = startRedisAndBlockUntilUp()

		var err error
		originalRedisConf, err = redisconf.Load(redisConfPath)
		Ω(err).ShouldNot(HaveOccurred())

		redisRestarted := make(chan bool)
		httpRequestReturned := make(chan bool)

		go checkRedisStopAndStart(redisRestarted)
		go doResetRequest(httpRequestReturned)

		select {
		case <-redisRestarted:
			select {
			case <-httpRequestReturned:
			case <-time.After(time.Second * 300):
				Fail("Test timed out after 300 seconds")
			}
		case <-time.After(time.Second * 10):
			Fail("Test timed out after 10 seconds")
		}

		conf, err := redisconf.Load(redisConfPath)
		Ω(err).ShouldNot(HaveOccurred())

		redisConn = helpers.BuildRedisClientFromConf(conf)
	})

	AfterEach(func() {
		stopAgent(agentSession)
		stopRedisAndDeleteData(redisConn, aofPath)
	})

	It("no longer uses the original password", func() {
		password := originalRedisConf.Get("requirepass")
		port := originalRedisConf.Get("port")
		uri := fmt.Sprintf("127.0.0.1:%s", port)
		redisConn, err := redis.Dial("tcp", uri)
		Ω(err).ShouldNot(HaveOccurred())

		_, err = redisConn.Do("AUTH", password)
		Ω(err).Should(MatchError("ERR invalid password"))
	})

	It("resets the configuration", func() {
		config, err := redis.Strings(redisConn.Do("CONFIG", "GET", "maxmemory-policy"))

		Ω(err).ShouldNot(HaveOccurred())
		Ω(config[1]).Should(Equal("noeviction"))
	})

	It("deletes all data from redis", func() {
		values, err := redis.Values(redisConn.Do("KEYS", "*"))
		Ω(err).ShouldNot(HaveOccurred())
		Ω(values).Should(BeEmpty())
	})

	It("has an empty AOF file", func() {
		data, err := ioutil.ReadFile(aofPath)
		Ω(err).ShouldNot(HaveOccurred())
		Ω(string(data)).Should(Equal(""))
	})
})

func startRedisAndBlockUntilUp() (*gexec.Session, string) {
	session, connection := startRedis(redisConfPath)

	_, err := connection.Do("CONFIG", "SET", "maxmemory-policy", "allkeys-lru")
	Ω(err).ShouldNot(HaveOccurred())

	cwd, err := os.Getwd()
	Ω(err).ShouldNot(HaveOccurred())
	aofPath := filepath.Join(cwd, "appendonly.aof")

	Eventually(redisNotWritingAof(connection)).Should(BeTrue())
	Expect(helpers.FileExists(aofPath)).To(BeTrue())

	return session, aofPath
}

func redisNotWritingAof(redisConn redis.Conn) func() bool {
	return func() bool {
		out, _ := redis.String(redisConn.Do("INFO", "persistence"))
		return strings.Contains(out, "aof_pending_rewrite:0") &&
			strings.Contains(out, "aof_rewrite_scheduled:0") &&
			strings.Contains(out, "aof_rewrite_in_progress:0")
	}
}

func doResetRequest(c chan<- bool) {
	defer GinkgoRecover()

	request, _ := http.NewRequest("DELETE", "http://127.0.0.1:9876", nil)
	request.SetBasicAuth("admin", "supersecretpassword")
	response, err := http.DefaultClient.Do(request)
	Ω(err).ShouldNot(HaveOccurred())
	Ω(response.StatusCode).To(Equal(http.StatusOK))

	c <- true
}

func checkRedisStopAndStart(c chan<- bool) {
	defer GinkgoRecover()

	Eventually(redisSession, "10s").Should(gexec.Exit())

	// Sleep here to emulate the time it takes monit to do it's thing
	time.Sleep(time.Millisecond * 200)

	var err error
	redisSession, err = gexec.Start(exec.Command("redis-server", redisConfPath), GinkgoWriter, GinkgoWriter)
	Ω(err).ShouldNot(HaveOccurred())

	conf, err := redisconf.Load(redisConfPath)
	Ω(err).ShouldNot(HaveOccurred())

	port, err := strconv.Atoi(conf.Get("port"))
	Ω(err).ShouldNot(HaveOccurred())

	Expect(helpers.ServiceAvailable(uint(port))).To(BeTrue())

	c <- true
}

func startRedis(confPath string) (*gexec.Session, redis.Conn) {
	redisSession, err := gexec.Start(exec.Command("redis-server", confPath), GinkgoWriter, GinkgoWriter)
	Ω(err).ShouldNot(HaveOccurred())

	conf, err := redisconf.Load(confPath)
	Ω(err).ShouldNot(HaveOccurred())

	port, err := strconv.Atoi(conf.Get("port"))
	Ω(err).ShouldNot(HaveOccurred())

	Expect(helpers.ServiceAvailable(uint(port))).To(BeTrue())
	return redisSession, helpers.BuildRedisClient(uint(port), "localhost", conf.Get("requirepass"))
}

func stopRedisAndDeleteData(redisConn redis.Conn, aofPath string) {
	redisSession.Kill().Wait()
	Eventually(redisSession).Should(gexec.Exit())

	os.Remove(aofPath)
	os.Remove(filepath.Join(aofPath, "..", "dump.rdb"))
}
<commit_msg>Try using Eventually instead of go routines<commit_after>package agentintegration_test

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/onsi/gomega/gexec"
	"github.com/pivotal-cf/cf-redis-broker/integration/helpers"
	"github.com/pivotal-cf/cf-redis-broker/redisconf"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var redisSession *gexec.Session
var agentSession *gexec.Session

var _ = Describe("DELETE /", func() {

	var (
		redisConn         redis.Conn
		aofPath           string
		originalRedisConf redisconf.Conf
	)

	BeforeEach(func() {
		agentSession = startAgent()
		redisSession, aofPath = startRedisAndBlockUntilUp()

		var err error
		originalRedisConf, err = redisconf.Load(redisConfPath)
		Ω(err).ShouldNot(HaveOccurred())

		// redisRestarted := make(chan bool)
		// httpRequestReturned := make(chan bool)

		request, _ := http.NewRequest("DELETE", "http://127.0.0.1:9876", nil)
		request.SetBasicAuth("admin", "supersecretpassword")
		response, err := http.DefaultClient.Do(request)
		Eventually(err).ShouldNot(HaveOccurred())
		Eventually(response.StatusCode).Should(Equal(http.StatusOK))
		Eventually(redisSession, "10s").Should(gexec.Exit())
		time.Sleep(time.Millisecond * 200)
		redisSession, err = gexec.Start(exec.Command("redis-server", redisConfPath), GinkgoWriter, GinkgoWriter)
		Eventually(err).ShouldNot(HaveOccurred())

		// go checkRedisStopAndStart(redisRestarted)
		// go doResetRequest(httpRequestReturned)
		//
		// select {
		// case <-redisRestarted:
		// 	select {
		// 	case <-httpRequestReturned:
		// 	case <-time.After(time.Second * 300):
		// 		Fail("Test timed out after 300 seconds")
		// 	}
		// case <-time.After(time.Second * 10):
		// 	Fail("Test timed out after 10 seconds")
		// }

		conf, err := redisconf.Load(redisConfPath)
		Ω(err).ShouldNot(HaveOccurred())

		_, err = strconv.Atoi(conf.Get("port"))
		Expect(err).NotTo(HaveOccurred())

		redisConn = helpers.BuildRedisClientFromConf(conf)
	})

	AfterEach(func() {
		stopAgent(agentSession)
		stopRedisAndDeleteData(redisConn, aofPath)
	})

	It("no longer uses the original password", func() {
		password := originalRedisConf.Get("requirepass")
		port := originalRedisConf.Get("port")
		uri := fmt.Sprintf("127.0.0.1:%s", port)
		redisConn, err := redis.Dial("tcp", uri)
		Ω(err).ShouldNot(HaveOccurred())

		_, err = redisConn.Do("AUTH", password)
		Ω(err).Should(MatchError("ERR invalid password"))
	})

	It("resets the configuration", func() {
		config, err := redis.Strings(redisConn.Do("CONFIG", "GET", "maxmemory-policy"))

		Ω(err).ShouldNot(HaveOccurred())
		Ω(config[1]).Should(Equal("noeviction"))
	})

	It("deletes all data from redis", func() {
		values, err := redis.Values(redisConn.Do("KEYS", "*"))
		Ω(err).ShouldNot(HaveOccurred())
		Ω(values).Should(BeEmpty())
	})

	It("has an empty AOF file", func() {
		data, err := ioutil.ReadFile(aofPath)
		Ω(err).ShouldNot(HaveOccurred())
		Ω(string(data)).Should(Equal(""))
	})
})

func startRedisAndBlockUntilUp() (*gexec.Session, string) {
	session, connection := startRedis(redisConfPath)

	_, err := connection.Do("CONFIG", "SET", "maxmemory-policy", "allkeys-lru")
	Ω(err).ShouldNot(HaveOccurred())

	cwd, err := os.Getwd()
	Ω(err).ShouldNot(HaveOccurred())
	aofPath := filepath.Join(cwd, "appendonly.aof")

	Eventually(redisNotWritingAof(connection)).Should(BeTrue())
	Expect(helpers.FileExists(aofPath)).To(BeTrue())

	return session, aofPath
}

func redisNotWritingAof(redisConn redis.Conn) func() bool {
	return func() bool {
		out, _ := redis.String(redisConn.Do("INFO", "persistence"))
		return strings.Contains(out, "aof_pending_rewrite:0") &&
			strings.Contains(out, "aof_rewrite_scheduled:0") &&
			strings.Contains(out, "aof_rewrite_in_progress:0")
	}
}

func doResetRequest(c chan<- bool) {
	defer GinkgoRecover()

	request, _ := http.NewRequest("DELETE", "http://127.0.0.1:9876", nil)
	request.SetBasicAuth("admin", "supersecretpassword")
	response, err := http.DefaultClient.Do(request)
	Ω(err).ShouldNot(HaveOccurred())
	Ω(response.StatusCode).To(Equal(http.StatusOK))

	c <- true
}

func checkRedisStopAndStart(c chan<- bool) {
	defer GinkgoRecover()

	Eventually(redisSession, "10s").Should(gexec.Exit())

	// Sleep here to emulate the time it takes monit to do it's thing
	time.Sleep(time.Millisecond * 200)

	var err error
	redisSession, err = gexec.Start(exec.Command("redis-server", redisConfPath), GinkgoWriter, GinkgoWriter)
	Ω(err).ShouldNot(HaveOccurred())

	conf, err := redisconf.Load(redisConfPath)
	Ω(err).ShouldNot(HaveOccurred())

	port, err := strconv.Atoi(conf.Get("port"))
	Ω(err).ShouldNot(HaveOccurred())

	Expect(helpers.ServiceAvailable(uint(port))).To(BeTrue())

	c <- true
}

func startRedis(confPath string) (*gexec.Session, redis.Conn) {
	redisSession, err := gexec.Start(exec.Command("redis-server", confPath), GinkgoWriter, GinkgoWriter)
	Ω(err).ShouldNot(HaveOccurred())

	conf, err := redisconf.Load(confPath)
	Ω(err).ShouldNot(HaveOccurred())

	port, err := strconv.Atoi(conf.Get("port"))
	Ω(err).ShouldNot(HaveOccurred())

	Expect(helpers.ServiceAvailable(uint(port))).To(BeTrue())
	return redisSession, helpers.BuildRedisClient(uint(port), "localhost", conf.Get("requirepass"))
}

func stopRedisAndDeleteData(redisConn redis.Conn, aofPath string) {
	redisSession.Kill().Wait()
	Eventually(redisSession).Should(gexec.Exit())

	os.Remove(aofPath)
	os.Remove(filepath.Join(aofPath, "..", "dump.rdb"))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

var timer *time.Ticker

func scheduler(seconds time.Duration) *time.Ticker {
	ticker := time.NewTicker(seconds * time.Second)
	go func() {
		for t := range ticker.C {
			// do stuff
			fmt.Println(t)
		}
	}()
	return ticker
}

func Start(timer *time.Ticker) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		timer = scheduler(1)
		w.Write([]byte("Starting scheduler"))
	})
}

func Stop(timer *time.Ticker) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		timer.Stop()
		w.Write([]byte("Stoping scheduler"))
	})
}

func main() {
	timer = scheduler(1)
	http.Handle("/start", Start(timer))
	http.Handle("/stop", Stop(timer))
	log.Fatal(http.ListenAndServe(":8080", nil))
}
<commit_msg>	modified:   main.go<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

type scheduler struct {
	t    <-chan time.Time
	quit chan struct{}
	f    func()
}

type schedulerPool struct {
	schedulers map[string]scheduler
}

func NewScheduler() *schedulerPool {
	return &schedulerPool{
		schedulers: make(map[string]scheduler),
	}
}

func (s *schedulerPool) Add(name string, duration time.Duration) {

	scheduler := scheduler{
		t:    time.NewTicker(duration).C,
		quit: make(chan struct{}),
		f:    func() { fmt.Println(name) },
	}

	s.schedulers[name] = scheduler
	go func() {
		for {
			select {
			case <-scheduler.t:
				scheduler.f()
			case <-scheduler.quit:
				return
			}
		}
	}()
}

func (s *schedulerPool) Stop(name string) error {
	scheduler := s.schedulers[name]
	close(scheduler.quit)
	return nil
}

func New(s *schedulerPool) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		d := r.URL.Query().Get("duration")
		n := r.URL.Query().Get("name")
		seconds, _ := time.ParseDuration(d)
		s.Add(n, seconds)
		w.Write([]byte("Starting scheduler"))
	})
}

func Stop(s *schedulerPool) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		n := r.URL.Query().Get("name")
		s.Stop(n)
		w.Write([]byte("Stoping scheduler"))
	})
}

func main() {
	s := NewScheduler()
	http.Handle("/new", New(s))
	http.Handle("/stop", Stop(s))
	log.Fatal(http.ListenAndServe(":8080", nil))
}
<|endoftext|>


<commit_before>package client

import (
	"bytes"
	"encoding/json"
	"fmt"
)

/*
 Copyleft 2016 Alexander I.Grafov <grafov@gmail.com>

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

 ॐ तारे तुत्तारे तुरे स्व
*/

type User struct {
	Login          string `json:"login"`
	Name           string `json:"name"`
	Email          string `json:"email"`
	Theme          string `json:"theme"`
	OrgID          int    `json:"orgId"`
	IsGrafanaAdmin bool   `json:"isGrafanaAdmin"`
}

// GetUser loads a dashboard from Grafana instance along with metadata for a dashboard.
func (r *Instance) GetUser() (User, error) {
	var (
		raw  []byte
		user User
		code int
		err  error
	)
	if raw, code, err = r.get("api/user", nil); err != nil {
		return User{}, err
	}
	if code != 200 {
		return User{}, fmt.Errorf("HTTP error %d: returns %s", code, raw)
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.UseNumber()
	if err := dec.Decode(&user); err != nil {
		return User{}, fmt.Errorf("unmarshal user: %s\n%s", err, raw)
	}
	return user, err
}
<commit_msg>Add GetUsers to client.<commit_after>package client

import (
	"bytes"
	"encoding/json"
	"fmt"
)

/*
 Copyleft 2016 Alexander I.Grafov <grafov@gmail.com>

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

 ॐ तारे तुत्तारे तुरे स्व
*/

type User struct {
	Login          string `json:"login"`
	Name           string `json:"name"`
	Email          string `json:"email"`
	Theme          string `json:"theme"`
	OrgID          int    `json:"orgId"`
	IsGrafanaAdmin bool   `json:"isGrafanaAdmin"`
}

// GetUser get an actual user.
func (r *Instance) GetUser() (User, error) {
	var (
		raw  []byte
		user User
		code int
		err  error
	)
	if raw, code, err = r.get("api/user", nil); err != nil {
		return user, err
	}
	if code != 200 {
		return user, fmt.Errorf("HTTP error %d: returns %s", code, raw)
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.UseNumber()
	if err := dec.Decode(&user); err != nil {
		return user, fmt.Errorf("unmarshal user: %s\n%s", err, raw)
	}
	return user, err
}

// GetUsers get all users.
func (r *Instance) GetUsers() ([]User, error) {
	var (
		raw   []byte
		users []User
		code  int
		err   error
	)
	if raw, code, err = r.get("api/users", nil); err != nil {
		return users, err
	}
	if code != 200 {
		return users, fmt.Errorf("HTTP error %d: returns %s", code, raw)
	}
	dec := json.NewDecoder(bytes.NewReader(raw))
	dec.UseNumber()
	if err := dec.Decode(&users); err != nil {
		return users, fmt.Errorf("unmarshal users: %s\n%s", err, raw)
	}
	return users, err
}
<|endoftext|>