<commit_before>package pipeline

import (
	"bytes"
	"fmt"
	"strings"
	"time"

	"github.com/influxdata/kapacitor/tick"
)

// The type of data that travels along an edge connecting two nodes in a Pipeline.
type EdgeType int

const (
	// No data is transfered
	NoEdge EdgeType = iota
	// Data is transfered immediately and one point at a time.
	StreamEdge
	// Data is transfered in batches as soon as it is ready.
	BatchEdge
	// Data is transfered as it is received from a map function.
	ReduceEdge
)

type ID int

func (e EdgeType) String() string {
	switch e {
	case StreamEdge:
		return "stream"
	case BatchEdge:
		return "batch"
	case ReduceEdge:
		return "reduce"
	default:
		return "unknown EdgeType"
	}
}

//Generic node in a pipeline
type Node interface {
	// List of parents of this node.
	Parents() []Node
	// List of children of this node.
	Children() []Node
	// Add a parent node only, does not add the child relation.
	addParent(p Node)
	// Links a child node by adding both the parent and child relation.
	linkChild(c Node)

	// Short description of the node does not need to be unique
	Desc() string

	// Friendly readable unique name of the node
	Name() string
	SetName(string)

	// Unique id for the node
	ID() ID
	setID(ID)

	// The type of input the node wants.
	Wants() EdgeType
	// The type of output the node provides.
	Provides() EdgeType

	// Helper methods for walking DAG
	tMark() bool
	setTMark(b bool)
	pMark() bool
	setPMark(b bool)
	setPipeline(*Pipeline)
	pipeline() *Pipeline

	// Return .dot string to graph DAG
	dot(buf *bytes.Buffer)
}

type node struct {
	p        *Pipeline
	desc     string
	name     string
	id       ID
	parents  []Node
	children []Node
	wants    EdgeType
	provides EdgeType
	tm       bool
	pm       bool
}

// tick:ignore
func (n *node) Desc() string {
	return n.desc
}

// tick:ignore
func (n *node) ID() ID {
	return n.id
}

func (n *node) setID(id ID) {
	n.id = id
}

// tick:ignore
func (n *node) Name() string {
	if n.name == "" {
		n.name = fmt.Sprintf("%s%d", n.Desc(), n.ID())
	}
	return n.name
}

// tick:ignore
func (n *node) SetName(name string) {
	n.name = name
}

// tick:ignore
func (n *node) Parents() []Node {
	return n.parents
}

// tick:ignore
func (n *node) Children() []Node {
	return n.children
}

func (n *node) addParent(c Node) {
	n.parents = append(n.parents, c)
}

func (n *node) linkChild(c Node) {
	c.setPipeline(n.p)
	n.p.assignID(c)
	n.children = append(n.children, c)
	c.addParent(n)
}

func (n *node) tMark() bool {
	return n.tm
}

func (n *node) setTMark(b bool) {
	n.tm = b
}

func (n *node) pMark() bool {
	return n.pm
}

func (n *node) setPMark(b bool) {
	n.pm = b
}

func (n *node) setPipeline(p *Pipeline) {
	n.p = p
}
func (n *node) pipeline() *Pipeline {
	return n.p
}

// tick:ignore
func (n *node) Wants() EdgeType {
	return n.wants
}

// tick:ignore
func (n *node) Provides() EdgeType {
	return n.provides
}

func (n *node) dot(buf *bytes.Buffer) {
	for _, c := range n.children {
		buf.Write([]byte(fmt.Sprintf("%s -> %s;\n", n.Name(), c.Name())))
	}
}

// Create a new stream of data that contains the internal statistics of the node.
// The interval represents how often to emit the statistics based on real time.
// This means the interval time is independent of the times of the data points the source node is receiving.
func (n *node) Stats(interval time.Duration) *StatsNode {
	stats := newStatsNode(n, interval)
	n.pipeline().addSource(stats)
	return stats
}

const nodeNameMarker = "NODE_NAME"
const intervalMarker = "INTERVAL"

// Helper function for creating an alert on low throughput, aka deadman's switch.
//
// - Threshold -- trigger alert if throughput drops below threshold in points/interval.
// - Interval -- how often to check the throughput.
// - Expressions -- optional list of expressions to also evaluate. Useful for time of day alerting.
//
// Example:
//    var data = stream.from()...
//    // Trigger critical alert if the throughput drops below 100 points per 10s and checked every 10s.
//    data.deadman(100.0, 10s)
//    //Do normal processing of data
//    data....
//
// The above is equivalent to this
// Example:
//    var data = stream.from()...
//    // Trigger critical alert if the throughput drops below 100 points per 10s and checked every 10s.
//    data.stats(10s)
//          .derivative('collected')
//              .unit(10s)
//              .nonNegative()
//          .alert()
//              .id('node \'stream0\' in task \'{{ .TaskName }}\'')
//              .message('{{ .ID }} is {{ if eq .Level "OK" }}alive{{ else }}dead{{ end }}: {{ index .Fields "collected" | printf "%0.3f" }} points/10s.')
//              .crit(lamdba: "collected" <= 100.0)
//    //Do normal processing of data
//    data....
//
// The `id` and `message` alert properties can be configured globally via the 'deadman' configuration section.
//
// Since the AlertNode is the last piece it can be further modified as normal.
// Example:
//    var data = stream.from()...
//    // Trigger critical alert if the throughput drops below 100 points per 1s and checked every 10s.
//    data.deadman(100.0, 10s).slack().channel('#dead_tasks')
//    //Do normal processing of data
//    data....
//
// You can specify additional lambda expressions to further constrain when the deadman's switch is triggered.
// Example:
//    var data = stream.from()...
//    // Trigger critical alert if the throughput drops below 100 points per 10s and checked every 10s.
//    // Only trigger the alert if the time of day is between 8am-5pm.
//    data.deadman(100.0, 10s, lambda: hour("time") >= 8 AND hour("time") <= 17)
//    //Do normal processing of data
//    data....
//
func (n *node) Deadman(threshold float64, interval time.Duration, expr ...tick.Node) *AlertNode {
	dn := n.Stats(interval).
		Derivative("collected").NonNegative()
	dn.Unit = interval

	an := dn.Alert()
	critExpr := &tick.BinaryNode{
		Operator: tick.TokenLessEqual,
		Left: &tick.ReferenceNode{
			Reference: "collected",
		},
		Right: &tick.NumberNode{
			IsFloat: true,
			Float64: threshold,
		},
	}
	// Add any additional expressions
	for _, e := range expr {
		critExpr = &tick.BinaryNode{
			Operator: tick.TokenAnd,
			Left:     critExpr,
			Right:    e,
		}
	}
	an.Crit = critExpr
	// Replace NODE_NAME with actual name of the node in the Id.
	an.Id = strings.Replace(n.pipeline().deadman.Id(), nodeNameMarker, n.Name(), 1)
	// Set the message on the alert node.
	an.Message = strings.Replace(n.pipeline().deadman.Message(), intervalMarker, interval.String(), 1)
	return an
}

// ---------------------------------
// Chaining methods
//

// basic implementation of node + chaining methods
type chainnode struct {
	node
}

func newBasicChainNode(desc string, wants, provides EdgeType) chainnode {
	return chainnode{node{
		desc:     desc,
		wants:    wants,
		provides: provides,
	}}
}

// Create a new node that filters the data stream by a given expression.
func (n *chainnode) Where(expression tick.Node) *WhereNode {
	w := newWhereNode(n.provides, expression)
	n.linkChild(w)
	return w
}

// Create an http output node that caches the most recent data it has received.
// The cached data is available at the given endpoint.
// The endpoint is the relative path from the API endpoint of the running task.
// For example if the task endpoint is at "/api/v1/task/<task_name>" and endpoint is
// "top10", then the data can be requested from "/api/v1/task/<task_name>/top10".
func (n *chainnode) HttpOut(endpoint string) *HTTPOutNode {
	h := newHTTPOutNode(n.provides, endpoint)
	n.linkChild(h)
	return h
}

// Create an influxdb output node that will store the incoming data into InfluxDB.
func (n *chainnode) InfluxDBOut() *InfluxDBOutNode {
	i := newInfluxDBOutNode(n.provides)
	n.linkChild(i)
	return i
}

// Create an alert node, which can trigger alerts.
func (n *chainnode) Alert() *AlertNode {
	a := newAlertNode(n.provides)
	n.linkChild(a)
	return a
}

// Perform the union of this node and all other given nodes.
func (n *chainnode) Union(node ...Node) *UnionNode {
	u := newUnionNode(n.provides, node)
	n.linkChild(u)
	return u
}

// Join this node with other nodes. The data is joined on timestamp.
func (n *chainnode) Join(others ...Node) *JoinNode {
	others = append([]Node{n}, others...)
	j := newJoinNode(n.provides, others)
	return j
}

// Create an eval node that will evaluate the given transformation function to each data point.
//  A list of expressions may be provided and will be evaluated in the order they are given
// and results of previous expressions are made available to later expressions.
func (n *chainnode) Eval(expressions ...tick.Node) *EvalNode {
	e := newEvalNode(n.provides, expressions)
	n.linkChild(e)
	return e
}

// Group the data by a set of tags.
//
// Can pass literal * to group by all dimensions.
// Example:
//    .groupBy(*)
//
func (n *chainnode) GroupBy(tag ...interface{}) *GroupByNode {
	g := newGroupByNode(n.provides, tag)
	n.linkChild(g)
	return g
}

// Curently you must use MapReduce
//// Perform just the map step of a map-reduce operation.
//// A map step must always be followed by a reduce step.
//// See Apply for performing simple transformations.
//// See MapReduce for performing map-reduce in one command.
////
//// NOTE: Map can only be applied to batch edges.
//func (n *chainnode) Map(f interface{}) (c *MapNode) {
//	if n.Provides() != BatchEdge {
//		panic("cannot MapReduce non batch edge, did you forget to window the data?")
//	}
//	c = newMapNode(f)
//	n.linkChild(c)
//	return c
//}
//
//// Perform just the reduce step of a map-reduce operation.
////
//// NOTE: Reduce can only be applied to map edges.
//func (n *chainnode) Reduce(f interface{}) (c *ReduceNode) {
//	switch n.Provides() {
//	case ReduceEdge:
//		c = newReduceNode(f)
//	default:
//		panic("cannot Reduce non reduce edge, did you forget to map the data?")
//	}
//	n.linkChild(c)
//	return c
//}

// Perform a map-reduce operation on the data.
// The built-in functions under `influxql` provide the
// selection,aggregation, and transformation functions
// from the InfluxQL language.
//
// MapReduce may be applied to either a batch or a stream edge.
// In the case of a batch each batch is passed to the mapper idependently.
// In the case of a stream all incoming data points that have
// the exact same time are combined into a batch and sent to the mapper.
func (n *chainnode) MapReduce(mr MapReduceInfo) *ReduceNode {
	var m *MapNode
	var r *ReduceNode
	m = newMapNode(n.Provides(), mr.Map)
	r = newReduceNode(mr.Reduce, mr.Edge)
	n.linkChild(m)
	m.linkChild(r)
	return r
}

// Create a new node that windows the stream by time.
//
// NOTE: Window can only be applied to stream edges.
func (n *chainnode) Window() *WindowNode {
	if n.Provides() != StreamEdge {
		panic("cannot Window batch edge")
	}
	w := newWindowNode()
	n.linkChild(w)
	return w
}

// Create a new node that samples the incoming points or batches.
//
// One point will be emitted every count or duration specified.
func (n *chainnode) Sample(rate interface{}) *SampleNode {
	s := newSampleNode(n.Provides(), rate)
	n.linkChild(s)
	return s
}

// Create a new node that computes the derivative of adjacent points.
func (n *chainnode) Derivative(field string) *DerivativeNode {
	s := newDerivativeNode(n.Provides(), field)
	n.linkChild(s)
	return s
}
<commit_msg>Fixed typo in MapReduce documentation.<commit_after>package pipeline

import (
	"bytes"
	"fmt"
	"strings"
	"time"

	"github.com/influxdata/kapacitor/tick"
)

// The type of data that travels along an edge connecting two nodes in a Pipeline.
type EdgeType int

