<commit_before>// Copyright 2013 MongoDB, 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 rolling_file_appender provides a slogger Appender that
// supports log rotation.

package rolling_file_appender

import (
	"fmt"
	"github.com/tolsen/slogger/v2"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"time"
)

type RollingFileAppender struct {
	MaxFileSize     int64
	MaxRotatedLogs  int
	file            *os.File
	absPath         string
	curFileSize     int64
	headerGenerator func() []string
}

// New creates a new RollingFileAppender.  filename is path to the
// file to log to.  It can be a relative path (with respect to the
// current working directory) or an absolute path.  maxFileSize is the
// approximate file size that will be allowed before the log file is
// rotated.  Rotated log files will have suffix of the form
// .YYYY-MM-DDTHH-MM-SS or .YYYY-MM-DDTHH-MM-SS-N (where N is an
// incrementing serial number used to resolve conflicts) appended to
// them.  Set maxFileSize to a non-positive number if you wish there
// to be no limit.  maxRotatedLogs specifies the maximum number of
// rotated logs allowed before old logs are deleted.  If
// rotateIfExists is set to true and a log file with the same filename
// already exists, then the current one will be rotated.  If
// rotateIfExists is set to true and a log file with the same filename
// already exists, then the current log file will be appended to.  If
// a log file with the same filename does not exist, then a new log
// file is created regardless of the value of rotateIfExists.  As
// RotatingFileAppender is asynchronous, an errHandler can be provided
// that will be called when an error occurs.  It can set to nil if you
// do not want to provide one.  The return value headerGenerator, if
// not nil, is logged at the beginning of every log file.
//
// Note that after creating a RollingFileAppender with New(), you will
// probably want to defer a call to RollingFileAppender's Close() (or
// at least Flush()).  This ensures that in case of program exit
// (normal or panicking) that any pending logs are logged.
func New(filename string, maxFileSize int64, maxRotatedLogs int, rotateIfExists bool, headerGenerator func() []string) (*RollingFileAppender, error) {
	if headerGenerator == nil {
		headerGenerator = func() []string {
			return []string{}
		}
	}

	absPath, err := filepath.Abs(filename)
	if err != nil {
		return nil, err
	}

	appender := &RollingFileAppender{
		MaxFileSize:     maxFileSize,
		MaxRotatedLogs:  maxRotatedLogs,
		absPath:         absPath,
		headerGenerator: headerGenerator,
	}

	fileInfo, err := os.Stat(absPath)
	if err == nil && rotateIfExists { // err == nil means file exists
		return appender, appender.rotate()
	} else {
		// we're either creating a new log file or appending to the current one
		appender.file, err = os.OpenFile(
			absPath,
			os.O_WRONLY|os.O_APPEND|os.O_CREATE,
			0666,
		)
		if err != nil {
			return nil, err
		}

		if fileInfo != nil {
			appender.curFileSize = fileInfo.Size()
		}

		return appender, appender.logHeader()
	}
}

func (self *RollingFileAppender) Append(log *slogger.Log) error {
	n, err := self.appendSansSizeTracking(log)
	self.curFileSize += int64(n)

	if err != nil {
		return err
	}

	if self.MaxFileSize > 0 && self.curFileSize > self.MaxFileSize {
		return self.rotate()
	}

	return nil
}

func (self *RollingFileAppender) Close() error {
	err := self.Flush()
	if err != nil {
		return err
	}
	return self.file.Close()
}

func (self *RollingFileAppender) Flush() error {
	return self.file.Sync()
}

func rotatedFilename(baseFilename string, t time.Time, serial int) string {
	filename := fmt.Sprintf(
		"%s.%d-%02d-%02dT%02d-%02d-%02d",
		baseFilename,
		t.Year(),
		t.Month(),
		t.Day(),
		t.Hour(),
		t.Minute(),
		t.Second(),
	)

	if serial > 0 {
		filename = fmt.Sprintf("%s-%d", filename, serial)
	}

	return filename
}

func (self *RollingFileAppender) appendSansSizeTracking(log *slogger.Log) (bytesWritten int, err error) {
	if self.file == nil {
		return 0, NoFileError{}
	}

	msg := slogger.FormatLog(log)
	bytesWritten, err = self.file.WriteString(msg)

	if err != nil {
		err = WriteError{self.absPath, err}
	}

	return
}

func (self *RollingFileAppender) logHeader() error {
	header := self.headerGenerator()
	for _, line := range header {

		log := &slogger.Log{
			Prefix:     "header",
			Level:      slogger.INFO,
			Filename:   "",
			Line:       0,
			Timestamp:  time.Now(),
			MessageFmt: line,
			Args:       []interface{}{},
		}

		// do not count header as part of size towards rotation in
		// order to prevent infinite rotation when max size is smaller
		// than header
		_, err := self.appendSansSizeTracking(log)
		if err != nil {
			return err
		}
	}

	return nil
}

func (self *RollingFileAppender) removeMaxRotatedLogs() error {
	rotationTimes, err := self.rotationTimeSlice()

	if err != nil {
		return MinorRotationError{err}
	}

	numLogsToDelete := len(rotationTimes) - self.MaxRotatedLogs

	// return if we're under the limit
	if numLogsToDelete <= 0 {
		return nil
	}

	// otherwise remove enough of the oldest logfiles to bring us
	// under the limit
	sort.Sort(rotationTimes)
	for _, rotationTime := range rotationTimes[:numLogsToDelete] {
		if err = os.Remove(rotationTime.Filename); err != nil {
			return MinorRotationError{err}
		}
	}
	return nil
}

const MAX_ROTATE_SERIAL_NUM = 1000000000

func (self *RollingFileAppender) renameLogFile(oldFilename string) error {
	now := time.Now()

	var newFilename string
	var err error

	for serial := 0; err == nil; serial++ { // err == nil means file exists
		if serial > MAX_ROTATE_SERIAL_NUM {
			return RenameError{
				oldFilename,
				newFilename,
				fmt.Errorf("Reached max serial number: %d", MAX_ROTATE_SERIAL_NUM),
			}
		}
		newFilename = rotatedFilename(self.absPath, now, serial)
		_, err = os.Stat(newFilename)
	}

	err = os.Rename(oldFilename, newFilename)

	if err != nil {
		return RenameError{oldFilename, newFilename, err}
	}
	return nil
}

func (self *RollingFileAppender) rotate() error {
	// rename old log
	if err := self.renameLogFile(self.absPath); err != nil {
		return err
	}

	// close current log if we have one open
	if self.file != nil {
		if err := self.file.Close(); err != nil {
			return CloseError{self.absPath, err}
		}
	}
	self.curFileSize = 0

	// create new log
	file, err := os.Create(self.absPath)
	if err != nil {
		self.file = nil
		return OpenError{self.absPath, err}
	}
	self.file = file
	self.logHeader()

	// remove really old logs
	self.removeMaxRotatedLogs()

	return nil
}

func (self *RollingFileAppender) rotationTimeSlice() (RotationTimeSlice, error) {
	candidateFilenames, err := filepath.Glob(self.absPath + ".*")

	if err != nil {
		return nil, err
	}

	rotationTimes := make(RotationTimeSlice, 0, len(candidateFilenames))

	for _, candidateFilename := range candidateFilenames {
		rotationTime, err := extractRotationTimeFromFilename(candidateFilename)
		if err == nil {
			rotationTimes = append(rotationTimes, rotationTime)
		}
	}

	return rotationTimes, nil
}

type CloseError struct {
	Filename string
	Err      error
}

func (self CloseError) Error() string {
	return fmt.Sprintf(
		"rolling_file_appender: Failed to close %s: %s",
		self.Filename,
		self.Err.Error(),
	)
}

func IsCloseError(err error) bool {
	_, ok := err.(CloseError)
	return ok
}

type MinorRotationError struct {
	Err error
}

func (self MinorRotationError) Error() string {
	return ("rolling_file_appender: minor error while rotating logs: " + self.Err.Error())
}

func IsMinorRotationError(err error) bool {
	_, ok := err.(MinorRotationError)
	return ok
}

type NoFileError struct{}

func (NoFileError) Error() string {
	return "rolling_file_appender: No log file to write to"
}

func IsNoFileError(err error) bool {
	_, ok := err.(NoFileError)
	return ok
}

type OpenError struct {
	Filename string
	Err      error
}

func (self OpenError) Error() string {
	return fmt.Sprintf(
		"rolling_file_appender: Failed to open %s: %s",
		self.Filename,
		self.Err.Error(),
	)
}

func IsOpenError(err error) bool {
	_, ok := err.(OpenError)
	return ok
}

type RenameError struct {
	OldFilename string
	NewFilename string
	Err         error
}

func (self RenameError) Error() string {
	return fmt.Sprintf(
		"rolling_file_appender: Failed to rename %s to %s: %s",
		self.OldFilename,
		self.NewFilename,
		self.Err.Error(),
	)
}

func IsRenameError(err error) bool {
	_, ok := err.(RenameError)
	return ok
}

type WriteError struct {
	Filename string
	Err      error
}

func (self WriteError) Error() string {
	return fmt.Sprintf(
		"rolling_file_appender: Failed to write to %s: %s",
		self.Filename,
		self.Err.Error(),
	)
}

func IsWriteError(err error) bool {
	_, ok := err.(WriteError)
	return ok
}

type RotationTime struct {
	Time     time.Time
	Serial   int
	Filename string
}

type RotationTimeSlice [](*RotationTime)

func (self RotationTimeSlice) Len() int {
	return len(self)
}

func (self RotationTimeSlice) Less(i, j int) bool {
	if self[i].Time == self[j].Time {
		return self[i].Serial < self[j].Serial
	}

	return self[i].Time.Before(self[j].Time)
}

func (self RotationTimeSlice) Swap(i, j int) {
	self[i], self[j] = self[j], self[i]
}

var rotatedTimeRegExp = regexp.MustCompile(`\.(\d+-\d\d-\d\dT\d\d-\d\d-\d\d)(-(\d+))?$`)

func extractRotationTimeFromFilename(filename string) (*RotationTime, error) {
	match := rotatedTimeRegExp.FindStringSubmatch(filename)

	if match == nil {
		return nil, fmt.Errorf("Filename does not match rotation time format: %s", filename)
	}

	rotatedTime, err := time.Parse("2006-01-02T15-04-05", match[1])
	if err != nil {
		return nil, fmt.Errorf(
			"Time %s in filename %s did not parse: %v",
			match[1],
			filename,
			err,
		)
	}

	var serial int
	if match[3] != "" {
		serial, err = strconv.Atoi(match[3])

		if err != nil {
			return nil, fmt.Errorf(
				"Could not parse serial number in filename %s: %v",
				filename,
				err,
			)
		}
	}

	return &RotationTime{rotatedTime, serial, filename}, nil
}
<commit_msg>During log rotation, close file before renaming it<commit_after>// Copyright 2013 MongoDB, 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 rolling_file_appender provides a slogger Appender that
// supports log rotation.

package rolling_file_appender

import (
	"fmt"
	"github.com/tolsen/slogger/v2"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"time"
)

type RollingFileAppender struct {
	MaxFileSize     int64
	MaxRotatedLogs  int
	file            *os.File
	absPath         string
	curFileSize     int64
	headerGenerator func() []string
}

