<commit_before>package main

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"os/user"
	"path/filepath"
	"strings"
	"time"

	"github.com/PuerkitoBio/goquery"
	"github.com/bgentry/speakeasy"
	"github.com/bitly/go-simplejson"
)

type ikaClient http.Client

type stage struct {
	Name  string `json:"name"`
	Image string `json:"asset_path"`
}

type regulation struct {
	Regular []stage
	Gachi   []stage
}

type schedule struct {
	TimeBegin time.Time  `json:"datetime_begin"`
	TimeEnd   time.Time  `json:"datetime_end"`
	Stages    regulation `json:"stages"`
	GachiRule string     `json:"gachi_rule"`
}

type stageInfo struct {
	Festival  bool       `json:"festival"`
	Schedules []schedule `json:"schedule"`
}

const (
	splatoonCookieName = "_wag_session"
	splatoonDomainURL  = "https://splatoon.nintendo.net/"

	splatoonClientID = "12af3d0a3a1f441eb900411bb50a835a"
	splatoonOauthURL = "https://splatoon.nintendo.net/users/auth/nintendo"
	nintendoOauthURL = "https://id.nintendo.net/oauth/authorize"

	splatoonScheduleAPI = "https://splatoon.nintendo.net/schedule/index.json"
)

func checkJSONError(data []byte) error {
	js, err := simplejson.NewJson(data)
	if err != nil {
		return err
	}

	if info := js.Get("error").MustString(); len(info) != 0 {
		return errors.New(info)
	}
	return nil
}

func decodeJSONSchedule(data []byte) (*stageInfo, error) {
	info := &stageInfo{}
	if err := json.Unmarshal(data, info); err != nil {
		return nil, err
	}
	return info, nil
}

func getSessionFromCookie(resp *http.Response) string {
	for _, cookie := range resp.Cookies() {
		if cookie.Name == "_wag_session" {
			return cookie.Value
		}
	}
	return ""
}

func getOauthQuery(oarthURL string, id string, password string) (url.Values, error) {
	doc, err := goquery.NewDocument(oarthURL)

	if err != nil {
		return nil, err
	}

	query := url.Values{}
	doc.Find("input").Each(func(_ int, s *goquery.Selection) {
		name, ok := s.Attr("name")
		if !ok {
			return
		}

		// parse from docment
		switch name {
		case "cliend_id":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		case "state":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		case "redirect_uri":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		case "response_type":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		}
	})

	// fixed value
	query.Add("client_id", splatoonClientID)
	query.Add("nintendo_authenticate", "")
	query.Add("nintendo_authorize", "")
	query.Add("scope", "")
	query.Add("lang", "ja-JP")

	// user info
	query.Add("username", id)
	query.Add("password", password)

	return query, nil
}

func createClient() (*ikaClient, error) {
	jar, err := cookiejar.New(nil)
	if err != nil {
		return nil, err
	}
	client := &http.Client{Jar: jar}
	return (*ikaClient)(client), nil
}

func (c *ikaClient) setSession(session string) {
	uri, _ := url.Parse(splatoonDomainURL)
	(*http.Client)(c).Jar.SetCookies(uri, []*http.Cookie{
		&http.Cookie{
			Secure:   true,
			HttpOnly: true,
			Name:     splatoonCookieName,
			Value:    session,
		}})
}

func (c *ikaClient) login(name string, password string) (string, error) {
	query, err := getOauthQuery(splatoonOauthURL, name, password)
	if err != nil {
		return "", err
	}

	resp, err := (*http.Client)(c).PostForm(nintendoOauthURL, query)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return "", errors.New(resp.Status)
	}

	session := getSessionFromCookie(resp)

	return session, nil
}

func (c *ikaClient) getStageInfo() (*stageInfo, error) {

	resp, err := (*http.Client)(c).Get(splatoonScheduleAPI)

	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if err = checkJSONError(body); err != nil {
		return nil, err
	}

	return decodeJSONSchedule(body)
}

func getCacheFile() (string, error) {
	me, err := user.Current()
	if err != nil {
		return "", err
	}
	return filepath.Join(me.HomeDir, ".ikaring.session"), nil
}

func readSession(path string) (string, error) {
	buff, err := ioutil.ReadFile(path)
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(buff)), nil
}

func writeSession(path string, session string) error {
	return ioutil.WriteFile(path, []byte(session), 600)
}

func getAccount(r io.Reader) (string, string, error) {
	scanner := bufio.NewScanner(r)
	for {
		fmt.Print("User: ")
		if scanner.Scan() {
			break
		}
	}
	username := scanner.Text()
	password, err := speakeasy.Ask("Password: ")
	return username, password, err
}

func (s schedule) String() string {
	timefmt := "01/02 15:04:05"
	str := fmt.Sprintf("%s - %s\n",
		s.TimeBegin.Format(timefmt), s.TimeEnd.Format(timefmt))

	str += "レギュラーマッチ\n"
	for i, stage := range s.Stages.Regular {
		if i == 0 {
			str += "\t"
		} else {
			str += ", "
		}
		str += stage.Name
	}
	str += "\n"

	str += fmt.Sprintf("ガチマッチ (%s)\n", s.GachiRule)
	for i, stage := range s.Stages.Gachi {
		if i == 0 {
			str += "\t"
		} else {
			str += ", "
		}
		str += stage.Name
	}
	str += "\n"
	return str
}

func main() {
	client, err := createClient()
	if err != nil {
		fmt.Println(err)
		return
	}

	path, err := getCacheFile()
	if err != nil {
		fmt.Println(err)
		return
	}

	session, err := readSession(path)
	if err == nil && len(session) > 0 {
		client.setSession(session)
	} else {
		username, password, err := getAccount(os.Stdin)
		session, err = client.login(username, password)
		if err != nil {
			fmt.Println(err)
			return
		}
	}

	if len(session) <= 0 {
		fmt.Println("ログインできませんでした")
		return
	}

	writeSession(path, session)

	info, err := client.getStageInfo()
	if err != nil {
		fmt.Println(err)
		return
	}

	for _, s := range info.Schedules {
		fmt.Printf("%v\n", s)
	}
}
<commit_msg>fix typo<commit_after>package main

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"os/user"
	"path/filepath"
	"strings"
	"time"

	"github.com/PuerkitoBio/goquery"
	"github.com/bgentry/speakeasy"
	"github.com/bitly/go-simplejson"
)

type ikaClient http.Client

type stage struct {
	Name  string `json:"name"`
	Image string `json:"asset_path"`
}

type regulation struct {
	Regular []stage
	Gachi   []stage
}

type schedule struct {
	TimeBegin time.Time  `json:"datetime_begin"`
	TimeEnd   time.Time  `json:"datetime_end"`
	Stages    regulation `json:"stages"`
	GachiRule string     `json:"gachi_rule"`
}

type stageInfo struct {
	Festival  bool       `json:"festival"`
	Schedules []schedule `json:"schedule"`
}

const (
	splatoonCookieName = "_wag_session"
	splatoonDomainURL  = "https://splatoon.nintendo.net/"

	splatoonOauthURL = "https://splatoon.nintendo.net/users/auth/nintendo"
	nintendoOauthURL = "https://id.nintendo.net/oauth/authorize"

	splatoonScheduleAPI = "https://splatoon.nintendo.net/schedule/index.json"
)

func checkJSONError(data []byte) error {
	js, err := simplejson.NewJson(data)
	if err != nil {
		return err
	}

	if info := js.Get("error").MustString(); len(info) != 0 {
		return errors.New(info)
	}
	return nil
}

func decodeJSONSchedule(data []byte) (*stageInfo, error) {
	info := &stageInfo{}
	if err := json.Unmarshal(data, info); err != nil {
		return nil, err
	}
	return info, nil
}

func getSessionFromCookie(resp *http.Response) string {
	for _, cookie := range resp.Cookies() {
		if cookie.Name == "_wag_session" {
			return cookie.Value
		}
	}
	return ""
}

func getOauthQuery(oarthURL string, id string, password string) (url.Values, error) {
	doc, err := goquery.NewDocument(oarthURL)

	if err != nil {
		return nil, err
	}

	query := url.Values{}
	doc.Find("input").Each(func(_ int, s *goquery.Selection) {
		name, ok := s.Attr("name")
		if !ok {
			return
		}

		// parse from docment
		switch name {
		case "client_id":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		case "state":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		case "redirect_uri":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		case "response_type":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		}
	})

	// fixed value
	query.Add("nintendo_authenticate", "")
	query.Add("nintendo_authorize", "")
	query.Add("scope", "")
	query.Add("lang", "ja-JP")

	// user info
	query.Add("username", id)
	query.Add("password", password)

	return query, nil
}

func createClient() (*ikaClient, error) {
	jar, err := cookiejar.New(nil)
	if err != nil {
		return nil, err
	}
	client := &http.Client{Jar: jar}
	return (*ikaClient)(client), nil
}

func (c *ikaClient) setSession(session string) {
	uri, _ := url.Parse(splatoonDomainURL)
	(*http.Client)(c).Jar.SetCookies(uri, []*http.Cookie{
		&http.Cookie{
			Secure:   true,
			HttpOnly: true,
			Name:     splatoonCookieName,
			Value:    session,
		}})
}

func (c *ikaClient) login(name string, password string) (string, error) {
	query, err := getOauthQuery(splatoonOauthURL, name, password)
	if err != nil {
		return "", err
	}

	resp, err := (*http.Client)(c).PostForm(nintendoOauthURL, query)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return "", errors.New(resp.Status)
	}

	session := getSessionFromCookie(resp)

	return session, nil
}

func (c *ikaClient) getStageInfo() (*stageInfo, error) {

	resp, err := (*http.Client)(c).Get(splatoonScheduleAPI)

	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if err = checkJSONError(body); err != nil {
		return nil, err
	}

	return decodeJSONSchedule(body)
}

func getCacheFile() (string, error) {
	me, err := user.Current()
	if err != nil {
		return "", err
	}
	return filepath.Join(me.HomeDir, ".ikaring.session"), nil
}

func readSession(path string) (string, error) {
	buff, err := ioutil.ReadFile(path)
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(buff)), nil
}

func writeSession(path string, session string) error {
	return ioutil.WriteFile(path, []byte(session), 600)
}

func getAccount(r io.Reader) (string, string, error) {
	scanner := bufio.NewScanner(r)
	for {
		fmt.Print("User: ")
		if scanner.Scan() {
			break
		}
	}
	username := scanner.Text()
	password, err := speakeasy.Ask("Password: ")
	return username, password, err
}

func (s schedule) String() string {
	timefmt := "01/02 15:04:05"
	str := fmt.Sprintf("%s - %s\n",
		s.TimeBegin.Format(timefmt), s.TimeEnd.Format(timefmt))

	str += "レギュラーマッチ\n"
	for i, stage := range s.Stages.Regular {
		if i == 0 {
			str += "\t"
		} else {
			str += ", "
		}
		str += stage.Name
	}
	str += "\n"

	str += fmt.Sprintf("ガチマッチ (%s)\n", s.GachiRule)
	for i, stage := range s.Stages.Gachi {
		if i == 0 {
			str += "\t"
		} else {
			str += ", "
		}
		str += stage.Name
	}
	str += "\n"
	return str
}

func main() {
	client, err := createClient()
	if err != nil {
		fmt.Println(err)
		return
	}

	path, err := getCacheFile()
	if err != nil {
		fmt.Println(err)
		return
	}

	session, err := readSession(path)
	if err == nil && len(session) > 0 {
		client.setSession(session)
	} else {
		username, password, err := getAccount(os.Stdin)
		session, err = client.login(username, password)
		if err != nil {
			fmt.Println(err)
			return
		}
	}

	if len(session) <= 0 {
		fmt.Println("ログインできませんでした")
		return
	}

	writeSession(path, session)

	info, err := client.getStageInfo()
	if err != nil {
		fmt.Println(err)
		return
	}

	for _, s := range info.Schedules {
		fmt.Printf("%v\n", s)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"net/http"
	"net/http/cookiejar"
	"net/url"

	"github.com/PuerkitoBio/goquery"
)

type ikaClient http.Client

const (
	splatoonClientID = "12af3d0a3a1f441eb900411bb50a835a"
	splatoonOauthURL = "https://splatoon.nintendo.net/users/auth/nintendo"
	nintendoOauthURL = "https://id.nintendo.net/oauth/authorize"
)

func getOauthQuery(oarthURL string, id string, password string) (url.Values, error) {
	doc, err := goquery.NewDocument(oarthURL)

	if err != nil {
		return nil, err
	}

	query := url.Values{}
	doc.Find("input").Each(func(_ int, s *goquery.Selection) {
		name, ok := s.Attr("name")
		if !ok {
			return
		}

		// parse from docment
		switch name {
		case "cliend_id":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		case "state":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		case "redirect_uri":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		case "response_type":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		}
	})

	// fixed value
	query.Add("client_id", splatoonClientID)
	query.Add("nintendo_authenticate", "")
	query.Add("nintendo_authorize", "")
	query.Add("scope", "")
	query.Add("lang", "ja-JP")

	// user info
	query.Add("username", id)
	query.Add("password", password)

	return query, nil
}

func createClient() (*ikaClient, error) {
	jar, err := cookiejar.New(nil)
	if err != nil {
		return nil, err
	}
	client := &http.Client{Jar: jar}
	return (*ikaClient)(client), nil
}

func (c *ikaClient) login(name string, password string) error {
	query, err := getOauthQuery(splatoonOauthURL, name, password)
	if err != nil {
		return err
	}

	resp, err := (*http.Client)(c).PostForm(nintendoOauthURL, query)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}

	return nil
}

