<commit_before>package fire

import (
	"net/http"
	"reflect"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/manyminds/api2go"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

// An Action describes the currently called action on the API.
type Action int

// All the available actions.
const (
	FindAll Action = iota
	FindOne
	Create
	Update
	Delete
)

// A Context provides useful contextual information to callbacks.
type Context struct {
	// The action that has been called.
	Action Action

	// The Model that will be saved during Create and Update.
	Model Model

	// The query that will be used during FindAll, FindOne, Update and Delete.
	//
	// Note: On FindOne, Update and Delete, the "_id" key is already set to the
	// document ID. On FindAll all ordinary filters and relationship filters are
	// also already present.
	Query bson.M

	// The sorting that will be used during FindAll.
	//
	// Note: This list will already include allowed sorting parameters passed
	// in the query parameters.
	Sorting []string

	// The db used to query.
	DB *mgo.Database

	// The underlying gin.context.
	GinContext *gin.Context

	// The underlying api2go.Request
	Api2GoReq *api2go.Request
}

// A Resource provides an interface to a model.
type Resource struct {
	// The model that this resource should provide (e.g. &Foo{}).
	Model Model

	// The Authorizer is run on all actions. Will return a Forbidden status if
	// an user error is returned.
	Authorizer Callback

	// The Validator is run to validate Create, Update and Delete actions. Will
	// return a Bad Request status if an user error is returned.
	Validator Callback

	adapter  *adapter
	endpoint *Endpoint
}

/* api2go interface */

// InitializeObject implements the api2go.ObjectInitializer interface.
func (r *Resource) InitializeObject(obj interface{}) {
	// initialize model
	Init(obj.(Model))
}

// FindAll implements the api2go.FindAll interface.
func (r *Resource) FindAll(req api2go.Request) (api2go.Responder, error) {
	// build context
	ctx := r.buildContext(FindAll, &req)
	ctx.Query = bson.M{}

	// set self referencing and to one relationship filters
	r.setRelationshipFilters(ctx)

	// add filters
	for _, attr := range r.Model.getBase().attributesByTag("filterable") {
		if values, ok := ctx.Api2GoReq.QueryParams["filter["+attr.name+"]"]; ok {
			ctx.Query[attr.dbField] = bson.M{"$in": values}
		}
	}

	// add sorting
	if fields, ok := req.QueryParams["sort"]; ok {
		for _, field := range fields {
			for _, attr := range r.Model.getBase().attributesByTag("sortable") {
				if field == attr.dbField || field == "-"+attr.dbField {
					ctx.Sorting = append(ctx.Sorting, field)
				}
			}
		}
	}

	// run authorizer if available
	if err := r.runCallback(r.Authorizer, ctx, http.StatusForbidden); err != nil {
		return nil, *err
	}

	// prepare slice
	pointer := newSlicePointer(r.Model)

	// query db
	err := r.endpoint.db.C(r.Model.Collection()).Find(ctx.Query).Sort(ctx.Sorting...).All(pointer)
	if err != nil {
		return nil, api2go.NewHTTPError(err, "error while retrieving resources", http.StatusInternalServerError)
	}

	// get content from pointer
	slice := sliceContent(pointer)

	// initialize each model
	s := reflect.ValueOf(slice)
	for i := 0; i < s.Len(); i++ {
		Init(s.Index(i).Interface().(Model))
	}

	return &response{Data: slice}, nil
}

// FindOne implements a part of the api2go.CRUD interface.
func (r *Resource) FindOne(id string, req api2go.Request) (api2go.Responder, error) {
	// validate id
	if !bson.IsObjectIdHex(id) {
		return nil, api2go.NewHTTPError(nil, "invalid id", http.StatusBadRequest)
	}

	// build context
	ctx := r.buildContext(FindOne, &req)
	ctx.Query = bson.M{"_id": bson.ObjectIdHex(id)}

	// run authorizer if available
	if err := r.runCallback(r.Authorizer, ctx, http.StatusForbidden); err != nil {
		return nil, *err
	}

	// prepare object
	obj := newStructPointer(r.Model)

	// query db
	err := r.endpoint.db.C(r.Model.Collection()).Find(ctx.Query).One(obj)
	if err == mgo.ErrNotFound {
		return nil, api2go.NewHTTPError(err, "resource not found", http.StatusNotFound)
	} else if err != nil {
		return nil, api2go.NewHTTPError(err, "error while retrieving resource", http.StatusInternalServerError)
	}

	// initialize model
	model := Init(obj.(Model))

	return &response{Data: model}, nil
}

// Create implements a part of the api2go.CRUD interface.
func (r *Resource) Create(obj interface{}, req api2go.Request) (api2go.Responder, error) {
	// build context
	ctx := r.buildContext(Create, &req)
	ctx.Model = obj.(Model)

	// run authorizer if available
	if err := r.runCallback(r.Authorizer, ctx, http.StatusForbidden); err != nil {
		return nil, *err
	}

	// validate model
	err := ctx.Model.Validate(true)
	if err != nil {
		return nil, api2go.NewHTTPError(nil, err.Error(), http.StatusBadRequest)
	}

	// run validator if available
	if err := r.runCallback(r.Validator, ctx, http.StatusBadRequest); err != nil {
		return nil, *err
	}

	// query db
	err = r.endpoint.db.C(r.Model.Collection()).Insert(ctx.Model)
	if err != nil {
		return nil, api2go.NewHTTPError(err, "error while saving resource", http.StatusInternalServerError)
	}

	return &response{Data: ctx.Model, Code: http.StatusCreated}, nil
}

// Update implements a part of the api2go.CRUD interface.
func (r *Resource) Update(obj interface{}, req api2go.Request) (api2go.Responder, error) {
	// build context
	ctx := r.buildContext(Update, &req)
	ctx.Model = obj.(Model)
	ctx.Query = bson.M{"_id": ctx.Model.ID()}

	// run authorizer if available
	if err := r.runCallback(r.Authorizer, ctx, http.StatusForbidden); err != nil {
		return nil, *err
	}

	// validate model
	err := ctx.Model.Validate(false)
	if err != nil {
		return nil, api2go.NewHTTPError(nil, err.Error(), http.StatusBadRequest)
	}

	// run validator if available
	if err := r.runCallback(r.Validator, ctx, http.StatusBadRequest); err != nil {
		return nil, *err
	}

	// query db
	err = r.endpoint.db.C(r.Model.Collection()).Update(ctx.Query, ctx.Model)
	if err != nil {
		return nil, api2go.NewHTTPError(err, "error while updating resource", http.StatusInternalServerError)
	}

	return &response{Data: ctx.Model}, nil
}

// Delete implements a part of the api2go.CRUD interface.
func (r *Resource) Delete(id string, req api2go.Request) (api2go.Responder, error) {
	// validate id
	if !bson.IsObjectIdHex(id) {
		return nil, api2go.NewHTTPError(nil, "invalid id", http.StatusBadRequest)
	}

	// build context
	ctx := r.buildContext(Delete, &req)
	ctx.Query = bson.M{"_id": bson.ObjectIdHex(id)}

	// run authorizer if available
	if err := r.runCallback(r.Authorizer, ctx, http.StatusForbidden); err != nil {
		return nil, *err
	}

	// run validator if available
	if err := r.runCallback(r.Validator, ctx, http.StatusBadRequest); err != nil {
		return nil, *err
	}

	// query db
	err := r.endpoint.db.C(r.Model.Collection()).Remove(ctx.Query)
	if err != nil {
		return nil, api2go.NewHTTPError(err, "error while deleting resource", http.StatusInternalServerError)
	}

	return &response{Code: http.StatusNoContent}, nil
}

func (r *Resource) buildContext(act Action, req *api2go.Request) *Context {
	return &Context{
		Action:     act,
		DB:         r.endpoint.db,
		GinContext: r.adapter.getContext(req),
		Api2GoReq:  req,
	}
}

func (r *Resource) setRelationshipFilters(ctx *Context) {
	for param, values := range ctx.Api2GoReq.QueryParams {
		// remove *Name params as not needed
		if strings.HasSuffix(param, "Name") {
			delete(ctx.Api2GoReq.QueryParams, param)
		}

		// handle *ID params
		if strings.HasSuffix(param, "ID") {
			// remove param in any case
			delete(ctx.Api2GoReq.QueryParams, param)

			// get plural name
			pluralName := strings.Replace(param, "ID", "", 1)

			// get singular name and continue if not existing
			singularName, ok := r.endpoint.nameMap[pluralName]
			if !ok {
				continue
			}

			// check if self referencing
			if singularName == r.Model.getBase().singularName {
				ctx.Query["_id"] = bson.M{"$in": stringsToIDs(values)}
			}

			// add to one relationship filters
			for _, rel := range r.Model.getBase().toOneRelationships {
				if rel.name == singularName {
					ctx.Query[rel.dbField] = bson.M{"$in": stringsToIDs(values)}
				}
			}
		}
	}
}

func (r *Resource) runCallback(cb Callback, ctx *Context, errorStatus int) *api2go.HTTPError {
	// check if callback is available
	if cb != nil {
		err, sysErr := cb(ctx)
		if sysErr != nil {
			// return system error
			httpErr := api2go.NewHTTPError(sysErr, "internal server error", http.StatusInternalServerError)
			return &httpErr
		}
		if err != nil {
			// return user error
			httpErr := api2go.NewHTTPError(nil, err.Error(), errorStatus)
			return &httpErr
		}
	}

	return nil
}
<commit_msg>correctly return unauthorized rather than forbidden<commit_after>package fire

import (
	"net/http"
	"reflect"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/manyminds/api2go"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

// An Action describes the currently called action on the API.
type Action int

// All the available actions.
const (
	FindAll Action = iota
	FindOne
	Create
	Update
	Delete
)

// A Context provides useful contextual information to callbacks.
type Context struct {
	// The action that has been called.
	Action Action

	// The Model that will be saved during Create and Update.
	Model Model

	// The query that will be used during FindAll, FindOne, Update and Delete.
	//
	// Note: On FindOne, Update and Delete, the "_id" key is already set to the
	// document ID. On FindAll all ordinary filters and relationship filters are
	// also already present.
	Query bson.M

	// The sorting that will be used during FindAll.
	//
	// Note: This list will already include allowed sorting parameters passed
	// in the query parameters.
	Sorting []string

	// The db used to query.
	DB *mgo.Database

	// The underlying gin.context.
	GinContext *gin.Context

	// The underlying api2go.Request
	Api2GoReq *api2go.Request
}

// A Resource provides an interface to a model.
type Resource struct {
	// The model that this resource should provide (e.g. &Foo{}).
	Model Model

	// The Authorizer is run on all actions. Will return an Unauthorized status
	// if an user error is returned.
	Authorizer Callback

	// The Validator is run to validate Create, Update and Delete actions. Will
	// return a Bad Request status if an user error is returned.
	Validator Callback

	adapter  *adapter
	endpoint *Endpoint
}

/* api2go interface */

// InitializeObject implements the api2go.ObjectInitializer interface.
func (r *Resource) InitializeObject(obj interface{}) {
	// initialize model
	Init(obj.(Model))
}

// FindAll implements the api2go.FindAll interface.
func (r *Resource) FindAll(req api2go.Request) (api2go.Responder, error) {
	// build context
	ctx := r.buildContext(FindAll, &req)
	ctx.Query = bson.M{}

	// set self referencing and to one relationship filters
	r.setRelationshipFilters(ctx)

	// add filters
	for _, attr := range r.Model.getBase().attributesByTag("filterable") {
		if values, ok := ctx.Api2GoReq.QueryParams["filter["+attr.name+"]"]; ok {
			ctx.Query[attr.dbField] = bson.M{"$in": values}
		}
	}

	// add sorting
	if fields, ok := req.QueryParams["sort"]; ok {
		for _, field := range fields {
			for _, attr := range r.Model.getBase().attributesByTag("sortable") {
				if field == attr.dbField || field == "-"+attr.dbField {
					ctx.Sorting = append(ctx.Sorting, field)
				}
			}
		}
	}

	// run authorizer if available
	if err := r.runCallback(r.Authorizer, ctx, http.StatusUnauthorized); err != nil {
		return nil, *err
	}

	// prepare slice
	pointer := newSlicePointer(r.Model)

	// query db
	err := r.endpoint.db.C(r.Model.Collection()).Find(ctx.Query).Sort(ctx.Sorting...).All(pointer)
	if err != nil {
		return nil, api2go.NewHTTPError(err, "error while retrieving resources", http.StatusInternalServerError)
	}

	// get content from pointer
	slice := sliceContent(pointer)

	// initialize each model
	s := reflect.ValueOf(slice)
	for i := 0; i < s.Len(); i++ {
		Init(s.Index(i).Interface().(Model))
	}

	return &response{Data: slice}, nil
}

// FindOne implements a part of the api2go.CRUD interface.
func (r *Resource) FindOne(id string, req api2go.Request) (api2go.Responder, error) {
	// validate id
	if !bson.IsObjectIdHex(id) {
		return nil, api2go.NewHTTPError(nil, "invalid id", http.StatusBadRequest)
	}

	// build context
	ctx := r.buildContext(FindOne, &req)
	ctx.Query = bson.M{"_id": bson.ObjectIdHex(id)}

	// run authorizer if available
	if err := r.runCallback(r.Authorizer, ctx, http.StatusUnauthorized); err != nil {
		return nil, *err
	}

	// prepare object
	obj := newStructPointer(r.Model)

	// query db
	err := r.endpoint.db.C(r.Model.Collection()).Find(ctx.Query).One(obj)
	if err == mgo.ErrNotFound {
		return nil, api2go.NewHTTPError(err, "resource not found", http.StatusNotFound)
	} else if err != nil {
		return nil, api2go.NewHTTPError(err, "error while retrieving resource", http.StatusInternalServerError)
	}

	// initialize model
	model := Init(obj.(Model))

	return &response{Data: model}, nil
}

// Create implements a part of the api2go.CRUD interface.
func (r *Resource) Create(obj interface{}, req api2go.Request) (api2go.Responder, error) {
	// build context
	ctx := r.buildContext(Create, &req)
	ctx.Model = obj.(Model)

	// run authorizer if available
	if err := r.runCallback(r.Authorizer, ctx, http.StatusUnauthorized); err != nil {
		return nil, *err
	}

	// validate model
	err := ctx.Model.Validate(true)
	if err != nil {
		return nil, api2go.NewHTTPError(nil, err.Error(), http.StatusBadRequest)
	}

	// run validator if available
	if err := r.runCallback(r.Validator, ctx, http.StatusBadRequest); err != nil {
		return nil, *err
	}

	// query db
	err = r.endpoint.db.C(r.Model.Collection()).Insert(ctx.Model)
	if err != nil {
		return nil, api2go.NewHTTPError(err, "error while saving resource", http.StatusInternalServerError)
	}

	return &response{Data: ctx.Model, Code: http.StatusCreated}, nil
}

// Update implements a part of the api2go.CRUD interface.
func (r *Resource) Update(obj interface{}, req api2go.Request) (api2go.Responder, error) {
	// build context
	ctx := r.buildContext(Update, &req)
	ctx.Model = obj.(Model)
	ctx.Query = bson.M{"_id": ctx.Model.ID()}

	// run authorizer if available
	if err := r.runCallback(r.Authorizer, ctx, http.StatusUnauthorized); err != nil {
		return nil, *err
	}

	// validate model
	err := ctx.Model.Validate(false)
	if err != nil {
		return nil, api2go.NewHTTPError(nil, err.Error(), http.StatusBadRequest)
	}

	// run validator if available
	if err := r.runCallback(r.Validator, ctx, http.StatusBadRequest); err != nil {
		return nil, *err
	}

	// query db
	err = r.endpoint.db.C(r.Model.Collection()).Update(ctx.Query, ctx.Model)
	if err != nil {
		return nil, api2go.NewHTTPError(err, "error while updating resource", http.StatusInternalServerError)
	}

	return &response{Data: ctx.Model}, nil
}

// Delete implements a part of the api2go.CRUD interface.
func (r *Resource) Delete(id string, req api2go.Request) (api2go.Responder, error) {
	// validate id
	if !bson.IsObjectIdHex(id) {
		return nil, api2go.NewHTTPError(nil, "invalid id", http.StatusBadRequest)
	}

	// build context
	ctx := r.buildContext(Delete, &req)
	ctx.Query = bson.M{"_id": bson.ObjectIdHex(id)}

	// run authorizer if available
	if err := r.runCallback(r.Authorizer, ctx, http.StatusUnauthorized); err != nil {
		return nil, *err
	}

	// run validator if available
	if err := r.runCallback(r.Validator, ctx, http.StatusBadRequest); err != nil {
		return nil, *err
	}

	// query db
	err := r.endpoint.db.C(r.Model.Collection()).Remove(ctx.Query)
	if err != nil {
		return nil, api2go.NewHTTPError(err, "error while deleting resource", http.StatusInternalServerError)
	}

	return &response{Code: http.StatusNoContent}, nil
}

func (r *Resource) buildContext(act Action, req *api2go.Request) *Context {
	return &Context{
		Action:     act,
		DB:         r.endpoint.db,
		GinContext: r.adapter.getContext(req),
		Api2GoReq:  req,
	}
}

func (r *Resource) setRelationshipFilters(ctx *Context) {
	for param, values := range ctx.Api2GoReq.QueryParams {
		// remove *Name params as not needed
		if strings.HasSuffix(param, "Name") {
			delete(ctx.Api2GoReq.QueryParams, param)
		}

		// handle *ID params
		if strings.HasSuffix(param, "ID") {
			// remove param in any case
			delete(ctx.Api2GoReq.QueryParams, param)

			// get plural name
			pluralName := strings.Replace(param, "ID", "", 1)

			// get singular name and continue if not existing
			singularName, ok := r.endpoint.nameMap[pluralName]
			if !ok {
				continue
			}

			// check if self referencing
			if singularName == r.Model.getBase().singularName {
				ctx.Query["_id"] = bson.M{"$in": stringsToIDs(values)}
			}

			// add to one relationship filters
			for _, rel := range r.Model.getBase().toOneRelationships {
				if rel.name == singularName {
					ctx.Query[rel.dbField] = bson.M{"$in": stringsToIDs(values)}
				}
			}
		}
	}
}

func (r *Resource) runCallback(cb Callback, ctx *Context, errorStatus int) *api2go.HTTPError {
	// check if callback is available
	if cb != nil {
		err, sysErr := cb(ctx)
		if sysErr != nil {
			// return system error
			httpErr := api2go.NewHTTPError(sysErr, "internal server error", http.StatusInternalServerError)
			return &httpErr
		}
		if err != nil {
			// return user error
			httpErr := api2go.NewHTTPError(nil, err.Error(), errorStatus)
			return &httpErr
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package f

import(
    "github.com/ricallinson/stackr"
)

/*
    Response represents the response from an HTTP request.
*/
type Response struct {

    // The stackr.Response.
    *stackr.Response

    // Assign the charset. Defaults to "utf-8".
    Charset string

    // Response local variables are scoped to the request, thus only available
    // to the view(s) rendered during that request / response cycle, if any.
    // Otherwise this API is identical to app.Locals.
    Locals map[string]string
}

/*
    Returns a new HTTP Response.
*/

func createResponse(res *stackr.Response) (*Response) {

    /*
        Create the Response.
    */

    r := &Response{}

    /*
        Set the stackr.Response.
    */

    r.Response = res

    /*
        Set default charset.
    */

    r.Charset = "utf-8"

    /*
        Prime the Locals map.
    */

    r.Locals = map[string]string{}

    /*
        Return the finished forgery.Response.
    */

    return r
}

/*
    Chainable alias of stackr's "res.StatusCode=".
*/
func (this *Response) Status(c int) (*Response) {
    this.StatusCode = c
    return this
}

/*
    Set header "field" to "value".
*/
func (this *Response) Set(f string, v string) {
    this.SetHeader(f, v)
}

/*
    Get the case-insensitive response header "field".
*/
func (this *Response) Get(f string) (string) {

    /*
        Possible future bug.
        http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
        Message headers are case-insensitive.
    */

    return this.Writer.Header().Get(f)
}

/*
    Set cookie "name" to "value", where "value" may be a string or interface
    converted to JSON. The "path" option defaults to "/".
*/
func (this *Response) Cookie(n string, v string, opt ...interface{}) {
    panic(halt)
}

/*
    Clear cookie "name". The "path" option defaults to "/".
*/
func (this *Response) ClearCookie(n string, opt ...interface{}) {
    panic(halt)
}

/*
    Redirect to the given "url" with optional "status" code defaulting to 302 "Found".
*/
func (this *Response) Redirect(url string, s ...int) {
    panic(halt)
}

/*
    Set the location header.
*/
func (this *Response) Location(url string) {
    panic(halt)
}

/*
    Send a response. This method performs a myriad of useful tasks for simple non-streaming
    responses such as automatically assigning the Content-Length unless previously defined
    and providing automatic HEAD and HTTP cache freshness support.

    res.Send(new Buffer('whoop'));
    res.Send(interface{ "some": "json" });
    res.Send('some html');
    res.Send('Sorry, we cannot find that!', 404);
    res.Send(interface{ "error": 'something blew up' }, 500);
    res.Send(200);
*/
func (this *Response) Send(b interface{}, s ...int) {
    panic(halt)
}

/*
    Send a JSON response. This method is identical to res.Send() when an object or slice is passed,
    however it may be used for explicit JSON conversion of non-objects
    (null, undefined, etc), though these are technically not valid JSON.

    res.Json(null)
    res.Json({ "user": 'tobi' })
    res.Json(interface{ error: 'message' }, 500)
*/
func (this *Response) Json(b interface{}, s ...int) {
    panic(halt)
}

/*
    Send a JSON response with JSONP support.
    This method is identical to "res.Json()" however opts-in to JSONP callback support.
*/
func (this *Response) Jsonp(b interface{}, s ...int) {
    panic(halt)
}

/*
    Sets the Content-Type to the mime lookup of type, or when "/" is present the
    Content-Type is simply set to this literal value.
*/
func (this *Response) Type(t string) {
    panic(halt)
}

/*
    Performs content-negotiation on the request Accept header field when present.
    This method uses "req.accepted", an array of acceptable types ordered by their
    quality values, otherwise the first callback is invoked. When no match is performed
    the server responds with 406 "Not Acceptable", or invokes the "default" callback.
*/
func (this *Response) Format(i interface{}) {
    panic(halt)
}

/*
    Sets the Content-Disposition header field to "attachment". If a filename is given then
    the Content-Type will be automatically set based on the extname via res.type(),
    and the Content-Disposition's "filename=" parameter will be set.

    res.Attachment();
    // Content-Disposition: attachment

    res.Attachment('path/to/logo.png');
    // Content-Disposition: attachment; filename="logo.png"
    // Content-Type: image/png
*/
func (this *Response) Attachment(f ...string) {
    panic(halt)
}

/*
    Transfer the file at the given path. Automatically defaults the Content-Type response
    header field based on the filename's extension.
*/
func (this *Response) Sendfile(p string, opt ...interface{}) {
    panic(halt)
}

/*
    Transfer the file at path as an "attachment", typically browsers will prompt the user for download.
    The Content-Disposition "filename=" parameter, aka the one that will appear in the brower
    dialog is set to path by default, however you may provide an override filename.
*/
func (this *Response) Download(p string, opt ...interface{}) {
    panic(halt)
}

/*
    Join the given "links" to populate the "Link" response header field.
*/
func (this *Response) Links(l []string) {
    panic(halt)
}

/*
    Render a "view". When an error occurs next(err) is invoked internally.
*/
func (this *Response) Render(v string, l ...interface{}) {
    panic(halt)
}<commit_msg>Docs update<commit_after>package f

import(
    "github.com/ricallinson/stackr"
)

/*
    Response represents the response from an HTTP request.
*/
type Response struct {

    // The stackr.Response.
    *stackr.Response

    // Assign the charset. Defaults to "utf-8".
    Charset string

    // Response local variables are scoped to the request, thus only available
    // to the view(s) rendered during that request / response cycle, if any.
    // Otherwise this API is identical to app.Locals.
    Locals map[string]string
}

/*
    Returns a new HTTP Response.
*/

func createResponse(res *stackr.Response) (*Response) {

    /*
        Create the Response.
    */

    r := &Response{}

    /*
        Set the stackr.Response.
    */

    r.Response = res

    /*
        Set default charset.
    */

    r.Charset = "utf-8"

    /*
        Prime the Locals map.
    */

    r.Locals = map[string]string{}

    /*
        Return the finished forgery.Response.
    */

    return r
}

/*
    Chainable alias of stackr's "res.StatusCode=".
*/
func (this *Response) Status(c int) (*Response) {
    this.StatusCode = c
    return this
}

/*
    Set header "field" to "value".
*/
func (this *Response) Set(f string, v string) {
    this.SetHeader(f, v)
}

/*
    Get the case-insensitive response header "field".
*/
func (this *Response) Get(f string) (string) {

    /*
        Possible future bug.
        http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
        Message headers are case-insensitive.
    */

    return this.Writer.Header().Get(f)
}

/*
    Set cookie "name" to "value", where "value" may be a string or interface
    converted to JSON. The "path" option defaults to "/".
*/
func (this *Response) Cookie(n string, v string, opt ...interface{}) {
    panic(halt)
}

/*
    Clear cookie "name". The "path" option defaults to "/".
*/
func (this *Response) ClearCookie(n string, opt ...interface{}) {
    panic(halt)
}

/*
    Redirect to the given "url" with optional "status" code defaulting to 302 "Found".
*/
func (this *Response) Redirect(url string, s ...int) {
    panic(halt)
}

/*
    Set the location header.
*/
func (this *Response) Location(url string) {
    panic(halt)
}

/*
    Send a response. This method performs a myriad of useful tasks for simple non-streaming
    responses such as automatically assigning the Content-Length unless previously defined
    and providing automatic HEAD and HTTP cache freshness support.

    res.Send(new Buffer('whoop'));
    res.Send(interface{ "some": "json" });
    res.Send('some html');
    res.Send('Sorry, we cannot find that!', 404);
    res.Send(interface{ "error": 'something blew up' }, 500);
    res.Send(200);
*/
func (this *Response) Send(b interface{}, s ...int) {
    panic(halt)
}

/*
    Send a JSON response. This method is identical to res.Send() when an object or slice is passed,
    however it may be used for explicit JSON conversion of non-objects
    (null, undefined, etc), though these are technically not valid JSON.

    res.Json(null)
    res.Json({ "user": 'tobi' })
    res.Json(interface{ error: 'message' }, 500)
*/
func (this *Response) Json(b interface{}, s ...int) {
    panic(halt)
}

/*
    Send a JSON response with JSONP support.
    This method is identical to "res.Json()" however opts-in to JSONP callback support.
*/
func (this *Response) Jsonp(b interface{}, s ...int) {
    panic(halt)
}

/*
    Sets the Content-Type to the mime lookup of type, or when "/" is present the
    Content-Type is simply set to this literal value.
*/
func (this *Response) Type(t string) {
    panic(halt)
}

/*
    Performs content-negotiation on the request Accept header field when present.
    This method uses "req.accepted", an array of acceptable types ordered by their
    quality values, otherwise the first callback is invoked. When no match is performed
    the server responds with 406 "Not Acceptable", or invokes the "default" callback.
*/
func (this *Response) Format(i interface{}) {
    panic(halt)
}

/*
    Sets the Content-Disposition header field to "attachment". If a filename is given then
    the Content-Type will be automatically set based on the extname via res.Type(),
    and the Content-Disposition's "filename=" parameter will be set.

    res.Attachment();
    // Content-Disposition: attachment

    res.Attachment('path/to/logo.png');
    // Content-Disposition: attachment; filename="logo.png"
    // Content-Type: image/png
*/
func (this *Response) Attachment(f ...string) {
    panic(halt)
}

/*
    Transfer the file at the given path. Automatically defaults the Content-Type response
    header field based on the filename's extension.
*/
func (this *Response) Sendfile(p string, opt ...interface{}) {
    panic(halt)
}

/*
    Transfer the file at path as an "attachment", typically browsers will prompt the user for download.
    The Content-Disposition "filename=" parameter, aka the one that will appear in the browser
    dialog is set to path by default, however you may provide an override filename.
*/
func (this *Response) Download(p string, opt ...interface{}) {
    panic(halt)
}

/*
    Join the given "links" to populate the "Link" response header field.
*/
func (this *Response) Links(l []string) {
    panic(halt)
}

/*
    Render a "view". When an error occurs next(err) is invoked internally.
*/
func (this *Response) Render(v string, l ...interface{}) {
    panic(halt)
}<|endoftext|>


<commit_before>package backend

// list of available backends
var backends = []Client{
	new(Bolt),
	new(Redis),
	new(GceDatastore),
}

// Get by name
func Get(s string) Client {
	for _, i := range backends {
		if i.Name() == s {
			return i
		}
	}
	return backends[0]
}

// Key struct
type Key struct {
	id   string
	dirs []string
}

// Entity represents a version record
type Entity struct {
	Version string
	Archive []*Map
}

// Map represents map type object
type Map struct {
	Key string
	Val string
}
<commit_msg>remove unused code<commit_after>package backend

// Key struct
type Key struct {
	id   string
	dirs []string
}

// Entity represents a version record
type Entity struct {
	Version string
	Archive []*Map
}

// Map represents map type object
type Map struct {
	Key string
	Val string
}
<|endoftext|>


<commit_before>package system

import (
	"github.com/quintilesims/layer0/common/testutils"
	"testing"
	"time"
)

// Test Resources:
// This test creates an environment named 'tp'
// and a deploy named 'alpine'
func TestTaskPerformance(t *testing.T) {
	t.Skip("Task Performance updates are WIP")
	t.Parallel()

	s := NewSystemTest(t, "cases/task_performance", nil)
	s.Terraform.Apply()
	defer s.Terraform.Destroy()

	environmentID := s.Terraform.Output("environment_id")
	deployID := s.Terraform.Output("deploy_id")

	// create 100 tasks
	taskNameCopies := map[string]int{
		"TaskA": 10,
		"TaskB": 10,
		"TaskC": 10,
		"TaskD": 10,
		"TaskE": 10,
		"TaskF": 10,
		"TaskG": 10,
		"TaskH": 10,
		"TaskI": 10,
		"TaskJ": 5,
		"TaskK": 1,
		"TaskL": 1,
		"TaskM": 1,
		"TaskN": 1,
		"TaskO": 1,
	}

	for taskName, copies := range taskNameCopies {
		log.Debugf("Creating task %s (copies: %d)", taskName, copies)
		s.Layer0.CreateTask(taskName, environmentID, deployID, copies, nil)
	}

	testutils.WaitFor(t, time.Second*30, time.Minute*10, func() bool {
		log.Debugf("Waiting for tasks to run")
		currentTaskNameCopies := map[string]int{}
		for _, taskSummary := range s.Layer0.ListTasks() {
			if taskSummary.EnvironmentID == environmentID {
				task := s.Layer0.GetTask(taskSummary.TaskID)
				currentTaskNameCopies[task.TaskName] = int(task.DesiredCount)
			}
		}

		ok := true
		for taskName, expectedCopies := range taskNameCopies {
			currentCopies := currentTaskNameCopies[taskName]
			log.Debugf("Task '%s' has %d/%d copies", taskName, currentCopies, expectedCopies)
			if currentCopies != expectedCopies {
				ok = false
			}
		}

		return ok
	})
}
<commit_msg>check status codes when tasks complete<commit_after>package system

import (
	"github.com/quintilesims/layer0/common/testutils"
	"testing"
	"time"
)

// Test Resources:
// This test creates an environment named 'tp'
// and a deploy named 'alpine'
func TestTaskPerformance(t *testing.T) {
	t.Parallel()

	s := NewSystemTest(t, "cases/task_performance", nil)
	s.Terraform.Apply()
	defer s.Terraform.Destroy()

	environmentID := s.Terraform.Output("environment_id")
	deployID := s.Terraform.Output("deploy_id")

	// create 100 tasks
	taskNameCopies := map[string]int{
		"TaskA": 50,
		"TaskB": 25,
		"TaskC": 10,
		"TaskD": 5,
		"TaskE": 5,
		"TaskF": 1,
		"TaskG": 1,
		"TaskH": 1,
		"TaskI": 1,
		"TaskJ": 1,
	}

	for taskName, copies := range taskNameCopies {
		log.Debugf("Creating task %s (copies: %d)", taskName, copies)
		s.Layer0.CreateTask(taskName, environmentID, deployID, copies, nil)
	}

	testutils.WaitFor(t, time.Second*30, time.Minute*10, func() bool {
		log.Debugf("Waiting for all tasks to run")

		var numTasks int
		for _, taskSummary := range s.Layer0.ListTasks() {
			if taskSummary.EnvironmentID == environmentID {
				numTasks++
			}
		}

		log.Debugf("%d/100 tasks have ran", numTasks)
		return numTasks >= 100
	})

	// sleep for 10 seconds since thats the longest a task will take
	time.Sleep(time.Second * 10)

	log.Debugf("Checking task exit codes")
	for _, taskSummary := range s.Layer0.ListTasks() {
		if taskSummary.EnvironmentID == environmentID {
			task := s.Layer0.GetTask(taskSummary.TaskID)
			detail := task.Copies[0].Details[0]

			if detail.ExitCode != 0 {
				t.Fatalf("Task %s has unexpected exit code: %#v", task.TaskID, detail)
			}
		}
	}
}
<|endoftext|>


<commit_before>package authutil

import (
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"errors"
	"fmt"
	"log"
	"net"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/cviecco/argon2"
	"github.com/foomo/htpasswd"
	"golang.org/x/crypto/bcrypt"
	"gopkg.in/ldap.v2"
)

// The argon2 defaults are:
// t = 3
// m = 12  // memory usage 2^N
// p = 1
// l = 32
// We will use slightly bigger values:

const argon2t = 40
const argon2m = 20
const argon2p = 2
const argon2l = 32

//There is no well defined number for argon2. We define our own
const argon2dPrefix = "$argon2d$"

const randomStringEntropyBytes = 32

func genRandomString() (string, error) {
	size := randomStringEntropyBytes
	rb := make([]byte, size)
	_, err := rand.Read(rb)
	if err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(rb), nil
}

// The format of the hash will be:
// $99d$SALT:HEXVALUE

func Argon2MakeNewHash(password []byte) (string, error) {

	salt, err := genRandomString()
	if err != nil {
		return "", err
	}
	key, err := argon2.Key(password, []byte(salt), argon2t, argon2p, argon2m, argon2l)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%s%s:%x", argon2dPrefix, salt, key), nil

}

// We only support argon2d as is the only pure golang implementation
func Argon2CompareHashAndPassword(hash string, password []byte) error {
	if !strings.HasPrefix(hash, argon2dPrefix) {
		err := errors.New("Dont understand hash format")
		return err
	}
	splitHashString := strings.SplitN(hash, ":", 2)
	hexKey := splitHashString[1]
	salt := splitHashString[0][len(argon2dPrefix):]
	//log.Printf("salt='%s' heykey=%s", salt, hexKey)
	key, err := argon2.Key(password, []byte(salt), argon2t, argon2p, argon2m, argon2l)
	if err != nil {
		return err
	}
	if hexKey == fmt.Sprintf("%x", key) {
		return nil
	}
	return errors.New("invalid password")
	//return nil
}

func CheckHtpasswdUserPassword(username string, password string, htpasswdBytes []byte) (bool, error) {
	//	secrets := HtdigestFileProvider(htpasswdFilename)
	passwords, err := htpasswd.ParseHtpasswd(htpasswdBytes)
	if err != nil {
		return false, err
	}
	hash, ok := passwords[username]
	if !ok {
		return false, nil
	}
	// only understand bcrypt
	if !strings.HasPrefix(hash, "$2y$") {
		err := errors.New("Can only use bcrypt for htpasswd")
		return false, err
	}
	err = bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
	if err != nil {
		return false, nil
	}
	return true, nil

}

func getLDAPConnection(u url.URL, timeoutSecs uint, rootCAs *x509.CertPool) (*ldap.Conn, string, error) {
	if u.Scheme != "ldaps" {
		err := errors.New("Invalid ldap scheme (we only support ldaps")
		return nil, "", err
	}
	//hostnamePort := server + ":636"
	serverPort := strings.Split(u.Host, ":")
	port := "636"
	if len(serverPort) == 2 {
		port = serverPort[1]
	}
	server := serverPort[0]
	hostnamePort := server + ":" + port

	timeout := time.Duration(time.Duration(timeoutSecs) * time.Second)
	start := time.Now()
	tlsConn, err := tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", hostnamePort,
		&tls.Config{ServerName: server, RootCAs: rootCAs})
	if err != nil {
		errorTime := time.Since(start).Seconds() * 1000
		log.Printf("connction failure for:%s (%s)(time(ms)=%v)", server, err.Error(), errorTime)
		return nil, "", err
	}

	// we dont close the tls connection directly  close defer to the new ldap connection
	conn := ldap.NewConn(tlsConn, true)
	return conn, server, nil
}

func CheckLDAPConnection(u url.URL, timeoutSecs uint, rootCAs *x509.CertPool) error {
	conn, _, err := getLDAPConnection(u, timeoutSecs, rootCAs)
	if err != nil {
		return err
	}
	defer conn.Close()
	timeout := time.Duration(time.Duration(timeoutSecs) * time.Second)
	conn.SetTimeout(timeout)
	conn.Start()
	return nil
}

func CheckLDAPUserPassword(u url.URL, bindDN string, bindPassword string, timeoutSecs uint, rootCAs *x509.CertPool) (bool, error) {
	timeout := time.Duration(time.Duration(timeoutSecs) * time.Second)
	conn, server, err := getLDAPConnection(u, timeoutSecs, rootCAs)
	if err != nil {
		return false, err
	}
	defer conn.Close()

	//connectionTime := time.Since(start).Seconds() * 1000

	conn.SetTimeout(timeout)
	conn.Start()
	err = conn.Bind(bindDN, bindPassword)
	if err != nil {
		log.Printf("Bind failure for server:%s bindDN:'%s' (%s)", server, bindDN, err.Error())
		if strings.Contains(err.Error(), "Invalid Credentials") {
			return false, nil
		}
		return false, err
	}
	return true, nil
}

func ParseLDAPURL(ldapUrl string) (*url.URL, error) {
	u, err := url.Parse(ldapUrl)
	if err != nil {
		return nil, err
	}
	if u.Scheme != "ldaps" {
		err := errors.New("Invalid ldap scheme (we only support ldaps")
		return nil, err
	}
	//extract port if any... and if NIL then set it to 636
	return u, nil
}

func getUserDNAndSimpleGroups(conn *ldap.Conn, UserSearchBaseDNs []string, UserSearchFilter string, username string) (string, []string, error) {
	for _, searchDN := range UserSearchBaseDNs {
		searchRequest := ldap.NewSearchRequest(
			searchDN,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			//fmt.Sprintf("(&(objectClass=organizationalPerson)&(uid=%s))", username),
			fmt.Sprintf(UserSearchFilter, username),
			[]string{"dn", "memberOf"},
			nil,
		)
		sr, err := conn.Search(searchRequest)
		if err != nil {
			return "", nil, err
		}
		if len(sr.Entries) != 1 {
			log.Printf("User does not exist or too many entries returned")
			continue
		}
		userDN := sr.Entries[0].DN
		userGroups := sr.Entries[0].GetAttributeValues("memberOf")
		return userDN, userGroups, nil
	}
	return "", nil, nil
}

func getSimpleUserAttributes(conn *ldap.Conn, UserSearchBaseDNs []string,
	UserSearchFilter string, username string, attributes []string) (m map[string][]string, err error) {
	for _, searchDN := range UserSearchBaseDNs {
		searchRequest := ldap.NewSearchRequest(
			searchDN,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			//fmt.Sprintf("(&(objectClass=organizationalPerson)&(uid=%s))", username),
			fmt.Sprintf(UserSearchFilter, username),
			attributes,
			nil,
		)
		sr, err := conn.Search(searchRequest)
		if err != nil {
			return nil, err
		}
		if len(sr.Entries) != 1 {
			log.Printf("User does not exist or too many entries returned")
			continue
		}
		m = make(map[string][]string)
		for _, attr := range attributes {
			m[attr] = sr.Entries[0].GetAttributeValues(attr)
		}
		return m, nil
	}
	err = errors.New("user not found or too many users found")
	return nil, err
}

func extractCNFromDNString(input []string) (output []string, err error) {
	re := regexp.MustCompile("^cn=([^,]+),.*")
	log.Printf("input=%v ", input)
	for _, dn := range input {
		matches := re.FindStringSubmatch(dn)
		if len(matches) == 2 {
			output = append(output, matches[1])
		} else {
			log.Printf("dn='%s' matches=%v", dn, matches)
			output = append(output, dn)
		}
	}
	return output, nil
}

func getUserGroups_RFC2307bis(conn *ldap.Conn, UserSearchBaseDNs []string,
	UserSearchFilter string, username string) ([]string, error) {
	dn, groupDNs, err := getUserDNAndSimpleGroups(conn, UserSearchBaseDNs, UserSearchFilter, username)
	if err != nil {
		return nil, err
	}
	if dn == "" {
		err := errors.New("User does not exist or too many entries returned")
		return nil, err
	}
	groupCNs, err := extractCNFromDNString(groupDNs)
	if err != nil {
		return nil, err
	}
	return groupCNs, nil
}

func getUserGroups_RFC2307(conn *ldap.Conn, GroupSearchBaseDNs []string,
	groupSearchFilter string, username string) (userGroups []string, err error) {
	for _, searchDN := range GroupSearchBaseDNs {
		searchRequest := ldap.NewSearchRequest(
			searchDN,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			fmt.Sprintf(groupSearchFilter, username),
			[]string{"cn"},
			nil,
		)
		sr, err := conn.Search(searchRequest)
		if err != nil {
			log.Printf("error on search request err:%s", err)
			return nil, err
		}
		for _, entry := range sr.Entries {
			userGroups = append(userGroups, entry.GetAttributeValues("cn")...)
		}
	}
	return userGroups, nil
}

func GetLDAPUserGroups(u url.URL, bindDN string, bindPassword string,
	timeoutSecs uint, rootCAs *x509.CertPool,
	username string,
	UserSearchBaseDNs []string, UserSearchFilter string,
	GroupSearchBaseDNs []string, GroupSearchFilter string) ([]string, error) {
	timeout := time.Duration(time.Duration(timeoutSecs) * time.Second)
	conn, _, err := getLDAPConnection(u, timeoutSecs, rootCAs)
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	conn.SetTimeout(timeout)
	conn.Start()
	err = conn.Bind(bindDN, bindPassword)
	if err != nil {
		return nil, err
	}
	rfcGroups, err := getUserGroups_RFC2307(conn, GroupSearchBaseDNs, GroupSearchFilter, username)
	if err != nil {
		return nil, err
	}
	memberGroups, err := getUserGroups_RFC2307bis(conn, UserSearchBaseDNs, UserSearchFilter, username)
	if err != nil {
		return nil, err
	}
	groupMap := make(map[string]bool)
	for _, group := range rfcGroups {
		groupMap[group] = true
	}
	for _, group := range memberGroups {
		groupMap[group] = true
	}
	var userGroups []string
	for group := range groupMap {
		userGroups = append(userGroups, group)
	}
	return userGroups, nil
}

func GetLDAPUserAttributes(u url.URL, bindDN string, bindPassword string,
	timeoutSecs uint, rootCAs *x509.CertPool,
	username string,
	UserSearchBaseDNs []string, UserSearchFilter string,
	attributes []string) (map[string][]string, error) {

	timeout := time.Duration(time.Duration(timeoutSecs) * time.Second)
	conn, _, err := getLDAPConnection(u, timeoutSecs, rootCAs)
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	conn.SetTimeout(timeout)
	conn.Start()
	err = conn.Bind(bindDN, bindPassword)
	if err != nil {
		return nil, err
	}

	return getSimpleUserAttributes(conn, UserSearchBaseDNs,
		UserSearchFilter, username, attributes)
}
<commit_msg>addressing comments<commit_after>package authutil

import (
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"errors"
	"fmt"
	"log"
	"net"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/cviecco/argon2"
	"github.com/foomo/htpasswd"
	"golang.org/x/crypto/bcrypt"
	"gopkg.in/ldap.v2"
)

// The argon2 defaults are:
// t = 3
// m = 12  // memory usage 2^N
// p = 1
// l = 32
// We will use slightly bigger values:

const argon2t = 40
const argon2m = 20
const argon2p = 2
const argon2l = 32

//There is no well defined number for argon2. We define our own
const argon2dPrefix = "$argon2d$"

const randomStringEntropyBytes = 32

func genRandomString() (string, error) {
	size := randomStringEntropyBytes
	rb := make([]byte, size)
	_, err := rand.Read(rb)
	if err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(rb), nil
}

// The format of the hash will be:
// $99d$SALT:HEXVALUE

func Argon2MakeNewHash(password []byte) (string, error) {

	salt, err := genRandomString()
	if err != nil {
		return "", err
	}
	key, err := argon2.Key(password, []byte(salt), argon2t, argon2p, argon2m, argon2l)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%s%s:%x", argon2dPrefix, salt, key), nil

}

// We only support argon2d as is the only pure golang implementation
func Argon2CompareHashAndPassword(hash string, password []byte) error {
	if !strings.HasPrefix(hash, argon2dPrefix) {
		err := errors.New("Dont understand hash format")
		return err
	}
	splitHashString := strings.SplitN(hash, ":", 2)
	hexKey := splitHashString[1]
	salt := splitHashString[0][len(argon2dPrefix):]
	//log.Printf("salt='%s' heykey=%s", salt, hexKey)
	key, err := argon2.Key(password, []byte(salt), argon2t, argon2p, argon2m, argon2l)
	if err != nil {
		return err
	}
	if hexKey == fmt.Sprintf("%x", key) {
		return nil
	}
	return errors.New("invalid password")
	//return nil
}

func CheckHtpasswdUserPassword(username string, password string, htpasswdBytes []byte) (bool, error) {
	//	secrets := HtdigestFileProvider(htpasswdFilename)
	passwords, err := htpasswd.ParseHtpasswd(htpasswdBytes)
	if err != nil {
		return false, err
	}
	hash, ok := passwords[username]
	if !ok {
		return false, nil
	}
	// only understand bcrypt
	if !strings.HasPrefix(hash, "$2y$") {
		err := errors.New("Can only use bcrypt for htpasswd")
		return false, err
	}
	err = bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
	if err != nil {
		return false, nil
	}
	return true, nil

}

func getLDAPConnection(u url.URL, timeoutSecs uint, rootCAs *x509.CertPool) (*ldap.Conn, string, error) {
	if u.Scheme != "ldaps" {
		err := errors.New("Invalid ldap scheme (we only support ldaps")
		return nil, "", err
	}
	//hostnamePort := server + ":636"
	serverPort := strings.Split(u.Host, ":")
	port := "636"
	if len(serverPort) == 2 {
		port = serverPort[1]
	}
	server := serverPort[0]
	hostnamePort := server + ":" + port

	timeout := time.Duration(time.Duration(timeoutSecs) * time.Second)
	start := time.Now()
	tlsConn, err := tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", hostnamePort,
		&tls.Config{ServerName: server, RootCAs: rootCAs})
	if err != nil {
		errorTime := time.Since(start).Seconds() * 1000
		log.Printf("connction failure for:%s (%s)(time(ms)=%v)", server, err.Error(), errorTime)
		return nil, "", err
	}

	// we dont close the tls connection directly  close defer to the new ldap connection
	conn := ldap.NewConn(tlsConn, true)
	return conn, server, nil
}

func CheckLDAPConnection(u url.URL, timeoutSecs uint, rootCAs *x509.CertPool) error {
	conn, _, err := getLDAPConnection(u, timeoutSecs, rootCAs)
	if err != nil {
		return err
	}
	defer conn.Close()
	timeout := time.Duration(time.Duration(timeoutSecs) * time.Second)
	conn.SetTimeout(timeout)
	conn.Start()
	return nil
}

func CheckLDAPUserPassword(u url.URL, bindDN string, bindPassword string, timeoutSecs uint, rootCAs *x509.CertPool) (bool, error) {
	timeout := time.Duration(time.Duration(timeoutSecs) * time.Second)
	conn, server, err := getLDAPConnection(u, timeoutSecs, rootCAs)
	if err != nil {
		return false, err
	}
	defer conn.Close()

	//connectionTime := time.Since(start).Seconds() * 1000

	conn.SetTimeout(timeout)
	conn.Start()
	err = conn.Bind(bindDN, bindPassword)
	if err != nil {
		log.Printf("Bind failure for server:%s bindDN:'%s' (%s)", server, bindDN, err.Error())
		if strings.Contains(err.Error(), "Invalid Credentials") {
			return false, nil
		}
		return false, err
	}
	return true, nil
}

func ParseLDAPURL(ldapUrl string) (*url.URL, error) {
	u, err := url.Parse(ldapUrl)
	if err != nil {
		return nil, err
	}
	if u.Scheme != "ldaps" {
		err := errors.New("Invalid ldap scheme (we only support ldaps")
		return nil, err
	}
	//extract port if any... and if NIL then set it to 636
	return u, nil
}

func getUserDNAndSimpleGroups(conn *ldap.Conn, UserSearchBaseDNs []string, UserSearchFilter string, username string) (string, []string, error) {
	for _, searchDN := range UserSearchBaseDNs {
		searchRequest := ldap.NewSearchRequest(
			searchDN,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			//fmt.Sprintf("(&(objectClass=organizationalPerson)&(uid=%s))", username),
			fmt.Sprintf(UserSearchFilter, username),
			[]string{"dn", "memberOf"},
			nil,
		)
		sr, err := conn.Search(searchRequest)
		if err != nil {
			return "", nil, err
		}
		if len(sr.Entries) != 1 {
			log.Printf("User does not exist or too many entries returned")
			continue
		}
		userDN := sr.Entries[0].DN
		userGroups := sr.Entries[0].GetAttributeValues("memberOf")
		return userDN, userGroups, nil
	}
	return "", nil, nil
}

func getSimpleUserAttributes(conn *ldap.Conn, UserSearchBaseDNs []string,
	UserSearchFilter string, username string, attributes []string) (m map[string][]string, err error) {
	for _, searchDN := range UserSearchBaseDNs {
		searchRequest := ldap.NewSearchRequest(
			searchDN,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			//fmt.Sprintf("(&(objectClass=organizationalPerson)&(uid=%s))", username),
			fmt.Sprintf(UserSearchFilter, username),
			attributes,
			nil,
		)
		sr, err := conn.Search(searchRequest)
		if err != nil {
			return nil, err
		}
		if len(sr.Entries) != 1 {
			log.Printf("User does not exist or too many entries returned")
			continue
		}
		m = make(map[string][]string)
		for _, attr := range attributes {
			m[attr] = sr.Entries[0].GetAttributeValues(attr)
		}
		return m, nil
	}
	err = errors.New("user not found or too many users found")
	return nil, err
}

func extractCNFromDNString(input []string) (output []string, err error) {
	re := regexp.MustCompile("^cn=([^,]+),.*")
	log.Printf("input=%v ", input)
	for _, dn := range input {
		matches := re.FindStringSubmatch(dn)
		if len(matches) == 2 {
			output = append(output, matches[1])
		} else {
			log.Printf("dn='%s' matches=%v", dn, matches)
			output = append(output, dn)
		}
	}
	return output, nil
}

func getUserGroupsRFC2307bis(conn *ldap.Conn, UserSearchBaseDNs []string,
	UserSearchFilter string, username string) ([]string, error) {
	dn, groupDNs, err := getUserDNAndSimpleGroups(conn, UserSearchBaseDNs, UserSearchFilter, username)
	if err != nil {
		return nil, err
	}
	if dn == "" {
		return nil, errors.New("User does not exist or too many entries returned")
	}
	groupCNs, err := extractCNFromDNString(groupDNs)
	if err != nil {
		return nil, err
	}
	return groupCNs, nil
}

func getUserGroupsRFC2307(conn *ldap.Conn, GroupSearchBaseDNs []string,
	groupSearchFilter string, username string) (userGroups []string, err error) {
	for _, searchDN := range GroupSearchBaseDNs {
		searchRequest := ldap.NewSearchRequest(
			searchDN,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			fmt.Sprintf(groupSearchFilter, username),
			[]string{"cn"},
			nil,
		)
		sr, err := conn.Search(searchRequest)
		if err != nil {
			log.Printf("error on search request err:%s", err)
			return nil, err
		}
		for _, entry := range sr.Entries {
			userGroups = append(userGroups, entry.GetAttributeValues("cn")...)
		}
	}
	return userGroups, nil
}

func GetLDAPUserGroups(u url.URL, bindDN string, bindPassword string,
	timeoutSecs uint, rootCAs *x509.CertPool,
	username string,
	UserSearchBaseDNs []string, UserSearchFilter string,
	GroupSearchBaseDNs []string, GroupSearchFilter string) ([]string, error) {
	timeout := time.Duration(time.Duration(timeoutSecs) * time.Second)
	conn, _, err := getLDAPConnection(u, timeoutSecs, rootCAs)
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	conn.SetTimeout(timeout)
	conn.Start()
	err = conn.Bind(bindDN, bindPassword)
	if err != nil {
		return nil, err
	}
	rfcGroups, err := getUserGroupsRFC2307(conn, GroupSearchBaseDNs, GroupSearchFilter, username)
	if err != nil {
		return nil, err
	}
	memberGroups, err := getUserGroupsRFC2307bis(conn, UserSearchBaseDNs, UserSearchFilter, username)
	if err != nil {
		return nil, err
	}
	groupMap := make(map[string]bool)
	for _, group := range rfcGroups {
		groupMap[group] = true
	}
	for _, group := range memberGroups {
		groupMap[group] = true
	}
	var userGroups []string
	for group := range groupMap {
		userGroups = append(userGroups, group)
	}
	return userGroups, nil
}

func GetLDAPUserAttributes(u url.URL, bindDN string, bindPassword string,
	timeoutSecs uint, rootCAs *x509.CertPool,
	username string,
	UserSearchBaseDNs []string, UserSearchFilter string,
	attributes []string) (map[string][]string, error) {

	timeout := time.Duration(time.Duration(timeoutSecs) * time.Second)
	conn, _, err := getLDAPConnection(u, timeoutSecs, rootCAs)
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	conn.SetTimeout(timeout)
	conn.Start()
	err = conn.Bind(bindDN, bindPassword)
	if err != nil {
		return nil, err
	}

	return getSimpleUserAttributes(conn, UserSearchBaseDNs,
		UserSearchFilter, username, attributes)
}
<|endoftext|>


<commit_before>package e2etest

import (
	"context"
	"errors"
	"io/ioutil"
	"reflect"
	"runtime"
	"strings"
	"testing"

	"github.com/andybalholm/crlf"
	"github.com/davecgh/go-spew/spew"
	"github.com/hashicorp/go-version"
	tfjson "github.com/hashicorp/terraform-json"
	"github.com/sergi/go-diff/diffmatchpatch"

	"github.com/hashicorp/terraform-exec/tfexec"
	"github.com/hashicorp/terraform-exec/tfexec/internal/testutil"
)

var (
	showMinVersion = version.Must(version.NewVersion("0.12.0"))

	providerAddressMinVersion = version.Must(version.NewVersion("0.13.0"))
)

func TestShow(t *testing.T) {
	runTest(t, "basic_with_state", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		if tfv.LessThan(showMinVersion) {
			t.Skip("terraform show was added in Terraform 0.12, so test is not valid")
		}

		providerName := "registry.terraform.io/-/null"
		if tfv.LessThan(providerAddressMinVersion) {
			providerName = "null"
		}

		expected := tfjson.State{
			FormatVersion: "0.1",
			// this is the version that wrote state, not the version that is running
			TerraformVersion: "0.12.24",
			Values: &tfjson.StateValues{
				RootModule: &tfjson.StateModule{
					Resources: []*tfjson.StateResource{{
						Address: "null_resource.foo",
						AttributeValues: map[string]interface{}{
							"id":       "5510719323588825107",
							"triggers": nil,
						},
						Mode:         tfjson.ManagedResourceMode,
						Type:         "null_resource",
						Name:         "foo",
						ProviderName: providerName,
					}},
				},
			},
		}

		err := tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.Show(context.Background())
		if err != nil {
			t.Fatal(err)
		}

		if !reflect.DeepEqual(actual, &expected) {
			t.Fatalf("actual: %s\nexpected: %s", spew.Sdump(actual), spew.Sdump(expected))
		}
	})
}

func TestShow_errInitRequired(t *testing.T) {
	runTest(t, "basic", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		if tfv.LessThan(showMinVersion) {
			t.Skip("terraform show was added in Terraform 0.12, so test is not valid")
		}

		var noInit *tfexec.ErrNoInit
		_, err := tf.Show(context.Background())
		if !errors.As(err, &noInit) {
			t.Fatalf("expected error ErrNoInit, got %T: %s", err, err)
		}
	})
}

func TestShow_versionMismatch(t *testing.T) {
	runTest(t, "basic", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		// only testing versions without show
		if tfv.GreaterThanOrEqual(showMinVersion) {
			t.Skip("terraform show was added in Terraform 0.12, so test is not valid")
		}

		var mismatch *tfexec.ErrVersionMismatch
		_, err := tf.Show(context.Background())
		if !errors.As(err, &mismatch) {
			t.Fatalf("expected version mismatch error, got %T %s", err, err)
		}
		if mismatch.Actual != "0.11.14" {
			t.Fatalf("expected version 0.11.14, got %q", mismatch.Actual)
		}
		if mismatch.MinInclusive != "0.12.0" {
			t.Fatalf("expected min 0.12.0, got %q", mismatch.MinInclusive)
		}
		if mismatch.MaxExclusive != "-" {
			t.Fatalf("expected max -, got %q", mismatch.MaxExclusive)
		}
	})
}

