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

// The following enables go generate to generate the doc.go file.
//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .

package main

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"unicode"

	"v.io/v23/vdl"
	"v.io/v23/vom"
	"v.io/x/lib/cmdline"
)

func main() {
	cmdline.Main(cmdVom)
}

var cmdVom = &cmdline.Command{
	Name:  "vom",
	Short: "helps debug the Vanadium Object Marshaling wire protocol",
	Long: `
Command vom helps debug the Vanadium Object Marshaling wire protocol.
`,
	Children: []*cmdline.Command{cmdDecode, cmdDump},
}

var cmdDecode = &cmdline.Command{
	Runner: cmdline.RunnerFunc(runDecode),
	Name:   "decode",
	Short:  "Decode data encoded in the vom format",
	Long: `
Decode decodes data encoded in the vom format.  If no arguments are provided,
decode reads the data from stdin, otherwise the argument is the data.

By default the data is assumed to be represented in hex, with all whitespace
anywhere in the data ignored.  Use the -data flag to specify other data
representations.

`,
	ArgsName: "[data]",
	ArgsLong: "[data] is the data to decode; if not specified, reads from stdin",
}

var cmdDump = &cmdline.Command{
	Runner: cmdline.RunnerFunc(runDump),
	Name:   "dump",
	Short:  "Dump data encoded in the vom format into formatted output",
	Long: `
Dump dumps data encoded in the vom format, generating formatted output
describing each portion of the encoding.  If no arguments are provided, dump
reads the data from stdin, otherwise the argument is the data.

By default the data is assumed to be represented in hex, with all whitespace
anywhere in the data ignored.  Use the -data flag to specify other data
representations.

Calling "vom dump" with no flags and no arguments combines the default stdin
mode with the default hex mode.  This default mode is special; certain non-hex
characters may be input to represent commands:
  . (period)    Calls Dumper.Status to get the current decoding status.
  ; (semicolon) Calls Dumper.Flush to flush output and start a new message.

This lets you cut-and-paste hex strings into your terminal, and use the commands
to trigger status or flush calls; i.e. a rudimentary debugging UI.

See v.io/v23/vom.Dumper for details on the dump output.
`,
	ArgsName: "[data]",
	ArgsLong: "[data] is the data to dump; if not specified, reads from stdin",
}

var (
	flagDataRep = dataRepHex
)

func init() {
	cmdDecode.Flags.Var(&flagDataRep, "data",
		"Data representation, one of "+fmt.Sprint(dataRepAll))
	cmdDump.Flags.Var(&flagDataRep, "data",
		"Data representation, one of "+fmt.Sprint(dataRepAll))
}

func runDecode(env *cmdline.Env, args []string) error {
	// Convert all inputs into a reader over binary bytes.
	var data string
	switch {
	case len(args) > 1:
		return env.UsageErrorf("too many args")
	case len(args) == 1:
		data = args[0]
	default:
		bytes, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			return err
		}
		data = string(bytes)
	}
	binbytes, err := dataToBinaryBytes(data)
	if err != nil {
		return err
	}
	reader := bytes.NewBuffer(binbytes)
	// Decode the binary bytes.
	// TODO(toddw): Add a flag to set a specific type to decode into.
	decoder := vom.NewDecoder(reader)
	var result *vdl.Value
	if err := decoder.Decode(&result); err != nil {
		return err
	}
	fmt.Fprintln(env.Stdout, result)
	if reader.Len() != 0 {
		return fmt.Errorf("%d leftover bytes: % x", reader.Len(), reader.String())
	}
	return nil
}

func runDump(env *cmdline.Env, args []string) error {
	// Handle non-streaming cases.
	switch {
	case len(args) > 1:
		return env.UsageErrorf("too many args")
	case len(args) == 1:
		binbytes, err := dataToBinaryBytes(args[0])
		if err != nil {
			return err
		}
		fmt.Fprintln(env.Stdout, vom.Dump(binbytes))
		return nil
	}
	// Handle streaming from stdin.
	// TODO(toddw): Add a flag to configure stdout/stderr dumping.
	dumper := vom.NewDumper(dumpWriter{env.Stdout, env.Stdout})
	defer dumper.Close()
	// Handle simple non-hex cases.
	switch flagDataRep {
	case dataRepBinary:
		_, err := io.Copy(dumper, os.Stdin)
		return err
	}
	return runDumpHexStream(dumper)
}

// runDumpHexStream handles the hex stdin-streaming special-case, with commands
// for status and flush.  This is tricky because we need to strip whitespace,
// handle commands where they appear in the stream, and deal with the fact that
// it takes two hex characters to encode a single byte.
//
// The strategy is to run a ReadLoop that reads into a reasonably-sized buffer.
// Inside the ReadLoop we take the buffer, strip whitespace, and keep looping to
// process all data up to each command, and then process the command.  If a
// command appears in the middle of two hex characters representing a byte, we
// send the command first, before sending the byte.
//
// Any leftover non-command single byte is stored in buf and bufStart is set, so
// that the next iteration of ReadLoop can read after those bytes.
func runDumpHexStream(dumper *vom.Dumper) error {
	buf := make([]byte, 1024)
	bufStart := 0
ReadLoop:
	for {
		n, err := os.Stdin.Read(buf[bufStart:])
		switch {
		case n == 0 && err == io.EOF:
			return nil
		case n == 0 && err != nil:
			return err
		}
		// We may have hex interspersed with spaces and commands.  The strategy is
		// to strip all whitespace, and process each even-sized chunk of hex bytes
		// up to a command or the end of the buffer.
		//
		// Data that appears before a command is written before the command, and
		// data after the command is written after.  But if a command appears in the
		// middle of two hex characters representing a byte, we send the command
		// first, before sending the byte.
		hexbytes := bytes.Map(dropWhitespace, buf[:bufStart+n])
		for len(hexbytes) > 0 {
			end := len(hexbytes)
			cmdIndex := bytes.IndexAny(hexbytes, ".;")
			if cmdIndex != -1 {
				end = cmdIndex
			} else if end == 1 {
				// We have a single non-command byte left in hexbytes; copy it into buf
				// and set bufStart.
				copy(buf, hexbytes[0:1])
				bufStart = 1
				continue ReadLoop
			}
			if end%2 == 1 {
				end -= 1 // Ensure the end is on an even boundary.
			}
			// Write this even-sized chunk of hex bytes to the dumper.
			binbytes, err := hex.DecodeString(string(hexbytes[:end]))
			if err != nil {
				return err
			}
			if _, err := dumper.Write(binbytes); err != nil {
				return err
			}
			// Handle commands.
			if cmdIndex != -1 {
				switch cmd := hexbytes[cmdIndex]; cmd {
				case '.':
					dumper.Status()
				case ';':
					dumper.Flush()
				default:
					return fmt.Errorf("unhandled command %q", cmd)
				}
				// Move data after the command forward.
				copy(hexbytes[cmdIndex:], hexbytes[cmdIndex+1:])
				hexbytes = hexbytes[:len(hexbytes)-1]
			}
			// Move data after the end forward.
			copy(hexbytes, hexbytes[end:])
			hexbytes = hexbytes[:len(hexbytes)-end]
		}
		bufStart = 0
	}
}

func dataToBinaryBytes(data string) ([]byte, error) {
	// Transform all data representations to binary.
	switch flagDataRep {
	case dataRepHex:
		// Remove all whitespace out of the hex string.
		binbytes, err := hex.DecodeString(strings.Map(dropWhitespace, data))
		if err != nil {
			return nil, err
		}
		return binbytes, nil
	}
	return []byte(data), nil
}

func dropWhitespace(r rune) rune {
	if unicode.IsSpace(r) {
		return -1
	}
	return r
}

type dumpWriter struct {
	atom, status io.Writer
}

var _ vom.DumpWriter = dumpWriter{}

func (w dumpWriter) WriteAtom(atom vom.DumpAtom) {
	w.atom.Write([]byte(atom.String() + "\n"))
}

func (w dumpWriter) WriteStatus(status vom.DumpStatus) {
	w.status.Write([]byte(status.String() + "\n"))
}
<commit_msg>ref: Fix bug in vom zero-struct-field encoding.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// The following enables go generate to generate the doc.go file.
//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .

package main

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"unicode"

	"v.io/v23/vdl"
	"v.io/v23/vom"
	"v.io/x/lib/cmdline"
)

func main() {
	cmdline.Main(cmdVom)
}

var cmdVom = &cmdline.Command{
	Name:  "vom",
	Short: "helps debug the Vanadium Object Marshaling wire protocol",
	Long: `
Command vom helps debug the Vanadium Object Marshaling wire protocol.
`,
	Children: []*cmdline.Command{cmdDecode, cmdDump},
}

var cmdDecode = &cmdline.Command{
	Runner: cmdline.RunnerFunc(runDecode),
	Name:   "decode",
	Short:  "Decode data encoded in the vom format",
	Long: `
Decode decodes data encoded in the vom format.  If no arguments are provided,
decode reads the data from stdin, otherwise the argument is the data.

By default the data is assumed to be represented in hex, with all whitespace
anywhere in the data ignored.  Use the -data flag to specify other data
representations.

`,
	ArgsName: "[data]",
	ArgsLong: "[data] is the data to decode; if not specified, reads from stdin",
}

var cmdDump = &cmdline.Command{
	Runner: cmdline.RunnerFunc(runDump),
	Name:   "dump",
	Short:  "Dump data encoded in the vom format into formatted output",
	Long: `
Dump dumps data encoded in the vom format, generating formatted output
describing each portion of the encoding.  If no arguments are provided, dump
reads the data from stdin, otherwise the argument is the data.

By default the data is assumed to be represented in hex, with all whitespace
anywhere in the data ignored.  Use the -data flag to specify other data
representations.

Calling "vom dump" with no flags and no arguments combines the default stdin
mode with the default hex mode.  This default mode is special; certain non-hex
characters may be input to represent commands:
  . (period)    Calls Dumper.Status to get the current decoding status.
  ; (semicolon) Calls Dumper.Flush to flush output and start a new message.

This lets you cut-and-paste hex strings into your terminal, and use the commands
to trigger status or flush calls; i.e. a rudimentary debugging UI.

See v.io/v23/vom.Dumper for details on the dump output.
`,
	ArgsName: "[data]",
	ArgsLong: "[data] is the data to dump; if not specified, reads from stdin",
}

var (
	flagDataRep = dataRepHex
)

func init() {
	cmdDecode.Flags.Var(&flagDataRep, "data",
		"Data representation, one of "+fmt.Sprint(dataRepAll))
	cmdDump.Flags.Var(&flagDataRep, "data",
		"Data representation, one of "+fmt.Sprint(dataRepAll))
}

func runDecode(env *cmdline.Env, args []string) error {
	// Convert all inputs into a reader over binary bytes.
	var data string
	switch {
	case len(args) > 1:
		return env.UsageErrorf("too many args")
	case len(args) == 1:
		data = args[0]
	default:
		bytes, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			return err
		}
		data = string(bytes)
	}
	binbytes, err := dataToBinaryBytes(data)
	if err != nil {
		return err
	}
	reader := bytes.NewBuffer(binbytes)
	// Decode the binary bytes.
	// TODO(toddw): Add a flag to set a specific type to decode into.
	decoder := vom.NewDecoder(reader)
	var result *vdl.Value
	if err := decoder.Decode(&result); err != nil {
		return err
	}
	fmt.Fprintln(env.Stdout, result)
	if reader.Len() != 0 {
		return fmt.Errorf("%d leftover bytes: % x", reader.Len(), reader.String())
	}
	return nil
}

func runDump(env *cmdline.Env, args []string) error {
	// Handle non-streaming cases.
	switch {
	case len(args) > 1:
		return env.UsageErrorf("too many args")
	case len(args) == 1:
		binbytes, err := dataToBinaryBytes(args[0])
		if err != nil {
			return err
		}
		fmt.Fprintln(env.Stdout, vom.Dump(binbytes))
		return nil
	}
	// Handle streaming from stdin.
	dumper := vom.NewDumper(vom.NewDumpWriter(env.Stdout))
	defer dumper.Close()
	// Handle simple non-hex cases.
	switch flagDataRep {
	case dataRepBinary:
		_, err := io.Copy(dumper, os.Stdin)
		return err
	}
	return runDumpHexStream(dumper)
}