func main() {
}
<commit_msg>implement ikaClient.getStageInfo()<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"net/url"

	"github.com/PuerkitoBio/goquery"
	simplejson "github.com/bitly/go-simplejson"
)

type ikaClient http.Client

type stage struct {
	Name  string `json:"name"`
	Image string `json:"asset_path"`
}

type regulation struct {
	Regular []stage
	Gachi   []stage
}

type schedule struct {
	TimeBegin string     `json:"datetime_begin"`
	TimeEnd   string     `json:"datetime_end"`
	Stages    regulation `json:"stages"`
	GachiRule string     `json:"gachi_rule"`
}

type stageInfo struct {
	Festival  bool       `json:"festival"`
	Schedules []schedule `json:"schedule"`
}

const (
	splatoonClientID = "12af3d0a3a1f441eb900411bb50a835a"
	splatoonOauthURL = "https://splatoon.nintendo.net/users/auth/nintendo"
	nintendoOauthURL = "https://id.nintendo.net/oauth/authorize"

	splatoonScheduleAPI = "https://splatoon.nintendo.net/schedule/index.json"
)

func checkJSONError(data []byte) error {
	js, err := simplejson.NewJson(data)
	if err != nil {
		return err
	}

	if info := js.Get("error").MustString(); len(info) != 0 {
		return errors.New(info)
	}
	return nil
}

func decodeJSONSchedule(data []byte) (*stageInfo, error) {
	info := &stageInfo{}
	if err := json.Unmarshal(data, info); err != nil {
		return nil, err
	}
	return info, nil
}

func getOauthQuery(oarthURL string, id string, password string) (url.Values, error) {
	doc, err := goquery.NewDocument(oarthURL)

	if err != nil {
		return nil, err
	}

	query := url.Values{}
	doc.Find("input").Each(func(_ int, s *goquery.Selection) {
		name, ok := s.Attr("name")
		if !ok {
			return
		}

		// parse from docment
		switch name {
		case "cliend_id":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		case "state":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		case "redirect_uri":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		case "response_type":
			if v, ok := s.Attr("value"); ok {
				query.Add(name, v)
			}
		}
	})

	// fixed value
	query.Add("client_id", splatoonClientID)
	query.Add("nintendo_authenticate", "")
	query.Add("nintendo_authorize", "")
	query.Add("scope", "")
	query.Add("lang", "ja-JP")

	// user info
	query.Add("username", id)
	query.Add("password", password)

	return query, nil
}

func createClient() (*ikaClient, error) {
	jar, err := cookiejar.New(nil)
	if err != nil {
		return nil, err
	}
	client := &http.Client{Jar: jar}
	return (*ikaClient)(client), nil
}

func (c *ikaClient) login(name string, password string) error {
	query, err := getOauthQuery(splatoonOauthURL, name, password)
	if err != nil {
		return err
	}

	resp, err := (*http.Client)(c).PostForm(nintendoOauthURL, query)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}

	return nil
}

func (c *ikaClient) getStageInfo() (*stageInfo, error) {

	resp, err := (*http.Client)(c).Get(splatoonScheduleAPI)

	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return decodeJSONSchedule(resp.Body)
}

func main() {
	_, err := createClient()
	if err != nil {
		fmt.Println(err)
	}
}
<|endoftext|>


<commit_before>package builder

import (
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	s2iapi "github.com/openshift/source-to-image/pkg/api"
	utilruntime "k8s.io/kubernetes/pkg/util/runtime"

	"github.com/docker/docker/pkg/parsers"
	docker "github.com/fsouza/go-dockerclient"
	"github.com/golang/glog"
	"github.com/openshift/source-to-image/pkg/tar"
)

var (
	// DefaultPushRetryCount is the number of retries of pushing the built Docker image
	// into a configured repository
	DefaultPushRetryCount = 6
	// DefaultPushRetryDelay is the time to wait before triggering a push retry
	DefaultPushRetryDelay = 5 * time.Second
	// RetriableErrors is a set of strings that indicate that an retriable error occurred.
	RetriableErrors = []string{
		"ping attempt failed with error",
		"is already in progress",
		"connection reset by peer",
		"transport closed before response was received",
	}
)

// DockerClient is an interface to the Docker client that contains
// the methods used by the common builder
type DockerClient interface {
	BuildImage(opts docker.BuildImageOptions) error
	PushImage(opts docker.PushImageOptions, auth docker.AuthConfiguration) error
	RemoveImage(name string) error
	CreateContainer(opts docker.CreateContainerOptions) (*docker.Container, error)
	DownloadFromContainer(id string, opts docker.DownloadFromContainerOptions) error
	PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
	RemoveContainer(opts docker.RemoveContainerOptions) error
	InspectImage(name string) (*docker.Image, error)
	StartContainer(id string, hostConfig *docker.HostConfig) error
	WaitContainer(id string) (int, error)
	Logs(opts docker.LogsOptions) error
	TagImage(name string, opts docker.TagImageOptions) error
}

// pushImage pushes a docker image to the registry specified in its tag.
// The method will retry to push the image when following scenarios occur:
// - Docker registry is down temporarily or permanently
// - other image is being pushed to the registry
// If any other scenario the push will fail, without retries.
func pushImage(client DockerClient, name string, authConfig docker.AuthConfiguration) error {
	repository, tag := docker.ParseRepositoryTag(name)
	opts := docker.PushImageOptions{
		Name: repository,
		Tag:  tag,
	}
	if glog.V(5) {
		opts.OutputStream = os.Stderr
	}
	var err error
	var retriableError = false

	for retries := 0; retries <= DefaultPushRetryCount; retries++ {
		err = client.PushImage(opts, authConfig)
		if err == nil {
			return nil
		}

		errMsg := fmt.Sprintf("%s", err)
		for _, errorString := range RetriableErrors {
			if strings.Contains(errMsg, errorString) {
				retriableError = true
				break
			}
		}
		if !retriableError {
			return err
		}

		utilruntime.HandleError(fmt.Errorf("push for image %s failed, will retry in %s ...", name, DefaultPushRetryDelay))
		glog.Flush()
		time.Sleep(DefaultPushRetryDelay)
	}
	return err
}

func removeImage(client DockerClient, name string) error {
	return client.RemoveImage(name)
}

// buildImage invokes a docker build on a particular directory
func buildImage(client DockerClient, dir string, dockerfilePath string, noCache bool, tag string, tar tar.Tar, pullAuth *docker.AuthConfigurations, forcePull bool, cgLimits *s2iapi.CGroupLimits) error {
	// TODO: be able to pass a stream directly to the Docker build to avoid the double temp hit
	r, w := io.Pipe()
	go func() {
		defer utilruntime.HandleCrash()
		defer w.Close()
		if err := tar.CreateTarStream(dir, false, w); err != nil {
			w.CloseWithError(err)
		}
	}()
	defer w.Close()
	glog.V(5).Infof("Invoking Docker build to create %q", tag)
	opts := docker.BuildImageOptions{
		Name:           tag,
		RmTmpContainer: true,
		OutputStream:   os.Stdout,
		InputStream:    r,
		Dockerfile:     dockerfilePath,
		NoCache:        noCache,
		Pull:           forcePull,
	}
	if cgLimits != nil {
		opts.Memory = cgLimits.MemoryLimitBytes
		opts.Memswap = cgLimits.MemorySwap
		opts.CPUShares = cgLimits.CPUShares
		opts.CPUPeriod = cgLimits.CPUPeriod
		opts.CPUQuota = cgLimits.CPUQuota
	}
	if pullAuth != nil {
		opts.AuthConfigs = *pullAuth
	}
	return client.BuildImage(opts)
}

// tagImage uses the dockerClient to tag a Docker image with name. It is a
// helper to facilitate the usage of dockerClient.TagImage, because the former
// requires the name to be split into more explicit parts.
func tagImage(dockerClient DockerClient, image, name string) error {
	repo, tag := parsers.ParseRepositoryTag(name)
	return dockerClient.TagImage(image, docker.TagImageOptions{
		Repo: repo,
		Tag:  tag,
		// We need to set Force to true to update the tag even if it
		// already exists. This is the same behavior as `docker build -t
		// tag .`.
		Force: true,
	})
}

// dockerRun mimics the 'docker run --rm' CLI command. It uses the Docker Remote
// API to create and start a container and stream its logs. The container is
// removed after it terminates.
func dockerRun(client DockerClient, createOpts docker.CreateContainerOptions, logsOpts docker.LogsOptions) error {
	// Create a new container.
	glog.V(4).Infof("Creating container with options {Name:%q Config:%+v HostConfig:%+v} ...", createOpts.Name, createOpts.Config, createOpts.HostConfig)
	c, err := client.CreateContainer(createOpts)
	if err != nil {
		return fmt.Errorf("create container %q: %v", createOpts.Name, err)
	}

	containerName := containerNameOrID(c)

	// Container was created, so we defer its removal.
	defer func() {
		glog.V(4).Infof("Removing container %q ...", containerName)
		if err := client.RemoveContainer(docker.RemoveContainerOptions{ID: c.ID}); err != nil {
			glog.Warningf("Failed to remove container %q: %v", containerName, err)
		} else {
			glog.V(4).Infof("Removed container %q", containerName)
		}
	}()

	// Start the container.
	glog.V(4).Infof("Starting container %q ...", containerName)
	if err := client.StartContainer(c.ID, nil); err != nil {
		return fmt.Errorf("start container %q: %v", containerName, err)
	}

	// Stream container logs.
	glog.V(4).Infof("Streaming logs of container %q with options %+v ...", containerName, logsOpts)
	logsOpts.Container = c.ID
	if err := client.Logs(logsOpts); err != nil {
		return fmt.Errorf("streaming logs of %q: %v", containerName, err)
	}

	// Return an error if the exit code of the container is non-zero.
	glog.V(4).Infof("Waiting for container %q to stop ...", containerName)
	exitCode, err := client.WaitContainer(c.ID)
	if err != nil {
		return fmt.Errorf("waiting for container %q to stop: %v", containerName, err)
	}
	if exitCode != 0 {
		return fmt.Errorf("container %q returned non-zero exit code: %d", containerName, exitCode)
	}

	return nil
}

func containerNameOrID(c *docker.Container) string {
	if c.Name != "" {
		return c.Name
	}
	return c.ID
}
<commit_msg>Include container ID in glog message<commit_after>package builder

import (
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	s2iapi "github.com/openshift/source-to-image/pkg/api"
	utilruntime "k8s.io/kubernetes/pkg/util/runtime"

	"github.com/docker/docker/pkg/parsers"
	docker "github.com/fsouza/go-dockerclient"
	"github.com/golang/glog"
	"github.com/openshift/source-to-image/pkg/tar"
)

var (
	// DefaultPushRetryCount is the number of retries of pushing the built Docker image
	// into a configured repository
	DefaultPushRetryCount = 6
	// DefaultPushRetryDelay is the time to wait before triggering a push retry
	DefaultPushRetryDelay = 5 * time.Second
	// RetriableErrors is a set of strings that indicate that an retriable error occurred.
	RetriableErrors = []string{
		"ping attempt failed with error",
		"is already in progress",
		"connection reset by peer",
		"transport closed before response was received",
	}
)

// DockerClient is an interface to the Docker client that contains
// the methods used by the common builder
type DockerClient interface {
	BuildImage(opts docker.BuildImageOptions) error
	PushImage(opts docker.PushImageOptions, auth docker.AuthConfiguration) error
	RemoveImage(name string) error
	CreateContainer(opts docker.CreateContainerOptions) (*docker.Container, error)
	DownloadFromContainer(id string, opts docker.DownloadFromContainerOptions) error
	PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
	RemoveContainer(opts docker.RemoveContainerOptions) error
	InspectImage(name string) (*docker.Image, error)
	StartContainer(id string, hostConfig *docker.HostConfig) error
	WaitContainer(id string) (int, error)
	Logs(opts docker.LogsOptions) error
	TagImage(name string, opts docker.TagImageOptions) error
}

