<commit_before>/*----------------------------------------------------------------
 *  Copyright (c) ThoughtWorks, Inc.
 *  Licensed under the Apache License, Version 2.0
 *  See LICENSE in the project root for license information.
 *----------------------------------------------------------------*/

package conceptExtractor

import (
	"testing"

	"os"

	"github.com/getgauge/gauge/config"
	"github.com/getgauge/gauge/gauge"
	"github.com/getgauge/gauge/gauge_messages"
	. "gopkg.in/check.v1"
)

func Test(t *testing.T) { TestingT(t) }

type MySuite struct{}

var _ = Suite(&MySuite{})

func (s *MySuite) SetUpTest(c *C) {
	config.ProjectRoot, _ = os.Getwd()
}

func (s *MySuite) TestExtractConceptWithoutParameters(c *C) {
	STEP := "step that takes a table"
	name := "concept"
	conceptName := &gauge_messages.Step{Name: name}
	concept, conceptText, err := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP}}, "# sdfdsf\nsome comment\n* some step\n## sce\n* step", "")

	c.Assert(err, IsNil)
	c.Assert(concept, Equals, "# concept\n* step that takes a table\n")
	c.Assert(conceptText, Equals, "* concept")
}

func (s *MySuite) TestExtractConcept(c *C) {
	STEP := "step that takes a table \"arg\""
	name := "concept with \"arg\""
	conceptName := &gauge_messages.Step{Name: name}
	concept, conceptText, err := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP}}, "# sdfdsf\nsome comment\n* some step\n## sce\n* step", "")

	c.Assert(err, IsNil)
	c.Assert(concept, Equals, "# concept with <arg>\n* step that takes a table <arg>\n")
	c.Assert(conceptText, Equals, "* concept with \"arg\"")
}

func (s *MySuite) TestExtractConceptWithSkippedParameters(c *C) {
	STEP := "step that takes a table \"arg\" and \"hello again\" "
	name := "concept with \"arg\""
	conceptName := &gauge_messages.Step{Name: name}
	concept, conceptText, err := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP}}, "# sdfdsf\nsome comment\n* some step\n## sce\n* step", "")

	c.Assert(err, IsNil)
	c.Assert(concept, Equals, "# concept with <arg>\n* step that takes a table <arg> and \"hello again\"\n")
	c.Assert(conceptText, Equals, "* concept with \"arg\"")
}

func (s *MySuite) TestExtractConceptWithDynamicAndStaticParameters(c *C) {
	STEP := "step that takes a table \"arg\" and <hello again> "
	name := "concept with \"arg\" <hello again>"
	conceptName := &gauge_messages.Step{Name: name}
	concept, conceptText, err := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP}}, "# sdfdsf\n\n|hello again|name|\n|hey|hello|\n\n## sce\n* step", "")

	c.Assert(err, IsNil)
	c.Assert(concept, Equals, "# concept with <arg> <hello again>\n* step that takes a table <arg> and <hello again>\n")
	c.Assert(conceptText, Equals, "* concept with \"arg\" <hello again>")
}

func (s *MySuite) TestExtractConceptWithDynamicAndStaticParametersWithParamChar(c *C) {
	STEP := "step that takes a table \"arg <hello>\" and <hello again> "
	name := "concept with \"arg <hello>\" <hello again>"
	conceptName := &gauge_messages.Step{Name: name}
	concept, conceptText, err := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP}}, "# sdfdsf\n\n|hello again|name|\n|hey|hello|\n\n## sce\n* step", "")

	c.Assert(err, IsNil)
	c.Assert(concept, Equals, "# concept with <arg {hello}> <hello again>\n* step that takes a table <arg {hello}> and <hello again>\n")
	c.Assert(conceptText, Equals, "* concept with \"arg <hello>\" <hello again>")
}

func (s *MySuite) TestExtractConceptWithTableAsArg(c *C) {
	STEP := "step that takes a table"
	name := "concept with <table1>"
	conceptName := &gauge_messages.Step{Name: name}
	tableName := TABLE + "1"
	table := `	|id|name|
	|--|----|
	|1 |foo |
	|2 |bar |
	`
	concept, conceptText, err := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP, Table: table, ParamTableName: tableName},
		&gauge_messages.Step{Name: STEP, Table: table, ParamTableName: tableName}}, "# sdfdsf\nsome comment\n* some step\n## sce\n* step", "")

	c.Assert(err, IsNil)
	c.Assert(concept, Equals, "# concept with <table1>\n* step that takes a table <table1>\n* step that takes a table <table1>\n")
	c.Assert(conceptText, Equals, "* concept with "+`

   |id|name|
   |--|----|
   |1 |foo |
   |2 |bar |
`)
}

func (s *MySuite) TestExtractConceptWithTableAsArgAndTableWithDynamicArgs(c *C) {
	STEP := "step that takes a table"
	name := "concept with <table1>"
	conceptName := &gauge_messages.Step{Name: name}
	tableName := TABLE + "1"
	table := `	|id|name|
	|--|----|
	|1 |hello <foo> |
	|2 |bar |
	`
	concept, conceptText, _ := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP, Table: table, ParamTableName: tableName},
		&gauge_messages.Step{Name: STEP, Table: table, ParamTableName: tableName}}, "# sdfdsf\n\n|foo|name|\n|hey|hello|\n\n ##helloasdasdasd\n\n* step", "")

	c.Assert(concept, Equals, "# concept with <table1>\n* step that takes a table <table1>\n* step that takes a table <table1>\n")
	c.Assert(conceptText, Equals, "* concept with "+`

   |id|name       |
   |--|-----------|
   |1 |hello <foo>|
   |2 |bar        |
`)
}

func (s *MySuite) TestExtractConceptWithSkippedTableAsArg(c *C) {
	STEP := "step that takes a table"
	name := "concept with <table1>"
	conceptName := &gauge_messages.Step{Name: name}
	tableName := TABLE + "1"
	table := `	|id|name|
	|--|----|
	|1 |foo |
	|2 |bar |
	`
	concept, conceptText, _ := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP, Table: table, ParamTableName: tableName},
		&gauge_messages.Step{Name: STEP, Table: table, ParamTableName: tableName}, &gauge_messages.Step{Name: STEP, Table: table}}, "# sdfdsf\nsome comment\n* some step\n## sce\n* step", "")

	c.Assert(concept, Equals, "# concept with <table1>\n* step that takes a table <table1>\n* step that takes a table <table1>\n* step that takes a table "+`

   |id|name|
   |--|----|
   |1 |foo |
   |2 |bar |
`)
	c.Assert(conceptText, Equals, "* concept with "+`

   |id|name|
   |--|----|
   |1 |foo |
   |2 |bar |
`)
}

func (s *MySuite) TestExtractConceptWithTableWithDynamicArgs(c *C) {
	STEP := "step that takes a table"
	name := "concept with"
	conceptName := &gauge_messages.Step{Name: name}
	table := `|id|name|
	|--|----|
	|1 |<foo>|
	|2 |bar |
	`
	concept, conceptText, _ := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP, Table: table}},
		"# sdfdsf\n\n|foo|name|\n|hey|hello|\n\n##helloasdasdasd\n\n* step", "")

	c.Assert(concept, Equals, "# concept with <foo>\n* step that takes a table "+`

   |id|name |
   |--|-----|
   |1 |<foo>|
   |2 |bar  |
`)
	c.Assert(conceptText, Equals, "* concept with <foo>")
}

func (s *MySuite) TestReplaceText(c *C) {
	content := `Copyright 2015 ThoughtWorks, Inc.

	This file is part of Gauge.

	Gauge is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	Gauge is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Gauge.  If not, see <http://www.gnu.org/licenses/>.`

	replacement := `* concept with
     |id|name|
     |--|----|
     |1 |foo |
     |2 |bar |
`
	five := int32(5)
	ten := int32(10)
	info := &gauge_messages.TextInfo{StartingLineNo: five, EndLineNo: ten}
	finalText := replaceText(content, info, replacement)

	c.Assert(finalText, Equals, `Copyright 2015 ThoughtWorks, Inc.

	This file is part of Gauge.

* concept with
     |id|name|
     |--|----|
     |1 |foo |
     |2 |bar |

	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Gauge.  If not, see <http://www.gnu.org/licenses/>.`)
}

func (s *MySuite) TestIsDuplicateConcept(c *C) {
	concept := &gauge.Concept{ConceptStep: &gauge.Step{Value: "concept", IsConcept: true}, FileName: "sdfsdf.cpt"}
	dictionary := gauge.NewConceptDictionary()
	dictionary.ConceptsMap["sdfsdf.cpt"] = concept

	isDuplicate := isDuplicateConcept(&gauge.Step{Value: "concept"}, dictionary)

	c.Assert(isDuplicate, Equals, true)
}

func (s *MySuite) TestIsDuplicateConceptWithUniqueConcepts(c *C) {
	concept := &gauge.Concept{ConceptStep: &gauge.Step{Value: "concept", IsConcept: true}, FileName: "sdfsdf.cpt"}
	dictionary := gauge.NewConceptDictionary()
	dictionary.ConceptsMap["sdfsdf.cpt"] = concept

	isDuplicate := isDuplicateConcept(&gauge.Step{Value: "concept1"}, dictionary)

	c.Assert(isDuplicate, Equals, false)
}
<commit_msg>Fix formatting related test failures (#1797)<commit_after>/*----------------------------------------------------------------
 *  Copyright (c) ThoughtWorks, Inc.
 *  Licensed under the Apache License, Version 2.0
 *  See LICENSE in the project root for license information.
 *----------------------------------------------------------------*/

package conceptExtractor

import (
	"testing"

	"os"

	"github.com/getgauge/gauge/config"
	"github.com/getgauge/gauge/gauge"
	"github.com/getgauge/gauge/gauge_messages"
	. "gopkg.in/check.v1"
)

func Test(t *testing.T) { TestingT(t) }

type MySuite struct{}

var _ = Suite(&MySuite{})

func (s *MySuite) SetUpTest(c *C) {
	config.ProjectRoot, _ = os.Getwd()
}

func (s *MySuite) TestExtractConceptWithoutParameters(c *C) {
	STEP := "step that takes a table"
	name := "concept"
	conceptName := &gauge_messages.Step{Name: name}
	concept, conceptText, err := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP}}, "# sdfdsf\nsome comment\n* some step\n## sce\n* step", "")

	c.Assert(err, IsNil)
	c.Assert(concept, Equals, "# concept\n* step that takes a table\n")
	c.Assert(conceptText, Equals, "* concept")
}

func (s *MySuite) TestExtractConcept(c *C) {
	STEP := "step that takes a table \"arg\""
	name := "concept with \"arg\""
	conceptName := &gauge_messages.Step{Name: name}
	concept, conceptText, err := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP}}, "# sdfdsf\nsome comment\n* some step\n## sce\n* step", "")

	c.Assert(err, IsNil)
	c.Assert(concept, Equals, "# concept with <arg>\n* step that takes a table <arg>\n")
	c.Assert(conceptText, Equals, "* concept with \"arg\"")
}

func (s *MySuite) TestExtractConceptWithSkippedParameters(c *C) {
	STEP := "step that takes a table \"arg\" and \"hello again\" "
	name := "concept with \"arg\""
	conceptName := &gauge_messages.Step{Name: name}
	concept, conceptText, err := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP}}, "# sdfdsf\nsome comment\n* some step\n## sce\n* step", "")

	c.Assert(err, IsNil)
	c.Assert(concept, Equals, "# concept with <arg>\n* step that takes a table <arg> and \"hello again\"\n")
	c.Assert(conceptText, Equals, "* concept with \"arg\"")
}

func (s *MySuite) TestExtractConceptWithDynamicAndStaticParameters(c *C) {
	STEP := "step that takes a table \"arg\" and <hello again> "
	name := "concept with \"arg\" <hello again>"
	conceptName := &gauge_messages.Step{Name: name}
	concept, conceptText, err := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP}}, "# sdfdsf\n\n|hello again|name|\n|hey|hello|\n\n## sce\n* step", "")

	c.Assert(err, IsNil)
	c.Assert(concept, Equals, "# concept with <arg> <hello again>\n* step that takes a table <arg> and <hello again>\n")
	c.Assert(conceptText, Equals, "* concept with \"arg\" <hello again>")
}

