<commit_before>package boomer

import (
	"fmt"
	"log"
	"strconv"
	"stressengineio/engineio2"
	"sync/atomic"
)

// pointer of engineio client
var MessageCount int64
var ConnectionCount int64
var ShowReceivedMessages bool
var StartCountMessages bool
var SubscribeCount int64

// sunny :Special handling for web socket connection
func (b *Boomer) runWorkerEngineIo(n int) {

	ShowReceivedMessages = false
	StartCountMessages = false
	client, err := engineioclient2.Dial(b.RawURL, nil)
	if err != nil {
		log.Fatal("dial:", err)
	}

	//Loop:
	for {
		select {
		case ev := <-client.Event:
			switch ev.Type {
			case "Open":
				b.clients[n] = client
				atomic.AddInt64(&ConnectionCount, 1)
				if int(ConnectionCount) == b.C {
					fmt.Printf("\n---Connected: %d/%d\n", ConnectionCount, b.C)
				}
			case "Close":
				atomic.AddInt64(&ConnectionCount, -1)
				log.Printf("---Disconnected by remote server:%d\n", ConnectionCount)
				b.clients[n] = nil
			case "Message":
				if StartCountMessages {
					atomic.AddInt64(&MessageCount, 1)
				}
				if ShowReceivedMessages {
					log.Print("Got a message")
				}
			}
			//default: bug fix: do not use default in select-for, which will cause cpu-usage
		}
	}
}

func (b *Boomer) subscribe(clientIdx int, topicName string) {
	if b.clients[clientIdx] == nil {
		log.Fatal("connnection failed:", clientIdx)
		return
	}
	deviceID := "boom" + strconv.Itoa(clientIdx)
	v := &struct {
		ID            string                 `json:"id"`
		Version       int                    `json:"version"`
		Platform      string                 `json:"platform"`
		Topics        []string               `json:"topics"`
		LastPacketIds map[string]interface{} `json:"lastPacketIds"`
		LastUnicastID string                 `json:"lastUnicastId"`
	}{
		ID:            deviceID,
		Version:       2,
		Platform:      "stressEngineIO",
		Topics:        []string{},
		LastPacketIds: nil,
		LastUnicastID: ""}
	err := b.clients[clientIdx].Emit("pushId", v)
	if err != nil {
		log.Fatal("emit failed:", err)
	}
	//log.Printf("new client deviceId: %s", deviceID)

	t := struct {
		Topic string `json:"topic"`
	}{topicName}
	b.clients[clientIdx].Emit("subscribeTopic", &t)
	atomic.AddInt64(&SubscribeCount, 1)
	if int(SubscribeCount) == b.C {
		fmt.Printf("\n---Subscibing done: %v\n", SubscribeCount)
	}
}
<commit_msg>show detail message content<commit_after>package boomer

import (
	"fmt"
	"log"
	"strconv"
	"stressengineio/engineio2"
	"sync/atomic"
)

// pointer of engineio client
var MessageCount int64
var ConnectionCount int64
var ShowReceivedMessages bool
var StartCountMessages bool
var SubscribeCount int64

// sunny :Special handling for web socket connection
func (b *Boomer) runWorkerEngineIo(n int) {

	ShowReceivedMessages = false
	StartCountMessages = false
	client, err := engineioclient2.Dial(b.RawURL, nil)
	if err != nil {
		log.Fatal("dial:", err)
	}

	//Loop:
	for {
		select {
		case ev := <-client.Event:
			switch ev.Type {
			case "Open":
				b.clients[n] = client
				atomic.AddInt64(&ConnectionCount, 1)
				if int(ConnectionCount) == b.C {
					fmt.Printf("\n---Connected: %d/%d\n", ConnectionCount, b.C)
				}
			case "Close":
				atomic.AddInt64(&ConnectionCount, -1)
				log.Printf("---Disconnected by remote server:%d\n", ConnectionCount)
				b.clients[n] = nil
			case "Message":
				if StartCountMessages {
					atomic.AddInt64(&MessageCount, 1)
				}
				if ShowReceivedMessages {
					log.Printf("Got a Message %s", string(ev.Data))
					//log.Print("Got a message")
				}
			}
			//default: bug fix: do not use default in select-for, which will cause cpu-usage
		}
	}
}

func (b *Boomer) subscribe(clientIdx int, topicName string) {
	if b.clients[clientIdx] == nil {
		log.Fatal("connnection failed:", clientIdx)
		return
	}
	deviceID := "boom" + strconv.Itoa(clientIdx)
	v := &struct {
		ID            string                 `json:"id"`
		Version       int                    `json:"version"`
		Platform      string                 `json:"platform"`
		Topics        []string               `json:"topics"`
		LastPacketIds map[string]interface{} `json:"lastPacketIds"`
		LastUnicastID string                 `json:"lastUnicastId"`
	}{
		ID:            deviceID,
		Version:       2,
		Platform:      "stressEngineIO",
		Topics:        []string{},
		LastPacketIds: nil,
		LastUnicastID: ""}
	err := b.clients[clientIdx].Emit("pushId", v)
	if err != nil {
		log.Fatal("emit failed:", err)
	}
	//log.Printf("new client deviceId: %s", deviceID)

	t := struct {
		Topic string `json:"topic"`
	}{topicName}
	b.clients[clientIdx].Emit("subscribeTopic", &t)
	atomic.AddInt64(&SubscribeCount, 1)
	if int(SubscribeCount) == b.C {
		fmt.Printf("\n---Subscibing done: %v\n", SubscribeCount)
	}
}
<|endoftext|>


<commit_before>package torlib

import (
	"crypto/sha1"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"os"

	"code.uber.internal/go-common.git/x/log"
	"code.uber.internal/infra/kraken-torrent/bencode"
)

// Info is a torrent info dictionary.
type Info struct {
	PieceLength int64  `bencode:"piece length"`
	Pieces      Pieces `bencode:"pieces"`
	Name        string `bencode:"name"`
	Length      int64  `bencode:"length"`
}

// NewInfoFromFile creates new info given file and piecelength
func NewInfoFromFile(name, filepath string, pieceLength int64) (Info, error) {
	length, pieces, err := generatePieces(filepath, pieceLength)
	if err != nil {
		return Info{}, err
	}

	return Info{
		PieceLength: pieceLength,
		Pieces:      pieces,
		Name:        name,
		Length:      length,
	}, nil
}

// PieceHash returns the hash for given piece
func (info *Info) PieceHash(piece int) ([]byte, error) {
	if piece >= info.NumPieces() {
		return nil, fmt.Errorf("Piece index %d out of range %d", piece, info.NumPieces())
	}

	if math.Mod(float64(len(info.Pieces)), float64(info.NumPieces())) != 0.0 {
		return nil, fmt.Errorf("Length of piece hash %d is not a muliple of the number of pieces %d",
			len(info.Pieces), info.NumPieces())
	}

	pieceHashSize := info.pieceHashSize()
	start := piece * pieceHashSize
	end := (piece + 1) * pieceHashSize
	hash := make([]byte, end-start)
	copy(hash, info.Pieces[start:end])
	return hash, nil
}

// TotalLength returns a total length of all torrent files
func (info *Info) TotalLength() (ret int64) {
	return info.Length
}

// NumPieces return number of pieces in a torrent
func (info *Info) NumPieces() int {
	if len(info.Pieces)%20 != 0 {
		panic(len(info.Pieces))
	}
	return len(info.Pieces) / 20
}

// Validate returns error if the Info is invalid.
func (info *Info) Validate() error {
	if len(info.Pieces)%20 != 0 {
		return errors.New("pieces has invalid length")
	}
	if info.PieceLength == 0 {
		if info.TotalLength() != 0 {
			return errors.New("zero piece length")
		}
	} else {
		if int((info.TotalLength()+info.PieceLength-1)/info.PieceLength) != info.NumPieces() {
			return fmt.Errorf("piece count and file lengths are at odds: num pieces %d", info.NumPieces())
		}
	}
	return nil
}

// ComputeInfoHash returns the hash of Info
// it is an identifier of a torrent
func (info *Info) ComputeInfoHash() (InfoHash, error) {
	infoBytes, err := bencode.Marshal(info)
	if err != nil {
		return InfoHash{}, err
	}
	return NewInfoHashFromBytes(infoBytes), nil
}

// Serialize returns info as bytes
func (info *Info) Serialize() ([]byte, error) {
	bytes, err := json.Marshal(info)
	if err != nil {
		log.Error(err)
		return nil, err
	}
	return bytes, nil
}

// pieceHashSize returns the size of each piece hash.
func (info *Info) pieceHashSize() int {
	return sha1.New().Size()
}

// generatePieces hashes file content in chunks given path and pieceLength, and returns file length and hashes
func generatePieces(fp string, pieceLength int64) (int64, Pieces, error) {
	if pieceLength <= 0 {
		return 0, nil, errors.New("piece length must be positive")
	}

	f, err := os.Open(fp)
	if err != nil {
		return 0, nil, err
	}
	defer f.Close()

	stat, err := f.Stat()
	if err != nil {
		return 0, nil, err
	}

	// Pipe file content
	pr, pw := io.Pipe()
	defer pr.Close()
	go func() {
		_, err := io.Copy(pw, f)
		pw.CloseWithError(err)
	}()

	// Generate hash
	var pieces []byte
	for {
		hasher := sha1.New()
		wn, err := io.CopyN(hasher, pr, pieceLength)
		if err == io.EOF {
			err = nil
		}
		if err != nil {
			return 0, nil, err
		}
		if wn == 0 {
			break
		}
		pieces = hasher.Sum(pieces)
		if wn < pieceLength {
			break
		}
	}

	return stat.Size(), Pieces(pieces), nil
}
<commit_msg>Remove kraken-torrent dependency<commit_after>package torlib

import (
	"bytes"
	"crypto/sha1"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"os"

	"code.uber.internal/go-common.git/x/log"
	bencode "github.com/jackpal/bencode-go"
)

// Info is a torrent info dictionary.
type Info struct {
	PieceLength int64  `bencode:"piece length"`
	Pieces      Pieces `bencode:"pieces"`
	Name        string `bencode:"name"`
	Length      int64  `bencode:"length"`
}

// NewInfoFromFile creates new info given file and piecelength
func NewInfoFromFile(name, filepath string, pieceLength int64) (Info, error) {
	length, pieces, err := generatePieces(filepath, pieceLength)
	if err != nil {
		return Info{}, err
	}

	return Info{
		PieceLength: pieceLength,
		Pieces:      pieces,
		Name:        name,
		Length:      length,
	}, nil
}

// PieceHash returns the hash for given piece
func (info *Info) PieceHash(piece int) ([]byte, error) {
	if piece >= info.NumPieces() {
		return nil, fmt.Errorf("Piece index %d out of range %d", piece, info.NumPieces())
	}

	if math.Mod(float64(len(info.Pieces)), float64(info.NumPieces())) != 0.0 {
		return nil, fmt.Errorf("Length of piece hash %d is not a muliple of the number of pieces %d",
			len(info.Pieces), info.NumPieces())
	}

	pieceHashSize := info.pieceHashSize()
	start := piece * pieceHashSize
	end := (piece + 1) * pieceHashSize
	hash := make([]byte, end-start)
	copy(hash, info.Pieces[start:end])
	return hash, nil
}

// TotalLength returns a total length of all torrent files
func (info *Info) TotalLength() (ret int64) {
	return info.Length
}

// NumPieces return number of pieces in a torrent
func (info *Info) NumPieces() int {
	if len(info.Pieces)%20 != 0 {
		panic(len(info.Pieces))
	}
	return len(info.Pieces) / 20
}

// Validate returns error if the Info is invalid.
func (info *Info) Validate() error {
	if len(info.Pieces)%20 != 0 {
		return errors.New("pieces has invalid length")
	}
	if info.PieceLength == 0 {
		if info.TotalLength() != 0 {
			return errors.New("zero piece length")
		}
	} else {
		if int((info.TotalLength()+info.PieceLength-1)/info.PieceLength) != info.NumPieces() {
			return fmt.Errorf("piece count and file lengths are at odds: num pieces %d", info.NumPieces())
		}
	}
	return nil
}

// ComputeInfoHash returns the hash of Info
// it is an identifier of a torrent
func (info *Info) ComputeInfoHash() (InfoHash, error) {
	b := new(bytes.Buffer)
	if err := bencode.Marshal(b, *info); err != nil {
		return InfoHash{}, err
	}
	return NewInfoHashFromBytes(b.Bytes()), nil
}

// Serialize returns info as bytes
func (info *Info) Serialize() ([]byte, error) {
	bytes, err := json.Marshal(info)
	if err != nil {
		log.Error(err)
		return nil, err
	}
	return bytes, nil
}

// pieceHashSize returns the size of each piece hash.
func (info *Info) pieceHashSize() int {
	return sha1.New().Size()
}

