<commit_before>package pyraconv

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"fmt"
	"math"
	"strconv"
	"time"

	// "code.google.com/p/go.text/encoding"
	// "code.google.com/p/go.text/encoding/charmap"
	// "code.google.com/p/go.text/encoding/japanese"
	// "code.google.com/p/go.text/encoding/korean"
	// "code.google.com/p/go.text/encoding/simplifiedchinese"
	// "code.google.com/p/go.text/encoding/traditionalchinese"
)

// ToInterfaceArray converts interface{} to []interface{} and never returns nil
func ToInterfaceArray(i1 interface{}) []interface{} {
	if i1 == nil {
		return []interface{}{}
	}
	switch i2 := i1.(type) {
	default:
		return []interface{}{}
	case []interface{}:
		return i2
	}
	return []interface{}{}
}

// ToInterfaceMap converts interface{} to map[string]interface{} and never returns nil
func ToInterfaceMap(i1 interface{}) map[string]interface{} {
	if i1 == nil {
		return map[string]interface{}{}
	}
	switch i2 := i1.(type) {
	case map[string]interface{}:
		return i2
	default:
		return map[string]interface{}{}
	}
	return map[string]interface{}{}
}

// ToStringArray converts interface{} to []string and never returns nil
func ToStringArray(i1 interface{}) []string {
	if i1 == nil {
		return []string{}
	}
	switch i2 := i1.(type) {
	default:
		return []string{fmt.Sprint(i2)}
	case []string:
		return i2
	case []interface{}:
		var ss []string
		for _, i3 := range i2 {
			ss = append(ss, ToString(i3))
		}
		return ss
	}
	return []string{}
}

// ToStringMap converts interface{} to map[string]string and never returns nil
func ToStringMap(i1 interface{}) map[string]string {
	if i1 == nil {
		return map[string]string{}
	}
	switch i2 := i1.(type) {
	case map[string]interface{}:
		m1 := map[string]string{}
		for k, v := range m1 {
			m1[k] = ToString(v)
		}
		return m1
	case map[string]string:
		return i2

	default:
		return map[string]string{}
	}
	return map[string]string{}
}

// ToString converts interface{} to string
func ToString(i1 interface{}) string {
	if i1 == nil {
		return ""
	}
	switch i2 := i1.(type) {
	default:
		return fmt.Sprint(i2)
	case bool:
		if i2 {
			return "true"
		} else {
			return "false"
		}
	case string:
		return i2
	case *bool:
		if i2 == nil {
			return ""
		}
		if *i2 {
			return "true"
		} else {
			return "false"
		}
	case *string:
		if i2 == nil {
			return ""
		}
		return *i2
	case *json.Number:
		return i2.String()
	case json.Number:
		return i2.String()
	}
	return ""
}

// ToInt64 converts interface{} to int64
func ToInt64(i1 interface{}) int64 {
	if i1 == nil {
		return 0
	}
	switch i2 := i1.(type) {
	default:
		i3, _ := strconv.ParseInt(ToString(i2), 10, 64)
		return i3
	case *json.Number:
		i3, _ := i2.Int64()
		return i3
	case json.Number:
		i3, _ := i2.Int64()
		return i3
	case int64:
		return i2
	case float64:
		return int64(i2)
	case float32:
		return int64(i2)
	case uint64:
		return int64(i2)
	case int:
		return int64(i2)
	case uint:
		return int64(i2)
	case bool:
		if i2 {
			return 1
		} else {
			return 0
		}
	case *bool:
		if i2 == nil {
			return 0
		}
		if *i2 {
			return 1
		} else {
			return 0
		}
	}
	return 0
}

// ToBool converts interface{} to bool
func ToBool(i1 interface{}) bool {
	if i1 == nil {
		return false
	}
	switch i2 := i1.(type) {
	default:
		return false
	case bool:
		return i2
	case string:
		return i2 == "true"
	case int:
		return i2 != 0
	case *bool:
		if i2 == nil {
			return false
		}
		return *i2
	case *string:
		if i2 == nil {
			return false
		}
		return *i2 == "true"
	case *int:
		if i2 == nil {
			return false
		}
		return *i2 != 0
	}
	return false
}

// CloneObject creates copy of object using GOB.
func CloneObject(a, b interface{}) {
	buff := new(bytes.Buffer)
	enc := gob.NewEncoder(buff)
	dec := gob.NewDecoder(buff)
	enc.Encode(a)
	dec.Decode(b)
}

// func ShoterRunes(s1 string, n int) string {
// 	r1 := []rune(s1)
// 	if len(r1) > n {
// 		r1 = r1[0:n]
// 	}
// 	s2 := string(r1)
// 	return s2
// }

// func SecureWebId(s1 string) (string, bool) {
// 	for _, c := range s1 {
// 		if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
// 			return "", false
// 		}
// 	}
// 	return s1, true
// }
// func DebugObject(id string, o1 interface{}) {
// 	b, _ := json.Marshal(o1)
// 	log.Println(id, string(b))
// }

// func CharsetGetEncding(charset string) encoding.Encoding {
// 	if charset == "" {
// 		return nil
// 	}
// 	charset = strings.ToLower(charset)
// 	charset = strings.Replace(charset, "-", "", -1)
// 	if charset == "utf8" {
// 		return nil
// 	}
// 	if charset == "koi8" {
// 		return charmap.KOI8R
// 	}
// 	if charset == "koi8r" {
// 		return charmap.KOI8R
// 	}
// 	if charset == "koi8u" {
// 		return charmap.KOI8U
// 	}

// 	if charset == "iso88591" {
// 		return charmap.Windows1252
// 	}

// 	if charset == "iso88592" {
// 		return charmap.ISO8859_2
// 	}
// 	if charset == "iso88593" {
// 		return charmap.ISO8859_3
// 	}
// 	if charset == "iso88594" {
// 		return charmap.ISO8859_4
// 	}
// 	if charset == "iso88595" {
// 		return charmap.ISO8859_5
// 	}
// 	if charset == "iso88596" {
// 		return charmap.ISO8859_6
// 	}
// 	if charset == "iso88597" {
// 		return charmap.ISO8859_7
// 	}
// 	if charset == "iso88598" {
// 		return charmap.ISO8859_8
// 	}
// 	// if charset == "iso88599" {return charmap.ISO8859_9}
// 	if charset == "iso885910" {
// 		return charmap.ISO8859_10
// 	}
// 	// if charset == "iso885911" {return charmap.ISO8859_11}
// 	// if charset == "iso885912" {return charmap.ISO8859_12}
// 	if charset == "iso885913" {
// 		return charmap.ISO8859_13
// 	}
// 	if charset == "iso885914" {
// 		return charmap.ISO8859_14
// 	}
// 	if charset == "iso885915" {
// 		return charmap.ISO8859_15
// 	}
// 	if charset == "iso885916" {
// 		return charmap.ISO8859_16
// 	}

// 	if charset == "windows1250" {
// 		return charmap.Windows1250
// 	}
// 	if charset == "windows1251" {
// 		return charmap.Windows1251
// 	}
// 	if charset == "windows1252" {
// 		return charmap.Windows1252
// 	}
// 	if charset == "windows1253" {
// 		return charmap.Windows1253
// 	}
// 	if charset == "windows1254" {
// 		return charmap.Windows1254
// 	}
// 	if charset == "windows1255" {
// 		return charmap.Windows1255
// 	}
// 	if charset == "windows1256" {
// 		return charmap.Windows1256
// 	}
// 	if charset == "windows1257" {
// 		return charmap.Windows1257
// 	}
// 	if charset == "windows1258" {
// 		return charmap.Windows1258
// 	}
// 	if charset == "windows874" {
// 		return charmap.Windows874
// 	}

// 	if charset == "eucjp" {
// 		return japanese.EUCJP
// 	}
// 	if charset == "iso2022jp" {
// 		return japanese.ISO2022JP
// 	}
// 	if charset == "shiftjis" {
// 		return japanese.ShiftJIS
// 	}

// 	if charset == "euckr" {
// 		return korean.EUCKR
// 	}

// 	if charset == "gb18030" {
// 		return simplifiedchinese.GB18030
// 	}
// 	if charset == "gbk" {
// 		return simplifiedchinese.GBK
// 	}
// 	if charset == "hzgb2312" {
// 		return simplifiedchinese.HZGB2312
// 	}

// 	if charset == "big5" {
// 		return traditionalchinese.Big5
// 	}

// 	return nil

// }
func TimeSince(t1 time.Time) (since string) {
	now := time.Now()
	d1 := time.Since(t1)
	if d1.Hours() > 24.0 {
		if t1.Year() == now.Year() {
			if t1.Month() == now.Month() {
				if t1.Day() == now.Day() {
					since = t1.Format("15:04")
				} else {
					since = t1.Format("02-Jan")
				}
			} else {
				since = t1.Format("02-Jan")
			}
		} else {
			since = t1.Format("02-Jan-2006")
		}
	} else {
		// since = d1.String()
		hours := int(d1.Hours())
		minutes := int(d1.Minutes() - float64(60.0*hours))
		since = fmt.Sprintf("%02d:%02d", hours, minutes)
	}
	return since
}

func MovingExpAvg(value float64, oldValue float64, ftime float64, time float64) float64 {
	alpha := 1. - math.Exp(-ftime/time)
	r := alpha*value + (1.-alpha)*oldValue
	return r
}
<commit_msg>added ToFloat32() and ToFloat64() request<commit_after>package pyraconv

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"fmt"
	"math"
	"strconv"
	"time"

	// "code.google.com/p/go.text/encoding"
	// "code.google.com/p/go.text/encoding/charmap"
	// "code.google.com/p/go.text/encoding/japanese"
	// "code.google.com/p/go.text/encoding/korean"
	// "code.google.com/p/go.text/encoding/simplifiedchinese"
	// "code.google.com/p/go.text/encoding/traditionalchinese"
)

// ToInterfaceArray converts interface{} to []interface{} and never returns nil
func ToInterfaceArray(i1 interface{}) []interface{} {
	if i1 == nil {
		return []interface{}{}
	}
	switch i2 := i1.(type) {
	default:
		return []interface{}{}
	case []interface{}:
		return i2
	}
	return []interface{}{}
}

// ToInterfaceMap converts interface{} to map[string]interface{} and never returns nil
func ToInterfaceMap(i1 interface{}) map[string]interface{} {
	if i1 == nil {
		return map[string]interface{}{}
	}
	switch i2 := i1.(type) {
	case map[string]interface{}:
		return i2
	default:
		return map[string]interface{}{}
	}
	return map[string]interface{}{}
}

// ToStringArray converts interface{} to []string and never returns nil
func ToStringArray(i1 interface{}) []string {
	if i1 == nil {
		return []string{}
	}
	switch i2 := i1.(type) {
	default:
		return []string{fmt.Sprint(i2)}
	case []string:
		return i2
	case []interface{}:
		var ss []string
		for _, i3 := range i2 {
			ss = append(ss, ToString(i3))
		}
		return ss
	}
	return []string{}
}

// ToStringMap converts interface{} to map[string]string and never returns nil
func ToStringMap(i1 interface{}) map[string]string {
	if i1 == nil {
		return map[string]string{}
	}
	switch i2 := i1.(type) {
	case map[string]interface{}:
		m1 := map[string]string{}
		for k, v := range m1 {
			m1[k] = ToString(v)
		}
		return m1
	case map[string]string:
		return i2

	default:
		return map[string]string{}
	}
	return map[string]string{}
}

// ToString converts interface{} to string
func ToString(i1 interface{}) string {
	if i1 == nil {
		return ""
	}
	switch i2 := i1.(type) {
	default:
		return fmt.Sprint(i2)
	case bool:
		if i2 {
			return "true"
		} else {
			return "false"
		}
	case string:
		return i2
	case *bool:
		if i2 == nil {
			return ""
		}
		if *i2 {
			return "true"
		} else {
			return "false"
		}
	case *string:
		if i2 == nil {
			return ""
		}
		return *i2
	case *json.Number:
		return i2.String()
	case json.Number:
		return i2.String()
	}
	return ""
}

// ToInt64 converts interface{} to int64
func ToInt64(i1 interface{}) int64 {
	if i1 == nil {
		return 0
	}
	switch i2 := i1.(type) {
	default:
		i3, _ := strconv.ParseInt(ToString(i2), 10, 64)
		return i3
	case *json.Number:
		i3, _ := i2.Int64()
		return i3
	case json.Number:
		i3, _ := i2.Int64()
		return i3
	case int64:
		return i2
	case float64:
		return int64(i2)
	case float32:
		return int64(i2)
	case uint64:
		return int64(i2)
	case int:
		return int64(i2)
	case uint:
		return int64(i2)
	case bool:
		if i2 {
			return 1
		} else {
			return 0
		}
	case *bool:
		if i2 == nil {
			return 0
		}
		if *i2 {
			return 1
		} else {
			return 0
		}
	}
	return 0
}