func (s *MySuite) TestExtractConceptWithDynamicAndStaticParametersWithParamChar(c *C) {
	STEP := "step that takes a table \"arg <hello>\" and <hello again> "
	name := "concept with \"arg <hello>\" <hello again>"
	conceptName := &gauge_messages.Step{Name: name}
	concept, conceptText, err := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP}}, "# sdfdsf\n\n|hello again|name|\n|hey|hello|\n\n## sce\n* step", "")

	c.Assert(err, IsNil)
	c.Assert(concept, Equals, "# concept with <arg {hello}> <hello again>\n* step that takes a table <arg {hello}> and <hello again>\n")
	c.Assert(conceptText, Equals, "* concept with \"arg <hello>\" <hello again>")
}

func (s *MySuite) TestExtractConceptWithTableAsArg(c *C) {
	STEP := "step that takes a table"
	name := "concept with <table1>"
	conceptName := &gauge_messages.Step{Name: name}
	tableName := TABLE + "1"
	table := `	|id|name|
	|--|----|
	|1 |foo |
	|2 |bar |
	`
	concept, conceptText, err := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP, Table: table, ParamTableName: tableName},
		&gauge_messages.Step{Name: STEP, Table: table, ParamTableName: tableName}}, "# sdfdsf\nsome comment\n* some step\n## sce\n* step", "")

	c.Assert(err, IsNil)
	c.Assert(concept, Equals, "# concept with <table1>\n* step that takes a table <table1>\n* step that takes a table <table1>\n")
	c.Assert(conceptText, Equals, "* concept with "+`

   |id|name|
   |--|----|
   |1 |foo |
   |2 |bar |
`)
}

func (s *MySuite) TestExtractConceptWithTableAsArgAndTableWithDynamicArgs(c *C) {
	STEP := "step that takes a table"
	name := "concept with <table1>"
	conceptName := &gauge_messages.Step{Name: name}
	tableName := TABLE + "1"
	table := `	|id|name|
	|--|----|
	|1 |hello <foo> |
	|2 |bar |
	`
	concept, conceptText, _ := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP, Table: table, ParamTableName: tableName},
		&gauge_messages.Step{Name: STEP, Table: table, ParamTableName: tableName}}, "# sdfdsf\n\n|foo|name|\n|hey|hello|\n\n ##helloasdasdasd\n\n* step", "")

	c.Assert(concept, Equals, "# concept with <table1>\n* step that takes a table <table1>\n* step that takes a table <table1>\n")
	c.Assert(conceptText, Equals, "* concept with "+`

   |id|name       |
   |--|-----------|
   |1 |hello <foo>|
   |2 |bar        |
`)
}

func (s *MySuite) TestExtractConceptWithSkippedTableAsArg(c *C) {
	STEP := "step that takes a table"
	name := "concept with <table1>"
	conceptName := &gauge_messages.Step{Name: name}
	tableName := TABLE + "1"
	table := `	|id|name|
	|--|----|
	|1 |foo |
	|2 |bar |
	`
	concept, conceptText, _ := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP, Table: table, ParamTableName: tableName},
		&gauge_messages.Step{Name: STEP, Table: table, ParamTableName: tableName}, &gauge_messages.Step{Name: STEP, Table: table}}, "# sdfdsf\nsome comment\n* some step\n## sce\n* step", "")

	c.Assert(concept, Equals, "# concept with <table1>\n* step that takes a table <table1>\n* step that takes a table <table1>\n* step that takes a table"+`

   |id|name|
   |--|----|
   |1 |foo |
   |2 |bar |
`)
	c.Assert(conceptText, Equals, "* concept with "+`

   |id|name|
   |--|----|
   |1 |foo |
   |2 |bar |
`)
}

func (s *MySuite) TestExtractConceptWithTableWithDynamicArgs(c *C) {
	STEP := "step that takes a table"
	name := "concept with"
	conceptName := &gauge_messages.Step{Name: name}
	table := `|id|name|
	|--|----|
	|1 |<foo>|
	|2 |bar |
	`
	concept, conceptText, _ := getExtractedConcept(conceptName, []*gauge_messages.Step{&gauge_messages.Step{Name: STEP, Table: table}},
		"# sdfdsf\n\n|foo|name|\n|hey|hello|\n\n##helloasdasdasd\n\n* step", "")

	c.Assert(concept, Equals, "# concept with <foo>\n* step that takes a table"+`

   |id|name |
   |--|-----|
   |1 |<foo>|
   |2 |bar  |
`)
	c.Assert(conceptText, Equals, "* concept with <foo>")
}

func (s *MySuite) TestReplaceText(c *C) {
	content := `Copyright 2015 ThoughtWorks, Inc.

	This file is part of Gauge.

	Gauge is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	Gauge is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Gauge.  If not, see <http://www.gnu.org/licenses/>.`

	replacement := `* concept with
     |id|name|
     |--|----|
     |1 |foo |
     |2 |bar |
`
	five := int32(5)
	ten := int32(10)
	info := &gauge_messages.TextInfo{StartingLineNo: five, EndLineNo: ten}
	finalText := replaceText(content, info, replacement)

	c.Assert(finalText, Equals, `Copyright 2015 ThoughtWorks, Inc.

	This file is part of Gauge.

* concept with
     |id|name|
     |--|----|
     |1 |foo |
     |2 |bar |

	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Gauge.  If not, see <http://www.gnu.org/licenses/>.`)
}

func (s *MySuite) TestIsDuplicateConcept(c *C) {
	concept := &gauge.Concept{ConceptStep: &gauge.Step{Value: "concept", IsConcept: true}, FileName: "sdfsdf.cpt"}
	dictionary := gauge.NewConceptDictionary()
	dictionary.ConceptsMap["sdfsdf.cpt"] = concept

	isDuplicate := isDuplicateConcept(&gauge.Step{Value: "concept"}, dictionary)

	c.Assert(isDuplicate, Equals, true)
}

func (s *MySuite) TestIsDuplicateConceptWithUniqueConcepts(c *C) {
	concept := &gauge.Concept{ConceptStep: &gauge.Step{Value: "concept", IsConcept: true}, FileName: "sdfsdf.cpt"}
	dictionary := gauge.NewConceptDictionary()
	dictionary.ConceptsMap["sdfsdf.cpt"] = concept

	isDuplicate := isDuplicateConcept(&gauge.Step{Value: "concept1"}, dictionary)

	c.Assert(isDuplicate, Equals, false)
}
<|endoftext|>


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

// This package provides basic interfaces to I/O primitives.
// Its primary job is to wrap existing implementations of such primitives,
// such as those in package os, into shared public interfaces that
// abstract the functionality.
// It also provides buffering primitives and some other basic operations.
package io

import (
	"bytes";
	"os";
	"strings";
)

// Error represents an unexpected I/O behavior.
type Error struct {
	os.ErrorString
}

// ErrShortWrite means that a write accepted fewer bytes than requested
// but failed to return an explicit error.
var ErrShortWrite os.Error = &Error{"short write"}

// ErrUnexpectedEOF means that os.EOF was encountered in the
// middle of reading a fixed-size block or data structure.
var ErrUnexpectedEOF os.Error = &Error{"unexpected EOF"}

// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p) bytes into p.  It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered.
// Even if Read returns n < len(p),
// it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, Read conventionally
// returns what is available rather than block waiting for more.
//
// At the end of the input stream, Read returns 0, os.EOF.
// Read may return a non-zero number of bytes with a non-nil err.
// In particular, a Read that exhausts the input may return n > 0, os.EOF.
type Reader interface {
	Read(p []byte) (n int, err os.Error);
}

// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
type Writer interface {
	Write(p []byte) (n int, err os.Error);
}

// Closer is the interface that wraps the basic Close method.
type Closer interface {
	Close() os.Error;
}

// Seeker is the interface that wraps the basic Seek method.
//
// Seek sets the offset for the next Read or Write to offset,
// interpreted according to whence: 0 means relative to the origin of
// the file, 1 means relative to the current offset, and 2 means
// relative to the end.  Seek returns the new offset and an Error, if
// any.
type Seeker interface {
	Seek(offset int64, whence int) (ret int64, err os.Error);
}

// ReadWrite is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
	Reader;
	Writer;
}

// ReadCloser is the interface that groups the basic Read and Close methods.
type ReadCloser interface {
	Reader;
	Closer;
}

// WriteCloser is the interface that groups the basic Write and Close methods.
type WriteCloser interface {
	Writer;
	Closer;
}

// ReadWriteCloser is the interface that groups the basic Read, Write and Close methods.
type ReadWriteCloser interface {
	Reader;
	Writer;
	Closer;
}

// ReadSeeker is the interface that groups the basic Read and Seek methods.
type ReadSeeker interface {
	Reader;
	Seeker;
}

// WriteSeeker is the interface that groups the basic Write and Seek methods.
type WriteSeeker interface {
	Writer;
	Seeker;
}

// ReadWriteSeeker is the interface that groups the basic Read, Write and Seek methods.
type ReadWriteSeeker interface {
	Reader;
	Writer;
	Seeker;
}

// WriteString writes the contents of the string s to w, which accepts an array of bytes.
func WriteString(w Writer, s string) (n int, err os.Error) {
	return w.Write(strings.Bytes(s))
}

// ReadAtLeast reads from r into buf until it has read at least min bytes.
// It returns the number of bytes copied and an error if fewer bytes were read.
// The error is os.EOF only if no bytes were read.
// If an EOF happens after reading fewer than min bytes,
// ReadAtLeast returns ErrUnexpectedEOF.
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err os.Error) {
	n = 0;
	for n < min {
		nn, e := r.Read(buf[n:len(buf)]);
		if nn > 0 {
			n += nn
		}
		if e != nil {
			if e == os.EOF && n > 0 {
				e = ErrUnexpectedEOF;
			}
			return n, e
		}
	}
	return n, nil
}

// ReadFull reads exactly len(buf) bytes from r into buf.
// It returns the number of bytes copied and an error if fewer bytes were read.
// The error is os.EOF only if no bytes were read.
// If an EOF happens after reading some but not all the bytes,
// ReadFull returns ErrUnexpectedEOF.
func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
	return ReadAtLeast(r, buf, len(buf));
}

// Copyn copies n bytes (or until an error) from src to dst.
// It returns the number of bytes copied and the error, if any.
func Copyn(src Reader, dst Writer, n int64) (written int64, err os.Error) {
	buf := make([]byte, 32*1024);
	for written < n {
		l := len(buf);
		if d := n - written; d < int64(l) {
			l = int(d);
		}
		nr, er := src.Read(buf[0 : l]);
		if nr > 0 {
			nw, ew := dst.Write(buf[0 : nr]);
			if nw > 0 {
				written += int64(nw);
			}
			if ew != nil {
				err = ew;
				break;
			}
			if nr != nw {
				err = ErrShortWrite;
				break;
			}
		}
		if er != nil {
			err = er;
			break;
		}
	}
	return written, err
}

// Copy copies from src to dst until either EOF is reached
// on src or an error occurs.  It returns the number of bytes
// copied and the error, if any.
func Copy(src Reader, dst Writer) (written int64, err os.Error) {
	buf := make([]byte, 32*1024);
	for {
		nr, er := src.Read(buf);
		if nr > 0 {
			nw, ew := dst.Write(buf[0:nr]);
			if nw > 0 {
				written += int64(nw);
			}
			if ew != nil {
				err = ew;
				break;
			}
			if nr != nw {
				err = ErrShortWrite;
				break;
			}
		}
		if er == os.EOF {
			break;
		}
		if er != nil {
			err = er;
			break;
		}
	}
	return written, err
}

// LimitReader returns a Reader that reads from r
// but stops with os.EOF after n bytes.
func LimitReader(r Reader, n int64) Reader {
	return &limitedReader{r, n};
}

type limitedReader struct {
	r Reader;
	n int64;
}

func (l *limitedReader) Read(p []byte) (n int, err os.Error) {
	if l.n <= 0 {
		return 0, os.EOF;
	}
	if int64(len(p)) > l.n {
		p = p[0:l.n];
	}
	n, err = l.r.Read(p);
	l.n -= int64(n);
	return;
}
<commit_msg>add SectionReader, ReaderAt.<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package provides basic interfaces to I/O primitives.
// Its primary job is to wrap existing implementations of such primitives,
// such as those in package os, into shared public interfaces that
// abstract the functionality.
// It also provides buffering primitives and some other basic operations.
package io

