<commit_before>package httpproxy

import (
	"bytes"
	"crypto/md5"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/endpoints"
	v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
	"github.com/rancher/norman/httperror"
	v1 "github.com/rancher/rancher/pkg/generated/norman/core/v1"
)

const (
	defaultAWSRegion      = "us-east-1"
	defaultUSGovAWSRegion = "us-gov-west-1"
)

var requiredHeadersForAws = map[string]bool{"host": true,
	"x-amz-content-sha256": true,
	"x-amz-date":           true,
	"x-amz-user-agent":     true}

type SecretGetter func(namespace, name string) (*v1.Secret, error)

type Signer interface {
	sign(*http.Request, SecretGetter, string) error
}

func newSigner(auth string) Signer {
	splitAuth := strings.Split(auth, " ")
	switch strings.ToLower(splitAuth[0]) {
	case "awsv4":
		return awsv4{}
	case "bearer":
		return bearer{}
	case "basic":
		return basic{}
	case "digest":
		return digest{}
	case "arbitrary":
		return arbitrary{}
	}
	return nil
}

func (br bearer) sign(req *http.Request, secrets SecretGetter, auth string) error {
	data, secret, err := getAuthData(auth, secrets, []string{"passwordField", "credID"})
	if err != nil {
		return err
	}
	req.Header.Set(AuthHeader, fmt.Sprintf("%s %s", "Bearer", secret[data["passwordField"]]))
	return nil
}

func (b basic) sign(req *http.Request, secrets SecretGetter, auth string) error {
	data, secret, err := getAuthData(auth, secrets, []string{"usernameField", "passwordField", "credID"})
	if err != nil {
		return err
	}
	key := fmt.Sprintf("%s:%s", secret[data["usernameField"]], secret[data["passwordField"]])
	encoded := base64.URLEncoding.EncodeToString([]byte(key))
	req.Header.Set(AuthHeader, fmt.Sprintf("%s %s", "Basic", encoded))
	return nil
}

func (a awsv4) sign(req *http.Request, secrets SecretGetter, auth string) error {
	_, secret, err := getAuthData(auth, secrets, []string{"credID"})
	if err != nil {
		return err
	}
	service, region := a.getServiceAndRegion(req.URL.Host)
	creds := credentials.NewStaticCredentials(secret["accessKey"], secret["secretKey"], "")
	awsSigner := v4.NewSigner(creds)
	var body []byte
	if req.Body != nil {
		body, err = ioutil.ReadAll(req.Body)
		if err != nil {
			return fmt.Errorf("error reading request body %v", err)
		}
	}
	oldHeader, newHeader := http.Header{}, http.Header{}
	for header, value := range req.Header {
		if _, ok := requiredHeadersForAws[strings.ToLower(header)]; ok {
			newHeader[header] = value
		} else {
			oldHeader[header] = value
		}
	}
	req.Header = newHeader
	_, err = awsSigner.Sign(req, bytes.NewReader(body), service, region, time.Now())
	if err != nil {
		return err
	}
	for key, val := range oldHeader {
		req.Header.Add(key, strings.Join(val, ""))
	}
	return nil
}

func (a awsv4) getServiceAndRegion(host string) (string, string) {
	service := ""
	region := ""
	for _, partition := range endpoints.DefaultPartitions() {
		service, region = partitionServiceAndRegion(partition, host)
		// empty region is valid, but if one is found it should be assumed correct
		if region != "" {
			return service, region
		}
	}

	// if no region is found, global endpoint is assumed.
	// https://docs.aws.amazon.com/general/latest/gr/sigv4_elements.html
	if strings.Contains(host, "us-gov") {
		return service, defaultUSGovAWSRegion
	}

	return service, defaultAWSRegion
}

func partitionServiceAndRegion(partition endpoints.Partition, host string) (string, string) {
	service := ""
	partitionServices := partition.Services()
	for _, part := range strings.Split(host, ".") {
		if id := partitionServices[part].ID(); id != "" {
			service = id
			break
		}
	}

	if service == "" {
		return "", ""
	}

	host = strings.Trim(host, service)
	serviceRegions := partitionServices[service].Regions()
	for _, part := range strings.Split(host, ".") {
		if id := serviceRegions[part].ID(); id != "" {
			return service, id
		}
	}
	return service, ""
}

func (d digest) sign(req *http.Request, secrets SecretGetter, auth string) error {
	data, secret, err := getAuthData(auth, secrets, []string{"usernameField", "passwordField", "credID"})
	if err != nil {
		return err
	}
	resp, err := doNewRequest(req) // request to get challenge fields from server
	if err != nil {
		return err
	}
	challengeData, err := parseChallenge(resp.Header.Get("WWW-Authenticate"))
	if err != nil {
		return err
	}
	challengeData["username"] = secret[data["usernameField"]]
	challengeData["password"] = secret[data["passwordField"]]
	signature, err := buildSignature(challengeData, req)
	if err != nil {
		return err
	}
	req.Header.Set(AuthHeader, fmt.Sprintf("%s %s", "Digest", signature))
	return nil
}

func doNewRequest(req *http.Request) (*http.Response, error) {
	newReq, err := http.NewRequest(req.Method, req.URL.String(), nil)
	if err != nil {
		return nil, err
	}
	newReq.Header.Set("Content-Type", "application/json")
	client := http.Client{}
	resp, err := client.Do(newReq)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != httperror.Unauthorized.Status {
		return nil, fmt.Errorf("expected 401 status code, got %v", resp.StatusCode)
	}
	resp.Body.Close()
	return resp, err
}

func parseChallenge(header string) (map[string]string, error) {
	if header == "" {
		return nil, fmt.Errorf("failed to get WWW-Authenticate header")
	}
	s := strings.Trim(header, " \n\r\t")
	if !strings.HasPrefix(s, "Digest ") {
		return nil, fmt.Errorf("bad challenge %s", header)
	}
	data := map[string]string{}
	s = strings.Trim(s[7:], " \n\r\t")
	terms := strings.Split(s, ", ")
	for _, term := range terms {
		splitTerm := strings.SplitN(term, "=", 2)
		data[splitTerm[0]] = strings.Trim(splitTerm[1], "\"")
	}
	return data, nil
}

func formResponse(qop string, data map[string]string, req *http.Request) (string, string) {
	hash1 := hash(fmt.Sprintf("%s:%s:%s", data["username"], data["realm"], data["password"]))
	hash2 := hash(fmt.Sprintf("%s:%s", req.Method, req.URL.Path))
	if qop == "" {
		return hash(fmt.Sprintf("%s:%s:%s", hash1, data["nonce"], hash2)), ""

	} else if qop == "auth" {
		cnonce := data["cnonce"]
		if cnonce == "" {
			cnonce = getCnonce()
		}
		return hash(fmt.Sprintf("%s:%s:%08x:%s:%s:%s",
			hash1, data["nonce"], 00000001, cnonce, qop, hash2)), cnonce
	}
	return "", ""
}

func buildSignature(data map[string]string, req *http.Request) (string, error) {
	qop, ok := data["qop"]
	if ok && qop != "auth" && qop != "" {
		return "", fmt.Errorf("qop not implemented %s", data["qop"])
	}
	response, cnonce := formResponse(qop, data, req)
	if response == "" {
		return "", fmt.Errorf("error forming response qop: %s", qop)
	}
	auth := []string{fmt.Sprintf(`username="%s"`, data["username"])}
	auth = append(auth, fmt.Sprintf(`realm="%s"`, data["realm"]))
	auth = append(auth, fmt.Sprintf(`nonce="%s"`, data["nonce"]))
	auth = append(auth, fmt.Sprintf(`uri="%s"`, req.URL.Path))
	auth = append(auth, fmt.Sprintf(`response="%s"`, response))
	if val, ok := data["opaque"]; ok && val != "" {
		auth = append(auth, fmt.Sprintf(`opaque="%s"`, data["opaque"]))
	}
	if qop != "" {
		auth = append(auth, fmt.Sprintf("qop=%s", qop))
		auth = append(auth, fmt.Sprintf("nc=%08x", 00000001))
		auth = append(auth, fmt.Sprintf("cnonce=%s", cnonce))
	}
	return strings.Join(auth, ", "), nil
}

func hash(field string) string {
	f := md5.New()
	f.Write([]byte(field))
	return hex.EncodeToString(f.Sum(nil))
}

func getCnonce() string {
	b := make([]byte, 8)
	io.ReadFull(rand.Reader, b)
	return fmt.Sprintf("%x", b)[:16]
}

func (a arbitrary) sign(req *http.Request, secrets SecretGetter, auth string) error {
	data, _, err := getAuthData(auth, secrets, []string{})
	if err != nil {
		return err
	}
	splitHeaders := strings.Split(data["headers"], ",")
	for _, header := range splitHeaders {
		val := strings.SplitN(header, "=", 2)
		req.Header.Set(val[0], val[1])
	}
	return nil
}

type awsv4 struct{}

type bearer struct{}

type basic struct{}

type digest struct{}

type arbitrary struct{}
<commit_msg>Fix wrong region for China IAM service<commit_after>package httpproxy

import (
	"bytes"
	"crypto/md5"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/endpoints"
	v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
	"github.com/rancher/norman/httperror"
	v1 "github.com/rancher/rancher/pkg/generated/norman/core/v1"
)

const (
	defaultAWSRegion      = "us-east-1"
	defaultUSGovAWSRegion = "us-gov-west-1"
	cnNorth1AWSRegion     = "cn-north-1"
	cnNorthwest1AWSRegion = "cn-northwest-1"
)

var requiredHeadersForAws = map[string]bool{"host": true,
	"x-amz-content-sha256": true,
	"x-amz-date":           true,
	"x-amz-user-agent":     true}

type SecretGetter func(namespace, name string) (*v1.Secret, error)

type Signer interface {
	sign(*http.Request, SecretGetter, string) error
}

func newSigner(auth string) Signer {
	splitAuth := strings.Split(auth, " ")
	switch strings.ToLower(splitAuth[0]) {
	case "awsv4":
		return awsv4{}
	case "bearer":
		return bearer{}
	case "basic":
		return basic{}
	case "digest":
		return digest{}
	case "arbitrary":
		return arbitrary{}
	}
	return nil
}

func (br bearer) sign(req *http.Request, secrets SecretGetter, auth string) error {
	data, secret, err := getAuthData(auth, secrets, []string{"passwordField", "credID"})
	if err != nil {
		return err
	}
	req.Header.Set(AuthHeader, fmt.Sprintf("%s %s", "Bearer", secret[data["passwordField"]]))
	return nil
}

func (b basic) sign(req *http.Request, secrets SecretGetter, auth string) error {
	data, secret, err := getAuthData(auth, secrets, []string{"usernameField", "passwordField", "credID"})
	if err != nil {
		return err
	}
	key := fmt.Sprintf("%s:%s", secret[data["usernameField"]], secret[data["passwordField"]])
	encoded := base64.URLEncoding.EncodeToString([]byte(key))
	req.Header.Set(AuthHeader, fmt.Sprintf("%s %s", "Basic", encoded))
	return nil
}

func (a awsv4) sign(req *http.Request, secrets SecretGetter, auth string) error {
	_, secret, err := getAuthData(auth, secrets, []string{"credID"})
	if err != nil {
		return err
	}
	service, region := a.getServiceAndRegion(req.URL.Host)
	creds := credentials.NewStaticCredentials(secret["accessKey"], secret["secretKey"], "")
	awsSigner := v4.NewSigner(creds)
	var body []byte
	if req.Body != nil {
		body, err = ioutil.ReadAll(req.Body)
		if err != nil {
			return fmt.Errorf("error reading request body %v", err)
		}
	}
	oldHeader, newHeader := http.Header{}, http.Header{}
	for header, value := range req.Header {
		if _, ok := requiredHeadersForAws[strings.ToLower(header)]; ok {
			newHeader[header] = value
		} else {
			oldHeader[header] = value
		}
	}
	req.Header = newHeader
	_, err = awsSigner.Sign(req, bytes.NewReader(body), service, region, time.Now())
	if err != nil {
		return err
	}
	for key, val := range oldHeader {
		req.Header.Add(key, strings.Join(val, ""))
	}
	return nil
}

