<commit_before>/*
Copyright 2016 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 apiserver

import (
	"context"
	"net/http"
	"net/url"
	"strings"
	"sync/atomic"
	"time"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/httpstream"
	utilnet "k8s.io/apimachinery/pkg/util/net"
	"k8s.io/apimachinery/pkg/util/proxy"
	auditinternal "k8s.io/apiserver/pkg/apis/audit"
	"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
	endpointmetrics "k8s.io/apiserver/pkg/endpoints/metrics"
	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/server/egressselector"
	utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
	"k8s.io/apiserver/pkg/util/x509metrics"
	restclient "k8s.io/client-go/rest"
	"k8s.io/client-go/transport"
	"k8s.io/klog/v2"
	apiregistrationv1api "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
	apiregistrationv1apihelper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper"
)

const (
	aggregatorComponent string = "aggregator"

	aggregatedDiscoveryTimeout = 5 * time.Second
)

type certKeyFunc func() ([]byte, []byte)

// proxyHandler provides a http.Handler which will proxy traffic to locations
// specified by items implementing Redirector.
type proxyHandler struct {
	// localDelegate is used to satisfy local APIServices
	localDelegate http.Handler

	// proxyCurrentCertKeyContent holds the client cert used to identify this proxy. Backing APIServices use this to confirm the proxy's identity
	proxyCurrentCertKeyContent certKeyFunc
	proxyTransport             *http.Transport

	// Endpoints based routing to map from cluster IP to routable IP
	serviceResolver ServiceResolver

	handlingInfo atomic.Value

	// egressSelector selects the proper egress dialer to communicate with the custom apiserver
	// overwrites proxyTransport dialer if not nil
	egressSelector *egressselector.EgressSelector
}

type proxyHandlingInfo struct {
	// local indicates that this APIService is locally satisfied
	local bool

	// name is the name of the APIService
	name string
	// restConfig holds the information for building a roundtripper
	restConfig *restclient.Config
	// transportBuildingError is an error produced while building the transport.  If this
	// is non-nil, it will be reported to clients.
	transportBuildingError error
	// proxyRoundTripper is the re-useable portion of the transport.  It does not vary with any request.
	proxyRoundTripper http.RoundTripper
	// serviceName is the name of the service this handler proxies to
	serviceName string
	// namespace is the namespace the service lives in
	serviceNamespace string
	// serviceAvailable indicates this APIService is available or not
	serviceAvailable bool
	// servicePort is the port of the service this handler proxies to
	servicePort int32
}

func proxyError(w http.ResponseWriter, req *http.Request, error string, code int) {
	http.Error(w, error, code)

	ctx := req.Context()
	info, ok := genericapirequest.RequestInfoFrom(ctx)
	if !ok {
		klog.Warning("no RequestInfo found in the context")
		return
	}
	// TODO: record long-running request differently? The long-running check func does not necessarily match the one of the aggregated apiserver
	endpointmetrics.RecordRequestTermination(req, info, aggregatorComponent, code)
}

func (r *proxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	value := r.handlingInfo.Load()
	if value == nil {
		r.localDelegate.ServeHTTP(w, req)
		return
	}
	handlingInfo := value.(proxyHandlingInfo)
	if handlingInfo.local {
		if r.localDelegate == nil {
			http.Error(w, "", http.StatusNotFound)
			return
		}
		r.localDelegate.ServeHTTP(w, req)
		return
	}

	if !handlingInfo.serviceAvailable {
		proxyError(w, req, "service unavailable", http.StatusServiceUnavailable)
		return
	}

	if handlingInfo.transportBuildingError != nil {
		proxyError(w, req, handlingInfo.transportBuildingError.Error(), http.StatusInternalServerError)
		return
	}

	user, ok := genericapirequest.UserFrom(req.Context())
	if !ok {
		proxyError(w, req, "missing user", http.StatusInternalServerError)
		return
	}

	// write a new location based on the existing request pointed at the target service
	location := &url.URL{}
	location.Scheme = "https"
	rloc, err := r.serviceResolver.ResolveEndpoint(handlingInfo.serviceNamespace, handlingInfo.serviceName, handlingInfo.servicePort)
	if err != nil {
		klog.Errorf("error resolving %s/%s: %v", handlingInfo.serviceNamespace, handlingInfo.serviceName, err)
		proxyError(w, req, "service unavailable", http.StatusServiceUnavailable)
		return
	}
	location.Host = rloc.Host
	location.Path = req.URL.Path
	location.RawQuery = req.URL.Query().Encode()

	newReq, cancelFn := newRequestForProxy(location, req)
	defer cancelFn()

	if handlingInfo.proxyRoundTripper == nil {
		proxyError(w, req, "", http.StatusNotFound)
		return
	}

	proxyRoundTripper := handlingInfo.proxyRoundTripper
	upgrade := httpstream.IsUpgradeRequest(req)

	proxyRoundTripper = transport.NewAuthProxyRoundTripper(user.GetName(), user.GetGroups(), user.GetExtra(), proxyRoundTripper)

	// If we are upgrading, then the upgrade path tries to use this request with the TLS config we provide, but it does
	// NOT use the proxyRoundTripper.  It's a direct dial that bypasses the proxyRoundTripper.  This means that we have to
	// attach the "correct" user headers to the request ahead of time.
	if upgrade {
		transport.SetAuthProxyHeaders(newReq, user.GetName(), user.GetGroups(), user.GetExtra())
	}

	handler := proxy.NewUpgradeAwareHandler(location, proxyRoundTripper, true, upgrade, &responder{w: w})
	handler.InterceptRedirects = false
	handler.RequireSameHostRedirects = false
	utilflowcontrol.RequestDelegated(req.Context())
	handler.ServeHTTP(w, newReq)
}

// newRequestForProxy returns a shallow copy of the original request with a context that may include a timeout for discovery requests
func newRequestForProxy(location *url.URL, req *http.Request) (*http.Request, context.CancelFunc) {
	newCtx := req.Context()
	cancelFn := func() {}

	if requestInfo, ok := genericapirequest.RequestInfoFrom(req.Context()); ok {
		// trim leading and trailing slashes. Then "/apis/group/version" requests are for discovery, so if we have exactly three
		// segments that we are going to proxy, we have a discovery request.
		if !requestInfo.IsResourceRequest && len(strings.Split(strings.Trim(requestInfo.Path, "/"), "/")) == 3 {
			// discovery requests are used by kubectl and others to determine which resources a server has.  This is a cheap call that
			// should be fast for every aggregated apiserver.  Latency for aggregation is expected to be low (as for all extensions)
			// so forcing a short timeout here helps responsiveness of all clients.
			newCtx, cancelFn = context.WithTimeout(newCtx, aggregatedDiscoveryTimeout)
		}
	}

	// WithContext creates a shallow clone of the request with the same context.
	newReq := req.WithContext(newCtx)
	newReq.Header = utilnet.CloneHeader(req.Header)
	newReq.URL = location
	newReq.Host = location.Host

	// If the original request has an audit ID, let's make sure we propagate this
	// to the aggregated server.
	if auditID, found := genericapirequest.AuditIDFrom(req.Context()); found {
		newReq.Header.Set(auditinternal.HeaderAuditID, string(auditID))
	}

	return newReq, cancelFn
}

// responder implements rest.Responder for assisting a connector in writing objects or errors.
type responder struct {
	w http.ResponseWriter
}

// TODO this should properly handle content type negotiation
// if the caller asked for protobuf and you write JSON bad things happen.
func (r *responder) Object(statusCode int, obj runtime.Object) {
	responsewriters.WriteRawJSON(statusCode, obj, r.w)
}

func (r *responder) Error(_ http.ResponseWriter, _ *http.Request, err error) {
	http.Error(r.w, err.Error(), http.StatusServiceUnavailable)
}

// these methods provide locked access to fields

func (r *proxyHandler) updateAPIService(apiService *apiregistrationv1api.APIService) {
	if apiService.Spec.Service == nil {
		r.handlingInfo.Store(proxyHandlingInfo{local: true})
		return
	}

	proxyClientCert, proxyClientKey := r.proxyCurrentCertKeyContent()

	clientConfig := &restclient.Config{
		TLSClientConfig: restclient.TLSClientConfig{
			Insecure:   apiService.Spec.InsecureSkipTLSVerify,
			ServerName: apiService.Spec.Service.Name + "." + apiService.Spec.Service.Namespace + ".svc",
			CertData:   proxyClientCert,
			KeyData:    proxyClientKey,
			CAData:     apiService.Spec.CABundle,
		},
	}
	clientConfig.Wrap(x509metrics.NewMissingSANRoundTripperWrapperConstructor(x509MissingSANCounter))

	newInfo := proxyHandlingInfo{
		name:             apiService.Name,
		restConfig:       clientConfig,
		serviceName:      apiService.Spec.Service.Name,
		serviceNamespace: apiService.Spec.Service.Namespace,
		servicePort:      *apiService.Spec.Service.Port,
		serviceAvailable: apiregistrationv1apihelper.IsAPIServiceConditionTrue(apiService, apiregistrationv1api.Available),
	}
	if r.egressSelector != nil {
		networkContext := egressselector.Cluster.AsNetworkContext()
		var egressDialer utilnet.DialFunc
		egressDialer, err := r.egressSelector.Lookup(networkContext)
		if err != nil {
			klog.Warning(err.Error())
		} else {
			newInfo.restConfig.Dial = egressDialer
		}
	} else if r.proxyTransport != nil && r.proxyTransport.DialContext != nil {
		newInfo.restConfig.Dial = r.proxyTransport.DialContext
	}
	newInfo.proxyRoundTripper, newInfo.transportBuildingError = restclient.TransportFor(newInfo.restConfig)
	if newInfo.transportBuildingError != nil {
		klog.Warning(newInfo.transportBuildingError.Error())
	}
	r.handlingInfo.Store(newInfo)
}
<commit_msg>remove unused parameter: intercceptRedirects & RequireSameHostRedirects<commit_after>/*
Copyright 2016 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 apiserver

import (
	"context"
	"net/http"
	"net/url"
	"strings"
	"sync/atomic"
	"time"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/httpstream"
	utilnet "k8s.io/apimachinery/pkg/util/net"
	"k8s.io/apimachinery/pkg/util/proxy"
	auditinternal "k8s.io/apiserver/pkg/apis/audit"
	"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
	endpointmetrics "k8s.io/apiserver/pkg/endpoints/metrics"
	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/server/egressselector"
	utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
	"k8s.io/apiserver/pkg/util/x509metrics"
	restclient "k8s.io/client-go/rest"
	"k8s.io/client-go/transport"
	"k8s.io/klog/v2"
	apiregistrationv1api "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
	apiregistrationv1apihelper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper"
)

const (
	aggregatorComponent string = "aggregator"

	aggregatedDiscoveryTimeout = 5 * time.Second
)

type certKeyFunc func() ([]byte, []byte)

// proxyHandler provides a http.Handler which will proxy traffic to locations
// specified by items implementing Redirector.
type proxyHandler struct {
	// localDelegate is used to satisfy local APIServices
	localDelegate http.Handler

	// proxyCurrentCertKeyContent holds the client cert used to identify this proxy. Backing APIServices use this to confirm the proxy's identity
	proxyCurrentCertKeyContent certKeyFunc
	proxyTransport             *http.Transport

	// Endpoints based routing to map from cluster IP to routable IP
	serviceResolver ServiceResolver

	handlingInfo atomic.Value

	// egressSelector selects the proper egress dialer to communicate with the custom apiserver
	// overwrites proxyTransport dialer if not nil
	egressSelector *egressselector.EgressSelector
}

type proxyHandlingInfo struct {
	// local indicates that this APIService is locally satisfied
	local bool

	// name is the name of the APIService
	name string
	// restConfig holds the information for building a roundtripper
	restConfig *restclient.Config
	// transportBuildingError is an error produced while building the transport.  If this
	// is non-nil, it will be reported to clients.
	transportBuildingError error
	// proxyRoundTripper is the re-useable portion of the transport.  It does not vary with any request.
	proxyRoundTripper http.RoundTripper
	// serviceName is the name of the service this handler proxies to
	serviceName string
	// namespace is the namespace the service lives in
	serviceNamespace string
	// serviceAvailable indicates this APIService is available or not
	serviceAvailable bool
	// servicePort is the port of the service this handler proxies to
	servicePort int32
}

func proxyError(w http.ResponseWriter, req *http.Request, error string, code int) {
	http.Error(w, error, code)

	ctx := req.Context()
	info, ok := genericapirequest.RequestInfoFrom(ctx)
	if !ok {
		klog.Warning("no RequestInfo found in the context")
		return
	}
	// TODO: record long-running request differently? The long-running check func does not necessarily match the one of the aggregated apiserver
	endpointmetrics.RecordRequestTermination(req, info, aggregatorComponent, code)
}

func (r *proxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	value := r.handlingInfo.Load()
	if value == nil {
		r.localDelegate.ServeHTTP(w, req)
		return
	}
	handlingInfo := value.(proxyHandlingInfo)
	if handlingInfo.local {
		if r.localDelegate == nil {
			http.Error(w, "", http.StatusNotFound)
			return
		}
		r.localDelegate.ServeHTTP(w, req)
		return
	}

	if !handlingInfo.serviceAvailable {
		proxyError(w, req, "service unavailable", http.StatusServiceUnavailable)
		return
	}

	if handlingInfo.transportBuildingError != nil {
		proxyError(w, req, handlingInfo.transportBuildingError.Error(), http.StatusInternalServerError)
		return
	}

	user, ok := genericapirequest.UserFrom(req.Context())
	if !ok {
		proxyError(w, req, "missing user", http.StatusInternalServerError)
		return
	}

	// write a new location based on the existing request pointed at the target service
	location := &url.URL{}
	location.Scheme = "https"
	rloc, err := r.serviceResolver.ResolveEndpoint(handlingInfo.serviceNamespace, handlingInfo.serviceName, handlingInfo.servicePort)
	if err != nil {
		klog.Errorf("error resolving %s/%s: %v", handlingInfo.serviceNamespace, handlingInfo.serviceName, err)
		proxyError(w, req, "service unavailable", http.StatusServiceUnavailable)
		return
	}
	location.Host = rloc.Host
	location.Path = req.URL.Path
	location.RawQuery = req.URL.Query().Encode()

	newReq, cancelFn := newRequestForProxy(location, req)
	defer cancelFn()

	if handlingInfo.proxyRoundTripper == nil {
		proxyError(w, req, "", http.StatusNotFound)
		return
	}

	proxyRoundTripper := handlingInfo.proxyRoundTripper
	upgrade := httpstream.IsUpgradeRequest(req)

	proxyRoundTripper = transport.NewAuthProxyRoundTripper(user.GetName(), user.GetGroups(), user.GetExtra(), proxyRoundTripper)

	// If we are upgrading, then the upgrade path tries to use this request with the TLS config we provide, but it does
	// NOT use the proxyRoundTripper.  It's a direct dial that bypasses the proxyRoundTripper.  This means that we have to
	// attach the "correct" user headers to the request ahead of time.
	if upgrade {
		transport.SetAuthProxyHeaders(newReq, user.GetName(), user.GetGroups(), user.GetExtra())
	}

	handler := proxy.NewUpgradeAwareHandler(location, proxyRoundTripper, true, upgrade, &responder{w: w})
	utilflowcontrol.RequestDelegated(req.Context())
	handler.ServeHTTP(w, newReq)
}

// newRequestForProxy returns a shallow copy of the original request with a context that may include a timeout for discovery requests
func newRequestForProxy(location *url.URL, req *http.Request) (*http.Request, context.CancelFunc) {
	newCtx := req.Context()
	cancelFn := func() {}

	if requestInfo, ok := genericapirequest.RequestInfoFrom(req.Context()); ok {
		// trim leading and trailing slashes. Then "/apis/group/version" requests are for discovery, so if we have exactly three
		// segments that we are going to proxy, we have a discovery request.
		if !requestInfo.IsResourceRequest && len(strings.Split(strings.Trim(requestInfo.Path, "/"), "/")) == 3 {
			// discovery requests are used by kubectl and others to determine which resources a server has.  This is a cheap call that
			// should be fast for every aggregated apiserver.  Latency for aggregation is expected to be low (as for all extensions)
			// so forcing a short timeout here helps responsiveness of all clients.
			newCtx, cancelFn = context.WithTimeout(newCtx, aggregatedDiscoveryTimeout)
		}
	}

	// WithContext creates a shallow clone of the request with the same context.
	newReq := req.WithContext(newCtx)
	newReq.Header = utilnet.CloneHeader(req.Header)
	newReq.URL = location
	newReq.Host = location.Host

	// If the original request has an audit ID, let's make sure we propagate this
	// to the aggregated server.
	if auditID, found := genericapirequest.AuditIDFrom(req.Context()); found {
		newReq.Header.Set(auditinternal.HeaderAuditID, string(auditID))
	}

	return newReq, cancelFn
}

// responder implements rest.Responder for assisting a connector in writing objects or errors.
type responder struct {
	w http.ResponseWriter
}

// TODO this should properly handle content type negotiation
// if the caller asked for protobuf and you write JSON bad things happen.
func (r *responder) Object(statusCode int, obj runtime.Object) {
	responsewriters.WriteRawJSON(statusCode, obj, r.w)
}

func (r *responder) Error(_ http.ResponseWriter, _ *http.Request, err error) {
	http.Error(r.w, err.Error(), http.StatusServiceUnavailable)
}

// these methods provide locked access to fields

func (r *proxyHandler) updateAPIService(apiService *apiregistrationv1api.APIService) {
	if apiService.Spec.Service == nil {
		r.handlingInfo.Store(proxyHandlingInfo{local: true})
		return
	}

	proxyClientCert, proxyClientKey := r.proxyCurrentCertKeyContent()

	clientConfig := &restclient.Config{
		TLSClientConfig: restclient.TLSClientConfig{
			Insecure:   apiService.Spec.InsecureSkipTLSVerify,
			ServerName: apiService.Spec.Service.Name + "." + apiService.Spec.Service.Namespace + ".svc",
			CertData:   proxyClientCert,
			KeyData:    proxyClientKey,
			CAData:     apiService.Spec.CABundle,
		},
	}
	clientConfig.Wrap(x509metrics.NewMissingSANRoundTripperWrapperConstructor(x509MissingSANCounter))

	newInfo := proxyHandlingInfo{
		name:             apiService.Name,
		restConfig:       clientConfig,
		serviceName:      apiService.Spec.Service.Name,
		serviceNamespace: apiService.Spec.Service.Namespace,
		servicePort:      *apiService.Spec.Service.Port,
		serviceAvailable: apiregistrationv1apihelper.IsAPIServiceConditionTrue(apiService, apiregistrationv1api.Available),
	}
	if r.egressSelector != nil {
		networkContext := egressselector.Cluster.AsNetworkContext()
		var egressDialer utilnet.DialFunc
		egressDialer, err := r.egressSelector.Lookup(networkContext)
		if err != nil {
			klog.Warning(err.Error())
		} else {
			newInfo.restConfig.Dial = egressDialer
		}
	} else if r.proxyTransport != nil && r.proxyTransport.DialContext != nil {
		newInfo.restConfig.Dial = r.proxyTransport.DialContext
	}
	newInfo.proxyRoundTripper, newInfo.transportBuildingError = restclient.TransportFor(newInfo.restConfig)
	if newInfo.transportBuildingError != nil {
		klog.Warning(newInfo.transportBuildingError.Error())
	}
	r.handlingInfo.Store(newInfo)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"time"

	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/go-docopt"
	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/technoweenie/grohl"
	"github.com/flynn/flynn/discoverd/client"
	"github.com/flynn/flynn/host/cli"
	"github.com/flynn/flynn/host/config"
	"github.com/flynn/flynn/host/ports"
	"github.com/flynn/flynn/host/sampi"
	"github.com/flynn/flynn/host/types"
	"github.com/flynn/flynn/pkg/attempt"
	"github.com/flynn/flynn/pkg/cluster"
	"github.com/flynn/flynn/pkg/shutdown"
	"github.com/flynn/flynn/pkg/stream"
)

// discoverdAttempts is the attempt strategy that is used to connect to discoverd.
var discoverdAttempts = attempt.Strategy{
	Min:   5,
	Total: 10 * time.Minute,
	Delay: 200 * time.Millisecond,
}

const configFile = "/etc/flynn/host.json"

func init() {
	log.SetFlags(log.Lshortfile | log.Lmicroseconds)

	cli.Register("daemon", runDaemon, `
usage: flynn-host daemon [options] [--meta=<KEY=VAL>...]

options:
  --external=IP          external IP of host
  --manifest=PATH        path to manifest file [default: /etc/flynn-host.json]
  --state=PATH           path to state file [default: /var/lib/flynn/host-state.bolt]
  --id=ID                host id
  --force                kill all containers booted by flynn-host before starting
  --volpath=PATH         directory to create volumes in [default: /var/lib/flynn/host-volumes]
  --backend=BACKEND      runner backend [default: libvirt-lxc]
  --meta=<KEY=VAL>...    key=value pair to add as metadata
  --bind=IP              bind containers to IP
  --flynn-init=PATH      path to flynn-init binary [default: /usr/bin/flynn-init]
	`)
}

func main() {
	usage := `usage: flynn-host [-h|--help] <command> [<args>...]

Options:
  -h, --help                 Show this message

Commands:
  help                       Show usage for a specific command
  init                       Create cluster configuration for daemon
  daemon                     Start the daemon
  download                   Download container images
  bootstrap                  Bootstrap layer 1
  inspect                    Get low-level information about a job
  log                        Get the logs of a job
  ps                         List jobs
  stop                       Stop running jobs
  upload-debug-info          Upload debug information to an anonymous gist

See 'flynn-host help <command>' for more information on a specific command.
`

	args, _ := docopt.Parse(usage, nil, true, "", true)
	cmd := args.String["<command>"]
	cmdArgs := args.All["<args>"].([]string)

	if cmd == "help" {
		if len(cmdArgs) == 0 { // `flynn help`
			fmt.Println(usage)
			return
		} else { // `flynn help <command>`
			cmd = cmdArgs[0]
			cmdArgs = []string{"--help"}
		}
	}

	if cmd == "daemon" {
		// merge in args and env from config file, if available
		var c *config.Config
		var err error
		if n := os.Getenv("FLYNN_HOST_CONFIG"); n != "" {
			c, err = config.Open(n)
			if err != nil {
				log.Fatalf("error opening config file %s: %s", n, err)
			}
		} else {
			c, err = config.Open(configFile)
			if err != nil && !os.IsNotExist(err) {
				log.Fatalf("error opening config file %s: %s", configFile, err)
			}
			if c == nil {
				c = &config.Config{}
			}
		}
		cmdArgs = append(cmdArgs, c.Args...)
		for k, v := range c.Env {
			os.Setenv(k, v)
		}
	}

	if err := cli.Run(cmd, cmdArgs); err != nil {
		log.Fatal(err)
	}
}

func runDaemon(args *docopt.Args) {
	hostname, _ := os.Hostname()
	externalAddr := args.String["--external"]
	bindAddr := args.String["--bind"]
	manifestFile := args.String["--manifest"]
	stateFile := args.String["--state"]
	hostID := args.String["--id"]
	force := args.Bool["--force"]
	volPath := args.String["--volpath"]
	backendName := args.String["--backend"]
	flynnInit := args.String["--flynn-init"]
	metadata := args.All["--meta"].([]string)

	grohl.AddContext("app", "host")
	grohl.Log(grohl.Data{"at": "start"})
	g := grohl.NewContext(grohl.Data{"fn": "main"})

	if hostID == "" {
		hostID = strings.Replace(hostname, "-", "", -1)
	}
	if strings.Contains(hostID, "-") {
		log.Fatal("host id must not contain dashes")
	}
	if externalAddr == "" {
		var err error
		externalAddr, err = config.DefaultExternalIP()
		if err != nil {
			log.Fatal(err)
		}
	}

	portAlloc := map[string]*ports.Allocator{
		"tcp": ports.NewAllocator(55000, 65535),
		"udp": ports.NewAllocator(55000, 65535),
	}

	sh := shutdown.NewHandler()
	state := NewState(hostID, stateFile)
	var backend Backend
	var err error

	switch backendName {
	case "libvirt-lxc":
		backend, err = NewLibvirtLXCBackend(state, volPath, "/tmp/flynn-host-logs", flynnInit)
	default:
		log.Fatalf("unknown backend %q", backendName)
	}
	if err != nil {
		sh.Fatal(err)
	}

	router, err := serveHTTP(&Host{state: state, backend: backend}, &attachHandler{state: state, backend: backend}, sh)
	if err != nil {
		sh.Fatal(err)
	}

	if err := state.Restore(backend); err != nil {
		sh.Fatal(err)
	}

	var jobStream stream.Stream
	sh.BeforeExit(func() {
		if jobStream != nil {
			jobStream.Close()
		}
		backend.Cleanup()
	})

	if force {
		if err := backend.Cleanup(); err != nil {
			sh.Fatal(err)
		}
	}

	runner := &manifestRunner{
		env:          parseEnviron(),
		externalAddr: externalAddr,
		bindAddr:     bindAddr,
		backend:      backend,
		state:        state,
		ports:        portAlloc,
	}

	discAddr := os.Getenv("DISCOVERD")
	var disc *discoverd.Client
	if manifestFile != "" {
		var r io.Reader
		var f *os.File
		if manifestFile == "-" {
			r = os.Stdin
		} else {
			f, err = os.Open(manifestFile)
			if err != nil {
				sh.Fatal(err)
			}
			r = f
		}
		services, err := runner.runManifest(r)
		if err != nil {
			sh.Fatal(err)
		}
		if f != nil {
			f.Close()
		}

		if d, ok := services["discoverd"]; ok {
			discAddr = fmt.Sprintf("%s:%d", d.ExternalIP, d.TCPPorts[0])
			var disc *discoverd.Client
			err = discoverdAttempts.Run(func() (err error) {
				disc, err = discoverd.NewClientWithAddr(discAddr)
				return
			})
			if err != nil {
				sh.Fatal(err)
			}
		}
	}

	if discAddr == "" && externalAddr != "" {
		discAddr = externalAddr + ":1111"
	}
	// HACK: use env as global for discoverd connection in sampic
	os.Setenv("DISCOVERD", discAddr)
	if disc == nil {
		disc, err = discoverd.NewClientWithAddr(discAddr)
		if err != nil {
			sh.Fatal(err)
		}
	}
	sh.BeforeExit(func() { disc.UnregisterAll() })
	sampiStandby, err := disc.RegisterAndStandby("flynn-host", externalAddr+":1113", map[string]string{"id": hostID})
	if err != nil {
		sh.Fatal(err)
	}

	// Check if we are the leader so that we can use the cluster functions directly
	sampiCluster := sampi.NewCluster(sampi.NewState())
	sampiAPI := &sampi.HTTPAPI{Cluster: sampiCluster}
	select {
	case <-sampiStandby:
		g.Log(grohl.Data{"at": "sampi_leader"})
		sampiAPI.RegisterRoutes(router, sh)
	case <-time.After(5 * time.Millisecond):
		go func() {
			<-sampiStandby
			g.Log(grohl.Data{"at": "sampi_leader"})
			sampiAPI.RegisterRoutes(router, sh)
		}()
	}
	cluster, err := cluster.NewClient()
	if err != nil {
		sh.Fatal(err)
	}
	sh.BeforeExit(func() { cluster.Close() })

	g.Log(grohl.Data{"at": "sampi_connected"})

	events := state.AddListener("all")
	go syncScheduler(cluster, hostID, events)

	h := &host.Host{ID: hostID, Metadata: make(map[string]string)}
	for _, s := range metadata {
		kv := strings.SplitN(s, "=", 2)
		h.Metadata[kv[0]] = kv[1]
	}

	for {
		newLeader := cluster.NewLeaderSignal()

		h.Jobs = state.ClusterJobs()
		jobs := make(chan *host.Job)
		jobStream, err = cluster.RegisterHost(h, jobs)
		if err != nil {
			sh.Fatal(err)
		}
		g.Log(grohl.Data{"at": "host_registered"})
		for job := range jobs {
			if externalAddr != "" {
				if job.Config.Env == nil {
					job.Config.Env = make(map[string]string)
				}
				job.Config.Env["EXTERNAL_IP"] = externalAddr
				job.Config.Env["DISCOVERD"] = discAddr
			}
			if err := backend.Run(job); err != nil {
				state.SetStatusFailed(job.ID, err)
			}
		}
		g.Log(grohl.Data{"at": "sampi_disconnected", "err": jobStream.Err})

		// if the process is shutting down, just block
		if sh.Active {
			<-make(chan struct{})
		}

		<-newLeader
	}
}

func syncScheduler(scheduler *cluster.Client, hostID string, events <-chan host.Event) {
	for event := range events {
		if event.Event != "stop" {
			continue
		}
		grohl.Log(grohl.Data{"fn": "scheduler_event", "at": "remove_job", "job.id": event.JobID})
		if err := scheduler.RemoveJob(hostID, event.JobID); err != nil {
			grohl.Log(grohl.Data{"fn": "scheduler_event", "at": "remove_job", "status": "error", "err": err, "job.id": event.JobID})
		}
	}
}
<commit_msg>host: Drop unnecessary shutdown cleanup.<commit_after>package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"time"

	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/go-docopt"
	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/technoweenie/grohl"
	"github.com/flynn/flynn/discoverd/client"
	"github.com/flynn/flynn/host/cli"
	"github.com/flynn/flynn/host/config"
	"github.com/flynn/flynn/host/ports"
	"github.com/flynn/flynn/host/sampi"
	"github.com/flynn/flynn/host/types"
	"github.com/flynn/flynn/pkg/attempt"
	"github.com/flynn/flynn/pkg/cluster"
	"github.com/flynn/flynn/pkg/shutdown"
)

// discoverdAttempts is the attempt strategy that is used to connect to discoverd.
var discoverdAttempts = attempt.Strategy{
	Min:   5,
	Total: 10 * time.Minute,
	Delay: 200 * time.Millisecond,
}

const configFile = "/etc/flynn/host.json"

func init() {
	log.SetFlags(log.Lshortfile | log.Lmicroseconds)

	cli.Register("daemon", runDaemon, `
usage: flynn-host daemon [options] [--meta=<KEY=VAL>...]

options:
  --external=IP          external IP of host
  --manifest=PATH        path to manifest file [default: /etc/flynn-host.json]
  --state=PATH           path to state file [default: /var/lib/flynn/host-state.bolt]
  --id=ID                host id
  --force                kill all containers booted by flynn-host before starting
  --volpath=PATH         directory to create volumes in [default: /var/lib/flynn/host-volumes]
  --backend=BACKEND      runner backend [default: libvirt-lxc]
  --meta=<KEY=VAL>...    key=value pair to add as metadata
  --bind=IP              bind containers to IP
  --flynn-init=PATH      path to flynn-init binary [default: /usr/bin/flynn-init]
	`)
}

func main() {
	usage := `usage: flynn-host [-h|--help] <command> [<args>...]

Options:
  -h, --help                 Show this message

Commands:
  help                       Show usage for a specific command
  init                       Create cluster configuration for daemon
  daemon                     Start the daemon
  download                   Download container images
  bootstrap                  Bootstrap layer 1
  inspect                    Get low-level information about a job
  log                        Get the logs of a job
  ps                         List jobs
  stop                       Stop running jobs
  upload-debug-info          Upload debug information to an anonymous gist

See 'flynn-host help <command>' for more information on a specific command.
`

	args, _ := docopt.Parse(usage, nil, true, "", true)
	cmd := args.String["<command>"]
	cmdArgs := args.All["<args>"].([]string)

	if cmd == "help" {
		if len(cmdArgs) == 0 { // `flynn help`
			fmt.Println(usage)
			return
		} else { // `flynn help <command>`
			cmd = cmdArgs[0]
			cmdArgs = []string{"--help"}
		}
	}

	if cmd == "daemon" {
		// merge in args and env from config file, if available
		var c *config.Config
		var err error
		if n := os.Getenv("FLYNN_HOST_CONFIG"); n != "" {
			c, err = config.Open(n)
			if err != nil {
				log.Fatalf("error opening config file %s: %s", n, err)
			}
		} else {
			c, err = config.Open(configFile)
			if err != nil && !os.IsNotExist(err) {
				log.Fatalf("error opening config file %s: %s", configFile, err)
			}
			if c == nil {
				c = &config.Config{}
			}
		}
		cmdArgs = append(cmdArgs, c.Args...)
		for k, v := range c.Env {
			os.Setenv(k, v)
		}
	}

	if err := cli.Run(cmd, cmdArgs); err != nil {
		log.Fatal(err)
	}
}

func runDaemon(args *docopt.Args) {
	hostname, _ := os.Hostname()
	externalAddr := args.String["--external"]
	bindAddr := args.String["--bind"]
	manifestFile := args.String["--manifest"]
	stateFile := args.String["--state"]
	hostID := args.String["--id"]
	force := args.Bool["--force"]
	volPath := args.String["--volpath"]
	backendName := args.String["--backend"]
	flynnInit := args.String["--flynn-init"]
	metadata := args.All["--meta"].([]string)

	grohl.AddContext("app", "host")
	grohl.Log(grohl.Data{"at": "start"})
	g := grohl.NewContext(grohl.Data{"fn": "main"})

	if hostID == "" {
		hostID = strings.Replace(hostname, "-", "", -1)
	}
	if strings.Contains(hostID, "-") {
		log.Fatal("host id must not contain dashes")
	}
	if externalAddr == "" {
		var err error
		externalAddr, err = config.DefaultExternalIP()
		if err != nil {
			log.Fatal(err)
		}
	}

	portAlloc := map[string]*ports.Allocator{
		"tcp": ports.NewAllocator(55000, 65535),
		"udp": ports.NewAllocator(55000, 65535),
	}

	sh := shutdown.NewHandler()
	state := NewState(hostID, stateFile)
	var backend Backend
	var err error

	switch backendName {
	case "libvirt-lxc":
		backend, err = NewLibvirtLXCBackend(state, volPath, "/tmp/flynn-host-logs", flynnInit)
	default:
		log.Fatalf("unknown backend %q", backendName)
	}
	if err != nil {
		sh.Fatal(err)
	}

	router, err := serveHTTP(&Host{state: state, backend: backend}, &attachHandler{state: state, backend: backend}, sh)
	if err != nil {
		sh.Fatal(err)
	}

	if err := state.Restore(backend); err != nil {
		sh.Fatal(err)
	}

	sh.BeforeExit(func() { backend.Cleanup() })

	if force {
		if err := backend.Cleanup(); err != nil {
			sh.Fatal(err)
		}
	}

	runner := &manifestRunner{
		env:          parseEnviron(),
		externalAddr: externalAddr,
		bindAddr:     bindAddr,
		backend:      backend,
		state:        state,
		ports:        portAlloc,
	}

	discAddr := os.Getenv("DISCOVERD")
	var disc *discoverd.Client
	if manifestFile != "" {
		var r io.Reader
		var f *os.File
		if manifestFile == "-" {
			r = os.Stdin
		} else {
			f, err = os.Open(manifestFile)
			if err != nil {
				sh.Fatal(err)
			}
			r = f
		}
		services, err := runner.runManifest(r)
		if err != nil {
			sh.Fatal(err)
		}
		if f != nil {
			f.Close()
		}

		if d, ok := services["discoverd"]; ok {
			discAddr = fmt.Sprintf("%s:%d", d.ExternalIP, d.TCPPorts[0])
			var disc *discoverd.Client
			err = discoverdAttempts.Run(func() (err error) {
				disc, err = discoverd.NewClientWithAddr(discAddr)
				return
			})
			if err != nil {
				sh.Fatal(err)
			}
		}
	}

	if discAddr == "" && externalAddr != "" {
		discAddr = externalAddr + ":1111"
	}
	// HACK: use env as global for discoverd connection in sampic
	os.Setenv("DISCOVERD", discAddr)
	if disc == nil {
		disc, err = discoverd.NewClientWithAddr(discAddr)
		if err != nil {
			sh.Fatal(err)
		}
	}
	sh.BeforeExit(func() { disc.UnregisterAll() })
	sampiStandby, err := disc.RegisterAndStandby("flynn-host", externalAddr+":1113", map[string]string{"id": hostID})
	if err != nil {
		sh.Fatal(err)
	}

	// Check if we are the leader so that we can use the cluster functions directly
	sampiCluster := sampi.NewCluster(sampi.NewState())
	sampiAPI := &sampi.HTTPAPI{Cluster: sampiCluster}
	select {
	case <-sampiStandby:
		g.Log(grohl.Data{"at": "sampi_leader"})
		sampiAPI.RegisterRoutes(router, sh)
	case <-time.After(5 * time.Millisecond):
		go func() {
			<-sampiStandby
			g.Log(grohl.Data{"at": "sampi_leader"})
			sampiAPI.RegisterRoutes(router, sh)
		}()
	}
	cluster, err := cluster.NewClient()
	if err != nil {
		sh.Fatal(err)
	}
	sh.BeforeExit(func() { cluster.Close() })

	g.Log(grohl.Data{"at": "sampi_connected"})

	events := state.AddListener("all")
	go syncScheduler(cluster, hostID, events)

	h := &host.Host{ID: hostID, Metadata: make(map[string]string)}
	for _, s := range metadata {
		kv := strings.SplitN(s, "=", 2)
		h.Metadata[kv[0]] = kv[1]
	}

	for {
		newLeader := cluster.NewLeaderSignal()

		h.Jobs = state.ClusterJobs()
		jobs := make(chan *host.Job)
		jobStream, err := cluster.RegisterHost(h, jobs)
		if err != nil {
			sh.Fatal(err)
		}
		g.Log(grohl.Data{"at": "host_registered"})
		for job := range jobs {
			if externalAddr != "" {
				if job.Config.Env == nil {
					job.Config.Env = make(map[string]string)
				}
				job.Config.Env["EXTERNAL_IP"] = externalAddr
				job.Config.Env["DISCOVERD"] = discAddr
			}
			if err := backend.Run(job); err != nil {
				state.SetStatusFailed(job.ID, err)
			}
		}
		g.Log(grohl.Data{"at": "sampi_disconnected", "err": jobStream.Err})

		// if the process is shutting down, just block
		if sh.Active {
			<-make(chan struct{})
		}

		<-newLeader
	}
}

func syncScheduler(scheduler *cluster.Client, hostID string, events <-chan host.Event) {
	for event := range events {
		if event.Event != "stop" {
			continue
		}
		grohl.Log(grohl.Data{"fn": "scheduler_event", "at": "remove_job", "job.id": event.JobID})
		if err := scheduler.RemoveJob(hostID, event.JobID); err != nil {
			grohl.Log(grohl.Data{"fn": "scheduler_event", "at": "remove_job", "status": "error", "err": err, "job.id": event.JobID})
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Rook 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

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 config

import (
	"fmt"
	"net"
	"strings"

	"github.com/coreos/pkg/capnslog"
	cephver "github.com/rook/rook/pkg/operator/ceph/version"
)

// ClusterInfo is a collection of information about a particular Ceph cluster. Rook uses information
// about the cluster to configure daemons to connect to the desired cluster.
type ClusterInfo struct {
	FSID          string
	MonitorSecret string
	AdminSecret   string
	Name          string
	Monitors      map[string]*MonInfo
	CephVersion   cephver.CephVersion
}

// MonInfo is a collection of information about a Ceph mon.
type MonInfo struct {
	Name     string `json:"name"`
	Endpoint string `json:"endpoint"`
}

// IsInitialized returns true if the critical information in the ClusterInfo struct has been filled
// in. This method exists less out of necessity than the desire to be explicit about the lifecycle
// of the ClusterInfo struct during startup, specifically that it is expected to exist after the
// Rook operator has started up or connected to the first components of the Ceph cluster.
func (c *ClusterInfo) IsInitialized() bool {
	if c == nil || c.FSID == "" || c.MonitorSecret == "" || c.AdminSecret == "" {
		logger.Errorf("clusterInfo: %+v", c)
		return false
	}
	return true
}

// NewMonInfo returns a new Ceph mon info struct from the given inputs.
func NewMonInfo(name, ip string, port int32) *MonInfo {
	return &MonInfo{Name: name, Endpoint: net.JoinHostPort(ip, fmt.Sprintf("%d", port))}
}

// Log writes the cluster info struct to the logger
func (c *ClusterInfo) Log(logger *capnslog.PackageLogger) {
	mons := []string{}
	for _, m := range c.Monitors {
		// Sprintf formatting is safe as user input isn't being used. Issue https://github.com/rook/rook/issues/4575
		mons = append(mons, fmt.Sprintf("{Name: %s, Endpoint: %s}", m.Name, m.Endpoint))
	}
	monsec := ""
	if c.MonitorSecret != "" {
		monsec = "<hidden>"
	}
	admsec := ""
	if c.AdminSecret != "" {
		admsec = "<hidden>"
	}
	s := fmt.Sprintf(
		"ClusterInfo: {FSID: %s, MonitorSecret: %s, AdminSecret: %s, Name: %s, Monitors: %s}",
		c.FSID, monsec, admsec, c.Name, strings.Join(mons, " "))
	logger.Info(s)
}
<commit_msg>ceph: stop potential secret leak<commit_after>/*
Copyright 2016 The Rook 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

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 config

import (
	"fmt"
	"net"
	"strings"

	"github.com/coreos/pkg/capnslog"
	cephver "github.com/rook/rook/pkg/operator/ceph/version"
)

// ClusterInfo is a collection of information about a particular Ceph cluster. Rook uses information
// about the cluster to configure daemons to connect to the desired cluster.
type ClusterInfo struct {
	FSID          string
	MonitorSecret string
	AdminSecret   string
	Name          string
	Monitors      map[string]*MonInfo
	CephVersion   cephver.CephVersion
}

// MonInfo is a collection of information about a Ceph mon.
type MonInfo struct {
	Name     string `json:"name"`
	Endpoint string `json:"endpoint"`
}

// IsInitialized returns true if the critical information in the ClusterInfo struct has been filled
// in. This method exists less out of necessity than the desire to be explicit about the lifecycle
// of the ClusterInfo struct during startup, specifically that it is expected to exist after the
// Rook operator has started up or connected to the first components of the Ceph cluster.
func (c *ClusterInfo) IsInitialized() bool {
	var isInitialized bool

	if c == nil {
		logger.Error("clusterInfo is nil")
	} else if c.FSID == "" {
		logger.Error("cluster fsid is empty")
	} else if c.MonitorSecret == "" {
		logger.Error("monitor secret is empty")
	} else if c.AdminSecret == "" {
		logger.Error("admin secret is empty")
	} else {
		isInitialized = true
	}

	return isInitialized
}

// NewMonInfo returns a new Ceph mon info struct from the given inputs.
func NewMonInfo(name, ip string, port int32) *MonInfo {
	return &MonInfo{Name: name, Endpoint: net.JoinHostPort(ip, fmt.Sprintf("%d", port))}
}

// Log writes the cluster info struct to the logger
func (c *ClusterInfo) Log(logger *capnslog.PackageLogger) {
	mons := []string{}
	for _, m := range c.Monitors {
		// Sprintf formatting is safe as user input isn't being used. Issue https://github.com/rook/rook/issues/4575
		mons = append(mons, fmt.Sprintf("{Name: %s, Endpoint: %s}", m.Name, m.Endpoint))
	}
	monsec := ""
	if c.MonitorSecret != "" {
		monsec = "<hidden>"
	}
	admsec := ""
	if c.AdminSecret != "" {
		admsec = "<hidden>"
	}
	s := fmt.Sprintf(
		"ClusterInfo: {FSID: %s, MonitorSecret: %s, AdminSecret: %s, Name: %s, Monitors: %s}",
		c.FSID, monsec, admsec, c.Name, strings.Join(mons, " "))
	logger.Info(s)
}
<|endoftext|>


<commit_before>package main

import(
	"fmt"
	"os"
	"flag"
)

var repoRoot = flag.String("d", ".", "path to repo")

type Commit struct {
	Tree string
	Parent string
	Author string
	Committer string
}

func ParseCommit(in io.Reader) (*Commit, error) {

	commit := new(Commit)

	return commit, nil
}

func main() {
	flag.Parse()

	f, err := GetArgInputFile()

	cmt, err := ParseCommit(f)

	if err != nil {
		fmt.Fprintln(os.Stderr, "ERROR:", err)
		return
	}

	fmt.Fprintln(cmt)
}
<commit_msg>FIX: compile err<commit_after>package main

import(
	"fmt"
	"os"
	"flag"
	"io"
)

var repoRoot = flag.String("d", ".", "path to repo")

type Commit struct {
	Tree string
	Parent string
	Author string
	Committer string
}

func ParseCommit(in io.Reader) (*Commit, error) {

	commit := new(Commit)

	return commit, nil
}

func main() {
	flag.Parse()

	f, err := GetArgInputFile()

	cmt, err := ParseCommit(f)

	if err != nil {
		fmt.Fprintln(os.Stderr, "ERROR:", err)
		return
	}

	fmt.Fprintln(os.Stdout, cmt)
}
<|endoftext|>


<commit_before>// Copyright 2017-2018 Authors of Cilium
//
// 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 loader

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"path"
	"runtime"

	"github.com/cilium/cilium/pkg/command/exec"

	"github.com/sirupsen/logrus"
)

// OutputType determines the type to be generated by the compilation steps.
type OutputType string

const (
	outputObject   = OutputType("obj")
	outputAssembly = OutputType("asm")
	outputSource   = OutputType("c")
)

// progInfo describes a program to be compiled with the expected output format
type progInfo struct {
	// Source is the program source (base) filename to be compiled
	Source string
	// Output is the expected (base) filename produced from the source
	Output string
	// OutputType to be created by LLVM
	OutputType OutputType
}

// directoryInfo includes relevant directories for compilation and linking
type directoryInfo struct {
	// Library contains the library code to be used for compilation
	Library string
	// Runtime contains headers for compilation
	Runtime string
	// State contains node, lxc, and features headers for templatization
	State string
	// Output is the directory where the files will be stored
	Output string
}

var (
	compiler       = "clang"
	linker         = "llc"
	standardCFlags = []string{"-O2", "-target", "bpf",
		fmt.Sprintf("-D__NR_CPUS__=%d", runtime.NumCPU()),
		"-Wno-address-of-packed-member", "-Wno-unknown-warning-option"}
	standardLDFlags = []string{"-march=bpf", "-mcpu=probe"}

	endpointPrefix   = "bpf_lxc"
	endpointProg     = fmt.Sprintf("%s.%s", endpointPrefix, outputSource)
	endpointObj      = fmt.Sprintf("%s.o", endpointPrefix)
	endpointObjDebug = fmt.Sprintf("%s.dbg.o", endpointPrefix)
	endpointAsm      = fmt.Sprintf("%s.%s", endpointPrefix, outputAssembly)

	// testIncludes allows the unit tests to inject additional include
	// paths into the compile command at test time. It is usually nil.
	testIncludes string

	debugProgs = []*progInfo{
		{
			Source:     endpointProg,
			Output:     endpointObjDebug,
			OutputType: outputObject,
		},
		{
			Source:     endpointProg,
			Output:     endpointAsm,
			OutputType: outputAssembly,
		},
		{
			Source:     endpointProg,
			Output:     endpointProg,
			OutputType: outputSource,
		},
	}
	datapathProg = &progInfo{
		Source:     endpointProg,
		Output:     endpointObj,
		OutputType: outputObject,
	}
)

// progLDFlags determines the loader flags for the specified prog and paths.
func progLDFlags(prog *progInfo, dir *directoryInfo) []string {
	return []string{
		fmt.Sprintf("-filetype=%s", prog.OutputType),
		"-o", path.Join(dir.Output, prog.Output),
	}
}

// prepareCmdPipes attaches pipes to the stdout and stderr of the specified
// command, and returns the stdout, stderr, and any error that may have
// occurred while creating the pipes.
func prepareCmdPipes(cmd *exec.Cmd) (io.ReadCloser, io.ReadCloser, error) {
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, nil, fmt.Errorf("Failed to get stdout pipe: %s", err)
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		stdout.Close()
		return nil, nil, fmt.Errorf("Failed to get stderr pipe: %s", err)
	}

	return stdout, stderr, nil
}

// compileAndLink links the specified program from the specified path to the
// intermediate representation, to the output specified in the prog's info.
func compileAndLink(ctx context.Context, prog *progInfo, dir *directoryInfo, debug bool, compileArgs ...string) error {
	compileCmd, cancelCompile := exec.WithCancel(ctx, compiler, compileArgs...)
	defer cancelCompile()
	compilerStdout, compilerStderr, err := prepareCmdPipes(compileCmd)
	if err != nil {
		return err
	}

	linkArgs := make([]string, 0, 8)
	if debug {
		linkArgs = append(linkArgs, "-mattr=dwarfris")
	}
	linkArgs = append(linkArgs, standardLDFlags...)
	linkArgs = append(linkArgs, progLDFlags(prog, dir)...)

	linkCmd := exec.CommandContext(ctx, linker, linkArgs...)
	linkCmd.Stdin = compilerStdout
	if err := compileCmd.Start(); err != nil {
		return fmt.Errorf("Failed to start command %s: %s", compileCmd.Args, err)
	}

	var compileOut []byte
	/* Ignoring the output here because pkg/command/exec will log it. */
	_, err = linkCmd.CombinedOutput(log, true)
	if err == nil {
		compileOut, _ = ioutil.ReadAll(compilerStderr)
		err = compileCmd.Wait()
	} else {
		cancelCompile()
	}
	if err != nil {
		err = fmt.Errorf("Failed to compile %s: %s", prog.Output, err)
		log.WithFields(logrus.Fields{
			"compiler-pid": compileCmd.Process.Pid,
			"linker-pid":   linkCmd.Process.Pid,
		}).Error(err)
		if compileOut != nil {
			scopedLog := log.Warn
			if debug {
				scopedLog = log.Debug
			}
			scanner := bufio.NewScanner(bytes.NewReader(compileOut))
			for scanner.Scan() {
				scopedLog(scanner.Text())
			}
		}
	}

	return err
}

