<commit_before>package email

import (
	"bytes"
	"crypto/tls"
	"html/template"
	"net"
	"net/mail"
	"net/smtp"
	"time"

	"github.com/labstack/gommon/random"
)

type (
	Email struct {
		Auth        smtp.Auth
		Header      map[string]string
		Template    *template.Template
		smtpAddress string
	}

	Message struct {
		ID          string  `json:"id"`
		From        string  `json:"from"`
		To          string  `json:"to"`
		CC          string  `json:"cc"`
		Subject     string  `json:"subject"`
		Text        string  `json:"text"`
		HTML        string  `json:"html"`
		Inlines     []*File `json:"inlines"`
		Attachments []*File `json:"attachments"`
		buffer      *bytes.Buffer
		boundary    string
	}

	File struct {
		Name    string
		Type    string
		Content string
	}
)

func New(smtpAddress string) *Email {
	return &Email{
		smtpAddress: smtpAddress,
		Header:      map[string]string{},
	}
}

func (m *Message) writeHeader(key, value string) {
	m.buffer.WriteString(key)
	m.buffer.WriteString(": ")
	m.buffer.WriteString(value)
	m.buffer.WriteString("\r\n")
}

func (m *Message) writeBoundary() {
	m.buffer.WriteString("--")
	m.buffer.WriteString(m.boundary)
	m.buffer.WriteString("\r\n")
}

func (m *Message) writeText(content string, contentType string) {
	m.writeBoundary()
	m.writeHeader("Content-Type", contentType+"; charset=UTF-8")
	m.buffer.WriteString("\r\n")
	m.buffer.WriteString(content)
	m.buffer.WriteString("\r\n")
	m.buffer.WriteString("\r\n")
}

func (m *Message) writeFile(f *File, disposition string) {
	m.writeBoundary()
	m.writeHeader("Content-Type", f.Type+`; name="`+f.Name+`"`)
	m.writeHeader("Content-Disposition", disposition+`; filename="`+f.Name+`"`)
	m.writeHeader("Content-Transfer-Encoding", "base64")
	m.buffer.WriteString("\r\n")
	m.buffer.WriteString(f.Content)
	m.buffer.WriteString("\r\n")
	m.buffer.WriteString("\r\n")
}

func (e *Email) Send(m *Message) (err error) {
	// Message header
	m.buffer = bytes.NewBuffer(make([]byte, 256))
	m.buffer.Reset()
	m.boundary = random.String(16)
	m.writeHeader("MIME-Version", "1.0")
	m.writeHeader("Message-ID", m.ID)
	m.writeHeader("Date", time.Now().Format(time.RFC1123Z))
	m.writeHeader("From", m.From)
	m.writeHeader("To", m.To)
	if m.CC != "" {
		m.writeHeader("CC", m.CC)
	}
	if m.Subject != "" {
		m.writeHeader("Subject", m.Subject)
	}
	// Extra
	for k, v := range e.Header {
		m.writeHeader(k, v)
	}
	m.writeHeader("Content-Type", "multipart/mixed; boundary="+m.boundary)
	m.buffer.WriteString("\r\n")

	// Message body
	if m.Text != "" {
		m.writeText(m.Text, "text/plain")
	} else if m.HTML != "" {
		m.writeText(m.HTML, "text/html")
	} else {
		// TODO:
	}

	// Inlines/attachments
	for _, f := range m.Inlines {
		m.writeFile(f, "inline")
	}
	for _, f := range m.Attachments {
		m.writeFile(f, "attachment")
	}
	m.buffer.WriteString("--")
	m.buffer.WriteString(m.boundary)
	m.buffer.WriteString("--")

	// Dial
	c, err := smtp.Dial(e.smtpAddress)
	if err != nil {
		return
	}
	defer c.Close()

	// Check if TLS is required
	if ok, _ := c.Extension("STARTTLS"); ok {
		host, _, _ := net.SplitHostPort(e.smtpAddress)
		config := &tls.Config{ServerName: host}
		if err = c.StartTLS(config); err != nil {
			return err
		}
	}

	// Authenticate
	if e.Auth != nil {
		if err = c.Auth(e.Auth); err != nil {
			return
		}
	}

	// Send message
	from, err := mail.ParseAddress(m.From)
	if err != nil {
		return
	}
	if err = c.Mail(from.Address); err != nil {
		return
	}
	to, err := mail.ParseAddressList(m.To)
	if err != nil {
		return
	}
	for _, a := range to {
		if err = c.Rcpt(a.Address); err != nil {
			return
		}
	}
	wc, err := c.Data()
	if err != nil {
		return
	}
	defer wc.Close()
	_, err = m.buffer.WriteTo(wc)
	return
}
<commit_msg>[Email] Text to BodyText & HTML to BodyHTML<commit_after>package email

import (
	"bytes"
	"crypto/tls"
	"html/template"
	"net"
	"net/mail"
	"net/smtp"
	"time"

	"github.com/labstack/gommon/random"
)

type (
	Email struct {
		Auth        smtp.Auth
		Header      map[string]string
		Template    *template.Template
		smtpAddress string
	}

	Message struct {
		ID          string  `json:"id"`
		From        string  `json:"from"`
		To          string  `json:"to"`
		CC          string  `json:"cc"`
		Subject     string  `json:"subject"`
		BodyText    string  `json:"body_text"`
		BodyHTML    string  `json:"body_html"`
		Inlines     []*File `json:"inlines"`
		Attachments []*File `json:"attachments"`
		buffer      *bytes.Buffer
		boundary    string
	}

	File struct {
		Name    string
		Type    string
		Content string
	}
)

func New(smtpAddress string) *Email {
	return &Email{
		smtpAddress: smtpAddress,
		Header:      map[string]string{},
	}
}

func (m *Message) writeHeader(key, value string) {
	m.buffer.WriteString(key)
	m.buffer.WriteString(": ")
	m.buffer.WriteString(value)
	m.buffer.WriteString("\r\n")
}

func (m *Message) writeBoundary() {
	m.buffer.WriteString("--")
	m.buffer.WriteString(m.boundary)
	m.buffer.WriteString("\r\n")
}

func (m *Message) writeText(content string, contentType string) {
	m.writeBoundary()
	m.writeHeader("Content-Type", contentType+"; charset=UTF-8")
	m.buffer.WriteString("\r\n")
	m.buffer.WriteString(content)
	m.buffer.WriteString("\r\n")
	m.buffer.WriteString("\r\n")
}

func (m *Message) writeFile(f *File, disposition string) {
	m.writeBoundary()
	m.writeHeader("Content-Type", f.Type+`; name="`+f.Name+`"`)
	m.writeHeader("Content-Disposition", disposition+`; filename="`+f.Name+`"`)
	m.writeHeader("Content-Transfer-Encoding", "base64")
	m.buffer.WriteString("\r\n")
	m.buffer.WriteString(f.Content)
	m.buffer.WriteString("\r\n")
	m.buffer.WriteString("\r\n")
}

func (e *Email) Send(m *Message) (err error) {
	// Message header
	m.buffer = bytes.NewBuffer(make([]byte, 256))
	m.buffer.Reset()
	m.boundary = random.String(16)
	m.writeHeader("MIME-Version", "1.0")
	m.writeHeader("Message-ID", m.ID)
	m.writeHeader("Date", time.Now().Format(time.RFC1123Z))
	m.writeHeader("From", m.From)
	m.writeHeader("To", m.To)
	if m.CC != "" {
		m.writeHeader("CC", m.CC)
	}
	if m.Subject != "" {
		m.writeHeader("Subject", m.Subject)
	}
	// Extra
	for k, v := range e.Header {
		m.writeHeader(k, v)
	}
	m.writeHeader("Content-Type", "multipart/mixed; boundary="+m.boundary)
	m.buffer.WriteString("\r\n")

	// Message body
	if m.BodyText != "" {
		m.writeText(m.BodyText, "text/plain")
	} else if m.BodyHTML != "" {
		m.writeText(m.BodyHTML, "text/html")
	} else {
		// TODO:
	}

	// Inlines/attachments
	for _, f := range m.Inlines {
		m.writeFile(f, "inline")
	}
	for _, f := range m.Attachments {
		m.writeFile(f, "attachment")
	}
	m.buffer.WriteString("--")
	m.buffer.WriteString(m.boundary)
	m.buffer.WriteString("--")

	// Dial
	c, err := smtp.Dial(e.smtpAddress)
	if err != nil {
		return
	}
	defer c.Close()

	// Check if TLS is required
	if ok, _ := c.Extension("STARTTLS"); ok {
		host, _, _ := net.SplitHostPort(e.smtpAddress)
		config := &tls.Config{ServerName: host}
		if err = c.StartTLS(config); err != nil {
			return err
		}
	}

	// Authenticate
	if e.Auth != nil {
		if err = c.Auth(e.Auth); err != nil {
			return
		}
	}

	// Send message
	from, err := mail.ParseAddress(m.From)
	if err != nil {
		return
	}
	if err = c.Mail(from.Address); err != nil {
		return
	}
	to, err := mail.ParseAddressList(m.To)
	if err != nil {
		return
	}
	for _, a := range to {
		if err = c.Rcpt(a.Address); err != nil {
			return
		}
	}
	wc, err := c.Data()
	if err != nil {
		return
	}
	defer wc.Close()
	_, err = m.buffer.WriteTo(wc)
	return
}
<|endoftext|>


<commit_before>//go:build windows
// +build windows

/*
Copyright 2018 Google Inc.

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

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

Unless required by applicable law or 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.
*/

// The cli application implements the end-user client for the Splice service.
package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"time"

	appclient "github.com/google/splice/cli/appclient"
	"github.com/google/certtostore"
	"github.com/google/splice/appengine/server"
	"github.com/google/splice/models"
	"github.com/google/splice/shared/certs"
	metadata "github.com/google/splice/shared/crypto"
	"github.com/google/splice/shared/provisioning"
)

const (
	pollMaxRetries = 100
)

var (
	myName       = flag.String("name", "", "The requested hostname.")
	pollInterval = flag.Int("poll_interval", 30, "Time in seconds between server polling attempts.")
	serverAddr   = flag.String("server", "", "The address of the Splice app server.")
	reallyJoin   = flag.Bool("really_join", false, "Really join the local machine if the request succeeds.")
	unattended   = flag.Bool("unattended", false, "Runs in unattended mode. A valid certificate is required for unattended mode.")
	verbose      = flag.Bool("verbose", false, "Give more verbose output.")

	// GCE
	isGCE = flag.Bool("gce", false, "Include GCE Metadata.")

	// Auth flags
	username = flag.String("user_name", "", "User name for login.")

	// Encryption flags
	certIssuers       = flag.String("cert_issuer", "", "Comma delimited list of client certificate issuers to be looked up for metadata encryption.")
	certIntermediates = flag.String("cert_intermediate", "", "Comma delimited list of additional intermediate certificate issuers.")
	certContainer     = flag.String("cert_container", "", "The client certificate CNG container name.")
	encrypt           = flag.Bool("encrypt", true, "Encrypt all metadata in transit.")
	generateCert      = flag.Bool("generate_cert", false, "Generate a self-signed certificate for encryption.")

	// Generator Support
	generatorID = flag.String("generator_id", "", "The identity of a Splice name generator to be associated with the request.")

	issuers, intermediates []string
)

type client interface {
	Do(*http.Request) (*http.Response, error)
}

// post posts JSON data to the splice application server
func post(c client, msg interface{}, addr string) (*models.Response, error) {
	body, err := json.Marshal(msg)
	if err != nil {
		return nil, fmt.Errorf("error marshalling message(%v): %v", msg, err)
	}

	req, err := http.NewRequest("POST", addr, bytes.NewBuffer(body))
	if err != nil {
		return nil, fmt.Errorf("error composing post request: %v", err)
	}
	req.Header.Set("Content-Type", "application/json")

	res, err := c.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error executing post request: %v", err)
	}
	defer res.Body.Close()

	if res.StatusCode < http.StatusOK || res.StatusCode > http.StatusIMUsed {
		return nil, fmt.Errorf("invalid response code received for request: %d", res.StatusCode)
	}

	respBody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("error reading response body: %v", err)
	}

	resp := &models.Response{}
	if err := json.Unmarshal(respBody, resp); err != nil {
		msg := fmt.Sprintf("json.Unmarshal returned: %v\n\nResponse Body: %s", err, respBody)
		if *verbose {
			msg = fmt.Sprintf("%s (body: %s)", msg, respBody)
		}
		return nil, fmt.Errorf(msg)
	}
	return resp, nil
}

// request posts to the splice request endpoint and returns the
// requestID if successful or an error.
func request(c client, clientID string, cert certs.Certificate) (string, error) {
	model := &models.ClientRequest{
		Hostname: *myName,
		ClientID: clientID,
	}
	endpoint := *serverAddr + "/request"
	if *unattended {
		endpoint = endpoint + "-unattended"
	}

	if *isGCE {
		model.GCEMetadata.Audience = endpoint
		if err := model.GCEMetadata.Read(); err != nil {
			return "", fmt.Errorf("error reading GCE metadata: %v", err)
		}
	}

	if *encrypt {
		model.ClientCert = cert.Cert.Raw
	}

	if *generatorID != "" {
		model.GeneratorID = *generatorID
	}

	resp, err := post(c, model, endpoint)
	if err != nil {
		return "", fmt.Errorf("post(%s, %q) returned %v", model, endpoint, err)
	}
	if resp.ErrorCode != server.StatusSuccess {
		return "", fmt.Errorf("post to %s returned: %v %d %s", endpoint, resp.Status, resp.ErrorCode, resp.ResponseData)
	}

	if *verbose {
		fmt.Printf("Request ID: %s\n", resp.RequestID)
	}
	return resp.RequestID, nil
}