import (
	"bytes";
	"os";
	"strings";
)

// Error represents an unexpected I/O behavior.
type Error struct {
	os.ErrorString
}

// ErrShortWrite means that a write accepted fewer bytes than requested
// but failed to return an explicit error.
var ErrShortWrite os.Error = &Error{"short write"}

// ErrUnexpectedEOF means that os.EOF was encountered in the
// middle of reading a fixed-size block or data structure.
var ErrUnexpectedEOF os.Error = &Error{"unexpected EOF"}

// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p) bytes into p.  It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered.
// Even if Read returns n < len(p),
// it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, Read conventionally
// returns what is available rather than block waiting for more.
//
// At the end of the input stream, Read returns 0, os.EOF.
// Read may return a non-zero number of bytes with a non-nil err.
// In particular, a Read that exhausts the input may return n > 0, os.EOF.
type Reader interface {
	Read(p []byte) (n int, err os.Error);
}

// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
type Writer interface {
	Write(p []byte) (n int, err os.Error);
}

// Closer is the interface that wraps the basic Close method.
type Closer interface {
	Close() os.Error;
}

// Seeker is the interface that wraps the basic Seek method.
//
// Seek sets the offset for the next Read or Write to offset,
// interpreted according to whence: 0 means relative to the origin of
// the file, 1 means relative to the current offset, and 2 means
// relative to the end.  Seek returns the new offset and an Error, if
// any.
type Seeker interface {
	Seek(offset int64, whence int) (ret int64, err os.Error);
}

// ReadWrite is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
	Reader;
	Writer;
}

// ReadCloser is the interface that groups the basic Read and Close methods.
type ReadCloser interface {
	Reader;
	Closer;
}

// WriteCloser is the interface that groups the basic Write and Close methods.
type WriteCloser interface {
	Writer;
	Closer;
}

// ReadWriteCloser is the interface that groups the basic Read, Write and Close methods.
type ReadWriteCloser interface {
	Reader;
	Writer;
	Closer;
}

// ReadSeeker is the interface that groups the basic Read and Seek methods.
type ReadSeeker interface {
	Reader;
	Seeker;
}

// WriteSeeker is the interface that groups the basic Write and Seek methods.
type WriteSeeker interface {
	Writer;
	Seeker;
}

// ReadWriteSeeker is the interface that groups the basic Read, Write and Seek methods.
type ReadWriteSeeker interface {
	Reader;
	Writer;
	Seeker;
}

// ReaderAt is the interface that wraps the basic ReadAt method.
//
// ReadAt reads len(p) bytes into p starting at offset off in the
// underlying data stream.  It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered.
//
// Even if ReadAt returns n < len(p),
// it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, ReadAt blocks
// until either all the data is available or an error occurs.
//
// At the end of the input stream, ReadAt returns 0, os.EOF.
// ReadAt may return a non-zero number of bytes with a non-nil err.
// In particular, a ReadAt that exhausts the input may return n > 0, os.EOF.
type ReaderAt interface {
	ReadAt(p []byte, off int64) (n int, err os.Error);
}

// WriterAt is the interface that wraps the basic WriteAt method.
//
// WriteAt writes len(p) bytes from p to the underlying data stream
// at offset off.  It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// WriteAt must return a non-nil error if it returns n < len(p).
type WriterAt interface {
	WriteAt(p []byte, off int64) (n int, err os.Error);
}

// WriteString writes the contents of the string s to w, which accepts an array of bytes.
func WriteString(w Writer, s string) (n int, err os.Error) {
	return w.Write(strings.Bytes(s))
}

// ReadAtLeast reads from r into buf until it has read at least min bytes.
// It returns the number of bytes copied and an error if fewer bytes were read.
// The error is os.EOF only if no bytes were read.
// If an EOF happens after reading fewer than min bytes,
// ReadAtLeast returns ErrUnexpectedEOF.
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err os.Error) {
	n = 0;
	for n < min {
		nn, e := r.Read(buf[n:len(buf)]);
		if nn > 0 {
			n += nn
		}
		if e != nil {
			if e == os.EOF && n > 0 {
				e = ErrUnexpectedEOF;
			}
			return n, e
		}
	}
	return n, nil
}

// ReadFull reads exactly len(buf) bytes from r into buf.
// It returns the number of bytes copied and an error if fewer bytes were read.
// The error is os.EOF only if no bytes were read.
// If an EOF happens after reading some but not all the bytes,
// ReadFull returns ErrUnexpectedEOF.
func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
	return ReadAtLeast(r, buf, len(buf));
}

// Copyn copies n bytes (or until an error) from src to dst.
// It returns the number of bytes copied and the error, if any.
func Copyn(src Reader, dst Writer, n int64) (written int64, err os.Error) {
	buf := make([]byte, 32*1024);
	for written < n {
		l := len(buf);
		if d := n - written; d < int64(l) {
			l = int(d);
		}
		nr, er := src.Read(buf[0 : l]);
		if nr > 0 {
			nw, ew := dst.Write(buf[0 : nr]);
			if nw > 0 {
				written += int64(nw);
			}
			if ew != nil {
				err = ew;
				break;
			}
			if nr != nw {
				err = ErrShortWrite;
				break;
			}
		}
		if er != nil {
			err = er;
			break;
		}
	}
	return written, err
}

// Copy copies from src to dst until either EOF is reached
// on src or an error occurs.  It returns the number of bytes
// copied and the error, if any.
func Copy(src Reader, dst Writer) (written int64, err os.Error) {
	buf := make([]byte, 32*1024);
	for {
		nr, er := src.Read(buf);
		if nr > 0 {
			nw, ew := dst.Write(buf[0:nr]);
			if nw > 0 {
				written += int64(nw);
			}
			if ew != nil {
				err = ew;
				break;
			}
			if nr != nw {
				err = ErrShortWrite;
				break;
			}
		}
		if er == os.EOF {
			break;
		}
		if er != nil {
			err = er;
			break;
		}
	}
	return written, err
}

// LimitReader returns a Reader that reads from r
// but stops with os.EOF after n bytes.
func LimitReader(r Reader, n int64) Reader {
	return &limitedReader{r, n};
}

type limitedReader struct {
	r Reader;
	n int64;
}

func (l *limitedReader) Read(p []byte) (n int, err os.Error) {
	if l.n <= 0 {
		return 0, os.EOF;
	}
	if int64(len(p)) > l.n {
		p = p[0:l.n];
	}
	n, err = l.r.Read(p);
	l.n -= int64(n);
	return;
}

// NewSectionReader returns a SectionReader that reads from r
// starting at offset off and stops with os.EOF after n bytes.
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader {
	return &SectionReader{r, off, off, off+n};
}

// SectionReader implements Read, Seek, and ReadAt on a section
// of an underlying ReaderAt.
type SectionReader struct {
	r ReaderAt;
	base int64;
	off int64;
	limit int64;
}

func (s *SectionReader) Read(p []byte) (n int, err os.Error) {
	if s.off >= s.limit {
		return 0, os.EOF;
	}
	if max := s.limit - s.off; int64(len(p)) > max {
		p = p[0:max];
	}
	n, err = s.r.ReadAt(p, s.off);
	s.off += int64(n);
	return;
}

func (s *SectionReader) Seek(offset int64, whence int) (ret int64, err os.Error) {
	switch whence {
	default:
		return 0, os.EINVAL
	case 0:
		offset += s.base
	case 1:
		offset += s.off
	case 2:
		offset += s.limit
	}
	if offset < s.off || offset > s.limit {
		return 0, os.EINVAL
	}
	s.off = offset;
	return offset - s.base, nil
}

func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err os.Error) {
	if off < 0 || off >= s.limit - s.base {
		return 0, os.EOF;
	}
	off += s.base;
	if max := s.limit - off; int64(len(p)) > max {
		p = p[0:max];
	}
	return s.r.ReadAt(p, off);
}

<|endoftext|>


<commit_before>package tagging

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"github.com/olivere/elastic"

	"github.com/trackit/trackit/db"
	"github.com/trackit/trackit/es"
	"github.com/trackit/trackit/models"
)

// UpdateMostUsedTagsForAccount updates most used tags in MySQL for the specified AWS account
func UpdateMostUsedTagsForAccount(ctx context.Context, account int, awsAccount string) error {
	mostUsedTags, err := getMostUsedTagsForAccount(ctx, account, []string{
		"aws:cloudformation:stack-id",
		"aws:cloudformation:logical-id",
		"aws:cloudformation:stack-name",
	})
	if err != nil {
		return err
	}

	mostUsedTagsStr, err := json.Marshal(mostUsedTags)
	if err != nil {
		return err
	}

	model := models.MostUsedTag{
		AwsAccountID: account,
		ReportDate:   time.Now(),
		Tags:         string(mostUsedTagsStr),
	}
	return model.Insert(db.Db)
}

func getMostUsedTagsForAccount(ctx context.Context, account int, ignoredTags []string) ([]string, error) {
	client := es.Client
	indexName := es.IndexNameForUserId(account, destIndexName)

	indexExists, err := client.IndexExists(indexName).Do(ctx)
	if err != nil {
		return nil, err
	}
	if !indexExists {
		return []string{}, nil
	}

	filterQueries := getFilterQueriesFromIgnoredTags(ignoredTags)

	index := client.Search().Index(indexName)
	termsAgg := elastic.NewTermsAggregation().Field("tags.key").Size(5)
	filterAgg := elastic.NewFilterAggregation().Filter(elastic.NewBoolQuery().MustNot(filterQueries...)).SubAggregation("terms", termsAgg)
	nestedAgg := elastic.NewNestedAggregation().Path("tags").SubAggregation("filter", filterAgg)
	reportDateAgg := elastic.NewTermsAggregation().Field("reportDate").Order("_term", false).Size(1).SubAggregation("nested", nestedAgg)
	res, err := index.Size(0).Query(elastic.NewMatchAllQuery()).Aggregation("reportDate", reportDateAgg).Do(ctx)
	if err != nil {
		return nil, err
	}

	return processMostUsedTagsResult(res)
}

func getFilterQueriesFromIgnoredTags(ignoredTags []string) []elastic.Query {
	queries := []elastic.Query{}

	for _, ignoredTag := range ignoredTags {
		queries = append(queries, elastic.NewTermQuery("tags.key", ignoredTag))
	}

	return queries
}

func processMostUsedTagsResult(res *elastic.SearchResult) ([]string, error) {
	reportDateRes, found := res.Aggregations.Terms("reportDate")
	if !found || len(reportDateRes.Buckets) <= 0 {
		return nil, errors.New("could not query elastic search")
	}
	nestedRes, found := reportDateRes.Buckets[0].Aggregations.Nested("nested")
	if !found {
		return nil, errors.New("could not query elastic search")
	}
	filterRes, found := nestedRes.Aggregations.Filter("filter")
	if !found {
		return nil, errors.New("could not query elastic search")
	}
	termsRes, found := filterRes.Aggregations.Terms("terms")
	if !found {
		return nil, errors.New("could not query elastic search")
	}

	mostUsedTags := []string{}

	for _, result := range termsRes.Buckets {
		mostUsedTags = append(mostUsedTags, fmt.Sprintf("%s", result.Key))
	}

	return mostUsedTags, nil
}
<commit_msg>Moved list of ignored tags for most-used-tags task in global scope<commit_after>package tagging

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"github.com/olivere/elastic"

	"github.com/trackit/trackit/db"
	"github.com/trackit/trackit/es"
	"github.com/trackit/trackit/models"
)

var ignoredTags = []string{
	"aws:cloudformation:stack-id",
	"aws:cloudformation:logical-id",
	"aws:cloudformation:stack-name",
}

// UpdateMostUsedTagsForAccount updates most used tags in MySQL for the specified AWS account
func UpdateMostUsedTagsForAccount(ctx context.Context, account int, awsAccount string) error {
	mostUsedTags, err := getMostUsedTagsForAccount(ctx, account, ignoredTags)
	if err != nil {
		return err
	}

	mostUsedTagsStr, err := json.Marshal(mostUsedTags)
	if err != nil {
		return err
	}

	model := models.MostUsedTag{
		AwsAccountID: account,
		ReportDate:   time.Now(),
		Tags:         string(mostUsedTagsStr),
	}
	return model.Insert(db.Db)
}

