<commit_before>// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package harness

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/coreos/mantle/harness"
	"github.com/coreos/mantle/platform"
	"github.com/coreos/mantle/platform/machine/gcloud"
	"github.com/coreos/mantle/pluton"
	"github.com/coreos/mantle/pluton/spawn"
)

var bastionMachine platform.Machine

// Call this from main after setting all the global options. Tests are filtered
// by name based on the glob pattern given.
func RunSuite(pattern string) {
	Opts.GCEOptions.Options = &Opts.PlatformOptions

	tests, err := filterTests(Tests, pattern)
	if err != nil {
		fmt.Printf("Error filtering glob pattern: %v", err)
		os.Exit(1)

	}

	opts := harness.Options{
		OutputDir: Opts.OutputDir,
		Parallel:  Opts.Parallel,
		Verbose:   true,
	}
	suite := harness.NewSuite(opts, tests)

	// setup a node for in cluster services not tied to individual test life-cycles
	var cloud platform.Cluster
	switch Opts.CloudPlatform {
	case "gce":
		var bastionDir = filepath.Join(Opts.OutputDir, "bastion")

		err := os.MkdirAll(bastionDir, 0777)
		if err != nil {
			fmt.Printf("setting up bastion cluster: %v\n", err)
			os.Exit(1)
		}

		cloud, err = gcloud.NewCluster(&Opts.GCEOptions, bastionDir)
		if err != nil {
			fmt.Printf("setting up bastion cluster: %v\n", err)
			os.Exit(1)
		}

		bastionMachine, err = cloud.NewMachine("")
		if err != nil {
			fmt.Printf("setting up bastion cluster: %v\n", err)

			cloud.Destroy()
			os.Exit(1)
		}
	default:
		fmt.Printf("invalid cloud platform %v\n", Opts.CloudPlatform)
		os.Exit(1)
	}

	if err := suite.Run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		fmt.Println("FAIL")

		cloud.Destroy()
		os.Exit(1)
	}
	fmt.Println("PASS")

	cloud.Destroy()
	os.Exit(0)
}

func filterTests(tests harness.Tests, pattern string) (harness.Tests, error) {
	var filteredTests = make(harness.Tests)
	for name, t := range tests {
		match, err := filepath.Match(pattern, name)
		if err != nil {
			return nil, err
		}
		if !match {
			continue
		}
		filteredTests[name] = t
	}
	return filteredTests, nil
}

// RunTest is called inside the closure passed into the harness. Currently only
// GCE is supported, no reason this can't change
func runTest(t pluton.Test, h *harness.H) {
	h.Parallel()

	var cloud platform.Cluster
	var err error

	switch Opts.CloudPlatform {
	case "gce":
		cloud, err = gcloud.NewCluster(&Opts.GCEOptions, h.OutputDir())
	default:
		err = fmt.Errorf("invalid cloud platform %v", Opts.CloudPlatform)
	}

	if err != nil {
		h.Fatalf("Cluster failed: %v", err)
	}
	defer func() {
		if err := cloud.Destroy(); err != nil {
			h.Logf("cluster.Destroy(): %v", err)
		}
	}()

	config := spawn.BootkubeConfig{
		ImageRepo:      Opts.BootkubeRepo,
		ImageTag:       Opts.BootkubeTag,
		ScriptDir:      Opts.BootkubeScriptDir,
		InitialWorkers: t.Options.InitialWorkers,
		InitialMasters: t.Options.InitialMasters,
		SelfHostEtcd:   t.Options.SelfHostEtcd,
	}

	c, err := spawn.MakeBootkubeCluster(cloud, config, bastionMachine)
	if err != nil {
		h.Fatalf("creating cluster: %v", err)
	}

	// TODO(pb): evidence that harness and spawn should be the same package?
	c.H = h

	t.Run(c)
}
<commit_msg>pluton/harness: use larger machine for bastion<commit_after>// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package harness

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/coreos/mantle/harness"
	"github.com/coreos/mantle/platform"
	"github.com/coreos/mantle/platform/machine/gcloud"
	"github.com/coreos/mantle/pluton"
	"github.com/coreos/mantle/pluton/spawn"
)

var bastionMachine platform.Machine

// Call this from main after setting all the global options. Tests are filtered
// by name based on the glob pattern given.
func RunSuite(pattern string) {
	Opts.GCEOptions.Options = &Opts.PlatformOptions

	tests, err := filterTests(Tests, pattern)
	if err != nil {
		fmt.Printf("Error filtering glob pattern: %v", err)
		os.Exit(1)

	}

	opts := harness.Options{
		OutputDir: Opts.OutputDir,
		Parallel:  Opts.Parallel,
		Verbose:   true,
	}
	suite := harness.NewSuite(opts, tests)

	// setup a node for in cluster services not tied to individual test life-cycles
	var cloud platform.Cluster
	switch Opts.CloudPlatform {
	case "gce":
		var bastionDir = filepath.Join(Opts.OutputDir, "bastion")

		err := os.MkdirAll(bastionDir, 0777)
		if err != nil {
			fmt.Printf("setting up bastion cluster: %v\n", err)
			os.Exit(1)
		}

		// override machine size for bastion
		bastionOpts := Opts.GCEOptions
		bastionOpts.MachineType = "n1-standard-4"

		cloud, err = gcloud.NewCluster(&bastionOpts, bastionDir)
		if err != nil {
			fmt.Printf("setting up bastion cluster: %v\n", err)
			os.Exit(1)
		}

		bastionMachine, err = cloud.NewMachine("")
		if err != nil {
			fmt.Printf("setting up bastion cluster: %v\n", err)

			cloud.Destroy()
			os.Exit(1)
		}

	default:
		fmt.Printf("invalid cloud platform %v\n", Opts.CloudPlatform)
		os.Exit(1)
	}

	if err := suite.Run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		fmt.Println("FAIL")

		cloud.Destroy()
		os.Exit(1)
	}
	fmt.Println("PASS")

	cloud.Destroy()
	os.Exit(0)
}

func filterTests(tests harness.Tests, pattern string) (harness.Tests, error) {
	var filteredTests = make(harness.Tests)
	for name, t := range tests {
		match, err := filepath.Match(pattern, name)
		if err != nil {
			return nil, err
		}
		if !match {
			continue
		}
		filteredTests[name] = t
	}
	return filteredTests, nil
}

// RunTest is called inside the closure passed into the harness. Currently only
// GCE is supported, no reason this can't change
func runTest(t pluton.Test, h *harness.H) {
	h.Parallel()

	var cloud platform.Cluster
	var err error

	switch Opts.CloudPlatform {
	case "gce":
		cloud, err = gcloud.NewCluster(&Opts.GCEOptions, h.OutputDir())
	default:
		err = fmt.Errorf("invalid cloud platform %v", Opts.CloudPlatform)
	}

	if err != nil {
		h.Fatalf("Cluster failed: %v", err)
	}
	defer func() {
		if err := cloud.Destroy(); err != nil {
			h.Logf("cluster.Destroy(): %v", err)
		}
	}()

	config := spawn.BootkubeConfig{
		ImageRepo:      Opts.BootkubeRepo,
		ImageTag:       Opts.BootkubeTag,
		ScriptDir:      Opts.BootkubeScriptDir,
		InitialWorkers: t.Options.InitialWorkers,
		InitialMasters: t.Options.InitialMasters,
		SelfHostEtcd:   t.Options.SelfHostEtcd,
	}

	c, err := spawn.MakeBootkubeCluster(cloud, config, bastionMachine)
	if err != nil {
		h.Fatalf("creating cluster: %v", err)
	}

	// TODO(pb): evidence that harness and spawn should be the same package?
	c.H = h

	t.Run(c)
}
<|endoftext|>


<commit_before>package pprint

import (
	"io"
)

// toStream recursively converts a document into the stream elements
// we'll be using.  We use channels to organize the coroutines.
func toStream(document Element) <-chan streamElt {
	ch := make(chan streamElt)
	go func() {
		visitElement(document, ch)
	}()
	return ch
}

func visitElement(document Element, out chan<- streamElt) {
	switch doc := document.(type) {
	case *Text:
		out <- &textElt{elt{-1}, doc.text}
	case *Cond:
		out <- &condElt{elt{-1}, doc.small, doc.continuation, doc.tail}
	case *LineBreak:
		out <- &crlfElt{elt{-1}}
	case *Concat:
		for _, elt := range doc.children {
			visitElement(elt, out)
		}
	case *Group:
		out <- &gbegElt{elt{-1}}
		visitElement(doc.child, out)
		out <- &gendElt{elt{-1}}
	case *Nest:
		out <- &nbegElt{elt{-1}}
		out <- &gbegElt{elt{-1}}
		visitElement(doc.child, out)
		out <- &gendElt{elt{-1}}
		out <- &nendElt{elt{-1}}
	default:
		panic("Couldn't understand document type")
	}
}

// annotateLastChar is the next step; it takes the stream elements
// from `toStream` and adds information about the horizontal position
// of their last character.  This is not possible with NBeg and GBeg
// elements as we haven't got enough information yet.
func annotateLastChar(in <-chan streamElt) <-chan streamElt {
	ch := make(chan streamElt)
	go func() {
		position := 0
		for {
			select {
			case elt, ok := <-in:
				if !ok {
					return
				}
				switch elt := elt.(type) {
				case *textElt:
					position += len(elt.payload)
					elt.hpos = position
					ch <- elt
				case *condElt:
					position += len(elt.small)
					elt.hpos = position
					ch <- elt
				case *crlfElt:
					elt.hpos = position
					ch <- elt
				case *gbegElt, *nbegElt:
					// Don't have enough information yet to do this
					// accurately.
					ch <- elt
				case *gendElt:
					elt.hpos = position
					ch <- elt
				case *nendElt:
					elt.hpos = position
					ch <- elt
				}
			}
		}
	}()
	return ch
}

type lookaheadStack []streamElt

// annotateGBeg is the next step; we take the horizontal position
// information gotten from `annotateLastChar` and compute the `hpos`
// for GBeg elements.  We don't need to do it for NBeg, but for GBeg
// it matters for linebreaks.
func annotateGBeg(in <-chan streamElt) <-chan streamElt {
	ch := make(chan streamElt)
	go func() {
		var lookahead []lookaheadStack
		for {
			select {
			case element, ok := <-in:
				if !ok {
					return
				}
				switch element := element.(type) {
				case *textElt, *condElt, *crlfElt, *nbegElt, *nendElt:
					if len(lookahead) == 0 {
						ch <- element
					} else {
						last := len(lookahead) - 1
						lookahead[last] = append(lookahead[last], element)
					}
				case *gbegElt:
					newList := make(lookaheadStack, 0)
					lookahead = append(lookahead, newList)
				case *gendElt:
					last := len(lookahead) - 1
					top := lookahead[last]
					lookahead = lookahead[0:last]
					if len(lookahead) == 0 {
						// this, then, was the topmost stack
						ch <- &gbegElt{elt{element.hpos}}
						for _, e := range top {
							ch <- e
						}
						ch <- element
					} else {
						newtop := lookahead[last-1]
						newtop = append(newtop, &gbegElt{elt{element.hpos}})
						newtop = append(newtop, top...)
						newtop = append(newtop, element)
						lookahead[last-1] = newtop
					}
				}
			}
		}
	}()
	return ch
}

// Kiselyov's original formulation includes an alternate third phase
// which limits lookahead to the width of the page.  This is difficult
// for us because we don't guarantee docs are of nonzero length,
// although that could be finessed, and also it adds extra complexity
// for minimal benefit.  This implementation skips it.

// The final phase is to compute output.  Each time we see a
// `gbeg_element_t`, we can compare its `hpos` with `rightEdge` to see
// whether it'll fit without breaking.  If it does fit, increment
// `fittingElements` and proceed, which will cause the logic for
// `text_element_t` and `cond_element_t` to just append stuff without
// line breaks.  If it doesn't fit, set `fittingElements` to 0, which
// will cause `cond_element_t` to do line breaks.  When we do a line
// break, we need to compute where the new right edge of the 'page'
// would be in the context of the original stream; so if we saw a
// `cond_element_t` with `e.hpos` of 300 (meaning it ends at
// horizontal position 300), the new right edge would be 300 -
// indentation + page width.
func output(in <-chan streamElt, width int, output io.Writer) error {
	fittingElements := 0
	rightEdge := width
	hpos := 0
	var indent []int
	for {
		select {
		case elt, ok := <-in:
			if !ok {
				return nil
			}
			switch elt := elt.(type) {
			case *textElt:
				_, err := output.Write(([]byte)(elt.payload))
				if err != nil {
					return err
				}
				hpos += len(elt.payload)
			case *condElt:
				if fittingElements == 0 {
					var currentIndent int
					if len(indent) == 0 {
						currentIndent = 0
					} else {
						currentIndent = indent[len(indent)-1]
					}
					_, err := output.Write(([]byte)(elt.tail))
					if err != nil {
						return err
					}
					_, err = output.Write(([]byte)("\n"))
					if err != nil {
						return err
					}
					for i := 0; i < currentIndent; i++ {
						_, err := output.Write(([]byte)(" "))
						if err != nil {
							return err
						}
					}
					_, err = output.Write(([]byte)(elt.cont))
					if err != nil {
						return err
					}
					fittingElements = 0
					hpos = currentIndent + len(elt.cont)
					rightEdge = (width - hpos) + elt.hpos
				} else {
					_, err := output.Write(([]byte)(elt.small))
					if err != nil {
						return err
					}
					hpos += len(elt.small)
				}
			case *crlfElt:
				var currentIndent int
				if len(indent) == 0 {
					currentIndent = 0
				} else {
					currentIndent = indent[len(indent)-1]
				}
				_, err := output.Write(([]byte)("\n"))
				if err != nil {
					return err
				}
				for i := 0; i < currentIndent; i++ {
					_, err := output.Write(([]byte)(" "))
					if err != nil {
						return err
					}
				}
				fittingElements = 0
				hpos = currentIndent
				rightEdge = (width - hpos) + elt.hpos
			case *gbegElt:
				if fittingElements != 0 || elt.hpos <= rightEdge {
					fittingElements++
				} else {
					fittingElements = 0
				}
			case *gendElt:
				if fittingElements != 0 {
					fittingElements--
				}
			case *nbegElt:
				indent = append(indent, elt.hpos)
			case *nendElt:
				if len(indent) > 0 {
					indent = indent[0 : len(indent)-1]
				}
			}
		}
	}
}