// ToFloat64 converts interface{} to float64
func ToFloat64(i1 interface{}) float64 {
	if i1 == nil {
		return 0.0
	}
	switch i2 := i1.(type) {
	default:
		i3, _ := strconv.ParseFloat(ToString(i2), 64)
		return i3
	case *json.Number:
		i3, _ := i2.Float64()
		return i3
	case json.Number:
		i3, _ := i2.Float64()
		return i3
	case int64:
		return float64(i2)
	case float64:
		return i2
	case float32:
		return float64(i2)
	case uint64:
		return float64(i2)
	case int:
		return float64(i2)
	case uint:
		return float64(i2)
	case bool:
		if i2 {
			return 1.0
		} else {
			return 0.0
		}
	case *bool:
		if i2 == nil {
			return 0.0
		}
		if *i2 {
			return 1.0
		} else {
			return 0.0
		}
	}
	return 0.0
}

// ToFloat64 converts interface{} to float64
func ToFloat32(i1 interface{}) float32 {
	if i1 == nil {
		return 0.0
	}
	switch i2 := i1.(type) {
	default:
		i3, _ := strconv.ParseFloat(ToString(i2), 64)
		return float32(i3)
	case *json.Number:
		i3, _ := i2.Float64()
		return float32(i3)
	case json.Number:
		i3, _ := i2.Float64()
		return float32(i3)
	case int64:
		return float32(i2)
	case float64:
		return float32(i2)
	case float32:
		return i2
	case uint64:
		return float32(i2)
	case int:
		return float32(i2)
	case uint:
		return float32(i2)
	case bool:
		if i2 {
			return 1.0
		} else {
			return 0.0
		}
	case *bool:
		if i2 == nil {
			return 0.0
		}
		if *i2 {
			return 1.0
		} else {
			return 0.0
		}
	}
	return 0.0
}

// ToBool converts interface{} to bool
func ToBool(i1 interface{}) bool {
	if i1 == nil {
		return false
	}
	switch i2 := i1.(type) {
	default:
		return false
	case bool:
		return i2
	case string:
		return i2 == "true"
	case int:
		return i2 != 0
	case *bool:
		if i2 == nil {
			return false
		}
		return *i2
	case *string:
		if i2 == nil {
			return false
		}
		return *i2 == "true"
	case *int:
		if i2 == nil {
			return false
		}
		return *i2 != 0
	}
	return false
}

// CloneObject creates copy of object using GOB.
func CloneObject(a, b interface{}) {
	buff := new(bytes.Buffer)
	enc := gob.NewEncoder(buff)
	dec := gob.NewDecoder(buff)
	enc.Encode(a)
	dec.Decode(b)
}

// func ShoterRunes(s1 string, n int) string {
// 	r1 := []rune(s1)
// 	if len(r1) > n {
// 		r1 = r1[0:n]
// 	}
// 	s2 := string(r1)
// 	return s2
// }

// func SecureWebId(s1 string) (string, bool) {
// 	for _, c := range s1 {
// 		if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
// 			return "", false
// 		}
// 	}
// 	return s1, true
// }
// func DebugObject(id string, o1 interface{}) {
// 	b, _ := json.Marshal(o1)
// 	log.Println(id, string(b))
// }

// func CharsetGetEncding(charset string) encoding.Encoding {
// 	if charset == "" {
// 		return nil
// 	}
// 	charset = strings.ToLower(charset)
// 	charset = strings.Replace(charset, "-", "", -1)
// 	if charset == "utf8" {
// 		return nil
// 	}
// 	if charset == "koi8" {
// 		return charmap.KOI8R
// 	}
// 	if charset == "koi8r" {
// 		return charmap.KOI8R
// 	}
// 	if charset == "koi8u" {
// 		return charmap.KOI8U
// 	}

// 	if charset == "iso88591" {
// 		return charmap.Windows1252
// 	}

// 	if charset == "iso88592" {
// 		return charmap.ISO8859_2
// 	}
// 	if charset == "iso88593" {
// 		return charmap.ISO8859_3
// 	}
// 	if charset == "iso88594" {
// 		return charmap.ISO8859_4
// 	}
// 	if charset == "iso88595" {
// 		return charmap.ISO8859_5
// 	}
// 	if charset == "iso88596" {
// 		return charmap.ISO8859_6
// 	}
// 	if charset == "iso88597" {
// 		return charmap.ISO8859_7
// 	}
// 	if charset == "iso88598" {
// 		return charmap.ISO8859_8
// 	}
// 	// if charset == "iso88599" {return charmap.ISO8859_9}
// 	if charset == "iso885910" {
// 		return charmap.ISO8859_10
// 	}
// 	// if charset == "iso885911" {return charmap.ISO8859_11}
// 	// if charset == "iso885912" {return charmap.ISO8859_12}
// 	if charset == "iso885913" {
// 		return charmap.ISO8859_13
// 	}
// 	if charset == "iso885914" {
// 		return charmap.ISO8859_14
// 	}
// 	if charset == "iso885915" {
// 		return charmap.ISO8859_15
// 	}
// 	if charset == "iso885916" {
// 		return charmap.ISO8859_16
// 	}

// 	if charset == "windows1250" {
// 		return charmap.Windows1250
// 	}
// 	if charset == "windows1251" {
// 		return charmap.Windows1251
// 	}
// 	if charset == "windows1252" {
// 		return charmap.Windows1252
// 	}
// 	if charset == "windows1253" {
// 		return charmap.Windows1253
// 	}
// 	if charset == "windows1254" {
// 		return charmap.Windows1254
// 	}
// 	if charset == "windows1255" {
// 		return charmap.Windows1255
// 	}
// 	if charset == "windows1256" {
// 		return charmap.Windows1256
// 	}
// 	if charset == "windows1257" {
// 		return charmap.Windows1257
// 	}
// 	if charset == "windows1258" {
// 		return charmap.Windows1258
// 	}
// 	if charset == "windows874" {
// 		return charmap.Windows874
// 	}

// 	if charset == "eucjp" {
// 		return japanese.EUCJP
// 	}
// 	if charset == "iso2022jp" {
// 		return japanese.ISO2022JP
// 	}
// 	if charset == "shiftjis" {
// 		return japanese.ShiftJIS
// 	}

// 	if charset == "euckr" {
// 		return korean.EUCKR
// 	}

// 	if charset == "gb18030" {
// 		return simplifiedchinese.GB18030
// 	}
// 	if charset == "gbk" {
// 		return simplifiedchinese.GBK
// 	}
// 	if charset == "hzgb2312" {
// 		return simplifiedchinese.HZGB2312
// 	}

// 	if charset == "big5" {
// 		return traditionalchinese.Big5
// 	}

// 	return nil

// }
func TimeSince(t1 time.Time) (since string) {
	now := time.Now()
	d1 := time.Since(t1)
	if d1.Hours() > 24.0 {
		if t1.Year() == now.Year() {
			if t1.Month() == now.Month() {
				if t1.Day() == now.Day() {
					since = t1.Format("15:04")
				} else {
					since = t1.Format("02-Jan")
				}
			} else {
				since = t1.Format("02-Jan")
			}
		} else {
			since = t1.Format("02-Jan-2006")
		}
	} else {
		// since = d1.String()
		hours := int(d1.Hours())
		minutes := int(d1.Minutes() - float64(60.0*hours))
		since = fmt.Sprintf("%02d:%02d", hours, minutes)
	}
	return since
}

func MovingExpAvg(value float64, oldValue float64, ftime float64, time float64) float64 {
	alpha := 1. - math.Exp(-ftime/time)
	r := alpha*value + (1.-alpha)*oldValue
	return r
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"regexp"
	"strings"

	"github.com/kyma-project/test-infra/development/tools/pkg/common"
	"github.com/kyma-project/test-infra/development/tools/pkg/dnscollector"
	log "github.com/sirupsen/logrus"
	"golang.org/x/oauth2/google"
	compute "google.golang.org/api/compute/v1"
	dns "google.golang.org/api/dns/v1"
)

const defaultAddressRegexpList = "(remoteenvs-)?gkeint-(pr|commit)-.*,(remoteenvs-)?gke-upgrade-(pr|commit)-.*,(remoteenvs-)?(e2etest),gke-central-(pr|commit)-.*,gke-backup-(pr|commit)-.*,gkecompint-(pr|commit)-.*,gke-minio-(pr|commit)-.*,gke-minio-min-(pr|commit).*"
const minAgeInHours = 1
const minPatternLength = 5

var (
	project               = flag.String("project", "", "project id [required]")
	regions               = flag.String("regions", "", "comma-separted list of GCP regions [required]")
	dnsZone               = flag.String("dnsZone", "", "Name of the DNS Managed Zone [Required]")
	dryRun                = flag.Bool("dryRun", true, "Dry Run enabled, nothing is deleted")
	ageInHours            = flag.Int("ageInHours", 2, "IP Address age in hours. Addresses older than: now()-ageInHours are considered for removal.")
	addressNameRegexpList = flag.String("addressRegexpList", defaultAddressRegexpList, "Address name regexp list. Separate items with commas, spaces are trimmed. Matching addresses are considered for removal.")
)

func main() {
	flag.Parse()

	if *project == "" {
		fmt.Fprint(os.Stderr, "missing -project flag\n\n")
		flag.Usage()
		os.Exit(2)
	}

	if *dnsZone == "" {
		fmt.Fprint(os.Stderr, "missing -dnsZone flag\n\n")
		flag.Usage()
		os.Exit(2)
	}

	regionsList := splitPatterns(*regions)
	for _, region := range regionsList {
		if len(region) == 0 {
			fmt.Fprint(os.Stderr, "invalid region: \"\"\n\n")
			flag.Usage()
			os.Exit(2)
		}
	}

	patterns := splitPatterns(*addressNameRegexpList)
	regexpList := []*regexp.Regexp{}
	for _, pattern := range patterns {
		if len(pattern) < minPatternLength {
			fmt.Fprintf(os.Stderr, "invalid pattern: \"%s\". Value must not be shorter than %d characters.\n\n", pattern, minPatternLength)
			flag.Usage()
			os.Exit(2)
		}
		regexpList = append(regexpList, regexp.MustCompile(pattern))
	}

	if *ageInHours < minAgeInHours {
		fmt.Fprintf(os.Stderr, "invalid ageInHours. Value must not be smaller than %d\n\n", minAgeInHours)
		flag.Usage()
		os.Exit(2)
	}

	common.ShoutFirst("Running with arguments: project: \"%s\", regions: \"%s\", dnsZone: \"%s\", dryRun: %t, ageInHours: %d, addressRegexpList: %s", *project, quoteElems(regionsList), *dnsZone, *dryRun, *ageInHours, quoteElems(patterns))
	ctx := context.Background()

	computeConn, err := google.DefaultClient(ctx, compute.CloudPlatformScope)
	if err != nil {
		log.Fatalf("Could not get authenticated client: %v", err)
	}

	computeSvc, err := compute.New(computeConn)
	if err != nil {
		log.Fatalf("Could not initialize gke client for Compute API: %v", err)
	}

	dnsSvc, err := dns.New(computeConn)
	if err != nil {
		log.Fatalf("Could not initialize gke client for Compute API: %v", err)
	}

	computeAPI := &dnscollector.ComputeServiceWrapper{Context: ctx, Compute: computeSvc}
	dnsAPI := &dnscollector.DNSServiceWrapper{Context: ctx, DNS: dnsSvc}
	shouldRemoveFunc := dnscollector.DefaultIPAddressRemovalPredicate(regexpList, *ageInHours)

	cleaner := dnscollector.New(computeAPI, dnsAPI, shouldRemoveFunc)
	allSucceeded, err := cleaner.Run(*project, *dnsZone, regionsList, !(*dryRun))

	if err != nil {
		log.Fatalf("IP/DNS collector error: %v", err)
	}

	if !allSucceeded {
		log.Warn("Some operations failed.")
	}

	common.Shout("Finished")
}

func quoteElems(elems []string) string {

	res := "\"" + elems[0] + "\""
	for i := 1; i < len(elems); i++ {
		res = res + ",\"" + elems[i] + "\""
	}

	return "[" + res + "]"
}

func splitPatterns(commaSeparated string) []string {

	res := []string{}
	values := strings.Split(commaSeparated, ",")
	for _, pattern := range values {
		val := strings.Trim(pattern, " ")
		res = append(res, val)
	}

	return res
}
<commit_msg>Dns cleaner (#1959)<commit_after>package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"regexp"
	"strings"

	"github.com/kyma-project/test-infra/development/tools/pkg/common"
	"github.com/kyma-project/test-infra/development/tools/pkg/dnscollector"
	log "github.com/sirupsen/logrus"
	"golang.org/x/oauth2/google"
	compute "google.golang.org/api/compute/v1"
	dns "google.golang.org/api/dns/v1"
)

const defaultAddressRegexpList = "(remoteenvs-)?gkeint-(pr|commit|rel)-.*,(remoteenvs-)?gke-upgrade-(pr|commit|rel)-.*,(remoteenvs-)?(e2etest),gke-central-(pr|commit|rel)-.*,gke-backup-(pr|commit|rel)-.*,gkecompint-(pr|commit)-.*,gke-minio-(pr|commit)-.*,gke-minio-min-(pr|commit)-.*,service-catalog.*,apiserver.gke.*"
const minAgeInHours = 1
const minPatternLength = 5

