<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"

	linuxproc "github.com/c9s/goprocinfo/linux"
)

// customProcs is our extended linuxproc.Process type
// with custom cpuUsage attribute
type customProcs struct {
	linuxproc.Process
	CPUUsage float64
}

// byCPU helps us sort array of customProcs by CPUUsage
type byCPU []customProcs

// byRSS helps us sort array of customProcs by Status.VmRSS
type byRSS []customProcs

func (p byRSS) Len() int {
	return len(p)
}
func (p byRSS) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}
func (p byRSS) Less(i, j int) bool {
	return p[i].Status.VmRSS < p[j].Status.VmRSS
}

func (p byCPU) Len() int {
	return len(p)
}
func (p byCPU) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}
func (p byCPU) Less(i, j int) bool {
	return p[i].CPUUsage < p[j].CPUUsage
}

// dockerSnapshot containes process list and cpu usage for some docker container
type dockerSnapshot struct {
	processes []linuxproc.Process
	cpuStat   uint64
}

// historyEntry containes all dockerSnapshots for some moment in time
type historyEntry map[string]dockerSnapshot

// history holds RRD-like array of last 60 history entries
var history [60]historyEntry

// channel allows communication between data scrappers and data processors
var channel chan historyEntry

// getProcesses returns list of processes for given dockerID
func getProcesses(dockerID string) ([]linuxproc.Process, error) {
	tasksPath := fmt.Sprintf("/sys/fs/cgroup/cpu/docker/%s/tasks", dockerID)
	dataBytes, err := ioutil.ReadFile(tasksPath)
	if err != nil {
		return nil, err
	}

	tasksString := string(dataBytes)
	tasks := strings.Split(tasksString, "\n")
	var procs []linuxproc.Process
	for _, t := range tasks {
		pid, err := strconv.ParseUint(t, 10, 64)
		if err == nil {
			p, err := linuxproc.ReadProcess(pid, "/proc/")
			if err != nil {
				continue
			}
			if p.Cmdline != "" && p.Status.VmRSS > 0 {
				procs = append(procs, *p)
			}
		}
	}
	return procs, nil
}

// getCPUTotalUsage returns amount of CPU time, used by given docker container
func getCPUTotalUsage(dockerID string) (uint64, error) {
	cpuAcctPath := fmt.Sprintf("/sys/fs/cgroup/cpuacct/docker/%s/cpuacct.stat", dockerID)
	dataBytes, err := ioutil.ReadFile(cpuAcctPath)
	if err != nil {
		return 0, nil
	}
	cpuAcctString := string(dataBytes)
	cpuAcct := strings.Split(cpuAcctString, "\n")
	var total uint64
	total = 0
	for _, s := range cpuAcct {
		splitted := strings.Split(s, " ")
		if len(splitted) > 1 {
			usage, _ := strconv.ParseUint(splitted[1], 10, 64)
			total += usage
		}
	}

	return total, nil
}

// findProc searches for given pid in list of processes and returns
// its process if found
func findProc(pid uint64, procs []linuxproc.Process) *linuxproc.Process {
	for _, p := range procs {
		if p.Status.Pid == pid {
			return &p
		}
	}
	return nil
}

// getLastData returns latest data from history with added calculated CPU usage
func getLastData(dockerID string) ([]customProcs, error) {
	last := len(history) - 1
	first := last
	for i, e := range history {
		if len(e[dockerID].processes) > 0 {
			first = i
			break
		}
	}
	entry1 := history[first][dockerID]
	entry2 := history[last][dockerID]
	var procs []customProcs
	for _, p2 := range entry2.processes {
		p1 := findProc(p2.Status.Pid, entry1.processes)
		if p1 != nil {
			user := int64(p2.Stat.Utime-p1.Stat.Utime) + (p2.Stat.Cutime - p1.Stat.Cutime)
			system := int64(p2.Stat.Stime-p1.Stat.Stime) + (p2.Stat.Cstime - p1.Stat.Cstime)
			percent := (float64(user+system) / float64(entry2.cpuStat-entry1.cpuStat)) * 100
			procs = append(procs, customProcs{p2, percent})
		}

	}
	return procs, nil
}

// getTopCPU returns `limit` entries with top CPU usage
func getTopCPU(dockerID string, limit int) ([]customProcs, error) {
	procs, err := getLastData(dockerID)
	if err != nil {
		return nil, err
	}
	sort.Sort(sort.Reverse(byCPU(procs)))
	var result []customProcs
	for _, p := range procs[:limit] {
		result = append(result, p)
		//fmt.Printf("%f%% CPU %s\n", p.CPUUsage, p.Cmdline)
	}
	return result, nil
}

// getTopMem returns `limit` entries with top VmRSS usage
func getTopMem(dockerID string, limit int) ([]customProcs, error) {
	procs, err := getLastData(dockerID)
	if err != nil {
		return nil, err
	}
	sort.Sort(sort.Reverse(byRSS(procs)))
	var result []customProcs
	for _, p := range procs[:limit] {
		result = append(result, p)
		//fmt.Printf("%dKb %s\n", p.Status.VmRSS, p.Cmdline)
	}
	return result, nil
}

// getDockerIDs collects all docker ids from cgroups pseudo-filesystem
func getDockerIDs() ([]string, error) {
	d, err := os.Open("/sys/fs/cgroup/cpu/docker")
	if err != nil {
		return nil, err
	}
	defer d.Close()

	results := make([]string, 0, 50)
	for {
		fis, err := d.Readdir(10)
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, err
		}

		for _, fi := range fis {
			// We only care about directories, since all pids are dirs
			if !fi.IsDir() {
				continue
			}
			name := fi.Name()
			if len(name) > '8' {
				results = append(results, name)
			}
		}
	}

	return results, nil
}

// memHandler handles requests for top memory eaters
func memHandler(res http.ResponseWriter, req *http.Request) {
	dockerID := "f33b34a760f631a7176f10d9babab89c20dd0ebde744ed83b1ea27f21ce0bb75"
	result, err := getTopMem(dockerID, 5)
	if err != nil {
		fmt.Println(err)
	}
	jsonResult, _ := json.Marshal(result)
	res.Header().Set(
		"Content-Type",
		"text/json",
	)
	io.WriteString(res, string(jsonResult))
}

// cpuHandler handles requests for top CPU eaters
func cpuHandler(res http.ResponseWriter, req *http.Request) {
	dockerID := "f33b34a760f631a7176f10d9babab89c20dd0ebde744ed83b1ea27f21ce0bb75"
	result, err := getTopCPU(dockerID, 5)
	if err != nil {
		fmt.Println(err)
	}
	jsonResult, _ := json.Marshal(result)
	res.Header().Set(
		"Content-Type",
		"text/json",
	)
	io.WriteString(res, string(jsonResult))
}

// collectData scrapes procs data for all docker containers
// every second, and sends through channel to getData()
func collectData() {
	dockerIDs, err := getDockerIDs()
	if err != nil {
		fmt.Println(err)
	}
	for {
		entry := make(historyEntry)
		for _, id := range dockerIDs {
			res, _ := getProcesses(id)
			cpu, _ := getCPUTotalUsage(id)
			entry[id] = dockerSnapshot{res, cpu}
		}
		channel <- entry
		time.Sleep(time.Second)
	}
}

// processData processes data from collectData
// and stores it inside global var history
func processData() {
	for {
		entry := <-channel
		for i, v := range history[1:] {
			history[i] = v
		}
		history[59] = entry
	}
}

func main() {
	channel = make(chan historyEntry)
	go collectData()
	go processData()
	http.HandleFunc("/mem", memHandler)
	http.HandleFunc("/cpu", cpuHandler)
	http.ListenAndServe(":8801", nil)
}
<commit_msg>UPD universal hanler, dockerID in request URL<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	linuxproc "github.com/c9s/goprocinfo/linux"
)

// customProcs is our extended linuxproc.Process type
// with custom cpuUsage attribute
type customProcs struct {
	linuxproc.Process
	CPUUsage float64
}

// byCPU helps us sort array of customProcs by CPUUsage
type byCPU []customProcs

// byRSS helps us sort array of customProcs by Status.VmRSS
type byRSS []customProcs

func (p byRSS) Len() int {
	return len(p)
}
func (p byRSS) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}
func (p byRSS) Less(i, j int) bool {
	return p[i].Status.VmRSS < p[j].Status.VmRSS
}

func (p byCPU) Len() int {
	return len(p)
}
func (p byCPU) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}
func (p byCPU) Less(i, j int) bool {
	return p[i].CPUUsage < p[j].CPUUsage
}

// dockerSnapshot containes process list and cpu usage for some docker container
type dockerSnapshot struct {
	processes []linuxproc.Process
	cpuStat   uint64
}

// historyEntry containes all dockerSnapshots for some moment in time
type historyEntry map[string]dockerSnapshot

// history holds RRD-like array of last 60 history entries
var history [60]historyEntry

// channel allows communication between data scrappers and data processors
var channel chan historyEntry

// getProcesses returns list of processes for given dockerID
func getProcesses(dockerID string) ([]linuxproc.Process, error) {
	tasksPath := fmt.Sprintf("/sys/fs/cgroup/cpu/docker/%s/tasks", dockerID)
	dataBytes, err := ioutil.ReadFile(tasksPath)
	if err != nil {
		return nil, err
	}

	tasksString := string(dataBytes)
	tasks := strings.Split(tasksString, "\n")
	var procs []linuxproc.Process
	for _, t := range tasks {
		pid, err := strconv.ParseUint(t, 10, 64)
		if err == nil {
			p, err := linuxproc.ReadProcess(pid, "/proc/")
			if err != nil {
				continue
			}
			if p.Cmdline != "" && p.Status.VmRSS > 0 {
				procs = append(procs, *p)
			}
		}
	}
	return procs, nil
}

// getCPUTotalUsage returns amount of CPU time, used by given docker container
func getCPUTotalUsage(dockerID string) (uint64, error) {
	cpuAcctPath := fmt.Sprintf("/sys/fs/cgroup/cpuacct/docker/%s/cpuacct.stat", dockerID)
	dataBytes, err := ioutil.ReadFile(cpuAcctPath)
	if err != nil {
		return 0, nil
	}
	cpuAcctString := string(dataBytes)
	cpuAcct := strings.Split(cpuAcctString, "\n")
	var total uint64
	total = 0
	for _, s := range cpuAcct {
		splitted := strings.Split(s, " ")
		if len(splitted) > 1 {
			usage, _ := strconv.ParseUint(splitted[1], 10, 64)
			total += usage
		}
	}

	return total, nil
}

// findProc searches for given pid in list of processes and returns
// its process if found
func findProc(pid uint64, procs []linuxproc.Process) *linuxproc.Process {
	for _, p := range procs {
		if p.Status.Pid == pid {
			return &p
		}
	}
	return nil
}

// getLastData returns latest data from history with added calculated CPU usage
func getLastData(dockerID string) ([]customProcs, error) {
	last := len(history) - 1
	first := last
	for i, e := range history {
		if len(e[dockerID].processes) > 0 {
			first = i
			break
		}
	}
	entry1 := history[first][dockerID]
	entry2 := history[last][dockerID]
	var procs []customProcs
	for _, p2 := range entry2.processes {
		p1 := findProc(p2.Status.Pid, entry1.processes)
		if p1 != nil {
			user := int64(p2.Stat.Utime-p1.Stat.Utime) + (p2.Stat.Cutime - p1.Stat.Cutime)
			system := int64(p2.Stat.Stime-p1.Stat.Stime) + (p2.Stat.Cstime - p1.Stat.Cstime)
			percent := (float64(user+system) / float64(entry2.cpuStat-entry1.cpuStat)) * 100
			procs = append(procs, customProcs{p2, percent})
		}

	}
	return procs, nil
}

// getTopCPU returns `limit` entries with top CPU usage
func getTopCPU(dockerID string, limit int) ([]customProcs, error) {
	procs, err := getLastData(dockerID)
	if err != nil {
		return nil, err
	}
	sort.Sort(sort.Reverse(byCPU(procs)))
	var result []customProcs
	for _, p := range procs[:limit] {
		result = append(result, p)
		//fmt.Printf("%f%% CPU %s\n", p.CPUUsage, p.Cmdline)
	}
	return result, nil
}

// getTopMem returns `limit` entries with top VmRSS usage
func getTopMem(dockerID string, limit int) ([]customProcs, error) {
	procs, err := getLastData(dockerID)
	if err != nil {
		return nil, err
	}
	sort.Sort(sort.Reverse(byRSS(procs)))
	var result []customProcs
	for _, p := range procs[:limit] {
		result = append(result, p)
		//fmt.Printf("%dKb %s\n", p.Status.VmRSS, p.Cmdline)
	}
	return result, nil
}

