<commit_before>package main

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

	"github.com/BurntSushi/toml"
	"github.com/kelseyhightower/confd/backends"
	"github.com/kelseyhightower/confd/log"
	"github.com/kelseyhightower/confd/resource/template"
)

var (
	configFile        = ""
	defaultConfigFile = "/etc/confd/confd.toml"
	backend           string
	clientCaKeys      string
	clientCert        string
	clientKey         string
	confdir           string
	config            Config // holds the global confd config.
	debug             bool
	interval          int
	keepStageFile     bool
	nodes             Nodes
	noop              bool
	onetime           bool
	prefix            string
	printVersion      bool
	quiet             bool
	scheme            string
	srvDomain         string
	templateConfig    template.Config
	backendsConfig    backends.Config
	verbose           bool
	watch             bool
)

// A Config structure is used to configure confd.
type Config struct {
	Backend      string   `toml:"backend"`
	BackendNodes []string `toml:"nodes"`
	ClientCaKeys string   `toml:"client_cakeys"`
	ClientCert   string   `toml:"client_cert"`
	ClientKey    string   `toml:"client_key"`
	ConfDir      string   `toml:"confdir"`
	Debug        bool     `toml:"debug"`
	Interval     int      `toml:"interval"`
	Noop         bool     `toml:"noop"`
	Prefix       string   `toml:"prefix"`
	Quiet        bool     `toml:"quiet"`
	SRVDomain    string   `toml:"srv_domain"`
	Scheme       string   `toml:"scheme"`
	Verbose      bool     `toml:"verbose"`
	Watch        bool     `toml:"watch"`
}

func init() {
	flag.StringVar(&backend, "backend", "etcd", "backend to use")
	flag.StringVar(&clientCaKeys, "client-ca-keys", "", "client ca keys")
	flag.StringVar(&clientCert, "client-cert", "", "the client cert")
	flag.StringVar(&clientKey, "client-key", "", "the client key")
	flag.StringVar(&confdir, "confdir", "/etc/confd", "confd conf directory")
	flag.StringVar(&configFile, "config-file", "", "the confd config file")
	flag.BoolVar(&debug, "debug", false, "enable debug logging")
	flag.IntVar(&interval, "interval", 600, "backend polling interval")
	flag.BoolVar(&keepStageFile, "keep-stage-file", false, "keep staged files")
	flag.Var(&nodes, "node", "list of backend nodes")
	flag.BoolVar(&noop, "noop", false, "only show pending changes")
	flag.BoolVar(&onetime, "onetime", false, "run once and exit")
	flag.StringVar(&prefix, "prefix", "/", "key path prefix")
	flag.BoolVar(&printVersion, "version", false, "print version and exit")
	flag.BoolVar(&quiet, "quiet", false, "enable quiet logging")
	flag.StringVar(&scheme, "scheme", "http", "the backend URI scheme (http or https)")
	flag.StringVar(&srvDomain, "srv-domain", "", "the name of the resource record")
	flag.BoolVar(&verbose, "verbose", false, "enable verbose logging")
	flag.BoolVar(&watch, "watch", false, "enable watch support")
}

// initConfig initializes the confd configuration by first setting defaults,
// then overriding setting from the confd config file, and finally overriding
// settings from flags set on the command line.
// It returns an error if any.
func initConfig() error {
	if configFile == "" {
		if _, err := os.Stat(defaultConfigFile); !os.IsNotExist(err) {
			configFile = defaultConfigFile
		}
	}
	// Set defaults.
	config = Config{
		Backend:  "etcd",
		ConfDir:  "/etc/confd",
		Interval: 600,
		Prefix:   "/",
		Scheme:   "http",
	}
	// Update config from the TOML configuration file.
	if configFile == "" {
		log.Warning("Skipping confd config file.")
	} else {
		log.Debug("Loading " + configFile)
		configBytes, err := ioutil.ReadFile(configFile)
		if err != nil {
			return err
		}
		_, err = toml.Decode(string(configBytes), &config)
		if err != nil {
			return err
		}
	}
	// Update config from commandline flags.
	processFlags()

	// Configure logging.
	log.SetQuiet(config.Quiet)
	log.SetVerbose(config.Verbose)
	log.SetDebug(config.Debug)

	// Update BackendNodes from SRV records.
	if config.Backend != "env" && config.SRVDomain != "" {
		log.Info("SRV domain set to " + config.SRVDomain)
		srvNodes, err := getBackendNodesFromSRV(config.Backend, config.SRVDomain, config.Scheme)
		if err != nil {
			return errors.New("Cannot get nodes from SRV records " + err.Error())
		}
		config.BackendNodes = srvNodes
	}
	if len(config.BackendNodes) == 0 {
		switch config.Backend {
		case "consul":
			config.BackendNodes = []string{"127.0.0.1:8500"}
		case "etcd":
			peerstr := os.Getenv("ETCDCTL_PEERS")
			if len(peerstr) > 0 {
				config.BackendNodes = strings.Split(peerstr, ",")
			} else {
				config.BackendNodes = []string{"http://127.0.0.1:4001"}
			}
		}
	}
	// Initialize the storage client
	log.Notice("Backend set to " + config.Backend)
        if config.Backend == "zookeeper" && config.Watch == true {
             log.Notice("Watch is not supported for backend " + config.Backend + "exiting...")
             os.Exit(1);
        }
	backendsConfig = backends.Config{
		Backend:      config.Backend,
		ClientCaKeys: config.ClientCaKeys,
		ClientCert:   config.ClientCert,
		ClientKey:    config.ClientKey,
		BackendNodes: config.BackendNodes,
		Scheme:       config.Scheme,
	}
	// Template configuration.
	templateConfig = template.Config{
		ConfDir:       config.ConfDir,
		ConfigDir:     filepath.Join(config.ConfDir, "conf.d"),
		KeepStageFile: keepStageFile,
		Noop:          config.Noop,
		Prefix:        config.Prefix,
		TemplateDir:   filepath.Join(config.ConfDir, "templates"),
	}
	return nil
}

func getBackendNodesFromSRV(backend, domain, scheme string) ([]string, error) {
	nodes := make([]string, 0)
	// Ignore the CNAME as we don't need it.
	_, addrs, err := net.LookupSRV(backend, "tcp", domain)
	if err != nil {
		return nodes, err
	}
	for _, srv := range addrs {
		host := strings.TrimRight(srv.Target, ".")
		port := strconv.FormatUint(uint64(srv.Port), 10)
		nodes = append(nodes, fmt.Sprintf("%s://%s", scheme, net.JoinHostPort(host, port)))
	}
	return nodes, nil
}

// processFlags iterates through each flag set on the command line and
// overrides corresponding configuration settings.
func processFlags() {
	flag.Visit(setConfigFromFlag)
}

func setConfigFromFlag(f *flag.Flag) {
	switch f.Name {
	case "backend":
		config.Backend = backend
	case "debug":
		config.Debug = debug
	case "client-cert":
		config.ClientCert = clientCert
	case "client-key":
		config.ClientKey = clientKey
	case "client-cakeys":
		config.ClientCaKeys = clientCaKeys
	case "confdir":
		config.ConfDir = confdir
	case "node":
		config.BackendNodes = nodes
	case "interval":
		config.Interval = interval
	case "noop":
		config.Noop = noop
	case "prefix":
		config.Prefix = prefix
	case "quiet":
		config.Quiet = quiet
	case "scheme":
		config.Scheme = scheme
	case "srv-domain":
		config.SRVDomain = srvDomain
	case "verbose":
		config.Verbose = verbose
	case "watch":
		config.Watch = watch
	}
}
<commit_msg>Add default config for redis<commit_after>package main

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

	"github.com/BurntSushi/toml"
	"github.com/kelseyhightower/confd/backends"
	"github.com/kelseyhightower/confd/log"
	"github.com/kelseyhightower/confd/resource/template"
)

var (
	configFile        = ""
	defaultConfigFile = "/etc/confd/confd.toml"
	backend           string
	clientCaKeys      string
	clientCert        string
	clientKey         string
	confdir           string
	config            Config // holds the global confd config.
	debug             bool
	interval          int
	keepStageFile     bool
	nodes             Nodes
	noop              bool
	onetime           bool
	prefix            string
	printVersion      bool
	quiet             bool
	scheme            string
	srvDomain         string
	templateConfig    template.Config
	backendsConfig    backends.Config
	verbose           bool
	watch             bool
)

// A Config structure is used to configure confd.
type Config struct {
	Backend      string   `toml:"backend"`
	BackendNodes []string `toml:"nodes"`
	ClientCaKeys string   `toml:"client_cakeys"`
	ClientCert   string   `toml:"client_cert"`
	ClientKey    string   `toml:"client_key"`
	ConfDir      string   `toml:"confdir"`
	Debug        bool     `toml:"debug"`
	Interval     int      `toml:"interval"`
	Noop         bool     `toml:"noop"`
	Prefix       string   `toml:"prefix"`
	Quiet        bool     `toml:"quiet"`
	SRVDomain    string   `toml:"srv_domain"`
	Scheme       string   `toml:"scheme"`
	Verbose      bool     `toml:"verbose"`
	Watch        bool     `toml:"watch"`
}

func init() {
	flag.StringVar(&backend, "backend", "etcd", "backend to use")
	flag.StringVar(&clientCaKeys, "client-ca-keys", "", "client ca keys")
	flag.StringVar(&clientCert, "client-cert", "", "the client cert")
	flag.StringVar(&clientKey, "client-key", "", "the client key")
	flag.StringVar(&confdir, "confdir", "/etc/confd", "confd conf directory")
	flag.StringVar(&configFile, "config-file", "", "the confd config file")
	flag.BoolVar(&debug, "debug", false, "enable debug logging")
	flag.IntVar(&interval, "interval", 600, "backend polling interval")
	flag.BoolVar(&keepStageFile, "keep-stage-file", false, "keep staged files")
	flag.Var(&nodes, "node", "list of backend nodes")
	flag.BoolVar(&noop, "noop", false, "only show pending changes")
	flag.BoolVar(&onetime, "onetime", false, "run once and exit")
	flag.StringVar(&prefix, "prefix", "/", "key path prefix")
	flag.BoolVar(&printVersion, "version", false, "print version and exit")
	flag.BoolVar(&quiet, "quiet", false, "enable quiet logging")
	flag.StringVar(&scheme, "scheme", "http", "the backend URI scheme (http or https)")
	flag.StringVar(&srvDomain, "srv-domain", "", "the name of the resource record")
	flag.BoolVar(&verbose, "verbose", false, "enable verbose logging")
	flag.BoolVar(&watch, "watch", false, "enable watch support")
}

// initConfig initializes the confd configuration by first setting defaults,
// then overriding setting from the confd config file, and finally overriding
// settings from flags set on the command line.
// It returns an error if any.
func initConfig() error {
	if configFile == "" {
		if _, err := os.Stat(defaultConfigFile); !os.IsNotExist(err) {
			configFile = defaultConfigFile
		}
	}
	// Set defaults.
	config = Config{
		Backend:  "etcd",
		ConfDir:  "/etc/confd",
		Interval: 600,
		Prefix:   "/",
		Scheme:   "http",
	}
	// Update config from the TOML configuration file.
	if configFile == "" {
		log.Warning("Skipping confd config file.")
	} else {
		log.Debug("Loading " + configFile)
		configBytes, err := ioutil.ReadFile(configFile)
		if err != nil {
			return err
		}
		_, err = toml.Decode(string(configBytes), &config)
		if err != nil {
			return err
		}
	}
	// Update config from commandline flags.
	processFlags()

	// Configure logging.
	log.SetQuiet(config.Quiet)
	log.SetVerbose(config.Verbose)
	log.SetDebug(config.Debug)

	// Update BackendNodes from SRV records.
	if config.Backend != "env" && config.SRVDomain != "" {
		log.Info("SRV domain set to " + config.SRVDomain)
		srvNodes, err := getBackendNodesFromSRV(config.Backend, config.SRVDomain, config.Scheme)
		if err != nil {
			return errors.New("Cannot get nodes from SRV records " + err.Error())
		}
		config.BackendNodes = srvNodes
	}
	if len(config.BackendNodes) == 0 {
		switch config.Backend {
		case "consul":
			config.BackendNodes = []string{"127.0.0.1:8500"}
		case "etcd":
			peerstr := os.Getenv("ETCDCTL_PEERS")
			if len(peerstr) > 0 {
				config.BackendNodes = strings.Split(peerstr, ",")
			} else {
				config.BackendNodes = []string{"http://127.0.0.1:4001"}
			}
		case "redis":
			config.BackendNodes = []string{"127.0.0.1:6379"}
		}
	}
	// Initialize the storage client
	log.Notice("Backend set to " + config.Backend)

	if config.Watch {
		unsupportedBackends := map[string]bool{
			"zookeeper": true,
			"redis":     true,
		}

		if unsupportedBackends[config.Backend] {
			log.Notice("Watch is not supported for backend " + config.Backend + " exiting...")
			os.Exit(1)
		}
	}

	backendsConfig = backends.Config{
		Backend:      config.Backend,
		ClientCaKeys: config.ClientCaKeys,
		ClientCert:   config.ClientCert,
		ClientKey:    config.ClientKey,
		BackendNodes: config.BackendNodes,
		Scheme:       config.Scheme,
	}
	// Template configuration.
	templateConfig = template.Config{
		ConfDir:       config.ConfDir,
		ConfigDir:     filepath.Join(config.ConfDir, "conf.d"),
		KeepStageFile: keepStageFile,
		Noop:          config.Noop,
		Prefix:        config.Prefix,
		TemplateDir:   filepath.Join(config.ConfDir, "templates"),
	}
	return nil
}

