<commit_before>package mpmulticore

import (
	"bufio"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	mp "github.com/mackerelio/go-mackerel-plugin-helper"
)

var graphDef = map[string]mp.Graphs{
	"multicore.cpu.#": {
		Label: "MultiCore CPU",
		Unit:  "percentage",
		Metrics: []mp.Metrics{
			{Name: "user", Label: "user", Diff: false, Stacked: true},
			{Name: "nice", Label: "nice", Diff: false, Stacked: true},
			{Name: "system", Label: "system", Diff: false, Stacked: true},
			{Name: "idle", Label: "idle", Diff: false, Stacked: true},
			{Name: "iowait", Label: "ioWait", Diff: false, Stacked: true},
			{Name: "irq", Label: "irq", Diff: false, Stacked: true},
			{Name: "softirq", Label: "softirq", Diff: false, Stacked: true},
			{Name: "steal", Label: "steal", Diff: false, Stacked: true},
			{Name: "guest", Label: "guest", Diff: false, Stacked: true},
			{Name: "guest_nice", Label: "guest_nice", Diff: false, Stacked: true},
		},
	},
	"multicore.loadavg_per_core": {
		Label: "MultiCore loadavg5 per core",
		Unit:  "float",
		Metrics: []mp.Metrics{
			{Name: "loadavg5", Label: "loadavg5", Diff: false, Stacked: false},
		},
	},
}

type saveItem struct {
	LastTime       time.Time
	ProcStatsByCPU map[string]procStats
}

type procStats struct {
	User      *uint64 `json:"user"`
	Nice      *uint64 `json:"nice"`
	System    *uint64 `json:"system"`
	Idle      *uint64 `json:"idle"`
	IoWait    *uint64 `json:"iowait"`
	Irq       *uint64 `json:"irq"`
	SoftIrq   *uint64 `json:"softirq"`
	Steal     *uint64 `json:"steal"`
	Guest     *uint64 `json:"guest"`
	GuestNice *uint64 `json:"guest_nice"`
	Total     uint64  `json:"total"`
}

type cpuPercentages struct {
	GroupName string
	User      *float64
	Nice      *float64
	System    *float64
	Idle      *float64
	IoWait    *float64
	Irq       *float64
	SoftIrq   *float64
	Steal     *float64
	Guest     *float64
	GuestNice *float64
}

func parseCounters(values []string) ([]uint64, error) {
	var result []uint64
	for _, v := range values {
		f, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return nil, err
		}
		result = append(result, f)
	}
	return result, nil
}

func fill(arr []uint64, elementCount int) []*uint64 {
	var filled []*uint64
	for _, v := range arr {
		copy := v
		filled = append(filled, &copy)
	}

	if len(arr) < elementCount {
		emptyArray := make([]*uint64, elementCount-len(arr))
		filled = append(filled, emptyArray...)
	}
	return filled
}

func parseProcStat(out io.Reader) (map[string]procStats, error) {
	scanner := bufio.NewScanner(out)
	var result = make(map[string]procStats)
	for scanner.Scan() {
		line := scanner.Text()
		if !strings.HasPrefix(line, "cpu") {
			continue
		}

		fields := strings.Fields(line)
		key := fields[0]
		values := fields[1:]

		// skip total cpu usage
		if key == "cpu" {
			continue
		}

		counterValues, err := parseCounters(values)
		if err != nil {
			return nil, err
		}

		var total uint64
		for _, v := range counterValues {
			total += v
		}

		filledValues := fill(counterValues, 10)

		result[key] = procStats{
			User:      filledValues[0],
			Nice:      filledValues[1],
			System:    filledValues[2],
			Idle:      filledValues[3],
			IoWait:    filledValues[4],
			Irq:       filledValues[5],
			SoftIrq:   filledValues[6],
			Steal:     filledValues[7],
			Guest:     filledValues[8],
			GuestNice: filledValues[9],
			Total:     total,
		}
	}
	return result, nil
}

func collectProcStatValues() (map[string]procStats, error) {
	file, err := os.Open("/proc/stat")
	if err != nil {
		return nil, err
	}
	defer file.Close()
	return parseProcStat(file)
}

func saveValues(tempFileName string, values map[string]procStats, now time.Time) error {
	f, err := os.Create(tempFileName)
	if err != nil {
		return err
	}
	defer f.Close()

	s := saveItem{
		LastTime:       now,
		ProcStatsByCPU: values,
	}

	encoder := json.NewEncoder(f)
	err = encoder.Encode(s)
	if err != nil {
		return err
	}

	return nil
}

func fetchSavedItem(tempFileName string) (*saveItem, error) {
	f, err := os.Open(tempFileName)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	defer f.Close()

	var stat saveItem
	decoder := json.NewDecoder(f)
	err = decoder.Decode(&stat)
	if err != nil {
		return nil, err
	}
	return &stat, nil
}