const (
	// No data is transfered
	NoEdge EdgeType = iota
	// Data is transfered immediately and one point at a time.
	StreamEdge
	// Data is transfered in batches as soon as it is ready.
	BatchEdge
	// Data is transfered as it is received from a map function.
	ReduceEdge
)

type ID int

func (e EdgeType) String() string {
	switch e {
	case StreamEdge:
		return "stream"
	case BatchEdge:
		return "batch"
	case ReduceEdge:
		return "reduce"
	default:
		return "unknown EdgeType"
	}
}

//Generic node in a pipeline
type Node interface {
	// List of parents of this node.
	Parents() []Node
	// List of children of this node.
	Children() []Node
	// Add a parent node only, does not add the child relation.
	addParent(p Node)
	// Links a child node by adding both the parent and child relation.
	linkChild(c Node)

	// Short description of the node does not need to be unique
	Desc() string

	// Friendly readable unique name of the node
	Name() string
	SetName(string)

	// Unique id for the node
	ID() ID
	setID(ID)

	// The type of input the node wants.
	Wants() EdgeType
	// The type of output the node provides.
	Provides() EdgeType

	// Helper methods for walking DAG
	tMark() bool
	setTMark(b bool)
	pMark() bool
	setPMark(b bool)
	setPipeline(*Pipeline)
	pipeline() *Pipeline

	// Return .dot string to graph DAG
	dot(buf *bytes.Buffer)
}

type node struct {
	p        *Pipeline
	desc     string
	name     string
	id       ID
	parents  []Node
	children []Node
	wants    EdgeType
	provides EdgeType
	tm       bool
	pm       bool
}

// tick:ignore
func (n *node) Desc() string {
	return n.desc
}

// tick:ignore
func (n *node) ID() ID {
	return n.id
}

func (n *node) setID(id ID) {
	n.id = id
}

// tick:ignore
func (n *node) Name() string {
	if n.name == "" {
		n.name = fmt.Sprintf("%s%d", n.Desc(), n.ID())
	}
	return n.name
}

// tick:ignore
func (n *node) SetName(name string) {
	n.name = name
}

// tick:ignore
func (n *node) Parents() []Node {
	return n.parents
}

// tick:ignore
func (n *node) Children() []Node {
	return n.children
}

func (n *node) addParent(c Node) {
	n.parents = append(n.parents, c)
}

func (n *node) linkChild(c Node) {
	c.setPipeline(n.p)
	n.p.assignID(c)
	n.children = append(n.children, c)
	c.addParent(n)
}

func (n *node) tMark() bool {
	return n.tm
}

func (n *node) setTMark(b bool) {
	n.tm = b
}

func (n *node) pMark() bool {
	return n.pm
}

func (n *node) setPMark(b bool) {
	n.pm = b
}

func (n *node) setPipeline(p *Pipeline) {
	n.p = p
}
func (n *node) pipeline() *Pipeline {
	return n.p
}

// tick:ignore
func (n *node) Wants() EdgeType {
	return n.wants
}

// tick:ignore
func (n *node) Provides() EdgeType {
	return n.provides
}

func (n *node) dot(buf *bytes.Buffer) {
	for _, c := range n.children {
		buf.Write([]byte(fmt.Sprintf("%s -> %s;\n", n.Name(), c.Name())))
	}
}

// Create a new stream of data that contains the internal statistics of the node.
// The interval represents how often to emit the statistics based on real time.
// This means the interval time is independent of the times of the data points the source node is receiving.
func (n *node) Stats(interval time.Duration) *StatsNode {
	stats := newStatsNode(n, interval)
	n.pipeline().addSource(stats)
	return stats
}

const nodeNameMarker = "NODE_NAME"
const intervalMarker = "INTERVAL"

// Helper function for creating an alert on low throughput, aka deadman's switch.
//
// - Threshold -- trigger alert if throughput drops below threshold in points/interval.
// - Interval -- how often to check the throughput.
// - Expressions -- optional list of expressions to also evaluate. Useful for time of day alerting.
//
// Example:
//    var data = stream.from()...
//    // Trigger critical alert if the throughput drops below 100 points per 10s and checked every 10s.
//    data.deadman(100.0, 10s)
//    //Do normal processing of data
//    data....
//
// The above is equivalent to this
// Example:
//    var data = stream.from()...
//    // Trigger critical alert if the throughput drops below 100 points per 10s and checked every 10s.
//    data.stats(10s)
//          .derivative('collected')
//              .unit(10s)
//              .nonNegative()
//          .alert()
//              .id('node \'stream0\' in task \'{{ .TaskName }}\'')
//              .message('{{ .ID }} is {{ if eq .Level "OK" }}alive{{ else }}dead{{ end }}: {{ index .Fields "collected" | printf "%0.3f" }} points/10s.')
//              .crit(lamdba: "collected" <= 100.0)
//    //Do normal processing of data
//    data....
//
// The `id` and `message` alert properties can be configured globally via the 'deadman' configuration section.
//
// Since the AlertNode is the last piece it can be further modified as normal.
// Example:
//    var data = stream.from()...
//    // Trigger critical alert if the throughput drops below 100 points per 1s and checked every 10s.
//    data.deadman(100.0, 10s).slack().channel('#dead_tasks')
//    //Do normal processing of data
//    data....
//
// You can specify additional lambda expressions to further constrain when the deadman's switch is triggered.
// Example:
//    var data = stream.from()...
//    // Trigger critical alert if the throughput drops below 100 points per 10s and checked every 10s.
//    // Only trigger the alert if the time of day is between 8am-5pm.
//    data.deadman(100.0, 10s, lambda: hour("time") >= 8 AND hour("time") <= 17)
//    //Do normal processing of data
//    data....
//
func (n *node) Deadman(threshold float64, interval time.Duration, expr ...tick.Node) *AlertNode {
	dn := n.Stats(interval).
		Derivative("collected").NonNegative()
	dn.Unit = interval

	an := dn.Alert()
	critExpr := &tick.BinaryNode{
		Operator: tick.TokenLessEqual,
		Left: &tick.ReferenceNode{
			Reference: "collected",
		},
		Right: &tick.NumberNode{
			IsFloat: true,
			Float64: threshold,
		},
	}
	// Add any additional expressions
	for _, e := range expr {
		critExpr = &tick.BinaryNode{
			Operator: tick.TokenAnd,
			Left:     critExpr,
			Right:    e,
		}
	}
	an.Crit = critExpr
	// Replace NODE_NAME with actual name of the node in the Id.
	an.Id = strings.Replace(n.pipeline().deadman.Id(), nodeNameMarker, n.Name(), 1)
	// Set the message on the alert node.
	an.Message = strings.Replace(n.pipeline().deadman.Message(), intervalMarker, interval.String(), 1)
	return an
}

// ---------------------------------
// Chaining methods
//

// basic implementation of node + chaining methods
type chainnode struct {
	node
}

func newBasicChainNode(desc string, wants, provides EdgeType) chainnode {
	return chainnode{node{
		desc:     desc,
		wants:    wants,
		provides: provides,
	}}
}

// Create a new node that filters the data stream by a given expression.
func (n *chainnode) Where(expression tick.Node) *WhereNode {
	w := newWhereNode(n.provides, expression)
	n.linkChild(w)
	return w
}

// Create an http output node that caches the most recent data it has received.
// The cached data is available at the given endpoint.
// The endpoint is the relative path from the API endpoint of the running task.
// For example if the task endpoint is at "/api/v1/task/<task_name>" and endpoint is
// "top10", then the data can be requested from "/api/v1/task/<task_name>/top10".
func (n *chainnode) HttpOut(endpoint string) *HTTPOutNode {
	h := newHTTPOutNode(n.provides, endpoint)
	n.linkChild(h)
	return h
}

// Create an influxdb output node that will store the incoming data into InfluxDB.
func (n *chainnode) InfluxDBOut() *InfluxDBOutNode {
	i := newInfluxDBOutNode(n.provides)
	n.linkChild(i)
	return i
}

// Create an alert node, which can trigger alerts.
func (n *chainnode) Alert() *AlertNode {
	a := newAlertNode(n.provides)
	n.linkChild(a)
	return a
}

// Perform the union of this node and all other given nodes.
func (n *chainnode) Union(node ...Node) *UnionNode {
	u := newUnionNode(n.provides, node)
	n.linkChild(u)
	return u
}

// Join this node with other nodes. The data is joined on timestamp.
func (n *chainnode) Join(others ...Node) *JoinNode {
	others = append([]Node{n}, others...)
	j := newJoinNode(n.provides, others)
	return j
}

// Create an eval node that will evaluate the given transformation function to each data point.
//  A list of expressions may be provided and will be evaluated in the order they are given
// and results of previous expressions are made available to later expressions.
func (n *chainnode) Eval(expressions ...tick.Node) *EvalNode {
	e := newEvalNode(n.provides, expressions)
	n.linkChild(e)
	return e
}

// Group the data by a set of tags.
//
// Can pass literal * to group by all dimensions.
// Example:
//    .groupBy(*)
//
func (n *chainnode) GroupBy(tag ...interface{}) *GroupByNode {
	g := newGroupByNode(n.provides, tag)
	n.linkChild(g)
	return g
}

// Curently you must use MapReduce
//// Perform just the map step of a map-reduce operation.
//// A map step must always be followed by a reduce step.
//// See Apply for performing simple transformations.
//// See MapReduce for performing map-reduce in one command.
////
//// NOTE: Map can only be applied to batch edges.
//func (n *chainnode) Map(f interface{}) (c *MapNode) {
//	if n.Provides() != BatchEdge {
//		panic("cannot MapReduce non batch edge, did you forget to window the data?")
//	}
//	c = newMapNode(f)
//	n.linkChild(c)
//	return c
//}
//
//// Perform just the reduce step of a map-reduce operation.
////
//// NOTE: Reduce can only be applied to map edges.
//func (n *chainnode) Reduce(f interface{}) (c *ReduceNode) {
//	switch n.Provides() {
//	case ReduceEdge:
//		c = newReduceNode(f)
//	default:
//		panic("cannot Reduce non reduce edge, did you forget to map the data?")
//	}
//	n.linkChild(c)
//	return c
//}

// Perform a map-reduce operation on the data.
// The built-in functions under `influxql` provide the
// selection,aggregation, and transformation functions
// from the InfluxQL language.
//
// MapReduce may be applied to either a batch or a stream edge.
// In the case of a batch each batch is passed to the mapper independently.
// In the case of a stream all incoming data points that have
// the exact same time are combined into a batch and sent to the mapper.
func (n *chainnode) MapReduce(mr MapReduceInfo) *ReduceNode {
	var m *MapNode
	var r *ReduceNode
	m = newMapNode(n.Provides(), mr.Map)
	r = newReduceNode(mr.Reduce, mr.Edge)
	n.linkChild(m)
	m.linkChild(r)
	return r
}

// Create a new node that windows the stream by time.
//
// NOTE: Window can only be applied to stream edges.
func (n *chainnode) Window() *WindowNode {
	if n.Provides() != StreamEdge {
		panic("cannot Window batch edge")
	}
	w := newWindowNode()
	n.linkChild(w)
	return w
}

// Create a new node that samples the incoming points or batches.
//
// One point will be emitted every count or duration specified.
func (n *chainnode) Sample(rate interface{}) *SampleNode {
	s := newSampleNode(n.Provides(), rate)
	n.linkChild(s)
	return s
}

// Create a new node that computes the derivative of adjacent points.
func (n *chainnode) Derivative(field string) *DerivativeNode {
	s := newDerivativeNode(n.Provides(), field)
	n.linkChild(s)
	return s
}
<|endoftext|>


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

// Adapted from encoding/xml/read_test.go.

// Package atom defines XML data structures for an Atom feed.
package atom

import (
	"encoding/xml"
	"time"
)

type Feed struct {
	XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
	Title   string   `xml:"title"`
	ID      string   `xml:"id"`
	Link    []Link   `xml:"link"`
	Updated TimeStr  `xml:"updated"`
	Author  *Person  `xml:"author"`
	Entry   []*Entry `xml:"entry"`
}