func resultPoll(c client, reqID string, clientID string) (*models.Response, error) {
	status := &models.StatusQuery{
		RequestID: reqID,
		ClientID:  clientID,
	}

	endpoint := *serverAddr + "/result"
	if *unattended {
		endpoint = endpoint + "-unattended"
	}

	for i := 0; i < pollMaxRetries; i++ {
		time.Sleep(time.Duration(*pollInterval) * time.Second)
		resp, err := post(c, status, endpoint)
		if err != nil {
			return nil, fmt.Errorf("post: %v", err)
		}
		fmt.Println("Checking for a result...")
		if *verbose {
			fmt.Printf("%v\n", resp)
		}
		if resp.ErrorCode == server.StatusInvalidCertError {
			// Retry lookups for Datastores to allow eventual consistency.
			fmt.Println("Result not found or invalid cert, retrying...")
			continue
		}
		if resp.ErrorCode != server.StatusSuccess {
			return resp, fmt.Errorf("server processing failed, request:%s, id:%s, status:%d %v, data: %s", reqID, clientID, resp.ErrorCode, resp.Status, resp.ResponseData)
		}
		if resp.Status == models.RequestStatusFailed {
			return resp, fmt.Errorf("domain join failed, request:%s, id:%s, status:%d %v, data: %s", reqID, clientID, resp.ErrorCode, resp.Status, resp.ResponseData)
		}
		if *generatorID == "" {
			if (resp.Status == models.RequestStatusCompleted) && (resp.Hostname != *myName) {
				fmt.Printf("Result returned is for a different host, got %s, want %s.\n", resp.Hostname, *myName)
				return resp, nil
			}
		}
		if (resp.Status == models.RequestStatusCompleted) && resp.ResponseData != nil {
			fmt.Printf("Successfully retrieved result for host %s.\n", resp.Hostname)
			return resp, nil
		}
	}

	return nil, fmt.Errorf("retry limit (%d) exceeded", pollMaxRetries)
}

func checkFlags() error {
	switch {
	case *myName == "" && *generatorID == "":
		return errors.New("must provide either -name or -generator_id")
	case *serverAddr == "":
		return errors.New("the -server flag is required")
	case *encrypt && !*generateCert && *certIssuers == "":
		return errors.New("-encrypt requires either -generate_cert or -cert_issuer")
	case *encrypt && *generateCert && *certIssuers != "":
		return errors.New("-encrypt is not supported with both -generate_cert and -cert_issuer")
	}

	if !strings.HasPrefix(*serverAddr, "http") {
		*serverAddr = "https://" + *serverAddr
	}

	if *certIssuers != "" {
		issuers = strings.Split(*certIssuers, ",")
	}

	if *certIntermediates != "" {
		intermediates = strings.Split(*certIntermediates, ",")
	}

	return nil
}

func main() {
	var err error

	if err = checkFlags(); err != nil {
		log.Fatal(err.Error())
	}

	var cert certs.Certificate
	if len(issuers) >= 1 {
		store, err := certs.NewStore(*certContainer, issuers, intermediates)
		if err != nil {
			log.Fatalf("error opening certificate store: %v", err)
		}
		defer store.Close()

		var ctx certs.Context
		cert, ctx, err = store.Find()
		if err != nil || cert.Cert == nil || cert.Decrypter == nil {
			log.Fatalf("error locating client certificate for issuers '%v': %v", issuers, err)
		}
		defer ctx.Close()
	}

	if *encrypt {
		if *generateCert {
			notBefore := time.Now().Add(-1 * time.Hour)
			notAfter := time.Now().Add(time.Hour * 24 * 365 * 1)
			err = cert.Generate(*myName, notBefore, notAfter)
			if err != nil {
				log.Fatalf("error generating self-signed certificate: %v", err)
			}
		}
		fmt.Println("Requesting encryption with public key.")
	} else {
		fmt.Println("Not requesting encryption.")
	}

	// UUID is the fallback clientID when cert lookups
	// are not enabled.
	var clientID string
	if len(issuers) >= 1 {
		// The SHA256 hash of the cert is used server side for client verification when
		// certificate verification is enabled.
		clientID = certs.ClientID(cert.Cert.Raw)
	} else {
		computer, err := certtostore.CompProdInfo()
		if err != nil {
			log.Fatalf("certtostore.CompInfo returned %v", err)
		}
		clientID = computer.UUID
	}

	var c client
	if !*unattended {
		c, err = appclient.Connect(*serverAddr, *username)
		if err != nil {
			log.Fatalf("SSO error: %v", err)
		}
	} else {
		c, err = appclient.TLSClient(cert.Cert.Raw, cert.Decrypter)
		if err != nil {
			log.Fatalf("error during TLS client setup: %v", err)
		}
	}

	reqID, err := request(c, clientID, cert)
	if err != nil {
		log.Fatalf("request: %v", err)
	}
	fmt.Println("Successfully submitted join request.")

	resp, err := resultPoll(c, reqID, clientID)
	if err != nil {
		log.Fatalf("resultPoll: %v\n", err)
	}
	meta := metadata.Metadata{
		Data:   resp.ResponseData,
		AESKey: resp.ResponseKey,
		Nonce:  resp.CipherNonce,
	}

	if *encrypt {
		meta.Data, err = meta.Decrypt(cert.Decrypter)
		if err != nil {
			log.Fatalf("error decrypting metadata: %v", err)
		}
	}

	if *reallyJoin {
		if err := provisioning.OfflineJoin(meta.Data); err != nil {
			log.Fatalf("error applying join metadata to host: %v", err)
		}
		fmt.Println("Successfully joined the domain! Reboot required to complete domain join.")
	} else {
		fmt.Println("Metadata received but skipping application without -really_join")
	}
}
<commit_msg>Remove hostname-specific log message from CLI.<commit_after>//go:build windows
// +build windows

/*
Copyright 2018 Google Inc.

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

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

Unless required by applicable law or 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.
*/

// The cli application implements the end-user client for the Splice service.
package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"time"

	appclient "github.com/google/splice/cli/appclient"
	"github.com/google/certtostore"
	"github.com/google/splice/appengine/server"
	"github.com/google/splice/models"
	"github.com/google/splice/shared/certs"
	metadata "github.com/google/splice/shared/crypto"
	"github.com/google/splice/shared/provisioning"
)

const (
	pollMaxRetries = 100
)

var (
	myName       = flag.String("name", "", "The requested hostname.")
	pollInterval = flag.Int("poll_interval", 30, "Time in seconds between server polling attempts.")
	serverAddr   = flag.String("server", "", "The address of the Splice app server.")
	reallyJoin   = flag.Bool("really_join", false, "Really join the local machine if the request succeeds.")
	unattended   = flag.Bool("unattended", false, "Runs in unattended mode. A valid certificate is required for unattended mode.")
	verbose      = flag.Bool("verbose", false, "Give more verbose output.")

	// GCE
	isGCE = flag.Bool("gce", false, "Include GCE Metadata.")

	// Auth flags
	username = flag.String("user_name", "", "User name for login.")

	// Encryption flags
	certIssuers       = flag.String("cert_issuer", "", "Comma delimited list of client certificate issuers to be looked up for metadata encryption.")
	certIntermediates = flag.String("cert_intermediate", "", "Comma delimited list of additional intermediate certificate issuers.")
	certContainer     = flag.String("cert_container", "", "The client certificate CNG container name.")
	encrypt           = flag.Bool("encrypt", true, "Encrypt all metadata in transit.")
	generateCert      = flag.Bool("generate_cert", false, "Generate a self-signed certificate for encryption.")

	// Generator Support
	generatorID = flag.String("generator_id", "", "The identity of a Splice name generator to be associated with the request.")

	issuers, intermediates []string
)

type client interface {
	Do(*http.Request) (*http.Response, error)
}

// post posts JSON data to the splice application server
func post(c client, msg interface{}, addr string) (*models.Response, error) {
	body, err := json.Marshal(msg)
	if err != nil {
		return nil, fmt.Errorf("error marshalling message(%v): %v", msg, err)
	}

	req, err := http.NewRequest("POST", addr, bytes.NewBuffer(body))
	if err != nil {
		return nil, fmt.Errorf("error composing post request: %v", err)
	}
	req.Header.Set("Content-Type", "application/json")

	res, err := c.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error executing post request: %v", err)
	}
	defer res.Body.Close()

	if res.StatusCode < http.StatusOK || res.StatusCode > http.StatusIMUsed {
		return nil, fmt.Errorf("invalid response code received for request: %d", res.StatusCode)
	}

	respBody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("error reading response body: %v", err)
	}

	resp := &models.Response{}
	if err := json.Unmarshal(respBody, resp); err != nil {
		msg := fmt.Sprintf("json.Unmarshal returned: %v\n\nResponse Body: %s", err, respBody)
		if *verbose {
			msg = fmt.Sprintf("%s (body: %s)", msg, respBody)
		}
		return nil, fmt.Errorf(msg)
	}
	return resp, nil
}

// request posts to the splice request endpoint and returns the
// requestID if successful or an error.
func request(c client, clientID string, cert certs.Certificate) (string, error) {
	model := &models.ClientRequest{
		Hostname: *myName,
		ClientID: clientID,
	}
	endpoint := *serverAddr + "/request"
	if *unattended {
		endpoint = endpoint + "-unattended"
	}

	if *isGCE {
		model.GCEMetadata.Audience = endpoint
		if err := model.GCEMetadata.Read(); err != nil {
			return "", fmt.Errorf("error reading GCE metadata: %v", err)
		}
	}

	if *encrypt {
		model.ClientCert = cert.Cert.Raw
	}

	if *generatorID != "" {
		model.GeneratorID = *generatorID
	}

	resp, err := post(c, model, endpoint)
	if err != nil {
		return "", fmt.Errorf("post(%s, %q) returned %v", model, endpoint, err)
	}
	if resp.ErrorCode != server.StatusSuccess {
		return "", fmt.Errorf("post to %s returned: %v %d %s", endpoint, resp.Status, resp.ErrorCode, resp.ResponseData)
	}

	if *verbose {
		fmt.Printf("Request ID: %s\n", resp.RequestID)
	}
	return resp.RequestID, nil
}

func resultPoll(c client, reqID string, clientID string) (*models.Response, error) {
	status := &models.StatusQuery{
		RequestID: reqID,
		ClientID:  clientID,
	}

	endpoint := *serverAddr + "/result"
	if *unattended {
		endpoint = endpoint + "-unattended"
	}

	for i := 0; i < pollMaxRetries; i++ {
		time.Sleep(time.Duration(*pollInterval) * time.Second)
		resp, err := post(c, status, endpoint)
		if err != nil {
			return nil, fmt.Errorf("post: %v", err)
		}
		fmt.Println("Checking for a result...")
		if *verbose {
			fmt.Printf("%v\n", resp)
		}
		if resp.ErrorCode == server.StatusInvalidCertError {
			// Retry lookups for Datastores to allow eventual consistency.
			fmt.Println("Result not found or invalid cert, retrying...")
			continue
		}
		if resp.ErrorCode != server.StatusSuccess {
			return resp, fmt.Errorf("server processing failed, request:%s, id:%s, status:%d %v, data: %s", reqID, clientID, resp.ErrorCode, resp.Status, resp.ResponseData)
		}
		if resp.Status == models.RequestStatusFailed {
			return resp, fmt.Errorf("domain join failed, request:%s, id:%s, status:%d %v, data: %s", reqID, clientID, resp.ErrorCode, resp.Status, resp.ResponseData)
		}
		if *generatorID == "" {
			if (resp.Status == models.RequestStatusCompleted) && (resp.Hostname != *myName) {
				fmt.Printf("Result returned is for a different host, got %s, want %s.\n", resp.Hostname, *myName)
				return resp, nil
			}
		}
		if (resp.Status == models.RequestStatusCompleted) && resp.ResponseData != nil {
			fmt.Println("Successfully retrieved result.")
			return resp, nil
		}
	}

	return nil, fmt.Errorf("retry limit (%d) exceeded", pollMaxRetries)
}

func checkFlags() error {
	switch {
	case *myName == "" && *generatorID == "":
		return errors.New("must provide either -name or -generator_id")
	case *serverAddr == "":
		return errors.New("the -server flag is required")
	case *encrypt && !*generateCert && *certIssuers == "":
		return errors.New("-encrypt requires either -generate_cert or -cert_issuer")
	case *encrypt && *generateCert && *certIssuers != "":
		return errors.New("-encrypt is not supported with both -generate_cert and -cert_issuer")
	}

	if !strings.HasPrefix(*serverAddr, "http") {
		*serverAddr = "https://" + *serverAddr
	}

	if *certIssuers != "" {
		issuers = strings.Split(*certIssuers, ",")
	}

	if *certIntermediates != "" {
		intermediates = strings.Split(*certIntermediates, ",")
	}

	return nil
}

