<commit_before>package paystack

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/mitchellh/mapstructure"
)

const (
	// library version
	version = "0.1.0"

	// defaultHTTPTimeout is the default timeout on the http client
	defaultHTTPTimeout = 60 * time.Second

	// base URL for all Paystack API requests
	baseURL = "https://api.paystack.co"

	// User agent used when communicating with the Paystack API.
	userAgent = "paystack-go/" + version
)

type service struct {
	client *Client
}

// Client manages communication with the Paystack API
type Client struct {
	common service      // Reuse a single struct instead of allocating one for each service on the heap.
	client *http.Client // HTTP client used to communicate with the API.

	// the API Key used to authenticate all Paystack API requests
	key string

	baseURL *url.URL

	logger Logger
	// Services supported by the Paystack API.
	// Miscellaneous actions are directly implemented on the Client object
	Customer     *CustomerService
	Transaction  *TransactionService
	SubAccount   *SubAccountService
	Plan         *PlanService
	Subscription *SubscriptionService
	Page         *PageService
	Settlement   *SettlementService
	Transfer     *TransferService
	Charge       *ChargeService
	Bank         *BankService
	BulkCharge   *BulkChargeService

	LoggingEnabled bool
	Logger         Logger
}

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

type Metadata map[string]interface{}

// Response represents arbitrary response data
type Response map[string]interface{}

// RequestValues aliased to url.Values as a workaround
type RequestValues url.Values

func (v RequestValues) MarshalJSON() ([]byte, error) {
	m := make(map[string]interface{}, 3)
	for k, val := range v {
		m[k] = val[0]
	}
	return json.Marshal(m)
}

type ListMeta struct {
	Total     int `json:"total"`
	Skipped   int `json:"skipped"`
	PerPage   int `json:"perPage"`
	Page      int `json:"page"`
	PageCount int `json:"pageCount"`
}

// NewClient creates a new Paystack API client with the given API key
// and HTTP client, allowing overriding of the HTTP client to use.
// This is useful if you're running in a Google AppEngine environment
// where the http.DefaultClient is not available.
func NewClient(key string, httpClient *http.Client) *Client {
	if httpClient == nil {
		httpClient = &http.Client{Timeout: defaultHTTPTimeout}
	}

	u, _ := url.Parse(baseURL)
	c := &Client{
		client:         httpClient,
		key:            key,
		baseURL:        u,
		LoggingEnabled: true,
		Logger:         log.New(os.Stderr, "", log.LstdFlags),
	}

	c.common.client = c
	c.Customer = (*CustomerService)(&c.common)
	c.Transaction = (*TransactionService)(&c.common)
	c.SubAccount = (*SubAccountService)(&c.common)
	c.Plan = (*PlanService)(&c.common)
	c.Subscription = (*SubscriptionService)(&c.common)
	c.Page = (*PageService)(&c.common)
	c.Settlement = (*SettlementService)(&c.common)
	c.Transfer = (*TransferService)(&c.common)
	c.Charge = (*ChargeService)(&c.common)
	c.Bank = (*BankService)(&c.common)
	c.BulkCharge = (*BulkChargeService)(&c.common)

	return c
}

// s.client.Call("POST", "/v1/plans", PlanRequest{}, &plan)
func (c *Client) Call(method, path string, body, v interface{}) error {
	var buf io.ReadWriter
	if body != nil {
		buf = new(bytes.Buffer)
		err := json.NewEncoder(buf).Encode(body)
		if err != nil {
			return err
		}
	}
	u, _ := c.baseURL.Parse(path)
	req, err := http.NewRequest(method, u.String(), buf)

	if err != nil {
		if c.LoggingEnabled {
			c.Logger.Printf("Cannot create Paystack request: %v\n", err)
		}
		return err
	}

	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	req.Header.Set("Authorization", "Bearer "+c.key)
	if ua := req.Header.Get("User-Agent"); ua == "" {
		req.Header.Set("User-Agent", userAgent)
	} else {
		req.Header.Set("User-Agent", userAgent+" "+ua)
	}

	if c.LoggingEnabled {
		c.Logger.Printf("Requesting %v %v%v\n", req.Method, req.URL.Host, req.URL.Path)
		c.Logger.Printf("POST request data %v\n", buf)
	}

	start := time.Now()

	resp, err := c.client.Do(req)
	if err != nil {
		return err
	}

	if c.LoggingEnabled {
		c.Logger.Printf("Completed in %v\n", time.Since(start))
	}

	defer resp.Body.Close()
	return c.decodeResponse(resp, v)
}

func (c *Client) ResolveCardBIN(bin int) (Response, error) {
	u := fmt.Sprintf("/decision/bin/%d", bin)
	resp := Response{}
	err := c.Call("GET", u, nil, &resp)

	return resp, err
}

func (c *Client) CheckBalance() (Response, error) {
	resp := Response{}
	err := c.Call("GET", "balance", nil, &resp)
	// check balance 'data' node is an array
	resp2 := resp["data"].([]interface{})[0].(map[string]interface{})
	return resp2, err
}

func (c *Client) GetSessionTimeout() (Response, error) {
	resp := Response{}
	err := c.Call("GET", "/integration/payment_session_timeout", nil, &resp)
	return resp, err
}

func (c *Client) UpdateSessionTimeout(timeout int) (Response, error) {
	data := url.Values{}
	data.Add("timeout", string(timeout))
	resp := Response{}
	u := "/integration/payment_session_timeout"
	err := c.Call("PUT", u, data, &resp)
	return resp, err
}

// INTERNALS
func paginateURL(path string, count, offset int) string {
	return fmt.Sprintf("%s?perPage=%d&page=%d", path, count, offset)
}

func mapstruct(data interface{}, v interface{}) error {
	config := &mapstructure.DecoderConfig{
		Result:           v,
		TagName:          "json",
		WeaklyTypedInput: true,
	}
	decoder, err := mapstructure.NewDecoder(config)
	if err = decoder.Decode(data); err != nil {
		return err
	}
	return nil
}

func mustGetTestKey() string {
	key := os.Getenv("PAYSTACK_KEY")

	if len(key) == 0 {
		panic("PAYSTACK_KEY environment variable is not set\n")
	}

	return key
}

// decodeResponse decodes the JSON response from the Twitter API.
func (c *Client) decodeResponse(httpResp *http.Response, v interface{}) error {
	var resp Response
	respBody, err := ioutil.ReadAll(httpResp.Body)
	json.Unmarshal(respBody, &resp)

	if status, _ := resp["status"].(bool); !status || httpResp.StatusCode >= 400 {
		if c.LoggingEnabled {
			c.Logger.Printf("Paystack error: %+v", err)
		}
		return newAPIError(httpResp)
	}

	if c.LoggingEnabled {
		c.Logger.Printf("Paystack response: %v\n", resp)
	}

	if data, ok := resp["data"]; ok {
		switch t := resp["data"].(type) {
		case map[string]interface{}:
			return mapstruct(data, v)
		default:
			_ = t
			return mapstruct(resp, v)
		}
	}
	// if response data does not contain data key, map entire response to v
	return mapstruct(resp, v)
}
<commit_msg>Rename c.Logger -> c.Log<commit_after>package paystack

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/mitchellh/mapstructure"
)

const (
	// library version
	version = "0.1.0"

	// defaultHTTPTimeout is the default timeout on the http client
	defaultHTTPTimeout = 60 * time.Second

	// base URL for all Paystack API requests
	baseURL = "https://api.paystack.co"

	// User agent used when communicating with the Paystack API.
	userAgent = "paystack-go/" + version
)

type service struct {
	client *Client
}

// Client manages communication with the Paystack API
type Client struct {
	common service      // Reuse a single struct instead of allocating one for each service on the heap.
	client *http.Client // HTTP client used to communicate with the API.

	// the API Key used to authenticate all Paystack API requests
	key string

	baseURL *url.URL

	logger Logger
	// Services supported by the Paystack API.
	// Miscellaneous actions are directly implemented on the Client object
	Customer     *CustomerService
	Transaction  *TransactionService
	SubAccount   *SubAccountService
	Plan         *PlanService
	Subscription *SubscriptionService
	Page         *PageService
	Settlement   *SettlementService
	Transfer     *TransferService
	Charge       *ChargeService
	Bank         *BankService
	BulkCharge   *BulkChargeService

	LoggingEnabled bool
	Log            Logger
}

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

type Metadata map[string]interface{}

// Response represents arbitrary response data
type Response map[string]interface{}

// RequestValues aliased to url.Values as a workaround
type RequestValues url.Values

func (v RequestValues) MarshalJSON() ([]byte, error) {
	m := make(map[string]interface{}, 3)
	for k, val := range v {
		m[k] = val[0]
	}
	return json.Marshal(m)
}

type ListMeta struct {
	Total     int `json:"total"`
	Skipped   int `json:"skipped"`
	PerPage   int `json:"perPage"`
	Page      int `json:"page"`
	PageCount int `json:"pageCount"`
}

// NewClient creates a new Paystack API client with the given API key
// and HTTP client, allowing overriding of the HTTP client to use.
// This is useful if you're running in a Google AppEngine environment
// where the http.DefaultClient is not available.
func NewClient(key string, httpClient *http.Client) *Client {
	if httpClient == nil {
		httpClient = &http.Client{Timeout: defaultHTTPTimeout}
	}

	u, _ := url.Parse(baseURL)
	c := &Client{
		client:         httpClient,
		key:            key,
		baseURL:        u,
		LoggingEnabled: true,
		Log:            log.New(os.Stderr, "", log.LstdFlags),
	}

	c.common.client = c
	c.Customer = (*CustomerService)(&c.common)
	c.Transaction = (*TransactionService)(&c.common)
	c.SubAccount = (*SubAccountService)(&c.common)
	c.Plan = (*PlanService)(&c.common)
	c.Subscription = (*SubscriptionService)(&c.common)
	c.Page = (*PageService)(&c.common)
	c.Settlement = (*SettlementService)(&c.common)
	c.Transfer = (*TransferService)(&c.common)
	c.Charge = (*ChargeService)(&c.common)
	c.Bank = (*BankService)(&c.common)
	c.BulkCharge = (*BulkChargeService)(&c.common)

	return c
}

// s.client.Call("POST", "/v1/plans", PlanRequest{}, &plan)
func (c *Client) Call(method, path string, body, v interface{}) error {
	var buf io.ReadWriter
	if body != nil {
		buf = new(bytes.Buffer)
		err := json.NewEncoder(buf).Encode(body)
		if err != nil {
			return err
		}
	}
	u, _ := c.baseURL.Parse(path)
	req, err := http.NewRequest(method, u.String(), buf)

	if err != nil {
		if c.LoggingEnabled {
			c.Log.Printf("Cannot create Paystack request: %v\n", err)
		}
		return err
	}

	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	req.Header.Set("Authorization", "Bearer "+c.key)
	if ua := req.Header.Get("User-Agent"); ua == "" {
		req.Header.Set("User-Agent", userAgent)
	} else {
		req.Header.Set("User-Agent", userAgent+" "+ua)
	}

	if c.LoggingEnabled {
		c.Log.Printf("Requesting %v %v%v\n", req.Method, req.URL.Host, req.URL.Path)
		c.Log.Printf("POST request data %v\n", buf)
	}

	start := time.Now()

	resp, err := c.client.Do(req)
	if err != nil {
		return err
	}

	if c.LoggingEnabled {
		c.Log.Printf("Completed in %v\n", time.Since(start))
	}

	defer resp.Body.Close()
	return c.decodeResponse(resp, v)
}

func (c *Client) ResolveCardBIN(bin int) (Response, error) {
	u := fmt.Sprintf("/decision/bin/%d", bin)
	resp := Response{}
	err := c.Call("GET", u, nil, &resp)

	return resp, err
}

func (c *Client) CheckBalance() (Response, error) {
	resp := Response{}
	err := c.Call("GET", "balance", nil, &resp)
	// check balance 'data' node is an array
	resp2 := resp["data"].([]interface{})[0].(map[string]interface{})
	return resp2, err
}

func (c *Client) GetSessionTimeout() (Response, error) {
	resp := Response{}
	err := c.Call("GET", "/integration/payment_session_timeout", nil, &resp)
	return resp, err
}

func (c *Client) UpdateSessionTimeout(timeout int) (Response, error) {
	data := url.Values{}
	data.Add("timeout", string(timeout))
	resp := Response{}
	u := "/integration/payment_session_timeout"
	err := c.Call("PUT", u, data, &resp)
	return resp, err
}

