<commit_before>package main

import (
	"fmt"
	"log"
	"math/big"
	"reflect"
	"regexp"
	"sort"
	"strings"
)

var (
	// we match <magnitude> <unit>
	// where <magnitude> can look like:
	//   2
	//   2.5
	//   2 1/2
	// and <unit> can look like
	//   oz
	//   cup
	//   cups
	//   tsp
	//   etc...
	re = regexp.MustCompile(`^\s*(\d+\s+\d+/\d+|\d+|\d+\.\d+|\d+/\d+)\s+([a-zA-Z]*)\s*$`)
)

type System int8

const (
	Metric System = iota
	Imperial
)

func (sys System) String() string {
	switch sys {
	case Metric:
		return "Metric system"
	case Imperial:
		return "Imperial system"
	}
	return fmt.Sprintf("Unknown system %v", sys)
}

type OutputType byte

// semi-overlapping flags
const (
	SuppressOutput OutputType = 1 << iota // rest ignored if this is set
	MultiplesOk                           // If set, we can have more than one of this unit
	FractionsOk                           // If set, we can have less than one of this unit
)

type Volume int64

const (
	Milliliter Volume = 8
	Deciliter         = Milliliter * 100
	Liter             = Milliliter * 1000

	EighthTeaspoon  = Milliliter * 5 / 8
	QuarterTeaspoon = EighthTeaspoon * 2
	HalfTeaspoon    = QuarterTeaspoon * 2
	Teaspoon        = HalfTeaspoon * 2
	Tablespoon      = Teaspoon * 3
	FluidOunce      = Tablespoon * 2
	QuarterCup      = FluidOunce * 2
	HalfCup         = FluidOunce * 4
	ThreeQuarterCup = FluidOunce * 6
	Cup             = FluidOunce * 8
	Pint            = Cup * 2
	Quart           = Cup * 4
	Gallon          = Quart * 4
)

type unitInfo struct {
	measurement   Measurement
	system        System
	out           string
	in            []string
	decimalPlaces int
	outputType    OutputType
}

type byMeasurement []*unitInfo

func (a byMeasurement) Len() int           { return len(a) }
func (a byMeasurement) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byMeasurement) Less(i, j int) bool { return a[i].measurement.Int64() < a[j].measurement.Int64() }

var (
	volumeInfo = map[Measurement]*unitInfo{
		Milliliter: &unitInfo{
			nil,
			Metric,
			"ml",
			[]string{"ml", "milliliter", "milliliters", "millilitre", "millilitres", "mL"},
			0,
			MultiplesOk,
		},
		Deciliter: &unitInfo{
			nil,
			Metric,
			"",
			[]string{"dl", "deciliter", "deciliters", "decilitre", "decilitres", "dL"},
			0,
			SuppressOutput,
		},
		Liter: &unitInfo{
			nil,
			Metric,
			"l",
			[]string{"l", "liter", "liters", "litre", "litres", "L"},
			3,
			MultiplesOk,
		},

		EighthTeaspoon: &unitInfo{
			nil,
			Imperial,
			"1/8 tsp",
			[]string{},
			0,
			0,
		},
		QuarterTeaspoon: &unitInfo{
			nil,
			Imperial,
			"1/4 tsp",
			[]string{},
			0,
			0,
		},
		HalfTeaspoon: &unitInfo{
			nil,
			Imperial,
			"1/2 tsp",
			[]string{},
			0,
			0,
		},
		Teaspoon: &unitInfo{
			nil,
			Imperial,
			"tsp",
			[]string{"t", "teaspoon", "teaspoons", "tsp.", "tsp"},
			0,
			MultiplesOk,
		},
		Tablespoon: &unitInfo{
			nil,
			Imperial,
			"T",
			[]string{"T", "tablespoon", "tablespoons", "tbl.", "tbl", "tbs.", "tbsp."},
			0,
			MultiplesOk,
		},
		FluidOunce: &unitInfo{
			nil,
			Imperial,
			"",
			[]string{"fluid ounce", "fluid ounces", "fl oz"},
			0,
			SuppressOutput,
		},
		QuarterCup: &unitInfo{
			nil,
			Imperial,
			"1/4 c",
			[]string{},
			0,
			0,
		},
		HalfCup: &unitInfo{
			nil,
			Imperial,
			"1/2 c",
			[]string{},
			0,
			0,
		},
		ThreeQuarterCup: &unitInfo{
			nil,
			Imperial,
			"3/4 c",
			[]string{},
			0,
			0,
		},
		Cup: &unitInfo{
			nil,
			Imperial,
			"c",
			[]string{"c", "cup", "cups"},
			0,
			MultiplesOk,
		},
		Pint: &unitInfo{
			nil,
			Imperial,
			"",
			[]string{"p", "pt", "pint", "pints", "fl pt"},
			0,
			SuppressOutput,
		},
		Quart: &unitInfo{
			nil,
			Imperial,
			"qt",
			[]string{"q", "quart", "quarts", "qt", "fl qt"},
			0,
			MultiplesOk,
		},
		Gallon: &unitInfo{
			nil,
			Imperial,
			"gal",
			[]string{"gal", "gallon", "gallons", "g"},
			0,
			MultiplesOk,
		},
	}
)

func (v Volume) Output(sys System) string {
	return Output(v, volumeOutput[sys])
}

func (v Volume) Add(o Measurement) (result Measurement, err error) {
	if other, ok := o.(Volume); ok {
		return v + other, nil
	} else {
		return v, fmt.Errorf("Volume incompatible with %v", reflect.ValueOf(o).Type())
	}
}

func (v Volume) Mul(r *big.Rat) (Measurement, error) {
	if i, err := mul(int64(v), r); err != nil {
		return Teaspoon, err
	} else {
		return Volume(i), nil
	}
}

func (v Volume) Int64() int64 {
	return int64(v)
}

type Weight int64

const (
	Milligram Weight = 8
	Gram             = Milligram * 1000
	Kilogram         = Gram * 1000

	Ounce = 28409 * Milligram
	Pound = Ounce * 16
)

var (
	weightInfo = map[Measurement]*unitInfo{
		Milligram: &unitInfo{
			nil,
			Metric,
			"mg",
			[]string{"mg", "milligram", "milligrams", "milligramme", "milligrammes"},
			0,
			MultiplesOk,
		},
		Gram: &unitInfo{
			nil,
			Metric,
			"g",
			[]string{"g", "gram", "grams", "gramme", "grammes"},
			3,
			MultiplesOk,
		},
		Kilogram: &unitInfo{
			nil,
			Metric,
			"kg",
			[]string{"kg", "kilogram", "kilograms", "kilogramme", "kilogrammes"},
			3,
			MultiplesOk,
		},

		Ounce: &unitInfo{
			nil,
			Imperial,
			"oz",
			[]string{"oz", "ounce", "ounces"},
			1,
			MultiplesOk | FractionsOk,
		},
		Pound: &unitInfo{
			nil,
			Imperial,
			"lb",
			[]string{"lb", "#", "pound", "pounds"},
			0,
			MultiplesOk,
		},
	}
)

func (w Weight) Output(sys System) string {
	return Output(w, weightOutput[sys])
}

func (w Weight) Add(o Measurement) (result Measurement, err error) {
	if other, ok := o.(Weight); ok {
		return w + other, nil
	} else {
		return w, fmt.Errorf("Weight incompatible with %v", reflect.ValueOf(o).Type())
	}
}

func (w Weight) Mul(r *big.Rat) (Measurement, error) {
	if i, err := mul(int64(w), r); err != nil {
		return Ounce, err
	} else {
		return Weight(i), nil
	}
}

func (w Weight) Int64() int64 {
	return int64(w)
}

type Measurement interface {
	Add(other Measurement) (result Measurement, err error)
	Mul(r *big.Rat) (Measurement, error)
	Output(sys System) string
	Int64() int64
}

func mul(i int64, r *big.Rat) (int64, error) {
	result := big.NewRat(i, 1)
	result.Mul(result, r)
	if !result.IsInt() {
		return 0, fmt.Errorf("Error multiplying %v by %v.  We ended up with non-integral value %v", i, r, result)
	}
	return result.Num().Int64(), nil
}

// Can parse things like "1/4", ".5", "2", "2 1/2"
func parseMagnitude(s string) (*big.Rat, error) {
	tokens := strings.Split(s, " ")
	accum := new(big.Rat)
	r := new(big.Rat)
	for _, t := range tokens {
		if _, ok := r.SetString(t); !ok {
			return nil, fmt.Errorf("Error parsing %v.  Token %v could not be parsed by big.Rat")
		}
		accum.Add(accum, r)
	}
	return accum, nil
}

func Parse(s string) (m Measurement, err error) {
	if matches := re.FindStringSubmatch(s); matches != nil {
		if len(matches) == 3 {
			magnitude := matches[1]
			unit := matches[2]
			log.Printf("%#v: %#v: %#v", s, magnitude, unit)
			if info, ok := measurementLookup[unit]; ok {
				if mag, err := parseMagnitude(magnitude); err != nil {
					return nil, err
				} else {
					return info.measurement.Mul(mag)
				}
			} else {
				return nil, fmt.Errorf("Could not recognize [%v] as unit in %s", unit, s)
			}
		} else {
			return nil, fmt.Errorf("Unable to parse [%v] as measurment.  Matches was %#v", s, matches)
		}
	} else {
		return nil, fmt.Errorf("Unable to parse [%v] as measurement via regexp", s)
	}

}

func Output(m Measurement, units byMeasurement) string {
	remainder := m.Int64()
	tokens := make([]string, 0)
	for _, u := range units {
		ui := u.measurement.Int64()
		if remainder >= ui {
			if remainder%ui == 0 {
				div := remainder / ui
				remainder = 0
				if div == 1 {
					if u.outputType&MultiplesOk == 0 {
						// if multiples not supported, the value is encoded in unitInfo
						tokens = append(tokens, u.out)
					} else {
						tokens = append(tokens, fmt.Sprintf("%v %s", div, u.out))
					}
				} else { // div != 1
					if u.outputType&MultiplesOk != 0 {
						tokens = append(tokens, fmt.Sprintf("%v %s", div, u.out))
					} else { // should not happen
						return fmt.Sprintf("Error: unable to represent measurement %#v", m)
					}
				}
			} else { // fractional value over 1
				if u.decimalPlaces == 0 {
					div := remainder / ui
					tokens = append(tokens, fmt.Sprintf("%v %s", div, u.out))
					remainder = remainder % ui
				} else {
					f := float64(remainder) / float64(ui)
					tokens = append(tokens, fmt.Sprintf("%.*f %s", u.decimalPlaces, f, u.out))
					remainder = 0
				}
			}
		} else { // remainder < ui
			if u.outputType&FractionsOk != 0 {
				f := float64(remainder) / float64(ui)
				tokens = append(tokens, fmt.Sprintf("%.*f %s", u.decimalPlaces, f, u.out))
				remainder = 0
			} else { // look for a smaller unit we can handle
				continue
			}
		}
		if remainder == 0 {
			break
		}
	}
	if len(tokens) != 0 {
		return strings.Join(tokens, ", ")
	} else {
		return "TODO(gina) what to do here?"
	}
}

var allMeasurementInfo = []map[Measurement]*unitInfo{volumeInfo, weightInfo}

var measurementLookup = make(map[string]*unitInfo)

var volumeOutput = make(map[System]byMeasurement)
var weightOutput = make(map[System]byMeasurement)

func extractOutputs(sys System, m map[Measurement]*unitInfo) byMeasurement {
	s := make([]*unitInfo, 0, len(m))
	for _, u := range m {
		if u.system == sys && u.outputType&SuppressOutput == 0 {
			s = append(s, u)
		}
	}
	result := byMeasurement(s)
	sort.Sort(sort.Reverse(result))
	return result
}

func init() {
	for _, m := range allMeasurementInfo {
		for measurement, info := range m {
			info.measurement = measurement

			for _, in := range info.in {
				measurementLookup[in] = info
			}
		}
	}

	volumeOutput[Metric] = extractOutputs(Metric, volumeInfo)
	volumeOutput[Imperial] = extractOutputs(Imperial, volumeInfo)

	weightOutput[Metric] = extractOutputs(Metric, weightInfo)
	weightOutput[Imperial] = extractOutputs(Imperial, weightInfo)
}

// 	}
// }

// need maps of accepted strings to the matching values

// Is there some kind of 'unit' interface that these both implement?
// Seems like we want a method that accepts a string and returns an
// insance of that unit
<commit_msg>new comments without code changed yet<commit_after>package main

import (
	"fmt"
	"log"
	"math/big"
	"reflect"
	"regexp"
	"sort"
	"strings"
)

