<commit_before>package handler

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"runtime/debug"
	"time"

	"github.com/TimothyYe/godns"
	"golang.org/x/net/proxy"
)

// CloudflareHandler struct definition
type CloudflareHandler struct {
	Configuration *godns.Settings
	API           string
}

// DNSRecordResponse struct
type DNSRecordResponse struct {
	Records []DNSRecord `json:"result"`
	Success bool        `json:"success"`
}

// DNSRecordUpdateResponse struct
type DNSRecordUpdateResponse struct {
	Record  DNSRecord `json:"result"`
	Success bool      `json:"success"`
}

// DNSRecord for Cloudflare API
type DNSRecord struct {
	ID      string `json:"id"`
	IP      string `json:"content"`
	Name    string `json:"name"`
	Proxied bool   `json:"proxied"`
	Type    string `json:"type"`
	ZoneID  string `json:"zone_id"`
}

// SetIP updates DNSRecord.IP
func (r *DNSRecord) SetIP(ip string) {
	r.IP = ip
}

// ZoneResponse is a wrapper for Zones
type ZoneResponse struct {
	Zones   []Zone `json:"result"`
	Success bool   `json:"success"`
}

// Zone object with id and name
type Zone struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

// SetConfiguration pass dns settings and store it to handler instance
func (handler *CloudflareHandler) SetConfiguration(conf *godns.Settings) {
	handler.Configuration = conf
	handler.API = "https://api.cloudflare.com/client/v4"
}

// DomainLoop the main logic loop
func (handler *CloudflareHandler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) {
	defer func() {
		if err := recover(); err != nil {
			log.Printf("Recovered in %v: %v\n", err, debug.Stack())
			panicChan <- *domain
		}
	}()

	var lastIP string
	for {
		currentIP, err := godns.GetCurrentIP(handler.Configuration)
		if err != nil {
			log.Println("Error in GetCurrentIP:", err)
			continue
		}
		log.Println("Current IP is:", currentIP)
		//check against locally cached IP, if no change, skip update
		if currentIP == lastIP {
			log.Printf("IP is the same as cached one. Skip update.\n")
		} else {
			lastIP = currentIP

			log.Println("Checking IP for domain", domain.DomainName)
			zoneID := handler.getZone(domain.DomainName)
			if zoneID != "" {
				records := handler.getDNSRecords(zoneID)

				// update records
				for _, rec := range records {
					if !recordTracked(domain, &rec) {
						log.Println("Skiping record:", rec.Name)
						continue
					}
					if rec.IP != currentIP {
						log.Printf("IP mismatch: Current(%+v) vs Cloudflare(%+v)\r\n", currentIP, rec.IP)
						handler.updateRecord(rec, currentIP)
					} else {
						log.Printf("Record OK: %+v - %+v\r\n", rec.Name, rec.IP)
					}
				}
			} else {
				log.Println("Failed to find zone for domain:", domain.DomainName)
			}
		}
		// Interval is 5 minutes
		log.Printf("Going to sleep, will start next checking in %d minutes...\r\n", godns.INTERVAL)
		time.Sleep(time.Minute * godns.INTERVAL)
	}
}

// Check if record is present in domain conf
func recordTracked(domain *godns.Domain, record *DNSRecord) bool {
	for _, subDomain := range domain.SubDomains {
		sd := fmt.Sprintf("%s.%s", subDomain, domain.DomainName)
		if record.Name == sd {
			return true
		}
	}

	return false
}

// Create a new request with auth in place and optional proxy
func (handler *CloudflareHandler) newRequest(method, url string, body io.Reader) (*http.Request, *http.Client) {
	client := &http.Client{}

	if handler.Configuration.Socks5Proxy != "" {
		log.Println("use socks5 proxy:" + handler.Configuration.Socks5Proxy)
		dialer, err := proxy.SOCKS5("tcp", handler.Configuration.Socks5Proxy, nil, proxy.Direct)
		if err != nil {
			log.Println("can't connect to the proxy:", err)
		} else {
			httpTransport := &http.Transport{}
			client.Transport = httpTransport
			httpTransport.Dial = dialer.Dial
		}
	}

	req, _ := http.NewRequest(method, handler.API+url, body)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Auth-Email", handler.Configuration.Email)
	req.Header.Set("X-Auth-Key", handler.Configuration.Password)
	return req, client
}

// Find the correct zone via domain name
func (handler *CloudflareHandler) getZone(domain string) string {

	var z ZoneResponse

	req, client := handler.newRequest("GET", "/zones", nil)
	resp, err := client.Do(req)
	if err != nil {
		log.Println("Request error:", err.Error())
		return ""
	}

	body, _ := ioutil.ReadAll(resp.Body)
	err = json.Unmarshal(body, &z)
	if err != nil {
		log.Printf("Decoder error: %+v\n", err)
		log.Printf("Response body: %+v\n", string(body))
		return ""
	}
	if z.Success != true {
		log.Printf("Response failed: %+v\n", string(body))
		return ""
	}

	for _, zone := range z.Zones {
		if zone.Name == domain {
			return zone.ID
		}
	}
	return ""
}

// Get all DNS A records for a zone
func (handler *CloudflareHandler) getDNSRecords(zoneID string) []DNSRecord {

	var empty []DNSRecord
	var r DNSRecordResponse

	req, client := handler.newRequest("GET", "/zones/"+zoneID+"/dns_records?type=A", nil)
	resp, err := client.Do(req)
	if err != nil {
		log.Println("Request error:", err.Error())
		return empty
	}

	body, _ := ioutil.ReadAll(resp.Body)
	err = json.Unmarshal(body, &r)
	if err != nil {
		log.Printf("Decoder error: %+v\n", err)
		log.Printf("Response body: %+v\n", string(body))
		return empty
	}
	if r.Success != true {
		body, _ := ioutil.ReadAll(resp.Body)
		log.Printf("Response failed: %+v\n", string(body))
		return empty

	}
	return r.Records
}

// Update DNS A Record with new IP
func (handler *CloudflareHandler) updateRecord(record DNSRecord, newIP string) {

	var r DNSRecordUpdateResponse
	record.SetIP(newIP)

	j, _ := json.Marshal(record)
	req, client := handler.newRequest("PUT",
		"/zones/"+record.ZoneID+"/dns_records/"+record.ID,
		bytes.NewBuffer(j),
	)
	resp, err := client.Do(req)
	if err != nil {
		log.Println("Request error:", err.Error())
		return
	}

	body, _ := ioutil.ReadAll(resp.Body)
	err = json.Unmarshal(body, &r)
	if err != nil {
		log.Printf("Decoder error: %+v\n", err)
		log.Printf("Response body: %+v\n", string(body))
		return
	}
	if r.Success != true {
		body, _ := ioutil.ReadAll(resp.Body)
		log.Printf("Response failed: %+v\n", string(body))
	} else {
		log.Printf("Record updated: %+v - %+v", record.Name, record.IP)
	}
}
<commit_msg>send mail notification<commit_after>package handler

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"runtime/debug"
	"time"

	"github.com/TimothyYe/godns"
	"golang.org/x/net/proxy"
)

// CloudflareHandler struct definition
type CloudflareHandler struct {
	Configuration *godns.Settings
	API           string
}

// DNSRecordResponse struct
type DNSRecordResponse struct {
	Records []DNSRecord `json:"result"`
	Success bool        `json:"success"`
}

// DNSRecordUpdateResponse struct
type DNSRecordUpdateResponse struct {
	Record  DNSRecord `json:"result"`
	Success bool      `json:"success"`
}

// DNSRecord for Cloudflare API
type DNSRecord struct {
	ID      string `json:"id"`
	IP      string `json:"content"`
	Name    string `json:"name"`
	Proxied bool   `json:"proxied"`
	Type    string `json:"type"`
	ZoneID  string `json:"zone_id"`
}

// SetIP updates DNSRecord.IP
func (r *DNSRecord) SetIP(ip string) {
	r.IP = ip
}

// ZoneResponse is a wrapper for Zones
type ZoneResponse struct {
	Zones   []Zone `json:"result"`
	Success bool   `json:"success"`
}

// Zone object with id and name
type Zone struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

// SetConfiguration pass dns settings and store it to handler instance
func (handler *CloudflareHandler) SetConfiguration(conf *godns.Settings) {
	handler.Configuration = conf
	handler.API = "https://api.cloudflare.com/client/v4"
}

// DomainLoop the main logic loop
func (handler *CloudflareHandler) DomainLoop(domain *godns.Domain, panicChan chan<- godns.Domain) {
	defer func() {
		if err := recover(); err != nil {
			log.Printf("Recovered in %v: %v\n", err, debug.Stack())
			panicChan <- *domain
		}
	}()

	var lastIP string
	for {
		currentIP, err := godns.GetCurrentIP(handler.Configuration)
		if err != nil {
			log.Println("Error in GetCurrentIP:", err)
			continue
		}
		log.Println("Current IP is:", currentIP)
		//check against locally cached IP, if no change, skip update
		if currentIP == lastIP {
			log.Printf("IP is the same as cached one. Skip update.\n")
		} else {
			lastIP = currentIP

			log.Println("Checking IP for domain", domain.DomainName)
			zoneID := handler.getZone(domain.DomainName)
			if zoneID != "" {
				records := handler.getDNSRecords(zoneID)

				// update records
				for _, rec := range records {
					if !recordTracked(domain, &rec) {
						log.Println("Skiping record:", rec.Name)
						continue
					}
					if rec.IP != currentIP {
						log.Printf("IP mismatch: Current(%+v) vs Cloudflare(%+v)\r\n", currentIP, rec.IP)
						handler.updateRecord(rec, currentIP)

						// Send mail notification if notify is enabled
						if handler.Configuration.Notify.Enabled {
							log.Print("Sending notification to:", handler.Configuration.Notify.SendTo)
							godns.SendNotify(handler.Configuration, rec.Name, currentIP)
						}
					} else {
						log.Printf("Record OK: %+v - %+v\r\n", rec.Name, rec.IP)
					}
				}
			} else {
				log.Println("Failed to find zone for domain:", domain.DomainName)
			}
		}
		// Interval is 5 minutes
		log.Printf("Going to sleep, will start next checking in %d minutes...\r\n", godns.INTERVAL)
		time.Sleep(time.Minute * godns.INTERVAL)
	}
}

// Check if record is present in domain conf
func recordTracked(domain *godns.Domain, record *DNSRecord) bool {
	for _, subDomain := range domain.SubDomains {
		sd := fmt.Sprintf("%s.%s", subDomain, domain.DomainName)
		if record.Name == sd {
			return true
		}
	}

	return false
}

// Create a new request with auth in place and optional proxy
func (handler *CloudflareHandler) newRequest(method, url string, body io.Reader) (*http.Request, *http.Client) {
	client := &http.Client{}

	if handler.Configuration.Socks5Proxy != "" {
		log.Println("use socks5 proxy:" + handler.Configuration.Socks5Proxy)
		dialer, err := proxy.SOCKS5("tcp", handler.Configuration.Socks5Proxy, nil, proxy.Direct)
		if err != nil {
			log.Println("can't connect to the proxy:", err)
		} else {
			httpTransport := &http.Transport{}
			client.Transport = httpTransport
			httpTransport.Dial = dialer.Dial
		}
	}

	req, _ := http.NewRequest(method, handler.API+url, body)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Auth-Email", handler.Configuration.Email)
	req.Header.Set("X-Auth-Key", handler.Configuration.Password)
	return req, client
}

// Find the correct zone via domain name
func (handler *CloudflareHandler) getZone(domain string) string {

	var z ZoneResponse

	req, client := handler.newRequest("GET", "/zones", nil)
	resp, err := client.Do(req)
	if err != nil {
		log.Println("Request error:", err.Error())
		return ""
	}

	body, _ := ioutil.ReadAll(resp.Body)
	err = json.Unmarshal(body, &z)
	if err != nil {
		log.Printf("Decoder error: %+v\n", err)
		log.Printf("Response body: %+v\n", string(body))
		return ""
	}
	if z.Success != true {
		log.Printf("Response failed: %+v\n", string(body))
		return ""
	}

	for _, zone := range z.Zones {
		if zone.Name == domain {
			return zone.ID
		}
	}
	return ""
}

// Get all DNS A records for a zone
func (handler *CloudflareHandler) getDNSRecords(zoneID string) []DNSRecord {

	var empty []DNSRecord
	var r DNSRecordResponse

	req, client := handler.newRequest("GET", "/zones/"+zoneID+"/dns_records?type=A", nil)
	resp, err := client.Do(req)
	if err != nil {
		log.Println("Request error:", err.Error())
		return empty
	}

	body, _ := ioutil.ReadAll(resp.Body)
	err = json.Unmarshal(body, &r)
	if err != nil {
		log.Printf("Decoder error: %+v\n", err)
		log.Printf("Response body: %+v\n", string(body))
		return empty
	}
	if r.Success != true {
		body, _ := ioutil.ReadAll(resp.Body)
		log.Printf("Response failed: %+v\n", string(body))
		return empty

	}
	return r.Records
}

// Update DNS A Record with new IP
func (handler *CloudflareHandler) updateRecord(record DNSRecord, newIP string) {

	var r DNSRecordUpdateResponse
	record.SetIP(newIP)

	j, _ := json.Marshal(record)
	req, client := handler.newRequest("PUT",
		"/zones/"+record.ZoneID+"/dns_records/"+record.ID,
		bytes.NewBuffer(j),
	)
	resp, err := client.Do(req)
	if err != nil {
		log.Println("Request error:", err.Error())
		return
	}

	body, _ := ioutil.ReadAll(resp.Body)
	err = json.Unmarshal(body, &r)
	if err != nil {
		log.Printf("Decoder error: %+v\n", err)
		log.Printf("Response body: %+v\n", string(body))
		return
	}
	if r.Success != true {
		body, _ := ioutil.ReadAll(resp.Body)
		log.Printf("Response failed: %+v\n", string(body))
	} else {
		log.Printf("Record updated: %+v - %+v", record.Name, record.IP)
	}
}
<|endoftext|>