// pushImage pushes a docker image to the registry specified in its tag.
// The method will retry to push the image when following scenarios occur:
// - Docker registry is down temporarily or permanently
// - other image is being pushed to the registry
// If any other scenario the push will fail, without retries.
func pushImage(client DockerClient, name string, authConfig docker.AuthConfiguration) error {
	repository, tag := docker.ParseRepositoryTag(name)
	opts := docker.PushImageOptions{
		Name: repository,
		Tag:  tag,
	}
	if glog.V(5) {
		opts.OutputStream = os.Stderr
	}
	var err error
	var retriableError = false

	for retries := 0; retries <= DefaultPushRetryCount; retries++ {
		err = client.PushImage(opts, authConfig)
		if err == nil {
			return nil
		}

		errMsg := fmt.Sprintf("%s", err)
		for _, errorString := range RetriableErrors {
			if strings.Contains(errMsg, errorString) {
				retriableError = true
				break
			}
		}
		if !retriableError {
			return err
		}

		utilruntime.HandleError(fmt.Errorf("push for image %s failed, will retry in %s ...", name, DefaultPushRetryDelay))
		glog.Flush()
		time.Sleep(DefaultPushRetryDelay)
	}
	return err
}

func removeImage(client DockerClient, name string) error {
	return client.RemoveImage(name)
}

// buildImage invokes a docker build on a particular directory
func buildImage(client DockerClient, dir string, dockerfilePath string, noCache bool, tag string, tar tar.Tar, pullAuth *docker.AuthConfigurations, forcePull bool, cgLimits *s2iapi.CGroupLimits) error {
	// TODO: be able to pass a stream directly to the Docker build to avoid the double temp hit
	r, w := io.Pipe()
	go func() {
		defer utilruntime.HandleCrash()
		defer w.Close()
		if err := tar.CreateTarStream(dir, false, w); err != nil {
			w.CloseWithError(err)
		}
	}()
	defer w.Close()
	glog.V(5).Infof("Invoking Docker build to create %q", tag)
	opts := docker.BuildImageOptions{
		Name:           tag,
		RmTmpContainer: true,
		OutputStream:   os.Stdout,
		InputStream:    r,
		Dockerfile:     dockerfilePath,
		NoCache:        noCache,
		Pull:           forcePull,
	}
	if cgLimits != nil {
		opts.Memory = cgLimits.MemoryLimitBytes
		opts.Memswap = cgLimits.MemorySwap
		opts.CPUShares = cgLimits.CPUShares
		opts.CPUPeriod = cgLimits.CPUPeriod
		opts.CPUQuota = cgLimits.CPUQuota
	}
	if pullAuth != nil {
		opts.AuthConfigs = *pullAuth
	}
	return client.BuildImage(opts)
}

// tagImage uses the dockerClient to tag a Docker image with name. It is a
// helper to facilitate the usage of dockerClient.TagImage, because the former
// requires the name to be split into more explicit parts.
func tagImage(dockerClient DockerClient, image, name string) error {
	repo, tag := parsers.ParseRepositoryTag(name)
	return dockerClient.TagImage(image, docker.TagImageOptions{
		Repo: repo,
		Tag:  tag,
		// We need to set Force to true to update the tag even if it
		// already exists. This is the same behavior as `docker build -t
		// tag .`.
		Force: true,
	})
}

// dockerRun mimics the 'docker run --rm' CLI command. It uses the Docker Remote
// API to create and start a container and stream its logs. The container is
// removed after it terminates.
func dockerRun(client DockerClient, createOpts docker.CreateContainerOptions, logsOpts docker.LogsOptions) error {
	// Create a new container.
	glog.V(4).Infof("Creating container with options {Name:%q Config:%+v HostConfig:%+v} ...", createOpts.Name, createOpts.Config, createOpts.HostConfig)
	c, err := client.CreateContainer(createOpts)
	if err != nil {
		return fmt.Errorf("create container %q: %v", createOpts.Name, err)
	}

	containerName := containerNameOrID(c)

	// Container was created, so we defer its removal.
	defer func() {
		glog.V(4).Infof("Removing container %q ...", containerName)
		if err := client.RemoveContainer(docker.RemoveContainerOptions{ID: c.ID}); err != nil {
			glog.Warningf("Failed to remove container %q: %v", containerName, err)
		} else {
			glog.V(4).Infof("Removed container %q", containerName)
		}
	}()

	// Start the container.
	glog.V(4).Infof("Starting container %q ...", containerName)
	if err := client.StartContainer(c.ID, nil); err != nil {
		return fmt.Errorf("start container %q: %v", containerName, err)
	}

	// Stream container logs.
	logsOpts.Container = c.ID
	glog.V(4).Infof("Streaming logs of container %q with options %+v ...", containerName, logsOpts)
	if err := client.Logs(logsOpts); err != nil {
		return fmt.Errorf("streaming logs of %q: %v", containerName, err)
	}

	// Return an error if the exit code of the container is non-zero.
	glog.V(4).Infof("Waiting for container %q to stop ...", containerName)
	exitCode, err := client.WaitContainer(c.ID)
	if err != nil {
		return fmt.Errorf("waiting for container %q to stop: %v", containerName, err)
	}
	if exitCode != 0 {
		return fmt.Errorf("container %q returned non-zero exit code: %d", containerName, exitCode)
	}

	return nil
}

func containerNameOrID(c *docker.Container) string {
	if c.Name != "" {
		return c.Name
	}
	return c.ID
}
<|endoftext|>


<commit_before>package openstack

import (
	"fmt"
	"sync"

	"github.com/go-kit/kit/log"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	core_v1 "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/tools/cache"

	kubernikus_v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	"github.com/sapcc/kubernikus/pkg/client/openstack/admin"
	openstack_kluster "github.com/sapcc/kubernikus/pkg/client/openstack/kluster"
	openstack_project "github.com/sapcc/kubernikus/pkg/client/openstack/project"
	utillog "github.com/sapcc/kubernikus/pkg/util/log"
)

type SharedOpenstackClientFactory interface {
	KlusterClientFor(*kubernikus_v1.Kluster) (openstack_kluster.KlusterClient, error)
	ProjectClientFor(authOptions *tokens.AuthOptions) (openstack_project.ProjectClient, error)
	ProjectAdminClientFor(string) (openstack_project.ProjectClient, error)
	ProviderClientFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ProviderClient, error)
	ProviderClientForKluster(kluster *kubernikus_v1.Kluster, logger log.Logger) (*gophercloud.ProviderClient, error)
	AdminClient() (admin.AdminClient, error)
}

type factory struct {
	klusterClients sync.Map
	projectClients sync.Map
	adminClient    admin.AdminClient

	secrets          core_v1.SecretInterface
	klusters         cache.SharedIndexInformer
	adminAuthOptions *tokens.AuthOptions
	logger           log.Logger
}

func NewSharedOpenstackClientFactory(secrets core_v1.SecretInterface, klusters cache.SharedIndexInformer, adminAuthOptions *tokens.AuthOptions, logger log.Logger) SharedOpenstackClientFactory {
	factory := &factory{
		secrets:          secrets,
		klusters:         klusters,
		adminAuthOptions: adminAuthOptions,
		logger:           logger,
	}

	klusters.AddEventHandler(cache.ResourceEventHandlerFuncs{
		DeleteFunc: func(obj interface{}) {
			if kluster, ok := obj.(*kubernikus_v1.Kluster); ok {
				factory.logger.Log(
					"msg", "deleting shared openstack client",
					"kluster", kluster.Name,
					"v", 5)
				factory.klusterClients.Delete(kluster.GetUID())
			}
		},
	})

	return factory
}

func (f *factory) AdminClient() (admin.AdminClient, error) {
	if f.adminClient != nil {
		return f.adminClient, nil
	}

	identity, compute, network, err := f.serviceClientsFor(f.adminAuthOptions, f.logger)
	if err != nil {
		return nil, err
	}

	var client admin.AdminClient
	client = admin.NewAdminClient(network, compute, identity)
	client = admin.LoggingClient{client, f.logger}

	f.adminClient = client

	return f.adminClient, nil
}

func (f *factory) KlusterClientFor(kluster *kubernikus_v1.Kluster) (openstack_kluster.KlusterClient, error) {
	if obj, found := f.klusterClients.Load(kluster.GetUID()); found {
		return obj.(openstack_kluster.KlusterClient), nil
	}

	authOptions, err := f.authOptionsForKluster(kluster)
	if err != nil {
		return nil, err
	}

	identity, compute, network, err := f.serviceClientsFor(authOptions, f.logger)
	if err != nil {
		return nil, err
	}

	var client openstack_kluster.KlusterClient
	client = openstack_kluster.NewKlusterClient(identity, compute, network, kluster)
	client = &openstack_kluster.LoggingClient{client, log.With(f.logger, "kluster", kluster.GetName(), "project", kluster.Account())}

	f.klusterClients.Store(kluster.GetUID(), client)

	return client, nil
}

func (f *factory) ProjectClientFor(authOptions *tokens.AuthOptions) (openstack_project.ProjectClient, error) {
	if authOptions.Scope.ProjectID == "" {
		return nil, fmt.Errorf("AuthOptions must be scoped to a projectID")
	}
	return f.projectClient(authOptions.Scope.ProjectID, authOptions)
}

func (f *factory) ProjectAdminClientFor(projectID string) (openstack_project.ProjectClient, error) {
	return f.projectClient(projectID, f.adminAuthOptions)
}

func (f *factory) projectClient(projectID string, authOptions *tokens.AuthOptions) (openstack_project.ProjectClient, error) {
	if obj, found := f.projectClients.Load(projectID); found {
		return obj.(openstack_project.ProjectClient), nil
	}

	identity, compute, network, err := f.serviceClientsFor(authOptions, f.logger)
	if err != nil {
		return nil, err
	}

	var client openstack_project.ProjectClient
	client = openstack_project.NewProjectClient(projectID, network, compute, identity)
	client = &openstack_project.LoggingClient{client, log.With(f.logger, "project_id", projectID)}

	f.projectClients.Store(projectID, client)
	return client, nil
}

func (f *factory) authOptionsForKluster(kluster *kubernikus_v1.Kluster) (*tokens.AuthOptions, error) {
	secret, err := f.secrets.Get(kluster.Name, meta_v1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("Couldn't retrieve secret %s/%s: %v", kluster.GetNamespace(), kluster.Name, err)
	}

	authOptions := &tokens.AuthOptions{
		IdentityEndpoint: string(secret.Data["openstack-auth-url"]),
		Username:         string(secret.Data["openstack-username"]),
		Password:         string(secret.Data["openstack-password"]),
		DomainName:       string(secret.Data["openstack-domain-name"]),
		AllowReauth:      true,
		Scope: tokens.Scope{
			ProjectID: string(secret.Data["openstack-project-id"]),
		},
	}

	f.logger.Log(
		"msg", "using authOptions from secret",
		"identity_endpoint", authOptions.IdentityEndpoint,
		"username", authOptions.Username,
		"domain_name", authOptions.DomainName,
		"project_id", authOptions.Scope.ProjectID,
		"v", 5)

	return authOptions, nil
}

func (f *factory) ProviderClientForKluster(kluster *kubernikus_v1.Kluster, logger log.Logger) (*gophercloud.ProviderClient, error) {
	authOptions, err := f.authOptionsForKluster(kluster)
	if err != nil {
		return nil, err
	}
	return f.ProviderClientFor(authOptions, logger)
}

func (f *factory) ProviderClientFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ProviderClient, error) {
	provider, err := utillog.NewLoggingProviderClient(authOptions.IdentityEndpoint, logger)
	if err != nil {
		return nil, err
	}

	provider.UseTokenLock()

	err = openstack.AuthenticateV3(provider, authOptions, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, err
	}

	return provider, nil
}

func (f *factory) serviceClientsFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ServiceClient, *gophercloud.ServiceClient, *gophercloud.ServiceClient, error) {
	providerClient, err := f.ProviderClientFor(authOptions, logger)
	if err != nil {
		return nil, nil, nil, err
	}

	identity, err := openstack.NewIdentityV3(providerClient, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, nil, nil, err
	}

	compute, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, nil, nil, err
	}

	network, err := openstack.NewNetworkV2(providerClient, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, nil, nil, err
	}

	return identity, compute, network, nil
}
<commit_msg>emergency fix<commit_after>package openstack

import (
	"fmt"
	"sync"

	"github.com/go-kit/kit/log"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	core_v1 "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/tools/cache"

	kubernikus_v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	"github.com/sapcc/kubernikus/pkg/client/openstack/admin"
	openstack_kluster "github.com/sapcc/kubernikus/pkg/client/openstack/kluster"
	openstack_project "github.com/sapcc/kubernikus/pkg/client/openstack/project"
	utillog "github.com/sapcc/kubernikus/pkg/util/log"
)

type SharedOpenstackClientFactory interface {
	KlusterClientFor(*kubernikus_v1.Kluster) (openstack_kluster.KlusterClient, error)
	ProjectClientFor(authOptions *tokens.AuthOptions) (openstack_project.ProjectClient, error)
	ProjectAdminClientFor(string) (openstack_project.ProjectClient, error)
	ProviderClientFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ProviderClient, error)
	ProviderClientForKluster(kluster *kubernikus_v1.Kluster, logger log.Logger) (*gophercloud.ProviderClient, error)
	AdminClient() (admin.AdminClient, error)
}

