<commit_before>/**
 * Copyright 2016 Confluent Inc.
 *
 * 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 main

import (
	"bufio"
	"fmt"
	"github.com/confluentinc/confluent-kafka-go/kafka"
	"gopkg.in/alecthomas/kingpin.v2"
	"os"
	"os/signal"
	"strings"
	"syscall"
)

var (
	verbosity     = 2
	exit_eof      = false
	eof_cnt       = 0
	partition_cnt = 0
	key_delim     = ""
	sigs          chan os.Signal
)

func run_producer(config *kafka.ConfigMap, topic string, partition int32) {
	p, err := kafka.NewProducer(config)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to create producer: %s\n", err)
		os.Exit(1)
	}

	fmt.Fprintf(os.Stderr, "Created Producer %v, topic %s [%d]\n", p, topic, partition)

	tp := kafka.TopicPartition{Topic: &topic, Partition: partition}

	go func(drs chan kafka.Event) {
		for ev := range drs {
			m, ok := ev.(*kafka.Message)
			if !ok {
				continue
			}
			if m.TopicPartition.Error != nil {
				fmt.Fprintf(os.Stderr, "%% Delivery error: %v\n", m.TopicPartition)
			} else if verbosity >= 2 {
				fmt.Fprintf(os.Stderr, "%% Delivered %v\n", m)
			}
		}
	}(p.Events)

	reader := bufio.NewReader(os.Stdin)

	for true {
		line, err := reader.ReadString('\n')
		if err != nil {
			break
		}

		line = strings.TrimSuffix(line, "\n")
		if len(line) == 0 {
			continue
		}

		msg := kafka.Message{TopicPartition: tp}

		if key_delim != "" {
			vec := strings.SplitN(line, key_delim, 2)
			if len(vec[0]) > 0 {
				msg.Key = ([]byte)(vec[0])
			}
			if len(vec) == 2 && len(vec[1]) > 0 {
				msg.Value = ([]byte)(vec[1])
			}
		} else {
			msg.Value = ([]byte)(line)
		}

		p.ProduceChannel <- &msg
	}

	fmt.Fprintf(os.Stderr, "%% Flushing\n")
	p.Flush(10000)
	fmt.Fprintf(os.Stderr, "%% Closing\n")
	p.Close()
}

func run_consumer(config *kafka.ConfigMap, topics []string) {
	c, err := kafka.NewConsumer(config)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to create consumer: %s\n", err)
		os.Exit(1)
	}

	fmt.Fprintf(os.Stderr, "%% Created Consumer %v\n", c)

	c.SubscribeTopics(topics, nil)

	run := true

	for run == true {
		select {

		case sig := <-sigs:
			fmt.Fprintf(os.Stderr, "%% Terminating on signal %v\n", sig)
			run = false

		case ev := <-c.Events:
			switch e := ev.(type) {
			case kafka.AssignedPartitions:
				fmt.Fprintf(os.Stderr, "%% %v\n", e)
				c.Assign(e.Partitions)
				partition_cnt = len(e.Partitions)
				eof_cnt = 0
			case kafka.RevokedPartitions:
				fmt.Fprintf(os.Stderr, "%% %v\n", e)
				c.Unassign()
				partition_cnt = 0
				eof_cnt = 0
			case *kafka.Message:
				if verbosity >= 2 {
					fmt.Fprintf(os.Stderr, "%% %v:\n", e.TopicPartition)
				}
				if key_delim != "" {
					if e.Key != nil {
						fmt.Printf("%s%s", string(e.Key), key_delim)
					} else {
						fmt.Printf("%s", key_delim)
					}
				}
				fmt.Println(string(e.Value))
			case kafka.PartitionEof:
				fmt.Fprintf(os.Stderr, "%% Reached %v\n", e)
				eof_cnt += 1
				if exit_eof && eof_cnt >= partition_cnt {
					run = false
				}
			case kafka.KafkaError:
				fmt.Fprintf(os.Stderr, "%% Error: %v\n", e)
				run = false
			default:
				fmt.Fprintf(os.Stderr, "%% Unhandled event %T ignored: %v\n", e, e)
			}
		}
	}

	fmt.Fprintf(os.Stderr, "%% Closing consumer\n")
	c.Close()
}

func main() {
	sigs = make(chan os.Signal)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

	// Default config
	config := kafka.ConfigMap{"session.timeout.ms": 6000}

	/* General options */
	brokers := kingpin.Flag("broker", "Bootstrap broker(s)").Required().String()
	kingpin.Flag("config", "Configuration property (prop=val)").Short('X').PlaceHolder("PROP=VAL").SetValue(&config)
	key_delim_arg := kingpin.Flag("key-delim", "Key and value delimiter (empty string=dont print/parse key)").Default("").String()

	/* Producer mode options */
	mode_P := kingpin.Command("produce", "Produce messages")
	topic := mode_P.Flag("topic", "Topic to produce to").Required().String()
	partition := mode_P.Flag("partition", "Partition to produce to").Default("-1").Int()

	/* Consumer mode options */
	mode_C := kingpin.Command("consume", "Consume messages").Default()
	group := mode_C.Flag("group", "Consumer group").Required().String()
	topics := mode_C.Arg("topic", "Topic(s) to subscribe to").Required().Strings()
	var initial_offset kafka.Offset = kafka.KAFKA_OFFSET_BEGINNING
	mode_C.Flag("offset", "Initial offset").Short('o').SetValue(&initial_offset)
	exit_eof_arg := mode_C.Flag("eof", "Exit when EOF is reached for all partitions").Bool()

	mode := kingpin.Parse()

	key_delim = *key_delim_arg
	exit_eof = *exit_eof_arg
	config["bootstrap.servers"] = *brokers

	switch mode {
	case "produce":
		config["default.topic.config"] = kafka.ConfigMap{"produce.offset.report": true}
		run_producer(&config, *topic, int32(*partition))

	case "consume":
		config["group.id"] = *group
		config["go.events.channel.enable"] = true
		config["go.application.rebalance.enable"] = true
		config["default.topic.config"] = kafka.ConfigMap{"auto.offset.reset": initial_offset}
		run_consumer(&config, *topics)
	}

}
<commit_msg>gofkacat fixes<commit_after>/**
 * Copyright 2016 Confluent Inc.
 *
 * 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 main

import (
	"bufio"
	"fmt"
	"github.com/confluentinc/confluent-kafka-go/kafka"
	"gopkg.in/alecthomas/kingpin.v2"
	"os"
	"os/signal"
	"strings"
	"syscall"
)

var (
	verbosity     = 2
	exit_eof      = false
	eof_cnt       = 0
	partition_cnt = 0
	key_delim     = ""
	sigs          chan os.Signal
)

func run_producer(config *kafka.ConfigMap, topic string, partition int32) {
	p, err := kafka.NewProducer(config)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to create producer: %s\n", err)
		os.Exit(1)
	}

	fmt.Fprintf(os.Stderr, "Created Producer %v, topic %s [%d]\n", p, topic, partition)

	tp := kafka.TopicPartition{Topic: &topic, Partition: partition}

	go func(drs chan kafka.Event) {
		for ev := range drs {
			m, ok := ev.(*kafka.Message)
			if !ok {
				continue
			}
			if m.TopicPartition.Error != nil {
				fmt.Fprintf(os.Stderr, "%% Delivery error: %v\n", m.TopicPartition)
			} else if verbosity >= 2 {
				fmt.Fprintf(os.Stderr, "%% Delivered %v\n", m)
			}
		}
	}(p.Events)

	reader := bufio.NewReader(os.Stdin)
	stdin_chan := make(chan string)

	go func() {
		for true {
			line, err := reader.ReadString('\n')
			if err != nil {
				break
			}

			line = strings.TrimSuffix(line, "\n")
			if len(line) == 0 {
				continue
			}

			stdin_chan <- line
		}
		close(stdin_chan)
	}()

	run := true

	for run == true {
		select {
		case sig := <-sigs:
			fmt.Fprintf(os.Stderr, "%% Terminating on signal %v\n", sig)
			run = false

		case line, ok := <-stdin_chan:
			if !ok {
				run = false
				break
			}

			msg := kafka.Message{TopicPartition: tp}

			if key_delim != "" {
				vec := strings.SplitN(line, key_delim, 2)
				if len(vec[0]) > 0 {
					msg.Key = ([]byte)(vec[0])
				}
				if len(vec) == 2 && len(vec[1]) > 0 {
					msg.Value = ([]byte)(vec[1])
				}
			} else {
				msg.Value = ([]byte)(line)
			}

			p.ProduceChannel <- &msg
		}
	}

	fmt.Fprintf(os.Stderr, "%% Flushing\n")
	p.Flush(10000)
	fmt.Fprintf(os.Stderr, "%% Closing\n")
	p.Close()
}

func run_consumer(config *kafka.ConfigMap, topics []string) {
	c, err := kafka.NewConsumer(config)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to create consumer: %s\n", err)
		os.Exit(1)
	}

	fmt.Fprintf(os.Stderr, "%% Created Consumer %v\n", c)

	c.SubscribeTopics(topics, nil)

	run := true

	for run == true {
		select {

		case sig := <-sigs:
			fmt.Fprintf(os.Stderr, "%% Terminating on signal %v\n", sig)
			run = false

		case ev := <-c.Events:
			switch e := ev.(type) {
			case kafka.AssignedPartitions:
				fmt.Fprintf(os.Stderr, "%% %v\n", e)
				c.Assign(e.Partitions)
				partition_cnt = len(e.Partitions)
				eof_cnt = 0
			case kafka.RevokedPartitions:
				fmt.Fprintf(os.Stderr, "%% %v\n", e)
				c.Unassign()
				partition_cnt = 0
				eof_cnt = 0
			case *kafka.Message:
				if verbosity >= 2 {
					fmt.Fprintf(os.Stderr, "%% %v:\n", e.TopicPartition)
				}
				if key_delim != "" {
					if e.Key != nil {
						fmt.Printf("%s%s", string(e.Key), key_delim)
					} else {
						fmt.Printf("%s", key_delim)
					}
				}
				fmt.Println(string(e.Value))
			case kafka.PartitionEof:
				fmt.Fprintf(os.Stderr, "%% Reached %v\n", e)
				eof_cnt += 1
				if exit_eof && eof_cnt >= partition_cnt {
					run = false
				}
			case kafka.KafkaError:
				fmt.Fprintf(os.Stderr, "%% Error: %v\n", e)
				run = false
			default:
				fmt.Fprintf(os.Stderr, "%% Unhandled event %T ignored: %v\n", e, e)
			}
		}
	}

	fmt.Fprintf(os.Stderr, "%% Closing consumer\n")
	c.Close()
}

type ConfigArgs struct {
	conf kafka.ConfigMap
}

func (c *ConfigArgs) String() string {
	return "FIXME"
}

func (c *ConfigArgs) Set(value string) error {
	return c.conf.Set(value)
}

func (c *ConfigArgs) IsCumulative() bool {
	return true
}

func main() {
	sigs = make(chan os.Signal)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

	// Default config
	var confargs ConfigArgs
	confargs.conf = kafka.ConfigMap{"session.timeout.ms": 6000}

	/* General options */
	brokers := kingpin.Flag("broker", "Bootstrap broker(s)").Required().String()
	kingpin.Flag("config", "Configuration property (prop=val)").Short('X').PlaceHolder("PROP=VAL").SetValue(&confargs)
	key_delim_arg := kingpin.Flag("key-delim", "Key and value delimiter (empty string=dont print/parse key)").Default("").String()

	/* Producer mode options */
	mode_P := kingpin.Command("produce", "Produce messages")
	topic := mode_P.Flag("topic", "Topic to produce to").Required().String()
	partition := mode_P.Flag("partition", "Partition to produce to").Default("-1").Int()

	/* Consumer mode options */
	mode_C := kingpin.Command("consume", "Consume messages").Default()
	group := mode_C.Flag("group", "Consumer group").Required().String()
	topics := mode_C.Arg("topic", "Topic(s) to subscribe to").Required().Strings()
	var initial_offset kafka.Offset = kafka.KAFKA_OFFSET_BEGINNING
	mode_C.Flag("offset", "Initial offset").Short('o').SetValue(&initial_offset)
	exit_eof_arg := mode_C.Flag("eof", "Exit when EOF is reached for all partitions").Bool()

	mode := kingpin.Parse()

	key_delim = *key_delim_arg
	exit_eof = *exit_eof_arg
	confargs.conf["bootstrap.servers"] = *brokers

	switch mode {
	case "produce":
		confargs.conf["default.topic.config"] = kafka.ConfigMap{"produce.offset.report": true}
		run_producer((*kafka.ConfigMap)(&confargs.conf), *topic, int32(*partition))

	case "consume":
		confargs.conf["group.id"] = *group
		confargs.conf["go.events.channel.enable"] = true
		confargs.conf["go.application.rebalance.enable"] = true
		confargs.conf["default.topic.config"] = kafka.ConfigMap{"auto.offset.reset": initial_offset}
		run_consumer((*kafka.ConfigMap)(&confargs.conf), *topics)
	}

}
<|endoftext|>


