<commit_before>package acme

import (
	"context"
	"errors"
	"fmt"

	"github.com/golang/glog"
	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
	"github.com/jetstack/cert-manager/pkg/issuer/acme/client"
	"github.com/jetstack/cert-manager/third_party/crypto/acme"
)

const (
	successObtainedAuthorization = "ObtainAuthorization"
	reasonPresentChallenge       = "PresentChallenge"
	reasonSelfCheck              = "SelfCheck"
	errorGetACMEAccount          = "ErrGetACMEAccount"
	errorCheckAuthorization      = "ErrCheckAuthorization"
	errorObtainAuthorization     = "ErrObtainAuthorization"
	errorInvalidConfig           = "ErrInvalidConfig"

	messageObtainedAuthorization    = "Obtained authorization for domain %s"
	messagePresentChallenge         = "Presenting %s challenge for domain %s"
	messageSelfCheck                = "Performing self-check for domain %s"
	messageErrorGetACMEAccount      = "Error getting ACME account: "
	messageErrorCheckAuthorization  = "Error checking ACME domain validation: "
	messageErrorObtainAuthorization = "Error obtaining ACME domain authorization: "
	messageErrorMissingConfig       = "certificate.spec.acme must be specified"
)

// Prepare will ensure the issuer has been initialised and is ready to issue
// certificates for the domains listed on the Certificate resource.
//
// It will send the appropriate Letsencrypt authorizations, and complete
// challenge requests if neccessary.
func (a *Acme) Prepare(ctx context.Context, crt *v1alpha1.Certificate) error {
	if crt.Spec.ACME == nil {
		crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorInvalidConfig, messageErrorMissingConfig)
		return fmt.Errorf(messageErrorMissingConfig)
	}

	glog.V(4).Infof("Getting ACME client")
	// obtain an ACME client
	cl, err := a.acmeClient()
	if err != nil {
		return err
	}

	order, err := a.getOrCreateOrder(ctx, cl, crt)
	if err != nil {
		return err
	}

	allAuthorizations, err := getAuthorizations(ctx, cl, order.Authorizations...)
	if err != nil {
		s := messageErrorCheckAuthorization + err.Error()
		crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorCheckAuthorization, s)
		return errors.New(s)
	}

	failed, pending, valid := partitionAuthorizations(allAuthorizations...)
	glog.Infof("Authorizations for Certificate %q: %d failed, %d pending, %d valid", crt.Name, len(failed), len(pending), len(valid))
	toCleanup := append(failed, valid...)
	for _, auth := range toCleanup {
		err := a.cleanupAuthorization(ctx, cl, crt, auth)
		if err != nil {
			return err
		}
	}

	if len(failed) > 0 {
		glog.Infof("Found %d failed authorizations. Cleaning up pending authorizations and clearing order URL")
		// clear the order url to trigger a new order to be created
		crt.Status.ACMEStatus().Order.URL = ""
		// clean up pending authorizations
		for _, auth := range pending {
			err := a.cleanupAuthorization(ctx, cl, crt, auth)
			if err != nil {
				// TODO: clean up remaining authorizations if one fails
				return err
			}
		}
		// TODO: pretty-print the list of failed authorizations
		s := fmt.Sprintf("Error obtaining validations for domains %v", failed)
		crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorCheckAuthorization, s)
		return errors.New(s)
	}

	// all validations have been obtained
	if len(pending) == 0 {
		glog.Infof("No more pending authorizations remaining - challenge verification complete")
		return nil
	}

	var failingSelfChecks []string
	for _, auth := range pending {
		selfCheckPassed, challenge, err := a.presentAuthorization(ctx, cl, crt, auth)
		if err != nil {
			return err
		}
		if selfCheckPassed {
			glog.Infof("Self check passed for domain %q", auth.Identifier.Value)
			err := a.acceptChallenge(ctx, cl, auth, challenge)
			if err != nil {
				return err
			}
		} else {
			glog.Infof("Self check failed for domain %q", auth.Identifier.Value)
			failingSelfChecks = append(failingSelfChecks, auth.Identifier.Value)
		}
	}

	if len(failingSelfChecks) > 0 {
		return fmt.Errorf("self check failed for domains: %v", failingSelfChecks)
	}

	return nil
}

// getOrCreateOrder will attempt to retrieve an existing order for a
// certificate using the status.acme.order.url field.
//
// - if it's not set, it will call createOrder and return
//
// - if it is set, and the order is not in an error state, it will be returned
//
// - if it is set, and the order is in an invalid state, an event will be
//   logged and createOrder will be called
//
// - if an error occurs obtaining the order, it will be returned
func (a *Acme) getOrCreateOrder(ctx context.Context, cl client.Interface, crt *v1alpha1.Certificate) (*acme.Order, error) {
	orderURL := crt.Status.ACMEStatus().Order.URL
	glog.Infof("Checking existing order URL %q", orderURL)
	var err error
	var order *acme.Order
	// if the existing order URL is blank, create a new order
	if orderURL == "" {
		glog.Infof("Existing order URL not set. Creating new order.")
		return a.createOrder(ctx, cl, crt)
	}

	glog.Infof("Requesting order details for %q from ACME server", crt.Name)
	order, err = cl.GetOrder(ctx, orderURL)

	if err != nil {
		glog.Infof("Error requesting existing order details for %q from ACME server: %v", crt.Name, err)
		return nil, err
	}

	if !orderIsValidForCertificate(order, crt) {
		glog.Infof("Existing order is not valid for requested DNS names. Creating new order.")
		return a.createOrder(ctx, cl, crt)
	}

	glog.Infof("Order %q status is %q", order.URL, order.Status)
	switch order.Status {
	// create a new order if the old one is invalid
	case acme.StatusDeactivated, acme.StatusInvalid, acme.StatusRevoked:
		// TODO: log an event
		glog.Infof("Existing order is in state %q - creating a new order.", order.Status)
		return a.createOrder(ctx, cl, crt)
	case acme.StatusValid, acme.StatusPending, acme.StatusProcessing:
		return order, nil
	}

	return nil, fmt.Errorf("order %q unknown status: %q", order.URL, order.Status)
}

func (a *Acme) acceptChallenge(ctx context.Context, cl client.Interface, auth *acme.Authorization, challenge *acme.Challenge) error {
	glog.Infof("Accepting challenge for domain %q", auth.Identifier.Value)
	var err error
	challenge, err = cl.AcceptChallenge(ctx, challenge)
	if err != nil {
		return err
	}

	glog.Infof("Waiting for authorization for domain %q", auth.Identifier.Value)
	authorization, err := cl.WaitAuthorization(ctx, auth.URL)
	if err != nil {
		return err
	}

	if authorization.Status != acme.StatusValid {
		return fmt.Errorf("expected acme domain authorization status for %q to be valid, but it is %q", authorization.Identifier.Value, authorization.Status)
	}

	glog.Infof("Successfully authorized domain %q", auth.Identifier.Value)

	return nil
}

// presentAuthorization will present the challenge required for the given
// authorization using the supplied certificate configuration.
// If ths authorization is already presented, it will return no error.
// If the self-check for the authorization has passed, it will return true.
// Otherwise it will return false.
func (a *Acme) presentAuthorization(ctx context.Context, cl client.Interface, crt *v1alpha1.Certificate, auth *acme.Authorization) (bool, *acme.Challenge, error) {
	glog.Infof("Presenting challenge for domain %q", auth.Identifier.Value)
	challenge, err := a.challengeForAuthorization(cl, crt, auth)
	if err != nil {
		// TODO: handle error properly
		return false, nil, err
	}
	domain := auth.Identifier.Value
	token := challenge.Token
	key, err := keyForChallenge(cl, challenge)
	if err != nil {
		return false, challenge, err
	}
	solver, err := a.solverFor(challenge.Type)
	if err != nil {
		// TODO: handle error properly
		return false, challenge, err
	}
	err = solver.Present(ctx, crt, domain, token, key)
	if err != nil {
		// TODO: handle error properly
		return false, challenge, err
	}
	glog.Infof("Performing check to ensure challenge has propagated")
	ok, err := solver.Check(domain, token, key)
	if err != nil {
		return false, challenge, err
	}
	return ok, challenge, nil
}

// cleanupAuthorization will clean up a given authorization.
// To do this, it first determines the challenge type to use for the
// authorization based on the Issuer and Certificate configuration.
// It then calls CleanUp on the appropriate Solver.
// CleanUp will clean up any remaining resources left over from attempting to
// solve the given challenge.
// If a valid challenge type is not configured, cleanupAuthorization will
// return an error.
func (a *Acme) cleanupAuthorization(ctx context.Context, cl client.Interface, crt *v1alpha1.Certificate, auth *acme.Authorization) error {
	glog.Infof("Cleaning up authorization for %q", auth.Identifier.Value)
	challenge, err := a.challengeForAuthorization(cl, crt, auth)
	if err != nil {
		return err
	}
	domain := auth.Identifier.Value
	token := challenge.Token
	key, err := keyForChallenge(cl, challenge)
	if err != nil {
		return err
	}

	solver, err := a.solverFor(challenge.Type)
	if err != nil {
		return err
	}

	return solver.CleanUp(ctx, crt, domain, token, key)
}

// keyForChallenge will return the key to use for solving a given acme
// challenge.
// Only http-01 and dns-01 challenges are supported.
// An error will be returned if obtaining the key fails, or the challenge type
// is unsupported.
func keyForChallenge(cl client.Interface, challenge *acme.Challenge) (string, error) {
	var err error
	switch challenge.Type {
	case "http-01":
		return cl.HTTP01ChallengeResponse(challenge.Token)
	case "dns-01":
		return cl.DNS01ChallengeRecord(challenge.Token)
	default:
		err = fmt.Errorf("unsupported challenge type %s", challenge.Type)
	}
	return "", err
}

// getAuthorizations will query the ACME server for the Authorization resources
// for the given list of authorization URLs using the given ACME client.
// It will return an error if obtaining any of the given authorizations fails.
func getAuthorizations(ctx context.Context, cl client.Interface, urls ...string) ([]*acme.Authorization, error) {
	var authzs []*acme.Authorization
	for _, url := range urls {
		a, err := cl.GetAuthorization(ctx, url)
		if err != nil {
			return nil, err
		}
		authzs = append(authzs, a)
	}
	return authzs, nil
}

// partitionAuthorizations will split a list of Authorizations into failed,
// pending and valid states.
func partitionAuthorizations(authzs ...*acme.Authorization) (failed, pending, valid []*acme.Authorization) {
	for _, a := range authzs {
		switch a.Status {
		case acme.StatusDeactivated, acme.StatusInvalid, acme.StatusRevoked, acme.StatusUnknown:
			failed = append(failed, a)
		case acme.StatusPending, acme.StatusProcessing:
			pending = append(pending, a)
		case acme.StatusValid:
			valid = append(valid, a)
		}
	}
	return failed, pending, valid
}

// pickChallengeType will select a challenge type to used based on the types
// offered by the ACME server (i.e. auth.Challenges), the options configured on
// the Certificate resource (cfg) and the providers configured on the
// corresponding Issuer resource. If there is no challenge type that can be
// used, it will return an error.
func (a *Acme) pickChallengeType(domain string, auth *acme.Authorization, cfg []v1alpha1.ACMECertificateDomainConfig) (string, error) {
	for _, d := range cfg {
		for _, dom := range d.Domains {
			if dom == domain {
				for _, challenge := range auth.Challenges {
					switch {
					case challenge.Type == "http-01" && d.HTTP01 != nil && a.issuer.GetSpec().ACME.HTTP01 != nil:
						return challenge.Type, nil
					case challenge.Type == "dns-01" && d.DNS01 != nil && a.issuer.GetSpec().ACME.DNS01 != nil:
						return challenge.Type, nil
					}
				}
			}
		}
	}
	return "", fmt.Errorf("no configured and supported challenge type found")
}

func (a *Acme) challengeForAuthorization(cl client.Interface, crt *v1alpha1.Certificate, auth *acme.Authorization) (*acme.Challenge, error) {
	domain := auth.Identifier.Value
	glog.Infof("picking challenge type for domain %q", domain)
	challengeType, err := a.pickChallengeType(domain, auth, crt.Spec.ACME.Config)
	if err != nil {
		return nil, fmt.Errorf("error picking challenge type to use for domain '%s': %s", domain, err.Error())
	}

	for _, challenge := range auth.Challenges {
		if challenge.Type != challengeType {
			continue
		}
		glog.Infof("picked challenge type %q for domain %q", challenge.Type, domain)
		return challenge, nil
	}
	return nil, fmt.Errorf("challenge mechanism '%s' not allowed for domain", challengeType)
}
<commit_msg>Add TODO about cleaning up old authorization attempts<commit_after>package acme