// INTERNALS
func paginateURL(path string, count, offset int) string {
	return fmt.Sprintf("%s?perPage=%d&page=%d", path, count, offset)
}

func mapstruct(data interface{}, v interface{}) error {
	config := &mapstructure.DecoderConfig{
		Result:           v,
		TagName:          "json",
		WeaklyTypedInput: true,
	}
	decoder, err := mapstructure.NewDecoder(config)
	if err = decoder.Decode(data); err != nil {
		return err
	}
	return nil
}

func mustGetTestKey() string {
	key := os.Getenv("PAYSTACK_KEY")

	if len(key) == 0 {
		panic("PAYSTACK_KEY environment variable is not set\n")
	}

	return key
}

// decodeResponse decodes the JSON response from the Twitter API.
func (c *Client) decodeResponse(httpResp *http.Response, v interface{}) error {
	var resp Response
	respBody, err := ioutil.ReadAll(httpResp.Body)
	json.Unmarshal(respBody, &resp)

	if status, _ := resp["status"].(bool); !status || httpResp.StatusCode >= 400 {
		if c.LoggingEnabled {
			c.Log.Printf("Paystack error: %+v", err)
		}
		return newAPIError(httpResp)
	}

	if c.LoggingEnabled {
		c.Log.Printf("Paystack response: %v\n", resp)
	}

	if data, ok := resp["data"]; ok {
		switch t := resp["data"].(type) {
		case map[string]interface{}:
			return mapstruct(data, v)
		default:
			_ = t
			return mapstruct(resp, v)
		}
	}
	// if response data does not contain data key, map entire response to v
	return mapstruct(resp, v)
}
<|endoftext|>


<commit_before>package ironman

import (
	"log"
	"path/filepath"

	"github.com/blevesearch/bleve"
	"github.com/ironman-project/ironman/template/manager"
	"github.com/ironman-project/ironman/template/manager/git"
	"github.com/ironman-project/ironman/template/model"
	"github.com/ironman-project/ironman/template/repository"
	brepository "github.com/ironman-project/ironman/template/repository/bleve"
)

const (
	indexName = "templates.index"
)

//Ironman is the one administering the local
type Ironman struct {
	manager     manager.Manager
	modelReader model.Reader
	repository  repository.Repository
	home        string
}

//New returns a new instance of ironman
func New(home string, options ...Option) *Ironman {
	ir := &Ironman{}
	for _, option := range options {
		option(ir)
	}
	if ir.manager == nil {
		manager := git.New(home)
		ir.manager = manager
	}

	if ir.repository == nil {
		indexPath := filepath.Join(home, indexName)
		index, err := buildIndex(indexPath)
		if err != nil {
			log.Fatal("Failed to create ironman templates index", err)
		}
		ir.repository = brepository.New(
			brepository.SetIndex(index),
		)
	}

	if ir.modelReader == nil {
		decoder := model.NewDecoder(model.DecoderTypeYAML)
		modelReader := model.NewFSReader([]string{".git"}, model.MetadataFileExtensionYAML, decoder)
		ir.modelReader = modelReader
	}
	return ir
}

func buildIndex(path string) (bleve.Index, error) {
	// open the index
	index, err := bleve.Open(path)
	if err == bleve.ErrorIndexPathDoesNotExist {
		index, err = brepository.BuildIndex(path)
		if err != nil {
			return nil, err
		}
	} else if err != nil {
		return nil, err
	}
	return index, nil
}

//Install installs a new template based on a template locator
func (i *Ironman) Install(templateLocator string) error {
	ID, err := i.manager.Install(templateLocator)
	if err != nil {
		return err
	}

	templatePath := i.manager.TemplateLocation(ID)
	model, err := i.modelReader.Read(templatePath)

	if err != nil {
		return err
	}

	_, err = i.repository.Index(model)

	if err != nil {
		//rollback manager installation
		_ = i.manager.Uninstall(ID)
		return err
	}

	return nil
}

//Link Creates a symlink to the ironman repository from any path in the filesystem
func (i *Ironman) Link(templatePath, templateID string) error {

	err := i.manager.Link(templatePath, templateID)

	if err != nil {
		return err
	}

	return nil
}

//List returns a list of all the installed ironman templates
func (i *Ironman) List() ([]*model.Template, error) {
	results, err := i.repository.List()
	if err != nil {
		return nil, err
	}

	return results, nil
}

//Uninstall uninstalls an ironman template
func (i *Ironman) Uninstall(templateID string) error {
	err := i.manager.Uninstall(templateID)
	if err != nil {
		return err
	}
	return nil
}

//Unlink unlinks a previously linked ironman template
func (i *Ironman) Unlink(templateID string) error {
	err := i.manager.Unlink(templateID)
	if err != nil {
		return err
	}
	return nil
}