func calcCPUUsage(currentValues map[string]procStats, now time.Time, savedItem *saveItem) ([]cpuPercentages, error) {
	lastValues := savedItem.ProcStatsByCPU
	lastTime := savedItem.LastTime

	diffTime := now.Unix() - lastTime.Unix()
	if diffTime > 600 {
		return nil, fmt.Errorf("Too long duration")
	}

	var result []cpuPercentages
	for key, current := range currentValues {
		last, ok := lastValues[key]
		if ok {
			user, err := calcPercentage(current.User, last.User, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			nice, err := calcPercentage(current.Nice, last.Nice, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			system, err := calcPercentage(current.System, last.System, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			idle, err := calcPercentage(current.Idle, last.Idle, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			iowait, err := calcPercentage(current.IoWait, last.IoWait, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			irq, err := calcPercentage(current.Irq, last.Irq, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			softirq, err := calcPercentage(current.SoftIrq, last.SoftIrq, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			steal, err := calcPercentage(current.Steal, last.Steal, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			guest, err := calcPercentage(current.Guest, last.Guest, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			guestNice, err := calcPercentage(current.GuestNice, last.GuestNice, current.Total, last.Total)
			if err != nil {
				return nil, err
			}

			result = append(result, cpuPercentages{
				GroupName: key,
				User:      user,
				Nice:      nice,
				System:    system,
				Idle:      idle,
				IoWait:    iowait,
				Irq:       irq,
				SoftIrq:   softirq,
				Steal:     steal,
				Guest:     guest,
				GuestNice: guestNice,
			})
		}

	}

	return result, nil
}

func calcPercentage(currentValue *uint64, lastValue *uint64, currentTotal uint64, lastTotal uint64) (*float64, error) {
	if currentValue == nil || lastValue == nil {
		return nil, nil
	}

	diff := *currentValue - *lastValue
	total := currentTotal - lastTotal

	if diff < 0 || total < 0 {
		return nil, errors.New("counter may be reset")
	}

	ret := float64(diff) / float64(total) * 100.0
	return &ret, nil
}

func fetchLoadavg5() (float64, error) {
	contentbytes, err := ioutil.ReadFile("/proc/loadavg")
	if err != nil {
		return 0.0, err
	}
	content := string(contentbytes)
	cols := strings.Fields(content)

	if len(cols) > 2 {
		f, err := strconv.ParseFloat(cols[1], 64)
		if err != nil {
			return 0.0, err
		}
		return f, nil
	}
	return 0.0, fmt.Errorf("cannot fetch loadavg5")
}

func printValue(key string, value *float64, time time.Time) {
	if value != nil {
		fmt.Printf("%s\t%f\t%d\n", key, *value, time.Unix())
	}
}

func outputCPUUsage(cpuUsage []cpuPercentages, now time.Time) {
	for _, u := range cpuUsage {
		printValue(fmt.Sprintf("multicore.cpu.%s.user", u.GroupName), u.User, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.nice", u.GroupName), u.Nice, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.system", u.GroupName), u.System, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.idle", u.GroupName), u.Idle, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.iowait", u.GroupName), u.IoWait, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.irq", u.GroupName), u.Irq, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.softirq", u.GroupName), u.SoftIrq, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.steal", u.GroupName), u.Steal, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.guest", u.GroupName), u.Guest, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.guest_nice", u.GroupName), u.GuestNice, now)
	}
}

func outputLoadavgPerCore(loadavgPerCore float64, now time.Time) {
	printValue("multicore.loadavg_per_core.loadavg5", &loadavgPerCore, now)
}

func outputDefinitions() {
	fmt.Println("# mackerel-agent-plugin")
	var graphs mp.GraphDef
	graphs.Graphs = graphDef

	b, err := json.Marshal(graphs)
	if err != nil {
		log.Fatalln("OutputDefinitions: ", err)
	}
	fmt.Println(string(b))
}

func outputMulticore(tempFileName string) {
	now := time.Now()

	currentValues, err := collectProcStatValues()
	if err != nil {
		log.Fatalln("collectProcStatValues: ", err)
	}

	savedItem, err := fetchSavedItem(tempFileName)
	saveValues(tempFileName, currentValues, now)
	if err != nil {
		log.Fatalln("fetchLastValues: ", err)
	}

	// maybe first time run
	if savedItem == nil {
		return
	}

	cpuUsage, err := calcCPUUsage(currentValues, now, savedItem)
	if err != nil {
		log.Fatalln("calcCPUUsage: ", err)
	}

	loadavg5, err := fetchLoadavg5()
	if err != nil {
		log.Fatalln("fetchLoadavg5: ", err)
	}
	loadPerCPUCount := loadavg5 / (float64(len(cpuUsage)))

	outputCPUUsage(cpuUsage, now)
	outputLoadavgPerCore(loadPerCPUCount, now)
}

func generateTempfilePath() string {
	dir := os.Getenv("MACKEREL_PLUGIN_WORKDIR")
	if dir == "" {
		dir = os.TempDir()
	}
	return filepath.Join(dir, "mackerel-plugin-multicore")
}

// Do the plugin
func Do() {
	var tempFileName string
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	tempFileName = *optTempfile
	if tempFileName == "" {
		tempFileName = generateTempfilePath()
	}

	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		outputDefinitions()
	} else {
		outputMulticore(tempFileName)
	}
}
<commit_msg>correct counter reset judge<commit_after>package mpmulticore

import (
	"bufio"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	mp "github.com/mackerelio/go-mackerel-plugin-helper"
)

var graphDef = map[string]mp.Graphs{
	"multicore.cpu.#": {
		Label: "MultiCore CPU",
		Unit:  "percentage",
		Metrics: []mp.Metrics{
			{Name: "user", Label: "user", Diff: false, Stacked: true},
			{Name: "nice", Label: "nice", Diff: false, Stacked: true},
			{Name: "system", Label: "system", Diff: false, Stacked: true},
			{Name: "idle", Label: "idle", Diff: false, Stacked: true},
			{Name: "iowait", Label: "ioWait", Diff: false, Stacked: true},
			{Name: "irq", Label: "irq", Diff: false, Stacked: true},
			{Name: "softirq", Label: "softirq", Diff: false, Stacked: true},
			{Name: "steal", Label: "steal", Diff: false, Stacked: true},
			{Name: "guest", Label: "guest", Diff: false, Stacked: true},
			{Name: "guest_nice", Label: "guest_nice", Diff: false, Stacked: true},
		},
	},
	"multicore.loadavg_per_core": {
		Label: "MultiCore loadavg5 per core",
		Unit:  "float",
		Metrics: []mp.Metrics{
			{Name: "loadavg5", Label: "loadavg5", Diff: false, Stacked: false},
		},
	},
}

type saveItem struct {
	LastTime       time.Time
	ProcStatsByCPU map[string]procStats
}

type procStats struct {
	User      *uint64 `json:"user"`
	Nice      *uint64 `json:"nice"`
	System    *uint64 `json:"system"`
	Idle      *uint64 `json:"idle"`
	IoWait    *uint64 `json:"iowait"`
	Irq       *uint64 `json:"irq"`
	SoftIrq   *uint64 `json:"softirq"`
	Steal     *uint64 `json:"steal"`
	Guest     *uint64 `json:"guest"`
	GuestNice *uint64 `json:"guest_nice"`
	Total     uint64  `json:"total"`
}

type cpuPercentages struct {
	GroupName string
	User      *float64
	Nice      *float64
	System    *float64
	Idle      *float64
	IoWait    *float64
	Irq       *float64
	SoftIrq   *float64
	Steal     *float64
	Guest     *float64
	GuestNice *float64
}

func parseCounters(values []string) ([]uint64, error) {
	var result []uint64
	for _, v := range values {
		f, err := strconv.ParseUint(v, 10, 64)
		if err != nil {
			return nil, err
		}
		result = append(result, f)
	}
	return result, nil
}

func fill(arr []uint64, elementCount int) []*uint64 {
	var filled []*uint64
	for _, v := range arr {
		copy := v
		filled = append(filled, &copy)
	}

	if len(arr) < elementCount {
		emptyArray := make([]*uint64, elementCount-len(arr))
		filled = append(filled, emptyArray...)
	}
	return filled
}

func parseProcStat(out io.Reader) (map[string]procStats, error) {
	scanner := bufio.NewScanner(out)
	var result = make(map[string]procStats)
	for scanner.Scan() {
		line := scanner.Text()
		if !strings.HasPrefix(line, "cpu") {
			continue
		}

		fields := strings.Fields(line)
		key := fields[0]
		values := fields[1:]

		// skip total cpu usage
		if key == "cpu" {
			continue
		}

		counterValues, err := parseCounters(values)
		if err != nil {
			return nil, err
		}

		var total uint64
		for _, v := range counterValues {
			total += v
		}

		filledValues := fill(counterValues, 10)

		result[key] = procStats{
			User:      filledValues[0],
			Nice:      filledValues[1],
			System:    filledValues[2],
			Idle:      filledValues[3],
			IoWait:    filledValues[4],
			Irq:       filledValues[5],
			SoftIrq:   filledValues[6],
			Steal:     filledValues[7],
			Guest:     filledValues[8],
			GuestNice: filledValues[9],
			Total:     total,
		}
	}
	return result, nil
}

func collectProcStatValues() (map[string]procStats, error) {
	file, err := os.Open("/proc/stat")
	if err != nil {
		return nil, err
	}
	defer file.Close()
	return parseProcStat(file)
}

func saveValues(tempFileName string, values map[string]procStats, now time.Time) error {
	f, err := os.Create(tempFileName)
	if err != nil {
		return err
	}
	defer f.Close()

	s := saveItem{
		LastTime:       now,
		ProcStatsByCPU: values,
	}

	encoder := json.NewEncoder(f)
	err = encoder.Encode(s)
	if err != nil {
		return err
	}

	return nil
}

func fetchSavedItem(tempFileName string) (*saveItem, error) {
	f, err := os.Open(tempFileName)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	defer f.Close()

	var stat saveItem
	decoder := json.NewDecoder(f)
	err = decoder.Decode(&stat)
	if err != nil {
		return nil, err
	}
	return &stat, nil
}

func calcCPUUsage(currentValues map[string]procStats, now time.Time, savedItem *saveItem) ([]cpuPercentages, error) {
	lastValues := savedItem.ProcStatsByCPU
	lastTime := savedItem.LastTime

	diffTime := now.Unix() - lastTime.Unix()
	if diffTime > 600 {
		return nil, fmt.Errorf("Too long duration")
	}

	var result []cpuPercentages
	for key, current := range currentValues {
		last, ok := lastValues[key]
		if ok {
			user, err := calcPercentage(current.User, last.User, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			nice, err := calcPercentage(current.Nice, last.Nice, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			system, err := calcPercentage(current.System, last.System, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			idle, err := calcPercentage(current.Idle, last.Idle, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			iowait, err := calcPercentage(current.IoWait, last.IoWait, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			irq, err := calcPercentage(current.Irq, last.Irq, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			softirq, err := calcPercentage(current.SoftIrq, last.SoftIrq, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			steal, err := calcPercentage(current.Steal, last.Steal, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			guest, err := calcPercentage(current.Guest, last.Guest, current.Total, last.Total)
			if err != nil {
				return nil, err
			}
			guestNice, err := calcPercentage(current.GuestNice, last.GuestNice, current.Total, last.Total)
			if err != nil {
				return nil, err
			}

			result = append(result, cpuPercentages{
				GroupName: key,
				User:      user,
				Nice:      nice,
				System:    system,
				Idle:      idle,
				IoWait:    iowait,
				Irq:       irq,
				SoftIrq:   softirq,
				Steal:     steal,
				Guest:     guest,
				GuestNice: guestNice,
			})
		}

	}

	return result, nil
}

func calcPercentage(currentValue *uint64, lastValue *uint64, currentTotal uint64, lastTotal uint64) (*float64, error) {
	if currentValue == nil || lastValue == nil {
		return nil, nil
	}

	if *lastValue > *currentValue || lastTotal > currentTotal {
		return nil, errors.New("counter may be reset")
	}

	ret := float64(*currentValue-*lastValue) / float64(currentTotal-lastTotal) * 100.0
	return &ret, nil
}

func fetchLoadavg5() (float64, error) {
	contentbytes, err := ioutil.ReadFile("/proc/loadavg")
	if err != nil {
		return 0.0, err
	}
	content := string(contentbytes)
	cols := strings.Fields(content)

	if len(cols) > 2 {
		f, err := strconv.ParseFloat(cols[1], 64)
		if err != nil {
			return 0.0, err
		}
		return f, nil
	}
	return 0.0, fmt.Errorf("cannot fetch loadavg5")
}

func printValue(key string, value *float64, time time.Time) {
	if value != nil {
		fmt.Printf("%s\t%f\t%d\n", key, *value, time.Unix())
	}
}

func outputCPUUsage(cpuUsage []cpuPercentages, now time.Time) {
	for _, u := range cpuUsage {
		printValue(fmt.Sprintf("multicore.cpu.%s.user", u.GroupName), u.User, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.nice", u.GroupName), u.Nice, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.system", u.GroupName), u.System, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.idle", u.GroupName), u.Idle, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.iowait", u.GroupName), u.IoWait, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.irq", u.GroupName), u.Irq, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.softirq", u.GroupName), u.SoftIrq, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.steal", u.GroupName), u.Steal, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.guest", u.GroupName), u.Guest, now)
		printValue(fmt.Sprintf("multicore.cpu.%s.guest_nice", u.GroupName), u.GuestNice, now)
	}
}

func outputLoadavgPerCore(loadavgPerCore float64, now time.Time) {
	printValue("multicore.loadavg_per_core.loadavg5", &loadavgPerCore, now)
}

func outputDefinitions() {
	fmt.Println("# mackerel-agent-plugin")
	var graphs mp.GraphDef
	graphs.Graphs = graphDef

	b, err := json.Marshal(graphs)
	if err != nil {
		log.Fatalln("OutputDefinitions: ", err)
	}
	fmt.Println(string(b))
}

func outputMulticore(tempFileName string) {
	now := time.Now()

	currentValues, err := collectProcStatValues()
	if err != nil {
		log.Fatalln("collectProcStatValues: ", err)
	}

	savedItem, err := fetchSavedItem(tempFileName)
	saveValues(tempFileName, currentValues, now)
	if err != nil {
		log.Fatalln("fetchLastValues: ", err)
	}

	// maybe first time run
	if savedItem == nil {
		return
	}

	cpuUsage, err := calcCPUUsage(currentValues, now, savedItem)
	if err != nil {
		log.Fatalln("calcCPUUsage: ", err)
	}

	loadavg5, err := fetchLoadavg5()
	if err != nil {
		log.Fatalln("fetchLoadavg5: ", err)
	}
	loadPerCPUCount := loadavg5 / (float64(len(cpuUsage)))

	outputCPUUsage(cpuUsage, now)
	outputLoadavgPerCore(loadPerCPUCount, now)
}

func generateTempfilePath() string {
	dir := os.Getenv("MACKEREL_PLUGIN_WORKDIR")
	if dir == "" {
		dir = os.TempDir()
	}
	return filepath.Join(dir, "mackerel-plugin-multicore")
}

// Do the plugin
func Do() {
	var tempFileName string
	optTempfile := flag.String("tempfile", "", "Temp file name")
	flag.Parse()

	tempFileName = *optTempfile
	if tempFileName == "" {
		tempFileName = generateTempfilePath()
	}

	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		outputDefinitions()
	} else {
		outputMulticore(tempFileName)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"code.google.com/p/go.net/websocket"
	"encoding/json"
	"expvar"
	"io"
	"log"
	"net/http"
	"os"
	"runtime"
	"strconv"
	"time"
)

type wsConnection struct {
	// The websocket connection.
	ws *websocket.Conn

	// Buffered channel of outbound messages.
	send chan string
}

type monitorHub struct {
	connections map[*wsConnection]bool
	broadcast   chan string
	register    chan *wsConnection
	unregister  chan *wsConnection
}

var hub = monitorHub{
	broadcast:   make(chan string),
	register:    make(chan *wsConnection, 10),
	unregister:  make(chan *wsConnection, 10),
	connections: make(map[*wsConnection]bool),
}

func (h *monitorHub) run() {
	for {
		select {
		case c := <-h.register:
			h.connections[c] = true
			log.Println("Queuing initial status")
			c.send <- initialStatus()
		case c := <-h.unregister:
			log.Println("Unregistering connection")
			delete(h.connections, c)
		case m := <-h.broadcast:
			for c := range h.connections {
				if len(c.send)+5 > cap(c.send) {
					log.Println("WS connection too close to cap")
					c.send <- `{"error": "too slow"}`
					close(c.send)
					go c.ws.Close()
					h.unregister <- c
					continue
				}
				select {
				case c.send <- m:
				default:
					close(c.send)
					delete(h.connections, c)
					log.Println("Closing channel when sending")
					go c.ws.Close()
				}
			}
		}
	}
}

func (c *wsConnection) reader() {
	for {
		var message string
		err := websocket.Message.Receive(c.ws, &message)
		if err != nil {
			log.Println("WS read error:", err)
			break
		}
		log.Println("WS message", message)
		// TODO(ask) take configuration options etc
		//h.broadcast <- message
	}
	c.ws.Close()
}

func (c *wsConnection) writer() {
	for message := range c.send {
		err := websocket.Message.Send(c.ws, message)
		if err != nil {
			log.Println("WS write error:", err)
			break
		}
	}
	c.ws.Close()
}

func wsHandler(ws *websocket.Conn) {
	log.Println("Starting new WS connection")
	c := &wsConnection{send: make(chan string, 180), ws: ws}
	hub.register <- c
	defer func() {
		log.Println("sending unregister message")
		hub.unregister <- c
	}()
	go c.writer()
	c.reader()
}

func initialStatus() string {
	status := map[string]string{"v": VERSION, "id": serverId, "ip": serverIP}
	hostname, err := os.Hostname()
	if err == nil {
		status["h"] = hostname
	}

	status["up"] = strconv.Itoa(int(time.Since(timeStarted).Seconds()))
	status["started"] = strconv.Itoa(int(timeStarted.Unix()))

	message, err := json.Marshal(status)
	return string(message)
}

func logStatus() {
	log.Println(initialStatus())
	// Does not impact performance too much
	lastQueryCount := expVarToInt64(qCounter)

	for {
		current := expVarToInt64(qCounter)
		newQueries := current - lastQueryCount
		lastQueryCount = current

		log.Println("goroutines", runtime.NumGoroutine(), "queries", newQueries)

		time.Sleep(60 * time.Second)
	}
}

func monitor() {
	go logStatus()

	if len(*flaghttp) == 0 {
		return
	}
	go hub.run()
	go httpHandler()

	lastQueryCount := expVarToInt64(qCounter)

	for {
		current := expVarToInt64(qCounter)
		newQueries := current - lastQueryCount
		lastQueryCount = current

		status := map[string]string{}
		status["up"] = strconv.Itoa(int(time.Since(timeStarted).Seconds()))
		status["qs"] = qCounter.String()
		status["qps"] = strconv.FormatInt(newQueries, 10)

		message, err := json.Marshal(status)

		if err == nil {
			hub.broadcast <- string(message)
		}
		time.Sleep(1 * time.Second)
	}
}

func MainServer(w http.ResponseWriter, req *http.Request) {
	if req.RequestURI != "/version" {
		http.NotFound(w, req)
		return
	}
	io.WriteString(w, `<html><head><title>GeoDNS `+
		VERSION+`</title><body>`+
		initialStatus()+
		`</body></html>`)
}

func httpHandler() {
	http.Handle("/monitor", websocket.Handler(wsHandler))
	http.HandleFunc("/", MainServer)

	log.Fatal(http.ListenAndServe(*flaghttp, nil))
}

func expVarToInt64(i *expvar.Int) (j int64) {
	j, _ = strconv.ParseInt(i.String(), 10, 64)
	return
}
<commit_msg>Better message when websocket connection closes<commit_after>package main

import (
	"code.google.com/p/go.net/websocket"
	"encoding/json"
	"expvar"
	"io"
	"log"
	"net/http"
	"os"
	"runtime"
	"strconv"
	"time"
)

type wsConnection struct {
	// The websocket connection.
	ws *websocket.Conn

	// Buffered channel of outbound messages.
	send chan string
}

type monitorHub struct {
	connections map[*wsConnection]bool
	broadcast   chan string
	register    chan *wsConnection
	unregister  chan *wsConnection
}

var hub = monitorHub{
	broadcast:   make(chan string),
	register:    make(chan *wsConnection, 10),
	unregister:  make(chan *wsConnection, 10),
	connections: make(map[*wsConnection]bool),
}

func (h *monitorHub) run() {
	for {
		select {
		case c := <-h.register:
			h.connections[c] = true
			log.Println("Queuing initial status")
			c.send <- initialStatus()
		case c := <-h.unregister:
			log.Println("Unregistering connection")
			delete(h.connections, c)
		case m := <-h.broadcast:
			for c := range h.connections {
				if len(c.send)+5 > cap(c.send) {
					log.Println("WS connection too close to cap")
					c.send <- `{"error": "too slow"}`
					close(c.send)
					go c.ws.Close()
					h.unregister <- c
					continue
				}
				select {
				case c.send <- m:
				default:
					close(c.send)
					delete(h.connections, c)
					log.Println("Closing channel when sending")
					go c.ws.Close()
				}
			}
		}
	}
}

func (c *wsConnection) reader() {
	for {
		var message string
		err := websocket.Message.Receive(c.ws, &message)
		if err != nil {
			if err == io.EOF {
				log.Println("WS connection closed")
			} else {
				log.Println("WS read error:", err)
			}
			break
		}
		log.Println("WS message", message)
		// TODO(ask) take configuration options etc
		//h.broadcast <- message
	}
	c.ws.Close()
}

func (c *wsConnection) writer() {
	for message := range c.send {
		err := websocket.Message.Send(c.ws, message)
		if err != nil {
			log.Println("WS write error:", err)
			break
		}
	}
	c.ws.Close()
}

func wsHandler(ws *websocket.Conn) {
	log.Println("Starting new WS connection")
	c := &wsConnection{send: make(chan string, 180), ws: ws}
	hub.register <- c
	defer func() {
		log.Println("sending unregister message")
		hub.unregister <- c
	}()
	go c.writer()
	c.reader()
}

func initialStatus() string {
	status := map[string]string{"v": VERSION, "id": serverId, "ip": serverIP}
	hostname, err := os.Hostname()
	if err == nil {
		status["h"] = hostname
	}

	status["up"] = strconv.Itoa(int(time.Since(timeStarted).Seconds()))
	status["started"] = strconv.Itoa(int(timeStarted.Unix()))

	message, err := json.Marshal(status)
	return string(message)
}

func logStatus() {
	log.Println(initialStatus())
	// Does not impact performance too much
	lastQueryCount := expVarToInt64(qCounter)

	for {
		current := expVarToInt64(qCounter)
		newQueries := current - lastQueryCount
		lastQueryCount = current

		log.Println("goroutines", runtime.NumGoroutine(), "queries", newQueries)

		time.Sleep(60 * time.Second)
	}
}

func monitor() {
	go logStatus()

	if len(*flaghttp) == 0 {
		return
	}
	go hub.run()
	go httpHandler()

	lastQueryCount := expVarToInt64(qCounter)

	for {
		current := expVarToInt64(qCounter)
		newQueries := current - lastQueryCount
		lastQueryCount = current

		status := map[string]string{}
		status["up"] = strconv.Itoa(int(time.Since(timeStarted).Seconds()))
		status["qs"] = qCounter.String()
		status["qps"] = strconv.FormatInt(newQueries, 10)

		message, err := json.Marshal(status)

		if err == nil {
			hub.broadcast <- string(message)
		}
		time.Sleep(1 * time.Second)
	}
}

func MainServer(w http.ResponseWriter, req *http.Request) {
	if req.RequestURI != "/version" {
		http.NotFound(w, req)
		return
	}
	io.WriteString(w, `<html><head><title>GeoDNS `+
		VERSION+`</title><body>`+
		initialStatus()+
		`</body></html>`)
}

func httpHandler() {
	http.Handle("/monitor", websocket.Handler(wsHandler))
	http.HandleFunc("/", MainServer)

	log.Fatal(http.ListenAndServe(*flaghttp, nil))
}

func expVarToInt64(i *expvar.Int) (j int64) {
	j, _ = strconv.ParseInt(i.String(), 10, 64)
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package lifecycle

import (
	"fmt"
	"io"
	"time"

	"github.com/golang/glog"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilcache "k8s.io/apimachinery/pkg/util/cache"
	"k8s.io/apimachinery/pkg/util/clock"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/admission"
	"k8s.io/apiserver/pkg/admission/initializer"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	corelisters "k8s.io/client-go/listers/core/v1"
)

const (
	// Name of admission plug-in
	PluginName = "NamespaceLifecycle"
	// how long a namespace stays in the force live lookup cache before expiration.
	forceLiveLookupTTL = 30 * time.Second
	// how long to wait for a missing namespace before re-checking the cache (and then doing a live lookup)
	// this accomplishes two things:
	// 1. It allows a watch-fed cache time to observe a namespace creation event
	// 2. It allows time for a namespace creation to distribute to members of a storage cluster,
	//    so the live lookup has a better chance of succeeding even if it isn't performed against the leader.
	missingNamespaceWait = 50 * time.Millisecond
)

// Register registers a plugin
func Register(plugins *admission.Plugins) {
	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
		return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic,
			// user specified configuration that cannot be rebuilt
			"openshift-config",
			// the CVO which is the root we use to rebuild all the rest
			"openshift-cluster-version",
			// contains a namespaced list of all nodes in the cluster (yeah, weird.  they do it for multi-tenant management I think?)
			"openshift-cluster-api",
		))
	})
}

// Lifecycle is an implementation of admission.Interface.
// It enforces life-cycle constraints around a Namespace depending on its Phase
type Lifecycle struct {
	*admission.Handler
	client             kubernetes.Interface
	immortalNamespaces sets.String
	namespaceLister    corelisters.NamespaceLister
	// forceLiveLookupCache holds a list of entries for namespaces that we have a strong reason to believe are stale in our local cache.
	// if a namespace is in this cache, then we will ignore our local state and always fetch latest from api server.
	forceLiveLookupCache *utilcache.LRUExpireCache
}

var _ = initializer.WantsExternalKubeInformerFactory(&Lifecycle{})
var _ = initializer.WantsExternalKubeClientSet(&Lifecycle{})

func (l *Lifecycle) Admit(a admission.Attributes) error {
	// prevent deletion of immortal namespaces
	if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
		return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
	}

	// always allow non-namespaced resources
	if len(a.GetNamespace()) == 0 && a.GetKind().GroupKind() != v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
		return nil
	}

	if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
		// if a namespace is deleted, we want to prevent all further creates into it
		// while it is undergoing termination.  to reduce incidences where the cache
		// is slow to update, we add the namespace into a force live lookup list to ensure
		// we are not looking at stale state.
		if a.GetOperation() == admission.Delete {
			l.forceLiveLookupCache.Add(a.GetName(), true, forceLiveLookupTTL)
		}
		// allow all operations to namespaces
		return nil
	}

	// always allow deletion of other resources
	if a.GetOperation() == admission.Delete {
		return nil
	}

	// always allow access review checks.  Returning status about the namespace would be leaking information
	if isAccessReview(a) {
		return nil
	}

	// we need to wait for our caches to warm
	if !l.WaitForReady() {
		return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
	}

	var (
		exists bool
		err    error
	)

	namespace, err := l.namespaceLister.Get(a.GetNamespace())
	if err != nil {
		if !errors.IsNotFound(err) {
			return errors.NewInternalError(err)
		}
	} else {
		exists = true
	}

	if !exists && a.GetOperation() == admission.Create {
		// give the cache time to observe the namespace before rejecting a create.
		// this helps when creating a namespace and immediately creating objects within it.
		time.Sleep(missingNamespaceWait)
		namespace, err = l.namespaceLister.Get(a.GetNamespace())
		switch {
		case errors.IsNotFound(err):
			// no-op
		case err != nil:
			return errors.NewInternalError(err)
		default:
			exists = true
		}
		if exists {
			glog.V(4).Infof("found %s in cache after waiting", a.GetNamespace())
		}
	}

	// forceLiveLookup if true will skip looking at local cache state and instead always make a live call to server.
	forceLiveLookup := false
	if _, ok := l.forceLiveLookupCache.Get(a.GetNamespace()); ok {
		// we think the namespace was marked for deletion, but our current local cache says otherwise, we will force a live lookup.
		forceLiveLookup = exists && namespace.Status.Phase == v1.NamespaceActive
	}

	// refuse to operate on non-existent namespaces
	if !exists || forceLiveLookup {
		// as a last resort, make a call directly to storage
		namespace, err = l.client.CoreV1().Namespaces().Get(a.GetNamespace(), metav1.GetOptions{})
		switch {
		case errors.IsNotFound(err):
			return err
		case err != nil:
			return errors.NewInternalError(err)
		}
		glog.V(4).Infof("found %s via storage lookup", a.GetNamespace())
	}

	// ensure that we're not trying to create objects in terminating namespaces
	if a.GetOperation() == admission.Create {
		if namespace.Status.Phase != v1.NamespaceTerminating {
			return nil
		}

		// TODO: This should probably not be a 403
		return admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated", a.GetNamespace()))
	}

	return nil
}

// NewLifecycle creates a new namespace Lifecycle admission control handler
func NewLifecycle(immortalNamespaces sets.String) (*Lifecycle, error) {
	return newLifecycleWithClock(immortalNamespaces, clock.RealClock{})
}

func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (*Lifecycle, error) {
	forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock)
	return &Lifecycle{
		Handler:              admission.NewHandler(admission.Create, admission.Update, admission.Delete),
		immortalNamespaces:   immortalNamespaces,
		forceLiveLookupCache: forceLiveLookupCache,
	}, nil
}

// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
func (l *Lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
	namespaceInformer := f.Core().V1().Namespaces()
	l.namespaceLister = namespaceInformer.Lister()
	l.SetReadyFunc(namespaceInformer.Informer().HasSynced)
}

// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
func (l *Lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) {
	l.client = client
}

// ValidateInitialization implements the InitializationValidator interface.
func (l *Lifecycle) ValidateInitialization() error {
	if l.namespaceLister == nil {
		return fmt.Errorf("missing namespaceLister")
	}
	if l.client == nil {
		return fmt.Errorf("missing client")
	}
	return nil
}

// accessReviewResources are resources which give a view into permissions in a namespace.  Users must be allowed to create these
// resources because returning "not found" errors allows someone to search for the "people I'm going to fire in 2017" namespace.
var accessReviewResources = map[schema.GroupResource]bool{
	{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}:                            true,
	schema.GroupResource{Group: "", Resource: "subjectaccessreviews"}:                                 true,
	schema.GroupResource{Group: "", Resource: "localsubjectaccessreviews"}:                            true,
	schema.GroupResource{Group: "", Resource: "resourceaccessreviews"}:                                true,
	schema.GroupResource{Group: "", Resource: "localresourceaccessreviews"}:                           true,
	schema.GroupResource{Group: "", Resource: "selfsubjectrulesreviews"}:                              true,
	schema.GroupResource{Group: "", Resource: "subjectrulesreviews"}:                                  true,
	schema.GroupResource{Group: "authorization.openshift.io", Resource: "subjectaccessreviews"}:       true,
	schema.GroupResource{Group: "authorization.openshift.io", Resource: "localsubjectaccessreviews"}:  true,
	schema.GroupResource{Group: "authorization.openshift.io", Resource: "resourceaccessreviews"}:      true,
	schema.GroupResource{Group: "authorization.openshift.io", Resource: "localresourceaccessreviews"}: true,
	schema.GroupResource{Group: "authorization.openshift.io", Resource: "selfsubjectrulesreviews"}:    true,
	schema.GroupResource{Group: "authorization.openshift.io", Resource: "subjectrulesreviews"}:        true,
}

func isAccessReview(a admission.Attributes) bool {
	return accessReviewResources[a.GetResource().GroupResource()]
}
<commit_msg>UPSTREAM: <carry>: Update protected namespaces: openshift-machine-api<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package lifecycle

import (
	"fmt"
	"io"
	"time"

	"github.com/golang/glog"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilcache "k8s.io/apimachinery/pkg/util/cache"
	"k8s.io/apimachinery/pkg/util/clock"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/admission"
	"k8s.io/apiserver/pkg/admission/initializer"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	corelisters "k8s.io/client-go/listers/core/v1"
)

const (
	// Name of admission plug-in
	PluginName = "NamespaceLifecycle"
	// how long a namespace stays in the force live lookup cache before expiration.
	forceLiveLookupTTL = 30 * time.Second
	// how long to wait for a missing namespace before re-checking the cache (and then doing a live lookup)
	// this accomplishes two things:
	// 1. It allows a watch-fed cache time to observe a namespace creation event
	// 2. It allows time for a namespace creation to distribute to members of a storage cluster,
	//    so the live lookup has a better chance of succeeding even if it isn't performed against the leader.
	missingNamespaceWait = 50 * time.Millisecond
)

// Register registers a plugin
func Register(plugins *admission.Plugins) {
	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
		return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic,
			// user specified configuration that cannot be rebuilt
			"openshift-config",
			// the CVO which is the root we use to rebuild all the rest
			"openshift-cluster-version",
			// contains a namespaced list of all nodes in the cluster (yeah, weird.  they do it for multi-tenant management I think?)
			"openshift-machine-api",
		))
	})
}