import (
	"context"
	"errors"
	"fmt"

	"github.com/golang/glog"
	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
	"github.com/jetstack/cert-manager/pkg/issuer/acme/client"
	"github.com/jetstack/cert-manager/third_party/crypto/acme"
)

const (
	successObtainedAuthorization = "ObtainAuthorization"
	reasonPresentChallenge       = "PresentChallenge"
	reasonSelfCheck              = "SelfCheck"
	errorGetACMEAccount          = "ErrGetACMEAccount"
	errorCheckAuthorization      = "ErrCheckAuthorization"
	errorObtainAuthorization     = "ErrObtainAuthorization"
	errorInvalidConfig           = "ErrInvalidConfig"

	messageObtainedAuthorization    = "Obtained authorization for domain %s"
	messagePresentChallenge         = "Presenting %s challenge for domain %s"
	messageSelfCheck                = "Performing self-check for domain %s"
	messageErrorGetACMEAccount      = "Error getting ACME account: "
	messageErrorCheckAuthorization  = "Error checking ACME domain validation: "
	messageErrorObtainAuthorization = "Error obtaining ACME domain authorization: "
	messageErrorMissingConfig       = "certificate.spec.acme must be specified"
)

// Prepare will ensure the issuer has been initialised and is ready to issue
// certificates for the domains listed on the Certificate resource.
//
// It will send the appropriate Letsencrypt authorizations, and complete
// challenge requests if neccessary.
func (a *Acme) Prepare(ctx context.Context, crt *v1alpha1.Certificate) error {
	if crt.Spec.ACME == nil {
		crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorInvalidConfig, messageErrorMissingConfig)
		return fmt.Errorf(messageErrorMissingConfig)
	}

	glog.V(4).Infof("Getting ACME client")
	// obtain an ACME client
	cl, err := a.acmeClient()
	if err != nil {
		return err
	}

	// TODO: clean up old authorization attempts.
	// This is complex because the Certificate resource may have been updated
	// to no longer include information required to clean up the challenge
	// (for example, if a domain is removed from a certificate while an authz
	// is in progress). This means we need to store a list of challenges we are
	// currently presenting on the certificates status field, which contains
	// enough information to clean up the resource:
	//
	// http01: domain and the fact it is http01
	// dns01: domain, token, key and dns01 provider name

	order, err := a.getOrCreateOrder(ctx, cl, crt)
	if err != nil {
		return err
	}

	allAuthorizations, err := getAuthorizations(ctx, cl, order.Authorizations...)
	if err != nil {
		s := messageErrorCheckAuthorization + err.Error()
		crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorCheckAuthorization, s)
		return errors.New(s)
	}

	failed, pending, valid := partitionAuthorizations(allAuthorizations...)
	glog.Infof("Authorizations for Certificate %q: %d failed, %d pending, %d valid", crt.Name, len(failed), len(pending), len(valid))
	toCleanup := append(failed, valid...)
	for _, auth := range toCleanup {
		err := a.cleanupAuthorization(ctx, cl, crt, auth)
		if err != nil {
			return err
		}
	}

	if len(failed) > 0 {
		glog.Infof("Found %d failed authorizations. Cleaning up pending authorizations and clearing order URL")
		// clear the order url to trigger a new order to be created
		crt.Status.ACMEStatus().Order.URL = ""
		// clean up pending authorizations
		for _, auth := range pending {
			err := a.cleanupAuthorization(ctx, cl, crt, auth)
			if err != nil {
				// TODO: clean up remaining authorizations if one fails
				return err
			}
		}
		// TODO: pretty-print the list of failed authorizations
		s := fmt.Sprintf("Error obtaining validations for domains %v", failed)
		crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorCheckAuthorization, s)
		return errors.New(s)
	}

	// all validations have been obtained
	if len(pending) == 0 {
		glog.Infof("No more pending authorizations remaining - challenge verification complete")
		return nil
	}

	var failingSelfChecks []string
	for _, auth := range pending {
		selfCheckPassed, challenge, err := a.presentAuthorization(ctx, cl, crt, auth)
		if err != nil {
			return err
		}
		if selfCheckPassed {
			glog.Infof("Self check passed for domain %q", auth.Identifier.Value)
			err := a.acceptChallenge(ctx, cl, auth, challenge)
			if err != nil {
				return err
			}
		} else {
			glog.Infof("Self check failed for domain %q", auth.Identifier.Value)
			failingSelfChecks = append(failingSelfChecks, auth.Identifier.Value)
		}
	}

	if len(failingSelfChecks) > 0 {
		return fmt.Errorf("self check failed for domains: %v", failingSelfChecks)
	}

	return nil
}

// getOrCreateOrder will attempt to retrieve an existing order for a
// certificate using the status.acme.order.url field.
//
// - if it's not set, it will call createOrder and return
//
// - if it is set, and the order is not in an error state, it will be returned
//
// - if it is set, and the order is in an invalid state, an event will be
//   logged and createOrder will be called
//
// - if an error occurs obtaining the order, it will be returned
func (a *Acme) getOrCreateOrder(ctx context.Context, cl client.Interface, crt *v1alpha1.Certificate) (*acme.Order, error) {
	orderURL := crt.Status.ACMEStatus().Order.URL
	glog.Infof("Checking existing order URL %q", orderURL)
	var err error
	var order *acme.Order
	// if the existing order URL is blank, create a new order
	if orderURL == "" {
		glog.Infof("Existing order URL not set. Creating new order.")
		return a.createOrder(ctx, cl, crt)
	}

	glog.Infof("Requesting order details for %q from ACME server", crt.Name)
	order, err = cl.GetOrder(ctx, orderURL)

	if err != nil {
		glog.Infof("Error requesting existing order details for %q from ACME server: %v", crt.Name, err)
		return nil, err
	}

	if !orderIsValidForCertificate(order, crt) {
		glog.Infof("Existing order is not valid for requested DNS names. Creating new order.")
		return a.createOrder(ctx, cl, crt)
	}

	glog.Infof("Order %q status is %q", order.URL, order.Status)
	switch order.Status {
	// create a new order if the old one is invalid
	case acme.StatusDeactivated, acme.StatusInvalid, acme.StatusRevoked:
		// TODO: log an event
		glog.Infof("Existing order is in state %q - creating a new order.", order.Status)
		return a.createOrder(ctx, cl, crt)
	case acme.StatusValid, acme.StatusPending, acme.StatusProcessing:
		return order, nil
	}

	return nil, fmt.Errorf("order %q unknown status: %q", order.URL, order.Status)
}

func (a *Acme) acceptChallenge(ctx context.Context, cl client.Interface, auth *acme.Authorization, challenge *acme.Challenge) error {
	glog.Infof("Accepting challenge for domain %q", auth.Identifier.Value)
	var err error
	challenge, err = cl.AcceptChallenge(ctx, challenge)
	if err != nil {
		return err
	}

	glog.Infof("Waiting for authorization for domain %q", auth.Identifier.Value)
	authorization, err := cl.WaitAuthorization(ctx, auth.URL)
	if err != nil {
		return err
	}

	if authorization.Status != acme.StatusValid {
		return fmt.Errorf("expected acme domain authorization status for %q to be valid, but it is %q", authorization.Identifier.Value, authorization.Status)
	}

	glog.Infof("Successfully authorized domain %q", auth.Identifier.Value)

	return nil
}

// presentAuthorization will present the challenge required for the given
// authorization using the supplied certificate configuration.
// If ths authorization is already presented, it will return no error.
// If the self-check for the authorization has passed, it will return true.
// Otherwise it will return false.
func (a *Acme) presentAuthorization(ctx context.Context, cl client.Interface, crt *v1alpha1.Certificate, auth *acme.Authorization) (bool, *acme.Challenge, error) {
	glog.Infof("Presenting challenge for domain %q", auth.Identifier.Value)
	challenge, err := a.challengeForAuthorization(cl, crt, auth)
	if err != nil {
		// TODO: handle error properly
		return false, nil, err
	}
	domain := auth.Identifier.Value
	token := challenge.Token
	key, err := keyForChallenge(cl, challenge)
	if err != nil {
		return false, challenge, err
	}
	solver, err := a.solverFor(challenge.Type)
	if err != nil {
		// TODO: handle error properly
		return false, challenge, err
	}
	err = solver.Present(ctx, crt, domain, token, key)
	if err != nil {
		// TODO: handle error properly
		return false, challenge, err
	}
	glog.Infof("Performing check to ensure challenge has propagated")
	ok, err := solver.Check(domain, token, key)
	if err != nil {
		return false, challenge, err
	}
	return ok, challenge, nil
}

// cleanupAuthorization will clean up a given authorization.
// To do this, it first determines the challenge type to use for the
// authorization based on the Issuer and Certificate configuration.
// It then calls CleanUp on the appropriate Solver.
// CleanUp will clean up any remaining resources left over from attempting to
// solve the given challenge.
// If a valid challenge type is not configured, cleanupAuthorization will
// return an error.
func (a *Acme) cleanupAuthorization(ctx context.Context, cl client.Interface, crt *v1alpha1.Certificate, auth *acme.Authorization) error {
	glog.Infof("Cleaning up authorization for %q", auth.Identifier.Value)
	challenge, err := a.challengeForAuthorization(cl, crt, auth)
	if err != nil {
		return err
	}
	domain := auth.Identifier.Value
	token := challenge.Token
	key, err := keyForChallenge(cl, challenge)
	if err != nil {
		return err
	}

	solver, err := a.solverFor(challenge.Type)
	if err != nil {
		return err
	}

	return solver.CleanUp(ctx, crt, domain, token, key)
}

// keyForChallenge will return the key to use for solving a given acme
// challenge.
// Only http-01 and dns-01 challenges are supported.
// An error will be returned if obtaining the key fails, or the challenge type
// is unsupported.
func keyForChallenge(cl client.Interface, challenge *acme.Challenge) (string, error) {
	var err error
	switch challenge.Type {
	case "http-01":
		return cl.HTTP01ChallengeResponse(challenge.Token)
	case "dns-01":
		return cl.DNS01ChallengeRecord(challenge.Token)
	default:
		err = fmt.Errorf("unsupported challenge type %s", challenge.Type)
	}
	return "", err
}

// getAuthorizations will query the ACME server for the Authorization resources
// for the given list of authorization URLs using the given ACME client.
// It will return an error if obtaining any of the given authorizations fails.
func getAuthorizations(ctx context.Context, cl client.Interface, urls ...string) ([]*acme.Authorization, error) {
	var authzs []*acme.Authorization
	for _, url := range urls {
		a, err := cl.GetAuthorization(ctx, url)
		if err != nil {
			return nil, err
		}
		authzs = append(authzs, a)
	}
	return authzs, nil
}

// partitionAuthorizations will split a list of Authorizations into failed,
// pending and valid states.
func partitionAuthorizations(authzs ...*acme.Authorization) (failed, pending, valid []*acme.Authorization) {
	for _, a := range authzs {
		switch a.Status {
		case acme.StatusDeactivated, acme.StatusInvalid, acme.StatusRevoked, acme.StatusUnknown:
			failed = append(failed, a)
		case acme.StatusPending, acme.StatusProcessing:
			pending = append(pending, a)
		case acme.StatusValid:
			valid = append(valid, a)
		}
	}
	return failed, pending, valid
}

// pickChallengeType will select a challenge type to used based on the types
// offered by the ACME server (i.e. auth.Challenges), the options configured on
// the Certificate resource (cfg) and the providers configured on the
// corresponding Issuer resource. If there is no challenge type that can be
// used, it will return an error.
func (a *Acme) pickChallengeType(domain string, auth *acme.Authorization, cfg []v1alpha1.ACMECertificateDomainConfig) (string, error) {
	for _, d := range cfg {
		for _, dom := range d.Domains {
			if dom == domain {
				for _, challenge := range auth.Challenges {
					switch {
					case challenge.Type == "http-01" && d.HTTP01 != nil && a.issuer.GetSpec().ACME.HTTP01 != nil:
						return challenge.Type, nil
					case challenge.Type == "dns-01" && d.DNS01 != nil && a.issuer.GetSpec().ACME.DNS01 != nil:
						return challenge.Type, nil
					}
				}
			}
		}
	}
	return "", fmt.Errorf("no configured and supported challenge type found")
}

func (a *Acme) challengeForAuthorization(cl client.Interface, crt *v1alpha1.Certificate, auth *acme.Authorization) (*acme.Challenge, error) {
	domain := auth.Identifier.Value
	glog.Infof("picking challenge type for domain %q", domain)
	challengeType, err := a.pickChallengeType(domain, auth, crt.Spec.ACME.Config)
	if err != nil {
		return nil, fmt.Errorf("error picking challenge type to use for domain '%s': %s", domain, err.Error())
	}

	for _, challenge := range auth.Challenges {
		if challenge.Type != challengeType {
			continue
		}
		glog.Infof("picked challenge type %q for domain %q", challenge.Type, domain)
		return challenge, nil
	}
	return nil, fmt.Errorf("challenge mechanism '%s' not allowed for domain", challengeType)
}
<|endoftext|>


