<commit_before>/*
Copyright 2020 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 anago

import (
	"os"
	"path/filepath"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"

	"k8s.io/release/pkg/build"
	"k8s.io/release/pkg/release"
)

// pushCmd represents the subcommand for `krel anago push`
var pushCmd = &cobra.Command{
	Use:   "push",
	Short: "Push release artifacts into the Google Cloud",
	Long: `krel anago push

This subcommand can be used to push the release artifacts to the Google Cloud.
It's only indented to be used from anago, which means the command might be
removed in future releases again when anago goes end of life.
`,
	SilenceUsage:  true,
	SilenceErrors: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return errors.Wrap(runPush(pushOpts), "run krel anago push")
	},
}

var (
	pushOpts     = &build.Options{}
	runStage     bool
	runRelease   bool
	buildVersion string
)

func init() {
	pushCmd.PersistentFlags().BoolVar(
		&runStage,
		"stage",
		false,
		"run in stage mode",
	)

	pushCmd.PersistentFlags().BoolVar(
		&runRelease,
		"release",
		false,
		"run in release mode",
	)

	pushCmd.PersistentFlags().StringVar(
		&pushOpts.Version,
		"version",
		"",
		"version to be used",
	)

	pushCmd.PersistentFlags().StringVar(
		&pushOpts.BuildDir,
		"build-dir",
		"",
		"build artifact directory of the release",
	)

	pushCmd.PersistentFlags().StringVar(
		&pushOpts.Bucket,
		"bucket",
		"",
		"GCS bucket to be used",
	)

	pushCmd.PersistentFlags().StringVar(
		&pushOpts.Registry,
		"container-registry",
		"",
		"Container image registry to be used",
	)

	pushCmd.PersistentFlags().StringVar(
		&buildVersion,
		"build-version",
		"",
		"Build version from Jenkins (only used when --release specified)",
	)

	pushOpts.AllowDup = true
	pushOpts.ValidateRemoteImageDigests = true

	AnagoCmd.AddCommand(pushCmd)
}

func runPush(opts *build.Options) error {
	buildInstance := build.NewInstance(opts)
	if err := buildInstance.CheckReleaseBucket(); err != nil {
		return errors.Wrap(err, "check release bucket access")
	}

	if runStage {
		return runPushStage(buildInstance, opts)
	} else if runRelease {
		return runPushRelease(buildInstance, opts)
	}

	return errors.New("neither --stage nor --release provided")
}

func runPushStage(
	buildInstance *build.Instance,
	opts *build.Options,
) error {
	workDir := os.Getenv("GOPATH")
	if workDir == "" {
		return errors.New("GOPATH is not set")
	}

	// Stage the local source tree
	if err := buildInstance.StageLocalSourceTree(workDir, buildVersion); err != nil {
		return errors.Wrap(err, "staging local source tree")
	}

	// Stage local artifacts and write checksums
	if err := buildInstance.StageLocalArtifacts(); err != nil {
		return errors.Wrap(err, "staging local artifacts")
	}
	gcsPath := filepath.Join(opts.Bucket, "stage", buildVersion, opts.Version)

	// Push gcs-stage to GCS
	if err := buildInstance.PushReleaseArtifacts(
		filepath.Join(opts.BuildDir, release.GCSStagePath, opts.Version),
		filepath.Join(gcsPath, release.GCSStagePath, opts.Version),
	); err != nil {
		return errors.Wrap(err, "pushing release artifacts")
	}

	// Push container release-images to GCS
	if err := buildInstance.PushReleaseArtifacts(
		filepath.Join(opts.BuildDir, release.ImagesPath),
		filepath.Join(gcsPath, release.ImagesPath),
	); err != nil {
		return errors.Wrap(err, "pushing release artifacts")
	}

	// Push container images into registry
	if err := buildInstance.PushContainerImages(); err != nil {
		return errors.Wrap(err, "pushing container images")
	}

	return nil
}

func runPushRelease(
	buildInstance *build.Instance,
	opts *build.Options,
) error {
	if err := buildInstance.CopyStagedFromGCS(opts.Bucket, buildVersion); err != nil {
		return errors.Wrap(err, "copy staged from GCS")
	}

	// In an official nomock release, we want to ensure that container images
	// have been promoted from staging to production, so we do the image
	// manifest validation against production instead of staging.
	targetRegistry := opts.Registry
	if targetRegistry == release.GCRIOPathStaging {
		targetRegistry = release.GCRIOPathProd
	}
	// Image promotion has been done on nomock stage, verify that the images
	// are available.
	if err := release.NewImages().Validate(
		targetRegistry, opts.Version, opts.BuildDir,
	); err != nil {
		return errors.Wrap(err, "validate container images")
	}

	if err := release.NewPublisher().PublishVersion(
		"release", opts.Version, opts.BuildDir, opts.Bucket, "", nil, false, false,
	); err != nil {
		return errors.Wrap(err, "publish release")
	}
	return nil
}
<commit_msg>Pass correct gcsRoot in krel anago push<commit_after>/*
Copyright 2020 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 anago

import (
	"os"
	"path/filepath"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"

	"k8s.io/release/pkg/build"
	"k8s.io/release/pkg/release"
)

// pushCmd represents the subcommand for `krel anago push`
var pushCmd = &cobra.Command{
	Use:   "push",
	Short: "Push release artifacts into the Google Cloud",
	Long: `krel anago push

This subcommand can be used to push the release artifacts to the Google Cloud.
It's only indented to be used from anago, which means the command might be
removed in future releases again when anago goes end of life.
`,
	SilenceUsage:  true,
	SilenceErrors: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return errors.Wrap(runPush(pushOpts), "run krel anago push")
	},
}

var (
	pushOpts     = &build.Options{}
	runStage     bool
	runRelease   bool
	buildVersion string
)

func init() {
	pushCmd.PersistentFlags().BoolVar(
		&runStage,
		"stage",
		false,
		"run in stage mode",
	)

	pushCmd.PersistentFlags().BoolVar(
		&runRelease,
		"release",
		false,
		"run in release mode",
	)

	pushCmd.PersistentFlags().StringVar(
		&pushOpts.Version,
		"version",
		"",
		"version to be used",
	)

	pushCmd.PersistentFlags().StringVar(
		&pushOpts.BuildDir,
		"build-dir",
		"",
		"build artifact directory of the release",
	)

	pushCmd.PersistentFlags().StringVar(
		&pushOpts.Bucket,
		"bucket",
		"",
		"GCS bucket to be used",
	)

	pushCmd.PersistentFlags().StringVar(
		&pushOpts.Registry,
		"container-registry",
		"",
		"Container image registry to be used",
	)

	pushCmd.PersistentFlags().StringVar(
		&buildVersion,
		"build-version",
		"",
		"Build version from Jenkins (only used when --release specified)",
	)

	pushOpts.AllowDup = true
	pushOpts.ValidateRemoteImageDigests = true

	AnagoCmd.AddCommand(pushCmd)
}

func runPush(opts *build.Options) error {
	buildInstance := build.NewInstance(opts)
	if err := buildInstance.CheckReleaseBucket(); err != nil {
		return errors.Wrap(err, "check release bucket access")
	}

	if runStage {
		return runPushStage(buildInstance, opts)
	} else if runRelease {
		return runPushRelease(buildInstance, opts)
	}

	return errors.New("neither --stage nor --release provided")
}

func runPushStage(
	buildInstance *build.Instance,
	opts *build.Options,
) error {
	workDir := os.Getenv("GOPATH")
	if workDir == "" {
		return errors.New("GOPATH is not set")
	}

	// Stage the local source tree
	if err := buildInstance.StageLocalSourceTree(workDir, buildVersion); err != nil {
		return errors.Wrap(err, "staging local source tree")
	}

	// Stage local artifacts and write checksums
	if err := buildInstance.StageLocalArtifacts(); err != nil {
		return errors.Wrap(err, "staging local artifacts")
	}
	gcsPath := filepath.Join(opts.Bucket, "stage", buildVersion, opts.Version)

	// Push gcs-stage to GCS
	if err := buildInstance.PushReleaseArtifacts(
		filepath.Join(opts.BuildDir, release.GCSStagePath, opts.Version),
		filepath.Join(gcsPath, release.GCSStagePath, opts.Version),
	); err != nil {
		return errors.Wrap(err, "pushing release artifacts")
	}

	// Push container release-images to GCS
	if err := buildInstance.PushReleaseArtifacts(
		filepath.Join(opts.BuildDir, release.ImagesPath),
		filepath.Join(gcsPath, release.ImagesPath),
	); err != nil {
		return errors.Wrap(err, "pushing release artifacts")
	}

	// Push container images into registry
	if err := buildInstance.PushContainerImages(); err != nil {
		return errors.Wrap(err, "pushing container images")
	}

	return nil
}

func runPushRelease(
	buildInstance *build.Instance,
	opts *build.Options,
) error {
	if err := buildInstance.CopyStagedFromGCS(opts.Bucket, buildVersion); err != nil {
		return errors.Wrap(err, "copy staged from GCS")
	}

	// In an official nomock release, we want to ensure that container images
	// have been promoted from staging to production, so we do the image
	// manifest validation against production instead of staging.
	targetRegistry := opts.Registry
	if targetRegistry == release.GCRIOPathStaging {
		targetRegistry = release.GCRIOPathProd
	}
	// Image promotion has been done on nomock stage, verify that the images
	// are available.
	if err := release.NewImages().Validate(
		targetRegistry, opts.Version, opts.BuildDir,
	); err != nil {
		return errors.Wrap(err, "validate container images")
	}

	if err := release.NewPublisher().PublishVersion(
		"release", opts.Version, opts.BuildDir, opts.Bucket, "release", nil, false, false,
	); err != nil {
		return errors.Wrap(err, "publish release")
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/datawire/teleproxy/pkg/k8s"
	"github.com/datawire/teleproxy/pkg/tpu"
	"github.com/spf13/cobra"
)

type Syncer struct {
	Watcher     *k8s.Watcher
	SyncCommand string
	Kinds       []string
	Mux         sync.Mutex // protects the whole data structure
	Dirty       bool
	ModTime     time.Time
	SyncTime    time.Time
	SyncCount   int
	MinInterval time.Duration
	MaxInterval time.Duration
	WarmupDelay time.Duration
	router      *http.ServeMux
	port        string
	snapshotMux sync.Mutex // protects just the snapshot map
	snapshots   map[string]map[string][]byte
}

func (s *Syncer) maybeSync() {
	s.Mux.Lock()
	defer s.Mux.Unlock()

	if !s.Dirty {
		return
	}

	now := time.Now()
	if now.Sub(s.ModTime) > s.MinInterval || now.Sub(s.SyncTime) > s.MaxInterval {
		s.SyncTime = now
		s.Dirty = false
		s.sync()
	}
}

func (s *Syncer) sync() {
	s.SyncCount += 1
	snapshot_id := s.write()
	s.invoke(snapshot_id)
}

func (s *Syncer) write() string {
	s.snapshotMux.Lock()
	defer s.snapshotMux.Unlock()
	snapshot_id := fmt.Sprintf("%d", s.SyncCount)
	s.snapshots[snapshot_id] = make(map[string][]byte)
	for _, kind := range s.Kinds {
		resources := s.Watcher.List(kind)
		bytes, err := k8s.MarshalResources(resources)
		if err != nil {
			panic(err)
		}
		s.snapshots[snapshot_id][kind] = bytes
		for _, rsrc := range resources {
			qname := path.Join(kind, rsrc.Namespace(), rsrc.Name())
			bytes, err := k8s.MarshalResource(rsrc)
			if err != nil {
				panic(err)
			}
			s.snapshots[snapshot_id][qname] = bytes
		}
	}
	s.cleanup()
	return snapshot_id
}

func (s *Syncer) cleanup() {
	for k := range s.snapshots {
		keep := false
		for c := s.SyncCount; c > s.SyncCount-10; c-- {
			id := fmt.Sprintf("%d", c)
			if id == k {
				keep = true
			}
		}
		if !keep {
			delete(s.snapshots, k)
			log.Printf("deleting snapshot %s", k)
		}
	}
}

func (s *Syncer) invoke(snapshot_id string) {
	k := tpu.NewKeeper("SYNC", fmt.Sprintf("%s http://localhost:%s/api/snapshot/%s", s.SyncCommand, s.port, snapshot_id))
	k.Limit = 1
	k.Start()
	k.Wait()
}

func (s *Syncer) Run() {
	go func() {
		time.Sleep(s.WarmupDelay)
		for {
			s.maybeSync()
			time.Sleep(s.MinInterval)
		}
	}()

	for _, k := range s.Kinds {
		// this alias is important so the func picks up the
		// value from the current iteration instead of the
		// value from the last iteration
		kind := k
		s.Watcher.WatchNamespace(NAMESPACE, kind, func(_ *k8s.Watcher) {
			s.Mux.Lock()
			defer s.Mux.Unlock()
			s.Dirty = true
			s.ModTime = time.Now()
		})
	}
	s.Watcher.Start()
	s.serve()
}

func (s *Syncer) serve() {
	s.routes()

	ln, err := net.Listen("tcp", fmt.Sprintf(":%s", PORT))
	if err != nil {
		// Error starting or closing listener:
		log.Fatalf("kubewatch: %v", err)
	}

	_, port, err := net.SplitHostPort(ln.Addr().String())
	if err != nil {
		log.Fatalf("kubewatch: %v", err)
	}
	s.port = port

	server := http.Server{
		Handler: s.router,
	}

	if err := server.Serve(ln); err != http.ErrServerClosed {
		// Error starting or closing listener:
		log.Fatalf("kubewatch: %v", err)
	}
}

func (s *Syncer) routes() {
	s.router.HandleFunc("/api/snapshot/", s.safe(s.handleSnapshot()))
}

func (s *Syncer) safe(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if r := recover(); r != nil {
				var msg string
				switch e := r.(type) {
				case error:
					msg = e.Error()
				default:
					msg = fmt.Sprintf("%v", r)
				}
				http.Error(w, fmt.Sprintf("Server Error: %s", msg), http.StatusInternalServerError)
			}
		}()
		h(w, r)
	}
}

func (s *Syncer) handleSnapshot() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.snapshotMux.Lock()
		defer s.snapshotMux.Unlock()
		parts := strings.Split(r.URL.Path, "/")
		parts = parts[3:]
		snapshot_id := parts[0]
		snapshot, ok := s.snapshots[snapshot_id]
		if !ok {
			http.NotFound(w, r)
			return
		}
		qname := strings.Join(parts[1:], "/")
		body, ok := snapshot[qname]
		if !ok {
			http.NotFound(w, r)
		}
		w.Write(body)
	}
}

var Version = "(unknown version)"

var KUBEWATCH = &cobra.Command{
	Use:  "kubewatch [options] <resources>",
	Args: cobra.MinimumNArgs(1),
	Run:  kubewatch,
}

func init() {
	KUBEWATCH.Version = Version
	KUBEWATCH.Flags().StringVarP(&PORT, "port", "p", "0", "port for kubewatch api")
	KUBEWATCH.Flags().StringVarP(&SYNC_COMMAND, "sync", "s", "curl", "sync command")
	KUBEWATCH.Flags().StringVarP(&NAMESPACE, "namespace", "n", "", "namespace to watch (defaults to all)")
	KUBEWATCH.Flags().DurationVarP(&MIN_INTERVAL, "min-interval", "m", 250*time.Millisecond, "min sync interval")
	KUBEWATCH.Flags().DurationVarP(&MAX_INTERVAL, "max-interval", "M", time.Second, "max sync interval")
	KUBEWATCH.Flags().DurationVarP(&WARMUP_DELAY, "warmup-delay", "w", 0, "warmup delay")
}

var (
	PORT         string
	SYNC_COMMAND string
	NAMESPACE    string
	MIN_INTERVAL time.Duration
	MAX_INTERVAL time.Duration
	WARMUP_DELAY time.Duration
)

func kubewatch(cmd *cobra.Command, args []string) {
	s := Syncer{
		Watcher:     k8s.NewClient(nil).Watcher(),
		SyncCommand: SYNC_COMMAND,
		Kinds:       args,
		MinInterval: MIN_INTERVAL,
		MaxInterval: MAX_INTERVAL,
		WarmupDelay: WARMUP_DELAY,
		router:      http.NewServeMux(),
		snapshots:   make(map[string]map[string][]byte),
	}

	s.Run()
}

func main() {
	KUBEWATCH.Execute()
}
<commit_msg>Handle s.Watcher.WatchNamespace error<commit_after>package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/datawire/teleproxy/pkg/k8s"
	"github.com/datawire/teleproxy/pkg/tpu"
	"github.com/spf13/cobra"
)

type Syncer struct {
	Watcher     *k8s.Watcher
	SyncCommand string
	Kinds       []string
	Mux         sync.Mutex // protects the whole data structure
	Dirty       bool
	ModTime     time.Time
	SyncTime    time.Time
	SyncCount   int
	MinInterval time.Duration
	MaxInterval time.Duration
	WarmupDelay time.Duration
	router      *http.ServeMux
	port        string
	snapshotMux sync.Mutex // protects just the snapshot map
	snapshots   map[string]map[string][]byte
}

func (s *Syncer) maybeSync() {
	s.Mux.Lock()
	defer s.Mux.Unlock()

	if !s.Dirty {
		return
	}

	now := time.Now()
	if now.Sub(s.ModTime) > s.MinInterval || now.Sub(s.SyncTime) > s.MaxInterval {
		s.SyncTime = now
		s.Dirty = false
		s.sync()
	}
}

func (s *Syncer) sync() {
	s.SyncCount += 1
	snapshot_id := s.write()
	s.invoke(snapshot_id)
}

func (s *Syncer) write() string {
	s.snapshotMux.Lock()
	defer s.snapshotMux.Unlock()
	snapshot_id := fmt.Sprintf("%d", s.SyncCount)
	s.snapshots[snapshot_id] = make(map[string][]byte)
	for _, kind := range s.Kinds {
		resources := s.Watcher.List(kind)
		bytes, err := k8s.MarshalResources(resources)
		if err != nil {
			panic(err)
		}
		s.snapshots[snapshot_id][kind] = bytes
		for _, rsrc := range resources {
			qname := path.Join(kind, rsrc.Namespace(), rsrc.Name())
			bytes, err := k8s.MarshalResource(rsrc)
			if err != nil {
				panic(err)
			}
			s.snapshots[snapshot_id][qname] = bytes
		}
	}
	s.cleanup()
	return snapshot_id
}

func (s *Syncer) cleanup() {
	for k := range s.snapshots {
		keep := false
		for c := s.SyncCount; c > s.SyncCount-10; c-- {
			id := fmt.Sprintf("%d", c)
			if id == k {
				keep = true
			}
		}
		if !keep {
			delete(s.snapshots, k)
			log.Printf("deleting snapshot %s", k)
		}
	}
}

func (s *Syncer) invoke(snapshot_id string) {
	k := tpu.NewKeeper("SYNC", fmt.Sprintf("%s http://localhost:%s/api/snapshot/%s", s.SyncCommand, s.port, snapshot_id))
	k.Limit = 1
	k.Start()
	k.Wait()
}

func (s *Syncer) Run() {
	go func() {
		time.Sleep(s.WarmupDelay)
		for {
			s.maybeSync()
			time.Sleep(s.MinInterval)
		}
	}()

	for _, k := range s.Kinds {
		// this alias is important so the func picks up the
		// value from the current iteration instead of the
		// value from the last iteration
		kind := k
		err := s.Watcher.WatchNamespace(NAMESPACE, kind, func(_ *k8s.Watcher) {
			s.Mux.Lock()
			defer s.Mux.Unlock()
			s.Dirty = true
			s.ModTime = time.Now()
		})
		if err != nil {
			log.Fatalf("kubewatch: %v", err)
		}
	}
	s.Watcher.Start()
	s.serve()
}

func (s *Syncer) serve() {
	s.routes()

	ln, err := net.Listen("tcp", fmt.Sprintf(":%s", PORT))
	if err != nil {
		// Error starting or closing listener:
		log.Fatalf("kubewatch: %v", err)
	}

	_, port, err := net.SplitHostPort(ln.Addr().String())
	if err != nil {
		log.Fatalf("kubewatch: %v", err)
	}
	s.port = port

	server := http.Server{
		Handler: s.router,
	}

	if err := server.Serve(ln); err != http.ErrServerClosed {
		// Error starting or closing listener:
		log.Fatalf("kubewatch: %v", err)
	}
}

func (s *Syncer) routes() {
	s.router.HandleFunc("/api/snapshot/", s.safe(s.handleSnapshot()))
}

func (s *Syncer) safe(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if r := recover(); r != nil {
				var msg string
				switch e := r.(type) {
				case error:
					msg = e.Error()
				default:
					msg = fmt.Sprintf("%v", r)
				}
				http.Error(w, fmt.Sprintf("Server Error: %s", msg), http.StatusInternalServerError)
			}
		}()
		h(w, r)
	}
}

func (s *Syncer) handleSnapshot() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.snapshotMux.Lock()
		defer s.snapshotMux.Unlock()
		parts := strings.Split(r.URL.Path, "/")
		parts = parts[3:]
		snapshot_id := parts[0]
		snapshot, ok := s.snapshots[snapshot_id]
		if !ok {
			http.NotFound(w, r)
			return
		}
		qname := strings.Join(parts[1:], "/")
		body, ok := snapshot[qname]
		if !ok {
			http.NotFound(w, r)
		}
		w.Write(body)
	}
}

var Version = "(unknown version)"

var KUBEWATCH = &cobra.Command{
	Use:  "kubewatch [options] <resources>",
	Args: cobra.MinimumNArgs(1),
	Run:  kubewatch,
}

func init() {
	KUBEWATCH.Version = Version
	KUBEWATCH.Flags().StringVarP(&PORT, "port", "p", "0", "port for kubewatch api")
	KUBEWATCH.Flags().StringVarP(&SYNC_COMMAND, "sync", "s", "curl", "sync command")
	KUBEWATCH.Flags().StringVarP(&NAMESPACE, "namespace", "n", "", "namespace to watch (defaults to all)")
	KUBEWATCH.Flags().DurationVarP(&MIN_INTERVAL, "min-interval", "m", 250*time.Millisecond, "min sync interval")
	KUBEWATCH.Flags().DurationVarP(&MAX_INTERVAL, "max-interval", "M", time.Second, "max sync interval")
	KUBEWATCH.Flags().DurationVarP(&WARMUP_DELAY, "warmup-delay", "w", 0, "warmup delay")
}

var (
	PORT         string
	SYNC_COMMAND string
	NAMESPACE    string
	MIN_INTERVAL time.Duration
	MAX_INTERVAL time.Duration
	WARMUP_DELAY time.Duration
)

func kubewatch(cmd *cobra.Command, args []string) {
	s := Syncer{
		Watcher:     k8s.NewClient(nil).Watcher(),
		SyncCommand: SYNC_COMMAND,
		Kinds:       args,
		MinInterval: MIN_INTERVAL,
		MaxInterval: MAX_INTERVAL,
		WarmupDelay: WARMUP_DELAY,
		router:      http.NewServeMux(),
		snapshots:   make(map[string]map[string][]byte),
	}

	s.Run()
}

func main() {
	KUBEWATCH.Execute()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	notable "../../notable"
	"log"
	"net/http"
	"net/url"
	"os"
)

type OKResponse struct {
	Text string `json:"text"`
}

type SummaryResponse struct {
	Notes []notable.Note `json:"notes"`
}

func getAndSetHandler(responseWriter http.ResponseWriter, request *http.Request) {
	request.ParseForm()
	token := request.Form.Get("token")

	if validToken(token) {
		var err error

		if request.Method == "POST" {
			recordNote(request.Form)
			responseWriter.Write([]byte("Got it!"))
		} else {
			err = respondWithJSON(responseWriter, SummaryResponse{notable.Notes()})
		}

		if err != nil {
			http.Error(responseWriter, err.Error(), http.StatusInternalServerError)
		}
	} else {
		fmt.Printf("Invalid token received: %s\n", token)
		http.Error(responseWriter, "Invalid token", http.StatusForbidden)
	}
}

func clearHandler(responseWriter http.ResponseWriter, request *http.Request) {
	request.ParseForm()
	token := request.Form.Get("token")

	if validToken(token) && request.Method == "POST" {
		notable.Reset()
	}
}

func emailHandler(responseWriter http.ResponseWriter, request *http.Request) {
	request.ParseForm()
	token := request.Form.Get("token")

	if validToken(token) {
		responseWriter.Header().Set("Content-Type", "text/html")
		responseWriter.Write([]byte(notable.Email()))
	}
}

func recordNote(form url.Values) {
	user_id := form.Get("user_id")
	category := form.Get("trigger_word")
	text := form.Get("text")
	slackToken := os.Getenv("SLACK_API_TOKEN")

	notable.Record(user_id, category, text, slackToken)
}

func slashCommandToken() string {
	return os.Getenv("SLACK_TOKEN")
}

func validToken(token string) bool {
	return token == slashCommandToken()
}

func respondWithJSON(responseWriter http.ResponseWriter, response interface{}) error {
	responseJSON, err := json.Marshal(response)
	if err != nil {
		return err
	}

	responseWriter.Header().Set("Content-Type", "application/json")
	responseWriter.Write(responseJSON)

	return nil
}

func main() {
	port := os.Getenv("PORT")

	if port == "" {
		log.Fatal("$PORT must be set")
	}

	http.HandleFunc("/email", emailHandler)
	http.HandleFunc("/clear", clearHandler)
	http.HandleFunc("/", getAndSetHandler)

	log.Fatal(http.ListenAndServe(":"+port, nil))
}
<commit_msg>use git<commit_after>package main

import (
	"encoding/json"
	"fmt"
	notable "github.com/joshualcoffee/notable"
	"log"
	"net/http"
	"net/url"
	"os"
)

type OKResponse struct {
	Text string `json:"text"`
}

type SummaryResponse struct {
	Notes []notable.Note `json:"notes"`
}

func getAndSetHandler(responseWriter http.ResponseWriter, request *http.Request) {
	request.ParseForm()
	token := request.Form.Get("token")

	if validToken(token) {
		var err error

		if request.Method == "POST" {
			recordNote(request.Form)
			responseWriter.Write([]byte("Got it!"))
		} else {
			err = respondWithJSON(responseWriter, SummaryResponse{notable.Notes()})
		}

		if err != nil {
			http.Error(responseWriter, err.Error(), http.StatusInternalServerError)
		}
	} else {
		fmt.Printf("Invalid token received: %s\n", token)
		http.Error(responseWriter, "Invalid token", http.StatusForbidden)
	}
}

func clearHandler(responseWriter http.ResponseWriter, request *http.Request) {
	request.ParseForm()
	token := request.Form.Get("token")

	if validToken(token) && request.Method == "POST" {
		notable.Reset()
	}
}

func emailHandler(responseWriter http.ResponseWriter, request *http.Request) {
	request.ParseForm()
	token := request.Form.Get("token")

	if validToken(token) {
		responseWriter.Header().Set("Content-Type", "text/html")
		responseWriter.Write([]byte(notable.Email()))
	}
}

func recordNote(form url.Values) {
	user_id := form.Get("user_id")
	category := form.Get("trigger_word")
	text := form.Get("text")
	slackToken := os.Getenv("SLACK_API_TOKEN")

	notable.Record(user_id, category, text, slackToken)
}

func slashCommandToken() string {
	return os.Getenv("SLACK_TOKEN")
}

func validToken(token string) bool {
	return token == slashCommandToken()
}

func respondWithJSON(responseWriter http.ResponseWriter, response interface{}) error {
	responseJSON, err := json.Marshal(response)
	if err != nil {
		return err
	}

	responseWriter.Header().Set("Content-Type", "application/json")
	responseWriter.Write(responseJSON)

	return nil
}

func main() {
	port := os.Getenv("PORT")

	if port == "" {
		log.Fatal("$PORT must be set")
	}

	http.HandleFunc("/email", emailHandler)
	http.HandleFunc("/clear", clearHandler)
	http.HandleFunc("/", getAndSetHandler)

	log.Fatal(http.ListenAndServe(":"+port, nil))
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"time"
)

var (
	printMilliseconds = flag.Bool("ms", false, "print milliseconds instead of mm:ss.000")
	suppressFilenames = flag.Bool("n", false, "suppress printing of file names")
)

var usage = `usage: timestamp [flags] [file...]
If files are named, they are read and the timestamp output in
the named files is merged into one time sequence.

When used on a single file
`

func main() {
	flag.Usage = func() {
		fmt.Fprint(os.Stderr, usage)
		flag.PrintDefaults()
		os.Exit(2)
	}
	flag.Parse()
	if args := flag.Args(); len(args) > 0 {
		if len(args) == 1 {
			*suppressFilenames = true
		}
		mergeFiles(flag.Args())
		return
	}
	t0 := time.Now()
	b := bufio.NewReader(os.Stdin)
	out := bufio.NewWriter(os.Stdout)
	wasPrefix := false
	fmt.Fprintf(out, "start %s\n", time.Now().Format(time.RFC1123Z))
	out.Flush()
	for {
		line, isPrefix, err := b.ReadLine()
		if err != nil {
			break
		}
		if !wasPrefix {
			printStamp(out, time.Now().Sub(t0))
		}
		out.Write(line)
		if !isPrefix {
			out.WriteByte('\n')
			out.Flush()
		}
		wasPrefix = isPrefix
	}
	out.Flush()
}

func mergeFiles(files []string) {
	fs := make([]*bufio.Reader, len(files))
	for i, file := range files {
		f, err := os.Open(file)
		if err != nil {
			log.Fatalf("mergetime: cannot open file: %v", err)
		}
		fs[i] = bufio.NewReader(f)
	}
	out := readLines(fs[0], files[0])
	for i, f := range fs[1:] {
		out = merge(readLines(f, files[i+1]), out)
	}
	startLine := <-out
	if startLine.line != "start\n" {
		panic("no start")
	}
	stdout := bufio.NewWriter(os.Stdout)
	t0 := startLine.t
	fmt.Fprintf(stdout, "start %s\n", t0.Format(time.RFC1123Z))
	stdout.Flush()
	for line := range out {
		printStamp(stdout, line.t.Sub(t0))
		if !*suppressFilenames {
			fmt.Fprintf(stdout, "%s: ", line.name)
		}
		stdout.WriteString(line.line)
		stdout.Flush()
	}
}

func merge(c0, c1 <-chan line) <-chan line {
	out := make(chan line)
	go func() {
		defer close(out)
		var line0, line1 line
		r0, r1 := c0, c1
		ok0, ok1 := true, true
		for {
			if r0 != nil {
				line0, ok0 = <-r0
				r0 = nil
			}
			if r1 != nil {
				line1, ok1 = <-r1
				r1 = nil
			}
			switch {
			case !ok0 && !ok1:
				return
			case !ok0:
				out <- line1
				r1 = c1
			case !ok1:
				out <- line0
				r0 = c0
			default:
				if line0.t.Before(line1.t) {
					out <- line0
					r0 = c0
				} else {
					out <- line1
					r1 = c1
				}
			}
		}
	}()
	return out
}

type line struct {
	t    time.Time
	line string
	name string
}

func readLines(r *bufio.Reader, name string) <-chan line {
	out := make(chan line)
	go func() {
		defer close(out)
		startLine, err := r.ReadString('\n')
		if err != nil || !strings.HasPrefix(startLine, "start ") {
			log.Printf("mergetime: cannot read start line")
			return
		}

		start, err := time.Parse(time.RFC1123Z, startLine[len("start "):len(startLine)-1])
		if err != nil {
			log.Printf("mergetime: cannot parse start line %q: %v", startLine, err)
		}
		out <- line{start, "start\n", name}
		prev := start
		for {
			s, err := r.ReadString('\n')
			if err == io.EOF {
				break
			}
			if err != nil {
				log.Printf("mergetime: read error: %v", err)
				break
			}
			i := strings.Index(s, " ")
			if i == -1 {
				log.Printf("mergetime: line has no timestamp: %q", s)
				out <- line{prev, s, name}
				continue
			}
			var d time.Duration
			if strings.Index(s[0:i], ":") >= 0 {
				var min, sec, millisec int
				if _, err := fmt.Sscanf(s[0:i], "%d:%d.%d", &min, &sec, &millisec); err != nil {
					log.Printf("mergetime: cannot parse timestamp on line %q", s)
					out <- line{prev, s, name}
					continue
				}
				d = time.Duration(min)*time.Minute +
					time.Duration(sec)*time.Second +
					time.Duration(millisec)*time.Millisecond
			} else {
				var millisec int64
				if _, err := fmt.Scanf(s[0:i], "%d", &millisec); err != nil {
					log.Printf("mergetime: cannot parse timestamp on line %q", s)
					out <- line{prev, s, name}
					continue
				}
				d = time.Duration(millisec) * time.Millisecond
			}
			t := start.Add(d)
			out <- line{t, s[i+1:], name}
			prev = t
		}
	}()
	return out
}

func printStamp(w io.Writer, d time.Duration) {
	if *printMilliseconds {
		fmt.Fprintf(w, "%010d ", d/1e6)
		return
	}
	msec := d / time.Millisecond
	sec := d / time.Second
	min := d / time.Minute
	fmt.Fprintf(w, "%d:%02d.%03d ", min, sec%60, msec%1000)
}
<commit_msg>cmd/timestamp: print milliseconds on start line<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"time"
)

var (
	printMilliseconds = flag.Bool("ms", false, "print milliseconds instead of mm:ss.000")
	suppressFilenames = flag.Bool("n", false, "suppress printing of file names")
)

var usage = `usage: timestamp [flags] [file...]
If files are named, they are read and the timestamp output in
the named files is merged into one time sequence.

When used on a single file
`

const headerTimeFormat = "2006-01-02 15:04:05.000 -0700"

func main() {
	flag.Usage = func() {
		fmt.Fprint(os.Stderr, usage)
		flag.PrintDefaults()
		os.Exit(2)
	}
	flag.Parse()
	if args := flag.Args(); len(args) > 0 {
		if len(args) == 1 {
			*suppressFilenames = true
		}
		mergeFiles(flag.Args())
		return
	}
	t0 := time.Now()
	b := bufio.NewReader(os.Stdin)
	out := bufio.NewWriter(os.Stdout)
	wasPrefix := false
	fmt.Fprintf(out, "start %s\n", time.Now().Format(headerTimeFormat))
	out.Flush()
	for {
		line, isPrefix, err := b.ReadLine()
		if err != nil {
			break
		}
		if !wasPrefix {
			printStamp(out, time.Now().Sub(t0))
		}
		out.Write(line)
		if !isPrefix {
			out.WriteByte('\n')
			out.Flush()
		}
		wasPrefix = isPrefix
	}
	out.Flush()
}

func mergeFiles(files []string) {
	fs := make([]*bufio.Reader, len(files))
	for i, file := range files {
		f, err := os.Open(file)
		if err != nil {
			log.Fatalf("timestamp: cannot open file: %v", err)
		}
		fs[i] = bufio.NewReader(f)
	}
	out := readLines(fs[0], files[0])
	for i, f := range fs[1:] {
		out = merge(readLines(f, files[i+1]), out)
	}
	startLine := <-out
	if startLine.line != "start\n" {
		panic("no start")
	}
	stdout := bufio.NewWriter(os.Stdout)
	t0 := startLine.t
	fmt.Fprintf(stdout, "start %s\n", t0.Format(headerTimeFormat))
	stdout.Flush()
	for line := range out {
		printStamp(stdout, line.t.Sub(t0))
		if !*suppressFilenames {
			fmt.Fprintf(stdout, "%s: ", line.name)
		}
		stdout.WriteString(line.line)
		stdout.Flush()
	}
}

func merge(c0, c1 <-chan line) <-chan line {
	out := make(chan line)
	go func() {
		defer close(out)
		var line0, line1 line
		r0, r1 := c0, c1
		ok0, ok1 := true, true
		for {
			if r0 != nil {
				line0, ok0 = <-r0
				r0 = nil
			}
			if r1 != nil {
				line1, ok1 = <-r1
				r1 = nil
			}
			switch {
			case !ok0 && !ok1:
				return
			case !ok0:
				out <- line1
				r1 = c1
			case !ok1:
				out <- line0
				r0 = c0
			default:
				if line0.t.Before(line1.t) {
					out <- line0
					r0 = c0
				} else {
					out <- line1
					r1 = c1
				}
			}
		}
	}()
	return out
}

type line struct {
	t    time.Time
	line string
	name string
}

func readLines(r *bufio.Reader, name string) <-chan line {
	out := make(chan line)
	go func() {
		defer close(out)
		startLine, err := r.ReadString('\n')
		if err != nil || !strings.HasPrefix(startLine, "start ") {
			log.Printf("timestamp: cannot read start line")
			return
		}

		start, err := time.Parse(headerTimeFormat, startLine[len("start "):len(startLine)-1])
		if err != nil {
			log.Printf("timestamp: cannot parse start line %q: %v", startLine, err)
		}
		out <- line{start, "start\n", name}
		prev := start
		for {
			s, err := r.ReadString('\n')
			if err == io.EOF {
				break
			}
			if err != nil {
				log.Printf("timestamp: read error: %v", err)
				break
			}
			i := strings.Index(s, " ")
			if i == -1 {
				log.Printf("timestamp: line has no timestamp: %q", s)
				out <- line{prev, s, name}
				continue
			}
			var d time.Duration
			if strings.Index(s[0:i], ":") >= 0 {
				var min, sec, millisec int
				if _, err := fmt.Sscanf(s[0:i], "%d:%d.%d", &min, &sec, &millisec); err != nil {
					log.Printf("timestamp: cannot parse timestamp on line %q", s)
					out <- line{prev, s, name}
					continue
				}
				d = time.Duration(min)*time.Minute +
					time.Duration(sec)*time.Second +
					time.Duration(millisec)*time.Millisecond
			} else {
				var millisec int64
				if _, err := fmt.Scanf(s[0:i], "%d", &millisec); err != nil {
					log.Printf("timestamp: cannot parse timestamp on line %q", s)
					out <- line{prev, s, name}
					continue
				}
				d = time.Duration(millisec) * time.Millisecond
			}
			t := start.Add(d)
			out <- line{t, s[i+1:], name}
			prev = t
		}
	}()
	return out
}

func printStamp(w io.Writer, d time.Duration) {
	if *printMilliseconds {
		fmt.Fprintf(w, "%010d ", d/1e6)
		return
	}
	msec := d / time.Millisecond
	sec := d / time.Second
	min := d / time.Minute
	fmt.Fprintf(w, "%d:%02d.%03d ", min, sec%60, msec%1000)
}
<|endoftext|>


<commit_before>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tsuru

import (
	"bytes"
	"github.com/globocom/tsuru/cmd"
	"launchpad.net/gocheck"
	"net/http"
)

func (s *S) TestEnvGetInfo(c *gocheck.C) {
	e := EnvGet{}
	i := e.Info()
	desc := `retrieve environment variables for an app.

If you don't provide the app name, tsuru will try to guess it.`
	c.Assert(i.Name, gocheck.Equals, "env-get")
	c.Assert(i.Usage, gocheck.Equals, "env-get [--app appname] [ENVIRONMENT_VARIABLE1] [ENVIRONMENT_VARIABLE2] ...")
	c.Assert(i.Desc, gocheck.Equals, desc)
	c.Assert(i.MinArgs, gocheck.Equals, 0)
}

func (s *S) TestEnvGetRun(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "DATABASE_HOST=somehost\n"
	context := cmd.Context{
		Args:   []string{"DATABASE_HOST"},
		Stdout: &stdout,
		Stderr: &stderr,
	}
	client := cmd.NewClient(&http.Client{Transport: &transport{msg: result, status: http.StatusOK}}, nil, manager)
	command := EnvGet{}
	command.Flags().Parse(true, []string{"-a", "someapp"})
	err := command.Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestEnvGetRunWithMultipleParams(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "DATABASE_HOST=somehost\nDATABASE_USER=someuser"
	params := []string{"DATABASE_HOST", "DATABASE_USER"}
	context := cmd.Context{
		Args:   params,
		Stdout: &stdout,
		Stderr: &stderr,
	}
	client := cmd.NewClient(&http.Client{Transport: &transport{msg: result, status: http.StatusOK}}, nil, manager)
	command := EnvGet{}
	command.Flags().Parse(true, []string{"-a", "someapp"})
	err := command.Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestEnvGetWithoutTheFlag(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "DATABASE_HOST=somehost\nDATABASE_USER=someuser"
	params := []string{"DATABASE_HOST", "DATABASE_USER"}
	context := cmd.Context{
		Args:   params,
		Stdout: &stdout,
		Stderr: &stderr,
	}
	trans := &conditionalTransport{
		transport{
			msg:    result,
			status: http.StatusOK,
		},
		func(req *http.Request) bool {
			return req.URL.Path == "/apps/seek/env" && req.Method == "GET"
		},
	}
	client := cmd.NewClient(&http.Client{Transport: trans}, nil, manager)
	fake := &FakeGuesser{name: "seek"}
	err := (&EnvGet{GuessingCommand{G: fake}}).Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestEnvSetInfo(c *gocheck.C) {
	e := EnvSet{}
	i := e.Info()
	desc := `set environment variables for an app.

If you don't provide the app name, tsuru will try to guess it.`
	c.Assert(i.Name, gocheck.Equals, "env-set")
	c.Assert(i.Usage, gocheck.Equals, "env-set <NAME=value> [NAME=value] ... [--app appname]")
	c.Assert(i.Desc, gocheck.Equals, desc)
	c.Assert(i.MinArgs, gocheck.Equals, 1)
}

func (s *S) TestEnvSetRun(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "variable(s) successfully exported\n"
	context := cmd.Context{
		Args:   []string{"DATABASE_HOST=somehost"},
		Stdout: &stdout,
		Stderr: &stderr,
	}
	client := cmd.NewClient(&http.Client{Transport: &transport{msg: result, status: http.StatusOK}}, nil, manager)
	command := EnvSet{}
	command.Flags().Parse(true, []string{"-a", "someapp"})
	err := command.Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestEnvSetRunWithMultipleParams(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "variable(s) successfully exported\n"
	context := cmd.Context{
		Args:   []string{"DATABASE_HOST=somehost", "DATABASE_USER=user"},
		Stdout: &stdout,
		Stderr: &stderr,
	}
	client := cmd.NewClient(&http.Client{Transport: &transport{msg: result, status: http.StatusOK}}, nil, manager)
	command := EnvSet{}
	command.Flags().Parse(true, []string{"-a", "someapp"})
	err := command.Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestEnvSetWithoutFlag(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "variable(s) successfully exported\n"
	context := cmd.Context{
		Args:   []string{"DATABASE_HOST=somehost", "DATABASE_USER=user"},
		Stdout: &stdout,
		Stderr: &stderr,
	}
	trans := &conditionalTransport{
		transport{
			msg:    result,
			status: http.StatusOK,
		},
		func(req *http.Request) bool {
			return req.URL.Path == "/apps/otherapp/env" && req.Method == "POST"
		},
	}
	client := cmd.NewClient(&http.Client{Transport: trans}, nil, manager)
	fake := &FakeGuesser{name: "otherapp"}
	err := (&EnvSet{GuessingCommand{G: fake}}).Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestEnvUnsetInfo(c *gocheck.C) {
	e := EnvUnset{}
	i := e.Info()
	desc := `unset environment variables for an app.

If you don't provide the app name, tsuru will try to guess it.`
	c.Assert(i.Name, gocheck.Equals, "env-unset")
	c.Assert(i.Usage, gocheck.Equals, "env-unset <ENVIRONMENT_VARIABLE1> [ENVIRONMENT_VARIABLE2] ... [ENVIRONMENT_VARIABLEN] [--app appname]")
	c.Assert(i.Desc, gocheck.Equals, desc)
	c.Assert(i.MinArgs, gocheck.Equals, 1)
}

func (s *S) TestEnvUnsetRun(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "variable(s) successfully unset\n"
	context := cmd.Context{
		Args:   []string{"DATABASE_HOST"},
		Stdout: &stdout,
		Stderr: &stderr,
	}
	client := cmd.NewClient(&http.Client{Transport: &transport{msg: result, status: http.StatusOK}}, nil, manager)
	command := EnvUnset{}
	command.Flags().Parse(true, []string{"-a", "someapp"})
	err := command.Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}
func (s *S) TestEnvUnsetWithoutFlag(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "variable(s) successfully unset\n"
	context := cmd.Context{
		Args:   []string{"DATABASE_HOST"},
		Stdout: &stdout,
		Stderr: &stderr,
	}
	trans := &conditionalTransport{
		transport{
			msg:    result,
			status: http.StatusOK,
		},
		func(req *http.Request) bool {
			return req.URL.Path == "/apps/otherapp/env" && req.Method == "DELETE"
		},
	}
	client := cmd.NewClient(&http.Client{Transport: trans}, nil, manager)
	fake := &FakeGuesser{name: "otherapp"}
	err := (&EnvUnset{GuessingCommand{G: fake}}).Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestRequestEnvUrl(c *gocheck.C) {
	*AppName = "someapp"
	result := "DATABASE_HOST=somehost"
	client := cmd.NewClient(&http.Client{Transport: &transport{msg: result, status: http.StatusOK}}, nil, manager)
	args := []string{"DATABASE_HOST"}
	b, err := requestEnvUrl("GET", GuessingCommand{G: &FakeGuesser{name: "someapp"}}, args, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(b, gocheck.Equals, result)
}
<commit_msg>cmd/tsuru-base: fix test<commit_after>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tsuru

import (
	"bytes"
	"github.com/globocom/tsuru/cmd"
	"launchpad.net/gocheck"
	"net/http"
)

func (s *S) TestEnvGetInfo(c *gocheck.C) {
	e := EnvGet{}
	i := e.Info()
	desc := `retrieve environment variables for an app.

If you don't provide the app name, tsuru will try to guess it.`
	c.Assert(i.Name, gocheck.Equals, "env-get")
	c.Assert(i.Usage, gocheck.Equals, "env-get [--app appname] [ENVIRONMENT_VARIABLE1] [ENVIRONMENT_VARIABLE2] ...")
	c.Assert(i.Desc, gocheck.Equals, desc)
	c.Assert(i.MinArgs, gocheck.Equals, 0)
}

func (s *S) TestEnvGetRun(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "DATABASE_HOST=somehost\n"
	context := cmd.Context{
		Args:   []string{"DATABASE_HOST"},
		Stdout: &stdout,
		Stderr: &stderr,
	}
	client := cmd.NewClient(&http.Client{Transport: &transport{msg: result, status: http.StatusOK}}, nil, manager)
	command := EnvGet{}
	command.Flags().Parse(true, []string{"-a", "someapp"})
	err := command.Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestEnvGetRunWithMultipleParams(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "DATABASE_HOST=somehost\nDATABASE_USER=someuser"
	params := []string{"DATABASE_HOST", "DATABASE_USER"}
	context := cmd.Context{
		Args:   params,
		Stdout: &stdout,
		Stderr: &stderr,
	}
	client := cmd.NewClient(&http.Client{Transport: &transport{msg: result, status: http.StatusOK}}, nil, manager)
	command := EnvGet{}
	command.Flags().Parse(true, []string{"-a", "someapp"})
	err := command.Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestEnvGetWithoutTheFlag(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "DATABASE_HOST=somehost\nDATABASE_USER=someuser"
	params := []string{"DATABASE_HOST", "DATABASE_USER"}
	context := cmd.Context{
		Args:   params,
		Stdout: &stdout,
		Stderr: &stderr,
	}
	trans := &conditionalTransport{
		transport{
			msg:    result,
			status: http.StatusOK,
		},
		func(req *http.Request) bool {
			return req.URL.Path == "/apps/seek/env" && req.Method == "GET"
		},
	}
	client := cmd.NewClient(&http.Client{Transport: trans}, nil, manager)
	fake := &FakeGuesser{name: "seek"}
	err := (&EnvGet{GuessingCommand{G: fake}}).Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestEnvSetInfo(c *gocheck.C) {
	e := EnvSet{}
	i := e.Info()
	desc := `set environment variables for an app.

If you don't provide the app name, tsuru will try to guess it.`
	c.Assert(i.Name, gocheck.Equals, "env-set")
	c.Assert(i.Usage, gocheck.Equals, "env-set <NAME=value> [NAME=value] ... [--app appname]")
	c.Assert(i.Desc, gocheck.Equals, desc)
	c.Assert(i.MinArgs, gocheck.Equals, 1)
}

func (s *S) TestEnvSetRun(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "variable(s) successfully exported\n"
	context := cmd.Context{
		Args:   []string{"DATABASE_HOST=somehost"},
		Stdout: &stdout,
		Stderr: &stderr,
	}
	client := cmd.NewClient(&http.Client{Transport: &transport{msg: result, status: http.StatusOK}}, nil, manager)
	command := EnvSet{}
	command.Flags().Parse(true, []string{"-a", "someapp"})
	err := command.Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestEnvSetRunWithMultipleParams(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "variable(s) successfully exported\n"
	context := cmd.Context{
		Args:   []string{"DATABASE_HOST=somehost", "DATABASE_USER=user"},
		Stdout: &stdout,
		Stderr: &stderr,
	}
	client := cmd.NewClient(&http.Client{Transport: &transport{msg: result, status: http.StatusOK}}, nil, manager)
	command := EnvSet{}
	command.Flags().Parse(true, []string{"-a", "someapp"})
	err := command.Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestEnvSetWithoutFlag(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "variable(s) successfully exported\n"
	context := cmd.Context{
		Args:   []string{"DATABASE_HOST=somehost", "DATABASE_USER=user"},
		Stdout: &stdout,
		Stderr: &stderr,
	}
	trans := &conditionalTransport{
		transport{
			msg:    result,
			status: http.StatusOK,
		},
		func(req *http.Request) bool {
			return req.URL.Path == "/apps/otherapp/env" && req.Method == "POST"
		},
	}
	client := cmd.NewClient(&http.Client{Transport: trans}, nil, manager)
	fake := &FakeGuesser{name: "otherapp"}
	err := (&EnvSet{GuessingCommand{G: fake}}).Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestEnvUnsetInfo(c *gocheck.C) {
	e := EnvUnset{}
	i := e.Info()
	desc := `unset environment variables for an app.

If you don't provide the app name, tsuru will try to guess it.`
	c.Assert(i.Name, gocheck.Equals, "env-unset")
	c.Assert(i.Usage, gocheck.Equals, "env-unset <ENVIRONMENT_VARIABLE1> [ENVIRONMENT_VARIABLE2] ... [ENVIRONMENT_VARIABLEN] [--app appname]")
	c.Assert(i.Desc, gocheck.Equals, desc)
	c.Assert(i.MinArgs, gocheck.Equals, 1)
}

func (s *S) TestEnvUnsetRun(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "variable(s) successfully unset\n"
	context := cmd.Context{
		Args:   []string{"DATABASE_HOST"},
		Stdout: &stdout,
		Stderr: &stderr,
	}
	client := cmd.NewClient(&http.Client{Transport: &transport{msg: result, status: http.StatusOK}}, nil, manager)
	command := EnvUnset{}
	command.Flags().Parse(true, []string{"-a", "someapp"})
	err := command.Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}
func (s *S) TestEnvUnsetWithoutFlag(c *gocheck.C) {
	var stdout, stderr bytes.Buffer
	result := "variable(s) successfully unset\n"
	context := cmd.Context{
		Args:   []string{"DATABASE_HOST"},
		Stdout: &stdout,
		Stderr: &stderr,
	}
	trans := &conditionalTransport{
		transport{
			msg:    result,
			status: http.StatusOK,
		},
		func(req *http.Request) bool {
			return req.URL.Path == "/apps/otherapp/env" && req.Method == "DELETE"
		},
	}
	client := cmd.NewClient(&http.Client{Transport: trans}, nil, manager)
	fake := &FakeGuesser{name: "otherapp"}
	err := (&EnvUnset{GuessingCommand{G: fake}}).Run(&context, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(stdout.String(), gocheck.Equals, result)
}

func (s *S) TestRequestEnvUrl(c *gocheck.C) {
	result := "DATABASE_HOST=somehost"
	client := cmd.NewClient(&http.Client{Transport: &transport{msg: result, status: http.StatusOK}}, nil, manager)
	args := []string{"DATABASE_HOST"}
	g := GuessingCommand{G: &FakeGuesser{name: "someapp"}, appName: "something"}
	b, err := requestEnvUrl("GET", g, args, client)
	c.Assert(err, gocheck.IsNil)
	c.Assert(b, gocheck.Equals, result)
}
<|endoftext|>


<commit_before>package uavtalkconnection

import (
	"fmt"

	log "github.com/Sirupsen/logrus"
	"github.com/openflylab/bridge/common"
	"github.com/openflylab/bridge/dispatcher"
)

/*
 * This is where we manage the whole usb layer.
 * States are used to manage handshake and session management.
 * See https://github.com/TauLabs/TauLabs/wiki/UAVTalk-session-management
 * and https://wiki.openpilot.org/display/WIKI/UAVTalk
**/

