<commit_before>package api

import (
	"fmt"
	"time"

	"github.com/asaskevich/govalidator"
	jwt "github.com/dgrijalva/jwt-go"
	"github.com/ellcrys/util"
	"github.com/ncodes/cocoon/core/api/api/proto_api"
	"github.com/ncodes/cocoon/core/common"
	"github.com/ncodes/cocoon/core/orderer/proto_orderer"
	"github.com/ncodes/cocoon/core/types"
	"github.com/ncodes/cstructs"
	context "golang.org/x/net/context"
)

var (
	// CocoonStatusCreated indicates a created cocoon
	CocoonStatusCreated = "created"

	// CocoonStatusStarted indicates a started cocoon cocoon
	CocoonStatusStarted = "started"

	// CocoonStatusRunning indicates a running cocoon code
	CocoonStatusRunning = "running"

	// CocoonStatusBuilding indicates a cocoon in build phase
	CocoonStatusBuilding = "building"

	// CocoonStatusStopped indicates a stopped cocoon
	CocoonStatusStopped = "stopped"

	// CocoonStatusDead indicates a dead cocoon
	CocoonStatusDead = "dead"
)

// watchCocoonStatus checks the status of a cocoon on interval and passes it to a callback function.
// The callback is also passed a `done` function to be called to stop the status check. Returning
// error from the callback will also stop the status check.
// This function blocks the current goroutine.
func (api *API) watchCocoonStatus(ctx context.Context, cocoon *types.Cocoon, callback func(s string, doneFunc func()) error) error {
	var done = false
	var err error
	var status string
	for !done {
		status, err = api.GetCocoonStatus(cocoon.ID)
		if err != nil {
			return err
		}
		err = callback(status, func() {
			done = true
		})
		if err != nil {
			done = true
			continue
		}
		time.Sleep(500 * time.Millisecond)
	}
	return err
}

// putCocoon adds a new cocoon. If another cocoon with a matching key
// exists, it is effectively shadowed
func (api *API) putCocoon(ctx context.Context, cocoon *types.Cocoon) error {

	ordererConn, err := api.ordererDiscovery.GetGRPConn()
	if err != nil {
		return err
	}
	defer ordererConn.Close()

	createdAt, _ := time.Parse(time.RFC3339Nano, cocoon.CreatedAt)
	odc := proto_orderer.NewOrdererClient(ordererConn)
	_, err = odc.Put(ctx, &proto_orderer.PutTransactionParams{
		CocoonID:   types.SystemCocoonID,
		LedgerName: types.GetSystemPublicLedgerName(),
		Transactions: []*proto_orderer.Transaction{
			&proto_orderer.Transaction{
				Id:        util.UUID4(),
				Key:       types.MakeCocoonKey(cocoon.ID),
				Value:     string(cocoon.ToJSON()),
				CreatedAt: createdAt.Unix(),
			},
		},
	})

	return err
}

