<commit_before>package core

import (
	"fmt"
	"strconv"
	"sync"

	"github.com/af83/edwig/audit"
	"github.com/af83/edwig/model"
	"github.com/af83/edwig/siri"
)

type StopMonitoringSubscriptionBroadcaster interface {
	model.Stopable
	model.Startable

	HandleStopMonitoringBroadcastEvent(*model.StopMonitoringBroadcastEvent)
	HandleSubscriptionRequest([]*siri.XMLStopMonitoringSubscriptionRequestEntry)
}

type SIRIStopMonitoringSubscriptionBroadcaster struct {
	model.ClockConsumer
	model.UUIDConsumer

	siriConnector

	stopMonitoringBroadcaster SIRIStopMonitoringBroadcaster
	toBroadcast               map[SubscriptionId][]model.StopVisitId
	notMonitored              map[SubscriptionId]map[string]struct{}

	mutex *sync.Mutex //protect the map
}

type SIRIStopMonitoringSubscriptionBroadcasterFactory struct{}

func (factory *SIRIStopMonitoringSubscriptionBroadcasterFactory) CreateConnector(partner *Partner) Connector {
	if _, ok := partner.Connector(SIRI_SUBSCRIPTION_REQUEST_DISPATCHER); !ok {
		partner.CreateSubscriptionRequestDispatcher()
	}
	return newSIRIStopMonitoringSubscriptionBroadcaster(partner)
}

func (factory *SIRIStopMonitoringSubscriptionBroadcasterFactory) Validate(apiPartner *APIPartner) bool {
	ok := apiPartner.ValidatePresenceOfSetting("remote_objectid_kind")
	ok = ok && apiPartner.ValidatePresenceOfSetting("remote_url")
	ok = ok && apiPartner.ValidatePresenceOfSetting("remote_credential")
	ok = ok && apiPartner.ValidatePresenceOfSetting("local_credential")
	return ok
}

func newSIRIStopMonitoringSubscriptionBroadcaster(partner *Partner) *SIRIStopMonitoringSubscriptionBroadcaster {
	siriStopMonitoringSubscriptionBroadcaster := &SIRIStopMonitoringSubscriptionBroadcaster{}
	siriStopMonitoringSubscriptionBroadcaster.partner = partner
	siriStopMonitoringSubscriptionBroadcaster.mutex = &sync.Mutex{}
	siriStopMonitoringSubscriptionBroadcaster.toBroadcast = make(map[SubscriptionId][]model.StopVisitId)
	siriStopMonitoringSubscriptionBroadcaster.notMonitored = make(map[SubscriptionId]map[string]struct{})

	siriStopMonitoringSubscriptionBroadcaster.stopMonitoringBroadcaster = NewSIRIStopMonitoringBroadcaster(siriStopMonitoringSubscriptionBroadcaster)

	return siriStopMonitoringSubscriptionBroadcaster
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) Stop() {
	connector.stopMonitoringBroadcaster.Stop()
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) Start() {
	connector.stopMonitoringBroadcaster.Start()
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) HandleStopMonitoringBroadcastEvent(event *model.StopMonitoringBroadcastEvent) {
	tx := connector.Partner().Referential().NewTransaction()
	defer tx.Close()

	switch event.ModelType {
	case "StopVisit":
		sv, ok := tx.Model().StopVisits().Find(model.StopVisitId(event.ModelId))
		if ok {
			subsIds := connector.checkEvent(sv, tx)
			if len(subsIds) != 0 {
				connector.addStopVisit(subsIds, sv.Id())
			}
		}
	case "VehicleJourney":
		for _, sv := range tx.Model().StopVisits().FindFollowingByVehicleJourneyId(model.VehicleJourneyId(event.ModelId)) {
			subsIds := connector.checkEvent(sv, tx)
			if len(subsIds) != 0 {
				connector.addStopVisit(subsIds, sv.Id())
			}
		}
	case "StopArea":
		sa, ok := tx.Model().StopAreas().Find(model.StopAreaId(event.ModelId))
		if ok {
			connector.checkStopAreaEvent(&sa, tx)
		}
	default:
		return
	}
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) addStopVisit(subsIds []SubscriptionId, svId model.StopVisitId) {
	connector.mutex.Lock()
	for _, subId := range subsIds {
		connector.toBroadcast[SubscriptionId(subId)] = append(connector.toBroadcast[SubscriptionId(subId)], svId)
	}
	connector.mutex.Unlock()
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) checkEvent(sv model.StopVisit, tx *model.Transaction) (subscriptionIds []SubscriptionId) {
	if sv.Origin == string(connector.Partner().Slug()) {
		return
	}

	vj, _ := tx.Model().VehicleJourneys().Find(sv.VehicleJourneyId)

	for _, stopAreaObjectId := range tx.Model().StopAreas().FindAscendantsWithObjectIdKind(sv.StopAreaId, connector.partner.RemoteObjectIDKind(SIRI_STOP_MONITORING_SUBSCRIPTION_BROADCASTER)) {
		subs := connector.partner.Subscriptions().FindByResourceId(stopAreaObjectId.String(), "StopMonitoringBroadcast")

		for _, sub := range subs {
			resource := sub.Resource(stopAreaObjectId)
			if resource == nil || resource.SubscribedUntil.Before(connector.Clock().Now()) {
				continue
			}

			if lineRef := sub.SubscriptionOption("LineRef"); lineRef != "" && lineRef != string(vj.LineId) {
				continue
			}

			lastState, ok := resource.LastState(string(sv.Id()))
			if ok && !lastState.(*stopMonitoringLastChange).Haschanged(sv) {
				continue
			}

			if !ok {
				smlc := &stopMonitoringLastChange{}
				smlc.InitState(&sv, sub)
				resource.SetLastState(string(sv.Id()), smlc)
			}

			subscriptionIds = append(subscriptionIds, sub.Id())
		}
	}

	return
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) checkStopAreaEvent(stopArea *model.StopArea, tx *model.Transaction) {
	obj, ok := stopArea.ObjectID(connector.partner.RemoteObjectIDKind(SIRI_STOP_MONITORING_SUBSCRIPTION_BROADCASTER))
	if !ok {
		return
	}

	connector.mutex.Lock()

	subs := connector.partner.Subscriptions().FindByResourceId(obj.String(), "StopMonitoringBroadcast")
	for _, sub := range subs {
		resource := sub.Resource(obj)
		if resource == nil || resource.SubscribedUntil.Before(connector.Clock().Now()) {
			continue
		}

		lastState, ok := resource.LastState(string(stopArea.Id()))
		if ok {
			partners, ok := lastState.(*stopAreaLastChange).Haschanged(stopArea)
			if ok {
				nm, ok := connector.notMonitored[sub.Id()]
				if !ok {
					nm = make(map[string]struct{})
					connector.notMonitored[sub.Id()] = nm
				}
				for _, partner := range partners {
					nm[partner] = struct{}{}
				}
			}
			lastState.(*stopAreaLastChange).UpdateState(stopArea)
		} else { // Should not happen
			salc := &stopAreaLastChange{}
			salc.InitState(stopArea, sub)
			resource.SetLastState(string(stopArea.Id()), salc)
		}
	}

	connector.mutex.Unlock()
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) HandleSubscriptionRequest(request *siri.XMLSubscriptionRequest) []siri.SIRIResponseStatus {
	tx := connector.Partner().Referential().NewTransaction()
	defer tx.Close()

	resps := []siri.SIRIResponseStatus{}

	for _, sm := range request.XMLSubscriptionSMEntries() {
		logStashEvent := connector.newLogStashEvent()
		logXMLStopMonitoringSubscriptionEntry(logStashEvent, sm)

		rs := siri.SIRIResponseStatus{
			RequestMessageRef: sm.MessageIdentifier(),
			SubscriberRef:     sm.SubscriberRef(),
			SubscriptionRef:   sm.SubscriptionIdentifier(),
			ResponseTimestamp: connector.Clock().Now(),
		}

		objectid := model.NewObjectID(connector.partner.RemoteObjectIDKind(SIRI_STOP_MONITORING_SUBSCRIPTION_BROADCASTER), sm.MonitoringRef())
		sa, ok := tx.Model().StopAreas().FindByObjectId(objectid)
		if !ok {
			rs.ErrorType = "InvalidDataReferencesError"
			rs.ErrorText = fmt.Sprintf("StopArea not found: '%s'", objectid.Value())
			resps = append(resps, rs)

			logSIRIStopMonitoringSubscriptionResponseEntry(logStashEvent, &rs)
			audit.CurrentLogStash().WriteEvent(logStashEvent)

			continue
		}

		lineRef := ""
		if sm.LineRef() != "" {
			line_objectid := model.NewObjectID(connector.partner.RemoteObjectIDKind(SIRI_STOP_MONITORING_SUBSCRIPTION_BROADCASTER), sm.LineRef())
			line, ok := tx.Model().Lines().FindByObjectId(line_objectid)
			if !ok {
				rs.ErrorType = "InvalidDataReferencesError"
				rs.ErrorText = fmt.Sprintf("Line not found: '%s'", line_objectid.Value())
				resps = append(resps, rs)

				logSIRIStopMonitoringSubscriptionResponseEntry(logStashEvent, &rs)
				audit.CurrentLogStash().WriteEvent(logStashEvent)

				continue
			}
			lineRef = string(line.Id())
		}

		sub, ok := connector.Partner().Subscriptions().FindByExternalId(sm.SubscriptionIdentifier())
		if !ok {
			sub = connector.Partner().Subscriptions().New("StopMonitoringBroadcast")
			sub.SetExternalId(sm.SubscriptionIdentifier())
		}

		ref := model.Reference{
			ObjectId: &objectid,
			Type:     "StopArea",
		}

		r := sub.CreateAddNewResource(ref)
		r.SubscribedAt = connector.Clock().Now()
		r.SubscribedUntil = sm.InitialTerminationTime()

		connector.fillOptions(sub, r, request, sm)
		sub.SetSubscriptionOption("LineRef", lineRef)

		rs.Status = true
		rs.ValidUntil = sm.InitialTerminationTime()
		resps = append(resps, rs)

		logSIRIStopMonitoringSubscriptionResponseEntry(logStashEvent, &rs)
		audit.CurrentLogStash().WriteEvent(logStashEvent)

		// Init SA LastChange
		salc := &stopAreaLastChange{}
		salc.InitState(&sa, sub)
		r.SetLastState(string(sa.Id()), salc)
		// Init StopVisits LastChange
		connector.addStopAreaStopVisits(sa, sub, r)
	}

	return resps
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) addStopAreaStopVisits(sa model.StopArea, sub *Subscription, res *SubscribedResource) {
	tx := connector.Partner().Referential().NewTransaction()
	defer tx.Close()

	for _, saId := range tx.Model().StopAreas().FindFamily(sa.Id()) {
		for _, sv := range tx.Model().StopVisits().FindFollowingByStopAreaId(saId) {
			if _, ok := res.LastState(string(sv.Id())); ok {
				continue
			}
			smlc := &stopMonitoringLastChange{}
			smlc.InitState(&sv, sub)
			res.SetLastState(string(sv.Id()), smlc)
			connector.addStopVisit([]SubscriptionId{sub.Id()}, sv.Id())
		}
	}
}

// WIP Need to do something about this method Refs #6338
func (smsb *SIRIStopMonitoringSubscriptionBroadcaster) fillOptions(s *Subscription, r *SubscribedResource, request *siri.XMLSubscriptionRequest, sm *siri.XMLStopMonitoringSubscriptionRequestEntry) {
	changeBeforeUpdates := request.ChangeBeforeUpdates()
	if changeBeforeUpdates == "" {
		changeBeforeUpdates = "PT1M"
	}

	s.SetSubscriptionOption("StopVisitTypes", sm.StopVisitTypes())
	s.SetSubscriptionOption("IncrementalUpdates", request.IncrementalUpdates())
	s.SetSubscriptionOption("MaximumStopVisits", sm.MaximumStopVisits())
	s.SetSubscriptionOption("ChangeBeforeUpdates", changeBeforeUpdates)
	s.SetSubscriptionOption("MessageIdentifier", request.MessageIdentifier())
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) newLogStashEvent() audit.LogStashEvent {
	event := connector.partner.NewLogStashEvent()
	event["connector"] = "StopMonitoringSubscriptionBroadcaster"
	return event
}

func logXMLStopMonitoringSubscriptionEntry(logStashEvent audit.LogStashEvent, request *siri.XMLStopMonitoringSubscriptionRequestEntry) {
	logStashEvent["siriType"] = "StopMonitoringSubscriptionEntry"
	logStashEvent["messageIdentifier"] = request.MessageIdentifier()
	logStashEvent["monitoringRef"] = request.MonitoringRef()
	logStashEvent["stopVisitTypes"] = request.StopVisitTypes()
	logStashEvent["subscriberRef"] = request.SubscriberRef()
	logStashEvent["subscriptionIdentifier"] = request.SubscriptionIdentifier()
	logStashEvent["initialTerminationTime"] = request.InitialTerminationTime().String()
	logStashEvent["requestTimestamp"] = request.RequestTimestamp().String()
	logStashEvent["requestXML"] = request.RawXML()
}

func logSIRIStopMonitoringSubscriptionResponseEntry(logStashEvent audit.LogStashEvent, smEntry *siri.SIRIResponseStatus) {
	logStashEvent["requestMessageRef"] = smEntry.RequestMessageRef
	logStashEvent["subscriptionRef"] = smEntry.SubscriptionRef
	logStashEvent["responseTimestamp"] = smEntry.ResponseTimestamp.String()
	logStashEvent["validUntil"] = smEntry.ValidUntil.String()
	logStashEvent["status"] = strconv.FormatBool(smEntry.Status)
	if !smEntry.Status {
		logStashEvent["errorType"] = smEntry.ErrorType
		if smEntry.ErrorType == "OtherError" {
			logStashEvent["errorNumber"] = strconv.Itoa(smEntry.ErrorNumber)
		}
		logStashEvent["errorText"] = smEntry.ErrorText
	}
}