type stateHolder struct {
	connection *dispatcher.Connection
	inChan     chan Packet
	outChan    chan Packet

	state state
}

func (sh *stateHolder) setState(s state) {
	sh.state = s
	sh.state.start()
}

type state interface {
	start()
	in(Packet) bool  // packets coming from the dispatcher
	out(Packet) bool // packets coming from the flight controller
}

type notConnected struct {
	stateHolder *stateHolder

	flightTelemetryStats *common.Definition
}

func (s *notConnected) start() {
	var err error
	s.flightTelemetryStats, err = definitions.GetDefinitionForName("FlightTelemetryStats")
	if err != nil {
		log.Fatal(err)
	}

	log.Info("Started notConnected state")

	handshakeReq := createGCSTelemetryStatsObjectPacket("HandshakeReq")
	s.stateHolder.inChan <- handshakeReq
}

func (s *notConnected) in(p Packet) bool {
	return false
}

func (s *notConnected) out(p Packet) bool {
	if p.definition == s.flightTelemetryStats {
		log.Info(p.data["Status"])
		if p.data["Status"] == "Disconnected" {
			handshakeReq := createGCSTelemetryStatsObjectPacket("HandshakeReq")
			s.stateHolder.inChan <- handshakeReq
		} else if p.data["Status"] == "HandshakeAck" {
			handshakeConnected := createGCSTelemetryStatsObjectPacket("Connected")
			s.stateHolder.inChan <- handshakeConnected
		} else if p.data["Status"] == "Connected" {
			s.stateHolder.setState(&noSession{stateHolder: s.stateHolder})
		}
	}
	return false
}