<commit_before>package communicator

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"time"

	packerssh "github.com/hashicorp/packer/communicator/ssh"
	"github.com/hashicorp/packer/helper/multistep"
	helperssh "github.com/hashicorp/packer/helper/ssh"
	"github.com/hashicorp/packer/template/interpolate"
	"github.com/masterzen/winrm"
	"github.com/mitchellh/go-homedir"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
)

// Config is the common configuration that communicators allow within
// a builder.
type Config struct {
	Type string `mapstructure:"communicator"`

	// SSH
	SSHHost                   string        `mapstructure:"ssh_host"`
	SSHPort                   int           `mapstructure:"ssh_port"`
	SSHUsername               string        `mapstructure:"ssh_username"`
	SSHPassword               string        `mapstructure:"ssh_password"`
	SSHPublicKey              []byte        `mapstructure:"ssh_public_key"`
	SSHPrivateKey             []byte        `mapstructure:"ssh_private_key"`
	SSHKeyPairName            string        `mapstructure:"ssh_keypair_name"`
	SSHTemporaryKeyPairName   string        `mapstructure:"temporary_key_pair_name"`
	SSHClearAuthorizedKeys    bool          `mapstructure:"ssh_clear_authorized_keys"`
	SSHPrivateKeyFile         string        `mapstructure:"ssh_private_key_file"`
	SSHInterface              string        `mapstructure:"ssh_interface"`
	SSHIPVersion              string        `mapstructure:"ssh_ip_version"`
	SSHPty                    bool          `mapstructure:"ssh_pty"`
	SSHTimeout                time.Duration `mapstructure:"ssh_timeout"`
	SSHAgentAuth              bool          `mapstructure:"ssh_agent_auth"`
	SSHDisableAgentForwarding bool          `mapstructure:"ssh_disable_agent_forwarding"`
	SSHHandshakeAttempts      int           `mapstructure:"ssh_handshake_attempts"`
	SSHBastionHost            string        `mapstructure:"ssh_bastion_host"`
	SSHBastionPort            int           `mapstructure:"ssh_bastion_port"`
	SSHBastionAgentAuth       bool          `mapstructure:"ssh_bastion_agent_auth"`
	SSHBastionUsername        string        `mapstructure:"ssh_bastion_username"`
	SSHBastionPassword        string        `mapstructure:"ssh_bastion_password"`
	SSHBastionPrivateKeyFile  string        `mapstructure:"ssh_bastion_private_key_file"`
	SSHFileTransferMethod     string        `mapstructure:"ssh_file_transfer_method"`
	SSHProxyHost              string        `mapstructure:"ssh_proxy_host"`
	SSHProxyPort              int           `mapstructure:"ssh_proxy_port"`
	SSHProxyUsername          string        `mapstructure:"ssh_proxy_username"`
	SSHProxyPassword          string        `mapstructure:"ssh_proxy_password"`
	SSHKeepAliveInterval      time.Duration `mapstructure:"ssh_keep_alive_interval"`
	SSHReadWriteTimeout       time.Duration `mapstructure:"ssh_read_write_timeout"`

	// WinRM
	WinRMUser               string        `mapstructure:"winrm_username"`
	WinRMPassword           string        `mapstructure:"winrm_password"`
	WinRMHost               string        `mapstructure:"winrm_host"`
	WinRMPort               int           `mapstructure:"winrm_port"`
	WinRMTimeout            time.Duration `mapstructure:"winrm_timeout"`
	WinRMUseSSL             bool          `mapstructure:"winrm_use_ssl"`
	WinRMInsecure           bool          `mapstructure:"winrm_insecure"`
	WinRMUseNTLM            bool          `mapstructure:"winrm_use_ntlm"`
	WinRMTransportDecorator func() winrm.Transporter
}

// ReadSSHPrivateKeyFile returns the SSH private key bytes
func (c *Config) ReadSSHPrivateKeyFile() ([]byte, error) {
	var privateKey []byte

	if c.SSHPrivateKeyFile != "" {
		keyPath, err := homedir.Expand(c.SSHPrivateKeyFile)
		if err != nil {
			return privateKey, fmt.Errorf("Error expanding path for SSH private key: %s", err)
		}
		privateKey, err = ioutil.ReadFile(keyPath)
		if err != nil {
			return privateKey, fmt.Errorf("Error on reading SSH private key: %s", err)
		}
	}
	return privateKey, nil
}

// SSHConfigFunc returns a function that can be used for the SSH communicator
// config for connecting to the instance created over SSH using the private key
// or password.
func (c *Config) SSHConfigFunc() func(multistep.StateBag) (*ssh.ClientConfig, error) {
	return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
		sshConfig := &ssh.ClientConfig{
			User:            c.SSHUsername,
			HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		}

		if c.SSHAgentAuth {
			authSock := os.Getenv("SSH_AUTH_SOCK")
			if authSock == "" {
				return nil, fmt.Errorf("SSH_AUTH_SOCK is not set")
			}

			sshAgent, err := net.Dial("unix", authSock)
			if err != nil {
				return nil, fmt.Errorf("Cannot connect to SSH Agent socket %q: %s", authSock, err)
			}

			sshConfig.Auth = append(sshConfig.Auth, ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers))
		}

		var privateKeys [][]byte
		if c.SSHPrivateKeyFile != "" {
			privateKey, err := c.ReadSSHPrivateKeyFile()
			if err != nil {
				return nil, err
			}
			privateKeys = append(privateKeys, privateKey)
		}

		// aws,alicloud,cloudstack,digitalOcean,oneAndOne,openstack,oracle & profitbricks key
		if iKey, hasKey := state.GetOk("privateKey"); hasKey {
			privateKeys = append(privateKeys, []byte(iKey.(string)))
		}

		if len(c.SSHPrivateKey) != 0 {
			privateKeys = append(privateKeys, c.SSHPrivateKey)
		}

		for _, key := range privateKeys {
			signer, err := ssh.ParsePrivateKey(key)
			if err != nil {
				return nil, fmt.Errorf("Error setting up SSH config: %s", err)
			}
			sshConfig.Auth = append(sshConfig.Auth, ssh.PublicKeys(signer))
		}

		if c.SSHPassword != "" {
			sshConfig.Auth = append(sshConfig.Auth,
				ssh.Password(c.SSHPassword),
				ssh.KeyboardInteractive(packerssh.PasswordKeyboardInteractive(c.SSHPassword)),
			)
		}
		return sshConfig, nil
	}
}

// Port returns the port that will be used for access based on config.
func (c *Config) Port() int {
	switch c.Type {
	case "ssh":
		return c.SSHPort
	case "winrm":
		return c.WinRMPort
	default:
		return 0
	}
}

// Host returns the port that will be used for access based on config.
func (c *Config) Host() string {
	switch c.Type {
	case "ssh":
		return c.SSHHost
	case "winrm":
		return c.WinRMHost
	default:
		return ""
	}
}

// User returns the port that will be used for access based on config.
func (c *Config) User() string {
	switch c.Type {
	case "ssh":
		return c.SSHUsername
	case "winrm":
		return c.WinRMUser
	default:
		return ""
	}
}

// Password returns the port that will be used for access based on config.
func (c *Config) Password() string {
	switch c.Type {
	case "ssh":
		return c.SSHPassword
	case "winrm":
		return c.WinRMPassword
	default:
		return ""
	}
}

func (c *Config) Prepare(ctx *interpolate.Context) []error {
	if c.Type == "" {
		c.Type = "ssh"
	}

	var errs []error
	switch c.Type {
	case "ssh":
		if es := c.prepareSSH(ctx); len(es) > 0 {
			errs = append(errs, es...)
		}
	case "winrm":
		if es := c.prepareWinRM(ctx); len(es) > 0 {
			errs = append(errs, es...)
		}
	case "docker", "none":
		break
	default:
		return []error{fmt.Errorf("Communicator type %s is invalid", c.Type)}
	}

	return errs
}

func (c *Config) prepareSSH(ctx *interpolate.Context) []error {
	if c.SSHPort == 0 {
		c.SSHPort = 22
	}

	if c.SSHTimeout == 0 {
		c.SSHTimeout = 5 * time.Minute
	}

	if c.SSHKeepAliveInterval == 0 {
		c.SSHKeepAliveInterval = 5 * time.Second
	}

	if c.SSHHandshakeAttempts == 0 {
		c.SSHHandshakeAttempts = 10
	}

	if c.SSHBastionHost != "" {
		if c.SSHBastionPort == 0 {
			c.SSHBastionPort = 22
		}

		if c.SSHBastionPrivateKeyFile == "" && c.SSHPrivateKeyFile != "" {
			c.SSHBastionPrivateKeyFile = c.SSHPrivateKeyFile
		}
	}

	if c.SSHProxyHost != "" {
		if c.SSHProxyPort == 0 {
			c.SSHProxyPort = 1080
		}
	}

	if c.SSHFileTransferMethod == "" {
		c.SSHFileTransferMethod = "scp"
	}

	// Validation
	var errs []error
	if c.SSHUsername == "" {
		errs = append(errs, errors.New("An ssh_username must be specified\n  Note: some builders used to default ssh_username to \"root\"."))
	}

	if c.SSHPrivateKeyFile != "" {
		path, err := homedir.Expand(c.SSHPrivateKeyFile)
		if err != nil {
			errs = append(errs, fmt.Errorf(
				"ssh_private_key_file is invalid: %s", err))
		} else if _, err := os.Stat(path); err != nil {
			errs = append(errs, fmt.Errorf(
				"ssh_private_key_file is invalid: %s", err))
		} else if _, err := helperssh.FileSigner(path); err != nil {
			errs = append(errs, fmt.Errorf(
				"ssh_private_key_file is invalid: %s", err))
		}
	}

	if c.SSHBastionHost != "" && !c.SSHBastionAgentAuth {
		if c.SSHBastionPassword == "" && c.SSHBastionPrivateKeyFile == "" {
			errs = append(errs, errors.New(
				"ssh_bastion_password or ssh_bastion_private_key_file must be specified"))
		} else if c.SSHBastionPrivateKeyFile != "" {
			path, err := homedir.Expand(c.SSHBastionPrivateKeyFile)
			if err != nil {
				errs = append(errs, fmt.Errorf(
					"ssh_bastion_private_key_file is invalid: %s", err))
			} else if _, err := os.Stat(path); err != nil {
				errs = append(errs, fmt.Errorf(
					"ssh_bastion_private_key_file is invalid: %s", err))
			} else if _, err := helperssh.FileSigner(path); err != nil {
				errs = append(errs, fmt.Errorf(
					"ssh_bastion_private_key_file is invalid: %s", err))
			}
		}
	}

	if c.SSHFileTransferMethod != "scp" && c.SSHFileTransferMethod != "sftp" {
		errs = append(errs, fmt.Errorf(
			"ssh_file_transfer_method ('%s') is invalid, valid methods: sftp, scp",
			c.SSHFileTransferMethod))
	}

	if c.SSHBastionHost != "" && c.SSHProxyHost != "" {
		errs = append(errs, errors.New("please specify either ssh_bastion_host or ssh_proxy_host, not both"))
	}

	return errs
}

func (c *Config) prepareWinRM(ctx *interpolate.Context) []error {
	if c.WinRMPort == 0 && c.WinRMUseSSL {
		c.WinRMPort = 5986
	} else if c.WinRMPort == 0 {
		c.WinRMPort = 5985
	}

	if c.WinRMTimeout == 0 {
		c.WinRMTimeout = 30 * time.Minute
	}

	if c.WinRMUseNTLM == true {
		c.WinRMTransportDecorator = func() winrm.Transporter { return &winrm.ClientNTLM{} }
	}

	var errs []error
	if c.WinRMUser == "" {
		errs = append(errs, errors.New("winrm_username must be specified."))
	}

	return errs
}
<commit_msg>communicator/ssh: make ssh keys payload internal<commit_after>package communicator

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"time"

	packerssh "github.com/hashicorp/packer/communicator/ssh"
	"github.com/hashicorp/packer/helper/multistep"
	helperssh "github.com/hashicorp/packer/helper/ssh"
	"github.com/hashicorp/packer/template/interpolate"
	"github.com/masterzen/winrm"
	"github.com/mitchellh/go-homedir"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
)

