<commit_before>/*
Copyright 2017 Tuenti Technologies S.L. All rights reserved.

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

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

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

package pouch

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"text/template"
	"time"

	"github.com/tuenti/pouch/pkg/vault"
)

const (
	DefaultFileMode = os.FileMode(0600)
)

type Pouch interface {
	Run(context.Context) error
	Watch(path string) error
	AddStatusNotifier(StatusNotifier)
	ServiceReloader(Reloader)
}

type StatusNotifier interface {
	NotifyReady() error
}

type Reloader interface {
	Reload(context.Context, string) error
}

type pouch struct {
	State *PouchState

	Vault     vault.Vault
	Secrets   map[string]SecretConfig
	Files     map[string]FileConfig
	Notifiers map[string]NotifierConfig
	Reloader  Reloader

	statusNotifiers  []StatusNotifier
	pendingNotifiers map[string]bool
}

func getFileContent(fc FileConfig, data interface{}, secretFunc interface{}) (string, error) {
	if fc.Template != "" && fc.TemplateFile != "" {
		return "", fmt.Errorf("inline template and template file specified")
	}
	var t *template.Template
	funcMap := template.FuncMap{
		"secret": secretFunc,
	}
	var err error
	switch {
	case fc.Template != "":
		t, err = template.New("inline-template").Funcs(funcMap).Parse(fc.Template)
		if err != nil {
			return "", err
		}
	case fc.TemplateFile != "":
		d, err := ioutil.ReadFile(fc.TemplateFile)
		if err != nil {
			return "", err
		}
		t, err = template.New(fc.TemplateFile).Funcs(funcMap).Parse(string(d))
		if err != nil {
			return "", err
		}
	default:
		return "", fmt.Errorf("no content defined for file %s", fc.Path)
	}
	var b bytes.Buffer
	err = t.Execute(&b, data)
	if err != nil {
		return "", err
	}
	return b.String(), nil
}

func dirMode(mode os.FileMode) os.FileMode {
	result := os.FileMode(0)
	for i := 01; i <= 0777; i *= 010 {
		mask := os.FileMode(7 * i)
		if mode&mask > 0 {
			result |= (mode & mask) | os.FileMode(i)
		}
	}
	return result
}

var dataFuncMap = template.FuncMap{
	"env":      os.Getenv,
	"hostname": os.Hostname,
}

func resolveData(data map[string]interface{}) map[string]interface{} {
	result := make(map[string]interface{})
	for k, d := range data {
		resolved, err := func() (interface{}, error) {
			v, ok := d.(string)
			if !ok {
				return d, nil
			}
			t, err := template.New("secret-data").Funcs(dataFuncMap).Parse(v)
			if err != nil {
				return d, err
			}
			var b bytes.Buffer
			err = t.Execute(&b, nil)
			if err != nil {
				return d, err
			}
			return b.String(), nil
		}()
		if err != nil {
			log.Printf("When resolving data template '%s' for '%s': %v", d, k, err)
		}
		result[k] = resolved
	}
	return result
}

func (p *pouch) resolveSecret(name string, c SecretConfig) error {
	options := &vault.RequestOptions{Data: resolveData(c.Data)}
	s, err := p.Vault.Request(c.HTTPMethod, c.VaultURL, options)
	if err != nil {
		return err
	}
	p.State.SetSecret(name, s)
	return nil
}

func (p *pouch) resolveFile(fc FileConfig) error {
	mode := os.FileMode(fc.Mode)
	if mode == 0 {
		mode = DefaultFileMode
	}
	dir := path.Dir(fc.Path)
	err := os.MkdirAll(dir, dirMode(mode))
	if err != nil {
		return err
	}

	secretFunc := func(name, key string) (interface{}, error) {
		secret, found := p.State.Secrets[name]
		if !found {
			return nil, fmt.Errorf("unknown secret: %s", name)
		}
		value, found := secret.Data[key]
		if !found {
			return nil, fmt.Errorf("unkown key in secret '%s': %s", name, key)
		}
		secret.RegisterUsage(fc.Path)
		return value, nil
	}

	content, err := getFileContent(fc, nil, secretFunc)
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(fc.Path, []byte(content), mode)
	if err != nil {
		return fmt.Errorf("couldn't write secret in '%s': %s", fc.Path, err)
	}

	p.addForNotify(fc.Notify...)
	return nil
}

func (p *pouch) Run(ctx context.Context) error {
	err := p.Vault.Login()
	if err != nil {
		return err
	}
	p.State.Token = p.Vault.GetToken()
	err = p.State.Save()
	if err != nil {
		log.Printf("Couldn't save state: %s", err)
	}

	for name, c := range p.Secrets {
		if s, found := p.State.Secrets[name]; found {
			// Clean files using this secret, we'll process templates in case
			// someone has changed
			s.FilesUsing = nil
		} else {
			err = p.resolveSecret(name, c)
			if err != nil {
				return err
			}
		}
	}

	for name := range p.State.Secrets {
		if _, found := p.Secrets[name]; !found {
			p.State.DeleteSecret(name)
		}
	}

	for _, fc := range p.Files {
		err := p.resolveFile(fc)
		if err != nil {
			return err
		}
	}

	p.NotifyReady()

	for {
		p.notifyPending()

		err = p.State.Save()
		if err != nil {
			log.Printf("Couldn't save state: %s", err)
		}

		var nextUpdate <-chan time.Time
		s, ttu := p.State.NextUpdate()
		if s != nil {
			nextUpdate = time.After(ttu)
		} else {
			log.Printf("No secret to update")
		}

		select {
		case <-nextUpdate:
			log.Printf("Updating secret '%s'", s.Name)
			err = p.resolveSecret(s.Name, p.Secrets[s.Name])
			if err != nil {
				return err
			}
			for _, f := range p.State.Secrets[s.Name].FilesUsing {
				err = p.resolveFile(p.Files[f])
				if err != nil {
					return err
				}
			}
		case <-ctx.Done():
			return nil
		}
	}
}

func NewPouch(s *PouchState, vc vault.Vault, sc map[string]SecretConfig, fc []FileConfig, nc map[string]NotifierConfig) Pouch {
	fileMap := make(map[string]FileConfig)
	for _, f := range fc {
		fileMap[f.Path] = f
	}
	return &pouch{State: s, Vault: vc, Secrets: sc, Files: fileMap, Notifiers: nc}
}

func (p *pouch) ServiceReloader(r Reloader) {
	p.Reloader = r
}

func (p *pouch) AddStatusNotifier(n StatusNotifier) {
	p.statusNotifiers = append(p.statusNotifiers, n)
}

func (p *pouch) NotifyReady() {
	for _, n := range p.statusNotifiers {
		err := n.NotifyReady()
		if err != nil {
			log.Println(err)
		}
	}
}

func (p *pouch) addForNotify(names ...string) {
	if p.pendingNotifiers == nil {
		p.pendingNotifiers = make(map[string]bool)
	}
	for _, name := range names {
		p.pendingNotifiers[name] = true
	}
}

func (p *pouch) notifyPending() {
	for pending := range p.pendingNotifiers {
		p.Notify(pending)
		delete(p.pendingNotifiers, pending)
	}
}
<commit_msg>Save the state any time we set a secret<commit_after>/*
Copyright 2017 Tuenti Technologies S.L. All rights reserved.

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

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

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

package pouch

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"text/template"
	"time"

	"github.com/tuenti/pouch/pkg/vault"
)

const (
	DefaultFileMode = os.FileMode(0600)
)

type Pouch interface {
	Run(context.Context) error
	Watch(path string) error
	AddStatusNotifier(StatusNotifier)
	ServiceReloader(Reloader)
}

type StatusNotifier interface {
	NotifyReady() error
}

type Reloader interface {
	Reload(context.Context, string) error
}

type pouch struct {
	State *PouchState

	Vault     vault.Vault
	Secrets   map[string]SecretConfig
	Files     map[string]FileConfig
	Notifiers map[string]NotifierConfig
	Reloader  Reloader

	statusNotifiers  []StatusNotifier
	pendingNotifiers map[string]bool
}

func getFileContent(fc FileConfig, data interface{}, secretFunc interface{}) (string, error) {
	if fc.Template != "" && fc.TemplateFile != "" {
		return "", fmt.Errorf("inline template and template file specified")
	}
	var t *template.Template
	funcMap := template.FuncMap{
		"secret": secretFunc,
	}
	var err error
	switch {
	case fc.Template != "":
		t, err = template.New("inline-template").Funcs(funcMap).Parse(fc.Template)
		if err != nil {
			return "", err
		}
	case fc.TemplateFile != "":
		d, err := ioutil.ReadFile(fc.TemplateFile)
		if err != nil {
			return "", err
		}
		t, err = template.New(fc.TemplateFile).Funcs(funcMap).Parse(string(d))
		if err != nil {
			return "", err
		}
	default:
		return "", fmt.Errorf("no content defined for file %s", fc.Path)
	}
	var b bytes.Buffer
	err = t.Execute(&b, data)
	if err != nil {
		return "", err
	}
	return b.String(), nil
}

func dirMode(mode os.FileMode) os.FileMode {
	result := os.FileMode(0)
	for i := 01; i <= 0777; i *= 010 {
		mask := os.FileMode(7 * i)
		if mode&mask > 0 {
			result |= (mode & mask) | os.FileMode(i)
		}
	}
	return result
}

var dataFuncMap = template.FuncMap{
	"env":      os.Getenv,
	"hostname": os.Hostname,
}

func resolveData(data map[string]interface{}) map[string]interface{} {
	result := make(map[string]interface{})
	for k, d := range data {
		resolved, err := func() (interface{}, error) {
			v, ok := d.(string)
			if !ok {
				return d, nil
			}
			t, err := template.New("secret-data").Funcs(dataFuncMap).Parse(v)
			if err != nil {
				return d, err
			}
			var b bytes.Buffer
			err = t.Execute(&b, nil)
			if err != nil {
				return d, err
			}
			return b.String(), nil
		}()
		if err != nil {
			log.Printf("When resolving data template '%s' for '%s': %v", d, k, err)
		}
		result[k] = resolved
	}
	return result
}

func (p *pouch) resolveSecret(name string, c SecretConfig) error {
	options := &vault.RequestOptions{Data: resolveData(c.Data)}
	s, err := p.Vault.Request(c.HTTPMethod, c.VaultURL, options)
	if err != nil {
		return err
	}
	p.State.SetSecret(name, s)
	err = p.State.Save()
	if err != nil {
		log.Printf("Couldn't save state: %s", err)
	}
	return nil
}

func (p *pouch) resolveFile(fc FileConfig) error {
	mode := os.FileMode(fc.Mode)
	if mode == 0 {
		mode = DefaultFileMode
	}
	dir := path.Dir(fc.Path)
	err := os.MkdirAll(dir, dirMode(mode))
	if err != nil {
		return err
	}

	secretFunc := func(name, key string) (interface{}, error) {
		secret, found := p.State.Secrets[name]
		if !found {
			return nil, fmt.Errorf("unknown secret: %s", name)
		}
		value, found := secret.Data[key]
		if !found {
			return nil, fmt.Errorf("unkown key in secret '%s': %s", name, key)
		}
		secret.RegisterUsage(fc.Path)
		return value, nil
	}

	content, err := getFileContent(fc, nil, secretFunc)
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(fc.Path, []byte(content), mode)
	if err != nil {
		return fmt.Errorf("couldn't write secret in '%s': %s", fc.Path, err)
	}

	p.addForNotify(fc.Notify...)
	return nil
}

func (p *pouch) Run(ctx context.Context) error {
	err := p.Vault.Login()
	if err != nil {
		return err
	}
	p.State.Token = p.Vault.GetToken()
	err = p.State.Save()
	if err != nil {
		log.Printf("Couldn't save state: %s", err)
	}

	for name, c := range p.Secrets {
		if s, found := p.State.Secrets[name]; found {
			// Clean files using this secret, we'll process templates in case
			// someone has changed
			s.FilesUsing = nil
		} else {
			err = p.resolveSecret(name, c)
			if err != nil {
				return err
			}
		}
	}

	for name := range p.State.Secrets {
		if _, found := p.Secrets[name]; !found {
			p.State.DeleteSecret(name)
		}
	}

	for _, fc := range p.Files {
		err := p.resolveFile(fc)
		if err != nil {
			return err
		}
	}

	p.NotifyReady()

	for {
		p.notifyPending()

		err = p.State.Save()
		if err != nil {
			log.Printf("Couldn't save state: %s", err)
		}

		var nextUpdate <-chan time.Time
		s, ttu := p.State.NextUpdate()
		if s != nil {
			nextUpdate = time.After(ttu)
		} else {
			log.Printf("No secret to update")
		}

		select {
		case <-nextUpdate:
			log.Printf("Updating secret '%s'", s.Name)
			err = p.resolveSecret(s.Name, p.Secrets[s.Name])
			if err != nil {
				return err
			}
			for _, f := range p.State.Secrets[s.Name].FilesUsing {
				err = p.resolveFile(p.Files[f])
				if err != nil {
					return err
				}
			}
		case <-ctx.Done():
			return nil
		}
	}
}

func NewPouch(s *PouchState, vc vault.Vault, sc map[string]SecretConfig, fc []FileConfig, nc map[string]NotifierConfig) Pouch {
	fileMap := make(map[string]FileConfig)
	for _, f := range fc {
		fileMap[f.Path] = f
	}
	return &pouch{State: s, Vault: vc, Secrets: sc, Files: fileMap, Notifiers: nc}
}

func (p *pouch) ServiceReloader(r Reloader) {
	p.Reloader = r
}

func (p *pouch) AddStatusNotifier(n StatusNotifier) {
	p.statusNotifiers = append(p.statusNotifiers, n)
}

func (p *pouch) NotifyReady() {
	for _, n := range p.statusNotifiers {
		err := n.NotifyReady()
		if err != nil {
			log.Println(err)
		}
	}
}

func (p *pouch) addForNotify(names ...string) {
	if p.pendingNotifiers == nil {
		p.pendingNotifiers = make(map[string]bool)
	}
	for _, name := range names {
		p.pendingNotifiers[name] = true
	}
}

func (p *pouch) notifyPending() {
	for pending := range p.pendingNotifiers {
		p.Notify(pending)
		delete(p.pendingNotifiers, pending)
	}
}
<|endoftext|>


<commit_before>package prime

import (
	"math"
	"runtime"
)

var ps []uint64

func fill(i uint64, max uint64, ready chan bool) {
	a := 2 * i
	for a <= max {
		ps[a] = 1
		a = a + i
	}
	<-ready
}

func Primes(max uint64) []uint64 {
	runtime.GOMAXPROCS(runtime.NumCPU())
	ready := make(chan bool, runtime.NumCPU())

	ps = make([]uint64, max+1)
	m := uint64(math.Sqrt(float64(max)))
	for i := uint64(2); i <= m; i = i + 2 {

		if ps[i] == 0 {
			go fill(i, max, ready)
			ready <- true
		}
		if i == 2 {
			i = 1
		}

	}

	for i := 0; i < runtime.NumCPU(); i++ {
		ready <- true
	}

	var t []uint64
	m = uint64(len(ps))
	for i := uint64(2); i < uint64(len(ps)); i++ {
		if ps[i] == 0 {
			t = append(t, ps[i])
		}
	}
	return t
}
<commit_msg>removing the global<commit_after>package prime

import (
	"math"
	"runtime"
)

func fill(ps []uint64, i uint64, max uint64, ready chan bool) {
	a := 2 * i
	for a <= max {
		ps[a] = 1
		a = a + i
	}
	<-ready
}

func Primes(max uint64) []uint64 {
	runtime.GOMAXPROCS(runtime.NumCPU())
	ready := make(chan bool, runtime.NumCPU())

	var ps = make([]uint64, max+1)
	m := uint64(math.Sqrt(float64(max)))
	for i := uint64(2); i <= m; i = i + 2 {

		if ps[i] == 0 {
			go fill(i, max, ready)
			ready <- true
		}
		if i == 2 {
			i = 1
		}

	}

	for i := 0; i < runtime.NumCPU(); i++ {
		ready <- true
	}

	var t []uint64
	m = uint64(len(ps))
	for i := uint64(2); i < uint64(len(ps)); i++ {
		if ps[i] == 0 {
			t = append(t, ps[i])
		}
	}
	return t
}
<|endoftext|>


<commit_before>package main

import (
    "bufio"
    "container/list"
    "io"
    "log"
    "net"
    "strconv"
    "strings"
)

func getBackend(hostname string) string {
    println("Getting backend for '", hostname, "'")
    ba := []byte(hostname)
    for i:=0; i < len(ba); i++ {
        println("Byte", i, "is", ba[i])
    }
    if hostname == "www.dilbert.com" {
        return "184.106.169.31"
    } else if hostname == "www.gilesthomas.com" {
        return "212.110.190.213"
    } else if hostname == "www.pythonanywhere.com" {
        return "50.19.109.98"
    }
    return "8.8.8.8"
}

func handleHTTPConnection(downstream net.Conn) {
    reader := bufio.NewReader(downstream)
    hostname := ""
    readLines := list.New()
    for hostname == "" {
        bytes, _, error := reader.ReadLine()
        if error != nil {
            println("Error reading")
            return
        }
        line := string(bytes)
        readLines.PushBack(line)
        if strings.HasPrefix(line, "Host: ") {
            hostname = strings.TrimPrefix(line, "Host: ")
            break
        }
    }
    if hostname == "" {
        println("No host!")
        return
    }
    backendAddress := getBackend(hostname)

    println("Making upstream connection to", backendAddress + ":80")
    upstream, error := net.Dial("tcp", backendAddress + ":80")
    if error != nil {
        println("Couldn't connect to upstream", error)
        return
    }

    for element := readLines.Front(); element != nil; element = element.Next() {
        line := element.Value.(string)
        upstream.Write([]byte(line))
        upstream.Write([]byte("\n"))
    }

    go io.Copy(upstream, reader)
    go io.Copy(downstream, upstream)
}

func handleHTTPSConnection(downstream net.Conn) {
    firstByte := make([]byte, 1)
    _, error := downstream.Read(firstByte)
    if error != nil {
        println("Couldn't read first byte :-(")
        return
    }
    if firstByte[0] != 0x16 {
        println("Not TLS :-(")
    }

    versionBytes := make([]byte, 2)
    _, error = downstream.Read(versionBytes)
    if error != nil {
        println("Couldn't read version bytes :-(")
        return
    }
    if versionBytes[0] < 3 || (versionBytes[0] == 3 && versionBytes[1] < 1) {
        println("SSL < 3.1 so it's still not TLS")
        return
    }

    restLengthBytes := make([]byte, 2)
    _, error = downstream.Read(restLengthBytes)
    if error != nil {
        println("Couldn't read restLength bytes :-(")
        return
    }
    restLength := (int(restLengthBytes[0]) << 8) + int(restLengthBytes[1])
    println("restLength is", restLength)

    rest := make([]byte, restLength)
    _, error = downstream.Read(rest)
    if error != nil {
        println("Couldn't read rest of bytes")
        return
    }

    current := 0

    handshakeType := rest[0]
    current += 1
    if handshakeType != 0x1 {
        println("Not a ClientHello")
        return
    }

    // Skip over another length
    current += 3
    // Skip over protocolversion
    println("Protocolversion is", rest[current], rest[current+1])
    current += 2
    // Skip over random number
    current += 4 + 28
    // Skip over session ID
    sessionIDLength := int(rest[current])
    current += 1
    println("session ID length", sessionIDLength)
    current += sessionIDLength

    cipherSuiteLength := (int(rest[current]) << 8) + int(rest[current + 1])
    current += 2
    println("CipherSuite length is", cipherSuiteLength)
    current += cipherSuiteLength

    compressionMethodLength := int(rest[current])
    current += 1
    println("CompressionMethodLength is", compressionMethodLength)
    current += compressionMethodLength

    if current > restLength {
        println("no extensions")
        return
    }

    extensionsLength := (int(rest[current]) << 8) + int(rest[current + 1])
    current += 2
    println("ExtensionsLength", extensionsLength)

    hostname := ""
    for current < restLength && hostname == "" {
        extensionType := (int(rest[current]) << 8) + int(rest[current + 1])
        current += 2
        println("Extension type", extensionType)

        extensionDataLength := (int(rest[current]) << 8) + int(rest[current + 1])
        current += 2
        println("Extension data length", extensionDataLength)

        if extensionType == 0 {
            println("It's an SNI")

            // Skip over number of names as we're assuming there's just one
            current += 2

            nameType := rest[current]
            current += 1
            if nameType != 0 {
                println("Not a hostname")
                return
            }
            nameLen := (int(rest[current]) << 8) + int(rest[current+1])
            current += 2
            println("Name length", nameLen)
            hostname = string(rest[current:current + nameLen])
            println("got a name:", hostname)
        } else {
            println("Not an SNI")
        }

        current += extensionDataLength
    }
    if hostname == "" {
        println("No hostname")
        return
    }

    backendAddress := getBackend(hostname)
    println("Connecting via TCP to", backendAddress + ":443")
    upstream, error := net.Dial("tcp", backendAddress + ":443")
    if error != nil {
        log.Fatal(error)
        return
    }
    println("Connected, replaying header")

    upstream.Write(firstByte)
    upstream.Write(versionBytes)
    upstream.Write(restLengthBytes)
    upstream.Write(rest)

    println("Header replayed, going live")
    go io.Copy(upstream, downstream)
    go io.Copy(downstream, upstream)
}

func doProxy(done chan int, port int, handle func(net.Conn)) {
    listener, error := net.Listen("tcp", "0.0.0.0:" + strconv.Itoa(port))
    if error != nil {
        println("Couldn't start listening", error)
    }
    println("Started proxy on", port, "-- listening...")
    for {
        connection, error := listener.Accept()
        if error != nil {
            println("Accept error", error)
            return
        }

        go handle(connection)
    }
    done <- 1
}

func main() {
    httpDone := make(chan int)
    go doProxy(httpDone, 80, handleHTTPConnection)

    httpsDone := make(chan int)
    go doProxy(httpsDone, 443, handleHTTPSConnection)

    <- httpDone
    <- httpsDone
}
<commit_msg>Stripped some error logging.  Basic functionality works.#<commit_after>package main

import (
    "bufio"
    "container/list"
    "io"
    "log"
    "net"
    "strconv"
    "strings"
)

func getBackend(hostname string) string {
    if hostname == "www.dilbert.com" {
        return "184.106.169.31"
    } else if hostname == "www.gilesthomas.com" {
        return "212.110.190.213"
    } else if hostname == "www.pythonanywhere.com" {
        return "50.19.109.98"
    }
    return "8.8.8.8"
}

func handleHTTPConnection(downstream net.Conn) {
    reader := bufio.NewReader(downstream)
    hostname := ""
    readLines := list.New()
    for hostname == "" {
        bytes, _, error := reader.ReadLine()
        if error != nil {
            println("Error reading")
            return
        }
        line := string(bytes)
        readLines.PushBack(line)
        if strings.HasPrefix(line, "Host: ") {
            hostname = strings.TrimPrefix(line, "Host: ")
            break
        }
    }
    if hostname == "" {
        println("No host!")
        return
    }
    backendAddress := getBackend(hostname)

    upstream, error := net.Dial("tcp", backendAddress + ":80")
    if error != nil {
        println("Couldn't connect to upstream", error)
        return
    }

    for element := readLines.Front(); element != nil; element = element.Next() {
        line := element.Value.(string)
        upstream.Write([]byte(line))
        upstream.Write([]byte("\n"))
    }

    go io.Copy(upstream, reader)
    go io.Copy(downstream, upstream)
}

func handleHTTPSConnection(downstream net.Conn) {
    firstByte := make([]byte, 1)
    _, error := downstream.Read(firstByte)
    if error != nil {
        println("Couldn't read first byte :-(")
        return
    }
    if firstByte[0] != 0x16 {
        println("Not TLS :-(")
    }

    versionBytes := make([]byte, 2)
    _, error = downstream.Read(versionBytes)
    if error != nil {
        println("Couldn't read version bytes :-(")
        return
    }
    if versionBytes[0] < 3 || (versionBytes[0] == 3 && versionBytes[1] < 1) {
        println("SSL < 3.1 so it's still not TLS")
        return
    }

    restLengthBytes := make([]byte, 2)
    _, error = downstream.Read(restLengthBytes)
    if error != nil {
        println("Couldn't read restLength bytes :-(")
        return
    }
    restLength := (int(restLengthBytes[0]) << 8) + int(restLengthBytes[1])

    rest := make([]byte, restLength)
    _, error = downstream.Read(rest)
    if error != nil {
        println("Couldn't read rest of bytes")
        return
    }

    current := 0

    handshakeType := rest[0]
    current += 1
    if handshakeType != 0x1 {
        println("Not a ClientHello")
        return
    }

    // Skip over another length
    current += 3
    // Skip over protocolversion
    current += 2
    // Skip over random number
    current += 4 + 28
    // Skip over session ID
    sessionIDLength := int(rest[current])
    current += 1
    current += sessionIDLength

    cipherSuiteLength := (int(rest[current]) << 8) + int(rest[current + 1])
    current += 2
    current += cipherSuiteLength

    compressionMethodLength := int(rest[current])
    current += 1
    current += compressionMethodLength

    if current > restLength {
        println("no extensions")
        return
    }

    // Skip over extensionsLength
    // extensionsLength := (int(rest[current]) << 8) + int(rest[current + 1])
    current += 2

    hostname := ""
    for current < restLength && hostname == "" {
        extensionType := (int(rest[current]) << 8) + int(rest[current + 1])
        current += 2

        extensionDataLength := (int(rest[current]) << 8) + int(rest[current + 1])
        current += 2

        if extensionType == 0 {

            // Skip over number of names as we're assuming there's just one
            current += 2

            nameType := rest[current]
            current += 1
            if nameType != 0 {
                println("Not a hostname")
                return
            }
            nameLen := (int(rest[current]) << 8) + int(rest[current+1])
            current += 2
            hostname = string(rest[current:current + nameLen])
        }

        current += extensionDataLength
    }
    if hostname == "" {
        println("No hostname")
        return
    }

    backendAddress := getBackend(hostname)
    upstream, error := net.Dial("tcp", backendAddress + ":443")
    if error != nil {
        log.Fatal(error)
        return
    }

    upstream.Write(firstByte)
    upstream.Write(versionBytes)
    upstream.Write(restLengthBytes)
    upstream.Write(rest)

    go io.Copy(upstream, downstream)
    go io.Copy(downstream, upstream)
}

func doProxy(done chan int, port int, handle func(net.Conn)) {
    listener, error := net.Listen("tcp", "0.0.0.0:" + strconv.Itoa(port))
    if error != nil {
        println("Couldn't start listening", error)
    }
    println("Started proxy on", port, "-- listening...")
    for {
        connection, error := listener.Accept()
        if error != nil {
            println("Accept error", error)
            return
        }

        go handle(connection)
    }
    done <- 1
}

func main() {
    httpDone := make(chan int)
    go doProxy(httpDone, 80, handleHTTPConnection)

    httpsDone := make(chan int)
    go doProxy(httpsDone, 443, handleHTTPSConnection)

    <- httpDone
    <- httpsDone
}
<|endoftext|>


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

package martian

import (
	"bufio"
	"crypto/tls"
	"errors"
	"io"
	"net"
	"net/http"
	"net/url"
	"sync"
	"sync/atomic"
	"time"

	"github.com/google/martian/log"
	"github.com/google/martian/mitm"
	"github.com/google/martian/proxyutil"
)

var errClose = errors.New("closing connection")
var noop = Noop("martian")

func isCloseable(err error) bool {
	if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
		return true
	}

	switch err {
	case io.EOF, io.ErrClosedPipe, errClose:
		return true
	}

	return false
}

// Proxy is an HTTP proxy with support for TLS MITM and customizable behavior.
type Proxy struct {
	roundTripper http.RoundTripper
	timeout      time.Duration
	mitm         *mitm.Config
	proxyURL     *url.URL
	conns        *sync.WaitGroup
	mux          *http.ServeMux
	closing      int32 // atomic

	reqmod RequestModifier
	resmod ResponseModifier
}

// NewProxy returns a new HTTP proxy.
func NewProxy() *Proxy {
	return &Proxy{
		roundTripper: http.DefaultTransport,
		timeout:      5 * time.Minute,
		conns:        &sync.WaitGroup{},
		mux:          http.DefaultServeMux,
		reqmod:       noop,
		resmod:       noop,
	}
}

// SetRoundTripper sets the http.RoundTripper of the proxy.
func (p *Proxy) SetRoundTripper(rt http.RoundTripper) {
	p.roundTripper = rt
}

// SetTimeout sets the request timeout of the proxy.
func (p *Proxy) SetTimeout(timeout time.Duration) {
	p.timeout = timeout
}

// SetMITM sets the config to use for MITMing of CONNECT requests.
func (p *Proxy) SetMITM(config *mitm.Config) {
	p.mitm = config
}

// SetDownstreamProxy sets the proxy that receives requests from the upstream
// proxy.
func (p *Proxy) SetDownstreamProxy(proxyURL *url.URL) {
	p.proxyURL = proxyURL
}

// SetMux sets the http.ServeMux to use for internal proxy requests. Defaults
// to http.DefaultServeMux.
func (p *Proxy) SetMux(mux *http.ServeMux) {
	p.mux = mux
}

// Close sets the proxying to the closing state and waits for all connections
// to resolve.
func (p *Proxy) Close() {
	log.Infof("martian: closing down proxy")

	atomic.StoreInt32(&p.closing, 1)
	p.conns.Wait()
}

// Closing returns whether the proxy is in the closing state.
func (p *Proxy) Closing() bool {
	return atomic.LoadInt32(&p.closing) == 1
}

// SetRequestModifier sets the request modifier.
func (p *Proxy) SetRequestModifier(reqmod RequestModifier) {
	if reqmod == nil {
		reqmod = noop
	}

	p.reqmod = reqmod
}

// SetResponseModifier sets the response modifier.
func (p *Proxy) SetResponseModifier(resmod ResponseModifier) {
	if resmod == nil {
		resmod = noop
	}

	p.resmod = resmod
}

// Serve accepts connections from the listener and handles the requests.
func (p *Proxy) Serve(l net.Listener) error {
	defer l.Close()

	// TODO(adamtanner): This forces the http.Transport to not upgrade requests
	// to HTTP/2 in Go 1.6+. Remove this once Martian can support HTTP/2.
	if tr, ok := p.roundTripper.(*http.Transport); ok {
		tr.TLSNextProto = make(map[string]func(string, *tls.Conn) http.RoundTripper)
	}

	var delay time.Duration
	for {
		if p.Closing() {
			return nil
		}

		conn, err := l.Accept()
		if err != nil {
			if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
				if delay == 0 {
					delay = 5 * time.Millisecond
				} else {
					delay *= 2
				}
				if max := time.Second; delay > max {
					delay = max
				}

				log.Debugf("martian: temporary error on accept: %v", err)
				time.Sleep(delay)
				continue
			}

			log.Errorf("martian: failed to accept: %v", err)
			return err
		}
		delay = 0
		log.Debugf("martian: accepted connection from %s", conn.RemoteAddr())

		if tconn, ok := conn.(*net.TCPConn); ok {
			tconn.SetKeepAlive(true)
			tconn.SetKeepAlivePeriod(3 * time.Minute)
		}

		go p.handleLoop(conn)
	}
}

func (p *Proxy) handleLoop(conn net.Conn) {
	p.conns.Add(1)
	defer p.conns.Done()
	defer conn.Close()

	s, err := newSession()
	if err != nil {
		log.Errorf("martian: failed to create session: %v", err)
		return
	}

	ctx, err := withSession(s)
	if err != nil {
		log.Errorf("martian: failed to create context: %v", err)
		return
	}

	brw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))

	for {
		deadline := time.Now().Add(p.timeout)
		conn.SetDeadline(deadline)

		if err := p.handle(ctx, conn, brw); isCloseable(err) {
			log.Debugf("martian: closing connection: %v", conn.RemoteAddr())
			return
		}
	}
}

func (p *Proxy) handle(ctx *Context, conn net.Conn, brw *bufio.ReadWriter) error {
	log.Debugf("martian: waiting for request: %v", conn.RemoteAddr())

	req, err := http.ReadRequest(brw.Reader)
	if err != nil {
		if isCloseable(err) {
			log.Debugf("martian: connection closed prematurely: %v", err)
		} else {
			log.Errorf("martian: failed to read request: %v", err)
		}

		// TODO: TCPConn.WriteClose() to avoid sending an RST to the client.

		return errClose
	}
	defer req.Body.Close()

	if h, pattern := p.mux.Handler(req); pattern != "" {
		defer brw.Flush()

		closing := req.Close || p.Closing()

		log.Infof("martian: intercepted configuration request: %s", req.URL)
		rw := newResponseWriter(brw, closing)
		defer rw.Close()

		h.ServeHTTP(rw, req)

		// Call WriteHeader to ensure a response is sent, since the handler isn't
		// required to call WriteHeader/Write.
		rw.WriteHeader(200)

		if closing {
			return errClose
		}

		return nil
	}

	ctx, err = withSession(ctx.Session())
	if err != nil {
		log.Errorf("martian: failed to build new context: %v", err)
		return err
	}

	link(req, ctx)
	defer unlink(req)

	if tconn, ok := conn.(*tls.Conn); ok {
		ctx.Session().MarkSecure()

		cs := tconn.ConnectionState()
		req.TLS = &cs
	}

	req.URL.Scheme = "http"
	if ctx.Session().IsSecure() {
		log.Debugf("martian: forcing HTTPS inside secure session")
		req.URL.Scheme = "https"
	}

	req.RemoteAddr = conn.RemoteAddr().String()
	if req.URL.Host == "" {
		req.URL.Host = req.Host
	}

	log.Infof("martian: received request: %s", req.URL)

	if req.Method == "CONNECT" {
		if err := p.reqmod.ModifyRequest(req); err != nil {
			log.Errorf("martian: error modifying CONNECT request: %v", err)
			proxyutil.Warning(req.Header, err)
		}

		if p.mitm != nil {
			log.Debugf("martian: attempting MITM for connection: %s", req.Host)
			res := proxyutil.NewResponse(200, nil, req)

			if err := p.resmod.ModifyResponse(res); err != nil {
				log.Errorf("martian: error modifying CONNECT response: %v", err)
				proxyutil.Warning(res.Header, err)
			}

			res.Write(brw)
			brw.Flush()

			log.Debugf("martian: completed MITM for connection: %s", req.Host)

			tlsconn := tls.Server(conn, p.mitm.TLSForHost(req.Host))
			brw.Writer.Reset(tlsconn)
			brw.Reader.Reset(tlsconn)

			return p.handle(ctx, tlsconn, brw)
		}

		log.Debugf("martian: attempting to establish CONNECT tunnel: %s", req.URL.Host)
		res, cconn, cerr := p.connect(req)
		if cerr != nil {
			log.Errorf("martian: failed to CONNECT: %v", err)
			res = proxyutil.NewResponse(502, nil, req)
			proxyutil.Warning(res.Header, cerr)

			if err := p.resmod.ModifyResponse(res); err != nil {
				log.Errorf("martian: error modifying CONNECT response: %v", err)
				proxyutil.Warning(res.Header, err)
			}

			res.Write(brw)
			return brw.Flush()
		}
		defer res.Body.Close()
		defer cconn.Close()

		if err := p.resmod.ModifyResponse(res); err != nil {
			log.Errorf("martian: error modifying CONNECT response: %v", err)
			proxyutil.Warning(res.Header, err)
		}

		res.Write(brw)
		brw.Flush()

		cbw := bufio.NewWriter(cconn)
		cbr := bufio.NewReader(cconn)
		defer cbw.Flush()

		copySync := func(w io.Writer, r io.Reader, donec chan<- bool) {
			if _, err := io.Copy(w, r); err != nil && err != io.EOF {
				log.Errorf("martian: failed to copy CONNECT tunnel: %v", err)
			}

			log.Debugf("martian: CONNECT tunnel finished copying")
			donec <- true
		}

		donec := make(chan bool, 2)
		go copySync(cbw, brw, donec)
		go copySync(brw, cbr, donec)

		log.Debugf("martian: established CONNECT tunnel, proxying traffic")
		<-donec
		<-donec
		log.Debugf("martian: closed CONNECT tunnel")

		return errClose
	}

	if err := p.reqmod.ModifyRequest(req); err != nil {
		log.Errorf("martian: error modifying request: %v", err)
		proxyutil.Warning(req.Header, err)
	}

	res, err := p.roundTrip(ctx, req)
	if err != nil {
		log.Errorf("martian: failed to round trip: %v", err)
		res = proxyutil.NewResponse(502, nil, req)
		proxyutil.Warning(res.Header, err)
	}
	defer res.Body.Close()

	if err := p.resmod.ModifyResponse(res); err != nil {
		log.Errorf("martian: error modifying response: %v", err)
		proxyutil.Warning(res.Header, err)
	}

	var closing error
	if req.Close || p.Closing() {
		log.Debugf("martian: received close request: %v", req.RemoteAddr)
		res.Header.Add("Connection", "close")
		closing = errClose
	}

	log.Debugf("martian: sent response: %v", req.URL)
	res.Write(brw)
	brw.Flush()

	return closing
}

func (p *Proxy) roundTrip(ctx *Context, req *http.Request) (*http.Response, error) {
	if ctx.SkippingRoundTrip() {
		log.Debugf("martian: skipping round trip")
		return proxyutil.NewResponse(200, nil, req), nil
	}

	if tr, ok := p.roundTripper.(*http.Transport); ok {
		tr.Proxy = http.ProxyURL(p.proxyURL)
	}

	return p.roundTripper.RoundTrip(req)
}

func (p *Proxy) connect(req *http.Request) (*http.Response, net.Conn, error) {
	if p.proxyURL != nil {
		log.Debugf("martian: CONNECT with downstream proxy: %s", p.proxyURL.Host)

		conn, err := net.Dial("tcp", p.proxyURL.Host)
		if err != nil {
			return nil, nil, err
		}
		pbw := bufio.NewWriter(conn)
		pbr := bufio.NewReader(conn)

		req.Write(pbw)
		pbw.Flush()

		res, err := http.ReadResponse(pbr, req)
		if err != nil {
			return nil, nil, err
		}

		return res, conn, nil
	}

	log.Debugf("martian: CONNECT to host directly: %s", req.URL.Host)

	conn, err := net.Dial("tcp", req.URL.Host)
	if err != nil {
		return nil, nil, err
	}

	return proxyutil.NewResponse(200, nil, req), conn, nil
}
<commit_msg>proxy: fix race conditions; use custom http.Transport over http.DefaultTransport.<commit_after>// Copyright 2015 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package martian

import (
	"bufio"
	"crypto/tls"
	"errors"
	"io"
	"net"
	"net/http"
	"net/url"
	"sync"
	"sync/atomic"
	"time"

	"github.com/google/martian/log"
	"github.com/google/martian/mitm"
	"github.com/google/martian/proxyutil"
)

var errClose = errors.New("closing connection")
var noop = Noop("martian")

func isCloseable(err error) bool {
	if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
		return true
	}

	switch err {
	case io.EOF, io.ErrClosedPipe, errClose:
		return true
	}

	return false
}

// Proxy is an HTTP proxy with support for TLS MITM and customizable behavior.
type Proxy struct {
	roundTripper http.RoundTripper
	timeout      time.Duration
	mitm         *mitm.Config
	proxyURL     *url.URL
	conns        *sync.WaitGroup
	mux          *http.ServeMux
	closing      int32 // atomic

	reqmod RequestModifier
	resmod ResponseModifier
}

// NewProxy returns a new HTTP proxy.
func NewProxy() *Proxy {
	return &Proxy{
		roundTripper: &http.Transport{
			// TODO(adamtanner): This forces the http.Transport to not upgrade requests
			// to HTTP/2 in Go 1.6+. Remove this once Martian can support HTTP/2.
			TLSNextProto: make(map[string]func(string, *tls.Conn) http.RoundTripper),
			Proxy:        http.ProxyFromEnvironment,
			Dial: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 30 * time.Second,
			}).Dial,
			TLSHandshakeTimeout:   10 * time.Second,
			ExpectContinueTimeout: time.Second,
		},
		timeout: 5 * time.Minute,
		conns:   &sync.WaitGroup{},
		mux:     http.DefaultServeMux,
		reqmod:  noop,
		resmod:  noop,
	}
}

// SetRoundTripper sets the http.RoundTripper of the proxy.
func (p *Proxy) SetRoundTripper(rt http.RoundTripper) {
	p.roundTripper = rt

	if tr, ok := p.roundTripper.(*http.Transport); ok {
		tr.TLSNextProto = make(map[string]func(string, *tls.Conn) http.RoundTripper)
		tr.Proxy = http.ProxyURL(p.proxyURL)
	}
}

// SetDownstreamProxy sets the proxy that receives requests from the upstream
// proxy.
func (p *Proxy) SetDownstreamProxy(proxyURL *url.URL) {
	p.proxyURL = proxyURL

	if tr, ok := p.roundTripper.(*http.Transport); ok {
		tr.Proxy = http.ProxyURL(p.proxyURL)
	}
}

// SetTimeout sets the request timeout of the proxy.
func (p *Proxy) SetTimeout(timeout time.Duration) {
	p.timeout = timeout
}

// SetMITM sets the config to use for MITMing of CONNECT requests.
func (p *Proxy) SetMITM(config *mitm.Config) {
	p.mitm = config
}

// SetMux sets the http.ServeMux to use for internal proxy requests. Defaults
// to http.DefaultServeMux.
func (p *Proxy) SetMux(mux *http.ServeMux) {
	p.mux = mux
}

// Close sets the proxying to the closing state and waits for all connections
// to resolve.
func (p *Proxy) Close() {
	log.Infof("martian: closing down proxy")

	atomic.StoreInt32(&p.closing, 1)
	p.conns.Wait()
}

// Closing returns whether the proxy is in the closing state.
func (p *Proxy) Closing() bool {
	return atomic.LoadInt32(&p.closing) == 1
}

// SetRequestModifier sets the request modifier.
func (p *Proxy) SetRequestModifier(reqmod RequestModifier) {
	if reqmod == nil {
		reqmod = noop
	}

	p.reqmod = reqmod
}

// SetResponseModifier sets the response modifier.
func (p *Proxy) SetResponseModifier(resmod ResponseModifier) {
	if resmod == nil {
		resmod = noop
	}

	p.resmod = resmod
}

// Serve accepts connections from the listener and handles the requests.
func (p *Proxy) Serve(l net.Listener) error {
	defer l.Close()

	var delay time.Duration
	for {
		if p.Closing() {
			return nil
		}

		conn, err := l.Accept()
		if err != nil {
			if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
				if delay == 0 {
					delay = 5 * time.Millisecond
				} else {
					delay *= 2
				}
				if max := time.Second; delay > max {
					delay = max
				}

				log.Debugf("martian: temporary error on accept: %v", err)
				time.Sleep(delay)
				continue
			}

			log.Errorf("martian: failed to accept: %v", err)
			return err
		}
		delay = 0
		log.Debugf("martian: accepted connection from %s", conn.RemoteAddr())

		if tconn, ok := conn.(*net.TCPConn); ok {
			tconn.SetKeepAlive(true)
			tconn.SetKeepAlivePeriod(3 * time.Minute)
		}

		go p.handleLoop(conn)
	}
}

func (p *Proxy) handleLoop(conn net.Conn) {
	p.conns.Add(1)
	defer p.conns.Done()
	defer conn.Close()

	s, err := newSession()
	if err != nil {
		log.Errorf("martian: failed to create session: %v", err)
		return
	}

	ctx, err := withSession(s)
	if err != nil {
		log.Errorf("martian: failed to create context: %v", err)
		return
	}

	brw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))

	for {
		deadline := time.Now().Add(p.timeout)
		conn.SetDeadline(deadline)

		if err := p.handle(ctx, conn, brw); isCloseable(err) {
			log.Debugf("martian: closing connection: %v", conn.RemoteAddr())
			return
		}
	}
}

func (p *Proxy) handle(ctx *Context, conn net.Conn, brw *bufio.ReadWriter) error {
	log.Debugf("martian: waiting for request: %v", conn.RemoteAddr())

	req, err := http.ReadRequest(brw.Reader)
	if err != nil {
		if isCloseable(err) {
			log.Debugf("martian: connection closed prematurely: %v", err)
		} else {
			log.Errorf("martian: failed to read request: %v", err)
		}

		// TODO: TCPConn.WriteClose() to avoid sending an RST to the client.

		return errClose
	}
	defer req.Body.Close()

	if h, pattern := p.mux.Handler(req); pattern != "" {
		defer brw.Flush()

		closing := req.Close || p.Closing()

		log.Infof("martian: intercepted configuration request: %s", req.URL)
		rw := newResponseWriter(brw, closing)
		defer rw.Close()

		h.ServeHTTP(rw, req)

		// Call WriteHeader to ensure a response is sent, since the handler isn't
		// required to call WriteHeader/Write.
		rw.WriteHeader(200)

		if closing {
			return errClose
		}

		return nil
	}

	ctx, err = withSession(ctx.Session())
	if err != nil {
		log.Errorf("martian: failed to build new context: %v", err)
		return err
	}

	link(req, ctx)
	defer unlink(req)

	if tconn, ok := conn.(*tls.Conn); ok {
		ctx.Session().MarkSecure()

		cs := tconn.ConnectionState()
		req.TLS = &cs
	}

	req.URL.Scheme = "http"
	if ctx.Session().IsSecure() {
		log.Debugf("martian: forcing HTTPS inside secure session")
		req.URL.Scheme = "https"
	}

	req.RemoteAddr = conn.RemoteAddr().String()
	if req.URL.Host == "" {
		req.URL.Host = req.Host
	}

	log.Infof("martian: received request: %s", req.URL)

	if req.Method == "CONNECT" {
		if err := p.reqmod.ModifyRequest(req); err != nil {
			log.Errorf("martian: error modifying CONNECT request: %v", err)
			proxyutil.Warning(req.Header, err)
		}

		if p.mitm != nil {
			log.Debugf("martian: attempting MITM for connection: %s", req.Host)
			res := proxyutil.NewResponse(200, nil, req)

			if err := p.resmod.ModifyResponse(res); err != nil {
				log.Errorf("martian: error modifying CONNECT response: %v", err)
				proxyutil.Warning(res.Header, err)
			}

			res.Write(brw)
			brw.Flush()

			log.Debugf("martian: completed MITM for connection: %s", req.Host)

			tlsconn := tls.Server(conn, p.mitm.TLSForHost(req.Host))
			brw.Writer.Reset(tlsconn)
			brw.Reader.Reset(tlsconn)

			return p.handle(ctx, tlsconn, brw)
		}

		log.Debugf("martian: attempting to establish CONNECT tunnel: %s", req.URL.Host)
		res, cconn, cerr := p.connect(req)
		if cerr != nil {
			log.Errorf("martian: failed to CONNECT: %v", err)
			res = proxyutil.NewResponse(502, nil, req)
			proxyutil.Warning(res.Header, cerr)

			if err := p.resmod.ModifyResponse(res); err != nil {
				log.Errorf("martian: error modifying CONNECT response: %v", err)
				proxyutil.Warning(res.Header, err)
			}

			res.Write(brw)
			return brw.Flush()
		}
		defer res.Body.Close()
		defer cconn.Close()

		if err := p.resmod.ModifyResponse(res); err != nil {
			log.Errorf("martian: error modifying CONNECT response: %v", err)
			proxyutil.Warning(res.Header, err)
		}

		res.Write(brw)
		brw.Flush()

		cbw := bufio.NewWriter(cconn)
		cbr := bufio.NewReader(cconn)
		defer cbw.Flush()

		copySync := func(w io.Writer, r io.Reader, donec chan<- bool) {
			if _, err := io.Copy(w, r); err != nil && err != io.EOF {
				log.Errorf("martian: failed to copy CONNECT tunnel: %v", err)
			}

			log.Debugf("martian: CONNECT tunnel finished copying")
			donec <- true
		}

		donec := make(chan bool, 2)
		go copySync(cbw, brw, donec)
		go copySync(brw, cbr, donec)

		log.Debugf("martian: established CONNECT tunnel, proxying traffic")
		<-donec
		<-donec
		log.Debugf("martian: closed CONNECT tunnel")

		return errClose
	}

	if err := p.reqmod.ModifyRequest(req); err != nil {
		log.Errorf("martian: error modifying request: %v", err)
		proxyutil.Warning(req.Header, err)
	}

	res, err := p.roundTrip(ctx, req)
	if err != nil {
		log.Errorf("martian: failed to round trip: %v", err)
		res = proxyutil.NewResponse(502, nil, req)
		proxyutil.Warning(res.Header, err)
	}
	defer res.Body.Close()

	if err := p.resmod.ModifyResponse(res); err != nil {
		log.Errorf("martian: error modifying response: %v", err)
		proxyutil.Warning(res.Header, err)
	}

	var closing error
	if req.Close || p.Closing() {
		log.Debugf("martian: received close request: %v", req.RemoteAddr)
		res.Header.Add("Connection", "close")
		closing = errClose
	}

	log.Debugf("martian: sent response: %v", req.URL)
	res.Write(brw)
	brw.Flush()

	return closing
}

func (p *Proxy) roundTrip(ctx *Context, req *http.Request) (*http.Response, error) {
	if ctx.SkippingRoundTrip() {
		log.Debugf("martian: skipping round trip")
		return proxyutil.NewResponse(200, nil, req), nil
	}

	return p.roundTripper.RoundTrip(req)
}

func (p *Proxy) connect(req *http.Request) (*http.Response, net.Conn, error) {
	if p.proxyURL != nil {
		log.Debugf("martian: CONNECT with downstream proxy: %s", p.proxyURL.Host)

		conn, err := net.Dial("tcp", p.proxyURL.Host)
		if err != nil {
			return nil, nil, err
		}
		pbw := bufio.NewWriter(conn)
		pbr := bufio.NewReader(conn)

		req.Write(pbw)
		pbw.Flush()

		res, err := http.ReadResponse(pbr, req)
		if err != nil {
			return nil, nil, err
		}

		return res, conn, nil
	}

	log.Debugf("martian: CONNECT to host directly: %s", req.URL.Host)

	conn, err := net.Dial("tcp", req.URL.Host)
	if err != nil {
		return nil, nil, err
	}

	return proxyutil.NewResponse(200, nil, req), conn, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"strings"
	"sync"
	"time"

	"context"

	"github.com/abbot/go-http-auth"
)

const (
	browserStarted int = iota
	browserFailed
	seleniumError
)

const (
	pingPath  string = "/ping"
	errPath   string = "/err"
	routePath string = "/wd/hub/session"
	proxyPath string = routePath + "/"
	head      int    = len(proxyPath)
	tail      int    = head + 32
	sessPart  int    = 4 // /wd/hub/session/{various length session}
)

var (
	quota           = make(map[string]Browsers)
	routes   Routes = make(Routes)
	num      uint64
	numLock  sync.Mutex
	confLock sync.RWMutex
)

// Routes - an MD5 to host map
type Routes map[string]*Host

type caps map[string]interface{}

func (c *caps) capability(k string) string {
	dc := (*c)["desiredCapabilities"]
	switch dc.(type) {
	case map[string]interface{}:
		v := dc.(map[string]interface{})
		switch v[k].(type) {
		case string:
			return v[k].(string)
		}
	}
	return ""
}

func (c *caps) setCapability(k string, v string) {
	(*c)["desiredCapabilities"].(map[string]interface{})[k] = v
}

func (c *caps) browser() string {
	return c.capability("browserName")
}

func (c *caps) version() string {
	return c.capability("version")
}

func (c *caps) setVersion(version string) {
	c.setCapability("version", version)
}

func (h *Host) session(ctx context.Context, header http.Header, c caps) (map[string]interface{}, int) {
	b, _ := json.Marshal(c)
	req, err := http.NewRequest(http.MethodPost, h.sessionURL(), bytes.NewReader(b))
	if err != nil {
		return nil, seleniumError
	}
	for key, values := range header {
		for _, value := range values {
			req.Header.Add(key, value)
		}
	}
	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()
	req = req.WithContext(ctx)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}
	if err != nil {
		return nil, seleniumError
	}
	var reply map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&reply)
	if err != nil {
		return nil, seleniumError
	}
	if resp.StatusCode != http.StatusOK {
		return reply, browserFailed
	}
	return reply, browserStarted
}

func reply(w http.ResponseWriter, msg map[string]interface{}) {
	reply, _ := json.Marshal(msg)
	w.Header().Set("Content-Type", "application/json")
	w.Write(reply)
}

func serial() uint64 {
	numLock.Lock()
	defer numLock.Unlock()
	id := num
	num++
	return id
}

func info(r *http.Request) (user, remote string) {
	user = "unknown"
	if u, _, ok := r.BasicAuth(); ok {
		user = u
	}
	remote = r.Header.Get("X-Forwarded-For")
	if remote != "" {
		return
	}
	remote, _, _ = net.SplitHostPort(r.RemoteAddr)
	return
}

func fmtBrowser(browser, version string) string {
	if version != "" {
		return fmt.Sprintf("%s-%s", browser, version)
	}
	return browser
}

func browserErrMsg(js map[string]interface{}) string {
	if js == nil {
		return ""
	}
	val, ok := js["value"].(map[string]interface{})
	if !ok {
		return ""
	}
	msg, ok := val["message"].(string)
	if !ok {
		return ""
	}
	return msg
}

func jsonErrMsg(msg string) string {
	message := make(map[string]string)
	message["message"] = msg
	value := make(map[string]interface{})
	value["value"] = message
	value["status"] = 13
	result, _ := json.Marshal(value)
	return string(result)
}

func route(w http.ResponseWriter, r *http.Request) {
	start := time.Now()
	id := serial()
	user, remote := info(r)
	var c caps
	err := json.NewDecoder(r.Body).Decode(&c)
	if err != nil {
		http.Error(w, fmt.Sprintf("bad json format: %s", err.Error()), http.StatusBadRequest)
		log.Printf("[%d] [BAD_JSON] [%s] [%s] [%v]\n", id, user, remote, err)
		return
	}
	browser, version := c.browser(), c.version()
	if browser == "" {
		http.Error(w, "browser not set", http.StatusBadRequest)
		log.Printf("[%d] [BROWSER_NOT_SET] [%s] [%s]\n", id, user, remote)
		return
	}
	count := 0
	confLock.RLock()
	browsers := quota[user]
	excludedHosts := newSet()
	hosts, version, excludedRegions := browsers.find(browser, version, excludedHosts, newSet())
	confLock.RUnlock()
	if len(hosts) == 0 {
		http.Error(w, fmt.Sprintf("unsupported browser: %s", fmtBrowser(browser, version)), http.StatusNotFound)
		log.Printf("[%d] [UNSUPPORTED_BROWSER] [%s] [%s] [%s]\n", id, user, remote, fmtBrowser(browser, version))
		return
	}
loop:
	for h, i := hosts.choose(); ; h, i = hosts.choose() {
		count++
		if h == nil {
			break loop
		}
		log.Printf("[%d] [SESSION_ATTEMPTED] [%s] [%s] [%s] [%s] [%d]\n", id, user, remote, fmtBrowser(browser, version), h.net(), count)
		c.setVersion(version)
		resp, status := h.session(r.Context(), r.Header, c)
		select {
		case <-r.Context().Done():
			log.Printf("[%d] [%.2fs] [CLIENT_DISCONNECTED] [%s] [%s] [%s] [%s] [%d]\n", id, float64(time.Now().Sub(start).Seconds()), user, remote, fmtBrowser(browser, version), h.net(), count)
			return
		default:
		}
		switch status {
		case browserStarted:
			sess := resp["sessionId"].(string)
			resp["sessionId"] = h.sum() + sess
			reply(w, resp)
			log.Printf("[%d] [%.2fs] [SESSION_CREATED] [%s] [%s] [%s] [%s] [%s] [%d]\n", id, float64(time.Now().Sub(start).Seconds()), user, remote, fmtBrowser(browser, version), h.net(), sess, count)
			return
		case browserFailed:
			hosts = append(hosts[:i], hosts[i+1:]...)
		case seleniumError:
			excludedHosts.add(h.Name)
			excludedRegions.add(h.region)
			hosts, version, excludedRegions = browsers.find(browser, version, excludedHosts, excludedRegions)
		}
		log.Printf("[%d] [SESSION_FAILED] [%s] [%s] [%s] [%s] %s\n", id, user, remote, fmtBrowser(browser, version), h.net(), browserErrMsg(resp))
		if len(hosts) == 0 {
			break loop
		}
	}
	http.Error(w, jsonErrMsg(fmt.Sprintf("cannot create session %s on any hosts after %d attempt(s)", fmtBrowser(browser, version), count)), http.StatusInternalServerError)
	log.Printf("[%d] [SESSION_NOT_CREATED] [%s] [%s] [%s]\n", id, user, remote, fmtBrowser(browser, version))
}

func proxy(r *http.Request) {
	_, remote := info(r)
	r.URL.Scheme = "http"
	if len(r.URL.Path) > tail {
		sum := r.URL.Path[head:tail]
		proxyPath := r.URL.Path[:head] + r.URL.Path[tail:]
		confLock.RLock()
		h, ok := routes[sum]
		confLock.RUnlock()
		if ok {
			if r.Body != nil {
				if body, err := ioutil.ReadAll(r.Body); err == nil {
					r.Body.Close()
					var msg map[string]interface{}
					if err := json.Unmarshal(body, &msg); err == nil {
						delete(msg, "sessionId")
						body, _ = json.Marshal(msg)
						r.ContentLength = int64(len(body))
					}
					r.Body = ioutil.NopCloser(bytes.NewReader(body))
				}
			}
			r.URL.Host = h.net()
			r.URL.Path = proxyPath
			if r.Method == "DELETE" {
				sess := strings.Split(proxyPath, "/")[sessPart]
				log.Printf("[SESSION_DELETED] [%s] [%s] [%s]\n", remote, h.net(), sess)
			}
			return
		}
		log.Printf("[ROUTE_NOT_FOUND] [%s] [%s]\n", remote, proxyPath)
	} else {
		log.Printf("[INVALID_URL] [%s] [%s]\n", remote, r.URL.Path)
	}
	r.URL.Host = listen
	r.URL.Path = errPath
}

func ping(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Ok\n"))
}

func err(w http.ResponseWriter, r *http.Request) {
	http.Error(w, "route not found", http.StatusNotFound)
}

func postOnly(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
			return
		}
		handler(w, r)
	}
}

func withCloseNotifier(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx, cancel := context.WithCancel(r.Context())
		go func() {
			handler(w, r.WithContext(ctx))
			cancel()
		}()
		select {
		case <-w.(http.CloseNotifier).CloseNotify():
			cancel()
		case <-ctx.Done():
		}
	}
}

func readConfig(fn string, browsers *Browsers) error {
	file, err := ioutil.ReadFile(fn)
	if err != nil {
		return fmt.Errorf("error reading configuration file %s: %v", fn, err)
	}
	if err := xml.Unmarshal(file, browsers); err != nil {
		return fmt.Errorf("error parsing configuration file %s: %v", fn, err)
	}
	return nil
}

func appendRoutes(routes Routes, config *Browsers) Routes {
	for _, b := range config.Browsers {
		for _, v := range b.Versions {
			for _, r := range v.Regions {
				for i, h := range r.Hosts {
					r.Hosts[i].region = r.Name
					routes[h.sum()] = &r.Hosts[i]
				}
			}
		}
	}
	return routes
}

func requireBasicAuth(authenticator *auth.BasicAuth, handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
	return authenticator.Wrap(func(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
		handler(w, &r.Request)
	})
}

func mux() http.Handler {
	mux := http.NewServeMux()
	authenticator := auth.NewBasicAuthenticator(
		"Selenium Grid Router",
		auth.HtpasswdFileProvider(users),
	)
	mux.HandleFunc(pingPath, ping)
	mux.HandleFunc(errPath, err)
	mux.HandleFunc(routePath, requireBasicAuth(authenticator, postOnly(withCloseNotifier(route))))
	mux.Handle(proxyPath, &httputil.ReverseProxy{Director: proxy})
	return mux
}
<commit_msg>Fix #46<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"strings"
	"sync"
	"time"

	"context"

	"github.com/abbot/go-http-auth"
)

const (
	browserStarted int = iota
	browserFailed
	seleniumError
)

const (
	pingPath  string = "/ping"
	errPath   string = "/err"
	routePath string = "/wd/hub/session"
	proxyPath string = routePath + "/"
	head      int    = len(proxyPath)
	tail      int    = head + 32
	sessPart  int    = 4 // /wd/hub/session/{various length session}
)

var (
	quota           = make(map[string]Browsers)
	routes   Routes = make(Routes)
	num      uint64
	numLock  sync.Mutex
	confLock sync.RWMutex
)

// Routes - an MD5 to host map
type Routes map[string]*Host

type caps map[string]interface{}

func (c *caps) capability(k string) string {
	dc := (*c)["desiredCapabilities"]
	switch dc.(type) {
	case map[string]interface{}:
		v := dc.(map[string]interface{})
		switch v[k].(type) {
		case string:
			return v[k].(string)
		}
	}
	return ""
}

func (c *caps) setCapability(k string, v string) {
	(*c)["desiredCapabilities"].(map[string]interface{})[k] = v
}

func (c *caps) browser() string {
	return c.capability("browserName")
}

func (c *caps) version() string {
	return c.capability("version")
}

func (c *caps) setVersion(version string) {
	c.setCapability("version", version)
}

func (h *Host) session(ctx context.Context, header http.Header, c caps) (map[string]interface{}, int) {
	b, _ := json.Marshal(c)
	req, err := http.NewRequest(http.MethodPost, h.sessionURL(), bytes.NewReader(b))
	if err != nil {
		return nil, seleniumError
	}
	for key, values := range header {
		for _, value := range values {
			req.Header.Add(key, value)
		}
	}
	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()
	req = req.WithContext(ctx)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}
	if err != nil {
		return nil, seleniumError
	}
	var reply map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&reply)
	if err != nil {
		return nil, seleniumError
	}
	if resp.StatusCode != http.StatusOK {
		return reply, browserFailed
	}
	return reply, browserStarted
}

func reply(w http.ResponseWriter, msg map[string]interface{}) {
	reply, _ := json.Marshal(msg)
	w.Header().Set("Content-Type", "application/json")
	w.Write(reply)
}

func serial() uint64 {
	numLock.Lock()
	defer numLock.Unlock()
	id := num
	num++
	return id
}

func info(r *http.Request) (user, remote string) {
	user = "unknown"
	if u, _, ok := r.BasicAuth(); ok {
		user = u
	}
	remote = r.Header.Get("X-Forwarded-For")
	if remote != "" {
		return
	}
	remote, _, _ = net.SplitHostPort(r.RemoteAddr)
	return
}

func fmtBrowser(browser, version string) string {
	if version != "" {
		return fmt.Sprintf("%s-%s", browser, version)
	}
	return browser
}

func browserErrMsg(js map[string]interface{}) string {
	if js == nil {
		return ""
	}
	val, ok := js["value"].(map[string]interface{})
	if !ok {
		return ""
	}
	msg, ok := val["message"].(string)
	if !ok {
		return ""
	}
	return msg
}

func jsonErrMsg(msg string) string {
	message := make(map[string]string)
	message["message"] = msg
	value := make(map[string]interface{})
	value["value"] = message
	value["status"] = 13
	result, _ := json.Marshal(value)
	return string(result)
}

func route(w http.ResponseWriter, r *http.Request) {
	start := time.Now()
	id := serial()
	user, remote := info(r)
	var c caps
	err := json.NewDecoder(r.Body).Decode(&c)
	if err != nil {
		http.Error(w, fmt.Sprintf("bad json format: %s", err.Error()), http.StatusBadRequest)
		log.Printf("[%d] [BAD_JSON] [%s] [%s] [%v]\n", id, user, remote, err)
		return
	}
	browser, version := c.browser(), c.version()
	if browser == "" {
		http.Error(w, "browser not set", http.StatusBadRequest)
		log.Printf("[%d] [BROWSER_NOT_SET] [%s] [%s]\n", id, user, remote)
		return
	}
	count := 0
	confLock.RLock()
	browsers := quota[user]
	excludedHosts := newSet()
	hosts, version, excludedRegions := browsers.find(browser, version, excludedHosts, newSet())
	confLock.RUnlock()
	if len(hosts) == 0 {
		http.Error(w, fmt.Sprintf("unsupported browser: %s", fmtBrowser(browser, version)), http.StatusNotFound)
		log.Printf("[%d] [UNSUPPORTED_BROWSER] [%s] [%s] [%s]\n", id, user, remote, fmtBrowser(browser, version))
		return
	}
loop:
	for h, i := hosts.choose(); ; h, i = hosts.choose() {
		count++
		if h == nil {
			break loop
		}
		log.Printf("[%d] [SESSION_ATTEMPTED] [%s] [%s] [%s] [%s] [%d]\n", id, user, remote, fmtBrowser(browser, version), h.net(), count)
		c.setVersion(version)
		resp, status := h.session(r.Context(), r.Header, c)
		select {
		case <-r.Context().Done():
			log.Printf("[%d] [%.2fs] [CLIENT_DISCONNECTED] [%s] [%s] [%s] [%s] [%d]\n", id, float64(time.Now().Sub(start).Seconds()), user, remote, fmtBrowser(browser, version), h.net(), count)
			return
		default:
		}
		switch status {
		case browserStarted:
			sess := resp["sessionId"].(string)
			resp["sessionId"] = h.sum() + sess
			reply(w, resp)
			log.Printf("[%d] [%.2fs] [SESSION_CREATED] [%s] [%s] [%s] [%s] [%s] [%d]\n", id, float64(time.Now().Sub(start).Seconds()), user, remote, fmtBrowser(browser, version), h.net(), sess, count)
			return
		case browserFailed:
			hosts = append(hosts[:i], hosts[i+1:]...)
		case seleniumError:
			excludedHosts.add(h.Name)
			excludedRegions.add(h.region)
			hosts, version, excludedRegions = browsers.find(browser, version, excludedHosts, excludedRegions)
		}
		log.Printf("[%d] [SESSION_FAILED] [%s] [%s] [%s] [%s] %s\n", id, user, remote, fmtBrowser(browser, version), h.net(), browserErrMsg(resp))
		if len(hosts) == 0 {
			break loop
		}
	}
	http.Error(w, jsonErrMsg(fmt.Sprintf("cannot create session %s on any hosts after %d attempt(s)", fmtBrowser(browser, version), count)), http.StatusInternalServerError)
	log.Printf("[%d] [SESSION_NOT_CREATED] [%s] [%s] [%s]\n", id, user, remote, fmtBrowser(browser, version))
}

func proxy(r *http.Request) {
	_, remote := info(r)
	r.URL.Scheme = "http"
	if len(r.URL.Path) > tail {
		sum := r.URL.Path[head:tail]
		proxyPath := r.URL.Path[:head] + r.URL.Path[tail:]
		confLock.RLock()
		h, ok := routes[sum]
		confLock.RUnlock()
		if ok {
			if r.Body != nil {
				if body, err := ioutil.ReadAll(r.Body); err == nil {
					r.Body.Close()
					var msg map[string]interface{}
					if err := json.Unmarshal(body, &msg); err == nil {
						delete(msg, "sessionId")
						body, _ = json.Marshal(msg)
						r.ContentLength = int64(len(body))
					}
					r.Body = ioutil.NopCloser(bytes.NewReader(body))
				}
			}
			r.URL.Host = h.net()
			r.URL.Path = proxyPath
			fragments := strings.Split(proxyPath, "/")
			if r.Method == "DELETE" && len(fragments) == sessPart+1 {
				sess := fragments[sessPart]
				log.Printf("[SESSION_DELETED] [%s] [%s] [%s]\n", remote, h.net(), sess)
			}
			return
		}
		log.Printf("[ROUTE_NOT_FOUND] [%s] [%s]\n", remote, proxyPath)
	} else {
		log.Printf("[INVALID_URL] [%s] [%s]\n", remote, r.URL.Path)
	}
	r.URL.Host = listen
	r.URL.Path = errPath
}

func ping(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Ok\n"))
}

func err(w http.ResponseWriter, r *http.Request) {
	http.Error(w, "route not found", http.StatusNotFound)
}

func postOnly(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
			return
		}
		handler(w, r)
	}
}

func withCloseNotifier(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx, cancel := context.WithCancel(r.Context())
		go func() {
			handler(w, r.WithContext(ctx))
			cancel()
		}()
		select {
		case <-w.(http.CloseNotifier).CloseNotify():
			cancel()
		case <-ctx.Done():
		}
	}
}

func readConfig(fn string, browsers *Browsers) error {
	file, err := ioutil.ReadFile(fn)
	if err != nil {
		return fmt.Errorf("error reading configuration file %s: %v", fn, err)
	}
	if err := xml.Unmarshal(file, browsers); err != nil {
		return fmt.Errorf("error parsing configuration file %s: %v", fn, err)
	}
	return nil
}

func appendRoutes(routes Routes, config *Browsers) Routes {
	for _, b := range config.Browsers {
		for _, v := range b.Versions {
			for _, r := range v.Regions {
				for i, h := range r.Hosts {
					r.Hosts[i].region = r.Name
					routes[h.sum()] = &r.Hosts[i]
				}
			}
		}
	}
	return routes
}

func requireBasicAuth(authenticator *auth.BasicAuth, handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
	return authenticator.Wrap(func(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
		handler(w, &r.Request)
	})
}

func mux() http.Handler {
	mux := http.NewServeMux()
	authenticator := auth.NewBasicAuthenticator(
		"Selenium Grid Router",
		auth.HtpasswdFileProvider(users),
	)
	mux.HandleFunc(pingPath, ping)
	mux.HandleFunc(errPath, err)
	mux.HandleFunc(routePath, requireBasicAuth(authenticator, postOnly(withCloseNotifier(route))))
	mux.Handle(proxyPath, &httputil.ReverseProxy{Director: proxy})
	return mux
}
<|endoftext|>


<commit_before>package purse

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
)

const (
	ext string = ".sql"
)

// Purse is a key/value collection of loaded SQL files by name : content.
// It is safe for concurrent use by multiple goroutines.
type Purse struct {
	mux   sync.RWMutex
	files map[string]string
}

// New loads all SQL files in the specified directory dir.
//
// A loaded file's contents can be accessed via Get().
//
// Returns an error if the directory does not exist or is not a directory.
func New(dir string) (*Purse, error) {
	f, err := os.Open(dir)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	fis, err := f.Readdir(-1)
	if err != nil {
		return nil, err
	}

	s := &Purse{files: make(map[string]string, 0)}

	for _, fi := range fis {
		if !fi.IsDir() && filepath.Ext(fi.Name()) == ext {
			f, err := os.Open(filepath.Join(dir, fi.Name()))
			if err != nil {
				return nil, err
			}
			defer f.Close()

			fd, err := ioutil.ReadAll(f)
			if err != nil {
				return nil, err
			}
			s.files[fi.Name()] = string(fd)
		}
	}
	return s, nil
}

// Get returns a loaded file's contents and existence of the file by filename.
func (p *Purse) Get(filename string) (v string, ok bool) {
	p.mux.RLock()
	v, ok = p.files[filename]
	p.mux.RUnlock()
	return
}
<commit_msg>changed confusing variable<commit_after>package purse

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
)

const (
	ext string = ".sql"
)

// Purse is a key/value collection of loaded SQL files by name : content.
// It is safe for concurrent use by multiple goroutines.
type Purse struct {
	mu    sync.RWMutex
	files map[string]string
}

// New loads all SQL files in the specified directory dir.
//
// A loaded file's contents can be accessed via Get().
//
// Returns an error if the directory does not exist or is not a directory.
func New(dir string) (*Purse, error) {
	f, err := os.Open(dir)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	fis, err := f.Readdir(-1)
	if err != nil {
		return nil, err
	}

	s := &Purse{files: make(map[string]string, 0)}

	for _, fi := range fis {
		if !fi.IsDir() && filepath.Ext(fi.Name()) == ext {
			f, err := os.Open(filepath.Join(dir, fi.Name()))
			if err != nil {
				return nil, err
			}
			defer f.Close()

			fd, err := ioutil.ReadAll(f)
			if err != nil {
				return nil, err
			}
			s.files[fi.Name()] = string(fd)
		}
	}
	return s, nil
}

// Get returns a loaded file's contents and existence of the file by filename.
func (p *Purse) Get(filename string) (v string, ok bool) {
	p.mux.RLock()
	v, ok = p.files[filename]
	p.mux.RUnlock()
	return
}
<|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 "reflect"

func GetLoggingMetricCaiObject(d TerraformResourceData, config *Config) (Asset, error) {
	name, err := assetName(d, config, "//logging.googleapis.com/projects/{{project}}/metrics/{{%name}}")
	if err != nil {
		return Asset{}, err
	}
	if obj, err := GetLoggingMetricApiObject(d, config); err == nil {
		return Asset{
			Name: name,
			Type: "logging.googleapis.com/Metric",
			Resource: &AssetResource{
				Version:              "v2",
				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/logging/v2/rest",
				DiscoveryName:        "Metric",
				Data:                 obj,
			},
		}, nil
	} else {
		return Asset{}, err
	}
}

func GetLoggingMetricApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
	obj := make(map[string]interface{})
	nameProp, err := expandLoggingMetricName(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
	}
	descriptionProp, err := expandLoggingMetricDescription(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
	}
	filterProp, err := expandLoggingMetricFilter(d.Get("filter"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("filter"); !isEmptyValue(reflect.ValueOf(filterProp)) && (ok || !reflect.DeepEqual(v, filterProp)) {
		obj["filter"] = filterProp
	}
	metricDescriptorProp, err := expandLoggingMetricMetricDescriptor(d.Get("metric_descriptor"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("metric_descriptor"); !isEmptyValue(reflect.ValueOf(metricDescriptorProp)) && (ok || !reflect.DeepEqual(v, metricDescriptorProp)) {
		obj["metricDescriptor"] = metricDescriptorProp
	}
	labelExtractorsProp, err := expandLoggingMetricLabelExtractors(d.Get("label_extractors"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("label_extractors"); !isEmptyValue(reflect.ValueOf(labelExtractorsProp)) && (ok || !reflect.DeepEqual(v, labelExtractorsProp)) {
		obj["labelExtractors"] = labelExtractorsProp
	}
	valueExtractorProp, err := expandLoggingMetricValueExtractor(d.Get("value_extractor"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("value_extractor"); !isEmptyValue(reflect.ValueOf(valueExtractorProp)) && (ok || !reflect.DeepEqual(v, valueExtractorProp)) {
		obj["valueExtractor"] = valueExtractorProp
	}
	bucketOptionsProp, err := expandLoggingMetricBucketOptions(d.Get("bucket_options"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("bucket_options"); !isEmptyValue(reflect.ValueOf(bucketOptionsProp)) && (ok || !reflect.DeepEqual(v, bucketOptionsProp)) {
		obj["bucketOptions"] = bucketOptionsProp
	}

	return obj, nil
}

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

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

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

func expandLoggingMetricMetricDescriptor(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

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

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

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

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

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

	return transformed, nil
}

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

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

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

func expandLoggingMetricMetricDescriptorLabels(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	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{})

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

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

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

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

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

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

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

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

func expandLoggingMetricLabelExtractors(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) {
	if v == nil {
		return map[string]string{}, nil
	}
	m := make(map[string]string)
	for k, val := range v.(map[string]interface{}) {
		m[k] = val.(string)
	}
	return m, nil
}

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

func expandLoggingMetricBucketOptions(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

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

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

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

	return transformed, nil
}

func expandLoggingMetricBucketOptionsLinearBuckets(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

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

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

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

	return transformed, nil
}

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

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

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

func expandLoggingMetricBucketOptionsExponentialBuckets(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

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

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

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

	return transformed, nil
}

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

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

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

func expandLoggingMetricBucketOptionsExplicitBuckets(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

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

	return transformed, nil
}

func expandLoggingMetricBucketOptionsExplicitBucketsBounds(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}
<commit_msg>Make logging metric descriptor labels a set (#301)<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 (
	"reflect"

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

func GetLoggingMetricCaiObject(d TerraformResourceData, config *Config) (Asset, error) {
	name, err := assetName(d, config, "//logging.googleapis.com/projects/{{project}}/metrics/{{%name}}")
	if err != nil {
		return Asset{}, err
	}
	if obj, err := GetLoggingMetricApiObject(d, config); err == nil {
		return Asset{
			Name: name,
			Type: "logging.googleapis.com/Metric",
			Resource: &AssetResource{
				Version:              "v2",
				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/logging/v2/rest",
				DiscoveryName:        "Metric",
				Data:                 obj,
			},
		}, nil
	} else {
		return Asset{}, err
	}
}

func GetLoggingMetricApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
	obj := make(map[string]interface{})
	nameProp, err := expandLoggingMetricName(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
	}
	descriptionProp, err := expandLoggingMetricDescription(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
	}
	filterProp, err := expandLoggingMetricFilter(d.Get("filter"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("filter"); !isEmptyValue(reflect.ValueOf(filterProp)) && (ok || !reflect.DeepEqual(v, filterProp)) {
		obj["filter"] = filterProp
	}
	metricDescriptorProp, err := expandLoggingMetricMetricDescriptor(d.Get("metric_descriptor"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("metric_descriptor"); !isEmptyValue(reflect.ValueOf(metricDescriptorProp)) && (ok || !reflect.DeepEqual(v, metricDescriptorProp)) {
		obj["metricDescriptor"] = metricDescriptorProp
	}
	labelExtractorsProp, err := expandLoggingMetricLabelExtractors(d.Get("label_extractors"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("label_extractors"); !isEmptyValue(reflect.ValueOf(labelExtractorsProp)) && (ok || !reflect.DeepEqual(v, labelExtractorsProp)) {
		obj["labelExtractors"] = labelExtractorsProp
	}
	valueExtractorProp, err := expandLoggingMetricValueExtractor(d.Get("value_extractor"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("value_extractor"); !isEmptyValue(reflect.ValueOf(valueExtractorProp)) && (ok || !reflect.DeepEqual(v, valueExtractorProp)) {
		obj["valueExtractor"] = valueExtractorProp
	}
	bucketOptionsProp, err := expandLoggingMetricBucketOptions(d.Get("bucket_options"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("bucket_options"); !isEmptyValue(reflect.ValueOf(bucketOptionsProp)) && (ok || !reflect.DeepEqual(v, bucketOptionsProp)) {
		obj["bucketOptions"] = bucketOptionsProp
	}

	return obj, nil
}

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

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

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

func expandLoggingMetricMetricDescriptor(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

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

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

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

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

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

	return transformed, nil
}

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

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

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

func expandLoggingMetricMetricDescriptorLabels(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{})

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

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

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

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

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

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

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

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

func expandLoggingMetricLabelExtractors(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) {
	if v == nil {
		return map[string]string{}, nil
	}
	m := make(map[string]string)
	for k, val := range v.(map[string]interface{}) {
		m[k] = val.(string)
	}
	return m, nil
}

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

func expandLoggingMetricBucketOptions(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

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

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

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

	return transformed, nil
}

func expandLoggingMetricBucketOptionsLinearBuckets(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

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

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

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

	return transformed, nil
}

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

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

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

func expandLoggingMetricBucketOptionsExponentialBuckets(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

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

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

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

	return transformed, nil
}

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

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

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

func expandLoggingMetricBucketOptionsExplicitBuckets(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

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

	return transformed, nil
}

func expandLoggingMetricBucketOptionsExplicitBucketsBounds(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}
<|endoftext|>