// runDumpHexStream handles the hex stdin-streaming special-case, with commands
// for status and flush.  This is tricky because we need to strip whitespace,
// handle commands where they appear in the stream, and deal with the fact that
// it takes two hex characters to encode a single byte.
//
// The strategy is to run a ReadLoop that reads into a reasonably-sized buffer.
// Inside the ReadLoop we take the buffer, strip whitespace, and keep looping to
// process all data up to each command, and then process the command.  If a
// command appears in the middle of two hex characters representing a byte, we
// send the command first, before sending the byte.
//
// Any leftover non-command single byte is stored in buf and bufStart is set, so
// that the next iteration of ReadLoop can read after those bytes.
func runDumpHexStream(dumper *vom.Dumper) error {
	buf := make([]byte, 1024)
	bufStart := 0
ReadLoop:
	for {
		n, err := os.Stdin.Read(buf[bufStart:])
		switch {
		case n == 0 && err == io.EOF:
			return nil
		case n == 0 && err != nil:
			return err
		}
		// We may have hex interspersed with spaces and commands.  The strategy is
		// to strip all whitespace, and process each even-sized chunk of hex bytes
		// up to a command or the end of the buffer.
		//
		// Data that appears before a command is written before the command, and
		// data after the command is written after.  But if a command appears in the
		// middle of two hex characters representing a byte, we send the command
		// first, before sending the byte.
		hexbytes := bytes.Map(dropWhitespace, buf[:bufStart+n])
		for len(hexbytes) > 0 {
			end := len(hexbytes)
			cmdIndex := bytes.IndexAny(hexbytes, ".;")
			if cmdIndex != -1 {
				end = cmdIndex
			} else if end == 1 {
				// We have a single non-command byte left in hexbytes; copy it into buf
				// and set bufStart.
				copy(buf, hexbytes[0:1])
				bufStart = 1
				continue ReadLoop
			}
			if end%2 == 1 {
				end -= 1 // Ensure the end is on an even boundary.
			}
			// Write this even-sized chunk of hex bytes to the dumper.
			binbytes, err := hex.DecodeString(string(hexbytes[:end]))
			if err != nil {
				return err
			}
			if _, err := dumper.Write(binbytes); err != nil {
				return err
			}
			// Handle commands.
			if cmdIndex != -1 {
				switch cmd := hexbytes[cmdIndex]; cmd {
				case '.':
					dumper.Status()
				case ';':
					dumper.Flush()
				default:
					return fmt.Errorf("unhandled command %q", cmd)
				}
				// Move data after the command forward.
				copy(hexbytes[cmdIndex:], hexbytes[cmdIndex+1:])
				hexbytes = hexbytes[:len(hexbytes)-1]
			}
			// Move data after the end forward.
			copy(hexbytes, hexbytes[end:])
			hexbytes = hexbytes[:len(hexbytes)-end]
		}
		bufStart = 0
	}
}

func dataToBinaryBytes(data string) ([]byte, error) {
	// Transform all data representations to binary.
	switch flagDataRep {
	case dataRepHex:
		// Remove all whitespace out of the hex string.
		binbytes, err := hex.DecodeString(strings.Map(dropWhitespace, data))
		if err != nil {
			return nil, err
		}
		return binbytes, nil
	}
	return []byte(data), nil
}

func dropWhitespace(r rune) rune {
	if unicode.IsSpace(r) {
		return -1
	}
	return r
}
<|endoftext|>


<commit_before>package gen

import (
	"fmt"
	"path"
	"strconv"
	"strings"
	"text/template"
)

var (
	defaultFuncs = template.FuncMap{
		"greaterThanEqualF": func(b, a float64) bool {
			return b >= a
		},
		"lessThanEqualF": func(b, a float64) bool {
			return b <= a
		},
		"greaterThanEqual": func(b, a int) bool {
			return b >= a
		},
		"capitalize": func(b string) string {
			if len(b) == 0 {
				return b
			}

			return strings.ToUpper(b[:1]) + b[1:]
		},
		"notempty": func(b string) bool {
			return strings.TrimSpace(b) != ""
		},
		"empty": func(b string) bool {
			return strings.TrimSpace(b) == ""
		},
		"title": func(b string) string {
			return strings.ToTitle(b)
		},
		"trim": func(b, suff string) string {
			return strings.Trim(b, suff)
		},
		"trimSuffix": func(b, suff string) string {
			return strings.TrimSuffix(b, suff)
		},
		"trimPrefix": func(b, pre string) string {
			return strings.TrimPrefix(b, pre)
		},
		"hasSuffix": func(b, suff string) bool {
			return strings.HasSuffix(b, suff)
		},
		"hasPrefix": func(b, pre string) bool {
			return strings.HasPrefix(b, pre)
		},
		"lower": func(b string) string {
			return strings.ToLower(b)
		},
		"upper": func(b string) string {
			return strings.ToUpper(b)
		},
		"joinPath": func(b ...string) string {
			return path.Join(b...)
		},
		"basePathName": func(b string) string {
			return path.Base(b)
		},
		"join": func(vals []string, jn string) string {
			return strings.Join(vals, jn)
		},
		"joinInterface": func(vals []interface{}, jn string) string {
			var items []string
			for _, val := range vals {
				items = append(items, fmt.Sprintf("%+s", val))
			}
			return strings.Join(items, jn)
		},
		"joinSlice": func(vals []string, jn string) string {
			return strings.Join(vals, jn)
		},
		"joinVariadic": func(jn string, vals ...string) string {
			return strings.Join(vals, jn)
		},
		"splitAfter": func(b string, sp string, n int) []string {
			return strings.SplitAfterN(b, sp, n)
		},
		"split": func(b string, sp string) []string {
			return strings.Split(b, sp)
		},
		"indent": func(b string) string {
			return strings.Join(strings.Split(b, "\n"), "\n\t")
		},
		"lessThanEqual": func(b, a int) bool {
			return b <= a
		},
		"greaterThanF": func(b, a float64) bool {
			return b > a
		},
		"lessThanF": func(b, a float64) bool {
			return b < a
		},
		"greaterThan": func(b, a int) bool {
			return b > a
		},
		"lessThan": func(b, a int) bool {
			return b < a
		},
		"trimspace": func(b string) string {
			return strings.TrimSpace(b)
		},
		"equal": func(b, a interface{}) bool {
			return b == a
		},
		"not": func(b bool) bool {
			return !!b
		},
		"notequal": func(b, a interface{}) bool {
			return b != a
		},
		"quote": quote,
		"prefixInt": func(prefix string, b int) string {
			return fmt.Sprintf("%s%d", prefix, b)
		},
		"stub": func(count int) string {
			var vals []string

			for i := count; i > 0; i-- {
				vals = append(vals, "_")
			}

			return strings.Join(vals, ",")
		},
		"subs": func(word string, b int) string {
			return word[:b]
		},
		"add": func(a, b int) int {
			return a + b
		},
		"multiply": func(a, b int) int {
			return a * b
		},
		"subtract": func(a, b int) int {
			return a - b
		},
		"divide": func(a, b int) int {
			return a / b
		},
		"lenNotEqual": func(b interface{}, target int) bool {
			return lenOff(b) != target
		},
		"lenEqual": func(b interface{}, target int) bool {
			return lenOff(b) == target
		},
		"lenOf": lenOff,
		"percentage": func(a, b float64) float64 {
			return (a / b) * 100
		},
	}
)

func quote(b interface{}) string {
	switch bo := b.(type) {
	case string:
		return strconv.Quote(bo)
	case int:
		return strconv.Quote(strconv.Itoa(bo))
	case bool:
		return strconv.Quote(strconv.FormatBool(bo))
	case int64:
		return strconv.Quote(strconv.Itoa(int(bo)))
	case float32:
		mo := strconv.FormatFloat(float64(bo), 'f', 4, 32)
		return strconv.Quote(mo)
	case float64:
		mo := strconv.FormatFloat(bo, 'f', 4, 32)
		return strconv.Quote(mo)
	case byte:
		return strconv.QuoteRune(rune(bo))
	case rune:
		return strconv.QuoteRune(bo)
	default:
		return "Unconvertible Type"
	}
}

func lenOff(b interface{}) int {
	switch bo := b.(type) {
	case []string:
		return len(bo)
	case string:
		return len(bo)
	case []int:
		return len(bo)
	case []bool:
		return len(bo)
	case []int64:
		return len(bo)
	case []float32:
		return len(bo)
	case []float64:
		return len(bo)
	case []byte:
		return len(bo)
	default:
		return 0
	}
}

// ToTemplateFuncs returns a template.FuncMap which is a union of all key and values
// from the provided map. It does not check for function type and will override any previos
// key values.
func ToTemplateFuncs(funcs ...map[string]interface{}) template.FuncMap {
	tfuncs := make(map[string]interface{})

	for _, item := range funcs {
		for k, v := range item {
			tfuncs[k] = v
		}
	}

	return template.FuncMap(tfuncs)
}

// ToTemplate returns a template instance with the giving templ string and functions.
func ToTemplate(name string, templ string, mx template.FuncMap) (*template.Template, error) {
	tml, err := template.New(name).Funcs(defaultFuncs).Funcs(mx).Parse(templ)
	if err != nil {
		return nil, err
	}

	return tml, nil
}
<commit_msg>Add method nthOf<commit_after>package gen

import (
	"fmt"
	"path"
	"strconv"
	"strings"
	"text/template"
)

var (
	defaultFuncs = template.FuncMap{
		"greaterThanEqualF": func(b, a float64) bool {
			return b >= a
		},
		"lessThanEqualF": func(b, a float64) bool {
			return b <= a
		},
		"greaterThanEqual": func(b, a int) bool {
			return b >= a
		},
		"capitalize": func(b string) string {
			if len(b) == 0 {
				return b
			}

			return strings.ToUpper(b[:1]) + b[1:]
		},
		"notempty": func(b string) bool {
			return strings.TrimSpace(b) != ""
		},
		"empty": func(b string) bool {
			return strings.TrimSpace(b) == ""
		},
		"title": func(b string) string {
			return strings.ToTitle(b)
		},
		"trim": func(b, suff string) string {
			return strings.Trim(b, suff)
		},
		"trimSuffix": func(b, suff string) string {
			return strings.TrimSuffix(b, suff)
		},
		"trimPrefix": func(b, pre string) string {
			return strings.TrimPrefix(b, pre)
		},
		"hasSuffix": func(b, suff string) bool {
			return strings.HasSuffix(b, suff)
		},
		"hasPrefix": func(b, pre string) bool {
			return strings.HasPrefix(b, pre)
		},
		"lower": func(b string) string {
			return strings.ToLower(b)
		},
		"upper": func(b string) string {
			return strings.ToUpper(b)
		},
		"joinPath": func(b ...string) string {
			return path.Join(b...)
		},
		"basePathName": func(b string) string {
			return path.Base(b)
		},
		"join": func(vals []string, jn string) string {
			return strings.Join(vals, jn)
		},
		"joinInterface": func(vals []interface{}, jn string) string {
			var items []string
			for _, val := range vals {
				items = append(items, fmt.Sprintf("%+s", val))
			}
			return strings.Join(items, jn)
		},
		"joinSlice": func(vals []string, jn string) string {
			return strings.Join(vals, jn)
		},
		"joinVariadic": func(jn string, vals ...string) string {
			return strings.Join(vals, jn)
		},
		"splitAfter": func(b string, sp string, n int) []string {
			return strings.SplitAfterN(b, sp, n)
		},
		"split": func(b string, sp string) []string {
			return strings.Split(b, sp)
		},
		"indent": func(b string) string {
			return strings.Join(strings.Split(b, "\n"), "\n\t")
		},
		"lessThanEqual": func(b, a int) bool {
			return b <= a
		},
		"greaterThanF": func(b, a float64) bool {
			return b > a
		},
		"lessThanF": func(b, a float64) bool {
			return b < a
		},
		"greaterThan": func(b, a int) bool {
			return b > a
		},
		"lessThan": func(b, a int) bool {
			return b < a
		},
		"trimspace": func(b string) string {
			return strings.TrimSpace(b)
		},
		"equal": func(b, a interface{}) bool {
			return b == a
		},
		"not": func(b bool) bool {
			return !!b
		},
		"notequal": func(b, a interface{}) bool {
			return b != a
		},
		"quote": quote,
		"prefixInt": func(prefix string, b int) string {
			return fmt.Sprintf("%s%d", prefix, b)
		},
		"stub": func(count int) string {
			var vals []string

			for i := count; i > 0; i-- {
				vals = append(vals, "_")
			}

			return strings.Join(vals, ",")
		},
		"subs": func(word string, b int) string {
			return word[:b]
		},
		"add": func(a, b int) int {
			return a + b
		},
		"multiply": func(a, b int) int {
			return a * b
		},
		"subtract": func(a, b int) int {
			return a - b
		},
		"divide": func(a, b int) int {
			return a / b
		},
		"lenNotEqual": func(b interface{}, target int) bool {
			return lenOff(b) != target
		},
		"lenEqual": func(b interface{}, target int) bool {
			return lenOff(b) == target
		},
		"percentage": func(a, b float64) float64 {
			return (a / b) * 100
		},
		"lenOf": lenOff,
		"nthOf": nthOf,
	}
)