// progLDFlags determines the compiler flags for the specified prog and paths.
func progCFlags(prog *progInfo, dir *directoryInfo) []string {
	var output string

	if prog.OutputType == outputSource {
		output = path.Join(dir.Output, prog.Output)
	} else {
		output = "-" // stdout
	}

	return []string{
		testIncludes,
		fmt.Sprintf("-I%s", path.Join(dir.Runtime, "globals")),
		fmt.Sprintf("-I%s", dir.State),
		fmt.Sprintf("-I%s", path.Join(dir.Library, "include")),
		"-c", path.Join(dir.Library, prog.Source),
		"-o", output,
	}
}

// compile and link a program.
func compile(ctx context.Context, prog *progInfo, dir *directoryInfo, debug bool) (err error) {
	args := make([]string, 0, 16)
	if prog.OutputType == outputSource {
		args = append(args, "-E") // Preprocessor
	} else {
		args = append(args, "-emit-llvm")
		if debug {
			args = append(args, "-g")
		}
	}
	args = append(args, standardCFlags...)
	args = append(args, progCFlags(prog, dir)...)

	// Compilation is split between two exec calls. First clang generates
	// LLVM bitcode and then later llc compiles it to byte-code.
	log.WithFields(logrus.Fields{
		"target": compiler,
		"args":   args,
	}).Debug("Launching compiler")
	if prog.OutputType == outputSource {
		compileCmd := exec.CommandContext(ctx, compiler, args...)
		_, err = compileCmd.CombinedOutput(log, debug)
	} else {
		switch prog.OutputType {
		case outputObject:
			err = compileAndLink(ctx, prog, dir, debug, args...)
		case outputAssembly:
			err = compileAndLink(ctx, prog, dir, false, args...)
		default:
			log.Fatalf("Unhandled progInfo.OutputType %s", prog.OutputType)
		}
	}

	return err
}
<commit_msg>datapath: Fix nil dereference in logging statement<commit_after>// Copyright 2017-2019 Authors of Cilium
//
// 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 loader

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"runtime"

	"github.com/cilium/cilium/pkg/command/exec"

	"github.com/sirupsen/logrus"
)