// Config is the common configuration that communicators allow within
// a builder.
type Config struct {
	Type string `mapstructure:"communicator"`

	// SSH
	SSHHost                   string        `mapstructure:"ssh_host"`
	SSHPort                   int           `mapstructure:"ssh_port"`
	SSHUsername               string        `mapstructure:"ssh_username"`
	SSHPassword               string        `mapstructure:"ssh_password"`
	SSHKeyPairName            string        `mapstructure:"ssh_keypair_name"`
	SSHTemporaryKeyPairName   string        `mapstructure:"temporary_key_pair_name"`
	SSHClearAuthorizedKeys    bool          `mapstructure:"ssh_clear_authorized_keys"`
	SSHPrivateKeyFile         string        `mapstructure:"ssh_private_key_file"`
	SSHInterface              string        `mapstructure:"ssh_interface"`
	SSHIPVersion              string        `mapstructure:"ssh_ip_version"`
	SSHPty                    bool          `mapstructure:"ssh_pty"`
	SSHTimeout                time.Duration `mapstructure:"ssh_timeout"`
	SSHAgentAuth              bool          `mapstructure:"ssh_agent_auth"`
	SSHDisableAgentForwarding bool          `mapstructure:"ssh_disable_agent_forwarding"`
	SSHHandshakeAttempts      int           `mapstructure:"ssh_handshake_attempts"`
	SSHBastionHost            string        `mapstructure:"ssh_bastion_host"`
	SSHBastionPort            int           `mapstructure:"ssh_bastion_port"`
	SSHBastionAgentAuth       bool          `mapstructure:"ssh_bastion_agent_auth"`
	SSHBastionUsername        string        `mapstructure:"ssh_bastion_username"`
	SSHBastionPassword        string        `mapstructure:"ssh_bastion_password"`
	SSHBastionPrivateKeyFile  string        `mapstructure:"ssh_bastion_private_key_file"`
	SSHFileTransferMethod     string        `mapstructure:"ssh_file_transfer_method"`
	SSHProxyHost              string        `mapstructure:"ssh_proxy_host"`
	SSHProxyPort              int           `mapstructure:"ssh_proxy_port"`
	SSHProxyUsername          string        `mapstructure:"ssh_proxy_username"`
	SSHProxyPassword          string        `mapstructure:"ssh_proxy_password"`
	SSHKeepAliveInterval      time.Duration `mapstructure:"ssh_keep_alive_interval"`
	SSHReadWriteTimeout       time.Duration `mapstructure:"ssh_read_write_timeout"`
	// SSH Internals
	SSHPublicKey  []byte
	SSHPrivateKey []byte

	// WinRM
	WinRMUser               string        `mapstructure:"winrm_username"`
	WinRMPassword           string        `mapstructure:"winrm_password"`
	WinRMHost               string        `mapstructure:"winrm_host"`
	WinRMPort               int           `mapstructure:"winrm_port"`
	WinRMTimeout            time.Duration `mapstructure:"winrm_timeout"`
	WinRMUseSSL             bool          `mapstructure:"winrm_use_ssl"`
	WinRMInsecure           bool          `mapstructure:"winrm_insecure"`
	WinRMUseNTLM            bool          `mapstructure:"winrm_use_ntlm"`
	WinRMTransportDecorator func() winrm.Transporter
}

// ReadSSHPrivateKeyFile returns the SSH private key bytes
func (c *Config) ReadSSHPrivateKeyFile() ([]byte, error) {
	var privateKey []byte

	if c.SSHPrivateKeyFile != "" {
		keyPath, err := homedir.Expand(c.SSHPrivateKeyFile)
		if err != nil {
			return privateKey, fmt.Errorf("Error expanding path for SSH private key: %s", err)
		}
		privateKey, err = ioutil.ReadFile(keyPath)
		if err != nil {
			return privateKey, fmt.Errorf("Error on reading SSH private key: %s", err)
		}
	}
	return privateKey, nil
}

// SSHConfigFunc returns a function that can be used for the SSH communicator
// config for connecting to the instance created over SSH using the private key
// or password.
func (c *Config) SSHConfigFunc() func(multistep.StateBag) (*ssh.ClientConfig, error) {
	return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
		sshConfig := &ssh.ClientConfig{
			User:            c.SSHUsername,
			HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		}

		if c.SSHAgentAuth {
			authSock := os.Getenv("SSH_AUTH_SOCK")
			if authSock == "" {
				return nil, fmt.Errorf("SSH_AUTH_SOCK is not set")
			}

			sshAgent, err := net.Dial("unix", authSock)
			if err != nil {
				return nil, fmt.Errorf("Cannot connect to SSH Agent socket %q: %s", authSock, err)
			}

			sshConfig.Auth = append(sshConfig.Auth, ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers))
		}

		var privateKeys [][]byte
		if c.SSHPrivateKeyFile != "" {
			privateKey, err := c.ReadSSHPrivateKeyFile()
			if err != nil {
				return nil, err
			}
			privateKeys = append(privateKeys, privateKey)
		}

		// aws,alicloud,cloudstack,digitalOcean,oneAndOne,openstack,oracle & profitbricks key
		if iKey, hasKey := state.GetOk("privateKey"); hasKey {
			privateKeys = append(privateKeys, []byte(iKey.(string)))
		}

		if len(c.SSHPrivateKey) != 0 {
			privateKeys = append(privateKeys, c.SSHPrivateKey)
		}

		for _, key := range privateKeys {
			signer, err := ssh.ParsePrivateKey(key)
			if err != nil {
				return nil, fmt.Errorf("Error setting up SSH config: %s", err)
			}
			sshConfig.Auth = append(sshConfig.Auth, ssh.PublicKeys(signer))
		}

		if c.SSHPassword != "" {
			sshConfig.Auth = append(sshConfig.Auth,
				ssh.Password(c.SSHPassword),
				ssh.KeyboardInteractive(packerssh.PasswordKeyboardInteractive(c.SSHPassword)),
			)
		}
		return sshConfig, nil
	}
}

// Port returns the port that will be used for access based on config.
func (c *Config) Port() int {
	switch c.Type {
	case "ssh":
		return c.SSHPort
	case "winrm":
		return c.WinRMPort
	default:
		return 0
	}
}

// Host returns the port that will be used for access based on config.
func (c *Config) Host() string {
	switch c.Type {
	case "ssh":
		return c.SSHHost
	case "winrm":
		return c.WinRMHost
	default:
		return ""
	}
}

// User returns the port that will be used for access based on config.
func (c *Config) User() string {
	switch c.Type {
	case "ssh":
		return c.SSHUsername
	case "winrm":
		return c.WinRMUser
	default:
		return ""
	}
}

// Password returns the port that will be used for access based on config.
func (c *Config) Password() string {
	switch c.Type {
	case "ssh":
		return c.SSHPassword
	case "winrm":
		return c.WinRMPassword
	default:
		return ""
	}
}

func (c *Config) Prepare(ctx *interpolate.Context) []error {
	if c.Type == "" {
		c.Type = "ssh"
	}

	var errs []error
	switch c.Type {
	case "ssh":
		if es := c.prepareSSH(ctx); len(es) > 0 {
			errs = append(errs, es...)
		}
	case "winrm":
		if es := c.prepareWinRM(ctx); len(es) > 0 {
			errs = append(errs, es...)
		}
	case "docker", "none":
		break
	default:
		return []error{fmt.Errorf("Communicator type %s is invalid", c.Type)}
	}

	return errs
}

func (c *Config) prepareSSH(ctx *interpolate.Context) []error {
	if c.SSHPort == 0 {
		c.SSHPort = 22
	}

	if c.SSHTimeout == 0 {
		c.SSHTimeout = 5 * time.Minute
	}

	if c.SSHKeepAliveInterval == 0 {
		c.SSHKeepAliveInterval = 5 * time.Second
	}

	if c.SSHHandshakeAttempts == 0 {
		c.SSHHandshakeAttempts = 10
	}

	if c.SSHBastionHost != "" {
		if c.SSHBastionPort == 0 {
			c.SSHBastionPort = 22
		}

		if c.SSHBastionPrivateKeyFile == "" && c.SSHPrivateKeyFile != "" {
			c.SSHBastionPrivateKeyFile = c.SSHPrivateKeyFile
		}
	}

	if c.SSHProxyHost != "" {
		if c.SSHProxyPort == 0 {
			c.SSHProxyPort = 1080
		}
	}

	if c.SSHFileTransferMethod == "" {
		c.SSHFileTransferMethod = "scp"
	}

	// Validation
	var errs []error
	if c.SSHUsername == "" {
		errs = append(errs, errors.New("An ssh_username must be specified\n  Note: some builders used to default ssh_username to \"root\"."))
	}

	if c.SSHPrivateKeyFile != "" {
		path, err := homedir.Expand(c.SSHPrivateKeyFile)
		if err != nil {
			errs = append(errs, fmt.Errorf(
				"ssh_private_key_file is invalid: %s", err))
		} else if _, err := os.Stat(path); err != nil {
			errs = append(errs, fmt.Errorf(
				"ssh_private_key_file is invalid: %s", err))
		} else if _, err := helperssh.FileSigner(path); err != nil {
			errs = append(errs, fmt.Errorf(
				"ssh_private_key_file is invalid: %s", err))
		}
	}

	if c.SSHBastionHost != "" && !c.SSHBastionAgentAuth {
		if c.SSHBastionPassword == "" && c.SSHBastionPrivateKeyFile == "" {
			errs = append(errs, errors.New(
				"ssh_bastion_password or ssh_bastion_private_key_file must be specified"))
		} else if c.SSHBastionPrivateKeyFile != "" {
			path, err := homedir.Expand(c.SSHBastionPrivateKeyFile)
			if err != nil {
				errs = append(errs, fmt.Errorf(
					"ssh_bastion_private_key_file is invalid: %s", err))
			} else if _, err := os.Stat(path); err != nil {
				errs = append(errs, fmt.Errorf(
					"ssh_bastion_private_key_file is invalid: %s", err))
			} else if _, err := helperssh.FileSigner(path); err != nil {
				errs = append(errs, fmt.Errorf(
					"ssh_bastion_private_key_file is invalid: %s", err))
			}
		}
	}

	if c.SSHFileTransferMethod != "scp" && c.SSHFileTransferMethod != "sftp" {
		errs = append(errs, fmt.Errorf(
			"ssh_file_transfer_method ('%s') is invalid, valid methods: sftp, scp",
			c.SSHFileTransferMethod))
	}

	if c.SSHBastionHost != "" && c.SSHProxyHost != "" {
		errs = append(errs, errors.New("please specify either ssh_bastion_host or ssh_proxy_host, not both"))
	}

	return errs
}

func (c *Config) prepareWinRM(ctx *interpolate.Context) []error {
	if c.WinRMPort == 0 && c.WinRMUseSSL {
		c.WinRMPort = 5986
	} else if c.WinRMPort == 0 {
		c.WinRMPort = 5985
	}

	if c.WinRMTimeout == 0 {
		c.WinRMTimeout = 30 * time.Minute
	}

	if c.WinRMUseNTLM == true {
		c.WinRMTransportDecorator = func() winrm.Transporter { return &winrm.ClientNTLM{} }
	}

	var errs []error
	if c.WinRMUser == "" {
		errs = append(errs, errors.New("winrm_username must be specified."))
	}

	return errs
}
<|endoftext|>


<commit_before>package httputils

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"runtime"
	"strconv"
	"time"

	// Below is a port of the exponential backoff implementation from
	// google-http-java-client.
	"github.com/cenkalti/backoff"
	"github.com/fiorix/go-web/autogzip"
	"github.com/skia-dev/glog"
	"go.skia.org/infra/go/metrics2"
	"go.skia.org/infra/go/timer"
	"go.skia.org/infra/go/util"
)

const (
	DIAL_TIMEOUT    = time.Minute
	REQUEST_TIMEOUT = 5 * time.Minute

	// Exponential backoff defaults.
	INITIAL_INTERVAL     = 500 * time.Millisecond
	RANDOMIZATION_FACTOR = 0.5
	BACKOFF_MULTIPLIER   = 1.5
	MAX_INTERVAL         = 60 * time.Second
	MAX_ELAPSED_TIME     = 5 * time.Minute

	MAX_BYTES_IN_RESPONSE_BODY = 10 * 1024 //10 KB
)

// DialTimeout is a dialer that sets a timeout.
func DialTimeout(network, addr string) (net.Conn, error) {
	return net.DialTimeout(network, addr, DIAL_TIMEOUT)
}

// NewTimeoutClient creates a new http.Client with both a dial timeout and a
// request timeout.
func NewTimeoutClient() *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			Dial: DialTimeout,
		},
		Timeout: REQUEST_TIMEOUT,
	}
}

type BackOffConfig struct {
	initialInterval     time.Duration
	maxInterval         time.Duration
	maxElapsedTime      time.Duration
	randomizationFactor float64
	backOffMultiplier   float64
}

// NewBackOffTransport creates a BackOffTransport with default values. Look at
// NewConfiguredBackOffTransport for an example of how the values impact behavior.
func NewBackOffTransport() http.RoundTripper {
	config := &BackOffConfig{
		initialInterval:     INITIAL_INTERVAL,
		maxInterval:         MAX_INTERVAL,
		maxElapsedTime:      MAX_ELAPSED_TIME,
		randomizationFactor: RANDOMIZATION_FACTOR,
		backOffMultiplier:   BACKOFF_MULTIPLIER,
	}
	return NewConfiguredBackOffTransport(config)
}

type BackOffTransport struct {
	http.Transport
	backOffConfig *BackOffConfig
}

type ResponsePagination struct {
	Offset int `json:"offset"`
	Size   int `json:"size"`
	Total  int `json:"total"`
}

// NewBackOffTransport creates a BackOffTransport with the specified config.
//
// Example: The default retry_interval is .5 seconds, default randomization_factor
// is 0.5, default multiplier is 1.5 and the default max_interval is 1 minute. For
// 10 tries the sequence will be (values in seconds) and assuming we go over the
// max_elapsed_time on the 10th try:
//
//  request#     retry_interval     randomized_interval
//  1             0.5                [0.25,   0.75]
//  2             0.75               [0.375,  1.125]
//  3             1.125              [0.562,  1.687]
//  4             1.687              [0.8435, 2.53]
//  5             2.53               [1.265,  3.795]
//  6             3.795              [1.897,  5.692]
//  7             5.692              [2.846,  8.538]
//  8             8.538              [4.269, 12.807]
//  9            12.807              [6.403, 19.210]
//  10           19.210              backoff.Stop
func NewConfiguredBackOffTransport(config *BackOffConfig) http.RoundTripper {
	return &BackOffTransport{
		Transport:     http.Transport{Dial: DialTimeout},
		backOffConfig: config,
	}
}