const noSessionStateFirstSend = 1
const noSessionStateCreateSession = 2
const noSessionStateFetchObjects = 3

type noSession struct {
	stateHolder *stateHolder

	currentSessionStateCreationStep int
	currentObjectID                 uint8
	numberOfObjects                 uint8

	sessionManaging *common.Definition
}

func (s *noSession) start() {
	log.Info("Started noSession state")

	s.currentSessionStateCreationStep = noSessionStateFirstSend

	var err error
	s.sessionManaging, err = definitions.GetDefinitionForName("SessionManaging")
	if err != nil {
		log.Fatal(err)
	}

	sessionManagingReq := createSessionManagingRequest()
	s.stateHolder.inChan <- sessionManagingReq
}

func (s *noSession) in(p Packet) bool {
	return false
}

func (s *noSession) out(p Packet) bool {
	if p.definition == s.sessionManaging {
		if p.cmd == objectCmd || p.cmd == objectCmdWithAck {
			log.Infof("%d %d %d %d %d", *(p.data["SessionID"].(*uint16)), *(p.data["ObjectID"].(*uint32)), *(p.data["ObjectInstances"].(*uint8)), *(p.data["NumberOfObjects"].(*uint8)), *(p.data["ObjectOfInterestIndex"].(*uint8)))
			numberOfObjects := *(p.data["NumberOfObjects"].(*uint8))
			if numberOfObjects != 0 {
				s.numberOfObjects = numberOfObjects - 1
			}
			if p.cmd == objectCmdWithAck {
				log.Info("objectCmdWithAck")
				sessionManagingPacketAck := createSessionManagingPacketAck()
				s.stateHolder.inChan <- sessionManagingPacketAck

				sessionManagingPacket := createSessionManagingPacket(4224, s.currentObjectID)
				s.currentObjectID++
				s.currentSessionStateCreationStep = noSessionStateFetchObjects
				s.stateHolder.inChan <- sessionManagingPacket

				if s.currentObjectID > s.numberOfObjects {
					s.stateHolder.setState(&stream{})
				}
			} else {
				sessionManagingPacket := createSessionManagingPacket(0, 0)
				s.stateHolder.inChan <- sessionManagingPacket
			}
		} else if p.cmd == objectAck {
			log.Info("Received Ack for SessionManaging")
		} else if p.cmd == objectNack {
			log.Info("Received Nack for SessionManaging")
			// TODO Failsafe...
		}
	}
	return false
}