<commit_before>// Package combiner contains methods to consume Job objects, orchestrating
// the download, combination, and upload of a group of related PDF files.
package combiner

import (
  "time"
  "log"
  "fmt"
  "io/ioutil"
  "errors"
  "pdfcombiner/cpdf"
  "pdfcombiner/job"
)

var (
  downloadTimeout = 3 * time.Minute
  verbose = true
  basedir = "/tmp/"
)

// A stat represents statistics about a completed document transfer operation.
type stat struct {
  filename string
  size     int
  dlSecs   time.Duration
}

// Get an individual file from S3.  If successful, writes the file out to disk
// and sends a stat object back to the main channel.  If there are errors they
// are sent back through the error channel.
func getFile(j *job.Job, docname string, c chan stat, e chan error) {
  start := time.Now()
  data, err := j.Get(docname)
  if err != nil {
    e <- fmt.Errorf("%v: %v", docname, err)
    return
  }
  path := basedir+docname
  err = ioutil.WriteFile(path, data, 0644)
  if err != nil {
    e <- err; return
    return
  }
  c <- stat{ filename: docname,
             size: len(data),
             dlSecs: time.Since(start) }
}

// Fan out workers to download each document in parallel, then block
// until all downloads are complete.
func getAllFiles(j *job.Job) {
  start := time.Now()
  j.Connect()
  c := make(chan stat)
  e := make(chan error)
  for _,doc := range j.DocList{
    go getFile(j,doc,c,e)
  }

  totalBytes := waitForDownloads(j,c,e)
  printSummary(start, totalBytes, j.CompleteCount())
}

// Listen on several channels for information from background download
// tasks -- each task will either send a stat through c, an error through
// e, or timeout.  Once all docs are accounted for, return the total number
// of bytes recieved.
func waitForDownloads(j *job.Job, c chan stat, e chan error) (totalBytes int) {
  for _,_ = range j.DocList{
    select {
      case packet := <-c:
        if verbose { log.Printf("%s was %d bytes\n", packet.filename,packet.size) }
        totalBytes += packet.size
        j.MarkComplete(packet.filename)
      case err := <-e:
        j.AddError(err)
      case <-time.After(downloadTimeout):
        j.AddError(errors.New("Timed out while downloading"))
        return
    }
  }
  return
}

// Print a summary of the download activity.
func printSummary(start time.Time, bytes int, count int){
  elapsed := time.Since(start)
  seconds := elapsed.Seconds()
  mbps := float64(bytes) / 1024 / 1024 / seconds
  log.Printf("got %d bytes over %d files in %f secs (%f MB/s)\n",
             bytes, count, seconds, mbps)
}

// Send an update on the success or failure of the operation to the
// callback URL provided by the job originator.
func postToCallback(j *job.Job){
  log.Println("work complete, posting success to callback:",j.Callback)
  log.Println(j)
}

// The entry point to this package.  Given a Job, download all the files,
// combine them into a single one, upload it to AWS and post the status to
// a callback endpoint.
func Combine(j *job.Job) bool {
  defer postToCallback(j)
  getAllFiles(j)
  if j.HasDownloadedDocs() {
    cpdf.Merge(j.Downloaded)
  }
  return true
}

<commit_msg>Buffer channels to prevent deadlock<commit_after>// Package combiner contains methods to consume Job objects, orchestrating
// the download, combination, and upload of a group of related PDF files.
package combiner

import (
  "time"
  "log"
  "fmt"
  "io/ioutil"
  "errors"
  "pdfcombiner/cpdf"
  "pdfcombiner/job"
)

var (
  downloadTimeout = 3 * time.Minute
  verbose = true
  basedir = "/tmp/"
)

// A stat represents statistics about a completed document transfer operation.
type stat struct {
  filename string
  size     int
  dlSecs   time.Duration
}

// Get an individual file from S3.  If successful, writes the file out to disk
// and sends a stat object back to the main channel.  If there are errors they
// are sent back through the error channel.
func getFile(j *job.Job, docname string, c chan stat, e chan error) {
  start := time.Now()
  data, err := j.Get(docname)
  if err != nil {
    e <- fmt.Errorf("%v: %v", docname, err)
    return
  }
  path := basedir+docname
  err = ioutil.WriteFile(path, data, 0644)
  if err != nil {
    e <- err; return
    return
  }
  c <- stat{ filename: docname,
             size: len(data),
             dlSecs: time.Since(start) }
}

// Fan out workers to download each document in parallel, then block
// until all downloads are complete.
func getAllFiles(j *job.Job) {
  start := time.Now()
  j.Connect()
  c := make(chan stat, j.DocCount())
  e := make(chan error, j.DocCount())
  for _,doc := range j.DocList{
    go getFile(j,doc,c,e)
  }

  totalBytes := waitForDownloads(j,c,e)
  printSummary(start, totalBytes, j.CompleteCount())
}

// Listen on several channels for information from background download
// tasks -- each task will either send a stat through c, an error through
// e, or timeout.  Once all docs are accounted for, return the total number
// of bytes recieved.
func waitForDownloads(j *job.Job, c chan stat, e chan error) (totalBytes int) {
  for _,_ = range j.DocList{
    select {
      case packet := <-c:
        if verbose { log.Printf("%s was %d bytes\n", packet.filename,packet.size) }
        totalBytes += packet.size
        j.MarkComplete(packet.filename)
      case err := <-e:
        j.AddError(err)
      case <-time.After(downloadTimeout):
        j.AddError(errors.New("Timed out while downloading"))
        return
    }
  }
  return
}

// Print a summary of the download activity.
func printSummary(start time.Time, bytes int, count int){
  elapsed := time.Since(start)
  seconds := elapsed.Seconds()
  mbps := float64(bytes) / 1024 / 1024 / seconds
  log.Printf("got %d bytes over %d files in %f secs (%f MB/s)\n",
             bytes, count, seconds, mbps)
}

// Send an update on the success or failure of the operation to the
// callback URL provided by the job originator.
func postToCallback(j *job.Job){
  log.Println("work complete, posting success to callback:",j.Callback)
  log.Println(j)
}

// The entry point to this package.  Given a Job, download all the files,
// combine them into a single one, upload it to AWS and post the status to
// a callback endpoint.
func Combine(j *job.Job) bool {
  defer postToCallback(j)
  getAllFiles(j)
  if j.HasDownloadedDocs() {
    cpdf.Merge(j.Downloaded)
  }
  return true
}

<|endoftext|>


<commit_before>// Poseidon
// Copyright (c) The Poseidon Authors.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
// LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR
// A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

package k8sclient