func getBackendNodesFromSRV(backend, domain, scheme string) ([]string, error) {
	nodes := make([]string, 0)
	// Ignore the CNAME as we don't need it.
	_, addrs, err := net.LookupSRV(backend, "tcp", domain)
	if err != nil {
		return nodes, err
	}
	for _, srv := range addrs {
		host := strings.TrimRight(srv.Target, ".")
		port := strconv.FormatUint(uint64(srv.Port), 10)
		nodes = append(nodes, fmt.Sprintf("%s://%s", scheme, net.JoinHostPort(host, port)))
	}
	return nodes, nil
}

// processFlags iterates through each flag set on the command line and
// overrides corresponding configuration settings.
func processFlags() {
	flag.Visit(setConfigFromFlag)
}

func setConfigFromFlag(f *flag.Flag) {
	switch f.Name {
	case "backend":
		config.Backend = backend
	case "debug":
		config.Debug = debug
	case "client-cert":
		config.ClientCert = clientCert
	case "client-key":
		config.ClientKey = clientKey
	case "client-cakeys":
		config.ClientCaKeys = clientCaKeys
	case "confdir":
		config.ConfDir = confdir
	case "node":
		config.BackendNodes = nodes
	case "interval":
		config.Interval = interval
	case "noop":
		config.Noop = noop
	case "prefix":
		config.Prefix = prefix
	case "quiet":
		config.Quiet = quiet
	case "scheme":
		config.Scheme = scheme
	case "srv-domain":
		config.SRVDomain = srvDomain
	case "verbose":
		config.Verbose = verbose
	case "watch":
		config.Watch = watch
	}
}
<|endoftext|>


<commit_before>package client

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/miekg/dns"
	"github.com/skynetservices/skydns1/msg"
	"github.com/artemkaint/go-powerdns-client/response"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"os"
)

var (
	ErrNoHttpAddress   = errors.New("No HTTP address specified")
	ErrNoDnsAddress    = errors.New("No DNS address specified")
	ErrInvalidResponse = errors.New("Invalid HTTP response")
	ErrServiceNotFound = errors.New("Service not found")
	ErrConflictingUUID = errors.New("Conflicting UUID")
)

type (
	Client struct {
		base    string
		secret  string
		h       *http.Client
		basedns string
		domain  string
		d       *dns.Client
		DNS     bool // if true use the DNS when listing servies
	}

	NameCount map[string]int
)

func logger(msg string, buffer *bytes.Buffer) error {
	fmt.Fprintf(os.Stderr, "Log: Method:%s \n %s \n", msg, buffer)
	return nil
}

// NewClient creates a new skydns client with the specificed host address and
// DNS port.
func NewClient(base, secret, domain, basedns string) (*Client, error) {
	if base == "" {
		return nil, ErrNoHttpAddress
	}
	if basedns == "" {
		return nil, ErrNoDnsAddress
	}
	return &Client{
		base:    base,
		basedns: basedns,
		domain:  dns.Fqdn(domain),
		secret:  secret,
		h:       &http.Client{},
		d:       &dns.Client{},
	}, nil
}

// GET /servers
func (c *Client) GetServers() (*[]response.ServerResource, error) {
    b := bytes.NewBuffer(nil)
    req, err := c.newRequest("GET", c.joinUrl("servers"), b)
    if err != nil {
        return nil, err
    }
    resp, err := c.h.Do(req)
    if err != nil {
        return nil, err
    }
    if resp.Body != nil {
        defer resp.Body.Close()
    }
    switch resp.StatusCode {
    	case http.StatusOK:
    		break
    	default:
    		return nil, ErrInvalidResponse
    }
    var s *[]response.ServerResource
    if err := json.NewDecoder(resp.Body).Decode(&s); err != nil {
        return nil, err
    }
    return s, nil
}

// GET /servers/:server_id
func (c *Client) GetServer(uuid string) (*response.ServerResource, error) {
    b := bytes.NewBuffer(nil)
    req, err := c.newRequest("GET", c.joinUrl(fmt.Sprintf("servers/%s", uuid)), b)
    if err != nil {
        return nil, err
    }
    resp, err := c.h.Do(req)
    if err != nil {
        return nil, err
    }
    if resp.Body != nil {
        defer resp.Body.Close()
    }
    switch resp.StatusCode {
    	case http.StatusOK:
    		break
    	default:
    		return nil, ErrInvalidResponse
    }
    var s *response.ServerResource
    if err := json.NewDecoder(resp.Body).Decode(&s); err != nil {
        return nil, err
    }
    return s, nil
}

// GET /servers/:server_id/config
func (c *Client) GetServerConfigs() error {
    // TODO: need implement
    return nil
}

// GET /servers/:server_id/config/:config_setting_name
func (c *Client) GetServerConfig(uuid string) error {
    // TODO: need implement
    return nil
}

// GET /servers/:server_id/zones
func (c *Client) GetZones() (*[]response.ZoneCollection, error) {
    b := bytes.NewBuffer(nil)
    req, err := c.newRequest("GET", c.joinUrl(fmt.Sprintf("servers/%s/zones", "localhost")), b)
    if err != nil {
        return nil, err
    }
    resp, err := c.h.Do(req)
    if err != nil {
        return nil, err
    }
    if resp.Body != nil {
        defer resp.Body.Close()
    }
    switch resp.StatusCode {
    	case http.StatusOK:
    		break
    	default:
    		return nil, ErrInvalidResponse
    }
    var s *[]response.ZoneCollection
    if err := json.NewDecoder(resp.Body).Decode(&s); err != nil {
        return nil, err
    }
    return s, nil
}

// POST /servers/:server_id/zones
func (c *Client) AddZone(zone string, server interface{}) (*response.ZoneCollection, error) {
    if server == nil {
        server = "localhost"
    }
    jsonZone, err := json.Marshal(response.ZoneCollection{
        Name: zone,
        Kind: "Native",
        Masters: []string{},
        Nameservers: []string{},
    })
    if err != nil {
        return nil, err
    }
    b := bytes.NewBuffer([]byte(jsonZone))
    req, err := c.newRequest("POST", c.joinUrl(fmt.Sprintf("servers/%s/zones", server)), b)
    if err != nil {
        return nil, err
    }
    resp, err := c.h.Do(req)
    if err != nil {
        return nil, err
    }
    if resp.Body != nil {
        defer resp.Body.Close()
    }
    switch resp.StatusCode {
    	case http.StatusOK:
    		break
        case http.StatusCreated:
    		break
    	case http.StatusBadRequest:
    		return nil, ErrInvalidResponse
        case 422:
            var s *response.Error
            if err := json.NewDecoder(resp.Body).Decode(&s); err != nil {
                return nil, errors.New("Resource already exists")
            }
            var errorText string
            if errorText = "Resource already exists"; s.Error != "" {
                errorText = s.Error
            }
    		return nil, errors.New(errorText)
    	default:
    		return nil, ErrInvalidResponse
    }
    var s *response.ZoneCollection
    if err := json.NewDecoder(resp.Body).Decode(&s); err != nil {
        return nil, err
    }
    return s, nil
}

func (c *Client) Add(uuid string, s *msg.Service) error {
	b := bytes.NewBuffer(nil)
	if err := json.NewEncoder(b).Encode(s); err != nil {
		return err
	}
	_, error := c.AddZone(s.Version, nil)
	if error != nil {
        fmt.Fprintf(os.Stderr, "Log: ERROR: %s \n", error.Error())
	}
	return error
}

func (c *Client) Delete(uuid string) error {
	logger("Delete", nil)
	req, err := c.newRequest("DELETE", c.joinUrl(uuid), nil)
	if err != nil {
		return err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}
	return nil
}

func (c *Client) Get(uuid string) (*msg.Service, error) {
	logger("GET", nil)
	req, err := c.newRequest("GET", c.joinUrl(uuid), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}
	switch resp.StatusCode {
	case http.StatusOK:
		break
	case http.StatusNotFound:
		return nil, ErrServiceNotFound
	default:
		return nil, ErrInvalidResponse
	}

	var s *msg.Service
	if err := json.NewDecoder(resp.Body).Decode(&s); err != nil {
		return nil, err
	}
	return s, nil
}

func (c *Client) Update(uuid string, ttl uint32) error {
	b := bytes.NewBuffer([]byte(fmt.Sprintf(`{"TTL":%d}`, ttl)))

	logger("Update", b)
	req, err := c.newRequest("PATCH", c.joinUrl(uuid), b)
	if err != nil {
		return err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}
	return nil
}

func (c *Client) GetAllServices() ([]*msg.Service, error) {
	logger("GetAll", nil)
	req, err := c.newRequest("GET", c.joinUrl(""), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	var out []*msg.Service
	if resp.StatusCode == http.StatusOK {
		if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
			return nil, err
		}
	}
	return out, nil
}

func (c *Client) GetAllServicesDNS() ([]*msg.Service, error) {
	req, err := c.newRequestDNS("", dns.TypeSRV)
	if err != nil {
		return nil, err
	}
	resp, _, err := c.d.Exchange(req, c.basedns)
	if err != nil {
		return nil, err
	}
	// Handle UUID.skydns.local additional section stuff? TODO(miek)
	s := make([]*msg.Service, len(resp.Answer))
	for i, r := range resp.Answer {
		if v, ok := r.(*dns.SRV); ok {
			s[i] = &msg.Service{
				// TODO(miek): uehh, stuff it in Name?
				Name: v.Header().Name + " (Priority: " + strconv.Itoa(int(v.Priority)) + ", " + "Weight: " + strconv.Itoa(int(v.Weight)) + ")",
				Host: v.Target,
				Port: v.Port,
				TTL:  r.Header().Ttl,
			}
		}
	}
	return s, nil
}