// PrettyPrint prints `doc` to `out` assuming a right page edge of
// `width`.
func PrettyPrint(doc Element, width int, out io.Writer) error {
	return output(annotateGBeg(annotateLastChar(toStream(doc))), width, out)
}
<commit_msg>Use `io.WriteString` instead of doing it ourself.<commit_after>package pprint

import (
	"io"
	"strings"
)

// toStream recursively converts a document into the stream elements
// we'll be using.  We use channels to organize the coroutines.
func toStream(document Element) <-chan streamElt {
	ch := make(chan streamElt)
	go func() {
		visitElement(document, ch)
	}()
	return ch
}

func visitElement(document Element, out chan<- streamElt) {
	switch doc := document.(type) {
	case *Text:
		out <- &textElt{elt{-1}, doc.text}
	case *Cond:
		out <- &condElt{elt{-1}, doc.small, doc.continuation, doc.tail}
	case *LineBreak:
		out <- &crlfElt{elt{-1}}
	case *Concat:
		for _, elt := range doc.children {
			visitElement(elt, out)
		}
	case *Group:
		out <- &gbegElt{elt{-1}}
		visitElement(doc.child, out)
		out <- &gendElt{elt{-1}}
	case *Nest:
		out <- &nbegElt{elt{-1}}
		out <- &gbegElt{elt{-1}}
		visitElement(doc.child, out)
		out <- &gendElt{elt{-1}}
		out <- &nendElt{elt{-1}}
	default:
		panic("Couldn't understand document type")
	}
}

// annotateLastChar is the next step; it takes the stream elements
// from `toStream` and adds information about the horizontal position
// of their last character.  This is not possible with NBeg and GBeg
// elements as we haven't got enough information yet.
func annotateLastChar(in <-chan streamElt) <-chan streamElt {
	ch := make(chan streamElt)
	go func() {
		position := 0
		for {
			select {
			case elt, ok := <-in:
				if !ok {
					return
				}
				switch elt := elt.(type) {
				case *textElt:
					position += len(elt.payload)
					elt.hpos = position
					ch <- elt
				case *condElt:
					position += len(elt.small)
					elt.hpos = position
					ch <- elt
				case *crlfElt:
					elt.hpos = position
					ch <- elt
				case *gbegElt, *nbegElt:
					// Don't have enough information yet to do this
					// accurately.
					ch <- elt
				case *gendElt:
					elt.hpos = position
					ch <- elt
				case *nendElt:
					elt.hpos = position
					ch <- elt
				}
			}
		}
	}()
	return ch
}

type lookaheadStack []streamElt

// annotateGBeg is the next step; we take the horizontal position
// information gotten from `annotateLastChar` and compute the `hpos`
// for GBeg elements.  We don't need to do it for NBeg, but for GBeg
// it matters for linebreaks.
func annotateGBeg(in <-chan streamElt) <-chan streamElt {
	ch := make(chan streamElt)
	go func() {
		var lookahead []lookaheadStack
		for {
			select {
			case element, ok := <-in:
				if !ok {
					return
				}
				switch element := element.(type) {
				case *textElt, *condElt, *crlfElt, *nbegElt, *nendElt:
					if len(lookahead) == 0 {
						ch <- element
					} else {
						last := len(lookahead) - 1
						lookahead[last] = append(lookahead[last], element)
					}
				case *gbegElt:
					newList := make(lookaheadStack, 0)
					lookahead = append(lookahead, newList)
				case *gendElt:
					last := len(lookahead) - 1
					top := lookahead[last]
					lookahead = lookahead[0:last]
					if len(lookahead) == 0 {
						// this, then, was the topmost stack
						ch <- &gbegElt{elt{element.hpos}}
						for _, e := range top {
							ch <- e
						}
						ch <- element
					} else {
						newtop := lookahead[last-1]
						newtop = append(newtop, &gbegElt{elt{element.hpos}})
						newtop = append(newtop, top...)
						newtop = append(newtop, element)
						lookahead[last-1] = newtop
					}
				}
			}
		}
	}()
	return ch
}

// Kiselyov's original formulation includes an alternate third phase
// which limits lookahead to the width of the page.  This is difficult
// for us because we don't guarantee docs are of nonzero length,
// although that could be finessed, and also it adds extra complexity
// for minimal benefit.  This implementation skips it.

// The final phase is to compute output.  Each time we see a
// `gbeg_element_t`, we can compare its `hpos` with `rightEdge` to see
// whether it'll fit without breaking.  If it does fit, increment
// `fittingElements` and proceed, which will cause the logic for
// `text_element_t` and `cond_element_t` to just append stuff without
// line breaks.  If it doesn't fit, set `fittingElements` to 0, which
// will cause `cond_element_t` to do line breaks.  When we do a line
// break, we need to compute where the new right edge of the 'page'
// would be in the context of the original stream; so if we saw a
// `cond_element_t` with `e.hpos` of 300 (meaning it ends at
// horizontal position 300), the new right edge would be 300 -
// indentation + page width.
func output(in <-chan streamElt, width int, output io.Writer) error {
	fittingElements := 0
	rightEdge := width
	hpos := 0
	var indent []int
	for {
		select {
		case elt, ok := <-in:
			if !ok {
				return nil
			}
			switch elt := elt.(type) {
			case *textElt:
				_, err := io.WriteString(output, elt.payload)
				if err != nil {
					return err
				}
				hpos += len(elt.payload)
			case *condElt:
				if fittingElements == 0 {
					var currentIndent int
					if len(indent) == 0 {
						currentIndent = 0
					} else {
						currentIndent = indent[len(indent)-1]
					}
					_, err := io.WriteString(output, elt.tail)
					if err != nil {
						return err
					}
					_, err = io.WriteString(output, "\n")
					if err != nil {
						return err
					}
					_, err = io.WriteString(output, strings.Repeat(" ", currentIndent))
					if err != nil {
						return err
					}
					_, err = io.WriteString(output, elt.cont)
					if err != nil {
						return err
					}
					fittingElements = 0
					hpos = currentIndent + len(elt.cont)
					rightEdge = (width - hpos) + elt.hpos
				} else {
					_, err := io.WriteString(output, elt.small)
					if err != nil {
						return err
					}
					hpos += len(elt.small)
				}
			case *crlfElt:
				var currentIndent int
				if len(indent) == 0 {
					currentIndent = 0
				} else {
					currentIndent = indent[len(indent)-1]
				}
				_, err := io.WriteString(output, "\n")
				if err != nil {
					return err
				}
				_, err = io.WriteString(output, strings.Repeat(" ", currentIndent))
				if err != nil {
					return err
				}
				fittingElements = 0
				hpos = currentIndent
				rightEdge = (width - hpos) + elt.hpos
			case *gbegElt:
				if fittingElements != 0 || elt.hpos <= rightEdge {
					fittingElements++
				} else {
					fittingElements = 0
				}
			case *gendElt:
				if fittingElements != 0 {
					fittingElements--
				}
			case *nbegElt:
				indent = append(indent, elt.hpos)
			case *nendElt:
				if len(indent) > 0 {
					indent = indent[0 : len(indent)-1]
				}
			}
		}
	}
}

// PrettyPrint prints `doc` to `out` assuming a right page edge of
// `width`.
func PrettyPrint(doc Element, width int, out io.Writer) error {
	return output(annotateGBeg(annotateLastChar(toStream(doc))), width, out)
}
<|endoftext|>


<commit_before>package prefixedlog

import (
	"bytes"
	"fmt"
	"runtime"
	"sort"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
)

const (
	nocolor = 0
	red     = 31
	green   = 32
	yellow  = 33
	blue    = 34
	gray    = 37
)

var (
	baseTimestamp time.Time
	isTerminal    bool
)

func init() {
	baseTimestamp = time.Now()
	isTerminal = logrus.IsTerminal()
}

func miniTS() int {
	return int(time.Since(baseTimestamp) / time.Second)
}

type TextFormatter struct {
	// Set to true to bypass checking for a TTY before outputting colors.
	ForceColors bool

	// Force disabling colors.
	DisableColors bool

	// Disable timestamp logging. useful when output is redirected to logging
	// system that already adds timestamps.
	DisableTimestamp bool

	// Enable logging the full timestamp when a TTY is attached instead of just
	// the time passed since beginning of execution.
	FullTimestamp bool

	// TimestampFormat to use for display when a full timestamp is printed
	TimestampFormat string

	// The fields are sorted by default for a consistent output. For applications
	// that log extremely frequently and don't use the JSON formatter this may not
	// be desired.
	DisableSorting bool
}

func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	var keys []string = make([]string, 0, len(entry.Data))
	for k := range entry.Data {
		keys = append(keys, k)
	}

	if !f.DisableSorting {
		sort.Strings(keys)
	}

	b := &bytes.Buffer{}

	prefixFieldClashes(entry.Data)

	isColorTerminal := isTerminal && (runtime.GOOS != "windows")
	isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors

	timestampFormat := f.TimestampFormat
	if timestampFormat == "" {
		timestampFormat = logrus.DefaultTimestampFormat
	}
	if isColored {
		f.printColored(b, entry, keys, timestampFormat)
	} else {
		if !f.DisableTimestamp {
			f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
		}
		f.appendKeyValue(b, "level", entry.Level.String())
		if entry.Message != "" {
			f.appendKeyValue(b, "msg", entry.Message)
		}
		for _, key := range keys {
			f.appendKeyValue(b, key, entry.Data[key])
		}
	}

	b.WriteByte('\n')
	return b.Bytes(), nil
}

func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
	var levelColor int
	switch entry.Level {
	case DebugLevel:
		levelColor = gray
	case WarnLevel:
		levelColor = yellow
	case ErrorLevel, FatalLevel, PanicLevel:
		levelColor = red
	default:
		levelColor = blue
	}

	levelText := strings.ToUpper(entry.Level.String())[0:4]

	if !f.FullTimestamp {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
	} else {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
	}
	for _, k := range keys {
		v := entry.Data[k]
		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v)
	}
}

func needsQuoting(text string) bool {
	for _, ch := range text {
		if !((ch >= 'a' && ch <= 'z') ||
		(ch >= 'A' && ch <= 'Z') ||
		(ch >= '0' && ch <= '9') ||
		ch == '-' || ch == '.') {
			return false
		}
	}
	return true
}

func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {

	b.WriteString(key)
	b.WriteByte('=')

	switch value := value.(type) {
	case string:
		if needsQuoting(value) {
			b.WriteString(value)
		} else {
			fmt.Fprintf(b, "%q", value)
		}
	case error:
		errmsg := value.Error()
		if needsQuoting(errmsg) {
			b.WriteString(errmsg)
		} else {
			fmt.Fprintf(b, "%q", value)
		}
	default:
		fmt.Fprint(b, value)
	}

	b.WriteByte(' ')
}
<commit_msg>Implement alternative formatter<commit_after>package prefixedlog

import (
	"bytes"
	"fmt"
	"runtime"
	"sort"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/mgutz/ansi"
)

var (
	baseTimestamp time.Time
	isTerminal    bool
)

func init() {
	baseTimestamp = time.Now()
	isTerminal = logrus.IsTerminal()
}

func miniTS() int {
	return int(time.Since(baseTimestamp) / time.Second)
}

type TextFormatter struct {
	// Set to true to bypass checking for a TTY before outputting colors.
	ForceColors bool

	// Force disabling colors.
	DisableColors bool

	// Disable timestamp logging. useful when output is redirected to logging
	// system that already adds timestamps.
	DisableTimestamp bool

	// Enable logging the full timestamp when a TTY is attached instead of just
	// the time passed since beginning of execution.
	FullTimestamp bool

	// TimestampFormat to use for display when a full timestamp is printed
	TimestampFormat string

	// The fields are sorted by default for a consistent output. For applications
	// that log extremely frequently and don't use the JSON formatter this may not
	// be desired.
	DisableSorting bool
}