func (a awsv4) getServiceAndRegion(host string) (string, string) {
	service := ""
	region := ""
	for _, partition := range endpoints.DefaultPartitions() {
		service, region = partitionServiceAndRegion(partition, host)
		// empty region is valid, but if one is found it should be assumed correct
		if region != "" {
			return service, region
		}
	}
	if strings.EqualFold(service, "iam") {
		// This conditional is meant to cover a discrepancy in the IAM service for the China regions.
		// The following doc states that IAM uses a globally unique endpoint, and the default
		// region "us-east-1" should be used as part of the Credential authentication parameter
		// (Current backend behavior). However, using "us-east-1" with any of the China regions will throw
		// the error "SignatureDoesNotMatch: Credential should be scoped to a valid region, not 'us-east-1'.".
		// https://docs.aws.amazon.com/general/latest/gr/sigv4_elements.html
		//
		// This other doc states the region value for China services should be "cn-north-1" or "cn-northwest-1"
		// including IAM (See IAM endpoints in the tables). So they need to be set manually to prevent the error
		// caused by the "us-east-1" default.
		// https://docs.amazonaws.cn/en_us/aws/latest/userguide/endpoints-Beijing.html
		if strings.Contains(host, cnNorth1AWSRegion) {
			return service, cnNorth1AWSRegion
		}
		if strings.Contains(host, cnNorthwest1AWSRegion) {
			return service, cnNorthwest1AWSRegion
		}
	}
	// if no region is found, global endpoint is assumed.
	// https://docs.aws.amazon.com/general/latest/gr/sigv4_elements.html
	if strings.Contains(host, "us-gov") {
		return service, defaultUSGovAWSRegion
	}

	return service, defaultAWSRegion
}

func partitionServiceAndRegion(partition endpoints.Partition, host string) (string, string) {
	service := ""
	partitionServices := partition.Services()
	for _, part := range strings.Split(host, ".") {
		if id := partitionServices[part].ID(); id != "" {
			service = id
			break
		}
	}

	if service == "" {
		return "", ""
	}

	host = strings.Trim(host, service)
	serviceRegions := partitionServices[service].Regions()
	for _, part := range strings.Split(host, ".") {
		if id := serviceRegions[part].ID(); id != "" {
			return service, id
		}
	}
	return service, ""
}

func (d digest) sign(req *http.Request, secrets SecretGetter, auth string) error {
	data, secret, err := getAuthData(auth, secrets, []string{"usernameField", "passwordField", "credID"})
	if err != nil {
		return err
	}
	resp, err := doNewRequest(req) // request to get challenge fields from server
	if err != nil {
		return err
	}
	challengeData, err := parseChallenge(resp.Header.Get("WWW-Authenticate"))
	if err != nil {
		return err
	}
	challengeData["username"] = secret[data["usernameField"]]
	challengeData["password"] = secret[data["passwordField"]]
	signature, err := buildSignature(challengeData, req)
	if err != nil {
		return err
	}
	req.Header.Set(AuthHeader, fmt.Sprintf("%s %s", "Digest", signature))
	return nil
}

func doNewRequest(req *http.Request) (*http.Response, error) {
	newReq, err := http.NewRequest(req.Method, req.URL.String(), nil)
	if err != nil {
		return nil, err
	}
	newReq.Header.Set("Content-Type", "application/json")
	client := http.Client{}
	resp, err := client.Do(newReq)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != httperror.Unauthorized.Status {
		return nil, fmt.Errorf("expected 401 status code, got %v", resp.StatusCode)
	}
	resp.Body.Close()
	return resp, err
}

func parseChallenge(header string) (map[string]string, error) {
	if header == "" {
		return nil, fmt.Errorf("failed to get WWW-Authenticate header")
	}
	s := strings.Trim(header, " \n\r\t")
	if !strings.HasPrefix(s, "Digest ") {
		return nil, fmt.Errorf("bad challenge %s", header)
	}
	data := map[string]string{}
	s = strings.Trim(s[7:], " \n\r\t")
	terms := strings.Split(s, ", ")
	for _, term := range terms {
		splitTerm := strings.SplitN(term, "=", 2)
		data[splitTerm[0]] = strings.Trim(splitTerm[1], "\"")
	}
	return data, nil
}

func formResponse(qop string, data map[string]string, req *http.Request) (string, string) {
	hash1 := hash(fmt.Sprintf("%s:%s:%s", data["username"], data["realm"], data["password"]))
	hash2 := hash(fmt.Sprintf("%s:%s", req.Method, req.URL.Path))
	if qop == "" {
		return hash(fmt.Sprintf("%s:%s:%s", hash1, data["nonce"], hash2)), ""

	} else if qop == "auth" {
		cnonce := data["cnonce"]
		if cnonce == "" {
			cnonce = getCnonce()
		}
		return hash(fmt.Sprintf("%s:%s:%08x:%s:%s:%s",
			hash1, data["nonce"], 00000001, cnonce, qop, hash2)), cnonce
	}
	return "", ""
}

func buildSignature(data map[string]string, req *http.Request) (string, error) {
	qop, ok := data["qop"]
	if ok && qop != "auth" && qop != "" {
		return "", fmt.Errorf("qop not implemented %s", data["qop"])
	}
	response, cnonce := formResponse(qop, data, req)
	if response == "" {
		return "", fmt.Errorf("error forming response qop: %s", qop)
	}
	auth := []string{fmt.Sprintf(`username="%s"`, data["username"])}
	auth = append(auth, fmt.Sprintf(`realm="%s"`, data["realm"]))
	auth = append(auth, fmt.Sprintf(`nonce="%s"`, data["nonce"]))
	auth = append(auth, fmt.Sprintf(`uri="%s"`, req.URL.Path))
	auth = append(auth, fmt.Sprintf(`response="%s"`, response))
	if val, ok := data["opaque"]; ok && val != "" {
		auth = append(auth, fmt.Sprintf(`opaque="%s"`, data["opaque"]))
	}
	if qop != "" {
		auth = append(auth, fmt.Sprintf("qop=%s", qop))
		auth = append(auth, fmt.Sprintf("nc=%08x", 00000001))
		auth = append(auth, fmt.Sprintf("cnonce=%s", cnonce))
	}
	return strings.Join(auth, ", "), nil
}

func hash(field string) string {
	f := md5.New()
	f.Write([]byte(field))
	return hex.EncodeToString(f.Sum(nil))
}

func getCnonce() string {
	b := make([]byte, 8)
	io.ReadFull(rand.Reader, b)
	return fmt.Sprintf("%x", b)[:16]
}

func (a arbitrary) sign(req *http.Request, secrets SecretGetter, auth string) error {
	data, _, err := getAuthData(auth, secrets, []string{})
	if err != nil {
		return err
	}
	splitHeaders := strings.Split(data["headers"], ",")
	for _, header := range splitHeaders {
		val := strings.SplitN(header, "=", 2)
		req.Header.Set(val[0], val[1])
	}
	return nil
}

type awsv4 struct{}

type bearer struct{}

type basic struct{}

type digest struct{}

type arbitrary struct{}
<|endoftext|>


<commit_before>package message

import (
	"fmt"
	"regexp"

	"slackbot_atlassian/atlassian"
	"slackbot_atlassian/config"
	"slackbot_atlassian/log"
)

type Message struct {
	SlackChannel string
	AsUser       config.SlackUser
	Text         string
}

type MessageMatcher interface {
	GetMatchingMessages([]*config.MessageTrigger, ...atlassian.ActivityIssue) []Message
}

type matcher struct {
	cfg                config.SlackConfig
	custom_jira_fields []config.CustomJiraFieldConfig
}

func NewMessageMatcher(cfg config.SlackConfig, custom_jira_fields ...config.CustomJiraFieldConfig) MessageMatcher {
	return matcher{cfg, custom_jira_fields}
}

func (m matcher) GetMatchingMessages(triggers []*config.MessageTrigger, activity_issues ...atlassian.ActivityIssue) []Message {
	messages := make([]Message, 0)

	for _, activity_issue := range activity_issues {
		for _, trigger := range triggers {
			if match, ok, err := m.get_match(trigger, activity_issue); ok && err != nil {
				messages = append(messages, match.get_messages()...)
			} else if err != nil {
				log.LogF("Error matching issue %v: %s", activity_issue, err)
			}
		}
	}

	return messages
}

func (m matcher) get_match(trigger *config.MessageTrigger, activity_issue atlassian.ActivityIssue) (*match, bool, error) {
	for name, match := range trigger.GetCompiledMatches() {
		// Look up the value for this field
		field_val, ok, err := m.get_trigger_field_value(name, activity_issue)
		if err != nil || !ok {
			return nil, ok, err
		}

		if !match.MatchString(field_val) {
			return nil, false, nil
		}
	}

	return &match{m.cfg.Users, trigger, activity_issue}, true, nil
}

func (m matcher) get_trigger_field_value(name string, activity_issue atlassian.ActivityIssue) (string, bool, error) {
	// First, check if this is a custom field defined by the JSON
	for _, cf := range m.custom_jira_fields {
		if cf.Name == name {
			val, ok := activity_issue.Issue.Fields[cf.JiraField]
			if !ok {
				return "", false, nil
			}
			s, ok := val.(string)
			if !ok {
				return "", false, fmt.Errorf("Wrong type for %s / %s: want string, have %T", cf.Name, cf.JiraField, val)
			}
			return s, ok, nil
		}
	}

	// Try to get this as a field from the issue
	if val, ok := activity_issue.Issue.Fields[name]; ok {
		s, ok := val.(string)
		if !ok {
			return "", false, fmt.Errorf("Wrong type for %s: want string, have %T", name, val)
		}
		return s, ok, nil
	}

	return "", false, nil
}

type match struct {
	users          map[string]config.SlackUser
	trigger        *config.MessageTrigger
	activity_issue atlassian.ActivityIssue
}

func (m match) get_messages() []Message {
	message := Message{
		m.trigger.SlackChannel,
		config.SlackUser{
			Name: m.activity_issue.Activity.Author.Name,
		},
		GetTextFromActivityItem(m.activity_issue.Activity),
	}
	return []Message{message}
}

func GetTextFromActivityItem(activity *atlassian.ActivityItem) string {
	// Strip name from start of title
	re := regexp.MustCompile("^<a.+?")
	text := re.ReplaceAllString(activity.Title, "")

	// Convert HTML links
	re = regexp.MustCompile(`<a .*?href="(.+?)".*?>(.+?)</a>`)
	text = re.ReplaceAllString(text, "<$1|$2>")

	// Strip duplicate whitespace
	re = regexp.MustCompile(" +")
	text = re.ReplaceAllString(text, " ")

	return text
}
<commit_msg>fix custom field check<commit_after>package message

import (
	"fmt"
	"regexp"

	"slackbot_atlassian/atlassian"
	"slackbot_atlassian/config"
	"slackbot_atlassian/log"
)

type Message struct {
	SlackChannel string
	AsUser       config.SlackUser
	Text         string
}

type MessageMatcher interface {
	GetMatchingMessages([]*config.MessageTrigger, ...atlassian.ActivityIssue) []Message
}

type matcher struct {
	cfg                config.SlackConfig
	custom_jira_fields []config.CustomJiraFieldConfig
}

func NewMessageMatcher(cfg config.SlackConfig, custom_jira_fields ...config.CustomJiraFieldConfig) MessageMatcher {
	return matcher{cfg, custom_jira_fields}
}

func (m matcher) GetMatchingMessages(triggers []*config.MessageTrigger, activity_issues ...atlassian.ActivityIssue) []Message {
	messages := make([]Message, 0)

	for _, activity_issue := range activity_issues {
		for _, trigger := range triggers {
			if match, ok, err := m.get_match(trigger, activity_issue); ok && err != nil {
				messages = append(messages, match.get_messages()...)
			} else if err != nil {
				log.LogF("Error matching issue %v: %s", activity_issue, err)
			}
		}
	}

	return messages
}

func (m matcher) get_match(trigger *config.MessageTrigger, activity_issue atlassian.ActivityIssue) (*match, bool, error) {
	for name, match := range trigger.GetCompiledMatches() {
		// Look up the value for this field
		field_val, ok, err := m.get_trigger_field_value(name, activity_issue)
		if err != nil || !ok {
			return nil, ok, err
		}

		if !match.MatchString(field_val) {
			return nil, false, nil
		}
	}

	return &match{m.cfg.Users, trigger, activity_issue}, true, nil
}