func (c *Client) GetRegions() (NameCount, error) {
	logger("Get Regions" + fmt.Sprintf("%s/skydns/regions/", c.base), nil)
	req, err := c.newRequest("GET", fmt.Sprintf("%s/skydns/regions/", c.base), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	var out NameCount
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *Client) GetEnvironments() (NameCount, error) {
	req, err := c.newRequest("GET", fmt.Sprintf("%s/skydns/environments/", c.base), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	var out NameCount
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *Client) AddCallback(uuid string, cb *msg.Callback) error {
	buf := bytes.NewBuffer(nil)
	if err := json.NewEncoder(buf).Encode(cb); err != nil {
		return err
	}
	req, err := c.newRequest("PUT", fmt.Sprintf("%s/skydns/callbacks/%s", c.base, uuid), buf)
	if err != nil {
		return err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	switch resp.StatusCode {
	case http.StatusCreated:
		return nil
	case http.StatusNotFound:
		return ErrServiceNotFound
	default:
		return ErrInvalidResponse
	}
}

func (c *Client) joinUrl(action string) string {
	return fmt.Sprintf("%s/%s", c.base, action)
}

func (c *Client) newRequest(method, url string, body io.Reader) (*http.Request, error) {
	req, err := http.NewRequest(method, url, body)
	req.Header.Add("X-API-Key", "SOMEKEY")
	if c.secret != "" {
		req.Header.Add("Authorization", c.secret)
	}
	return req, err
}

func (c *Client) newRequestDNS(qname string, qtype uint16) (*dns.Msg, error) {
	m := new(dns.Msg)
	if qname == "" {
		m.SetQuestion(c.domain, qtype)
	} else {
		m.SetQuestion(qname+"."+c.domain, qtype)
	}
	return m, nil
}

func (c *Client) extractBaseFromLocation(location string) (string, error) {
	u, err := url.ParseRequestURI(location)
	if err != nil {
		return "", err
	}
	base := u.Scheme + "://" + u.Host
	return base, nil
}<commit_msg>Fix return value from function GetServers<commit_after>package client

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/miekg/dns"
	"github.com/skynetservices/skydns1/msg"
	"github.com/artemkaint/go-powerdns-client/response"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"os"
)

var (
	ErrNoHttpAddress   = errors.New("No HTTP address specified")
	ErrNoDnsAddress    = errors.New("No DNS address specified")
	ErrInvalidResponse = errors.New("Invalid HTTP response")
	ErrServiceNotFound = errors.New("Service not found")
	ErrConflictingUUID = errors.New("Conflicting UUID")
)

type (
	Client struct {
		base    string
		secret  string
		h       *http.Client
		basedns string
		domain  string
		d       *dns.Client
		DNS     bool // if true use the DNS when listing servies
	}

	NameCount map[string]int
)

func logger(msg string, buffer *bytes.Buffer) error {
	fmt.Fprintf(os.Stderr, "Log: Method:%s \n %s \n", msg, buffer)
	return nil
}

// NewClient creates a new skydns client with the specificed host address and
// DNS port.
func NewClient(base, secret, domain, basedns string) (*Client, error) {
	if base == "" {
		return nil, ErrNoHttpAddress
	}
	if basedns == "" {
		return nil, ErrNoDnsAddress
	}
	return &Client{
		base:    base,
		basedns: basedns,
		domain:  dns.Fqdn(domain),
		secret:  secret,
		h:       &http.Client{},
		d:       &dns.Client{},
	}, nil
}

// GET /servers
func (c *Client) GetServers() ([]response.ServerResource, error) {
    b := bytes.NewBuffer(nil)
    req, err := c.newRequest("GET", c.joinUrl("servers"), b)
    if err != nil {
        return nil, err
    }
    resp, err := c.h.Do(req)
    if err != nil {
        return nil, err
    }
    if resp.Body != nil {
        defer resp.Body.Close()
    }
    switch resp.StatusCode {
    	case http.StatusOK:
    		break
    	default:
    		return nil, ErrInvalidResponse
    }
    var s []response.ServerResource
    if err := json.NewDecoder(resp.Body).Decode(&s); err != nil {
        return nil, err
    }
    return s, nil
}

// GET /servers/:server_id
func (c *Client) GetServer(uuid string) (*response.ServerResource, error) {
    b := bytes.NewBuffer(nil)
    req, err := c.newRequest("GET", c.joinUrl(fmt.Sprintf("servers/%s", uuid)), b)
    if err != nil {
        return nil, err
    }
    resp, err := c.h.Do(req)
    if err != nil {
        return nil, err
    }
    if resp.Body != nil {
        defer resp.Body.Close()
    }
    switch resp.StatusCode {
    	case http.StatusOK:
    		break
    	default:
    		return nil, ErrInvalidResponse
    }
    var s *response.ServerResource
    if err := json.NewDecoder(resp.Body).Decode(&s); err != nil {
        return nil, err
    }
    return s, nil
}

// GET /servers/:server_id/config
func (c *Client) GetServerConfigs() error {
    // TODO: need implement
    return nil
}

// GET /servers/:server_id/config/:config_setting_name
func (c *Client) GetServerConfig(uuid string) error {
    // TODO: need implement
    return nil
}

// GET /servers/:server_id/zones
func (c *Client) GetZones() (*[]response.ZoneCollection, error) {
    b := bytes.NewBuffer(nil)
    req, err := c.newRequest("GET", c.joinUrl(fmt.Sprintf("servers/%s/zones", "localhost")), b)
    if err != nil {
        return nil, err
    }
    resp, err := c.h.Do(req)
    if err != nil {
        return nil, err
    }
    if resp.Body != nil {
        defer resp.Body.Close()
    }
    switch resp.StatusCode {
    	case http.StatusOK:
    		break
    	default:
    		return nil, ErrInvalidResponse
    }
    var s *[]response.ZoneCollection
    if err := json.NewDecoder(resp.Body).Decode(&s); err != nil {
        return nil, err
    }
    return s, nil
}

// POST /servers/:server_id/zones
func (c *Client) AddZone(zone string, server interface{}) (*response.ZoneCollection, error) {
    if server == nil {
        server = "localhost"
    }
    jsonZone, err := json.Marshal(response.ZoneCollection{
        Name: zone,
        Kind: "Native",
        Masters: []string{},
        Nameservers: []string{},
    })
    if err != nil {
        return nil, err
    }
    b := bytes.NewBuffer([]byte(jsonZone))
    req, err := c.newRequest("POST", c.joinUrl(fmt.Sprintf("servers/%s/zones", server)), b)
    if err != nil {
        return nil, err
    }
    resp, err := c.h.Do(req)
    if err != nil {
        return nil, err
    }
    if resp.Body != nil {
        defer resp.Body.Close()
    }
    switch resp.StatusCode {
    	case http.StatusOK:
    		break
        case http.StatusCreated:
    		break
    	case http.StatusBadRequest:
    		return nil, ErrInvalidResponse
        case 422:
            var s *response.Error
            if err := json.NewDecoder(resp.Body).Decode(&s); err != nil {
                return nil, errors.New("Resource already exists")
            }
            var errorText string
            if errorText = "Resource already exists"; s.Error != "" {
                errorText = s.Error
            }
    		return nil, errors.New(errorText)
    	default:
    		return nil, ErrInvalidResponse
    }
    var s *response.ZoneCollection
    if err := json.NewDecoder(resp.Body).Decode(&s); err != nil {
        return nil, err
    }
    return s, nil
}

func (c *Client) Add(uuid string, s *msg.Service) error {
	b := bytes.NewBuffer(nil)
	if err := json.NewEncoder(b).Encode(s); err != nil {
		return err
	}
	_, error := c.AddZone(s.Version, nil)
	if error != nil {
        fmt.Fprintf(os.Stderr, "Log: ERROR: %s \n", error.Error())
	}
	return error
}

func (c *Client) Delete(uuid string) error {
	logger("Delete", nil)
	req, err := c.newRequest("DELETE", c.joinUrl(uuid), nil)
	if err != nil {
		return err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}
	return nil
}

func (c *Client) Get(uuid string) (*msg.Service, error) {
	logger("GET", nil)
	req, err := c.newRequest("GET", c.joinUrl(uuid), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}
	switch resp.StatusCode {
	case http.StatusOK:
		break
	case http.StatusNotFound:
		return nil, ErrServiceNotFound
	default:
		return nil, ErrInvalidResponse
	}

	var s *msg.Service
	if err := json.NewDecoder(resp.Body).Decode(&s); err != nil {
		return nil, err
	}
	return s, nil
}

func (c *Client) Update(uuid string, ttl uint32) error {
	b := bytes.NewBuffer([]byte(fmt.Sprintf(`{"TTL":%d}`, ttl)))

	logger("Update", b)
	req, err := c.newRequest("PATCH", c.joinUrl(uuid), b)
	if err != nil {
		return err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}
	return nil
}

func (c *Client) GetAllServices() ([]*msg.Service, error) {
	logger("GetAll", nil)
	req, err := c.newRequest("GET", c.joinUrl(""), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	var out []*msg.Service
	if resp.StatusCode == http.StatusOK {
		if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
			return nil, err
		}
	}
	return out, nil
}

func (c *Client) GetAllServicesDNS() ([]*msg.Service, error) {
	req, err := c.newRequestDNS("", dns.TypeSRV)
	if err != nil {
		return nil, err
	}
	resp, _, err := c.d.Exchange(req, c.basedns)
	if err != nil {
		return nil, err
	}
	// Handle UUID.skydns.local additional section stuff? TODO(miek)
	s := make([]*msg.Service, len(resp.Answer))
	for i, r := range resp.Answer {
		if v, ok := r.(*dns.SRV); ok {
			s[i] = &msg.Service{
				// TODO(miek): uehh, stuff it in Name?
				Name: v.Header().Name + " (Priority: " + strconv.Itoa(int(v.Priority)) + ", " + "Weight: " + strconv.Itoa(int(v.Weight)) + ")",
				Host: v.Target,
				Port: v.Port,
				TTL:  r.Header().Ttl,
			}
		}
	}
	return s, nil
}

func (c *Client) GetRegions() (NameCount, error) {
	logger("Get Regions" + fmt.Sprintf("%s/skydns/regions/", c.base), nil)
	req, err := c.newRequest("GET", fmt.Sprintf("%s/skydns/regions/", c.base), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	var out NameCount
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *Client) GetEnvironments() (NameCount, error) {
	req, err := c.newRequest("GET", fmt.Sprintf("%s/skydns/environments/", c.base), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	var out NameCount
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *Client) AddCallback(uuid string, cb *msg.Callback) error {
	buf := bytes.NewBuffer(nil)
	if err := json.NewEncoder(buf).Encode(cb); err != nil {
		return err
	}
	req, err := c.newRequest("PUT", fmt.Sprintf("%s/skydns/callbacks/%s", c.base, uuid), buf)
	if err != nil {
		return err
	}
	resp, err := c.h.Do(req)
	if err != nil {
		return err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	switch resp.StatusCode {
	case http.StatusCreated:
		return nil
	case http.StatusNotFound:
		return ErrServiceNotFound
	default:
		return ErrInvalidResponse
	}
}

func (c *Client) joinUrl(action string) string {
	return fmt.Sprintf("%s/%s", c.base, action)
}

func (c *Client) newRequest(method, url string, body io.Reader) (*http.Request, error) {
	req, err := http.NewRequest(method, url, body)
	req.Header.Add("X-API-Key", "SOMEKEY")
	if c.secret != "" {
		req.Header.Add("Authorization", c.secret)
	}
	return req, err
}

func (c *Client) newRequestDNS(qname string, qtype uint16) (*dns.Msg, error) {
	m := new(dns.Msg)
	if qname == "" {
		m.SetQuestion(c.domain, qtype)
	} else {
		m.SetQuestion(qname+"."+c.domain, qtype)
	}
	return m, nil
}

func (c *Client) extractBaseFromLocation(location string) (string, error) {
	u, err := url.ParseRequestURI(location)
	if err != nil {
		return "", err
	}
	base := u.Scheme + "://" + u.Host
	return base, nil
}<|endoftext|>


<commit_before>package v1

import (
	"database/sql"
	"strconv"
	"time"

	"git.zxq.co/ripple/rippleapi/common"
	"git.zxq.co/ripple/rippleapi/limit"
)

type rankRequestsStatusResponse struct {
	common.ResponseBase
	QueueSize       int        `json:"queue_size"`
	MaxPerUser      int        `json:"max_per_user"`
	Submitted       int        `json:"submitted"`
	SubmittedByUser *int       `json:"submitted_by_user,omitempty"`
	CanSubmit       *bool      `json:"can_submit,omitempty"`
	NextExpiration  *time.Time `json:"next_expiration"`
}

// BeatmapRankRequestsStatusGET gets the current status for beatmap ranking requests.
func BeatmapRankRequestsStatusGET(md common.MethodData) common.CodeMessager {
	c := common.GetConf()
	rows, err := md.DB.Query("SELECT userid, time FROM rank_requests WHERE time > ? ORDER BY id ASC LIMIT "+strconv.Itoa(c.RankQueueSize), time.Now().Add(-time.Hour*24).Unix())
	if err != nil {
		md.Err(err)
		return Err500
	}
	var r rankRequestsStatusResponse
	// if it's not auth-free access and we have got ReadConfidential, we can
	// know if this user can submit beatmaps or not.
	hasConfid := md.ID() != 0 && md.User.TokenPrivileges&common.PrivilegeReadConfidential > 0
	if hasConfid {
		r.SubmittedByUser = new(int)
	}
	isFirst := true
	for rows.Next() {
		var (
			user      int
			timestamp common.UnixTimestamp
		)
		err := rows.Scan(&user, &timestamp)
		if err != nil {
			md.Err(err)
			continue
		}
		// if the user submitted this rank request, increase the number of
		// rank requests submitted by this user
		if user == md.ID() && r.SubmittedByUser != nil {
			(*r.SubmittedByUser)++
		}
		// also, if this is the first result, it means it will be the next to
		// expire.
		if isFirst {
			x := time.Time(timestamp)
			r.NextExpiration = &x
			isFirst = false
		}
		r.Submitted++
	}
	r.QueueSize = c.RankQueueSize
	r.MaxPerUser = c.BeatmapRequestsPerUser
	if hasConfid {
		x := r.Submitted < r.QueueSize && *r.SubmittedByUser < r.MaxPerUser
		r.CanSubmit = &x
	}
	r.Code = 200
	return r
}

type submitRequestData struct {
	ID    int `json:"id"`
	SetID int `json:"set_id"`
}

// BeatmapRankRequestsSubmitPOST submits a new beatmap for ranking approval.
func BeatmapRankRequestsSubmitPOST(md common.MethodData) common.CodeMessager {
	var d submitRequestData
	err := md.RequestData.Unmarshal(&d)
	if err != nil {
		return ErrBadJSON
	}
	// check json data is present
	if d.ID == 0 && d.SetID == 0 {
		return ErrMissingField("id|set_id")
	}

	// you've been rate limited
	if !limit.NonBlockingRequest("rankrequest:u:"+strconv.Itoa(md.ID()), 5) {
		return common.SimpleResponse(429, "You may only try to request 5 beatmaps per minute.")
	}
	if !limit.NonBlockingRequest("rankrequest:ip:"+md.C.ClientIP(), 8) {
		return common.SimpleResponse(429, "You may only try to request 8 beatmaps per minute from the same IP.")
	}

	// find out from BeatmapRankRequestsStatusGET if we can submit beatmaps.
	statusRaw := BeatmapRankRequestsStatusGET(md)
	status, ok := statusRaw.(rankRequestsStatusResponse)
	if !ok {
		// if it's not a rankRequestsStatusResponse, it means it's an error
		return statusRaw
	}
	if !*status.CanSubmit {
		return common.SimpleResponse(403, "It's not possible to do a rank request at this time.")
	}

	w := common.
		Where("beatmap_id = ?", strconv.Itoa(d.ID)).Or().
		Where("beatmapset_id = ?", strconv.Itoa(d.SetID))

	var ranked int
	err = md.DB.QueryRow("SELECT ranked FROM beatmaps "+w.Clause+" LIMIT 1", w.Params...).Scan(&ranked)
	if ranked >= 2 {
		return common.SimpleResponse(406, "That beatmap is already ranked.")
	}

	switch err {
	case nil:
		// move on
	case sql.ErrNoRows:
		if d.SetID != 0 {
			md.R.Publish("ripple:beatmap_updates:sets", strconv.Itoa(d.SetID))
		} else {
			md.R.Publish("ripple:beatmap_updates:single", strconv.Itoa(d.ID))
		}
	default:
		md.Err(err)
		return Err500
	}

	// type and value of beatmap rank request
	t := "b"
	v := d.ID
	if d.SetID != 0 {
		t = "s"
		v = d.SetID
	}
	err = md.DB.QueryRow("SELECT 1 FROM rank_requests WHERE bid = ? AND type = ? AND time > ?",
		v, t, time.Now().Add(-time.Hour*24).Unix()).Scan(new(int))

	// error handling
	switch err {
	case sql.ErrNoRows:
		break
	case nil:
		// we're returning a success because if the request was already sent in the past 24
		// hours, it's as if the user submitted it.
		return BeatmapRankRequestsStatusGET(md)
	default:
		md.Err(err)
		return Err500
	}

	_, err = md.DB.Exec(
		"INSERT INTO rank_requests (userid, bid, type, time, blacklisted) VALUES (?, ?, ?, ?, 0)",
		md.ID(), v, t, time.Now().Unix())
	if err != nil {
		md.Err(err)
		return Err500
	}

	return BeatmapRankRequestsStatusGET(md)
}
<commit_msg>ripple: -> lets:<commit_after>package v1

import (
	"database/sql"
	"strconv"
	"time"

	"git.zxq.co/ripple/rippleapi/common"
	"git.zxq.co/ripple/rippleapi/limit"
)

type rankRequestsStatusResponse struct {
	common.ResponseBase
	QueueSize       int        `json:"queue_size"`
	MaxPerUser      int        `json:"max_per_user"`
	Submitted       int        `json:"submitted"`
	SubmittedByUser *int       `json:"submitted_by_user,omitempty"`
	CanSubmit       *bool      `json:"can_submit,omitempty"`
	NextExpiration  *time.Time `json:"next_expiration"`
}

// BeatmapRankRequestsStatusGET gets the current status for beatmap ranking requests.
func BeatmapRankRequestsStatusGET(md common.MethodData) common.CodeMessager {
	c := common.GetConf()
	rows, err := md.DB.Query("SELECT userid, time FROM rank_requests WHERE time > ? ORDER BY id ASC LIMIT "+strconv.Itoa(c.RankQueueSize), time.Now().Add(-time.Hour*24).Unix())
	if err != nil {
		md.Err(err)
		return Err500
	}
	var r rankRequestsStatusResponse
	// if it's not auth-free access and we have got ReadConfidential, we can
	// know if this user can submit beatmaps or not.
	hasConfid := md.ID() != 0 && md.User.TokenPrivileges&common.PrivilegeReadConfidential > 0
	if hasConfid {
		r.SubmittedByUser = new(int)
	}
	isFirst := true
	for rows.Next() {
		var (
			user      int
			timestamp common.UnixTimestamp
		)
		err := rows.Scan(&user, &timestamp)
		if err != nil {
			md.Err(err)
			continue
		}
		// if the user submitted this rank request, increase the number of
		// rank requests submitted by this user
		if user == md.ID() && r.SubmittedByUser != nil {
			(*r.SubmittedByUser)++
		}
		// also, if this is the first result, it means it will be the next to
		// expire.
		if isFirst {
			x := time.Time(timestamp)
			r.NextExpiration = &x
			isFirst = false
		}
		r.Submitted++
	}
	r.QueueSize = c.RankQueueSize
	r.MaxPerUser = c.BeatmapRequestsPerUser
	if hasConfid {
		x := r.Submitted < r.QueueSize && *r.SubmittedByUser < r.MaxPerUser
		r.CanSubmit = &x
	}
	r.Code = 200
	return r
}

type submitRequestData struct {
	ID    int `json:"id"`
	SetID int `json:"set_id"`
}

// BeatmapRankRequestsSubmitPOST submits a new beatmap for ranking approval.
func BeatmapRankRequestsSubmitPOST(md common.MethodData) common.CodeMessager {
	var d submitRequestData
	err := md.RequestData.Unmarshal(&d)
	if err != nil {
		return ErrBadJSON
	}
	// check json data is present
	if d.ID == 0 && d.SetID == 0 {
		return ErrMissingField("id|set_id")
	}

	// you've been rate limited
	if !limit.NonBlockingRequest("rankrequest:u:"+strconv.Itoa(md.ID()), 5) {
		return common.SimpleResponse(429, "You may only try to request 5 beatmaps per minute.")
	}
	if !limit.NonBlockingRequest("rankrequest:ip:"+md.C.ClientIP(), 8) {
		return common.SimpleResponse(429, "You may only try to request 8 beatmaps per minute from the same IP.")
	}

	// find out from BeatmapRankRequestsStatusGET if we can submit beatmaps.
	statusRaw := BeatmapRankRequestsStatusGET(md)
	status, ok := statusRaw.(rankRequestsStatusResponse)
	if !ok {
		// if it's not a rankRequestsStatusResponse, it means it's an error
		return statusRaw
	}
	if !*status.CanSubmit {
		return common.SimpleResponse(403, "It's not possible to do a rank request at this time.")
	}

	w := common.
		Where("beatmap_id = ?", strconv.Itoa(d.ID)).Or().
		Where("beatmapset_id = ?", strconv.Itoa(d.SetID))

	var ranked int
	err = md.DB.QueryRow("SELECT ranked FROM beatmaps "+w.Clause+" LIMIT 1", w.Params...).Scan(&ranked)
	if ranked >= 2 {
		return common.SimpleResponse(406, "That beatmap is already ranked.")
	}

	switch err {
	case nil:
		// move on
	case sql.ErrNoRows:
		if d.SetID != 0 {
			md.R.Publish("lets:beatmap_updates:sets", strconv.Itoa(d.SetID))
		} else {
			md.R.Publish("lets:beatmap_updates:single", strconv.Itoa(d.ID))
		}
	default:
		md.Err(err)
		return Err500
	}

	// type and value of beatmap rank request
	t := "b"
	v := d.ID
	if d.SetID != 0 {
		t = "s"
		v = d.SetID
	}
	err = md.DB.QueryRow("SELECT 1 FROM rank_requests WHERE bid = ? AND type = ? AND time > ?",
		v, t, time.Now().Add(-time.Hour*24).Unix()).Scan(new(int))

	// error handling
	switch err {
	case sql.ErrNoRows:
		break
	case nil:
		// we're returning a success because if the request was already sent in the past 24
		// hours, it's as if the user submitted it.
		return BeatmapRankRequestsStatusGET(md)
	default:
		md.Err(err)
		return Err500
	}

	_, err = md.DB.Exec(
		"INSERT INTO rank_requests (userid, bid, type, time, blacklisted) VALUES (?, ?, ?, ?, 0)",
		md.ID(), v, t, time.Now().Unix())
	if err != nil {
		md.Err(err)
		return Err500
	}

	return BeatmapRankRequestsStatusGET(md)
}
<|endoftext|>


<commit_before>/*It is (49+len(pstr)) bytes long.
handshake: <pstrlen><pstr><reserved><info_hash><peer_id>
pstrlen: string length of <pstr>, as a single raw byte
pstr: string identifier of the protocol
reserved: eight (8) reserved bytes. All current implementations use all zeroes. Each bit in these bytes can be used to change the behavior of the protocol. An email from Bram suggests that trailing bits should be used first, so that leading bits may be used to change the meaning of trailing bits.
info_hash: 20-byte SHA1 hash of the info key in the metainfo file. This is the same info_hash that is transmitted in tracker requests.
peer_id: 20-byte string used as a unique ID for the client. This is usually the same peer_id that is transmitted in tracker requests (but not always e.g. an anonymity option in Azureus).

pstrlen = 19, and pstr = "BitTorrent protocol"
peer_id = -AZ2060-
*/

const keepalive =0;
const (
choke = iota;
unchoke;
interested;
not interested;
have;
bitfield;
request;
piece;
port;
)

/*
keep-alive: <len=0000>
The keep-alive message is a message with zero bytes, specified with the length prefix set to zero. There is no message ID and no payload. Peers may close a connection if they receive no messages (keep-alive or any other message) for a certain period of time, so a keep-alive message must be sent to maintain the connection alive if no command have been sent for a given amount of time. This amount of time is generally two minutes.

choke: <len=0001><id=0>
The choke message is fixed-length and has no payload.

unchoke: <len=0001><id=1>
The unchoke message is fixed-length and has no payload.

interested: <len=0001><id=2>
The interested message is fixed-length and has no payload.

not interested: <len=0001><id=3>
The not interested message is fixed-length and has no payload.
have: <len=0005><id=4><piece index>

bitfield: <len=0001+X><id=5><bitfield>

request: <len=0013><id=6><index><begin><length>
The request message is fixed length, and is used to request a block. The payload contains the following information:
index: integer specifying the zero-based piece index
begin: integer specifying the zero-based byte offset within the piece
length: integer specifying the requested length.

piece: <len=0009+X><id=7><index><begin><block>
The piece message is variable length, where X is the length of the block. The payload contains the following information:
index: integer specifying the zero-based piece index
begin: integer specifying the zero-based byte offset within the piece
block: block of data, which is a subset of the piece specified by index.

cancel: <len=0013><id=8><index><begin><length>
The cancel message is fixed length, and is used to cancel block requests. The payload is identical to that of the "request" message. It is typically used during "End Game" (see the Algorithms section below).

port: <len=0003><id=9><listen-port>
The port message is sent by newer versions of the Mainline that implements a DHT tracker. The listen port is the port this peer's DHT node is listening on. This peer should be inserted in the local routing table (if DHT tracker is supported).
*/
type handshake struct {
	pstrlen int8;
	pstr string;
	reserved [8]byte;
	info_hash [20]byte;
	peer_id [20]byte;
}

type message struct {
	length int32;
	msg_id  int8;
	payload *[]byte;
}

type bttracker interface {
	Request();
}

type btpeer interface {
	KeepAlive();
	Choke();
	Unchoke();
	Interested();
	NotInterested();
	Have();
	BitField();
	Request();
	Piece();
	Cancel();
	Port();
}

type client struct {
	bool am_unchoked;
	bool am_interested;
	bool peer_unchoked;
	bool peer_interested;
	net.Conn conn;
}

type torrent struct {
	clients []client;
	trackers []tracker;
}

/*piece: <len=0009+X><id=7><index><begin><block>*/
func (client *client) Piece(int32 index, int32 begin, block []byte) os.Error{
	var msg  [13]byte;
	msg[0:4] = 5;
	msg[5] = ;
	msg[6:9] = piece;
	n,err := client.conn.Write(msg);
	return err;

}

/*cancel: <len=0013><id=8><index><begin><length>*/
func (client *client) Have(int32 index, int32 begin, int32 length,int32 piece) os.Error{
	msg[0:4] = 5;
	msg[5] = have;
	msg[6:9] = piece;
	n,err := client.conn.Write(msg);
	return err;
}

/*port: <len=0003><id=9><listen-port>*/
func (client *client) Have(int16 port) os.Error{
	msg[0:4] = 5;
	msg[5] = have;
	msg[6:9] = piece;
	n,err := client.conn.Write(msg);
	return err;
}
func (client *client) Have(int32 index) os.Error{
	var msg [9]byte;
	msg[0:4] = 5;
	msg[5] = have;
	msg[6:9] = piece;
	n,err := client.conn.Write(msg);
	return err;

}
func (client *client) Request(int32 index,int32 begin, int32 length) os.Error{
	var msg [17]byte;
	msg[0:4] = 13;
	msg[5] = request;
	msg[6:9] = index;
	msg[10:13] = begin;
	msg[14:17] = length;
	n,err := client.conn.Write(msg);
	return err;
}

func (client *client) Choke() os.Error{
	return msgNoPayLoad(choke);
}

func (client *client) Unchoke() os.Error{
	return msgNoPayLoad(unchoke);
}

func (client *client) Interested() os.Error{
	return msgNoPayLoad(interested);
}

func (client *client) Uninterested() os.Error{
	return msgNoPayLoad(uninterested);
}

func (client *client) Choke() os.Error{
	return msgNoPayLoad(choke);
}

func (client *client) msgNoPayload(int8 id) os.Error{
	var msg [5]byte;
	msg[0:4] = 1;
	msg[5] = id;
	n,err := client.conn.Write(msg);
	return err;
}

func (client *client) KeepAlive() os.Error{
	var length [4]byte = 0;
	n,err := client.conn.Write(length);
	return err;
}

<commit_msg>p2p code compiles, not complete<commit_after>package gotorrent

import "net"
import "os"
import "bytes"
import "strings"
import "encoding/binary"
/*It is (49+len(pstr)) bytes long.
handshake: <pstrlen><pstr><reserved><info_hash><peer_id>
pstrlen: string length of <pstr>, as a single raw byte
pstr: string identifier of the protocol
reserved: eight (8) reserved bytes. All current implementations use all zeroes. Each bit in these bytes can be used to change the behavior of the protocol. An email from Bram suggests that trailing bits should be used first, so that leading bits may be used to change the meaning of trailing bits.
info_hash: 20-byte SHA1 hash of the info key in the metainfo file. This is the same info_hash that is transmitted in tracker requests.
peer_id: 20-byte string used as a unique ID for the client. This is usually the same peer_id that is transmitted in tracker requests (but not always e.g. an anonymity option in Azureus).

pstrlen = 19, and pstr = "BitTorrent protocol"
peer_id = -AZ2060-
*/

const keepalive =0;
const (
choke = iota;
unchoke;
interested;
uninterested;
have;
bitfield;
request;
piece;
cancel;
port;
)

/*
keep-alive: <len=0000>
The keep-alive message is a message with zero bytes, specified with the length prefix set to zero. There is no message ID and no payload. Peers may close a connection if they receive no messages (keep-alive or any other message) for a certain period of time, so a keep-alive message must be sent to maintain the connection alive if no command have been sent for a given amount of time. This amount of time is generally two minutes.

choke: <len=0001><id=0>
The choke message is fixed-length and has no payload.

unchoke: <len=0001><id=1>
The unchoke message is fixed-length and has no payload.

interested: <len=0001><id=2>
The interested message is fixed-length and has no payload.

not interested: <len=0001><id=3>
The not interested message is fixed-length and has no payload.
have: <len=0005><id=4><piece index>

bitfield: <len=0001+X><id=5><bitfield>

request: <len=0013><id=6><index><begin><length>
The request message is fixed length, and is used to request a block. The payload contains the following information:
index: integer specifying the zero-based piece index
begin: integer specifying the zero-based byte offset within the piece
length: integer specifying the requested length.

piece: <len=0009+X><id=7><index><begin><block>
The piece message is variable length, where X is the length of the block. The payload contains the following information:
index: integer specifying the zero-based piece index
begin: integer specifying the zero-based byte offset within the piece
block: block of data, which is a subset of the piece specified by index.

cancel: <len=0013><id=8><index><begin><length>
The cancel message is fixed length, and is used to cancel block requests. The payload is identical to that of the "request" message. It is typically used during "End Game" (see the Algorithms section below).

port: <len=0003><id=9><listen-port>
The port message is sent by newer versions of the Mainline that implements a DHT tracker. The listen port is the port this peer's DHT node is listening on. This peer should be inserted in the local routing table (if DHT tracker is supported).
*/
type handshake struct {
	pstrlen uint8;
	reserved [8]byte;
}

type message struct {
	length uint32;
	msg_id  uint8;
	payload *[]byte;
}

type bttracker interface {
	Request();
}

type btpeer interface {
	KeepAlive();
	Choke();
	Unchoke();
	Interested();
	NotInterested();
	Have();
	BitField();
	Request();
	Piece();
	Cancel();
	Port();
}

type Client struct {
	am_unchoked bool;
	am_interested bool;
	peer_unchoked bool;
	peer_interested bool;
	conn *net.Conn;
	torrent *torrent
}

type tracker struct {
	name string;
}

type torrent struct {
	trackers []tracker;
	Pstr string;
	info_hash [20]byte;
	peer_id [20]byte;
}

/*handshake: <pstrlen><pstr><reserved><info_hash><peer_id>*/
func (c *Client) HandShake() os.Error{
	var pstrlen uint8 = (uint8)(len(c.torrent.Pstr));
	msg := make([]byte,49+pstrlen);
	msg[0] = pstrlen;
	bytes.Copy(msg[1:pstrlen], strings.Bytes(c.torrent.Pstr));
	bytes.Copy(msg[pstrlen+9:pstrlen+28], &(c.torrent.info_hash));
	bytes.Copy(msg[pstrlen+29:pstrlen+48], &(c.torrent.peer_id));
	_,err := c.conn.Write(msg);
	return err;
}

/*piece: <len=0009+X><id=7><index><begin><block>*/
func (c *Client) Piece(index uint32, begin uint32, block []byte) os.Error{
	blocksize := (uint32)(len(block));
	msg := make([]byte,9);
	binary.LittleEndian.PutUint32(msg[0:3],blocksize+9);
	binary.LittleEndian.PutUint32(msg[5:8],index);
	binary.LittleEndian.PutUint32(msg[9:12],begin);
	msg[4] = piece;
	_,err := c.conn.Write(msg);
	if err == nil {
		_,err2 := c.conn.Write(block);
		return err2
	}
	return err;

}

/*cancel: <len=0013><id=8><index><begin><length>*/
func (c *Client) Cancel(index uint32, begin uint32, length uint32) os.Error{
	msg := make([]byte,17);
	binary.LittleEndian.PutUint32(msg[0:3],13);
	msg[4] = cancel;
	binary.LittleEndian.PutUint32(msg[5:8],index);
	binary.LittleEndian.PutUint32(msg[9:12],begin);
	binary.LittleEndian.PutUint32(msg[13:16],length);
	_,err := c.conn.Write(msg);
	return err;
}

/*port: <len=0003><id=9><listen-port>*/
func (c *Client) Port(portnum uint16) os.Error{
	msg :=make([]byte,7);
	binary.LittleEndian.PutUint32(msg[0:3],3);
	msg[4] = port;
	binary.LittleEndian.PutUint16(msg[5:6],portnum);
	_,err := c.conn.Write(msg);
	return err;
}
func (c *Client) Have(index uint32) os.Error{
	msg := make([]byte,9);
	binary.LittleEndian.PutUint32(msg[0:3],5);
	binary.LittleEndian.PutUint32(msg[5:8],piece);
	msg[4] = have;
	_,err := c.conn.Write(msg);
	return err;

}
func (c *Client) Request(index uint32, begin uint32, length uint32) os.Error{
	msg := make([]byte,17);
	binary.LittleEndian.PutUint32(msg[0:3],13);
	binary.LittleEndian.PutUint32(msg[5:8],index);
	binary.LittleEndian.PutUint32(msg[9:12],begin);
	binary.LittleEndian.PutUint32(msg[13:16],length);
	msg[4] = request;
	_,err := c.conn.Write(msg);
	return err;
}

func (c *Client) msgNoPayLoad(id uint8) os.Error{
	msg :=make([]byte,5);
	binary.LittleEndian.PutUint32(msg[0:3],1);
	msg[4] = id;
	_,err := c.conn.Write(msg);
	return err;
}

func (c *Client) Unchoke() os.Error{
	return c.msgNoPayLoad(unchoke);
}

func (c *Client) Interested() os.Error{
	return c.msgNoPayLoad(interested);
}

func (c *Client) Uninterested() os.Error{
	return c.msgNoPayLoad(uninterested);
}

func (c *Client) Choke() os.Error{
	return c.msgNoPayLoad(choke);
}

func (c *Client) KeepAlive() os.Error{
	msg := make([]byte,4);
	binary.LittleEndian.PutUint32(msg[0:4],0);
	_,err := c.conn.Write(msg);
	return err;
}

<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"path"
)

var (
	homeUser   = os.Getenv("HOME")
	configPath = path.Join(homeUser, ".dnscli")
	configFile = path.Join(configPath, "dnsimple.json")
)

type Config struct {
	Token  string
	Domain string
	Mail   string
	ApiURL string
}

func createConfigPath() {
	// Create the config directory
	err := os.Mkdir(configPath, 0755)
	if err != nil {
		log.Fatal("pathConfig: ", err)
	}

	// Create an empty config file
	c := Config{}

	emptyConfig, err := json.Marshal(c)
	if err != nil {
		log.Fatal("pathConfig-Marshal: ", err)
	}

	ioutil.WriteFile(configFile, emptyConfig, 0644)
}
<commit_msg>Provide global config<commit_after>package main

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"path"
)

var (
	homeUser   = os.Getenv("HOME")
	configPath = path.Join(homeUser, ".dnscli")
	configFile = path.Join(configPath, "dnsimple.json")
	config     = readConfig()
)

type Config struct {
	Token  string
	Domain string
	Mail   string
	ApiURL string
}

func createConfigPath() {
	// Create the config directory
	err := os.Mkdir(configPath, 0755)
	if err != nil {
		log.Fatal("pathConfig: ", err)
	}

	// Create an empty config file
	c := Config{}

	emptyConfig, err := json.Marshal(c)
	if err != nil {
		log.Fatal("pathConfig-Marshal: ", err)
	}

	ioutil.WriteFile(configFile, emptyConfig, 0644)
}
<|endoftext|>


<commit_before>// Copyright 2017 Pilosa Corp.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.

package pilosa

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"sort"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/pilosa/go-pilosa/internal"
)

// Client is the HTTP client for Pilosa server.
type Client struct {
	cluster *Cluster
	client  *http.Client
}

// DefaultClient creates a client with the default address and options.
func DefaultClient() *Client {
	return NewClientWithURI(DefaultURI())
}

// NewClientWithURI creates a client with the given server address.
func NewClientWithURI(uri *URI) *Client {
	return NewClientWithCluster(NewClusterWithHost(uri), nil)
}

// NewClientWithCluster creates a client with the given cluster and options.
// Pass nil for default options.
func NewClientWithCluster(cluster *Cluster, options *ClientOptions) *Client {
	if options == nil {
		options = &ClientOptions{}
	}
	return &Client{
		cluster: cluster,
		client:  newHTTPClient(options.withDefaults()),
	}
}

// Query runs the given query against the server with the given options.
// Pass nil for default options.
func (c *Client) Query(query PQLQuery, options *QueryOptions) (*QueryResponse, error) {
	if err := query.Error(); err != nil {
		return nil, err
	}
	if options == nil {
		options = &QueryOptions{}
	}
	data := makeRequestData(query.serialize(), options)
	path := fmt.Sprintf("/index/%s/query", query.Index().name)
	_, buf, err := c.httpRequest("POST", path, data, rawResponse)
	if err != nil {
		return nil, err
	}
	iqr := &internal.QueryResponse{}
	err = proto.Unmarshal(buf, iqr)
	if err != nil {
		return nil, err
	}
	queryResponse, err := newQueryResponseFromInternal(iqr)
	if err != nil {
		return nil, err
	}
	if !queryResponse.Success {
		return nil, NewError(queryResponse.ErrorMessage)
	}
	return queryResponse, nil
}

// CreateIndex creates an index on the server using the given Index struct.
func (c *Client) CreateIndex(index *Index) error {
	data := []byte(index.options.String())
	path := fmt.Sprintf("/index/%s", index.name)
	_, _, err := c.httpRequest("POST", path,
		data, noResponse)
	if err != nil {
		return err
	}
	if index.options.TimeQuantum != TimeQuantumNone {
		err = c.patchIndexTimeQuantum(index)
	}
	return err

}

// CreateFrame creates a frame on the server using the given Frame struct.
func (c *Client) CreateFrame(frame *Frame) error {
	data := []byte(frame.options.String())
	path := fmt.Sprintf("/index/%s/frame/%s", frame.index.name, frame.name)
	_, _, err := c.httpRequest("POST", path, data, noResponse)
	if err != nil {
		return err
	}
	if frame.options.TimeQuantum != TimeQuantumNone {
		err = c.patchFrameTimeQuantum(frame)
	}
	return err

}

// EnsureIndex creates an index on the server if it does not exist.
func (c *Client) EnsureIndex(index *Index) error {
	err := c.CreateIndex(index)
	if err == ErrorIndexExists {
		return nil
	}
	return err
}

// EnsureFrame creates a frame on the server if it doesn't exists.
func (c *Client) EnsureFrame(frame *Frame) error {
	err := c.CreateFrame(frame)
	if err == ErrorFrameExists {
		return nil
	}
	return err
}

// DeleteIndex deletes an index on the server.
func (c *Client) DeleteIndex(index *Index) error {
	path := fmt.Sprintf("/index/%s", index.name)
	_, _, err := c.httpRequest("DELETE", path, nil, noResponse)
	return err

}

// DeleteFrame deletes a frame on the server.
func (c *Client) DeleteFrame(frame *Frame) error {
	path := fmt.Sprintf("/index/%s/frame/%s", frame.index.name, frame.name)
	_, _, err := c.httpRequest("DELETE", path, nil, noResponse)
	return err
}

// Schema returns the indexes and frames on the server.
func (c *Client) Schema() (*Schema, error) {
	_, buf, err := c.httpRequest("GET", "/index", nil, errorCheckedResponse)
	if err != nil {
		return nil, err
	}
	var schema *Schema
	err = json.NewDecoder(bytes.NewReader(buf)).Decode(&schema)
	if err != nil {
		return nil, err
	}
	return schema, nil
}

func (c *Client) ImportFrame(frame *Frame, bitIterator *CSVBitIterator, batchSize uint) error {
	const sliceWidth = 1048576
	canContinue := true
	bitGroup := map[uint64][]Bit{}
	var currentBatchSize uint
	indexName := frame.index.name
	frameName := frame.name

	callback := func(bit Bit) bool {
		slice := bit.ColumnID / sliceWidth
		if sliceArray, ok := bitGroup[slice]; ok {
			bitGroup[slice] = append(sliceArray, bit)
		} else {
			bitGroup[slice] = []Bit{bit}
		}
		currentBatchSize++
		return currentBatchSize < batchSize
	}
	for canContinue {
		err := bitIterator.Iterate(callback)
		if err == io.EOF {
			canContinue = false
		} else if err != nil {
			return err
		}
		for slice, bits := range bitGroup {
			err := c.importBits(indexName, frameName, slice, bits)
			if err != nil {
				return err
			}
		}
		bitGroup = map[uint64][]Bit{}
	}

	return nil
}

func (c *Client) importBits(indexName string, frameName string, slice uint64, bits []Bit) error {
	sort.Sort(bitsForSort(bits))
	nodes, err := c.fetchFragmentNodes(indexName, slice)
	if err != nil {
		return err
	}
	for _, node := range nodes {
		uri, err := NewURIFromAddress(node.Host)
		if err != nil {
			return err
		}
		client := NewClientWithURI(uri)
		err = client.importNode(bitsToImportRequest(indexName, frameName, slice, bits))
		if err != nil {
			return err
		}
	}

	return nil
}

func (c *Client) fetchFragmentNodes(indexName string, slice uint64) ([]FragmentNode, error) {
	path := fmt.Sprintf("/fragment/nodes?slice=%d&index=%s", slice, indexName)
	_, body, err := c.httpRequest("GET", path, []byte{}, errorCheckedResponse)
	if err != nil {
		return nil, err
	}
	fragmentNodes := []FragmentNode{}
	err = json.Unmarshal(body, &fragmentNodes)
	if err != nil {
		return nil, err
	}
	return fragmentNodes, nil
}

func (c *Client) importNode(request *internal.ImportRequest) error {
	data, _ := proto.Marshal(request)
	// request.Marshal never returns an error
	_, _, err := c.httpRequest("POST", "/import", data, noResponse)
	if err != nil {
		return err
	}

	return nil
}

func (c *Client) patchIndexTimeQuantum(index *Index) error {
	data := []byte(fmt.Sprintf(`{"timeQuantum": "%s"}`, index.options.TimeQuantum))
	path := fmt.Sprintf("/index/%s/time-quantum", index.name)
	_, _, err := c.httpRequest("PATCH", path, data, noResponse)
	return err
}

func (c *Client) patchFrameTimeQuantum(frame *Frame) error {
	data := []byte(fmt.Sprintf(`{"index": "%s", "frame": "%s", "timeQuantum": "%s"}`,
		frame.index.name, frame.name, frame.options.TimeQuantum))
	path := fmt.Sprintf("/index/%s/frame/%s/time-quantum", frame.index.name, frame.name)
	_, _, err := c.httpRequest("PATCH", path, data, noResponse)
	return err
}

func (c *Client) httpRequest(method string, path string, data []byte, returnResponse returnClientInfo) (*http.Response, []byte, error) {
	addr := c.cluster.Host()
	if addr == nil {
		return nil, nil, ErrorEmptyCluster
	}
	if data == nil {
		data = []byte{}
	}
	request, err := http.NewRequest(method, addr.Normalize()+path, bytes.NewReader(data))
	if err != nil {
		return nil, nil, err
	}
	// both Content-Type and Accept headers must be set for protobuf content
	request.Header.Set("Content-Type", "application/x-protobuf")
	request.Header.Set("Accept", "application/x-protobuf")
	response, err := c.client.Do(request)
	if err != nil {
		c.cluster.RemoveHost(addr)
		return nil, nil, err
	}
	defer response.Body.Close()
	// TODO: Optimize buffer creation
	buf, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, nil, err
	}
	if returnResponse == rawResponse {
		return response, buf, nil
	}
	if response.StatusCode < 200 || response.StatusCode >= 300 {
		msg := string(buf)
		err = matchError(msg)
		if err != nil {
			return nil, nil, err
		}
		return nil, nil, NewError(fmt.Sprintf("Server error (%d) %s: %s", response.StatusCode, response.Status, msg))
	}
	if returnResponse == noResponse {
		return nil, nil, nil
	}
	return response, buf, nil
}

func newHTTPClient(options *ClientOptions) *http.Client {
	transport := &http.Transport{
		Dial: (&net.Dialer{
			Timeout: options.ConnectTimeout,
		}).Dial,
		MaxIdleConnsPerHost: options.PoolSizePerRoute,
		MaxIdleConns:        options.TotalPoolSize,
	}
	return &http.Client{
		Transport: transport,
		Timeout:   options.SocketTimeout,
	}
}

func makeRequestData(query string, options *QueryOptions) []byte {
	request := &internal.QueryRequest{
		Query:       query,
		ColumnAttrs: options.Columns,
	}
	r, _ := proto.Marshal(request)
	// request.Marshal never returns an error
	return r
}

func matchError(msg string) error {
	switch msg {
	case "index already exists\n":
		return ErrorIndexExists
	case "frame already exists\n":
		return ErrorFrameExists
	}
	return nil
}

func bitsToImportRequest(indexName string, frameName string, slice uint64, bits []Bit) *internal.ImportRequest {
	rowIDs := make([]uint64, 0, len(bits))
	columnIDs := make([]uint64, 0, len(bits))
	timestamps := make([]int64, 0, len(bits))
	for _, bit := range bits {
		rowIDs = append(rowIDs, bit.RowID)
		columnIDs = append(columnIDs, bit.ColumnID)
		timestamps = append(timestamps, bit.Timestamp)
	}
	return &internal.ImportRequest{
		Index:      indexName,
		Frame:      frameName,
		Slice:      slice,
		RowIDs:     rowIDs,
		ColumnIDs:  columnIDs,
		Timestamps: timestamps,
	}
}

// ClientOptions control the properties of client connection to the server
type ClientOptions struct {
	SocketTimeout    time.Duration
	ConnectTimeout   time.Duration
	PoolSizePerRoute int
	TotalPoolSize    int
}

func (options *ClientOptions) withDefaults() (updated *ClientOptions) {
	// copy options so the original is not updated
	updated = &ClientOptions{}
	*updated = *options
	// impose defaults
	if updated.SocketTimeout <= 0 {
		updated.SocketTimeout = time.Second * 300
	}
	if updated.ConnectTimeout <= 0 {
		updated.ConnectTimeout = time.Second * 30
	}
	if updated.PoolSizePerRoute <= 0 {
		updated.PoolSizePerRoute = 10
	}
	if updated.TotalPoolSize <= 100 {
		updated.TotalPoolSize = 100
	}
	return
}

// QueryOptions contains options to customize the Query function.
type QueryOptions struct {
	// Columns enables returning columns in the query response.
	Columns bool
}

// Schema contains the index and frame metadata.
type Schema struct {
	Indexes []*IndexInfo `json:"indexes"`
}

// IndexInfo represents schema information for an index
type IndexInfo struct {
	Name   string       `json:"name"`
	Frames []*FrameInfo `json:"frames"`
}

type returnClientInfo uint

const (
	noResponse returnClientInfo = iota
	rawResponse
	errorCheckedResponse
)

type FragmentNode struct {
	Host         string
	InternalHost string
}
<commit_msg>reset current batch size<commit_after>// Copyright 2017 Pilosa Corp.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.

package pilosa

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"sort"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/pilosa/go-pilosa/internal"
)