// OutputType determines the type to be generated by the compilation steps.
type OutputType string

const (
	outputObject   = OutputType("obj")
	outputAssembly = OutputType("asm")
	outputSource   = OutputType("c")
)

// progInfo describes a program to be compiled with the expected output format
type progInfo struct {
	// Source is the program source (base) filename to be compiled
	Source string
	// Output is the expected (base) filename produced from the source
	Output string
	// OutputType to be created by LLVM
	OutputType OutputType
}

// directoryInfo includes relevant directories for compilation and linking
type directoryInfo struct {
	// Library contains the library code to be used for compilation
	Library string
	// Runtime contains headers for compilation
	Runtime string
	// State contains node, lxc, and features headers for templatization
	State string
	// Output is the directory where the files will be stored
	Output string
}

var (
	compiler       = "clang"
	linker         = "llc"
	standardCFlags = []string{"-O2", "-target", "bpf",
		fmt.Sprintf("-D__NR_CPUS__=%d", runtime.NumCPU()),
		"-Wno-address-of-packed-member", "-Wno-unknown-warning-option"}
	standardLDFlags = []string{"-march=bpf", "-mcpu=probe"}

	endpointPrefix   = "bpf_lxc"
	endpointProg     = fmt.Sprintf("%s.%s", endpointPrefix, outputSource)
	endpointObj      = fmt.Sprintf("%s.o", endpointPrefix)
	endpointObjDebug = fmt.Sprintf("%s.dbg.o", endpointPrefix)
	endpointAsm      = fmt.Sprintf("%s.%s", endpointPrefix, outputAssembly)

	// testIncludes allows the unit tests to inject additional include
	// paths into the compile command at test time. It is usually nil.
	testIncludes string

	debugProgs = []*progInfo{
		{
			Source:     endpointProg,
			Output:     endpointObjDebug,
			OutputType: outputObject,
		},
		{
			Source:     endpointProg,
			Output:     endpointAsm,
			OutputType: outputAssembly,
		},
		{
			Source:     endpointProg,
			Output:     endpointProg,
			OutputType: outputSource,
		},
	}
	datapathProg = &progInfo{
		Source:     endpointProg,
		Output:     endpointObj,
		OutputType: outputObject,
	}
)