// RoundTrip implements the RoundTripper interface.
func (t *BackOffTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	// Initialize the exponential backoff client.
	backOffClient := &backoff.ExponentialBackOff{
		InitialInterval:     t.backOffConfig.initialInterval,
		RandomizationFactor: t.backOffConfig.randomizationFactor,
		Multiplier:          t.backOffConfig.backOffMultiplier,
		MaxInterval:         t.backOffConfig.maxInterval,
		MaxElapsedTime:      t.backOffConfig.maxElapsedTime,
		Clock:               backoff.SystemClock,
	}
	// Make a copy of the request's Body so that we can reuse it if the request
	// needs to be backed off and retried.
	bodyBuf := bytes.Buffer{}
	if req.Body != nil {
		if _, err := bodyBuf.ReadFrom(req.Body); err != nil {
			return nil, fmt.Errorf("Failed to read request body: %v", err)
		}
	}

	var resp *http.Response
	var err error
	roundTripOp := func() error {
		if req.Body != nil {
			req.Body = ioutil.NopCloser(bytes.NewBufferString(bodyBuf.String()))
		}
		resp, err = t.Transport.RoundTrip(req)
		if err != nil {
			return fmt.Errorf("Error while making the round trip: %s", err)
		}
		if resp != nil {
			if resp.StatusCode >= 500 && resp.StatusCode <= 599 {
				// We can't close the resp.Body on success, so we must do it in each of the failure cases.
				return fmt.Errorf("Got server error statuscode %d while making the HTTP %s request to %s\nResponse: %s", resp.StatusCode, req.Method, req.URL, readAndClose(resp.Body))
			} else if resp.StatusCode < 200 || resp.StatusCode > 299 {
				// We can't close the resp.Body on success, so we must do it in each of the failure cases.
				// Stop backing off if there are non server errors.
				backOffClient.MaxElapsedTime = backoff.Stop
				return fmt.Errorf("Got non server error statuscode %d while making the HTTP %s request to %s\nResponse: %s", resp.StatusCode, req.Method, req.URL, readAndClose(resp.Body))
			}
		}
		return nil
	}
	notifyFunc := func(err error, wait time.Duration) {
		glog.Warningf("Got error: %s. Retrying HTTP request after sleeping for %s", err, wait)
	}

	if err := backoff.RetryNotify(roundTripOp, backOffClient, notifyFunc); err != nil {
		return nil, fmt.Errorf("HTTP request failed inspite of exponential backoff: %s", err)
	}
	return resp, nil
}

// readAndClose reads the content of a ReadCloser (e.g. http Response), and returns it as a string.
// If the response was nil or there was a problem, it will return empty string.  The reader,
//if non-null, will be closed by this function.
func readAndClose(r io.ReadCloser) string {
	if r != nil {
		defer util.Close(r)
		if b, err := ioutil.ReadAll(io.LimitReader(r, MAX_BYTES_IN_RESPONSE_BODY)); err != nil {
			glog.Warningf("There was a potential problem reading the response body: %s", err)
		} else {
			return fmt.Sprintf("%q", string(b))
		}
	}
	return ""
}

// TODO(stephana): Remove 'r' from the argument list since it's not used. It would
// be also useful if we could specify a return status explicitly.

// ReportError formats an HTTP error response and also logs the detailed error message.
// The message parameter is returned in the HTTP response. If it is not provided then
// "Unknown error" will be returned instead.
func ReportError(w http.ResponseWriter, r *http.Request, err error, message string) {
	glog.Errorln(message, err)
	if err != io.ErrClosedPipe {
		httpErrMsg := message
		if message == "" {
			httpErrMsg = "Unknown error"
		}
		http.Error(w, httpErrMsg, 500)
	}
}

// responseProxy implements http.ResponseWriter and records the status codes.
type responseProxy struct {
	http.ResponseWriter
	wroteHeader bool
}

func (rp *responseProxy) WriteHeader(code int) {
	if !rp.wroteHeader {
		glog.Infof("Response Code: %d", code)
		metrics2.GetCounter("http.response", map[string]string{"statuscode": strconv.Itoa(code)}).Inc(1)
		rp.ResponseWriter.WriteHeader(code)
		rp.wroteHeader = true
	}
}

// recordResponse returns a wrapped http.Handler that records the status codes of the
// responses.
//
// Note that if a handler doesn't explicitly set a response code and goes with
// the default of 200 then this will never record anything.
func recordResponse(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		h.ServeHTTP(&responseProxy{ResponseWriter: w}, r)
	})
}

// LoggingGzipRequestResponse records parts of the request and the response to
// the logs and gzips responses when appropriate.
func LoggingGzipRequestResponse(h http.Handler) http.Handler {
	return autogzip.Handle(LoggingRequestResponse(h))
}

// LoggingRequestResponse records parts of the request and the response to the logs.
func LoggingRequestResponse(h http.Handler) http.Handler {
	// Closure to capture the request.
	f := func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if err := recover(); err != nil {
				const size = 64 << 10
				buf := make([]byte, size)
				buf = buf[:runtime.Stack(buf, false)]
				glog.Errorf("panic serving %v: %v\n%s", r.URL.Path, err, buf)
			}
		}()
		defer timer.New(fmt.Sprintf("Request: %s %s %#v Content Length: %d Latency:", r.URL.Path, r.Method, r.URL, r.ContentLength)).Stop()
		h.ServeHTTP(w, r)
	}

	return recordResponse(http.HandlerFunc(f))
}

// MakeResourceHandler is an HTTP handler function designed for serving files.
func MakeResourceHandler(resourcesDir string) func(http.ResponseWriter, *http.Request) {
	fileServer := http.FileServer(http.Dir(resourcesDir))
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Cache-Control", "max-age=300")
		fileServer.ServeHTTP(w, r)
	}
}

// CorsHandler is an HTTP handler function which adds the necessary header for CORS.
func CorsHandler(h func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Access-Control-Allow-Origin", "*")
		h(w, r)
	}
}

// PaginationParams is helper function to extract pagination parameters from a
// URL query string. It assumes that 'offset' and 'size' are the query parameters
// used for pagination. It parses the values and returns an error if they are
// not integers. If the params are not set the defaults are proviced.
// Further it ensures that size is never above max size.
func PaginationParams(query url.Values, defaultOffset, defaultSize, maxSize int) (int, int, error) {
	size, err := getPositiveInt(query, "size", defaultSize)
	if err != nil {
		return 0, 0, err
	}

	offset, err := getPositiveInt(query, "offset", defaultOffset)
	if err != nil {
		return 0, 0, err
	}

	return offset, util.MinInt(size, maxSize), nil
}

// getPositiveInt parses the param in query and ensures it is >= 0 using
// default value when necessary.
func getPositiveInt(query url.Values, param string, defaultVal int) (int, error) {
	var val int
	var err error
	if valStr := query.Get(param); valStr == "" {
		return defaultVal, nil
	} else {
		val, err = strconv.Atoi(valStr)
		if err != nil {
			return 0, fmt.Errorf("Not a valid integer value.")
		}
	}
	if val < 0 {
		return defaultVal, nil
	}
	return val, nil
}
<commit_msg>Log request URL before handling the request, to aid in debugging.<commit_after>package httputils

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"runtime"
	"strconv"
	"time"

	// Below is a port of the exponential backoff implementation from
	// google-http-java-client.
	"github.com/cenkalti/backoff"
	"github.com/fiorix/go-web/autogzip"
	"github.com/skia-dev/glog"
	"go.skia.org/infra/go/metrics2"
	"go.skia.org/infra/go/timer"
	"go.skia.org/infra/go/util"
)

const (
	DIAL_TIMEOUT    = time.Minute
	REQUEST_TIMEOUT = 5 * time.Minute

	// Exponential backoff defaults.
	INITIAL_INTERVAL     = 500 * time.Millisecond
	RANDOMIZATION_FACTOR = 0.5
	BACKOFF_MULTIPLIER   = 1.5
	MAX_INTERVAL         = 60 * time.Second
	MAX_ELAPSED_TIME     = 5 * time.Minute

	MAX_BYTES_IN_RESPONSE_BODY = 10 * 1024 //10 KB
)

// DialTimeout is a dialer that sets a timeout.
func DialTimeout(network, addr string) (net.Conn, error) {
	return net.DialTimeout(network, addr, DIAL_TIMEOUT)
}

// NewTimeoutClient creates a new http.Client with both a dial timeout and a
// request timeout.
func NewTimeoutClient() *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			Dial: DialTimeout,
		},
		Timeout: REQUEST_TIMEOUT,
	}
}

type BackOffConfig struct {
	initialInterval     time.Duration
	maxInterval         time.Duration
	maxElapsedTime      time.Duration
	randomizationFactor float64
	backOffMultiplier   float64
}

// NewBackOffTransport creates a BackOffTransport with default values. Look at
// NewConfiguredBackOffTransport for an example of how the values impact behavior.
func NewBackOffTransport() http.RoundTripper {
	config := &BackOffConfig{
		initialInterval:     INITIAL_INTERVAL,
		maxInterval:         MAX_INTERVAL,
		maxElapsedTime:      MAX_ELAPSED_TIME,
		randomizationFactor: RANDOMIZATION_FACTOR,
		backOffMultiplier:   BACKOFF_MULTIPLIER,
	}
	return NewConfiguredBackOffTransport(config)
}

type BackOffTransport struct {
	http.Transport
	backOffConfig *BackOffConfig
}

type ResponsePagination struct {
	Offset int `json:"offset"`
	Size   int `json:"size"`
	Total  int `json:"total"`
}

// NewBackOffTransport creates a BackOffTransport with the specified config.
//
// Example: The default retry_interval is .5 seconds, default randomization_factor
// is 0.5, default multiplier is 1.5 and the default max_interval is 1 minute. For
// 10 tries the sequence will be (values in seconds) and assuming we go over the
// max_elapsed_time on the 10th try:
//
//  request#     retry_interval     randomized_interval
//  1             0.5                [0.25,   0.75]
//  2             0.75               [0.375,  1.125]
//  3             1.125              [0.562,  1.687]
//  4             1.687              [0.8435, 2.53]
//  5             2.53               [1.265,  3.795]
//  6             3.795              [1.897,  5.692]
//  7             5.692              [2.846,  8.538]
//  8             8.538              [4.269, 12.807]
//  9            12.807              [6.403, 19.210]
//  10           19.210              backoff.Stop
func NewConfiguredBackOffTransport(config *BackOffConfig) http.RoundTripper {
	return &BackOffTransport{
		Transport:     http.Transport{Dial: DialTimeout},
		backOffConfig: config,
	}
}

// RoundTrip implements the RoundTripper interface.
func (t *BackOffTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	// Initialize the exponential backoff client.
	backOffClient := &backoff.ExponentialBackOff{
		InitialInterval:     t.backOffConfig.initialInterval,
		RandomizationFactor: t.backOffConfig.randomizationFactor,
		Multiplier:          t.backOffConfig.backOffMultiplier,
		MaxInterval:         t.backOffConfig.maxInterval,
		MaxElapsedTime:      t.backOffConfig.maxElapsedTime,
		Clock:               backoff.SystemClock,
	}
	// Make a copy of the request's Body so that we can reuse it if the request
	// needs to be backed off and retried.
	bodyBuf := bytes.Buffer{}
	if req.Body != nil {
		if _, err := bodyBuf.ReadFrom(req.Body); err != nil {
			return nil, fmt.Errorf("Failed to read request body: %v", err)
		}
	}

	var resp *http.Response
	var err error
	roundTripOp := func() error {
		if req.Body != nil {
			req.Body = ioutil.NopCloser(bytes.NewBufferString(bodyBuf.String()))
		}
		resp, err = t.Transport.RoundTrip(req)
		if err != nil {
			return fmt.Errorf("Error while making the round trip: %s", err)
		}
		if resp != nil {
			if resp.StatusCode >= 500 && resp.StatusCode <= 599 {
				// We can't close the resp.Body on success, so we must do it in each of the failure cases.
				return fmt.Errorf("Got server error statuscode %d while making the HTTP %s request to %s\nResponse: %s", resp.StatusCode, req.Method, req.URL, readAndClose(resp.Body))
			} else if resp.StatusCode < 200 || resp.StatusCode > 299 {
				// We can't close the resp.Body on success, so we must do it in each of the failure cases.
				// Stop backing off if there are non server errors.
				backOffClient.MaxElapsedTime = backoff.Stop
				return fmt.Errorf("Got non server error statuscode %d while making the HTTP %s request to %s\nResponse: %s", resp.StatusCode, req.Method, req.URL, readAndClose(resp.Body))
			}
		}
		return nil
	}
	notifyFunc := func(err error, wait time.Duration) {
		glog.Warningf("Got error: %s. Retrying HTTP request after sleeping for %s", err, wait)
	}

	if err := backoff.RetryNotify(roundTripOp, backOffClient, notifyFunc); err != nil {
		return nil, fmt.Errorf("HTTP request failed inspite of exponential backoff: %s", err)
	}
	return resp, nil
}

// readAndClose reads the content of a ReadCloser (e.g. http Response), and returns it as a string.
// If the response was nil or there was a problem, it will return empty string.  The reader,
//if non-null, will be closed by this function.
func readAndClose(r io.ReadCloser) string {
	if r != nil {
		defer util.Close(r)
		if b, err := ioutil.ReadAll(io.LimitReader(r, MAX_BYTES_IN_RESPONSE_BODY)); err != nil {
			glog.Warningf("There was a potential problem reading the response body: %s", err)
		} else {
			return fmt.Sprintf("%q", string(b))
		}
	}
	return ""
}

// TODO(stephana): Remove 'r' from the argument list since it's not used. It would
// be also useful if we could specify a return status explicitly.