func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	var keys []string = make([]string, 0, len(entry.Data))
	for k := range entry.Data {
		keys = append(keys, k)
	}

	if !f.DisableSorting {
		sort.Strings(keys)
	}

	b := &bytes.Buffer{}

	prefixFieldClashes(entry.Data)

	isColorTerminal := isTerminal && (runtime.GOOS != "windows")
	isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors

	timestampFormat := f.TimestampFormat
	if timestampFormat == "" {
		timestampFormat = time.Stamp
	}
	if isColored {
		f.printColored(b, entry, keys, timestampFormat)
	} else {
		if !f.DisableTimestamp {
			f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
		}
		f.appendKeyValue(b, "level", entry.Level.String())
		if entry.Message != "" {
			f.appendKeyValue(b, "msg", entry.Message)
		}
		for _, key := range keys {
			f.appendKeyValue(b, key, entry.Data[key])
		}
	}

	b.WriteByte('\n')
	return b.Bytes(), nil
}

func (f *TextFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry, keys []string, timestampFormat string) {
	var levelColor string
	switch entry.Level {
	case logrus.InfoLevel:
		levelColor = ansi.Green
	case logrus.WarnLevel:
		levelColor = ansi.Magenta
	case logrus.ErrorLevel:
		levelColor = ansi.Red
	case logrus.FatalLevel, logrus.PanicLevel:
		levelColor = ansi.LightRed
	default:
		levelColor = ansi.LightBlue
	}

	levelText := strings.ToUpper(entry.Level.String())
	prefix := ""

	prefixValue, ok := entry.Data["prefix"]
	if ok {
		prefix = fmt.Sprint(" ", ansi.LightWhite, prefixValue, ":", ansi.Reset)
	}

	if !f.FullTimestamp {
		fmt.Fprintf(b, "%s%04d%s %s%+5s%s%s %s", ansi.LightBlack, miniTS(), ansi.Reset, levelColor, levelText, ansi.Reset, prefix, entry.Message)
	} else {
		fmt.Fprintf(b, "%s%s%s %s%+5s%s%s %s", ansi.LightBlack, entry.Time.Format(timestampFormat), ansi.Reset, levelColor, levelText, ansi.Reset, prefix, entry.Message)
	}
	for _, k := range keys {
		v := entry.Data[k]
		fmt.Fprintf(b, " %s%s%s=%+v", levelColor, k, ansi.Reset, v)
	}
}

func needsQuoting(text string) bool {
	for _, ch := range text {
		if !((ch >= 'a' && ch <= 'z') ||
		(ch >= 'A' && ch <= 'Z') ||
		(ch >= '0' && ch <= '9') ||
		ch == '-' || ch == '.') {
			return false
		}
	}
	return true
}

func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
	b.WriteString(key)
	b.WriteByte('=')

	switch value := value.(type) {
	case string:
		if needsQuoting(value) {
			b.WriteString(value)
		} else {
			fmt.Fprintf(b, "%q", value)
		}
	case error:
		errmsg := value.Error()
		if needsQuoting(errmsg) {
			b.WriteString(errmsg)
		} else {
			fmt.Fprintf(b, "%q", value)
		}
	default:
		fmt.Fprint(b, value)
	}

	b.WriteByte(' ')
}

func prefixFieldClashes(data logrus.Fields) {
	_, ok := data["time"]
	if ok {
		data["fields.time"] = data["time"]
	}
	_, ok = data["msg"]
	if ok {
		data["fields.msg"] = data["msg"]
	}
	_, ok = data["level"]
	if ok {
		data["fields.level"] = data["level"]
	}
}
<|endoftext|>


<commit_before>package provision

import (
	"bytes"
	"fmt"
	"text/template"

	"github.com/docker/machine/libmachine/auth"
	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/engine"
	"github.com/docker/machine/libmachine/swarm"
  "github.com/docker/machine/libmachine/kubernetes"
)

type GenericProvisioner struct {
	OsReleaseId       string
	DockerOptionsDir  string
	DaemonOptionsFile string
	KubernetesManifestFile string
  KubernetesKubeletPath string
	Packages          []string
	OsReleaseInfo     *OsRelease
	Driver            drivers.Driver
	AuthOptions       auth.AuthOptions
	EngineOptions     engine.EngineOptions
	SwarmOptions      swarm.SwarmOptions
  KubernetesOptions kubernetes.KubernetesOptions
}

func (provisioner *GenericProvisioner) Hostname() (string, error) {
	return provisioner.SSHCommand("hostname")
}

func (provisioner *GenericProvisioner) SetHostname(hostname string) error {
	if _, err := provisioner.SSHCommand(fmt.Sprintf(
		"sudo hostname %s && echo %q | sudo tee /etc/hostname",
		hostname,
		hostname,
	)); err != nil {
		return err
	}

	// ubuntu/debian use 127.0.1.1 for non "localhost" loopback hostnames: https://www.debian.org/doc/manuals/debian-reference/ch05.en.html#_the_hostname_resolution
	if _, err := provisioner.SSHCommand(fmt.Sprintf(
		"if grep -xq 127.0.1.1.* /etc/hosts; then sudo sed -i 's/^127.0.1.1.*/127.0.1.1 %s/g' /etc/hosts; else echo '127.0.1.1 %s' | sudo tee -a /etc/hosts; fi",
		hostname,
		hostname,
	)); err != nil {
		return err
	}

	return nil
}

func (provisioner *GenericProvisioner) GetDockerOptionsDir() string {
	return provisioner.DockerOptionsDir
}

func (provisioner *GenericProvisioner) SSHCommand(args string) (string, error) {
	return drivers.RunSSHCommandFromDriver(provisioner.Driver, args)
}

func (provisioner *GenericProvisioner) CompatibleWithHost() bool {
	return provisioner.OsReleaseInfo.Id == provisioner.OsReleaseId
}

func (provisioner *GenericProvisioner) GetAuthOptions() auth.AuthOptions {
	return provisioner.AuthOptions
}

func (provisioner *GenericProvisioner) SetOsReleaseInfo(info *OsRelease) {
	provisioner.OsReleaseInfo = info
}

func (provisioner *GenericProvisioner) GetKubernetesOptions() kubernetes.KubernetesOptions {
    return provisioner.KubernetesOptions
}

func (provisioner *GenericProvisioner) Generatek8sOptions() (*k8sOptions, error) {
    type ConfigDetails struct {
        ClusterName string
        CertDir string
    }

	var (
		k8sCfg bytes.Buffer
        k8sKubeletCfg bytes.Buffer
	)

    configParams := ConfigDetails{
        provisioner.Driver.GetMachineName(),
        provisioner.KubernetesOptions.K8SCertPath,
    }

    k8sKubeletConfigTmpl := `apiVersion: v1
kind: Config
clusters:
  - cluster:
      certificate-authority: {{.CertDir}}/ca.pem
      server: https://127.0.0.1:6443
    name: {{.ClusterName}}
contexts:
  - context:
      cluster: {{.ClusterName}}
      user: kubelet
    name: {{.ClusterName}}
users:
  - name: kubelet
    user:
      client-certificate: {{.CertDir}}/kubelet/cert.pem
      client-key: {{.CertDir}}/kubelet/key.pem`

	k8sConfigTmpl := `
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {"name":"{{.ClusterName}}"},
"spec":{
  "hostNetwork": true,
  "containers":[
    {
      "name": "etcd",
      "image": "b.gcr.io/kuar/etcd:2.1.1",
      "args": [
              "--data-dir=/var/lib/etcd",
              "--advertise-client-urls=http://127.0.0.1:2379",
              "--listen-client-urls=http://127.0.0.1:2379",
              "--listen-peer-urls=http://127.0.0.1:2380",
              "--name=etcd"
        ]
    },
    {
      "name": "controller-manager",
      "image": "gcr.io/google_containers/hyperkube:v1.1.2",
      "args": [
              "/hyperkube",
              "controller-manager",
              "--master=http://127.0.0.1:8080",
              "--v=2"
        ]
    },
    {
      "name": "apiserver",
      "image": "gcr.io/google_containers/hyperkube:v1.1.2",
      "volumeMounts": [
          {"name": "certs",
          "mountPath": "{{.CertDir}}",
          "readOnly": true },
          {"name": "policies",
          "mountPath": "/etc/kubernetes/policies",
          "readOnly": true }
          ],
      "args": [
              "/hyperkube",
              "apiserver",
              "--token-auth-file={{.CertDir}}/tokenfile.txt",
              "--client-ca-file=/var/run/kubernetes/ca.pem",
              "--allow-privileged=true",
              "--service-cluster-ip-range=10.0.20.0/24",
              "--insecure-bind-address=0.0.0.0",
              "--insecure-port=8080",
              "--secure-port=6443",
              "--etcd-servers=http://127.0.0.1:2379",
              "--tls-cert-file={{.CertDir}}/apiserver/cert.pem",
              "--tls-private-key-file={{.CertDir}}/apiserver/key.pem",
              "--v=2"
        ]
    },
    {
      "name": "proxy",
      "image": "gcr.io/google_containers/hyperkube:v1.1.2",
      "securityContext": {
        "privileged": true
        },
      "args": [
              "/hyperkube",
              "proxy",
              "--master=http://127.0.0.1:8080",
              "--v=2"
        ]
    },
    {
      "name": "scheduler",
      "image": "gcr.io/google_containers/hyperkube:v1.1.2",
      "args": [
              "/hyperkube",
              "scheduler",
              "--master=http://127.0.0.1:8080",
              "--v=2"
        ]
    }
  ],
  "volumes":[
    { "name": "certs",
      "hostPath": {
        "path": "{{.CertDir}}"
      }
    }, { "name": "policies",
        "hostPath": {
            "path": "/etc/kubernetes/policies"
        }
    }
  ]
 }
}
`
	t, err := template.New("k8sConfig").Parse(k8sConfigTmpl)
	if err != nil {
		return nil, err
	}

  kt, err := template.New("k8sKubeletConfig").Parse(k8sKubeletConfigTmpl)
  if err != nil {
      return nil, err
  }

  k8sPolicyCfg, err := GeneratePolicyFile(provisioner.KubernetesOptions.K8SUser)
  if err != nil {
    return nil, err
  }

	/*
	k8sContext := EngineConfigContext{
		DockerPort:    1234,
		AuthOptions:   provisioner.AuthOptions,
		EngineOptions: provisioner.EngineOptions,
	}
	*/

	//t.Execute(&k8sCfg, k8sContext)
    t.Execute(&k8sCfg, configParams)
    kt.Execute(&k8sKubeletCfg, configParams)

	return &k8sOptions{
		k8sOptions:     k8sCfg.String(),
		k8sOptionsPath: provisioner.KubernetesManifestFile,
    k8sKubeletCfg:  k8sKubeletCfg.String(),
    k8sKubeletPath: provisioner.KubernetesKubeletPath,
    k8sPolicyCfg:   k8sPolicyCfg,
	}, nil
}

func (provisioner *GenericProvisioner) GetOsReleaseInfo() (*OsRelease, error) {
	return provisioner.OsReleaseInfo, nil

}

func (provisioner *GenericProvisioner) GenerateDockerOptions(dockerPort int) (*DockerOptions, error) {
	var (
		engineCfg bytes.Buffer
	)

	driverNameLabel := fmt.Sprintf("provider=%s", provisioner.Driver.DriverName())
	provisioner.EngineOptions.Labels = append(provisioner.EngineOptions.Labels, driverNameLabel)

	engineConfigTmpl := `
DOCKER_OPTS='
-H tcp://0.0.0.0:{{.DockerPort}}
-H unix:///var/run/docker.sock
--storage-driver {{.EngineOptions.StorageDriver}}
--tlsverify
--tlscacert {{.AuthOptions.CaCertRemotePath}}
--tlscert {{.AuthOptions.ServerCertRemotePath}}
--tlskey {{.AuthOptions.ServerKeyRemotePath}}
{{ range .EngineOptions.Labels }}--label {{.}}
{{ end }}{{ range .EngineOptions.InsecureRegistry }}--insecure-registry {{.}}
{{ end }}{{ range .EngineOptions.RegistryMirror }}--registry-mirror {{.}}
{{ end }}{{ range .EngineOptions.ArbitraryFlags }}--{{.}}
{{ end }}
'
{{range .EngineOptions.Env}}export \"{{ printf "%q" . }}\"
{{end}}
`
	t, err := template.New("engineConfig").Parse(engineConfigTmpl)
	if err != nil {
		return nil, err
	}

	engineConfigContext := EngineConfigContext{
		DockerPort:    dockerPort,
		AuthOptions:   provisioner.AuthOptions,
		EngineOptions: provisioner.EngineOptions,
	}

	t.Execute(&engineCfg, engineConfigContext)

	return &DockerOptions{
		EngineOptions:     engineCfg.String(),
		EngineOptionsPath: provisioner.DaemonOptionsFile,
	}, nil
}

func (provisioner *GenericProvisioner) GetDriver() drivers.Driver {
	return provisioner.Driver
}
<commit_msg>missed configuration manifest<commit_after>package provision

import (
	"bytes"
	"fmt"
	"text/template"

	"github.com/docker/machine/libmachine/auth"
	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/engine"
	"github.com/docker/machine/libmachine/swarm"
  "github.com/docker/machine/libmachine/kubernetes"
)