// Non-default state files cannot be migrated from 0.12 to 0.13,
// so we maintain one fixture per supported version.
// See github.com/hashicorp/terraform/25920
func TestShowStateFile012(t *testing.T) {
	runTestVersions(t, []string{testutil.Latest012}, "non_default_statefile_012", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		expected := tfjson.State{
			FormatVersion:    "0.1",
			TerraformVersion: "0.12.29",
			Values: &tfjson.StateValues{
				RootModule: &tfjson.StateModule{
					Resources: []*tfjson.StateResource{{
						Address: "null_resource.foo",
						AttributeValues: map[string]interface{}{
							"id":       "3610244792381545397",
							"triggers": nil,
						},
						Mode:         tfjson.ManagedResourceMode,
						Type:         "null_resource",
						Name:         "foo",
						ProviderName: "null",
					}},
				},
			},
		}

		err := tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.ShowStateFile(context.Background(), "statefilefoo")
		if err != nil {
			t.Fatal(err)
		}

		if !reflect.DeepEqual(actual, &expected) {
			t.Fatalf("actual: %s\nexpected: %s", spew.Sdump(actual), spew.Sdump(expected))
		}
	})
}

func TestShowStateFile013(t *testing.T) {
	runTestVersions(t, []string{testutil.Latest013}, "non_default_statefile_013", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		expected := tfjson.State{
			FormatVersion:    "0.1",
			TerraformVersion: "0.13.0",
			Values: &tfjson.StateValues{
				RootModule: &tfjson.StateModule{
					Resources: []*tfjson.StateResource{{
						Address: "null_resource.foo",
						AttributeValues: map[string]interface{}{
							"id":       "3610244792381545397",
							"triggers": nil,
						},
						Mode:         tfjson.ManagedResourceMode,
						Type:         "null_resource",
						Name:         "foo",
						ProviderName: "registry.terraform.io/hashicorp/null",
					}},
				},
			},
		}

		err := tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.ShowStateFile(context.Background(), "statefilefoo")
		if err != nil {
			t.Fatal(err)
		}

		if !reflect.DeepEqual(actual, &expected) {
			t.Fatalf("actual: %s\nexpected: %s", spew.Sdump(actual), spew.Sdump(expected))
		}
	})
}