// progLDFlags determines the loader flags for the specified prog and paths.
func progLDFlags(prog *progInfo, dir *directoryInfo) []string {
	return []string{
		fmt.Sprintf("-filetype=%s", prog.OutputType),
		"-o", path.Join(dir.Output, prog.Output),
	}
}

// prepareCmdPipes attaches pipes to the stdout and stderr of the specified
// command, and returns the stdout, stderr, and any error that may have
// occurred while creating the pipes.
func prepareCmdPipes(cmd *exec.Cmd) (io.ReadCloser, io.ReadCloser, error) {
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, nil, fmt.Errorf("Failed to get stdout pipe: %s", err)
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		stdout.Close()
		return nil, nil, fmt.Errorf("Failed to get stderr pipe: %s", err)
	}

	return stdout, stderr, nil
}

func pidFromProcess(proc *os.Process) string {
	result := "not-started"
	if proc != nil {
		result = fmt.Sprintf("%d", proc.Pid)
	}
	return result
}

// compileAndLink links the specified program from the specified path to the
// intermediate representation, to the output specified in the prog's info.
func compileAndLink(ctx context.Context, prog *progInfo, dir *directoryInfo, debug bool, compileArgs ...string) error {
	compileCmd, cancelCompile := exec.WithCancel(ctx, compiler, compileArgs...)
	defer cancelCompile()
	compilerStdout, compilerStderr, err := prepareCmdPipes(compileCmd)
	if err != nil {
		return err
	}

	linkArgs := make([]string, 0, 8)
	if debug {
		linkArgs = append(linkArgs, "-mattr=dwarfris")
	}
	linkArgs = append(linkArgs, standardLDFlags...)
	linkArgs = append(linkArgs, progLDFlags(prog, dir)...)

	linkCmd := exec.CommandContext(ctx, linker, linkArgs...)
	linkCmd.Stdin = compilerStdout
	if err := compileCmd.Start(); err != nil {
		return fmt.Errorf("Failed to start command %s: %s", compileCmd.Args, err)
	}

	var compileOut []byte
	/* Ignoring the output here because pkg/command/exec will log it. */
	_, err = linkCmd.CombinedOutput(log, true)
	if err == nil {
		compileOut, _ = ioutil.ReadAll(compilerStderr)
		err = compileCmd.Wait()
	} else {
		cancelCompile()
	}
	if err != nil {
		err = fmt.Errorf("Failed to compile %s: %s", prog.Output, err)
		log.WithFields(logrus.Fields{
			"compiler-pid": pidFromProcess(compileCmd.Process),
			"linker-pid":   pidFromProcess(linkCmd.Process),
		}).Error(err)
		if compileOut != nil {
			scopedLog := log.Warn
			if debug {
				scopedLog = log.Debug
			}
			scanner := bufio.NewScanner(bytes.NewReader(compileOut))
			for scanner.Scan() {
				scopedLog(scanner.Text())
			}
		}
	}

	return err
}