func getMostUsedTagsForAccount(ctx context.Context, account int, ignoredTags []string) ([]string, error) {
	client := es.Client
	indexName := es.IndexNameForUserId(account, destIndexName)

	indexExists, err := client.IndexExists(indexName).Do(ctx)
	if err != nil {
		return nil, err
	}
	if !indexExists {
		return []string{}, nil
	}

	filterQueries := getFilterQueriesFromIgnoredTags(ignoredTags)

	index := client.Search().Index(indexName)
	termsAgg := elastic.NewTermsAggregation().Field("tags.key").Size(5)
	filterAgg := elastic.NewFilterAggregation().Filter(elastic.NewBoolQuery().MustNot(filterQueries...)).SubAggregation("terms", termsAgg)
	nestedAgg := elastic.NewNestedAggregation().Path("tags").SubAggregation("filter", filterAgg)
	reportDateAgg := elastic.NewTermsAggregation().Field("reportDate").Order("_term", false).Size(1).SubAggregation("nested", nestedAgg)
	res, err := index.Size(0).Query(elastic.NewMatchAllQuery()).Aggregation("reportDate", reportDateAgg).Do(ctx)
	if err != nil {
		return nil, err
	}

	return processMostUsedTagsResult(res)
}

func getFilterQueriesFromIgnoredTags(ignoredTags []string) []elastic.Query {
	queries := []elastic.Query{}

	for _, ignoredTag := range ignoredTags {
		queries = append(queries, elastic.NewTermQuery("tags.key", ignoredTag))
	}

	return queries
}

func processMostUsedTagsResult(res *elastic.SearchResult) ([]string, error) {
	reportDateRes, found := res.Aggregations.Terms("reportDate")
	if !found || len(reportDateRes.Buckets) <= 0 {
		return nil, errors.New("could not query elastic search")
	}
	nestedRes, found := reportDateRes.Buckets[0].Aggregations.Nested("nested")
	if !found {
		return nil, errors.New("could not query elastic search")
	}
	filterRes, found := nestedRes.Aggregations.Filter("filter")
	if !found {
		return nil, errors.New("could not query elastic search")
	}
	termsRes, found := filterRes.Aggregations.Terms("terms")
	if !found {
		return nil, errors.New("could not query elastic search")
	}

	mostUsedTags := []string{}

	for _, result := range termsRes.Buckets {
		mostUsedTags = append(mostUsedTags, fmt.Sprintf("%s", result.Key))
	}

	return mostUsedTags, nil
}
<|endoftext|>


<commit_before>package pagerduty

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

// https://developer.pagerduty.com/documentation/integration/events/trigger

var Endpoint = "https://events.pagerduty.com/generic/2010-04-15/create_event.json"

// Context is an interface for the contexts field in PD events.
type Context interface {
	GetType() string
}

type ContextLink struct {
	Type string `json:"type"`
	Href string `json:"href"`
	Text string `json:"text,omitempty"`
}

type ContextImage struct {
	Type string `json:"type"`
	Src  string `json:"src"`
	Href string `json:"href,omitempty"`
	Alt  string `json:"alt,omitempty"`
}

type Event struct {
	ServiceKey  string                 `json:"service_key"`
	EventType   string                 `json:"event_type"`
	Description string                 `json:"description"`
	IncidentKey string                 `json:"incident_key,omitempty"`
	Details     map[string]interface{} `json:"details,omitempty"` // arbitrary json
	Client      string                 `json:"client,omitempty"`
	ClientUrl   string                 `json:"client_url,omitempty"`
	Contexts    []Context              `json:"contexts,omitempty"`
}

type Response struct {
	Status      string   `json:"status"`
	Message     string   `json:"message"`
	IncidentKey string   `json:"incident_key,omitempty"`
	Errors      []string `json:"errors,omitempty"`
	StatusCode  int      `json:""`
}

// NewEvent returns an initialized Event structure. You probably don't
// want to use this and instead use NewTrigger/NewAck/NewResolve.
func NewEvent(serviceKey, eventType, description string) *Event {
	return &Event{
		ServiceKey:  serviceKey,
		EventType:   eventType,
		Description: description,
		Details:     make(map[string]interface{}),
		Contexts:    make([]Context, 0),
	}
}

func NewTrigger(serviceKey, description string) *Event {
	return NewEvent(serviceKey, "trigger", description)
}

func NewAck(serviceKey, description string) *Event {
	return NewEvent(serviceKey, "acknowledge", description)
}

func NewResolve(serviceKey, description string) *Event {
	return NewEvent(serviceKey, "resolve", description)
}

func NewResponse(status, message, incidentKey string) *Response {
	out := Response{
		Status:      status,
		Message:     message,
		IncidentKey: incidentKey,
		Errors:      make([]string, 0),
	}

	return &out
}

// AuthenticatedPost authenticates with the provided token and posts the
// provided body.
func AuthenticatedPost(token string, body []byte) (*http.Response, error) {
	tokenHdr := fmt.Sprintf("Token token=%s", token)
	buf := bytes.NewBuffer(body)

	req, err := http.NewRequest("POST", Endpoint, buf)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Authorization", tokenHdr)

	client := &http.Client{}
	return client.Do(req)
}

// Send posts the event to Pagerduty using the provided token.
func (e *Event) Send(token string) (*Response, error) {
	err := e.checkRequired()
	if err != nil {
		return nil, err
	}

	js, err := json.Marshal(e)
	if err != nil {
		log.Printf("json.Marshal failed: %s\n", err)
		return nil, err
	}

	resp, err := AuthenticatedPost(token, js)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

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

	if resp.StatusCode == 200 {
		out := Response{}
		err = json.Unmarshal(body, &out)
		if err != nil {
			log.Printf("json.Unmarshal failed: %s\n", err)
			return nil, err
		}
		out.StatusCode = resp.StatusCode
		return &out, nil
	} else {
		msg := fmt.Sprintf("Server returned %d: %q", resp, string(body))
		return nil, errors.New(msg)
	}
}

func (e *Event) checkRequired() error {
	et := e.EventType

	if len(et) == 0 {
		return errors.New("EventType is a required field.")
	}

	if et != "trigger" && et != "acknowledge" && et != "resolve" {
		msg := fmt.Sprintf("EventType must be one of 'trigger', 'acknowledge', or 'resolve'. Got: %q", et)
		return errors.New(msg)
	}

	if len(e.ServiceKey) == 0 {
		return errors.New("ServiceKey is a required field.")
	}

	if len(e.Description) == 0 {
		return errors.New("Description is a required field.")
	}

	return nil
}

func (c *ContextLink) GetType() string {
	return "link"
}

func (c *ContextImage) GetType() string {
	return "image"
}
<commit_msg>Always return a response, even on errors.<commit_after>package pagerduty

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

// https://developer.pagerduty.com/documentation/integration/events/trigger

var Endpoint = "https://events.pagerduty.com/generic/2010-04-15/create_event.json"

// Context is an interface for the contexts field in PD events.
type Context interface {
	GetType() string
}

type ContextLink struct {
	Type string `json:"type"`
	Href string `json:"href"`
	Text string `json:"text,omitempty"`
}

type ContextImage struct {
	Type string `json:"type"`
	Src  string `json:"src"`
	Href string `json:"href,omitempty"`
	Alt  string `json:"alt,omitempty"`
}

type Event struct {
	ServiceKey  string                 `json:"service_key"`
	EventType   string                 `json:"event_type"`
	Description string                 `json:"description"`
	IncidentKey string                 `json:"incident_key,omitempty"`
	Details     map[string]interface{} `json:"details,omitempty"` // arbitrary json
	Client      string                 `json:"client,omitempty"`
	ClientUrl   string                 `json:"client_url,omitempty"`
	Contexts    []Context              `json:"contexts,omitempty"`
}

type Response struct {
	Status      string   `json:"status"`
	Message     string   `json:"message"`
	IncidentKey string   `json:"incident_key,omitempty"`
	Errors      []string `json:"errors,omitempty"`
	StatusCode  int      `json:""`
}

// NewEvent returns an initialized Event structure. You probably don't
// want to use this and instead use NewTrigger/NewAck/NewResolve.
func NewEvent(serviceKey, eventType, description string) *Event {
	return &Event{
		ServiceKey:  serviceKey,
		EventType:   eventType,
		Description: description,
		Details:     make(map[string]interface{}),
		Contexts:    make([]Context, 0),
	}
}

func NewTrigger(serviceKey, description string) *Event {
	return NewEvent(serviceKey, "trigger", description)
}

func NewAck(serviceKey, description string) *Event {
	return NewEvent(serviceKey, "acknowledge", description)
}

func NewResolve(serviceKey, description string) *Event {
	return NewEvent(serviceKey, "resolve", description)
}

func NewResponse(status, message, incidentKey string) *Response {
	out := Response{
		Status:      status,
		Message:     message,
		IncidentKey: incidentKey,
		Errors:      make([]string, 0),
	}

	return &out
}

// AuthenticatedPost authenticates with the provided token and posts the
// provided body.
func AuthenticatedPost(token string, body []byte) (*http.Response, error) {
	tokenHdr := fmt.Sprintf("Token token=%s", token)
	buf := bytes.NewBuffer(body)

	req, err := http.NewRequest("POST", Endpoint, buf)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Authorization", tokenHdr)

	client := &http.Client{}
	return client.Do(req)
}

// Send posts the event to Pagerduty using the provided token.
func (e *Event) Send(token string) (*Response, error) {
	err := e.checkRequired()
	if err != nil {
		return e.respond("error", err.Error()), err
	}

	js, err := json.Marshal(e)
	if err != nil {
		log.Printf("json.Marshal failed: %s\n", err)
		return e.respond("error", err.Error()), err
	}

	resp, err := AuthenticatedPost(token, js)
	if err != nil {
		return e.respond("error", err.Error()), err
	}
	defer resp.Body.Close()

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

	if resp.StatusCode == 200 {
		out := Response{}
		err = json.Unmarshal(body, &out)
		if err != nil {
			log.Printf("json.Unmarshal failed: %s\n", err)
			return nil, err
		}
		out.StatusCode = resp.StatusCode
		return &out, nil
	} else {
		msg := fmt.Sprintf("Server returned %d: %q", resp, string(body))
		return e.respond("error", msg), errors.New(msg)
	}
}

func (e *Event) respond(status, message string) *Response {
	return NewResponse(status, message, e.IncidentKey)
}

func (e *Event) checkRequired() error {
	et := e.EventType

	if len(et) == 0 {
		return errors.New("EventType is a required field.")
	}

	if et != "trigger" && et != "acknowledge" && et != "resolve" {
		msg := fmt.Sprintf("EventType must be one of 'trigger', 'acknowledge', or 'resolve'. Got: %q", et)
		return errors.New(msg)
	}

	if len(e.ServiceKey) == 0 {
		return errors.New("ServiceKey is a required field.")
	}

	if len(e.Description) == 0 {
		return errors.New("Description is a required field.")
	}

	return nil
}

func (c *ContextLink) GetType() string {
	return "link"
}

func (c *ContextImage) GetType() string {
	return "image"
}
<|endoftext|>


<commit_before>package smtp

import (
	"errors"
	"fmt"
	netmail "net/mail"
	"net/smtp"
	"strings"
	"sync"

	"github.com/ije/gox/valid"
)

type SMTP struct {
	addr string
	auth smtp.Auth
}

func New(host string, port uint16, username string, password string) *SMTP {
	return &SMTP{fmt.Sprintf("%s:%d", host, port), smtp.PlainAuth("", username, password, host)}
}

func (s *SMTP) Auth() (err error) {
	c, err := smtp.Dial(s.addr)
	if err != nil {
		return
	}
	defer c.Close()
	return c.Auth(s.auth)
}