// Plan files cannot be transferred between different Terraform versions,
// so we maintain one fixture per supported version
func TestShowPlanFile012(t *testing.T) {
	runTestVersions(t, []string{testutil.Latest012}, "non_default_planfile_012", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		// plan file fixture was created in Linux, and is
		// not compatible with Windows
		if runtime.GOOS == "windows" {
			t.Skip("plan file created in 0.12 on Linux is not compatible with Windows")
		}

		providerName := "null"

		expected := tfjson.Plan{
			FormatVersion:    "0.1",
			TerraformVersion: "0.12.29",
			PlannedValues: &tfjson.StateValues{
				RootModule: &tfjson.StateModule{
					Resources: []*tfjson.StateResource{{
						Address: "null_resource.foo",
						AttributeValues: map[string]interface{}{
							"triggers": nil,
						},
						Mode:         tfjson.ManagedResourceMode,
						Type:         "null_resource",
						Name:         "foo",
						ProviderName: providerName,
					}},
				},
			},
			ResourceChanges: []*tfjson.ResourceChange{{
				Address:      "null_resource.foo",
				Mode:         tfjson.ManagedResourceMode,
				Type:         "null_resource",
				Name:         "foo",
				ProviderName: providerName,
				Change: &tfjson.Change{
					Actions:      tfjson.Actions{tfjson.ActionCreate},
					After:        map[string]interface{}{"triggers": nil},
					AfterUnknown: map[string]interface{}{"id": (true)},
				},
			}},
			Config: &tfjson.Config{
				RootModule: &tfjson.ConfigModule{
					Resources: []*tfjson.ConfigResource{{
						Address:           "null_resource.foo",
						Mode:              tfjson.ManagedResourceMode,
						Type:              "null_resource",
						Name:              "foo",
						ProviderConfigKey: "null",
					}},
				},
			},
		}

		err := tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.ShowPlanFile(context.Background(), "planfilefoo")
		if err != nil {
			t.Fatal(err)
		}

		if !reflect.DeepEqual(actual, &expected) {
			t.Fatalf("actual: %s\nexpected: %s", spew.Sdump(actual), spew.Sdump(expected))
		}
	})
}