type stream struct {
}

func (s *stream) start() {

}

func (s *stream) in(p Packet) bool {
	log.Info(p)
	return true
}

func (s *stream) out(p Packet) bool {
	log.Info(p)
	return true
}

func newStateHolder(d *dispatcher.Dispatcher) *stateHolder {
	sh := &stateHolder{}
	sh.connection = dispatcher.NewConnection()
	sh.inChan = make(chan Packet, dispatcher.ChanQueueLength)
	sh.outChan = make(chan Packet, dispatcher.ChanQueueLength)

	d.AddConnection(sh.connection)

	sh.setState(&notConnected{stateHolder: sh})

	go func() {
		for {
			packet := <-sh.outChan
			if sh.state.out(packet) == true {
				dispatcherPacket, err := newDispatherPacketFromPacket(packet)
				if err != nil {
					log.Warning(err)
				}
				sh.connection.OutChan <- dispatcherPacket
			}
		}
	}()

	go func() {
		for {
			dispatcherPacket := <-sh.connection.InChan
			packet, err := newPacketFromDispatcher(dispatcherPacket)
			if err != nil {
				log.Warning(err)
				continue
			}
			if sh.state.in(*packet) {
				sh.inChan <- *packet
			}
		}
	}()

	return sh
}

func createGCSTelemetryStatsObjectPacket(status string) Packet {
	definition, err := definitions.GetDefinitionForName("GCSTelemetryStats")
	if err != nil {
		log.Fatal(err)
	}
	packet := newPacket(definition, objectCmd, 0, map[string]interface{}{
		"Status":     status,
		"TxDataRate": float64(0),
		"RxDataRate": float64(0),
		"TxFailures": float64(0),
		"RxFailures": float64(0),
		"TxRetries":  float64(0),
	})
	return *packet
}