func (s *SMTP) SendMail(mail *Mail, from interface{}, to interface{}, oneToOne bool) (err error) {
	if mail == nil {
		err = errors.New("mail is nil")
		return
	}

	var sender *netmail.Address
	var recipients AddressList

	if from != nil {
		switch a := from.(type) {
		case string:
			sender, err = netmail.ParseAddress(a)
			if err != nil {
				return
			}
		case netmail.Address:
			if valid.IsEmail(a.Address) {
				sender = &a
			}
		case *netmail.Address:
			if valid.IsEmail(a.Address) {
				sender = a
			}
		}
	}
	if sender == nil {
		err = ErrEmptySender
		return
	}

	if to != nil {
		switch a := to.(type) {
		case string:
			var list []*netmail.Address
			list, err = netmail.ParseAddressList(a)
			if err != nil {
				return
			}
			recipients = AddressList(list)
		case []netmail.Address:
			for _, s := range a {
				if valid.IsEmail(s.Address) {
					recipients = append(recipients, &s)
				}
			}
		case []*netmail.Address:
			for _, s := range a {
				if valid.IsEmail(s.Address) {
					recipients = append(recipients, s)
				}
			}
		case AddressList:
			for _, s := range a {
				if valid.IsEmail(s.Address) {
					recipients = append(recipients, s)
				}
			}
		case map[string]string:
			tmp := map[string]string{}
			for name, email := range a {
				if valid.IsEmail(email) {
					tmp[email] = strings.TrimSpace(name)
				}
			}
			for email, name := range tmp {
				recipients = append(recipients, &netmail.Address{name, email})
			}
		}
	}
	if recipients == nil || len(recipients) == 0 {
		err = ErrEmptyRecipients
		return
	}

	if len(mail.Subject) == 0 {
		err = ErrEmptySubject
		return
	}

	if len(mail.PlainText) == 0 && len(mail.Html) == 0 {
		err = ErrEmptyContent
		return
	}

	if !oneToOne {
		err = smtp.SendMail(s.addr, s.auth, sender.Address, recipients.List(), mail.MakeBody(sender, recipients))
		if err != nil {
			err = &SendError{Message: err.Error(), From: sender, To: recipients}
		}
		return
	}

	var wg sync.WaitGroup
	var errs OTOSendError
	for _, recipient := range recipients {
		wg.Add(1)
		go func(to *netmail.Address) {
			err = smtp.SendMail(s.addr, s.auth, sender.Address, []string{to.Address}, mail.MakeBody(sender, AddressList{to}))
			if err != nil {
				errs.Errors = append(errs.Errors, &SendError{Message: err.Error(), From: sender, To: AddressList{to}})
			}
			wg.Done()
		}(recipient)
	}
	wg.Wait()
	if len(errs.Errors) > 0 {
		err = &errs
	}
	return
}
<commit_msg>add DefaultFrom for SMTP struct<commit_after>package smtp

import (
	"errors"
	"fmt"
	netmail "net/mail"
	"net/smtp"
	"strings"
	"sync"

	"github.com/ije/gox/valid"
)

type SMTP struct {
	DefaultFrom *netmail.Address
	addr        string
	auth        smtp.Auth
}

func New(host string, port uint16, username string, password string, defaultForm *netmail.Address) *SMTP {
	return &SMTP{defaultForm, fmt.Sprintf("%s:%d", host, port), smtp.PlainAuth("", username, password, host)}
}

func (s *SMTP) Auth() (err error) {
	c, err := smtp.Dial(s.addr)
	if err != nil {
		return
	}
	defer c.Close()
	return c.Auth(s.auth)
}

func (s *SMTP) SendMail(mail *Mail, from interface{}, to interface{}, oneToOne bool) (err error) {
	if mail == nil {
		err = errors.New("mail is nil")
		return
	}

	var sender *netmail.Address
	var recipients AddressList

	if from != nil {
		switch a := from.(type) {
		case string:
			sender, err = netmail.ParseAddress(a)
			if err != nil {
				return
			}
		case netmail.Address:
			if valid.IsEmail(a.Address) {
				sender = &a
			}
		case *netmail.Address:
			if valid.IsEmail(a.Address) {
				sender = a
			}
		}
	}
	if sender == nil {
		sender = s.DefaultFrom
	}
	if sender == nil {
		err = ErrEmptySender
		return
	}

	if to != nil {
		switch a := to.(type) {
		case string:
			var list []*netmail.Address
			list, err = netmail.ParseAddressList(a)
			if err != nil {
				return
			}
			recipients = AddressList(list)
		case []netmail.Address:
			for _, s := range a {
				if valid.IsEmail(s.Address) {
					recipients = append(recipients, &s)
				}
			}
		case []*netmail.Address:
			for _, s := range a {
				if valid.IsEmail(s.Address) {
					recipients = append(recipients, s)
				}
			}
		case AddressList:
			for _, s := range a {
				if valid.IsEmail(s.Address) {
					recipients = append(recipients, s)
				}
			}
		case map[string]string:
			tmp := map[string]string{}
			for name, email := range a {
				if valid.IsEmail(email) {
					tmp[email] = strings.TrimSpace(name)
				}
			}
			for email, name := range tmp {
				recipients = append(recipients, &netmail.Address{name, email})
			}
		}
	}
	if recipients == nil || len(recipients) == 0 {
		err = ErrEmptyRecipients
		return
	}

	if len(mail.Subject) == 0 {
		err = ErrEmptySubject
		return
	}

	if len(mail.PlainText) == 0 && len(mail.Html) == 0 {
		err = ErrEmptyContent
		return
	}

	if !oneToOne {
		err = smtp.SendMail(s.addr, s.auth, sender.Address, recipients.List(), mail.MakeBody(sender, recipients))
		if err != nil {
			err = &SendError{Message: err.Error(), From: sender, To: recipients}
		}
		return
	}

	var wg sync.WaitGroup
	var errs OTOSendError
	for _, recipient := range recipients {
		wg.Add(1)
		go func(to *netmail.Address) {
			err = smtp.SendMail(s.addr, s.auth, sender.Address, []string{to.Address}, mail.MakeBody(sender, AddressList{to}))
			if err != nil {
				errs.Errors = append(errs.Errors, &SendError{Message: err.Error(), From: sender, To: AddressList{to}})
			}
			wg.Done()
		}(recipient)
	}
	wg.Wait()
	if len(errs.Errors) > 0 {
		err = &errs
	}
	return
}
<|endoftext|>


<commit_before>package plugins

import (
	"github.com/kayex/sirius/model"
	"math/rand"
	"strings"
)

var endings = []string{
	"casino",
	"linguini",
	"bambino",
	"ripperoni",
}

type Ripperino struct{}

func (r *Ripperino) Run(m model.Message) []Transformation {
	if !strings.HasPrefix(m.Text, "ripperino") {
		return NoTransformation()
	}

	// 1 in 10 times, go full Grino
	if rand.Int() % 10 == 1 {
		return []Transformation{rapperGrino()}
	}

	return []Transformation{Append(getRandomEnding())}
}

func rapperGrino() Transformation {
	return Substitute("ripperino", "~ripperino~ RAPPER GRINO")
}

func getRandomEnding() string {
	e := rand.Int() % len(endings)
	return endings[e]
}
<commit_msg>Add space<commit_after>package plugins

import (
	"github.com/kayex/sirius/model"
	"math/rand"
	"strings"
)

var endings = []string{
	"casino",
	"linguini",
	"bambino",
	"ripperoni",
}

type Ripperino struct{}

func (r *Ripperino) Run(m model.Message) []Transformation {
	if !strings.HasPrefix(m.Text, "ripperino") {
		return NoTransformation()
	}

	// 1 in 10 times, go full Grino
	if rand.Int() % 10 == 1 {
		return []Transformation{rapperGrino()}
	}

	return []Transformation{Append(" " + getRandomEnding())}
}

func rapperGrino() Transformation {
	return Substitute("ripperino", "~ripperino~ RAPPER GRINO")
}

func getRandomEnding() string {
	e := rand.Int() % len(endings)
	return endings[e]
}
<|endoftext|>


<commit_before>// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package kustfile

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"reflect"
	"regexp"
	"strings"

	"sigs.k8s.io/kustomize/api/filesys"
	"sigs.k8s.io/kustomize/api/konfig"
	"sigs.k8s.io/kustomize/api/types"
	"sigs.k8s.io/yaml"
)

var fieldMarshallingOrder = determineFieldOrder()

// determineFieldOrder returns a slice of Kustomization field
// names in the preferred order for serialization to a file.
// The field list is checked against the actual struct type
// to confirm that all fields are present, and no unknown
// fields are specified. Deprecated fields are removed from
// the list, meaning they will drop to the bottom on output
// (if present). The ordering and/or deprecation of fields
// in nested structs is not determined or considered.
func determineFieldOrder() []string {
	m := make(map[string]bool)
	s := reflect.ValueOf(&types.Kustomization{}).Elem()
	typeOfT := s.Type()
	for i := 0; i < s.NumField(); i++ {
		m[typeOfT.Field(i).Name] = false
	}

	ordered := []string{
		"Resources",
		"Bases",
		"NamePrefix",
		"NameSuffix",
		"Namespace",
		"Crds",
		"CommonLabels",
		"CommonAnnotations",
		"PatchesStrategicMerge",
		"PatchesJson6902",
		"Patches",
		"ConfigMapGenerator",
		"SecretGenerator",
		"GeneratorOptions",
		"Vars",
		"Images",
		"Replicas",
		"Configurations",
		"Generators",
		"Transformers",
		"Inventory",
		"Components",
	}

	// Add deprecated fields here.
	deprecated := map[string]bool{}

	// Account for the inlined TypeMeta fields.
	var result []string
	result = append(result, "APIVersion", "Kind")
	m["TypeMeta"] = true

	// Make sure all these fields are recognized.
	for _, n := range ordered {
		if _, ok := m[n]; ok {
			m[n] = true
		} else {
			log.Fatalf("%s is not a recognized field.", n)
		}
		// Keep if not deprecated.
		if _, f := deprecated[n]; !f {
			result = append(result, n)
		}
	}
	return result
}

// commentedField records the comment associated with a kustomization field
// field has to be a recognized kustomization field
// comment can be empty
type commentedField struct {
	field   string
	comment []byte
}

func (cf *commentedField) appendComment(comment []byte) {
	cf.comment = append(cf.comment, comment...)
}

func squash(x [][]byte) []byte {
	return bytes.Join(x, []byte(``))
}

type kustomizationFile struct {
	path           string
	fSys           filesys.FileSystem
	originalFields []*commentedField
}

// NewKustomizationFile returns a new instance.
func NewKustomizationFile(fSys filesys.FileSystem) (*kustomizationFile, error) { // nolint
	mf := &kustomizationFile{fSys: fSys}
	err := mf.validate()
	if err != nil {
		return nil, err
	}
	return mf, nil
}

func (mf *kustomizationFile) GetPath() string {
	if mf == nil {
		return ""
	}
	return mf.path
}

func (mf *kustomizationFile) validate() error {
	match := 0
	var path []string
	for _, kfilename := range konfig.RecognizedKustomizationFileNames() {
		if mf.fSys.Exists(kfilename) {
			match += 1
			path = append(path, kfilename)
		}
	}

	switch match {
	case 0:
		return fmt.Errorf(
			"Missing kustomization file '%s'.\n",
			konfig.DefaultKustomizationFileName())
	case 1:
		mf.path = path[0]
	default:
		return fmt.Errorf("Found multiple kustomization file: %v\n", path)
	}

	if mf.fSys.IsDir(mf.path) {
		return fmt.Errorf("%s should be a file", mf.path)
	}
	return nil
}

func (mf *kustomizationFile) Read() (*types.Kustomization, error) {
	data, err := mf.fSys.ReadFile(mf.path)
	if err != nil {
		return nil, err
	}
	data, err = types.FixKustomizationPreUnmarshalling(data)
	if err != nil {
		return nil, err
	}
	var k types.Kustomization
	err = k.Unmarshal(data)
	if err != nil {
		return nil, err
	}
	k.FixKustomizationPostUnmarshalling()
	err = mf.parseCommentedFields(data)
	if err != nil {
		return nil, err
	}
	return &k, err
}

func (mf *kustomizationFile) Write(kustomization *types.Kustomization) error {
	if kustomization == nil {
		return errors.New("util: kustomization file arg is nil")
	}
	data, err := mf.marshal(kustomization)
	if err != nil {
		return err
	}
	return mf.fSys.WriteFile(mf.path, data)
}

// StringInSlice returns true if the string is in the slice.
func StringInSlice(str string, list []string) bool {
	for _, v := range list {
		if v == str {
			return true
		}
	}
	return false
}

