<commit_before>package smux

import (
	"io"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/errors"
)

const (
	defaultAcceptBacklog = 1024
	defaultCloseWait     = 1024
)

const (
	errBrokenPipe      = "broken pipe"
	errInvalidProtocol = "invalid protocol version"
)

// Session defines a multiplexed connection for streams
type Session struct {
	conn io.ReadWriteCloser

	config       *Config
	nextStreamID uint32                   // next stream identifier
	streams      map[uint32]*Stream       // all streams in this session
	rdEvents     map[uint32]chan struct{} // stream read notification

	tbf         chan struct{}      // tokenbuffer
	frameQueues map[uint32][]Frame // stream input frame queue

	die           chan struct{} // flag session has died
	chAccepts     chan *Stream
	chActiveClose chan uint32
	dataReady     int32 // flag data has arrived
	mu            sync.Mutex
}

func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
	s := new(Session)
	s.die = make(chan struct{})
	s.conn = conn
	s.config = config
	s.streams = make(map[uint32]*Stream)
	s.frameQueues = make(map[uint32][]Frame)
	s.rdEvents = make(map[uint32]chan struct{})
	s.chAccepts = make(chan *Stream, defaultAcceptBacklog)
	s.chActiveClose = make(chan uint32, defaultCloseWait)
	s.tbf = make(chan struct{}, config.MaxFrameTokens)
	for i := 0; i < config.MaxFrameTokens; i++ {
		s.tbf <- struct{}{}
	}
	if client {
		s.nextStreamID = 1
	} else {
		s.nextStreamID = 2
	}
	go s.recvLoop()
	go s.monitor()
	go s.keepalive()
	return s
}

// OpenStream is used to create a new stream
func (s *Session) OpenStream() (*Stream, error) {
	if s.IsClosed() {
		return nil, errors.New(errBrokenPipe)
	}

	sid := atomic.AddUint32(&s.nextStreamID, 2)
	chNotifyReader := make(chan struct{}, 1)
	stream := newStream(sid, s.config.MaxFrameSize, chNotifyReader, s)

	s.mu.Lock()
	s.rdEvents[sid] = chNotifyReader
	s.streams[sid] = stream
	s.mu.Unlock()

	s.sendFrame(newFrame(cmdSYN, sid))
	return stream, nil
}

// AcceptStream is used to block until the next available stream
// is ready to be accepted.
func (s *Session) AcceptStream() (*Stream, error) {
	select {
	case stream := <-s.chAccepts:
		return stream, nil
	case <-s.die:
		return nil, errors.New(errBrokenPipe)
	}
}

// Close is used to close the session and all streams.
func (s *Session) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	select {
	case <-s.die:
		return errors.New(errBrokenPipe)
	default:
		for k := range s.streams {
			s.streams[k].Close()
		}
		s.sendFrame(newFrame(cmdTerminate, 0))
		s.conn.Close()
		close(s.die)
	}
	return nil
}

// IsClosed does a safe check to see if we have shutdown
func (s *Session) IsClosed() bool {
	select {
	case <-s.die:
		return true
	default:
		return false
	}
}

// NumStreams returns the number of currently open streams
func (s *Session) NumStreams() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return len(s.streams)
}

// notify the session that a session has closed
func (s *Session) streamClosed(sid uint32) {
	s.chActiveClose <- sid
}

// nonblocking read from session pool, for streams
func (s *Session) nioread(sid uint32) *Frame {
	s.mu.Lock()
	frames := s.frameQueues[sid]
	if len(frames) > 0 {
		f := frames[0]
		s.frameQueues[sid] = frames[1:]
		s.tbf <- struct{}{}
		s.mu.Unlock()
		return &f
	}
	s.mu.Unlock()
	return nil
}

// session read a frame from underlying connection
func (s *Session) readFrame(buffer []byte) (f Frame, err error) {
	if _, err := io.ReadFull(s.conn, buffer[:headerSize]); err != nil {
		return f, errors.Wrap(err, "readFrame")
	}

	dec := rawHeader(buffer)
	if dec.Version() != version {
		return f, errors.New(errInvalidProtocol)
	}

	if length := dec.Length(); length > 0 {
		if _, err := io.ReadFull(s.conn, buffer[headerSize:headerSize+length]); err != nil {
			return f, errors.Wrap(err, "readFrame")
		}
		f.UnmarshalBinary(buffer[:headerSize+length])
		return f, nil
	}
	f.UnmarshalBinary(buffer[:headerSize])
	return f, nil
}

// monitors streams
func (s *Session) monitor() {
	for {
		select {
		case sid := <-s.chActiveClose:
			s.mu.Lock()
			delete(s.streams, sid)
			delete(s.rdEvents, sid)
			ntokens := len(s.frameQueues[sid])
			delete(s.frameQueues, sid)
			s.mu.Unlock()
			for i := 0; i < ntokens; i++ { // return remaining tokens to the pool
				s.tbf <- struct{}{}
			}
		case <-s.die:
			return
		}
	}
}

// recvLoop keeps on reading from underlying connection if tokens are available
func (s *Session) recvLoop() {
	buffer := make([]byte, (1<<16)+headerSize)
	for {
		select {
		case <-s.tbf:
			if f, err := s.readFrame(buffer); err == nil {
				s.mu.Lock()
				switch f.cmd {
				case cmdNOP:
					s.tbf <- struct{}{}
				case cmdTerminate:
					s.Close()
					return
				case cmdSYN:
					if _, ok := s.streams[f.sid]; !ok {
						chNotifyReader := make(chan struct{}, 1)
						s.streams[f.sid] = newStream(f.sid, s.config.MaxFrameSize, chNotifyReader, s)
						s.rdEvents[f.sid] = chNotifyReader
						s.chAccepts <- s.streams[f.sid]
					} else { // stream exists, RST the peer
						s.sendFrame(newFrame(cmdRST, f.sid))
					}
					s.tbf <- struct{}{}
				case cmdRST:
					if _, ok := s.streams[f.sid]; ok {
						s.streams[f.sid].Close()
					}
					s.tbf <- struct{}{}
				case cmdPSH:
					if _, ok := s.streams[f.sid]; ok {
						s.frameQueues[f.sid] = append(s.frameQueues[f.sid], f)
						select {
						case s.rdEvents[f.sid] <- struct{}{}:
						default:
						}
					} else { // stream absent
						s.sendFrame(newFrame(cmdRST, f.sid))
						s.tbf <- struct{}{}
					}
				default:
					s.sendFrame(newFrame(cmdRST, f.sid))
				}
				s.mu.Unlock()
				atomic.StoreInt32(&s.dataReady, 1)
			} else {
				s.Close()
				return
			}
		case <-s.die:
			return
		}
	}
}

func (s *Session) keepalive() {
	tickerPing := time.NewTicker(s.config.KeepAliveInterval)
	tickerTimeout := time.NewTicker(s.config.KeepAliveTimeout)
	defer tickerPing.Stop()
	defer tickerTimeout.Stop()
	for {
		select {
		case <-tickerPing.C:
			s.sendFrame(newFrame(cmdNOP, 0))
		case <-tickerTimeout.C:
			if !atomic.CompareAndSwapInt32(&s.dataReady, 1, 0) {
				s.Close()
				return
			}
		case <-s.die:
			return
		}
	}
}

func (s *Session) sendFrame(f Frame) {
	bts, _ := f.MarshalBinary()
	s.conn.Write(bts)
}
<commit_msg>add comment<commit_after>package smux

import (
	"io"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/errors"
)

const (
	defaultAcceptBacklog = 1024
	defaultCloseWait     = 1024
)

const (
	errBrokenPipe      = "broken pipe"
	errInvalidProtocol = "invalid protocol version"
)

// Session defines a multiplexed connection for streams
type Session struct {
	conn io.ReadWriteCloser

	config       *Config
	nextStreamID uint32                   // next stream identifier
	streams      map[uint32]*Stream       // all streams in this session
	rdEvents     map[uint32]chan struct{} // stream read notification

	tbf         chan struct{}      // tokenbuffer
	frameQueues map[uint32][]Frame // stream input frame queue

	die           chan struct{} // flag session has died
	chAccepts     chan *Stream
	chActiveClose chan uint32
	dataReady     int32 // flag data has arrived
	mu            sync.Mutex
}

func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
	s := new(Session)
	s.die = make(chan struct{})
	s.conn = conn
	s.config = config
	s.streams = make(map[uint32]*Stream)
	s.frameQueues = make(map[uint32][]Frame)
	s.rdEvents = make(map[uint32]chan struct{})
	s.chAccepts = make(chan *Stream, defaultAcceptBacklog)
	s.chActiveClose = make(chan uint32, defaultCloseWait)
	s.tbf = make(chan struct{}, config.MaxFrameTokens)
	for i := 0; i < config.MaxFrameTokens; i++ {
		s.tbf <- struct{}{}
	}
	if client {
		s.nextStreamID = 1
	} else {
		s.nextStreamID = 2
	}
	go s.recvLoop()
	go s.monitor()
	go s.keepalive()
	return s
}

// OpenStream is used to create a new stream
func (s *Session) OpenStream() (*Stream, error) {
	if s.IsClosed() {
		return nil, errors.New(errBrokenPipe)
	}

	sid := atomic.AddUint32(&s.nextStreamID, 2)
	chNotifyReader := make(chan struct{}, 1)
	stream := newStream(sid, s.config.MaxFrameSize, chNotifyReader, s)

	s.mu.Lock()
	s.rdEvents[sid] = chNotifyReader
	s.streams[sid] = stream
	s.mu.Unlock()

	s.sendFrame(newFrame(cmdSYN, sid))
	return stream, nil
}

// AcceptStream is used to block until the next available stream
// is ready to be accepted.
func (s *Session) AcceptStream() (*Stream, error) {
	select {
	case stream := <-s.chAccepts:
		return stream, nil
	case <-s.die:
		return nil, errors.New(errBrokenPipe)
	}
}

// Close is used to close the session and all streams.
func (s *Session) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	select {
	case <-s.die:
		return errors.New(errBrokenPipe)
	default:
		for k := range s.streams {
			s.streams[k].Close()
		}
		s.sendFrame(newFrame(cmdTerminate, 0))
		s.conn.Close()
		close(s.die)
	}
	return nil
}

// IsClosed does a safe check to see if we have shutdown
func (s *Session) IsClosed() bool {
	select {
	case <-s.die:
		return true
	default:
		return false
	}
}

// NumStreams returns the number of currently open streams
func (s *Session) NumStreams() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return len(s.streams)
}

// notify the session that a session has closed
func (s *Session) streamClosed(sid uint32) {
	s.chActiveClose <- sid
}

// nonblocking read from session pool, for streams
func (s *Session) nioread(sid uint32) *Frame {
	s.mu.Lock()
	frames := s.frameQueues[sid]
	if len(frames) > 0 {
		f := frames[0]
		s.frameQueues[sid] = frames[1:]
		s.tbf <- struct{}{}
		s.mu.Unlock()
		return &f
	}
	s.mu.Unlock()
	return nil
}

// session read a frame from underlying connection
func (s *Session) readFrame(buffer []byte) (f Frame, err error) {
	if _, err := io.ReadFull(s.conn, buffer[:headerSize]); err != nil {
		return f, errors.Wrap(err, "readFrame")
	}

	dec := rawHeader(buffer)
	if dec.Version() != version {
		return f, errors.New(errInvalidProtocol)
	}

	if length := dec.Length(); length > 0 {
		if _, err := io.ReadFull(s.conn, buffer[headerSize:headerSize+length]); err != nil {
			return f, errors.Wrap(err, "readFrame")
		}
		f.UnmarshalBinary(buffer[:headerSize+length])
		return f, nil
	}
	f.UnmarshalBinary(buffer[:headerSize])
	return f, nil
}

// monitors streams
func (s *Session) monitor() {
	for {
		select {
		case sid := <-s.chActiveClose:
			s.mu.Lock()
			delete(s.streams, sid)
			delete(s.rdEvents, sid)
			ntokens := len(s.frameQueues[sid])
			delete(s.frameQueues, sid)
			s.mu.Unlock()
			for i := 0; i < ntokens; i++ { // return remaining tokens to the pool
				s.tbf <- struct{}{}
			}
		case <-s.die:
			return
		}
	}
}