// generatePieces hashes file content in chunks given path and pieceLength, and returns file length and hashes
func generatePieces(fp string, pieceLength int64) (int64, Pieces, error) {
	if pieceLength <= 0 {
		return 0, nil, errors.New("piece length must be positive")
	}

	f, err := os.Open(fp)
	if err != nil {
		return 0, nil, err
	}
	defer f.Close()

	stat, err := f.Stat()
	if err != nil {
		return 0, nil, err
	}

	// Pipe file content
	pr, pw := io.Pipe()
	defer pr.Close()
	go func() {
		_, err := io.Copy(pw, f)
		pw.CloseWithError(err)
	}()

	// Generate hash
	var pieces []byte
	for {
		hasher := sha1.New()
		wn, err := io.CopyN(hasher, pr, pieceLength)
		if err == io.EOF {
			err = nil
		}
		if err != nil {
			return 0, nil, err
		}
		if wn == 0 {
			break
		}
		pieces = hasher.Sum(pieces)
		if wn < pieceLength {
			break
		}
	}

	return stat.Size(), Pieces(pieces), nil
}
<|endoftext|>


<commit_before>package command

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"strings"

	"github.com/hashicorp/consul/api"
	"github.com/hashicorp/consul/configutil"
	"github.com/mitchellh/cli"
	text "github.com/tonnerre/golang-text"
)

// maxLineLength is the maximum width of any line.
const maxLineLength int = 72

// FlagSetFlags is an enum to define what flags are present in the
// default FlagSet returned.
type FlagSetFlags uint

const (
	FlagSetNone       FlagSetFlags = 1 << iota
	FlagSetClientHTTP FlagSetFlags = 1 << iota
	FlagSetServerHTTP FlagSetFlags = 1 << iota

	FlagSetHTTP = FlagSetClientHTTP | FlagSetServerHTTP
)

type BaseCommand struct {
	UI    cli.Ui
	Flags FlagSetFlags

	HideNormalFlagsHelp bool

	flagSet *flag.FlagSet
	hidden  *flag.FlagSet

	// These are the options which correspond to the HTTP API options
	httpAddr      configutil.StringValue
	token         configutil.StringValue
	caFile        configutil.StringValue
	caPath        configutil.StringValue
	certFile      configutil.StringValue
	keyFile       configutil.StringValue
	tlsServerName configutil.StringValue

	datacenter configutil.StringValue
	stale      configutil.BoolValue
}

// HTTPClient returns a client with the parsed flags. It panics if the command
// does not accept HTTP flags or if the flags have not been parsed.
func (c *BaseCommand) HTTPClient() (*api.Client, error) {
	if !c.hasClientHTTP() && !c.hasServerHTTP() {
		panic("no http flags defined")
	}
	if !c.flagSet.Parsed() {
		panic("flags have not been parsed")
	}

	config := api.DefaultConfig()
	c.httpAddr.Merge(&config.Address)
	c.token.Merge(&config.Token)
	c.caFile.Merge(&config.TLSConfig.CAFile)
	c.caPath.Merge(&config.TLSConfig.CAPath)
	c.certFile.Merge(&config.TLSConfig.CertFile)
	c.keyFile.Merge(&config.TLSConfig.KeyFile)
	c.tlsServerName.Merge(&config.TLSConfig.Address)
	c.datacenter.Merge(&config.Datacenter)
	return api.NewClient(config)
}

func (c *BaseCommand) HTTPAddr() string {
	return c.httpAddr.String()
}

func (c *BaseCommand) HTTPToken() string {
	return c.token.String()
}

func (c *BaseCommand) HTTPDatacenter() string {
	return c.datacenter.String()
}

func (c *BaseCommand) HTTPStale() bool {
	var stale bool
	c.stale.Merge(&stale)
	return stale
}

// httpFlagsClient is the list of flags that apply to HTTP connections.
func (c *BaseCommand) httpFlagsClient(f *flag.FlagSet) *flag.FlagSet {
	if f == nil {
		f = flag.NewFlagSet("", flag.ContinueOnError)
	}

	f.Var(&c.caFile, "ca-file",
		"Path to a CA file to use for TLS when communicating with Consul. This "+
			"can also be specified via the CONSUL_CACERT environment variable.")
	f.Var(&c.caPath, "ca-path",
		"Path to a directory of CA certificates to use for TLS when communicating "+
			"with Consul. This can also be specified via the CONSUL_CAPATH environment variable.")
	f.Var(&c.certFile, "client-cert",
		"Path to a client cert file to use for TLS when 'verify_incoming' is enabled. This "+
			"can also be specified via the CONSUL_CLIENT_CERT environment variable.")
	f.Var(&c.keyFile, "client-key",
		"Path to a client key file to use for TLS when 'verify_incoming' is enabled. This "+
			"can also be specified via the CONSUL_CLIENT_KEY environment variable.")
	f.Var(&c.httpAddr, "http-addr",
		"The `address` and port of the Consul HTTP agent. The value can be an IP "+
			"address or DNS address, but it must also include the port. This can "+
			"also be specified via the CONSUL_HTTP_ADDR environment variable. The "+
			"default value is http://127.0.0.1:8500. The scheme can also be set to "+
			"HTTPS by setting the environment variable CONSUL_HTTP_SSL=true.")
	f.Var(&c.token, "token",
		"ACL token to use in the request. This can also be specified via the "+
			"CONSUL_HTTP_TOKEN environment variable. If unspecified, the query will "+
			"default to the token of the Consul agent at the HTTP address.")
	f.Var(&c.tlsServerName, "tls-server-name",
		"The server name to use as the SNI host when connecting via TLS. This "+
			"can also be specified via the CONSUL_TLS_SERVER_NAME environment variable.")

	return f
}

// httpFlagsServer is the list of flags that apply to HTTP connections.
func (c *BaseCommand) httpFlagsServer(f *flag.FlagSet) *flag.FlagSet {
	if f == nil {
		f = flag.NewFlagSet("", flag.ContinueOnError)
	}

	f.Var(&c.datacenter, "datacenter",
		"Name of the datacenter to query. If unspecified, this will default to "+
			"the datacenter of the queried agent.")
	f.Var(&c.stale, "stale",
		"Permit any Consul server (non-leader) to respond to this request. This "+
			"allows for lower latency and higher throughput, but can result in "+
			"stale data. This option has no effect on non-read operations. The "+
			"default value is false.")

	return f
}

// NewFlagSet creates a new flag set for the given command. It automatically
// generates help output and adds the appropriate API flags.
func (c *BaseCommand) NewFlagSet(command cli.Command) *flag.FlagSet {
	f := flag.NewFlagSet("", flag.ContinueOnError)
	f.Usage = func() { c.UI.Error(command.Help()) }

	if c.hasClientHTTP() {
		c.httpFlagsClient(f)
	}

	if c.hasServerHTTP() {
		c.httpFlagsServer(f)
	}

	errR, errW := io.Pipe()
	errScanner := bufio.NewScanner(errR)
	go func() {
		for errScanner.Scan() {
			c.UI.Error(errScanner.Text())
		}
	}()
	f.SetOutput(errW)

	c.flagSet = f
	c.hidden = flag.NewFlagSet("", flag.ContinueOnError)

	return f
}

// HideFlags is used to set hidden flags that will not be shown in help text
func (c *BaseCommand) HideFlags(flags ...string) {
	for _, f := range flags {
		c.hidden.String(f, "", "")
	}
}

// Parse is used to parse the underlying flag set.
func (c *BaseCommand) Parse(args []string) error {
	return c.flagSet.Parse(args)
}

// Help returns the help for this flagSet.
func (c *BaseCommand) Help() string {
	// Some commands with subcommands (kv/snapshot) call this without initializing
	// any flags first, so exit early to avoid a panic
	if c.flagSet == nil {
		return ""
	}
	return c.helpFlagsFor(c.flagSet)
}

// hasClientHTTP returns true if this meta command contains client HTTP flags.
func (c *BaseCommand) hasClientHTTP() bool {
	return c.Flags&FlagSetClientHTTP != 0
}

// hasServerHTTP returns true if this meta command contains server HTTP flags.
func (c *BaseCommand) hasServerHTTP() bool {
	return c.Flags&FlagSetServerHTTP != 0
}

// helpFlagsFor visits all flags in the given flag set and prints formatted
// help output. This function is sad because there's no "merging" of command
// line flags. We explicitly pull out our "common" options into another section
// by doing string comparisons :(.
func (c *BaseCommand) helpFlagsFor(f *flag.FlagSet) string {
	httpFlagsClient := c.httpFlagsClient(nil)
	httpFlagsServer := c.httpFlagsServer(nil)

	var out bytes.Buffer

	firstHTTP := true
	if c.hasClientHTTP() {
		if firstHTTP {
			printTitle(&out, "HTTP API Options")
			firstHTTP = false
		}
		httpFlagsClient.VisitAll(func(f *flag.Flag) {
			printFlag(&out, f)
		})
	}
	if c.hasServerHTTP() {
		if firstHTTP {
			printTitle(&out, "HTTP API Options")
			firstHTTP = false
		}
		httpFlagsServer.VisitAll(func(f *flag.Flag) {
			printFlag(&out, f)
		})
	}

	if !c.HideNormalFlagsHelp {
		firstCommand := true
		f.VisitAll(func(f *flag.Flag) {
			// Skip HTTP flags as they will be grouped separately
			if flagContains(httpFlagsClient, f) || flagContains(httpFlagsServer, f) || flagContains(c.hidden, f) {
				return
			}
			if firstCommand {
				printTitle(&out, "Command Options")
				firstCommand = false
			}
			printFlag(&out, f)
		})
	}

	return strings.TrimRight(out.String(), "\n")
}

// printTitle prints a consistently-formatted title to the given writer.
func printTitle(w io.Writer, s string) {
	fmt.Fprintf(w, "%s\n\n", s)
}

// printFlag prints a single flag to the given writer.
func printFlag(w io.Writer, f *flag.Flag) {
	example, _ := flag.UnquoteUsage(f)
	if example != "" {
		fmt.Fprintf(w, "  -%s=<%s>\n", f.Name, example)
	} else {
		fmt.Fprintf(w, "  -%s\n", f.Name)
	}

	indented := wrapAtLength(f.Usage, 5)
	fmt.Fprintf(w, "%s\n\n", indented)
}

// flagContains returns true if the given flag is contained in the given flag
// set or false otherwise.
func flagContains(fs *flag.FlagSet, f *flag.Flag) bool {
	var skip bool

	fs.VisitAll(func(hf *flag.Flag) {
		if skip {
			return
		}

		if f.Name == hf.Name {
			skip = true
			return
		}
	})

	return skip
}

// wrapAtLength wraps the given text at the maxLineLength, taking into account
// any provided left padding.
func wrapAtLength(s string, pad int) string {
	wrapped := text.Wrap(s, maxLineLength-pad)
	lines := strings.Split(wrapped, "\n")
	for i, line := range lines {
		lines[i] = strings.Repeat(" ", pad) + line
	}
	return strings.Join(lines, "\n")
}
<commit_msg>Split BaseCommand http config into a separate function<commit_after>package command

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"strings"

	"github.com/hashicorp/consul/api"
	"github.com/hashicorp/consul/configutil"
	"github.com/mitchellh/cli"
	text "github.com/tonnerre/golang-text"
)

// maxLineLength is the maximum width of any line.
const maxLineLength int = 72

// FlagSetFlags is an enum to define what flags are present in the
// default FlagSet returned.
type FlagSetFlags uint

const (
	FlagSetNone       FlagSetFlags = 1 << iota
	FlagSetClientHTTP FlagSetFlags = 1 << iota
	FlagSetServerHTTP FlagSetFlags = 1 << iota

	FlagSetHTTP = FlagSetClientHTTP | FlagSetServerHTTP
)

type BaseCommand struct {
	UI    cli.Ui
	Flags FlagSetFlags

	HideNormalFlagsHelp bool

	flagSet *flag.FlagSet
	hidden  *flag.FlagSet

	// These are the options which correspond to the HTTP API options
	httpAddr      configutil.StringValue
	token         configutil.StringValue
	caFile        configutil.StringValue
	caPath        configutil.StringValue
	certFile      configutil.StringValue
	keyFile       configutil.StringValue
	tlsServerName configutil.StringValue

	datacenter configutil.StringValue
	stale      configutil.BoolValue
}

// HTTPClient returns a client with the parsed flags. It panics if the command
// does not accept HTTP flags or if the flags have not been parsed.
func (c *BaseCommand) HTTPClient() (*api.Client, error) {
	if !c.hasClientHTTP() && !c.hasServerHTTP() {
		panic("no http flags defined")
	}
	if !c.flagSet.Parsed() {
		panic("flags have not been parsed")
	}

	config := api.DefaultConfig()
	c.MergeHTTPConfig(config)
	return api.NewClient(config)
}