func (mf *kustomizationFile) parseCommentedFields(content []byte) error {
	buffer := bytes.NewBuffer(content)
	var comments [][]byte

	line, err := buffer.ReadBytes('\n')
	for err == nil {
		if isCommentOrBlankLine(line) {
			comments = append(comments, line)
		} else {
			matched, field := findMatchedField(line)
			if matched {
				mf.originalFields = append(mf.originalFields, &commentedField{field: field, comment: squash(comments)})
				comments = [][]byte{}
			} else if len(comments) > 0 {
				mf.originalFields[len(mf.originalFields)-1].appendComment(squash(comments))
				comments = [][]byte{}
			}
		}
		line, err = buffer.ReadBytes('\n')
	}

	if err != io.EOF {
		return err
	}
	return nil
}

// marshal converts a kustomization to a byte stream.
func (mf *kustomizationFile) marshal(kustomization *types.Kustomization) ([]byte, error) {
	var output []byte
	for _, comment := range mf.originalFields {
		output = append(output, comment.comment...)
		content, err := marshalField(comment.field, kustomization)
		if err != nil {
			return content, err
		}
		output = append(output, content...)
	}
	for _, field := range fieldMarshallingOrder {
		if mf.hasField(field) {
			continue
		}
		content, err := marshalField(field, kustomization)
		if err != nil {
			return content, nil
		}
		output = append(output, content...)
	}
	return output, nil
}

func (mf *kustomizationFile) hasField(name string) bool {
	for _, n := range mf.originalFields {
		if n.field == name {
			return true
		}
	}
	return false
}

/*
 isCommentOrBlankLine determines if a line is a comment or blank line
 Return true for following lines
 # This line is a comment
       # This line is also a comment with several leading white spaces

 (The line above is a blank line)
*/
func isCommentOrBlankLine(line []byte) bool {
	s := bytes.TrimRight(bytes.TrimLeft(line, " "), "\n")
	return len(s) == 0 || bytes.HasPrefix(s, []byte(`#`))
}

func findMatchedField(line []byte) (bool, string) {
	for _, field := range fieldMarshallingOrder {
		// (?i) is for case insensitive regexp matching
		r := regexp.MustCompile("^(" + "(?i)" + field + "):")
		if r.Match(line) {
			return true, field
		}
	}
	return false, ""
}

// marshalField marshal a given field of a kustomization object into yaml format.
// If the field wasn't in the original kustomization.yaml file or wasn't added,
// an empty []byte is returned.
func marshalField(field string, kustomization *types.Kustomization) ([]byte, error) {
	r := reflect.ValueOf(*kustomization)
	v := r.FieldByName(strings.Title(field))

	if !v.IsValid() || isEmpty(v) {
		return []byte{}, nil
	}

	k := &types.Kustomization{}
	kr := reflect.ValueOf(k)
	kv := kr.Elem().FieldByName(strings.Title(field))
	kv.Set(v)

	return yaml.Marshal(k)
}

func isEmpty(v reflect.Value) bool {
	// If v is a pointer type
	if v.Type().Kind() == reflect.Ptr {
		return v.IsNil()
	}
	return v.Len() == 0
}
<commit_msg>fix edit commands remove metadata<commit_after>// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package kustfile

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"reflect"
	"regexp"
	"strings"

	"sigs.k8s.io/kustomize/api/filesys"
	"sigs.k8s.io/kustomize/api/konfig"
	"sigs.k8s.io/kustomize/api/types"
	"sigs.k8s.io/yaml"
)

var fieldMarshallingOrder = determineFieldOrder()

// determineFieldOrder returns a slice of Kustomization field
// names in the preferred order for serialization to a file.
// The field list is checked against the actual struct type
// to confirm that all fields are present, and no unknown
// fields are specified. Deprecated fields are removed from
// the list, meaning they will drop to the bottom on output
// (if present). The ordering and/or deprecation of fields
// in nested structs is not determined or considered.
func determineFieldOrder() []string {
	m := make(map[string]bool)
	s := reflect.ValueOf(&types.Kustomization{}).Elem()
	typeOfT := s.Type()
	for i := 0; i < s.NumField(); i++ {
		m[typeOfT.Field(i).Name] = false
	}

	ordered := []string{
		"MetaData",
		"Resources",
		"Bases",
		"NamePrefix",
		"NameSuffix",
		"Namespace",
		"Crds",
		"CommonLabels",
		"CommonAnnotations",
		"PatchesStrategicMerge",
		"PatchesJson6902",
		"Patches",
		"ConfigMapGenerator",
		"SecretGenerator",
		"GeneratorOptions",
		"Vars",
		"Images",
		"Replicas",
		"Configurations",
		"Generators",
		"Transformers",
		"Inventory",
		"Components",
	}

	// Add deprecated fields here.
	deprecated := map[string]bool{}

	// Account for the inlined TypeMeta fields.
	var result []string
	result = append(result, "APIVersion", "Kind")
	m["TypeMeta"] = true

	// Make sure all these fields are recognized.
	for _, n := range ordered {
		if _, ok := m[n]; ok {
			m[n] = true
		} else {
			log.Fatalf("%s is not a recognized field.", n)
		}
		// Keep if not deprecated.
		if _, f := deprecated[n]; !f {
			result = append(result, n)
		}
	}
	return result
}

// commentedField records the comment associated with a kustomization field
// field has to be a recognized kustomization field
// comment can be empty
type commentedField struct {
	field   string
	comment []byte
}

func (cf *commentedField) appendComment(comment []byte) {
	cf.comment = append(cf.comment, comment...)
}

func squash(x [][]byte) []byte {
	return bytes.Join(x, []byte(``))
}

type kustomizationFile struct {
	path           string
	fSys           filesys.FileSystem
	originalFields []*commentedField
}

// NewKustomizationFile returns a new instance.
func NewKustomizationFile(fSys filesys.FileSystem) (*kustomizationFile, error) { // nolint
	mf := &kustomizationFile{fSys: fSys}
	err := mf.validate()
	if err != nil {
		return nil, err
	}
	return mf, nil
}

func (mf *kustomizationFile) GetPath() string {
	if mf == nil {
		return ""
	}
	return mf.path
}

func (mf *kustomizationFile) validate() error {
	match := 0
	var path []string
	for _, kfilename := range konfig.RecognizedKustomizationFileNames() {
		if mf.fSys.Exists(kfilename) {
			match += 1
			path = append(path, kfilename)
		}
	}

	switch match {
	case 0:
		return fmt.Errorf(
			"Missing kustomization file '%s'.\n",
			konfig.DefaultKustomizationFileName())
	case 1:
		mf.path = path[0]
	default:
		return fmt.Errorf("Found multiple kustomization file: %v\n", path)
	}

	if mf.fSys.IsDir(mf.path) {
		return fmt.Errorf("%s should be a file", mf.path)
	}
	return nil
}

func (mf *kustomizationFile) Read() (*types.Kustomization, error) {
	data, err := mf.fSys.ReadFile(mf.path)
	if err != nil {
		return nil, err
	}
	data, err = types.FixKustomizationPreUnmarshalling(data)
	if err != nil {
		return nil, err
	}
	var k types.Kustomization
	err = k.Unmarshal(data)
	if err != nil {
		return nil, err
	}
	k.FixKustomizationPostUnmarshalling()
	err = mf.parseCommentedFields(data)
	if err != nil {
		return nil, err
	}
	return &k, err
}

func (mf *kustomizationFile) Write(kustomization *types.Kustomization) error {
	if kustomization == nil {
		return errors.New("util: kustomization file arg is nil")
	}
	data, err := mf.marshal(kustomization)
	if err != nil {
		return err
	}
	return mf.fSys.WriteFile(mf.path, data)
}

// StringInSlice returns true if the string is in the slice.
func StringInSlice(str string, list []string) bool {
	for _, v := range list {
		if v == str {
			return true
		}
	}
	return false
}

func (mf *kustomizationFile) parseCommentedFields(content []byte) error {
	buffer := bytes.NewBuffer(content)
	var comments [][]byte

	line, err := buffer.ReadBytes('\n')
	for err == nil {
		if isCommentOrBlankLine(line) {
			comments = append(comments, line)
		} else {
			matched, field := findMatchedField(line)
			if matched {
				mf.originalFields = append(mf.originalFields, &commentedField{field: field, comment: squash(comments)})
				comments = [][]byte{}
			} else if len(comments) > 0 {
				mf.originalFields[len(mf.originalFields)-1].appendComment(squash(comments))
				comments = [][]byte{}
			}
		}
		line, err = buffer.ReadBytes('\n')
	}

	if err != io.EOF {
		return err
	}
	return nil
}

// marshal converts a kustomization to a byte stream.
func (mf *kustomizationFile) marshal(kustomization *types.Kustomization) ([]byte, error) {
	var output []byte
	for _, comment := range mf.originalFields {
		output = append(output, comment.comment...)
		content, err := marshalField(comment.field, kustomization)
		if err != nil {
			return content, err
		}
		output = append(output, content...)
	}
	for _, field := range fieldMarshallingOrder {
		if mf.hasField(field) {
			continue
		}
		content, err := marshalField(field, kustomization)
		if err != nil {
			return content, nil
		}
		output = append(output, content...)
	}
	return output, nil
}

func (mf *kustomizationFile) hasField(name string) bool {
	for _, n := range mf.originalFields {
		if n.field == name {
			return true
		}
	}
	return false
}

/*
 isCommentOrBlankLine determines if a line is a comment or blank line
 Return true for following lines
 # This line is a comment
       # This line is also a comment with several leading white spaces

 (The line above is a blank line)
*/
func isCommentOrBlankLine(line []byte) bool {
	s := bytes.TrimRight(bytes.TrimLeft(line, " "), "\n")
	return len(s) == 0 || bytes.HasPrefix(s, []byte(`#`))
}

func findMatchedField(line []byte) (bool, string) {
	for _, field := range fieldMarshallingOrder {
		// (?i) is for case insensitive regexp matching
		r := regexp.MustCompile("^(" + "(?i)" + field + "):")
		if r.Match(line) {
			return true, field
		}
	}
	return false, ""
}

// marshalField marshal a given field of a kustomization object into yaml format.
// If the field wasn't in the original kustomization.yaml file or wasn't added,
// an empty []byte is returned.
func marshalField(field string, kustomization *types.Kustomization) ([]byte, error) {
	r := reflect.ValueOf(*kustomization)
	v := r.FieldByName(strings.Title(field))

	if !v.IsValid() || isEmpty(v) {
		return []byte{}, nil
	}

	k := &types.Kustomization{}
	kr := reflect.ValueOf(k)
	kv := kr.Elem().FieldByName(strings.Title(field))
	kv.Set(v)

	return yaml.Marshal(k)
}

func isEmpty(v reflect.Value) bool {
	// If v is a pointer type
	if v.Type().Kind() == reflect.Ptr {
		return v.IsNil()
	}
	return v.Len() == 0
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/autoscaling"
	"github.com/aws/aws-sdk-go/service/ec2"
	"log"
	"time"
)

// How often flywheel will update its internal state and/or check for idle
// timeouts
const SPIN_INTERVAL = time.Second

// HTTP requests "ping" the flywheel goroutine. This updates the idle timeout,
// and returns the current status to the http request.
type Ping struct {
	replyTo      chan Pong
	requestStart bool
	requestStop  bool
}

type Pong struct {
	Status      int       `json:"-"`
	StatusName  string    `json:"status"`
	Err         error     `json:"error,omitempty"`
	LastStarted time.Time `json:"last-started,omitempty"`
	LastStopped time.Time `json:"last-stopped,omitempty"`
}

// The Flywheel struct holds all the state required by the flywheel goroutine.
type Flywheel struct {
	config      *Config
	running     bool
	pings       chan Ping
	status      int
	ready       bool
	stopAt      time.Time
	lastStarted time.Time
	lastStopped time.Time
	ec2         *ec2.EC2
	autoscaling *autoscaling.AutoScaling
	hcInterval  time.Duration
	idleTimeout time.Duration
}

func New(config *Config) *Flywheel {
	region := "ap-southeast-2"

	var hcInterval time.Duration
	var idleTimeout time.Duration

	s := config.HcInterval
	if s == "" {
		hcInterval = time.Minute
	} else {
		d, err := time.ParseDuration(s)
		if err != nil {
			log.Printf("Invalid duration: %v", err)
			hcInterval = time.Minute
		} else {
			hcInterval = d
		}
	}

	s = config.IdleTimeout
	if s == "" {
		idleTimeout = time.Minute
	} else {
		d, err := time.ParseDuration(s)
		if err != nil {
			log.Printf("Invalid duration: %v", err)
			idleTimeout = time.Minute
		} else {
			idleTimeout = d
		}
	}

	awsConfig := &aws.Config{Region: &region}
	return &Flywheel{
		hcInterval:  hcInterval,
		idleTimeout: idleTimeout,
		config:      config,
		pings:       make(chan Ping),
		stopAt:      time.Now(),
		ec2:         ec2.New(awsConfig),
		autoscaling: autoscaling.New(awsConfig),
	}
}