// START TEST

type TestSIRIStopMonitoringSubscriptionBroadcasterFactory struct{}

type TestStopMonitoringSubscriptionBroadcaster struct {
	model.UUIDConsumer

	events                    []*model.StopMonitoringBroadcastEvent
	stopMonitoringBroadcaster SIRIStopMonitoringBroadcaster
}

func NewTestStopMonitoringSubscriptionBroadcaster() *TestStopMonitoringSubscriptionBroadcaster {
	connector := &TestStopMonitoringSubscriptionBroadcaster{}
	return connector
}

func (connector *TestStopMonitoringSubscriptionBroadcaster) HandleStopMonitoringBroadcastEvent(event *model.StopMonitoringBroadcastEvent) {
	connector.events = append(connector.events, event)
}

func (factory *TestSIRIStopMonitoringSubscriptionBroadcasterFactory) Validate(apiPartner *APIPartner) bool {
	return true
}

func (factory *TestSIRIStopMonitoringSubscriptionBroadcasterFactory) CreateConnector(partner *Partner) Connector {
	return NewTestStopMonitoringSubscriptionBroadcaster()
}

// END TEST
<commit_msg>Handle LineRef filter in SM subscription broadcast in the initial state<commit_after>package core

import (
	"fmt"
	"strconv"
	"sync"

	"github.com/af83/edwig/audit"
	"github.com/af83/edwig/model"
	"github.com/af83/edwig/siri"
)

type StopMonitoringSubscriptionBroadcaster interface {
	model.Stopable
	model.Startable

	HandleStopMonitoringBroadcastEvent(*model.StopMonitoringBroadcastEvent)
	HandleSubscriptionRequest([]*siri.XMLStopMonitoringSubscriptionRequestEntry)
}

type SIRIStopMonitoringSubscriptionBroadcaster struct {
	model.ClockConsumer
	model.UUIDConsumer

	siriConnector

	stopMonitoringBroadcaster SIRIStopMonitoringBroadcaster
	toBroadcast               map[SubscriptionId][]model.StopVisitId
	notMonitored              map[SubscriptionId]map[string]struct{}

	mutex *sync.Mutex //protect the map
}

type SIRIStopMonitoringSubscriptionBroadcasterFactory struct{}

func (factory *SIRIStopMonitoringSubscriptionBroadcasterFactory) CreateConnector(partner *Partner) Connector {
	if _, ok := partner.Connector(SIRI_SUBSCRIPTION_REQUEST_DISPATCHER); !ok {
		partner.CreateSubscriptionRequestDispatcher()
	}
	return newSIRIStopMonitoringSubscriptionBroadcaster(partner)
}

func (factory *SIRIStopMonitoringSubscriptionBroadcasterFactory) Validate(apiPartner *APIPartner) bool {
	ok := apiPartner.ValidatePresenceOfSetting("remote_objectid_kind")
	ok = ok && apiPartner.ValidatePresenceOfSetting("remote_url")
	ok = ok && apiPartner.ValidatePresenceOfSetting("remote_credential")
	ok = ok && apiPartner.ValidatePresenceOfSetting("local_credential")
	return ok
}

func newSIRIStopMonitoringSubscriptionBroadcaster(partner *Partner) *SIRIStopMonitoringSubscriptionBroadcaster {
	siriStopMonitoringSubscriptionBroadcaster := &SIRIStopMonitoringSubscriptionBroadcaster{}
	siriStopMonitoringSubscriptionBroadcaster.partner = partner
	siriStopMonitoringSubscriptionBroadcaster.mutex = &sync.Mutex{}
	siriStopMonitoringSubscriptionBroadcaster.toBroadcast = make(map[SubscriptionId][]model.StopVisitId)
	siriStopMonitoringSubscriptionBroadcaster.notMonitored = make(map[SubscriptionId]map[string]struct{})

	siriStopMonitoringSubscriptionBroadcaster.stopMonitoringBroadcaster = NewSIRIStopMonitoringBroadcaster(siriStopMonitoringSubscriptionBroadcaster)

	return siriStopMonitoringSubscriptionBroadcaster
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) Stop() {
	connector.stopMonitoringBroadcaster.Stop()
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) Start() {
	connector.stopMonitoringBroadcaster.Start()
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) HandleStopMonitoringBroadcastEvent(event *model.StopMonitoringBroadcastEvent) {
	tx := connector.Partner().Referential().NewTransaction()
	defer tx.Close()

	switch event.ModelType {
	case "StopVisit":
		sv, ok := tx.Model().StopVisits().Find(model.StopVisitId(event.ModelId))
		if ok {
			subsIds := connector.checkEvent(sv, tx)
			if len(subsIds) != 0 {
				connector.addStopVisit(subsIds, sv.Id())
			}
		}
	case "VehicleJourney":
		for _, sv := range tx.Model().StopVisits().FindFollowingByVehicleJourneyId(model.VehicleJourneyId(event.ModelId)) {
			subsIds := connector.checkEvent(sv, tx)
			if len(subsIds) != 0 {
				connector.addStopVisit(subsIds, sv.Id())
			}
		}
	case "StopArea":
		sa, ok := tx.Model().StopAreas().Find(model.StopAreaId(event.ModelId))
		if ok {
			connector.checkStopAreaEvent(&sa, tx)
		}
	default:
		return
	}
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) addStopVisit(subsIds []SubscriptionId, svId model.StopVisitId) {
	connector.mutex.Lock()
	for _, subId := range subsIds {
		connector.toBroadcast[SubscriptionId(subId)] = append(connector.toBroadcast[SubscriptionId(subId)], svId)
	}
	connector.mutex.Unlock()
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) checkEvent(sv model.StopVisit, tx *model.Transaction) (subscriptionIds []SubscriptionId) {
	if sv.Origin == string(connector.Partner().Slug()) {
		return
	}

	vj, _ := tx.Model().VehicleJourneys().Find(sv.VehicleJourneyId)

	for _, stopAreaObjectId := range tx.Model().StopAreas().FindAscendantsWithObjectIdKind(sv.StopAreaId, connector.partner.RemoteObjectIDKind(SIRI_STOP_MONITORING_SUBSCRIPTION_BROADCASTER)) {
		subs := connector.partner.Subscriptions().FindByResourceId(stopAreaObjectId.String(), "StopMonitoringBroadcast")

		for _, sub := range subs {
			resource := sub.Resource(stopAreaObjectId)
			if resource == nil || resource.SubscribedUntil.Before(connector.Clock().Now()) {
				continue
			}

			if lineRef := sub.SubscriptionOption("LineRef"); lineRef != "" && lineRef != string(vj.LineId) {
				continue
			}

			lastState, ok := resource.LastState(string(sv.Id()))
			if ok && !lastState.(*stopMonitoringLastChange).Haschanged(sv) {
				continue
			}

			if !ok {
				smlc := &stopMonitoringLastChange{}
				smlc.InitState(&sv, sub)
				resource.SetLastState(string(sv.Id()), smlc)
			}

			subscriptionIds = append(subscriptionIds, sub.Id())
		}
	}

	return
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) checkStopAreaEvent(stopArea *model.StopArea, tx *model.Transaction) {
	obj, ok := stopArea.ObjectID(connector.partner.RemoteObjectIDKind(SIRI_STOP_MONITORING_SUBSCRIPTION_BROADCASTER))
	if !ok {
		return
	}

	connector.mutex.Lock()

	subs := connector.partner.Subscriptions().FindByResourceId(obj.String(), "StopMonitoringBroadcast")
	for _, sub := range subs {
		resource := sub.Resource(obj)
		if resource == nil || resource.SubscribedUntil.Before(connector.Clock().Now()) {
			continue
		}

		lastState, ok := resource.LastState(string(stopArea.Id()))
		if ok {
			partners, ok := lastState.(*stopAreaLastChange).Haschanged(stopArea)
			if ok {
				nm, ok := connector.notMonitored[sub.Id()]
				if !ok {
					nm = make(map[string]struct{})
					connector.notMonitored[sub.Id()] = nm
				}
				for _, partner := range partners {
					nm[partner] = struct{}{}
				}
			}
			lastState.(*stopAreaLastChange).UpdateState(stopArea)
		} else { // Should not happen
			salc := &stopAreaLastChange{}
			salc.InitState(stopArea, sub)
			resource.SetLastState(string(stopArea.Id()), salc)
		}
	}

	connector.mutex.Unlock()
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) HandleSubscriptionRequest(request *siri.XMLSubscriptionRequest) []siri.SIRIResponseStatus {
	tx := connector.Partner().Referential().NewTransaction()
	defer tx.Close()

	resps := []siri.SIRIResponseStatus{}

	for _, sm := range request.XMLSubscriptionSMEntries() {
		logStashEvent := connector.newLogStashEvent()
		logXMLStopMonitoringSubscriptionEntry(logStashEvent, sm)

		rs := siri.SIRIResponseStatus{
			RequestMessageRef: sm.MessageIdentifier(),
			SubscriberRef:     sm.SubscriberRef(),
			SubscriptionRef:   sm.SubscriptionIdentifier(),
			ResponseTimestamp: connector.Clock().Now(),
		}

		objectid := model.NewObjectID(connector.partner.RemoteObjectIDKind(SIRI_STOP_MONITORING_SUBSCRIPTION_BROADCASTER), sm.MonitoringRef())
		sa, ok := tx.Model().StopAreas().FindByObjectId(objectid)
		if !ok {
			rs.ErrorType = "InvalidDataReferencesError"
			rs.ErrorText = fmt.Sprintf("StopArea not found: '%s'", objectid.Value())
			resps = append(resps, rs)

			logSIRIStopMonitoringSubscriptionResponseEntry(logStashEvent, &rs)
			audit.CurrentLogStash().WriteEvent(logStashEvent)

			continue
		}

		lineRef := ""
		if sm.LineRef() != "" {
			line_objectid := model.NewObjectID(connector.partner.RemoteObjectIDKind(SIRI_STOP_MONITORING_SUBSCRIPTION_BROADCASTER), sm.LineRef())
			line, ok := tx.Model().Lines().FindByObjectId(line_objectid)
			if !ok {
				rs.ErrorType = "InvalidDataReferencesError"
				rs.ErrorText = fmt.Sprintf("Line not found: '%s'", line_objectid.Value())
				resps = append(resps, rs)

				logSIRIStopMonitoringSubscriptionResponseEntry(logStashEvent, &rs)
				audit.CurrentLogStash().WriteEvent(logStashEvent)

				continue
			}
			lineRef = string(line.Id())
		}

		sub, ok := connector.Partner().Subscriptions().FindByExternalId(sm.SubscriptionIdentifier())
		if !ok {
			sub = connector.Partner().Subscriptions().New("StopMonitoringBroadcast")
			sub.SetExternalId(sm.SubscriptionIdentifier())
		}

		ref := model.Reference{
			ObjectId: &objectid,
			Type:     "StopArea",
		}

		r := sub.CreateAddNewResource(ref)
		r.SubscribedAt = connector.Clock().Now()
		r.SubscribedUntil = sm.InitialTerminationTime()

		connector.fillOptions(sub, r, request, sm)
		sub.SetSubscriptionOption("LineRef", lineRef)

		rs.Status = true
		rs.ValidUntil = sm.InitialTerminationTime()
		resps = append(resps, rs)

		logSIRIStopMonitoringSubscriptionResponseEntry(logStashEvent, &rs)
		audit.CurrentLogStash().WriteEvent(logStashEvent)

		// Init SA LastChange
		salc := &stopAreaLastChange{}
		salc.InitState(&sa, sub)
		r.SetLastState(string(sa.Id()), salc)
		// Init StopVisits LastChange
		connector.addStopAreaStopVisits(sa, sub, r)
	}

	return resps
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) addStopAreaStopVisits(sa model.StopArea, sub *Subscription, res *SubscribedResource) {
	tx := connector.Partner().Referential().NewTransaction()
	defer tx.Close()

	for _, saId := range tx.Model().StopAreas().FindFamily(sa.Id()) {
		for _, sv := range tx.Model().StopVisits().FindFollowingByStopAreaId(saId) {
			if _, ok := res.LastState(string(sv.Id())); ok {
				continue
			}
			// Handle LineRef filter
			vj, _ := tx.Model().VehicleJourneys().Find(sv.VehicleJourneyId)
			if lineRef := sub.SubscriptionOption("LineRef"); lineRef != "" && lineRef != string(vj.LineId) {
				continue
			}

			smlc := &stopMonitoringLastChange{}
			smlc.InitState(&sv, sub)
			res.SetLastState(string(sv.Id()), smlc)
			connector.addStopVisit([]SubscriptionId{sub.Id()}, sv.Id())
		}
	}
}

// WIP Need to do something about this method Refs #6338
func (smsb *SIRIStopMonitoringSubscriptionBroadcaster) fillOptions(s *Subscription, r *SubscribedResource, request *siri.XMLSubscriptionRequest, sm *siri.XMLStopMonitoringSubscriptionRequestEntry) {
	changeBeforeUpdates := request.ChangeBeforeUpdates()
	if changeBeforeUpdates == "" {
		changeBeforeUpdates = "PT1M"
	}

	s.SetSubscriptionOption("StopVisitTypes", sm.StopVisitTypes())
	s.SetSubscriptionOption("IncrementalUpdates", request.IncrementalUpdates())
	s.SetSubscriptionOption("MaximumStopVisits", sm.MaximumStopVisits())
	s.SetSubscriptionOption("ChangeBeforeUpdates", changeBeforeUpdates)
	s.SetSubscriptionOption("MessageIdentifier", request.MessageIdentifier())
}

func (connector *SIRIStopMonitoringSubscriptionBroadcaster) newLogStashEvent() audit.LogStashEvent {
	event := connector.partner.NewLogStashEvent()
	event["connector"] = "StopMonitoringSubscriptionBroadcaster"
	return event
}