var (
	project               = flag.String("project", "", "project id [required]")
	regions               = flag.String("regions", "", "comma-separted list of GCP regions [required]")
	dnsZone               = flag.String("dnsZone", "", "Name of the DNS Managed Zone [Required]")
	dryRun                = flag.Bool("dryRun", true, "Dry Run enabled, nothing is deleted")
	ageInHours            = flag.Int("ageInHours", 2, "IP Address age in hours. Addresses older than: now()-ageInHours are considered for removal.")
	addressNameRegexpList = flag.String("addressRegexpList", defaultAddressRegexpList, "Address name regexp list. Separate items with commas, spaces are trimmed. Matching addresses are considered for removal.")
)

func main() {
	flag.Parse()

	if *project == "" {
		fmt.Fprint(os.Stderr, "missing -project flag\n\n")
		flag.Usage()
		os.Exit(2)
	}

	if *dnsZone == "" {
		fmt.Fprint(os.Stderr, "missing -dnsZone flag\n\n")
		flag.Usage()
		os.Exit(2)
	}

	regionsList := splitPatterns(*regions)
	for _, region := range regionsList {
		if len(region) == 0 {
			fmt.Fprint(os.Stderr, "invalid region: \"\"\n\n")
			flag.Usage()
			os.Exit(2)
		}
	}

	patterns := splitPatterns(*addressNameRegexpList)
	regexpList := []*regexp.Regexp{}
	for _, pattern := range patterns {
		if len(pattern) < minPatternLength {
			fmt.Fprintf(os.Stderr, "invalid pattern: \"%s\". Value must not be shorter than %d characters.\n\n", pattern, minPatternLength)
			flag.Usage()
			os.Exit(2)
		}
		regexpList = append(regexpList, regexp.MustCompile(pattern))
	}

	if *ageInHours < minAgeInHours {
		fmt.Fprintf(os.Stderr, "invalid ageInHours. Value must not be smaller than %d\n\n", minAgeInHours)
		flag.Usage()
		os.Exit(2)
	}

	common.ShoutFirst("Running with arguments: project: \"%s\", regions: \"%s\", dnsZone: \"%s\", dryRun: %t, ageInHours: %d, addressRegexpList: %s", *project, quoteElems(regionsList), *dnsZone, *dryRun, *ageInHours, quoteElems(patterns))
	ctx := context.Background()

	computeConn, err := google.DefaultClient(ctx, compute.CloudPlatformScope)
	if err != nil {
		log.Fatalf("Could not get authenticated client: %v", err)
	}

	computeSvc, err := compute.New(computeConn)
	if err != nil {
		log.Fatalf("Could not initialize gke client for Compute API: %v", err)
	}

	dnsSvc, err := dns.New(computeConn)
	if err != nil {
		log.Fatalf("Could not initialize gke client for Compute API: %v", err)
	}

	computeAPI := &dnscollector.ComputeServiceWrapper{Context: ctx, Compute: computeSvc}
	dnsAPI := &dnscollector.DNSServiceWrapper{Context: ctx, DNS: dnsSvc}
	shouldRemoveFunc := dnscollector.DefaultIPAddressRemovalPredicate(regexpList, *ageInHours)

	cleaner := dnscollector.New(computeAPI, dnsAPI, shouldRemoveFunc)
	allSucceeded, err := cleaner.Run(*project, *dnsZone, regionsList, !(*dryRun))

	if err != nil {
		log.Fatalf("IP/DNS collector error: %v", err)
	}

	if !allSucceeded {
		log.Warn("Some operations failed.")
	}

	common.Shout("Finished")
}

func quoteElems(elems []string) string {

	res := "\"" + elems[0] + "\""
	for i := 1; i < len(elems); i++ {
		res = res + ",\"" + elems[i] + "\""
	}

	return "[" + res + "]"
}

func splitPatterns(commaSeparated string) []string {

	res := []string{}
	values := strings.Split(commaSeparated, ",")
	for _, pattern := range values {
		val := strings.Trim(pattern, " ")
		res = append(res, val)
	}

	return res
}
<|endoftext|>


<commit_before>package vault

import (
	"context"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"net/http"
	"path"
	"strings"
	"sync/atomic"
	"time"

	"code.cloudfoundry.org/lager"
	"github.com/hashicorp/go-retryablehttp"
	"github.com/hashicorp/go-rootcerts"
	vaultapi "github.com/hashicorp/vault/api"
)

// The APIClient is a SecretReader which maintains an authorized
// client using the Login and Renew functions.
type APIClient struct {
	logger lager.Logger

	apiURL       string
	namespace    string
	clientConfig ClientConfig
	tlsConfig    TLSConfig
	authConfig   AuthConfig
	queryTimeout time.Duration

	clientValue *atomic.Value

	renewable bool
}

// NewAPIClient with the associated authorization config and underlying vault client.
func NewAPIClient(logger lager.Logger, apiURL string, clientConfig ClientConfig, tlsConfig TLSConfig, authConfig AuthConfig, namespace string, queryTimeout time.Duration) (*APIClient, error) {
	ac := &APIClient{
		logger: logger,

		apiURL:       apiURL,
		namespace:    namespace,
		clientConfig: clientConfig,
		tlsConfig:    tlsConfig,
		authConfig:   authConfig,
		queryTimeout: queryTimeout,

		clientValue: &atomic.Value{},

		renewable: true,
	}

	client, err := ac.baseClient()
	if err != nil {
		return nil, err
	}

	ac.setClient(client)

	return ac, nil
}

// Read must be called after a successful login has occurred or an
// un-authorized client will be used.
func (ac *APIClient) Read(path string) (*vaultapi.Secret, error) {
	// Check if path is kv1 or kv2
	path = sanitizePath(path)
	mountPath, kv2, err := isKVv2(path, ac.client())
	if err != nil {
		return nil, err
	}

	// If the path is under a kv2 mount, add the /data/ path to the prefix
	if kv2 {
		path = addPrefixToVKVPath(path, mountPath, "data")
	}

	secret, err := ac.client().Logical().Read(path)
	if err != nil || secret == nil {
		return secret, err
	}

	// Need to discard the metadata object and pull the v2 data field up to match kv1
	if kv2 {
		if data, ok := secret.Data["data"]; ok && data != nil {
			secret.Data = data.(map[string]interface{})
			secret.LeaseDuration = -1
		} else {
			// Return a nil secret object if the secret was deleted, but not destroyed
			return nil, nil
		}
	}

	return secret, err
}

func (ac *APIClient) loginParams() map[string]interface{} {
	loginParams := make(map[string]interface{})
	for k, v := range ac.authConfig.Params {
		loginParams[k] = v
	}

	return loginParams
}

// Login the APIClient using the credentials passed at
// construction. Returns a duration after which renew must be called.
func (ac *APIClient) Login() (time.Duration, error) {
	logger := ac.logger.Session("login")

	// If we are configured with a client token return right away
	// and trigger a renewal.
	if ac.authConfig.ClientToken != "" {
		newClient, err := ac.clientWithToken(ac.authConfig.ClientToken)
		if err != nil {
			logger.Error("failed-to-create-client", err)
			return time.Second, err
		}

		ac.setClient(newClient)

		logger.Info("token-set")

		return time.Second, nil
	}

	client := ac.client()
	loginPath := path.Join("auth", ac.authConfig.Backend, "login")

	if ac.authConfig.Backend == "ldap" || ac.authConfig.Backend == "okta" {
		username, ok := ac.loginParams()["username"].(string)
		if !ok {
			err := fmt.Errorf("failed to assert username as string")
			logger.Error("failed", err)
			return time.Second, err
		}
		loginPath = path.Join("auth", ac.authConfig.Backend, "login", username)
	}

	secret, err := client.Logical().Write(loginPath, ac.loginParams())
	if err != nil {
		logger.Error("failed", err)
		return time.Second, err
	}

	logger.Info("succeeded", lager.Data{
		"token-accessor": secret.Auth.Accessor,
		"lease-duration": secret.Auth.LeaseDuration,
		"policies":       secret.Auth.Policies,
	})

	newClient, err := ac.clientWithToken(secret.Auth.ClientToken)
	if err != nil {
		logger.Error("failed-to-create-client", err)
		return time.Second, err
	}

	ac.setClient(newClient)

	return time.Duration(secret.Auth.LeaseDuration) * time.Second, nil
}

// Renew the APIClient login using the credentials passed at
// construction. Must be called after a successful login. Returns a
// duration after which renew must be called again.
func (ac *APIClient) Renew() (time.Duration, error) {
	if !ac.renewable {
		return time.Second, nil
	}

	logger := ac.logger.Session("renew")

	client := ac.client()

	secret, err := client.Auth().Token().RenewSelf(0)
	if err != nil {
		// When tests with a Vault dev server, renew is not allowed.
		if strings.Index(err.Error(), "lease is not renewable") > 0 {
			ac.renewable = false
			return time.Second, nil
		}
		logger.Error("failed", err)
		return time.Second, err
	}

	logger.Info("succeeded", lager.Data{
		"token-accessor": secret.Auth.Accessor,
		"lease-duration": secret.Auth.LeaseDuration,
		"policies":       secret.Auth.Policies,
	})

	newClient, err := ac.clientWithToken(secret.Auth.ClientToken)
	if err != nil {
		logger.Error("failed-to-create-client", err)
		return time.Second, err
	}

	ac.setClient(newClient)

	return time.Duration(secret.Auth.LeaseDuration) * time.Second, nil
}

func (ac *APIClient) client() *vaultapi.Client {
	return ac.clientValue.Load().(*vaultapi.Client)
}

func (ac *APIClient) setClient(client *vaultapi.Client) {
	ac.clientValue.Store(client)
}

func (ac *APIClient) baseClient() (*vaultapi.Client, error) {
	config := vaultapi.DefaultConfig()
	if ac.queryTimeout > 0 {
		config.Timeout = ac.queryTimeout
	}

	err := ac.configureTLS(config.HttpClient.Transport.(*http.Transport).TLSClientConfig)
	if err != nil {
		return nil, err
	}

	config.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) {
		retryable, retryErr := retryablehttp.DefaultRetryPolicy(ctx, resp, err)
		if retryErr != nil || retryable {
			return retryable, retryErr
		}
		// We also want to retry when hitting Vault rate limit.
		if resp.StatusCode == 429 {
			return true, nil
		}
		return false, nil
	}

	// Let's define retry backoff parameters explicitly
	config.MinRetryWait = time.Millisecond * 1000
	config.MaxRetryWait = time.Millisecond * 3000
	config.MaxRetries = 2

	client, err := vaultapi.NewClient(config)
	if err != nil {
		return nil, err
	}

	err = client.SetAddress(ac.apiURL)
	if err != nil {
		return nil, err
	}

	if !ac.clientConfig.DisableSRVLookup {
		client.SetSRVLookup(true)
	}

	if ac.namespace != "" {
		client.SetNamespace(ac.namespace)
	}

	return client, nil
}

func (ac *APIClient) configureTLS(config *tls.Config) error {
	if ac.tlsConfig.CACert != "" || ac.tlsConfig.CACertFile != "" || ac.tlsConfig.CAPath != "" {
		rootConfig := &rootcerts.Config{
			CAFile:        ac.tlsConfig.CACertFile,
			CAPath:        ac.tlsConfig.CAPath,
			CACertificate: []byte(ac.tlsConfig.CACert),
		}

		if err := rootcerts.ConfigureTLS(config, rootConfig); err != nil {
			return err
		}
	}

	if ac.tlsConfig.ClientCertFile != "" {
		content, err := ioutil.ReadFile(ac.tlsConfig.ClientCertFile)
		if err != nil {
			return err
		}

		ac.tlsConfig.ClientCert = string(content)
	}

	if ac.tlsConfig.ClientKeyFile != "" {
		content, err := ioutil.ReadFile(ac.tlsConfig.ClientKeyFile)
		if err != nil {
			return err
		}

		ac.tlsConfig.ClientKey = string(content)
	}

	if ac.tlsConfig.Insecure {
		config.InsecureSkipVerify = true
	}

	var clientCert tls.Certificate
	foundClientCert := false

	switch {
	case ac.tlsConfig.ClientCert != "" && ac.tlsConfig.ClientKey != "":
		var err error
		clientCert, err = tls.X509KeyPair([]byte(ac.tlsConfig.ClientCert), []byte(ac.tlsConfig.ClientKey))
		if err != nil {
			return err
		}

		foundClientCert = true
	case ac.tlsConfig.ClientCert != "" || ac.tlsConfig.ClientKey != "":
		return fmt.Errorf("both client cert and client key must be provided")
	}

	if foundClientCert {
		// We use this function to ignore the server's preferential list of
		// CAs, otherwise any CA used for the cert auth backend must be in the
		// server's CA pool
		config.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
			return &clientCert, nil
		}
	}

	if ac.tlsConfig.ServerName != "" {
		config.ServerName = ac.tlsConfig.ServerName
	}

	return nil
}

func (ac *APIClient) clientWithToken(token string) (*vaultapi.Client, error) {
	client, err := ac.baseClient()
	if err != nil {
		return nil, err
	}

	client.SetToken(token)

	return client, nil
}

func (ac *APIClient) health() (*vaultapi.HealthResponse, error) {
	client, err := ac.baseClient()
	if err != nil {
		return nil, err
	}

	healthResponse, err := client.Sys().Health()
	return healthResponse, err
}
<commit_msg>addressed review comments.<commit_after>package vault