type Entry struct {
	Title     string  `xml:"title"`
	ID        string  `xml:"id"`
	Link      []Link  `xml:"link"`
	Published TimeStr `xml:"published"`
	Updated   TimeStr `xml:"updated"`
	Author    *Person `xml:"author"`
	Summary   *Text   `xml:"summary"`
	Content   *Text   `xml:"content"`
}

type Link struct {
	Rel  string `xml:"rel,attr"`
	Href string `xml:"href,attr"`
}

type Person struct {
	Name     string `xml:"name"`
	URI      string `xml:"uri,omitempty"`
	Email    string `xml:"email,omitempty"`
	InnerXML string `xml:",innerxml"`
}

type Text struct {
	Type string `xml:"type,attr"`
	Body string `xml:",chardata"`
}

type TimeStr string

func Time(t time.Time) TimeStr {
	return TimeStr(t.Format("2006-01-02T15:04:05-07:00"))
}
<commit_msg>go.blog/pkg/atom: remove package<commit_after><|endoftext|>


<commit_before>// Copyright The ezgliding 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 igc

import (
	"fmt"
	"image/color"
	"math"
	"time"

	"github.com/golang/geo/s2"
	kml "github.com/twpayne/go-kml"
)

// PhaseType represents a flight phase.
//
// Possible values include Towing, PossibleCruising/Cruising,
// PossibleCircling/Circling, Unknown.
type PhaseType int

const (
	Unknown          PhaseType = 0
	Towing           PhaseType = 1
	PossibleCruising PhaseType = 2
	Cruising         PhaseType = 3
	PossibleCircling PhaseType = 4
	Circling         PhaseType = 5
)

// CirclingType indicates Left, Right or Mixed circling.
type CirclingType int

const (
	Mixed CirclingType = 0
	Left  CirclingType = 1
	Right CirclingType = 2
)

const (
	// MinTurnRate is the min rate to consider circling
	MinTurnRate = 6.5
	// MaxTurnRate is the max rate considered for valid turns
	MaxTurnRate = 22.5
	// MinCirclingTime is used to decide when a switch to circling occurs.
	// This value is used when calculating flight phases to switch from
	// PossibleCircling to Circling.
	MinCirclingTime = 15
	// MinCruisingTime is used to decide when a switch to cruising occurs.
	// This value is used when calculating flight phases to switch from
	// PossibleCruising to Cruising.
	MinCruisingTime = 10
)

// Phase is a flight phase (towing, cruising, circling).
type Phase struct {
	Type         PhaseType
	CirclingType CirclingType
	Start        Point
	StartIndex   int
	End          Point
	EndIndex     int
	AvgVario     float64
	TopVario     float64
	AvgGndSpeed  float64
	TopGndSpeed  float64
	Distance     float64
	LD           float64
	Centroid     s2.LatLng
	CellID       s2.CellID
}

// Phases returns the list of flight phases for the Track.
// Each phases is one of Cruising, Circling, Towing or Unknown.
func (track *Track) Phases() ([]Phase, error) {

	if len(track.phases) > 0 {
		return track.phases, nil
	}

	var currPhase PhaseType
	var startIndex int
	var currPoint Point
	var turning bool
	//var turnRate float64

	currPhase = Cruising
	track.phases = []Phase{
		Phase{Type: Cruising, StartIndex: 0, Start: track.Points[0]},
	}

	// we need the bearings for each point to calculate turn rates
	var d float64
	for i := 1; i < len(track.Points); i++ {
		track.Points[i-1].bearing = track.Points[i-1].Bearing(track.Points[i])
		d = track.Points[i-1].Distance(track.Points[i])
		track.Points[i].distance = track.Points[i-1].distance + d
		track.Points[i].speed = d / track.Points[i].Time.Sub(track.Points[i-1].Time).Seconds()
	}

	for i := 0; i < len(track.Points)-1; i++ {
		currPoint = track.Points[i]
		turning, _ = track.isTurning(i)

		if currPhase == Cruising {
			// if cruising check for turning
			if turning {
				// set possible circling if turning
				currPhase = PossibleCircling
				startIndex = i
			} // else continue
		} else if currPhase == PossibleCircling {
			// if possible circling check for turning longer than min circling time
			if turning {
				if currPoint.Time.Sub(track.Points[startIndex].Time).Seconds() > MinCirclingTime {
					// if true then set circling
					currPhase = Circling
					track.wrapPhase(startIndex, Circling)
				}
			} else {
				// if not go back to cruising
				currPhase = Cruising
			}
		} else if currPhase == Circling {
			// if circling check for stopping to turn
			if !turning {
				// if stopping set possible cruising
				currPhase = PossibleCruising
				startIndex = i
			}
		} else if currPhase == PossibleCruising {
			// if possible cruising check for longer than min cruising
			if !turning {
				if currPoint.Time.Sub(track.Points[startIndex].Time).Seconds() > MinCruisingTime {
					// if true then set cruising
					currPhase = Cruising
					track.wrapPhase(startIndex, Cruising)
				}
			} else {
				// if not go back to circling
				currPhase = Circling
			}
		}
	}

	return track.phases, nil
}

func (track *Track) wrapPhase(index int, phaseType PhaseType) {
	p := &track.phases[len(track.phases)-1]

	p.EndIndex = index
	p.End = track.Points[index]

	// compute phase stats
	altGain := float64(p.End.GNSSAltitude - p.Start.GNSSAltitude)
	p.Distance = p.End.distance - p.Start.distance
	p.AvgVario = altGain / p.Duration().Seconds()
	p.AvgGndSpeed = p.Distance / (p.Duration().Seconds() / 3600)

	if p.Type == Cruising {
		p.LD = p.Distance * 1000.0 / math.Abs(altGain)
	}
	pts := make([]s2.LatLng, p.EndIndex-p.StartIndex)
	for i := p.StartIndex; i < p.EndIndex; i++ {
		pts[i-p.StartIndex] = track.Points[i].LatLng
	}
	centroid := s2.LatLngFromPoint(s2.PolylineFromLatLngs(pts).Centroid())
	p.CellID = s2.CellIDFromLatLng(centroid)
	p.Centroid = centroid

	track.phases = append(track.phases, Phase{Type: phaseType, StartIndex: index, Start: track.Points[index]})
}

func (track *Track) isTurning(i int) (bool, float64) {
	turnRate := (track.Points[i+1].bearing - track.Points[i].bearing).Abs().Degrees() / track.Points[i+1].Time.Sub(track.Points[i].Time).Seconds()
	return math.Abs(turnRate) > MinTurnRate, turnRate
}

// Duration returns the duration of this flight phase.
func (p *Phase) Duration() time.Duration {
	return p.End.Time.Sub(p.Start.Time)
}

func (track *Track) encodePhasesKML() (*kml.CompoundElement, error) {

	result := kml.Document()
	result.Add(
		kml.SharedStyle(
			"cruising",
			kml.LineStyle(
				kml.Color(color.RGBA{R: 0, G: 0, B: 255, A: 127}),
				kml.Width(4),
			),
		),
		kml.SharedStyle(
			"circling",
			kml.LineStyle(
				kml.Color(color.RGBA{R: 0, G: 255, B: 0, A: 127}),
				kml.Width(4),
			),
		),
		kml.SharedStyle(
			"attempt",
			kml.LineStyle(
				kml.Color(color.RGBA{R: 255, G: 0, B: 0, A: 127}),
				kml.Width(4),
			),
		),
	)

	phases, err := track.Phases()
	if err != nil {
		return result, err
	}

	for i := 0; i < len(phases)-2; i++ {
		phase := phases[i]
		//fmt.Printf("%v\t%v\n", ph.Start, ph.End)
		coords := make([]kml.Coordinate, phase.EndIndex-phase.StartIndex+1)
		for i := phase.StartIndex; i <= phase.EndIndex; i++ {
			p := track.Points[i]
			coords[i-phase.StartIndex].Lat = p.Lat.Degrees()
			coords[i-phase.StartIndex].Lon = p.Lng.Degrees()
			coords[i-phase.StartIndex].Alt = float64(p.GNSSAltitude)
		}
		style := "#cruising"
		if phase.Type == Circling && phase.End.Time.Sub(phase.Start.Time).Seconds() < 45 {
			style = "#attempt"
		} else if phase.Type == Circling {
			style = "#circling"
		}
		result.Add(
			kml.Placemark(
				kml.StyleURL(style),
				kml.LineString(
					kml.Extrude(false),
					kml.Tessellate(false),
					kml.AltitudeMode("absolute"),
					kml.Coordinates(coords...),
				),
			))

		name := fmt.Sprintf("Lat: %v Lng: %v",
			phase.Centroid.Lat.Degrees(), phase.Centroid.Lng.Degrees())
		desc := fmt.Sprintf("Alt Gain: %dm (%dm %dm)
Distance: %.2fkm
Speed: %.2fkm/h
LD: %v
Vario: %.1fm/s
Cell: %v
",
			phase.End.GNSSAltitude-phase.Start.GNSSAltitude,
			phase.Start.GNSSAltitude, phase.End.GNSSAltitude, phase.Distance,
			phase.AvgGndSpeed, phase.LD, phase.AvgVario, phase.CellID)
		result.Add(
			kml.Placemark(
				kml.Name(name),
				kml.Description(desc),
				kml.Point(
					kml.Coordinates(kml.Coordinate{
						Lon: phase.Centroid.Lng.Degrees(), Lat: phase.Centroid.Lat.Degrees(),
					}),
				),
			))
	}
	return result, nil
}
<commit_msg>Check start/end when setting phasevario and speed<commit_after>// Copyright The ezgliding 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 igc

import (
	"fmt"
	"image/color"
	"math"
	"time"

	"github.com/golang/geo/s2"
	kml "github.com/twpayne/go-kml"
)

// PhaseType represents a flight phase.
//
// Possible values include Towing, PossibleCruising/Cruising,
// PossibleCircling/Circling, Unknown.
type PhaseType int

const (
	Unknown          PhaseType = 0
	Towing           PhaseType = 1
	PossibleCruising PhaseType = 2
	Cruising         PhaseType = 3
	PossibleCircling PhaseType = 4
	Circling         PhaseType = 5
)

// CirclingType indicates Left, Right or Mixed circling.
type CirclingType int

const (
	Mixed CirclingType = 0
	Left  CirclingType = 1
	Right CirclingType = 2
)

const (
	// MinTurnRate is the min rate to consider circling
	MinTurnRate = 6.5
	// MaxTurnRate is the max rate considered for valid turns
	MaxTurnRate = 22.5
	// MinCirclingTime is used to decide when a switch to circling occurs.
	// This value is used when calculating flight phases to switch from
	// PossibleCircling to Circling.
	MinCirclingTime = 15
	// MinCruisingTime is used to decide when a switch to cruising occurs.
	// This value is used when calculating flight phases to switch from
	// PossibleCruising to Cruising.
	MinCruisingTime = 10
)

// Phase is a flight phase (towing, cruising, circling).
type Phase struct {
	Type         PhaseType
	CirclingType CirclingType
	Start        Point
	StartIndex   int
	End          Point
	EndIndex     int
	AvgVario     float64
	TopVario     float64
	AvgGndSpeed  float64
	TopGndSpeed  float64
	Distance     float64
	LD           float64
	Centroid     s2.LatLng
	CellID       s2.CellID
}