// New creates a new RollingFileAppender.  filename is path to the
// file to log to.  It can be a relative path (with respect to the
// current working directory) or an absolute path.  maxFileSize is the
// approximate file size that will be allowed before the log file is
// rotated.  Rotated log files will have suffix of the form
// .YYYY-MM-DDTHH-MM-SS or .YYYY-MM-DDTHH-MM-SS-N (where N is an
// incrementing serial number used to resolve conflicts) appended to
// them.  Set maxFileSize to a non-positive number if you wish there
// to be no limit.  maxRotatedLogs specifies the maximum number of
// rotated logs allowed before old logs are deleted.  If
// rotateIfExists is set to true and a log file with the same filename
// already exists, then the current one will be rotated.  If
// rotateIfExists is set to true and a log file with the same filename
// already exists, then the current log file will be appended to.  If
// a log file with the same filename does not exist, then a new log
// file is created regardless of the value of rotateIfExists.  As
// RotatingFileAppender is asynchronous, an errHandler can be provided
// that will be called when an error occurs.  It can set to nil if you
// do not want to provide one.  The return value headerGenerator, if
// not nil, is logged at the beginning of every log file.
//
// Note that after creating a RollingFileAppender with New(), you will
// probably want to defer a call to RollingFileAppender's Close() (or
// at least Flush()).  This ensures that in case of program exit
// (normal or panicking) that any pending logs are logged.
func New(filename string, maxFileSize int64, maxRotatedLogs int, rotateIfExists bool, headerGenerator func() []string) (*RollingFileAppender, error) {
	if headerGenerator == nil {
		headerGenerator = func() []string {
			return []string{}
		}
	}

	absPath, err := filepath.Abs(filename)
	if err != nil {
		return nil, err
	}

	appender := &RollingFileAppender{
		MaxFileSize:     maxFileSize,
		MaxRotatedLogs:  maxRotatedLogs,
		absPath:         absPath,
		headerGenerator: headerGenerator,
	}

	fileInfo, err := os.Stat(absPath)
	if err == nil && rotateIfExists { // err == nil means file exists
		return appender, appender.rotate()
	} else {
		// we're either creating a new log file or appending to the current one
		appender.file, err = os.OpenFile(
			absPath,
			os.O_WRONLY|os.O_APPEND|os.O_CREATE,
			0666,
		)
		if err != nil {
			return nil, err
		}

		if fileInfo != nil {
			appender.curFileSize = fileInfo.Size()
		}

		return appender, appender.logHeader()
	}
}

func (self *RollingFileAppender) Append(log *slogger.Log) error {
	n, err := self.appendSansSizeTracking(log)
	self.curFileSize += int64(n)

	if err != nil {
		return err
	}

	if self.MaxFileSize > 0 && self.curFileSize > self.MaxFileSize {
		return self.rotate()
	}

	return nil
}

func (self *RollingFileAppender) Close() error {
	err := self.Flush()
	if err != nil {
		return err
	}
	return self.file.Close()
}

func (self *RollingFileAppender) Flush() error {
	return self.file.Sync()
}

func rotatedFilename(baseFilename string, t time.Time, serial int) string {
	filename := fmt.Sprintf(
		"%s.%d-%02d-%02dT%02d-%02d-%02d",
		baseFilename,
		t.Year(),
		t.Month(),
		t.Day(),
		t.Hour(),
		t.Minute(),
		t.Second(),
	)

	if serial > 0 {
		filename = fmt.Sprintf("%s-%d", filename, serial)
	}

	return filename
}

func (self *RollingFileAppender) appendSansSizeTracking(log *slogger.Log) (bytesWritten int, err error) {
	if self.file == nil {
		return 0, NoFileError{}
	}

	msg := slogger.FormatLog(log)
	bytesWritten, err = self.file.WriteString(msg)

	if err != nil {
		err = WriteError{self.absPath, err}
	}

	return
}

func (self *RollingFileAppender) logHeader() error {
	header := self.headerGenerator()
	for _, line := range header {

		log := &slogger.Log{
			Prefix:     "header",
			Level:      slogger.INFO,
			Filename:   "",
			Line:       0,
			Timestamp:  time.Now(),
			MessageFmt: line,
			Args:       []interface{}{},
		}

		// do not count header as part of size towards rotation in
		// order to prevent infinite rotation when max size is smaller
		// than header
		_, err := self.appendSansSizeTracking(log)
		if err != nil {
			return err
		}
	}

	return nil
}

func (self *RollingFileAppender) removeMaxRotatedLogs() error {
	rotationTimes, err := self.rotationTimeSlice()

	if err != nil {
		return MinorRotationError{err}
	}

	numLogsToDelete := len(rotationTimes) - self.MaxRotatedLogs

	// return if we're under the limit
	if numLogsToDelete <= 0 {
		return nil
	}

	// otherwise remove enough of the oldest logfiles to bring us
	// under the limit
	sort.Sort(rotationTimes)
	for _, rotationTime := range rotationTimes[:numLogsToDelete] {
		if err = os.Remove(rotationTime.Filename); err != nil {
			return MinorRotationError{err}
		}
	}
	return nil
}

const MAX_ROTATE_SERIAL_NUM = 1000000000

func (self *RollingFileAppender) renameLogFile(oldFilename string) error {
	now := time.Now()

	var newFilename string
	var err error

	for serial := 0; err == nil; serial++ { // err == nil means file exists
		if serial > MAX_ROTATE_SERIAL_NUM {
			return RenameError{
				oldFilename,
				newFilename,
				fmt.Errorf("Reached max serial number: %d", MAX_ROTATE_SERIAL_NUM),
			}
		}
		newFilename = rotatedFilename(self.absPath, now, serial)
		_, err = os.Stat(newFilename)
	}

	err = os.Rename(oldFilename, newFilename)

	if err != nil {
		return RenameError{oldFilename, newFilename, err}
	}
	return nil
}

func (self *RollingFileAppender) rotate() error {
	// close current log if we have one open
	if self.file != nil {
		if err := self.file.Close(); err != nil {
			return CloseError{self.absPath, err}
		}
	}
	self.curFileSize = 0

	// rename old log
	if err := self.renameLogFile(self.absPath); err != nil {
		return err
	}

	// create new log
	file, err := os.Create(self.absPath)
	if err != nil {
		self.file = nil
		return OpenError{self.absPath, err}
	}
	self.file = file
	self.logHeader()

	// remove really old logs
	self.removeMaxRotatedLogs()

	return nil
}

func (self *RollingFileAppender) rotationTimeSlice() (RotationTimeSlice, error) {
	candidateFilenames, err := filepath.Glob(self.absPath + ".*")

	if err != nil {
		return nil, err
	}

	rotationTimes := make(RotationTimeSlice, 0, len(candidateFilenames))

	for _, candidateFilename := range candidateFilenames {
		rotationTime, err := extractRotationTimeFromFilename(candidateFilename)
		if err == nil {
			rotationTimes = append(rotationTimes, rotationTime)
		}
	}

	return rotationTimes, nil
}

type CloseError struct {
	Filename string
	Err      error
}

func (self CloseError) Error() string {
	return fmt.Sprintf(
		"rolling_file_appender: Failed to close %s: %s",
		self.Filename,
		self.Err.Error(),
	)
}

func IsCloseError(err error) bool {
	_, ok := err.(CloseError)
	return ok
}

type MinorRotationError struct {
	Err error
}

func (self MinorRotationError) Error() string {
	return ("rolling_file_appender: minor error while rotating logs: " + self.Err.Error())
}

func IsMinorRotationError(err error) bool {
	_, ok := err.(MinorRotationError)
	return ok
}

type NoFileError struct{}

func (NoFileError) Error() string {
	return "rolling_file_appender: No log file to write to"
}

func IsNoFileError(err error) bool {
	_, ok := err.(NoFileError)
	return ok
}

type OpenError struct {
	Filename string
	Err      error
}

func (self OpenError) Error() string {
	return fmt.Sprintf(
		"rolling_file_appender: Failed to open %s: %s",
		self.Filename,
		self.Err.Error(),
	)
}

func IsOpenError(err error) bool {
	_, ok := err.(OpenError)
	return ok
}

type RenameError struct {
	OldFilename string
	NewFilename string
	Err         error
}

func (self RenameError) Error() string {
	return fmt.Sprintf(
		"rolling_file_appender: Failed to rename %s to %s: %s",
		self.OldFilename,
		self.NewFilename,
		self.Err.Error(),
	)
}

func IsRenameError(err error) bool {
	_, ok := err.(RenameError)
	return ok
}

type WriteError struct {
	Filename string
	Err      error
}

func (self WriteError) Error() string {
	return fmt.Sprintf(
		"rolling_file_appender: Failed to write to %s: %s",
		self.Filename,
		self.Err.Error(),
	)
}

func IsWriteError(err error) bool {
	_, ok := err.(WriteError)
	return ok
}

type RotationTime struct {
	Time     time.Time
	Serial   int
	Filename string
}

type RotationTimeSlice [](*RotationTime)

func (self RotationTimeSlice) Len() int {
	return len(self)
}

func (self RotationTimeSlice) Less(i, j int) bool {
	if self[i].Time == self[j].Time {
		return self[i].Serial < self[j].Serial
	}

	return self[i].Time.Before(self[j].Time)
}

func (self RotationTimeSlice) Swap(i, j int) {
	self[i], self[j] = self[j], self[i]
}

var rotatedTimeRegExp = regexp.MustCompile(`\.(\d+-\d\d-\d\dT\d\d-\d\d-\d\d)(-(\d+))?$`)

func extractRotationTimeFromFilename(filename string) (*RotationTime, error) {
	match := rotatedTimeRegExp.FindStringSubmatch(filename)

	if match == nil {
		return nil, fmt.Errorf("Filename does not match rotation time format: %s", filename)
	}

	rotatedTime, err := time.Parse("2006-01-02T15-04-05", match[1])
	if err != nil {
		return nil, fmt.Errorf(
			"Time %s in filename %s did not parse: %v",
			match[1],
			filename,
			err,
		)
	}

	var serial int
	if match[3] != "" {
		serial, err = strconv.Atoi(match[3])

		if err != nil {
			return nil, fmt.Errorf(
				"Could not parse serial number in filename %s: %v",
				filename,
				err,
			)
		}
	}

	return &RotationTime{rotatedTime, serial, filename}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/binary"
	"erlang/dist"
	"flag"
	"fmt"
	"log"
	"net"
	"strconv"
	"time"
)

var listenPort string

func init() {
	flag.StringVar(&listenPort, "port", "4369", "listen port")
}

type regAns struct {
	reply   []byte
	isClose bool
}

type regReq struct {
	buf     []byte
	replyTo chan regAns
	conn    net.Conn
}

func main() {
	flag.Parse()

	l, err := net.Listen("tcp", net.JoinHostPort("", listenPort))
	if err != nil {
		log.Fatal(err)
	}
	epm := make(chan regReq, 10)
	go epmReg(epm)
	for {
		conn, err := l.Accept()
		log.Printf("Accept new")
		if err != nil {
			log.Printf(err.Error())
		} else {
			go mLoop(conn, epm)
		}
	}
	log.Printf("Exit")
}

type nodeRec struct {
	dist.NodeInfo
	Time  time.Time
	Ready bool
	conn  net.Conn
}