// Client is the HTTP client for Pilosa server.
type Client struct {
	cluster *Cluster
	client  *http.Client
}

// DefaultClient creates a client with the default address and options.
func DefaultClient() *Client {
	return NewClientWithURI(DefaultURI())
}

// NewClientWithURI creates a client with the given server address.
func NewClientWithURI(uri *URI) *Client {
	return NewClientWithCluster(NewClusterWithHost(uri), nil)
}

// NewClientWithCluster creates a client with the given cluster and options.
// Pass nil for default options.
func NewClientWithCluster(cluster *Cluster, options *ClientOptions) *Client {
	if options == nil {
		options = &ClientOptions{}
	}
	return &Client{
		cluster: cluster,
		client:  newHTTPClient(options.withDefaults()),
	}
}

// Query runs the given query against the server with the given options.
// Pass nil for default options.
func (c *Client) Query(query PQLQuery, options *QueryOptions) (*QueryResponse, error) {
	if err := query.Error(); err != nil {
		return nil, err
	}
	if options == nil {
		options = &QueryOptions{}
	}
	data := makeRequestData(query.serialize(), options)
	path := fmt.Sprintf("/index/%s/query", query.Index().name)
	_, buf, err := c.httpRequest("POST", path, data, rawResponse)
	if err != nil {
		return nil, err
	}
	iqr := &internal.QueryResponse{}
	err = proto.Unmarshal(buf, iqr)
	if err != nil {
		return nil, err
	}
	queryResponse, err := newQueryResponseFromInternal(iqr)
	if err != nil {
		return nil, err
	}
	if !queryResponse.Success {
		return nil, NewError(queryResponse.ErrorMessage)
	}
	return queryResponse, nil
}

