<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 middleware

import (
	"net/http"
	"strings"

	"github.com/go-swagger/go-swagger/errors"
	"github.com/go-swagger/go-swagger/httpkit"
	"github.com/go-swagger/go-swagger/httpkit/middleware/untyped"
	"github.com/go-swagger/go-swagger/spec"
	"github.com/go-swagger/go-swagger/strfmt"
	"github.com/gorilla/context"
)

// A Builder can create middlewares
type Builder func(http.Handler) http.Handler

// PassthroughBuilder returns the handler, aka the builder identity function
func PassthroughBuilder(handler http.Handler) http.Handler { return handler }

// RequestBinder is an interface for types to implement
// when they want to be able to bind from a request
type RequestBinder interface {
	BindRequest(*http.Request, *MatchedRoute) error
}

// Responder is an interface for types to implement
// when they want to be considered for writing HTTP responses
type Responder interface {
	WriteResponse(http.ResponseWriter, httpkit.Producer)
}

// Context is a type safe wrapper around an untyped request context
// used throughout to store request context with the gorilla context module
type Context struct {
	spec    *spec.Document
	api     RoutableAPI
	router  Router
	formats strfmt.Registry
}

type routableUntypedAPI struct {
	api             *untyped.API
	handlers        map[string]map[string]http.Handler
	defaultConsumes string
	defaultProduces string
}

func newRoutableUntypedAPI(spec *spec.Document, api *untyped.API, context *Context) *routableUntypedAPI {
	var handlers map[string]map[string]http.Handler
	if spec == nil || api == nil {
		return nil
	}
	for method, hls := range spec.Operations() {
		um := strings.ToUpper(method)
		for path, op := range hls {
			schemes := spec.SecurityDefinitionsFor(op)

			if oh, ok := api.OperationHandlerFor(method, path); ok {
				if handlers == nil {
					handlers = make(map[string]map[string]http.Handler)
				}
				if b, ok := handlers[um]; !ok || b == nil {
					handlers[um] = make(map[string]http.Handler)
				}

				handlers[um][path] = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					// lookup route info in the context
					route, _ := context.RouteInfo(r)

					// bind and validate the request using reflection
					bound, validation := context.BindAndValidate(r, route)
					if validation != nil {
						context.Respond(w, r, route.Produces, route, validation)
						return
					}

					// actually handle the request
					result, err := oh.Handle(bound)
					if err != nil {
						// respond with failure
						context.Respond(w, r, route.Produces, route, err)
						return
					}

					// respond with success
					context.Respond(w, r, route.Produces, route, result)
				})

				if len(schemes) > 0 {
					handlers[um][path] = newSecureAPI(context, handlers[um][path])
				}
			}
		}
	}

	return &routableUntypedAPI{
		api:             api,
		handlers:        handlers,
		defaultProduces: api.DefaultProduces,
		defaultConsumes: api.DefaultConsumes,
	}
}

func (r *routableUntypedAPI) HandlerFor(method, path string) (http.Handler, bool) {
	paths, ok := r.handlers[strings.ToUpper(method)]
	if !ok {
		return nil, false
	}
	handler, ok := paths[path]
	return handler, ok
}
func (r *routableUntypedAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {
	return r.api.ServeError
}
func (r *routableUntypedAPI) ConsumersFor(mediaTypes []string) map[string]httpkit.Consumer {
	return r.api.ConsumersFor(mediaTypes)
}
func (r *routableUntypedAPI) ProducersFor(mediaTypes []string) map[string]httpkit.Producer {
	return r.api.ProducersFor(mediaTypes)
}
func (r *routableUntypedAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]httpkit.Authenticator {
	return r.api.AuthenticatorsFor(schemes)
}
func (r *routableUntypedAPI) Formats() strfmt.Registry {
	return r.api.Formats()
}

func (r *routableUntypedAPI) DefaultProduces() string {
	return r.defaultProduces
}

func (r *routableUntypedAPI) DefaultConsumes() string {
	return r.defaultConsumes
}

// NewRoutableContext creates a new context for a routable API
func NewRoutableContext(spec *spec.Document, routableAPI RoutableAPI, routes Router) *Context {
	ctx := &Context{spec: spec, api: routableAPI}
	return ctx
}

// NewContext creates a new context wrapper
func NewContext(spec *spec.Document, api *untyped.API, routes Router) *Context {
	ctx := &Context{spec: spec}
	ctx.api = newRoutableUntypedAPI(spec, api, ctx)
	return ctx
}

// Serve serves the specified spec with the specified api registrations as a http.Handler
func Serve(spec *spec.Document, api *untyped.API) http.Handler {
	return ServeWithBuilder(spec, api, PassthroughBuilder)
}

// ServeWithBuilder serves the specified spec with the specified api registrations as a http.Handler that is decorated
// by the Builder
func ServeWithBuilder(spec *spec.Document, api *untyped.API, builder Builder) http.Handler {
	context := NewContext(spec, api, nil)
	return context.APIHandler(builder)
}

type contextKey int8

const (
	_ contextKey = iota
	ctxContentType
	ctxResponseFormat
	ctxMatchedRoute
	ctxAllowedMethods
	ctxBoundParams
	ctxSecurityPrincipal

	ctxConsumer
)

type contentTypeValue struct {
	MediaType string
	Charset   string
}

// BasePath returns the base path for this API
func (c *Context) BasePath() string {
	return c.spec.BasePath()
}

// RequiredProduces returns the accepted content types for responses
func (c *Context) RequiredProduces() []string {
	return c.spec.RequiredProduces()
}

// BindValidRequest binds a params object to a request but only when the request is valid
// if the request is not valid an error will be returned
func (c *Context) BindValidRequest(request *http.Request, route *MatchedRoute, binder RequestBinder) error {
	var res []error

	requestContentType := "*/*"
	// check and validate content type, select consumer
	if httpkit.CanHaveBody(request.Method) {
		ct, _, err := httpkit.ContentType(request.Header, httpkit.IsDelete(request.Method))
		if err != nil {
			res = append(res, err)
		} else {
			if err := validateContentType(route.Consumes, ct); err != nil {
				res = append(res, err)
			}
			route.Consumer = route.Consumers[ct]
			requestContentType = ct
		}
	}

	// check and validate the response format
	if len(res) == 0 && httpkit.NeedsContentType(request.Method) {
		if str := NegotiateContentType(request, route.Produces, requestContentType); str == "" {
			res = append(res, errors.InvalidResponseFormat(request.Header.Get(httpkit.HeaderAccept), route.Produces))
		}
	}

	// now bind the request with the provided binder
	// it's assumed the binder will also validate the request and return an error if the
	// request is invalid
	if binder != nil && len(res) == 0 {
		if err := binder.BindRequest(request, route); err != nil {
			res = append(res, err)
		}
	}

	if len(res) > 0 {
		return errors.CompositeValidationError(res...)
	}
	return nil
}

// ContentType gets the parsed value of a content type
func (c *Context) ContentType(request *http.Request) (string, string, *errors.ParseError) {
	if v, ok := context.GetOk(request, ctxContentType); ok {
		if val, ok := v.(*contentTypeValue); ok {
			return val.MediaType, val.Charset, nil
		}
	}

	mt, cs, err := httpkit.ContentType(request.Header, httpkit.IsDelete(request.Method))
	if err != nil {
		return "", "", err
	}
	context.Set(request, ctxContentType, &contentTypeValue{mt, cs})
	return mt, cs, nil
}

// LookupRoute looks a route up and returns true when it is found
func (c *Context) LookupRoute(request *http.Request) (*MatchedRoute, bool) {
	if route, ok := c.router.Lookup(request.Method, request.URL.Path); ok {
		return route, ok
	}
	return nil, false
}

// RouteInfo tries to match a route for this request
func (c *Context) RouteInfo(request *http.Request) (*MatchedRoute, bool) {
	if v, ok := context.GetOk(request, ctxMatchedRoute); ok {
		if val, ok := v.(*MatchedRoute); ok {
			return val, ok
		}
	}

	if route, ok := c.LookupRoute(request); ok {
		context.Set(request, ctxMatchedRoute, route)
		return route, ok
	}

	return nil, false
}

// ResponseFormat negotiates the response content type
func (c *Context) ResponseFormat(r *http.Request, offers []string) string {
	if v, ok := context.GetOk(r, ctxResponseFormat); ok {
		if val, ok := v.(string); ok {
			return val
		}
	}

	format := NegotiateContentType(r, offers, "")
	if format != "" {
		context.Set(r, ctxResponseFormat, format)
	}
	return format
}

// AllowedMethods gets the allowed methods for the path of this request
func (c *Context) AllowedMethods(request *http.Request) []string {
	return c.router.OtherMethods(request.Method, request.URL.Path)
}

// Authorize authorizes the request
func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interface{}, error) {
	if len(route.Authenticators) == 0 {
		return nil, nil
	}
	if v, ok := context.GetOk(request, ctxSecurityPrincipal); ok {
		return v, nil
	}

	for _, authenticator := range route.Authenticators {
		applies, usr, err := authenticator.Authenticate(request)
		if !applies || err != nil || usr == nil {
			continue
		}
		context.Set(request, ctxSecurityPrincipal, usr)
		return usr, nil
	}

	return nil, errors.Unauthenticated("invalid credentials")
}

// BindAndValidate binds and validates the request
func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute) (interface{}, error) {
	if v, ok := context.GetOk(request, ctxBoundParams); ok {
		if val, ok := v.(*validation); ok {
			if len(val.result) > 0 {
				return val.bound, errors.CompositeValidationError(val.result...)
			}
			return val.bound, nil
		}
	}
	result := validateRequest(c, request, matched)
	if result != nil {
		context.Set(request, ctxBoundParams, result)
	}
	if len(result.result) > 0 {
		return result.bound, errors.CompositeValidationError(result.result...)
	}
	return result.bound, nil
}

// NotFound the default not found responder for when no route has been matched yet
func (c *Context) NotFound(rw http.ResponseWriter, r *http.Request) {
	c.Respond(rw, r, []string{c.api.DefaultProduces()}, nil, errors.NotFound("not found"))
}

// Respond renders the response after doing some content negotiation
func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, data interface{}) {
	offers := []string{c.api.DefaultProduces()}
	for _, mt := range produces {
		if mt != c.api.DefaultProduces() {
			offers = append(offers, mt)
		}
	}

	format := c.ResponseFormat(r, offers)
	rw.Header().Set(httpkit.HeaderContentType, format)

	if resp, ok := data.(Responder); ok {
		producers := route.Producers
		prod, ok := producers[format]
		if !ok {
			prods := c.api.ProducersFor([]string{c.api.DefaultProduces()})
			pr, ok := prods[c.api.DefaultProduces()]
			if !ok {
				panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
			}
			prod = pr
		}
		resp.WriteResponse(rw, prod)
		return
	}

	if err, ok := data.(error); ok {
		if format == "" {
			rw.Header().Set(httpkit.HeaderContentType, httpkit.JSONMime)
		}
		if route == nil || route.Operation == nil {
			c.api.ServeErrorFor("")(rw, r, err)
			return
		}
		c.api.ServeErrorFor(route.Operation.ID)(rw, r, err)
		return
	}

	if route == nil || route.Operation == nil {
		rw.WriteHeader(200)
		if r.Method == "HEAD" {
			return
		}
		producers := c.api.ProducersFor(offers)
		prod, ok := producers[format]
		if !ok {
			panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
		}
		if err := prod.Produce(rw, data); err != nil {
			panic(err) // let the recovery middleware deal with this
		}
		return
	}

	if _, code, ok := route.Operation.SuccessResponse(); ok {
		rw.WriteHeader(code)
		if code == 204 || r.Method == "HEAD" {
			return
		}

		producers := route.Producers
		prod, ok := producers[format]
		if !ok {
			if !ok {
				prods := c.api.ProducersFor([]string{c.api.DefaultProduces()})
				pr, ok := prods[c.api.DefaultProduces()]
				if !ok {
					panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
				}
				prod = pr
			}
		}
		if err := prod.Produce(rw, data); err != nil {
			panic(err) // let the recovery middleware deal with this
		}
		return
	}

	c.api.ServeErrorFor(route.Operation.ID)(rw, r, errors.New(http.StatusInternalServerError, "can't produce response"))
}