// ReportError formats an HTTP error response and also logs the detailed error message.
// The message parameter is returned in the HTTP response. If it is not provided then
// "Unknown error" will be returned instead.
func ReportError(w http.ResponseWriter, r *http.Request, err error, message string) {
	glog.Errorln(message, err)
	if err != io.ErrClosedPipe {
		httpErrMsg := message
		if message == "" {
			httpErrMsg = "Unknown error"
		}
		http.Error(w, httpErrMsg, 500)
	}
}

// responseProxy implements http.ResponseWriter and records the status codes.
type responseProxy struct {
	http.ResponseWriter
	wroteHeader bool
}

func (rp *responseProxy) WriteHeader(code int) {
	if !rp.wroteHeader {
		glog.Infof("Response Code: %d", code)
		metrics2.GetCounter("http.response", map[string]string{"statuscode": strconv.Itoa(code)}).Inc(1)
		rp.ResponseWriter.WriteHeader(code)
		rp.wroteHeader = true
	}
}

// recordResponse returns a wrapped http.Handler that records the status codes of the
// responses.
//
// Note that if a handler doesn't explicitly set a response code and goes with
// the default of 200 then this will never record anything.
func recordResponse(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		h.ServeHTTP(&responseProxy{ResponseWriter: w}, r)
	})
}

// LoggingGzipRequestResponse records parts of the request and the response to
// the logs and gzips responses when appropriate.
func LoggingGzipRequestResponse(h http.Handler) http.Handler {
	return autogzip.Handle(LoggingRequestResponse(h))
}

// LoggingRequestResponse records parts of the request and the response to the logs.
func LoggingRequestResponse(h http.Handler) http.Handler {
	// Closure to capture the request.
	f := func(w http.ResponseWriter, r *http.Request) {
		glog.Infof("Incoming request: %s %s %#v ", r.URL.Path, r.Method, *(r.URL))
		defer func() {
			if err := recover(); err != nil {
				const size = 64 << 10
				buf := make([]byte, size)
				buf = buf[:runtime.Stack(buf, false)]
				glog.Errorf("panic serving %v: %v\n%s", r.URL.Path, err, buf)
			}
		}()
		defer timer.New(fmt.Sprintf("Request: %s Latency:", r.URL.Path)).Stop()
		h.ServeHTTP(w, r)
	}

	return recordResponse(http.HandlerFunc(f))
}

// MakeResourceHandler is an HTTP handler function designed for serving files.
func MakeResourceHandler(resourcesDir string) func(http.ResponseWriter, *http.Request) {
	fileServer := http.FileServer(http.Dir(resourcesDir))
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Cache-Control", "max-age=300")
		fileServer.ServeHTTP(w, r)
	}
}

// CorsHandler is an HTTP handler function which adds the necessary header for CORS.
func CorsHandler(h func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Access-Control-Allow-Origin", "*")
		h(w, r)
	}
}

// PaginationParams is helper function to extract pagination parameters from a
// URL query string. It assumes that 'offset' and 'size' are the query parameters
// used for pagination. It parses the values and returns an error if they are
// not integers. If the params are not set the defaults are proviced.
// Further it ensures that size is never above max size.
func PaginationParams(query url.Values, defaultOffset, defaultSize, maxSize int) (int, int, error) {
	size, err := getPositiveInt(query, "size", defaultSize)
	if err != nil {
		return 0, 0, err
	}

	offset, err := getPositiveInt(query, "offset", defaultOffset)
	if err != nil {
		return 0, 0, err
	}

	return offset, util.MinInt(size, maxSize), nil
}

// getPositiveInt parses the param in query and ensures it is >= 0 using
// default value when necessary.
func getPositiveInt(query url.Values, param string, defaultVal int) (int, error) {
	var val int
	var err error
	if valStr := query.Get(param); valStr == "" {
		return defaultVal, nil
	} else {
		val, err = strconv.Atoi(valStr)
		if err != nil {
			return 0, fmt.Errorf("Not a valid integer value.")
		}
	}
	if val < 0 {
		return defaultVal, nil
	}
	return val, nil
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/geobe/gostip/go/controller"
	"github.com/geobe/gostip/go/model"
	"net/http"
	"log"
	"golang.org/x/crypto/acme/autocert"
	"crypto/tls"
	"flag"
)

// Server Ports, zu denen  Ports 80 und 443
// vom Internet Router (z.B. FritzBox) mit Port Forwarding weitergeleitet wird
const httpport = ":8070"
const tlsport = ":8443"

func main() {
	// read command line parameters
	account := flag.String("mail", "", "a mail account")
	mailpw := flag.String("mailpw", "", "password of the mail account")
	cfgfile := flag.String("cfgfile", "", "name of config file")
	flag.Parse()
	// setup mailer info
	model.SetMailer(*account, *mailpw)
	// prepare database
	model.Setup(*cfgfile)
	db := model.Db()
	defer db.Close()
//	model.ClearTestDb(db)
	model.InitProdDb(db)

	// mux verwaltet die Routen
	mux := controller.SetRouting()

	// die zugelassenen host namen
	allowedHosts := []string{"dkfai.spdns.org", "geobe.spdns.org"}

	// der Verwalter der LetsEncrypt Zertifikate
	certManager := autocert.Manager{
		Prompt:     autocert.AcceptTOS,
		HostPolicy: autocert.HostWhitelist(allowedHosts...), //your domain here
		Email: 	    "georg.beier@fh-zwickau.de",
		Cache:      autocert.DirCache("certs"), //folder for storing certificates
	}

	// konfiguriere server
	server := &http.Server{
		Addr:    "0.0.0.0" + tlsport,
		TLSConfig: &tls.Config{
			GetCertificate: certManager.GetCertificate,
		},
		Handler: mux,
	}

	// switching redirect handler
	handlerSwitch := &controller.HandlerSwitch{
		Mux:    mux,
		Redirect: http.HandlerFunc(controller.RedirectHTTP),
		AllowedHosts: allowedHosts,
	}

	// konfiguriere redirect server
	redirectserver := &http.Server{
		Addr:    "0.0.0.0" + httpport,
		Handler: handlerSwitch, //http.HandlerFunc(RedirectHTTP),
	}
	// starte den redirect server auf HTTP
	go redirectserver.ListenAndServe()

	// und starte den primären server auf HTTPS
	log.Printf("server starting\n")
	server.ListenAndServeTLS("", "")
}
<commit_msg>changed devserv ports to 18070 and 18443<commit_after>package main

import (
	"github.com/geobe/gostip/go/controller"
	"github.com/geobe/gostip/go/model"
	"net/http"
	"log"
	"golang.org/x/crypto/acme/autocert"
	"crypto/tls"
	"flag"
)

// Server Ports, zu denen  Ports 80 und 443
// vom Internet Router (z.B. FritzBox) mit Port Forwarding weitergeleitet wird
const httpport = ":18070"
const tlsport = ":18443"

func main() {
	// read command line parameters
	account := flag.String("mail", "", "a mail account")
	mailpw := flag.String("mailpw", "", "password of the mail account")
	cfgfile := flag.String("cfgfile", "", "name of config file")
	flag.Parse()
	// setup mailer info
	model.SetMailer(*account, *mailpw)
	// prepare database
	model.Setup(*cfgfile)
	db := model.Db()
	defer db.Close()
//	model.ClearTestDb(db)
	model.InitProdDb(db)

	// mux verwaltet die Routen
	mux := controller.SetRouting()

	// die zugelassenen host namen
	allowedHosts := []string{"dkfai.spdns.org", "geobe.spdns.org"}

	// der Verwalter der LetsEncrypt Zertifikate
	certManager := autocert.Manager{
		Prompt:     autocert.AcceptTOS,
		HostPolicy: autocert.HostWhitelist(allowedHosts...), //your domain here
		Email: 	    "georg.beier@fh-zwickau.de",
		Cache:      autocert.DirCache("certs"), //folder for storing certificates
	}

	// konfiguriere server
	server := &http.Server{
		Addr:    "0.0.0.0" + tlsport,
		TLSConfig: &tls.Config{
			GetCertificate: certManager.GetCertificate,
		},
		Handler: mux,
	}

	// switching redirect handler
	handlerSwitch := &controller.HandlerSwitch{
		Mux:    mux,
		Redirect: http.HandlerFunc(controller.RedirectHTTP),
		AllowedHosts: allowedHosts,
	}

	// konfiguriere redirect server
	redirectserver := &http.Server{
		Addr:    "0.0.0.0" + httpport,
		Handler: handlerSwitch, //http.HandlerFunc(RedirectHTTP),
	}
	// starte den redirect server auf HTTP
	go redirectserver.ListenAndServe()

	// und starte den primären server auf HTTPS
	log.Printf("server starting\n")
	server.ListenAndServeTLS("", "")
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"strings"

	"github.com/orktes/orlang/ast"
	"github.com/orktes/orlang/linter"
	"github.com/orktes/orlang/scanner"

	"github.com/gopherjs/gopherjs/js"
	"github.com/orktes/orlang/analyser"
	jscodegen "github.com/orktes/orlang/codegen/js"
	"github.com/orktes/orlang/parser"
	"github.com/orktes/orlang/types"
)

func configureAnalyzer(analsr *analyser.Analyser) {
	analsr.AddExternalFunc("print", &types.SignatureType{
		ArgumentNames: []string{"str"},
		ArgumentTypes: []types.Type{&types.InterfaceType{
			Name: "buildin(stringer)",
			Functions: []struct {
				Name string
				Type *types.SignatureType
			}{
				{
					Name: "toString",
					Type: &types.SignatureType{
						ArgumentNames: []string{},
						ArgumentTypes: []types.Type{},
						ReturnType:    types.StringType,
						Extern:        true,
					},
				},
			},
		}},
		ReturnType: types.VoidType,
		Extern:     true,
	})
}

func main() {

	js.Module.Get("exports").Set("Lint", func(input string) []linter.LintIssue {
		errors, err := linter.Lint(strings.NewReader(input), configureAnalyzer)
		if err != nil {
			panic(err)
		}

		return errors
	})

	js.Module.Get("exports").Set("AutoComplete", func(input string, line int, column int) (items []map[string]interface{}) {
		scanner := scanner.NewScanner(strings.NewReader(input))
		pars := parser.NewParser(analyser.NewAutoCompleteScanner(scanner, []ast.Position{ast.Position{Line: line, Column: column}}))

		file, err := pars.Parse()
		if err != nil {
			panic(err)
		}

		var result []analyser.AutoCompleteInfo
		alys, err := analyser.New(file)
		if err != nil {
			panic(err)
		}

		configureAnalyzer(alys)

		alys.AutoCompleteInfoCallback = func(res []analyser.AutoCompleteInfo) {
			result = res
		}

		alys.Analyse()

	itemLoop:
		for _, item := range result {
			switch item.Label {
			case "+", "-", "*", "/":
				continue itemLoop
			}

			insertText := item.Label
			switch t := item.Type.(type) {
			case *types.SignatureType:
				insertText = fmt.Sprintf("%s(%s)", insertText, strings.Join(t.ArgumentNames, ", "))
			case *types.StructType:
				if item.Kind == "Class" {
					insertText = fmt.Sprintf("%s{}", insertText)
				}
			}

			items = append(items, map[string]interface{}{
				"label":         item.Label,
				"insertText":    insertText,
				"kind":          item.Kind,
				"documentation": item.Type.GetName(),
				"detail":        item.Type.GetName(),
				"type":          fmt.Sprintf("%T", item.Type),
			})
		}

		return
	})

	js.Module.Get("exports").Set("Tokenize", func(input string) []scanner.Token {
		scan := scanner.NewScanner(strings.NewReader(input))

		tokens := []scanner.Token{}
		for {
			token := scan.Scan()
			tokens = append(tokens, token)
			if token.Type == scanner.TokenTypeEOF {
				break
			}
		}

		return tokens
	})

	js.Module.Get("exports").Set("Compile", func(input string) string {
		file, err := parser.Parse(strings.NewReader(input))
		if err != nil {
			panic(err)
		}

		analyser, err := analyser.New(file)
		if err != nil {
			panic(err)
		}

		configureAnalyzer(analyser)

		var analyErr error
		analyser.Error = func(node ast.Node, msg string, fatal bool) {
			if fatal {
				errStr := fmt.Sprintf(
					"%d:%d %s",
					node.StartPos().Line+1,
					node.StartPos().Column+1,
					msg,
				)
				analyErr = errors.New(errStr)
			}
		}

		info, err := analyser.Analyse()
		if err != nil {
			panic(err)
		}

		if analyErr != nil {
			panic(err)
		}

		code := jscodegen.New(info).Generate(file)

		return string(code)
	})
}
<commit_msg>Add macros to autocomplete<commit_after>package main

import (
	"errors"
	"fmt"
	"strings"

	"github.com/orktes/orlang/ast"
	"github.com/orktes/orlang/linter"
	"github.com/orktes/orlang/scanner"

	"github.com/gopherjs/gopherjs/js"
	"github.com/orktes/orlang/analyser"
	jscodegen "github.com/orktes/orlang/codegen/js"
	"github.com/orktes/orlang/parser"
	"github.com/orktes/orlang/types"
)

func configureAnalyzer(analsr *analyser.Analyser) {
	analsr.AddExternalFunc("print", &types.SignatureType{
		ArgumentNames: []string{"str"},
		ArgumentTypes: []types.Type{&types.InterfaceType{
			Name: "buildin(stringer)",
			Functions: []struct {
				Name string
				Type *types.SignatureType
			}{
				{
					Name: "toString",
					Type: &types.SignatureType{
						ArgumentNames: []string{},
						ArgumentTypes: []types.Type{},
						ReturnType:    types.StringType,
						Extern:        true,
					},
				},
			},
		}},
		ReturnType: types.VoidType,
		Extern:     true,
	})
}