// CreateIndex creates an index on the server using the given Index struct.
func (c *Client) CreateIndex(index *Index) error {
	data := []byte(index.options.String())
	path := fmt.Sprintf("/index/%s", index.name)
	_, _, err := c.httpRequest("POST", path,
		data, noResponse)
	if err != nil {
		return err
	}
	if index.options.TimeQuantum != TimeQuantumNone {
		err = c.patchIndexTimeQuantum(index)
	}
	return err

}

// CreateFrame creates a frame on the server using the given Frame struct.
func (c *Client) CreateFrame(frame *Frame) error {
	data := []byte(frame.options.String())
	path := fmt.Sprintf("/index/%s/frame/%s", frame.index.name, frame.name)
	_, _, err := c.httpRequest("POST", path, data, noResponse)
	if err != nil {
		return err
	}
	if frame.options.TimeQuantum != TimeQuantumNone {
		err = c.patchFrameTimeQuantum(frame)
	}
	return err

}

// EnsureIndex creates an index on the server if it does not exist.
func (c *Client) EnsureIndex(index *Index) error {
	err := c.CreateIndex(index)
	if err == ErrorIndexExists {
		return nil
	}
	return err
}

// EnsureFrame creates a frame on the server if it doesn't exists.
func (c *Client) EnsureFrame(frame *Frame) error {
	err := c.CreateFrame(frame)
	if err == ErrorFrameExists {
		return nil
	}
	return err
}