//Update updates an iroman template
func (i *Ironman) Update(templateID string) error {
	err := i.manager.Update(templateID)
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>use repository to validate if template exists before operations<commit_after>package ironman

import (
	"log"
	"path/filepath"

	"github.com/blevesearch/bleve"
	"github.com/ironman-project/ironman/template/manager"
	"github.com/ironman-project/ironman/template/manager/git"
	"github.com/ironman-project/ironman/template/model"
	"github.com/ironman-project/ironman/template/repository"
	brepository "github.com/ironman-project/ironman/template/repository/bleve"
	"github.com/pkg/errors"
)

const (
	indexName = "templates.index"
)

//Ironman is the one administering the local
type Ironman struct {
	manager     manager.Manager
	modelReader model.Reader
	repository  repository.Repository
	home        string
}

//New returns a new instance of ironman
func New(home string, options ...Option) *Ironman {
	ir := &Ironman{}
	for _, option := range options {
		option(ir)
	}
	if ir.manager == nil {
		manager := git.New(home)
		ir.manager = manager
	}

	if ir.repository == nil {
		indexPath := filepath.Join(home, indexName)
		index, err := buildIndex(indexPath)
		if err != nil {
			log.Fatal("Failed to create ironman templates index", err)
		}
		ir.repository = brepository.New(
			brepository.SetIndex(index),
		)
	}

	if ir.modelReader == nil {
		decoder := model.NewDecoder(model.DecoderTypeYAML)
		modelReader := model.NewFSReader([]string{".git"}, model.MetadataFileExtensionYAML, decoder)
		ir.modelReader = modelReader
	}
	return ir
}

func buildIndex(path string) (bleve.Index, error) {
	// open the index
	index, err := bleve.Open(path)
	if err == bleve.ErrorIndexPathDoesNotExist {
		index, err = brepository.BuildIndex(path)
		if err != nil {
			return nil, err
		}
	} else if err != nil {
		return nil, err
	}
	return index, nil
}

//Install installs a new template based on a template locator
func (i *Ironman) Install(templateLocator string) error {
	ID, err := i.manager.Install(templateLocator)
	if err != nil {
		return err
	}

	templatePath := i.manager.TemplateLocation(ID)
	model, err := i.modelReader.Read(templatePath)

	if err != nil {
		return err
	}

	exists, err := i.repository.Exists(model.ID)

	if err != nil {
		return errors.Wrapf(err, "Failed to validate if template already exists %s", model.ID)
	}

	if exists {
		return errors.Errorf("Template already exists %s", model.ID)
	}

	_, err = i.repository.Index(model)

	if err != nil {
		//rollback manager installation
		_ = i.manager.Uninstall(ID)
		return err
	}

	return nil
}

//Link Creates a symlink to the ironman repository from any path in the filesystem
func (i *Ironman) Link(templatePath, templateID string) error {

	err := i.manager.Link(templatePath, templateID)

	if err != nil {
		return err
	}

	return nil
}

//List returns a list of all the installed ironman templates
func (i *Ironman) List() ([]*model.Template, error) {
	results, err := i.repository.List()
	if err != nil {
		return nil, err
	}

	return results, nil
}

//Uninstall uninstalls an ironman template
func (i *Ironman) Uninstall(templateID string) error {

	exists, err := i.repository.Exists(templateID)

	if err != nil {
		return errors.Wrapf(err, "Failed to validate if template exists %s", templateID)
	}

	if !exists {
		return errors.Errorf("Template is not installed")
	}

	err = i.manager.Uninstall(templateID)

	if err != nil {
		return err
	}

	return nil
}

//Unlink unlinks a previously linked ironman template
func (i *Ironman) Unlink(templateID string) error {

	err := i.manager.Unlink(templateID)

	if err != nil {
		return err
	}

	return nil
}

//Update updates an iroman template
func (i *Ironman) Update(templateID string) error {
	exists, err := i.repository.Exists(templateID)

	if err != nil {
		return errors.Wrapf(err, "Failed to validate if template exists %s", templateID)
	}

	if !exists {
		return errors.Errorf("Template is not installed")
	}

	err = i.manager.Update(templateID)

	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package sphere

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

	"github.com/gorilla/websocket"
	"github.com/rs/xid"
)

const (
	// Read buffer size for websocket upgrader
	readBufferSize = 1024
	// Write buffer size for websocker upgrader
	writeBufferSize = 1024
	// Time allowed to write a message to the peer.
	writeWait = 10 * time.Second
	// Time allowed to read the next pong message from the peer.
	pongWait = 60 * time.Second
	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10
	// Maximum message size allowed from peer.
	maxMessageSize = 512
)

var (
	// Websocket Upgrader
	upgrader = websocket.Upgrader{
		ReadBufferSize:  readBufferSize,
		WriteBufferSize: writeBufferSize,
	}
	// Guid to generate globally unique id
	guid = xid.New()
)

// Default creates a new instance of Sphere
func Default(brokers ...IBroker) *Sphere {
	// declare agent
	var broker IBroker
	// set declared agent if parameter exists
	for _, i := range brokers {
		broker = i
		break
	}
	if broker == nil {
		broker = NewSimpleBroker()
	}
	// creates sphere instance
	sphere := &Sphere{
		broker:      broker,
		connections: newConnectionMap(),
		channels:    newChannelMap(),
		models:      newChannelModelMap(),
	}
	return sphere
}

// Sphere represents an entire Websocket instance
type Sphere struct {
	// a broker agent
	broker IBroker
	// list of active connections
	connections connectionmap
	// list of channels
	channels channelmap
	// list of models
	models channelmodelmap
}

// Handler handles and creates websocket connection
func (sphere *Sphere) Handler(w http.ResponseWriter, r *http.Request) {
	if conn, err := NewConnection(w, r); err == nil {
		sphere.connections.Set(conn.id, conn)
		// run connection queue
		go conn.queue()
		// action after connection disconnected
		defer func() {
			// unsubscribe all channels
			for item := range conn.channels.Iter() {
				channel := item.Val
				sphere.unsubscribe(channel.namespace, channel.room, conn)
			}
			// close all send and receive buffers
			conn.close()
			// remove connection from sphere after disconnect
			sphere.connections.Remove(conn.id)
		}()
		for {
			_, msg, err := conn.ReadMessage()
			if err != nil {
				return
			}
			if msg != nil {
				go sphere.process(conn, msg)
			}
		}
	}
}

// Models load channel or event models
func (sphere *Sphere) Models(models ...interface{}) {
	for _, item := range models {
		switch model := item.(type) {
		case IChannels:
			if !sphere.models.Has(model.Namespace()) {
				sphere.models.Set(model.Namespace(), model)
			} else {
				panic(fmt.Sprintf("model \"%s\" is already existed", model.Namespace()))
			}
		}
	}
}

// process parses and processes received message
func (sphere *Sphere) process(conn *Connection, msg []byte) {
	// convert received bytes to Packet object
	p, err := ParsePacket(msg)
	if err != nil {
		LogError(err)
		return
	}
	switch p.Type {
	case PacketTypeChannel:
		if p.Namespace != "" && p.Room != "" {
			// publish message to broker if it is a channel event / message
			p.Machine = sphere.broker.ID()
			sphere.publish(p, conn)
		} else {
			// if namespace or room is not provided, return error message
			p.Error = ErrBadScheme
			conn.send <- p
		}
	case PacketTypeSubscribe:
		if p.Namespace != "" && p.Room != "" {
			// subscribe connection to channel
			err := sphere.subscribe(p.Namespace, p.Room, conn)
			r := p.Response()
			r.SetError(err)
			// return success or failure message to user
			conn.send <- r
		} else {
			// if namespace or room is not provided, return error message
			p.Error = ErrBadScheme
			conn.send <- p
		}
	case PacketTypeUnsubscribe:
		if p.Namespace != "" && p.Room != "" {
			// unsubscribe connection from channel
			if sphere.models.Has(p.Namespace) {
				sphere.unsubscribe(p.Namespace, p.Room, conn)
			} else {
				// if namespace model does not existed, return error
				p.Error = ErrNotSupported
				conn.send <- p
			}
		} else {
			// if namespace or room is not provided, return error message
			p.Error = ErrBadScheme
			conn.send <- p
		}
	case PacketTypePing:
		// ping-pong
		r := p.Response()
		conn.send <- r
	case PacketTypeMessage:
		// receive event message
		conn.receive <- p
	}
}

// channel returns Channel object, channel will be automatually created when autoCreateOpts is true
func (sphere *Sphere) channel(namespace string, room string, autoCreateOpts ...bool) *Channel {
	c := make(chan *Channel)
	autoCreateOpt := false
	for _, opt := range autoCreateOpts {
		autoCreateOpt = opt
		break
	}
	name := sphere.broker.ChannelName(namespace, room)
	go func() {
		if tmp, ok := sphere.channels.Get(name); ok {
			c <- tmp
		} else {
			if autoCreateOpt {
				channel := NewChannel(namespace, room)
				sphere.channels.Set(name, channel)
				c <- channel
			} else {
				c <- nil
			}
		}
	}()
	return <-c
}

// subscribe trigger Broker OnSubscribe action and put connection into channel connections list
func (sphere *Sphere) subscribe(namespace string, room string, conn *Connection) error {
	var model IChannels
	if !sphere.models.Has(namespace) {
		return ErrNotSupported
	}
	if tmp, ok := sphere.models.Get(namespace); ok {
		model = tmp
	} else {
		return ErrNotSupported
	}
	if accept, err := model.Subscribe(room, conn); !accept && err == nil {
		return ErrUnauthorized
	} else if !accept && err != nil {
		return err
	}
	channel := sphere.channel(namespace, room, true)
	if channel == nil {
		return ErrNotFound
	}
	if err := channel.subscribe(conn); err != nil {
		return err
	}
	if !sphere.broker.IsSubscribed(channel.namespace, channel.room) {
		c := make(chan IError)
		go sphere.broker.OnSubscribe(channel, c)
		return <-c
	}
	return nil
}

// unsubscribe trigger Broker OnUnsubscribe action and remove connection from channel connections list
func (sphere *Sphere) unsubscribe(namespace string, room string, conn *Connection) error {
	var model IChannels
	if !sphere.models.Has(namespace) {
		return ErrNotSupported
	}
	if tmp, ok := sphere.models.Get(namespace); ok {
		model = tmp
	} else {
		return ErrNotSupported
	}
	if err := model.Disconnect(room, conn); err != nil {
		return err
	}
	channel := sphere.channel(namespace, room, false)
	if channel == nil {
		return ErrNotFound
	}
	err := channel.unsubscribe(conn)
	if err == nil && channel.connections.Count() == 0 {
		if sphere.broker.IsSubscribed(channel.namespace, channel.room) {
			c := make(chan IError)
			go sphere.broker.OnUnsubscribe(channel, c)
			return <-c
		}
	}
	return err
}

// publish trigger Broker OnPublish action, send message to user from broker
func (sphere *Sphere) publish(p *Packet, conn *Connection) error {
	var model IChannels
	if !sphere.models.Has(p.Namespace) {
		return ErrNotSupported
	}
	if tmp, ok := sphere.models.Get(p.Namespace); ok {
		model = tmp
	} else {
		return ErrNotSupported
	}
	channel := sphere.channel(p.Namespace, p.Room)
	if channel == nil {
		return ErrBadStatus
	}
	if !channel.isSubscribed(conn) {
		return ErrNotSubscribed
	}
	msg := p.Message
	if msg == nil || msg.Event == "" {
		return ErrBadScheme
	}
	res, err := model.Receive(msg.Event, msg.Data)
	if err != nil {
		return err
	}
	d := p.Response()
	if res != "" {
		d.Message.Data = res
	}
	if sphere.broker.IsSubscribed(channel.namespace, channel.room) {
		return sphere.broker.OnPublish(channel, d)
	}
	return ErrServerErrors
}
<commit_msg>message event handler<commit_after>package sphere

import (
	"net/http"
	"time"

	"github.com/gorilla/websocket"
	"github.com/rs/xid"
)

const (
	// Read buffer size for websocket upgrader
	readBufferSize = 1024
	// Write buffer size for websocker upgrader
	writeBufferSize = 1024
	// Time allowed to write a message to the peer.
	writeWait = 10 * time.Second
	// Time allowed to read the next pong message from the peer.
	pongWait = 60 * time.Second
	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10
	// Maximum message size allowed from peer.
	maxMessageSize = 512
)

var (
	// Websocket Upgrader
	upgrader = websocket.Upgrader{
		ReadBufferSize:  readBufferSize,
		WriteBufferSize: writeBufferSize,
	}
	// Guid to generate globally unique id
	guid = xid.New()
)

// Default creates a new instance of Sphere
func Default(brokers ...IBroker) *Sphere {
	// declare agent
	var broker IBroker
	// set declared agent if parameter exists
	for _, i := range brokers {
		broker = i
		break
	}
	if broker == nil {
		broker = NewSimpleBroker()
	}
	// creates sphere instance
	sphere := &Sphere{
		broker:      broker,
		connections: newConnectionMap(),
		channels:    newChannelMap(),
		models:      newChannelModelMap(),
		events:      newEventModelMap(),
	}
	return sphere
}

// Sphere represents an entire Websocket instance
type Sphere struct {
	// a broker agent
	broker IBroker
	// list of active connections
	connections connectionmap
	// list of channels
	channels channelmap
	// list of models
	models channelmodelmap
	// list of events
	events eventmodelmap
}

// Handler handles and creates websocket connection
func (sphere *Sphere) Handler(w http.ResponseWriter, r *http.Request) {
	if conn, err := NewConnection(w, r); err == nil {
		sphere.connections.Set(conn.id, conn)
		// run connection queue
		go conn.queue()
		// action after connection disconnected
		defer func() {
			// unsubscribe all channels
			for item := range conn.channels.Iter() {
				channel := item.Val
				sphere.unsubscribe(channel.namespace, channel.room, conn)
			}
			// close all send and receive buffers
			conn.close()
			// remove connection from sphere after disconnect
			sphere.connections.Remove(conn.id)
		}()
		for {
			_, msg, err := conn.ReadMessage()
			if err != nil {
				return
			}
			if msg != nil {
				go sphere.process(conn, msg)
			}
		}
	}
}

// Models load channel or event models
func (sphere *Sphere) Models(models ...interface{}) {
	for _, item := range models {
		switch model := item.(type) {
		case IChannels:
			if !sphere.models.Has(model.Namespace()) {
				sphere.models.Set(model.Namespace(), model)
			}
		case IEvents:
			if !sphere.events.Has(model.Namespace()) {
				sphere.events.Set(model.Namespace(), model)
			}
		}
	}
}

// process parses and processes received message
func (sphere *Sphere) process(conn *Connection, msg []byte) {
	// convert received bytes to Packet object
	p, err := ParsePacket(msg)
	if err != nil {
		LogError(err)
		return
	}
	switch p.Type {
	case PacketTypeChannel:
		if p.Namespace != "" && p.Room != "" {
			// publish message to broker if it is a channel event / message
			p.Machine = sphere.broker.ID()
			sphere.publish(p, conn)
		} else {
			// if namespace or room is not provided, return error message
			p.Error = ErrBadScheme
			conn.send <- p
		}
	case PacketTypeSubscribe:
		if p.Namespace != "" && p.Room != "" {
			// subscribe connection to channel
			err := sphere.subscribe(p.Namespace, p.Room, conn)
			r := p.Response()
			r.SetError(err)
			// return success or failure message to user
			conn.send <- r
		} else {
			// if namespace or room is not provided, return error message
			p.Error = ErrBadScheme
			conn.send <- p
		}
	case PacketTypeUnsubscribe:
		if p.Namespace != "" && p.Room != "" {
			// unsubscribe connection from channel
			if sphere.models.Has(p.Namespace) {
				sphere.unsubscribe(p.Namespace, p.Room, conn)
			} else {
				// if namespace model does not existed, return error
				p.Error = ErrNotSupported
				conn.send <- p
			}
		} else {
			// if namespace or room is not provided, return error message
			p.Error = ErrBadScheme
			conn.send <- p
		}
	case PacketTypeMessage:
		if p.Namespace != "" {
			// receive event message
			sphere.receive(p, conn)
		} else {
			p.Error = ErrBadScheme
			conn.send <- p
		}
	case PacketTypePing:
		// ping-pong
		r := p.Response()
		conn.send <- r
	}
}

// channel returns Channel object, channel will be automatually created when autoCreateOpts is true
func (sphere *Sphere) channel(namespace string, room string, autoCreateOpts ...bool) *Channel {
	c := make(chan *Channel)
	autoCreateOpt := false
	for _, opt := range autoCreateOpts {
		autoCreateOpt = opt
		break
	}
	name := sphere.broker.ChannelName(namespace, room)
	go func() {
		if tmp, ok := sphere.channels.Get(name); ok {
			c <- tmp
		} else {
			if autoCreateOpt {
				channel := NewChannel(namespace, room)
				sphere.channels.Set(name, channel)
				c <- channel
			} else {
				c <- nil
			}
		}
	}()
	return <-c
}

// subscribe trigger Broker OnSubscribe action and put connection into channel connections list
func (sphere *Sphere) subscribe(namespace string, room string, conn *Connection) IError {
	var model IChannels
	if !sphere.models.Has(namespace) {
		return ErrNotSupported
	}
	if tmp, ok := sphere.models.Get(namespace); ok {
		model = tmp
	} else {
		return ErrNotSupported
	}
	if accept, err := model.Subscribe(room, conn); !accept && err == nil {
		return ErrUnauthorized
	} else if !accept && err != nil {
		return err
	}
	channel := sphere.channel(namespace, room, true)
	if channel == nil {
		return ErrNotFound
	}
	if err := channel.subscribe(conn); err != nil {
		return err
	}
	if !sphere.broker.IsSubscribed(channel.namespace, channel.room) {
		c := make(chan IError)
		go sphere.broker.OnSubscribe(channel, c)
		return <-c
	}
	return nil
}

// unsubscribe trigger Broker OnUnsubscribe action and remove connection from channel connections list
func (sphere *Sphere) unsubscribe(namespace string, room string, conn *Connection) IError {
	var model IChannels
	if !sphere.models.Has(namespace) {
		return ErrNotSupported
	}
	if tmp, ok := sphere.models.Get(namespace); ok {
		model = tmp
	} else {
		return ErrNotSupported
	}
	if err := model.Disconnect(room, conn); err != nil {
		return err
	}
	channel := sphere.channel(namespace, room, false)
	if channel == nil {
		return ErrNotFound
	}
	err := channel.unsubscribe(conn)
	if err == nil && channel.connections.Count() == 0 {
		if sphere.broker.IsSubscribed(channel.namespace, channel.room) {
			c := make(chan IError)
			go sphere.broker.OnUnsubscribe(channel, c)
			return <-c
		}
	}
	return err
}

// publish trigger Broker OnPublish action, send message to user from broker
func (sphere *Sphere) publish(p *Packet, conn *Connection) IError {
	var model IChannels
	if !sphere.models.Has(p.Namespace) {
		return ErrNotSupported
	}
	if tmp, ok := sphere.models.Get(p.Namespace); ok {
		model = tmp
	} else {
		return ErrNotSupported
	}
	channel := sphere.channel(p.Namespace, p.Room)
	if channel == nil {
		return ErrBadStatus
	}
	if !channel.isSubscribed(conn) {
		return ErrNotSubscribed
	}
	msg := p.Message
	if msg == nil || msg.Event == "" {
		return ErrBadScheme
	}
	res, err := model.Receive(msg.Event, msg.Data)
	if err != nil {
		return err
	}
	d := p.Response()
	if res != "" {
		d.Message.Data = res
	}
	if sphere.broker.IsSubscribed(channel.namespace, channel.room) {
		return sphere.broker.OnPublish(channel, d)
	}
	return ErrServerErrors
}

// receive message and event handler
func (sphere *Sphere) receive(p *Packet, conn *Connection) IError {
	var model IEvents
	if !sphere.events.Has(p.Namespace) {
		return ErrNotSupported
	}
	if tmp, ok := sphere.events.Get(p.Namespace); ok {
		model = tmp
	} else {
		return ErrNotSupported
	}
	msg := p.Message
	if msg == nil || msg.Event == "" {
		return ErrBadScheme
	}
	res, err := model.Receive(msg.Event, msg.Data)
	if err != nil {
		return err
	}
	d := p.Response()
	if res != "" {
		d.Message.Data = res
	}
	conn.send <- d
	return nil
}
<|endoftext|>


<commit_before>package download

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"

	"github.com/subutai-io/base/agent/log"
	"github.com/subutai-io/gorjun/db"
)