<commit_before>// Copyright 2014 The gocui 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 main

import (
	"fmt"
	"log"
	"strings"

	"github.com/jroimartin/gocui"
)

const delta = 2

var (
	views   = []string{}
	curView = -1
	idxView = 0
)

func main() {
	var err error

	g := gocui.NewGui()
	if err := g.Init(); err != nil {
		log.Panicln(err)
	}
	defer g.Close()

	g.SetLayout(func(g *gocui.Gui) error { return nil })

	if err := initKeybindings(g); err != nil {
		log.Panicln(err)
	}
	if err := initViews(g); err != nil {
		log.Panicln(err)
	}

	err = g.MainLoop()
	if err != nil && err != gocui.ErrorQuit {
		log.Panicln(err)
	}
}

func initKeybindings(g *gocui.Gui) error {
	if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeySpace, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return newView(g)
		}); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyBackspace2, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return delView(g)
		}); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return nextView(g, true)
		}); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyArrowLeft, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return moveView(g, v, -delta, 0)
		}); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyArrowRight, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return moveView(g, v, delta, 0)
		}); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return moveView(g, v, 0, delta)
		}); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return moveView(g, v, 0, -delta)
		}); err != nil {
		return err
	}
	return nil
}

func quit(g *gocui.Gui, v *gocui.View) error {
	return gocui.ErrorQuit
}

func initViews(g *gocui.Gui) error {
	maxX, _ := g.Size()
	v, err := g.SetView("legend", maxX-22, 0, maxX-1, 6)
	if err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		fmt.Fprintln(v, "KEYBINDINGS")
		fmt.Fprintln(v, "Space: New View")
		fmt.Fprintln(v, "Tab: Next View")
		fmt.Fprintln(v, "← ↑ → ↓: Move View")
		fmt.Fprintln(v, "^C: Exit")
	}
	if err := newView(g); err != nil {
		log.Panicln(err)
	}
	return nil
}

func newView(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	name := fmt.Sprintf("v%v", idxView)
	v, err := g.SetView(name, maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5)
	if err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		v.Wrap = true
		fmt.Fprintln(v, strings.Repeat(name+" ", 30))
	}
	if err := g.SetCurrentView(name); err != nil {
		return err
	}
	v.BgColor = gocui.ColorRed

	if curView >= 0 {
		cv, err := g.View(views[curView])
		if err != nil {
			return err
		}
		cv.BgColor = g.BgColor
	}

	views = append(views, name)
	curView = len(views) - 1
	idxView += 1
	return nil
}

func delView(g *gocui.Gui) error {
	if len(views) <= 1 {
		return nil
	}

	if err := g.DeleteView(views[curView]); err != nil {
		return err
	}
	views = append(views[:curView], views[curView+1:]...)

	return nextView(g, false)
}

func nextView(g *gocui.Gui, disableCurrent bool) error {
	next := curView + 1
	if next > len(views)-1 {
		next = 0
	}

	nv, err := g.View(views[next])
	if err != nil {
		return err
	}
	if err := g.SetCurrentView(views[next]); err != nil {
		return err
	}
	nv.BgColor = gocui.ColorRed

	if disableCurrent && len(views) > 1 {
		cv, err := g.View(views[curView])
		if err != nil {
			return err
		}
		cv.BgColor = g.BgColor
	}

	curView = next
	return nil
}

func moveView(g *gocui.Gui, v *gocui.View, dx, dy int) error {
	name := v.Name()
	x0, y0, x1, y1, err := g.ViewPosition(name)
	if err != nil {
		return err
	}
	if _, err := g.SetView(name, x0+dx, y0+dy, x1+dx, y1+dy); err != nil {
		return err
	}
	return nil
}
<commit_msg>_examples/delete: Move legend to layout<commit_after>// Copyright 2014 The gocui 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 main

import (
	"fmt"
	"log"
	"strings"

	"github.com/jroimartin/gocui"
)

const delta = 2

var (
	views   = []string{}
	curView = -1
	idxView = 0
)

func main() {
	var err error

	g := gocui.NewGui()
	if err := g.Init(); err != nil {
		log.Panicln(err)
	}
	defer g.Close()

	g.SetLayout(layout)
	if err := initKeybindings(g); err != nil {
		log.Panicln(err)
	}
	if err := newView(g); err != nil {
		log.Panicln(err)
	}

	err = g.MainLoop()
	if err != nil && err != gocui.ErrorQuit {
		log.Panicln(err)
	}
}

func layout(g *gocui.Gui) error {
	maxX, _ := g.Size()
	v, err := g.SetView("legend", maxX-22, 0, maxX-1, 6)
	if err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		fmt.Fprintln(v, "KEYBINDINGS")
		fmt.Fprintln(v, "Space: New View")
		fmt.Fprintln(v, "Tab: Next View")
		fmt.Fprintln(v, "← ↑ → ↓: Move View")
		fmt.Fprintln(v, "^C: Exit")
	}
	return nil
}

func initKeybindings(g *gocui.Gui) error {
	if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeySpace, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return newView(g)
		}); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyBackspace2, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return delView(g)
		}); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return nextView(g, true)
		}); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyArrowLeft, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return moveView(g, v, -delta, 0)
		}); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyArrowRight, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return moveView(g, v, delta, 0)
		}); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return moveView(g, v, 0, delta)
		}); err != nil {
		return err
	}
	if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone,
		func(g *gocui.Gui, v *gocui.View) error {
			return moveView(g, v, 0, -delta)
		}); err != nil {
		return err
	}
	return nil
}

func quit(g *gocui.Gui, v *gocui.View) error {
	return gocui.ErrorQuit
}

func newView(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	name := fmt.Sprintf("v%v", idxView)
	v, err := g.SetView(name, maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5)
	if err != nil {
		if err != gocui.ErrorUnkView {
			return err
		}
		v.Wrap = true
		fmt.Fprintln(v, strings.Repeat(name+" ", 30))
	}
	if err := g.SetCurrentView(name); err != nil {
		return err
	}
	v.BgColor = gocui.ColorRed

	if curView >= 0 {
		cv, err := g.View(views[curView])
		if err != nil {
			return err
		}
		cv.BgColor = g.BgColor
	}

	views = append(views, name)
	curView = len(views) - 1
	idxView += 1
	return nil
}

func delView(g *gocui.Gui) error {
	if len(views) <= 1 {
		return nil
	}

	if err := g.DeleteView(views[curView]); err != nil {
		return err
	}
	views = append(views[:curView], views[curView+1:]...)

	return nextView(g, false)
}

func nextView(g *gocui.Gui, disableCurrent bool) error {
	next := curView + 1
	if next > len(views)-1 {
		next = 0
	}

	nv, err := g.View(views[next])
	if err != nil {
		return err
	}
	if err := g.SetCurrentView(views[next]); err != nil {
		return err
	}
	nv.BgColor = gocui.ColorRed

	if disableCurrent && len(views) > 1 {
		cv, err := g.View(views[curView])
		if err != nil {
			return err
		}
		cv.BgColor = g.BgColor
	}

	curView = next
	return nil
}