func main() {
	var err error

	if err = checkFlags(); err != nil {
		log.Fatal(err.Error())
	}

	var cert certs.Certificate
	if len(issuers) >= 1 {
		store, err := certs.NewStore(*certContainer, issuers, intermediates)
		if err != nil {
			log.Fatalf("error opening certificate store: %v", err)
		}
		defer store.Close()

		var ctx certs.Context
		cert, ctx, err = store.Find()
		if err != nil || cert.Cert == nil || cert.Decrypter == nil {
			log.Fatalf("error locating client certificate for issuers '%v': %v", issuers, err)
		}
		defer ctx.Close()
	}

	if *encrypt {
		if *generateCert {
			notBefore := time.Now().Add(-1 * time.Hour)
			notAfter := time.Now().Add(time.Hour * 24 * 365 * 1)
			err = cert.Generate(*myName, notBefore, notAfter)
			if err != nil {
				log.Fatalf("error generating self-signed certificate: %v", err)
			}
		}
		fmt.Println("Requesting encryption with public key.")
	} else {
		fmt.Println("Not requesting encryption.")
	}

	// UUID is the fallback clientID when cert lookups
	// are not enabled.
	var clientID string
	if len(issuers) >= 1 {
		// The SHA256 hash of the cert is used server side for client verification when
		// certificate verification is enabled.
		clientID = certs.ClientID(cert.Cert.Raw)
	} else {
		computer, err := certtostore.CompProdInfo()
		if err != nil {
			log.Fatalf("certtostore.CompInfo returned %v", err)
		}
		clientID = computer.UUID
	}

	var c client
	if !*unattended {
		c, err = appclient.Connect(*serverAddr, *username)
		if err != nil {
			log.Fatalf("SSO error: %v", err)
		}
	} else {
		c, err = appclient.TLSClient(cert.Cert.Raw, cert.Decrypter)
		if err != nil {
			log.Fatalf("error during TLS client setup: %v", err)
		}
	}

	reqID, err := request(c, clientID, cert)
	if err != nil {
		log.Fatalf("request: %v", err)
	}
	fmt.Println("Successfully submitted join request.")

	resp, err := resultPoll(c, reqID, clientID)
	if err != nil {
		log.Fatalf("resultPoll: %v\n", err)
	}
	meta := metadata.Metadata{
		Data:   resp.ResponseData,
		AESKey: resp.ResponseKey,
		Nonce:  resp.CipherNonce,
	}

	if *encrypt {
		meta.Data, err = meta.Decrypt(cert.Decrypter)
		if err != nil {
			log.Fatalf("error decrypting metadata: %v", err)
		}
	}

	if *reallyJoin {
		if err := provisioning.OfflineJoin(meta.Data); err != nil {
			log.Fatalf("error applying join metadata to host: %v", err)
		}
		fmt.Println("Successfully joined the domain! Reboot required to complete domain join.")
	} else {
		fmt.Println("Metadata received but skipping application without -really_join")
	}
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"os"
	"path"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/stepman/stepman"
	"github.com/codegangsta/cli"
)

func initLogFormatter() {
	log.SetFormatter(&log.TextFormatter{
		ForceColors:     true,
		FullTimestamp:   true,
		TimestampFormat: "15:04:05",
	})
}

func before(c *cli.Context) error {
	initLogFormatter()
	initHelpAndVersionFlags()
	initAppHelpTemplate()

	// Log level
	if logLevel, err := log.ParseLevel(c.String(LogLevelKey)); err != nil {
		log.Fatal("[BITRISE_CLI] - Failed to parse log level:", err)
	} else {
		log.SetLevel(logLevel)
	}

	// Setup
	err := stepman.CreateStepManDirIfNeeded()
	if err != nil {
		return err
	}

	// Debug mode
	stepman.DebugMode = c.Bool(DebugKey)
	return nil
}

func printVersion(c *cli.Context) {
	fmt.Fprintf(c.App.Writer, "%v\n", c.App.Version)
}

// Run ...
func Run() {
	cli.VersionPrinter = printVersion

	app := cli.NewApp()
	app.Name = path.Base(os.Args[0])
	app.Usage = "Step manager"
	app.Version = "0.9.13"

	app.Author = ""
	app.Email = ""

	app.Before = before

	app.Flags = flags
	app.Commands = commands

	if err := app.Run(os.Args); err != nil {
		log.Error("[STEPMAN] - Stepman finished:", err)
	}
}
<commit_msg>start of v0.9.14<commit_after>package cli

import (
	"fmt"
	"os"
	"path"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/stepman/stepman"
	"github.com/codegangsta/cli"
)

func initLogFormatter() {
	log.SetFormatter(&log.TextFormatter{
		ForceColors:     true,
		FullTimestamp:   true,
		TimestampFormat: "15:04:05",
	})
}

func before(c *cli.Context) error {
	initLogFormatter()
	initHelpAndVersionFlags()
	initAppHelpTemplate()

	// Log level
	if logLevel, err := log.ParseLevel(c.String(LogLevelKey)); err != nil {
		log.Fatal("[BITRISE_CLI] - Failed to parse log level:", err)
	} else {
		log.SetLevel(logLevel)
	}

	// Setup
	err := stepman.CreateStepManDirIfNeeded()
	if err != nil {
		return err
	}

	// Debug mode
	stepman.DebugMode = c.Bool(DebugKey)
	return nil
}

func printVersion(c *cli.Context) {
	fmt.Fprintf(c.App.Writer, "%v\n", c.App.Version)
}

// Run ...
func Run() {
	cli.VersionPrinter = printVersion

	app := cli.NewApp()
	app.Name = path.Base(os.Args[0])
	app.Usage = "Step manager"
	app.Version = "0.9.14"

	app.Author = ""
	app.Email = ""

	app.Before = before

	app.Flags = flags
	app.Commands = commands

	if err := app.Run(os.Args); err != nil {
		log.Error("[STEPMAN] - Stepman finished:", err)
	}
}
<|endoftext|>


<commit_before>package cli

import (
	"os"

	"pkg.re/essentialkaos/ek.v9/fmtc"
	"pkg.re/essentialkaos/ek.v9/fmtutil/table"
	"pkg.re/essentialkaos/ek.v9/knf"
	"pkg.re/essentialkaos/ek.v9/options"
	"pkg.re/essentialkaos/ek.v9/terminal"
	"pkg.re/essentialkaos/ek.v9/usage"

	"github.com/gongled/vgrepo/prefs"
	"github.com/gongled/vgrepo/repository"
	"github.com/gongled/vgrepo/storage"
	"github.com/gongled/vgrepo/index"
)

// ////////////////////////////////////////////////////////////////////////////////// //

const (
	APP  = "vgrepo"
	VER  = "2.0.0"
	DESC = "Simple CLI tool for managing Vagrant repositories"
)

const (
	CMD_ADD    = "add"
	CMD_DELETE = "delete"
	CMD_LIST   = "list"
	CMD_INFO   = "info"
	CMD_RENDER = "render"
	CMD_HELP   = "help"

	CMD_ADD_SHORTCUT    = "a"
	CMD_DELETE_SHORTCUT = "d"
	CMD_LIST_SHORTCUT   = "l"
	CMD_INFO_SHORTCUT   = "i"
	CMD_RENDER_SHORTCUT = "r"
)

const (
	KNF_STORAGE_URL  = "storage:url"
	KNF_STORAGE_PATH = "storage:path"
)

const (
	ARG_NO_COLOR = "nc:no-color"
	ARG_HELP     = "h:help"
	ARG_VER      = "v:version"
)

const (
	ERROR_UNSUPPORTED      = 1
	ERROR_INVALID_SETTINGS = 2
)

const CONFIG_FILE = "/etc/vgrepo/vgrepo.knf"

// ////////////////////////////////////////////////////////////////////////////////// //

var optionsMap = options.Map{
	ARG_NO_COLOR: {Type: options.BOOL},
	ARG_HELP:     {Type: options.BOOL, Alias: "u:usage"},
	ARG_VER:      {Type: options.BOOL, Alias: "ver"},
}

var preferences *prefs.Preferences

// ////////////////////////////////////////////////////////////////////////////////// //

func Init() {
	opts, errs := options.Parse(optionsMap)

	if len(errs) != 0 {
		fmtc.Println("Arguments parsing errors:")

		for _, err := range errs {
			fmtc.Printf("  %s\n", err.Error())
		}

		os.Exit(1)
	}

	if options.GetB(ARG_NO_COLOR) {
		fmtc.DisableColors = true
	}

	if options.GetB(ARG_VER) {
		showAbout()
		return
	}

	if options.GetB(ARG_HELP) || len(opts) == 0 {
		showUsage()
		return
	}

	switch len(opts) {
	case 0:
		showUsage()
		return
	case 1:
		processCommand(opts[0], nil)
	default:
		processCommand(opts[0], opts[1:])
	}
}

// ////////////////////////////////////////////////////////////////////////////////// //

func prepare() {
	err := knf.Global(CONFIG_FILE)

	preferences = prefs.NewPreferences(
		knf.GetS(KNF_STORAGE_PATH),
		knf.GetS(KNF_STORAGE_URL),
	)

	if err != nil {
		terminal.PrintErrorMessage(err.Error())
		os.Exit(ERROR_INVALID_SETTINGS)
	}
}

func processCommand(cmd string, args []string) {
	prepare()

	switch cmd {
	case CMD_ADD, CMD_ADD_SHORTCUT:
		addCommand(args)
	case CMD_DELETE, CMD_DELETE_SHORTCUT:
		deleteCommand(args)
	case CMD_LIST, CMD_LIST_SHORTCUT:
		listCommand()
	case CMD_INFO, CMD_INFO_SHORTCUT:
		infoCommand(args)
	case CMD_RENDER, CMD_RENDER_SHORTCUT:
		renderCommand(args)
	case CMD_HELP:
		showUsage()
	default:
		terminal.PrintErrorMessage("Error: unknown command %s", cmd)
		os.Exit(ERROR_UNSUPPORTED)
	}
}

// ////////////////////////////////////////////////////////////////////////////////// //

func addCommand(args []string) {
	if len(args) < 4 {
		terminal.PrintErrorMessage(
			"Error: unable to handle %v arguments",
			len(args),
		)
		os.Exit(1)
	}

	var (
		src      = args[0]
		name     = args[1]
		version  = args[2]
		provider = args[3]
	)

	r := repository.NewRepository(preferences, name)

	terminal.PrintActionMessage("Importing package")
	err := r.AddPackage(src, repository.NewPackage(name, version, provider))

	if err != nil {
		terminal.PrintActionStatus(1)
		terminal.PrintErrorMessage("Error: %s", err.Error())
		os.Exit(1)
	} else {
		terminal.PrintActionStatus(0)
	}
}

func deleteCommand(args []string) {
	if len(args) < 3 {
		terminal.PrintErrorMessage("Error: name, version and provider must be set")
		os.Exit(1)
	}

	var (
		name     = args[0]
		version  = args[1]
		provider = args[2]
	)

	r := repository.NewRepository(preferences, name)

	terminal.PrintActionMessage("Removing package")
	err := r.RemovePackage(repository.NewPackage(name, version, provider))

	if err != nil {
		terminal.PrintActionStatus(1)
		terminal.PrintErrorMessage("Error: %s", err.Error())
		os.Exit(1)
	} else {
		terminal.PrintActionStatus(0)
	}
}

func listCommand() {
	s := storage.NewStorage(preferences)

	listTableRender(s.Repositories())
}

func infoCommand(args []string) {
	if len(args) < 1 {
		terminal.PrintErrorMessage("Error: name must be set")
		os.Exit(1)
	}

	name := args[0]

	infoTableRender(repository.NewRepository(preferences, name))
}

func renderCommand(args []string) {
	if len(args) < 1 {
		terminal.PrintErrorMessage("Error: template must be set")
		os.Exit(1)
	}

	template := args[0]
	output := "index.html"

	if len(args) >= 2 {
		output = args[1]
	}

	terminal.PrintActionMessage("Rendering template")
	err := index.ExportIndex(storage.NewStorage(preferences), template, output)

	if err != nil {
		terminal.PrintActionStatus(1)
		terminal.PrintErrorMessage("Error: ", err.Error())
		os.Exit(1)
	}

	terminal.PrintActionStatus(0)
}

// ////////////////////////////////////////////////////////////////////////////////// //

func infoTableRender(r *repository.VRepository) {
	t := table.NewTable("Name", "Provider", "Version", "Checksum")
	table.HeaderCapitalize = true

	t.SetAlignments(table.ALIGN_LEFT, table.ALIGN_LEFT, table.ALIGN_RIGHT, table.ALIGN_LEFT)

	for _, v := range r.Versions {
		for _, p := range v.Providers {
			t.Add(r.Name, p.Name, v.Version, p.Checksum)
		}
	}

	if t.HasData() {
		t.Render()
	} else {
		terminal.PrintWarnMessage("Repository does not exist")
	}
}

func listTableRender(repos repository.VRepositoryList) {
	t := table.NewTable("Name", "Latest", "Metadata URL")
	table.HeaderCapitalize = true

	t.SetAlignments(table.ALIGN_LEFT, table.ALIGN_RIGHT, table.ALIGN_LEFT)
	for _, r := range repos {
		if r.HasMeta() {
			t.Add(r.Name, r.LatestVersion().Version, r.MetaURL())
		}
	}

	if t.HasData() {
		t.Render()
	} else {
		terminal.PrintWarnMessage("No repositories yet")
	}
}

// ////////////////////////////////////////////////////////////////////////////////// //