func quote(b interface{}) string {
	switch bo := b.(type) {
	case string:
		return strconv.Quote(bo)
	case int:
		return strconv.Quote(strconv.Itoa(bo))
	case bool:
		return strconv.Quote(strconv.FormatBool(bo))
	case int64:
		return strconv.Quote(strconv.Itoa(int(bo)))
	case float32:
		mo := strconv.FormatFloat(float64(bo), 'f', 4, 32)
		return strconv.Quote(mo)
	case float64:
		mo := strconv.FormatFloat(bo, 'f', 4, 32)
		return strconv.Quote(mo)
	case byte:
		return strconv.QuoteRune(rune(bo))
	case rune:
		return strconv.QuoteRune(bo)
	default:
		return "Unconvertible Type"
	}
}

func nthOf(b interface{}, index int) (val interface{}) {
	switch bo := b.(type) {
	case []interface{}:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []string:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case string:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []int:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []bool:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []int64:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []float32:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []float64:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []byte:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	}

	return
}

func lenOff(b interface{}) int {
	switch bo := b.(type) {
	case []interface{}:
		return len(bo)
	case []string:
		return len(bo)
	case string:
		return len(bo)
	case []int:
		return len(bo)
	case []bool:
		return len(bo)
	case []int64:
		return len(bo)
	case []float32:
		return len(bo)
	case []float64:
		return len(bo)
	case []byte:
		return len(bo)
	default:
		return 0
	}
}

// ToTemplateFuncs returns a template.FuncMap which is a union of all key and values
// from the provided map. It does not check for function type and will override any previos
// key values.
func ToTemplateFuncs(funcs ...map[string]interface{}) template.FuncMap {
	tfuncs := make(map[string]interface{})

	for _, item := range funcs {
		for k, v := range item {
			tfuncs[k] = v
		}
	}

	return template.FuncMap(tfuncs)
}

// ToTemplate returns a template instance with the giving templ string and functions.
func ToTemplate(name string, templ string, mx template.FuncMap) (*template.Template, error) {
	tml, err := template.New(name).Funcs(defaultFuncs).Funcs(mx).Parse(templ)
	if err != nil {
		return nil, err
	}

	return tml, nil
}
<|endoftext|>


<commit_before>package gen

var save = &Template{
	Name: "Save",
	Text: `
func (m *{{.Name}}) IsNewRecord() bool {
        return ar.IsZero(m.{{.PrimaryKeyField}})
}

func (m *{{.Name}}) IsPersistent() bool {
        return !m.IsNewRecord()
}

func (m *{{.Name}}) Save(validate ...bool) (bool, *ar.Errors) {
	if len(validate) == 0 || len(validate) > 0 && validate[0] {
		if ok, errs := m.IsValid(); !ok {
			return false, errs
		}
	}
	errs := &ar.Errors{}
        if m.IsNewRecord() {
                ins := ar.NewInsert(db, logger).Table("{{.TableName}}").Params(map[string]interface{}{ {{range .FieldsWithoutPrimaryKey}}
			"{{.ColumnName}}": m.{{.Name}},{{end}}
                })

                if result, err := ins.Exec(); err != nil {
			errs.AddError("base", err)
                        return false, errs
                } else {
			if lastId, err := result.LastInsertId(); err == nil {
				m.Id = {{.PrimaryKeyType}}(lastId)
			}
		}
                return true, nil
        }else{
		upd := ar.NewUpdate(db, logger).Table("{{.TableName}}").Params(map[string]interface{}{ {{range .Fields}}
		"{{.ColumnName}}": m.{{.Name}},{{end}}
		}).Where("{{.PrimaryKeyColumn}}", m.{{.PrimaryKeyField}})

		if _, err := upd.Exec(); err != nil {
			errs.AddError("base", err)
			return false, errs
		}
                return true, nil
        }
}
`}
<commit_msg>[FIX] Can't build when name of primary key isn't "Id".<commit_after>package gen

var save = &Template{
	Name: "Save",
	Text: `
func (m *{{.Name}}) IsNewRecord() bool {
        return ar.IsZero(m.{{.PrimaryKeyField}})
}

func (m *{{.Name}}) IsPersistent() bool {
        return !m.IsNewRecord()
}

func (m *{{.Name}}) Save(validate ...bool) (bool, *ar.Errors) {
	if len(validate) == 0 || len(validate) > 0 && validate[0] {
		if ok, errs := m.IsValid(); !ok {
			return false, errs
		}
	}
	errs := &ar.Errors{}
        if m.IsNewRecord() {
                ins := ar.NewInsert(db, logger).Table("{{.TableName}}").Params(map[string]interface{}{ {{range .FieldsWithoutPrimaryKey}}
			"{{.ColumnName}}": m.{{.Name}},{{end}}
                })

                if result, err := ins.Exec(); err != nil {
			errs.AddError("base", err)
                        return false, errs
                } else {
			if lastId, err := result.LastInsertId(); err == nil {
				m.{{.PrimaryKeyField}} = {{.PrimaryKeyType}}(lastId)
			}
		}
                return true, nil
        }else{
		upd := ar.NewUpdate(db, logger).Table("{{.TableName}}").Params(map[string]interface{}{ {{range .Fields}}
		"{{.ColumnName}}": m.{{.Name}},{{end}}
		}).Where("{{.PrimaryKeyColumn}}", m.{{.PrimaryKeyField}})

		if _, err := upd.Exec(); err != nil {
			errs.AddError("base", err)
			return false, errs
		}
                return true, nil
        }
}
`}
<|endoftext|>