// getDockerIDs collects all docker ids from cgroups pseudo-filesystem
func getDockerIDs() ([]string, error) {
	d, err := os.Open("/sys/fs/cgroup/cpu/docker")
	if err != nil {
		return nil, err
	}
	defer d.Close()

	results := make([]string, 0, 50)
	for {
		fis, err := d.Readdir(10)
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, err
		}

		for _, fi := range fis {
			// We only care about directories, since all pids are dirs
			if !fi.IsDir() {
				continue
			}
			name := fi.Name()
			if len(name) > '8' {
				results = append(results, name)
			}
		}
	}

	return results, nil
}

// httpHandler handles requests
func httpHandler(res http.ResponseWriter, req *http.Request) {
	var validPath = regexp.MustCompile("^/([a-zA-Z0-9]+)/(mem|cpu|all)$")
	m := validPath.FindStringSubmatch(req.URL.Path)
	//dockerID := "f33b34a760f631a7176f10d9babab89c20dd0ebde744ed83b1ea27f21ce0bb75"
	if m == nil {
		http.NotFound(res, req)
		return
	}
	dockerID := m[1]
	var result []customProcs
	var err error
	switch m[2] {
	case "cpu":
		result, err = getTopCPU(dockerID, 5)
	case "mem":
		result, err = getTopMem(dockerID, 5)
	case "all":
		result, err = getLastData(dockerID)
	}
	if err != nil {
		fmt.Println(err)
	}
	jsonResult, _ := json.Marshal(result)
	res.Header().Set(
		"Content-Type",
		"text/json",
	)
	io.WriteString(res, string(jsonResult))
}

// collectData scrapes procs data for all docker containers
// every second, and sends through channel to getData()
func collectData() {
	dockerIDs, err := getDockerIDs()
	if err != nil {
		fmt.Println(err)
	}
	for {
		entry := make(historyEntry)
		for _, id := range dockerIDs {
			res, _ := getProcesses(id)
			cpu, _ := getCPUTotalUsage(id)
			entry[id] = dockerSnapshot{res, cpu}
		}
		channel <- entry
		time.Sleep(time.Second)
	}
}

// processData processes data from collectData
// and stores it inside global var history
func processData() {
	for {
		entry := <-channel
		for i, v := range history[1:] {
			history[i] = v
		}
		history[59] = entry
	}
}

func main() {
	channel = make(chan historyEntry)
	go collectData()
	go processData()
	http.HandleFunc("/", httpHandler)
	http.ListenAndServe(":8801", nil)
}
<|endoftext|>


<commit_before>// Copyright © 2017 Circonus, Inc. <support@circonus.com>
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

package server

import (
	"bytes"
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/circonus-labs/circonus-agent/internal/builtins"
	"github.com/circonus-labs/circonus-agent/internal/config"
	"github.com/circonus-labs/circonus-agent/internal/plugins"
	"github.com/rs/zerolog"
	"github.com/spf13/viper"
)

func TestRouter(t *testing.T) {
	t.Log("Testing router")
	zerolog.SetGlobalLevel(zerolog.Disabled)

	t.Log("bad methods")
	{
		methods := []string{
			"CONNECT",
			"DELETE",
			"HEAD",
			"OPTIONS",
			"TRACE",
		}
		viper.Set(config.KeyListen, ":2609")
		s, err := New(nil, nil, nil)
		if err != nil {
			t.Fatalf("expected NO error, got (%s)", err)
		}
		if s == nil {
			t.Fatal("expected NOT nil")
		}

		for _, method := range methods {
			t.Logf("Method not allowed (%s)", method)
			req := httptest.NewRequest(method, "/", nil)
			w := httptest.NewRecorder()
			s.router(w, req)
			resp := w.Result()
			if resp.StatusCode != http.StatusMethodNotAllowed {
				t.Fatalf("expected %d, got %d", http.StatusMethodNotAllowed, resp.StatusCode)
			}
		}
		viper.Reset()
	}

	t.Log("invalid paths")
	{
		viper.Set(config.KeyListen, ":2609")
		viper.Set(config.KeyPluginDir, "testdata/")
		p, perr := plugins.New(context.Background())
		if perr != nil {
			t.Fatalf("expected NO error, got (%s)", perr)
		}
		reqtests := []struct {
			method string
			path   string
		}{
			{"GET", "/invalid"},
			{"GET", "/run/invalid"},
			{"GET", "/inventory/invalid"},
			{"POST", "/invalid"},
			{"PUT", "/invalid"},
			{"PUT", "/write/"}, // /write/ must be followed by an id/name to use as "plugin namespace"
		}
		s, err := New(nil, p, nil)
		if err != nil {
			t.Fatalf("expected NO error, got (%s)", err)
		}
		if s == nil {
			t.Fatal("expected NOT nil")
		}
		for _, reqtest := range reqtests {
			t.Logf("Invalid path (%s %s)", reqtest.method, reqtest.path)
			req := httptest.NewRequest(reqtest.method, reqtest.path, nil)
			w := httptest.NewRecorder()
			s.router(w, req)
			resp := w.Result()
			if resp.StatusCode != http.StatusNotFound {
				t.Fatalf("expected %d, got %d", http.StatusNotFound, resp.StatusCode)
			}
		}
		viper.Reset()
	}

	t.Log("valid")
	{
		viper.Set(config.KeyListen, ":2609")
		viper.Set(config.KeyStatsdDisabled, true)
		viper.Set(config.KeyPluginDir, "testdata/")
		b, berr := builtins.New()
		if berr != nil {
			t.Fatalf("expected no error, got (%s)", berr)
		}
		p, perr := plugins.New(context.Background())
		if perr != nil {
			t.Fatalf("expected NO error, got (%s)", perr)
		}
		s, err := New(b, p, nil)
		if err != nil {
			t.Fatalf("expected NO error, got (%s)", err)
		}
		if s == nil {
			t.Fatal("expected NOT nil")
		}
		reqtests := []struct {
			method string
			path   string
			code   int
		}{
			{"GET", "/", http.StatusOK},
			{"GET", "/run", http.StatusOK},
			{"GET", "/run/", http.StatusOK},
			{"GET", "/inventory", http.StatusOK},
			{"GET", "/inventory/", http.StatusOK},
			{"GET", "/stats", http.StatusOK},
			{"GET", "/stats/", http.StatusOK},
			{"GET", "/prom", http.StatusNoContent},
			{"GET", "/prom/", http.StatusNoContent},
		}
		for _, reqtest := range reqtests {
			t.Logf("OK path (%s %s)", reqtest.method, reqtest.path)
			req := httptest.NewRequest(reqtest.method, reqtest.path, nil)
			w := httptest.NewRecorder()
			s.router(w, req)
			resp := w.Result()
			if resp.StatusCode != reqtest.code {
				t.Fatalf("expected %d, got %d", reqtest.code, resp.StatusCode)
			}
		}
		viper.Reset()
	}

	t.Log("invalid (PUT /write/foo) w/o data")
	{
		viper.Set(config.KeyListen, ":2609")
		viper.Set(config.KeyStatsdDisabled, true)
		viper.Set(config.KeyPluginDir, "testdata/")
		p, perr := plugins.New(context.Background())
		if perr != nil {
			t.Fatalf("expected NO error, got (%s)", perr)
		}
		s, err := New(nil, p, nil)
		if err != nil {
			t.Fatalf("expected NO error, got (%s)", err)
		}
		if s == nil {
			t.Fatal("expected NOT nil")
		}

		req := httptest.NewRequest("PUT", "/write/foo", nil)
		w := httptest.NewRecorder()
		s.router(w, req)
		resp := w.Result()
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected %d, got %d", http.StatusBadRequest, resp.StatusCode)
		}
		viper.Reset()
	}

	t.Log("OK (PUT /write/foo) w/data")
	{
		viper.Set(config.KeyListen, ":2609")
		s, err := New(nil, nil, nil)
		if err != nil {
			t.Fatalf("expected NO error, got (%s)", err)
		}
		if s == nil {
			t.Fatal("expected NOT nil")
		}
		reqBody := bytes.NewReader([]byte(`{"test":{"_type":"i", "_value":1}}`))
		req := httptest.NewRequest("PUT", "/write/foo", reqBody)
		w := httptest.NewRecorder()
		s.router(w, req)
		resp := w.Result()
		if resp.StatusCode != http.StatusNoContent {
			t.Fatalf("expected %d, got %d", http.StatusNoContent, resp.StatusCode)
		}
	}
}
<commit_msg>stub builtins for testing<commit_after>// Copyright © 2017 Circonus, Inc. <support@circonus.com>
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

package server

import (
	"bytes"
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/circonus-labs/circonus-agent/internal/builtins"
	"github.com/circonus-labs/circonus-agent/internal/config"
	"github.com/circonus-labs/circonus-agent/internal/plugins"
	"github.com/rs/zerolog"
	"github.com/spf13/viper"
)

func TestRouter(t *testing.T) {
	t.Log("Testing router")
	zerolog.SetGlobalLevel(zerolog.Disabled)

	t.Log("bad methods")
	{
		methods := []string{
			"CONNECT",
			"DELETE",
			"HEAD",
			"OPTIONS",
			"TRACE",
		}
		viper.Set(config.KeyListen, ":2609")
		s, err := New(nil, nil, nil)
		if err != nil {
			t.Fatalf("expected NO error, got (%s)", err)
		}
		if s == nil {
			t.Fatal("expected NOT nil")
		}

		for _, method := range methods {
			t.Logf("Method not allowed (%s)", method)
			req := httptest.NewRequest(method, "/", nil)
			w := httptest.NewRecorder()
			s.router(w, req)
			resp := w.Result()
			if resp.StatusCode != http.StatusMethodNotAllowed {
				t.Fatalf("expected %d, got %d", http.StatusMethodNotAllowed, resp.StatusCode)
			}
		}
		viper.Reset()
	}

	t.Log("invalid paths")
	{
		viper.Set(config.KeyListen, ":2609")
		viper.Set(config.KeyPluginDir, "testdata/")
		b, berr := builtins.New()
		if berr != nil {
			t.Fatalf("expected no error, got (%s)", berr)
		}
		p, perr := plugins.New(context.Background())
		if perr != nil {
			t.Fatalf("expected NO error, got (%s)", perr)
		}
		reqtests := []struct {
			method string
			path   string
		}{
			{"GET", "/invalid"},
			{"GET", "/run/invalid"},
			{"GET", "/inventory/invalid"},
			{"POST", "/invalid"},
			{"PUT", "/invalid"},
			{"PUT", "/write/"}, // /write/ must be followed by an id/name to use as "plugin namespace"
		}
		s, err := New(b, p, nil)
		if err != nil {
			t.Fatalf("expected NO error, got (%s)", err)
		}
		if s == nil {
			t.Fatal("expected NOT nil")
		}
		for _, reqtest := range reqtests {
			t.Logf("Invalid path (%s %s)", reqtest.method, reqtest.path)
			req := httptest.NewRequest(reqtest.method, reqtest.path, nil)
			w := httptest.NewRecorder()
			s.router(w, req)
			resp := w.Result()
			if resp.StatusCode != http.StatusNotFound {
				t.Fatalf("expected %d, got %d", http.StatusNotFound, resp.StatusCode)
			}
		}
		viper.Reset()
	}

	t.Log("valid")
	{
		viper.Set(config.KeyListen, ":2609")
		viper.Set(config.KeyStatsdDisabled, true)
		viper.Set(config.KeyPluginDir, "testdata/")
		b, berr := builtins.New()
		if berr != nil {
			t.Fatalf("expected no error, got (%s)", berr)
		}
		p, perr := plugins.New(context.Background())
		if perr != nil {
			t.Fatalf("expected NO error, got (%s)", perr)
		}
		s, err := New(b, p, nil)
		if err != nil {
			t.Fatalf("expected NO error, got (%s)", err)
		}
		if s == nil {
			t.Fatal("expected NOT nil")
		}
		reqtests := []struct {
			method string
			path   string
			code   int
		}{
			{"GET", "/", http.StatusOK},
			{"GET", "/run", http.StatusOK},
			{"GET", "/run/", http.StatusOK},
			{"GET", "/inventory", http.StatusOK},
			{"GET", "/inventory/", http.StatusOK},
			{"GET", "/stats", http.StatusOK},
			{"GET", "/stats/", http.StatusOK},
			{"GET", "/prom", http.StatusNoContent},
			{"GET", "/prom/", http.StatusNoContent},
		}
		for _, reqtest := range reqtests {
			t.Logf("OK path (%s %s)", reqtest.method, reqtest.path)
			req := httptest.NewRequest(reqtest.method, reqtest.path, nil)
			w := httptest.NewRecorder()
			s.router(w, req)
			resp := w.Result()
			if resp.StatusCode != reqtest.code {
				t.Fatalf("expected %d, got %d", reqtest.code, resp.StatusCode)
			}
		}
		viper.Reset()
	}

	t.Log("invalid (PUT /write/foo) w/o data")
	{
		viper.Set(config.KeyListen, ":2609")
		viper.Set(config.KeyStatsdDisabled, true)
		viper.Set(config.KeyPluginDir, "testdata/")
		b, berr := builtins.New()
		if berr != nil {
			t.Fatalf("expected no error, got (%s)", berr)
		}
		p, perr := plugins.New(context.Background())
		if perr != nil {
			t.Fatalf("expected NO error, got (%s)", perr)
		}
		s, err := New(b, p, nil)
		if err != nil {
			t.Fatalf("expected NO error, got (%s)", err)
		}
		if s == nil {
			t.Fatal("expected NOT nil")
		}

		req := httptest.NewRequest("PUT", "/write/foo", nil)
		w := httptest.NewRecorder()
		s.router(w, req)
		resp := w.Result()
		if resp.StatusCode != http.StatusBadRequest {
			t.Fatalf("expected %d, got %d", http.StatusBadRequest, resp.StatusCode)
		}
		viper.Reset()
	}

	t.Log("OK (PUT /write/foo) w/data")
	{
		viper.Set(config.KeyListen, ":2609")
		s, err := New(nil, nil, nil)
		if err != nil {
			t.Fatalf("expected NO error, got (%s)", err)
		}
		if s == nil {
			t.Fatal("expected NOT nil")
		}
		reqBody := bytes.NewReader([]byte(`{"test":{"_type":"i", "_value":1}}`))
		req := httptest.NewRequest("PUT", "/write/foo", reqBody)
		w := httptest.NewRecorder()
		s.router(w, req)
		resp := w.Result()
		if resp.StatusCode != http.StatusNoContent {
			t.Fatalf("expected %d, got %d", http.StatusNoContent, resp.StatusCode)
		}
	}
}
<|endoftext|>


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