// APIHandler returns a handler to serve
func (c *Context) APIHandler(builder Builder) http.Handler {
	b := builder
	if b == nil {
		b = PassthroughBuilder
	}
	return specMiddleware(c, newRouter(c, b(newOperationExecutor(c))))
}
<commit_msg>adds a ResponderFunc helper<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 middleware

import (
	"net/http"
	"strings"

	"github.com/go-swagger/go-swagger/errors"
	"github.com/go-swagger/go-swagger/httpkit"
	"github.com/go-swagger/go-swagger/httpkit/middleware/untyped"
	"github.com/go-swagger/go-swagger/spec"
	"github.com/go-swagger/go-swagger/strfmt"
	"github.com/gorilla/context"
)

// A Builder can create middlewares
type Builder func(http.Handler) http.Handler

// PassthroughBuilder returns the handler, aka the builder identity function
func PassthroughBuilder(handler http.Handler) http.Handler { return handler }

// RequestBinder is an interface for types to implement
// when they want to be able to bind from a request
type RequestBinder interface {
	BindRequest(*http.Request, *MatchedRoute) error
}

// Responder is an interface for types to implement
// when they want to be considered for writing HTTP responses
type Responder interface {
	WriteResponse(http.ResponseWriter, httpkit.Producer)
}

// ResponderFunc wraps a func as a Responder interface
type ResponderFunc func(http.ResponseWriter, httpkit.Producer)

// WriteResponse writes to the response
func (fn ResponderFunc) WriteResponse(rw http.ResponseWriter, pr httpkit.Producer) {
	fn(rw, pr)
}

// Context is a type safe wrapper around an untyped request context
// used throughout to store request context with the gorilla context module
type Context struct {
	spec    *spec.Document
	api     RoutableAPI
	router  Router
	formats strfmt.Registry
}

type routableUntypedAPI struct {
	api             *untyped.API
	handlers        map[string]map[string]http.Handler
	defaultConsumes string
	defaultProduces string
}

func newRoutableUntypedAPI(spec *spec.Document, api *untyped.API, context *Context) *routableUntypedAPI {
	var handlers map[string]map[string]http.Handler
	if spec == nil || api == nil {
		return nil
	}
	for method, hls := range spec.Operations() {
		um := strings.ToUpper(method)
		for path, op := range hls {
			schemes := spec.SecurityDefinitionsFor(op)

			if oh, ok := api.OperationHandlerFor(method, path); ok {
				if handlers == nil {
					handlers = make(map[string]map[string]http.Handler)
				}
				if b, ok := handlers[um]; !ok || b == nil {
					handlers[um] = make(map[string]http.Handler)
				}

				handlers[um][path] = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					// lookup route info in the context
					route, _ := context.RouteInfo(r)

					// bind and validate the request using reflection
					bound, validation := context.BindAndValidate(r, route)
					if validation != nil {
						context.Respond(w, r, route.Produces, route, validation)
						return
					}

					// actually handle the request
					result, err := oh.Handle(bound)
					if err != nil {
						// respond with failure
						context.Respond(w, r, route.Produces, route, err)
						return
					}

					// respond with success
					context.Respond(w, r, route.Produces, route, result)
				})

				if len(schemes) > 0 {
					handlers[um][path] = newSecureAPI(context, handlers[um][path])
				}
			}
		}
	}

	return &routableUntypedAPI{
		api:             api,
		handlers:        handlers,
		defaultProduces: api.DefaultProduces,
		defaultConsumes: api.DefaultConsumes,
	}
}

func (r *routableUntypedAPI) HandlerFor(method, path string) (http.Handler, bool) {
	paths, ok := r.handlers[strings.ToUpper(method)]
	if !ok {
		return nil, false
	}
	handler, ok := paths[path]
	return handler, ok
}
func (r *routableUntypedAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {
	return r.api.ServeError
}
func (r *routableUntypedAPI) ConsumersFor(mediaTypes []string) map[string]httpkit.Consumer {
	return r.api.ConsumersFor(mediaTypes)
}
func (r *routableUntypedAPI) ProducersFor(mediaTypes []string) map[string]httpkit.Producer {
	return r.api.ProducersFor(mediaTypes)
}
func (r *routableUntypedAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]httpkit.Authenticator {
	return r.api.AuthenticatorsFor(schemes)
}
func (r *routableUntypedAPI) Formats() strfmt.Registry {
	return r.api.Formats()
}

func (r *routableUntypedAPI) DefaultProduces() string {
	return r.defaultProduces
}

func (r *routableUntypedAPI) DefaultConsumes() string {
	return r.defaultConsumes
}

// NewRoutableContext creates a new context for a routable API
func NewRoutableContext(spec *spec.Document, routableAPI RoutableAPI, routes Router) *Context {
	ctx := &Context{spec: spec, api: routableAPI}
	return ctx
}

// NewContext creates a new context wrapper
func NewContext(spec *spec.Document, api *untyped.API, routes Router) *Context {
	ctx := &Context{spec: spec}
	ctx.api = newRoutableUntypedAPI(spec, api, ctx)
	return ctx
}

// Serve serves the specified spec with the specified api registrations as a http.Handler
func Serve(spec *spec.Document, api *untyped.API) http.Handler {
	return ServeWithBuilder(spec, api, PassthroughBuilder)
}

// ServeWithBuilder serves the specified spec with the specified api registrations as a http.Handler that is decorated
// by the Builder
func ServeWithBuilder(spec *spec.Document, api *untyped.API, builder Builder) http.Handler {
	context := NewContext(spec, api, nil)
	return context.APIHandler(builder)
}

type contextKey int8

const (
	_ contextKey = iota
	ctxContentType
	ctxResponseFormat
	ctxMatchedRoute
	ctxAllowedMethods
	ctxBoundParams
	ctxSecurityPrincipal

	ctxConsumer
)

type contentTypeValue struct {
	MediaType string
	Charset   string
}

// BasePath returns the base path for this API
func (c *Context) BasePath() string {
	return c.spec.BasePath()
}

// RequiredProduces returns the accepted content types for responses
func (c *Context) RequiredProduces() []string {
	return c.spec.RequiredProduces()
}

// BindValidRequest binds a params object to a request but only when the request is valid
// if the request is not valid an error will be returned
func (c *Context) BindValidRequest(request *http.Request, route *MatchedRoute, binder RequestBinder) error {
	var res []error

	requestContentType := "*/*"
	// check and validate content type, select consumer
	if httpkit.CanHaveBody(request.Method) {
		ct, _, err := httpkit.ContentType(request.Header, httpkit.IsDelete(request.Method))
		if err != nil {
			res = append(res, err)
		} else {
			if err := validateContentType(route.Consumes, ct); err != nil {
				res = append(res, err)
			}
			route.Consumer = route.Consumers[ct]
			requestContentType = ct
		}
	}

	// check and validate the response format
	if len(res) == 0 && httpkit.NeedsContentType(request.Method) {
		if str := NegotiateContentType(request, route.Produces, requestContentType); str == "" {
			res = append(res, errors.InvalidResponseFormat(request.Header.Get(httpkit.HeaderAccept), route.Produces))
		}
	}

	// now bind the request with the provided binder
	// it's assumed the binder will also validate the request and return an error if the
	// request is invalid
	if binder != nil && len(res) == 0 {
		if err := binder.BindRequest(request, route); err != nil {
			res = append(res, err)
		}
	}

	if len(res) > 0 {
		return errors.CompositeValidationError(res...)
	}
	return nil
}

// ContentType gets the parsed value of a content type
func (c *Context) ContentType(request *http.Request) (string, string, *errors.ParseError) {
	if v, ok := context.GetOk(request, ctxContentType); ok {
		if val, ok := v.(*contentTypeValue); ok {
			return val.MediaType, val.Charset, nil
		}
	}

	mt, cs, err := httpkit.ContentType(request.Header, httpkit.IsDelete(request.Method))
	if err != nil {
		return "", "", err
	}
	context.Set(request, ctxContentType, &contentTypeValue{mt, cs})
	return mt, cs, nil
}

// LookupRoute looks a route up and returns true when it is found
func (c *Context) LookupRoute(request *http.Request) (*MatchedRoute, bool) {
	if route, ok := c.router.Lookup(request.Method, request.URL.Path); ok {
		return route, ok
	}
	return nil, false
}

// RouteInfo tries to match a route for this request
func (c *Context) RouteInfo(request *http.Request) (*MatchedRoute, bool) {
	if v, ok := context.GetOk(request, ctxMatchedRoute); ok {
		if val, ok := v.(*MatchedRoute); ok {
			return val, ok
		}
	}

	if route, ok := c.LookupRoute(request); ok {
		context.Set(request, ctxMatchedRoute, route)
		return route, ok
	}

	return nil, false
}

// ResponseFormat negotiates the response content type
func (c *Context) ResponseFormat(r *http.Request, offers []string) string {
	if v, ok := context.GetOk(r, ctxResponseFormat); ok {
		if val, ok := v.(string); ok {
			return val
		}
	}

	format := NegotiateContentType(r, offers, "")
	if format != "" {
		context.Set(r, ctxResponseFormat, format)
	}
	return format
}

// AllowedMethods gets the allowed methods for the path of this request
func (c *Context) AllowedMethods(request *http.Request) []string {
	return c.router.OtherMethods(request.Method, request.URL.Path)
}

// Authorize authorizes the request
func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interface{}, error) {
	if len(route.Authenticators) == 0 {
		return nil, nil
	}
	if v, ok := context.GetOk(request, ctxSecurityPrincipal); ok {
		return v, nil
	}

	for _, authenticator := range route.Authenticators {
		applies, usr, err := authenticator.Authenticate(request)
		if !applies || err != nil || usr == nil {
			continue
		}
		context.Set(request, ctxSecurityPrincipal, usr)
		return usr, nil
	}

	return nil, errors.Unauthenticated("invalid credentials")
}

// BindAndValidate binds and validates the request
func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute) (interface{}, error) {
	if v, ok := context.GetOk(request, ctxBoundParams); ok {
		if val, ok := v.(*validation); ok {
			if len(val.result) > 0 {
				return val.bound, errors.CompositeValidationError(val.result...)
			}
			return val.bound, nil
		}
	}
	result := validateRequest(c, request, matched)
	if result != nil {
		context.Set(request, ctxBoundParams, result)
	}
	if len(result.result) > 0 {
		return result.bound, errors.CompositeValidationError(result.result...)
	}
	return result.bound, nil
}

// NotFound the default not found responder for when no route has been matched yet
func (c *Context) NotFound(rw http.ResponseWriter, r *http.Request) {
	c.Respond(rw, r, []string{c.api.DefaultProduces()}, nil, errors.NotFound("not found"))
}

// Respond renders the response after doing some content negotiation
func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, data interface{}) {
	offers := []string{c.api.DefaultProduces()}
	for _, mt := range produces {
		if mt != c.api.DefaultProduces() {
			offers = append(offers, mt)
		}
	}

	format := c.ResponseFormat(r, offers)
	rw.Header().Set(httpkit.HeaderContentType, format)

	if resp, ok := data.(Responder); ok {
		producers := route.Producers
		prod, ok := producers[format]
		if !ok {
			prods := c.api.ProducersFor([]string{c.api.DefaultProduces()})
			pr, ok := prods[c.api.DefaultProduces()]
			if !ok {
				panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
			}
			prod = pr
		}
		resp.WriteResponse(rw, prod)
		return
	}

	if err, ok := data.(error); ok {
		if format == "" {
			rw.Header().Set(httpkit.HeaderContentType, httpkit.JSONMime)
		}
		if route == nil || route.Operation == nil {
			c.api.ServeErrorFor("")(rw, r, err)
			return
		}
		c.api.ServeErrorFor(route.Operation.ID)(rw, r, err)
		return
	}

	if route == nil || route.Operation == nil {
		rw.WriteHeader(200)
		if r.Method == "HEAD" {
			return
		}
		producers := c.api.ProducersFor(offers)
		prod, ok := producers[format]
		if !ok {
			panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
		}
		if err := prod.Produce(rw, data); err != nil {
			panic(err) // let the recovery middleware deal with this
		}
		return
	}

	if _, code, ok := route.Operation.SuccessResponse(); ok {
		rw.WriteHeader(code)
		if code == 204 || r.Method == "HEAD" {
			return
		}

		producers := route.Producers
		prod, ok := producers[format]
		if !ok {
			if !ok {
				prods := c.api.ProducersFor([]string{c.api.DefaultProduces()})
				pr, ok := prods[c.api.DefaultProduces()]
				if !ok {
					panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
				}
				prod = pr
			}
		}
		if err := prod.Produce(rw, data); err != nil {
			panic(err) // let the recovery middleware deal with this
		}
		return
	}

	c.api.ServeErrorFor(route.Operation.ID)(rw, r, errors.New(http.StatusInternalServerError, "can't produce response"))
}