func (c *BaseCommand) MergeHTTPConfig(config *api.Config) {
	c.httpAddr.Merge(&config.Address)
	c.token.Merge(&config.Token)
	c.caFile.Merge(&config.TLSConfig.CAFile)
	c.caPath.Merge(&config.TLSConfig.CAPath)
	c.certFile.Merge(&config.TLSConfig.CertFile)
	c.keyFile.Merge(&config.TLSConfig.KeyFile)
	c.tlsServerName.Merge(&config.TLSConfig.Address)
	c.datacenter.Merge(&config.Datacenter)
}

func (c *BaseCommand) HTTPAddr() string {
	return c.httpAddr.String()
}

func (c *BaseCommand) HTTPToken() string {
	return c.token.String()
}

func (c *BaseCommand) HTTPDatacenter() string {
	return c.datacenter.String()
}

func (c *BaseCommand) HTTPStale() bool {
	var stale bool
	c.stale.Merge(&stale)
	return stale
}

// httpFlagsClient is the list of flags that apply to HTTP connections.
func (c *BaseCommand) httpFlagsClient(f *flag.FlagSet) *flag.FlagSet {
	if f == nil {
		f = flag.NewFlagSet("", flag.ContinueOnError)
	}

	f.Var(&c.caFile, "ca-file",
		"Path to a CA file to use for TLS when communicating with Consul. This "+
			"can also be specified via the CONSUL_CACERT environment variable.")
	f.Var(&c.caPath, "ca-path",
		"Path to a directory of CA certificates to use for TLS when communicating "+
			"with Consul. This can also be specified via the CONSUL_CAPATH environment variable.")
	f.Var(&c.certFile, "client-cert",
		"Path to a client cert file to use for TLS when 'verify_incoming' is enabled. This "+
			"can also be specified via the CONSUL_CLIENT_CERT environment variable.")
	f.Var(&c.keyFile, "client-key",
		"Path to a client key file to use for TLS when 'verify_incoming' is enabled. This "+
			"can also be specified via the CONSUL_CLIENT_KEY environment variable.")
	f.Var(&c.httpAddr, "http-addr",
		"The `address` and port of the Consul HTTP agent. The value can be an IP "+
			"address or DNS address, but it must also include the port. This can "+
			"also be specified via the CONSUL_HTTP_ADDR environment variable. The "+
			"default value is http://127.0.0.1:8500. The scheme can also be set to "+
			"HTTPS by setting the environment variable CONSUL_HTTP_SSL=true.")
	f.Var(&c.token, "token",
		"ACL token to use in the request. This can also be specified via the "+
			"CONSUL_HTTP_TOKEN environment variable. If unspecified, the query will "+
			"default to the token of the Consul agent at the HTTP address.")
	f.Var(&c.tlsServerName, "tls-server-name",
		"The server name to use as the SNI host when connecting via TLS. This "+
			"can also be specified via the CONSUL_TLS_SERVER_NAME environment variable.")

	return f
}

// httpFlagsServer is the list of flags that apply to HTTP connections.
func (c *BaseCommand) httpFlagsServer(f *flag.FlagSet) *flag.FlagSet {
	if f == nil {
		f = flag.NewFlagSet("", flag.ContinueOnError)
	}

	f.Var(&c.datacenter, "datacenter",
		"Name of the datacenter to query. If unspecified, this will default to "+
			"the datacenter of the queried agent.")
	f.Var(&c.stale, "stale",
		"Permit any Consul server (non-leader) to respond to this request. This "+
			"allows for lower latency and higher throughput, but can result in "+
			"stale data. This option has no effect on non-read operations. The "+
			"default value is false.")

	return f
}

// NewFlagSet creates a new flag set for the given command. It automatically
// generates help output and adds the appropriate API flags.
func (c *BaseCommand) NewFlagSet(command cli.Command) *flag.FlagSet {
	f := flag.NewFlagSet("", flag.ContinueOnError)
	f.Usage = func() { c.UI.Error(command.Help()) }

	if c.hasClientHTTP() {
		c.httpFlagsClient(f)
	}

	if c.hasServerHTTP() {
		c.httpFlagsServer(f)
	}

	errR, errW := io.Pipe()
	errScanner := bufio.NewScanner(errR)
	go func() {
		for errScanner.Scan() {
			c.UI.Error(errScanner.Text())
		}
	}()
	f.SetOutput(errW)

	c.flagSet = f
	c.hidden = flag.NewFlagSet("", flag.ContinueOnError)

	return f
}

// HideFlags is used to set hidden flags that will not be shown in help text
func (c *BaseCommand) HideFlags(flags ...string) {
	for _, f := range flags {
		c.hidden.String(f, "", "")
	}
}

// Parse is used to parse the underlying flag set.
func (c *BaseCommand) Parse(args []string) error {
	return c.flagSet.Parse(args)
}

// Help returns the help for this flagSet.
func (c *BaseCommand) Help() string {
	// Some commands with subcommands (kv/snapshot) call this without initializing
	// any flags first, so exit early to avoid a panic
	if c.flagSet == nil {
		return ""
	}
	return c.helpFlagsFor(c.flagSet)
}

// hasClientHTTP returns true if this meta command contains client HTTP flags.
func (c *BaseCommand) hasClientHTTP() bool {
	return c.Flags&FlagSetClientHTTP != 0
}

// hasServerHTTP returns true if this meta command contains server HTTP flags.
func (c *BaseCommand) hasServerHTTP() bool {
	return c.Flags&FlagSetServerHTTP != 0
}

// helpFlagsFor visits all flags in the given flag set and prints formatted
// help output. This function is sad because there's no "merging" of command
// line flags. We explicitly pull out our "common" options into another section
// by doing string comparisons :(.
func (c *BaseCommand) helpFlagsFor(f *flag.FlagSet) string {
	httpFlagsClient := c.httpFlagsClient(nil)
	httpFlagsServer := c.httpFlagsServer(nil)

	var out bytes.Buffer

	firstHTTP := true
	if c.hasClientHTTP() {
		if firstHTTP {
			printTitle(&out, "HTTP API Options")
			firstHTTP = false
		}
		httpFlagsClient.VisitAll(func(f *flag.Flag) {
			printFlag(&out, f)
		})
	}
	if c.hasServerHTTP() {
		if firstHTTP {
			printTitle(&out, "HTTP API Options")
			firstHTTP = false
		}
		httpFlagsServer.VisitAll(func(f *flag.Flag) {
			printFlag(&out, f)
		})
	}

	if !c.HideNormalFlagsHelp {
		firstCommand := true
		f.VisitAll(func(f *flag.Flag) {
			// Skip HTTP flags as they will be grouped separately
			if flagContains(httpFlagsClient, f) || flagContains(httpFlagsServer, f) || flagContains(c.hidden, f) {
				return
			}
			if firstCommand {
				printTitle(&out, "Command Options")
				firstCommand = false
			}
			printFlag(&out, f)
		})
	}

	return strings.TrimRight(out.String(), "\n")
}

// printTitle prints a consistently-formatted title to the given writer.
func printTitle(w io.Writer, s string) {
	fmt.Fprintf(w, "%s\n\n", s)
}

// printFlag prints a single flag to the given writer.
func printFlag(w io.Writer, f *flag.Flag) {
	example, _ := flag.UnquoteUsage(f)
	if example != "" {
		fmt.Fprintf(w, "  -%s=<%s>\n", f.Name, example)
	} else {
		fmt.Fprintf(w, "  -%s\n", f.Name)
	}

	indented := wrapAtLength(f.Usage, 5)
	fmt.Fprintf(w, "%s\n\n", indented)
}

// flagContains returns true if the given flag is contained in the given flag
// set or false otherwise.
func flagContains(fs *flag.FlagSet, f *flag.Flag) bool {
	var skip bool

	fs.VisitAll(func(hf *flag.Flag) {
		if skip {
			return
		}

		if f.Name == hf.Name {
			skip = true
			return
		}
	})

	return skip
}

// wrapAtLength wraps the given text at the maxLineLength, taking into account
// any provided left padding.
func wrapAtLength(s string, pad int) string {
	wrapped := text.Wrap(s, maxLineLength-pad)
	lines := strings.Split(wrapped, "\n")
	for i, line := range lines {
		lines[i] = strings.Repeat(" ", pad) + line
	}
	return strings.Join(lines, "\n")
}
<|endoftext|>


<commit_before>package command

import (
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/hashicorp/terraform/terraform"
)

// PlanCommand is a Command implementation that compares a Terraform
// configuration to an actual infrastructure and shows the differences.
type PlanCommand struct {
	Meta
}

func (c *PlanCommand) Run(args []string) int {
	var destroy, refresh, detailed bool
	var outPath string
	var moduleDepth int

	args = c.Meta.process(args, true)

	cmdFlags := c.Meta.flagSet("plan")
	cmdFlags.BoolVar(&destroy, "destroy", false, "destroy")
	cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
	c.addModuleDepthFlag(cmdFlags, &moduleDepth)
	cmdFlags.StringVar(&outPath, "out", "", "path")
	cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
	cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
	cmdFlags.BoolVar(&detailed, "detailed-exitcode", false, "detailed-exitcode")
	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	var path string
	args = cmdFlags.Args()
	if len(args) > 1 {
		c.Ui.Error(
			"The plan command expects at most one argument with the path\n" +
				"to a Terraform configuration.\n")
		cmdFlags.Usage()
		return 1
	} else if len(args) == 1 {
		path = args[0]
	} else {
		var err error
		path, err = os.Getwd()
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
		}
	}

	countHook := new(CountHook)
	c.Meta.extraHooks = []terraform.Hook{countHook}

	ctx, _, err := c.Context(contextOpts{
		Destroy:   destroy,
		Path:      path,
		StatePath: c.Meta.statePath,
	})
	if err != nil {
		c.Ui.Error(err.Error())
		return 1
	}
	if !validateContext(ctx, c.Ui) {
		return 1
	}
	if err := ctx.Input(c.InputMode()); err != nil {
		c.Ui.Error(fmt.Sprintf("Error configuring: %s", err))
		return 1
	}

	if refresh {
		c.Ui.Output("Refreshing Terraform state prior to plan...\n")
		state, err := ctx.Refresh()
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
			return 1
		}
		c.Ui.Output("")

		if state != nil {
			log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
			if err := c.Meta.PersistState(state); err != nil {
				c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
				return 1
			}
		}
	}

	plan, err := ctx.Plan()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error running plan: %s", err))
		return 1
	}

	if plan.Diff.Empty() {
		c.Ui.Output(
			"No changes. Infrastructure is up-to-date. This means that Terraform\n" +
				"could not detect any differences between your configuration and\n" +
				"the real physical resources that exist. As a result, Terraform\n" +
				"doesn't need to do anything.")
		return 0
	}

	if outPath != "" {
		log.Printf("[INFO] Writing plan output to: %s", outPath)
		f, err := os.Create(outPath)
		if err == nil {
			defer f.Close()
			err = terraform.WritePlan(plan, f)
		}
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error writing plan file: %s", err))
			return 1
		}
	}

	if outPath == "" {
		c.Ui.Output(strings.TrimSpace(planHeaderNoOutput) + "\n")
	} else {
		c.Ui.Output(fmt.Sprintf(
			strings.TrimSpace(planHeaderYesOutput)+"\n",
			outPath))
	}

	c.Ui.Output(FormatPlan(&FormatPlanOpts{
		Plan:        plan,
		Color:       c.Colorize(),
		ModuleDepth: moduleDepth,
	}))

	c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
		"[reset][bold]Plan:[reset] "+
			"%d to add, %d to change, %d to destroy.",
		countHook.ToAdd,
		(countHook.ToChange + countHook.ToRemoveAndAdd),
		countHook.ToRemove)))

	if detailed {
		return 2
	}
	return 0
}

func (c *PlanCommand) Help() string {
	helpText := `
Usage: terraform plan [options] [dir]

  Generates an execution plan for Terraform.

  This execution plan can be reviewed prior to running apply to get a
  sense for what Terraform will do. Optionally, the plan can be saved to
  a Terraform plan file, and apply can take this plan file to execute
  this plan exactly.

Options:

  -backup=path        Path to backup the existing state file before
                      modifying. Defaults to the "-state-out" path with
                      ".backup" extension. Set to "-" to disable backup.

  -destroy            If set, a plan will be generated to destroy all resources
                      managed by the given configuration and state.

  -detailed-exitcode  Return detailed exit codes when the command exits. This
                      will change the meaning of exit codes to:
                      0 - Succeeded, diff is empty (no changes)
                      1 - Errored
                      2 - Succeeded, there is a diff

  -input=true         Ask for input for variables if not directly set.

  -module-depth=n     Specifies the depth of modules to show in the output.
                      This does not affect the plan itself, only the output
                      shown. By default, this is zero. -1 will expand all.

  -no-color           If specified, output won't contain any color.

  -out=path           Write a plan file to the given path. This can be used as
                      input to the "apply" command.

  -refresh=true       Update state prior to checking for differences.

  -state=statefile    Path to a Terraform state file to use to look
                      up Terraform-managed resources. By default it will
                      use the state "terraform.tfstate" if it exists.

  -target=resource    Resource to target. Operation will be limited to this
                      resource and its dependencies. This flag can be used
                      multiple times.

  -var 'foo=bar'      Set a variable in the Terraform configuration. This
                      flag can be set multiple times.

  -var-file=foo       Set variables in the Terraform configuration from
                      a file. If "terraform.tfvars" is present, it will be
                      automatically loaded if this flag is not specified.
`
	return strings.TrimSpace(helpText)
}

