<commit_before>package people

import (
	fthealth "github.com/Financial-Times/go-fthealth/v1_1"
	"github.com/Financial-Times/go-logger"
	"github.com/Financial-Times/neo-model-utils-go/mapper"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	"github.com/jmcvetta/neoism"
	"errors"
	"fmt"
	"strings"
)

const (
	messageServiceNotHealthy = "Service is NOT healthy"
	messageServiceHealthy    = "Service is healthy"
)

// Driver interface
type Driver interface {
	Read(id string, transactionID string) (person Person, found bool, err error)
	Healthchecks() []fthealth.Check
}

// CypherDriver struct
type CypherDriver struct {
	conn neoutils.NeoConnection
	env  string
}

//NewCypherDriver instantiate driver
func NewCypherDriver(conn neoutils.NeoConnection, env string) Driver {
	return &CypherDriver{conn, env}
}

// CheckConnectivity tests neo4j by running a simple cypher query
func (pcw *CypherDriver) CheckConnectivity() (string, error) {
	err := neoutils.Check(pcw.conn)
	if err != nil {
		return messageServiceNotHealthy, err
	}
	return messageServiceHealthy, nil
}

func (pcw *CypherDriver) Healthchecks() []fthealth.Check {
	checks := []fthealth.Check{fthealth.Check{
		Name:             "Neo4j Connectivity",
		BusinessImpact:   "Unable to retrieve People from Neo4j",
		PanicGuide:       "https://dewey.ft.com/public-people-api.html",
		Severity:         2,
		TechnicalSummary: "Cannot connect to Neo4j. If this check fails, check that the Neo4J cluster is responding.",
		Checker:          pcw.CheckConnectivity,
	},
	}
	return checks
}

type neoChangeEvent struct {
	StartedAt string
	EndedAt   string
}

type neoReadStruct struct {
	P struct {
		ID              string
		Types           []string
		DirectType      string
		PrefLabel       string
		Labels          []string
		Salutation      string
		BirthYear       int
		EmailAddress    string
		TwitterHandle   string
		FacebookProfile string
		LinkedinProfile string
		Description     string
		DescriptionXML  string
		ImageURL        string
	}
	M []struct {
		M struct {
			ID           string
			Types        []string
			DirectType   string
			PrefLabel    string
			Title        string
			ChangeEvents []neoChangeEvent
		}
		O struct {
			ID         string
			Types      []string
			DirectType string
			PrefLabel  string
			Labels     []string
		}
		R []struct {
			ID           string
			Types        []string
			DirectType   string
			PrefLabel    string
			ChangeEvents []neoChangeEvent
		}
	}
}

func (pcw CypherDriver) Read(uuid string, transactionID string) (Person, bool, error) {
	person := Person{}
	results := []struct {
		Rs []neoReadStruct
	}{}

	query := &neoism.CypherQuery{
		Statement: `
                        MATCH (identifier:UPPIdentifier{value:{uuid}})
                        MATCH (identifier)-[:IDENTIFIES]->(pp:Person)-[:EQUIVALENT_TO]->(canonical:Person)
						MATCH (canonical)<-[:EQUIVALENT_TO]-(p:Person)
                        OPTIONAL MATCH (p)<-[:HAS_MEMBER]-(m:Membership)
                        OPTIONAL MATCH (m)-[:HAS_ORGANISATION]->(o:Organisation)
                        OPTIONAL MATCH (m)-[rr:HAS_ROLE]->(r:MembershipRole)
                        WITH    canonical,
                                { id:o.uuid, types:labels(o), prefLabel:o.prefLabel} as o,
                                { id:m.uuid, types:labels(m), prefLabel:m.prefLabel, title:m.title, changeEvents:[{startedAt:m.inceptionDate}, {endedAt:m.terminationDate}] } as m ORDER BY m.terminationDateEpoch DESC, m.inceptionDateEpoch DESC, r, rr
						WITH 	m, o,canonical,
                        		{ id:r.uuid, types:labels(r), prefLabel:r.prefLabel, changeEvents:[{startedAt:rr.inceptionDate},{endedAt:rr.terminationDate}] } as r ORDER BY rr.terminationDateEpoch DESC, rr.inceptionDateEpoch DESC
                        WITH canonical, m, o, collect(r) as r ORDER BY o.uuid DESC
                        WITH canonical, collect({m:m, o:o, r:r}) as m
                        WITH m, { ID:canonical.prefUUID, types:labels(canonical), prefLabel:canonical.prefLabel, labels:canonical.aliases,
								birthYear:canonical.birthYear, salutation:canonical.salutation, emailAddress:canonical.emailAddress,
								twitterHandle:canonical.twitterHandle, facebookProfile:canonical.facebookProfile, linkedinProfile:canonical.linkedinProfile,
								imageUrl:canonical.imageUrl, Description:canonical.description, descriptionXML:canonical.descriptionXML} as p
                        RETURN collect ({p:p, m:m}) as rs
                        `,
		Parameters: neoism.Props{"uuid": uuid},
		Result:     &results,
	}

	err := pcw.conn.CypherBatch([]*neoism.CypherQuery{query})
	if err != nil {
		logger.WithTransactionID(transactionID).WithField("UUID", uuid).Error("Error Querying Neo4J for a Person")
		return Person{}, true, err
	}

	if len(results) == 0 || (len(results[0].Rs) == 0 || results[0].Rs[0].P.ID == "") {
		p, f, e := pcw.ReadOldConcordanceModel(uuid, transactionID)
		return p, f, e
	} else if len(results) != 1 {
		logger.WithTransactionID(transactionID).WithField("UUID", uuid).Errorf("Multiple people found with the same uuid: %s", uuid)
		return Person{}, true, err
	}

	person = neoReadStructToPerson(results[0].Rs[0], pcw.env)
	return person, true, nil
}

func (pcw CypherDriver) ReadOldConcordanceModel(uuid string, transactionID string) (person Person, found bool, err error) {
	person = Person{}
	results := []struct {
		Rs []neoReadStruct
	}{}

	query := &neoism.CypherQuery{
		Statement: `
                        MATCH (identifier:UPPIdentifier{value:{uuid}})
                        MATCH (identifier)-[:IDENTIFIES]->(p:Person)
                        OPTIONAL MATCH (p)<-[:HAS_MEMBER]-(m:Membership)
                        OPTIONAL MATCH (m)-[:HAS_ORGANISATION]->(o:Organisation)
                        OPTIONAL MATCH (m)-[rr:HAS_ROLE]->(r:MembershipRole)
                        WITH    p,
                                { id:o.uuid, types:labels(o), prefLabel:o.prefLabel} as o,
								{ id:m.uuid, types:labels(m), prefLabel:m.prefLabel, title:m.title, changeEvents:[{startedAt:m.inceptionDate}, {endedAt:m.terminationDate}] } as m ORDER BY m.terminationDateEpoch DESC, m.inceptionDateEpoch DESC, r, rr
						WITH   	p, m, o,
								{ id:r.uuid, types:labels(r), prefLabel:r.prefLabel, changeEvents:[{startedAt:rr.inceptionDate},{endedAt:rr.terminationDate}] } as r ORDER BY rr.terminationDateEpoch DESC, rr.inceptionDateEpoch DESC
                        WITH p, m, o, collect(r) as r ORDER BY o.uuid DESC
                        WITH p, collect({m:m, o:o, r:r}) as m
                        WITH m, { id:p.uuid, types:labels(p), prefLabel:p.prefLabel, labels:p.aliases,
												     birthYear:p.birthYear, salutation:p.salutation, emailAddress:p.emailAddress,
														 twitterHandle:p.twitterHandle, facebookProfile:p.facebookProfile, linkedinProfile:p.linkedinProfile,
														 imageUrl:p.imageUrl, Description:p.description, descriptionXML:p.descriptionXML} as p
                        RETURN collect ({p:p, m:m}) as rs
                        `,
		Parameters: neoism.Props{"uuid": uuid},
		Result:     &results,
	}

	err = pcw.conn.CypherBatch([]*neoism.CypherQuery{query})
	if err != nil {
		logger.WithTransactionID(transactionID).WithField("UUID", uuid).Error("Query execution failed")
		return Person{}, false, err
	} else if len(results) == 0 || len(results[0].Rs) == 0 {
		logger.WithTransactionID(transactionID).WithField("UUID", uuid).Error("Person not found")
		return Person{}, false, nil
	} else if len(results) != 1 && len(results[0].Rs) != 1 {
		logger.WithTransactionID(transactionID).WithField("UUID", uuid).Errorf("Multiple people found with the same uuid:%s !", uuid)
		return Person{}, true, err
	}

	person = neoReadStructToPerson(results[0].Rs[0], pcw.env)
	return person, true, nil
}

func neoReadStructToPerson(neo neoReadStruct, env string) Person {
	public := Person{}
	public.Thing = Thing{}
	public.ID = mapper.IDURL(neo.P.ID)
	public.APIURL = mapper.APIURL(neo.P.ID, neo.P.Types, env)
	public.Types = mapper.TypeURIs(neo.P.Types)
	public.DirectType = filterToMostSpecificType(neo.P.Types)
	public.PrefLabel = neo.P.PrefLabel
	if len(neo.P.Labels) > 0 {
		public.Labels = neo.P.Labels
	}
	public.BirthYear = neo.P.BirthYear
	public.Salutation = neo.P.Salutation
	public.Description = neo.P.Description
	public.DescriptionXML = neo.P.DescriptionXML
	public.EmailAddress = neo.P.EmailAddress
	public.TwitterHandle = neo.P.TwitterHandle
	public.FacebookProfile = neo.P.FacebookProfile
	public.ImageURL = neo.P.ImageURL

	if len(neo.M) > 0 {
		memberships := []Membership{}
		for _, neoMem := range neo.M {
			if neoMem.M.ID != "" && neoMem.O.ID != "" && len(neoMem.R) > 0 {
				membership := Membership{}
				membership.Title = neoMem.M.PrefLabel
				membership.Types = mapper.TypeURIs(neoMem.M.Types)
				membership.DirectType = filterToMostSpecificType(neoMem.M.Types)
				membership.Organisation = Organisation{}
				membership.Organisation.Thing = Thing{}
				membership.Organisation.ID = mapper.IDURL(neoMem.O.ID)
				membership.Organisation.APIURL = mapper.APIURL(neoMem.O.ID, neoMem.O.Types, env)
				membership.Organisation.Types = mapper.TypeURIs(neoMem.O.Types)
				membership.Organisation.DirectType = filterToMostSpecificType(neoMem.O.Types)
				membership.Organisation.PrefLabel = neoMem.O.PrefLabel
				if len(neoMem.O.Labels) > 0 {
					membership.Organisation.Labels = neoMem.O.Labels
				}
				if a, b := changeEvent(neoMem.M.ChangeEvents); a == true {
					membership.ChangeEvents = b
				}

				roles := []Role{}
				for _, neoRole := range neoMem.R {
					if neoRole.ID != "" {
						role := Role{}
						role.Thing = Thing{}
						role.ID = mapper.IDURL(neoRole.ID)
						role.APIURL = mapper.APIURL(neoRole.ID, neoRole.Types, env)
						role.Types = mapper.TypeURIs(neoRole.Types)
						role.DirectType = filterToMostSpecificType(neoRole.Types)
						role.PrefLabel = neoRole.PrefLabel
						if a, b := changeEvent(neoRole.ChangeEvents); a == true {
							role.ChangeEvents = b
						}
						roles = append(roles, role)
					}
				}
				if len(roles) > 0 {
					membership.Roles = roles
					memberships = append(memberships, membership)
				}
			}
			public.Memberships = memberships
		}
	}

	return public
}

func changeEvent(neoChgEvts []neoChangeEvent) (bool, []ChangeEvent) {
	var results []ChangeEvent

	if neoChgEvts[0].StartedAt == "" && neoChgEvts[1].EndedAt == "" {
		results = make([]ChangeEvent, 0, 0)
		return false, results
	}
	for _, neoChgEvt := range neoChgEvts {
		if neoChgEvt.StartedAt != "" {
			results = append(results, ChangeEvent{StartedAt: neoChgEvt.StartedAt})
		}
		if neoChgEvt.EndedAt != "" {
			results = append(results, ChangeEvent{EndedAt: neoChgEvt.EndedAt})
		}
	}
	return true, results
}

func filterToMostSpecificType(unfilteredTypes []string) string {
	mostSpecificType, err := mapper.MostSpecificType(unfilteredTypes)
	if err != nil {
		return ""
	}
	fullURI := mapper.TypeURIs([]string{mostSpecificType})
	return fullURI[0]
}

func handleEmptyError(e error, defaultMessage string) error {

	if e.Error() != "" {
		return e
	}

	neoError, ok := e.(neoism.NeoError)

	if !ok {
		return errors.New(defaultMessage)
	}

	if neoError.Exception != "" {
		neoError.Message = neoError.Exception
		return neoError
	}

	if neoError.Cause != nil {
		cause := fmt.Sprint(neoError.Cause)

		if cause != "" {
			neoError.Message = "Cause: " + cause
			return neoError
		}
	}

	if len(neoError.Stacktrace) > 0 {
		neoError.Message = strings.Join(neoError.Stacktrace, ", ")
		return neoError
	}

	neoError.Message = defaultMessage

	return neoError
}<commit_msg>Revert "Memberships and roles are order by termination date, then inception date with the most recent at the top of the list"<commit_after>package people