func moveView(g *gocui.Gui, v *gocui.View, dx, dy int) error {
	name := v.Name()
	x0, y0, x1, y1, err := g.ViewPosition(name)
	if err != nil {
		return err
	}
	if _, err := g.SetView(name, x0+dx, y0+dy, x1+dx, y1+dy); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package hook

import (
	"fmt"
	"log"
	"net/http"
	"net/url"

	"github.com/bitrise-io/bitrise-webhooks/bitriseapi"
	"github.com/bitrise-io/bitrise-webhooks/config"
	"github.com/bitrise-io/bitrise-webhooks/metrics"
	"github.com/bitrise-io/bitrise-webhooks/service"
	"github.com/bitrise-io/bitrise-webhooks/service/hook/bitbucketv2"
	hookCommon "github.com/bitrise-io/bitrise-webhooks/service/hook/common"
	"github.com/bitrise-io/bitrise-webhooks/service/hook/github"
	"github.com/bitrise-io/bitrise-webhooks/service/hook/slack"
	"github.com/bitrise-io/bitrise-webhooks/service/hook/visualstudioteamservices"
	"github.com/gorilla/mux"
)

func supportedProviders() map[string]hookCommon.Provider {
	return map[string]hookCommon.Provider{
		"github":       github.HookProvider{},
		"bitbucket-v2": bitbucketv2.HookProvider{},
		"slack":        slack.HookProvider{},
		"visual-studio-team-services": visualstudioteamservices.HookProvider{},
	}
}

// ----------------------------------
// --- Response handler functions ---

func respondWithErrorString(w http.ResponseWriter, provider *hookCommon.Provider, errStr string) {
	responseProvider := hookCommon.ResponseTransformer(DefaultResponseProvider{})
	if provider != nil {
		if respTransformer, ok := (*provider).(hookCommon.ResponseTransformer); ok {
			// provider can transform responses - let it do so
			responseProvider = respTransformer
		}
	}
	//
	respInfo := responseProvider.TransformErrorMessageResponse(errStr)
	httpStatusCode := 400 // default
	if respInfo.HTTPStatusCode != 0 {
		httpStatusCode = respInfo.HTTPStatusCode
	}
	service.RespondWith(w, httpStatusCode, respInfo.Data)
}

func respondWithSuccessMessage(w http.ResponseWriter, provider *hookCommon.Provider, msg string) {
	responseProvider := hookCommon.ResponseTransformer(DefaultResponseProvider{})
	if provider != nil {
		if respTransformer, ok := (*provider).(hookCommon.ResponseTransformer); ok {
			// provider can transform responses - let it do so
			responseProvider = respTransformer
		}
	}
	//
	respInfo := responseProvider.TransformSuccessMessageResponse(msg)
	httpStatusCode := 201 // default
	if respInfo.HTTPStatusCode != 0 {
		httpStatusCode = respInfo.HTTPStatusCode
	}
	service.RespondWith(w, httpStatusCode, respInfo.Data)
}

func respondWithResults(w http.ResponseWriter, provider *hookCommon.Provider, results hookCommon.TransformResponseInputModel) {
	responseProvider := hookCommon.ResponseTransformer(DefaultResponseProvider{})
	if provider != nil {
		if respTransformer, ok := (*provider).(hookCommon.ResponseTransformer); ok {
			// provider can transform responses - let it do so
			responseProvider = respTransformer
		}
	}
	//
	respInfo := responseProvider.TransformResponse(results)
	httpStatusCode := 201 // default
	if respInfo.HTTPStatusCode != 0 {
		httpStatusCode = respInfo.HTTPStatusCode
	}
	service.RespondWith(w, httpStatusCode, respInfo.Data)
}

// -------------------------
// --- Utility functions ---

func triggerBuild(triggerURL *url.URL, apiToken string, triggerAPIParams bitriseapi.TriggerAPIParamsModel) (bitriseapi.TriggerAPIResponseModel, bool, error) {
	log.Printf(" ===> trigger build: %s", triggerURL)
	isOnlyLog := !(config.SendRequestToURL != nil || config.GetServerEnvMode() == config.ServerEnvModeProd)
	if isOnlyLog {
		log.Println(" (debug) isOnlyLog: true")
	}

	responseModel, isSuccess, err := bitriseapi.TriggerBuild(triggerURL, apiToken, triggerAPIParams, isOnlyLog)
	if err != nil {
		log.Printf(" [!] Exception: failed to trigger build: %s", err)
		return bitriseapi.TriggerAPIResponseModel{}, false, fmt.Errorf("Failed to Trigger the Build: %s", err)
	}
	log.Printf(" ===> trigger build - DONE (success: %t) (%s)", isSuccess, triggerURL)
	log.Printf("      (debug) response: (%#v)", responseModel)
	return responseModel, isSuccess, nil
}

// ------------------------------
// --- Main HTTP Handler code ---

// HTTPHandler ...
func HTTPHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	serviceID := vars["service-id"]
	appSlug := vars["app-slug"]
	apiToken := vars["api-token"]

	if serviceID == "" {
		respondWithErrorString(w, nil, "No service-id defined")
		return
	}
	hookProvider, isSupported := supportedProviders()[serviceID]
	if !isSupported {
		respondWithErrorString(w, nil, fmt.Sprintf("Unsupported Webhook Type / Provider: %s", serviceID))
		return
	}

	if appSlug == "" {
		respondWithErrorString(w, &hookProvider, "No App Slug parameter defined")
		return
	}
	if apiToken == "" {
		respondWithErrorString(w, &hookProvider, "No API Token parameter defined")
		return
	}

	hookTransformResult := hookCommon.TransformResultModel{}
	metrics.Trace("Hook: Transform", func() {
		hookTransformResult = hookProvider.TransformRequest(r)
	})

	if hookTransformResult.ShouldSkip {
		respondWithSuccessMessage(w, &hookProvider, fmt.Sprintf("Acknowledged, but skipping. Reason: %s", hookTransformResult.Error))
		return
	}
	if hookTransformResult.Error != nil {
		errMsg := fmt.Sprintf("Failed to transform the webhook: %s", hookTransformResult.Error)
		log.Printf(" (debug) %s", errMsg)
		respondWithErrorString(w, &hookProvider, errMsg)
		return
	}

	// Let's Trigger a build / some builds!
	triggerURL := config.SendRequestToURL
	if triggerURL == nil {
		u, err := bitriseapi.BuildTriggerURL("https://www.bitrise.io", appSlug)
		if err != nil {
			log.Printf(" [!] Exception: hookHandler: failed to create Build Trigger URL: %s", err)
			respondWithErrorString(w, &hookProvider, fmt.Sprintf("Failed to create Build Trigger URL: %s", err))
			return
		}
		triggerURL = u
	}

	buildTriggerCount := len(hookTransformResult.TriggerAPIParams)
	if buildTriggerCount == 0 {
		respondWithErrorString(w, &hookProvider, "After processing the webhook we failed to detect any event in it which could be turned into a build.")
		return
	}

	respondWith := hookCommon.TransformResponseInputModel{
		Errors:                  []string{},
		SuccessTriggerResponses: []bitriseapi.TriggerAPIResponseModel{},
		FailedTriggerResponses:  []bitriseapi.TriggerAPIResponseModel{},
	}
	metrics.Trace("Hook: Trigger Builds", func() {
		for _, aBuildTriggerParam := range hookTransformResult.TriggerAPIParams {
			if triggerResp, isSuccess, err := triggerBuild(triggerURL, apiToken, aBuildTriggerParam); err != nil {
				respondWith.Errors = append(respondWith.Errors, fmt.Sprintf("Failed to Trigger Build: %s", err))
			} else if isSuccess {
				respondWith.SuccessTriggerResponses = append(respondWith.SuccessTriggerResponses, triggerResp)
			} else {
				respondWith.FailedTriggerResponses = append(respondWith.FailedTriggerResponses, triggerResp)
			}
		}
	})

	respondWithResults(w, &hookProvider, respondWith)
}
<commit_msg>changed the visual studio hook url to `/h/visualstudio/`<commit_after>package hook

import (
	"fmt"
	"log"
	"net/http"
	"net/url"

	"github.com/bitrise-io/bitrise-webhooks/bitriseapi"
	"github.com/bitrise-io/bitrise-webhooks/config"
	"github.com/bitrise-io/bitrise-webhooks/metrics"
	"github.com/bitrise-io/bitrise-webhooks/service"
	"github.com/bitrise-io/bitrise-webhooks/service/hook/bitbucketv2"
	hookCommon "github.com/bitrise-io/bitrise-webhooks/service/hook/common"
	"github.com/bitrise-io/bitrise-webhooks/service/hook/github"
	"github.com/bitrise-io/bitrise-webhooks/service/hook/slack"
	"github.com/bitrise-io/bitrise-webhooks/service/hook/visualstudioteamservices"
	"github.com/gorilla/mux"
)

func supportedProviders() map[string]hookCommon.Provider {
	return map[string]hookCommon.Provider{
		"github":       github.HookProvider{},
		"bitbucket-v2": bitbucketv2.HookProvider{},
		"slack":        slack.HookProvider{},
		"visualstudio": visualstudioteamservices.HookProvider{},
	}
}

// ----------------------------------
// --- Response handler functions ---

func respondWithErrorString(w http.ResponseWriter, provider *hookCommon.Provider, errStr string) {
	responseProvider := hookCommon.ResponseTransformer(DefaultResponseProvider{})
	if provider != nil {
		if respTransformer, ok := (*provider).(hookCommon.ResponseTransformer); ok {
			// provider can transform responses - let it do so
			responseProvider = respTransformer
		}
	}
	//
	respInfo := responseProvider.TransformErrorMessageResponse(errStr)
	httpStatusCode := 400 // default
	if respInfo.HTTPStatusCode != 0 {
		httpStatusCode = respInfo.HTTPStatusCode
	}
	service.RespondWith(w, httpStatusCode, respInfo.Data)
}

func respondWithSuccessMessage(w http.ResponseWriter, provider *hookCommon.Provider, msg string) {
	responseProvider := hookCommon.ResponseTransformer(DefaultResponseProvider{})
	if provider != nil {
		if respTransformer, ok := (*provider).(hookCommon.ResponseTransformer); ok {
			// provider can transform responses - let it do so
			responseProvider = respTransformer
		}
	}
	//
	respInfo := responseProvider.TransformSuccessMessageResponse(msg)
	httpStatusCode := 201 // default
	if respInfo.HTTPStatusCode != 0 {
		httpStatusCode = respInfo.HTTPStatusCode
	}
	service.RespondWith(w, httpStatusCode, respInfo.Data)
}

func respondWithResults(w http.ResponseWriter, provider *hookCommon.Provider, results hookCommon.TransformResponseInputModel) {
	responseProvider := hookCommon.ResponseTransformer(DefaultResponseProvider{})
	if provider != nil {
		if respTransformer, ok := (*provider).(hookCommon.ResponseTransformer); ok {
			// provider can transform responses - let it do so
			responseProvider = respTransformer
		}
	}
	//
	respInfo := responseProvider.TransformResponse(results)
	httpStatusCode := 201 // default
	if respInfo.HTTPStatusCode != 0 {
		httpStatusCode = respInfo.HTTPStatusCode
	}
	service.RespondWith(w, httpStatusCode, respInfo.Data)
}

// -------------------------
// --- Utility functions ---

func triggerBuild(triggerURL *url.URL, apiToken string, triggerAPIParams bitriseapi.TriggerAPIParamsModel) (bitriseapi.TriggerAPIResponseModel, bool, error) {
	log.Printf(" ===> trigger build: %s", triggerURL)
	isOnlyLog := !(config.SendRequestToURL != nil || config.GetServerEnvMode() == config.ServerEnvModeProd)
	if isOnlyLog {
		log.Println(" (debug) isOnlyLog: true")
	}

	responseModel, isSuccess, err := bitriseapi.TriggerBuild(triggerURL, apiToken, triggerAPIParams, isOnlyLog)
	if err != nil {
		log.Printf(" [!] Exception: failed to trigger build: %s", err)
		return bitriseapi.TriggerAPIResponseModel{}, false, fmt.Errorf("Failed to Trigger the Build: %s", err)
	}
	log.Printf(" ===> trigger build - DONE (success: %t) (%s)", isSuccess, triggerURL)
	log.Printf("      (debug) response: (%#v)", responseModel)
	return responseModel, isSuccess, nil
}

// ------------------------------
// --- Main HTTP Handler code ---