func (c *PlanCommand) Synopsis() string {
	return "Generate and show an execution plan"
}

const planHeaderNoOutput = `
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.
`

const planHeaderYesOutput = `
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Your plan was also saved to the path below. Call the "apply" subcommand
with this plan file and Terraform will exactly execute this execution
plan.

Path: %s
`
<commit_msg>In plan summary, be more honest about force-new changes.<commit_after>package command

import (
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/hashicorp/terraform/terraform"
)

// PlanCommand is a Command implementation that compares a Terraform
// configuration to an actual infrastructure and shows the differences.
type PlanCommand struct {
	Meta
}

func (c *PlanCommand) Run(args []string) int {
	var destroy, refresh, detailed bool
	var outPath string
	var moduleDepth int

	args = c.Meta.process(args, true)

	cmdFlags := c.Meta.flagSet("plan")
	cmdFlags.BoolVar(&destroy, "destroy", false, "destroy")
	cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
	c.addModuleDepthFlag(cmdFlags, &moduleDepth)
	cmdFlags.StringVar(&outPath, "out", "", "path")
	cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
	cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
	cmdFlags.BoolVar(&detailed, "detailed-exitcode", false, "detailed-exitcode")
	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	var path string
	args = cmdFlags.Args()
	if len(args) > 1 {
		c.Ui.Error(
			"The plan command expects at most one argument with the path\n" +
				"to a Terraform configuration.\n")
		cmdFlags.Usage()
		return 1
	} else if len(args) == 1 {
		path = args[0]
	} else {
		var err error
		path, err = os.Getwd()
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
		}
	}

	countHook := new(CountHook)
	c.Meta.extraHooks = []terraform.Hook{countHook}

	ctx, _, err := c.Context(contextOpts{
		Destroy:   destroy,
		Path:      path,
		StatePath: c.Meta.statePath,
	})
	if err != nil {
		c.Ui.Error(err.Error())
		return 1
	}
	if !validateContext(ctx, c.Ui) {
		return 1
	}
	if err := ctx.Input(c.InputMode()); err != nil {
		c.Ui.Error(fmt.Sprintf("Error configuring: %s", err))
		return 1
	}

	if refresh {
		c.Ui.Output("Refreshing Terraform state prior to plan...\n")
		state, err := ctx.Refresh()
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
			return 1
		}
		c.Ui.Output("")

		if state != nil {
			log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
			if err := c.Meta.PersistState(state); err != nil {
				c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
				return 1
			}
		}
	}

	plan, err := ctx.Plan()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error running plan: %s", err))
		return 1
	}

	if plan.Diff.Empty() {
		c.Ui.Output(
			"No changes. Infrastructure is up-to-date. This means that Terraform\n" +
				"could not detect any differences between your configuration and\n" +
				"the real physical resources that exist. As a result, Terraform\n" +
				"doesn't need to do anything.")
		return 0
	}

	if outPath != "" {
		log.Printf("[INFO] Writing plan output to: %s", outPath)
		f, err := os.Create(outPath)
		if err == nil {
			defer f.Close()
			err = terraform.WritePlan(plan, f)
		}
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error writing plan file: %s", err))
			return 1
		}
	}

	if outPath == "" {
		c.Ui.Output(strings.TrimSpace(planHeaderNoOutput) + "\n")
	} else {
		c.Ui.Output(fmt.Sprintf(
			strings.TrimSpace(planHeaderYesOutput)+"\n",
			outPath))
	}

	c.Ui.Output(FormatPlan(&FormatPlanOpts{
		Plan:        plan,
		Color:       c.Colorize(),
		ModuleDepth: moduleDepth,
	}))

	c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
		"[reset][bold]Plan:[reset] "+
			"%d to add, %d to change, %d to destroy.",
		countHook.ToAdd+countHook.ToRemoveAndAdd,
		countHook.ToChange,
		countHook.ToRemove+countHook.ToRemoveAndAdd)))

	if detailed {
		return 2
	}
	return 0
}

func (c *PlanCommand) Help() string {
	helpText := `
Usage: terraform plan [options] [dir]

  Generates an execution plan for Terraform.

  This execution plan can be reviewed prior to running apply to get a
  sense for what Terraform will do. Optionally, the plan can be saved to
  a Terraform plan file, and apply can take this plan file to execute
  this plan exactly.

Options:

  -backup=path        Path to backup the existing state file before
                      modifying. Defaults to the "-state-out" path with
                      ".backup" extension. Set to "-" to disable backup.

  -destroy            If set, a plan will be generated to destroy all resources
                      managed by the given configuration and state.

  -detailed-exitcode  Return detailed exit codes when the command exits. This
                      will change the meaning of exit codes to:
                      0 - Succeeded, diff is empty (no changes)
                      1 - Errored
                      2 - Succeeded, there is a diff

  -input=true         Ask for input for variables if not directly set.

  -module-depth=n     Specifies the depth of modules to show in the output.
                      This does not affect the plan itself, only the output
                      shown. By default, this is zero. -1 will expand all.

  -no-color           If specified, output won't contain any color.

  -out=path           Write a plan file to the given path. This can be used as
                      input to the "apply" command.

  -refresh=true       Update state prior to checking for differences.

  -state=statefile    Path to a Terraform state file to use to look
                      up Terraform-managed resources. By default it will
                      use the state "terraform.tfstate" if it exists.

  -target=resource    Resource to target. Operation will be limited to this
                      resource and its dependencies. This flag can be used
                      multiple times.

  -var 'foo=bar'      Set a variable in the Terraform configuration. This
                      flag can be set multiple times.

  -var-file=foo       Set variables in the Terraform configuration from
                      a file. If "terraform.tfvars" is present, it will be
                      automatically loaded if this flag is not specified.
`
	return strings.TrimSpace(helpText)
}

func (c *PlanCommand) Synopsis() string {
	return "Generate and show an execution plan"
}

const planHeaderNoOutput = `
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.
`

const planHeaderYesOutput = `
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Your plan was also saved to the path below. Call the "apply" subcommand
with this plan file and Terraform will exactly execute this execution
plan.

Path: %s
`
<|endoftext|>


<commit_before>package common

const SUCC int = 99
const FAIL int = 1
const CONFIG_NOT_FOUND int = 2

const TAG_ARCHIVE string = "arc_"
const TAG_PAGE string = "pag_"
const TAG_HOME string = "home_"
const TAG_CAT string = "cat_"
const TAG_NOTFOUND string = "404_"

//数据库最大空闲数
const def_Database_MAX_IDLE_NUM = 20

//数据库最大连接数
const def_Database_MAX_OPEN_NUM = 100

//默认Redis过期时间
const def_Redis_EXPIRE_TIME = 3600

//默认网站名称
const def_Website_SITE_NAME = "Beaker"

//默认网站描述
const def_Website_SITE_DES = "Beaker is a simple blog system."

//默认footer内容
const def_Website_SITE_FOOTER = "Beaker is a simple blog system."

//默认首页显示记录数
const def_Website_INDEX_LIST_NUM = 10

//默认tweet每页显示数
const def_Website_TWEET_NUM_ONE_PAGE = 10

const DefaultEditor string = "nano"

const SERVER_ENV_KEY string = "BEAKERPATH"
const ADMIN_ENV_KEY string = "BEAKERADMINPATH"
<commit_msg>open some const value<commit_after>package common

const SUCC int = 99
const FAIL int = 1
const CONFIG_NOT_FOUND int = 2

const TAG_ARCHIVE string = "arc_"
const TAG_PAGE string = "pag_"
const TAG_HOME string = "home_"
const TAG_CAT string = "cat_"
const TAG_NOTFOUND string = "404_"

//数据库最大空闲数
const Def_Database_MAX_IDLE_NUM = 20

//数据库最大连接数
const Def_Database_MAX_OPEN_NUM = 100

//默认Redis过期时间
const Def_Redis_EXPIRE_TIME = 3600

//默认网站名称
const Def_Website_SITE_NAME = "Beaker"

//默认网站描述
const Def_Website_SITE_DES = "Beaker is a simple blog system."

//默认footer内容
const Def_Website_SITE_FOOTER = "Beaker is a simple blog system."

//默认首页显示记录数
const Def_Website_INDEX_LIST_NUM = 10

//默认tweet每页显示数
const Def_Website_TWEET_NUM_ONE_PAGE = 10

const DefaultEditor string = "nano"

const SERVER_ENV_KEY string = "BEAKERPATH"
const ADMIN_ENV_KEY string = "BEAKERADMINPATH"
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"html/template"
	"os"

	"github.com/nicksnyder/go-i18n/i18n"
)

var testTemplate2 *template.Template

var funcMap2 = map[string]interface{}{
	"T": i18n.IdentityTfunc,
}

func initTranslation() {
	i18n.MustLoadTranslationFile("lang/en-US.all.json")
	T, _ := i18n.Tfunc("en-US")

	var err error
	//testTemplate, err = template.New("hello.gohtml").Funcs(funcMap).ParseFiles("hello.gohtml")

	testTemplate2, err = template.New("hello.gohtml").Funcs(map[string]interface{}{
		"T": T,
	}).ParseFiles("hello.gohtml")

	if err != nil {
		panic(err)
	}

	fmt.Println(T("program_greeting"))

	err = testTemplate2.Execute(os.Stdout, map[string]interface{}{
		"Person": "Bob",
	})
	if err != nil {
		fmt.Printf("%v", err)
	}
}
<commit_msg>No more use<commit_after><|endoftext|>


<commit_before>package mppostgres

import (
	"flag"
	"fmt"
	"os"

	"github.com/jmoiron/sqlx"
	// PostgreSQL Driver
	_ "github.com/lib/pq"
	mp "github.com/mackerelio/go-mackerel-plugin-helper"
	"github.com/mackerelio/mackerel-agent/logging"
)

var logger = logging.GetLogger("metrics.plugin.postgres")

var graphdef = map[string]mp.Graphs{
	"postgres.connections": {
		Label: "Postgres Connections",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "active", Label: "Active", Diff: false, Stacked: true},
			{Name: "waiting", Label: "Waiting", Diff: false, Stacked: true},
		},
	},
	"postgres.commits": {
		Label: "Postgres Commits",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "xact_commit", Label: "Xact Commit", Diff: true, Stacked: false},
			{Name: "xact_rollback", Label: "Xact Rollback", Diff: true, Stacked: false},
		},
	},
	"postgres.blocks": {
		Label: "Postgres Blocks",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "blks_read", Label: "Blocks Read", Diff: true, Stacked: false},
			{Name: "blks_hit", Label: "Blocks Hit", Diff: true, Stacked: false},
		},
	},
	"postgres.rows": {
		Label: "Postgres Rows",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "tup_returned", Label: "Returned Rows", Diff: true, Stacked: false},
			{Name: "tup_fetched", Label: "Fetched Rows", Diff: true, Stacked: true},
			{Name: "tup_inserted", Label: "Inserted Rows", Diff: true, Stacked: true},
			{Name: "tup_updated", Label: "Updated Rows", Diff: true, Stacked: true},
			{Name: "tup_deleted", Label: "Deleted Rows", Diff: true, Stacked: true},
		},
	},
	"postgres.size": {
		Label: "Postgres Data Size",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "total_size", Label: "Total Size", Diff: false, Stacked: false},
		},
	},
	"postgres.deadlocks": {
		Label: "Postgres Dead Locks",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "deadlocks", Label: "Deadlocks", Diff: true, Stacked: false},
		},
	},
	"postgres.iotime": {
		Label: "Postgres Block I/O time",
		Unit:  "float",
		Metrics: []mp.Metrics{
			{Name: "blk_read_time", Label: "Block Read Time (ms)", Diff: true, Stacked: false},
			{Name: "blk_write_time", Label: "Block Write Time (ms)", Diff: true, Stacked: false},
		},
	},
	"postgres.tempfile": {
		Label: "Postgres Temporary file",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "temp_bytes", Label: "Temporary file size (byte)", Diff: true, Stacked: false},
		},
	},
}

// PostgresPlugin mackerel plugin for PostgreSQL
type PostgresPlugin struct {
	Host     string
	Port     string
	Username string
	Password string
	SSLmode  string
	Timeout  int
	Tempfile string
	Option   string
}