// DeleteIndex deletes an index on the server.
func (c *Client) DeleteIndex(index *Index) error {
	path := fmt.Sprintf("/index/%s", index.name)
	_, _, err := c.httpRequest("DELETE", path, nil, noResponse)
	return err

}

// DeleteFrame deletes a frame on the server.
func (c *Client) DeleteFrame(frame *Frame) error {
	path := fmt.Sprintf("/index/%s/frame/%s", frame.index.name, frame.name)
	_, _, err := c.httpRequest("DELETE", path, nil, noResponse)
	return err
}

// Schema returns the indexes and frames on the server.
func (c *Client) Schema() (*Schema, error) {
	_, buf, err := c.httpRequest("GET", "/index", nil, errorCheckedResponse)
	if err != nil {
		return nil, err
	}
	var schema *Schema
	err = json.NewDecoder(bytes.NewReader(buf)).Decode(&schema)
	if err != nil {
		return nil, err
	}
	return schema, nil
}

// ImportFrame imports bits from the given CSV iterator
func (c *Client) ImportFrame(frame *Frame, bitIterator *CSVBitIterator, batchSize uint) error {
	const sliceWidth = 1048576
	canContinue := true
	bitGroup := map[uint64][]Bit{}
	var currentBatchSize uint
	indexName := frame.index.name
	frameName := frame.name

	callback := func(bit Bit) bool {
		slice := bit.ColumnID / sliceWidth
		if sliceArray, ok := bitGroup[slice]; ok {
			bitGroup[slice] = append(sliceArray, bit)
		} else {
			bitGroup[slice] = []Bit{bit}
		}
		currentBatchSize++
		return currentBatchSize < batchSize
	}
	for canContinue {
		err := bitIterator.Iterate(callback)
		if err == io.EOF {
			canContinue = false
		} else if err != nil {
			return err
		}
		for slice, bits := range bitGroup {
			err := c.importBits(indexName, frameName, slice, bits)
			if err != nil {
				return err
			}
		}
		bitGroup = map[uint64][]Bit{}
		currentBatchSize = 0
	}

	return nil
}