import (
	fthealth "github.com/Financial-Times/go-fthealth/v1_1"
	"github.com/Financial-Times/go-logger"
	"github.com/Financial-Times/neo-model-utils-go/mapper"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	"github.com/jmcvetta/neoism"
	"errors"
	"fmt"
	"strings"
)

const (
	messageServiceNotHealthy = "Service is NOT healthy"
	messageServiceHealthy    = "Service is healthy"
)

// Driver interface
type Driver interface {
	Read(id string, transactionID string) (person Person, found bool, err error)
	Healthchecks() []fthealth.Check
}

// CypherDriver struct
type CypherDriver struct {
	conn neoutils.NeoConnection
	env  string
}

//NewCypherDriver instantiate driver
func NewCypherDriver(conn neoutils.NeoConnection, env string) Driver {
	return &CypherDriver{conn, env}
}

// CheckConnectivity tests neo4j by running a simple cypher query
func (pcw *CypherDriver) CheckConnectivity() (string, error) {
	err := neoutils.Check(pcw.conn)
	if err != nil {
		return messageServiceNotHealthy, err
	}
	return messageServiceHealthy, nil
}

func (pcw *CypherDriver) Healthchecks() []fthealth.Check {
	checks := []fthealth.Check{fthealth.Check{
		Name:             "Neo4j Connectivity",
		BusinessImpact:   "Unable to retrieve People from Neo4j",
		PanicGuide:       "https://dewey.ft.com/public-people-api.html",
		Severity:         2,
		TechnicalSummary: "Cannot connect to Neo4j. If this check fails, check that the Neo4J cluster is responding.",
		Checker:          pcw.CheckConnectivity,
	},
	}
	return checks
}

type neoChangeEvent struct {
	StartedAt string
	EndedAt   string
}

type neoReadStruct struct {
	P struct {
		ID              string
		Types           []string
		DirectType      string
		PrefLabel       string
		Labels          []string
		Salutation      string
		BirthYear       int
		EmailAddress    string
		TwitterHandle   string
		FacebookProfile string
		LinkedinProfile string
		Description     string
		DescriptionXML  string
		ImageURL        string
	}
	M []struct {
		M struct {
			ID           string
			Types        []string
			DirectType   string
			PrefLabel    string
			Title        string
			ChangeEvents []neoChangeEvent
		}
		O struct {
			ID         string
			Types      []string
			DirectType string
			PrefLabel  string
			Labels     []string
		}
		R []struct {
			ID           string
			Types        []string
			DirectType   string
			PrefLabel    string
			ChangeEvents []neoChangeEvent
		}
	}
}

func (pcw CypherDriver) Read(uuid string, transactionID string) (Person, bool, error) {
	person := Person{}
	results := []struct {
		Rs []neoReadStruct
	}{}

	query := &neoism.CypherQuery{
		Statement: `
                        MATCH (identifier:UPPIdentifier{value:{uuid}})
                        MATCH (identifier)-[:IDENTIFIES]->(pp:Person)-[:EQUIVALENT_TO]->(canonical:Person)
						MATCH (canonical)<-[:EQUIVALENT_TO]-(p:Person)
                        OPTIONAL MATCH (p)<-[:HAS_MEMBER]-(m:Membership)
                        OPTIONAL MATCH (m)-[:HAS_ORGANISATION]->(o:Organisation)
                        OPTIONAL MATCH (m)-[rr:HAS_ROLE]->(r:MembershipRole)
                        WITH    canonical,
                                { id:o.uuid, types:labels(o), prefLabel:o.prefLabel} as o,
                                { id:m.uuid, types:labels(m), prefLabel:m.prefLabel, title:m.title, changeEvents:[{startedAt:m.inceptionDate}, {endedAt:m.terminationDate}] } as m,
                                { id:r.uuid, types:labels(r), prefLabel:r.prefLabel, changeEvents:[{startedAt:rr.inceptionDate}, {endedAt:rr.terminationDate}] } as r
                        WITH canonical, m, o, collect(r) as r ORDER BY o.uuid DESC
                        WITH canonical, collect({m:m, o:o, r:r}) as m
                        WITH m, { ID:canonical.prefUUID, types:labels(canonical), prefLabel:canonical.prefLabel, labels:canonical.aliases,
								birthYear:canonical.birthYear, salutation:canonical.salutation, emailAddress:canonical.emailAddress,
								twitterHandle:canonical.twitterHandle, facebookProfile:canonical.facebookProfile, linkedinProfile:canonical.linkedinProfile,
								imageUrl:canonical.imageUrl, Description:canonical.description, descriptionXML:canonical.descriptionXML} as p
                        RETURN collect ({p:p, m:m}) as rs
                        `,
		Parameters: neoism.Props{"uuid": uuid},
		Result:     &results,
	}

	err := pcw.conn.CypherBatch([]*neoism.CypherQuery{query})
	if err != nil {
		logger.WithTransactionID(transactionID).WithField("UUID", uuid).Error("Error Querying Neo4J for a Person")
		return Person{}, true, err
	}

	if len(results) == 0 || (len(results[0].Rs) == 0 || results[0].Rs[0].P.ID == "") {
		p, f, e := pcw.ReadOldConcordanceModel(uuid, transactionID)
		return p, f, e
	} else if len(results) != 1 {
		logger.WithTransactionID(transactionID).WithField("UUID", uuid).Errorf("Multiple people found with the same uuid: %s", uuid)
		return Person{}, true, err
	}

	person = neoReadStructToPerson(results[0].Rs[0], pcw.env)
	return person, true, nil
}

func (pcw CypherDriver) ReadOldConcordanceModel(uuid string, transactionID string) (person Person, found bool, err error) {
	person = Person{}
	results := []struct {
		Rs []neoReadStruct
	}{}

	query := &neoism.CypherQuery{
		Statement: `
                        MATCH (identifier:UPPIdentifier{value:{uuid}})
                        MATCH (identifier)-[:IDENTIFIES]->(p:Person)
                        OPTIONAL MATCH (p)<-[:HAS_MEMBER]-(m:Membership)
                        OPTIONAL MATCH (m)-[:HAS_ORGANISATION]->(o:Organisation)
                        OPTIONAL MATCH (m)-[rr:HAS_ROLE]->(r:MembershipRole)
                        WITH    p,
                                { id:o.uuid, types:labels(o), prefLabel:o.prefLabel} as o,
                                { id:m.uuid, types:labels(m), prefLabel:m.prefLabel, title:m.title, changeEvents:[{startedAt:m.inceptionDate}, {endedAt:m.terminationDate}] } as m,
                                { id:r.uuid, types:labels(r), prefLabel:r.prefLabel, changeEvents:[{startedAt:rr.inceptionDate}, {endedAt:rr.terminationDate}] } as r
                        WITH p, m, o, collect(r) as r ORDER BY o.uuid DESC
                        WITH p, collect({m:m, o:o, r:r}) as m
                        WITH m, { id:p.uuid, types:labels(p), prefLabel:p.prefLabel, labels:p.aliases,
												     birthYear:p.birthYear, salutation:p.salutation, emailAddress:p.emailAddress,
														 twitterHandle:p.twitterHandle, facebookProfile:p.facebookProfile, linkedinProfile:p.linkedinProfile,
														 imageUrl:p.imageUrl, Description:p.description, descriptionXML:p.descriptionXML} as p
                        RETURN collect ({p:p, m:m}) as rs
                        `,
		Parameters: neoism.Props{"uuid": uuid},
		Result:     &results,
	}

	err = pcw.conn.CypherBatch([]*neoism.CypherQuery{query})
	if err != nil {
		logger.WithTransactionID(transactionID).WithField("UUID", uuid).Error("Query execution failed")
		return Person{}, false, err
	} else if len(results) == 0 || len(results[0].Rs) == 0 {
		logger.WithTransactionID(transactionID).WithField("UUID", uuid).Error("Person not found")
		return Person{}, false, nil
	} else if len(results) != 1 && len(results[0].Rs) != 1 {
		logger.WithTransactionID(transactionID).WithField("UUID", uuid).Errorf("Multiple people found with the same uuid:%s !", uuid)
		return Person{}, true, err
	}

	person = neoReadStructToPerson(results[0].Rs[0], pcw.env)
	return person, true, nil
}

func neoReadStructToPerson(neo neoReadStruct, env string) Person {
	public := Person{}
	public.Thing = Thing{}
	public.ID = mapper.IDURL(neo.P.ID)
	public.APIURL = mapper.APIURL(neo.P.ID, neo.P.Types, env)
	public.Types = mapper.TypeURIs(neo.P.Types)
	public.DirectType = filterToMostSpecificType(neo.P.Types)
	public.PrefLabel = neo.P.PrefLabel
	if len(neo.P.Labels) > 0 {
		public.Labels = neo.P.Labels
	}
	public.BirthYear = neo.P.BirthYear
	public.Salutation = neo.P.Salutation
	public.Description = neo.P.Description
	public.DescriptionXML = neo.P.DescriptionXML
	public.EmailAddress = neo.P.EmailAddress
	public.TwitterHandle = neo.P.TwitterHandle
	public.FacebookProfile = neo.P.FacebookProfile
	public.ImageURL = neo.P.ImageURL

	if len(neo.M) > 0 {
		memberships := []Membership{}
		for _, neoMem := range neo.M {
			if neoMem.M.ID != "" && neoMem.O.ID != "" && len(neoMem.R) > 0 {
				membership := Membership{}
				membership.Title = neoMem.M.PrefLabel
				membership.Types = mapper.TypeURIs(neoMem.M.Types)
				membership.DirectType = filterToMostSpecificType(neoMem.M.Types)
				membership.Organisation = Organisation{}
				membership.Organisation.Thing = Thing{}
				membership.Organisation.ID = mapper.IDURL(neoMem.O.ID)
				membership.Organisation.APIURL = mapper.APIURL(neoMem.O.ID, neoMem.O.Types, env)
				membership.Organisation.Types = mapper.TypeURIs(neoMem.O.Types)
				membership.Organisation.DirectType = filterToMostSpecificType(neoMem.O.Types)
				membership.Organisation.PrefLabel = neoMem.O.PrefLabel
				if len(neoMem.O.Labels) > 0 {
					membership.Organisation.Labels = neoMem.O.Labels
				}
				if a, b := changeEvent(neoMem.M.ChangeEvents); a == true {
					membership.ChangeEvents = b
				}

				roles := []Role{}
				for _, neoRole := range neoMem.R {
					if neoRole.ID != "" {
						role := Role{}
						role.Thing = Thing{}
						role.ID = mapper.IDURL(neoRole.ID)
						role.APIURL = mapper.APIURL(neoRole.ID, neoRole.Types, env)
						role.Types = mapper.TypeURIs(neoRole.Types)
						role.DirectType = filterToMostSpecificType(neoRole.Types)
						role.PrefLabel = neoRole.PrefLabel
						if a, b := changeEvent(neoRole.ChangeEvents); a == true {
							role.ChangeEvents = b
						}
						roles = append(roles, role)
					}
				}
				if len(roles) > 0 {
					membership.Roles = roles
					memberships = append(memberships, membership)
				}
			}
			public.Memberships = memberships
		}
	}

	return public
}

func changeEvent(neoChgEvts []neoChangeEvent) (bool, []ChangeEvent) {
	var results []ChangeEvent

	if neoChgEvts[0].StartedAt == "" && neoChgEvts[1].EndedAt == "" {
		results = make([]ChangeEvent, 0, 0)
		return false, results
	}
	for _, neoChgEvt := range neoChgEvts {
		if neoChgEvt.StartedAt != "" {
			results = append(results, ChangeEvent{StartedAt: neoChgEvt.StartedAt})
		}
		if neoChgEvt.EndedAt != "" {
			results = append(results, ChangeEvent{EndedAt: neoChgEvt.EndedAt})
		}
	}
	return true, results
}

func filterToMostSpecificType(unfilteredTypes []string) string {
	mostSpecificType, err := mapper.MostSpecificType(unfilteredTypes)
	if err != nil {
		return ""
	}
	fullURI := mapper.TypeURIs([]string{mostSpecificType})
	return fullURI[0]
}

func handleEmptyError(e error, defaultMessage string) error {

	if e.Error() != "" {
		return e
	}

	neoError, ok := e.(neoism.NeoError)

	if !ok {
		return errors.New(defaultMessage)
	}

	if neoError.Exception != "" {
		neoError.Message = neoError.Exception
		return neoError
	}

	if neoError.Cause != nil {
		cause := fmt.Sprint(neoError.Cause)

		if cause != "" {
			neoError.Message = "Cause: " + cause
			return neoError
		}
	}

	if len(neoError.Stacktrace) > 0 {
		neoError.Message = strings.Join(neoError.Stacktrace, ", ")
		return neoError
	}

	neoError.Message = defaultMessage

	return neoError
}<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"

	"github.com/boltdb/bolt"
)