// APIHandler returns a handler to serve
func (c *Context) APIHandler(builder Builder) http.Handler {
	b := builder
	if b == nil {
		b = PassthroughBuilder
	}
	return specMiddleware(c, newRouter(c, b(newOperationExecutor(c))))
}
<|endoftext|>


<commit_before>package main

func newClassHealer() *class {
	class := &class{
		name: "Healer",
		// TODO stats
		health:               700,
		healthRegeneration:   2,
		mana:                 400,
		manaRegeneration:     6,
		armor:                defaultArmor,
		magicResistance:      defaultMagicResistance,
		criticalStrikeChance: defaultCriticalStrikeChance,
		criticalStrikeFactor: defaultCriticalStrikeFactor,
		cooldownReduction:    defaultCooldownReduction,
		damageThreatFactor:   defaultDamageThreatFactor,
		healingThreatFactor:  defaultHealingThreatFactor,
		// TODO abilities
		abilities: make([]*ability, 4),
	}
	return class
}
<commit_msg>Implement the healer abilities<commit_after>package main

import (
	"time"
)

func newClassHealer() *class {
	var q, w, e, r *ability
	class := &class{
		name: "Healer",
		// TODO stats
		health:               700,
		healthRegeneration:   2,
		mana:                 400,
		manaRegeneration:     6,
		armor:                defaultArmor,
		magicResistance:      defaultMagicResistance,
		criticalStrikeChance: defaultCriticalStrikeChance,
		criticalStrikeFactor: defaultCriticalStrikeFactor,
		cooldownReduction:    defaultCooldownReduction,
		damageThreatFactor:   defaultDamageThreatFactor,
		healingThreatFactor:  defaultHealingThreatFactor,
	}
	// Magic damage / Mana restore
	q = &ability{
		name:               "Healer Q",
		targetType:         targetTypeEnemy,
		healthCost:         0,
		manaCost:           0,
		activationDuration: 2 * time.Second,
		cooldownDuration:   2 * time.Second,
		disableTypes: []disableType{
			disableTypeSilence,
			disableTypeStun,
		},
		perform: func(performer, receiver *unit) {
			// TODO handle the error
			before, after, _ := newMagicDamage(performer, receiver, 100, q.name).perform(performer.game)
			// TODO send a message including the ability name
			performer.performManaModification((before - after) * 0.1)
		},
	}
	// HoT
	w = &ability{
		name:               "Healer W",
		targetType:         targetTypeFriend,
		healthCost:         0,
		manaCost:           40,
		activationDuration: 2 * time.Second,
		cooldownDuration:   4 * time.Second,
		disableTypes: []disableType{
			disableTypeSilence,
			disableTypeStun,
		},
		perform: func(performer, receiver *unit) {
			receiver.attachOperator(newHoT(
				newHealing(performer, receiver, 20, w.name),
				// TODO converter
				gameTime(12*time.Second),
			))
		},
	}
	// Healing
	e = &ability{
		name:               "Healer E",
		targetType:         targetTypeFriend,
		healthCost:         0,
		manaCost:           80,
		activationDuration: 2 * time.Second,
		cooldownDuration:   8 * time.Second,
		disableTypes: []disableType{
			disableTypeSilence,
			disableTypeStun,
		},
		perform: func(performer, receiver *unit) {
			newHealing(performer, receiver, 400, e.name).perform(performer.game)
		},
	}
	// HoT / Increasing critical strike chance and critical strike factor
	r = &ability{
		name:               "Healer R",
		targetType:         targetTypeAllFriends,
		healthCost:         0,
		manaCost:           200,
		activationDuration: 0,
		cooldownDuration:   60 * time.Second,
		disableTypes: []disableType{
			disableTypeSilence,
			disableTypeStun,
		},
		perform: func(performer, receiver *unit) {
			performer.attachOperator(newModifier(performer, 6*time.Second, unitModification{
				criticalStrikeChance: 0.5,
				criticalStrikeFactor: 1.5,
			}))
			for _, friend := range performer.game.friends(performer) {
				friend.attachOperator(newHoT(
					newHealing(performer, friend, 20, r.name),
					// TODO converter
					gameTime(6*time.Second),
				))
			}
		},
	}
	return class
}
<|endoftext|>


<commit_before>// Copyright 2014, The Serviced Authors. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE file.

package api

import (
	"encoding/json"
	"fmt"
	elastigo "github.com/mattbaird/elastigo/api"
	"github.com/mattbaird/elastigo/core"
	"github.com/zenoss/serviced/domain/service"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
)

// Export logs
// serviceIds: list of services to select (includes their children).
//             empty slice means no filter
// from: yyyy.mm.dd (inclusive)
//       "" means unbounded
// to: yyyy.mm.dd (inclusive)
//     "" means unbounded
// outfile: the exported logs will tgz'd and written here.
//          "" means "./serviced-log-export.tgz".

func (a *api) ExportLogs(serviceIds []string, from, to, outfile string) (err error) {
	var e error
	files := []*os.File{}
	fileIndex := make(map[string]map[string]int) // host => filename => index

	// make sure we can write to outfile
	if outfile == "" {
		pwd, e := os.Getwd()
		if e != nil {
			return fmt.Errorf("could not determine current directory: %s", e)
		}
		outfile = filepath.Join(pwd, "serviced-log-export.tgz")
	}
	if fp, e := filepath.Abs(outfile); e != nil {
		return fmt.Errorf("could not convert '%s' to an absolute path: %v", outfile, e)
	} else {
		outfile = filepath.Clean(fp)
	}
	if tgzfile, e := os.Create(outfile); e != nil {
		return fmt.Errorf("could not create %s: %s", outfile, e)
	} else {
		tgzfile.Close()
	}
	if e = os.Remove(outfile); e != nil {
		return fmt.Errorf("could not remove %s: %s", outfile, e)
	}

	// Validate and normalize the date range filter attributes "from" and "to"
	if from == "" && to == "" {
		to = time.Now().UTC().Format("2006.01.02")
		from = time.Now().UTC().AddDate(0, 0, -1).Format("2006.01.02")
	}
	if from != "" {
		if from, e = NormalizeYYYYMMDD(from); e != nil {
			return e
		}
	}
	if to != "" {
		if to, e = NormalizeYYYYMMDD(to); e != nil {
			return e
		}
	}

	query := "*"
	if len(serviceIds) > 0 {
		services, e := a.GetServices()
		if e != nil {
			return e
		}
		serviceMap := make(map[string]*service.Service)
		for _, service := range services {
			serviceMap[service.Id] = service
		}
		serviceIdMap := make(map[string]bool) //includes serviceIds, and their children as well
		for _, serviceId := range serviceIds {
			serviceIdMap[serviceId] = true
		}
		for _, service := range services {
			srvc := service
			for {
				found := false
				for _, serviceId := range serviceIds {
					if srvc.Id == serviceId {
						serviceIdMap[service.Id] = true
						found = true
						break
					}
				}
				if found || srvc.ParentServiceId == "" {
					break
				}
				srvc = serviceMap[srvc.ParentServiceId]
			}
		}
		re := regexp.MustCompile("\\A[\\w\\-]+\\z") //only letters, numbers, underscores, and dashes
		queryParts := []string{}
		for serviceId, _ := range serviceIdMap {
			if re.FindStringIndex(serviceId) == nil {
				return fmt.Errorf("invalid service ID format: %s", serviceId)
			}
			queryParts = append(queryParts, fmt.Sprintf("\"%s\"", strings.Replace(serviceId, "-", "\\-", -1)))
		}
		query = fmt.Sprintf("service:(%s)", strings.Join(queryParts, " OR "))
	}

	// Get a temporary directory
	tempdir, e := ioutil.TempDir("", "serviced-log-export-")
	if e != nil {
		return fmt.Errorf("could not create temp directory: %s", e)
	}
	defer os.RemoveAll(tempdir)

	days, e := LogstashDays()
	if e != nil {
		return e
	}
	foundIndexedDay := false
	for _, yyyymmdd := range days {
		// Skip the indexes that are filtered out by the date range
		if (from != "" && yyyymmdd < from) || (to != "" && yyyymmdd > to) {
			continue
		} else {
			foundIndexedDay = true
		}

		logstashIndex := fmt.Sprintf("logstash-%s", yyyymmdd)
		result, e := core.SearchUri(logstashIndex, "", query, "1m", 1000)
		if e != nil {
			return fmt.Errorf("failed to search elasticsearch: %s", e)
		}
		//TODO: Submit a patch to elastigo to support the "clear scroll" api. Add a "defer" here.
		remaining := result.Hits.Total > 0
		for remaining {
			result, e = core.Scroll(false, result.ScrollId, "1m")
			hits := result.Hits.Hits
			total := len(hits)
			for i := 0; i < total; i++ {
				host, logfile, compactLines, e := parseLogSource(hits[i].Source)
				if e != nil {
					return e
				}
				if _, found := fileIndex[host]; !found {
					fileIndex[host] = make(map[string]int)
				}
				if _, found := fileIndex[host][logfile]; !found {
					index := len(files)
					filename := filepath.Join(tempdir, fmt.Sprintf("%03d.log", index))
					file, e := os.Create(filename)
					if e != nil {
						return fmt.Errorf("failed to create file %s: %s", filename, e)
					}
					defer func() {
						if e := file.Close(); e != nil && err == nil {
							err = fmt.Errorf("failed to close file '%s' cleanly: %s", filename, e)
						}
					}()
					fileIndex[host][logfile] = index
					files = append(files, file)
				}
				index := fileIndex[host][logfile]
				file := files[index]
				filename := filepath.Join(tempdir, fmt.Sprintf("%03d.log", index))
				for _, line := range compactLines {
					formatted := fmt.Sprintf("%016x\t%016x\t%s\n", line.Timestamp, line.Offset, line.Message)
					if _, e := file.WriteString(formatted); e != nil {
						return fmt.Errorf("failed writing to file %s: %s", filename, e)
					}
				}
			}
			remaining = len(hits) > 0
		}
	}
	if !foundIndexedDay {
		return fmt.Errorf("no logstash indexes exist for the given date range %s - %s", from, to)
	}

	indexData := []string{}
	for host, logfileIndex := range fileIndex {
		for logfile, i := range logfileIndex {
			filename := filepath.Join(tempdir, fmt.Sprintf("%03d.log", i))
			cmd := exec.Command("sort", filename, "-o", filename)
			if output, e := cmd.CombinedOutput(); e != nil {
				return fmt.Errorf("failed sorting %s, error: %v, output: %s", filename, e, output)
			}
			cmd = exec.Command("sed", "s/^[0-9a-f]*\\t[0-9a-f]*\\t//", "-i", filename)
			if output, e := cmd.CombinedOutput(); e != nil {
				return fmt.Errorf("failed stripping sort prefixes from %s, error: %v, output: %s", filename, e, output)
			}
			indexData = append(indexData, fmt.Sprintf("%03d.log\t%s\t%s", i, strconv.Quote(host), strconv.Quote(logfile)))
		}
	}
	sort.Strings(indexData)
	indexData = append([]string{"INDEX OF LOG FILES", "File\tHost\tOriginal Filename"}, indexData...)
	indexData = append(indexData, "")
	indexFile := filepath.Join(tempdir, "index.txt")
	e = ioutil.WriteFile(indexFile, []byte(strings.Join(indexData, "\n")), 0644)
	if e != nil {
		return fmt.Errorf("failed writing to %s: %s", indexFile, e)
	}

	cmd := exec.Command("tar", "-czf", outfile, "-C", filepath.Dir(tempdir), filepath.Base(tempdir))
	if output, e := cmd.CombinedOutput(); e != nil {
		return fmt.Errorf("failed to write tgz cmd:%+v, error:%v, output:%s", cmd, e, string(output))
	}
	return nil
}

type logLine struct {
	Host      string    `json:"host"`
	File      string    `json:"file"`
	Timestamp time.Time `json:"@timestamp"`
	Offset    string    `json:"offset"`
	Message   string    `json:"message"`
}