func (c *Client) importBits(indexName string, frameName string, slice uint64, bits []Bit) error {
	sort.Sort(bitsForSort(bits))
	nodes, err := c.fetchFragmentNodes(indexName, slice)
	if err != nil {
		return err
	}
	for _, node := range nodes {
		uri, err := NewURIFromAddress(node.Host)
		if err != nil {
			return err
		}
		client := NewClientWithURI(uri)
		err = client.importNode(bitsToImportRequest(indexName, frameName, slice, bits))
		if err != nil {
			return err
		}
	}

	return nil
}

func (c *Client) fetchFragmentNodes(indexName string, slice uint64) ([]FragmentNode, error) {
	path := fmt.Sprintf("/fragment/nodes?slice=%d&index=%s", slice, indexName)
	_, body, err := c.httpRequest("GET", path, []byte{}, errorCheckedResponse)
	if err != nil {
		return nil, err
	}
	fragmentNodes := []FragmentNode{}
	err = json.Unmarshal(body, &fragmentNodes)
	if err != nil {
		return nil, err
	}
	return fragmentNodes, nil
}

func (c *Client) importNode(request *internal.ImportRequest) error {
	data, _ := proto.Marshal(request)
	// request.Marshal never returns an error
	_, _, err := c.httpRequest("POST", "/import", data, noResponse)
	if err != nil {
		return err
	}

	return nil
}

func (c *Client) patchIndexTimeQuantum(index *Index) error {
	data := []byte(fmt.Sprintf(`{"timeQuantum": "%s"}`, index.options.TimeQuantum))
	path := fmt.Sprintf("/index/%s/time-quantum", index.name)
	_, _, err := c.httpRequest("PATCH", path, data, noResponse)
	return err
}

func (c *Client) patchFrameTimeQuantum(frame *Frame) error {
	data := []byte(fmt.Sprintf(`{"index": "%s", "frame": "%s", "timeQuantum": "%s"}`,
		frame.index.name, frame.name, frame.options.TimeQuantum))
	path := fmt.Sprintf("/index/%s/frame/%s/time-quantum", frame.index.name, frame.name)
	_, _, err := c.httpRequest("PATCH", path, data, noResponse)
	return err
}

func (c *Client) httpRequest(method string, path string, data []byte, returnResponse returnClientInfo) (*http.Response, []byte, error) {
	addr := c.cluster.Host()
	if addr == nil {
		return nil, nil, ErrorEmptyCluster
	}
	if data == nil {
		data = []byte{}
	}
	request, err := http.NewRequest(method, addr.Normalize()+path, bytes.NewReader(data))
	if err != nil {
		return nil, nil, err
	}
	// both Content-Type and Accept headers must be set for protobuf content
	request.Header.Set("Content-Type", "application/x-protobuf")
	request.Header.Set("Accept", "application/x-protobuf")
	response, err := c.client.Do(request)
	if err != nil {
		c.cluster.RemoveHost(addr)
		return nil, nil, err
	}
	defer response.Body.Close()
	// TODO: Optimize buffer creation
	buf, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, nil, err
	}
	if returnResponse == rawResponse {
		return response, buf, nil
	}
	if response.StatusCode < 200 || response.StatusCode >= 300 {
		msg := string(buf)
		err = matchError(msg)
		if err != nil {
			return nil, nil, err
		}
		return nil, nil, NewError(fmt.Sprintf("Server error (%d) %s: %s", response.StatusCode, response.Status, msg))
	}
	if returnResponse == noResponse {
		return nil, nil, nil
	}
	return response, buf, nil
}

func newHTTPClient(options *ClientOptions) *http.Client {
	transport := &http.Transport{
		Dial: (&net.Dialer{
			Timeout: options.ConnectTimeout,
		}).Dial,
		MaxIdleConnsPerHost: options.PoolSizePerRoute,
		MaxIdleConns:        options.TotalPoolSize,
	}
	return &http.Client{
		Transport: transport,
		Timeout:   options.SocketTimeout,
	}
}

func makeRequestData(query string, options *QueryOptions) []byte {
	request := &internal.QueryRequest{
		Query:       query,
		ColumnAttrs: options.Columns,
	}
	r, _ := proto.Marshal(request)
	// request.Marshal never returns an error
	return r
}

func matchError(msg string) error {
	switch msg {
	case "index already exists\n":
		return ErrorIndexExists
	case "frame already exists\n":
		return ErrorFrameExists
	}
	return nil
}

func bitsToImportRequest(indexName string, frameName string, slice uint64, bits []Bit) *internal.ImportRequest {
	rowIDs := make([]uint64, 0, len(bits))
	columnIDs := make([]uint64, 0, len(bits))
	timestamps := make([]int64, 0, len(bits))
	for _, bit := range bits {
		rowIDs = append(rowIDs, bit.RowID)
		columnIDs = append(columnIDs, bit.ColumnID)
		timestamps = append(timestamps, bit.Timestamp)
	}
	return &internal.ImportRequest{
		Index:      indexName,
		Frame:      frameName,
		Slice:      slice,
		RowIDs:     rowIDs,
		ColumnIDs:  columnIDs,
		Timestamps: timestamps,
	}
}

// ClientOptions control the properties of client connection to the server
type ClientOptions struct {
	SocketTimeout    time.Duration
	ConnectTimeout   time.Duration
	PoolSizePerRoute int
	TotalPoolSize    int
}

func (options *ClientOptions) withDefaults() (updated *ClientOptions) {
	// copy options so the original is not updated
	updated = &ClientOptions{}
	*updated = *options
	// impose defaults
	if updated.SocketTimeout <= 0 {
		updated.SocketTimeout = time.Second * 300
	}
	if updated.ConnectTimeout <= 0 {
		updated.ConnectTimeout = time.Second * 30
	}
	if updated.PoolSizePerRoute <= 0 {
		updated.PoolSizePerRoute = 10
	}
	if updated.TotalPoolSize <= 100 {
		updated.TotalPoolSize = 100
	}
	return
}

// QueryOptions contains options to customize the Query function.
type QueryOptions struct {
	// Columns enables returning columns in the query response.
	Columns bool
}

// Schema contains the index and frame metadata.
type Schema struct {
	Indexes []*IndexInfo `json:"indexes"`
}

// IndexInfo represents schema information for an index
type IndexInfo struct {
	Name   string       `json:"name"`
	Frames []*FrameInfo `json:"frames"`
}

type returnClientInfo uint

const (
	noResponse returnClientInfo = iota
	rawResponse
	errorCheckedResponse
)

type FragmentNode struct {
	Host         string
	InternalHost string
}
<|endoftext|>


<commit_before>//
// SPDX-License-Identifier: BSD-3-Clause
//

package gofish

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"

	"strings"

	"github.com/stmcginnis/gofish/common"
	"github.com/stmcginnis/gofish/redfish"
)

const userAgent = "gofish/1.0"
const applicationJSON = "application/json"

// APIClient represents a connection to a Redfish/Swordfish enabled service
// or device.
type APIClient struct {
	// Endpoint is the URL of the *fish service
	endpoint string

	// HTTPClient is for direct http actions
	HTTPClient *http.Client

	// Service is the ServiceRoot of this Redfish instance
	Service *Service

	// Auth information saved for later to be able to log out
	auth *redfish.AuthToken

	// dumpWriter will receive HTTP dumps if non-nil.
	dumpWriter io.Writer
}