// Lifecycle is an implementation of admission.Interface.
// It enforces life-cycle constraints around a Namespace depending on its Phase
type Lifecycle struct {
	*admission.Handler
	client             kubernetes.Interface
	immortalNamespaces sets.String
	namespaceLister    corelisters.NamespaceLister
	// forceLiveLookupCache holds a list of entries for namespaces that we have a strong reason to believe are stale in our local cache.
	// if a namespace is in this cache, then we will ignore our local state and always fetch latest from api server.
	forceLiveLookupCache *utilcache.LRUExpireCache
}

var _ = initializer.WantsExternalKubeInformerFactory(&Lifecycle{})
var _ = initializer.WantsExternalKubeClientSet(&Lifecycle{})

func (l *Lifecycle) Admit(a admission.Attributes) error {
	// prevent deletion of immortal namespaces
	if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
		return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
	}

	// always allow non-namespaced resources
	if len(a.GetNamespace()) == 0 && a.GetKind().GroupKind() != v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
		return nil
	}

	if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
		// if a namespace is deleted, we want to prevent all further creates into it
		// while it is undergoing termination.  to reduce incidences where the cache
		// is slow to update, we add the namespace into a force live lookup list to ensure
		// we are not looking at stale state.
		if a.GetOperation() == admission.Delete {
			l.forceLiveLookupCache.Add(a.GetName(), true, forceLiveLookupTTL)
		}
		// allow all operations to namespaces
		return nil
	}

	// always allow deletion of other resources
	if a.GetOperation() == admission.Delete {
		return nil
	}

	// always allow access review checks.  Returning status about the namespace would be leaking information
	if isAccessReview(a) {
		return nil
	}

	// we need to wait for our caches to warm
	if !l.WaitForReady() {
		return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
	}

	var (
		exists bool
		err    error
	)

	namespace, err := l.namespaceLister.Get(a.GetNamespace())
	if err != nil {
		if !errors.IsNotFound(err) {
			return errors.NewInternalError(err)
		}
	} else {
		exists = true
	}

	if !exists && a.GetOperation() == admission.Create {
		// give the cache time to observe the namespace before rejecting a create.
		// this helps when creating a namespace and immediately creating objects within it.
		time.Sleep(missingNamespaceWait)
		namespace, err = l.namespaceLister.Get(a.GetNamespace())
		switch {
		case errors.IsNotFound(err):
			// no-op
		case err != nil:
			return errors.NewInternalError(err)
		default:
			exists = true
		}
		if exists {
			glog.V(4).Infof("found %s in cache after waiting", a.GetNamespace())
		}
	}

	// forceLiveLookup if true will skip looking at local cache state and instead always make a live call to server.
	forceLiveLookup := false
	if _, ok := l.forceLiveLookupCache.Get(a.GetNamespace()); ok {
		// we think the namespace was marked for deletion, but our current local cache says otherwise, we will force a live lookup.
		forceLiveLookup = exists && namespace.Status.Phase == v1.NamespaceActive
	}

	// refuse to operate on non-existent namespaces
	if !exists || forceLiveLookup {
		// as a last resort, make a call directly to storage
		namespace, err = l.client.CoreV1().Namespaces().Get(a.GetNamespace(), metav1.GetOptions{})
		switch {
		case errors.IsNotFound(err):
			return err
		case err != nil:
			return errors.NewInternalError(err)
		}
		glog.V(4).Infof("found %s via storage lookup", a.GetNamespace())
	}

	// ensure that we're not trying to create objects in terminating namespaces
	if a.GetOperation() == admission.Create {
		if namespace.Status.Phase != v1.NamespaceTerminating {
			return nil
		}

		// TODO: This should probably not be a 403
		return admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated", a.GetNamespace()))
	}

	return nil
}