type logMultiLine struct {
	Host      string    `json:"host"`
	File      string    `json:"file"`
	Timestamp time.Time `json:"@timestamp"`
	Offset    []string  `json:"offset"`
	Message   string    `json:"message"`
}

type compactLogLine struct {
	Timestamp int64 //nanoseconds since the epoch, truncated at the minute to hide jitter
	Offset    int64
	Message   string
}

var newline = regexp.MustCompile("\\r?\\n")

// return: host, file, lines, error
func parseLogSource(source []byte) (string, string, []compactLogLine, error) {

	var line logLine
	if e := json.Unmarshal(source, &line); e == nil {
		offset, e := strconv.ParseInt(line.Offset, 10, 64)
		if e != nil {
			return "", "", nil, fmt.Errorf("failed to parse offset \"%s\" in \"%s\": %s", line.Offset, source, e)
		}
		compactLine := compactLogLine{
			Timestamp: truncateToMinute(line.Timestamp.UnixNano()),
			Offset:    offset,
			Message:   line.Message,
		}
		return line.Host, line.File, []compactLogLine{compactLine}, nil
	}
	var multiLine logMultiLine
	if e := json.Unmarshal(source, &multiLine); e != nil {
		return "", "", nil, fmt.Errorf("failed to parse JSON \"%s\": %s", source, e)
	}
	messages := newline.Split(multiLine.Message, -1)
	if len(messages) != len(multiLine.Offset) {
		return "", "", nil, fmt.Errorf("offsets do not correspond with lines: %s", source)
	}
	timestamp := truncateToMinute(multiLine.Timestamp.UnixNano())
	compactLines := make([]compactLogLine, len(messages))
	for i, offsetString := range multiLine.Offset {
		offset, e := strconv.ParseInt(offsetString, 10, 64)
		if e != nil {
			return "", "", nil, fmt.Errorf("failed to parse offset \"%s\" in \"%s\": %s", offsetString, source, e)
		}
		compactLines = append(compactLines, compactLogLine{
			Timestamp: timestamp,
			Offset:    offset,
			Message:   messages[i],
		})
	}
	return multiLine.Host, multiLine.File, compactLines, nil
}

// Matches optional non-digits, 4 digits, optional non-digits, 2 digits, optional non-digits, 2 digits, optional non-digits
// Returns those 8 digits formatted as "dddd.dd.dd", or error if unparseable.
func NormalizeYYYYMMDD(s string) (string, error) {
	match := yyyymmdd_matcher.FindStringSubmatch(s)
	if match == nil {
		return "", fmt.Errorf("could not parse '%s' as yyyymmdd", s)
	}
	return fmt.Sprintf("%s.%s.%s", match[1], match[2], match[3]), nil
}

var yyyymmdd_matcher = regexp.MustCompile("\\A[^0-9]*([0-9]{4})[^0-9]*([0-9]{2})[^0-9]*([0-9]{2})[^0-9]*\\z")

// Returns a list of all the dates with a logstash-YYYY.MM.DD index available in ElasticSearch.
// The strings are in YYYY.MM.DD format, and in reverse chronological order.
var LogstashDays = func() ([]string, error) {
	response, e := elastigo.DoCommand("GET", "/_aliases", nil)
	if e != nil {
		return []string{}, fmt.Errorf("couldn't fetch list of indices: %s", e)
	}
	var aliasMap map[string]interface{}
	if e = json.Unmarshal(response, &aliasMap); e != nil {
		return []string{}, fmt.Errorf("couldn't parse response (%s): %s", response, e)
	}
	result := make([]string, 0, len(aliasMap))
	for index, _ := range aliasMap {
		if trimmed := strings.TrimPrefix(index, "logstash-"); trimmed != index {
			if trimmed, e = NormalizeYYYYMMDD(trimmed); e != nil {
				trimmed = ""
			}
			result = append(result, trimmed)
		}
	}
	sort.Sort(sort.Reverse(sort.StringSlice(result)))
	return result, nil
}

func truncateToMinute(nanos int64) int64 {
	return nanos / int64(time.Minute) * int64(time.Minute)
}
<commit_msg>Code review feedback from wkharold<commit_after>// Copyright 2014, The Serviced Authors. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE file.

package api

import (
	"encoding/json"
	"fmt"
	elastigo "github.com/mattbaird/elastigo/api"
	"github.com/mattbaird/elastigo/core"
	"github.com/zenoss/serviced/domain/service"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
)

// ExportLogs exports logs from ElasticSearch.
// serviceIds: list of services to select (includes their children). Empty slice means no filter
// from: yyyy.mm.dd (inclusive), "" means unbounded
// to: yyyy.mm.dd (inclusive), "" means unbounded
// outfile: the exported logs will tgz'd and written here. "" means "./serviced-log-export.tgz".
func (a *api) ExportLogs(serviceIds []string, from, to, outfile string) (err error) {
	var e error
	files := []*os.File{}
	fileIndex := make(map[string]map[string]int) // host => filename => index

	// make sure we can write to outfile
	if outfile == "" {
		pwd, e := os.Getwd()
		if e != nil {
			return fmt.Errorf("could not determine current directory: %s", e)
		}
		outfile = filepath.Join(pwd, "serviced-log-export.tgz")
	}
	if fp, e := filepath.Abs(outfile); e != nil {
		return fmt.Errorf("could not convert '%s' to an absolute path: %v", outfile, e)
	} else {
		outfile = filepath.Clean(fp)
	}
	if tgzfile, e := os.Create(outfile); e != nil {
		return fmt.Errorf("could not create %s: %s", outfile, e)
	} else {
		tgzfile.Close()
	}
	if e = os.Remove(outfile); e != nil {
		return fmt.Errorf("could not remove %s: %s", outfile, e)
	}

	// Validate and normalize the date range filter attributes "from" and "to"
	if from == "" && to == "" {
		to = time.Now().UTC().Format("2006.01.02")
		from = time.Now().UTC().AddDate(0, 0, -1).Format("2006.01.02")
	}
	if from != "" {
		if from, e = NormalizeYYYYMMDD(from); e != nil {
			return e
		}
	}
	if to != "" {
		if to, e = NormalizeYYYYMMDD(to); e != nil {
			return e
		}
	}

	query := "*"
	if len(serviceIds) > 0 {
		services, e := a.GetServices()
		if e != nil {
			return e
		}
		serviceMap := make(map[string]*service.Service)
		for _, service := range services {
			serviceMap[service.Id] = service
		}
		serviceIdMap := make(map[string]bool) //includes serviceIds, and their children as well
		for _, serviceId := range serviceIds {
			serviceIdMap[serviceId] = true
		}
		for _, service := range services {
			srvc := service
			for {
				found := false
				for _, serviceId := range serviceIds {
					if srvc.Id == serviceId {
						serviceIdMap[service.Id] = true
						found = true
						break
					}
				}
				if found || srvc.ParentServiceId == "" {
					break
				}
				srvc = serviceMap[srvc.ParentServiceId]
			}
		}
		re := regexp.MustCompile("\\A[\\w\\-]+\\z") //only letters, numbers, underscores, and dashes
		queryParts := []string{}
		for serviceId := range serviceIdMap {
			if re.FindStringIndex(serviceId) == nil {
				return fmt.Errorf("invalid service ID format: %s", serviceId)
			}
			queryParts = append(queryParts, fmt.Sprintf("\"%s\"", strings.Replace(serviceId, "-", "\\-", -1)))
		}
		query = fmt.Sprintf("service:(%s)", strings.Join(queryParts, " OR "))
	}

	// Get a temporary directory
	tempdir, e := ioutil.TempDir("", "serviced-log-export-")
	if e != nil {
		return fmt.Errorf("could not create temp directory: %s", e)
	}
	defer os.RemoveAll(tempdir)

	days, e := LogstashDays()
	if e != nil {
		return e
	}
	foundIndexedDay := false
	for _, yyyymmdd := range days {
		// Skip the indexes that are filtered out by the date range
		if (from != "" && yyyymmdd < from) || (to != "" && yyyymmdd > to) {
			continue
		} else {
			foundIndexedDay = true
		}

		logstashIndex := fmt.Sprintf("logstash-%s", yyyymmdd)
		result, e := core.SearchUri(logstashIndex, "", query, "1m", 1000)
		if e != nil {
			return fmt.Errorf("failed to search elasticsearch: %s", e)
		}
		//TODO: Submit a patch to elastigo to support the "clear scroll" api. Add a "defer" here.
		remaining := result.Hits.Total > 0
		for remaining {
			result, e = core.Scroll(false, result.ScrollId, "1m")
			hits := result.Hits.Hits
			total := len(hits)
			for i := 0; i < total; i++ {
				host, logfile, compactLines, e := parseLogSource(hits[i].Source)
				if e != nil {
					return e
				}
				if _, found := fileIndex[host]; !found {
					fileIndex[host] = make(map[string]int)
				}
				if _, found := fileIndex[host][logfile]; !found {
					index := len(files)
					filename := filepath.Join(tempdir, fmt.Sprintf("%03d.log", index))
					file, e := os.Create(filename)
					if e != nil {
						return fmt.Errorf("failed to create file %s: %s", filename, e)
					}
					defer func() {
						if e := file.Close(); e != nil && err == nil {
							err = fmt.Errorf("failed to close file '%s' cleanly: %s", filename, e)
						}
					}()
					fileIndex[host][logfile] = index
					files = append(files, file)
				}
				index := fileIndex[host][logfile]
				file := files[index]
				filename := filepath.Join(tempdir, fmt.Sprintf("%03d.log", index))
				for _, line := range compactLines {
					formatted := fmt.Sprintf("%016x\t%016x\t%s\n", line.Timestamp, line.Offset, line.Message)
					if _, e := file.WriteString(formatted); e != nil {
						return fmt.Errorf("failed writing to file %s: %s", filename, e)
					}
				}
			}
			remaining = len(hits) > 0
		}
	}
	if !foundIndexedDay {
		return fmt.Errorf("no logstash indexes exist for the given date range %s - %s", from, to)
	}

	indexData := []string{}
	for host, logfileIndex := range fileIndex {
		for logfile, i := range logfileIndex {
			filename := filepath.Join(tempdir, fmt.Sprintf("%03d.log", i))
			cmd := exec.Command("sort", filename, "-o", filename)
			if output, e := cmd.CombinedOutput(); e != nil {
				return fmt.Errorf("failed sorting %s, error: %v, output: %s", filename, e, output)
			}
			cmd = exec.Command("sed", "s/^[0-9a-f]*\\t[0-9a-f]*\\t//", "-i", filename)
			if output, e := cmd.CombinedOutput(); e != nil {
				return fmt.Errorf("failed stripping sort prefixes from %s, error: %v, output: %s", filename, e, output)
			}
			indexData = append(indexData, fmt.Sprintf("%03d.log\t%s\t%s", i, strconv.Quote(host), strconv.Quote(logfile)))
		}
	}
	sort.Strings(indexData)
	indexData = append([]string{"INDEX OF LOG FILES", "File\tHost\tOriginal Filename"}, indexData...)
	indexData = append(indexData, "")
	indexFile := filepath.Join(tempdir, "index.txt")
	e = ioutil.WriteFile(indexFile, []byte(strings.Join(indexData, "\n")), 0644)
	if e != nil {
		return fmt.Errorf("failed writing to %s: %s", indexFile, e)
	}

	cmd := exec.Command("tar", "-czf", outfile, "-C", filepath.Dir(tempdir), filepath.Base(tempdir))
	if output, e := cmd.CombinedOutput(); e != nil {
		return fmt.Errorf("failed to write tgz cmd:%+v, error:%v, output:%s", cmd, e, string(output))
	}
	return nil
}

type logLine struct {
	Host      string    `json:"host"`
	File      string    `json:"file"`
	Timestamp time.Time `json:"@timestamp"`
	Offset    string    `json:"offset"`
	Message   string    `json:"message"`
}

type logMultiLine struct {
	Host      string    `json:"host"`
	File      string    `json:"file"`
	Timestamp time.Time `json:"@timestamp"`
	Offset    []string  `json:"offset"`
	Message   string    `json:"message"`
}

type compactLogLine struct {
	Timestamp int64 //nanoseconds since the epoch, truncated at the minute to hide jitter
	Offset    int64
	Message   string
}

var newline = regexp.MustCompile("\\r?\\n")