package gl

// #include "gl.h"
//
// // Workaround for https://github.com/go-gl/gl/issues/104
// void gogl_glGetShaderSource(GLuint shader, GLsizei bufsize, GLsizei* len, GLchar* source) {
//     glGetShaderSource(shader, bufsize, len, source);
// }
//
import "C"

// Shader

type Shader Object

func CreateShader(type_ GLenum) Shader { return Shader(C.glCreateShader(C.GLenum(type_))) }

func (shader Shader) Delete() { C.glDeleteShader(C.GLuint(shader)) }

func (shader Shader) GetInfoLog() string {
	var length C.GLint
	C.glGetShaderiv(C.GLuint(shader), C.GLenum(INFO_LOG_LENGTH), &length)
	// length is buffer size including null character

	if length > 1 {
		log := C.malloc(C.size_t(length))
		defer C.free(log)
		C.glGetShaderInfoLog(C.GLuint(shader), C.GLsizei(length), nil, (*C.GLchar)(log))
		return C.GoString((*C.char)(log))
	}
	return ""
}

func (shader Shader) GetSource() string {
	var length C.GLint
	C.glGetShaderiv(C.GLuint(shader), C.GLenum(SHADER_SOURCE_LENGTH), &length)

	log := C.malloc(C.size_t(length + 1))
	C.gogl_glGetShaderSource(C.GLuint(shader), C.GLsizei(length), nil, (*C.GLchar)(log))

	defer C.free(log)

	if length > 1 {
		log := C.malloc(C.size_t(length + 1))
		defer C.free(log)
		C.gogl_glGetShaderSource(C.GLuint(shader), C.GLsizei(length), nil, (*C.GLchar)(log))
		return C.GoString((*C.char)(log))
	}
	return ""
}

func (shader Shader) Source(source string) {

	csource := glString(source)
	defer freeString(csource)

	var one C.GLint = C.GLint(len(source))

	C.glShaderSource(C.GLuint(shader), 1, &csource, &one)
}

func (shader Shader) Compile() { C.glCompileShader(C.GLuint(shader)) }

func (shader Shader) Get(param GLenum) int {
	var rv C.GLint

	C.glGetShaderiv(C.GLuint(shader), C.GLenum(param), &rv)
	return int(rv)
}
<commit_msg>Support for multiple string inputs to shader.Source().<commit_after>// Copyright 2012 The go-gl Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gl

// #include "gl.h"
//
// // Workaround for https://github.com/go-gl/gl/issues/104
// void gogl_glGetShaderSource(GLuint shader, GLsizei bufsize, GLsizei* len, GLchar* source) {
//     glGetShaderSource(shader, bufsize, len, source);
// }
//
import "C"
import "unsafe"

// Shader

type Shader Object

func CreateShader(type_ GLenum) Shader { return Shader(C.glCreateShader(C.GLenum(type_))) }

func (shader Shader) Delete() { C.glDeleteShader(C.GLuint(shader)) }

func (shader Shader) GetInfoLog() string {
	var length C.GLint
	C.glGetShaderiv(C.GLuint(shader), C.GLenum(INFO_LOG_LENGTH), &length)
	// length is buffer size including null character

	if length > 1 {
		log := C.malloc(C.size_t(length))
		defer C.free(log)
		C.glGetShaderInfoLog(C.GLuint(shader), C.GLsizei(length), nil, (*C.GLchar)(log))
		return C.GoString((*C.char)(log))
	}
	return ""
}

func (shader Shader) GetSource() string {
	var length C.GLint
	C.glGetShaderiv(C.GLuint(shader), C.GLenum(SHADER_SOURCE_LENGTH), &length)

	log := C.malloc(C.size_t(length + 1))
	C.gogl_glGetShaderSource(C.GLuint(shader), C.GLsizei(length), nil, (*C.GLchar)(log))

	defer C.free(log)

	if length > 1 {
		log := C.malloc(C.size_t(length + 1))
		defer C.free(log)
		C.gogl_glGetShaderSource(C.GLuint(shader), C.GLsizei(length), nil, (*C.GLchar)(log))
		return C.GoString((*C.char)(log))
	}
	return ""
}

func (shader Shader) Source(source ...string) {
	count := C.GLsizei(len(source))
	cstrings := make([]*C.GLchar, count)
	length := make([]C.GLint, count)

	for i, s := range source {
		csource := glString(s)
		cstrings[i] = csource
		length[i] = C.GLint(len(s))
		defer freeString(csource)
	}

	C.glShaderSource(C.GLuint(shader), count, (**_Ctype_GLchar)(unsafe.Pointer(&cstrings[0])), (*_Ctype_GLint)(unsafe.Pointer(&length[0])))
}

func (shader Shader) Compile() { C.glCompileShader(C.GLuint(shader)) }

func (shader Shader) Get(param GLenum) int {
	var rv C.GLint

	C.glGetShaderiv(C.GLuint(shader), C.GLenum(param), &rv)
	return int(rv)
}
<|endoftext|>


<commit_before>package brightbox

import (
	"bytes"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/brightbox/gobrightbox"
	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceBrightboxLoadBalancer() *schema.Resource {
	return &schema.Resource{
		Create: resourceBrightboxLoadBalancerCreate,
		Read:   resourceBrightboxLoadBalancerRead,
		Update: resourceBrightboxLoadBalancerUpdate,
		Delete: resourceBrightboxLoadBalancerDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"policy": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"certificate_pem": &schema.Schema{
				Type:      schema.TypeString,
				Optional:  true,
				StateFunc: hash_string,
			},
			"certificate_private_key": &schema.Schema{
				Type:      schema.TypeString,
				Optional:  true,
				StateFunc: hash_string,
			},
			"sslv3": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			"status": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"locked": &schema.Schema{
				Type:     schema.TypeBool,
				Computed: true,
			},
			"buffer_size": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"nodes": &schema.Schema{
				Type:     schema.TypeSet,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Optional: true,
				Computed: true,
				Set:      schema.HashString,
			},
			"listener": &schema.Schema{
				Type:     schema.TypeSet,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"protocol": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"in": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},

						"out": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},

						"timeout": &schema.Schema{
							Type:     schema.TypeInt,
							Optional: true,
							Default:  50000,
						},
					},
				},
				Set: resourceBrightboxLbListenerHash,
			},
			"healthcheck": &schema.Schema{
				Type:     schema.TypeList,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"type": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"port": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},
						"request": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
							Computed: true,
						},
						"interval": &schema.Schema{
							Type:     schema.TypeInt,
							Optional: true,
							Computed: true,
						},
						"timeout": &schema.Schema{
							Type:     schema.TypeInt,
							Optional: true,
							Computed: true,
						},
						"threshold_up": &schema.Schema{
							Type:     schema.TypeInt,
							Optional: true,
							Computed: true,
						},
						"threshold_down": &schema.Schema{
							Type:     schema.TypeInt,
							Optional: true,
							Computed: true,
						},
					},
				},
			},
		},
	}
}

func resourceBrightboxLbListenerHash(
	v interface{},
) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%d-", m["in"].(int)))
	buf.WriteString(fmt.Sprintf("%s-",
		strings.ToLower(m["protocol"].(string))))
	buf.WriteString(fmt.Sprintf("%d-", m["out"].(int)))
	buf.WriteString(fmt.Sprintf("%d-", m["timeout"].(int)))

	return hashcode.String(buf.String())
}

func setLoadBalancerAttributes(
	d *schema.ResourceData,
	load_balancer *brightbox.LoadBalancer,
) {
	d.Set("name", load_balancer.Name)
	d.Set("status", load_balancer.Status)
	d.Set("locked", load_balancer.Locked)
	d.Set("policy", load_balancer.Policy)
	d.Set("buffer_size", load_balancer.BufferSize)

	nodeIds := make([]string, 0, len(load_balancer.Nodes))
	for _, node := range load_balancer.Nodes {
		nodeIds = append(nodeIds, node.Id)
	}
	d.Set("nodes", nodeIds)

	cipIds := make([]string, 0, len(load_balancer.CloudIPs))
	for _, cip := range load_balancer.CloudIPs {
		cipIds = append(cipIds, cip.Id)
	}
	d.Set("cloud_ips", cipIds)

	listeners := make([]map[string]interface{}, 0, len(load_balancer.Listeners))
	for _, listener := range load_balancer.Listeners {
		l := map[string]interface{}{
			"protocol": listener.Protocol,
			"in":       listener.In,
			"out":      listener.Out,
			"timeout":  listener.Timeout,
		}

		listeners = append(listeners, l)
	}
	d.Set("listener", listeners)
	log.Printf("[DEBUG] Healthcheck details are %#v", load_balancer.Healthcheck)
	healthchecks := make([]map[string]interface{}, 0, 1)
	chk := map[string]interface{}{
		"type":           load_balancer.Healthcheck.Type,
		"port":           load_balancer.Healthcheck.Port,
		"request":        load_balancer.Healthcheck.Request,
		"interval":       load_balancer.Healthcheck.Interval,
		"timeout":        load_balancer.Healthcheck.Timeout,
		"threshold_up":   load_balancer.Healthcheck.ThresholdUp,
		"threshold_down": load_balancer.Healthcheck.ThresholdDown,
	}
	healthchecks = append(healthchecks, chk)
	d.Set("healthcheck", healthchecks)
}

func loadBalancerStateRefresh(client *brightbox.Client, loadBalancerID string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		loadBalancer, err := client.LoadBalancer(loadBalancerID)
		if err != nil {
			log.Printf("Error on Load Balancer State Refresh: %s", err)
			return nil, "", err
		}
		return loadBalancer, loadBalancer.Status, nil
	}
}

func resourceBrightboxLoadBalancerCreate(
	d *schema.ResourceData,
	meta interface{},
) error {
	client := meta.(*CompositeClient).ApiClient

	log.Printf("[DEBUG] Load Balancer create called")
	load_balancer_opts := &brightbox.LoadBalancerOptions{}

	err := addUpdateableLoadBalancerOptions(d, load_balancer_opts)
	if err != nil {
		return err
	}

	log.Printf("[DEBUG] Load Balancer create configuration %#v", load_balancer_opts)
	output_load_balancer_options(load_balancer_opts)

	load_balancer, err := client.CreateLoadBalancer(load_balancer_opts)
	if err != nil {
		return fmt.Errorf("Error creating server: %s", err)
	}

	d.SetId(load_balancer.Id)

	log.Printf("[INFO] Waiting for Load Balancer (%s) to become available", d.Id())

	stateConf := resource.StateChangeConf{
		Pending:    []string{"creating"},
		Target:     []string{"active"},
		Refresh:    loadBalancerStateRefresh(client, load_balancer.Id),
		Timeout:    5 * time.Minute,
		Delay:      10 * time.Second,
		MinTimeout: 3 * time.Second,
	}
	active_load_balancer, err := stateConf.WaitForState()
	if err != nil {
		return err
	}

	setLoadBalancerAttributes(d, active_load_balancer.(*brightbox.LoadBalancer))

	return nil
}