var (
	// we match <magnitude> <unit>
	// where <magnitude> can look like:
	//   2
	//   2.5
	//   2 1/2
	// and <unit> can look like
	//   oz
	//   cup
	//   cups
	//   tsp
	//   etc...
	re = regexp.MustCompile(`^\s*(\d+\s+\d+/\d+|\d+|\d+\.\d+|\d+/\d+)\s+([a-zA-Z]*)\s*$`)
)

type System int8

const (
	Metric System = iota
	Imperial
)

func (sys System) String() string {
	switch sys {
	case Metric:
		return "Metric system"
	case Imperial:
		return "Imperial system"
	}
	return fmt.Sprintf("Unknown system %v", sys)
}

type OutputType byte

// semi-overlapping flags
const (
	SuppressOutput OutputType = 1 << iota // rest ignored if this is set
	MultiplesOk                           // If set, we can have more than one of this unit
	FractionsOk                           // If set, we can have less than one of this unit
)

type Volume int64

const (
	Milliliter Volume = 8
	Deciliter         = Milliliter * 100
	Liter             = Milliliter * 1000

	EighthTeaspoon  = Milliliter * 5 / 8
	QuarterTeaspoon = EighthTeaspoon * 2
	HalfTeaspoon    = QuarterTeaspoon * 2
	Teaspoon        = HalfTeaspoon * 2
	Tablespoon      = Teaspoon * 3
	FluidOunce      = Tablespoon * 2
	QuarterCup      = FluidOunce * 2
	HalfCup         = FluidOunce * 4
	ThreeQuarterCup = FluidOunce * 6
	Cup             = FluidOunce * 8
	Pint            = Cup * 2
	Quart           = Cup * 4
	Gallon          = Quart * 4
)

type unitInfo struct {
	measurement   Measurement
	system        System
	out           string
	in            []string
	decimalPlaces int
	outputType    OutputType
}

type byMeasurement []*unitInfo

func (a byMeasurement) Len() int           { return len(a) }
func (a byMeasurement) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byMeasurement) Less(i, j int) bool { return a[i].measurement.Int64() < a[j].measurement.Int64() }

var (
	volumeInfo = map[Measurement]*unitInfo{
		Milliliter: &unitInfo{
			nil,
			Metric,
			"ml",
			[]string{"ml", "milliliter", "milliliters", "millilitre", "millilitres", "mL"},
			0,
			MultiplesOk,
		},
		Deciliter: &unitInfo{
			nil,
			Metric,
			"",
			[]string{"dl", "deciliter", "deciliters", "decilitre", "decilitres", "dL"},
			0,
			SuppressOutput,
		},
		Liter: &unitInfo{
			nil,
			Metric,
			"l",
			[]string{"l", "liter", "liters", "litre", "litres", "L"},
			3,
			MultiplesOk,
		},

		EighthTeaspoon: &unitInfo{
			nil,
			Imperial,
			"1/8 tsp",
			[]string{},
			0,
			0,
		},
		QuarterTeaspoon: &unitInfo{
			nil,
			Imperial,
			"1/4 tsp",
			[]string{},
			0,
			0,
		},
		HalfTeaspoon: &unitInfo{
			nil,
			Imperial,
			"1/2 tsp",
			[]string{},
			0,
			0,
		},
		Teaspoon: &unitInfo{
			nil,
			Imperial,
			"tsp",
			[]string{"t", "teaspoon", "teaspoons", "tsp.", "tsp"},
			0,
			MultiplesOk,
		},
		Tablespoon: &unitInfo{
			nil,
			Imperial,
			"T",
			[]string{"T", "tablespoon", "tablespoons", "tbl.", "tbl", "tbs.", "tbsp."},
			0,
			MultiplesOk,
		},
		FluidOunce: &unitInfo{
			nil,
			Imperial,
			"",
			[]string{"fluid ounce", "fluid ounces", "fl oz"},
			0,
			SuppressOutput,
		},
		QuarterCup: &unitInfo{
			nil,
			Imperial,
			"1/4 c",
			[]string{},
			0,
			0,
		},
		HalfCup: &unitInfo{
			nil,
			Imperial,
			"1/2 c",
			[]string{},
			0,
			0,
		},
		ThreeQuarterCup: &unitInfo{
			nil,
			Imperial,
			"3/4 c",
			[]string{},
			0,
			0,
		},
		Cup: &unitInfo{
			nil,
			Imperial,
			"c",
			[]string{"c", "cup", "cups"},
			0,
			MultiplesOk,
		},
		Pint: &unitInfo{
			nil,
			Imperial,
			"",
			[]string{"p", "pt", "pint", "pints", "fl pt"},
			0,
			SuppressOutput,
		},
		Quart: &unitInfo{
			nil,
			Imperial,
			"qt",
			[]string{"q", "quart", "quarts", "qt", "fl qt"},
			0,
			MultiplesOk,
		},
		Gallon: &unitInfo{
			nil,
			Imperial,
			"gal",
			[]string{"gal", "gallon", "gallons", "g"},
			0,
			MultiplesOk,
		},
	}
)

func (v Volume) Output(sys System) string {
	return Output(v, volumeOutput[sys])
}

func (v Volume) Add(o Measurement) (result Measurement, err error) {
	if other, ok := o.(Volume); ok {
		return v + other, nil
	} else {
		return v, fmt.Errorf("Volume incompatible with %v", reflect.ValueOf(o).Type())
	}
}

func (v Volume) Mul(r *big.Rat) (Measurement, error) {
	if i, err := mul(int64(v), r); err != nil {
		return Teaspoon, err
	} else {
		return Volume(i), nil
	}
}

func (v Volume) Int64() int64 {
	return int64(v)
}

type Weight int64

const (
	Milligram Weight = 8
	Gram             = Milligram * 1000
	Kilogram         = Gram * 1000

	Ounce = 28409 * Milligram
	Pound = Ounce * 16
)

var (
	weightInfo = map[Measurement]*unitInfo{
		Milligram: &unitInfo{
			nil,
			Metric,
			"mg",
			[]string{"mg", "milligram", "milligrams", "milligramme", "milligrammes"},
			0,
			MultiplesOk,
		},
		Gram: &unitInfo{
			nil,
			Metric,
			"g",
			[]string{"g", "gram", "grams", "gramme", "grammes"},
			3,
			MultiplesOk,
		},
		Kilogram: &unitInfo{
			nil,
			Metric,
			"kg",
			[]string{"kg", "kilogram", "kilograms", "kilogramme", "kilogrammes"},
			3,
			MultiplesOk,
		},

		Ounce: &unitInfo{
			nil,
			Imperial,
			"oz",
			[]string{"oz", "ounce", "ounces"},
			1,
			MultiplesOk | FractionsOk,
		},
		Pound: &unitInfo{
			nil,
			Imperial,
			"lb",
			[]string{"lb", "#", "pound", "pounds"},
			0,
			MultiplesOk,
		},
	}
)

func (w Weight) Output(sys System) string {
	return Output(w, weightOutput[sys])
}

func (w Weight) Add(o Measurement) (result Measurement, err error) {
	if other, ok := o.(Weight); ok {
		return w + other, nil
	} else {
		return w, fmt.Errorf("Weight incompatible with %v", reflect.ValueOf(o).Type())
	}
}

func (w Weight) Mul(r *big.Rat) (Measurement, error) {
	if i, err := mul(int64(w), r); err != nil {
		return Ounce, err
	} else {
		return Weight(i), nil
	}
}

func (w Weight) Int64() int64 {
	return int64(w)
}

type Measurement interface {
	Add(other Measurement) (result Measurement, err error)
	Mul(r *big.Rat) (Measurement, error)
	Output(sys System) string
	Int64() int64
}

func mul(i int64, r *big.Rat) (int64, error) {
	result := big.NewRat(i, 1)
	result.Mul(result, r)
	if !result.IsInt() {
		return 0, fmt.Errorf("Error multiplying %v by %v.  We ended up with non-integral value %v", i, r, result)
	}
	return result.Num().Int64(), nil
}

// Can parse things like "1/4", ".5", "2", "2 1/2"
func parseMagnitude(s string) (*big.Rat, error) {
	tokens := strings.Split(s, " ")
	accum := new(big.Rat)
	r := new(big.Rat)
	for _, t := range tokens {
		if _, ok := r.SetString(t); !ok {
			return nil, fmt.Errorf("Error parsing %v.  Token %v could not be parsed by big.Rat")
		}
		accum.Add(accum, r)
	}
	return accum, nil
}

func Parse(s string) (m Measurement, err error) {
	if matches := re.FindStringSubmatch(s); matches != nil {
		if len(matches) == 3 {
			magnitude := matches[1]
			unit := matches[2]
			log.Printf("%#v: %#v: %#v", s, magnitude, unit)
			if info, ok := measurementLookup[unit]; ok {
				if mag, err := parseMagnitude(magnitude); err != nil {
					return nil, err
				} else {
					return info.measurement.Mul(mag)
				}
			} else {
				return nil, fmt.Errorf("Could not recognize [%v] as unit in %s", unit, s)
			}
		} else {
			return nil, fmt.Errorf("Unable to parse [%v] as measurment.  Matches was %#v", s, matches)
		}
	} else {
		return nil, fmt.Errorf("Unable to parse [%v] as measurement via regexp", s)
	}

}

func Output(m Measurement, units byMeasurement) string {
	remainder := m.Int64()

	// units are already ordered by size, biggest first.  We loop
	// through them, looking for ones to apply, building up a slice of
	// tokens, which will be joined at the end.

	// Here are the cases applied on each iteration through the loop:
	//
	//  If remainder equals the unit size and the unit does not
	//  support multiples: just output the unit and finish the loop
	//
	//  If (unit < remainder and unit divides cleanly into remainder),
	//  or unit does not support decimals: Add division token, reduce
	//  remainder to remainder % ui
	//
	//  If (unit < remainder and unit supports decimals), or unit
	//  supports fractions: output fraction, zero out remainder
	tokens := make([]string, 0)
	for _, u := range units {
		ui := u.measurement.Int64()
		if remainder >= ui {
			if remainder%ui == 0 {
				div := remainder / ui
				remainder = 0
				if div == 1 {
					if u.outputType&MultiplesOk == 0 {
						// if multiples not supported, the value is encoded in unitInfo
						tokens = append(tokens, u.out)
					} else {
						tokens = append(tokens, fmt.Sprintf("%v %s", div, u.out))
					}
				} else { // div != 1
					if u.outputType&MultiplesOk != 0 {
						tokens = append(tokens, fmt.Sprintf("%v %s", div, u.out))
					} else { // should not happen
						return fmt.Sprintf("Error: unable to represent measurement %#v", m)
					}
				}
			} else { // fractional value over 1
				if u.decimalPlaces == 0 {
					div := remainder / ui
					tokens = append(tokens, fmt.Sprintf("%v %s", div, u.out))
					remainder = remainder % ui
				} else {
					f := float64(remainder) / float64(ui)
					tokens = append(tokens, fmt.Sprintf("%.*f %s", u.decimalPlaces, f, u.out))
					remainder = 0
				}
			}
		} else { // remainder < ui
			if u.outputType&FractionsOk != 0 {
				f := float64(remainder) / float64(ui)
				tokens = append(tokens, fmt.Sprintf("%.*f %s", u.decimalPlaces, f, u.out))
				remainder = 0
			} else { // look for a smaller unit we can handle
				continue
			}
		}
		if remainder == 0 {
			break
		}
	}
	if len(tokens) != 0 {
		return strings.Join(tokens, ", ")
	} else {
		return "TODO(gina) what to do here?"
	}
}

var allMeasurementInfo = []map[Measurement]*unitInfo{volumeInfo, weightInfo}

var measurementLookup = make(map[string]*unitInfo)

var volumeOutput = make(map[System]byMeasurement)
var weightOutput = make(map[System]byMeasurement)

func extractOutputs(sys System, m map[Measurement]*unitInfo) byMeasurement {
	s := make([]*unitInfo, 0, len(m))
	for _, u := range m {
		if u.system == sys && u.outputType&SuppressOutput == 0 {
			s = append(s, u)
		}
	}
	result := byMeasurement(s)
	sort.Sort(sort.Reverse(result))
	return result
}

func init() {
	for _, m := range allMeasurementInfo {
		for measurement, info := range m {
			info.measurement = measurement

			for _, in := range info.in {
				measurementLookup[in] = info
			}
		}
	}

	volumeOutput[Metric] = extractOutputs(Metric, volumeInfo)
	volumeOutput[Imperial] = extractOutputs(Imperial, volumeInfo)

	weightOutput[Metric] = extractOutputs(Metric, weightInfo)
	weightOutput[Imperial] = extractOutputs(Imperial, weightInfo)
}

// 	}
// }

// need maps of accepted strings to the matching values