// progLDFlags determines the compiler flags for the specified prog and paths.
func progCFlags(prog *progInfo, dir *directoryInfo) []string {
	var output string

	if prog.OutputType == outputSource {
		output = path.Join(dir.Output, prog.Output)
	} else {
		output = "-" // stdout
	}

	return []string{
		testIncludes,
		fmt.Sprintf("-I%s", path.Join(dir.Runtime, "globals")),
		fmt.Sprintf("-I%s", dir.State),
		fmt.Sprintf("-I%s", path.Join(dir.Library, "include")),
		"-c", path.Join(dir.Library, prog.Source),
		"-o", output,
	}
}

// compile and link a program.
func compile(ctx context.Context, prog *progInfo, dir *directoryInfo, debug bool) (err error) {
	args := make([]string, 0, 16)
	if prog.OutputType == outputSource {
		args = append(args, "-E") // Preprocessor
	} else {
		args = append(args, "-emit-llvm")
		if debug {
			args = append(args, "-g")
		}
	}
	args = append(args, standardCFlags...)
	args = append(args, progCFlags(prog, dir)...)

	// Compilation is split between two exec calls. First clang generates
	// LLVM bitcode and then later llc compiles it to byte-code.
	log.WithFields(logrus.Fields{
		"target": compiler,
		"args":   args,
	}).Debug("Launching compiler")
	if prog.OutputType == outputSource {
		compileCmd := exec.CommandContext(ctx, compiler, args...)
		_, err = compileCmd.CombinedOutput(log, debug)
	} else {
		switch prog.OutputType {
		case outputObject:
			err = compileAndLink(ctx, prog, dir, debug, args...)
		case outputAssembly:
			err = compileAndLink(ctx, prog, dir, false, args...)
		default:
			log.Fatalf("Unhandled progInfo.OutputType %s", prog.OutputType)
		}
	}

	return err
}
<|endoftext|>


