<commit_before>package api

import (
	"crypto/tls"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"golang.org/x/net/http2"

	"github.com/hashicorp/go-cleanhttp"
	"github.com/hashicorp/go-rootcerts"
	"github.com/sethgrid/pester"
)

const EnvVaultAddress = "VAULT_ADDR"
const EnvVaultCACert = "VAULT_CACERT"
const EnvVaultCAPath = "VAULT_CAPATH"
const EnvVaultClientCert = "VAULT_CLIENT_CERT"
const EnvVaultClientKey = "VAULT_CLIENT_KEY"
const EnvVaultInsecure = "VAULT_SKIP_VERIFY"
const EnvVaultTLSServerName = "VAULT_TLS_SERVER_NAME"
const EnvVaultWrapTTL = "VAULT_WRAP_TTL"
const EnvVaultMaxRetries = "VAULT_MAX_RETRIES"
const EnvVaultToken = "VAULT_TOKEN"

// WrappingLookupFunc is a function that, given an HTTP verb and a path,
// returns an optional string duration to be used for response wrapping (e.g.
// "15s", or simply "15"). The path will not begin with "/v1/" or "v1/" or "/",
// however, end-of-path forward slashes are not trimmed, so must match your
// called path precisely.
type WrappingLookupFunc func(operation, path string) string

// Config is used to configure the creation of the client.
type Config struct {
	// Address is the address of the Vault server. This should be a complete
	// URL such as "http://vault.example.com". If you need a custom SSL
	// cert or want to enable insecure mode, you need to specify a custom
	// HttpClient.
	Address string

	// HttpClient is the HTTP client to use, which will currently always have the
	// same values as http.DefaultClient. This is used to control redirect behavior.
	HttpClient *http.Client

	redirectSetup sync.Once

	// MaxRetries controls the maximum number of times to retry when a 5xx error
	// occurs. Set to 0 or less to disable retrying. Defaults to 0.
	MaxRetries int
}

// TLSConfig contains the parameters needed to configure TLS on the HTTP client
// used to communicate with Vault.
type TLSConfig struct {
	// CACert is the path to a PEM-encoded CA cert file to use to verify the
	// Vault server SSL certificate.
	CACert string

	// CAPath is the path to a directory of PEM-encoded CA cert files to verify
	// the Vault server SSL certificate.
	CAPath string

	// ClientCert is the path to the certificate for Vault communication
	ClientCert string

	// ClientKey is the path to the private key for Vault communication
	ClientKey string

	// TLSServerName, if set, is used to set the SNI host when connecting via
	// TLS.
	TLSServerName string

	// Insecure enables or disables SSL verification
	Insecure bool
}

// DefaultConfig returns a default configuration for the client. It is
// safe to modify the return value of this function.
//
// The default Address is https://127.0.0.1:8200, but this can be overridden by
// setting the `VAULT_ADDR` environment variable.
func DefaultConfig() *Config {
	config := &Config{
		Address:    "https://127.0.0.1:8200",
		HttpClient: cleanhttp.DefaultClient(),
	}
	config.HttpClient.Timeout = time.Second * 60
	transport := config.HttpClient.Transport.(*http.Transport)
	transport.TLSHandshakeTimeout = 10 * time.Second
	transport.TLSClientConfig = &tls.Config{
		MinVersion: tls.VersionTLS12,
	}

	if v := os.Getenv(EnvVaultAddress); v != "" {
		config.Address = v
	}

	return config
}

// ConfigureTLS takes a set of TLS configurations and applies those to the the HTTP client.
func (c *Config) ConfigureTLS(t *TLSConfig) error {
	if c.HttpClient == nil {
		c.HttpClient = DefaultConfig().HttpClient
	}

	var clientCert tls.Certificate
	foundClientCert := false
	if t.CACert != "" || t.CAPath != "" || t.ClientCert != "" || t.ClientKey != "" || t.Insecure {
		if t.ClientCert != "" && t.ClientKey != "" {
			var err error
			clientCert, err = tls.LoadX509KeyPair(t.ClientCert, t.ClientKey)
			if err != nil {
				return err
			}
			foundClientCert = true
		} else if t.ClientCert != "" || t.ClientKey != "" {
			return fmt.Errorf("Both client cert and client key must be provided")
		}
	}

	clientTLSConfig := c.HttpClient.Transport.(*http.Transport).TLSClientConfig
	rootConfig := &rootcerts.Config{
		CAFile: t.CACert,
		CAPath: t.CAPath,
	}
	if err := rootcerts.ConfigureTLS(clientTLSConfig, rootConfig); err != nil {
		return err
	}

	clientTLSConfig.InsecureSkipVerify = t.Insecure

	if foundClientCert {
		clientTLSConfig.Certificates = []tls.Certificate{clientCert}
	}
	if t.TLSServerName != "" {
		clientTLSConfig.ServerName = t.TLSServerName
	}

	return nil
}

// ReadEnvironment reads configuration information from the
// environment. If there is an error, no configuration value
// is updated.
func (c *Config) ReadEnvironment() error {
	var envAddress string
	var envCACert string
	var envCAPath string
	var envClientCert string
	var envClientKey string
	var envInsecure bool
	var envTLSServerName string
	var envMaxRetries *uint64

	// Parse the environment variables
	if v := os.Getenv(EnvVaultAddress); v != "" {
		envAddress = v
	}
	if v := os.Getenv(EnvVaultMaxRetries); v != "" {
		maxRetries, err := strconv.ParseUint(v, 10, 32)
		if err != nil {
			return err
		}
		envMaxRetries = &maxRetries
	}
	if v := os.Getenv(EnvVaultCACert); v != "" {
		envCACert = v
	}
	if v := os.Getenv(EnvVaultCAPath); v != "" {
		envCAPath = v
	}
	if v := os.Getenv(EnvVaultClientCert); v != "" {
		envClientCert = v
	}
	if v := os.Getenv(EnvVaultClientKey); v != "" {
		envClientKey = v
	}
	if v := os.Getenv(EnvVaultInsecure); v != "" {
		var err error
		envInsecure, err = strconv.ParseBool(v)
		if err != nil {
			return fmt.Errorf("Could not parse VAULT_SKIP_VERIFY")
		}
	}
	if v := os.Getenv(EnvVaultTLSServerName); v != "" {
		envTLSServerName = v
	}

	// Configure the HTTP clients TLS configuration.
	t := &TLSConfig{
		CACert:        envCACert,
		CAPath:        envCAPath,
		ClientCert:    envClientCert,
		ClientKey:     envClientKey,
		TLSServerName: envTLSServerName,
		Insecure:      envInsecure,
	}
	if err := c.ConfigureTLS(t); err != nil {
		return err
	}

	if envAddress != "" {
		c.Address = envAddress
	}

	if envMaxRetries != nil {
		c.MaxRetries = int(*envMaxRetries) + 1
	}

	return nil
}

// Client is the client to the Vault API. Create a client with
// NewClient.
type Client struct {
	addr               *url.URL
	config             *Config
	token              string
	wrappingLookupFunc WrappingLookupFunc
}

// NewClient returns a new client for the given configuration.
//
// If the environment variable `VAULT_TOKEN` is present, the token will be
// automatically added to the client. Otherwise, you must manually call
// `SetToken()`.
func NewClient(c *Config) (*Client, error) {
	if c == nil {
		c = DefaultConfig()
		if err := c.ReadEnvironment(); err != nil {
			return nil, fmt.Errorf("error reading environment: %v", err)
		}
	}

	u, err := url.Parse(c.Address)
	if err != nil {
		return nil, err
	}

	if c.HttpClient == nil {
		c.HttpClient = DefaultConfig().HttpClient
	}

	tp := c.HttpClient.Transport.(*http.Transport)
	if err := http2.ConfigureTransport(tp); err != nil {
		return nil, err
	}

	redirFunc := func() {
		// Ensure redirects are not automatically followed
		// Note that this is sane for the API client as it has its own
		// redirect handling logic (and thus also for command/meta),
		// but in e.g. http_test actual redirect handling is necessary
		c.HttpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
			// Returning this value causes the Go net library to not close the
			// response body and to nil out the error. Otherwise pester tries
			// three times on every redirect because it sees an error from this
			// function (to prevent redirects) passing through to it.
			return http.ErrUseLastResponse
		}
	}

	c.redirectSetup.Do(redirFunc)

	client := &Client{
		addr:   u,
		config: c,
	}

	if token := os.Getenv(EnvVaultToken); token != "" {
		client.SetToken(token)
	}

	return client, nil
}

// Sets the address of Vault in the client. The format of address should be
// "<Scheme>://<Host>:<Port>". Setting this on a client will override the
// value of VAULT_ADDR environment variable.
func (c *Client) SetAddress(addr string) error {
	var err error
	if c.addr, err = url.Parse(addr); err != nil {
		return fmt.Errorf("failed to set address: %v", err)
	}

	return nil
}

// Address returns the Vault URL the client is configured to connect to
func (c *Client) Address() string {
	return c.addr.String()
}

// SetMaxRetries sets the number of retries that will be used in the case of certain errors
func (c *Client) SetMaxRetries(retries int) {
	c.config.MaxRetries = retries
}

// SetWrappingLookupFunc sets a lookup function that returns desired wrap TTLs
// for a given operation and path
func (c *Client) SetWrappingLookupFunc(lookupFunc WrappingLookupFunc) {
	c.wrappingLookupFunc = lookupFunc
}

// Token returns the access token being used by this client. It will
// return the empty string if there is no token set.
func (c *Client) Token() string {
	return c.token
}

// SetToken sets the token directly. This won't perform any auth
// verification, it simply sets the token properly for future requests.
func (c *Client) SetToken(v string) {
	c.token = v
}

// ClearToken deletes the token if it is set or does nothing otherwise.
func (c *Client) ClearToken() {
	c.token = ""
}

// NewRequest creates a new raw request object to query the Vault server
// configured for this client. This is an advanced method and generally
// doesn't need to be called externally.
func (c *Client) NewRequest(method, path string) *Request {
	req := &Request{
		Method: method,
		URL: &url.URL{
			User:   c.addr.User,
			Scheme: c.addr.Scheme,
			Host:   c.addr.Host,
			Path:   path,
		},
		ClientToken: c.token,
		Params:      make(map[string][]string),
	}

	var lookupPath string
	switch {
	case strings.HasPrefix(path, "/v1/"):
		lookupPath = strings.TrimPrefix(path, "/v1/")
	case strings.HasPrefix(path, "v1/"):
		lookupPath = strings.TrimPrefix(path, "v1/")
	default:
		lookupPath = path
	}
	if c.wrappingLookupFunc != nil {
		req.WrapTTL = c.wrappingLookupFunc(method, lookupPath)
	} else {
		req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath)
	}

	return req
}

// RawRequest performs the raw request given. This request may be against
// a Vault server not configured with this client. This is an advanced operation
// that generally won't need to be called externally.
func (c *Client) RawRequest(r *Request) (*Response, error) {
	redirectCount := 0
START:
	req, err := r.ToHTTP()
	if err != nil {
		return nil, err
	}

	client := pester.NewExtendedClient(c.config.HttpClient)
	client.Backoff = pester.LinearJitterBackoff
	client.MaxRetries = c.config.MaxRetries

	var result *Response
	resp, err := client.Do(req)
	if resp != nil {
		result = &Response{Response: resp}
	}
	if err != nil {
		if strings.Contains(err.Error(), "tls: oversized") {
			err = fmt.Errorf(
				"%s\n\n"+
					"This error usually means that the server is running with TLS disabled\n"+
					"but the client is configured to use TLS. Please either enable TLS\n"+
					"on the server or run the client with -address set to an address\n"+
					"that uses the http protocol:\n\n"+
					"    vault <command> -address http://<address>\n\n"+
					"You can also set the VAULT_ADDR environment variable:\n\n\n"+
					"    VAULT_ADDR=http://<address> vault <command>\n\n"+
					"where <address> is replaced by the actual address to the server.",
				err)
		}
		return result, err
	}

	// Check for a redirect, only allowing for a single redirect
	if (resp.StatusCode == 301 || resp.StatusCode == 302 || resp.StatusCode == 307) && redirectCount == 0 {
		// Parse the updated location
		respLoc, err := resp.Location()
		if err != nil {
			return result, err
		}

		// Ensure a protocol downgrade doesn't happen
		if req.URL.Scheme == "https" && respLoc.Scheme != "https" {
			return result, fmt.Errorf("redirect would cause protocol downgrade")
		}

		// Update the request
		r.URL = respLoc

		// Reset the request body if any
		if err := r.ResetJSONBody(); err != nil {
			return result, err
		}

		// Retry the request
		redirectCount++
		goto START
	}

	if err := result.Error(); err != nil {
		return result, err
	}

	return result, nil
}
<commit_msg>Respect the configured address's path in the client (#2588)<commit_after>package api