func (m matcher) get_trigger_field_value(name string, activity_issue atlassian.ActivityIssue) (string, bool, error) {
	// First, check if this is a custom field defined by the JSON
	for _, cf := range m.custom_jira_fields {
		if cf.Name == name {
			val, ok := activity_issue.Issue.Fields[cf.JiraField]
			if !ok {
				return "", false, nil
			}
			field, ok := val.(map[string]interface{})
			if !ok {
				return "", false, fmt.Errorf("Wrong type for %s / %s: want string, have %T", cf.Name, cf.JiraField, val)
			}
			value := field["value"].(string)
			return value, ok, nil
		}
	}

	// Try to get this as a field from the issue
	if val, ok := activity_issue.Issue.Fields[name]; ok {
		s, ok := val.(string)
		if !ok {
			return "", false, fmt.Errorf("Wrong type for %s: want string, have %T", name, val)
		}
		return s, ok, nil
	}

	return "", false, nil
}

type match struct {
	users          map[string]config.SlackUser
	trigger        *config.MessageTrigger
	activity_issue atlassian.ActivityIssue
}

func (m match) get_messages() []Message {
	message := Message{
		m.trigger.SlackChannel,
		config.SlackUser{
			Name: m.activity_issue.Activity.Author.Name,
		},
		GetTextFromActivityItem(m.activity_issue.Activity),
	}
	return []Message{message}
}

func GetTextFromActivityItem(activity *atlassian.ActivityItem) string {
	// Strip name from start of title
	re := regexp.MustCompile("^<a.+?")
	text := re.ReplaceAllString(activity.Title, "")

	// Convert HTML links
	re = regexp.MustCompile(`<a .*?href="(.+?)".*?>(.+?)</a>`)
	text = re.ReplaceAllString(text, "<$1|$2>")

	// Strip duplicate whitespace
	re = regexp.MustCompile(" +")
	text = re.ReplaceAllString(text, " ")

	return text
}
<|endoftext|>


<commit_before>/*
tsuru is a command line tool for application developers.

It provide some commands that allow a developer to register himself/herself,
manage teams, apps and services.

Usage:

	% tsuru <command> [args]

The currently available commands are (grouped by subject):

    target            changes or retrive the current tsuru server
    version           displays current tsuru version

    user-create       creates a new user
    login             authenticates the user with tsuru server
    logout            finishes the session with tsuru server
    key-add           adds a public key to tsuru deploy server
    key-remove        removes a public key from tsuru deploy server

    team-create       creates a new team (adding the current user to it automatically)
    team-list         list teams that the user is member
    team-user-add     adds a user to a team
    team-user-remove  removes a user from a team

    app-create        creates an app
    app-remove        removes an app
    app-list          lists apps that the user has access (see app-grant and team-user-add)
    app-grant         allows a team to have access to an app
    app-revoke        revokes access to an app from a team
    log               shows log for an app
    run               runs a command in all units of an app
    restart           restarts the app's application server

    env-get           display environment variables for an app
    env-set           set environment variable(s) to an app
    env-unset         unset environment variable(s) from an app

    bind              binds an app to a service instance
    unbind            unbinds an app from a service instance

    service-list      list all services, and instances of each service
    service-add       creates a new instance of a service
    service-remove    removes a instance of a service
    service-status    checks the status of a service instance
    service-info      list instances of a service, and apps binded to each instance
    service-doc       displays documentation for a service

Use "tsuru help <command>" for more information about a command.

Change/retrieve remote tsuru server

Usage:

	% tsuru target [target]

This command should be used to get current tsuru target, or retrieve current
target.

The target is the tsuru server to which all operations will be directed to.

Create a user

Usage:

	% tsuru user-create <email>

user-create creates a user within tsuru remote server. It will ask for the
password before issue the request.

Authenticate within remote tsuru server

Usage:

	% tsuru login <email>

Login will ask for the password and check if the user is successfully
authenticated. If so, the token generated by the tsuru server will be stored in
${HOME}/.tsuru_token.

All tsuru actions require the user to be authenticated (except login and
user-create, obviously).

Logout from remote tsuru server

Usage:

	% tsuru logout

Logout will delete the token file and terminate the session within tsuru
server.

Add SSH public key to tsuru's git server

Usage:

	% tsuru key-add [${HOME}/.ssh/id_rsa.pub]

key-add sends your public key to tsuru's git server. By default, it will try
send a public RSA key, located at ${HOME}/.ssh/id_rsa.pub. If you want to send
other file, you can call it with the path to the file. For example:

	% tsuru key-add /etc/my-keys/id_dsa.pub

The key will be added to the current logged in user.

Remove SSH public key from tsuru's git server

Usage:

	% tsuru key-remove [${HOME}/.ssh/id_rsa.pub]

key-remove removes your public key from tsuru's git server. By default, it will
try to remove a key that match you public RSA key located at
${HOME}/.ssh/id_rsa.pub. If you want to remove a key located somewhere else,
you can pass it as parameter to key-remove:

	% tsuru key-remove /etc/my-keys/id_dsa.pub

The key will be removed from the current logged in user.

Create a new team for the user

Usage:

	% tsuru team-create <teamname>

team-create will create a team for the user. Tsuru requires a user to be a
member of at least one team in order to create an app or a service instance.

When you create a team, you're automatically member of this team.

List teams that the user is member of

Usage:

	% tsuru team-list

team-list will list all teams that you are member of.

Add a user to a team

Usage:

	% tsuru team-user-add <teamname> <useremail>

team-user-add adds a user to a team. You need to be a member of the team to be
able to add a user to it.

Remove a user from a team

Usage:

	% tsuru team-user-remove <teamname> <useremail>

team-user-remove removes a user from a team. You need to be a member of the
team to be able to remove a user from it.

A team can never have 0 users. If you are the last member of a team, you can't
remove yourself from it.

Create an app

Usage:

	% tsuru app-create <appname> <platform>

app-create will create a new app using the given name and platform. For tsuru,
a platform is a Juju charm. To check the available platforms/charms, check this
URL: https://github.com/timeredbull/charms/tree/master/centos.

In order to create an app, you need to be member of at least one team. All
teams that you are member (see "tsuru team-list") will be able to access the
app.

Remove an app

Usage:

	% tsuru app-remove <appname>

app-remove removes an app. If the app is binded to any service instance, it
will be unbinded before be removed (see "tsuru unbind"). You need to be a
member of a team that has access to the app to be able to remove it (you are
able to remove any app that you see in "tsuru app-list").

List apps that the user has access to

Usage:

	% tsuru app-list

app-list will list all apps that you have access to. App access is controlled
by teams. If your team has access to an app, then you have access to it.

Allow a team to access an app

Usage:

	% tsuru app-grant <appname> <teamname>

app-grant will allow a team to access an app. You need to be a member of a team
that has access to the app to allow another team to access it.

Revoke from a team access to an app

Usage:

	% tsuru app-revoke <appname> <teamname>

app-revoke will revoke the permission to access an app from a team. You need to
have access to the app to revoke access from a team.

An app cannot be orphaned, so it will always have at least one authorized team.

See app's logs

Usage:

	% tsuru log <appname>

Log will show log entries for an app. These logs are not related to the code of
the app itself, but to actions of the app in tsuru server (deployments,
restarts, etc.).

Run an arbitrary command in the app machine

Usage:

	% tsuru run <appname> <command> [commandarg1] [commandarg2] ... [commandargn]

Run will run an arbitrary command in the app machine. Base directory for all
commands is the root of the app. For example, in a Django app, "tsuru run" may
show the following output:

	% tsuru run polls ls -l
	app.conf
	brogui
	deploy
	foo
	__init__.py
	__init__.pyc
	main.go
	manage.py
	settings.py
	settings.pyc
	templates
	urls.py
	urls.pyc

Restart the app's application server

Usage:

	% tsuru restart <appname>

Restart will call the restart hook from the app platform (the "restart" hook
from the Juju charm).
*/
package documentation
<commit_msg>cmd/tsuru: actual docs for version<commit_after>/*
tsuru is a command line tool for application developers.

It provide some commands that allow a developer to register himself/herself,
manage teams, apps and services.

Usage:

	% tsuru <command> [args]

The currently available commands are (grouped by subject):

    target            changes or retrive the current tsuru server
    version           displays current tsuru version

    user-create       creates a new user
    login             authenticates the user with tsuru server
    logout            finishes the session with tsuru server
    key-add           adds a public key to tsuru deploy server
    key-remove        removes a public key from tsuru deploy server

    team-create       creates a new team (adding the current user to it automatically)
    team-list         list teams that the user is member
    team-user-add     adds a user to a team
    team-user-remove  removes a user from a team

    app-create        creates an app
    app-remove        removes an app
    app-list          lists apps that the user has access (see app-grant and team-user-add)
    app-grant         allows a team to have access to an app
    app-revoke        revokes access to an app from a team
    log               shows log for an app
    run               runs a command in all units of an app
    restart           restarts the app's application server

    env-get           display environment variables for an app
    env-set           set environment variable(s) to an app
    env-unset         unset environment variable(s) from an app

    bind              binds an app to a service instance
    unbind            unbinds an app from a service instance

    service-list      list all services, and instances of each service
    service-add       creates a new instance of a service
    service-remove    removes a instance of a service
    service-status    checks the status of a service instance
    service-info      list instances of a service, and apps binded to each instance
    service-doc       displays documentation for a service

Use "tsuru help <command>" for more information about a command.

Change/retrieve remote tsuru server

Usage:

	% tsuru target [target]

This command should be used to get current tsuru target, or retrieve current
target.

The target is the tsuru server to which all operations will be directed to.

Check current version

Usage:

	% tsuru version

This command returns the current version of tsuru command.

Create a user

Usage:

	% tsuru user-create <email>

user-create creates a user within tsuru remote server. It will ask for the
password before issue the request.

Authenticate within remote tsuru server

Usage:

	% tsuru login <email>

Login will ask for the password and check if the user is successfully
authenticated. If so, the token generated by the tsuru server will be stored in
${HOME}/.tsuru_token.

All tsuru actions require the user to be authenticated (except login and
user-create, obviously).

Logout from remote tsuru server

Usage:

	% tsuru logout

Logout will delete the token file and terminate the session within tsuru
server.

Add SSH public key to tsuru's git server

Usage:

	% tsuru key-add [${HOME}/.ssh/id_rsa.pub]

key-add sends your public key to tsuru's git server. By default, it will try
send a public RSA key, located at ${HOME}/.ssh/id_rsa.pub. If you want to send
other file, you can call it with the path to the file. For example:

	% tsuru key-add /etc/my-keys/id_dsa.pub

The key will be added to the current logged in user.

Remove SSH public key from tsuru's git server

Usage:

	% tsuru key-remove [${HOME}/.ssh/id_rsa.pub]

key-remove removes your public key from tsuru's git server. By default, it will
try to remove a key that match you public RSA key located at
${HOME}/.ssh/id_rsa.pub. If you want to remove a key located somewhere else,
you can pass it as parameter to key-remove:

	% tsuru key-remove /etc/my-keys/id_dsa.pub

The key will be removed from the current logged in user.

Create a new team for the user

Usage:

	% tsuru team-create <teamname>

team-create will create a team for the user. Tsuru requires a user to be a
member of at least one team in order to create an app or a service instance.

When you create a team, you're automatically member of this team.

List teams that the user is member of

Usage:

	% tsuru team-list

team-list will list all teams that you are member of.

Add a user to a team

Usage:

	% tsuru team-user-add <teamname> <useremail>

team-user-add adds a user to a team. You need to be a member of the team to be
able to add a user to it.

Remove a user from a team

Usage:

	% tsuru team-user-remove <teamname> <useremail>

team-user-remove removes a user from a team. You need to be a member of the
team to be able to remove a user from it.

A team can never have 0 users. If you are the last member of a team, you can't
remove yourself from it.

Create an app

Usage:

	% tsuru app-create <appname> <platform>

app-create will create a new app using the given name and platform. For tsuru,
a platform is a Juju charm. To check the available platforms/charms, check this
URL: https://github.com/timeredbull/charms/tree/master/centos.

In order to create an app, you need to be member of at least one team. All
teams that you are member (see "tsuru team-list") will be able to access the
app.

Remove an app

Usage:

	% tsuru app-remove <appname>

app-remove removes an app. If the app is binded to any service instance, it
will be unbinded before be removed (see "tsuru unbind"). You need to be a
member of a team that has access to the app to be able to remove it (you are
able to remove any app that you see in "tsuru app-list").

List apps that the user has access to

Usage:

	% tsuru app-list

app-list will list all apps that you have access to. App access is controlled
by teams. If your team has access to an app, then you have access to it.

Allow a team to access an app

Usage:

	% tsuru app-grant <appname> <teamname>

app-grant will allow a team to access an app. You need to be a member of a team
that has access to the app to allow another team to access it.

Revoke from a team access to an app

Usage:

	% tsuru app-revoke <appname> <teamname>

app-revoke will revoke the permission to access an app from a team. You need to
have access to the app to revoke access from a team.

An app cannot be orphaned, so it will always have at least one authorized team.

See app's logs

Usage:

	% tsuru log <appname>

Log will show log entries for an app. These logs are not related to the code of
the app itself, but to actions of the app in tsuru server (deployments,
restarts, etc.).

Run an arbitrary command in the app machine

Usage:

	% tsuru run <appname> <command> [commandarg1] [commandarg2] ... [commandargn]

Run will run an arbitrary command in the app machine. Base directory for all
commands is the root of the app. For example, in a Django app, "tsuru run" may
show the following output:

	% tsuru run polls ls -l
	app.conf
	brogui
	deploy
	foo
	__init__.py
	__init__.pyc
	main.go
	manage.py
	settings.py
	settings.pyc
	templates
	urls.py
	urls.pyc

Restart the app's application server

Usage:

	% tsuru restart <appname>

Restart will call the restart hook from the app platform (the "restart" hook
from the Juju charm).
*/
package documentation
<|endoftext|>