// recvLoop keeps on reading from underlying connection if tokens are available
func (s *Session) recvLoop() {
	buffer := make([]byte, (1<<16)+headerSize)
	for {
		select {
		case <-s.tbf:
			if f, err := s.readFrame(buffer); err == nil {
				s.mu.Lock()
				switch f.cmd {
				case cmdNOP:
					s.tbf <- struct{}{}
				case cmdTerminate:
					s.Close()
					return
				case cmdSYN:
					if _, ok := s.streams[f.sid]; !ok {
						chNotifyReader := make(chan struct{}, 1)
						s.streams[f.sid] = newStream(f.sid, s.config.MaxFrameSize, chNotifyReader, s)
						s.rdEvents[f.sid] = chNotifyReader
						s.chAccepts <- s.streams[f.sid]
					} else { // stream exists, RST the peer
						s.sendFrame(newFrame(cmdRST, f.sid))
					}
					s.tbf <- struct{}{}
				case cmdRST:
					if _, ok := s.streams[f.sid]; ok {
						s.streams[f.sid].Close()
					} else { // must do nothing if stream is absent
					}
					s.tbf <- struct{}{}
				case cmdPSH:
					if _, ok := s.streams[f.sid]; ok {
						s.frameQueues[f.sid] = append(s.frameQueues[f.sid], f)
						select {
						case s.rdEvents[f.sid] <- struct{}{}:
						default:
						}
					} else { // stream is absent
						s.sendFrame(newFrame(cmdRST, f.sid))
						s.tbf <- struct{}{}
					}
				default:
					s.sendFrame(newFrame(cmdRST, f.sid))
				}
				s.mu.Unlock()
				atomic.StoreInt32(&s.dataReady, 1)
			} else {
				s.Close()
				return
			}
		case <-s.die:
			return
		}
	}
}

func (s *Session) keepalive() {
	tickerPing := time.NewTicker(s.config.KeepAliveInterval)
	tickerTimeout := time.NewTicker(s.config.KeepAliveTimeout)
	defer tickerPing.Stop()
	defer tickerTimeout.Stop()
	for {
		select {
		case <-tickerPing.C:
			s.sendFrame(newFrame(cmdNOP, 0))
		case <-tickerTimeout.C:
			if !atomic.CompareAndSwapInt32(&s.dataReady, 1, 0) {
				s.Close()
				return
			}
		case <-s.die:
			return
		}
	}
}

func (s *Session) sendFrame(f Frame) {
	bts, _ := f.MarshalBinary()
	s.conn.Write(bts)
}
<|endoftext|>


<commit_before>// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.

package widgets

import (
	"image"

	. "github.com/gizak/termui"
)

type List struct {
	Block
	Rows             []string
	WrapText         bool
	TextStyle        Style
	SelectedRow      uint
	topRow           uint
	SelectedRowStyle Style
}

func NewList() *List {
	return &List{
		Block:            *NewBlock(),
		TextStyle:        Theme.List.Text,
		SelectedRowStyle: Theme.List.Text,
	}
}

func (self *List) Draw(buf *Buffer) {
	self.Block.Draw(buf)

	point := self.Inner.Min

	if self.SelectedRow >= uint(self.Inner.Max.Y)+self.topRow-2 {
		self.topRow = self.SelectedRow - uint(self.Inner.Max.Y) + 2
	} else if self.SelectedRow < self.topRow {
		self.topRow = self.SelectedRow
	}

	for row := self.topRow; row < uint(len(self.Rows)) && point.Y < self.Inner.Max.Y; row++ {
		cells := ParseText(self.Rows[row], self.TextStyle)
		if self.WrapText {
			cells = WrapCells(cells, uint(self.Inner.Dx()))
		}
		for j := 0; j < len(cells) && point.Y < self.Inner.Max.Y; j++ {
			style := cells[j].Style
			if row == self.SelectedRow {
				style = self.SelectedRowStyle
			}
			if cells[j].Rune == '\n' {
				point = image.Pt(self.Inner.Min.X, point.Y+1)
			} else {
				if point.X+1 == self.Inner.Max.X+1 && len(cells) > self.Inner.Dx() {
					buf.SetCell(NewCell(ELLIPSES, style), point.Add(image.Pt(-1, 0)))
					break
				} else {
					buf.SetCell(NewCell(cells[j].Rune, style), point)
					point = point.Add(image.Pt(1, 0))
				}
			}
		}
		point = image.Pt(self.Inner.Min.X, point.Y+1)
	}

	if self.topRow > 0 {
		buf.SetCell(
			NewCell(UP_ARROW, NewStyle(ColorWhite)),
			image.Pt(self.Inner.Max.X-1, self.Inner.Min.Y),
		)
	}
	if len(self.Rows) > int(self.topRow)+self.Inner.Dy() {
		buf.SetCell(
			NewCell(DOWN_ARROW, NewStyle(ColorWhite)),
			image.Pt(self.Inner.Max.X-1, self.Inner.Max.Y-1),
		)
	}
}

func (self *List) ScrollUp() {
	if self.SelectedRow > 0 {
		self.SelectedRow--
		if self.SelectedRow < self.topRow {
			self.topRow--
		}
	}
}

func (self *List) ScrollDown() {
	if self.SelectedRow < uint(len(self.Rows))-1 {
		self.SelectedRow++
		if self.SelectedRow-self.topRow > uint(self.Inner.Dy()-1) {
			self.topRow++
		}
	}
}

func (self *List) PageUp() { // Goes up one whole page.
	if int(self.SelectedRow)-self.Inner.Dy() >= 0 {
		self.topRow -= uint(self.Inner.Dy())
	} else { // If at the first 'page', then go to the top and select the first item.
		self.topRow = 0
	}
	self.SelectedRow = self.topRow
}

func (self *List) PageDown() { // Down one whole page
	if len(self.Rows)-int(self.topRow) > self.Inner.Dy() {
		self.topRow += uint(self.Inner.Dy())
		self.SelectedRow = self.topRow
	} else { // If at last 'page', then select last item.
		self.SelectedRow = uint(len(self.Rows) - 1)
	}
}
<commit_msg>Fix list PageUp scrolling<commit_after>// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.

package widgets

import (
	"image"

	. "github.com/gizak/termui"
)

type List struct {
	Block
	Rows             []string
	WrapText         bool
	TextStyle        Style
	SelectedRow      uint
	topRow           uint
	SelectedRowStyle Style
}

func NewList() *List {
	return &List{
		Block:            *NewBlock(),
		TextStyle:        Theme.List.Text,
		SelectedRowStyle: Theme.List.Text,
	}
}

func (self *List) Draw(buf *Buffer) {
	self.Block.Draw(buf)

	point := self.Inner.Min

	if self.SelectedRow >= uint(self.Inner.Max.Y)+self.topRow-2 {
		self.topRow = self.SelectedRow - uint(self.Inner.Max.Y) + 2
	} else if self.SelectedRow < self.topRow {
		self.topRow = self.SelectedRow
	}

	for row := self.topRow; row < uint(len(self.Rows)) && point.Y < self.Inner.Max.Y; row++ {
		cells := ParseText(self.Rows[row], self.TextStyle)
		if self.WrapText {
			cells = WrapCells(cells, uint(self.Inner.Dx()))
		}
		for j := 0; j < len(cells) && point.Y < self.Inner.Max.Y; j++ {
			style := cells[j].Style
			if row == self.SelectedRow {
				style = self.SelectedRowStyle
			}
			if cells[j].Rune == '\n' {
				point = image.Pt(self.Inner.Min.X, point.Y+1)
			} else {
				if point.X+1 == self.Inner.Max.X+1 && len(cells) > self.Inner.Dx() {
					buf.SetCell(NewCell(ELLIPSES, style), point.Add(image.Pt(-1, 0)))
					break
				} else {
					buf.SetCell(NewCell(cells[j].Rune, style), point)
					point = point.Add(image.Pt(1, 0))
				}
			}
		}
		point = image.Pt(self.Inner.Min.X, point.Y+1)
	}

	if self.topRow > 0 {
		buf.SetCell(
			NewCell(UP_ARROW, NewStyle(ColorWhite)),
			image.Pt(self.Inner.Max.X-1, self.Inner.Min.Y),
		)
	}
	if len(self.Rows) > int(self.topRow)+self.Inner.Dy() {
		buf.SetCell(
			NewCell(DOWN_ARROW, NewStyle(ColorWhite)),
			image.Pt(self.Inner.Max.X-1, self.Inner.Max.Y-1),
		)
	}
}

func (self *List) ScrollUp() {
	if self.SelectedRow > 0 {
		self.SelectedRow--
		if self.SelectedRow < self.topRow {
			self.topRow--
		}
	}
}

func (self *List) ScrollDown() {
	if self.SelectedRow < uint(len(self.Rows))-1 {
		self.SelectedRow++
		if self.SelectedRow-self.topRow > uint(self.Inner.Dy()-1) {
			self.topRow++
		}
	}
}

// PageUp scrolls up one whole page.
func (self *List) PageUp() {
	// if on the first 'page'
	if int(self.SelectedRow)-self.Inner.Dy() < 0 {
		// go to the top
		self.topRow = 0
	} else {
		self.topRow = uint(MaxInt(int(self.topRow)-self.Inner.Dy(), 0))
	}
	self.SelectedRow = self.topRow
}

// PageDown scolls down one whole page.
func (self *List) PageDown() {
	// if on last 'page'
	if len(self.Rows)-int(self.topRow) <= self.Inner.Dy() {
		// select last item
		self.SelectedRow = uint(len(self.Rows) - 1)
	} else {
		self.topRow += uint(self.Inner.Dy())
		self.SelectedRow = self.topRow
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"log"
	"net"
	"net/http"
	"net/http/fcgi"
	"strings"
)

type FastCGIServer struct{}

func (s *FastCGIServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	ServeHTTP(w, req)
}

func ServeHTTP(w http.ResponseWriter, req *http.Request) {
	url := req.URL
	urlComponents := strings.Split(url.Path, "/")
	log.Println(urlComponents)
	if urlComponents[1] != "app" || len(urlComponents) < 3 {
		log.Println("Not in app:", urlComponents)
		return
	}
	session, err := mgo.Dial("localhost")
	if err != nil {
		panic(err)
	}
	defer session.Close()

	// Optional. Switch the session to a monotonic behavior
	session.SetMode(mgo.Monotonic, true)

	if urlComponents[2] == "GetLines" {
		c := session.DB("dispotrains").C("lines")
		var lines []bson.M = make([]bson.M, 0)
		c.Find(nil).Sort("value.network", "value.id").All(&lines)
		jsonLines := make([]bson.M, 0)
		for _, line := range lines {
			jsonLines = append(jsonLines, line["value"].(bson.M))
		}
		json.NewEncoder(w).Encode(&jsonLines)
	} else if urlComponents[2] == "GetStation" && len(urlComponents) >= 4 {
		var stationName string = urlComponents[3]
		c := session.DB("dispotrains").C("stations")
		var station bson.M
		c.Find(bson.M{"name": stationName}).One(&station)
		json.NewEncoder(w).Encode(&station)
	} else {
		log.Println("Not a recognized action")
	}
}

/*func main() {
    http.HandleFunc("/", ServeHTTP)
    http.ListenAndServe(":8080", nil)
}*/

func main() {
	fmt.Printf("Starting server")
	l, _ := net.Listen("tcp", "127.0.0.1:9000")
	b := new(FastCGIServer)
	fcgi.Serve(l, b)
}
<commit_msg>Add API call to get all stations information; useful for app sync<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
	"log"
	"net"
	"net/http"
	"net/http/fcgi"
	"strings"
)

type FastCGIServer struct{}

func (s *FastCGIServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	ServeHTTP(w, req)
}

func ServeHTTP(w http.ResponseWriter, req *http.Request) {
	url := req.URL
	urlComponents := strings.Split(url.Path, "/")
	log.Println(urlComponents)
	if urlComponents[1] != "app" || len(urlComponents) < 3 {
		log.Println("Not in app:", urlComponents)
		return
	}
	session, err := mgo.Dial("localhost")
	if err != nil {
		panic(err)
	}
	defer session.Close()

	// Optional. Switch the session to a monotonic behavior
	session.SetMode(mgo.Monotonic, true)

	if urlComponents[2] == "GetLines" {
		c := session.DB("dispotrains").C("lines")
		var lines []bson.M = make([]bson.M, 0)
		c.Find(nil).Sort("value.network", "value.id").All(&lines)
		jsonLines := make([]bson.M, 0)
		for _, line := range lines {
			jsonLines = append(jsonLines, line["value"].(bson.M))
		}
		json.NewEncoder(w).Encode(&jsonLines)
	} else if urlComponents[2] == "GetStation" && len(urlComponents) >= 4 {
		var stationName string = urlComponents[3]
		c := session.DB("dispotrains").C("stations")
		var station bson.M
		c.Find(bson.M{"name": stationName}).One(&station)
		json.NewEncoder(w).Encode(&station)
	} else if urlComponents[2] == "GetStations" {
		c := session.DB("dispotrains").C("stations")
		var stations []bson.M = make([]bson.M, 0)
		if err := c.Find(nil).All(&stations); err != nil {
			log.Println(err)
		}
		jsonStations := make([]bson.M, 0)
		for _, station := range stations {
			delete(station, "_id")
			jsonStations = append(jsonStations, station)
		}
		json.NewEncoder(w).Encode(&jsonStations)
	} else {
		log.Println("Not a recognized action")
	}
}

/*func main() {
    http.HandleFunc("/", ServeHTTP)
    http.ListenAndServe(":8080", nil)
}*/

func main() {
	fmt.Printf("Starting server")
	l, _ := net.Listen("tcp", "127.0.0.1:9000")
	b := new(FastCGIServer)
	fcgi.Serve(l, b)
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 metadata

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"k8s.io/klog/v2"

	metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/rest"
)