func TestShowPlanFile013(t *testing.T) {
	runTestVersions(t, []string{testutil.Latest013}, "non_default_planfile_013", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		providerName := "registry.terraform.io/hashicorp/null"

		expected := tfjson.Plan{
			FormatVersion:    "0.1",
			TerraformVersion: "0.13.0",
			PlannedValues: &tfjson.StateValues{
				RootModule: &tfjson.StateModule{
					Resources: []*tfjson.StateResource{{
						Address: "null_resource.foo",
						AttributeValues: map[string]interface{}{
							"triggers": nil,
						},
						Mode:         tfjson.ManagedResourceMode,
						Type:         "null_resource",
						Name:         "foo",
						ProviderName: providerName,
					}},
				},
			},
			ResourceChanges: []*tfjson.ResourceChange{{
				Address:      "null_resource.foo",
				Mode:         tfjson.ManagedResourceMode,
				Type:         "null_resource",
				Name:         "foo",
				ProviderName: providerName,
				Change: &tfjson.Change{
					Actions:      tfjson.Actions{tfjson.ActionCreate},
					After:        map[string]interface{}{"triggers": nil},
					AfterUnknown: map[string]interface{}{"id": true},
				},
			}},
			Config: &tfjson.Config{
				RootModule: &tfjson.ConfigModule{
					Resources: []*tfjson.ConfigResource{{
						Address:           "null_resource.foo",
						Mode:              tfjson.ManagedResourceMode,
						Type:              "null_resource",
						Name:              "foo",
						ProviderConfigKey: "null",
					}},
				},
			},
		}

		err := tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.ShowPlanFile(context.Background(), "planfilefoo")
		if err != nil {
			t.Fatal(err)
		}

		if !reflect.DeepEqual(actual, &expected) {
			t.Fatalf("actual: %s\nexpected: %s", spew.Sdump(actual), spew.Sdump(expected))
		}
	})
}