func setUsageCommands(info *usage.Info) {
	info.AddCommand(
		CMD_ADD,
		"Add image to the Vagrant repository",
		"source",
		"name",
		"version",
		"provider",
	)
	info.AddCommand(
		CMD_LIST,
		"Show the list of available images",
	)
	info.AddCommand(
		CMD_DELETE,
		"Delete the image from the repository",
		"name",
		"version",
		"provider",
	)
	info.AddCommand(
		CMD_INFO,
		"Display info of the particular repository",
		"name",
	)
	info.AddCommand(
		CMD_RENDER,
		"Create index by given template file",
		"template",
		"?output",
	)
	info.AddCommand(
		CMD_HELP,
		"Display the current help message",
	)
}

func setUsageOptions(info *usage.Info) {
	info.AddOption(ARG_NO_COLOR, "Disable colors in output")
	info.AddOption(ARG_HELP, "Show this help message")
	info.AddOption(ARG_VER, "Show version")
}

func setUsageExamples(info *usage.Info) {
	info.AddExample(
		"add $HOME/powerbox-1.0.0.box powerbox 1.1.0 virtualbox",
		"Add image to the Vagrant repository",
	)
	info.AddExample(
		"list",
		"Show the list of available repositories",
	)
	info.AddExample(
		"delete powerbox 1.1.0",
		"Remove the image from the repository",
	)
	info.AddExample(
		"info powerbox",
		"Show detailed info about the repository",
	)
	info.AddExample(
		"render /etc/vgrepo/templates/default.tpl index.html",
		"Create index file by given template with output index.html",
	)
}

func showUsage() {
	info := usage.NewInfo(APP)

	setUsageCommands(info)
	setUsageOptions(info)
	setUsageExamples(info)

	info.Render()
}

func showAbout() {
	about := &usage.About{
		App:     APP,
		Version: VER,
		Desc:    DESC,
		Year:    2014,
		Owner:   "Gleb E Goncharov",
		License: "MIT License",
	}

	about.Render()
}

// ////////////////////////////////////////////////////////////////////////////////// //
<commit_msg>Swap arguments for rendering template.<commit_after>package cli

import (
	"os"

	"pkg.re/essentialkaos/ek.v9/fmtc"
	"pkg.re/essentialkaos/ek.v9/fmtutil/table"
	"pkg.re/essentialkaos/ek.v9/knf"
	"pkg.re/essentialkaos/ek.v9/options"
	"pkg.re/essentialkaos/ek.v9/terminal"
	"pkg.re/essentialkaos/ek.v9/usage"

	"github.com/gongled/vgrepo/prefs"
	"github.com/gongled/vgrepo/repository"
	"github.com/gongled/vgrepo/storage"
	"github.com/gongled/vgrepo/index"
)

// ////////////////////////////////////////////////////////////////////////////////// //

const (
	APP  = "vgrepo"
	VER  = "2.0.0"
	DESC = "Simple CLI tool for managing Vagrant repositories"
)

const (
	CMD_ADD    = "add"
	CMD_DELETE = "delete"
	CMD_LIST   = "list"
	CMD_INFO   = "info"
	CMD_RENDER = "render"
	CMD_HELP   = "help"

	CMD_ADD_SHORTCUT    = "a"
	CMD_DELETE_SHORTCUT = "d"
	CMD_LIST_SHORTCUT   = "l"
	CMD_INFO_SHORTCUT   = "i"
	CMD_RENDER_SHORTCUT = "r"
)

const (
	KNF_STORAGE_URL  = "storage:url"
	KNF_STORAGE_PATH = "storage:path"
)

const (
	ARG_NO_COLOR = "nc:no-color"
	ARG_HELP     = "h:help"
	ARG_VER      = "v:version"
)

const (
	ERROR_UNSUPPORTED      = 1
	ERROR_INVALID_SETTINGS = 2
)

const CONFIG_FILE = "/etc/vgrepo/vgrepo.knf"

// ////////////////////////////////////////////////////////////////////////////////// //

var optionsMap = options.Map{
	ARG_NO_COLOR: {Type: options.BOOL},
	ARG_HELP:     {Type: options.BOOL, Alias: "u:usage"},
	ARG_VER:      {Type: options.BOOL, Alias: "ver"},
}

var preferences *prefs.Preferences

// ////////////////////////////////////////////////////////////////////////////////// //

func Init() {
	opts, errs := options.Parse(optionsMap)

	if len(errs) != 0 {
		fmtc.Println("Arguments parsing errors:")

		for _, err := range errs {
			fmtc.Printf("  %s\n", err.Error())
		}

		os.Exit(1)
	}

	if options.GetB(ARG_NO_COLOR) {
		fmtc.DisableColors = true
	}

	if options.GetB(ARG_VER) {
		showAbout()
		return
	}

	if options.GetB(ARG_HELP) || len(opts) == 0 {
		showUsage()
		return
	}

	switch len(opts) {
	case 0:
		showUsage()
		return
	case 1:
		processCommand(opts[0], nil)
	default:
		processCommand(opts[0], opts[1:])
	}
}

// ////////////////////////////////////////////////////////////////////////////////// //

func prepare() {
	err := knf.Global(CONFIG_FILE)

	preferences = prefs.NewPreferences(
		knf.GetS(KNF_STORAGE_PATH),
		knf.GetS(KNF_STORAGE_URL),
	)

	if err != nil {
		terminal.PrintErrorMessage(err.Error())
		os.Exit(ERROR_INVALID_SETTINGS)
	}
}

func processCommand(cmd string, args []string) {
	prepare()

	switch cmd {
	case CMD_ADD, CMD_ADD_SHORTCUT:
		addCommand(args)
	case CMD_DELETE, CMD_DELETE_SHORTCUT:
		deleteCommand(args)
	case CMD_LIST, CMD_LIST_SHORTCUT:
		listCommand()
	case CMD_INFO, CMD_INFO_SHORTCUT:
		infoCommand(args)
	case CMD_RENDER, CMD_RENDER_SHORTCUT:
		renderCommand(args)
	case CMD_HELP:
		showUsage()
	default:
		terminal.PrintErrorMessage("Error: unknown command %s", cmd)
		os.Exit(ERROR_UNSUPPORTED)
	}
}

// ////////////////////////////////////////////////////////////////////////////////// //

func addCommand(args []string) {
	if len(args) < 4 {
		terminal.PrintErrorMessage(
			"Error: unable to handle %v arguments",
			len(args),
		)
		os.Exit(1)
	}

	var (
		src      = args[0]
		name     = args[1]
		version  = args[2]
		provider = args[3]
	)

	r := repository.NewRepository(preferences, name)

	terminal.PrintActionMessage("Importing package")
	err := r.AddPackage(src, repository.NewPackage(name, version, provider))

	if err != nil {
		terminal.PrintActionStatus(1)
		terminal.PrintErrorMessage("Error: %s", err.Error())
		os.Exit(1)
	} else {
		terminal.PrintActionStatus(0)
	}
}

func deleteCommand(args []string) {
	if len(args) < 3 {
		terminal.PrintErrorMessage("Error: name, version and provider must be set")
		os.Exit(1)
	}

	var (
		name     = args[0]
		version  = args[1]
		provider = args[2]
	)

	r := repository.NewRepository(preferences, name)

	terminal.PrintActionMessage("Removing package")
	err := r.RemovePackage(repository.NewPackage(name, version, provider))

	if err != nil {
		terminal.PrintActionStatus(1)
		terminal.PrintErrorMessage("Error: %s", err.Error())
		os.Exit(1)
	} else {
		terminal.PrintActionStatus(0)
	}
}

func listCommand() {
	s := storage.NewStorage(preferences)

	listTableRender(s.Repositories())
}

func infoCommand(args []string) {
	if len(args) < 1 {
		terminal.PrintErrorMessage("Error: name must be set")
		os.Exit(1)
	}

	name := args[0]

	infoTableRender(repository.NewRepository(preferences, name))
}

func renderCommand(args []string) {
	if len(args) < 1 {
		terminal.PrintErrorMessage("Error: template must be set")
		os.Exit(1)
	}

	output := args[0]
	template := "/etc/vgrepo/templates/default.tpl"

	if len(args) >= 2 {
		template = args[1]
	}

	terminal.PrintActionMessage("Rendering template")
	err := index.ExportIndex(storage.NewStorage(preferences), template, output)

	if err != nil {
		terminal.PrintActionStatus(1)
		terminal.PrintErrorMessage("Error: ", err.Error())
		os.Exit(1)
	}

	terminal.PrintActionStatus(0)
}

// ////////////////////////////////////////////////////////////////////////////////// //

func infoTableRender(r *repository.VRepository) {
	t := table.NewTable("Name", "Provider", "Version", "Checksum")
	table.HeaderCapitalize = true

	t.SetAlignments(table.ALIGN_LEFT, table.ALIGN_LEFT, table.ALIGN_RIGHT, table.ALIGN_LEFT)

	for _, v := range r.Versions {
		for _, p := range v.Providers {
			t.Add(r.Name, p.Name, v.Version, p.Checksum)
		}
	}

	if t.HasData() {
		t.Render()
	} else {
		terminal.PrintWarnMessage("Repository does not exist")
	}
}

func listTableRender(repos repository.VRepositoryList) {
	t := table.NewTable("Name", "Latest", "Metadata URL")
	table.HeaderCapitalize = true

	t.SetAlignments(table.ALIGN_LEFT, table.ALIGN_RIGHT, table.ALIGN_LEFT)
	for _, r := range repos {
		if r.HasMeta() {
			t.Add(r.Name, r.LatestVersion().Version, r.MetaURL())
		}
	}

	if t.HasData() {
		t.Render()
	} else {
		terminal.PrintWarnMessage("No repositories yet")
	}
}

// ////////////////////////////////////////////////////////////////////////////////// //

func setUsageCommands(info *usage.Info) {
	info.AddCommand(
		CMD_ADD,
		"Add image to the Vagrant repository",
		"source",
		"name",
		"version",
		"provider",
	)
	info.AddCommand(
		CMD_LIST,
		"Show the list of available images",
	)
	info.AddCommand(
		CMD_DELETE,
		"Delete the image from the repository",
		"name",
		"version",
		"provider",
	)
	info.AddCommand(
		CMD_INFO,
		"Display info of the particular repository",
		"name",
	)
	info.AddCommand(
		CMD_RENDER,
		"Create index by given template file",
		"template",
		"?output",
	)
	info.AddCommand(
		CMD_HELP,
		"Display the current help message",
	)
}

func setUsageOptions(info *usage.Info) {
	info.AddOption(ARG_NO_COLOR, "Disable colors in output")
	info.AddOption(ARG_HELP, "Show this help message")
	info.AddOption(ARG_VER, "Show version")
}

func setUsageExamples(info *usage.Info) {
	info.AddExample(
		"add $HOME/powerbox-1.0.0.box powerbox 1.1.0 virtualbox",
		"Add image to the Vagrant repository",
	)
	info.AddExample(
		"list",
		"Show the list of available repositories",
	)
	info.AddExample(
		"delete powerbox 1.1.0",
		"Remove the image from the repository",
	)
	info.AddExample(
		"info powerbox",
		"Show detailed info about the repository",
	)
	info.AddExample(
		"render index.html /etc/vgrepo/templates/default.tpl",
		"Create index file by given template with output index.html",
	)
}

func showUsage() {
	info := usage.NewInfo(APP)

	setUsageCommands(info)
	setUsageOptions(info)
	setUsageExamples(info)

	info.Render()
}

func showAbout() {
	about := &usage.About{
		App:     APP,
		Version: VER,
		Desc:    DESC,
		Year:    2014,
		Owner:   "Gleb E Goncharov",
		License: "MIT License",
	}

	about.Render()
}

// ////////////////////////////////////////////////////////////////////////////////// //
<|endoftext|>


<commit_before>package cli

import (
	"context"
	"flag"
	"fmt"
	"io"
	"io/ioutil"

	"github.com/elpinal/coco3/config"
	"github.com/elpinal/coco3/eval"
	"github.com/elpinal/coco3/gate"
	"github.com/elpinal/coco3/parser"
)

type CLI struct {
	In  io.Reader
	Out io.Writer
	Err io.Writer

	config.Config

	exitCh chan int
	doneCh chan struct{} // to ensure exiting just after exitCh received
}

func (c *CLI) Run(args []string) int {
	c.exitCh = make(chan int)
	c.doneCh = make(chan struct{})
	f := flag.NewFlagSet("coco3", flag.ContinueOnError)
	f.SetOutput(c.Err)
	f.Usage = func() {
		c.Err.Write([]byte("coco3 is a shell.\n"))
		c.Err.Write([]byte("Usage:\n"))
		f.PrintDefaults()
	}

	flagC := f.String("c", "", "take first argument as a command to execute")
	if err := f.Parse(args); err != nil {
		return 2
	}

	if len(c.Config.StartUpCommand) > 0 {
		done := make(chan struct{})
		go func() {
			if err := c.execute(c.Config.StartUpCommand); err != nil {
				fmt.Fprintln(c.Err, err)
				c.exitCh <- 1
			}
			close(done)
		}()
		select {
		case code := <-c.exitCh:
			return code
		case <-done:
		}
	}

	if *flagC != "" {
		go func() {
			if err := c.execute([]byte(*flagC)); err != nil {
				fmt.Fprintln(c.Err, err)
				c.exitCh <- 1
			}
			c.exitCh <- 0
		}()
		return <-c.exitCh
	}

	if len(f.Args()) > 0 {
		defer func() {
			close(c.doneCh)
		}()
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		go c.runFiles(ctx, f.Args())
		return <-c.exitCh
	}

	conf := &c.Config
	conf.Init()
	g := gate.New(conf, c.In, c.Out, c.Err)
	go func() {
		for {
			if err := c.interact(g); err != nil {
				fmt.Fprintln(c.Err, err)
				g.Clear()
			}
		}
	}()
	return <-c.exitCh
}