var deleteScheme = runtime.NewScheme()
var parameterScheme = runtime.NewScheme()
var deleteOptionsCodec = serializer.NewCodecFactory(deleteScheme)
var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme)

var versionV1 = schema.GroupVersion{Version: "v1"}

func init() {
	metav1.AddToGroupVersion(parameterScheme, versionV1)
	metav1.AddToGroupVersion(deleteScheme, versionV1)
}

// Client allows callers to retrieve the object metadata for any
// Kubernetes-compatible API endpoint. The client uses the
// meta.k8s.io/v1 PartialObjectMetadata resource to more efficiently
// retrieve just the necessary metadata, but on older servers
// (Kubernetes 1.14 and before) will retrieve the object and then
// convert the metadata.
type Client struct {
	client *rest.RESTClient
}

var _ Interface = &Client{}

// ConfigFor returns a copy of the provided config with the
// appropriate metadata client defaults set.
func ConfigFor(inConfig *rest.Config) *rest.Config {
	config := rest.CopyConfig(inConfig)
	config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
	config.ContentType = "application/vnd.kubernetes.protobuf"
	config.NegotiatedSerializer = metainternalversionscheme.Codecs.WithoutConversion()
	if config.UserAgent == "" {
		config.UserAgent = rest.DefaultKubernetesUserAgent()
	}
	return config
}

// NewForConfigOrDie creates a new metadata client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) Interface {
	ret, err := NewForConfig(c)
	if err != nil {
		panic(err)
	}
	return ret
}

// NewForConfig creates a new metadata client that can retrieve object
// metadata details about any Kubernetes object (core, aggregated, or custom
// resource based) in the form of PartialObjectMetadata objects, or returns
// an error.
func NewForConfig(inConfig *rest.Config) (Interface, error) {
	config := ConfigFor(inConfig)
	// for serializing the options
	config.GroupVersion = &schema.GroupVersion{}
	config.APIPath = "/this-value-should-never-be-sent"

	restClient, err := rest.RESTClientFor(config)
	if err != nil {
		return nil, err
	}

	return &Client{client: restClient}, nil
}

type client struct {
	client    *Client
	namespace string
	resource  schema.GroupVersionResource
}

// Resource returns an interface that can access cluster or namespace
// scoped instances of resource.
func (c *Client) Resource(resource schema.GroupVersionResource) Getter {
	return &client{client: c, resource: resource}
}

// Namespace returns an interface that can access namespace-scoped instances of the
// provided resource.
func (c *client) Namespace(ns string) ResourceInterface {
	ret := *c
	ret.namespace = ns
	return &ret
}

// Delete removes the provided resource from the server.
func (c *client) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error {
	if len(name) == 0 {
		return fmt.Errorf("name is required")
	}
	// if DeleteOptions are delivered to Negotiator for serialization,
	// HTTP-Request header will bring "Content-Type: application/vnd.kubernetes.protobuf"
	// apiextensions-apiserver uses unstructuredNegotiatedSerializer to decode the input,
	// server-side will reply with 406 errors.
	// The special treatment here is to be compatible with CRD Handler
	// see: https://github.com/kubernetes/kubernetes/blob/1a845ccd076bbf1b03420fe694c85a5cd3bd6bed/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go#L843
	deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
	if err != nil {
		return err
	}

	result := c.client.client.
		Delete().
		AbsPath(append(c.makeURLSegments(name), subresources...)...).
		SetHeader("Content-Type", runtime.ContentTypeJSON).
		Body(deleteOptionsByte).
		Do(ctx)
	return result.Error()
}

// DeleteCollection triggers deletion of all resources in the specified scope (namespace or cluster).
func (c *client) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error {
	// See comment on Delete
	deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
	if err != nil {
		return err
	}

	result := c.client.client.
		Delete().
		AbsPath(c.makeURLSegments("")...).
		SetHeader("Content-Type", runtime.ContentTypeJSON).
		Body(deleteOptionsByte).
		SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).
		Do(ctx)
	return result.Error()
}

// Get returns the resource with name from the specified scope (namespace or cluster).
func (c *client) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
	if len(name) == 0 {
		return nil, fmt.Errorf("name is required")
	}
	result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Do(ctx)
	if err := result.Error(); err != nil {
		return nil, err
	}
	obj, err := result.Get()
	if runtime.IsNotRegisteredError(err) {
		klog.V(5).Infof("Unable to retrieve PartialObjectMetadata: %#v", err)
		rawBytes, err := result.Raw()
		if err != nil {
			return nil, err
		}
		var partial metav1.PartialObjectMetadata
		if err := json.Unmarshal(rawBytes, &partial); err != nil {
			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
		}
		if !isLikelyObjectMetadata(&partial) {
			return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema: %#v", partial)
		}
		partial.TypeMeta = metav1.TypeMeta{}
		return &partial, nil
	}
	if err != nil {
		return nil, err
	}
	partial, ok := obj.(*metav1.PartialObjectMetadata)
	if !ok {
		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
	}
	return partial, nil
}

// List returns all resources within the specified scope (namespace or cluster).
func (c *client) List(ctx context.Context, opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) {
	result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Do(ctx)
	if err := result.Error(); err != nil {
		return nil, err
	}
	obj, err := result.Get()
	if runtime.IsNotRegisteredError(err) {
		klog.V(5).Infof("Unable to retrieve PartialObjectMetadataList: %#v", err)
		rawBytes, err := result.Raw()
		if err != nil {
			return nil, err
		}
		var partial metav1.PartialObjectMetadataList
		if err := json.Unmarshal(rawBytes, &partial); err != nil {
			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadataList: %v", err)
		}
		partial.TypeMeta = metav1.TypeMeta{}
		return &partial, nil
	}
	if err != nil {
		return nil, err
	}
	partial, ok := obj.(*metav1.PartialObjectMetadataList)
	if !ok {
		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
	}
	return partial, nil
}

// Watch finds all changes to the resources in the specified scope (namespace or cluster).
func (c *client) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
	var timeout time.Duration
	if opts.TimeoutSeconds != nil {
		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
	}
	opts.Watch = true
	return c.client.client.Get().
		AbsPath(c.makeURLSegments("")...).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Timeout(timeout).
		Watch(ctx)
}

// Patch modifies the named resource in the specified scope (namespace or cluster).
func (c *client) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
	if len(name) == 0 {
		return nil, fmt.Errorf("name is required")
	}
	result := c.client.client.
		Patch(pt).
		AbsPath(append(c.makeURLSegments(name), subresources...)...).
		Body(data).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Do(ctx)
	if err := result.Error(); err != nil {
		return nil, err
	}
	obj, err := result.Get()
	if runtime.IsNotRegisteredError(err) {
		rawBytes, err := result.Raw()
		if err != nil {
			return nil, err
		}
		var partial metav1.PartialObjectMetadata
		if err := json.Unmarshal(rawBytes, &partial); err != nil {
			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
		}
		if !isLikelyObjectMetadata(&partial) {
			return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema")
		}
		partial.TypeMeta = metav1.TypeMeta{}
		return &partial, nil
	}
	if err != nil {
		return nil, err
	}
	partial, ok := obj.(*metav1.PartialObjectMetadata)
	if !ok {
		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
	}
	return partial, nil
}

func (c *client) makeURLSegments(name string) []string {
	url := []string{}
	if len(c.resource.Group) == 0 {
		url = append(url, "api")
	} else {
		url = append(url, "apis", c.resource.Group)
	}
	url = append(url, c.resource.Version)

	if len(c.namespace) > 0 {
		url = append(url, "namespaces", c.namespace)
	}
	url = append(url, c.resource.Resource)

	if len(name) > 0 {
		url = append(url, name)
	}

	return url
}