func logXMLStopMonitoringSubscriptionEntry(logStashEvent audit.LogStashEvent, request *siri.XMLStopMonitoringSubscriptionRequestEntry) {
	logStashEvent["siriType"] = "StopMonitoringSubscriptionEntry"
	logStashEvent["messageIdentifier"] = request.MessageIdentifier()
	logStashEvent["monitoringRef"] = request.MonitoringRef()
	logStashEvent["stopVisitTypes"] = request.StopVisitTypes()
	logStashEvent["subscriberRef"] = request.SubscriberRef()
	logStashEvent["subscriptionIdentifier"] = request.SubscriptionIdentifier()
	logStashEvent["initialTerminationTime"] = request.InitialTerminationTime().String()
	logStashEvent["requestTimestamp"] = request.RequestTimestamp().String()
	logStashEvent["requestXML"] = request.RawXML()
}

func logSIRIStopMonitoringSubscriptionResponseEntry(logStashEvent audit.LogStashEvent, smEntry *siri.SIRIResponseStatus) {
	logStashEvent["requestMessageRef"] = smEntry.RequestMessageRef
	logStashEvent["subscriptionRef"] = smEntry.SubscriptionRef
	logStashEvent["responseTimestamp"] = smEntry.ResponseTimestamp.String()
	logStashEvent["validUntil"] = smEntry.ValidUntil.String()
	logStashEvent["status"] = strconv.FormatBool(smEntry.Status)
	if !smEntry.Status {
		logStashEvent["errorType"] = smEntry.ErrorType
		if smEntry.ErrorType == "OtherError" {
			logStashEvent["errorNumber"] = strconv.Itoa(smEntry.ErrorNumber)
		}
		logStashEvent["errorText"] = smEntry.ErrorText
	}
}

// START TEST

type TestSIRIStopMonitoringSubscriptionBroadcasterFactory struct{}

type TestStopMonitoringSubscriptionBroadcaster struct {
	model.UUIDConsumer

	events                    []*model.StopMonitoringBroadcastEvent
	stopMonitoringBroadcaster SIRIStopMonitoringBroadcaster
}

func NewTestStopMonitoringSubscriptionBroadcaster() *TestStopMonitoringSubscriptionBroadcaster {
	connector := &TestStopMonitoringSubscriptionBroadcaster{}
	return connector
}

func (connector *TestStopMonitoringSubscriptionBroadcaster) HandleStopMonitoringBroadcastEvent(event *model.StopMonitoringBroadcastEvent) {
	connector.events = append(connector.events, event)
}

func (factory *TestSIRIStopMonitoringSubscriptionBroadcasterFactory) Validate(apiPartner *APIPartner) bool {
	return true
}

func (factory *TestSIRIStopMonitoringSubscriptionBroadcasterFactory) CreateConnector(partner *Partner) Connector {
	return NewTestStopMonitoringSubscriptionBroadcaster()
}

// END TEST
<|endoftext|>


<commit_before>package via

import (
	"compress/gzip"
	"fmt"
	"github.com/mrosset/gurl"
	"github.com/mrosset/util/console"
	"github.com/mrosset/util/file"
	"github.com/mrosset/util/json"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
)

var (
	client  = new(http.Client)
	verbose = false
	elog    = log.New(os.Stderr, "", log.Lshortfile)
	lfmt    = "%-20.20s %v\n"
	debug   = false
	expand  = os.ExpandEnv
	update  = false
	deps    = false
)

func Root(s string) {
	config.Root = s
}

func Verbose(b bool) {
	verbose = b
}

func Deps(b bool) {
	deps = b
}

func Update(b bool) {
	update = b
}

func Debug(b bool) {
	debug = b
}

func DownloadSrc(plan *Plan) (err error) {
	if file.Exists(plan.SourcePath()) && !update {
		return nil
	}
	fmt.Printf(lfmt, "download", plan.NameVersion())
	eurl := plan.Expand().Url
	u, err := url.Parse(eurl)
	if err != nil {
		return err
	}
	switch u.Scheme {
	case "ftp":
		wget(cache.Sources(), eurl)
	case "http", "https":
		return gurl.Download(cache.Sources(), eurl)
	default:
		return fmt.Errorf("%s: URL scheme is not supported", u.Scheme)
	}
	return nil
}

// Stages the downloaded source in via's cache directory
// the stage only happens once unless BuilInStage is used
func Stage(plan *Plan) (err error) {
	if plan.Url == "" || file.Exists(plan.GetStageDir()) {
		// nothing to stage
		return nil
	}
	fmt.Printf(lfmt, "stage", plan.NameVersion())
	u, err := url.Parse(plan.Expand().Url)
	if err != nil {
		elog.Println(err)
		return err
	}
	if u.Scheme == "git" {
		fmt.Println(cache.Stages())
		fmt.Println(plan.SourcePath())
		cmd := exec.Command("git", "clone", plan.SourcePath(), plan.SourceFile())
		cmd.Dir = cache.Stages()
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Run()
		goto ret
	}
	switch filepath.Ext(plan.SourceFile()) {
	case ".zip":
		unzip(cache.Stages(), plan.SourcePath())
	default:
		GNUUntar(cache.Stages(), plan.SourcePath())
	}
ret:
	fmt.Printf(lfmt, "patch", plan.NameVersion())
	if err := doCommands(join(cache.Stages(), plan.stageDir()), plan.Patch); err != nil {
		return err
	}
	return
}

// Calls each shell command in the plans Build field.
func Build(config *Config, plan *Plan) (err error) {
	var (
		build = plan.Build
	)
	if file.Exists(plan.PackagePath()) {
		fmt.Printf("FIXME: (short flags)  package %s exists building anyways.\n", plan.PackagePath())
	}
	for _, p := range plan.BuildDepends {
		if IsInstalled(config, p) {
			continue
		}
		if err := Install(config, p); err != nil {
			return err
		}
	}
	flags := append(config.Flags, plan.Flags...)
	os.MkdirAll(plan.BuildDir(), 0755)
	// Parent plan Build is run first this plans is added at the end.
	if plan.Inherit != "" {
		parent, _ := NewPlan(plan.Inherit)
		build = append(parent.Build, plan.Build...)
		flags = append(flags, parent.Flags...)
	}
	os.Setenv("SRCDIR", plan.GetStageDir())
	os.Setenv("Flags", expand(flags.String()))
	err = doCommands(plan.BuildDir(), build)
	if err != nil {
		return fmt.Errorf("%s in %s", err.Error(), plan.BuildDir())
	}
	return nil
}

func doCommands(dir string, cmds []string) (err error) {
	for i, j := range cmds {
		if debug {
			elog.Println(i, j)
		}
		cmd := exec.Command("bash", "-c", j)
		cmd.Dir = dir
		cmd.Stdin = os.Stdin
		if verbose {
			cmd.Stdout = os.Stdout
		}
		cmd.Stderr = os.Stderr
		err = cmd.Run()
		if err != nil {
			elog.Printf("%s: %s\n", j, err)
			return err
		}
	}
	return nil
}

func Package(bdir string, plan *Plan) (err error) {
	var (
		pack = plan.Package
	)
	pdir := join(cache.Packages(), plan.NameVersion())
	if bdir == "" {
		bdir = join(cache.Builds(), plan.NameVersion())
	}
	if plan.BuildInStage {
		bdir = join(cache.Stages(), plan.stageDir())
	}
	if file.Exists(pdir) {
		err := os.RemoveAll(pdir)
		if err != nil {
			return err
		}
	}
	err = os.Mkdir(pdir, 0755)
	if err != nil {
		elog.Println(err)
		return err
	}
	os.Setenv("PKGDIR", pdir)
	if plan.Inherit != "" {
		parent, _ := NewPlan(plan.Inherit)
		pack = append(parent.Package, plan.Package...)
	}
	err = doCommands(bdir, pack)
	if err != nil {
		return err
	}
	for _, j := range plan.SubPackages {
		sub, err := NewPlan(j)
		if err != nil {
			return err
		}
		if err = Package(bdir, sub); err != nil {
			return err
		}
	}
	err = CreatePackage(plan)
	if err != nil {
		return (err)
	}
	plan.Cid, err = IpfsAdd(Path(plan.PackagePath()))
	if err != nil {
		return err
	}
	return plan.Save()
	/*
		err = CreatePackage(plan)
		if err != nil {
			return err
		}
		return Sign(plan)
	*/
}

func CreatePackage(plan *Plan) (err error) {
	pfile := plan.PackagePath()
	os.MkdirAll(filepath.Dir(pfile), 0755)
	fd, err := os.Create(pfile)
	if err != nil {
		elog.Println(err)
		return err
	}
	defer fd.Close()
	gz := gzip.NewWriter(fd)
	defer gz.Close()
	return Tarball(gz, plan)
}

// Updates each plans Oid to the Oid of the tarball in publish git repo
// this function should never be used in production. It's used for making sure
// the plans Oid match the git repo's Oid
func SyncHashs() {
	plans, _ := GetPlans()
	for _, p := range plans {
		if file.Exists(p.PackagePath()) {
			p.Cid, _ = HashOnly(Path(p.PackagePath()))
			p.Save()
			log.Println(p.Cid, p.Name)
		}
	}
}

func Install(config *Config, name string) (err error) {
	plan, err := NewPlan(name)
	if err != nil {
		elog.Println(name, err)
		return
	}
	if IsInstalled(config, name) {
		fmt.Printf("FIXME: (short flags) package %s installed upgrading anyways.\n", plan.NameVersion())
		err := Remove(config, name)
		if err != nil {
			return err
		}
	}
	db := filepath.Join(config.DB.Installed(), plan.Name)
	if file.Exists(db) {
		return fmt.Errorf("%s is already installed", name)
	}
	cid, err := HashOnly(Path(plan.PackagePath()))
	if err != nil {
		return (err)
	}
	if cid != plan.Cid {
		return fmt.Errorf("%s Plans CID does not match tarballs got %s", plan.NameVersion(), cid)
	}
	man, err := ReadPackManifest(plan.PackageFilePath(config))
	if err != nil {
		return err
	}
	errs := conflicts(man)
	if len(errs) > 0 {
		//return errs[0]
		for _, e := range errs {
			elog.Println(e)
		}
	}
	fd, err := os.Open(plan.PackageFilePath(config))
	if err != nil {
		return
	}
	defer fd.Close()
	gz, err := gzip.NewReader(fd)
	if err != nil {
		return
	}
	defer gz.Close()
	os.MkdirAll(config.Root, 0755)
	err = Untar(config.Root, gz)
	if err != nil {
		return err
	}
	err = os.MkdirAll(db, 0755)
	if err != nil {
		elog.Println(err)
		return err
	}
	man.Cid = plan.Cid
	return json.Write(man, join(db, "manifest.json"))
}

func PostInstall(plan *Plan) (err error) {
	return doCommands("/", append(plan.PostInstall, config.PostInstall...))
}

func Remove(config *Config, name string) (err error) {
	if !IsInstalled(config, name) {
		err = fmt.Errorf("%s is not installed.", name)
		elog.Println(err)
		return err
	}

	man, err := ReadManifest(name)
	if err != nil {
		return err
	}
	for _, f := range man.Files {
		fpath := join(config.Root, f)
		err = os.Remove(fpath)
		if err != nil {
			elog.Println(f, err)
		}
	}

	return os.RemoveAll(join(config.DB.Installed(), name))
}

func BuildDeps(config *Config, plan *Plan) (err error) {
	deps := append(plan.AutoDepends, plan.ManualDepends...)
	for _, d := range deps {
		if IsInstalled(config, d) {
			continue
		}
		p, _ := NewPlan(d)
		if file.Exists(p.PackagePath()) {
			err := Install(config, p.Name)
			if err != nil {
				return err
			}
			continue
		}
		fmt.Println("building", d, "for", plan.NameVersion())
		err := BuildDeps(config, p)
		if err != nil {
			elog.Println(err)
			return err
		}
	}
	err = BuildSteps(config, plan)
	if err != nil {
		return err
	}
	return Install(config, plan.Name)
}

// Run all of the functions required to build a package
func BuildSteps(config *Config, plan *Plan) (err error) {
	if file.Exists(plan.PackageFile()) {
		return fmt.Errorf("package %s exists", plan.PackageFile())
	}
	if err := DownloadSrc(plan); err != nil {
		elog.Println(err)
		return err
	}
	if err := Stage(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "build", plan.NameVersion())
	if err := Build(config, plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "package", plan.NameVersion())
	if err := Package("", plan); err != nil {
		elog.Println(err)
		return err
	}
	return nil
}

var (
	rexName   = regexp.MustCompile("[A-Za-z]+")
	rexTruple = regexp.MustCompile("[0-9]+.[0-9]+.[0-9]+")
	rexDouble = regexp.MustCompile("[0-9]+.[0-9]+")
)

// Creates a new plan from a given Url
func Create(url, group string) (err error) {
	var (
		xfile   = filepath.Base(url)
		name    = rexName.FindString(xfile)
		triple  = rexTruple.FindString(xfile)
		double  = rexDouble.FindString(xfile)
		version string
	)
	switch {
	case triple != "":
		version = triple
	case double != "":
		version = double
	default:
		return fmt.Errorf("regex fail for %s", xfile)
	}
	plan := &Plan{Name: name, Version: version, Url: url, Group: group}
	plan.Inherit = "gnu"
	if file.Exists(plan.Path()) {
		return fmt.Errorf("%s already exists", plan.Path())
	}
	return plan.Save()
}

func IsInstalled(config *Config, name string) bool {
	return file.Exists(join(config.DB.Installed(), name))
}

func refactor(plan *Plan) {
}

func Lint() (err error) {
	e, err := PlanFiles()
	if err != nil {
		return err
	}
	for _, j := range e {
		plan, err := ReadPath(j)
		if err != nil {
			err = fmt.Errorf("%s %s", j, err)
			elog.Println(err)
			return err
		}
		// If Group is empty, we can set it
		if plan.Group == "" {
			plan.Group = baseDir(j)
		}
		if verbose {
			console.Println("lint", plan.Name, plan.Version)
		}
		sort.Strings(plan.SubPackages)
		sort.Strings(plan.Flags)
		sort.Strings(plan.Remove)
		sort.Strings(plan.AutoDepends)
		sort.Strings(plan.ManualDepends)
		sort.Strings(plan.BuildDepends)
		refactor(plan)
		err = plan.Save()
		if err != nil {
			elog.Println(err)
			return err
		}
	}
	console.Flush()
	return nil
}

