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

// Package table implements ordered, grouped two dimensional relations.
//
// There are two related abstractions: Table and Grouping.
//
// A Table is an ordered relation of rows and columns. Each column is
// a Go slice and hence must be homogeneously typed, but different
// columns may have different types. All columns in a Table have the
// same number of rows.
//
// A Grouping generalizes a Table by grouping the Table's rows into
// zero or more groups. A Table is itself a Grouping with zero or one
// groups. Most operations take a Grouping and operate on each group
// independently, though some operations sub-divide or combine groups.
//
// The structures of both Tables and Groupings are immutable. Adding a
// column to a Table returns a new Table and adding a new Table to a
// Grouping returns a new Grouping.
package table

import (
	"fmt"
	"reflect"

	"github.com/aclements/go-gg/generic"
)

// A Table is an ordered two dimensional relation. It consists of a
// set of named columns. Each column is a sequence of values of a
// consistent type or a constant value. All (non-constant) columns
// have the same length.
//
// The zero value of Table is the "empty table": it has no rows and no
// columns. Note that a Table may have one or more columns, but no
// rows; such a Table is *not* considered empty.
//
// A Table is also a trivial Grouping. If a Table is empty, it has no
// groups and hence the zero value of Table is also the "empty group".
// Otherwise, it consists only of the root group, RootGroupID.
//
// A Table's structure is immutable. To construct a Table, start with
// an empty table and add columns to it using Add.
type Table struct {
	cols     map[string]Slice
	consts   map[string]interface{}
	colNames []string
	len      int
}

// A Grouping is a set of tables with identical sets of columns, each
// identified by a distinct GroupID.
//
// Visually, a Grouping can be thought of as follows:
//
//	   Col A  Col B  Col C
//	------ group /a ------
//	0   5.4    "x"     90
//	1   -.2    "y"     30
//	------ group /b ------
//	0   9.3    "a"     10
//
// Like a Table, a Grouping's structure is immutable. To construct a
// Grouping, start with a Table (typically the empty Table, which is
// also the empty Grouping) and add tables to it using AddTable.
//
// Despite the fact that GroupIDs form a hierarchy, a Grouping ignores
// this hierarchy and simply operates on a flat map of distinct
// GroupIDs to Tables.
type Grouping interface {
	// Columns returns the names of the columns in this Grouping,
	// or nil if there are no Tables or the group consists solely
	// of empty Tables. All Tables in this Grouping have the same
	// set of columns.
	Columns() []string

	// Tables returns the group IDs of the tables in this
	// Grouping.
	Tables() []GroupID

	// Table returns the Table in group gid, or nil if there is no
	// such Table.
	Table(gid GroupID) *Table

	// AddTable returns a new Grouping with the addition of Table
	// t bound to group gid. If t is nil, it returns a Grouping
	// with group gid removed. If t is the empty Table, this is a
	// no-op because the empty Table contains no groups.
	// Otherwise, AddTable first removes group gid if it already
	// exists, and then adds t as a new group. Table t must have
	// the same set of columns as any existing Tables in this
	// group and they must have identical types; otherwise,
	// AddTable will panic.
	//
	// TODO The same set or the same sequence of columns? Given
	// that I never use the sequence (except maybe for printing),
	// perhaps it doesn't matter.
	//
	// TODO This doesn't make it easy to combine two Groupings. It
	// could instead take a Grouping and reparent it.
	AddTable(gid GroupID, t *Table) Grouping
}

type groupedTable struct {
	tables   map[GroupID]*Table
	groups   []GroupID
	colNames []string
}

// A Slice is a Go slice value.
//
// This is primarily for documentation. There is no way to statically
// enforce this in Go; however, functions that expect a Slice will
// panic with a *generic.TypeError if passed a non-slice value.
type Slice interface{}

func reflectSlice(s Slice) reflect.Value {
	rv := reflect.ValueOf(s)
	if rv.Kind() != reflect.Slice {
		panic(&generic.TypeError{rv.Type(), nil, "is not a slice"})
	}
	return rv
}

// Add returns a new Table with a new column bound to data, or removes
// the named column if data is nil. If Table t already has a column
// with the given name, Add first removes it. Then, if data is
// non-nil, Add adds a new column. If data is non-nil, it must have
// the same length as any existing columns or Add will panic.
//
// TODO: "Add" suggests mutation. Should this be called "Plus"?
func (t *Table) Add(name string, data Slice) *Table {
	// TODO: Currently adding N columns is O(N^2). If we built the
	// column index only when it was asked for, the usual case of
	// adding a bunch of columns and then using the final table
	// would be O(N).

	if data == nil {
		// Remove the column.
		if _, ok := t.cols[name]; !ok {
			if _, ok := t.consts[name]; !ok {
				// Nothing to remove.
				return t
			}
		}
		if len(t.colNames) == 1 {
			// We're removing the only column. This is now
			// an empty table.
			return &Table{}
		}
	}

	// Create the new table, removing any existing column with the
	// same name.
	nt := t.cloneSans(name)
	if data == nil {
		return nt
	}

	rv := reflectSlice(data)
	dataLen := rv.Len()
	if len(nt.cols) == 0 {
		// First non-constant column.
		nt.cols[name] = data
		nt.len = dataLen
	} else if nt.len != dataLen {
		panic(fmt.Sprintf("cannot add column %q with %d elements to table with %d rows", name, dataLen, nt.len))
	} else {
		nt.cols[name] = data
	}
	nt.colNames = append(nt.colNames, name)

	return nt
}

// AddConst returns a new Table with a new constant column whose value
// is val. If Table t already has a column with this name, AddConst
// first removes it.
//
// A constant column has the same value in every row of the Table. It
// does not itself have an inherent length.
func (t *Table) AddConst(name string, val interface{}) *Table {
	// Clone and remove any existing column with the same name.
	nt := t.cloneSans(name)
	nt.consts[name] = val
	nt.colNames = append(nt.colNames, name)
	return nt
}

// cloneSans returns a clone of t without column name.
func (t *Table) cloneSans(name string) *Table {
	// Create the new table, removing any existing column with the
	// same name.
	nt := &Table{make(map[string]Slice), make(map[string]interface{}), []string{}, t.len}
	for _, name2 := range t.colNames {
		if name != name2 {
			if c, ok := t.cols[name2]; ok {
				nt.cols[name2] = c
			}
			if cv, ok := t.consts[name2]; ok {
				nt.consts[name2] = cv
			}
			nt.colNames = append(nt.colNames, name2)
		}
	}
	return nt
}

// Len returns the number of rows in Table t.
func (t *Table) Len() int {
	return t.len
}

// Columns returns the names of the columns in Table t, or nil if this
// Table is empty.
func (t *Table) Columns() []string {
	return t.colNames
}

// Column returns the slice of data in column name of Table t, or nil
// if there is no such column. If name is a constant column, this
// returns a slice with the constant value repeated to the length of
// the Table.
func (t *Table) Column(name string) Slice {
	if c, ok := t.cols[name]; ok {
		// It's a regular column or a constant column with a
		// cached expansion.
		return c
	}

	if cv, ok := t.consts[name]; ok {
		// Expand the constant column and cache the result.
		expanded := generic.Repeat(cv, t.len)
		t.cols[name] = expanded
		return expanded
	}

	return nil
}

// MustColumn is like Column, but panics if there is no such column.
func (t *Table) MustColumn(name string) Slice {
	if c := t.Column(name); c != nil {
		return c
	}
	panic(fmt.Sprintf("unknown column %q", name))
}

// Const returns the value of constant column name. If this column
// does not exist or is not a constant column, Const returns nil,
// false.
func (t *Table) Const(name string) (val interface{}, ok bool) {
	cv, ok := t.consts[name]
	return cv, ok
}

// isEmpty returns true if t is an empty Table, meaning it has no rows
// or columns.
func (t *Table) isEmpty() bool {
	return t.colNames == nil
}

// Tables returns the groups IDs in this Table. If t is empty, there
// are no group IDs. Otherwise, there is only RootGroupID.
func (t *Table) Tables() []GroupID {
	if t.isEmpty() {
		return []GroupID{}
	}
	return []GroupID{RootGroupID}
}

// Table returns t if gid is RootGroupID and t is not empty; otherwise
// it returns nil.
func (t *Table) Table(gid GroupID) *Table {
	if gid == RootGroupID && !t.isEmpty() {
		return t
	}
	return nil
}

// AddTable returns a Grouping with up to two groups: first, t, if
// non-empty, is bound to RootGroupID; then t2, if non-empty, is bound
// to group gid.
//
// Typically this is used to build up a Grouping by starting with an
// empty Table and adding Tables to it.
func (t *Table) AddTable(gid GroupID, t2 *Table) Grouping {
	if t2 == nil {
		if gid == RootGroupID {
			return new(Table)
		}
		return t
	} else if t2.isEmpty() {
		return t
	} else if gid == RootGroupID {
		return t2
	}

	g := &groupedTable{
		tables:   map[GroupID]*Table{},
		groups:   []GroupID{},
		colNames: nil,
	}
	return g.AddTable(RootGroupID, t).AddTable(gid, t2)
}

func (g *groupedTable) Columns() []string {
	return g.colNames
}

func (g *groupedTable) Tables() []GroupID {
	return g.groups
}

func (g *groupedTable) Table(gid GroupID) *Table {
	return g.tables[gid]
}

func (g *groupedTable) AddTable(gid GroupID, t *Table) Grouping {
	// TODO: Currently adding N tables is O(N^2).

	if t != nil && t.isEmpty() {
		// Adding an empty table has no effect.
		return g
	}

	// Create the new grouped table, removing any existing table
	// with the same GID.
	ng := &groupedTable{map[GroupID]*Table{}, []GroupID{}, g.colNames}
	for _, gid2 := range g.groups {
		if gid != gid2 {
			ng.tables[gid2] = g.tables[gid2]
			ng.groups = append(ng.groups, gid2)
		}
	}
	if t == nil {
		if len(ng.groups) == 0 {
			ng.colNames = nil
		}
		return ng
	}

	if len(ng.groups) == 0 {
		ng.tables[gid] = t
		ng.groups = append(ng.groups, gid)
		ng.colNames = t.Columns()
		return ng
	}

	// Check that t's column structure matches.
	tBase := ng.tables[ng.groups[0]]
	for _, col := range ng.colNames {
		var t0, t1 reflect.Type
		if c, ok := tBase.cols[col]; ok {
			t0 = reflect.TypeOf(c).Elem()
		} else {
			t0 = reflect.TypeOf(tBase.consts[col])
		}
		if c, ok := t.cols[col]; ok {
			t1 = reflect.TypeOf(c).Elem()
		} else if cv, ok := t.consts[col]; ok {
			t1 = reflect.TypeOf(cv)
		} else {
			panic(fmt.Sprintf("table missing column %q", col))
		}
		if t0 != t1 {
			panic(&generic.TypeError{t0, t1, fmt.Sprintf("for column %q are not the same", col)})
		}
	}
	if len(t.cols) != len(ng.colNames) {
		// t has a column the group doesn't.
		colSet := map[string]bool{}
		for _, col := range ng.colNames {
			colSet[col] = true
		}
		for col := range t.cols {
			if !colSet[col] {
				panic(fmt.Sprintf("table has extra column %q", col))
			}
		}
	}

	ng.tables[gid] = t
	ng.groups = append(ng.groups, gid)
	return ng
}

// addTableUpdate adds table t to g by mutation. It assumes that the
// column structure matches. This is meant for internal use only.
//
// TODO: It would be nice if external users could achieve similar
// asymptotic performance safely. There could be a GroupBuilder API
// that does this and then freezes into a Grouped.
func (g *groupedTable) addTableUpdate(gid GroupID, t *Table) {
	if len(g.groups) == 0 {
		g.tables = map[GroupID]*Table{gid: t}
		g.groups = []GroupID{gid}
		g.colNames = t.Columns()
		return
	}

	if _, ok := g.tables[gid]; !ok {
		g.groups = append(g.groups, gid)
	}
	g.tables[gid] = t
}
<commit_msg>table: thoughts on high-level interface changes<commit_after>// Copyright 2016 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.