// Phases returns the list of flight phases for the Track.
// Each phases is one of Cruising, Circling, Towing or Unknown.
func (track *Track) Phases() ([]Phase, error) {

	if len(track.phases) > 0 {
		return track.phases, nil
	}

	var currPhase PhaseType
	var startIndex int
	var currPoint Point
	var turning bool
	//var turnRate float64

	currPhase = Cruising
	track.phases = []Phase{
		Phase{Type: Cruising, StartIndex: 0, Start: track.Points[0]},
	}

	// we need the bearings for each point to calculate turn rates
	var d float64
	for i := 1; i < len(track.Points); i++ {
		track.Points[i-1].bearing = track.Points[i-1].Bearing(track.Points[i])
		d = track.Points[i-1].Distance(track.Points[i])
		track.Points[i].distance = track.Points[i-1].distance + d
		track.Points[i].speed = d / track.Points[i].Time.Sub(track.Points[i-1].Time).Seconds()
	}

	for i := 0; i < len(track.Points)-1; i++ {
		currPoint = track.Points[i]
		turning, _ = track.isTurning(i)

		if currPhase == Cruising {
			// if cruising check for turning
			if turning {
				// set possible circling if turning
				currPhase = PossibleCircling
				startIndex = i
			} // else continue
		} else if currPhase == PossibleCircling {
			// if possible circling check for turning longer than min circling time
			if turning {
				if currPoint.Time.Sub(track.Points[startIndex].Time).Seconds() > MinCirclingTime {
					// if true then set circling
					currPhase = Circling
					track.wrapPhase(startIndex, Circling)
				}
			} else {
				// if not go back to cruising
				currPhase = Cruising
			}
		} else if currPhase == Circling {
			// if circling check for stopping to turn
			if !turning {
				// if stopping set possible cruising
				currPhase = PossibleCruising
				startIndex = i
			}
		} else if currPhase == PossibleCruising {
			// if possible cruising check for longer than min cruising
			if !turning {
				if currPoint.Time.Sub(track.Points[startIndex].Time).Seconds() > MinCruisingTime {
					// if true then set cruising
					currPhase = Cruising
					track.wrapPhase(startIndex, Cruising)
				}
			} else {
				// if not go back to circling
				currPhase = Circling
			}
		}
	}

	return track.phases, nil
}

func (track *Track) wrapPhase(index int, phaseType PhaseType) {
	p := &track.phases[len(track.phases)-1]

	p.EndIndex = index
	p.End = track.Points[index]

	// compute phase stats
	altGain := float64(p.End.GNSSAltitude - p.Start.GNSSAltitude)
	p.Distance = p.End.distance - p.Start.distance
	if p.Duration().Seconds() != 0 {
		p.AvgVario = altGain / p.Duration().Seconds()
	}
	p.AvgGndSpeed = p.Distance / (p.Duration().Seconds() / 3600)

	if p.Type == Cruising {
		p.LD = p.Distance * 1000.0 / math.Abs(altGain)
	}
	pts := make([]s2.LatLng, p.EndIndex-p.StartIndex)
	for i := p.StartIndex; i < p.EndIndex; i++ {
		pts[i-p.StartIndex] = track.Points[i].LatLng
	}
	centroid := s2.LatLngFromPoint(s2.PolylineFromLatLngs(pts).Centroid())
	p.CellID = s2.CellIDFromLatLng(centroid)
	p.Centroid = centroid

	track.phases = append(track.phases, Phase{Type: phaseType, StartIndex: index, Start: track.Points[index]})
}

func (track *Track) isTurning(i int) (bool, float64) {
	turnRate := (track.Points[i+1].bearing - track.Points[i].bearing).Abs().Degrees() / track.Points[i+1].Time.Sub(track.Points[i].Time).Seconds()
	return math.Abs(turnRate) > MinTurnRate, turnRate
}

// Duration returns the duration of this flight phase.
func (p *Phase) Duration() time.Duration {
	return p.End.Time.Sub(p.Start.Time)
}

func (track *Track) encodePhasesKML() (*kml.CompoundElement, error) {

	result := kml.Document()
	result.Add(
		kml.SharedStyle(
			"cruising",
			kml.LineStyle(
				kml.Color(color.RGBA{R: 0, G: 0, B: 255, A: 127}),
				kml.Width(4),
			),
		),
		kml.SharedStyle(
			"circling",
			kml.LineStyle(
				kml.Color(color.RGBA{R: 0, G: 255, B: 0, A: 127}),
				kml.Width(4),
			),
		),
		kml.SharedStyle(
			"attempt",
			kml.LineStyle(
				kml.Color(color.RGBA{R: 255, G: 0, B: 0, A: 127}),
				kml.Width(4),
			),
		),
	)

	phases, err := track.Phases()
	if err != nil {
		return result, err
	}

	for i := 0; i < len(phases)-2; i++ {
		phase := phases[i]
		//fmt.Printf("%v\t%v\n", ph.Start, ph.End)
		coords := make([]kml.Coordinate, phase.EndIndex-phase.StartIndex+1)
		for i := phase.StartIndex; i <= phase.EndIndex; i++ {
			p := track.Points[i]
			coords[i-phase.StartIndex].Lat = p.Lat.Degrees()
			coords[i-phase.StartIndex].Lon = p.Lng.Degrees()
			coords[i-phase.StartIndex].Alt = float64(p.GNSSAltitude)
		}
		style := "#cruising"
		if phase.Type == Circling && phase.End.Time.Sub(phase.Start.Time).Seconds() < 45 {
			style = "#attempt"
		} else if phase.Type == Circling {
			style = "#circling"
		}
		result.Add(
			kml.Placemark(
				kml.StyleURL(style),
				kml.LineString(
					kml.Extrude(false),
					kml.Tessellate(false),
					kml.AltitudeMode("absolute"),
					kml.Coordinates(coords...),
				),
			))

		name := fmt.Sprintf("Lat: %v Lng: %v",
			phase.Centroid.Lat.Degrees(), phase.Centroid.Lng.Degrees())
		desc := fmt.Sprintf("Alt Gain: %dm (%dm %dm)
Distance: %.2fkm
Speed: %.2fkm/h
LD: %v
Vario: %.1fm/s
Cell: %v
",
			phase.End.GNSSAltitude-phase.Start.GNSSAltitude,
			phase.Start.GNSSAltitude, phase.End.GNSSAltitude, phase.Distance,
			phase.AvgGndSpeed, phase.LD, phase.AvgVario, phase.CellID)
		result.Add(
			kml.Placemark(
				kml.Name(name),
				kml.Description(desc),
				kml.Point(
					kml.Coordinates(kml.Coordinate{
						Lon: phase.Centroid.Lng.Degrees(), Lat: phase.Centroid.Lat.Degrees(),
					}),
				),
			))
	}
	return result, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package model

import (
	"k8s.io/apiserver/pkg/authentication/user"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/fitasks"
)

// PKIModelBuilder configures PKI keypairs
type PKIModelBuilder struct {
	*KopsModelContext
}

var _ fi.ModelBuilder = &PKIModelBuilder{}

func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error {
	{
		// Keypair used by the kubelet
		t := &fitasks.Keypair{
			Name:    fi.String("kubelet"),
			Subject: "cn=kubelet",
			Type:    "client",
		}
		c.AddTask(t)
	}

	{
		// Keypair used by the kube-scheduler
		t := &fitasks.Keypair{
			Name:    fi.String("kube-scheduler"),
			Subject: "cn=" + user.KubeScheduler,
			Type:    "client",
		}
		c.AddTask(t)
	}

	{
		// Keypair used for admin kubecfg
		t := &fitasks.Keypair{
			Name:    fi.String("kubecfg"),
			Subject: "cn=kubecfg",
			Type:    "client",
		}
		c.AddTask(t)
	}

	{
		// Keypair used for apiserver

		// A few names used from inside the cluster, which all resolve the same based on our default suffixes
		alternateNames := []string{
			"kubernetes",
			"kubernetes.default",
			"kubernetes.default.svc",
			"kubernetes.default.svc." + b.Cluster.Spec.ClusterDNSDomain,
		}

		// Names specified in the cluster spec
		alternateNames = append(alternateNames, b.Cluster.Spec.MasterPublicName)
		alternateNames = append(alternateNames, b.Cluster.Spec.MasterInternalName)

		// Referencing it by internal IP should work also
		{
			ip, err := b.WellKnownServiceIP(1)
			if err != nil {
				return err
			}
			alternateNames = append(alternateNames, ip.String())
		}

		t := &fitasks.Keypair{
			Name:           fi.String("master"),
			Subject:        "cn=kubernetes-master",
			Type:           "server",
			AlternateNames: alternateNames,
		}
		c.AddTask(t)
	}

	return nil
}
<commit_msg>Add 127.0.0.1 to the IPs on the master apiserver<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package model

import (
	"k8s.io/apiserver/pkg/authentication/user"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/fitasks"
)

// PKIModelBuilder configures PKI keypairs
type PKIModelBuilder struct {
	*KopsModelContext
}

var _ fi.ModelBuilder = &PKIModelBuilder{}

func (b *PKIModelBuilder) Build(c *fi.ModelBuilderContext) error {
	{
		// Keypair used by the kubelet
		t := &fitasks.Keypair{
			Name:    fi.String("kubelet"),
			Subject: "cn=kubelet",
			Type:    "client",
		}
		c.AddTask(t)
	}

	{
		// Keypair used by the kube-scheduler
		t := &fitasks.Keypair{
			Name:    fi.String("kube-scheduler"),
			Subject: "cn=" + user.KubeScheduler,
			Type:    "client",
		}
		c.AddTask(t)
	}

	{
		// Keypair used for admin kubecfg
		t := &fitasks.Keypair{
			Name:    fi.String("kubecfg"),
			Subject: "cn=kubecfg",
			Type:    "client",
		}
		c.AddTask(t)
	}

	{
		// Keypair used for apiserver

		// A few names used from inside the cluster, which all resolve the same based on our default suffixes
		alternateNames := []string{
			"kubernetes",
			"kubernetes.default",
			"kubernetes.default.svc",
			"kubernetes.default.svc." + b.Cluster.Spec.ClusterDNSDomain,
		}

		// Names specified in the cluster spec
		alternateNames = append(alternateNames, b.Cluster.Spec.MasterPublicName)
		alternateNames = append(alternateNames, b.Cluster.Spec.MasterInternalName)

		// Referencing it by internal IP should work also
		{
			ip, err := b.WellKnownServiceIP(1)
			if err != nil {
				return err
			}
			alternateNames = append(alternateNames, ip.String())
		}

		// We also want to be able to reference it locally via https://127.0.0.1
		alternateNames = append(alternateNames, "127.0.0.1")

		t := &fitasks.Keypair{
			Name:           fi.String("master"),
			Subject:        "cn=kubernetes-master",
			Type:           "server",
			AlternateNames: alternateNames,
		}
		c.AddTask(t)
	}

	return nil
}
<|endoftext|>


<commit_before>package rule

import (
	"time"

	"github.com/lifesum/configsum/pkg/errors"
	"github.com/lifesum/configsum/pkg/generate"
)

// Supported kinds of rules.
const (
	KindOverride Kind = iota + 1
	KindExperiment
	KindRollout
)

// Kind defines the type of rule.
type Kind uint8

// Parameters is the set of keys and their new values that an applied rule sets.
type Parameters map[string]interface{}

// Bucket is a distinct set of parameters that can be used to control
// segregation by percentage split. Rules which are not of kind experiment will
// only have one bucket.
type Bucket struct {
	Name       string
	Parameters Parameters
	Percentage int
}

// Context carries information for rule decisions to match criteria.
type Context struct {
	User ContextUser
}

// ContextUser bundles user information for rule criteria to match.
type ContextUser struct {
	Age uint8
	ID  string
}

// Decisions reflects a matrix of rules applied to a config and if present the
// results of dice rolls for percenatage based decisions.
type Decisions map[string][]int

// Repo provides access to rules.
type Repo interface {
	lifecycle

	Create(input Rule) (Rule, error)
	GetByName(configID, name string) (Rule, error)
	UpdateWith(input Rule) (Rule, error)
	ListAll() ([]Rule, error)
	ListActive(configID string, now time.Time) ([]Rule, error)
}

// RepoMiddleware is a chainable behaviour modifier for Repo.
type RepoMiddleware func(Repo) Repo

type lifecycle interface {
	setup() error
	teardown() error
}

// Rule facilitates the overide of base configs with consumer provided parameters.
type Rule struct {
	active      bool
	activatedAt time.Time
	buckets     []Bucket
	configID    string
	createdAt   time.Time
	criteria    *Criteria
	description string
	deleted     bool
	endTime     time.Time
	ID          string
	kind        Kind
	name        string
	rollout     uint8
	startTime   time.Time
	updatedAt   time.Time
	RandFunc    generate.RandPercentageFunc
}

// New returns a valid rule.
func New(
	id, configID, name, description string,
	kind Kind,
	active bool,
	criteria *Criteria,
	buckets []Bucket,
	rollout *uint8,
	randFunc generate.RandPercentageFunc,
) (Rule, error) {
	r := Rule{
		active:      active,
		buckets:     buckets,
		configID:    configID,
		createdAt:   time.Now().UTC(),
		criteria:    criteria,
		description: description,
		ID:          id,
		kind:        kind,
		name:        name,
		RandFunc:    randFunc,
	}

	if rollout != nil {
		r.rollout = *rollout
	}

	err := r.validate()
	if err != nil {
		return Rule{}, err
	}

	return r, nil
}

func (r Rule) validate() error {
	if len(r.buckets) == 0 {
		return errors.Wrap(errors.ErrInvalidRule, "missing buckets")
	}

	if r.configID == "" {
		return errors.Wrap(errors.ErrInvalidRule, "missing configID")
	}

	if r.createdAt.IsZero() {
		return errors.Wrap(errors.ErrInvalidRule, "missing createdAt")
	}

	if r.ID == "" {
		return errors.Wrap(errors.ErrInvalidRule, "missing id")
	}

	if r.kind == 0 {
		return errors.Wrap(errors.ErrInvalidRule, "missing kind")
	}

	if r.name == "" {
		return errors.Wrap(errors.ErrInvalidRule, "missing metadate.name")
	}

	if r.rollout > 100 {
		return errors.Wrap(errors.ErrInvalidRule, "rollout percentage too high")
	}

	if len(r.buckets) > 1 {
		totalPercentage := 0
		for _, bucket := range r.buckets {
			totalPercentage = totalPercentage + bucket.Percentage
		}
		if totalPercentage != 100 {
			return errors.Wrap(errors.ErrInvalidRule, "bucket percentage not evenly distributed")
		}
	}

	return nil
}

// Run given an input params and context will try to match based on the rules
// Criteria and if matched overrides the input params with its own.
func (r Rule) Run(input Parameters, ctx Context, decisions []int, randInt generate.RandPercentageFunc) (Parameters, []int, error) {
	if r.criteria != nil && r.criteria.User != nil {
		if r.criteria.User.Age != nil {
			return nil, nil, errors.New("matching user age not implemented")
		}

		if r.criteria.User.ID != nil {
			ok, err := r.criteria.User.ID.match(ctx.User.ID)
			if err != nil {
				return nil, nil, errors.Wrap(err, "user id match")
			}

			if !ok {
				return nil, nil, errors.Wrap(errors.ErrRuleNoMatch, "user id")
			}
		}
	}

	var (
		params = Parameters{}
		d      = decisions
	)

	diceRollout := randInt()
	if len(decisions) != 0 {
		diceRollout = decisions[0]
	}

	switch r.kind {
	case KindOverride:
		params = r.buckets[0].Parameters
	case KindExperiment:
		return Parameters{}, nil, errors.New("experiment based rules not implemented")
	case KindRollout:
		if len(decisions) != 0 {
			d = decisions
		} else {
			d = append(d, diceRollout)
		}

		if diceRollout <= int(r.rollout) {
			params = r.buckets[0].Parameters
		} else {
			return nil, d, errors.Wrap(errors.ErrRuleNotInRollout, "rollout percentage")
		}
	}

	for name, value := range params {
		input[name] = value
	}

	return input, d, nil
}
<commit_msg>Consolide logic for storing dice roll decisions<commit_after>package rule

import (
	"time"

	"github.com/lifesum/configsum/pkg/errors"
	"github.com/lifesum/configsum/pkg/generate"
)

// Supported kinds of rules.
const (
	KindOverride Kind = iota + 1
	KindExperiment
	KindRollout
)

// Kind defines the type of rule.
type Kind uint8

// Parameters is the set of keys and their new values that an applied rule sets.
type Parameters map[string]interface{}

// Bucket is a distinct set of parameters that can be used to control
// segregation by percentage split. Rules which are not of kind experiment will
// only have one bucket.
type Bucket struct {
	Name       string
	Parameters Parameters
	Percentage int
}

// Context carries information for rule decisions to match criteria.
type Context struct {
	User ContextUser
}

// ContextUser bundles user information for rule criteria to match.
type ContextUser struct {
	Age uint8
	ID  string
}

// Decisions reflects a matrix of rules applied to a config and if present the
// results of dice rolls for percenatage based decisions.
type Decisions map[string][]int

// Repo provides access to rules.
type Repo interface {
	lifecycle

	Create(input Rule) (Rule, error)
	GetByName(configID, name string) (Rule, error)
	UpdateWith(input Rule) (Rule, error)
	ListAll() ([]Rule, error)
	ListActive(configID string, now time.Time) ([]Rule, error)
}

// RepoMiddleware is a chainable behaviour modifier for Repo.
type RepoMiddleware func(Repo) Repo

type lifecycle interface {
	setup() error
	teardown() error
}

// Rule facilitates the overide of base configs with consumer provided parameters.
type Rule struct {
	active      bool
	activatedAt time.Time
	buckets     []Bucket
	configID    string
	createdAt   time.Time
	criteria    *Criteria
	description string
	deleted     bool
	endTime     time.Time
	ID          string
	kind        Kind
	name        string
	rollout     uint8
	startTime   time.Time
	updatedAt   time.Time
	RandFunc    generate.RandPercentageFunc
}

// New returns a valid rule.
func New(
	id, configID, name, description string,
	kind Kind,
	active bool,
	criteria *Criteria,
	buckets []Bucket,
	rollout *uint8,
	randFunc generate.RandPercentageFunc,
) (Rule, error) {
	r := Rule{
		active:      active,
		buckets:     buckets,
		configID:    configID,
		createdAt:   time.Now().UTC(),
		criteria:    criteria,
		description: description,
		ID:          id,
		kind:        kind,
		name:        name,
		RandFunc:    randFunc,
	}

	if rollout != nil {
		r.rollout = *rollout
	}

	err := r.validate()
	if err != nil {
		return Rule{}, err
	}

	return r, nil
}

func (r Rule) validate() error {
	if len(r.buckets) == 0 {
		return errors.Wrap(errors.ErrInvalidRule, "missing buckets")
	}

	if r.configID == "" {
		return errors.Wrap(errors.ErrInvalidRule, "missing configID")
	}

	if r.createdAt.IsZero() {
		return errors.Wrap(errors.ErrInvalidRule, "missing createdAt")
	}

	if r.ID == "" {
		return errors.Wrap(errors.ErrInvalidRule, "missing id")
	}

	if r.kind == 0 {
		return errors.Wrap(errors.ErrInvalidRule, "missing kind")
	}

	if r.name == "" {
		return errors.Wrap(errors.ErrInvalidRule, "missing metadate.name")
	}

	if r.rollout > 100 {
		return errors.Wrap(errors.ErrInvalidRule, "rollout percentage too high")
	}

	if len(r.buckets) > 1 {
		totalPercentage := 0
		for _, bucket := range r.buckets {
			totalPercentage = totalPercentage + bucket.Percentage
		}
		if totalPercentage != 100 {
			return errors.Wrap(errors.ErrInvalidRule, "bucket percentage not evenly distributed")
		}
	}

	return nil
}

// Run given an input params and context will try to match based on the rules
// Criteria and if matched overrides the input params with its own.
func (r Rule) Run(input Parameters, ctx Context, decisions []int, randInt generate.RandPercentageFunc) (Parameters, []int, error) {
	if r.criteria != nil && r.criteria.User != nil {
		if r.criteria.User.Age != nil {
			return nil, nil, errors.New("matching user age not implemented")
		}

		if r.criteria.User.ID != nil {
			ok, err := r.criteria.User.ID.match(ctx.User.ID)
			if err != nil {
				return nil, nil, errors.Wrap(err, "user id match")
			}

			if !ok {
				return nil, nil, errors.Wrap(errors.ErrRuleNoMatch, "user id")
			}
		}
	}

	var (
		params = Parameters{}
		d      = []int{}
	)

	diceRollout := randInt()
	if len(decisions) != 0 {
		diceRollout = decisions[0]
	}

	switch r.kind {
	case KindOverride:
		params = r.buckets[0].Parameters
	case KindExperiment:
		return Parameters{}, nil, errors.New("experiment based rules not implemented")
	case KindRollout:
		if len(decisions) != 0 {
			d = decisions
		} else {
			d = append(d, diceRollout)
		}

		if diceRollout <= int(r.rollout) {
			params = r.buckets[0].Parameters
		} else {
			return nil, d, errors.Wrap(errors.ErrRuleNotInRollout, "rollout percentage")
		}
	}

	for name, value := range params {
		input[name] = value
	}

	return input, d, nil
}
<|endoftext|>


<commit_before>// RTLAMR - An rtl-sdr receiver for smart meters operating in the 900MHz ISM band.
// Copyright (C) 2015 Douglas Hall
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package scmplus

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"strconv"

	"github.com/bemasher/rtlamr/crc"
	"github.com/bemasher/rtlamr/decode"
	"github.com/bemasher/rtlamr/parse"
)