func fetchStatDatabase(db *sqlx.DB) (map[string]interface{}, error) {
	db = db.Unsafe()
	rows, err := db.Queryx(`SELECT * FROM pg_stat_database`)
	if err != nil {
		logger.Errorf("Failed to select pg_stat_database. %s", err)
		return nil, err
	}

	type pgStat struct {
		XactCommit   uint64   `db:"xact_commit"`
		XactRollback uint64   `db:"xact_rollback"`
		BlksRead     uint64   `db:"blks_read"`
		BlksHit      uint64   `db:"blks_hit"`
		BlkReadTime  *float64 `db:"blk_read_time"`
		BlkWriteTime *float64 `db:"blk_write_time"`
		TupReturned  uint64   `db:"tup_returned"`
		TupFetched   uint64   `db:"tup_fetched"`
		TupInserted  uint64   `db:"tup_inserted"`
		TupUpdated   uint64   `db:"tup_updated"`
		TupDeleted   uint64   `db:"tup_deleted"`
		Deadlocks    *uint64  `db:"deadlocks"`
		TempBytes    *uint64  `db:"temp_bytes"`
	}

	totalStat := pgStat{}
	for rows.Next() {
		p := pgStat{}
		if err := rows.StructScan(&p); err != nil {
			logger.Warningf("Failed to scan. %s", err)
			continue
		}
		totalStat.XactCommit += p.XactCommit
		totalStat.XactRollback += p.XactRollback
		totalStat.BlksRead += p.BlksRead
		totalStat.BlksHit += p.BlksHit
		if p.BlkReadTime != nil {
			if totalStat.BlkReadTime == nil {
				totalStat.BlkReadTime = p.BlkReadTime
			} else {
				*totalStat.BlkReadTime += *p.BlkReadTime
			}
		}
		if p.BlkWriteTime != nil {
			if totalStat.BlkWriteTime == nil {
				totalStat.BlkWriteTime = p.BlkWriteTime
			} else {
				*totalStat.BlkWriteTime += *p.BlkWriteTime
			}
		}
		totalStat.TupReturned += p.TupReturned
		totalStat.TupFetched += p.TupFetched
		totalStat.TupInserted += p.TupInserted
		totalStat.TupUpdated += p.TupUpdated
		totalStat.TupDeleted += p.TupDeleted
		if p.Deadlocks != nil {
			if totalStat.Deadlocks == nil {
				totalStat.Deadlocks = p.Deadlocks
			} else {
				*totalStat.Deadlocks += *p.Deadlocks
			}
		}
		if p.TempBytes != nil {
			if totalStat.TempBytes == nil {
				totalStat.TempBytes = p.TempBytes
			} else {
				*totalStat.TempBytes += *p.TempBytes
			}
		}
	}
	stat := make(map[string]interface{})
	stat["xact_commit"] = totalStat.XactCommit
	stat["xact_rollback"] = totalStat.XactRollback
	stat["blks_read"] = totalStat.BlksRead
	stat["blks_hit"] = totalStat.BlksHit
	if totalStat.BlkReadTime != nil {
		stat["blk_read_time"] = *totalStat.BlkReadTime
	}
	if totalStat.BlkWriteTime != nil {
		stat["blk_write_time"] = *totalStat.BlkWriteTime
	}
	stat["tup_returned"] = totalStat.TupReturned
	stat["tup_fetched"] = totalStat.TupFetched
	stat["tup_inserted"] = totalStat.TupInserted
	stat["tup_updated"] = totalStat.TupUpdated
	stat["tup_deleted"] = totalStat.TupDeleted
	if totalStat.Deadlocks != nil {
		stat["deadlocks"] = *totalStat.Deadlocks
	}
	if totalStat.TempBytes != nil {
		stat["temp_bytes"] = *totalStat.TempBytes
	}
	return stat, nil
}

func fetchConnections(db *sqlx.DB) (map[string]interface{}, error) {
	rows, err := db.Query(`
		select count(*), waiting from pg_stat_activity group by waiting
	`)
	if err != nil {
		logger.Errorf("Failed to select pg_stat_activity. %s", err)
		return nil, err
	}

	var totalActive, totalWaiting float64
	for rows.Next() {
		var count float64
		var waiting string
		if err := rows.Scan(&count, &waiting); err != nil {
			logger.Warningf("Failed to scan %s", err)
			continue
		}
		if waiting != "" {
			totalActive += count
		} else {
			totalWaiting += count
		}
	}

	return map[string]interface{}{
		"active":  totalActive,
		"waiting": totalWaiting,
	}, nil
}

func fetchDatabaseSize(db *sqlx.DB) (map[string]interface{}, error) {
	rows, err := db.Query("select sum(pg_database_size(datname)) as dbsize from pg_database")
	if err != nil {
		logger.Errorf("Failed to select pg_database_size. %s", err)
		return nil, err
	}

	var totalSize float64
	for rows.Next() {
		var dbsize float64
		if err := rows.Scan(&dbsize); err != nil {
			logger.Warningf("Failed to scan %s", err)
			continue
		}
		totalSize += dbsize
	}

	return map[string]interface{}{
		"total_size": totalSize,
	}, nil
}

func mergeStat(dst, src map[string]interface{}) {
	for k, v := range src {
		dst[k] = v
	}
}

// FetchMetrics interface for mackerelplugin
func (p PostgresPlugin) FetchMetrics() (map[string]interface{}, error) {

	db, err := sqlx.Connect("postgres", fmt.Sprintf("user=%s password=%s host=%s port=%s sslmode=%s connect_timeout=%d %s", p.Username, p.Password, p.Host, p.Port, p.SSLmode, p.Timeout, p.Option))
	if err != nil {
		logger.Errorf("FetchMetrics: %s", err)
		return nil, err
	}
	defer db.Close()

	statStatDatabase, err := fetchStatDatabase(db)
	if err != nil {
		return nil, err
	}
	statConnections, err := fetchConnections(db)
	if err != nil {
		return nil, err
	}
	statDatabaseSize, err := fetchDatabaseSize(db)
	if err != nil {
		return nil, err
	}

	stat := make(map[string]interface{})
	mergeStat(stat, statStatDatabase)
	mergeStat(stat, statConnections)
	mergeStat(stat, statDatabaseSize)

	return stat, err
}

// GraphDefinition interface for mackerelplugin
func (p PostgresPlugin) GraphDefinition() map[string]mp.Graphs {
	return graphdef
}

// Do the plugin
func Do() {
	optHost := flag.String("hostname", "localhost", "Hostname to login to")
	optPort := flag.String("port", "5432", "Database port")
	optUser := flag.String("user", "", "Postgres User")
	optDatabase := flag.String("database", "", "Database name")
	optPass := flag.String("password", "", "Postgres Password")
	optSSLmode := flag.String("sslmode", "disable", "Whether or not to use SSL")
	optConnectTimeout := flag.Int("connect_timeout", 5, "Maximum wait for connection, in seconds.")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	if *optUser == "" {
		logger.Warningf("user is required")
		flag.PrintDefaults()
		os.Exit(1)
	}
	if *optPass == "" {
		logger.Warningf("password is required")
		flag.PrintDefaults()
		os.Exit(1)
	}
	option := ""
	if *optDatabase != "" {
		option = fmt.Sprintf("dbname=%s", *optDatabase)
	}

	var postgres PostgresPlugin
	postgres.Host = *optHost
	postgres.Port = *optPort
	postgres.Username = *optUser
	postgres.Password = *optPass
	postgres.SSLmode = *optSSLmode
	postgres.Timeout = *optConnectTimeout
	postgres.Option = option

	helper := mp.NewMackerelPlugin(postgres)

	helper.Tempfile = *optTempfile
	helper.Run()
}
<commit_msg>postgres: collect dbsize only if connectable<commit_after>package mppostgres

import (
	"flag"
	"fmt"
	"os"

	"github.com/jmoiron/sqlx"
	// PostgreSQL Driver
	_ "github.com/lib/pq"
	mp "github.com/mackerelio/go-mackerel-plugin-helper"
	"github.com/mackerelio/mackerel-agent/logging"
)

var logger = logging.GetLogger("metrics.plugin.postgres")

var graphdef = map[string]mp.Graphs{
	"postgres.connections": {
		Label: "Postgres Connections",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "active", Label: "Active", Diff: false, Stacked: true},
			{Name: "waiting", Label: "Waiting", Diff: false, Stacked: true},
		},
	},
	"postgres.commits": {
		Label: "Postgres Commits",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "xact_commit", Label: "Xact Commit", Diff: true, Stacked: false},
			{Name: "xact_rollback", Label: "Xact Rollback", Diff: true, Stacked: false},
		},
	},
	"postgres.blocks": {
		Label: "Postgres Blocks",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "blks_read", Label: "Blocks Read", Diff: true, Stacked: false},
			{Name: "blks_hit", Label: "Blocks Hit", Diff: true, Stacked: false},
		},
	},
	"postgres.rows": {
		Label: "Postgres Rows",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "tup_returned", Label: "Returned Rows", Diff: true, Stacked: false},
			{Name: "tup_fetched", Label: "Fetched Rows", Diff: true, Stacked: true},
			{Name: "tup_inserted", Label: "Inserted Rows", Diff: true, Stacked: true},
			{Name: "tup_updated", Label: "Updated Rows", Diff: true, Stacked: true},
			{Name: "tup_deleted", Label: "Deleted Rows", Diff: true, Stacked: true},
		},
	},
	"postgres.size": {
		Label: "Postgres Data Size",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "total_size", Label: "Total Size", Diff: false, Stacked: false},
		},
	},
	"postgres.deadlocks": {
		Label: "Postgres Dead Locks",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "deadlocks", Label: "Deadlocks", Diff: true, Stacked: false},
		},
	},
	"postgres.iotime": {
		Label: "Postgres Block I/O time",
		Unit:  "float",
		Metrics: []mp.Metrics{
			{Name: "blk_read_time", Label: "Block Read Time (ms)", Diff: true, Stacked: false},
			{Name: "blk_write_time", Label: "Block Write Time (ms)", Diff: true, Stacked: false},
		},
	},
	"postgres.tempfile": {
		Label: "Postgres Temporary file",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "temp_bytes", Label: "Temporary file size (byte)", Diff: true, Stacked: false},
		},
	},
}

// PostgresPlugin mackerel plugin for PostgreSQL
type PostgresPlugin struct {
	Host     string
	Port     string
	Username string
	Password string
	SSLmode  string
	Timeout  int
	Tempfile string
	Option   string
}

func fetchStatDatabase(db *sqlx.DB) (map[string]interface{}, error) {
	db = db.Unsafe()
	rows, err := db.Queryx(`SELECT * FROM pg_stat_database`)
	if err != nil {
		logger.Errorf("Failed to select pg_stat_database. %s", err)
		return nil, err
	}

	type pgStat struct {
		XactCommit   uint64   `db:"xact_commit"`
		XactRollback uint64   `db:"xact_rollback"`
		BlksRead     uint64   `db:"blks_read"`
		BlksHit      uint64   `db:"blks_hit"`
		BlkReadTime  *float64 `db:"blk_read_time"`
		BlkWriteTime *float64 `db:"blk_write_time"`
		TupReturned  uint64   `db:"tup_returned"`
		TupFetched   uint64   `db:"tup_fetched"`
		TupInserted  uint64   `db:"tup_inserted"`
		TupUpdated   uint64   `db:"tup_updated"`
		TupDeleted   uint64   `db:"tup_deleted"`
		Deadlocks    *uint64  `db:"deadlocks"`
		TempBytes    *uint64  `db:"temp_bytes"`
	}

	totalStat := pgStat{}
	for rows.Next() {
		p := pgStat{}
		if err := rows.StructScan(&p); err != nil {
			logger.Warningf("Failed to scan. %s", err)
			continue
		}
		totalStat.XactCommit += p.XactCommit
		totalStat.XactRollback += p.XactRollback
		totalStat.BlksRead += p.BlksRead
		totalStat.BlksHit += p.BlksHit
		if p.BlkReadTime != nil {
			if totalStat.BlkReadTime == nil {
				totalStat.BlkReadTime = p.BlkReadTime
			} else {
				*totalStat.BlkReadTime += *p.BlkReadTime
			}
		}
		if p.BlkWriteTime != nil {
			if totalStat.BlkWriteTime == nil {
				totalStat.BlkWriteTime = p.BlkWriteTime
			} else {
				*totalStat.BlkWriteTime += *p.BlkWriteTime
			}
		}
		totalStat.TupReturned += p.TupReturned
		totalStat.TupFetched += p.TupFetched
		totalStat.TupInserted += p.TupInserted
		totalStat.TupUpdated += p.TupUpdated
		totalStat.TupDeleted += p.TupDeleted
		if p.Deadlocks != nil {
			if totalStat.Deadlocks == nil {
				totalStat.Deadlocks = p.Deadlocks
			} else {
				*totalStat.Deadlocks += *p.Deadlocks
			}
		}
		if p.TempBytes != nil {
			if totalStat.TempBytes == nil {
				totalStat.TempBytes = p.TempBytes
			} else {
				*totalStat.TempBytes += *p.TempBytes
			}
		}
	}
	stat := make(map[string]interface{})
	stat["xact_commit"] = totalStat.XactCommit
	stat["xact_rollback"] = totalStat.XactRollback
	stat["blks_read"] = totalStat.BlksRead
	stat["blks_hit"] = totalStat.BlksHit
	if totalStat.BlkReadTime != nil {
		stat["blk_read_time"] = *totalStat.BlkReadTime
	}
	if totalStat.BlkWriteTime != nil {
		stat["blk_write_time"] = *totalStat.BlkWriteTime
	}
	stat["tup_returned"] = totalStat.TupReturned
	stat["tup_fetched"] = totalStat.TupFetched
	stat["tup_inserted"] = totalStat.TupInserted
	stat["tup_updated"] = totalStat.TupUpdated
	stat["tup_deleted"] = totalStat.TupDeleted
	if totalStat.Deadlocks != nil {
		stat["deadlocks"] = *totalStat.Deadlocks
	}
	if totalStat.TempBytes != nil {
		stat["temp_bytes"] = *totalStat.TempBytes
	}
	return stat, nil
}