// NewLifecycle creates a new namespace Lifecycle admission control handler
func NewLifecycle(immortalNamespaces sets.String) (*Lifecycle, error) {
	return newLifecycleWithClock(immortalNamespaces, clock.RealClock{})
}

func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (*Lifecycle, error) {
	forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock)
	return &Lifecycle{
		Handler:              admission.NewHandler(admission.Create, admission.Update, admission.Delete),
		immortalNamespaces:   immortalNamespaces,
		forceLiveLookupCache: forceLiveLookupCache,
	}, nil
}

// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
func (l *Lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
	namespaceInformer := f.Core().V1().Namespaces()
	l.namespaceLister = namespaceInformer.Lister()
	l.SetReadyFunc(namespaceInformer.Informer().HasSynced)
}

// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
func (l *Lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) {
	l.client = client
}

// ValidateInitialization implements the InitializationValidator interface.
func (l *Lifecycle) ValidateInitialization() error {
	if l.namespaceLister == nil {
		return fmt.Errorf("missing namespaceLister")
	}
	if l.client == nil {
		return fmt.Errorf("missing client")
	}
	return nil
}

// accessReviewResources are resources which give a view into permissions in a namespace.  Users must be allowed to create these
// resources because returning "not found" errors allows someone to search for the "people I'm going to fire in 2017" namespace.
var accessReviewResources = map[schema.GroupResource]bool{
	{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}:                            true,
	schema.GroupResource{Group: "", Resource: "subjectaccessreviews"}:                                 true,
	schema.GroupResource{Group: "", Resource: "localsubjectaccessreviews"}:                            true,
	schema.GroupResource{Group: "", Resource: "resourceaccessreviews"}:                                true,
	schema.GroupResource{Group: "", Resource: "localresourceaccessreviews"}:                           true,
	schema.GroupResource{Group: "", Resource: "selfsubjectrulesreviews"}:                              true,
	schema.GroupResource{Group: "", Resource: "subjectrulesreviews"}:                                  true,
	schema.GroupResource{Group: "authorization.openshift.io", Resource: "subjectaccessreviews"}:       true,
	schema.GroupResource{Group: "authorization.openshift.io", Resource: "localsubjectaccessreviews"}:  true,
	schema.GroupResource{Group: "authorization.openshift.io", Resource: "resourceaccessreviews"}:      true,
	schema.GroupResource{Group: "authorization.openshift.io", Resource: "localresourceaccessreviews"}: true,
	schema.GroupResource{Group: "authorization.openshift.io", Resource: "selfsubjectrulesreviews"}:    true,
	schema.GroupResource{Group: "authorization.openshift.io", Resource: "subjectrulesreviews"}:        true,
}

func isAccessReview(a admission.Attributes) bool {
	return accessReviewResources[a.GetResource().GroupResource()]
}
<|endoftext|>


<commit_before>package admin

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

// User is GO representation of the json output of a user creation
type User struct {
	ID                  string        `json:"user_id" url:"uid"`
	DisplayName         string        `json:"display_name" url:"display-name"`
	Email               string        `json:"email" url:"email"`
	Suspended           *int          `json:"suspended" url:"suspended"`
	MaxBuckets          *int          `json:"max_buckets" url:"max-buckets"`
	Subusers            []interface{} `json:"subusers"`
	Keys                []UserKeySpec `json:"keys"`
	SwiftKeys           []interface{} `json:"swift_keys"`
	Caps                []UserCapSpec `json:"caps"`
	OpMask              string        `json:"op_mask"`
	DefaultPlacement    string        `json:"default_placement"`
	DefaultStorageClass string        `json:"default_storage_class"`
	PlacementTags       []interface{} `json:"placement_tags"`
	BucketQuota         QuotaSpec     `json:"bucket_quota"`
	UserQuota           QuotaSpec     `json:"user_quota"`
	TempURLKeys         []interface{} `json:"temp_url_keys"`
	Type                string        `json:"type"`
	MfaIds              []interface{} `json:"mfa_ids"`
	KeyType             string        `url:"key-type"`
	Tenant              string        `url:"tenant"`
	GenerateKey         *bool         `url:"generate-key"`
	PurgeData           *int          `url:"purge-data"`
	GenerateStat        *bool         `url:"stats"`
	Stat                UserStat      `json:"stats"`
	UserCaps            string        `url:"user-caps"`
}

// UserCapSpec represents a user capability which gives access to certain ressources
type UserCapSpec struct {
	Type string `json:"type"`
	Perm string `json:"perm"`
}

// UserKeySpec is the user credential configuration
type UserKeySpec struct {
	User      string `json:"user"`
	AccessKey string `json:"access_key" url:"access-key"`
	SecretKey string `json:"secret_key" url:"secret-key"`
}

// UserStat contains information about storage consumption by the ceph user
type UserStat struct {
	Size        *uint64 `json:"size"`
	SizeRounded *uint64 `json:"size_rounded"`
	NumObjects  *uint64 `json:"num_objects"`
}

// GetUser retrieves a given object store user
func (api *API) GetUser(ctx context.Context, user User) (User, error) {
	if user.ID == "" && len(user.Keys) == 0 {
		return User{}, errMissingUserID
	}
	if len(user.Keys) > 0 {
		for _, key := range user.Keys {
			if key.AccessKey == "" {
				return User{}, errMissingUserAccessKey
			}
		}
	}

	//  valid parameters not supported by go-ceph: sync
	body, err := api.call(ctx, http.MethodGet, "/user", valueToURLParams(user, []string{"uid", "access-key", "stats"}))

	if err != nil {
		return User{}, err
	}

	u := User{}
	err = json.Unmarshal(body, &u)
	if err != nil {
		return User{}, fmt.Errorf("%s. %s. %w", unmarshalError, string(body), err)
	}

	return u, nil
}

// GetUsers lists all object store users
func (api *API) GetUsers(ctx context.Context) (*[]string, error) {
	body, err := api.call(ctx, http.MethodGet, "/metadata/user", nil)
	if err != nil {
		return nil, err
	}
	var users *[]string
	err = json.Unmarshal(body, &users)
	if err != nil {
		return nil, fmt.Errorf("%s. %s. %w", unmarshalError, string(body), err)
	}

	return users, nil
}

// CreateUser creates a user in the object store
func (api *API) CreateUser(ctx context.Context, user User) (User, error) {
	if user.ID == "" {
		return User{}, errMissingUserID
	}
	if user.DisplayName == "" {
		return User{}, errMissingUserDisplayName
	}

	//  valid parameters not supported by go-ceph: system, exclusive, default-placement, placement-tags
	body, err := api.call(ctx, http.MethodPut, "/user", valueToURLParams(user, []string{"uid", "display-name", "email", "key-type", "access-key", "secret-key", "user-caps", "tenant", "generate-key", "max-buckets", "suspended", "op-mask"}))
	if err != nil {
		return User{}, err
	}

	// Unmarshal response into Go type
	u := User{}
	err = json.Unmarshal(body, &u)
	if err != nil {
		return User{}, fmt.Errorf("%s. %s. %w", unmarshalError, string(body), err)
	}

	return u, nil
}

// RemoveUser remove an user from the object store
func (api *API) RemoveUser(ctx context.Context, user User) error {
	if user.ID == "" {
		return errMissingUserID
	}

	_, err := api.call(ctx, http.MethodDelete, "/user", valueToURLParams(user, []string{"uid", "purge-data"}))
	if err != nil {
		return err
	}

	return nil
}