func init() {
	parse.Register("scm+", NewParser)
}

func NewPacketConfig(symbolLength int) (cfg decode.PacketConfig) {
	cfg.CenterFreq = 912600155
	cfg.DataRate = 32768
	cfg.SymbolLength = symbolLength
	cfg.PreambleSymbols = 16
	cfg.PacketSymbols = 16 * 8
	cfg.Preamble = "0001011010100011"

	return
}

type Parser struct {
	decode.Decoder
	crc.CRC
}

func (p Parser) Dec() decode.Decoder {
	return p.Decoder
}

func (p *Parser) Cfg() *decode.PacketConfig {
	return &p.Decoder.Cfg
}

func NewParser(symbolLength, decimation int) (p parse.Parser) {
	return &Parser{
		decode.NewDecoder(NewPacketConfig(symbolLength), decimation),
		crc.NewCRC("CCITT", 0xFFFF, 0x1021, 0x1D0F),
	}
}

func (p Parser) Parse(indices []int) (msgs []parse.Message) {
	seen := make(map[string]bool)

	for _, pkt := range p.Decoder.Slice(indices) {
		s := string(pkt)
		if seen[s] {
			continue
		}
		seen[s] = true

		data := parse.NewDataFromBytes(pkt)

		// If the checksum fails, bail.
		if residue := p.Checksum(data.Bytes[2:]); residue != p.Residue {
			continue
		}

		scm := NewSCM(data)

		// If the meter id is 0, bail.
		if scm.EndpointID == 0 || scm.ProtocolID != 0x1E {
			continue
		}

		msgs = append(msgs, scm)
	}

	return
}

// Standard Consumption Message Plus
type SCM struct {
	FrameSync    uint16 `xml:",attr"`
	ProtocolID   uint8  `xml:",attr"`
	EndpointType uint8  `xml:",attr"`
	EndpointID   uint32 `xml:",attr"`
	Consumption  uint32 `xml:",attr"`
	Tamper       uint16 `xml:",attr"`
	PacketCRC    uint16 `xml:"Checksum,attr",json:"Checksum"`
}

func NewSCM(data parse.Data) (scm SCM) {
	binary.Read(bytes.NewReader(data.Bytes), binary.BigEndian, &scm)

	return
}

func (scm SCM) MsgType() string {
	return "SCM+"
}

func (scm SCM) MeterID() uint32 {
	return scm.EndpointID
}

func (scm SCM) MeterType() uint8 {
	return scm.EndpointType
}

func (scm SCM) Checksum() []byte {
	checksum := make([]byte, 2)
	binary.BigEndian.PutUint16(checksum, scm.PacketCRC)
	return checksum
}

func (scm SCM) String() string {
	return fmt.Sprintf("{ProtocolID:0x%02X EndpointType:0x%02X EndpointID:%10d Consumption:%10d Tamper:0x%04X PacketCRC:0x%04X}",
		scm.ProtocolID,
		scm.EndpointType,
		scm.EndpointID,
		scm.Consumption,
		scm.Tamper,
		scm.PacketCRC,
	)
}