<commit_before>// +build linux darwin freebsd

package cmd

import (
	"fmt"
	"github.com/DataDog/datadog-go/statsd"
	"github.com/hpcloud/tail"
	"github.com/spf13/cobra"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	dnsmasqLog      = "/var/log/dnsmasq/dnsmasq"
	signalInterval  = 60
	yearSetInterval = 10
)

var dnsmasqCmd = &cobra.Command{
	Use:   "dnsmasq",
	Short: "Grab stats from dnsmasq logs and send to Datadog.",
	Long:  `Grab stats from dnsmasq logs and send to Datadog.`,
	PreRun: func(cmd *cobra.Command, args []string) {
		checkDnsmasqFlags()
	},
	Run: startDnsmasq,
}

func startDnsmasq(cmd *cobra.Command, args []string) {
	dog := DogConnect()
	t := OpenLogfile(DnsmasqLog)
	if FullLogs {
		dnsmasqFullLogsStats(t, dog)
	} else {
		dnsmasqSignalStats(t, dog)
	}
}

func checkDnsmasqFlags() {
	fmt.Println("Press CTRL-C to shutdown.")
}

// DNSServer is data gathered from a dnsmasq server log line.
type DNSServer struct {
	timestamp     int64
	address       string
	queriesSent   int64
	queriesFailed int64
}

// DNSStats is data gathered from dnsmasq time, queries and server lines.
type DNSStats struct {
	timestamp        int64
	queriesForwarded int64
	queriesLocal     int64
	servers          *[]DNSServer
}

var (
	// DnsmasqLog is the logfile that dnsmasq logs to.
	DnsmasqLog string

	// FullLogs determines whether we're looking at '--log-queries'
	// levels of logs for dnsmasq.
	FullLogs bool

	// CurrentTimestamp is the current timestamp from the dnsmasq logs.
	CurrentTimestamp int64

	// CurrentYear is the year this is happening.
	CurrentYear int
)

func init() {
	dnsmasqCmd.Flags().StringVarP(&DnsmasqLog, "log", "", dnsmasqLog, "dnsmasq log file.")
	dnsmasqCmd.Flags().BoolVarP(&FullLogs, "full", "", false, "Use full --log-queries logs.")
	RootCmd.AddCommand(dnsmasqCmd)
}

// SendLineStats sends the stats to Datadog.
func SendLineStats(dog *statsd.Client, line string, metric string) {
	Log(fmt.Sprintf("%s: %s", metric, line), "debug")
	oldTags := dog.Tags
	dog.Tags = append(dog.Tags, fmt.Sprintf("record:%s", metric))
	dog.Count("dnsmasq.event", 1, dog.Tags, 1)
	dog.Tags = oldTags
}

// Example Logs:
// Jan 29 20:32:55 dnsmasq[29389]: time 1454099575
// Jan 29 20:32:55 dnsmasq[29389]: cache size 150, 41/1841 cache insertions re-used unexpired cache entries.
// Jan 29 20:32:55 dnsmasq[29389]: queries forwarded 354453, queries answered locally 251099667
// Jan 29 20:32:55 dnsmasq[29389]: server 127.0.0.1#8600: queries sent 142940, retried or failed 0
// Jan 29 20:32:55 dnsmasq[29389]: server 172.16.0.23#53: queries sent 211510, retried or failed 0

func dnsmasqSignalStats(t *tail.Tail, dog *statsd.Client) {
	// Set the current time from timestamp. Helps us to skip any items that are old.
	CurrentTimestamp = time.Now().Unix()

	go dnsmasqSignals()
	go setCurrentYear()
	for line := range t.Lines {
		// Blank lines really mess this up - this protects against it.
		if line.Text == "" {
			continue
		}
		// Parse line to grab timestamp - compare against CurrentTimestamp.
		// If it's older - skip. We would rather skip instead of double
		// count older data.
		if isOldTimestamp(line.Text) {
			continue
		}
		content := strings.Split(line.Text, "]: ")[1]
		if strings.HasPrefix(content, "time") {
			Log(fmt.Sprintf("line: %s", content), "debug")
			grabTimestamp(content)
		}
		if strings.HasPrefix(content, "queries") {
			Log(fmt.Sprintf("line: %s", content), "debug")
			queriesForwarded(content)
			queriesLocal(content)
			queriesAuthoritativeZones(content)
		}
		if strings.HasPrefix(content, "server") {
			Log(fmt.Sprintf("line: %s", content), "debug")
			serverStats(content)
		}
	}
}

func grabTimestamp(content string) {
	r := regexp.MustCompile(`\d+`)
	timestamp := r.FindString(content)
	unixTimestamp, _ := strconv.ParseInt(timestamp, 10, 64)
	CurrentTimestamp = unixTimestamp
	Log(fmt.Sprintf("Timestamp: %d", unixTimestamp), "debug")
}

func serverStats(content string) {
	r := regexp.MustCompile(`server (\d+\.\d+\.\d+\.\d+#\d+): queries sent (\d+), retried or failed (\d+)`)
	server := r.FindAllStringSubmatch(content, -1)
	if server != nil {
		srvr := server[0]
		serverAddress := srvr[1]
		serverAddressSent, _ := strconv.Atoi(srvr[2])
		serverAddressRetryFailures, _ := strconv.Atoi(srvr[3])
		Log(fmt.Sprintf("Time: %d Server: %s Queries: %d Retries/Failures: %d\n", CurrentTimestamp, serverAddress, serverAddressSent, serverAddressRetryFailures), "debug")
	}
}

func queriesForwarded(content string) {
	r := regexp.MustCompile(`forwarded (\d+),`)
	forwarded := r.FindAllStringSubmatch(content, -1)
	if forwarded != nil {
		fwd := forwarded[0]
		value := fwd[1]
		queriesForwarded, _ := strconv.Atoi(value)
		Log(fmt.Sprintf("Forwarded Queries: %d", queriesForwarded), "debug")
	}
}

func queriesLocal(content string) {
	r := regexp.MustCompile(`queries answered locally (\d+)`)
	local := r.FindAllStringSubmatch(content, -1)
	if local != nil {
		lcl := local[0]
		lclv := lcl[1]
		localResponses, _ := strconv.Atoi(lclv)
		Log(fmt.Sprintf("Responded Locally: %d", localResponses), "debug")
	}
}

func queriesAuthoritativeZones(content string) {
	r := regexp.MustCompile(`for authoritative zones (\d+)`)
	zones := r.FindAllStringSubmatch(content, -1)
	if zones != nil {
		zone := zones[0]
		zonev := zone[1]
		authoritativeZones, _ := strconv.Atoi(zonev)
		Log(fmt.Sprintf("Authoritative Zones: %d", authoritativeZones), "debug")
	}
}

func dnsmasqSignals() {
	for {
		procs := GetMatches("dnsmasq", false)
		sendUSR1(procs)
		time.Sleep(time.Duration(signalInterval) * time.Second)
	}
}

func dnsmasqFullLogsStats(t *tail.Tail, dog *statsd.Client) {
	for line := range t.Lines {
		content := strings.Split(line.Text, "]: ")[1]
		if strings.HasPrefix(content, "/") {
			SendLineStats(dog, content, "hosts")
			continue
		}
		if strings.HasPrefix(content, "query") {
			SendLineStats(dog, content, "query")
			continue
		}
		if strings.HasPrefix(content, "cached") {
			SendLineStats(dog, content, "cached")
			continue
		}
		if strings.HasPrefix(content, "forwarded") {
			SendLineStats(dog, content, "forwarded")
			continue
		}
		if strings.HasPrefix(content, "reply") {
			SendLineStats(dog, content, "reply")
			continue
		}
	}
}

func sendUSR1(procs []ProcessList) {
	if len(procs) > 0 {
		for _, proc := range procs {
			proc.USR1()
		}
	}
}

func getCurrentYear() int {
	t := time.Now()
	year := t.Year()
	Log(fmt.Sprintf("Year: %d", year), "debug")
	return year
}

func setCurrentYear() {
	for {
		CurrentYear = getCurrentYear()
		time.Sleep(time.Duration(yearSetInterval) * time.Second)
	}
}

func isOldTimestamp(line string) bool {
	// Munge the Syslog timestamp and pull out the values.
	dateTime := strings.TrimSpace(strings.Split(line, " dnsmasq")[0])
	dateTime = fmt.Sprintf("%s %d", dateTime, CurrentYear)
	stamp, _ := time.Parse("Jan _2 15:04:05 2006", dateTime)
	// If it's older than now - then skip it.
	if stamp.Unix() < CurrentTimestamp {
		Log(fmt.Sprintf("Skipping: '%s'", dateTime), "info")
		return true
	}
	return false
}
<commit_msg>Making some progress.<commit_after>// +build linux darwin freebsd

package cmd

import (
	"fmt"
	"github.com/DataDog/datadog-go/statsd"
	"github.com/hpcloud/tail"
	"github.com/spf13/cobra"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	dnsmasqLog      = "/var/log/dnsmasq/dnsmasq"
	signalInterval  = 60
	yearSetInterval = 10
)

var dnsmasqCmd = &cobra.Command{
	Use:   "dnsmasq",
	Short: "Grab stats from dnsmasq logs and send to Datadog.",
	Long:  `Grab stats from dnsmasq logs and send to Datadog.`,
	PreRun: func(cmd *cobra.Command, args []string) {
		checkDnsmasqFlags()
	},
	Run: startDnsmasq,
}

func startDnsmasq(cmd *cobra.Command, args []string) {
	dog := DogConnect()
	t := OpenLogfile(DnsmasqLog)
	if FullLogs {
		dnsmasqFullLogsStats(t, dog)
	} else {
		dnsmasqSignalStats(t, dog)
	}
}

func checkDnsmasqFlags() {
	fmt.Println("Press CTRL-C to shutdown.")
}

// DNSServer is data gathered from a dnsmasq server log line.
type DNSServer struct {
	timestamp     int64
	address       string
	queriesSent   int64
	queriesFailed int64
}

// DNSStats is data gathered from dnsmasq time, queries and server lines.
type DNSStats struct {
	timestamp          int64
	queriesForwarded   int64
	queriesLocal       int64
	authoritativeZones int64
	servers            []DNSServer
}

var (
	// DnsmasqLog is the logfile that dnsmasq logs to.
	DnsmasqLog string

	// FullLogs determines whether we're looking at '--log-queries'
	// levels of logs for dnsmasq.
	FullLogs bool

	// CurrentTimestamp is the current timestamp from the dnsmasq logs.
	CurrentTimestamp int64

	// CurrentYear is the year this is happening.
	CurrentYear int

	// StatsCurrent is the current timestamp's stats.
	StatsCurrent *DNSStats

	// StatsPrevious is the last timestamp's stats.
	StatsPrevious *DNSStats
)