func resourceBrightboxLoadBalancerRead(
	d *schema.ResourceData,
	meta interface{},
) error {
	client := meta.(*CompositeClient).ApiClient

	log.Printf("[DEBUG] Load Balancer read called for %s", d.Id())
	load_balancer, err := client.LoadBalancer(d.Id())
	if err != nil {
		if strings.HasPrefix(err.Error(), "missing_resource:") {
			log.Printf("[WARN] Load Balancer not found, removing from state: %s", d.Id())
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error retrieving Load Balancer details: %s", err)
	}

	setLoadBalancerAttributes(d, load_balancer)

	return nil
}

func resourceBrightboxLoadBalancerUpdate(
	d *schema.ResourceData,
	meta interface{},
) error {
	client := meta.(*CompositeClient).ApiClient

	log.Printf("[DEBUG] Load Balancer update called for %s", d.Id())
	load_balancer_opts := &brightbox.LoadBalancerOptions{
		Id: d.Id(),
	}

	err := addUpdateableLoadBalancerOptions(d, load_balancer_opts)
	if err != nil {
		return err
	}

	log.Printf("[DEBUG] Load Balancer update configuration %#v", load_balancer_opts)
	output_load_balancer_options(load_balancer_opts)

	load_balancer, err := client.UpdateLoadBalancer(load_balancer_opts)
	if err != nil {
		return fmt.Errorf("Error updating load_balancer: %s", err)
	}

	setLoadBalancerAttributes(d, load_balancer)

	return nil
}

func resourceBrightboxLoadBalancerDelete(
	d *schema.ResourceData,
	meta interface{},
) error {
	client := meta.(*CompositeClient).ApiClient

	log.Printf("[DEBUG] Load Balancer delete called for %s", d.Id())
	err := client.DestroyLoadBalancer(d.Id())
	if err != nil {
		return fmt.Errorf("Error deleting Load Balancer: %s", err)
	}
	stateConf := resource.StateChangeConf{
		Pending:    []string{"deleting", "active"},
		Target:     []string{"deleted"},
		Refresh:    loadBalancerStateRefresh(client, d.Id()),
		Timeout:    5 * time.Minute,
		Delay:      10 * time.Second,
		MinTimeout: 3 * time.Second,
	}
	_, err = stateConf.WaitForState()
	if err != nil {
		return err
	}
	return nil
}

func addUpdateableLoadBalancerOptions(
	d *schema.ResourceData,
	opts *brightbox.LoadBalancerOptions,
) error {

	assign_string(d, &opts.Name, "name")
	assign_string(d, &opts.Policy, "policy")
	assign_string(d, &opts.CertificatePem, "certificate_pem")
	assign_string(d, &opts.CertificatePrivateKey, "certificate_private_key")
	assign_int(d, &opts.BufferSize, "buffer_size")
	assign_bool(d, &opts.SslV3, "sslv3")
	assign_listeners(d, &opts.Listeners)
	assign_nodes(d, &opts.Nodes)
	return assign_healthcheck(d, &opts.Healthcheck)
}

func assign_healthcheck(d *schema.ResourceData, target **brightbox.LoadBalancerHealthcheck) error {
	if d.HasChange("healthcheck") {
		hc := d.Get("healthcheck").([]interface{})
		if len(hc) > 1 {
			return fmt.Errorf("Only one health check per Load Balancer is supported")
		} else if len(hc) > 0 {
			check := hc[0].(map[string]interface{})
			temp := brightbox.LoadBalancerHealthcheck{
				Type: check["type"].(string),
				Port: check["port"].(int),
			}
			if attr, ok := check["request"]; ok {
				temp.Request = attr.(string)
			}
			if attr, ok := check["interval"]; ok {
				temp.Interval = attr.(int)
			}
			if attr, ok := check["timeout"]; ok {
				temp.Timeout = attr.(int)
			}
			if attr, ok := check["threshold_up"]; ok {
				temp.ThresholdUp = attr.(int)
			}
			if attr, ok := check["threshold_down"]; ok {
				temp.ThresholdDown = attr.(int)
			}
			*target = &temp
		}
	}
	return nil
}

func assign_listeners(d *schema.ResourceData, target **[]brightbox.LoadBalancerListener) {
	if d.HasChange("listener") {
		*target = expandListeners(d.Get("listener").(*schema.Set).List())
	}
}

func assign_nodes(d *schema.ResourceData, target **[]brightbox.LoadBalancerNode) {
	if d.HasChange("nodes") {
		*target = expandNodes(d.Get("nodes").(*schema.Set).List())
	}
}

func expandListeners(configured []interface{}) *[]brightbox.LoadBalancerListener {
	listeners := make([]brightbox.LoadBalancerListener, len(configured))

	for i, listen_source := range configured {
		data := listen_source.(map[string]interface{})
		listeners[i].Protocol = data["protocol"].(string)
		listeners[i].In = data["in"].(int)
		listeners[i].Out = data["out"].(int)
		if attr, ok := data["timeout"]; ok {
			listeners[i].Timeout = attr.(int)
		}
	}
	return &listeners
}

func expandNodes(configured []interface{}) *[]brightbox.LoadBalancerNode {
	nodes := make([]brightbox.LoadBalancerNode, len(configured))

	for i, data := range configured {
		nodes[i].Node = data.(string)
	}
	return &nodes
}

func output_load_balancer_options(opts *brightbox.LoadBalancerOptions) {
	if opts.Name != nil {
		log.Printf("[DEBUG] Load Balancer Name %v", *opts.Name)
	}
	if opts.Nodes != nil {
		log.Printf("[DEBUG] Load Balancer Nodes %#v", *opts.Nodes)
	}
	if opts.Policy != nil {
		log.Printf("[DEBUG] Load Balancer Policy %v", *opts.Policy)
	}
	if opts.BufferSize != nil {
		log.Printf("[DEBUG] Load Balancer BufferSize %v", *opts.BufferSize)
	}
	if opts.Listeners != nil {
		log.Printf("[DEBUG] Load Balancer Listeners %#v", *opts.Listeners)
	}
	if opts.Healthcheck != nil {
		log.Printf("[DEBUG] Load Balancer Healthcheck %#v", *opts.Healthcheck)
	}
	if opts.CertificatePem != nil {
		log.Printf("[DEBUG] Load Balancer CertificatePem %v", *opts.CertificatePem)
	}
	if opts.CertificatePrivateKey != nil {
		log.Printf("[DEBUG] Load Balancer CertificatePrivateKey %v", *opts.CertificatePrivateKey)
	}
	if opts.SslV3 != nil {
		log.Printf("[DEBUG] Load Balancer SslV3 %v", *opts.SslV3)
	}
}
<commit_msg>Enforce healthcheck max items in schema<commit_after>package brightbox

import (
	"bytes"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/brightbox/gobrightbox"
	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceBrightboxLoadBalancer() *schema.Resource {
	return &schema.Resource{
		Create: resourceBrightboxLoadBalancerCreate,
		Read:   resourceBrightboxLoadBalancerRead,
		Update: resourceBrightboxLoadBalancerUpdate,
		Delete: resourceBrightboxLoadBalancerDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"policy": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"certificate_pem": &schema.Schema{
				Type:      schema.TypeString,
				Optional:  true,
				StateFunc: hash_string,
			},
			"certificate_private_key": &schema.Schema{
				Type:      schema.TypeString,
				Optional:  true,
				StateFunc: hash_string,
			},
			"sslv3": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			"status": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"locked": &schema.Schema{
				Type:     schema.TypeBool,
				Computed: true,
			},
			"buffer_size": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"nodes": &schema.Schema{
				Type:     schema.TypeSet,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Optional: true,
				Computed: true,
				Set:      schema.HashString,
			},
			"listener": &schema.Schema{
				Type:     schema.TypeSet,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"protocol": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"in": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},

						"out": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},

						"timeout": &schema.Schema{
							Type:     schema.TypeInt,
							Optional: true,
							Default:  50000,
						},
					},
				},
				Set: resourceBrightboxLbListenerHash,
			},
			"healthcheck": &schema.Schema{
				Type:     schema.TypeList,
				MaxItems: 1,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"type": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"port": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
						},
						"request": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
							Computed: true,
						},
						"interval": &schema.Schema{
							Type:     schema.TypeInt,
							Optional: true,
							Computed: true,
						},
						"timeout": &schema.Schema{
							Type:     schema.TypeInt,
							Optional: true,
							Computed: true,
						},
						"threshold_up": &schema.Schema{
							Type:     schema.TypeInt,
							Optional: true,
							Computed: true,
						},
						"threshold_down": &schema.Schema{
							Type:     schema.TypeInt,
							Optional: true,
							Computed: true,
						},
					},
				},
			},
		},
	}
}

func resourceBrightboxLbListenerHash(
	v interface{},
) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%d-", m["in"].(int)))
	buf.WriteString(fmt.Sprintf("%s-",
		strings.ToLower(m["protocol"].(string))))
	buf.WriteString(fmt.Sprintf("%d-", m["out"].(int)))
	buf.WriteString(fmt.Sprintf("%d-", m["timeout"].(int)))

	return hashcode.String(buf.String())
}

func setLoadBalancerAttributes(
	d *schema.ResourceData,
	load_balancer *brightbox.LoadBalancer,
) {
	d.Set("name", load_balancer.Name)
	d.Set("status", load_balancer.Status)
	d.Set("locked", load_balancer.Locked)
	d.Set("policy", load_balancer.Policy)
	d.Set("buffer_size", load_balancer.BufferSize)

	nodeIds := make([]string, 0, len(load_balancer.Nodes))
	for _, node := range load_balancer.Nodes {
		nodeIds = append(nodeIds, node.Id)
	}
	d.Set("nodes", nodeIds)

	cipIds := make([]string, 0, len(load_balancer.CloudIPs))
	for _, cip := range load_balancer.CloudIPs {
		cipIds = append(cipIds, cip.Id)
	}
	d.Set("cloud_ips", cipIds)

	listeners := make([]map[string]interface{}, 0, len(load_balancer.Listeners))
	for _, listener := range load_balancer.Listeners {
		l := map[string]interface{}{
			"protocol": listener.Protocol,
			"in":       listener.In,
			"out":      listener.Out,
			"timeout":  listener.Timeout,
		}

		listeners = append(listeners, l)
	}
	d.Set("listener", listeners)
	log.Printf("[DEBUG] Healthcheck details are %#v", load_balancer.Healthcheck)
	healthchecks := make([]map[string]interface{}, 0, 1)
	chk := map[string]interface{}{
		"type":           load_balancer.Healthcheck.Type,
		"port":           load_balancer.Healthcheck.Port,
		"request":        load_balancer.Healthcheck.Request,
		"interval":       load_balancer.Healthcheck.Interval,
		"timeout":        load_balancer.Healthcheck.Timeout,
		"threshold_up":   load_balancer.Healthcheck.ThresholdUp,
		"threshold_down": load_balancer.Healthcheck.ThresholdDown,
	}
	healthchecks = append(healthchecks, chk)
	d.Set("healthcheck", healthchecks)
}

func loadBalancerStateRefresh(client *brightbox.Client, loadBalancerID string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		loadBalancer, err := client.LoadBalancer(loadBalancerID)
		if err != nil {
			log.Printf("Error on Load Balancer State Refresh: %s", err)
			return nil, "", err
		}
		return loadBalancer, loadBalancer.Status, nil
	}
}

func resourceBrightboxLoadBalancerCreate(
	d *schema.ResourceData,
	meta interface{},
) error {
	client := meta.(*CompositeClient).ApiClient

	log.Printf("[DEBUG] Load Balancer create called")
	load_balancer_opts := &brightbox.LoadBalancerOptions{}

	err := addUpdateableLoadBalancerOptions(d, load_balancer_opts)
	if err != nil {
		return err
	}

	log.Printf("[DEBUG] Load Balancer create configuration %#v", load_balancer_opts)
	output_load_balancer_options(load_balancer_opts)

	load_balancer, err := client.CreateLoadBalancer(load_balancer_opts)
	if err != nil {
		return fmt.Errorf("Error creating server: %s", err)
	}

	d.SetId(load_balancer.Id)

	log.Printf("[INFO] Waiting for Load Balancer (%s) to become available", d.Id())

	stateConf := resource.StateChangeConf{
		Pending:    []string{"creating"},
		Target:     []string{"active"},
		Refresh:    loadBalancerStateRefresh(client, load_balancer.Id),
		Timeout:    5 * time.Minute,
		Delay:      10 * time.Second,
		MinTimeout: 3 * time.Second,
	}
	active_load_balancer, err := stateConf.WaitForState()
	if err != nil {
		return err
	}

	setLoadBalancerAttributes(d, active_load_balancer.(*brightbox.LoadBalancer))

	return nil
}