func fatal(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
func Clean(name string) error {
	plan, err := NewPlan(name)
	if err != nil {
		return err
	}
	fmt.Printf(lfmt, "clean", plan.NameVersion())
	dir := join(cache.Builds(), plan.NameVersion())
	if err = os.RemoveAll(dir); err != nil {
		return err
	}

	if plan.BuildInStage {
		dir = join(cache.Stages(), plan.stageDir())
		return os.RemoveAll(dir)
	}
	return nil
}

func PlanFiles() ([]string, error) {
	return filepath.Glob(join(config.Plans, "*", "*.json"))
}

func conflicts(man *Plan) (errs []error) {
	for _, f := range man.Files {
		fpath := join(config.Root, f)
		if file.Exists(fpath) {
			errs = append(errs, fmt.Errorf("%s already exists.", f))
		}
	}
	return errs
}

func GetConfig() *Config {
	return config
}
<commit_msg>mark Plans as rebuilt during packaging<commit_after>package via

import (
	"compress/gzip"
	"fmt"
	"github.com/mrosset/gurl"
	"github.com/mrosset/util/console"
	"github.com/mrosset/util/file"
	"github.com/mrosset/util/json"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
)

var (
	client  = new(http.Client)
	verbose = false
	elog    = log.New(os.Stderr, "", log.Lshortfile)
	lfmt    = "%-20.20s %v\n"
	debug   = false
	expand  = os.ExpandEnv
	update  = false
	deps    = false
)

func Root(s string) {
	config.Root = s
}

func Verbose(b bool) {
	verbose = b
}

func Deps(b bool) {
	deps = b
}

func Update(b bool) {
	update = b
}

func Debug(b bool) {
	debug = b
}

func DownloadSrc(plan *Plan) (err error) {
	if file.Exists(plan.SourcePath()) && !update {
		return nil
	}
	fmt.Printf(lfmt, "download", plan.NameVersion())
	eurl := plan.Expand().Url
	u, err := url.Parse(eurl)
	if err != nil {
		return err
	}
	switch u.Scheme {
	case "ftp":
		wget(cache.Sources(), eurl)
	case "http", "https":
		return gurl.Download(cache.Sources(), eurl)
	default:
		return fmt.Errorf("%s: URL scheme is not supported", u.Scheme)
	}
	return nil
}

// Stages the downloaded source in via's cache directory
// the stage only happens once unless BuilInStage is used
func Stage(plan *Plan) (err error) {
	if plan.Url == "" || file.Exists(plan.GetStageDir()) {
		// nothing to stage
		return nil
	}
	fmt.Printf(lfmt, "stage", plan.NameVersion())
	u, err := url.Parse(plan.Expand().Url)
	if err != nil {
		elog.Println(err)
		return err
	}
	if u.Scheme == "git" {
		fmt.Println(cache.Stages())
		fmt.Println(plan.SourcePath())
		cmd := exec.Command("git", "clone", plan.SourcePath(), plan.SourceFile())
		cmd.Dir = cache.Stages()
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Run()
		goto ret
	}
	switch filepath.Ext(plan.SourceFile()) {
	case ".zip":
		unzip(cache.Stages(), plan.SourcePath())
	default:
		GNUUntar(cache.Stages(), plan.SourcePath())
	}
ret:
	fmt.Printf(lfmt, "patch", plan.NameVersion())
	if err := doCommands(join(cache.Stages(), plan.stageDir()), plan.Patch); err != nil {
		return err
	}
	return
}

// Calls each shell command in the plans Build field.
func Build(config *Config, plan *Plan) (err error) {
	var (
		build = plan.Build
	)
	if file.Exists(plan.PackagePath()) {
		fmt.Printf("FIXME: (short flags)  package %s exists building anyways.\n", plan.PackagePath())
	}
	for _, p := range plan.BuildDepends {
		if IsInstalled(config, p) {
			continue
		}
		if err := Install(config, p); err != nil {
			return err
		}
	}
	flags := append(config.Flags, plan.Flags...)
	os.MkdirAll(plan.BuildDir(), 0755)
	// Parent plan Build is run first this plans is added at the end.
	if plan.Inherit != "" {
		parent, _ := NewPlan(plan.Inherit)
		build = append(parent.Build, plan.Build...)
		flags = append(flags, parent.Flags...)
	}
	os.Setenv("SRCDIR", plan.GetStageDir())
	os.Setenv("Flags", expand(flags.String()))
	err = doCommands(plan.BuildDir(), build)
	if err != nil {
		return fmt.Errorf("%s in %s", err.Error(), plan.BuildDir())
	}
	return nil
}

func doCommands(dir string, cmds []string) (err error) {
	for i, j := range cmds {
		if debug {
			elog.Println(i, j)
		}
		cmd := exec.Command("bash", "-c", j)
		cmd.Dir = dir
		cmd.Stdin = os.Stdin
		if verbose {
			cmd.Stdout = os.Stdout
		}
		cmd.Stderr = os.Stderr
		err = cmd.Run()
		if err != nil {
			elog.Printf("%s: %s\n", j, err)
			return err
		}
	}
	return nil
}

func Package(bdir string, plan *Plan) (err error) {
	var (
		pack = plan.Package
	)
	pdir := join(cache.Packages(), plan.NameVersion())
	if bdir == "" {
		bdir = join(cache.Builds(), plan.NameVersion())
	}
	if plan.BuildInStage {
		bdir = join(cache.Stages(), plan.stageDir())
	}
	if file.Exists(pdir) {
		err := os.RemoveAll(pdir)
		if err != nil {
			return err
		}
	}
	err = os.Mkdir(pdir, 0755)
	if err != nil {
		elog.Println(err)
		return err
	}
	os.Setenv("PKGDIR", pdir)
	if plan.Inherit != "" {
		parent, _ := NewPlan(plan.Inherit)
		pack = append(parent.Package, plan.Package...)
	}
	err = doCommands(bdir, pack)
	if err != nil {
		return err
	}
	for _, j := range plan.SubPackages {
		sub, err := NewPlan(j)
		if err != nil {
			return err
		}
		if err = Package(bdir, sub); err != nil {
			return err
		}
	}
	err = CreatePackage(plan)
	if err != nil {
		return (err)
	}
	plan.Cid, err = IpfsAdd(Path(plan.PackagePath()))
	if err != nil {
		return err
	}
	plan.IsRebuilt = true
	return plan.Save()
	/*
		err = CreatePackage(plan)
		if err != nil {
			return err
		}
		return Sign(plan)
	*/
}

func CreatePackage(plan *Plan) (err error) {
	pfile := plan.PackagePath()
	os.MkdirAll(filepath.Dir(pfile), 0755)
	fd, err := os.Create(pfile)
	if err != nil {
		elog.Println(err)
		return err
	}
	defer fd.Close()
	gz := gzip.NewWriter(fd)
	defer gz.Close()
	return Tarball(gz, plan)
}

// Updates each plans Oid to the Oid of the tarball in publish git repo
// this function should never be used in production. It's used for making sure
// the plans Oid match the git repo's Oid
func SyncHashs() {
	plans, _ := GetPlans()
	for _, p := range plans {
		if file.Exists(p.PackagePath()) {
			p.Cid, _ = HashOnly(Path(p.PackagePath()))
			p.Save()
			log.Println(p.Cid, p.Name)
		}
	}
}

func Install(config *Config, name string) (err error) {
	plan, err := NewPlan(name)
	if err != nil {
		elog.Println(name, err)
		return
	}
	if IsInstalled(config, name) {
		fmt.Printf("FIXME: (short flags) package %s installed upgrading anyways.\n", plan.NameVersion())
		err := Remove(config, name)
		if err != nil {
			return err
		}
	}
	db := filepath.Join(config.DB.Installed(), plan.Name)
	if file.Exists(db) {
		return fmt.Errorf("%s is already installed", name)
	}
	cid, err := HashOnly(Path(plan.PackagePath()))
	if err != nil {
		return (err)
	}
	if cid != plan.Cid {
		return fmt.Errorf("%s Plans CID does not match tarballs got %s", plan.NameVersion(), cid)
	}
	man, err := ReadPackManifest(plan.PackageFilePath(config))
	if err != nil {
		return err
	}
	errs := conflicts(man)
	if len(errs) > 0 {
		//return errs[0]
		for _, e := range errs {
			elog.Println(e)
		}
	}
	fd, err := os.Open(plan.PackageFilePath(config))
	if err != nil {
		return
	}
	defer fd.Close()
	gz, err := gzip.NewReader(fd)
	if err != nil {
		return
	}
	defer gz.Close()
	os.MkdirAll(config.Root, 0755)
	err = Untar(config.Root, gz)
	if err != nil {
		return err
	}
	err = os.MkdirAll(db, 0755)
	if err != nil {
		elog.Println(err)
		return err
	}
	man.Cid = plan.Cid
	return json.Write(man, join(db, "manifest.json"))
}

func PostInstall(plan *Plan) (err error) {
	return doCommands("/", append(plan.PostInstall, config.PostInstall...))
}

func Remove(config *Config, name string) (err error) {
	if !IsInstalled(config, name) {
		err = fmt.Errorf("%s is not installed.", name)
		elog.Println(err)
		return err
	}

	man, err := ReadManifest(name)
	if err != nil {
		return err
	}
	for _, f := range man.Files {
		fpath := join(config.Root, f)
		err = os.Remove(fpath)
		if err != nil {
			elog.Println(f, err)
		}
	}

	return os.RemoveAll(join(config.DB.Installed(), name))
}

func BuildDeps(config *Config, plan *Plan) (err error) {
	deps := append(plan.AutoDepends, plan.ManualDepends...)
	for _, d := range deps {
		if IsInstalled(config, d) {
			continue
		}
		p, _ := NewPlan(d)
		if file.Exists(p.PackagePath()) {
			err := Install(config, p.Name)
			if err != nil {
				return err
			}
			continue
		}
		fmt.Println("building", d, "for", plan.NameVersion())
		err := BuildDeps(config, p)
		if err != nil {
			elog.Println(err)
			return err
		}
	}
	err = BuildSteps(config, plan)
	if err != nil {
		return err
	}
	return Install(config, plan.Name)
}

// Run all of the functions required to build a package
func BuildSteps(config *Config, plan *Plan) (err error) {
	if file.Exists(plan.PackageFile()) {
		return fmt.Errorf("package %s exists", plan.PackageFile())
	}
	if err := DownloadSrc(plan); err != nil {
		elog.Println(err)
		return err
	}
	if err := Stage(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "build", plan.NameVersion())
	if err := Build(config, plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "package", plan.NameVersion())
	if err := Package("", plan); err != nil {
		elog.Println(err)
		return err
	}
	return nil
}

var (
	rexName   = regexp.MustCompile("[A-Za-z]+")
	rexTruple = regexp.MustCompile("[0-9]+.[0-9]+.[0-9]+")
	rexDouble = regexp.MustCompile("[0-9]+.[0-9]+")
)

// Creates a new plan from a given Url
func Create(url, group string) (err error) {
	var (
		xfile   = filepath.Base(url)
		name    = rexName.FindString(xfile)
		triple  = rexTruple.FindString(xfile)
		double  = rexDouble.FindString(xfile)
		version string
	)
	switch {
	case triple != "":
		version = triple
	case double != "":
		version = double
	default:
		return fmt.Errorf("regex fail for %s", xfile)
	}
	plan := &Plan{Name: name, Version: version, Url: url, Group: group}
	plan.Inherit = "gnu"
	if file.Exists(plan.Path()) {
		return fmt.Errorf("%s already exists", plan.Path())
	}
	return plan.Save()
}

func IsInstalled(config *Config, name string) bool {
	return file.Exists(join(config.DB.Installed(), name))
}

func refactor(plan *Plan) {
}

func Lint() (err error) {
	e, err := PlanFiles()
	if err != nil {
		return err
	}
	for _, j := range e {
		plan, err := ReadPath(j)
		if err != nil {
			err = fmt.Errorf("%s %s", j, err)
			elog.Println(err)
			return err
		}
		// If Group is empty, we can set it
		if plan.Group == "" {
			plan.Group = baseDir(j)
		}
		if verbose {
			console.Println("lint", plan.Name, plan.Version)
		}
		sort.Strings(plan.SubPackages)
		sort.Strings(plan.Flags)
		sort.Strings(plan.Remove)
		sort.Strings(plan.AutoDepends)
		sort.Strings(plan.ManualDepends)
		sort.Strings(plan.BuildDepends)
		refactor(plan)
		err = plan.Save()
		if err != nil {
			elog.Println(err)
			return err
		}
	}
	console.Flush()
	return nil
}

func fatal(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
func Clean(name string) error {
	plan, err := NewPlan(name)
	if err != nil {
		return err
	}
	fmt.Printf(lfmt, "clean", plan.NameVersion())
	dir := join(cache.Builds(), plan.NameVersion())
	if err = os.RemoveAll(dir); err != nil {
		return err
	}

	if plan.BuildInStage {
		dir = join(cache.Stages(), plan.stageDir())
		return os.RemoveAll(dir)
	}
	return nil
}

func PlanFiles() ([]string, error) {
	return filepath.Glob(join(config.Plans, "*", "*.json"))
}

func conflicts(man *Plan) (errs []error) {
	for _, f := range man.Files {
		fpath := join(config.Root, f)
		if file.Exists(fpath) {
			errs = append(errs, fmt.Errorf("%s already exists.", f))
		}
	}
	return errs
}

func GetConfig() *Config {
	return config
}
<|endoftext|>


<commit_before>package killer

import (
	"fmt"
	"io"
	"os"
	"strings"
	"syscall"

	"github.com/fatih/color"
	"github.com/heppu/readline"
	"github.com/k0kubun/go-ansi"
	"github.com/mitchellh/go-ps"
)

type Killer struct {
	rl        *readline.Instance
	processes []ps.Process
	filtered  []ps.Process
	cursor    int
	filter    string
	done      bool
	killed    bool
	err       error
}

func NewKiller() (*Killer, error) {
	processes, err := ps.Processes()
	if err != nil {
		return nil, err
	}
	if len(processes) == 0 {
		return nil, fmt.Errorf("No processes")
	}

	k := &Killer{
		processes: processes,
		filtered:  processes,
		cursor:    len(processes) / 2,
	}

	color.Output = ansi.NewAnsiStdout()

	rl, err := readline.New("")
	if err != nil {
		return nil, err
	}
	rl.SetConfig(&readline.Config{
		HistoryLimit:        -1,
		FuncFilterInputRune: k.filterInput,
		Listener:            k,
		UniqueEditLine:      true,
		Stdout:              color.Output,
	})
	k.rl = rl
	return k, nil
}

func (k *Killer) Start() (err error) {
	for {
		if _, err = k.rl.Readline(); err != nil {
			if err == readline.ErrInterrupt {
				err = nil
				break
			}
			if err == io.EOF {
				err = nil
			}
			if err == nil {
				break
			}
		}
		if k.done {
			break
		}
	}

	k.rl.Clean()
	k.rl.Close()
	err = k.err
	return
}

func (k *Killer) nextProcess() {
	if len(k.filtered) > 1 {
		k.cursor = ((k.cursor+1)%len(k.filtered) + len(k.filtered)) % len(k.filtered)
	}
}

func (k *Killer) prevProcess() {
	if len(k.filtered) > 1 {
		k.cursor = ((k.cursor-1)%len(k.filtered) + len(k.filtered)) % len(k.filtered)
	}
}

func (k *Killer) killProcess(sig syscall.Signal) {
	var p *os.Process
	if k.filtered[k.cursor].Pid() == os.Getpid() {
		return
	}
	if p, k.err = os.FindProcess(k.filtered[k.cursor].Pid()); k.err != nil {
		return
	}

	k.err = p.Signal(sig)
}

func (k *Killer) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
	if !k.done {
		k.filter = string(line)
		k.filterProcesses()
	}

	bold := color.New(color.Bold).SprintFunc()
	prompt := fmt.Sprintf("  %s", bold("Filter processes"))
	postPrompt := fmt.Sprintf(" (%d/%d) : ", len(k.filtered), len(k.processes))
	k.rl.SetPrompt(prompt + postPrompt)
	k.rl.Refresh()

	if len(k.filtered) > 0 {
		k.printProcesses()
		if !k.done {
			ansi.CursorPreviousLine(8)
			ansi.CursorForward(18 + len(postPrompt) + len(line))
		}
	}

	return nil, 0, false
}

func (k *Killer) printProcesses() {
	for i := -3; i < 4; i++ {
		ansi.Println()
		ansi.EraseInLine(2)
		index := ((k.cursor+i)%len(k.filtered) + len(k.filtered)) % len(k.filtered)
		if i == 0 {
			color.Set(color.FgCyan)
			ansi.Printf("❯ %s %d", k.filtered[index].Executable(), k.filtered[index].Pid())
			color.Unset()
		} else {
			ansi.Printf("  %s %d", k.filtered[index].Executable(), k.filtered[index].Pid())
		}
	}
	ansi.Println()
}

func (k *Killer) filterProcesses() {
	var found bool
	oldPid := 0
	if len(k.filtered) > k.cursor {
		oldPid = k.filtered[k.cursor].Pid()
	}
	k.filtered = make([]ps.Process, 0)

	for i := 0; i < len(k.processes); i++ {
		if index := strings.Index(strings.ToUpper(k.processes[i].Executable()), strings.ToUpper(k.filter)); index != -1 || k.filter == "" {
			if !found && oldPid == k.processes[i].Pid() {
				k.cursor = len(k.filtered)
				found = true
			}

			k.filtered = append(k.filtered, k.processes[i])
		}
	}

	if !found {
		k.cursor = 0
	}
}

func (k *Killer) filterInput(r rune) (rune, bool) {
	switch r {
	case readline.CharEnter:
		k.done = true
		if len(k.filtered) > 0 {
			k.killProcess(syscall.SIGTERM)
		}
		return readline.CharInterrupt, true

	case readline.CharInterrupt:
		k.done = true
		return r, true

	case readline.CharNext:
		k.nextProcess()
		return readline.CharForward, true

	case readline.CharPrev:
		k.prevProcess()
		return readline.CharForward, true
	}
	return r, true
}
<commit_msg>Sort processes alphabetically<commit_after>package killer

import (
	"fmt"
	"io"
	"os"
	"sort"
	"strings"
	"syscall"

	"github.com/fatih/color"
	"github.com/heppu/readline"
	"github.com/k0kubun/go-ansi"
	"github.com/mitchellh/go-ps"
)

type ByName []ps.Process

func (p ByName) Len() int           { return len(p) }
func (p ByName) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
func (p ByName) Less(i, j int) bool { return p[i].Executable() < p[j].Executable() }

type Killer struct {
	rl        *readline.Instance
	processes []ps.Process
	filtered  []ps.Process
	cursor    int
	filter    string
	done      bool
	killed    bool
	err       error
}

func NewKiller() (*Killer, error) {
	processes, err := ps.Processes()
	if err != nil {
		return nil, err
	}
	if len(processes) == 0 {
		return nil, fmt.Errorf("No processes")
	}
	sort.Sort(ByName(processes))

	k := &Killer{
		processes: processes,
		filtered:  processes,
		cursor:    len(processes) / 2,
	}

	color.Output = ansi.NewAnsiStdout()

	rl, err := readline.New("")
	if err != nil {
		return nil, err
	}
	rl.SetConfig(&readline.Config{
		HistoryLimit:        -1,
		FuncFilterInputRune: k.filterInput,
		Listener:            k,
		UniqueEditLine:      true,
		Stdout:              color.Output,
	})
	k.rl = rl
	return k, nil
}

func (k *Killer) Start() (err error) {
	for {
		if _, err = k.rl.Readline(); err != nil {
			if err == readline.ErrInterrupt {
				err = nil
				break
			}
			if err == io.EOF {
				err = nil
			}
			if err == nil {
				break
			}
		}
		if k.done {
			break
		}
	}

	k.rl.Clean()
	k.rl.Close()
	err = k.err
	return
}

func (k *Killer) nextProcess() {
	if len(k.filtered) > 1 {
		k.cursor = ((k.cursor+1)%len(k.filtered) + len(k.filtered)) % len(k.filtered)
	}
}

func (k *Killer) prevProcess() {
	if len(k.filtered) > 1 {
		k.cursor = ((k.cursor-1)%len(k.filtered) + len(k.filtered)) % len(k.filtered)
	}
}

func (k *Killer) killProcess(sig syscall.Signal) {
	var p *os.Process
	if k.filtered[k.cursor].Pid() == os.Getpid() {
		return
	}
	if p, k.err = os.FindProcess(k.filtered[k.cursor].Pid()); k.err != nil {
		return
	}

	k.err = p.Signal(sig)
}

func (k *Killer) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
	if !k.done {
		k.filter = string(line)
		k.filterProcesses()
	}

	bold := color.New(color.Bold).SprintFunc()
	prompt := fmt.Sprintf("  %s", bold("Filter processes"))
	postPrompt := fmt.Sprintf(" (%d/%d) : ", len(k.filtered), len(k.processes))
	k.rl.SetPrompt(prompt + postPrompt)
	k.rl.Refresh()

	if len(k.filtered) > 0 {
		k.printProcesses()
		if !k.done {
			ansi.CursorPreviousLine(8)
			ansi.CursorForward(18 + len(postPrompt) + len(line))
		}
	}

	return nil, 0, false
}