// Is there some kind of 'unit' interface that these both implement?
// Seems like we want a method that accepts a string and returns an
// insance of that unit
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"os"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/macie"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSMacieMemberAccountAssociation_basic(t *testing.T) {
	key := "MACIE_MEMBER_ACCOUNT_ID"
	memberAcctId := os.Getenv(key)
	if memberAcctId == "" {
		t.Skipf("Environment variable %s is not set", key)
	}

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSMacie(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSMacieMemberAccountAssociationDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSMacieMemberAccountAssociationConfig_basic(memberAcctId),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSMacieMemberAccountAssociationExists("aws_macie_member_account_association.test"),
				),
			},
		},
	})
}

func TestAccAWSMacieMemberAccountAssociation_self(t *testing.T) {
	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t); testAccPreCheckAWSMacie(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSMacieMemberAccountAssociationConfig_self,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSMacieMemberAccountAssociationExists("aws_macie_member_account_association.test"),
				),
			},
		},
	})
}

func testAccCheckAWSMacieMemberAccountAssociationDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).macieconn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_macie_member_account_association" {
			continue
		}

		req := &macie.ListMemberAccountsInput{}

		dissociated := true
		err := conn.ListMemberAccountsPages(req, func(page *macie.ListMemberAccountsOutput, lastPage bool) bool {
			for _, v := range page.MemberAccounts {
				if aws.StringValue(v.AccountId) == rs.Primary.Attributes["member_account_id"] {
					dissociated = false
					return false
				}
			}

			return true
		})
		if err != nil {
			return err
		}

		if !dissociated {
			return fmt.Errorf("Member account %s is not dissociated from Macie", rs.Primary.Attributes["member_account_id"])
		}
	}
	return nil
}

func testAccCheckAWSMacieMemberAccountAssociationExists(name string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		conn := testAccProvider.Meta().(*AWSClient).macieconn

		rs, ok := s.RootModule().Resources[name]
		if !ok {
			return fmt.Errorf("Not found: %s", name)
		}

		req := &macie.ListMemberAccountsInput{}

		exists := false
		err := conn.ListMemberAccountsPages(req, func(page *macie.ListMemberAccountsOutput, lastPage bool) bool {
			for _, v := range page.MemberAccounts {
				if aws.StringValue(v.AccountId) == rs.Primary.Attributes["member_account_id"] {
					exists = true
					return false
				}
			}

			return true
		})
		if err != nil {
			return err
		}

		if !exists {
			return fmt.Errorf("Member account %s is not associated with Macie", rs.Primary.Attributes["member_account_id"])
		}

		return nil
	}
}

func testAccAWSMacieMemberAccountAssociationConfig_basic(memberAcctId string) string {
	return fmt.Sprintf(`
resource "aws_macie_member_account_association" "test" {
  member_account_id = "%s"
}
`, memberAcctId)
}

const testAccAWSMacieMemberAccountAssociationConfig_self = `
data "aws_caller_identity" "current" {}

resource "aws_macie_member_account_association" "test" {
  member_account_id = "${data.aws_caller_identity.current.account_id}"
}
`
<commit_msg>Add missing CheckDestroy for aws_macie_member_account_association resource<commit_after>package aws

import (
	"fmt"
	"os"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/macie"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSMacieMemberAccountAssociation_basic(t *testing.T) {
	key := "MACIE_MEMBER_ACCOUNT_ID"
	memberAcctId := os.Getenv(key)
	if memberAcctId == "" {
		t.Skipf("Environment variable %s is not set", key)
	}

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t); testAccPreCheckAWSMacie(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSMacieMemberAccountAssociationDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSMacieMemberAccountAssociationConfig_basic(memberAcctId),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSMacieMemberAccountAssociationExists("aws_macie_member_account_association.test"),
				),
			},
		},
	})
}

func TestAccAWSMacieMemberAccountAssociation_self(t *testing.T) {
	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t); testAccPreCheckAWSMacie(t) },
		Providers: testAccProviders,
		// master account associated with Macie it can't be disassociated.
		CheckDestroy: nil,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSMacieMemberAccountAssociationConfig_self,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSMacieMemberAccountAssociationExists("aws_macie_member_account_association.test"),
				),
			},
		},
	})
}

func testAccCheckAWSMacieMemberAccountAssociationDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).macieconn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_macie_member_account_association" {
			continue
		}

		req := &macie.ListMemberAccountsInput{}

		dissociated := true
		err := conn.ListMemberAccountsPages(req, func(page *macie.ListMemberAccountsOutput, lastPage bool) bool {
			for _, v := range page.MemberAccounts {
				if aws.StringValue(v.AccountId) == rs.Primary.Attributes["member_account_id"] {
					dissociated = false
					return false
				}
			}

			return true
		})
		if err != nil {
			return err
		}

		if !dissociated {
			return fmt.Errorf("Member account %s is not dissociated from Macie", rs.Primary.Attributes["member_account_id"])
		}
	}
	return nil
}

func testAccCheckAWSMacieMemberAccountAssociationExists(name string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		conn := testAccProvider.Meta().(*AWSClient).macieconn

		rs, ok := s.RootModule().Resources[name]
		if !ok {
			return fmt.Errorf("Not found: %s", name)
		}

		req := &macie.ListMemberAccountsInput{}

		exists := false
		err := conn.ListMemberAccountsPages(req, func(page *macie.ListMemberAccountsOutput, lastPage bool) bool {
			for _, v := range page.MemberAccounts {
				if aws.StringValue(v.AccountId) == rs.Primary.Attributes["member_account_id"] {
					exists = true
					return false
				}
			}

			return true
		})
		if err != nil {
			return err
		}

		if !exists {
			return fmt.Errorf("Member account %s is not associated with Macie", rs.Primary.Attributes["member_account_id"])
		}

		return nil
	}
}

func testAccAWSMacieMemberAccountAssociationConfig_basic(memberAcctId string) string {
	return fmt.Sprintf(`
resource "aws_macie_member_account_association" "test" {
  member_account_id = "%s"
}
`, memberAcctId)
}

const testAccAWSMacieMemberAccountAssociationConfig_self = `
data "aws_caller_identity" "current" {}

resource "aws_macie_member_account_association" "test" {
  member_account_id = "${data.aws_caller_identity.current.account_id}"
}
`
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/chrissnell/syslog"
)

type handler struct {
	// To simplify implementation of our handler we embed helper
	// syslog.BaseHandler struct.
	*syslog.BaseHandler
}

type LogentriesHostEntity struct {
	Response  string `json:"response"`
	Host      Host   `json:"host"`
	Host_key  string `json:"host_key"`
	Worker    string `json:"worker"`
	Agent_key string `json:"agent_key"`
}

type Host struct {
	C        float64 `json:"c"`
	Name     string  `json:"name"`
	Distver  string  `json:"distver"`
	Hostname string  `json:"hostname"`
	Object   string  `json:"object"`
	Distname string  `json:"distname"`
	Key      string  `json:"key"`
}

type LogentriesLogEntity struct {
	Response string `json:"response"`
	Log_key  string `json:"log_key"`
	Log      Log    `json:"log"`
}

type Log struct {
	Token     string  `json:"token"`
	Created   float64 `json:"created"`
	Name      string  `json:"name`
	Retention float64 `json:"retention"`
	Filename  string  `json:"filename"`
	Object    string  `json:"object"`
	Type      string  `json:"type"`
	Key       string  `json:"key"`
	Follow    string  `json:"folow"`
}

type LogLine struct {
	Line  syslog.Message
	Token string
}

var (
	logconsumerPtr        *string
	logentriesAPIKeyPtr   *string
	listenAddrPtr         *string
	logentities           = make(map[string]LogentriesLogEntity)
	hostentities          = make(map[string]LogentriesHostEntity)
	tokenchan             = make(chan string)
	logentities_filename  = "logentries-logentities.gob"
	hostentities_filename = "logentries-hostentities.gob"
)

func newHandler() *handler {
	msg := make(chan syslog.Message)
	// Filter function name set to nil to disable filtering
	h := handler{syslog.NewBaseHandler(5, nil, false)}
	go h.mainLoop(msg)
	go ProcessLogMessage(msg)
	return &h
}

func (h *handler) mainLoop(msg chan syslog.Message) {
	for {
		m := h.Get()
		if m == nil {
			break
		}
		msg <- *m
	}
	fmt.Println("Exit handler")
	h.End()
}

func ProcessLogMessage(msg chan syslog.Message) {
	tokenfetchdone := make(chan bool, 1)
	logentrieschan := make(chan LogLine)
	lh := make(chan struct{ host, log string })

	var logline LogLine

	for m := range msg {
		if m.Hostname == "" {
			m.Hostname = "NONE"
		}
		go GetTokenForLog(tokenfetchdone, lh)
		lh <- struct{ host, log string }{m.Hostname, m.Tag}
		token := <-tokenchan
		<-tokenfetchdone

		logline.Token = token
		logline.Line = m

		go SendLogMessages(logentrieschan)
		logentrieschan <- logline

	}
}

func GetTokenForLog(tokenfetchdone chan bool, lh chan struct{ host, log string }) {
	select {
	case lht, msg_ok := <-lh:
		if !msg_ok {
			fmt.Println("msg channel closed")
		} else {

			var hostentity LogentriesHostEntity
			var logentity LogentriesLogEntity

			l := strings.Join([]string{lht.host, lht.log}, "::")

			hostentity = hostentities[lht.host]
			if hostentity.Host.Key == "" {
				hostentity = RegisterNewHost(lht.host)

				// Store our new host token in our map and sync it to disk
				hostentities[lht.host] = hostentity
				err := SyncHostEntitiesToDisk()
				if err != nil {
					log.Fatal(err)
				}
			}

			logentity = logentities[l]
			if logentity.Log.Token == "" {
				logentity := RegisterNewLog(hostentity, l)
				logentities[l] = logentity
				tokenchan <- logentity.Log.Token
				tokenfetchdone <- true
				err := SyncLogEntitiesToDisk()
				if err != nil {
					log.Fatal(err)
				}
			} else {
				tokenchan <- logentity.Log.Token
				tokenfetchdone <- true
			}
		}
	}
}

func DialLogEntries() (err error, conn net.Conn) {
	for {
		conn, err = net.Dial("tcp", *logconsumerPtr)
		if err == nil {
			return err, conn
		} else {
			fmt.Println("Could not connect to LogEntries log endpoint...retrying")
			// Wait for 5 seconds before redialing
			timer := time.NewTimer(time.Second * 5)
			<-timer.C
		}
	}
}

func SendLogMessages(msg chan LogLine) {
	err, conn := DialLogEntries()
	if err != nil {
		fmt.Println("Could not connect to LogEntries log endpoint ", err.Error())
	}

	select {
	case logline, msg_ok := <-msg:
		if !msg_ok {
			fmt.Println("msg channel closed")
		} else {
			t := logline.Line.Time
			line := fmt.Sprintf("%v %v %v %v\n", logline.Token, t.Format(time.RFC3339), logline.Line.Hostname, logline.Line.Content)
			_, err = conn.Write([]byte(line))
			if err != nil {
				log.Print("Send to Logentries endpoint failed.")
				break
			}
			// fmt.Printf("Sending line: %v", line)
		}
	}
}

func SyncLogEntitiesToDisk() (err error) {
	m := new(bytes.Buffer)
	enc := gob.NewEncoder(m)
	enc.Encode(logentities)
	err = ioutil.WriteFile("logentries-logentities.gob", m.Bytes(), 0600)
	return (err)
}

func SyncHostEntitiesToDisk() (err error) {
	m := new(bytes.Buffer)
	enc := gob.NewEncoder(m)
	enc.Encode(hostentities)
	err = ioutil.WriteFile("logentries-hostentities.gob", m.Bytes(), 0600)
	return (err)
}

func LoadLogEntitiesFromDisk() (err error) {
	n, err := ioutil.ReadFile("logentries-logentities.gob")
	if err != nil {
		return (err)
	}
	p := bytes.NewBuffer(n)
	dec := gob.NewDecoder(p)
	err = dec.Decode(&logentities)
	return (err)
}

func LoadHostEntitiesFromDisk() (err error) {
	n, err := ioutil.ReadFile("logentries-hostentities.gob")
	if err != nil {
		return (err)
	}
	p := bytes.NewBuffer(n)
	dec := gob.NewDecoder(p)
	err = dec.Decode(&hostentities)
	return (err)
}