func resourceBrightboxLoadBalancerRead(
	d *schema.ResourceData,
	meta interface{},
) error {
	client := meta.(*CompositeClient).ApiClient

	log.Printf("[DEBUG] Load Balancer read called for %s", d.Id())
	load_balancer, err := client.LoadBalancer(d.Id())
	if err != nil {
		if strings.HasPrefix(err.Error(), "missing_resource:") {
			log.Printf("[WARN] Load Balancer not found, removing from state: %s", d.Id())
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error retrieving Load Balancer details: %s", err)
	}

	setLoadBalancerAttributes(d, load_balancer)

	return nil
}

func resourceBrightboxLoadBalancerUpdate(
	d *schema.ResourceData,
	meta interface{},
) error {
	client := meta.(*CompositeClient).ApiClient

	log.Printf("[DEBUG] Load Balancer update called for %s", d.Id())
	load_balancer_opts := &brightbox.LoadBalancerOptions{
		Id: d.Id(),
	}

	err := addUpdateableLoadBalancerOptions(d, load_balancer_opts)
	if err != nil {
		return err
	}

	log.Printf("[DEBUG] Load Balancer update configuration %#v", load_balancer_opts)
	output_load_balancer_options(load_balancer_opts)

	load_balancer, err := client.UpdateLoadBalancer(load_balancer_opts)
	if err != nil {
		return fmt.Errorf("Error updating load_balancer: %s", err)
	}

	setLoadBalancerAttributes(d, load_balancer)

	return nil
}

func resourceBrightboxLoadBalancerDelete(
	d *schema.ResourceData,
	meta interface{},
) error {
	client := meta.(*CompositeClient).ApiClient

	log.Printf("[DEBUG] Load Balancer delete called for %s", d.Id())
	err := client.DestroyLoadBalancer(d.Id())
	if err != nil {
		return fmt.Errorf("Error deleting Load Balancer: %s", err)
	}
	stateConf := resource.StateChangeConf{
		Pending:    []string{"deleting", "active"},
		Target:     []string{"deleted"},
		Refresh:    loadBalancerStateRefresh(client, d.Id()),
		Timeout:    5 * time.Minute,
		Delay:      10 * time.Second,
		MinTimeout: 3 * time.Second,
	}
	_, err = stateConf.WaitForState()
	if err != nil {
		return err
	}
	return nil
}

func addUpdateableLoadBalancerOptions(
	d *schema.ResourceData,
	opts *brightbox.LoadBalancerOptions,
) error {

	assign_string(d, &opts.Name, "name")
	assign_string(d, &opts.Policy, "policy")
	assign_string(d, &opts.CertificatePem, "certificate_pem")
	assign_string(d, &opts.CertificatePrivateKey, "certificate_private_key")
	assign_int(d, &opts.BufferSize, "buffer_size")
	assign_bool(d, &opts.SslV3, "sslv3")
	assign_listeners(d, &opts.Listeners)
	assign_nodes(d, &opts.Nodes)
	return assign_healthcheck(d, &opts.Healthcheck)
}

func assign_healthcheck(d *schema.ResourceData, target **brightbox.LoadBalancerHealthcheck) error {
	if d.HasChange("healthcheck") {
		hc := d.Get("healthcheck").([]interface{})
		check := hc[0].(map[string]interface{})
		temp := brightbox.LoadBalancerHealthcheck{
			Type: check["type"].(string),
			Port: check["port"].(int),
		}
		if attr, ok := check["request"]; ok {
			temp.Request = attr.(string)
		}
		if attr, ok := check["interval"]; ok {
			temp.Interval = attr.(int)
		}
		if attr, ok := check["timeout"]; ok {
			temp.Timeout = attr.(int)
		}
		if attr, ok := check["threshold_up"]; ok {
			temp.ThresholdUp = attr.(int)
		}
		if attr, ok := check["threshold_down"]; ok {
			temp.ThresholdDown = attr.(int)
		}
		*target = &temp
	}
	return nil
}

func assign_listeners(d *schema.ResourceData, target **[]brightbox.LoadBalancerListener) {
	if d.HasChange("listener") {
		*target = expandListeners(d.Get("listener").(*schema.Set).List())
	}
}

func assign_nodes(d *schema.ResourceData, target **[]brightbox.LoadBalancerNode) {
	if d.HasChange("nodes") {
		*target = expandNodes(d.Get("nodes").(*schema.Set).List())
	}
}

func expandListeners(configured []interface{}) *[]brightbox.LoadBalancerListener {
	listeners := make([]brightbox.LoadBalancerListener, len(configured))

	for i, listen_source := range configured {
		data := listen_source.(map[string]interface{})
		listeners[i].Protocol = data["protocol"].(string)
		listeners[i].In = data["in"].(int)
		listeners[i].Out = data["out"].(int)
		if attr, ok := data["timeout"]; ok {
			listeners[i].Timeout = attr.(int)
		}
	}
	return &listeners
}

func expandNodes(configured []interface{}) *[]brightbox.LoadBalancerNode {
	nodes := make([]brightbox.LoadBalancerNode, len(configured))

	for i, data := range configured {
		nodes[i].Node = data.(string)
	}
	return &nodes
}

func output_load_balancer_options(opts *brightbox.LoadBalancerOptions) {
	if opts.Name != nil {
		log.Printf("[DEBUG] Load Balancer Name %v", *opts.Name)
	}
	if opts.Nodes != nil {
		log.Printf("[DEBUG] Load Balancer Nodes %#v", *opts.Nodes)
	}
	if opts.Policy != nil {
		log.Printf("[DEBUG] Load Balancer Policy %v", *opts.Policy)
	}
	if opts.BufferSize != nil {
		log.Printf("[DEBUG] Load Balancer BufferSize %v", *opts.BufferSize)
	}
	if opts.Listeners != nil {
		log.Printf("[DEBUG] Load Balancer Listeners %#v", *opts.Listeners)
	}
	if opts.Healthcheck != nil {
		log.Printf("[DEBUG] Load Balancer Healthcheck %#v", *opts.Healthcheck)
	}
	if opts.CertificatePem != nil {
		log.Printf("[DEBUG] Load Balancer CertificatePem %v", *opts.CertificatePem)
	}
	if opts.CertificatePrivateKey != nil {
		log.Printf("[DEBUG] Load Balancer CertificatePrivateKey %v", *opts.CertificatePrivateKey)
	}
	if opts.SslV3 != nil {
		log.Printf("[DEBUG] Load Balancer SslV3 %v", *opts.SslV3)
	}
}
<|endoftext|>


<commit_before>package tasks

import (
	"fmt"
	"io/ioutil"
	"os"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/dnephin/dobi/config"
	"github.com/fsouza/go-dockerclient"
	"github.com/kballard/go-shellquote"
)

// CommandTask is a task which runs a command in a container to produce a
// file or set of files.
type CommandTask struct {
	baseTask
	config *config.CommandConfig
}

// NewCommandTask creates a new CommandTask object
func NewCommandTask(options taskOptions, conf *config.CommandConfig) *CommandTask {
	return &CommandTask{
		baseTask: baseTask{name: options.name},
		config:   conf,
	}
}

func (t *CommandTask) String() string {
	return fmt.Sprintf("CommandTask(name=%s, config=%s)", t.name, t.config)
}

func (t *CommandTask) logger() *log.Entry {
	return log.WithFields(log.Fields{
		"task":     "Command",
		"name":     t.name,
		"use":      t.config.Use,
		"command":  t.config.Command,
		"artifact": t.config.Artifact,
	})
}

// Run creates the host path if it doesn't already exist
func (t *CommandTask) Run(ctx *ExecuteContext) error {
	t.logger().Info("run")
	stale, err := t.isStale(ctx)
	if !stale || err != nil {
		return err
	}
	t.logger().Debug("artifact is stale")

	err = t.runContainer(ctx)
	if err != nil {
		return err
	}
	ctx.setModified(t.name)
	t.logger().Info("done")
	return nil
}

func (t *CommandTask) isStale(ctx *ExecuteContext) (bool, error) {
	if ctx.isModified(t.config.Dependencies()...) {
		return true, nil
	}

	if t.config.Artifact == "" {
		return true, nil
	}

	artifactLastModified, err := t.artifactLastModified()
	if err != nil {
		return true, err
	}

	volumesLastModified, err := t.volumesLastModified(ctx)
	if err != nil {
		return true, err
	}

	if artifactLastModified.Before(volumesLastModified) {
		t.logger().Debug("artifact older than volume files")
		return true, nil
	}

	image, err := ctx.tasks.images[t.config.Use].getImage(ctx)
	if err != nil {
		return true, err
	}
	if artifactLastModified.Before(image.Created) {
		t.logger().Debug("artifact older than image")
		return true, nil
	}
	return false, nil
}

func (t *CommandTask) artifactLastModified() (time.Time, error) {
	info, err := os.Stat(t.config.Artifact)
	// File or directory doesn't exist
	if err != nil {
		return time.Time{}, nil
	}

	if !info.IsDir() {
		return info.ModTime(), nil
	}

	return lastModified(t.config.Artifact)
}

// TODO: support a .volumeignore file used to ignore mtime of files
func (t *CommandTask) volumesLastModified(ctx *ExecuteContext) (time.Time, error) {
	volumePaths := []string{}
	ctx.tasks.EachVolume(t.config.Volumes, func(name string, volume *VolumeTask) {
		volumePaths = append(volumePaths, volume.config.Path)
	})
	return lastModified(volumePaths...)
}

func (t *CommandTask) volumeBinds(ctx *ExecuteContext) []string {
	binds := []string{}
	ctx.tasks.EachVolume(t.config.Volumes, func(name string, volume *VolumeTask) {
		binds = append(binds, volume.asBind())
	})
	return binds
}

func (t *CommandTask) runContainer(ctx *ExecuteContext) error {
	// TODO: move this to config resource validation?
	command, err := shellquote.Split(t.config.Command)
	if err != nil {
		return fmt.Errorf("Failed to parse command: %s", err)
	}

	// TODO: support other run options
	container, err := ctx.client.CreateContainer(docker.CreateContainerOptions{
		Name: fmt.Sprintf("%s-%s", ctx.environment.ExecID, t.name),
		Config: &docker.Config{
			Cmd:       command,
			Image:     ctx.tasks.images[t.config.Use].getImageName(ctx),
			OpenStdin: t.config.Interactive,
			Tty:       t.config.Interactive,
		},
		HostConfig: &docker.HostConfig{
			Binds:      t.volumeBinds(ctx),
			Privileged: t.config.Privileged,
		},
	})
	if err != nil {
		return fmt.Errorf("Failed creating container: %s", err)
	}

	if err := ctx.client.StartContainer(container.ID, nil); err != nil {
		return fmt.Errorf("Failed starting container: %s", err)
	}

	if err := ctx.client.AttachToContainer(docker.AttachToContainerOptions{
		Container: container.ID,
		// TODO: send this to a buffer for --quiet
		OutputStream: os.Stdout,
		ErrorStream:  os.Stderr,
		InputStream:  ioutil.NopCloser(os.Stdin),
		Logs:         false,
		Stream:       true,
		Stdin:        t.config.Interactive,
		RawTerminal:  t.config.Interactive,
		Stdout:       true,
		Stderr:       true,
	}); err != nil {
		return fmt.Errorf("Failed attaching to container: %s", err)
	}

	// TODO: stop container first if interactive?
	if err := ctx.client.RemoveContainer(docker.RemoveContainerOptions{
		ID:            container.ID,
		RemoveVolumes: true,
	}); err != nil {
		t.logger().WithFields(log.Fields{
			"container": container.ID,
			"error":     err.Error(),
		}).Warn("Failed to remove container")
	}
	return nil
}
<commit_msg>Set up signal handler.<commit_after>package tasks

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"syscall"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/dnephin/dobi/config"
	docker "github.com/fsouza/go-dockerclient"
	shellquote "github.com/kballard/go-shellquote"
)

// CommandTask is a task which runs a command in a container to produce a
// file or set of files.
type CommandTask struct {
	baseTask
	config *config.CommandConfig
}

// NewCommandTask creates a new CommandTask object
func NewCommandTask(options taskOptions, conf *config.CommandConfig) *CommandTask {
	return &CommandTask{
		baseTask: baseTask{name: options.name},
		config:   conf,
	}
}

func (t *CommandTask) String() string {
	return fmt.Sprintf("CommandTask(name=%s, config=%s)", t.name, t.config)
}

func (t *CommandTask) logger() *log.Entry {
	return log.WithFields(log.Fields{
		"task":     "Command",
		"name":     t.name,
		"use":      t.config.Use,
		"command":  t.config.Command,
		"artifact": t.config.Artifact,
	})
}