// ClientConfig holds the settings for establishing a connection.
type ClientConfig struct {
	// Endpoint is the URL of the redfish service
	Endpoint string

	// Username is the optional user name to authenticate with.
	Username string

	// Password is the password to use for authentication.
	Password string

	// Insecure controls whether to enforce SSL certificate validity.
	Insecure bool

	// HTTPClient is the optional client to connect with.
	HTTPClient *http.Client

	// DumpWriter is an optional io.Writer to receive dumps of HTTP
	// requests and responses.
	DumpWriter io.Writer
}

// Connect creates a new client connection to a Redfish service.
func Connect(config ClientConfig) (c *APIClient, err error) {

	if !strings.HasPrefix(config.Endpoint, "http") {
		return c, fmt.Errorf("endpoint must starts with http or https")
	}

	client := &APIClient{
		endpoint:   config.Endpoint,
		dumpWriter: config.DumpWriter,
	}

	if config.HTTPClient == nil {
		defaultTransport := http.DefaultTransport.(*http.Transport)
		transport := &http.Transport{
			Proxy:                 defaultTransport.Proxy,
			DialContext:           defaultTransport.DialContext,
			MaxIdleConns:          defaultTransport.MaxIdleConns,
			IdleConnTimeout:       defaultTransport.IdleConnTimeout,
			ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
			TLSHandshakeTimeout:   defaultTransport.TLSHandshakeTimeout,
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: config.Insecure,
			},
		}
		client.HTTPClient = &http.Client{Transport: transport}
	} else {
		client.HTTPClient = config.HTTPClient
	}

	if config.Username != "" {
		// Authenticate with the service
		service, err := ServiceRoot(client)
		if err != nil {
			return nil, err
		}

		auth, err := service.CreateSession(config.Username, config.Password)
		if err != nil {
			return nil, err
		}

		client.Service = service
		client.auth = auth
	}

	return client, err
}

// ConnectDefault creates an unauthenticated connection to a Redfish service.
func ConnectDefault(endpoint string) (c *APIClient, err error) {
	if !strings.HasPrefix(endpoint, "http") {
		return c, fmt.Errorf("endpoint must starts with http or https")
	}

	client := &APIClient{endpoint: endpoint}
	client.HTTPClient = &http.Client{}

	// Fetch the service root
	service, err := ServiceRoot(client)
	if err != nil {
		return nil, err
	}
	client.Service = service

	return client, err
}

// Get performs a GET request against the Redfish service.
func (c *APIClient) Get(url string) (*http.Response, error) {
	relativePath := url
	if relativePath == "" {
		relativePath = common.DefaultServiceRoot
	}

	return c.runRequest("GET", relativePath, nil)
}

// Post performs a Post request against the Redfish service.
func (c *APIClient) Post(url string, payload interface{}) (*http.Response, error) {
	return c.runRequest("POST", url, payload)
}

// Put performs a Put request against the Redfish service.
func (c *APIClient) Put(url string, payload interface{}) (*http.Response, error) {
	return c.runRequest("PUT", url, payload)
}

// Patch performs a Patch request against the Redfish service.
func (c *APIClient) Patch(url string, payload interface{}) (*http.Response, error) {
	return c.runRequest("PATCH", url, payload)
}

// Delete performs a Delete request against the Redfish service.
func (c *APIClient) Delete(url string) error {
	resp, err := c.runRequest("DELETE", url, nil)
	if err != nil {
		return err
	}
	if resp.Body != nil {
		resp.Body.Close()
	}
	return nil
}

// runRequest actually performs the REST calls.
func (c *APIClient) runRequest(method string, url string, payload interface{}) (*http.Response, error) {
	if url == "" {
		return nil, fmt.Errorf("unable to execute request, no target provided")
	}

	var payloadBuffer io.ReadSeeker
	if payload != nil {
		body, err := json.Marshal(payload)
		if err != nil {
			return nil, err
		}
		payloadBuffer = bytes.NewReader(body)
	}

	endpoint := fmt.Sprintf("%s%s", c.endpoint, url)
	req, err := http.NewRequest(method, endpoint, payloadBuffer)
	if err != nil {
		return nil, err
	}

	// Add common headers
	req.Header.Set("User-Agent", userAgent)
	req.Header.Set("Accept", applicationJSON)

	// Add content info if present
	if payload != nil {
		req.Header.Set("Content-Type", applicationJSON)
	}

	// Add auth info if authenticated
	if c.auth != nil && c.auth.Token != "" {
		req.Header.Set("X-Auth-Token", c.auth.Token)
	}
	req.Close = true

	// Dump request if needed.
	if c.dumpWriter != nil {
		d, err := httputil.DumpRequestOut(req, true)
		if err != nil {
			return nil, err
		}

		d = append(d, '\n')
		_, err = c.dumpWriter.Write(d)
		if err != nil {
			panic(err)
		}
	}

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, err
	}

	// Dump response if needed.
	if c.dumpWriter != nil {
		d, err := httputil.DumpResponse(resp, true)
		if err != nil {
			defer resp.Body.Close()
			return nil, err
		}

		d = append(d, '\n')
		_, err = c.dumpWriter.Write(d)
		if err != nil {
			panic(err)
		}
	}

	if resp.StatusCode != 200 && resp.StatusCode != 201 && resp.StatusCode != 202 && resp.StatusCode != 204 {
		payload, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		defer resp.Body.Close()
		return nil, fmt.Errorf("%d: %s", resp.StatusCode, string(payload))
	}

	return resp, err
}

// Logout will delete any active session. Useful to defer logout when creating
// a new connection.
func (c *APIClient) Logout() {
	if c.Service != nil && c.auth != nil {
		_ = c.Service.DeleteSession(c.auth.Session)
	}
}
<commit_msg>Also don't operate if resp is nil<commit_after>//
// SPDX-License-Identifier: BSD-3-Clause
//

package gofish

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"

	"strings"

	"github.com/stmcginnis/gofish/common"
	"github.com/stmcginnis/gofish/redfish"
)

const userAgent = "gofish/1.0"
const applicationJSON = "application/json"

// APIClient represents a connection to a Redfish/Swordfish enabled service
// or device.
type APIClient struct {
	// Endpoint is the URL of the *fish service
	endpoint string

	// HTTPClient is for direct http actions
	HTTPClient *http.Client

	// Service is the ServiceRoot of this Redfish instance
	Service *Service

	// Auth information saved for later to be able to log out
	auth *redfish.AuthToken

	// dumpWriter will receive HTTP dumps if non-nil.
	dumpWriter io.Writer
}

// ClientConfig holds the settings for establishing a connection.
type ClientConfig struct {
	// Endpoint is the URL of the redfish service
	Endpoint string

	// Username is the optional user name to authenticate with.
	Username string

	// Password is the password to use for authentication.
	Password string

	// Insecure controls whether to enforce SSL certificate validity.
	Insecure bool

	// HTTPClient is the optional client to connect with.
	HTTPClient *http.Client

	// DumpWriter is an optional io.Writer to receive dumps of HTTP
	// requests and responses.
	DumpWriter io.Writer
}

// Connect creates a new client connection to a Redfish service.
func Connect(config ClientConfig) (c *APIClient, err error) {

	if !strings.HasPrefix(config.Endpoint, "http") {
		return c, fmt.Errorf("endpoint must starts with http or https")
	}

	client := &APIClient{
		endpoint:   config.Endpoint,
		dumpWriter: config.DumpWriter,
	}

	if config.HTTPClient == nil {
		defaultTransport := http.DefaultTransport.(*http.Transport)
		transport := &http.Transport{
			Proxy:                 defaultTransport.Proxy,
			DialContext:           defaultTransport.DialContext,
			MaxIdleConns:          defaultTransport.MaxIdleConns,
			IdleConnTimeout:       defaultTransport.IdleConnTimeout,
			ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
			TLSHandshakeTimeout:   defaultTransport.TLSHandshakeTimeout,
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: config.Insecure,
			},
		}
		client.HTTPClient = &http.Client{Transport: transport}
	} else {
		client.HTTPClient = config.HTTPClient
	}

	if config.Username != "" {
		// Authenticate with the service
		service, err := ServiceRoot(client)
		if err != nil {
			return nil, err
		}

		auth, err := service.CreateSession(config.Username, config.Password)
		if err != nil {
			return nil, err
		}

		client.Service = service
		client.auth = auth
	}

	return client, err
}

// ConnectDefault creates an unauthenticated connection to a Redfish service.
func ConnectDefault(endpoint string) (c *APIClient, err error) {
	if !strings.HasPrefix(endpoint, "http") {
		return c, fmt.Errorf("endpoint must starts with http or https")
	}

	client := &APIClient{endpoint: endpoint}
	client.HTTPClient = &http.Client{}

	// Fetch the service root
	service, err := ServiceRoot(client)
	if err != nil {
		return nil, err
	}
	client.Service = service

	return client, err
}

// Get performs a GET request against the Redfish service.
func (c *APIClient) Get(url string) (*http.Response, error) {
	relativePath := url
	if relativePath == "" {
		relativePath = common.DefaultServiceRoot
	}

	return c.runRequest("GET", relativePath, nil)
}

// Post performs a Post request against the Redfish service.
func (c *APIClient) Post(url string, payload interface{}) (*http.Response, error) {
	return c.runRequest("POST", url, payload)
}

// Put performs a Put request against the Redfish service.
func (c *APIClient) Put(url string, payload interface{}) (*http.Response, error) {
	return c.runRequest("PUT", url, payload)
}

// Patch performs a Patch request against the Redfish service.
func (c *APIClient) Patch(url string, payload interface{}) (*http.Response, error) {
	return c.runRequest("PATCH", url, payload)
}

// Delete performs a Delete request against the Redfish service.
func (c *APIClient) Delete(url string) error {
	resp, err := c.runRequest("DELETE", url, nil)
	if err != nil {
		return err
	}
	if resp != nil && resp.Body != nil {
		resp.Body.Close()
	}
	return nil
}

// runRequest actually performs the REST calls.
func (c *APIClient) runRequest(method string, url string, payload interface{}) (*http.Response, error) {
	if url == "" {
		return nil, fmt.Errorf("unable to execute request, no target provided")
	}

	var payloadBuffer io.ReadSeeker
	if payload != nil {
		body, err := json.Marshal(payload)
		if err != nil {
			return nil, err
		}
		payloadBuffer = bytes.NewReader(body)
	}

	endpoint := fmt.Sprintf("%s%s", c.endpoint, url)
	req, err := http.NewRequest(method, endpoint, payloadBuffer)
	if err != nil {
		return nil, err
	}

	// Add common headers
	req.Header.Set("User-Agent", userAgent)
	req.Header.Set("Accept", applicationJSON)

	// Add content info if present
	if payload != nil {
		req.Header.Set("Content-Type", applicationJSON)
	}

	// Add auth info if authenticated
	if c.auth != nil && c.auth.Token != "" {
		req.Header.Set("X-Auth-Token", c.auth.Token)
	}
	req.Close = true

	// Dump request if needed.
	if c.dumpWriter != nil {
		d, err := httputil.DumpRequestOut(req, true)
		if err != nil {
			return nil, err
		}

		d = append(d, '\n')
		_, err = c.dumpWriter.Write(d)
		if err != nil {
			panic(err)
		}
	}

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, err
	}

	// Dump response if needed.
	if c.dumpWriter != nil {
		d, err := httputil.DumpResponse(resp, true)
		if err != nil {
			defer resp.Body.Close()
			return nil, err
		}

		d = append(d, '\n')
		_, err = c.dumpWriter.Write(d)
		if err != nil {
			panic(err)
		}
	}

	if resp.StatusCode != 200 && resp.StatusCode != 201 && resp.StatusCode != 202 && resp.StatusCode != 204 {
		payload, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		defer resp.Body.Close()
		return nil, fmt.Errorf("%d: %s", resp.StatusCode, string(payload))
	}

	return resp, err
}

// Logout will delete any active session. Useful to defer logout when creating
// a new connection.
func (c *APIClient) Logout() {
	if c.Service != nil && c.auth != nil {
		_ = c.Service.DeleteSession(c.auth.Session)
	}
}
<|endoftext|>