func fetchConnections(db *sqlx.DB) (map[string]interface{}, error) {
	rows, err := db.Query(`
		select count(*), waiting from pg_stat_activity group by waiting
	`)
	if err != nil {
		logger.Errorf("Failed to select pg_stat_activity. %s", err)
		return nil, err
	}

	var totalActive, totalWaiting float64
	for rows.Next() {
		var count float64
		var waiting string
		if err := rows.Scan(&count, &waiting); err != nil {
			logger.Warningf("Failed to scan %s", err)
			continue
		}
		if waiting != "" {
			totalActive += count
		} else {
			totalWaiting += count
		}
	}

	return map[string]interface{}{
		"active":  totalActive,
		"waiting": totalWaiting,
	}, nil
}

func fetchDatabaseSize(db *sqlx.DB) (map[string]interface{}, error) {
	rows, err := db.Query("select sum(pg_database_size(datname)) as dbsize from pg_database where has_database_privilege(datname, 'connect')")
	if err != nil {
		logger.Errorf("Failed to select pg_database_size. %s", err)
		return nil, err
	}

	var totalSize float64
	for rows.Next() {
		var dbsize float64
		if err := rows.Scan(&dbsize); err != nil {
			logger.Warningf("Failed to scan %s", err)
			continue
		}
		totalSize += dbsize
	}

	return map[string]interface{}{
		"total_size": totalSize,
	}, nil
}

func mergeStat(dst, src map[string]interface{}) {
	for k, v := range src {
		dst[k] = v
	}
}

// FetchMetrics interface for mackerelplugin
func (p PostgresPlugin) FetchMetrics() (map[string]interface{}, error) {

	db, err := sqlx.Connect("postgres", fmt.Sprintf("user=%s password=%s host=%s port=%s sslmode=%s connect_timeout=%d %s", p.Username, p.Password, p.Host, p.Port, p.SSLmode, p.Timeout, p.Option))
	if err != nil {
		logger.Errorf("FetchMetrics: %s", err)
		return nil, err
	}
	defer db.Close()

	statStatDatabase, err := fetchStatDatabase(db)
	if err != nil {
		return nil, err
	}
	statConnections, err := fetchConnections(db)
	if err != nil {
		return nil, err
	}
	statDatabaseSize, err := fetchDatabaseSize(db)
	if err != nil {
		return nil, err
	}

	stat := make(map[string]interface{})
	mergeStat(stat, statStatDatabase)
	mergeStat(stat, statConnections)
	mergeStat(stat, statDatabaseSize)

	return stat, err
}

// GraphDefinition interface for mackerelplugin
func (p PostgresPlugin) GraphDefinition() map[string]mp.Graphs {
	return graphdef
}

// Do the plugin
func Do() {
	optHost := flag.String("hostname", "localhost", "Hostname to login to")
	optPort := flag.String("port", "5432", "Database port")
	optUser := flag.String("user", "", "Postgres User")
	optDatabase := flag.String("database", "", "Database name")
	optPass := flag.String("password", "", "Postgres Password")
	optSSLmode := flag.String("sslmode", "disable", "Whether or not to use SSL")
	optConnectTimeout := flag.Int("connect_timeout", 5, "Maximum wait for connection, in seconds.")
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	if *optUser == "" {
		logger.Warningf("user is required")
		flag.PrintDefaults()
		os.Exit(1)
	}
	if *optPass == "" {
		logger.Warningf("password is required")
		flag.PrintDefaults()
		os.Exit(1)
	}
	option := ""
	if *optDatabase != "" {
		option = fmt.Sprintf("dbname=%s", *optDatabase)
	}

	var postgres PostgresPlugin
	postgres.Host = *optHost
	postgres.Port = *optPort
	postgres.Username = *optUser
	postgres.Password = *optPass
	postgres.SSLmode = *optSSLmode
	postgres.Timeout = *optConnectTimeout
	postgres.Option = option

	helper := mp.NewMackerelPlugin(postgres)

	helper.Tempfile = *optTempfile
	helper.Run()
}
<|endoftext|>


<commit_before>package filesystem

import (
	"fmt"
	"github.com/Symantec/Dominator/lib/filter"
	"github.com/Symantec/Dominator/lib/format"
	"github.com/Symantec/Dominator/lib/hash"
	"io"
)

type NumLinksTable map[uint64]int

type ListSelector uint8

const (
	ListSelectSkipMode = 1 << iota
	ListSelectSkipNumLinks
	ListSelectSkipUid
	ListSelectSkipGid
	ListSelectSkipSizeDevnum
	ListSelectSkipMtime
	ListSelectSkipName
	ListSelectSkipData

	ListSelectAll = 0
)

type GenericInode interface {
	// GetUid() and GetGid() may be added if needed.
	//GetUid() uint32
	//GetGid() uint32
	List(w io.Writer, name string, numLinksTable NumLinksTable,
		numLinks int, listSelector ListSelector, filter *filter.Filter) error
	WriteMetadata(name string) error
}

type InodeTable map[uint64]GenericInode
type InodeToFilenamesTable map[uint64][]string
type FilenameToInodeTable map[string]uint64
type HashToInodesTable map[hash.Hash][]uint64

type FileSystem struct {
	InodeTable               InodeTable
	inodeToFilenamesTable    InodeToFilenamesTable
	filenameToInodeTable     FilenameToInodeTable
	hashToInodesTable        HashToInodesTable
	NumRegularInodes         uint64
	TotalDataBytes           uint64
	numComputedRegularInodes *uint64
	DirectoryCount           uint64
	DirectoryInode
}

func (fs *FileSystem) RebuildInodePointers() error {
	return fs.rebuildInodePointers()
}

func (fs *FileSystem) InodeToFilenamesTable() InodeToFilenamesTable {
	return fs.buildInodeToFilenamesTable()
}

func (fs *FileSystem) FilenameToInodeTable() FilenameToInodeTable {
	return fs.buildFilenameToInodeTable()
}

func (fs *FileSystem) HashToInodesTable() HashToInodesTable {
	return fs.buildHashToInodesTable()
}

func (fs *FileSystem) ComputeTotalDataBytes() {
	fs.computeTotalDataBytes()
}

func Decode(reader io.Reader) (*FileSystem, error) {
	return decode(reader)
}

func (fs *FileSystem) Encode(writer io.Writer) error {
	return fs.encode(writer)
}

func (fs *FileSystem) Filter(filter *filter.Filter) *FileSystem {
	return fs.filter(filter)
}

func (fs *FileSystem) Listf(w io.Writer, listSelector ListSelector,
	filter *filter.Filter) error {
	return fs.list(w, listSelector, filter)
}

func (fs *FileSystem) List(w io.Writer) error {
	return fs.list(w, ListSelectAll, nil)
}

func (fs *FileSystem) NumComputedRegularInodes() uint64 {
	return fs.computeNumComputedRegularInodes()
}

func (fs *FileSystem) String() string {
	return fmt.Sprintf("Tree: %d inodes, total file size: %s, number of regular inodes: %d",
		len(fs.InodeTable),
		format.FormatBytes(fs.TotalDataBytes),
		fs.NumRegularInodes)
}

type DirectoryInode struct {
	EntryList     []*DirectoryEntry
	EntriesByName map[string]*DirectoryEntry
	Mode          FileMode
	Uid           uint32
	Gid           uint32
}

func (directory *DirectoryInode) BuildEntryMap() {
	directory.buildEntryMap()
}

func (inode *DirectoryInode) GetUid() uint32 {
	return inode.Uid
}

func (inode *DirectoryInode) GetGid() uint32 {
	return inode.Gid
}

func (inode *DirectoryInode) List(w io.Writer, name string,
	numLinksTable NumLinksTable, numLinks int,
	listSelector ListSelector, filter *filter.Filter) error {
	return inode.list(w, name, numLinksTable, numLinks, listSelector, filter)
}

func (inode *DirectoryInode) Write(name string) error {
	return inode.write(name)
}

func (inode *DirectoryInode) WriteMetadata(name string) error {
	return inode.writeMetadata(name)
}

type DirectoryEntry struct {
	Name        string
	InodeNumber uint64
	inode       GenericInode // Keep private to avoid encoding/transmission.
}

func (dirent *DirectoryEntry) Inode() GenericInode {
	return dirent.inode
}

func (dirent *DirectoryEntry) SetInode(inode GenericInode) {
	dirent.inode = inode
}

func (dirent *DirectoryEntry) String() string {
	return dirent.Name
}

type RegularInode struct {
	Mode             FileMode
	Uid              uint32
	Gid              uint32
	MtimeNanoSeconds int32
	MtimeSeconds     int64
	Size             uint64
	Hash             hash.Hash
}

func (inode *RegularInode) GetUid() uint32 {
	return inode.Uid
}

func (inode *RegularInode) GetGid() uint32 {
	return inode.Gid
}

func (inode *RegularInode) List(w io.Writer, name string,
	numLinksTable NumLinksTable, numLinks int,
	listSelector ListSelector, filter *filter.Filter) error {
	return inode.list(w, name, numLinksTable, numLinks, listSelector)
}

func (inode *RegularInode) WriteMetadata(name string) error {
	return inode.writeMetadata(name)
}

type ComputedRegularInode struct {
	Mode   FileMode
	Uid    uint32
	Gid    uint32
	Source string
}

func (inode *ComputedRegularInode) List(w io.Writer, name string,
	numLinksTable NumLinksTable, numLinks int,
	listSelector ListSelector, filter *filter.Filter) error {
	return inode.list(w, name, numLinksTable, numLinks, listSelector)
}

func (inode *ComputedRegularInode) WriteMetadata(name string) error {
	panic("cannot write metadata for computed file: " + name)
}

type SymlinkInode struct {
	Uid     uint32
	Gid     uint32
	Symlink string
}

func (inode *SymlinkInode) GetUid() uint32 {
	return inode.Uid
}

func (inode *SymlinkInode) GetGid() uint32 {
	return inode.Gid
}

func (inode *SymlinkInode) List(w io.Writer, name string,
	numLinksTable NumLinksTable, numLinks int,
	listSelector ListSelector, filter *filter.Filter) error {
	return inode.list(w, name, numLinksTable, numLinks, listSelector)
}

func (inode *SymlinkInode) Write(name string) error {
	return inode.write(name)
}

func (inode *SymlinkInode) WriteMetadata(name string) error {
	return inode.writeMetadata(name)
}

type SpecialInode struct {
	Mode             FileMode
	Uid              uint32
	Gid              uint32
	MtimeNanoSeconds int32
	MtimeSeconds     int64
	Rdev             uint64
}

func (inode *SpecialInode) GetUid() uint32 {
	return inode.Uid
}

func (inode *SpecialInode) GetGid() uint32 {
	return inode.Gid
}

func (inode *SpecialInode) List(w io.Writer, name string,
	numLinksTable NumLinksTable, numLinks int,
	listSelector ListSelector, filter *filter.Filter) error {
	return inode.list(w, name, numLinksTable, numLinks, listSelector)
}

func (inode *SpecialInode) Write(name string) error {
	return inode.write(name)
}

func (inode *SpecialInode) WriteMetadata(name string) error {
	return inode.writeMetadata(name)
}

type FileMode uint32

func (mode FileMode) String() string {
	return mode.string()
}

func CompareFileSystems(left, right *FileSystem, logWriter io.Writer) bool {
	return compareFileSystems(left, right, logWriter)
}

func CompareDirectoryInodes(left, right *DirectoryInode,
	logWriter io.Writer) bool {
	return compareDirectoryInodes(left, right, logWriter)
}