func init() {
	dnsmasqCmd.Flags().StringVarP(&DnsmasqLog, "log", "", dnsmasqLog, "dnsmasq log file.")
	dnsmasqCmd.Flags().BoolVarP(&FullLogs, "full", "", false, "Use full --log-queries logs.")
	RootCmd.AddCommand(dnsmasqCmd)
}

// SendLineStats sends the stats to Datadog.
func SendLineStats(dog *statsd.Client, line string, metric string) {
	Log(fmt.Sprintf("%s: %s", metric, line), "debug")
	oldTags := dog.Tags
	dog.Tags = append(dog.Tags, fmt.Sprintf("record:%s", metric))
	dog.Count("dnsmasq.event", 1, dog.Tags, 1)
	dog.Tags = oldTags
}

// Example Logs:
// Jan 29 20:32:55 dnsmasq[29389]: time 1454099575
// Jan 29 20:32:55 dnsmasq[29389]: cache size 150, 41/1841 cache insertions re-used unexpired cache entries.
// Jan 29 20:32:55 dnsmasq[29389]: queries forwarded 354453, queries answered locally 251099667
// Jan 29 20:32:55 dnsmasq[29389]: server 127.0.0.1#8600: queries sent 142940, retried or failed 0
// Jan 29 20:32:55 dnsmasq[29389]: server 172.16.0.23#53: queries sent 211510, retried or failed 0

func dnsmasqSignalStats(t *tail.Tail, dog *statsd.Client) {
	// Set the current time from timestamp. Helps us to skip any items that are old.
	CurrentTimestamp = time.Now().Unix()
	StatsCurrent = new(DNSStats)
	StatsPrevious = new(DNSStats)

	go dnsmasqSignals()
	go setCurrentYear()
	for line := range t.Lines {
		// Blank lines really mess this up - this protects against it.
		if line.Text == "" {
			continue
		}
		// Parse line to grab timestamp - compare against CurrentTimestamp.
		// If it's older - skip. We would rather skip instead of double
		// count older data.
		if isOldTimestamp(line.Text) {
			continue
		}
		content := strings.Split(line.Text, "]: ")[1]
		if strings.HasPrefix(content, "time") {
			Log(fmt.Sprintf("line: %s", content), "debug")
			grabTimestamp(content)
		}
		if strings.HasPrefix(content, "queries") {
			Log(fmt.Sprintf("line: %s", content), "debug")
			queriesForwarded(content)
			queriesLocal(content)
			queriesAuthoritativeZones(content)
		}
		if strings.HasPrefix(content, "server") {
			Log(fmt.Sprintf("line: %s", content), "debug")
			serverStats(content)
		}
	}
}

func grabTimestamp(content string) {
	// If we have correct stats in both Current and Previous.
	if (StatsCurrent.timestamp > 0) && (StatsPrevious.timestamp > 0) {
		// Let's send the stats to Datadog.
		Log("Stats: Sending stats.", "info")
		// Copy Current to Previous and zero out current.
	} else if (StatsCurrent.timestamp > 0) && (StatsPrevious.timestamp == 0) {
		// We don't have enough stats to send.
		// Copy Current to Previous and zero out current.
		Log("Stats: Not enough stats to send.", "info")
		StatsPrevious = StatsCurrent
		StatsCurrent = new(DNSStats)
	}
	// If everything's 0 - then just keep going.
	// Grab the timestamp from the log line.
	r := regexp.MustCompile(`\d+`)
	timestamp := r.FindString(content)
	unixTimestamp, _ := strconv.ParseInt(timestamp, 10, 64)
	CurrentTimestamp = unixTimestamp
	Log(fmt.Sprintf("StatsCurrent: %#v", StatsCurrent), "debug")
	StatsCurrent.timestamp = unixTimestamp
	Log(fmt.Sprintf("Timestamp: %d", unixTimestamp), "debug")
}

func serverStats(content string) {
	r := regexp.MustCompile(`server (\d+\.\d+\.\d+\.\d+#\d+): queries sent (\d+), retried or failed (\d+)`)
	server := r.FindAllStringSubmatch(content, -1)
	if server != nil {
		srvr := server[0]
		serverAddress := srvr[1]
		serverAddressSent, _ := strconv.ParseInt(srvr[2], 10, 64)
		serverAddressRetryFailures, _ := strconv.ParseInt(srvr[3], 10, 64)
		serverStruct := DNSServer{timestamp: CurrentTimestamp, address: serverAddress, queriesSent: serverAddressSent, queriesFailed: serverAddressRetryFailures}
		StatsCurrent.servers = append(StatsCurrent.servers, serverStruct)
		Log(fmt.Sprintf("Time: %d Server: %s Queries: %d Retries/Failures: %d\n", CurrentTimestamp, serverAddress, serverAddressSent, serverAddressRetryFailures), "debug")
	}
}

func queriesForwarded(content string) {
	r := regexp.MustCompile(`forwarded (\d+),`)
	forwarded := r.FindAllStringSubmatch(content, -1)
	if forwarded != nil {
		fwd := forwarded[0]
		queriesForwarded, _ := strconv.ParseInt(fwd[1], 10, 64)
		StatsCurrent.queriesForwarded = queriesForwarded
		Log(fmt.Sprintf("Forwarded Queries: %d", queriesForwarded), "debug")
	}
}

func queriesLocal(content string) {
	r := regexp.MustCompile(`queries answered locally (\d+)`)
	local := r.FindAllStringSubmatch(content, -1)
	if local != nil {
		lcl := local[0]
		localResponses, _ := strconv.ParseInt(lcl[1], 10, 64)
		StatsCurrent.queriesLocal = localResponses
		Log(fmt.Sprintf("Responded Locally: %d", localResponses), "debug")
	}
}

func queriesAuthoritativeZones(content string) {
	r := regexp.MustCompile(`for authoritative zones (\d+)`)
	zones := r.FindAllStringSubmatch(content, -1)
	if zones != nil {
		zone := zones[0]
		authoritativeZones, _ := strconv.ParseInt(zone[1], 10, 64)
		StatsCurrent.authoritativeZones = authoritativeZones
		Log(fmt.Sprintf("Authoritative Zones: %d", authoritativeZones), "debug")
	}
}

func dnsmasqSignals() {
	for {
		procs := GetMatches("dnsmasq", false)
		sendUSR1(procs)
		time.Sleep(time.Duration(signalInterval) * time.Second)
	}
}

func dnsmasqFullLogsStats(t *tail.Tail, dog *statsd.Client) {
	for line := range t.Lines {
		content := strings.Split(line.Text, "]: ")[1]
		if strings.HasPrefix(content, "/") {
			SendLineStats(dog, content, "hosts")
			continue
		}
		if strings.HasPrefix(content, "query") {
			SendLineStats(dog, content, "query")
			continue
		}
		if strings.HasPrefix(content, "cached") {
			SendLineStats(dog, content, "cached")
			continue
		}
		if strings.HasPrefix(content, "forwarded") {
			SendLineStats(dog, content, "forwarded")
			continue
		}
		if strings.HasPrefix(content, "reply") {
			SendLineStats(dog, content, "reply")
			continue
		}
	}
}

func sendUSR1(procs []ProcessList) {
	if len(procs) > 0 {
		for _, proc := range procs {
			proc.USR1()
		}
	}
}

func getCurrentYear() int {
	t := time.Now()
	year := t.Year()
	Log(fmt.Sprintf("Year: %d", year), "debug")
	return year
}

func setCurrentYear() {
	for {
		CurrentYear = getCurrentYear()
		time.Sleep(time.Duration(yearSetInterval) * time.Second)
	}
}

func isOldTimestamp(line string) bool {
	// Munge the Syslog timestamp and pull out the values.
	dateTime := strings.TrimSpace(strings.Split(line, " dnsmasq")[0])
	dateTime = fmt.Sprintf("%s %d", dateTime, CurrentYear)
	stamp, _ := time.Parse("Jan _2 15:04:05 2006", dateTime)
	// If it's older than now - then skip it.
	if stamp.Unix() < CurrentTimestamp {
		Log(fmt.Sprintf("Skipping: '%s'", dateTime), "info")
		return true
	}
	return false
}
<|endoftext|>


<commit_before>// Copyright © 2016 Transparencia Mexicana AC. <ben@pixative.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
  "os"
  "log"
  "github.com/transparenciamx/tsv/storage"
)

// Utility method to open a storage handler
func connectStorage(host, database string) (*storage.Handler, error ) {
  // Use linked storage if available
  linked := os.Getenv("STORAGE_PORT")
  if linked != "" {
    host = linked[6:]
  }

  // Connect to storage instance
  db, err := storage.NewHandler(host, database)
  if err != nil {
    return nil, err
  }
  log.Printf("Using storage: %s/%s", host, database)
  return db, nil
}
<commit_msg>Add helper methods for the flow bot messages<commit_after>// Copyright © 2016 Transparencia Mexicana AC. <ben@pixative.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
  "os"
  "log"
  "github.com/transparenciamx/tsv/storage"
  "github.com/transparenciamx/tsv/bot"
  "encoding/json"
  "github.com/Jeffail/gabs"
  "fmt"
)

// Utility method to open a storage handler
func connectStorage(host, database string) (*storage.Handler, error ) {
  // Use linked storage if available
  linked := os.Getenv("STORAGE_PORT")
  if linked != "" {
    host = linked[6:]
  }

  // Connect to storage instance
  db, err := storage.NewHandler(host, database)
  if err != nil {
    return nil, err
  }
  log.Printf("Using storage: %s/%s", host, database)
  return db, nil
}

// GetWelcomeMessage returns a ready to use 'welcome message' for a new user
func GetWelcomeMessage(user string) *bot.Message {
  return &bot.Message{
    Recipient: bot.Recipient{
      ID: user,
    },
    Message: bot.MessageBody{
      Attachment: bot.Attachment{
        Type: "template",
        Payload: bot.Payload{
          TemplateType: "button",
          Text: "Elije una de las siguientes opciones:",
          Buttons: []bot.URLButton{
            {
              Type: "postback",
              Title: "Nueva Consulta",
              Payload: "NEW_QUERY",
            },
            {
              Type: "web_url",
              URL: "https://www.testigosocial.mx",
              Title: "Visitar Sitio Web",
            },
          },
        },
      },
    },
  }
}

// GetQueryMenu returns a menu with the available basic queries
func GetQueryMenu(user string) *bot.Message {
  return &bot.Message{
    Recipient: bot.Recipient{
      ID: user,
    },
    Message: bot.MessageBody{
      Text: "Seleccione el criterio para su consulta de los procesos de contratación registrados:",
      QuickReplies: []bot.QuickReply{
        {ContentType: "text", Title: "Recientes", Payload: "RECENT"},
        {ContentType: "text", Title: "Mayor Monto", Payload: "AMOUNT"},
        {ContentType: "text", Title: "Grupo Aeroportuario", Payload: "GACM"},
        {ContentType: "text", Title: "Ciudad de México", Payload: "CDMX"},
      },
    },
  }
}

// GetContractListMessage return a list message based on the provided query results
func GetContractListMessage(user string, list []interface{}) *bot.Message {
  els := []bot.ListElement{}
  for _, rec := range list {
    json, _ := json.Marshal(rec)
    r, _ := gabs.ParseJSON(json)
    releases, _ := r.Search("releases").Children()
    els = append(els, bot.ListElement{
      Title: releases[0].Path("ocid").String(),
      Subtitle: releases[0].Path("tender.title").String(),
      DefaultAction: bot.URLButton{
        Type: "web_url",
        URL: fmt.Sprintf("https://www.testigosocial.mx/contratos/%s", r.Path("_id").Data().(string)),
      },
    })
  }

  return &bot.Message{
    Recipient: bot.Recipient{
      ID: user,
    },
    Message: bot.MessageBody{
      Attachment: bot.Attachment{
        Type: "template",
        Payload: bot.Payload{
          TemplateType: "list",
          TopElementStyle: "compact",
          Elements: els,
        },
      },
    },
  }
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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

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

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

package cmd

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	units "github.com/docker/go-units"
	"github.com/docker/machine/libmachine/host"
	"github.com/golang/glog"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"

	"io/ioutil"

	cmdUtil "k8s.io/minikube/cmd/util"
	"k8s.io/minikube/pkg/minikube/cluster"
	cfg "k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
	"k8s.io/minikube/pkg/minikube/kubeconfig"
	"k8s.io/minikube/pkg/minikube/kubernetes_versions"
	"k8s.io/minikube/pkg/minikube/machine"
	"k8s.io/minikube/pkg/util"
	pkgutil "k8s.io/minikube/pkg/util"
)