type factory struct {
	klusterClients sync.Map
	projectClients sync.Map
	adminClient    admin.AdminClient

	secrets          core_v1.SecretInterface
	klusters         cache.SharedIndexInformer
	adminAuthOptions *tokens.AuthOptions
	logger           log.Logger
}

func NewSharedOpenstackClientFactory(secrets core_v1.SecretInterface, klusters cache.SharedIndexInformer, adminAuthOptions *tokens.AuthOptions, logger log.Logger) SharedOpenstackClientFactory {
	factory := &factory{
		secrets:          secrets,
		klusters:         klusters,
		adminAuthOptions: adminAuthOptions,
		logger:           logger,
	}

	if klusrers != nil {
		klusters.AddEventHandler(cache.ResourceEventHandlerFuncs{
			DeleteFunc: func(obj interface{}) {
				if kluster, ok := obj.(*kubernikus_v1.Kluster); ok {
					factory.logger.Log(
						"msg", "deleting shared openstack client",
						"kluster", kluster.Name,
						"v", 5)
					factory.klusterClients.Delete(kluster.GetUID())
				}
			},
		})
	}

	return factory
}

func (f *factory) AdminClient() (admin.AdminClient, error) {
	if f.adminClient != nil {
		return f.adminClient, nil
	}

	identity, compute, network, err := f.serviceClientsFor(f.adminAuthOptions, f.logger)
	if err != nil {
		return nil, err
	}

	var client admin.AdminClient
	client = admin.NewAdminClient(network, compute, identity)
	client = admin.LoggingClient{client, f.logger}

	f.adminClient = client

	return f.adminClient, nil
}

func (f *factory) KlusterClientFor(kluster *kubernikus_v1.Kluster) (openstack_kluster.KlusterClient, error) {
	if obj, found := f.klusterClients.Load(kluster.GetUID()); found {
		return obj.(openstack_kluster.KlusterClient), nil
	}

	authOptions, err := f.authOptionsForKluster(kluster)
	if err != nil {
		return nil, err
	}

	identity, compute, network, err := f.serviceClientsFor(authOptions, f.logger)
	if err != nil {
		return nil, err
	}

	var client openstack_kluster.KlusterClient
	client = openstack_kluster.NewKlusterClient(identity, compute, network, kluster)
	client = &openstack_kluster.LoggingClient{client, log.With(f.logger, "kluster", kluster.GetName(), "project", kluster.Account())}

	f.klusterClients.Store(kluster.GetUID(), client)

	return client, nil
}

func (f *factory) ProjectClientFor(authOptions *tokens.AuthOptions) (openstack_project.ProjectClient, error) {
	if authOptions.Scope.ProjectID == "" {
		return nil, fmt.Errorf("AuthOptions must be scoped to a projectID")
	}
	return f.projectClient(authOptions.Scope.ProjectID, authOptions)
}

func (f *factory) ProjectAdminClientFor(projectID string) (openstack_project.ProjectClient, error) {
	return f.projectClient(projectID, f.adminAuthOptions)
}

func (f *factory) projectClient(projectID string, authOptions *tokens.AuthOptions) (openstack_project.ProjectClient, error) {
	if obj, found := f.projectClients.Load(projectID); found {
		return obj.(openstack_project.ProjectClient), nil
	}

	identity, compute, network, err := f.serviceClientsFor(authOptions, f.logger)
	if err != nil {
		return nil, err
	}

	var client openstack_project.ProjectClient
	client = openstack_project.NewProjectClient(projectID, network, compute, identity)
	client = &openstack_project.LoggingClient{client, log.With(f.logger, "project_id", projectID)}

	f.projectClients.Store(projectID, client)
	return client, nil
}

func (f *factory) authOptionsForKluster(kluster *kubernikus_v1.Kluster) (*tokens.AuthOptions, error) {
	secret, err := f.secrets.Get(kluster.Name, meta_v1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("Couldn't retrieve secret %s/%s: %v", kluster.GetNamespace(), kluster.Name, err)
	}

	authOptions := &tokens.AuthOptions{
		IdentityEndpoint: string(secret.Data["openstack-auth-url"]),
		Username:         string(secret.Data["openstack-username"]),
		Password:         string(secret.Data["openstack-password"]),
		DomainName:       string(secret.Data["openstack-domain-name"]),
		AllowReauth:      true,
		Scope: tokens.Scope{
			ProjectID: string(secret.Data["openstack-project-id"]),
		},
	}

	f.logger.Log(
		"msg", "using authOptions from secret",
		"identity_endpoint", authOptions.IdentityEndpoint,
		"username", authOptions.Username,
		"domain_name", authOptions.DomainName,
		"project_id", authOptions.Scope.ProjectID,
		"v", 5)

	return authOptions, nil
}

func (f *factory) ProviderClientForKluster(kluster *kubernikus_v1.Kluster, logger log.Logger) (*gophercloud.ProviderClient, error) {
	authOptions, err := f.authOptionsForKluster(kluster)
	if err != nil {
		return nil, err
	}
	return f.ProviderClientFor(authOptions, logger)
}

func (f *factory) ProviderClientFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ProviderClient, error) {
	provider, err := utillog.NewLoggingProviderClient(authOptions.IdentityEndpoint, logger)
	if err != nil {
		return nil, err
	}

	provider.UseTokenLock()

	err = openstack.AuthenticateV3(provider, authOptions, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, err
	}

	return provider, nil
}

func (f *factory) serviceClientsFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ServiceClient, *gophercloud.ServiceClient, *gophercloud.ServiceClient, error) {
	providerClient, err := f.ProviderClientFor(authOptions, logger)
	if err != nil {
		return nil, nil, nil, err
	}

	identity, err := openstack.NewIdentityV3(providerClient, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, nil, nil, err
	}

	compute, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, nil, nil, err
	}

	network, err := openstack.NewNetworkV2(providerClient, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, nil, nil, err
	}

	return identity, compute, network, nil
}
<|endoftext|>


<commit_before>package models

import (
	"errors"
	"time"

	"github.com/gogo/protobuf/proto"
	"github.com/interuss/dss/pkg/dss/geo"
	"github.com/interuss/dss/pkg/dssproto"
	dspb "github.com/interuss/dss/pkg/dssproto"
	dsserr "github.com/interuss/dss/pkg/errors"

	"github.com/golang/geo/s2"
	"github.com/golang/protobuf/ptypes"
)

var (
	// maxSubscriptionDuration is the largest allowed interval between StartTime
	// and EndTime.
	maxSubscriptionDuration = time.Hour * 24

	// maxClockSkew is the largest allowed interval between the StartTime of a new
	// subscription and the server's idea of the current time.
	maxClockSkew = time.Minute * 5
)

type Subscription struct {
	ID                ID
	Url               string
	NotificationIndex int
	Owner             Owner
	Cells             s2.CellUnion
	StartTime         *time.Time
	EndTime           *time.Time
	Version           *Version
	AltitudeHi        *float32
	AltitudeLo        *float32
}

func (s *Subscription) ToNotifyProto() *dspb.SubscriberToNotify {
	return &dspb.SubscriberToNotify{
		Url: s.Url,
		Subscriptions: []*dspb.SubscriptionState{
			&dspb.SubscriptionState{
				NotificationIndex: int32(s.NotificationIndex),
				SubscriptionId:    s.ID.String(),
			},
		},
	}
}

func (s *Subscription) ToProto() (*dspb.Subscription, error) {
	result := &dspb.Subscription{
		Id:                s.ID.String(),
		Owner:             s.Owner.String(),
		Callbacks:         &dssproto.SubscriptionCallbacks{IdentificationServiceAreaUrl: s.Url},
		NotificationIndex: int32(s.NotificationIndex),
		Version:           s.Version.String(),
	}

	if s.StartTime != nil {
		ts, err := ptypes.TimestampProto(*s.StartTime)
		if err != nil {
			return nil, err
		}
		result.TimeStart = ts
	}

	if s.EndTime != nil {
		ts, err := ptypes.TimestampProto(*s.EndTime)
		if err != nil {
			return nil, err
		}
		result.TimeEnd = ts
	}
	return result, nil
}

func (s *Subscription) SetExtents(extents *dspb.Volume4D) error {
	var err error
	if extents == nil {
		return nil
	}
	if startTime := extents.GetTimeStart(); startTime != nil {
		ts, err := ptypes.Timestamp(startTime)
		if err != nil {
			return err
		}
		s.StartTime = &ts
	}

	if endTime := extents.GetTimeEnd(); endTime != nil {
		ts, err := ptypes.Timestamp(endTime)
		if err != nil {
			return err
		}
		s.EndTime = &ts
	}

	space := extents.GetSpatialVolume()
	if space == nil {
		return errors.New("missing required spatial_volume")
	}
	s.AltitudeHi = proto.Float32(space.GetAltitudeHi())
	s.AltitudeLo = proto.Float32(space.GetAltitudeLo())
	footprint := space.GetFootprint()
	if footprint == nil {
		return errors.New("spatial_volume missing required footprint")
	}
	s.Cells, err = geo.GeoPolygonToCellIDs(footprint)
	return err
}

func (s *Subscription) AdjustTimeRange(now time.Time, old *Subscription) error {
	if s.StartTime == nil {
		// If StartTime was omitted, default to Now() for new subscriptions or re-
		// use the existing time of existing subscriptions.
		if old == nil {
			s.StartTime = &now
		} else {
			s.StartTime = old.StartTime
		}
	} else {
		// If setting the StartTime explicitly ensure it is not too far in the past.
		if now.Sub(*s.StartTime) > maxClockSkew {
			return dsserr.BadRequest("subscription time_start must not be in the past")
		}
	}

	// If EndTime was omitted default to the existing subscription's EndTime.
	if s.EndTime == nil && old != nil {
		s.EndTime = old.EndTime
	}

	// Or if this is a new subscription default to StartTime + 1 day.
	if s.EndTime == nil {
		truncatedEndTime := s.StartTime.Add(maxSubscriptionDuration)
		s.EndTime = &truncatedEndTime
	}

	// EndTime cannot be before StartTime.
	if s.EndTime.Sub(*s.StartTime) < 0 {
		return dsserr.BadRequest("subscription time_end must be after time_start")
	}

	// EndTime cannot be 24 hrs after StartTime
	if s.EndTime.Sub(*s.StartTime) > maxSubscriptionDuration {
		return dsserr.BadRequest("subscription window exceeds 24 hours")
	}

	return nil
}
<commit_msg>Remove duplicate important of dssproto (#190)<commit_after>package models

import (
	"errors"
	"time"

	"github.com/gogo/protobuf/proto"
	"github.com/interuss/dss/pkg/dss/geo"
	dspb "github.com/interuss/dss/pkg/dssproto"
	dsserr "github.com/interuss/dss/pkg/errors"

	"github.com/golang/geo/s2"
	"github.com/golang/protobuf/ptypes"
)

var (
	// maxSubscriptionDuration is the largest allowed interval between StartTime
	// and EndTime.
	maxSubscriptionDuration = time.Hour * 24

	// maxClockSkew is the largest allowed interval between the StartTime of a new
	// subscription and the server's idea of the current time.
	maxClockSkew = time.Minute * 5
)

type Subscription struct {
	ID                ID
	Url               string
	NotificationIndex int
	Owner             Owner
	Cells             s2.CellUnion
	StartTime         *time.Time
	EndTime           *time.Time
	Version           *Version
	AltitudeHi        *float32
	AltitudeLo        *float32
}

func (s *Subscription) ToNotifyProto() *dspb.SubscriberToNotify {
	return &dspb.SubscriberToNotify{
		Url: s.Url,
		Subscriptions: []*dspb.SubscriptionState{
			&dspb.SubscriptionState{
				NotificationIndex: int32(s.NotificationIndex),
				SubscriptionId:    s.ID.String(),
			},
		},
	}
}

func (s *Subscription) ToProto() (*dspb.Subscription, error) {
	result := &dspb.Subscription{
		Id:                s.ID.String(),
		Owner:             s.Owner.String(),
		Callbacks:         &dspb.SubscriptionCallbacks{IdentificationServiceAreaUrl: s.Url},
		NotificationIndex: int32(s.NotificationIndex),
		Version:           s.Version.String(),
	}

	if s.StartTime != nil {
		ts, err := ptypes.TimestampProto(*s.StartTime)
		if err != nil {
			return nil, err
		}
		result.TimeStart = ts
	}

	if s.EndTime != nil {
		ts, err := ptypes.TimestampProto(*s.EndTime)
		if err != nil {
			return nil, err
		}
		result.TimeEnd = ts
	}
	return result, nil
}

func (s *Subscription) SetExtents(extents *dspb.Volume4D) error {
	var err error
	if extents == nil {
		return nil
	}
	if startTime := extents.GetTimeStart(); startTime != nil {
		ts, err := ptypes.Timestamp(startTime)
		if err != nil {
			return err
		}
		s.StartTime = &ts
	}

	if endTime := extents.GetTimeEnd(); endTime != nil {
		ts, err := ptypes.Timestamp(endTime)
		if err != nil {
			return err
		}
		s.EndTime = &ts
	}

	space := extents.GetSpatialVolume()
	if space == nil {
		return errors.New("missing required spatial_volume")
	}
	s.AltitudeHi = proto.Float32(space.GetAltitudeHi())
	s.AltitudeLo = proto.Float32(space.GetAltitudeLo())
	footprint := space.GetFootprint()
	if footprint == nil {
		return errors.New("spatial_volume missing required footprint")
	}
	s.Cells, err = geo.GeoPolygonToCellIDs(footprint)
	return err
}

func (s *Subscription) AdjustTimeRange(now time.Time, old *Subscription) error {
	if s.StartTime == nil {
		// If StartTime was omitted, default to Now() for new subscriptions or re-
		// use the existing time of existing subscriptions.
		if old == nil {
			s.StartTime = &now
		} else {
			s.StartTime = old.StartTime
		}
	} else {
		// If setting the StartTime explicitly ensure it is not too far in the past.
		if now.Sub(*s.StartTime) > maxClockSkew {
			return dsserr.BadRequest("subscription time_start must not be in the past")
		}
	}

	// If EndTime was omitted default to the existing subscription's EndTime.
	if s.EndTime == nil && old != nil {
		s.EndTime = old.EndTime
	}

	// Or if this is a new subscription default to StartTime + 1 day.
	if s.EndTime == nil {
		truncatedEndTime := s.StartTime.Add(maxSubscriptionDuration)
		s.EndTime = &truncatedEndTime
	}

	// EndTime cannot be before StartTime.
	if s.EndTime.Sub(*s.StartTime) < 0 {
		return dsserr.BadRequest("subscription time_end must be after time_start")
	}

	// EndTime cannot be 24 hrs after StartTime
	if s.EndTime.Sub(*s.StartTime) > maxSubscriptionDuration {
		return dsserr.BadRequest("subscription window exceeds 24 hours")
	}

	return nil
}
<|endoftext|>


<commit_before>package vsphere

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"path"
	"runtime"
	"strings"
	"sync"

	"gopkg.in/gcfg.v1"

	log "github.com/Sirupsen/logrus"
	"github.com/matt-deboer/etcdcd/pkg/platform"
	"github.com/vmware/govmomi"
	"github.com/vmware/govmomi/find"
	"github.com/vmware/govmomi/object"
	"github.com/vmware/govmomi/property"
	"github.com/vmware/govmomi/session"
	"github.com/vmware/govmomi/vim25"
	"github.com/vmware/govmomi/vim25/mo"
	"github.com/vmware/govmomi/vim25/types"
	"golang.org/x/net/context"

	etcd "github.com/coreos/etcd/client"
)