// return: host, file, lines, error
func parseLogSource(source []byte) (string, string, []compactLogLine, error) {

	var line logLine
	if e := json.Unmarshal(source, &line); e == nil {
		offset, e := strconv.ParseInt(line.Offset, 10, 64)
		if e != nil {
			return "", "", nil, fmt.Errorf("failed to parse offset \"%s\" in \"%s\": %s", line.Offset, source, e)
		}
		compactLine := compactLogLine{
			Timestamp: truncateToMinute(line.Timestamp.UnixNano()),
			Offset:    offset,
			Message:   line.Message,
		}
		return line.Host, line.File, []compactLogLine{compactLine}, nil
	}
	var multiLine logMultiLine
	if e := json.Unmarshal(source, &multiLine); e != nil {
		return "", "", nil, fmt.Errorf("failed to parse JSON \"%s\": %s", source, e)
	}
	messages := newline.Split(multiLine.Message, -1)
	if len(messages) != len(multiLine.Offset) {
		return "", "", nil, fmt.Errorf("offsets do not correspond with lines: %s", source)
	}
	timestamp := truncateToMinute(multiLine.Timestamp.UnixNano())
	compactLines := make([]compactLogLine, len(messages))
	for i, offsetString := range multiLine.Offset {
		offset, e := strconv.ParseInt(offsetString, 10, 64)
		if e != nil {
			return "", "", nil, fmt.Errorf("failed to parse offset \"%s\" in \"%s\": %s", offsetString, source, e)
		}
		compactLines = append(compactLines, compactLogLine{
			Timestamp: timestamp,
			Offset:    offset,
			Message:   messages[i],
		})
	}
	return multiLine.Host, multiLine.File, compactLines, nil
}

// NormalizeYYYYMMDD matches optional non-digits, 4 digits, optional non-digits,
// 2 digits, optional non-digits, 2 digits, optional non-digits
// Returns those 8 digits formatted as "dddd.dd.dd", or error if unparseable.
func NormalizeYYYYMMDD(s string) (string, error) {
	match := yyyymmddMatcher.FindStringSubmatch(s)
	if match == nil {
		return "", fmt.Errorf("could not parse '%s' as yyyymmdd", s)
	}
	return fmt.Sprintf("%s.%s.%s", match[1], match[2], match[3]), nil
}

var yyyymmddMatcher = regexp.MustCompile("\\A[^0-9]*([0-9]{4})[^0-9]*([0-9]{2})[^0-9]*([0-9]{2})[^0-9]*\\z")

// Returns a list of all the dates with a logstash-YYYY.MM.DD index available in ElasticSearch.
// The strings are in YYYY.MM.DD format, and in reverse chronological order.
var LogstashDays = func() ([]string, error) {
	response, e := elastigo.DoCommand("GET", "/_aliases", nil)
	if e != nil {
		return []string{}, fmt.Errorf("couldn't fetch list of indices: %s", e)
	}
	var aliasMap map[string]interface{}
	if e = json.Unmarshal(response, &aliasMap); e != nil {
		return []string{}, fmt.Errorf("couldn't parse response (%s): %s", response, e)
	}
	result := make([]string, 0, len(aliasMap))
	for index := range aliasMap {
		if trimmed := strings.TrimPrefix(index, "logstash-"); trimmed != index {
			if trimmed, e = NormalizeYYYYMMDD(trimmed); e != nil {
				trimmed = ""
			}
			result = append(result, trimmed)
		}
	}
	sort.Sort(sort.Reverse(sort.StringSlice(result)))
	return result, nil
}

func truncateToMinute(nanos int64) int64 {
	return nanos / int64(time.Minute) * int64(time.Minute)
}
<|endoftext|>


<commit_before>// Package cli implements a command line client for Inago. Cobra CLI
// is used as framework.
package cli

import (
	"net/url"
	"time"

	"github.com/spf13/cobra"
	"golang.org/x/net/context"

	"github.com/giantswarm/inago/controller"
	"github.com/giantswarm/inago/file-system/real"
	"github.com/giantswarm/inago/file-system/spec"
	"github.com/giantswarm/inago/fleet"
	"github.com/giantswarm/inago/logging"
	"github.com/giantswarm/inago/task"
)

var (
	globalFlags struct {
		FleetEndpoint string
		NoBlock       bool
		Verbose       bool

		Tunnel                   string
		SSHUsername              string
		SSHTimeout               time.Duration
		SSHStrictHostKeyChecking bool
		SSHKnownHostsFile        string
	}

	fs             filesystemspec.FileSystem
	newLogger      logging.Logger
	newFleet       fleet.Fleet
	newTaskService task.Service
	newController  controller.Controller

	newCtx context.Context

	// MainCmd contains the cobra.Command to execute inagoctl.
	MainCmd = &cobra.Command{
		Use:   "inagoctl",
		Short: "Inago orchestrates groups of units on Fleet clusters",
		Long:  "Inago orchestrates groups of units on Fleet clusters",
		Run:   mainRun,
		PersistentPreRun: func(cmd *cobra.Command, args []string) {
			// This callback is executed after flags are parsed and before any
			// command runs.
			fs = filesystemreal.NewFileSystem()

			loggingConfig := logging.DefaultConfig()
			if globalFlags.Verbose {
				loggingConfig.LogLevel = "DEBUG"
			}
			newLogger = logging.NewLogger(loggingConfig)

			URL, err := url.Parse(globalFlags.FleetEndpoint)
			if err != nil {
				panic(err)
			}

			newFleetConfig := fleet.DefaultConfig()
			newFleetConfig.Endpoint = *URL
			newFleetConfig.Logger = newLogger
			newSSHTunnelConfig := fleet.DefaultSSHTunnelConfig()
			newSSHTunnelConfig.Endpoint = *URL
			newSSHTunnelConfig.KnownHostsFile = globalFlags.SSHKnownHostsFile
			newSSHTunnelConfig.Logger = newLogger
			newSSHTunnelConfig.StrictHostKeyChecking = globalFlags.SSHStrictHostKeyChecking
			newSSHTunnelConfig.Timeout = globalFlags.SSHTimeout
			newSSHTunnelConfig.Tunnel = globalFlags.Tunnel
			newSSHTunnelConfig.Username = globalFlags.SSHUsername
			newSSHTunnel, err := fleet.NewSSHTunnel(newSSHTunnelConfig)
			if err != nil {
				panic(err)
			}
			newFleetConfig.SSHTunnel = newSSHTunnel
			newFleet, err = fleet.NewFleet(newFleetConfig)
			if err != nil {
				panic(err)
			}

			newTaskServiceConfig := task.DefaultConfig()
			newTaskServiceConfig.Logger = newLogger
			newTaskService = task.NewTaskService(newTaskServiceConfig)

			newControllerConfig := controller.DefaultConfig()
			newControllerConfig.Logger = newLogger
			newControllerConfig.Fleet = newFleet
			newControllerConfig.TaskService = newTaskService

			newController = controller.NewController(newControllerConfig)

			newCtx = context.Background()
		},
	}
)

func init() {
	MainCmd.PersistentFlags().StringVar(&globalFlags.FleetEndpoint, "fleet-endpoint", "unix:///var/run/fleet.sock", "endpoint used to connect to fleet")
	MainCmd.PersistentFlags().BoolVar(&globalFlags.NoBlock, "no-block", false, "block on synchronous actions")
	MainCmd.PersistentFlags().BoolVarP(&globalFlags.Verbose, "verbose", "v", false, "verbose output")

	MainCmd.PersistentFlags().StringVar(&globalFlags.Tunnel, "tunnel", "", "use a tunnel to communicate with fleet")
	MainCmd.PersistentFlags().StringVar(&globalFlags.SSHUsername, "ssh-username", "core", "username to use when connecting to CoreOS machine")
	MainCmd.PersistentFlags().DurationVar(&globalFlags.SSHTimeout, "ssh-timeout", time.Duration(10*time.Second), "timeout in seconds when establishing the connection via SSH")
	MainCmd.PersistentFlags().BoolVar(&globalFlags.SSHStrictHostKeyChecking, "ssh-strict-host-key-checking", true, "verify host keys presented by remote machines before initiating SSH connections")
	MainCmd.PersistentFlags().StringVar(&globalFlags.SSHKnownHostsFile, "ssh-known-hosts-file", "~/.fleetctl/known_hosts", "file used to store remote machine fingerprints")

	MainCmd.AddCommand(submitCmd)
	MainCmd.AddCommand(statusCmd)
	MainCmd.AddCommand(startCmd)
	MainCmd.AddCommand(stopCmd)
	MainCmd.AddCommand(destroyCmd)
	MainCmd.AddCommand(upCmd)
	MainCmd.AddCommand(updateCmd)
	MainCmd.AddCommand(validateCmd)
	MainCmd.AddCommand(versionCmd)
}

func mainRun(cmd *cobra.Command, args []string) {
	cmd.Help()
}
<commit_msg>fixed tunnel initialization<commit_after>// Package cli implements a command line client for Inago. Cobra CLI
// is used as framework.
package cli

import (
	"net/url"
	"time"

	"github.com/spf13/cobra"
	"golang.org/x/net/context"

	"github.com/giantswarm/inago/controller"
	"github.com/giantswarm/inago/file-system/real"
	"github.com/giantswarm/inago/file-system/spec"
	"github.com/giantswarm/inago/fleet"
	"github.com/giantswarm/inago/logging"
	"github.com/giantswarm/inago/task"
)

var (
	globalFlags struct {
		FleetEndpoint string
		NoBlock       bool
		Verbose       bool

		Tunnel                   string
		SSHUsername              string
		SSHTimeout               time.Duration
		SSHStrictHostKeyChecking bool
		SSHKnownHostsFile        string
	}

	fs             filesystemspec.FileSystem
	newLogger      logging.Logger
	newFleet       fleet.Fleet
	newTaskService task.Service
	newController  controller.Controller

	newCtx context.Context

	// MainCmd contains the cobra.Command to execute inagoctl.
	MainCmd = &cobra.Command{
		Use:   "inagoctl",
		Short: "Inago orchestrates groups of units on Fleet clusters",
		Long:  "Inago orchestrates groups of units on Fleet clusters",
		Run:   mainRun,
		PersistentPreRun: func(cmd *cobra.Command, args []string) {
			// This callback is executed after flags are parsed and before any
			// command runs.
			fs = filesystemreal.NewFileSystem()

			loggingConfig := logging.DefaultConfig()
			if globalFlags.Verbose {
				loggingConfig.LogLevel = "DEBUG"
			}
			newLogger = logging.NewLogger(loggingConfig)

			URL, err := url.Parse(globalFlags.FleetEndpoint)
			if err != nil {
				panic(err)
			}

			newFleetConfig := fleet.DefaultConfig()
			newFleetConfig.Endpoint = *URL
			newFleetConfig.Logger = newLogger
			if globalFlags.Tunnel != "" {
				newSSHTunnelConfig := fleet.DefaultSSHTunnelConfig()
				newSSHTunnelConfig.Endpoint = *URL
				newSSHTunnelConfig.KnownHostsFile = globalFlags.SSHKnownHostsFile
				newSSHTunnelConfig.Logger = newLogger
				newSSHTunnelConfig.StrictHostKeyChecking = globalFlags.SSHStrictHostKeyChecking
				newSSHTunnelConfig.Timeout = globalFlags.SSHTimeout
				newSSHTunnelConfig.Tunnel = globalFlags.Tunnel
				newSSHTunnelConfig.Username = globalFlags.SSHUsername
				newSSHTunnel, err := fleet.NewSSHTunnel(newSSHTunnelConfig)
				if err != nil {
					panic(err)
				}
				newFleetConfig.SSHTunnel = newSSHTunnel
			}
			newFleet, err = fleet.NewFleet(newFleetConfig)
			if err != nil {
				panic(err)
			}

			newTaskServiceConfig := task.DefaultConfig()
			newTaskServiceConfig.Logger = newLogger
			newTaskService = task.NewTaskService(newTaskServiceConfig)

			newControllerConfig := controller.DefaultConfig()
			newControllerConfig.Logger = newLogger
			newControllerConfig.Fleet = newFleet
			newControllerConfig.TaskService = newTaskService

			newController = controller.NewController(newControllerConfig)

			newCtx = context.Background()
		},
	}
)