const (
	isoURL                = "iso-url"
	memory                = "memory"
	cpus                  = "cpus"
	humanReadableDiskSize = "disk-size"
	vmDriver              = "vm-driver"
	xhyveDiskDriver       = "xhyve-disk-driver"
	kubernetesVersion     = "kubernetes-version"
	hostOnlyCIDR          = "host-only-cidr"
	containerRuntime      = "container-runtime"
	networkPlugin         = "network-plugin"
	hypervVirtualSwitch   = "hyperv-virtual-switch"
	kvmNetwork            = "kvm-network"
	keepContext           = "keep-context"
	createMount           = "mount"
	featureGates          = "feature-gates"
	apiServerName         = "apiserver-name"
	dnsDomain             = "dns-domain"
	mountString           = "mount-string"
)

var (
	registryMirror   []string
	dockerEnv        []string
	dockerOpt        []string
	insecureRegistry []string
	extraOptions     util.ExtraOptionSlice
)

// startCmd represents the start command
var startCmd = &cobra.Command{
	Use:   "start",
	Short: "Starts a local kubernetes cluster",
	Long: `Starts a local kubernetes cluster using VM. This command
assumes you have already installed one of the VM drivers: virtualbox/vmwarefusion/kvm/xhyve/hyperv.`,
	Run: runStart,
}

func runStart(cmd *cobra.Command, args []string) {
	api, err := machine.NewAPIClient(clientType)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error getting client: %s\n", err)
		os.Exit(1)
	}
	defer api.Close()

	diskSize := viper.GetString(humanReadableDiskSize)
	diskSizeMB := calculateDiskSizeInMB(diskSize)

	if diskSizeMB < constants.MinimumDiskSizeMB {
		err := fmt.Errorf("Disk Size %dMB (%s) is too small, the minimum disk size is %dMB", diskSizeMB, diskSize, constants.MinimumDiskSizeMB)
		glog.Errorln("Error parsing disk size:", err)
		os.Exit(1)
	}

	validateK8sVersion(viper.GetString(kubernetesVersion))

	config := cluster.MachineConfig{
		MinikubeISO:         viper.GetString(isoURL),
		Memory:              viper.GetInt(memory),
		CPUs:                viper.GetInt(cpus),
		DiskSize:            diskSizeMB,
		VMDriver:            viper.GetString(vmDriver),
		XhyveDiskDriver:     viper.GetString(xhyveDiskDriver),
		DockerEnv:           dockerEnv,
		DockerOpt:           dockerOpt,
		InsecureRegistry:    insecureRegistry,
		RegistryMirror:      registryMirror,
		HostOnlyCIDR:        viper.GetString(hostOnlyCIDR),
		HypervVirtualSwitch: viper.GetString(hypervVirtualSwitch),
		KvmNetwork:          viper.GetString(kvmNetwork),
		Downloader:          pkgutil.DefaultDownloader{},
	}

	fmt.Printf("Starting local Kubernetes %s cluster...\n", viper.GetString(kubernetesVersion))
	fmt.Println("Starting VM...")
	var host *host.Host
	start := func() (err error) {
		host, err = cluster.StartHost(api, config)
		if err != nil {
			glog.Errorf("Error starting host: %s.\n\n Retrying.\n", err)
		}
		return err
	}
	err = util.RetryAfter(5, start, 2*time.Second)
	if err != nil {
		glog.Errorln("Error starting host: ", err)
		cmdUtil.MaybeReportErrorAndExit(err)
	}

	ip, err := host.Driver.GetIP()
	if err != nil {
		glog.Errorln("Error starting host: ", err)
		cmdUtil.MaybeReportErrorAndExit(err)
	}
	kubernetesConfig := cluster.KubernetesConfig{
		KubernetesVersion: viper.GetString(kubernetesVersion),
		NodeIP:            ip,
		APIServerName:     viper.GetString(apiServerName),
		DNSDomain:         viper.GetString(dnsDomain),
		FeatureGates:      viper.GetString(featureGates),
		ContainerRuntime:  viper.GetString(containerRuntime),
		NetworkPlugin:     viper.GetString(networkPlugin),
		ExtraOptions:      extraOptions,
	}

	fmt.Println("SSH-ing files into VM...")
	if err := cluster.UpdateCluster(host, host.Driver, kubernetesConfig); err != nil {
		glog.Errorln("Error updating cluster: ", err)
		cmdUtil.MaybeReportErrorAndExit(err)
	}

	fmt.Println("Setting up certs...")
	if err := cluster.SetupCerts(host.Driver, kubernetesConfig.APIServerName); err != nil {
		glog.Errorln("Error configuring authentication: ", err)
		cmdUtil.MaybeReportErrorAndExit(err)
	}

	fmt.Println("Starting cluster components...")
	if err := cluster.StartCluster(host, kubernetesConfig); err != nil {
		glog.Errorln("Error starting cluster: ", err)
		cmdUtil.MaybeReportErrorAndExit(err)
	}

	fmt.Println("Connecting to cluster...")
	kubeHost, err := host.Driver.GetURL()
	if err != nil {
		glog.Errorln("Error connecting to cluster: ", err)
	}
	kubeHost = strings.Replace(kubeHost, "tcp://", "https://", -1)
	kubeHost = strings.Replace(kubeHost, ":2376", ":"+strconv.Itoa(constants.APIServerPort), -1)

	fmt.Println("Setting up kubeconfig...")
	// setup kubeconfig

	kubeConfigEnv := os.Getenv(constants.KubeconfigEnvVar)
	var kubeConfigFile string
	if kubeConfigEnv == "" {
		kubeConfigFile = constants.KubeconfigPath
	} else {
		kubeConfigFile = filepath.SplitList(kubeConfigEnv)[0]
	}

	kubeCfgSetup := &kubeconfig.KubeConfigSetup{
		ClusterName:          cfg.GetMachineName(),
		ClusterServerAddress: kubeHost,
		ClientCertificate:    constants.MakeMiniPath("apiserver.crt"),
		ClientKey:            constants.MakeMiniPath("apiserver.key"),
		CertificateAuthority: constants.MakeMiniPath("ca.crt"),
		KeepContext:          viper.GetBool(keepContext),
	}
	kubeCfgSetup.SetKubeConfigFile(kubeConfigFile)

	if err := kubeconfig.SetupKubeConfig(kubeCfgSetup); err != nil {
		glog.Errorln("Error setting up kubeconfig: ", err)
		cmdUtil.MaybeReportErrorAndExit(err)
	}

	// start 9p server mount
	if viper.GetBool(createMount) {
		fmt.Printf("Setting up hostmount on %s...\n", viper.GetString(mountString))

		path := os.Args[0]
		mountDebugVal := 0
		if glog.V(8) {
			mountDebugVal = 1
		}
		mountCmd := exec.Command(path, "mount", fmt.Sprintf("--v=%d", mountDebugVal), viper.GetString(mountString))
		mountCmd.Env = append(os.Environ(), constants.IsMinikubeChildProcess+"=true")
		if glog.V(8) {
			mountCmd.Stdout = os.Stdout
			mountCmd.Stderr = os.Stderr
		}
		err = mountCmd.Start()
		if err != nil {
			glog.Errorf("Error running command minikube mount %s", err)
			cmdUtil.MaybeReportErrorAndExit(err)
		}
		err = ioutil.WriteFile(filepath.Join(constants.GetMinipath(), constants.MountProcessFileName), []byte(strconv.Itoa(mountCmd.Process.Pid)), 0644)
		if err != nil {
			glog.Errorf("Error writing mount process pid to file: %s", err)
			cmdUtil.MaybeReportErrorAndExit(err)
		}
	}

	if kubeCfgSetup.KeepContext {
		fmt.Printf("The local Kubernetes cluster has started. The kubectl context has not been altered, kubectl will require \"--context=%s\" to use the local Kubernetes cluster.\n",
			kubeCfgSetup.ClusterName)
	} else {
		fmt.Println("Kubectl is now configured to use the cluster.")
	}
}

func validateK8sVersion(version string) {
	validVersion, err := kubernetes_versions.IsValidLocalkubeVersion(version, constants.KubernetesVersionGCSURL)
	if err != nil {
		glog.Errorln("Error getting valid kubernetes versions", err)
		os.Exit(1)
	}
	if !validVersion {
		fmt.Println("Invalid Kubernetes version.")
		kubernetes_versions.PrintKubernetesVersionsFromGCS(os.Stdout)
		os.Exit(1)
	}
}

func calculateDiskSizeInMB(humanReadableDiskSize string) int {
	diskSize, err := units.FromHumanSize(humanReadableDiskSize)
	if err != nil {
		glog.Errorf("Invalid disk size: %s", err)
	}
	return int(diskSize / units.MB)
}

func init() {
	startCmd.Flags().Bool(keepContext, constants.DefaultKeepContext, "This will keep the existing kubectl context and will create a minikube context.")
	startCmd.Flags().Bool(createMount, false, "This will start the mount daemon and automatically mount files into minikube")
	startCmd.Flags().String(mountString, constants.DefaultMountDir+":"+constants.DefaultMountEndpoint, "The argument to pass the minikube mount command on start")
	startCmd.Flags().String(isoURL, constants.DefaultIsoUrl, "Location of the minikube iso")
	startCmd.Flags().String(vmDriver, constants.DefaultVMDriver, fmt.Sprintf("VM driver is one of: %v", constants.SupportedVMDrivers))
	startCmd.Flags().Int(memory, constants.DefaultMemory, "Amount of RAM allocated to the minikube VM")
	startCmd.Flags().Int(cpus, constants.DefaultCPUS, "Number of CPUs allocated to the minikube VM")
	startCmd.Flags().String(humanReadableDiskSize, constants.DefaultDiskSize, "Disk size allocated to the minikube VM (format: <number>[<unit>], where unit = b, k, m or g)")
	startCmd.Flags().String(hostOnlyCIDR, "192.168.99.1/24", "The CIDR to be used for the minikube VM (only supported with Virtualbox driver)")
	startCmd.Flags().String(hypervVirtualSwitch, "", "The hyperv virtual switch name. Defaults to first found. (only supported with HyperV driver)")
	startCmd.Flags().String(kvmNetwork, "default", "The KVM network name. (only supported with KVM driver)")
	startCmd.Flags().String(xhyveDiskDriver, "ahci-hd", "The disk driver to use [ahci-hd|virtio-blk] (only supported with xhyve driver)")
	startCmd.Flags().StringArrayVar(&dockerEnv, "docker-env", nil, "Environment variables to pass to the Docker daemon. (format: key=value)")
	startCmd.Flags().StringArrayVar(&dockerOpt, "docker-opt", nil, "Specify arbitrary flags to pass to the Docker daemon. (format: key=value)")
	startCmd.Flags().String(apiServerName, constants.APIServerName, "The apiserver name which is used in the generated certificate for localkube/kubernetes.  This can be used if you want to make the apiserver available from outside the machine")
	startCmd.Flags().String(dnsDomain, "", "The cluster dns domain name used in the kubernetes cluster")
	startCmd.Flags().StringSliceVar(&insecureRegistry, "insecure-registry", nil, "Insecure Docker registries to pass to the Docker daemon")
	startCmd.Flags().StringSliceVar(&registryMirror, "registry-mirror", nil, "Registry mirrors to pass to the Docker daemon")
	startCmd.Flags().String(kubernetesVersion, constants.DefaultKubernetesVersion, "The kubernetes version that the minikube VM will use (ex: v1.2.3) \n OR a URI which contains a localkube binary (ex: https://storage.googleapis.com/minikube/k8sReleases/v1.3.0/localkube-linux-amd64)")
	startCmd.Flags().String(containerRuntime, "", "The container runtime to be used")
	startCmd.Flags().String(networkPlugin, "", "The name of the network plugin")
	startCmd.Flags().String(featureGates, "", "A set of key=value pairs that describe feature gates for alpha/experimental features.")
	startCmd.Flags().Var(&extraOptions, "extra-config",
		`A set of key=value pairs that describe configuration that may be passed to different components.
		The key should be '.' separated, and the first part before the dot is the component to apply the configuration to.
		Valid components are: kubelet, apiserver, controller-manager, etcd, proxy, scheduler.`)
	viper.BindPFlags(startCmd.Flags())
	RootCmd.AddCommand(startCmd)
}
<commit_msg>Only validate the version if it's not default.<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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

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

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