import (
	"crypto/tls"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"
	"path"

	"golang.org/x/net/http2"

	"github.com/hashicorp/go-cleanhttp"
	"github.com/hashicorp/go-rootcerts"
	"github.com/sethgrid/pester"
)

const EnvVaultAddress = "VAULT_ADDR"
const EnvVaultCACert = "VAULT_CACERT"
const EnvVaultCAPath = "VAULT_CAPATH"
const EnvVaultClientCert = "VAULT_CLIENT_CERT"
const EnvVaultClientKey = "VAULT_CLIENT_KEY"
const EnvVaultInsecure = "VAULT_SKIP_VERIFY"
const EnvVaultTLSServerName = "VAULT_TLS_SERVER_NAME"
const EnvVaultWrapTTL = "VAULT_WRAP_TTL"
const EnvVaultMaxRetries = "VAULT_MAX_RETRIES"
const EnvVaultToken = "VAULT_TOKEN"

// WrappingLookupFunc is a function that, given an HTTP verb and a path,
// returns an optional string duration to be used for response wrapping (e.g.
// "15s", or simply "15"). The path will not begin with "/v1/" or "v1/" or "/",
// however, end-of-path forward slashes are not trimmed, so must match your
// called path precisely.
type WrappingLookupFunc func(operation, path string) string

// Config is used to configure the creation of the client.
type Config struct {
	// Address is the address of the Vault server. This should be a complete
	// URL such as "http://vault.example.com". If you need a custom SSL
	// cert or want to enable insecure mode, you need to specify a custom
	// HttpClient.
	Address string

	// HttpClient is the HTTP client to use, which will currently always have the
	// same values as http.DefaultClient. This is used to control redirect behavior.
	HttpClient *http.Client

	redirectSetup sync.Once

	// MaxRetries controls the maximum number of times to retry when a 5xx error
	// occurs. Set to 0 or less to disable retrying. Defaults to 0.
	MaxRetries int
}

// TLSConfig contains the parameters needed to configure TLS on the HTTP client
// used to communicate with Vault.
type TLSConfig struct {
	// CACert is the path to a PEM-encoded CA cert file to use to verify the
	// Vault server SSL certificate.
	CACert string

	// CAPath is the path to a directory of PEM-encoded CA cert files to verify
	// the Vault server SSL certificate.
	CAPath string

	// ClientCert is the path to the certificate for Vault communication
	ClientCert string

	// ClientKey is the path to the private key for Vault communication
	ClientKey string

	// TLSServerName, if set, is used to set the SNI host when connecting via
	// TLS.
	TLSServerName string

	// Insecure enables or disables SSL verification
	Insecure bool
}

// DefaultConfig returns a default configuration for the client. It is
// safe to modify the return value of this function.
//
// The default Address is https://127.0.0.1:8200, but this can be overridden by
// setting the `VAULT_ADDR` environment variable.
func DefaultConfig() *Config {
	config := &Config{
		Address:    "https://127.0.0.1:8200",
		HttpClient: cleanhttp.DefaultClient(),
	}
	config.HttpClient.Timeout = time.Second * 60
	transport := config.HttpClient.Transport.(*http.Transport)
	transport.TLSHandshakeTimeout = 10 * time.Second
	transport.TLSClientConfig = &tls.Config{
		MinVersion: tls.VersionTLS12,
	}

	if v := os.Getenv(EnvVaultAddress); v != "" {
		config.Address = v
	}

	return config
}

// ConfigureTLS takes a set of TLS configurations and applies those to the the HTTP client.
func (c *Config) ConfigureTLS(t *TLSConfig) error {
	if c.HttpClient == nil {
		c.HttpClient = DefaultConfig().HttpClient
	}

	var clientCert tls.Certificate
	foundClientCert := false
	if t.CACert != "" || t.CAPath != "" || t.ClientCert != "" || t.ClientKey != "" || t.Insecure {
		if t.ClientCert != "" && t.ClientKey != "" {
			var err error
			clientCert, err = tls.LoadX509KeyPair(t.ClientCert, t.ClientKey)
			if err != nil {
				return err
			}
			foundClientCert = true
		} else if t.ClientCert != "" || t.ClientKey != "" {
			return fmt.Errorf("Both client cert and client key must be provided")
		}
	}

	clientTLSConfig := c.HttpClient.Transport.(*http.Transport).TLSClientConfig
	rootConfig := &rootcerts.Config{
		CAFile: t.CACert,
		CAPath: t.CAPath,
	}
	if err := rootcerts.ConfigureTLS(clientTLSConfig, rootConfig); err != nil {
		return err
	}

	clientTLSConfig.InsecureSkipVerify = t.Insecure

	if foundClientCert {
		clientTLSConfig.Certificates = []tls.Certificate{clientCert}
	}
	if t.TLSServerName != "" {
		clientTLSConfig.ServerName = t.TLSServerName
	}

	return nil
}

// ReadEnvironment reads configuration information from the
// environment. If there is an error, no configuration value
// is updated.
func (c *Config) ReadEnvironment() error {
	var envAddress string
	var envCACert string
	var envCAPath string
	var envClientCert string
	var envClientKey string
	var envInsecure bool
	var envTLSServerName string
	var envMaxRetries *uint64

	// Parse the environment variables
	if v := os.Getenv(EnvVaultAddress); v != "" {
		envAddress = v
	}
	if v := os.Getenv(EnvVaultMaxRetries); v != "" {
		maxRetries, err := strconv.ParseUint(v, 10, 32)
		if err != nil {
			return err
		}
		envMaxRetries = &maxRetries
	}
	if v := os.Getenv(EnvVaultCACert); v != "" {
		envCACert = v
	}
	if v := os.Getenv(EnvVaultCAPath); v != "" {
		envCAPath = v
	}
	if v := os.Getenv(EnvVaultClientCert); v != "" {
		envClientCert = v
	}
	if v := os.Getenv(EnvVaultClientKey); v != "" {
		envClientKey = v
	}
	if v := os.Getenv(EnvVaultInsecure); v != "" {
		var err error
		envInsecure, err = strconv.ParseBool(v)
		if err != nil {
			return fmt.Errorf("Could not parse VAULT_SKIP_VERIFY")
		}
	}
	if v := os.Getenv(EnvVaultTLSServerName); v != "" {
		envTLSServerName = v
	}

	// Configure the HTTP clients TLS configuration.
	t := &TLSConfig{
		CACert:        envCACert,
		CAPath:        envCAPath,
		ClientCert:    envClientCert,
		ClientKey:     envClientKey,
		TLSServerName: envTLSServerName,
		Insecure:      envInsecure,
	}
	if err := c.ConfigureTLS(t); err != nil {
		return err
	}

	if envAddress != "" {
		c.Address = envAddress
	}

	if envMaxRetries != nil {
		c.MaxRetries = int(*envMaxRetries) + 1
	}

	return nil
}

// Client is the client to the Vault API. Create a client with
// NewClient.
type Client struct {
	addr               *url.URL
	config             *Config
	token              string
	wrappingLookupFunc WrappingLookupFunc
}

// NewClient returns a new client for the given configuration.
//
// If the environment variable `VAULT_TOKEN` is present, the token will be
// automatically added to the client. Otherwise, you must manually call
// `SetToken()`.
func NewClient(c *Config) (*Client, error) {
	if c == nil {
		c = DefaultConfig()
		if err := c.ReadEnvironment(); err != nil {
			return nil, fmt.Errorf("error reading environment: %v", err)
		}
	}

	u, err := url.Parse(c.Address)
	if err != nil {
		return nil, err
	}

	if c.HttpClient == nil {
		c.HttpClient = DefaultConfig().HttpClient
	}

	tp := c.HttpClient.Transport.(*http.Transport)
	if err := http2.ConfigureTransport(tp); err != nil {
		return nil, err
	}

	redirFunc := func() {
		// Ensure redirects are not automatically followed
		// Note that this is sane for the API client as it has its own
		// redirect handling logic (and thus also for command/meta),
		// but in e.g. http_test actual redirect handling is necessary
		c.HttpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
			// Returning this value causes the Go net library to not close the
			// response body and to nil out the error. Otherwise pester tries
			// three times on every redirect because it sees an error from this
			// function (to prevent redirects) passing through to it.
			return http.ErrUseLastResponse
		}
	}

	c.redirectSetup.Do(redirFunc)

	client := &Client{
		addr:   u,
		config: c,
	}

	if token := os.Getenv(EnvVaultToken); token != "" {
		client.SetToken(token)
	}

	return client, nil
}

// Sets the address of Vault in the client. The format of address should be
// "<Scheme>://<Host>:<Port>". Setting this on a client will override the
// value of VAULT_ADDR environment variable.
func (c *Client) SetAddress(addr string) error {
	var err error
	if c.addr, err = url.Parse(addr); err != nil {
		return fmt.Errorf("failed to set address: %v", err)
	}

	return nil
}

// Address returns the Vault URL the client is configured to connect to
func (c *Client) Address() string {
	return c.addr.String()
}

// SetMaxRetries sets the number of retries that will be used in the case of certain errors
func (c *Client) SetMaxRetries(retries int) {
	c.config.MaxRetries = retries
}

// SetWrappingLookupFunc sets a lookup function that returns desired wrap TTLs
// for a given operation and path
func (c *Client) SetWrappingLookupFunc(lookupFunc WrappingLookupFunc) {
	c.wrappingLookupFunc = lookupFunc
}

// Token returns the access token being used by this client. It will
// return the empty string if there is no token set.
func (c *Client) Token() string {
	return c.token
}

// SetToken sets the token directly. This won't perform any auth
// verification, it simply sets the token properly for future requests.
func (c *Client) SetToken(v string) {
	c.token = v
}

// ClearToken deletes the token if it is set or does nothing otherwise.
func (c *Client) ClearToken() {
	c.token = ""
}

// NewRequest creates a new raw request object to query the Vault server
// configured for this client. This is an advanced method and generally
// doesn't need to be called externally.
func (c *Client) NewRequest(method, requestPath string) *Request {
	req := &Request{
		Method: method,
		URL: &url.URL{
			User:   c.addr.User,
			Scheme: c.addr.Scheme,
			Host:   c.addr.Host,
			Path:   path.Join(c.addr.Path, requestPath),
		},
		ClientToken: c.token,
		Params:      make(map[string][]string),
	}

	var lookupPath string
	switch {
	case strings.HasPrefix(requestPath, "/v1/"):
		lookupPath = strings.TrimPrefix(requestPath, "/v1/")
	case strings.HasPrefix(requestPath, "v1/"):
		lookupPath = strings.TrimPrefix(requestPath, "v1/")
	default:
		lookupPath = requestPath
	}
	if c.wrappingLookupFunc != nil {
		req.WrapTTL = c.wrappingLookupFunc(method, lookupPath)
	} else {
		req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath)
	}

	return req
}