// HTTPHandler ...
func HTTPHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	serviceID := vars["service-id"]
	appSlug := vars["app-slug"]
	apiToken := vars["api-token"]

	if serviceID == "" {
		respondWithErrorString(w, nil, "No service-id defined")
		return
	}
	hookProvider, isSupported := supportedProviders()[serviceID]
	if !isSupported {
		respondWithErrorString(w, nil, fmt.Sprintf("Unsupported Webhook Type / Provider: %s", serviceID))
		return
	}

	if appSlug == "" {
		respondWithErrorString(w, &hookProvider, "No App Slug parameter defined")
		return
	}
	if apiToken == "" {
		respondWithErrorString(w, &hookProvider, "No API Token parameter defined")
		return
	}

	hookTransformResult := hookCommon.TransformResultModel{}
	metrics.Trace("Hook: Transform", func() {
		hookTransformResult = hookProvider.TransformRequest(r)
	})

	if hookTransformResult.ShouldSkip {
		respondWithSuccessMessage(w, &hookProvider, fmt.Sprintf("Acknowledged, but skipping. Reason: %s", hookTransformResult.Error))
		return
	}
	if hookTransformResult.Error != nil {
		errMsg := fmt.Sprintf("Failed to transform the webhook: %s", hookTransformResult.Error)
		log.Printf(" (debug) %s", errMsg)
		respondWithErrorString(w, &hookProvider, errMsg)
		return
	}

	// Let's Trigger a build / some builds!
	triggerURL := config.SendRequestToURL
	if triggerURL == nil {
		u, err := bitriseapi.BuildTriggerURL("https://www.bitrise.io", appSlug)
		if err != nil {
			log.Printf(" [!] Exception: hookHandler: failed to create Build Trigger URL: %s", err)
			respondWithErrorString(w, &hookProvider, fmt.Sprintf("Failed to create Build Trigger URL: %s", err))
			return
		}
		triggerURL = u
	}

	buildTriggerCount := len(hookTransformResult.TriggerAPIParams)
	if buildTriggerCount == 0 {
		respondWithErrorString(w, &hookProvider, "After processing the webhook we failed to detect any event in it which could be turned into a build.")
		return
	}

	respondWith := hookCommon.TransformResponseInputModel{
		Errors:                  []string{},
		SuccessTriggerResponses: []bitriseapi.TriggerAPIResponseModel{},
		FailedTriggerResponses:  []bitriseapi.TriggerAPIResponseModel{},
	}
	metrics.Trace("Hook: Trigger Builds", func() {
		for _, aBuildTriggerParam := range hookTransformResult.TriggerAPIParams {
			if triggerResp, isSuccess, err := triggerBuild(triggerURL, apiToken, aBuildTriggerParam); err != nil {
				respondWith.Errors = append(respondWith.Errors, fmt.Sprintf("Failed to Trigger Build: %s", err))
			} else if isSuccess {
				respondWith.SuccessTriggerResponses = append(respondWith.SuccessTriggerResponses, triggerResp)
			} else {
				respondWith.FailedTriggerResponses = append(respondWith.FailedTriggerResponses, triggerResp)
			}
		}
	})

	respondWithResults(w, &hookProvider, respondWith)
}
<|endoftext|>


<commit_before>package imager

import (
	// Internal packages
	"fmt"
	"os"
	"path"

	// Third-party packages
	"github.com/mitchellh/goamz/aws"
	"github.com/mitchellh/goamz/s3"
)

// A Source represents an image source, which is usually matched against a URL endpoint, and
// provides options related to that endpoint.
type Source struct {
	bucket *s3.Bucket
	cache  *FileCache
}

func NewSource(region, bucket, accessKey, secretKey string) (*Source, error) {
	auth, err := aws.GetAuth(accessKey, secretKey)
	if err != nil {
		return nil, err
	}

	if _, exists := aws.Regions[region]; !exists {
		return nil, fmt.Errorf("S3 region by name '%s' not found", region)
	}

	s := &Source{
		bucket: s3.New(auth, aws.Regions[region]).Bucket(bucket),
	}

	return s, nil
}

func (s *Source) InitCache(base string, size int64) error {
	path := path.Join(os.TempDir(), base, s.bucket.Region.Name, s.bucket.Name)

	c, err := NewFileCache(path, size)
	if err != nil {
		return err
	}

	s.cache = c
	return nil
}

func (s *Source) GetFile(path string) ([]byte, error) {
	// Check for locally cached data.
	if s.cache != nil {
		if data := s.cache.Get(path); data != nil {
			return data.([]byte), nil
		}
	}

	// Get data from S3 bucket.
	data, err := s.bucket.Get(path)
	if err != nil {
		return nil, err
	}

	// Cache data locally.
	if s.cache != nil {
		s.cache.Add(path, data)
	}

	return data, nil
}
<commit_msg>Move to 'github.com/goamz/goamz' repository for AWS<commit_after>package imager

import (
	// Internal packages
	"fmt"
	"os"
	"path"
	"time"

	// Third-party packages
	"github.com/goamz/goamz/aws"
	"github.com/goamz/goamz/s3"
)

// A Source represents an image source, which is usually matched against a URL endpoint, and
// provides options related to that endpoint.
type Source struct {
	bucket *s3.Bucket
	cache  *FileCache
}

func NewSource(region, bucket, accessKey, secretKey string) (*Source, error) {
	// Authorization token is set to expire 5 years in the future.
	auth, err := aws.GetAuth(accessKey, secretKey, "", time.Now().AddDate(5, 0, 0))
	if err != nil {
		return nil, err
	}

	if _, exists := aws.Regions[region]; !exists {
		return nil, fmt.Errorf("S3 region by name '%s' not found", region)
	}

	s := &Source{
		bucket: s3.New(auth, aws.Regions[region]).Bucket(bucket),
	}

	return s, nil
}

func (s *Source) InitCache(base string, size int64) error {
	path := path.Join(os.TempDir(), base, s.bucket.Region.Name, s.bucket.Name)

	c, err := NewFileCache(path, size)
	if err != nil {
		return err
	}

	s.cache = c
	return nil
}

func (s *Source) Get(path string) ([]byte, error) {
	// Check for locally cached data.
	if s.cache != nil {
		if data := s.cache.Get(path); data != nil {
			return data.([]byte), nil
		}
	}

	// Get data from S3 bucket.
	data, err := s.bucket.Get(path)
	if err != nil {
		return nil, err
	}

	// Cache data locally.
	if s.cache != nil {
		s.cache.Add(path, data)
	}

	return data, nil
}
<|endoftext|>


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

package flare

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"runtime"
	"time"

	"github.com/go-chi/chi/middleware"

	"github.com/go-chi/chi"
	"github.com/go-kit/kit/log"
	"github.com/pkg/errors"

	"github.com/diegobernardes/flare/document"
	infraHTTP "github.com/diegobernardes/flare/infra/http"
	infraMiddleware "github.com/diegobernardes/flare/infra/http/middleware"
	"github.com/diegobernardes/flare/resource"
	"github.com/diegobernardes/flare/subscription"
)

type server struct {
	addr       string
	httpServer http.Server
	handler    struct {
		resource     *resource.Service
		subscription *subscription.Service
		document     *document.Service
	}
	middleware struct {
		timeout time.Duration
	}
	logger        log.Logger
	writeResponse func(http.ResponseWriter, interface{}, int, http.Header)
}

func (s *server) start() error {
	router, err := s.router()
	if err != nil {
		return errors.Wrap(err, "error during router initialization")
	}

	s.httpServer = http.Server{
		Addr:    s.addr,
		Handler: router,
	}

	go func() {
		if err := s.httpServer.ListenAndServe(); err != nil {
			if err == http.ErrServerClosed {
				return
			}
			fmt.Println(errors.Wrap(err, "error during server initialization").Error())

			process, err := os.FindProcess(os.Getpid())
			if err != nil {
				fmt.Println(errors.Wrap(err, "could not find flare process to exit").Error())
				os.Exit(1)
			}
			if err := process.Signal(os.Interrupt); err != nil {
				fmt.Println(errors.Wrap(err, "error during signal send to flare process").Error())
				os.Exit(1)
			}
		}
	}()

	return nil
}

func (s *server) stop() error {
	if err := s.httpServer.Shutdown(context.Background()); err != nil {
		return errors.Wrap(err, "error during server close")
	}
	return nil
}

func (s *server) router() (http.Handler, error) {
	r := chi.NewRouter()
	if err := s.initMiddleware(r); err != nil {
		return nil, errors.Wrap(err, "error during middleware initialization")
	}

	r.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) {
		s.writeResponse(w, map[string]interface{}{
			"error": map[string]interface{}{
				"status": http.StatusBadRequest,
				"title":  "method not allowed",
			},
		}, http.StatusBadRequest, nil)
	})

	r.NotFound(func(w http.ResponseWriter, r *http.Request) {
		s.writeResponse(w, map[string]interface{}{
			"error": map[string]interface{}{
				"status": http.StatusNotFound,
				"title":  "not found",
			},
		}, http.StatusNotFound, nil)
	})

	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		s.writeResponse(w, map[string]interface{}{
			"version":   Version,
			"commit":    Commit,
			"buildTime": BuildTime,
			"goVersion": runtime.Version(),
		}, http.StatusOK, nil)
	})

	r.Route("/resources", s.routerResource)
	r.Route("/resources/{resourceId}/subscriptions", s.routerSubscription)
	r.Route("/documents", s.routerDocument)

	return r, nil
}

func (s *server) initMiddleware(r chi.Router) error {
	logger := infraMiddleware.NewLog(s.logger)
	writer, err := infraHTTP.NewWriter(s.logger)
	if err != nil {
		return errors.New("error during writer initialization")
	}

	recoverMiddleware, err := infraMiddleware.NewRecover(s.logger, writer)
	if err != nil {
		return errors.New("error during recover middleware initialization")
	}

	r.Use(recoverMiddleware.Handler)
	r.Use(middleware.DefaultCompress)
	r.Use(middleware.RealIP)
	r.Use(middleware.RequestID)
	r.Use(middleware.StripSlashes)
	r.Use(middleware.Timeout(s.middleware.timeout))
	r.Use(logger.Handler)

	return nil
}

func (s *server) routerResource(r chi.Router) {
	r.Get("/", s.handler.resource.HandleIndex)
	r.Post("/", s.handler.resource.HandleCreate)
	r.Get("/{id}", s.handler.resource.HandleShow)
	r.Delete("/{id}", s.handler.resource.HandleDelete)
}

func (s *server) routerSubscription(r chi.Router) {
	r.Get("/", s.handler.subscription.HandleIndex)
	r.Post("/", s.handler.subscription.HandleCreate)
	r.Get("/{id}", s.handler.subscription.HandleShow)
	r.Delete("/{id}", s.handler.subscription.HandleDelete)
}

func (s *server) routerDocument(r chi.Router) {
	r.Get("/*", s.handler.document.HandleShow)
	r.Put("/*", s.handler.document.HandleUpdate)
	r.Delete("/*", s.handler.document.HandleDelete)
}

func newServer(options ...func(*server)) (*server, error) {
	s := &server{}

	for _, option := range options {
		option(s)
	}

	if s.addr == "" {
		s.addr = ":8080"
	}

	if s.handler.resource == nil {
		return nil, errors.New("missing handler.resource")
	}

	if s.handler.subscription == nil {
		return nil, errors.New("missing handler.subscription")
	}

	if s.handler.document == nil {
		return nil, errors.New("missing handler.document")
	}

	if s.logger == nil {
		return nil, errors.New("missing logger")
	}

	s.writeResponse = infraHTTP.WriteResponse(s.logger)
	return s, nil
}

func serverAddr(addr string) func(*server) {
	return func(s *server) { s.addr = addr }
}

func serverHandlerResource(handler *resource.Service) func(*server) {
	return func(s *server) { s.handler.resource = handler }
}

func serverHandlerSubscription(handler *subscription.Service) func(*server) {
	return func(s *server) { s.handler.subscription = handler }
}

func serverHandlerDocument(handler *document.Service) func(*server) {
	return func(s *server) { s.handler.document = handler }
}

func serverLogger(logger log.Logger) func(*server) {
	return func(s *server) { s.logger = logger }
}