<commit_before>/*
 * This file is part of easyKV.
 * Based on code from confd.
 * https://github.com/kelseyhightower/confd/blob/5e4438f2e7c2e04fee7981548ccc775181377a8b/backends/zookeeper/client.go
 * Users who have contributed to this file
 * © 2013 Kelsey Hightower
 * © 2014 github.com/sheepkiller
 * © 2015 cinience@hotmail.com
 *
 * © 2016 The easyKV Authors
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

package zookeeper

import (
	"context"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/HeavyHorst/easykv"
	zk "github.com/tevino/go-zookeeper/zk"
)

// Client provides a wrapper around the zookeeper client
type Client struct {
	client *zk.Conn
}

// New returns an *zookeeper.Client with a connection to named machines.
// It returns an error if a connection to the cluster cannot be made.
func New(machines []string) (*Client, error) {
	c, _, err := zk.Connect(machines, time.Second)
	if err != nil {
		panic(err)
	}
	return &Client{c}, nil
}

// Close closes the zookeper client connection.
func (c *Client) Close() {
	if c.client != nil {
		c.client.Close()
	}
}

func nodeWalk(prefix string, c *Client, vars map[string]string) error {
	l, stat, err := c.client.Children(prefix)
	if err != nil {
		return err
	}

	if stat.NumChildren == 0 {
		b, _, err := c.client.Get(prefix)
		if err != nil {
			return err
		}
		vars[prefix] = string(b)

	} else {
		for _, key := range l {
			s := prefix + "/" + key
			_, stat, err := c.client.Exists(s)
			if err != nil {
				return err
			}
			if stat.NumChildren == 0 {
				b, _, err := c.client.Get(s)
				if err != nil {
					return err
				}
				vars[s] = string(b)
			} else {
				nodeWalk(s, c, vars)
			}
		}
	}
	return nil
}

// GetValues is used to lookup all keys with a prefix.
// Several prefixes can be specified in the keys array.
func (c *Client) GetValues(keys []string) (map[string]string, error) {
	vars := make(map[string]string)
	for _, v := range keys {
		v = strings.Replace(v, "/*", "", -1)
		_, _, err := c.client.Exists(v)
		if err != nil {
			return vars, err
		}
		if v == "/" {
			v = ""
		}
		err = nodeWalk(v, c, vars)
		if err != nil {
			return vars, err
		}
	}
	return vars, nil
}

type watchResponse struct {
	waitIndex uint64
	err       error
}

func (c *Client) watch(ctx context.Context, key string, respChan chan watchResponse) {
	_, _, keyWatcher, err := c.client.GetW(key)
	if err != nil {
		respChan <- watchResponse{0, err}
	}
	_, _, childWatcher, err := c.client.ChildrenW(key)
	if err != nil {
		respChan <- watchResponse{0, err}
	}

	for {
		select {
		case e := <-keyWatcher.EvtCh:
			if e.Type == zk.EventNodeDataChanged {
				respChan <- watchResponse{1, e.Err}
			}
		case e := <-childWatcher.EvtCh:
			if e.Type == zk.EventNodeChildrenChanged {
				respChan <- watchResponse{1, e.Err}
			}
		case <-ctx.Done():
			c.client.RemoveWatcher(childWatcher)
			c.client.RemoveWatcher(keyWatcher)
			return
		}
	}
}

// WatchPrefix watches a specific prefix for changes.
func (c *Client) WatchPrefix(ctx context.Context, prefix string, opts ...easykv.WatchOption) (uint64, error) {
	var options easykv.WatchOptions
	for _, o := range opts {
		o(&options)
	}

	// List the childrens first
	entries, err := c.GetValues([]string{prefix})
	if err != nil {
		return 0, err
	}

	respChan := make(chan watchResponse)
	wg := sync.WaitGroup{}
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	//watch all subfolders for changes
	watchMap := make(map[string]string)
	for k := range entries {
		for _, v := range options.Keys {
			if strings.HasPrefix(k, v) {
				for dir := filepath.Dir(k); dir != "/"; dir = filepath.Dir(dir) {
					if _, ok := watchMap[dir]; !ok {
						watchMap[dir] = ""
						wg.Add(1)
						go func(dir string) {
							defer wg.Done()
							c.watch(ctx, dir, respChan)
						}(dir)
					}
				}
				break
			}
		}
	}

	//watch all keys in prefix for changes
	for k := range entries {
		for _, v := range options.Keys {
			if strings.HasPrefix(k, v) {
				wg.Add(1)
				go func(k string) {
					defer wg.Done()
					c.watch(ctx, k, respChan)
				}(k)
				break
			}
		}
	}

	for {
		select {
		case <-ctx.Done():
			wg.Wait()
			return options.WaitIndex, nil
		case r := <-respChan:
			cancel()
			go func() {
				for range respChan {
				}
			}()
			wg.Wait()
			close(respChan)
			return r.waitIndex, r.err
		}
	}
}
<commit_msg>zookeeper: use map[string]struct{} instead of map[string]string - the value is not needed<commit_after>/*
 * This file is part of easyKV.
 * Based on code from confd.
 * https://github.com/kelseyhightower/confd/blob/5e4438f2e7c2e04fee7981548ccc775181377a8b/backends/zookeeper/client.go
 * Users who have contributed to this file
 * © 2013 Kelsey Hightower
 * © 2014 github.com/sheepkiller
 * © 2015 cinience@hotmail.com
 *
 * © 2016 The easyKV Authors
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

package zookeeper

import (
	"context"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/HeavyHorst/easykv"
	zk "github.com/tevino/go-zookeeper/zk"
)

// Client provides a wrapper around the zookeeper client
type Client struct {
	client *zk.Conn
}

// New returns an *zookeeper.Client with a connection to named machines.
// It returns an error if a connection to the cluster cannot be made.
func New(machines []string) (*Client, error) {
	c, _, err := zk.Connect(machines, time.Second)
	if err != nil {
		panic(err)
	}
	return &Client{c}, nil
}

// Close closes the zookeper client connection.
func (c *Client) Close() {
	if c.client != nil {
		c.client.Close()
	}
}

func nodeWalk(prefix string, c *Client, vars map[string]string) error {
	l, stat, err := c.client.Children(prefix)
	if err != nil {
		return err
	}

	if stat.NumChildren == 0 {
		b, _, err := c.client.Get(prefix)
		if err != nil {
			return err
		}
		vars[prefix] = string(b)

	} else {
		for _, key := range l {
			s := prefix + "/" + key
			_, stat, err := c.client.Exists(s)
			if err != nil {
				return err
			}
			if stat.NumChildren == 0 {
				b, _, err := c.client.Get(s)
				if err != nil {
					return err
				}
				vars[s] = string(b)
			} else {
				nodeWalk(s, c, vars)
			}
		}
	}
	return nil
}

// GetValues is used to lookup all keys with a prefix.
// Several prefixes can be specified in the keys array.
func (c *Client) GetValues(keys []string) (map[string]string, error) {
	vars := make(map[string]string)
	for _, v := range keys {
		v = strings.Replace(v, "/*", "", -1)
		_, _, err := c.client.Exists(v)
		if err != nil {
			return vars, err
		}
		if v == "/" {
			v = ""
		}
		err = nodeWalk(v, c, vars)
		if err != nil {
			return vars, err
		}
	}
	return vars, nil
}

type watchResponse struct {
	waitIndex uint64
	err       error
}

func (c *Client) watch(ctx context.Context, key string, respChan chan watchResponse) {
	_, _, keyWatcher, err := c.client.GetW(key)
	if err != nil {
		respChan <- watchResponse{0, err}
	}
	_, _, childWatcher, err := c.client.ChildrenW(key)
	if err != nil {
		respChan <- watchResponse{0, err}
	}

	for {
		select {
		case e := <-keyWatcher.EvtCh:
			if e.Type == zk.EventNodeDataChanged {
				respChan <- watchResponse{1, e.Err}
			}
		case e := <-childWatcher.EvtCh:
			if e.Type == zk.EventNodeChildrenChanged {
				respChan <- watchResponse{1, e.Err}
			}
		case <-ctx.Done():
			c.client.RemoveWatcher(childWatcher)
			c.client.RemoveWatcher(keyWatcher)
			return
		}
	}
}

// WatchPrefix watches a specific prefix for changes.
func (c *Client) WatchPrefix(ctx context.Context, prefix string, opts ...easykv.WatchOption) (uint64, error) {
	var options easykv.WatchOptions
	for _, o := range opts {
		o(&options)
	}

	// List the childrens first
	entries, err := c.GetValues([]string{prefix})
	if err != nil {
		return 0, err
	}

	respChan := make(chan watchResponse)
	wg := sync.WaitGroup{}
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	//watch all subfolders for changes
	watchMap := make(map[string]struct{})
	for k := range entries {
		for _, v := range options.Keys {
			if strings.HasPrefix(k, v) {
				for dir := filepath.Dir(k); dir != "/"; dir = filepath.Dir(dir) {
					if _, ok := watchMap[dir]; !ok {
						watchMap[dir] = struct{}{}
						wg.Add(1)
						go func(dir string) {
							defer wg.Done()
							c.watch(ctx, dir, respChan)
						}(dir)
					}
				}
				break
			}
		}
	}

	//watch all keys in prefix for changes
	for k := range entries {
		for _, v := range options.Keys {
			if strings.HasPrefix(k, v) {
				wg.Add(1)
				go func(k string) {
					defer wg.Done()
					c.watch(ctx, k, respChan)
				}(k)
				break
			}
		}
	}

	for {
		select {
		case <-ctx.Done():
			wg.Wait()
			return options.WaitIndex, nil
		case r := <-respChan:
			cancel()
			go func() {
				for range respChan {
				}
			}()
			wg.Wait()
			close(respChan)
			return r.waitIndex, r.err
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Knative Authors.

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

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

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

package v1alpha1

import (
	"testing"

	"github.com/google/go-cmp/cmp"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func Test_Invalid_BuildGCSResource(t *testing.T) {
	testcases := []struct {
		name             string
		pipelineResource *PipelineResource
	}{{
		name: "no location params",
		pipelineResource: &PipelineResource{
			ObjectMeta: metav1.ObjectMeta{
				Name: "buildgcs-resource-with-no-location-param",
			},
			Spec: PipelineResourceSpec{
				Type: PipelineResourceTypeStorage,
				Params: []Param{{
					Name:  "NotLocation",
					Value: "doesntmatter",
				}, {
					Name:  "type",
					Value: "build-gcs",
				}},
			},
		},
	}, {
		name: "location param with empty value",
		pipelineResource: &PipelineResource{
			ObjectMeta: metav1.ObjectMeta{
				Name: "gcs-resource-with-empty-location-param",
			},
			Spec: PipelineResourceSpec{
				Type: PipelineResourceTypeStorage,
				Params: []Param{{
					Name:  "Location",
					Value: "",
				}, {
					Name:  "type",
					Value: "build-gcs",
				}},
			},
		},
	}, {
		name: "no artifactType params",
		pipelineResource: &PipelineResource{
			ObjectMeta: metav1.ObjectMeta{
				Name: "buildgcs-resource-with-no-artifactType-param",
			},
			Spec: PipelineResourceSpec{
				Type: PipelineResourceTypeStorage,
				Params: []Param{{
					Name:  "Location",
					Value: "gs://test",
				}, {
					Name:  "type",
					Value: "build-gcs",
				}},
			},
		},
	}, {
		name: "artifactType param with empty value",
		pipelineResource: &PipelineResource{
			ObjectMeta: metav1.ObjectMeta{
				Name: "gcs-resource-with-empty-location-param",
			},
			Spec: PipelineResourceSpec{
				Type: PipelineResourceTypeStorage,
				Params: []Param{{
					Name:  "Location",
					Value: "gs://test",
				}, {
					Name:  "type",
					Value: "build-gcs",
				}, {
					Name:  "ArtifactType",
					Value: "",
				}},
			},
		},
	}, {
		name: "artifactType param with invalid value",
		pipelineResource: &PipelineResource{
			ObjectMeta: metav1.ObjectMeta{
				Name: "gcs-resource-with-empty-location-param",
			},
			Spec: PipelineResourceSpec{
				Type: PipelineResourceTypeStorage,
				Params: []Param{{
					Name:  "Location",
					Value: "gs://test",
				}, {
					Name:  "type",
					Value: "build-gcs",
				}, {
					Name:  "ArtifactType",
					Value: "invalid-type",
				}},
			},
		},
	}}
	for _, tc := range testcases {
		t.Run(tc.name, func(t *testing.T) {
			_, err := NewStorageResource(tc.pipelineResource)
			if err == nil {
				t.Error("Expected error creating BuildGCS resource")
			}
		})
	}
}

func Test_Valid_NewBuildGCSResource(t *testing.T) {
	pr := &PipelineResource{
		ObjectMeta: metav1.ObjectMeta{
			Name: "build-gcs-resource",
		},
		Spec: PipelineResourceSpec{
			Type: PipelineResourceTypeStorage,
			Params: []Param{{
				Name:  "Location",
				Value: "gs://fake-bucket",
			}, {
				Name:  "type",
				Value: "build-gcs",
			}, {
				Name:  "ArtifactType",
				Value: "Manifest",
			}, {
				Name:  "DestinationDir",
				Value: "/var/home",
			}},
			SecretParams: []SecretParam{{
				SecretKey:  "secretKey",
				SecretName: "secretName",
				FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
			}},
		},
	}
	expectedGCSResource := &BuildGCSResource{
		Name:           "build-gcs-resource",
		Location:       "gs://fake-bucket",
		Type:           PipelineResourceTypeStorage,
		DestinationDir: "/var/home",
		ArtifactType:   "Manifest",
		Secrets: []SecretParam{{
			SecretName: "secretName",
			SecretKey:  "secretKey",
			FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
		}},
	}

	r, err := NewBuildGCSResource(pr)
	if err != nil {
		t.Fatalf("Unexpected error creating BuildGCS resource: %s", err)
	}
	if d := cmp.Diff(expectedGCSResource, r); d != "" {
		t.Errorf("Mismatch of BuildGCS resource: %s", d)
	}
}

func Test_BuildGCSGetReplacements(t *testing.T) {
	r := &BuildGCSResource{
		Name:     "gcs-resource",
		Location: "gs://fake-bucket",
		Type:     PipelineResourceTypeBuildGCS,
	}
	expectedReplacementMap := map[string]string{
		"name":     "gcs-resource",
		"type":     "build-gcs",
		"location": "gs://fake-bucket",
	}
	if d := cmp.Diff(r.Replacements(), expectedReplacementMap); d != "" {
		t.Errorf("BuildGCS Replacement map mismatch: %s", d)
	}
}

func Test_BuildGCSGetDownloadContainerSpec(t *testing.T) {
	testcases := []struct {
		name           string
		resource       *BuildGCSResource
		wantContainers []corev1.Container
		wantErr        bool
	}{{
		name: "valid download protected buckets",
		resource: &BuildGCSResource{
			Name:           "gcs-valid",
			Location:       "gs://some-bucket",
			DestinationDir: "/workspace",
			ArtifactType:   "archive",
			Secrets: []SecretParam{{
				SecretName: "secretName",
				FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
				SecretKey:  "key.json",
			}},
		},
		wantContainers: []corev1.Container{
			CreateDirContainer("gcs-valid", "/workspace"), {
				Name:  "storage-fetch-gcs-valid",
				Image: "gcr.io/cloud-builders/gcs-fetcher:latest",
				Args: []string{"--type", "archive", "--location", "gs://some-bucket",
					"--dest_dir", "/workspace"},
				Env: []corev1.EnvVar{{
					Name:  "GOOGLE_APPLICATION_CREDENTIALS",
					Value: "/var/secret/secretName/key.json",
				}},
				VolumeMounts: []corev1.VolumeMount{{
					Name:      "volume-gcs-valid-secretName",
					MountPath: "/var/secret/secretName",
				}},
			}},
	}, {
		name: "duplicate secret mount paths",
		resource: &BuildGCSResource{
			Name:           "gcs-valid",
			Location:       "gs://some-bucket",
			DestinationDir: "/workspace",
			ArtifactType:   "archive",
			Secrets: []SecretParam{{
				SecretName: "secretName",
				FieldName:  "fieldName",
				SecretKey:  "key.json",
			}, {
				SecretKey:  "key.json",
				SecretName: "secretName",
				FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
			}},
		},
		wantContainers: []corev1.Container{
			CreateDirContainer("gcs-valid", "/workspace"), {
				Name:  "storage-fetch-gcs-valid",
				Image: "gcr.io/cloud-builders/gcs-fetcher:latest",
				Args: []string{"--type", "archive", "--location", "gs://some-bucket",
					"--dest_dir", "/workspace"},
				Env: []corev1.EnvVar{{
					Name:  "GOOGLE_APPLICATION_CREDENTIALS",
					Value: "/var/secret/secretName/key.json",
				}},
				VolumeMounts: []corev1.VolumeMount{{
					Name:      "volume-gcs-valid-secretName",
					MountPath: "/var/secret/secretName",
				}},
			}},
	}, {
		name: "invalid no destination directory set",
		resource: &BuildGCSResource{
			Name:         "gcs-invalid",
			Location:     "gs://some-bucket",
			ArtifactType: "archive",
		},
		wantErr: true,
	}}
	for _, tc := range testcases {
		t.Run(tc.name, func(t *testing.T) {
			gotContainers, err := tc.resource.GetDownloadContainerSpec()
			if tc.wantErr && err == nil {
				t.Fatalf("Expected error to be %t but got %v:", tc.wantErr, err)
			}
			if d := cmp.Diff(gotContainers, tc.wantContainers); d != "" {
				t.Errorf("Error mismatch between download containers spec: %s", d)
			}
		})
	}
}

func Test_BuildGCSGetUploadContainerSpec(t *testing.T) {
	testcases := []struct {
		name           string
		resource       *BuildGCSResource
		wantContainers []corev1.Container
		wantErr        bool
	}{{
		name: "valid upload to protected buckets with directory paths",
		resource: &BuildGCSResource{
			Name:           "gcs-valid",
			Location:       "gs://some-bucket/manifest.json",
			DestinationDir: "/workspace",
			ArtifactType:   "manifest",
			Secrets: []SecretParam{{
				SecretName: "secretName",
				FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
				SecretKey:  "key.json",
			}},
		},
		wantContainers: []corev1.Container{{
			Name:  "storage-upload-gcs-valid",
			Image: "gcr.io/cloud-builders/gcs-uploader:latest",
			Args:  []string{"--location", "gs://some-bucket/manifest.json", "--dir", "/workspace"},
			Env:   []corev1.EnvVar{{Name: "GOOGLE_APPLICATION_CREDENTIALS", Value: "/var/secret/secretName/key.json"}},
			VolumeMounts: []corev1.VolumeMount{{
				Name:      "volume-gcs-valid-secretName",
				MountPath: "/var/secret/secretName",
			}},
		}},
	}, {
		name: "duplicate secret mount paths",
		resource: &BuildGCSResource{
			Name:           "gcs-valid",
			Location:       "gs://some-bucket/manifest.json",
			DestinationDir: "/workspace",
			ArtifactType:   "manifest",
			Secrets: []SecretParam{{
				SecretName: "secretName",
				FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
				SecretKey:  "key.json",
			}, {
				SecretKey:  "key.json",
				SecretName: "secretName",
				FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
			}},
		},
		wantContainers: []corev1.Container{{
			Name:  "storage-upload-gcs-valid",
			Image: "gcr.io/cloud-builders/gcs-uploader:latest",
			Args:  []string{"--location", "gs://some-bucket/manifest.json", "--dir", "/workspace"},
			Env: []corev1.EnvVar{
				{Name: "GOOGLE_APPLICATION_CREDENTIALS", Value: "/var/secret/secretName/key.json"},
			},
			VolumeMounts: []corev1.VolumeMount{{
				Name:      "volume-gcs-valid-secretName",
				MountPath: "/var/secret/secretName",
			}},
		}},
	}, {
		name: "invalid upload to protected buckets with single file",
		resource: &BuildGCSResource{
			Name:           "gcs-valid",
			ArtifactType:   "archive",
			Location:       "gs://some-bucket",
			DestinationDir: "/workspace/results.tar",
		},
		wantErr: true,
	}, {
		name: "invalid upload with no source directory path",
		resource: &BuildGCSResource{
			Name:     "gcs-invalid",
			Location: "gs://some-bucket/manifest.json",
		},
		wantErr: true,
	}}
	for _, tc := range testcases {
		t.Run(tc.name, func(t *testing.T) {
			gotContainers, err := tc.resource.GetUploadContainerSpec()
			if tc.wantErr && err == nil {
				t.Fatalf("Expected error to be %t but got %v:", tc.wantErr, err)
			}

			if d := cmp.Diff(gotContainers, tc.wantContainers); d != "" {
				t.Errorf("Error mismatch between upload containers spec: %s", d)
			}
		})
	}
}
<commit_msg>Fix unit tests.<commit_after>/*
Copyright 2018 The Knative Authors.

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

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

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

package v1alpha1

import (
	"testing"

	"github.com/google/go-cmp/cmp"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func Test_Invalid_BuildGCSResource(t *testing.T) {
	testcases := []struct {
		name             string
		pipelineResource *PipelineResource
	}{{
		name: "no location params",
		pipelineResource: &PipelineResource{
			ObjectMeta: metav1.ObjectMeta{
				Name: "buildgcs-resource-with-no-location-param",
			},
			Spec: PipelineResourceSpec{
				Type: PipelineResourceTypeStorage,
				Params: []Param{{
					Name:  "NotLocation",
					Value: "doesntmatter",
				}, {
					Name:  "type",
					Value: "build-gcs",
				}},
			},
		},
	}, {
		name: "location param with empty value",
		pipelineResource: &PipelineResource{
			ObjectMeta: metav1.ObjectMeta{
				Name: "gcs-resource-with-empty-location-param",
			},
			Spec: PipelineResourceSpec{
				Type: PipelineResourceTypeStorage,
				Params: []Param{{
					Name:  "Location",
					Value: "",
				}, {
					Name:  "type",
					Value: "build-gcs",
				}},
			},
		},
	}, {
		name: "no artifactType params",
		pipelineResource: &PipelineResource{
			ObjectMeta: metav1.ObjectMeta{
				Name: "buildgcs-resource-with-no-artifactType-param",
			},
			Spec: PipelineResourceSpec{
				Type: PipelineResourceTypeStorage,
				Params: []Param{{
					Name:  "Location",
					Value: "gs://test",
				}, {
					Name:  "type",
					Value: "build-gcs",
				}},
			},
		},
	}, {
		name: "artifactType param with empty value",
		pipelineResource: &PipelineResource{
			ObjectMeta: metav1.ObjectMeta{
				Name: "gcs-resource-with-empty-location-param",
			},
			Spec: PipelineResourceSpec{
				Type: PipelineResourceTypeStorage,
				Params: []Param{{
					Name:  "Location",
					Value: "gs://test",
				}, {
					Name:  "type",
					Value: "build-gcs",
				}, {
					Name:  "ArtifactType",
					Value: "",
				}},
			},
		},
	}, {
		name: "artifactType param with invalid value",
		pipelineResource: &PipelineResource{
			ObjectMeta: metav1.ObjectMeta{
				Name: "gcs-resource-with-empty-location-param",
			},
			Spec: PipelineResourceSpec{
				Type: PipelineResourceTypeStorage,
				Params: []Param{{
					Name:  "Location",
					Value: "gs://test",
				}, {
					Name:  "type",
					Value: "build-gcs",
				}, {
					Name:  "ArtifactType",
					Value: "invalid-type",
				}},
			},
		},
	}}
	for _, tc := range testcases {
		t.Run(tc.name, func(t *testing.T) {
			_, err := NewStorageResource(tc.pipelineResource)
			if err == nil {
				t.Error("Expected error creating BuildGCS resource")
			}
		})
	}
}

func Test_Valid_NewBuildGCSResource(t *testing.T) {
	pr := &PipelineResource{
		ObjectMeta: metav1.ObjectMeta{
			Name: "build-gcs-resource",
		},
		Spec: PipelineResourceSpec{
			Type: PipelineResourceTypeStorage,
			Params: []Param{{
				Name:  "Location",
				Value: "gs://fake-bucket",
			}, {
				Name:  "type",
				Value: "build-gcs",
			}, {
				Name:  "ArtifactType",
				Value: "manifest",
			}, {
				Name:  "DestinationDir",
				Value: "/var/home",
			}},
			SecretParams: []SecretParam{{
				SecretKey:  "secretKey",
				SecretName: "secretName",
				FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
			}},
		},
	}
	expectedGCSResource := &BuildGCSResource{
		Name:           "build-gcs-resource",
		Location:       "gs://fake-bucket",
		Type:           PipelineResourceTypeStorage,
		DestinationDir: "/var/home",
		ArtifactType:   "manifest",
		Secrets: []SecretParam{{
			SecretName: "secretName",
			SecretKey:  "secretKey",
			FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
		}},
	}

	r, err := NewBuildGCSResource(pr)
	if err != nil {
		t.Fatalf("Unexpected error creating BuildGCS resource: %s", err)
	}
	if d := cmp.Diff(expectedGCSResource, r); d != "" {
		t.Errorf("Mismatch of BuildGCS resource: %s", d)
	}
}

func Test_BuildGCSGetReplacements(t *testing.T) {
	r := &BuildGCSResource{
		Name:     "gcs-resource",
		Location: "gs://fake-bucket",
		Type:     PipelineResourceTypeBuildGCS,
	}
	expectedReplacementMap := map[string]string{
		"name":     "gcs-resource",
		"type":     "build-gcs",
		"location": "gs://fake-bucket",
	}
	if d := cmp.Diff(r.Replacements(), expectedReplacementMap); d != "" {
		t.Errorf("BuildGCS Replacement map mismatch: %s", d)
	}
}

func Test_BuildGCSGetDownloadContainerSpec(t *testing.T) {
	testcases := []struct {
		name           string
		resource       *BuildGCSResource
		wantContainers []corev1.Container
		wantErr        bool
	}{{
		name: "valid download protected buckets",
		resource: &BuildGCSResource{
			Name:           "gcs-valid",
			Location:       "gs://some-bucket",
			DestinationDir: "/workspace",
			ArtifactType:   "archive",
			Secrets: []SecretParam{{
				SecretName: "secretName",
				FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
				SecretKey:  "key.json",
			}},
		},
		wantContainers: []corev1.Container{
			CreateDirContainer("gcs-valid", "/workspace"), {
				Name:  "storage-fetch-gcs-valid",
				Image: "gcr.io/cloud-builders/gcs-fetcher:latest",
				Args: []string{"--type", "archive", "--location", "gs://some-bucket",
					"--dest_dir", "/workspace"},
				Env: []corev1.EnvVar{{
					Name:  "GOOGLE_APPLICATION_CREDENTIALS",
					Value: "/var/secret/secretName/key.json",
				}},
				VolumeMounts: []corev1.VolumeMount{{
					Name:      "volume-gcs-valid-secretName",
					MountPath: "/var/secret/secretName",
				}},
			}},
	}, {
		name: "duplicate secret mount paths",
		resource: &BuildGCSResource{
			Name:           "gcs-valid",
			Location:       "gs://some-bucket",
			DestinationDir: "/workspace",
			ArtifactType:   "archive",
			Secrets: []SecretParam{{
				SecretName: "secretName",
				FieldName:  "fieldName",
				SecretKey:  "key.json",
			}, {
				SecretKey:  "key.json",
				SecretName: "secretName",
				FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
			}},
		},
		wantContainers: []corev1.Container{
			CreateDirContainer("gcs-valid", "/workspace"), {
				Name:  "storage-fetch-gcs-valid",
				Image: "gcr.io/cloud-builders/gcs-fetcher:latest",
				Args: []string{"--type", "archive", "--location", "gs://some-bucket",
					"--dest_dir", "/workspace"},
				Env: []corev1.EnvVar{{
					Name:  "GOOGLE_APPLICATION_CREDENTIALS",
					Value: "/var/secret/secretName/key.json",
				}},
				VolumeMounts: []corev1.VolumeMount{{
					Name:      "volume-gcs-valid-secretName",
					MountPath: "/var/secret/secretName",
				}},
			}},
	}, {
		name: "invalid no destination directory set",
		resource: &BuildGCSResource{
			Name:         "gcs-invalid",
			Location:     "gs://some-bucket",
			ArtifactType: "archive",
		},
		wantErr: true,
	}}
	for _, tc := range testcases {
		t.Run(tc.name, func(t *testing.T) {
			gotContainers, err := tc.resource.GetDownloadContainerSpec()
			if tc.wantErr && err == nil {
				t.Fatalf("Expected error to be %t but got %v:", tc.wantErr, err)
			}
			if d := cmp.Diff(gotContainers, tc.wantContainers); d != "" {
				t.Errorf("Error mismatch between download containers spec: %s", d)
			}
		})
	}
}

func Test_BuildGCSGetUploadContainerSpec(t *testing.T) {
	testcases := []struct {
		name           string
		resource       *BuildGCSResource
		wantContainers []corev1.Container
		wantErr        bool
	}{{
		name: "valid upload to protected buckets with directory paths",
		resource: &BuildGCSResource{
			Name:           "gcs-valid",
			Location:       "gs://some-bucket/manifest.json",
			DestinationDir: "/workspace",
			ArtifactType:   "manifest",
			Secrets: []SecretParam{{
				SecretName: "secretName",
				FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
				SecretKey:  "key.json",
			}},
		},
		wantContainers: []corev1.Container{{
			Name:  "storage-upload-gcs-valid",
			Image: "gcr.io/cloud-builders/gcs-uploader:latest",
			Args:  []string{"--location", "gs://some-bucket/manifest.json", "--dir", "/workspace"},
			Env:   []corev1.EnvVar{{Name: "GOOGLE_APPLICATION_CREDENTIALS", Value: "/var/secret/secretName/key.json"}},
			VolumeMounts: []corev1.VolumeMount{{
				Name:      "volume-gcs-valid-secretName",
				MountPath: "/var/secret/secretName",
			}},
		}},
	}, {
		name: "duplicate secret mount paths",
		resource: &BuildGCSResource{
			Name:           "gcs-valid",
			Location:       "gs://some-bucket/manifest.json",
			DestinationDir: "/workspace",
			ArtifactType:   "manifest",
			Secrets: []SecretParam{{
				SecretName: "secretName",
				FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
				SecretKey:  "key.json",
			}, {
				SecretKey:  "key.json",
				SecretName: "secretName",
				FieldName:  "GOOGLE_APPLICATION_CREDENTIALS",
			}},
		},
		wantContainers: []corev1.Container{{
			Name:  "storage-upload-gcs-valid",
			Image: "gcr.io/cloud-builders/gcs-uploader:latest",
			Args:  []string{"--location", "gs://some-bucket/manifest.json", "--dir", "/workspace"},
			Env: []corev1.EnvVar{
				{Name: "GOOGLE_APPLICATION_CREDENTIALS", Value: "/var/secret/secretName/key.json"},
			},
			VolumeMounts: []corev1.VolumeMount{{
				Name:      "volume-gcs-valid-secretName",
				MountPath: "/var/secret/secretName",
			}},
		}},
	}, {
		name: "invalid upload to protected buckets with single file",
		resource: &BuildGCSResource{
			Name:           "gcs-valid",
			ArtifactType:   "archive",
			Location:       "gs://some-bucket",
			DestinationDir: "/workspace/results.tar",
		},
		wantErr: true,
	}, {
		name: "invalid upload with no source directory path",
		resource: &BuildGCSResource{
			Name:     "gcs-invalid",
			Location: "gs://some-bucket/manifest.json",
		},
		wantErr: true,
	}}
	for _, tc := range testcases {
		t.Run(tc.name, func(t *testing.T) {
			gotContainers, err := tc.resource.GetUploadContainerSpec()
			if tc.wantErr && err == nil {
				t.Fatalf("Expected error to be %t but got %v:", tc.wantErr, err)
			}

			if d := cmp.Diff(gotContainers, tc.wantContainers); d != "" {
				t.Errorf("Error mismatch between upload containers spec: %s", d)
			}
		})
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/gamelift"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
	"log"
)

func resourceAwsGameliftSessionQueue() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsGameliftSessionQueueCreate,
		Read:   resourceAwsGameliftSessionQueueRead,
		Update: resourceAwsGameliftSessionQueueUpdate,
		Delete: resourceAwsGameliftSessionQueueDelete,

		Schema: map[string]*schema.Schema{
			"destinations": {
				Type:     schema.TypeList,
				Optional: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
			},
			"name": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validation.StringLenBetween(1, 1024),
			},
			"player_latency_policy": {
				Type:     schema.TypeList,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"maximum_individual_player_latency_milliseconds": {
							Type:         schema.TypeInt,
							Required:     true,
							ValidateFunc: validation.IntAtLeast(0),
						},
						"policy_duration_seconds": {
							Type:         schema.TypeInt,
							Optional:     true,
							ValidateFunc: validation.IntAtLeast(0),
						},
					},
				},
			},
			"timeout_in_seconds": {
				Type:     schema.TypeInt,
				Optional: true,
			},
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceAwsGameliftSessionQueueCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).gameliftconn

	input := gamelift.CreateGameSessionQueueInput{
		Name:                  aws.String(d.Get("name").(string)),
		Destinations:          expandGameliftGameSessionQueueDestinations(d.Get("destinations").([]interface{})),
		PlayerLatencyPolicies: expandGameliftGameSessionPlayerLatencyPolicies(d.Get("player_latency_policy").([]interface{})),
		TimeoutInSeconds:      aws.Int64(int64(d.Get("timeout_in_seconds").(int))),
	}
	if v, ok := d.GetOk("name"); ok {
		input.Name = aws.String(v.(string))
	}
	log.Printf("[INFO] Creating Gamelift Session Queue: %s", input)
	out, err := conn.CreateGameSessionQueue(&input)
	if err != nil {
		return err
	}

	d.SetId(*out.GameSessionQueue.GameSessionQueueArn)
	d.Set("name", out.GameSessionQueue.Name)

	return resourceAwsGameliftSessionQueueRead(d, meta)
}

func resourceAwsGameliftSessionQueueRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).gameliftconn
	log.Printf("[INFO] Describing Gamelift Session Queues: %s", d.Get("name"))
	limit := int64(1)
	out, err := conn.DescribeGameSessionQueues(&gamelift.DescribeGameSessionQueuesInput{
		Names: aws.StringSlice([]string{d.Get("name").(string)}),
		Limit: &limit,
	})
	if err != nil {
		if isAWSErr(err, gamelift.ErrCodeNotFoundException, "") {
			log.Printf("[WARN] Gamelift Session Queues (%s) not found, removing from state", d.Get("name"))
			d.SetId("")
			return nil
		}
		return err
	}
	sessionQueues := out.GameSessionQueues

	if len(sessionQueues) < 1 {
		log.Printf("[WARN] Gamelift Session Queue (%s) not found, removing from state", d.Get("name"))
		d.SetId("")
		return nil
	}
	if len(sessionQueues) != 1 {
		return fmt.Errorf("expected exactly 1 Gamelift Session Queues, found %d under %q",
			len(sessionQueues), d.Get("name"))
	}
	sessionQueue := sessionQueues[0]

	d.Set("arn", sessionQueue.GameSessionQueueArn)
	d.Set("name", sessionQueue.Name)
	d.Set("timeout_in_seconds", sessionQueue.TimeoutInSeconds)
	d.Set("destinations", sessionQueue.Destinations)
	if err := d.Set("player_latency_policy", flattenGameliftPlayerLatencyPolicies(sessionQueue.PlayerLatencyPolicies)); err != nil {
		return fmt.Errorf("error setting player_latency_policy: %s", err)
	}

	return nil
}

func flattenGameliftPlayerLatencyPolicies(playerLatencyPolicies []*gamelift.PlayerLatencyPolicy) []interface{} {
	lst := []interface{}{}
	for _, policy := range playerLatencyPolicies {
		m := map[string]interface{}{
			"maximum_individual_player_latency_milliseconds": aws.Int64Value(policy.MaximumIndividualPlayerLatencyMilliseconds),
			"policy_duration_seconds":                        aws.Int64Value(policy.PolicyDurationSeconds),
		}
		lst = append(lst, m)
	}
	return lst
}

func resourceAwsGameliftSessionQueueUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).gameliftconn

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

	log.Printf("[INFO] Updating Gamelift Session Queue: %s", name)

	input := gamelift.UpdateGameSessionQueueInput{
		Name:                  aws.String(d.Get("name").(string)),
		Destinations:          expandGameliftGameSessionQueueDestinations(d.Get("destinations").([]interface{})),
		PlayerLatencyPolicies: expandGameliftGameSessionPlayerLatencyPolicies(d.Get("player_latency_policy").([]interface{})),
		TimeoutInSeconds:      aws.Int64(int64(d.Get("timeout_in_seconds").(int))),
	}

	_, err := conn.UpdateGameSessionQueue(&input)
	if err != nil {
		return err
	}

	return resourceAwsGameliftSessionQueueRead(d, meta)
}

func resourceAwsGameliftSessionQueueDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).gameliftconn
	name := d.Get("name").(string)
	log.Printf("[INFO] Deleting Gamelift Session Queue: %s", name)
	_, err := conn.DeleteGameSessionQueue(&gamelift.DeleteGameSessionQueueInput{
		Name: aws.String(name),
	})
	if isAWSErr(err, gamelift.ErrCodeNotFoundException, "") {
		return nil
	}
	if err != nil {
		return fmt.Errorf("error deleting Gamelift Game Session Queue (%s): %s", d.Id(), err)
	}

	return nil
}

func expandGameliftGameSessionQueueDestinations(destinationsMap []interface{}) []*gamelift.GameSessionQueueDestination {
	if len(destinationsMap) < 1 {
		return nil
	}
	var destinations []*gamelift.GameSessionQueueDestination
	for _, destination := range destinationsMap {
		destinations = append(
			destinations,
			&gamelift.GameSessionQueueDestination{
				DestinationArn: aws.String(destination.(string)),
			})
	}
	return destinations
}

func expandGameliftGameSessionPlayerLatencyPolicies(destinationsPlayerLatencyPolicyMap []interface{}) []*gamelift.PlayerLatencyPolicy {
	if len(destinationsPlayerLatencyPolicyMap) < 1 {
		return nil
	}
	var playerLatencyPolicies []*gamelift.PlayerLatencyPolicy
	for _, playerLatencyPolicy := range destinationsPlayerLatencyPolicyMap {
		item := playerLatencyPolicy.(map[string]interface{})
		playerLatencyPolicies = append(
			playerLatencyPolicies,
			&gamelift.PlayerLatencyPolicy{
				MaximumIndividualPlayerLatencyMilliseconds: aws.Int64(int64(item["maximum_individual_player_latency_milliseconds"].(int))),
				PolicyDurationSeconds:                      aws.Int64(int64(item["policy_duration_seconds"].(int))),
			})
	}
	return playerLatencyPolicies
}
<commit_msg>aws_gamelift_session_queue.timeout_in_seconds validation<commit_after>package aws

import (
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/gamelift"
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
	"log"
)

func resourceAwsGameliftSessionQueue() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsGameliftSessionQueueCreate,
		Read:   resourceAwsGameliftSessionQueueRead,
		Update: resourceAwsGameliftSessionQueueUpdate,
		Delete: resourceAwsGameliftSessionQueueDelete,

		Schema: map[string]*schema.Schema{
			"destinations": {
				Type:     schema.TypeList,
				Optional: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
			},
			"name": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validation.StringLenBetween(1, 1024),
			},
			"player_latency_policy": {
				Type:     schema.TypeList,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"maximum_individual_player_latency_milliseconds": {
							Type:         schema.TypeInt,
							Required:     true,
							ValidateFunc: validation.IntAtLeast(0),
						},
						"policy_duration_seconds": {
							Type:         schema.TypeInt,
							Optional:     true,
							ValidateFunc: validation.IntAtLeast(0),
						},
					},
				},
			},
			"timeout_in_seconds": {
				Type:         schema.TypeInt,
				Optional:     true,
				ValidateFunc: validation.IntBetween(10, 600),
			},
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceAwsGameliftSessionQueueCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).gameliftconn

	input := gamelift.CreateGameSessionQueueInput{
		Name:                  aws.String(d.Get("name").(string)),
		Destinations:          expandGameliftGameSessionQueueDestinations(d.Get("destinations").([]interface{})),
		PlayerLatencyPolicies: expandGameliftGameSessionPlayerLatencyPolicies(d.Get("player_latency_policy").([]interface{})),
		TimeoutInSeconds:      aws.Int64(int64(d.Get("timeout_in_seconds").(int))),
	}
	if v, ok := d.GetOk("name"); ok {
		input.Name = aws.String(v.(string))
	}
	log.Printf("[INFO] Creating Gamelift Session Queue: %s", input)
	out, err := conn.CreateGameSessionQueue(&input)
	if err != nil {
		return err
	}

	d.SetId(*out.GameSessionQueue.GameSessionQueueArn)
	d.Set("name", out.GameSessionQueue.Name)

	return resourceAwsGameliftSessionQueueRead(d, meta)
}

func resourceAwsGameliftSessionQueueRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).gameliftconn
	log.Printf("[INFO] Describing Gamelift Session Queues: %s", d.Get("name"))
	limit := int64(1)
	out, err := conn.DescribeGameSessionQueues(&gamelift.DescribeGameSessionQueuesInput{
		Names: aws.StringSlice([]string{d.Get("name").(string)}),
		Limit: &limit,
	})
	if err != nil {
		if isAWSErr(err, gamelift.ErrCodeNotFoundException, "") {
			log.Printf("[WARN] Gamelift Session Queues (%s) not found, removing from state", d.Get("name"))
			d.SetId("")
			return nil
		}
		return err
	}
	sessionQueues := out.GameSessionQueues

	if len(sessionQueues) < 1 {
		log.Printf("[WARN] Gamelift Session Queue (%s) not found, removing from state", d.Get("name"))
		d.SetId("")
		return nil
	}
	if len(sessionQueues) != 1 {
		return fmt.Errorf("expected exactly 1 Gamelift Session Queues, found %d under %q",
			len(sessionQueues), d.Get("name"))
	}
	sessionQueue := sessionQueues[0]

	d.Set("arn", sessionQueue.GameSessionQueueArn)
	d.Set("name", sessionQueue.Name)
	d.Set("timeout_in_seconds", sessionQueue.TimeoutInSeconds)
	d.Set("destinations", sessionQueue.Destinations)
	if err := d.Set("player_latency_policy", flattenGameliftPlayerLatencyPolicies(sessionQueue.PlayerLatencyPolicies)); err != nil {
		return fmt.Errorf("error setting player_latency_policy: %s", err)
	}

	return nil
}

func flattenGameliftPlayerLatencyPolicies(playerLatencyPolicies []*gamelift.PlayerLatencyPolicy) []interface{} {
	lst := []interface{}{}
	for _, policy := range playerLatencyPolicies {
		m := map[string]interface{}{
			"maximum_individual_player_latency_milliseconds": aws.Int64Value(policy.MaximumIndividualPlayerLatencyMilliseconds),
			"policy_duration_seconds":                        aws.Int64Value(policy.PolicyDurationSeconds),
		}
		lst = append(lst, m)
	}
	return lst
}

func resourceAwsGameliftSessionQueueUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).gameliftconn

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

	log.Printf("[INFO] Updating Gamelift Session Queue: %s", name)

	input := gamelift.UpdateGameSessionQueueInput{
		Name:                  aws.String(d.Get("name").(string)),
		Destinations:          expandGameliftGameSessionQueueDestinations(d.Get("destinations").([]interface{})),
		PlayerLatencyPolicies: expandGameliftGameSessionPlayerLatencyPolicies(d.Get("player_latency_policy").([]interface{})),
		TimeoutInSeconds:      aws.Int64(int64(d.Get("timeout_in_seconds").(int))),
	}

	_, err := conn.UpdateGameSessionQueue(&input)
	if err != nil {
		return err
	}

	return resourceAwsGameliftSessionQueueRead(d, meta)
}

func resourceAwsGameliftSessionQueueDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).gameliftconn
	name := d.Get("name").(string)
	log.Printf("[INFO] Deleting Gamelift Session Queue: %s", name)
	_, err := conn.DeleteGameSessionQueue(&gamelift.DeleteGameSessionQueueInput{
		Name: aws.String(name),
	})
	if isAWSErr(err, gamelift.ErrCodeNotFoundException, "") {
		return nil
	}
	if err != nil {
		return fmt.Errorf("error deleting Gamelift Game Session Queue (%s): %s", d.Id(), err)
	}

	return nil
}

func expandGameliftGameSessionQueueDestinations(destinationsMap []interface{}) []*gamelift.GameSessionQueueDestination {
	if len(destinationsMap) < 1 {
		return nil
	}
	var destinations []*gamelift.GameSessionQueueDestination
	for _, destination := range destinationsMap {
		destinations = append(
			destinations,
			&gamelift.GameSessionQueueDestination{
				DestinationArn: aws.String(destination.(string)),
			})
	}
	return destinations
}

func expandGameliftGameSessionPlayerLatencyPolicies(destinationsPlayerLatencyPolicyMap []interface{}) []*gamelift.PlayerLatencyPolicy {
	if len(destinationsPlayerLatencyPolicyMap) < 1 {
		return nil
	}
	var playerLatencyPolicies []*gamelift.PlayerLatencyPolicy
	for _, playerLatencyPolicy := range destinationsPlayerLatencyPolicyMap {
		item := playerLatencyPolicy.(map[string]interface{})
		playerLatencyPolicies = append(
			playerLatencyPolicies,
			&gamelift.PlayerLatencyPolicy{
				MaximumIndividualPlayerLatencyMilliseconds: aws.Int64(int64(item["maximum_individual_player_latency_milliseconds"].(int))),
				PolicyDurationSeconds:                      aws.Int64(int64(item["policy_duration_seconds"].(int))),
			})
	}
	return playerLatencyPolicies
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"strconv"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/pricing"
	"k8s.io/klog"
	"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
)

var outputPath = ""

func main() {
	flag.StringVar(&outputPath, "out", outputPath, "file to write")

	flag.Parse()

	if err := run(); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}

func run() error {
	if outputPath == "" {
		return fmt.Errorf("must specify output file with --out")
	}

	klog.Info("Beginning AWS Machine Refresh")

	// Not currently in the API
	t2CreditsPerHour := map[string]float32{
		"t1.micro":   1,
		"t2.nano":    3,
		"t2.micro":   6,
		"t2.small":   12,
		"t2.medium":  24,
		"t2.large":   36,
		"t2.xlarge":  54,
		"t2.2xlarge": 81.6,
		"t3.nano":    6,
		"t3.micro":   12,
		"t3.small":   24,
		"t3.medium":  24,
		"t3.large":   36,
		"t3.xlarge":  96,
		"t3.2xlarge": 192,
	}

	machines := []awsup.AWSMachineTypeInfo{}
	families := make(map[string]struct{})

	prices := []aws.JSONValue{}

	config := aws.NewConfig()
	// Give verbose errors on auth problems
	config = config.WithCredentialsChainVerboseErrors(true)
	// Default to us-east-1
	config = config.WithRegion("us-east-1")

	sess, err := session.NewSession()
	if err != nil {
		return err
	}
	svc := pricing.New(sess, config)
	typeTerm := pricing.FilterTypeTermMatch
	input := &pricing.GetProductsInput{
		Filters: []*pricing.Filter{
			{
				Field: aws.String("operatingSystem"),
				Type:  &typeTerm,
				Value: aws.String("Linux"),
			},
			{
				Field: aws.String("tenancy"),
				Type:  &typeTerm,
				Value: aws.String("shared"),
			},
			{
				Field: aws.String("location"),
				Type:  &typeTerm,
				Value: aws.String("US East (N. Virginia)"),
			},
			{
				Field: aws.String("preInstalledSw"),
				Type:  &typeTerm,
				Value: aws.String("NA"),
			},
		},
		FormatVersion: aws.String("aws_v1"),
		ServiceCode:   aws.String("AmazonEC2"),
	}

	for {
		result, err := svc.GetProducts(input)

		if err != nil {
			if aerr, ok := err.(awserr.Error); ok {
				switch aerr.Code() {
				case pricing.ErrCodeInternalErrorException:
					return fmt.Errorf("%s: %v", pricing.ErrCodeInternalErrorException, aerr)
				case pricing.ErrCodeInvalidParameterException:
					return fmt.Errorf("%s: %v", pricing.ErrCodeInvalidParameterException, aerr)
				case pricing.ErrCodeNotFoundException:
					return fmt.Errorf("%s: %v", pricing.ErrCodeNotFoundException, aerr)
				case pricing.ErrCodeInvalidNextTokenException:
					return fmt.Errorf("%s: %v", pricing.ErrCodeInvalidNextTokenException, aerr)
				case pricing.ErrCodeExpiredNextTokenException:
					return fmt.Errorf("%s: %v", pricing.ErrCodeExpiredNextTokenException, aerr)
				default:
					return aerr
				}
			} else {
				// Print the error, cast err to awserr.Error to get the Code and
				// Message from an error.
				return err
			}
		}

		prices = append(prices, result.PriceList...)

		if result.NextToken != nil {
			input.NextToken = result.NextToken
		} else {
			break
		}
	}

	var warnings []string

	seen := map[string]bool{}
	for _, item := range prices {
		for k, v := range item {
			if k == "product" {
				product := v.(map[string]interface{})
				attributes := map[string]string{}
				for k, v := range product["attributes"].(map[string]interface{}) {
					attributes[k] = v.(string)
				}

				instanceType := attributes["instanceType"]

				if _, ok := seen[instanceType]; ok {
					continue
				}
				seen[instanceType] = true

				machine := awsup.AWSMachineTypeInfo{
					Name:  instanceType,
					Cores: stringToInt(attributes["vcpu"]),
				}

				memory := strings.TrimRight(attributes["memory"], " GiB")
				machine.MemoryGB = stringToFloat32(memory)

				if attributes["storage"] != "EBS only" {
					storage := strings.Split(attributes["storage"], " ")
					count := stringToInt(storage[0])
					var size int
					if storage[2] == "NVMe" {
						count = 1
						size = stringToInt(storage[0])
					} else {
						size = stringToInt(storage[2])
					}

					ephemeralDisks := []int{}
					for i := 0; i < count; i++ {
						ephemeralDisks = append(ephemeralDisks, size)
					}

					machine.EphemeralDisks = ephemeralDisks
				}

				if attributes["instanceFamily"] == "GPU instance" {
					machine.GPU = true
				}

				if attributes["ecu"] == "Variable" {
					machine.Burstable = true
					machine.ECU = t2CreditsPerHour[machine.Name] // This is actually credits * ECUs, but we'll add that later
				} else if attributes["ecu"] == "NA" {
					machine.ECU = 0
				} else {
					machine.ECU = stringToFloat32(attributes["ecu"])
				}

				if enis, enisOK := InstanceENIsAvailable[instanceType]; enisOK {
					machine.InstanceENIs = enis
				} else {
					warnings = append(warnings, fmt.Sprintf("ENIs not known for %s", instanceType))
				}

				if ipsPerENI, ipsOK := InstanceIPsAvailable[instanceType]; ipsOK {
					machine.InstanceIPsPerENI = int(ipsPerENI)
				} else {
					warnings = append(warnings, fmt.Sprintf("IPs per ENI not known for %s", instanceType))
				}

				machines = append(machines, machine)

				family := strings.Split(instanceType, ".")[0]
				families[family] = struct{}{}

			}
		}
	}

	sortedFamilies := []string{}
	for f := range families {
		sortedFamilies = append(sortedFamilies, f)
	}
	sort.Strings(sortedFamilies)

	sort.Slice(machines, func(i, j int) bool {
		// Sort first by family
		tokensI := strings.Split(machines[i].Name, ".")
		tokensJ := strings.Split(machines[j].Name, ".")

		if tokensI[0] != tokensJ[0] {
			return tokensI[0] < tokensJ[0]
		}

		// Then sort by size within the family
		if machines[i].MemoryGB != machines[j].MemoryGB {
			return machines[i].MemoryGB < machines[j].MemoryGB
		}

		// Fallback: sort by name
		return machines[i].Name < machines[j].Name
	})

	var output string

	if len(warnings) != 0 {
		output = output + "\n"
		for _, warning := range warnings {
			output = output + "// WARNING: " + warning + "\n"
		}
		output = output + "\n"
	}

	for _, f := range sortedFamilies {
		output = output + fmt.Sprintf("\n// %s family", f)
		for _, m := range machines {
			if family := strings.Split(m.Name, ".")[0]; family == f {
				var ecu string
				if m.Burstable {
					ecu = fmt.Sprintf("%v * BurstableCreditsToECUS", m.ECU)
				} else {
					ecu = fmt.Sprint(m.ECU)
				}

				body := fmt.Sprintf(`
	{
		Name: "%s",
		MemoryGB: %v,
		ECU: %v,
		Cores: %v,
		InstanceENIs: %v,
		InstanceIPsPerENI: %v,
	`, m.Name, m.MemoryGB, ecu, m.Cores, m.InstanceENIs, m.InstanceIPsPerENI)
				output = output + body

				// Avoid awkward []int(nil) syntax
				if len(m.EphemeralDisks) == 0 {
					output = output + "EphemeralDisks: nil,\n"
				} else {
					output = output + fmt.Sprintf("EphemeralDisks: %#v,\n", m.EphemeralDisks)
				}

				if m.Burstable {
					output = output + "Burstable: true,\n"
				}

				if m.GPU {
					output = output + "GPU: true,\n"
				}

				output = output + "},\n"
			}
		}
		output = output + "\n"
	}

	klog.Infof("Writing changes to %v", outputPath)

	fileInput, err := ioutil.ReadFile(outputPath)
	if err != nil {
		return fmt.Errorf("error reading %s: %v", outputPath, err)
	}
	scanner := bufio.NewScanner(bytes.NewReader(fileInput))

	scanner.Split(bufio.ScanLines)

	var newfile string
	flag := false
	done := false
	for scanner.Scan() {
		line := scanner.Text()

		if strings.Contains(line, "END GENERATED CONTENT") {
			flag = false
			done = true
		}
		if !flag {
			newfile = newfile + line + "\n"
		}
		if strings.Contains(line, "BEGIN GENERATED CONTENT") {
			flag = true
			newfile = newfile + output
		}
	}

	if !done {
		return fmt.Errorf("BEGIN GENERATED CONTENT / END GENERATED CONTENT markers not found")
	}

	err = ioutil.WriteFile(outputPath, []byte(newfile), 0644)
	if err != nil {
		return fmt.Errorf("error writing %s: %v", outputPath, err)
	}

	klog.Info("Done.")
	klog.Flush()

	return nil
}

func stringToFloat32(s string) float32 {
	// For 1,000 case
	clean := strings.Replace(s, ",", "", -1)
	value, err := strconv.ParseFloat(clean, 32)
	if err != nil {
		klog.Errorf("error converting string to float32: %v", err)
	}
	return float32(value)
}

func stringToInt(s string) int {
	// For 1,000 case
	clean := strings.Replace(s, ",", "", -1)
	value, err := strconv.Atoi(clean)
	if err != nil {
		klog.Error(err)
	}
	return value
}
<commit_msg>fix string trim func in main<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"strconv"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/pricing"
	"k8s.io/klog"
	"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
)

var outputPath = ""

func main() {
	flag.StringVar(&outputPath, "out", outputPath, "file to write")

	flag.Parse()

	if err := run(); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}

func run() error {
	if outputPath == "" {
		return fmt.Errorf("must specify output file with --out")
	}

	klog.Info("Beginning AWS Machine Refresh")

	// Not currently in the API
	t2CreditsPerHour := map[string]float32{
		"t1.micro":   1,
		"t2.nano":    3,
		"t2.micro":   6,
		"t2.small":   12,
		"t2.medium":  24,
		"t2.large":   36,
		"t2.xlarge":  54,
		"t2.2xlarge": 81.6,
		"t3.nano":    6,
		"t3.micro":   12,
		"t3.small":   24,
		"t3.medium":  24,
		"t3.large":   36,
		"t3.xlarge":  96,
		"t3.2xlarge": 192,
	}

	machines := []awsup.AWSMachineTypeInfo{}
	families := make(map[string]struct{})

	prices := []aws.JSONValue{}

	config := aws.NewConfig()
	// Give verbose errors on auth problems
	config = config.WithCredentialsChainVerboseErrors(true)
	// Default to us-east-1
	config = config.WithRegion("us-east-1")

	sess, err := session.NewSession()
	if err != nil {
		return err
	}
	svc := pricing.New(sess, config)
	typeTerm := pricing.FilterTypeTermMatch
	input := &pricing.GetProductsInput{
		Filters: []*pricing.Filter{
			{
				Field: aws.String("operatingSystem"),
				Type:  &typeTerm,
				Value: aws.String("Linux"),
			},
			{
				Field: aws.String("tenancy"),
				Type:  &typeTerm,
				Value: aws.String("shared"),
			},
			{
				Field: aws.String("location"),
				Type:  &typeTerm,
				Value: aws.String("US East (N. Virginia)"),
			},
			{
				Field: aws.String("preInstalledSw"),
				Type:  &typeTerm,
				Value: aws.String("NA"),
			},
		},
		FormatVersion: aws.String("aws_v1"),
		ServiceCode:   aws.String("AmazonEC2"),
	}

	for {
		result, err := svc.GetProducts(input)

		if err != nil {
			if aerr, ok := err.(awserr.Error); ok {
				switch aerr.Code() {
				case pricing.ErrCodeInternalErrorException:
					return fmt.Errorf("%s: %v", pricing.ErrCodeInternalErrorException, aerr)
				case pricing.ErrCodeInvalidParameterException:
					return fmt.Errorf("%s: %v", pricing.ErrCodeInvalidParameterException, aerr)
				case pricing.ErrCodeNotFoundException:
					return fmt.Errorf("%s: %v", pricing.ErrCodeNotFoundException, aerr)
				case pricing.ErrCodeInvalidNextTokenException:
					return fmt.Errorf("%s: %v", pricing.ErrCodeInvalidNextTokenException, aerr)
				case pricing.ErrCodeExpiredNextTokenException:
					return fmt.Errorf("%s: %v", pricing.ErrCodeExpiredNextTokenException, aerr)
				default:
					return aerr
				}
			} else {
				// Print the error, cast err to awserr.Error to get the Code and
				// Message from an error.
				return err
			}
		}

		prices = append(prices, result.PriceList...)

		if result.NextToken != nil {
			input.NextToken = result.NextToken
		} else {
			break
		}
	}

	var warnings []string

	seen := map[string]bool{}
	for _, item := range prices {
		for k, v := range item {
			if k == "product" {
				product := v.(map[string]interface{})
				attributes := map[string]string{}
				for k, v := range product["attributes"].(map[string]interface{}) {
					attributes[k] = v.(string)
				}

				instanceType := attributes["instanceType"]

				if _, ok := seen[instanceType]; ok {
					continue
				}
				seen[instanceType] = true

				machine := awsup.AWSMachineTypeInfo{
					Name:  instanceType,
					Cores: stringToInt(attributes["vcpu"]),
				}

				memory := strings.TrimSuffix(attributes["memory"], " GiB")
				machine.MemoryGB = stringToFloat32(memory)

				if attributes["storage"] != "EBS only" {
					storage := strings.Split(attributes["storage"], " ")
					count := stringToInt(storage[0])
					var size int
					if storage[2] == "NVMe" {
						count = 1
						size = stringToInt(storage[0])
					} else {
						size = stringToInt(storage[2])
					}

					ephemeralDisks := []int{}
					for i := 0; i < count; i++ {
						ephemeralDisks = append(ephemeralDisks, size)
					}

					machine.EphemeralDisks = ephemeralDisks
				}

				if attributes["instanceFamily"] == "GPU instance" {
					machine.GPU = true
				}

				if attributes["ecu"] == "Variable" {
					machine.Burstable = true
					machine.ECU = t2CreditsPerHour[machine.Name] // This is actually credits * ECUs, but we'll add that later
				} else if attributes["ecu"] == "NA" {
					machine.ECU = 0
				} else {
					machine.ECU = stringToFloat32(attributes["ecu"])
				}

				if enis, enisOK := InstanceENIsAvailable[instanceType]; enisOK {
					machine.InstanceENIs = enis
				} else {
					warnings = append(warnings, fmt.Sprintf("ENIs not known for %s", instanceType))
				}

				if ipsPerENI, ipsOK := InstanceIPsAvailable[instanceType]; ipsOK {
					machine.InstanceIPsPerENI = int(ipsPerENI)
				} else {
					warnings = append(warnings, fmt.Sprintf("IPs per ENI not known for %s", instanceType))
				}

				machines = append(machines, machine)

				family := strings.Split(instanceType, ".")[0]
				families[family] = struct{}{}

			}
		}
	}

	sortedFamilies := []string{}
	for f := range families {
		sortedFamilies = append(sortedFamilies, f)
	}
	sort.Strings(sortedFamilies)

	sort.Slice(machines, func(i, j int) bool {
		// Sort first by family
		tokensI := strings.Split(machines[i].Name, ".")
		tokensJ := strings.Split(machines[j].Name, ".")

		if tokensI[0] != tokensJ[0] {
			return tokensI[0] < tokensJ[0]
		}

		// Then sort by size within the family
		if machines[i].MemoryGB != machines[j].MemoryGB {
			return machines[i].MemoryGB < machines[j].MemoryGB
		}

		// Fallback: sort by name
		return machines[i].Name < machines[j].Name
	})

	var output string

	if len(warnings) != 0 {
		output = output + "\n"
		for _, warning := range warnings {
			output = output + "// WARNING: " + warning + "\n"
		}
		output = output + "\n"
	}

	for _, f := range sortedFamilies {
		output = output + fmt.Sprintf("\n// %s family", f)
		for _, m := range machines {
			if family := strings.Split(m.Name, ".")[0]; family == f {
				var ecu string
				if m.Burstable {
					ecu = fmt.Sprintf("%v * BurstableCreditsToECUS", m.ECU)
				} else {
					ecu = fmt.Sprint(m.ECU)
				}

				body := fmt.Sprintf(`
	{
		Name: "%s",
		MemoryGB: %v,
		ECU: %v,
		Cores: %v,
		InstanceENIs: %v,
		InstanceIPsPerENI: %v,
	`, m.Name, m.MemoryGB, ecu, m.Cores, m.InstanceENIs, m.InstanceIPsPerENI)
				output = output + body

				// Avoid awkward []int(nil) syntax
				if len(m.EphemeralDisks) == 0 {
					output = output + "EphemeralDisks: nil,\n"
				} else {
					output = output + fmt.Sprintf("EphemeralDisks: %#v,\n", m.EphemeralDisks)
				}

				if m.Burstable {
					output = output + "Burstable: true,\n"
				}

				if m.GPU {
					output = output + "GPU: true,\n"
				}

				output = output + "},\n"
			}
		}
		output = output + "\n"
	}

	klog.Infof("Writing changes to %v", outputPath)

	fileInput, err := ioutil.ReadFile(outputPath)
	if err != nil {
		return fmt.Errorf("error reading %s: %v", outputPath, err)
	}
	scanner := bufio.NewScanner(bytes.NewReader(fileInput))

	scanner.Split(bufio.ScanLines)

	var newfile string
	flag := false
	done := false
	for scanner.Scan() {
		line := scanner.Text()

		if strings.Contains(line, "END GENERATED CONTENT") {
			flag = false
			done = true
		}
		if !flag {
			newfile = newfile + line + "\n"
		}
		if strings.Contains(line, "BEGIN GENERATED CONTENT") {
			flag = true
			newfile = newfile + output
		}
	}

	if !done {
		return fmt.Errorf("BEGIN GENERATED CONTENT / END GENERATED CONTENT markers not found")
	}

	err = ioutil.WriteFile(outputPath, []byte(newfile), 0644)
	if err != nil {
		return fmt.Errorf("error writing %s: %v", outputPath, err)
	}

	klog.Info("Done.")
	klog.Flush()

	return nil
}

func stringToFloat32(s string) float32 {
	// For 1,000 case
	clean := strings.Replace(s, ",", "", -1)
	value, err := strconv.ParseFloat(clean, 32)
	if err != nil {
		klog.Errorf("error converting string to float32: %v", err)
	}
	return float32(value)
}

func stringToInt(s string) int {
	// For 1,000 case
	clean := strings.Replace(s, ",", "", -1)
	value, err := strconv.Atoi(clean)
	if err != nil {
		klog.Error(err)
	}
	return value
}
<|endoftext|>


<commit_before>package prune

import (
	"fmt"
	"strings"

	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

type Resource struct {
	group      string
	version    string
	kind       string
	namespaced bool
}

func (pr Resource) String() string {
	return fmt.Sprintf("%v/%v, Kind=%v, Namespaced=%v", pr.group, pr.version, pr.kind, pr.namespaced)
}

func GetRESTMappings(mapper meta.RESTMapper, pruneResources *[]Resource) (namespaced, nonNamespaced []*meta.RESTMapping, err error) {
	if len(*pruneResources) == 0 {
		// default allowlist
		*pruneResources = []Resource{
			{"", "v1", "ConfigMap", true},
			{"", "v1", "Endpoints", true},
			{"", "v1", "Namespace", false},
			{"", "v1", "PersistentVolumeClaim", true},
			{"", "v1", "PersistentVolume", false},
			{"", "v1", "Pod", true},
			{"", "v1", "ReplicationController", true},
			{"", "v1", "Secret", true},
			{"", "v1", "Service", true},
			{"batch", "v1", "Job", true},
			{"batch", "v1", "CronJob", true},
			{"networking.k8s.io", "v1", "Ingress", true},
			{"apps", "v1", "DaemonSet", true},
			{"apps", "v1", "Deployment", true},
			{"apps", "v1", "ReplicaSet", true},
			{"apps", "v1", "StatefulSet", true},
		}
	}

	for _, resource := range *pruneResources {
		addedMapping, err := mapper.RESTMapping(schema.GroupKind{Group: resource.group, Kind: resource.kind}, resource.version)
		if err != nil {
			return nil, nil, fmt.Errorf("invalid resource %v: %v", resource, err)
		}
		if resource.namespaced {
			namespaced = append(namespaced, addedMapping)
		} else {
			nonNamespaced = append(nonNamespaced, addedMapping)
		}
	}

	return namespaced, nonNamespaced, nil
}

func ParseResources(mapper meta.RESTMapper, gvks []string) ([]Resource, error) {
	pruneResources := []Resource{}
	for _, groupVersionKind := range gvks {
		gvk := strings.Split(groupVersionKind, "/")
		if len(gvk) != 3 {
			return nil, fmt.Errorf("invalid GroupVersionKind format: %v, please follow <group/version/kind>", groupVersionKind)
		}

		if gvk[0] == "core" {
			gvk[0] = ""
		}
		mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk[0], Kind: gvk[2]}, gvk[1])
		if err != nil {
			return pruneResources, err
		}
		var namespaced bool
		namespaceScope := mapping.Scope.Name()
		switch namespaceScope {
		case meta.RESTScopeNameNamespace:
			namespaced = true
		case meta.RESTScopeNameRoot:
			namespaced = false
		default:
			return pruneResources, fmt.Errorf("Unknown namespace scope: %q", namespaceScope)
		}

		pruneResources = append(pruneResources, Resource{gvk[0], gvk[1], gvk[2], namespaced})
	}
	return pruneResources, nil
}
<commit_msg>Add boilerplate header into util/prune<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package prune

import (
	"fmt"
	"strings"

	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

type Resource struct {
	group      string
	version    string
	kind       string
	namespaced bool
}

func (pr Resource) String() string {
	return fmt.Sprintf("%v/%v, Kind=%v, Namespaced=%v", pr.group, pr.version, pr.kind, pr.namespaced)
}

func GetRESTMappings(mapper meta.RESTMapper, pruneResources *[]Resource) (namespaced, nonNamespaced []*meta.RESTMapping, err error) {
	if len(*pruneResources) == 0 {
		// default allowlist
		*pruneResources = []Resource{
			{"", "v1", "ConfigMap", true},
			{"", "v1", "Endpoints", true},
			{"", "v1", "Namespace", false},
			{"", "v1", "PersistentVolumeClaim", true},
			{"", "v1", "PersistentVolume", false},
			{"", "v1", "Pod", true},
			{"", "v1", "ReplicationController", true},
			{"", "v1", "Secret", true},
			{"", "v1", "Service", true},
			{"batch", "v1", "Job", true},
			{"batch", "v1", "CronJob", true},
			{"networking.k8s.io", "v1", "Ingress", true},
			{"apps", "v1", "DaemonSet", true},
			{"apps", "v1", "Deployment", true},
			{"apps", "v1", "ReplicaSet", true},
			{"apps", "v1", "StatefulSet", true},
		}
	}

	for _, resource := range *pruneResources {
		addedMapping, err := mapper.RESTMapping(schema.GroupKind{Group: resource.group, Kind: resource.kind}, resource.version)
		if err != nil {
			return nil, nil, fmt.Errorf("invalid resource %v: %v", resource, err)
		}
		if resource.namespaced {
			namespaced = append(namespaced, addedMapping)
		} else {
			nonNamespaced = append(nonNamespaced, addedMapping)
		}
	}

	return namespaced, nonNamespaced, nil
}

func ParseResources(mapper meta.RESTMapper, gvks []string) ([]Resource, error) {
	pruneResources := []Resource{}
	for _, groupVersionKind := range gvks {
		gvk := strings.Split(groupVersionKind, "/")
		if len(gvk) != 3 {
			return nil, fmt.Errorf("invalid GroupVersionKind format: %v, please follow <group/version/kind>", groupVersionKind)
		}

		if gvk[0] == "core" {
			gvk[0] = ""
		}
		mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk[0], Kind: gvk[2]}, gvk[1])
		if err != nil {
			return pruneResources, err
		}
		var namespaced bool
		namespaceScope := mapping.Scope.Name()
		switch namespaceScope {
		case meta.RESTScopeNameNamespace:
			namespaced = true
		case meta.RESTScopeNameRoot:
			namespaced = false
		default:
			return pruneResources, fmt.Errorf("Unknown namespace scope: %q", namespaceScope)
		}

		pruneResources = append(pruneResources, Resource{gvk[0], gvk[1], gvk[2], namespaced})
	}
	return pruneResources, nil
}
<|endoftext|>