func CompareDirectoriesMetadata(left, right *DirectoryInode,
	logWriter io.Writer) bool {
	return compareDirectoriesMetadata(left, right, logWriter)
}

func CompareDirectoryEntries(left, right *DirectoryEntry,
	logWriter io.Writer) bool {
	return compareDirectoryEntries(left, right, logWriter)
}

func CompareInodes(left, right GenericInode, logWriter io.Writer) (
	sameType, sameMetadata, sameData bool) {
	return compareInodes(left, right, logWriter)
}

func CompareRegularInodes(left, right *RegularInode, logWriter io.Writer) bool {
	return compareRegularInodes(left, right, logWriter)
}

func CompareRegularInodesMetadata(left, right *RegularInode,
	logWriter io.Writer) bool {
	return compareRegularInodesMetadata(left, right, logWriter)
}

func CompareRegularInodesData(left, right *RegularInode,
	logWriter io.Writer) bool {
	return compareRegularInodesData(left, right, logWriter)
}

func CompareSymlinkInodes(left, right *SymlinkInode, logWriter io.Writer) bool {
	return compareSymlinkInodes(left, right, logWriter)
}

func CompareSymlinkInodesMetadata(left, right *SymlinkInode,
	logWriter io.Writer) bool {
	return compareSymlinkInodesMetadata(left, right, logWriter)
}

func CompareSymlinkInodesData(left, right *SymlinkInode,
	logWriter io.Writer) bool {
	return compareSymlinkInodesData(left, right, logWriter)
}

func CompareSpecialInodes(left, right *SpecialInode, logWriter io.Writer) bool {
	return compareSpecialInodes(left, right, logWriter)
}

func CompareSpecialInodesMetadata(left, right *SpecialInode,
	logWriter io.Writer) bool {
	return compareSpecialInodesMetadata(left, right, logWriter)
}

func CompareSpecialInodesData(left, right *SpecialInode,
	logWriter io.Writer) bool {
	return compareSpecialInodesData(left, right, logWriter)
}

func ForceWriteMetadata(inode GenericInode, name string) error {
	return forceWriteMetadata(inode, name)
}
<commit_msg>Sort lib/filesystem.FileSystem methods.<commit_after>package filesystem

import (
	"fmt"
	"github.com/Symantec/Dominator/lib/filter"
	"github.com/Symantec/Dominator/lib/format"
	"github.com/Symantec/Dominator/lib/hash"
	"io"
)

type NumLinksTable map[uint64]int

type ListSelector uint8

const (
	ListSelectSkipMode = 1 << iota
	ListSelectSkipNumLinks
	ListSelectSkipUid
	ListSelectSkipGid
	ListSelectSkipSizeDevnum
	ListSelectSkipMtime
	ListSelectSkipName
	ListSelectSkipData

	ListSelectAll = 0
)

type GenericInode interface {
	// GetUid() and GetGid() may be added if needed.
	//GetUid() uint32
	//GetGid() uint32
	List(w io.Writer, name string, numLinksTable NumLinksTable,
		numLinks int, listSelector ListSelector, filter *filter.Filter) error
	WriteMetadata(name string) error
}

type InodeTable map[uint64]GenericInode
type InodeToFilenamesTable map[uint64][]string
type FilenameToInodeTable map[string]uint64
type HashToInodesTable map[hash.Hash][]uint64

type FileSystem struct {
	InodeTable               InodeTable
	inodeToFilenamesTable    InodeToFilenamesTable
	filenameToInodeTable     FilenameToInodeTable
	hashToInodesTable        HashToInodesTable
	NumRegularInodes         uint64
	TotalDataBytes           uint64
	numComputedRegularInodes *uint64
	DirectoryCount           uint64
	DirectoryInode
}

func Decode(reader io.Reader) (*FileSystem, error) {
	return decode(reader)
}

func (fs *FileSystem) ComputeTotalDataBytes() {
	fs.computeTotalDataBytes()
}

func (fs *FileSystem) Encode(writer io.Writer) error {
	return fs.encode(writer)
}

func (fs *FileSystem) FilenameToInodeTable() FilenameToInodeTable {
	return fs.buildFilenameToInodeTable()
}

func (fs *FileSystem) Filter(filter *filter.Filter) *FileSystem {
	return fs.filter(filter)
}

func (fs *FileSystem) HashToInodesTable() HashToInodesTable {
	return fs.buildHashToInodesTable()
}

func (fs *FileSystem) InodeToFilenamesTable() InodeToFilenamesTable {
	return fs.buildInodeToFilenamesTable()
}

func (fs *FileSystem) List(w io.Writer) error {
	return fs.list(w, ListSelectAll, nil)
}

func (fs *FileSystem) Listf(w io.Writer, listSelector ListSelector,
	filter *filter.Filter) error {
	return fs.list(w, listSelector, filter)
}

func (fs *FileSystem) NumComputedRegularInodes() uint64 {
	return fs.computeNumComputedRegularInodes()
}

func (fs *FileSystem) RebuildInodePointers() error {
	return fs.rebuildInodePointers()
}

func (fs *FileSystem) String() string {
	return fmt.Sprintf("Tree: %d inodes, total file size: %s, number of regular inodes: %d",
		len(fs.InodeTable),
		format.FormatBytes(fs.TotalDataBytes),
		fs.NumRegularInodes)
}

type DirectoryInode struct {
	EntryList     []*DirectoryEntry
	EntriesByName map[string]*DirectoryEntry
	Mode          FileMode
	Uid           uint32
	Gid           uint32
}

func (directory *DirectoryInode) BuildEntryMap() {
	directory.buildEntryMap()
}

func (inode *DirectoryInode) GetUid() uint32 {
	return inode.Uid
}

func (inode *DirectoryInode) GetGid() uint32 {
	return inode.Gid
}

func (inode *DirectoryInode) List(w io.Writer, name string,
	numLinksTable NumLinksTable, numLinks int,
	listSelector ListSelector, filter *filter.Filter) error {
	return inode.list(w, name, numLinksTable, numLinks, listSelector, filter)
}

func (inode *DirectoryInode) Write(name string) error {
	return inode.write(name)
}

func (inode *DirectoryInode) WriteMetadata(name string) error {
	return inode.writeMetadata(name)
}

type DirectoryEntry struct {
	Name        string
	InodeNumber uint64
	inode       GenericInode // Keep private to avoid encoding/transmission.
}

func (dirent *DirectoryEntry) Inode() GenericInode {
	return dirent.inode
}

func (dirent *DirectoryEntry) SetInode(inode GenericInode) {
	dirent.inode = inode
}

func (dirent *DirectoryEntry) String() string {
	return dirent.Name
}

type RegularInode struct {
	Mode             FileMode
	Uid              uint32
	Gid              uint32
	MtimeNanoSeconds int32
	MtimeSeconds     int64
	Size             uint64
	Hash             hash.Hash
}

func (inode *RegularInode) GetUid() uint32 {
	return inode.Uid
}

func (inode *RegularInode) GetGid() uint32 {
	return inode.Gid
}

func (inode *RegularInode) List(w io.Writer, name string,
	numLinksTable NumLinksTable, numLinks int,
	listSelector ListSelector, filter *filter.Filter) error {
	return inode.list(w, name, numLinksTable, numLinks, listSelector)
}

func (inode *RegularInode) WriteMetadata(name string) error {
	return inode.writeMetadata(name)
}

type ComputedRegularInode struct {
	Mode   FileMode
	Uid    uint32
	Gid    uint32
	Source string
}

func (inode *ComputedRegularInode) List(w io.Writer, name string,
	numLinksTable NumLinksTable, numLinks int,
	listSelector ListSelector, filter *filter.Filter) error {
	return inode.list(w, name, numLinksTable, numLinks, listSelector)
}

func (inode *ComputedRegularInode) WriteMetadata(name string) error {
	panic("cannot write metadata for computed file: " + name)
}

type SymlinkInode struct {
	Uid     uint32
	Gid     uint32
	Symlink string
}

func (inode *SymlinkInode) GetUid() uint32 {
	return inode.Uid
}

func (inode *SymlinkInode) GetGid() uint32 {
	return inode.Gid
}

func (inode *SymlinkInode) List(w io.Writer, name string,
	numLinksTable NumLinksTable, numLinks int,
	listSelector ListSelector, filter *filter.Filter) error {
	return inode.list(w, name, numLinksTable, numLinks, listSelector)
}

func (inode *SymlinkInode) Write(name string) error {
	return inode.write(name)
}

func (inode *SymlinkInode) WriteMetadata(name string) error {
	return inode.writeMetadata(name)
}

type SpecialInode struct {
	Mode             FileMode
	Uid              uint32
	Gid              uint32
	MtimeNanoSeconds int32
	MtimeSeconds     int64
	Rdev             uint64
}

func (inode *SpecialInode) GetUid() uint32 {
	return inode.Uid
}

func (inode *SpecialInode) GetGid() uint32 {
	return inode.Gid
}

func (inode *SpecialInode) List(w io.Writer, name string,
	numLinksTable NumLinksTable, numLinks int,
	listSelector ListSelector, filter *filter.Filter) error {
	return inode.list(w, name, numLinksTable, numLinks, listSelector)
}

func (inode *SpecialInode) Write(name string) error {
	return inode.write(name)
}

func (inode *SpecialInode) WriteMetadata(name string) error {
	return inode.writeMetadata(name)
}

type FileMode uint32

func (mode FileMode) String() string {
	return mode.string()
}

func CompareFileSystems(left, right *FileSystem, logWriter io.Writer) bool {
	return compareFileSystems(left, right, logWriter)
}

func CompareDirectoryInodes(left, right *DirectoryInode,
	logWriter io.Writer) bool {
	return compareDirectoryInodes(left, right, logWriter)
}

func CompareDirectoriesMetadata(left, right *DirectoryInode,
	logWriter io.Writer) bool {
	return compareDirectoriesMetadata(left, right, logWriter)
}

func CompareDirectoryEntries(left, right *DirectoryEntry,
	logWriter io.Writer) bool {
	return compareDirectoryEntries(left, right, logWriter)
}

func CompareInodes(left, right GenericInode, logWriter io.Writer) (
	sameType, sameMetadata, sameData bool) {
	return compareInodes(left, right, logWriter)
}

func CompareRegularInodes(left, right *RegularInode, logWriter io.Writer) bool {
	return compareRegularInodes(left, right, logWriter)
}

func CompareRegularInodesMetadata(left, right *RegularInode,
	logWriter io.Writer) bool {
	return compareRegularInodesMetadata(left, right, logWriter)
}

func CompareRegularInodesData(left, right *RegularInode,
	logWriter io.Writer) bool {
	return compareRegularInodesData(left, right, logWriter)
}

func CompareSymlinkInodes(left, right *SymlinkInode, logWriter io.Writer) bool {
	return compareSymlinkInodes(left, right, logWriter)
}

func CompareSymlinkInodesMetadata(left, right *SymlinkInode,
	logWriter io.Writer) bool {
	return compareSymlinkInodesMetadata(left, right, logWriter)
}

func CompareSymlinkInodesData(left, right *SymlinkInode,
	logWriter io.Writer) bool {
	return compareSymlinkInodesData(left, right, logWriter)
}

func CompareSpecialInodes(left, right *SpecialInode, logWriter io.Writer) bool {
	return compareSpecialInodes(left, right, logWriter)
}

func CompareSpecialInodesMetadata(left, right *SpecialInode,
	logWriter io.Writer) bool {
	return compareSpecialInodesMetadata(left, right, logWriter)
}

func CompareSpecialInodesData(left, right *SpecialInode,
	logWriter io.Writer) bool {
	return compareSpecialInodesData(left, right, logWriter)
}

func ForceWriteMetadata(inode GenericInode, name string) error {
	return forceWriteMetadata(inode, name)
}
<|endoftext|>


<commit_before>package cmd

import (
	"errors"
	"fmt"
	"fuse/pkg/kubectl"
	"github.com/spf13/cobra"
	"os"
	"time"
)

var (
	applyCmd = &cobra.Command{
		Use:   "apply",
		Short: "Perform safe deployment to Kubernetes cluster",
		Long:  `Apply new configuration to Kubernetes cluster and monitor release delivery`,
		RunE:  applyCmdHandler,
		PreRunE: func(cmd *cobra.Command, args []string) error {
			if configurationYaml == "" {
				return errors.New("mandatory configuration spec filename is not provided")
			}
			return nil
		},
	}

	configurationYaml string
	clusterTimeout    time.Duration
)

func init() {
	applyCmd.Flags().StringVarP(&configurationYaml, "configuration", "f", "", "Rollout configuration spec file (yaml), mandatory")
	applyCmd.MarkFlagRequired("configuration")
	applyCmd.MarkFlagFilename("configuration", "yml", "yaml")

	applyCmd.Flags().DurationVarP(&clusterTimeout, "rollout-timeout", "t", 2*time.Minute, "Rollout timeout")
	RootCmd.AddCommand(applyCmd)
}