var (
	path = "/tmp/"
)

type AptItem struct {
	Architecture string `json:"architecture,omitempty"`
	Description  string `json:"description,omitempty"`
	Filename     string `json:"filename,omitempty"`
	Md5Sum       string `json:"md5Sum,omitempty"`
	Name         string `json:"name,omitempty"`
	Package      string `json:"package,omitempty"`
	Version      string `json:"version,omitempty"`
	Size         int64  `json:"size"`
}

type RawItem struct {
	Md5Sum  string `json:"md5Sum,omitempty"`
	Name    string `json:"name,omitempty"`
	Package string `json:"package,omitempty"`
	Version string `json:"version,omitempty"`
	Size    int64  `json:"size"`
}

type ListItem struct {
	Architecture   string `json:"architecture"`
	ConfigContents string `json:"configContents"`
	Extra          struct {
		Lxc_idMap           string `json:"lxc.id_map"`
		Lxc_include         string `json:"lxc.include"`
		Lxc_mount           string `json:"lxc.mount"`
		Lxc_mount_entry     string `json:"lxc.mount.entry"`
		Lxc_network_flags   string `json:"lxc.network.flags"`
		Lxc_network_hwaddr  string `json:"lxc.network.hwaddr"`
		Lxc_network_link    string `json:"lxc.network.link"`
		Lxc_network_type    string `json:"lxc.network.type"`
		Lxc_rootfs          string `json:"lxc.rootfs"`
		Subutai_config_path string `json:"subutai.config.path"`
		Subutai_git_branch  string `json:"subutai.git.branch"`
	} `json:"extra"`
	ID               string `json:"id"`
	Md5Sum           string `json:"md5Sum"`
	Name             string `json:"name"`
	OwnerFprint      string `json:"ownerFprint"`
	Package          string `json:"package"`
	PackagesContents string `json:"packagesContents"`
	Parent           string `json:"parent"`
	Size             int64  `json:"size"`
	Version          string `json:"version"`
}

func Handler(w http.ResponseWriter, r *http.Request) {
	hash := r.URL.Query().Get("hash")
	name := r.URL.Query().Get("name")
	id := r.URL.Query().Get("id")
	if len(id) > 0 {
		hash = id
		tmp := strings.Split(id, ".")
		if len(tmp) == 2 {
			hash = tmp[1]
		}
	}
	if len(hash) == 0 && len(name) == 0 {
		w.Write([]byte("Please specify hash or name"))
		return
	} else if len(name) != 0 {
		hash = db.LastHash(name)
	}
	w.Header().Set("Content-Disposition", "attachment; filename="+db.Read(hash))
	w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
	f, err := os.Open(path + hash)
	log.Check(log.WarnLevel, "Opening file "+path+hash, err)
	fi, _ := f.Stat()
	w.Header().Set("Content-Length", fmt.Sprint(fi.Size()))
	defer f.Close()
	io.Copy(w, f)
}