func createSessionManagingRequest() Packet {
	definition, err := definitions.GetDefinitionForName("SessionManaging")
	if err != nil {
		log.Fatal(err)
	}
	packet := newPacket(definition, objectRequest, 0, map[string]interface{}{})
	return *packet
}

func createSessionManagingPacket(sessionID uint32, objectOfInterestIndex uint8) Packet {
	definition, err := definitions.GetDefinitionForName("SessionManaging")
	if err != nil {
		log.Fatal(err)
	}
	packet := newPacket(definition, objectCmd, 0, map[string]interface{}{
		"SessionID":             float64(sessionID),
		"ObjectID":              float64(0),
		"ObjectInstances":       float64(0),
		"NumberOfObjects":       float64(0),
		"ObjectOfInterestIndex": float64(objectOfInterestIndex),
	})
	return *packet
}

func createSessionManagingPacketAck() Packet {
	definition, err := definitions.GetDefinitionForName("SessionManaging")
	if err != nil {
		log.Fatal(err)
	}
	packet := newPacket(definition, objectAck, 0, map[string]interface{}{})
	return *packet
}

func newPacketFromDispatcher(dispatcherPacket interface{}) (*Packet, error) {
	switch dp := dispatcherPacket.(type) {
	case dispatcher.Update:
		definition, err := definitions.GetDefinitionForObjectID(dp.ObjectID)
		if err != nil {
			return nil, err
		}
		var cmd uint8
		if definition.TelemetryFlight.Acked == true {
			cmd = objectCmdWithAck
		}
		return newPacket(definition, cmd, dp.InstanceID, dp.Data), nil
	case dispatcher.Request:
		definition, err := definitions.GetDefinitionForObjectID(dp.ObjectID)
		if err != nil {
			return nil, err
		}
		return newPacket(definition, objectRequest, dp.InstanceID, map[string]interface{}{}), nil
	default:
		return nil, fmt.Errorf("Only Update or Request packets con go through USB connection")
	}
}

func newDispatherPacketFromPacket(packet Packet) (interface{}, error) {
	if packet.cmd == objectCmd || packet.cmd == objectCmdWithAck {
		return dispatcher.Update{ObjectID: packet.definition.ObjectID, InstanceID: packet.instanceID}, nil
	}
	return nil, fmt.Errorf("Only packets with cmd == 0 or cmd == 2 can go out of the flight controller")
}
<commit_msg>fix flight controller when reaching last sessionmanaging uavo<commit_after>package uavtalkconnection

import (
	"fmt"

	log "github.com/Sirupsen/logrus"
	"github.com/openflylab/bridge/common"
	"github.com/openflylab/bridge/dispatcher"
)

/*
 * This is where we manage the whole usb layer.
 * States are used to manage handshake and session management.
 * See https://github.com/TauLabs/TauLabs/wiki/UAVTalk-session-management
 * and https://wiki.openpilot.org/display/WIKI/UAVTalk
**/

type stateHolder struct {
	connection *dispatcher.Connection
	inChan     chan Packet
	outChan    chan Packet

	state state
}

func (sh *stateHolder) setState(s state) {
	sh.state = s
	sh.state.start()
}

type state interface {
	start()
	in(Packet) bool  // packets coming from the dispatcher
	out(Packet) bool // packets coming from the flight controller
}

type notConnected struct {
	stateHolder *stateHolder

	flightTelemetryStats *common.Definition
}

func (s *notConnected) start() {
	var err error
	s.flightTelemetryStats, err = definitions.GetDefinitionForName("FlightTelemetryStats")
	if err != nil {
		log.Fatal(err)
	}

	log.Info("Started notConnected state")

	handshakeReq := createGCSTelemetryStatsObjectPacket("HandshakeReq")
	s.stateHolder.inChan <- handshakeReq
}

func (s *notConnected) in(p Packet) bool {
	return false
}

func (s *notConnected) out(p Packet) bool {
	if p.definition == s.flightTelemetryStats {
		log.Info(p.data["Status"])
		if p.data["Status"] == "Disconnected" {
			handshakeReq := createGCSTelemetryStatsObjectPacket("HandshakeReq")
			s.stateHolder.inChan <- handshakeReq
		} else if p.data["Status"] == "HandshakeAck" {
			handshakeConnected := createGCSTelemetryStatsObjectPacket("Connected")
			s.stateHolder.inChan <- handshakeConnected
		} else if p.data["Status"] == "Connected" {
			s.stateHolder.setState(&noSession{stateHolder: s.stateHolder})
		}
	}
	return false
}