func serverMiddlewareTimeout(duration time.Duration) func(*server) {
	return func(s *server) { s.middleware.timeout = duration }
}
<commit_msg>server: fix import order<commit_after>// Copyright 2017 Diego Bernardes. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package flare

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"runtime"
	"time"

	"github.com/go-chi/chi"
	"github.com/go-chi/chi/middleware"
	"github.com/go-kit/kit/log"
	"github.com/pkg/errors"

	"github.com/diegobernardes/flare/document"
	infraHTTP "github.com/diegobernardes/flare/infra/http"
	infraMiddleware "github.com/diegobernardes/flare/infra/http/middleware"
	"github.com/diegobernardes/flare/resource"
	"github.com/diegobernardes/flare/subscription"
)

type server struct {
	addr       string
	httpServer http.Server
	handler    struct {
		resource     *resource.Service
		subscription *subscription.Service
		document     *document.Service
	}
	middleware struct {
		timeout time.Duration
	}
	logger        log.Logger
	writeResponse func(http.ResponseWriter, interface{}, int, http.Header)
}

func (s *server) start() error {
	router, err := s.router()
	if err != nil {
		return errors.Wrap(err, "error during router initialization")
	}

	s.httpServer = http.Server{
		Addr:    s.addr,
		Handler: router,
	}

	go func() {
		if err := s.httpServer.ListenAndServe(); err != nil {
			if err == http.ErrServerClosed {
				return
			}
			fmt.Println(errors.Wrap(err, "error during server initialization").Error())

			process, err := os.FindProcess(os.Getpid())
			if err != nil {
				fmt.Println(errors.Wrap(err, "could not find flare process to exit").Error())
				os.Exit(1)
			}
			if err := process.Signal(os.Interrupt); err != nil {
				fmt.Println(errors.Wrap(err, "error during signal send to flare process").Error())
				os.Exit(1)
			}
		}
	}()

	return nil
}

func (s *server) stop() error {
	if err := s.httpServer.Shutdown(context.Background()); err != nil {
		return errors.Wrap(err, "error during server close")
	}
	return nil
}

func (s *server) router() (http.Handler, error) {
	r := chi.NewRouter()
	if err := s.initMiddleware(r); err != nil {
		return nil, errors.Wrap(err, "error during middleware initialization")
	}

	r.MethodNotAllowed(func(w http.ResponseWriter, r *http.Request) {
		s.writeResponse(w, map[string]interface{}{
			"error": map[string]interface{}{
				"status": http.StatusBadRequest,
				"title":  "method not allowed",
			},
		}, http.StatusBadRequest, nil)
	})

	r.NotFound(func(w http.ResponseWriter, r *http.Request) {
		s.writeResponse(w, map[string]interface{}{
			"error": map[string]interface{}{
				"status": http.StatusNotFound,
				"title":  "not found",
			},
		}, http.StatusNotFound, nil)
	})

	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		s.writeResponse(w, map[string]interface{}{
			"version":   Version,
			"commit":    Commit,
			"buildTime": BuildTime,
			"goVersion": runtime.Version(),
		}, http.StatusOK, nil)
	})

	r.Route("/resources", s.routerResource)
	r.Route("/resources/{resourceId}/subscriptions", s.routerSubscription)
	r.Route("/documents", s.routerDocument)

	return r, nil
}

func (s *server) initMiddleware(r chi.Router) error {
	logger := infraMiddleware.NewLog(s.logger)
	writer, err := infraHTTP.NewWriter(s.logger)
	if err != nil {
		return errors.New("error during writer initialization")
	}

	recoverMiddleware, err := infraMiddleware.NewRecover(s.logger, writer)
	if err != nil {
		return errors.New("error during recover middleware initialization")
	}

	r.Use(recoverMiddleware.Handler)
	r.Use(middleware.DefaultCompress)
	r.Use(middleware.RealIP)
	r.Use(middleware.RequestID)
	r.Use(middleware.StripSlashes)
	r.Use(middleware.Timeout(s.middleware.timeout))
	r.Use(logger.Handler)

	return nil
}

func (s *server) routerResource(r chi.Router) {
	r.Get("/", s.handler.resource.HandleIndex)
	r.Post("/", s.handler.resource.HandleCreate)
	r.Get("/{id}", s.handler.resource.HandleShow)
	r.Delete("/{id}", s.handler.resource.HandleDelete)
}

func (s *server) routerSubscription(r chi.Router) {
	r.Get("/", s.handler.subscription.HandleIndex)
	r.Post("/", s.handler.subscription.HandleCreate)
	r.Get("/{id}", s.handler.subscription.HandleShow)
	r.Delete("/{id}", s.handler.subscription.HandleDelete)
}

func (s *server) routerDocument(r chi.Router) {
	r.Get("/*", s.handler.document.HandleShow)
	r.Put("/*", s.handler.document.HandleUpdate)
	r.Delete("/*", s.handler.document.HandleDelete)
}

func newServer(options ...func(*server)) (*server, error) {
	s := &server{}

	for _, option := range options {
		option(s)
	}

	if s.addr == "" {
		s.addr = ":8080"
	}

	if s.handler.resource == nil {
		return nil, errors.New("missing handler.resource")
	}

	if s.handler.subscription == nil {
		return nil, errors.New("missing handler.subscription")
	}

	if s.handler.document == nil {
		return nil, errors.New("missing handler.document")
	}

	if s.logger == nil {
		return nil, errors.New("missing logger")
	}

	s.writeResponse = infraHTTP.WriteResponse(s.logger)
	return s, nil
}

func serverAddr(addr string) func(*server) {
	return func(s *server) { s.addr = addr }
}

func serverHandlerResource(handler *resource.Service) func(*server) {
	return func(s *server) { s.handler.resource = handler }
}

func serverHandlerSubscription(handler *subscription.Service) func(*server) {
	return func(s *server) { s.handler.subscription = handler }
}

func serverHandlerDocument(handler *document.Service) func(*server) {
	return func(s *server) { s.handler.document = handler }
}

func serverLogger(logger log.Logger) func(*server) {
	return func(s *server) { s.logger = logger }
}

func serverMiddlewareTimeout(duration time.Duration) func(*server) {
	return func(s *server) { s.middleware.timeout = duration }
}
<|endoftext|>


<commit_before>// Copyright 2019 Google LLC
//
// 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
//
//     https://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 v1alpha1

import (
	"fmt"

	serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
	servicecatalogv1beta1 "github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"knative.dev/pkg/apis"
	duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
)

// GetGroupVersionKind returns the GroupVersionKind.
func (r *App) GetGroupVersionKind() schema.GroupVersionKind {
	return SchemeGroupVersion.WithKind("App")
}

// ConditionType represents a Service condition value
const (
	// AppConditionReady is set when the app is configured
	// and is usable by developers.
	AppConditionReady = apis.ConditionReady
	// AppConditionSourceReady is set when the build is ready.
	AppConditionSourceReady apis.ConditionType = "SourceReady"
	// AppConditionKnativeServiceReady is set when service is ready.
	AppConditionKnativeServiceReady apis.ConditionType = "KnativeServiceReady"
	// AppConditionSpaceReady is used to indicate when the space has an error that
	// causes apps to not reconcile correctly.
	AppConditionSpaceReady apis.ConditionType = "SpaceReady"
	// AppConditionRouteReady is set when route is ready.
	AppConditionRouteReady apis.ConditionType = "RouteReady"
	// AppConditionEnvVarSecretReady is set when env var secret is ready.
	AppConditionEnvVarSecretReady apis.ConditionType = "EnvVarSecretReady"
	// AppConditionServiceBindingsReady is set when all service bindings are ready.
	AppConditionServiceBindingsReady apis.ConditionType = "ServiceBindingsReady"
)

func (status *AppStatus) manage() apis.ConditionManager {
	return apis.NewLivingConditionSet(
		AppConditionSourceReady,
		AppConditionKnativeServiceReady,
		AppConditionSpaceReady,
		AppConditionEnvVarSecretReady,
	).Manage(status)
}

// IsReady looks at the conditions to see if they are happy.
func (status *AppStatus) IsReady() bool {
	return status.manage().IsHappy()
}

// GetCondition returns the condition by name.
func (status *AppStatus) GetCondition(t apis.ConditionType) *apis.Condition {
	return status.manage().GetCondition(t)
}

// InitializeConditions sets the initial values to the conditions.
func (status *AppStatus) InitializeConditions() {
	status.manage().InitializeConditions()
}

// SourceCondition gets a manager for the state of the source.
func (status *AppStatus) SourceCondition() SingleConditionManager {
	return NewSingleConditionManager(status.manage(), AppConditionSourceReady, "Source")
}

// KnativeServiceCondition gets a manager for the state of the Knative Service.
func (status *AppStatus) KnativeServiceCondition() SingleConditionManager {
	return NewSingleConditionManager(status.manage(), AppConditionKnativeServiceReady, "Knative Service")
}

// RouteCondition gets a manager for the state of the kf Route.
func (status *AppStatus) RouteCondition() SingleConditionManager {
	return NewSingleConditionManager(status.manage(), AppConditionRouteReady, "Route")
}

// EnvVarSecretCondition gets a manager for the state of the env var secret.
func (status *AppStatus) EnvVarSecretCondition() SingleConditionManager {
	return NewSingleConditionManager(status.manage(), AppConditionEnvVarSecretReady, "Env Var Secret")
}

// ServiceBindingCondition gets a manager for the state of the service bindings.
func (status *AppStatus) ServiceBindingCondition() SingleConditionManager {
	return NewSingleConditionManager(status.manage(), AppConditionServiceBindingsReady, "Service Bindings")
}

// PropagateSourceStatus copies the source status to the app's.
func (status *AppStatus) PropagateSourceStatus(source *Source) {
	status.LatestCreatedSourceName = source.Name

	cond := source.Status.GetCondition(SourceConditionSucceeded)
	if PropagateCondition(status.manage(), AppConditionSourceReady, cond) {
		status.LatestReadySourceName = source.Name
		status.SourceStatusFields = source.Status.SourceStatusFields
	}
}

// PropagateKnativeServiceStatus updates the Knative service status to reflect
// the underlying service.
func (status *AppStatus) PropagateKnativeServiceStatus(service *serving.Service) {
	// Stopped apps don't have a Knative service
	if service == nil {
		return
	}

	cond := service.Status.GetCondition(apis.ConditionReady)

	if PropagateCondition(status.manage(), AppConditionKnativeServiceReady, cond) {
		status.ConfigurationStatusFields = service.Status.ConfigurationStatusFields
		status.RouteStatusFields = service.Status.RouteStatusFields
	}
}

// PropagateEnvVarSecretStatus updates the env var secret readiness status.
func (status *AppStatus) PropagateEnvVarSecretStatus(secret *v1.Secret) {
	status.manage().MarkTrue(AppConditionEnvVarSecretReady)
}

// serviceBindingConditionType creates a Conditiontype for a ServiceBinding.
func serviceBindingConditionType(binding *servicecatalogv1beta1.ServiceBinding) (apis.ConditionType, error) {
	serviceInstance, ok := binding.Labels[ComponentLabel]
	if !ok {
		return "", fmt.Errorf("binding %s is missing the label %s", binding.Name, ComponentLabel)
	}

	return apis.ConditionType(fmt.Sprintf("%sReady", serviceInstance)), nil
}