func (k *Killer) printProcesses() {
	for i := -3; i < 4; i++ {
		ansi.Println()
		ansi.EraseInLine(2)
		index := ((k.cursor+i)%len(k.filtered) + len(k.filtered)) % len(k.filtered)
		if i == 0 {
			color.Set(color.FgCyan)
			ansi.Printf("❯ %s %d", k.filtered[index].Executable(), k.filtered[index].Pid())
			color.Unset()
		} else {
			ansi.Printf("  %s %d", k.filtered[index].Executable(), k.filtered[index].Pid())
		}
	}
	ansi.Println()
}

func (k *Killer) filterProcesses() {
	var found bool
	oldPid := 0
	if len(k.filtered) > k.cursor {
		oldPid = k.filtered[k.cursor].Pid()
	}
	k.filtered = make([]ps.Process, 0)

	for i := 0; i < len(k.processes); i++ {
		if index := strings.Index(strings.ToUpper(k.processes[i].Executable()), strings.ToUpper(k.filter)); index != -1 || k.filter == "" {
			if !found && oldPid == k.processes[i].Pid() {
				k.cursor = len(k.filtered)
				found = true
			}

			k.filtered = append(k.filtered, k.processes[i])
		}
	}

	if !found {
		k.cursor = 0
	}
}

func (k *Killer) filterInput(r rune) (rune, bool) {
	switch r {
	case readline.CharEnter:
		k.done = true
		if len(k.filtered) > 0 {
			k.killProcess(syscall.SIGTERM)
		}
		return readline.CharInterrupt, true

	case readline.CharInterrupt:
		k.done = true
		return r, true

	case readline.CharNext:
		k.nextProcess()
		return readline.CharForward, true

	case readline.CharPrev:
		k.prevProcess()
		return readline.CharForward, true
	}
	return r, true
}
<|endoftext|>


<commit_before>package beta

import (
	"testing"

	"github.com/go-math/support/assert"
)

func TestCDF(t *testing.T) {
	points := []float64{
		-1.00, -0.85, -0.70, -0.55, -0.40, -0.25, -0.10, 0.05, 0.20, 0.35,
		0.50, 0.65, 0.80, 0.95, 1.10, 1.25, 1.40, 1.55, 1.70, 1.85, 2.00,
	}

	values := []float64{
		0.000000000000000e+00,
		1.401875000000000e-02,
		5.230000000000002e-02,
		1.095187500000000e-01,
		1.807999999999999e-01,
		2.617187500000001e-01,
		3.483000000000000e-01,
		4.370187500000001e-01,
		5.248000000000003e-01,
		6.090187500000001e-01,
		6.875000000000000e-01,
		7.585187500000001e-01,
		8.208000000000000e-01,
		8.735187499999999e-01,
		9.163000000000000e-01,
		9.492187500000000e-01,
		9.728000000000000e-01,
		9.880187500000001e-01,
		9.963000000000000e-01,
		9.995187500000000e-01,
		1.000000000000000e+00,
	}

	assert.AlmostEqual(New(2, 3, -1, 2).CDF(points), values, t)
}

func TestInvCDF(t *testing.T) {
	points := []float64{
		0.00, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50,
		0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00,
	}

	values := []float64{
		3.000000000000000e+00,
		3.025320565519104e+00,
		3.051316701949486e+00,
		3.078045554270711e+00,
		3.105572809000084e+00,
		3.133974596215561e+00,
		3.163339973465924e+00,
		3.193774225170145e+00,
		3.225403330758517e+00,
		3.258380151290432e+00,
		3.292893218813452e+00,
		3.329179606750063e+00,
		3.367544467966324e+00,
		3.408392021690038e+00,
		3.452277442494834e+00,
		3.500000000000000e+00,
		3.552786404500042e+00,
		3.612701665379257e+00,
		3.683772233983162e+00,
		3.776393202250021e+00,
		4.000000000000000e+00,
	}

	assert.AlmostEqual(New(1, 2, 3, 4).InvCDF(points), values, t)
}
<commit_msg>Added benchmarks for beta.CDF and InvCDF<commit_after>package beta

import (
	"testing"

	"github.com/go-math/prob/dist/uniform"
	"github.com/go-math/support/assert"
)

func TestCDF(t *testing.T) {
	points := []float64{
		-1.00, -0.85, -0.70, -0.55, -0.40, -0.25, -0.10, 0.05, 0.20, 0.35,
		0.50, 0.65, 0.80, 0.95, 1.10, 1.25, 1.40, 1.55, 1.70, 1.85, 2.00,
	}

	values := []float64{
		0.000000000000000e+00,
		1.401875000000000e-02,
		5.230000000000002e-02,
		1.095187500000000e-01,
		1.807999999999999e-01,
		2.617187500000001e-01,
		3.483000000000000e-01,
		4.370187500000001e-01,
		5.248000000000003e-01,
		6.090187500000001e-01,
		6.875000000000000e-01,
		7.585187500000001e-01,
		8.208000000000000e-01,
		8.735187499999999e-01,
		9.163000000000000e-01,
		9.492187500000000e-01,
		9.728000000000000e-01,
		9.880187500000001e-01,
		9.963000000000000e-01,
		9.995187500000000e-01,
		1.000000000000000e+00,
	}

	assert.AlmostEqual(New(2, 3, -1, 2).CDF(points), values, t)
}

func TestInvCDF(t *testing.T) {
	points := []float64{
		0.00, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50,
		0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.00,
	}

	values := []float64{
		3.000000000000000e+00,
		3.025320565519104e+00,
		3.051316701949486e+00,
		3.078045554270711e+00,
		3.105572809000084e+00,
		3.133974596215561e+00,
		3.163339973465924e+00,
		3.193774225170145e+00,
		3.225403330758517e+00,
		3.258380151290432e+00,
		3.292893218813452e+00,
		3.329179606750063e+00,
		3.367544467966324e+00,
		3.408392021690038e+00,
		3.452277442494834e+00,
		3.500000000000000e+00,
		3.552786404500042e+00,
		3.612701665379257e+00,
		3.683772233983162e+00,
		3.776393202250021e+00,
		4.000000000000000e+00,
	}

	assert.AlmostEqual(New(1, 2, 3, 4).InvCDF(points), values, t)
}