func RegisterNewHost(h string) (he LogentriesHostEntity) {
	v := url.Values{}
	v.Set("request", "register")
	v.Set("user_key", *logentriesAPIKeyPtr)
	v.Set("name", h)
	v.Set("hostname", h)
	v.Set("distver", "")
	v.Set("system", "")
	v.Set("distname", "")
	res, err := http.PostForm("http://api.logentries.com/", v)
	if err != nil {
		log.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	err = json.Unmarshal(body, &he)
	return (he)
}

func RegisterNewLog(e LogentriesHostEntity, n string) (logentity LogentriesLogEntity) {
	v := url.Values{}
	v.Set("request", "new_log")
	v.Set("user_key", *logentriesAPIKeyPtr)
	v.Set("host_key", e.Host.Key)
	v.Set("name", n)
	v.Set("filename", "")
	v.Set("retention", "-1")
	v.Set("source", "token")
	res, err := http.PostForm("http://api.logentries.com/", v)
	if err != nil {
		log.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	err = json.Unmarshal(body, &logentity)
	return (logentity)
}

func main() {

	logconsumerPtr = flag.String("consumer", "api.logentries.com:10000", "Logentries log consumer endpoint <host:port> (Default: api.logentries.com:10000)")
	logentriesAPIKeyPtr = flag.String("apikey", "", "Logentries API key")
	listenAddrPtr = flag.String("listen", "0.0.0.0:1987", "Host/port to listen for syslog messages <host:port> (Default: 0.0.0.0:1987)")

	flag.Parse()

	if *logentriesAPIKeyPtr == "" {
		log.Fatal("Must pass a Logentries API key. Use -h for help.")
	}

	if _, err := os.Stat(logentities_filename); err == nil {
		err = LoadLogEntitiesFromDisk()
		if err != nil {
			log.Fatal(err)
		}
	}

	if _, err := os.Stat(hostentities_filename); err == nil {
		err = LoadHostEntitiesFromDisk()
		if err != nil {
			log.Fatal(err)
		}
	}

	// Create a server with one handler and run one listen gorutine
	s := syslog.NewServer()
	s.AddAllowedRunes("-._")
	s.AddHandler(newHandler())
	s.Listen(*listenAddrPtr)

	// Wait for terminating signal
	sc := make(chan os.Signal, 2)
	signal.Notify(sc, syscall.SIGTERM, syscall.SIGINT)
	<-sc

	// Shutdown the server
	fmt.Println("Shutdown the server...")
	s.Shutdown()
	fmt.Println("Server is down")
}
<commit_msg>Return err as last return value<commit_after>package main

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/chrissnell/syslog"
)

type handler struct {
	// To simplify implementation of our handler we embed helper
	// syslog.BaseHandler struct.
	*syslog.BaseHandler
}

type LogentriesHostEntity struct {
	Response  string `json:"response"`
	Host      Host   `json:"host"`
	Host_key  string `json:"host_key"`
	Worker    string `json:"worker"`
	Agent_key string `json:"agent_key"`
}

type Host struct {
	C        float64 `json:"c"`
	Name     string  `json:"name"`
	Distver  string  `json:"distver"`
	Hostname string  `json:"hostname"`
	Object   string  `json:"object"`
	Distname string  `json:"distname"`
	Key      string  `json:"key"`
}

type LogentriesLogEntity struct {
	Response string `json:"response"`
	Log_key  string `json:"log_key"`
	Log      Log    `json:"log"`
}

type Log struct {
	Token     string  `json:"token"`
	Created   float64 `json:"created"`
	Name      string  `json:"name`
	Retention float64 `json:"retention"`
	Filename  string  `json:"filename"`
	Object    string  `json:"object"`
	Type      string  `json:"type"`
	Key       string  `json:"key"`
	Follow    string  `json:"folow"`
}

type LogLine struct {
	Line  syslog.Message
	Token string
}

var (
	logconsumerPtr        *string
	logentriesAPIKeyPtr   *string
	listenAddrPtr         *string
	logentities           = make(map[string]LogentriesLogEntity)
	hostentities          = make(map[string]LogentriesHostEntity)
	tokenchan             = make(chan string)
	logentities_filename  = "logentries-logentities.gob"
	hostentities_filename = "logentries-hostentities.gob"
)

func newHandler() *handler {
	msg := make(chan syslog.Message)
	// Filter function name set to nil to disable filtering
	h := handler{syslog.NewBaseHandler(5, nil, false)}
	go h.mainLoop(msg)
	go ProcessLogMessage(msg)
	return &h
}

func (h *handler) mainLoop(msg chan syslog.Message) {
	for {
		m := h.Get()
		if m == nil {
			break
		}
		msg <- *m
	}
	fmt.Println("Exit handler")
	h.End()
}

func ProcessLogMessage(msg chan syslog.Message) {
	tokenfetchdone := make(chan bool, 1)
	logentrieschan := make(chan LogLine)
	lh := make(chan struct{ host, log string })

	var logline LogLine

	for m := range msg {
		if m.Hostname == "" {
			m.Hostname = "NONE"
		}
		go GetTokenForLog(tokenfetchdone, lh)
		lh <- struct{ host, log string }{m.Hostname, m.Tag}
		token := <-tokenchan
		<-tokenfetchdone

		logline.Token = token
		logline.Line = m

		go SendLogMessages(logentrieschan)
		logentrieschan <- logline

	}
}

func GetTokenForLog(tokenfetchdone chan bool, lh chan struct{ host, log string }) {
	select {
	case lht, msg_ok := <-lh:
		if !msg_ok {
			fmt.Println("msg channel closed")
		} else {

			var hostentity LogentriesHostEntity
			var logentity LogentriesLogEntity

			l := strings.Join([]string{lht.host, lht.log}, "::")

			hostentity = hostentities[lht.host]
			if hostentity.Host.Key == "" {
				hostentity = RegisterNewHost(lht.host)

				// Store our new host token in our map and sync it to disk
				hostentities[lht.host] = hostentity
				err := SyncHostEntitiesToDisk()
				if err != nil {
					log.Fatal(err)
				}
			}

			logentity = logentities[l]
			if logentity.Log.Token == "" {
				logentity := RegisterNewLog(hostentity, l)
				logentities[l] = logentity
				tokenchan <- logentity.Log.Token
				tokenfetchdone <- true
				err := SyncLogEntitiesToDisk()
				if err != nil {
					log.Fatal(err)
				}
			} else {
				tokenchan <- logentity.Log.Token
				tokenfetchdone <- true
			}
		}
	}
}

func DialLogEntries() (conn net.Conn, err error) {
	for {
		conn, err = net.Dial("tcp", *logconsumerPtr)
		if err == nil {
			return conn, err
		} else {
			fmt.Println("Could not connect to LogEntries log endpoint...retrying")
			// Wait for 5 seconds before redialing
			timer := time.NewTimer(time.Second * 5)
			<-timer.C
		}
	}
}

func SendLogMessages(msg chan LogLine) {
	conn, err := DialLogEntries()
	if err != nil {
		fmt.Println("Could not connect to LogEntries log endpoint ", err.Error())
	}

	select {
	case logline, msg_ok := <-msg:
		if !msg_ok {
			fmt.Println("msg channel closed")
		} else {
			t := logline.Line.Time
			line := fmt.Sprintf("%v %v %v %v\n", logline.Token, t.Format(time.RFC3339), logline.Line.Hostname, logline.Line.Content)
			_, err = conn.Write([]byte(line))
			if err != nil {
				log.Print("Send to Logentries endpoint failed.")
				break
			}
			// fmt.Printf("Sending line: %v", line)
		}
	}
}

func SyncLogEntitiesToDisk() (err error) {
	m := new(bytes.Buffer)
	enc := gob.NewEncoder(m)
	enc.Encode(logentities)
	err = ioutil.WriteFile("logentries-logentities.gob", m.Bytes(), 0600)
	return (err)
}

func SyncHostEntitiesToDisk() (err error) {
	m := new(bytes.Buffer)
	enc := gob.NewEncoder(m)
	enc.Encode(hostentities)
	err = ioutil.WriteFile("logentries-hostentities.gob", m.Bytes(), 0600)
	return (err)
}

func LoadLogEntitiesFromDisk() (err error) {
	n, err := ioutil.ReadFile("logentries-logentities.gob")
	if err != nil {
		return (err)
	}
	p := bytes.NewBuffer(n)
	dec := gob.NewDecoder(p)
	err = dec.Decode(&logentities)
	return (err)
}

func LoadHostEntitiesFromDisk() (err error) {
	n, err := ioutil.ReadFile("logentries-hostentities.gob")
	if err != nil {
		return (err)
	}
	p := bytes.NewBuffer(n)
	dec := gob.NewDecoder(p)
	err = dec.Decode(&hostentities)
	return (err)
}

func RegisterNewHost(h string) (he LogentriesHostEntity) {
	v := url.Values{}
	v.Set("request", "register")
	v.Set("user_key", *logentriesAPIKeyPtr)
	v.Set("name", h)
	v.Set("hostname", h)
	v.Set("distver", "")
	v.Set("system", "")
	v.Set("distname", "")
	res, err := http.PostForm("http://api.logentries.com/", v)
	if err != nil {
		log.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	err = json.Unmarshal(body, &he)
	return (he)
}

func RegisterNewLog(e LogentriesHostEntity, n string) (logentity LogentriesLogEntity) {
	v := url.Values{}
	v.Set("request", "new_log")
	v.Set("user_key", *logentriesAPIKeyPtr)
	v.Set("host_key", e.Host.Key)
	v.Set("name", n)
	v.Set("filename", "")
	v.Set("retention", "-1")
	v.Set("source", "token")
	res, err := http.PostForm("http://api.logentries.com/", v)
	if err != nil {
		log.Fatal(err)
	}
	body, err := ioutil.ReadAll(res.Body)
	err = json.Unmarshal(body, &logentity)
	return (logentity)
}

func main() {

	logconsumerPtr = flag.String("consumer", "api.logentries.com:10000", "Logentries log consumer endpoint <host:port> (Default: api.logentries.com:10000)")
	logentriesAPIKeyPtr = flag.String("apikey", "", "Logentries API key")
	listenAddrPtr = flag.String("listen", "0.0.0.0:1987", "Host/port to listen for syslog messages <host:port> (Default: 0.0.0.0:1987)")

	flag.Parse()

	if *logentriesAPIKeyPtr == "" {
		log.Fatal("Must pass a Logentries API key. Use -h for help.")
	}

	if _, err := os.Stat(logentities_filename); err == nil {
		err = LoadLogEntitiesFromDisk()
		if err != nil {
			log.Fatal(err)
		}
	}

	if _, err := os.Stat(hostentities_filename); err == nil {
		err = LoadHostEntitiesFromDisk()
		if err != nil {
			log.Fatal(err)
		}
	}

	// Create a server with one handler and run one listen gorutine
	s := syslog.NewServer()
	s.AddAllowedRunes("-._")
	s.AddHandler(newHandler())
	s.Listen(*listenAddrPtr)

	// Wait for terminating signal
	sc := make(chan os.Signal, 2)
	signal.Notify(sc, syscall.SIGTERM, syscall.SIGINT)
	<-sc

	// Shutdown the server
	fmt.Println("Shutdown the server...")
	s.Shutdown()
	fmt.Println("Server is down")
}
<|endoftext|>


<commit_before>package awsdoc

import (
	"github.com/wallix/awless/cloud"
	"github.com/wallix/awless/cloud/properties"
)

var (
	timeouts      = []string{"10", "60", "180", "300", "600", "900"}
	boolean       = []string{"true", "false"}
	services      = []string{"iam", "ec2", "s3", "route53", "elbv2", "rds", "autoscaling", "lambda", "sns", "sqs", "cloudwatch", "cloudfront", "ecr", "ecs", "applicationautoscaling", "acm", "sts", "cloudformation"}
	instanceTypes = []string{"t2.nano", "t2.micro", "t2.small", "t2.medium", "t2.large", "t2.xlarge", "t2.2xlarge", "m4.large", "m4.xlarge", "c4.large", "c4.xlarge"}
	s3ACLs        = []string{"private", "public-read", "public-read-write", "aws-exec-read", "authenticated-read", "bucket-owner-read", "bucket-owner-full-control", "log-delivery-write"}
	distros       = []string{"amazonlinux", "canonical:ubuntu", "redhat:rhel", "debian:debian", "suselinux", "windows:server"}
)

var EnumDoc = map[string][]string{

	"attach.policy.access":  {"readonly", "full"},
	"attach.policy.service": services,

	"check.database.state":   {"available", "backing-up", "creating", "deleting", "failed", "maintenance", "modifying", "rebooting", "renaming", "resetting-master-credentials", "restore-error", "storage-full", "upgrading", "not-found"},
	"check.database.timeout": timeouts,

	"check.certificate.state":   {"issued", "pending_validation", "not-found"},
	"check.certificate.timeout": timeouts,

	"check.distribution.state":   {"Deployed", "InProgress", "not-found"},
	"check.distribution.timeout": timeouts,

	"check.instance.state":   {"pending", "running", "shutting-down", "terminated", "stopping", "stopped", "not-found"},
	"check.instance.timeout": timeouts,

	"check.loadbalancer.state":   {"provisioning", "active", "failed", "not-found"},
	"check.loadbalancer.timeout": timeouts,

	"check.natgateway.state":   {"pending", "failed", "available", "deleting", "deleted", "not-found"},
	"check.natgateway.timeout": timeouts,

	"check.networkinterface.state":   {"available", "attaching", "detaching", "in-use", "not-found"},
	"check.networkinterface.timeout": timeouts,

	"check.scalinggroup.count":   {"0"},
	"check.scalinggroup.timeout": timeouts,

	"check.securitygroup.state":   {"unused"},
	"check.securitygroup.timeout": timeouts,

	"check.volume.state":   {"available", "in-use", "not-found"},
	"check.volume.timeout": timeouts,

	"create.accesskey.save": boolean,

	"create.alarm.operator":           {"GreaterThanThreshold", "LessThanThreshold", "LessThanOrEqualToThreshold", "GreaterThanOrEqualToThreshold"},
	"create.alarm.statistic-function": {"Minimum", "Maximum", "Sum", "Average", "SampleCount", "pNN.NN"},
	"create.alarm.unit":               {"Seconds", "Microseconds", "Milliseconds", "Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes", "Bits", "Kilobits", "Megabits", "Gigabits", "Terabits", "Percent", "Count", "Bytes/Second", "Kilobytes/Second", "Megabytes/Second", "Gigabytes/Second", "Terabytes/Second", "Bits/Second", "Kilobits/Second", "Megabits/Second", "Gigabits/Second", "Terabits/Second", "Count/Second", "None"},

	"create.appscalingtarget.dimension":         {"ecs:service:DesiredCount", "ec2:spot-fleet-request:TargetCapacity", "elasticmapreduce:instancegroup:InstanceCount", "appstream:fleet:DesiredCapacity", "dynamodb:table:ReadCapacityUnits", "dynamodb:table:WriteCapacityUnits", "dynamodb:index:ReadCapacityUnits", "dynamodb:index:WriteCapacityUnits"},
	"create.appscalingtarget.service-namespace": {"ecs", "ec2", "elasticmapreduce", "appstream", "dynamodb"},

	"create.appscalingpolicy.dimension":                    {"ecs:service:DesiredCount", "ec2:spot-fleet-request:TargetCapacity", "elasticmapreduce:instancegroup:InstanceCount", "appstream:fleet:DesiredCapacity", "dynamodb:table:ReadCapacityUnits", "dynamodb:table:WriteCapacityUnits", "dynamodb:index:ReadCapacityUnits", "dynamodb:index:WriteCapacityUnits"},
	"create.appscalingpolicy.service-namespace":            {"ecs", "ec2", "elasticmapreduce", "appstream", "dynamodb"},
	"create.appscalingpolicy.type":                         {"StepScaling"},
	"create.appscalingpolicy.stepscaling-adjustment-type":  {"ChangeInCapacity", "ExactCapacity", "PercentChangeInCapacity"},
	"create.appscalingpolicy.stepscaling-adjustments":      {"0::+1", ":0:-1", "75::+1"},
	"create.appscalingpolicy.stepscaling-aggregation-type": {"Minimum", "Maximum", "Average"},

	"create.bucket.acl": s3ACLs,

	"create.database.engine":             {"mysql", "mariadb", "postgres", "aurora", "oracle-se1", "oracle-se2", "oracle-se", "oracle-ee", "sqlserver-ee", "sqlserver-se", "sqlserver-ex", "sqlserver-web"},
	"create.database.copytagstosnapshot": boolean,
	"create.database.encrypted":          boolean,
	"create.database.license":            {"license-included", "bring-your-own-license", "general-public-license"},
	"create.database.multiaz":            boolean,
	"create.database.public":             boolean,
	"create.database.storagetype":        {"standard", "gp2", "io1"},
	"create.database.type":               {"db.t1.micro", "db.m1.small", "db.m1.medium", "db.m1.large", "db.m1.xlarge", "db.m2.xlarge |db.m2.2xlarge", "db.m2.4xlarge", "db.m3.medium", "db.m3.large", "db.m3.xlarge", "db.m3.2xlarge", "db.m4.large", "db.m4.xlarge", "db.m4.2xlarge", "db.m4.4xlarge", "db.m4.10xlarge", "db.r3.large", "db.r3.xlarge", "db.r3.2xlarge", "db.r3.4xlarge", "db.r3.8xlarge", "db.t2.micro", "db.t2.small", "db.t2.medium", "db.t2.large"},

	"create.distribution.default-file":    {"index.html"},
	"create.distribution.enable":          boolean,
	"create.distribution.forward-cookies": {"all", "none", "whitelist"},
	"create.distribution.forward-queries": boolean,
	"create.distribution.https-behaviour": {"allow-all", "redirect-to-https", "https-only"},
	"create.distribution.price-class":     {"PriceClass_All", "PriceClass_100", "PriceClass_200"},

	"create.elasticip.domain": {"vpc", "ec2-classic"},

	"create.function.runtime": {"nodejs", "nodejs4.3", "nodejs6.10", "java8", "python2.7", "python3.6", "dotnetcore1.0", "nodejs4.3-edge"},

	"create.instance.distro":   distros,
	"create.instance.type":     instanceTypes,
	"create.instance.lock":     boolean,
	"create.instance.userdata": {""},

	"create.image.reboot": boolean,

	"create.keypair.encrypted": boolean,

	"create.launchconfiguration.distro":   distros,
	"create.launchconfiguration.type":     {"t2.nano", "t2.micro", "t2.small", "t2.medium", "t2.large", "t2.xlarge", "t2.2xlarge", "m4.large", "m4.xlarge", "c4.large", "c4.xlarge"},
	"create.launchconfiguration.userdata": {""},
	"create.launchconfiguration.public":   boolean,

	"create.listener.actiontype": {"forward"},
	"create.listener.protocol":   {"HTTP", "HTTPS"},
	"create.listener.sslpolicy":  {"ELBSecurityPolicy-2016-08", "ELBSecurityPolicy-TLS-1-2-2017-01", "ELBSecurityPolicy-TLS-1-1-2017-01", "ELBSecurityPolicy-2015-05", "ELBSecurityPolicy-TLS-1-0-2015-04"},

	"create.policy.action":   {""},
	"create.policy.effect":   {"Allow", "Deny"},
	"create.policy.resource": {"*"},

	"create.record.type": {"A", "AAAA", "CNAME", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"},

	"create.s3object.acl": s3ACLs,

	"create.scalinggroup.healthcheck-type": {"EC2", "ELB"},

	"create.scalingpolicy.adjustment-type": {"ChangeInCapacity", "ExactCapacity", "PercentChangeInCapacity"},

	"create.stack.capabilities": {"CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"},
	"create.stack.on-failure":   {"DO_NOTHING", "ROLLBACK", "DELETE"},

	"create.subnet.public": boolean,

	"create.subscription.protocol": {"http", "https", "email", "email-json", "sms", "sqs", "lambda"},

	"create.zone.isprivate": boolean,

	"delete.containertask.all-versions": boolean,

	"delete.database.skip-snapshot": boolean,

	"delete.image.delete-snapshots": boolean,

	"delete.policy.all-versions": boolean,

	"delete.record.type": {"A", "AAAA", "CNAME", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"},

	"detach.networkinterface.force": boolean,

	"detach.policy.access":  {"readonly", "full"},
	"detach.policy.service": services,

	"import.image.architecture": {"i386", "x86_64"},
	"import.image.license":      {"AWS", "BYOL"},
	"import.image.platform":     {"Windows", "Linux"},

	"restart.database.with-failover": boolean,

	"start.containertask.type": {"task", "service"},

	"stop.containertask.type": {"task", "service"},

	"update.bucket.acl":            {"private", "public-read", "public-read-write", "aws-exec-read", "authenticated-read", "bucket-owner-read", "bucket-owner-full-control", "log-delivery-write"},
	"update.bucket.public-website": boolean,
	"update.bucket.index-suffix":   {"index.html"},

	"update.distribution.default-file":    {"index.html"},
	"update.distribution.forward-cookies": {"all", "none", "whitelist"},
	"update.distribution.forward-queries": boolean,
	"update.distribution.https-behaviour": {"allow-all", "redirect-to-https", "https-only"},
	"update.distribution.price-class":     {"PriceClass_All", "PriceClass_100", "PriceClass_200"},
	"update.distribution.enable":          boolean,

	"update.image.operation": {"add", "remove"},

	"update.instance.type": instanceTypes,

	"update.policy.effect": {"Allow", "Deny"},

	"update.s3object.acl": s3ACLs,

	"update.securitygroup.inbound":   {"revoke", "authorize"},
	"update.securitygroup.outbound":  {"revoke", "authorize"},
	"update.securitygroup.protocol":  {"tcp", "udp", "icmp", "any"},
	"update.securitygroup.portrange": {""},

	"update.stack.capabilities": {"CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"},

	"update.targetgroup.stickiness": boolean,

	"update.subnet.public": boolean,

	"update.record.type": {"A", "AAAA", "CNAME", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"},
}

type ParamType struct {
	ResourceType, PropertyName string
}

var ParamTypeDoc = map[string]*ParamType{
	"attach.policy.group": {ResourceType: cloud.Group, PropertyName: properties.Name},
	"attach.policy.role":  {ResourceType: cloud.Role, PropertyName: properties.Name},
	"attach.policy.user":  {ResourceType: cloud.User, PropertyName: properties.Name},
	"attach.policy.arn":   {ResourceType: cloud.Policy, PropertyName: properties.Arn},

	"attach.role.instanceprofile": {ResourceType: cloud.InstanceProfile, PropertyName: properties.Name},

	"create.accesskey.user": {ResourceType: cloud.User, PropertyName: properties.Name},

	"create.instance.role": {ResourceType: cloud.Role, PropertyName: properties.Name},

	"create.record.values": {ResourceType: cloud.Record, PropertyName: properties.Records},

	"delete.policy.arn":   {ResourceType: cloud.Policy, PropertyName: properties.Arn},
	"detach.policy.arn":   {ResourceType: cloud.Policy, PropertyName: properties.Arn},
	"detach.policy.group": {ResourceType: cloud.Group, PropertyName: properties.Name},
	"detach.policy.role":  {ResourceType: cloud.Role, PropertyName: properties.Name},
	"detach.policy.user":  {ResourceType: cloud.User, PropertyName: properties.Name},

	"detach.role.instanceprofile": {ResourceType: cloud.InstanceProfile, PropertyName: properties.Name},

	"update.policy.arn": {ResourceType: cloud.Policy, PropertyName: properties.Arn},

	"update.securitygroup.cidr": {ResourceType: cloud.Subnet, PropertyName: properties.CIDR},
}
<commit_msg>Copy image: clarify prompt following user feedback<commit_after>package awsdoc

import (
	"github.com/wallix/awless/cloud"
	"github.com/wallix/awless/cloud/properties"
)

var (
	timeouts      = []string{"10", "60", "180", "300", "600", "900"}
	boolean       = []string{"true", "false"}
	services      = []string{"iam", "ec2", "s3", "route53", "elbv2", "rds", "autoscaling", "lambda", "sns", "sqs", "cloudwatch", "cloudfront", "ecr", "ecs", "applicationautoscaling", "acm", "sts", "cloudformation"}
	instanceTypes = []string{"t2.nano", "t2.micro", "t2.small", "t2.medium", "t2.large", "t2.xlarge", "t2.2xlarge", "m4.large", "m4.xlarge", "c4.large", "c4.xlarge"}
	s3ACLs        = []string{"private", "public-read", "public-read-write", "aws-exec-read", "authenticated-read", "bucket-owner-read", "bucket-owner-full-control", "log-delivery-write"}
	distros       = []string{"amazonlinux", "canonical:ubuntu", "redhat:rhel", "debian:debian", "suselinux", "windows:server"}
	regions       = []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2", "eu-west-1", "eu-west-2", "eu-west-3", "eu-central-1", "ca-central-1", "ap-northeast-1", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-south-1", "sa-east-1"}
)

var EnumDoc = map[string][]string{

	"attach.policy.access":  {"readonly", "full"},
	"attach.policy.service": services,

	"check.database.state":   {"available", "backing-up", "creating", "deleting", "failed", "maintenance", "modifying", "rebooting", "renaming", "resetting-master-credentials", "restore-error", "storage-full", "upgrading", "not-found"},
	"check.database.timeout": timeouts,

	"check.certificate.state":   {"issued", "pending_validation", "not-found"},
	"check.certificate.timeout": timeouts,

	"check.distribution.state":   {"Deployed", "InProgress", "not-found"},
	"check.distribution.timeout": timeouts,

	"check.instance.state":   {"pending", "running", "shutting-down", "terminated", "stopping", "stopped", "not-found"},
	"check.instance.timeout": timeouts,

	"check.loadbalancer.state":   {"provisioning", "active", "failed", "not-found"},
	"check.loadbalancer.timeout": timeouts,

	"check.natgateway.state":   {"pending", "failed", "available", "deleting", "deleted", "not-found"},
	"check.natgateway.timeout": timeouts,

	"check.networkinterface.state":   {"available", "attaching", "detaching", "in-use", "not-found"},
	"check.networkinterface.timeout": timeouts,

	"check.scalinggroup.count":   {"0"},
	"check.scalinggroup.timeout": timeouts,

	"check.securitygroup.state":   {"unused"},
	"check.securitygroup.timeout": timeouts,

	"check.volume.state":   {"available", "in-use", "not-found"},
	"check.volume.timeout": timeouts,

	"create.accesskey.save": boolean,

	"create.alarm.operator":           {"GreaterThanThreshold", "LessThanThreshold", "LessThanOrEqualToThreshold", "GreaterThanOrEqualToThreshold"},
	"create.alarm.statistic-function": {"Minimum", "Maximum", "Sum", "Average", "SampleCount", "pNN.NN"},
	"create.alarm.unit":               {"Seconds", "Microseconds", "Milliseconds", "Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes", "Bits", "Kilobits", "Megabits", "Gigabits", "Terabits", "Percent", "Count", "Bytes/Second", "Kilobytes/Second", "Megabytes/Second", "Gigabytes/Second", "Terabytes/Second", "Bits/Second", "Kilobits/Second", "Megabits/Second", "Gigabits/Second", "Terabits/Second", "Count/Second", "None"},

	"create.appscalingtarget.dimension":         {"ecs:service:DesiredCount", "ec2:spot-fleet-request:TargetCapacity", "elasticmapreduce:instancegroup:InstanceCount", "appstream:fleet:DesiredCapacity", "dynamodb:table:ReadCapacityUnits", "dynamodb:table:WriteCapacityUnits", "dynamodb:index:ReadCapacityUnits", "dynamodb:index:WriteCapacityUnits"},
	"create.appscalingtarget.service-namespace": {"ecs", "ec2", "elasticmapreduce", "appstream", "dynamodb"},

	"create.appscalingpolicy.dimension":                    {"ecs:service:DesiredCount", "ec2:spot-fleet-request:TargetCapacity", "elasticmapreduce:instancegroup:InstanceCount", "appstream:fleet:DesiredCapacity", "dynamodb:table:ReadCapacityUnits", "dynamodb:table:WriteCapacityUnits", "dynamodb:index:ReadCapacityUnits", "dynamodb:index:WriteCapacityUnits"},
	"create.appscalingpolicy.service-namespace":            {"ecs", "ec2", "elasticmapreduce", "appstream", "dynamodb"},
	"create.appscalingpolicy.type":                         {"StepScaling"},
	"create.appscalingpolicy.stepscaling-adjustment-type":  {"ChangeInCapacity", "ExactCapacity", "PercentChangeInCapacity"},
	"create.appscalingpolicy.stepscaling-adjustments":      {"0::+1", ":0:-1", "75::+1"},
	"create.appscalingpolicy.stepscaling-aggregation-type": {"Minimum", "Maximum", "Average"},

	"create.bucket.acl": s3ACLs,

	"create.database.engine":             {"mysql", "mariadb", "postgres", "aurora", "oracle-se1", "oracle-se2", "oracle-se", "oracle-ee", "sqlserver-ee", "sqlserver-se", "sqlserver-ex", "sqlserver-web"},
	"create.database.copytagstosnapshot": boolean,
	"create.database.encrypted":          boolean,
	"create.database.license":            {"license-included", "bring-your-own-license", "general-public-license"},
	"create.database.multiaz":            boolean,
	"create.database.public":             boolean,
	"create.database.storagetype":        {"standard", "gp2", "io1"},
	"create.database.type":               {"db.t1.micro", "db.m1.small", "db.m1.medium", "db.m1.large", "db.m1.xlarge", "db.m2.xlarge |db.m2.2xlarge", "db.m2.4xlarge", "db.m3.medium", "db.m3.large", "db.m3.xlarge", "db.m3.2xlarge", "db.m4.large", "db.m4.xlarge", "db.m4.2xlarge", "db.m4.4xlarge", "db.m4.10xlarge", "db.r3.large", "db.r3.xlarge", "db.r3.2xlarge", "db.r3.4xlarge", "db.r3.8xlarge", "db.t2.micro", "db.t2.small", "db.t2.medium", "db.t2.large"},

	"create.distribution.default-file":    {"index.html"},
	"create.distribution.enable":          boolean,
	"create.distribution.forward-cookies": {"all", "none", "whitelist"},
	"create.distribution.forward-queries": boolean,
	"create.distribution.https-behaviour": {"allow-all", "redirect-to-https", "https-only"},
	"create.distribution.price-class":     {"PriceClass_All", "PriceClass_100", "PriceClass_200"},

	"create.elasticip.domain": {"vpc", "ec2-classic"},

	"create.function.runtime": {"nodejs", "nodejs4.3", "nodejs6.10", "java8", "python2.7", "python3.6", "dotnetcore1.0", "nodejs4.3-edge"},

	"create.instance.distro":   distros,
	"create.instance.type":     instanceTypes,
	"create.instance.lock":     boolean,
	"create.instance.userdata": {""},

	"create.image.reboot": boolean,

	"create.keypair.encrypted": boolean,

	"create.launchconfiguration.distro":   distros,
	"create.launchconfiguration.type":     {"t2.nano", "t2.micro", "t2.small", "t2.medium", "t2.large", "t2.xlarge", "t2.2xlarge", "m4.large", "m4.xlarge", "c4.large", "c4.xlarge"},
	"create.launchconfiguration.userdata": {""},
	"create.launchconfiguration.public":   boolean,

	"create.listener.actiontype": {"forward"},
	"create.listener.protocol":   {"HTTP", "HTTPS"},
	"create.listener.sslpolicy":  {"ELBSecurityPolicy-2016-08", "ELBSecurityPolicy-TLS-1-2-2017-01", "ELBSecurityPolicy-TLS-1-1-2017-01", "ELBSecurityPolicy-2015-05", "ELBSecurityPolicy-TLS-1-0-2015-04"},

	"create.policy.action":   {""},
	"create.policy.effect":   {"Allow", "Deny"},
	"create.policy.resource": {"*"},

	"create.record.type": {"A", "AAAA", "CNAME", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"},

	"create.s3object.acl": s3ACLs,

	"create.scalinggroup.healthcheck-type": {"EC2", "ELB"},

	"create.scalingpolicy.adjustment-type": {"ChangeInCapacity", "ExactCapacity", "PercentChangeInCapacity"},

	"create.stack.capabilities": {"CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"},
	"create.stack.on-failure":   {"DO_NOTHING", "ROLLBACK", "DELETE"},

	"create.subnet.public": boolean,

	"create.subscription.protocol": {"http", "https", "email", "email-json", "sms", "sqs", "lambda"},

	"create.zone.isprivate": boolean,

	"copy.image.source-id":     {""},
	"copy.image.source-region": regions,

	"delete.containertask.all-versions": boolean,

	"delete.database.skip-snapshot": boolean,

	"delete.image.delete-snapshots": boolean,

	"delete.policy.all-versions": boolean,

	"delete.record.type": {"A", "AAAA", "CNAME", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"},

	"detach.networkinterface.force": boolean,

	"detach.policy.access":  {"readonly", "full"},
	"detach.policy.service": services,

	"import.image.architecture": {"i386", "x86_64"},
	"import.image.license":      {"AWS", "BYOL"},
	"import.image.platform":     {"Windows", "Linux"},

	"restart.database.with-failover": boolean,

	"start.containertask.type": {"task", "service"},

	"stop.containertask.type": {"task", "service"},

	"update.bucket.acl":            {"private", "public-read", "public-read-write", "aws-exec-read", "authenticated-read", "bucket-owner-read", "bucket-owner-full-control", "log-delivery-write"},
	"update.bucket.public-website": boolean,
	"update.bucket.index-suffix":   {"index.html"},

	"update.distribution.default-file":    {"index.html"},
	"update.distribution.forward-cookies": {"all", "none", "whitelist"},
	"update.distribution.forward-queries": boolean,
	"update.distribution.https-behaviour": {"allow-all", "redirect-to-https", "https-only"},
	"update.distribution.price-class":     {"PriceClass_All", "PriceClass_100", "PriceClass_200"},
	"update.distribution.enable":          boolean,

	"update.image.operation": {"add", "remove"},

	"update.instance.type": instanceTypes,

	"update.policy.effect": {"Allow", "Deny"},

	"update.s3object.acl": s3ACLs,

	"update.securitygroup.inbound":   {"revoke", "authorize"},
	"update.securitygroup.outbound":  {"revoke", "authorize"},
	"update.securitygroup.protocol":  {"tcp", "udp", "icmp", "any"},
	"update.securitygroup.portrange": {""},

	"update.stack.capabilities": {"CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"},

	"update.targetgroup.stickiness": boolean,

	"update.subnet.public": boolean,

	"update.record.type": {"A", "AAAA", "CNAME", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"},
}

type ParamType struct {
	ResourceType, PropertyName string
}

var ParamTypeDoc = map[string]*ParamType{
	"attach.policy.group": {ResourceType: cloud.Group, PropertyName: properties.Name},
	"attach.policy.role":  {ResourceType: cloud.Role, PropertyName: properties.Name},
	"attach.policy.user":  {ResourceType: cloud.User, PropertyName: properties.Name},
	"attach.policy.arn":   {ResourceType: cloud.Policy, PropertyName: properties.Arn},

	"attach.role.instanceprofile": {ResourceType: cloud.InstanceProfile, PropertyName: properties.Name},

	"create.accesskey.user": {ResourceType: cloud.User, PropertyName: properties.Name},

	"create.instance.role": {ResourceType: cloud.Role, PropertyName: properties.Name},

	"create.record.values": {ResourceType: cloud.Record, PropertyName: properties.Records},

	"delete.policy.arn":   {ResourceType: cloud.Policy, PropertyName: properties.Arn},
	"detach.policy.arn":   {ResourceType: cloud.Policy, PropertyName: properties.Arn},
	"detach.policy.group": {ResourceType: cloud.Group, PropertyName: properties.Name},
	"detach.policy.role":  {ResourceType: cloud.Role, PropertyName: properties.Name},
	"detach.policy.user":  {ResourceType: cloud.User, PropertyName: properties.Name},

	"detach.role.instanceprofile": {ResourceType: cloud.InstanceProfile, PropertyName: properties.Name},

	"update.policy.arn": {ResourceType: cloud.Policy, PropertyName: properties.Arn},

	"update.securitygroup.cidr": {ResourceType: cloud.Subnet, PropertyName: properties.CIDR},
}
<|endoftext|>


<commit_before>package api_test

import (
	"github.com/nais/naisd/api"
	"github.com/nais/naisd/api/naisrequest"
	"github.com/nais/naisd/pkg/event"
	"testing"

	"github.com/stretchr/testify/assert"
)

type containerImageTest struct {
	name      string
	container deployment.ContainerImage
}

var containerImageTests = []containerImageTest{
	{
		name: "nginx",
		container: deployment.ContainerImage{
			Name: "docker.io/library/nginx",
			Tag:  "latest",
		},
	},
	{
		name: "nginx:latest",
		container: deployment.ContainerImage{
			Name: "docker.io/library/nginx",
			Tag:  "latest",
		},
	},
	{
		name: "nginx:tagged",
		container: deployment.ContainerImage{
			Name: "docker.io/library/nginx",
			Tag:  "tagged",
		},
	},
	{
		name: "organization/repo:0.1.2",
		container: deployment.ContainerImage{
			Name: "docker.io/organization/repo",
			Tag:  "0.1.2",
		},
	},
	{
		name: "nginx@sha256:5c3c0bbb737db91024882667ad5acbe64230ddecaca1d019968d8df2c4adab35",
		container: deployment.ContainerImage{
			Name: "docker.io/library/nginx",
			Hash: "sha256:5c3c0bbb737db91024882667ad5acbe64230ddecaca1d019968d8df2c4adab35",
		},
	},
	{
		name: "internal.repo:12345/foo/bar/image",
		container: deployment.ContainerImage{
			Name: "internal.repo:12345/foo/bar/image",
			Tag:  "latest",
		},
	},
	{
		name: "internal.repo:12345/foo/bar/image:tagged",
		container: deployment.ContainerImage{
			Name: "internal.repo:12345/foo/bar/image",
			Tag:  "tagged",
		},
	},
	{
		name: "internal.repo:12345/foo/bar/image@sha256:5c3c0bbb737db91024882667ad5acbe64230ddecaca1d019968d8df2c4adab35",
		container: deployment.ContainerImage{
			Name: "internal.repo:12345/foo/bar/image",
			Hash: "sha256:5c3c0bbb737db91024882667ad5acbe64230ddecaca1d019968d8df2c4adab35",
		},
	},
}

func TestContainerImage(t *testing.T) {
	for _, test := range containerImageTests {
		container := api.ContainerImage(test.name)
		assert.Equal(t, test.container, container)
	}
}

func TestNewDeploymentEvent(t *testing.T) {

	deploymentRequest := naisrequest.Deploy{
		Application:      "myapplication",
		Version:          "1.2.3",
		FasitEnvironment: "t0",
		FasitUsername:    "A123456",
		Namespace:        "mynamespace",
		Environment:      "whichenvironment",
	}

	manifest := api.NaisManifest{
		Team:  "myteam",
		Image: "image:version",
	}

	t.Run("Event defaults are picked up from Application correctly", func(t *testing.T) {
		event := api.NewDeploymentEvent(deploymentRequest, manifest, "test-cluster")

		assert.Equal(t, deployment.PlatformType_nais, event.GetPlatform().GetType())
		assert.Empty(t, event.GetPlatform().GetVariant())
		assert.Equal(t, deployment.System_naisd, event.GetSource())
		assert.Equal(t, "A123456", event.GetDeployer().GetIdent())
		assert.Equal(t, "myteam", event.GetTeam())
		assert.Equal(t, deployment.RolloutStatus_unknown, event.GetRolloutStatus())
		assert.Equal(t, deployment.Environment_development, event.GetEnvironment())
		assert.Equal(t, "mynamespace", event.GetNamespace())
		assert.Equal(t, "test-cluster", event.GetCluster())
		assert.Equal(t, "myapplication", event.GetApplication())
		assert.Equal(t, "version", event.GetVersion())

		image := event.GetImage()
		assert.NotEmpty(t, image)
		assert.Equal(t, deployment.ContainerImage{
			Name: "docker.io/library/image",
			Tag:  "version",
		}, *image)

		assert.True(t, event.GetTimestampAsTime().Unix() > 0)
		assert.True(t, event.GetTimestampAsTime().UnixNano() > 0)
	})

	t.Run("Production cluster derived from FasitEnvironment=p", func(t *testing.T) {
		deploymentRequest.FasitEnvironment = "p"
		event := api.NewDeploymentEvent(deploymentRequest, manifest, "test-cluster")
		assert.Equal(t, deployment.Environment_production, event.GetEnvironment())
	})

}
<commit_msg>fix broken test<commit_after>package api_test

import (
	"github.com/nais/naisd/api"
	"github.com/nais/naisd/api/naisrequest"
	"github.com/nais/naisd/pkg/event"
	"testing"

	"github.com/stretchr/testify/assert"
)

type containerImageTest struct {
	name      string
	container deployment.ContainerImage
}

var containerImageTests = []containerImageTest{
	{
		name: "nginx",
		container: deployment.ContainerImage{
			Name: "docker.io/library/nginx",
			Tag:  "latest",
		},
	},
	{
		name: "nginx:latest",
		container: deployment.ContainerImage{
			Name: "docker.io/library/nginx",
			Tag:  "latest",
		},
	},
	{
		name: "nginx:tagged",
		container: deployment.ContainerImage{
			Name: "docker.io/library/nginx",
			Tag:  "tagged",
		},
	},
	{
		name: "organization/repo:0.1.2",
		container: deployment.ContainerImage{
			Name: "docker.io/organization/repo",
			Tag:  "0.1.2",
		},
	},
	{
		name: "nginx@sha256:5c3c0bbb737db91024882667ad5acbe64230ddecaca1d019968d8df2c4adab35",
		container: deployment.ContainerImage{
			Name: "docker.io/library/nginx",
			Hash: "sha256:5c3c0bbb737db91024882667ad5acbe64230ddecaca1d019968d8df2c4adab35",
		},
	},
	{
		name: "internal.repo:12345/foo/bar/image",
		container: deployment.ContainerImage{
			Name: "internal.repo:12345/foo/bar/image",
			Tag:  "latest",
		},
	},
	{
		name: "internal.repo:12345/foo/bar/image:tagged",
		container: deployment.ContainerImage{
			Name: "internal.repo:12345/foo/bar/image",
			Tag:  "tagged",
		},
	},
	{
		name: "internal.repo:12345/foo/bar/image@sha256:5c3c0bbb737db91024882667ad5acbe64230ddecaca1d019968d8df2c4adab35",
		container: deployment.ContainerImage{
			Name: "internal.repo:12345/foo/bar/image",
			Hash: "sha256:5c3c0bbb737db91024882667ad5acbe64230ddecaca1d019968d8df2c4adab35",
		},
	},
}

func TestContainerImage(t *testing.T) {
	for _, test := range containerImageTests {
		container := api.ContainerImage(test.name)
		assert.Equal(t, test.container, container)
	}
}

func TestNewDeploymentEvent(t *testing.T) {

	deploymentRequest := naisrequest.Deploy{
		Application:      "myapplication",
		Version:          "1.2.3",
		FasitEnvironment: "t0",
		FasitUsername:    "A123456",
		Namespace:        "mynamespace",
		Environment:      "whichenvironment",
	}

	manifest := api.NaisManifest{
		Team:  "myteam",
		Image: "image:version",
	}

	t.Run("Event defaults are picked up from Application correctly", func(t *testing.T) {
		event := api.NewDeploymentEvent(deploymentRequest, manifest, "test-cluster")

		assert.Equal(t, deployment.PlatformType_nais, event.GetPlatform().GetType())
		assert.Empty(t, event.GetPlatform().GetVariant())
		assert.Equal(t, deployment.System_naisd, event.GetSource())
		assert.Equal(t, "A123456", event.GetDeployer().GetIdent())
		assert.Equal(t, "myteam", event.GetTeam())
		assert.Equal(t, deployment.RolloutStatus_complete, event.GetRolloutStatus())
		assert.Equal(t, deployment.Environment_development, event.GetEnvironment())
		assert.Equal(t, "mynamespace", event.GetNamespace())
		assert.Equal(t, "test-cluster", event.GetCluster())
		assert.Equal(t, "myapplication", event.GetApplication())
		assert.Equal(t, "version", event.GetVersion())

		image := event.GetImage()
		assert.NotEmpty(t, image)
		assert.Equal(t, deployment.ContainerImage{
			Name: "docker.io/library/image",
			Tag:  "version",
		}, *image)

		assert.True(t, event.GetTimestampAsTime().Unix() > 0)
		assert.True(t, event.GetTimestampAsTime().UnixNano() > 0)
	})

	t.Run("Production cluster derived from FasitEnvironment=p", func(t *testing.T) {
		deploymentRequest.FasitEnvironment = "p"
		event := api.NewDeploymentEvent(deploymentRequest, manifest, "test-cluster")
		assert.Equal(t, deployment.Environment_production, event.GetEnvironment())
	})

}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"launchpad.net/mgo"
	"log"
	"os"
	"runtime/pprof"
	. "github.com/timeredbull/tsuru/api/service"
	. "github.com/timeredbull/tsuru/database"
)

var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
var memprofile = flag.String("memprofile", "", "write memory profile to file")

func main() {
	flag.Parse()
	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	session, err := mgo.Dial("localhost:27017")
	if err != nil {
		panic(err)
	}
	Mdb = session.DB("tsuru")
	defer session.Close()

	sType := &ServiceType{Name: "Mysql", Charm: "mysql"}
	sType.Create()
	var s Service
	var name string
	for i := 0; i < 700; i++ {
		name = fmt.Sprintf("myService%d", i)
		s = Service{ServiceTypeId: sType.Id, Name: name}
		s.Create()
	}
	s = Service{}
	s.All()

	if *memprofile != "" {
		f, err := os.Create(*memprofile)
		if err != nil {
			panic(err)
			/* log.Fatal(err) */
		}
		pprof.WriteHeapProfile(f)
		f.Close()
		return
	}
}
<commit_msg>Dropping collection in the end of the profile<commit_after>package main

import (
	"flag"
	"fmt"
	"launchpad.net/mgo"
	"log"
	"os"
	"runtime/pprof"
	. "github.com/timeredbull/tsuru/api/service"
	. "github.com/timeredbull/tsuru/database"
)

var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
var memprofile = flag.String("memprofile", "", "write memory profile to file")

func main() {
	flag.Parse()
	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	session, err := mgo.Dial("localhost:27017")
	if err != nil {
		panic(err)
	}
	Mdb = session.DB("tsuru")
	defer session.Close()

	sType := &ServiceType{Name: "Mysql", Charm: "mysql"}
	sType.Create()
	var s Service
	var name string
	for i := 0; i < 700; i++ {
		name = fmt.Sprintf("myService%d", i)
		s = Service{ServiceTypeId: sType.Id, Name: name}
		s.Create()
	}
	s = Service{}
	s.All()

	if *memprofile != "" {
		f, err := os.Create(*memprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.WriteHeapProfile(f)
		f.Close()
	}

	c := Mdb.C("services")
	defer c.DropCollection()
}
<|endoftext|>


<commit_before>package openstack

import (
	"fmt"
	"log"
	"time"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
	"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
)

func resourceComputeInstance() *schema.Resource {
	return &schema.Resource{
		Create: resourceComputeInstanceCreate,
		Read:   resourceComputeInstanceRead,
		Update: resourceComputeInstanceUpdate,
		Delete: resourceComputeInstanceDelete,

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

			"image_ref": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
			},

			"flavor_ref": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
			},

			"security_groups": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				ForceNew: false,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Set: func(v interface{}) int {
					return hashcode.String(v.(string))
				},
			},

			"availability_zone": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"networks": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"uuid": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},

						"port": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},

						"fixed_ip": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
					},
				},
			},

			"metadata": &schema.Schema{
				Type:     schema.TypeMap,
				Optional: true,
				ForceNew: false,
			},

			"config_drive": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				ForceNew: true,
			},

			"access_ip_v4": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
				ForceNew: false,
			},

			"access_ip_v6": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
				ForceNew: false,
			},

			"key_pair": &schema.Schema{
				Type:     schema.TypeMap,
				Optional: true,
				ForceNew: true,
			},
		},
	}
}