const noSessionStateFirstSend = 1
const noSessionStateCreateSession = 2
const noSessionStateFetchObjects = 3

type noSession struct {
	stateHolder *stateHolder

	currentSessionStateCreationStep int
	currentObjectID                 uint8
	numberOfObjects                 uint8

	sessionManaging *common.Definition
}

func (s *noSession) start() {
	log.Info("Started noSession state")

	s.currentSessionStateCreationStep = noSessionStateFirstSend

	var err error
	s.sessionManaging, err = definitions.GetDefinitionForName("SessionManaging")
	if err != nil {
		log.Fatal(err)
	}

	sessionManagingReq := createSessionManagingRequest()
	s.stateHolder.inChan <- sessionManagingReq
}

func (s *noSession) in(p Packet) bool {
	return false
}

func (s *noSession) out(p Packet) bool {
	if p.definition == s.sessionManaging {
		if p.cmd == objectCmd || p.cmd == objectCmdWithAck {
			log.Infof("%d %d %d %d %d", *(p.data["SessionID"].(*uint16)), *(p.data["ObjectID"].(*uint32)), *(p.data["ObjectInstances"].(*uint8)), *(p.data["NumberOfObjects"].(*uint8)), *(p.data["ObjectOfInterestIndex"].(*uint8)))
			numberOfObjects := *(p.data["NumberOfObjects"].(*uint8))
			if numberOfObjects != 0 {
				s.numberOfObjects = numberOfObjects
			}
			if p.cmd == objectCmdWithAck {
				log.Info("objectCmdWithAck")
				sessionManagingPacketAck := createSessionManagingPacketAck()
				s.stateHolder.inChan <- sessionManagingPacketAck

				if s.currentObjectID >= s.numberOfObjects {
					s.stateHolder.setState(&stream{})
					return false
				}

				sessionManagingPacket := createSessionManagingPacket(4224, s.currentObjectID)
				s.currentObjectID++
				s.currentSessionStateCreationStep = noSessionStateFetchObjects
				s.stateHolder.inChan <- sessionManagingPacket
			} else {
				sessionManagingPacket := createSessionManagingPacket(0, 0)
				s.stateHolder.inChan <- sessionManagingPacket
			}
		} else if p.cmd == objectAck {
			log.Info("Received Ack for SessionManaging")
		} else if p.cmd == objectNack {
			log.Info("Received Nack for SessionManaging")
			// TODO Failsafe...
		}
	}
	return false
}

type stream struct {
}

func (s *stream) start() {

}

func (s *stream) in(p Packet) bool {
	log.Info(p)
	return true
}

func (s *stream) out(p Packet) bool {
	log.Info(p)
	return true
}

func newStateHolder(d *dispatcher.Dispatcher) *stateHolder {
	sh := &stateHolder{}
	sh.connection = dispatcher.NewConnection()
	sh.inChan = make(chan Packet, dispatcher.ChanQueueLength)
	sh.outChan = make(chan Packet, dispatcher.ChanQueueLength)

	d.AddConnection(sh.connection)

	sh.setState(&notConnected{stateHolder: sh})

	go func() {
		for {
			packet := <-sh.outChan
			if sh.state.out(packet) == true {
				dispatcherPacket, err := newDispatherPacketFromPacket(packet)
				if err != nil {
					log.Warning(err)
				}
				sh.connection.OutChan <- dispatcherPacket
			}
		}
	}()

	go func() {
		for {
			dispatcherPacket := <-sh.connection.InChan
			packet, err := newPacketFromDispatcher(dispatcherPacket)
			if err != nil {
				log.Warning(err)
				continue
			}
			if sh.state.in(*packet) {
				sh.inChan <- *packet
			}
		}
	}()

	return sh
}

func createGCSTelemetryStatsObjectPacket(status string) Packet {
	definition, err := definitions.GetDefinitionForName("GCSTelemetryStats")
	if err != nil {
		log.Fatal(err)
	}
	packet := newPacket(definition, objectCmd, 0, map[string]interface{}{
		"Status":     status,
		"TxDataRate": float64(0),
		"RxDataRate": float64(0),
		"TxFailures": float64(0),
		"RxFailures": float64(0),
		"TxRetries":  float64(0),
	})
	return *packet
}

func createSessionManagingRequest() Packet {
	definition, err := definitions.GetDefinitionForName("SessionManaging")
	if err != nil {
		log.Fatal(err)
	}
	packet := newPacket(definition, objectRequest, 0, map[string]interface{}{})
	return *packet
}

func createSessionManagingPacket(sessionID uint32, objectOfInterestIndex uint8) Packet {
	definition, err := definitions.GetDefinitionForName("SessionManaging")
	if err != nil {
		log.Fatal(err)
	}
	packet := newPacket(definition, objectCmd, 0, map[string]interface{}{
		"SessionID":             float64(sessionID),
		"ObjectID":              float64(0),
		"ObjectInstances":       float64(0),
		"NumberOfObjects":       float64(0),
		"ObjectOfInterestIndex": float64(objectOfInterestIndex),
	})
	return *packet
}

func createSessionManagingPacketAck() Packet {
	definition, err := definitions.GetDefinitionForName("SessionManaging")
	if err != nil {
		log.Fatal(err)
	}
	packet := newPacket(definition, objectAck, 0, map[string]interface{}{})
	return *packet
}

func newPacketFromDispatcher(dispatcherPacket interface{}) (*Packet, error) {
	switch dp := dispatcherPacket.(type) {
	case dispatcher.Update:
		definition, err := definitions.GetDefinitionForObjectID(dp.ObjectID)
		if err != nil {
			return nil, err
		}
		var cmd uint8
		if definition.TelemetryFlight.Acked == true {
			cmd = objectCmdWithAck
		}
		return newPacket(definition, cmd, dp.InstanceID, dp.Data), nil
	case dispatcher.Request:
		definition, err := definitions.GetDefinitionForObjectID(dp.ObjectID)
		if err != nil {
			return nil, err
		}
		return newPacket(definition, objectRequest, dp.InstanceID, map[string]interface{}{}), nil
	default:
		return nil, fmt.Errorf("Only Update or Request packets con go through USB connection")
	}
}

func newDispatherPacketFromPacket(packet Packet) (interface{}, error) {
	if packet.cmd == objectCmd || packet.cmd == objectCmdWithAck {
		return dispatcher.Update{ObjectID: packet.definition.ObjectID, InstanceID: packet.instanceID}, nil
	}
	return nil, fmt.Errorf("Only packets with cmd == 0 or cmd == 2 can go out of the flight controller")
}
<|endoftext|>


<commit_before>// Copyright 2018 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

//go:generate go run internal/content_gen.go -o content_prod.go

package main

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha1"
	"crypto/subtle"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"strings"
	"time"

	"periph.io/x/periph"
)

const cacheControlNone = "Cache-Control:no-cache,private"

func initState(s *drvState, st *periph.State) {
	s.Loaded = make([]string, len(st.Loaded))
	for i, v := range st.Loaded {
		s.Loaded[i] = v.String()
	}
	s.Skipped = make([]driverFailure, len(st.Skipped))
	for i, v := range st.Skipped {
		s.Skipped[i].D = v.D.String()
		s.Skipped[i].Err = v.Err.Error()
	}
	s.Failed = make([]driverFailure, len(st.Failed))
	for i, v := range st.Failed {
		s.Failed[i].D = v.D.String()
		s.Failed[i].Err = v.Err.Error()
	}
}

type apiHandler struct {
	path string
	fn   interface{}
}

type webServer struct {
	ln     net.Listener
	server http.Server
	apis   jsonAPI
	key    [8]byte
}

func getHostAndPort(hostport string) (string, int, error) {
	host, portStr, err := net.SplitHostPort(hostport)
	if err != nil {
		return "", 0, fmt.Errorf("could not split http address: %v", err)
	}
	if host == "" {
		host = "localhost"
	}
	var port int
	if portStr != "" {
		if port, err = strconv.Atoi(portStr); err != nil {
			return "", 0, fmt.Errorf("invalid port number: %v", err)
		}
	}
	return host, port, nil
}

func isLocalhost(host string) bool {
	switch host {
	case "localhost", "127.0.0.1", "[::1]", "::1":
		return true
	}
	return false
}

func newWebServer(hostport string, state *periph.State, verbose bool) (*webServer, error) {
	s := &webServer{server: http.Server{Handler: http.DefaultServeMux}}
	if _, err := rand.Read(s.key[:]); err != nil {
		return nil, err
	}
	initState(&s.apis.state, state)
	var err error
	host, port, err := getHostAndPort(hostport)
	if err != nil {
		return nil, err
	}

	for _, h := range s.apis.getAPIs() {
		http.HandleFunc(h.path, s.api(h.fn))
	}
	http.HandleFunc("/raw/periph/v1/xsrf_token", noContent(s.apiXSRFTokenHandler))
	http.HandleFunc("/favicon.ico", getOnly(s.getFavicon))
	// Do not use getOnly here as it is the 'catch all, one and we want to check
	// that before the method.
	http.HandleFunc("/", noContent(s.getRoot))
	// We love middlewares!
	if isLocalhost(host) {
		s.apis.hostname = "localhost"
		s.server.Handler = localOnly(s.server.Handler)
	} else {
		if s.apis.hostname, err = os.Hostname(); err != nil {
			return nil, err
		}
	}
	if verbose {
		s.server.Handler = loggingHandler(s.server.Handler)
	}

	if s.ln, err = net.Listen("tcp", fmt.Sprintf("%s:%d", host, port)); err != nil {
		return nil, err
	}
	s.server.Addr = s.ln.Addr().String()
	go s.server.Serve(s.ln)
	return s, nil
}

func (s *webServer) Close() error {
	return s.ln.Close()
}

// Inspired by https://github.com/golang/net/blob/master/xsrftoken/xsrf.go

func (s *webServer) generateToken(userID string, now time.Time) string {
	milliTime := (now.UnixNano() + 1e6 - 1) / 1e6
	h := hmac.New(sha1.New, s.key[:])
	fmt.Fprintf(h, "%d:%s", milliTime, userID)
	return fmt.Sprintf("%x:%s", milliTime, strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "="))
}

func (s *webServer) validateToken(token string, userID string) bool {
	now := time.Now()
	sep := strings.Index(token, ":")
	if sep < 0 {
		return false
	}
	millis, err := strconv.ParseInt(token[:sep], 16, 64)
	if err != nil {
		return false
	}
	issueTime := time.Unix(0, millis*1e6)
	if now.Sub(issueTime) >= 24*time.Hour || issueTime.After(now.Add(1*time.Minute)) {
		return false
	}
	return subtle.ConstantTimeCompare([]byte(token), []byte(s.generateToken(userID, issueTime))) == 1
}

func (s *webServer) setXSRFCookie(addr string, w http.ResponseWriter) string {
	t := s.generateToken(strings.SplitN(addr, ":", 2)[0], time.Now())
	c := http.Cookie{
		Name:   "XSRF-TOKEN",
		Value:  t,
		MaxAge: 23 * 60 * 60,
	}
	http.SetCookie(w, &c)
	return t
}

// Handlers.

// /
func (s *webServer) getRoot(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.Error(w, "Not Found", http.StatusNotFound)
		return
	}
	if r.Method != "GET" && r.Method != "HEAD" {
		http.Error(w, "Only GET is allowed", http.StatusMethodNotAllowed)
		return
	}
	s.setXSRFCookie(r.RemoteAddr, w)
	content := getContent("ui/index.html")
	if content == nil {
		http.Error(w, "Content missing", 500)
		return
	}
	w.Header().Set("Content-Type", "text/html")
	w.Header().Set("Cache-Control", cacheControlContent)
	w.Write(content)
}

// /favicon.ico
func (s *webServer) getFavicon(w http.ResponseWriter, r *http.Request) {
	content := getContent("ui/favicon.ico")
	if content == nil {
		http.Error(w, "Content missing", 500)
		return
	}
	w.Header().Set("Content-Type", "image/png")
	w.Header().Set("Cache-Control", cacheControlContent)
	w.Write(content)
}

// /raw/periph/v1/xsrf_token
func (s *webServer) apiXSRFTokenHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Only POST is allowed", http.StatusMethodNotAllowed)
		return
	}
	t := s.setXSRFCookie(r.RemoteAddr, w)
	w.Header().Set("Content-Type", "text/plain")
	w.Header().Set("Cache-Control", cacheControlNone)
	w.WriteHeader(200)
	w.Write([]byte(t))
}