// ModifyUser - http://docs.ceph.com/docs/latest/radosgw/adminops/#modify-user
func (api *API) ModifyUser(ctx context.Context, user User) (User, error) {
	if user.ID == "" {
		return User{}, errMissingUserID
	}

	// valid parameters not supported by go-ceph: system, default-placement, placement-tags
	body, err := api.call(ctx, http.MethodPost, "/user", valueToURLParams(user, []string{"uid", "display-name", "email", "generate-key", "access-key", "secret-key", "key-type", "max-buckets", "suspended", "op-mask"}))
	if err != nil {
		return User{}, err
	}

	u := User{}
	err = json.Unmarshal(body, &u)
	if err != nil {
		return User{}, fmt.Errorf("%s. %s. %w", unmarshalError, string(body), err)
	}

	return u, nil
}
<commit_msg>rgw/admin: Extend the User data to parse Subuser data<commit_after>package admin

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

// User is GO representation of the json output of a user creation
type User struct {
	ID                  string         `json:"user_id" url:"uid"`
	DisplayName         string         `json:"display_name" url:"display-name"`
	Email               string         `json:"email" url:"email"`
	Suspended           *int           `json:"suspended" url:"suspended"`
	MaxBuckets          *int           `json:"max_buckets" url:"max-buckets"`
	Subusers            []SubuserSpec  `json:"subusers" url:"-"`
	Keys                []UserKeySpec  `json:"keys"`
	SwiftKeys           []SwiftKeySpec `json:"swift_keys" url:"-"`
	Caps                []UserCapSpec  `json:"caps"`
	OpMask              string         `json:"op_mask"`
	DefaultPlacement    string         `json:"default_placement"`
	DefaultStorageClass string         `json:"default_storage_class"`
	PlacementTags       []interface{}  `json:"placement_tags"`
	BucketQuota         QuotaSpec      `json:"bucket_quota"`
	UserQuota           QuotaSpec      `json:"user_quota"`
	TempURLKeys         []interface{}  `json:"temp_url_keys"`
	Type                string         `json:"type"`
	MfaIds              []interface{}  `json:"mfa_ids"`
	KeyType             string         `url:"key-type"`
	Tenant              string         `url:"tenant"`
	GenerateKey         *bool          `url:"generate-key"`
	PurgeData           *int           `url:"purge-data"`
	GenerateStat        *bool          `url:"stats"`
	Stat                UserStat       `json:"stats"`
	UserCaps            string         `url:"user-caps"`
}

// SubuserSpec represents a subusers of a ceph-rgw user
type SubuserSpec struct {
	Name   string        `json:"id" url:"subuser"`
	Access SubuserAccess `json:"permissions" url:"access"`

	// these are always nil in answers, they are only relevant in requests
	GenerateKey *bool   `json:"-" url:"generate-key"`
	SecretKey   *string `json:"-" url:"secret-key"`
	Secret      *string `json:"-" url:"secret"`
	PurgeKeys   *bool   `json:"-" url:"purge-keys"`
	KeyType     *string `json:"-" url:"key-type"`
}

// SubuserAccess represents an access level for a subuser
type SubuserAccess string

// The possible values of SubuserAccess
//
// There are two sets of constants as the API parameters and the
// values returned by the API do not match.  The SubuserAccess* values
// must be used when setting access level, the SubuserAccessReply*
// values are the ones that may be returned. This is a design problem
// of the upstream API. We do not feel confident to do the mapping in
// the library.
const (
	SubuserAccessNone      SubuserAccess = ""
	SubuserAccessRead      SubuserAccess = "read"
	SubuserAccessWrite     SubuserAccess = "write"
	SubuserAccessReadWrite SubuserAccess = "readwrite"
	SubuserAccessFull      SubuserAccess = "full"

	SubuserAccessReplyNone      SubuserAccess = "<none>"
	SubuserAccessReplyRead      SubuserAccess = "read"
	SubuserAccessReplyWrite     SubuserAccess = "write"
	SubuserAccessReplyReadWrite SubuserAccess = "read-write"
	SubuserAccessReplyFull      SubuserAccess = "full-control"
)

// SwiftKeySpec represents the secret key associated to a subuser
type SwiftKeySpec struct {
	User      string `json:"user"`
	SecretKey string `json:"secret_key"`
}

// UserCapSpec represents a user capability which gives access to certain ressources
type UserCapSpec struct {
	Type string `json:"type"`
	Perm string `json:"perm"`
}

// UserKeySpec is the user credential configuration
type UserKeySpec struct {
	User      string `json:"user"`
	AccessKey string `json:"access_key" url:"access-key"`
	SecretKey string `json:"secret_key" url:"secret-key"`
}

// UserStat contains information about storage consumption by the ceph user
type UserStat struct {
	Size        *uint64 `json:"size"`
	SizeRounded *uint64 `json:"size_rounded"`
	NumObjects  *uint64 `json:"num_objects"`
}

// GetUser retrieves a given object store user
func (api *API) GetUser(ctx context.Context, user User) (User, error) {
	if user.ID == "" && len(user.Keys) == 0 {
		return User{}, errMissingUserID
	}
	if len(user.Keys) > 0 {
		for _, key := range user.Keys {
			if key.AccessKey == "" {
				return User{}, errMissingUserAccessKey
			}
		}
	}

	//  valid parameters not supported by go-ceph: sync
	body, err := api.call(ctx, http.MethodGet, "/user", valueToURLParams(user, []string{"uid", "access-key", "stats"}))

	if err != nil {
		return User{}, err
	}

	u := User{}
	err = json.Unmarshal(body, &u)
	if err != nil {
		return User{}, fmt.Errorf("%s. %s. %w", unmarshalError, string(body), err)
	}

	return u, nil
}

// GetUsers lists all object store users
func (api *API) GetUsers(ctx context.Context) (*[]string, error) {
	body, err := api.call(ctx, http.MethodGet, "/metadata/user", nil)
	if err != nil {
		return nil, err
	}
	var users *[]string
	err = json.Unmarshal(body, &users)
	if err != nil {
		return nil, fmt.Errorf("%s. %s. %w", unmarshalError, string(body), err)
	}

	return users, nil
}

// CreateUser creates a user in the object store
func (api *API) CreateUser(ctx context.Context, user User) (User, error) {
	if user.ID == "" {
		return User{}, errMissingUserID
	}
	if user.DisplayName == "" {
		return User{}, errMissingUserDisplayName
	}

	//  valid parameters not supported by go-ceph: system, exclusive, default-placement, placement-tags
	body, err := api.call(ctx, http.MethodPut, "/user", valueToURLParams(user, []string{"uid", "display-name", "email", "key-type", "access-key", "secret-key", "user-caps", "tenant", "generate-key", "max-buckets", "suspended", "op-mask"}))
	if err != nil {
		return User{}, err
	}

	// Unmarshal response into Go type
	u := User{}
	err = json.Unmarshal(body, &u)
	if err != nil {
		return User{}, fmt.Errorf("%s. %s. %w", unmarshalError, string(body), err)
	}

	return u, nil
}

// RemoveUser remove an user from the object store
func (api *API) RemoveUser(ctx context.Context, user User) error {
	if user.ID == "" {
		return errMissingUserID
	}

	_, err := api.call(ctx, http.MethodDelete, "/user", valueToURLParams(user, []string{"uid", "purge-data"}))
	if err != nil {
		return err
	}

	return nil
}

// ModifyUser - http://docs.ceph.com/docs/latest/radosgw/adminops/#modify-user
func (api *API) ModifyUser(ctx context.Context, user User) (User, error) {
	if user.ID == "" {
		return User{}, errMissingUserID
	}

	// valid parameters not supported by go-ceph: system, default-placement, placement-tags
	body, err := api.call(ctx, http.MethodPost, "/user", valueToURLParams(user, []string{"uid", "display-name", "email", "generate-key", "access-key", "secret-key", "key-type", "max-buckets", "suspended", "op-mask"}))
	if err != nil {
		return User{}, err
	}

	u := User{}
	err = json.Unmarshal(body, &u)
	if err != nil {
		return User{}, fmt.Errorf("%s. %s. %w", unmarshalError, string(body), err)
	}

	return u, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Vitess Authors.

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

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

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

package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/golang/protobuf/ptypes"
	"github.com/spf13/cobra"

	"vitess.io/vitess/go/vt/log"
	"vitess.io/vitess/go/vt/logutil"
	"vitess.io/vitess/go/vt/topo"
	"vitess.io/vitess/go/vt/topo/topoproto"

	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
	vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata"
)

var (
	findAllShardsInKeyspaceCmd = &cobra.Command{
		Use:     "FindAllShardsInKeyspace keyspace",
		Aliases: []string{"findallshardsinkeyspace"},
		Args:    cobra.ExactArgs(1),
		RunE:    commandFindAllShardsInKeyspace,
	}
	getCellInfoNamesCmd = &cobra.Command{
		Use:  "GetCellInfoNames",
		Args: cobra.NoArgs,
		RunE: commandGetCellInfoNames,
	}
	getCellInfoCmd = &cobra.Command{
		Use:  "GetCellInfo cell",
		Args: cobra.ExactArgs(1),
		RunE: commandGetCellInfo,
	}
	getCellsAliasesCmd = &cobra.Command{
		Use:  "GetCellsAliases",
		Args: cobra.NoArgs,
		RunE: commandGetCellsAliases,
	}
	getKeyspaceCmd = &cobra.Command{
		Use:     "GetKeyspace keyspace",
		Aliases: []string{"getkeyspace"},
		Args:    cobra.ExactArgs(1),
		RunE:    commandGetKeyspace,
	}
	getKeyspacesCmd = &cobra.Command{
		Use:     "GetKeyspaces",
		Aliases: []string{"getkeyspaces"},
		Args:    cobra.NoArgs,
		RunE:    commandGetKeyspaces,
	}
	getTabletCmd = &cobra.Command{
		Use:  "GetTablet alias",
		Args: cobra.ExactArgs(1),
		RunE: commandGetTablet,
	}
	getTabletsCmd = &cobra.Command{
		Use:  "GetTablets [--cell $c1, ...] [--keyspace $ks [--shard $shard]]",
		Args: cobra.NoArgs,
		RunE: commandGetTablets,
	}
	initShardPrimaryCmd = &cobra.Command{
		Use:  "InitShardPrimary",
		Args: cobra.ExactArgs(2),
		RunE: commandInitShardPrimary,
	}
)

func commandFindAllShardsInKeyspace(cmd *cobra.Command, args []string) error {
	ks := cmd.Flags().Arg(0)
	resp, err := client.FindAllShardsInKeyspace(commandCtx, &vtctldatapb.FindAllShardsInKeyspaceRequest{
		Keyspace: ks,
	})

	if err != nil {
		return err
	}

	data, err := MarshalJSON(resp)
	if err != nil {
		return err
	}

	fmt.Printf("%s\n", data)
	return nil
}

func commandGetCellInfoNames(cmd *cobra.Command, args []string) error {
	resp, err := client.GetCellInfoNames(commandCtx, &vtctldatapb.GetCellInfoNamesRequest{})
	if err != nil {
		return err
	}

	fmt.Printf("%s\n", strings.Join(resp.Names, "\n"))

	return nil
}

func commandGetCellInfo(cmd *cobra.Command, args []string) error {
	cell := cmd.Flags().Arg(0)
	resp, err := client.GetCellInfo(commandCtx, &vtctldatapb.GetCellInfoRequest{Cell: cell})

	if err != nil {
		return err
	}

	data, err := MarshalJSON(resp.CellInfo)
	if err != nil {
		return err
	}

	fmt.Printf("%s\n", data)

	return nil
}

func commandGetCellsAliases(cmd *cobra.Command, args []string) error {
	resp, err := client.GetCellsAliases(commandCtx, &vtctldatapb.GetCellsAliasesRequest{})
	if err != nil {
		return err
	}

	data, err := MarshalJSON(resp.Aliases)
	if err != nil {
		return err
	}

	fmt.Printf("%s\n", data)

	return nil
}

func commandGetKeyspace(cmd *cobra.Command, args []string) error {
	ks := cmd.Flags().Arg(0)
	resp, err := client.GetKeyspace(commandCtx, &vtctldatapb.GetKeyspaceRequest{
		Keyspace: ks,
	})

	if err != nil {
		return err
	}

	fmt.Printf("%+v\n", resp.Keyspace)

	return nil
}

func commandGetKeyspaces(cmd *cobra.Command, args []string) error {
	resp, err := client.GetKeyspaces(commandCtx, &vtctldatapb.GetKeyspacesRequest{})
	if err != nil {
		return err
	}

	data, err := MarshalJSON(resp.Keyspaces)
	if err != nil {
		return err
	}

	fmt.Printf("%s\n", data)

	return nil
}