const (
	ActivePowerState         = "poweredOn"
	RoundTripperDefaultCount = 3
)

var clientLock sync.Mutex

// VSphere is an implementation of cloud provider Interface for VSphere.
type VSphere struct {
	client *govmomi.Client
	cfg    *VSphereConfig
	// InstanceID of the server where this VSphere object is instantiated.
	localInstanceID string
}

type VSphereConfig struct {
	Global struct {
		// vCenter username.
		User string `gcfg:"user"`
		// vCenter password in clear text.
		Password string `gcfg:"password"`
		// vCenter IP.
		VCenterIP string `gcfg:"server"`
		// vCenter port.
		VCenterPort string `gcfg:"port"`
		// True if vCenter uses self-signed cert.
		InsecureFlag bool `gcfg:"insecure-flag"`
		// Datacenter in which VMs are located.
		Datacenter string `gcfg:"datacenter"`
		// Datastore in which vmdks are stored.
		Datastore string `gcfg:"datastore"`
		// WorkingDir is path where VMs can be found.
		WorkingDir string `gcfg:"working-dir"`
		// Soap round tripper count (retries = RoundTripper - 1)
		RoundTripperCount uint `gcfg:"soap-roundtrip-count"`
		// VMUUID is the VM Instance UUID of virtual machine which can be retrieved from instanceUuid
		// property in VmConfigInfo, or also set as vc.uuid in VMX file.
		// If not set, will be fetched from the machine via sysfs (requires root)
		VMUUID string `gcfg:"vm-uuid"`
	}
	Network struct {
		// PublicNetwork is name of the network the VMs are joined to.
		PublicNetwork string `gcfg:"public-network"`
	}
}

func init() {
	platform.Register("vsphere", func(config io.Reader) (platform.Platform, error) {
		cfg, err := readConfig(config)
		if err != nil && strings.Contains(err.Error(), "errors") {
			log.Fatal("Failed reading config: ", err)
		}
		return newVSphere(cfg)
	})
}

// Parses vSphere cloud config file and stores it into VSphereConfig.
func readConfig(config io.Reader) (VSphereConfig, error) {
	if config == nil {
		err := fmt.Errorf("no config file given")
		return VSphereConfig{}, err
	}

	var cfg VSphereConfig
	err := gcfg.ReadInto(&cfg, config)
	return cfg, err
}

// ExpectedMembers returns a list of members that should form the cluster
func (vs *VSphere) ExpectedMembers(
	memberFilter string, clientScheme string, clientPort int, serverScheme string, serverPort int) ([]etcd.Member, error) {

	members := []etcd.Member{}
	names, err := vs.list(memberFilter)
	if err != nil {
		return nil, err
	}
	for _, name := range names {
		addrs, err := vs.getAddresses(name)
		if err != nil {
			return nil, err
		}
		member := etcd.Member{Name: name, ClientURLs: []string{}, PeerURLs: []string{}}
		for _, addr := range addrs {
			member.ClientURLs = append(member.ClientURLs, fmt.Sprintf("%s://%s:%d", clientScheme, addr, clientPort))
			member.PeerURLs = append(member.PeerURLs, fmt.Sprintf("%s://%s:%d", serverScheme, addr, serverPort))
		}

		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("ExpectedMembers: member: %#v", member)
		}
		members = append(members, member)
	}
	return members, nil
}

// LocalInstanceName returns a list of members that should form the cluster
func (vs *VSphere) LocalInstanceName() string {
	return vs.localInstanceID
}

// Returns the name of the VM on which this code is running.
// Prerequisite: this code assumes VMWare vmtools or open-vm-tools to be installed in the VM.
// Will attempt to determine the machine's name via it's UUID in this precedence order, failing if neither have a UUID:
// * cloud config value VMUUID
// * sysfs entry
func getVMName(client *govmomi.Client, cfg *VSphereConfig) (string, error) {

	var vmUUID string

	if cfg.Global.VMUUID != "" {
		vmUUID = cfg.Global.VMUUID
	} else {
		// This needs root privileges on the host, and will fail otherwise.
		vmUUIDbytes, err := ioutil.ReadFile("/sys/devices/virtual/dmi/id/product_uuid")
		if err != nil {
			return "", err
		}

		vmUUID = string(vmUUIDbytes)
		cfg.Global.VMUUID = vmUUID
	}

	if vmUUID == "" {
		return "", fmt.Errorf("unable to determine machine ID from cloud configuration or sysfs")
	}

	// Create context
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Create a new finder
	f := find.NewFinder(client.Client, true)

	// Fetch and set data center
	dc, err := f.Datacenter(ctx, cfg.Global.Datacenter)
	if err != nil {
		return "", err
	}
	f.SetDatacenter(dc)

	s := object.NewSearchIndex(client.Client)

	svm, err := s.FindByUuid(ctx, dc, strings.ToLower(strings.TrimSpace(vmUUID)), true, nil)
	if err != nil {
		return "", err
	}

	var vm mo.VirtualMachine
	err = s.Properties(ctx, svm.Reference(), []string{"name"}, &vm)
	if err != nil {
		return "", err
	}
	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("getVMName: vm.Name=%s", vm.Name)
	}
	return vm.Name, nil
}

func newVSphere(cfg VSphereConfig) (*VSphere, error) {

	if cfg.Global.WorkingDir != "" {
		cfg.Global.WorkingDir = path.Clean(cfg.Global.WorkingDir) + "/"
	}
	if cfg.Global.RoundTripperCount == 0 {
		cfg.Global.RoundTripperCount = RoundTripperDefaultCount
	}
	if cfg.Global.VCenterPort != "" {
		log.Warningf("port is a deprecated field in vsphere.conf and will be removed in future release.")
	}

	c, err := newClient(context.TODO(), &cfg)
	if err != nil {
		return nil, err
	}

	id, err := getVMName(c, &cfg)
	if err != nil {
		return nil, err
	}

	vs := VSphere{
		client:          c,
		cfg:             &cfg,
		localInstanceID: id,
	}
	runtime.SetFinalizer(&vs, logout)

	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("newVSphere: vs: %#v", vs)
	}

	return &vs, nil
}

func logout(vs *VSphere) {
	vs.client.Logout(context.TODO())
}

func newClient(ctx context.Context, cfg *VSphereConfig) (*govmomi.Client, error) {
	// Parse URL from string
	u, err := url.Parse(fmt.Sprintf("https://%s/sdk", cfg.Global.VCenterIP))
	if err != nil {
		return nil, err
	}
	// set username and password for the URL
	u.User = url.UserPassword(cfg.Global.User, cfg.Global.Password)

	// Connect and log in to ESX or vCenter
	c, err := govmomi.NewClient(ctx, u, cfg.Global.InsecureFlag)
	if err != nil {
		return nil, err
	}

	// Add retry functionality
	c.RoundTripper = vim25.Retry(c.RoundTripper, vim25.TemporaryNetworkError(int(cfg.Global.RoundTripperCount)))

	return c, nil
}

// Returns a client which communicates with vCenter.
// This client can used to perform further vCenter operations.
func vSphereLogin(ctx context.Context, vs *VSphere) error {
	var err error
	clientLock.Lock()
	defer clientLock.Unlock()
	if vs.client == nil {
		vs.client, err = newClient(ctx, vs.cfg)
		if err != nil {
			return err
		}
		return nil
	}

	m := session.NewManager(vs.client.Client)
	// retrieve client's current session
	u, err := m.UserSession(ctx)
	if err != nil {
		log.Errorf("Error while obtaining user session. err: %q", err)
		return err
	}
	if u != nil {
		return nil
	}

	log.Warningf("Creating new client session since the existing session is not valid or not authenticated")
	vs.client.Logout(ctx)
	vs.client, err = newClient(ctx, vs.cfg)
	if err != nil {
		return err
	}

	return nil
}

// Returns vSphere object `virtual machine` by its name.
func getVirtualMachineByName(ctx context.Context, cfg *VSphereConfig, c *govmomi.Client, nodeName string) (*object.VirtualMachine, error) {
	name := nodeNameToVMName(nodeName)

	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("getVirtualMachineByName: name=%s", name)
	}

	// Create a new finder
	f := find.NewFinder(c.Client, true)

	// Fetch and set data center
	dc, err := f.Datacenter(ctx, cfg.Global.Datacenter)
	if err != nil {
		return nil, err
	}
	f.SetDatacenter(dc)

	vmRegex := cfg.Global.WorkingDir + name

	// Retrieve vm by name
	//TODO: also look for vm inside subfolders
	vm, err := f.VirtualMachine(ctx, vmRegex)
	if err != nil {
		return nil, err
	}

	return vm, nil
}

func getVirtualMachineManagedObjectReference(ctx context.Context, c *govmomi.Client, vm *object.VirtualMachine, field string, dst interface{}) error {
	collector := property.DefaultCollector(c.Client)

	// Retrieve required field from VM object
	err := collector.RetrieveOne(ctx, vm.Reference(), []string{field}, dst)
	if err != nil {
		return err
	}
	return nil
}

// Returns names of running VMs inside VM folder.
func getInstances(ctx context.Context, cfg *VSphereConfig, c *govmomi.Client, filter string) ([]string, error) {
	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("getInstances: filter=%s", filter)
	}

	f := find.NewFinder(c.Client, true)
	dc, err := f.Datacenter(ctx, cfg.Global.Datacenter)
	if err != nil {
		return nil, err
	}

	f.SetDatacenter(dc)

	vmRegex := cfg.Global.WorkingDir + filter

	//TODO: get all vms inside subfolders
	vms, err := f.VirtualMachineList(ctx, vmRegex)
	if err != nil {
		return nil, err
	}

	var vmRef []types.ManagedObjectReference
	for _, vm := range vms {
		vmRef = append(vmRef, vm.Reference())
	}

	pc := property.DefaultCollector(c.Client)

	var vmt []mo.VirtualMachine
	err = pc.Retrieve(ctx, vmRef, []string{"name", "summary"}, &vmt)
	if err != nil {
		return nil, err
	}

	var vmList []string
	for _, vm := range vmt {
		if vm.Summary.Runtime.PowerState == ActivePowerState {
			vmList = append(vmList, vm.Name)
		} else if vm.Summary.Config.Template == false {
			log.Warningf("VM %s, is not in %s state", vm.Name, ActivePowerState)
		}
	}
	return vmList, nil
}