// http.Handler/HandlerFunc decorators.

// localOnly disallow remote access.
//
// It must be the front line decorator.
func localOnly(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if host, _, err := net.SplitHostPort(r.RemoteAddr); err != nil || !isLocalhost(host) {
			http.Error(w, "permission denied", http.StatusForbidden)
			r.Body.Close()
			return
		}
		h.ServeHTTP(w, r)
	})
}

// enforceXSRF is an handler wrapper that enforces the XSRF token.
//
// In practice it's only used for the APIs within the api() decorator.
func (s *webServer) enforceXSRF(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		c, _ := r.Cookie("XSRF-TOKEN")
		if c == nil {
			log.Printf("Missing XSRF-TOKEN cookie")
			http.Error(w, "Missing XSRF-TOKEN cookie", 400)
			r.Body.Close()
			return
		}
		if !s.validateToken(c.Value, strings.SplitN(r.RemoteAddr, ":", 2)[0]) {
			log.Printf("Invalid XSRF-TOKEN cookie %q", c.Value)
			http.Error(w, "Invalid XSRF-TOKEN cookie", 400)
			r.Body.Close()
			return
		}
		h(w, r)
	}
}

// getOnly returns an http.Handler that refuses other verbs than GET or HEAD.
//
// Also uses noContent().
func getOnly(h http.HandlerFunc) http.HandlerFunc {
	return noContent(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" && r.Method != "HEAD" {
			http.Error(w, "Only GET is allowed", http.StatusMethodNotAllowed)
			return
		}
		h(w, r)
	})
}

// noContent ensure no content is posted. It closes r.Body.
func noContent(h http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		n, err := io.Copy(ioutil.Discard, r.Body)
		r.Body.Close()
		if n != 0 {
			http.Error(w, "Unexpected content", 400)
			return
		}
		if err != nil {
			http.Error(w, err.Error(), 400)
			return
		}
		h(w, r)
	})
}

// api wraps a JSON api handler.
func (s *webServer) api(h interface{}) http.HandlerFunc {
	v := reflect.ValueOf(h)
	t := v.Type()
	if t.Kind() != reflect.Func {
		panic("send API func")
	}
	var inT reflect.Type
	if nArg := t.NumIn(); nArg == 1 {
		inT = t.In(0)
	} else if nArg != 0 {
		panic("pass func that accepts zero or one arg")
	}
	if t.NumOut() != 2 {
		panic("pass func that returns two args")
	}
	return s.enforceXSRF(func(w http.ResponseWriter, r *http.Request) {
		defer r.Body.Close()
		if r.Method != "POST" {
			http.Error(w, "Only POST is allowed", http.StatusMethodNotAllowed)
			return
		}
		// Ignore suffix "; charset=utf-8" for now.
		if !strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") {
			http.Error(w, "Content-Type must be application/json", 400)
			return
		}
		d := json.NewDecoder(r.Body)
		// d.DisallowUnknownFields() is only available in Go 1.10+.
		callDisallowUnknownFields(d)
		var in []reflect.Value
		if inT != nil {
			inv := reflect.New(inT)
			if err := d.Decode(inv.Interface()); err != nil {
				http.Error(w, fmt.Sprintf("Malformed user data: %v", err), 400)
			}
			in = append(in, inv.Elem())
		} else {
			var m map[string]string
			if err := d.Decode(&m); err != nil {
				http.Error(w, fmt.Sprintf("Malformed user data: %v", err), 400)
			}
			if len(m) != 0 {
				http.Error(w, "Unexpected data", 400)
			}
		}
		out := v.Call(in)
		raw, err := json.Marshal(out[0].Interface())
		if err != nil {
			http.Error(w, fmt.Sprintf("Malformed response: %v", err), 500)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		w.Header().Set("Cache-Control", cacheControlNone)
		w.WriteHeader(int(out[1].Int()))
		w.Write(raw)
	})
}
<commit_msg>periph-web: disallow query argument<commit_after>// Copyright 2018 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

//go:generate go run internal/content_gen.go -o content_prod.go

package main

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha1"
	"crypto/subtle"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"strings"
	"time"

	"periph.io/x/periph"
)

const cacheControlNone = "Cache-Control:no-cache,private"

func initState(s *drvState, st *periph.State) {
	s.Loaded = make([]string, len(st.Loaded))
	for i, v := range st.Loaded {
		s.Loaded[i] = v.String()
	}
	s.Skipped = make([]driverFailure, len(st.Skipped))
	for i, v := range st.Skipped {
		s.Skipped[i].D = v.D.String()
		s.Skipped[i].Err = v.Err.Error()
	}
	s.Failed = make([]driverFailure, len(st.Failed))
	for i, v := range st.Failed {
		s.Failed[i].D = v.D.String()
		s.Failed[i].Err = v.Err.Error()
	}
}

type apiHandler struct {
	path string
	fn   interface{}
}

type webServer struct {
	ln     net.Listener
	server http.Server
	apis   jsonAPI
	key    [8]byte
}

func getHostAndPort(hostport string) (string, int, error) {
	host, portStr, err := net.SplitHostPort(hostport)
	if err != nil {
		return "", 0, fmt.Errorf("could not split http address: %v", err)
	}
	if host == "" {
		host = "localhost"
	}
	var port int
	if portStr != "" {
		if port, err = strconv.Atoi(portStr); err != nil {
			return "", 0, fmt.Errorf("invalid port number: %v", err)
		}
	}
	return host, port, nil
}

func isLocalhost(host string) bool {
	switch host {
	case "localhost", "127.0.0.1", "[::1]", "::1":
		return true
	}
	return false
}

func newWebServer(hostport string, state *periph.State, verbose bool) (*webServer, error) {
	s := &webServer{server: http.Server{Handler: http.DefaultServeMux}}
	if _, err := rand.Read(s.key[:]); err != nil {
		return nil, err
	}
	initState(&s.apis.state, state)
	var err error
	host, port, err := getHostAndPort(hostport)
	if err != nil {
		return nil, err
	}

	for _, h := range s.apis.getAPIs() {
		http.HandleFunc(h.path, s.api(h.fn))
	}
	http.HandleFunc("/raw/periph/v1/xsrf_token", noContent(s.apiXSRFTokenHandler))
	http.HandleFunc("/favicon.ico", getOnly(s.getFavicon))
	// Do not use getOnly here as it is the 'catch all, one and we want to check
	// that before the method.
	http.HandleFunc("/", noContent(s.getRoot))
	// We love middlewares!
	if isLocalhost(host) {
		s.apis.hostname = "localhost"
		s.server.Handler = localOnly(s.server.Handler)
	} else {
		if s.apis.hostname, err = os.Hostname(); err != nil {
			return nil, err
		}
	}
	if verbose {
		s.server.Handler = loggingHandler(s.server.Handler)
	}

	if s.ln, err = net.Listen("tcp", fmt.Sprintf("%s:%d", host, port)); err != nil {
		return nil, err
	}
	s.server.Addr = s.ln.Addr().String()
	go s.server.Serve(s.ln)
	return s, nil
}

func (s *webServer) Close() error {
	return s.ln.Close()
}

// Inspired by https://github.com/golang/net/blob/master/xsrftoken/xsrf.go

func (s *webServer) generateToken(userID string, now time.Time) string {
	milliTime := (now.UnixNano() + 1e6 - 1) / 1e6
	h := hmac.New(sha1.New, s.key[:])
	fmt.Fprintf(h, "%d:%s", milliTime, userID)
	return fmt.Sprintf("%x:%s", milliTime, strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "="))
}

func (s *webServer) validateToken(token string, userID string) bool {
	now := time.Now()
	sep := strings.Index(token, ":")
	if sep < 0 {
		return false
	}
	millis, err := strconv.ParseInt(token[:sep], 16, 64)
	if err != nil {
		return false
	}
	issueTime := time.Unix(0, millis*1e6)
	if now.Sub(issueTime) >= 24*time.Hour || issueTime.After(now.Add(1*time.Minute)) {
		return false
	}
	return subtle.ConstantTimeCompare([]byte(token), []byte(s.generateToken(userID, issueTime))) == 1
}

func (s *webServer) setXSRFCookie(addr string, w http.ResponseWriter) string {
	t := s.generateToken(strings.SplitN(addr, ":", 2)[0], time.Now())
	c := http.Cookie{
		Name:   "XSRF-TOKEN",
		Value:  t,
		MaxAge: 23 * 60 * 60,
	}
	http.SetCookie(w, &c)
	return t
}

// Handlers.

// /
func (s *webServer) getRoot(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.Error(w, "Not Found", http.StatusNotFound)
		return
	}
	if r.Method != "GET" && r.Method != "HEAD" {
		http.Error(w, "Only GET is allowed", http.StatusMethodNotAllowed)
		return
	}
	s.setXSRFCookie(r.RemoteAddr, w)
	content := getContent("ui/index.html")
	if content == nil {
		http.Error(w, "Content missing", 500)
		return
	}
	w.Header().Set("Content-Type", "text/html")
	w.Header().Set("Cache-Control", cacheControlContent)
	w.Write(content)
}

// /favicon.ico
func (s *webServer) getFavicon(w http.ResponseWriter, r *http.Request) {
	content := getContent("ui/favicon.ico")
	if content == nil {
		http.Error(w, "Content missing", 500)
		return
	}
	w.Header().Set("Content-Type", "image/png")
	w.Header().Set("Cache-Control", cacheControlContent)
	w.Write(content)
}

// /raw/periph/v1/xsrf_token
func (s *webServer) apiXSRFTokenHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Only POST is allowed", http.StatusMethodNotAllowed)
		return
	}
	t := s.setXSRFCookie(r.RemoteAddr, w)
	w.Header().Set("Content-Type", "text/plain")
	w.Header().Set("Cache-Control", cacheControlNone)
	w.WriteHeader(200)
	w.Write([]byte(t))
}

// http.Handler/HandlerFunc decorators.

// localOnly disallow remote access.
//
// It must be the front line decorator.
func localOnly(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if host, _, err := net.SplitHostPort(r.RemoteAddr); err != nil || !isLocalhost(host) {
			http.Error(w, "permission denied", http.StatusForbidden)
			r.Body.Close()
			return
		}
		h.ServeHTTP(w, r)
	})
}

// enforceXSRF is an handler wrapper that enforces the XSRF token.
//
// In practice it's only used for the APIs within the api() decorator.
func (s *webServer) enforceXSRF(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		c, _ := r.Cookie("XSRF-TOKEN")
		if c == nil {
			log.Printf("Missing XSRF-TOKEN cookie")
			http.Error(w, "Missing XSRF-TOKEN cookie", 400)
			r.Body.Close()
			return
		}
		if !s.validateToken(c.Value, strings.SplitN(r.RemoteAddr, ":", 2)[0]) {
			log.Printf("Invalid XSRF-TOKEN cookie %q", c.Value)
			http.Error(w, "Invalid XSRF-TOKEN cookie", 400)
			r.Body.Close()
			return
		}
		h(w, r)
	}
}

// getOnly returns an http.Handler that refuses other verbs than GET or HEAD.
//
// Also uses noContent().
func getOnly(h http.HandlerFunc) http.HandlerFunc {
	return noContent(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" && r.Method != "HEAD" {
			http.Error(w, "Only GET is allowed", http.StatusMethodNotAllowed)
			return
		}
		h(w, r)
	})
}

// noContent ensure no content is posted. It closes r.Body.
func noContent(h http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		n, err := io.Copy(ioutil.Discard, r.Body)
		r.Body.Close()
		if n != 0 {
			http.Error(w, "Unexpected content", 400)
			return
		}
		if err != nil {
			http.Error(w, err.Error(), 400)
			return
		}
		h(w, r)
	})
}

// api wraps a JSON api handler.
func (s *webServer) api(h interface{}) http.HandlerFunc {
	v := reflect.ValueOf(h)
	t := v.Type()
	if t.Kind() != reflect.Func {
		panic("send API func")
	}
	var inT reflect.Type
	if nArg := t.NumIn(); nArg == 1 {
		inT = t.In(0)
	} else if nArg != 0 {
		panic("pass func that accepts zero or one arg")
	}
	if t.NumOut() != 2 {
		panic("pass func that returns two args")
	}
	return s.enforceXSRF(func(w http.ResponseWriter, r *http.Request) {
		defer r.Body.Close()
		if r.Method != "POST" {
			http.Error(w, "Only POST is allowed", http.StatusMethodNotAllowed)
			return
		}
		if r.URL.RawQuery != "" {
			http.Error(w, "Do not use query argment", 400)
			return
		}
		// Ignore suffix "; charset=utf-8" for now.
		if !strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") {
			http.Error(w, "Content-Type must be application/json", 400)
			return
		}
		d := json.NewDecoder(r.Body)
		// d.DisallowUnknownFields() is only available in Go 1.10+.
		callDisallowUnknownFields(d)
		var in []reflect.Value
		if inT != nil {
			inv := reflect.New(inT)
			if err := d.Decode(inv.Interface()); err != nil {
				http.Error(w, fmt.Sprintf("Malformed user data: %v", err), 400)
			}
			in = append(in, inv.Elem())
		} else {
			var m map[string]string
			if err := d.Decode(&m); err != nil {
				http.Error(w, fmt.Sprintf("Malformed user data: %v", err), 400)
			}
			if len(m) != 0 {
				http.Error(w, "Unexpected data", 400)
			}
		}
		out := v.Call(in)
		raw, err := json.Marshal(out[0].Interface())
		if err != nil {
			http.Error(w, fmt.Sprintf("Malformed response: %v", err), 500)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		w.Header().Set("Cache-Control", cacheControlNone)
		w.WriteHeader(int(out[1].Int()))
		w.Write(raw)
	})
}
<|endoftext|>