// Run creates the host path if it doesn't already exist
func (t *CommandTask) Run(ctx *ExecuteContext) error {
	t.logger().Info("run")
	stale, err := t.isStale(ctx)
	if !stale || err != nil {
		return err
	}
	t.logger().Debug("artifact is stale")

	err = t.runContainer(ctx)
	if err != nil {
		return err
	}
	ctx.setModified(t.name)
	t.logger().Info("done")
	return nil
}

func (t *CommandTask) isStale(ctx *ExecuteContext) (bool, error) {
	if ctx.isModified(t.config.Dependencies()...) {
		return true, nil
	}

	if t.config.Artifact == "" {
		return true, nil
	}

	artifactLastModified, err := t.artifactLastModified()
	if err != nil {
		return true, err
	}

	volumesLastModified, err := t.volumesLastModified(ctx)
	if err != nil {
		return true, err
	}

	if artifactLastModified.Before(volumesLastModified) {
		t.logger().Debug("artifact older than volume files")
		return true, nil
	}

	image, err := ctx.tasks.images[t.config.Use].getImage(ctx)
	if err != nil {
		return true, err
	}
	if artifactLastModified.Before(image.Created) {
		t.logger().Debug("artifact older than image")
		return true, nil
	}
	return false, nil
}

func (t *CommandTask) artifactLastModified() (time.Time, error) {
	info, err := os.Stat(t.config.Artifact)
	// File or directory doesn't exist
	if err != nil {
		return time.Time{}, nil
	}

	if !info.IsDir() {
		return info.ModTime(), nil
	}

	return lastModified(t.config.Artifact)
}

// TODO: support a .volumeignore file used to ignore mtime of files
func (t *CommandTask) volumesLastModified(ctx *ExecuteContext) (time.Time, error) {
	volumePaths := []string{}
	ctx.tasks.EachVolume(t.config.Volumes, func(name string, volume *VolumeTask) {
		volumePaths = append(volumePaths, volume.config.Path)
	})
	return lastModified(volumePaths...)
}

func (t *CommandTask) volumeBinds(ctx *ExecuteContext) []string {
	binds := []string{}
	ctx.tasks.EachVolume(t.config.Volumes, func(name string, volume *VolumeTask) {
		binds = append(binds, volume.asBind())
	})
	return binds
}

func (t *CommandTask) runContainer(ctx *ExecuteContext) error {
	// TODO: move this to config resource validation?
	command, err := shellquote.Split(t.config.Command)
	if err != nil {
		return fmt.Errorf("Failed to parse command: %s", err)
	}

	// TODO: support other run options
	container, err := ctx.client.CreateContainer(docker.CreateContainerOptions{
		Name: fmt.Sprintf("%s-%s", ctx.environment.ExecID, t.name),
		Config: &docker.Config{
			Cmd:       command,
			Image:     ctx.tasks.images[t.config.Use].getImageName(ctx),
			OpenStdin: t.config.Interactive,
			Tty:       t.config.Interactive,
		},
		HostConfig: &docker.HostConfig{
			Binds:      t.volumeBinds(ctx),
			Privileged: t.config.Privileged,
		},
	})
	if err != nil {
		return fmt.Errorf("Failed creating container: %s", err)
	}

	chanSig := t.forwardSignals(ctx.client, container.ID)
	defer signal.Stop(chanSig)

	if err := ctx.client.StartContainer(container.ID, nil); err != nil {
		return fmt.Errorf("Failed starting container: %s", err)
	}

	if err := ctx.client.AttachToContainer(docker.AttachToContainerOptions{
		Container: container.ID,
		// TODO: send this to a buffer for --quiet
		OutputStream: os.Stdout,
		ErrorStream:  os.Stderr,
		InputStream:  ioutil.NopCloser(os.Stdin),
		Logs:         false,
		Stream:       true,
		Stdin:        t.config.Interactive,
		RawTerminal:  t.config.Interactive,
		Stdout:       true,
		Stderr:       true,
	}); err != nil {
		return fmt.Errorf("Failed attaching to container: %s", err)
	}

	// TODO: blocks here on interactive
	status, err := ctx.client.WaitContainer(container.ID)
	if err != nil {
		t.logger().Warnf("Failed to wait on container exit: %s", err)
	}
	if status != 0 {
		t.logger().WithFields(log.Fields{"status": status}).Warn(
			"Container exited with non-zero status code")
	}

	if err := ctx.client.RemoveContainer(docker.RemoveContainerOptions{
		ID:            container.ID,
		RemoveVolumes: true,
	}); err != nil {
		t.logger().WithFields(log.Fields{"container": container.ID}).Warnf(
			"Failed to remove container: %s", err)
	}
	return nil
}

func (t *CommandTask) forwardSignals(client *docker.Client, containerID string) chan<- os.Signal {
	chanSig := make(chan os.Signal, 128)

	// TODO: not all of these exist on windows?
	signal.Notify(chanSig, syscall.SIGINT, syscall.SIGTERM)

	kill := func(sig os.Signal) {
		t.logger().WithFields(log.Fields{"signal": sig}).Debug("Received signal")

		intSig, ok := sig.(syscall.Signal)
		if !ok {
			t.logger().WithFields(log.Fields{"signal": sig}).Warnf(
				"Failed to convert signal from %T", sig)
			return
		}

		if err := client.KillContainer(docker.KillContainerOptions{
			ID:     containerID,
			Signal: docker.Signal(intSig),
		}); err != nil {
			t.logger().WithFields(log.Fields{"signal": sig}).Warnf(
				"Failed to send signal: %s", err)
		}
	}

	go func() {
		for sig := range chanSig {
			kill(sig)
		}
	}()
	return chanSig
}
<|endoftext|>


<commit_before>package cloudflare

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

	"github.com/pkg/errors"
)

type TeamsDevicesList struct {
	Response
	Result []TeamsDeviceListItem `json:"result"`
}

type TeamsDeviceDetail struct {
	Response
	Result TeamsDeviceListItem `json:"result"`
}

type TeamsDeviceListItem struct {
	User         UserItem `json:"user,omitempty"`
	ID           string   `json:"id,omitempty"`
	Key          string   `json:"key,omitempty"`
	DeviceType   string   `json:"device_type,omitempty"`
	Name         string   `json:"name,omitempty"`
	Model        string   `json:"model,omitempty"`
	Manufacturer string   `json:"manufacturer,omitempty"`
	Deleted      bool     `json:"deleted,omitempty"`
	Version      string   `json:"version,omitempty"`
	SerialNumber string   `json:"serial_number,omitempty"`
	OSVersion    string   `json:"os_version,omitempty"`
	MacAddress   string   `json:"mac_address,omitempty"`
	IP           string   `json:"ip,omitempty"`
	Created      string   `json:"created,omitempty"`
	Updated      string   `json:"updated,omitempty"`
	LastSeen     string   `json:"last_seen,omitempty"`
	RevokedAt    string   `json:"revoked_at,omitempty"`
}

type UserItem struct {
	ID    string `json:"id,omitempty"`
	Name  string `json:"name,omitempty"`
	Email string `json:"email,omitempty"`
}

// ListTeamsDevice returns all devices for a given account.
//
// API reference : https://api.cloudflare.com/#devices-list-devices
func (api *API) ListTeamsDevices(ctx context.Context, accountID string) ([]TeamsDeviceListItem, error) {
	uri := fmt.Sprintf("/%s/%s/devices", AccountRouteRoot, accountID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return []TeamsDeviceListItem{}, err
	}

	var response TeamsDevicesList
	err = json.Unmarshal(res, &response)
	if err != nil {
		return []TeamsDeviceListItem{}, errors.Wrap(err, errUnmarshalError)
	}

	return response.Result, nil
}

// RevokeTeamsDevice revokes device with given identifiers.
//
// API reference : https://api.cloudflare.com/#devices-revoke-devices
func (api *API) RevokeTeamsDevices(ctx context.Context, accountID string, deviceIds []string) (Response, error) {
	uri := fmt.Sprintf("/%s/%s/devices/revoke", AccountRouteRoot, accountID)

	res, err := api.makeRequestContextWithHeaders(ctx, http.MethodPost, uri, deviceIds)
	if err != nil {
		return Response{}, err
	}

	result := Response{}
	if err := json.Unmarshal(res, &result); err != nil {
		return result, errors.Wrap(err, errUnmarshalError)
	}

	return result, err
}

// GetTeamsDeviceDetails gets device details.
//
// API reference : https://api.cloudflare.com/#devices-device-details
func (api *API) GetTeamsDeviceDetails(ctx context.Context, accountID string, deviceID string) (TeamsDeviceListItem, error) {
	uri := fmt.Sprintf("/%s/%s/devices/%s", AccountRouteRoot, accountID, deviceID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return TeamsDeviceListItem{}, err
	}

	var response TeamsDeviceDetail
	err = json.Unmarshal(res, &response)
	if err != nil {
		return TeamsDeviceListItem{}, errors.Wrap(err, errUnmarshalError)
	}

	return response.Result, nil
}
<commit_msg>s/makeRequestContextWithHeaders/makeRequestContext<commit_after>package cloudflare

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

	"github.com/pkg/errors"
)

type TeamsDevicesList struct {
	Response
	Result []TeamsDeviceListItem `json:"result"`
}

type TeamsDeviceDetail struct {
	Response
	Result TeamsDeviceListItem `json:"result"`
}

type TeamsDeviceListItem struct {
	User         UserItem `json:"user,omitempty"`
	ID           string   `json:"id,omitempty"`
	Key          string   `json:"key,omitempty"`
	DeviceType   string   `json:"device_type,omitempty"`
	Name         string   `json:"name,omitempty"`
	Model        string   `json:"model,omitempty"`
	Manufacturer string   `json:"manufacturer,omitempty"`
	Deleted      bool     `json:"deleted,omitempty"`
	Version      string   `json:"version,omitempty"`
	SerialNumber string   `json:"serial_number,omitempty"`
	OSVersion    string   `json:"os_version,omitempty"`
	MacAddress   string   `json:"mac_address,omitempty"`
	IP           string   `json:"ip,omitempty"`
	Created      string   `json:"created,omitempty"`
	Updated      string   `json:"updated,omitempty"`
	LastSeen     string   `json:"last_seen,omitempty"`
	RevokedAt    string   `json:"revoked_at,omitempty"`
}

type UserItem struct {
	ID    string `json:"id,omitempty"`
	Name  string `json:"name,omitempty"`
	Email string `json:"email,omitempty"`
}

// ListTeamsDevice returns all devices for a given account.
//
// API reference : https://api.cloudflare.com/#devices-list-devices
func (api *API) ListTeamsDevices(ctx context.Context, accountID string) ([]TeamsDeviceListItem, error) {
	uri := fmt.Sprintf("/%s/%s/devices", AccountRouteRoot, accountID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return []TeamsDeviceListItem{}, err
	}

	var response TeamsDevicesList
	err = json.Unmarshal(res, &response)
	if err != nil {
		return []TeamsDeviceListItem{}, errors.Wrap(err, errUnmarshalError)
	}

	return response.Result, nil
}

// RevokeTeamsDevice revokes device with given identifiers.
//
// API reference : https://api.cloudflare.com/#devices-revoke-devices
func (api *API) RevokeTeamsDevices(ctx context.Context, accountID string, deviceIds []string) (Response, error) {
	uri := fmt.Sprintf("/%s/%s/devices/revoke", AccountRouteRoot, accountID)

	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, deviceIds)
	if err != nil {
		return Response{}, err
	}

	result := Response{}
	if err := json.Unmarshal(res, &result); err != nil {
		return result, errors.Wrap(err, errUnmarshalError)
	}

	return result, err
}

// GetTeamsDeviceDetails gets device details.
//
// API reference : https://api.cloudflare.com/#devices-device-details
func (api *API) GetTeamsDeviceDetails(ctx context.Context, accountID string, deviceID string) (TeamsDeviceListItem, error) {
	uri := fmt.Sprintf("/%s/%s/devices/%s", AccountRouteRoot, accountID, deviceID)

	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return TeamsDeviceListItem{}, err
	}

	var response TeamsDeviceDetail
	err = json.Unmarshal(res, &response)
	if err != nil {
		return TeamsDeviceListItem{}, errors.Wrap(err, errUnmarshalError)
	}

	return response.Result, nil
}
<|endoftext|>


<commit_before>package norway

import (
	"testing"

	. "github.com/smartystreets/goconvey/convey"
)

func TestParseEntryNoSubDirectories(t *testing.T) {
	Convey("Given a single directory line that indicates no sub-directories", t, func() {
		entryLine := "D"
		Convey("When the entry is parsed", func() {
			entry := ParseEntry(entryLine)
			Convey("IsDirectory should be true", func() {
				So(entry.IsDirectory, ShouldBeTrue)
			})
		})
	})
}