func TestShowPlanFileRaw012(t *testing.T) {
	runTestVersions(t, []string{testutil.Latest012}, "non_default_planfile_012", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		// plan file fixture was created in Linux, and is
		// not compatible with Windows
		if runtime.GOOS == "windows" {
			t.Skip("plan file created in 0.12 on Linux is not compatible with Windows")
		}

		// crlf will standardize our line endings for us
		f, err := crlf.Open("testdata/non_default_planfile_012/human_readable_output.txt")
		if err != nil {
			t.Fatal(err)
		}
		defer f.Close()
		expected, err := ioutil.ReadAll(f)
		if err != nil {
			t.Fatal(err)
		}

		err = tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.ShowPlanFileRaw(context.Background(), "planfilefoo")
		if err != nil {
			t.Fatal(err)
		}

		if strings.TrimSpace(actual) != strings.TrimSpace(string(expected)) {
			dmp := diffmatchpatch.New()
			diffs := dmp.DiffMain(strings.TrimSpace(actual), strings.TrimSpace(string(expected)), false)
			t.Fatalf("actual:\n\n%s\n\nexpected:\n\n%s\n\ndiff:\n\n%s", actual, string(expected), dmp.DiffPrettyText(diffs))
		}
	})
}

func TestShowPlanFileRaw013(t *testing.T) {
	runTestVersions(t, []string{testutil.Latest013}, "non_default_planfile_013", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		// crlf will standardize our line endings for us
		f, err := crlf.Open("testdata/non_default_planfile_013/human_readable_output.txt")
		if err != nil {
			t.Fatal(err)
		}
		defer f.Close()
		expected, err := ioutil.ReadAll(f)
		if err != nil {
			t.Fatal(err)
		}

		err = tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.ShowPlanFileRaw(context.Background(), "planfilefoo")
		if err != nil {
			t.Fatal(err)
		}

		if strings.TrimSpace(actual) != strings.TrimSpace(string(expected)) {
			dmp := diffmatchpatch.New()
			diffs := dmp.DiffMain(strings.TrimSpace(actual), strings.TrimSpace(string(expected)), false)
			t.Fatalf("actual:\n\n%s\n\nexpected:\n\n%s\n\ndiff:\n\n%s", actual, string(expected), dmp.DiffPrettyText(diffs))
		}
	})
}
<commit_msg>Skip linux-dependent tests on all other OSes<commit_after>package e2etest