func epmReg(in <-chan regReq) {

	var nReg = make(map[string]*nodeRec)

	for {
		select {
		case req := <-in:
			buf := req.buf
			if len(buf) == 0 {
				rs := len(nReg)
				log.Printf("REG %d records", rs)
				now := time.Now()

				for node, rec := range nReg {
					if rec.conn == req.conn {
						log.Printf("Connection for %s dropped", node)
						nReg[node].Ready = false
						nReg[node].Time = now
					} else if rs > 10 && !rec.Ready && now.Sub(rec.Time).Seconds() > 30 {
						log.Printf("REG prune %s:%+v", node, rec)
						delete(nReg, node)
					}
				}
				continue
			}
			replyTo := req.replyTo
			log.Printf("IN: %v", buf)
			switch dist.MessageId(buf[0]) {
			case dist.ALIVE2_REQ:
				nConn := req.conn
				nPort := binary.BigEndian.Uint16(buf[1:3])
				nType := buf[3]
				nProto := buf[4]
				highVsn := binary.BigEndian.Uint16(buf[5:7])
				lowVsn := binary.BigEndian.Uint16(buf[7:9])
				nLen := binary.BigEndian.Uint16(buf[9:11])
				offset := (11 + nLen)
				nName := string(buf[11:offset])
				//nELen := binary.BigEndian.Uint16(buf[offset:(offset+2)])
				nExtra := buf[(offset + 2):]
				log.Printf("Alive: N:%s, P:%d, T:%d", nName, nPort, nType)

				reply := make([]byte, 4)
				reply[0] = byte(dist.ALIVE2_RESP)
				reply[1] = 0 // OK

				var data uint16 = 0
				if rec, ok := nReg[nName]; ok {
					log.Printf("Node %s found", nName)
					if rec.Ready {
						log.Printf("Node %s is running", nName)
						reply[1] = 1 // ERROR
						data = 99    // CANNOT REGISTER
					} else {
						log.Printf("Node %s is not running", nName)
						rec.conn = nConn
						rec.Port = nPort
						rec.Type = nType
						rec.Protocol = nProto
						rec.HighVsn = highVsn
						rec.LowVsn = lowVsn
						rec.Extra = nExtra
						rec.Creation = (rec.Creation % 3) + 1
						rec.Ready = true
						data = rec.Creation
					}
				} else {
					log.Printf("New node %s", nName)
					rec := &nodeRec{
						NodeInfo: dist.NodeInfo{
							Name:     nName,
							Port:     nPort,
							Type:     nType,
							Protocol: nProto,
							HighVsn:  highVsn,
							LowVsn:   lowVsn,
							Extra:    nExtra,
							Creation: 1,
						},
						conn:  nConn,
						Time:  time.Now(),
						Ready: true,
					}
					nReg[nName] = rec
					data = rec.Creation
				}

				binary.BigEndian.PutUint16(reply[2:4], data)
				replyTo <- regAns{reply: reply, isClose: false}
			case dist.PORT_PLEASE2_REQ:
				nName := buf[1:]
				var reply []byte
				if rec, ok := nReg[string(nName)]; ok {
					reply = make([]byte, 14+len(nName)+len(rec.Extra))
					reply[0] = byte(dist.PORT2_RESP)
					reply[1] = 0 // OK
					binary.BigEndian.PutUint16(reply[2:4], rec.Port)
					reply[4] = rec.Type
					reply[5] = rec.Protocol
					binary.BigEndian.PutUint16(reply[6:8], rec.HighVsn)
					binary.BigEndian.PutUint16(reply[8:10], rec.LowVsn)
					nLen := len(rec.Name)
					binary.BigEndian.PutUint16(reply[10:12], uint16(nLen))
					offset := (12 + nLen)
					copy(reply[12:offset], rec.Name)
					nELen := len(rec.Extra)
					binary.BigEndian.PutUint16(reply[offset:offset+2], uint16(nELen))
					copy(reply[offset+2:offset+2+nELen], rec.Extra)
				} else {
					reply = make([]byte, 2)
					reply[0] = byte(dist.PORT2_RESP)
					reply[1] = 1 // ERROR
				}
				replyTo <- regAns{reply: reply, isClose: true}
			case dist.NAMES_REQ, dist.DUMP_REQ:
				log.Printf("NAMES_REQ")
				lp, err := strconv.Atoi(listenPort)
				if err != nil {
					log.Printf("Cannot convert %s to integer", listenPort)
					replyTo <- regAns{reply: nil, isClose: true}
				} else {
					log.Printf("Make a reply")

					var replyB bytes.Buffer
					reply := make([]byte, 4)
					binary.BigEndian.PutUint32(reply, uint32(lp))
					replyB.Write(reply)

					for node, rec := range nReg {
						if rec.Ready {
							if dist.MessageId(buf[0]) == dist.NAMES_REQ {
								replyB.Write([]byte(fmt.Sprintf("name %s at port %d\n", node, rec.Port)))
							} else {
								if rec.Ready {
									replyB.Write([]byte(fmt.Sprintf("active name     <%s> at port %d\n", node, rec.Port)))
								} else {
									replyB.Write([]byte(fmt.Sprintf("old/unused name <%s>, port = %d\n", node, rec.Port)))
								}
							}
						}
					}
					replyTo <- regAns{reply: replyB.Bytes(), isClose: true}
				}
			case dist.KILL_REQ:
				replyTo <- regAns{reply: []byte("OK"), isClose: true}
			default:
				replyTo <- regAns{reply: nil, isClose: true}
			}
		}
	}
}

func mLoop(c net.Conn, epm chan regReq) {
	buf := make([]byte, 1024)
	for {
		n, err := c.Read(buf)
		if err != nil {
			c.Close()
			log.Printf("Stop loop: %v", err)
			epm <- regReq{buf: []byte{}, conn: c}
			return
		}
		length := binary.BigEndian.Uint16(buf[0:2])
		if length != uint16(n-2) {
			log.Printf("Incomplete packet: %d from %d", n, length)
		}
		log.Printf("Read %d, %d: %v", n, length, buf[2:n])
		if isClose := handleMsg(c, buf[2:n], epm); isClose {
			break
		}
	}
	c.Close()
}

func handleMsg(c net.Conn, buf []byte, epm chan regReq) bool {
	myChan := make(chan regAns)
	epm <- regReq{buf: buf, replyTo: myChan, conn: c}
	select {
	case ans := <-myChan:
		log.Printf("Got reply: %+v", ans)
		if ans.reply != nil {
			c.Write(ans.reply)
		}
		return ans.isClose
	}
	return true
}
<commit_msg>Add flags for table size limit and unregistered nodes TTL<commit_after>package main

import (
	"bytes"
	"encoding/binary"
	"erlang/dist"
	"flag"
	"fmt"
	"log"
	"net"
	"strconv"
	"time"
)

var listenPort string
var regLimit int
var unregTTL int

func init() {
	flag.StringVar(&listenPort, "port", "4369", "listen port")
	flag.IntVar(&regLimit, "nodes-limit", 1000, "limit size of registration table to prune unregistered nodes")
	flag.IntVar(&unregTTL, "unreg-ttl", 10, "prune unregistered nodes if unregistration older than this value in minutes")
}

type regAns struct {
	reply   []byte
	isClose bool
}

type regReq struct {
	buf     []byte
	replyTo chan regAns
	conn    net.Conn
}

func main() {
	flag.Parse()

	l, err := net.Listen("tcp", net.JoinHostPort("", listenPort))
	if err != nil {
		log.Fatal(err)
	}
	epm := make(chan regReq, 10)
	go epmReg(epm)
	for {
		conn, err := l.Accept()
		log.Printf("Accept new")
		if err != nil {
			log.Printf(err.Error())
		} else {
			go mLoop(conn, epm)
		}
	}
	log.Printf("Exit")
}

type nodeRec struct {
	dist.NodeInfo
	Time  time.Time
	Ready bool
	conn  net.Conn
}

func epmReg(in <-chan regReq) {

	var nReg = make(map[string]*nodeRec)

	for {
		select {
		case req := <-in:
			buf := req.buf
			if len(buf) == 0 {
				rs := len(nReg)
				log.Printf("REG %d records", rs)
				now := time.Now()

				for node, rec := range nReg {
					if rec.conn == req.conn {
						log.Printf("Connection for %s dropped", node)
						nReg[node].Ready = false
						nReg[node].Time = now
					} else if rs > regLimit && !rec.Ready && now.Sub(rec.Time).Minutes() > float64(unregTTL) {
						log.Printf("REG prune %s:%+v", node, rec)
						delete(nReg, node)
					}
				}
				continue
			}
			replyTo := req.replyTo
			log.Printf("IN: %v", buf)
			switch dist.MessageId(buf[0]) {
			case dist.ALIVE2_REQ:
				nConn := req.conn
				nPort := binary.BigEndian.Uint16(buf[1:3])
				nType := buf[3]
				nProto := buf[4]
				highVsn := binary.BigEndian.Uint16(buf[5:7])
				lowVsn := binary.BigEndian.Uint16(buf[7:9])
				nLen := binary.BigEndian.Uint16(buf[9:11])
				offset := (11 + nLen)
				nName := string(buf[11:offset])
				//nELen := binary.BigEndian.Uint16(buf[offset:(offset+2)])
				nExtra := buf[(offset + 2):]
				log.Printf("Alive: N:%s, P:%d, T:%d", nName, nPort, nType)

				reply := make([]byte, 4)
				reply[0] = byte(dist.ALIVE2_RESP)
				reply[1] = 0 // OK

				var data uint16 = 0
				if rec, ok := nReg[nName]; ok {
					log.Printf("Node %s found", nName)
					if rec.Ready {
						log.Printf("Node %s is running", nName)
						reply[1] = 1 // ERROR
						data = 99    // CANNOT REGISTER
					} else {
						log.Printf("Node %s is not running", nName)
						rec.conn = nConn
						rec.Port = nPort
						rec.Type = nType
						rec.Protocol = nProto
						rec.HighVsn = highVsn
						rec.LowVsn = lowVsn
						rec.Extra = nExtra
						rec.Creation = (rec.Creation % 3) + 1
						rec.Ready = true
						data = rec.Creation
					}
				} else {
					log.Printf("New node %s", nName)
					rec := &nodeRec{
						NodeInfo: dist.NodeInfo{
							Name:     nName,
							Port:     nPort,
							Type:     nType,
							Protocol: nProto,
							HighVsn:  highVsn,
							LowVsn:   lowVsn,
							Extra:    nExtra,
							Creation: 1,
						},
						conn:  nConn,
						Time:  time.Now(),
						Ready: true,
					}
					nReg[nName] = rec
					data = rec.Creation
				}

				binary.BigEndian.PutUint16(reply[2:4], data)
				replyTo <- regAns{reply: reply, isClose: false}
			case dist.PORT_PLEASE2_REQ:
				nName := buf[1:]
				var reply []byte
				if rec, ok := nReg[string(nName)]; ok {
					reply = make([]byte, 14+len(nName)+len(rec.Extra))
					reply[0] = byte(dist.PORT2_RESP)
					reply[1] = 0 // OK
					binary.BigEndian.PutUint16(reply[2:4], rec.Port)
					reply[4] = rec.Type
					reply[5] = rec.Protocol
					binary.BigEndian.PutUint16(reply[6:8], rec.HighVsn)
					binary.BigEndian.PutUint16(reply[8:10], rec.LowVsn)
					nLen := len(rec.Name)
					binary.BigEndian.PutUint16(reply[10:12], uint16(nLen))
					offset := (12 + nLen)
					copy(reply[12:offset], rec.Name)
					nELen := len(rec.Extra)
					binary.BigEndian.PutUint16(reply[offset:offset+2], uint16(nELen))
					copy(reply[offset+2:offset+2+nELen], rec.Extra)
				} else {
					reply = make([]byte, 2)
					reply[0] = byte(dist.PORT2_RESP)
					reply[1] = 1 // ERROR
				}
				replyTo <- regAns{reply: reply, isClose: true}
			case dist.NAMES_REQ, dist.DUMP_REQ:
				log.Printf("NAMES_REQ")
				lp, err := strconv.Atoi(listenPort)
				if err != nil {
					log.Printf("Cannot convert %s to integer", listenPort)
					replyTo <- regAns{reply: nil, isClose: true}
				} else {
					log.Printf("Make a reply")

					var replyB bytes.Buffer
					reply := make([]byte, 4)
					binary.BigEndian.PutUint32(reply, uint32(lp))
					replyB.Write(reply)

					for node, rec := range nReg {
						if rec.Ready {
							if dist.MessageId(buf[0]) == dist.NAMES_REQ {
								replyB.Write([]byte(fmt.Sprintf("name %s at port %d\n", node, rec.Port)))
							} else {
								if rec.Ready {
									replyB.Write([]byte(fmt.Sprintf("active name     <%s> at port %d\n", node, rec.Port)))
								} else {
									replyB.Write([]byte(fmt.Sprintf("old/unused name <%s>, port = %d\n", node, rec.Port)))
								}
							}
						}
					}
					replyTo <- regAns{reply: replyB.Bytes(), isClose: true}
				}
			case dist.KILL_REQ:
				replyTo <- regAns{reply: []byte("OK"), isClose: true}
			default:
				replyTo <- regAns{reply: nil, isClose: true}
			}
		}
	}
}

func mLoop(c net.Conn, epm chan regReq) {
	buf := make([]byte, 1024)
	for {
		n, err := c.Read(buf)
		if err != nil {
			c.Close()
			log.Printf("Stop loop: %v", err)
			epm <- regReq{buf: []byte{}, conn: c}
			return
		}
		length := binary.BigEndian.Uint16(buf[0:2])
		if length != uint16(n-2) {
			log.Printf("Incomplete packet: %d from %d", n, length)
		}
		log.Printf("Read %d, %d: %v", n, length, buf[2:n])
		if isClose := handleMsg(c, buf[2:n], epm); isClose {
			break
		}
	}
	c.Close()
}

func handleMsg(c net.Conn, buf []byte, epm chan regReq) bool {
	myChan := make(chan regAns)
	epm <- regReq{buf: buf, replyTo: myChan, conn: c}
	select {
	case ans := <-myChan:
		log.Printf("Got reply: %+v", ans)
		if ans.reply != nil {
			c.Write(ans.reply)
		}
		return ans.isClose
	}
	return true
}
<|endoftext|>


<commit_before>package uncertainty

import (
	"errors"
	"math"

	"github.com/ready-steady/linear/matrix"
)

var (
	infinity = math.Inf(1.0)
)

func invert(U, Λ []float64, m uint) ([]float64, error) {
	T := make([]float64, m*m)
	for i := uint(0); i < m; i++ {
		if Λ[i] == 0.0 {
			return nil, errors.New("the matrix is not invertible")
		}
		λ := 1.0 / Λ[i]
		for j := uint(0); j < m; j++ {
			T[j*m+i] = λ * U[i*m+j]
		}
	}

	I := make([]float64, m*m)
	matrix.Multiply(U, T, I, m, m, m)

	return I, nil
}