func commandGetTablet(cmd *cobra.Command, args []string) error {
	aliasStr := cmd.Flags().Arg(0)
	alias, err := topoproto.ParseTabletAlias(aliasStr)
	if err != nil {
		return err
	}

	resp, err := client.GetTablet(commandCtx, &vtctldatapb.GetTabletRequest{TabletAlias: alias})
	if err != nil {
		return err
	}

	data, err := MarshalJSON(resp.Tablet)
	if err != nil {
		return err
	}

	fmt.Printf("%s\n", data)

	return nil
}

var getTabletsArgs = struct {
	Cells    []string
	Keyspace string
	Shard    string

	Format string
}{}

func commandGetTablets(cmd *cobra.Command, args []string) error {
	format := strings.ToLower(getTabletsArgs.Format)

	switch format {
	case "awk", "json":
	default:
		return fmt.Errorf("invalid output format, got %s", getTabletsArgs.Format)
	}

	resp, err := client.GetTablets(commandCtx, &vtctldatapb.GetTabletsRequest{
		Cells:    getTabletsArgs.Cells,
		Keyspace: getTabletsArgs.Keyspace,
		Shard:    getTabletsArgs.Shard,
	})
	if err != nil {
		return err
	}

	switch format {
	case "awk":
		lineFn := func(t *topodatapb.Tablet) string {
			ti := topo.TabletInfo{
				Tablet: t,
			}

			keyspace := t.Keyspace
			if keyspace == "" {
				keyspace = "<null>"
			}

			shard := t.Shard
			if shard == "" {
				shard = "<null>"
			}

			mtst := "<null>"
			// special case for old primary that hasn't been updated in the topo
			// yet.
			if t.MasterTermStartTime != nil && t.MasterTermStartTime.Seconds > 0 {
				mtst = logutil.ProtoToTime(t.MasterTermStartTime).Format(time.RFC3339)
			}

			return fmt.Sprintf("%v %v %v %v %v %v %v %v", topoproto.TabletAliasString(t.Alias), keyspace, shard, topoproto.TabletTypeLString(t.Type), ti.Addr(), ti.MysqlAddr(), fmtMapAwkable(t.Tags), mtst)
		}

		for _, t := range resp.Tablets {
			fmt.Println(lineFn(t))
		}
	case "json":
		data, err := MarshalJSON(resp.Tablets)
		if err != nil {
			return err
		}

		fmt.Printf("%s\n", data)
	}

	return nil
}

var initShardPrimaryArgs = struct {
	WaitReplicasTimeout time.Duration
	Force               bool
}{}

func commandInitShardPrimary(cmd *cobra.Command, args []string) error {
	keyspace, shard, err := topoproto.ParseKeyspaceShard(cmd.Flags().Arg(0))
	if err != nil {
		return err
	}

	tabletAlias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(1))
	if err != nil {
		return err
	}

	resp, err := client.InitShardPrimary(commandCtx, &vtctldatapb.InitShardPrimaryRequest{
		Keyspace:                keyspace,
		Shard:                   shard,
		PrimaryElectTabletAlias: tabletAlias,
		WaitReplicasTimeout:     ptypes.DurationProto(initShardPrimaryArgs.WaitReplicasTimeout),
		Force:                   initShardPrimaryArgs.Force,
	})

	for _, event := range resp.Events {
		log.Infof("%v", event)
	}

	return err
}

func init() {
	rootCmd.AddCommand(findAllShardsInKeyspaceCmd)

	rootCmd.AddCommand(getCellInfoNamesCmd)
	rootCmd.AddCommand(getCellInfoCmd)
	rootCmd.AddCommand(getCellsAliasesCmd)

	rootCmd.AddCommand(getKeyspaceCmd)
	rootCmd.AddCommand(getKeyspacesCmd)

	rootCmd.AddCommand(getTabletCmd)
	getTabletsCmd.Flags().StringSliceVarP(&getTabletsArgs.Cells, "cell", "c", nil, "TODO")
	getTabletsCmd.Flags().StringVarP(&getTabletsArgs.Keyspace, "keyspace", "k", "", "TODO")
	getTabletsCmd.Flags().StringVarP(&getTabletsArgs.Shard, "shard", "s", "", "TODO")
	getTabletsCmd.Flags().StringVar(&getTabletsArgs.Format, "format", "awk", "Output format to use; valid choices are (json, awk)")
	rootCmd.AddCommand(getTabletsCmd)

	initShardPrimaryCmd.Flags().DurationVar(&initShardPrimaryArgs.WaitReplicasTimeout, "wait-replicas-timeout", 30*time.Second, "time to wait for replicas to catch up in reparenting")
	initShardPrimaryCmd.Flags().BoolVar(&initShardPrimaryArgs.Force, "force", false, "will force the reparent even if the provided tablet is not a master or the shard master")
	rootCmd.AddCommand(initShardPrimaryCmd)
}
<commit_msg>Add extra flag validation<commit_after>/*
Copyright 2020 The Vitess Authors.

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

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

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

package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/golang/protobuf/ptypes"
	"github.com/spf13/cobra"

	"vitess.io/vitess/go/vt/log"
	"vitess.io/vitess/go/vt/logutil"
	"vitess.io/vitess/go/vt/topo"
	"vitess.io/vitess/go/vt/topo/topoproto"

	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
	vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata"
)

var (
	findAllShardsInKeyspaceCmd = &cobra.Command{
		Use:     "FindAllShardsInKeyspace keyspace",
		Aliases: []string{"findallshardsinkeyspace"},
		Args:    cobra.ExactArgs(1),
		RunE:    commandFindAllShardsInKeyspace,
	}
	getCellInfoNamesCmd = &cobra.Command{
		Use:  "GetCellInfoNames",
		Args: cobra.NoArgs,
		RunE: commandGetCellInfoNames,
	}
	getCellInfoCmd = &cobra.Command{
		Use:  "GetCellInfo cell",
		Args: cobra.ExactArgs(1),
		RunE: commandGetCellInfo,
	}
	getCellsAliasesCmd = &cobra.Command{
		Use:  "GetCellsAliases",
		Args: cobra.NoArgs,
		RunE: commandGetCellsAliases,
	}
	getKeyspaceCmd = &cobra.Command{
		Use:     "GetKeyspace keyspace",
		Aliases: []string{"getkeyspace"},
		Args:    cobra.ExactArgs(1),
		RunE:    commandGetKeyspace,
	}
	getKeyspacesCmd = &cobra.Command{
		Use:     "GetKeyspaces",
		Aliases: []string{"getkeyspaces"},
		Args:    cobra.NoArgs,
		RunE:    commandGetKeyspaces,
	}
	getTabletCmd = &cobra.Command{
		Use:  "GetTablet alias",
		Args: cobra.ExactArgs(1),
		RunE: commandGetTablet,
	}
	getTabletsCmd = &cobra.Command{
		Use:  "GetTablets [--cell $c1, ...] [--keyspace $ks [--shard $shard]]",
		Args: cobra.NoArgs,
		RunE: commandGetTablets,
	}
	initShardPrimaryCmd = &cobra.Command{
		Use:  "InitShardPrimary",
		Args: cobra.ExactArgs(2),
		RunE: commandInitShardPrimary,
	}
)

func commandFindAllShardsInKeyspace(cmd *cobra.Command, args []string) error {
	ks := cmd.Flags().Arg(0)
	resp, err := client.FindAllShardsInKeyspace(commandCtx, &vtctldatapb.FindAllShardsInKeyspaceRequest{
		Keyspace: ks,
	})

	if err != nil {
		return err
	}

	data, err := MarshalJSON(resp)
	if err != nil {
		return err
	}

	fmt.Printf("%s\n", data)
	return nil
}

func commandGetCellInfoNames(cmd *cobra.Command, args []string) error {
	resp, err := client.GetCellInfoNames(commandCtx, &vtctldatapb.GetCellInfoNamesRequest{})
	if err != nil {
		return err
	}

	fmt.Printf("%s\n", strings.Join(resp.Names, "\n"))

	return nil
}

func commandGetCellInfo(cmd *cobra.Command, args []string) error {
	cell := cmd.Flags().Arg(0)
	resp, err := client.GetCellInfo(commandCtx, &vtctldatapb.GetCellInfoRequest{Cell: cell})

	if err != nil {
		return err
	}

	data, err := MarshalJSON(resp.CellInfo)
	if err != nil {
		return err
	}

	fmt.Printf("%s\n", data)

	return nil
}

func commandGetCellsAliases(cmd *cobra.Command, args []string) error {
	resp, err := client.GetCellsAliases(commandCtx, &vtctldatapb.GetCellsAliasesRequest{})
	if err != nil {
		return err
	}

	data, err := MarshalJSON(resp.Aliases)
	if err != nil {
		return err
	}

	fmt.Printf("%s\n", data)

	return nil
}

func commandGetKeyspace(cmd *cobra.Command, args []string) error {
	ks := cmd.Flags().Arg(0)
	resp, err := client.GetKeyspace(commandCtx, &vtctldatapb.GetKeyspaceRequest{
		Keyspace: ks,
	})

	if err != nil {
		return err
	}

	fmt.Printf("%+v\n", resp.Keyspace)

	return nil
}

func commandGetKeyspaces(cmd *cobra.Command, args []string) error {
	resp, err := client.GetKeyspaces(commandCtx, &vtctldatapb.GetKeyspacesRequest{})
	if err != nil {
		return err
	}

	data, err := MarshalJSON(resp.Keyspaces)
	if err != nil {
		return err
	}

	fmt.Printf("%s\n", data)

	return nil
}

func commandGetTablet(cmd *cobra.Command, args []string) error {
	aliasStr := cmd.Flags().Arg(0)
	alias, err := topoproto.ParseTabletAlias(aliasStr)
	if err != nil {
		return err
	}

	resp, err := client.GetTablet(commandCtx, &vtctldatapb.GetTabletRequest{TabletAlias: alias})
	if err != nil {
		return err
	}

	data, err := MarshalJSON(resp.Tablet)
	if err != nil {
		return err
	}

	fmt.Printf("%s\n", data)

	return nil
}

var getTabletsArgs = struct {
	Cells    []string
	Keyspace string
	Shard    string

	Format string
}{}

func commandGetTablets(cmd *cobra.Command, args []string) error {
	format := strings.ToLower(getTabletsArgs.Format)

	switch format {
	case "awk", "json":
	default:
		return fmt.Errorf("invalid output format, got %s", getTabletsArgs.Format)
	}

	if getTabletsArgs.Keyspace == "" && getTabletsArgs.Shard != "" {
		return fmt.Errorf("--shard (= %s) cannot be passed without also passing --keyspace", getTabletsArgs.Shard)
	}

	resp, err := client.GetTablets(commandCtx, &vtctldatapb.GetTabletsRequest{
		Cells:    getTabletsArgs.Cells,
		Keyspace: getTabletsArgs.Keyspace,
		Shard:    getTabletsArgs.Shard,
	})
	if err != nil {
		return err
	}

	switch format {
	case "awk":
		lineFn := func(t *topodatapb.Tablet) string {
			ti := topo.TabletInfo{
				Tablet: t,
			}

			keyspace := t.Keyspace
			if keyspace == "" {
				keyspace = "<null>"
			}

			shard := t.Shard
			if shard == "" {
				shard = "<null>"
			}

			mtst := "<null>"
			// special case for old primary that hasn't been updated in the topo
			// yet.
			if t.MasterTermStartTime != nil && t.MasterTermStartTime.Seconds > 0 {
				mtst = logutil.ProtoToTime(t.MasterTermStartTime).Format(time.RFC3339)
			}

			return fmt.Sprintf("%v %v %v %v %v %v %v %v", topoproto.TabletAliasString(t.Alias), keyspace, shard, topoproto.TabletTypeLString(t.Type), ti.Addr(), ti.MysqlAddr(), fmtMapAwkable(t.Tags), mtst)
		}

		for _, t := range resp.Tablets {
			fmt.Println(lineFn(t))
		}
	case "json":
		data, err := MarshalJSON(resp.Tablets)
		if err != nil {
			return err
		}

		fmt.Printf("%s\n", data)
	}

	return nil
}

var initShardPrimaryArgs = struct {
	WaitReplicasTimeout time.Duration
	Force               bool
}{}

func commandInitShardPrimary(cmd *cobra.Command, args []string) error {
	keyspace, shard, err := topoproto.ParseKeyspaceShard(cmd.Flags().Arg(0))
	if err != nil {
		return err
	}

	tabletAlias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(1))
	if err != nil {
		return err
	}

	resp, err := client.InitShardPrimary(commandCtx, &vtctldatapb.InitShardPrimaryRequest{
		Keyspace:                keyspace,
		Shard:                   shard,
		PrimaryElectTabletAlias: tabletAlias,
		WaitReplicasTimeout:     ptypes.DurationProto(initShardPrimaryArgs.WaitReplicasTimeout),
		Force:                   initShardPrimaryArgs.Force,
	})

	for _, event := range resp.Events {
		log.Infof("%v", event)
	}

	return err
}

func init() {
	rootCmd.AddCommand(findAllShardsInKeyspaceCmd)

	rootCmd.AddCommand(getCellInfoNamesCmd)
	rootCmd.AddCommand(getCellInfoCmd)
	rootCmd.AddCommand(getCellsAliasesCmd)

	rootCmd.AddCommand(getKeyspaceCmd)
	rootCmd.AddCommand(getKeyspacesCmd)

	rootCmd.AddCommand(getTabletCmd)
	getTabletsCmd.Flags().StringSliceVarP(&getTabletsArgs.Cells, "cell", "c", nil, "TODO")
	getTabletsCmd.Flags().StringVarP(&getTabletsArgs.Keyspace, "keyspace", "k", "", "TODO")
	getTabletsCmd.Flags().StringVarP(&getTabletsArgs.Shard, "shard", "s", "", "TODO")
	getTabletsCmd.Flags().StringVar(&getTabletsArgs.Format, "format", "awk", "Output format to use; valid choices are (json, awk)")
	rootCmd.AddCommand(getTabletsCmd)

	initShardPrimaryCmd.Flags().DurationVar(&initShardPrimaryArgs.WaitReplicasTimeout, "wait-replicas-timeout", 30*time.Second, "time to wait for replicas to catch up in reparenting")
	initShardPrimaryCmd.Flags().BoolVar(&initShardPrimaryArgs.Force, "force", false, "will force the reparent even if the provided tablet is not a master or the shard master")
	rootCmd.AddCommand(initShardPrimaryCmd)
}
<|endoftext|>


<commit_before>/*
Copyright 2017 Google Inc.

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

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

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

/*
Package consultopo implements topo.Server with consul as the backend.
*/
package consultopo

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"sync"

	"github.com/hashicorp/consul/api"

	"vitess.io/vitess/go/vt/log"
	"vitess.io/vitess/go/vt/topo"
)