func main() {

	js.Module.Get("exports").Set("Lint", func(input string) []linter.LintIssue {
		errors, err := linter.Lint(strings.NewReader(input), configureAnalyzer)
		if err != nil {
			panic(err)
		}

		return errors
	})

	js.Module.Get("exports").Set("AutoComplete", func(input string, line int, column int) (items []map[string]interface{}) {
		scanner := scanner.NewScanner(strings.NewReader(input))
		pars := parser.NewParser(analyser.NewAutoCompleteScanner(scanner, []ast.Position{ast.Position{Line: line, Column: column}}))

		file, err := pars.Parse()
		if err != nil {
			panic(err)
		}

		var result []analyser.AutoCompleteInfo
		alys, err := analyser.New(file)
		if err != nil {
			panic(err)
		}

		configureAnalyzer(alys)

		alys.AutoCompleteInfoCallback = func(res []analyser.AutoCompleteInfo) {
			result = res
		}

		alys.Analyse()

	itemLoop:
		for _, item := range result {
			switch item.Label {
			case "+", "-", "*", "/":
				continue itemLoop
			}

			insertText := item.Label
			switch t := item.Type.(type) {
			case *types.SignatureType:
				insertText = fmt.Sprintf("%s(%s)", insertText, strings.Join(t.ArgumentNames, ", "))
			case *types.StructType:
				if item.Kind == "Class" {
					insertText = fmt.Sprintf("%s{}", insertText)
				}
			}

			items = append(items, map[string]interface{}{
				"label":         item.Label,
				"insertText":    insertText,
				"kind":          item.Kind,
				"documentation": item.Type.GetName(),
				"detail":        item.Type.GetName(),
				"type":          fmt.Sprintf("%T", item.Type),
			})
		}

		for macroName, _ := range file.Macros {
			items = append(items, map[string]interface{}{
				"label":         macroName + "!",
				"insertText":    macroName + "!",
				"kind":          "Reference",
				"documentation": "macro " + macroName,
				"detail":        "macro " + macroName,
				"type":          "macro",
			})
		}

		return
	})

	js.Module.Get("exports").Set("Tokenize", func(input string) []scanner.Token {
		scan := scanner.NewScanner(strings.NewReader(input))

		tokens := []scanner.Token{}
		for {
			token := scan.Scan()
			tokens = append(tokens, token)
			if token.Type == scanner.TokenTypeEOF {
				break
			}
		}

		return tokens
	})

	js.Module.Get("exports").Set("Compile", func(input string) string {
		file, err := parser.Parse(strings.NewReader(input))
		if err != nil {
			panic(err)
		}

		analyser, err := analyser.New(file)
		if err != nil {
			panic(err)
		}

		configureAnalyzer(analyser)

		var analyErr error
		analyser.Error = func(node ast.Node, msg string, fatal bool) {
			if fatal {
				errStr := fmt.Sprintf(
					"%d:%d %s",
					node.StartPos().Line+1,
					node.StartPos().Column+1,
					msg,
				)
				analyErr = errors.New(errStr)
			}
		}

		info, err := analyser.Analyse()
		if err != nil {
			panic(err)
		}

		if analyErr != nil {
			panic(err)
		}

		code := jscodegen.New(info).Generate(file)

		return string(code)
	})
}
<|endoftext|>


<commit_before>// Copyright 2015 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package graph

import "github.com/cayleygraph/cayley/quad"

// Transaction stores a bunch of Deltas to apply automatically on the database.
type Transaction struct {
	// Deltas stores the deltas in the right order
	Deltas []Delta
	// deltas stores the deltas in a map to avoid duplications
	deltas map[Delta]struct{}
}

// NewTransaction initialize a new transaction.
func NewTransaction() *Transaction {
	return &Transaction{Deltas: make([]Delta, 0, 10), deltas: make(map[Delta]struct{}, 10)}
}

// AddQuad adds a new quad to the transaction if it is not already present in it.
// If there is a 'remove' delta for that quad, it will remove that delta from
// the transaction instead of actually adding the quad.
func (t *Transaction) AddQuad(q quad.Quad) {
	ad, rd := createDeltas(q)

	if _, adExists := t.deltas[ad]; !adExists {
		if _, rdExists := t.deltas[rd]; rdExists {
			t.deleteDelta(rd)
		} else {
			t.addDelta(ad)
		}
	}
}

// RemoveQuad adds a quad to remove to the transaction.
// The quad will be removed from the database if it is not present in the
// transaction, otherwise it simply remove it from the transaction.
func (t *Transaction) RemoveQuad(q quad.Quad) {
	ad, rd := createDeltas(q)

	if _, adExists := t.deltas[ad]; adExists {
		t.deleteDelta(ad)
	} else {
		if _, rdExists := t.deltas[rd]; !rdExists {
			t.addDelta(rd)
		}
	}
}

func createDeltas(q quad.Quad) (ad, rd Delta) {
	ad = Delta{
		Quad:   q,
		Action: Add,
	}
	rd = Delta{
		Quad:   q,
		Action: Delete,
	}
	return
}

func (t *Transaction) addDelta(d Delta) {
	t.Deltas = append(t.Deltas, d)
	t.deltas[d] = struct{}{}
}

func (t *Transaction) deleteDelta(d Delta) {
	delete(t.deltas, d)

	for i, id := range t.Deltas {
		if id == d {
			t.Deltas = append(t.Deltas[:i], t.Deltas[i+1:]...)
			break
		}
	}
}
<commit_msg>Update transaction.go<commit_after>// Copyright 2015 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package graph

import "github.com/cayleygraph/cayley/quad"

// Transaction stores a bunch of Deltas to apply together in an atomic step on the database.
type Transaction struct {
	// Deltas stores the deltas in the right order
	Deltas []Delta
	// deltas stores the deltas in a map to avoid duplications
	deltas map[Delta]struct{}
}

// NewTransaction initialize a new transaction.
func NewTransaction() *Transaction {
	return &Transaction{Deltas: make([]Delta, 0, 10), deltas: make(map[Delta]struct{}, 10)}
}

// AddQuad adds a new quad to the transaction if it is not already present in it.
// If there is a 'remove' delta for that quad, it will remove that delta from
// the transaction instead of actually adding the quad.
func (t *Transaction) AddQuad(q quad.Quad) {
	ad, rd := createDeltas(q)

	if _, adExists := t.deltas[ad]; !adExists {
		if _, rdExists := t.deltas[rd]; rdExists {
			t.deleteDelta(rd)
		} else {
			t.addDelta(ad)
		}
	}
}

// RemoveQuad adds a quad to remove to the transaction.
// The quad will be removed from the database if it is not present in the
// transaction, otherwise it simply remove it from the transaction.
func (t *Transaction) RemoveQuad(q quad.Quad) {
	ad, rd := createDeltas(q)

	if _, adExists := t.deltas[ad]; adExists {
		t.deleteDelta(ad)
	} else {
		if _, rdExists := t.deltas[rd]; !rdExists {
			t.addDelta(rd)
		}
	}
}

func createDeltas(q quad.Quad) (ad, rd Delta) {
	ad = Delta{
		Quad:   q,
		Action: Add,
	}
	rd = Delta{
		Quad:   q,
		Action: Delete,
	}
	return
}

func (t *Transaction) addDelta(d Delta) {
	t.Deltas = append(t.Deltas, d)
	t.deltas[d] = struct{}{}
}

func (t *Transaction) deleteDelta(d Delta) {
	delete(t.deltas, d)

	for i, id := range t.Deltas {
		if id == d {
			t.Deltas = append(t.Deltas[:i], t.Deltas[i+1:]...)
			break
		}
	}
}
<|endoftext|>


<commit_before>package vxlan

import (
	"fmt"
	"net"
	"syscall"

	log "github.com/golang/glog"
	"github.com/vishvananda/netlink"

	"github.com/flynn/flynn/flannel/pkg/ip"
)

type vxlanDeviceAttrs struct {
	vni       uint32
	name      string
	vtepIndex int
	vtepAddr  net.IP
	vtepPort  int
}

type vxlanDevice struct {
	link *netlink.Vxlan
}

func newVXLANDevice(devAttrs *vxlanDeviceAttrs) (*vxlanDevice, error) {
	link := &netlink.Vxlan{
		LinkAttrs: netlink.LinkAttrs{
			Name: devAttrs.name,
		},
		VxlanId:      int(devAttrs.vni),
		VtepDevIndex: devAttrs.vtepIndex,
		SrcAddr:      devAttrs.vtepAddr,
		Port:         devAttrs.vtepPort,
		Learning:     false,
	}

	link, err := ensureLink(link)
	if err != nil {
		return nil, err
	}

	return &vxlanDevice{
		link: link,
	}, nil
}

func ensureLink(vxlan *netlink.Vxlan) (*netlink.Vxlan, error) {
	err := netlink.LinkAdd(vxlan)
	if err == syscall.EEXIST {
		// it's ok if the device already exists as long as config is similar
		existing, err := netlink.LinkByName(vxlan.Name)
		if err != nil {
			return nil, err
		}

		incompat := vxlanLinksIncompat(vxlan, existing)
		if incompat == "" {
			return existing.(*netlink.Vxlan), nil
		}

		// delete existing
		log.Warningf("%q already exists with incompatible configuration: %v; recreating device", vxlan.Name, incompat)
		if err = netlink.LinkDel(existing); err != nil {
			return nil, fmt.Errorf("failed to delete interface: %v", err)
		}

		// create new
		if err = netlink.LinkAdd(vxlan); err != nil {
			return nil, fmt.Errorf("failed to create vxlan interface: %v", err)
		}
	} else if err != nil {
		return nil, err
	}

	ifindex := vxlan.Index
	link, err := netlink.LinkByIndex(vxlan.Index)
	if err != nil {
		return nil, fmt.Errorf("can't locate created vxlan device with index %v", ifindex)
	}
	var ok bool
	if vxlan, ok = link.(*netlink.Vxlan); !ok {
		return nil, fmt.Errorf("created vxlan device with index %v is not vxlan", ifindex)
	}

	return vxlan, nil
}

func (dev *vxlanDevice) Configure(ipn ip.IP4Net) error {
	if err := setAddr4(dev.link, ipn.ToIPNet()); err != nil {
		return err
	}

	if err := netlink.LinkSetUp(dev.link); err != nil {
		return fmt.Errorf("failed to set interface %s to UP state: %s", dev.link.Attrs().Name, err)
	}

	// explicitly add a route since there might be a route for a subnet already
	// installed by Docker and then it won't get auto added
	route := netlink.Route{
		LinkIndex: dev.link.Attrs().Index,
		Scope:     netlink.SCOPE_UNIVERSE,
		Dst:       ipn.Network().ToIPNet(),
	}
	if err := netlink.RouteAdd(&route); err != nil && err != syscall.EEXIST {
		return fmt.Errorf("failed to add route (%s -> %s): %v", ipn.Network().String(), dev.link.Attrs().Name, err)
	}

	return nil
}

func (dev *vxlanDevice) Destroy() {
	netlink.LinkDel(dev.link)
}

func (dev *vxlanDevice) MACAddr() net.HardwareAddr {
	return dev.link.HardwareAddr
}

func (dev *vxlanDevice) MTU() int {
	return dev.link.MTU
}

type neigh struct {
	MAC net.HardwareAddr
	IP  ip.IP4
}

func (dev *vxlanDevice) AddL2(n neigh) error {
	log.Infof("calling NeighAdd: %v, %v", n.IP, n.MAC)
	return netlink.NeighAdd(&netlink.Neigh{
		LinkIndex:    dev.link.Index,
		State:        netlink.NUD_PERMANENT,
		Family:       syscall.AF_BRIDGE,
		Flags:        netlink.NTF_SELF,
		IP:           n.IP.ToIP(),
		HardwareAddr: n.MAC,
	})
}

func (dev *vxlanDevice) DelL2(n neigh) error {
	log.Infof("calling NeighDel: %v, %v", n.IP, n.MAC)
	return netlink.NeighDel(&netlink.Neigh{
		LinkIndex:    dev.link.Index,
		Family:       syscall.AF_BRIDGE,
		Flags:        netlink.NTF_SELF,
		IP:           n.IP.ToIP(),
		HardwareAddr: n.MAC,
	})
}

func (dev *vxlanDevice) AddL3(n neigh) error {
	log.Infof("calling NeighSet: %v, %v", n.IP, n.MAC)
	return netlink.NeighSet(&netlink.Neigh{
		LinkIndex:    dev.link.Index,
		State:        netlink.NUD_PERMANENT,
		Type:         syscall.RTN_UNICAST,
		IP:           n.IP.ToIP(),
		HardwareAddr: n.MAC,
	})
}

func (dev *vxlanDevice) DelL3(n neigh) error {
	log.Infof("calling NeighDel: %v, %v", n.IP, n.MAC)
	return netlink.NeighDel(&netlink.Neigh{
		LinkIndex:    dev.link.Index,
		State:        netlink.NUD_PERMANENT,
		Type:         syscall.RTN_UNICAST,
		IP:           n.IP.ToIP(),
		HardwareAddr: n.MAC,
	})
}

func (dev *vxlanDevice) AddRoute(subnet ip.IP4Net) error {
	route := &netlink.Route{
		Scope: netlink.SCOPE_UNIVERSE,
		Dst:   subnet.ToIPNet(),
		Gw:    subnet.IP.ToIP(),
	}

	log.Infof("calling RouteAdd: %s", subnet)
	return netlink.RouteAdd(route)
}

func (dev *vxlanDevice) DelRoute(subnet ip.IP4Net) error {
	route := &netlink.Route{
		Scope: netlink.SCOPE_UNIVERSE,
		Dst:   subnet.ToIPNet(),
		Gw:    subnet.IP.ToIP(),
	}
	log.Infof("calling RouteDel: %s", subnet)
	return netlink.RouteDel(route)
}