//
func getRolledList(specList *[]kubectl.Deployment, skipMissing bool) (*[]kubectl.Deployment, error) {
	rolledList := make([]kubectl.Deployment, 0)
	for _, spec := range *specList {
		// fetch data from cluster
		r, err := kubectl.CommandDeploymentInfo(spec.GetNamespace(), spec.GetName()).RunAndParseFirst()
		if err != nil && !skipMissing {
			return nil, err
		}
		if r == nil {
			continue
		}

		// convert to Deployment
		d, err := r.ToDeployment()
		if err != nil && !skipMissing {
			return nil, err
		}
		if d == nil {
			continue
		}

		rolledList = append(rolledList, *d)
	}
	return &rolledList, nil
}

// Load and check configuration (yaml file), parse and return all
// deployments defined in configuration file
func initRollOut() (*[]kubectl.Deployment, error) {
	// parsing provided configuration
	fullResourceList, err := kubectl.ParseLocalFile(configurationYaml)
	if err != nil {
		return nil, err
	}

	// filtering deployments, if no deployments found it's an error
	newConfigurationList := fullResourceList.FilteredByKind(kubectl.KindDeployment).ToDeploymentList()
	if len(newConfigurationList) == 0 {
		return nil, errors.New("no Deployment resources found in configuration")
	}

	return &newConfigurationList, nil
}

// Start deploy process / apply new configuration to cluster and display output
func applyRollOut(specList *[]kubectl.Deployment) error {
	stdout, err := kubectl.CommandApply(configurationYaml).RunPlain()
	fmt.Println(string(stdout)) // in case of error, display output
	if err != nil {
		return err
	}

	return nil
}

// Monitor configuration delivery, all unavailable replicas of each deployment
// should be 0. Wait until timeout.
func monitorRollOut(specList *[]kubectl.Deployment) (bool, error) {
	fmt.Printf("==> Starting rollout monitoring, timeout is %v seconds\n", clusterTimeout)
	willExpireAt := time.Now().Add(clusterTimeout)

	for {
		// make initial delay..
		time.Sleep(5 * time.Second)
		rolledList, err := getRolledList(specList, true)
		if err != nil {
			return false, err
		}

		// 1) timeout reached?
		if time.Now().After(willExpireAt) {
			break
		}

		// 2) every deployment defined in spec registered in cluster?
		if len(*specList) != len(*rolledList) {
			fmt.Printf("===> Waiting for deployment registration, %d to go...\n", len(*specList)-len(*rolledList))
			continue
		}

		// 3) every deployment is rolled out?
		isRolledOut := true
		for _, d := range *rolledList {
			fmt.Printf("===> Deployment: %s, %s\n", d.GetKey(), d.GetStatusString())
			if !d.IsReady() {
				isRolledOut = false
			}
		}

		// 4) if rolled out, stop..
		if isRolledOut {
			fmt.Println("==> Rollout done!")
			return true, nil
		}
	}

	// timeout reached, aborting...
	fmt.Println("===> Rollout failed!")
	return false, nil
}

// Finalize delivery process, either do nothing or display logs for each pod of each deployment
// in order to have information about broken delivery
func finalizeRollOut(specList *[]kubectl.Deployment, isRolledOut bool) error {

	// display logs for each pod attached to deployment list
	fmt.Println("==> Fetching logs...")
	rolledList, err := getRolledList(specList, false)
	if err != nil {
		return err
	}

	for _, d := range *rolledList {
		// get list of pods connected to deployment
		rlist, err := kubectl.CommandPodListBySelector(d.GetNamespace(), d.GetSelector()).RunAndParse()
		if err != nil {
			return err
		}

		// display logs for each pod
		plist := rlist.FilteredByKind(kubectl.KindPod).ToPodList()
		for _, pod := range plist {
			stdout, err := kubectl.CommandPodLogs(pod.GetNamespace(), pod.GetName()).RunPlain()
			fmt.Printf("===> Deployment: %s, Pod: %s:\n", d.GetKey(), pod.GetKey())
			fmt.Println(string(stdout))

			if err != nil {
				return err
			}
		}
	}

	// if deploy successful do nothing..
	if isRolledOut {
		return nil
	}

	// error registered, if deployment has > 1 replica sets, rollback it
	fmt.Println("==> Rollout failed, starting undo process...")
	for _, d := range *rolledList {
		// get list of replica sets connected to deployment
		rlist, err := kubectl.CommandReplicaSetListBySelector(d.GetNamespace(), d.GetSelector()).RunAndParse()
		if err != nil {
			return err
		}

		// if deployment has previous configuration - perform "rollout undo"
		// otherwise - do nothing...
		if len(rlist) > 1 {
			stdout, err := kubectl.CommandRollback(d.GetNamespace(), d.GetKind(), d.GetName()).RunPlain()
			fmt.Printf("===> Deployment: %s - rolled back to previous release\n", d.GetKey())
			fmt.Println(string(stdout))
			if err != nil {
				return err
			}

		} else {
			fmt.Printf("===> Deployment: %s - no rollback history available", d.GetKey())
		}
	}

	return nil
}

// command handler
func applyCmdHandler(cmd *cobra.Command, args []string) error {
	var specList *[]kubectl.Deployment
	var err error
	var isRolledOut bool

	// load and parse configuration spec
	if specList, err = initRollOut(); err != nil {
		return err
	}

	// apply configuration / start rollout
	if err = applyRollOut(specList); err != nil {
		return err
	}

	// monitor rollout
	if isRolledOut, err = monitorRollOut(specList); err != nil {
		return err
	}

	// finalize deploy
	if err = finalizeRollOut(specList, isRolledOut); err != nil {
		return err
	}

	// signalize to CI/CD about final status
	if isRolledOut {
		os.Exit(0)
	} else {
		os.Exit(1)
	}

	return nil
}
<commit_msg>skip pod if not received (cache/network issue)<commit_after>package cmd

import (
	"errors"
	"fmt"
	"fuse/pkg/kubectl"
	"github.com/spf13/cobra"
	"os"
	"time"
)

var (
	applyCmd = &cobra.Command{
		Use:   "apply",
		Short: "Perform safe deployment to Kubernetes cluster",
		Long:  `Apply new configuration to Kubernetes cluster and monitor release delivery`,
		RunE:  applyCmdHandler,
		PreRunE: func(cmd *cobra.Command, args []string) error {
			if configurationYaml == "" {
				return errors.New("mandatory configuration spec filename is not provided")
			}
			return nil
		},
	}

	configurationYaml string
	clusterTimeout    time.Duration
)

func init() {
	applyCmd.Flags().StringVarP(&configurationYaml, "configuration", "f", "", "Rollout configuration spec file (yaml), mandatory")
	applyCmd.MarkFlagRequired("configuration")
	applyCmd.MarkFlagFilename("configuration", "yml", "yaml")

	applyCmd.Flags().DurationVarP(&clusterTimeout, "rollout-timeout", "t", 2*time.Minute, "Rollout timeout")
	RootCmd.AddCommand(applyCmd)
}

//
func getRolledList(specList *[]kubectl.Deployment, skipMissing bool) (*[]kubectl.Deployment, error) {
	rolledList := make([]kubectl.Deployment, 0)
	for _, spec := range *specList {
		// fetch data from cluster
		r, err := kubectl.CommandDeploymentInfo(spec.GetNamespace(), spec.GetName()).RunAndParseFirst()
		if err != nil && !skipMissing {
			return nil, err
		}
		if r == nil {
			continue
		}

		// convert to Deployment
		d, err := r.ToDeployment()
		if err != nil && !skipMissing {
			return nil, err
		}
		if d == nil {
			continue
		}

		rolledList = append(rolledList, *d)
	}
	return &rolledList, nil
}

// Load and check configuration (yaml file), parse and return all
// deployments defined in configuration file
func initRollOut() (*[]kubectl.Deployment, error) {
	// parsing provided configuration
	fullResourceList, err := kubectl.ParseLocalFile(configurationYaml)
	if err != nil {
		return nil, err
	}

	// filtering deployments, if no deployments found it's an error
	newConfigurationList := fullResourceList.FilteredByKind(kubectl.KindDeployment).ToDeploymentList()
	if len(newConfigurationList) == 0 {
		return nil, errors.New("no Deployment resources found in configuration")
	}

	return &newConfigurationList, nil
}

// Start deploy process / apply new configuration to cluster and display output
func applyRollOut(specList *[]kubectl.Deployment) error {
	stdout, err := kubectl.CommandApply(configurationYaml).RunPlain()
	fmt.Println(string(stdout)) // in case of error, display output
	if err != nil {
		return err
	}

	return nil
}

// Monitor configuration delivery, all unavailable replicas of each deployment
// should be 0. Wait until timeout.
func monitorRollOut(specList *[]kubectl.Deployment) (bool, error) {
	fmt.Printf("==> Starting rollout monitoring, timeout is %v seconds\n", clusterTimeout)
	willExpireAt := time.Now().Add(clusterTimeout)

	for {
		// make initial delay..
		time.Sleep(5 * time.Second)
		rolledList, err := getRolledList(specList, true)
		if err != nil {
			return false, err
		}

		// 1) timeout reached?
		if time.Now().After(willExpireAt) {
			break
		}

		// 2) every deployment defined in spec registered in cluster?
		if len(*specList) != len(*rolledList) {
			fmt.Printf("===> Waiting for deployment registration, %d to go...\n", len(*specList)-len(*rolledList))
			continue
		}

		// 3) every deployment is rolled out?
		isRolledOut := true
		for _, d := range *rolledList {
			fmt.Printf("===> Deployment: %s, %s\n", d.GetKey(), d.GetStatusString())
			if !d.IsReady() {
				isRolledOut = false
			}
		}

		// 4) if rolled out, stop..
		if isRolledOut {
			fmt.Println("==> Rollout done!")
			return true, nil
		}
	}

	// timeout reached, aborting...
	fmt.Println("===> Rollout failed!")
	return false, nil
}

// Finalize delivery process, either do nothing or display logs for each pod of each deployment
// in order to have information about broken delivery
func finalizeRollOut(specList *[]kubectl.Deployment, isRolledOut bool) error {

	// display logs for each pod attached to deployment list
	fmt.Println("==> Fetching logs...")
	rolledList, err := getRolledList(specList, false)
	if err != nil {
		return err
	}

	for _, d := range *rolledList {
		// get list of pods connected to deployment
		rlist, _ := kubectl.CommandPodListBySelector(d.GetNamespace(), d.GetSelector()).RunAndParse()
		if rlist == nil {
			continue
		}

		// display logs for each pod
		plist := rlist.FilteredByKind(kubectl.KindPod).ToPodList()
		for _, pod := range plist {
			stdout, err := kubectl.CommandPodLogs(pod.GetNamespace(), pod.GetName()).RunPlain()
			fmt.Printf("===> Deployment: %s, Pod: %s:\n", d.GetKey(), pod.GetKey())
			fmt.Println(string(stdout))

			if err != nil {
				return err
			}
		}
	}

	// if deploy successful do nothing..
	if isRolledOut {
		return nil
	}

	// error registered, if deployment has > 1 replica sets, rollback it
	fmt.Println("==> Rollout failed, starting undo process...")
	for _, d := range *rolledList {
		// get list of replica sets connected to deployment
		rlist, err := kubectl.CommandReplicaSetListBySelector(d.GetNamespace(), d.GetSelector()).RunAndParse()
		if err != nil {
			return err
		}

		// if deployment has previous configuration - perform "rollout undo"
		// otherwise - do nothing...
		if len(rlist) > 1 {
			stdout, err := kubectl.CommandRollback(d.GetNamespace(), d.GetKind(), d.GetName()).RunPlain()
			fmt.Printf("===> Deployment: %s - rolled back to previous release\n", d.GetKey())
			fmt.Println(string(stdout))
			if err != nil {
				return err
			}

		} else {
			fmt.Printf("===> Deployment: %s - no rollback history available", d.GetKey())
		}
	}

	return nil
}

// command handler
func applyCmdHandler(cmd *cobra.Command, args []string) error {
	var specList *[]kubectl.Deployment
	var err error
	var isRolledOut bool

	// load and parse configuration spec
	if specList, err = initRollOut(); err != nil {
		return err
	}

	// apply configuration / start rollout
	if err = applyRollOut(specList); err != nil {
		return err
	}

	// monitor rollout
	if isRolledOut, err = monitorRollOut(specList); err != nil {
		return err
	}

	// finalize deploy
	if err = finalizeRollOut(specList, isRolledOut); err != nil {
		return err
	}

	// signalize to CI/CD about final status
	if isRolledOut {
		os.Exit(0)
	} else {
		os.Exit(1)
	}

	return nil
}
<|endoftext|>