func List(repo string, w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(""))
	for k, v := range db.List() {
		if db.Info(k)["type"] == repo {
			w.Write([]byte("
" + v + "
"))
		}
	}
	w.Write([]byte(""))
}

func Search(repo string, w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query().Get("q")
	w.Write([]byte(""))
	for k, v := range db.Search(query) {
		w.Write([]byte("
" + v + "
"))
	}
	w.Write([]byte(""))
}

func Info(repo string, r *http.Request) []byte {
	var item, js []byte
	name := r.URL.Query().Get("name")
	version := r.URL.Query().Get("version")
	rtype := r.URL.Query().Get("type")
	id := r.URL.Query().Get("id")

	ids := strings.Split(id, ".")
	if len(ids) > 1 {
		name = db.Read(ids[1])
	}

	counter := 0
	for k, _ := range db.Search(name) {
		info := db.Info(k)
		if info["type"] == repo {
			counter++
			if rtype == "text" && repo == "template" {
				if strings.HasPrefix(info["name"], name) && (len(version) == 0 || info["version"] == version) {
					return ([]byte("public." + k))
				}
				return []byte("")
			}
			f, err := os.Open(path + k)
			defer f.Close()
			log.Check(log.WarnLevel, "Opening file "+path+k, err)
			fi, _ := f.Stat()
			switch repo {
			case "template":
				item, _ = json.Marshal(ListItem{
					Name:         strings.Split(info["name"], "-")[0],
					ID:           info["owner"] + "." + k,
					OwnerFprint:  info["owner"],
					Parent:       info["parent"],
					Version:      info["version"],
					Architecture: strings.ToUpper(info["arch"]),
					Size:         fi.Size(),
					Md5Sum:       k,
				})
			case "apt":
				item, _ = json.Marshal(AptItem{
					Name:         info["name"],
					Md5Sum:       info["Md5Sum"],
					Description:  info["Description"],
					Architecture: info["Architecture"],
					Package:      info["Package"],
					Version:      info["Version"],
					Size:         fi.Size(),
				})
			case "raw":
				item, _ = json.Marshal(RawItem{
					Name:    info["name"],
					Md5Sum:  k,
					Package: info["package"],
					Version: info["version"],
					Size:    fi.Size(),
				})
			}
			if counter > 1 {
				js = append(js, []byte(",")[0])
			}
			js = append(js, item...)
		}
	}
	if counter > 1 {
		js = append([]byte("["), js...)
		js = append(js, []byte("]")[0])
	}
	return js
}
<commit_msg>Fixed multiple output for info<commit_after>package download

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"

	"github.com/subutai-io/base/agent/log"
	"github.com/subutai-io/gorjun/db"
)

var (
	path = "/tmp/"
)

type AptItem struct {
	Architecture string `json:"architecture,omitempty"`
	Description  string `json:"description,omitempty"`
	Filename     string `json:"filename,omitempty"`
	Md5Sum       string `json:"md5Sum,omitempty"`
	Name         string `json:"name,omitempty"`
	Package      string `json:"package,omitempty"`
	Version      string `json:"version,omitempty"`
	Size         int64  `json:"size"`
}

type RawItem struct {
	Md5Sum  string `json:"md5Sum,omitempty"`
	Name    string `json:"name,omitempty"`
	Package string `json:"package,omitempty"`
	Version string `json:"version,omitempty"`
	Size    int64  `json:"size"`
	ID      string `json:"id"`
}

type ListItem struct {
	Architecture   string `json:"architecture"`
	ConfigContents string `json:"configContents"`
	Extra          struct {
		Lxc_idMap           string `json:"lxc.id_map"`
		Lxc_include         string `json:"lxc.include"`
		Lxc_mount           string `json:"lxc.mount"`
		Lxc_mount_entry     string `json:"lxc.mount.entry"`
		Lxc_network_flags   string `json:"lxc.network.flags"`
		Lxc_network_hwaddr  string `json:"lxc.network.hwaddr"`
		Lxc_network_link    string `json:"lxc.network.link"`
		Lxc_network_type    string `json:"lxc.network.type"`
		Lxc_rootfs          string `json:"lxc.rootfs"`
		Subutai_config_path string `json:"subutai.config.path"`
		Subutai_git_branch  string `json:"subutai.git.branch"`
	} `json:"extra"`
	ID               string `json:"id"`
	Md5Sum           string `json:"md5Sum"`
	Name             string `json:"name"`
	OwnerFprint      string `json:"ownerFprint"`
	Package          string `json:"package"`
	PackagesContents string `json:"packagesContents"`
	Parent           string `json:"parent"`
	Size             int64  `json:"size"`
	Version          string `json:"version"`
}

func Handler(w http.ResponseWriter, r *http.Request) {
	hash := r.URL.Query().Get("hash")
	name := r.URL.Query().Get("name")
	id := r.URL.Query().Get("id")
	if len(id) > 0 {
		hash = id
		tmp := strings.Split(id, ".")
		if len(tmp) == 2 {
			hash = tmp[1]
		}
	}
	if len(hash) == 0 && len(name) == 0 {
		w.Write([]byte("Please specify hash or name"))
		return
	} else if len(name) != 0 {
		hash = db.LastHash(name)
	}
	w.Header().Set("Content-Disposition", "attachment; filename="+db.Read(hash))
	w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
	f, err := os.Open(path + hash)
	log.Check(log.WarnLevel, "Opening file "+path+hash, err)
	fi, _ := f.Stat()
	w.Header().Set("Content-Length", fmt.Sprint(fi.Size()))
	defer f.Close()
	io.Copy(w, f)
}

func List(repo string, w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(""))
	for k, v := range db.List() {
		if db.Info(k)["type"] == repo {
			w.Write([]byte("
" + v + "
"))
		}
	}
	w.Write([]byte(""))
}

func Search(repo string, w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query().Get("q")
	w.Write([]byte(""))
	for k, v := range db.Search(query) {
		w.Write([]byte("
" + v + "
"))
	}
	w.Write([]byte(""))
}

func Info(repo string, r *http.Request) []byte {
	var item, js []byte
	name := r.URL.Query().Get("name")
	version := r.URL.Query().Get("version")
	rtype := r.URL.Query().Get("type")
	id := r.URL.Query().Get("id")

	ids := strings.Split(id, ".")
	if len(ids) > 1 {
		name = db.Read(ids[1])
	}

	counter := 0
	for k, _ := range db.Search(name) {
		info := db.Info(k)
		if info["type"] == repo {
			counter++
			if rtype == "text" && repo == "template" {
				if strings.HasPrefix(info["name"], name) && (len(version) == 0 || info["version"] == version) {
					return ([]byte("public." + k))
				}
				return []byte("")
			}
			f, err := os.Open(path + k)
			defer f.Close()
			log.Check(log.WarnLevel, "Opening file "+path+k, err)
			fi, _ := f.Stat()
			switch repo {
			case "template":
				item, _ = json.Marshal(ListItem{
					Name:         strings.Split(info["name"], "-")[0],
					ID:           info["owner"] + "." + k,
					OwnerFprint:  info["owner"],
					Parent:       info["parent"],
					Version:      info["version"],
					Architecture: strings.ToUpper(info["arch"]),
					Size:         fi.Size(),
					Md5Sum:       k,
				})
			case "apt":
				item, _ = json.Marshal(AptItem{
					Name:         info["name"],
					Md5Sum:       info["Md5Sum"],
					Description:  info["Description"],
					Architecture: info["Architecture"],
					Package:      info["Package"],
					Version:      info["Version"],
					Size:         fi.Size(),
				})
			case "raw":
				item, _ = json.Marshal(RawItem{
					Name:    info["name"],
					ID:      k,
					Md5Sum:  k,
					Package: info["package"],
					Version: info["version"],
					Size:    fi.Size(),
				})
			}
			if counter > 1 {
				js = append(js, []byte(",")[0])
			}
			js = append(js, item...)
			if name == strings.Split(info["name"], "-subutai-template")[0] {
				return item
			}
		}
	}
	if counter > 1 {
		js = append([]byte("["), js...)
		js = append(js, []byte("]")[0])
	}
	return js
}
<|endoftext|>


<commit_before>package unit

import (
	"sort"
	"strconv"
	"sync"
)

// Uniter is an interface representing a type that can be converted
// to a unit.
type Uniter interface {
	Unit() *Unit
}

// Dimension is a type representing an SI base dimension or other
// orthogonal dimension. If a new dimension is desired for a
// domain-specific problem, NewDimension should be used. Integers
// should never be cast as type dimension
//	// Good: Create a package constant with an init function
//	var MyDimension unit.Dimension
//	init(){
//		MyDimension = NewDimension("my")
//	}
//	main(){
//		var := MyDimension(28.2)
//	}
type Dimension int

const (
	reserved Dimension = iota
	// SI Base Units
	CurrentDim Dimension
	LengthDim
	LuminousIntensityDim
	MassDim
	TemperatureDim
	TimeDim
	// Other common SI Dimensions
	AngleDim             // e.g. radians
	lastPackageDimension // Used in create dimension
)

var lastCreatedDimension Dimension = lastPackageDimension
var dimensionToSymbol map[Dimension]string = make(map[Dimension]string) // for printing
var symbolToDimension map[string]Dimension = make(map[string]Dimension) // for guaranteeing there aren't two identical symbols

// TODO: Should we actually reserve "common" SI unit symbols ("N", "J", etc.) so there isn't confusion
// TODO: If we have a fancier ParseUnit, maybe the 'reserved' symbols should be a different map
// 		map[string]string which says how they go?
func init() {
	dimensionToSymbol[CurrentDim] = "A"
	symbolToDimension["A"] = CurrentDim
	dimensionToSymbol[LengthDim] = "m"
	symbolToDimension["m"] = LengthDim
	dimensionToSymbol[LuminousIntensityDim] = "cd"
	symbolToDimension["cd"] = LuminousIntensityDim
	dimensionToSymbol[MassDim] = "kg"
	symbolToDimension["kg"] = MassDim
	dimensionToSymbol[TemperatureDim] = "K"
	symbolToDimension["K"] = TemperatureDim
	dimensionToSymbol[TimeDim] = "s"
	symbolToDimension["s"] = TimeDim
	dimensionToSymbol[AngleDim] = "rad"
	symbolToDimension["rad"] = AngleDim

	// Reserve common SI symbols
	// base units
	symbolToDimension["mol"] = reserved
	// prefixes
	symbolToDimension["Y"] = reserved
	symbolToDimension["Z"] = reserved
	symbolToDimension["E"] = reserved
	symbolToDimension["P"] = reserved
	symbolToDimension["T"] = reserved
	symbolToDimension["G"] = reserved
	symbolToDimension["M"] = reserved
	symbolToDimension["k"] = reserved
	symbolToDimension["h"] = reserved
	symbolToDimension["da"] = reserved
	symbolToDimension["d"] = reserved
	symbolToDimension["c"] = reserved
	symbolToDimension["m"] = reserved
	symbolToDimension["μ"] = reserved
	symbolToDimension["n"] = reserved
	symbolToDimension["p"] = reserved
	symbolToDimension["f"] = reserved
	symbolToDimension["a"] = reserved
	symbolToDimension["z"] = reserved
	symbolToDimension["y"] = reserved
	// SI Derived units with special symbols
	symbolToDimension["sr"] = reserved
	symbolToDimension["F"] = reserved
	symbolToDimension["C"] = reserved
	symbolToDimension["S"] = reserved
	symbolToDimension["H"] = reserved
	symbolToDimension["V"] = reserved
	symbolToDimension["Ω"] = reserved
	symbolToDimension["J"] = reserved
	symbolToDimension["N"] = reserved
	symbolToDimension["Hz"] = reserved
	symbolToDimension["lx"] = reserved
	symbolToDimension["lm"] = reserved
	symbolToDimension["Wb"] = reserved
	symbolToDimension["T"] = reserved
	symbolToDimension["W"] = reserved
	symbolToDimension["Pa"] = reserved
	symbolToDimension["Bq"] = reserved
	symbolToDimension["Gy"] = reserved
	symbolToDimension["Sv"] = reserved
	symbolToDimension["kat"] = reserved
	// Units in use with SI
	symbolToDimension["ha"] = reserved
	symbolToDimension["L"] = reserved
	symbolToDimension["l"] = reserved
	// Units in Use Temporarily with SI
	symbolToDimension["bar"] = reserved
	symbolToDimension["b"] = reserved
	symbolToDimension["Ci"] = reserved
	symbolToDimension["R"] = reserved
	symbolToDimension["rd"] = reserved
	symbolToDimension["rem"] = reserved
}

// Dimensions represent the dimensionality of the unit in powers
// of that dimension. If a key is not present, the power of that
// dimension is zero. Dimensions is used in conjuction with NewUnit
type Dimensions map[Dimension]int

var newUnitMutex *sync.Mutex = &sync.Mutex{} // so there is no race condition for dimension

// NewDimension returns a new dimension variable which will have a
// unique representation across packages to prevent accidental overlap.
// The input string represents a symbol name which will be used for printing
// Unit types. This symbol may not overlap with any of the SI base units
// or other symbols of common use in SI ("kg", "J", "μ", etc.). A list of
// such symbols can be found at http://lamar.colostate.edu/~hillger/basic.htm or
// by consulting the package source. Furthermore, the provided symbol is also
// forbidden from overlapping with other packages calling NewDimension. NewDimension
// is expecting to be used only during initialization, and as such it will panic
// if the symbol matching an existing symbol
// NewDimension should only be called for unit types that are actually orthogonal
// to the base dimensions defined in this package. Please see the package-level
// documentation for further explanation
func NewDimension(symbol string) Dimension {
	newUnitMutex.Lock()
	defer newUnitMutex.Unlock()
	lastCreatedDimension++
	_, ok := symbolToDimension[symbol]
	if ok {
		panic("unit: dimension string " + symbol + " already used")
	}
	dimensionToSymbol[lastCreatedDimension] = symbol
	symbolToDimension[symbol] = lastCreatedDimension
	return lastCreatedDimension
}

// Unit is a type a value with generic SI units. Most useful for
// translating between dimensions, for example, by multiplying
// an acceleration with a mass to get a force. Please see the
// package documentation for further explanation.
type Unit struct {
	dimensions map[Dimension]int // Map for custom dimensions
	value      float64
}

// NewUnit creates a new variable of type Unit which has the value
// specified by value and the dimensions specified by the
// base units struct. The value is always in SI Units.
//
// Example: To create an acceleration of 3 m/s^2, one could do
// myvar := CreateUnit(3.0, &Dimensions{unit.LengthDim: 1, unit.TimeDim: -2})
func NewUnit(value float64, d Dimensions) *Unit {
	u := &Unit{
		dimensions: make(map[Dimension]int),
	}
	for key, val := range d {
		u.dimensions[key] = val
	}
	u.value = value
	return u
}

// DimensionsMatch checks if the dimensions of two Uniters are the same
func DimensionsMatch(a, b Uniter) bool {
	aUnit := a.Unit()
	bUnit := b.Unit()
	if len(aUnit.dimensions) != len(bUnit.dimensions) {
		return false
	}
	for key, val := range aUnit.dimensions {
		if bUnit.dimensions[key] != val {
			return false
		}
	}
	return true
}

// Add adds the function argument to the reciever. Panics if the units of
// the receiver and the argument don't match.
func (u *Unit) Add(uniter Uniter) *Unit {
	a := uniter.Unit()
	if !DimensionsMatch(u, a) {
		panic("Attempted to add the values of two units whose dimensions do not match.")
	}
	u.value += a.value
	return u
}

// Unit implements the Uniter interface
func (u *Unit) Unit() *Unit {
	return u
}

// Mul multiply the receiver by the input changing the dimensions
// of the receiver as appropriate. The input is not changed
func (u *Unit) Mul(uniter Uniter) *Unit {
	a := uniter.Unit()
	for key, val := range a.dimensions {
		u.dimensions[key] += val
	}
	u.value *= a.value
	return u
}

// Div divides the receiver by the argument changing the
// dimensions of the receiver as appropriate
func (u *Unit) Div(uniter Uniter) *Unit {
	a := uniter.Unit()
	u.value /= a.value
	for key, val := range a.dimensions {
		u.dimensions[key] -= val
	}
	return u
}

// Value return the raw value of the unit as a float64. Use of this
// method is, in general, not recommended, though it can be useful
// for printing. Instead, the FromUnit type of a specific
// dimension should be used to guarantee dimension consistency
func (u *Unit) Value() float64 {
	return u.value
}

type symbolString struct {
	symbol string
	pow    int
}

type unitPrinters []symbolString

func (u unitPrinters) Len() int {
	return len(u)
}

func (u unitPrinters) Less(i, j int) bool {
	return u[i].symbol < u[j].symbol
}

func (u unitPrinters) Swap(i, j int) {
	u[i], u[j] = u[j], u[i]
}

// String makes Unit satisfy the stringer interface. The unit is printed
// using strconv.FormatFloat(unit.value, 'e', -1, 64), with dimensions
// appended. If the power if the dimension is not zero or one,
// symbol^power is appended, if the power is one, just the symbol is appended
// and if the power is zero, nothing is appended. Dimensions are appended
// in order by symbol name.
func (u Unit) String() string {
	str := strconv.FormatFloat(u.value, 'e', -1, 64)
	// Map iterates randomly, but print should be in a fixed order. Can't use
	// dimension number, because for user-defined dimension that number may
	// not be fixed from run to run.
	data := make(unitPrinters, 0, 10)
	for dimension, power := range u.dimensions {
		if power != 0 {
			data = append(data, symbolString{dimensionToSymbol[dimension], power})
		}
	}
	sort.Sort(data)
	for _, s := range data {
		str += " " + s.symbol
		if s.pow != 1 {
			str += "^" + strconv.Itoa(s.pow)
		}
	}
	return str
}
<commit_msg>fixed constant definition error<commit_after>package unit

import (
	"sort"
	"strconv"
	"sync"
)

// Uniter is an interface representing a type that can be converted
// to a unit.
type Uniter interface {
	Unit() *Unit
}

// Dimension is a type representing an SI base dimension or other
// orthogonal dimension. If a new dimension is desired for a
// domain-specific problem, NewDimension should be used. Integers
// should never be cast as type dimension
//	// Good: Create a package constant with an init function
//	var MyDimension unit.Dimension
//	init(){
//		MyDimension = NewDimension("my")
//	}
//	main(){
//		var := MyDimension(28.2)
//	}
type Dimension int

const (
	// SI Base Units
	reserved Dimension = iota
	CurrentDim
	LengthDim
	LuminousIntensityDim
	MassDim
	TemperatureDim
	TimeDim
	// Other common SI Dimensions
	AngleDim             // e.g. radians
	lastPackageDimension // Used in create dimension
)

var lastCreatedDimension Dimension = lastPackageDimension
var dimensionToSymbol map[Dimension]string = make(map[Dimension]string)
var symbolToDimension map[string]Dimension = make(map[string]Dimension) // for guaranteeing there aren't two identical symbols

// TODO: Should we actually reserve "common" SI unit symbols ("N", "J", etc.) so there isn't confusion
// TODO: If we have a fancier ParseUnit, maybe the 'reserved' symbols should be a different map
// 		map[string]string which says how they go?
func init() {
	dimensionToSymbol[CurrentDim] = "A"
	symbolToDimension["A"] = CurrentDim
	dimensionToSymbol[LengthDim] = "m"
	symbolToDimension["m"] = LengthDim
	dimensionToSymbol[LuminousIntensityDim] = "cd"
	symbolToDimension["cd"] = LuminousIntensityDim
	dimensionToSymbol[MassDim] = "kg"
	symbolToDimension["kg"] = MassDim
	dimensionToSymbol[TemperatureDim] = "K"
	symbolToDimension["K"] = TemperatureDim
	dimensionToSymbol[TimeDim] = "s"
	symbolToDimension["s"] = TimeDim
	dimensionToSymbol[AngleDim] = "rad"
	symbolToDimension["rad"] = AngleDim

	// Reserve common SI symbols
	// base units
	symbolToDimension["mol"] = reserved
	// prefixes
	symbolToDimension["Y"] = reserved
	symbolToDimension["Z"] = reserved
	symbolToDimension["E"] = reserved
	symbolToDimension["P"] = reserved
	symbolToDimension["T"] = reserved
	symbolToDimension["G"] = reserved
	symbolToDimension["M"] = reserved
	symbolToDimension["k"] = reserved
	symbolToDimension["h"] = reserved
	symbolToDimension["da"] = reserved
	symbolToDimension["d"] = reserved
	symbolToDimension["c"] = reserved
	symbolToDimension["m"] = reserved
	symbolToDimension["μ"] = reserved
	symbolToDimension["n"] = reserved
	symbolToDimension["p"] = reserved
	symbolToDimension["f"] = reserved
	symbolToDimension["a"] = reserved
	symbolToDimension["z"] = reserved
	symbolToDimension["y"] = reserved
	// SI Derived units with special symbols
	symbolToDimension["sr"] = reserved
	symbolToDimension["F"] = reserved
	symbolToDimension["C"] = reserved
	symbolToDimension["S"] = reserved
	symbolToDimension["H"] = reserved
	symbolToDimension["V"] = reserved
	symbolToDimension["Ω"] = reserved
	symbolToDimension["J"] = reserved
	symbolToDimension["N"] = reserved
	symbolToDimension["Hz"] = reserved
	symbolToDimension["lx"] = reserved
	symbolToDimension["lm"] = reserved
	symbolToDimension["Wb"] = reserved
	symbolToDimension["T"] = reserved
	symbolToDimension["W"] = reserved
	symbolToDimension["Pa"] = reserved
	symbolToDimension["Bq"] = reserved
	symbolToDimension["Gy"] = reserved
	symbolToDimension["Sv"] = reserved
	symbolToDimension["kat"] = reserved
	// Units in use with SI
	symbolToDimension["ha"] = reserved
	symbolToDimension["L"] = reserved
	symbolToDimension["l"] = reserved
	// Units in Use Temporarily with SI
	symbolToDimension["bar"] = reserved
	symbolToDimension["b"] = reserved
	symbolToDimension["Ci"] = reserved
	symbolToDimension["R"] = reserved
	symbolToDimension["rd"] = reserved
	symbolToDimension["rem"] = reserved
}

// Dimensions represent the dimensionality of the unit in powers
// of that dimension. If a key is not present, the power of that
// dimension is zero. Dimensions is used in conjuction with NewUnit
type Dimensions map[Dimension]int

var newUnitMutex *sync.Mutex = &sync.Mutex{} // so there is no race condition for dimension

// NewDimension returns a new dimension variable which will have a
// unique representation across packages to prevent accidental overlap.
// The input string represents a symbol name which will be used for printing
// Unit types. This symbol may not overlap with any of the SI base units
// or other symbols of common use in SI ("kg", "J", "μ", etc.). A list of
// such symbols can be found at http://lamar.colostate.edu/~hillger/basic.htm or
// by consulting the package source. Furthermore, the provided symbol is also
// forbidden from overlapping with other packages calling NewDimension. NewDimension
// is expecting to be used only during initialization, and as such it will panic
// if the symbol matching an existing symbol
// NewDimension should only be called for unit types that are actually orthogonal
// to the base dimensions defined in this package. Please see the package-level
// documentation for further explanation
func NewDimension(symbol string) Dimension {
	newUnitMutex.Lock()
	defer newUnitMutex.Unlock()
	lastCreatedDimension++
	_, ok := symbolToDimension[symbol]
	if ok {
		panic("unit: dimension string " + symbol + " already used")
	}
	dimensionToSymbol[lastCreatedDimension] = symbol
	symbolToDimension[symbol] = lastCreatedDimension
	return lastCreatedDimension
}

// Unit is a type a value with generic SI units. Most useful for
// translating between dimensions, for example, by multiplying
// an acceleration with a mass to get a force. Please see the
// package documentation for further explanation.
type Unit struct {
	dimensions map[Dimension]int // Map for custom dimensions
	value      float64
}

// NewUnit creates a new variable of type Unit which has the value
// specified by value and the dimensions specified by the
// base units struct. The value is always in SI Units.
//
// Example: To create an acceleration of 3 m/s^2, one could do
// myvar := CreateUnit(3.0, &Dimensions{unit.LengthDim: 1, unit.TimeDim: -2})
func NewUnit(value float64, d Dimensions) *Unit {
	u := &Unit{
		dimensions: make(map[Dimension]int),
	}
	for key, val := range d {
		u.dimensions[key] = val
	}
	u.value = value
	return u
}

// DimensionsMatch checks if the dimensions of two Uniters are the same
func DimensionsMatch(a, b Uniter) bool {
	aUnit := a.Unit()
	bUnit := b.Unit()
	if len(aUnit.dimensions) != len(bUnit.dimensions) {
		return false
	}
	for key, val := range aUnit.dimensions {
		if bUnit.dimensions[key] != val {
			return false
		}
	}
	return true
}

// Add adds the function argument to the reciever. Panics if the units of
// the receiver and the argument don't match.
func (u *Unit) Add(uniter Uniter) *Unit {
	a := uniter.Unit()
	if !DimensionsMatch(u, a) {
		panic("Attempted to add the values of two units whose dimensions do not match.")
	}
	u.value += a.value
	return u
}

// Unit implements the Uniter interface
func (u *Unit) Unit() *Unit {
	return u
}

// Mul multiply the receiver by the input changing the dimensions
// of the receiver as appropriate. The input is not changed
func (u *Unit) Mul(uniter Uniter) *Unit {
	a := uniter.Unit()
	for key, val := range a.dimensions {
		u.dimensions[key] += val
	}
	u.value *= a.value
	return u
}

// Div divides the receiver by the argument changing the
// dimensions of the receiver as appropriate
func (u *Unit) Div(uniter Uniter) *Unit {
	a := uniter.Unit()
	u.value /= a.value
	for key, val := range a.dimensions {
		u.dimensions[key] -= val
	}
	return u
}

// Value return the raw value of the unit as a float64. Use of this
// method is, in general, not recommended, though it can be useful
// for printing. Instead, the FromUnit type of a specific
// dimension should be used to guarantee dimension consistency
func (u *Unit) Value() float64 {
	return u.value
}

type symbolString struct {
	symbol string
	pow    int
}

type unitPrinters []symbolString

func (u unitPrinters) Len() int {
	return len(u)
}

func (u unitPrinters) Less(i, j int) bool {
	return u[i].symbol < u[j].symbol
}

func (u unitPrinters) Swap(i, j int) {
	u[i], u[j] = u[j], u[i]
}

// String makes Unit satisfy the stringer interface. The unit is printed
// using strconv.FormatFloat(unit.value, 'e', -1, 64), with dimensions
// appended. If the power if the dimension is not zero or one,
// symbol^power is appended, if the power is one, just the symbol is appended
// and if the power is zero, nothing is appended. Dimensions are appended
// in order by symbol name.
func (u Unit) String() string {
	str := strconv.FormatFloat(u.value, 'e', -1, 64)
	// Map iterates randomly, but print should be in a fixed order. Can't use
	// dimension number, because for user-defined dimension that number may
	// not be fixed from run to run.
	data := make(unitPrinters, 0, 10)
	for dimension, power := range u.dimensions {
		if power != 0 {
			data = append(data, symbolString{dimensionToSymbol[dimension], power})
		}
	}
	sort.Sort(data)
	for _, s := range data {
		str += " " + s.symbol
		if s.pow != 1 {
			str += "^" + strconv.Itoa(s.pow)
		}
	}
	return str
}
<|endoftext|>


<commit_before>package terraform

import (
	"fmt"

	"github.com/hashicorp/terraform/addrs"
	"github.com/zclconf/go-cty/cty"
)

// NodePlannableResourceInstance represents a _single_ resource
// instance that is plannable. This means this represents a single
// count index, for example.
type NodePlannableResourceInstance struct {
	*NodeAbstractResourceInstance
}

var (
	_ GraphNodeSubPath              = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeReferenceable        = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeReferencer           = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeResource             = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeResourceInstance     = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeAttachResourceConfig = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeAttachResourceState  = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeEvalable             = (*NodePlannableResourceInstance)(nil)
)

// GraphNodeEvalable
func (n *NodePlannableResourceInstance) EvalTree() EvalNode {
	addr := n.ResourceInstanceAddr()

	// State still uses legacy-style internal ids, so we need to shim to get
	// a suitable key to use.
	stateId := NewLegacyResourceInstanceAddress(addr).stateId()

	// Determine the dependencies for the state.
	stateDeps := n.StateReferences()

	// Eval info is different depending on what kind of resource this is
	switch addr.Resource.Resource.Mode {
	case addrs.ManagedResourceMode:
		return n.evalTreeManagedResource(addr, stateId, stateDeps)
	case addrs.DataResourceMode:
		return n.evalTreeDataResource(addr, stateId, stateDeps)
	default:
		panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
	}
}

func (n *NodePlannableResourceInstance) evalTreeDataResource(addr addrs.AbsResourceInstance, stateId string, stateDeps []string) EvalNode {
	var provider ResourceProvider
	var providerSchema *ProviderSchema
	var diff *InstanceDiff
	var state *InstanceState
	var configVal cty.Value

	return &EvalSequence{
		Nodes: []EvalNode{
			&EvalReadState{
				Name:   stateId,
				Output: &state,
			},

			&EvalGetProvider{
				Addr:   n.ResolvedProvider,
				Output: &provider,
				Schema: &providerSchema,
			},

			&EvalReadDataDiff{
				Addr:           addr.Resource,
				Config:         n.Config,
				Provider:       &provider,
				ProviderSchema: &providerSchema,
				Output:         &diff,
				OutputValue:    &configVal,
				OutputState:    &state,
			},

			&EvalIf{
				If: func(ctx EvalContext) (bool, error) {
					computed := !configVal.IsWhollyKnown()

					// If the configuration is complete and we
					// already have a state then we don't need to
					// do any further work during apply, because we
					// already populated the state during refresh.
					if !computed && state != nil {
						return true, EvalEarlyExitError{}
					}

					return true, nil
				},
				Then: EvalNoop{},
			},

			&EvalWriteState{
				Name:         stateId,
				ResourceType: n.Config.Type,
				Provider:     n.ResolvedProvider,
				Dependencies: stateDeps,
				State:        &state,
			},

			&EvalWriteDiff{
				Name: stateId,
				Diff: &diff,
			},
		},
	}
}

func (n *NodePlannableResourceInstance) evalTreeManagedResource(addr addrs.AbsResourceInstance, stateId string, stateDeps []string) EvalNode {
	var provider ResourceProvider
	var providerSchema *ProviderSchema
	var diff *InstanceDiff
	var state *InstanceState

	return &EvalSequence{
		Nodes: []EvalNode{
			&EvalReadState{
				Name:   stateId,
				Output: &state,
			},

			&EvalGetProvider{
				Addr:   n.ResolvedProvider,
				Output: &provider,
				Schema: &providerSchema,
			},

			&EvalDiff{
				Addr:           addr.Resource,
				Config:         n.Config,
				Provider:       &provider,
				ProviderSchema: &providerSchema,
				State:          &state,
				OutputDiff:     &diff,
				OutputState:    &state,
			},
			&EvalCheckPreventDestroy{
				Addr:   addr.Resource,
				Config: n.Config,
				Diff:   &diff,
			},
			&EvalWriteState{
				Name:         stateId,
				ResourceType: n.Config.Type,
				Provider:     n.ResolvedProvider,
				Dependencies: stateDeps,
				State:        &state,
			},
			&EvalWriteDiff{
				Name: stateId,
				Diff: &diff,
			},
		},
	}
}
<commit_msg>core: fix NodePlannableResourceInstance data reads<commit_after>package terraform

import (
	"fmt"

	"github.com/hashicorp/terraform/addrs"
	"github.com/zclconf/go-cty/cty"
)

// NodePlannableResourceInstance represents a _single_ resource
// instance that is plannable. This means this represents a single
// count index, for example.
type NodePlannableResourceInstance struct {
	*NodeAbstractResourceInstance
}

var (
	_ GraphNodeSubPath              = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeReferenceable        = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeReferencer           = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeResource             = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeResourceInstance     = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeAttachResourceConfig = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeAttachResourceState  = (*NodePlannableResourceInstance)(nil)
	_ GraphNodeEvalable             = (*NodePlannableResourceInstance)(nil)
)

// GraphNodeEvalable
func (n *NodePlannableResourceInstance) EvalTree() EvalNode {
	addr := n.ResourceInstanceAddr()

	// State still uses legacy-style internal ids, so we need to shim to get
	// a suitable key to use.
	stateId := NewLegacyResourceInstanceAddress(addr).stateId()

	// Determine the dependencies for the state.
	stateDeps := n.StateReferences()

	// Eval info is different depending on what kind of resource this is
	switch addr.Resource.Resource.Mode {
	case addrs.ManagedResourceMode:
		return n.evalTreeManagedResource(addr, stateId, stateDeps)
	case addrs.DataResourceMode:
		return n.evalTreeDataResource(addr, stateId, stateDeps)
	default:
		panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
	}
}

func (n *NodePlannableResourceInstance) evalTreeDataResource(addr addrs.AbsResourceInstance, stateId string, stateDeps []string) EvalNode {
	var provider ResourceProvider
	var providerSchema *ProviderSchema
	var diff *InstanceDiff
	var state *InstanceState
	var configVal cty.Value

	return &EvalSequence{
		Nodes: []EvalNode{
			&EvalReadState{
				Name:   stateId,
				Output: &state,
			},

			&EvalGetProvider{
				Addr:   n.ResolvedProvider,
				Output: &provider,
				Schema: &providerSchema,
			},

			&EvalReadDataDiff{
				Addr:           addr.Resource,
				Config:         n.Config,
				Provider:       &provider,
				ProviderSchema: &providerSchema,
				Output:         &diff,
				OutputValue:    &configVal,
				OutputState:    &state,
			},

			&EvalWriteState{
				Name:         stateId,
				ResourceType: n.Config.Type,
				Provider:     n.ResolvedProvider,
				Dependencies: stateDeps,
				State:        &state,
			},

			&EvalWriteDiff{
				Name: stateId,
				Diff: &diff,
			},
		},
	}
}

func (n *NodePlannableResourceInstance) evalTreeManagedResource(addr addrs.AbsResourceInstance, stateId string, stateDeps []string) EvalNode {
	var provider ResourceProvider
	var providerSchema *ProviderSchema
	var diff *InstanceDiff
	var state *InstanceState

	return &EvalSequence{
		Nodes: []EvalNode{
			&EvalReadState{
				Name:   stateId,
				Output: &state,
			},

			&EvalGetProvider{
				Addr:   n.ResolvedProvider,
				Output: &provider,
				Schema: &providerSchema,
			},

			&EvalDiff{
				Addr:           addr.Resource,
				Config:         n.Config,
				Provider:       &provider,
				ProviderSchema: &providerSchema,
				State:          &state,
				OutputDiff:     &diff,
				OutputState:    &state,
			},
			&EvalCheckPreventDestroy{
				Addr:   addr.Resource,
				Config: n.Config,
				Diff:   &diff,
			},
			&EvalWriteState{
				Name:         stateId,
				ResourceType: n.Config.Type,
				Provider:     n.ResolvedProvider,
				Dependencies: stateDeps,
				State:        &state,
			},
			&EvalWriteDiff{
				Name: stateId,
				Diff: &diff,
			},
		},
	}
}
<|endoftext|>


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

package submission

import (
	"context"
	"crypto/sha256"
	"encoding/json"
	"errors"
	"fmt"
	"regexp"
	"testing"
	"time"

	ct "github.com/google/certificate-transparency-go"
	"github.com/google/certificate-transparency-go/client"
	"github.com/google/certificate-transparency-go/ctpolicy"
	"github.com/google/certificate-transparency-go/loglist"
	"github.com/google/certificate-transparency-go/testdata"
	"github.com/google/certificate-transparency-go/tls"
	"github.com/google/certificate-transparency-go/x509"
	"github.com/google/certificate-transparency-go/x509util"

	"github.com/google/go-cmp/cmp"
)

// ReadCertFile returns the first certificate it finds in file provided.
func readCertFile(filename string) string {
	fmt.Println(filename)
	data, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE")
	if err != nil {
		return ""
	}
	return string(data[0])
}

var (
	RootsCerts = map[string][]string{
		"ct.googleapis.com/aviator/": {
			readCertFile("../trillian/testdata/fake-ca-1.cert"),
			readCertFile("testdata/some.cert"),
		},
		"ct.googleapis.com/rocketeer/": {
			readCertFile("../trillian/testdata/fake-ca.cert"),
			readCertFile("../trillian/testdata/fake-ca-1.cert"),
			readCertFile("testdata/some.cert"),
			readCertFile("testdata/another.cert"),
		},
		"ct.googleapis.com/icarus/": {
			"aW52YWxpZDAwMA==", // encoded 'invalid000'
			readCertFile("testdata/another.cert"),
		},
		"uncollectable-roots/log/": {
			"invalid",
		},
	}
)

// buildNoLogClient is LogClientBuilder that always fails.
func buildNoLogClient(log *loglist.Log) (client.AddLogClient, error) {
	return nil, errors.New("bad client builder")
}

// Stub for AddLogClient interface
type emptyLogClient struct {
}

func (e emptyLogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return nil, nil
}

func (e emptyLogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return nil, nil
}

func (e emptyLogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) {
	return nil, nil
}

// buildEmptyLogClient produces empty stub Log clients.
func buildEmptyLogClient(log *loglist.Log) (client.AddLogClient, error) {
	return emptyLogClient{}, nil
}

func sampleLogList(t *testing.T) *loglist.LogList {
	t.Helper()
	var loglist loglist.LogList
	err := json.Unmarshal([]byte(testdata.SampleLogList), &loglist)
	if err != nil {
		t.Fatalf("Unable to Unmarshal testdata.SampleLogList %v", err)
	}
	return &loglist
}

func sampleValidLogList(t *testing.T) *loglist.LogList {
	t.Helper()
	ll := sampleLogList(t)
	// Id of invalid Log description Racketeer
	inval := 3
	ll.Logs = append(ll.Logs[:inval], ll.Logs[inval+1:]...)
	return ll
}

func sampleUncollectableLogList(t *testing.T) *loglist.LogList {
	t.Helper()
	ll := sampleValidLogList(t)
	// Append loglist that is unable to provide roots on request.
	ll.Logs = append(ll.Logs, loglist.Log{
		Description: "Does not return roots", Key: []byte("VW5jb2xsZWN0YWJsZUxvZ0xpc3Q="),
		MaximumMergeDelay: 123, OperatedBy: []int{0},
		URL:            "uncollectable-roots/log/",
		DNSAPIEndpoint: "uncollectavle.ct.googleapis.com",
	})
	return ll
}

func TestNewDistributorLogClients(t *testing.T) {
	testCases := []struct {
		name      string
		ll        *loglist.LogList
		lcBuilder LogClientBuilder
		errRegexp *regexp.Regexp
	}{
		{
			name:      "ValidLogClients",
			ll:        sampleValidLogList(t),
			lcBuilder: buildEmptyLogClient,
		},
		{
			name:      "NoLogClients",
			ll:        sampleValidLogList(t),
			lcBuilder: buildNoLogClient,
			errRegexp: regexp.MustCompile("failed to create log client"),
		},
		{
			name:      "NoLogClientsEmptyLogList",
			ll:        &loglist.LogList{},
			lcBuilder: buildNoLogClient,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			_, err := NewDistributor(tc.ll, ctpolicy.ChromeCTPolicy{}, tc.lcBuilder)
			if gotErr, wantErr := err != nil, tc.errRegexp != nil; gotErr != wantErr {
				var unwantedErr string
				if gotErr {
					unwantedErr = fmt.Sprintf(" (%q)", err)
				}
				t.Errorf("Got error = %v%s, expected error = %v", gotErr, unwantedErr, wantErr)
			} else if tc.errRegexp != nil && !tc.errRegexp.MatchString(err.Error()) {
				t.Errorf("Error %q did not match expected regexp %q", err, tc.errRegexp)
			}
		})
	}
}

// TestSCT builds a mock SCT for given logURL.
func testSCT(logURL string) *ct.SignedCertificateTimestamp {
	var keyID [sha256.Size]byte
	copy(keyID[:], logURL)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: ct.V1,
		LogID:      ct.LogID{KeyID: keyID},
		Timestamp:  1234,
		Extensions: []byte{},
		Signature: ct.DigitallySigned{
			Algorithm: tls.SignatureAndHashAlgorithm{
				Hash:      tls.SHA256,
				Signature: tls.ECDSA,
			},
		},
	}
}

// Stub for AddLogCLient interface
type stubLogClient struct {
	logURL string
}

func (m stubLogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return nil, nil
}

func (m stubLogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	if _, ok := RootsCerts[m.logURL]; ok {
		return testSCT(m.logURL), nil
	}
	return nil, fmt.Errorf("Log %q has no roots", m.logURL)
}

func (m stubLogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) {
	roots := []ct.ASN1Cert{}
	if certs, ok := RootsCerts[m.logURL]; ok {
		for _, cert := range certs {
			roots = append(roots, ct.ASN1Cert{Data: []byte(cert)})
		}
	}
	return roots, nil
}

func buildStubLogClient(log *loglist.Log) (client.AddLogClient, error) {
	return stubLogClient{logURL: log.URL}, nil
}

func TestNewDistributorRootPools(t *testing.T) {
	testCases := []struct {
		name    string
		ll      *loglist.LogList
		rootNum map[string]int
	}{
		{
			name:    "InactiveZeroRoots",
			ll:      sampleValidLogList(t),
			rootNum: map[string]int{"ct.googleapis.com/aviator/": 0, "ct.googleapis.com/rocketeer/": 4, "ct.googleapis.com/icarus/": 1}, // aviator is not active; 1 of 2 icarus roots is not x509 struct
		},
		{
			name:    "CouldNotCollect",
			ll:      sampleUncollectableLogList(t),
			rootNum: map[string]int{"ct.googleapis.com/aviator/": 0, "ct.googleapis.com/rocketeer/": 4, "ct.googleapis.com/icarus/": 1, "uncollectable-roots/log/": 0}, // aviator is not active; uncollectable client cannot provide roots
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			dist, _ := NewDistributor(tc.ll, ctpolicy.ChromeCTPolicy{}, buildStubLogClient)
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			go dist.Run(ctx)
			// First Log refresh expected.
			<-ctx.Done()

			dist.mu.Lock()
			defer dist.mu.Unlock()
			for logURL, wantNum := range tc.rootNum {
				gotNum := 0
				if roots, ok := dist.logRoots[logURL]; ok {
					gotNum = len(roots.RawCertificates())
				}
				if wantNum != gotNum {
					t.Errorf("Expected %d root(s) for Log %s, got %d", wantNum, logURL, gotNum)
				}
			}
		})
	}
}

func pemFileToDERChain(t *testing.T, filename string) [][]byte {
	t.Helper()
	rawChain, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE")
	if err != nil {
		t.Fatalf("failed to load testdata: %v", err)
	}
	return rawChain
}

func getSCTMap(l []*AssignedSCT) map[string]*AssignedSCT {
	m := map[string]*AssignedSCT{}
	for _, asct := range l {
		m[asct.LogURL] = asct
	}
	return m
}

// Stub CT policy to run tests.
type stubCTPolicy struct {
	baseNum int
}

// Builds simplistic policy requiring n SCTs from any Logs for each cert.
func buildStubCTPolicy(n int) stubCTPolicy {
	return stubCTPolicy{baseNum: n}
}

func (stubP stubCTPolicy) LogsByGroup(cert *x509.Certificate, approved *loglist.LogList) (ctpolicy.LogPolicyData, error) {
	baseGroup, err := ctpolicy.BaseGroupFor(approved, stubP.baseNum)
	groups := ctpolicy.LogPolicyData{baseGroup.Name: &baseGroup}
	return groups, err
}

func TestDistributorAddPreChain(t *testing.T) {
	testCases := []struct {
		name     string
		ll       *loglist.LogList
		plc      ctpolicy.CTPolicy
		rawChain [][]byte
		scts     []*AssignedSCT
		wantErr  bool
	}{
		{
			name:     "MalformedChainRequest",
			ll:       sampleValidLogList(t),
			plc:      ctpolicy.ChromeCTPolicy{},
			rawChain: pemFileToDERChain(t, "../trillian/testdata/subleaf.misordered.chain"),
			scts:     nil,
			wantErr:  true,
		},
		{
			name:     "CallBeforeInit",
			ll:       sampleValidLogList(t),
			plc:      ctpolicy.ChromeCTPolicy{},
			rawChain: nil,
			scts:     nil,
			wantErr:  true,
		},
		{
			name:     "InsufficientSCTsForPolicy",
			ll:       sampleValidLogList(t),
			plc:      ctpolicy.AppleCTPolicy{},
			rawChain: pemFileToDERChain(t, "../trillian/testdata/subleaf.chain"), // subleaf chain is fake-ca-1-rooted
			scts:     []*AssignedSCT{},
			wantErr:  true, // Not enough SCTs for policy
		},
		{
			name:     "FullChain1Policy",
			ll:       sampleValidLogList(t),
			plc:      buildStubCTPolicy(1),
			rawChain: pemFileToDERChain(t, "../trillian/testdata/subleaf.chain"),
			scts: []*AssignedSCT{
				{
					LogURL: "ct.googleapis.com/rocketeer/",
					SCT:    testSCT("ct.googleapis.com/rocketeer/"),
				},
			},
			wantErr: false,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			dist, _ := NewDistributor(tc.ll, tc.plc, buildStubLogClient)
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			dist.Run(ctx)

			scts, err := dist.AddPreChain(context.Background(), tc.rawChain)
			if gotErr := (err != nil); gotErr != tc.wantErr {
				t.Errorf("Expected to get errors is %v while actually getting errors is %v", tc.wantErr, gotErr)
			}

			if got, want := len(scts), len(tc.scts); got != want {
				t.Errorf("Expected to get %d SCTs on AddPreChain request, got %d", want, got)
			}
			gotMap := getSCTMap(tc.scts)
			for _, asct := range scts {
				if wantedSCT, ok := gotMap[asct.LogURL]; !ok {
					t.Errorf("dist.AddPreChain() = (_, %v), want err? %t", err, tc.wantErr)
				} else if diff := cmp.Diff(asct, wantedSCT); diff != "" {
					t.Errorf("Got unexpected SCT for Log %q", asct.LogURL)
				}
			}
		})
	}
}
<commit_msg>submission: defer loading of test certificates (#474)<commit_after>// Copyright 2019 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package submission

import (
	"context"
	"crypto/sha256"
	"encoding/json"
	"errors"
	"fmt"
	"regexp"
	"testing"
	"time"

	ct "github.com/google/certificate-transparency-go"
	"github.com/google/certificate-transparency-go/client"
	"github.com/google/certificate-transparency-go/ctpolicy"
	"github.com/google/certificate-transparency-go/loglist"
	"github.com/google/certificate-transparency-go/testdata"
	"github.com/google/certificate-transparency-go/tls"
	"github.com/google/certificate-transparency-go/x509"
	"github.com/google/certificate-transparency-go/x509util"

	"github.com/google/go-cmp/cmp"
)

// readCertFile returns the first certificate it finds in file provided.
func readCertFile(filename string) string {
	data, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE")
	if err != nil {
		return ""
	}
	return string(data[0])
}

type rootInfo struct {
	raw      string
	filename string
}

var (
	RootsCerts = map[string][]rootInfo{
		"ct.googleapis.com/aviator/": {
			rootInfo{filename: "../trillian/testdata/fake-ca-1.cert"},
			rootInfo{filename: "testdata/some.cert"},
		},
		"ct.googleapis.com/rocketeer/": {
			rootInfo{filename: "../trillian/testdata/fake-ca.cert"},
			rootInfo{filename: "../trillian/testdata/fake-ca-1.cert"},
			rootInfo{filename: "testdata/some.cert"},
			rootInfo{filename: "testdata/another.cert"},
		},
		"ct.googleapis.com/icarus/": {
			rootInfo{raw: "aW52YWxpZDAwMA=="}, // encoded 'invalid000'
			rootInfo{filename: "testdata/another.cert"},
		},
		"uncollectable-roots/log/": {
			rootInfo{raw: "invalid"},
		},
	}
)

// buildNoLogClient is LogClientBuilder that always fails.
func buildNoLogClient(log *loglist.Log) (client.AddLogClient, error) {
	return nil, errors.New("bad client builder")
}

// Stub for AddLogClient interface
type emptyLogClient struct {
}

func (e emptyLogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return nil, nil
}

func (e emptyLogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return nil, nil
}

func (e emptyLogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) {
	return nil, nil
}

// buildEmptyLogClient produces empty stub Log clients.
func buildEmptyLogClient(log *loglist.Log) (client.AddLogClient, error) {
	return emptyLogClient{}, nil
}

func sampleLogList(t *testing.T) *loglist.LogList {
	t.Helper()
	var loglist loglist.LogList
	err := json.Unmarshal([]byte(testdata.SampleLogList), &loglist)
	if err != nil {
		t.Fatalf("Unable to Unmarshal testdata.SampleLogList %v", err)
	}
	return &loglist
}

func sampleValidLogList(t *testing.T) *loglist.LogList {
	t.Helper()
	ll := sampleLogList(t)
	// Id of invalid Log description Racketeer
	inval := 3
	ll.Logs = append(ll.Logs[:inval], ll.Logs[inval+1:]...)
	return ll
}

func sampleUncollectableLogList(t *testing.T) *loglist.LogList {
	t.Helper()
	ll := sampleValidLogList(t)
	// Append loglist that is unable to provide roots on request.
	ll.Logs = append(ll.Logs, loglist.Log{
		Description: "Does not return roots", Key: []byte("VW5jb2xsZWN0YWJsZUxvZ0xpc3Q="),
		MaximumMergeDelay: 123, OperatedBy: []int{0},
		URL:            "uncollectable-roots/log/",
		DNSAPIEndpoint: "uncollectavle.ct.googleapis.com",
	})
	return ll
}

func TestNewDistributorLogClients(t *testing.T) {
	testCases := []struct {
		name      string
		ll        *loglist.LogList
		lcBuilder LogClientBuilder
		errRegexp *regexp.Regexp
	}{
		{
			name:      "ValidLogClients",
			ll:        sampleValidLogList(t),
			lcBuilder: buildEmptyLogClient,
		},
		{
			name:      "NoLogClients",
			ll:        sampleValidLogList(t),
			lcBuilder: buildNoLogClient,
			errRegexp: regexp.MustCompile("failed to create log client"),
		},
		{
			name:      "NoLogClientsEmptyLogList",
			ll:        &loglist.LogList{},
			lcBuilder: buildNoLogClient,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			_, err := NewDistributor(tc.ll, ctpolicy.ChromeCTPolicy{}, tc.lcBuilder)
			if gotErr, wantErr := err != nil, tc.errRegexp != nil; gotErr != wantErr {
				var unwantedErr string
				if gotErr {
					unwantedErr = fmt.Sprintf(" (%q)", err)
				}
				t.Errorf("Got error = %v%s, expected error = %v", gotErr, unwantedErr, wantErr)
			} else if tc.errRegexp != nil && !tc.errRegexp.MatchString(err.Error()) {
				t.Errorf("Error %q did not match expected regexp %q", err, tc.errRegexp)
			}
		})
	}
}

// TestSCT builds a mock SCT for given logURL.
func testSCT(logURL string) *ct.SignedCertificateTimestamp {
	var keyID [sha256.Size]byte
	copy(keyID[:], logURL)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: ct.V1,
		LogID:      ct.LogID{KeyID: keyID},
		Timestamp:  1234,
		Extensions: []byte{},
		Signature: ct.DigitallySigned{
			Algorithm: tls.SignatureAndHashAlgorithm{
				Hash:      tls.SHA256,
				Signature: tls.ECDSA,
			},
		},
	}
}