import (
	"context"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"net/http"
	"path"
	"strings"
	"sync/atomic"
	"time"

	"code.cloudfoundry.org/lager"
	"github.com/hashicorp/go-retryablehttp"
	"github.com/hashicorp/go-rootcerts"
	vaultapi "github.com/hashicorp/vault/api"
)

// The APIClient is a SecretReader which maintains an authorized
// client using the Login and Renew functions.
type APIClient struct {
	logger lager.Logger

	apiURL       string
	namespace    string
	clientConfig ClientConfig
	tlsConfig    TLSConfig
	authConfig   AuthConfig
	queryTimeout time.Duration

	clientValue *atomic.Value

	renewable bool
}

// NewAPIClient with the associated authorization config and underlying vault client.
func NewAPIClient(logger lager.Logger, apiURL string, clientConfig ClientConfig, tlsConfig TLSConfig, authConfig AuthConfig, namespace string, queryTimeout time.Duration) (*APIClient, error) {
	ac := &APIClient{
		logger: logger,

		apiURL:       apiURL,
		namespace:    namespace,
		clientConfig: clientConfig,
		tlsConfig:    tlsConfig,
		authConfig:   authConfig,
		queryTimeout: queryTimeout,

		clientValue: &atomic.Value{},

		renewable: true,
	}

	client, err := ac.baseClient()
	if err != nil {
		return nil, err
	}

	ac.setClient(client)

	return ac, nil
}

// Read must be called after a successful login has occurred or an
// un-authorized client will be used.
func (ac *APIClient) Read(path string) (*vaultapi.Secret, error) {
	// Check if path is kv1 or kv2
	path = sanitizePath(path)
	mountPath, kv2, err := isKVv2(path, ac.client())
	if err != nil {
		return nil, err
	}

	// If the path is under a kv2 mount, add the /data/ path to the prefix
	if kv2 {
		path = addPrefixToVKVPath(path, mountPath, "data")
	}

	secret, err := ac.client().Logical().Read(path)
	if err != nil || secret == nil {
		return secret, err
	}

	// Need to discard the metadata object and pull the v2 data field up to match kv1
	if kv2 {
		if data, ok := secret.Data["data"]; ok && data != nil {
			secret.Data = data.(map[string]interface{})
			secret.LeaseDuration = -1
		} else {
			// Return a nil secret object if the secret was deleted, but not destroyed
			return nil, nil
		}
	}

	return secret, err
}

func (ac *APIClient) loginParams() map[string]interface{} {
	loginParams := make(map[string]interface{})
	for k, v := range ac.authConfig.Params {
		loginParams[k] = v
	}

	return loginParams
}

// Login the APIClient using the credentials passed at
// construction. Returns a duration after which renew must be called.
func (ac *APIClient) Login() (time.Duration, error) {
	logger := ac.logger.Session("login")

	// If we are configured with a client token return right away
	// and trigger a renewal.
	if ac.authConfig.ClientToken != "" {
		newClient, err := ac.clientWithToken(ac.authConfig.ClientToken)
		if err != nil {
			logger.Error("failed-to-create-client", err)
			return time.Second, err
		}

		ac.setClient(newClient)

		logger.Info("token-set")

		return time.Second, nil
	}

	client := ac.client()
	loginPath := path.Join("auth", ac.authConfig.Backend, "login")

	if ac.authConfig.Backend == "ldap" || ac.authConfig.Backend == "okta" {
		username, ok := ac.loginParams()["username"].(string)
		if !ok {
			err := fmt.Errorf("failed to assert username as string")
			logger.Error("failed", err)
			return time.Second, err
		}
		loginPath = path.Join("auth", ac.authConfig.Backend, "login", username)
	}

	secret, err := client.Logical().Write(loginPath, ac.loginParams())
	if err != nil {
		logger.Error("failed", err)
		return time.Second, err
	}

	logger.Info("succeeded", lager.Data{
		"token-accessor": secret.Auth.Accessor,
		"lease-duration": secret.Auth.LeaseDuration,
		"policies":       secret.Auth.Policies,
	})

	newClient, err := ac.clientWithToken(secret.Auth.ClientToken)
	if err != nil {
		logger.Error("failed-to-create-client", err)
		return time.Second, err
	}

	ac.setClient(newClient)

	return time.Duration(secret.Auth.LeaseDuration) * time.Second, nil
}

// Renew the APIClient login using the credentials passed at
// construction. Must be called after a successful login. Returns a
// duration after which renew must be called again.
func (ac *APIClient) Renew() (time.Duration, error) {
	if !ac.renewable {
		return time.Second, nil
	}

	logger := ac.logger.Session("renew")

	client := ac.client()

	secret, err := client.Auth().Token().RenewSelf(0)
	if err != nil {
		// When tests with a Vault dev server, renew is not allowed.
		if strings.Index(err.Error(), "lease is not renewable") > 0 {
			ac.renewable = false
			return time.Second, nil
		}
		logger.Error("failed", err)
		return time.Second, err
	}

	logger.Info("succeeded", lager.Data{
		"token-accessor": secret.Auth.Accessor,
		"lease-duration": secret.Auth.LeaseDuration,
		"policies":       secret.Auth.Policies,
	})

	newClient, err := ac.clientWithToken(secret.Auth.ClientToken)
	if err != nil {
		logger.Error("failed-to-create-client", err)
		return time.Second, err
	}

	ac.setClient(newClient)

	return time.Duration(secret.Auth.LeaseDuration) * time.Second, nil
}

func (ac *APIClient) client() *vaultapi.Client {
	return ac.clientValue.Load().(*vaultapi.Client)
}

func (ac *APIClient) setClient(client *vaultapi.Client) {
	ac.clientValue.Store(client)
}

func (ac *APIClient) baseClient() (*vaultapi.Client, error) {
	config := vaultapi.DefaultConfig()
	if ac.queryTimeout > 0 {
		config.Timeout = ac.queryTimeout
	}

	err := ac.configureTLS(config.HttpClient.Transport.(*http.Transport).TLSClientConfig)
	if err != nil {
		return nil, err
	}

	config.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) {
		retryable, retryErr := retryablehttp.DefaultRetryPolicy(ctx, resp, err)
		if retryErr != nil || retryable {
			return retryable, retryErr
		}

		// We also want to retry when hitting Vault rate limit.
		//
		// Per https://www.vaultproject.io/api-docs, 429 is default return code
		// for health status of standby nodes.
		//
		// And per https://github.com/hashicorp/vault/blob/main/api/response.go#L32,
		// only when request path is not "/v1/sys/health", 429 is treated as quota
		// limit reached.
		if resp.StatusCode == http.StatusTooManyRequests && resp.Request.URL.Path != "/v1/sys/health" {
			return true, nil
		}

		return false, nil
	}

	// Let's define retry backoff parameters explicitly
	config.MinRetryWait = time.Millisecond * 1000
	config.MaxRetryWait = time.Millisecond * 3000
	config.MaxRetries = 2

	client, err := vaultapi.NewClient(config)
	if err != nil {
		return nil, err
	}

	err = client.SetAddress(ac.apiURL)
	if err != nil {
		return nil, err
	}

	if !ac.clientConfig.DisableSRVLookup {
		client.SetSRVLookup(true)
	}

	if ac.namespace != "" {
		client.SetNamespace(ac.namespace)
	}

	return client, nil
}

func (ac *APIClient) configureTLS(config *tls.Config) error {
	if ac.tlsConfig.CACert != "" || ac.tlsConfig.CACertFile != "" || ac.tlsConfig.CAPath != "" {
		rootConfig := &rootcerts.Config{
			CAFile:        ac.tlsConfig.CACertFile,
			CAPath:        ac.tlsConfig.CAPath,
			CACertificate: []byte(ac.tlsConfig.CACert),
		}

		if err := rootcerts.ConfigureTLS(config, rootConfig); err != nil {
			return err
		}
	}

	if ac.tlsConfig.ClientCertFile != "" {
		content, err := ioutil.ReadFile(ac.tlsConfig.ClientCertFile)
		if err != nil {
			return err
		}

		ac.tlsConfig.ClientCert = string(content)
	}

	if ac.tlsConfig.ClientKeyFile != "" {
		content, err := ioutil.ReadFile(ac.tlsConfig.ClientKeyFile)
		if err != nil {
			return err
		}

		ac.tlsConfig.ClientKey = string(content)
	}

	if ac.tlsConfig.Insecure {
		config.InsecureSkipVerify = true
	}

	var clientCert tls.Certificate
	foundClientCert := false

	switch {
	case ac.tlsConfig.ClientCert != "" && ac.tlsConfig.ClientKey != "":
		var err error
		clientCert, err = tls.X509KeyPair([]byte(ac.tlsConfig.ClientCert), []byte(ac.tlsConfig.ClientKey))
		if err != nil {
			return err
		}

		foundClientCert = true
	case ac.tlsConfig.ClientCert != "" || ac.tlsConfig.ClientKey != "":
		return fmt.Errorf("both client cert and client key must be provided")
	}

	if foundClientCert {
		// We use this function to ignore the server's preferential list of
		// CAs, otherwise any CA used for the cert auth backend must be in the
		// server's CA pool
		config.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
			return &clientCert, nil
		}
	}

	if ac.tlsConfig.ServerName != "" {
		config.ServerName = ac.tlsConfig.ServerName
	}

	return nil
}

func (ac *APIClient) clientWithToken(token string) (*vaultapi.Client, error) {
	client, err := ac.baseClient()
	if err != nil {
		return nil, err
	}

	client.SetToken(token)

	return client, nil
}

func (ac *APIClient) health() (*vaultapi.HealthResponse, error) {
	client, err := ac.baseClient()
	if err != nil {
		return nil, err
	}

	healthResponse, err := client.Sys().Health()
	return healthResponse, err
}
<|endoftext|>


<commit_before>package authentication

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"github.com/Azure/go-autorest/autorest/azure"
)

var environmentTranslationMap = map[string]azure.Environment{
	"public":       azure.PublicCloud,
	"usgovernment": azure.USGovernmentCloud,
	"german":       azure.GermanCloud,
	"china":        azure.ChinaCloud,
}

type Environment struct {
	Portal                  string         `json:"portal"`
	Authentication          Authentication `json:"authentication"`
	Media                   string         `json:"media"`
	GraphAudience           string         `json:"graphAudience"`
	Graph                   string         `json:"graph"`
	Name                    string         `json:"name"`
	Suffixes                Suffixes       `json:"suffixes"`
	Batch                   string         `json:"batch"`
	ResourceManager         string         `json:"resourceManager"`
	VmImageAliasDoc         string         `json:"vmImageAliasDoc"`
	ActiveDirectoryDataLake string         `json:"activeDirectoryDataLake"`
	SqlManagement           string         `json:"sqlManagement"`
	Gallery                 string         `json:"gallery"`
}

type Authentication struct {
	LoginEndpoint    string   `json:"loginEndpoint"`
	Audiences        []string `json:"audiences"`
	Tenant           string   `json:"tenant"`
	IdentityProvider string   `json:"identityProvider"`
}

type Suffixes struct {
	AzureDataLakeStoreFileSystem        string `json:"azureDataLakeStoreFileSystem"`
	AcrLoginServer                      string `json:"acrLoginServer"`
	SqlServerHostname                   string `json:"sqlServerHostname"`
	AzureDataLakeAnalyticsCatalogAndJob string `json:"azureDataLakeAnalyticsCatalogAndJob"`
	KeyVaultDns                         string `json:"keyVaultDns"`
	Storage                             string `json:"storage"`
	AzureFrontDoorEndpointSuffix        string `json:"azureFrontDoorEndpointSuffix"`
}

// DetermineEnvironment determines what the Environment name is within
// the Azure SDK for Go and then returns the association environment, if it exists.
func DetermineEnvironment(name string) (*azure.Environment, error) {
	// detect cloud from environment
	env, envErr := azure.EnvironmentFromName(name)

	if envErr != nil {
		// try again with wrapped value to support readable values like german instead of AZUREGERMANCLOUD
		wrapped := fmt.Sprintf("AZURE%sCLOUD", name)
		env, envErr = azure.EnvironmentFromName(wrapped)
		if envErr != nil {
			return nil, fmt.Errorf("An Azure Environment with name %q was not found: %+v", name, envErr)
		}
	}

	return &env, nil
}

// LoadEnvironmentFromUrl attempts to load the specified environment from the endpoint.
// if the endpoint is an empty string, or an environment can't be
// found at the endpoint url then an error is returned
func LoadEnvironmentFromUrl(endpoint string) (*azure.Environment, error) {
	if endpoint == "" {
		return nil, fmt.Errorf("Endpoint was not set!")
	}

	env, err := azure.EnvironmentFromURL(endpoint)
	if err != nil {
		return nil, fmt.Errorf("Error retrieving Environment from Endpoint %q: %+v", endpoint, err)
	}

	return &env, nil
}