package cmd

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	units "github.com/docker/go-units"
	"github.com/docker/machine/libmachine/host"
	"github.com/golang/glog"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"

	"io/ioutil"

	cmdUtil "k8s.io/minikube/cmd/util"
	"k8s.io/minikube/pkg/minikube/cluster"
	cfg "k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
	"k8s.io/minikube/pkg/minikube/kubeconfig"
	"k8s.io/minikube/pkg/minikube/kubernetes_versions"
	"k8s.io/minikube/pkg/minikube/machine"
	"k8s.io/minikube/pkg/util"
	pkgutil "k8s.io/minikube/pkg/util"
)

const (
	isoURL                = "iso-url"
	memory                = "memory"
	cpus                  = "cpus"
	humanReadableDiskSize = "disk-size"
	vmDriver              = "vm-driver"
	xhyveDiskDriver       = "xhyve-disk-driver"
	kubernetesVersion     = "kubernetes-version"
	hostOnlyCIDR          = "host-only-cidr"
	containerRuntime      = "container-runtime"
	networkPlugin         = "network-plugin"
	hypervVirtualSwitch   = "hyperv-virtual-switch"
	kvmNetwork            = "kvm-network"
	keepContext           = "keep-context"
	createMount           = "mount"
	featureGates          = "feature-gates"
	apiServerName         = "apiserver-name"
	dnsDomain             = "dns-domain"
	mountString           = "mount-string"
)

var (
	registryMirror   []string
	dockerEnv        []string
	dockerOpt        []string
	insecureRegistry []string
	extraOptions     util.ExtraOptionSlice
)

// startCmd represents the start command
var startCmd = &cobra.Command{
	Use:   "start",
	Short: "Starts a local kubernetes cluster",
	Long: `Starts a local kubernetes cluster using VM. This command
assumes you have already installed one of the VM drivers: virtualbox/vmwarefusion/kvm/xhyve/hyperv.`,
	Run: runStart,
}

func runStart(cmd *cobra.Command, args []string) {
	api, err := machine.NewAPIClient(clientType)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error getting client: %s\n", err)
		os.Exit(1)
	}
	defer api.Close()

	diskSize := viper.GetString(humanReadableDiskSize)
	diskSizeMB := calculateDiskSizeInMB(diskSize)

	if diskSizeMB < constants.MinimumDiskSizeMB {
		err := fmt.Errorf("Disk Size %dMB (%s) is too small, the minimum disk size is %dMB", diskSizeMB, diskSize, constants.MinimumDiskSizeMB)
		glog.Errorln("Error parsing disk size:", err)
		os.Exit(1)
	}

	if dv := viper.GetString(kubernetesVersion); dv != constants.DefaultKubernetesVersion {
		validateK8sVersion(dv)
	}

	config := cluster.MachineConfig{
		MinikubeISO:         viper.GetString(isoURL),
		Memory:              viper.GetInt(memory),
		CPUs:                viper.GetInt(cpus),
		DiskSize:            diskSizeMB,
		VMDriver:            viper.GetString(vmDriver),
		XhyveDiskDriver:     viper.GetString(xhyveDiskDriver),
		DockerEnv:           dockerEnv,
		DockerOpt:           dockerOpt,
		InsecureRegistry:    insecureRegistry,
		RegistryMirror:      registryMirror,
		HostOnlyCIDR:        viper.GetString(hostOnlyCIDR),
		HypervVirtualSwitch: viper.GetString(hypervVirtualSwitch),
		KvmNetwork:          viper.GetString(kvmNetwork),
		Downloader:          pkgutil.DefaultDownloader{},
	}

	fmt.Printf("Starting local Kubernetes %s cluster...\n", viper.GetString(kubernetesVersion))
	fmt.Println("Starting VM...")
	var host *host.Host
	start := func() (err error) {
		host, err = cluster.StartHost(api, config)
		if err != nil {
			glog.Errorf("Error starting host: %s.\n\n Retrying.\n", err)
		}
		return err
	}
	err = util.RetryAfter(5, start, 2*time.Second)
	if err != nil {
		glog.Errorln("Error starting host: ", err)
		cmdUtil.MaybeReportErrorAndExit(err)
	}

	ip, err := host.Driver.GetIP()
	if err != nil {
		glog.Errorln("Error starting host: ", err)
		cmdUtil.MaybeReportErrorAndExit(err)
	}
	kubernetesConfig := cluster.KubernetesConfig{
		KubernetesVersion: viper.GetString(kubernetesVersion),
		NodeIP:            ip,
		APIServerName:     viper.GetString(apiServerName),
		DNSDomain:         viper.GetString(dnsDomain),
		FeatureGates:      viper.GetString(featureGates),
		ContainerRuntime:  viper.GetString(containerRuntime),
		NetworkPlugin:     viper.GetString(networkPlugin),
		ExtraOptions:      extraOptions,
	}

	fmt.Println("SSH-ing files into VM...")
	if err := cluster.UpdateCluster(host, host.Driver, kubernetesConfig); err != nil {
		glog.Errorln("Error updating cluster: ", err)
		cmdUtil.MaybeReportErrorAndExit(err)
	}

	fmt.Println("Setting up certs...")
	if err := cluster.SetupCerts(host.Driver, kubernetesConfig.APIServerName); err != nil {
		glog.Errorln("Error configuring authentication: ", err)
		cmdUtil.MaybeReportErrorAndExit(err)
	}

	fmt.Println("Starting cluster components...")
	if err := cluster.StartCluster(host, kubernetesConfig); err != nil {
		glog.Errorln("Error starting cluster: ", err)
		cmdUtil.MaybeReportErrorAndExit(err)
	}

	fmt.Println("Connecting to cluster...")
	kubeHost, err := host.Driver.GetURL()
	if err != nil {
		glog.Errorln("Error connecting to cluster: ", err)
	}
	kubeHost = strings.Replace(kubeHost, "tcp://", "https://", -1)
	kubeHost = strings.Replace(kubeHost, ":2376", ":"+strconv.Itoa(constants.APIServerPort), -1)

	fmt.Println("Setting up kubeconfig...")
	// setup kubeconfig

	kubeConfigEnv := os.Getenv(constants.KubeconfigEnvVar)
	var kubeConfigFile string
	if kubeConfigEnv == "" {
		kubeConfigFile = constants.KubeconfigPath
	} else {
		kubeConfigFile = filepath.SplitList(kubeConfigEnv)[0]
	}

	kubeCfgSetup := &kubeconfig.KubeConfigSetup{
		ClusterName:          cfg.GetMachineName(),
		ClusterServerAddress: kubeHost,
		ClientCertificate:    constants.MakeMiniPath("apiserver.crt"),
		ClientKey:            constants.MakeMiniPath("apiserver.key"),
		CertificateAuthority: constants.MakeMiniPath("ca.crt"),
		KeepContext:          viper.GetBool(keepContext),
	}
	kubeCfgSetup.SetKubeConfigFile(kubeConfigFile)

	if err := kubeconfig.SetupKubeConfig(kubeCfgSetup); err != nil {
		glog.Errorln("Error setting up kubeconfig: ", err)
		cmdUtil.MaybeReportErrorAndExit(err)
	}

	// start 9p server mount
	if viper.GetBool(createMount) {
		fmt.Printf("Setting up hostmount on %s...\n", viper.GetString(mountString))

		path := os.Args[0]
		mountDebugVal := 0
		if glog.V(8) {
			mountDebugVal = 1
		}
		mountCmd := exec.Command(path, "mount", fmt.Sprintf("--v=%d", mountDebugVal), viper.GetString(mountString))
		mountCmd.Env = append(os.Environ(), constants.IsMinikubeChildProcess+"=true")
		if glog.V(8) {
			mountCmd.Stdout = os.Stdout
			mountCmd.Stderr = os.Stderr
		}
		err = mountCmd.Start()
		if err != nil {
			glog.Errorf("Error running command minikube mount %s", err)
			cmdUtil.MaybeReportErrorAndExit(err)
		}
		err = ioutil.WriteFile(filepath.Join(constants.GetMinipath(), constants.MountProcessFileName), []byte(strconv.Itoa(mountCmd.Process.Pid)), 0644)
		if err != nil {
			glog.Errorf("Error writing mount process pid to file: %s", err)
			cmdUtil.MaybeReportErrorAndExit(err)
		}
	}

	if kubeCfgSetup.KeepContext {
		fmt.Printf("The local Kubernetes cluster has started. The kubectl context has not been altered, kubectl will require \"--context=%s\" to use the local Kubernetes cluster.\n",
			kubeCfgSetup.ClusterName)
	} else {
		fmt.Println("Kubectl is now configured to use the cluster.")
	}
}

func validateK8sVersion(version string) {
	validVersion, err := kubernetes_versions.IsValidLocalkubeVersion(version, constants.KubernetesVersionGCSURL)
	if err != nil {
		glog.Errorln("Error getting valid kubernetes versions", err)
		os.Exit(1)
	}
	if !validVersion {
		fmt.Println("Invalid Kubernetes version.")
		kubernetes_versions.PrintKubernetesVersionsFromGCS(os.Stdout)
		os.Exit(1)
	}
}

func calculateDiskSizeInMB(humanReadableDiskSize string) int {
	diskSize, err := units.FromHumanSize(humanReadableDiskSize)
	if err != nil {
		glog.Errorf("Invalid disk size: %s", err)
	}
	return int(diskSize / units.MB)
}

func init() {
	startCmd.Flags().Bool(keepContext, constants.DefaultKeepContext, "This will keep the existing kubectl context and will create a minikube context.")
	startCmd.Flags().Bool(createMount, false, "This will start the mount daemon and automatically mount files into minikube")
	startCmd.Flags().String(mountString, constants.DefaultMountDir+":"+constants.DefaultMountEndpoint, "The argument to pass the minikube mount command on start")
	startCmd.Flags().String(isoURL, constants.DefaultIsoUrl, "Location of the minikube iso")
	startCmd.Flags().String(vmDriver, constants.DefaultVMDriver, fmt.Sprintf("VM driver is one of: %v", constants.SupportedVMDrivers))
	startCmd.Flags().Int(memory, constants.DefaultMemory, "Amount of RAM allocated to the minikube VM")
	startCmd.Flags().Int(cpus, constants.DefaultCPUS, "Number of CPUs allocated to the minikube VM")
	startCmd.Flags().String(humanReadableDiskSize, constants.DefaultDiskSize, "Disk size allocated to the minikube VM (format: <number>[<unit>], where unit = b, k, m or g)")
	startCmd.Flags().String(hostOnlyCIDR, "192.168.99.1/24", "The CIDR to be used for the minikube VM (only supported with Virtualbox driver)")
	startCmd.Flags().String(hypervVirtualSwitch, "", "The hyperv virtual switch name. Defaults to first found. (only supported with HyperV driver)")
	startCmd.Flags().String(kvmNetwork, "default", "The KVM network name. (only supported with KVM driver)")
	startCmd.Flags().String(xhyveDiskDriver, "ahci-hd", "The disk driver to use [ahci-hd|virtio-blk] (only supported with xhyve driver)")
	startCmd.Flags().StringArrayVar(&dockerEnv, "docker-env", nil, "Environment variables to pass to the Docker daemon. (format: key=value)")
	startCmd.Flags().StringArrayVar(&dockerOpt, "docker-opt", nil, "Specify arbitrary flags to pass to the Docker daemon. (format: key=value)")
	startCmd.Flags().String(apiServerName, constants.APIServerName, "The apiserver name which is used in the generated certificate for localkube/kubernetes.  This can be used if you want to make the apiserver available from outside the machine")
	startCmd.Flags().String(dnsDomain, "", "The cluster dns domain name used in the kubernetes cluster")
	startCmd.Flags().StringSliceVar(&insecureRegistry, "insecure-registry", nil, "Insecure Docker registries to pass to the Docker daemon")
	startCmd.Flags().StringSliceVar(&registryMirror, "registry-mirror", nil, "Registry mirrors to pass to the Docker daemon")
	startCmd.Flags().String(kubernetesVersion, constants.DefaultKubernetesVersion, "The kubernetes version that the minikube VM will use (ex: v1.2.3) \n OR a URI which contains a localkube binary (ex: https://storage.googleapis.com/minikube/k8sReleases/v1.3.0/localkube-linux-amd64)")
	startCmd.Flags().String(containerRuntime, "", "The container runtime to be used")
	startCmd.Flags().String(networkPlugin, "", "The name of the network plugin")
	startCmd.Flags().String(featureGates, "", "A set of key=value pairs that describe feature gates for alpha/experimental features.")
	startCmd.Flags().Var(&extraOptions, "extra-config",
		`A set of key=value pairs that describe configuration that may be passed to different components.
		The key should be '.' separated, and the first part before the dot is the component to apply the configuration to.
		Valid components are: kubelet, apiserver, controller-manager, etcd, proxy, scheduler.`)
	viper.BindPFlags(startCmd.Flags())
	RootCmd.AddCommand(startCmd)
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

var historyCmd = &cobra.Command{
	Use:   "history",
	Short: "Search keyword history",
	Long:  "Search keyword history",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("history called")
	},
}