func (scm SCM) Record() (r []string) {
	r = append(r, "0x"+strconv.FormatUint(uint64(scm.FrameSync), 16))
	r = append(r, "0x"+strconv.FormatUint(uint64(scm.ProtocolID), 16))
	r = append(r, "0x"+strconv.FormatUint(uint64(scm.EndpointType), 16))
	r = append(r, strconv.FormatUint(uint64(scm.EndpointID), 10))
	r = append(r, strconv.FormatUint(uint64(scm.Consumption), 10))
	r = append(r, "0x"+strconv.FormatUint(uint64(scm.Tamper), 16))
	r = append(r, "0x"+strconv.FormatUint(uint64(scm.PacketCRC), 16))

	return
}
<commit_msg>Update comment about break point in SCM+ decoder.<commit_after>// RTLAMR - An rtl-sdr receiver for smart meters operating in the 900MHz ISM band.
// Copyright (C) 2015 Douglas Hall
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package scmplus

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"strconv"

	"github.com/bemasher/rtlamr/crc"
	"github.com/bemasher/rtlamr/decode"
	"github.com/bemasher/rtlamr/parse"
)

func init() {
	parse.Register("scm+", NewParser)
}

func NewPacketConfig(symbolLength int) (cfg decode.PacketConfig) {
	cfg.CenterFreq = 912600155
	cfg.DataRate = 32768
	cfg.SymbolLength = symbolLength
	cfg.PreambleSymbols = 16
	cfg.PacketSymbols = 16 * 8
	cfg.Preamble = "0001011010100011"

	return
}

type Parser struct {
	decode.Decoder
	crc.CRC
}

func (p Parser) Dec() decode.Decoder {
	return p.Decoder
}

func (p *Parser) Cfg() *decode.PacketConfig {
	return &p.Decoder.Cfg
}

func NewParser(symbolLength, decimation int) (p parse.Parser) {
	return &Parser{
		decode.NewDecoder(NewPacketConfig(symbolLength), decimation),
		crc.NewCRC("CCITT", 0xFFFF, 0x1021, 0x1D0F),
	}
}

func (p Parser) Parse(indices []int) (msgs []parse.Message) {
	seen := make(map[string]bool)

	for _, pkt := range p.Decoder.Slice(indices) {
		s := string(pkt)
		if seen[s] {
			continue
		}
		seen[s] = true

		data := parse.NewDataFromBytes(pkt)

		// If the checksum fails, bail.
		if residue := p.Checksum(data.Bytes[2:]); residue != p.Residue {
			continue
		}

		scm := NewSCM(data)

		// If the EndpointID is 0 or ProtocolID is invalid, bail.
		if scm.EndpointID == 0 || scm.ProtocolID != 0x1E {
			continue
		}

		msgs = append(msgs, scm)
	}

	return
}

// Standard Consumption Message Plus
type SCM struct {
	FrameSync    uint16 `xml:",attr"`
	ProtocolID   uint8  `xml:",attr"`
	EndpointType uint8  `xml:",attr"`
	EndpointID   uint32 `xml:",attr"`
	Consumption  uint32 `xml:",attr"`
	Tamper       uint16 `xml:",attr"`
	PacketCRC    uint16 `xml:"Checksum,attr",json:"Checksum"`
}

func NewSCM(data parse.Data) (scm SCM) {
	binary.Read(bytes.NewReader(data.Bytes), binary.BigEndian, &scm)

	return
}

func (scm SCM) MsgType() string {
	return "SCM+"
}

func (scm SCM) MeterID() uint32 {
	return scm.EndpointID
}

func (scm SCM) MeterType() uint8 {
	return scm.EndpointType
}

func (scm SCM) Checksum() []byte {
	checksum := make([]byte, 2)
	binary.BigEndian.PutUint16(checksum, scm.PacketCRC)
	return checksum
}

func (scm SCM) String() string {
	return fmt.Sprintf("{ProtocolID:0x%02X EndpointType:0x%02X EndpointID:%10d Consumption:%10d Tamper:0x%04X PacketCRC:0x%04X}",
		scm.ProtocolID,
		scm.EndpointType,
		scm.EndpointID,
		scm.Consumption,
		scm.Tamper,
		scm.PacketCRC,
	)
}

func (scm SCM) Record() (r []string) {
	r = append(r, "0x"+strconv.FormatUint(uint64(scm.FrameSync), 16))
	r = append(r, "0x"+strconv.FormatUint(uint64(scm.ProtocolID), 16))
	r = append(r, "0x"+strconv.FormatUint(uint64(scm.EndpointType), 16))
	r = append(r, strconv.FormatUint(uint64(scm.EndpointID), 10))
	r = append(r, strconv.FormatUint(uint64(scm.Consumption), 10))
	r = append(r, "0x"+strconv.FormatUint(uint64(scm.Tamper), 16))
	r = append(r, "0x"+strconv.FormatUint(uint64(scm.PacketCRC), 16))

	return
}
<|endoftext|>


<commit_before>// Copyright 2020 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
//
//      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 site define HTTP handlers.
package site

import (
	"fmt"
	"github.com/google/triage-party/pkg/provider"
	"html/template"
	"image/color"
	"math"
	"net/http"
	"net/url"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/google/triage-party/pkg/hubbub"
	"github.com/google/triage-party/pkg/triage"
	"github.com/google/triage-party/pkg/updater"

	"github.com/dustin/go-humanize"
	"gopkg.in/yaml.v2"

	"k8s.io/klog/v2"
)

// VERSION is what version of Triage Party we advertise as.
const VERSION = "v1.3.0"

var (
	nonWordRe = regexp.MustCompile(`\W`)

	// MaxPlayers is how many players to enable in the web interface.
	MaxPlayers = 20

	// Cut-off points for human duration (reversed order)
	defaultMagnitudes = []humanize.RelTimeMagnitude{
		{time.Second, "now", time.Second},
		{2 * time.Second, "1 second %s", 1},
		{time.Minute, "%d seconds %s", time.Second},
		{2 * time.Minute, "1 minute %s", 1},
		{time.Hour, "%d minutes %s", time.Minute},
		{2 * time.Hour, "1 hour %s", 1},
		{humanize.Day, "%d hours %s", time.Hour},
		{2 * humanize.Day, "1 day %s", 1},
		{20 * humanize.Day, "%d days %s", humanize.Day},
		{8 * humanize.Week, "%d weeks %s", humanize.Week},
		{humanize.Year, "%d months %s", humanize.Month},
		{18 * humanize.Month, "1 year %s", 1},
		{2 * humanize.Year, "2 years %s", 1},
		{humanize.LongTime, "%d years %s", humanize.Year},
		{math.MaxInt64, "a long while %s", 1},
	}
)

// Config is how external users interact with this package.
type Config struct {
	BaseDirectory string
	Name          string
	WarnAge       time.Duration
	Updater       *updater.Updater
	Party         *triage.Party
}

func New(c *Config) *Handlers {
	return &Handlers{
		baseDir:   c.BaseDirectory,
		updater:   c.Updater,
		party:     c.Party,
		siteName:  c.Name,
		warnAge:   c.WarnAge,
		startTime: time.Now(),
	}
}

// Handlers is a mix of config and client interfaces to connect with.
type Handlers struct {
	baseDir   string
	updater   *updater.Updater
	party     *triage.Party
	siteName  string
	warnAge   time.Duration
	startTime time.Time
}

// Root redirects to leaderboard.
func (h *Handlers) Root() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		sts, err := h.party.ListCollections()
		if err != nil {
			klog.Errorf("collections: %v", err)
			return
		}
		http.Redirect(w, r, fmt.Sprintf("/s/%s", sts[0].ID), http.StatusSeeOther)
	}
}

// Page are values that are passed into the renderer
type Page struct {
	Version      string
	SiteName     string
	ID           string
	Title        string
	Description  string
	Warning      template.HTML
	Notification template.HTML
	Total        int
	TotalShown   int
	Types        string
	UniqueItems  []*hubbub.Conversation
	ResultAge    time.Duration
	Stale        bool

	Player        int
	Players       int
	PlayerChoices []string
	PlayerNums    []int
	Index         int

	AverageResponseLatency time.Duration
	TotalPullRequests      int
	TotalIssues            int

	ClosedPerDay float64

	Collection  triage.Collection
	Collections []triage.Collection

	Swimlanes            []*Swimlane
	CollectionResult     *triage.CollectionResult
	SelectorVar          string
	SelectorOptions      []Choice
	Milestone            *provider.Milestone
	CompletionETA        time.Time
	MilestoneETA         time.Time
	MilestoneCountOffset int
	MilestoneVeryLate    bool

	OpenStats     *triage.CollectionResult
	VelocityStats *triage.CollectionResult
	GetVars       string
	Status        string
}

// Choice is a selector choice
type Choice struct {
	Value    int
	Text     string
	Selected bool
}

// is this request an HTTP refresh?
func isRefresh(r *http.Request) bool {
	cc := r.Header["Cache-Control"]
	if len(cc) == 0 {
		return false
	}
	//	klog.Infof("cc=%s headers=%+v", cc, r.Header)
	return cc[0] == "max-age-0" || cc[0] == "no-cache"
}

// helper to get integers from a URL
func getInt(url *url.URL, key string, fallback int) int {
	vals := url.Query()[key]
	if len(vals) == 1 {
		i, err := strconv.ParseInt(vals[0], 10, 32)
		if err != nil {
			klog.Warningf("bad %s int value: %v", key, vals)
			return fallback
		}
		return int(i)
	}
	return fallback
}

func toYAML(v interface{}) string {
	s, err := yaml.Marshal(v)
	if err != nil {
		return fmt.Sprintf("yaml err: %v", err)
	}
	return string(s)
}

// Acknowledge JS sanitization issues (what data do we trust?)
func toJS(s string) template.JS {
	return template.JS(s)
}

// Acknowledge JS sanitization issues (what data do we trust?)
func toJSfunc(s string) template.JS {
	return template.JS(nonWordRe.ReplaceAllString(s, "_"))
}

// Make a class name
func className(s string) template.HTMLAttr {
	s = strings.ToLower(nonWordRe.ReplaceAllString(s, "-"))
	s = strings.Replace(s, "_", "-", -1)
	return template.HTMLAttr(s)
}

func parseHexColor(s string) (c color.RGBA, err error) {
	c.A = 0xff

	if s[0] != '#' {
		return c, fmt.Errorf("%q is not a valid hex color", s)
	}

	hexToByte := func(b byte) byte {
		switch {
		case b >= '0' && b <= '9':
			return b - '0'
		case b >= 'a' && b <= 'f':
			return b - 'a' + 10
		case b >= 'A' && b <= 'F':
			return b - 'A' + 10
		}
		err = fmt.Errorf("%q is not a parseable hex color", s)
		return 0
	}

	switch len(s) {
	case 7:
		c.R = hexToByte(s[1])<<4 + hexToByte(s[2])
		c.G = hexToByte(s[3])<<4 + hexToByte(s[4])
		c.B = hexToByte(s[5])<<4 + hexToByte(s[6])
	case 4:
		c.R = hexToByte(s[1]) * 17
		c.G = hexToByte(s[2]) * 17
		c.B = hexToByte(s[3]) * 17
	default:
		err = fmt.Errorf("%q is not a proper hex color", s)
	}
	return
}

// pick an appropriate text color given a background color
func textColor(s string) template.CSS {

	color, err := parseHexColor(fmt.Sprintf("#%s", strings.TrimPrefix(s, "#")))
	if err != nil {
		klog.Errorf("parse hex color failed: %v", err)
		return "f00"
	}

	// human eye is most sensitive to green
	lum := (0.299*float64(color.R) + 0.587*float64(color.G) + 0.114*float64(color.B)) / 255
	if lum > 0.5 {
		return "111"
	}
	return "fff"
}

func unixNano(t time.Time) int64 {
	return t.UnixNano()
}

func humanDuration(d time.Duration) string {
	return roughTime(time.Now().Add(-d))
}

func toDays(d time.Duration) string {
	return fmt.Sprintf("%0.1fd", d.Hours()/24)
}