type GenericProvisioner struct {
	OsReleaseId       string
	DockerOptionsDir  string
	DaemonOptionsFile string
	KubernetesManifestFile string
  KubernetesKubeletPath string
	Packages          []string
	OsReleaseInfo     *OsRelease
	Driver            drivers.Driver
	AuthOptions       auth.AuthOptions
	EngineOptions     engine.EngineOptions
	SwarmOptions      swarm.SwarmOptions
  KubernetesOptions kubernetes.KubernetesOptions
}

func (provisioner *GenericProvisioner) Hostname() (string, error) {
	return provisioner.SSHCommand("hostname")
}

func (provisioner *GenericProvisioner) SetHostname(hostname string) error {
	if _, err := provisioner.SSHCommand(fmt.Sprintf(
		"sudo hostname %s && echo %q | sudo tee /etc/hostname",
		hostname,
		hostname,
	)); err != nil {
		return err
	}

	// ubuntu/debian use 127.0.1.1 for non "localhost" loopback hostnames: https://www.debian.org/doc/manuals/debian-reference/ch05.en.html#_the_hostname_resolution
	if _, err := provisioner.SSHCommand(fmt.Sprintf(
		"if grep -xq 127.0.1.1.* /etc/hosts; then sudo sed -i 's/^127.0.1.1.*/127.0.1.1 %s/g' /etc/hosts; else echo '127.0.1.1 %s' | sudo tee -a /etc/hosts; fi",
		hostname,
		hostname,
	)); err != nil {
		return err
	}

	return nil
}

func (provisioner *GenericProvisioner) GetDockerOptionsDir() string {
	return provisioner.DockerOptionsDir
}

func (provisioner *GenericProvisioner) SSHCommand(args string) (string, error) {
	return drivers.RunSSHCommandFromDriver(provisioner.Driver, args)
}

func (provisioner *GenericProvisioner) CompatibleWithHost() bool {
	return provisioner.OsReleaseInfo.Id == provisioner.OsReleaseId
}

func (provisioner *GenericProvisioner) GetAuthOptions() auth.AuthOptions {
	return provisioner.AuthOptions
}

func (provisioner *GenericProvisioner) SetOsReleaseInfo(info *OsRelease) {
	provisioner.OsReleaseInfo = info
}

func (provisioner *GenericProvisioner) GetKubernetesOptions() kubernetes.KubernetesOptions {
    return provisioner.KubernetesOptions
}

func (provisioner *GenericProvisioner) Generatek8sOptions() (*k8sOptions, error) {
    type ConfigDetails struct {
        ClusterName string
        CertDir string
    }

	var (
		k8sCfg bytes.Buffer
        k8sKubeletCfg bytes.Buffer
	)

    configParams := ConfigDetails{
        provisioner.Driver.GetMachineName(),
        provisioner.KubernetesOptions.K8SCertPath,
    }

    k8sKubeletConfigTmpl := `apiVersion: v1
kind: Config
clusters:
  - cluster:
      certificate-authority: {{.CertDir}}/ca.pem
      server: https://127.0.0.1:6443
    name: {{.ClusterName}}
contexts:
  - context:
      cluster: {{.ClusterName}}
      user: kubelet
    name: {{.ClusterName}}
users:
  - name: kubelet
    user:
      client-certificate: {{.CertDir}}/kubelet/cert.pem
      client-key: {{.CertDir}}/kubelet/key.pem`

	k8sConfigTmpl := `
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {"name":"{{.ClusterName}}"},
"spec":{
  "hostNetwork": true,
  "containers":[
    {
      "name": "etcd",
      "image": "b.gcr.io/kuar/etcd:2.1.1",
      "args": [
              "--data-dir=/var/lib/etcd",
              "--advertise-client-urls=http://127.0.0.1:2379",
              "--listen-client-urls=http://127.0.0.1:2379",
              "--listen-peer-urls=http://127.0.0.1:2380",
              "--name=etcd"
        ]
    },
    {
      "name": "controller-manager",
      "image": "gcr.io/google_containers/hyperkube:v1.1.2",
      "volumeMounts": [
          {"name": "certs",
          "mountPath": "{{.CertDir}}",
          "readOnly": true }
          ],
      "args": [
              "/hyperkube",
              "controller-manager",
              "--service-account-private-key-file={{.CertDir}}/apiserver/key.pem",
              "--root-ca-file=/var/run/kubernetes/ca.pem",
              "--master=http://127.0.0.1:8080",
              "--v=2"
        ]
    },
    {
      "name": "apiserver",
      "image": "gcr.io/google_containers/hyperkube:v1.1.2",
      "volumeMounts": [
          {"name": "certs",
          "mountPath": "{{.CertDir}}",
          "readOnly": true },
          {"name": "policies",
          "mountPath": "/etc/kubernetes/policies",
          "readOnly": true }
          ],
      "args": [
              "/hyperkube",
              "apiserver",
              "--token-auth-file={{.CertDir}}/tokenfile.txt",
              "--client-ca-file=/var/run/kubernetes/ca.pem",
              "--allow-privileged=true",
              "--service-cluster-ip-range=10.0.0.1/24",
              "--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,SecurityContextDeny,ResourceQuota",
              "--insecure-bind-address=0.0.0.0",
              "--insecure-port=8080",
              "--secure-port=6443",
              "--etcd-servers=http://127.0.0.1:2379",
              "--tls-cert-file={{.CertDir}}/apiserver/cert.pem",
              "--tls-private-key-file={{.CertDir}}/apiserver/key.pem",
              "--v=2"
        ]
    },
    {
      "name": "proxy",
      "image": "gcr.io/google_containers/hyperkube:v1.1.2",
      "securityContext": {
        "privileged": true
        },
      "args": [
              "/hyperkube",
              "proxy",
              "--master=http://127.0.0.1:8080",
              "--v=2"
        ]
    },
    {
      "name": "scheduler",
      "image": "gcr.io/google_containers/hyperkube:v1.1.2",
      "args": [
              "/hyperkube",
              "scheduler",
              "--master=http://127.0.0.1:8080",
              "--v=2"
        ]
    }
  ],
  "volumes":[
    { "name": "certs",
      "hostPath": {
        "path": "{{.CertDir}}"
      }
    }, { "name": "policies",
        "hostPath": {
            "path": "/etc/kubernetes/policies"
        }
    }
  ]
 }
}
`
	t, err := template.New("k8sConfig").Parse(k8sConfigTmpl)
	if err != nil {
		return nil, err
	}

  kt, err := template.New("k8sKubeletConfig").Parse(k8sKubeletConfigTmpl)
  if err != nil {
      return nil, err
  }

  k8sPolicyCfg, err := GeneratePolicyFile(provisioner.KubernetesOptions.K8SUser)
  if err != nil {
    return nil, err
  }

	/*
	k8sContext := EngineConfigContext{
		DockerPort:    1234,
		AuthOptions:   provisioner.AuthOptions,
		EngineOptions: provisioner.EngineOptions,
	}
	*/

	//t.Execute(&k8sCfg, k8sContext)
    t.Execute(&k8sCfg, configParams)
    kt.Execute(&k8sKubeletCfg, configParams)

	return &k8sOptions{
		k8sOptions:     k8sCfg.String(),
		k8sOptionsPath: provisioner.KubernetesManifestFile,
    k8sKubeletCfg:  k8sKubeletCfg.String(),
    k8sKubeletPath: provisioner.KubernetesKubeletPath,
    k8sPolicyCfg:   k8sPolicyCfg,
	}, nil
}

func (provisioner *GenericProvisioner) GetOsReleaseInfo() (*OsRelease, error) {
	return provisioner.OsReleaseInfo, nil

}

func (provisioner *GenericProvisioner) GenerateDockerOptions(dockerPort int) (*DockerOptions, error) {
	var (
		engineCfg bytes.Buffer
	)

	driverNameLabel := fmt.Sprintf("provider=%s", provisioner.Driver.DriverName())
	provisioner.EngineOptions.Labels = append(provisioner.EngineOptions.Labels, driverNameLabel)

	engineConfigTmpl := `
DOCKER_OPTS='
-H tcp://0.0.0.0:{{.DockerPort}}
-H unix:///var/run/docker.sock
--storage-driver {{.EngineOptions.StorageDriver}}
--tlsverify
--tlscacert {{.AuthOptions.CaCertRemotePath}}
--tlscert {{.AuthOptions.ServerCertRemotePath}}
--tlskey {{.AuthOptions.ServerKeyRemotePath}}
{{ range .EngineOptions.Labels }}--label {{.}}
{{ end }}{{ range .EngineOptions.InsecureRegistry }}--insecure-registry {{.}}
{{ end }}{{ range .EngineOptions.RegistryMirror }}--registry-mirror {{.}}
{{ end }}{{ range .EngineOptions.ArbitraryFlags }}--{{.}}
{{ end }}
'
{{range .EngineOptions.Env}}export \"{{ printf "%q" . }}\"
{{end}}
`
	t, err := template.New("engineConfig").Parse(engineConfigTmpl)
	if err != nil {
		return nil, err
	}

	engineConfigContext := EngineConfigContext{
		DockerPort:    dockerPort,
		AuthOptions:   provisioner.AuthOptions,
		EngineOptions: provisioner.EngineOptions,
	}

	t.Execute(&engineCfg, engineConfigContext)

	return &DockerOptions{
		EngineOptions:     engineCfg.String(),
		EngineOptionsPath: provisioner.DaemonOptionsFile,
	}, nil
}

func (provisioner *GenericProvisioner) GetDriver() drivers.Driver {
	return provisioner.Driver
}
<|endoftext|>


<commit_before>package feiertage

import (
	"fmt"
	"sort"
	"testing"
)

//-------------------------

//-------------------------

func compareAndFail(t *testing.T, f Feiertag, d string) {
	if f.Format("02.01.2006") != d {
		fmt.Printf("%s but should be %s\n", f, d)
		t.Fail()
	}
}

func TestOstern(t *testing.T) {
	compareAndFail(t, Ostern(2015), "06.04.2015")
	compareAndFail(t, Ostern(2016), "27.03.2016")
}

func TestOsternAusnahmejahre(t *testing.T) {
	compareAndFail(t, Ostern(1954), "18.04.1954")
	compareAndFail(t, Ostern(1981), "19.04.1981")
}

func TestSommerWinterZeit(t *testing.T) {
	compareAndFail(t, BeginnSommerzeit(2015), "29.03.2015")
	compareAndFail(t, BeginnWinterzeit(2016), "30.10.2016")
}

func TestBußUndBetTag(t *testing.T) {
	compareAndFail(t, BußUndBettag(2015), "18.11.2015")
	compareAndFail(t, BußUndBettag(2016), "16.11.2016")
}

func TestVorwärtssucher(t *testing.T) {
	compareAndFail(t, Erntedankfest(2015), "04.10.2015")
	compareAndFail(t, Erntedankfest(2016), "02.10.2016")
	compareAndFail(t, Muttertag(2015), "10.05.2015")
	compareAndFail(t, Muttertag(2016), "08.05.2016")
}

func TestAdvent(t *testing.T) {
	// VierterAdvent=Rückwärtssucher
	compareAndFail(t, VierterAdvent(2016), "18.12.2016")
	compareAndFail(t, DritterAdvent(2016), "11.12.2016")
	compareAndFail(t, ZweiterAdvent(2016), "04.12.2016")
	compareAndFail(t, ErsterAdvent(2016), "27.11.2016")
	compareAndFail(t, VierterAdvent(2006), "24.12.2006")
	compareAndFail(t, VierterAdvent(2006), VierterAdvent(2006).Format("02.01.2006"))
}

//-------------------------

func TestFeiertage(t *testing.T) {

	fun := []func(int) Feiertag{Neujahr, Epiphanias, HeiligeDreiKönige, Valentinstag,
		Weiberfastnacht, Karnevalssonntag, Rosenmontag, Fastnacht, Aschermittwoch,
		Palmsonntag, Gründonnerstag, Karfreitag, Ostern, BeginnSommerzeit, Ostermontag,
		Walpurgisnacht, TagDerArbeit, TagDerBefreiung, Muttertag, ChristiHimmelfahrt,
		Vatertag, Pfingsten, PfingstMontag, Dreifaltigkeitssonntag, Fronleichnam,
		MariäHimmelfahrt, TagDerDeutschenEinheit, Erntedankfest, Reformationstag,
		Halloween, BeginnWinterzeit, Allerheiligen, Allerseelen, Martinstag,
		Karnevalsbeginn, BußUndBettag, Volkstrauertag, Nikolaus, MariäUnbefleckteEmpfängnis,
		Totensonntag, ErsterAdvent, ZweiterAdvent, DritterAdvent, VierterAdvent,
		Heiligabend, Weihnachten, ZweiterWeihnachtsfeiertag, Silvester}

	years := []int{2015, 2016}

	for _, y := range years {
		feiern := []Feiertag{}
		for _, f := range fun {
			feiern = append(feiern, f(y))
		}
		sort.Sort(ByDate(feiern))
		for _, f := range feiern {
			fmt.Println(f)
		}
	}
}