// List returns names of VMs (inside vm folder) by applying filter and which are currently running.
func (vs *VSphere) list(filter string) ([]string, error) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	vmList, err := getInstances(ctx, vs.cfg, vs.client, filter)
	if err != nil {
		return nil, err
	}

	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("Found %d instances matching %s:[ %s ]",
			len(vmList), filter, strings.Join(vmList, ", "))
	}

	var nodeNames []string
	for _, n := range vmList {
		nodeNames = append(nodeNames, n)
	}
	return nodeNames, nil
}

func (vs *VSphere) getAddresses(nodeName string) ([]string, error) {
	addrs := []string{}

	// Create context
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	vm, err := getVirtualMachineByName(ctx, vs.cfg, vs.client, nodeName)
	if err != nil {
		return nil, err
	}

	var mvm mo.VirtualMachine
	err = getVirtualMachineManagedObjectReference(ctx, vs.client, vm, "guest.net", &mvm)
	if err != nil {
		return nil, err
	}

	// retrieve VM's ip(s)
	for _, v := range mvm.Guest.Net {
		if v.Network == vs.cfg.Network.PublicNetwork {
			for _, ip := range v.IpAddress {
				addrs = append(addrs, ip)
			}
		} else if log.GetLevel() >= log.DebugLevel {
			log.Debugf("getAddresses: nodeName=%s, net %v are not in configured network", nodeName, v.IpAddress)
		}
	}
	return addrs, nil
}

// nodeNameToVMName maps a NodeName to the vmware infrastructure name
func nodeNameToVMName(nodeName string) string {
	return string(nodeName)
}
<commit_msg>wrap ipv6 addrs<commit_after>package vsphere

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"path"
	"runtime"
	"strings"
	"sync"

	"gopkg.in/gcfg.v1"

	log "github.com/Sirupsen/logrus"
	"github.com/matt-deboer/etcdcd/pkg/platform"
	"github.com/vmware/govmomi"
	"github.com/vmware/govmomi/find"
	"github.com/vmware/govmomi/object"
	"github.com/vmware/govmomi/property"
	"github.com/vmware/govmomi/session"
	"github.com/vmware/govmomi/vim25"
	"github.com/vmware/govmomi/vim25/mo"
	"github.com/vmware/govmomi/vim25/types"
	"golang.org/x/net/context"

	etcd "github.com/coreos/etcd/client"
)

const (
	ActivePowerState         = "poweredOn"
	RoundTripperDefaultCount = 3
)

var clientLock sync.Mutex

// VSphere is an implementation of cloud provider Interface for VSphere.
type VSphere struct {
	client *govmomi.Client
	cfg    *VSphereConfig
	// InstanceID of the server where this VSphere object is instantiated.
	localInstanceID string
}

type VSphereConfig struct {
	Global struct {
		// vCenter username.
		User string `gcfg:"user"`
		// vCenter password in clear text.
		Password string `gcfg:"password"`
		// vCenter IP.
		VCenterIP string `gcfg:"server"`
		// vCenter port.
		VCenterPort string `gcfg:"port"`
		// True if vCenter uses self-signed cert.
		InsecureFlag bool `gcfg:"insecure-flag"`
		// Datacenter in which VMs are located.
		Datacenter string `gcfg:"datacenter"`
		// Datastore in which vmdks are stored.
		Datastore string `gcfg:"datastore"`
		// WorkingDir is path where VMs can be found.
		WorkingDir string `gcfg:"working-dir"`
		// Soap round tripper count (retries = RoundTripper - 1)
		RoundTripperCount uint `gcfg:"soap-roundtrip-count"`
		// VMUUID is the VM Instance UUID of virtual machine which can be retrieved from instanceUuid
		// property in VmConfigInfo, or also set as vc.uuid in VMX file.
		// If not set, will be fetched from the machine via sysfs (requires root)
		VMUUID string `gcfg:"vm-uuid"`
	}
	Network struct {
		// PublicNetwork is name of the network the VMs are joined to.
		PublicNetwork string `gcfg:"public-network"`
	}
}

func init() {
	platform.Register("vsphere", func(config io.Reader) (platform.Platform, error) {
		cfg, err := readConfig(config)
		if err != nil && strings.Contains(err.Error(), "errors") {
			log.Fatal("Failed reading config: ", err)
		}
		return newVSphere(cfg)
	})
}

// Parses vSphere cloud config file and stores it into VSphereConfig.
func readConfig(config io.Reader) (VSphereConfig, error) {
	if config == nil {
		err := fmt.Errorf("no config file given")
		return VSphereConfig{}, err
	}

	var cfg VSphereConfig
	err := gcfg.ReadInto(&cfg, config)
	return cfg, err
}

// ExpectedMembers returns a list of members that should form the cluster
func (vs *VSphere) ExpectedMembers(
	memberFilter string, clientScheme string, clientPort int, serverScheme string, serverPort int) ([]etcd.Member, error) {

	members := []etcd.Member{}
	names, err := vs.list(memberFilter)
	if err != nil {
		return nil, err
	}
	for _, name := range names {
		addrs, err := vs.getAddresses(name)
		if err != nil {
			return nil, err
		}
		member := etcd.Member{Name: name, ClientURLs: []string{}, PeerURLs: []string{}}
		for _, a := range addrs {
			addr := a
			if strings.Contains(a, ":") {
				addr = "[" + a + "]"
			}
			member.ClientURLs = append(member.ClientURLs, fmt.Sprintf("%s://%s:%d", clientScheme, addr, clientPort))
			member.PeerURLs = append(member.PeerURLs, fmt.Sprintf("%s://%s:%d", serverScheme, addr, serverPort))
		}

		if log.GetLevel() >= log.DebugLevel {
			log.Debugf("ExpectedMembers: member: %#v", member)
		}
		members = append(members, member)
	}
	return members, nil
}

// LocalInstanceName returns a list of members that should form the cluster
func (vs *VSphere) LocalInstanceName() string {
	return vs.localInstanceID
}

// Returns the name of the VM on which this code is running.
// Prerequisite: this code assumes VMWare vmtools or open-vm-tools to be installed in the VM.
// Will attempt to determine the machine's name via it's UUID in this precedence order, failing if neither have a UUID:
// * cloud config value VMUUID
// * sysfs entry
func getVMName(client *govmomi.Client, cfg *VSphereConfig) (string, error) {

	var vmUUID string

	if cfg.Global.VMUUID != "" {
		vmUUID = cfg.Global.VMUUID
	} else {
		// This needs root privileges on the host, and will fail otherwise.
		vmUUIDbytes, err := ioutil.ReadFile("/sys/devices/virtual/dmi/id/product_uuid")
		if err != nil {
			return "", err
		}

		vmUUID = string(vmUUIDbytes)
		cfg.Global.VMUUID = vmUUID
	}

	if vmUUID == "" {
		return "", fmt.Errorf("unable to determine machine ID from cloud configuration or sysfs")
	}

	// Create context
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Create a new finder
	f := find.NewFinder(client.Client, true)

	// Fetch and set data center
	dc, err := f.Datacenter(ctx, cfg.Global.Datacenter)
	if err != nil {
		return "", err
	}
	f.SetDatacenter(dc)

	s := object.NewSearchIndex(client.Client)

	svm, err := s.FindByUuid(ctx, dc, strings.ToLower(strings.TrimSpace(vmUUID)), true, nil)
	if err != nil {
		return "", err
	}

	var vm mo.VirtualMachine
	err = s.Properties(ctx, svm.Reference(), []string{"name"}, &vm)
	if err != nil {
		return "", err
	}
	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("getVMName: vm.Name=%s", vm.Name)
	}
	return vm.Name, nil
}

func newVSphere(cfg VSphereConfig) (*VSphere, error) {

	if cfg.Global.WorkingDir != "" {
		cfg.Global.WorkingDir = path.Clean(cfg.Global.WorkingDir) + "/"
	}
	if cfg.Global.RoundTripperCount == 0 {
		cfg.Global.RoundTripperCount = RoundTripperDefaultCount
	}
	if cfg.Global.VCenterPort != "" {
		log.Warningf("port is a deprecated field in vsphere.conf and will be removed in future release.")
	}

	c, err := newClient(context.TODO(), &cfg)
	if err != nil {
		return nil, err
	}

	id, err := getVMName(c, &cfg)
	if err != nil {
		return nil, err
	}

	vs := VSphere{
		client:          c,
		cfg:             &cfg,
		localInstanceID: id,
	}
	runtime.SetFinalizer(&vs, logout)

	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("newVSphere: vs: %#v", vs)
	}

	return &vs, nil
}

func logout(vs *VSphere) {
	vs.client.Logout(context.TODO())
}

func newClient(ctx context.Context, cfg *VSphereConfig) (*govmomi.Client, error) {
	// Parse URL from string
	u, err := url.Parse(fmt.Sprintf("https://%s/sdk", cfg.Global.VCenterIP))
	if err != nil {
		return nil, err
	}
	// set username and password for the URL
	u.User = url.UserPassword(cfg.Global.User, cfg.Global.Password)

	// Connect and log in to ESX or vCenter
	c, err := govmomi.NewClient(ctx, u, cfg.Global.InsecureFlag)
	if err != nil {
		return nil, err
	}

	// Add retry functionality
	c.RoundTripper = vim25.Retry(c.RoundTripper, vim25.TemporaryNetworkError(int(cfg.Global.RoundTripperCount)))

	return c, nil
}

// Returns a client which communicates with vCenter.
// This client can used to perform further vCenter operations.
func vSphereLogin(ctx context.Context, vs *VSphere) error {
	var err error
	clientLock.Lock()
	defer clientLock.Unlock()
	if vs.client == nil {
		vs.client, err = newClient(ctx, vs.cfg)
		if err != nil {
			return err
		}
		return nil
	}

	m := session.NewManager(vs.client.Client)
	// retrieve client's current session
	u, err := m.UserSession(ctx)
	if err != nil {
		log.Errorf("Error while obtaining user session. err: %q", err)
		return err
	}
	if u != nil {
		return nil
	}

	log.Warningf("Creating new client session since the existing session is not valid or not authenticated")
	vs.client.Logout(ctx)
	vs.client, err = newClient(ctx, vs.cfg)
	if err != nil {
		return err
	}

	return nil
}

// Returns vSphere object `virtual machine` by its name.
func getVirtualMachineByName(ctx context.Context, cfg *VSphereConfig, c *govmomi.Client, nodeName string) (*object.VirtualMachine, error) {
	name := nodeNameToVMName(nodeName)

	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("getVirtualMachineByName: name=%s", name)
	}

	// Create a new finder
	f := find.NewFinder(c.Client, true)

	// Fetch and set data center
	dc, err := f.Datacenter(ctx, cfg.Global.Datacenter)
	if err != nil {
		return nil, err
	}
	f.SetDatacenter(dc)

	vmRegex := cfg.Global.WorkingDir + name

	// Retrieve vm by name
	//TODO: also look for vm inside subfolders
	vm, err := f.VirtualMachine(ctx, vmRegex)
	if err != nil {
		return nil, err
	}

	return vm, nil
}

func getVirtualMachineManagedObjectReference(ctx context.Context, c *govmomi.Client, vm *object.VirtualMachine, field string, dst interface{}) error {
	collector := property.DefaultCollector(c.Client)

	// Retrieve required field from VM object
	err := collector.RetrieveOne(ctx, vm.Reference(), []string{field}, dst)
	if err != nil {
		return err
	}
	return nil
}

// Returns names of running VMs inside VM folder.
func getInstances(ctx context.Context, cfg *VSphereConfig, c *govmomi.Client, filter string) ([]string, error) {
	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("getInstances: filter=%s", filter)
	}

	f := find.NewFinder(c.Client, true)
	dc, err := f.Datacenter(ctx, cfg.Global.Datacenter)
	if err != nil {
		return nil, err
	}

	f.SetDatacenter(dc)

	vmRegex := cfg.Global.WorkingDir + filter

	//TODO: get all vms inside subfolders
	vms, err := f.VirtualMachineList(ctx, vmRegex)
	if err != nil {
		return nil, err
	}

	var vmRef []types.ManagedObjectReference
	for _, vm := range vms {
		vmRef = append(vmRef, vm.Reference())
	}

	pc := property.DefaultCollector(c.Client)

	var vmt []mo.VirtualMachine
	err = pc.Retrieve(ctx, vmRef, []string{"name", "summary"}, &vmt)
	if err != nil {
		return nil, err
	}

	var vmList []string
	for _, vm := range vmt {
		if vm.Summary.Runtime.PowerState == ActivePowerState {
			vmList = append(vmList, vm.Name)
		} else if vm.Summary.Config.Template == false {
			log.Warningf("VM %s, is not in %s state", vm.Name, ActivePowerState)
		}
	}
	return vmList, nil
}