func main() {
	db, err := bolt.Open("my.db", 0600, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	db.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucket([]byte("MyBucket"))
		if err != nil {
			return fmt.Errorf("create bucket: %s", err)
		}
		return nil
	})

	db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("MyBucket"))
		err := b.Put([]byte("answer"), []byte("42"))
		return err
	})

	db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("MyBucket"))
		v := b.Get([]byte("answer"))
		fmt.Printf("The answer is: %s\n", v)
		return nil
	})

	db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("MyBucket"))
		c := b.Cursor()

		for k, v := c.First(); k != nil; k, v = c.Next() {
			fmt.Printf("key=%s, value=%s\n", k, v)
		}

		return nil
	})
}
<commit_msg>Initial implementation (Not tested)<commit_after>package main

import (
	"fmt"
	"log"

	"github.com/boltdb/bolt"
)

type PersistentMap struct {
	db   *bolt.DB
	name string
}

func NewPersistentMap(name, basepath string) *PersistentMap {
	db, err := bolt.Open(basepath+name+".db", 0600, nil)
	if err != nil {
		log.Fatal(err)
	}

	db.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucket([]byte(name))
		if err != nil {
			return fmt.Errorf("create bucket: %s", err)
		}
		return nil
	})

	return &PersistentMap{db: db, name: name}
}

func (m *PersistentMap) Set(key string, data []byte) {
	m.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(m.name))
		err := b.Put([]byte(key), data)
		return err
	})
}

func (m *PersistentMap) Get(key string) []byte {
	returnValue := []byte{}
	m.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(m.name))
		returnValue = b.Get([]byte(key))
		return nil
	})
	return returnValue
}

func (m *PersistentMap) Close() {
	m.db.Close()
}

func (m *PersistentMap) Delete(key string) {
	m.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(m.name))
		err := b.Delete([]byte(key))
		return err
	})
}

type Tuple struct {
	Key   string
	Value []byte
}

func (m *PersistentMap) IterationChannel() chan Tuple {
	returnChan := make(chan Tuple)

	go func() {
		m.db.View(func(tx *bolt.Tx) error {
			b := tx.Bucket([]byte(m.name))
			c := b.Cursor()

			for k, v := c.First(); k != nil; k, v = c.Next() {
				returnChan <- Tuple{string(k), v}
			}
			close(returnChan)
			return nil
		})
	}()

	return returnChan
}

func main() {

	persistentmap := NewPersistentMap("test", "./")
	fmt.Println(persistentmap)

	persistentmap.Set("answer1", []byte("42"))
	persistentmap.Set("answer2", []byte("43"))
	persistentmap.Set("answer3", []byte("44"))

	for tuple := range persistentmap.IterationChannel() {
		fmt.Println("Tuple", tuple.Key, string(tuple.Value))
	}

	fmt.Println("E1", string(persistentmap.Get("answer1")))
	persistentmap.Delete("answer1")

	fmt.Println("E2", string(persistentmap.Get("answer1")))

}
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package maas

import (
	"fmt"
	"net/http"

	"github.com/juju/errors"
	"github.com/juju/gomaasapi"
	"github.com/juju/loggo"

	"github.com/juju/juju/cloud"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/environs/config"
)

// Logger for the MAAS provider.
var logger = loggo.GetLogger("juju.provider.maas")

type maasEnvironProvider struct {
	environProviderCredentials
}

var _ environs.EnvironProvider = (*maasEnvironProvider)(nil)

var providerInstance maasEnvironProvider

func (maasEnvironProvider) Open(cfg *config.Config) (environs.Environ, error) {
	logger.Debugf("opening model %q.", cfg.Name())
	env, err := NewEnviron(cfg)
	if err != nil {
		return nil, err
	}
	return env, nil
}

var errAgentNameAlreadySet = errors.New(
	"maas-agent-name is already set; this should not be set by hand")

// RestrictedConfigAttributes is specified in the EnvironProvider interface.
func (p maasEnvironProvider) RestrictedConfigAttributes() []string {
	return []string{"maas-server"}
}

// PrepareForCreateEnvironment is specified in the EnvironProvider interface.
func (p maasEnvironProvider) PrepareForCreateEnvironment(cfg *config.Config) (*config.Config, error) {
	attrs := cfg.UnknownAttrs()
	oldName, found := attrs["maas-agent-name"]
	if found && oldName != "" {
		return nil, errAgentNameAlreadySet
	}
	attrs["maas-agent-name"] = cfg.UUID()
	return cfg.Apply(attrs)
}

// BootstrapConfig is specified in the EnvironProvider interface.
func (p maasEnvironProvider) BootstrapConfig(args environs.BootstrapConfigParams) (*config.Config, error) {
	// For MAAS, either:
	// 1. the endpoint from the cloud definition defines the MAAS server URL
	//    (if a full cloud definition had been set up)
	// 2. the region defines the MAAS server ip/host
	//    (if the bootstrap shortcut is used)
	server := args.CloudEndpoint
	if server == "" && args.CloudRegion != "" {
		server = fmt.Sprintf("http://%s/MAAS", args.CloudRegion)
	}
	if server == "" {
		return nil, errors.New("MAAS server not specified")
	}
	attrs := map[string]interface{}{
		"maas-server": server,
	}
	// Add the credentials.
	switch authType := args.Credentials.AuthType(); authType {
	case cloud.OAuth1AuthType:
		credentialAttrs := args.Credentials.Attributes()
		for k, v := range credentialAttrs {
			attrs[k] = v
		}
	default:
		return nil, errors.NotSupportedf("%q auth-type", authType)
	}
	cfg, err := args.Config.Apply(attrs)
	if err != nil {
		return nil, errors.Trace(err)
	}
	return p.PrepareForCreateEnvironment(cfg)
}

// PrepareForBootstrap is specified in the EnvironProvider interface.
func (p maasEnvironProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) {
	env, err := p.Open(cfg)
	if err != nil {
		return nil, err
	}
	if ctx.ShouldVerifyCredentials() {
		if err := verifyCredentials(env.(*maasEnviron)); err != nil {
			return nil, err
		}
	}
	return env, nil
}

func verifyCredentials(env *maasEnviron) error {
	var err error
	// Verify we can connect to the server and authenticate.
	// TODO (mfoord): horrible hardcoded version check.
	if env.apiVersion == "2.0" {
		// TODO (mfoord): use a lighterweight endpoint than machines.
		_, err = env.maasController.Machines(gomaasapi.MachinesParams{})
	} else {
		_, err = env.getMAASClient().GetSubObject("maas").CallGet("get_config", nil)
	}
	if err, ok := errors.Cause(err).(gomaasapi.ServerError); ok && err.StatusCode == http.StatusUnauthorized {
		logger.Debugf("authentication failed: %v", err)
		return errors.New(`authentication failed.

Please ensure the credentials are correct.`)
	}
	return nil
}

// SecretAttrs is specified in the EnvironProvider interface.
func (prov maasEnvironProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) {
	secretAttrs := make(map[string]string)
	maasCfg, err := prov.newConfig(cfg)
	if err != nil {
		return nil, err
	}
	secretAttrs["maas-oauth"] = maasCfg.maasOAuth()
	return secretAttrs, nil
}

// DetectRegions is specified in the environs.CloudRegionDetector interface.
func (p maasEnvironProvider) DetectRegions() ([]cloud.Region, error) {
	return nil, errors.NotFoundf("regions")
}
<commit_msg>Comment update<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package maas

import (
	"fmt"
	"net/http"

	"github.com/juju/errors"
	"github.com/juju/gomaasapi"
	"github.com/juju/loggo"

	"github.com/juju/juju/cloud"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/environs/config"
)

// Logger for the MAAS provider.
var logger = loggo.GetLogger("juju.provider.maas")

type maasEnvironProvider struct {
	environProviderCredentials
}

var _ environs.EnvironProvider = (*maasEnvironProvider)(nil)

var providerInstance maasEnvironProvider

func (maasEnvironProvider) Open(cfg *config.Config) (environs.Environ, error) {
	logger.Debugf("opening model %q.", cfg.Name())
	env, err := NewEnviron(cfg)
	if err != nil {
		return nil, err
	}
	return env, nil
}

var errAgentNameAlreadySet = errors.New(
	"maas-agent-name is already set; this should not be set by hand")

// RestrictedConfigAttributes is specified in the EnvironProvider interface.
func (p maasEnvironProvider) RestrictedConfigAttributes() []string {
	return []string{"maas-server"}
}

// PrepareForCreateEnvironment is specified in the EnvironProvider interface.
func (p maasEnvironProvider) PrepareForCreateEnvironment(cfg *config.Config) (*config.Config, error) {
	attrs := cfg.UnknownAttrs()
	oldName, found := attrs["maas-agent-name"]
	if found && oldName != "" {
		return nil, errAgentNameAlreadySet
	}
	attrs["maas-agent-name"] = cfg.UUID()
	return cfg.Apply(attrs)
}

// BootstrapConfig is specified in the EnvironProvider interface.
func (p maasEnvironProvider) BootstrapConfig(args environs.BootstrapConfigParams) (*config.Config, error) {
	// For MAAS, either:
	// 1. the endpoint from the cloud definition defines the MAAS server URL
	//    (if a full cloud definition had been set up)
	// 2. the region defines the MAAS server ip/host
	//    (if the bootstrap shortcut is used)
	server := args.CloudEndpoint
	if server == "" && args.CloudRegion != "" {
		server = fmt.Sprintf("http://%s/MAAS", args.CloudRegion)
	}
	if server == "" {
		return nil, errors.New("MAAS server not specified")
	}
	attrs := map[string]interface{}{
		"maas-server": server,
	}
	// Add the credentials.
	switch authType := args.Credentials.AuthType(); authType {
	case cloud.OAuth1AuthType:
		credentialAttrs := args.Credentials.Attributes()
		for k, v := range credentialAttrs {
			attrs[k] = v
		}
	default:
		return nil, errors.NotSupportedf("%q auth-type", authType)
	}
	cfg, err := args.Config.Apply(attrs)
	if err != nil {
		return nil, errors.Trace(err)
	}
	return p.PrepareForCreateEnvironment(cfg)
}

// PrepareForBootstrap is specified in the EnvironProvider interface.
func (p maasEnvironProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) {
	env, err := p.Open(cfg)
	if err != nil {
		return nil, err
	}
	if ctx.ShouldVerifyCredentials() {
		if err := verifyCredentials(env.(*maasEnviron)); err != nil {
			return nil, err
		}
	}
	return env, nil
}

func verifyCredentials(env *maasEnviron) error {
	var err error
	// Verify we can connect to the server and authenticate.
	// TODO (mfoord): horrible hardcoded version check.
	if env.apiVersion == "2.0" {
		// TODO (mfoord): use a lighterweight endpoint than machines.
		// Could implement /api/2.0/maas/ op=get_config in new API
		// layer.
		_, err = env.maasController.Machines(gomaasapi.MachinesParams{})
	} else {
		_, err = env.getMAASClient().GetSubObject("maas").CallGet("get_config", nil)
	}
	if err, ok := errors.Cause(err).(gomaasapi.ServerError); ok && err.StatusCode == http.StatusUnauthorized {
		logger.Debugf("authentication failed: %v", err)
		return errors.New(`authentication failed.

Please ensure the credentials are correct.`)
	}
	return nil
}

// SecretAttrs is specified in the EnvironProvider interface.
func (prov maasEnvironProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) {
	secretAttrs := make(map[string]string)
	maasCfg, err := prov.newConfig(cfg)
	if err != nil {
		return nil, err
	}
	secretAttrs["maas-oauth"] = maasCfg.maasOAuth()
	return secretAttrs, nil
}

// DetectRegions is specified in the environs.CloudRegionDetector interface.
func (p maasEnvironProvider) DetectRegions() ([]cloud.Region, error) {
	return nil, errors.NotFoundf("regions")
}
<|endoftext|>


<commit_before>// This package implements a provisioner for Packer that executes
// shell scripts within the remote machine.
package shell

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"strings"
	"time"

	"github.com/mitchellh/packer/common"
	"github.com/mitchellh/packer/helper/config"
	"github.com/mitchellh/packer/packer"
	"github.com/mitchellh/packer/template/interpolate"
)

type Config struct {
	common.PackerConfig `mapstructure:",squash"`

	// If true, the script contains binary and line endings will not be
	// converted from Windows to Unix-style.
	Binary bool

	// An inline script to execute. Multiple strings are all executed
	// in the context of a single shell.
	Inline []string

	// The shebang value used when running inline scripts.
	InlineShebang string `mapstructure:"inline_shebang"`

	// The local path of the shell script to upload and execute.
	Script string

	// An array of multiple scripts to run.
	Scripts []string

	// An array of environment variables that will be injected before
	// your command(s) are executed.
	Vars []string `mapstructure:"environment_vars"`

	// The remote path where the local shell script will be uploaded to.
	// This should be set to a writable file that is in a pre-existing directory.
	RemotePath string `mapstructure:"remote_path"`

	// The command used to execute the script. The '{{ .Path }}' variable
	// should be used to specify where the script goes, {{ .Vars }}
	// can be used to inject the environment_vars into the environment.
	ExecuteCommand string `mapstructure:"execute_command"`

	// The timeout for retrying to start the process. Until this timeout
	// is reached, if the provisioner can't start a process, it retries.
	// This can be set high to allow for reboots.
	RawStartRetryTimeout string `mapstructure:"start_retry_timeout"`

	startRetryTimeout time.Duration
	ctx               interpolate.Context
}