func init() {
	MainCmd.PersistentFlags().StringVar(&globalFlags.FleetEndpoint, "fleet-endpoint", "unix:///var/run/fleet.sock", "endpoint used to connect to fleet")
	MainCmd.PersistentFlags().BoolVar(&globalFlags.NoBlock, "no-block", false, "block on synchronous actions")
	MainCmd.PersistentFlags().BoolVarP(&globalFlags.Verbose, "verbose", "v", false, "verbose output")

	MainCmd.PersistentFlags().StringVar(&globalFlags.Tunnel, "tunnel", "", "use a tunnel to communicate with fleet")
	MainCmd.PersistentFlags().StringVar(&globalFlags.SSHUsername, "ssh-username", "core", "username to use when connecting to CoreOS machine")
	MainCmd.PersistentFlags().DurationVar(&globalFlags.SSHTimeout, "ssh-timeout", time.Duration(10*time.Second), "timeout in seconds when establishing the connection via SSH")
	MainCmd.PersistentFlags().BoolVar(&globalFlags.SSHStrictHostKeyChecking, "ssh-strict-host-key-checking", true, "verify host keys presented by remote machines before initiating SSH connections")
	MainCmd.PersistentFlags().StringVar(&globalFlags.SSHKnownHostsFile, "ssh-known-hosts-file", "~/.fleetctl/known_hosts", "file used to store remote machine fingerprints")

	MainCmd.AddCommand(submitCmd)
	MainCmd.AddCommand(statusCmd)
	MainCmd.AddCommand(startCmd)
	MainCmd.AddCommand(stopCmd)
	MainCmd.AddCommand(destroyCmd)
	MainCmd.AddCommand(upCmd)
	MainCmd.AddCommand(updateCmd)
	MainCmd.AddCommand(validateCmd)
	MainCmd.AddCommand(versionCmd)
}

func mainRun(cmd *cobra.Command, args []string) {
	cmd.Help()
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"

	"github.com/urfave/cli"
)

func main() {
	app := cli.NewApp()
	app.Name = "gitpods-api"
	app.Usage = "git in the cloud!"

	app.Action = apiAction
	app.Flags = apiFlags

	app.Commands = []cli.Command{{
		Name:  "db",
		Usage: "Run actions on the database",
		Subcommands: []cli.Command{{
			Name:   "migrate",
			Flags:  dbFlags,
			Action: dbMigrateAction,
		}, {
			Name:   "reset",
			Flags:  dbFlags,
			Action: dbResetAction,
		}},
	}, {
		Name:  "users",
		Usage: "Manage users",
		Flags: usersFlags,
		Subcommands: []cli.Command{{
			Name:   "create",
			Action: usersCreateAction,
			Flags: []cli.Flag{
				cli.StringFlag{Name: usersEmail},
				cli.StringFlag{Name: usersUsername},
				cli.StringFlag{Name: usersName},
				cli.StringFlag{Name: usersPassword},
			},
		}},
	}}

	if err := app.Run(os.Args); err != nil {
		log.Fatal(err)
	}
}
<commit_msg>cmd/api: Rename binary name<commit_after>package main

import (
	"log"
	"os"

	"github.com/urfave/cli"
)

func main() {
	app := cli.NewApp()
	app.Name = "sourcepods-api"
	app.Usage = "git in the cloud!"

	app.Action = apiAction
	app.Flags = apiFlags

	app.Commands = []cli.Command{{
		Name:  "db",
		Usage: "Run actions on the database",
		Subcommands: []cli.Command{{
			Name:   "migrate",
			Flags:  dbFlags,
			Action: dbMigrateAction,
		}, {
			Name:   "reset",
			Flags:  dbFlags,
			Action: dbResetAction,
		}},
	}, {
		Name:  "users",
		Usage: "Manage users",
		Flags: usersFlags,
		Subcommands: []cli.Command{{
			Name:   "create",
			Action: usersCreateAction,
			Flags: []cli.Flag{
				cli.StringFlag{Name: usersEmail},
				cli.StringFlag{Name: usersUsername},
				cli.StringFlag{Name: usersName},
				cli.StringFlag{Name: usersPassword},
			},
		}},
	}}

	if err := app.Run(os.Args); err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"os/user"
	"path"
	"runtime"
	"strings"
	"time"

	"github.com/pkg/errors"

	"gopkg.in/yaml.v2"
)

type app struct {
	filenameWithPath string
	kubectlUser      string
	kubeloginAlias   string
	kubeloginServer  string
}

var (
	aliasFlag              string
	userFlag               string
	kubeloginServerBaseURL string
	doneChannel            chan bool
	usageMessage           = `Kubelogin Usage:

  One time login:
    kubelogin login --server-url=https://kubelogin.example.com --kubectl-user=user

  Configure an alias (shortcut):
    kubelogin config --alias=example --server-url=https://kubelogin.example.com --kubectl-user=example_oidc

  Use an alias:
    kubelogin login example`
)

//AliasConfig contains the structure of what's in the config file
type AliasConfig struct {
	Alias       string `yaml:"alias"`
	BaseURL     string `yaml:"server-url"`
	KubectlUser string `yaml:"kubectl-user"`
}

type Config struct {
	Aliases []*AliasConfig `yaml:"aliases"`
}

func findFreePort() (string, error) {
	server, err := net.Listen("tcp", ":0")
	if err != nil {
		return "", err
	}
	defer server.Close()
	hostString := server.Addr().String()
	_, portString, err := net.SplitHostPort(hostString)
	if err != nil {
		return "", err
	}
	return portString, nil
}

func (app *app) makeExchange(token string) error {
	url := fmt.Sprintf("%s/exchange?token=%s", app.kubeloginServer, token)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Printf("Unable to create request. %s", err)
		return err
	}
	client := http.DefaultClient
	res, err := client.Do(req)
	if err != nil {
		log.Printf("Unable to make request. %s", err)
		return err
	}
	if res.StatusCode != http.StatusOK {
		log.Fatalf("Failed to retrieve token from kubelogin server. Please try again or contact your administrator")
	}
	defer res.Body.Close()
	jwt, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Printf("Unable to read response body. %s", err)
		return err
	}
	if err := app.configureKubectl(string(jwt)); err != nil {
		log.Printf("Error when setting credentials: %v", err)
		return err
	}
	return nil
}

func (app *app) tokenHandler(w http.ResponseWriter, r *http.Request) {
	token := r.FormValue("token")
	if err := app.makeExchange(token); err != nil {
		log.Fatalf("Could not exchange token for jwt %v", err)
	}
	fmt.Fprint(w, "You are now logged in! You can close this window")
	doneChannel <- true
}

func (app *app) configureKubectl(jwt string) error {
	configCmd := exec.Command("kubectl", "config", "set-credentials", app.kubectlUser, "--token="+jwt)
	if err := configCmd.Run(); err != nil {
		return err
	}
	return nil
}

func (app *app) generateAuthURL() (string, string, error) {
	portNum, err := findFreePort()
	if err != nil {
		log.Print("err, could not find an open port")
		return "", "", err
	}

	loginURL := fmt.Sprintf("%s/login?port=%s", app.kubeloginServer, portNum)

	return loginURL, portNum, nil
}

func createMux(app app) *http.ServeMux {
	newMux := http.NewServeMux()
	newMux.HandleFunc("/exchange/", app.tokenHandler)
	newMux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(""))
		return
	})
	return newMux
}

func generateURLAndListenForServerResponse(app app) {
	loginURL, portNum, err := app.generateAuthURL()
	if err != nil {
		log.Fatal(err.Error())
	}
	doneChannel = make(chan bool)
	go func() {
		l, err := net.Listen("tcp", ":"+portNum)
		if err != nil {
			fmt.Printf("Error listening on port: %s. Error: %v\n", portNum, err)
			os.Exit(1)
		}
		if runtime.GOOS == "darwin" {
			// On OS X, run the `open` CLI to use the default browser to open the login URL.
			fmt.Printf("Opening %s ...\n", loginURL)
			err := exec.Command("open", loginURL).Run()
			if err != nil {
				fmt.Printf("Error opening; please open the URL manually.\n", loginURL)
			}
		} else {
			fmt.Printf("Follow this URL to log into auth provider: %s\n", loginURL)
		}
		if err = http.Serve(l, createMux(app)); err != nil {
			fmt.Printf("Error listening on port: %s. Error: %v\n", portNum, err)
			os.Exit(1)
		}
	}()
	<-doneChannel
	fmt.Println("You are now logged in! Enjoy kubectl-ing!")
	time.Sleep(1 * time.Second)
}

func setFlags(command *flag.FlagSet, loginCmd bool) {
	if !loginCmd {
		command.StringVar(&aliasFlag, "alias", "default", "alias name in the config file, used for an easy login")
	}
	command.StringVar(&userFlag, "kubectl-user", "kubelogin_user", "in kubectl config, username used to store credentials")
	command.StringVar(&kubeloginServerBaseURL, "server-url", "", "base URL of the kubelogin server, ex: https://kubelogin.example.com")
}
func (app *app) getConfigSettings(alias string) error {
	yamlFile, err := ioutil.ReadFile(app.filenameWithPath)
	if err != nil {
		return errors.Wrap(err, "failed to read config file for login use")
	}
	var config Config
	if err := yaml.Unmarshal(yamlFile, &config); err != nil {
		return errors.Wrap(err, "failed to unmarshal yaml file for login use")
	}

	aliasConfig, ok := config.aliasSearch(alias)
	if !ok {
		return errors.New("Could not find specified alias, check spelling or use the config verb to create an alias")
	}
	app.kubectlUser = aliasConfig.KubectlUser
	app.kubeloginServer = aliasConfig.BaseURL
	return nil
}

func (config *Config) aliasSearch(alias string) (*AliasConfig, bool) {
	for index, aliases := range config.Aliases {
		if alias == aliases.Alias {
			return config.Aliases[index], true
		}
	}
	return nil, false
}

func (config *Config) createConfig(onDiskFile string, aliasConfig AliasConfig) error {
	log.Print("Couldn't find config file in root directory. Creating config file...")
	createCmd := exec.Command("touch", onDiskFile)
	if err := createCmd.Run(); err != nil {
		return errors.Wrap(err, "failed to create file in root directory")
	}
	log.Print("Config file created, setting config values...")
	config.Aliases = make([]*AliasConfig, 0)
	config.appendAlias(aliasConfig)
	if err := config.writeToFile(onDiskFile); err != nil {
		log.Fatal(err)
	}
	log.Print("File configured")
	return nil
}

func (config *Config) newAliasConfig(kubeloginrcAlias, loginServerURL, kubectlUser string) AliasConfig {
	newConfig := AliasConfig{
		BaseURL:     loginServerURL,
		Alias:       kubeloginrcAlias,
		KubectlUser: kubectlUser,
	}
	return newConfig
}

func (config *Config) appendAlias(aliasConfig AliasConfig) {
	config.Aliases = append(config.Aliases, &aliasConfig)
}

func (config *Config) writeToFile(onDiskFile string) error {
	marshaledYaml, err := yaml.Marshal(config)
	if err != nil {
		return errors.Wrap(err, "failed to marshal alias yaml")
	}
	if err := ioutil.WriteFile(onDiskFile, marshaledYaml, 0600); err != nil {
		return errors.Wrap(err, "failed to write to kubeloginrc file with the alias")
	}
	log.Printf(string(marshaledYaml))
	return nil
}

func (config *Config) updateAlias(aliasConfig *AliasConfig, loginServerURL *url.URL, onDiskFile string) error {
	aliasConfig.KubectlUser = userFlag
	aliasConfig.BaseURL = loginServerURL.String()
	if err := config.writeToFile(onDiskFile); err != nil {
		log.Fatal(err)
	}
	log.Print("Alias updated")
	return nil
}

func (app *app) configureFile(kubeloginrcAlias string, loginServerURL *url.URL, kubectlUser string) error {
	var config Config
	aliasConfig := config.newAliasConfig(kubeloginrcAlias, loginServerURL.String(), kubectlUser)
	yamlFile, err := ioutil.ReadFile(app.filenameWithPath)
	if err != nil {
		if err := config.createConfig(app.filenameWithPath, aliasConfig); err != nil {
			return err
		}
		return nil
	}
	if err := yaml.Unmarshal(yamlFile, &config); err != nil {
		return errors.Wrap(err, "failed to unmarshal yaml file")
	}
	foundAliasConfig, ok := config.aliasSearch(aliasFlag)
	if !ok {
		newConfig := config.newAliasConfig(kubeloginrcAlias, loginServerURL.String(), kubectlUser)
		config.appendAlias(newConfig)
		if err := config.writeToFile(app.filenameWithPath); err != nil {
			log.Fatal(err)
		}
		log.Print("New Alias configured")
		return nil
	}
	if err := config.updateAlias(foundAliasConfig, loginServerURL, app.filenameWithPath); err != nil {
		return err
	}
	return nil
}