import (
	"context"
	"errors"
	"io/ioutil"
	"reflect"
	"runtime"
	"strings"
	"testing"

	"github.com/andybalholm/crlf"
	"github.com/davecgh/go-spew/spew"
	"github.com/hashicorp/go-version"
	tfjson "github.com/hashicorp/terraform-json"
	"github.com/sergi/go-diff/diffmatchpatch"

	"github.com/hashicorp/terraform-exec/tfexec"
	"github.com/hashicorp/terraform-exec/tfexec/internal/testutil"
)

var (
	showMinVersion = version.Must(version.NewVersion("0.12.0"))

	providerAddressMinVersion = version.Must(version.NewVersion("0.13.0"))
)

func TestShow(t *testing.T) {
	runTest(t, "basic_with_state", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		if tfv.LessThan(showMinVersion) {
			t.Skip("terraform show was added in Terraform 0.12, so test is not valid")
		}

		providerName := "registry.terraform.io/-/null"
		if tfv.LessThan(providerAddressMinVersion) {
			providerName = "null"
		}

		expected := tfjson.State{
			FormatVersion: "0.1",
			// this is the version that wrote state, not the version that is running
			TerraformVersion: "0.12.24",
			Values: &tfjson.StateValues{
				RootModule: &tfjson.StateModule{
					Resources: []*tfjson.StateResource{{
						Address: "null_resource.foo",
						AttributeValues: map[string]interface{}{
							"id":       "5510719323588825107",
							"triggers": nil,
						},
						Mode:         tfjson.ManagedResourceMode,
						Type:         "null_resource",
						Name:         "foo",
						ProviderName: providerName,
					}},
				},
			},
		}

		err := tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.Show(context.Background())
		if err != nil {
			t.Fatal(err)
		}

		if !reflect.DeepEqual(actual, &expected) {
			t.Fatalf("actual: %s\nexpected: %s", spew.Sdump(actual), spew.Sdump(expected))
		}
	})
}

func TestShow_errInitRequired(t *testing.T) {
	runTest(t, "basic", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		if tfv.LessThan(showMinVersion) {
			t.Skip("terraform show was added in Terraform 0.12, so test is not valid")
		}

		var noInit *tfexec.ErrNoInit
		_, err := tf.Show(context.Background())
		if !errors.As(err, &noInit) {
			t.Fatalf("expected error ErrNoInit, got %T: %s", err, err)
		}
	})
}

func TestShow_versionMismatch(t *testing.T) {
	runTest(t, "basic", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		// only testing versions without show
		if tfv.GreaterThanOrEqual(showMinVersion) {
			t.Skip("terraform show was added in Terraform 0.12, so test is not valid")
		}

		var mismatch *tfexec.ErrVersionMismatch
		_, err := tf.Show(context.Background())
		if !errors.As(err, &mismatch) {
			t.Fatalf("expected version mismatch error, got %T %s", err, err)
		}
		if mismatch.Actual != "0.11.14" {
			t.Fatalf("expected version 0.11.14, got %q", mismatch.Actual)
		}
		if mismatch.MinInclusive != "0.12.0" {
			t.Fatalf("expected min 0.12.0, got %q", mismatch.MinInclusive)
		}
		if mismatch.MaxExclusive != "-" {
			t.Fatalf("expected max -, got %q", mismatch.MaxExclusive)
		}
	})
}

// Non-default state files cannot be migrated from 0.12 to 0.13,
// so we maintain one fixture per supported version.
// See github.com/hashicorp/terraform/25920
func TestShowStateFile012(t *testing.T) {
	runTestVersions(t, []string{testutil.Latest012}, "non_default_statefile_012", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		expected := tfjson.State{
			FormatVersion:    "0.1",
			TerraformVersion: "0.12.29",
			Values: &tfjson.StateValues{
				RootModule: &tfjson.StateModule{
					Resources: []*tfjson.StateResource{{
						Address: "null_resource.foo",
						AttributeValues: map[string]interface{}{
							"id":       "3610244792381545397",
							"triggers": nil,
						},
						Mode:         tfjson.ManagedResourceMode,
						Type:         "null_resource",
						Name:         "foo",
						ProviderName: "null",
					}},
				},
			},
		}

		err := tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.ShowStateFile(context.Background(), "statefilefoo")
		if err != nil {
			t.Fatal(err)
		}

		if !reflect.DeepEqual(actual, &expected) {
			t.Fatalf("actual: %s\nexpected: %s", spew.Sdump(actual), spew.Sdump(expected))
		}
	})
}

func TestShowStateFile013(t *testing.T) {
	runTestVersions(t, []string{testutil.Latest013}, "non_default_statefile_013", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		expected := tfjson.State{
			FormatVersion:    "0.1",
			TerraformVersion: "0.13.0",
			Values: &tfjson.StateValues{
				RootModule: &tfjson.StateModule{
					Resources: []*tfjson.StateResource{{
						Address: "null_resource.foo",
						AttributeValues: map[string]interface{}{
							"id":       "3610244792381545397",
							"triggers": nil,
						},
						Mode:         tfjson.ManagedResourceMode,
						Type:         "null_resource",
						Name:         "foo",
						ProviderName: "registry.terraform.io/hashicorp/null",
					}},
				},
			},
		}

		err := tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.ShowStateFile(context.Background(), "statefilefoo")
		if err != nil {
			t.Fatal(err)
		}

		if !reflect.DeepEqual(actual, &expected) {
			t.Fatalf("actual: %s\nexpected: %s", spew.Sdump(actual), spew.Sdump(expected))
		}
	})
}

// Plan files cannot be transferred between different Terraform versions,
// so we maintain one fixture per supported version
func TestShowPlanFile012_linux(t *testing.T) {
	runTestVersions(t, []string{testutil.Latest012}, "non_default_planfile_012", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		if runtime.GOOS != "linux" {
			t.Skip("plan file created in 0.12 on Linux is not compatible with other systems")
		}

		providerName := "null"

		expected := tfjson.Plan{
			FormatVersion:    "0.1",
			TerraformVersion: "0.12.29",
			PlannedValues: &tfjson.StateValues{
				RootModule: &tfjson.StateModule{
					Resources: []*tfjson.StateResource{{
						Address: "null_resource.foo",
						AttributeValues: map[string]interface{}{
							"triggers": nil,
						},
						Mode:         tfjson.ManagedResourceMode,
						Type:         "null_resource",
						Name:         "foo",
						ProviderName: providerName,
					}},
				},
			},
			ResourceChanges: []*tfjson.ResourceChange{{
				Address:      "null_resource.foo",
				Mode:         tfjson.ManagedResourceMode,
				Type:         "null_resource",
				Name:         "foo",
				ProviderName: providerName,
				Change: &tfjson.Change{
					Actions:      tfjson.Actions{tfjson.ActionCreate},
					After:        map[string]interface{}{"triggers": nil},
					AfterUnknown: map[string]interface{}{"id": (true)},
				},
			}},
			Config: &tfjson.Config{
				RootModule: &tfjson.ConfigModule{
					Resources: []*tfjson.ConfigResource{{
						Address:           "null_resource.foo",
						Mode:              tfjson.ManagedResourceMode,
						Type:              "null_resource",
						Name:              "foo",
						ProviderConfigKey: "null",
					}},
				},
			},
		}

		err := tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.ShowPlanFile(context.Background(), "planfilefoo")
		if err != nil {
			t.Fatal(err)
		}

		if !reflect.DeepEqual(actual, &expected) {
			t.Fatalf("actual: %s\nexpected: %s", spew.Sdump(actual), spew.Sdump(expected))
		}
	})
}

func TestShowPlanFile013(t *testing.T) {
	runTestVersions(t, []string{testutil.Latest013}, "non_default_planfile_013", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		providerName := "registry.terraform.io/hashicorp/null"

		expected := tfjson.Plan{
			FormatVersion:    "0.1",
			TerraformVersion: "0.13.0",
			PlannedValues: &tfjson.StateValues{
				RootModule: &tfjson.StateModule{
					Resources: []*tfjson.StateResource{{
						Address: "null_resource.foo",
						AttributeValues: map[string]interface{}{
							"triggers": nil,
						},
						Mode:         tfjson.ManagedResourceMode,
						Type:         "null_resource",
						Name:         "foo",
						ProviderName: providerName,
					}},
				},
			},
			ResourceChanges: []*tfjson.ResourceChange{{
				Address:      "null_resource.foo",
				Mode:         tfjson.ManagedResourceMode,
				Type:         "null_resource",
				Name:         "foo",
				ProviderName: providerName,
				Change: &tfjson.Change{
					Actions:      tfjson.Actions{tfjson.ActionCreate},
					After:        map[string]interface{}{"triggers": nil},
					AfterUnknown: map[string]interface{}{"id": true},
				},
			}},
			Config: &tfjson.Config{
				RootModule: &tfjson.ConfigModule{
					Resources: []*tfjson.ConfigResource{{
						Address:           "null_resource.foo",
						Mode:              tfjson.ManagedResourceMode,
						Type:              "null_resource",
						Name:              "foo",
						ProviderConfigKey: "null",
					}},
				},
			},
		}

		err := tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.ShowPlanFile(context.Background(), "planfilefoo")
		if err != nil {
			t.Fatal(err)
		}

		if !reflect.DeepEqual(actual, &expected) {
			t.Fatalf("actual: %s\nexpected: %s", spew.Sdump(actual), spew.Sdump(expected))
		}
	})
}