type Provisioner struct {
	config Config
}

type ExecuteCommandTemplate struct {
	Vars string
	Path string
}

func (p *Provisioner) Prepare(raws ...interface{}) error {
	err := config.Decode(&p.config, &config.DecodeOpts{
		Interpolate: true,
		InterpolateFilter: &interpolate.RenderFilter{
			Exclude: []string{
				"execute_command",
			},
		},
	}, raws...)
	if err != nil {
		return err
	}

	if p.config.ExecuteCommand == "" {
		p.config.ExecuteCommand = "chmod +x {{.Path}}; {{.Vars}} {{.Path}}"
	}

	if p.config.Inline != nil && len(p.config.Inline) == 0 {
		p.config.Inline = nil
	}

	if p.config.InlineShebang == "" {
		p.config.InlineShebang = "/bin/sh -e"
	}

	if p.config.RawStartRetryTimeout == "" {
		p.config.RawStartRetryTimeout = "5m"
	}

	if p.config.RemotePath == "" {
		p.config.RemotePath = fmt.Sprintf(
			"/tmp/script_%d.sh", rand.Intn(9999))
	}

	if p.config.Scripts == nil {
		p.config.Scripts = make([]string, 0)
	}

	if p.config.Vars == nil {
		p.config.Vars = make([]string, 0)
	}

	var errs *packer.MultiError
	if p.config.Script != "" && len(p.config.Scripts) > 0 {
		errs = packer.MultiErrorAppend(errs,
			errors.New("Only one of script or scripts can be specified."))
	}

	if p.config.Script != "" {
		p.config.Scripts = []string{p.config.Script}
	}

	if len(p.config.Scripts) == 0 && p.config.Inline == nil {
		errs = packer.MultiErrorAppend(errs,
			errors.New("Either a script file or inline script must be specified."))
	} else if len(p.config.Scripts) > 0 && p.config.Inline != nil {
		errs = packer.MultiErrorAppend(errs,
			errors.New("Only a script file or an inline script can be specified, not both."))
	}

	for _, path := range p.config.Scripts {
		if _, err := os.Stat(path); err != nil {
			errs = packer.MultiErrorAppend(errs,
				fmt.Errorf("Bad script '%s': %s", path, err))
		}
	}

	// Do a check for bad environment variables, such as '=foo', 'foobar'
	for idx, kv := range p.config.Vars {
		vs := strings.SplitN(kv, "=", 2)
		if len(vs) != 2 || vs[0] == "" {
			errs = packer.MultiErrorAppend(errs,
				fmt.Errorf("Environment variable not in format 'key=value': %s", kv))
		} else {
			// Replace single quotes so they parse
			vs[1] = strings.Replace(vs[1], "'", `'"'"'`, -1)

			// Single quote env var values
			p.config.Vars[idx] = fmt.Sprintf("%s='%s'", vs[0], vs[1])
		}
	}

	if p.config.RawStartRetryTimeout != "" {
		p.config.startRetryTimeout, err = time.ParseDuration(p.config.RawStartRetryTimeout)
		if err != nil {
			errs = packer.MultiErrorAppend(
				errs, fmt.Errorf("Failed parsing start_retry_timeout: %s", err))
		}
	}

	if errs != nil && len(errs.Errors) > 0 {
		return errs
	}

	return nil
}

func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
	scripts := make([]string, len(p.config.Scripts))
	copy(scripts, p.config.Scripts)

	// If we have an inline script, then turn that into a temporary
	// shell script and use that.
	if p.config.Inline != nil {
		tf, err := ioutil.TempFile("", "packer-shell")
		if err != nil {
			return fmt.Errorf("Error preparing shell script: %s", err)
		}
		defer os.Remove(tf.Name())

		// Set the path to the temporary file
		scripts = append(scripts, tf.Name())

		// Write our contents to it
		writer := bufio.NewWriter(tf)
		writer.WriteString(fmt.Sprintf("#!%s\n", p.config.InlineShebang))
		for _, command := range p.config.Inline {
			if _, err := writer.WriteString(command + "\n"); err != nil {
				return fmt.Errorf("Error preparing shell script: %s", err)
			}
		}

		if err := writer.Flush(); err != nil {
			return fmt.Errorf("Error preparing shell script: %s", err)
		}

		tf.Close()
	}

	// Build our variables up by adding in the build name and builder type
	envVars := make([]string, len(p.config.Vars)+2)
	envVars[0] = fmt.Sprintf("PACKER_BUILD_NAME='%s'", p.config.PackerBuildName)
	envVars[1] = fmt.Sprintf("PACKER_BUILDER_TYPE='%s'", p.config.PackerBuilderType)
	copy(envVars[2:], p.config.Vars)

	for _, path := range scripts {
		ui.Say(fmt.Sprintf("Provisioning with shell script: %s", path))

		log.Printf("Opening %s for reading", path)
		f, err := os.Open(path)
		if err != nil {
			return fmt.Errorf("Error opening shell script: %s", err)
		}
		defer f.Close()

		// Flatten the environment variables
		flattendVars := strings.Join(envVars, " ")

		// Compile the command
		p.config.ctx.Data = &ExecuteCommandTemplate{
			Vars: flattendVars,
			Path: p.config.RemotePath,
		}
		command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
		if err != nil {
			return fmt.Errorf("Error processing command: %s", err)
		}

		// Upload the file and run the command. Do this in the context of
		// a single retryable function so that we don't end up with
		// the case that the upload succeeded, a restart is initiated,
		// and then the command is executed but the file doesn't exist
		// any longer.
		var cmd *packer.RemoteCmd
		err = p.retryable(func() error {
			if _, err := f.Seek(0, 0); err != nil {
				return err
			}

			var r io.Reader = f
			if !p.config.Binary {
				r = &UnixReader{Reader: r}
			}

			if err := comm.Upload(p.config.RemotePath, r, nil); err != nil {
				return fmt.Errorf("Error uploading script: %s", err)
			}

			cmd = &packer.RemoteCmd{
				Command: fmt.Sprintf("chmod 0755 %s", p.config.RemotePath),
			}
			if err := comm.Start(cmd); err != nil {
				return fmt.Errorf(
					"Error chmodding script file to 0755 in remote "+
						"machine: %s", err)
			}
			cmd.Wait()

			cmd = &packer.RemoteCmd{Command: command}
			return cmd.StartWithUi(comm, ui)
		})
		if err != nil {
			return err
		}

		if cmd.ExitStatus != 0 {
			return fmt.Errorf("Script exited with non-zero exit status: %d", cmd.ExitStatus)
		}

		// Delete the temporary file we created
		cmd = &packer.RemoteCmd{
			Command: fmt.Sprintf("rm -f %s", p.config.RemotePath),
		}
		if err := comm.Start(cmd); err != nil {
			return fmt.Errorf(
				"Error removing temporary script at %s: %s",
				p.config.RemotePath, err)
		}
		cmd.Wait()
		if cmd.ExitStatus != 0 {
			return fmt.Errorf(
				"Error removing temporary script at %s!",
				p.config.RemotePath)
		}
	}

	return nil
}

func (p *Provisioner) Cancel() {
	// Just hard quit. It isn't a big deal if what we're doing keeps
	// running on the other side.
	os.Exit(0)
}

// retryable will retry the given function over and over until a
// non-error is returned.
func (p *Provisioner) retryable(f func() error) error {
	startTimeout := time.After(p.config.startRetryTimeout)
	for {
		var err error
		if err = f(); err == nil {
			return nil
		}

		// Create an error and log it
		err = fmt.Errorf("Retryable error: %s", err)
		log.Printf(err.Error())

		// Check if we timed out, otherwise we retry. It is safe to
		// retry since the only error case above is if the command
		// failed to START.
		select {
		case <-startTimeout:
			return err
		default:
			time.Sleep(2 * time.Second)
		}
	}
}
<commit_msg>provisioner/shell: retry file delete [GH-2286]<commit_after>// This package implements a provisioner for Packer that executes
// shell scripts within the remote machine.
package shell

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"strings"
	"time"

	"github.com/mitchellh/packer/common"
	"github.com/mitchellh/packer/helper/config"
	"github.com/mitchellh/packer/packer"
	"github.com/mitchellh/packer/template/interpolate"
)

type Config struct {
	common.PackerConfig `mapstructure:",squash"`

	// If true, the script contains binary and line endings will not be
	// converted from Windows to Unix-style.
	Binary bool

	// An inline script to execute. Multiple strings are all executed
	// in the context of a single shell.
	Inline []string

	// The shebang value used when running inline scripts.
	InlineShebang string `mapstructure:"inline_shebang"`

	// The local path of the shell script to upload and execute.
	Script string

	// An array of multiple scripts to run.
	Scripts []string

	// An array of environment variables that will be injected before
	// your command(s) are executed.
	Vars []string `mapstructure:"environment_vars"`

	// The remote path where the local shell script will be uploaded to.
	// This should be set to a writable file that is in a pre-existing directory.
	RemotePath string `mapstructure:"remote_path"`

	// The command used to execute the script. The '{{ .Path }}' variable
	// should be used to specify where the script goes, {{ .Vars }}
	// can be used to inject the environment_vars into the environment.
	ExecuteCommand string `mapstructure:"execute_command"`

	// The timeout for retrying to start the process. Until this timeout
	// is reached, if the provisioner can't start a process, it retries.
	// This can be set high to allow for reboots.
	RawStartRetryTimeout string `mapstructure:"start_retry_timeout"`

	startRetryTimeout time.Duration
	ctx               interpolate.Context
}

type Provisioner struct {
	config Config
}

type ExecuteCommandTemplate struct {
	Vars string
	Path string
}

func (p *Provisioner) Prepare(raws ...interface{}) error {
	err := config.Decode(&p.config, &config.DecodeOpts{
		Interpolate: true,
		InterpolateFilter: &interpolate.RenderFilter{
			Exclude: []string{
				"execute_command",
			},
		},
	}, raws...)
	if err != nil {
		return err
	}

	if p.config.ExecuteCommand == "" {
		p.config.ExecuteCommand = "chmod +x {{.Path}}; {{.Vars}} {{.Path}}"
	}

	if p.config.Inline != nil && len(p.config.Inline) == 0 {
		p.config.Inline = nil
	}

	if p.config.InlineShebang == "" {
		p.config.InlineShebang = "/bin/sh -e"
	}

	if p.config.RawStartRetryTimeout == "" {
		p.config.RawStartRetryTimeout = "5m"
	}

	if p.config.RemotePath == "" {
		p.config.RemotePath = fmt.Sprintf(
			"/tmp/script_%d.sh", rand.Intn(9999))
	}

	if p.config.Scripts == nil {
		p.config.Scripts = make([]string, 0)
	}

	if p.config.Vars == nil {
		p.config.Vars = make([]string, 0)
	}

	var errs *packer.MultiError
	if p.config.Script != "" && len(p.config.Scripts) > 0 {
		errs = packer.MultiErrorAppend(errs,
			errors.New("Only one of script or scripts can be specified."))
	}

	if p.config.Script != "" {
		p.config.Scripts = []string{p.config.Script}
	}

	if len(p.config.Scripts) == 0 && p.config.Inline == nil {
		errs = packer.MultiErrorAppend(errs,
			errors.New("Either a script file or inline script must be specified."))
	} else if len(p.config.Scripts) > 0 && p.config.Inline != nil {
		errs = packer.MultiErrorAppend(errs,
			errors.New("Only a script file or an inline script can be specified, not both."))
	}

	for _, path := range p.config.Scripts {
		if _, err := os.Stat(path); err != nil {
			errs = packer.MultiErrorAppend(errs,
				fmt.Errorf("Bad script '%s': %s", path, err))
		}
	}

	// Do a check for bad environment variables, such as '=foo', 'foobar'
	for idx, kv := range p.config.Vars {
		vs := strings.SplitN(kv, "=", 2)
		if len(vs) != 2 || vs[0] == "" {
			errs = packer.MultiErrorAppend(errs,
				fmt.Errorf("Environment variable not in format 'key=value': %s", kv))
		} else {
			// Replace single quotes so they parse
			vs[1] = strings.Replace(vs[1], "'", `'"'"'`, -1)

			// Single quote env var values
			p.config.Vars[idx] = fmt.Sprintf("%s='%s'", vs[0], vs[1])
		}
	}

	if p.config.RawStartRetryTimeout != "" {
		p.config.startRetryTimeout, err = time.ParseDuration(p.config.RawStartRetryTimeout)
		if err != nil {
			errs = packer.MultiErrorAppend(
				errs, fmt.Errorf("Failed parsing start_retry_timeout: %s", err))
		}
	}

	if errs != nil && len(errs.Errors) > 0 {
		return errs
	}

	return nil
}