// Package table implements ordered, grouped two dimensional relations.
//
// There are two related abstractions: Table and Grouping.
//
// A Table is an ordered relation of rows and columns. Each column is
// a Go slice and hence must be homogeneously typed, but different
// columns may have different types. All columns in a Table have the
// same number of rows.
//
// A Grouping generalizes a Table by grouping the Table's rows into
// zero or more groups. A Table is itself a Grouping with zero or one
// groups. Most operations take a Grouping and operate on each group
// independently, though some operations sub-divide or combine groups.
//
// The structures of both Tables and Groupings are immutable. Adding a
// column to a Table returns a new Table and adding a new Table to a
// Grouping returns a new Grouping.
package table

import (
	"fmt"
	"reflect"

	"github.com/aclements/go-gg/generic"
)

// TODO
//
// Rename Table to T?
//
// Make Add replace column in place?
//
// Require same columns sequence for adding a table?
//
// Make Table an interface? Then columns could be constructed lazily.
//
// Have separate builder/viewer APIs?

// A Table is an ordered two dimensional relation. It consists of a
// set of named columns. Each column is a sequence of values of a
// consistent type or a constant value. All (non-constant) columns
// have the same length.
//
// The zero value of Table is the "empty table": it has no rows and no
// columns. Note that a Table may have one or more columns, but no
// rows; such a Table is *not* considered empty.
//
// A Table is also a trivial Grouping. If a Table is empty, it has no
// groups and hence the zero value of Table is also the "empty group".
// Otherwise, it consists only of the root group, RootGroupID.
//
// A Table's structure is immutable. To construct a Table, start with
// an empty table and add columns to it using Add.
type Table struct {
	cols     map[string]Slice
	consts   map[string]interface{}
	colNames []string
	len      int
}

// A Grouping is a set of tables with identical sets of columns, each
// identified by a distinct GroupID.
//
// Visually, a Grouping can be thought of as follows:
//
//	   Col A  Col B  Col C
//	------ group /a ------
//	0   5.4    "x"     90
//	1   -.2    "y"     30
//	------ group /b ------
//	0   9.3    "a"     10
//
// Like a Table, a Grouping's structure is immutable. To construct a
// Grouping, start with a Table (typically the empty Table, which is
// also the empty Grouping) and add tables to it using AddTable.
//
// Despite the fact that GroupIDs form a hierarchy, a Grouping ignores
// this hierarchy and simply operates on a flat map of distinct
// GroupIDs to Tables.
type Grouping interface {
	// Columns returns the names of the columns in this Grouping,
	// or nil if there are no Tables or the group consists solely
	// of empty Tables. All Tables in this Grouping have the same
	// set of columns.
	Columns() []string

	// Tables returns the group IDs of the tables in this
	// Grouping.
	Tables() []GroupID

	// Table returns the Table in group gid, or nil if there is no
	// such Table.
	Table(gid GroupID) *Table

	// AddTable returns a new Grouping with the addition of Table
	// t bound to group gid. If t is nil, it returns a Grouping
	// with group gid removed. If t is the empty Table, this is a
	// no-op because the empty Table contains no groups.
	// Otherwise, AddTable first removes group gid if it already
	// exists, and then adds t as a new group. Table t must have
	// the same set of columns as any existing Tables in this
	// group and they must have identical types; otherwise,
	// AddTable will panic.
	//
	// TODO The same set or the same sequence of columns? Given
	// that I never use the sequence (except maybe for printing),
	// perhaps it doesn't matter.
	//
	// TODO This doesn't make it easy to combine two Groupings. It
	// could instead take a Grouping and reparent it.
	AddTable(gid GroupID, t *Table) Grouping
}

type groupedTable struct {
	tables   map[GroupID]*Table
	groups   []GroupID
	colNames []string
}

// A Slice is a Go slice value.
//
// This is primarily for documentation. There is no way to statically
// enforce this in Go; however, functions that expect a Slice will
// panic with a *generic.TypeError if passed a non-slice value.
type Slice interface{}

func reflectSlice(s Slice) reflect.Value {
	rv := reflect.ValueOf(s)
	if rv.Kind() != reflect.Slice {
		panic(&generic.TypeError{rv.Type(), nil, "is not a slice"})
	}
	return rv
}

// Add returns a new Table with a new column bound to data, or removes
// the named column if data is nil. If Table t already has a column
// with the given name, Add first removes it. Then, if data is
// non-nil, Add adds a new column. If data is non-nil, it must have
// the same length as any existing columns or Add will panic.
//
// TODO: "Add" suggests mutation. Should this be called "Plus"?
func (t *Table) Add(name string, data Slice) *Table {
	// TODO: Currently adding N columns is O(N^2). If we built the
	// column index only when it was asked for, the usual case of
	// adding a bunch of columns and then using the final table
	// would be O(N).

	if data == nil {
		// Remove the column.
		if _, ok := t.cols[name]; !ok {
			if _, ok := t.consts[name]; !ok {
				// Nothing to remove.
				return t
			}
		}
		if len(t.colNames) == 1 {
			// We're removing the only column. This is now
			// an empty table.
			return &Table{}
		}
	}

	// Create the new table, removing any existing column with the
	// same name.
	nt := t.cloneSans(name)
	if data == nil {
		return nt
	}

	rv := reflectSlice(data)
	dataLen := rv.Len()
	if len(nt.cols) == 0 {
		// First non-constant column.
		nt.cols[name] = data
		nt.len = dataLen
	} else if nt.len != dataLen {
		panic(fmt.Sprintf("cannot add column %q with %d elements to table with %d rows", name, dataLen, nt.len))
	} else {
		nt.cols[name] = data
	}
	nt.colNames = append(nt.colNames, name)

	return nt
}

// AddConst returns a new Table with a new constant column whose value
// is val. If Table t already has a column with this name, AddConst
// first removes it.
//
// A constant column has the same value in every row of the Table. It
// does not itself have an inherent length.
func (t *Table) AddConst(name string, val interface{}) *Table {
	// Clone and remove any existing column with the same name.
	nt := t.cloneSans(name)
	nt.consts[name] = val
	nt.colNames = append(nt.colNames, name)
	return nt
}

// cloneSans returns a clone of t without column name.
func (t *Table) cloneSans(name string) *Table {
	// Create the new table, removing any existing column with the
	// same name.
	nt := &Table{make(map[string]Slice), make(map[string]interface{}), []string{}, t.len}
	for _, name2 := range t.colNames {
		if name != name2 {
			if c, ok := t.cols[name2]; ok {
				nt.cols[name2] = c
			}
			if cv, ok := t.consts[name2]; ok {
				nt.consts[name2] = cv
			}
			nt.colNames = append(nt.colNames, name2)
		}
	}
	return nt
}

// Len returns the number of rows in Table t.
func (t *Table) Len() int {
	return t.len
}

// Columns returns the names of the columns in Table t, or nil if this
// Table is empty.
func (t *Table) Columns() []string {
	return t.colNames
}

// Column returns the slice of data in column name of Table t, or nil
// if there is no such column. If name is a constant column, this
// returns a slice with the constant value repeated to the length of
// the Table.
func (t *Table) Column(name string) Slice {
	if c, ok := t.cols[name]; ok {
		// It's a regular column or a constant column with a
		// cached expansion.
		return c
	}

	if cv, ok := t.consts[name]; ok {
		// Expand the constant column and cache the result.
		expanded := generic.Repeat(cv, t.len)
		t.cols[name] = expanded
		return expanded
	}

	return nil
}

// MustColumn is like Column, but panics if there is no such column.
func (t *Table) MustColumn(name string) Slice {
	if c := t.Column(name); c != nil {
		return c
	}
	panic(fmt.Sprintf("unknown column %q", name))
}

// Const returns the value of constant column name. If this column
// does not exist or is not a constant column, Const returns nil,
// false.
func (t *Table) Const(name string) (val interface{}, ok bool) {
	cv, ok := t.consts[name]
	return cv, ok
}

// isEmpty returns true if t is an empty Table, meaning it has no rows
// or columns.
func (t *Table) isEmpty() bool {
	return t.colNames == nil
}

// Tables returns the groups IDs in this Table. If t is empty, there
// are no group IDs. Otherwise, there is only RootGroupID.
func (t *Table) Tables() []GroupID {
	if t.isEmpty() {
		return []GroupID{}
	}
	return []GroupID{RootGroupID}
}

// Table returns t if gid is RootGroupID and t is not empty; otherwise
// it returns nil.
func (t *Table) Table(gid GroupID) *Table {
	if gid == RootGroupID && !t.isEmpty() {
		return t
	}
	return nil
}

// AddTable returns a Grouping with up to two groups: first, t, if
// non-empty, is bound to RootGroupID; then t2, if non-empty, is bound
// to group gid.
//
// Typically this is used to build up a Grouping by starting with an
// empty Table and adding Tables to it.
func (t *Table) AddTable(gid GroupID, t2 *Table) Grouping {
	if t2 == nil {
		if gid == RootGroupID {
			return new(Table)
		}
		return t
	} else if t2.isEmpty() {
		return t
	} else if gid == RootGroupID {
		return t2
	}

	g := &groupedTable{
		tables:   map[GroupID]*Table{},
		groups:   []GroupID{},
		colNames: nil,
	}
	return g.AddTable(RootGroupID, t).AddTable(gid, t2)
}

func (g *groupedTable) Columns() []string {
	return g.colNames
}

func (g *groupedTable) Tables() []GroupID {
	return g.groups
}

func (g *groupedTable) Table(gid GroupID) *Table {
	return g.tables[gid]
}

func (g *groupedTable) AddTable(gid GroupID, t *Table) Grouping {
	// TODO: Currently adding N tables is O(N^2).

	if t != nil && t.isEmpty() {
		// Adding an empty table has no effect.
		return g
	}

	// Create the new grouped table, removing any existing table
	// with the same GID.
	ng := &groupedTable{map[GroupID]*Table{}, []GroupID{}, g.colNames}
	for _, gid2 := range g.groups {
		if gid != gid2 {
			ng.tables[gid2] = g.tables[gid2]
			ng.groups = append(ng.groups, gid2)
		}
	}
	if t == nil {
		if len(ng.groups) == 0 {
			ng.colNames = nil
		}
		return ng
	}

	if len(ng.groups) == 0 {
		ng.tables[gid] = t
		ng.groups = append(ng.groups, gid)
		ng.colNames = t.Columns()
		return ng
	}

	// Check that t's column structure matches.
	tBase := ng.tables[ng.groups[0]]
	for _, col := range ng.colNames {
		var t0, t1 reflect.Type
		if c, ok := tBase.cols[col]; ok {
			t0 = reflect.TypeOf(c).Elem()
		} else {
			t0 = reflect.TypeOf(tBase.consts[col])
		}
		if c, ok := t.cols[col]; ok {
			t1 = reflect.TypeOf(c).Elem()
		} else if cv, ok := t.consts[col]; ok {
			t1 = reflect.TypeOf(cv)
		} else {
			panic(fmt.Sprintf("table missing column %q", col))
		}
		if t0 != t1 {
			panic(&generic.TypeError{t0, t1, fmt.Sprintf("for column %q are not the same", col)})
		}
	}
	if len(t.cols) != len(ng.colNames) {
		// t has a column the group doesn't.
		colSet := map[string]bool{}
		for _, col := range ng.colNames {
			colSet[col] = true
		}
		for col := range t.cols {
			if !colSet[col] {
				panic(fmt.Sprintf("table has extra column %q", col))
			}
		}
	}

	ng.tables[gid] = t
	ng.groups = append(ng.groups, gid)
	return ng
}

// addTableUpdate adds table t to g by mutation. It assumes that the
// column structure matches. This is meant for internal use only.
//
// TODO: It would be nice if external users could achieve similar
// asymptotic performance safely. There could be a GroupBuilder API
// that does this and then freezes into a Grouped.
func (g *groupedTable) addTableUpdate(gid GroupID, t *Table) {
	if len(g.groups) == 0 {
		g.tables = map[GroupID]*Table{gid: t}
		g.groups = []GroupID{gid}
		g.colNames = t.Columns()
		return
	}

	if _, ok := g.tables[gid]; !ok {
		g.groups = append(g.groups, gid)
	}
	g.tables[gid] = t
}
<|endoftext|>


<commit_before>package todos

import (
	"net/http"
	"strconv"

	"github.com/13pinj/todoapp/controllers"
	"github.com/13pinj/todoapp/models/todo"
	"github.com/13pinj/todoapp/models/todolist"
	"github.com/13pinj/todoapp/models/user"
	"github.com/gin-gonic/gin"
)