func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	osClient := config.computeV2Client

	var createOpts servers.CreateOptsBuilder

	serverCreateOpts := &servers.CreateOpts{
		Name:      d.Get("name").(string),
		ImageRef:  d.Get("image_ref").(string),
		FlavorRef: d.Get("flavor_ref").(string),
		//SecurityGroups []string
		AvailabilityZone: d.Get("availability_zone").(string),
		Networks:         resourceInstanceNetworks(d),
		Metadata:         resourceInstanceMetadata(d),
		ConfigDrive:      d.Get("config_drive").(bool),
	}

	if kp, ok := d.Get("key_pair").(map[string]interface{}); ok && kp != nil {
		if keyName, ok := kp["name"].(string); ok && keyName != "" {
			createOpts = &keypairs.CreateOptsExt{
				serverCreateOpts,
				keyName,
			}
		}
	}

	log.Printf("[INFO] Requesting instance creation")
	server, err := servers.Create(osClient, createOpts).Extract()
	if err != nil {
		return fmt.Errorf("Error creating OpenStack server: %s", err)
	}
	log.Printf("[INFO] Instance ID: %s", server.ID)

	// Store the ID now
	d.SetId(server.ID)

	// Wait for the instance to become running so we can get some attributes
	// that aren't available until later.
	log.Printf(
		"[DEBUG] Waiting for instance (%s) to become running",
		server.ID)

	stateConf := &resource.StateChangeConf{
		Pending:    []string{"BUILD"},
		Target:     "ACTIVE",
		Refresh:    ServerStateRefreshFunc(osClient, server.ID),
		Timeout:    10 * time.Minute,
		Delay:      10 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	serverRaw, err := stateConf.WaitForState()
	if err != nil {
		return fmt.Errorf(
			"Error waiting for instance (%s) to become ready: %s",
			server.ID, err)
	}

	server = serverRaw.(*servers.Server)

	return resourceComputeInstanceRead(d, meta)
}