func roughTime(t time.Time) string {
	if t.IsZero() {
		return ""
	}

	ds := humanize.CustomRelTime(t, time.Now(), "ago", "from now", defaultMagnitudes)
	ds = strings.Replace(ds, " ago", "", 1)

	ds = strings.Replace(ds, " minutes", "min", 1)
	ds = strings.Replace(ds, " minute", "min", 1)

	ds = strings.Replace(ds, " hours", "h", 1)
	ds = strings.Replace(ds, " hour", "h", 1)

	ds = strings.Replace(ds, " days", "d", 1)
	ds = strings.Replace(ds, " day", "d", 1)

	ds = strings.Replace(ds, " months", "mo", 1)
	ds = strings.Replace(ds, " month", "mo", 1)

	ds = strings.Replace(ds, " years", "y", 1)
	ds = strings.Replace(ds, " year", "y", 1)

	ds = strings.Replace(ds, " weeks", "wk", 1)
	ds = strings.Replace(ds, " week", "wk", 1)

	return ds
}

func avatar(u *provider.User) template.HTML {
	return template.HTML(fmt.Sprintf(`<a href="%s" title="%s"><img src="%s" width="20" height="20"></a>`, u.GetHTMLURL(), u.GetLogin(), u.GetAvatarURL()))
}

// playerFilter filters out results for a particular player
func playerFilter(result *triage.CollectionResult, player int, players int) *triage.CollectionResult {
	klog.Infof("Filtering for player %d of %d ...", player, players)

	os := []*triage.RuleResult{}
	seen := map[string]*triage.Rule{}

	for _, o := range result.RuleResults {
		cs := []*hubbub.Conversation{}

		for _, i := range o.Items {
			if (i.ID % players) == (player - 1) {
				cs = append(cs, i)
			}
		}

		os = append(os, triage.SummarizeRuleResult(o.Rule, cs, seen))
	}

	return triage.SummarizeCollectionResult(result.Collection, os)
}

// Healthz returns a dummy healthz page - it's always happy here!
func (h *Handlers) Healthz() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(fmt.Sprintf("ok: %s", h.updater.Status())))
	}
}

// Threadz returns a threadz page
func (h *Handlers) Threadz() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		klog.Infof("GET %s: %v", r.URL.Path, r.Header)
		w.WriteHeader(http.StatusOK)
		w.Write(stack())
	}
}

// stack returns a formatted stack trace of all goroutines
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
func stack() []byte {
	buf := make([]byte, 1024)
	for {
		n := runtime.Stack(buf, true)
		if n < len(buf) {
			return buf[:n]
		}
		buf = make([]byte, 2*len(buf))
	}
}
<commit_msg>Update base version to v1.4.0-DEV<commit_after>// Copyright 2020 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
//
//      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 site define HTTP handlers.
package site

import (
	"fmt"
	"html/template"
	"image/color"
	"math"
	"net/http"
	"net/url"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/google/triage-party/pkg/provider"

	"github.com/google/triage-party/pkg/hubbub"
	"github.com/google/triage-party/pkg/triage"
	"github.com/google/triage-party/pkg/updater"

	"github.com/dustin/go-humanize"
	"gopkg.in/yaml.v2"

	"k8s.io/klog/v2"
)

// VERSION is what version of Triage Party we advertise as.
const VERSION = "v1.4.0-DEV (master)"

var (
	nonWordRe = regexp.MustCompile(`\W`)

	// MaxPlayers is how many players to enable in the web interface.
	MaxPlayers = 20

	// Cut-off points for human duration (reversed order)
	defaultMagnitudes = []humanize.RelTimeMagnitude{
		{time.Second, "now", time.Second},
		{2 * time.Second, "1 second %s", 1},
		{time.Minute, "%d seconds %s", time.Second},
		{2 * time.Minute, "1 minute %s", 1},
		{time.Hour, "%d minutes %s", time.Minute},
		{2 * time.Hour, "1 hour %s", 1},
		{humanize.Day, "%d hours %s", time.Hour},
		{2 * humanize.Day, "1 day %s", 1},
		{20 * humanize.Day, "%d days %s", humanize.Day},
		{8 * humanize.Week, "%d weeks %s", humanize.Week},
		{humanize.Year, "%d months %s", humanize.Month},
		{18 * humanize.Month, "1 year %s", 1},
		{2 * humanize.Year, "2 years %s", 1},
		{humanize.LongTime, "%d years %s", humanize.Year},
		{math.MaxInt64, "a long while %s", 1},
	}
)

// Config is how external users interact with this package.
type Config struct {
	BaseDirectory string
	Name          string
	WarnAge       time.Duration
	Updater       *updater.Updater
	Party         *triage.Party
}

func New(c *Config) *Handlers {
	return &Handlers{
		baseDir:   c.BaseDirectory,
		updater:   c.Updater,
		party:     c.Party,
		siteName:  c.Name,
		warnAge:   c.WarnAge,
		startTime: time.Now(),
	}
}

// Handlers is a mix of config and client interfaces to connect with.
type Handlers struct {
	baseDir   string
	updater   *updater.Updater
	party     *triage.Party
	siteName  string
	warnAge   time.Duration
	startTime time.Time
}

// Root redirects to leaderboard.
func (h *Handlers) Root() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		sts, err := h.party.ListCollections()
		if err != nil {
			klog.Errorf("collections: %v", err)
			return
		}
		http.Redirect(w, r, fmt.Sprintf("/s/%s", sts[0].ID), http.StatusSeeOther)
	}
}

// Page are values that are passed into the renderer
type Page struct {
	Version      string
	SiteName     string
	ID           string
	Title        string
	Description  string
	Warning      template.HTML
	Notification template.HTML
	Total        int
	TotalShown   int
	Types        string
	UniqueItems  []*hubbub.Conversation
	ResultAge    time.Duration
	Stale        bool

	Player        int
	Players       int
	PlayerChoices []string
	PlayerNums    []int
	Index         int

	AverageResponseLatency time.Duration
	TotalPullRequests      int
	TotalIssues            int

	ClosedPerDay float64

	Collection  triage.Collection
	Collections []triage.Collection

	Swimlanes            []*Swimlane
	CollectionResult     *triage.CollectionResult
	SelectorVar          string
	SelectorOptions      []Choice
	Milestone            *provider.Milestone
	CompletionETA        time.Time
	MilestoneETA         time.Time
	MilestoneCountOffset int
	MilestoneVeryLate    bool

	OpenStats     *triage.CollectionResult
	VelocityStats *triage.CollectionResult
	GetVars       string
	Status        string
}

// Choice is a selector choice
type Choice struct {
	Value    int
	Text     string
	Selected bool
}

// is this request an HTTP refresh?
func isRefresh(r *http.Request) bool {
	cc := r.Header["Cache-Control"]
	if len(cc) == 0 {
		return false
	}
	//	klog.Infof("cc=%s headers=%+v", cc, r.Header)
	return cc[0] == "max-age-0" || cc[0] == "no-cache"
}

// helper to get integers from a URL
func getInt(url *url.URL, key string, fallback int) int {
	vals := url.Query()[key]
	if len(vals) == 1 {
		i, err := strconv.ParseInt(vals[0], 10, 32)
		if err != nil {
			klog.Warningf("bad %s int value: %v", key, vals)
			return fallback
		}
		return int(i)
	}
	return fallback
}

func toYAML(v interface{}) string {
	s, err := yaml.Marshal(v)
	if err != nil {
		return fmt.Sprintf("yaml err: %v", err)
	}
	return string(s)
}

// Acknowledge JS sanitization issues (what data do we trust?)
func toJS(s string) template.JS {
	return template.JS(s)
}

// Acknowledge JS sanitization issues (what data do we trust?)
func toJSfunc(s string) template.JS {
	return template.JS(nonWordRe.ReplaceAllString(s, "_"))
}

// Make a class name
func className(s string) template.HTMLAttr {
	s = strings.ToLower(nonWordRe.ReplaceAllString(s, "-"))
	s = strings.Replace(s, "_", "-", -1)
	return template.HTMLAttr(s)
}

func parseHexColor(s string) (c color.RGBA, err error) {
	c.A = 0xff

	if s[0] != '#' {
		return c, fmt.Errorf("%q is not a valid hex color", s)
	}

	hexToByte := func(b byte) byte {
		switch {
		case b >= '0' && b <= '9':
			return b - '0'
		case b >= 'a' && b <= 'f':
			return b - 'a' + 10
		case b >= 'A' && b <= 'F':
			return b - 'A' + 10
		}
		err = fmt.Errorf("%q is not a parseable hex color", s)
		return 0
	}

	switch len(s) {
	case 7:
		c.R = hexToByte(s[1])<<4 + hexToByte(s[2])
		c.G = hexToByte(s[3])<<4 + hexToByte(s[4])
		c.B = hexToByte(s[5])<<4 + hexToByte(s[6])
	case 4:
		c.R = hexToByte(s[1]) * 17
		c.G = hexToByte(s[2]) * 17
		c.B = hexToByte(s[3]) * 17
	default:
		err = fmt.Errorf("%q is not a proper hex color", s)
	}
	return
}

// pick an appropriate text color given a background color
func textColor(s string) template.CSS {

	color, err := parseHexColor(fmt.Sprintf("#%s", strings.TrimPrefix(s, "#")))
	if err != nil {
		klog.Errorf("parse hex color failed: %v", err)
		return "f00"
	}

	// human eye is most sensitive to green
	lum := (0.299*float64(color.R) + 0.587*float64(color.G) + 0.114*float64(color.B)) / 255
	if lum > 0.5 {
		return "111"
	}
	return "fff"
}

func unixNano(t time.Time) int64 {
	return t.UnixNano()
}

func humanDuration(d time.Duration) string {
	return roughTime(time.Now().Add(-d))
}

func toDays(d time.Duration) string {
	return fmt.Sprintf("%0.1fd", d.Hours()/24)
}

func roughTime(t time.Time) string {
	if t.IsZero() {
		return ""
	}

	ds := humanize.CustomRelTime(t, time.Now(), "ago", "from now", defaultMagnitudes)
	ds = strings.Replace(ds, " ago", "", 1)

	ds = strings.Replace(ds, " minutes", "min", 1)
	ds = strings.Replace(ds, " minute", "min", 1)

	ds = strings.Replace(ds, " hours", "h", 1)
	ds = strings.Replace(ds, " hour", "h", 1)

	ds = strings.Replace(ds, " days", "d", 1)
	ds = strings.Replace(ds, " day", "d", 1)

	ds = strings.Replace(ds, " months", "mo", 1)
	ds = strings.Replace(ds, " month", "mo", 1)

	ds = strings.Replace(ds, " years", "y", 1)
	ds = strings.Replace(ds, " year", "y", 1)

	ds = strings.Replace(ds, " weeks", "wk", 1)
	ds = strings.Replace(ds, " week", "wk", 1)

	return ds
}

func avatar(u *provider.User) template.HTML {
	return template.HTML(fmt.Sprintf(`<a href="%s" title="%s"><img src="%s" width="20" height="20"></a>`, u.GetHTMLURL(), u.GetLogin(), u.GetAvatarURL()))
}

// playerFilter filters out results for a particular player
func playerFilter(result *triage.CollectionResult, player int, players int) *triage.CollectionResult {
	klog.Infof("Filtering for player %d of %d ...", player, players)

	os := []*triage.RuleResult{}
	seen := map[string]*triage.Rule{}

	for _, o := range result.RuleResults {
		cs := []*hubbub.Conversation{}

		for _, i := range o.Items {
			if (i.ID % players) == (player - 1) {
				cs = append(cs, i)
			}
		}

		os = append(os, triage.SummarizeRuleResult(o.Rule, cs, seen))
	}

	return triage.SummarizeCollectionResult(result.Collection, os)
}

// Healthz returns a dummy healthz page - it's always happy here!
func (h *Handlers) Healthz() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(fmt.Sprintf("ok: %s", h.updater.Status())))
	}
}

// Threadz returns a threadz page
func (h *Handlers) Threadz() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		klog.Infof("GET %s: %v", r.URL.Path, r.Header)
		w.WriteHeader(http.StatusOK)
		w.Write(stack())
	}
}

// stack returns a formatted stack trace of all goroutines
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
func stack() []byte {
	buf := make([]byte, 1024)
	for {
		n := runtime.Stack(buf, true)
		if n < len(buf) {
			return buf[:n]
		}
		buf = make([]byte, 2*len(buf))
	}
}
<|endoftext|>


<commit_before>package ui