func TestThanksgiving(t *testing.T) {
	//Vorwärtssucher Donnerstag
	compareAndFail(t, Thanksgiving(2010), "25.11.2010")
	compareAndFail(t, Thanksgiving(2014), "27.11.2014")
	compareAndFail(t, Thanksgiving(2015), "26.11.2015")
	compareAndFail(t, Thanksgiving(2016), "24.11.2016")
	compareAndFail(t, Thanksgiving(2017), "23.11.2017")
	compareAndFail(t, Thanksgiving(2018), "22.11.2018")
	compareAndFail(t, Thanksgiving(2019), "28.11.2019")
	compareAndFail(t, Thanksgiving(2025), "27.11.2025")
	compareAndFail(t, Thanksgiving(2028), "23.11.2028")
	compareAndFail(t, Thanksgiving(2029), "22.11.2029")
}
<commit_msg>Testing coveralls.<commit_after>package feiertage

import (
	"fmt"
	"sort"
	"testing"
)

//-------------------------

//-------------------------

func compareAndFail(t *testing.T, f Feiertag, d string) {
	if f.Format("02.01.2006") != d {
		fmt.Printf("%s but should be %s\n", f, d)
		t.Fail()
	}
}

func TestOstern(t *testing.T) {
	compareAndFail(t, Ostern(2015), "05.04.2015")
	compareAndFail(t, Ostern(2016), "27.03.2016")
}

func TestOsternAusnahmejahre(t *testing.T) {
	compareAndFail(t, Ostern(1954), "18.04.1954")
	compareAndFail(t, Ostern(1981), "19.04.1981")
}

func TestSommerWinterZeit(t *testing.T) {
	compareAndFail(t, BeginnSommerzeit(2015), "29.03.2015")
	compareAndFail(t, BeginnWinterzeit(2016), "30.10.2016")
}

func TestBußUndBetTag(t *testing.T) {
	compareAndFail(t, BußUndBettag(2015), "18.11.2015")
	compareAndFail(t, BußUndBettag(2016), "16.11.2016")
}

func TestVorwärtssucher(t *testing.T) {
	compareAndFail(t, Erntedankfest(2015), "04.10.2015")
	compareAndFail(t, Erntedankfest(2016), "02.10.2016")
	compareAndFail(t, Muttertag(2015), "10.05.2015")
	compareAndFail(t, Muttertag(2016), "08.05.2016")
}

func TestAdvent(t *testing.T) {
	// VierterAdvent=Rückwärtssucher
	compareAndFail(t, VierterAdvent(2016), "18.12.2016")
	compareAndFail(t, DritterAdvent(2016), "11.12.2016")
	compareAndFail(t, ZweiterAdvent(2016), "04.12.2016")
	compareAndFail(t, ErsterAdvent(2016), "27.11.2016")
	compareAndFail(t, VierterAdvent(2006), "24.12.2006")
	compareAndFail(t, VierterAdvent(2006), VierterAdvent(2006).Format("02.01.2006"))
}

//-------------------------

func TestFeiertage(t *testing.T) {

	fun := []func(int) Feiertag{Neujahr, Epiphanias, HeiligeDreiKönige, Valentinstag,
		Weiberfastnacht, Karnevalssonntag, Rosenmontag, Fastnacht, Aschermittwoch,
		Palmsonntag, Gründonnerstag, Karfreitag, Ostern, BeginnSommerzeit, Ostermontag,
		Walpurgisnacht, TagDerArbeit, TagDerBefreiung, Muttertag, ChristiHimmelfahrt,
		Vatertag, Pfingsten, PfingstMontag, Dreifaltigkeitssonntag, Fronleichnam,
		MariäHimmelfahrt, TagDerDeutschenEinheit, Erntedankfest, Reformationstag,
		Halloween, BeginnWinterzeit, Allerheiligen, Allerseelen, Martinstag,
		Karnevalsbeginn, BußUndBettag, Volkstrauertag, Nikolaus, MariäUnbefleckteEmpfängnis,
		Totensonntag, ErsterAdvent, ZweiterAdvent, DritterAdvent, VierterAdvent,
		Heiligabend, Weihnachten, ZweiterWeihnachtsfeiertag, Silvester}

	years := []int{2015, 2016}

	for _, y := range years {
		feiern := []Feiertag{}
		for _, f := range fun {
			feiern = append(feiern, f(y))
		}
		sort.Sort(ByDate(feiern))
		for _, f := range feiern {
			fmt.Println(f)
		}
	}
}

func TestThanksgiving(t *testing.T) {
	//Vorwärtssucher Donnerstag
	compareAndFail(t, Thanksgiving(2010), "25.11.2010")
	compareAndFail(t, Thanksgiving(2014), "27.11.2014")
	compareAndFail(t, Thanksgiving(2015), "26.11.2015")
	compareAndFail(t, Thanksgiving(2016), "24.11.2016")
	compareAndFail(t, Thanksgiving(2017), "23.11.2017")
	compareAndFail(t, Thanksgiving(2018), "22.11.2018")
	compareAndFail(t, Thanksgiving(2019), "28.11.2019")
	compareAndFail(t, Thanksgiving(2025), "27.11.2025")
	compareAndFail(t, Thanksgiving(2028), "23.11.2028")
	compareAndFail(t, Thanksgiving(2029), "22.11.2029")
}
<|endoftext|>