// Stub for AddLogCLient interface
type stubLogClient struct {
	logURL string
}

func (m stubLogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return nil, nil
}

func (m stubLogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	if _, ok := RootsCerts[m.logURL]; ok {
		return testSCT(m.logURL), nil
	}
	return nil, fmt.Errorf("Log %q has no roots", m.logURL)
}

func (m stubLogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) {
	roots := []ct.ASN1Cert{}
	if certInfos, ok := RootsCerts[m.logURL]; ok {
		for _, certInfo := range certInfos {
			if len(certInfo.raw) > 0 {
				roots = append(roots, ct.ASN1Cert{Data: []byte(certInfo.raw)})
			} else {

				roots = append(roots, ct.ASN1Cert{Data: []byte(readCertFile(certInfo.filename))})
			}
		}
	}
	return roots, nil
}

func buildStubLogClient(log *loglist.Log) (client.AddLogClient, error) {
	return stubLogClient{logURL: log.URL}, nil
}

func TestNewDistributorRootPools(t *testing.T) {
	testCases := []struct {
		name    string
		ll      *loglist.LogList
		rootNum map[string]int
	}{
		{
			name:    "InactiveZeroRoots",
			ll:      sampleValidLogList(t),
			rootNum: map[string]int{"ct.googleapis.com/aviator/": 0, "ct.googleapis.com/rocketeer/": 4, "ct.googleapis.com/icarus/": 1}, // aviator is not active; 1 of 2 icarus roots is not x509 struct
		},
		{
			name:    "CouldNotCollect",
			ll:      sampleUncollectableLogList(t),
			rootNum: map[string]int{"ct.googleapis.com/aviator/": 0, "ct.googleapis.com/rocketeer/": 4, "ct.googleapis.com/icarus/": 1, "uncollectable-roots/log/": 0}, // aviator is not active; uncollectable client cannot provide roots
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			dist, _ := NewDistributor(tc.ll, ctpolicy.ChromeCTPolicy{}, buildStubLogClient)
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			go dist.Run(ctx)
			// First Log refresh expected.
			<-ctx.Done()

			dist.mu.Lock()
			defer dist.mu.Unlock()
			for logURL, wantNum := range tc.rootNum {
				gotNum := 0
				if roots, ok := dist.logRoots[logURL]; ok {
					gotNum = len(roots.RawCertificates())
				}
				if wantNum != gotNum {
					t.Errorf("Expected %d root(s) for Log %s, got %d", wantNum, logURL, gotNum)
				}
			}
		})
	}
}