func inspect(x []float64, m uint) (bool, []float64) {
	ok, signs := true, make([]float64, m)
	for i := uint(0); i < m; i++ {
		switch x[i] {
		case -infinity:
			ok, signs[i] = false, -1.0
		case infinity:
			ok, signs[i] = false, +1.0
		}
	}
	return ok, signs
}

func multiply(A, x []float64, m, n uint) []float64 {
	y := make([]float64, m)
	ok, s := inspect(x, n)
	if ok {
		matrix.Multiply(A, x, y, m, n, 1)
		return y
	}
	for i := uint(0); i < m; i++ {
		fin, inf := 0.0, 0.0
		for j := uint(0); j < n; j++ {
			a := A[j*m+i]
			if a == 0.0 {
				continue
			}
			if s[j] == 0.0 {
				fin += a * x[j]
			} else {
				inf += a * s[j]
			}
		}
		if inf != 0.0 {
			y[i] = inf * infinity
		} else {
			y[i] = fin
		}
	}
	return y
}

func quadratic(A, x []float64, m uint) float64 {
	ok, s := inspect(x, m)
	if ok {
		y := make([]float64, m)
		matrix.Multiply(A, x, y, m, m, 1)
		return matrix.Dot(x, y, m)
	}
	Fin, Inf, INF := 0.0, 0.0, 0.0
	for i := uint(0); i < m; i++ {
		fin, inf := 0.0, 0.0
		for j := uint(0); j < m; j++ {
			a := A[j*m+i]
			if a == 0.0 {
				continue
			}
			if s[j] == 0.0 {
				fin += a * x[j]
			} else {
				inf += a * s[j]
			}
		}
		if s[i] == 0.0 {
			Fin += x[i] * fin
			Inf += x[i] * inf
		} else {
			Inf += s[i] * fin
			INF += s[i] * inf
		}
	}
	if INF != 0.0 {
		return INF * infinity
	} else if Inf != 0.0 {
		return Inf * infinity
	} else {
		return Fin
	}
}
<commit_msg>i/uncertainty: make a cosmetic adjustment<commit_after>package uncertainty

import (
	"errors"
	"math"

	"github.com/ready-steady/linear/matrix"
)

var (
	infinity = math.Inf(1.0)
)

func inspect(x []float64, m uint) (bool, []float64) {
	ok, signs := true, make([]float64, m)
	for i := uint(0); i < m; i++ {
		switch x[i] {
		case -infinity:
			ok, signs[i] = false, -1.0
		case infinity:
			ok, signs[i] = false, +1.0
		}
	}
	return ok, signs
}

func invert(U, Λ []float64, m uint) ([]float64, error) {
	T := make([]float64, m*m)
	for i := uint(0); i < m; i++ {
		if Λ[i] == 0.0 {
			return nil, errors.New("the matrix is not invertible")
		}
		λ := 1.0 / Λ[i]
		for j := uint(0); j < m; j++ {
			T[j*m+i] = λ * U[i*m+j]
		}
	}

	I := make([]float64, m*m)
	matrix.Multiply(U, T, I, m, m, m)

	return I, nil
}

func multiply(A, x []float64, m, n uint) []float64 {
	y := make([]float64, m)
	ok, s := inspect(x, n)
	if ok {
		matrix.Multiply(A, x, y, m, n, 1)
		return y
	}
	for i := uint(0); i < m; i++ {
		fin, inf := 0.0, 0.0
		for j := uint(0); j < n; j++ {
			a := A[j*m+i]
			if a == 0.0 {
				continue
			}
			if s[j] == 0.0 {
				fin += a * x[j]
			} else {
				inf += a * s[j]
			}
		}
		if inf != 0.0 {
			y[i] = inf * infinity
		} else {
			y[i] = fin
		}
	}
	return y
}

func quadratic(A, x []float64, m uint) float64 {
	ok, s := inspect(x, m)
	if ok {
		y := make([]float64, m)
		matrix.Multiply(A, x, y, m, m, 1)
		return matrix.Dot(x, y, m)
	}
	Fin, Inf, INF := 0.0, 0.0, 0.0
	for i := uint(0); i < m; i++ {
		fin, inf := 0.0, 0.0
		for j := uint(0); j < m; j++ {
			a := A[j*m+i]
			if a == 0.0 {
				continue
			}
			if s[j] == 0.0 {
				fin += a * x[j]
			} else {
				inf += a * s[j]
			}
		}
		if s[i] == 0.0 {
			Fin += x[i] * fin
			Inf += x[i] * inf
		} else {
			Inf += s[i] * fin
			INF += s[i] * inf
		}
	}
	if INF != 0.0 {
		return INF * infinity
	} else if Inf != 0.0 {
		return Inf * infinity
	} else {
		return Fin
	}
}
<|endoftext|>


<commit_before>package app

import (
	"fmt"
	humanize "github.com/dustin/go-humanize"
	"github.com/heysquirrel/tribe/git"
	"github.com/heysquirrel/tribe/view"
	"github.com/jroimartin/gocui"
	"io"
	"strconv"
	"strings"
)

type Column struct {
	name string
	size float64
}

type Row []string

type Table struct {
	width   int
	columns []Column
	rows    []Row
}

func NewColumn(name string, size float64) Column {
	return Column{name: name, size: size}
}

func NewTable(width int) *Table {
	table := new(Table)
	table.width = width
	table.columns = make([]Column, 0)
	table.rows = make([]Row, 0)

	return table
}

func (t *Table) AddColumn(name string, size float64) {
	t.columns = append(t.columns, NewColumn(name, size))
}

func (t *Table) MustAddRow(row Row) {
	if len(t.columns) != len(row) {
		panic("Row size should match column size")
	}

	t.rows = append(t.rows, row)
}

func Center2(s string, width int) string {
	leftPad := width/2 + len(s)/2

	if leftPad%2 != 0 {
		leftPad = leftPad + 1
	}

	return fmt.Sprintf(fmt.Sprintf("%%-%ds", width), fmt.Sprintf(fmt.Sprintf("%%%ds", leftPad), s))
}

func (t *Table) Render(w io.Writer) {
	maxView := t.width - 2

	header := make([]string, 0)
	for _, column := range t.columns {
		columnSize := int(float64(maxView) * column.size)
		header = append(header, Center2(column.name, columnSize))
	}
	fmt.Fprintln(w, strings.Join(header, "|"))

	fmt.Fprintf(w, "+%s+\n", strings.Repeat("-", maxView))

	for i, row := range t.rows {
		columns := make([]string, 0)

		for j, column := range t.columns {
			columnSize := int(float64(maxView) * column.size)
			data := row[j]
			if i == 0 && j == 0 {
				data = fmt.Sprintf(" 🌶  %s", data)
			}
			columnFormat := fmt.Sprintf(" %%-%ds", columnSize-1)
			columns = append(columns, fmt.Sprintf(columnFormat, data))
		}

		fmt.Fprintln(w, strings.Join(columns, "|"))
	}
}

func (a *App) UpdateContributors2(contributors []*git.Contributor) {
	a.updateView(contributorsView, func(v *gocui.View) {
		maxX, _ := v.Size()
		table := NewTable(maxX)
		table.AddColumn("NAME", 0.55)
		table.AddColumn("COMMITS", 0.2)
		table.AddColumn("LAST COMMIT", 0.25)

		for _, contributor := range contributors {
			table.MustAddRow([]string{contributor.Name, strconv.Itoa(contributor.Count), humanize.Time(contributor.LastCommit)})
		}

		table.Render(v)
	})
}

func (a *App) UpdateRelatedFiles(files []*git.RelatedFile) {
	a.updateView(associatedFilesView, func(v *gocui.View) {
		maxX, _ := v.Size()

		table := NewTable(maxX)
		table.AddColumn("NAME", 0.75)
		table.AddColumn("COMMITS", 0.1)
		table.AddColumn("LAST COMMIT", 0.15)

		for _, file := range files {
			table.MustAddRow([]string{
				view.RenderFilename(file.Name),
				strconv.Itoa(file.Count),
				humanize.Time(file.LastCommit)})
		}

		table.Render(v)
	})
}
<commit_msg>Calculate column size (int) when adding column to table.<commit_after>package app

import (
	"fmt"
	humanize "github.com/dustin/go-humanize"
	"github.com/heysquirrel/tribe/git"
	"github.com/heysquirrel/tribe/view"
	"github.com/jroimartin/gocui"
	"io"
	"strconv"
	"strings"
)

type Column struct {
	name string
	size int
}

type Row []string

type Table struct {
	width   int
	columns []Column
	rows    []Row
}

func NewColumn(name string, size int) Column {
	return Column{name: name, size: size}
}

func NewTable(width int) *Table {
	table := new(Table)
	table.width = width
	table.columns = make([]Column, 0)
	table.rows = make([]Row, 0)

	return table
}

func (t *Table) AddColumn(name string, size float64) {
	columnSize := int(float64(t.width) * size)
	t.columns = append(t.columns, NewColumn(name, columnSize))
}

func (t *Table) MustAddRow(row Row) {
	if len(t.columns) != len(row) {
		panic("Row size should match column size")
	}

	t.rows = append(t.rows, row)
}

func Center2(s string, width int) string {
	leftPad := width/2 + len(s)/2

	if leftPad%2 != 0 {
		leftPad = leftPad + 1
	}

	return fmt.Sprintf(fmt.Sprintf("%%-%ds", width), fmt.Sprintf(fmt.Sprintf("%%%ds", leftPad), s))
}

func (t *Table) Render(w io.Writer) {
	maxView := t.width - 2

	header := make([]string, 0)
	for _, column := range t.columns {
		header = append(header, Center2(column.name, column.size))
	}
	fmt.Fprintln(w, strings.Join(header, "|"))

	fmt.Fprintf(w, "+%s+\n", strings.Repeat("-", maxView))

	for i, row := range t.rows {
		columns := make([]string, 0)

		for j, column := range t.columns {
			data := row[j]
			if i == 0 && j == 0 {
				data = fmt.Sprintf(" 🌶  %s", data)
			}
			columnFormat := fmt.Sprintf(" %%-%ds", column.size-1)
			columns = append(columns, fmt.Sprintf(columnFormat, data))
		}

		fmt.Fprintln(w, strings.Join(columns, "|"))
	}
}

func (a *App) UpdateContributors2(contributors []*git.Contributor) {
	a.updateView(contributorsView, func(v *gocui.View) {
		maxX, _ := v.Size()
		table := NewTable(maxX)
		table.AddColumn("NAME", 0.55)
		table.AddColumn("COMMITS", 0.2)
		table.AddColumn("LAST COMMIT", 0.25)

		for _, contributor := range contributors {
			table.MustAddRow([]string{contributor.Name, strconv.Itoa(contributor.Count), humanize.Time(contributor.LastCommit)})
		}

		table.Render(v)
	})
}

func (a *App) UpdateRelatedFiles(files []*git.RelatedFile) {
	a.updateView(associatedFilesView, func(v *gocui.View) {
		maxX, _ := v.Size()

		table := NewTable(maxX)
		table.AddColumn("NAME", 0.75)
		table.AddColumn("COMMITS", 0.1)
		table.AddColumn("LAST COMMIT", 0.15)

		for _, file := range files {
			table.MustAddRow([]string{
				view.RenderFilename(file.Name),
				strconv.Itoa(file.Count),
				humanize.Time(file.LastCommit)})
		}

		table.Render(v)
	})
}
<|endoftext|>


<commit_before>package openshiftkubeapiserver