// List returns names of VMs (inside vm folder) by applying filter and which are currently running.
func (vs *VSphere) list(filter string) ([]string, error) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	vmList, err := getInstances(ctx, vs.cfg, vs.client, filter)
	if err != nil {
		return nil, err
	}

	if log.GetLevel() >= log.DebugLevel {
		log.Debugf("Found %d instances matching %s:[ %s ]",
			len(vmList), filter, strings.Join(vmList, ", "))
	}

	var nodeNames []string
	for _, n := range vmList {
		nodeNames = append(nodeNames, n)
	}
	return nodeNames, nil
}

func (vs *VSphere) getAddresses(nodeName string) ([]string, error) {
	addrs := []string{}

	// Create context
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	vm, err := getVirtualMachineByName(ctx, vs.cfg, vs.client, nodeName)
	if err != nil {
		return nil, err
	}

	var mvm mo.VirtualMachine
	err = getVirtualMachineManagedObjectReference(ctx, vs.client, vm, "guest.net", &mvm)
	if err != nil {
		return nil, err
	}

	// retrieve VM's ip(s)
	for _, v := range mvm.Guest.Net {
		if v.Network == vs.cfg.Network.PublicNetwork {
			for _, ip := range v.IpAddress {
				addrs = append(addrs, ip)
			}
		} else if log.GetLevel() >= log.DebugLevel {
			log.Debugf("getAddresses: nodeName=%s, net %v are not in configured network", nodeName, v.IpAddress)
		}
	}
	return addrs, nil
}

// nodeNameToVMName maps a NodeName to the vmware infrastructure name
func nodeNameToVMName(nodeName string) string {
	return string(nodeName)
}
<|endoftext|>


<commit_before>package smokescreen

import (
	"fmt"
	"sort"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestMetricsTags(t *testing.T) {
	r := require.New(t)

	t.Run("add custom tags", func(t *testing.T) {
		metric := "acl.allow"
		mc := NewNoOpMetricsClient()

		err := mc.AddMetricTags(metric, []string{"globalize"})
		r.NoError(err)

		tags := mc.GetMetricTags(metric)
		r.Len(tags, 1)
		r.Equal(tags[0], "globalize")

		err = mc.AddMetricTags(metric, []string{"ignore"})
		r.NoError(err)

		tags = mc.GetMetricTags(metric)
		r.Len(tags, 2)
	})

	t.Run("add invalid tags", func(t *testing.T) {
		metric := "acl.does.not.exist"
		mc := NewNoOpMetricsClient()

		err := mc.AddMetricTags(metric, []string{"globalize"})
		r.Error(err)
	})
}

func TestMetricsClient(t *testing.T) {
	r := require.New(t)

	// Passing NewMetricsClient a missing statsd address should always fail
	t.Run("nil statsd addr", func(t *testing.T) {
		mc, err := NewMetricsClient("", "test_namespace")
		r.Error(err)
		r.Nil(mc)
	})

	// MetricsClient is not thread safe. Adding a tag after smokescreen has started
	// should always return an error.
	t.Run("adding metrics after started", func(t *testing.T) {
		mc := NewNoOpMetricsClient()
		mc.started.Store(true)

		err := mc.AddMetricTags("acl.allow", []string{"globalize"})
		r.Error(err)
	})
}

type MockMetricsClient struct {
	MetricsClient

	counts map[string]uint64
	mu     sync.Mutex
}

func NewMockMetricsClient() *MockMetricsClient {
	return &MockMetricsClient{
		*NewNoOpMetricsClient(),
		make(map[string]uint64),
		sync.Mutex{},
	}
}

// countOne increments a metric count by 1, starting the count at 1 if the metric has
// not yet been counted. Call with m.mu.Lock held.
func (m *MockMetricsClient) countOne(metric string) {
	if i, ok := m.counts[metric]; ok {
		m.counts[metric] = i + 1
	} else {
		m.counts[metric] = 1
	}
}

func (m *MockMetricsClient) GetCount(metric string, tags ...string) (uint64, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	mName := metric
	sort.Strings(tags)
	if len(tags) > 0 {
		mName = fmt.Sprintf("%s %v", mName, tags)
	}
	i, ok := m.counts[mName]
	if !ok {
		keys := make([]string, len(m.counts))
		for k, _ := range m.counts {
			keys = append(keys, k)
		}
		return 0, fmt.Errorf("unknown metric %s (know %s)", mName, strings.Join(keys, ","))
	}

	return i, nil
}

func (m *MockMetricsClient) Incr(metric string, rate float64) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.countOne(metric)

	return m.MetricsClient.Incr(metric, rate)
}

func (m *MockMetricsClient) IncrWithTags(metric string, tags []string, rate float64) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	sort.Strings(tags)
	mName := fmt.Sprintf("%s %v", metric, tags)
	m.countOne(mName)

	return m.MetricsClient.IncrWithTags(metric, tags, rate)
}

func (m *MockMetricsClient) Timing(metric string, d time.Duration, rate float64) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.countOne(metric)

	return m.MetricsClient.Timing(metric, d, rate)
}

func (m *MockMetricsClient) TimingWithTags(metric string, d time.Duration, rate float64, tags []string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	sort.Strings(tags)
	mName := fmt.Sprintf("%s %v", metric, tags)
	m.countOne(mName)

	return m.MetricsClient.TimingWithTags(metric, d, rate, tags)
}

var _ MetricsClientInterface = &MockMetricsClient{}
<commit_msg>comments on {,New}MockMetricsClient<commit_after>package smokescreen

import (
	"fmt"
	"sort"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestMetricsTags(t *testing.T) {
	r := require.New(t)

	t.Run("add custom tags", func(t *testing.T) {
		metric := "acl.allow"
		mc := NewNoOpMetricsClient()

		err := mc.AddMetricTags(metric, []string{"globalize"})
		r.NoError(err)

		tags := mc.GetMetricTags(metric)
		r.Len(tags, 1)
		r.Equal(tags[0], "globalize")

		err = mc.AddMetricTags(metric, []string{"ignore"})
		r.NoError(err)

		tags = mc.GetMetricTags(metric)
		r.Len(tags, 2)
	})

	t.Run("add invalid tags", func(t *testing.T) {
		metric := "acl.does.not.exist"
		mc := NewNoOpMetricsClient()

		err := mc.AddMetricTags(metric, []string{"globalize"})
		r.Error(err)
	})
}

func TestMetricsClient(t *testing.T) {
	r := require.New(t)

	// Passing NewMetricsClient a missing statsd address should always fail
	t.Run("nil statsd addr", func(t *testing.T) {
		mc, err := NewMetricsClient("", "test_namespace")
		r.Error(err)
		r.Nil(mc)
	})

	// MetricsClient is not thread safe. Adding a tag after smokescreen has started
	// should always return an error.
	t.Run("adding metrics after started", func(t *testing.T) {
		mc := NewNoOpMetricsClient()
		mc.started.Store(true)

		err := mc.AddMetricTags("acl.allow", []string{"globalize"})
		r.Error(err)
	})
}

// MockMetricsClient is a MetricsClient that counts metric updates.
type MockMetricsClient struct {
	MetricsClient

	counts map[string]uint64
	mu     sync.Mutex
}

// NewMockMetricsClient returns a new MockMetricsClient that wraps a NoOpMetricsClient
// with counters to track metric updates.
func NewMockMetricsClient() *MockMetricsClient {
	return &MockMetricsClient{
		*NewNoOpMetricsClient(),
		make(map[string]uint64),
		sync.Mutex{},
	}
}

// countOne increments a metric count by 1, starting the count at 1 if the metric has
// not yet been counted. Call with m.mu.Lock held.
func (m *MockMetricsClient) countOne(metric string) {
	if i, ok := m.counts[metric]; ok {
		m.counts[metric] = i + 1
	} else {
		m.counts[metric] = 1
	}
}

func (m *MockMetricsClient) GetCount(metric string, tags ...string) (uint64, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	mName := metric
	sort.Strings(tags)
	if len(tags) > 0 {
		mName = fmt.Sprintf("%s %v", mName, tags)
	}
	i, ok := m.counts[mName]
	if !ok {
		keys := make([]string, len(m.counts))
		for k, _ := range m.counts {
			keys = append(keys, k)
		}
		return 0, fmt.Errorf("unknown metric %s (know %s)", mName, strings.Join(keys, ","))
	}

	return i, nil
}

func (m *MockMetricsClient) Incr(metric string, rate float64) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.countOne(metric)

	return m.MetricsClient.Incr(metric, rate)
}

func (m *MockMetricsClient) IncrWithTags(metric string, tags []string, rate float64) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	sort.Strings(tags)
	mName := fmt.Sprintf("%s %v", metric, tags)
	m.countOne(mName)

	return m.MetricsClient.IncrWithTags(metric, tags, rate)
}

func (m *MockMetricsClient) Timing(metric string, d time.Duration, rate float64) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.countOne(metric)

	return m.MetricsClient.Timing(metric, d, rate)
}

func (m *MockMetricsClient) TimingWithTags(metric string, d time.Duration, rate float64, tags []string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	sort.Strings(tags)
	mName := fmt.Sprintf("%s %v", metric, tags)
	m.countOne(mName)

	return m.MetricsClient.TimingWithTags(metric, d, rate, tags)
}

var _ MetricsClientInterface = &MockMetricsClient{}
<|endoftext|>


<commit_before>// +build windows

package windows

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"strings"
	"unsafe"

	. "github.com/Azure/go-ansiterm"
	. "github.com/Azure/go-ansiterm/winterm"
)

// ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation.
type ansiReader struct {
	file     *os.File
	fd       uintptr
	buffer   []byte
	cbBuffer int
	command  []byte
	// TODO(azlinux): Remove this and hard-code the string -- it is not going to change
	escapeSequence []byte
}

func newAnsiReader(nFile int) *ansiReader {
	file, fd := GetStdFile(nFile)
	return &ansiReader{
		file:           file,
		fd:             fd,
		command:        make([]byte, 0, ANSI_MAX_CMD_LENGTH),
		escapeSequence: []byte(KEY_ESC_CSI),
		buffer:         make([]byte, 0),
	}
}

// Close closes the wrapped file.
func (ar *ansiReader) Close() (err error) {
	return ar.file.Close()
}

// Fd returns the file descriptor of the wrapped file.
func (ar *ansiReader) Fd() uintptr {
	return ar.fd
}

// Read reads up to len(p) bytes of translated input events into p.
func (ar *ansiReader) Read(p []byte) (int, error) {
	if len(p) == 0 {
		return 0, nil
	}

	// Previously read bytes exist, read as much as we can and return
	if len(ar.buffer) > 0 {
		logger.Debugf("Reading previously cached bytes")

		originalLength := len(ar.buffer)
		copiedLength := copy(p, ar.buffer)

		if copiedLength == originalLength {
			ar.buffer = make([]byte, 0, len(p))
		} else {
			ar.buffer = ar.buffer[copiedLength:]
		}

		logger.Debugf("Read from cache p[%d]: % x", copiedLength, p)
		return copiedLength, nil
	}

	// Read and translate key events
	events, err := readInputEvents(ar.fd, len(p))
	if err != nil {
		return 0, err
	} else if len(events) == 0 {
		logger.Debug("No input events detected")
		return 0, nil
	}

	keyBytes := translateKeyEvents(events, ar.escapeSequence)

	// Save excess bytes and right-size keyBytes
	if len(keyBytes) > len(p) {
		logger.Debugf("Received %d keyBytes, only room for %d bytes", len(keyBytes), len(p))
		ar.buffer = keyBytes[len(p):]
		keyBytes = keyBytes[:len(p)]
	} else if len(keyBytes) == 0 {
		logger.Debug("No key bytes returned from the translater")
		return 0, nil
	}

	copiedLength := copy(p, keyBytes)
	if copiedLength != len(keyBytes) {
		return 0, errors.New("Unexpected copy length encountered.")
	}

	logger.Debugf("Read        p[%d]: % x", copiedLength, p)
	logger.Debugf("Read keyBytes[%d]: % x", copiedLength, keyBytes)
	return copiedLength, nil
}

// readInputEvents polls until at least one event is available.
func readInputEvents(fd uintptr, maxBytes int) ([]INPUT_RECORD, error) {
	// Determine the maximum number of records to retrieve
	// -- Cast around the type system to obtain the size of a single INPUT_RECORD.
	//    unsafe.Sizeof requires an expression vs. a type-reference; the casting
	//    tricks the type system into believing it has such an expression.
	recordSize := int(unsafe.Sizeof(*((*INPUT_RECORD)(unsafe.Pointer(&maxBytes)))))
	countRecords := maxBytes / recordSize
	if countRecords > MAX_INPUT_EVENTS {
		countRecords = MAX_INPUT_EVENTS
	}
	logger.Debugf("[windows] readInputEvents: Reading %v records (buffer size %v, record size %v)", countRecords, maxBytes, recordSize)

	// Wait for and read input events
	events := make([]INPUT_RECORD, countRecords)
	nEvents := uint32(0)
	eventsExist, err := WaitForSingleObject(fd, WAIT_INFINITE)
	if err != nil {
		return nil, err
	}

	if eventsExist {
		err = ReadConsoleInput(fd, events, &nEvents)
		if err != nil {
			return nil, err
		}
	}

	// Return a slice restricted to the number of returned records
	logger.Debugf("[windows] readInputEvents: Read %v events", nEvents)
	return events[:nEvents], nil
}