func (c *CLI) interact(g gate.Gate) error {
	for {
		old, err := enterRowMode()
		if err != nil {
			return err
		}
		r, err := g.Read()
		if err != nil {
			return err
		}
		c.Out.Write([]byte{'\n'})
		if err := exitRowMode(old); err != nil {
			return err
		}
		if err := c.execute([]byte(string(r))); err != nil {
			return err
		}
		g.Clear()
	}
}

func (c *CLI) execute(b []byte) error {
	f, err := parser.ParseSrc(b)
	if err != nil {
		return err
	}
	e := eval.New(c.In, c.Out, c.Err)
	err = e.Eval(f.Lines)
	select {
	case code := <-e.ExitCh:
		c.exitCh <- code
		<-c.doneCh
	default:
	}
	return err
}

func (c *CLI) runFiles(ctx context.Context, files []string) {
	for _, file := range files {
		b, err := ioutil.ReadFile(file)
		if err != nil {
			fmt.Fprintln(c.Err, err)
			c.exitCh <- 1
			return
		}
		if err := c.execute(b); err != nil {
			fmt.Fprintln(c.Err, err)
			c.exitCh <- 1
			return
		}
		select {
		case <-ctx.Done():
			return
		default:
		}
	}
	c.exitCh <- 0
}
<commit_msg>Clean up code<commit_after>package cli

import (
	"context"
	"flag"
	"fmt"
	"io"
	"io/ioutil"

	"github.com/elpinal/coco3/config"
	"github.com/elpinal/coco3/eval"
	"github.com/elpinal/coco3/gate"
	"github.com/elpinal/coco3/parser"
)

type CLI struct {
	In  io.Reader
	Out io.Writer
	Err io.Writer

	config.Config

	exitCh chan int
	doneCh chan struct{} // to ensure exiting just after exitCh received
}

func (c *CLI) Run(args []string) int {
	c.exitCh = make(chan int)
	c.doneCh = make(chan struct{})

	f := flag.NewFlagSet("coco3", flag.ContinueOnError)
	f.SetOutput(c.Err)
	f.Usage = func() {
		c.Err.Write([]byte("coco3 is a shell.\n"))
		c.Err.Write([]byte("Usage:\n"))
		f.PrintDefaults()
	}

	flagC := f.String("c", "", "take first argument as a command to execute")
	if err := f.Parse(args); err != nil {
		return 2
	}

	if len(c.Config.StartUpCommand) > 0 {
		done := make(chan struct{})
		go func() {
			if err := c.execute(c.Config.StartUpCommand); err != nil {
				fmt.Fprintln(c.Err, err)
				c.exitCh <- 1
			}
			close(done)
		}()
		select {
		case code := <-c.exitCh:
			return code
		case <-done:
		}
	}

	if *flagC != "" {
		go func() {
			if err := c.execute([]byte(*flagC)); err != nil {
				fmt.Fprintln(c.Err, err)
				c.exitCh <- 1
			}
			c.exitCh <- 0
		}()
		return <-c.exitCh
	}

	if len(f.Args()) > 0 {
		defer func() {
			close(c.doneCh)
		}()
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		go c.runFiles(ctx, f.Args())
		return <-c.exitCh
	}

	conf := &c.Config
	conf.Init()
	g := gate.New(conf, c.In, c.Out, c.Err)
	go func() {
		for {
			if err := c.interact(g); err != nil {
				fmt.Fprintln(c.Err, err)
				g.Clear()
			}
		}
	}()
	return <-c.exitCh
}

func (c *CLI) interact(g gate.Gate) error {
	for {
		old, err := enterRowMode()
		if err != nil {
			return err
		}
		r, err := g.Read()
		if err != nil {
			return err
		}
		c.Out.Write([]byte{'\n'})
		if err := exitRowMode(old); err != nil {
			return err
		}
		if err := c.execute([]byte(string(r))); err != nil {
			return err
		}
		g.Clear()
	}
}

func (c *CLI) execute(b []byte) error {
	f, err := parser.ParseSrc(b)
	if err != nil {
		return err
	}
	e := eval.New(c.In, c.Out, c.Err)
	err = e.Eval(f.Lines)
	select {
	case code := <-e.ExitCh:
		c.exitCh <- code
		<-c.doneCh
	default:
	}
	return err
}

func (c *CLI) runFiles(ctx context.Context, files []string) {
	for _, file := range files {
		b, err := ioutil.ReadFile(file)
		if err != nil {
			fmt.Fprintln(c.Err, err)
			c.exitCh <- 1
			return
		}
		if err := c.execute(b); err != nil {
			fmt.Fprintln(c.Err, err)
			c.exitCh <- 1
			return
		}
		select {
		case <-ctx.Done():
			return
		default:
		}
	}
	c.exitCh <- 0
}
<|endoftext|>


<commit_before>package main

import (
    "hagerbot.com/vox"
    "time"
)

func main() {
    err := vox.Init("", 44100, 2, 0)
    if err != nil {
        panic(err)
    }
    defer vox.Quit()

    println(vox.Version)

    song, err := vox.Open("../data/songs/test.sunvox")
    if err != nil {
        panic(err)
    }
    defer song.Close()

    println(song.Name())

    song.SetVolume(256)

    song.Mod[7].Trigger(0, 64, 128, 0, 0)
    time.Sleep(1 * time.Second)
    song.Mod[7].Trigger(0, 64, 128, 0, 0)
    time.Sleep(1 * time.Second)

    song.Play()

    for !song.Finished() {
    }
}
<commit_msg>Update demo to use new path<commit_after>package main

import (
	"github.com/ajhager/vox"
	"time"
)

func main() {
	err := vox.Init("", 44100, 2, 0)
	if err != nil {
		panic(err)
	}
	defer vox.Quit()

	println(vox.Version)

	song, err := vox.Open("../data/songs/test.sunvox")
	if err != nil {
		panic(err)
	}
	defer song.Close()

	println(song.Name())

	song.SetVolume(256)

	song.Mod[7].Trigger(0, 64, 128, 0, 0)
	time.Sleep(1 * time.Second)
	song.Mod[7].Trigger(0, 64, 128, 0, 0)
	time.Sleep(1 * time.Second)

	song.Play()

	for !song.Finished() {
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or 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 starter

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"
	"syscall"

	"github.com/coreos/etcd-starter/Godeps/_workspace/src/github.com/coreos/etcd/client"
	"github.com/coreos/etcd-starter/Godeps/_workspace/src/github.com/coreos/etcd/etcdmain"
	"github.com/coreos/etcd-starter/Godeps/_workspace/src/github.com/coreos/etcd/migrate"
	"github.com/coreos/etcd-starter/Godeps/_workspace/src/github.com/coreos/etcd/pkg/flags"
	"github.com/coreos/etcd-starter/Godeps/_workspace/src/github.com/coreos/etcd/pkg/osutil"
	"github.com/coreos/etcd-starter/Godeps/_workspace/src/github.com/coreos/etcd/wal"
	"github.com/coreos/etcd-starter/Godeps/_workspace/src/golang.org/x/net/context"
)

type version string

const (
	internalV1      version = "1"
	internalV2      version = "2"
	internalV2Proxy version = "2.proxy"
	internalUnknown version = "unknown"

	v0_4      version = "v0.4"
	v2_0      version = "v2.0"
	v2_0Proxy version = "v2.0 proxy"
	empty     version = "empty"
	unknown   version = "unknown"
)

var (
	v2SpecialFlags = []string{
		"initial-cluster",
		"listen-peer-urls",
		"listen-client-urls",
		"proxy",
	}
)

func StartDesiredVersion(binDir string, args []string) {
	fs, err := parseConfig(args)
	if err != nil {
		return
	}

	ver := checkInternalVersion(fs)
	log.Printf("starter: starting etcd version %s", ver)
	var p string
	switch ver {
	case internalV1:
		p = path.Join(binDir, "1", "etcd")
	case internalV2:
		p = path.Join(binDir, "2", "etcd")
	case internalV2Proxy:
		p = path.Join(binDir, "2", "etcd")
		if _, err := os.Stat(standbyInfo4(fs.Lookup("data-dir").Value.String())); err != nil {
			log.Printf("starter: detected standby_info file. Adding --proxy=on flag to ensure node runs in v2.0 proxy mode.")
			log.Printf("starter: before removing v0.4 data, --proxy=on flag MUST be added.")
		}
		// append proxy flag to args to trigger proxy mode
		args = append(args, "-proxy=on")
	default:
		log.Panicf("starter: unhandled start version")
	}

	log.Printf("starter: starting with %s %v with env %v", p, args, syscall.Environ())
	err = syscall.Exec(p, append([]string{p}, args...), syscall.Environ())
	if err != nil {
		log.Fatalf("starter: failed to execute %s: %v", p, err)
	}
}

func checkInternalVersion(fs *flag.FlagSet) version {
	// If it uses 2.0 env var explicitly, start 2.0
	for _, name := range v2SpecialFlags {
		if fs.Lookup(name).Value.String() != "" {
			return internalV2
		}
	}

	dataDir := fs.Lookup("data-dir").Value.String()
	if dataDir == "" {
		log.Fatalf("starter: please set --data-dir or ETCD_DATA_DIR for etcd")
	}
	// check the data directory
	dataver, err := wal.DetectVersion(dataDir)
	if err != nil {
		log.Fatalf("starter: failed to detect etcd version in %v: %v", dataDir, err)
	}
	log.Printf("starter: detected etcd version %s in %s", dataver, dataDir)
	switch dataver {
	case wal.WALv2_0:
		return internalV2
	case wal.WALv2_0_1:
		return internalV2
	case wal.WALv2_0Proxy:
		return internalV2Proxy
	case wal.WALv0_4:
		standbyInfo, err := migrate.DecodeStandbyInfo4FromFile(standbyInfo4(dataDir))
		if err != nil && !os.IsNotExist(err) {
			log.Fatalf("starter: failed to decode standbyInfo in %v: %v", dataDir, err)
		}
		inStandbyMode := standbyInfo != nil && standbyInfo.Running
		if inStandbyMode {
			ver, err := checkInternalVersionByClientURLs(standbyInfo.ClientURLs(), clientTLSInfo(fs))
			if err != nil {
				log.Printf("starter: failed to check start version through peers: %v", err)
				return internalV1
			}
			if ver == internalV2 {
				osutil.Unsetenv("ETCD_DISCOVERY")
				os.Args = append(os.Args, "-initial-cluster", standbyInfo.InitialCluster())
				return internalV2Proxy
			}
			return ver
		}
		ver, err := checkInternalVersionByDataDir4(dataDir)
		if err != nil {
			log.Fatalf("starter: failed to check start version in %v: %v", dataDir, err)
		}
		return ver
	case wal.WALNotExist:
		discovery := fs.Lookup("discovery").Value.String()
		dpeers, err := getPeersFromDiscoveryURL(discovery)
		if err != nil {
			log.Printf("starter: failed to get peers from discovery %s: %v", discovery, err)
		}
		peerStr := fs.Lookup("peers").Value.String()
		ppeers := getPeersFromPeersFlag(peerStr, peerTLSInfo(fs))

		urls := getClientURLsByPeerURLs(append(dpeers, ppeers...), peerTLSInfo(fs))
		ver, err := checkInternalVersionByClientURLs(urls, clientTLSInfo(fs))
		if err != nil {
			log.Printf("starter: failed to check start version through peers: %v", err)
			return internalV2
		}
		return ver
	case wal.WALUnknown:
		log.Printf("starter: unrecognized contents in data directory %s", dataDir)
		return internalV2
	}
	// never reach here
	log.Panicf("starter: unhandled etcd version in %v", dataDir)
	return internalUnknown
}

func checkInternalVersionByDataDir4(dataDir string) (version, error) {
	// check v0.4 snapshot
	snap4, err := migrate.DecodeLatestSnapshot4FromDir(snapDir4(dataDir))
	if err != nil {
		return internalUnknown, err
	}
	if snap4 != nil {
		st := &migrate.Store4{}
		if err := json.Unmarshal(snap4.State, st); err != nil {
			return internalUnknown, err
		}
		dir := st.Root.Children["_etcd"]
		n, ok := dir.Children["next-internal-version"]
		if ok && n.Value == "2" {
			return internalV2, nil
		}
	}

	// check v0.4 log
	ents4, err := migrate.DecodeLog4FromFile(logFile4(dataDir))
	if err != nil {
		return internalUnknown, err
	}
	for _, e := range ents4 {
		cmd, err := migrate.NewCommand4(e.GetCommandName(), e.GetCommand(), nil)
		if err != nil {
			return internalUnknown, err
		}
		setcmd, ok := cmd.(*migrate.SetCommand)
		if !ok {
			continue
		}
		if setcmd.Key == "/_etcd/next-internal-version" && setcmd.Value == "2" {
			return internalV2, nil
		}
	}
	return internalV1, nil
}

func getClientURLsByPeerURLs(peers []string, tls *TLSInfo) []string {
	c, err := newDefaultClient(tls)
	if err != nil {
		log.Printf("starter: new client error: %v", err)
		return nil
	}
	var urls []string
	for _, u := range peers {
		resp, err := c.Get(u + "/etcdURL")
		if err != nil {
			log.Printf("starter: failed to get /etcdURL from %s", u)
			continue
		}
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Printf("starter: failed to read body from %s", u)
			continue
		}
		urls = append(urls, string(b))
	}
	return urls
}