func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	osClient := config.computeV2Client

	server, err := servers.Get(osClient, d.Id()).Extract()
	if err != nil {
		return fmt.Errorf("Error retrieving OpenStack server: %s", err)
	}

	log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server)

	d.Set("name", server.Name)
	d.Set("access_ip_v4", server.AccessIPv4)
	d.Set("access_ip_v6", server.AccessIPv6)

	host := server.AccessIPv4
	if host == "" {
		if publicAddressesRaw, ok := server.Addresses["public"]; ok {
			publicAddresses := publicAddressesRaw.([]interface{})
			for _, paRaw := range publicAddresses {
				pa := paRaw.(map[string]interface{})
				if pa["version"].(float64) == 4 {
					host = pa["addr"].(string)
				}
			}
		}
	}

	log.Printf("host: %s", host)

	// Initialize the connection info
	d.SetConnInfo(map[string]string{
		"type": "ssh",
		"host": host,
	})

	d.Set("metadata", server.Metadata)

	return nil
}

func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	osClient := config.computeV2Client

	var updateOpts servers.UpdateOpts
	// If the Metadata has changed, then update that.
	if d.HasChange("name") {
		updateOpts.Name = d.Get("name").(string)
	}
	if d.HasChange("access_ip_v4") {
		updateOpts.AccessIPv4 = d.Get("access_ip_v4").(string)
	}
	if d.HasChange("access_ip_v6") {
		updateOpts.AccessIPv4 = d.Get("access_ip_v6").(string)
	}

	// If there's nothing to update, don't waste an HTTP call.
	if updateOpts != (servers.UpdateOpts{}) {
		log.Printf("[DEBUG] Updating Server %s with options: %+v", d.Id(), updateOpts)

		_, err := servers.Update(osClient, d.Id(), updateOpts).Extract()
		if err != nil {
			return fmt.Errorf("Error updating OpenStack server: %s", err)
		}
	}

	if d.HasChange("metadata") {
		var metadataOpts servers.MetadataOpts
		metadataOpts = make(servers.MetadataOpts)
		newMetadata := d.Get("metadata").(map[string]interface{})
		for k, v := range newMetadata {
			metadataOpts[k] = v.(string)
		}

		_, err := servers.UpdateMetadata(osClient, d.Id(), metadataOpts).Extract()
		if err != nil {
			return fmt.Errorf("Error updating OpenStack server (%s) metadata: %s", d.Id(), err)
		}
	}

	return resourceComputeInstanceRead(d, meta)
}