// CreateCocoon creates a new cocoon and initial release. The new
// cocoon is also added to the identity's list of cocoons
func (api *API) CreateCocoon(ctx context.Context, req *proto_api.CocoonPayloadRequest) (*proto_api.Response, error) {
	var err error
	var claims jwt.MapClaims
	var releaseID = util.UUID4()
	var now = time.Now()

	if claims, err = api.checkCtxAccessToken(ctx); err != nil {
		return nil, types.ErrInvalidOrExpiredToken
	}

	loggedInIdentity := claims["identity"].(string)
	var cocoon = types.Cocoon{
		ID:             req.ID,
		URL:            req.URL,
		Version:        req.Version,
		Language:       req.Language,
		BuildParam:     req.BuildParam,
		Memory:         int(req.Memory),
		CPUShare:       int(req.CPUShare),
		SigThreshold:   int(req.SigThreshold),
		NumSignatories: int(req.NumSignatories),
		Link:           req.Link,
	}

	// set acl map if provided
	if len(req.ACL) > 0 {
		var aclMap map[string]interface{}
		err := util.FromJSON(req.ACL, &aclMap)
		if err != nil {
			return nil, fmt.Errorf("acl: malformed json")
		}
		cocoon.ACL = types.NewACLMap(aclMap)
	}

	// set firewall if provided
	if len(req.Firewall) > 0 {
		for _, f := range req.Firewall {
			cocoon.Firewall = append(cocoon.Firewall, types.FirewallRule{
				Destination:     f.Destination,
				DestinationPort: f.DestinationPort,
				Protocol:        f.Protocol,
			})
		}
	}

	cocoon.Status = CocoonStatusCreated
	cocoon.Releases = []string{releaseID}
	cocoon.IdentityID = loggedInIdentity
	cocoon.Signatories = append(cocoon.Signatories, cocoon.IdentityID)
	cocoon.CreatedAt = now.UTC().Format(time.RFC3339Nano)

	// ensure a similar cocoon does not exist
	_, err = api.GetCocoon(ctx, &proto_api.GetCocoonRequest{ID: cocoon.ID})
	if err != nil && err != types.ErrCocoonNotFound {
		return nil, err
	} else if err == nil {
		return nil, fmt.Errorf("cocoon with matching ID already exists")
	}

	// If ACL is set, create an ACLMap, set the cocoon.ACL
	if len(req.ACL) > 0 {
		var aclMap map[string]interface{}
		if err := util.FromJSON(req.ACL, &aclMap); err != nil {
			return nil, fmt.Errorf("acl: malformed json")
		}
		cocoon.ACL = types.NewACLMap(aclMap)
	}

	if err := ValidateCocoon(&cocoon); err != nil {
		return nil, err
	}

	// Resolve firewall rules destination
	outputFirewall, err := common.ResolveFirewall(cocoon.Firewall)
	if err != nil {
		return nil, fmt.Errorf("Firewall: %s", err)
	}

	cocoon.Firewall = outputFirewall.DeDup()

	// if a link cocoon id is provided, check if the linked cocoon exists
	// TODO: Provide a permission (ACL) mechanism
	if len(cocoon.Link) > 0 {
		cocoonToLinkTo, err := api.getCocoon(ctx, cocoon.Link)
		if err != nil {
			if err != types.ErrCocoonNotFound {
				return nil, err
			} else if err == types.ErrCocoonNotFound {
				return nil, fmt.Errorf("link: cannot link to a non-existing cocoon %s", cocoon.Link)
			}
		}
		// ensure logged in user owns the cocoon being linked
		if loggedInIdentity != cocoonToLinkTo.IdentityID {
			return nil, fmt.Errorf("link: Permission denied. Cannot create a native link to a cocoon you did not create")
		}
	}
<commit_msg>removed redundant code block and called `Dedup` on firewall object before resolving<commit_after>package api

import (
	"fmt"
	"time"

	"github.com/asaskevich/govalidator"
	jwt "github.com/dgrijalva/jwt-go"
	"github.com/ellcrys/util"
	"github.com/ncodes/cocoon/core/api/api/proto_api"
	"github.com/ncodes/cocoon/core/common"
	"github.com/ncodes/cocoon/core/orderer/proto_orderer"
	"github.com/ncodes/cocoon/core/types"
	"github.com/ncodes/cstructs"
	context "golang.org/x/net/context"
)

var (
	// CocoonStatusCreated indicates a created cocoon
	CocoonStatusCreated = "created"

	// CocoonStatusStarted indicates a started cocoon cocoon
	CocoonStatusStarted = "started"

	// CocoonStatusRunning indicates a running cocoon code
	CocoonStatusRunning = "running"

	// CocoonStatusBuilding indicates a cocoon in build phase
	CocoonStatusBuilding = "building"

	// CocoonStatusStopped indicates a stopped cocoon
	CocoonStatusStopped = "stopped"

	// CocoonStatusDead indicates a dead cocoon
	CocoonStatusDead = "dead"
)

// watchCocoonStatus checks the status of a cocoon on interval and passes it to a callback function.
// The callback is also passed a `done` function to be called to stop the status check. Returning
// error from the callback will also stop the status check.
// This function blocks the current goroutine.
func (api *API) watchCocoonStatus(ctx context.Context, cocoon *types.Cocoon, callback func(s string, doneFunc func()) error) error {
	var done = false
	var err error
	var status string
	for !done {
		status, err = api.GetCocoonStatus(cocoon.ID)
		if err != nil {
			return err
		}
		err = callback(status, func() {
			done = true
		})
		if err != nil {
			done = true
			continue
		}
		time.Sleep(500 * time.Millisecond)
	}
	return err
}

// putCocoon adds a new cocoon. If another cocoon with a matching key
// exists, it is effectively shadowed
func (api *API) putCocoon(ctx context.Context, cocoon *types.Cocoon) error {

	ordererConn, err := api.ordererDiscovery.GetGRPConn()
	if err != nil {
		return err
	}
	defer ordererConn.Close()

	createdAt, _ := time.Parse(time.RFC3339Nano, cocoon.CreatedAt)
	odc := proto_orderer.NewOrdererClient(ordererConn)
	_, err = odc.Put(ctx, &proto_orderer.PutTransactionParams{
		CocoonID:   types.SystemCocoonID,
		LedgerName: types.GetSystemPublicLedgerName(),
		Transactions: []*proto_orderer.Transaction{
			&proto_orderer.Transaction{
				Id:        util.UUID4(),
				Key:       types.MakeCocoonKey(cocoon.ID),
				Value:     string(cocoon.ToJSON()),
				CreatedAt: createdAt.Unix(),
			},
		},
	})

	return err
}

// CreateCocoon creates a new cocoon and initial release. The new
// cocoon is also added to the identity's list of cocoons
func (api *API) CreateCocoon(ctx context.Context, req *proto_api.CocoonPayloadRequest) (*proto_api.Response, error) {
	var err error
	var claims jwt.MapClaims
	var releaseID = util.UUID4()
	var now = time.Now()

	if claims, err = api.checkCtxAccessToken(ctx); err != nil {
		return nil, types.ErrInvalidOrExpiredToken
	}

	loggedInIdentity := claims["identity"].(string)
	var cocoon = types.Cocoon{
		ID:             req.ID,
		URL:            req.URL,
		Version:        req.Version,
		Language:       req.Language,
		BuildParam:     req.BuildParam,
		Memory:         int(req.Memory),
		CPUShare:       int(req.CPUShare),
		SigThreshold:   int(req.SigThreshold),
		NumSignatories: int(req.NumSignatories),
		Link:           req.Link,
	}

	// set acl map if provided
	if len(req.ACL) > 0 {
		var aclMap map[string]interface{}
		err := util.FromJSON(req.ACL, &aclMap)
		if err != nil {
			return nil, fmt.Errorf("acl: malformed json")
		}
		cocoon.ACL = types.NewACLMap(aclMap)
	}

	// set firewall if provided
	if len(req.Firewall) > 0 {
		for _, f := range req.Firewall {
			cocoon.Firewall = append(cocoon.Firewall, types.FirewallRule{
				Destination:     f.Destination,
				DestinationPort: f.DestinationPort,
				Protocol:        f.Protocol,
			})
		}
	}

	cocoon.Status = CocoonStatusCreated
	cocoon.Releases = []string{releaseID}
	cocoon.IdentityID = loggedInIdentity
	cocoon.Signatories = append(cocoon.Signatories, cocoon.IdentityID)
	cocoon.CreatedAt = now.UTC().Format(time.RFC3339Nano)

	// ensure a similar cocoon does not exist
	_, err = api.GetCocoon(ctx, &proto_api.GetCocoonRequest{ID: cocoon.ID})
	if err != nil && err != types.ErrCocoonNotFound {
		return nil, err
	} else if err == nil {
		return nil, fmt.Errorf("cocoon with matching ID already exists")
	}

	if err := ValidateCocoon(&cocoon); err != nil {
		return nil, err
	}

	// Resolve firewall rules destination
	cocoon.Firewall = outputFirewall.DeDup()
	outputFirewall, err := common.ResolveFirewall(cocoon.Firewall)
	if err != nil {
		return nil, fmt.Errorf("Firewall: %s", err)
	}

	// if a link cocoon id is provided, check if the linked cocoon exists
	// TODO: Provide a permission (ACL) mechanism
	if len(cocoon.Link) > 0 {
		cocoonToLinkTo, err := api.getCocoon(ctx, cocoon.Link)
		if err != nil {
			if err != types.ErrCocoonNotFound {
				return nil, err
			} else if err == types.ErrCocoonNotFound {
				return nil, fmt.Errorf("link: cannot link to a non-existing cocoon %s", cocoon.Link)
			}
		}
		// ensure logged in user owns the cocoon being linked
		if loggedInIdentity != cocoonToLinkTo.IdentityID {
			return nil, fmt.Errorf("link: Permission denied. Cannot create a native link to a cocoon you did not create")
		}
	}

{'fraction_non_alphanumeric': 0.10966104767083559, 'fraction_numerical': 0.002990755845568244, 'mean_word_length': 3.6699111299195937, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2188234', 'n_tokens_mistral': 4085, 'n_tokens_neox': 3753, 'n_words': 1014}