// Index выводит страницу со всеми списками дел текущего пользователя.
// Если пользователь незалогинен, перенаправляет на главную.
// GET /
func Index(c *gin.Context) {
	u, ok := user.FromContext(c)
	if !ok {
		ctl.Redirect(c, "/")
		return
	}
	ctl.RenderHTML(c, "todos_index.tmpl", gin.H{
		"Lists": u.Lists,
	})
}

// CreateList создает новый список дел с заголовком из POST-параметра title
// и перенаправляет на страницу этого списка.
// POST /list-create
func CreateList(c *gin.Context) {
	u, ok := user.FromContext(c)
	if !ok {
		ctl.Render403(c)
		return
	}
	l := todolist.New(c.PostForm("title"))
	l.UserID = u.ID
	err := l.Save()
	if err != nil {
		c.String(http.StatusOK, err.Error())
		return
	}
	ctl.Redirect(c, l.Path())
}

// ShowList выводит страницу списка дел, на которой отображается его заголовок
// и содержание.
// GET /list/:id
func ShowList(c *gin.Context) {
	l, ok := getlist(c)
	if !ok {
		return
	}
	ctl.RenderHTML(c, "todos_show.tmpl", gin.H{
		"List": l,
	})
}

// UpdateList изменяет заголовок списка на тот, который был получен
// POST-параметре title и перенаправляет на страницу этого списка.
// POST /list/:id/update
func UpdateList(c *gin.Context) {
	l, ok := getlist(c)
	if !ok {
		return
	}
	l.Title = c.PostForm("title")
	err := l.Save()
	if err != nil {
		ctl.RenderJSON(c, gin.H{
			"status": "error",
			"error":  err.Error(),
		})
		return
	}
	ctl.RenderJSON(c, gin.H{
		"status": "success",
	})
}

// DestroyList стирает список из базы и перенаправляет на главную.
// POST /list/:id/destroy
func DestroyList(c *gin.Context) {
	l, ok := getlist(c)
	if !ok {
		return
	}
	l.Destroy()
	c.Status(http.StatusOK)
}

// CreateTask создает новое задание в списке с текстом
// из POST-параметра label и перенаправляет на страницу списка.
// POST /list/:id/add
func CreateTask(c *gin.Context) {
	l, ok := getlist(c)
	if !ok {
		return
	}
	err := l.Add(c.PostForm("label"))
	if err != nil {
		ctl.RenderHTML(c, "todos_show.tmpl", gin.H{
			"List":       l,
			"AlertError": err.Error(),
		})
		return
	}
	ctl.Redirect(c, l.Path())
}

// UpdateTask изменяет поля задания используя POST-параметры done и label.
// Поля, для который не заданы значения в параметрах запроса, должны остаться
// неизменными.
// После выполнения запроса UpdateTask перенаправляет клиент на страницу списка.
// POST /task/:id/update
func UpdateTask(c *gin.Context) {
	td, _, ok := gettask(c)
	if !ok {
		return
	}
	label, ok := c.GetPostForm("label")
	if ok {
		td.Label = label
	}
	done, ok := c.GetPostForm("done")
	if ok {
		td.Done = (done != "0")
	}
	err := td.Save()
	if err != nil {
		ctl.RenderJSON(c, gin.H{
			"status": "error",
			"error":  err.Error(),
		})
		return
	}
	ctl.RenderJSON(c, gin.H{
		"status": "success",
	})
}

// DestroyTask стирает задание из списка и перенаправляет на страницу списка.
// POST /task/:id/destroy
func DestroyTask(c *gin.Context) {
	td, _, ok := gettask(c)
	if !ok {
		return
	}
	td.Destroy()
	c.Status(http.StatusOK)
}

func getlist(c *gin.Context) (*todolist.TodoList, bool) {
	u, ok := user.FromContext(c)
	if !ok {
		ctl.Render403(c)
		return nil, false
	}
	id, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		ctl.Render404(c)
		return nil, false
	}
	l, ok := todolist.Find(uint(id))
	if !ok {
		ctl.Render404(c)
		return nil, false
	}
	if l.UserID != u.ID {
		ctl.Render403(c)
		return nil, false
	}
	return l, true
}

func gettask(c *gin.Context) (*todo.Todo, *todolist.TodoList, bool) {
	u, ok := user.FromContext(c)
	if !ok {
		ctl.Render403(c)
		return nil, nil, false
	}
	id, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		ctl.Render404(c)
		return nil, nil, false
	}
	td, ok := todo.Find(uint(id))
	if !ok {
		ctl.Render404(c)
		return nil, nil, false
	}
	l, ok := todolist.Find(td.TodoListID)
	if !ok {
		ctl.Render500(c)
		return nil, nil, false
	}
	if u.ID != l.UserID {
		ctl.Render403(c)
		return nil, nil, false
	}
	return td, l, true
}
<commit_msg>Fix controller output<commit_after>package todos

import (
	"net/http"
	"strconv"

	"github.com/13pinj/todoapp/controllers"
	"github.com/13pinj/todoapp/models/todo"
	"github.com/13pinj/todoapp/models/todolist"
	"github.com/13pinj/todoapp/models/user"
	"github.com/gin-gonic/gin"
)

// Index выводит страницу со всеми списками дел текущего пользователя.
// Если пользователь незалогинен, перенаправляет на главную.
// GET /
func Index(c *gin.Context) {
	u, ok := user.FromContext(c)
	if !ok {
		ctl.Redirect(c, "/")
		return
	}
	ctl.RenderHTML(c, "todos_index.tmpl", gin.H{
		"Lists": u.Lists,
	})
}

// CreateList создает новый список дел с заголовком из POST-параметра title
// и перенаправляет на страницу этого списка.
// POST /list-create
func CreateList(c *gin.Context) {
	u, ok := user.FromContext(c)
	if !ok {
		ctl.Render403(c)
		return
	}
	l := todolist.New(c.PostForm("title"))
	l.UserID = u.ID
	err := l.Save()
	if err != nil {
		ctl.RenderHTML(c, "todos_index.tmpl", gin.H{
			"Lists":      u.Lists,
			"AlertError": err.Error(),
		})
		return
	}
	ctl.Redirect(c, l.Path())
}

// ShowList выводит страницу списка дел, на которой отображается его заголовок
// и содержание.
// GET /list/:id
func ShowList(c *gin.Context) {
	l, ok := getlist(c)
	if !ok {
		return
	}
	u, _ := user.FromContext(c)
	ctl.RenderHTML(c, "todos_show.tmpl", gin.H{
		"List":  l,
		"Lists": u.Lists,
	})
}

// UpdateList изменяет заголовок списка на тот, который был получен
// POST-параметре title и перенаправляет на страницу этого списка.
// POST /list/:id/update
func UpdateList(c *gin.Context) {
	l, ok := getlist(c)
	if !ok {
		return
	}
	l.Title = c.PostForm("title")
	err := l.Save()
	if err != nil {
		ctl.RenderJSON(c, gin.H{
			"status": "error",
			"error":  err.Error(),
		})
		return
	}
	ctl.RenderJSON(c, gin.H{
		"status": "success",
	})
}

// DestroyList стирает список из базы и перенаправляет на главную.
// POST /list/:id/destroy
func DestroyList(c *gin.Context) {
	l, ok := getlist(c)
	if !ok {
		return
	}
	l.Destroy()
	c.Status(http.StatusOK)
}

// CreateTask создает новое задание в списке с текстом
// из POST-параметра label и перенаправляет на страницу списка.
// POST /list/:id/add
func CreateTask(c *gin.Context) {
	l, ok := getlist(c)
	if !ok {
		return
	}
	err := l.Add(c.PostForm("label"))
	if err != nil {
		ctl.RenderHTML(c, "todos_show.tmpl", gin.H{
			"List":       l,
			"AlertError": err.Error(),
		})
		return
	}
	ctl.Redirect(c, l.Path())
}

// UpdateTask изменяет поля задания используя POST-параметры done и label.
// Поля, для который не заданы значения в параметрах запроса, должны остаться
// неизменными.
// После выполнения запроса UpdateTask перенаправляет клиент на страницу списка.
// POST /task/:id/update
func UpdateTask(c *gin.Context) {
	td, _, ok := gettask(c)
	if !ok {
		return
	}
	label, ok := c.GetPostForm("label")
	if ok {
		td.Label = label
	}
	done, ok := c.GetPostForm("done")
	if ok {
		td.Done = (done != "0")
	}
	err := td.Save()
	if err != nil {
		ctl.RenderJSON(c, gin.H{
			"status": "error",
			"error":  err.Error(),
		})
		return
	}
	ctl.RenderJSON(c, gin.H{
		"status": "success",
	})
}

// DestroyTask стирает задание из списка и перенаправляет на страницу списка.
// POST /task/:id/destroy
func DestroyTask(c *gin.Context) {
	td, _, ok := gettask(c)
	if !ok {
		return
	}
	td.Destroy()
	c.Status(http.StatusOK)
}

func getlist(c *gin.Context) (*todolist.TodoList, bool) {
	u, ok := user.FromContext(c)
	if !ok {
		ctl.Render403(c)
		return nil, false
	}
	id, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		ctl.Render404(c)
		return nil, false
	}
	l, ok := todolist.Find(uint(id))
	if !ok {
		ctl.Render404(c)
		return nil, false
	}
	if l.UserID != u.ID {
		ctl.Render403(c)
		return nil, false
	}
	return l, true
}

func gettask(c *gin.Context) (*todo.Todo, *todolist.TodoList, bool) {
	u, ok := user.FromContext(c)
	if !ok {
		ctl.Render403(c)
		return nil, nil, false
	}
	id, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		ctl.Render404(c)
		return nil, nil, false
	}
	td, ok := todo.Find(uint(id))
	if !ok {
		ctl.Render404(c)
		return nil, nil, false
	}
	l, ok := todolist.Find(td.TodoListID)
	if !ok {
		ctl.Render500(c)
		return nil, nil, false
	}
	if u.ID != l.UserID {
		ctl.Render403(c)
		return nil, nil, false
	}
	return td, l, true
}
<|endoftext|>


<commit_before>// Copyright Istio 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 main

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"github.com/spf13/cobra"

	"istio.io/istio/pilot/pkg/util/sets"
	testenv "istio.io/istio/pkg/test/env"
	"istio.io/pkg/log"
	pkgversion "istio.io/pkg/version"
)

func main() {
	rootCmd.Flags().StringVar(&args.Hub, "hub", args.Hub, "docker hub")
	rootCmd.Flags().StringVar(&args.Tag, "tag", args.Tag, "docker tag")

	rootCmd.Flags().StringVar(&args.BaseVersion, "base-version", args.BaseVersion, "base version to use")
	rootCmd.Flags().StringVar(&args.ProxyVersion, "proxy-version", args.ProxyVersion, "proxy version to use")
	rootCmd.Flags().StringVar(&args.IstioVersion, "istio-version", args.IstioVersion, "istio version to use")

	rootCmd.Flags().StringSliceVar(&args.Targets, "targets", args.Targets, "targets to build")
	rootCmd.Flags().StringSliceVar(&args.Variants, "variants", args.Variants, "variants to build")
	rootCmd.Flags().StringSliceVar(&args.Architectures, "architecures", args.Architectures, "architectures to build")
	rootCmd.Flags().BoolVar(&args.Push, "push", args.Push, "push targets to registry")
	rootCmd.Flags().BoolVar(&args.Save, "save", args.Save, "save targets to tar.gz")
	rootCmd.Flags().BoolVar(&args.BuildxEnabled, "buildx", args.BuildxEnabled, "use buildx for builds")
	rootCmd.Flags().BoolVar(&version, "version", version, "show build version")

	if err := rootCmd.Execute(); err != nil {
		os.Exit(-1)
	}
}