func vxlanLinksIncompat(l1, l2 netlink.Link) string {
	if l1.Type() != l2.Type() {
		return fmt.Sprintf("link type: %v vs %v", l1.Type(), l2.Type())
	}

	v1 := l1.(*netlink.Vxlan)
	v2 := l2.(*netlink.Vxlan)

	if v1.VxlanId != v2.VxlanId {
		return fmt.Sprintf("vni: %v vs %v", v1.VxlanId, v2.VxlanId)
	}

	if v1.VtepDevIndex > 0 && v2.VtepDevIndex > 0 && v1.VtepDevIndex != v2.VtepDevIndex {
		return fmt.Sprintf("vtep (external) interface: %v vs %v", v1.VtepDevIndex, v2.VtepDevIndex)
	}

	if len(v1.SrcAddr) > 0 && len(v2.SrcAddr) > 0 && !v1.SrcAddr.Equal(v2.SrcAddr) {
		return fmt.Sprintf("vtep (external) IP: %v vs %v", v1.SrcAddr, v2.SrcAddr)
	}

	if len(v1.Group) > 0 && len(v2.Group) > 0 && !v1.Group.Equal(v2.Group) {
		return fmt.Sprintf("group address: %v vs %v", v1.Group, v2.Group)
	}

	if v1.L2miss != v2.L2miss {
		return fmt.Sprintf("l2miss: %v vs %v", v1.L2miss, v2.L2miss)
	}

	if v1.Port > 0 && v2.Port > 0 && v1.Port != v2.Port {
		return fmt.Sprintf("port: %v vs %v", v1.Port, v2.Port)
	}

	return ""
}

// sets IP4 addr on link removing any existing ones first
func setAddr4(link *netlink.Vxlan, ipn *net.IPNet) error {
	addrs, err := netlink.AddrList(link, syscall.AF_INET)
	if err != nil {
		return err
	}

	addr := netlink.Addr{ipn, ""}
	existing := false
	for _, old := range addrs {
		if old.IPNet.String() == addr.IPNet.String() {
			existing = true
			continue
		}
		if err = netlink.AddrDel(link, &old); err != nil {
			return fmt.Errorf("failed to delete IPv4 addr %s from %s", old.String(), link.Attrs().Name)
		}
	}

	if !existing {
		if err = netlink.AddrAdd(link, &addr); err != nil {
			return fmt.Errorf("failed to add IP address %s to %s: %s", ipn.String(), link.Attrs().Name, err)
		}
	}

	return nil
}
<commit_msg>flannel: Use field names in netlink addr<commit_after>package vxlan

import (
	"fmt"
	"net"
	"syscall"

	log "github.com/golang/glog"
	"github.com/vishvananda/netlink"

	"github.com/flynn/flynn/flannel/pkg/ip"
)

type vxlanDeviceAttrs struct {
	vni       uint32
	name      string
	vtepIndex int
	vtepAddr  net.IP
	vtepPort  int
}

type vxlanDevice struct {
	link *netlink.Vxlan
}

func newVXLANDevice(devAttrs *vxlanDeviceAttrs) (*vxlanDevice, error) {
	link := &netlink.Vxlan{
		LinkAttrs: netlink.LinkAttrs{
			Name: devAttrs.name,
		},
		VxlanId:      int(devAttrs.vni),
		VtepDevIndex: devAttrs.vtepIndex,
		SrcAddr:      devAttrs.vtepAddr,
		Port:         devAttrs.vtepPort,
		Learning:     false,
	}

	link, err := ensureLink(link)
	if err != nil {
		return nil, err
	}

	return &vxlanDevice{
		link: link,
	}, nil
}

func ensureLink(vxlan *netlink.Vxlan) (*netlink.Vxlan, error) {
	err := netlink.LinkAdd(vxlan)
	if err == syscall.EEXIST {
		// it's ok if the device already exists as long as config is similar
		existing, err := netlink.LinkByName(vxlan.Name)
		if err != nil {
			return nil, err
		}

		incompat := vxlanLinksIncompat(vxlan, existing)
		if incompat == "" {
			return existing.(*netlink.Vxlan), nil
		}

		// delete existing
		log.Warningf("%q already exists with incompatible configuration: %v; recreating device", vxlan.Name, incompat)
		if err = netlink.LinkDel(existing); err != nil {
			return nil, fmt.Errorf("failed to delete interface: %v", err)
		}

		// create new
		if err = netlink.LinkAdd(vxlan); err != nil {
			return nil, fmt.Errorf("failed to create vxlan interface: %v", err)
		}
	} else if err != nil {
		return nil, err
	}

	ifindex := vxlan.Index
	link, err := netlink.LinkByIndex(vxlan.Index)
	if err != nil {
		return nil, fmt.Errorf("can't locate created vxlan device with index %v", ifindex)
	}
	var ok bool
	if vxlan, ok = link.(*netlink.Vxlan); !ok {
		return nil, fmt.Errorf("created vxlan device with index %v is not vxlan", ifindex)
	}

	return vxlan, nil
}

func (dev *vxlanDevice) Configure(ipn ip.IP4Net) error {
	if err := setAddr4(dev.link, ipn.ToIPNet()); err != nil {
		return err
	}

	if err := netlink.LinkSetUp(dev.link); err != nil {
		return fmt.Errorf("failed to set interface %s to UP state: %s", dev.link.Attrs().Name, err)
	}

	// explicitly add a route since there might be a route for a subnet already
	// installed by Docker and then it won't get auto added
	route := netlink.Route{
		LinkIndex: dev.link.Attrs().Index,
		Scope:     netlink.SCOPE_UNIVERSE,
		Dst:       ipn.Network().ToIPNet(),
	}
	if err := netlink.RouteAdd(&route); err != nil && err != syscall.EEXIST {
		return fmt.Errorf("failed to add route (%s -> %s): %v", ipn.Network().String(), dev.link.Attrs().Name, err)
	}

	return nil
}

func (dev *vxlanDevice) Destroy() {
	netlink.LinkDel(dev.link)
}

func (dev *vxlanDevice) MACAddr() net.HardwareAddr {
	return dev.link.HardwareAddr
}

func (dev *vxlanDevice) MTU() int {
	return dev.link.MTU
}

type neigh struct {
	MAC net.HardwareAddr
	IP  ip.IP4
}

func (dev *vxlanDevice) AddL2(n neigh) error {
	log.Infof("calling NeighAdd: %v, %v", n.IP, n.MAC)
	return netlink.NeighAdd(&netlink.Neigh{
		LinkIndex:    dev.link.Index,
		State:        netlink.NUD_PERMANENT,
		Family:       syscall.AF_BRIDGE,
		Flags:        netlink.NTF_SELF,
		IP:           n.IP.ToIP(),
		HardwareAddr: n.MAC,
	})
}

func (dev *vxlanDevice) DelL2(n neigh) error {
	log.Infof("calling NeighDel: %v, %v", n.IP, n.MAC)
	return netlink.NeighDel(&netlink.Neigh{
		LinkIndex:    dev.link.Index,
		Family:       syscall.AF_BRIDGE,
		Flags:        netlink.NTF_SELF,
		IP:           n.IP.ToIP(),
		HardwareAddr: n.MAC,
	})
}

func (dev *vxlanDevice) AddL3(n neigh) error {
	log.Infof("calling NeighSet: %v, %v", n.IP, n.MAC)
	return netlink.NeighSet(&netlink.Neigh{
		LinkIndex:    dev.link.Index,
		State:        netlink.NUD_PERMANENT,
		Type:         syscall.RTN_UNICAST,
		IP:           n.IP.ToIP(),
		HardwareAddr: n.MAC,
	})
}

func (dev *vxlanDevice) DelL3(n neigh) error {
	log.Infof("calling NeighDel: %v, %v", n.IP, n.MAC)
	return netlink.NeighDel(&netlink.Neigh{
		LinkIndex:    dev.link.Index,
		State:        netlink.NUD_PERMANENT,
		Type:         syscall.RTN_UNICAST,
		IP:           n.IP.ToIP(),
		HardwareAddr: n.MAC,
	})
}

func (dev *vxlanDevice) AddRoute(subnet ip.IP4Net) error {
	route := &netlink.Route{
		Scope: netlink.SCOPE_UNIVERSE,
		Dst:   subnet.ToIPNet(),
		Gw:    subnet.IP.ToIP(),
	}

	log.Infof("calling RouteAdd: %s", subnet)
	return netlink.RouteAdd(route)
}

func (dev *vxlanDevice) DelRoute(subnet ip.IP4Net) error {
	route := &netlink.Route{
		Scope: netlink.SCOPE_UNIVERSE,
		Dst:   subnet.ToIPNet(),
		Gw:    subnet.IP.ToIP(),
	}
	log.Infof("calling RouteDel: %s", subnet)
	return netlink.RouteDel(route)
}

func vxlanLinksIncompat(l1, l2 netlink.Link) string {
	if l1.Type() != l2.Type() {
		return fmt.Sprintf("link type: %v vs %v", l1.Type(), l2.Type())
	}

	v1 := l1.(*netlink.Vxlan)
	v2 := l2.(*netlink.Vxlan)

	if v1.VxlanId != v2.VxlanId {
		return fmt.Sprintf("vni: %v vs %v", v1.VxlanId, v2.VxlanId)
	}

	if v1.VtepDevIndex > 0 && v2.VtepDevIndex > 0 && v1.VtepDevIndex != v2.VtepDevIndex {
		return fmt.Sprintf("vtep (external) interface: %v vs %v", v1.VtepDevIndex, v2.VtepDevIndex)
	}

	if len(v1.SrcAddr) > 0 && len(v2.SrcAddr) > 0 && !v1.SrcAddr.Equal(v2.SrcAddr) {
		return fmt.Sprintf("vtep (external) IP: %v vs %v", v1.SrcAddr, v2.SrcAddr)
	}

	if len(v1.Group) > 0 && len(v2.Group) > 0 && !v1.Group.Equal(v2.Group) {
		return fmt.Sprintf("group address: %v vs %v", v1.Group, v2.Group)
	}

	if v1.L2miss != v2.L2miss {
		return fmt.Sprintf("l2miss: %v vs %v", v1.L2miss, v2.L2miss)
	}

	if v1.Port > 0 && v2.Port > 0 && v1.Port != v2.Port {
		return fmt.Sprintf("port: %v vs %v", v1.Port, v2.Port)
	}

	return ""
}