func BenchmarkCDF(b *testing.B) {
	dist := New(1, 2, 0, 1)
	points := uniform.New(0, 1).Sample(1000)

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		dist.CDF(points)
	}
}

func BenchmarkInvCDF(b *testing.B) {
	dist := New(1, 2, 0, 1)
	points := uniform.New(0, 1).Sample(1000)

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		dist.InvCDF(points)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2021 The cert-manager Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package admission

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"sort"

	"gomodules.xyz/jsonpatch/v2"
	admissionv1 "k8s.io/api/admission/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	apijson "k8s.io/apimachinery/pkg/runtime/serializer/json"

	"github.com/jetstack/cert-manager/pkg/webhook/handlers"
)

// RequestHandler is an implementation of the webhook's request handling that
// invokes a validating and/or mutating admission plugin (or chain of plugins).
//
// All runtime.Objects passed to the mutation and validation handlers will be in
// their internal versions to make handling multiple API versions easier.
//
// During mutation, objects will be decoded using the scheme provided during the
// NewRequestHandler call. This scheme will also be used to invoke defaulting functions
// when the object is decoded.
// This means that all resources passed to mutating admission plugins will have default
// values applied before converting them into the internal version.
type RequestHandler struct {
	scheme *runtime.Scheme

	// codecFactory used to create encoders and decoders
	codecFactory serializer.CodecFactory

	// serializer used to write resources as JSON after mutation to determine
	// the final jsonpatch for resources
	serializer *apijson.Serializer

	// decoder used to decode & convert resources in AdmissionRequests into
	// their internal versions
	decoder runtime.Decoder

	validator ValidationInterface
	mutator   MutationInterface
}

// NewRequestHandler will construct a new request handler using the given scheme for
// conversion & defaulting. Either validator or mutator can be nil, and if so no
// action will be taken.
func NewRequestHandler(scheme *runtime.Scheme, validator ValidationInterface, mutator MutationInterface) *RequestHandler {
	cf := serializer.NewCodecFactory(scheme)
	return &RequestHandler{
		scheme:       scheme,
		codecFactory: cf,
		serializer:   apijson.NewSerializerWithOptions(apijson.DefaultMetaFactory, scheme, scheme, apijson.SerializerOptions{}),
		decoder:      cf.UniversalDecoder(),
		validator:    validator,
		mutator:      mutator,
	}
}

var _ handlers.ValidatingAdmissionHook = &RequestHandler{}
var _ handlers.MutatingAdmissionHook = &RequestHandler{}

// Validate will decode the Object (and OldObject, if set) in the AdmissionRequest into the
// internal API version.
// It will then invoke the validation handler to build a list of warning messages and any
// errors generated during the admission chain.
func (rh *RequestHandler) Validate(ctx context.Context, admissionSpec *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse {
	status := &admissionv1.AdmissionResponse{}
	status.UID = admissionSpec.UID
	// short-path if there is no validator actually registered or the handler does not handle this operation.
	if rh.validator == nil || !rh.validator.Handles(admissionSpec.Operation) {
		status.Allowed = true
		return status
	}

	// decode new version of object
	obj, _, err := rh.decoder.Decode(admissionSpec.Object.Raw, nil, nil)
	if err != nil {
		return badRequestError(status, err)
	}

	// attempt to decode old object
	var oldObj runtime.Object
	if len(admissionSpec.OldObject.Raw) > 0 {
		oldObj, _, err = rh.decoder.Decode(admissionSpec.OldObject.Raw, nil, nil)
		if err != nil {
			return badRequestError(status, err)
		}
	}

	warnings, err := rh.validator.Validate(ctx, *admissionSpec, oldObj, obj)
	status.Warnings = warnings

	// return with allowed = false if any errors occurred
	if err != nil {
		status.Allowed = false
		status.Result = &metav1.Status{
			Status: metav1.StatusFailure, Code: http.StatusNotAcceptable, Reason: metav1.StatusReasonNotAcceptable,
			Message: err.Error(),
		}
		return status
	}
	status.Allowed = true
	return status
}

func (rh *RequestHandler) Mutate(ctx context.Context, admissionSpec *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse {
	status := &admissionv1.AdmissionResponse{}
	status.UID = admissionSpec.UID
	status.Allowed = true
	// short-path if there is no mutator actually registered
	// we still continue if the mutator does not handle the resource so scheme-registered
	// defaulting functions are still run against the object.
	if rh.mutator == nil {
		status.Allowed = true
		return status
	}

	// If the resource submitted to the webhook is in a different version to the request version,
	// we must take special steps to ensure the correct defaults are applied to the resource (as
	// defaults are applied by the decoder when the resource is decoded in the version of the
	// encoded resource).
	obj, errResponse := rh.decodeRequestObject(status, admissionSpec.Kind, *admissionSpec.RequestKind, admissionSpec.Object.Raw)
	if errResponse != nil {
		return errResponse
	}

	if rh.mutator.Handles(admissionSpec.Operation) {
		if err := rh.mutator.Mutate(ctx, *admissionSpec, obj); err != nil {
			return internalServerError(status, err)
		}
	}

	// Convert the object into the original version that was submitted to the webhook
	// before generating the patch.
	outputGroupVersioner := runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: admissionSpec.Kind.Group, Version: admissionSpec.Kind.Version})
	finalObject, err := rh.scheme.ConvertToVersion(obj, outputGroupVersioner)
	if err != nil {
		return internalServerError(status, err)
	}

	patch, err := rh.createMutatePatch(admissionSpec, finalObject)
	if err != nil {
		return internalServerError(status, err)
	}

	patchType := admissionv1.PatchTypeJSONPatch
	status.PatchType = &patchType
	status.Patch = patch

	return status
}

// decodeRequestObject will decode the given 'bytes' into the internal API version.
// It will apply defaults using the 'defaultsInGVK', regardless of what API version
// the encoded bytes are in.
func (rh *RequestHandler) decodeRequestObject(status *admissionv1.AdmissionResponse, objectGVK, defaultInGVK metav1.GroupVersionKind, bytes []byte) (runtime.Object, *admissionv1.AdmissionResponse) {
	if objectGVK == defaultInGVK {
		obj, _, err := rh.decoder.Decode(bytes, nil, nil)
		if err != nil {
			return nil, badRequestError(status, err)
		}
		return obj, nil
	}

	// First, use the UniversalDeserializer to decode the bytes (which does not perform
	// conversion or defaulting).
	encodedObj, _, err := rh.codecFactory.UniversalDeserializer().Decode(bytes, nil, nil)
	if err != nil {
		return nil, badRequestError(status, err)
	}

	// Then convert into the internal version of the object
	internalObj, err := rh.scheme.ConvertToVersion(encodedObj, runtime.InternalGroupVersioner)
	if err != nil {
		return nil, internalServerError(status, err)
	}

	// Now convert into the request version so we can apply the appropriate defaults
	requestGroupVersioner := runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: defaultInGVK.Group, Version: defaultInGVK.Version})
	requestObj, err := rh.scheme.ConvertToVersion(internalObj, requestGroupVersioner)
	if err != nil {
		return nil, internalServerError(status, err)
	}

	// At last, apply defaults in the request API version
	rh.scheme.Default(requestObj)

	// Finally, convert the resource back to the internal version so regular admission can proceed
	obj, err := rh.scheme.ConvertToVersion(requestObj, runtime.InternalGroupVersioner)
	if err != nil {
		return nil, internalServerError(status, err)
	}

	return obj, nil
}

func badRequestError(status *admissionv1.AdmissionResponse, err error) *admissionv1.AdmissionResponse {
	status.Allowed = false
	status.Result = &metav1.Status{
		Status: metav1.StatusFailure, Code: http.StatusBadRequest, Reason: metav1.StatusReasonBadRequest,
		Message: err.Error(),
	}
	return status
}

func internalServerError(status *admissionv1.AdmissionResponse, err error) *admissionv1.AdmissionResponse {
	status.Allowed = false
	status.Result = &metav1.Status{
		Status: metav1.StatusFailure, Code: http.StatusInternalServerError, Reason: metav1.StatusReasonInternalError,
		Message: err.Error(),
	}
	return status
}

// createMutatePatch will generate a JSON patch based upon the given original
// raw object, and the mutated typed object.
func (rh *RequestHandler) createMutatePatch(req *admissionv1.AdmissionRequest, obj runtime.Object) ([]byte, error) {
	var buf bytes.Buffer

	encoder := rh.codecFactory.EncoderForVersion(rh.serializer, schema.GroupVersion{Group: req.Kind.Group, Version: req.Kind.Version})
	if err := encoder.Encode(obj, &buf); err != nil {
		return nil, fmt.Errorf("failed to encode object after mutation: %s", err)
	}

	ops, err := jsonpatch.CreatePatch(req.Object.Raw, buf.Bytes())
	if err != nil {
		return nil, fmt.Errorf("failed to set mutation patch: %s", err)
	}

	sortOps(ops)

	patch, err := json.Marshal(ops)
	if err != nil {
		return nil, fmt.Errorf("failed to generate json patch: %s", err)
	}

	return patch, nil
}