var rootCmd = &cobra.Command{
	Use:   "",
	Short: "Builds Istio docker images",
	RunE: func(cmd *cobra.Command, _ []string) error {
		if version {
			fmt.Println(pkgversion.Info.GitRevision)
			os.Exit(0)
		}
		log.Infof("Args: %+v", args)
		if args.Push && args.Save {
			// TODO(https://github.com/moby/buildkit/issues/1555) support both
			return fmt.Errorf("--push and --save are mutually exclusive")
		}
		_, inCI := os.LookupEnv("CI")
		if (args.Hub == "docker.io/istio" || args.Hub == "istio" || args.Hub == "gcr.io/istio-release") && !inCI {
			// Safety check against developer error. If they have a legitimate use case, they can set CI var
			return fmt.Errorf("pushing to official registry only supported in CI")
		}

		tarFiles, err := ConstructBakeFile(args)
		if err != nil {
			return err
		}
		targets := []string{}
		for _, t := range args.Targets {
			targets = append(targets, fmt.Sprintf("docker.%s", t))
		}
		if err := RunMake(args, targets...); err != nil {
			return err
		}
		if err := RunBake(args); err != nil {
			return err
		}
		if err := RunSave(args, tarFiles); err != nil {
			return err
		}

		return nil
	},
}

func RunBake(args Args) error {
	out := filepath.Join(testenv.LocalOut, "dockerx_build", "docker-bake.json")
	_ = os.MkdirAll(filepath.Join(testenv.LocalOut, "release", "docker"), 0o755)
	if err := createBuildxBuilderIfNeeded(args); err != nil {
		return err
	}
	c := VerboseCommand("docker", "buildx", "bake", "-f", out, "all")
	c.Stdout = os.Stdout
	return c.Run()
}

// --save requires a custom builder. Automagically create it if needed
func createBuildxBuilderIfNeeded(a Args) error {
	if !a.Save {
		return nil
	}
	if _, f := os.LookupEnv("CI"); !f {
		// For now only do this for CI, we do not want to mess with users config. And users rarely use --save
		return nil
	}
	return exec.Command("sh", "-c", `
export DOCKER_CLI_EXPERIMENTAL=enabled
if ! docker buildx ls | grep -q container-builder; then
  docker buildx create --driver-opt network=host,image=gcr.io/istio-testing/buildkit:v0.9.2 --name container-builder --buildkitd-flags="--debug"
  # Pre-warm the builder. If it fails, fetch logs, but continue
  docker buildx inspect --bootstrap container-builder || docker logs buildx_buildkit_container-builder0 || true
fi
docker buildx use container-builder`).Run()
}

// RunSave handles the --save portion. Part of this is done by buildx natively - it will emit .tar
// files. We need tar.gz though, so we have a bit more work to do
func RunSave(a Args, files map[string]string) error {
	if !a.Save {
		return nil
	}

	root := filepath.Join(testenv.LocalOut, "release", "docker")
	for name, alias := range files {
		// Gzip the file
		if err := VerboseCommand("gzip", "--fast", "--force", filepath.Join(root, name+".tar")).Run(); err != nil {
			return err
		}
		// If it has an alias (ie pilot-debug -> pilot), copy it over. Copy after gzip to avoid double compute.
		if alias != "" {
			if err := CopyFile(filepath.Join(root, name+".tar.gz"), filepath.Join(root, alias+".tar.gz")); err != nil {
				return err
			}
		}
	}

	return nil
}

func CopyFile(src, dst string) error {
	log.Infof("Copying %v -> %v", src, dst)
	in, err := os.Open(src)
	if err != nil {
		return fmt.Errorf("failed to open file %v to copy: %v", src, err)
	}
	defer in.Close()

	if err := os.MkdirAll(path.Join(dst, ".."), 0o750); err != nil {
		return fmt.Errorf("failed to make destination directory %v: %v", dst, err)
	}
	out, err := os.Create(dst)
	if err != nil {
		return fmt.Errorf("failed to create file %v to copy to: %v", dst, err)
	}
	defer out.Close()

	if _, err = io.Copy(out, in); err != nil {
		return fmt.Errorf("failed to copy %v to %v: %v", src, dst, err)
	}

	return nil
}

func sp(s string) *string {
	return &s
}

// ConstructBakeFile constructs a docker-bake.json to be passed to `docker buildx bake`.
// This command is an extremely powerful command to build many images in parallel, but is pretty undocumented.
// Most info can be found from the source at https://github.com/docker/buildx/blob/master/bake/bake.go.
func ConstructBakeFile(a Args) (map[string]string, error) {
	// Targets defines all images we are actually going to build
	targets := map[string]Target{}
	// Groups just bundles targets together to make them easier to work with
	groups := map[string]Group{}

	variants := sets.NewSet(a.Variants...)
	// hasDoubleDefault checks if we defined both DefaultVariant and PrimaryVariant. If we did, these
	// are the same exact docker build, just requesting different tags. As an optimization, and to ensure
	// byte-for-byte identical images, we will collapse these into a single build with multiple tags.
	hasDoubleDefault := variants.Contains(DefaultVariant) && variants.Contains(PrimaryVariant)

	allGroups := sets.NewSet()
	// Tar files builds a mapping of tar file name (when used with --save) -> alias for that
	// If the value is "", the tar file exists but has no aliases
	tarFiles := map[string]string{}
	for _, variant := range a.Variants {
		for _, target := range a.Targets {
			if variant == DefaultVariant && hasDoubleDefault {
				// This will be process by the PrimaryVariant, skip it here
				continue
			}

			baseDist := variant
			if baseDist == DefaultVariant {
				baseDist = PrimaryVariant
			}

			// These images do not actually use distroless even when specified. So skip to avoid extra building
			if strings.HasPrefix(target, "app_") && variant == DistrolessVariant {
				continue
			}
			p := filepath.Join(testenv.LocalOut, "dockerx_build", fmt.Sprintf("docker.%s", target))
			t := Target{
				Context:    sp(p),
				Dockerfile: sp(fmt.Sprintf("Dockerfile.%s", target)),
				Args: map[string]string{
					// Base version defines the tag of the base image to use. Typically, set in the Makefile and not overridden.
					"BASE_VERSION": args.BaseVersion,
					// Base distribution picks which variant to build
					"BASE_DISTRIBUTION": baseDist,
					// Additional metadata injected into some images
					"proxy_version":    args.ProxyVersion,
					"istio_version":    args.IstioVersion,
					"VM_IMAGE_NAME":    vmImageName(target),
					"VM_IMAGE_VERSION": vmImageVersion(target),
				},
				Platforms: args.Architectures,
			}

			if variant == DefaultVariant {
				// For default, we have no suffix
				t.Tags = []string{fmt.Sprintf("%s/%s:%s", a.Hub, target, a.Tag)}
			} else {
				// Otherwise, we have a suffix with the variant
				t.Tags = []string{fmt.Sprintf("%s/%s:%s-%s", a.Hub, target, a.Tag, variant)}
				// If we need a default as well, add it as a second tag for the same image to avoid building twice
				if variant == PrimaryVariant && hasDoubleDefault {
					t.Tags = append(t.Tags, fmt.Sprintf("%s/%s:%s", a.Hub, target, a.Tag))
				}
			}

			// See https://docs.docker.com/engine/reference/commandline/buildx_build/#output
			if args.Push {
				t.Outputs = []string{"type=registry"}
			} else if args.Save {
				n := target
				if variant != "" {
					n += "-" + variant
				}

				tarFiles[n] = ""
				if variant == PrimaryVariant && hasDoubleDefault {
					tarFiles[n] = target
				}
				t.Outputs = []string{"type=docker,dest=" + filepath.Join(testenv.LocalOut, "release", "docker", n+".tar")}
			} else {
				t.Outputs = []string{"type=docker"}
			}

			name := fmt.Sprintf("%s-%s", target, variant)
			targets[name] = t
			tgts := groups[variant].Targets
			tgts = append(tgts, name)
			groups[variant] = Group{tgts}

			allGroups.Insert(variant)
		}
	}
	groups["all"] = Group{allGroups.SortedList()}
	bf := BakeFile{
		Target: targets,
		Group:  groups,
	}
	out := filepath.Join(testenv.LocalOut, "dockerx_build", "docker-bake.json")
	j, err := json.MarshalIndent(bf, "", "  ")
	if err != nil {
		return nil, err
	}
	_ = os.MkdirAll(filepath.Join(testenv.LocalOut, "dockerx_build"), 0o755)
	return tarFiles, os.WriteFile(out, j, 0o644)
}

func vmImageName(target string) string {
	if !strings.HasPrefix(target, "app_sidecar") {
		// Not a VM
		return ""
	}
	if strings.HasPrefix(target, "app_sidecar_base") {
		return strings.Split(target, "_")[3]
	}

	return strings.Split(target, "_")[2]
}

func vmImageVersion(target string) string {
	if !strings.HasPrefix(target, "app_sidecar") {
		// Not a VM
		return ""
	}
	if strings.HasPrefix(target, "app_sidecar_base") {
		return strings.Split(target, "_")[4]
	}

	return strings.Split(target, "_")[3]
}

// VerboseCommand runs a command, outputting stderr and stdout
func VerboseCommand(name string, arg ...string) *exec.Cmd {
	log.Infof("Running command: %v %v", name, strings.Join(arg, " "))
	cmd := exec.Command(name, arg...)
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	return cmd
}

func StandardEnv(args Args) []string {
	env := os.Environ()
	if len(sets.NewSet(args.Targets...).Delete("proxyv2")) <= 1 {
		// If we are building multiple, it is faster to build all binaries in a single invocation
		// Otherwise, build just the single item. proxyv2 is special since it is always built separately with tag=agent.
		// Ideally we would just always build the targets we need but our Makefile is not that smart
		env = append(env, "BUILD_ALL=false")
	}

	env = append(env,
		// Build should already run in container, having multiple layers of docker causes issues
		"BUILD_WITH_CONTAINER=0",
		// Overwrite rules for buildx
		"DOCKER_RULE=./tools/docker-copy.sh $^ $(DOCKERX_BUILD_TOP)/$@",
		"RENAME_TEMPLATE=mkdir -p $(DOCKERX_BUILD_TOP)/$@ && cp $(ECHO_DOCKER)/$(VM_OS_DOCKERFILE_TEMPLATE) $(DOCKERX_BUILD_TOP)/$@/Dockerfile$(suffix $@)",
	)
	return env
}

// RunMake runs a make command for the repo, with standard environment variables set
func RunMake(args Args, c ...string) error {
	cmd := VerboseCommand("make", c...)
	cmd.Env = StandardEnv(args)
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	cmd.Dir = testenv.IstioSrc
	log.Infof("Running make %v", strings.Join(c, " "))
	return cmd.Run()
}
<commit_msg>Fix docker.save having `-default` in tar name (#36061)<commit_after>// Copyright Istio 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 main

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"github.com/spf13/cobra"

	"istio.io/istio/pilot/pkg/util/sets"
	testenv "istio.io/istio/pkg/test/env"
	"istio.io/pkg/log"
	pkgversion "istio.io/pkg/version"
)

func main() {
	rootCmd.Flags().StringVar(&args.Hub, "hub", args.Hub, "docker hub")
	rootCmd.Flags().StringVar(&args.Tag, "tag", args.Tag, "docker tag")

	rootCmd.Flags().StringVar(&args.BaseVersion, "base-version", args.BaseVersion, "base version to use")
	rootCmd.Flags().StringVar(&args.ProxyVersion, "proxy-version", args.ProxyVersion, "proxy version to use")
	rootCmd.Flags().StringVar(&args.IstioVersion, "istio-version", args.IstioVersion, "istio version to use")

	rootCmd.Flags().StringSliceVar(&args.Targets, "targets", args.Targets, "targets to build")
	rootCmd.Flags().StringSliceVar(&args.Variants, "variants", args.Variants, "variants to build")
	rootCmd.Flags().StringSliceVar(&args.Architectures, "architecures", args.Architectures, "architectures to build")
	rootCmd.Flags().BoolVar(&args.Push, "push", args.Push, "push targets to registry")
	rootCmd.Flags().BoolVar(&args.Save, "save", args.Save, "save targets to tar.gz")
	rootCmd.Flags().BoolVar(&args.BuildxEnabled, "buildx", args.BuildxEnabled, "use buildx for builds")
	rootCmd.Flags().BoolVar(&version, "version", version, "show build version")

	if err := rootCmd.Execute(); err != nil {
		os.Exit(-1)
	}
}