// Runs the main loop for the Flywheel.
// Never returns, so should probably be run as a goroutine.
func (fw *Flywheel) Spin() {
	hchan := make(chan int, 1)

	go fw.HealthWatcher(hchan)

	ticker := time.NewTicker(SPIN_INTERVAL)
	for {
		select {
		case ping := <-fw.pings:
			fw.RecvPing(&ping)
		case <-ticker.C:
			fw.Poll()
		case status := <-hchan:
			if fw.status != status {
				log.Printf("Healthcheck - status is now %v", StatusString(status))
				fw.status = status
			}
		}
	}
}

// HTTP requests "ping" the flywheel goroutine. This updates the idle timeout,
// and returns the current status to the http request.
func (fw *Flywheel) RecvPing(ping *Ping) {
	var pong Pong

	ch := ping.replyTo
	defer close(ch)

	switch fw.status {
	case STOPPED:
		if ping.requestStart {
			fw.Start()
		}

	case STARTED:
		if ping.requestStop {
			fw.Stop()
		} else {
			fw.stopAt = time.Now().Add(fw.idleTimeout)
			log.Printf("Timer update. Stop scheduled for %v", fw.stopAt)
		}
	}

	pong.Status = fw.status
	pong.StatusName = StatusString(fw.status)
	pong.LastStarted = fw.lastStarted
	pong.LastStopped = fw.lastStopped

	ch <- pong
}

// The periodic check for starting/stopping state transitions and idle
// timeouts
func (fw *Flywheel) Poll() {
	switch fw.status {
	case STARTED:
		if time.Now().After(fw.stopAt) {
			fw.Stop()
			log.Print("Idle timeout - shutting down")
			fw.status = STOPPING
		}

	case STOPPING:
		if fw.ready {
			log.Print("Shutdown complete")
			fw.status = STOPPED
		}

	case STARTING:
		if fw.ready {
			fw.status = STARTED
			fw.stopAt = time.Now().Add(fw.idleTimeout)
			log.Printf("Startup complete. Stop scheduled for %v", fw.stopAt)
		}
	}
}

// Start all the resources managed by the flywheel.
func (fw *Flywheel) Start() error {
	fw.lastStarted = time.Now()
	log.Print("Startup beginning")

	var err error
	err = fw.StartInstances()

	if err == nil {
		err = fw.UnterminateAutoScaling()
	}
	if err == nil {
		err = fw.StartAutoScaling()
	}

	if err != nil {
		log.Printf("Error starting: %v", err)
		return err
	}

	fw.ready = false
	fw.stopAt = time.Now().Add(fw.idleTimeout)
	fw.status = STARTING
	return nil
}

// Start EC2 instances
func (fw *Flywheel) StartInstances() error {
	_, err := fw.ec2.StartInstances(
		&ec2.StartInstancesInput{
			InstanceIds: fw.config.AwsInstances(),
		},
	)
	return err
}

// Restore autoscaling group instances
func (fw *Flywheel) UnterminateAutoScaling() error {
	var err error
	for groupName, size := range fw.config.AutoScaling.Terminate {
		_, err = fw.autoscaling.UpdateAutoScalingGroup(
			&autoscaling.UpdateAutoScalingGroupInput{
				AutoScalingGroupName: &groupName,
				MaxSize:              &size,
				MinSize:              &size,
			},
		)
		if err != nil {
			return err
		}
	}
	return nil
}

// Start EC2 instances in a suspended autoscale group
// @note The autoscale group isn't unsuspended here. It's done by the
//       healthcheck once all the instances are healthy.
func (fw *Flywheel) StartAutoScaling() error {
	var err error
	var awsGroupNames []*string
	for _, groupName := range fw.config.AutoScaling.Stop {
		awsGroupNames = append(awsGroupNames, &groupName)
	}

	resp, err := fw.autoscaling.DescribeAutoScalingGroups(
		&autoscaling.DescribeAutoScalingGroupsInput{
			AutoScalingGroupNames: awsGroupNames,
		},
	)
	if err != nil {
		return err
	}

	for _, group := range resp.AutoScalingGroups {
		// NOTE: Processes not unsuspended here. Needs to be triggered after
		// startup, before entering STARTED state.
		instanceIds := []*string{}
		for _, instance := range group.Instances {
			instanceIds = append(instanceIds, instance.InstanceId)
		}

		_, err := fw.ec2.StartInstances(
			&ec2.StartInstancesInput{
				InstanceIds: instanceIds,
			},
		)
		if err != nil {
			return err
		}
	}

	return nil
}

// Stop all resources managed by the flywheel
func (fw *Flywheel) Stop() error {
	fw.lastStopped = time.Now()

	var err error
	err = fw.StopInstances()

	if err == nil {
		err = fw.TerminateAutoScaling()
	}
	if err == nil {
		err = fw.StopAutoScaling()
	}

	if err != nil {
		log.Printf("Error stopping: %v", err)
		return err
	}

	fw.ready = false
	fw.status = STOPPING
	return nil
}

// Stop EC2 instances
func (fw *Flywheel) StopInstances() error {
	_, err := fw.ec2.StopInstances(
		&ec2.StopInstancesInput{
			InstanceIds: fw.config.AwsInstances(),
		},
	)
	return err
}

// Suspend ReplaceUnhealthy in an autoscale group and stop the instances.
func (fw *Flywheel) StopAutoScaling() error {
	var err error
	var awsGroupNames []*string

	if len(fw.config.AutoScaling.Stop) == 0 {
		return nil
	}

	for _, groupName := range fw.config.AutoScaling.Stop {
		awsGroupNames = append(awsGroupNames, &groupName)
	}

	resp, err := fw.autoscaling.DescribeAutoScalingGroups(
		&autoscaling.DescribeAutoScalingGroupsInput{
			AutoScalingGroupNames: awsGroupNames,
		},
	)
	if err != nil {
		return err
	}

	for _, group := range resp.AutoScalingGroups {
		_, err = fw.autoscaling.SuspendProcesses(
			&autoscaling.ScalingProcessQuery{
				AutoScalingGroupName: group.AutoScalingGroupName,
				ScalingProcesses: []*string{
					aws.String("ReplaceUnhealthy"),
				},
			},
		)
		if err != nil {
			return err
		}

		instanceIds := []*string{}
		for _, instance := range group.Instances {
			instanceIds = append(instanceIds, instance.InstanceId)
		}

		_, err := fw.ec2.StopInstances(
			&ec2.StopInstancesInput{
				InstanceIds: instanceIds,
			},
		)
		if err != nil {
			return err
		}
	}

	return nil
}