// KeyEvent Translation Helpers

var arrowKeyMapPrefix = map[WORD]string{
	VK_UP:    "%s%sA",
	VK_DOWN:  "%s%sB",
	VK_RIGHT: "%s%sC",
	VK_LEFT:  "%s%sD",
}

var keyMapPrefix = map[WORD]string{
	VK_UP:     "\x1B[%sA",
	VK_DOWN:   "\x1B[%sB",
	VK_RIGHT:  "\x1B[%sC",
	VK_LEFT:   "\x1B[%sD",
	VK_HOME:   "\x1B[1%s~", // showkey shows ^[[1
	VK_END:    "\x1B[4%s~", // showkey shows ^[[4
	VK_INSERT: "\x1B[2%s~",
	VK_DELETE: "\x1B[3%s~",
	VK_PRIOR:  "\x1B[5%s~",
	VK_NEXT:   "\x1B[6%s~",
	VK_F1:     "",
	VK_F2:     "",
	VK_F3:     "\x1B[13%s~",
	VK_F4:     "\x1B[14%s~",
	VK_F5:     "\x1B[15%s~",
	VK_F6:     "\x1B[17%s~",
	VK_F7:     "\x1B[18%s~",
	VK_F8:     "\x1B[19%s~",
	VK_F9:     "\x1B[20%s~",
	VK_F10:    "\x1B[21%s~",
	VK_F11:    "\x1B[23%s~",
	VK_F12:    "\x1B[24%s~",
}

// translateKeyEvents converts the input events into the appropriate ANSI string.
func translateKeyEvents(events []INPUT_RECORD, escapeSequence []byte) []byte {
	var buffer bytes.Buffer
	for _, event := range events {
		if event.EventType == KEY_EVENT && event.KeyEvent.KeyDown != 0 {
			buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence))
		}
	}

	return buffer.Bytes()
}

// keyToString maps the given input event record to the corresponding string.
func keyToString(keyEvent *KEY_EVENT_RECORD, escapeSequence []byte) string {
	if keyEvent.UnicodeChar == 0 {
		return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence)
	}

	_, alt, control := getControlKeys(keyEvent.ControlKeyState)
	if control {
		// TODO(azlinux): Implement following control sequences
		// <Ctrl>-D  Signals the end of input from the keyboard; also exits current shell.
		// <Ctrl>-H  Deletes the first character to the left of the cursor. Also called the ERASE key.
		// <Ctrl>-Q  Restarts printing after it has been stopped with <Ctrl>-s.
		// <Ctrl>-S  Suspends printing on the screen (does not stop the program).
		// <Ctrl>-U  Deletes all characters on the current line. Also called the KILL key.
		// <Ctrl>-E  Quits current command and creates a core

	}

	// <Alt>+Key generates ESC N Key
	if !control && alt {
		return KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar))
	}

	return string(keyEvent.UnicodeChar)
}

// formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string.
func formatVirtualKey(key WORD, controlState DWORD, escapeSequence []byte) string {
	shift, alt, control := getControlKeys(controlState)
	modifier := getControlKeysModifier(shift, alt, control, false)

	if format, ok := arrowKeyMapPrefix[key]; ok {
		return fmt.Sprintf(format, escapeSequence, modifier)
	}

	if format, ok := keyMapPrefix[key]; ok {
		return fmt.Sprintf(format, modifier)
	}

	return ""
}

// getControlKeys extracts the shift, alt, and ctrl key states.
func getControlKeys(controlState DWORD) (shift, alt, control bool) {
	shift = 0 != (controlState & SHIFT_PRESSED)
	alt = 0 != (controlState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
	control = 0 != (controlState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
	return shift, alt, control
}

// getControlKeysModifier returns the ANSI modifier for the given combination of control keys.
func getControlKeysModifier(shift, alt, control, meta bool) string {
	if shift && alt && control {
		return KEY_CONTROL_PARAM_8
	}
	if alt && control {
		return KEY_CONTROL_PARAM_7
	}
	if shift && control {
		return KEY_CONTROL_PARAM_6
	}
	if control {
		return KEY_CONTROL_PARAM_5
	}
	if shift && alt {
		return KEY_CONTROL_PARAM_4
	}
	if alt {
		return KEY_CONTROL_PARAM_3
	}
	if shift {
		return KEY_CONTROL_PARAM_2
	}
	return ""
}
<commit_msg>typofix - https://github.com/vlajos/misspell_fixer<commit_after>// +build windows

package windows

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"strings"
	"unsafe"

	. "github.com/Azure/go-ansiterm"
	. "github.com/Azure/go-ansiterm/winterm"
)

// ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation.
type ansiReader struct {
	file     *os.File
	fd       uintptr
	buffer   []byte
	cbBuffer int
	command  []byte
	// TODO(azlinux): Remove this and hard-code the string -- it is not going to change
	escapeSequence []byte
}

func newAnsiReader(nFile int) *ansiReader {
	file, fd := GetStdFile(nFile)
	return &ansiReader{
		file:           file,
		fd:             fd,
		command:        make([]byte, 0, ANSI_MAX_CMD_LENGTH),
		escapeSequence: []byte(KEY_ESC_CSI),
		buffer:         make([]byte, 0),
	}
}

// Close closes the wrapped file.
func (ar *ansiReader) Close() (err error) {
	return ar.file.Close()
}

// Fd returns the file descriptor of the wrapped file.
func (ar *ansiReader) Fd() uintptr {
	return ar.fd
}

// Read reads up to len(p) bytes of translated input events into p.
func (ar *ansiReader) Read(p []byte) (int, error) {
	if len(p) == 0 {
		return 0, nil
	}

	// Previously read bytes exist, read as much as we can and return
	if len(ar.buffer) > 0 {
		logger.Debugf("Reading previously cached bytes")

		originalLength := len(ar.buffer)
		copiedLength := copy(p, ar.buffer)

		if copiedLength == originalLength {
			ar.buffer = make([]byte, 0, len(p))
		} else {
			ar.buffer = ar.buffer[copiedLength:]
		}

		logger.Debugf("Read from cache p[%d]: % x", copiedLength, p)
		return copiedLength, nil
	}

	// Read and translate key events
	events, err := readInputEvents(ar.fd, len(p))
	if err != nil {
		return 0, err
	} else if len(events) == 0 {
		logger.Debug("No input events detected")
		return 0, nil
	}

	keyBytes := translateKeyEvents(events, ar.escapeSequence)

	// Save excess bytes and right-size keyBytes
	if len(keyBytes) > len(p) {
		logger.Debugf("Received %d keyBytes, only room for %d bytes", len(keyBytes), len(p))
		ar.buffer = keyBytes[len(p):]
		keyBytes = keyBytes[:len(p)]
	} else if len(keyBytes) == 0 {
		logger.Debug("No key bytes returned from the translator")
		return 0, nil
	}

	copiedLength := copy(p, keyBytes)
	if copiedLength != len(keyBytes) {
		return 0, errors.New("Unexpected copy length encountered.")
	}

	logger.Debugf("Read        p[%d]: % x", copiedLength, p)
	logger.Debugf("Read keyBytes[%d]: % x", copiedLength, keyBytes)
	return copiedLength, nil
}

// readInputEvents polls until at least one event is available.
func readInputEvents(fd uintptr, maxBytes int) ([]INPUT_RECORD, error) {
	// Determine the maximum number of records to retrieve
	// -- Cast around the type system to obtain the size of a single INPUT_RECORD.
	//    unsafe.Sizeof requires an expression vs. a type-reference; the casting
	//    tricks the type system into believing it has such an expression.
	recordSize := int(unsafe.Sizeof(*((*INPUT_RECORD)(unsafe.Pointer(&maxBytes)))))
	countRecords := maxBytes / recordSize
	if countRecords > MAX_INPUT_EVENTS {
		countRecords = MAX_INPUT_EVENTS
	}
	logger.Debugf("[windows] readInputEvents: Reading %v records (buffer size %v, record size %v)", countRecords, maxBytes, recordSize)

	// Wait for and read input events
	events := make([]INPUT_RECORD, countRecords)
	nEvents := uint32(0)
	eventsExist, err := WaitForSingleObject(fd, WAIT_INFINITE)
	if err != nil {
		return nil, err
	}

	if eventsExist {
		err = ReadConsoleInput(fd, events, &nEvents)
		if err != nil {
			return nil, err
		}
	}

	// Return a slice restricted to the number of returned records
	logger.Debugf("[windows] readInputEvents: Read %v events", nEvents)
	return events[:nEvents], nil
}

// KeyEvent Translation Helpers

var arrowKeyMapPrefix = map[WORD]string{
	VK_UP:    "%s%sA",
	VK_DOWN:  "%s%sB",
	VK_RIGHT: "%s%sC",
	VK_LEFT:  "%s%sD",
}

var keyMapPrefix = map[WORD]string{
	VK_UP:     "\x1B[%sA",
	VK_DOWN:   "\x1B[%sB",
	VK_RIGHT:  "\x1B[%sC",
	VK_LEFT:   "\x1B[%sD",
	VK_HOME:   "\x1B[1%s~", // showkey shows ^[[1
	VK_END:    "\x1B[4%s~", // showkey shows ^[[4
	VK_INSERT: "\x1B[2%s~",
	VK_DELETE: "\x1B[3%s~",
	VK_PRIOR:  "\x1B[5%s~",
	VK_NEXT:   "\x1B[6%s~",
	VK_F1:     "",
	VK_F2:     "",
	VK_F3:     "\x1B[13%s~",
	VK_F4:     "\x1B[14%s~",
	VK_F5:     "\x1B[15%s~",
	VK_F6:     "\x1B[17%s~",
	VK_F7:     "\x1B[18%s~",
	VK_F8:     "\x1B[19%s~",
	VK_F9:     "\x1B[20%s~",
	VK_F10:    "\x1B[21%s~",
	VK_F11:    "\x1B[23%s~",
	VK_F12:    "\x1B[24%s~",
}

// translateKeyEvents converts the input events into the appropriate ANSI string.
func translateKeyEvents(events []INPUT_RECORD, escapeSequence []byte) []byte {
	var buffer bytes.Buffer
	for _, event := range events {
		if event.EventType == KEY_EVENT && event.KeyEvent.KeyDown != 0 {
			buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence))
		}
	}

	return buffer.Bytes()
}

// keyToString maps the given input event record to the corresponding string.
func keyToString(keyEvent *KEY_EVENT_RECORD, escapeSequence []byte) string {
	if keyEvent.UnicodeChar == 0 {
		return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence)
	}

	_, alt, control := getControlKeys(keyEvent.ControlKeyState)
	if control {
		// TODO(azlinux): Implement following control sequences
		// <Ctrl>-D  Signals the end of input from the keyboard; also exits current shell.
		// <Ctrl>-H  Deletes the first character to the left of the cursor. Also called the ERASE key.
		// <Ctrl>-Q  Restarts printing after it has been stopped with <Ctrl>-s.
		// <Ctrl>-S  Suspends printing on the screen (does not stop the program).
		// <Ctrl>-U  Deletes all characters on the current line. Also called the KILL key.
		// <Ctrl>-E  Quits current command and creates a core

	}

	// <Alt>+Key generates ESC N Key
	if !control && alt {
		return KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar))
	}

	return string(keyEvent.UnicodeChar)
}

// formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string.
func formatVirtualKey(key WORD, controlState DWORD, escapeSequence []byte) string {
	shift, alt, control := getControlKeys(controlState)
	modifier := getControlKeysModifier(shift, alt, control, false)

	if format, ok := arrowKeyMapPrefix[key]; ok {
		return fmt.Sprintf(format, escapeSequence, modifier)
	}

	if format, ok := keyMapPrefix[key]; ok {
		return fmt.Sprintf(format, modifier)
	}

	return ""
}

// getControlKeys extracts the shift, alt, and ctrl key states.
func getControlKeys(controlState DWORD) (shift, alt, control bool) {
	shift = 0 != (controlState & SHIFT_PRESSED)
	alt = 0 != (controlState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
	control = 0 != (controlState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
	return shift, alt, control
}

// getControlKeysModifier returns the ANSI modifier for the given combination of control keys.
func getControlKeysModifier(shift, alt, control, meta bool) string {
	if shift && alt && control {
		return KEY_CONTROL_PARAM_8
	}
	if alt && control {
		return KEY_CONTROL_PARAM_7
	}
	if shift && control {
		return KEY_CONTROL_PARAM_6
	}
	if control {
		return KEY_CONTROL_PARAM_5
	}
	if shift && alt {
		return KEY_CONTROL_PARAM_4
	}
	if alt {
		return KEY_CONTROL_PARAM_3
	}
	if shift {
		return KEY_CONTROL_PARAM_2
	}
	return ""
}
<|endoftext|>