var rootCmd = &cobra.Command{
	Use:   "",
	Short: "Builds Istio docker images",
	RunE: func(cmd *cobra.Command, _ []string) error {
		if version {
			fmt.Println(pkgversion.Info.GitRevision)
			os.Exit(0)
		}
		log.Infof("Args: %+v", args)
		if args.Push && args.Save {
			// TODO(https://github.com/moby/buildkit/issues/1555) support both
			return fmt.Errorf("--push and --save are mutually exclusive")
		}
		_, inCI := os.LookupEnv("CI")
		if (args.Hub == "docker.io/istio" || args.Hub == "istio" || args.Hub == "gcr.io/istio-release") && !inCI {
			// Safety check against developer error. If they have a legitimate use case, they can set CI var
			return fmt.Errorf("pushing to official registry only supported in CI")
		}

		tarFiles, err := ConstructBakeFile(args)
		if err != nil {
			return err
		}
		targets := []string{}
		for _, t := range args.Targets {
			targets = append(targets, fmt.Sprintf("docker.%s", t))
		}
		if err := RunMake(args, targets...); err != nil {
			return err
		}
		if err := RunBake(args); err != nil {
			return err
		}
		if err := RunSave(args, tarFiles); err != nil {
			return err
		}

		return nil
	},
}

func RunBake(args Args) error {
	out := filepath.Join(testenv.LocalOut, "dockerx_build", "docker-bake.json")
	_ = os.MkdirAll(filepath.Join(testenv.LocalOut, "release", "docker"), 0o755)
	if err := createBuildxBuilderIfNeeded(args); err != nil {
		return err
	}
	c := VerboseCommand("docker", "buildx", "bake", "-f", out, "all")
	c.Stdout = os.Stdout
	return c.Run()
}

// --save requires a custom builder. Automagically create it if needed
func createBuildxBuilderIfNeeded(a Args) error {
	if !a.Save {
		return nil
	}
	if _, f := os.LookupEnv("CI"); !f {
		// For now only do this for CI, we do not want to mess with users config. And users rarely use --save
		return nil
	}
	return exec.Command("sh", "-c", `
export DOCKER_CLI_EXPERIMENTAL=enabled
if ! docker buildx ls | grep -q container-builder; then
  docker buildx create --driver-opt network=host,image=gcr.io/istio-testing/buildkit:v0.9.2 --name container-builder --buildkitd-flags="--debug"
  # Pre-warm the builder. If it fails, fetch logs, but continue
  docker buildx inspect --bootstrap container-builder || docker logs buildx_buildkit_container-builder0 || true
fi
docker buildx use container-builder`).Run()
}

// RunSave handles the --save portion. Part of this is done by buildx natively - it will emit .tar
// files. We need tar.gz though, so we have a bit more work to do
func RunSave(a Args, files map[string]string) error {
	if !a.Save {
		return nil
	}

	root := filepath.Join(testenv.LocalOut, "release", "docker")
	for name, alias := range files {
		// Gzip the file
		if err := VerboseCommand("gzip", "--fast", "--force", filepath.Join(root, name+".tar")).Run(); err != nil {
			return err
		}
		// If it has an alias (ie pilot-debug -> pilot), copy it over. Copy after gzip to avoid double compute.
		if alias != "" {
			if err := CopyFile(filepath.Join(root, name+".tar.gz"), filepath.Join(root, alias+".tar.gz")); err != nil {
				return err
			}
		}
	}

	return nil
}

func CopyFile(src, dst string) error {
	log.Infof("Copying %v -> %v", src, dst)
	in, err := os.Open(src)
	if err != nil {
		return fmt.Errorf("failed to open file %v to copy: %v", src, err)
	}
	defer in.Close()

	if err := os.MkdirAll(path.Join(dst, ".."), 0o750); err != nil {
		return fmt.Errorf("failed to make destination directory %v: %v", dst, err)
	}
	out, err := os.Create(dst)
	if err != nil {
		return fmt.Errorf("failed to create file %v to copy to: %v", dst, err)
	}
	defer out.Close()

	if _, err = io.Copy(out, in); err != nil {
		return fmt.Errorf("failed to copy %v to %v: %v", src, dst, err)
	}

	return nil
}

func sp(s string) *string {
	return &s
}

// ConstructBakeFile constructs a docker-bake.json to be passed to `docker buildx bake`.
// This command is an extremely powerful command to build many images in parallel, but is pretty undocumented.
// Most info can be found from the source at https://github.com/docker/buildx/blob/master/bake/bake.go.
func ConstructBakeFile(a Args) (map[string]string, error) {
	// Targets defines all images we are actually going to build
	targets := map[string]Target{}
	// Groups just bundles targets together to make them easier to work with
	groups := map[string]Group{}

	variants := sets.NewSet(a.Variants...)
	// hasDoubleDefault checks if we defined both DefaultVariant and PrimaryVariant. If we did, these
	// are the same exact docker build, just requesting different tags. As an optimization, and to ensure
	// byte-for-byte identical images, we will collapse these into a single build with multiple tags.
	hasDoubleDefault := variants.Contains(DefaultVariant) && variants.Contains(PrimaryVariant)

	allGroups := sets.NewSet()
	// Tar files builds a mapping of tar file name (when used with --save) -> alias for that
	// If the value is "", the tar file exists but has no aliases
	tarFiles := map[string]string{}
	for _, variant := range a.Variants {
		for _, target := range a.Targets {
			if variant == DefaultVariant && hasDoubleDefault {
				// This will be process by the PrimaryVariant, skip it here
				continue
			}

			baseDist := variant
			if baseDist == DefaultVariant {
				baseDist = PrimaryVariant
			}

			// These images do not actually use distroless even when specified. So skip to avoid extra building
			if strings.HasPrefix(target, "app_") && variant == DistrolessVariant {
				continue
			}
			p := filepath.Join(testenv.LocalOut, "dockerx_build", fmt.Sprintf("docker.%s", target))
			t := Target{
				Context:    sp(p),
				Dockerfile: sp(fmt.Sprintf("Dockerfile.%s", target)),
				Args: map[string]string{
					// Base version defines the tag of the base image to use. Typically, set in the Makefile and not overridden.
					"BASE_VERSION": args.BaseVersion,
					// Base distribution picks which variant to build
					"BASE_DISTRIBUTION": baseDist,
					// Additional metadata injected into some images
					"proxy_version":    args.ProxyVersion,
					"istio_version":    args.IstioVersion,
					"VM_IMAGE_NAME":    vmImageName(target),
					"VM_IMAGE_VERSION": vmImageVersion(target),
				},
				Platforms: args.Architectures,
			}

			if variant == DefaultVariant {
				// For default, we have no suffix
				t.Tags = []string{fmt.Sprintf("%s/%s:%s", a.Hub, target, a.Tag)}
			} else {
				// Otherwise, we have a suffix with the variant
				t.Tags = []string{fmt.Sprintf("%s/%s:%s-%s", a.Hub, target, a.Tag, variant)}
				// If we need a default as well, add it as a second tag for the same image to avoid building twice
				if variant == PrimaryVariant && hasDoubleDefault {
					t.Tags = append(t.Tags, fmt.Sprintf("%s/%s:%s", a.Hub, target, a.Tag))
				}
			}

			// See https://docs.docker.com/engine/reference/commandline/buildx_build/#output
			if args.Push {
				t.Outputs = []string{"type=registry"}
			} else if args.Save {
				n := target
				if variant != "" && variant != DefaultVariant { // For default variant, we do not add it.
					n += "-" + variant
				}

				tarFiles[n] = ""
				if variant == PrimaryVariant && hasDoubleDefault {
					tarFiles[n] = target
				}
				t.Outputs = []string{"type=docker,dest=" + filepath.Join(testenv.LocalOut, "release", "docker", n+".tar")}
			} else {
				t.Outputs = []string{"type=docker"}
			}

			name := fmt.Sprintf("%s-%s", target, variant)
			targets[name] = t
			tgts := groups[variant].Targets
			tgts = append(tgts, name)
			groups[variant] = Group{tgts}

			allGroups.Insert(variant)
		}
	}
	groups["all"] = Group{allGroups.SortedList()}
	bf := BakeFile{
		Target: targets,
		Group:  groups,
	}
	out := filepath.Join(testenv.LocalOut, "dockerx_build", "docker-bake.json")
	j, err := json.MarshalIndent(bf, "", "  ")
	if err != nil {
		return nil, err
	}
	_ = os.MkdirAll(filepath.Join(testenv.LocalOut, "dockerx_build"), 0o755)
	return tarFiles, os.WriteFile(out, j, 0o644)
}

func vmImageName(target string) string {
	if !strings.HasPrefix(target, "app_sidecar") {
		// Not a VM
		return ""
	}
	if strings.HasPrefix(target, "app_sidecar_base") {
		return strings.Split(target, "_")[3]
	}

	return strings.Split(target, "_")[2]
}

func vmImageVersion(target string) string {
	if !strings.HasPrefix(target, "app_sidecar") {
		// Not a VM
		return ""
	}
	if strings.HasPrefix(target, "app_sidecar_base") {
		return strings.Split(target, "_")[4]
	}

	return strings.Split(target, "_")[3]
}

// VerboseCommand runs a command, outputting stderr and stdout
func VerboseCommand(name string, arg ...string) *exec.Cmd {
	log.Infof("Running command: %v %v", name, strings.Join(arg, " "))
	cmd := exec.Command(name, arg...)
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	return cmd
}

func StandardEnv(args Args) []string {
	env := os.Environ()
	if len(sets.NewSet(args.Targets...).Delete("proxyv2")) <= 1 {
		// If we are building multiple, it is faster to build all binaries in a single invocation
		// Otherwise, build just the single item. proxyv2 is special since it is always built separately with tag=agent.
		// Ideally we would just always build the targets we need but our Makefile is not that smart
		env = append(env, "BUILD_ALL=false")
	}

	env = append(env,
		// Build should already run in container, having multiple layers of docker causes issues
		"BUILD_WITH_CONTAINER=0",
		// Overwrite rules for buildx
		"DOCKER_RULE=./tools/docker-copy.sh $^ $(DOCKERX_BUILD_TOP)/$@",
		"RENAME_TEMPLATE=mkdir -p $(DOCKERX_BUILD_TOP)/$@ && cp $(ECHO_DOCKER)/$(VM_OS_DOCKERFILE_TEMPLATE) $(DOCKERX_BUILD_TOP)/$@/Dockerfile$(suffix $@)",
	)
	return env
}

// RunMake runs a make command for the repo, with standard environment variables set
func RunMake(args Args, c ...string) error {
	cmd := VerboseCommand("make", c...)
	cmd.Env = StandardEnv(args)
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	cmd.Dir = testenv.IstioSrc
	log.Infof("Running make %v", strings.Join(c, " "))
	return cmd.Run()
}
<|endoftext|>


<commit_before>// Copyright 2015 CoreOS, 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 (
	"flag"
	"fmt"
	"log"
	"path"
	"time"

	"github.com/coreos/etcd/etcdserver/etcdserverpb"
	"github.com/coreos/etcd/pkg/pbutil"
	"github.com/coreos/etcd/pkg/types"
	"github.com/coreos/etcd/raft/raftpb"
	"github.com/coreos/etcd/snap"
	"github.com/coreos/etcd/wal"
	"github.com/coreos/etcd/wal/walpb"
)