func main() {
	var app app
	loginCommmand := flag.NewFlagSet("login", flag.ExitOnError)
	setFlags(loginCommmand, true)
	configCommand := flag.NewFlagSet("config", flag.ExitOnError)
	setFlags(configCommand, false)
	user, err := user.Current()
	if err != nil {
		log.Fatalf("Could not determine current user of this system. Err: %v", err)
	}
	app.filenameWithPath = path.Join(user.HomeDir, "/.kubeloginrc.yaml")
	if len(os.Args) < 3 {
		fmt.Println(usageMessage)
		os.Exit(1)
	}
	switch os.Args[1] {
	case "login":
		if !strings.HasPrefix(os.Args[2], "--") {
			//use alias to extract needed information
			if err := app.getConfigSettings(os.Args[2]); err != nil {
				log.Fatal(err)
			}
			generateURLAndListenForServerResponse(app)
		} else {
			loginCommmand.Parse(os.Args[2:])
			if loginCommmand.Parsed() {
				if kubeloginServerBaseURL == "" {
					log.Fatal("--server-url must be set!")
				}
				app.kubectlUser = userFlag
				app.kubeloginServer = kubeloginServerBaseURL
				generateURLAndListenForServerResponse(app)
			}
		}
	case "config":
		configCommand.Parse(os.Args[2:])
		if configCommand.Parsed() {
			if kubeloginServerBaseURL == "" {
				log.Fatal("--server-url must be set!")
			}
			verifiedServerURL, err := url.ParseRequestURI(kubeloginServerBaseURL)
			if err != nil {
				log.Fatalf("Invalid URL given: %v | Err: %v", kubeloginServerBaseURL, err)
			}

			if err := app.configureFile(aliasFlag, verifiedServerURL, userFlag); err != nil {
				log.Fatal(err)
			}
			os.Exit(0)
		}
	default:
		fmt.Println(usageMessage)
		os.Exit(1)
	}
}
<commit_msg>Replace touch command with go functions<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"os/user"
	"path"
	"runtime"
	"strings"
	"time"

	"github.com/pkg/errors"

	"gopkg.in/yaml.v2"
)

type app struct {
	filenameWithPath string
	kubectlUser      string
	kubeloginAlias   string
	kubeloginServer  string
}

var (
	aliasFlag              string
	userFlag               string
	kubeloginServerBaseURL string
	doneChannel            chan bool
	usageMessage           = `Kubelogin Usage:

  One time login:
    kubelogin login --server-url=https://kubelogin.example.com --kubectl-user=user

  Configure an alias (shortcut):
    kubelogin config --alias=example --server-url=https://kubelogin.example.com --kubectl-user=example_oidc

  Use an alias:
    kubelogin login example`
)

//AliasConfig contains the structure of what's in the config file
type AliasConfig struct {
	Alias       string `yaml:"alias"`
	BaseURL     string `yaml:"server-url"`
	KubectlUser string `yaml:"kubectl-user"`
}

type Config struct {
	Aliases []*AliasConfig `yaml:"aliases"`
}

func findFreePort() (string, error) {
	server, err := net.Listen("tcp", ":0")
	if err != nil {
		return "", err
	}
	defer server.Close()
	hostString := server.Addr().String()
	_, portString, err := net.SplitHostPort(hostString)
	if err != nil {
		return "", err
	}
	return portString, nil
}

func (app *app) makeExchange(token string) error {
	url := fmt.Sprintf("%s/exchange?token=%s", app.kubeloginServer, token)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Printf("Unable to create request. %s", err)
		return err
	}
	client := http.DefaultClient
	res, err := client.Do(req)
	if err != nil {
		log.Printf("Unable to make request. %s", err)
		return err
	}
	if res.StatusCode != http.StatusOK {
		log.Fatalf("Failed to retrieve token from kubelogin server. Please try again or contact your administrator")
	}
	defer res.Body.Close()
	jwt, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Printf("Unable to read response body. %s", err)
		return err
	}
	if err := app.configureKubectl(string(jwt)); err != nil {
		log.Printf("Error when setting credentials: %v", err)
		return err
	}
	return nil
}

func (app *app) tokenHandler(w http.ResponseWriter, r *http.Request) {
	token := r.FormValue("token")
	if err := app.makeExchange(token); err != nil {
		log.Fatalf("Could not exchange token for jwt %v", err)
	}
	fmt.Fprint(w, "You are now logged in! You can close this window")
	doneChannel <- true
}

func (app *app) configureKubectl(jwt string) error {
	configCmd := exec.Command("kubectl", "config", "set-credentials", app.kubectlUser, "--token="+jwt)
	if err := configCmd.Run(); err != nil {
		return err
	}
	return nil
}

func (app *app) generateAuthURL() (string, string, error) {
	portNum, err := findFreePort()
	if err != nil {
		log.Print("err, could not find an open port")
		return "", "", err
	}

	loginURL := fmt.Sprintf("%s/login?port=%s", app.kubeloginServer, portNum)

	return loginURL, portNum, nil
}

func createMux(app app) *http.ServeMux {
	newMux := http.NewServeMux()
	newMux.HandleFunc("/exchange/", app.tokenHandler)
	newMux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(""))
		return
	})
	return newMux
}

func generateURLAndListenForServerResponse(app app) {
	loginURL, portNum, err := app.generateAuthURL()
	if err != nil {
		log.Fatal(err.Error())
	}
	doneChannel = make(chan bool)
	go func() {
		l, err := net.Listen("tcp", ":"+portNum)
		if err != nil {
			fmt.Printf("Error listening on port: %s. Error: %v\n", portNum, err)
			os.Exit(1)
		}
		if runtime.GOOS == "darwin" {
			// On OS X, run the `open` CLI to use the default browser to open the login URL.
			fmt.Printf("Opening %s ...\n", loginURL)
			err := exec.Command("open", loginURL).Run()
			if err != nil {
				fmt.Printf("Error opening; please open the URL manually.\n", loginURL)
			}
		} else {
			fmt.Printf("Follow this URL to log into auth provider: %s\n", loginURL)
		}
		if err = http.Serve(l, createMux(app)); err != nil {
			fmt.Printf("Error listening on port: %s. Error: %v\n", portNum, err)
			os.Exit(1)
		}
	}()
	<-doneChannel
	fmt.Println("You are now logged in! Enjoy kubectl-ing!")
	time.Sleep(1 * time.Second)
}

func setFlags(command *flag.FlagSet, loginCmd bool) {
	if !loginCmd {
		command.StringVar(&aliasFlag, "alias", "default", "alias name in the config file, used for an easy login")
	}
	command.StringVar(&userFlag, "kubectl-user", "kubelogin_user", "in kubectl config, username used to store credentials")
	command.StringVar(&kubeloginServerBaseURL, "server-url", "", "base URL of the kubelogin server, ex: https://kubelogin.example.com")
}
func (app *app) getConfigSettings(alias string) error {
	yamlFile, err := ioutil.ReadFile(app.filenameWithPath)
	if err != nil {
		return errors.Wrap(err, "failed to read config file for login use")
	}
	var config Config
	if err := yaml.Unmarshal(yamlFile, &config); err != nil {
		return errors.Wrap(err, "failed to unmarshal yaml file for login use")
	}

	aliasConfig, ok := config.aliasSearch(alias)
	if !ok {
		return errors.New("Could not find specified alias, check spelling or use the config verb to create an alias")
	}
	app.kubectlUser = aliasConfig.KubectlUser
	app.kubeloginServer = aliasConfig.BaseURL
	return nil
}

func (config *Config) aliasSearch(alias string) (*AliasConfig, bool) {
	for index, aliases := range config.Aliases {
		if alias == aliases.Alias {
			return config.Aliases[index], true
		}
	}
	return nil, false
}

func (config *Config) createConfig(onDiskFile string, aliasConfig AliasConfig) error {
	log.Print("Couldn't find config file in root directory. Creating config file...")
	// Does file exist? Update mtime, else create file
	_, e := os.Stat(onDiskFile)
	if os.IsNotExist(e) { // Create file
		fh, err := os.Create(onDiskFile)
		if err != nil {
			return errors.Wrap(err, "failed to create file in root directory")
		}
		fh.Close()
	} else { //File Exists
		err := os.Chtimes(onDiskFile, time.Now(), time.Now())
		if err != nil {
			log.Print("Config file exists, but could not update file access times. Insufficient permissions?")
		}
	}

	log.Print("Config file created, setting config values...")
	config.Aliases = make([]*AliasConfig, 0)
	config.appendAlias(aliasConfig)
	if err := config.writeToFile(onDiskFile); err != nil {
		log.Fatal(err)
	}
	log.Print("File configured")
	return nil
}

func (config *Config) newAliasConfig(kubeloginrcAlias, loginServerURL, kubectlUser string) AliasConfig {
	newConfig := AliasConfig{
		BaseURL:     loginServerURL,
		Alias:       kubeloginrcAlias,
		KubectlUser: kubectlUser,
	}
	return newConfig
}

func (config *Config) appendAlias(aliasConfig AliasConfig) {
	config.Aliases = append(config.Aliases, &aliasConfig)
}

func (config *Config) writeToFile(onDiskFile string) error {
	marshaledYaml, err := yaml.Marshal(config)
	if err != nil {
		return errors.Wrap(err, "failed to marshal alias yaml")
	}
	if err := ioutil.WriteFile(onDiskFile, marshaledYaml, 0600); err != nil {
		return errors.Wrap(err, "failed to write to kubeloginrc file with the alias")
	}
	log.Printf(string(marshaledYaml))
	return nil
}

func (config *Config) updateAlias(aliasConfig *AliasConfig, loginServerURL *url.URL, onDiskFile string) error {
	aliasConfig.KubectlUser = userFlag
	aliasConfig.BaseURL = loginServerURL.String()
	if err := config.writeToFile(onDiskFile); err != nil {
		log.Fatal(err)
	}
	log.Print("Alias updated")
	return nil
}

func (app *app) configureFile(kubeloginrcAlias string, loginServerURL *url.URL, kubectlUser string) error {
	var config Config
	aliasConfig := config.newAliasConfig(kubeloginrcAlias, loginServerURL.String(), kubectlUser)
	yamlFile, err := ioutil.ReadFile(app.filenameWithPath)
	if err != nil {
		if err := config.createConfig(app.filenameWithPath, aliasConfig); err != nil {
			return err
		}
		return nil
	}
	if err := yaml.Unmarshal(yamlFile, &config); err != nil {
		return errors.Wrap(err, "failed to unmarshal yaml file")
	}
	foundAliasConfig, ok := config.aliasSearch(aliasFlag)
	if !ok {
		newConfig := config.newAliasConfig(kubeloginrcAlias, loginServerURL.String(), kubectlUser)
		config.appendAlias(newConfig)
		if err := config.writeToFile(app.filenameWithPath); err != nil {
			log.Fatal(err)
		}
		log.Print("New Alias configured")
		return nil
	}
	if err := config.updateAlias(foundAliasConfig, loginServerURL, app.filenameWithPath); err != nil {
		return err
	}
	return nil
}