func TestParseEntryFile(t *testing.T) {
	Convey("Given a regular file entry", t, func() {
		entryLine := "/bio_ssl.c/1.14/Mon Apr 21 16:34:43 2014//"
		Convey("When the entry is parsed", func() {
			entry := ParseEntry(entryLine)
			Convey("IsDirectory should be false", func() {
				So(entry.IsDirectory, ShouldBeFalse)
			})
			Convey("IsRemoved should be false", func() {
				So(entry.IsRemoved, ShouldBeFalse)
			})
			Convey("IsAdded should be false", func() {
				So(entry.IsAdded, ShouldBeFalse)
			})
			Convey("FileName should match bio_ssl.c", func() {
				So(entry.FileName, ShouldEqual, "bio_ssl.c")
			})
			Convey("Revision should match 1.14", func() {
				So(entry.Revision, ShouldEqual, "1.14")
			})
			Convey("Timestamp should match Mon Apr 21 16:34:43 2014", func() {
				So(entry.Timestamp, ShouldEqual, "Mon Apr 21 16:34:43 2014")
			})
			Convey("Options should be an empty string", func() {
				So(entry.Options, ShouldEqual, "")
			})
			Convey("Tagdate should be an empty string", func() {
				So(entry.Tagdate, ShouldEqual, "")
			})
		})
	})
}

func TestParseEntryFileWithOptions(t *testing.T) {
	Convey("Given a regular file entry with options", t, func() {
		entryLine := "/some_file.c/10.24/Fri Apr 11 08:17:05 2014/-kkv/"
		Convey("When the entry is parsed", func() {
			entry := ParseEntry(entryLine)
			Convey("IsDirectory should be false", func() {
				So(entry.IsDirectory, ShouldBeFalse)
			})
			Convey("IsRemoved should be false", func() {
				So(entry.IsRemoved, ShouldBeFalse)
			})
			Convey("IsAdded should be false", func() {
				So(entry.IsAdded, ShouldBeFalse)
			})
			Convey("FileName should match some_file.c", func() {
				So(entry.FileName, ShouldEqual, "some_file.c")
			})
			Convey("Revision should match 1.24", func() {
				So(entry.Revision, ShouldEqual, "1.24")
			})
			Convey("Timestamp should match Fri Apr 11 08:17:05 2014", func() {
				So(entry.Timestamp, ShouldEqual, "Fri Apr 11 08:17:05 2014")
			})
			Convey("Options should be equal to -kkv", func() {
				So(entry.Options, ShouldEqual, "-kkv")
			})
			Convey("Tagdate should be an empty string", func() {
				So(entry.Tagdate, ShouldEqual, "")
			})
		})
	})
}

func TestParseEntryFileWithTag(t *testing.T) {
	Convey("Given a regular file entry with options", t, func() {
		entryLine := "/some_file.bin/10.1.4.1.22.1/Tue Apr  8 14:00:44 2014/-kb/TB_01_05X_000Y_snapshots"
		Convey("When the entry is parsed", func() {
			entry := ParseEntry(entryLine)
			Convey("IsDirectory should be false", func() {
				So(entry.IsDirectory, ShouldBeFalse)
			})
			Convey("IsRemoved should be false", func() {
				So(entry.IsRemoved, ShouldBeFalse)
			})
			Convey("IsAdded should be false", func() {
				So(entry.IsAdded, ShouldBeFalse)
			})
			Convey("FileName should match some_file.bin", func() {
				So(entry.FileName, ShouldEqual, "some_file.bin")
			})
			Convey("Revision should match 10.1.4.1.22.1", func() {
				So(entry.Revision, ShouldEqual, "10.1.4.1.22.1")
			})
			Convey("Timestamp should match Tue Apr  8 14:00:44 2014", func() {
				So(entry.Timestamp, ShouldEqual, "Tue Apr  8 14:00:44 2014")
			})
			Convey("Options should be equal to -kb", func() {
				So(entry.Options, ShouldEqual, "-kb")
			})
			Convey("Tagdate should be equal to ", func() {
				So(entry.Tagdate, ShouldEqual, "TB_01_05X_000Y_snapshots")
			})
		})
	})
}

func TestParseEntryDirectory(t *testing.T) {
	Convey("Given a regular file entry with options", t, func() {
		entryLine := "D/some_directory////"
		Convey("When the entry is parsed", func() {
			entry := ParseEntry(entryLine)
			Convey("IsDirectory should be true", func() {
				So(entry.IsDirectory, ShouldBeTrue)
			})
			Convey("IsRemoved should be false", func() {
				So(entry.IsRemoved, ShouldBeFalse)
			})
			Convey("IsAdded should be false", func() {
				So(entry.IsAdded, ShouldBeFalse)
			})
			Convey("FileName should match some_directory", func() {
				So(entry.FileName, ShouldEqual, "some_directory")
			})
			Convey("Revision should be an empty string", func() {
				So(entry.Revision, ShouldEqual, "")
			})
			Convey("Timestamp should be an empty string", func() {
				So(entry.Timestamp, ShouldEqual, "")
			})
			Convey("Options should be an empty string", func() {
				So(entry.Options, ShouldEqual, "")
			})
			Convey("Tagdate should be an empty string", func() {
				So(entry.Tagdate, ShouldEqual, "")
			})
		})
	})
}
<commit_msg>Fix the parsing test expected revision<commit_after>package norway

import (
	"testing"

	. "github.com/smartystreets/goconvey/convey"
)

func TestParseEntryNoSubDirectories(t *testing.T) {
	Convey("Given a single directory line that indicates no sub-directories", t, func() {
		entryLine := "D"
		Convey("When the entry is parsed", func() {
			entry := ParseEntry(entryLine)
			Convey("IsDirectory should be true", func() {
				So(entry.IsDirectory, ShouldBeTrue)
			})
		})
	})
}

func TestParseEntryFile(t *testing.T) {
	Convey("Given a regular file entry", t, func() {
		entryLine := "/bio_ssl.c/1.14/Mon Apr 21 16:34:43 2014//"
		Convey("When the entry is parsed", func() {
			entry := ParseEntry(entryLine)
			Convey("IsDirectory should be false", func() {
				So(entry.IsDirectory, ShouldBeFalse)
			})
			Convey("IsRemoved should be false", func() {
				So(entry.IsRemoved, ShouldBeFalse)
			})
			Convey("IsAdded should be false", func() {
				So(entry.IsAdded, ShouldBeFalse)
			})
			Convey("FileName should match bio_ssl.c", func() {
				So(entry.FileName, ShouldEqual, "bio_ssl.c")
			})
			Convey("Revision should match 1.14", func() {
				So(entry.Revision, ShouldEqual, "1.14")
			})
			Convey("Timestamp should match Mon Apr 21 16:34:43 2014", func() {
				So(entry.Timestamp, ShouldEqual, "Mon Apr 21 16:34:43 2014")
			})
			Convey("Options should be an empty string", func() {
				So(entry.Options, ShouldEqual, "")
			})
			Convey("Tagdate should be an empty string", func() {
				So(entry.Tagdate, ShouldEqual, "")
			})
		})
	})
}

func TestParseEntryFileWithOptions(t *testing.T) {
	Convey("Given a regular file entry with options", t, func() {
		entryLine := "/some_file.c/10.24/Fri Apr 11 08:17:05 2014/-kkv/"
		Convey("When the entry is parsed", func() {
			entry := ParseEntry(entryLine)
			Convey("IsDirectory should be false", func() {
				So(entry.IsDirectory, ShouldBeFalse)
			})
			Convey("IsRemoved should be false", func() {
				So(entry.IsRemoved, ShouldBeFalse)
			})
			Convey("IsAdded should be false", func() {
				So(entry.IsAdded, ShouldBeFalse)
			})
			Convey("FileName should match some_file.c", func() {
				So(entry.FileName, ShouldEqual, "some_file.c")
			})
			Convey("Revision should match 10.24", func() {
				So(entry.Revision, ShouldEqual, "10.24")
			})
			Convey("Timestamp should match Fri Apr 11 08:17:05 2014", func() {
				So(entry.Timestamp, ShouldEqual, "Fri Apr 11 08:17:05 2014")
			})
			Convey("Options should be equal to -kkv", func() {
				So(entry.Options, ShouldEqual, "-kkv")
			})
			Convey("Tagdate should be an empty string", func() {
				So(entry.Tagdate, ShouldEqual, "")
			})
		})
	})
}

func TestParseEntryFileWithTag(t *testing.T) {
	Convey("Given a regular file entry with options", t, func() {
		entryLine := "/some_file.bin/10.1.4.1.22.1/Tue Apr  8 14:00:44 2014/-kb/TB_01_05X_000Y_snapshots"
		Convey("When the entry is parsed", func() {
			entry := ParseEntry(entryLine)
			Convey("IsDirectory should be false", func() {
				So(entry.IsDirectory, ShouldBeFalse)
			})
			Convey("IsRemoved should be false", func() {
				So(entry.IsRemoved, ShouldBeFalse)
			})
			Convey("IsAdded should be false", func() {
				So(entry.IsAdded, ShouldBeFalse)
			})
			Convey("FileName should match some_file.bin", func() {
				So(entry.FileName, ShouldEqual, "some_file.bin")
			})
			Convey("Revision should match 10.1.4.1.22.1", func() {
				So(entry.Revision, ShouldEqual, "10.1.4.1.22.1")
			})
			Convey("Timestamp should match Tue Apr  8 14:00:44 2014", func() {
				So(entry.Timestamp, ShouldEqual, "Tue Apr  8 14:00:44 2014")
			})
			Convey("Options should be equal to -kb", func() {
				So(entry.Options, ShouldEqual, "-kb")
			})
			Convey("Tagdate should be equal to ", func() {
				So(entry.Tagdate, ShouldEqual, "TB_01_05X_000Y_snapshots")
			})
		})
	})
}

func TestParseEntryDirectory(t *testing.T) {
	Convey("Given a regular file entry with options", t, func() {
		entryLine := "D/some_directory////"
		Convey("When the entry is parsed", func() {
			entry := ParseEntry(entryLine)
			Convey("IsDirectory should be true", func() {
				So(entry.IsDirectory, ShouldBeTrue)
			})
			Convey("IsRemoved should be false", func() {
				So(entry.IsRemoved, ShouldBeFalse)
			})
			Convey("IsAdded should be false", func() {
				So(entry.IsAdded, ShouldBeFalse)
			})
			Convey("FileName should match some_directory", func() {
				So(entry.FileName, ShouldEqual, "some_directory")
			})
			Convey("Revision should be an empty string", func() {
				So(entry.Revision, ShouldEqual, "")
			})
			Convey("Timestamp should be an empty string", func() {
				So(entry.Timestamp, ShouldEqual, "")
			})
			Convey("Options should be an empty string", func() {
				So(entry.Options, ShouldEqual, "")
			})
			Convey("Tagdate should be an empty string", func() {
				So(entry.Tagdate, ShouldEqual, "")
			})
		})
	})
}
<|endoftext|>


<commit_before>// Package text contains utility functions for manipulating raw text strings
package text

import (
	"fmt"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/fatih/camelcase"
)

// See https://golang.org/ref/spec#Keywords
var reservedKeyWords = map[string]bool{
	"break":       true,
	"case":        true,
	"chan":        true,
	"const":       true,
	"continue":    true,
	"default":     true,
	"defer":       true,
	"else":        true,
	"fallthrough": true,
	"for":         true,
	"func":        true,
	"go":          true,
	"goto":        true,
	"if":          true,
	"import":      true,
	"interface":   true,
	"map":         true,
	"package":     true,
	"range":       true,
	"return":      true,
	"select":      true,
	"struct":      true,
	"switch":      true,
	"type":        true,
	"var":         true,
}

// taken from https://github.com/golang/lint/blob/32a87160691b3c96046c0c678fe57c5bef761456/lint.go#L702
var commonInitialisms = map[string]bool{
	"API":   true,
	"ASCII": true,
	"CPU":   true,
	"CSS":   true,
	"DNS":   true,
	"EOF":   true,
	"GUID":  true,
	"HTML":  true,
	"HTTP":  true,
	"HTTPS": true,
	"ID":    true,
	"IP":    true,
	"JSON":  true,
	"LHS":   true,
	"QPS":   true,
	"RAM":   true,
	"RHS":   true,
	"RPC":   true,
	"SLA":   true,
	"SMTP":  true,
	"SQL":   true,
	"SSH":   true,
	"TCP":   true,
	"TLS":   true,
	"TTL":   true,
	"UDP":   true,
	"UI":    true,
	"UID":   true,
	"UUID":  true,
	"URI":   true,
	"URL":   true,
	"UTF8":  true,
	"VM":    true,
	"XML":   true,
	"XSRF":  true,
	"XSS":   true,
}