func TestShowPlanFileRaw012_linux(t *testing.T) {
	runTestVersions(t, []string{testutil.Latest012}, "non_default_planfile_012", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		if runtime.GOOS != "linux" {
			t.Skip("plan file created in 0.12 on Linux is not compatible with other systems")
		}

		// crlf will standardize our line endings for us
		f, err := crlf.Open("testdata/non_default_planfile_012/human_readable_output.txt")
		if err != nil {
			t.Fatal(err)
		}
		defer f.Close()
		expected, err := ioutil.ReadAll(f)
		if err != nil {
			t.Fatal(err)
		}

		err = tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.ShowPlanFileRaw(context.Background(), "planfilefoo")
		if err != nil {
			t.Fatal(err)
		}

		if strings.TrimSpace(actual) != strings.TrimSpace(string(expected)) {
			dmp := diffmatchpatch.New()
			diffs := dmp.DiffMain(strings.TrimSpace(actual), strings.TrimSpace(string(expected)), false)
			t.Fatalf("actual:\n\n%s\n\nexpected:\n\n%s\n\ndiff:\n\n%s", actual, string(expected), dmp.DiffPrettyText(diffs))
		}
	})
}

func TestShowPlanFileRaw013(t *testing.T) {
	runTestVersions(t, []string{testutil.Latest013}, "non_default_planfile_013", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
		// crlf will standardize our line endings for us
		f, err := crlf.Open("testdata/non_default_planfile_013/human_readable_output.txt")
		if err != nil {
			t.Fatal(err)
		}
		defer f.Close()
		expected, err := ioutil.ReadAll(f)
		if err != nil {
			t.Fatal(err)
		}

		err = tf.Init(context.Background())
		if err != nil {
			t.Fatalf("error running Init in test directory: %s", err)
		}

		actual, err := tf.ShowPlanFileRaw(context.Background(), "planfilefoo")
		if err != nil {
			t.Fatal(err)
		}

		if strings.TrimSpace(actual) != strings.TrimSpace(string(expected)) {
			dmp := diffmatchpatch.New()
			diffs := dmp.DiffMain(strings.TrimSpace(actual), strings.TrimSpace(string(expected)), false)
			t.Fatalf("actual:\n\n%s\n\nexpected:\n\n%s\n\ndiff:\n\n%s", actual, string(expected), dmp.DiffPrettyText(diffs))
		}
	})
}
<|endoftext|>


<commit_before>// Copyright (c) 2014, Los Alamos National Security, LLC All rights reserved.
//
// This software was produced under U.S. Government contract DE-AC52-06NA25396
// for Los Alamos National Laboratory (LANL), which is operated by Los Alamos
// National Security, LLC for the U.S. Department of Energy. The U.S. Government
// has rights to use, reproduce, and distribute this software.  NEITHER THE
// GOVERNMENT NOR LOS ALAMOS NATIONAL SECURITY, LLC MAKES ANY WARRANTY, EXPRESS
// OR IMPLIED, OR ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE.  If
// software is modified to produce derivative works, such modified software
// should be clearly marked, so as not to confuse it with the version available
// from LANL.
//
// Additionally, redistribution and use in source and binary forms, with or
// without modification, are permitted provided that the following conditions
// are met:
//
// . Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// . Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// . Neither the name of Los Alamos National Security, LLC, Los Alamos National
//   Laboratory, LANL, the U.S. Government, nor the names of its contributors
//   may be used to endorse or promote products derived from this software
//   without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY LOS ALAMOS NATIONAL SECURITY, LLC AND
// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
// NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL LOS ALAMOS NATIONAL
// SECURITY, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// LA-CC 10-123

// A simple 2D heat transfer simulation in Go by Samuel K. Gutierrez

// To Build:
// go build heat-tx.go

// To Profile:
// Build
// ./heat-tx -cpuprofile=heat-tx.prof
// go tool pprof ./heat-tx ./heat-tx.prof

// To Plot (gnuplot):
// plot './heat-img.dat' matrix with image

package main

import (
    "flag"
    "strconv"
    "fmt"
    "bufio"
    "os"
    "log"
    "runtime/pprof"
)

// Application constants
const (
    // Application Name
    AppName string = "go-heat-tx"
    // Application version string
    AppVerStr string = "0.1"
    // Max simulation time
    TMax uint64 = 1024
    // Mesh size (x and y)
    N uint64 = 512
    // Thermal conductivity
    ThermCond float64 = 0.6
    // Some constant
    K float64 = 0.4
)

// 2D mesh
type Mesh struct {
    nx, ny uint64 // mesh size in x and y
    cells [][]float64 // mesh cells
}

type SimParams struct {
    // Thermal conductivity
    c float64
    deltaS float64
    // time interval
    deltaT float64
    // Max sim iterations
    tMax uint64
}

type HeatTxSim struct {
    // The meshes
    newMesh, oldMesh *Mesh
    // Simulation parameters
    params *SimParams
}

// NewMesh returns an empty mesh of the specified width and height
func NewMesh(x, y uint64) *Mesh {
    cells := make([][]float64, x)
    for i := range cells {
        cells[i] = make([]float64, y)
    }
    // **remember** unlike C, we can return the address of a local variable.
    // in fact, this returns a fresh instance each time the following code is
    // evaluated - w00t.
    return &Mesh{nx: x, ny: y, cells: cells}
}

// NewSimParams returns a new set of initialized simulation parameters based on
// the provided input.
func NewSimParams(nx uint64, thermCond float64, tMax uint64) *SimParams {
    fmt.Println("o initializing simulation parameters...")
    ds := 1.0 / (float64(nx) + 1.0)
    dt := (ds * ds) / (4.0 * thermCond)
    sp := &SimParams{c: thermCond, deltaS: ds, deltaT: dt, tMax: tMax}
    fmt.Print(sp)
    return sp
}

// Nice SimParams printing
func (p *SimParams) String() string {
    pStr := ""
    pStr += fmt.Sprintf(". max_t: %d\n", p.tMax)
    pStr += fmt.Sprintf(". c: %f\n", p.c)
    pStr += fmt.Sprintf(". delta_s: %f\n", p.deltaS)
    pStr += fmt.Sprintf(". delta_t: %f\n", p.deltaT)
    pStr += "\n"
    return pStr
}

// Nice Mesh String representation
func (m *Mesh) String() string {
    mStr := ""
    for i := range m.cells {
        for j := range m.cells[i] {
            mStr += strconv.FormatFloat(m.cells[i][j], 'e', 1, 64) + " "
        }
        mStr += "\n"
    }
    return mStr
}

func NewHeatTxSim(x, y uint64,  thermCond float64, tMax uint64) *HeatTxSim {
    return &HeatTxSim{params: NewSimParams(x, thermCond, tMax),
                      newMesh: NewMesh(x, y), oldMesh: NewMesh(x, y)}
}

func (m *Mesh) SetInitConds() {
    x0 := m.nx / 2
    y0 := m.ny / 2
    x  := m.nx / 4
    y  := uint64(0)
    radiusErr := int64(1 - x)

    for x >= y {
        m.cells[ x + x0][ y + y0] = K
        m.cells[ x + x0][ y + y0] = K * .50
        m.cells[ y + x0][ x + y0] = K * .60
        m.cells[-x + x0][ y + y0] = K * .70
        m.cells[-y + x0][ x + y0] = K * .80
        m.cells[-x + x0][-y + y0] = K * .70
        m.cells[-y + x0][-x + y0] = K * .60
        m.cells[ x + x0][-y + y0] = K * .50
        m.cells[ y + x0][-x + y0] = K
        y++
        if radiusErr < 0 {
            radiusErr += int64(2 * y + 1)
        } else {
            x--
            radiusErr += int64(2 * (y - x + 1))
        }
    }
}

// Runs the simulation
func (s *HeatTxSim) Run() {
    nx := len(s.oldMesh.cells) - 1
    ny := len(s.oldMesh.cells[0]) - 1
    ds2 := s.params.deltaS * s.params.deltaS
    cdtods2 := (s.params.c * s.params.deltaT) / ds2
    tMax := s.params.tMax;
    newMesh := s.newMesh
    oldMesh := s.oldMesh

    fmt.Println("o starting simulation...")
    for t := uint64(0); t < tMax; t++ {
        if t % 100 == 0 {
            fmt.Println(". starting iteration", t, "of", tMax)
        }
        for i := 1; i < nx; i++ {
            for j := 1; j < ny; j++ {
                newMesh.cells[i][j] =
                    oldMesh.cells[i][j] +
                    (cdtods2 *
                     (oldMesh.cells[i + 1][j] +
                      oldMesh.cells[i - 1][j] -
                      4.0 * oldMesh.cells[i][j] +
                      oldMesh.cells[i][j + 1] +
                      oldMesh.cells[i][j - 1]))
            }
        }
        // swap old and new - this is just a pointer swap
        oldMesh, newMesh = newMesh, oldMesh
        // Constant heat source
        oldMesh.SetInitConds()
    }
}

// Dumps to a text file with the current newMesh cell values
func (s *HeatTxSim) Dump() error {
    dumpFile, err := os.Create("heat-img.dat")
    if err != nil { return err }
    defer func() {
        if err := dumpFile.Close(); err != nil {
            panic(err)
        }
    }()
    // Create a new buffered writer
    w := bufio.NewWriter(dumpFile)
    for i := range s.newMesh.cells {
        for j := range s.newMesh.cells[0] {
            fmt.Fprintf(w, "%f", s.newMesh.cells[i][j])
            if j != len(s.newMesh.cells) - 1 {
                fmt.Fprintf(w, " ")
            }
        }
        fmt.Fprintln(w)
    }
    return w.Flush()
}

func main() {
    var cpuprofile = flag.String("cpuprofile", "", "write CPU profile to file")
    // Parse user input
    flag.Parse()
    // Determine whther or not CPU profiling is on
    if *cpuprofile != "" {
        f, err := os.Create(*cpuprofile)
        if err != nil {
            log.Fatal(err)
        }
        pprof.StartCPUProfile(f)
        defer pprof.StopCPUProfile()
    }
    // Let the games begin
    fmt.Println("o", AppName, AppVerStr)
    sim := NewHeatTxSim(N, N, ThermCond, TMax)
    sim.oldMesh.SetInitConds()
    sim.Run()
    err := sim.Dump()
    if (err != nil) { panic(err) }
}
<commit_msg>heat-tx: Go performance improvment by @lionkov.<commit_after>// Copyright (c) 2014, Los Alamos National Security, LLC All rights reserved.
//
// This software was produced under U.S. Government contract DE-AC52-06NA25396
// for Los Alamos National Laboratory (LANL), which is operated by Los Alamos
// National Security, LLC for the U.S. Department of Energy. The U.S. Government
// has rights to use, reproduce, and distribute this software.  NEITHER THE
// GOVERNMENT NOR LOS ALAMOS NATIONAL SECURITY, LLC MAKES ANY WARRANTY, EXPRESS
// OR IMPLIED, OR ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE.  If
// software is modified to produce derivative works, such modified software
// should be clearly marked, so as not to confuse it with the version available
// from LANL.
//
// Additionally, redistribution and use in source and binary forms, with or
// without modification, are permitted provided that the following conditions
// are met:
//
// . Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// . Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// . Neither the name of Los Alamos National Security, LLC, Los Alamos National
//   Laboratory, LANL, the U.S. Government, nor the names of its contributors
//   may be used to endorse or promote products derived from this software
//   without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY LOS ALAMOS NATIONAL SECURITY, LLC AND
// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
// NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL LOS ALAMOS NATIONAL
// SECURITY, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// LA-CC 10-123

// A simple 2D heat transfer simulation in Go by Samuel K. Gutierrez

// To Build:
// go build heat-tx.go

// To Profile:
// Build
// ./heat-tx -cpuprofile=heat-tx.prof
// go tool pprof ./heat-tx ./heat-tx.prof

// To Plot (gnuplot):
// plot './heat-img.dat' matrix with image

package main

import (
    "flag"
    "strconv"
    "fmt"
    "bufio"
    "os"
    "log"
    "runtime/pprof"
)

// Application constants
const (
    // Application Name
    AppName string = "go-heat-tx"
    // Application version string
    AppVerStr string = "0.1"
    // Max simulation time
    TMax uint64 = 1024
    // Mesh size (x and y)
    N uint64 = 512
    // Thermal conductivity
    ThermCond float64 = 0.6
    // Some constant
    K float64 = 0.4
)

// 2D mesh
type Mesh struct {
    nx, ny uint64 // mesh size in x and y
    cells [][]float64 // mesh cells
}

type SimParams struct {
    // Thermal conductivity
    c float64
    deltaS float64
    // time interval
    deltaT float64
    // Max sim iterations
    tMax uint64
}

type HeatTxSim struct {
    // The meshes
    newMesh, oldMesh *Mesh
    // Simulation parameters
    params *SimParams
}

// NewMesh returns an empty mesh of the specified width and height
func NewMesh(x, y uint64) *Mesh {
    cells := make([][]float64, x)
    for i := range cells {
        cells[i] = make([]float64, y)
    }
    // **remember** unlike C, we can return the address of a local variable.
    // in fact, this returns a fresh instance each time the following code is
    // evaluated - w00t.
    return &Mesh{nx: x, ny: y, cells: cells}
}