// PropagateServiceBindingsStatus updates the service binding readiness status.
func (status *AppStatus) PropagateServiceBindingsStatus(bindings []servicecatalogv1beta1.ServiceBinding) {

	// Gather binding names
	var bindingNames []string
	for _, binding := range bindings {
		bindingNames = append(bindingNames, binding.Name)
	}
	status.ServiceBindingNames = bindingNames

	// Gather binding conditions
	var conditionTypes []apis.ConditionType
	for _, binding := range bindings {
		conditionType, err := serviceBindingConditionType(&binding)
		if err != nil {
			status.manage().MarkFalse(AppConditionServiceBindingsReady, "Error", "%v", err)
			return
		}

		conditionTypes = append(conditionTypes, conditionType)
	}

	duckStatus := &duckv1beta1.Status{}
	manager := apis.NewLivingConditionSet(conditionTypes...).Manage(duckStatus)
	manager.InitializeConditions()

	for _, binding := range bindings {
		if binding.Generation != binding.Status.ReconciledGeneration {

			// this binding's conditions are out of date.
			continue
		}

		for _, cond := range binding.Status.Conditions {
			if cond.Type != servicecatalogv1beta1.ServiceBindingConditionReady {
				continue
			}

			conditionType, err := serviceBindingConditionType(&binding)
			if err != nil {
				status.manage().MarkFalse(AppConditionServiceBindingsReady, "Error", "%v", err)
				return
			}
			switch v1.ConditionStatus(cond.Status) {
			case v1.ConditionTrue:
				manager.MarkTrue(conditionType)
			case v1.ConditionFalse:
				manager.MarkFalse(conditionType, cond.Reason, cond.Message)
			case v1.ConditionUnknown:
				manager.MarkUnknown(conditionType, cond.Reason, cond.Message)
			}
		}
	}

	// Copy Ready condition
	PropagateCondition(status.manage(), AppConditionServiceBindingsReady, manager.GetCondition(apis.ConditionReady))
}

// MarkSpaceHealthy notes that the space was able to be retrieved and
// defaults can be applied from it.
func (status *AppStatus) MarkSpaceHealthy() {
	status.manage().MarkTrue(AppConditionSpaceReady)
}

// MarkSpaceUnhealthy notes that the space was could not be retrieved.
func (status *AppStatus) MarkSpaceUnhealthy(reason, message string) {
	status.manage().MarkFalse(AppConditionSpaceReady, reason, message)
}