import (
	"crypto/x509"
	"fmt"
	"time"

	"k8s.io/apiserver/pkg/authentication/authenticator"
	"k8s.io/apiserver/pkg/authentication/group"
	"k8s.io/apiserver/pkg/authentication/request/anonymous"
	"k8s.io/apiserver/pkg/authentication/request/bearertoken"
	"k8s.io/apiserver/pkg/authentication/request/headerrequest"
	"k8s.io/apiserver/pkg/authentication/request/union"
	"k8s.io/apiserver/pkg/authentication/request/websocket"
	x509request "k8s.io/apiserver/pkg/authentication/request/x509"
	tokencache "k8s.io/apiserver/pkg/authentication/token/cache"
	tokenunion "k8s.io/apiserver/pkg/authentication/token/union"
	genericapiserver "k8s.io/apiserver/pkg/server"
	webhooktoken "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
	kclientsetexternal "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/util/cert"
	sacontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
	"k8s.io/kubernetes/pkg/serviceaccount"

	configv1 "github.com/openshift/api/config/v1"
	kubecontrolplanev1 "github.com/openshift/api/kubecontrolplane/v1"
	osinv1 "github.com/openshift/api/osin/v1"
	oauthclient "github.com/openshift/client-go/oauth/clientset/versioned/typed/oauth/v1"
	oauthclientlister "github.com/openshift/client-go/oauth/listers/oauth/v1"
	userclient "github.com/openshift/client-go/user/clientset/versioned"
	usertypedclient "github.com/openshift/client-go/user/clientset/versioned/typed/user/v1"
	userinformer "github.com/openshift/client-go/user/informers/externalversions/user/v1"
	"github.com/openshift/origin/pkg/apiserver/authentication/oauth"
	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
	cmdutil "github.com/openshift/origin/pkg/cmd/util"
	oauthvalidation "github.com/openshift/origin/pkg/oauth/apis/oauth/validation"
	"github.com/openshift/origin/pkg/oauthserver/authenticator/password/bootstrap"
	"github.com/openshift/origin/pkg/oauthserver/authenticator/request/paramtoken"
	usercache "github.com/openshift/origin/pkg/user/cache"
)

// TODO we can re-trim these args to the the kubeapiserver config again if we feel like it, but for now we need it to be
// TODO obviously safe for 3.11
func NewAuthenticator(
	servingInfo configv1.ServingInfo,
	serviceAccountPublicKeyFiles []string, oauthConfig *osinv1.OAuthConfig, authConfig kubecontrolplanev1.MasterAuthConfig,
	privilegedLoopbackConfig *rest.Config,
	oauthClientLister oauthclientlister.OAuthClientLister,
	groupInformer userinformer.GroupInformer,
) (authenticator.Request, map[string]genericapiserver.PostStartHookFunc, error) {
	kubeExternalClient, err := kclientsetexternal.NewForConfig(privilegedLoopbackConfig)
	if err != nil {
		return nil, nil, err
	}
	oauthClient, err := oauthclient.NewForConfig(privilegedLoopbackConfig)
	if err != nil {
		return nil, nil, err
	}
	userClient, err := userclient.NewForConfig(privilegedLoopbackConfig)
	if err != nil {
		return nil, nil, err
	}

	// this is safe because the server does a quorum read and we're hitting a "magic" authorizer to get permissions based on system:masters
	// once the cache is added, we won't be paying a double hop cost to etcd on each request, so the simplification will help.
	serviceAccountTokenGetter := sacontroller.NewGetterFromClient(kubeExternalClient)
	apiClientCAs, err := cmdutil.CertPoolFromFile(servingInfo.ClientCA)
	if err != nil {
		return nil, nil, err
	}

	return newAuthenticator(
		serviceAccountPublicKeyFiles,
		oauthConfig,
		authConfig,
		oauthClient.OAuthAccessTokens(),
		oauthClientLister,
		serviceAccountTokenGetter,
		userClient.User().Users(),
		apiClientCAs,
		usercache.NewGroupCache(groupInformer),
		bootstrap.NewBootstrapUserDataGetter(kubeExternalClient.CoreV1(), kubeExternalClient.CoreV1()),
	)
}

func newAuthenticator(
	serviceAccountPublicKeyFiles []string,
	oauthConfig *osinv1.OAuthConfig,
	authConfig kubecontrolplanev1.MasterAuthConfig,
	accessTokenGetter oauthclient.OAuthAccessTokenInterface,
	oauthClientLister oauthclientlister.OAuthClientLister,
	tokenGetter serviceaccount.ServiceAccountTokenGetter,
	userGetter usertypedclient.UserInterface,
	apiClientCAs *x509.CertPool,
	groupMapper oauth.UserToGroupMapper,
	bootstrapUserDataGetter bootstrap.BootstrapUserDataGetter,
) (authenticator.Request, map[string]genericapiserver.PostStartHookFunc, error) {
	postStartHooks := map[string]genericapiserver.PostStartHookFunc{}
	authenticators := []authenticator.Request{}
	tokenAuthenticators := []authenticator.Token{}

	// ServiceAccount token
	if len(serviceAccountPublicKeyFiles) > 0 {
		publicKeys := []interface{}{}
		for _, keyFile := range serviceAccountPublicKeyFiles {
			readPublicKeys, err := cert.PublicKeysFromFile(keyFile)
			if err != nil {
				return nil, nil, fmt.Errorf("Error reading service account key file %s: %v", keyFile, err)
			}
			publicKeys = append(publicKeys, readPublicKeys...)
		}

		serviceAccountTokenAuthenticator := serviceaccount.JWTTokenAuthenticator(
			serviceaccount.LegacyIssuer,
			publicKeys,
			serviceaccount.NewLegacyValidator(true, tokenGetter),
		)
		tokenAuthenticators = append(tokenAuthenticators, serviceAccountTokenAuthenticator)
	}

	// OAuth token
	// this looks weird because it no longer belongs here (needs to be a remote token auth backed by osin)
	if oauthConfig != nil || len(authConfig.OAuthMetadataFile) > 0 {
		// if we have no OAuthConfig but have an OAuthMetadataFile, we still need to honor OAuth tokens
		// to keep the checks below simple, we build an empty OAuthConfig
		// since we do not know anything about the remote OAuth server's config,
		// we assume it supports the bootstrap oauth user by setting a non-nil session config
		if oauthConfig == nil {
			oauthConfig = &osinv1.OAuthConfig{
				SessionConfig: &osinv1.SessionConfig{},
			}
		}

		validators := []oauth.OAuthTokenValidator{oauth.NewExpirationValidator(), oauth.NewUIDValidator()}
		if inactivityTimeout := oauthConfig.TokenConfig.AccessTokenInactivityTimeoutSeconds; inactivityTimeout != nil {
			timeoutValidator := oauth.NewTimeoutValidator(accessTokenGetter, oauthClientLister, *inactivityTimeout, oauthvalidation.MinimumInactivityTimeoutSeconds)
			validators = append(validators, timeoutValidator)
			postStartHooks["openshift.io-TokenTimeoutUpdater"] = func(context genericapiserver.PostStartHookContext) error {
				go timeoutValidator.Run(context.StopCh)
				return nil
			}
		}
		oauthTokenAuthenticator := oauth.NewTokenAuthenticator(accessTokenGetter, userGetter, groupMapper, validators...)
		tokenAuthenticators = append(tokenAuthenticators,
			// if you have an OAuth bearer token, you're a human (usually)
			group.NewTokenGroupAdder(oauthTokenAuthenticator, []string{bootstrappolicy.AuthenticatedOAuthGroup}))

		if oauthConfig.SessionConfig != nil {
			tokenAuthenticators = append(tokenAuthenticators,
				// bootstrap oauth user that can do anything, backed by a secret
				oauth.NewBootstrapAuthenticator(accessTokenGetter, bootstrapUserDataGetter, validators...))
		}
	}

	for _, wta := range authConfig.WebhookTokenAuthenticators {
		ttl, err := time.ParseDuration(wta.CacheTTL)
		if err != nil {
			return nil, nil, fmt.Errorf("Error parsing CacheTTL=%q: %v", wta.CacheTTL, err)
		}
		webhookTokenAuthenticator, err := webhooktoken.New(wta.ConfigFile, ttl)
		if err != nil {
			return nil, nil, fmt.Errorf("Failed to create webhook token authenticator for ConfigFile=%q: %v", wta.ConfigFile, err)
		}
		tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuthenticator)
	}

	if len(tokenAuthenticators) > 0 {
		// Combine all token authenticators
		tokenAuth := tokenunion.New(tokenAuthenticators...)

		// wrap with short cache on success.
		// this means a revoked service account token or access token will be valid for up to 10 seconds.
		// it also means group membership changes on users may take up to 10 seconds to become effective.
		tokenAuth = tokencache.New(tokenAuth, 10*time.Second, 0)

		authenticators = append(authenticators,
			bearertoken.New(tokenAuth),
			websocket.NewProtocolAuthenticator(tokenAuth),
			paramtoken.New("access_token", tokenAuth, true),
		)
	}

	// build cert authenticator
	// TODO: add "system:" prefix in authenticator, limit cert to username
	// TODO: add "system:" prefix to groups in authenticator, limit cert to group name
	opts := x509request.DefaultVerifyOptions()
	opts.Roots = apiClientCAs
	certauth := x509request.New(opts, x509request.CommonNameUserConversion)
	authenticators = append(authenticators, certauth)

	resultingAuthenticator := union.NewFailOnError(authenticators...)

	topLevelAuthenticators := []authenticator.Request{}
	// if we have a front proxy providing authentication configuration, wire it up and it should come first
	if authConfig.RequestHeader != nil {
		requestHeaderAuthenticator, err := headerrequest.NewSecure(
			authConfig.RequestHeader.ClientCA,
			authConfig.RequestHeader.ClientCommonNames,
			authConfig.RequestHeader.UsernameHeaders,
			authConfig.RequestHeader.GroupHeaders,
			authConfig.RequestHeader.ExtraHeaderPrefixes,
		)
		if err != nil {
			return nil, nil, fmt.Errorf("Error building front proxy auth config: %v", err)
		}
		topLevelAuthenticators = append(topLevelAuthenticators, union.New(requestHeaderAuthenticator, resultingAuthenticator))

	} else {
		topLevelAuthenticators = append(topLevelAuthenticators, resultingAuthenticator)

	}
	topLevelAuthenticators = append(topLevelAuthenticators, anonymous.NewAuthenticator())

	return group.NewAuthenticatedGroupAdder(union.NewFailOnError(topLevelAuthenticators...)), postStartHooks, nil
}
<commit_msg>support dynamic cert reloading<commit_after>package openshiftkubeapiserver

import (
	"fmt"
	"time"

	"k8s.io/apiserver/pkg/server/certs"

	"k8s.io/apiserver/pkg/authentication/authenticator"
	"k8s.io/apiserver/pkg/authentication/group"
	"k8s.io/apiserver/pkg/authentication/request/anonymous"
	"k8s.io/apiserver/pkg/authentication/request/bearertoken"
	"k8s.io/apiserver/pkg/authentication/request/headerrequest"
	"k8s.io/apiserver/pkg/authentication/request/union"
	"k8s.io/apiserver/pkg/authentication/request/websocket"
	x509request "k8s.io/apiserver/pkg/authentication/request/x509"
	tokencache "k8s.io/apiserver/pkg/authentication/token/cache"
	tokenunion "k8s.io/apiserver/pkg/authentication/token/union"
	genericapiserver "k8s.io/apiserver/pkg/server"
	webhooktoken "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
	kclientsetexternal "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/util/cert"
	sacontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
	"k8s.io/kubernetes/pkg/serviceaccount"

	configv1 "github.com/openshift/api/config/v1"
	kubecontrolplanev1 "github.com/openshift/api/kubecontrolplane/v1"
	osinv1 "github.com/openshift/api/osin/v1"
	oauthclient "github.com/openshift/client-go/oauth/clientset/versioned/typed/oauth/v1"
	oauthclientlister "github.com/openshift/client-go/oauth/listers/oauth/v1"
	userclient "github.com/openshift/client-go/user/clientset/versioned"
	usertypedclient "github.com/openshift/client-go/user/clientset/versioned/typed/user/v1"
	userinformer "github.com/openshift/client-go/user/informers/externalversions/user/v1"
	"github.com/openshift/origin/pkg/apiserver/authentication/oauth"
	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
	oauthvalidation "github.com/openshift/origin/pkg/oauth/apis/oauth/validation"
	"github.com/openshift/origin/pkg/oauthserver/authenticator/password/bootstrap"
	"github.com/openshift/origin/pkg/oauthserver/authenticator/request/paramtoken"
	usercache "github.com/openshift/origin/pkg/user/cache"
)