<commit_before>// Copyright The OpenTelemetry 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 pulsarexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/pulsarexporter"

import (
	"context"

	"go.opentelemetry.io/collector/component"
	"go.opentelemetry.io/collector/config"
	"go.opentelemetry.io/collector/consumer"
	"go.opentelemetry.io/collector/exporter/exporterhelper"
)

const (
	typeStr             = "pulsar"
	defaultTracesTopic  = "otlp_spans"
	defaultMetricsTopic = "otlp_metrics"
	defaultLogsTopic    = "otlp_logs"
	defaultEncoding     = "otlp_proto"
	defaultBroker       = "pulsar://localhost:6650"
)

// FactoryOption applies changes to pulsarExporterFactory.
type FactoryOption func(factory *pulsarExporterFactory)

// WithTracesMarshalers adds tracesMarshalers.
func WithTracesMarshalers(tracesMarshalers ...TracesMarshaler) FactoryOption {
	return func(factory *pulsarExporterFactory) {
		for _, marshaler := range tracesMarshalers {
			factory.tracesMarshalers[marshaler.Encoding()] = marshaler
		}
	}
}

// NewFactory creates Pulsar exporter factory.
func NewFactory(options ...FactoryOption) component.ExporterFactory {
	f := &pulsarExporterFactory{
		tracesMarshalers:  tracesMarshalers(),
		metricsMarshalers: metricsMarshalers(),
		logsMarshalers:    logsMarshalers(),
	}
	for _, o := range options {
		o(f)
	}
	return component.NewExporterFactory(
		typeStr,
		createDefaultConfig,
		component.WithTracesExporter(f.createTracesExporter),
		component.WithMetricsExporter(f.createMetricsExporter),
		component.WithLogsExporter(f.createLogsExporter),
	)
}

func createDefaultConfig() config.Exporter {
	return &Config{
		ExporterSettings: config.NewExporterSettings(config.NewComponentID(typeStr)),
		TimeoutSettings:  exporterhelper.NewDefaultTimeoutSettings(),
		RetrySettings:    exporterhelper.NewDefaultRetrySettings(),
		QueueSettings:    exporterhelper.NewDefaultQueueSettings(),
		Endpoint:         defaultBroker,
		// using an empty topic to track when it has not been set by user, default is based on traces or metrics.
		Topic:          "",
		Encoding:       defaultEncoding,
		Authentication: Authentication{},
	}
}

type pulsarExporterFactory struct {
	tracesMarshalers  map[string]TracesMarshaler
	metricsMarshalers map[string]MetricsMarshaler
	logsMarshalers    map[string]LogsMarshaler
}

func (f *pulsarExporterFactory) createTracesExporter(
	_ context.Context,
	set component.ExporterCreateSettings,
	cfg config.Exporter,
) (component.TracesExporter, error) {
	oCfg := *(cfg.(*Config))
	if oCfg.Topic == "" {
		oCfg.Topic = defaultTracesTopic
	}
	if oCfg.Encoding == "otlp_json" {
		set.Logger.Info("otlp_json is considered experimental and should not be used in a production environment")
	}
	exp, err := newTracesExporter(oCfg, set, f.tracesMarshalers)
	if err != nil {
		return nil, err
	}
	return exporterhelper.NewTracesExporter(
		cfg,
		set,
		exp.tracesPusher,
		exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
		// Disable exporterhelper Timeout, because we cannot pass a Context to the Producer,
		// and will rely on the Pulsar Producer Timeout logic.
		exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}),
		exporterhelper.WithRetry(oCfg.RetrySettings),
		exporterhelper.WithQueue(oCfg.QueueSettings),
		exporterhelper.WithShutdown(exp.Close))
}

func (f *pulsarExporterFactory) createMetricsExporter(
	_ context.Context,
	set component.ExporterCreateSettings,
	cfg config.Exporter,
) (component.MetricsExporter, error) {
	oCfg := *(cfg.(*Config))
	if oCfg.Topic == "" {
		oCfg.Topic = defaultMetricsTopic
	}
	if oCfg.Encoding == "otlp_json" {
		set.Logger.Info("otlp_json is considered experimental and should not be used in a production environment")
	}
	exp, err := newMetricsExporter(oCfg, set, f.metricsMarshalers)
	if err != nil {
		return nil, err
	}
	return exporterhelper.NewMetricsExporter(
		cfg,
		set,
		exp.metricsDataPusher,
		exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
		// Disable exporterhelper Timeout, because we cannot pass a Context to the Producer,
		// and will rely on the sarama Pulsar Timeout logic.
		exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}),
		exporterhelper.WithRetry(oCfg.RetrySettings),
		exporterhelper.WithQueue(oCfg.QueueSettings),
		exporterhelper.WithShutdown(exp.Close))
}

func (f *pulsarExporterFactory) createLogsExporter(
	_ context.Context,
	set component.ExporterCreateSettings,
	cfg config.Exporter,
) (component.LogsExporter, error) {
	oCfg := *(cfg.(*Config))
	if oCfg.Topic == "" {
		oCfg.Topic = defaultLogsTopic
	}
	if oCfg.Encoding == "otlp_json" {
		set.Logger.Info("otlp_json is considered experimental and should not be used in a production environment")
	}
	exp, err := newLogsExporter(oCfg, set, f.logsMarshalers)
	if err != nil {
		return nil, err
	}
	return exporterhelper.NewLogsExporter(
		cfg,
		set,
		exp.logsDataPusher,
		exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
		// Disable exporterhelper Timeout, because we cannot pass a Context to the Producer,
		// and will rely on the Pulsar Producer Timeout logic.
		exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}),
		exporterhelper.WithRetry(oCfg.RetrySettings),
		exporterhelper.WithQueue(oCfg.QueueSettings),
		exporterhelper.WithShutdown(exp.Close))
}
<commit_msg>Set correct stability level for pulsar exporter, matches readme (#12877)<commit_after>// Copyright The OpenTelemetry 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 pulsarexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/pulsarexporter"

import (
	"context"

	"go.opentelemetry.io/collector/component"
	"go.opentelemetry.io/collector/config"
	"go.opentelemetry.io/collector/consumer"
	"go.opentelemetry.io/collector/exporter/exporterhelper"
)

const (
	typeStr = "pulsar"
	// The stability level of the exporter.
	stability = component.StabilityLevelInDevelopment

	defaultTracesTopic  = "otlp_spans"
	defaultMetricsTopic = "otlp_metrics"
	defaultLogsTopic    = "otlp_logs"
	defaultEncoding     = "otlp_proto"
	defaultBroker       = "pulsar://localhost:6650"
)

// FactoryOption applies changes to pulsarExporterFactory.
type FactoryOption func(factory *pulsarExporterFactory)

// WithTracesMarshalers adds tracesMarshalers.
func WithTracesMarshalers(tracesMarshalers ...TracesMarshaler) FactoryOption {
	return func(factory *pulsarExporterFactory) {
		for _, marshaler := range tracesMarshalers {
			factory.tracesMarshalers[marshaler.Encoding()] = marshaler
		}
	}
}

// NewFactory creates Pulsar exporter factory.
func NewFactory(options ...FactoryOption) component.ExporterFactory {
	f := &pulsarExporterFactory{
		tracesMarshalers:  tracesMarshalers(),
		metricsMarshalers: metricsMarshalers(),
		logsMarshalers:    logsMarshalers(),
	}
	for _, o := range options {
		o(f)
	}
	return component.NewExporterFactory(
		typeStr,
		createDefaultConfig,
		component.WithTracesExporterAndStabilityLevel(f.createTracesExporter, stability),
		component.WithMetricsExporterAndStabilityLevel(f.createMetricsExporter, stability),
		component.WithLogsExporterAndStabilityLevel(f.createLogsExporter, stability),
	)
}

func createDefaultConfig() config.Exporter {
	return &Config{
		ExporterSettings: config.NewExporterSettings(config.NewComponentID(typeStr)),
		TimeoutSettings:  exporterhelper.NewDefaultTimeoutSettings(),
		RetrySettings:    exporterhelper.NewDefaultRetrySettings(),
		QueueSettings:    exporterhelper.NewDefaultQueueSettings(),
		Endpoint:         defaultBroker,
		// using an empty topic to track when it has not been set by user, default is based on traces or metrics.
		Topic:          "",
		Encoding:       defaultEncoding,
		Authentication: Authentication{},
	}
}

type pulsarExporterFactory struct {
	tracesMarshalers  map[string]TracesMarshaler
	metricsMarshalers map[string]MetricsMarshaler
	logsMarshalers    map[string]LogsMarshaler
}

func (f *pulsarExporterFactory) createTracesExporter(
	_ context.Context,
	set component.ExporterCreateSettings,
	cfg config.Exporter,
) (component.TracesExporter, error) {
	oCfg := *(cfg.(*Config))
	if oCfg.Topic == "" {
		oCfg.Topic = defaultTracesTopic
	}
	if oCfg.Encoding == "otlp_json" {
		set.Logger.Info("otlp_json is considered experimental and should not be used in a production environment")
	}
	exp, err := newTracesExporter(oCfg, set, f.tracesMarshalers)
	if err != nil {
		return nil, err
	}
	return exporterhelper.NewTracesExporter(
		cfg,
		set,
		exp.tracesPusher,
		exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
		// Disable exporterhelper Timeout, because we cannot pass a Context to the Producer,
		// and will rely on the Pulsar Producer Timeout logic.
		exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}),
		exporterhelper.WithRetry(oCfg.RetrySettings),
		exporterhelper.WithQueue(oCfg.QueueSettings),
		exporterhelper.WithShutdown(exp.Close))
}

func (f *pulsarExporterFactory) createMetricsExporter(
	_ context.Context,
	set component.ExporterCreateSettings,
	cfg config.Exporter,
) (component.MetricsExporter, error) {
	oCfg := *(cfg.(*Config))
	if oCfg.Topic == "" {
		oCfg.Topic = defaultMetricsTopic
	}
	if oCfg.Encoding == "otlp_json" {
		set.Logger.Info("otlp_json is considered experimental and should not be used in a production environment")
	}
	exp, err := newMetricsExporter(oCfg, set, f.metricsMarshalers)
	if err != nil {
		return nil, err
	}
	return exporterhelper.NewMetricsExporter(
		cfg,
		set,
		exp.metricsDataPusher,
		exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
		// Disable exporterhelper Timeout, because we cannot pass a Context to the Producer,
		// and will rely on the sarama Pulsar Timeout logic.
		exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}),
		exporterhelper.WithRetry(oCfg.RetrySettings),
		exporterhelper.WithQueue(oCfg.QueueSettings),
		exporterhelper.WithShutdown(exp.Close))
}

func (f *pulsarExporterFactory) createLogsExporter(
	_ context.Context,
	set component.ExporterCreateSettings,
	cfg config.Exporter,
) (component.LogsExporter, error) {
	oCfg := *(cfg.(*Config))
	if oCfg.Topic == "" {
		oCfg.Topic = defaultLogsTopic
	}
	if oCfg.Encoding == "otlp_json" {
		set.Logger.Info("otlp_json is considered experimental and should not be used in a production environment")
	}
	exp, err := newLogsExporter(oCfg, set, f.logsMarshalers)
	if err != nil {
		return nil, err
	}
	return exporterhelper.NewLogsExporter(
		cfg,
		set,
		exp.logsDataPusher,
		exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
		// Disable exporterhelper Timeout, because we cannot pass a Context to the Producer,
		// and will rely on the Pulsar Producer Timeout logic.
		exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}),
		exporterhelper.WithRetry(oCfg.RetrySettings),
		exporterhelper.WithQueue(oCfg.QueueSettings),
		exporterhelper.WithShutdown(exp.Close))
}
<|endoftext|>