func sortOps(ops []jsonpatch.JsonPatchOperation) {
	sort.Slice(ops, func(i, j int) bool {
		return ops[i].Path < ops[j].Path
	})
}
<commit_msg>Avoid defaulting objects before validation<commit_after>/*
Copyright 2021 The cert-manager Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package admission

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"sort"

	"gomodules.xyz/jsonpatch/v2"
	admissionv1 "k8s.io/api/admission/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	apijson "k8s.io/apimachinery/pkg/runtime/serializer/json"

	"github.com/jetstack/cert-manager/pkg/webhook/handlers"
)

// RequestHandler is an implementation of the webhook's request handling that
// invokes a validating and/or mutating admission plugin (or chain of plugins).
//
// All runtime.Objects passed to the mutation and validation handlers will be in
// their internal versions to make handling multiple API versions easier.
//
// During mutation, objects will be decoded using the scheme provided during the
// NewRequestHandler call. This scheme will also be used to invoke defaulting functions
// when the object is decoded.
// This means that all resources passed to mutating admission plugins will have default
// values applied before converting them into the internal version.
type RequestHandler struct {
	scheme *runtime.Scheme

	// codecFactory used to create encoders and decoders
	codecFactory serializer.CodecFactory

	// serializer used to write resources as JSON after mutation to determine
	// the final jsonpatch for resources
	serializer *apijson.Serializer

	// decoder used to decode & convert resources in AdmissionRequests into
	// their internal versions
	decoder runtime.Decoder

	validator ValidationInterface
	mutator   MutationInterface
}

// NewRequestHandler will construct a new request handler using the given scheme for
// conversion & defaulting. Either validator or mutator can be nil, and if so no
// action will be taken.
func NewRequestHandler(scheme *runtime.Scheme, validator ValidationInterface, mutator MutationInterface) *RequestHandler {
	cf := serializer.NewCodecFactory(scheme)
	return &RequestHandler{
		scheme:       scheme,
		codecFactory: cf,
		serializer:   apijson.NewSerializerWithOptions(apijson.DefaultMetaFactory, scheme, scheme, apijson.SerializerOptions{}),
		decoder:      cf.UniversalDecoder(),
		validator:    validator,
		mutator:      mutator,
	}
}

var _ handlers.ValidatingAdmissionHook = &RequestHandler{}
var _ handlers.MutatingAdmissionHook = &RequestHandler{}

// Validate will decode the Object (and OldObject, if set) in the AdmissionRequest into the
// internal API version.
// It will then invoke the validation handler to build a list of warning messages and any
// errors generated during the admission chain.
func (rh *RequestHandler) Validate(ctx context.Context, admissionSpec *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse {
	status := &admissionv1.AdmissionResponse{}
	status.UID = admissionSpec.UID
	// short-path if there is no validator actually registered or the handler does not handle this operation.
	if rh.validator == nil || !rh.validator.Handles(admissionSpec.Operation) {
		status.Allowed = true
		return status
	}

	// decode new version of object
	obj, err := rh.deseralizeToInternalVersion(admissionSpec.Object.Raw)
	if err != nil {
		return badRequestError(status, err)
	}

	// attempt to decode old object
	var oldObj runtime.Object
	if len(admissionSpec.OldObject.Raw) > 0 {
		oldObj, err = rh.deseralizeToInternalVersion(admissionSpec.OldObject.Raw)
		if err != nil {
			return badRequestError(status, err)
		}
	}

	warnings, err := rh.validator.Validate(ctx, *admissionSpec, oldObj, obj)
	status.Warnings = warnings

	// return with allowed = false if any errors occurred
	if err != nil {
		status.Allowed = false
		status.Result = &metav1.Status{
			Status: metav1.StatusFailure, Code: http.StatusNotAcceptable, Reason: metav1.StatusReasonNotAcceptable,
			Message: err.Error(),
		}
		return status
	}
	status.Allowed = true
	return status
}

func (rh *RequestHandler) Mutate(ctx context.Context, admissionSpec *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse {
	status := &admissionv1.AdmissionResponse{}
	status.UID = admissionSpec.UID
	status.Allowed = true
	// short-path if there is no mutator actually registered
	// we still continue if the mutator does not handle the resource so scheme-registered
	// defaulting functions are still run against the object.
	if rh.mutator == nil {
		status.Allowed = true
		return status
	}

	// If the resource submitted to the webhook is in a different version to the request version,
	// we must take special steps to ensure the correct defaults are applied to the resource (as
	// defaults are applied by the decoder when the resource is decoded in the version of the
	// encoded resource).
	obj, errResponse := rh.decodeRequestObject(status, admissionSpec.Kind, *admissionSpec.RequestKind, admissionSpec.Object.Raw)
	if errResponse != nil {
		return errResponse
	}

	if rh.mutator.Handles(admissionSpec.Operation) {
		if err := rh.mutator.Mutate(ctx, *admissionSpec, obj); err != nil {
			return internalServerError(status, err)
		}
	}

	// Convert the object into the original version that was submitted to the webhook
	// before generating the patch.
	outputGroupVersioner := runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: admissionSpec.Kind.Group, Version: admissionSpec.Kind.Version})
	finalObject, err := rh.scheme.ConvertToVersion(obj, outputGroupVersioner)
	if err != nil {
		return internalServerError(status, err)
	}

	patch, err := rh.createMutatePatch(admissionSpec, finalObject)
	if err != nil {
		return internalServerError(status, err)
	}

	patchType := admissionv1.PatchTypeJSONPatch
	status.PatchType = &patchType
	status.Patch = patch

	return status
}

// decodeRequestObject will decode the given 'bytes' into the internal API version.
// It will apply defaults using the 'defaultsInGVK', regardless of what API version
// the encoded bytes are in.
func (rh *RequestHandler) decodeRequestObject(status *admissionv1.AdmissionResponse, objectGVK, defaultInGVK metav1.GroupVersionKind, bytes []byte) (runtime.Object, *admissionv1.AdmissionResponse) {
	if objectGVK == defaultInGVK {
		obj, _, err := rh.decoder.Decode(bytes, nil, nil)
		if err != nil {
			return nil, badRequestError(status, err)
		}
		return obj, nil
	}

	// Decode the object to the internal version without defaulting
	internalObj, err := rh.deseralizeToInternalVersion(bytes)
	if err != nil {
		return nil, badRequestError(status, err)
	}

	// Now convert into the request version so we can apply the appropriate defaults
	requestGroupVersioner := runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: defaultInGVK.Group, Version: defaultInGVK.Version})
	requestObj, err := rh.scheme.ConvertToVersion(internalObj, requestGroupVersioner)
	if err != nil {
		return nil, internalServerError(status, err)
	}

	// At last, apply defaults in the request API version
	rh.scheme.Default(requestObj)

	// Finally, convert the resource back to the internal version so regular admission can proceed
	obj, err := rh.scheme.ConvertToVersion(requestObj, runtime.InternalGroupVersioner)
	if err != nil {
		return nil, internalServerError(status, err)
	}

	return obj, nil
}

// deseralizeToInternalVersion will decode an object into its internal version
// without applying default values.
func (rh *RequestHandler) deseralizeToInternalVersion(bytes []byte) (runtime.Object, error) {
	// First, use the UniversalDeserializer to decode the bytes (which does not perform
	// conversion or defaulting).
	obj, _, err := rh.codecFactory.UniversalDeserializer().Decode(bytes, nil, nil)
	if err != nil {
		return nil, err
	}

	// Then convert to the internal version
	return rh.scheme.ConvertToVersion(obj, runtime.InternalGroupVersioner)
}

func badRequestError(status *admissionv1.AdmissionResponse, err error) *admissionv1.AdmissionResponse {
	status.Allowed = false
	status.Result = &metav1.Status{
		Status: metav1.StatusFailure, Code: http.StatusBadRequest, Reason: metav1.StatusReasonBadRequest,
		Message: err.Error(),
	}
	return status
}

func internalServerError(status *admissionv1.AdmissionResponse, err error) *admissionv1.AdmissionResponse {
	status.Allowed = false
	status.Result = &metav1.Status{
		Status: metav1.StatusFailure, Code: http.StatusInternalServerError, Reason: metav1.StatusReasonInternalError,
		Message: err.Error(),
	}
	return status
}

// createMutatePatch will generate a JSON patch based upon the given original
// raw object, and the mutated typed object.
func (rh *RequestHandler) createMutatePatch(req *admissionv1.AdmissionRequest, obj runtime.Object) ([]byte, error) {
	var buf bytes.Buffer

	encoder := rh.codecFactory.EncoderForVersion(rh.serializer, schema.GroupVersion{Group: req.Kind.Group, Version: req.Kind.Version})
	if err := encoder.Encode(obj, &buf); err != nil {
		return nil, fmt.Errorf("failed to encode object after mutation: %s", err)
	}

	ops, err := jsonpatch.CreatePatch(req.Object.Raw, buf.Bytes())
	if err != nil {
		return nil, fmt.Errorf("failed to set mutation patch: %s", err)
	}

	sortOps(ops)

	patch, err := json.Marshal(ops)
	if err != nil {
		return nil, fmt.Errorf("failed to generate json patch: %s", err)
	}

	return patch, nil
}

func sortOps(ops []jsonpatch.JsonPatchOperation) {
	sort.Slice(ops, func(i, j int) bool {
		return ops[i].Path < ops[j].Path
	})
}
<|endoftext|>


<commit_before>package via

import (
	"compress/gzip"
	"errors"
	"fmt"
	"github.com/str1ngs/gurl"
	"github.com/str1ngs/util/console"
	"github.com/str1ngs/util/file"
	"github.com/str1ngs/util/json"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"sort"
)

var (
	client  = new(http.Client)
	verbose = false
	elog    = log.New(os.Stderr, "", log.Lshortfile)
	lfmt    = "%-20.20s %v\n"
	debug   = false
	expand  = os.ExpandEnv
	update  = false
	deps    = false
)

func Root(s string) {
	config.Root = s
}

func Verbose(b bool) {
	verbose = b
}

func Deps(b bool) {
	deps = b
}

func Update(b bool) {
	update = b
}

func Debug(b bool) {
	debug = b
}

func DownloadSrc(plan *Plan) (err error) {
	if file.Exists(plan.SourcePath()) && !update {
		return nil
	}
	fmt.Printf(lfmt, "download", plan.NameVersion())
	u, err := url.Parse(plan.Url)
	if err != nil {
		return err
	}
	switch u.Scheme {
	case "ftp":
		wget(cache.Sources(), plan.Url)
	case "http", "https":
		return gurl.Download(cache.Sources(), plan.Url)
	default:
		panic("unsupported")
	}
	return nil
}

// Stages the downloaded source via's cache directory
// the stage only happens once unless BuilInStage is used
func Stage(plan *Plan) (err error) {
	if plan.Url == "" || file.Exists(plan.GetStageDir()) {
		// nothing to stage
		return nil
	}
	u, err := url.Parse(plan.Url)
	if err != nil {
		elog.Println(err)
		return err
	}
	if u.Scheme == "git" {
		fmt.Println(cache.Stages())
		fmt.Println(plan.SourcePath())
		cmd := exec.Command("git", "clone", plan.SourcePath(), plan.SourceFile())
		cmd.Dir = cache.Stages()
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Run()
		goto ret
	}
	switch path.Ext(plan.SourceFile()) {
	case ".zip":
		unzip(cache.Stages(), plan.SourcePath())
	default:
		GNUUntar(cache.Stages(), plan.SourcePath())
	}
ret:
	fmt.Printf(lfmt, "patch", plan.NameVersion())
	if err := doCommands(join(cache.Stages(), plan.stageDir()), plan.Patch); err != nil {
		return err
	}
	return
}

// Calls each shell command in the plans Build field.
func Build(plan *Plan) (err error) {
	var (
		build = plan.Build
	)
	if file.Exists(plan.PackagePath()) {
		fmt.Printf("FIXME: (short flags)  package %s exists building anyways.\n", plan.PackagePath())
	}
	flags := config.Flags
	if plan.Flags != nil {
		flags = append(flags, plan.Flags...)
	}
	if !file.Exists(plan.BuildDir()) {
		os.MkdirAll(plan.BuildDir(), 0755)
	}
	// Parent plan's Build is run first this plans is added at the end.
	if plan.Inherit != "" {
		parent, _ := NewPlan(plan.Inherit)
		build = append(parent.Build, plan.Build...)
		flags = append(flags, parent.Flags...)
	}
	os.Setenv("SRCDIR", plan.GetStageDir())
	os.Setenv("Flags", expand(flags.String()))
	err = doCommands(plan.BuildDir(), build)
	if err != nil {
		es := fmt.Sprintf("%s in %s", err.Error(), plan.BuildDir())
		return errors.New(es)
	}
	return nil
}

func doCommands(dir string, cmds []string) (err error) {
	for i, j := range cmds {
		j := expand(j)
		if debug {
			elog.Println(i, j)
		}
		cmd := exec.Command("sh", "-c", j)
		cmd.Dir = dir
		cmd.Stdin = os.Stdin
		if verbose {
			cmd.Stdout = os.Stdout
		}
		cmd.Stderr = os.Stderr
		err = cmd.Run()
		if err != nil {
			elog.Printf("%s: %s\n", j, err)
			return err
		}
	}
	return nil
}

func Package(bdir string, plan *Plan) (err error) {
	var (
		pack = plan.Package
	)
	pdir := join(cache.Packages(), plan.NameVersion())
	if bdir == "" {
		bdir = join(cache.Builds(), plan.NameVersion())
	}
	if plan.BuildInStage {
		bdir = join(cache.Stages(), plan.stageDir())
	}
	if file.Exists(pdir) {
		err := os.RemoveAll(pdir)
		if err != nil {
			return err
		}
	}
	err = os.Mkdir(pdir, 0755)
	if err != nil {
		elog.Println(err)
		return err
	}
	os.Setenv("PKGDIR", pdir)
	if plan.Inherit != "" {
		parent, _ := NewPlan(plan.Inherit)
		pack = parent.Package
		pack = append(pack, plan.Package...)
	}
	err = doCommands(bdir, pack)
	if err != nil {
		return err
	}
	for _, j := range plan.SubPackages {
		sub, err := NewPlan(j)
		if err != nil {
			return err
		}
		if err = Package(bdir, sub); err != nil {
			return err
		}
	}
	return CreatePackage(plan)
	/*
		err = CreatePackage(plan)
		if err != nil {
			return err
		}
		return Sign(plan)
	*/
}

func CreatePackage(plan *Plan) (err error) {
	pfile := plan.PackagePath()
	os.MkdirAll(path.Dir(pfile), 0755)
	fd, err := os.Create(pfile)
	if err != nil {
		elog.Println(err)
		return err
	}
	defer fd.Close()
	gz := gzip.NewWriter(fd)
	defer gz.Close()
	return Tarball(gz, plan)
}

func Install(name string) (err error) {
	plan, err := NewPlan(name)
	if err != nil {
		elog.Println(name, err)
		return
	}
	fmt.Printf(lfmt, "installing", plan.Name)
	if IsInstalled(name) {
		fmt.Printf("FIXME: (short flags) package %s installed upgrading anyways.\n", plan.NameVersion())
		err := Remove(name)
		if err != nil {
			return err
		}
	}
	for _, d := range plan.Depends {
		if IsInstalled(d) {
			continue
		}
		err := Install(d)
		if err != nil {
			return err
		}
	}
	db := path.Join(config.DB.Installed(), plan.Name)
	if file.Exists(db) {
		return fmt.Errorf("%s is already installed", name)
	}
	pfile := plan.PackagePath()
	if !file.Exists(pfile) {
		//return errors.New(fmt.Sprintf("%s does not exist", pfile))
		err := gurl.Download(config.Repo+"/master", config.Binary+"/"+plan.PackageFile())
		if err != nil {
			log.Fatal(err)
		}
		//fatal(gurl.Download(config.Repo, config.Binary+"/"+plan.PackageFile()+".sig"))
	}
	/*
		err = CheckSig(pfile)
		if err != nil {
			return
		}
	*/
	man, err := ReadPackManifest(pfile)
	if err != nil {
		return err
	}
	errs := conflicts(man)
	if len(errs) > 0 {
		for _, e := range errs {
			elog.Println(e)
		}
		//return errs[0]
	}
	fd, err := os.Open(pfile)
	if err != nil {
		return
	}
	defer fd.Close()
	gz, err := gzip.NewReader(fd)
	if err != nil {
		return
	}
	defer gz.Close()
	err = Untar(config.Root, gz)
	if err != nil {
		return err
	}
	err = os.MkdirAll(db, 0755)
	if err != nil {
		elog.Println(err)
		return err
	}
	err = json.Write(man, join(db, "manifest.json"))
	if err != nil {
		return err
	}
	return PostInstall(plan)
}

func PostInstall(plan *Plan) (err error) {
	return doCommands("/", append(plan.PostInstall, config.PostInstall...))
}

func Remove(name string) (err error) {
	if !IsInstalled(name) {
		err = fmt.Errorf("%s is not installed.", name)
		elog.Println(err)
		return err
	}

	man, err := ReadManifest(name)
	if err != nil {
		return err
	}
	for _, f := range man.Files {
		fpath := join(config.Root, f)
		err = os.Remove(fpath)
		if err != nil {
			elog.Println(f, err)
		}
	}

	return os.RemoveAll(join(config.DB.Installed(), name))
}

func BuildDeps(plan *Plan) (err error) {
	deps := append(plan.Depends, plan.ManDepends...)
	for _, d := range deps {
		if IsInstalled(d) {
			continue
		}
		p, _ := NewPlan(d)
		if file.Exists(p.PackagePath()) {
			return Install(p.Name)
		}
		err := BuildDeps(p)
		if err != nil {
			panic("build deps")
			fmt.Println(err)
			return err
		}
	}
	err = BuildSteps(plan)
	if err != nil {
		return err
	}
	return Install(plan.Name)
}

// Run all of the functions required to build a package
func BuildSteps(plan *Plan) (err error) {
	if file.Exists(plan.PackageFile()) {
		return fmt.Errorf("package %s exists", plan.PackageFile())
	}
	if err := DownloadSrc(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "stage", plan.NameVersion())
	if err := Stage(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "build", plan.NameVersion())
	if err := Build(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "package", plan.NameVersion())
	if err := Package("", plan); err != nil {
		elog.Println(err)
		return err
	}
	return nil
}

var (
	rexName   = regexp.MustCompile("[A-Za-z]+")
	rexTruple = regexp.MustCompile("[0-9]+.[0-9]+.[0-9]+")
	rexDouble = regexp.MustCompile("[0-9]+.[0-9]+")
)