// TODO we can re-trim these args to the the kubeapiserver config again if we feel like it, but for now we need it to be
// TODO obviously safe for 3.11
func NewAuthenticator(
	servingInfo configv1.ServingInfo,
	serviceAccountPublicKeyFiles []string, oauthConfig *osinv1.OAuthConfig, authConfig kubecontrolplanev1.MasterAuthConfig,
	privilegedLoopbackConfig *rest.Config,
	oauthClientLister oauthclientlister.OAuthClientLister,
	groupInformer userinformer.GroupInformer,
) (authenticator.Request, map[string]genericapiserver.PostStartHookFunc, error) {
	kubeExternalClient, err := kclientsetexternal.NewForConfig(privilegedLoopbackConfig)
	if err != nil {
		return nil, nil, err
	}
	oauthClient, err := oauthclient.NewForConfig(privilegedLoopbackConfig)
	if err != nil {
		return nil, nil, err
	}
	userClient, err := userclient.NewForConfig(privilegedLoopbackConfig)
	if err != nil {
		return nil, nil, err
	}

	// this is safe because the server does a quorum read and we're hitting a "magic" authorizer to get permissions based on system:masters
	// once the cache is added, we won't be paying a double hop cost to etcd on each request, so the simplification will help.
	serviceAccountTokenGetter := sacontroller.NewGetterFromClient(kubeExternalClient)

	return newAuthenticator(
		serviceAccountPublicKeyFiles,
		oauthConfig,
		authConfig,
		oauthClient.OAuthAccessTokens(),
		oauthClientLister,
		serviceAccountTokenGetter,
		userClient.User().Users(),
		servingInfo.ClientCA,
		usercache.NewGroupCache(groupInformer),
		bootstrap.NewBootstrapUserDataGetter(kubeExternalClient.CoreV1(), kubeExternalClient.CoreV1()),
	)
}

func newAuthenticator(
	serviceAccountPublicKeyFiles []string,
	oauthConfig *osinv1.OAuthConfig,
	authConfig kubecontrolplanev1.MasterAuthConfig,
	accessTokenGetter oauthclient.OAuthAccessTokenInterface,
	oauthClientLister oauthclientlister.OAuthClientLister,
	tokenGetter serviceaccount.ServiceAccountTokenGetter,
	userGetter usertypedclient.UserInterface,
	apiClientCABundle string,
	groupMapper oauth.UserToGroupMapper,
	bootstrapUserDataGetter bootstrap.BootstrapUserDataGetter,
) (authenticator.Request, map[string]genericapiserver.PostStartHookFunc, error) {
	postStartHooks := map[string]genericapiserver.PostStartHookFunc{}
	authenticators := []authenticator.Request{}
	tokenAuthenticators := []authenticator.Token{}

	// ServiceAccount token
	if len(serviceAccountPublicKeyFiles) > 0 {
		publicKeys := []interface{}{}
		for _, keyFile := range serviceAccountPublicKeyFiles {
			readPublicKeys, err := cert.PublicKeysFromFile(keyFile)
			if err != nil {
				return nil, nil, fmt.Errorf("Error reading service account key file %s: %v", keyFile, err)
			}
			publicKeys = append(publicKeys, readPublicKeys...)
		}

		serviceAccountTokenAuthenticator := serviceaccount.JWTTokenAuthenticator(
			serviceaccount.LegacyIssuer,
			publicKeys,
			serviceaccount.NewLegacyValidator(true, tokenGetter),
		)
		tokenAuthenticators = append(tokenAuthenticators, serviceAccountTokenAuthenticator)
	}

	// OAuth token
	// this looks weird because it no longer belongs here (needs to be a remote token auth backed by osin)
	if oauthConfig != nil || len(authConfig.OAuthMetadataFile) > 0 {
		// if we have no OAuthConfig but have an OAuthMetadataFile, we still need to honor OAuth tokens
		// to keep the checks below simple, we build an empty OAuthConfig
		// since we do not know anything about the remote OAuth server's config,
		// we assume it supports the bootstrap oauth user by setting a non-nil session config
		if oauthConfig == nil {
			oauthConfig = &osinv1.OAuthConfig{
				SessionConfig: &osinv1.SessionConfig{},
			}
		}

		validators := []oauth.OAuthTokenValidator{oauth.NewExpirationValidator(), oauth.NewUIDValidator()}
		if inactivityTimeout := oauthConfig.TokenConfig.AccessTokenInactivityTimeoutSeconds; inactivityTimeout != nil {
			timeoutValidator := oauth.NewTimeoutValidator(accessTokenGetter, oauthClientLister, *inactivityTimeout, oauthvalidation.MinimumInactivityTimeoutSeconds)
			validators = append(validators, timeoutValidator)
			postStartHooks["openshift.io-TokenTimeoutUpdater"] = func(context genericapiserver.PostStartHookContext) error {
				go timeoutValidator.Run(context.StopCh)
				return nil
			}
		}
		oauthTokenAuthenticator := oauth.NewTokenAuthenticator(accessTokenGetter, userGetter, groupMapper, validators...)
		tokenAuthenticators = append(tokenAuthenticators,
			// if you have an OAuth bearer token, you're a human (usually)
			group.NewTokenGroupAdder(oauthTokenAuthenticator, []string{bootstrappolicy.AuthenticatedOAuthGroup}))

		if oauthConfig.SessionConfig != nil {
			tokenAuthenticators = append(tokenAuthenticators,
				// bootstrap oauth user that can do anything, backed by a secret
				oauth.NewBootstrapAuthenticator(accessTokenGetter, bootstrapUserDataGetter, validators...))
		}
	}

	for _, wta := range authConfig.WebhookTokenAuthenticators {
		ttl, err := time.ParseDuration(wta.CacheTTL)
		if err != nil {
			return nil, nil, fmt.Errorf("Error parsing CacheTTL=%q: %v", wta.CacheTTL, err)
		}
		webhookTokenAuthenticator, err := webhooktoken.New(wta.ConfigFile, ttl)
		if err != nil {
			return nil, nil, fmt.Errorf("Failed to create webhook token authenticator for ConfigFile=%q: %v", wta.ConfigFile, err)
		}
		tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuthenticator)
	}

	if len(tokenAuthenticators) > 0 {
		// Combine all token authenticators
		tokenAuth := tokenunion.New(tokenAuthenticators...)

		// wrap with short cache on success.
		// this means a revoked service account token or access token will be valid for up to 10 seconds.
		// it also means group membership changes on users may take up to 10 seconds to become effective.
		tokenAuth = tokencache.New(tokenAuth, 10*time.Second, 0)

		authenticators = append(authenticators,
			bearertoken.New(tokenAuth),
			websocket.NewProtocolAuthenticator(tokenAuth),
			paramtoken.New("access_token", tokenAuth, true),
		)
	}

	// build cert authenticator
	// TODO: add "system:" prefix in authenticator, limit cert to username
	// TODO: add "system:" prefix to groups in authenticator, limit cert to group name
	dynamicCA := certs.NewDynamicCA(apiClientCABundle)
	if err := dynamicCA.CheckCerts(); err != nil {
		return nil, nil, err
	}
	certauth := x509request.NewDynamic(dynamicCA.GetVerifier, x509request.CommonNameUserConversion)
	postStartHooks["openshift.io-clientCA-reload"] = func(context genericapiserver.PostStartHookContext) error {
		go dynamicCA.Run(context.StopCh)
		return nil
	}
	authenticators = append(authenticators, certauth)

	resultingAuthenticator := union.NewFailOnError(authenticators...)

	topLevelAuthenticators := []authenticator.Request{}
	// if we have a front proxy providing authentication configuration, wire it up and it should come first
	if authConfig.RequestHeader != nil {
		requestHeaderAuthenticator, dynamicReloadFn, err := headerrequest.NewSecure(
			authConfig.RequestHeader.ClientCA,
			authConfig.RequestHeader.ClientCommonNames,
			authConfig.RequestHeader.UsernameHeaders,
			authConfig.RequestHeader.GroupHeaders,
			authConfig.RequestHeader.ExtraHeaderPrefixes,
		)
		if err != nil {
			return nil, nil, fmt.Errorf("Error building front proxy auth config: %v", err)
		}
		postStartHooks["openshift.io-requestheader-reload"] = func(context genericapiserver.PostStartHookContext) error {
			go dynamicReloadFn(context.StopCh)
			return nil
		}
		topLevelAuthenticators = append(topLevelAuthenticators, union.New(requestHeaderAuthenticator, resultingAuthenticator))

	} else {
		topLevelAuthenticators = append(topLevelAuthenticators, resultingAuthenticator)

	}
	topLevelAuthenticators = append(topLevelAuthenticators, anonymous.NewAuthenticator())

	return group.NewAuthenticatedGroupAdder(union.NewFailOnError(topLevelAuthenticators...)), postStartHooks, nil
}
<|endoftext|>


<commit_before>/*
    MP3Cat is a fast command line utility for concatenating MP3 files
    without re-encoding. It supports both constant bit rate (CBR) and
    variable bit rate (VBR) files.
*/
package main

import (
    "fmt"
    "io"
    "os"
    "path"
    "path/filepath"
    "golang.org/x/crypto/ssh/terminal"
    "github.com/dmulholland/mp3lib"
    "github.com/dmulholland/clio/go/clio"
)

const version = "2.5.0.dev"

var helptext = fmt.Sprintf(`
Usage: %s [FLAGS] [OPTIONS] [ARGUMENTS]

  This tool concatenates MP3 files without re-encoding. It supports both
  constant bit rate (CBR) and variable bit rate (VBR) MP3 files. It also
  strips ID3 tags and garbage data from the output.

  Files to be merged can be specified as a list of filenames:

    $ mp3cat one.mp3 two.mp3 three.mp3

  Alternatively, an entire directory of files can be merged:

    $ mp3cat --dir /path/to/directory/

Arguments:
  [files]                 List of input files to merge.

Options:
  -d, --dir <path>        Directory of files to merge.
  -i, --interlace <path>  Interlace a spacer file between each input file.
  -o, --out <path>        Output filename. Defaults to 'output.mp3'.

Flags:
  -f, --force             Overwrite an existing output file.
      --help              Display this help text and exit.
  -t, --tag               Copy the ID3 tag from the first input file.
  -v, --verbose           Report progress.
      --version           Display the application's version number and exit.
`, filepath.Base(os.Args[0]))

func main() {

    // Parse the command line arguments.
    parser := clio.NewParser(helptext, version)
    parser.AddFlag("force f")
    parser.AddFlag("verbose v")
    parser.AddFlag("debug")
    parser.AddFlag("tag t")
    parser.AddStr("out o", "output.mp3")
    parser.AddStr("dir d", "")
    parser.AddStr("interlace i", "")
    parser.Parse()

    // Make sure we have a list of files to merge.
    var files []string
    if parser.Found("dir") {
        globs, err := filepath.Glob(path.Join(parser.GetStr("dir"), "*.mp3"))
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
        if globs == nil || len(globs) == 0 {
            fmt.Fprintln(os.Stderr, "Error: no files found.")
            os.Exit(1)
        }
        files = globs
    } else if parser.HasArgs() {
        files = parser.GetArgs()
    } else {
        fmt.Fprintln(os.Stderr, "Error: you must specify files to merge.")
        os.Exit(1)
    }

    // Are we interlacing a spacer file?
    if parser.Found("interlace") {
        files = interlace(files, parser.GetStr("interlace"))
    }

    // Make sure all the files in the list actually exist.
    validateFiles(files)

    // Set debug mode if the user supplied a --debug flag.
    if parser.GetFlag("debug") {
        mp3lib.DebugMode = true
    }

    // Merge the input files.
    merge(
        parser.GetStr("out"),
        files,
        parser.GetFlag("force"),
        parser.GetFlag("verbose"),
        parser.GetFlag("tag"))
}

// Check that all the files in the list exist.
func validateFiles(files []string) {
    for _, file := range files {
        if _, err := os.Stat(file); err != nil {
            fmt.Fprintf(
                os.Stderr,
                "Error: the file '%v' does not exist.\n", file)
            os.Exit(1)
        }
    }
}

// Interlace a spacer file between each file in the list.
func interlace(files []string, spacer string) []string {
    var interlaced []string
    for _, file := range files {
        interlaced = append(interlaced, file)
        interlaced = append(interlaced, spacer)
    }
    return interlaced[:len(interlaced) - 1]
}