// RawRequest performs the raw request given. This request may be against
// a Vault server not configured with this client. This is an advanced operation
// that generally won't need to be called externally.
func (c *Client) RawRequest(r *Request) (*Response, error) {
	redirectCount := 0
START:
	req, err := r.ToHTTP()
	if err != nil {
		return nil, err
	}

	client := pester.NewExtendedClient(c.config.HttpClient)
	client.Backoff = pester.LinearJitterBackoff
	client.MaxRetries = c.config.MaxRetries

	var result *Response
	resp, err := client.Do(req)
	if resp != nil {
		result = &Response{Response: resp}
	}
	if err != nil {
		if strings.Contains(err.Error(), "tls: oversized") {
			err = fmt.Errorf(
				"%s\n\n"+
					"This error usually means that the server is running with TLS disabled\n"+
					"but the client is configured to use TLS. Please either enable TLS\n"+
					"on the server or run the client with -address set to an address\n"+
					"that uses the http protocol:\n\n"+
					"    vault <command> -address http://<address>\n\n"+
					"You can also set the VAULT_ADDR environment variable:\n\n\n"+
					"    VAULT_ADDR=http://<address> vault <command>\n\n"+
					"where <address> is replaced by the actual address to the server.",
				err)
		}
		return result, err
	}

	// Check for a redirect, only allowing for a single redirect
	if (resp.StatusCode == 301 || resp.StatusCode == 302 || resp.StatusCode == 307) && redirectCount == 0 {
		// Parse the updated location
		respLoc, err := resp.Location()
		if err != nil {
			return result, err
		}

		// Ensure a protocol downgrade doesn't happen
		if req.URL.Scheme == "https" && respLoc.Scheme != "https" {
			return result, fmt.Errorf("redirect would cause protocol downgrade")
		}

		// Update the request
		r.URL = respLoc

		// Reset the request body if any
		if err := r.ResetJSONBody(); err != nil {
			return result, err
		}

		// Retry the request
		redirectCount++
		goto START
	}

	if err := result.Error(); err != nil {
		return result, err
	}

	return result, nil
}
<|endoftext|>


<commit_before>package client

import (
	"fmt"
	"github.com/andygrunwald/go-gerrit"
	"github.com/andygrunwald/watson/storage"
	"github.com/andygrunwald/watson/storage/identity"
	"log"
)

type Crawler struct {
	Client              *Client
	ChangeSetQueryLimit int
	Storage             chan *storage.ChangeSet
	IdentityStorage     chan *identity.Identity
}

func NewCrawler(c *Client) *Crawler {
	crawler := &Crawler{
		Client: c,
	}

	return crawler
}

func (c *Crawler) Projects() (*map[string]storage.Project, error) {
	/*
		opt := &gerrit.ProjectOptions{
			Description: true,
			Tree:        true,
			Type:        "ALL",
		}

		projects, _, err := c.Client.Gerrit.Projects.ListProjects(opt)
	*/
	projects, _, err := c.Client.Gerrit.Projects.ListProjects(nil)

	res := make(map[string]storage.Project, len(*projects))

	for name, p := range *projects {
		// TODO Store name, p
		// type Project *gerrit.ProjectInfo
		res[name] = storage.Project(p)
	}

	return &res, err
}

func (c *Crawler) Changesets(project string) {
	for startNum := 0; ; {
		endNum := startNum + c.ChangeSetQueryLimit
		log.Printf("Querying for changes %d...%d for project %s", startNum, endNum, project)

		opt := &gerrit.QueryChangeOptions{
			Start: startNum,
		}
		opt.Query = []string{fmt.Sprintf("project:%s", project)}
		opt.Limit = c.ChangeSetQueryLimit
		opt.AdditionalFields = []string{
			"DETAILED_ACCOUNTS",
			"LABELS",
			"WEB_LINKS",
			"ALL_FILES",
			"MESSAGES",
			"CHANGE_ACTIONS",
			"REVIEWED",
			"WEB_LINKS",
			"COMMIT_FOOTERS",
			"ALL_REVISIONS",
			"DOWNLOAD_COMMANDS",
			"CURRENT_COMMIT",
			"ALL_COMMITS",
			"CURRENT_FILES",
			"CURRENT_REVISION",
			"DETAILED_LABELS",
		}

		changes, resp, err := c.Client.Gerrit.Changes.QueryChanges(opt)
		if err != nil {
			log.Printf("ERROR ... %+v", err)
			continue
		}

		if changes == nil {
			log.Printf("changes is nil ... %+v", resp)
			continue
		}

		numOfChangesets := len(*changes)
		log.Printf(">>>> Received %d changes to process for project %s", numOfChangesets, project)
		startNum += numOfChangesets

		for _, change := range *changes {
			cs := &storage.ChangeSet{
				Change: &change,
			}
			c.Storage <- cs

			// Collect identities
			c.IdentityStorage <- identity.AccountInfo(change.Owner).Identify()

			c.IdentityStorage <- identity.AccountInfo(change.Labels.CodeReview.Approved).Identify()
			c.IdentityStorage <- identity.AccountInfo(change.Labels.CodeReview.Rejected).Identify()
			c.IdentityStorage <- identity.AccountInfo(change.Labels.CodeReview.Recommended).Identify()
			c.IdentityStorage <- identity.AccountInfo(change.Labels.CodeReview.Disliked).Identify()

			for _, ai := range change.Labels.CodeReview.All {
				c.IdentityStorage <- identity.ApprovalInfo(ai).Identify()
			}

			c.IdentityStorage <- identity.AccountInfo(change.Labels.Verified.Approved).Identify()
			c.IdentityStorage <- identity.AccountInfo(change.Labels.Verified.Rejected).Identify()
			c.IdentityStorage <- identity.AccountInfo(change.Labels.Verified.Recommended).Identify()
			c.IdentityStorage <- identity.AccountInfo(change.Labels.Verified.Disliked).Identify()

			for _, ai := range change.Labels.Verified.All {
				c.IdentityStorage <- identity.ApprovalInfo(ai).Identify()
			}

			for _, ai := range change.RemovableReviewers {
				c.IdentityStorage <- identity.AccountInfo(ai).Identify()
			}

			for _, cmi := range change.Messages {
				c.IdentityStorage <- identity.AccountInfo(cmi.Author).Identify()
			}

			for _, ri := range change.Revisions {
				c.IdentityStorage <- identity.AccountInfo(ri.Uploader).Identify()
				//c.IdentityStorage <- identity.GitPersonInfo(ri.Commit.Author).Identify()
				//c.IdentityStorage <- identity.GitPersonInfo(ri.Commit.Committer).Identify()

				// Parents   []CommitInfo  `json:"parents"`
			}

			// TODO Add all identities to IdentityStorage
			// GitPersonInfo, AccountInfo, EmailInfo
		}

		// Last changeset have a key: _more_changes (set to true)
		// TODO
		if numOfChangesets == 0 || (numOfChangesets > 0 && numOfChangesets < c.ChangeSetQueryLimit) {
			break
		}
	}
}
<commit_msg>Make build green again<commit_after>package client

import (
	"fmt"
	"log"

	"github.com/andygrunwald/go-gerrit"
	"github.com/andygrunwald/watson/storage"
	"github.com/andygrunwald/watson/storage/identity"
)

type Crawler struct {
	Client              *Client
	ChangeSetQueryLimit int
	Storage             chan *storage.ChangeSet
	IdentityStorage     chan *identity.Identity
}

func NewCrawler(c *Client) *Crawler {
	crawler := &Crawler{
		Client: c,
	}

	return crawler
}

func (c *Crawler) Projects() (*map[string]storage.Project, error) {
	/*
		opt := &gerrit.ProjectOptions{
			Description: true,
			Tree:        true,
			Type:        "ALL",
		}

		projects, _, err := c.Client.Gerrit.Projects.ListProjects(opt)
	*/
	projects, _, err := c.Client.Gerrit.Projects.ListProjects(nil)

	res := make(map[string]storage.Project, len(*projects))

	for name, p := range *projects {
		// TODO Store name, p
		// type Project *gerrit.ProjectInfo
		res[name] = storage.Project(p)
	}

	return &res, err
}

func (c *Crawler) Changesets(project string) {
	for startNum := 0; ; {
		endNum := startNum + c.ChangeSetQueryLimit
		log.Printf("Querying for changes %d...%d for project %s", startNum, endNum, project)

		opt := &gerrit.QueryChangeOptions{
			Start: startNum,
		}
		opt.Query = []string{fmt.Sprintf("project:%s", project)}
		opt.Limit = c.ChangeSetQueryLimit
		opt.AdditionalFields = []string{
			"DETAILED_ACCOUNTS",
			"LABELS",
			"WEB_LINKS",
			"ALL_FILES",
			"MESSAGES",
			"CHANGE_ACTIONS",
			"REVIEWED",
			"WEB_LINKS",
			"COMMIT_FOOTERS",
			"ALL_REVISIONS",
			"DOWNLOAD_COMMANDS",
			"CURRENT_COMMIT",
			"ALL_COMMITS",
			"CURRENT_FILES",
			"CURRENT_REVISION",
			"DETAILED_LABELS",
		}

		changes, resp, err := c.Client.Gerrit.Changes.QueryChanges(opt)
		if err != nil {
			log.Printf("ERROR ... %+v", err)
			continue
		}

		if changes == nil {
			log.Printf("changes is nil ... %+v", resp)
			continue
		}

		numOfChangesets := len(*changes)
		log.Printf(">>>> Received %d changes to process for project %s", numOfChangesets, project)
		startNum += numOfChangesets

		for _, change := range *changes {
			cs := &storage.ChangeSet{
				Change: &change,
			}
			c.Storage <- cs

			// Collect identities
			c.IdentityStorage <- identity.AccountInfo(change.Owner).Identify()

			c.IdentityStorage <- identity.AccountInfo(change.Labels["CodeReview"].Approved).Identify()
			c.IdentityStorage <- identity.AccountInfo(change.Labels["CodeReview"].Rejected).Identify()
			c.IdentityStorage <- identity.AccountInfo(change.Labels["CodeReview"].Recommended).Identify()
			c.IdentityStorage <- identity.AccountInfo(change.Labels["CodeReview"].Disliked).Identify()

			for _, ai := range change.Labels["CodeReview"].All {
				c.IdentityStorage <- identity.ApprovalInfo(ai).Identify()
			}

			c.IdentityStorage <- identity.AccountInfo(change.Labels["Verified"].Approved).Identify()
			c.IdentityStorage <- identity.AccountInfo(change.Labels["Verified"].Rejected).Identify()
			c.IdentityStorage <- identity.AccountInfo(change.Labels["Verified"].Recommended).Identify()
			c.IdentityStorage <- identity.AccountInfo(change.Labels["Verified"].Disliked).Identify()

			for _, ai := range change.Labels["Verified"].All {
				c.IdentityStorage <- identity.ApprovalInfo(ai).Identify()
			}

			for _, ai := range change.RemovableReviewers {
				c.IdentityStorage <- identity.AccountInfo(ai).Identify()
			}

			for _, cmi := range change.Messages {
				c.IdentityStorage <- identity.AccountInfo(cmi.Author).Identify()
			}

			for _, ri := range change.Revisions {
				c.IdentityStorage <- identity.AccountInfo(ri.Uploader).Identify()
				//c.IdentityStorage <- identity.GitPersonInfo(ri.Commit.Author).Identify()
				//c.IdentityStorage <- identity.GitPersonInfo(ri.Commit.Committer).Identify()

				// Parents   []CommitInfo  `json:"parents"`
			}

			// TODO Add all identities to IdentityStorage
			// GitPersonInfo, AccountInfo, EmailInfo
		}

		// Last changeset have a key: _more_changes (set to true)
		// TODO
		if numOfChangesets == 0 || (numOfChangesets > 0 && numOfChangesets < c.ChangeSetQueryLimit) {
			break
		}
	}
}
<|endoftext|>