func isLikelyObjectMetadata(meta *metav1.PartialObjectMetadata) bool {
	return len(meta.UID) > 0 || !meta.CreationTimestamp.IsZero() || len(meta.Name) > 0 || len(meta.GenerateName) > 0
}
<commit_msg>expose NewForConfigAndClient for the metadata client<commit_after>/*
Copyright 2018 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 metadata

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

	"k8s.io/klog/v2"

	metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/rest"
)

var deleteScheme = runtime.NewScheme()
var parameterScheme = runtime.NewScheme()
var deleteOptionsCodec = serializer.NewCodecFactory(deleteScheme)
var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme)

var versionV1 = schema.GroupVersion{Version: "v1"}

func init() {
	metav1.AddToGroupVersion(parameterScheme, versionV1)
	metav1.AddToGroupVersion(deleteScheme, versionV1)
}

// Client allows callers to retrieve the object metadata for any
// Kubernetes-compatible API endpoint. The client uses the
// meta.k8s.io/v1 PartialObjectMetadata resource to more efficiently
// retrieve just the necessary metadata, but on older servers
// (Kubernetes 1.14 and before) will retrieve the object and then
// convert the metadata.
type Client struct {
	client *rest.RESTClient
}

var _ Interface = &Client{}

// ConfigFor returns a copy of the provided config with the
// appropriate metadata client defaults set.
func ConfigFor(inConfig *rest.Config) *rest.Config {
	config := rest.CopyConfig(inConfig)
	config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
	config.ContentType = "application/vnd.kubernetes.protobuf"
	config.NegotiatedSerializer = metainternalversionscheme.Codecs.WithoutConversion()
	if config.UserAgent == "" {
		config.UserAgent = rest.DefaultKubernetesUserAgent()
	}
	return config
}

// NewForConfigOrDie creates a new metadata client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) Interface {
	ret, err := NewForConfig(c)
	if err != nil {
		panic(err)
	}
	return ret
}

// NewForConfig creates a new metadata client that can retrieve object
// metadata details about any Kubernetes object (core, aggregated, or custom
// resource based) in the form of PartialObjectMetadata objects, or returns
// an error.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
func NewForConfig(inConfig *rest.Config) (Interface, error) {
	config := ConfigFor(inConfig)

	httpClient, err := rest.HTTPClientFor(config)
	if err != nil {
		return nil, err
	}
	return NewForConfigAndClient(config, httpClient)
}

// NewForConfigAndClient creates a new metadata client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient(inConfig *rest.Config, h *http.Client) (Interface, error) {
	config := ConfigFor(inConfig)
	// for serializing the options
	config.GroupVersion = &schema.GroupVersion{}
	config.APIPath = "/this-value-should-never-be-sent"

	restClient, err := rest.RESTClientForConfigAndClient(config, h)
	if err != nil {
		return nil, err
	}

	return &Client{client: restClient}, nil
}

type client struct {
	client    *Client
	namespace string
	resource  schema.GroupVersionResource
}

// Resource returns an interface that can access cluster or namespace
// scoped instances of resource.
func (c *Client) Resource(resource schema.GroupVersionResource) Getter {
	return &client{client: c, resource: resource}
}

// Namespace returns an interface that can access namespace-scoped instances of the
// provided resource.
func (c *client) Namespace(ns string) ResourceInterface {
	ret := *c
	ret.namespace = ns
	return &ret
}

// Delete removes the provided resource from the server.
func (c *client) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error {
	if len(name) == 0 {
		return fmt.Errorf("name is required")
	}
	// if DeleteOptions are delivered to Negotiator for serialization,
	// HTTP-Request header will bring "Content-Type: application/vnd.kubernetes.protobuf"
	// apiextensions-apiserver uses unstructuredNegotiatedSerializer to decode the input,
	// server-side will reply with 406 errors.
	// The special treatment here is to be compatible with CRD Handler
	// see: https://github.com/kubernetes/kubernetes/blob/1a845ccd076bbf1b03420fe694c85a5cd3bd6bed/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go#L843
	deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
	if err != nil {
		return err
	}

	result := c.client.client.
		Delete().
		AbsPath(append(c.makeURLSegments(name), subresources...)...).
		SetHeader("Content-Type", runtime.ContentTypeJSON).
		Body(deleteOptionsByte).
		Do(ctx)
	return result.Error()
}

// DeleteCollection triggers deletion of all resources in the specified scope (namespace or cluster).
func (c *client) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error {
	// See comment on Delete
	deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
	if err != nil {
		return err
	}

	result := c.client.client.
		Delete().
		AbsPath(c.makeURLSegments("")...).
		SetHeader("Content-Type", runtime.ContentTypeJSON).
		Body(deleteOptionsByte).
		SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).
		Do(ctx)
	return result.Error()
}

// Get returns the resource with name from the specified scope (namespace or cluster).
func (c *client) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
	if len(name) == 0 {
		return nil, fmt.Errorf("name is required")
	}
	result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Do(ctx)
	if err := result.Error(); err != nil {
		return nil, err
	}
	obj, err := result.Get()
	if runtime.IsNotRegisteredError(err) {
		klog.V(5).Infof("Unable to retrieve PartialObjectMetadata: %#v", err)
		rawBytes, err := result.Raw()
		if err != nil {
			return nil, err
		}
		var partial metav1.PartialObjectMetadata
		if err := json.Unmarshal(rawBytes, &partial); err != nil {
			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
		}
		if !isLikelyObjectMetadata(&partial) {
			return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema: %#v", partial)
		}
		partial.TypeMeta = metav1.TypeMeta{}
		return &partial, nil
	}
	if err != nil {
		return nil, err
	}
	partial, ok := obj.(*metav1.PartialObjectMetadata)
	if !ok {
		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
	}
	return partial, nil
}

// List returns all resources within the specified scope (namespace or cluster).
func (c *client) List(ctx context.Context, opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) {
	result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Do(ctx)
	if err := result.Error(); err != nil {
		return nil, err
	}
	obj, err := result.Get()
	if runtime.IsNotRegisteredError(err) {
		klog.V(5).Infof("Unable to retrieve PartialObjectMetadataList: %#v", err)
		rawBytes, err := result.Raw()
		if err != nil {
			return nil, err
		}
		var partial metav1.PartialObjectMetadataList
		if err := json.Unmarshal(rawBytes, &partial); err != nil {
			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadataList: %v", err)
		}
		partial.TypeMeta = metav1.TypeMeta{}
		return &partial, nil
	}
	if err != nil {
		return nil, err
	}
	partial, ok := obj.(*metav1.PartialObjectMetadataList)
	if !ok {
		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
	}
	return partial, nil
}

// Watch finds all changes to the resources in the specified scope (namespace or cluster).
func (c *client) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
	var timeout time.Duration
	if opts.TimeoutSeconds != nil {
		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
	}
	opts.Watch = true
	return c.client.client.Get().
		AbsPath(c.makeURLSegments("")...).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Timeout(timeout).
		Watch(ctx)
}

// Patch modifies the named resource in the specified scope (namespace or cluster).
func (c *client) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
	if len(name) == 0 {
		return nil, fmt.Errorf("name is required")
	}
	result := c.client.client.
		Patch(pt).
		AbsPath(append(c.makeURLSegments(name), subresources...)...).
		Body(data).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Do(ctx)
	if err := result.Error(); err != nil {
		return nil, err
	}
	obj, err := result.Get()
	if runtime.IsNotRegisteredError(err) {
		rawBytes, err := result.Raw()
		if err != nil {
			return nil, err
		}
		var partial metav1.PartialObjectMetadata
		if err := json.Unmarshal(rawBytes, &partial); err != nil {
			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
		}
		if !isLikelyObjectMetadata(&partial) {
			return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema")
		}
		partial.TypeMeta = metav1.TypeMeta{}
		return &partial, nil
	}
	if err != nil {
		return nil, err
	}
	partial, ok := obj.(*metav1.PartialObjectMetadata)
	if !ok {
		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
	}
	return partial, nil
}

func (c *client) makeURLSegments(name string) []string {
	url := []string{}
	if len(c.resource.Group) == 0 {
		url = append(url, "api")
	} else {
		url = append(url, "apis", c.resource.Group)
	}
	url = append(url, c.resource.Version)

	if len(c.namespace) > 0 {
		url = append(url, "namespaces", c.namespace)
	}
	url = append(url, c.resource.Resource)

	if len(name) > 0 {
		url = append(url, name)
	}

	return url
}

func isLikelyObjectMetadata(meta *metav1.PartialObjectMetadata) bool {
	return len(meta.UID) > 0 || !meta.CreationTimestamp.IsZero() || len(meta.Name) > 0 || len(meta.GenerateName) > 0
}
<|endoftext|>


<commit_before>// +build windows,amd64

package winapi

import (
	"fmt"

	ole "github.com/go-ole/go-ole"
	"github.com/go-ole/go-ole/oleutil"
	so "github.com/iamacarpet/go-win64api/shared"
	"github.com/scjalliance/comshim"
)

// BackupBitLockerRecoveryKeys backups up volume recovery information to Active Directory.
// Requires one or more PersistentVolumeIDs, available from GetBitLockerRecoveryInfo.
//
// Ref: https://docs.microsoft.com/en-us/windows/win32/secprov/backuprecoveryinformationtoactivedirectory-win32-encryptablevolume
func BackupBitLockerRecoveryKeys(persistentVolumeIDs []string) error {
	comshim.Add(1)
	defer comshim.Done()

	w := &wmi{}
	if err := w.Connect(); err != nil {
		return fmt.Errorf("wmi.Connect: %w", err)
	}
	defer w.Close()

	for _, pvid := range persistentVolumeIDs {
		raw, err := oleutil.CallMethod(w.svc, "ExecQuery",
			fmt.Sprintf(`SELECT * FROM Win32_EncryptableVolume WHERE PersistentVolumeID="%s"`, pvid),
		)
		if err != nil {
			return fmt.Errorf("ExecQuery: %w", err)
		}
		result := raw.ToIDispatch()
		defer result.Release()

		itemRaw, err := oleutil.CallMethod(result, "ItemIndex", 0)
		if err != nil {
			return fmt.Errorf("failed to fetch result row while processing BitLocker info: %w", err)
		}
		item := itemRaw.ToIDispatch()
		defer item.Release()

		keys, err := getKeyProtectors(item)
		if err != nil {
			return fmt.Errorf("getKeyProtectors: %w", err)
		}

		for _, k := range keys {
			statusResultRaw, err := oleutil.CallMethod(item, "BackupRecoveryInformationToActiveDirectory", k)
			if err != nil {
				return fmt.Errorf("unable to backup bitlocker information to active directory: %w", err)
			} else if val, ok := statusResultRaw.Value().(int32); val != 0 || !ok {
				return fmt.Errorf("invalid result while backing up bitlocker information to active directory: %v", val)
			}
		}
	}
	return nil
}

// GetBitLockerConversionStatus returns the Bitlocker conversion status for all local drives.
func GetBitLockerConversionStatus() ([]*so.BitLockerConversionStatus, error) {
	comshim.Add(1)
	defer comshim.Done()

	return getBitLockerConversionStatusInternal("")
}

// GetBitLockerConversionStatusForDrive returns the Bitlocker conversion status for a specific drive.
func GetBitLockerConversionStatusForDrive(driveLetter string) (*so.BitLockerConversionStatus, error) {
	comshim.Add(1)
	defer comshim.Done()

	result, err := getBitLockerConversionStatusInternal(" WHERE DriveLetter = '" + driveLetter + "'")
	if err != nil {
		return nil, err
	}

	if len(result) < 1 {
		return nil, fmt.Errorf("error getting BitLocker conversion status, drive not found: %s", driveLetter)
	} else if len(result) > 1 {
		return nil, fmt.Errorf("error getting BitLocker conversion status, too many results: %s", driveLetter)
	} else {
		return result[0], err
	}
}

// GetBitLockerRecoveryInfo returns the Bitlocker device info for all local drives.
func GetBitLockerRecoveryInfo() ([]*so.BitLockerDeviceInfo, error) {
	comshim.Add(1)
	defer comshim.Done()

	return getBitLockerRecoveryInfoInternal("")
}

// GetBitLockerRecoveryInfoForDrive returns the Bitlocker device info for a specific drive.
func GetBitLockerRecoveryInfoForDrive(driveLetter string) (*so.BitLockerDeviceInfo, error) {
	comshim.Add(1)
	defer comshim.Done()

	result, err := getBitLockerRecoveryInfoInternal(" WHERE DriveLetter = '" + driveLetter + "'")
	if err != nil {
		return nil, err
	}

	if len(result) < 1 {
		return nil, fmt.Errorf("error getting BitLocker Recovery Info, drive not found: %s", driveLetter)
	} else if len(result) > 1 {
		return nil, fmt.Errorf("error getting BitLocker Recovery Info, too many results: %s", driveLetter)
	} else {
		return result[0], err
	}
}

type wmi struct {
	intf *ole.IDispatch
	svc  *ole.IDispatch
}

func (w *wmi) Connect() error {
	unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator")
	if err != nil {
		return fmt.Errorf("unable to create initial object, %w", err)
	}
	defer unknown.Release()
	w.intf, err = unknown.QueryInterface(ole.IID_IDispatch)
	if err != nil {
		return fmt.Errorf("unable to create initial object, %w", err)
	}
	serviceRaw, err := oleutil.CallMethod(w.intf, "ConnectServer", nil, `\\.\ROOT\CIMV2\Security\MicrosoftVolumeEncryption`)
	if err != nil {
		return fmt.Errorf("permission denied: %w", err)
	}
	w.svc = serviceRaw.ToIDispatch()
	return nil
}

func (w *wmi) Close() {
	w.svc.Release()
	w.intf.Release()
}

func getBitLockerConversionStatusInternal(where string) ([]*so.BitLockerConversionStatus, error) {
	w := &wmi{}
	if err := w.Connect(); err != nil {
		return nil, fmt.Errorf("wmi.Connect: %w", err)
	}
	defer w.Close()
	raw, err := oleutil.CallMethod(w.svc, "ExecQuery", "SELECT * FROM Win32_EncryptableVolume"+where)
	if err != nil {
		return nil, fmt.Errorf("ExecQuery: %w", err)
	}
	result := raw.ToIDispatch()
	defer result.Release()

	ret := []*so.BitLockerConversionStatus{}

	countVar, err := oleutil.GetProperty(result, "Count")
	if err != nil {
		return nil, fmt.Errorf("unable to get property Count while processing BitLocker info: %w", err)
	}
	count := int(countVar.Val)

	for i := 0; i < count; i++ {
		retData, err := bitlockerConversionStatus(result, i)
		if err != nil {
			return nil, err
		}

		ret = append(ret, retData)
	}

	return ret, nil
}

func getBitLockerRecoveryInfoInternal(where string) ([]*so.BitLockerDeviceInfo, error) {
	w := &wmi{}
	if err := w.Connect(); err != nil {
		return nil, fmt.Errorf("wmi.Connect: %w", err)
	}
	defer w.Close()
	raw, err := oleutil.CallMethod(w.svc, "ExecQuery", "SELECT * FROM Win32_EncryptableVolume"+where)
	if err != nil {
		return nil, fmt.Errorf("ExecQuery: %w", err)
	}
	result := raw.ToIDispatch()
	defer result.Release()

	retBitLocker := []*so.BitLockerDeviceInfo{}

	countVar, err := oleutil.GetProperty(result, "Count")
	if err != nil {
		return nil, fmt.Errorf("unable to get property Count while processing BitLocker info: %w", err)
	}
	count := int(countVar.Val)

	for i := 0; i < count; i++ {
		retData, err := bitlockerRecoveryInfo(result, i)
		if err != nil {
			return nil, err
		}

		retBitLocker = append(retBitLocker, retData)
	}

	return retBitLocker, nil
}

func getKeyProtectors(item *ole.IDispatch) ([]string, error) {
	kp := []string{}
	var keyProtectorResults ole.VARIANT
	ole.VariantInit(&keyProtectorResults)
	keyIDResultRaw, err := oleutil.CallMethod(item, "GetKeyProtectors", 3, &keyProtectorResults)
	if err != nil {
		return nil, fmt.Errorf("Unable to get Key Protectors while getting BitLocker info. %s", err.Error())
	} else if val, ok := keyIDResultRaw.Value().(int32); val != 0 || !ok {
		return nil, fmt.Errorf("Unable to get Key Protectors while getting BitLocker info. Return code %d", val)
	}
	keyProtectorValues := keyProtectorResults.ToArray().ToValueArray()
	for _, keyIDItemRaw := range keyProtectorValues {
		keyIDItem, ok := keyIDItemRaw.(string)
		if !ok {
			return nil, fmt.Errorf("KeyProtectorID wasn't a string...")
		}
		kp = append(kp, keyIDItem)
	}
	return kp, nil
}

func bitlockerConversionStatus(result *ole.IDispatch, i int) (*so.BitLockerConversionStatus, error) {
	itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch result row while processing BitLocker info: %w", err)
	}
	item := itemRaw.ToIDispatch()
	defer item.Release()

	retData := &so.BitLockerConversionStatus{}

	// https://docs.microsoft.com/en-us/windows/win32/secprov/getconversionstatus-win32-encryptablevolume
	var conversionStatus ole.VARIANT
	ole.VariantInit(&conversionStatus)
	var encryptionPercentage ole.VARIANT
	ole.VariantInit(&encryptionPercentage)
	var encryptionFlags ole.VARIANT
	ole.VariantInit(&encryptionFlags)
	var wipingStatus ole.VARIANT
	ole.VariantInit(&wipingStatus)
	var wipingPercentage ole.VARIANT
	ole.VariantInit(&wipingPercentage)
	statusResultRaw, err := oleutil.CallMethod(
		item, "GetConversionStatus",
		&conversionStatus,
		&encryptionPercentage,
		&encryptionFlags,
		&wipingStatus,
		&wipingPercentage,
		0,
	)
	if err != nil {
		return nil, fmt.Errorf("unable to get conversion status while getting BitLocker info: %w", err)
	} else if val, ok := statusResultRaw.Value().(uint32); val != 0 || !ok {
		// The possible return values of GetConversionStatus are S_OK (0x0) and FVE_E_LOCKED_VOLUME (0x80310000)
		// If the return value is not 0, the volume is locked.
		return nil, fmt.Errorf("unable to get conversion status while getting BitLocker info; the volume is locked. Return code %d", val)
	}

	retData.ConversionStatus = conversionStatus.Value().(uint32)
	retData.EncryptionPercentage = encryptionPercentage.Value().(uint32)
	retData.EncryptionFlags = encryptionFlags.Value().(uint32)
	retData.WipingStatus = wipingStatus.Value().(uint32)
	retData.WipingPercentage = wipingPercentage.Value().(uint32)

	return retData, nil
}

func bitlockerRecoveryInfo(result *ole.IDispatch, i int) (*so.BitLockerDeviceInfo, error) {
	itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch result row while processing BitLocker info. %w", err)
	}
	item := itemRaw.ToIDispatch()
	defer item.Release()

	retData := &so.BitLockerDeviceInfo{
		RecoveryKeys: []string{},
	}

	resDeviceID, err := oleutil.GetProperty(item, "DeviceID")
	if err != nil {
		return nil, fmt.Errorf("Error while getting property DeviceID from BitLocker info. %s", err.Error())
	}
	retData.DeviceID = resDeviceID.ToString()

	resPersistentVolumeID, err := oleutil.GetProperty(item, "PersistentVolumeID")
	if err != nil {
		return nil, fmt.Errorf("Error while getting property PersistentVolumeID from BitLocker info. %s", err.Error())
	}
	retData.PersistentVolumeID = resPersistentVolumeID.ToString()

	resDriveLetter, err := oleutil.GetProperty(item, "DriveLetter")
	if err != nil {
		return nil, fmt.Errorf("Error while getting property DriveLetter from BitLocker info. %s", err.Error())
	}
	retData.DriveLetter = resDriveLetter.ToString()

	resProtectionStatus, err := oleutil.GetProperty(item, "ProtectionStatus")
	if err != nil {
		return nil, fmt.Errorf("Error while getting property ProtectionStatus from BitLocker info. %s", err.Error())
	}
	var ok bool
	retData.ProtectionStatus, ok = resProtectionStatus.Value().(uint32)
	if !ok {
		return nil, fmt.Errorf("Failed to parse ProtectionStatus from BitLocker info as uint32")
	}

	resConversionStatus, err := oleutil.GetProperty(item, "ConversionStatus")
	if err != nil {
		return nil, fmt.Errorf("error while getting property ConversionStatus from BitLocker info: %w", err)
	}
	ok = false
	retData.ConversionStatus, ok = resConversionStatus.Value().(uint32)
	if !ok {
		return nil, fmt.Errorf("Failed to parse ConversionStatus from BitLocker info as uint32")
	}
	keys, err := getKeyProtectors(item)
	if err != nil {
		return nil, fmt.Errorf("getKeyProtectors: %w", err)
	}

	for _, k := range keys {
		err = func() error {
			var recoveryKey ole.VARIANT
			ole.VariantInit(&recoveryKey)
			recoveryKeyResultRaw, err := oleutil.CallMethod(item, "GetKeyProtectorNumericalPassword", k, &recoveryKey)
			if err != nil {
				return fmt.Errorf("Unable to get Recovery Key while getting BitLocker info. %s", err.Error())
			} else if val, ok := recoveryKeyResultRaw.Value().(int32); val != 0 || !ok {
				return fmt.Errorf("Unable to get Recovery Key while getting BitLocker info. Return code %d", val)
			}
			retData.RecoveryKeys = append(retData.RecoveryKeys, recoveryKey.ToString())
			return nil
		}()
		if err != nil {
			return nil, err
		}
	}

	return retData, nil
}
<commit_msg>fix bitlocker conversion status<commit_after>// +build windows,amd64

package winapi

import (
	"fmt"

	ole "github.com/go-ole/go-ole"
	"github.com/go-ole/go-ole/oleutil"
	so "github.com/iamacarpet/go-win64api/shared"
	"github.com/scjalliance/comshim"
)

// BackupBitLockerRecoveryKeys backups up volume recovery information to Active Directory.
// Requires one or more PersistentVolumeIDs, available from GetBitLockerRecoveryInfo.
//
// Ref: https://docs.microsoft.com/en-us/windows/win32/secprov/backuprecoveryinformationtoactivedirectory-win32-encryptablevolume
func BackupBitLockerRecoveryKeys(persistentVolumeIDs []string) error {
	comshim.Add(1)
	defer comshim.Done()

	w := &wmi{}
	if err := w.Connect(); err != nil {
		return fmt.Errorf("wmi.Connect: %w", err)
	}
	defer w.Close()

	for _, pvid := range persistentVolumeIDs {
		raw, err := oleutil.CallMethod(w.svc, "ExecQuery",
			fmt.Sprintf(`SELECT * FROM Win32_EncryptableVolume WHERE PersistentVolumeID="%s"`, pvid),
		)
		if err != nil {
			return fmt.Errorf("ExecQuery: %w", err)
		}
		result := raw.ToIDispatch()
		defer result.Release()

		itemRaw, err := oleutil.CallMethod(result, "ItemIndex", 0)
		if err != nil {
			return fmt.Errorf("failed to fetch result row while processing BitLocker info: %w", err)
		}
		item := itemRaw.ToIDispatch()
		defer item.Release()

		keys, err := getKeyProtectors(item)
		if err != nil {
			return fmt.Errorf("getKeyProtectors: %w", err)
		}

		for _, k := range keys {
			statusResultRaw, err := oleutil.CallMethod(item, "BackupRecoveryInformationToActiveDirectory", k)
			if err != nil {
				return fmt.Errorf("unable to backup bitlocker information to active directory: %w", err)
			} else if val, ok := statusResultRaw.Value().(int32); val != 0 || !ok {
				return fmt.Errorf("invalid result while backing up bitlocker information to active directory: %v", val)
			}
		}
	}
	return nil
}

// GetBitLockerConversionStatus returns the Bitlocker conversion status for all local drives.
func GetBitLockerConversionStatus() ([]*so.BitLockerConversionStatus, error) {
	comshim.Add(1)
	defer comshim.Done()

	return getBitLockerConversionStatusInternal("")
}

// GetBitLockerConversionStatusForDrive returns the Bitlocker conversion status for a specific drive.
func GetBitLockerConversionStatusForDrive(driveLetter string) (*so.BitLockerConversionStatus, error) {
	comshim.Add(1)
	defer comshim.Done()

	result, err := getBitLockerConversionStatusInternal(" WHERE DriveLetter = '" + driveLetter + "'")
	if err != nil {
		return nil, err
	}

	if len(result) < 1 {
		return nil, fmt.Errorf("error getting BitLocker conversion status, drive not found: %s", driveLetter)
	} else if len(result) > 1 {
		return nil, fmt.Errorf("error getting BitLocker conversion status, too many results: %s", driveLetter)
	} else {
		return result[0], err
	}
}

// GetBitLockerRecoveryInfo returns the Bitlocker device info for all local drives.
func GetBitLockerRecoveryInfo() ([]*so.BitLockerDeviceInfo, error) {
	comshim.Add(1)
	defer comshim.Done()

	return getBitLockerRecoveryInfoInternal("")
}

// GetBitLockerRecoveryInfoForDrive returns the Bitlocker device info for a specific drive.
func GetBitLockerRecoveryInfoForDrive(driveLetter string) (*so.BitLockerDeviceInfo, error) {
	comshim.Add(1)
	defer comshim.Done()

	result, err := getBitLockerRecoveryInfoInternal(" WHERE DriveLetter = '" + driveLetter + "'")
	if err != nil {
		return nil, err
	}

	if len(result) < 1 {
		return nil, fmt.Errorf("error getting BitLocker Recovery Info, drive not found: %s", driveLetter)
	} else if len(result) > 1 {
		return nil, fmt.Errorf("error getting BitLocker Recovery Info, too many results: %s", driveLetter)
	} else {
		return result[0], err
	}
}

type wmi struct {
	intf *ole.IDispatch
	svc  *ole.IDispatch
}

func (w *wmi) Connect() error {
	unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator")
	if err != nil {
		return fmt.Errorf("unable to create initial object, %w", err)
	}
	defer unknown.Release()
	w.intf, err = unknown.QueryInterface(ole.IID_IDispatch)
	if err != nil {
		return fmt.Errorf("unable to create initial object, %w", err)
	}
	serviceRaw, err := oleutil.CallMethod(w.intf, "ConnectServer", nil, `\\.\ROOT\CIMV2\Security\MicrosoftVolumeEncryption`)
	if err != nil {
		return fmt.Errorf("permission denied: %w", err)
	}
	w.svc = serviceRaw.ToIDispatch()
	return nil
}

func (w *wmi) Close() {
	w.svc.Release()
	w.intf.Release()
}

func getBitLockerConversionStatusInternal(where string) ([]*so.BitLockerConversionStatus, error) {
	w := &wmi{}
	if err := w.Connect(); err != nil {
		return nil, fmt.Errorf("wmi.Connect: %w", err)
	}
	defer w.Close()
	raw, err := oleutil.CallMethod(w.svc, "ExecQuery", "SELECT * FROM Win32_EncryptableVolume"+where)
	if err != nil {
		return nil, fmt.Errorf("ExecQuery: %w", err)
	}
	result := raw.ToIDispatch()
	defer result.Release()

	ret := []*so.BitLockerConversionStatus{}

	countVar, err := oleutil.GetProperty(result, "Count")
	if err != nil {
		return nil, fmt.Errorf("unable to get property Count while processing BitLocker info: %w", err)
	}
	count := int(countVar.Val)

	for i := 0; i < count; i++ {
		retData, err := bitlockerConversionStatus(result, i)
		if err != nil {
			return nil, err
		}

		ret = append(ret, retData)
	}

	return ret, nil
}

func getBitLockerRecoveryInfoInternal(where string) ([]*so.BitLockerDeviceInfo, error) {
	w := &wmi{}
	if err := w.Connect(); err != nil {
		return nil, fmt.Errorf("wmi.Connect: %w", err)
	}
	defer w.Close()
	raw, err := oleutil.CallMethod(w.svc, "ExecQuery", "SELECT * FROM Win32_EncryptableVolume"+where)
	if err != nil {
		return nil, fmt.Errorf("ExecQuery: %w", err)
	}
	result := raw.ToIDispatch()
	defer result.Release()

	retBitLocker := []*so.BitLockerDeviceInfo{}

	countVar, err := oleutil.GetProperty(result, "Count")
	if err != nil {
		return nil, fmt.Errorf("unable to get property Count while processing BitLocker info: %w", err)
	}
	count := int(countVar.Val)

	for i := 0; i < count; i++ {
		retData, err := bitlockerRecoveryInfo(result, i)
		if err != nil {
			return nil, err
		}

		retBitLocker = append(retBitLocker, retData)
	}

	return retBitLocker, nil
}

func getKeyProtectors(item *ole.IDispatch) ([]string, error) {
	kp := []string{}
	var keyProtectorResults ole.VARIANT
	ole.VariantInit(&keyProtectorResults)
	keyIDResultRaw, err := oleutil.CallMethod(item, "GetKeyProtectors", 3, &keyProtectorResults)
	if err != nil {
		return nil, fmt.Errorf("Unable to get Key Protectors while getting BitLocker info. %s", err.Error())
	} else if val, ok := keyIDResultRaw.Value().(int32); val != 0 || !ok {
		return nil, fmt.Errorf("Unable to get Key Protectors while getting BitLocker info. Return code %d", val)
	}
	keyProtectorValues := keyProtectorResults.ToArray().ToValueArray()
	for _, keyIDItemRaw := range keyProtectorValues {
		keyIDItem, ok := keyIDItemRaw.(string)
		if !ok {
			return nil, fmt.Errorf("KeyProtectorID wasn't a string...")
		}
		kp = append(kp, keyIDItem)
	}
	return kp, nil
}

func bitlockerConversionStatus(result *ole.IDispatch, i int) (*so.BitLockerConversionStatus, error) {
	itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch result row while processing BitLocker info: %w", err)
	}
	item := itemRaw.ToIDispatch()
	defer item.Release()

	retData := &so.BitLockerConversionStatus{}

	// https://docs.microsoft.com/en-us/windows/win32/secprov/getconversionstatus-win32-encryptablevolume
	var conversionStatus ole.VARIANT
	ole.VariantInit(&conversionStatus)
	var encryptionPercentage ole.VARIANT
	ole.VariantInit(&encryptionPercentage)
	var encryptionFlags ole.VARIANT
	ole.VariantInit(&encryptionFlags)
	var wipingStatus ole.VARIANT
	ole.VariantInit(&wipingStatus)
	var wipingPercentage ole.VARIANT
	ole.VariantInit(&wipingPercentage)
	statusResultRaw, err := oleutil.CallMethod(
		item, "GetConversionStatus",
		&conversionStatus,
		&encryptionPercentage,
		&encryptionFlags,
		&wipingStatus,
		&wipingPercentage,
		0,
	)
	if err != nil {
		return nil, fmt.Errorf("unable to get conversion status while getting BitLocker info: %w", err)
	} else if val, ok := statusResultRaw.Value().(uint32); val != 0 || !ok {
		// The possible return values of GetConversionStatus are S_OK (0x0) and FVE_E_LOCKED_VOLUME (0x80310000)
		// If the return value is not 0, the volume is locked.
		return nil, fmt.Errorf("unable to get conversion status while getting BitLocker info; the volume is locked. Return code %d", val)
	}

	retData.ConversionStatus = conversionStatus.Value().(uint32)
	retData.EncryptionPercentage = encryptionPercentage.Value().(uint32)
	retData.EncryptionFlags = encryptionFlags.Value().(uint32)
	retData.WipingStatus = wipingStatus.Value().(uint32)
	retData.WipingPercentage = wipingPercentage.Value().(uint32)

	return retData, nil
}

func bitlockerRecoveryInfo(result *ole.IDispatch, i int) (*so.BitLockerDeviceInfo, error) {
	itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch result row while processing BitLocker info. %w", err)
	}
	item := itemRaw.ToIDispatch()
	defer item.Release()

	retData := &so.BitLockerDeviceInfo{
		RecoveryKeys: []string{},
	}

	resDeviceID, err := oleutil.GetProperty(item, "DeviceID")
	if err != nil {
		return nil, fmt.Errorf("Error while getting property DeviceID from BitLocker info. %s", err.Error())
	}
	retData.DeviceID = resDeviceID.ToString()

	resPersistentVolumeID, err := oleutil.GetProperty(item, "PersistentVolumeID")
	if err != nil {
		return nil, fmt.Errorf("Error while getting property PersistentVolumeID from BitLocker info. %s", err.Error())
	}
	retData.PersistentVolumeID = resPersistentVolumeID.ToString()

	resDriveLetter, err := oleutil.GetProperty(item, "DriveLetter")
	if err != nil {
		return nil, fmt.Errorf("Error while getting property DriveLetter from BitLocker info. %s", err.Error())
	}
	retData.DriveLetter = resDriveLetter.ToString()

	resProtectionStatus, err := oleutil.GetProperty(item, "ProtectionStatus")
	if err != nil {
		return nil, fmt.Errorf("Error while getting property ProtectionStatus from BitLocker info. %s", err.Error())
	}
	var ok bool
	retData.ProtectionStatus, ok = resProtectionStatus.Value().(uint32)
	if !ok {
		protectionStatus, ok := resProtectionStatus.Value().(int32)
		if !ok {
			return nil, fmt.Errorf("Failed to parse ProtectionStatus from BitLocker info as uint32 or int32")
		}
		retData.ProtectionStatus = uint32(protectionStatus)
	}

	resConversionStatus, err := oleutil.GetProperty(item, "ConversionStatus")
	if err != nil {
		return nil, fmt.Errorf("error while getting property ConversionStatus from BitLocker info: %w", err)
	}
	ok = false
	retData.ConversionStatus, ok = resConversionStatus.Value().(uint32)
	if !ok {
		conversionStatus, ok := resConversionStatus.Value().(int32)
		if !ok {
			return nil, fmt.Errorf("Failed to parse ConversionStatus from BitLocker info as uint32 or int32")
		}
		retData.ConversionStatus = uint32(conversionStatus)
	}
	keys, err := getKeyProtectors(item)
	if err != nil {
		return nil, fmt.Errorf("getKeyProtectors: %w", err)
	}

	for _, k := range keys {
		err = func() error {
			var recoveryKey ole.VARIANT
			ole.VariantInit(&recoveryKey)
			recoveryKeyResultRaw, err := oleutil.CallMethod(item, "GetKeyProtectorNumericalPassword", k, &recoveryKey)
			if err != nil {
				return fmt.Errorf("Unable to get Recovery Key while getting BitLocker info. %s", err.Error())
			} else if val, ok := recoveryKeyResultRaw.Value().(int32); val != 0 || !ok {
				return fmt.Errorf("Unable to get Recovery Key while getting BitLocker info. Return code %d", val)
			}
			retData.RecoveryKeys = append(retData.RecoveryKeys, recoveryKey.ToString())
			return nil
		}()
		if err != nil {
			return nil, err
		}
	}

	return retData, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/csv"
	"errors"
	"github.com/llgcode/draw2d"
	"github.com/llgcode/draw2d/draw2dimg"
	"github.com/llgcode/draw2d/draw2dkit"
	"io"
	"log"
	"math"
	"os"
	"strconv"
	"strings"
	"time"

	"./netpbm"
	"golang.org/x/image/draw"
	"image"
	"image/color"
)

type Point [2]float64

func DegmintoFloat(val string, dir string) float64 {
	p := strings.Index(val, ".")
	if p < 2 {
		return math.NaN()
	}
	deg, err := strconv.ParseFloat(val[0:p-2], 64)
	if err != nil {
		return math.NaN()
	}
	min, err := strconv.ParseFloat(val[p-2:], 64)
	if err != nil {
		return math.NaN()
	}
	deg += min / 60
	if dir[0] == 'S' || dir[0] == 'W' {
		deg = -deg
	}
	return deg
}

func (p *Point) FromGPS(lat, latdir, lon, londir string) error {
	p[0] = DegmintoFloat(lat, latdir)
	p[1] = DegmintoFloat(lon, londir)
	return nil
}

func TimeFromGPS(gtime, date string) time.Time {
	var yr, dy int
	var mo time.Month
	if len(date) > 5 {
		dy, _ = strconv.Atoi(date[0:2])
		month, _ := strconv.Atoi(date[2:4])
		mo = time.Month(month)
		yr, _ = strconv.Atoi(date[4:6])
		yr += 2000
	} else {
		t := time.Now()
		yr = t.Year()
		mo = t.Month()
		dy = t.Day()
	}
	hr, _ := strconv.Atoi(gtime[0:2])
	mn, _ := strconv.Atoi(gtime[2:4])
	sc, _ := strconv.Atoi(gtime[4:6])
	return time.Date(yr, mo, dy, hr, mn, sc, 0, time.UTC)
}

type GPRMC struct {
	Time      time.Time
	Validity  byte
	LatLong   Point
	Speed     float32
	Course    float32
	Variation float32
	EastWest  byte
}

func (g *GPRMC) FromNMEA(s []string) error {
	if s[0] != "$GPRMC" {
		return errors.New("Record is not a GPRMC record")
	}
	if len(s) != 13 {
		return errors.New("Record does not have 13 fields")
	}
	g.Time = TimeFromGPS(s[1], s[9])
	g.Validity = s[2][0]
	g.LatLong.FromGPS(s[3], s[4], s[5], s[6])
	f, _ := strconv.ParseFloat(s[7], 32)
	g.Speed = float32(f)
	f, _ = strconv.ParseFloat(s[8], 32)
	g.Course = float32(f)
	f, _ = strconv.ParseFloat(s[10], 32)
	g.Variation = float32(f)
	if len(s[11]) > 0 {
		g.EastWest = s[10][0]
	}
	return nil
}

func epd_write(i image.Image) {
	cmd, err := os.OpenFile("/dev/epd/command", os.O_RDWR, 0666)
	if err != nil {
		return
	}
	img, err := os.OpenFile("/dev/epd/LE/display", os.O_RDWR, 0666)
	if err != nil {
		return
	}
	cmd.Write([]byte("C"))
	netpbm.PbmData(img, i)
	cmd.Write([]byte("P"))
	img.Close()
	cmd.Close()
}

func show_data(name, value string) image.Image {
	draw2d.SetFontFolder("/usr/share/fonts/truetype/roboto/")
	i := image.NewRGBA(image.Rect(0, 0, 264, 176))
	draw.Src.Draw(i, i.Bounds(), image.White, image.ZP)
	gc := draw2dimg.NewGraphicContext(i)
	gc.Save()
	gc.SetStrokeColor(color.Black)
	gc.SetFillColor(color.Black)
	draw2dkit.Rectangle(gc, 10, 10, 50, 50)
	gc.FillStroke()

	gc.SetFontData(draw2d.FontData{Name: "Roboto", Family: draw2d.FontFamilyMono, Style: draw2d.FontStyleNormal})
	gc.SetFontSize(52)
	gc.FillStringAt(name, 60, 80)
	gc.FillStringAt(value, 60, 160)

	gc.Restore()
	return i
}

func show_speed() {
	f, _ := os.Open("/dev/ttyUSB0")
	r := csv.NewReader(f)
	r.FieldsPerRecord = -1
	for {
		rec, err := r.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal(err)
		}
		if rec[0] == "$GPRMC" {
			var g GPRMC
			g.FromNMEA(rec)
			img := show_data("Speed", strconv.FormatFloat(float64(g.Speed), 'f', -1, 32))
			epd_write(img)
			break
		}
	}
	f.Close()
}

func main() {
	for {
		show_speed()
		time.Sleep(1 * time.Minute)
	}
}
<commit_msg>simpler font.<commit_after>package main

import (
	"encoding/csv"
	"errors"
	"io"
	"log"
	"math"
	"os"
	"strconv"
	"strings"
	"time"

	"./netpbm"
	"golang.org/x/image/draw"
	"golang.org/x/image/font"
	"golang.org/x/image/font/gofont/gomedium"
	"github.com/golang/freetype/truetype"
	"golang.org/x/image/math/fixed"

	"image"
	"image/color"
)

type Point [2]float64

func DegmintoFloat(val string, dir string) float64 {
	p := strings.Index(val, ".")
	if p < 2 {
		return math.NaN()
	}
	deg, err := strconv.ParseFloat(val[0:p-2], 64)
	if err != nil {
		return math.NaN()
	}
	min, err := strconv.ParseFloat(val[p-2:], 64)
	if err != nil {
		return math.NaN()
	}
	deg += min / 60
	if dir[0] == 'S' || dir[0] == 'W' {
		deg = -deg
	}
	return deg
}

func (p *Point) FromGPS(lat, latdir, lon, londir string) error {
	p[0] = DegmintoFloat(lat, latdir)
	p[1] = DegmintoFloat(lon, londir)
	return nil
}

func TimeFromGPS(gtime, date string) time.Time {
	var yr, dy int
	var mo time.Month
	if len(date) > 5 {
		dy, _ = strconv.Atoi(date[0:2])
		month, _ := strconv.Atoi(date[2:4])
		mo = time.Month(month)
		yr, _ = strconv.Atoi(date[4:6])
		yr += 2000
	} else {
		t := time.Now()
		yr = t.Year()
		mo = t.Month()
		dy = t.Day()
	}
	hr, _ := strconv.Atoi(gtime[0:2])
	mn, _ := strconv.Atoi(gtime[2:4])
	sc, _ := strconv.Atoi(gtime[4:6])
	return time.Date(yr, mo, dy, hr, mn, sc, 0, time.UTC)
}

type GPRMC struct {
	Time      time.Time
	Validity  byte
	LatLong   Point
	Speed     float32
	Course    float32
	Variation float32
	EastWest  byte
}

func (g *GPRMC) FromNMEA(s []string) error {
	if s[0] != "$GPRMC" {
		return errors.New("Record is not a GPRMC record")
	}
	if len(s) != 13 {
		return errors.New("Record does not have 13 fields")
	}
	g.Time = TimeFromGPS(s[1], s[9])
	g.Validity = s[2][0]
	g.LatLong.FromGPS(s[3], s[4], s[5], s[6])
	f, _ := strconv.ParseFloat(s[7], 32)
	g.Speed = float32(f)
	f, _ = strconv.ParseFloat(s[8], 32)
	g.Course = float32(f)
	f, _ = strconv.ParseFloat(s[10], 32)
	g.Variation = float32(f)
	if len(s[11]) > 0 {
		g.EastWest = s[10][0]
	}
	return nil
}

func epd_write(i image.Image) {
	cmd, err := os.OpenFile("/dev/epd/command", os.O_RDWR, 0666)
	if err != nil {
		return
	}
	img, err := os.OpenFile("/dev/epd/LE/display", os.O_RDWR, 0666)
	if err != nil {
		return
	}
	cmd.Write([]byte("C"))
	netpbm.PbmData(img, i)
	cmd.Write([]byte("P"))
	img.Close()
	cmd.Close()
}

func show_data(name, value string) image.Image {
	i := image.NewRGBA(image.Rect(0, 0, 264, 176))
	draw.Src.Draw(i, i.Bounds(), image.White, image.ZP)
	tt, err := truetype.Parse(gomedium.TTF)
	face := truetype.NewFace(tt, &truetype.Options{
			Size:    52,
			Hinting: font.HintingNone,
			DPI:     72,
		})

	d := &font.Drawer{
		Dst: i,
		Src: image.Black,
		Face: face,
	}
	d.Dot = fixed.P(60,80)
	d.DrawString(name)
	d.Dot = fixed.P(60,160)
	d.DrawString(value)

	return i
}

func show_speed() {
	f, _ := os.Open("/dev/ttyUSB0")
	r := csv.NewReader(f)
	r.FieldsPerRecord = -1
	for {
		rec, err := r.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal(err)
		}
		if rec[0] == "$GPRMC" {
			var g GPRMC
			g.FromNMEA(rec)
			img := show_data("Speed", strconv.FormatFloat(float64(g.Speed), 'f', -1, 32))
			epd_write(img)
			break
		}
	}
	f.Close()
}

func main() {
	for {
		show_speed()
		time.Sleep(1 * time.Minute)
	}
}
<|endoftext|>


<commit_before>package middleware

import (
	"bytes"
	"context"
	"log"
	"net/http"
	"os"
	"time"
)

var (
	LogEntryCtxKey = &contextKey{"LogEntry"}

	DefaultLogger = RequestLogger(&DefaultLogFormatter{logger: log.New(os.Stdout, "", log.LstdFlags)})
)

// Logger is a middleware that logs the start and end of each request, along
// with some useful data about what was requested, what the response status was,
// and how long it took to return. When standard output is a TTY, Logger will
// print in color, otherwise it will print in black and white.
//
// Logger prints a request ID if one is provided.
func Logger(next http.Handler) http.Handler {
	return DefaultLogger(next)
}

func RequestLogger(f LogFormatter) func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			entry := f.NewLogEntry(r)
			ww := NewWrapResponseWriter(w, r.ProtoMajor)

			t1 := time.Now()
			defer func() {
				t2 := time.Now()
				entry.Write(ww.Status(), ww.BytesWritten(), t2.Sub(t1))
			}()

			next.ServeHTTP(ww, WithLogEntry(r, entry))
		}
		return http.HandlerFunc(fn)
	}
}

type LogFormatter interface {
	NewLogEntry(r *http.Request) LogEntry
}

type LogEntry interface {
	Write(status, bytes int, elapsed time.Duration)
	Panic(v interface{}, stack []byte)
}

func GetLogEntry(r *http.Request) LogEntry {
	entry, _ := r.Context().Value(LogEntryCtxKey).(LogEntry)
	return entry
}

func WithLogEntry(r *http.Request, entry LogEntry) *http.Request {
	r = r.WithContext(context.WithValue(r.Context(), LogEntryCtxKey, entry))
	return r
}

type DefaultLogFormatter struct {
	logger *log.Logger
}

func (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry {
	entry := &defaultLogEntry{
		DefaultLogFormatter: l,
		request:             r,
		buf:                 &bytes.Buffer{},
	}

	reqID := GetReqID(r.Context())
	if reqID != "" {
		cW(entry.buf, nYellow, "[%s] ", reqID)
	}
	cW(entry.buf, nCyan, "\"")
	cW(entry.buf, bMagenta, "%s ", r.Method)

	scheme := "http"
	if r.TLS != nil {
		scheme = "https"
	}
	cW(entry.buf, nCyan, "%s://%s%s %s\" ", scheme, r.Host, r.RequestURI, r.Proto)

	entry.buf.WriteString("from ")
	entry.buf.WriteString(r.RemoteAddr)
	entry.buf.WriteString(" - ")

	return entry
}

type defaultLogEntry struct {
	*DefaultLogFormatter
	request *http.Request
	buf     *bytes.Buffer
}

func (l *defaultLogEntry) Write(status, bytes int, elapsed time.Duration) {
	if status == StatusClientClosedRequest {
		cW(l.buf, bRed, "[disconnected]")
	} else {
		switch {
		case status < 200:
			cW(l.buf, bBlue, "%03d", status)
		case status < 300:
			cW(l.buf, bGreen, "%03d", status)
		case status < 400:
			cW(l.buf, bCyan, "%03d", status)
		case status < 500:
			cW(l.buf, bYellow, "%03d", status)
		default:
			cW(l.buf, bRed, "%03d", status)
		}
	}

	cW(l.buf, bBlue, " %dB", bytes)

	l.buf.WriteString(" in ")
	if elapsed < 500*time.Millisecond {
		cW(l.buf, nGreen, "%s", elapsed)
	} else if elapsed < 5*time.Second {
		cW(l.buf, nYellow, "%s", elapsed)
	} else {
		cW(l.buf, nRed, "%s", elapsed)
	}

	l.logger.Print(l.buf.String())
}

func (l *defaultLogEntry) Panic(v interface{}, stack []byte) {
	panicEntry := l.NewLogEntry(l.request).(*defaultLogEntry)
	cW(panicEntry.buf, bRed, "panic: %+v", v)
	l.logger.Print(panicEntry.buf.String())
	l.logger.Print(string(stack))
}
<commit_msg>Export logger field on DefaultLogFormatter, for ability to change pkg level default logger format<commit_after>package middleware

import (
	"bytes"
	"context"
	"log"
	"net/http"
	"os"
	"time"
)

var (
	LogEntryCtxKey = &contextKey{"LogEntry"}

	DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags)})
)

// Logger is a middleware that logs the start and end of each request, along
// with some useful data about what was requested, what the response status was,
// and how long it took to return. When standard output is a TTY, Logger will
// print in color, otherwise it will print in black and white.
//
// Logger prints a request ID if one is provided.
func Logger(next http.Handler) http.Handler {
	return DefaultLogger(next)
}

func RequestLogger(f LogFormatter) func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			entry := f.NewLogEntry(r)
			ww := NewWrapResponseWriter(w, r.ProtoMajor)

			t1 := time.Now()
			defer func() {
				t2 := time.Now()
				entry.Write(ww.Status(), ww.BytesWritten(), t2.Sub(t1))
			}()

			next.ServeHTTP(ww, WithLogEntry(r, entry))
		}
		return http.HandlerFunc(fn)
	}
}

type LogFormatter interface {
	NewLogEntry(r *http.Request) LogEntry
}

type LogEntry interface {
	Write(status, bytes int, elapsed time.Duration)
	Panic(v interface{}, stack []byte)
}

func GetLogEntry(r *http.Request) LogEntry {
	entry, _ := r.Context().Value(LogEntryCtxKey).(LogEntry)
	return entry
}

func WithLogEntry(r *http.Request, entry LogEntry) *http.Request {
	r = r.WithContext(context.WithValue(r.Context(), LogEntryCtxKey, entry))
	return r
}

type DefaultLogFormatter struct {
	Logger *log.Logger
}

func (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry {
	entry := &defaultLogEntry{
		DefaultLogFormatter: l,
		request:             r,
		buf:                 &bytes.Buffer{},
	}

	reqID := GetReqID(r.Context())
	if reqID != "" {
		cW(entry.buf, nYellow, "[%s] ", reqID)
	}
	cW(entry.buf, nCyan, "\"")
	cW(entry.buf, bMagenta, "%s ", r.Method)

	scheme := "http"
	if r.TLS != nil {
		scheme = "https"
	}
	cW(entry.buf, nCyan, "%s://%s%s %s\" ", scheme, r.Host, r.RequestURI, r.Proto)

	entry.buf.WriteString("from ")
	entry.buf.WriteString(r.RemoteAddr)
	entry.buf.WriteString(" - ")

	return entry
}

type defaultLogEntry struct {
	*DefaultLogFormatter
	request *http.Request
	buf     *bytes.Buffer
}

func (l *defaultLogEntry) Write(status, bytes int, elapsed time.Duration) {
	if status == StatusClientClosedRequest {
		cW(l.buf, bRed, "[disconnected]")
	} else {
		switch {
		case status < 200:
			cW(l.buf, bBlue, "%03d", status)
		case status < 300:
			cW(l.buf, bGreen, "%03d", status)
		case status < 400:
			cW(l.buf, bCyan, "%03d", status)
		case status < 500:
			cW(l.buf, bYellow, "%03d", status)
		default:
			cW(l.buf, bRed, "%03d", status)
		}
	}

	cW(l.buf, bBlue, " %dB", bytes)

	l.buf.WriteString(" in ")
	if elapsed < 500*time.Millisecond {
		cW(l.buf, nGreen, "%s", elapsed)
	} else if elapsed < 5*time.Second {
		cW(l.buf, nYellow, "%s", elapsed)
	} else {
		cW(l.buf, nRed, "%s", elapsed)
	}

	l.Logger.Print(l.buf.String())
}

func (l *defaultLogEntry) Panic(v interface{}, stack []byte) {
	panicEntry := l.NewLogEntry(l.request).(*defaultLogEntry)
	cW(panicEntry.buf, bRed, "panic: %+v", v)
	l.Logger.Print(panicEntry.buf.String())
	l.Logger.Print(string(stack))
}
<|endoftext|>


<commit_before>package agent

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/buildkite/agent/api"
	"github.com/buildkite/agent/logger"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/googleapi"
	storage "google.golang.org/api/storage/v1"
)

type GSUploaderConfig struct {
	// The destination which includes the GS bucket name and the path.
	// gs://my-bucket-name/foo/bar
	Destination string

	// Whether or not HTTP calls shoud be debugged
	DebugHTTP bool
}

type GSUploader struct {
	// The gs bucket path set from the destination
	BucketPath string

	// The gs bucket name set from the destination
	BucketName string

	// The configuration
	conf GSUploaderConfig

	// The logger instance to use
	logger logger.Logger

	// The GS service
	service *storage.Service
}

func NewGSUploader(l logger.Logger, c GSUploaderConfig) (*GSUploader, error) {
	client, err := newGoogleClient(storage.DevstorageFullControlScope)
	if err != nil {
		return nil, errors.New(fmt.Sprintf("Error creating Google Cloud Storage client: %v", err))
	}
	service, err := storage.New(client)
	if err != nil {
		return nil, err
	}
	bucketName, bucketPath := ParseGSDestination(c.Destination)
	return &GSUploader{
		BucketPath: bucketPath,
		BucketName: bucketName,
		conf:       c,
		logger:     l,
		service:    service,
	}, nil
}

func ParseGSDestination(destination string) (name string, path string) {
	parts := strings.Split(strings.TrimPrefix(string(destination), "gs://"), "/")
	path = strings.Join(parts[1:len(parts)], "/")
	name = parts[0]
	return
}

func newGoogleClient(scope string) (*http.Client, error) {
	if os.Getenv("BUILDKITE_GS_APPLICATION_CREDENTIALS") != "" {
		data, err := ioutil.ReadFile(os.Getenv("BUILDKITE_GS_APPLICATION_CREDENTIALS"))
		if err != nil {
			return nil, err
		}
		conf, err := google.JWTConfigFromJSON(data, scope)
		if err != nil {
			return nil, err
		}
		return conf.Client(oauth2.NoContext), nil
	}
	return google.DefaultClient(context.Background(), scope)
}

func (u *GSUploader) URL(artifact *api.Artifact) string {
	host := "storage.googleapis.com"
	if os.Getenv("BUILDKITE_GCS_ACCESS_HOST") != "" {
		host = os.Getenv("BUILDKITE_GCS_ACCESS_HOST")
	}

	var artifactURL = &url.URL{
		Scheme: "https",
		Host:   host,
		Path:   u.BucketName + "/" + u.artifactPath(artifact),
	}
	return artifactURL.String()
}

func (u *GSUploader) Upload(artifact *api.Artifact) error {
	permission := os.Getenv("BUILDKITE_GS_ACL")

	// The dirtiest validation method ever...
	if permission != "" &&
		permission != "authenticatedRead" &&
		permission != "private" &&
		permission != "projectPrivate" &&
		permission != "publicRead" &&
		permission != "publicReadWrite" {
		return fmt.Errorf("Invalid GS ACL `%s`", permission)
	}

	if permission == "" {
		u.logger.Debug("Uploading \"%s\" to bucket \"%s\" with default permission",
			u.artifactPath(artifact), u.BucketName)
	} else {
		u.logger.Debug("Uploading \"%s\" to bucket \"%s\" with permission \"%s\"",
			u.artifactPath(artifact), u.BucketName, permission)
	}
	object := &storage.Object{
		Name:               u.artifactPath(artifact),
		ContentType:        artifact.ContentType,
		ContentDisposition: u.contentDisposition(artifact),
	}
	file, err := os.Open(artifact.AbsolutePath)
	if err != nil {
		return errors.New(fmt.Sprintf("Failed to open file \"%q\" (%v)", artifact.AbsolutePath, err))
	}
	call := u.service.Objects.Insert(u.BucketName, object)
	if permission != "" {
		call = call.PredefinedAcl(permission)
	}
	if res, err := call.Media(file, googleapi.ContentType("")).Do(); err == nil {
		u.logger.Debug("Created object %v at location %v\n\n", res.Name, res.SelfLink)
	} else {
		return errors.New(fmt.Sprintf("Failed to PUT file \"%s\" (%v)", u.artifactPath(artifact), err))
	}

	return nil
}

func (u *GSUploader) artifactPath(artifact *api.Artifact) string {
	parts := []string{u.BucketPath, artifact.Path}

	return strings.Join(parts, "/")
}

func (u *GSUploader) contentDisposition(a *api.Artifact) string {
	return fmt.Sprintf("inline; filename=\"%s\"", filepath.Base(a.Path))
}
<commit_msg>Fix assumption that custom GCS hosts will maintain the same path format as GCS.<commit_after>package agent

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/buildkite/agent/api"
	"github.com/buildkite/agent/logger"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/googleapi"
	storage "google.golang.org/api/storage/v1"
)

type GSUploaderConfig struct {
	// The destination which includes the GS bucket name and the path.
	// gs://my-bucket-name/foo/bar
	Destination string

	// Whether or not HTTP calls shoud be debugged
	DebugHTTP bool
}

type GSUploader struct {
	// The gs bucket path set from the destination
	BucketPath string

	// The gs bucket name set from the destination
	BucketName string

	// The configuration
	conf GSUploaderConfig

	// The logger instance to use
	logger logger.Logger

	// The GS service
	service *storage.Service
}

func NewGSUploader(l logger.Logger, c GSUploaderConfig) (*GSUploader, error) {
	client, err := newGoogleClient(storage.DevstorageFullControlScope)
	if err != nil {
		return nil, errors.New(fmt.Sprintf("Error creating Google Cloud Storage client: %v", err))
	}
	service, err := storage.New(client)
	if err != nil {
		return nil, err
	}
	bucketName, bucketPath := ParseGSDestination(c.Destination)
	return &GSUploader{
		BucketPath: bucketPath,
		BucketName: bucketName,
		conf:       c,
		logger:     l,
		service:    service,
	}, nil
}

func ParseGSDestination(destination string) (name string, path string) {
	parts := strings.Split(strings.TrimPrefix(string(destination), "gs://"), "/")
	path = strings.Join(parts[1:len(parts)], "/")
	name = parts[0]
	return
}

func newGoogleClient(scope string) (*http.Client, error) {
	if os.Getenv("BUILDKITE_GS_APPLICATION_CREDENTIALS") != "" {
		data, err := ioutil.ReadFile(os.Getenv("BUILDKITE_GS_APPLICATION_CREDENTIALS"))
		if err != nil {
			return nil, err
		}
		conf, err := google.JWTConfigFromJSON(data, scope)
		if err != nil {
			return nil, err
		}
		return conf.Client(oauth2.NoContext), nil
	}
	return google.DefaultClient(context.Background(), scope)
}

func (u *GSUploader) URL(artifact *api.Artifact) string {
	host := "storage.googleapis.com"
	path := fmt.Sprintf("%s/%s", u.BucketName, u.artifactPath(artifact))

	// When overriding the default GCS host to go through a proxy, we should use the path passed in directly - it's not safe to assume that a proxy's URL path follows the same path format as GCS.
	if os.Getenv("BUILDKITE_GCS_ACCESS_HOST") != "" {
		host = os.Getenv("BUILDKITE_GCS_ACCESS_HOST")
		path = u.artifactPath(artifact)
	}

	var artifactURL = &url.URL{
		Scheme: "https",
		Host:   host,
		Path:   path,
	}
	return artifactURL.String()
}

func (u *GSUploader) Upload(artifact *api.Artifact) error {
	permission := os.Getenv("BUILDKITE_GS_ACL")

	// The dirtiest validation method ever...
	if permission != "" &&
		permission != "authenticatedRead" &&
		permission != "private" &&
		permission != "projectPrivate" &&
		permission != "publicRead" &&
		permission != "publicReadWrite" {
		return fmt.Errorf("Invalid GS ACL `%s`", permission)
	}

	if permission == "" {
		u.logger.Debug("Uploading \"%s\" to bucket \"%s\" with default permission",
			u.artifactPath(artifact), u.BucketName)
	} else {
		u.logger.Debug("Uploading \"%s\" to bucket \"%s\" with permission \"%s\"",
			u.artifactPath(artifact), u.BucketName, permission)
	}
	object := &storage.Object{
		Name:               u.artifactPath(artifact),
		ContentType:        artifact.ContentType,
		ContentDisposition: u.contentDisposition(artifact),
	}
	file, err := os.Open(artifact.AbsolutePath)
	if err != nil {
		return errors.New(fmt.Sprintf("Failed to open file \"%q\" (%v)", artifact.AbsolutePath, err))
	}
	call := u.service.Objects.Insert(u.BucketName, object)
	if permission != "" {
		call = call.PredefinedAcl(permission)
	}
	if res, err := call.Media(file, googleapi.ContentType("")).Do(); err == nil {
		u.logger.Debug("Created object %v at location %v\n\n", res.Name, res.SelfLink)
	} else {
		return errors.New(fmt.Sprintf("Failed to PUT file \"%s\" (%v)", u.artifactPath(artifact), err))
	}

	return nil
}

func (u *GSUploader) artifactPath(artifact *api.Artifact) string {
	parts := []string{u.BucketPath, artifact.Path}

	return strings.Join(parts, "/")
}

func (u *GSUploader) contentDisposition(a *api.Artifact) string {
	return fmt.Sprintf("inline; filename=\"%s\"", filepath.Base(a.Path))
}
<|endoftext|>


<commit_before><commit_msg>need to splat the configstring<commit_after><|endoftext|>


<commit_before><commit_msg>updates for tracing<commit_after><|endoftext|>


<commit_before>package main

import (
	"os"
	"strconv"
	"syscall"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

func processExists(pid int) bool {
	proc, _ := os.FindProcess(pid)
	err := proc.Signal(syscall.Signal(0))
	// err could also be "permission denied", but for this test it's good enough
	return err == nil
}

var _ = Describe("Ssh", func() {
	Context("When starting ssh-agent", func() {
		vars := "SSH_AGENT_PID=123; export SSH_AGENT_PID;\nFOO=bar; export FOO;\nANSWER=42\n"
		BeforeEach(func() {
			os.Setenv("SSH_AGENT_PID", "")
			os.Setenv("FOO", "")
			os.Setenv("ANSWER", "")
		})

		It("sets the environment variables ssh-agent prints to STDOUT", func() {
			setEnvironmentVariablesFromString(vars)

			Expect(os.Getenv("SSH_AGENT_PID")).To(Equal("123"))
			Expect(os.Getenv("FOO")).To(Equal("bar"))
			Expect(os.Getenv("ANSWER")).To(Equal("42"))
		})

		It("can start and kill the agent", func() {
			err := startSshAgent()
			Expect(err).To(BeNil())

			Expect(os.Getenv("SSH_AGENT_PID")).ToNot(BeEmpty())

			pid, err := strconv.Atoi(os.Getenv("SSH_AGENT_PID"))
			Expect(err).To(BeNil())

			err = killSshAgent()
			Expect(err).To(BeNil())

			Eventually(func() bool {
				return processExists(pid)
			}).Should(BeFalse())
		})
	})
})
<commit_msg>added tests for  function<commit_after>package main

import (
	"os"
	"path"
	"strconv"
	"syscall"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

func processExists(pid int) bool {
	proc, _ := os.FindProcess(pid)
	err := proc.Signal(syscall.Signal(0))
	// err could also be "permission denied", but for this test it's good enough
	return err == nil
}

var _ = Describe("Ssh", func() {
	Context("When starting ssh-agent", func() {
		vars := "SSH_AGENT_PID=123; export SSH_AGENT_PID;\nFOO=bar; export FOO;\nANSWER=42\n"
		BeforeEach(func() {
			os.Setenv("SSH_AGENT_PID", "")
			os.Setenv("FOO", "")
			os.Setenv("ANSWER", "")
		})

		It("sets the environment variables ssh-agent prints to STDOUT", func() {
			setEnvironmentVariablesFromString(vars)

			Expect(os.Getenv("SSH_AGENT_PID")).To(Equal("123"))
			Expect(os.Getenv("FOO")).To(Equal("bar"))
			Expect(os.Getenv("ANSWER")).To(Equal("42"))
		})

		It("can start and kill the agent", func() {
			err := startSshAgent()
			Expect(err).To(BeNil())

			Expect(os.Getenv("SSH_AGENT_PID")).ToNot(BeEmpty())

			pid, err := strconv.Atoi(os.Getenv("SSH_AGENT_PID"))
			Expect(err).To(BeNil())

			err = killSshAgent()
			Expect(err).To(BeNil())

			Eventually(func() bool {
				return processExists(pid)
			}).Should(BeFalse())
		})
	})

	homeDir := "/tmp"
	sshDir := path.Join(homeDir, ".ssh")
	sshClientConfigFile := path.Join(homeDir, ".ssh/config")

	Context("When configuring ssh", func() {
		It("can crete the directory", func() {

			// ensure the directory doesn't exists
			_, sshDirErr := os.Stat(sshDir)
			Expect(sshDirErr).NotTo(BeNil())

			err := mkSSHDir(sshClientConfigFile)
			Expect(err).To(BeNil())

			// check that the directory now exists
			_, resultErr := os.Stat(sshDir)
			Expect(resultErr).To(BeNil())

			// cleanup
			err = os.RemoveAll(sshDir)
			Expect(err).To(BeNil())
		})
		It("can ignores when the directory already exists", func() {

			// make sure the directory already exists
			sshDirErr := os.MkdirAll(sshDir, 0700)
			Expect(sshDirErr).To(BeNil())

			err := mkSSHDir(sshClientConfigFile)
			Expect(err).To(BeNil())

			// cleanup
			err = os.RemoveAll(sshDir)
			Expect(err).To(BeNil())
		})

	})
})
<|endoftext|>


<commit_before>package util

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/freneticmonkey/migrate/migrate/config"
)

// WorkingPathAbs This is determined using the current working directory and
// the value of Config.Options.WorkingPath
var WorkingPathAbs string

func Config(conf config.Config) {

	// Make path absolute
	cwd, err := os.Getwd()
	ErrorCheck(err)

	WorkingPathAbs = filepath.Join(cwd, conf.Options.WorkingPath)
}

func WorkingSubDir(subDir string) string {
	return filepath.Join(WorkingPathAbs, subDir)
}

// Recursively check the path for typetype files and add them to the
// schema list as we're going

func ReadDirRelative(path string, fileType string, files *[]string) (err error) {

	// Make path absolute
	cwd, err := os.Getwd()
	ErrorCheck(err)

	path = filepath.Join(cwd, path)

	return ReadDirAbsolute(path, fileType, files)
}

func ReadDirAbsolute(path string, fileType string, files *[]string) (err error) {

	// Basic path check
	if _, err = os.Stat(path); os.IsNotExist(err) {
		// This path is busted
		return err
	}

	dirInfo, err := ioutil.ReadDir(path)
	ErrorCheck(err)

	for _, fileinfo := range dirInfo {
		path := filepath.Join(path, fileinfo.Name())
		if fileinfo.IsDir() {
			ReadDirAbsolute(path, fileType, files)
		} else {
			// Check if the file is a YAML file
			if strings.ToLower(filepath.Ext(path)) == "."+fileType {
				*files = append(*files, path)
			}
		}
	}

	return err
}

// cleanUp is a helper function which empties the target folder
func CleanPath(path string) (err error) {

	// Ensure that the path is within the working folder
	var rel string
	rel, err = filepath.Rel(WorkingPathAbs, path)
	if strings.HasPrefix(rel, "..") {
		return errors.New("Cannot clean paths outside of the working directory: Path: " + rel)
	}

	wp := fmt.Sprintf("%s/*", path)

	LogWarn("Cleaning Path: " + wp)
	os.RemoveAll(wp)

	return err
}

func RecreateFolder(path string) (err error) {

	return err
}
<commit_msg>Fixed folder cleanup to actually remove the folders<commit_after>package util

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/freneticmonkey/migrate/migrate/config"
)

// WorkingPathAbs This is determined using the current working directory and
// the value of Config.Options.WorkingPath
var WorkingPathAbs string

func Config(conf config.Config) {

	// Make path absolute
	cwd, err := os.Getwd()
	ErrorCheck(err)

	WorkingPathAbs = filepath.Join(cwd, conf.Options.WorkingPath)
}

func WorkingSubDir(subDir string) string {
	return filepath.Join(WorkingPathAbs, subDir)
}

// Recursively check the path for typetype files and add them to the
// schema list as we're going

func ReadDirRelative(path string, fileType string, files *[]string) (err error) {

	// Make path absolute
	cwd, err := os.Getwd()
	ErrorCheck(err)

	path = filepath.Join(cwd, path)

	return ReadDirAbsolute(path, fileType, files)
}

func ReadDirAbsolute(path string, fileType string, files *[]string) (err error) {

	// Basic path check
	if _, err = os.Stat(path); os.IsNotExist(err) {
		// This path is busted
		return err
	}

	dirInfo, err := ioutil.ReadDir(path)
	ErrorCheck(err)

	for _, fileinfo := range dirInfo {
		path := filepath.Join(path, fileinfo.Name())
		if fileinfo.IsDir() {
			ReadDirAbsolute(path, fileType, files)
		} else {
			// Check if the file is a YAML file
			if strings.ToLower(filepath.Ext(path)) == "."+fileType {
				*files = append(*files, path)
			}
		}
	}

	return err
}

// cleanUp is a helper function which empties the target folder
func CleanPath(path string) (err error) {

	// Ensure that the path is within the working folder
	var rel string
	rel, err = filepath.Rel(WorkingPathAbs, path)
	if strings.HasPrefix(rel, "..") {
		return errors.New("Cannot clean paths outside of the working directory: Path: " + rel)
	}
	LogWarn("Cleaning Path: " + path)
	os.RemoveAll(path)

	return err
}

func RecreateFolder(path string) (err error) {

	return err
}
<|endoftext|>