var (
	consulAuthClientStaticFile = flag.String("consul_auth_client_static_file", "", "JSON File to read the topos/tokens from.")
)

// AuthConsulClientCred ...
type AuthConsulClientCred struct {
	Token string
}

// Factory is the consul topo.Factory implementation.
type Factory struct{}

// HasGlobalReadOnlyCell is part of the topo.Factory interface.
func (f Factory) HasGlobalReadOnlyCell(serverAddr, root string) bool {
	return false
}

// Create is part of the topo.Factory interface.
func (f Factory) Create(cell, serverAddr, root string) (topo.Conn, error) {
	return NewServer(cell, serverAddr, root)
}

func getClientConfig() (creds map[string]*AuthConsulClientCred, err error) {
	creds = make(map[string]*AuthConsulClientCred)
	// Check parameters.
	if *consulAuthClientStaticFile == "" {
		// Not configured, nothing to do.
		log.Infof("Not configuring consul auth, as consul_auth_client_static_file was not provided")
		return creds, nil
	}

	// Create and register auth server.
	data, err := ioutil.ReadFile(*consulAuthClientStaticFile)
	if err != nil {
		err = fmt.Errorf("Failed to read consul_auth_client_static_file file: %v", err)
		return creds, err
	}

	if err := json.Unmarshal(data, &creds); err != nil {
		err = fmt.Errorf(fmt.Sprintf("Error parsing auth server config: %v", err))
	}
	return creds, nil
}

// Server is the implementation of topo.Server for consul.
type Server struct {
	// client is the consul api client.
	client *api.Client
	kv     *api.KV

	// root is the root path for this client.
	root string

	// mu protects the following fields.
	mu sync.Mutex
	// locks is a map of *lockInstance structures.
	// The key is the filepath of the Lock file.
	locks map[string]*lockInstance
}

// lockInstance keeps track of one lock held by this client.
type lockInstance struct {
	// lock has the api.Lock structure.
	lock *api.Lock

	// done is closed when the lock is release by this process.
	done chan struct{}
}

// NewServer returns a new consultopo.Server.
func NewServer(cell, serverAddr, root string) (*Server, error) {
	creds, err := getClientConfig()
	if err != nil {
		return nil, err
	}
	cfg := api.DefaultConfig()
	cfg.Address = serverAddr
	if creds[cell] != nil {
		cfg.Token = creds[cell].Token
	}
	client, err := api.NewClient(cfg)
	if err != nil {
		return nil, err
	}

	return &Server{
		client: client,
		kv:     client.KV(),
		root:   root,
		locks:  make(map[string]*lockInstance),
	}, nil
}

// Close implements topo.Server.Close.
// It will nil out the global and cells fields, so any attempt to
// re-use this server will panic.
func (s *Server) Close() {
	s.client = nil
	s.kv = nil
	s.mu.Lock()
	defer s.mu.Unlock()
	s.locks = nil
}

func init() {
	topo.RegisterFactory("consul", Factory{})
}
<commit_msg>Updating comments for clarity<commit_after>/*
Copyright 2017 Google Inc.

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

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

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

/*
Package consultopo implements topo.Server with consul as the backend.
*/
package consultopo

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"sync"

	"github.com/hashicorp/consul/api"

	"vitess.io/vitess/go/vt/log"
	"vitess.io/vitess/go/vt/topo"
)

var (
	consulAuthClientStaticFile = flag.String("consul_auth_client_static_file", "", "JSON File to read the topos/tokens from.")
)

// AuthConsulClientCred ...
type AuthConsulClientCred struct {
	Token string
}

// Factory is the consul topo.Factory implementation.
type Factory struct{}

// HasGlobalReadOnlyCell is part of the topo.Factory interface.
func (f Factory) HasGlobalReadOnlyCell(serverAddr, root string) bool {
	return false
}

// Create is part of the topo.Factory interface.
func (f Factory) Create(cell, serverAddr, root string) (topo.Conn, error) {
	return NewServer(cell, serverAddr, root)
}

func getClientConfig() (creds map[string]*AuthConsulClientCred, err error) {
	creds = make(map[string]*AuthConsulClientCred)

	if *consulAuthClientStaticFile == "" {
		// Not configured, nothing to do.
		log.Infof("Consul client auth is not set up. consul_auth_client_static_file was not provided")
		return creds, nil
	}

	data, err := ioutil.ReadFile(*consulAuthClientStaticFile)
	if err != nil {
		err = fmt.Errorf("Failed to read consul_auth_client_static_file file: %v", err)
		return creds, err
	}

	if err := json.Unmarshal(data, &creds); err != nil {
		err = fmt.Errorf(fmt.Sprintf("Error parsing auth server config: %v", err))
	}
	return creds, nil
}

// Server is the implementation of topo.Server for consul.
type Server struct {
	// client is the consul api client.
	client *api.Client
	kv     *api.KV

	// root is the root path for this client.
	root string

	// mu protects the following fields.
	mu sync.Mutex
	// locks is a map of *lockInstance structures.
	// The key is the filepath of the Lock file.
	locks map[string]*lockInstance
}

// lockInstance keeps track of one lock held by this client.
type lockInstance struct {
	// lock has the api.Lock structure.
	lock *api.Lock

	// done is closed when the lock is release by this process.
	done chan struct{}
}

// NewServer returns a new consultopo.Server.
func NewServer(cell, serverAddr, root string) (*Server, error) {
	creds, err := getClientConfig()
	if err != nil {
		return nil, err
	}
	cfg := api.DefaultConfig()
	cfg.Address = serverAddr
	if creds[cell] != nil {
		cfg.Token = creds[cell].Token
	}
	client, err := api.NewClient(cfg)
	if err != nil {
		return nil, err
	}

	return &Server{
		client: client,
		kv:     client.KV(),
		root:   root,
		locks:  make(map[string]*lockInstance),
	}, nil
}

// Close implements topo.Server.Close.
// It will nil out the global and cells fields, so any attempt to
// re-use this server will panic.
func (s *Server) Close() {
	s.client = nil
	s.kv = nil
	s.mu.Lock()
	defer s.mu.Unlock()
	s.locks = nil
}

func init() {
	topo.RegisterFactory("consul", Factory{})
}
<|endoftext|>


<commit_before>// +build linux

package main

import (
	"os"

	"github.com/opencontainers/runc/libcontainer/system"
	"github.com/urfave/cli"
)

func shouldUseRootlessCgroupManager(context *cli.Context) (bool, error) {
	if context != nil {
		b, err := parseBoolOrAuto(context.GlobalString("rootless"))
		if err != nil {
			return false, err
		}
		// nil b stands for "auto detect"
		if b != nil {
			return *b, nil
		}
	}
	if os.Geteuid() != 0 {
		return true, nil
	}
	if !system.RunningInUserNS() {
		// euid == 0 , in the initial ns (i.e. the real root)
		return false, nil
	}
	// euid = 0, in a userns.
	// As we are unaware of cgroups path, we can't determine whether we have the full
	// access to the cgroups path.
	// Either way, we can safely decide to use the rootless cgroups manager.
	return true, nil
}

func shouldHonorXDGRuntimeDir() bool {
	if os.Getenv("XDG_RUNTIME_DIR") == "" {
		return false
	}
	if os.Geteuid() != 0 {
		return true
	}
	if !system.RunningInUserNS() {
		// euid == 0 , in the initial ns (i.e. the real root)
		// in this case, we should use /run/runc and ignore
		// $XDG_RUNTIME_DIR (e.g. /run/user/0) for backward
		// compatibility.
		return false
	}
	// euid = 0, in a userns.
	u, ok := os.LookupEnv("USER")
	return !ok || u != "root"
}
<commit_msg>systemd: fix rootful-in-userns regression<commit_after>// +build linux

package main