<commit_before>/*
   Copyright The containerd Authors.

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

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

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

package client

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"syscall"
	"testing"
	"time"

	. "github.com/containerd/containerd"
	"github.com/containerd/containerd/containers"
	"github.com/containerd/containerd/oci"
	"github.com/containerd/containerd/pkg/testutil"
	srvconfig "github.com/containerd/containerd/services/server/config"
	"github.com/containerd/containerd/sys"
	exec "golang.org/x/sys/execabs"
)

// the following nolint is for shutting up gometalinter on non-linux.
// nolint: unused
func newDaemonWithConfig(t *testing.T, configTOML string) (*Client, *daemon, func()) {
	if testing.Short() {
		t.Skip()
	}
	testutil.RequiresRoot(t)
	var (
		ctrd              = daemon{}
		configTOMLDecoded srvconfig.Config
		buf               = bytes.NewBuffer(nil)
	)

	tempDir, err := os.MkdirTemp("", "containerd-test-new-daemon-with-config")
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		if err != nil {
			os.RemoveAll(tempDir)
		}
	}()

	configTOMLFile := filepath.Join(tempDir, "config.toml")
	if err = os.WriteFile(configTOMLFile, []byte(configTOML), 0600); err != nil {
		t.Fatal(err)
	}

	if err = srvconfig.LoadConfig(configTOMLFile, &configTOMLDecoded); err != nil {
		t.Fatal(err)
	}

	address := configTOMLDecoded.GRPC.Address
	if address == "" {
		if runtime.GOOS == "windows" {
			address = fmt.Sprintf(`\\.\pipe\containerd-containerd-test-%s`, filepath.Base(tempDir))
		} else {
			address = filepath.Join(tempDir, "containerd.sock")
		}
	}
	args := []string{"-c", configTOMLFile}
	if configTOMLDecoded.Root == "" {
		args = append(args, "--root", filepath.Join(tempDir, "root"))
	}
	if configTOMLDecoded.State == "" {
		args = append(args, "--state", filepath.Join(tempDir, "state"))
	}
	if err = ctrd.start("containerd", address, args, buf, buf); err != nil {
		t.Fatalf("%v: %s", err, buf.String())
	}

	waitCtx, waitCancel := context.WithTimeout(context.TODO(), 2*time.Second)
	client, err := ctrd.waitForStart(waitCtx)
	waitCancel()
	if err != nil {
		ctrd.Kill()
		ctrd.Wait()
		t.Fatalf("%v: %s", err, buf.String())
	}

	cleanup := func() {
		if err := client.Close(); err != nil {
			t.Errorf("failed to close client: %v", err)
		}
		if err := ctrd.Stop(); err != nil {
			if err := ctrd.Kill(); err != nil {
				t.Errorf("failed to signal containerd: %v", err)
			}
		}
		if err := ctrd.Wait(); err != nil {
			if _, ok := err.(*exec.ExitError); !ok {
				t.Errorf("failed to wait for: %v", err)
			}
		}
		if err := sys.ForceRemoveAll(tempDir); err != nil {
			t.Errorf("failed to remove %s: %v", tempDir, err)
		}
		if t.Failed() {
			t.Log("Daemon output:\n", buf.String())
		}

		// cleaning config-specific resources is up to the caller
	}
	return client, &ctrd, cleanup
}

// TestRestartMonitor tests restarting containers
// with the restart monitor service plugin
func TestRestartMonitor(t *testing.T) {
	const (
		interval = 10 * time.Second
		epsilon  = 1 * time.Second
		count    = 20
	)
	configTOML := fmt.Sprintf(`
version = 2
[plugins]
  [plugins."io.containerd.internal.v1.restart"]
	  interval = "%s"
`, interval.String())
	client, _, cleanup := newDaemonWithConfig(t, configTOML)
	defer cleanup()

	var (
		ctx, cancel = testContext(t)
		id          = t.Name()
	)
	defer cancel()

	image, err := client.Pull(ctx, testImage, WithPullUnpack)
	if err != nil {
		t.Fatal(err)
	}

	container, err := client.NewContainer(ctx, id,
		WithNewSnapshot(id, image),
		WithNewSpec(
			oci.WithImageConfig(image),
			longCommand,
		),
		withRestartStatus(Running),
	)
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		if err := container.Delete(ctx, WithSnapshotCleanup); err != nil {
			t.Logf("failed to delete container: %v", err)
		}
	}()

	task, err := container.NewTask(ctx, empty())
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		if _, err := task.Delete(ctx, WithProcessKill); err != nil {
			t.Logf("failed to delete task: %v", err)
		}
	}()

	if err := task.Start(ctx); err != nil {
		t.Fatal(err)
	}

	if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
		t.Fatal(err)
	}

	begin := time.Now()
	lastCheck := begin

	expected := begin.Add(interval).Add(epsilon)

	// Deadline determines when check for restart should be aborted.
	deadline := begin.Add(interval).Add(epsilon * count)
	for {
		status, err := task.Status(ctx)
		now := time.Now()
		if err != nil {
			// ErrNotFound is expected here, because the restart monitor
			// temporarily removes the task before restarting.
			t.Logf("%v: err=%v", now, err)
		} else {
			t.Logf("%v: status=%q", now, status.Status)

			if status.Status == Running {
				break
			}
		}

		// lastCheck represents the last time the status was seen as not running
		lastCheck = now
		if lastCheck.After(deadline) {
			t.Logf("%v: the task was not restarted", lastCheck)
			return
		}
		time.Sleep(epsilon)
	}

	// Use the last timestamp for when the process was seen as not running for the check
	if lastCheck.After(expected) {
		t.Fatalf("%v: the task was restarted, but it must be before %v", lastCheck, expected)
	}
	t.Logf("%v: the task was restarted since %v", time.Now(), lastCheck)
}

// withRestartStatus is a copy of "github.com/containerd/containerd/runtime/restart".WithStatus.
// This copy is needed because `go test` refuses circular imports.
func withRestartStatus(status ProcessStatus) func(context.Context, *Client, *containers.Container) error {
	return func(_ context.Context, _ *Client, c *containers.Container) error {
		if c.Labels == nil {
			c.Labels = make(map[string]string)
		}
		c.Labels["containerd.io/restart.status"] = string(status)
		return nil
	}
}
<commit_msg>Disable restart monitor test in Windows<commit_after>/*
   Copyright The containerd Authors.

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

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

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

package client

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"syscall"
	"testing"
	"time"

	. "github.com/containerd/containerd"
	"github.com/containerd/containerd/containers"
	"github.com/containerd/containerd/oci"
	"github.com/containerd/containerd/pkg/testutil"
	srvconfig "github.com/containerd/containerd/services/server/config"
	"github.com/containerd/containerd/sys"
	exec "golang.org/x/sys/execabs"
)

// the following nolint is for shutting up gometalinter on non-linux.
// nolint: unused
func newDaemonWithConfig(t *testing.T, configTOML string) (*Client, *daemon, func()) {
	if testing.Short() {
		t.Skip()
	}
	testutil.RequiresRoot(t)
	var (
		ctrd              = daemon{}
		configTOMLDecoded srvconfig.Config
		buf               = bytes.NewBuffer(nil)
	)

	tempDir, err := os.MkdirTemp("", "containerd-test-new-daemon-with-config")
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		if err != nil {
			os.RemoveAll(tempDir)
		}
	}()

	configTOMLFile := filepath.Join(tempDir, "config.toml")
	if err = os.WriteFile(configTOMLFile, []byte(configTOML), 0600); err != nil {
		t.Fatal(err)
	}

	if err = srvconfig.LoadConfig(configTOMLFile, &configTOMLDecoded); err != nil {
		t.Fatal(err)
	}

	address := configTOMLDecoded.GRPC.Address
	if address == "" {
		if runtime.GOOS == "windows" {
			address = fmt.Sprintf(`\\.\pipe\containerd-containerd-test-%s`, filepath.Base(tempDir))
		} else {
			address = filepath.Join(tempDir, "containerd.sock")
		}
	}
	args := []string{"-c", configTOMLFile}
	if configTOMLDecoded.Root == "" {
		args = append(args, "--root", filepath.Join(tempDir, "root"))
	}
	if configTOMLDecoded.State == "" {
		args = append(args, "--state", filepath.Join(tempDir, "state"))
	}
	if err = ctrd.start("containerd", address, args, buf, buf); err != nil {
		t.Fatalf("%v: %s", err, buf.String())
	}

	waitCtx, waitCancel := context.WithTimeout(context.TODO(), 2*time.Second)
	client, err := ctrd.waitForStart(waitCtx)
	waitCancel()
	if err != nil {
		ctrd.Kill()
		ctrd.Wait()
		t.Fatalf("%v: %s", err, buf.String())
	}

	cleanup := func() {
		if err := client.Close(); err != nil {
			t.Errorf("failed to close client: %v", err)
		}
		if err := ctrd.Stop(); err != nil {
			if err := ctrd.Kill(); err != nil {
				t.Errorf("failed to signal containerd: %v", err)
			}
		}
		if err := ctrd.Wait(); err != nil {
			if _, ok := err.(*exec.ExitError); !ok {
				t.Errorf("failed to wait for: %v", err)
			}
		}
		if err := sys.ForceRemoveAll(tempDir); err != nil {
			t.Errorf("failed to remove %s: %v", tempDir, err)
		}
		if t.Failed() {
			t.Log("Daemon output:\n", buf.String())
		}

		// cleaning config-specific resources is up to the caller
	}
	return client, &ctrd, cleanup
}

// TestRestartMonitor tests restarting containers
// with the restart monitor service plugin
func TestRestartMonitor(t *testing.T) {
	if runtime.GOOS == "windows" {
		// This test on Windows encounters the following error in some environments:
		// "The process cannot access the file because it is being used by another process. (0x20)"
		// Skip this test until this error can be evaluated and the appropriate
		// test fix or environment configuration can be determined.
		t.Skip("Skipping flaky test on Windows")
	}
	const (
		interval = 10 * time.Second
		epsilon  = 1 * time.Second
		count    = 20
	)
	configTOML := fmt.Sprintf(`
version = 2
[plugins]
  [plugins."io.containerd.internal.v1.restart"]
	  interval = "%s"
`, interval.String())
	client, _, cleanup := newDaemonWithConfig(t, configTOML)
	defer cleanup()

	var (
		ctx, cancel = testContext(t)
		id          = t.Name()
	)
	defer cancel()

	image, err := client.Pull(ctx, testImage, WithPullUnpack)
	if err != nil {
		t.Fatal(err)
	}

	container, err := client.NewContainer(ctx, id,
		WithNewSnapshot(id, image),
		WithNewSpec(
			oci.WithImageConfig(image),
			longCommand,
		),
		withRestartStatus(Running),
	)
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		if err := container.Delete(ctx, WithSnapshotCleanup); err != nil {
			t.Logf("failed to delete container: %v", err)
		}
	}()

	task, err := container.NewTask(ctx, empty())
	if err != nil {
		t.Fatal(err)
	}
	defer func() {
		if _, err := task.Delete(ctx, WithProcessKill); err != nil {
			t.Logf("failed to delete task: %v", err)
		}
	}()

	if err := task.Start(ctx); err != nil {
		t.Fatal(err)
	}

	if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
		t.Fatal(err)
	}

	begin := time.Now()
	lastCheck := begin

	expected := begin.Add(interval).Add(epsilon)

	// Deadline determines when check for restart should be aborted.
	deadline := begin.Add(interval).Add(epsilon * count)
	for {
		status, err := task.Status(ctx)
		now := time.Now()
		if err != nil {
			// ErrNotFound is expected here, because the restart monitor
			// temporarily removes the task before restarting.
			t.Logf("%v: err=%v", now, err)
		} else {
			t.Logf("%v: status=%q", now, status.Status)

			if status.Status == Running {
				break
			}
		}

		// lastCheck represents the last time the status was seen as not running
		lastCheck = now
		if lastCheck.After(deadline) {
			t.Logf("%v: the task was not restarted", lastCheck)
			return
		}
		time.Sleep(epsilon)
	}

	// Use the last timestamp for when the process was seen as not running for the check
	if lastCheck.After(expected) {
		t.Fatalf("%v: the task was restarted, but it must be before %v", lastCheck, expected)
	}
	t.Logf("%v: the task was restarted since %v", time.Now(), lastCheck)
}

// withRestartStatus is a copy of "github.com/containerd/containerd/runtime/restart".WithStatus.
// This copy is needed because `go test` refuses circular imports.
func withRestartStatus(status ProcessStatus) func(context.Context, *Client, *containers.Container) error {
	return func(_ context.Context, _ *Client, c *containers.Container) error {
		if c.Labels == nil {
			c.Labels = make(map[string]string)
		}
		c.Labels["containerd.io/restart.status"] = string(status)
		return nil
	}
}
<|endoftext|>


<commit_before>package plugin

import (
	"bytes"
	"log"
	"net/http"
	"strings"

	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
	. "github.com/onsi/gomega/ghttp"
)

var _ = Describe("add-plugin-repo command", func() {
	BeforeEach(func() {
		helpers.RunIfExperimental("skipping until approved")
	})

	Describe("help", func() {
		Context("when --help flag is provided", func() {
			It("displays command usage to output", func() {
				session := helpers.CF("add-plugin-repo", "--help")
				Eventually(session.Out).Should(Say("NAME:"))
				Eventually(session.Out).Should(Say("add-plugin-repo - Add a new plugin repository"))
				Eventually(session.Out).Should(Say("USAGE:"))
				Eventually(session.Out).Should(Say("cf add-plugin-repo REPO_NAME URL"))
				Eventually(session.Out).Should(Say("EXAMPLES"))
				Eventually(session.Out).Should(Say("cf add-plugin-repo ExampleRepo https://example\\.com/repo"))
				Eventually(session.Out).Should(Say("SEE ALSO:"))
				Eventually(session.Out).Should(Say("install-plugin, list-plugin-repos"))
				Eventually(session).Should(Exit(0))
			})
		})
	})

	Context("when the command line arguments are invalid", func() {
		Context("when no arguments are provided", func() {
			It("fails with incorrect usage message and displays help", func() {
				session := helpers.CF("add-plugin-repo")
				Eventually(session.Err).Should(Say("Incorrect Usage: the required arguments `REPO_NAME` and `URL` were not provided"))
				Eventually(session.Out).Should(Say("USAGE:"))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when only one argument is provided", func() {
			It("fails with incorrect usage message and displays help", func() {
				session := helpers.CF("add-plugin-repo", "repo-name")
				Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `URL` was not provided"))
				Eventually(session.Out).Should(Say("USAGE:"))
				Eventually(session).Should(Exit(1))
			})
		})
	})

	Context("when the user provides a url without a protocol scheme", func() {
		It("defaults to 'https://'", func() {
			session := helpers.CF("add-plugin-repo", "some-repo", "example.com/repo")
			Eventually(session.Err).Should(Say("Could not add repository 'some-repo' from https://example\\.com/repo:"))
		})
	})

	Context("when the provided URL is a valid plugin repository", func() {
		var server *Server
		var serverURL string
		BeforeEach(func() {
			server, serverURL = helpers.NewPluginRepositoryTLSServer(helpers.PluginRepository{
				Plugins: []helpers.Plugin{},
			})
		})

		AfterEach(func() {
			server.Close()
		})

		It("displays OK and exits 0", func() {
			session := helpers.CF("add-plugin-repo", "repo1", serverURL)
			Eventually(session).Should(Exit(0))
		})

		Context("when the repo URL is already in use", func() {
			BeforeEach(func() {
				session := helpers.CF("add-plugin-repo", "repo1", serverURL)
				Eventually(session.Out).Should(Say("%s added as 'repo1'", serverURL))
				Eventually(session).Should(Exit(0))
			})

			It("errors and says the repo URL has already been added", func() {
				session := helpers.CF("add-plugin-repo", "some-repo", serverURL)

				Eventually(session.Err).Should(Say("%s \\(%s\\) already exists\\.", serverURL, "repo1"))
				Eventually(session).Should(Exit(1))
			})

			Context("when omitting the schema from the serverURL", func() {
				It("attempts to connect to https:// ", func() {
					session := helpers.CF("add-plugin-repo", "some-repo", strings.TrimPrefix(serverURL, "https://"))

					Eventually(session.Err).Should(Say("https://127\\.0\\.0\\.1:\\d{1,5} \\(repo1\\) already exists\\."))
					Eventually(session).Should(Exit(1))
				})
			})
		})

		Context("when the repo name is already in use", func() {
			BeforeEach(func() {
				session := helpers.CF("add-plugin-repo", "repo1", serverURL)
				Eventually(session).Should(Exit(0))
			})

			It("errors and says the repo name is taken", func() {
				session := helpers.CF("add-plugin-repo", "repo1", "some-other-url")

				Eventually(session.Err).Should(Say("Plugin repo named 'repo1' already exists, please use another name\\."))
				Eventually(session).Should(Exit(1))
			})
		})
	})

	Context("when the provided URL is NOT a valid plugin repository", func() {
		var server *Server
		BeforeEach(func() {
			server = NewTLSServer()
			// Suppresses ginkgo server logs
			server.HTTPTestServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
		})

		AfterEach(func() {
			server.Close()
		})

		Context("when the protocol is unsupported", func() {
			It("reports an appropriate error", func() {
				session := helpers.CF("add-plugin-repo", "repo1", "ftp://example.com/repo")

				Eventually(session.Err).Should(Say("Could not add repository 'repo1' from ftp://example\\.com/repo: Get ftp://example\\.com/list: unsupported protocol scheme \"ftp\""))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when the domain cannot be reached", func() {
			It("reports an appropriate error", func() {
				session := helpers.CF("add-plugin-repo", "repo1", "cfpluginrepothatdoesnotexist.cf-app.com")

				Eventually(session.Err).Should(Say("Could not add repository 'repo1' from https://cfpluginrepothatdoesnotexist\\.cf-app\\.com: Get https://cfpluginrepothatdoesnotexist\\.cf-app\\.com/list: dial tcp: lookup cfpluginrepothatdoesnotexist\\.cf-app\\.com on 127\\.0\\.1\\.1:\\d{1,5}: no such host"))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when the path cannot be found", func() {
			BeforeEach(func() {
				server.AppendHandlers(
					RespondWith(http.StatusNotFound, "foobar"),
				)
			})

			It("returns an appropriate error", func() {
				session := helpers.CF("add-plugin-repo", "repo1", server.URL())

				Eventually(session.Err).Should(Say("Could not add repository 'repo1' from https://127\\.0\\.0\\.1:\\d{1,5}: Error Code: 404"))
				Eventually(session.Err).Should(Say("Raw Response: foobar"))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when the response is not parseable", func() {
			BeforeEach(func() {
				server.AppendHandlers(
					RespondWith(http.StatusOK, `{"plugins":[}`),
				)
			})

			It("returns an appropriate error", func() {
				session := helpers.CF("add-plugin-repo", "repo1", server.URL())

				Eventually(session.Err).Should(Say("Could not add repository 'repo1' from https://127\\.0\\.0\\.1:\\d{1,5}: invalid character '}' looking for beginning of value"))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})
	})
})
<commit_msg>add a missed `Eventually...Exit(1))`<commit_after>package plugin

import (
	"bytes"
	"log"
	"net/http"
	"strings"

	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
	. "github.com/onsi/gomega/ghttp"
)

var _ = Describe("add-plugin-repo command", func() {
	BeforeEach(func() {
		helpers.RunIfExperimental("skipping until approved")
	})

	Describe("help", func() {
		Context("when --help flag is provided", func() {
			It("displays command usage to output", func() {
				session := helpers.CF("add-plugin-repo", "--help")
				Eventually(session.Out).Should(Say("NAME:"))
				Eventually(session.Out).Should(Say("add-plugin-repo - Add a new plugin repository"))
				Eventually(session.Out).Should(Say("USAGE:"))
				Eventually(session.Out).Should(Say("cf add-plugin-repo REPO_NAME URL"))
				Eventually(session.Out).Should(Say("EXAMPLES"))
				Eventually(session.Out).Should(Say("cf add-plugin-repo ExampleRepo https://example\\.com/repo"))
				Eventually(session.Out).Should(Say("SEE ALSO:"))
				Eventually(session.Out).Should(Say("install-plugin, list-plugin-repos"))
				Eventually(session).Should(Exit(0))
			})
		})
	})

	Context("when the command line arguments are invalid", func() {
		Context("when no arguments are provided", func() {
			It("fails with incorrect usage message and displays help", func() {
				session := helpers.CF("add-plugin-repo")
				Eventually(session.Err).Should(Say("Incorrect Usage: the required arguments `REPO_NAME` and `URL` were not provided"))
				Eventually(session.Out).Should(Say("USAGE:"))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when only one argument is provided", func() {
			It("fails with incorrect usage message and displays help", func() {
				session := helpers.CF("add-plugin-repo", "repo-name")
				Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `URL` was not provided"))
				Eventually(session.Out).Should(Say("USAGE:"))
				Eventually(session).Should(Exit(1))
			})
		})
	})

	Context("when the user provides a url without a protocol scheme", func() {
		It("defaults to 'https://'", func() {
			session := helpers.CF("add-plugin-repo", "some-repo", "example.com/repo")
			Eventually(session.Err).Should(Say("Could not add repository 'some-repo' from https://example\\.com/repo:"))
			Eventually(session).Should(Exit(1))
		})
	})

	Context("when the provided URL is a valid plugin repository", func() {
		var server *Server
		var serverURL string
		BeforeEach(func() {
			server, serverURL = helpers.NewPluginRepositoryTLSServer(helpers.PluginRepository{
				Plugins: []helpers.Plugin{},
			})
		})

		AfterEach(func() {
			server.Close()
		})

		It("displays OK and exits 0", func() {
			session := helpers.CF("add-plugin-repo", "repo1", serverURL)
			Eventually(session).Should(Exit(0))
		})

		Context("when the repo URL is already in use", func() {
			BeforeEach(func() {
				session := helpers.CF("add-plugin-repo", "repo1", serverURL)
				Eventually(session.Out).Should(Say("%s added as 'repo1'", serverURL))
				Eventually(session).Should(Exit(0))
			})

			It("errors and says the repo URL has already been added", func() {
				session := helpers.CF("add-plugin-repo", "some-repo", serverURL)

				Eventually(session.Err).Should(Say("%s \\(%s\\) already exists\\.", serverURL, "repo1"))
				Eventually(session).Should(Exit(1))
			})

			Context("when omitting the schema from the serverURL", func() {
				It("attempts to connect to https:// ", func() {
					session := helpers.CF("add-plugin-repo", "some-repo", strings.TrimPrefix(serverURL, "https://"))

					Eventually(session.Err).Should(Say("https://127\\.0\\.0\\.1:\\d{1,5} \\(repo1\\) already exists\\."))
					Eventually(session).Should(Exit(1))
				})
			})
		})

		Context("when the repo name is already in use", func() {
			BeforeEach(func() {
				session := helpers.CF("add-plugin-repo", "repo1", serverURL)
				Eventually(session).Should(Exit(0))
			})

			It("errors and says the repo name is taken", func() {
				session := helpers.CF("add-plugin-repo", "repo1", "some-other-url")

				Eventually(session.Err).Should(Say("Plugin repo named 'repo1' already exists, please use another name\\."))
				Eventually(session).Should(Exit(1))
			})
		})
	})

	Context("when the provided URL is NOT a valid plugin repository", func() {
		var server *Server
		BeforeEach(func() {
			server = NewTLSServer()
			// Suppresses ginkgo server logs
			server.HTTPTestServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
		})

		AfterEach(func() {
			server.Close()
		})

		Context("when the protocol is unsupported", func() {
			It("reports an appropriate error", func() {
				session := helpers.CF("add-plugin-repo", "repo1", "ftp://example.com/repo")

				Eventually(session.Err).Should(Say("Could not add repository 'repo1' from ftp://example\\.com/repo: Get ftp://example\\.com/list: unsupported protocol scheme \"ftp\""))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when the domain cannot be reached", func() {
			It("reports an appropriate error", func() {
				session := helpers.CF("add-plugin-repo", "repo1", "cfpluginrepothatdoesnotexist.cf-app.com")

				Eventually(session.Err).Should(Say("Could not add repository 'repo1' from https://cfpluginrepothatdoesnotexist\\.cf-app\\.com: Get https://cfpluginrepothatdoesnotexist\\.cf-app\\.com/list: dial tcp: lookup cfpluginrepothatdoesnotexist\\.cf-app\\.com on 127\\.0\\.1\\.1:\\d{1,5}: no such host"))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when the path cannot be found", func() {
			BeforeEach(func() {
				server.AppendHandlers(
					RespondWith(http.StatusNotFound, "foobar"),
				)
			})

			It("returns an appropriate error", func() {
				session := helpers.CF("add-plugin-repo", "repo1", server.URL())

				Eventually(session.Err).Should(Say("Could not add repository 'repo1' from https://127\\.0\\.0\\.1:\\d{1,5}: Error Code: 404"))
				Eventually(session.Err).Should(Say("Raw Response: foobar"))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when the response is not parseable", func() {
			BeforeEach(func() {
				server.AppendHandlers(
					RespondWith(http.StatusOK, `{"plugins":[}`),
				)
			})

			It("returns an appropriate error", func() {
				session := helpers.CF("add-plugin-repo", "repo1", server.URL())

				Eventually(session.Err).Should(Say("Could not add repository 'repo1' from https://127\\.0\\.0\\.1:\\d{1,5}: invalid character '}' looking for beginning of value"))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session).Should(Exit(1))
			})
		})
	})
})
<|endoftext|>


<commit_before>package main

import (
	"bitbucket.org/ant512/gobble/akismet"
	"errors"
	"fmt"
	"github.com/russross/blackfriday"
	"html"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

type FileRepository struct {
	directory string
	posts     BlogPosts
	tags      map[string]int
	mutex     sync.RWMutex
}

func NewFileRepository(directory string) *FileRepository {

	f := new(FileRepository)
	f.directory = directory

	go f.update()

	return f
}

func (f *FileRepository) AllTags() map[string]int {
	return f.tags
}

func (f *FileRepository) AllPosts() BlogPosts {
	return f.posts
}

func (f *FileRepository) PostWithUrl(url string) (*BlogPost, error) {

	f.mutex.RLock()
	defer f.mutex.RUnlock()

	for i := range f.posts {
		if f.posts[i].Url() == url {
			return f.posts[i], nil
		}
	}

	err := errors.New("Could not find post")

	return nil, err
}

func (f *FileRepository) PostWithId(id int) (*BlogPost, error) {

	f.mutex.RLock()
	defer f.mutex.RUnlock()

	for i := range f.posts {
		if f.posts[i].Id == id {
			return f.posts[i], nil
		}
	}

	err := errors.New("Could not find post")

	return nil, err
}

func (f *FileRepository) PostsWithTag(tag string, start int, count int) (BlogPosts, int) {

	f.mutex.RLock()
	defer f.mutex.RUnlock()

	filteredPosts := BlogPosts{}

	for i := range f.posts {
		if f.posts[i].ContainsTag(tag) {
			filteredPosts = append(filteredPosts, f.posts[i])
		}
	}

	if start > len(filteredPosts) {
		return BlogPosts{}, 0
	}

	if start+count > len(filteredPosts) {
		count = len(filteredPosts) - start
	}

	return filteredPosts[start : start+count], len(filteredPosts)
}

func (f *FileRepository) SearchPosts(term string, start int, count int) (BlogPosts, int) {

	f.mutex.RLock()
	defer f.mutex.RUnlock()

	filteredPosts := BlogPosts{}

	if len(term) > 0 {
		for i := range f.posts {
			if f.posts[i].ContainsTerm(term) {
				filteredPosts = append(filteredPosts, f.posts[i])
			}
		}
	} else {
		filteredPosts = f.posts
	}

	if start > len(filteredPosts) {
		return BlogPosts{}, 0
	}

	if start+count > len(filteredPosts) {
		count = len(filteredPosts) - start
	}

	return filteredPosts[start : start+count], len(filteredPosts)
}

func (f *FileRepository) SaveComment(post *BlogPost, akismetAPIKey, serverAddress, remoteAddress, userAgent, referer, author, email, body string) {

	// TODO: Ensure file name is unique
	isSpam, _ := akismet.IsSpamComment(body, serverAddress, remoteAddress, userAgent, referer, author, email, akismetAPIKey)

	log.Println(isSpam)
	log.Println(serverAddress)
	log.Printf("%s", remoteAddress)
	log.Println(userAgent)
	log.Println(body)
	log.Println(referer)
	log.Println(author)
	log.Println(email)
	log.Println(akismetAPIKey)

	comment := new(Comment)

	comment.Author = html.EscapeString(author)
	comment.Email = html.EscapeString(email)
	comment.Date = time.Now()
	comment.Body = html.EscapeString(body)
	comment.IsSpam = isSpam

	f.mutex.Lock()
	post.Comments = append(post.Comments, comment)
	f.mutex.Unlock()

	postPath := post.FilePath[:len(post.FilePath)-3]

	dirname := postPath + string(filepath.Separator) + "comments" + string(filepath.Separator)

	filename := timeToFilename(comment.Date)

	log.Println(dirname + filename)
	os.MkdirAll(dirname, 0775)

	content := "Author: " + comment.Author + "\n"
	content += "Email: " + comment.Email + "\n"
	content += "Date: " + timeToString(comment.Date) + "\n"

	if isSpam {
		content += "Spam: true\n"
	}

	content += "\n"

	content += comment.Body

	err := ioutil.WriteFile(dirname+filename, []byte(content), 0644)

	if err != nil {
		log.Println(err)
	}
}

func (f *FileRepository) update() {
	for {
		start := time.Now()

		f.fetchAllPosts()
		f.fetchAllTags()

		end := time.Now()
		log.Printf("Cached %v posts in %v", len(f.posts), end.Sub(start))

		time.Sleep(10 * time.Minute)
	}
}

func (f *FileRepository) fetchAllPosts() error {

	dirname := f.directory + string(filepath.Separator)

	files, err := ioutil.ReadDir(dirname)

	posts := BlogPosts{}

	for i := range files {

		if files[i].IsDir() {
			continue
		}

		if filepath.Ext(files[i].Name()) != ".md" {
			continue
		}

		post, err := f.fetchPost(dirname + files[i].Name())

		if err != nil {
			return err
		}

		posts = append(posts, post)
	}

	sort.Sort(posts)

	f.mutex.Lock()
	f.posts = posts
	f.mutex.Unlock()

	return err
}

func (f *FileRepository) fetchAllTags() {

	// We're using a map to simulate a set
	tags := make(map[string]int)

	f.mutex.RLock()

	for i := range f.posts {
		for j := range f.posts[i].Tags {

			value := tags[strings.ToLower(f.posts[i].Tags[j])] + 1
			tags[strings.ToLower(f.posts[i].Tags[j])] = value
		}
	}

	f.mutex.RUnlock()

	f.mutex.Lock()
	f.tags = tags
	f.mutex.Unlock()
}

func (f *FileRepository) fetchPost(filename string) (*BlogPost, error) {

	post := new(BlogPost)
	post.FilePath = filename

	file, err := ioutil.ReadFile(filename)

	if err != nil {
		return post, err
	}

	file = []byte(extractPostHeader(string(file), post))

	htmlFlags := blackfriday.HTML_USE_SMARTYPANTS
	extensions := blackfriday.EXTENSION_AUTOLINK | blackfriday.EXTENSION_HARD_LINE_BREAK | blackfriday.EXTENSION_FENCED_CODE | blackfriday.EXTENSION_NO_INTRA_EMPHASIS

	renderer := blackfriday.HtmlRenderer(htmlFlags, post.Title, "")

	output := blackfriday.Markdown(file, renderer, extensions)

	post.Body = string(output)

	f.fetchCommentsForPost(post, filename)

	return post, nil
}

func (f *FileRepository) fetchCommentsForPost(post *BlogPost, filename string) {

	dirname := filename[:len(filename)-3] + string(filepath.Separator) + "comments" + string(filepath.Separator)

	files, err := ioutil.ReadDir(dirname)

	if err != nil {
		return
	}

	post.Comments = Comments{}

	for i := range files {

		if files[i].IsDir() {
			continue
		}

		if filepath.Ext(files[i].Name()) != ".md" {
			continue
		}

		comment, err := f.fetchComment(dirname + files[i].Name())

		if err != nil {
			log.Fatal(err)
			return
		}

		post.Comments = append(post.Comments, comment)
	}
}

func (f *FileRepository) fetchComment(filename string) (*Comment, error) {
	comment := new(Comment)

	file, err := ioutil.ReadFile(filename)

	if err != nil {
		return comment, err
	}

	file = []byte(extractCommentHeader(string(file), comment))

	htmlFlags := blackfriday.HTML_USE_SMARTYPANTS
	extensions := blackfriday.EXTENSION_AUTOLINK | blackfriday.EXTENSION_HARD_LINE_BREAK | blackfriday.EXTENSION_FENCED_CODE | blackfriday.EXTENSION_NO_INTRA_EMPHASIS

	renderer := blackfriday.HtmlRenderer(htmlFlags, "", "")

	output := blackfriday.Markdown(file, renderer, extensions)

	comment.Body = string(output)

	return comment, nil
}

func extractCommentHeader(text string, comment *Comment) string {

	lines := strings.Split(text, "\n")

	headerSize := 0

	for i := range lines {
		if strings.Contains(lines[i], ":") {
			components := strings.Split(lines[i], ":")

			header := strings.ToLower(strings.Trim(components[0], " "))
			separatorIndex := strings.Index(lines[i], ":") + 1
			data := strings.Trim(lines[i][separatorIndex:], " ")

			switch header {
			case "author":
				comment.Author = data
			case "email":
				comment.Email = data
			case "date":
				comment.Date = stringToTime(data)
			case "spam":
				comment.IsSpam = data == "true"
			default:
				continue
			}

			headerSize += len(lines[i]) + 1
		} else {
			break
		}
	}

	return text[headerSize:]
}

func extractPostHeader(text string, post *BlogPost) string {

	lines := strings.Split(text, "\n")

	headerSize := 0

	for i := range lines {
		if strings.Contains(lines[i], ":") {
			components := strings.Split(lines[i], ":")

			header := strings.ToLower(strings.Trim(components[0], " "))
			separatorIndex := strings.Index(lines[i], ":") + 1
			data := strings.Trim(lines[i][separatorIndex:], " ")

			switch header {
			case "title":
				post.Title = data
			case "id":
				post.Id, _ = strconv.Atoi(data)
			case "tags":

				tags := strings.Split(data, ",")

				formattedTags := []string{}

				for j := range tags {
					tags[j] = strings.Trim(tags[j], " ")
					tags[j] = strings.Replace(tags[j], " ", "-", -1)
					tags[j] = strings.Replace(tags[j], "/", "-", -1)
					tags[j] = strings.ToLower(tags[j])

					if tags[j] != "" {
						formattedTags = append(formattedTags, tags[j])
					}
				}

				post.Tags = formattedTags
			case "date":
				post.PublishDate = stringToTime(data)
			case "disallowcomments":
				post.DisallowComments = data == "true"
			default:
				continue
			}

			headerSize += len(lines[i]) + 1
		} else {
			break
		}
	}

	return text[headerSize:]
}

func timeToFilename(t time.Time) string {
	return fmt.Sprintf("%04d-%02d-%02d_%02d-%02d-%02d.md", t.Year(), int(t.Month()), t.Day(), t.Hour(), t.Minute(), t.Second())
}

func timeToString(t time.Time) string {
	return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", t.Year(), int(t.Month()), t.Day(), t.Hour(), t.Minute(), t.Second())
}

func stringToTime(s string) time.Time {

	year, err := strconv.Atoi(s[:4])

	if err != nil {
		return time.Unix(0, 0)
	}

	month, err := strconv.Atoi(s[5:7])

	if err != nil {
		return time.Unix(0, 0)
	}

	day, err := strconv.Atoi(s[8:10])

	if err != nil {
		return time.Unix(0, 0)
	}

	hour, err := strconv.Atoi(s[11:13])

	if err != nil {
		return time.Unix(0, 0)
	}

	minute, err := strconv.Atoi(s[14:16])

	if err != nil {
		return time.Unix(0, 0)
	}

	seconds, err := strconv.Atoi(s[17:19])

	if err != nil {
		return time.Unix(0, 0)
	}

	location, err := time.LoadLocation("UTC")

	return time.Date(year, time.Month(month), day, hour, minute, seconds, 0, location)
}
<commit_msg>More Akismet logging.<commit_after>package main

import (
	"bitbucket.org/ant512/gobble/akismet"
	"errors"
	"fmt"
	"github.com/russross/blackfriday"
	"html"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

type FileRepository struct {
	directory string
	posts     BlogPosts
	tags      map[string]int
	mutex     sync.RWMutex
}

func NewFileRepository(directory string) *FileRepository {

	f := new(FileRepository)
	f.directory = directory

	go f.update()

	return f
}

func (f *FileRepository) AllTags() map[string]int {
	return f.tags
}

func (f *FileRepository) AllPosts() BlogPosts {
	return f.posts
}

func (f *FileRepository) PostWithUrl(url string) (*BlogPost, error) {

	f.mutex.RLock()
	defer f.mutex.RUnlock()

	for i := range f.posts {
		if f.posts[i].Url() == url {
			return f.posts[i], nil
		}
	}

	err := errors.New("Could not find post")

	return nil, err
}

func (f *FileRepository) PostWithId(id int) (*BlogPost, error) {

	f.mutex.RLock()
	defer f.mutex.RUnlock()

	for i := range f.posts {
		if f.posts[i].Id == id {
			return f.posts[i], nil
		}
	}

	err := errors.New("Could not find post")

	return nil, err
}

func (f *FileRepository) PostsWithTag(tag string, start int, count int) (BlogPosts, int) {

	f.mutex.RLock()
	defer f.mutex.RUnlock()

	filteredPosts := BlogPosts{}

	for i := range f.posts {
		if f.posts[i].ContainsTag(tag) {
			filteredPosts = append(filteredPosts, f.posts[i])
		}
	}

	if start > len(filteredPosts) {
		return BlogPosts{}, 0
	}

	if start+count > len(filteredPosts) {
		count = len(filteredPosts) - start
	}

	return filteredPosts[start : start+count], len(filteredPosts)
}

func (f *FileRepository) SearchPosts(term string, start int, count int) (BlogPosts, int) {

	f.mutex.RLock()
	defer f.mutex.RUnlock()

	filteredPosts := BlogPosts{}

	if len(term) > 0 {
		for i := range f.posts {
			if f.posts[i].ContainsTerm(term) {
				filteredPosts = append(filteredPosts, f.posts[i])
			}
		}
	} else {
		filteredPosts = f.posts
	}

	if start > len(filteredPosts) {
		return BlogPosts{}, 0
	}

	if start+count > len(filteredPosts) {
		count = len(filteredPosts) - start
	}

	return filteredPosts[start : start+count], len(filteredPosts)
}

func (f *FileRepository) SaveComment(post *BlogPost, akismetAPIKey, serverAddress, remoteAddress, userAgent, referer, author, email, body string) {

	// TODO: Ensure file name is unique
	isSpam, _ := akismet.IsSpamComment(body, serverAddress, remoteAddress, userAgent, referer, author, email, akismetAPIKey)

	meta := fmt.Sprintf("\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", isSpam, serverAddress, remoteAddress, userAgent, body, referer, author, email)

	log.Println(isSpam)
	log.Println(serverAddress)
	log.Printf("%s", remoteAddress)
	log.Println(userAgent)
	log.Println(body)
	log.Println(referer)
	log.Println(author)
	log.Println(email)
	log.Println(akismetAPIKey)

	comment := new(Comment)

	comment.Author = html.EscapeString(author)
	comment.Email = html.EscapeString(email)
	comment.Date = time.Now()
	comment.Body = html.EscapeString(body + meta)
	comment.IsSpam = isSpam

	f.mutex.Lock()
	post.Comments = append(post.Comments, comment)
	f.mutex.Unlock()

	postPath := post.FilePath[:len(post.FilePath)-3]

	dirname := postPath + string(filepath.Separator) + "comments" + string(filepath.Separator)

	filename := timeToFilename(comment.Date)

	log.Println(dirname + filename)
	os.MkdirAll(dirname, 0775)

	content := "Author: " + comment.Author + "\n"
	content += "Email: " + comment.Email + "\n"
	content += "Date: " + timeToString(comment.Date) + "\n"

	if isSpam {
		content += "Spam: true\n"
	}

	content += "\n"

	content += comment.Body

	err := ioutil.WriteFile(dirname+filename, []byte(content), 0644)

	if err != nil {
		log.Println(err)
	}
}

func (f *FileRepository) update() {
	for {
		start := time.Now()

		f.fetchAllPosts()
		f.fetchAllTags()

		end := time.Now()
		log.Printf("Cached %v posts in %v", len(f.posts), end.Sub(start))

		time.Sleep(10 * time.Minute)
	}
}

func (f *FileRepository) fetchAllPosts() error {

	dirname := f.directory + string(filepath.Separator)

	files, err := ioutil.ReadDir(dirname)

	posts := BlogPosts{}

	for i := range files {

		if files[i].IsDir() {
			continue
		}

		if filepath.Ext(files[i].Name()) != ".md" {
			continue
		}

		post, err := f.fetchPost(dirname + files[i].Name())

		if err != nil {
			return err
		}

		posts = append(posts, post)
	}

	sort.Sort(posts)

	f.mutex.Lock()
	f.posts = posts
	f.mutex.Unlock()

	return err
}

func (f *FileRepository) fetchAllTags() {

	// We're using a map to simulate a set
	tags := make(map[string]int)

	f.mutex.RLock()

	for i := range f.posts {
		for j := range f.posts[i].Tags {

			value := tags[strings.ToLower(f.posts[i].Tags[j])] + 1
			tags[strings.ToLower(f.posts[i].Tags[j])] = value
		}
	}

	f.mutex.RUnlock()

	f.mutex.Lock()
	f.tags = tags
	f.mutex.Unlock()
}

func (f *FileRepository) fetchPost(filename string) (*BlogPost, error) {

	post := new(BlogPost)
	post.FilePath = filename

	file, err := ioutil.ReadFile(filename)

	if err != nil {
		return post, err
	}

	file = []byte(extractPostHeader(string(file), post))

	htmlFlags := blackfriday.HTML_USE_SMARTYPANTS
	extensions := blackfriday.EXTENSION_AUTOLINK | blackfriday.EXTENSION_HARD_LINE_BREAK | blackfriday.EXTENSION_FENCED_CODE | blackfriday.EXTENSION_NO_INTRA_EMPHASIS

	renderer := blackfriday.HtmlRenderer(htmlFlags, post.Title, "")

	output := blackfriday.Markdown(file, renderer, extensions)

	post.Body = string(output)

	f.fetchCommentsForPost(post, filename)

	return post, nil
}

func (f *FileRepository) fetchCommentsForPost(post *BlogPost, filename string) {

	dirname := filename[:len(filename)-3] + string(filepath.Separator) + "comments" + string(filepath.Separator)

	files, err := ioutil.ReadDir(dirname)

	if err != nil {
		return
	}

	post.Comments = Comments{}

	for i := range files {

		if files[i].IsDir() {
			continue
		}

		if filepath.Ext(files[i].Name()) != ".md" {
			continue
		}

		comment, err := f.fetchComment(dirname + files[i].Name())

		if err != nil {
			log.Fatal(err)
			return
		}

		post.Comments = append(post.Comments, comment)
	}
}

func (f *FileRepository) fetchComment(filename string) (*Comment, error) {
	comment := new(Comment)

	file, err := ioutil.ReadFile(filename)

	if err != nil {
		return comment, err
	}

	file = []byte(extractCommentHeader(string(file), comment))

	htmlFlags := blackfriday.HTML_USE_SMARTYPANTS
	extensions := blackfriday.EXTENSION_AUTOLINK | blackfriday.EXTENSION_HARD_LINE_BREAK | blackfriday.EXTENSION_FENCED_CODE | blackfriday.EXTENSION_NO_INTRA_EMPHASIS

	renderer := blackfriday.HtmlRenderer(htmlFlags, "", "")

	output := blackfriday.Markdown(file, renderer, extensions)

	comment.Body = string(output)

	return comment, nil
}

func extractCommentHeader(text string, comment *Comment) string {

	lines := strings.Split(text, "\n")

	headerSize := 0

	for i := range lines {
		if strings.Contains(lines[i], ":") {
			components := strings.Split(lines[i], ":")

			header := strings.ToLower(strings.Trim(components[0], " "))
			separatorIndex := strings.Index(lines[i], ":") + 1
			data := strings.Trim(lines[i][separatorIndex:], " ")

			switch header {
			case "author":
				comment.Author = data
			case "email":
				comment.Email = data
			case "date":
				comment.Date = stringToTime(data)
			case "spam":
				comment.IsSpam = data == "true"
			default:
				continue
			}

			headerSize += len(lines[i]) + 1
		} else {
			break
		}
	}

	return text[headerSize:]
}

func extractPostHeader(text string, post *BlogPost) string {

	lines := strings.Split(text, "\n")

	headerSize := 0

	for i := range lines {
		if strings.Contains(lines[i], ":") {
			components := strings.Split(lines[i], ":")

			header := strings.ToLower(strings.Trim(components[0], " "))
			separatorIndex := strings.Index(lines[i], ":") + 1
			data := strings.Trim(lines[i][separatorIndex:], " ")

			switch header {
			case "title":
				post.Title = data
			case "id":
				post.Id, _ = strconv.Atoi(data)
			case "tags":

				tags := strings.Split(data, ",")

				formattedTags := []string{}

				for j := range tags {
					tags[j] = strings.Trim(tags[j], " ")
					tags[j] = strings.Replace(tags[j], " ", "-", -1)
					tags[j] = strings.Replace(tags[j], "/", "-", -1)
					tags[j] = strings.ToLower(tags[j])

					if tags[j] != "" {
						formattedTags = append(formattedTags, tags[j])
					}
				}

				post.Tags = formattedTags
			case "date":
				post.PublishDate = stringToTime(data)
			case "disallowcomments":
				post.DisallowComments = data == "true"
			default:
				continue
			}

			headerSize += len(lines[i]) + 1
		} else {
			break
		}
	}

	return text[headerSize:]
}

func timeToFilename(t time.Time) string {
	return fmt.Sprintf("%04d-%02d-%02d_%02d-%02d-%02d.md", t.Year(), int(t.Month()), t.Day(), t.Hour(), t.Minute(), t.Second())
}

func timeToString(t time.Time) string {
	return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", t.Year(), int(t.Month()), t.Day(), t.Hour(), t.Minute(), t.Second())
}

func stringToTime(s string) time.Time {

	year, err := strconv.Atoi(s[:4])

	if err != nil {
		return time.Unix(0, 0)
	}

	month, err := strconv.Atoi(s[5:7])

	if err != nil {
		return time.Unix(0, 0)
	}

	day, err := strconv.Atoi(s[8:10])

	if err != nil {
		return time.Unix(0, 0)
	}

	hour, err := strconv.Atoi(s[11:13])

	if err != nil {
		return time.Unix(0, 0)
	}

	minute, err := strconv.Atoi(s[14:16])

	if err != nil {
		return time.Unix(0, 0)
	}

	seconds, err := strconv.Atoi(s[17:19])

	if err != nil {
		return time.Unix(0, 0)
	}

	location, err := time.LoadLocation("UTC")

	return time.Date(year, time.Month(month), day, hour, minute, seconds, 0, location)
}
<|endoftext|>