<commit_before>// ----------------------------------------------------------------------------
//
//     ***     AUTO GENERATED CODE    ***    AUTO GENERATED CODE     ***
//
// ----------------------------------------------------------------------------
//
//     This file is automatically generated by Magic Modules and manual
//     changes will be clobbered when the file is regenerated.
//
//     Please read more about how to change this file in
//     .github/CONTRIBUTING.md.
//
// ----------------------------------------------------------------------------

package google

import (
	"bytes"
	"fmt"
	"reflect"
	"sort"
	"strings"

	"github.com/hashicorp/terraform-plugin-sdk/helper/hashcode"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourceComputeFirewallRuleHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["protocol"].(string))))

	// We need to make sure to sort the strings below so that we always
	// generate the same hash code no matter what is in the set.
	if v, ok := m["ports"]; ok && v != nil {
		s := convertStringArr(v.([]interface{}))
		sort.Strings(s)

		for _, v := range s {
			buf.WriteString(fmt.Sprintf("%s-", v))
		}
	}

	return hashcode.String(buf.String())
}

func compareCaseInsensitive(k, old, new string, d *schema.ResourceData) bool {
	return strings.ToLower(old) == strings.ToLower(new)
}

func GetComputeFirewallCaiObject(d TerraformResourceData, config *Config) (Asset, error) {
	name, err := assetName(d, config, "//compute.googleapis.com/projects/{{project}}/global/firewalls/{{name}}")
	if err != nil {
		return Asset{}, err
	}
	if obj, err := GetComputeFirewallApiObject(d, config); err == nil {
		return Asset{
			Name: name,
			Type: "compute.googleapis.com/Firewall",
			Resource: &AssetResource{
				Version:              "v1",
				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/compute/v1/rest",
				DiscoveryName:        "Firewall",
				Data:                 obj,
			},
		}, nil
	} else {
		return Asset{}, err
	}
}

func GetComputeFirewallApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
	obj := make(map[string]interface{})
	allowedProp, err := expandComputeFirewallAllow(d.Get("allow"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("allow"); !isEmptyValue(reflect.ValueOf(allowedProp)) && (ok || !reflect.DeepEqual(v, allowedProp)) {
		obj["allowed"] = allowedProp
	}
	deniedProp, err := expandComputeFirewallDeny(d.Get("deny"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("deny"); !isEmptyValue(reflect.ValueOf(deniedProp)) && (ok || !reflect.DeepEqual(v, deniedProp)) {
		obj["denied"] = deniedProp
	}
	descriptionProp, err := expandComputeFirewallDescription(d.Get("description"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) {
		obj["description"] = descriptionProp
	}
	destinationRangesProp, err := expandComputeFirewallDestinationRanges(d.Get("destination_ranges"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("destination_ranges"); !isEmptyValue(reflect.ValueOf(destinationRangesProp)) && (ok || !reflect.DeepEqual(v, destinationRangesProp)) {
		obj["destinationRanges"] = destinationRangesProp
	}
	directionProp, err := expandComputeFirewallDirection(d.Get("direction"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("direction"); !isEmptyValue(reflect.ValueOf(directionProp)) && (ok || !reflect.DeepEqual(v, directionProp)) {
		obj["direction"] = directionProp
	}
	disabledProp, err := expandComputeFirewallDisabled(d.Get("disabled"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("disabled"); ok || !reflect.DeepEqual(v, disabledProp) {
		obj["disabled"] = disabledProp
	}
	logConfigProp, err := expandComputeFirewallLogConfig(nil, d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("log_config"); !isEmptyValue(reflect.ValueOf(logConfigProp)) && (ok || !reflect.DeepEqual(v, logConfigProp)) {
		obj["logConfig"] = logConfigProp
	}
	nameProp, err := expandComputeFirewallName(d.Get("name"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) {
		obj["name"] = nameProp
	}
	networkProp, err := expandComputeFirewallNetwork(d.Get("network"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("network"); !isEmptyValue(reflect.ValueOf(networkProp)) && (ok || !reflect.DeepEqual(v, networkProp)) {
		obj["network"] = networkProp
	}
	priorityProp, err := expandComputeFirewallPriority(d.Get("priority"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("priority"); ok || !reflect.DeepEqual(v, priorityProp) {
		obj["priority"] = priorityProp
	}
	sourceRangesProp, err := expandComputeFirewallSourceRanges(d.Get("source_ranges"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("source_ranges"); !isEmptyValue(reflect.ValueOf(sourceRangesProp)) && (ok || !reflect.DeepEqual(v, sourceRangesProp)) {
		obj["sourceRanges"] = sourceRangesProp
	}
	sourceServiceAccountsProp, err := expandComputeFirewallSourceServiceAccounts(d.Get("source_service_accounts"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("source_service_accounts"); !isEmptyValue(reflect.ValueOf(sourceServiceAccountsProp)) && (ok || !reflect.DeepEqual(v, sourceServiceAccountsProp)) {
		obj["sourceServiceAccounts"] = sourceServiceAccountsProp
	}
	sourceTagsProp, err := expandComputeFirewallSourceTags(d.Get("source_tags"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("source_tags"); !isEmptyValue(reflect.ValueOf(sourceTagsProp)) && (ok || !reflect.DeepEqual(v, sourceTagsProp)) {
		obj["sourceTags"] = sourceTagsProp
	}
	targetServiceAccountsProp, err := expandComputeFirewallTargetServiceAccounts(d.Get("target_service_accounts"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("target_service_accounts"); !isEmptyValue(reflect.ValueOf(targetServiceAccountsProp)) && (ok || !reflect.DeepEqual(v, targetServiceAccountsProp)) {
		obj["targetServiceAccounts"] = targetServiceAccountsProp
	}
	targetTagsProp, err := expandComputeFirewallTargetTags(d.Get("target_tags"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("target_tags"); !isEmptyValue(reflect.ValueOf(targetTagsProp)) && (ok || !reflect.DeepEqual(v, targetTagsProp)) {
		obj["targetTags"] = targetTagsProp
	}

	return obj, nil
}

func expandComputeFirewallAllow(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	l := v.([]interface{})
	req := make([]interface{}, 0, len(l))
	for _, raw := range l {
		if raw == nil {
			continue
		}
		original := raw.(map[string]interface{})
		transformed := make(map[string]interface{})

		transformedProtocol, err := expandComputeFirewallAllowProtocol(original["protocol"], d, config)
		if err != nil {
			return nil, err
		} else if val := reflect.ValueOf(transformedProtocol); val.IsValid() && !isEmptyValue(val) {
			transformed["IPProtocol"] = transformedProtocol
		}

		transformedPorts, err := expandComputeFirewallAllowPorts(original["ports"], d, config)
		if err != nil {
			return nil, err
		} else if val := reflect.ValueOf(transformedPorts); val.IsValid() && !isEmptyValue(val) {
			transformed["ports"] = transformedPorts
		}

		req = append(req, transformed)
	}
	return req, nil
}

func expandComputeFirewallAllowProtocol(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallAllowPorts(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallDeny(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	l := v.([]interface{})
	req := make([]interface{}, 0, len(l))
	for _, raw := range l {
		if raw == nil {
			continue
		}
		original := raw.(map[string]interface{})
		transformed := make(map[string]interface{})

		transformedProtocol, err := expandComputeFirewallDenyProtocol(original["protocol"], d, config)
		if err != nil {
			return nil, err
		} else if val := reflect.ValueOf(transformedProtocol); val.IsValid() && !isEmptyValue(val) {
			transformed["IPProtocol"] = transformedProtocol
		}

		transformedPorts, err := expandComputeFirewallDenyPorts(original["ports"], d, config)
		if err != nil {
			return nil, err
		} else if val := reflect.ValueOf(transformedPorts); val.IsValid() && !isEmptyValue(val) {
			transformed["ports"] = transformedPorts
		}

		req = append(req, transformed)
	}
	return req, nil
}

func expandComputeFirewallDenyProtocol(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallDenyPorts(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallDestinationRanges(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}

func expandComputeFirewallDirection(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallDisabled(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallLogConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	transformed := make(map[string]interface{})
	transformedEnableLogging, err := expandComputeFirewallLogConfigEnableLogging(d.Get("enable_logging"), d, config)
	if err != nil {
		return nil, err
	} else {
		transformed["enable"] = transformedEnableLogging
	}

	return transformed, nil
}

func expandComputeFirewallLogConfigEnableLogging(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallNetwork(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	f, err := parseGlobalFieldValue("networks", v.(string), "project", d, config, true)
	if err != nil {
		return nil, fmt.Errorf("Invalid value for network: %s", err)
	}
	return f.RelativeLink(), nil
}

func expandComputeFirewallPriority(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallSourceRanges(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}

func expandComputeFirewallSourceServiceAccounts(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}

func expandComputeFirewallSourceTags(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}

func expandComputeFirewallTargetServiceAccounts(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}

func expandComputeFirewallTargetTags(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}
<commit_msg>add firewall logging controls (#3780) (#485)<commit_after>// ----------------------------------------------------------------------------
//
//     ***     AUTO GENERATED CODE    ***    AUTO GENERATED CODE     ***
//
// ----------------------------------------------------------------------------
//
//     This file is automatically generated by Magic Modules and manual
//     changes will be clobbered when the file is regenerated.
//
//     Please read more about how to change this file in
//     .github/CONTRIBUTING.md.
//
// ----------------------------------------------------------------------------

package google

import (
	"bytes"
	"fmt"
	"reflect"
	"sort"
	"strings"

	"github.com/hashicorp/terraform-plugin-sdk/helper/hashcode"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourceComputeFirewallRuleHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["protocol"].(string))))

	// We need to make sure to sort the strings below so that we always
	// generate the same hash code no matter what is in the set.
	if v, ok := m["ports"]; ok && v != nil {
		s := convertStringArr(v.([]interface{}))
		sort.Strings(s)

		for _, v := range s {
			buf.WriteString(fmt.Sprintf("%s-", v))
		}
	}

	return hashcode.String(buf.String())
}

func compareCaseInsensitive(k, old, new string, d *schema.ResourceData) bool {
	return strings.ToLower(old) == strings.ToLower(new)
}

func diffSuppressEnableLogging(k, old, new string, d *schema.ResourceData) bool {
	if k == "log_config.#" {
		if new == "0" && d.Get("enable_logging").(bool) {
			return true
		}
	}

	return false
}

func resourceComputeFirewallEnableLoggingCustomizeDiff(diff *schema.ResourceDiff, v interface{}) error {
	enableLogging, enableExists := diff.GetOkExists("enable_logging")
	if !enableExists {
		return nil
	}

	logConfigExists := diff.Get("log_config.#").(int) != 0
	if logConfigExists && enableLogging == false {
		return fmt.Errorf("log_config cannot be defined when enable_logging is false")
	}

	return nil
}

func GetComputeFirewallCaiObject(d TerraformResourceData, config *Config) (Asset, error) {
	name, err := assetName(d, config, "//compute.googleapis.com/projects/{{project}}/global/firewalls/{{name}}")
	if err != nil {
		return Asset{}, err
	}
	if obj, err := GetComputeFirewallApiObject(d, config); err == nil {
		return Asset{
			Name: name,
			Type: "compute.googleapis.com/Firewall",
			Resource: &AssetResource{
				Version:              "v1",
				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/compute/v1/rest",
				DiscoveryName:        "Firewall",
				Data:                 obj,
			},
		}, nil
	} else {
		return Asset{}, err
	}
}

func GetComputeFirewallApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
	obj := make(map[string]interface{})
	allowedProp, err := expandComputeFirewallAllow(d.Get("allow"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("allow"); !isEmptyValue(reflect.ValueOf(allowedProp)) && (ok || !reflect.DeepEqual(v, allowedProp)) {
		obj["allowed"] = allowedProp
	}
	deniedProp, err := expandComputeFirewallDeny(d.Get("deny"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("deny"); !isEmptyValue(reflect.ValueOf(deniedProp)) && (ok || !reflect.DeepEqual(v, deniedProp)) {
		obj["denied"] = deniedProp
	}
	descriptionProp, err := expandComputeFirewallDescription(d.Get("description"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) {
		obj["description"] = descriptionProp
	}
	destinationRangesProp, err := expandComputeFirewallDestinationRanges(d.Get("destination_ranges"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("destination_ranges"); !isEmptyValue(reflect.ValueOf(destinationRangesProp)) && (ok || !reflect.DeepEqual(v, destinationRangesProp)) {
		obj["destinationRanges"] = destinationRangesProp
	}
	directionProp, err := expandComputeFirewallDirection(d.Get("direction"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("direction"); !isEmptyValue(reflect.ValueOf(directionProp)) && (ok || !reflect.DeepEqual(v, directionProp)) {
		obj["direction"] = directionProp
	}
	disabledProp, err := expandComputeFirewallDisabled(d.Get("disabled"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("disabled"); ok || !reflect.DeepEqual(v, disabledProp) {
		obj["disabled"] = disabledProp
	}
	logConfigProp, err := expandComputeFirewallLogConfig(d.Get("log_config"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("log_config"); ok || !reflect.DeepEqual(v, logConfigProp) {
		obj["logConfig"] = logConfigProp
	}
	nameProp, err := expandComputeFirewallName(d.Get("name"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) {
		obj["name"] = nameProp
	}
	networkProp, err := expandComputeFirewallNetwork(d.Get("network"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("network"); !isEmptyValue(reflect.ValueOf(networkProp)) && (ok || !reflect.DeepEqual(v, networkProp)) {
		obj["network"] = networkProp
	}
	priorityProp, err := expandComputeFirewallPriority(d.Get("priority"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("priority"); ok || !reflect.DeepEqual(v, priorityProp) {
		obj["priority"] = priorityProp
	}
	sourceRangesProp, err := expandComputeFirewallSourceRanges(d.Get("source_ranges"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("source_ranges"); !isEmptyValue(reflect.ValueOf(sourceRangesProp)) && (ok || !reflect.DeepEqual(v, sourceRangesProp)) {
		obj["sourceRanges"] = sourceRangesProp
	}
	sourceServiceAccountsProp, err := expandComputeFirewallSourceServiceAccounts(d.Get("source_service_accounts"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("source_service_accounts"); !isEmptyValue(reflect.ValueOf(sourceServiceAccountsProp)) && (ok || !reflect.DeepEqual(v, sourceServiceAccountsProp)) {
		obj["sourceServiceAccounts"] = sourceServiceAccountsProp
	}
	sourceTagsProp, err := expandComputeFirewallSourceTags(d.Get("source_tags"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("source_tags"); !isEmptyValue(reflect.ValueOf(sourceTagsProp)) && (ok || !reflect.DeepEqual(v, sourceTagsProp)) {
		obj["sourceTags"] = sourceTagsProp
	}
	targetServiceAccountsProp, err := expandComputeFirewallTargetServiceAccounts(d.Get("target_service_accounts"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("target_service_accounts"); !isEmptyValue(reflect.ValueOf(targetServiceAccountsProp)) && (ok || !reflect.DeepEqual(v, targetServiceAccountsProp)) {
		obj["targetServiceAccounts"] = targetServiceAccountsProp
	}
	targetTagsProp, err := expandComputeFirewallTargetTags(d.Get("target_tags"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("target_tags"); !isEmptyValue(reflect.ValueOf(targetTagsProp)) && (ok || !reflect.DeepEqual(v, targetTagsProp)) {
		obj["targetTags"] = targetTagsProp
	}

	return obj, nil
}

func expandComputeFirewallAllow(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	l := v.([]interface{})
	req := make([]interface{}, 0, len(l))
	for _, raw := range l {
		if raw == nil {
			continue
		}
		original := raw.(map[string]interface{})
		transformed := make(map[string]interface{})

		transformedProtocol, err := expandComputeFirewallAllowProtocol(original["protocol"], d, config)
		if err != nil {
			return nil, err
		} else if val := reflect.ValueOf(transformedProtocol); val.IsValid() && !isEmptyValue(val) {
			transformed["IPProtocol"] = transformedProtocol
		}

		transformedPorts, err := expandComputeFirewallAllowPorts(original["ports"], d, config)
		if err != nil {
			return nil, err
		} else if val := reflect.ValueOf(transformedPorts); val.IsValid() && !isEmptyValue(val) {
			transformed["ports"] = transformedPorts
		}

		req = append(req, transformed)
	}
	return req, nil
}

func expandComputeFirewallAllowProtocol(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallAllowPorts(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallDeny(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	l := v.([]interface{})
	req := make([]interface{}, 0, len(l))
	for _, raw := range l {
		if raw == nil {
			continue
		}
		original := raw.(map[string]interface{})
		transformed := make(map[string]interface{})

		transformedProtocol, err := expandComputeFirewallDenyProtocol(original["protocol"], d, config)
		if err != nil {
			return nil, err
		} else if val := reflect.ValueOf(transformedProtocol); val.IsValid() && !isEmptyValue(val) {
			transformed["IPProtocol"] = transformedProtocol
		}

		transformedPorts, err := expandComputeFirewallDenyPorts(original["ports"], d, config)
		if err != nil {
			return nil, err
		} else if val := reflect.ValueOf(transformedPorts); val.IsValid() && !isEmptyValue(val) {
			transformed["ports"] = transformedPorts
		}

		req = append(req, transformed)
	}
	return req, nil
}

func expandComputeFirewallDenyProtocol(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallDenyPorts(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallDestinationRanges(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}

func expandComputeFirewallDirection(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallDisabled(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallLogConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	transformed := make(map[string]interface{})

	if len(l) == 0 || l[0] == nil {
		// send enable = enable_logging value to ensure correct logging status if there is no config
		transformed["enable"] = d.Get("enable_logging").(bool)
		return transformed, nil
	}

	raw := l[0]
	original := raw.(map[string]interface{})

	// The log_config block is specified, so logging should be enabled
	transformed["enable"] = true
	transformed["metadata"] = original["metadata"]

	return transformed, nil
}

func expandComputeFirewallName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallNetwork(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	f, err := parseGlobalFieldValue("networks", v.(string), "project", d, config, true)
	if err != nil {
		return nil, fmt.Errorf("Invalid value for network: %s", err)
	}
	return f.RelativeLink(), nil
}

func expandComputeFirewallPriority(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeFirewallSourceRanges(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}

func expandComputeFirewallSourceServiceAccounts(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}

func expandComputeFirewallSourceTags(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}

func expandComputeFirewallTargetServiceAccounts(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}

func expandComputeFirewallTargetTags(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}
<|endoftext|>


<commit_before>package api

const (
	// Version of Rikka
	Version = "0.1.7"
)
<commit_msg>updat to 0.1.8<commit_after>package api

const (
	// Version of Rikka
	Version = "0.1.8"
)
<|endoftext|>


<commit_before>// BaruwaAPI Golang bindings for Baruwa REST API
// Copyright (C) 2019 Andrew Colin Kissa <andrew@topdog.za.net>

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

package api

import (
	"fmt"
	"net/url"

	"github.com/google/go-querystring/query"
)

// Domain holds domains
type Domain struct {
	ID                int     `json:"id,omitempty" url:"id,omitempty"`
	Name              string  `json:"name" url:"name"`
	SiteURL           string  `json:"site_url" url:"site_url"`
	Enabled           bool    `json:"status" url:"status"`
	AcceptInbound     bool    `json:"accept_inbound" url:"accept_inbound"`
	DiscardMail       bool    `json:"discard_mail" url:"discard_mail"`
	SMTPCallout       bool    `json:"smtp_callout" url:"smtp_callout"`
	LdapCallout       bool    `json:"ldap_callout" url:"ldap_callout"`
	VirusChecks       bool    `json:"virus_checks" url:"virus_checks"`
	VirusChecksAtSMTP bool    `json:"virus_checks_at_smtp" url:"virus_checks_at_smtp"`
	BlockMacros       bool    `json:"block_macros" url:"block_macros"`
	SpamChecks        bool    `json:"spam_checks" url:"spam_checks"`
	SpamActions       int     `json:"spam_actions" url:"spam_actions"`
	HighspamActions   int     `json:"highspam_actions" url:"highspam_actions"`
	VirusActions      int     `json:"virus_actions" url:"virus_actions"`
	LowScore          float64 `json:"low_score" url:"low_score"`
	HighScore         float64 `json:"high_score" url:"high_score"`
	MessageSize       string  `json:"message_size" url:"message_size"`
	DeliveryMode      int     `json:"delivery_mode" url:"delivery_mode"`
	Language          string  `json:"language" url:"language"`
	Timezone          string  `json:"timezone" url:"timezone"`
	ReportEvery       int     `json:"report_every" url:"report_every"`
	Organizations     int     `json:"organizations,omitempty" url:"organizations,omitempty"`
}

// DomainList holds domain smarthosts
type DomainList struct {
	Items []Domain `json:"items"`
	Links Links    `json:"links"`
	Meta  Meta     `json:"meta"`
}

// GetDomains returns a DomainList object
// This contains a paginated list of domains and links
// to the neighbouring pages.
// https://www.baruwa.com/docs/api/#list-all-domains
func (c *Client) GetDomains(opts *ListOptions) (l *DomainList, err error) {
	l = &DomainList{}

	err = c.get("domains", opts, l)

	return
}

// GetDomain returns a domain
// https://www.baruwa.com/docs/api/#retrieve-a-domain
func (c *Client) GetDomain(domainID int) (domain *Domain, err error) {
	if domainID <= 0 {
		err = fmt.Errorf(domainIDError)
		return
	}

	domain = &Domain{}

	err = c.get(fmt.Sprintf("domains/%d", domainID), nil, domain)

	return
}

// GetDomainByName returns a domain
// https://www.baruwa.com/docs/api/#retrieve-a-domain-by-name
func (c *Client) GetDomainByName(domainName string) (domain *Domain, err error) {
	if domainName == "" {
		err = fmt.Errorf(domainNameParamError)
		return
	}

	domain = &Domain{}

	err = c.get(fmt.Sprintf("domains/byname/%s", domainName), nil, domain)

	return
}

// CreateDomain creates a domain
// https://www.baruwa.com/docs/api/#create-a-new-domain
func (c *Client) CreateDomain(domain *Domain) (err error) {
	var v url.Values

	if domain == nil {
		err = fmt.Errorf(domainParamError)
		return
	}

	if v, err = query.Values(domain); err != nil {
		return
	}

	err = c.post("domains", v, domain)

	return
}

// UpdateDomain updates a domain
// https://www.baruwa.com/docs/api/#update-a-domain
func (c *Client) UpdateDomain(domain *Domain) (err error) {
	var v url.Values

	if domain == nil {
		err = fmt.Errorf(domainParamError)
		return
	}

	if domain.ID <= 0 {
		err = fmt.Errorf(domainSIDError)
		return
	}

	if v, err = query.Values(domain); err != nil {
		return
	}

	err = c.put(fmt.Sprintf("domains/%d", domain.ID), v, domain)

	return
}

// DeleteDomain deletes a domain
// https://www.baruwa.com/docs/api/#delete-a-domain
func (c *Client) DeleteDomain(domainID int) (err error) {
	if domainID <= 0 {
		err = fmt.Errorf(domainIDError)
		return
	}

	err = c.delete(fmt.Sprintf("domains/%d", domainID), nil)

	return
}
<commit_msg>FIX: Update domain struct<commit_after>// BaruwaAPI Golang bindings for Baruwa REST API
// Copyright (C) 2019 Andrew Colin Kissa <andrew@topdog.za.net>

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

package api

import (
	"fmt"
	"net/url"

	"github.com/google/go-querystring/query"
)

// Domain holds domains
type Domain struct {
	ID                int          `json:"id,omitempty" url:"id,omitempty"`
	Name              string       `json:"name" url:"name"`
	SiteURL           string       `json:"site_url" url:"site_url"`
	Enabled           bool         `json:"status" url:"status"`
	AcceptInbound     bool         `json:"accept_inbound" url:"accept_inbound"`
	DiscardMail       bool         `json:"discard_mail" url:"discard_mail"`
	SMTPCallout       bool         `json:"smtp_callout" url:"smtp_callout"`
	LdapCallout       bool         `json:"ldap_callout" url:"ldap_callout"`
	VirusChecks       bool         `json:"virus_checks" url:"virus_checks"`
	VirusChecksAtSMTP bool         `json:"virus_checks_at_smtp" url:"virus_checks_at_smtp"`
	BlockMacros       bool         `json:"block_macros" url:"block_macros"`
	SpamChecks        bool         `json:"spam_checks" url:"spam_checks"`
	SpamActions       int          `json:"spam_actions" url:"spam_actions"`
	HighspamActions   int          `json:"highspam_actions" url:"highspam_actions"`
	VirusActions      int          `json:"virus_actions" url:"virus_actions"`
	LowScore          LocalFloat64 `json:"low_score" url:"low_score"`
	HighScore         LocalFloat64 `json:"high_score" url:"high_score"`
	MessageSize       string       `json:"message_size" url:"message_size"`
	DeliveryMode      int          `json:"delivery_mode" url:"delivery_mode"`
	Language          string       `json:"language" url:"language"`
	Timezone          string       `json:"timezone" url:"timezone"`
	ReportEvery       int          `json:"report_every" url:"report_every"`
	Organizations     []int        `json:"organizations,omitempty" url:"organizations,omitempty"`
}

// DomainList holds domain smarthosts
type DomainList struct {
	Items []Domain `json:"items"`
	Links Links    `json:"links"`
	Meta  Meta     `json:"meta"`
}

// GetDomains returns a DomainList object
// This contains a paginated list of domains and links
// to the neighbouring pages.
// https://www.baruwa.com/docs/api/#list-all-domains
func (c *Client) GetDomains(opts *ListOptions) (l *DomainList, err error) {
	l = &DomainList{}

	err = c.get("domains", opts, l)

	return
}

// GetDomain returns a domain
// https://www.baruwa.com/docs/api/#retrieve-a-domain
func (c *Client) GetDomain(domainID int) (domain *Domain, err error) {
	if domainID <= 0 {
		err = fmt.Errorf(domainIDError)
		return
	}

	domain = &Domain{}

	err = c.get(fmt.Sprintf("domains/%d", domainID), nil, domain)

	return
}

// GetDomainByName returns a domain
// https://www.baruwa.com/docs/api/#retrieve-a-domain-by-name
func (c *Client) GetDomainByName(domainName string) (domain *Domain, err error) {
	if domainName == "" {
		err = fmt.Errorf(domainNameParamError)
		return
	}

	domain = &Domain{}

	err = c.get(fmt.Sprintf("domains/byname/%s", domainName), nil, domain)

	return
}

// CreateDomain creates a domain
// https://www.baruwa.com/docs/api/#create-a-new-domain
func (c *Client) CreateDomain(domain *Domain) (err error) {
	var v url.Values

	if domain == nil {
		err = fmt.Errorf(domainParamError)
		return
	}

	if v, err = query.Values(domain); err != nil {
		return
	}

	err = c.post("domains", v, domain)

	return
}

// UpdateDomain updates a domain
// https://www.baruwa.com/docs/api/#update-a-domain
func (c *Client) UpdateDomain(domain *Domain) (err error) {
	var v url.Values

	if domain == nil {
		err = fmt.Errorf(domainParamError)
		return
	}

	if domain.ID <= 0 {
		err = fmt.Errorf(domainSIDError)
		return
	}

	if v, err = query.Values(domain); err != nil {
		return
	}

	err = c.put(fmt.Sprintf("domains/%d", domain.ID), v, domain)

	return
}

// DeleteDomain deletes a domain
// https://www.baruwa.com/docs/api/#delete-a-domain
func (c *Client) DeleteDomain(domainID int) (err error) {
	if domainID <= 0 {
		err = fmt.Errorf(domainIDError)
		return
	}

	err = c.delete(fmt.Sprintf("domains/%d", domainID), nil)

	return
}
<|endoftext|>


<commit_before>package api

import (
	"errors"
	"fmt"
	"net/url"
	"os"

	"github.com/aisk/logp"
	"github.com/levigross/grequests"
)

type EngineInfo struct {
	AppID string `json:"appId"`
}

type VersionInfo struct {
	VersionTag string `json:"versionTag"`
}

type GroupDeployInfo struct {
	Deployable bool        `json:"deployable"`
	Version    VersionInfo `json:"version"`
}

type InstanceInfo struct {
	Name string `json:"name"`
	Prod int    `json:"prod"`
}

type GetGroupsResult struct {
	GroupName    string            `json:"groupName"`
	Repository   string            `json:"repository"`
	Domain       string            `json:"domain"`
	Instances    []InstanceInfo    `json:"instances"`
	Staging      GroupDeployInfo   `json:"staging"`
	Production   GroupDeployInfo   `json:"production"`
	Environments map[string]string `json:"environments"`
}

type DeployOptions struct {
	DirectUpload   bool
	Message        string
	NoDepsCache    bool
	OverwriteFuncs bool
	Options        string // Additional options in urlencode format
}

func deploy(appID string, group string, prod int, params map[string]interface{}) (*grequests.Response, error) {
	client := NewClientByApp(appID)

	opts, err := client.options()
	if err != nil {
		return nil, err
	}
	opts.Headers["X-LC-Id"] = appID

	var url string
	switch prod {
	case 0:
		url = "/1.1/engine/groups/" + group + "/staging/version"
	case 1:
		url = "/1.1/engine/groups/" + group + "/production/version"
	default:
		return nil, fmt.Errorf("invalid prod value %d", prod)
	}

	directUpload, _ := params["direct"].(bool)
	delete(params, "direct")
	if directUpload {
		opts.Data = func() map[string]string {
			data := make(map[string]string)
			for k, v := range params {
				data[k] = fmt.Sprint(v)
			}
			return data
		}()
		archiveFilePath, ok := params["zipUrl"].(string)
		if !ok {
			return nil, fmt.Errorf("bad archive file path")
		}
		fd, err := os.Open(archiveFilePath)
		if err != nil {
			return nil, err
		}
		defer func() {
			if err := fd.Close(); err != nil {
				logp.Error(err)
			}
		}()
		opts.Files = []grequests.FileUpload{
			{
				FileName:     "leanengine.zip",
				FileContents: fd,
				FieldName:    "tarball",
			},
		}

		return client.post(url, nil, opts)
	}
	return client.post(url, params, opts)
}

// DeployImage will deploy the engine group with specify image tag
func DeployImage(appID string, group string, prod int, imageTag string, opts *DeployOptions) (string, error) {
	params, err := prepareDeployParams(opts)

	if err != nil {
		return "", err
	}

	params["versionTag"] = imageTag

	resp, err := deploy(appID, group, prod, params)
	if err != nil {
		return "", err
	}
	result := new(struct {
		EventToken string `json:"eventToken"`
	})
	err = resp.JSON(result)
	return result.EventToken, err
}

// DeployAppFromGit will deploy applications with user's git repo
// returns the event token for polling deploy log
func DeployAppFromGit(appID string, group string, prod int, revision string, opts *DeployOptions) (string, error) {
	params, err := prepareDeployParams(opts)

	if err != nil {
		return "", err
	}

	params["gitTag"] = revision

	resp, err := deploy(appID, group, prod, params)
	if err != nil {
		return "", err
	}
	result := new(struct {
		EventToken string `json:"eventToken"`
	})
	err = resp.JSON(result)
	return result.EventToken, err
}

// DeployAppFromFile will deploy applications with specific file
// returns the event token for polling deploy log
func DeployAppFromFile(appID string, group string, prod int, fileURL string, opts *DeployOptions) (string, error) {
	params, err := prepareDeployParams(opts)

	if err != nil {
		return "", err
	}

	params["direct"] = opts.DirectUpload
	params["zipUrl"] = fileURL

	resp, err := deploy(appID, group, prod, params)
	if err != nil {
		return "", err
	}

	result := new(struct {
		EventToken string `json:"eventToken"`
	})
	err = resp.JSON(result)
	return result.EventToken, err
}

// GetGroups returns the application's engine groups
func GetGroups(appID string) ([]*GetGroupsResult, error) {
	client := NewClientByApp(appID)

	opts, err := client.options()
	if err != nil {
		return nil, err
	}
	opts.Headers["X-LC-Id"] = appID

	resp, err := client.get("/1.1/engine/groups?all=true", opts)
	if err != nil {
		return nil, err
	}

	var result []*GetGroupsResult
	err = resp.JSON(&result)
	if err != nil {
		return nil, err
	}

	// filter the staging group, since it's not used anymore
	var filtered []*GetGroupsResult
	for _, group := range result {
		if group.GroupName == "staging" {
			continue
		}
		filtered = append(filtered, group)
	}

	return filtered, nil
}

// GetGroup will fetch all groups from API and return the current group info
func GetGroup(appID string, groupName string) (*GetGroupsResult, error) {
	groups, err := GetGroups(appID)
	if err != nil {
		return nil, err
	}
	for _, group := range groups {
		if group.GroupName == groupName {
			return group, nil
		}
	}
	return nil, errors.New("Failed to find group: " + groupName)
}

func GetEngineInfo(appID string) (*EngineInfo, error) {
	client := NewClientByApp(appID)

	opts, err := client.options()
	if err != nil {
		return nil, err
	}
	opts.Headers["X-LC-Id"] = appID

	response, err := client.get("/1.1/engine", opts)
	if err != nil {
		return nil, err
	}
	var result = new(EngineInfo)
	err = response.JSON(result)
	return result, err
}

func PutEnvironments(appID string, group string, envs map[string]string) error {
	client := NewClientByApp(appID)

	opts, err := client.options()
	if err != nil {
		return err
	}
	opts.Headers["X-LC-Id"] = appID

	params := make(map[string]interface{})
	environments := make(map[string]interface{})
	for k, v := range envs {
		environments[k] = v
	}
	params["environments"] = environments

	url := "/1.1/engine/groups/" + group
	response, err := client.patch(url, params, opts)
	if err != nil {
		return err
	}
	if response.StatusCode != 200 {
		return fmt.Errorf("Error updating environment variable, code: %d", response.StatusCode)
	}
	return nil
}

func prepareDeployParams(options *DeployOptions) (map[string]interface{}, error) {
	params := map[string]interface{}{
		"noDependenciesCache": options.NoDepsCache,
		"overwriteFunctions":  options.OverwriteFuncs,
		"async":               true,
	}

	if options.Message != "" {
		params["comment"] = options.Message
	}

	if options.Options != "" {
		queryString, err := url.ParseQuery(options.Options)

		if err != nil {
			return nil, err
		}

		for k, v := range queryString {
			params[k] = v[0]
		}
	}

	return params, nil
}
<commit_msg>chore: rename multipart field's name<commit_after>package api

import (
	"errors"
	"fmt"
	"net/url"
	"os"

	"github.com/aisk/logp"
	"github.com/levigross/grequests"
)

type EngineInfo struct {
	AppID string `json:"appId"`
}

type VersionInfo struct {
	VersionTag string `json:"versionTag"`
}

type GroupDeployInfo struct {
	Deployable bool        `json:"deployable"`
	Version    VersionInfo `json:"version"`
}

type InstanceInfo struct {
	Name string `json:"name"`
	Prod int    `json:"prod"`
}

type GetGroupsResult struct {
	GroupName    string            `json:"groupName"`
	Repository   string            `json:"repository"`
	Domain       string            `json:"domain"`
	Instances    []InstanceInfo    `json:"instances"`
	Staging      GroupDeployInfo   `json:"staging"`
	Production   GroupDeployInfo   `json:"production"`
	Environments map[string]string `json:"environments"`
}

type DeployOptions struct {
	DirectUpload   bool
	Message        string
	NoDepsCache    bool
	OverwriteFuncs bool
	Options        string // Additional options in urlencode format
}

func deploy(appID string, group string, prod int, params map[string]interface{}) (*grequests.Response, error) {
	client := NewClientByApp(appID)

	opts, err := client.options()
	if err != nil {
		return nil, err
	}
	opts.Headers["X-LC-Id"] = appID

	var url string
	switch prod {
	case 0:
		url = "/1.1/engine/groups/" + group + "/staging/version"
	case 1:
		url = "/1.1/engine/groups/" + group + "/production/version"
	default:
		return nil, fmt.Errorf("invalid prod value %d", prod)
	}

	directUpload, _ := params["direct"].(bool)
	delete(params, "direct")
	if directUpload {
		opts.Data = func() map[string]string {
			data := make(map[string]string)
			for k, v := range params {
				data[k] = fmt.Sprint(v)
			}
			return data
		}()
		archiveFilePath := opts.Data["zipUrl"]
		delete(opts.Data, "zipUrl")
		fd, err := os.Open(archiveFilePath)
		if err != nil {
			return nil, err
		}
		defer func() {
			if err := fd.Close(); err != nil {
				logp.Error(err)
			}
		}()
		opts.Files = []grequests.FileUpload{
			{
				FileName:     "leanengine.zip",
				FileContents: fd,
				FieldName:    "deploy",
			},
		}

		return client.post(url, nil, opts)
	}
	return client.post(url, params, opts)
}

// DeployImage will deploy the engine group with specify image tag
func DeployImage(appID string, group string, prod int, imageTag string, opts *DeployOptions) (string, error) {
	params, err := prepareDeployParams(opts)

	if err != nil {
		return "", err
	}

	params["versionTag"] = imageTag

	resp, err := deploy(appID, group, prod, params)
	if err != nil {
		return "", err
	}
	result := new(struct {
		EventToken string `json:"eventToken"`
	})
	err = resp.JSON(result)
	return result.EventToken, err
}

// DeployAppFromGit will deploy applications with user's git repo
// returns the event token for polling deploy log
func DeployAppFromGit(appID string, group string, prod int, revision string, opts *DeployOptions) (string, error) {
	params, err := prepareDeployParams(opts)

	if err != nil {
		return "", err
	}

	params["gitTag"] = revision

	resp, err := deploy(appID, group, prod, params)
	if err != nil {
		return "", err
	}
	result := new(struct {
		EventToken string `json:"eventToken"`
	})
	err = resp.JSON(result)
	return result.EventToken, err
}

// DeployAppFromFile will deploy applications with specific file
// returns the event token for polling deploy log
func DeployAppFromFile(appID string, group string, prod int, fileURL string, opts *DeployOptions) (string, error) {
	params, err := prepareDeployParams(opts)

	if err != nil {
		return "", err
	}

	params["direct"] = opts.DirectUpload
	params["zipUrl"] = fileURL

	resp, err := deploy(appID, group, prod, params)
	if err != nil {
		return "", err
	}

	result := new(struct {
		EventToken string `json:"eventToken"`
	})
	err = resp.JSON(result)
	return result.EventToken, err
}

// GetGroups returns the application's engine groups
func GetGroups(appID string) ([]*GetGroupsResult, error) {
	client := NewClientByApp(appID)

	opts, err := client.options()
	if err != nil {
		return nil, err
	}
	opts.Headers["X-LC-Id"] = appID

	resp, err := client.get("/1.1/engine/groups?all=true", opts)
	if err != nil {
		return nil, err
	}

	var result []*GetGroupsResult
	err = resp.JSON(&result)
	if err != nil {
		return nil, err
	}

	// filter the staging group, since it's not used anymore
	var filtered []*GetGroupsResult
	for _, group := range result {
		if group.GroupName == "staging" {
			continue
		}
		filtered = append(filtered, group)
	}

	return filtered, nil
}

// GetGroup will fetch all groups from API and return the current group info
func GetGroup(appID string, groupName string) (*GetGroupsResult, error) {
	groups, err := GetGroups(appID)
	if err != nil {
		return nil, err
	}
	for _, group := range groups {
		if group.GroupName == groupName {
			return group, nil
		}
	}
	return nil, errors.New("Failed to find group: " + groupName)
}

func GetEngineInfo(appID string) (*EngineInfo, error) {
	client := NewClientByApp(appID)

	opts, err := client.options()
	if err != nil {
		return nil, err
	}
	opts.Headers["X-LC-Id"] = appID

	response, err := client.get("/1.1/engine", opts)
	if err != nil {
		return nil, err
	}
	var result = new(EngineInfo)
	err = response.JSON(result)
	return result, err
}

func PutEnvironments(appID string, group string, envs map[string]string) error {
	client := NewClientByApp(appID)

	opts, err := client.options()
	if err != nil {
		return err
	}
	opts.Headers["X-LC-Id"] = appID

	params := make(map[string]interface{})
	environments := make(map[string]interface{})
	for k, v := range envs {
		environments[k] = v
	}
	params["environments"] = environments

	url := "/1.1/engine/groups/" + group
	response, err := client.patch(url, params, opts)
	if err != nil {
		return err
	}
	if response.StatusCode != 200 {
		return fmt.Errorf("Error updating environment variable, code: %d", response.StatusCode)
	}
	return nil
}

func prepareDeployParams(options *DeployOptions) (map[string]interface{}, error) {
	params := map[string]interface{}{
		"noDependenciesCache": options.NoDepsCache,
		"overwriteFunctions":  options.OverwriteFuncs,
		"async":               true,
	}

	if options.Message != "" {
		params["comment"] = options.Message
	}

	if options.Options != "" {
		queryString, err := url.ParseQuery(options.Options)

		if err != nil {
			return nil, err
		}

		for k, v := range queryString {
			params[k] = v[0]
		}
	}

	return params, nil
}
<|endoftext|>


<commit_before>package api

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/types"

	"github.com/julienschmidt/httprouter"
)

var (
	// TODO: Replace this function by accepting user input.
	recommendedHosts = func() uint64 {
		if build.Release == "dev" {
			return 2
		}
		if build.Release == "standard" {
			return 2
		}
		if build.Release == "testing" {
			return 1
		}
		panic("unrecognized release constant in api")
	}()
)

type (
	// RenterGET contains various renter metrics.
	RenterGET struct {
		FinancialMetrics modules.RenterFinancialMetrics `json:"financialmetrics"`
	}

	// DownloadQueue contains the renter's download queue.
	RenterDownloadQueue struct {
		Downloads []modules.DownloadInfo `json:"downloads"`
	}

	// RenterFiles lists the files known to the renter.
	RenterFiles struct {
		Files []modules.FileInfo `json:"files"`
	}

	// RenterLoad lists files that were loaded into the renter.
	RenterLoad struct {
		FilesAdded []string `json:"filesadded"`
	}

	// RenterShareASCII contains an ASCII-encoded .sia file.
	RenterShareASCII struct {
		ASCIIsia string `json:"asciisia"`
	}

	// ActiveHosts lists active hosts on the network.
	ActiveHosts struct {
		Hosts []modules.HostDBEntry `json:"hosts"`
	}
)

// renterHandler handles the API call to /renter.
func (srv *Server) renterHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	writeJSON(w, RenterGET{
		FinancialMetrics: srv.renter.FinancialMetrics(),
	})
}

// renterAllowanceHandlerGET handles the API call to get the allowance.
func (srv *Server) renterAllowanceHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	writeJSON(w, srv.renter.Allowance())
}

// renterAllowanceHandlerPOST handles the API call to set the allowance.
func (srv *Server) renterAllowanceHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	// scan values
	funds, ok := scanAmount(req.FormValue("funds"))
	if !ok {
		writeError(w, "Couldn't parse funds", http.StatusBadRequest)
		return
	}
	// var hosts uint64
	// _, err := fmt.Sscan(req.FormValue("hosts"), &hosts)
	// if err != nil {
	// 	writeError(w, "Couldn't parse hosts: "+err.Error(), http.StatusBadRequest)
	// 	return
	// }
	var period types.BlockHeight
	_, err := fmt.Sscan(req.FormValue("period"), &period)
	if err != nil {
		writeError(w, "Couldn't parse period: "+err.Error(), http.StatusBadRequest)
		return
	}
	// var renewWindow types.BlockHeight
	// _, err = fmt.Sscan(req.FormValue("renewwindow"), &renewWindow)
	// if err != nil {
	// 	writeError(w, "Couldn't parse renewwindow: "+err.Error(), http.StatusBadRequest)
	// 	return
	// }

	err = srv.renter.SetAllowance(modules.Allowance{
		Funds:  funds,
		Period: period,

		// TODO: let user specify these
		Hosts:       recommendedHosts,
		RenewWindow: period / 2,
	})
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeSuccess(w)
}

// renterDownloadsHandler handles the API call to request the download queue.
func (srv *Server) renterDownloadsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	writeJSON(w, RenterDownloadQueue{
		Downloads: srv.renter.DownloadQueue(),
	})
}

// renterLoadHandler handles the API call to load a '.sia' file.
func (srv *Server) renterLoadHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	files, err := srv.renter.LoadSharedFiles(req.FormValue("source"))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	writeJSON(w, RenterLoad{FilesAdded: files})
}

// renterLoadAsciiHandler handles the API call to load a '.sia' file
// in ASCII form.
func (srv *Server) renterLoadAsciiHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	files, err := srv.renter.LoadSharedFilesAscii(req.FormValue("asciisia"))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	writeJSON(w, RenterLoad{FilesAdded: files})
}

// renterRenameHandler handles the API call to rename a file entry in the
// renter.
func (srv *Server) renterRenameHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := srv.renter.RenameFile(strings.TrimPrefix(ps.ByName("siapath"), "/"), req.FormValue("newsiapath"))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	writeSuccess(w)
}

// renterFilesHandler handles the API call to list all of the files.
func (srv *Server) renterFilesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	writeJSON(w, RenterFiles{
		Files: srv.renter.FileList(),
	})
}

// renterDeleteHander handles the API call to delete a file entry from the
// renter.
func (srv *Server) renterDeleteHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := srv.renter.DeleteFile(strings.TrimPrefix(ps.ByName("siapath"), "/"))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	writeSuccess(w)
}

// renterDownloadHandler handles the API call to download a file.
func (srv *Server) renterDownloadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := srv.renter.Download(strings.TrimPrefix(ps.ByName("siapath"), "/"), req.FormValue("destination"))
	if err != nil {
		writeError(w, "Download failed: "+err.Error(), http.StatusInternalServerError)
		return
	}

	writeSuccess(w)
}

// renterShareHandler handles the API call to create a '.sia' file that
// shares a set of file.
func (srv *Server) renterShareHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := srv.renter.ShareFiles(strings.Split(req.FormValue("siapaths"), ","), req.FormValue("destination"))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	writeSuccess(w)
}

// renterShareAsciiHandler handles the API call to return a '.sia' file
// in ascii form.
func (srv *Server) renterShareAsciiHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	ascii, err := srv.renter.ShareFilesAscii(strings.Split(req.FormValue("siapaths"), ","))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeJSON(w, RenterShareASCII{
		ASCIIsia: ascii,
	})
}

// renterUploadHandler handles the API call to upload a file.
func (srv *Server) renterUploadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := srv.renter.Upload(modules.FileUploadParams{
		Source:  req.FormValue("source"),
		SiaPath: strings.TrimPrefix(ps.ByName("siapath"), "/"),
		// let the renter decide these values; eventually they will be configurable
		ErasureCode: nil,
	})
	if err != nil {
		writeError(w, "Upload failed: "+err.Error(), http.StatusInternalServerError)
		return
	}

	writeSuccess(w)
}

// renterHostsActiveHandler handes the API call asking for the list of active
// hosts.
func (srv *Server) renterHostsActiveHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	writeJSON(w, ActiveHosts{
		Hosts: srv.renter.ActiveHosts(),
	})
}

// renterHostsAllHandler handes the API call asking for the list of all hosts.
func (srv *Server) renterHostsAllHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	writeJSON(w, ActiveHosts{
		Hosts: srv.renter.AllHosts(),
	})
}
<commit_msg>fix rc recommendedHosts<commit_after>package api

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/types"

	"github.com/julienschmidt/httprouter"
)

var (
	// TODO: Replace this function by accepting user input.
	recommendedHosts = func() uint64 {
		if build.Release == "dev" {
			return 2
		}
		if build.Release == "standard" {
			return 6
		}
		if build.Release == "testing" {
			return 1
		}
		panic("unrecognized release constant in api")
	}()
)

type (
	// RenterGET contains various renter metrics.
	RenterGET struct {
		FinancialMetrics modules.RenterFinancialMetrics `json:"financialmetrics"`
	}

	// DownloadQueue contains the renter's download queue.
	RenterDownloadQueue struct {
		Downloads []modules.DownloadInfo `json:"downloads"`
	}

	// RenterFiles lists the files known to the renter.
	RenterFiles struct {
		Files []modules.FileInfo `json:"files"`
	}

	// RenterLoad lists files that were loaded into the renter.
	RenterLoad struct {
		FilesAdded []string `json:"filesadded"`
	}

	// RenterShareASCII contains an ASCII-encoded .sia file.
	RenterShareASCII struct {
		ASCIIsia string `json:"asciisia"`
	}

	// ActiveHosts lists active hosts on the network.
	ActiveHosts struct {
		Hosts []modules.HostDBEntry `json:"hosts"`
	}
)

// renterHandler handles the API call to /renter.
func (srv *Server) renterHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	writeJSON(w, RenterGET{
		FinancialMetrics: srv.renter.FinancialMetrics(),
	})
}

// renterAllowanceHandlerGET handles the API call to get the allowance.
func (srv *Server) renterAllowanceHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	writeJSON(w, srv.renter.Allowance())
}

// renterAllowanceHandlerPOST handles the API call to set the allowance.
func (srv *Server) renterAllowanceHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	// scan values
	funds, ok := scanAmount(req.FormValue("funds"))
	if !ok {
		writeError(w, "Couldn't parse funds", http.StatusBadRequest)
		return
	}
	// var hosts uint64
	// _, err := fmt.Sscan(req.FormValue("hosts"), &hosts)
	// if err != nil {
	// 	writeError(w, "Couldn't parse hosts: "+err.Error(), http.StatusBadRequest)
	// 	return
	// }
	var period types.BlockHeight
	_, err := fmt.Sscan(req.FormValue("period"), &period)
	if err != nil {
		writeError(w, "Couldn't parse period: "+err.Error(), http.StatusBadRequest)
		return
	}
	// var renewWindow types.BlockHeight
	// _, err = fmt.Sscan(req.FormValue("renewwindow"), &renewWindow)
	// if err != nil {
	// 	writeError(w, "Couldn't parse renewwindow: "+err.Error(), http.StatusBadRequest)
	// 	return
	// }

	err = srv.renter.SetAllowance(modules.Allowance{
		Funds:  funds,
		Period: period,

		// TODO: let user specify these
		Hosts:       recommendedHosts,
		RenewWindow: period / 2,
	})
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeSuccess(w)
}

// renterDownloadsHandler handles the API call to request the download queue.
func (srv *Server) renterDownloadsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	writeJSON(w, RenterDownloadQueue{
		Downloads: srv.renter.DownloadQueue(),
	})
}

// renterLoadHandler handles the API call to load a '.sia' file.
func (srv *Server) renterLoadHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	files, err := srv.renter.LoadSharedFiles(req.FormValue("source"))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	writeJSON(w, RenterLoad{FilesAdded: files})
}

// renterLoadAsciiHandler handles the API call to load a '.sia' file
// in ASCII form.
func (srv *Server) renterLoadAsciiHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	files, err := srv.renter.LoadSharedFilesAscii(req.FormValue("asciisia"))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	writeJSON(w, RenterLoad{FilesAdded: files})
}

// renterRenameHandler handles the API call to rename a file entry in the
// renter.
func (srv *Server) renterRenameHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := srv.renter.RenameFile(strings.TrimPrefix(ps.ByName("siapath"), "/"), req.FormValue("newsiapath"))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	writeSuccess(w)
}

// renterFilesHandler handles the API call to list all of the files.
func (srv *Server) renterFilesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	writeJSON(w, RenterFiles{
		Files: srv.renter.FileList(),
	})
}

// renterDeleteHander handles the API call to delete a file entry from the
// renter.
func (srv *Server) renterDeleteHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := srv.renter.DeleteFile(strings.TrimPrefix(ps.ByName("siapath"), "/"))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	writeSuccess(w)
}

// renterDownloadHandler handles the API call to download a file.
func (srv *Server) renterDownloadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := srv.renter.Download(strings.TrimPrefix(ps.ByName("siapath"), "/"), req.FormValue("destination"))
	if err != nil {
		writeError(w, "Download failed: "+err.Error(), http.StatusInternalServerError)
		return
	}

	writeSuccess(w)
}

// renterShareHandler handles the API call to create a '.sia' file that
// shares a set of file.
func (srv *Server) renterShareHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := srv.renter.ShareFiles(strings.Split(req.FormValue("siapaths"), ","), req.FormValue("destination"))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	writeSuccess(w)
}

// renterShareAsciiHandler handles the API call to return a '.sia' file
// in ascii form.
func (srv *Server) renterShareAsciiHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	ascii, err := srv.renter.ShareFilesAscii(strings.Split(req.FormValue("siapaths"), ","))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeJSON(w, RenterShareASCII{
		ASCIIsia: ascii,
	})
}

// renterUploadHandler handles the API call to upload a file.
func (srv *Server) renterUploadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	err := srv.renter.Upload(modules.FileUploadParams{
		Source:  req.FormValue("source"),
		SiaPath: strings.TrimPrefix(ps.ByName("siapath"), "/"),
		// let the renter decide these values; eventually they will be configurable
		ErasureCode: nil,
	})
	if err != nil {
		writeError(w, "Upload failed: "+err.Error(), http.StatusInternalServerError)
		return
	}

	writeSuccess(w)
}

// renterHostsActiveHandler handes the API call asking for the list of active
// hosts.
func (srv *Server) renterHostsActiveHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	writeJSON(w, ActiveHosts{
		Hosts: srv.renter.ActiveHosts(),
	})
}

// renterHostsAllHandler handes the API call asking for the list of all hosts.
func (srv *Server) renterHostsAllHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	writeJSON(w, ActiveHosts{
		Hosts: srv.renter.AllHosts(),
	})
}
<|endoftext|>


<commit_before>package api

import (
	"net/http"

	"github.com/anthonynsimon/parrot/auth"
	"github.com/anthonynsimon/parrot/datastore"
	"github.com/pressly/chi"
)

// TODO: inject store via closures instead of keeping global var
var store datastore.Store

func NewRouter(ds datastore.Store, authProvider auth.Provider) http.Handler {
	store = ds
	handleToken := tokenMiddleware(authProvider)

	router := chi.NewRouter()
	// Enforce use of Content-Type header for POST, PUT and PATCH methods and validate it's JSON
	router.Use(cors, enforceContentTypeJSON)

	router.Get("/ping", ping)
	router.Post("/authenticate", authenticate(authProvider))

	router.Route("/users", func(r1 chi.Router) {
		r1.Post("/", createUser)
	})

	router.Route("/projects", func(r1 chi.Router) {
		// Past this point, all routes require a valid token
		r1.Use(handleToken)
		r1.Get("/", getUserProjects)
		r1.Post("/", createProject)

		r1.Route("/:projectID", func(r2 chi.Router) {
			r2.Get("/", mustAuthorize(canViewProject, showProject))
			r2.Put("/", mustAuthorize(canUpdateProject, updateProject))
			r2.Delete("/", mustAuthorize(canDeleteProject, deleteProject))

			r2.Route("/users", func(r3 chi.Router) {
				r3.Get("/", mustAuthorize(canViewProjectRoles, getProjectUsers))
				r3.Post("/", mustAuthorize(canAssignRoles, assignProjectUser))
				r3.Put("/:userID", mustAuthorize(canUpdateRoles, updateProjectUser))
				r3.Delete("/:userID", mustAuthorize(canRevokeRoles, revokeProjectUser))
			})

			r2.Route("/locales", func(r3 chi.Router) {
				r3.Get("/", mustAuthorize(canViewLocales, findLocales))
				r3.Post("/", mustAuthorize(canCreateLocales, createLocale))
				r3.Get("/:localeID", mustAuthorize(canViewLocales, showLocale))
				r3.Put("/:localeID", mustAuthorize(canUpdateLocales, updateLocale))
				r3.Delete("/:localeID", mustAuthorize(canDeleteLocales, deleteLocale))
			})
		})
	})

	return router
}
<commit_msg>Add gzip<commit_after>package api

import (
	"net/http"

	"github.com/anthonynsimon/parrot/auth"
	"github.com/anthonynsimon/parrot/datastore"
	"github.com/pressly/chi"
	"github.com/pressly/chi/middleware"
)

// TODO: inject store via closures instead of keeping global var
var store datastore.Store

func NewRouter(ds datastore.Store, authProvider auth.Provider) http.Handler {
	store = ds
	handleToken := tokenMiddleware(authProvider)

	router := chi.NewRouter()
	// Enforce use of Content-Type header for POST, PUT and PATCH methods and validate it's JSON
	router.Use(middleware.DefaultCompress, cors, enforceContentTypeJSON)

	router.Get("/ping", ping)
	router.Post("/authenticate", authenticate(authProvider))

	router.Route("/users", func(r1 chi.Router) {
		r1.Post("/", createUser)
	})

	router.Route("/projects", func(r1 chi.Router) {
		// Past this point, all routes require a valid token
		r1.Use(handleToken)
		r1.Get("/", getUserProjects)
		r1.Post("/", createProject)

		r1.Route("/:projectID", func(r2 chi.Router) {
			r2.Get("/", mustAuthorize(canViewProject, showProject))
			r2.Put("/", mustAuthorize(canUpdateProject, updateProject))
			r2.Delete("/", mustAuthorize(canDeleteProject, deleteProject))

			r2.Route("/users", func(r3 chi.Router) {
				r3.Get("/", mustAuthorize(canViewProjectRoles, getProjectUsers))
				r3.Post("/", mustAuthorize(canAssignRoles, assignProjectUser))
				r3.Put("/:userID", mustAuthorize(canUpdateRoles, updateProjectUser))
				r3.Delete("/:userID", mustAuthorize(canRevokeRoles, revokeProjectUser))
			})

			r2.Route("/locales", func(r3 chi.Router) {
				r3.Get("/", mustAuthorize(canViewLocales, findLocales))
				r3.Post("/", mustAuthorize(canCreateLocales, createLocale))
				r3.Get("/:localeID", mustAuthorize(canViewLocales, showLocale))
				r3.Put("/:localeID", mustAuthorize(canUpdateLocales, updateLocale))
				r3.Delete("/:localeID", mustAuthorize(canDeleteLocales, deleteLocale))
			})
		})
	})

	return router
}
<|endoftext|>