import (
	"github.com/ICGog/poseidongo/pkg/firmament"
	"github.com/golang/glog"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

var clientSet kubernetes.Interface

func BindPodToNode(podName string, namespace string, nodeName string) {
	// TODO(ionel): Reuse namespace clients!
	err := clientSet.CoreV1().Pods(namespace).Bind(&v1.Binding{
		meta_v1.TypeMeta{},
		meta_v1.ObjectMeta{
			Name: podName,
		},
		v1.ObjectReference{
			Namespace: namespace,
			Name:      nodeName,
		}})
	if err != nil {
		glog.Fatalf("Could not bind %v", err)
	}
}

func DeletePod(podName string, namespace string) {
	// TODO(ionel): Reuse namespace clients!
	clientSet.CoreV1().Pods(namespace).Delete(podName, &meta_v1.DeleteOptions{})
}

func GetClientConfig(kubeconfig string) (*rest.Config, error) {
	if kubeconfig != "" {
		return clientcmd.BuildConfigFromFlags("", kubeconfig)
	}
	return rest.InClusterConfig()
}

func New(schedulerName string, kubeConfig string, kubeVersionMajor, kubeVersionMinor int, firmamentAddress string) {
	config, err := GetClientConfig(kubeConfig)
	if err != nil {
		glog.Fatalf("Failed to load client config: %v", err)
	}
	clientSet, err = kubernetes.NewForConfig(config)
	if err != nil {
		glog.Fatalf("Failed to create connection: %v", err)
	}
	fc, conn, err := firmament.New(firmamentAddress)
	if err != nil {
		glog.Fatalf("Failed to connect to Firmament: %v", err)
	}
	defer conn.Close()
	glog.Info("k8s newclient called")
	stopCh := make(chan struct{})
	go NewPodWatcher(kubeVersionMajor, kubeVersionMinor, schedulerName, clientSet, fc).Run(stopCh, 10)
	go NewNodeWatcher(clientSet, fc).Run(stopCh, 10)

	// We block here.
	<-stopCh
}
<commit_msg>Removed unnecessary TODOs.<commit_after>// Poseidon
// Copyright (c) The Poseidon Authors.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
// LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR
// A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

package k8sclient

import (
	"github.com/ICGog/poseidongo/pkg/firmament"
	"github.com/golang/glog"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

var clientSet kubernetes.Interface

func BindPodToNode(podName string, namespace string, nodeName string) {
	err := clientSet.CoreV1().Pods(namespace).Bind(&v1.Binding{
		meta_v1.TypeMeta{},
		meta_v1.ObjectMeta{
			Name: podName,
		},
		v1.ObjectReference{
			Namespace: namespace,
			Name:      nodeName,
		}})
	if err != nil {
		glog.Fatalf("Could not bind %v", err)
	}
}

func DeletePod(podName string, namespace string) {
	clientSet.CoreV1().Pods(namespace).Delete(podName, &meta_v1.DeleteOptions{})
}

func GetClientConfig(kubeconfig string) (*rest.Config, error) {
	if kubeconfig != "" {
		return clientcmd.BuildConfigFromFlags("", kubeconfig)
	}
	return rest.InClusterConfig()
}

func New(schedulerName string, kubeConfig string, kubeVersionMajor, kubeVersionMinor int, firmamentAddress string) {
	config, err := GetClientConfig(kubeConfig)
	if err != nil {
		glog.Fatalf("Failed to load client config: %v", err)
	}
	clientSet, err = kubernetes.NewForConfig(config)
	if err != nil {
		glog.Fatalf("Failed to create connection: %v", err)
	}
	fc, conn, err := firmament.New(firmamentAddress)
	if err != nil {
		glog.Fatalf("Failed to connect to Firmament: %v", err)
	}
	defer conn.Close()
	glog.Info("k8s newclient called")
	stopCh := make(chan struct{})
	go NewPodWatcher(kubeVersionMajor, kubeVersionMinor, schedulerName, clientSet, fc).Run(stopCh, 10)
	go NewNodeWatcher(clientSet, fc).Run(stopCh, 10)

	// We block here.
	<-stopCh
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes 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 server

import (
	"net/http"
	"strings"

	"github.com/golang/glog"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apiserver/pkg/authentication/authenticator"
	"k8s.io/apiserver/pkg/authentication/user"
	"k8s.io/apiserver/pkg/authorization/authorizer"
)

// KubeletAuth implements AuthInterface
type KubeletAuth struct {
	// authenticator identifies the user for requests to the Kubelet API
	authenticator.Request
	// authorizerAttributeGetter builds authorization.Attributes for a request to the Kubelet API
	authorizer.RequestAttributesGetter
	// authorizer determines whether a given authorization.Attributes is allowed
	authorizer.Authorizer
}

// NewKubeletAuth returns a kubelet.AuthInterface composed of the given authenticator, attribute getter, and authorizer
func NewKubeletAuth(authenticator authenticator.Request, authorizerAttributeGetter authorizer.RequestAttributesGetter, authorizer authorizer.Authorizer) AuthInterface {
	return &KubeletAuth{authenticator, authorizerAttributeGetter, authorizer}
}

func NewNodeAuthorizerAttributesGetter(nodeName types.NodeName) authorizer.RequestAttributesGetter {
	return nodeAuthorizerAttributesGetter{nodeName: nodeName}
}

type nodeAuthorizerAttributesGetter struct {
	nodeName types.NodeName
}

func isSubpath(subpath, path string) bool {
	path = strings.TrimSuffix(path, "/")
	return subpath == path || (strings.HasPrefix(subpath, path) && subpath[len(path)] == '/')
}

// GetRequestAttributes populates authorizer attributes for the requests to the kubelet API.
// Default attributes are: {apiVersion=v1,verb=<http verb from request>,resource=nodes,name=<node name>,subresource=proxy}
// More specific verb/resource is set for the following request patterns:
//    /stats/*   => verb=<api verb from request>, resource=nodes, name=<node name>, subresource=stats
//    /metrics/* => verb=<api verb from request>, resource=nodes, name=<node name>, subresource=metrics
//    /logs/*    => verb=<api verb from request>, resource=nodes, name=<node name>, subresource=log
//    /spec/*    => verb=<api verb from request>, resource=nodes, name=<node name>, subresource=spec
func (n nodeAuthorizerAttributesGetter) GetRequestAttributes(u user.Info, r *http.Request) authorizer.Attributes {

	apiVerb := ""
	switch r.Method {
	case "POST":
		apiVerb = "create"
	case "GET":
		apiVerb = "get"
	case "PUT":
		apiVerb = "update"
	case "PATCH":
		apiVerb = "patch"
	case "DELETE":
		apiVerb = "delete"
	}

	requestPath := r.URL.Path

	// Default attributes mirror the API attributes that would allow this access to the kubelet API
	attrs := authorizer.AttributesRecord{
		User:            u,
		Verb:            apiVerb,
		Namespace:       "",
		APIGroup:        "",
		APIVersion:      "v1",
		Resource:        "nodes",
		Subresource:     "proxy",
		Name:            string(n.nodeName),
		ResourceRequest: true,
		Path:            requestPath,
	}

	// Override subresource for specific paths
	// This allows subdividing access to the kubelet API
	switch {
	case isSubpath(requestPath, statsPath):
		attrs.Subresource = "stats"
	case isSubpath(requestPath, metricsPath):
		attrs.Subresource = "metrics"
	case isSubpath(requestPath, logsPath):
		// "log" to match other log subresources (pods/log, etc)
		attrs.Subresource = "log"
	case isSubpath(requestPath, specPath):
		attrs.Subresource = "spec"
	}

	glog.V(5).Infof("Node request attributes: attrs=%#v", attrs)

	return attrs
}
<commit_msg>make sure that log includes user information<commit_after>/*
Copyright 2015 The Kubernetes 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 server

import (
	"net/http"
	"strings"

	"github.com/golang/glog"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apiserver/pkg/authentication/authenticator"
	"k8s.io/apiserver/pkg/authentication/user"
	"k8s.io/apiserver/pkg/authorization/authorizer"
)

// KubeletAuth implements AuthInterface
type KubeletAuth struct {
	// authenticator identifies the user for requests to the Kubelet API
	authenticator.Request
	// authorizerAttributeGetter builds authorization.Attributes for a request to the Kubelet API
	authorizer.RequestAttributesGetter
	// authorizer determines whether a given authorization.Attributes is allowed
	authorizer.Authorizer
}

// NewKubeletAuth returns a kubelet.AuthInterface composed of the given authenticator, attribute getter, and authorizer
func NewKubeletAuth(authenticator authenticator.Request, authorizerAttributeGetter authorizer.RequestAttributesGetter, authorizer authorizer.Authorizer) AuthInterface {
	return &KubeletAuth{authenticator, authorizerAttributeGetter, authorizer}
}

func NewNodeAuthorizerAttributesGetter(nodeName types.NodeName) authorizer.RequestAttributesGetter {
	return nodeAuthorizerAttributesGetter{nodeName: nodeName}
}

type nodeAuthorizerAttributesGetter struct {
	nodeName types.NodeName
}

func isSubpath(subpath, path string) bool {
	path = strings.TrimSuffix(path, "/")
	return subpath == path || (strings.HasPrefix(subpath, path) && subpath[len(path)] == '/')
}

// GetRequestAttributes populates authorizer attributes for the requests to the kubelet API.
// Default attributes are: {apiVersion=v1,verb=<http verb from request>,resource=nodes,name=<node name>,subresource=proxy}
// More specific verb/resource is set for the following request patterns:
//    /stats/*   => verb=<api verb from request>, resource=nodes, name=<node name>, subresource=stats
//    /metrics/* => verb=<api verb from request>, resource=nodes, name=<node name>, subresource=metrics
//    /logs/*    => verb=<api verb from request>, resource=nodes, name=<node name>, subresource=log
//    /spec/*    => verb=<api verb from request>, resource=nodes, name=<node name>, subresource=spec
func (n nodeAuthorizerAttributesGetter) GetRequestAttributes(u user.Info, r *http.Request) authorizer.Attributes {

	apiVerb := ""
	switch r.Method {
	case "POST":
		apiVerb = "create"
	case "GET":
		apiVerb = "get"
	case "PUT":
		apiVerb = "update"
	case "PATCH":
		apiVerb = "patch"
	case "DELETE":
		apiVerb = "delete"
	}

	requestPath := r.URL.Path

	// Default attributes mirror the API attributes that would allow this access to the kubelet API
	attrs := authorizer.AttributesRecord{
		User:            u,
		Verb:            apiVerb,
		Namespace:       "",
		APIGroup:        "",
		APIVersion:      "v1",
		Resource:        "nodes",
		Subresource:     "proxy",
		Name:            string(n.nodeName),
		ResourceRequest: true,
		Path:            requestPath,
	}

	// Override subresource for specific paths
	// This allows subdividing access to the kubelet API
	switch {
	case isSubpath(requestPath, statsPath):
		attrs.Subresource = "stats"
	case isSubpath(requestPath, metricsPath):
		attrs.Subresource = "metrics"
	case isSubpath(requestPath, logsPath):
		// "log" to match other log subresources (pods/log, etc)
		attrs.Subresource = "log"
	case isSubpath(requestPath, specPath):
		attrs.Subresource = "spec"
	}

	glog.V(5).Infof("Node request attributes: user=%#v attrs=%#v", attrs.GetUser(), attrs)

	return attrs
}
<|endoftext|>


<commit_before>package commands

import (
	"errors"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/atotto/clipboard"
	"github.com/codegangsta/cli"

	"github.com/gummiboll/forgetful/reader"
	"github.com/gummiboll/forgetful/storage"
	"github.com/gummiboll/forgetful/writer"
)

type byLetterNocase []string

func (s byLetterNocase) Len() int {
	return len(s)
}
func (s byLetterNocase) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}
func (s byLetterNocase) Less(i, j int) bool {
	return strings.ToLower(s[i]) < strings.ToLower(s[j])
}

// FormatNoteList returns a formatted list of notes
func FormatNoteList(notes []storage.Note) (rnotes []string) {
	for _, n := range notes {
		nStr := fmt.Sprintf("\U0001f4c3  %s", n.Name)
		if n.Temporary {
			validTo := n.CreatedAt.Add(24 * time.Hour)
			dur := validTo.Sub(time.Now())
			nStr += fmt.Sprintf(" (\U0001f4a5 in %s) ", dur)
		}
		rnotes = append(rnotes, nStr)
	}

	sort.Sort(byLetterNocase(rnotes))

	return rnotes
}

// NoteName returns name of note and error if note isnt present
func NoteName(c *cli.Context) (n string, err error) {
	if c.Args().Present() != true {
		return "", errors.New("Missing argument: name")
	}

	return strings.Join(c.Args(), " "), nil
}

// AddCommand adds a Note
func AddCommand(c *cli.Context, i storage.Impl) (n storage.Note, err error) {
	nName, err := NoteName(c)
	if err != nil {
		return n, err
	}

	if exists := i.NoteExists(nName); exists == true {
		return n, fmt.Errorf("Note already exists")
	}

	n.Name = nName
	n.Temporary = c.Bool("t")

	// Only open editor if -p (read from clipboard) isnt set
	if c.IsSet("p") {
		nText, err := clipboard.ReadAll()
		if err != nil {
			return n, err
		}
		n.Text = nText
	} else {
		if err := writer.WriteNote(&n); err != nil {
			return n, err
		}
	}

	if err := i.SaveNote(&n); err != nil {
		return n, err
	}

	return n, nil
}

// DeleteCommand deletes a Note
func DeleteCommand(c *cli.Context, i storage.Impl) (n storage.Note, err error) {
	nName, err := NoteName(c)
	if err != nil {
		return n, err
	}

	n, err = i.LoadNote(nName)
	if err != nil {
		return n, err
	}

	if i.DeleteNote(n) != nil {
		return n, err
	}

	return n, nil
}

// EditCommand edits a Note
func EditCommand(c *cli.Context, i storage.Impl) (n storage.Note, err error) {
	nName, err := NoteName(c)
	if err != nil {
		return n, err
	}

	n, err = i.LoadNote(nName)
	if err != nil {
		return n, err
	}

	if err := writer.WriteNote(&n); err != nil {
		return n, err
	}

	if err := i.SaveNote(&n); err != nil {
		return n, err
	}

	return n, nil

}

// ReadCommand reads a Note
func ReadCommand(c *cli.Context, i storage.Impl) (err error) {
	nName, err := NoteName(c)
	if err != nil {
		return err
	}

	n, err := i.LoadNote(nName)
	if err != nil {
		return err
	}

	if err := reader.ReadNote(n); err != nil {
		return err
	}

	return nil
}

// ListCommand lists Notes
func ListCommand(c *cli.Context, i storage.Impl) (rnotes []string) {
	nName := strings.Join(c.Args(), " ")
	notes := i.ListNotes(nName)

	return FormatNoteList(notes)

}

// SearchCommand searches for Notes
func SearchCommand(c *cli.Context, i storage.Impl) (rnotes []string, err error) {
	nName, err := NoteName(c)
	if err != nil {
		return rnotes, err
	}

	notes := i.SearchNotes(nName)

	return FormatNoteList(notes), nil
}

// ShareCommand shares a Note
func ShareCommand(c *cli.Context, i storage.Impl) (n storage.Note, url string, err error) {
	nName, err := NoteName(c)
	if err != nil {
		return n, url, err
	}

	n, err = i.LoadNote(nName)
	if err != nil {
		return n, url, err
	}

	url, err = reader.ShareNote(n, "http://hastebin.com/documents")

	if err != nil {
		return n, url, err
	}

	return n, url, nil
}

// KeepCommand keeps a Note
func KeepCommand(c *cli.Context, i storage.Impl) (n storage.Note, err error) {
	nName, err := NoteName(c)
	if err != nil {
		return n, err
	}

	n, err = i.LoadNote(nName)
	if err != nil {
		return n, err
	}

	n.Temporary = false
	if err := i.SaveNote(&n); err != nil {
		return n, err
	}

	return n, err
}
<commit_msg>Changed list layout<commit_after>package commands

import (
	"errors"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/atotto/clipboard"
	"github.com/codegangsta/cli"

	"github.com/gummiboll/forgetful/reader"
	"github.com/gummiboll/forgetful/storage"
	"github.com/gummiboll/forgetful/writer"
)

type byLetterNocase []string

func (s byLetterNocase) Len() int {
	return len(s)
}
func (s byLetterNocase) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}
func (s byLetterNocase) Less(i, j int) bool {
	return strings.ToLower(s[i]) < strings.ToLower(s[j])
}

// FormatNoteList returns a formatted list of notes
func FormatNoteList(notes []storage.Note) (rnotes []string) {
	for _, n := range notes {
		nStr := fmt.Sprintf("\U0001f539  %s", n.Name)
		if n.Temporary {
			validTo := n.CreatedAt.Add(24 * time.Hour)
			dur := validTo.Sub(time.Now())
			nStr += fmt.Sprintf(" (\U0001f4a5 in %s) ", dur)
		}
		rnotes = append(rnotes, nStr)
	}

	sort.Sort(byLetterNocase(rnotes))

	return rnotes
}

// NoteName returns name of note and error if note isnt present
func NoteName(c *cli.Context) (n string, err error) {
	if c.Args().Present() != true {
		return "", errors.New("Missing argument: name")
	}

	return strings.Join(c.Args(), " "), nil
}

// AddCommand adds a Note
func AddCommand(c *cli.Context, i storage.Impl) (n storage.Note, err error) {
	nName, err := NoteName(c)
	if err != nil {
		return n, err
	}

	if exists := i.NoteExists(nName); exists == true {
		return n, fmt.Errorf("Note already exists")
	}

	n.Name = nName
	n.Temporary = c.Bool("t")

	// Only open editor if -p (read from clipboard) isnt set
	if c.IsSet("p") {
		nText, err := clipboard.ReadAll()
		if err != nil {
			return n, err
		}
		n.Text = nText
	} else {
		if err := writer.WriteNote(&n); err != nil {
			return n, err
		}
	}

	if err := i.SaveNote(&n); err != nil {
		return n, err
	}

	return n, nil
}

// DeleteCommand deletes a Note
func DeleteCommand(c *cli.Context, i storage.Impl) (n storage.Note, err error) {
	nName, err := NoteName(c)
	if err != nil {
		return n, err
	}

	n, err = i.LoadNote(nName)
	if err != nil {
		return n, err
	}

	if i.DeleteNote(n) != nil {
		return n, err
	}

	return n, nil
}

// EditCommand edits a Note
func EditCommand(c *cli.Context, i storage.Impl) (n storage.Note, err error) {
	nName, err := NoteName(c)
	if err != nil {
		return n, err
	}

	n, err = i.LoadNote(nName)
	if err != nil {
		return n, err
	}

	if err := writer.WriteNote(&n); err != nil {
		return n, err
	}

	if err := i.SaveNote(&n); err != nil {
		return n, err
	}

	return n, nil

}

// ReadCommand reads a Note
func ReadCommand(c *cli.Context, i storage.Impl) (err error) {
	nName, err := NoteName(c)
	if err != nil {
		return err
	}

	n, err := i.LoadNote(nName)
	if err != nil {
		return err
	}

	if err := reader.ReadNote(n); err != nil {
		return err
	}

	return nil
}

// ListCommand lists Notes
func ListCommand(c *cli.Context, i storage.Impl) (rnotes []string) {
	nName := strings.Join(c.Args(), " ")
	notes := i.ListNotes(nName)

	return FormatNoteList(notes)

}

// SearchCommand searches for Notes
func SearchCommand(c *cli.Context, i storage.Impl) (rnotes []string, err error) {
	nName, err := NoteName(c)
	if err != nil {
		return rnotes, err
	}

	notes := i.SearchNotes(nName)

	return FormatNoteList(notes), nil
}

// ShareCommand shares a Note
func ShareCommand(c *cli.Context, i storage.Impl) (n storage.Note, url string, err error) {
	nName, err := NoteName(c)
	if err != nil {
		return n, url, err
	}

	n, err = i.LoadNote(nName)
	if err != nil {
		return n, url, err
	}

	url, err = reader.ShareNote(n, "http://hastebin.com/documents")

	if err != nil {
		return n, url, err
	}

	return n, url, nil
}

// KeepCommand keeps a Note
func KeepCommand(c *cli.Context, i storage.Impl) (n storage.Note, err error) {
	nName, err := NoteName(c)
	if err != nil {
		return n, err
	}

	n, err = i.LoadNote(nName)
	if err != nil {
		return n, err
	}

	n.Temporary = false
	if err := i.SaveNote(&n); err != nil {
		return n, err
	}

	return n, err
}
<|endoftext|>


<commit_before>package commands

import (
	"bytes"
	"fmt"
	"github.com/github/git-media/gitmedia"
	"github.com/spf13/cobra"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime/debug"
	"strings"
	"time"
)

var (
	Debugging    = false
	ErrorBuffer  = &bytes.Buffer{}
	ErrorWriter  = io.MultiWriter(os.Stderr, ErrorBuffer)
	OutputWriter = io.MultiWriter(os.Stdout, ErrorBuffer)
	RootCmd      = &cobra.Command{
		Use:   "git-media",
		Short: "Git Media provides large file support to Git.",
		Run: func(cmd *cobra.Command, args []string) {
			versionCommand(cmd, args)
			cmd.Usage()
		},
	}
)

// Error prints a formatted message to Stderr.  It also gets printed to the
// panic log if one is created for this command.
func Error(format string, args ...interface{}) {
	line := fmt.Sprintf(format, args...)
	fmt.Fprintln(ErrorWriter, line)
}

// Print prints a formatted message to Stdout.  It also gets printed to the
// panic log if one is created for this command.
func Print(format string, args ...interface{}) {
	line := fmt.Sprintf(format, args...)
	fmt.Fprintln(OutputWriter, line)
}

// Exit prints a formatted message and exits.
func Exit(format string, args ...interface{}) {
	Error(format, args...)
	os.Exit(2)
}

// Debug prints a formatted message if debugging is enabled.  The formatted
// message also shows up in the panic log, if created.
func Debug(format string, args ...interface{}) {
	if !Debugging {
		return
	}
	log.Printf(format, args...)
}

// Panic prints a formatted message, and writes a stack trace for the error to
// a log file before exiting.
func Panic(err error, format string, args ...interface{}) {
	Error(format, args...)
	file := handlePanic(err)

	if len(file) > 0 {
		fmt.Fprintf(os.Stderr, "\nErrors logged to %s.\nUse `git media logs last` to view the log.\n", file)
	}
	os.Exit(2)
}

func Run() {
	RootCmd.Execute()
}

func PipeMediaCommand(name string, args ...string) error {
	return PipeCommand("bin/"+name, args...)
}

func PipeCommand(name string, args ...string) error {
	cmd := exec.Command(name, args...)
	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	return cmd.Run()
}

func handlePanic(err error) string {
	if err == nil {
		return ""
	}

	Debug(err.Error())
	return logPanic(err, false)
}

func logEnv(w io.Writer) {
	for _, env := range gitmedia.Environ() {
		fmt.Fprintln(w, env)
	}
}

func logPanic(loggedError error, recursive bool) string {
	var fmtWriter io.Writer = os.Stderr

	if err := os.MkdirAll(gitmedia.LocalLogDir, 0755); err != nil {
		fmt.Fprintf(fmtWriter, "Unable to log panic to %s: %s\n\n", gitmedia.LocalLogDir, err.Error())
		return ""
	}

	now := time.Now()
	name := now.Format("2006-01-02T15:04:05.999999999")
	full := filepath.Join(gitmedia.LocalLogDir, name+".log")

	file, err := os.Create(full)
	if err == nil {
		fmtWriter = file
		defer file.Close()
	}

	fmt.Fprintf(fmtWriter, "> %s", filepath.Base(os.Args[0]))
	if len(os.Args) > 0 {
		fmt.Fprintf(fmtWriter, " %s", strings.Join(os.Args[1:], " "))
	}
	fmt.Fprint(fmtWriter, "\n")

	logEnv(fmtWriter)
	fmt.Fprint(fmtWriter, "\n")

	fmtWriter.Write(ErrorBuffer.Bytes())
	fmt.Fprint(fmtWriter, "\n")

	fmt.Fprintln(fmtWriter, loggedError.Error())
	fmtWriter.Write(debug.Stack())

	if err != nil && !recursive {
		fmt.Fprintf(fmtWriter, "Unable to log panic to %s\n\n", full)
		logPanic(err, true)
	}

	return full
}

func init() {
	log.SetOutput(ErrorWriter)
}
<commit_msg>remove punctuation from log filenames<commit_after>package commands

import (
	"bytes"
	"fmt"
	"github.com/github/git-media/gitmedia"
	"github.com/spf13/cobra"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime/debug"
	"strings"
	"time"
)

var (
	Debugging    = false
	ErrorBuffer  = &bytes.Buffer{}
	ErrorWriter  = io.MultiWriter(os.Stderr, ErrorBuffer)
	OutputWriter = io.MultiWriter(os.Stdout, ErrorBuffer)
	RootCmd      = &cobra.Command{
		Use:   "git-media",
		Short: "Git Media provides large file support to Git.",
		Run: func(cmd *cobra.Command, args []string) {
			versionCommand(cmd, args)
			cmd.Usage()
		},
	}
)

// Error prints a formatted message to Stderr.  It also gets printed to the
// panic log if one is created for this command.
func Error(format string, args ...interface{}) {
	line := fmt.Sprintf(format, args...)
	fmt.Fprintln(ErrorWriter, line)
}

// Print prints a formatted message to Stdout.  It also gets printed to the
// panic log if one is created for this command.
func Print(format string, args ...interface{}) {
	line := fmt.Sprintf(format, args...)
	fmt.Fprintln(OutputWriter, line)
}

// Exit prints a formatted message and exits.
func Exit(format string, args ...interface{}) {
	Error(format, args...)
	os.Exit(2)
}

// Debug prints a formatted message if debugging is enabled.  The formatted
// message also shows up in the panic log, if created.
func Debug(format string, args ...interface{}) {
	if !Debugging {
		return
	}
	log.Printf(format, args...)
}

// Panic prints a formatted message, and writes a stack trace for the error to
// a log file before exiting.
func Panic(err error, format string, args ...interface{}) {
	Error(format, args...)
	file := handlePanic(err)

	if len(file) > 0 {
		fmt.Fprintf(os.Stderr, "\nErrors logged to %s.\nUse `git media logs last` to view the log.\n", file)
	}
	os.Exit(2)
}

func Run() {
	RootCmd.Execute()
}

func PipeMediaCommand(name string, args ...string) error {
	return PipeCommand("bin/"+name, args...)
}

func PipeCommand(name string, args ...string) error {
	cmd := exec.Command(name, args...)
	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	return cmd.Run()
}

func handlePanic(err error) string {
	if err == nil {
		return ""
	}

	Debug(err.Error())
	return logPanic(err, false)
}

func logEnv(w io.Writer) {
	for _, env := range gitmedia.Environ() {
		fmt.Fprintln(w, env)
	}
}

func logPanic(loggedError error, recursive bool) string {
	var fmtWriter io.Writer = os.Stderr

	if err := os.MkdirAll(gitmedia.LocalLogDir, 0755); err != nil {
		fmt.Fprintf(fmtWriter, "Unable to log panic to %s: %s\n\n", gitmedia.LocalLogDir, err.Error())
		return ""
	}

	now := time.Now()
	name := now.Format("20060102T150405.999999999")
	full := filepath.Join(gitmedia.LocalLogDir, name+".log")

	file, err := os.Create(full)
	if err == nil {
		fmtWriter = file
		defer file.Close()
	}

	fmt.Fprintf(fmtWriter, "> %s", filepath.Base(os.Args[0]))
	if len(os.Args) > 0 {
		fmt.Fprintf(fmtWriter, " %s", strings.Join(os.Args[1:], " "))
	}
	fmt.Fprint(fmtWriter, "\n")

	logEnv(fmtWriter)
	fmt.Fprint(fmtWriter, "\n")

	fmtWriter.Write(ErrorBuffer.Bytes())
	fmt.Fprint(fmtWriter, "\n")

	fmt.Fprintln(fmtWriter, loggedError.Error())
	fmtWriter.Write(debug.Stack())

	if err != nil && !recursive {
		fmt.Fprintf(fmtWriter, "Unable to log panic to %s\n\n", full)
		logPanic(err, true)
	}

	return full
}

func init() {
	log.SetOutput(ErrorWriter)
}
<|endoftext|>


<commit_before>package true_git

import (
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/Masterminds/semver"
	"github.com/go-git/go-git/v5"

	"github.com/werf/logboek"

	"github.com/werf/werf/pkg/util"
)

func GitOpenWithCustomWorktreeDir(gitDir, worktreeDir string) (*git.Repository, error) {
	return git.PlainOpenWithOptions(worktreeDir, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
}

type FetchOptions struct {
	All       bool
	TagsOnly  bool
	Prune     bool
	PruneTags bool
	Unshallow bool
	RefSpecs  map[string]string
}

func Fetch(path string, options FetchOptions) error {
	command := "git"
	commandArgs := []string{"-C", path, "fetch"}

	if options.Unshallow {
		commandArgs = append(commandArgs, "--unshallow")
	}

	if options.All {
		commandArgs = append(commandArgs, "--all")
	}

	if options.TagsOnly {
		commandArgs = append(commandArgs, "--tags")
	}

	if options.Prune || options.PruneTags {
		commandArgs = append(commandArgs, "--prune")

		if options.PruneTags {
			commandArgs = append(commandArgs, "--prune-tags")
		}
	}

	for remote, refSpec := range options.RefSpecs {
		commandArgs = append(commandArgs, remote, refSpec)
	}

	logboek.Debug.LogLnDetails(command, strings.Join(commandArgs, " "))

	cmd := exec.Command(command, commandArgs...)
	cmd.Stdout = outStream
	cmd.Stderr = errStream

	return cmd.Run()
}

func IsShallowClone(path string) (bool, error) {
	if gitVersion.LessThan(semver.MustParse("2.15.0")) {
		exist, err := util.FileExists(filepath.Join(path, ".git", "shallow"))
		if err != nil {
			return false, err
		}

		return exist, nil
	}

	cmd := exec.Command("git", "-C", path, "rev-parse", "--is-shallow-repository")

	res, err := cmd.Output()
	if err != nil {
		return false, err
	}

	return strings.TrimSpace(string(res)) == "true", nil
}
<commit_msg>Git history synchronization: sync tags only with git >=2.17.0<commit_after>package true_git

import (
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/Masterminds/semver"
	"github.com/go-git/go-git/v5"

	"github.com/werf/logboek"

	"github.com/werf/werf/pkg/util"
)

func GitOpenWithCustomWorktreeDir(gitDir, worktreeDir string) (*git.Repository, error) {
	return git.PlainOpenWithOptions(worktreeDir, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
}

type FetchOptions struct {
	All       bool
	TagsOnly  bool
	Prune     bool
	PruneTags bool
	Unshallow bool
	RefSpecs  map[string]string
}

func Fetch(path string, options FetchOptions) error {
	command := "git"
	commandArgs := []string{"-C", path, "fetch"}

	if options.Unshallow {
		commandArgs = append(commandArgs, "--unshallow")
	}

	if options.All {
		commandArgs = append(commandArgs, "--all")
	}

	if options.TagsOnly {
		commandArgs = append(commandArgs, "--tags")
	}

	if options.Prune || options.PruneTags {
		commandArgs = append(commandArgs, "--prune")

		if options.PruneTags && !gitVersion.LessThan(semver.MustParse("2.17.0")) {
			commandArgs = append(commandArgs, "--prune-tags")
		}
	}

	for remote, refSpec := range options.RefSpecs {
		commandArgs = append(commandArgs, remote, refSpec)
	}

	logboek.Debug.LogLnDetails(command, strings.Join(commandArgs, " "))

	cmd := exec.Command(command, commandArgs...)
	cmd.Stdout = outStream
	cmd.Stderr = errStream

	return cmd.Run()
}

func IsShallowClone(path string) (bool, error) {
	if gitVersion.LessThan(semver.MustParse("2.15.0")) {
		exist, err := util.FileExists(filepath.Join(path, ".git", "shallow"))
		if err != nil {
			return false, err
		}

		return exist, nil
	}

	cmd := exec.Command("git", "-C", path, "rev-parse", "--is-shallow-repository")

	res, err := cmd.Output()
	if err != nil {
		return false, err
	}

	return strings.TrimSpace(string(res)) == "true", nil
}
<|endoftext|>


<commit_before>package commands

import (
	"errors"
	"flag"
	"fmt"
	"github.com/orchardup/go-orchard/authenticator"
	"github.com/orchardup/go-orchard/proxy"
	"github.com/orchardup/go-orchard/tlsconfig"
	"github.com/orchardup/go-orchard/utils"
	"github.com/orchardup/go-orchard/vendor/crypto/tls"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"strings"
	"syscall"
	"text/tabwriter"
)

type Command struct {
	Run       func(cmd *Command, args []string) error
	UsageLine string
	Short     string
	Long      string
	Flag      flag.FlagSet
}

func (c *Command) Name() string {
	name := c.UsageLine
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

func (c *Command) Usage() {
	fmt.Fprintf(os.Stderr, "Usage: %s\n\n", c.UsageLine)
	fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long))
	os.Exit(2)
}

func (c *Command) UsageError(format string, args ...interface{}) error {
	fmt.Fprintf(os.Stderr, format, args...)
	fmt.Fprintf(os.Stderr, "\nUsage: %s\n", c.UsageLine)
	os.Exit(2)
	return fmt.Errorf(format, args...)
}

var All = []*Command{
	Hosts,
	Docker,
	Proxy,
}

var HostSubcommands = []*Command{
	CreateHost,
	RemoveHost,
}

func init() {
	Hosts.Run = RunHosts
	CreateHost.Run = RunCreateHost
	RemoveHost.Run = RunRemoveHost
	Docker.Run = RunDocker
	Proxy.Run = RunProxy
}

var Hosts = &Command{
	UsageLine: "hosts",
	Short:     "Manage hosts",
	Long: `Manage hosts.

Usage: orchard hosts [COMMAND] [ARGS...]

Commands:
  ls          List hosts (default)
  create      Create a host
  rm          Remove a host

Run 'orchard hosts COMMAND -h' for more information on a command.
`,
}

var CreateHost = &Command{
	UsageLine: "create [-m MEMORY] [NAME]",
	Short:     "Create a host",
	Long: fmt.Sprintf(`Create a host.

You can optionally specify a name for the host - if not, it will be
named 'default', and 'orchard docker' commands will use it automatically.

You can also specify how much RAM the host should have with -m.
Valid amounts are %s.`, validSizes),
}

var flCreateSize = CreateHost.Flag.String("m", "512M", "")
var validSizes = "512M, 1G, 2G, 4G and 8G"

var RemoveHost = &Command{
	UsageLine: "rm [-f] [NAME]",
	Short:     "Remove a host",
	Long: `Remove a host.

You can optionally specify which host to remove - if you don't, the default
host (named 'default') will be removed.

Set -f to bypass the confirmation step, at your peril.
`,
}

var flRemoveHostForce = RemoveHost.Flag.Bool("f", false, "")

var Docker = &Command{
	UsageLine: "docker [-H HOST] [COMMAND...]",
	Short:     "Run a Docker command against a host",
	Long: `Run a Docker command against a host.

Wraps the 'docker' command-line tool - see the Docker website for reference:

    http://docs.docker.io/en/latest/reference/commandline/

You can optionally specify a host by name - if you don't, the default host
will be used.`,
}

var flDockerHost = Docker.Flag.String("H", "", "")

var Proxy = &Command{
	UsageLine: "proxy [-H HOST] [LISTEN_URL]",
	Short:     "Start a local proxy to a host's Docker daemon",
	Long: `Start a local proxy to a host's Docker daemon.

By default, listens on a Unix socket at a random path, e.g.

    $ orchard proxy
    Started proxy at unix:///tmp/orchard-12345/orchard.sock

    $ docker -H unix:///tmp/orchard-12345/orchard.sock run ubuntu echo hello world
    hello world

Instead, you can specify a URL to listen on, which can be a socket or TCP address:

    $ orchard proxy unix:///path/to/socket
    $ orchard proxy tcp://localhost:1234
`,
}

var flProxyHost = Proxy.Flag.String("H", "", "")

func RunHosts(cmd *Command, args []string) error {
	list := len(args) == 0 || (len(args) == 1 && args[0] == "ls")

	if !list {
		for _, subcommand := range HostSubcommands {
			if subcommand.Name() == args[0] {
				subcommand.Flag.Usage = func() { subcommand.Usage() }
				subcommand.Flag.Parse(args[1:])
				args = subcommand.Flag.Args()
				err := subcommand.Run(subcommand, args)
				return err
			}
		}

		return fmt.Errorf("Unknown `hosts` subcommand: %s", args[0])
	}

	httpClient, err := authenticator.Authenticate()
	if err != nil {
		return err
	}

	hosts, err := httpClient.GetHosts()
	if err != nil {
		return err
	}

	writer := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
	fmt.Fprintln(writer, "NAME\tSIZE\tIP")
	for _, host := range hosts {
		fmt.Fprintf(writer, "%s\t%s\t%s\n", host.Name, utils.HumanSize(host.Size*1024*1024), host.IPAddress)
	}
	writer.Flush()

	return nil
}

func RunCreateHost(cmd *Command, args []string) error {
	if len(args) > 1 {
		return cmd.UsageError("`orchard hosts create` expects at most 1 argument, but got more: %s", strings.Join(args[1:], " "))
	}

	httpClient, err := authenticator.Authenticate()
	if err != nil {
		return err
	}

	hostName, humanName := GetHostName(args)
	humanName = utils.Capitalize(humanName)

	size, sizeString := GetHostSize()
	if size == -1 {
		fmt.Fprintf(os.Stderr, "Sorry, %q isn't a size we support.\nValid sizes are %s.\n", sizeString, validSizes)
		return nil
	}

	host, err := httpClient.CreateHost(hostName, size)
	if err != nil {
		// HACK. api.go should decode JSON and return a specific type of error for this case.
		if strings.Contains(err.Error(), "already exists") {
			fmt.Fprintf(os.Stderr, "%s is already running.\nYou can create additional hosts with `orchard hosts create [NAME]`.\n", humanName)
			return nil
		}
		if strings.Contains(err.Error(), "Invalid value") {
			fmt.Fprintf(os.Stderr, "Sorry, '%s' isn't a valid host name.\nHost names can only contain lowercase letters, numbers and underscores.\n", hostName)
			return nil
		}
		if strings.Contains(err.Error(), "Unsupported size") {
			fmt.Fprintf(os.Stderr, "Sorry, %q isn't a size we support.\nValid sizes are %s.\n", sizeString, validSizes)
			return nil
		}

		return err
	}
	fmt.Fprintf(os.Stderr, "%s running at %s\n", humanName, host.IPAddress)

	return nil
}

func RunRemoveHost(cmd *Command, args []string) error {
	if len(args) > 1 {
		return cmd.UsageError("`orchard hosts rm` expects at most 1 argument, but got more: %s", strings.Join(args[1:], " "))
	}

	hostName, humanName := GetHostName(args)

	if !*flRemoveHostForce {
		var confirm string
		fmt.Printf("Going to remove %s. All data on it will be lost.\n", humanName)
		fmt.Print("Are you sure you're ready? [yN] ")
		fmt.Scanln(&confirm)

		if strings.ToLower(confirm) != "y" {
			return nil
		}
	}

	httpClient, err := authenticator.Authenticate()
	if err != nil {
		return err
	}

	err = httpClient.DeleteHost(hostName)
	if err != nil {
		// HACK. api.go should decode JSON and return a specific type of error for this case.
		if strings.Contains(err.Error(), "Not found") {
			fmt.Fprintf(os.Stderr, "%s doesn't seem to be running.\nYou can view your running hosts with `orchard hosts`.\n", utils.Capitalize(humanName))
			return nil
		}

		return err
	}
	fmt.Fprintf(os.Stderr, "Removed %s\n", humanName)

	return nil
}

func RunDocker(cmd *Command, args []string) error {
	return WithDockerProxy("", func(listenURL string) error {
		err := CallDocker(args, []string{"DOCKER_HOST=" + listenURL})
		if err != nil {
			return fmt.Errorf("Docker exited with error")
		}
		return nil
	})
}

func RunProxy(cmd *Command, args []string) error {
	specifiedURL := ""

	if len(args) == 1 {
		specifiedURL = args[0]
	} else if len(args) > 1 {
		return cmd.UsageError("`orchard proxy` expects at most 1 argument, but got: %s", strings.Join(args, " "))
	}

	return WithDockerProxy(specifiedURL, func(listenURL string) error {
		fmt.Fprintf(os.Stderr, "Started proxy at %s\n", listenURL)

		c := make(chan os.Signal)
		signal.Notify(c, syscall.SIGINT, syscall.SIGKILL)
		<-c

		fmt.Fprintln(os.Stderr, "\nStopping proxy")
		return nil
	})
}

func WithDockerProxy(listenURL string, callback func(string) error) error {
	hostName := "default"
	if *flDockerHost != "" {
		hostName = *flDockerHost
	}

	if listenURL == "" {
		dirname, err := ioutil.TempDir("/tmp", "orchard-")
		if err != nil {
			return fmt.Errorf("Error creating temporary directory: %s\n", err)
		}
		defer os.RemoveAll(dirname)
		listenURL = fmt.Sprintf("unix://%s", path.Join(dirname, "orchard.sock"))
	}

	listenType, listenAddr, err := ListenArgs(listenURL)
	if err != nil {
		return err
	}

	p, err := MakeProxy(listenType, listenAddr, hostName)
	if err != nil {
		return fmt.Errorf("Error starting proxy: %v\n", err)
	}

	go p.Start()
	defer p.Stop()

	if err := <-p.ErrorChannel; err != nil {
		return fmt.Errorf("Error starting proxy: %v\n", err)
	}

	if err := callback(listenURL); err != nil {
		return err
	}

	return nil
}

var validListenTypes = []string{"tcp", "tcp4", "tcp6", "unix", "unixpacket"}

func ListenArgs(url string) (string, string, error) {
	parts := strings.SplitN(url, "://", 2)
	if len(parts) != 2 {
		return "", "", fmt.Errorf("Invalid URL: %q", url)
	}
	for _, validType := range validListenTypes {
		if parts[0] == validType {
			return parts[0], parts[1], nil
		}
	}
	return "", "", fmt.Errorf("Invalid URL type: %q", parts[0])
}

func MakeProxy(listenType, listenAddr string, hostName string) (*proxy.Proxy, error) {
	httpClient, err := authenticator.Authenticate()
	if err != nil {
		return nil, err
	}

	host, err := httpClient.GetHost(hostName)
	if err != nil {
		// HACK. api.go should decode JSON and return a specific type of error for this case.
		if strings.Contains(err.Error(), "Not found") {
			humanName := GetHumanHostName(hostName)
			return nil, fmt.Errorf("%s doesn't seem to be running.\nYou can create it with `orchard hosts create %s`.", utils.Capitalize(humanName), hostName)
		}

		return nil, err
	}
	destination := host.IPAddress + ":4243"

	certData := []byte(host.ClientCert)
	keyData := []byte(host.ClientKey)
	config, err := tlsconfig.GetTLSConfig(certData, keyData)
	if err != nil {
		return nil, err
	}

	return proxy.New(
		func() (net.Listener, error) { return net.Listen(listenType, listenAddr) },
		func() (net.Conn, error) { return tls.Dial("tcp", destination, config) },
	), nil
}

func CallDocker(args []string, env []string) error {
	dockerPath := GetDockerPath()
	if dockerPath == "" {
		return errors.New("Can't find `docker` executable in $PATH.\nYou might need to install it: http://docs.docker.io/en/latest/installation/#installation-list")
	}

	cmd := exec.Command(dockerPath, args...)
	cmd.Env = env
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func GetDockerPath() string {
	for _, dir := range strings.Split(os.Getenv("PATH"), ":") {
		dockerPath := path.Join(dir, "docker")
		_, err := os.Stat(dockerPath)
		if err == nil {
			return dockerPath
		}
	}
	return ""
}

func GetHostName(args []string) (string, string) {
	hostName := "default"

	if len(args) > 0 {
		hostName = args[0]
	}

	return hostName, GetHumanHostName(hostName)
}

func GetHumanHostName(hostName string) string {
	if hostName == "default" {
		return "default host"
	} else {
		return fmt.Sprintf("host '%s'", hostName)
	}
}

func GetHostSize() (int, string) {
	sizeString := *flCreateSize

	bytes, err := utils.RAMInBytes(sizeString)
	if err != nil {
		return -1, sizeString
	}

	megs := bytes / (1024 * 1024)
	if megs < 1 {
		return -1, sizeString
	}

	return int(megs), sizeString
}
<commit_msg>Fix: 'orchard proxy' wasn't picking up -H argument<commit_after>package commands

import (
	"errors"
	"flag"
	"fmt"
	"github.com/orchardup/go-orchard/authenticator"
	"github.com/orchardup/go-orchard/proxy"
	"github.com/orchardup/go-orchard/tlsconfig"
	"github.com/orchardup/go-orchard/utils"
	"github.com/orchardup/go-orchard/vendor/crypto/tls"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"strings"
	"syscall"
	"text/tabwriter"
)

type Command struct {
	Run       func(cmd *Command, args []string) error
	UsageLine string
	Short     string
	Long      string
	Flag      flag.FlagSet
}

func (c *Command) Name() string {
	name := c.UsageLine
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

func (c *Command) Usage() {
	fmt.Fprintf(os.Stderr, "Usage: %s\n\n", c.UsageLine)
	fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long))
	os.Exit(2)
}

func (c *Command) UsageError(format string, args ...interface{}) error {
	fmt.Fprintf(os.Stderr, format, args...)
	fmt.Fprintf(os.Stderr, "\nUsage: %s\n", c.UsageLine)
	os.Exit(2)
	return fmt.Errorf(format, args...)
}

var All = []*Command{
	Hosts,
	Docker,
	Proxy,
}

var HostSubcommands = []*Command{
	CreateHost,
	RemoveHost,
}

func init() {
	Hosts.Run = RunHosts
	CreateHost.Run = RunCreateHost
	RemoveHost.Run = RunRemoveHost
	Docker.Run = RunDocker
	Proxy.Run = RunProxy
}

var Hosts = &Command{
	UsageLine: "hosts",
	Short:     "Manage hosts",
	Long: `Manage hosts.

Usage: orchard hosts [COMMAND] [ARGS...]

Commands:
  ls          List hosts (default)
  create      Create a host
  rm          Remove a host

Run 'orchard hosts COMMAND -h' for more information on a command.
`,
}

var CreateHost = &Command{
	UsageLine: "create [-m MEMORY] [NAME]",
	Short:     "Create a host",
	Long: fmt.Sprintf(`Create a host.

You can optionally specify a name for the host - if not, it will be
named 'default', and 'orchard docker' commands will use it automatically.

You can also specify how much RAM the host should have with -m.
Valid amounts are %s.`, validSizes),
}

var flCreateSize = CreateHost.Flag.String("m", "512M", "")
var validSizes = "512M, 1G, 2G, 4G and 8G"

var RemoveHost = &Command{
	UsageLine: "rm [-f] [NAME]",
	Short:     "Remove a host",
	Long: `Remove a host.

You can optionally specify which host to remove - if you don't, the default
host (named 'default') will be removed.

Set -f to bypass the confirmation step, at your peril.
`,
}

var flRemoveHostForce = RemoveHost.Flag.Bool("f", false, "")

var Docker = &Command{
	UsageLine: "docker [-H HOST] [COMMAND...]",
	Short:     "Run a Docker command against a host",
	Long: `Run a Docker command against a host.

Wraps the 'docker' command-line tool - see the Docker website for reference:

    http://docs.docker.io/en/latest/reference/commandline/

You can optionally specify a host by name - if you don't, the default host
will be used.`,
}

var flDockerHost = Docker.Flag.String("H", "", "")

var Proxy = &Command{
	UsageLine: "proxy [-H HOST] [LISTEN_URL]",
	Short:     "Start a local proxy to a host's Docker daemon",
	Long: `Start a local proxy to a host's Docker daemon.

By default, listens on a Unix socket at a random path, e.g.

    $ orchard proxy
    Started proxy at unix:///tmp/orchard-12345/orchard.sock

    $ docker -H unix:///tmp/orchard-12345/orchard.sock run ubuntu echo hello world
    hello world

Instead, you can specify a URL to listen on, which can be a socket or TCP address:

    $ orchard proxy unix:///path/to/socket
    $ orchard proxy tcp://localhost:1234
`,
}

var flProxyHost = Proxy.Flag.String("H", "", "")

func RunHosts(cmd *Command, args []string) error {
	list := len(args) == 0 || (len(args) == 1 && args[0] == "ls")

	if !list {
		for _, subcommand := range HostSubcommands {
			if subcommand.Name() == args[0] {
				subcommand.Flag.Usage = func() { subcommand.Usage() }
				subcommand.Flag.Parse(args[1:])
				args = subcommand.Flag.Args()
				err := subcommand.Run(subcommand, args)
				return err
			}
		}

		return fmt.Errorf("Unknown `hosts` subcommand: %s", args[0])
	}

	httpClient, err := authenticator.Authenticate()
	if err != nil {
		return err
	}

	hosts, err := httpClient.GetHosts()
	if err != nil {
		return err
	}

	writer := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
	fmt.Fprintln(writer, "NAME\tSIZE\tIP")
	for _, host := range hosts {
		fmt.Fprintf(writer, "%s\t%s\t%s\n", host.Name, utils.HumanSize(host.Size*1024*1024), host.IPAddress)
	}
	writer.Flush()

	return nil
}

func RunCreateHost(cmd *Command, args []string) error {
	if len(args) > 1 {
		return cmd.UsageError("`orchard hosts create` expects at most 1 argument, but got more: %s", strings.Join(args[1:], " "))
	}

	httpClient, err := authenticator.Authenticate()
	if err != nil {
		return err
	}

	hostName, humanName := GetHostName(args)
	humanName = utils.Capitalize(humanName)

	size, sizeString := GetHostSize()
	if size == -1 {
		fmt.Fprintf(os.Stderr, "Sorry, %q isn't a size we support.\nValid sizes are %s.\n", sizeString, validSizes)
		return nil
	}

	host, err := httpClient.CreateHost(hostName, size)
	if err != nil {
		// HACK. api.go should decode JSON and return a specific type of error for this case.
		if strings.Contains(err.Error(), "already exists") {
			fmt.Fprintf(os.Stderr, "%s is already running.\nYou can create additional hosts with `orchard hosts create [NAME]`.\n", humanName)
			return nil
		}
		if strings.Contains(err.Error(), "Invalid value") {
			fmt.Fprintf(os.Stderr, "Sorry, '%s' isn't a valid host name.\nHost names can only contain lowercase letters, numbers and underscores.\n", hostName)
			return nil
		}
		if strings.Contains(err.Error(), "Unsupported size") {
			fmt.Fprintf(os.Stderr, "Sorry, %q isn't a size we support.\nValid sizes are %s.\n", sizeString, validSizes)
			return nil
		}

		return err
	}
	fmt.Fprintf(os.Stderr, "%s running at %s\n", humanName, host.IPAddress)

	return nil
}

func RunRemoveHost(cmd *Command, args []string) error {
	if len(args) > 1 {
		return cmd.UsageError("`orchard hosts rm` expects at most 1 argument, but got more: %s", strings.Join(args[1:], " "))
	}

	hostName, humanName := GetHostName(args)

	if !*flRemoveHostForce {
		var confirm string
		fmt.Printf("Going to remove %s. All data on it will be lost.\n", humanName)
		fmt.Print("Are you sure you're ready? [yN] ")
		fmt.Scanln(&confirm)

		if strings.ToLower(confirm) != "y" {
			return nil
		}
	}

	httpClient, err := authenticator.Authenticate()
	if err != nil {
		return err
	}

	err = httpClient.DeleteHost(hostName)
	if err != nil {
		// HACK. api.go should decode JSON and return a specific type of error for this case.
		if strings.Contains(err.Error(), "Not found") {
			fmt.Fprintf(os.Stderr, "%s doesn't seem to be running.\nYou can view your running hosts with `orchard hosts`.\n", utils.Capitalize(humanName))
			return nil
		}

		return err
	}
	fmt.Fprintf(os.Stderr, "Removed %s\n", humanName)

	return nil
}

func RunDocker(cmd *Command, args []string) error {
	return WithDockerProxy("", *flDockerHost, func(listenURL string) error {
		err := CallDocker(args, []string{"DOCKER_HOST=" + listenURL})
		if err != nil {
			return fmt.Errorf("Docker exited with error")
		}
		return nil
	})
}

func RunProxy(cmd *Command, args []string) error {
	specifiedURL := ""

	if len(args) == 1 {
		specifiedURL = args[0]
	} else if len(args) > 1 {
		return cmd.UsageError("`orchard proxy` expects at most 1 argument, but got: %s", strings.Join(args, " "))
	}

	return WithDockerProxy(specifiedURL, *flProxyHost, func(listenURL string) error {
		fmt.Fprintf(os.Stderr, "Started proxy at %s\n", listenURL)

		c := make(chan os.Signal)
		signal.Notify(c, syscall.SIGINT, syscall.SIGKILL)
		<-c

		fmt.Fprintln(os.Stderr, "\nStopping proxy")
		return nil
	})
}

func WithDockerProxy(listenURL, hostName string, callback func(string) error) error {
	if hostName == "" {
		hostName = "default"
	}

	if listenURL == "" {
		dirname, err := ioutil.TempDir("/tmp", "orchard-")
		if err != nil {
			return fmt.Errorf("Error creating temporary directory: %s\n", err)
		}
		defer os.RemoveAll(dirname)
		listenURL = fmt.Sprintf("unix://%s", path.Join(dirname, "orchard.sock"))
	}

	listenType, listenAddr, err := ListenArgs(listenURL)
	if err != nil {
		return err
	}

	p, err := MakeProxy(listenType, listenAddr, hostName)
	if err != nil {
		return fmt.Errorf("Error starting proxy: %v\n", err)
	}

	go p.Start()
	defer p.Stop()

	if err := <-p.ErrorChannel; err != nil {
		return fmt.Errorf("Error starting proxy: %v\n", err)
	}

	if err := callback(listenURL); err != nil {
		return err
	}

	return nil
}

var validListenTypes = []string{"tcp", "tcp4", "tcp6", "unix", "unixpacket"}

func ListenArgs(url string) (string, string, error) {
	parts := strings.SplitN(url, "://", 2)
	if len(parts) != 2 {
		return "", "", fmt.Errorf("Invalid URL: %q", url)
	}
	for _, validType := range validListenTypes {
		if parts[0] == validType {
			return parts[0], parts[1], nil
		}
	}
	return "", "", fmt.Errorf("Invalid URL type: %q", parts[0])
}

func MakeProxy(listenType, listenAddr string, hostName string) (*proxy.Proxy, error) {
	httpClient, err := authenticator.Authenticate()
	if err != nil {
		return nil, err
	}

	host, err := httpClient.GetHost(hostName)
	if err != nil {
		// HACK. api.go should decode JSON and return a specific type of error for this case.
		if strings.Contains(err.Error(), "Not found") {
			humanName := GetHumanHostName(hostName)
			return nil, fmt.Errorf("%s doesn't seem to be running.\nYou can create it with `orchard hosts create %s`.", utils.Capitalize(humanName), hostName)
		}

		return nil, err
	}
	destination := host.IPAddress + ":4243"

	certData := []byte(host.ClientCert)
	keyData := []byte(host.ClientKey)
	config, err := tlsconfig.GetTLSConfig(certData, keyData)
	if err != nil {
		return nil, err
	}

	return proxy.New(
		func() (net.Listener, error) { return net.Listen(listenType, listenAddr) },
		func() (net.Conn, error) { return tls.Dial("tcp", destination, config) },
	), nil
}

func CallDocker(args []string, env []string) error {
	dockerPath := GetDockerPath()
	if dockerPath == "" {
		return errors.New("Can't find `docker` executable in $PATH.\nYou might need to install it: http://docs.docker.io/en/latest/installation/#installation-list")
	}

	cmd := exec.Command(dockerPath, args...)
	cmd.Env = env
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func GetDockerPath() string {
	for _, dir := range strings.Split(os.Getenv("PATH"), ":") {
		dockerPath := path.Join(dir, "docker")
		_, err := os.Stat(dockerPath)
		if err == nil {
			return dockerPath
		}
	}
	return ""
}

func GetHostName(args []string) (string, string) {
	hostName := "default"

	if len(args) > 0 {
		hostName = args[0]
	}

	return hostName, GetHumanHostName(hostName)
}

func GetHumanHostName(hostName string) string {
	if hostName == "default" {
		return "default host"
	} else {
		return fmt.Sprintf("host '%s'", hostName)
	}
}

func GetHostSize() (int, string) {
	sizeString := *flCreateSize

	bytes, err := utils.RAMInBytes(sizeString)
	if err != nil {
		return -1, sizeString
	}

	megs := bytes / (1024 * 1024)
	if megs < 1 {
		return -1, sizeString
	}

	return int(megs), sizeString
}
<|endoftext|>


<commit_before>package commands

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/github/git-lfs/lfs"
	"github.com/spf13/cobra"
)

var (
	Debugging    = false
	ErrorBuffer  = &bytes.Buffer{}
	ErrorWriter  = io.MultiWriter(os.Stderr, ErrorBuffer)
	OutputWriter = io.MultiWriter(os.Stdout, ErrorBuffer)
	RootCmd      = &cobra.Command{
		Use:   "git-lfs",
		Short: "Git LFS provides large file storage to Git.",
		Run: func(cmd *cobra.Command, args []string) {
			versionCommand(cmd, args)
			cmd.Usage()
		},
	}
)

// Error prints a formatted message to Stderr.  It also gets printed to the
// panic log if one is created for this command.
func Error(format string, args ...interface{}) {
	line := fmt.Sprintf(format, args...)
	fmt.Fprintln(ErrorWriter, line)
}

// Print prints a formatted message to Stdout.  It also gets printed to the
// panic log if one is created for this command.
func Print(format string, args ...interface{}) {
	line := fmt.Sprintf(format, args...)
	fmt.Fprintln(OutputWriter, line)
}

// Exit prints a formatted message and exits.
func Exit(format string, args ...interface{}) {
	Error(format, args...)
	os.Exit(2)
}

// Debug prints a formatted message if debugging is enabled.  The formatted
// message also shows up in the panic log, if created.
func Debug(format string, args ...interface{}) {
	if !Debugging {
		return
	}
	log.Printf(format, args...)
}

// LoggedError prints a formatted message to Stderr and writes a stack trace for
// the error to a log file without exiting.
func LoggedError(err error, format string, args ...interface{}) {
	Error(format, args...)
	file := handlePanic(err)

	if len(file) > 0 {
		fmt.Fprintf(os.Stderr, "\nErrors logged to %s\nUse `git lfs logs last` to view the log.\n", file)
	}
}

// Panic prints a formatted message, and writes a stack trace for the error to
// a log file before exiting.
func Panic(err error, format string, args ...interface{}) {
	LoggedError(err, format, args...)
	os.Exit(2)
}

func Run() {
	RootCmd.Execute()
}

func PipeMediaCommand(name string, args ...string) error {
	return PipeCommand("bin/"+name, args...)
}

func PipeCommand(name string, args ...string) error {
	cmd := exec.Command(name, args...)
	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	return cmd.Run()
}

func requireStdin(msg string) {
	stat, _ := os.Stdin.Stat()
	if (stat.Mode() & os.ModeCharDevice) != 0 {
		Error("Cannot read from STDIN. %s", msg)
		os.Exit(1)
	}
}

func handlePanic(err error) string {
	if err == nil {
		return ""
	}

	return logPanic(err, false)
}

func logEnv(w io.Writer) {
	for _, env := range lfs.Environ() {
		fmt.Fprintln(w, env)
	}
}

func logPanic(loggedError error, recursive bool) string {
	var fmtWriter io.Writer = os.Stderr

	if err := os.MkdirAll(lfs.LocalLogDir, 0755); err != nil {
		fmt.Fprintf(fmtWriter, "Unable to log panic to %s: %s\n\n", lfs.LocalLogDir, err.Error())
		return ""
	}

	now := time.Now()
	name := now.Format("20060102T150405.999999999")
	full := filepath.Join(lfs.LocalLogDir, name+".log")

	if file, err := os.Create(full); err != nil {
		if !recursive {
			filename := full
			full = ""
			defer func() {
				fmt.Fprintf(fmtWriter, "Unable to log panic to %s\n\n", filename)
				logPanic(err, true)
			}()
		}
	} else {
		fmtWriter = file
		defer file.Close()
	}

	fmt.Fprintf(fmtWriter, "> %s", filepath.Base(os.Args[0]))
	if len(os.Args) > 0 {
		fmt.Fprintf(fmtWriter, " %s", strings.Join(os.Args[1:], " "))
	}
	fmt.Fprintln(fmtWriter)

	logEnv(fmtWriter)
	fmt.Fprintln(fmtWriter)

	fmtWriter.Write(ErrorBuffer.Bytes())
	fmt.Fprintln(fmtWriter)

	fmt.Fprintln(fmtWriter, loggedError.Error())

	if wErr, ok := loggedError.(ErrorWithStack); ok {
		fmt.Fprintln(fmtWriter, wErr.InnerError())
		for key, value := range wErr.Context() {
			fmt.Fprintf(fmtWriter, "%s=%s\n", key, value)
		}
		fmtWriter.Write(wErr.Stack())
	} else {
		fmtWriter.Write(lfs.Stack())
	}

	return full
}

type ErrorWithStack interface {
	Context() map[string]string
	InnerError() string
	Stack() []byte
}

func init() {
	log.SetOutput(ErrorWriter)
}
<commit_msg>Don't try to log panic to file in recursive calls<commit_after>package commands

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/github/git-lfs/lfs"
	"github.com/spf13/cobra"
)

var (
	Debugging    = false
	ErrorBuffer  = &bytes.Buffer{}
	ErrorWriter  = io.MultiWriter(os.Stderr, ErrorBuffer)
	OutputWriter = io.MultiWriter(os.Stdout, ErrorBuffer)
	RootCmd      = &cobra.Command{
		Use:   "git-lfs",
		Short: "Git LFS provides large file storage to Git.",
		Run: func(cmd *cobra.Command, args []string) {
			versionCommand(cmd, args)
			cmd.Usage()
		},
	}
)

// Error prints a formatted message to Stderr.  It also gets printed to the
// panic log if one is created for this command.
func Error(format string, args ...interface{}) {
	line := fmt.Sprintf(format, args...)
	fmt.Fprintln(ErrorWriter, line)
}

// Print prints a formatted message to Stdout.  It also gets printed to the
// panic log if one is created for this command.
func Print(format string, args ...interface{}) {
	line := fmt.Sprintf(format, args...)
	fmt.Fprintln(OutputWriter, line)
}

// Exit prints a formatted message and exits.
func Exit(format string, args ...interface{}) {
	Error(format, args...)
	os.Exit(2)
}

// Debug prints a formatted message if debugging is enabled.  The formatted
// message also shows up in the panic log, if created.
func Debug(format string, args ...interface{}) {
	if !Debugging {
		return
	}
	log.Printf(format, args...)
}

// LoggedError prints a formatted message to Stderr and writes a stack trace for
// the error to a log file without exiting.
func LoggedError(err error, format string, args ...interface{}) {
	Error(format, args...)
	file := handlePanic(err)

	if len(file) > 0 {
		fmt.Fprintf(os.Stderr, "\nErrors logged to %s\nUse `git lfs logs last` to view the log.\n", file)
	}
}

// Panic prints a formatted message, and writes a stack trace for the error to
// a log file before exiting.
func Panic(err error, format string, args ...interface{}) {
	LoggedError(err, format, args...)
	os.Exit(2)
}

func Run() {
	RootCmd.Execute()
}

func PipeMediaCommand(name string, args ...string) error {
	return PipeCommand("bin/"+name, args...)
}

func PipeCommand(name string, args ...string) error {
	cmd := exec.Command(name, args...)
	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	return cmd.Run()
}

func requireStdin(msg string) {
	stat, _ := os.Stdin.Stat()
	if (stat.Mode() & os.ModeCharDevice) != 0 {
		Error("Cannot read from STDIN. %s", msg)
		os.Exit(1)
	}
}

func handlePanic(err error) string {
	if err == nil {
		return ""
	}

	return logPanic(err, false)
}

func logEnv(w io.Writer) {
	for _, env := range lfs.Environ() {
		fmt.Fprintln(w, env)
	}
}

func logPanic(loggedError error, recursive bool) string {
	var fmtWriter io.Writer = os.Stderr

	if !recursive {
		if err := os.MkdirAll(lfs.LocalLogDir, 0755); err != nil {
			fmt.Fprintf(fmtWriter, "Unable to log panic to %s: %s\n\n", lfs.LocalLogDir, err.Error())
			return ""
		}

		now := time.Now()
		name := now.Format("20060102T150405.999999999")
		full := filepath.Join(lfs.LocalLogDir, name+".log")

		if file, err := os.Create(full); err != nil {
			filename := full
			full = ""
			defer func() {
				fmt.Fprintf(fmtWriter, "Unable to log panic to %s\n\n", filename)
				logPanic(err, true)
			}()
		} else {
			fmtWriter = file
			defer file.Close()
		}
	}

	fmt.Fprintf(fmtWriter, "> %s", filepath.Base(os.Args[0]))
	if len(os.Args) > 0 {
		fmt.Fprintf(fmtWriter, " %s", strings.Join(os.Args[1:], " "))
	}
	fmt.Fprintln(fmtWriter)

	logEnv(fmtWriter)
	fmt.Fprintln(fmtWriter)

	fmtWriter.Write(ErrorBuffer.Bytes())
	fmt.Fprintln(fmtWriter)

	fmt.Fprintln(fmtWriter, loggedError.Error())

	if wErr, ok := loggedError.(ErrorWithStack); ok {
		fmt.Fprintln(fmtWriter, wErr.InnerError())
		for key, value := range wErr.Context() {
			fmt.Fprintf(fmtWriter, "%s=%s\n", key, value)
		}
		fmtWriter.Write(wErr.Stack())
	} else {
		fmtWriter.Write(lfs.Stack())
	}

	return full
}

type ErrorWithStack interface {
	Context() map[string]string
	InnerError() string
	Stack() []byte
}

func init() {
	log.SetOutput(ErrorWriter)
}
<|endoftext|>


<commit_before>package common

import (
	"firempq/util"
)

const (
	STYPE_PRIORITY_QUEUE      = "priority_queue" // Highest priority goes first.
	STYPE_FIFO_QUEUE          = "fifo_queue"     // Standard FIFO
	STYPE_DOUBLE_SIDED_QUEUE  = "ds_queue"       // Double sided queue
	STYPE_FAIR_PRIORITY_QUEUE = "fair_queue"     // POPs are fairly distributed across all priorities.
	STYPE_COUNTERS            = "counters"       // Atomic counters.
	STYPE_SEQUENCE_READ       = "seqreader"      // Data to read in sequential order.
)

type ServiceMetaInfo struct {
	Stype    string
	Id       int32
	Name     string
	CreateTs int64
	Disabled bool
}

func NewServiceMetaInfo(stype string, id int32, name string) *ServiceMetaInfo {
	return &ServiceMetaInfo{
		Stype:    stype,
		Id:       id,
		Name:     name,
		CreateTs: util.Uts(),
		Disabled: false,
	}
}

func ServiceInfoFromBinary(data []byte) (*ServiceMetaInfo, error) {
	smi := ServiceMetaInfo{}
	err := util.StructFromBinary(&smi, data)
	if err != nil {
		return nil, err
	}
	return &smi, nil
}

func (q *ServiceMetaInfo) ToBinary() []byte {
	return util.StructToBinary(q)
}
<commit_msg>Shorter names for available services.<commit_after>package common

import (
	"firempq/util"
)

const (
	STYPE_PRIORITY_QUEUE      = "pqueue"    // Highest priority goes first.
	STYPE_DOUBLE_SIDED_QUEUE  = "dsqueue"   // Double sided queue
	STYPE_FIFO_QUEUE          = "fifoqueue" // Standard FIFO
	STYPE_FAIR_PRIORITY_QUEUE = "fairqueue" // POPs are fairly distributed across all priorities.
	STYPE_COUNTERS            = "counters"  // Atomic counters.
	STYPE_SEQUENCE_READ       = "seqreader" // Data to read in sequential order.
)

type ServiceMetaInfo struct {
	Stype    string
	Id       int32
	Name     string
	CreateTs int64
	Disabled bool
}

func NewServiceMetaInfo(stype string, id int32, name string) *ServiceMetaInfo {
	return &ServiceMetaInfo{
		Stype:    stype,
		Id:       id,
		Name:     name,
		CreateTs: util.Uts(),
		Disabled: false,
	}
}

func ServiceInfoFromBinary(data []byte) (*ServiceMetaInfo, error) {
	smi := ServiceMetaInfo{}
	err := util.StructFromBinary(&smi, data)
	if err != nil {
		return nil, err
	}
	return &smi, nil
}

func (q *ServiceMetaInfo) ToBinary() []byte {
	return util.StructToBinary(q)
}
<|endoftext|>