func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
	scripts := make([]string, len(p.config.Scripts))
	copy(scripts, p.config.Scripts)

	// If we have an inline script, then turn that into a temporary
	// shell script and use that.
	if p.config.Inline != nil {
		tf, err := ioutil.TempFile("", "packer-shell")
		if err != nil {
			return fmt.Errorf("Error preparing shell script: %s", err)
		}
		defer os.Remove(tf.Name())

		// Set the path to the temporary file
		scripts = append(scripts, tf.Name())

		// Write our contents to it
		writer := bufio.NewWriter(tf)
		writer.WriteString(fmt.Sprintf("#!%s\n", p.config.InlineShebang))
		for _, command := range p.config.Inline {
			if _, err := writer.WriteString(command + "\n"); err != nil {
				return fmt.Errorf("Error preparing shell script: %s", err)
			}
		}

		if err := writer.Flush(); err != nil {
			return fmt.Errorf("Error preparing shell script: %s", err)
		}

		tf.Close()
	}

	// Build our variables up by adding in the build name and builder type
	envVars := make([]string, len(p.config.Vars)+2)
	envVars[0] = fmt.Sprintf("PACKER_BUILD_NAME='%s'", p.config.PackerBuildName)
	envVars[1] = fmt.Sprintf("PACKER_BUILDER_TYPE='%s'", p.config.PackerBuilderType)
	copy(envVars[2:], p.config.Vars)

	for _, path := range scripts {
		ui.Say(fmt.Sprintf("Provisioning with shell script: %s", path))

		log.Printf("Opening %s for reading", path)
		f, err := os.Open(path)
		if err != nil {
			return fmt.Errorf("Error opening shell script: %s", err)
		}
		defer f.Close()

		// Flatten the environment variables
		flattendVars := strings.Join(envVars, " ")

		// Compile the command
		p.config.ctx.Data = &ExecuteCommandTemplate{
			Vars: flattendVars,
			Path: p.config.RemotePath,
		}
		command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
		if err != nil {
			return fmt.Errorf("Error processing command: %s", err)
		}

		// Upload the file and run the command. Do this in the context of
		// a single retryable function so that we don't end up with
		// the case that the upload succeeded, a restart is initiated,
		// and then the command is executed but the file doesn't exist
		// any longer.
		var cmd *packer.RemoteCmd
		err = p.retryable(func() error {
			if _, err := f.Seek(0, 0); err != nil {
				return err
			}

			var r io.Reader = f
			if !p.config.Binary {
				r = &UnixReader{Reader: r}
			}

			if err := comm.Upload(p.config.RemotePath, r, nil); err != nil {
				return fmt.Errorf("Error uploading script: %s", err)
			}

			cmd = &packer.RemoteCmd{
				Command: fmt.Sprintf("chmod 0755 %s", p.config.RemotePath),
			}
			if err := comm.Start(cmd); err != nil {
				return fmt.Errorf(
					"Error chmodding script file to 0755 in remote "+
						"machine: %s", err)
			}
			cmd.Wait()

			cmd = &packer.RemoteCmd{Command: command}
			return cmd.StartWithUi(comm, ui)
		})
		if err != nil {
			return err
		}

		if cmd.ExitStatus != 0 {
			return fmt.Errorf("Script exited with non-zero exit status: %d", cmd.ExitStatus)
		}

		// Delete the temporary file we created. We retry this a few times
		// since if the above rebooted we have to wait until the reboot
		// completes.
		err = p.retryable(func() error {
			cmd = &packer.RemoteCmd{
				Command: fmt.Sprintf("rm -f %s", p.config.RemotePath),
			}
			if err := comm.Start(cmd); err != nil {
				return fmt.Errorf(
					"Error removing temporary script at %s: %s",
					p.config.RemotePath, err)
			}
			cmd.Wait()
			return nil
		})
		if err != nil {
			return err
		}

		if cmd.ExitStatus != 0 {
			return fmt.Errorf(
				"Error removing temporary script at %s!",
				p.config.RemotePath)
		}
	}

	return nil
}

func (p *Provisioner) Cancel() {
	// Just hard quit. It isn't a big deal if what we're doing keeps
	// running on the other side.
	os.Exit(0)
}

// retryable will retry the given function over and over until a
// non-error is returned.
func (p *Provisioner) retryable(f func() error) error {
	startTimeout := time.After(p.config.startRetryTimeout)
	for {
		var err error
		if err = f(); err == nil {
			return nil
		}

		// Create an error and log it
		err = fmt.Errorf("Retryable error: %s", err)
		log.Printf(err.Error())

		// Check if we timed out, otherwise we retry. It is safe to
		// retry since the only error case above is if the command
		// failed to START.
		select {
		case <-startTimeout:
			return err
		default:
			time.Sleep(2 * time.Second)
		}
	}
}
<|endoftext|>


<commit_before>package cli

import (
	"log"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/99designs/aws-vault/prompt"
	"github.com/99designs/aws-vault/server"
	"github.com/99designs/aws-vault/vault"
	"github.com/99designs/keyring"
	"gopkg.in/alecthomas/kingpin.v2"
)

type ExecCommandInput struct {
	Profile      string
	Command      string
	Args         []string
	Keyring      keyring.Keyring
	Duration     time.Duration
	RoleDuration time.Duration
	MfaToken     string
	MfaPrompt    prompt.PromptFunc
	StartServer  bool
	Signals      chan os.Signal
	NoSession    bool
}

func ConfigureExecCommand(app *kingpin.Application) {
	input := ExecCommandInput{}

	cmd := app.Command("exec", "Executes a command with AWS credentials in the environment")
	cmd.Flag("no-session", "Use root credentials, no session created").
		Short('n').
		BoolVar(&input.NoSession)

	cmd.Flag("session-ttl", "Expiration time for aws session").
		Default("4h").
		OverrideDefaultFromEnvar("AWS_SESSION_TTL").
		Short('t').
		DurationVar(&input.Duration)

	cmd.Flag("assume-role-ttl", "Expiration time for aws assumed role").
		Default("15m").
		OverrideDefaultFromEnvar("AWS_ASSUME_ROLE_TTL").
		DurationVar(&input.RoleDuration)

	cmd.Flag("mfa-token", "The mfa token to use").
		Short('m').
		StringVar(&input.MfaToken)

	cmd.Flag("server", "Run the server in the background for credentials").
		Short('s').
		BoolVar(&input.StartServer)

	cmd.Arg("profile", "Name of the profile").
		Required().
		HintAction(ProfileNames).
		StringVar(&input.Profile)

	cmd.Arg("cmd", "Command to execute").
		Default(os.Getenv("SHELL")).
		StringVar(&input.Command)

	cmd.Arg("args", "Command arguments").
		StringsVar(&input.Args)

	cmd.Action(func(c *kingpin.ParseContext) error {
		input.Keyring = keyringImpl
		input.MfaPrompt = prompt.Method(GlobalFlags.PromptDriver)
		input.Signals = make(chan os.Signal)
		signal.Notify(input.Signals, os.Interrupt, os.Kill)
		ExecCommand(app, input)
		return nil
	})
}

func ExecCommand(app *kingpin.Application, input ExecCommandInput) {
	if os.Getenv("AWS_VAULT") != "" {
		app.Fatalf("aws-vault sessions should be nested with care, unset $AWS_VAULT to force")
		return
	}

	var setEnv = true

	if input.NoSession && input.StartServer {
		app.Fatalf("Can't start a credential server without a session")
		return
	}

	creds, err := vault.NewVaultCredentials(input.Keyring, input.Profile, vault.VaultOptions{
		SessionDuration:    input.Duration,
		AssumeRoleDuration: input.RoleDuration,
		MfaToken:           input.MfaToken,
		MfaPrompt:          input.MfaPrompt,
		NoSession:          input.NoSession,
		Config:             awsConfig,
	})
	if err != nil {
		app.Fatalf("%v", err)
	}

	val, err := creds.Get()
	if err != nil {
		app.Fatalf(awsConfig.FormatCredentialError(err, input.Profile))
	}

	if input.StartServer {
		if err := server.StartCredentialsServer(creds); err != nil {
			app.Fatalf("Failed to start credential server: %v", err)
		} else {
			setEnv = false
		}
	}

	env := environ(os.Environ())
	env.Set("AWS_VAULT", input.Profile)

	env.Unset("AWS_ACCESS_KEY_ID")
	env.Unset("AWS_SECRET_ACCESS_KEY")
	env.Unset("AWS_CREDENTIAL_FILE")
	env.Unset("AWS_DEFAULT_PROFILE")
	env.Unset("AWS_PROFILE")

	if profile, _ := awsConfig.Profile(input.Profile); profile.Region != "" {
		log.Printf("Setting subprocess env: AWS_DEFAULT_REGION=%s, AWS_REGION=%s", profile.Region, profile.Region)
		env.Set("AWS_DEFAULT_REGION", profile.Region)
		env.Set("AWS_REGION", profile.Region)
	}

	if setEnv {
		log.Println("Setting subprocess env: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY")
		env.Set("AWS_ACCESS_KEY_ID", val.AccessKeyID)
		env.Set("AWS_SECRET_ACCESS_KEY", val.SecretAccessKey)

		if val.SessionToken != "" {
			log.Println("Setting subprocess env: AWS_SESSION_TOKEN, AWS_SECURITY_TOKEN")
			env.Set("AWS_SESSION_TOKEN", val.SessionToken)
			env.Set("AWS_SECURITY_TOKEN", val.SessionToken)
		}
	}

	cmd := exec.Command(input.Command, input.Args...)
	cmd.Env = env
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Start(); err != nil {
		app.Fatalf("%v", err)
	}
	// wait for the command to finish
	waitCh := make(chan error, 1)
	go func() {
		waitCh <- cmd.Wait()
		close(waitCh)
	}()

	for {
		select {
		case sig := <-input.Signals:
			if err = cmd.Process.Signal(sig); err != nil {
				app.Errorf("%v", err)
				break
			}
		case err := <-waitCh:
			var waitStatus syscall.WaitStatus
			if exitError, ok := err.(*exec.ExitError); ok {
				waitStatus = exitError.Sys().(syscall.WaitStatus)
				os.Exit(waitStatus.ExitStatus())
			}
			if err != nil {
				app.Fatalf("%v", err)
			}
			return
		}
	}
}

// environ is a slice of strings representing the environment, in the form "key=value".
type environ []string

// Unset an environment variable by key
func (e *environ) Unset(key string) {
	for i := range *e {
		if strings.HasPrefix((*e)[i], key+"=") {
			(*e)[i] = (*e)[len(*e)-1]
			*e = (*e)[:len(*e)-1]
			break
		}
	}
}

// Set adds an environment variable, replacing any existing ones of the same key
func (e *environ) Set(key, val string) {
	e.Unset(key)
	*e = append(*e, key+"="+val)
}

// ProfileNames returns a slice of profile names from the AWS config
func ProfileNames() []string {
	var profileNames []string
	for _, profile := range awsConfig.Profiles() {
		profileNames = append(profileNames, profile.Name)
	}
	return profileNames
}
<commit_msg>Move the signal setup down to ExecCommand()<commit_after>package cli

import (
	"log"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/99designs/aws-vault/prompt"
	"github.com/99designs/aws-vault/server"
	"github.com/99designs/aws-vault/vault"
	"github.com/99designs/keyring"
	"gopkg.in/alecthomas/kingpin.v2"
)

type ExecCommandInput struct {
	Profile      string
	Command      string
	Args         []string
	Keyring      keyring.Keyring
	Duration     time.Duration
	RoleDuration time.Duration
	MfaToken     string
	MfaPrompt    prompt.PromptFunc
	StartServer  bool
	Signals      chan os.Signal
	NoSession    bool
}

func ConfigureExecCommand(app *kingpin.Application) {
	input := ExecCommandInput{}

	cmd := app.Command("exec", "Executes a command with AWS credentials in the environment")
	cmd.Flag("no-session", "Use root credentials, no session created").
		Short('n').
		BoolVar(&input.NoSession)

	cmd.Flag("session-ttl", "Expiration time for aws session").
		Default("4h").
		OverrideDefaultFromEnvar("AWS_SESSION_TTL").
		Short('t').
		DurationVar(&input.Duration)

	cmd.Flag("assume-role-ttl", "Expiration time for aws assumed role").
		Default("15m").
		OverrideDefaultFromEnvar("AWS_ASSUME_ROLE_TTL").
		DurationVar(&input.RoleDuration)

	cmd.Flag("mfa-token", "The mfa token to use").
		Short('m').
		StringVar(&input.MfaToken)

	cmd.Flag("server", "Run the server in the background for credentials").
		Short('s').
		BoolVar(&input.StartServer)

	cmd.Arg("profile", "Name of the profile").
		Required().
		HintAction(ProfileNames).
		StringVar(&input.Profile)

	cmd.Arg("cmd", "Command to execute").
		Default(os.Getenv("SHELL")).
		StringVar(&input.Command)

	cmd.Arg("args", "Command arguments").
		StringsVar(&input.Args)

	cmd.Action(func(c *kingpin.ParseContext) error {
		input.Keyring = keyringImpl
		input.MfaPrompt = prompt.Method(GlobalFlags.PromptDriver)
		input.Signals = make(chan os.Signal)
		ExecCommand(app, input)
		return nil
	})
}