func main() {
	from := flag.String("data-dir", "", "")
	flag.Parse()
	if *from == "" {
		log.Fatal("Must provide -data-dir flag")
	}

	ss := snap.New(snapDir(*from))
	snapshot, err := ss.Load()
	var walsnap walpb.Snapshot
	switch err {
	case nil:
		walsnap.Index, walsnap.Term = snapshot.Metadata.Index, snapshot.Metadata.Term
		nodes := genIDSlice(snapshot.Metadata.ConfState.Nodes)
		fmt.Printf("Snapshot:\nterm=%d index=%d nodes=%s\n",
			walsnap.Term, walsnap.Index, nodes)
	case snap.ErrNoSnapshot:
		fmt.Printf("Snapshot:\nempty\n")
	default:
		log.Fatalf("Failed loading snapshot: %v", err)
	}

	w, err := wal.Open(walDir(*from), walsnap)
	if err != nil {
		log.Fatalf("Failed opening WAL: %v", err)
	}
	wmetadata, state, ents, err := w.ReadAll()
	w.Close()
	if err != nil {
		log.Fatalf("Failed reading WAL: %v", err)
	}
	id, cid := parseWALMetadata(wmetadata)
	vid := types.ID(state.Vote)
	fmt.Printf("WAL metadata:\nnodeID=%s clusterID=%s term=%d commitIndex=%d vote=%s\n",
		id, cid, state.Term, state.Commit, vid)

	fmt.Printf("WAL entries:\n")
	fmt.Printf("lastIndex=%d\n", ents[len(ents)-1].Index)
	fmt.Printf("%4s\t%10s\ttype\tdata\n", "term", "index")
	for _, e := range ents {
		msg := fmt.Sprintf("%4d\t%10d", e.Term, e.Index)
		switch e.Type {
		case raftpb.EntryNormal:
			msg = fmt.Sprintf("%s\tnorm", msg)
			var r etcdserverpb.Request
			if err := r.Unmarshal(e.Data); err != nil {
				msg = fmt.Sprintf("%s\t???", msg)
				break
			}
			switch r.Method {
			case "":
				msg = fmt.Sprintf("%s\tnoop", msg)
			case "SYNC":
				msg = fmt.Sprintf("%s\tmethod=SYNC time=%q", msg, time.Unix(0, r.Time))
			case "QGET", "DELETE":
				msg = fmt.Sprintf("%s\tmethod=%s path=%s", msg, r.Method, excerpt(r.Path, 64, 64))
			default:
				msg = fmt.Sprintf("%s\tmethod=%s path=%s val=%s", msg, r.Method, excerpt(r.Path, 64, 64), excerpt(r.Val, 128, 0))
			}
		case raftpb.EntryConfChange:
			msg = fmt.Sprintf("%s\tconf", msg)
			var r raftpb.ConfChange
			if err := r.Unmarshal(e.Data); err != nil {
				msg = fmt.Sprintf("%s\t???", msg)
			} else {
				msg = fmt.Sprintf("%s\tmethod=%s id=%s", msg, r.Type, types.ID(r.NodeID))
			}
		}
		fmt.Println(msg)
	}
}

func walDir(dataDir string) string { return path.Join(dataDir, "wal") }

func snapDir(dataDir string) string { return path.Join(dataDir, "snap") }

func parseWALMetadata(b []byte) (id, cid types.ID) {
	var metadata etcdserverpb.Metadata
	pbutil.MustUnmarshal(&metadata, b)
	id = types.ID(metadata.NodeID)
	cid = types.ID(metadata.ClusterID)
	return
}

func genIDSlice(a []uint64) []types.ID {
	ids := make([]types.ID, len(a))
	for i, id := range a {
		ids[i] = types.ID(id)
	}
	return ids
}

// excerpt replaces middle part with ellipsis and returns a double-quoted
// string safely escaped with Go syntax.
func excerpt(str string, pre, suf int) string {
	if pre+suf > len(str) {
		return fmt.Sprintf("%q", str)
	}
	return fmt.Sprintf("%q...%q", str[:pre], str[len(str)-suf:])
}
<commit_msg>tools: etcd-dump-logs tool support dump from a given snapshot file<commit_after>// Copyright 2015 CoreOS, 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 (
	"flag"
	"fmt"
	"log"
	"path"
	"time"

	"github.com/coreos/etcd/etcdserver/etcdserverpb"
	"github.com/coreos/etcd/pkg/pbutil"
	"github.com/coreos/etcd/pkg/types"
	"github.com/coreos/etcd/raft/raftpb"
	"github.com/coreos/etcd/snap"
	"github.com/coreos/etcd/wal"
	"github.com/coreos/etcd/wal/walpb"
)

func main() {
	from := flag.String("data-dir", "", "")
	snapfile := flag.String("snap-file", "", "The base name of snapshot file to read")
	flag.Parse()
	if *from == "" {
		log.Fatal("Must provide -data-dir flag")
	}

	var (
		snapshot *raftpb.Snapshot
		err      error
	)

	if *snapfile == "" {
		ss := snap.New(snapDir(*from))
		snapshot, err = ss.Load()
	} else {
		snapshot, err = snap.Read(path.Join(snapDir(*from), *snapfile))
	}

	var walsnap walpb.Snapshot
	switch err {
	case nil:
		walsnap.Index, walsnap.Term = snapshot.Metadata.Index, snapshot.Metadata.Term
		nodes := genIDSlice(snapshot.Metadata.ConfState.Nodes)
		fmt.Printf("Snapshot:\nterm=%d index=%d nodes=%s\n",
			walsnap.Term, walsnap.Index, nodes)
	case snap.ErrNoSnapshot:
		fmt.Printf("Snapshot:\nempty\n")
	default:
		log.Fatalf("Failed loading snapshot: %v", err)
	}

	w, err := wal.Open(walDir(*from), walsnap)
	if err != nil {
		log.Fatalf("Failed opening WAL: %v", err)
	}
	wmetadata, state, ents, err := w.ReadAll()
	w.Close()
	if err != nil {
		log.Fatalf("Failed reading WAL: %v", err)
	}
	id, cid := parseWALMetadata(wmetadata)
	vid := types.ID(state.Vote)
	fmt.Printf("WAL metadata:\nnodeID=%s clusterID=%s term=%d commitIndex=%d vote=%s\n",
		id, cid, state.Term, state.Commit, vid)

	fmt.Printf("WAL entries:\n")
	fmt.Printf("lastIndex=%d\n", ents[len(ents)-1].Index)
	fmt.Printf("%4s\t%10s\ttype\tdata\n", "term", "index")
	for _, e := range ents {
		msg := fmt.Sprintf("%4d\t%10d", e.Term, e.Index)
		switch e.Type {
		case raftpb.EntryNormal:
			msg = fmt.Sprintf("%s\tnorm", msg)
			var r etcdserverpb.Request
			if err := r.Unmarshal(e.Data); err != nil {
				msg = fmt.Sprintf("%s\t???", msg)
				break
			}
			switch r.Method {
			case "":
				msg = fmt.Sprintf("%s\tnoop", msg)
			case "SYNC":
				msg = fmt.Sprintf("%s\tmethod=SYNC time=%q", msg, time.Unix(0, r.Time))
			case "QGET", "DELETE":
				msg = fmt.Sprintf("%s\tmethod=%s path=%s", msg, r.Method, excerpt(r.Path, 64, 64))
			default:
				msg = fmt.Sprintf("%s\tmethod=%s path=%s val=%s", msg, r.Method, excerpt(r.Path, 64, 64), excerpt(r.Val, 128, 0))
			}
		case raftpb.EntryConfChange:
			msg = fmt.Sprintf("%s\tconf", msg)
			var r raftpb.ConfChange
			if err := r.Unmarshal(e.Data); err != nil {
				msg = fmt.Sprintf("%s\t???", msg)
			} else {
				msg = fmt.Sprintf("%s\tmethod=%s id=%s", msg, r.Type, types.ID(r.NodeID))
			}
		}
		fmt.Println(msg)
	}
}

func walDir(dataDir string) string { return path.Join(dataDir, "member", "wal") }

func snapDir(dataDir string) string { return path.Join(dataDir, "member", "snap") }

func parseWALMetadata(b []byte) (id, cid types.ID) {
	var metadata etcdserverpb.Metadata
	pbutil.MustUnmarshal(&metadata, b)
	id = types.ID(metadata.NodeID)
	cid = types.ID(metadata.ClusterID)
	return
}

func genIDSlice(a []uint64) []types.ID {
	ids := make([]types.ID, len(a))
	for i, id := range a {
		ids[i] = types.ID(id)
	}
	return ids
}

// excerpt replaces middle part with ellipsis and returns a double-quoted
// string safely escaped with Go syntax.
func excerpt(str string, pre, suf int) string {
	if pre+suf > len(str) {
		return fmt.Sprintf("%q", str)
	}
	return fmt.Sprintf("%q...%q", str[:pre], str[len(str)-suf:])
}
<|endoftext|>


<commit_before>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package mqtt

import (
	"sync"
	"time"

	MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
	"github.com/TheThingsNetwork/ttn/core"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/TheThingsNetwork/ttn/utils/stats"
	"github.com/apex/log"
)

// Adapter type materializes an mqtt adapter which implements the basic mqtt protocol
type Adapter struct {
	Client
	ctx           log.Interface
	packets       chan PktReq // Channel used to "transforms" incoming request to something we can handle concurrently
	registrations chan RegReq // Incoming registrations
}

// Handler defines topic-specific handler.
type Handler interface {
	Topic() string
	Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error
}

// Message sent through the response channel of a pktReq or regReq
type MsgRes []byte // The response content.

// Message sent through the packets channel when an incoming request arrives
type PktReq struct {
	Packet []byte      // The actual packet that has been parsed
	Chresp chan MsgRes // A response channel waiting for an success or reject confirmation
}

// Message sent through the registration channel when an incoming registration arrives
type RegReq struct {
	Registration core.Registration
	Chresp       chan MsgRes
}

// MQTT Schemes available
type Scheme string

const (
	Tcp       Scheme = "tcp"
	Tls       Scheme = "tls"
	WebSocket Scheme = "ws"
)

// NewAdapter constructs and allocates a new mqtt adapter
//
// The client is expected to be already connected to the right broker and ready to be used.
func NewAdapter(client Client, ctx log.Interface) *Adapter {
	adapter := &Adapter{
		Client:        client,
		ctx:           ctx,
		packets:       make(chan PktReq),
		registrations: make(chan RegReq),
	}

	return adapter
}

// Send implements the core.Adapter interface
func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, error) {
	stats.MarkMeter("mqtt_adapter.send")
	stats.UpdateHistogram("mqtt_adapter.send_recipients", int64(len(recipients)))

	// Marshal the packet to raw binary data
	data, err := p.MarshalBinary()
	if err != nil {
		a.ctx.WithError(err).Warn("Invalid Packet")
		return nil, errors.New(errors.Structural, err)
	}

	a.ctx.Debug("Sending Packet")

	// Prepare ground for parrallel mqtt publications
	nb := len(recipients)
	cherr := make(chan error, nb)
	chresp := make(chan []byte, nb)
	wg := sync.WaitGroup{}
	wg.Add(2 * nb)

	for _, r := range recipients {
		// Get the actual recipient
		recipient, ok := r.(MqttRecipient)
		if !ok {
			err := errors.New(errors.Structural, "Unable to interpret recipient as mqttRecipient")
			a.ctx.WithField("recipient", r).Warn(err.Error())
			cherr <- err
			continue
		}

		// Subscribe to down channel (before publishing anything)
		chdown := make(chan []byte)
		token := a.Subscribe(recipient.TopicDown(), 2, func(client Client, msg MQTT.Message) {
			chdown <- msg.Payload()
		})
		if token.Wait() && token.Error() != nil {
			err := errors.New(errors.Operational, "Unable to subscribe to down topic")
			a.ctx.WithField("recipient", recipient).Warn(err.Error())
			cherr <- err
			close(chdown)
			continue
		}

		// Publish on each topic
		go func(recipient MqttRecipient) {
			defer wg.Done()

			ctx := a.ctx.WithField("topic", recipient.TopicUp())

			// Publish packet
			token := a.Publish(recipient.TopicUp(), 2, false, data)
			if token.Wait() && token.Error() != nil {
				ctx.WithError(token.Error()).Error("Unable to publish")
				cherr <- errors.New(errors.Operational, token.Error())
				return
			}
		}(recipient)

		// Pull responses from each down topic, expecting only one
		go func(recipient MqttRecipient, chdown <-chan []byte) {
			defer wg.Done()

			ctx := a.ctx.WithField("topic", recipient.TopicDown())

			defer func(ctx log.Interface) {
				if token := a.Unsubscribe(recipient.TopicDown()); token.Wait() && token.Error() != nil {
					ctx.Warn("Unable to unsubscribe topic")
				}
			}(ctx)

			// Forward the downlink response received if any
			select {
			case data, ok := <-chdown:
				if ok {
					chresp <- data
				}
			case <-time.After(time.Second): // Timeout
			}
		}(recipient, chdown)
	}

	// Wait for each request to be done
	stats.IncCounter("mqtt_adapter.waiting_for_send")
	wg.Wait()
	stats.DecCounter("mqtt_adapter.waiting_for_send")
	close(cherr)
	close(chresp)

	// Collect errors
	errored := len(cherr)

	// Collect response
	if len(chresp) > 1 {
		return nil, errors.New(errors.Behavioural, "Received too many positive answers")
	}

	if len(chresp) == 0 && errored != 0 {
		return nil, errors.New(errors.Operational, "No positive response from recipients but got unexpected answers")
	}

	if len(chresp) == 0 && errored == 0 {
		return nil, errors.New(errors.Behavioural, "No recipient gave a positive answer")
	}

	return <-chresp, nil
}