// Create a new file at the specified output path containing the merged
// contents of the list of input files.
func merge(outpath string, inpaths []string, force, verbose, tag bool) {

    var totalFrames uint32
    var totalBytes uint32
    var totalFiles int
    var firstBitRate int
    var isVBR bool

    // Only overwrite an existing file if the --force flag has been used.
    if _, err := os.Stat(outpath); err == nil {
        if !force {
            fmt.Fprintf(
                os.Stderr,
                "Error: the file '%v' already exists.\n", outpath)
            os.Exit(1)
        }
    }

    // If the list of input files includes the output file we'll end up in an
    // infinite loop.
    for _, filepath := range inpaths {
        if filepath == outpath {
            fmt.Fprintln(
                os.Stderr,
                "Error: the list of input files includes the output file.")
            os.Exit(1)
        }
    }

    // Create the output file.
    outfile, err := os.Create(outpath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    if verbose {
        line()
    }

    // Loop over the input files and append their MP3 frames to the output
    // file.
    for _, inpath := range inpaths {

        if verbose {
            fmt.Println("+", inpath)
        }

        infile, err := os.Open(inpath)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        isFirstFrame := true

        for {

            // Read the next frame from the input file.
            frame := mp3lib.NextFrame(infile)
            if frame == nil {
                break
            }

            // Skip the first frame if it's a VBR header.
            if isFirstFrame {
                isFirstFrame = false
                if mp3lib.IsXingHeader(frame) || mp3lib.IsVbriHeader(frame) {
                    continue
                }
            }

            // If we detect more than one bitrate we'll need to add a VBR
            // header to the output file.
            if firstBitRate == 0 {
                firstBitRate = frame.BitRate
            } else if frame.BitRate != firstBitRate {
                isVBR = true
            }

            // Write the frame to the output file.
            _, err := outfile.Write(frame.RawBytes)
            if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
            }

            totalFrames += 1
            totalBytes += uint32(len(frame.RawBytes))
        }

        infile.Close()
        totalFiles += 1
    }

    outfile.Close()
    if verbose {
        line()
    }

    // If we detected multiple bitrates, prepend a VBR header to the file.
    if isVBR {
        if verbose {
            fmt.Println("• Multiple bitrates detected. Adding VBR header.")
        }
        addXingHeader(outpath, totalFrames, totalBytes)
    }

    // Copy the ID3v2 tag from the first input file if requested. Order of
    // operations is important here. The ID3 tag must be the first item in
    // the file - in particular, it must come *before* any VBR header.
    if tag {
        if verbose {
            fmt.Println("• Adding ID3 tag.")
        }
        addID3v2Tag(outpath, inpaths[0])
    }

    // Print a count of the number of files merged.
    if verbose {
        fmt.Printf("• %v files merged.\n", totalFiles)
        line()
    }
}