func ExecCommand(app *kingpin.Application, input ExecCommandInput) {
	if os.Getenv("AWS_VAULT") != "" {
		app.Fatalf("aws-vault sessions should be nested with care, unset $AWS_VAULT to force")
		return
	}

	var setEnv = true

	if input.NoSession && input.StartServer {
		app.Fatalf("Can't start a credential server without a session")
		return
	}

	creds, err := vault.NewVaultCredentials(input.Keyring, input.Profile, vault.VaultOptions{
		SessionDuration:    input.Duration,
		AssumeRoleDuration: input.RoleDuration,
		MfaToken:           input.MfaToken,
		MfaPrompt:          input.MfaPrompt,
		NoSession:          input.NoSession,
		Config:             awsConfig,
	})
	if err != nil {
		app.Fatalf("%v", err)
	}

	val, err := creds.Get()
	if err != nil {
		app.Fatalf(awsConfig.FormatCredentialError(err, input.Profile))
	}

	if input.StartServer {
		if err := server.StartCredentialsServer(creds); err != nil {
			app.Fatalf("Failed to start credential server: %v", err)
		} else {
			setEnv = false
		}
	}

	env := environ(os.Environ())
	env.Set("AWS_VAULT", input.Profile)

	env.Unset("AWS_ACCESS_KEY_ID")
	env.Unset("AWS_SECRET_ACCESS_KEY")
	env.Unset("AWS_CREDENTIAL_FILE")
	env.Unset("AWS_DEFAULT_PROFILE")
	env.Unset("AWS_PROFILE")

	if profile, _ := awsConfig.Profile(input.Profile); profile.Region != "" {
		log.Printf("Setting subprocess env: AWS_DEFAULT_REGION=%s, AWS_REGION=%s", profile.Region, profile.Region)
		env.Set("AWS_DEFAULT_REGION", profile.Region)
		env.Set("AWS_REGION", profile.Region)
	}

	if setEnv {
		log.Println("Setting subprocess env: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY")
		env.Set("AWS_ACCESS_KEY_ID", val.AccessKeyID)
		env.Set("AWS_SECRET_ACCESS_KEY", val.SecretAccessKey)

		if val.SessionToken != "" {
			log.Println("Setting subprocess env: AWS_SESSION_TOKEN, AWS_SECURITY_TOKEN")
			env.Set("AWS_SESSION_TOKEN", val.SessionToken)
			env.Set("AWS_SECURITY_TOKEN", val.SessionToken)
		}
	}

	cmd := exec.Command(input.Command, input.Args...)
	cmd.Env = env
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	signal.Notify(input.Signals, os.Interrupt, os.Kill)

	if err := cmd.Start(); err != nil {
		app.Fatalf("%v", err)
	}
	// wait for the command to finish
	waitCh := make(chan error, 1)
	go func() {
		waitCh <- cmd.Wait()
		close(waitCh)
	}()

	for {
		select {
		case sig := <-input.Signals:
			if err = cmd.Process.Signal(sig); err != nil {
				app.Errorf("%v", err)
				break
			}
		case err := <-waitCh:
			var waitStatus syscall.WaitStatus
			if exitError, ok := err.(*exec.ExitError); ok {
				waitStatus = exitError.Sys().(syscall.WaitStatus)
				os.Exit(waitStatus.ExitStatus())
			}
			if err != nil {
				app.Fatalf("%v", err)
			}
			return
		}
	}
}

// environ is a slice of strings representing the environment, in the form "key=value".
type environ []string

// Unset an environment variable by key
func (e *environ) Unset(key string) {
	for i := range *e {
		if strings.HasPrefix((*e)[i], key+"=") {
			(*e)[i] = (*e)[len(*e)-1]
			*e = (*e)[:len(*e)-1]
			break
		}
	}
}

// Set adds an environment variable, replacing any existing ones of the same key
func (e *environ) Set(key, val string) {
	e.Unset(key)
	*e = append(*e, key+"="+val)
}

// ProfileNames returns a slice of profile names from the AWS config
func ProfileNames() []string {
	var profileNames []string
	for _, profile := range awsConfig.Profiles() {
		profileNames = append(profileNames, profile.Name)
	}
	return profileNames
}
<|endoftext|>


<commit_before>package chain

import (
	"fmt"
	"strconv"
	"github.com/bit4bit/glivo"
//	"os"
)

//Permite Concatenar acciones alrededor del GetDigits
//Esto simula el GetDigits de *plivoframework* :)
//Ej:
//chain := NewChainDigits(call)
//chain.SetTimeout(10)
//chain.Play("mi audio").Play("Then other.wav").Reply() //get response
type ChainDigits struct {
	call *glivo.Call
	timeout uint16
	digitTimeout uint16
	finishOnKey uint8
	numDigits int
	retries uint8
	playBeep bool
	validDigits string
	invalidDigitsSound string

	commands []CommandChainable
	result chan glivo.Event
}

func NewChainDigits(call *glivo.Call) *ChainDigits{
	return &ChainDigits{
		call: call,
		timeout: 5,
		digitTimeout: 2,
		finishOnKey:'#',
		numDigits: 99,
		retries: 1,
		playBeep: false,
		validDigits: "123456789*#",
		invalidDigitsSound: "silence_stream://250",
		commands: make([]CommandChainable, 50),
		result: make(chan glivo.Event),

	}
}

type ChainableDigits interface {
	Play(files string) ChainDigits
	Speak(phrase string) ChainDigits
	Wait(seconds int) ChainDigits
}

func (digits *ChainDigits) SetTimeout(t uint16){
	digits.timeout = t
}

func (digits *ChainDigits) SetDigitTimeout(t uint16){
	digits.digitTimeout = t
}

func (digits *ChainDigits) SetFinishOnKey(k uint8){
	digits.finishOnKey = k
}

func (digits *ChainDigits) SetNumDigits(n int){
	digits.numDigits = n
}

func (digits *ChainDigits) SetRetries(n uint8){
	digits.retries = n
}

func (digits *ChainDigits) SetPlayBeep(b bool){
	digits.playBeep = b
}

func (digits *ChainDigits) SetInvalidDigitsSound(s string){
	digits.invalidDigitsSound = s
}

func (digits *ChainDigits) SetValidDigits(s string){
	digits.validDigits = s
}

//Reproduce archivo local al servidor Freeswitch
func (digits *ChainDigits) Play(file string) *ChainDigits{
	digits.commands = append(digits.commands, CommandChainable{"play", file})
	return digits
}

func (digits *ChainDigits) Speak(phrase string) *ChainDigits{
	digits.commands = append(digits.commands, CommandChainable{"say", phrase})
	return digits
}

func (digits *ChainDigits) Wait(seconds int) *ChainDigits{
	digits.commands = append(digits.commands, CommandChainable{"wait", strconv.Itoa(seconds)})
	return digits
}

//Espera que se responda con los digitos indicados
//y retorna si o no
func (digits *ChainDigits) Question(question string) (bool, error){
	defer func(){
		digits.commands = nil
	}()

	separator := "!"

	outputs := make([]string, 200)

	for _, command := range digits.commands {
		switch(command.app){
		case "say":
			//@todo permitir cambiar Engine y Voz
			outputs = append(outputs, fmt.Sprintf("say:flite:slt:%s", command.args))
		case "wait":
			value, err := strconv.Atoi(command.args)
			if err != nil {
				value = 1
			}
			outputs = append(outputs, fmt.Sprintf("file_string://silence_stream://%d", value * 1000))
		case "play":
			outputs = append(outputs, command.args)

		}
	}

	sound_file := ""
	for _, output := range outputs {
		if len(output) == 0 {
			continue
		}
		if len(sound_file) == 0 {
			sound_file += output
		}else{
			sound_file += separator + output
		}
	}

	regexp := "^("
	regexp += question
	regexp += ")$"

	cmd := fmt.Sprintf("%d %d %d %d '%c' '%s' %s pagd_input %s %d",
		len(question),
		len(question),
		digits.retries,
		digits.timeout * 1000,
		digits.finishOnKey,
		sound_file,
		digits.invalidDigitsSound,
		regexp,
		digits.digitTimeout * 1000,
	)

	block := make(chan glivo.Event)
	digits.call.RegisterEventHandle("getdigits_app",
		glivo.NewWaitEventHandle(block, map[string]string{
			"Variable_read_result" : "success",
			"Application" : "play_and_get_digits",
		}),
	)

	digits.call.SetVar("playback_delimiter", "!")
	digits.call.SetVar("playback_terminators", "none")
	digits.call.Execute("play_and_get_digits", cmd, true)
	digits.call.Reply()

	digits.commands = nil

	ev := <-block
	digits.call.UnregisterEventHandle("getdigits_app")
	return ev.Content["Variable_pagd_input"] == question, nil
}

//Espera digitos, y colecion al final retonar
//toda la secuencia digitada
func (digits *ChainDigits) CollectInput() (string, error){
	defer func(){
		digits.commands = nil
	}()

	separator := "!"

	outputs := make([]string, 200)

	for _, command := range digits.commands {
		switch(command.app){
		case "say":
			//@todo permitir cambiar el engine y la voz
			outputs = append(outputs, fmt.Sprintf("say:flite:slt:%s", command.args))
		case "wait":
			value, err := strconv.Atoi(command.args)
			if err != nil {
				value = 1
			}
			outputs = append(outputs, fmt.Sprintf("file_string://silence_stream://%d", strconv.Itoa(value * 1000)))
		case "play":
			outputs = append(outputs, command.args)

		}
	}

	sound_file := ""
	for _, output := range outputs {
		if len(output) == 0 {
			continue
		}
		if len(sound_file) == 0 {
			sound_file += output
		}else{
			sound_file += separator + output
		}
	}

	regexp := "^("
	for _, c := range digits.validDigits {
		if len(regexp) == 2 {
			regexp += fmt.Sprintf("%c", c)
		}else{
			regexp += fmt.Sprintf("|%c", c)
		}
	}
	regexp += ")+$"

	cmd := fmt.Sprintf("%d %d %d %d '%c' '%s' %s pagd_input %s %d",
		1,
		digits.numDigits,
		digits.retries,
		digits.timeout * 1000,
		digits.finishOnKey,
		sound_file,
		digits.invalidDigitsSound,
		regexp,
		digits.digitTimeout * 1000,
	)

	block := make(chan string)
	cldtmf := glivo.NewCollectDTMFEventHandle(block, digits.numDigits, digits.validDigits, '#')
	digits.call.RegisterEventHandle("collect_input", cldtmf)

	digits.call.SetVar("playback_delimiter", "!")
	digits.call.SetVar("playback_terminators", "none")
	digits.call.Execute("play_and_get_digits", cmd, true)
	digits.call.Reply()

	digits.commands = nil

	collection := <-block
	digits.call.UnregisterEventHandle("collect_input")
	return collection,nil
}

//Espera digitos, y colecion al final retonar
//toda la secuencia digitada
func (digits *ChainDigits) Do() {
	defer func(){
		digits.commands = nil
	}()

	separator := "!"

	outputs := make([]string, 200)

	for _, command := range digits.commands {
		switch(command.app){
		case "say":
			//@todo permitir cambiar el engine y la voz
			outputs = append(outputs, fmt.Sprintf("say:flite:slt:%s", command.args))
		case "wait":
			value, err := strconv.Atoi(command.args)
			if err != nil {
				value = 1
			}
			outputs = append(outputs, fmt.Sprintf("file_string://silence_stream://%d", strconv.Itoa(value * 1000)))
		case "play":
			outputs = append(outputs, command.args)

		}
	}

	sound_file := ""
	for _, output := range outputs {
		if len(output) == 0 {
			continue
		}
		if len(sound_file) == 0 {
			sound_file += output
		}else{
			sound_file += separator + output
		}
	}
	digits.call.SetVar("playback_delimiter", "!")
	digits.call.SetVar("playback_terminators", "none")
	digits.call.Execute("playback", sound_file, true)
	digits.call.Reply()
}

<commit_msg>corr chan Event -> chan interface{}<commit_after>package chain

import (
	"fmt"
	"strconv"
	"github.com/bit4bit/glivo"
//	"os"
)

//Permite Concatenar acciones alrededor del GetDigits
//Esto simula el GetDigits de *plivoframework* :)
//Ej:
//chain := NewChainDigits(call)
//chain.SetTimeout(10)
//chain.Play("mi audio").Play("Then other.wav").Reply() //get response
type ChainDigits struct {
	call *glivo.Call
	timeout uint16
	digitTimeout uint16
	finishOnKey uint8
	numDigits int
	retries uint8
	playBeep bool
	validDigits string
	invalidDigitsSound string

	commands []CommandChainable
	result chan glivo.Event
}

func NewChainDigits(call *glivo.Call) *ChainDigits{
	return &ChainDigits{
		call: call,
		timeout: 5,
		digitTimeout: 2,
		finishOnKey:'#',
		numDigits: 99,
		retries: 1,
		playBeep: false,
		validDigits: "123456789*#",
		invalidDigitsSound: "silence_stream://250",
		commands: make([]CommandChainable, 50),
		result: make(chan glivo.Event),

	}
}

type ChainableDigits interface {
	Play(files string) ChainDigits
	Speak(phrase string) ChainDigits
	Wait(seconds int) ChainDigits
}