// GetRecipient implements the core.Adapter interface
func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) {
	recipient := new(mqttRecipient)
	if err := recipient.UnmarshalBinary(raw); err != nil {
		return nil, errors.New(errors.Structural, err)
	}
	return *recipient, nil
}

// Next implements the core.Adapter interface
func (a *Adapter) Next() ([]byte, core.AckNacker, error) {
	p := <-a.packets
	return p.Packet, mqttAckNacker{Chresp: p.Chresp}, nil
}

// NextRegistration implements the core.Adapter interface. Not implemented for this adapters.
func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) {
	return nil, nil, nil
}

// Bind registers a handler to a specific endpoint
func (a *Adapter) Bind(h Handler) error {
	ctx := a.ctx.WithField("topic", h.Topic())
	ctx.Info("Subscribe new handler")
	token := a.Subscribe(h.Topic(), 2, func(client Client, msg MQTT.Message) {
		ctx.Debug("Handle new mqtt message")
		if err := h.Handle(client, a.packets, a.registrations, msg); err != nil {
			ctx.WithError(err).Warn("Unable to handle mqtt message")
		}
	})
	if token.Wait() && token.Error() != nil {
		ctx.WithError(token.Error()).Error("Unable to Subscribe")
		return errors.New(errors.Operational, token.Error())
	}
	return nil
}
<commit_msg>Implements missing NextRegistration() method in mqtt adapter<commit_after>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package mqtt

import (
	"sync"
	"time"

	MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
	"github.com/TheThingsNetwork/ttn/core"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/TheThingsNetwork/ttn/utils/stats"
	"github.com/apex/log"
)

// Adapter type materializes an mqtt adapter which implements the basic mqtt protocol
type Adapter struct {
	Client
	ctx           log.Interface
	packets       chan PktReq // Channel used to "transforms" incoming request to something we can handle concurrently
	registrations chan RegReq // Incoming registrations
}

// Handler defines topic-specific handler.
type Handler interface {
	Topic() string
	Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error
}

// Message sent through the response channel of a pktReq or regReq
type MsgRes []byte // The response content.

// Message sent through the packets channel when an incoming request arrives
type PktReq struct {
	Packet []byte      // The actual packet that has been parsed
	Chresp chan MsgRes // A response channel waiting for an success or reject confirmation
}

// Message sent through the registration channel when an incoming registration arrives
type RegReq struct {
	Registration core.Registration
	Chresp       chan MsgRes
}

// MQTT Schemes available
type Scheme string

const (
	Tcp       Scheme = "tcp"
	Tls       Scheme = "tls"
	WebSocket Scheme = "ws"
)

// NewAdapter constructs and allocates a new mqtt adapter
//
// The client is expected to be already connected to the right broker and ready to be used.
func NewAdapter(client Client, ctx log.Interface) *Adapter {
	adapter := &Adapter{
		Client:        client,
		ctx:           ctx,
		packets:       make(chan PktReq),
		registrations: make(chan RegReq),
	}

	return adapter
}

// Send implements the core.Adapter interface
func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, error) {
	stats.MarkMeter("mqtt_adapter.send")
	stats.UpdateHistogram("mqtt_adapter.send_recipients", int64(len(recipients)))

	// Marshal the packet to raw binary data
	data, err := p.MarshalBinary()
	if err != nil {
		a.ctx.WithError(err).Warn("Invalid Packet")
		return nil, errors.New(errors.Structural, err)
	}

	a.ctx.Debug("Sending Packet")

	// Prepare ground for parrallel mqtt publications
	nb := len(recipients)
	cherr := make(chan error, nb)
	chresp := make(chan []byte, nb)
	wg := sync.WaitGroup{}
	wg.Add(2 * nb)

	for _, r := range recipients {
		// Get the actual recipient
		recipient, ok := r.(MqttRecipient)
		if !ok {
			err := errors.New(errors.Structural, "Unable to interpret recipient as mqttRecipient")
			a.ctx.WithField("recipient", r).Warn(err.Error())
			cherr <- err
			continue
		}

		// Subscribe to down channel (before publishing anything)
		chdown := make(chan []byte)
		token := a.Subscribe(recipient.TopicDown(), 2, func(client Client, msg MQTT.Message) {
			chdown <- msg.Payload()
		})
		if token.Wait() && token.Error() != nil {
			err := errors.New(errors.Operational, "Unable to subscribe to down topic")
			a.ctx.WithField("recipient", recipient).Warn(err.Error())
			cherr <- err
			close(chdown)
			continue
		}

		// Publish on each topic
		go func(recipient MqttRecipient) {
			defer wg.Done()

			ctx := a.ctx.WithField("topic", recipient.TopicUp())

			// Publish packet
			token := a.Publish(recipient.TopicUp(), 2, false, data)
			if token.Wait() && token.Error() != nil {
				ctx.WithError(token.Error()).Error("Unable to publish")
				cherr <- errors.New(errors.Operational, token.Error())
				return
			}
		}(recipient)

		// Pull responses from each down topic, expecting only one
		go func(recipient MqttRecipient, chdown <-chan []byte) {
			defer wg.Done()

			ctx := a.ctx.WithField("topic", recipient.TopicDown())

			defer func(ctx log.Interface) {
				if token := a.Unsubscribe(recipient.TopicDown()); token.Wait() && token.Error() != nil {
					ctx.Warn("Unable to unsubscribe topic")
				}
			}(ctx)

			// Forward the downlink response received if any
			select {
			case data, ok := <-chdown:
				if ok {
					chresp <- data
				}
			case <-time.After(time.Second): // Timeout
			}
		}(recipient, chdown)
	}

	// Wait for each request to be done
	stats.IncCounter("mqtt_adapter.waiting_for_send")
	wg.Wait()
	stats.DecCounter("mqtt_adapter.waiting_for_send")
	close(cherr)
	close(chresp)

	// Collect errors
	errored := len(cherr)

	// Collect response
	if len(chresp) > 1 {
		return nil, errors.New(errors.Behavioural, "Received too many positive answers")
	}

	if len(chresp) == 0 && errored != 0 {
		return nil, errors.New(errors.Operational, "No positive response from recipients but got unexpected answers")
	}

	if len(chresp) == 0 && errored == 0 {
		return nil, errors.New(errors.Behavioural, "No recipient gave a positive answer")
	}

	return <-chresp, nil
}

// GetRecipient implements the core.Adapter interface
func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) {
	recipient := new(mqttRecipient)
	if err := recipient.UnmarshalBinary(raw); err != nil {
		return nil, errors.New(errors.Structural, err)
	}
	return *recipient, nil
}

// Next implements the core.Adapter interface
func (a *Adapter) Next() ([]byte, core.AckNacker, error) {
	p := <-a.packets
	return p.Packet, mqttAckNacker{Chresp: p.Chresp}, nil
}

// NextRegistration implements the core.Adapter interface. Not implemented for this adapters.
func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) {
	r := <-a.registrations
	return r.Registration, mqttAckNacker{Chresp: r.Chresp}, nil
}

// Bind registers a handler to a specific endpoint
func (a *Adapter) Bind(h Handler) error {
	ctx := a.ctx.WithField("topic", h.Topic())
	ctx.Info("Subscribe new handler")
	token := a.Subscribe(h.Topic(), 2, func(client Client, msg MQTT.Message) {
		ctx.Debug("Handle new mqtt message")
		if err := h.Handle(client, a.packets, a.registrations, msg); err != nil {
			ctx.WithError(err).Warn("Unable to handle mqtt message")
		}
	})
	if token.Wait() && token.Error() != nil {
		ctx.WithError(token.Error()).Error("Unable to Subscribe")
		return errors.New(errors.Operational, token.Error())
	}
	return nil
}
<|endoftext|>


<commit_before>package aws

import (
	"crypto/sha1"
	"encoding/hex"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsIAMServerCertificate() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsIAMServerCertificateCreate,
		Read:   resourceAwsIAMServerCertificateRead,
		Delete: resourceAwsIAMServerCertificateDelete,
		Importer: &schema.ResourceImporter{
			State: resourceAwsIAMServerCertificateImport,
		},

		Schema: map[string]*schema.Schema{
			"certificate_body": {
				Type:      schema.TypeString,
				Required:  true,
				ForceNew:  true,
				StateFunc: normalizeCert,
			},

			"certificate_chain": {
				Type:      schema.TypeString,
				Optional:  true,
				ForceNew:  true,
				StateFunc: normalizeCert,
			},

			"path": {
				Type:     schema.TypeString,
				Optional: true,
				Default:  "/",
				ForceNew: true,
			},

			"private_key": {
				Type:      schema.TypeString,
				Required:  true,
				ForceNew:  true,
				StateFunc: normalizeCert,
				Sensitive: true,
			},

			"name": {
				Type:          schema.TypeString,
				Optional:      true,
				Computed:      true,
				ForceNew:      true,
				ConflictsWith: []string{"name_prefix"},
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if len(value) > 128 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 128 characters", k))
					}
					return
				},
			},

			"name_prefix": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if len(value) > 30 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 30 characters, name is limited to 128", k))
					}
					return
				},
			},

			"arn": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
		},
	}
}

func resourceAwsIAMServerCertificateCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).iamconn

	var sslCertName string
	if v, ok := d.GetOk("name"); ok {
		sslCertName = v.(string)
	} else if v, ok := d.GetOk("name_prefix"); ok {
		sslCertName = resource.PrefixedUniqueId(v.(string))
	} else {
		sslCertName = resource.UniqueId()
	}

	createOpts := &iam.UploadServerCertificateInput{
		CertificateBody:       aws.String(d.Get("certificate_body").(string)),
		PrivateKey:            aws.String(d.Get("private_key").(string)),
		ServerCertificateName: aws.String(sslCertName),
	}

	if v, ok := d.GetOk("certificate_chain"); ok {
		createOpts.CertificateChain = aws.String(v.(string))
	}

	if v, ok := d.GetOk("path"); ok {
		createOpts.Path = aws.String(v.(string))
	}

	log.Printf("[DEBUG] Creating IAM Server Certificate with opts: %s", createOpts)
	resp, err := conn.UploadServerCertificate(createOpts)
	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			return fmt.Errorf("[WARN] Error uploading server certificate, error: %s: %s", awsErr.Code(), awsErr.Message())
		}
		return fmt.Errorf("[WARN] Error uploading server certificate, error: %s", err)
	}

	d.SetId(*resp.ServerCertificateMetadata.ServerCertificateId)
	d.Set("name", sslCertName)

	return resourceAwsIAMServerCertificateRead(d, meta)
}

func resourceAwsIAMServerCertificateRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).iamconn
	resp, err := conn.GetServerCertificate(&iam.GetServerCertificateInput{
		ServerCertificateName: aws.String(d.Get("name").(string)),
	})

	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			if awsErr.Code() == "NoSuchEntity" {
				log.Printf("[WARN] IAM Server Cert (%s) not found, removing from state", d.Id())
				d.SetId("")
				return nil
			}
			return fmt.Errorf("[WARN] Error reading IAM Server Certificate: %s: %s", awsErr.Code(), awsErr.Message())
		}
		return fmt.Errorf("[WARN] Error reading IAM Server Certificate: %s", err)
	}

	d.SetId(*resp.ServerCertificate.ServerCertificateMetadata.ServerCertificateId)

	// these values should always be present, and have a default if not set in
	// configuration, and so safe to reference with nil checks
	d.Set("certificate_body", normalizeCert(resp.ServerCertificate.CertificateBody))

	c := normalizeCert(resp.ServerCertificate.CertificateChain)
	if c != "" {
		d.Set("certificate_chain", c)
	}

	d.Set("path", resp.ServerCertificate.ServerCertificateMetadata.Path)
	d.Set("arn", resp.ServerCertificate.ServerCertificateMetadata.Arn)

	return nil
}

func resourceAwsIAMServerCertificateDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).iamconn
	log.Printf("[INFO] Deleting IAM Server Certificate: %s", d.Id())
	err := resource.Retry(10*time.Minute, func() *resource.RetryError {
		_, err := conn.DeleteServerCertificate(&iam.DeleteServerCertificateInput{
			ServerCertificateName: aws.String(d.Get("name").(string)),
		})

		if err != nil {
			if awsErr, ok := err.(awserr.Error); ok {
				if awsErr.Code() == "DeleteConflict" && strings.Contains(awsErr.Message(), "currently in use by arn") {
					log.Printf("[WARN] Conflict deleting server certificate: %s, retrying", awsErr.Message())
					return resource.RetryableError(err)
				}
				if awsErr.Code() == "NoSuchEntity" {
					log.Printf("[WARN] IAM Server Certificate (%s) not found, removing from state", d.Id())
					d.SetId("")
					return nil
				}
			}
			return resource.NonRetryableError(err)
		}
		return nil
	})

	if err != nil {
		return err
	}

	d.SetId("")
	return nil
}