import (
	"os"

	"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
	"github.com/opencontainers/runc/libcontainer/system"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

func shouldUseRootlessCgroupManager(context *cli.Context) (bool, error) {
	if context != nil {
		b, err := parseBoolOrAuto(context.GlobalString("rootless"))
		if err != nil {
			return false, err
		}
		// nil b stands for "auto detect"
		if b != nil {
			return *b, nil
		}
	}
	if os.Geteuid() != 0 {
		return true, nil
	}
	if !system.RunningInUserNS() {
		// euid == 0 , in the initial ns (i.e. the real root)
		return false, nil
	}
	// euid = 0, in a userns.
	//
	// [systemd driver]
	// We can call DetectUID() to parse the OwnerUID value from `busctl --user --no-pager status` result.
	// The value corresponds to sd_bus_creds_get_owner_uid(3).
	// If the value is 0, we have rootful systemd inside userns, so we do not need the rootless cgroup manager.
	//
	// On error, we assume we are root. An error may happen during shelling out to `busctl` CLI,
	// mostly when $DBUS_SESSION_BUS_ADDRESS is unset.
	if context.GlobalBool("systemd-cgroup") {
		ownerUID, err := systemd.DetectUID()
		if err != nil {
			logrus.WithError(err).Debug("failed to get the OwnerUID value, assuming the value to be 0")
			ownerUID = 0
		}
		return ownerUID != 0, nil
	}
	// [cgroupfs driver]
	// As we are unaware of cgroups path, we can't determine whether we have the full
	// access to the cgroups path.
	// Either way, we can safely decide to use the rootless cgroups manager.
	return true, nil
}

func shouldHonorXDGRuntimeDir() bool {
	if os.Getenv("XDG_RUNTIME_DIR") == "" {
		return false
	}
	if os.Geteuid() != 0 {
		return true
	}
	if !system.RunningInUserNS() {
		// euid == 0 , in the initial ns (i.e. the real root)
		// in this case, we should use /run/runc and ignore
		// $XDG_RUNTIME_DIR (e.g. /run/user/0) for backward
		// compatibility.
		return false
	}
	// euid = 0, in a userns.
	u, ok := os.LookupEnv("USER")
	return !ok || u != "root"
}
<|endoftext|>


<commit_before>package bitbucket

import (
	"errors"

	"github.com/mitchellh/mapstructure"
)

type Workspace struct {
	c *Client

	Repositories *Repositories
	Permissions  *Permission

	UUID       string
	Type       string
	Slug       string
	Is_Private bool
	Name       string
	workspace
}

type WorkspaceList struct {
	Page       int
	Pagelen    int
	MaxDepth   int
	Size       int
	Next       string
	Workspaces []Workspace
}

type Permission struct {
	c *Client

	Type string
}

type ProjectsRes struct {
	Page     int32
	Pagelen  int32
	MaxDepth int32
	Size     int32
	Items    []Project
}

func (t *Permission) GetUserPermissions(organization, member string) (*Permission, error) {
	urlStr := t.c.requestUrl("/workspaces/%s/permissions?q=user.nickname=\"%s\"", organization, member)
	response, err := t.c.executePaginated("GET", urlStr, "")
	if err != nil {
		return nil, err
	}

	return decodePermission(response), err
}

func (t *Permission) GetUserPermissionsByUuid(organization, member string) (*Permission, error) {
	urlStr := t.c.requestUrl("/workspaces/%s/permissions?q=user.uuid=\"%s\"", organization, member)
	response, err := t.c.executePaginated("GET", urlStr, "")
	if err != nil {
		return nil, err
	}

	return decodePermission(response), err
}

func (t *Workspace) List() (*WorkspaceList, error) {
	urlStr := t.c.requestUrl("/workspaces")
	response, err := t.c.executePaginated("GET", urlStr, "")
	if err != nil {
		return nil, err
	}

	return decodeWorkspaceList(response)
}

func (t *Workspace) Get(workspace string) (*Workspace, error) {
	urlStr := t.c.requestUrl("/workspaces/%s", workspace)
	response, err := t.c.execute("GET", urlStr, "")
	if err != nil {
		return nil, err
	}

	return decodeWorkspace(response)
}

func (w *Workspace) Members(teamname string) (interface{}, error) {
	urlStr := w.c.requestUrl("/workspaces/%s/members", teamname)
	return w.c.execute("GET", urlStr, "")
}

func (w *Workspace) Projects(teamname string) (*ProjectsRes, error) {
	urlStr := w.c.requestUrl("/workspaces/%s/projects/", teamname)
	response, err := w.c.executePaginated("GET", urlStr, "")
	if err != nil {
		return nil, err
	}

	return decodeProjects(response)
}

func decodePermission(permission interface{}) *Permission {
	permissionResponseMap := permission.(map[string]interface{})
	if permissionResponseMap["size"].(float64) == 0 {
		return nil
	}

	permissionValues := permissionResponseMap["values"].([]interface{})
	if len(permissionValues) == 0 {
		return nil
	}

	permissionValue := permissionValues[0].(map[string]interface{})
	return &Permission{
		Type: permissionValue["permission"].(string),
	}
}

func decodeWorkspace(workspace interface{}) (*Workspace, error) {
	var workspaceEntry Workspace
	workspaceResponseMap := workspace.(map[string]interface{})

	if workspaceResponseMap["type"] != nil && workspaceResponseMap["type"] == "error" {
		return nil, DecodeError(workspaceResponseMap)
	}

	err := mapstructure.Decode(workspace, &workspaceEntry)
	return &workspaceEntry, err
}

func decodeWorkspaceList(workspaceResponse interface{}) (*WorkspaceList, error) {
	workspaceResponseMap := workspaceResponse.(map[string]interface{})
	workspaceMapList := workspaceResponseMap["values"].([]interface{})

	var workspaces []Workspace
	for _, workspaceMap := range workspaceMapList {
		workspaceEntry, err := decodeWorkspace(workspaceMap)
		if err != nil {
			return nil, err
		}
		workspaces = append(workspaces, *workspaceEntry)
	}

	page, ok := workspaceResponseMap["page"].(float64)
	if !ok {
		page = 0
	}

	pagelen, ok := workspaceResponseMap["pagelen"].(float64)
	if !ok {
		pagelen = 0
	}
	max_depth, ok := workspaceResponseMap["max_depth"].(float64)
	if !ok {
		max_depth = 0
	}
	size, ok := workspaceResponseMap["size"].(float64)
	if !ok {
		size = 0
	}

	next, ok := workspaceResponseMap["next"].(string)
	if !ok {
		next = ""
	}

	workspacesList := WorkspaceList{
		Page:       int(page),
		Pagelen:    int(pagelen),
		MaxDepth:   int(max_depth),
		Size:       int(size),
		Next:       next,
		Workspaces: workspaces,
	}

	return &workspacesList, nil
}

func decodeProjects(projectResponse interface{}) (*ProjectsRes, error) {
	projectsResponseMap, ok := projectResponse.(map[string]interface{})
	if !ok {
		return nil, errors.New("Not a valid format")
	}

	var projects []Project
	projectArray := projectsResponseMap["values"].([]interface{})
	for _, projectEntry := range projectArray {
		var project Project
		if err := mapstructure.Decode(projectEntry, &project); err == nil {
			projects = append(projects, project)
		}
	}

	page, ok := projectsResponseMap["page"].(float64)
	if !ok {
		page = 0
	}

	pagelen, ok := projectsResponseMap["pagelen"].(float64)
	if !ok {
		pagelen = 0
	}
	max_depth, ok := projectsResponseMap["max_width"].(float64)
	if !ok {
		max_depth = 0
	}
	size, ok := projectsResponseMap["size"].(float64)
	if !ok {
		size = 0
	}

	res := ProjectsRes{
		Page:     int32(page),
		Pagelen:  int32(pagelen),
		MaxDepth: int32(max_depth),
		Size:     int32(size),
		Items:    projects,
	}
	return &res, nil
}
<commit_msg>Update Get Workspace Members to return response as a struct (#196)<commit_after>package bitbucket

import (
	"errors"

	"github.com/mitchellh/mapstructure"
)

type Workspace struct {
	c *Client

	Repositories *Repositories
	Permissions  *Permission

	UUID       string
	Type       string
	Slug       string
	Is_Private bool
	Name       string
	workspace
}

type WorkspaceList struct {
	Page       int
	Pagelen    int
	MaxDepth   int
	Size       int
	Next       string
	Workspaces []Workspace
}

type Permission struct {
	c *Client

	Type string
}

type ProjectsRes struct {
	Page     int32
	Pagelen  int32
	MaxDepth int32
	Size     int32
	Items    []Project
}

type WorkspaceMembers struct {
	Page    int
	Pagelen int
	Size    int
	Members []User
}

func (t *Permission) GetUserPermissions(organization, member string) (*Permission, error) {
	urlStr := t.c.requestUrl("/workspaces/%s/permissions?q=user.nickname=\"%s\"", organization, member)
	response, err := t.c.executePaginated("GET", urlStr, "")
	if err != nil {
		return nil, err
	}

	return decodePermission(response), err
}

func (t *Permission) GetUserPermissionsByUuid(organization, member string) (*Permission, error) {
	urlStr := t.c.requestUrl("/workspaces/%s/permissions?q=user.uuid=\"%s\"", organization, member)
	response, err := t.c.executePaginated("GET", urlStr, "")
	if err != nil {
		return nil, err
	}

	return decodePermission(response), err
}

func (t *Workspace) List() (*WorkspaceList, error) {
	urlStr := t.c.requestUrl("/workspaces")
	response, err := t.c.executePaginated("GET", urlStr, "")
	if err != nil {
		return nil, err
	}

	return decodeWorkspaceList(response)
}

func (t *Workspace) Get(workspace string) (*Workspace, error) {
	urlStr := t.c.requestUrl("/workspaces/%s", workspace)
	response, err := t.c.execute("GET", urlStr, "")
	if err != nil {
		return nil, err
	}

	return decodeWorkspace(response)
}

func (w *Workspace) Members(teamname string) (*WorkspaceMembers, error) {
	urlStr := w.c.requestUrl("/workspaces/%s/members", teamname)
	response, err := w.c.execute("GET", urlStr, "")
	if err != nil {
		return nil, err
	}

	return decodeMembers(response)
}

func (w *Workspace) Projects(teamname string) (*ProjectsRes, error) {
	urlStr := w.c.requestUrl("/workspaces/%s/projects/", teamname)
	response, err := w.c.executePaginated("GET", urlStr, "")
	if err != nil {
		return nil, err
	}

	return decodeProjects(response)
}

func decodePermission(permission interface{}) *Permission {
	permissionResponseMap := permission.(map[string]interface{})
	if permissionResponseMap["size"].(float64) == 0 {
		return nil
	}

	permissionValues := permissionResponseMap["values"].([]interface{})
	if len(permissionValues) == 0 {
		return nil
	}

	permissionValue := permissionValues[0].(map[string]interface{})
	return &Permission{
		Type: permissionValue["permission"].(string),
	}
}

func decodeWorkspace(workspace interface{}) (*Workspace, error) {
	var workspaceEntry Workspace
	workspaceResponseMap := workspace.(map[string]interface{})

	if workspaceResponseMap["type"] != nil && workspaceResponseMap["type"] == "error" {
		return nil, DecodeError(workspaceResponseMap)
	}

	err := mapstructure.Decode(workspace, &workspaceEntry)
	return &workspaceEntry, err
}

func decodeWorkspaceList(workspaceResponse interface{}) (*WorkspaceList, error) {
	workspaceResponseMap := workspaceResponse.(map[string]interface{})
	workspaceMapList := workspaceResponseMap["values"].([]interface{})

	var workspaces []Workspace
	for _, workspaceMap := range workspaceMapList {
		workspaceEntry, err := decodeWorkspace(workspaceMap)
		if err != nil {
			return nil, err
		}
		workspaces = append(workspaces, *workspaceEntry)
	}

	page, ok := workspaceResponseMap["page"].(float64)
	if !ok {
		page = 0
	}

	pagelen, ok := workspaceResponseMap["pagelen"].(float64)
	if !ok {
		pagelen = 0
	}
	max_depth, ok := workspaceResponseMap["max_depth"].(float64)
	if !ok {
		max_depth = 0
	}
	size, ok := workspaceResponseMap["size"].(float64)
	if !ok {
		size = 0
	}

	next, ok := workspaceResponseMap["next"].(string)
	if !ok {
		next = ""
	}

	workspacesList := WorkspaceList{
		Page:       int(page),
		Pagelen:    int(pagelen),
		MaxDepth:   int(max_depth),
		Size:       int(size),
		Next:       next,
		Workspaces: workspaces,
	}

	return &workspacesList, nil
}

func decodeProjects(projectResponse interface{}) (*ProjectsRes, error) {
	projectsResponseMap, ok := projectResponse.(map[string]interface{})
	if !ok {
		return nil, errors.New("Not a valid format")
	}

	var projects []Project
	projectArray := projectsResponseMap["values"].([]interface{})
	for _, projectEntry := range projectArray {
		var project Project
		if err := mapstructure.Decode(projectEntry, &project); err == nil {
			projects = append(projects, project)
		}
	}

	page, ok := projectsResponseMap["page"].(float64)
	if !ok {
		page = 0
	}

	pagelen, ok := projectsResponseMap["pagelen"].(float64)
	if !ok {
		pagelen = 0
	}
	max_depth, ok := projectsResponseMap["max_width"].(float64)
	if !ok {
		max_depth = 0
	}
	size, ok := projectsResponseMap["size"].(float64)
	if !ok {
		size = 0
	}

	res := ProjectsRes{
		Page:     int32(page),
		Pagelen:  int32(pagelen),
		MaxDepth: int32(max_depth),
		Size:     int32(size),
		Items:    projects,
	}
	return &res, nil
}

func decodeMembers(membersResponse interface{}) (*WorkspaceMembers, error) {
	responseMap, ok := membersResponse.(map[string]interface{})
	if !ok {
		return nil, errors.New("not a valid format")
	}

	var members []User
	userArray := responseMap["values"].([]interface{})
	for _, userEntry := range userArray {
		userEntryMap := userEntry.(map[string]interface{})

		member, err := decodeUser(userEntryMap["user"])
		if err != nil {
			return nil, err
		}
		members = append(members, *member)
	}

	page, ok := responseMap["page"].(int)
	if !ok {
		page = 0
	}
	pagelen, ok := responseMap["pagelen"].(int)
	if !ok {
		pagelen = 0
	}
	size, ok := responseMap["size"].(int)
	if !ok {
		size = 0
	}

	workspaceMembers := WorkspaceMembers{
		Page:    page,
		Pagelen: pagelen,
		Size:    size,
		Members: members,
	}
	return &workspaceMembers, nil
}
<|endoftext|>