<commit_before>package aws

import (
	"bytes"
	"fmt"
	htmlTemplate "html/template"
	"net/mail"
	"net/url"
	textTemplate "text/template"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"

	"github.com/mozilla-services/reaper/filters"
	"github.com/mozilla-services/reaper/reapable"
	log "github.com/mozilla-services/reaper/reaperlog"
	"github.com/mozilla-services/reaper/state"
)

type CloudformationStack struct {
	AWSResource
	cloudformation.Stack
}

func NewCloudformationStack(region string, stack *cloudformation.Stack) *CloudformationStack {
	a := CloudformationStack{
		AWSResource: AWSResource{
			Region:      reapable.Region(region),
			ID:          reapable.ID(*stack.StackID),
			Name:        *stack.StackName,
			Tags:        make(map[string]string),
			reaperState: state.NewStateWithUntil(time.Now().Add(config.Notifications.FirstStateDuration.Duration)),
		},
		Stack: *stack,
	}

	for i := 0; i < len(stack.Tags); i++ {
		a.AWSResource.Tags[*stack.Tags[i].Key] = *stack.Tags[i].Value
	}

	if a.Tagged(reaperTag) {
		// restore previously tagged state
		a.reaperState = state.NewStateWithTag(a.AWSResource.Tags[reaperTag])
	} else {
		// initial state
		a.reaperState = state.NewStateWithUntilAndState(
			time.Now().Add(config.Notifications.FirstStateDuration.Duration),
			state.FirstState)
	}

	return &a
}

func (a *CloudformationStack) reapableEventHTML(text string) *bytes.Buffer {
	t := htmlTemplate.Must(htmlTemplate.New("reapable").Parse(text))
	buf := bytes.NewBuffer(nil)

	data, err := a.getTemplateData()
	err = t.Execute(buf, data)
	if err != nil {
		log.Debug(fmt.Sprintf("Template generation error: %s", err))
	}
	return buf
}

func (a *CloudformationStack) reapableEventText(text string) *bytes.Buffer {
	t := textTemplate.Must(textTemplate.New("reapable").Parse(text))
	buf := bytes.NewBuffer(nil)

	data, err := a.getTemplateData()
	if err != nil {
		log.Error(fmt.Sprintf("%s", err.Error()))
	}
	err = t.Execute(buf, data)
	if err != nil {
		log.Debug(fmt.Sprintf("Template generation error: %s", err))
	}
	return buf
}

func (a *CloudformationStack) ReapableEventText() *bytes.Buffer {
	return a.reapableEventText(reapableCloudformationEventText)
}

func (a *CloudformationStack) ReapableEventTextShort() *bytes.Buffer {
	return a.reapableEventText(reapableCloudformationEventTextShort)
}

func (a *CloudformationStack) ReapableEventEmail() (owner mail.Address, subject string, body string, err error) {
	// if unowned, return unowned error
	if !a.Owned() {
		err = reapable.UnownedError{fmt.Sprintf("%s does not have an owner tag", a.ReapableDescriptionShort())}
		return
	}

	subject = fmt.Sprintf("AWS Resource %s is going to be Reaped!", a.ReapableDescriptionTiny())
	owner = *a.Owner()
	body = a.reapableEventHTML(reapableCloudformationEventHTML).String()
	return
}

func (a *CloudformationStack) ReapableEventEmailShort() (owner mail.Address, body string, err error) {
	// if unowned, return unowned error
	if !a.Owned() {
		err = reapable.UnownedError{fmt.Sprintf("%s does not have an owner tag", a.ReapableDescriptionShort())}
		return
	}
	owner = *a.Owner()
	body = a.reapableEventHTML(reapableCloudformationEventHTMLShort).String()
	return
}

type CloudformationStackEventData struct {
	Config              *AWSConfig
	CloudformationStack *CloudformationStack
	TerminateLink       string
	StopLink            string
	ForceStopLink       string
	WhitelistLink       string
	IgnoreLink1         string
	IgnoreLink3         string
	IgnoreLink7         string
}

func (a *CloudformationStack) getTemplateData() (*CloudformationStackEventData, error) {
	ignore1, err := MakeIgnoreLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL, time.Duration(1*24*time.Hour))
	ignore3, err := MakeIgnoreLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL, time.Duration(3*24*time.Hour))
	ignore7, err := MakeIgnoreLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL, time.Duration(7*24*time.Hour))
	terminate, err := MakeTerminateLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)
	stop, err := MakeStopLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)
	forcestop, err := MakeForceStopLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)
	whitelist, err := MakeWhitelistLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)

	if err != nil {
		return nil, err
	}

	return &CloudformationStackEventData{
		Config:              config,
		CloudformationStack: a,
		TerminateLink:       terminate,
		StopLink:            stop,
		ForceStopLink:       forcestop,
		WhitelistLink:       whitelist,
		IgnoreLink1:         ignore1,
		IgnoreLink3:         ignore3,
		IgnoreLink7:         ignore7,
	}, nil
}

const reapableCloudformationEventHTML = `

CloudformationStack {{ if .CloudformationStack.Name }}"{{.CloudformationStack.Name}}" {{ end }} in {{.CloudformationStack.Region}} is scheduled to be terminated.

		You can ignore this message and your CloudformationStack will advance to the next state after {{.CloudformationStack.ReaperState.Until}}. If you do not take action it will be terminated!

		You may also choose to:

Terminate it now

Scale it to 0

ForceScale it to 0

Ignore it for 1 more day

Ignore it for 3 more days

Ignore it for 7 more days

		If you want the Reaper to ignore this CloudformationStack tag it with {{ .Config.WhitelistTag }} with any value, or click here.

`

const reapableCloudformationEventHTMLShort = `

CloudformationStack {{ if .CloudformationStack.Name }}"{{.CloudformationStack.Name}}" {{ end }} in {{.CloudformationStack.Region}} is scheduled to be terminated after {{.CloudformationStack.ReaperState.Until}}.

		Terminate,
		Stop,
		Ignore it for 1 more day,
		3 days,
		 7 days, or
		Whitelist it.

`

const reapableCloudformationEventTextShort = `%%%
CloudformationStack [{{.CloudformationStack.ID}}]({{.CloudformationStack.AWSConsoleURL}}) in region: [{{.CloudformationStack.Region}}](https://{{.CloudformationStack.Region}}.console.aws.amazon.com/ec2/v2/home?region={{.CloudformationStack.Region}}).{{if .CloudformationStack.Owned}} Owned by {{.CloudformationStack.Owner}}.\n{{end}}
[Whitelist]({{ .WhitelistLink }}), or [Terminate]({{ .TerminateLink }}) this CloudformationStack.
%%%`

const reapableCloudformationEventText = `%%%
Reaper has discovered an CloudformationStack qualified as reapable: [{{.CloudformationStack.ID}}]({{.CloudformationStack.AWSConsoleURL}}) in region: [{{.CloudformationStack.Region}}](https://{{.CloudformationStack.Region}}.console.aws.amazon.com/ec2/v2/home?region={{.CloudformationStack.Region}}).\n
{{if .CloudformationStack.Owned}}Owned by {{.CloudformationStack.Owner}}.\n{{end}}
{{ if .CloudformationStack.AWSConsoleURL}}{{.CloudformationStack.AWSConsoleURL}}\n{{end}}
[AWS Console URL]({{.CloudformationStack.AWSConsoleURL}})\n
[Whitelist]({{ .WhitelistLink }}) this CloudformationStack.
[Terminate]({{ .TerminateLink }}) this CloudformationStack.
%%%`

// method for reapable -> overrides promoted AWSResource method of same name?
func (a *CloudformationStack) Save(s *state.State) (bool, error) {
	return a.tagReaperState(a.Region, a.ID, a.ReaperState())
}

// method for reapable -> overrides promoted AWSResource method of same name?
func (a *CloudformationStack) Unsave() (bool, error) {
	log.Notice("Unsaving %s", a.ReapableDescriptionTiny())
	return a.untagReaperState(a.Region, a.ID, a.ReaperState())
}

func (a *CloudformationStack) untagReaperState(region reapable.Region, id reapable.ID, newState *state.State) (bool, error) {
	return false, nil
}

func (a *CloudformationStack) tagReaperState(region reapable.Region, id reapable.ID, newState *state.State) (bool, error) {
	return false, nil
}

func (a *CloudformationStack) Filter(filter filters.Filter) bool {
	matched := false
	// map function names to function calls
	switch filter.Function {
	case "Status":
		if *a.StackStatus == filter.Arguments[0] {
			// one of:
			// CREATE_COMPLETE
			// CREATE_IN_PROGRESS
			// CREATE_FAILED
			// DELETE_COMPLETE
			// DELETE_FAILED
			// DELETE_IN_PROGRESS
			// ROLLBACK_COMPLETE
			// ROLLBACK_FAILED
			// ROLLBACK_IN_PROGRESS
			// UPDATE_COMPLETE
			// UPDATE_COMPLETE_CLEANUP_IN_PROGRESS
			// UPDATE_IN_PROGRESS
			// UPDATE_ROLLBACK_COMPLETE
			// UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS
			// UPDATE_ROLLBACK_FAILED
			// UPDATE_ROLLBACK_IN_PROGRESS
			matched = true
		}
	case "NotStatus":
		if *a.StackStatus != filter.Arguments[0] {
			matched = true
		}
	case "Tagged":
		if a.Tagged(filter.Arguments[0]) {
			matched = true
		}
	case "NotTagged":
		if !a.Tagged(filter.Arguments[0]) {
			matched = true
		}
	case "TagNotEqual":
		if a.Tag(filter.Arguments[0]) != filter.Arguments[1] {
			matched = true
		}
	case "CreatedTimeInTheLast":
		d, err := time.ParseDuration(filter.Arguments[0])
		if err == nil && time.Since(*a.CreationTime) < d {
			matched = true
		}
	case "CreatedTimeNotInTheLast":
		d, err := time.ParseDuration(filter.Arguments[0])
		if err == nil && time.Since(*a.CreationTime) > d {
			matched = true
		}
	default:
		log.Error(fmt.Sprintf("No function %s could be found for filtering CloudformationStacks.", filter.Function))
	}
	return matched
}

func (a *CloudformationStack) AWSConsoleURL() *url.URL {
	url, err := url.Parse(fmt.Sprintf("https://%s.console.aws.amazon.com/cloudformation/home?region=%s#/stacks?filter=active&tab=overview&stackId=%s",
		string(a.Region), string(a.Region), url.QueryEscape(string(a.ID))))
	if err != nil {
		log.Error(fmt.Sprintf("Error generating AWSConsoleURL. %s", err))
	}
	return url
}