func resourceAwsIAMServerCertificateImport(
	d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
	d.Set("name", d.Id())
	// private_key can't be fetched from any API call
	return []*schema.ResourceData{d}, nil
}

func normalizeCert(cert interface{}) string {
	if cert == nil || cert == (*string)(nil) {
		return ""
	}

	var rawCert string
	switch cert.(type) {
	case string:
		rawCert = cert.(string)
	case *string:
		rawCert = *cert.(*string)
	default:
		return ""
	}

	cleanVal := sha1.Sum(stripCR([]byte(strings.TrimSpace(rawCert))))
	return hex.EncodeToString(cleanVal[:])
}

// strip CRs from raw literals. Lifted from go/scanner/scanner.go
// See https://github.com/golang/go/blob/release-branch.go1.6/src/go/scanner/scanner.go#L479
func stripCR(b []byte) []byte {
	c := make([]byte, len(b))
	i := 0
	for _, ch := range b {
		if ch != '\r' {
			c[i] = ch
			i++
		}
	}
	return c[:i]
}
<commit_msg>resource/iam_server_certificate: Increase deletion timeout (#907)<commit_after>package aws

import (
	"crypto/sha1"
	"encoding/hex"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsIAMServerCertificate() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsIAMServerCertificateCreate,
		Read:   resourceAwsIAMServerCertificateRead,
		Delete: resourceAwsIAMServerCertificateDelete,
		Importer: &schema.ResourceImporter{
			State: resourceAwsIAMServerCertificateImport,
		},

		Schema: map[string]*schema.Schema{
			"certificate_body": {
				Type:      schema.TypeString,
				Required:  true,
				ForceNew:  true,
				StateFunc: normalizeCert,
			},

			"certificate_chain": {
				Type:      schema.TypeString,
				Optional:  true,
				ForceNew:  true,
				StateFunc: normalizeCert,
			},

			"path": {
				Type:     schema.TypeString,
				Optional: true,
				Default:  "/",
				ForceNew: true,
			},

			"private_key": {
				Type:      schema.TypeString,
				Required:  true,
				ForceNew:  true,
				StateFunc: normalizeCert,
				Sensitive: true,
			},

			"name": {
				Type:          schema.TypeString,
				Optional:      true,
				Computed:      true,
				ForceNew:      true,
				ConflictsWith: []string{"name_prefix"},
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if len(value) > 128 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 128 characters", k))
					}
					return
				},
			},

			"name_prefix": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if len(value) > 30 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 30 characters, name is limited to 128", k))
					}
					return
				},
			},

			"arn": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
		},
	}
}

func resourceAwsIAMServerCertificateCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).iamconn

	var sslCertName string
	if v, ok := d.GetOk("name"); ok {
		sslCertName = v.(string)
	} else if v, ok := d.GetOk("name_prefix"); ok {
		sslCertName = resource.PrefixedUniqueId(v.(string))
	} else {
		sslCertName = resource.UniqueId()
	}

	createOpts := &iam.UploadServerCertificateInput{
		CertificateBody:       aws.String(d.Get("certificate_body").(string)),
		PrivateKey:            aws.String(d.Get("private_key").(string)),
		ServerCertificateName: aws.String(sslCertName),
	}

	if v, ok := d.GetOk("certificate_chain"); ok {
		createOpts.CertificateChain = aws.String(v.(string))
	}

	if v, ok := d.GetOk("path"); ok {
		createOpts.Path = aws.String(v.(string))
	}

	log.Printf("[DEBUG] Creating IAM Server Certificate with opts: %s", createOpts)
	resp, err := conn.UploadServerCertificate(createOpts)
	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			return fmt.Errorf("[WARN] Error uploading server certificate, error: %s: %s", awsErr.Code(), awsErr.Message())
		}
		return fmt.Errorf("[WARN] Error uploading server certificate, error: %s", err)
	}

	d.SetId(*resp.ServerCertificateMetadata.ServerCertificateId)
	d.Set("name", sslCertName)

	return resourceAwsIAMServerCertificateRead(d, meta)
}

func resourceAwsIAMServerCertificateRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).iamconn
	resp, err := conn.GetServerCertificate(&iam.GetServerCertificateInput{
		ServerCertificateName: aws.String(d.Get("name").(string)),
	})

	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			if awsErr.Code() == "NoSuchEntity" {
				log.Printf("[WARN] IAM Server Cert (%s) not found, removing from state", d.Id())
				d.SetId("")
				return nil
			}
			return fmt.Errorf("[WARN] Error reading IAM Server Certificate: %s: %s", awsErr.Code(), awsErr.Message())
		}
		return fmt.Errorf("[WARN] Error reading IAM Server Certificate: %s", err)
	}

	d.SetId(*resp.ServerCertificate.ServerCertificateMetadata.ServerCertificateId)

	// these values should always be present, and have a default if not set in
	// configuration, and so safe to reference with nil checks
	d.Set("certificate_body", normalizeCert(resp.ServerCertificate.CertificateBody))

	c := normalizeCert(resp.ServerCertificate.CertificateChain)
	if c != "" {
		d.Set("certificate_chain", c)
	}

	d.Set("path", resp.ServerCertificate.ServerCertificateMetadata.Path)
	d.Set("arn", resp.ServerCertificate.ServerCertificateMetadata.Arn)

	return nil
}

func resourceAwsIAMServerCertificateDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).iamconn
	log.Printf("[INFO] Deleting IAM Server Certificate: %s", d.Id())
	err := resource.Retry(15*time.Minute, func() *resource.RetryError {
		_, err := conn.DeleteServerCertificate(&iam.DeleteServerCertificateInput{
			ServerCertificateName: aws.String(d.Get("name").(string)),
		})

		if err != nil {
			if awsErr, ok := err.(awserr.Error); ok {
				if awsErr.Code() == "DeleteConflict" && strings.Contains(awsErr.Message(), "currently in use by arn") {
					log.Printf("[WARN] Conflict deleting server certificate: %s, retrying", awsErr.Message())
					return resource.RetryableError(err)
				}
				if awsErr.Code() == "NoSuchEntity" {
					log.Printf("[WARN] IAM Server Certificate (%s) not found, removing from state", d.Id())
					d.SetId("")
					return nil
				}
			}
			return resource.NonRetryableError(err)
		}
		return nil
	})

	if err != nil {
		return err
	}

	d.SetId("")
	return nil
}

func resourceAwsIAMServerCertificateImport(
	d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
	d.Set("name", d.Id())
	// private_key can't be fetched from any API call
	return []*schema.ResourceData{d}, nil
}

func normalizeCert(cert interface{}) string {
	if cert == nil || cert == (*string)(nil) {
		return ""
	}

	var rawCert string
	switch cert.(type) {
	case string:
		rawCert = cert.(string)
	case *string:
		rawCert = *cert.(*string)
	default:
		return ""
	}

	cleanVal := sha1.Sum(stripCR([]byte(strings.TrimSpace(rawCert))))
	return hex.EncodeToString(cleanVal[:])
}

// strip CRs from raw literals. Lifted from go/scanner/scanner.go
// See https://github.com/golang/go/blob/release-branch.go1.6/src/go/scanner/scanner.go#L479
func stripCR(b []byte) []byte {
	c := make([]byte, len(b))
	i := 0
	for _, ch := range b {
		if ch != '\r' {
			c[i] = ch
			i++
		}
	}
	return c[:i]
}
<|endoftext|>


<commit_before>package models

import (
	. "eaciit/wfdemo-git/library/helper"
	"time"

	"github.com/eaciit/orm"
)

type Monitoring struct {
	orm.ModelBase      `bson:"-",json:"-"`
	ID                 string ` bson:"_id" , json:"_id" `
	TimeStamp          time.Time
	DateInfo           DateInfo
	LastUpdate         time.Time
	LastUpdateDateInfo DateInfo
	Project            string
	Turbine            string

	Production       float64 // MWh - Energy
	WindSpeed        float64
	PerformanceIndex float64 // skip
	MachineAvail     float64 // skip
	GridAvail        float64 // skip

	RotorSpeedRPM float64
	/*===================================*/
	IsAlarm   bool
	IsWarning bool

	Type       string // Alarm, Brake, Warning
	Status     string // ok, brake, N/A
	StatusCode int    // brake : AlarmID
	StatusDesc string // brake : AlarmDescription
}

func (m *Monitoring) New() *Monitoring {
	timeStampStr := m.TimeStamp.Format("060102_150405")
	m.ID = m.Project + "#" + m.Turbine + "#" + timeStampStr
	return m
}

func (m *Monitoring) RecordID() interface{} {
	return m.ID
}

func (m *Monitoring) TableName() string {
	return "Monitoring"
}

type MonitoringEvent struct {
	orm.ModelBase    `bson:"-",json:"-"`
	ID               string ` bson:"_id" , json:"_id" `
	Project          string
	Turbine          string
	TimeStamp        time.Time
	DateInfo         DateInfo
	GroupTimeStamp   time.Time
	AlarmId          int
	AlarmDescription string
	Type             string // Alarm, Brake, Warning
	Status           string /// down, up
}

func (m MonitoringEvent) New() MonitoringEvent {
	timestampstr := m.TimeStamp.Format("060102_150405")
	nowstr := time.Now().Format("060102_150405")

	m.ID = m.Project + "#" + m.Turbine + "#" + timestampstr + "#" + m.AlarmId + "#" + m.Status + "#" + m.Type + "_" + nowstr
	return m
}

func (m *MonitoringEvent) RecordID() interface{} {
	return m.ID
}

func (m *MonitoringEvent) TableName() string {
	return "MonitoringEvent"
}
<commit_msg>type mismatch bug<commit_after>package models

import (
	. "eaciit/wfdemo-git/library/helper"
	"time"

	"github.com/eaciit/orm"
	tk "github.com/eaciit/toolkit"
)

type Monitoring struct {
	orm.ModelBase      `bson:"-",json:"-"`
	ID                 string ` bson:"_id" , json:"_id" `
	TimeStamp          time.Time
	DateInfo           DateInfo
	LastUpdate         time.Time
	LastUpdateDateInfo DateInfo
	Project            string
	Turbine            string

	Production       float64 // MWh - Energy
	WindSpeed        float64
	PerformanceIndex float64 // skip
	MachineAvail     float64 // skip
	GridAvail        float64 // skip

	RotorSpeedRPM float64
	/*===================================*/
	IsAlarm   bool
	IsWarning bool

	Type       string // Alarm, Brake, Warning
	Status     string // ok, brake, N/A
	StatusCode int    // brake : AlarmID
	StatusDesc string // brake : AlarmDescription
}

func (m *Monitoring) New() *Monitoring {
	timeStampStr := m.TimeStamp.Format("060102_150405")
	m.ID = m.Project + "#" + m.Turbine + "#" + timeStampStr
	return m
}

func (m *Monitoring) RecordID() interface{} {
	return m.ID
}

func (m *Monitoring) TableName() string {
	return "Monitoring"
}

type MonitoringEvent struct {
	orm.ModelBase    `bson:"-",json:"-"`
	ID               string ` bson:"_id" , json:"_id" `
	Project          string
	Turbine          string
	TimeStamp        time.Time
	DateInfo         DateInfo
	GroupTimeStamp   time.Time
	AlarmId          int
	AlarmDescription string
	Type             string // Alarm, Brake, Warning
	Status           string /// down, up
}

func (m MonitoringEvent) New() MonitoringEvent {
	timestampstr := m.TimeStamp.Format("060102_150405")
	nowstr := time.Now().Format("060102_150405")

	m.ID = m.Project + "#" + m.Turbine + "#" + timestampstr + "#" + tk.ToString(m.AlarmId) + "#" + m.Status + "#" + m.Type + "_" + nowstr
	return m
}

func (m *MonitoringEvent) RecordID() interface{} {
	return m.ID
}

func (m *MonitoringEvent) TableName() string {
	return "MonitoringEvent"
}
<|endoftext|>