func (status *AppStatus) duck() *duckv1beta1.Status {
	return &status.Status
}
<commit_msg>add check for no service bindings (#617)<commit_after>// Copyright 2019 Google LLC
//
// 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
//
//     https://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 v1alpha1

import (
	"fmt"

	serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
	servicecatalogv1beta1 "github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"knative.dev/pkg/apis"
	duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
)

// GetGroupVersionKind returns the GroupVersionKind.
func (r *App) GetGroupVersionKind() schema.GroupVersionKind {
	return SchemeGroupVersion.WithKind("App")
}

// ConditionType represents a Service condition value
const (
	// AppConditionReady is set when the app is configured
	// and is usable by developers.
	AppConditionReady = apis.ConditionReady
	// AppConditionSourceReady is set when the build is ready.
	AppConditionSourceReady apis.ConditionType = "SourceReady"
	// AppConditionKnativeServiceReady is set when service is ready.
	AppConditionKnativeServiceReady apis.ConditionType = "KnativeServiceReady"
	// AppConditionSpaceReady is used to indicate when the space has an error that
	// causes apps to not reconcile correctly.
	AppConditionSpaceReady apis.ConditionType = "SpaceReady"
	// AppConditionRouteReady is set when route is ready.
	AppConditionRouteReady apis.ConditionType = "RouteReady"
	// AppConditionEnvVarSecretReady is set when env var secret is ready.
	AppConditionEnvVarSecretReady apis.ConditionType = "EnvVarSecretReady"
	// AppConditionServiceBindingsReady is set when all service bindings are ready.
	AppConditionServiceBindingsReady apis.ConditionType = "ServiceBindingsReady"
)

func (status *AppStatus) manage() apis.ConditionManager {
	return apis.NewLivingConditionSet(
		AppConditionSourceReady,
		AppConditionKnativeServiceReady,
		AppConditionSpaceReady,
		AppConditionEnvVarSecretReady,
	).Manage(status)
}

// IsReady looks at the conditions to see if they are happy.
func (status *AppStatus) IsReady() bool {
	return status.manage().IsHappy()
}

// GetCondition returns the condition by name.
func (status *AppStatus) GetCondition(t apis.ConditionType) *apis.Condition {
	return status.manage().GetCondition(t)
}

// InitializeConditions sets the initial values to the conditions.
func (status *AppStatus) InitializeConditions() {
	status.manage().InitializeConditions()
}

// SourceCondition gets a manager for the state of the source.
func (status *AppStatus) SourceCondition() SingleConditionManager {
	return NewSingleConditionManager(status.manage(), AppConditionSourceReady, "Source")
}

// KnativeServiceCondition gets a manager for the state of the Knative Service.
func (status *AppStatus) KnativeServiceCondition() SingleConditionManager {
	return NewSingleConditionManager(status.manage(), AppConditionKnativeServiceReady, "Knative Service")
}

// RouteCondition gets a manager for the state of the kf Route.
func (status *AppStatus) RouteCondition() SingleConditionManager {
	return NewSingleConditionManager(status.manage(), AppConditionRouteReady, "Route")
}

// EnvVarSecretCondition gets a manager for the state of the env var secret.
func (status *AppStatus) EnvVarSecretCondition() SingleConditionManager {
	return NewSingleConditionManager(status.manage(), AppConditionEnvVarSecretReady, "Env Var Secret")
}

// ServiceBindingCondition gets a manager for the state of the service bindings.
func (status *AppStatus) ServiceBindingCondition() SingleConditionManager {
	return NewSingleConditionManager(status.manage(), AppConditionServiceBindingsReady, "Service Bindings")
}

// PropagateSourceStatus copies the source status to the app's.
func (status *AppStatus) PropagateSourceStatus(source *Source) {
	status.LatestCreatedSourceName = source.Name

	cond := source.Status.GetCondition(SourceConditionSucceeded)
	if PropagateCondition(status.manage(), AppConditionSourceReady, cond) {
		status.LatestReadySourceName = source.Name
		status.SourceStatusFields = source.Status.SourceStatusFields
	}
}

// PropagateKnativeServiceStatus updates the Knative service status to reflect
// the underlying service.
func (status *AppStatus) PropagateKnativeServiceStatus(service *serving.Service) {
	// Stopped apps don't have a Knative service
	if service == nil {
		return
	}

	cond := service.Status.GetCondition(apis.ConditionReady)

	if PropagateCondition(status.manage(), AppConditionKnativeServiceReady, cond) {
		status.ConfigurationStatusFields = service.Status.ConfigurationStatusFields
		status.RouteStatusFields = service.Status.RouteStatusFields
	}
}

// PropagateEnvVarSecretStatus updates the env var secret readiness status.
func (status *AppStatus) PropagateEnvVarSecretStatus(secret *v1.Secret) {
	status.manage().MarkTrue(AppConditionEnvVarSecretReady)
}

// serviceBindingConditionType creates a Conditiontype for a ServiceBinding.
func serviceBindingConditionType(binding *servicecatalogv1beta1.ServiceBinding) (apis.ConditionType, error) {
	serviceInstance, ok := binding.Labels[ComponentLabel]
	if !ok {
		return "", fmt.Errorf("binding %s is missing the label %s", binding.Name, ComponentLabel)
	}

	return apis.ConditionType(fmt.Sprintf("%sReady", serviceInstance)), nil
}

// PropagateServiceBindingsStatus updates the service binding readiness status.
func (status *AppStatus) PropagateServiceBindingsStatus(bindings []servicecatalogv1beta1.ServiceBinding) {

	// Gather binding names
	var bindingNames []string
	for _, binding := range bindings {
		bindingNames = append(bindingNames, binding.Name)
	}
	status.ServiceBindingNames = bindingNames

	// Gather binding conditions
	var conditionTypes []apis.ConditionType
	for _, binding := range bindings {
		conditionType, err := serviceBindingConditionType(&binding)
		if err != nil {
			status.manage().MarkFalse(AppConditionServiceBindingsReady, "Error", "%v", err)
			return
		}

		conditionTypes = append(conditionTypes, conditionType)
	}

	duckStatus := &duckv1beta1.Status{}
	manager := apis.NewLivingConditionSet(conditionTypes...).Manage(duckStatus)
	manager.InitializeConditions()

	for _, binding := range bindings {
		if binding.Generation != binding.Status.ReconciledGeneration {

			// this binding's conditions are out of date.
			continue
		}

		for _, cond := range binding.Status.Conditions {
			if cond.Type != servicecatalogv1beta1.ServiceBindingConditionReady {
				continue
			}

			conditionType, err := serviceBindingConditionType(&binding)
			if err != nil {
				status.manage().MarkFalse(AppConditionServiceBindingsReady, "Error", "%v", err)
				return
			}
			switch v1.ConditionStatus(cond.Status) {
			case v1.ConditionTrue:
				manager.MarkTrue(conditionType)
			case v1.ConditionFalse:
				manager.MarkFalse(conditionType, cond.Reason, cond.Message)
			case v1.ConditionUnknown:
				manager.MarkUnknown(conditionType, cond.Reason, cond.Message)
			}
		}
	}

	// if there are no bindings, set the happy condition to true
	if len(bindings) == 0 {
		manager.MarkTrue(apis.ConditionReady)
	}

	// Copy Ready condition
	PropagateCondition(status.manage(), AppConditionServiceBindingsReady, manager.GetCondition(apis.ConditionReady))
	status.ServiceBindingConditions = duckStatus.Conditions
}

// MarkSpaceHealthy notes that the space was able to be retrieved and
// defaults can be applied from it.
func (status *AppStatus) MarkSpaceHealthy() {
	status.manage().MarkTrue(AppConditionSpaceReady)
}

// MarkSpaceUnhealthy notes that the space was could not be retrieved.
func (status *AppStatus) MarkSpaceUnhealthy(reason, message string) {
	status.manage().MarkFalse(AppConditionSpaceReady, reason, message)
}

func (status *AppStatus) duck() *duckv1beta1.Status {
	return &status.Status
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Knative 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 scaling

import (
	"context"
	"math"
	"sync"
	"time"

	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"knative.dev/pkg/logging"
	av1alpha1 "knative.dev/serving/pkg/apis/autoscaling/v1alpha1"
	"knative.dev/serving/pkg/autoscaler/metrics"
)

// tickInterval is how often the Autoscaler evaluates the metrics
// and issues a decision.
const tickInterval = 2 * time.Second

// Decider is a resource which observes the request load of a Revision and
// recommends a number of replicas to run.
// +k8s:deepcopy-gen=true
type Decider struct {
	metav1.ObjectMeta
	Spec   DeciderSpec
	Status DeciderStatus
}

// DeciderSpec is the parameters by which the Revision should be scaled.
type DeciderSpec struct {
	MaxScaleUpRate   float64
	MaxScaleDownRate float64
	// The metric used for scaling, i.e. concurrency, rps.
	ScalingMetric string
	// The value of scaling metric per pod that we target to maintain.
	// TargetValue <= TotalValue.
	TargetValue float64
	// The total value of scaling metric that a pod can maintain.
	TotalValue float64
	// The burst capacity that user wants to maintain without queuing at the POD level.
	// Note, that queueing still might happen due to the non-ideal load balancing.
	TargetBurstCapacity float64
	// ActivatorCapacity is the single activator capacity, for subsetting.
	ActivatorCapacity float64
	// PanicThreshold is the threshold at which panic mode is entered. It represents
	// a factor of the currently observed load over the panic window over the ready
	// pods. I.e. if this is 2, panic mode will be entered if the observed metric
	// is twice as high as the current population can handle.
	PanicThreshold float64
	// StableWindow is needed to determine when to exit panic mode.
	StableWindow time.Duration
	// ScaleDownDelay is the time that must pass at reduced concurrency before a
	// scale-down decision is applied.
	ScaleDownDelay time.Duration
	// InitialScale is the calculated initial scale of the revision, taking both
	// revision initial scale and cluster initial scale into account. Revision initial
	// scale overrides cluster initial scale.
	InitialScale int32
	// Reachable describes whether the revision is referenced by any route.
	Reachable bool
}

// DeciderStatus is the current scale recommendation.
type DeciderStatus struct {
	// DesiredScale is the target number of instances that autoscaler
	// this revision needs.
	DesiredScale int32

	// ExcessBurstCapacity is the difference between spare capacity
	// (how much more load the pods in the revision deployment can take before being
	// overloaded) and the configured target burst capacity.
	// If this number is negative: Activator will be threaded in
	// the request path by the PodAutoscaler controller.
	ExcessBurstCapacity int32

	// NumActivators is the computed number of activators
	// necessary to back the revision.
	NumActivators int32
}

// ScaleResult holds the scale result of the UniScaler evaluation cycle.
type ScaleResult struct {
	// DesiredPodCount is the number of pods Autoscaler suggests for the revision.
	DesiredPodCount int32
	// ExcessBurstCapacity is computed headroom of the revision taking into
	// the account target burst capacity.
	ExcessBurstCapacity int32
	// NumActivators is the number of activators required to back this revision.
	NumActivators int32
	// ScaleValid specifies whether this scale result is valid, i.e. whether
	// Autoscaler had all the necessary information to compute a suggestion.
	ScaleValid bool
}

var invalidSR = ScaleResult{
	ScaleValid:    false,
	NumActivators: MinActivators,
}

// UniScaler records statistics for a particular Decider and proposes the scale for the Decider's target based on those statistics.
type UniScaler interface {
	// Scale computes a scaling suggestion for a revision.
	Scale(*zap.SugaredLogger, time.Time) ScaleResult

	// Update reconfigures the UniScaler according to the DeciderSpec.
	Update(*DeciderSpec)
}

// UniScalerFactory creates a UniScaler for a given PA using the given dynamic configuration.
type UniScalerFactory func(*Decider) (UniScaler, error)

// scalerRunner wraps a UniScaler and a channel for implementing shutdown behavior.
type scalerRunner struct {
	scaler UniScaler
	stopCh chan struct{}
	pokeCh chan struct{}
	logger *zap.SugaredLogger

	// mux guards access to decider.
	mux     sync.RWMutex
	decider *Decider
}

func (sr *scalerRunner) latestScale() int32 {
	sr.mux.RLock()
	defer sr.mux.RUnlock()
	return sr.decider.Status.DesiredScale
}

func sameSign(a, b int32) bool {
	return (a&math.MinInt32)^(b&math.MinInt32) == 0
}

// decider returns a thread safe deep copy of the owned decider.
func (sr *scalerRunner) safeDecider() *Decider {
	sr.mux.RLock()
	defer sr.mux.RUnlock()
	return sr.decider.DeepCopy()
}

func (sr *scalerRunner) updateLatestScale(sRes ScaleResult) bool {
	ret := false
	sr.mux.Lock()
	defer sr.mux.Unlock()
	if sr.decider.Status.DesiredScale != sRes.DesiredPodCount {
		sr.decider.Status.DesiredScale = sRes.DesiredPodCount
		ret = true
	}
	if sr.decider.Status.NumActivators != sRes.NumActivators {
		sr.decider.Status.NumActivators = sRes.NumActivators
		ret = true
	}

	// If sign has changed -- then we have to update KPA.
	ret = ret || !sameSign(sr.decider.Status.ExcessBurstCapacity, sRes.ExcessBurstCapacity)

	// Update with the latest calculation anyway.
	sr.decider.Status.ExcessBurstCapacity = sRes.ExcessBurstCapacity
	return ret
}

// MultiScaler maintains a collection of UniScalers.
type MultiScaler struct {
	scalersMutex sync.RWMutex
	scalers      map[types.NamespacedName]*scalerRunner

	scalersStopCh <-chan struct{}

	uniScalerFactory UniScalerFactory

	logger *zap.SugaredLogger

	watcherMutex sync.RWMutex
	watcher      func(types.NamespacedName)

	tickProvider func(time.Duration) *time.Ticker
}

// NewMultiScaler constructs a MultiScaler.
func NewMultiScaler(
	stopCh <-chan struct{},
	uniScalerFactory UniScalerFactory,
	logger *zap.SugaredLogger) *MultiScaler {
	return &MultiScaler{
		scalers:          make(map[types.NamespacedName]*scalerRunner),
		scalersStopCh:    stopCh,
		uniScalerFactory: uniScalerFactory,
		logger:           logger,
		tickProvider:     time.NewTicker,
	}
}

// Get returns the copy of the current Decider.
func (m *MultiScaler) Get(_ context.Context, namespace, name string) (*Decider, error) {
	key := types.NamespacedName{Namespace: namespace, Name: name}
	m.scalersMutex.RLock()
	defer m.scalersMutex.RUnlock()
	scaler, exists := m.scalers[key]
	if !exists {
		// This GroupResource is a lie, but unfortunately this interface requires one.
		return nil, errors.NewNotFound(av1alpha1.Resource("Deciders"), key.String())
	}
	return scaler.safeDecider(), nil
}

// Create instantiates the desired Decider.
func (m *MultiScaler) Create(ctx context.Context, decider *Decider) (*Decider, error) {
	key := types.NamespacedName{Namespace: decider.Namespace, Name: decider.Name}
	m.scalersMutex.Lock()
	defer m.scalersMutex.Unlock()
	scaler, exists := m.scalers[key]
	if !exists {
		var err error
		scaler, err = m.createScaler(ctx, decider)
		if err != nil {
			return nil, err
		}
		m.scalers[key] = scaler
	}
	return scaler.safeDecider(), nil
}

// Update applies the desired DeciderSpec to a currently running Decider.
func (m *MultiScaler) Update(_ context.Context, decider *Decider) (*Decider, error) {
	key := types.NamespacedName{Namespace: decider.Namespace, Name: decider.Name}
	m.scalersMutex.Lock()
	defer m.scalersMutex.Unlock()
	if scaler, exists := m.scalers[key]; exists {
		scaler.mux.Lock()
		defer scaler.mux.Unlock()
		// Make sure we store the copy.
		scaler.decider = decider.DeepCopy()
		scaler.scaler.Update(&decider.Spec)
		return decider, nil
	}
	// This GroupResource is a lie, but unfortunately this interface requires one.
	return nil, errors.NewNotFound(av1alpha1.Resource("Deciders"), key.String())
}

// Delete stops and removes a Decider.
func (m *MultiScaler) Delete(_ context.Context, namespace, name string) {
	key := types.NamespacedName{Namespace: namespace, Name: name}
	m.scalersMutex.Lock()
	defer m.scalersMutex.Unlock()
	if scaler, exists := m.scalers[key]; exists {
		close(scaler.stopCh)
		delete(m.scalers, key)
	}
}

// Watch registers a singleton function to call when DeciderStatus is updated.
func (m *MultiScaler) Watch(fn func(types.NamespacedName)) {
	m.watcherMutex.Lock()
	defer m.watcherMutex.Unlock()

	if m.watcher != nil {
		m.logger.Fatal("Multiple calls to Watch() not supported")
	}
	m.watcher = fn
}

// Inform sends an update to the registered watcher function, if it is set.
func (m *MultiScaler) Inform(event types.NamespacedName) bool {
	m.watcherMutex.RLock()
	defer m.watcherMutex.RUnlock()

	if m.watcher != nil {
		m.watcher(event)
		return true
	}
	return false
}

func (m *MultiScaler) runScalerTicker(runner *scalerRunner) {
	metricKey := types.NamespacedName{Namespace: runner.decider.Namespace, Name: runner.decider.Name}
	ticker := m.tickProvider(tickInterval)
	go func() {
		defer ticker.Stop()
		for {
			select {
			case <-m.scalersStopCh:
				return
			case <-runner.stopCh:
				return
			case <-ticker.C:
				m.tickScaler(runner.scaler, runner, metricKey)
			case <-runner.pokeCh:
				m.tickScaler(runner.scaler, runner, metricKey)
			}
		}
	}()
}

func (m *MultiScaler) createScaler(ctx context.Context, decider *Decider) (*scalerRunner, error) {
	d := decider.DeepCopy()
	scaler, err := m.uniScalerFactory(d)
	if err != nil {
		return nil, err
	}

	runner := &scalerRunner{
		scaler:  scaler,
		stopCh:  make(chan struct{}),
		decider: d,
		pokeCh:  make(chan struct{}),
		logger:  logging.FromContext(ctx),
	}
	d.Status.DesiredScale = -1
	switch tbc := d.Spec.TargetBurstCapacity; tbc {
	case -1, 0:
		d.Status.ExcessBurstCapacity = int32(tbc)
	default:
		// If TBC > Target * InitialScale, then we know initial
		// scale won't be enough to cover TBC and we'll be behind activator.
		d.Status.ExcessBurstCapacity = int32(float64(d.Spec.InitialScale)*d.Spec.TotalValue - tbc)
	}

	m.runScalerTicker(runner)
	return runner, nil
}

func (m *MultiScaler) tickScaler(scaler UniScaler, runner *scalerRunner, metricKey types.NamespacedName) {
	sr := scaler.Scale(runner.logger, time.Now())

	if !sr.ScaleValid {
		return
	}

	if runner.updateLatestScale(sr) {
		m.Inform(metricKey)
	}
}

// Poke checks if the autoscaler needs to be run immediately.
func (m *MultiScaler) Poke(key types.NamespacedName, stat metrics.Stat) {
	m.scalersMutex.RLock()
	defer m.scalersMutex.RUnlock()

	scaler, exists := m.scalers[key]
	if !exists {
		return
	}

	if scaler.latestScale() == 0 && stat.AverageConcurrentRequests != 0 {
		scaler.pokeCh <- struct{}{}
	}
}
<commit_msg>Further remove usage of the context in the scale runner (#10653)<commit_after>/*
Copyright 2018 The Knative 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 scaling

import (
	"context"
	"math"
	"sync"
	"time"

	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"knative.dev/pkg/logging/logkey"
	av1alpha1 "knative.dev/serving/pkg/apis/autoscaling/v1alpha1"
	"knative.dev/serving/pkg/autoscaler/metrics"
)

// tickInterval is how often the Autoscaler evaluates the metrics
// and issues a decision.
const tickInterval = 2 * time.Second

// Decider is a resource which observes the request load of a Revision and
// recommends a number of replicas to run.
// +k8s:deepcopy-gen=true
type Decider struct {
	metav1.ObjectMeta
	Spec   DeciderSpec
	Status DeciderStatus
}

// DeciderSpec is the parameters by which the Revision should be scaled.
type DeciderSpec struct {
	MaxScaleUpRate   float64
	MaxScaleDownRate float64
	// The metric used for scaling, i.e. concurrency, rps.
	ScalingMetric string
	// The value of scaling metric per pod that we target to maintain.
	// TargetValue <= TotalValue.
	TargetValue float64
	// The total value of scaling metric that a pod can maintain.
	TotalValue float64
	// The burst capacity that user wants to maintain without queuing at the POD level.
	// Note, that queueing still might happen due to the non-ideal load balancing.
	TargetBurstCapacity float64
	// ActivatorCapacity is the single activator capacity, for subsetting.
	ActivatorCapacity float64
	// PanicThreshold is the threshold at which panic mode is entered. It represents
	// a factor of the currently observed load over the panic window over the ready
	// pods. I.e. if this is 2, panic mode will be entered if the observed metric
	// is twice as high as the current population can handle.
	PanicThreshold float64
	// StableWindow is needed to determine when to exit panic mode.
	StableWindow time.Duration
	// ScaleDownDelay is the time that must pass at reduced concurrency before a
	// scale-down decision is applied.
	ScaleDownDelay time.Duration
	// InitialScale is the calculated initial scale of the revision, taking both
	// revision initial scale and cluster initial scale into account. Revision initial
	// scale overrides cluster initial scale.
	InitialScale int32
	// Reachable describes whether the revision is referenced by any route.
	Reachable bool
}

// DeciderStatus is the current scale recommendation.
type DeciderStatus struct {
	// DesiredScale is the target number of instances that autoscaler
	// this revision needs.
	DesiredScale int32

	// ExcessBurstCapacity is the difference between spare capacity
	// (how much more load the pods in the revision deployment can take before being
	// overloaded) and the configured target burst capacity.
	// If this number is negative: Activator will be threaded in
	// the request path by the PodAutoscaler controller.
	ExcessBurstCapacity int32

	// NumActivators is the computed number of activators
	// necessary to back the revision.
	NumActivators int32
}

// ScaleResult holds the scale result of the UniScaler evaluation cycle.
type ScaleResult struct {
	// DesiredPodCount is the number of pods Autoscaler suggests for the revision.
	DesiredPodCount int32
	// ExcessBurstCapacity is computed headroom of the revision taking into
	// the account target burst capacity.
	ExcessBurstCapacity int32
	// NumActivators is the number of activators required to back this revision.
	NumActivators int32
	// ScaleValid specifies whether this scale result is valid, i.e. whether
	// Autoscaler had all the necessary information to compute a suggestion.
	ScaleValid bool
}

var invalidSR = ScaleResult{
	ScaleValid:    false,
	NumActivators: MinActivators,
}

// UniScaler records statistics for a particular Decider and proposes the scale for the Decider's target based on those statistics.
type UniScaler interface {
	// Scale computes a scaling suggestion for a revision.
	Scale(*zap.SugaredLogger, time.Time) ScaleResult

	// Update reconfigures the UniScaler according to the DeciderSpec.
	Update(*DeciderSpec)
}

// UniScalerFactory creates a UniScaler for a given PA using the given dynamic configuration.
type UniScalerFactory func(*Decider) (UniScaler, error)

// scalerRunner wraps a UniScaler and a channel for implementing shutdown behavior.
type scalerRunner struct {
	scaler UniScaler
	stopCh chan struct{}
	pokeCh chan struct{}
	logger *zap.SugaredLogger

	// mux guards access to decider.
	mux     sync.RWMutex
	decider *Decider
}

func (sr *scalerRunner) latestScale() int32 {
	sr.mux.RLock()
	defer sr.mux.RUnlock()
	return sr.decider.Status.DesiredScale
}

func sameSign(a, b int32) bool {
	return (a&math.MinInt32)^(b&math.MinInt32) == 0
}

// decider returns a thread safe deep copy of the owned decider.
func (sr *scalerRunner) safeDecider() *Decider {
	sr.mux.RLock()
	defer sr.mux.RUnlock()
	return sr.decider.DeepCopy()
}

func (sr *scalerRunner) updateLatestScale(sRes ScaleResult) bool {
	ret := false
	sr.mux.Lock()
	defer sr.mux.Unlock()
	if sr.decider.Status.DesiredScale != sRes.DesiredPodCount {
		sr.decider.Status.DesiredScale = sRes.DesiredPodCount
		ret = true
	}
	if sr.decider.Status.NumActivators != sRes.NumActivators {
		sr.decider.Status.NumActivators = sRes.NumActivators
		ret = true
	}

	// If sign has changed -- then we have to update KPA.
	ret = ret || !sameSign(sr.decider.Status.ExcessBurstCapacity, sRes.ExcessBurstCapacity)

	// Update with the latest calculation anyway.
	sr.decider.Status.ExcessBurstCapacity = sRes.ExcessBurstCapacity
	return ret
}

// MultiScaler maintains a collection of UniScalers.
type MultiScaler struct {
	scalersMutex sync.RWMutex
	scalers      map[types.NamespacedName]*scalerRunner

	scalersStopCh <-chan struct{}

	uniScalerFactory UniScalerFactory

	logger *zap.SugaredLogger

	watcherMutex sync.RWMutex
	watcher      func(types.NamespacedName)

	tickProvider func(time.Duration) *time.Ticker
}

// NewMultiScaler constructs a MultiScaler.
func NewMultiScaler(
	stopCh <-chan struct{},
	uniScalerFactory UniScalerFactory,
	logger *zap.SugaredLogger) *MultiScaler {
	return &MultiScaler{
		scalers:          make(map[types.NamespacedName]*scalerRunner),
		scalersStopCh:    stopCh,
		uniScalerFactory: uniScalerFactory,
		logger:           logger,
		tickProvider:     time.NewTicker,
	}
}

// Get returns the copy of the current Decider.
func (m *MultiScaler) Get(_ context.Context, namespace, name string) (*Decider, error) {
	key := types.NamespacedName{Namespace: namespace, Name: name}
	m.scalersMutex.RLock()
	defer m.scalersMutex.RUnlock()
	scaler, exists := m.scalers[key]
	if !exists {
		// This GroupResource is a lie, but unfortunately this interface requires one.
		return nil, errors.NewNotFound(av1alpha1.Resource("Deciders"), key.String())
	}
	return scaler.safeDecider(), nil
}

// Create instantiates the desired Decider.
func (m *MultiScaler) Create(_ context.Context, decider *Decider) (*Decider, error) {
	key := types.NamespacedName{Namespace: decider.Namespace, Name: decider.Name}
	m.scalersMutex.Lock()
	defer m.scalersMutex.Unlock()
	scaler, exists := m.scalers[key]
	if !exists {
		var err error
		scaler, err = m.createScaler(decider, key)
		if err != nil {
			return nil, err
		}
		m.scalers[key] = scaler
	}
	return scaler.safeDecider(), nil
}

// Update applies the desired DeciderSpec to a currently running Decider.
func (m *MultiScaler) Update(_ context.Context, decider *Decider) (*Decider, error) {
	key := types.NamespacedName{Namespace: decider.Namespace, Name: decider.Name}
	m.scalersMutex.Lock()
	defer m.scalersMutex.Unlock()
	if scaler, exists := m.scalers[key]; exists {
		scaler.mux.Lock()
		defer scaler.mux.Unlock()
		// Make sure we store the copy.
		scaler.decider = decider.DeepCopy()
		scaler.scaler.Update(&decider.Spec)
		return decider, nil
	}
	// This GroupResource is a lie, but unfortunately this interface requires one.
	return nil, errors.NewNotFound(av1alpha1.Resource("Deciders"), key.String())
}

// Delete stops and removes a Decider.
func (m *MultiScaler) Delete(_ context.Context, namespace, name string) {
	key := types.NamespacedName{Namespace: namespace, Name: name}
	m.scalersMutex.Lock()
	defer m.scalersMutex.Unlock()
	if scaler, exists := m.scalers[key]; exists {
		close(scaler.stopCh)
		delete(m.scalers, key)
	}
}

// Watch registers a singleton function to call when DeciderStatus is updated.
func (m *MultiScaler) Watch(fn func(types.NamespacedName)) {
	m.watcherMutex.Lock()
	defer m.watcherMutex.Unlock()

	if m.watcher != nil {
		m.logger.Fatal("Multiple calls to Watch() not supported")
	}
	m.watcher = fn
}

// Inform sends an update to the registered watcher function, if it is set.
func (m *MultiScaler) Inform(event types.NamespacedName) bool {
	m.watcherMutex.RLock()
	defer m.watcherMutex.RUnlock()

	if m.watcher != nil {
		m.watcher(event)
		return true
	}
	return false
}

func (m *MultiScaler) runScalerTicker(runner *scalerRunner, metricKey types.NamespacedName) {
	ticker := m.tickProvider(tickInterval)
	go func() {
		defer ticker.Stop()
		for {
			select {
			case <-m.scalersStopCh:
				return
			case <-runner.stopCh:
				return
			case <-ticker.C:
				m.tickScaler(runner.scaler, runner, metricKey)
			case <-runner.pokeCh:
				m.tickScaler(runner.scaler, runner, metricKey)
			}
		}
	}()
}

func (m *MultiScaler) createScaler(decider *Decider, key types.NamespacedName) (*scalerRunner, error) {
	d := decider.DeepCopy()
	scaler, err := m.uniScalerFactory(d)
	if err != nil {
		return nil, err
	}

	runner := &scalerRunner{
		scaler:  scaler,
		stopCh:  make(chan struct{}),
		decider: d,
		pokeCh:  make(chan struct{}),
		logger:  m.logger.With(zap.String(logkey.Key, key.String())),
	}
	d.Status.DesiredScale = -1
	switch tbc := d.Spec.TargetBurstCapacity; tbc {
	case -1, 0:
		d.Status.ExcessBurstCapacity = int32(tbc)
	default:
		// If TBC > Target * InitialScale, then we know initial
		// scale won't be enough to cover TBC and we'll be behind activator.
		d.Status.ExcessBurstCapacity = int32(float64(d.Spec.InitialScale)*d.Spec.TotalValue - tbc)
	}

	m.runScalerTicker(runner, key)
	return runner, nil
}

func (m *MultiScaler) tickScaler(scaler UniScaler, runner *scalerRunner, metricKey types.NamespacedName) {
	sr := scaler.Scale(runner.logger, time.Now())

	if !sr.ScaleValid {
		return
	}

	if runner.updateLatestScale(sr) {
		m.Inform(metricKey)
	}
}

// Poke checks if the autoscaler needs to be run immediately.
func (m *MultiScaler) Poke(key types.NamespacedName, stat metrics.Stat) {
	m.scalersMutex.RLock()
	defer m.scalersMutex.RUnlock()

	scaler, exists := m.scalers[key]
	if !exists {
		return
	}

	if scaler.latestScale() == 0 && stat.AverageConcurrentRequests != 0 {
		scaler.pokeCh <- struct{}{}
	}
}
<|endoftext|>