func checkInternalVersionByClientURLs(urls []string, tls *TLSInfo) (version, error) {
	c, err := newDefaultClient(tls)
	if err != nil {
		return internalUnknown, err
	}
	for _, u := range urls {
		resp, err := c.Get(u + "/version")
		if err != nil {
			log.Printf("starter: failed to get /version from %s", u)
			continue
		}
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Printf("starter: failed to read body from %s", u)
			continue
		}

		var m map[string]string
		err = json.Unmarshal(b, &m)
		if err != nil {
			log.Printf("starter: failed to unmarshal body %s from %s", b, u)
			continue
		}
		switch m["internalVersion"] {
		case "1":
			return internalV1, nil
		case "2":
			return internalV2, nil
		default:
			log.Printf("starter: unrecognized internal version %s from %s", m["internalVersion"], u)
		}
	}
	return internalUnknown, fmt.Errorf("failed to get version from urls %v", urls)
}

func getPeersFromDiscoveryURL(discoverURL string) ([]string, error) {
	if discoverURL == "" {
		return nil, nil
	}

	u, err := url.Parse(discoverURL)
	if err != nil {
		return nil, err
	}
	token := u.Path
	u.Path = ""
	cfg := client.Config{
		Transport: &http.Transport{},
		Endpoints: []string{u.String()},
	}
	c, err := client.New(cfg)
	if err != nil {
		return nil, err
	}
	dc := client.NewKeysAPIWithPrefix(c, "")

	ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
	resp, err := dc.Get(ctx, token, nil)
	cancel()
	if err != nil {
		return nil, err
	}
	peers := make([]string, 0)
	// append non-config keys to peers
	for _, n := range resp.Node.Nodes {
		if g := path.Base(n.Key); g == "_config" || g == "_state" {
			continue
		}
		peers = append(peers, n.Value)
	}
	return peers, nil
}

func getPeersFromPeersFlag(str string, tls *TLSInfo) []string {
	peers := trimSplit(str, ",")
	for i, p := range peers {
		peers[i] = tls.Scheme() + "://" + p
	}
	return peers
}

func newDefaultClient(tls *TLSInfo) (*http.Client, error) {
	tr := &http.Transport{}
	if tls.Scheme() == "https" {
		tlsConfig, err := tls.ClientConfig()
		if err != nil {
			return nil, err
		}
		tr.TLSClientConfig = tlsConfig
	}
	return &http.Client{Transport: tr}, nil
}

type value struct {
	isBoolFlag bool
	s          string
}

func (v *value) String() string { return v.s }

func (v *value) Set(s string) error {
	v.s = s
	return nil
}

func (v *value) IsBoolFlag() bool { return v.isBoolFlag }

type boolFlag interface {
	flag.Value
	IsBoolFlag() bool
}

// parseConfig parses out the input config from cmdline arguments and
// environment variables.
func parseConfig(args []string) (*flag.FlagSet, error) {
	fs := flag.NewFlagSet("full flagset", flag.ContinueOnError)
	etcdmain.NewConfig().VisitAll(func(f *flag.Flag) {
		_, isBoolFlag := f.Value.(boolFlag)
		fs.Var(&value{isBoolFlag: isBoolFlag}, f.Name, "")
	})
	if err := fs.Parse(args); err != nil {
		return nil, err
	}
	if err := flags.SetFlagsFromEnv(fs); err != nil {
		return nil, err
	}
	return fs, nil
}

func clientTLSInfo(fs *flag.FlagSet) *TLSInfo {
	return &TLSInfo{
		CAFile:   fs.Lookup("ca-file").Value.String(),
		CertFile: fs.Lookup("cert-file").Value.String(),
		KeyFile:  fs.Lookup("key-file").Value.String(),
	}
}

func peerTLSInfo(fs *flag.FlagSet) *TLSInfo {
	return &TLSInfo{
		CAFile:   fs.Lookup("peer-ca-file").Value.String(),
		CertFile: fs.Lookup("peer-cert-file").Value.String(),
		KeyFile:  fs.Lookup("peer-key-file").Value.String(),
	}
}

func snapDir4(dataDir string) string {
	return path.Join(dataDir, "snapshot")
}

func logFile4(dataDir string) string {
	return path.Join(dataDir, "log")
}

func standbyInfo4(dataDir string) string {
	return path.Join(dataDir, "standby_info")
}

func trimSplit(s, sep string) []string {
	trimmed := strings.Split(s, sep)
	for i := range trimmed {
		trimmed[i] = strings.TrimSpace(trimmed[i])
	}
	return trimmed
}
<commit_msg>not set data-dir is v2<commit_after>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or 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 starter

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"
	"syscall"

	"github.com/coreos/etcd-starter/Godeps/_workspace/src/github.com/coreos/etcd/client"
	"github.com/coreos/etcd-starter/Godeps/_workspace/src/github.com/coreos/etcd/etcdmain"
	"github.com/coreos/etcd-starter/Godeps/_workspace/src/github.com/coreos/etcd/migrate"
	"github.com/coreos/etcd-starter/Godeps/_workspace/src/github.com/coreos/etcd/pkg/flags"
	"github.com/coreos/etcd-starter/Godeps/_workspace/src/github.com/coreos/etcd/pkg/osutil"
	"github.com/coreos/etcd-starter/Godeps/_workspace/src/github.com/coreos/etcd/wal"
	"github.com/coreos/etcd-starter/Godeps/_workspace/src/golang.org/x/net/context"
)

type version string

const (
	internalV1      version = "1"
	internalV2      version = "2"
	internalV2Proxy version = "2.proxy"
	internalUnknown version = "unknown"

	v0_4      version = "v0.4"
	v2_0      version = "v2.0"
	v2_0Proxy version = "v2.0 proxy"
	empty     version = "empty"
	unknown   version = "unknown"
)

var (
	v2SpecialFlags = []string{
		"initial-cluster",
		"listen-peer-urls",
		"listen-client-urls",
		"proxy",
	}
)

func StartDesiredVersion(binDir string, args []string) {
	fs, err := parseConfig(args)
	if err != nil {
		return
	}

	ver := checkInternalVersion(fs)
	log.Printf("starter: starting etcd version %s", ver)
	var p string
	switch ver {
	case internalV1:
		p = path.Join(binDir, "1", "etcd")
	case internalV2:
		p = path.Join(binDir, "2", "etcd")
	case internalV2Proxy:
		p = path.Join(binDir, "2", "etcd")
		if _, err := os.Stat(standbyInfo4(fs.Lookup("data-dir").Value.String())); err != nil {
			log.Printf("starter: detected standby_info file. Adding --proxy=on flag to ensure node runs in v2.0 proxy mode.")
			log.Printf("starter: before removing v0.4 data, --proxy=on flag MUST be added.")
		}
		// append proxy flag to args to trigger proxy mode
		args = append(args, "-proxy=on")
	default:
		log.Panicf("starter: unhandled start version")
	}

	log.Printf("starter: starting with %s %v with env %v", p, args, syscall.Environ())
	err = syscall.Exec(p, append([]string{p}, args...), syscall.Environ())
	if err != nil {
		log.Fatalf("starter: failed to execute %s: %v", p, err)
	}
}

func checkInternalVersion(fs *flag.FlagSet) version {
	// If it uses 2.0 env var explicitly, start 2.0
	for _, name := range v2SpecialFlags {
		if fs.Lookup(name).Value.String() != "" {
			return internalV2
		}
	}

	dataDir := fs.Lookup("data-dir").Value.String()
	if dataDir == "" {
		log.Printf("starter: data-dir is not set")
		return internalV2
	}
	// check the data directory
	dataver, err := wal.DetectVersion(dataDir)
	if err != nil {
		log.Fatalf("starter: failed to detect etcd version in %v: %v", dataDir, err)
	}
	log.Printf("starter: detected etcd version %s in %s", dataver, dataDir)
	switch dataver {
	case wal.WALv2_0:
		return internalV2
	case wal.WALv2_0_1:
		return internalV2
	case wal.WALv2_0Proxy:
		return internalV2Proxy
	case wal.WALv0_4:
		standbyInfo, err := migrate.DecodeStandbyInfo4FromFile(standbyInfo4(dataDir))
		if err != nil && !os.IsNotExist(err) {
			log.Fatalf("starter: failed to decode standbyInfo in %v: %v", dataDir, err)
		}
		inStandbyMode := standbyInfo != nil && standbyInfo.Running
		if inStandbyMode {
			ver, err := checkInternalVersionByClientURLs(standbyInfo.ClientURLs(), clientTLSInfo(fs))
			if err != nil {
				log.Printf("starter: failed to check start version through peers: %v", err)
				return internalV1
			}
			if ver == internalV2 {
				osutil.Unsetenv("ETCD_DISCOVERY")
				os.Args = append(os.Args, "-initial-cluster", standbyInfo.InitialCluster())
				return internalV2Proxy
			}
			return ver
		}
		ver, err := checkInternalVersionByDataDir4(dataDir)
		if err != nil {
			log.Fatalf("starter: failed to check start version in %v: %v", dataDir, err)
		}
		return ver
	case wal.WALNotExist:
		discovery := fs.Lookup("discovery").Value.String()
		dpeers, err := getPeersFromDiscoveryURL(discovery)
		if err != nil {
			log.Printf("starter: failed to get peers from discovery %s: %v", discovery, err)
		}
		peerStr := fs.Lookup("peers").Value.String()
		ppeers := getPeersFromPeersFlag(peerStr, peerTLSInfo(fs))

		urls := getClientURLsByPeerURLs(append(dpeers, ppeers...), peerTLSInfo(fs))
		ver, err := checkInternalVersionByClientURLs(urls, clientTLSInfo(fs))
		if err != nil {
			log.Printf("starter: failed to check start version through peers: %v", err)
			return internalV2
		}
		return ver
	case wal.WALUnknown:
		log.Printf("starter: unrecognized contents in data directory %s", dataDir)
		return internalV2
	}
	// never reach here
	log.Panicf("starter: unhandled etcd version in %v", dataDir)
	return internalUnknown
}

func checkInternalVersionByDataDir4(dataDir string) (version, error) {
	// check v0.4 snapshot
	snap4, err := migrate.DecodeLatestSnapshot4FromDir(snapDir4(dataDir))
	if err != nil {
		return internalUnknown, err
	}
	if snap4 != nil {
		st := &migrate.Store4{}
		if err := json.Unmarshal(snap4.State, st); err != nil {
			return internalUnknown, err
		}
		dir := st.Root.Children["_etcd"]
		n, ok := dir.Children["next-internal-version"]
		if ok && n.Value == "2" {
			return internalV2, nil
		}
	}

	// check v0.4 log
	ents4, err := migrate.DecodeLog4FromFile(logFile4(dataDir))
	if err != nil {
		return internalUnknown, err
	}
	for _, e := range ents4 {
		cmd, err := migrate.NewCommand4(e.GetCommandName(), e.GetCommand(), nil)
		if err != nil {
			return internalUnknown, err
		}
		setcmd, ok := cmd.(*migrate.SetCommand)
		if !ok {
			continue
		}
		if setcmd.Key == "/_etcd/next-internal-version" && setcmd.Value == "2" {
			return internalV2, nil
		}
	}
	return internalV1, nil
}

func getClientURLsByPeerURLs(peers []string, tls *TLSInfo) []string {
	c, err := newDefaultClient(tls)
	if err != nil {
		log.Printf("starter: new client error: %v", err)
		return nil
	}
	var urls []string
	for _, u := range peers {
		resp, err := c.Get(u + "/etcdURL")
		if err != nil {
			log.Printf("starter: failed to get /etcdURL from %s", u)
			continue
		}
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Printf("starter: failed to read body from %s", u)
			continue
		}
		urls = append(urls, string(b))
	}
	return urls
}

func checkInternalVersionByClientURLs(urls []string, tls *TLSInfo) (version, error) {
	c, err := newDefaultClient(tls)
	if err != nil {
		return internalUnknown, err
	}
	for _, u := range urls {
		resp, err := c.Get(u + "/version")
		if err != nil {
			log.Printf("starter: failed to get /version from %s", u)
			continue
		}
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Printf("starter: failed to read body from %s", u)
			continue
		}

		var m map[string]string
		err = json.Unmarshal(b, &m)
		if err != nil {
			log.Printf("starter: failed to unmarshal body %s from %s", b, u)
			continue
		}
		switch m["internalVersion"] {
		case "1":
			return internalV1, nil
		case "2":
			return internalV2, nil
		default:
			log.Printf("starter: unrecognized internal version %s from %s", m["internalVersion"], u)
		}
	}
	return internalUnknown, fmt.Errorf("failed to get version from urls %v", urls)
}

func getPeersFromDiscoveryURL(discoverURL string) ([]string, error) {
	if discoverURL == "" {
		return nil, nil
	}

	u, err := url.Parse(discoverURL)
	if err != nil {
		return nil, err
	}
	token := u.Path
	u.Path = ""
	cfg := client.Config{
		Transport: &http.Transport{},
		Endpoints: []string{u.String()},
	}
	c, err := client.New(cfg)
	if err != nil {
		return nil, err
	}
	dc := client.NewKeysAPIWithPrefix(c, "")

	ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
	resp, err := dc.Get(ctx, token, nil)
	cancel()
	if err != nil {
		return nil, err
	}
	peers := make([]string, 0)
	// append non-config keys to peers
	for _, n := range resp.Node.Nodes {
		if g := path.Base(n.Key); g == "_config" || g == "_state" {
			continue
		}
		peers = append(peers, n.Value)
	}
	return peers, nil
}