func init() {
	RootCmd.AddCommand(historyCmd)
}
<commit_msg>WIP history command<commit_after>package cmd

import (
	"fmt"

	"github.com/fatih/color"
	"github.com/spf13/cobra"
	"github.com/wataru0225/sreq/snippet"
)

var historyCmd = &cobra.Command{
	Use:   "history",
	Short: "Search keyword history",
	Long:  "Search keyword history",
	Run: func(cmd *cobra.Command, args []string) {
		var snippets snippet.Snippets
		snippets.Load()
		for _, snip := range snippets.Snippets {
			fmt.Println(color.YellowString(snip.Url))
			fmt.Println(color.GreenString(snip.Title))
			fmt.Println(color.GreenString(snip.SearchKeyword))
			fmt.Println(color.WhiteString("-------------------------------"))
		}
	},
}

func init() {
	RootCmd.AddCommand(historyCmd)
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"os"
	"sort"
	"strings"
	"text/tabwriter"

	"github.com/dtan4/k8ship/kubernetes"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

// historyCmd represents the history command
var historyCmd = &cobra.Command{
	Use:   "history",
	Short: "View deployment history",
	RunE:  doHistory,
}

var historyOpts = struct {
	namespace string
}{}

func doHistory(cmd *cobra.Command, args []string) error {
	client, err := kubernetes.NewClient(rootOpts.annotationPrefix, rootOpts.kubeconfig, rootOpts.context)
	if err != nil {
		return errors.Wrap(err, "failed to create Kubernetes client")
	}

	ds, err := client.ListDeployments(historyOpts.namespace)
	if err != nil {
		return errors.Wrap(err, "failed to retrieve Deployments")
	}

	if len(ds) == 0 {
		return errors.Errorf("no Deployment found in namespace %s", historyOpts.namespace)
	}

	for _, d := range ds {
		fmt.Println("===== " + d.Name())

		rs, err := client.ListReplicaSets(d)
		if err != nil {
			return errors.Wrap(err, "failed to retrieve ReplicaSets")
		}

		lines := formatHistory(rs)
		sort.Sort(sort.Reverse(sort.StringSlice(lines)))

		w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
		headers := []string{
			"CREATED AT",
			"IMAGES",
		}
		fmt.Fprintln(w, strings.Join(headers, "\t"))

		for _, l := range lines {
			fmt.Fprintln(w, l)
		}

		w.Flush()
	}

	return nil
}

func formatHistory(rs []*kubernetes.ReplicaSet) []string {
	lines := make([]string, 0, len(rs))

	for _, r := range rs {
		lines = append(lines, fmt.Sprintf("%s\t%s", r.CreatedAt(), r.Images()))
	}

	return lines
}

func init() {
	RootCmd.AddCommand(historyCmd)

	historyCmd.Flags().StringVarP(&historyOpts.namespace, "namespace", "n", kubernetes.DefaultNamespace(), "Kubernetes namespace")
}
<commit_msg>Format images hashmap<commit_after>package cmd

import (
	"fmt"
	"os"
	"sort"
	"strings"
	"text/tabwriter"

	"github.com/dtan4/k8ship/kubernetes"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

// historyCmd represents the history command
var historyCmd = &cobra.Command{
	Use:   "history",
	Short: "View deployment history",
	RunE:  doHistory,
}

var historyOpts = struct {
	namespace string
}{}

func doHistory(cmd *cobra.Command, args []string) error {
	client, err := kubernetes.NewClient(rootOpts.annotationPrefix, rootOpts.kubeconfig, rootOpts.context)
	if err != nil {
		return errors.Wrap(err, "failed to create Kubernetes client")
	}

	ds, err := client.ListDeployments(historyOpts.namespace)
	if err != nil {
		return errors.Wrap(err, "failed to retrieve Deployments")
	}

	if len(ds) == 0 {
		return errors.Errorf("no Deployment found in namespace %s", historyOpts.namespace)
	}

	for _, d := range ds {
		fmt.Println("===== " + d.Name())

		rs, err := client.ListReplicaSets(d)
		if err != nil {
			return errors.Wrap(err, "failed to retrieve ReplicaSets")
		}

		lines := formatHistory(rs)
		sort.Sort(sort.Reverse(sort.StringSlice(lines)))

		w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
		headers := []string{
			"CREATED AT",
			"IMAGES",
		}
		fmt.Fprintln(w, strings.Join(headers, "\t"))

		for _, l := range lines {
			fmt.Fprintln(w, l)
		}

		w.Flush()
	}

	return nil
}

func formatHistory(rs []*kubernetes.ReplicaSet) []string {
	lines := make([]string, 0, len(rs))

	for _, r := range rs {
		lines = append(lines, fmt.Sprintf("%s\t%s", r.CreatedAt(), formatImages(r.Images())))
	}

	return lines
}

func formatImages(images map[string]string) string {
	ss := make([]string, 0, len(images))

	for k, v := range images {
		ss = append(ss, k+" => "+v)
	}

	return strings.Join(ss, ",")
}

func init() {
	RootCmd.AddCommand(historyCmd)

	historyCmd.Flags().StringVarP(&historyOpts.namespace, "namespace", "n", kubernetes.DefaultNamespace(), "Kubernetes namespace")
}
<|endoftext|>


<commit_before>package frame

import (
	"fmt"
	"os"
	"strings"
	"time"
	"unicode"

	"github.com/zetamatta/nyagos/dos"
)

func Format2Prompt(format string) string {
	if format == "" {
		format = "[$P]$_$$$S"
	}
	var buffer strings.Builder
	lastchar := '\000'
	for reader := strings.NewReader(format); reader.Len() > 0; {
		ch, _, err := reader.ReadRune()
		if err != nil {
			break
		}
		if lastchar == '$' {
			c := unicode.ToLower(ch)
			if c == 'a' {
				buffer.WriteRune('&')
			} else if c == 'b' {
				buffer.WriteRune('|')
			} else if c == 'c' {
				buffer.WriteRune('(')
			} else if c == 'd' {
				buffer.WriteString(time.Now().Format("2006-01-02"))
			} else if c == 'e' {
				buffer.WriteRune('\x1B')
			} else if c == 'f' {
				buffer.WriteRune(')')
			} else if c == 'g' {
				buffer.WriteRune('>')
			} else if c == 'h' {
				buffer.WriteRune('\b')
			} else if c == 'l' {
				buffer.WriteRune('<')
			} else if c == 'n' {
				wd, err := os.Getwd()
				if err == nil {
					buffer.WriteString(wd[:2])
				}
			} else if c == 'p' {
				if wd, err := os.Getwd(); err != nil {
					fmt.Fprintf(os.Stderr, "$P: %s\n", err.Error())
				} else {
					buffer.WriteString(dos.ReplaceHomeToTildeSlash(wd))
				}
			} else if c == 'q' {
				buffer.WriteRune('=')
			} else if c == 's' {
				buffer.WriteRune(' ')
			} else if c == 't' {
				now := time.Now()
				hour, min, sec := now.Clock()
				nnn := now.Nanosecond() / 10000000
				buffer.WriteString(
					fmt.Sprintf("%02d:%02d:%02d.%02d",
						hour, min, sec, nnn))
			} else if c == 'u' {
				r := 0
				for i := 0; i < 4 && reader.Len() > 0; i++ {
					r1, _, err := reader.ReadRune()
					if err != nil {
						break
					}
					n := strings.IndexRune("0123456789ABCDEF",
						unicode.ToUpper(r1))
					if n < 0 {
						reader.UnreadRune()
						break
					}
					r = r*16 + n
				}
				if r > 0 {
					buffer.WriteRune(rune(r))
				}
			} else if c == 'v' {
				// Windows Version
			} else if c == '_' {
				buffer.WriteRune('\n')
			} else if c == '$' {
				buffer.WriteRune('$')
				ch = '\000'
			} else {
				buffer.WriteRune('$')
				buffer.WriteRune(ch)
			}
		} else if ch != '$' {
			buffer.WriteRune(ch)
		}
		lastchar = ch
	}
	return buffer.String()
}
<commit_msg>Fix frame/prompt.go for golint<commit_after>package frame

import (
	"fmt"
	"os"
	"strings"
	"time"
	"unicode"

	"github.com/zetamatta/nyagos/dos"
)

// Format2Prompt converts format-string to output-string
func Format2Prompt(format string) string {
	if format == "" {
		format = "[$P]$_$$$S"
	}
	var buffer strings.Builder
	lastchar := '\000'
	for reader := strings.NewReader(format); reader.Len() > 0; {
		ch, _, err := reader.ReadRune()
		if err != nil {
			break
		}
		if lastchar == '$' {
			c := unicode.ToLower(ch)
			if c == 'a' {
				buffer.WriteRune('&')
			} else if c == 'b' {
				buffer.WriteRune('|')
			} else if c == 'c' {
				buffer.WriteRune('(')
			} else if c == 'd' {
				buffer.WriteString(time.Now().Format("2006-01-02"))
			} else if c == 'e' {
				buffer.WriteRune('\x1B')
			} else if c == 'f' {
				buffer.WriteRune(')')
			} else if c == 'g' {
				buffer.WriteRune('>')
			} else if c == 'h' {
				buffer.WriteRune('\b')
			} else if c == 'l' {
				buffer.WriteRune('<')
			} else if c == 'n' {
				wd, err := os.Getwd()
				if err == nil {
					buffer.WriteString(wd[:2])
				}
			} else if c == 'p' {
				if wd, err := os.Getwd(); err != nil {
					fmt.Fprintf(os.Stderr, "$P: %s\n", err.Error())
				} else {
					buffer.WriteString(dos.ReplaceHomeToTildeSlash(wd))
				}
			} else if c == 'q' {
				buffer.WriteRune('=')
			} else if c == 's' {
				buffer.WriteRune(' ')
			} else if c == 't' {
				now := time.Now()
				hour, min, sec := now.Clock()
				nnn := now.Nanosecond() / 10000000
				buffer.WriteString(
					fmt.Sprintf("%02d:%02d:%02d.%02d",
						hour, min, sec, nnn))
			} else if c == 'u' {
				r := 0
				for i := 0; i < 4 && reader.Len() > 0; i++ {
					r1, _, err := reader.ReadRune()
					if err != nil {
						break
					}
					n := strings.IndexRune("0123456789ABCDEF",
						unicode.ToUpper(r1))
					if n < 0 {
						reader.UnreadRune()
						break
					}
					r = r*16 + n
				}
				if r > 0 {
					buffer.WriteRune(rune(r))
				}
			} else if c == 'v' {
				// Windows Version
			} else if c == '_' {
				buffer.WriteRune('\n')
			} else if c == '$' {
				buffer.WriteRune('$')
				ch = '\000'
			} else {
				buffer.WriteRune('$')
				buffer.WriteRune(ch)
			}
		} else if ch != '$' {
			buffer.WriteRune(ch)
		}
		lastchar = ch
	}
	return buffer.String()
}
<|endoftext|>