// sets IP4 addr on link removing any existing ones first
func setAddr4(link *netlink.Vxlan, ipn *net.IPNet) error {
	addrs, err := netlink.AddrList(link, syscall.AF_INET)
	if err != nil {
		return err
	}

	addr := netlink.Addr{IPNet: ipn}
	existing := false
	for _, old := range addrs {
		if old.IPNet.String() == addr.IPNet.String() {
			existing = true
			continue
		}
		if err = netlink.AddrDel(link, &old); err != nil {
			return fmt.Errorf("failed to delete IPv4 addr %s from %s", old.String(), link.Attrs().Name)
		}
	}

	if !existing {
		if err = netlink.AddrAdd(link, &addr); err != nil {
			return fmt.Errorf("failed to add IP address %s to %s: %s", ipn.String(), link.Attrs().Name, err)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Stefan Nyman.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package hellosign

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"

	"github.com/ajg/form"
)

const (
	baseURL                  string = "https://api.hellosign.com/v3"
	contentType                     = "content-type"
	xRatelimitLimit                 = "x-Ratelimit-Limit"
	xRatelimitLimitRemaining        = "x-Ratelimit-Limit-Remaining"
	xRateLimitReset                 = "x-Ratelimit-Reset"
)

// ListInfo struct with properties for all list epts.
type ListInfo struct {
	Page       uint64 `json:"page"`
	NumPages   uint64 `json:"num_pages"`
	NumResults uint64 `json:"num_results"`
	PageSize   uint64 `json:"page_size"`
}

// ListParms struct with options for performing list operations.
type ListParms struct {
	AccountID *string `form:"account_id,omitempty"`
	Page      *uint64 `form:"page,omitempty"`
	PageSize  *uint64 `form:"page_size,omitempty"`
	Query     *string `form:"query,omitempty"`
}

// FormField a field where some kind of action needs to be taken.
type FormField struct {
	APIID    string `json:"api_id"`
	Name     string `json:"name"`
	Type     string `json:"type"`
	X        uint64 `json:"x"`
	Y        uint64 `json:"y"`
	Width    uint64 `json:"width"`
	Height   uint64 `json:"height"`
	Required bool   `json:"required"`
}

// APIErr an error returned from the Hellosign API.
type APIErr struct {
	Code    int // HTTP response code
	Message string
	Name    string
}

// APIWarn a list of warnings returned from the HelloSign API.
type APIWarn struct {
	Code     int // HTTP response code
	Warnings []struct {
		Message string
		Name    string
	}
}

func (a APIErr) Error() string {
	return fmt.Sprintf("%s: %s", a.Name, a.Message)
}

func (a APIWarn) Error() string {
	outMsg := ""
	for _, w := range a.Warnings {
		outMsg += fmt.Sprintf("%s: %s\n", w.Name, w.Message)
	}
	return outMsg
}

type hellosign struct {
	apiKey             string
	RateLimit          uint64 // Number of requests allowed per hour
	RateLimitRemaining uint64 // Remaining number of requests this hour
	RateLimitReset     uint64 // When the limit will be reset. In seconds from epoch
	LastStatusCode     int
}

// Initializes a new Hellosign API client.
func newHellosign(apiKey string) *hellosign {
	return &hellosign{
		apiKey: apiKey,
	}
}

func (c *hellosign) perform(req *http.Request) (*http.Response, error) {
	req.Header.Add("accept", "application/json")
	req.SetBasicAuth(c.apiKey, "")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	c.LastStatusCode = resp.StatusCode
	if resp.StatusCode >= 400 {
		return nil, c.parseResponseError(resp)
	}
	for _, hk := range []string{xRatelimitLimit, xRatelimitLimitRemaining, xRateLimitReset} {
		hv := resp.Header.Get(hk)
		if hv == "" {
			continue
		}
		hvui, pErr := strconv.ParseUint(hv, 10, 64)
		if pErr != nil {
			continue
		}
		switch hk {
		case xRatelimitLimit:
			c.RateLimit = hvui
		case xRatelimitLimitRemaining:
			c.RateLimitRemaining = hvui
		case xRateLimitReset:
			c.RateLimitReset = hvui
		}
	}
	return resp, err
}

func (c *hellosign) parseResponseError(resp *http.Response) error {
	e := &struct {
		Err struct {
			Msg  *string `json:"error_msg"`
			Name *string `json:"error_name"`
		} `json:"error"`
	}{}
	w := &struct {
		Warnings []struct {
			Msg  *string `json:"warning_msg"`
			Name *string `json:"warning_name"`
		} `json:"warnings"`
	}{}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal(b, e)
	if err != nil {
		return err
	}
	if e.Err.Name != nil {
		return APIErr{Code: resp.StatusCode, Message: *e.Err.Msg, Name: *e.Err.Name}
	}
	err = json.Unmarshal(b, w)
	if err != nil {
		return err
	}
	if len(w.Warnings) == 0 {
		return errors.New("Could not parse response error or warning")
	}
	retErr := APIWarn{}
	warns := []struct {
		Name    string
		Message string
	}{}
	for _, w := range w.Warnings {
		warns = append(warns, struct {
			Name    string
			Message string
		}{
			Name:    *w.Name,
			Message: *w.Msg,
		})
	}
	return retErr
}

func (c *hellosign) parseResponse(resp *http.Response, dst interface{}) error {
	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		d := json.NewDecoder(resp.Body)
		d.UseNumber()
		return d.Decode(dst)
	}
	return errors.New("Status code invalid")
}

func (c *hellosign) postForm(ept string, o interface{}) (*http.Response, error) {
	v := ""
	if o != nil {
		encoded, err := form.EncodeToString(o)
		if err != nil {
			return nil, err
		}
		v = encoded
	}
	req, err := http.NewRequest(http.MethodPost, c.getEptURL(ept), strings.NewReader(v))
	if err != nil {
		return nil, err
	}
	req.Header.Add(contentType, "application/x-www-form-urlencoded")
	return c.perform(req)
}

func (c *hellosign) postFormAndParse(ept string, inp, dst interface{}) (err error) {
	resp, err := c.postForm(ept, inp)
	if err != nil {
		return err
	}
	defer func() { err = resp.Body.Close() }()
	return c.parseResponse(resp, dst)
}

func (c *hellosign) postEmptyExpect(ept string, expected int) (ok bool, err error) {
	resp, err := c.postForm(ept, nil)
	if err != nil {
		return false, err
	}
	defer func() { err = resp.Body.Close() }()
	if resp.StatusCode != expected {
		return false, errors.New(resp.Status)
	}
	return true, nil
}

func (c *hellosign) delete(ept string) (*http.Response, error) {
	req, err := http.NewRequest(http.MethodDelete, c.getEptURL(ept), nil)
	if err != nil {
		return nil, err
	}
	return c.perform(req)
}

// GetEptURL returns the full HelloSign api url for a given endpoint.
func GetEptURL(ept string) string {
	return fmt.Sprintf("%s/%s", baseURL, ept)
}

func (c *hellosign) getEptURL(ept string) string {
	return GetEptURL(ept)
}

func (c *hellosign) get(ept string, params *string) (*http.Response, error) {
	url := c.getEptURL(ept)
	if params != nil && *params == "" {
		url = fmt.Sprintf("%s?%s", url, *params)
	}
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.perform(req)
	return resp, err
}

func (c *hellosign) getAndParse(ept string, params *string, dst interface{}) (err error) {
	resp, err := c.get(ept, params)
	if err != nil {
		return err
	}
	defer func() { err = resp.Body.Close() }()
	return c.parseResponse(resp, dst)
}

func (c *hellosign) getFiles(ept, fileType string, getURL bool) (body []byte, fileURL *FileURL, err error) {
	if fileType != "" && fileType != "pdf" && fileType != "zip" {
		return []byte{}, nil, errors.New("Invalid file type specified, pdf or zip")
	}
	parms, err := form.EncodeToString(&struct {
		FileType string `form:"file_type,omitempty"`
		GetURL   bool   `form:"get_url,omitempty"`
	}{
		FileType: fileType,
		GetURL:   getURL,
	})
	if err != nil {
		return []byte{}, nil, err
	}
	resp, err := c.get(ept, &parms)
	if err != nil {
		return []byte{}, nil, err
	}
	defer func() { err = resp.Body.Close() }()
	if resp.StatusCode != http.StatusOK {
		return []byte{}, nil, errors.New(resp.Status)
	}
	if getURL {
		msg := &FileURL{}
		if respErr := c.parseResponse(resp, msg); respErr != nil {
			return []byte{}, nil, respErr
		}
		return []byte{}, msg, nil
	}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte{}, nil, err
	}
	return b, nil, nil
}

func (c *hellosign) list(ept string, parms ListParms, out interface{}) error {
	paramString, err := form.EncodeToString(parms)
	if err != nil {
		return err
	}
	if err := c.getAndParse(ept, &paramString, out); err != nil {
		return err
	}
	return nil
}
<commit_msg>change list parms into non-pointers, fix logic error in get<commit_after>// Copyright 2016 Stefan Nyman.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package hellosign

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"

	"github.com/ajg/form"
)

const (
	baseURL                  string = "https://api.hellosign.com/v3"
	contentType                     = "content-type"
	xRatelimitLimit                 = "x-Ratelimit-Limit"
	xRatelimitLimitRemaining        = "x-Ratelimit-Limit-Remaining"
	xRateLimitReset                 = "x-Ratelimit-Reset"
)

// ListInfo struct with properties for all list epts.
type ListInfo struct {
	Page       uint64 `json:"page"`
	NumPages   uint64 `json:"num_pages"`
	NumResults uint64 `json:"num_results"`
	PageSize   uint64 `json:"page_size"`
}

// ListParms struct with options for performing list operations.
type ListParms struct {
	AccountID string `form:"account_id,omitempty"`
	Page      uint64 `form:"page,omitempty"`
	PageSize  uint64 `form:"page_size,omitempty"`
	Query     string `form:"query,omitempty"`
}

// FormField a field where some kind of action needs to be taken.
type FormField struct {
	APIID    string `json:"api_id"`
	Name     string `json:"name"`
	Type     string `json:"type"`
	X        uint64 `json:"x"`
	Y        uint64 `json:"y"`
	Width    uint64 `json:"width"`
	Height   uint64 `json:"height"`
	Required bool   `json:"required"`
}

// APIErr an error returned from the Hellosign API.
type APIErr struct {
	Code    int // HTTP response code
	Message string
	Name    string
}

// APIWarn a list of warnings returned from the HelloSign API.
type APIWarn struct {
	Code     int // HTTP response code
	Warnings []struct {
		Message string
		Name    string
	}
}

func (a APIErr) Error() string {
	return fmt.Sprintf("%s: %s", a.Name, a.Message)
}

func (a APIWarn) Error() string {
	outMsg := ""
	for _, w := range a.Warnings {
		outMsg += fmt.Sprintf("%s: %s\n", w.Name, w.Message)
	}
	return outMsg
}

type hellosign struct {
	apiKey             string
	RateLimit          uint64 // Number of requests allowed per hour
	RateLimitRemaining uint64 // Remaining number of requests this hour
	RateLimitReset     uint64 // When the limit will be reset. In seconds from epoch
	LastStatusCode     int
}

// Initializes a new Hellosign API client.
func newHellosign(apiKey string) *hellosign {
	return &hellosign{
		apiKey: apiKey,
	}
}

func (c *hellosign) perform(req *http.Request) (*http.Response, error) {
	req.Header.Add("accept", "application/json")
	req.SetBasicAuth(c.apiKey, "")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	c.LastStatusCode = resp.StatusCode
	if resp.StatusCode >= 400 {
		return nil, c.parseResponseError(resp)
	}
	for _, hk := range []string{xRatelimitLimit, xRatelimitLimitRemaining, xRateLimitReset} {
		hv := resp.Header.Get(hk)
		if hv == "" {
			continue
		}
		hvui, pErr := strconv.ParseUint(hv, 10, 64)
		if pErr != nil {
			continue
		}
		switch hk {
		case xRatelimitLimit:
			c.RateLimit = hvui
		case xRatelimitLimitRemaining:
			c.RateLimitRemaining = hvui
		case xRateLimitReset:
			c.RateLimitReset = hvui
		}
	}
	return resp, err
}

func (c *hellosign) parseResponseError(resp *http.Response) error {
	e := &struct {
		Err struct {
			Msg  *string `json:"error_msg"`
			Name *string `json:"error_name"`
		} `json:"error"`
	}{}
	w := &struct {
		Warnings []struct {
			Msg  *string `json:"warning_msg"`
			Name *string `json:"warning_name"`
		} `json:"warnings"`
	}{}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal(b, e)
	if err != nil {
		return err
	}
	if e.Err.Name != nil {
		return APIErr{Code: resp.StatusCode, Message: *e.Err.Msg, Name: *e.Err.Name}
	}
	err = json.Unmarshal(b, w)
	if err != nil {
		return err
	}
	if len(w.Warnings) == 0 {
		return errors.New("Could not parse response error or warning")
	}
	retErr := APIWarn{}
	warns := []struct {
		Name    string
		Message string
	}{}
	for _, w := range w.Warnings {
		warns = append(warns, struct {
			Name    string
			Message string
		}{
			Name:    *w.Name,
			Message: *w.Msg,
		})
	}
	return retErr
}

func (c *hellosign) parseResponse(resp *http.Response, dst interface{}) error {
	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		d := json.NewDecoder(resp.Body)
		d.UseNumber()
		return d.Decode(dst)
	}
	return errors.New("Status code invalid")
}

func (c *hellosign) postForm(ept string, o interface{}) (*http.Response, error) {
	v := ""
	if o != nil {
		encoded, err := form.EncodeToString(o)
		if err != nil {
			return nil, err
		}
		v = encoded
	}
	req, err := http.NewRequest(http.MethodPost, c.getEptURL(ept), strings.NewReader(v))
	if err != nil {
		return nil, err
	}
	req.Header.Add(contentType, "application/x-www-form-urlencoded")
	return c.perform(req)
}

func (c *hellosign) postFormAndParse(ept string, inp, dst interface{}) (err error) {
	resp, err := c.postForm(ept, inp)
	if err != nil {
		return err
	}
	defer func() { err = resp.Body.Close() }()
	return c.parseResponse(resp, dst)
}

func (c *hellosign) postEmptyExpect(ept string, expected int) (ok bool, err error) {
	resp, err := c.postForm(ept, nil)
	if err != nil {
		return false, err
	}
	defer func() { err = resp.Body.Close() }()
	if resp.StatusCode != expected {
		return false, errors.New(resp.Status)
	}
	return true, nil
}

func (c *hellosign) delete(ept string) (*http.Response, error) {
	req, err := http.NewRequest(http.MethodDelete, c.getEptURL(ept), nil)
	if err != nil {
		return nil, err
	}
	return c.perform(req)
}

// GetEptURL returns the full HelloSign api url for a given endpoint.
func GetEptURL(ept string) string {
	return fmt.Sprintf("%s/%s", baseURL, ept)
}

func (c *hellosign) getEptURL(ept string) string {
	return GetEptURL(ept)
}

func (c *hellosign) get(ept string, params *string) (*http.Response, error) {
	url := c.getEptURL(ept)
	if params != nil && *params != "" {
		url = fmt.Sprintf("%s?%s", url, *params)
	}
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.perform(req)
	return resp, err
}

func (c *hellosign) getAndParse(ept string, params *string, dst interface{}) (err error) {
	resp, err := c.get(ept, params)
	if err != nil {
		return err
	}
	defer func() { err = resp.Body.Close() }()
	return c.parseResponse(resp, dst)
}

func (c *hellosign) getFiles(ept, fileType string, getURL bool) (body []byte, fileURL *FileURL, err error) {
	if fileType != "" && fileType != "pdf" && fileType != "zip" {
		return []byte{}, nil, errors.New("Invalid file type specified, pdf or zip")
	}
	parms, err := form.EncodeToString(&struct {
		FileType string `form:"file_type,omitempty"`
		GetURL   bool   `form:"get_url,omitempty"`
	}{
		FileType: fileType,
		GetURL:   getURL,
	})
	if err != nil {
		return []byte{}, nil, err
	}
	resp, err := c.get(ept, &parms)
	if err != nil {
		return []byte{}, nil, err
	}
	defer func() { err = resp.Body.Close() }()
	if resp.StatusCode != http.StatusOK {
		return []byte{}, nil, errors.New(resp.Status)
	}
	if getURL {
		msg := &FileURL{}
		if respErr := c.parseResponse(resp, msg); respErr != nil {
			return []byte{}, nil, respErr
		}
		return []byte{}, msg, nil
	}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte{}, nil, err
	}
	return b, nil, nil
}

func (c *hellosign) list(ept string, parms ListParms, out interface{}) error {
	paramString, err := form.EncodeToString(parms)
	if err != nil {
		return err
	}
	if err := c.getAndParse(ept, &paramString, out); err != nil {
		return err
	}
	return nil
}
<|endoftext|>