func pemFileToDERChain(t *testing.T, filename string) [][]byte {
	t.Helper()
	rawChain, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE")
	if err != nil {
		t.Fatalf("failed to load testdata: %v", err)
	}
	return rawChain
}

func getSCTMap(l []*AssignedSCT) map[string]*AssignedSCT {
	m := map[string]*AssignedSCT{}
	for _, asct := range l {
		m[asct.LogURL] = asct
	}
	return m
}

// Stub CT policy to run tests.
type stubCTPolicy struct {
	baseNum int
}

// Builds simplistic policy requiring n SCTs from any Logs for each cert.
func buildStubCTPolicy(n int) stubCTPolicy {
	return stubCTPolicy{baseNum: n}
}

func (stubP stubCTPolicy) LogsByGroup(cert *x509.Certificate, approved *loglist.LogList) (ctpolicy.LogPolicyData, error) {
	baseGroup, err := ctpolicy.BaseGroupFor(approved, stubP.baseNum)
	groups := ctpolicy.LogPolicyData{baseGroup.Name: &baseGroup}
	return groups, err
}

func TestDistributorAddPreChain(t *testing.T) {
	testCases := []struct {
		name     string
		ll       *loglist.LogList
		plc      ctpolicy.CTPolicy
		rawChain [][]byte
		scts     []*AssignedSCT
		wantErr  bool
	}{
		{
			name:     "MalformedChainRequest",
			ll:       sampleValidLogList(t),
			plc:      ctpolicy.ChromeCTPolicy{},
			rawChain: pemFileToDERChain(t, "../trillian/testdata/subleaf.misordered.chain"),
			scts:     nil,
			wantErr:  true,
		},
		{
			name:     "CallBeforeInit",
			ll:       sampleValidLogList(t),
			plc:      ctpolicy.ChromeCTPolicy{},
			rawChain: nil,
			scts:     nil,
			wantErr:  true,
		},
		{
			name:     "InsufficientSCTsForPolicy",
			ll:       sampleValidLogList(t),
			plc:      ctpolicy.AppleCTPolicy{},
			rawChain: pemFileToDERChain(t, "../trillian/testdata/subleaf.chain"), // subleaf chain is fake-ca-1-rooted
			scts:     []*AssignedSCT{},
			wantErr:  true, // Not enough SCTs for policy
		},
		{
			name:     "FullChain1Policy",
			ll:       sampleValidLogList(t),
			plc:      buildStubCTPolicy(1),
			rawChain: pemFileToDERChain(t, "../trillian/testdata/subleaf.chain"),
			scts: []*AssignedSCT{
				{
					LogURL: "ct.googleapis.com/rocketeer/",
					SCT:    testSCT("ct.googleapis.com/rocketeer/"),
				},
			},
			wantErr: false,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			dist, _ := NewDistributor(tc.ll, tc.plc, buildStubLogClient)
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			dist.Run(ctx)

			scts, err := dist.AddPreChain(context.Background(), tc.rawChain)
			if gotErr := (err != nil); gotErr != tc.wantErr {
				t.Errorf("Expected to get errors is %v while actually getting errors is %v", tc.wantErr, gotErr)
			}

			if got, want := len(scts), len(tc.scts); got != want {
				t.Errorf("Expected to get %d SCTs on AddPreChain request, got %d", want, got)
			}
			gotMap := getSCTMap(tc.scts)
			for _, asct := range scts {
				if wantedSCT, ok := gotMap[asct.LogURL]; !ok {
					t.Errorf("dist.AddPreChain() = (_, %v), want err? %t", err, tc.wantErr)
				} else if diff := cmp.Diff(asct, wantedSCT); diff != "" {
					t.Errorf("Got unexpected SCT for Log %q", asct.LogURL)
				}
			}
		})
	}
}
<|endoftext|>