import (
	"net/http"

	"github.com/gorilla/mux"
	"github.com/rancher/apiserver/pkg/parse"
	"github.com/rancher/rancher/pkg/cacerts"
	v3 "github.com/rancher/rancher/pkg/generated/controllers/management.cattle.io/v3"
)

func New(prefs v3.PreferenceCache, clusterRegistrationTokenCache v3.ClusterRegistrationTokenCache) http.Handler {
	router := mux.NewRouter()
	router.UseEncodedPath()

	router.Handle("/", vue.IndexFile())
	router.Handle("/cacerts", cacerts.Handler(clusterRegistrationTokenCache))
	router.Handle("/asset-manifest.json", ember.ServeAsset())
	router.Handle("/crossdomain.xml", ember.ServeAsset())
	router.Handle("/dashboard", http.RedirectHandler("/dashboard/", http.StatusFound))
	router.Handle("/dashboard/", vue.IndexFile())
	router.Handle("/humans.txt", ember.ServeAsset())
	router.Handle("/index.txt", ember.ServeAsset())
	router.Handle("/robots.txt", ember.ServeAsset())
	router.Handle("/VERSION.txt", ember.ServeAsset())
	router.Handle("/favicon.png", vue.ServeFaviconDashboard())
	router.Handle("/favicon.ico", vue.ServeFaviconDashboard())
	router.Path("/verify-auth-azure").Queries("state", "{state}").HandlerFunc(redirectAuth)
	router.Path("/verify-auth").Queries("state", "{state}").HandlerFunc(redirectAuth)
	router.PathPrefix("/api-ui").Handler(ember.ServeAsset())
	router.PathPrefix("/assets/rancher-ui-driver-linode").Handler(emberAlwaysOffline.ServeAsset())
	router.PathPrefix("/assets").Handler(ember.ServeAsset())
	router.PathPrefix("/dashboard/").Handler(vue.IndexFileOnNotFound())
	router.PathPrefix("/ember-fetch").Handler(ember.ServeAsset())
	router.PathPrefix("/engines-dist").Handler(ember.ServeAsset())
	router.PathPrefix("/static").Handler(ember.ServeAsset())
	router.PathPrefix("/translations").Handler(ember.ServeAsset())
	router.NotFoundHandler = vueIndexUnlessAPI()

	return router
}

func vueIndexUnlessAPI() http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		if parse.IsBrowser(req, true) {
			vueIndex.ServeHTTP(rw, req)
		} else {
			http.NotFound(rw, req)
		}
	})
}
<commit_msg>Route /g* to ember index<commit_after>package ui

import (
	"net/http"

	"github.com/gorilla/mux"
	"github.com/rancher/apiserver/pkg/parse"
	"github.com/rancher/rancher/pkg/cacerts"
	v3 "github.com/rancher/rancher/pkg/generated/controllers/management.cattle.io/v3"
)

func New(prefs v3.PreferenceCache, clusterRegistrationTokenCache v3.ClusterRegistrationTokenCache) http.Handler {
	router := mux.NewRouter()
	router.UseEncodedPath()

	router.Handle("/", vue.IndexFile())
	router.Handle("/cacerts", cacerts.Handler(clusterRegistrationTokenCache))
	router.Handle("/asset-manifest.json", ember.ServeAsset())
	router.Handle("/crossdomain.xml", ember.ServeAsset())
	router.Handle("/dashboard", http.RedirectHandler("/dashboard/", http.StatusFound))
	router.Handle("/dashboard/", vue.IndexFile())
	router.Handle("/humans.txt", ember.ServeAsset())
	router.Handle("/index.txt", ember.ServeAsset())
	router.Handle("/robots.txt", ember.ServeAsset())
	router.Handle("/VERSION.txt", ember.ServeAsset())
	router.Handle("/favicon.png", vue.ServeFaviconDashboard())
	router.Handle("/favicon.ico", vue.ServeFaviconDashboard())
	router.Path("/verify-auth-azure").Queries("state", "{state}").HandlerFunc(redirectAuth)
	router.Path("/verify-auth").Queries("state", "{state}").HandlerFunc(redirectAuth)
	router.PathPrefix("/api-ui").Handler(ember.ServeAsset())
	router.PathPrefix("/assets/rancher-ui-driver-linode").Handler(emberAlwaysOffline.ServeAsset())
	router.PathPrefix("/assets").Handler(ember.ServeAsset())
	router.PathPrefix("/dashboard/").Handler(vue.IndexFileOnNotFound())
	router.PathPrefix("/ember-fetch").Handler(ember.ServeAsset())
	router.PathPrefix("/engines-dist").Handler(ember.ServeAsset())
	router.PathPrefix("/static").Handler(ember.ServeAsset())
	router.PathPrefix("/translations").Handler(ember.ServeAsset())
	router.PathPrefix("/g").Handler(ember.IndexFile())
	router.NotFoundHandler = vueIndexUnlessAPI()

	return router
}

func vueIndexUnlessAPI() http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		if parse.IsBrowser(req, true) {
			vueIndex.ServeHTTP(rw, req)
		} else {
			http.NotFound(rw, req)
		}
	})
}
<|endoftext|>


<commit_before>package player

import "github.com/lean-poker/poker-player-go/leanpoker"

const VERSION = "Default Go folding player"

func BetRequest(state *leanpoker.Game) int {
	return 1
}

func Showdown(state *leanpoker.Game) {

}

func Version() string {
	return VERSION
}
<commit_msg>Added version.<commit_after>package player

import "github.com/lean-poker/poker-player-go/leanpoker"

const VERSION = "Pasha Team Player 0.0.1"

func BetRequest(state *leanpoker.Game) int {
	return 1
}

func Showdown(state *leanpoker.Game) {

}

func Version() string {
	return VERSION
}
<|endoftext|>


<commit_before>package update

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"

	"github.com/experimental-platform/platconf/platconf"
)

type Opts struct {
	Channel string `short:"c" long:"channel" description:"Channel to be installed"`
	//Force bool `short:"f" long:"force" description:"Force installing the current latest release"`
}

func (o *Opts) Execute(args []string) error {
	os.Setenv("DOCKER_API_VERSION", "1.22")

	// TODO remove this later
	log.Fatal("The update functionality is not yet available.")

	err := runUpdate(o.Channel, "/")
	if err != nil {
		button(buttonError)
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	return nil
}

func runUpdate(specifiedChannel string, rootDir string) error {
	// prepare
	platconf.RequireRoot()
	button(buttonRainbow)
	setStatus("preparing", nil, nil)

	// get channel
	channel, channelSource := getChannel(specifiedChannel)
	logChannelDetection(channel, channelSource)

	// get release data
	releaseData, err := fetchReleaseData(channel)
	if err != nil {
		return err
	}

	// get & extract 'configure'
	configureImgData := releaseData.GetImageByName("quay.io/experimentalplatform/configure")
	if configureImgData == nil {
		return fmt.Errorf("configure image data not found in the manifest")
	}

	configureExtractDir, err := extractConfigure(configureImgData.Name)
	if err != nil {
		return nil
	}
	defer os.RemoveAll(configureExtractDir)

	// setup paths
	fmt.Println("Creating folders in '/etc/systemd' in case they don't exist yet.")
	err = setupPaths(rootDir)
	if err != nil {
		return err
	}

	return nil
}

func setupPaths(rootPrefix string) error {
	requiredPaths := []string{
		"/etc/protonet",
		"/etc/systemd/journal.conf.d",
		"/etc/systemd/system",
		"/etc/systemd/system/docker.service.d",
		"/etc/systemd/system/scripts",
		"/etc/udev/rules.d",
		"/opt/bin",
	}

	for _, p := range requiredPaths {
		err := os.MkdirAll(path.Join(rootPrefix, p), 0755)
		if err != nil {
			return err
		}
	}

	return nil
}

func fetchReleaseData(channel string) (*platconf.ReleaseManifestV2, error) {
	data, err := fetchReleaseJSON(channel)
	if err != nil {
		return nil, err
	}

	var manifest platconf.ReleaseManifestV2
	err = json.Unmarshal(data, &manifest)
	if err != nil {
		return nil, err
	}

	return &manifest, nil
}

func fetchReleaseJSON(channel string) ([]byte, error) {
	url := fmt.Sprintf("https://raw.githubusercontent.com/protonet/builds/master/manifest-v2/%s.json", channel)
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Response status code was %d.", resp.StatusCode)
	}

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return data, nil
}

func extractConfigure(tag string) (string, error) {
	tmpDir, err := ioutil.TempDir("", "platconf_")
	if err != nil {
		return "", err
	}

	log.Println("Pulling configure image")
	err = pullImage("quay.io/experimentalplatform/configure", tag)
	if err != nil {
		return "", err
	}

	log.Println("Extracting configure image")
	err = extractDockerImage("quay.io/experimentalplatform/configure", tag, tmpDir)
	if err != nil {
		os.RemoveAll(tmpDir)
		return "", err
	}

	return tmpDir, nil
}
<commit_msg>fix extractConfigure in runUpdate<commit_after>package update

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"

	"github.com/experimental-platform/platconf/platconf"
)

type Opts struct {
	Channel string `short:"c" long:"channel" description:"Channel to be installed"`
	//Force bool `short:"f" long:"force" description:"Force installing the current latest release"`
}

func (o *Opts) Execute(args []string) error {
	os.Setenv("DOCKER_API_VERSION", "1.22")

	// TODO remove this later
	log.Fatal("The update functionality is not yet available.")

	err := runUpdate(o.Channel, "/")
	if err != nil {
		button(buttonError)
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	return nil
}

func runUpdate(specifiedChannel string, rootDir string) error {
	// prepare
	platconf.RequireRoot()
	button(buttonRainbow)
	setStatus("preparing", nil, nil)

	// get channel
	channel, channelSource := getChannel(specifiedChannel)
	logChannelDetection(channel, channelSource)

	// get release data
	releaseData, err := fetchReleaseData(channel)
	if err != nil {
		return err
	}

	// get & extract 'configure'
	configureImgData := releaseData.GetImageByName("quay.io/experimentalplatform/configure")
	if configureImgData == nil {
		return fmt.Errorf("configure image data not found in the manifest")
	}

	configureExtractDir, err := extractConfigure(configureImgData.Tag)
	if err != nil {
		return err
	}
	defer os.RemoveAll(configureExtractDir)

	// setup paths
	fmt.Println("Creating folders in '/etc/systemd' in case they don't exist yet.")
	err = setupPaths(rootDir)
	if err != nil {
		return err
	}

	return nil
}

func setupPaths(rootPrefix string) error {
	requiredPaths := []string{
		"/etc/protonet",
		"/etc/systemd/journal.conf.d",
		"/etc/systemd/system",
		"/etc/systemd/system/docker.service.d",
		"/etc/systemd/system/scripts",
		"/etc/udev/rules.d",
		"/opt/bin",
	}

	for _, p := range requiredPaths {
		err := os.MkdirAll(path.Join(rootPrefix, p), 0755)
		if err != nil {
			return err
		}
	}

	return nil
}

func fetchReleaseData(channel string) (*platconf.ReleaseManifestV2, error) {
	data, err := fetchReleaseJSON(channel)
	if err != nil {
		return nil, err
	}

	var manifest platconf.ReleaseManifestV2
	err = json.Unmarshal(data, &manifest)
	if err != nil {
		return nil, err
	}

	return &manifest, nil
}

func fetchReleaseJSON(channel string) ([]byte, error) {
	url := fmt.Sprintf("https://raw.githubusercontent.com/protonet/builds/master/manifest-v2/%s.json", channel)
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Response status code was %d.", resp.StatusCode)
	}

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return data, nil
}

func extractConfigure(tag string) (string, error) {
	tmpDir, err := ioutil.TempDir("", "platconf_")
	if err != nil {
		return "", err
	}

	log.Println("Pulling configure image")
	err = pullImage("quay.io/experimentalplatform/configure", tag)
	if err != nil {
		return "", err
	}

	log.Println("Extracting configure image")
	err = extractDockerImage("quay.io/experimentalplatform/configure", tag, tmpDir)
	if err != nil {
		os.RemoveAll(tmpDir)
		return "", err
	}

	return tmpDir, nil
}
<|endoftext|>