func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	osClient := config.computeV2Client

	err := servers.Delete(osClient, d.Id()).ExtractErr()
	if err != nil {
		return fmt.Errorf("Error deleting OpenStack server: %s", err)
	}

	// Wait for the instance to delete before moving on.
	log.Printf(
		"[DEBUG] Waiting for instance (%s) to delete",
		d.Id())

	stateConf := &resource.StateChangeConf{
		Target:     "",
		Refresh:    ServerStateRefreshFunc(osClient, d.Id()),
		Timeout:    10 * time.Minute,
		Delay:      10 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return fmt.Errorf(
			"Error waiting for instance (%s) to delete: %s",
			d.Id(), err)
	}

	d.SetId("")
	return nil
}

// ServerStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// an OpenStack instance.
func ServerStateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		s, err := servers.Get(client, instanceID).Extract()
		if err != nil {
			return nil, "", err
		}

		return s, s.Status, nil
	}
}

func resourceInstanceNetworks(d *schema.ResourceData) []servers.Network {
	rawNetworks := d.Get("networks").([]interface{})
	networks := make([]servers.Network, len(rawNetworks))
	for i, raw := range rawNetworks {
		rawMap := raw.(map[string]interface{})
		networks[i] = servers.Network{
			UUID:    rawMap["uuid"].(string),
			Port:    rawMap["port"].(string),
			FixedIP: rawMap["fixed_ip"].(string),
		}
	}
	return networks
}