// Prepend an Xing VBR header to the specified MP3 file.
func addXingHeader(filepath string, totalFrames, totalBytes uint32) {

    outputFile, err := os.Create(filepath + ".mp3cat.tmp")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    inputFile, err := os.Open(filepath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    templateFrame := mp3lib.NextFrame(inputFile)
    inputFile.Seek(0, 0)

    xingHeader := mp3lib.NewXingHeader(templateFrame, totalFrames, totalBytes)

    _, err = outputFile.Write(xingHeader.RawBytes)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    _, err = io.Copy(outputFile, inputFile)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    outputFile.Close()
    inputFile.Close()

    err = os.Remove(filepath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    err = os.Rename(filepath + ".mp3cat.tmp", filepath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

// Prepend an ID3v2 tag to the MP3 file at mp3Path, copying from tagPath.
func addID3v2Tag(mp3Path, tagPath string) {

    tagFile, err := os.Open(tagPath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    id3tag := mp3lib.NextID3v2Tag(tagFile)
    tagFile.Close()

    if id3tag != nil {
        outputFile, err := os.Create(mp3Path + ".mp3cat.tmp")
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        inputFile, err := os.Open(mp3Path)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        _, err = outputFile.Write(id3tag.RawBytes)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        _, err = io.Copy(outputFile, inputFile)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        outputFile.Close()
        inputFile.Close()

        err = os.Remove(mp3Path)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        err = os.Rename(mp3Path + ".mp3cat.tmp", mp3Path)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
    }
}

// Print a line to stdout if we're running in a terminal.
func line() {
    if terminal.IsTerminal(int(os.Stdout.Fd())) {
        width, _, err := terminal.GetSize(int(os.Stdout.Fd()))
        if err == nil {
            for i := 0; i < width; i++ {
                fmt.Print("─")
            }
            fmt.Println()
        }
    }
}
<commit_msg>Bump version to 2.5.0<commit_after>/*
    MP3Cat is a fast command line utility for concatenating MP3 files
    without re-encoding. It supports both constant bit rate (CBR) and
    variable bit rate (VBR) files.
*/
package main

import (
    "fmt"
    "io"
    "os"
    "path"
    "path/filepath"
    "golang.org/x/crypto/ssh/terminal"
    "github.com/dmulholland/mp3lib"
    "github.com/dmulholland/clio/go/clio"
)

const version = "2.5.0"

var helptext = fmt.Sprintf(`
Usage: %s [FLAGS] [OPTIONS] [ARGUMENTS]

  This tool concatenates MP3 files without re-encoding. It supports both
  constant bit rate (CBR) and variable bit rate (VBR) MP3 files. It also
  strips ID3 tags and garbage data from the output.

  Files to be merged can be specified as a list of filenames:

    $ mp3cat one.mp3 two.mp3 three.mp3

  Alternatively, an entire directory of files can be merged:

    $ mp3cat --dir /path/to/directory/

Arguments:
  [files]                 List of input files to merge.

Options:
  -d, --dir <path>        Directory of files to merge.
  -i, --interlace <path>  Interlace a spacer file between each input file.
  -o, --out <path>        Output filename. Defaults to 'output.mp3'.

Flags:
  -f, --force             Overwrite an existing output file.
      --help              Display this help text and exit.
  -t, --tag               Copy the ID3 tag from the first input file.
  -v, --verbose           Report progress.
      --version           Display the application's version number and exit.
`, filepath.Base(os.Args[0]))

func main() {

    // Parse the command line arguments.
    parser := clio.NewParser(helptext, version)
    parser.AddFlag("force f")
    parser.AddFlag("verbose v")
    parser.AddFlag("debug")
    parser.AddFlag("tag t")
    parser.AddStr("out o", "output.mp3")
    parser.AddStr("dir d", "")
    parser.AddStr("interlace i", "")
    parser.Parse()

    // Make sure we have a list of files to merge.
    var files []string
    if parser.Found("dir") {
        globs, err := filepath.Glob(path.Join(parser.GetStr("dir"), "*.mp3"))
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
        if globs == nil || len(globs) == 0 {
            fmt.Fprintln(os.Stderr, "Error: no files found.")
            os.Exit(1)
        }
        files = globs
    } else if parser.HasArgs() {
        files = parser.GetArgs()
    } else {
        fmt.Fprintln(os.Stderr, "Error: you must specify files to merge.")
        os.Exit(1)
    }

    // Are we interlacing a spacer file?
    if parser.Found("interlace") {
        files = interlace(files, parser.GetStr("interlace"))
    }

    // Make sure all the files in the list actually exist.
    validateFiles(files)

    // Set debug mode if the user supplied a --debug flag.
    if parser.GetFlag("debug") {
        mp3lib.DebugMode = true
    }

    // Merge the input files.
    merge(
        parser.GetStr("out"),
        files,
        parser.GetFlag("force"),
        parser.GetFlag("verbose"),
        parser.GetFlag("tag"))
}

// Check that all the files in the list exist.
func validateFiles(files []string) {
    for _, file := range files {
        if _, err := os.Stat(file); err != nil {
            fmt.Fprintf(
                os.Stderr,
                "Error: the file '%v' does not exist.\n", file)
            os.Exit(1)
        }
    }
}

// Interlace a spacer file between each file in the list.
func interlace(files []string, spacer string) []string {
    var interlaced []string
    for _, file := range files {
        interlaced = append(interlaced, file)
        interlaced = append(interlaced, spacer)
    }
    return interlaced[:len(interlaced) - 1]
}

// Create a new file at the specified output path containing the merged
// contents of the list of input files.
func merge(outpath string, inpaths []string, force, verbose, tag bool) {

    var totalFrames uint32
    var totalBytes uint32
    var totalFiles int
    var firstBitRate int
    var isVBR bool

    // Only overwrite an existing file if the --force flag has been used.
    if _, err := os.Stat(outpath); err == nil {
        if !force {
            fmt.Fprintf(
                os.Stderr,
                "Error: the file '%v' already exists.\n", outpath)
            os.Exit(1)
        }
    }

    // If the list of input files includes the output file we'll end up in an
    // infinite loop.
    for _, filepath := range inpaths {
        if filepath == outpath {
            fmt.Fprintln(
                os.Stderr,
                "Error: the list of input files includes the output file.")
            os.Exit(1)
        }
    }

    // Create the output file.
    outfile, err := os.Create(outpath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    if verbose {
        line()
    }

    // Loop over the input files and append their MP3 frames to the output
    // file.
    for _, inpath := range inpaths {

        if verbose {
            fmt.Println("+", inpath)
        }

        infile, err := os.Open(inpath)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        isFirstFrame := true

        for {

            // Read the next frame from the input file.
            frame := mp3lib.NextFrame(infile)
            if frame == nil {
                break
            }

            // Skip the first frame if it's a VBR header.
            if isFirstFrame {
                isFirstFrame = false
                if mp3lib.IsXingHeader(frame) || mp3lib.IsVbriHeader(frame) {
                    continue
                }
            }

            // If we detect more than one bitrate we'll need to add a VBR
            // header to the output file.
            if firstBitRate == 0 {
                firstBitRate = frame.BitRate
            } else if frame.BitRate != firstBitRate {
                isVBR = true
            }

            // Write the frame to the output file.
            _, err := outfile.Write(frame.RawBytes)
            if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
            }

            totalFrames += 1
            totalBytes += uint32(len(frame.RawBytes))
        }

        infile.Close()
        totalFiles += 1
    }

    outfile.Close()
    if verbose {
        line()
    }

    // If we detected multiple bitrates, prepend a VBR header to the file.
    if isVBR {
        if verbose {
            fmt.Println("• Multiple bitrates detected. Adding VBR header.")
        }
        addXingHeader(outpath, totalFrames, totalBytes)
    }

    // Copy the ID3v2 tag from the first input file if requested. Order of
    // operations is important here. The ID3 tag must be the first item in
    // the file - in particular, it must come *before* any VBR header.
    if tag {
        if verbose {
            fmt.Println("• Adding ID3 tag.")
        }
        addID3v2Tag(outpath, inpaths[0])
    }

    // Print a count of the number of files merged.
    if verbose {
        fmt.Printf("• %v files merged.\n", totalFiles)
        line()
    }
}

// Prepend an Xing VBR header to the specified MP3 file.
func addXingHeader(filepath string, totalFrames, totalBytes uint32) {

    outputFile, err := os.Create(filepath + ".mp3cat.tmp")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    inputFile, err := os.Open(filepath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    templateFrame := mp3lib.NextFrame(inputFile)
    inputFile.Seek(0, 0)

    xingHeader := mp3lib.NewXingHeader(templateFrame, totalFrames, totalBytes)

    _, err = outputFile.Write(xingHeader.RawBytes)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    _, err = io.Copy(outputFile, inputFile)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    outputFile.Close()
    inputFile.Close()

    err = os.Remove(filepath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    err = os.Rename(filepath + ".mp3cat.tmp", filepath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

// Prepend an ID3v2 tag to the MP3 file at mp3Path, copying from tagPath.
func addID3v2Tag(mp3Path, tagPath string) {

    tagFile, err := os.Open(tagPath)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    id3tag := mp3lib.NextID3v2Tag(tagFile)
    tagFile.Close()

    if id3tag != nil {
        outputFile, err := os.Create(mp3Path + ".mp3cat.tmp")
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        inputFile, err := os.Open(mp3Path)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        _, err = outputFile.Write(id3tag.RawBytes)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        _, err = io.Copy(outputFile, inputFile)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        outputFile.Close()
        inputFile.Close()

        err = os.Remove(mp3Path)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        err = os.Rename(mp3Path + ".mp3cat.tmp", mp3Path)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
    }
}

// Print a line to stdout if we're running in a terminal.
func line() {
    if terminal.IsTerminal(int(os.Stdout.Fd())) {
        width, _, err := terminal.GetSize(int(os.Stdout.Fd()))
        if err == nil {
            for i := 0; i < width; i++ {
                fmt.Print("─")
            }
            fmt.Println()
        }
    }
}
<|endoftext|>


<commit_before>//+build e2e

/*
Copyright 2020 The Knative Authors

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

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

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

package e2e

import (
	"testing"
	"time"

	camelclientset "github.com/apache/camel-k/pkg/client/clientset/versioned"

	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
	"knative.dev/eventing-contrib/camel/source/pkg/apis/sources/v1alpha1"
	camelsourceclient "knative.dev/eventing-contrib/camel/source/pkg/client/clientset/versioned"
	"knative.dev/eventing/test/lib"
	"knative.dev/eventing/test/lib/resources"
	knativeduck "knative.dev/pkg/apis/duck/v1beta1"
)

func TestCamelSource(t *testing.T) {

	const (
		camelSourceName = "e2e-camelsource"
		loggerPodName   = "e2e-camelsource-logger-pod"
		body            = "Hello, world!"
	)

	client := lib.Setup(t, true)
	defer lib.TearDown(client)

	t.Logf("Creating logger Pod")
	pod := resources.EventLoggerPod(loggerPodName)
	client.CreatePodOrFail(pod, lib.WithService(loggerPodName))

	camelClient := getCamelKClient(client)

	t.Logf("Creating Camel K IntegrationPlatform")
	createCamelPlatformOrFail(client, camelClient, camelSourceName)

	t.Logf("Creating Camel K Kit (to skip build)")
	createCamelKitOrFail(client, camelClient, camelSourceName)

	t.Logf("Creating CamelSource")
	createCamelSourceOrFail(client, &v1alpha1.CamelSource{
		ObjectMeta: meta.ObjectMeta{
			Name: camelSourceName,
		},
		Spec: v1alpha1.CamelSourceSpec{
			Source: v1alpha1.CamelSourceOriginSpec{
				Flow: &v1alpha1.Flow{
					"from": &map[string]interface{}{
						"uri": "timer:tick?period=1s",
						"steps": []interface{}{
							&map[string]interface{}{
								"set-body": &map[string]interface{}{
									"constant": body,
								},
							},
						},
					},
				},
			},
			Sink: &knativeduck.Destination{
				Ref: resources.ServiceRef(loggerPodName),
			},
		},
	})

	t.Logf("Waiting for all resources ready")
	client.WaitForAllTestResourcesReadyOrFail()

	t.Logf("Sleeping for 3s to let the timer tick at least once")
	time.Sleep(3 * time.Second)

	pods, err := client.Kube.Kube.CoreV1().Pods(client.Namespace).List(meta.ListOptions{
		LabelSelector: "camel.apache.org/integration",
	})
	if err != nil {
		t.Fatalf("cannot get integration pod: %v", err)
	}
	if len(pods.Items) == 0 {
		t.Fatalf("no integration pod found")
	}
	printPodLogs(t, client, pods.Items[0].Name, "integration")

	if err := client.CheckLog(loggerPodName, lib.CheckerContains(body)); err != nil {
		printPodLogs(t, client, pods.Items[0].Name, "integration")
		t.Fatalf("Strings %q not found in logs of logger pod %q: %v", body, loggerPodName, err)
	}
}

func printPodLogs(t *testing.T, c *lib.Client, podName, containerName string) {
	logs, err := c.Kube.PodLogs(podName, containerName, c.Namespace)
	if err == nil {
		t.Log(string(logs))
	}
	t.Logf("End of pod %s logs", podName)
}

func createCamelSourceOrFail(c *lib.Client, camelSource *v1alpha1.CamelSource) {
	camelSourceClientSet, err := camelsourceclient.NewForConfig(c.Config)
	if err != nil {
		c.T.Fatalf("Failed to create CamelSource client: %v", err)
	}

	cSources := camelSourceClientSet.SourcesV1alpha1().CamelSources(c.Namespace)
	if createdCamelSource, err := cSources.Create(camelSource); err != nil {
		c.T.Fatalf("Failed to create CamelSource %q: %v", camelSource.Name, err)
	} else {
		c.Tracker.AddObj(createdCamelSource)
	}
}

func createCamelPlatformOrFail(c *lib.Client, camelClient camelclientset.Interface, camelSourceName string) {
	platform := camelv1.IntegrationPlatform{
		ObjectMeta: meta.ObjectMeta{
			Name:      "camel-k",
			Namespace: c.Namespace,
		},
		Spec: camelv1.IntegrationPlatformSpec{
			Profile: camelv1.TraitProfileKnative,
		},
	}

	if _, err := camelClient.CamelV1().IntegrationPlatforms(c.Namespace).Create(&platform); err != nil {
		c.T.Fatalf("Failed to create IntegrationPlatform for CamelSource %q: %v", camelSourceName, err)
	}
}

func createCamelKitOrFail(c *lib.Client, camelClient camelclientset.Interface, camelSourceName string) {
	// Creating this kit manually because the Camel K platform is not configured to do it on its own.
	// Testing that Camel K works is not in scope for this test.
	kit := camelv1.IntegrationKit{
		ObjectMeta: meta.ObjectMeta{
			Name:      "test-kit",
			Namespace: c.Namespace,
			Labels: map[string]string{
				"camel.apache.org/kit.type": "external",
			},
		},
		Spec: camelv1.IntegrationKitSpec{
			Dependencies: []string{
				"camel:timer",
				"mvn:org.apache.camel.k/camel-k-loader-knative",
				"mvn:org.apache.camel.k/camel-k-loader-yaml",
				"mvn:org.apache.camel.k/camel-k-runtime-knative",
				"mvn:org.apache.camel.k/camel-k-runtime-main",
			},
			Image: "docker.io/testcamelk/camel-k-kit-knative-timer:1.0.0-RC2",
		},
	}

	if _, err := camelClient.CamelV1().IntegrationKits(c.Namespace).Create(&kit); err != nil {
		c.T.Fatalf("Failed to create IntegrationKit for CamelSource %q: %v", camelSourceName, err)
	}
}

func getCamelKClient(c *lib.Client) camelclientset.Interface {
	return camelclientset.NewForConfigOrDie(c.Config)
}
<commit_msg>Fix #1250: wait for integrationkit to be ready in CI (#1258)<commit_after>//+build e2e

/*
Copyright 2020 The Knative Authors

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

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

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

package e2e

import (
	"testing"
	"time"

	camelclientset "github.com/apache/camel-k/pkg/client/clientset/versioned"

	camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1"
	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
	"knative.dev/eventing-contrib/camel/source/pkg/apis/sources/v1alpha1"
	camelsourceclient "knative.dev/eventing-contrib/camel/source/pkg/client/clientset/versioned"
	"knative.dev/eventing/test/lib"
	"knative.dev/eventing/test/lib/resources"
	knativeduck "knative.dev/pkg/apis/duck/v1beta1"
)

func TestCamelSource(t *testing.T) {

	const (
		camelSourceName = "e2e-camelsource"
		loggerPodName   = "e2e-camelsource-logger-pod"
		body            = "Hello, world!"
	)

	client := lib.Setup(t, true)
	defer lib.TearDown(client)

	t.Logf("Creating logger Pod")
	pod := resources.EventLoggerPod(loggerPodName)
	client.CreatePodOrFail(pod, lib.WithService(loggerPodName))

	camelClient := getCamelKClient(client)

	t.Logf("Creating Camel K IntegrationPlatform")
	createCamelPlatformOrFail(client, camelClient, camelSourceName)

	t.Logf("Creating Camel K Kit (to skip build)")
	createCamelKitOrFail(client, camelClient, camelSourceName)

	t.Logf("Creating CamelSource")
	createCamelSourceOrFail(client, &v1alpha1.CamelSource{
		ObjectMeta: meta.ObjectMeta{
			Name: camelSourceName,
		},
		Spec: v1alpha1.CamelSourceSpec{
			Source: v1alpha1.CamelSourceOriginSpec{
				Flow: &v1alpha1.Flow{
					"from": &map[string]interface{}{
						"uri": "timer:tick?period=1s",
						"steps": []interface{}{
							&map[string]interface{}{
								"set-body": &map[string]interface{}{
									"constant": body,
								},
							},
						},
					},
				},
			},
			Sink: &knativeduck.Destination{
				Ref: resources.ServiceRef(loggerPodName),
			},
		},
	})

	t.Logf("Waiting for all resources ready")
	client.WaitForAllTestResourcesReadyOrFail()

	t.Logf("Sleeping for 3s to let the timer tick at least once")
	time.Sleep(3 * time.Second)

	pods, err := client.Kube.Kube.CoreV1().Pods(client.Namespace).List(meta.ListOptions{
		LabelSelector: "camel.apache.org/integration",
	})
	if err != nil {
		t.Fatalf("cannot get integration pod: %v", err)
	}
	if len(pods.Items) == 0 {
		t.Fatalf("no integration pod found")
	}
	printPodLogs(t, client, pods.Items[0].Name, "integration")

	if err := client.CheckLog(loggerPodName, lib.CheckerContains(body)); err != nil {
		printPodLogs(t, client, pods.Items[0].Name, "integration")
		t.Fatalf("Strings %q not found in logs of logger pod %q: %v", body, loggerPodName, err)
	}
}

func printPodLogs(t *testing.T, c *lib.Client, podName, containerName string) {
	logs, err := c.Kube.PodLogs(podName, containerName, c.Namespace)
	if err == nil {
		t.Log(string(logs))
	}
	t.Logf("End of pod %s logs", podName)
}

func createCamelSourceOrFail(c *lib.Client, camelSource *v1alpha1.CamelSource) {
	camelSourceClientSet, err := camelsourceclient.NewForConfig(c.Config)
	if err != nil {
		c.T.Fatalf("Failed to create CamelSource client: %v", err)
	}

	cSources := camelSourceClientSet.SourcesV1alpha1().CamelSources(c.Namespace)
	if createdCamelSource, err := cSources.Create(camelSource); err != nil {
		c.T.Fatalf("Failed to create CamelSource %q: %v", camelSource.Name, err)
	} else {
		c.Tracker.AddObj(createdCamelSource)
	}
}

func createCamelPlatformOrFail(c *lib.Client, camelClient camelclientset.Interface, camelSourceName string) {
	platform := camelv1.IntegrationPlatform{
		ObjectMeta: meta.ObjectMeta{
			Name:      "camel-k",
			Namespace: c.Namespace,
		},
		Spec: camelv1.IntegrationPlatformSpec{
			Profile: camelv1.TraitProfileKnative,
		},
	}

	if _, err := camelClient.CamelV1().IntegrationPlatforms(c.Namespace).Create(&platform); err != nil {
		c.T.Fatalf("Failed to create IntegrationPlatform for CamelSource %q: %v", camelSourceName, err)
	}
}

func createCamelKitOrFail(c *lib.Client, camelClient camelclientset.Interface, camelSourceName string) {
	// Creating this kit manually because the Camel K platform is not configured to do it on its own.
	// Testing that Camel K works is not in scope for this test.
	kit := camelv1.IntegrationKit{
		ObjectMeta: meta.ObjectMeta{
			Name:      "test-kit",
			Namespace: c.Namespace,
			Labels: map[string]string{
				"camel.apache.org/kit.type": "external",
			},
		},
		Spec: camelv1.IntegrationKitSpec{
			Dependencies: []string{
				"camel:timer",
				"mvn:org.apache.camel.k/camel-k-loader-knative",
				"mvn:org.apache.camel.k/camel-k-loader-yaml",
				"mvn:org.apache.camel.k/camel-k-runtime-knative",
				"mvn:org.apache.camel.k/camel-k-runtime-main",
			},
			Image: "docker.io/testcamelk/camel-k-kit-knative-timer:1.0.0-RC2",
		},
	}

	if _, err := camelClient.CamelV1().IntegrationKits(c.Namespace).Create(&kit); err != nil {
		c.T.Fatalf("Failed to create IntegrationKit for CamelSource %q: %v", camelSourceName, err)
	}

	// Wait for the kit to be "Ready" before creating other resources
	var ik *camelv1.IntegrationKit
	for i := 0; i < 30; i++ {
		var err error
		ik, err = camelClient.CamelV1().IntegrationKits(c.Namespace).Get(kit.Name, meta.GetOptions{})
		if err != nil {
			c.T.Fatalf("Failed to retrieve IntegrationKit %q: %v", kit.Name, err)
		}
		if ik.Status.Phase == camelv1.IntegrationKitPhaseReady {
			break
		}
		time.Sleep(1 * time.Second)
	}
	if ik == nil || ik.Status.Phase != camelv1.IntegrationKitPhaseReady {
		c.T.Fatalf("IntegrationKit %q is not ready", kit.Name)
	}
}

func getCamelKClient(c *lib.Client) camelclientset.Interface {
	return camelclientset.NewForConfigOrDie(c.Config)
}
<|endoftext|>