func normalizeEnvironmentName(input string) string {
	// Environment is stored as `Azure{Environment}Cloud`
	output := strings.ToLower(input)
	output = strings.TrimPrefix(output, "azure")
	output = strings.TrimSuffix(output, "cloud")

	// however Azure Public is `AzureCloud` in the CLI Profile and not `AzurePublicCloud`.
	if output == "" {
		return "public"
	}
	return output
}

// AzureEnvironmentByName returns a specific Azure Environment from the specified endpoint
func AzureEnvironmentByNameFromEndpoint(ctx context.Context, endpoint string, environmentName string) (*azure.Environment, error) {
	if env, ok := environmentTranslationMap[strings.ToLower(environmentName)]; ok {
		return &env, nil
	}

	environments, err := getSupportedEnvironments(ctx, endpoint)
	if err != nil {
		return nil, err
	}

	// while the array contains values
	for _, env := range environments {
		if strings.EqualFold(env.Name, environmentName) {
			aEnv, err := buildAzureEnvironment(env)
			if err != nil {
				return nil, err
			}
			return aEnv, nil
		}
	}

	return nil, fmt.Errorf("unable to find environment %q from endpoint %q", environmentName, endpoint)
}

// IsEnvironmentAzureStack returns whether a specific Azure Environment is an Azure Stack environment
func IsEnvironmentAzureStack(ctx context.Context, endpoint string, environmentName string) (bool, error) {
	if _, ok := environmentTranslationMap[strings.ToLower(environmentName)]; ok {
		return false, nil
	}

	environments, err := getSupportedEnvironments(ctx, endpoint)
	if err != nil {
		return false, err
	}

	// while the array contains values
	for _, env := range environments {
		if err != nil {
			return false, fmt.Errorf("unable to decode environment from %q response: %+v", endpoint, err)
		}
		if strings.EqualFold(env.Name, environmentName) {
			if strings.EqualFold(env.Authentication.IdentityProvider, "AAD") || strings.EqualFold(env.Authentication.Tenant, "common") {
				return true, nil
			}
			return false, nil
		}
	}

	return false, fmt.Errorf("unable to find environment %q from endpoint %q", environmentName, endpoint)
}

func getSupportedEnvironments(ctx context.Context, endpoint string) ([]Environment, error) {
	uri := fmt.Sprintf("https://%s/metadata/endpoints?api-version=2019-05-01", endpoint)
	client := http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
		},
	}
	req, err := http.NewRequestWithContext(ctx, "GET", uri, nil)
	if err != nil {
		return nil, err
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("retrieving environments from Azure MetaData service: %+v", err)
	}

	var environments []Environment
	if err := json.NewDecoder(resp.Body).Decode(&environments); err != nil {
		return nil, err
	}

	return environments, nil
}

func buildAzureEnvironment(env Environment) (*azure.Environment, error) {
	aEnv := &azure.Environment{
		Name:                       env.Name,
		ResourceManagerEndpoint:    env.ResourceManager,
		StorageEndpointSuffix:      env.Suffixes.Storage,
		ActiveDirectoryEndpoint:    env.Authentication.LoginEndpoint,
		GraphEndpoint:              env.Graph,
		KeyVaultEndpoint:           fmt.Sprintf("https://%s/", env.Suffixes.KeyVaultDns),
		GalleryEndpoint:            env.Gallery,
		BatchManagementEndpoint:    env.Batch,
		SQLDatabaseDNSSuffix:       env.Suffixes.SqlServerHostname,
		KeyVaultDNSSuffix:          env.Suffixes.KeyVaultDns,
		ContainerRegistryDNSSuffix: env.Suffixes.AcrLoginServer,
		ResourceIdentifiers: azure.ResourceIdentifier{
			// This isn't returned from the metadata url and is universal across all environments
			Storage:  "https://storage.azure.com/",
			Graph:    env.Graph,
			KeyVault: fmt.Sprintf("https://%s/", env.Suffixes.KeyVaultDns),
			Datalake: env.ActiveDirectoryDataLake,
			Batch:    env.Batch,
		},
	}

	if len(env.Authentication.Audiences) > 0 {
		aEnv.TokenAudience = env.Authentication.Audiences[0]
	} else {
		return nil, fmt.Errorf("unable to find token audience for environment %q", env.Name)
	}

	return aEnv, nil
}
<commit_msg>Update environment.go<commit_after>package authentication

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"github.com/Azure/go-autorest/autorest/azure"
)

var environmentTranslationMap = map[string]azure.Environment{
	"public":       azure.PublicCloud,
	"usgovernment": azure.USGovernmentCloud,
	"german":       azure.GermanCloud,
	"china":        azure.ChinaCloud,
}

type Environment struct {
	Portal                  string         `json:"portal"`
	Authentication          Authentication `json:"authentication"`
	Media                   string         `json:"media"`
	GraphAudience           string         `json:"graphAudience"`
	Graph                   string         `json:"graph"`
	Name                    string         `json:"name"`
	Suffixes                Suffixes       `json:"suffixes"`
	Batch                   string         `json:"batch"`
	ResourceManager         string         `json:"resourceManager"`
	VmImageAliasDoc         string         `json:"vmImageAliasDoc"`
	ActiveDirectoryDataLake string         `json:"activeDirectoryDataLake"`
	SqlManagement           string         `json:"sqlManagement"`
	Gallery                 string         `json:"gallery"`
}

type Authentication struct {
	LoginEndpoint    string   `json:"loginEndpoint"`
	Audiences        []string `json:"audiences"`
	Tenant           string   `json:"tenant"`
	IdentityProvider string   `json:"identityProvider"`
}

type Suffixes struct {
	AzureDataLakeStoreFileSystem        string `json:"azureDataLakeStoreFileSystem"`
	AcrLoginServer                      string `json:"acrLoginServer"`
	SqlServerHostname                   string `json:"sqlServerHostname"`
	AzureDataLakeAnalyticsCatalogAndJob string `json:"azureDataLakeAnalyticsCatalogAndJob"`
	KeyVaultDns                         string `json:"keyVaultDns"`
	Storage                             string `json:"storage"`
	AzureFrontDoorEndpointSuffix        string `json:"azureFrontDoorEndpointSuffix"`
}

// DetermineEnvironment determines what the Environment name is within
// the Azure SDK for Go and then returns the association environment, if it exists.
func DetermineEnvironment(name string) (*azure.Environment, error) {
	// detect cloud from environment
	env, envErr := azure.EnvironmentFromName(name)

	if envErr != nil {
		// try again with wrapped value to support readable values like german instead of AZUREGERMANCLOUD
		wrapped := fmt.Sprintf("AZURE%sCLOUD", name)
		env, envErr = azure.EnvironmentFromName(wrapped)
		if envErr != nil {
			return nil, fmt.Errorf("An Azure Environment with name %q was not found: %+v", name, envErr)
		}
	}

	return &env, nil
}

// LoadEnvironmentFromUrl attempts to load the specified environment from the endpoint.
// if the endpoint is an empty string, or an environment can't be
// found at the endpoint url then an error is returned
func LoadEnvironmentFromUrl(endpoint string) (*azure.Environment, error) {
	if endpoint == "" {
		return nil, fmt.Errorf("Endpoint was not set!")
	}

	env, err := azure.EnvironmentFromURL(endpoint)
	if err != nil {
		return nil, fmt.Errorf("Error retrieving Environment from Endpoint %q: %+v", endpoint, err)
	}

	return &env, nil
}

func normalizeEnvironmentName(input string) string {
	// Environment is stored as `Azure{Environment}Cloud`
	output := strings.ToLower(input)
	output = strings.TrimPrefix(output, "azure")
	output = strings.TrimSuffix(output, "cloud")

	// however Azure Public is `AzureCloud` in the CLI Profile and not `AzurePublicCloud`.
	if output == "" {
		return "public"
	}
	return output
}

// AzureEnvironmentByName returns a specific Azure Environment from the specified endpoint
func AzureEnvironmentByNameFromEndpoint(ctx context.Context, endpoint string, environmentName string) (*azure.Environment, error) {
	if env, ok := environmentTranslationMap[strings.ToLower(environmentName)]; ok {
		return &env, nil
	}

	environments, err := getSupportedEnvironments(ctx, endpoint)
	if err != nil {
		return nil, err
	}

	// while the array contains values
	for _, env := range environments {
		if strings.EqualFold(env.Name, environmentName) {
			aEnv, err := buildAzureEnvironment(env)
			if err != nil {
				return nil, err
			}
			return aEnv, nil
		}
	}

	return nil, fmt.Errorf("unable to find environment %q from endpoint %q", environmentName, endpoint)
}

// IsEnvironmentAzureStack returns whether a specific Azure Environment is an Azure Stack environment
func IsEnvironmentAzureStack(ctx context.Context, endpoint string, environmentName string) (bool, error) {
	if _, ok := environmentTranslationMap[strings.ToLower(environmentName)]; ok {
		return false, nil
	}

	environments, err := getSupportedEnvironments(ctx, endpoint)
	if err != nil {
		return false, err
	}

	// while the array contains values
	for _, env := range environments {
		if err != nil {
			return false, fmt.Errorf("unable to decode environment from %q response: %+v", endpoint, err)
		}
		if strings.EqualFold(env.Name, environmentName) {
			if !strings.EqualFold(env.Authentication.IdentityProvider, "AAD") || !strings.EqualFold(env.Authentication.Tenant, "common") {
				return true, nil
			}
			return false, nil
		}
	}

	return false, fmt.Errorf("unable to find environment %q from endpoint %q", environmentName, endpoint)
}

func getSupportedEnvironments(ctx context.Context, endpoint string) ([]Environment, error) {
	uri := fmt.Sprintf("https://%s/metadata/endpoints?api-version=2019-05-01", endpoint)
	client := http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
		},
	}
	req, err := http.NewRequestWithContext(ctx, "GET", uri, nil)
	if err != nil {
		return nil, err
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("retrieving environments from Azure MetaData service: %+v", err)
	}

	var environments []Environment
	if err := json.NewDecoder(resp.Body).Decode(&environments); err != nil {
		return nil, err
	}

	return environments, nil
}

func buildAzureEnvironment(env Environment) (*azure.Environment, error) {
	aEnv := &azure.Environment{
		Name:                       env.Name,
		ResourceManagerEndpoint:    env.ResourceManager,
		StorageEndpointSuffix:      env.Suffixes.Storage,
		ActiveDirectoryEndpoint:    env.Authentication.LoginEndpoint,
		GraphEndpoint:              env.Graph,
		KeyVaultEndpoint:           fmt.Sprintf("https://%s/", env.Suffixes.KeyVaultDns),
		GalleryEndpoint:            env.Gallery,
		BatchManagementEndpoint:    env.Batch,
		SQLDatabaseDNSSuffix:       env.Suffixes.SqlServerHostname,
		KeyVaultDNSSuffix:          env.Suffixes.KeyVaultDns,
		ContainerRegistryDNSSuffix: env.Suffixes.AcrLoginServer,
		ResourceIdentifiers: azure.ResourceIdentifier{
			// This isn't returned from the metadata url and is universal across all environments
			Storage:  "https://storage.azure.com/",
			Graph:    env.Graph,
			KeyVault: fmt.Sprintf("https://%s/", env.Suffixes.KeyVaultDns),
			Datalake: env.ActiveDirectoryDataLake,
			Batch:    env.Batch,
		},
	}

	if len(env.Authentication.Audiences) > 0 {
		aEnv.TokenAudience = env.Authentication.Audiences[0]
	} else {
		return nil, fmt.Errorf("unable to find token audience for environment %q", env.Name)
	}

	return aEnv, nil
}
<|endoftext|>


<commit_before>/*
 * MumbleDJ
 * By Matthieu Grieger
 * commands.go
 * Copyright (c) 2014 Matthieu Grieger (MIT License)
 */

package main

import (
	"errors"
	"fmt"
	"github.com/kennygrant/sanitize"
	"github.com/layeh/gumble/gumble"
	"os"
	"regexp"
	"strconv"
	"strings"
)