func resourceInstanceMetadata(d *schema.ResourceData) map[string]string {
	m := make(map[string]string)
	for key, val := range d.Get("metadata").(map[string]interface{}) {
		m[key] = val.(string)
	}
	return m
}
<commit_msg>server resizing<commit_after>package openstack

import (
	"fmt"
	"log"
	"time"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
	"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
)

func resourceComputeInstance() *schema.Resource {
	return &schema.Resource{
		Create: resourceComputeInstanceCreate,
		Read:   resourceComputeInstanceRead,
		Update: resourceComputeInstanceUpdate,
		Delete: resourceComputeInstanceDelete,

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

			"image_ref": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
			},

			"flavor_ref": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
			},

			"security_groups": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				ForceNew: false,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Set: func(v interface{}) int {
					return hashcode.String(v.(string))
				},
			},

			"availability_zone": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"networks": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"uuid": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},

						"port": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},

						"fixed_ip": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
					},
				},
			},

			"metadata": &schema.Schema{
				Type:     schema.TypeMap,
				Optional: true,
				ForceNew: false,
			},

			"config_drive": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				ForceNew: true,
			},

			"access_ip_v4": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
				ForceNew: false,
			},

			"access_ip_v6": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
				ForceNew: false,
			},

			"key_pair": &schema.Schema{
				Type:     schema.TypeMap,
				Optional: true,
				ForceNew: true,
			},
		},
	}
}

func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	osClient := config.computeV2Client

	var createOpts servers.CreateOptsBuilder

	serverCreateOpts := &servers.CreateOpts{
		Name:      d.Get("name").(string),
		ImageRef:  d.Get("image_ref").(string),
		FlavorRef: d.Get("flavor_ref").(string),
		//SecurityGroups []string
		AvailabilityZone: d.Get("availability_zone").(string),
		Networks:         resourceInstanceNetworks(d),
		Metadata:         resourceInstanceMetadata(d),
		ConfigDrive:      d.Get("config_drive").(bool),
	}

	if kp, ok := d.Get("key_pair").(map[string]interface{}); ok && kp != nil {
		if keyName, ok := kp["name"].(string); ok && keyName != "" {
			createOpts = &keypairs.CreateOptsExt{
				serverCreateOpts,
				keyName,
			}
		}
	}

	log.Printf("[INFO] Requesting instance creation")
	server, err := servers.Create(osClient, createOpts).Extract()
	if err != nil {
		return fmt.Errorf("Error creating OpenStack server: %s", err)
	}
	log.Printf("[INFO] Instance ID: %s", server.ID)

	// Store the ID now
	d.SetId(server.ID)

	// Wait for the instance to become running so we can get some attributes
	// that aren't available until later.
	log.Printf(
		"[DEBUG] Waiting for instance (%s) to become running",
		server.ID)

	stateConf := &resource.StateChangeConf{
		Pending:    []string{"BUILD"},
		Target:     "ACTIVE",
		Refresh:    ServerStateRefreshFunc(osClient, server.ID),
		Timeout:    10 * time.Minute,
		Delay:      10 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	serverRaw, err := stateConf.WaitForState()
	if err != nil {
		return fmt.Errorf(
			"Error waiting for instance (%s) to become ready: %s",
			server.ID, err)
	}

	server = serverRaw.(*servers.Server)

	return resourceComputeInstanceRead(d, meta)
}

func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	osClient := config.computeV2Client

	server, err := servers.Get(osClient, d.Id()).Extract()
	if err != nil {
		return fmt.Errorf("Error retrieving OpenStack server: %s", err)
	}

	log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server)

	d.Set("name", server.Name)
	d.Set("access_ip_v4", server.AccessIPv4)
	d.Set("access_ip_v6", server.AccessIPv6)

	host := server.AccessIPv4
	if host == "" {
		if publicAddressesRaw, ok := server.Addresses["public"]; ok {
			publicAddresses := publicAddressesRaw.([]interface{})
			for _, paRaw := range publicAddresses {
				pa := paRaw.(map[string]interface{})
				if pa["version"].(float64) == 4 {
					host = pa["addr"].(string)
				}
			}
		}
	}

	log.Printf("host: %s", host)

	// Initialize the connection info
	d.SetConnInfo(map[string]string{
		"type": "ssh",
		"host": host,
	})

	d.Set("metadata", server.Metadata)
	newFlavor, ok := server.Flavor["id"].(string)
	if !ok {
		return fmt.Errorf("Error setting OpenStack server's flavor: %v", newFlavor)
	}
	d.Set("flavor_ref", newFlavor)

	return nil
}

func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	osClient := config.computeV2Client

	var updateOpts servers.UpdateOpts
	// If the Metadata has changed, then update that.
	if d.HasChange("name") {
		updateOpts.Name = d.Get("name").(string)
	}
	if d.HasChange("access_ip_v4") {
		updateOpts.AccessIPv4 = d.Get("access_ip_v4").(string)
	}
	if d.HasChange("access_ip_v6") {
		updateOpts.AccessIPv4 = d.Get("access_ip_v6").(string)
	}

	// If there's nothing to update, don't waste an HTTP call.
	if updateOpts != (servers.UpdateOpts{}) {
		log.Printf("[DEBUG] Updating Server %s with options: %+v", d.Id(), updateOpts)

		_, err := servers.Update(osClient, d.Id(), updateOpts).Extract()
		if err != nil {
			return fmt.Errorf("Error updating OpenStack server: %s", err)
		}
	}

	if d.HasChange("metadata") {
		var metadataOpts servers.MetadataOpts
		metadataOpts = make(servers.MetadataOpts)
		newMetadata := d.Get("metadata").(map[string]interface{})
		for k, v := range newMetadata {
			metadataOpts[k] = v.(string)
		}

		_, err := servers.UpdateMetadata(osClient, d.Id(), metadataOpts).Extract()
		if err != nil {
			return fmt.Errorf("Error updating OpenStack server (%s) metadata: %s", d.Id(), err)
		}
	}

	if d.HasChange("flavor_ref") {
		resizeOpts := &servers.ResizeOpts{
			FlavorRef: d.Get("flavor_ref").(string),
		}
		err := servers.Resize(osClient, d.Id(), resizeOpts).ExtractErr()
		if err != nil {
			return fmt.Errorf("Error resizing OpenStack server: %s", err)
		}

		// Wait for the instance to finish resizing.
		log.Printf("[DEBUG] Waiting for instance (%s) to finish resizing", d.Id())

		stateConf := &resource.StateChangeConf{
			Pending:    []string{"RESIZE"},
			Target:     "VERIFY_RESIZE",
			Refresh:    ServerStateRefreshFunc(osClient, d.Id()),
			Timeout:    3 * time.Minute,
			Delay:      10 * time.Second,
			MinTimeout: 3 * time.Second,
		}

		_, err = stateConf.WaitForState()
		if err != nil {
			return fmt.Errorf("Error waiting for instance (%s) to resize: %s", d.Id(), err)
		}

		// Confirm resize.
		log.Printf("[DEBUG] Confirming resize")
		err = servers.ConfirmResize(osClient, d.Id()).ExtractErr()
		if err != nil {
			return fmt.Errorf("Error confirming resize of OpenStack server: %s", err)
		}

		stateConf = &resource.StateChangeConf{
			Pending:    []string{"VERIFY_RESIZE"},
			Target:     "ACTIVE",
			Refresh:    ServerStateRefreshFunc(osClient, d.Id()),
			Timeout:    3 * time.Minute,
			Delay:      10 * time.Second,
			MinTimeout: 3 * time.Second,
		}

		_, err = stateConf.WaitForState()
		if err != nil {
			return fmt.Errorf("Error waiting for instance (%s) to confirm resize: %s", d.Id(), err)
		}
	}

	return resourceComputeInstanceRead(d, meta)
}

func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	osClient := config.computeV2Client

	err := servers.Delete(osClient, d.Id()).ExtractErr()
	if err != nil {
		return fmt.Errorf("Error deleting OpenStack server: %s", err)
	}

	// Wait for the instance to delete before moving on.
	log.Printf("[DEBUG] Waiting for instance (%s) to delete", d.Id())

	stateConf := &resource.StateChangeConf{
		Target:     "",
		Refresh:    ServerStateRefreshFunc(osClient, d.Id()),
		Timeout:    10 * time.Minute,
		Delay:      10 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return fmt.Errorf(
			"Error waiting for instance (%s) to delete: %s",
			d.Id(), err)
	}

	d.SetId("")
	return nil
}

// ServerStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// an OpenStack instance.
func ServerStateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		s, err := servers.Get(client, instanceID).Extract()
		if err != nil {
			return nil, "", err
		}

		return s, s.Status, nil
	}
}

func resourceInstanceNetworks(d *schema.ResourceData) []servers.Network {
	rawNetworks := d.Get("networks").([]interface{})
	networks := make([]servers.Network, len(rawNetworks))
	for i, raw := range rawNetworks {
		rawMap := raw.(map[string]interface{})
		networks[i] = servers.Network{
			UUID:    rawMap["uuid"].(string),
			Port:    rawMap["port"].(string),
			FixedIP: rawMap["fixed_ip"].(string),
		}
	}
	return networks
}

func resourceInstanceMetadata(d *schema.ResourceData) map[string]string {
	m := make(map[string]string)
	for key, val := range d.Get("metadata").(map[string]interface{}) {
		m[key] = val.(string)
	}
	return m
}
<|endoftext|>