func getPeersFromPeersFlag(str string, tls *TLSInfo) []string {
	peers := trimSplit(str, ",")
	for i, p := range peers {
		peers[i] = tls.Scheme() + "://" + p
	}
	return peers
}

func newDefaultClient(tls *TLSInfo) (*http.Client, error) {
	tr := &http.Transport{}
	if tls.Scheme() == "https" {
		tlsConfig, err := tls.ClientConfig()
		if err != nil {
			return nil, err
		}
		tr.TLSClientConfig = tlsConfig
	}
	return &http.Client{Transport: tr}, nil
}

type value struct {
	isBoolFlag bool
	s          string
}

func (v *value) String() string { return v.s }

func (v *value) Set(s string) error {
	v.s = s
	return nil
}

func (v *value) IsBoolFlag() bool { return v.isBoolFlag }

type boolFlag interface {
	flag.Value
	IsBoolFlag() bool
}

// parseConfig parses out the input config from cmdline arguments and
// environment variables.
func parseConfig(args []string) (*flag.FlagSet, error) {
	fs := flag.NewFlagSet("full flagset", flag.ContinueOnError)
	etcdmain.NewConfig().VisitAll(func(f *flag.Flag) {
		_, isBoolFlag := f.Value.(boolFlag)
		fs.Var(&value{isBoolFlag: isBoolFlag}, f.Name, "")
	})
	if err := fs.Parse(args); err != nil {
		return nil, err
	}
	if err := flags.SetFlagsFromEnv(fs); err != nil {
		return nil, err
	}
	return fs, nil
}

func clientTLSInfo(fs *flag.FlagSet) *TLSInfo {
	return &TLSInfo{
		CAFile:   fs.Lookup("ca-file").Value.String(),
		CertFile: fs.Lookup("cert-file").Value.String(),
		KeyFile:  fs.Lookup("key-file").Value.String(),
	}
}

func peerTLSInfo(fs *flag.FlagSet) *TLSInfo {
	return &TLSInfo{
		CAFile:   fs.Lookup("peer-ca-file").Value.String(),
		CertFile: fs.Lookup("peer-cert-file").Value.String(),
		KeyFile:  fs.Lookup("peer-key-file").Value.String(),
	}
}

func snapDir4(dataDir string) string {
	return path.Join(dataDir, "snapshot")
}

func logFile4(dataDir string) string {
	return path.Join(dataDir, "log")
}

func standbyInfo4(dataDir string) string {
	return path.Join(dataDir, "standby_info")
}

func trimSplit(s, sep string) []string {
	trimmed := strings.Split(s, sep)
	for i := range trimmed {
		trimmed[i] = strings.TrimSpace(trimmed[i])
	}
	return trimmed
}
<|endoftext|>


<commit_before>// SMTP server package.
package smtpd

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"net"
	"os"
	"regexp"
	"strings"
	"time"
)

var (
	rcptToRE   = regexp.MustCompile(`[Tt][Oo]:<(.+)>`)
	mailFromRE = regexp.MustCompile(`[Ff][Rr][Oo][Mm]:<(.*)>`) // Delivery Status Notifications are sent with "MAIL FROM:<>"
)

// Definition of handler function.
type Handler func(remoteAddr net.Addr, from string, to []string, data []byte)

// ListenAndServe listens on the TCP network address addr
// and then calls Serve with handler to handle requests
// on incoming connections.
func ListenAndServe(addr string, handler Handler, appname string, hostname string) error {
	srv := &Server{Addr: addr, Handler: handler, Appname: appname, Hostname: hostname}
	return srv.ListenAndServe()
}

// Server is an SMTP server.
type Server struct {
	Addr     string // TCP address to listen on, defaults to ":25" (all addresses, port 25) if empty
	Handler  Handler
	Appname  string
	Hostname string
}

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.  If
// srv.Addr is blank, ":25" is used.
func (srv *Server) ListenAndServe() error {
	if srv.Addr == "" {
		srv.Addr = ":25"
	}
	if srv.Appname == "" {
		srv.Appname = "smtpd"
	}
	if srv.Hostname == "" {
		srv.Hostname, _ = os.Hostname()
	}
	ln, err := net.Listen("tcp", srv.Addr)
	if err != nil {
		return err
	}
	return srv.Serve(ln)
}

func (srv *Server) Serve(ln net.Listener) error {
	defer ln.Close()
	for {
		conn, err := ln.Accept()
		if err != nil {
			if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
				log.Printf("%s: Accept error: %v", srv.Appname, err)
				continue
			}
			return err
		}
		session, err := srv.newSession(conn)
		if err != nil {
			continue
		}
		go session.serve()
	}
}

type session struct {
	srv        *Server
	conn       net.Conn
	br         *bufio.Reader
	bw         *bufio.Writer
	remoteIP   string // Remote IP address
	remoteHost string // Remote hostname according to reverse DNS lookup
	remoteName string // Remote hostname as supplied with EHLO
}

// Create new session from connection.
func (srv *Server) newSession(conn net.Conn) (s *session, err error) {
	s = &session{
		srv:  srv,
		conn: conn,
		br:   bufio.NewReader(conn),
		bw:   bufio.NewWriter(conn),
	}
	return
}

// Function called to handle connection requests.
func (s *session) serve() {
	defer s.conn.Close()
	var from string
	var to []string
	var buffer bytes.Buffer

	// Get remote end info for the Received header.
	s.remoteIP, _, _ = net.SplitHostPort(s.conn.RemoteAddr().String())
	names, err := net.LookupAddr(s.remoteIP)
	if err == nil && len(names) > 0 {
		s.remoteHost = names[0]
	} else {
		s.remoteHost = "unknown"
	}

	// Send banner.
	s.writef("220 %s %s SMTP Service ready", s.srv.Hostname, s.srv.Appname)

loop:
	for {
		// Attempt to read a line from the socket.
		// On error, assume the client has gone away i.e. return from serve().
		line, err := s.readLine()
		if err != nil {
			break
		}
		verb, args := s.parseLine(line)

		switch verb {
		case "EHLO", "HELO":
			s.remoteName = args
			s.writef("250 %s greets %s", s.srv.Hostname, s.remoteName)

			// RFC 2821 section 4.1.4 specifies that EHLO has the same effect as RSET.
			from = ""
			to = nil
			buffer.Reset()
		case "MAIL":
			match := mailFromRE.FindStringSubmatch(args)
			if match == nil {
				s.writef("501 Syntax error in parameters or arguments (invalid FROM parameter)")
			} else {
				from = match[1]
				s.writef("250 Ok")
			}
			to = nil
			buffer.Reset()
		case "RCPT":
			if from == "" {
				s.writef("503 Bad sequence of commands (MAIL required before RCPT)")
				break
			}

			match := rcptToRE.FindStringSubmatch(args)
			if match == nil {
				s.writef("501 Syntax error in parameters or arguments (invalid TO parameter)")
			} else {
				// RFC 5321 specifies 100 minimum recipients
				if len(to) == 100 {
					s.writef("452 Too many recipients")
				} else {
					to = append(to, match[1])
					s.writef("250 Ok")
				}
			}
		case "DATA":
			if from == "" || to == nil {
				s.writef("503 Bad sequence of commands (MAIL & RCPT required before DATA)")
				break
			}

			s.writef("354 Start mail input; end with <CR><LF>.<CR><LF>")

			// Attempt to read message body from the socket.
			// On error, assume the client has gone away i.e. return from serve().
			data, err := s.readData()
			if err != nil {
				break loop
			}

			// Create Received header & write message body into buffer.
			buffer.Reset()
			buffer.Write(s.makeHeaders(to))
			buffer.Write(data)
			s.writef("250 Ok: queued")

			// Pass mail on to handler.
			if s.srv.Handler != nil {
				go s.srv.Handler(s.conn.RemoteAddr(), from, to, buffer.Bytes())
			}

			// Reset for next mail.
			from = ""
			to = nil
			buffer.Reset()
		case "QUIT":
			s.writef("221 %s %s SMTP Service closing transmission channel", s.srv.Hostname, s.srv.Appname)
			break loop
		case "RSET":
			s.writef("250 Ok")
			from = ""
			to = nil
			buffer.Reset()
		case "NOOP":
			s.writef("250 Ok")
		case "HELP", "VRFY", "EXPN":
			// See RFC 5321 section 4.2.4 for usage of 500 & 502 reply codes
			s.writef("502 Command not implemented")
		default:
			// See RFC 5321 section 4.2.4 for usage of 500 & 502 reply codes
			s.writef("500 Syntax error, command unrecognized")
		}
	}
}

// Wrapper function for writing a complete line to the socket.
func (s *session) writef(format string, args ...interface{}) {
	fmt.Fprintf(s.bw, format+"\r\n", args...)
	s.bw.Flush()
}

// Read a complete line from the socket.
func (s *session) readLine() (string, error) {
	line, err := s.br.ReadString('\n')
	if err != nil {
		return "", err
	}
	line = strings.TrimSpace(line) // Strip trailing \r\n
	return line, err
}

// Parse a line read from the socket.
func (s *session) parseLine(line string) (verb string, args string) {
	if idx := strings.Index(line, " "); idx != -1 {
		verb = strings.ToUpper(line[:idx])
		args = strings.TrimSpace(line[idx+1:])
	} else {
		verb = strings.ToUpper(line)
		args = ""
	}
	return verb, args
}

// Read the message data following a DATA command.
func (s *session) readData() ([]byte, error) {
	var data []byte
	for {
		slice, err := s.br.ReadSlice('\n')
		if err != nil {
			return nil, err
		}
		// Handle end of data denoted by lone period (\r\n.\r\n)
		if bytes.Equal(slice, []byte(".\r\n")) {
			break
		}
		// Remove leading period (RFC 5321 section 4.5.2)
		if slice[0] == '.' {
			slice = slice[1:]
		}
		data = append(data, slice...)
	}
	return data, nil
}

// Create the Received header to comply with RFC 2821 section 3.8.2.
// TODO: Work out what to do with multiple to addresses.
func (s *session) makeHeaders(to []string) []byte {
	var buffer bytes.Buffer
	now := time.Now().Format("Mon, _2 Jan 2006 15:04:05 -0700 (MST)")
	buffer.WriteString(fmt.Sprintf("Received: from %s (%s [%s])\r\n", s.remoteName, s.remoteHost, s.remoteIP))
	buffer.WriteString(fmt.Sprintf("        by %s (%s) with SMTP\r\n", s.srv.Hostname, s.srv.Appname))
	buffer.WriteString(fmt.Sprintf("        for <%s>; %s\r\n", to[0], now))
	return buffer.Bytes()
}
<commit_msg>Improve comments to satisfy golint<commit_after>// Package smtpd implements a basic SMTP server.
package smtpd

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"net"
	"os"
	"regexp"
	"strings"
	"time"
)

var (
	rcptToRE   = regexp.MustCompile(`[Tt][Oo]:<(.+)>`)
	mailFromRE = regexp.MustCompile(`[Ff][Rr][Oo][Mm]:<(.*)>`) // Delivery Status Notifications are sent with "MAIL FROM:<>"
)

// Handler function called upon successful receipt of an email.
type Handler func(remoteAddr net.Addr, from string, to []string, data []byte)

// ListenAndServe listens on the TCP network address addr
// and then calls Serve with handler to handle requests
// on incoming connections.
func ListenAndServe(addr string, handler Handler, appname string, hostname string) error {
	srv := &Server{Addr: addr, Handler: handler, Appname: appname, Hostname: hostname}
	return srv.ListenAndServe()
}

// Server is an SMTP server.
type Server struct {
	Addr     string // TCP address to listen on, defaults to ":25" (all addresses, port 25) if empty
	Handler  Handler
	Appname  string
	Hostname string
}

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.  If
// srv.Addr is blank, ":25" is used.
func (srv *Server) ListenAndServe() error {
	if srv.Addr == "" {
		srv.Addr = ":25"
	}
	if srv.Appname == "" {
		srv.Appname = "smtpd"
	}
	if srv.Hostname == "" {
		srv.Hostname, _ = os.Hostname()
	}
	ln, err := net.Listen("tcp", srv.Addr)
	if err != nil {
		return err
	}
	return srv.Serve(ln)
}

// Serve creates a new SMTP session after a network connection is established.
func (srv *Server) Serve(ln net.Listener) error {
	defer ln.Close()
	for {
		conn, err := ln.Accept()
		if err != nil {
			if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
				log.Printf("%s: Accept error: %v", srv.Appname, err)
				continue
			}
			return err
		}
		session, err := srv.newSession(conn)
		if err != nil {
			continue
		}
		go session.serve()
	}
}

type session struct {
	srv        *Server
	conn       net.Conn
	br         *bufio.Reader
	bw         *bufio.Writer
	remoteIP   string // Remote IP address
	remoteHost string // Remote hostname according to reverse DNS lookup
	remoteName string // Remote hostname as supplied with EHLO
}

// Create new session from connection.
func (srv *Server) newSession(conn net.Conn) (s *session, err error) {
	s = &session{
		srv:  srv,
		conn: conn,
		br:   bufio.NewReader(conn),
		bw:   bufio.NewWriter(conn),
	}
	return
}