func main() {
	var app app
	loginCommmand := flag.NewFlagSet("login", flag.ExitOnError)
	setFlags(loginCommmand, true)
	configCommand := flag.NewFlagSet("config", flag.ExitOnError)
	setFlags(configCommand, false)
	user, err := user.Current()
	if err != nil {
		log.Fatalf("Could not determine current user of this system. Err: %v", err)
	}
	app.filenameWithPath = path.Join(user.HomeDir, "/.kubeloginrc.yaml")
	if len(os.Args) < 3 {
		fmt.Println(usageMessage)
		os.Exit(1)
	}
	switch os.Args[1] {
	case "login":
		if !strings.HasPrefix(os.Args[2], "--") {
			//use alias to extract needed information
			if err := app.getConfigSettings(os.Args[2]); err != nil {
				log.Fatal(err)
			}
			generateURLAndListenForServerResponse(app)
		} else {
			loginCommmand.Parse(os.Args[2:])
			if loginCommmand.Parsed() {
				if kubeloginServerBaseURL == "" {
					log.Fatal("--server-url must be set!")
				}
				app.kubectlUser = userFlag
				app.kubeloginServer = kubeloginServerBaseURL
				generateURLAndListenForServerResponse(app)
			}
		}
	case "config":
		configCommand.Parse(os.Args[2:])
		if configCommand.Parsed() {
			if kubeloginServerBaseURL == "" {
				log.Fatal("--server-url must be set!")
			}
			verifiedServerURL, err := url.ParseRequestURI(kubeloginServerBaseURL)
			if err != nil {
				log.Fatalf("Invalid URL given: %v | Err: %v", kubeloginServerBaseURL, err)
			}

			if err := app.configureFile(aliasFlag, verifiedServerURL, userFlag); err != nil {
				log.Fatal(err)
			}
			os.Exit(0)
		}
	default:
		fmt.Println(usageMessage)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"sort"
	"strconv"
	"strings"

	"github.com/ecc1/medtronic"
)

type (
	// Printer represents a function that prints an arbitrary value.
	Printer func(interface{})
)

var (
	formatFlag = flag.String("f", "openaps", "print result in specified `format`")

	format = map[string]Printer{
		"internal": showInternal,
		"json":     showJSON,
		"openaps":  showOpenAPS,
	}

	openAPSMode bool
)

func usage() {
	eprintf("usage: %s [options] command [ arg ... ]\n", os.Args[0])
	eprintf("   or: %s [options] command [ args.json ]\n", os.Args[0])
	flag.PrintDefaults()
	fmts := ""
	for k := range format {
		fmts += " " + k
	}
	eprintf("output formats:%s\n", fmts)
	keys := make([]string, len(command))
	i := 0
	for k := range command {
		keys[i] = k
		i++
	}
	sort.Strings(keys)
	cmds := ""
	for _, k := range keys {
		cmds += " " + k
	}
	eprintf("commands:%s\n", cmds)
	os.Exit(1)
}

func main() {
	flag.Usage = usage
	flag.Parse()
	printFn := format[*formatFlag]
	if printFn == nil {
		eprintf("%s: unknown format\n", *formatFlag)
		usage()
	}
	openAPSMode = *formatFlag == "openaps"
	if flag.NArg() == 0 {
		usage()
	}
	name := flag.Arg(0)
	cmd, found := command[name]
	if !found {
		eprintf("%s: unknown command\n", name)
		usage()
	}
	args := getArgs(name, cmd)
	pump := medtronic.Open()
	exitOnError(pump)
	defer pump.Close()
	pump.Wakeup()
	exitOnError(pump)
	result := cmd.Cmd(pump, args)
	exitOnError(pump)
	if result == nil {
		return
	}
	printFn(result)
}

func exitOnError(pump *medtronic.Pump) {
	err := pump.Error()
	if err == nil {
		return
	}
	if pump.NoResponse() {
		log.Print(err)
		os.Exit(2)
	}
	log.Fatal(err)
}

type (
	// Arguments represents the formal and actual parameters for a command.
	Arguments map[string]interface{}
)

// String returns the string value associated with the given key.
func (args Arguments) String(key string) (string, error) {
	arg := args[key]
	s, ok := arg.(string)
	if !ok {
		return s, fmt.Errorf("%q argument must be a string", key)
	}
	return s, nil
}

// Float returns the float64 value associated with the given key.
func (args Arguments) Float(key string) (float64, error) {
	arg := args[key]
	if openAPSMode {
		f, ok := arg.(float64)
		if !ok {
			return f, fmt.Errorf("%q parameter must be a number", key)
		}
		return f, nil
	}
	return strconv.ParseFloat(arg.(string), 64)
}

// Int returns the int value associated with the given key.
func (args Arguments) Int(key string) (int, error) {
	arg := args[key]
	if openAPSMode {
		f, ok := arg.(float64)
		if !ok {
			return int(f), fmt.Errorf("%q argument must be a number", key)
		}
		return int(f), nil
	}
	return strconv.Atoi(arg.(string))
}

// Strings returns the []string value associated with the given key.
func (args Arguments) Strings(key string) ([]string, error) {
	arg := args[key]
	if openAPSMode {
		v, ok := arg.([]interface{})
		if !ok {
			return nil, fmt.Errorf("%q argument must be an array", key)
		}
		a := make([]string, len(v))
		for i, si := range v {
			s, ok := si.(string)
			if !ok {
				return nil, fmt.Errorf("%q argument must be a list of strings", key)
			}
			a[i] = s
		}
		return a, nil
	}
	return arg.([]string), nil
}

func getArgs(name string, cmd Command) Arguments {
	params := cmd.Params
	argv := flag.Args()[1:]
	if len(params) == 0 {
		if len(argv) != 0 {
			log.Fatalf("%s does not take any arguments", name)
		}
		return nil
	}
	if openAPSMode {
		return openAPSArgs(name, params, argv, cmd.Variadic)
	}
	return cliArgs(name, params, argv, cmd.Variadic)
}

// Parse an openaps JSON file for arguments.
func openAPSArgs(name string, params []string, argv []string, variadic bool) Arguments {
	if len(argv) != 1 || !strings.HasSuffix(argv[0], ".json") {
		log.Fatalf("%s: openaps format requires single JSON argument file", name)
	}
	// Unmarshal the JSON argument file.
	file := argv[0]
	f, err := os.Open(file)
	if err != nil {
		log.Fatalf("%s: %v", name, err)
	}
	args := make(Arguments)
	err = json.NewDecoder(f).Decode(&args)
	if err != nil {
		log.Fatalf("%s: %v", name, err)
	}
	_ = f.Close()
	// Check that all parameters are present.
	for _, k := range params {
		_, present := args[k]
		if !present {
			log.Fatalf("%s: argument file %s is missing %q parameter", name, file, k)
		}
	}
	return args
}

// Collect command-line arguments.
func cliArgs(name string, params []string, argv []string, variadic bool) Arguments {
	if !variadic && len(argv) != len(params) {
		var p string
		if len(params) != 1 {
			p = "s"
		}
		log.Fatalf("%s requires %d argument%s", name, len(params), p)
	}
	args := make(Arguments)
	for i, k := range params {
		if variadic && i == len(params)-1 {
			// Bind all remaining args to this parameter.
			args[k] = argv[i:]
			break
		}
		if i >= len(argv) {
			// Bind remaining parameters to "".
			args[k] = ""
			continue
		}
		args[k] = argv[i]
	}
	return args
}

func eprintf(format string, arg ...interface{}) {
	fmt.Fprintf(os.Stderr, format, arg...)
}
<commit_msg>Improve control flow<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"sort"
	"strconv"
	"strings"

	"github.com/ecc1/medtronic"
)

type (
	// Printer represents a function that prints an arbitrary value.
	Printer func(interface{})
)

var (
	formatFlag = flag.String("f", "openaps", "print result in specified `format`")

	format = map[string]Printer{
		"internal": showInternal,
		"json":     showJSON,
		"openaps":  showOpenAPS,
	}

	openAPSMode bool
)

func usage() {
	eprintf("usage: %s [options] command [ arg ... ]\n", os.Args[0])
	eprintf("   or: %s [options] command [ args.json ]\n", os.Args[0])
	flag.PrintDefaults()
	fmts := ""
	for k := range format {
		fmts += " " + k
	}
	eprintf("output formats:%s\n", fmts)
	keys := make([]string, len(command))
	i := 0
	for k := range command {
		keys[i] = k
		i++
	}
	sort.Strings(keys)
	cmds := ""
	for _, k := range keys {
		cmds += " " + k
	}
	eprintf("commands:%s\n", cmds)
	os.Exit(1)
}

func main() {
	flag.Usage = usage
	flag.Parse()
	printFn := format[*formatFlag]
	if printFn == nil {
		eprintf("%s: unknown format\n", *formatFlag)
		usage()
	}
	openAPSMode = *formatFlag == "openaps"
	if flag.NArg() == 0 {
		usage()
	}
	name := flag.Arg(0)
	cmd, found := command[name]
	if !found {
		eprintf("%s: unknown command\n", name)
		usage()
	}
	args := getArgs(name, cmd)
	pump := medtronic.Open()
	exitOnError(pump)
	defer pump.Close()
	pump.Wakeup()
	exitOnError(pump)
	result := cmd.Cmd(pump, args)
	exitOnError(pump)
	if result != nil {
		printFn(result)
	}
}

func exitOnError(pump *medtronic.Pump) {
	err := pump.Error()
	if err == nil {
		return
	}
	if pump.NoResponse() {
		log.Print(err)
		os.Exit(2)
	}
	log.Fatal(err)
}

type (
	// Arguments represents the formal and actual parameters for a command.
	Arguments map[string]interface{}
)

// String returns the string value associated with the given key.
func (args Arguments) String(key string) (string, error) {
	arg := args[key]
	s, ok := arg.(string)
	if !ok {
		return s, fmt.Errorf("%q argument must be a string", key)
	}
	return s, nil
}

// Float returns the float64 value associated with the given key.
func (args Arguments) Float(key string) (float64, error) {
	arg := args[key]
	if openAPSMode {
		f, ok := arg.(float64)
		if !ok {
			return f, fmt.Errorf("%q parameter must be a number", key)
		}
		return f, nil
	}
	return strconv.ParseFloat(arg.(string), 64)
}

// Int returns the int value associated with the given key.
func (args Arguments) Int(key string) (int, error) {
	arg := args[key]
	if openAPSMode {
		f, ok := arg.(float64)
		if !ok {
			return int(f), fmt.Errorf("%q argument must be a number", key)
		}
		return int(f), nil
	}
	return strconv.Atoi(arg.(string))
}

// Strings returns the []string value associated with the given key.
func (args Arguments) Strings(key string) ([]string, error) {
	arg := args[key]
	if openAPSMode {
		v, ok := arg.([]interface{})
		if !ok {
			return nil, fmt.Errorf("%q argument must be an array", key)
		}
		a := make([]string, len(v))
		for i, si := range v {
			s, ok := si.(string)
			if !ok {
				return nil, fmt.Errorf("%q argument must be a list of strings", key)
			}
			a[i] = s
		}
		return a, nil
	}
	return arg.([]string), nil
}

func getArgs(name string, cmd Command) Arguments {
	params := cmd.Params
	argv := flag.Args()[1:]
	if len(params) == 0 {
		if len(argv) != 0 {
			log.Fatalf("%s does not take any arguments", name)
		}
		return nil
	}
	if openAPSMode {
		return openAPSArgs(name, params, argv, cmd.Variadic)
	}
	return cliArgs(name, params, argv, cmd.Variadic)
}

// Parse an openaps JSON file for arguments.
func openAPSArgs(name string, params []string, argv []string, variadic bool) Arguments {
	if len(argv) != 1 || !strings.HasSuffix(argv[0], ".json") {
		log.Fatalf("%s: openaps format requires single JSON argument file", name)
	}
	// Unmarshal the JSON argument file.
	file := argv[0]
	f, err := os.Open(file)
	if err != nil {
		log.Fatalf("%s: %v", name, err)
	}
	args := make(Arguments)
	err = json.NewDecoder(f).Decode(&args)
	if err != nil {
		log.Fatalf("%s: %v", name, err)
	}
	_ = f.Close()
	// Check that all parameters are present.
	for _, k := range params {
		_, present := args[k]
		if !present {
			log.Fatalf("%s: argument file %s is missing %q parameter", name, file, k)
		}
	}
	return args
}

// Collect command-line arguments.
func cliArgs(name string, params []string, argv []string, variadic bool) Arguments {
	if !variadic && len(argv) != len(params) {
		var p string
		if len(params) != 1 {
			p = "s"
		}
		log.Fatalf("%s requires %d argument%s", name, len(params), p)
	}
	args := make(Arguments)
	for i, k := range params {
		if variadic && i == len(params)-1 {
			// Bind all remaining args to this parameter.
			args[k] = argv[i:]
			break
		}
		if i >= len(argv) {
			// Bind remaining parameters to "".
			args[k] = ""
			continue
		}
		args[k] = argv[i]
	}
	return args
}

func eprintf(format string, arg ...interface{}) {
	fmt.Fprintf(os.Stderr, format, arg...)
}
<|endoftext|>