// Indent indents a block of text with an indent string. It does this by
// placing the given indent string at the front of every line, except on the
// last line, if the last line has no characters. This special treatment
// simplifies the generation of nested text structures.
func Indent(text, indent string) string {
	if text == "" {
		return text
	}
	if text[len(text)-1:] == "\n" {
		result := ""
		for _, j := range strings.Split(text[:len(text)-1], "\n") {
			result += indent + j + "\n"
		}
		return result
	}
	result := ""
	for _, j := range strings.Split(strings.TrimRight(text, "\n"), "\n") {
		result += indent + j + "\n"
	}
	return result[:len(result)-1]
}

// Underline returns the provided text together with a new line character and a
// line of "=" characters whose length is equal to the maximum line length in
// the provided text, followed by a final newline character.
func Underline(text string) string {
	var maxlen int
	for _, j := range strings.Split(text, "\n") {
		if len(j) > maxlen {
			maxlen = len(j)
		}
	}
	return text + "\n" + strings.Repeat("=", maxlen) + "\n"
}

// Returns a string of the same length, filled with "*"s.
func StarOut(text string) string {
	return strings.Repeat("*", len(text))
}

// GoIdentifierFrom provides a mechanism to mutate an arbitrary descriptive
// string (name) into an _exported_ Go identifier (variable name, function
// name, etc) that e.g. can be used in generated code, taking into account a
// blacklist of names that have already been used, plus the blacklist of the go
// language reserved key words (https://golang.org/ref/spec#Keywords), in order
// to guarantee that a new name is created which will not conflict with an
// existing type.
//
// Identifier syntax: https://golang.org/ref/spec#Identifiers
//
// Strategy to convert arbitrary unicode string to a valid identifier:
//
// 1) Ensure name is valid UTF-8; if not, replace it with empty string
//
// 2) Split name into arrays of allowed runes (words), by considering a run of
// disallowed unicode characters to act as a separator, where allowed runes
// include unicode letters, unicode numbers, and '_' character (disallowed
// runes are discarded)
//
// 3) Split words further into sub words, by decomposing camel case words as
// per https://github.com/fatih/camelcase#usage-and-examples
//
// 4) Designate the case of all subwords of all words to be uppercase, with the
// exception of the first subword of the first word, which should be lowercase
// if exported is false, otherwise uppercase
//
// 5) For each subword of each word, adjust as follows: if designated as
// lowercase, lowercase all characters of the subword; if designated as
// uppercase, then if recognised as a common "initialism", then uppercase all
// the characters of the subword, otherwise uppercase only the first character
// of the subword. Common "Initialisms" are defined as per:
// https://github.com/golang/lint/blob/32a87160691b3c96046c0c678fe57c5bef761456/lint.go#L702
//
// 6) Rejoin subwords to form a single word
//
// 7) Rejoin words into a single string
//
// 8) If the string starts with a number, add a leading `_`
//
// 9) If the string is the empty string or "_", set as "Identifier"
//
// 10) If the resulting identifier is in the given blacklist, or the list of
// reserved key words (https://golang.org/ref/spec#Keywords), append the lowest
// integer possible, >= 1, that results in no blacklist conflict
//
// 11) Add the new name to the given blacklist
//
// Note, the `map[string]bool` construction is simply a mechanism to implement
// set semantics; a value of `true` signifies inclusion in the set.
// Non-existence is equivalent to existence with a value of `false`; therefore
// it is recommended to only store `true` values.
func GoIdentifierFrom(name string, exported bool, blacklist map[string]bool) (identifier string) {
	if !utf8.ValidString(name) {
		name = ""
	}
	for i, word := range strings.FieldsFunc(
		name,
		func(c rune) bool {
			return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_'
		},
	) {
		caseAdaptedWord := ""
		for j, subWord := range camelcase.Split(word) {
			caseAdaptedWord += fixCase(subWord, i == 0 && j == 0 && !exported)
		}
		identifier += caseAdaptedWord
	}

	if strings.IndexFunc(
		identifier,
		func(c rune) bool {
			return unicode.IsNumber(c)
		},
	) == 0 {
		identifier = "_" + identifier
	}

	if identifier == "" || identifier == "_" {
		identifier = "Identifier"
	}

	// If name already exists, add an integer suffix to name. Start with "1" and increment
	// by 1 until an unused name is found. Example: if name FooBar was generated four times
	// , the first instance would be called FooBar, then the next would be FooBar1, the next
	// FooBar2 and the last would be assigned a name of FooBar3. We do this to guarantee we
	// don't use duplicate names for different logical entities.
	for k, baseName := 1, identifier; blacklist[identifier] || reservedKeyWords[identifier]; {
		identifier = fmt.Sprintf("%v%v", baseName, k)
		k++
	}
	blacklist[identifier] = true
	return
}

func fixCase(word string, makeLower bool) string {
	if word == "" {
		return ""
	}
	if makeLower {
		return strings.ToLower(word)
	}
	upper := strings.ToUpper(word)
	if commonInitialisms[upper] {
		return upper
	} else {
		firstRune, size := utf8.DecodeRuneInString(word)
		remainingString := word[size:]
		return string(unicode.ToUpper(firstRune)) + remainingString
	}
}

// Returns the indefinite article (in English) for a the given noun, which is
// 'an' for nouns beginning with a vowel, otherwise 'a'.
func IndefiniteArticle(noun string) string {
	if strings.ContainsRune("AEIOUaeiou", rune(noun[0])) {
		return "an"
	} else {
		return "a"
	}
}
<commit_msg>Forked github.com/fatih/camelcase<commit_after>// Package text contains utility functions for manipulating raw text strings
package text

import (
	"fmt"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/taskcluster/camelcase"
)

// See https://golang.org/ref/spec#Keywords
var reservedKeyWords = map[string]bool{
	"break":       true,
	"case":        true,
	"chan":        true,
	"const":       true,
	"continue":    true,
	"default":     true,
	"defer":       true,
	"else":        true,
	"fallthrough": true,
	"for":         true,
	"func":        true,
	"go":          true,
	"goto":        true,
	"if":          true,
	"import":      true,
	"interface":   true,
	"map":         true,
	"package":     true,
	"range":       true,
	"return":      true,
	"select":      true,
	"struct":      true,
	"switch":      true,
	"type":        true,
	"var":         true,
}

// taken from https://github.com/golang/lint/blob/32a87160691b3c96046c0c678fe57c5bef761456/lint.go#L702
var commonInitialisms = map[string]bool{
	"API":   true,
	"ASCII": true,
	"CPU":   true,
	"CSS":   true,
	"DNS":   true,
	"EOF":   true,
	"GUID":  true,
	"HTML":  true,
	"HTTP":  true,
	"HTTPS": true,
	"ID":    true,
	"IP":    true,
	"JSON":  true,
	"LHS":   true,
	"QPS":   true,
	"RAM":   true,
	"RHS":   true,
	"RPC":   true,
	"SLA":   true,
	"SMTP":  true,
	"SQL":   true,
	"SSH":   true,
	"TCP":   true,
	"TLS":   true,
	"TTL":   true,
	"UDP":   true,
	"UI":    true,
	"UID":   true,
	"UUID":  true,
	"URI":   true,
	"URL":   true,
	"UTF8":  true,
	"VM":    true,
	"XML":   true,
	"XSRF":  true,
	"XSS":   true,
}

// Indent indents a block of text with an indent string. It does this by
// placing the given indent string at the front of every line, except on the
// last line, if the last line has no characters. This special treatment
// simplifies the generation of nested text structures.
func Indent(text, indent string) string {
	if text == "" {
		return text
	}
	if text[len(text)-1:] == "\n" {
		result := ""
		for _, j := range strings.Split(text[:len(text)-1], "\n") {
			result += indent + j + "\n"
		}
		return result
	}
	result := ""
	for _, j := range strings.Split(strings.TrimRight(text, "\n"), "\n") {
		result += indent + j + "\n"
	}
	return result[:len(result)-1]
}

// Underline returns the provided text together with a new line character and a
// line of "=" characters whose length is equal to the maximum line length in
// the provided text, followed by a final newline character.
func Underline(text string) string {
	var maxlen int
	for _, j := range strings.Split(text, "\n") {
		if len(j) > maxlen {
			maxlen = len(j)
		}
	}
	return text + "\n" + strings.Repeat("=", maxlen) + "\n"
}

// Returns a string of the same length, filled with "*"s.
func StarOut(text string) string {
	return strings.Repeat("*", len(text))
}

// GoIdentifierFrom provides a mechanism to mutate an arbitrary descriptive
// string (name) into an _exported_ Go identifier (variable name, function
// name, etc) that e.g. can be used in generated code, taking into account a
// blacklist of names that have already been used, plus the blacklist of the go
// language reserved key words (https://golang.org/ref/spec#Keywords), in order
// to guarantee that a new name is created which will not conflict with an
// existing type.
//
// Identifier syntax: https://golang.org/ref/spec#Identifiers
//
// Strategy to convert arbitrary unicode string to a valid identifier:
//
// 1) Ensure name is valid UTF-8; if not, replace it with empty string
//
// 2) Split name into arrays of allowed runes (words), by considering a run of
// disallowed unicode characters to act as a separator, where allowed runes
// include unicode letters, unicode numbers, and '_' character (disallowed
// runes are discarded)
//
// 3) Split words further into sub words, by decomposing camel case words as
// per https://github.com/taskcluster/camelcase#usage-and-examples
//
// 4) Designate the case of all subwords of all words to be uppercase, with the
// exception of the first subword of the first word, which should be lowercase
// if exported is false, otherwise uppercase
//
// 5) For each subword of each word, adjust as follows: if designated as
// lowercase, lowercase all characters of the subword; if designated as
// uppercase, then if recognised as a common "initialism", then uppercase all
// the characters of the subword, otherwise uppercase only the first character
// of the subword. Common "Initialisms" are defined as per:
// https://github.com/golang/lint/blob/32a87160691b3c96046c0c678fe57c5bef761456/lint.go#L702
//
// 6) Rejoin subwords to form a single word
//
// 7) Rejoin words into a single string
//
// 8) If the string starts with a number, add a leading `_`
//
// 9) If the string is the empty string or "_", set as "Identifier"
//
// 10) If the resulting identifier is in the given blacklist, or the list of
// reserved key words (https://golang.org/ref/spec#Keywords), append the lowest
// integer possible, >= 1, that results in no blacklist conflict
//
// 11) Add the new name to the given blacklist
//
// Note, the `map[string]bool` construction is simply a mechanism to implement
// set semantics; a value of `true` signifies inclusion in the set.
// Non-existence is equivalent to existence with a value of `false`; therefore
// it is recommended to only store `true` values.
func GoIdentifierFrom(name string, exported bool, blacklist map[string]bool) (identifier string) {
	if !utf8.ValidString(name) {
		name = ""
	}
	for i, word := range strings.FieldsFunc(
		name,
		func(c rune) bool {
			return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_'
		},
	) {
		caseAdaptedWord := ""
		for j, subWord := range camelcase.Split(word) {
			caseAdaptedWord += fixCase(subWord, i == 0 && j == 0 && !exported)
		}
		identifier += caseAdaptedWord
	}

	if strings.IndexFunc(
		identifier,
		func(c rune) bool {
			return unicode.IsNumber(c)
		},
	) == 0 {
		identifier = "_" + identifier
	}

	if identifier == "" || identifier == "_" {
		identifier = "Identifier"
	}

	// If name already exists, add an integer suffix to name. Start with "1" and increment
	// by 1 until an unused name is found. Example: if name FooBar was generated four times
	// , the first instance would be called FooBar, then the next would be FooBar1, the next
	// FooBar2 and the last would be assigned a name of FooBar3. We do this to guarantee we
	// don't use duplicate names for different logical entities.
	for k, baseName := 1, identifier; blacklist[identifier] || reservedKeyWords[identifier]; {
		identifier = fmt.Sprintf("%v%v", baseName, k)
		k++
	}
	blacklist[identifier] = true
	return
}

func fixCase(word string, makeLower bool) string {
	if word == "" {
		return ""
	}
	if makeLower {
		return strings.ToLower(word)
	}
	upper := strings.ToUpper(word)
	if commonInitialisms[upper] {
		return upper
	} else {
		firstRune, size := utf8.DecodeRuneInString(word)
		remainingString := word[size:]
		return string(unicode.ToUpper(firstRune)) + remainingString
	}
}

// Returns the indefinite article (in English) for a the given noun, which is
// 'an' for nouns beginning with a vowel, otherwise 'a'.
func IndefiniteArticle(noun string) string {
	if strings.ContainsRune("AEIOUaeiou", rune(noun[0])) {
		return "an"
	} else {
		return "a"
	}
}
<|endoftext|>