func (digits *ChainDigits) SetTimeout(t uint16){
	digits.timeout = t
}

func (digits *ChainDigits) SetDigitTimeout(t uint16){
	digits.digitTimeout = t
}

func (digits *ChainDigits) SetFinishOnKey(k uint8){
	digits.finishOnKey = k
}

func (digits *ChainDigits) SetNumDigits(n int){
	digits.numDigits = n
}

func (digits *ChainDigits) SetRetries(n uint8){
	digits.retries = n
}

func (digits *ChainDigits) SetPlayBeep(b bool){
	digits.playBeep = b
}

func (digits *ChainDigits) SetInvalidDigitsSound(s string){
	digits.invalidDigitsSound = s
}

func (digits *ChainDigits) SetValidDigits(s string){
	digits.validDigits = s
}

//Reproduce archivo local al servidor Freeswitch
func (digits *ChainDigits) Play(file string) *ChainDigits{
	digits.commands = append(digits.commands, CommandChainable{"play", file})
	return digits
}

func (digits *ChainDigits) Speak(phrase string) *ChainDigits{
	digits.commands = append(digits.commands, CommandChainable{"say", phrase})
	return digits
}

func (digits *ChainDigits) Wait(seconds int) *ChainDigits{
	digits.commands = append(digits.commands, CommandChainable{"wait", strconv.Itoa(seconds)})
	return digits
}

//Espera que se responda con los digitos indicados
//y retorna si o no
func (digits *ChainDigits) Question(question string) (bool, error){
	defer func(){
		digits.commands = nil
	}()

	separator := "!"

	outputs := make([]string, 200)

	for _, command := range digits.commands {
		switch(command.app){
		case "say":
			//@todo permitir cambiar Engine y Voz
			outputs = append(outputs, fmt.Sprintf("say:flite:slt:%s", command.args))
		case "wait":
			value, err := strconv.Atoi(command.args)
			if err != nil {
				value = 1
			}
			outputs = append(outputs, fmt.Sprintf("file_string://silence_stream://%d", value * 1000))
		case "play":
			outputs = append(outputs, command.args)

		}
	}

	sound_file := ""
	for _, output := range outputs {
		if len(output) == 0 {
			continue
		}
		if len(sound_file) == 0 {
			sound_file += output
		}else{
			sound_file += separator + output
		}
	}

	regexp := "^("
	regexp += question
	regexp += ")$"

	cmd := fmt.Sprintf("%d %d %d %d '%c' '%s' %s pagd_input %s %d",
		len(question),
		len(question),
		digits.retries,
		digits.timeout * 1000,
		digits.finishOnKey,
		sound_file,
		digits.invalidDigitsSound,
		regexp,
		digits.digitTimeout * 1000,
	)

	block := make(chan interface{})
	digits.call.RegisterEventHandle("getdigits_app",
		glivo.NewWaitEventHandle(block, map[string]string{
			"Variable_read_result" : "success",
			"Application" : "play_and_get_digits",
		}),
	)

	digits.call.SetVar("playback_delimiter", "!")
	digits.call.SetVar("playback_terminators", "none")
	digits.call.Execute("play_and_get_digits", cmd, true)
	digits.call.Reply()

	digits.commands = nil

	ev := (<-block).(glivo.Event)
	digits.call.UnregisterEventHandle("getdigits_app")
	return ev.Content["Variable_pagd_input"] == question, nil
}

//Espera digitos, y colecion al final retonar
//toda la secuencia digitada
func (digits *ChainDigits) CollectInput() (string, error){
	defer func(){
		digits.commands = nil
	}()

	separator := "!"

	outputs := make([]string, 200)

	for _, command := range digits.commands {
		switch(command.app){
		case "say":
			//@todo permitir cambiar el engine y la voz
			outputs = append(outputs, fmt.Sprintf("say:flite:slt:%s", command.args))
		case "wait":
			value, err := strconv.Atoi(command.args)
			if err != nil {
				value = 1
			}
			outputs = append(outputs, fmt.Sprintf("file_string://silence_stream://%d", strconv.Itoa(value * 1000)))
		case "play":
			outputs = append(outputs, command.args)

		}
	}

	sound_file := ""
	for _, output := range outputs {
		if len(output) == 0 {
			continue
		}
		if len(sound_file) == 0 {
			sound_file += output
		}else{
			sound_file += separator + output
		}
	}

	regexp := "^("
	for _, c := range digits.validDigits {
		if len(regexp) == 2 {
			regexp += fmt.Sprintf("%c", c)
		}else{
			regexp += fmt.Sprintf("|%c", c)
		}
	}
	regexp += ")+$"

	cmd := fmt.Sprintf("%d %d %d %d '%c' '%s' %s pagd_input %s %d",
		1,
		digits.numDigits,
		digits.retries,
		digits.timeout * 1000,
		digits.finishOnKey,
		sound_file,
		digits.invalidDigitsSound,
		regexp,
		digits.digitTimeout * 1000,
	)

	block := make(chan string)
	cldtmf := glivo.NewCollectDTMFEventHandle(block, digits.numDigits, digits.validDigits, '#')
	digits.call.RegisterEventHandle("collect_input", cldtmf)

	digits.call.SetVar("playback_delimiter", "!")
	digits.call.SetVar("playback_terminators", "none")
	digits.call.Execute("play_and_get_digits", cmd, true)
	digits.call.Reply()

	digits.commands = nil

	collection := <-block
	digits.call.UnregisterEventHandle("collect_input")
	return collection,nil
}

//Espera digitos, y colecion al final retonar
//toda la secuencia digitada
func (digits *ChainDigits) Do() {
	defer func(){
		digits.commands = nil
	}()

	separator := "!"

	outputs := make([]string, 200)

	for _, command := range digits.commands {
		switch(command.app){
		case "say":
			//@todo permitir cambiar el engine y la voz
			outputs = append(outputs, fmt.Sprintf("say:flite:slt:%s", command.args))
		case "wait":
			value, err := strconv.Atoi(command.args)
			if err != nil {
				value = 1
			}
			outputs = append(outputs, fmt.Sprintf("file_string://silence_stream://%d", strconv.Itoa(value * 1000)))
		case "play":
			outputs = append(outputs, command.args)

		}
	}

	sound_file := ""
	for _, output := range outputs {
		if len(output) == 0 {
			continue
		}
		if len(sound_file) == 0 {
			sound_file += output
		}else{
			sound_file += separator + output
		}
	}
	digits.call.SetVar("playback_delimiter", "!")
	digits.call.SetVar("playback_terminators", "none")
	digits.call.Execute("playback", sound_file, true)
	digits.call.Reply()
}

<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"github.com/janosgyerik/portping"
)

// TODO
// flags: --tcp, --udp; default is tcp
// flag: -W timeout
// flag: -v verbose; default=false
// drop default count, print forever, until cancel with Control-C, and print stats

const defaultCount = 5

func exit() {
	flag.Usage()
	os.Exit(1)
}

type Params struct {
	host  string
	port  string
	count int
}

func parseArgs() Params {
	flag.Usage = func() {
		fmt.Printf("Usage: %s [options] host port\n\n", os.Args[0])
		flag.PrintDefaults()
	}

	countPtr := flag.Int("c", defaultCount, "stop after count connections")
	flag.Parse()

	if len(flag.Args()) < 2 {
		exit()
	}

	host := flag.Args()[0]
	port := flag.Args()[1]

	return Params{
		host: host,
		port: port,
		count: *countPtr,
	}
}

func main() {
	params := parseArgs()

	host := params.host
	port := params.port
	count := params.count

	addr := fmt.Sprintf("%s:%d", host, port)
	fmt.Printf("Starting to ping %s ...\n", addr)

	c := make(chan error)
	go portping.PingN(host, port, count, c)

	allSuccessful := true

	for i := 0; i < count; i++ {
		// TODO add time
		err := <-c
		if err != nil {
			allSuccessful = false
		}
		fmt.Printf("%s [%d] -> %s\n", addr, i + 1, portping.FormatResult(err))
	}

	// TODO print summary
	// --- host:port ping statistics ---
	// n connections attempted, m successful, x% failed
	// round-trip min/avg/max/stddev = a/b/c/d ms

	if !allSuccessful {
		os.Exit(1)
	}
}
<commit_msg>fixed addr formatting in cli<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"github.com/janosgyerik/portping"
	"net"
)

// TODO
// flags: --tcp, --udp; default is tcp
// flag: -W timeout
// flag: -v verbose; default=false
// drop default count, print forever, until cancel with Control-C, and print stats

const defaultCount = 5

func exit() {
	flag.Usage()
	os.Exit(1)
}

type Params struct {
	host  string
	port  string
	count int
}

func parseArgs() Params {
	flag.Usage = func() {
		fmt.Printf("Usage: %s [options] host port\n\n", os.Args[0])
		flag.PrintDefaults()
	}

	countPtr := flag.Int("c", defaultCount, "stop after count connections")
	flag.Parse()

	if len(flag.Args()) < 2 {
		exit()
	}

	host := flag.Args()[0]
	port := flag.Args()[1]

	return Params{
		host: host,
		port: port,
		count: *countPtr,
	}
}