// Reduce autoscaling min/max instances to 0, causing the instances to be terminated.
func (fw *Flywheel) TerminateAutoScaling() error {
	var err error
	var zero int64
	for groupName := range fw.config.AutoScaling.Terminate {
		_, err = fw.autoscaling.UpdateAutoScalingGroup(
			&autoscaling.UpdateAutoScalingGroupInput{
				AutoScalingGroupName: &groupName,
				MaxSize:              &zero,
				MinSize:              &zero,
			},
		)
		if err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Always update idle timer on healthcheck STARTED<commit_after>package main

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/autoscaling"
	"github.com/aws/aws-sdk-go/service/ec2"
	"log"
	"time"
)

// How often flywheel will update its internal state and/or check for idle
// timeouts
const SPIN_INTERVAL = time.Second

// HTTP requests "ping" the flywheel goroutine. This updates the idle timeout,
// and returns the current status to the http request.
type Ping struct {
	replyTo      chan Pong
	requestStart bool
	requestStop  bool
}

type Pong struct {
	Status      int       `json:"-"`
	StatusName  string    `json:"status"`
	Err         error     `json:"error,omitempty"`
	LastStarted time.Time `json:"last-started,omitempty"`
	LastStopped time.Time `json:"last-stopped,omitempty"`
}

// The Flywheel struct holds all the state required by the flywheel goroutine.
type Flywheel struct {
	config      *Config
	running     bool
	pings       chan Ping
	status      int
	ready       bool
	stopAt      time.Time
	lastStarted time.Time
	lastStopped time.Time
	ec2         *ec2.EC2
	autoscaling *autoscaling.AutoScaling
	hcInterval  time.Duration
	idleTimeout time.Duration
}

func New(config *Config) *Flywheel {
	region := "ap-southeast-2"

	var hcInterval time.Duration
	var idleTimeout time.Duration

	s := config.HcInterval
	if s == "" {
		hcInterval = time.Minute
	} else {
		d, err := time.ParseDuration(s)
		if err != nil {
			log.Printf("Invalid duration: %v", err)
			hcInterval = time.Minute
		} else {
			hcInterval = d
		}
	}

	s = config.IdleTimeout
	if s == "" {
		idleTimeout = time.Minute
	} else {
		d, err := time.ParseDuration(s)
		if err != nil {
			log.Printf("Invalid duration: %v", err)
			idleTimeout = time.Minute
		} else {
			idleTimeout = d
		}
	}

	awsConfig := &aws.Config{Region: &region}
	return &Flywheel{
		hcInterval:  hcInterval,
		idleTimeout: idleTimeout,
		config:      config,
		pings:       make(chan Ping),
		stopAt:      time.Now(),
		ec2:         ec2.New(awsConfig),
		autoscaling: autoscaling.New(awsConfig),
	}
}

// Runs the main loop for the Flywheel.
// Never returns, so should probably be run as a goroutine.
func (fw *Flywheel) Spin() {
	hchan := make(chan int, 1)

	go fw.HealthWatcher(hchan)

	ticker := time.NewTicker(SPIN_INTERVAL)
	for {
		select {
		case ping := <-fw.pings:
			fw.RecvPing(&ping)
		case <-ticker.C:
			fw.Poll()
		case status := <-hchan:
			if fw.status != status {
				log.Printf("Healthcheck - status is now %v", StatusString(status))
				if status == STARTED {
					fw.stopAt = time.Now().Add(fw.idleTimeout)
					log.Printf("Timer update. Stop scheduled for %v", fw.stopAt)
				}
				fw.status = status
			}
		}
	}
}

// HTTP requests "ping" the flywheel goroutine. This updates the idle timeout,
// and returns the current status to the http request.
func (fw *Flywheel) RecvPing(ping *Ping) {
	var pong Pong

	ch := ping.replyTo
	defer close(ch)

	switch fw.status {
	case STOPPED:
		if ping.requestStart {
			fw.Start()
		}

	case STARTED:
		if ping.requestStop {
			fw.Stop()
		} else {
			fw.stopAt = time.Now().Add(fw.idleTimeout)
			log.Printf("Timer update. Stop scheduled for %v", fw.stopAt)
		}
	}

	pong.Status = fw.status
	pong.StatusName = StatusString(fw.status)
	pong.LastStarted = fw.lastStarted
	pong.LastStopped = fw.lastStopped

	ch <- pong
}

// The periodic check for starting/stopping state transitions and idle
// timeouts
func (fw *Flywheel) Poll() {
	switch fw.status {
	case STARTED:
		if time.Now().After(fw.stopAt) {
			fw.Stop()
			log.Print("Idle timeout - shutting down")
			fw.status = STOPPING
		}

	case STOPPING:
		if fw.ready {
			log.Print("Shutdown complete")
			fw.status = STOPPED
		}

	case STARTING:
		if fw.ready {
			fw.status = STARTED
			fw.stopAt = time.Now().Add(fw.idleTimeout)
			log.Printf("Startup complete. Stop scheduled for %v", fw.stopAt)
		}
	}
}

// Start all the resources managed by the flywheel.
func (fw *Flywheel) Start() error {
	fw.lastStarted = time.Now()
	log.Print("Startup beginning")

	var err error
	err = fw.StartInstances()

	if err == nil {
		err = fw.UnterminateAutoScaling()
	}
	if err == nil {
		err = fw.StartAutoScaling()
	}

	if err != nil {
		log.Printf("Error starting: %v", err)
		return err
	}

	fw.ready = false
	fw.stopAt = time.Now().Add(fw.idleTimeout)
	fw.status = STARTING
	return nil
}

// Start EC2 instances
func (fw *Flywheel) StartInstances() error {
	_, err := fw.ec2.StartInstances(
		&ec2.StartInstancesInput{
			InstanceIds: fw.config.AwsInstances(),
		},
	)
	return err
}

// Restore autoscaling group instances
func (fw *Flywheel) UnterminateAutoScaling() error {
	var err error
	for groupName, size := range fw.config.AutoScaling.Terminate {
		_, err = fw.autoscaling.UpdateAutoScalingGroup(
			&autoscaling.UpdateAutoScalingGroupInput{
				AutoScalingGroupName: &groupName,
				MaxSize:              &size,
				MinSize:              &size,
			},
		)
		if err != nil {
			return err
		}
	}
	return nil
}

// Start EC2 instances in a suspended autoscale group
// @note The autoscale group isn't unsuspended here. It's done by the
//       healthcheck once all the instances are healthy.
func (fw *Flywheel) StartAutoScaling() error {
	var err error
	var awsGroupNames []*string
	for _, groupName := range fw.config.AutoScaling.Stop {
		awsGroupNames = append(awsGroupNames, &groupName)
	}

	resp, err := fw.autoscaling.DescribeAutoScalingGroups(
		&autoscaling.DescribeAutoScalingGroupsInput{
			AutoScalingGroupNames: awsGroupNames,
		},
	)
	if err != nil {
		return err
	}

	for _, group := range resp.AutoScalingGroups {
		// NOTE: Processes not unsuspended here. Needs to be triggered after
		// startup, before entering STARTED state.
		instanceIds := []*string{}
		for _, instance := range group.Instances {
			instanceIds = append(instanceIds, instance.InstanceId)
		}

		_, err := fw.ec2.StartInstances(
			&ec2.StartInstancesInput{
				InstanceIds: instanceIds,
			},
		)
		if err != nil {
			return err
		}
	}

	return nil
}

// Stop all resources managed by the flywheel
func (fw *Flywheel) Stop() error {
	fw.lastStopped = time.Now()

	var err error
	err = fw.StopInstances()

	if err == nil {
		err = fw.TerminateAutoScaling()
	}
	if err == nil {
		err = fw.StopAutoScaling()
	}

	if err != nil {
		log.Printf("Error stopping: %v", err)
		return err
	}

	fw.ready = false
	fw.status = STOPPING
	return nil
}

// Stop EC2 instances
func (fw *Flywheel) StopInstances() error {
	_, err := fw.ec2.StopInstances(
		&ec2.StopInstancesInput{
			InstanceIds: fw.config.AwsInstances(),
		},
	)
	return err
}

// Suspend ReplaceUnhealthy in an autoscale group and stop the instances.
func (fw *Flywheel) StopAutoScaling() error {
	var err error
	var awsGroupNames []*string

	if len(fw.config.AutoScaling.Stop) == 0 {
		return nil
	}

	for _, groupName := range fw.config.AutoScaling.Stop {
		awsGroupNames = append(awsGroupNames, &groupName)
	}

	resp, err := fw.autoscaling.DescribeAutoScalingGroups(
		&autoscaling.DescribeAutoScalingGroupsInput{
			AutoScalingGroupNames: awsGroupNames,
		},
	)
	if err != nil {
		return err
	}

	for _, group := range resp.AutoScalingGroups {
		_, err = fw.autoscaling.SuspendProcesses(
			&autoscaling.ScalingProcessQuery{
				AutoScalingGroupName: group.AutoScalingGroupName,
				ScalingProcesses: []*string{
					aws.String("ReplaceUnhealthy"),
				},
			},
		)
		if err != nil {
			return err
		}

		instanceIds := []*string{}
		for _, instance := range group.Instances {
			instanceIds = append(instanceIds, instance.InstanceId)
		}

		_, err := fw.ec2.StopInstances(
			&ec2.StopInstancesInput{
				InstanceIds: instanceIds,
			},
		)
		if err != nil {
			return err
		}
	}

	return nil
}

// Reduce autoscaling min/max instances to 0, causing the instances to be terminated.
func (fw *Flywheel) TerminateAutoScaling() error {
	var err error
	var zero int64
	for groupName := range fw.config.AutoScaling.Terminate {
		_, err = fw.autoscaling.UpdateAutoScalingGroup(
			&autoscaling.UpdateAutoScalingGroupInput{
				AutoScalingGroupName: &groupName,
				MaxSize:              &zero,
				MinSize:              &zero,
			},
		)
		if err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package heroku

import (
	"fmt"
	"log"
	"strings"
	"sync"

	"github.com/cyberdelia/heroku-go/v3"
	"github.com/hashicorp/terraform/helper/schema"
)

// Global lock to prevent parallelism for heroku_addon since
// the Heroku API cannot handle a single application requesting
// multiple addons simultaneously.
var addonLock sync.Mutex

func resourceHerokuAddon() *schema.Resource {
	return &schema.Resource{
		Create: resourceHerokuAddonCreate,
		Read:   resourceHerokuAddonRead,
		Update: resourceHerokuAddonUpdate,
		Delete: resourceHerokuAddonDelete,

		Schema: map[string]*schema.Schema{
			"app": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"plan": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"config": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				ForceNew: true,
				Elem: &schema.Schema{
					Type: schema.TypeMap,
				},
			},

			"provider_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"config_vars": &schema.Schema{
				Type:     schema.TypeList,
				Computed: true,
				Elem:     &schema.Schema{Type: schema.TypeMap},
			},
		},
	}
}

func resourceHerokuAddonCreate(d *schema.ResourceData, meta interface{}) error {
	addonLock.Lock()
	defer addonLock.Unlock()

	client := meta.(*heroku.Service)

	app := d.Get("app").(string)
	opts := heroku.AddonCreateOpts{Plan: d.Get("plan").(string)}

	if v := d.Get("config"); v != nil {
		config := make(map[string]string)
		for _, v := range v.([]interface{}) {
			for k, v := range v.(map[string]interface{}) {
				config[k] = v.(string)
			}
		}

		opts.Config = &config
	}

	log.Printf("[DEBUG] Addon create configuration: %#v, %#v", app, opts)
	a, err := client.AddonCreate(app, opts)
	if err != nil {
		return err
	}

	d.SetId(a.ID)
	log.Printf("[INFO] Addon ID: %s", d.Id())

	return resourceHerokuAddonRead(d, meta)
}

func resourceHerokuAddonRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*heroku.Service)

	addon, err := resourceHerokuAddonRetrieve(
		d.Get("app").(string), d.Id(), client)
	if err != nil {
		return err
	}

	// Determine the plan. If we were configured without a specific plan,
	// then just avoid the plan altogether (accepting anything that
	// Heroku sends down).
	plan := addon.Plan.Name
	if v := d.Get("plan").(string); v != "" {
		if idx := strings.IndexRune(v, ':'); idx == -1 {
			idx = strings.IndexRune(plan, ':')
			if idx > -1 {
				plan = plan[:idx]
			}
		}
	}

	d.Set("name", addon.Name)
	d.Set("plan", plan)
	d.Set("provider_id", addon.ProviderID)

	configVarsMap := make(map[string]interface{})
	for i := range addon.ConfigVars {
		configVarsMap[addon.ConfigVars[i]] = addon.ConfigVars[i]
	}
	d.Set("config_vars", []interface{}{configVarsMap})

	return nil
}

func resourceHerokuAddonUpdate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*heroku.Service)

	app := d.Get("app").(string)

	if d.HasChange("plan") {
		ad, err := client.AddonUpdate(
			app, d.Id(), heroku.AddonUpdateOpts{Plan: d.Get("plan").(string)})
		if err != nil {
			return err
		}

		// Store the new ID
		d.SetId(ad.ID)
	}

	return resourceHerokuAddonRead(d, meta)
}

func resourceHerokuAddonDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*heroku.Service)

	log.Printf("[INFO] Deleting Addon: %s", d.Id())

	// Destroy the app
	err := client.AddonDelete(d.Get("app").(string), d.Id())
	if err != nil {
		return fmt.Errorf("Error deleting addon: %s", err)
	}

	d.SetId("")
	return nil
}

func resourceHerokuAddonRetrieve(app string, id string, client *heroku.Service) (*heroku.Addon, error) {
	addon, err := client.AddonInfo(app, id)

	if err != nil {
		return nil, fmt.Errorf("Error retrieving addon: %s", err)
	}

	return addon, nil
}
<commit_msg>Revert "Heroku returns config_vars for addon as string array."<commit_after>package heroku

import (
	"fmt"
	"log"
	"strings"
	"sync"

	"github.com/cyberdelia/heroku-go/v3"
	"github.com/hashicorp/terraform/helper/schema"
)

// Global lock to prevent parallelism for heroku_addon since
// the Heroku API cannot handle a single application requesting
// multiple addons simultaneously.
var addonLock sync.Mutex

func resourceHerokuAddon() *schema.Resource {
	return &schema.Resource{
		Create: resourceHerokuAddonCreate,
		Read:   resourceHerokuAddonRead,
		Update: resourceHerokuAddonUpdate,
		Delete: resourceHerokuAddonDelete,

		Schema: map[string]*schema.Schema{
			"app": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"plan": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"config": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				ForceNew: true,
				Elem: &schema.Schema{
					Type: schema.TypeMap,
				},
			},

			"provider_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"config_vars": &schema.Schema{
				Type:     schema.TypeList,
				Computed: true,
				Elem:     &schema.Schema{Type: schema.TypeMap},
			},
		},
	}
}

func resourceHerokuAddonCreate(d *schema.ResourceData, meta interface{}) error {
	addonLock.Lock()
	defer addonLock.Unlock()

	client := meta.(*heroku.Service)

	app := d.Get("app").(string)
	opts := heroku.AddonCreateOpts{Plan: d.Get("plan").(string)}

	if v := d.Get("config"); v != nil {
		config := make(map[string]string)
		for _, v := range v.([]interface{}) {
			for k, v := range v.(map[string]interface{}) {
				config[k] = v.(string)
			}
		}

		opts.Config = &config
	}

	log.Printf("[DEBUG] Addon create configuration: %#v, %#v", app, opts)
	a, err := client.AddonCreate(app, opts)
	if err != nil {
		return err
	}

	d.SetId(a.ID)
	log.Printf("[INFO] Addon ID: %s", d.Id())

	return resourceHerokuAddonRead(d, meta)
}

func resourceHerokuAddonRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*heroku.Service)

	addon, err := resourceHerokuAddonRetrieve(
		d.Get("app").(string), d.Id(), client)
	if err != nil {
		return err
	}

	// Determine the plan. If we were configured without a specific plan,
	// then just avoid the plan altogether (accepting anything that
	// Heroku sends down).
	plan := addon.Plan.Name
	if v := d.Get("plan").(string); v != "" {
		if idx := strings.IndexRune(v, ':'); idx == -1 {
			idx = strings.IndexRune(plan, ':')
			if idx > -1 {
				plan = plan[:idx]
			}
		}
	}

	d.Set("name", addon.Name)
	d.Set("plan", plan)
	d.Set("provider_id", addon.ProviderID)
	d.Set("config_vars", []interface{}{addon.ConfigVars})

	return nil
}

func resourceHerokuAddonUpdate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*heroku.Service)

	app := d.Get("app").(string)

	if d.HasChange("plan") {
		ad, err := client.AddonUpdate(
			app, d.Id(), heroku.AddonUpdateOpts{Plan: d.Get("plan").(string)})
		if err != nil {
			return err
		}

		// Store the new ID
		d.SetId(ad.ID)
	}

	return resourceHerokuAddonRead(d, meta)
}

func resourceHerokuAddonDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*heroku.Service)

	log.Printf("[INFO] Deleting Addon: %s", d.Id())

	// Destroy the app
	err := client.AddonDelete(d.Get("app").(string), d.Id())
	if err != nil {
		return fmt.Errorf("Error deleting addon: %s", err)
	}

	d.SetId("")
	return nil
}

func resourceHerokuAddonRetrieve(app string, id string, client *heroku.Service) (*heroku.Addon, error) {
	addon, err := client.AddonInfo(app, id)

	if err != nil {
		return nil, fmt.Errorf("Error retrieving addon: %s", err)
	}

	return addon, nil
}
<|endoftext|>