// NewSimParams returns a new set of initialized simulation parameters based on
// the provided input.
func NewSimParams(nx uint64, thermCond float64, tMax uint64) *SimParams {
    fmt.Println("o initializing simulation parameters...")
    ds := 1.0 / (float64(nx) + 1.0)
    dt := (ds * ds) / (4.0 * thermCond)
    sp := &SimParams{c: thermCond, deltaS: ds, deltaT: dt, tMax: tMax}
    fmt.Print(sp)
    return sp
}

// Nice SimParams printing
func (p *SimParams) String() string {
    pStr := ""
    pStr += fmt.Sprintf(". max_t: %d\n", p.tMax)
    pStr += fmt.Sprintf(". c: %f\n", p.c)
    pStr += fmt.Sprintf(". delta_s: %f\n", p.deltaS)
    pStr += fmt.Sprintf(". delta_t: %f\n", p.deltaT)
    pStr += "\n"
    return pStr
}

// Nice Mesh String representation
func (m *Mesh) String() string {
    mStr := ""
    for i := range m.cells {
        for j := range m.cells[i] {
            mStr += strconv.FormatFloat(m.cells[i][j], 'e', 1, 64) + " "
        }
        mStr += "\n"
    }
    return mStr
}

func NewHeatTxSim(x, y uint64,  thermCond float64, tMax uint64) *HeatTxSim {
    return &HeatTxSim{params: NewSimParams(x, thermCond, tMax),
                      newMesh: NewMesh(x, y), oldMesh: NewMesh(x, y)}
}

func (m *Mesh) SetInitConds() {
    x0 := m.nx / 2
    y0 := m.ny / 2
    x  := m.nx / 4
    y  := uint64(0)
    radiusErr := int64(1 - x)

    for x >= y {
        m.cells[ x + x0][ y + y0] = K
        m.cells[ x + x0][ y + y0] = K * .50
        m.cells[ y + x0][ x + y0] = K * .60
        m.cells[-x + x0][ y + y0] = K * .70
        m.cells[-y + x0][ x + y0] = K * .80
        m.cells[-x + x0][-y + y0] = K * .70
        m.cells[-y + x0][-x + y0] = K * .60
        m.cells[ x + x0][-y + y0] = K * .50
        m.cells[ y + x0][-x + y0] = K
        y++
        if radiusErr < 0 {
            radiusErr += int64(2 * y + 1)
        } else {
            x--
            radiusErr += int64(2 * (y - x + 1))
        }
    }
}

// Runs the simulation
func (s *HeatTxSim) Run() {
    nx := len(s.oldMesh.cells) - 1
    ny := len(s.oldMesh.cells[0]) - 1
    ds2 := s.params.deltaS * s.params.deltaS
    cdtods2 := (s.params.c * s.params.deltaT) / ds2
    tMax := s.params.tMax;
    newMesh := s.newMesh
    oldMesh := s.oldMesh

    fmt.Println("o starting simulation...")
    for t := uint64(0); t < tMax; t++ {
        if t % 100 == 0 {
            fmt.Println(". starting iteration", t, "of", tMax)
        }
        for i := 1; i < nx; i++ {
            nci := newMesh.cells[i]
            oci := oldMesh.cells[i]
            ocip := oldMesh.cells[i - 1]
            ocin := oldMesh.cells[i + 1]
            for j := 1; j < ny; j++ {
                nci[j] = oci[j] + (cdtods2 * (ocin[j] + ocip[j] - 4.0 *
                                   oci[j] + oci[j + 1] + oci[j - 1]))
            }
        }
        // swap old and new - this is just a pointer swap
        oldMesh, newMesh = newMesh, oldMesh
        // Constant heat source
        oldMesh.SetInitConds()
    }
}

// Dumps to a text file with the current newMesh cell values
func (s *HeatTxSim) Dump() error {
    dumpFile, err := os.Create("heat-img.dat")
    if err != nil { return err }
    defer func() {
        if err := dumpFile.Close(); err != nil {
            panic(err)
        }
    }()
    // Create a new buffered writer
    w := bufio.NewWriter(dumpFile)
    for i := range s.newMesh.cells {
        for j := range s.newMesh.cells[0] {
            fmt.Fprintf(w, "%f", s.newMesh.cells[i][j])
            if j != len(s.newMesh.cells) - 1 {
                fmt.Fprintf(w, " ")
            }
        }
        fmt.Fprintln(w)
    }
    return w.Flush()
}

func main() {
    var cpuprofile = flag.String("cpuprofile", "", "write CPU profile to file")
    // Parse user input
    flag.Parse()
    // Determine whther or not CPU profiling is on
    if *cpuprofile != "" {
        f, err := os.Create(*cpuprofile)
        if err != nil {
            log.Fatal(err)
        }
        pprof.StartCPUProfile(f)
        defer pprof.StopCPUProfile()
    }
    // Let the games begin
    fmt.Println("o", AppName, AppVerStr)
    sim := NewHeatTxSim(N, N, ThermCond, TMax)
    sim.oldMesh.SetInitConds()
    sim.Run()
    err := sim.Dump()
    if (err != nil) { panic(err) }
}
<|endoftext|>


<commit_before>package httpexport

import (
	"bufio"
	"fmt"
	"github.com/arnehormann/mirror"
	"io"
	"net/http"
	"reflect"
)

func NewTypeServer(addr string) chan<- interface{} {
	typechan := make(chan interface{})
	go func(addr string, inchan <-chan interface{}) {
		server := typeServer{feed: inchan, write: htmlTypeWriter}
		err := http.ListenAndServe(addr, server)
		if err != nil {
			panic(err)
		}
	}(addr, typechan)
	return typechan
}

type typeWriter func(s *typeSession, t *reflect.Type) error

type typeServer struct {
	feed  <-chan interface{}
	write typeWriter
}

type typeSession struct {
	depth int
	buf   *bufio.Writer
}

func (server typeServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
	session := &typeSession{
		depth: 0,
		buf:   bufio.NewWriter(resp),
	}
	var t *reflect.Type
	if req.Method == "POST" {
		readType := reflect.TypeOf(<-server.feed)
		t = &readType
	}
	must(server.write(session, t))
	session.buf.Flush()
}

func must(err error) {
	if err != nil {
		panic(err)
	}
}

func mustIO(processed int, err error) {
	switch {
	case err == nil, err == io.EOF:
		return
	}
	panic(err)
}

// code for html type export

func htmlTypeWriter(session *typeSession, t *reflect.Type) error {
	const submit = `<form method="post"><button type="submit">Next</button></form>`
	if t == nil {
		// serve form on GET requests so favicon.ico and co don't skip object under inspection
		mustIO(session.buf.WriteString(`<!DOCTYPE html><html><body>` + submit + `</body></html>`))
		return nil
	}
	// write leading...
	mustIO(session.buf.WriteString(fmt.Sprintf(`
<!DOCTYPE html>
<title>Go: '%s'</title>%s`, t, submit)))
	// ignore errors for the calls; we can't reasonably handle them unless we add a buffer
	must(mirror.Walk(*t, session.typeToHTML))
	// close all tags
	must(session.typeToHTML(nil, 0, 0))
	must(session.closeHtmlTagsToDepth(0))
	// write closing code...
	mustIO(session.buf.WriteString(`</body></html>`))
	return nil
}

func (session *typeSession) closeHtmlTagsToDepth(depth int) error {
	for d := session.depth - depth; d >= 0; d-- {
		_, err := session.buf.WriteString("
")
		if err != nil {
			return err
		}
	}
	return nil
}

func (session *typeSession) typeToHTML(t *reflect.StructField, typeIndex, depth int) error {
	// for now, we are error-ignorant
	must(session.closeHtmlTagsToDepth(depth))
	if t == nil {
		return nil
	}
	tt := t.Type
	mustIO(session.buf.WriteString(fmt.Sprintf(
		`<div data-kind="%s" data-type="%s" data-size="%d" data-typeid="%d"`,
		tt.Kind(), tt, tt.Size(), typeIndex)))
	if len(t.Index) > 0 {
		mustIO(session.buf.WriteString(fmt.Sprintf(
			` data-field="%s" data-index="%v" data-offset="%d" data-tag="%s" `,
			t.Name, t.Index, t.Offset, t.Tag)))
	}
	switch tt.Kind() {
	case reflect.Chan:
		var direction string
		switch tt.ChanDir() {
		case reflect.RecvDir:
			direction = "receive"
		case reflect.SendDir:
			direction = "send"
		case reflect.BothDir:
			direction = "both"
		}
		session.buf.WriteString(` data-direction="` + direction + `"`)
	case reflect.Map:
		mustIO(session.buf.WriteString(fmt.Sprintf(
			` data-keytype="%s"`, tt.Key())))
	case reflect.Array:
		mustIO(session.buf.WriteString(fmt.Sprintf(
			` data-length="%d"`, tt.Len())))
	case reflect.Func:
		mustIO(session.buf.WriteString(fmt.Sprintf(
			` data-args-in="%d" data-args-out="%d"`, tt.NumIn(), tt.NumOut())))
	}
	mustIO(session.buf.WriteString(`>`))
	session.depth = depth
	return nil
}
<commit_msg>unified html generation code and improved error handling<commit_after>package httpexport

import (
	"bufio"
	"fmt"
	"github.com/arnehormann/mirror"
	"net/http"
	"reflect"
	"strings"
)

func NewTypeServer(addr string) chan<- interface{} {
	typechan := make(chan interface{})
	go func(addr string, inchan <-chan interface{}) {
		server := typeServer{feed: inchan, write: htmlTypeWriter}
		err := http.ListenAndServe(addr, server)
		if err != nil {
			panic(err)
		}
	}(addr, typechan)
	return typechan
}

type typeWriter func(s *typeSession, t *reflect.Type) error

type typeServer struct {
	feed  <-chan interface{}
	write typeWriter
}

type typeSession struct {
	depth int
	buf   *bufio.Writer
	err   error
}

func (server typeServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
	session := &typeSession{
		depth: 0,
		buf:   bufio.NewWriter(resp),
	}
	var t *reflect.Type
	if req.Method == "POST" {
		readType := reflect.TypeOf(<-server.feed)
		t = &readType
	}
	err := server.write(session, t)
	if err != nil {
		panic(err)
	}
	session.buf.Flush()
}

func (session *typeSession) Concat(text string) {
	if session.err != nil {
		return
	}
	_, err := session.buf.WriteString(text)
	session.err = err
}

func (session *typeSession) Concatf(format string, args ...interface{}) {
	if session.err != nil {
		return
	}
	_, err := session.buf.WriteString(fmt.Sprintf(format, args...))
	session.err = err
}

// code for html type export

func htmlTypeWriter(session *typeSession, t *reflect.Type) error {
	const submit = `<form method="post"><button type="submit">Next</button></form>`
	if t == nil {
		// serve form on GET requests so favicon.ico and co don't skip object under inspection
		session.Concat(`<!DOCTYPE html><html><body>` + submit + `</body></html>`)
		return session.err
	}
	// write leading...
	session.Concatf(`
<!DOCTYPE html>
<title>Go: '%s'</title>%s`, *t, submit)
	typeToHtml := func(t *reflect.StructField, typeIndex, depth int) error {
		// for now, we are error-ignorant
		// close open tags
		if session.depth > depth {
			session.Concat(strings.Repeat("
", session.depth-depth))
		}
		// if no type is given, return
		if t == nil {
			return nil
		}
		tt := t.Type
		session.Concatf(
			`<div data-kind="%s" data-type="%s" data-size="%d" data-typeid="%d"`,
			tt.Kind(), tt, tt.Size(), typeIndex)
		if len(t.Index) > 0 {
			session.Concatf(
				` data-field="%s" data-index="%v" data-offset="%d" data-tag="%s" `,
				t.Name, t.Index, t.Offset, t.Tag)
		}
		switch tt.Kind() {
		case reflect.Chan:
			var direction string
			switch tt.ChanDir() {
			case reflect.RecvDir:
				direction = "receive"
			case reflect.SendDir:
				direction = "send"
			case reflect.BothDir:
				direction = "both"
			}
			session.Concat(` data-direction="` + direction + `"`)
		case reflect.Map:
			session.Concatf(` data-keytype="%s"`, tt.Key())
		case reflect.Array:
			session.Concatf(` data-length="%d"`, tt.Len())
		case reflect.Func:
			session.Concatf(` data-args-in="%d" data-args-out="%d"`, tt.NumIn(), tt.NumOut())
		}
		session.Concat(`>`)
		session.depth = depth
		return session.err
	}
	// ignore errors for the calls; we can't reasonably handle them unless we add a buffer
	session.err = mirror.Walk(*t, typeToHtml)
	if session.err != nil {
		return session.err
	}
	// close all tags
	session.err = typeToHtml(nil, 0, 0)
	// write closing code...
	session.Concat(`</body></html>`)
	return session.err
}
<|endoftext|>