// Function called to handle connection requests.
func (s *session) serve() {
	defer s.conn.Close()
	var from string
	var to []string
	var buffer bytes.Buffer

	// Get remote end info for the Received header.
	s.remoteIP, _, _ = net.SplitHostPort(s.conn.RemoteAddr().String())
	names, err := net.LookupAddr(s.remoteIP)
	if err == nil && len(names) > 0 {
		s.remoteHost = names[0]
	} else {
		s.remoteHost = "unknown"
	}

	// Send banner.
	s.writef("220 %s %s SMTP Service ready", s.srv.Hostname, s.srv.Appname)

loop:
	for {
		// Attempt to read a line from the socket.
		// On error, assume the client has gone away i.e. return from serve().
		line, err := s.readLine()
		if err != nil {
			break
		}
		verb, args := s.parseLine(line)

		switch verb {
		case "EHLO", "HELO":
			s.remoteName = args
			s.writef("250 %s greets %s", s.srv.Hostname, s.remoteName)

			// RFC 2821 section 4.1.4 specifies that EHLO has the same effect as RSET.
			from = ""
			to = nil
			buffer.Reset()
		case "MAIL":
			match := mailFromRE.FindStringSubmatch(args)
			if match == nil {
				s.writef("501 Syntax error in parameters or arguments (invalid FROM parameter)")
			} else {
				from = match[1]
				s.writef("250 Ok")
			}
			to = nil
			buffer.Reset()
		case "RCPT":
			if from == "" {
				s.writef("503 Bad sequence of commands (MAIL required before RCPT)")
				break
			}

			match := rcptToRE.FindStringSubmatch(args)
			if match == nil {
				s.writef("501 Syntax error in parameters or arguments (invalid TO parameter)")
			} else {
				// RFC 5321 specifies 100 minimum recipients
				if len(to) == 100 {
					s.writef("452 Too many recipients")
				} else {
					to = append(to, match[1])
					s.writef("250 Ok")
				}
			}
		case "DATA":
			if from == "" || to == nil {
				s.writef("503 Bad sequence of commands (MAIL & RCPT required before DATA)")
				break
			}

			s.writef("354 Start mail input; end with <CR><LF>.<CR><LF>")

			// Attempt to read message body from the socket.
			// On error, assume the client has gone away i.e. return from serve().
			data, err := s.readData()
			if err != nil {
				break loop
			}

			// Create Received header & write message body into buffer.
			buffer.Reset()
			buffer.Write(s.makeHeaders(to))
			buffer.Write(data)
			s.writef("250 Ok: queued")

			// Pass mail on to handler.
			if s.srv.Handler != nil {
				go s.srv.Handler(s.conn.RemoteAddr(), from, to, buffer.Bytes())
			}

			// Reset for next mail.
			from = ""
			to = nil
			buffer.Reset()
		case "QUIT":
			s.writef("221 %s %s SMTP Service closing transmission channel", s.srv.Hostname, s.srv.Appname)
			break loop
		case "RSET":
			s.writef("250 Ok")
			from = ""
			to = nil
			buffer.Reset()
		case "NOOP":
			s.writef("250 Ok")
		case "HELP", "VRFY", "EXPN":
			// See RFC 5321 section 4.2.4 for usage of 500 & 502 reply codes
			s.writef("502 Command not implemented")
		default:
			// See RFC 5321 section 4.2.4 for usage of 500 & 502 reply codes
			s.writef("500 Syntax error, command unrecognized")
		}
	}
}

// Wrapper function for writing a complete line to the socket.
func (s *session) writef(format string, args ...interface{}) {
	fmt.Fprintf(s.bw, format+"\r\n", args...)
	s.bw.Flush()
}

// Read a complete line from the socket.
func (s *session) readLine() (string, error) {
	line, err := s.br.ReadString('\n')
	if err != nil {
		return "", err
	}
	line = strings.TrimSpace(line) // Strip trailing \r\n
	return line, err
}

// Parse a line read from the socket.
func (s *session) parseLine(line string) (verb string, args string) {
	if idx := strings.Index(line, " "); idx != -1 {
		verb = strings.ToUpper(line[:idx])
		args = strings.TrimSpace(line[idx+1:])
	} else {
		verb = strings.ToUpper(line)
		args = ""
	}
	return verb, args
}

// Read the message data following a DATA command.
func (s *session) readData() ([]byte, error) {
	var data []byte
	for {
		slice, err := s.br.ReadSlice('\n')
		if err != nil {
			return nil, err
		}
		// Handle end of data denoted by lone period (\r\n.\r\n)
		if bytes.Equal(slice, []byte(".\r\n")) {
			break
		}
		// Remove leading period (RFC 5321 section 4.5.2)
		if slice[0] == '.' {
			slice = slice[1:]
		}
		data = append(data, slice...)
	}
	return data, nil
}

// Create the Received header to comply with RFC 2821 section 3.8.2.
// TODO: Work out what to do with multiple to addresses.
func (s *session) makeHeaders(to []string) []byte {
	var buffer bytes.Buffer
	now := time.Now().Format("Mon, _2 Jan 2006 15:04:05 -0700 (MST)")
	buffer.WriteString(fmt.Sprintf("Received: from %s (%s [%s])\r\n", s.remoteName, s.remoteHost, s.remoteIP))
	buffer.WriteString(fmt.Sprintf("        by %s (%s) with SMTP\r\n", s.srv.Hostname, s.srv.Appname))
	buffer.WriteString(fmt.Sprintf("        for <%s>; %s\r\n", to[0], now))
	return buffer.Bytes()
}
<|endoftext|>


<commit_before>package slack

// Block Objects are also known as Composition Objects
//
// For more information: https://api.slack.com/reference/messaging/composition-objects

// BlockObject defines an interface that all block object types should
// implement.
// @TODO: Is this interface needed?
type blockObject interface {
	validateType() MessageObjectType
}

// ImageBlockObject An element to insert an image - this element can be used
// in section and context blocks only. If you want a block with only an image
// in it, you're looking for the image block.
//
// More Information: https://api.slack.com/reference/messaging/block-elements#image
type ImageBlockObject struct {
	Type     MessageObjectType `json:"type"`
	ImageURL string            `json:"image_url"`
	AltText  string            `json:"alt_text"`
}

// validateType enforces block objects for element and block parameters
func (s ImageBlockObject) validateType() MessageObjectType {
	return s.Type
}

// NewImageBlockObject returns a new instance of an image block element
func NewImageBlockObject(imageURL, altText string) *ImageBlockObject {
	return &ImageBlockObject{
		Type:     motImage,
		ImageURL: imageURL,
		AltText:  altText,
	}
}

// TextBlockObject defines a text element object to be used with blocks
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#text
type TextBlockObject struct {
	Type     string `json:"type"`
	Text     string `json:"text"`
	Emoji    bool   `json:"emoji,omitempty"`
	Verbatim bool   `json:"verbatim,omitempty"`
}

// validateType enforces block objects for element and block parameters
func (s TextBlockObject) validateType() MessageObjectType {
	return MessageObjectType(s.Type)
}

// NewTextBlockObject returns an instance of a new Text Block Object
func NewTextBlockObject(elementType, text string, emoji, verbatim bool) *TextBlockObject {
	return &TextBlockObject{
		Type:     elementType,
		Text:     text,
		Emoji:    emoji,
		Verbatim: verbatim,
	}
}

// ConfirmationBlockObject defines a dialog that provides a confirmation step to
// any interactive element. This dialog will ask the user to confirm their action by
// offering a confirm and deny buttons.
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#confirm
type ConfirmationBlockObject struct {
	Title   *TextBlockObject `json:"title"`
	Text    *TextBlockObject `json:"text"`
	Confirm *TextBlockObject `json:"confirm"`
	Deny    *TextBlockObject `json:"deny"`
}

// validateType enforces block objects for element and block parameters
func (s ConfirmationBlockObject) validateType() MessageObjectType {
	return motConfirmation
}

// NewConfirmationBlockObject returns an instance of a new Confirmation Block Object
func NewConfirmationBlockObject(title, text, confirm, deny *TextBlockObject) *ConfirmationBlockObject {
	return &ConfirmationBlockObject{
		Title:   title,
		Text:    text,
		Confirm: confirm,
		Deny:    deny,
	}
}

// OptionBlockObject represents a single selectable item in a select menu
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#option
type OptionBlockObject struct {
	Text  *TextBlockObject `json:"text"`
	Value string           `json:"value"`
}

// NewOptionBlockObject returns an instance of a new Option Block Element
func NewOptionBlockObject(value string, text *TextBlockObject) *OptionBlockObject {
	return &OptionBlockObject{
		Text:  text,
		Value: value,
	}
}

// validateType enforces block objects for element and block parameters
func (s OptionBlockObject) validateType() MessageObjectType {
	return motOption
}

// OptionGroupBlockObject Provides a way to group options in a select menu.
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#option-group
type OptionGroupBlockObject struct {
	Label   *TextBlockObject     `json:"label"`
	Options []*OptionBlockObject `json:"options"`
}

// validateType enforces block objects for element and block parameters
func (s OptionGroupBlockObject) validateType() MessageObjectType {
	return motOptionGroup
}

// NewOptionGroupBlockElement returns an instance of a new option group block element
func NewOptionGroupBlockElement(label *TextBlockObject, options ...*OptionBlockObject) *OptionGroupBlockObject {
	return &OptionGroupBlockObject{
		Label:   label,
		Options: options,
	}
}
<commit_msg>Add exported consts for text object types<commit_after>package slack

// Block Objects are also known as Composition Objects
//
// For more information: https://api.slack.com/reference/messaging/composition-objects

// BlockObject defines an interface that all block object types should
// implement.
// @TODO: Is this interface needed?
type blockObject interface {
	validateType() MessageObjectType
}

const (
	MarkdownType  = "mrkdwn"
	PlainTextType = "plain_text"
)

// ImageBlockObject An element to insert an image - this element can be used
// in section and context blocks only. If you want a block with only an image
// in it, you're looking for the image block.
//
// More Information: https://api.slack.com/reference/messaging/block-elements#image
type ImageBlockObject struct {
	Type     MessageObjectType `json:"type"`
	ImageURL string            `json:"image_url"`
	AltText  string            `json:"alt_text"`
}

// validateType enforces block objects for element and block parameters
func (s ImageBlockObject) validateType() MessageObjectType {
	return s.Type
}

// NewImageBlockObject returns a new instance of an image block element
func NewImageBlockObject(imageURL, altText string) *ImageBlockObject {
	return &ImageBlockObject{
		Type:     motImage,
		ImageURL: imageURL,
		AltText:  altText,
	}
}

// TextBlockObject defines a text element object to be used with blocks
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#text
type TextBlockObject struct {
	Type     string `json:"type"`
	Text     string `json:"text"`
	Emoji    bool   `json:"emoji,omitempty"`
	Verbatim bool   `json:"verbatim,omitempty"`
}

// validateType enforces block objects for element and block parameters
func (s TextBlockObject) validateType() MessageObjectType {
	return MessageObjectType(s.Type)
}

// NewTextBlockObject returns an instance of a new Text Block Object
func NewTextBlockObject(elementType, text string, emoji, verbatim bool) *TextBlockObject {
	return &TextBlockObject{
		Type:     elementType,
		Text:     text,
		Emoji:    emoji,
		Verbatim: verbatim,
	}
}

// ConfirmationBlockObject defines a dialog that provides a confirmation step to
// any interactive element. This dialog will ask the user to confirm their action by
// offering a confirm and deny buttons.
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#confirm
type ConfirmationBlockObject struct {
	Title   *TextBlockObject `json:"title"`
	Text    *TextBlockObject `json:"text"`
	Confirm *TextBlockObject `json:"confirm"`
	Deny    *TextBlockObject `json:"deny"`
}

// validateType enforces block objects for element and block parameters
func (s ConfirmationBlockObject) validateType() MessageObjectType {
	return motConfirmation
}

// NewConfirmationBlockObject returns an instance of a new Confirmation Block Object
func NewConfirmationBlockObject(title, text, confirm, deny *TextBlockObject) *ConfirmationBlockObject {
	return &ConfirmationBlockObject{
		Title:   title,
		Text:    text,
		Confirm: confirm,
		Deny:    deny,
	}
}

// OptionBlockObject represents a single selectable item in a select menu
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#option
type OptionBlockObject struct {
	Text  *TextBlockObject `json:"text"`
	Value string           `json:"value"`
}

// NewOptionBlockObject returns an instance of a new Option Block Element
func NewOptionBlockObject(value string, text *TextBlockObject) *OptionBlockObject {
	return &OptionBlockObject{
		Text:  text,
		Value: value,
	}
}

// validateType enforces block objects for element and block parameters
func (s OptionBlockObject) validateType() MessageObjectType {
	return motOption
}

// OptionGroupBlockObject Provides a way to group options in a select menu.
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#option-group
type OptionGroupBlockObject struct {
	Label   *TextBlockObject     `json:"label"`
	Options []*OptionBlockObject `json:"options"`
}

// validateType enforces block objects for element and block parameters
func (s OptionGroupBlockObject) validateType() MessageObjectType {
	return motOptionGroup
}

// NewOptionGroupBlockElement returns an instance of a new option group block element
func NewOptionGroupBlockElement(label *TextBlockObject, options ...*OptionBlockObject) *OptionGroupBlockObject {
	return &OptionGroupBlockObject{
		Label:   label,
		Options: options,
	}
}
<|endoftext|>