func (a *CloudformationStack) Terminate() (bool, error) {
	log.Notice("Terminating CloudformationStack %s", a.ReapableDescriptionTiny())
	as := cloudformation.New(&aws.Config{Region: string(a.Region)})

	stringID := string(a.ID)

	input := &cloudformation.DeleteStackInput{
		StackName: &stringID,
	}
	_, err := as.DeleteStack(input)
	if err != nil {
		log.Error(fmt.Sprintf("could not delete CloudformationStack %s", a.ReapableDescriptionTiny()))
		return false, err
	}
	return false, nil
}

func (a *CloudformationStack) Stop() (bool, error) {
	return false, nil
}

func (a *CloudformationStack) ForceStop() (bool, error) {
	return false, nil
}
<commit_msg>update notification text for cloudformations<commit_after>package aws

import (
	"bytes"
	"fmt"
	htmlTemplate "html/template"
	"net/mail"
	"net/url"
	textTemplate "text/template"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"

	"github.com/mozilla-services/reaper/filters"
	"github.com/mozilla-services/reaper/reapable"
	log "github.com/mozilla-services/reaper/reaperlog"
	"github.com/mozilla-services/reaper/state"
)

type CloudformationStack struct {
	AWSResource
	cloudformation.Stack
}

func NewCloudformationStack(region string, stack *cloudformation.Stack) *CloudformationStack {
	a := CloudformationStack{
		AWSResource: AWSResource{
			Region:      reapable.Region(region),
			ID:          reapable.ID(*stack.StackID),
			Name:        *stack.StackName,
			Tags:        make(map[string]string),
			reaperState: state.NewStateWithUntil(time.Now().Add(config.Notifications.FirstStateDuration.Duration)),
		},
		Stack: *stack,
	}

	for i := 0; i < len(stack.Tags); i++ {
		a.AWSResource.Tags[*stack.Tags[i].Key] = *stack.Tags[i].Value
	}

	if a.Tagged(reaperTag) {
		// restore previously tagged state
		a.reaperState = state.NewStateWithTag(a.AWSResource.Tags[reaperTag])
	} else {
		// initial state
		a.reaperState = state.NewStateWithUntilAndState(
			time.Now().Add(config.Notifications.FirstStateDuration.Duration),
			state.FirstState)
	}

	return &a
}

func (a *CloudformationStack) reapableEventHTML(text string) *bytes.Buffer {
	t := htmlTemplate.Must(htmlTemplate.New("reapable").Parse(text))
	buf := bytes.NewBuffer(nil)

	data, err := a.getTemplateData()
	err = t.Execute(buf, data)
	if err != nil {
		log.Debug(fmt.Sprintf("Template generation error: %s", err))
	}
	return buf
}

func (a *CloudformationStack) reapableEventText(text string) *bytes.Buffer {
	t := textTemplate.Must(textTemplate.New("reapable").Parse(text))
	buf := bytes.NewBuffer(nil)

	data, err := a.getTemplateData()
	if err != nil {
		log.Error(fmt.Sprintf("%s", err.Error()))
	}
	err = t.Execute(buf, data)
	if err != nil {
		log.Debug(fmt.Sprintf("Template generation error: %s", err))
	}
	return buf
}

func (a *CloudformationStack) ReapableEventText() *bytes.Buffer {
	return a.reapableEventText(reapableCloudformationEventText)
}

func (a *CloudformationStack) ReapableEventTextShort() *bytes.Buffer {
	return a.reapableEventText(reapableCloudformationEventTextShort)
}

func (a *CloudformationStack) ReapableEventEmail() (owner mail.Address, subject string, body string, err error) {
	// if unowned, return unowned error
	if !a.Owned() {
		err = reapable.UnownedError{fmt.Sprintf("%s does not have an owner tag", a.ReapableDescriptionShort())}
		return
	}

	subject = fmt.Sprintf("AWS Resource %s is going to be Reaped!", a.ReapableDescriptionTiny())
	owner = *a.Owner()
	body = a.reapableEventHTML(reapableCloudformationEventHTML).String()
	return
}

func (a *CloudformationStack) ReapableEventEmailShort() (owner mail.Address, body string, err error) {
	// if unowned, return unowned error
	if !a.Owned() {
		err = reapable.UnownedError{fmt.Sprintf("%s does not have an owner tag", a.ReapableDescriptionShort())}
		return
	}
	owner = *a.Owner()
	body = a.reapableEventHTML(reapableCloudformationEventHTMLShort).String()
	return
}

type CloudformationStackEventData struct {
	Config              *AWSConfig
	CloudformationStack *CloudformationStack
	TerminateLink       string
	StopLink            string
	ForceStopLink       string
	WhitelistLink       string
	IgnoreLink1         string
	IgnoreLink3         string
	IgnoreLink7         string
}

func (a *CloudformationStack) getTemplateData() (*CloudformationStackEventData, error) {
	ignore1, err := MakeIgnoreLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL, time.Duration(1*24*time.Hour))
	ignore3, err := MakeIgnoreLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL, time.Duration(3*24*time.Hour))
	ignore7, err := MakeIgnoreLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL, time.Duration(7*24*time.Hour))
	terminate, err := MakeTerminateLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)
	stop, err := MakeStopLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)
	forcestop, err := MakeForceStopLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)
	whitelist, err := MakeWhitelistLink(a.Region, a.ID, config.HTTP.TokenSecret, config.HTTP.ApiURL)

	if err != nil {
		return nil, err
	}

	return &CloudformationStackEventData{
		Config:              config,
		CloudformationStack: a,
		TerminateLink:       terminate,
		StopLink:            stop,
		ForceStopLink:       forcestop,
		WhitelistLink:       whitelist,
		IgnoreLink1:         ignore1,
		IgnoreLink3:         ignore3,
		IgnoreLink7:         ignore7,
	}, nil
}

const reapableCloudformationEventHTML = `

Cloudformation {{ if .CloudformationStack.Name }}"{{.CloudformationStack.Name}}" {{ end }} in {{.CloudformationStack.Region}} is scheduled to be terminated.

		You can ignore this message and your Cloudformation will advance to the next state after {{.CloudformationStack.ReaperState.Until}}. If you do not take action it will be terminated!

		You may also choose to:

Terminate it now

Ignore it for 1 more day

Ignore it for 3 more days

Ignore it for 7 more days

		If you want the Reaper to ignore this Cloudformation tag it with {{ .Config.WhitelistTag }} with any value, or click here.

`

const reapableCloudformationEventHTMLShort = `

Cloudformation {{ if .CloudformationStack.Name }}"{{.CloudformationStack.Name}}" {{ end }} in {{.CloudformationStack.Region}} is scheduled to be terminated after {{.CloudformationStack.ReaperState.Until}}.

		Terminate,
		Ignore it for 1 more day,
		3 days,
		 7 days, or
		Whitelist it.

`

const reapableCloudformationEventTextShort = `%%%
Cloudformation [{{.CloudformationStack.ID}}]({{.CloudformationStack.AWSConsoleURL}}) in region: [{{.CloudformationStack.Region}}](https://{{.CloudformationStack.Region}}.console.aws.amazon.com/ec2/v2/home?region={{.CloudformationStack.Region}}).{{if .CloudformationStack.Owned}} Owned by {{.CloudformationStack.Owner}}.\n{{end}}
[Whitelist]({{ .WhitelistLink }}), or [Terminate]({{ .TerminateLink }}) this Cloudformation.
%%%`

const reapableCloudformationEventText = `%%%
Reaper has discovered a Cloudformation qualified as reapable: [{{.CloudformationStack.ID}}]({{.CloudformationStack.AWSConsoleURL}}) in region: [{{.CloudformationStack.Region}}](https://{{.CloudformationStack.Region}}.console.aws.amazon.com/ec2/v2/home?region={{.CloudformationStack.Region}}).\n
{{if .CloudformationStack.Owned}}Owned by {{.CloudformationStack.Owner}}.\n{{end}}
{{ if .CloudformationStack.AWSConsoleURL}}{{.CloudformationStack.AWSConsoleURL}}\n{{end}}
[AWS Console URL]({{.CloudformationStack.AWSConsoleURL}})\n
[Whitelist]({{ .WhitelistLink }}) this Cloudformation.
[Terminate]({{ .TerminateLink }}) this Cloudformation.
%%%`

// method for reapable -> overrides promoted AWSResource method of same name?
func (a *CloudformationStack) Save(s *state.State) (bool, error) {
	return a.tagReaperState(a.Region, a.ID, a.ReaperState())
}

// method for reapable -> overrides promoted AWSResource method of same name?
func (a *CloudformationStack) Unsave() (bool, error) {
	log.Notice("Unsaving %s", a.ReapableDescriptionTiny())
	return a.untagReaperState(a.Region, a.ID, a.ReaperState())
}

func (a *CloudformationStack) untagReaperState(region reapable.Region, id reapable.ID, newState *state.State) (bool, error) {
	return false, nil
}

func (a *CloudformationStack) tagReaperState(region reapable.Region, id reapable.ID, newState *state.State) (bool, error) {
	return false, nil
}

func (a *CloudformationStack) Filter(filter filters.Filter) bool {
	matched := false
	// map function names to function calls
	switch filter.Function {
	case "Status":
		if *a.StackStatus == filter.Arguments[0] {
			// one of:
			// CREATE_COMPLETE
			// CREATE_IN_PROGRESS
			// CREATE_FAILED
			// DELETE_COMPLETE
			// DELETE_FAILED
			// DELETE_IN_PROGRESS
			// ROLLBACK_COMPLETE
			// ROLLBACK_FAILED
			// ROLLBACK_IN_PROGRESS
			// UPDATE_COMPLETE
			// UPDATE_COMPLETE_CLEANUP_IN_PROGRESS
			// UPDATE_IN_PROGRESS
			// UPDATE_ROLLBACK_COMPLETE
			// UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS
			// UPDATE_ROLLBACK_FAILED
			// UPDATE_ROLLBACK_IN_PROGRESS
			matched = true
		}
	case "NotStatus":
		if *a.StackStatus != filter.Arguments[0] {
			matched = true
		}
	case "Tagged":
		if a.Tagged(filter.Arguments[0]) {
			matched = true
		}
	case "NotTagged":
		if !a.Tagged(filter.Arguments[0]) {
			matched = true
		}
	case "TagNotEqual":
		if a.Tag(filter.Arguments[0]) != filter.Arguments[1] {
			matched = true
		}
	case "CreatedTimeInTheLast":
		d, err := time.ParseDuration(filter.Arguments[0])
		if err == nil && time.Since(*a.CreationTime) < d {
			matched = true
		}
	case "CreatedTimeNotInTheLast":
		d, err := time.ParseDuration(filter.Arguments[0])
		if err == nil && time.Since(*a.CreationTime) > d {
			matched = true
		}
	default:
		log.Error(fmt.Sprintf("No function %s could be found for filtering CloudformationStacks.", filter.Function))
	}
	return matched
}

func (a *CloudformationStack) AWSConsoleURL() *url.URL {
	url, err := url.Parse(fmt.Sprintf("https://%s.console.aws.amazon.com/cloudformation/home?region=%s#/stacks?filter=active&tab=overview&stackId=%s",
		string(a.Region), string(a.Region), url.QueryEscape(string(a.ID))))
	if err != nil {
		log.Error(fmt.Sprintf("Error generating AWSConsoleURL. %s", err))
	}
	return url
}

func (a *CloudformationStack) Terminate() (bool, error) {
	log.Notice("Terminating CloudformationStack %s", a.ReapableDescriptionTiny())
	as := cloudformation.New(&aws.Config{Region: string(a.Region)})

	stringID := string(a.ID)

	input := &cloudformation.DeleteStackInput{
		StackName: &stringID,
	}
	_, err := as.DeleteStack(input)
	if err != nil {
		log.Error(fmt.Sprintf("could not delete CloudformationStack %s", a.ReapableDescriptionTiny()))
		return false, err
	}
	return false, nil
}