// Called on text message event. Checks the message for a command string, and processes it accordingly if
// it contains a command.
func parseCommand(user *gumble.User, username, command string) {
	var com, argument string
	if strings.Contains(command, " ") {
		sanitizedCommand := sanitize.HTML(command)
		parsedCommand := strings.Split(sanitizedCommand, " ")
		com, argument = parsedCommand[0], parsedCommand[1]
	} else {
		com = command
		argument = ""
	}

	switch com {
	// Add command
	case dj.conf.Aliases.AddAlias:
		if dj.HasPermission(username, dj.conf.Permissions.AdminAdd) {
			if argument == "" {
				user.Send(NO_ARGUMENT_MSG)
			} else {
				if songTitle, err := add(username, argument); err == nil {
					dj.client.Self().Channel().Send(fmt.Sprintf(SONG_ADDED_HTML, username, songTitle), false)
					if dj.queue.Len() == 1 && !dj.audioStream.IsPlaying() {
						dj.currentSong = dj.queue.NextSong()
						if err := dj.currentSong.Download(); err == nil {
							dj.currentSong.Play()
						} else {
							user.Send(AUDIO_FAIL_MSG)
							dj.currentSong.Delete()
						}
					}
				} else {
					user.Send(INVALID_URL_MSG)
				}
			}
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	// Skip command
	case dj.conf.Aliases.SkipAlias:
		if dj.HasPermission(username, dj.conf.Permissions.AdminSkip) {
			if err := skip(username, false); err == nil {
				dj.client.Self().Channel().Send(fmt.Sprintf(SKIP_ADDED_HTML, username), false)
			}
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	// Forceskip command
	case dj.conf.Aliases.AdminSkipAlias:
		if dj.HasPermission(username, true) {
			if err := skip(username, true); err == nil {
				dj.client.Self().Channel().Send(ADMIN_SONG_SKIP_MSG, false)
			}
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	// Volume command
	case dj.conf.Aliases.VolumeAlias:
		if dj.HasPermission(username, dj.conf.Permissions.AdminVolume) {
			if argument == "" {
				dj.client.Self().Channel().Send(fmt.Sprintf(CUR_VOLUME_HTML, dj.audioStream.Volume()), false)
			} else {
				if err := volume(username, argument); err == nil {
					dj.client.Self().Channel().Send(fmt.Sprintf(VOLUME_SUCCESS_HTML, username, argument), false)
				} else {
					user.Send(fmt.Sprintf(NOT_IN_VOLUME_RANGE_MSG, dj.conf.Volume.LowestVolume, dj.conf.Volume.HighestVolume))
				}
			}
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	// Move command
	case dj.conf.Aliases.MoveAlias:
		if dj.HasPermission(username, dj.conf.Permissions.AdminMove) {
			if argument == "" {
				user.Send(NO_ARGUMENT_MSG)
			} else {
				if err := move(argument); err == nil {
					fmt.Printf("%s has been moved to %s.", dj.client.Self().Name(), argument)
				} else {
					user.Send(CHANNEL_DOES_NOT_EXIST_MSG)
				}
			}
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	// Reload command
	case dj.conf.Aliases.ReloadAlias:
		if dj.HasPermission(username, dj.conf.Permissions.AdminReload) {
			err := loadConfiguration()
			if err == nil {
				user.Send(CONFIG_RELOAD_SUCCESS_MSG)
			} else {
				panic(err)
			}
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	// Kill command
	case dj.conf.Aliases.KillAlias:
		if dj.HasPermission(username, dj.conf.Permissions.AdminKill) {
			if err := kill(); err == nil {
				fmt.Println("Kill successful. Goodbye!")
				os.Exit(0)
			} else {
				user.Send(KILL_ERROR_MSG)
			}
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	default:
		user.Send(COMMAND_DOESNT_EXIST_MSG)
	}
}

// Performs add functionality. Checks input URL for YouTube format, and adds
// the URL to the queue if the format matches.
func add(user, url string) (string, error) {
	youtubePatterns := []string{
		`https?:\/\/www\.youtube\.com\/watch\?v=([\w-]+)`,
		`https?:\/\/youtube\.com\/watch\?v=([\w-]+)`,
		`https?:\/\/youtu.be\/([\w-]+)`,
		`https?:\/\/youtube.com\/v\/([\w-]+)`,
		`https?:\/\/www.youtube.com\/v\/([\w-]+)`,
	}
	matchFound := false
	shortUrl := ""

	for _, pattern := range youtubePatterns {
		if re, err := regexp.Compile(pattern); err == nil {
			if re.MatchString(url) {
				matchFound = true
				shortUrl = re.FindStringSubmatch(url)[1]
				break
			}
		}
	}

	if matchFound {
		newSong := NewSong(user, shortUrl)
		if err := dj.queue.AddSong(newSong); err == nil {
			return newSong.title, nil
		} else {
			return "", errors.New("Could not add the Song to the queue.")
		}
	} else {
		return "", errors.New("The URL provided did not match a YouTube URL.")
	}
}

// Performs skip functionality. Adds a skip to the skippers slice for the current song, and then
// evaluates if a skip should be performed. Both skip and forceskip are implemented here.
func skip(user string, admin bool) error {
	if err := dj.currentSong.AddSkip(user); err == nil {
		if dj.currentSong.SkipReached(len(dj.client.Self().Channel().Users())) || admin {
			if err := dj.audioStream.Stop(); err == nil {
				dj.OnSongFinished()
				return nil
			} else {
				return errors.New("An error occurred while stopping the current song.")
			}
		} else {
			return nil
		}
	} else {
		return errors.New("An error occurred while adding a skip to the current song.")
	}
}

// Performs volume functionality. Checks input value against LowestVolume and HighestVolume from
// config to determine if the volume should be applied. If in the correct range, the new volume
// is applied and is immediately in effect.
func volume(user, value string) error {
	if parsedVolume, err := strconv.ParseFloat(value, 32); err == nil {
		newVolume := float32(parsedVolume)
		if newVolume >= dj.conf.Volume.LowestVolume && newVolume <= dj.conf.Volume.HighestVolume {
			dj.audioStream.SetVolume(newVolume)
			return nil
		} else {
			return errors.New("The volume supplied was not in the allowed range.")
		}
	} else {
		return errors.New("An error occurred while parsing the volume string.")
	}
}

// Performs move functionality. Determines if the supplied channel is valid and moves the bot
// to the channel if it is.
func move(channel string) error {
	if dj.client.Channels().Find(channel) != nil {
		dj.client.Self().Move(dj.client.Channels().Find(channel))
		return nil
	} else {
		return errors.New("The channel provided does not exist.")
	}
}

// Performs kill functionality. First cleans the ~/.mumbledj/songs directory to get rid of any
// excess m4a files. The bot then safely disconnects from the server.
func kill() error {
	songsDir := fmt.Sprintf("%s/.mumbledj/songs", dj.homeDir)
	if err := os.RemoveAll(songsDir); err != nil {
		return errors.New("An error occurred while deleting the audio files.")
	} else {
		if err := os.Mkdir(songsDir, 0777); err != nil {
			return errors.New("An error occurred while recreating the songs directory.")
		}
	}
	if err := dj.client.Disconnect(); err == nil {
		return nil
	} else {
		return errors.New("An error occurred while disconnecting from the server.")
	}
}
<commit_msg>Cleaned and relocated some code<commit_after>/*
 * MumbleDJ
 * By Matthieu Grieger
 * commands.go
 * Copyright (c) 2014 Matthieu Grieger (MIT License)
 */

package main

import (
	"errors"
	"fmt"
	"github.com/kennygrant/sanitize"
	"github.com/layeh/gumble/gumble"
	"os"
	"regexp"
	"strconv"
	"strings"
)

// Called on text message event. Checks the message for a command string, and processes it accordingly if
// it contains a command.
func parseCommand(user *gumble.User, username, command string) {
	var com, argument string
	if strings.Contains(command, " ") {
		sanitizedCommand := sanitize.HTML(command)
		parsedCommand := strings.Split(sanitizedCommand, " ")
		com, argument = parsedCommand[0], parsedCommand[1]
	} else {
		com = command
		argument = ""
	}

	switch com {
	// Add command
	case dj.conf.Aliases.AddAlias:
		if dj.HasPermission(username, dj.conf.Permissions.AdminAdd) {
			add(user, username, argument)
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	// Skip command
	case dj.conf.Aliases.SkipAlias:
		if dj.HasPermission(username, dj.conf.Permissions.AdminSkip) {
			skip(username, false)
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	// Forceskip command
	case dj.conf.Aliases.AdminSkipAlias:
		if dj.HasPermission(username, true) {
			skip(username, true)
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	// Volume command
	case dj.conf.Aliases.VolumeAlias:
		if dj.HasPermission(username, dj.conf.Permissions.AdminVolume) {
			volume(user, username, argument)
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	// Move command
	case dj.conf.Aliases.MoveAlias:
		if dj.HasPermission(username, dj.conf.Permissions.AdminMove) {
			move(user, argument)
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	// Reload command
	case dj.conf.Aliases.ReloadAlias:
		if dj.HasPermission(username, dj.conf.Permissions.AdminReload) {
			reload(user)
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	// Kill command
	case dj.conf.Aliases.KillAlias:
		if dj.HasPermission(username, dj.conf.Permissions.AdminKill) {
			kill()
		} else {
			user.Send(NO_PERMISSION_MSG)
		}
	default:
		user.Send(COMMAND_DOESNT_EXIST_MSG)
	}
}

// Performs add functionality. Checks input URL for YouTube format, and adds
// the URL to the queue if the format matches.
func add(user *gumble.User, username, url string) {
	if url == "" {
		user.Send(NO_ARGUMENT_MSG)
	} else {
		youtubePatterns := []string{
			`https?:\/\/www\.youtube\.com\/watch\?v=([\w-]+)`,
			`https?:\/\/youtube\.com\/watch\?v=([\w-]+)`,
			`https?:\/\/youtu.be\/([\w-]+)`,
			`https?:\/\/youtube.com\/v\/([\w-]+)`,
			`https?:\/\/www.youtube.com\/v\/([\w-]+)`,
		}
		matchFound := false
		shortUrl := ""

		for _, pattern := range youtubePatterns {
			if re, err := regexp.Compile(pattern); err == nil {
				if re.MatchString(url) {
					matchFound = true
					shortUrl = re.FindStringSubmatch(url)[1]
					break
				}
			}
		}

		if matchFound {
			newSong := NewSong(username, shortUrl)
			if err := dj.queue.AddSong(newSong); err == nil {
				dj.client.Self().Channel().Send(fmt.Sprintf(SONG_ADDED_HTML, username, newSong.title), false)
				if dj.queue.Len() == 1 && !dj.audioStream.IsPlaying() {
					dj.currentSong = dj.queue.NextSong()
					if err := dj.currentSong.Download(); err == nil {
						dj.currentSong.Play()
					} else {
						user.Send(AUDIO_FAIL_MSG)
						dj.currentSong.Delete()
					}
				}
			} else {
				panic(errors.New("Could not add the Song to the queue."))
			}
		} else {
			user.Send(INVALID_URL_MSG)
		}
	}
}

// Performs skip functionality. Adds a skip to the skippers slice for the current song, and then
// evaluates if a skip should be performed. Both skip and forceskip are implemented here.
func skip(user string, admin bool) {
	if err := dj.currentSong.AddSkip(user); err == nil {
		if admin {
			dj.client.Self().Channel().Send(ADMIN_SONG_SKIP_MSG, false)
		} else {
			dj.client.Self().Channel().Send(fmt.Sprintf(SKIP_ADDED_HTML, user), false)
		}
		if dj.currentSong.SkipReached(len(dj.client.Self().Channel().Users())) || admin {
			dj.client.Self().Channel().Send(SONG_SKIPPED_HTML, false)
			if err := dj.audioStream.Stop(); err == nil {
				dj.OnSongFinished()
			} else {
				panic(errors.New("An error occurred while stopping the current song."))
			}
		}
	} else {
		panic(errors.New("An error occurred while adding a skip to the current song."))
	}
}

// Performs volume functionality. Checks input value against LowestVolume and HighestVolume from
// config to determine if the volume should be applied. If in the correct range, the new volume
// is applied and is immediately in effect.
func volume(user *gumble.User, username, value string) {
	if value == "" {
		dj.client.Self().Channel().Send(fmt.Sprintf(CUR_VOLUME_HTML, dj.audioStream.Volume()), false)
	} else {
		if parsedVolume, err := strconv.ParseFloat(value, 32); err == nil {
			newVolume := float32(parsedVolume)
			if newVolume >= dj.conf.Volume.LowestVolume && newVolume <= dj.conf.Volume.HighestVolume {
				dj.audioStream.SetVolume(newVolume)
				dj.client.Self().Channel().Send(fmt.Sprintf(VOLUME_SUCCESS_HTML, username, dj.audioStream.Volume()), false)
			} else {
				user.Send(fmt.Sprintf(NOT_IN_VOLUME_RANGE_MSG, dj.conf.Volume.LowestVolume, dj.conf.Volume.HighestVolume))
			}
		} else {
			user.Send(fmt.Sprintf(NOT_IN_VOLUME_RANGE_MSG, dj.conf.Volume.LowestVolume, dj.conf.Volume.HighestVolume))
		}
	}
}

// Performs move functionality. Determines if the supplied channel is valid and moves the bot
// to the channel if it is.
func move(user *gumble.User, channel string) {
	if channel == "" {
		user.Send(NO_ARGUMENT_MSG)
	} else {
		if dj.client.Channels().Find(channel) != nil {
			dj.client.Self().Move(dj.client.Channels().Find(channel))
		} else {
			user.Send(CHANNEL_DOES_NOT_EXIST_MSG)
		}
	}
}

// Performs reload functionality. Tells command submitter if the reload completed successfully.
func reload(user *gumble.User) {
	if err := loadConfiguration(); err == nil {
		user.Send(CONFIG_RELOAD_SUCCESS_MSG)
	} else {
		panic(err)
	}
}

// Performs kill functionality. First cleans the ~/.mumbledj/songs directory to get rid of any
// excess m4a files. The bot then safely disconnects from the server.
func kill() {
	songsDir := fmt.Sprintf("%s/.mumbledj/songs", dj.homeDir)
	if err := os.RemoveAll(songsDir); err != nil {
		panic(errors.New("An error occurred while deleting the audio files."))
	} else {
		if err := os.Mkdir(songsDir, 0777); err != nil {
			panic(errors.New("An error occurred while recreating the songs directory."))
		}
	}
	if err := dj.client.Disconnect(); err == nil {
		fmt.Println("Kill successful. Goodbye!")
		os.Exit(0)
	} else {
		panic(errors.New("An error occurred while disconnecting from the server."))
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/mackerelio/gomkr/utils"
	mkr "github.com/mackerelio/mackerel-client-go"
)

var Commands = []cli.Command{
	commandStatus,
	commandHosts,
	commandCreate,
	commandUpdate,
	commandThrow,
	commandFetch,
	commandRetire,
}

var commandStatus = cli.Command{
	Name:  "status",
	Usage: "Show a host",
	Description: `
    Show the information of the host identified with <hostId>.
    Request "GET /api/v0/hosts/<hostId>". See http://help-ja.mackerel.io/entry/spec/api/v0#host-get.
`,
	Action: doStatus,
	Flags: []cli.Flag{
		cli.BoolFlag{Name: "verbose, v", Usage: "Verbose output mode"},
	},
}

var commandHosts = cli.Command{
	Name:  "hosts",
	Usage: "List hosts",
	Description: `
    List the information of the hosts refined by host name, service name, role name and/or status.
    Request "GET /api/v0/hosts.json". See http://help-ja.mackerel.io/entry/spec/api/v0#hosts-list.
`,
	Action: doHosts,
	Flags: []cli.Flag{
		cli.StringFlag{Name: "name, n", Value: "", Usage: "Show hosts only matched with <name>"},
		cli.StringFlag{Name: "service, s", Value: "", Usage: "Show hosts only belongs to <service>"},
		cli.StringSliceFlag{
			Name:  "role, r",
			Value: &cli.StringSlice{},
			Usage: "Show hosts only belongs to <role>. Multiple choice allow. Required --service",
		},
		cli.StringSliceFlag{
			Name:  "status, st",
			Value: &cli.StringSlice{},
			Usage: "Show hosts only matched <status>. Multiple choice allow.",
		},
		cli.BoolFlag{Name: "verbose, v", Usage: "Verbose output mode"},
	},
}

var commandCreate = cli.Command{
	Name:  "create",
	Usage: "Create a new host",
	Description: `
    Create a new host with staus and/or roleFullname.
    Request "POST /api/v0/hosts". See http://help-ja.mackerel.io/entry/spec/api/v0#host-create.
`,
	Action: doCreate,
	Flags: []cli.Flag{
		cli.StringFlag{Name: "status, st", Value: "", Usage: "Host status ('working', 'standby', 'meintenance')"},
		cli.StringSliceFlag{
			Name:  "roleFullname, R",
			Value: &cli.StringSlice{},
			Usage: "Multiple choice allow. ex. My-Service:proxy, My-Service:db-master",
		},
	},
}

var commandUpdate = cli.Command{
	Name:  "update",
	Usage: "Update the host",
	Description: `
    Update the host identified with <hostId>.
    Request "PUT /api/v0/hosts/<hostId>". See http://help-ja.mackerel.io/entry/spec/api/v0#host-update.
`,
	Action: doUpdate,
	Flags: []cli.Flag{
		cli.StringFlag{Name: "name, n", Value: "", Usage: "Update <hostId> hostname to <name>."},
		cli.StringFlag{Name: "status, st", Value: "", Usage: "Update <hostId> status to <status>."},
		cli.StringSliceFlag{
			Name:  "roleFullname, R",
			Value: &cli.StringSlice{},
			Usage: "Update <hostId> rolefullname to <roleFullname>.",
		},
	},
}

var commandThrow = cli.Command{
	Name:  "throw",
	Usage: "Post metric values",
	Description: `
    Post metric values to 'host metric' or 'service metric'.
    Output format of metric value is compatibled with that of Sensu plugin.
    Request "POST /api/v0/tsdb". See http://help-ja.mackerel.io/entry/spec/api/v0#metric-value-post.
`,
	Action: doThrow,
	Flags: []cli.Flag{
		cli.StringFlag{Name: "host, H", Value: "", Usage: "Post host metric values to <hostId>."},
		cli.StringFlag{Name: "service, s", Value: "", Usage: "Post service metric values to <service>."},
	},
}

var commandFetch = cli.Command{
	Name:  "fetch",
	Usage: "Fetch latest metric values",
	Description: `
    Fetch latest metric values about <hostId>... hosts.
    Request "GET /api/v0/tsdb/latest". See http://help-ja.mackerel.io/entry/spec/api/v0#tsdb-latest.
`,
	Action: doFetch,
	Flags: []cli.Flag{
		cli.StringSliceFlag{
			Name:  "name, n",
			Value: &cli.StringSlice{},
			Usage: "Fetch metric values identified with <name>. Required. Multiple choise allow. ",
		},
	},
}

var commandRetire = cli.Command{
	Name:  "retire",
	Usage: "Retire host",
	Description: `
    Retire host identified by <hostId>. Be careful because this is a irreversible operation.
    Request POST /api/v0/hosts/<hostId>/retire. See http://help-ja.mackerel.io/entry/spec/api/v0#host-retire.
`,
	Action: doRetire,
}

func debug(v ...interface{}) {
	if os.Getenv("DEBUG") != "" {
		log.Println(v...)
	}
}

func assert(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func newMackerel() *mkr.Client {
	apiKey := os.Getenv("MACKEREL_APIKEY")
	if apiKey == "" {
		utils.Log("error", `
    Not set MACKEREL_APIKEY environment variable. (Try "export MACKEREL_APIKEY='<Your apikey>'")
`)
		os.Exit(1)
	}

	if os.Getenv("DEBUG") != "" {
		mackerel, err := mkr.NewClientForTest(apiKey, "https://mackerel.io/api/v0", true)
		utils.DieIf(err)

		return mackerel
	} else {
		return mkr.NewClient(apiKey)
	}
}

type commandDoc struct {
	Parent    string
	Arguments string
}

var commandDocs = map[string]commandDoc{
	"status":    {"", "[-v|verbose]"},
	"hosts":   {"", "[--verbose | -v] [--name | -n <name>] [--service | -s <service>] [[--role | -r <role>]...] [[--status | --st <status>]...]"},
	"create":   {"", "[--status | -st <status>] [--roleFullname | -R <service:role>] <hostName>"},
	"update": {"", "[--name | -n <name>] [--status | -st <status>] [--roleFullname | -R <service:role>] <hostId>"},
	"throw":   {"", "[--host | -h <hostId>] [--service | -s <service>] stdin"},
	"fetch":   {"", "[--name | -n <metricName>] <hostId>..."},
	"retire":   {"", "<hostId>"},
}

// Makes template conditionals to generate per-command documents.
func mkCommandsTemplate(genTemplate func(commandDoc) string) string {
	template := "{{if false}}"
	for _, command := range append(Commands) {
		template = template + fmt.Sprintf("{{else if (eq .Name %q)}}%s", command.Name, genTemplate(commandDocs[command.Name]))
	}
	return template + "{{end}}"
}

func init() {
	argsTemplate := mkCommandsTemplate(func(doc commandDoc) string { return doc.Arguments })
	parentTemplate := mkCommandsTemplate(func(doc commandDoc) string { return string(strings.TrimLeft(doc.Parent+" ", " ")) })

	cli.CommandHelpTemplate = `NAME:
    {{.Name}} - {{.Usage}}

USAGE:
    gomkr ` + parentTemplate + `{{.Name}} ` + argsTemplate + `
{{if (len .Description)}}
DESCRIPTION: {{.Description}}
{{end}}{{if (len .Flags)}}
OPTIONS:
    {{range .Flags}}{{.}}
    {{end}}
{{end}}`
}

func doStatus(c *cli.Context) {
	argHostId := c.Args().Get(0)
	isVerbose := c.Bool("verbose")

	if argHostId == "" {
		cli.ShowCommandHelp(c, "status")
		os.Exit(1)
	}

	host, err := newMackerel().FindHost(argHostId)
	utils.DieIf(err)

	if isVerbose {
		PrettyPrintJson(host)
	} else {
		format := &HostFormat{
			Id:            host.Id,
			Name:          host.Name,
			Status:        host.Status,
			RoleFullnames: host.GetRoleFullnames(),
			IsRetired:     host.IsRetired,
			CreatedAt:     host.DateStringFromCreatedAt(),
			IpAddresses:   host.IpAddresses(),
		}

		PrettyPrintJson(format)
	}
}

func doHosts(c *cli.Context) {
	isVerbose := c.Bool("verbose")

	hosts, err := newMackerel().FindHosts(&mkr.FindHostsParam{
		Name:     c.String("name"),
		Service:  c.String("service"),
		Roles:    c.StringSlice("role"),
		Statuses: c.StringSlice("status"),
	})
	utils.DieIf(err)

	if isVerbose {
		PrettyPrintJson(hosts)
	} else {
		var hostsFormat []*HostFormat
		for _, host := range hosts {
			format := &HostFormat{
				Id:            host.Id,
				Name:          host.Name,
				Status:        host.Status,
				RoleFullnames: host.GetRoleFullnames(),
				IsRetired:     host.IsRetired,
				CreatedAt:     host.DateStringFromCreatedAt(),
				IpAddresses:   host.IpAddresses(),
			}
			hostsFormat = append(hostsFormat, format)
		}

		PrettyPrintJson(hostsFormat)
	}
}

func doCreate(c *cli.Context) {
	argHostName := c.Args().Get(0)
	optRoleFullnames := c.StringSlice("roleFullname")
	optStatus := c.String("status")

	if argHostName == "" {
		cli.ShowCommandHelp(c, "create")
		os.Exit(1)
	}

	hostId, err := newMackerel().CreateHost(&mkr.CreateHostParam{
		Name:          argHostName,
		RoleFullnames: optRoleFullnames,
	})
	utils.DieIf(err)

	utils.Log("created", hostId)

	if optStatus != "" {
		err := newMackerel().UpdateHostStatus(hostId, optStatus)
		utils.DieIf(err)
	}
}

func doUpdate(c *cli.Context) {
	argHostId := c.Args().Get(0)
	optName := c.String("name")
	optStatus := c.String("status")
	optRoleFullnames := c.StringSlice("roleFullname")

	if argHostId == "" {
		cli.ShowCommandHelp(c, "update")
		os.Exit(1)
	}

	isUpdated := false

	if optStatus != "" {
		err := newMackerel().UpdateHostStatus(argHostId, optStatus)
		utils.DieIf(err)

		isUpdated = true
	}
	if optName != "" || len(optRoleFullnames) > 0 {
		_, err := newMackerel().UpdateHost(argHostId, &mkr.UpdateHostParam{
			Name:          optName,
			RoleFullnames: optRoleFullnames,
		})
		utils.DieIf(err)

		isUpdated = true
	}

	if !isUpdated {
		cli.ShowCommandHelp(c, "update")
		os.Exit(1)
	}

	utils.Log("updated", argHostId)
}

func doThrow(c *cli.Context) {
	optHostId := c.String("host")
	optService := c.String("service")

	var metricValues []*(mkr.MetricValue)

	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		line := scanner.Text()

		// name, value, timestamp
		// ex.) tcp.CLOSING 0 1397031808
		items := strings.Fields(line)
		if len(items) != 3 {
			continue
		}
		value, err := strconv.ParseFloat(items[1], 64)
		if err != nil {
			utils.Log("warning", fmt.Sprintf("Failed to parse values: %s", err))
			continue
		}
		time, err := strconv.ParseInt(items[2], 10, 64)
		if err != nil {
			utils.Log("warning", fmt.Sprintf("Failed to parse values: %s", err))
			continue
		}

		metricValue := &mkr.MetricValue{
			Name:  items[0],
			Value: value,
			Time:  time,
		}

		metricValues = append(metricValues, metricValue)
	}
	utils.ErrorIf(scanner.Err())

	if optHostId != "" {
		err := newMackerel().PostHostMetricValuesByHostId(optHostId, metricValues)
		utils.DieIf(err)

		for _, metric := range metricValues {
			utils.Log("thrown", fmt.Sprintf("%s '%s\t%f\t%d'", optHostId, metric.Name, metric.Value, metric.Time))
		}
	} else if optService != "" {
		err := newMackerel().PostServiceMetricValues(optService, metricValues)
		utils.DieIf(err)

		for _, metric := range metricValues {
			utils.Log("thrown", fmt.Sprintf("%s '%s\t%f\t%d'", optService, metric.Name, metric.Value, metric.Time))
		}
	} else {
		cli.ShowCommandHelp(c, "throw")
		os.Exit(1)
	}
}

func doFetch(c *cli.Context) {
	argHostIds := c.Args()
	optMetricNames := c.StringSlice("name")

	if len(argHostIds) < 1 || len(optMetricNames) < 1 {
		cli.ShowCommandHelp(c, "fetch")
		os.Exit(1)
	}

	metricValues, err := newMackerel().FetchLatestMetricValues(argHostIds, optMetricNames)
	utils.DieIf(err)

	PrettyPrintJson(metricValues)
}

func doRetire(c *cli.Context) {
	argHostId := c.Args().Get(0)

	if argHostId == "" {
		cli.ShowCommandHelp(c, "retire")
		os.Exit(1)
	}

	err := newMackerel().RetireHost(argHostId)
	utils.DieIf(err)
	utils.Log("retired", argHostId)
}
<commit_msg>gofmt<commit_after>package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/mackerelio/gomkr/utils"
	mkr "github.com/mackerelio/mackerel-client-go"
)

var Commands = []cli.Command{
	commandStatus,
	commandHosts,
	commandCreate,
	commandUpdate,
	commandThrow,
	commandFetch,
	commandRetire,
}

var commandStatus = cli.Command{
	Name:  "status",
	Usage: "Show a host",
	Description: `
    Show the information of the host identified with <hostId>.
    Request "GET /api/v0/hosts/<hostId>". See http://help-ja.mackerel.io/entry/spec/api/v0#host-get.
`,
	Action: doStatus,
	Flags: []cli.Flag{
		cli.BoolFlag{Name: "verbose, v", Usage: "Verbose output mode"},
	},
}

var commandHosts = cli.Command{
	Name:  "hosts",
	Usage: "List hosts",
	Description: `
    List the information of the hosts refined by host name, service name, role name and/or status.
    Request "GET /api/v0/hosts.json". See http://help-ja.mackerel.io/entry/spec/api/v0#hosts-list.
`,
	Action: doHosts,
	Flags: []cli.Flag{
		cli.StringFlag{Name: "name, n", Value: "", Usage: "Show hosts only matched with <name>"},
		cli.StringFlag{Name: "service, s", Value: "", Usage: "Show hosts only belongs to <service>"},
		cli.StringSliceFlag{
			Name:  "role, r",
			Value: &cli.StringSlice{},
			Usage: "Show hosts only belongs to <role>. Multiple choice allow. Required --service",
		},
		cli.StringSliceFlag{
			Name:  "status, st",
			Value: &cli.StringSlice{},
			Usage: "Show hosts only matched <status>. Multiple choice allow.",
		},
		cli.BoolFlag{Name: "verbose, v", Usage: "Verbose output mode"},
	},
}

var commandCreate = cli.Command{
	Name:  "create",
	Usage: "Create a new host",
	Description: `
    Create a new host with staus and/or roleFullname.
    Request "POST /api/v0/hosts". See http://help-ja.mackerel.io/entry/spec/api/v0#host-create.
`,
	Action: doCreate,
	Flags: []cli.Flag{
		cli.StringFlag{Name: "status, st", Value: "", Usage: "Host status ('working', 'standby', 'meintenance')"},
		cli.StringSliceFlag{
			Name:  "roleFullname, R",
			Value: &cli.StringSlice{},
			Usage: "Multiple choice allow. ex. My-Service:proxy, My-Service:db-master",
		},
	},
}

var commandUpdate = cli.Command{
	Name:  "update",
	Usage: "Update the host",
	Description: `
    Update the host identified with <hostId>.
    Request "PUT /api/v0/hosts/<hostId>". See http://help-ja.mackerel.io/entry/spec/api/v0#host-update.
`,
	Action: doUpdate,
	Flags: []cli.Flag{
		cli.StringFlag{Name: "name, n", Value: "", Usage: "Update <hostId> hostname to <name>."},
		cli.StringFlag{Name: "status, st", Value: "", Usage: "Update <hostId> status to <status>."},
		cli.StringSliceFlag{
			Name:  "roleFullname, R",
			Value: &cli.StringSlice{},
			Usage: "Update <hostId> rolefullname to <roleFullname>.",
		},
	},
}

var commandThrow = cli.Command{
	Name:  "throw",
	Usage: "Post metric values",
	Description: `
    Post metric values to 'host metric' or 'service metric'.
    Output format of metric value is compatibled with that of Sensu plugin.
    Request "POST /api/v0/tsdb". See http://help-ja.mackerel.io/entry/spec/api/v0#metric-value-post.
`,
	Action: doThrow,
	Flags: []cli.Flag{
		cli.StringFlag{Name: "host, H", Value: "", Usage: "Post host metric values to <hostId>."},
		cli.StringFlag{Name: "service, s", Value: "", Usage: "Post service metric values to <service>."},
	},
}

var commandFetch = cli.Command{
	Name:  "fetch",
	Usage: "Fetch latest metric values",
	Description: `
    Fetch latest metric values about <hostId>... hosts.
    Request "GET /api/v0/tsdb/latest". See http://help-ja.mackerel.io/entry/spec/api/v0#tsdb-latest.
`,
	Action: doFetch,
	Flags: []cli.Flag{
		cli.StringSliceFlag{
			Name:  "name, n",
			Value: &cli.StringSlice{},
			Usage: "Fetch metric values identified with <name>. Required. Multiple choise allow. ",
		},
	},
}

var commandRetire = cli.Command{
	Name:  "retire",
	Usage: "Retire host",
	Description: `
    Retire host identified by <hostId>. Be careful because this is a irreversible operation.
    Request POST /api/v0/hosts/<hostId>/retire. See http://help-ja.mackerel.io/entry/spec/api/v0#host-retire.
`,
	Action: doRetire,
}

func debug(v ...interface{}) {
	if os.Getenv("DEBUG") != "" {
		log.Println(v...)
	}
}

func assert(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func newMackerel() *mkr.Client {
	apiKey := os.Getenv("MACKEREL_APIKEY")
	if apiKey == "" {
		utils.Log("error", `
    Not set MACKEREL_APIKEY environment variable. (Try "export MACKEREL_APIKEY='<Your apikey>'")
`)
		os.Exit(1)
	}

	if os.Getenv("DEBUG") != "" {
		mackerel, err := mkr.NewClientForTest(apiKey, "https://mackerel.io/api/v0", true)
		utils.DieIf(err)

		return mackerel
	} else {
		return mkr.NewClient(apiKey)
	}
}

type commandDoc struct {
	Parent    string
	Arguments string
}

var commandDocs = map[string]commandDoc{
	"status": {"", "[-v|verbose]"},
	"hosts":  {"", "[--verbose | -v] [--name | -n <name>] [--service | -s <service>] [[--role | -r <role>]...] [[--status | --st <status>]...]"},
	"create": {"", "[--status | -st <status>] [--roleFullname | -R <service:role>] <hostName>"},
	"update": {"", "[--name | -n <name>] [--status | -st <status>] [--roleFullname | -R <service:role>] <hostId>"},
	"throw":  {"", "[--host | -h <hostId>] [--service | -s <service>] stdin"},
	"fetch":  {"", "[--name | -n <metricName>] <hostId>..."},
	"retire": {"", "<hostId>"},
}

// Makes template conditionals to generate per-command documents.
func mkCommandsTemplate(genTemplate func(commandDoc) string) string {
	template := "{{if false}}"
	for _, command := range append(Commands) {
		template = template + fmt.Sprintf("{{else if (eq .Name %q)}}%s", command.Name, genTemplate(commandDocs[command.Name]))
	}
	return template + "{{end}}"
}

func init() {
	argsTemplate := mkCommandsTemplate(func(doc commandDoc) string { return doc.Arguments })
	parentTemplate := mkCommandsTemplate(func(doc commandDoc) string { return string(strings.TrimLeft(doc.Parent+" ", " ")) })

	cli.CommandHelpTemplate = `NAME:
    {{.Name}} - {{.Usage}}

USAGE:
    gomkr ` + parentTemplate + `{{.Name}} ` + argsTemplate + `
{{if (len .Description)}}
DESCRIPTION: {{.Description}}
{{end}}{{if (len .Flags)}}
OPTIONS:
    {{range .Flags}}{{.}}
    {{end}}
{{end}}`
}

func doStatus(c *cli.Context) {
	argHostId := c.Args().Get(0)
	isVerbose := c.Bool("verbose")

	if argHostId == "" {
		cli.ShowCommandHelp(c, "status")
		os.Exit(1)
	}

	host, err := newMackerel().FindHost(argHostId)
	utils.DieIf(err)

	if isVerbose {
		PrettyPrintJson(host)
	} else {
		format := &HostFormat{
			Id:            host.Id,
			Name:          host.Name,
			Status:        host.Status,
			RoleFullnames: host.GetRoleFullnames(),
			IsRetired:     host.IsRetired,
			CreatedAt:     host.DateStringFromCreatedAt(),
			IpAddresses:   host.IpAddresses(),
		}

		PrettyPrintJson(format)
	}
}

func doHosts(c *cli.Context) {
	isVerbose := c.Bool("verbose")

	hosts, err := newMackerel().FindHosts(&mkr.FindHostsParam{
		Name:     c.String("name"),
		Service:  c.String("service"),
		Roles:    c.StringSlice("role"),
		Statuses: c.StringSlice("status"),
	})
	utils.DieIf(err)

	if isVerbose {
		PrettyPrintJson(hosts)
	} else {
		var hostsFormat []*HostFormat
		for _, host := range hosts {
			format := &HostFormat{
				Id:            host.Id,
				Name:          host.Name,
				Status:        host.Status,
				RoleFullnames: host.GetRoleFullnames(),
				IsRetired:     host.IsRetired,
				CreatedAt:     host.DateStringFromCreatedAt(),
				IpAddresses:   host.IpAddresses(),
			}
			hostsFormat = append(hostsFormat, format)
		}

		PrettyPrintJson(hostsFormat)
	}
}

func doCreate(c *cli.Context) {
	argHostName := c.Args().Get(0)
	optRoleFullnames := c.StringSlice("roleFullname")
	optStatus := c.String("status")

	if argHostName == "" {
		cli.ShowCommandHelp(c, "create")
		os.Exit(1)
	}

	hostId, err := newMackerel().CreateHost(&mkr.CreateHostParam{
		Name:          argHostName,
		RoleFullnames: optRoleFullnames,
	})
	utils.DieIf(err)

	utils.Log("created", hostId)

	if optStatus != "" {
		err := newMackerel().UpdateHostStatus(hostId, optStatus)
		utils.DieIf(err)
	}
}

func doUpdate(c *cli.Context) {
	argHostId := c.Args().Get(0)
	optName := c.String("name")
	optStatus := c.String("status")
	optRoleFullnames := c.StringSlice("roleFullname")

	if argHostId == "" {
		cli.ShowCommandHelp(c, "update")
		os.Exit(1)
	}

	isUpdated := false

	if optStatus != "" {
		err := newMackerel().UpdateHostStatus(argHostId, optStatus)
		utils.DieIf(err)

		isUpdated = true
	}
	if optName != "" || len(optRoleFullnames) > 0 {
		_, err := newMackerel().UpdateHost(argHostId, &mkr.UpdateHostParam{
			Name:          optName,
			RoleFullnames: optRoleFullnames,
		})
		utils.DieIf(err)

		isUpdated = true
	}

	if !isUpdated {
		cli.ShowCommandHelp(c, "update")
		os.Exit(1)
	}

	utils.Log("updated", argHostId)
}

func doThrow(c *cli.Context) {
	optHostId := c.String("host")
	optService := c.String("service")

	var metricValues []*(mkr.MetricValue)

	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		line := scanner.Text()

		// name, value, timestamp
		// ex.) tcp.CLOSING 0 1397031808
		items := strings.Fields(line)
		if len(items) != 3 {
			continue
		}
		value, err := strconv.ParseFloat(items[1], 64)
		if err != nil {
			utils.Log("warning", fmt.Sprintf("Failed to parse values: %s", err))
			continue
		}
		time, err := strconv.ParseInt(items[2], 10, 64)
		if err != nil {
			utils.Log("warning", fmt.Sprintf("Failed to parse values: %s", err))
			continue
		}

		metricValue := &mkr.MetricValue{
			Name:  items[0],
			Value: value,
			Time:  time,
		}

		metricValues = append(metricValues, metricValue)
	}
	utils.ErrorIf(scanner.Err())

	if optHostId != "" {
		err := newMackerel().PostHostMetricValuesByHostId(optHostId, metricValues)
		utils.DieIf(err)

		for _, metric := range metricValues {
			utils.Log("thrown", fmt.Sprintf("%s '%s\t%f\t%d'", optHostId, metric.Name, metric.Value, metric.Time))
		}
	} else if optService != "" {
		err := newMackerel().PostServiceMetricValues(optService, metricValues)
		utils.DieIf(err)

		for _, metric := range metricValues {
			utils.Log("thrown", fmt.Sprintf("%s '%s\t%f\t%d'", optService, metric.Name, metric.Value, metric.Time))
		}
	} else {
		cli.ShowCommandHelp(c, "throw")
		os.Exit(1)
	}
}

func doFetch(c *cli.Context) {
	argHostIds := c.Args()
	optMetricNames := c.StringSlice("name")

	if len(argHostIds) < 1 || len(optMetricNames) < 1 {
		cli.ShowCommandHelp(c, "fetch")
		os.Exit(1)
	}

	metricValues, err := newMackerel().FetchLatestMetricValues(argHostIds, optMetricNames)
	utils.DieIf(err)

	PrettyPrintJson(metricValues)
}

func doRetire(c *cli.Context) {
	argHostId := c.Args().Get(0)

	if argHostId == "" {
		cli.ShowCommandHelp(c, "retire")
		os.Exit(1)
	}

	err := newMackerel().RetireHost(argHostId)
	utils.DieIf(err)
	utils.Log("retired", argHostId)
}
<|endoftext|>