func main() {
	params := parseArgs()

	host := params.host
	port := params.port
	count := params.count

	addr := net.JoinHostPort(host, port)
	fmt.Printf("Starting to ping %s ...\n", addr)

	c := make(chan error)
	go portping.PingN(host, port, count, c)

	allSuccessful := true

	for i := 0; i < count; i++ {
		// TODO add time
		err := <-c
		if err != nil {
			allSuccessful = false
		}
		fmt.Printf("%s [%d] -> %s\n", addr, i + 1, portping.FormatResult(err))
	}

	// TODO print summary
	// --- host:port ping statistics ---
	// n connections attempted, m successful, x% failed
	// round-trip min/avg/max/stddev = a/b/c/d ms

	if !allSuccessful {
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package cmd

// filesCmdGroup represents the instances command
import (
	"encoding/json"
	"errors"
	"fmt"

	"github.com/cozy/cozy-stack/client"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/spf13/cobra"
)

var errAppsMissingDomain = errors.New("Missing --domain flag")

var flagAppsDomain string
var flagAllDomains bool
var flagAppsDeactivated bool

var webappsCmdGroup = &cobra.Command{
	Use:   "apps [command]",
	Short: "Interact with the cozy applications",
	Long: `
cozy-stack apps allows to interact with the cozy applications.

It provides commands to install or update applications on
a cozy.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return cmd.Help()
	},
}

var installWebappCmd = &cobra.Command{
	Use: "install [slug] [sourceurl]",
	Short: `Install an application with the specified slug name
from the given source URL.`,
	Example: "$ cozy-stack apps install --domain cozy.tools:8080 drive 'git://github.com/cozy/cozy-drive.git#build'",
	RunE: func(cmd *cobra.Command, args []string) error {
		return installApp(cmd, args, consts.Apps)
	},
}

var updateWebappCmd = &cobra.Command{
	Use:     "update [slug] [sourceurl]",
	Short:   "Update the application with the specified slug name.",
	Aliases: []string{"upgrade"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return updateApp(cmd, args, consts.Apps)
	},
}

var uninstallWebappCmd = &cobra.Command{
	Use:     "uninstall [slug]",
	Short:   "Uninstall the application with the specified slug name.",
	Aliases: []string{"rm"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return uninstallApp(cmd, args, consts.Apps)
	},
}

var lsWebappsCmd = &cobra.Command{
	Use:   "ls",
	Short: "List the installed applications.",
	RunE: func(cmd *cobra.Command, args []string) error {
		return lsApps(cmd, args, consts.Apps)
	},
}

var konnectorsCmdGroup = &cobra.Command{
	Use:   "konnectors [command]",
	Short: "Interact with the cozy applications",
	Long: `
cozy-stack konnectors allows to interact with the cozy konnectors.

It provides commands to install or update applications on
a cozy.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return cmd.Help()
	},
}

var installKonnectorCmd = &cobra.Command{
	Use: "install [slug] [sourceurl]",
	Short: `Install an konnector with the specified slug name
from the given source URL.`,
	Example: "$ cozy-stack konnectors install --domain cozy.tools:8080 trainline 'git://github.com/cozy/cozy-konnector-trainline.git#build'",
	RunE: func(cmd *cobra.Command, args []string) error {
		return installApp(cmd, args, consts.Konnectors)
	},
}

var updateKonnectorCmd = &cobra.Command{
	Use:     "update [slug] [sourceurl]",
	Short:   "Update the konnector with the specified slug name.",
	Aliases: []string{"upgrade"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return updateApp(cmd, args, consts.Konnectors)
	},
}

var uninstallKonnectorCmd = &cobra.Command{
	Use:     "uninstall [slug]",
	Short:   "Uninstall the konnector with the specified slug name.",
	Aliases: []string{"rm"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return uninstallApp(cmd, args, consts.Konnectors)
	},
}

var lsKonnectorsCmd = &cobra.Command{
	Use:   "ls",
	Short: "List the installed konnectors.",
	RunE: func(cmd *cobra.Command, args []string) error {
		return lsApps(cmd, args, consts.Konnectors)
	},
}

func installApp(cmd *cobra.Command, args []string, appType string) error {
	if len(args) < 1 {
		return cmd.Help()
	}
	slug := args[0]
	var source string
	if len(args) == 1 {
		s, ok := consts.AppsRegistry[slug]
		if !ok {
			return cmd.Help()
		}
		source = s
	} else {
		source = args[1]
	}
	if flagAllDomains {
		return foreachDomains(func(in *client.Instance) error {
			c := newClient(in.Attrs.Domain, appType)
			_, err := c.InstallApp(&client.AppOptions{
				AppType:     appType,
				Slug:        slug,
				SourceURL:   source,
				Deactivated: flagAppsDeactivated,
			})
			if err != nil {
				if err.Error() == "Application with same slug already exists" {
					return nil
				}
				return err
			}
			fmt.Printf("Application installed successfully on %s\n", in.Attrs.Domain)
			return nil
		})
	}
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	app, err := c.InstallApp(&client.AppOptions{
		AppType:     appType,
		Slug:        slug,
		SourceURL:   source,
		Deactivated: flagAppsDeactivated,
	})
	if err != nil {
		return err
	}
	json, err := json.MarshalIndent(app.Attrs, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(json))
	return nil
}

func updateApp(cmd *cobra.Command, args []string, appType string) error {
	if len(args) == 0 || len(args) > 2 {
		return cmd.Help()
	}
	var src string
	if len(args) > 1 {
		src = args[1]
	}
	if flagAllDomains {
		return foreachDomains(func(in *client.Instance) error {
			c := newClient(in.Attrs.Domain, appType)
			_, err := c.UpdateApp(&client.AppOptions{
				AppType:   appType,
				Slug:      args[0],
				SourceURL: src,
			})
			if err != nil {
				if err.Error() == "Application is not installed" {
					return nil
				}
				return err
			}
			fmt.Printf("Application updated successfully on %s\n", in.Attrs.Domain)
			return nil
		})
	}
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	app, err := c.UpdateApp(&client.AppOptions{
		AppType:   appType,
		Slug:      args[0],
		SourceURL: src,
	})
	if err != nil {
		return err
	}
	json, err := json.MarshalIndent(app.Attrs, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(json))
	return nil
}

func uninstallApp(cmd *cobra.Command, args []string, appType string) error {
	if len(args) != 1 {
		return cmd.Help()
	}
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	app, err := c.UninstallApp(&client.AppOptions{
		AppType: appType,
		Slug:    args[0],
	})
	if err != nil {
		return err
	}
	json, err := json.MarshalIndent(app.Attrs, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(json))
	return nil
}

func lsApps(cmd *cobra.Command, args []string, appType string) error {
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	// TODO(pagination)
	apps, err := c.ListApps(appType)
	if err != nil {
		return err
	}
	for _, app := range apps {
		fmt.Printf("%s\t%s\t%s\t%s\n",
			app.Attrs.Slug, app.Attrs.Source, app.Attrs.Version, app.Attrs.State)
	}
	return nil
}

func foreachDomains(predicate func(*client.Instance) error) error {
	c := newAdminClient()
	// TODO(pagination): Make this iteration more robust
	list, err := c.ListInstances()
	if err != nil {
		return nil
	}
	var hasErr bool
	for _, i := range list {
		if err = predicate(i); err != nil {
			errPrintfln("%s: %s", i.Attrs.Domain, err)
			hasErr = true
		}
	}
	if hasErr {
		return errors.New("At least one error occured while executing this command")
	}
	return nil
}

func init() {
	webappsCmdGroup.PersistentFlags().StringVar(&flagAppsDomain, "domain", "", "specify the domain name of the instance")
	webappsCmdGroup.PersistentFlags().BoolVar(&flagAllDomains, "all-domains", false, "work on all domains iterativelly")
	installWebappCmd.PersistentFlags().BoolVar(&flagAppsDeactivated, "ask-permissions", false, "specify that the application should not be activated after installation")

	webappsCmdGroup.AddCommand(lsWebappsCmd)
	webappsCmdGroup.AddCommand(installWebappCmd)
	webappsCmdGroup.AddCommand(updateWebappCmd)
	webappsCmdGroup.AddCommand(uninstallWebappCmd)

	konnectorsCmdGroup.PersistentFlags().StringVar(&flagAppsDomain, "domain", "", "specify the domain name of the instance")
	konnectorsCmdGroup.PersistentFlags().BoolVar(&flagAllDomains, "all-domains", false, "work on all domains iterativelly")

	konnectorsCmdGroup.AddCommand(lsKonnectorsCmd)
	konnectorsCmdGroup.AddCommand(installKonnectorCmd)
	konnectorsCmdGroup.AddCommand(updateKonnectorCmd)
	konnectorsCmdGroup.AddCommand(uninstallKonnectorCmd)

	RootCmd.AddCommand(webappsCmdGroup)
	RootCmd.AddCommand(konnectorsCmdGroup)
}
<commit_msg>Use tabwriter for the cli apps ls<commit_after>package cmd

// filesCmdGroup represents the instances command
import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"text/tabwriter"

	"github.com/cozy/cozy-stack/client"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/spf13/cobra"
)

var errAppsMissingDomain = errors.New("Missing --domain flag")

var flagAppsDomain string
var flagAllDomains bool
var flagAppsDeactivated bool

var webappsCmdGroup = &cobra.Command{
	Use:   "apps [command]",
	Short: "Interact with the cozy applications",
	Long: `
cozy-stack apps allows to interact with the cozy applications.

It provides commands to install or update applications on
a cozy.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return cmd.Help()
	},
}

var installWebappCmd = &cobra.Command{
	Use: "install [slug] [sourceurl]",
	Short: `Install an application with the specified slug name
from the given source URL.`,
	Example: "$ cozy-stack apps install --domain cozy.tools:8080 drive 'git://github.com/cozy/cozy-drive.git#build'",
	RunE: func(cmd *cobra.Command, args []string) error {
		return installApp(cmd, args, consts.Apps)
	},
}

var updateWebappCmd = &cobra.Command{
	Use:     "update [slug] [sourceurl]",
	Short:   "Update the application with the specified slug name.",
	Aliases: []string{"upgrade"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return updateApp(cmd, args, consts.Apps)
	},
}

var uninstallWebappCmd = &cobra.Command{
	Use:     "uninstall [slug]",
	Short:   "Uninstall the application with the specified slug name.",
	Aliases: []string{"rm"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return uninstallApp(cmd, args, consts.Apps)
	},
}

var lsWebappsCmd = &cobra.Command{
	Use:   "ls",
	Short: "List the installed applications.",
	RunE: func(cmd *cobra.Command, args []string) error {
		return lsApps(cmd, args, consts.Apps)
	},
}

var konnectorsCmdGroup = &cobra.Command{
	Use:   "konnectors [command]",
	Short: "Interact with the cozy applications",
	Long: `
cozy-stack konnectors allows to interact with the cozy konnectors.

It provides commands to install or update applications on
a cozy.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return cmd.Help()
	},
}

var installKonnectorCmd = &cobra.Command{
	Use: "install [slug] [sourceurl]",
	Short: `Install an konnector with the specified slug name
from the given source URL.`,
	Example: "$ cozy-stack konnectors install --domain cozy.tools:8080 trainline 'git://github.com/cozy/cozy-konnector-trainline.git#build'",
	RunE: func(cmd *cobra.Command, args []string) error {
		return installApp(cmd, args, consts.Konnectors)
	},
}

var updateKonnectorCmd = &cobra.Command{
	Use:     "update [slug] [sourceurl]",
	Short:   "Update the konnector with the specified slug name.",
	Aliases: []string{"upgrade"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return updateApp(cmd, args, consts.Konnectors)
	},
}

var uninstallKonnectorCmd = &cobra.Command{
	Use:     "uninstall [slug]",
	Short:   "Uninstall the konnector with the specified slug name.",
	Aliases: []string{"rm"},
	RunE: func(cmd *cobra.Command, args []string) error {
		return uninstallApp(cmd, args, consts.Konnectors)
	},
}

var lsKonnectorsCmd = &cobra.Command{
	Use:   "ls",
	Short: "List the installed konnectors.",
	RunE: func(cmd *cobra.Command, args []string) error {
		return lsApps(cmd, args, consts.Konnectors)
	},
}

func installApp(cmd *cobra.Command, args []string, appType string) error {
	if len(args) < 1 {
		return cmd.Help()
	}
	slug := args[0]
	var source string
	if len(args) == 1 {
		s, ok := consts.AppsRegistry[slug]
		if !ok {
			return cmd.Help()
		}
		source = s
	} else {
		source = args[1]
	}
	if flagAllDomains {
		return foreachDomains(func(in *client.Instance) error {
			c := newClient(in.Attrs.Domain, appType)
			_, err := c.InstallApp(&client.AppOptions{
				AppType:     appType,
				Slug:        slug,
				SourceURL:   source,
				Deactivated: flagAppsDeactivated,
			})
			if err != nil {
				if err.Error() == "Application with same slug already exists" {
					return nil
				}
				return err
			}
			fmt.Printf("Application installed successfully on %s\n", in.Attrs.Domain)
			return nil
		})
	}
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	app, err := c.InstallApp(&client.AppOptions{
		AppType:     appType,
		Slug:        slug,
		SourceURL:   source,
		Deactivated: flagAppsDeactivated,
	})
	if err != nil {
		return err
	}
	json, err := json.MarshalIndent(app.Attrs, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(json))
	return nil
}

func updateApp(cmd *cobra.Command, args []string, appType string) error {
	if len(args) == 0 || len(args) > 2 {
		return cmd.Help()
	}
	var src string
	if len(args) > 1 {
		src = args[1]
	}
	if flagAllDomains {
		return foreachDomains(func(in *client.Instance) error {
			c := newClient(in.Attrs.Domain, appType)
			_, err := c.UpdateApp(&client.AppOptions{
				AppType:   appType,
				Slug:      args[0],
				SourceURL: src,
			})
			if err != nil {
				if err.Error() == "Application is not installed" {
					return nil
				}
				return err
			}
			fmt.Printf("Application updated successfully on %s\n", in.Attrs.Domain)
			return nil
		})
	}
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	app, err := c.UpdateApp(&client.AppOptions{
		AppType:   appType,
		Slug:      args[0],
		SourceURL: src,
	})
	if err != nil {
		return err
	}
	json, err := json.MarshalIndent(app.Attrs, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(json))
	return nil
}

func uninstallApp(cmd *cobra.Command, args []string, appType string) error {
	if len(args) != 1 {
		return cmd.Help()
	}
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	app, err := c.UninstallApp(&client.AppOptions{
		AppType: appType,
		Slug:    args[0],
	})
	if err != nil {
		return err
	}
	json, err := json.MarshalIndent(app.Attrs, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(json))
	return nil
}

func lsApps(cmd *cobra.Command, args []string, appType string) error {
	if flagAppsDomain == "" {
		errPrintfln("%s", errAppsMissingDomain)
		return cmd.Help()
	}
	c := newClient(flagAppsDomain, appType)
	// TODO(pagination)
	apps, err := c.ListApps(appType)
	if err != nil {
		return err
	}
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
	for _, app := range apps {
		fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
			app.Attrs.Slug,
			app.Attrs.Source,
			app.Attrs.Version,
			app.Attrs.State,
		)
	}
	return w.Flush()
}

func foreachDomains(predicate func(*client.Instance) error) error {
	c := newAdminClient()
	// TODO(pagination): Make this iteration more robust
	list, err := c.ListInstances()
	if err != nil {
		return nil
	}
	var hasErr bool
	for _, i := range list {
		if err = predicate(i); err != nil {
			errPrintfln("%s: %s", i.Attrs.Domain, err)
			hasErr = true
		}
	}
	if hasErr {
		return errors.New("At least one error occured while executing this command")
	}
	return nil
}

func init() {
	webappsCmdGroup.PersistentFlags().StringVar(&flagAppsDomain, "domain", "", "specify the domain name of the instance")
	webappsCmdGroup.PersistentFlags().BoolVar(&flagAllDomains, "all-domains", false, "work on all domains iterativelly")
	installWebappCmd.PersistentFlags().BoolVar(&flagAppsDeactivated, "ask-permissions", false, "specify that the application should not be activated after installation")

	webappsCmdGroup.AddCommand(lsWebappsCmd)
	webappsCmdGroup.AddCommand(installWebappCmd)
	webappsCmdGroup.AddCommand(updateWebappCmd)
	webappsCmdGroup.AddCommand(uninstallWebappCmd)

	konnectorsCmdGroup.PersistentFlags().StringVar(&flagAppsDomain, "domain", "", "specify the domain name of the instance")
	konnectorsCmdGroup.PersistentFlags().BoolVar(&flagAllDomains, "all-domains", false, "work on all domains iterativelly")

	konnectorsCmdGroup.AddCommand(lsKonnectorsCmd)
	konnectorsCmdGroup.AddCommand(installKonnectorCmd)
	konnectorsCmdGroup.AddCommand(updateKonnectorCmd)
	konnectorsCmdGroup.AddCommand(uninstallKonnectorCmd)

	RootCmd.AddCommand(webappsCmdGroup)
	RootCmd.AddCommand(konnectorsCmdGroup)
}
<|endoftext|>