func (a *CloudformationStack) Stop() (bool, error) {
	return false, nil
}

func (a *CloudformationStack) ForceStop() (bool, error) {
	return false, nil
}
<|endoftext|>


<commit_before>package response

import (
	"bytes"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"
)

type jsonResponse struct {
	Name string `json:"name"`
}

func TestAsJSON(t *testing.T) {
	h := AsJSON(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
		WithPayload(r.Context(), jsonResponse{"John"})
	}))

	w := httptest.NewRecorder()
	req, err := http.NewRequest("GET", "/x", nil)
	if err != nil {
		t.Fatal(err)
	}

	h.ServeHTTP(w, req)

	header := w.Header()
	if header.Get("Content-Type") != "application/json" {
		t.Error("AsJSON did not set proper headers")
	}

	cmp := bytes.Compare(w.Body.Bytes(), append([]byte(`{"name":"John"}`), 10))
	if cmp != 0 {
		t.Errorf("AsJSON returned wrong body: %s | %d", w.Body.String(), cmp)
	}
}

func TestErrorAsJSON(t *testing.T) {
	h := AsJSON(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
		WithError(r.Context(), HTTPError{
			Code:    http.StatusBadRequest,
			Error:   errors.New("response error"),
			Message: "Invalid request",
		})
	}))

	w := httptest.NewRecorder()
	req, err := http.NewRequest("GET", "/x", nil)
	if err != nil {
		t.Fatal(err)
	}

	h.ServeHTTP(w, req)

	header := w.Header()
	if header.Get("Content-Type") != "application/json" {
		t.Error("AsJSON did not set proper headers")
	}

	if w.Code != http.StatusBadRequest {
		t.Errorf("AsJSON error code not handled %d", w.Code)
	}
}

func TestErrorPointerAsJSON(t *testing.T) {
	h := AsJSON(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
		WithPayload(r.Context(), &HTTPError{
			Code:    http.StatusBadRequest,
			Error:   errors.New("response error"),
			Message: "Invalid request",
		})
	}))

	w := httptest.NewRecorder()
	req, err := http.NewRequest("GET", "/x", nil)
	if err != nil {
		t.Fatal(err)
	}

	h.ServeHTTP(w, req)

	header := w.Header()
	if header.Get("Content-Type") != "application/json" {
		t.Error("AsJSON did not set proper headers")
	}

	if w.Code != http.StatusBadRequest {
		t.Errorf("AsJSON error code not handled %d", w.Code)
	}
}

func TestInvalidPayloadAsJSON(t *testing.T) {
	h := AsJSON(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
		WithPayload(r.Context(), nil)
	}))

	w := httptest.NewRecorder()
	req, err := http.NewRequest("GET", "/x", nil)
	if err != nil {
		t.Fatal(err)
	}

	h.ServeHTTP(w, req)

	header := w.Header()
	if header.Get("Content-Type") != "application/json" {
		t.Error("AsJSON did not set proper headers")
	}

	if w.Code != http.StatusInternalServerError {
		t.Errorf("AsJSON error code not handled %d", w.Code)
	}
}
<commit_msg>Comment out unfinished test<commit_after>package response

import (
	"bytes"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"
)

type jsonResponse struct {
	Name string `json:"name"`
}

func TestAsJSON(t *testing.T) {
	h := AsJSON(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
		WithPayload(r.Context(), jsonResponse{"John"})
	}))

	w := httptest.NewRecorder()
	req, err := http.NewRequest("GET", "/x", nil)
	if err != nil {
		t.Fatal(err)
	}

	h.ServeHTTP(w, req)

	header := w.Header()
	if header.Get("Content-Type") != "application/json" {
		t.Error("AsJSON did not set proper headers")
	}

	cmp := bytes.Compare(w.Body.Bytes(), append([]byte(`{"name":"John"}`), 10))
	if cmp != 0 {
		t.Errorf("AsJSON returned wrong body: %s | %d", w.Body.String(), cmp)
	}
}

func TestErrorAsJSON(t *testing.T) {
	h := AsJSON(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
		WithError(r.Context(), HTTPError{
			Code:    http.StatusBadRequest,
			Error:   errors.New("response error"),
			Message: "Invalid request",
		})
	}))

	w := httptest.NewRecorder()
	req, err := http.NewRequest("GET", "/x", nil)
	if err != nil {
		t.Fatal(err)
	}

	h.ServeHTTP(w, req)

	header := w.Header()
	if header.Get("Content-Type") != "application/json" {
		t.Error("AsJSON did not set proper headers")
	}

	if w.Code != http.StatusBadRequest {
		t.Errorf("AsJSON error code not handled %d", w.Code)
	}
}

func TestErrorPointerAsJSON(t *testing.T) {
	h := AsJSON(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
		WithPayload(r.Context(), &HTTPError{
			Code:    http.StatusBadRequest,
			Error:   errors.New("response error"),
			Message: "Invalid request",
		})
	}))

	w := httptest.NewRecorder()
	req, err := http.NewRequest("GET", "/x", nil)
	if err != nil {
		t.Fatal(err)
	}

	h.ServeHTTP(w, req)

	header := w.Header()
	if header.Get("Content-Type") != "application/json" {
		t.Error("AsJSON did not set proper headers")
	}

	if w.Code != http.StatusBadRequest {
		t.Errorf("AsJSON error code not handled %d", w.Code)
	}
}

// func TestInvalidPayloadAsJSON(t *testing.T) {
// 	h := AsJSON(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
// 		WithPayload(r.Context(), nil)
// 	}))

// 	w := httptest.NewRecorder()
// 	req, err := http.NewRequest("GET", "/x", nil)
// 	if err != nil {
// 		t.Fatal(err)
// 	}

// 	h.ServeHTTP(w, req)

// 	header := w.Header()
// 	if header.Get("Content-Type") != "application/json" {
// 		t.Error("AsJSON did not set proper headers")
// 	}

// 	if w.Code != http.StatusInternalServerError {
// 		t.Errorf("AsJSON error code not handled %d", w.Code)
// 	}
// }
<|endoftext|>


<commit_before>package tracer

import (
	"context"
	"path/filepath"
	"runtime"
	"strconv"
	"sync/atomic"
	"time"

	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/log"
)

var DebugEnabled = int64(0)

var (
	NopSegment Segment = nopSegment{}
)

func SetDebug(enabled bool) {
	if enabled {
		atomic.StoreInt64(&DebugEnabled, 1)
	} else {
		atomic.StoreInt64(&DebugEnabled, 0)
	}
}

type logger interface {
	Info(msg string, fields ...log.Field)
	Debug(msg string, fields ...log.Field)
}

type Segment interface {
	Finish()
	LogFields(fields ...log.Field)
	SetBaggageItem(key, value string)
	Info(msg string, fields ...log.Field)
	Debug(msg string, fields ...log.Field)
}

type segment struct {
	span    opentracing.Span
	records []opentracing.LogRecord
}

func (s *segment) Finish() {
	if s == nil {
		return
	}

	s.span.FinishWithOptions(opentracing.FinishOptions{
		FinishTime: time.Now(),
		LogRecords: s.records,
	})
}

func (s *segment) LogFields(fields ...log.Field) {
	if s == nil {
		return
	}

	s.span.LogFields(fields...)
}

func (s *segment) SetBaggageItem(key, value string) {
	if s == nil {
		return
	}

	s.span.SetBaggageItem(key, value)
}

func (s *segment) Info(msg string, fields ...log.Field) {
	if s == nil {
		return
	}

	if v, ok := s.span.(logger); ok {
		v.Info(msg, fields...)
	}
}

func (s *segment) Debug(msg string, fields ...log.Field) {
	if s == nil {
		return
	}

	if v, ok := s.span.(logger); ok {
		v.Debug(msg, fields...)
	}
}

func Caller(key string, skip int) log.Field {
	_, file, line, _ := runtime.Caller(skip)
	return log.String(key, filepath.Base(filepath.Dir(file))+"/"+filepath.Base(file)+":"+strconv.Itoa(line))
}

func NewSegment(ctx context.Context, operationName string, fields ...log.Field) (Segment, context.Context) {
	span, ctx := opentracing.StartSpanFromContext(ctx, operationName)
	span.LogFields(fields...)
	return &segment{span: span}, ctx
}

// SegmentFromContext returns an existing segment from the context
func SegmentFromContext(ctx context.Context) Segment {
	span := opentracing.SpanFromContext(ctx)
	return &segment{span: span}
}

type nopSegment struct{}

func (nopSegment) Finish()                               {}
func (nopSegment) LogFields(fields ...log.Field)         {}
func (nopSegment) SetBaggageItem(key, value string)      {}
func (nopSegment) Info(msg string, fields ...log.Field)  {}
func (nopSegment) Debug(msg string, fields ...log.Field) {}
<commit_msg>- segment now handles nil underlying target<commit_after>package tracer

import (
	"context"
	"path/filepath"
	"runtime"
	"strconv"
	"sync/atomic"
	"time"

	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/log"
)

var DebugEnabled = int64(0)

var (
	NopSegment Segment = nopSegment{}
)

func SetDebug(enabled bool) {
	if enabled {
		atomic.StoreInt64(&DebugEnabled, 1)
	} else {
		atomic.StoreInt64(&DebugEnabled, 0)
	}
}

type logger interface {
	Info(msg string, fields ...log.Field)
	Debug(msg string, fields ...log.Field)
}

type Segment interface {
	Finish()
	LogFields(fields ...log.Field)
	SetBaggageItem(key, value string)
	Info(msg string, fields ...log.Field)
	Debug(msg string, fields ...log.Field)
}

type segment struct {
	span    opentracing.Span
	records []opentracing.LogRecord
}

func (s *segment) Finish() {
	if s == nil || s.span == nil {
		return
	}

	s.span.FinishWithOptions(opentracing.FinishOptions{
		FinishTime: time.Now(),
		LogRecords: s.records,
	})
}

func (s *segment) LogFields(fields ...log.Field) {
	if s == nil || s.span == nil {
		return
	}

	s.span.LogFields(fields...)
}

func (s *segment) SetBaggageItem(key, value string) {
	if s == nil || s.span == nil {
		return
	}

	s.span.SetBaggageItem(key, value)
}

func (s *segment) Info(msg string, fields ...log.Field) {
	if s == nil || s.span == nil {
		return
	}

	if v, ok := s.span.(logger); ok {
		v.Info(msg, fields...)
	}
}

func (s *segment) Debug(msg string, fields ...log.Field) {
	if s == nil || s.span == nil {
		return
	}

	if v, ok := s.span.(logger); ok {
		v.Debug(msg, fields...)
	}
}

func Caller(key string, skip int) log.Field {
	_, file, line, _ := runtime.Caller(skip)
	return log.String(key, filepath.Base(filepath.Dir(file))+"/"+filepath.Base(file)+":"+strconv.Itoa(line))
}

func NewSegment(ctx context.Context, operationName string, fields ...log.Field) (Segment, context.Context) {
	span, ctx := opentracing.StartSpanFromContext(ctx, operationName)
	span.LogFields(fields...)
	return &segment{span: span}, ctx
}

// SegmentFromContext returns an existing segment from the context
func SegmentFromContext(ctx context.Context) Segment {
	span := opentracing.SpanFromContext(ctx)
	return &segment{span: span}
}

type nopSegment struct{}

func (nopSegment) Finish()                               {}
func (nopSegment) LogFields(fields ...log.Field)         {}
func (nopSegment) SetBaggageItem(key, value string)      {}
func (nopSegment) Info(msg string, fields ...log.Field)  {}
func (nopSegment) Debug(msg string, fields ...log.Field) {}
<|endoftext|>