// Creates a new plan from a given Url
func Create(url, group string) (err error) {
	var (
		xfile   = path.Base(url)
		name    = rexName.FindString(xfile)
		truple  = rexTruple.FindString(xfile)
		double  = rexDouble.FindString(xfile)
		version string
	)
	switch {
	case truple != "":
		version = truple
	case double != "":
		version = double
	default:
		return errors.New("regex fail for " + xfile)
	}
	plan := &Plan{Name: name, Version: version, Url: url, Group: group}
	plan.Inherit = "gnu"
	if file.Exists(plan.Path()) {
		return errors.New(fmt.Sprintf("%s already exists", plan.Path()))
	}
	return plan.Save()
}

func IsInstalled(name string) bool {
	return file.Exists(join(config.DB.Installed(), name))
}

func Lint() (err error) {
	e, err := PlanFiles()
	if err != nil {
		return err
	}
	for _, j := range e {
		plan, err := ReadPath(j)
		if err != nil {
			err = fmt.Errorf("%s %s", j, err)
			elog.Println(err)
			return err
		}
		// If Group is empty, we can set it
		if plan.Group == "" {
			plan.Group = baseDir(j)
		}
		if verbose {
			console.Println("lint", plan.NameVersion(), plan.Package)
		}
		sort.Strings(plan.SubPackages)
		sort.Strings(plan.Flags)
		sort.Strings(plan.Remove)
		sort.Strings(plan.Depends)
		err = plan.Save()
		if err != nil {
			elog.Println(err)
			return err
		}
	}
	console.Flush()
	return nil
}

func fatal(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
func Clean(name string) error {
	plan, err := NewPlan(name)
	if err != nil {
		return err
	}
	fmt.Printf(lfmt, "clean", plan.NameVersion())
	dir := join(cache.Builds(), plan.NameVersion())
	if err = os.RemoveAll(dir); err != nil {
		return err
	}

	dir = join(cache.Stages(), plan.stageDir())
	return os.RemoveAll(dir)
}

func PlanFiles() ([]string, error) {
	return filepath.Glob(join(config.Plans, "*", "*.json"))
}

func conflicts(man *Plan) (errs []error) {
	for _, f := range man.Files {
		fpath := join(config.Root, f)
		if file.Exists(fpath) {
			errs = append(errs, fmt.Errorf("%s already exists.", f))
		}
	}
	return errs
}

func GetConfig() *Config {
	return config
}
<commit_msg>log BuildDeps errors<commit_after>package via

import (
	"compress/gzip"
	"errors"
	"fmt"
	"github.com/str1ngs/gurl"
	"github.com/str1ngs/util/console"
	"github.com/str1ngs/util/file"
	"github.com/str1ngs/util/json"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"sort"
)

var (
	client  = new(http.Client)
	verbose = false
	elog    = log.New(os.Stderr, "", log.Lshortfile)
	lfmt    = "%-20.20s %v\n"
	debug   = false
	expand  = os.ExpandEnv
	update  = false
	deps    = false
)

func Root(s string) {
	config.Root = s
}

func Verbose(b bool) {
	verbose = b
}

func Deps(b bool) {
	deps = b
}

func Update(b bool) {
	update = b
}

func Debug(b bool) {
	debug = b
}

func DownloadSrc(plan *Plan) (err error) {
	if file.Exists(plan.SourcePath()) && !update {
		return nil
	}
	fmt.Printf(lfmt, "download", plan.NameVersion())
	u, err := url.Parse(plan.Url)
	if err != nil {
		return err
	}
	switch u.Scheme {
	case "ftp":
		wget(cache.Sources(), plan.Url)
	case "http", "https":
		return gurl.Download(cache.Sources(), plan.Url)
	default:
		panic("unsupported")
	}
	return nil
}

// Stages the downloaded source via's cache directory
// the stage only happens once unless BuilInStage is used
func Stage(plan *Plan) (err error) {
	if plan.Url == "" || file.Exists(plan.GetStageDir()) {
		// nothing to stage
		return nil
	}
	u, err := url.Parse(plan.Url)
	if err != nil {
		elog.Println(err)
		return err
	}
	if u.Scheme == "git" {
		fmt.Println(cache.Stages())
		fmt.Println(plan.SourcePath())
		cmd := exec.Command("git", "clone", plan.SourcePath(), plan.SourceFile())
		cmd.Dir = cache.Stages()
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Run()
		goto ret
	}
	switch path.Ext(plan.SourceFile()) {
	case ".zip":
		unzip(cache.Stages(), plan.SourcePath())
	default:
		GNUUntar(cache.Stages(), plan.SourcePath())
	}
ret:
	fmt.Printf(lfmt, "patch", plan.NameVersion())
	if err := doCommands(join(cache.Stages(), plan.stageDir()), plan.Patch); err != nil {
		return err
	}
	return
}

// Calls each shell command in the plans Build field.
func Build(plan *Plan) (err error) {
	var (
		build = plan.Build
	)
	if file.Exists(plan.PackagePath()) {
		fmt.Printf("FIXME: (short flags)  package %s exists building anyways.\n", plan.PackagePath())
	}
	flags := config.Flags
	if plan.Flags != nil {
		flags = append(flags, plan.Flags...)
	}
	if !file.Exists(plan.BuildDir()) {
		os.MkdirAll(plan.BuildDir(), 0755)
	}
	// Parent plan's Build is run first this plans is added at the end.
	if plan.Inherit != "" {
		parent, _ := NewPlan(plan.Inherit)
		build = append(parent.Build, plan.Build...)
		flags = append(flags, parent.Flags...)
	}
	os.Setenv("SRCDIR", plan.GetStageDir())
	os.Setenv("Flags", expand(flags.String()))
	err = doCommands(plan.BuildDir(), build)
	if err != nil {
		es := fmt.Sprintf("%s in %s", err.Error(), plan.BuildDir())
		return errors.New(es)
	}
	return nil
}

func doCommands(dir string, cmds []string) (err error) {
	for i, j := range cmds {
		j := expand(j)
		if debug {
			elog.Println(i, j)
		}
		cmd := exec.Command("sh", "-c", j)
		cmd.Dir = dir
		cmd.Stdin = os.Stdin
		if verbose {
			cmd.Stdout = os.Stdout
		}
		cmd.Stderr = os.Stderr
		err = cmd.Run()
		if err != nil {
			elog.Printf("%s: %s\n", j, err)
			return err
		}
	}
	return nil
}

func Package(bdir string, plan *Plan) (err error) {
	var (
		pack = plan.Package
	)
	pdir := join(cache.Packages(), plan.NameVersion())
	if bdir == "" {
		bdir = join(cache.Builds(), plan.NameVersion())
	}
	if plan.BuildInStage {
		bdir = join(cache.Stages(), plan.stageDir())
	}
	if file.Exists(pdir) {
		err := os.RemoveAll(pdir)
		if err != nil {
			return err
		}
	}
	err = os.Mkdir(pdir, 0755)
	if err != nil {
		elog.Println(err)
		return err
	}
	os.Setenv("PKGDIR", pdir)
	if plan.Inherit != "" {
		parent, _ := NewPlan(plan.Inherit)
		pack = parent.Package
		pack = append(pack, plan.Package...)
	}
	err = doCommands(bdir, pack)
	if err != nil {
		return err
	}
	for _, j := range plan.SubPackages {
		sub, err := NewPlan(j)
		if err != nil {
			return err
		}
		if err = Package(bdir, sub); err != nil {
			return err
		}
	}
	return CreatePackage(plan)
	/*
		err = CreatePackage(plan)
		if err != nil {
			return err
		}
		return Sign(plan)
	*/
}

func CreatePackage(plan *Plan) (err error) {
	pfile := plan.PackagePath()
	os.MkdirAll(path.Dir(pfile), 0755)
	fd, err := os.Create(pfile)
	if err != nil {
		elog.Println(err)
		return err
	}
	defer fd.Close()
	gz := gzip.NewWriter(fd)
	defer gz.Close()
	return Tarball(gz, plan)
}

func Install(name string) (err error) {
	plan, err := NewPlan(name)
	if err != nil {
		elog.Println(name, err)
		return
	}
	fmt.Printf(lfmt, "installing", plan.Name)
	if IsInstalled(name) {
		fmt.Printf("FIXME: (short flags) package %s installed upgrading anyways.\n", plan.NameVersion())
		err := Remove(name)
		if err != nil {
			return err
		}
	}
	for _, d := range plan.Depends {
		if IsInstalled(d) {
			continue
		}
		err := Install(d)
		if err != nil {
			return err
		}
	}
	db := path.Join(config.DB.Installed(), plan.Name)
	if file.Exists(db) {
		return fmt.Errorf("%s is already installed", name)
	}
	pfile := plan.PackagePath()
	if !file.Exists(pfile) {
		//return errors.New(fmt.Sprintf("%s does not exist", pfile))
		err := gurl.Download(config.Repo+"/master", config.Binary+"/"+plan.PackageFile())
		if err != nil {
			log.Fatal(err)
		}
		//fatal(gurl.Download(config.Repo, config.Binary+"/"+plan.PackageFile()+".sig"))
	}
	/*
		err = CheckSig(pfile)
		if err != nil {
			return
		}
	*/
	man, err := ReadPackManifest(pfile)
	if err != nil {
		return err
	}
	errs := conflicts(man)
	if len(errs) > 0 {
		for _, e := range errs {
			elog.Println(e)
		}
		//return errs[0]
	}
	fd, err := os.Open(pfile)
	if err != nil {
		return
	}
	defer fd.Close()
	gz, err := gzip.NewReader(fd)
	if err != nil {
		return
	}
	defer gz.Close()
	err = Untar(config.Root, gz)
	if err != nil {
		return err
	}
	err = os.MkdirAll(db, 0755)
	if err != nil {
		elog.Println(err)
		return err
	}
	err = json.Write(man, join(db, "manifest.json"))
	if err != nil {
		return err
	}
	return PostInstall(plan)
}

func PostInstall(plan *Plan) (err error) {
	return doCommands("/", append(plan.PostInstall, config.PostInstall...))
}

func Remove(name string) (err error) {
	if !IsInstalled(name) {
		err = fmt.Errorf("%s is not installed.", name)
		elog.Println(err)
		return err
	}

	man, err := ReadManifest(name)
	if err != nil {
		return err
	}
	for _, f := range man.Files {
		fpath := join(config.Root, f)
		err = os.Remove(fpath)
		if err != nil {
			elog.Println(f, err)
		}
	}

	return os.RemoveAll(join(config.DB.Installed(), name))
}

func BuildDeps(plan *Plan) (err error) {
	deps := append(plan.Depends, plan.ManDepends...)
	for _, d := range deps {
		if IsInstalled(d) {
			continue
		}
		fmt.Println("building", d, "for", plan.NameVersion())
		p, _ := NewPlan(d)
		if file.Exists(p.PackagePath()) {
			return Install(p.Name)
		}
		err := BuildDeps(p)
		if err != nil {
			elog.Println(err)
			return err
		}
	}
	err = BuildSteps(plan)
	if err != nil {
		return err
	}
	return Install(plan.Name)
}

// Run all of the functions required to build a package
func BuildSteps(plan *Plan) (err error) {
	if file.Exists(plan.PackageFile()) {
		return fmt.Errorf("package %s exists", plan.PackageFile())
	}
	if err := DownloadSrc(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "stage", plan.NameVersion())
	if err := Stage(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "build", plan.NameVersion())
	if err := Build(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "package", plan.NameVersion())
	if err := Package("", plan); err != nil {
		elog.Println(err)
		return err
	}
	return nil
}

var (
	rexName   = regexp.MustCompile("[A-Za-z]+")
	rexTruple = regexp.MustCompile("[0-9]+.[0-9]+.[0-9]+")
	rexDouble = regexp.MustCompile("[0-9]+.[0-9]+")
)

// Creates a new plan from a given Url
func Create(url, group string) (err error) {
	var (
		xfile   = path.Base(url)
		name    = rexName.FindString(xfile)
		truple  = rexTruple.FindString(xfile)
		double  = rexDouble.FindString(xfile)
		version string
	)
	switch {
	case truple != "":
		version = truple
	case double != "":
		version = double
	default:
		return errors.New("regex fail for " + xfile)
	}
	plan := &Plan{Name: name, Version: version, Url: url, Group: group}
	plan.Inherit = "gnu"
	if file.Exists(plan.Path()) {
		return errors.New(fmt.Sprintf("%s already exists", plan.Path()))
	}
	return plan.Save()
}

func IsInstalled(name string) bool {
	return file.Exists(join(config.DB.Installed(), name))
}

func Lint() (err error) {
	e, err := PlanFiles()
	if err != nil {
		return err
	}
	for _, j := range e {
		plan, err := ReadPath(j)
		if err != nil {
			err = fmt.Errorf("%s %s", j, err)
			elog.Println(err)
			return err
		}
		// If Group is empty, we can set it
		if plan.Group == "" {
			plan.Group = baseDir(j)
		}
		if verbose {
			console.Println("lint", plan.NameVersion(), plan.Package)
		}
		sort.Strings(plan.SubPackages)
		sort.Strings(plan.Flags)
		sort.Strings(plan.Remove)
		sort.Strings(plan.Depends)
		err = plan.Save()
		if err != nil {
			elog.Println(err)
			return err
		}
	}
	console.Flush()
	return nil
}

func fatal(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
func Clean(name string) error {
	plan, err := NewPlan(name)
	if err != nil {
		return err
	}
	fmt.Printf(lfmt, "clean", plan.NameVersion())
	dir := join(cache.Builds(), plan.NameVersion())
	if err = os.RemoveAll(dir); err != nil {
		return err
	}

	dir = join(cache.Stages(), plan.stageDir())
	return os.RemoveAll(dir)
}

func PlanFiles() ([]string, error) {
	return filepath.Glob(join(config.Plans, "*", "*.json"))
}

func conflicts(man *Plan) (errs []error) {
	for _, f := range man.Files {
		fpath := join(config.Root, f)
		if file.Exists(fpath) {
			errs = append(errs, fmt.Errorf("%s already exists.", f))
		}
	}
	return errs
}

func GetConfig() *Config {
	return config
}
<|endoftext|>