<commit_before>package command

import (
	"flag"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/funkygao/columnize"
	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/color"
	"github.com/funkygao/golib/gofmt"
	"github.com/shirou/gopsutil/disk"
)

type Systool struct {
	Ui  cli.Ui
	Cmd string
}

func (this *Systool) Run(args []string) (exitCode int) {
	var (
		diskTool bool
		interval time.Duration
	)
	cmdFlags := flag.NewFlagSet("systool", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.BoolVar(&diskTool, "d", false, "")
	cmdFlags.DurationVar(&interval, "i", time.Second*3, "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	if diskTool {
		this.runDiskTool(interval)
		return
	}

	return
}

func (*Systool) runDiskTool(interval time.Duration) {
	var lastStats = make(map[string]disk.IOCountersStat)
	for {
		stats, err := disk.IOCounters()
		swallow(err)

		sortedDisks := make([]string, 0, len(stats))
		for d := range stats {
			sortedDisks = append(sortedDisks, d)
		}
		sort.Strings(sortedDisks)

		lines := []string{"Disk|io|iops|read bytes|write bytes|read#|write#|mergedR#|mergedW#|readT|writeT|ioT|wio"}
		for _, d := range sortedDisks {
			diskSuffix := d[len(d)-1]
			if diskSuffix > '9' || diskSuffix < '0' {
				// not partitioned
				continue
			}

			stat := stats[d]
			if last, present := lastStats[d]; present {
				stat.ReadBytes -= last.ReadBytes
				stat.WriteBytes -= last.WriteBytes
				stat.ReadCount -= last.ReadCount
				stat.WriteCount -= last.WriteCount
				stat.MergedReadCount -= last.MergedReadCount
				stat.MergedWriteCount -= last.MergedWriteCount
				stat.WriteTime -= last.WriteTime
				stat.ReadTime -= last.ReadTime
				stat.IoTime -= last.IoTime
				stat.WeightedIO -= last.WeightedIO
			}

			var (
				rbytes = gofmt.ByteSize(stat.ReadBytes).String()
				wbytes = gofmt.ByteSize(stat.WriteBytes).String()
			)
			if strings.HasSuffix(rbytes, "MB") || strings.HasSuffix(rbytes, "GB") || strings.HasSuffix(rbytes, "TB") {
				rbytes = color.Red("%-10s", rbytes)
			}
			if strings.HasSuffix(wbytes, "MB") || strings.HasSuffix(wbytes, "GB") || strings.HasSuffix(wbytes, "TB") {
				wbytes = color.Red("%-11s", wbytes)
			}

			lines = append(lines, fmt.Sprintf("%s|%d|%d|%s|%s|%d|%d|%d|%d|%d|%d|%d|%d",
				stat.Name,
				stat.IopsInProgress,
				int(stat.ReadCount+stat.WriteCount)/int(interval.Seconds()),
				rbytes, wbytes,
				stat.ReadCount, stat.WriteCount,
				stat.MergedReadCount, stat.MergedWriteCount,
				stat.ReadTime, stat.WriteTime,
				stat.IoTime,
				stat.WeightedIO))
		}

		refreshScreen()
		fmt.Println(columnize.SimpleFormat(lines))

		if len(lastStats) == 0 {
			time.Sleep(interval + time.Second*3)
		} else {
			time.Sleep(interval)
		}

		lastStats = stats
	}

}

func (*Systool) Synopsis() string {
	return "OS diagnostics tool"
}

func (this *Systool) Help() string {
	help := fmt.Sprintf(`
Usage: %s systool [options]

    %s

Options:

    -d
      Disk diagnostics

    -i interval
      e,g. -i 3s

`, this.Cmd, this.Synopsis())
	return strings.TrimSpace(help)
}
<commit_msg>systool: net tool added<commit_after>package command

import (
	"flag"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/funkygao/columnize"
	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/color"
	"github.com/funkygao/golib/gofmt"
	"github.com/shirou/gopsutil/disk"
	"github.com/shirou/gopsutil/net"
)

type Systool struct {
	Ui  cli.Ui
	Cmd string
}

func (this *Systool) Run(args []string) (exitCode int) {
	var (
		diskTool bool
		netTool  bool
		interval time.Duration
	)
	cmdFlags := flag.NewFlagSet("systool", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.BoolVar(&diskTool, "d", false, "")
	cmdFlags.BoolVar(&netTool, "n", false, "")
	cmdFlags.DurationVar(&interval, "i", time.Second*3, "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	if diskTool {
		this.runDiskTool(interval)
		return
	}

	if netTool {
		this.runNetTool(interval)
		return
	}

	return
}

func (*Systool) runNetTool(interval time.Duration) {
	for {
		refreshScreen()

		stats, err := net.ProtoCounters([]string{"tcp"})
		swallow(err)

		stat := stats[0].Stats
		sortedName := make([]string, 0, len(stat))
		for n := range stat {
			sortedName = append(sortedName, n)
		}
		sort.Strings(sortedName)

		for _, name := range sortedName {
			fmt.Printf("%20s %d\n", name, stat[name])
		}

		time.Sleep(interval)
	}
}

func (*Systool) runDiskTool(interval time.Duration) {
	var lastStats = make(map[string]disk.IOCountersStat)
	for {
		stats, err := disk.IOCounters()
		swallow(err)

		sortedDisks := make([]string, 0, len(stats))
		for d := range stats {
			sortedDisks = append(sortedDisks, d)
		}
		sort.Strings(sortedDisks)

		lines := []string{"Disk|io|iops|read bytes|write bytes|read#|write#|mergedR#|mergedW#|readT|writeT|ioT|wio"}
		for _, d := range sortedDisks {
			diskSuffix := d[len(d)-1]
			if diskSuffix > '9' || diskSuffix < '0' {
				// not partitioned
				continue
			}

			stat := stats[d]
			if last, present := lastStats[d]; present {
				stat.ReadBytes -= last.ReadBytes
				stat.WriteBytes -= last.WriteBytes
				stat.ReadCount -= last.ReadCount
				stat.WriteCount -= last.WriteCount
				stat.MergedReadCount -= last.MergedReadCount
				stat.MergedWriteCount -= last.MergedWriteCount
				stat.WriteTime -= last.WriteTime
				stat.ReadTime -= last.ReadTime
				stat.IoTime -= last.IoTime
				stat.WeightedIO -= last.WeightedIO
			}

			var (
				rbytes = gofmt.ByteSize(stat.ReadBytes).String()
				wbytes = gofmt.ByteSize(stat.WriteBytes).String()
			)
			if strings.HasSuffix(rbytes, "MB") || strings.HasSuffix(rbytes, "GB") || strings.HasSuffix(rbytes, "TB") {
				rbytes = color.Red("%-10s", rbytes)
			}
			if strings.HasSuffix(wbytes, "MB") || strings.HasSuffix(wbytes, "GB") || strings.HasSuffix(wbytes, "TB") {
				wbytes = color.Red("%-11s", wbytes)
			}

			lines = append(lines, fmt.Sprintf("%s|%d|%d|%s|%s|%d|%d|%d|%d|%d|%d|%d|%d",
				stat.Name,
				stat.IopsInProgress,
				int(stat.ReadCount+stat.WriteCount)/int(interval.Seconds()),
				rbytes, wbytes,
				stat.ReadCount, stat.WriteCount,
				stat.MergedReadCount, stat.MergedWriteCount,
				stat.ReadTime, stat.WriteTime,
				stat.IoTime,
				stat.WeightedIO))
		}

		refreshScreen()
		fmt.Println(columnize.SimpleFormat(lines))

		if len(lastStats) == 0 {
			time.Sleep(interval + time.Second*3)
		} else {
			time.Sleep(interval)
		}

		lastStats = stats
	}

}

func (*Systool) Synopsis() string {
	return "OS diagnostics tool"
}

func (this *Systool) Help() string {
	help := fmt.Sprintf(`
Usage: %s systool [options]

    %s

Options:

    -d
      Disk diagnostics

    -i interval
      e,g. -i 3s

`, this.Cmd, this.Synopsis())
	return strings.TrimSpace(help)
}
<|endoftext|>


<commit_before>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package http provides HTTP client and server implementations.

Get, Head, Post, and PostForm make HTTP requests:

	resp, err := http.Get("http://example.com/")
	...
	resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
	...
	resp, err := http.PostForm("http://example.com/form",
 		url.Values{"key": {"Value"}, "id": {"123"}})

The client must close the response body when finished with it:

	resp, err := http.Get("http://example.com/")
	if err != nil {
		// handle error
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	// ...

For control over HTTP client headers, redirect policy, and other
settings, create a Client:

	client := &http.Client{
		CheckRedirect: redirectPolicyFunc,
	}

	resp, err := client.Get("http://example.com")
	// ...

	req := http.NewRequest("GET", "http://example.com", nil)
	req.Header.Add("If-None-Match", `W/"wyzzy"`)
	resp, err := client.Do(req)
	// ...

For control over proxies, TLS configuration, keep-alives,
compression, and other settings, create a Transport:

	tr := &http.Transport{
		TLSClientConfig:    &tls.Config{RootCAs: pool},
		DisableCompression: true,
	}
	client := &http.Client{Transport: tr}
	resp, err := client.Get("https://example.com")

Clients and Transports are safe for concurrent use by multiple
goroutines and for efficiency should only be created once and re-used.

ListenAndServe starts an HTTP server with a given address and handler.
The handler is usually nil, which means to use DefaultServeMux.
Handle and HandleFunc add handlers to DefaultServeMux:

	http.Handle("/foo", fooHandler)

	http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.RawPath))
	})

	log.Fatal(http.ListenAndServe(":8080", nil))

More control over the server's behavior is available by creating a
custom Server:

	s := &http.Server{
		Addr:           ":8080",
		Handler:        myHandler,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	log.Fatal(s.ListenAndServe())
*/
package http
<commit_msg>net/http: the documentation should call NewRequest with the right signature.<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package http provides HTTP client and server implementations.

Get, Head, Post, and PostForm make HTTP requests:

	resp, err := http.Get("http://example.com/")
	...
	resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
	...
	resp, err := http.PostForm("http://example.com/form",
 		url.Values{"key": {"Value"}, "id": {"123"}})

The client must close the response body when finished with it:

	resp, err := http.Get("http://example.com/")
	if err != nil {
		// handle error
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	// ...

For control over HTTP client headers, redirect policy, and other
settings, create a Client:

	client := &http.Client{
		CheckRedirect: redirectPolicyFunc,
	}

	resp, err := client.Get("http://example.com")
	// ...

	req, err := http.NewRequest("GET", "http://example.com", nil)
	// ...
	req.Header.Add("If-None-Match", `W/"wyzzy"`)
	resp, err := client.Do(req)
	// ...

For control over proxies, TLS configuration, keep-alives,
compression, and other settings, create a Transport:

	tr := &http.Transport{
		TLSClientConfig:    &tls.Config{RootCAs: pool},
		DisableCompression: true,
	}
	client := &http.Client{Transport: tr}
	resp, err := client.Get("https://example.com")

Clients and Transports are safe for concurrent use by multiple
goroutines and for efficiency should only be created once and re-used.

ListenAndServe starts an HTTP server with a given address and handler.
The handler is usually nil, which means to use DefaultServeMux.
Handle and HandleFunc add handlers to DefaultServeMux:

	http.Handle("/foo", fooHandler)

	http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.RawPath))
	})

	log.Fatal(http.ListenAndServe(":8080", nil))

More control over the server's behavior is available by creating a
custom Server:

	s := &http.Server{
		Addr:           ":8080",
		Handler:        myHandler,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	log.Fatal(s.ListenAndServe())
*/
package http
<|endoftext|>


<commit_before>package command

import (
	"bufio"
	"flag"
	"fmt"
	"os/user"
	"strings"

	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/pipestream"
)

type Upgrade struct {
	Ui  cli.Ui
	Cmd string

	storeUrl       string
	uploadDir      string
	mode           string
	upgradeKateway bool
}

func (this *Upgrade) Run(args []string) (exitCode int) {
	cmdFlags := flag.NewFlagSet("upgrade", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.StringVar(&this.storeUrl, "url", "http://10.77.144.193:10080/gk", "")
	cmdFlags.StringVar(&this.uploadDir, "upload", "/var/www/html", "")
	cmdFlags.StringVar(&this.mode, "m", "d", "")
	cmdFlags.BoolVar(&this.upgradeKateway, "k", false, "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	if this.upgradeKateway {
		this.storeUrl = "http://10.77.144.193:10080/kateway"

		switch this.mode {
		case "d":
			this.runCmd("wget", []string{this.storeUrl})
			this.runCmd("chmod", []string{"a+x", "kateway"})
			this.runCmd("mv", []string{"-f", "kateway", "/var/wd/kateway/kateway"})

		case "u":
			this.Ui.Warn("you must run './build.sh -it kateway' first.")
			this.runCmd("cp", []string{"-f", "/root/gopkg/bin/kateway", this.uploadDir})
		}

		return
	}

	// work on gk
	switch this.mode {
	case "d":
		u, err := user.Current()
		swallow(err)
		this.runCmd("/usr/bin/gk", []string{"-v"})
		this.runCmd("rm", []string{"-f", "gk"})
		this.runCmd("rm", []string{"-f", fmt.Sprintf("%s/.gafka.cf", u.HomeDir)})
		this.runCmd("wget", []string{this.storeUrl})
		this.runCmd("chmod", []string{"a+x", "gk"})
		this.runCmd("mv", []string{"-f", "gk", "/usr/bin/gk"})
		this.runCmd("/usr/bin/gk", []string{"-v"})

	case "u":
		this.runCmd("cp", []string{"-f", "/root/gopkg/bin/gk", this.uploadDir})

	default:
		this.Ui.Error("invalid mode")
		return 2
	}

	this.Ui.Info("ok")
	return
}

func (this *Upgrade) runCmd(c string, args []string) {
	this.Ui.Output(fmt.Sprintf("  %s %+v", c, args))

	cmd := pipestream.New(c, args...)
	err := cmd.Open()
	swallow(err)
	defer cmd.Close()

	scanner := bufio.NewScanner(cmd.Reader())
	scanner.Split(bufio.ScanLines)
	for scanner.Scan() {
		this.Ui.Output(fmt.Sprintf("    %s", scanner.Text()))
	}
	err = scanner.Err()
	if err != nil {
		this.Ui.Error(err.Error())
	}

}

func (*Upgrade) Synopsis() string {
	return "Upgrade local gk to latest version"
}

func (this *Upgrade) Help() string {
	help := fmt.Sprintf(`
Usage: %s upgrade [options]

    Upgrade local gk to latest version

Options:

    -k
      Upgrade on kateway instead of gk

    -m <d|u>
      Download or upload mode
      Defaults download mode

    -url gk file server url
      Defaults http://10.77.144.193:10080/gk

    -upload dir
      Upload the gk file to target dir, only run on gk file server
      Defaults /var/www/html

`, this.Cmd)
	return strings.TrimSpace(help)
}
<commit_msg>use another central repo to dispatch gafka pkg<commit_after>package command

import (
	"bufio"
	"flag"
	"fmt"
	"os/user"
	"strings"

	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/pipestream"
)

type Upgrade struct {
	Ui  cli.Ui
	Cmd string

	storeUrl       string
	uploadDir      string
	mode           string
	upgradeKateway bool
}

func (this *Upgrade) Run(args []string) (exitCode int) {
	cmdFlags := flag.NewFlagSet("upgrade", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.StringVar(&this.storeUrl, "url", "http://10.213.57.149:10080/gk", "")
	cmdFlags.StringVar(&this.uploadDir, "upload", "/var/www/html", "")
	cmdFlags.StringVar(&this.mode, "m", "d", "")
	cmdFlags.BoolVar(&this.upgradeKateway, "k", false, "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	if this.upgradeKateway {
		this.storeUrl = "http://10.213.57.149:10080/kateway"

		switch this.mode {
		case "d":
			this.runCmd("wget", []string{this.storeUrl})
			this.runCmd("chmod", []string{"a+x", "kateway"})
			this.runCmd("mv", []string{"-f", "kateway", "/var/wd/kateway/kateway"})

		case "u":
			this.Ui.Warn("you must run './build.sh -it kateway' first.")
			this.runCmd("cp", []string{"-f", "/root/gopkg/bin/kateway", this.uploadDir})
		}

		return
	}

	// work on gk
	switch this.mode {
	case "d":
		u, err := user.Current()
		swallow(err)
		this.runCmd("/usr/bin/gk", []string{"-v"})
		this.runCmd("rm", []string{"-f", "gk"})
		this.runCmd("rm", []string{"-f", fmt.Sprintf("%s/.gafka.cf", u.HomeDir)})
		this.runCmd("wget", []string{this.storeUrl})
		this.runCmd("chmod", []string{"a+x", "gk"})
		this.runCmd("mv", []string{"-f", "gk", "/usr/bin/gk"})
		this.runCmd("/usr/bin/gk", []string{"-v"})

	case "u":
		this.runCmd("cp", []string{"-f", "/root/gopkg/bin/gk", this.uploadDir})

	default:
		this.Ui.Error("invalid mode")
		return 2
	}

	this.Ui.Info("ok")
	return
}

func (this *Upgrade) runCmd(c string, args []string) {
	this.Ui.Output(fmt.Sprintf("  %s %+v", c, args))

	cmd := pipestream.New(c, args...)
	err := cmd.Open()
	swallow(err)
	defer cmd.Close()

	scanner := bufio.NewScanner(cmd.Reader())
	scanner.Split(bufio.ScanLines)
	for scanner.Scan() {
		this.Ui.Output(fmt.Sprintf("    %s", scanner.Text()))
	}
	err = scanner.Err()
	if err != nil {
		this.Ui.Error(err.Error())
	}

}

func (*Upgrade) Synopsis() string {
	return "Upgrade local gk to latest version"
}

func (this *Upgrade) Help() string {
	help := fmt.Sprintf(`
Usage: %s upgrade [options]

    Upgrade local gk to latest version

Options:

    -k
      Upgrade on kateway instead of gk

    -m <d|u>
      Download or upload mode
      Defaults download mode

    -url gk file server url
      Defaults http://10.213.57.149:10080/gk

    -upload dir
      Upload the gk file to target dir, only run on gk file server
      Defaults /var/www/html

`, this.Cmd)
	return strings.TrimSpace(help)
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package net

import (
	"regexp";
	"testing";
)

type DialErrorTest struct {
	Net string;
	Laddr string;
	Raddr string;
	Pattern string;
}

var dialErrorTests = []DialErrorTest {
	DialErrorTest{
		"datakit", "", "mh/astro/r70",
		"dial datakit mh/astro/r70: unknown network datakit",
	},
	DialErrorTest{
		"tcp", "", "127.0.0.1:☺",
		"dial tcp 127.0.0.1:☺: unknown port tcp/☺",
	},
	DialErrorTest{
		"tcp", "", "no-such-name.google.com.:80",
		"dial tcp no-such-name.google.com.:80: lookup no-such-name.google.com.( on .*)?: no such host",
	},
	DialErrorTest{
		"tcp", "", "no-such-name.no-such-top-level-domain.:80",
		"dial tcp no-such-name.no-such-top-level-domain.:80: lookup no-such-name.no-such-top-level-domain.( on .*)?: no (.*)",
	},
	DialErrorTest{
		"tcp", "", "no-such-name:80",
		`dial tcp no-such-name:80: lookup no-such-name\..*\.( on .*)?: no such host`,
	},
	DialErrorTest{
		"tcp", "", "mh/astro/r70:http",
		"dial tcp mh/astro/r70:http: lookup mh/astro/r70: invalid domain name",
	},
	DialErrorTest{
		"unix", "", "/etc/file-not-found",
		"dial unix /etc/file-not-found: no such file or directory",
	},
	DialErrorTest{
		"unix", "", "/etc/",
		"dial unix /etc/: (permission denied|socket operation on non-socket)",
	},
}

func TestDialError(t *testing.T) {
	for i, tt := range dialErrorTests {
		c, e := Dial(tt.Net, tt.Laddr, tt.Raddr);
		if c != nil {
			c.Close();
		}
		if e == nil {
			t.Errorf("#%d: nil error, want match for %#q", i, tt.Pattern);
			continue;
		}
		s := e.String();
		match, _ := regexp.MatchString(tt.Pattern, s);
		if !match {
			t.Errorf("#%d: %q, want match for %#q", i, s, tt.Pattern);
		}
	}
}
<commit_msg>two more regexp tweaks<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package net

import (
	"regexp";
	"testing";
)

type DialErrorTest struct {
	Net string;
	Laddr string;
	Raddr string;
	Pattern string;
}

var dialErrorTests = []DialErrorTest {
	DialErrorTest{
		"datakit", "", "mh/astro/r70",
		"dial datakit mh/astro/r70: unknown network datakit",
	},
	DialErrorTest{
		"tcp", "", "127.0.0.1:☺",
		"dial tcp 127.0.0.1:☺: unknown port tcp/☺",
	},
	DialErrorTest{
		"tcp", "", "no-such-name.google.com.:80",
		"dial tcp no-such-name.google.com.:80: lookup no-such-name.google.com.( on .*)?: no (.*)",
	},
	DialErrorTest{
		"tcp", "", "no-such-name.no-such-top-level-domain.:80",
		"dial tcp no-such-name.no-such-top-level-domain.:80: lookup no-such-name.no-such-top-level-domain.( on .*)?: no (.*)",
	},
	DialErrorTest{
		"tcp", "", "no-such-name:80",
		`dial tcp no-such-name:80: lookup no-such-name\..*\.( on .*)?: no (.*)`,
	},
	DialErrorTest{
		"tcp", "", "mh/astro/r70:http",
		"dial tcp mh/astro/r70:http: lookup mh/astro/r70: invalid domain name",
	},
	DialErrorTest{
		"unix", "", "/etc/file-not-found",
		"dial unix /etc/file-not-found: no such file or directory",
	},
	DialErrorTest{
		"unix", "", "/etc/",
		"dial unix /etc/: (permission denied|socket operation on non-socket)",
	},
}

func TestDialError(t *testing.T) {
	for i, tt := range dialErrorTests {
		c, e := Dial(tt.Net, tt.Laddr, tt.Raddr);
		if c != nil {
			c.Close();
		}
		if e == nil {
			t.Errorf("#%d: nil error, want match for %#q", i, tt.Pattern);
			continue;
		}
		s := e.String();
		match, _ := regexp.MatchString(tt.Pattern, s);
		if !match {
			t.Errorf("#%d: %q, want match for %#q", i, s, tt.Pattern);
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package os

import (
	"runtime"
	"syscall"
)

// Options for Wait.
const (
	WNOHANG   = syscall.WNOHANG   // Don't wait if no process has exited.
	WSTOPPED  = syscall.WSTOPPED  // If set, status of stopped subprocesses is also reported.
	WUNTRACED = syscall.WUNTRACED // Usually an alias for WSTOPPED.
	WRUSAGE   = 1 << 20           // Record resource usage.
)

// WRUSAGE must not be too high a bit, to avoid clashing with Linux's
// WCLONE, WALL, and WNOTHREAD flags, which sit in the top few bits of
// the options

// Wait waits for the Process to exit or stop, and then returns a
// Waitmsg describing its status and an Error, if any. The options
// (WNOHANG etc.) affect the behavior of the Wait call.
func (p *Process) Wait(options int) (w *Waitmsg, err Error) {
	if p.Pid == -1 {
		return nil, EINVAL
	}
	var status syscall.WaitStatus
	var rusage *syscall.Rusage
	if options&WRUSAGE != 0 {
		rusage = new(syscall.Rusage)
		options ^= WRUSAGE
	}
	pid1, e := syscall.Wait4(p.Pid, &status, options, rusage)
	if e != 0 {
		return nil, NewSyscallError("wait", e)
	}
	if options&WSTOPPED == 0 {
		p.done = true
	}
	w = new(Waitmsg)
	w.Pid = pid1
	w.WaitStatus = status
	w.Rusage = rusage
	return w, nil
}

// Signal sends a signal to the Process.
func (p *Process) Signal(sig Signal) Error {
	if p.done {
		return NewError("os: process already finished")
	}
	if e := syscall.Kill(p.Pid, int(sig.(UnixSignal))); e != 0 {
		return Errno(e)
	}
	return nil
}

// Release releases any resources associated with the Process.
func (p *Process) Release() Error {
	// NOOP for unix.
	p.Pid = -1
	// no need for a finalizer anymore
	runtime.SetFinalizer(p, nil)
	return nil
}

// FindProcess looks for a running process by its pid.
// The Process it returns can be used to obtain information
// about the underlying operating system process.
func FindProcess(pid int) (p *Process, err Error) {
	// NOOP for unix.
	return newProcess(pid, 0), nil
}
<commit_msg>os: fix WNOHANG Waitmsg<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package os

import (
	"runtime"
	"syscall"
)

// Options for Wait.
const (
	WNOHANG   = syscall.WNOHANG   // Don't wait if no process has exited.
	WSTOPPED  = syscall.WSTOPPED  // If set, status of stopped subprocesses is also reported.
	WUNTRACED = syscall.WUNTRACED // Usually an alias for WSTOPPED.
	WRUSAGE   = 1 << 20           // Record resource usage.
)

// WRUSAGE must not be too high a bit, to avoid clashing with Linux's
// WCLONE, WALL, and WNOTHREAD flags, which sit in the top few bits of
// the options

// Wait waits for the Process to exit or stop, and then returns a
// Waitmsg describing its status and an Error, if any. The options
// (WNOHANG etc.) affect the behavior of the Wait call.
func (p *Process) Wait(options int) (w *Waitmsg, err Error) {
	if p.Pid == -1 {
		return nil, EINVAL
	}
	var status syscall.WaitStatus
	var rusage *syscall.Rusage
	if options&WRUSAGE != 0 {
		rusage = new(syscall.Rusage)
		options ^= WRUSAGE
	}
	pid1, e := syscall.Wait4(p.Pid, &status, options, rusage)
	if e != 0 {
		return nil, NewSyscallError("wait", e)
	}
	// With WNOHANG pid is 0 if child has not exited.
	if pid1 != 0 && options&WSTOPPED == 0 {
		p.done = true
	}
	w = new(Waitmsg)
	w.Pid = pid1
	w.WaitStatus = status
	w.Rusage = rusage
	return w, nil
}

// Signal sends a signal to the Process.
func (p *Process) Signal(sig Signal) Error {
	if p.done {
		return NewError("os: process already finished")
	}
	if e := syscall.Kill(p.Pid, int(sig.(UnixSignal))); e != 0 {
		return Errno(e)
	}
	return nil
}

// Release releases any resources associated with the Process.
func (p *Process) Release() Error {
	// NOOP for unix.
	p.Pid = -1
	// no need for a finalizer anymore
	runtime.SetFinalizer(p, nil)
	return nil
}

// FindProcess looks for a running process by its pid.
// The Process it returns can be used to obtain information
// about the underlying operating system process.
func FindProcess(pid int) (p *Process, err Error) {
	// NOOP for unix.
	return newProcess(pid, 0), nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"crypto/rsa"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"runtime"
	"strings"

	"github.com/letsencrypt/pebble/cmd"
	"gopkg.in/square/go-jose.v2"
)

const (
	version       = "0.0.1"
	userAgentBase = "pebble-client"
	locale        = "en-us"
)

func userAgent() string {
	return fmt.Sprintf(
		"%s %s (%s; %s)",
		userAgentBase, version, runtime.GOOS, runtime.GOARCH)
}

type client struct {
	server    *url.URL
	directory map[string]interface{}
	email     string
	acctID    string
	http      *http.Client
	privKey   jose.SigningKey
	nonce     string
}

func newClient(server, email string) (*client, error) {
	url, err := url.Parse(server)
	if err != nil {
		return nil, err
	}

	privKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, err
	}

	c := &client{
		server: url,
		email:  email,
		http:   &http.Client{},
		privKey: jose.SigningKey{
			Key:       privKey,
			Algorithm: jose.RS256,
		},
	}

	err = c.updateDirectory()
	if err != nil {
		return nil, err
	}

	err = c.updateNonce()
	if err != nil {
		return nil, err
	}

	err = c.register()
	if err != nil {
		return nil, err
	}

	return c, nil
}

func (c *client) sign(data []byte, url string) (*jose.JSONWebSignature, error) {
	signer, err := jose.NewSigner(c.privKey, &jose.SignerOptions{
		NonceSource: c,
		EmbedJWK:    true, // TODO(jsha): True only for registration
		ExtraHeaders: map[jose.HeaderKey]interface{}{
			"url": url,
		},
	})
	if err != nil {
		return nil, err
	}

	signed, err := signer.Sign(data)
	if err != nil {
		return nil, err
	}
	return signed, nil
}

func (c *client) updateDirectory() error {
	fmt.Printf("Requesting directory from %q\n", c.server.String())
	respBody, _, err := c.getAPI(c.server.String())
	if err != nil {
		return err
	}

	var directory map[string]interface{}
	err = json.Unmarshal(respBody, &directory)
	if err != nil {
		return err
	}

	c.directory = directory
	return nil
}

func (c *client) updateNonce() error {
	nonceURL := c.directory["new-nonce"].(string)
	if nonceURL == "" {
		return fmt.Errorf("Missing \"new-nonce\" entry in server directory")
	}
	fmt.Printf("Requesting nonce from %q\n", nonceURL)

	before := c.nonce
	_, _, err := c.getAPI(nonceURL)
	if err != nil {
		return err
	}
	after := c.nonce

	if before == after {
		return fmt.Errorf("Did not recieve a fresh nonce from new-nonce URL")
	}
	return nil
}

func (c *client) register() error {
	if acctURL, ok := c.directory["new-account"]; !ok || acctURL.(string) == "" {
		return fmt.Errorf("Missing \"new-account\" entry in server directory")
	}
	acctURL := c.directory["new-account"].(string)
	fmt.Printf("Registering new account with %q\n", acctURL)

	reqBody := struct {
		ToSAgreed bool `json:"terms-of-service-agreed"`
		Contact   []string
	}{
		ToSAgreed: true,
		Contact:   []string{"mailto:" + c.email},
	}

	reqBodyStr, err := json.Marshal(&reqBody)
	if err != nil {
		return err
	}

	_, resp, err := c.postAPI(acctURL, reqBodyStr)
	if err != nil {
		return err
	}

	locHeader := resp.Header.Get("Location")
	if locHeader == "" {
		return fmt.Errorf("No 'location' header with account URL in response")
	}

	c.acctID = locHeader
	return nil
}

// Nonce satisfies the JWS "NonceSource" interface
func (c *client) Nonce() (string, error) {
	n := c.nonce
	err := c.updateNonce()
	if err != nil {
		return n, err
	}
	return n, nil
}

func (c *client) doReq(req *http.Request) ([]byte, *http.Response, error) {
	resp, err := c.http.Do(req)
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	if n := resp.Header.Get("Replay-Nonce"); n != "" {
		c.nonce = n
	}

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, nil, err
	}
	if resp.StatusCode/100 != 2 {
		return nil, nil, fmt.Errorf("Response %d: %s", resp.StatusCode, respBody)
	}
	return respBody, resp, nil
}

func (c *client) getAPI(url string) ([]byte, *http.Response, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("User-Agent", userAgent())
	req.Header.Set("Accept-Language", locale)
	return c.doReq(req)
}

func (c *client) postAPI(url string, body []byte) ([]byte, *http.Response, error) {
	signedBody, err := c.sign(body, url)
	if err != nil {
		return nil, nil, err
	}

	bodyBuf := bytes.NewBuffer([]byte(signedBody.FullSerialize()))
	req, err := http.NewRequest("POST", url, bodyBuf)
	if err != nil {
		return nil, nil, err
	}

	req.Header.Set("Content-Type", "application/jose+json")
	req.Header.Set("User-Agent", userAgent())
	req.Header.Set("Accept-Language", locale)
	return c.doReq(req)
}

func (c *client) endpoints() []string {
	res := make([]string, 0, len(c.directory))
	for k := range c.directory {
		res = append(res, k)
	}
	return res
}

func (c *client) readEndpoint() (string, error) {
	var endpoint string
	scanner := bufio.NewScanner(os.Stdin)
	fmt.Printf("$> Enter a directory endpoint to POST: ")
	for scanner.Scan() {
		line := scanner.Text()
		if line == "" || line == "exit" || line == "q" {
			break
		}
		if _, ok := c.directory[line]; !ok {
			if url, err := url.Parse(line); err == nil {
				endpoint = url.String()
				break
			}
			fmt.Printf("Unknown directory endpoint or invalid URL: %q.\nAvailable choices: %s\n",
				line, strings.Join(c.endpoints(), ", "))
			fmt.Printf("$> Enter a directory endpoint to POST: ")
			continue
		}
		endpoint = c.directory[line].(string)
		break
	}
	if err := scanner.Err(); err != nil {
		return endpoint, err
	}
	return strings.TrimSpace(endpoint), nil
}

func (c *client) readJSON() ([]byte, error) {
	var jsonBuf string

	scanner := bufio.NewScanner(os.Stdin)
	fmt.Printf("$> Enter JSON body, empty line to finish : \n")
	for scanner.Scan() {
		line := scanner.Text()
		if line == "" || line == "exit" || line == "q" {
			break
		}
		jsonBuf += line
	}

	var indented bytes.Buffer
	err := json.Indent(&indented, []byte(jsonBuf), "", "  ")
	return indented.Bytes(), err
}

func (c *client) repl() error {
	for {
		endpoint, err := c.readEndpoint()
		if err != nil {
			return err
		}
		if endpoint == "" {
			break
		}

		body, err := c.readJSON()
		if err != nil {
			return err
		}

		respBody, resp, err := c.postAPI(endpoint, body)
		if err != nil {
			return err
		}

		var indented bytes.Buffer
		err = json.Indent(&indented, respBody, "", "  ")
		fmt.Printf("Response:\n%#v\n\n%s\n", resp, indented.String())
	}

	fmt.Println("Goodbye")
	return nil
}

func main() {
	server := flag.String("server", "http://localhost:14000/dir", "Directory address for Pebble server")
	email := flag.String("email", "", "Email address for ACME registration contact")
	flag.Parse()

	fmt.Println("welcome to the pebble shell")

	c, err := newClient(*server, *email)
	cmd.FailOnError(err,
		fmt.Sprintf("Failed to make new pebble client with email %q", *email))

	fmt.Printf("Your account ID is %q\n", c.acctID)
	fmt.Println("Starting REPL environment...\n\n")
	err = c.repl()
	cmd.FailOnError(err, "REPL error")
}
<commit_msg>Only embed JWK for registration. (#50)<commit_after>package main

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"crypto/rsa"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"runtime"
	"strings"

	"github.com/letsencrypt/pebble/cmd"
	"gopkg.in/square/go-jose.v2"
)

const (
	version       = "0.0.1"
	userAgentBase = "pebble-client"
	locale        = "en-us"
)

func userAgent() string {
	return fmt.Sprintf(
		"%s %s (%s; %s)",
		userAgentBase, version, runtime.GOOS, runtime.GOARCH)
}

type client struct {
	server    *url.URL
	directory map[string]interface{}
	email     string
	acctID    string
	http      *http.Client
	privKey   jose.SigningKey
	nonce     string
}

func newClient(server, email string) (*client, error) {
	url, err := url.Parse(server)
	if err != nil {
		return nil, err
	}

	privKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, err
	}

	c := &client{
		server: url,
		email:  email,
		http:   &http.Client{},
		privKey: jose.SigningKey{
			Key:       privKey,
			Algorithm: jose.RS256,
		},
	}

	err = c.updateDirectory()
	if err != nil {
		return nil, err
	}

	err = c.updateNonce()
	if err != nil {
		return nil, err
	}

	err = c.register()
	if err != nil {
		return nil, err
	}

	return c, nil
}

func (c *client) signEmbedded(data []byte, url string) (*jose.JSONWebSignature, error) {
	signer, err := jose.NewSigner(c.privKey, &jose.SignerOptions{
		NonceSource: c,
		EmbedJWK:    true,
		ExtraHeaders: map[jose.HeaderKey]interface{}{
			"url": url,
		},
	})
	if err != nil {
		return nil, err
	}

	signed, err := signer.Sign(data)
	if err != nil {
		return nil, err
	}
	return signed, nil
}

func (c *client) signKeyID(data []byte, url string) (*jose.JSONWebSignature, error) {
	jwk := &jose.JSONWebKey{
		Key:       c.privKey.Key,
		Algorithm: "RSA",
		KeyID:     c.acctID,
	}

	signerKey := jose.SigningKey{
		Key:       jwk,
		Algorithm: jose.RS256,
	}

	opts := &jose.SignerOptions{
		NonceSource: c,
		ExtraHeaders: map[jose.HeaderKey]interface{}{
			"url": url,
		},
	}

	signer, err := jose.NewSigner(signerKey, opts)
	if err != nil {
		fmt.Printf("Err making signer: %#v\n", err)
		return nil, err
	}
	signed, err := signer.Sign(data)
	if err != nil {
		fmt.Printf("Err using signer: %#v\n", err)
		return nil, err
	}
	return signed, nil
}

func (c *client) updateDirectory() error {
	fmt.Printf("Requesting directory from %q\n", c.server.String())
	respBody, _, err := c.getAPI(c.server.String())
	if err != nil {
		return err
	}

	var directory map[string]interface{}
	err = json.Unmarshal(respBody, &directory)
	if err != nil {
		return err
	}

	c.directory = directory
	return nil
}

func (c *client) updateNonce() error {
	nonceURL := c.directory["new-nonce"].(string)
	if nonceURL == "" {
		return fmt.Errorf("Missing \"new-nonce\" entry in server directory")
	}
	fmt.Printf("Requesting nonce from %q\n", nonceURL)

	before := c.nonce
	_, _, err := c.getAPI(nonceURL)
	if err != nil {
		return err
	}
	after := c.nonce

	if before == after {
		return fmt.Errorf("Did not recieve a fresh nonce from new-nonce URL")
	}
	return nil
}

func (c *client) register() error {
	if acctURL, ok := c.directory["new-account"]; !ok || acctURL.(string) == "" {
		return fmt.Errorf("Missing \"new-account\" entry in server directory")
	}
	acctURL := c.directory["new-account"].(string)
	fmt.Printf("Registering new account with %q\n", acctURL)

	reqBody := struct {
		ToSAgreed bool `json:"terms-of-service-agreed"`
		Contact   []string
	}{
		ToSAgreed: true,
		Contact:   []string{"mailto:" + c.email},
	}

	reqBodyStr, err := json.Marshal(&reqBody)
	if err != nil {
		return err
	}

	// Registration is a unique case where we _do_ want the JWK to be embedded (vs
	// using a Key ID) so we invoke `postAPI` with `true` for the embed argument.
	_, resp, err := c.postAPI(acctURL, reqBodyStr, true)
	if err != nil {
		return err
	}

	locHeader := resp.Header.Get("Location")
	if locHeader == "" {
		return fmt.Errorf("No 'location' header with account URL in response")
	}

	c.acctID = locHeader
	return nil
}

// Nonce satisfies the JWS "NonceSource" interface
func (c *client) Nonce() (string, error) {
	n := c.nonce
	err := c.updateNonce()
	if err != nil {
		return n, err
	}
	return n, nil
}

func (c *client) doReq(req *http.Request) ([]byte, *http.Response, error) {
	resp, err := c.http.Do(req)
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	if n := resp.Header.Get("Replay-Nonce"); n != "" {
		c.nonce = n
	}

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, nil, err
	}
	if resp.StatusCode/100 != 2 {
		return nil, nil, fmt.Errorf("Response %d: %s", resp.StatusCode, respBody)
	}
	return respBody, resp, nil
}

func (c *client) getAPI(url string) ([]byte, *http.Response, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("User-Agent", userAgent())
	req.Header.Set("Accept-Language", locale)
	return c.doReq(req)
}

func (c *client) postAPI(url string, body []byte, embedJWK bool) ([]byte, *http.Response, error) {
	var signedBody *jose.JSONWebSignature
	var err error

	if embedJWK {
		signedBody, err = c.signEmbedded(body, url)
	} else {
		signedBody, err = c.signKeyID(body, url)
	}

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

	bodyBuf := bytes.NewBuffer([]byte(signedBody.FullSerialize()))
	req, err := http.NewRequest("POST", url, bodyBuf)
	if err != nil {
		return nil, nil, err
	}

	req.Header.Set("Content-Type", "application/jose+json")
	req.Header.Set("User-Agent", userAgent())
	req.Header.Set("Accept-Language", locale)
	return c.doReq(req)
}

func (c *client) endpoints() []string {
	res := make([]string, 0, len(c.directory))
	for k := range c.directory {
		res = append(res, k)
	}
	return res
}

func (c *client) readEndpoint() (string, error) {
	var endpoint string
	scanner := bufio.NewScanner(os.Stdin)
	fmt.Printf("$> Enter a directory endpoint to POST: ")
	for scanner.Scan() {
		line := scanner.Text()
		if line == "" || line == "exit" || line == "q" {
			break
		}
		if _, ok := c.directory[line]; !ok {
			if url, err := url.Parse(line); err == nil {
				endpoint = url.String()
				break
			}
			fmt.Printf("Unknown directory endpoint or invalid URL: %q.\nAvailable choices: %s\n",
				line, strings.Join(c.endpoints(), ", "))
			fmt.Printf("$> Enter a directory endpoint to POST: ")
			continue
		}
		endpoint = c.directory[line].(string)
		break
	}
	if err := scanner.Err(); err != nil {
		return endpoint, err
	}
	return strings.TrimSpace(endpoint), nil
}

func (c *client) readJSON() ([]byte, error) {
	var jsonBuf string

	scanner := bufio.NewScanner(os.Stdin)
	fmt.Printf("$> Enter JSON body, empty line to finish : \n")
	for scanner.Scan() {
		line := scanner.Text()
		if line == "" || line == "exit" || line == "q" {
			break
		}
		jsonBuf += line
	}

	var indented bytes.Buffer
	err := json.Indent(&indented, []byte(jsonBuf), "", "  ")
	return indented.Bytes(), err
}

func (c *client) repl() error {
	for {
		endpoint, err := c.readEndpoint()
		if err != nil {
			return err
		}
		if endpoint == "" {
			break
		}

		body, err := c.readJSON()
		if err != nil {
			return err
		}

		respBody, resp, err := c.postAPI(endpoint, body, false)
		if err != nil {
			return err
		}

		var indented bytes.Buffer
		err = json.Indent(&indented, respBody, "", "  ")
		fmt.Printf("Response:\n%#v\n\n%s\n", resp, indented.String())
	}

	fmt.Println("Goodbye")
	return nil
}

func main() {
	server := flag.String("server", "http://localhost:14000/dir", "Directory address for Pebble server")
	email := flag.String("email", "", "Email address for ACME registration contact")
	flag.Parse()

	fmt.Println("welcome to the pebble shell")

	c, err := newClient(*server, *email)
	cmd.FailOnError(err,
		fmt.Sprintf("Failed to make new pebble client with email %q", *email))

	fmt.Printf("Your account ID is %q\n", c.acctID)
	fmt.Println("Starting REPL environment...\n\n")
	err = c.repl()
	cmd.FailOnError(err, "REPL error")
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris

package os

import (
	"runtime"
	"sync/atomic"
	"syscall"
)

// File represents an open file descriptor.
type File struct {
	*file
}

// file is the real representation of *File.
// The extra level of indirection ensures that no clients of os
// can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor.
type file struct {
	fd      int
	name    string
	dirinfo *dirInfo // nil unless directory being read
	nepipe  int32    // number of consecutive EPIPE in Write
}

// Fd returns the integer Unix file descriptor referencing the open file.
func (f *File) Fd() uintptr {
	if f == nil {
		return ^(uintptr(0))
	}
	return uintptr(f.fd)
}

// NewFile returns a new File with the given file descriptor and name.
func NewFile(fd uintptr, name string) *File {
	fdi := int(fd)
	if fdi < 0 {
		return nil
	}
	f := &File{&file{fd: fdi, name: name}}
	runtime.SetFinalizer(f.file, (*file).close)
	return f
}

// Auxiliary information if the File describes a directory
type dirInfo struct {
	buf  []byte // buffer for directory I/O
	nbuf int    // length of buf; return value from Getdirentries
	bufp int    // location of next record in buf.
}

func epipecheck(file *File, e error) {
	if e == syscall.EPIPE {
		if atomic.AddInt32(&file.nepipe, 1) >= 10 {
			sigpipe()
		}
	} else {
		atomic.StoreInt32(&file.nepipe, 0)
	}
}

// DevNull is the name of the operating system's ``null device.''
// On Unix-like systems, it is "/dev/null"; on Windows, "NUL".
const DevNull = "/dev/null"

// OpenFile is the generalized open call; most users will use Open
// or Create instead.  It opens the named file with specified flag
// (O_RDONLY etc.) and perm, (0666 etc.) if applicable.  If successful,
// methods on the returned File can be used for I/O.
// If there is an error, it will be of type *PathError.
func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
	r, e := syscall.Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
	if e != nil {
		return nil, &PathError{"open", name, e}
	}

	// There's a race here with fork/exec, which we are
	// content to live with.  See ../syscall/exec_unix.go.
	if !supportsCloseOnExec {
		syscall.CloseOnExec(r)
	}

	return NewFile(uintptr(r), name), nil
}

// Close closes the File, rendering it unusable for I/O.
// It returns an error, if any.
func (f *File) Close() error {
	if f == nil {
		return ErrInvalid
	}
	return f.file.close()
}

func (file *file) close() error {
	if file == nil || file.fd < 0 {
		return syscall.EINVAL
	}
	var err error
	if e := syscall.Close(file.fd); e != nil {
		err = &PathError{"close", file.name, e}
	}
	file.fd = -1 // so it can't be closed again

	// no need for a finalizer anymore
	runtime.SetFinalizer(file, nil)
	return err
}

// Stat returns the FileInfo structure describing file.
// If there is an error, it will be of type *PathError.
func (f *File) Stat() (fi FileInfo, err error) {
	if f == nil {
		return nil, ErrInvalid
	}
	var stat syscall.Stat_t
	err = syscall.Fstat(f.fd, &stat)
	if err != nil {
		return nil, &PathError{"stat", f.name, err}
	}
	return fileInfoFromStat(&stat, f.name), nil
}

// Stat returns a FileInfo describing the named file.
// If there is an error, it will be of type *PathError.
func Stat(name string) (fi FileInfo, err error) {
	var stat syscall.Stat_t
	err = syscall.Stat(name, &stat)
	if err != nil {
		return nil, &PathError{"stat", name, err}
	}
	return fileInfoFromStat(&stat, name), nil
}

// Lstat returns a FileInfo describing the named file.
// If the file is a symbolic link, the returned FileInfo
// describes the symbolic link.  Lstat makes no attempt to follow the link.
// If there is an error, it will be of type *PathError.
func Lstat(name string) (fi FileInfo, err error) {
	var stat syscall.Stat_t
	err = syscall.Lstat(name, &stat)
	if err != nil {
		return nil, &PathError{"lstat", name, err}
	}
	return fileInfoFromStat(&stat, name), nil
}

func (f *File) readdir(n int) (fi []FileInfo, err error) {
	dirname := f.name
	if dirname == "" {
		dirname = "."
	}
	names, err := f.Readdirnames(n)
	fi = make([]FileInfo, 0, len(names))
	for _, filename := range names {
		fip, lerr := lstat(dirname + "/" + filename)
		if IsNotExist(lerr) {
			// File disappeared between readdir + stat.
			// Just treat it as if it didn't exist.
			continue
		}
		if lerr != nil {
			return fi, lerr
		}
		fi = append(fi, fip)
	}
	return fi, err
}

// read reads up to len(b) bytes from the File.
// It returns the number of bytes read and an error, if any.
func (f *File) read(b []byte) (n int, err error) {
	return syscall.Read(f.fd, b)
}

// pread reads len(b) bytes from the File starting at byte offset off.
// It returns the number of bytes read and the error, if any.
// EOF is signaled by a zero count with err set to 0.
func (f *File) pread(b []byte, off int64) (n int, err error) {
	return syscall.Pread(f.fd, b, off)
}

// write writes len(b) bytes to the File.
// It returns the number of bytes written and an error, if any.
func (f *File) write(b []byte) (n int, err error) {
	for {
		m, err := syscall.Write(f.fd, b)
		n += m

		// If the syscall wrote some data but not all (short write)
		// or it returned EINTR, then assume it stopped early for
		// reasons that are uninteresting to the caller, and try again.
		if 0 < m && m < len(b) || err == syscall.EINTR {
			b = b[m:]
			continue
		}

		return n, err
	}
}

// pwrite writes len(b) bytes to the File starting at byte offset off.
// It returns the number of bytes written and an error, if any.
func (f *File) pwrite(b []byte, off int64) (n int, err error) {
	return syscall.Pwrite(f.fd, b, off)
}

// seek sets the offset for the next Read or Write on file to offset, interpreted
// according to whence: 0 means relative to the origin of the file, 1 means
// relative to the current offset, and 2 means relative to the end.
// It returns the new offset and an error, if any.
func (f *File) seek(offset int64, whence int) (ret int64, err error) {
	return syscall.Seek(f.fd, offset, whence)
}

// Truncate changes the size of the named file.
// If the file is a symbolic link, it changes the size of the link's target.
// If there is an error, it will be of type *PathError.
func Truncate(name string, size int64) error {
	if e := syscall.Truncate(name, size); e != nil {
		return &PathError{"truncate", name, e}
	}
	return nil
}

// Remove removes the named file or directory.
// If there is an error, it will be of type *PathError.
func Remove(name string) error {
	// System call interface forces us to know
	// whether name is a file or directory.
	// Try both: it is cheaper on average than
	// doing a Stat plus the right one.
	e := syscall.Unlink(name)
	if e == nil {
		return nil
	}
	e1 := syscall.Rmdir(name)
	if e1 == nil {
		return nil
	}

	// Both failed: figure out which error to return.
	// OS X and Linux differ on whether unlink(dir)
	// returns EISDIR, so can't use that.  However,
	// both agree that rmdir(file) returns ENOTDIR,
	// so we can use that to decide which error is real.
	// Rmdir might also return ENOTDIR if given a bad
	// file path, like /etc/passwd/foo, but in that case,
	// both errors will be ENOTDIR, so it's okay to
	// use the error from unlink.
	if e1 != syscall.ENOTDIR {
		e = e1
	}
	return &PathError{"remove", name, e}
}

// basename removes trailing slashes and the leading directory name from path name
func basename(name string) string {
	i := len(name) - 1
	// Remove trailing slashes
	for ; i > 0 && name[i] == '/'; i-- {
		name = name[:i]
	}
	// Remove leading directory name
	for i--; i >= 0; i-- {
		if name[i] == '/' {
			name = name[i+1:]
			break
		}
	}

	return name
}

// TempDir returns the default directory to use for temporary files.
func TempDir() string {
	dir := Getenv("TMPDIR")
	if dir == "" {
		dir = "/tmp"
	}
	return dir
}
<commit_msg>os: cap reads and writes to 2GB on Darwin and FreeBSD<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris

package os

import (
	"runtime"
	"sync/atomic"
	"syscall"
)

// File represents an open file descriptor.
type File struct {
	*file
}

// file is the real representation of *File.
// The extra level of indirection ensures that no clients of os
// can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor.
type file struct {
	fd      int
	name    string
	dirinfo *dirInfo // nil unless directory being read
	nepipe  int32    // number of consecutive EPIPE in Write
}

// Fd returns the integer Unix file descriptor referencing the open file.
func (f *File) Fd() uintptr {
	if f == nil {
		return ^(uintptr(0))
	}
	return uintptr(f.fd)
}

// NewFile returns a new File with the given file descriptor and name.
func NewFile(fd uintptr, name string) *File {
	fdi := int(fd)
	if fdi < 0 {
		return nil
	}
	f := &File{&file{fd: fdi, name: name}}
	runtime.SetFinalizer(f.file, (*file).close)
	return f
}

// Auxiliary information if the File describes a directory
type dirInfo struct {
	buf  []byte // buffer for directory I/O
	nbuf int    // length of buf; return value from Getdirentries
	bufp int    // location of next record in buf.
}

func epipecheck(file *File, e error) {
	if e == syscall.EPIPE {
		if atomic.AddInt32(&file.nepipe, 1) >= 10 {
			sigpipe()
		}
	} else {
		atomic.StoreInt32(&file.nepipe, 0)
	}
}

// DevNull is the name of the operating system's ``null device.''
// On Unix-like systems, it is "/dev/null"; on Windows, "NUL".
const DevNull = "/dev/null"

// OpenFile is the generalized open call; most users will use Open
// or Create instead.  It opens the named file with specified flag
// (O_RDONLY etc.) and perm, (0666 etc.) if applicable.  If successful,
// methods on the returned File can be used for I/O.
// If there is an error, it will be of type *PathError.
func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
	r, e := syscall.Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
	if e != nil {
		return nil, &PathError{"open", name, e}
	}

	// There's a race here with fork/exec, which we are
	// content to live with.  See ../syscall/exec_unix.go.
	if !supportsCloseOnExec {
		syscall.CloseOnExec(r)
	}

	return NewFile(uintptr(r), name), nil
}

// Close closes the File, rendering it unusable for I/O.
// It returns an error, if any.
func (f *File) Close() error {
	if f == nil {
		return ErrInvalid
	}
	return f.file.close()
}

func (file *file) close() error {
	if file == nil || file.fd < 0 {
		return syscall.EINVAL
	}
	var err error
	if e := syscall.Close(file.fd); e != nil {
		err = &PathError{"close", file.name, e}
	}
	file.fd = -1 // so it can't be closed again

	// no need for a finalizer anymore
	runtime.SetFinalizer(file, nil)
	return err
}

// Stat returns the FileInfo structure describing file.
// If there is an error, it will be of type *PathError.
func (f *File) Stat() (fi FileInfo, err error) {
	if f == nil {
		return nil, ErrInvalid
	}
	var stat syscall.Stat_t
	err = syscall.Fstat(f.fd, &stat)
	if err != nil {
		return nil, &PathError{"stat", f.name, err}
	}
	return fileInfoFromStat(&stat, f.name), nil
}

// Stat returns a FileInfo describing the named file.
// If there is an error, it will be of type *PathError.
func Stat(name string) (fi FileInfo, err error) {
	var stat syscall.Stat_t
	err = syscall.Stat(name, &stat)
	if err != nil {
		return nil, &PathError{"stat", name, err}
	}
	return fileInfoFromStat(&stat, name), nil
}

// Lstat returns a FileInfo describing the named file.
// If the file is a symbolic link, the returned FileInfo
// describes the symbolic link.  Lstat makes no attempt to follow the link.
// If there is an error, it will be of type *PathError.
func Lstat(name string) (fi FileInfo, err error) {
	var stat syscall.Stat_t
	err = syscall.Lstat(name, &stat)
	if err != nil {
		return nil, &PathError{"lstat", name, err}
	}
	return fileInfoFromStat(&stat, name), nil
}

func (f *File) readdir(n int) (fi []FileInfo, err error) {
	dirname := f.name
	if dirname == "" {
		dirname = "."
	}
	names, err := f.Readdirnames(n)
	fi = make([]FileInfo, 0, len(names))
	for _, filename := range names {
		fip, lerr := lstat(dirname + "/" + filename)
		if IsNotExist(lerr) {
			// File disappeared between readdir + stat.
			// Just treat it as if it didn't exist.
			continue
		}
		if lerr != nil {
			return fi, lerr
		}
		fi = append(fi, fip)
	}
	return fi, err
}

// Darwin and FreeBSD can't read or write 2GB+ at a time,
// even on 64-bit systems. See golang.org/issue/7812.
const (
	needsMaxRW = runtime.GOOS == "darwin" || runtime.GOOS == "freebsd"
	maxRW      = 2<<30 - 1
)

// read reads up to len(b) bytes from the File.
// It returns the number of bytes read and an error, if any.
func (f *File) read(b []byte) (n int, err error) {
	if needsMaxRW && len(b) > maxRW {
		b = b[:maxRW]
	}
	return syscall.Read(f.fd, b)
}

// pread reads len(b) bytes from the File starting at byte offset off.
// It returns the number of bytes read and the error, if any.
// EOF is signaled by a zero count with err set to nil.
func (f *File) pread(b []byte, off int64) (n int, err error) {
	if needsMaxRW && len(b) > maxRW {
		b = b[:maxRW]
	}
	return syscall.Pread(f.fd, b, off)
}

// write writes len(b) bytes to the File.
// It returns the number of bytes written and an error, if any.
func (f *File) write(b []byte) (n int, err error) {
	for {
		bcap := b
		if needsMaxRW && len(bcap) > maxRW {
			bcap = bcap[:maxRW]
		}
		m, err := syscall.Write(f.fd, bcap)
		n += m

		// If the syscall wrote some data but not all (short write)
		// or it returned EINTR, then assume it stopped early for
		// reasons that are uninteresting to the caller, and try again.
		if 0 < m && m < len(bcap) || err == syscall.EINTR {
			b = b[m:]
			continue
		}

		if needsMaxRW && len(bcap) != len(b) && err == nil {
			b = b[m:]
			continue
		}

		return n, err
	}
}

// pwrite writes len(b) bytes to the File starting at byte offset off.
// It returns the number of bytes written and an error, if any.
func (f *File) pwrite(b []byte, off int64) (n int, err error) {
	if needsMaxRW && len(b) > maxRW {
		b = b[:maxRW]
	}
	return syscall.Pwrite(f.fd, b, off)
}

// seek sets the offset for the next Read or Write on file to offset, interpreted
// according to whence: 0 means relative to the origin of the file, 1 means
// relative to the current offset, and 2 means relative to the end.
// It returns the new offset and an error, if any.
func (f *File) seek(offset int64, whence int) (ret int64, err error) {
	return syscall.Seek(f.fd, offset, whence)
}

// Truncate changes the size of the named file.
// If the file is a symbolic link, it changes the size of the link's target.
// If there is an error, it will be of type *PathError.
func Truncate(name string, size int64) error {
	if e := syscall.Truncate(name, size); e != nil {
		return &PathError{"truncate", name, e}
	}
	return nil
}

// Remove removes the named file or directory.
// If there is an error, it will be of type *PathError.
func Remove(name string) error {
	// System call interface forces us to know
	// whether name is a file or directory.
	// Try both: it is cheaper on average than
	// doing a Stat plus the right one.
	e := syscall.Unlink(name)
	if e == nil {
		return nil
	}
	e1 := syscall.Rmdir(name)
	if e1 == nil {
		return nil
	}

	// Both failed: figure out which error to return.
	// OS X and Linux differ on whether unlink(dir)
	// returns EISDIR, so can't use that.  However,
	// both agree that rmdir(file) returns ENOTDIR,
	// so we can use that to decide which error is real.
	// Rmdir might also return ENOTDIR if given a bad
	// file path, like /etc/passwd/foo, but in that case,
	// both errors will be ENOTDIR, so it's okay to
	// use the error from unlink.
	if e1 != syscall.ENOTDIR {
		e = e1
	}
	return &PathError{"remove", name, e}
}

// basename removes trailing slashes and the leading directory name from path name
func basename(name string) string {
	i := len(name) - 1
	// Remove trailing slashes
	for ; i > 0 && name[i] == '/'; i-- {
		name = name[:i]
	}
	// Remove leading directory name
	for i--; i >= 0; i-- {
		if name[i] == '/' {
			name = name[i+1:]
			break
		}
	}

	return name
}

// TempDir returns the default directory to use for temporary files.
func TempDir() string {
	dir := Getenv("TMPDIR")
	if dir == "" {
		dir = "/tmp"
	}
	return dir
}
<|endoftext|>


<commit_before>package infoblox

import (
	"fmt"
	"log"
	"net/url"
	"strings"

	"github.com/fanatic/go-infoblox"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceInfobloxRecord() *schema.Resource {
	return &schema.Resource{
		Create: resourceInfobloxRecordCreate,
		Read:   resourceInfobloxRecordRead,
		Update: resourceInfobloxRecordUpdate,
		Delete: resourceInfobloxRecordDelete,

		Schema: map[string]*schema.Schema{
			"domain": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"value": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"type": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"ttl": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "3600",
			},
		},
	}
}

func resourceInfobloxRecordCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*infoblox.Client)

	record := url.Values{}
	if err := getAll(d, record); err != nil {
		return err
	}

	log.Printf("[DEBUG] Infoblox Record create configuration: %#v", record)

	var recID string
	var err error

	switch strings.ToUpper(d.Get("type").(string)) {
	case "A":
		opts := &infoblox.Options{
			ReturnFields: []string{"ttl", "ipv4addr", "name"},
		}
		recID, err = client.RecordA().Create(record, opts, nil)
	case "AAAA":
		opts := &infoblox.Options{
			ReturnFields: []string{"ttl", "ipv6addr", "name"},
		}
		recID, err = client.RecordAAAA().Create(record, opts, nil)
	case "CNAME":
		opts := &infoblox.Options{
			ReturnFields: []string{"ttl", "canonical", "name"},
		}
		recID, err = client.RecordCname().Create(record, opts, nil)
	default:
		return fmt.Errorf("resourceInfobloxRecordCreate: unknown type")
	}

	if err != nil {
		return fmt.Errorf("Failed to create Infblox Record: %s", err)
	}

	d.SetId(recID)

	log.Printf("[INFO] record ID: %s", d.Id())

	return resourceInfobloxRecordRead(d, meta)
}

func resourceInfobloxRecordRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*infoblox.Client)

	switch strings.ToUpper(d.Get("type").(string)) {
	case "A":
		rec, err := client.GetRecordA(d.Id(), nil)
		if err != nil {
			return fmt.Errorf("Couldn't find Infoblox A record: %s", err)
		}
		d.Set("value", rec.Ipv4Addr)
		d.Set("type", "A")
		fqdn := strings.Split(rec.Name, ".")
		d.Set("name", fqdn[0])
		d.Set("domain", strings.Join(fqdn[1:], "."))
		d.Set("ttl", rec.Ttl)

	case "AAAA":
		rec, err := client.GetRecordAAAA(d.Id(), nil)
		if err != nil {
			return fmt.Errorf("Couldn't find Infoblox AAAA record: %s", err)
		}
		d.Set("value", rec.Ipv6Addr)
		d.Set("type", "AAAA")
		fqdn := strings.Split(rec.Name, ".")
		d.Set("name", fqdn[0])
		d.Set("domain", strings.Join(fqdn[1:], "."))
		d.Set("ttl", rec.Ttl)

	case "CNAME":
		rec, err := client.GetRecordCname(d.Id(), nil)
		if err != nil {
			return fmt.Errorf("Couldn't find Infoblox CNAME record: %s", err)
		}
		d.Set("value", rec.Canonical)
		d.Set("type", "CNAME")
		fqdn := strings.Split(rec.Name, ".")
		d.Set("name", fqdn[0])
		d.Set("domain", strings.Join(fqdn[1:], "."))
		d.Set("ttl", rec.Ttl)
	default:
		return fmt.Errorf("resourceInfobloxRecordRead: unknown type")
	}

	return nil
}

func resourceInfobloxRecordUpdate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*infoblox.Client)
	var recID string
	var err, updateErr error
	switch strings.ToUpper(d.Get("type").(string)) {
	case "A":
		_, err = client.GetRecordA(d.Id(), nil)
	case "AAAA":
		_, err = client.GetRecordAAAA(d.Id(), nil)
	case "CNAME":
		_, err = client.GetRecordCname(d.Id(), nil)
	default:
		return fmt.Errorf("resourceInfobloxRecordUpdate: unknown type")
	}

	if err != nil {
		return fmt.Errorf("Couldn't find Infoblox record: %s", err)
	}

	record := url.Values{}
	if err := getAll(d, record); err != nil {
		return err
	}

	log.Printf("[DEBUG] Infoblox Record update configuration: %#v", record)

	switch strings.ToUpper(d.Get("type").(string)) {
	case "A":
		opts := &infoblox.Options{
			ReturnFields: []string{"ttl", "ipv4addr", "name"},
		}
		recID, updateErr = client.RecordAObject(d.Id()).Update(record, opts, nil)
	case "AAAA":
		opts := &infoblox.Options{
			ReturnFields: []string{"ttl", "ipv6addr", "name"},
		}
		recID, updateErr = client.RecordAAAAObject(d.Id()).Update(record, opts, nil)
	case "CNAME":
		opts := &infoblox.Options{
			ReturnFields: []string{"ttl", "canonical", "name"},
		}
		recID, updateErr = client.RecordCnameObject(d.Id()).Update(record, opts, nil)
	default:
		return fmt.Errorf("resourceInfobloxRecordUpdate: unknown type")
	}

	if updateErr != nil {
		return fmt.Errorf("Failed to update Infblox Record: %s", err)
	}

	d.SetId(recID)

	return resourceInfobloxRecordRead(d, meta)
}

func resourceInfobloxRecordDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*infoblox.Client)

	log.Printf("[INFO] Deleting Infoblox Record: %s, %s", d.Get("name").(string), d.Id())
	switch strings.ToUpper(d.Get("type").(string)) {
	case "A":
		_, err := client.GetRecordA(d.Id(), nil)
		if err != nil {
			return fmt.Errorf("Couldn't find Infoblox A record: %s", err)
		}

		deleteErr := client.RecordAObject(d.Id()).Delete(nil)
		if deleteErr != nil {
			return fmt.Errorf("Error deleting Infoblox A Record: %s", err)
		}
	case "AAAA":
		_, err := client.GetRecordAAAA(d.Id(), nil)
		if err != nil {
			return fmt.Errorf("Couldn't find Infoblox AAAA record: %s", err)
		}

		deleteErr := client.RecordAAAAObject(d.Id()).Delete(nil)
		if deleteErr != nil {
			return fmt.Errorf("Error deleting Infoblox AAAA Record: %s", err)
		}
	case "CNAME":
		_, err := client.GetRecordCname(d.Id(), nil)
		if err != nil {
			return fmt.Errorf("Couldn't find Infoblox CNAME record: %s", err)
		}

		deleteErr := client.RecordCnameObject(d.Id()).Delete(nil)
		if deleteErr != nil {
			return fmt.Errorf("Error deleting Infoblox CNAME Record: %s", err)
		}
	default:
		return fmt.Errorf("resourceInfobloxRecordDelete: unknown type")
	}
	return nil
}

func getAll(d *schema.ResourceData, record url.Values) error {
	if attr, ok := d.GetOk("name"); ok {
		record.Set("name", attr.(string))
	}

	if attr, ok := d.GetOk("domain"); ok {
		record.Set("name", strings.Join([]string{record.Get("name"), attr.(string)}, "."))
	}

	if attr, ok := d.GetOk("ttl"); ok {
		record.Set("ttl", attr.(string))
	}

	var value string
	if attr, ok := d.GetOk("value"); ok {
		value = attr.(string)
	}

	switch strings.ToUpper(d.Get("type").(string)) {
	case "A":
		record.Set("ipv4addr", value)
	case "AAAA":
		record.Set("ipv6addr", value)
	case "CNAME":
		record.Set("canonical", value)
	default:
		return fmt.Errorf("getAll: type not found")
	}

	return nil
}
<commit_msg>Added view parameter for creating A record<commit_after>package infoblox

import (
	"fmt"
	"log"
	"net/url"
	"strings"

	"github.com/fanatic/go-infoblox"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceInfobloxRecord() *schema.Resource {
	return &schema.Resource{
		Create: resourceInfobloxRecordCreate,
		Read:   resourceInfobloxRecordRead,
		Update: resourceInfobloxRecordUpdate,
		Delete: resourceInfobloxRecordDelete,

		Schema: map[string]*schema.Schema{
			"domain": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"value": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"type": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"ttl": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "3600",
			},
			"view": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},
		},
	}
}

func resourceInfobloxRecordCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*infoblox.Client)

	record := url.Values{}
	if err := getAll(d, record); err != nil {
		return err
	}

	log.Printf("[DEBUG] Infoblox Record create configuration: %#v", record)

	var recID string
	var err error

	switch strings.ToUpper(d.Get("type").(string)) {
	case "A":
		opts := &infoblox.Options{
			ReturnFields: []string{"ttl", "ipv4addr", "name", "view"},
		}
		recID, err = client.RecordA().Create(record, opts, nil)
	case "AAAA":
		opts := &infoblox.Options{
			ReturnFields: []string{"ttl", "ipv6addr", "name"},
		}
		recID, err = client.RecordAAAA().Create(record, opts, nil)
	case "CNAME":
		opts := &infoblox.Options{
			ReturnFields: []string{"ttl", "canonical", "name"},
		}
		recID, err = client.RecordCname().Create(record, opts, nil)
	default:
		return fmt.Errorf("resourceInfobloxRecordCreate: unknown type")
	}

	if err != nil {
		return fmt.Errorf("Failed to create Infblox Record: %s", err)
	}

	d.SetId(recID)

	log.Printf("[INFO] record ID: %s", d.Id())

	return resourceInfobloxRecordRead(d, meta)
}

func resourceInfobloxRecordRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*infoblox.Client)

	switch strings.ToUpper(d.Get("type").(string)) {
	case "A":
		rec, err := client.GetRecordA(d.Id(), nil)
		if err != nil {
			return fmt.Errorf("Couldn't find Infoblox A record: %s", err)
		}
		d.Set("value", rec.Ipv4Addr)
		d.Set("type", "A")
		fqdn := strings.Split(rec.Name, ".")
		d.Set("name", fqdn[0])
		d.Set("domain", strings.Join(fqdn[1:], "."))
		d.Set("ttl", rec.Ttl)
		d.Set("view", rec.View)

	case "AAAA":
		rec, err := client.GetRecordAAAA(d.Id(), nil)
		if err != nil {
			return fmt.Errorf("Couldn't find Infoblox AAAA record: %s", err)
		}
		d.Set("value", rec.Ipv6Addr)
		d.Set("type", "AAAA")
		fqdn := strings.Split(rec.Name, ".")
		d.Set("name", fqdn[0])
		d.Set("domain", strings.Join(fqdn[1:], "."))
		d.Set("ttl", rec.Ttl)

	case "CNAME":
		rec, err := client.GetRecordCname(d.Id(), nil)
		if err != nil {
			return fmt.Errorf("Couldn't find Infoblox CNAME record: %s", err)
		}
		d.Set("value", rec.Canonical)
		d.Set("type", "CNAME")
		fqdn := strings.Split(rec.Name, ".")
		d.Set("name", fqdn[0])
		d.Set("domain", strings.Join(fqdn[1:], "."))
		d.Set("ttl", rec.Ttl)
	default:
		return fmt.Errorf("resourceInfobloxRecordRead: unknown type")
	}

	return nil
}

func resourceInfobloxRecordUpdate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*infoblox.Client)
	var recID string
	var err, updateErr error
	switch strings.ToUpper(d.Get("type").(string)) {
	case "A":
		_, err = client.GetRecordA(d.Id(), nil)
	case "AAAA":
		_, err = client.GetRecordAAAA(d.Id(), nil)
	case "CNAME":
		_, err = client.GetRecordCname(d.Id(), nil)
	default:
		return fmt.Errorf("resourceInfobloxRecordUpdate: unknown type")
	}

	if err != nil {
		return fmt.Errorf("Couldn't find Infoblox record: %s", err)
	}

	record := url.Values{}
	if err := getAll(d, record); err != nil {
		return err
	}

	log.Printf("[DEBUG] Infoblox Record update configuration: %#v", record)

	switch strings.ToUpper(d.Get("type").(string)) {
	case "A":
		opts := &infoblox.Options{
			ReturnFields: []string{"ttl", "ipv4addr", "name", "view"},
		}
		recID, updateErr = client.RecordAObject(d.Id()).Update(record, opts, nil)
	case "AAAA":
		opts := &infoblox.Options{
			ReturnFields: []string{"ttl", "ipv6addr", "name"},
		}
		recID, updateErr = client.RecordAAAAObject(d.Id()).Update(record, opts, nil)
	case "CNAME":
		opts := &infoblox.Options{
			ReturnFields: []string{"ttl", "canonical", "name"},
		}
		recID, updateErr = client.RecordCnameObject(d.Id()).Update(record, opts, nil)
	default:
		return fmt.Errorf("resourceInfobloxRecordUpdate: unknown type")
	}

	if updateErr != nil {
		return fmt.Errorf("Failed to update Infblox Record: %s", err)
	}

	d.SetId(recID)

	return resourceInfobloxRecordRead(d, meta)
}

func resourceInfobloxRecordDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*infoblox.Client)

	log.Printf("[INFO] Deleting Infoblox Record: %s, %s", d.Get("name").(string), d.Id())
	switch strings.ToUpper(d.Get("type").(string)) {
	case "A":
		_, err := client.GetRecordA(d.Id(), nil)
		if err != nil {
			return fmt.Errorf("Couldn't find Infoblox A record: %s", err)
		}

		deleteErr := client.RecordAObject(d.Id()).Delete(nil)
		if deleteErr != nil {
			return fmt.Errorf("Error deleting Infoblox A Record: %s", err)
		}
	case "AAAA":
		_, err := client.GetRecordAAAA(d.Id(), nil)
		if err != nil {
			return fmt.Errorf("Couldn't find Infoblox AAAA record: %s", err)
		}

		deleteErr := client.RecordAAAAObject(d.Id()).Delete(nil)
		if deleteErr != nil {
			return fmt.Errorf("Error deleting Infoblox AAAA Record: %s", err)
		}
	case "CNAME":
		_, err := client.GetRecordCname(d.Id(), nil)
		if err != nil {
			return fmt.Errorf("Couldn't find Infoblox CNAME record: %s", err)
		}

		deleteErr := client.RecordCnameObject(d.Id()).Delete(nil)
		if deleteErr != nil {
			return fmt.Errorf("Error deleting Infoblox CNAME Record: %s", err)
		}
	default:
		return fmt.Errorf("resourceInfobloxRecordDelete: unknown type")
	}
	return nil
}

func getAll(d *schema.ResourceData, record url.Values) error {
	if attr, ok := d.GetOk("name"); ok {
		record.Set("name", attr.(string))
	}

	if attr, ok := d.GetOk("domain"); ok {
		record.Set("name", strings.Join([]string{record.Get("name"), attr.(string)}, "."))
	}

	if attr, ok := d.GetOk("ttl"); ok {
		record.Set("ttl", attr.(string))
	}

	if attr, ok := d.GetOk("view"); ok {
		record.Set("view", attr.(string))
	}

	var value string
	if attr, ok := d.GetOk("value"); ok {
		value = attr.(string)
	}

	switch strings.ToUpper(d.Get("type").(string)) {
	case "A":
		record.Set("ipv4addr", value)
	case "AAAA":
		record.Set("ipv6addr", value)
	case "CNAME":
		record.Set("canonical", value)
	default:
		return fmt.Errorf("getAll: type not found")
	}

	return nil
}
<|endoftext|>


<commit_before>package release

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"os/exec"
	"time"

	"github.com/ghodss/yaml"
	"github.com/go-kit/kit/log"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/helm/pkg/chartutil"
	k8shelm "k8s.io/helm/pkg/helm"
	hapi_release "k8s.io/helm/pkg/proto/hapi/release"

	"github.com/weaveworks/flux"
	fluxk8s "github.com/weaveworks/flux/cluster/kubernetes"
	flux_v1beta1 "github.com/weaveworks/flux/integrations/apis/flux.weave.works/v1beta1"
)

type Action string

const (
	InstallAction Action = "CREATE"
	UpgradeAction Action = "UPDATE"
)

// Release contains clients needed to provide functionality related to helm releases
type Release struct {
	logger     log.Logger
	HelmClient *k8shelm.Client
}

type Releaser interface {
	GetDeployedRelease(name string) (*hapi_release.Release, error)
	Install(dir string, releaseName string, fhr flux_v1beta1.HelmRelease, action Action, opts InstallOptions) (*hapi_release.Release, error)
}

type DeployInfo struct {
	Name string
}

type InstallOptions struct {
	DryRun    bool
	ReuseName bool
}

// New creates a new Release instance.
func New(logger log.Logger, helmClient *k8shelm.Client) *Release {
	r := &Release{
		logger:     logger,
		HelmClient: helmClient,
	}
	return r
}

// GetReleaseName either retrieves the release name from the Custom Resource or constructs a new one
// in the form : $Namespace-$CustomResourceName
func GetReleaseName(fhr flux_v1beta1.HelmRelease) string {
	namespace := fhr.Namespace
	if namespace == "" {
		namespace = "default"
	}
	releaseName := fhr.Spec.ReleaseName
	if releaseName == "" {
		releaseName = fmt.Sprintf("%s-%s", namespace, fhr.Name)
	}

	return releaseName
}

// GetDeployedRelease returns a release with Deployed status
func (r *Release) GetDeployedRelease(name string) (*hapi_release.Release, error) {
	rls, err := r.HelmClient.ReleaseContent(name)
	if err != nil {
		return nil, err
	}
	if rls.Release.Info.Status.GetCode() == hapi_release.Status_DEPLOYED {
		return rls.GetRelease(), nil
	}
	return nil, nil
}

func (r *Release) canDelete(name string) (bool, error) {
	rls, err := r.HelmClient.ReleaseStatus(name)

	if err != nil {
		r.logger.Log("error", fmt.Sprintf("Error finding status for release (%s): %#v", name, err))
		return false, err
	}
	/*
		"UNKNOWN":          0,
		"DEPLOYED":         1,
		"DELETED":          2,
		"SUPERSEDED":       3,
		"FAILED":           4,
		"DELETING":         5,
		"PENDING_INSTALL":  6,
		"PENDING_UPGRADE":  7,
		"PENDING_ROLLBACK": 8,
	*/
	status := rls.GetInfo().GetStatus()
	switch status.Code {
	case 1, 4:
		r.logger.Log("info", fmt.Sprintf("Deleting release %s", name))
		return true, nil
	case 2:
		r.logger.Log("info", fmt.Sprintf("Release %s already deleted", name))
		return false, nil
	default:
		r.logger.Log("info", fmt.Sprintf("Release %s with status %s cannot be deleted", name, status.Code.String()))
		return false, fmt.Errorf("release %s with status %s cannot be deleted", name, status.Code.String())
	}
}

// Install performs a Chart release given the directory containing the
// charts, and the HelmRelease specifying the release. Depending
// on the release type, this is either a new release, or an upgrade of
// an existing one.
//
// TODO(michael): cloneDir is only relevant if installing from git;
// either split this procedure into two varieties, or make it more
// general and calculate the path to the chart in the caller.
func (r *Release) Install(chartPath, releaseName string, fhr flux_v1beta1.HelmRelease, action Action, opts InstallOptions, kubeClient *kubernetes.Clientset) (*hapi_release.Release, error) {
	if chartPath == "" {
		return nil, fmt.Errorf("empty path to chart supplied for resource %q", fhr.ResourceID().String())
	}
	_, err := os.Stat(chartPath)
	switch {
	case os.IsNotExist(err):
		return nil, fmt.Errorf("no file or dir at path to chart: %s", chartPath)
	case err != nil:
		return nil, fmt.Errorf("error statting path given for chart %s: %s", chartPath, err.Error())
	}

	r.logger.Log("info", fmt.Sprintf("processing release %s (as %s)", fhr.Spec.ReleaseName, releaseName),
		"action", fmt.Sprintf("%v", action),
		"options", fmt.Sprintf("%+v", opts),
		"timeout", fmt.Sprintf("%vs", fhr.GetTimeout()))

	// Read values from given valueFile paths (configmaps, etc.)
	mergedValues := chartutil.Values{}
	for _, valueFileSecret := range fhr.Spec.ValueFileSecrets {
		// Read the contents of the secret
		secret, err := kubeClient.CoreV1().Secrets(fhr.Namespace).Get(valueFileSecret.Name, v1.GetOptions{})
		if err != nil {
			r.logger.Log("error", fmt.Sprintf("Cannot get secret %s for Chart release [%s]: %#v", valueFileSecret.Name, fhr.Spec.ReleaseName, err))
			return nil, err
		}

		// Load values.yaml file and merge
		var values chartutil.Values
		err = yaml.Unmarshal(secret.Data["values.yaml"], &values)
		if err != nil {
			r.logger.Log("error", fmt.Sprintf("Cannot yaml.Unmashal values.yaml in secret %s for Chart release [%s]: %#v", valueFileSecret.Name, fhr.Spec.ReleaseName, err))
			return nil, err
		}
		mergedValues = mergeValues(mergedValues, values)
	}
	// Merge in values after valueFiles
	mergedValues = mergeValues(mergedValues, fhr.Spec.Values)

	strVals, err := mergedValues.YAML()
	if err != nil {
		r.logger.Log("error", fmt.Sprintf("Problem with supplied customizations for Chart release [%s]: %#v", fhr.Spec.ReleaseName, err))
		return nil, err
	}
	rawVals := []byte(strVals)

	switch action {
	case InstallAction:
		res, err := r.HelmClient.InstallRelease(
			chartPath,
			fhr.GetNamespace(),
			k8shelm.ValueOverrides(rawVals),
			k8shelm.ReleaseName(releaseName),
			k8shelm.InstallDryRun(opts.DryRun),
			k8shelm.InstallReuseName(opts.ReuseName),
			k8shelm.InstallTimeout(fhr.GetTimeout()),
		)

		if err != nil {
			r.logger.Log("error", fmt.Sprintf("Chart release failed: %s: %#v", fhr.Spec.ReleaseName, err))
			// purge the release if the install failed but only if this is the first revision
			history, err := r.HelmClient.ReleaseHistory(releaseName, k8shelm.WithMaxHistory(2))
			if err == nil && len(history.Releases) == 1 && history.Releases[0].Info.Status.Code == hapi_release.Status_FAILED {
				r.logger.Log("info", fmt.Sprintf("Deleting failed release: [%s]", fhr.Spec.ReleaseName))
				_, err = r.HelmClient.DeleteRelease(releaseName, k8shelm.DeletePurge(true))
				if err != nil {
					r.logger.Log("error", fmt.Sprintf("Release deletion error: %#v", err))
					return nil, err
				}
			}
			return nil, err
		}
		if !opts.DryRun {
			err = r.annotateResources(res.Release, fhr)
		}
		return res.Release, err
	case UpgradeAction:
		res, err := r.HelmClient.UpdateRelease(
			releaseName,
			chartPath,
			k8shelm.UpdateValueOverrides(rawVals),
			k8shelm.UpgradeDryRun(opts.DryRun),
			k8shelm.UpgradeTimeout(fhr.GetTimeout()),
			k8shelm.ResetValues(fhr.Spec.ResetValues),
		)

		if err != nil {
			r.logger.Log("error", fmt.Sprintf("Chart upgrade release failed: %s: %#v", fhr.Spec.ReleaseName, err))
			return nil, err
		}
		if !opts.DryRun {
			err = r.annotateResources(res.Release, fhr)
		}
		return res.Release, err
	default:
		err = fmt.Errorf("Valid install options: CREATE, UPDATE. Provided: %s", action)
		r.logger.Log("error", err.Error())
		return nil, err
	}
}

// Delete purges a Chart release
func (r *Release) Delete(name string) error {
	ok, err := r.canDelete(name)
	if !ok {
		if err != nil {
			return err
		}
		return nil
	}

	_, err = r.HelmClient.DeleteRelease(name, k8shelm.DeletePurge(true))
	if err != nil {
		r.logger.Log("error", fmt.Sprintf("Release deletion error: %#v", err))
		return err
	}
	r.logger.Log("info", fmt.Sprintf("Release deleted: [%s]", name))
	return nil
}

// annotateResources annotates each of the resources created (or updated)
// by the release so that we can spot them.
func (r *Release) annotateResources(release *hapi_release.Release, fhr flux_v1beta1.HelmRelease) error {
	args := []string{"annotate", "--overwrite"}
	args = append(args, "--namespace", release.Namespace)
	args = append(args, "-f", "-")
	args = append(args, fluxk8s.AntecedentAnnotation+"="+fhrResourceID(fhr).String())

	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
	defer cancel()
	cmd := exec.CommandContext(ctx, "kubectl", args...)
	cmd.Stdin = bytes.NewBufferString(release.Manifest)

	output, err := cmd.CombinedOutput()
	if err != nil {
		r.logger.Log("output", string(output), "err", err)
	}
	return err
}

// fhrResourceID constructs a flux.ResourceID for a HelmRelease resource.
func fhrResourceID(fhr flux_v1beta1.HelmRelease) flux.ResourceID {
	return flux.MakeResourceID(fhr.Namespace, "HelmRelease", fhr.Name)
}

// Merges source and destination `chartutils.Values`, preferring values from the source Values
// This is slightly adapted from https://github.com/helm/helm/blob/master/cmd/helm/install.go#L329
func mergeValues(dest, src chartutil.Values) chartutil.Values {
	for k, v := range src {
		// If the key doesn't exist already, then just set the key to that value
		if _, exists := dest[k]; !exists {
			dest[k] = v
			continue
		}
		nextMap, ok := v.(map[string]interface{})
		// If it isn't another map, overwrite the value
		if !ok {
			dest[k] = v
			continue
		}
		// Edge case: If the key exists in the destination, but isn't a map
		destMap, isMap := dest[k].(map[string]interface{})
		// If the source map has a map for this key, prefer it
		if !isMap {
			dest[k] = v
			continue
		}
		// If we got to this point, it is a map in both, so merge them
		dest[k] = mergeValues(destMap, nextMap)
	}
	return dest
}
<commit_msg>Annotate manifests outside of release NS<commit_after>package release

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"time"

	"github.com/ghodss/yaml"
	"github.com/go-kit/kit/log"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/client-go/kubernetes"
	"k8s.io/helm/pkg/chartutil"
	k8shelm "k8s.io/helm/pkg/helm"
	hapi_release "k8s.io/helm/pkg/proto/hapi/release"

	"github.com/weaveworks/flux"
	fluxk8s "github.com/weaveworks/flux/cluster/kubernetes"
	flux_v1beta1 "github.com/weaveworks/flux/integrations/apis/flux.weave.works/v1beta1"
	helmutil "k8s.io/helm/pkg/releaseutil"
)

type Action string

const (
	InstallAction Action = "CREATE"
	UpgradeAction Action = "UPDATE"
)

// Release contains clients needed to provide functionality related to helm releases
type Release struct {
	logger     log.Logger
	HelmClient *k8shelm.Client
}

type Releaser interface {
	GetDeployedRelease(name string) (*hapi_release.Release, error)
	Install(dir string, releaseName string, fhr flux_v1beta1.HelmRelease, action Action, opts InstallOptions) (*hapi_release.Release, error)
}

type DeployInfo struct {
	Name string
}

type InstallOptions struct {
	DryRun    bool
	ReuseName bool
}

// New creates a new Release instance.
func New(logger log.Logger, helmClient *k8shelm.Client) *Release {
	r := &Release{
		logger:     logger,
		HelmClient: helmClient,
	}
	return r
}

// GetReleaseName either retrieves the release name from the Custom Resource or constructs a new one
// in the form : $Namespace-$CustomResourceName
func GetReleaseName(fhr flux_v1beta1.HelmRelease) string {
	namespace := fhr.Namespace
	if namespace == "" {
		namespace = "default"
	}
	releaseName := fhr.Spec.ReleaseName
	if releaseName == "" {
		releaseName = fmt.Sprintf("%s-%s", namespace, fhr.Name)
	}

	return releaseName
}

// GetDeployedRelease returns a release with Deployed status
func (r *Release) GetDeployedRelease(name string) (*hapi_release.Release, error) {
	rls, err := r.HelmClient.ReleaseContent(name)
	if err != nil {
		return nil, err
	}
	if rls.Release.Info.Status.GetCode() == hapi_release.Status_DEPLOYED {
		return rls.GetRelease(), nil
	}
	return nil, nil
}

func (r *Release) canDelete(name string) (bool, error) {
	rls, err := r.HelmClient.ReleaseStatus(name)

	if err != nil {
		r.logger.Log("error", fmt.Sprintf("Error finding status for release (%s): %#v", name, err))
		return false, err
	}
	/*
		"UNKNOWN":          0,
		"DEPLOYED":         1,
		"DELETED":          2,
		"SUPERSEDED":       3,
		"FAILED":           4,
		"DELETING":         5,
		"PENDING_INSTALL":  6,
		"PENDING_UPGRADE":  7,
		"PENDING_ROLLBACK": 8,
	*/
	status := rls.GetInfo().GetStatus()
	switch status.Code {
	case 1, 4:
		r.logger.Log("info", fmt.Sprintf("Deleting release %s", name))
		return true, nil
	case 2:
		r.logger.Log("info", fmt.Sprintf("Release %s already deleted", name))
		return false, nil
	default:
		r.logger.Log("info", fmt.Sprintf("Release %s with status %s cannot be deleted", name, status.Code.String()))
		return false, fmt.Errorf("release %s with status %s cannot be deleted", name, status.Code.String())
	}
}

// Install performs a Chart release given the directory containing the
// charts, and the HelmRelease specifying the release. Depending
// on the release type, this is either a new release, or an upgrade of
// an existing one.
//
// TODO(michael): cloneDir is only relevant if installing from git;
// either split this procedure into two varieties, or make it more
// general and calculate the path to the chart in the caller.
func (r *Release) Install(chartPath, releaseName string, fhr flux_v1beta1.HelmRelease, action Action, opts InstallOptions, kubeClient *kubernetes.Clientset) (*hapi_release.Release, error) {
	if chartPath == "" {
		return nil, fmt.Errorf("empty path to chart supplied for resource %q", fhr.ResourceID().String())
	}
	_, err := os.Stat(chartPath)
	switch {
	case os.IsNotExist(err):
		return nil, fmt.Errorf("no file or dir at path to chart: %s", chartPath)
	case err != nil:
		return nil, fmt.Errorf("error statting path given for chart %s: %s", chartPath, err.Error())
	}

	r.logger.Log("info", fmt.Sprintf("processing release %s (as %s)", fhr.Spec.ReleaseName, releaseName),
		"action", fmt.Sprintf("%v", action),
		"options", fmt.Sprintf("%+v", opts),
		"timeout", fmt.Sprintf("%vs", fhr.GetTimeout()))

	// Read values from given valueFile paths (configmaps, etc.)
	mergedValues := chartutil.Values{}
	for _, valueFileSecret := range fhr.Spec.ValueFileSecrets {
		// Read the contents of the secret
		secret, err := kubeClient.CoreV1().Secrets(fhr.Namespace).Get(valueFileSecret.Name, v1.GetOptions{})
		if err != nil {
			r.logger.Log("error", fmt.Sprintf("Cannot get secret %s for Chart release [%s]: %#v", valueFileSecret.Name, fhr.Spec.ReleaseName, err))
			return nil, err
		}

		// Load values.yaml file and merge
		var values chartutil.Values
		err = yaml.Unmarshal(secret.Data["values.yaml"], &values)
		if err != nil {
			r.logger.Log("error", fmt.Sprintf("Cannot yaml.Unmashal values.yaml in secret %s for Chart release [%s]: %#v", valueFileSecret.Name, fhr.Spec.ReleaseName, err))
			return nil, err
		}
		mergedValues = mergeValues(mergedValues, values)
	}
	// Merge in values after valueFiles
	mergedValues = mergeValues(mergedValues, fhr.Spec.Values)

	strVals, err := mergedValues.YAML()
	if err != nil {
		r.logger.Log("error", fmt.Sprintf("Problem with supplied customizations for Chart release [%s]: %#v", fhr.Spec.ReleaseName, err))
		return nil, err
	}
	rawVals := []byte(strVals)

	switch action {
	case InstallAction:
		res, err := r.HelmClient.InstallRelease(
			chartPath,
			fhr.GetNamespace(),
			k8shelm.ValueOverrides(rawVals),
			k8shelm.ReleaseName(releaseName),
			k8shelm.InstallDryRun(opts.DryRun),
			k8shelm.InstallReuseName(opts.ReuseName),
			k8shelm.InstallTimeout(fhr.GetTimeout()),
		)

		if err != nil {
			r.logger.Log("error", fmt.Sprintf("Chart release failed: %s: %#v", fhr.Spec.ReleaseName, err))
			// purge the release if the install failed but only if this is the first revision
			history, err := r.HelmClient.ReleaseHistory(releaseName, k8shelm.WithMaxHistory(2))
			if err == nil && len(history.Releases) == 1 && history.Releases[0].Info.Status.Code == hapi_release.Status_FAILED {
				r.logger.Log("info", fmt.Sprintf("Deleting failed release: [%s]", fhr.Spec.ReleaseName))
				_, err = r.HelmClient.DeleteRelease(releaseName, k8shelm.DeletePurge(true))
				if err != nil {
					r.logger.Log("error", fmt.Sprintf("Release deletion error: %#v", err))
					return nil, err
				}
			}
			return nil, err
		}
		if !opts.DryRun {
			r.annotateResources(res.Release, fhr)
		}
		return res.Release, err
	case UpgradeAction:
		res, err := r.HelmClient.UpdateRelease(
			releaseName,
			chartPath,
			k8shelm.UpdateValueOverrides(rawVals),
			k8shelm.UpgradeDryRun(opts.DryRun),
			k8shelm.UpgradeTimeout(fhr.GetTimeout()),
			k8shelm.ResetValues(fhr.Spec.ResetValues),
		)

		if err != nil {
			r.logger.Log("error", fmt.Sprintf("Chart upgrade release failed: %s: %#v", fhr.Spec.ReleaseName, err))
			return nil, err
		}
		if !opts.DryRun {
			r.annotateResources(res.Release, fhr)
		}
		return res.Release, err
	default:
		err = fmt.Errorf("Valid install options: CREATE, UPDATE. Provided: %s", action)
		r.logger.Log("error", err.Error())
		return nil, err
	}
}

// Delete purges a Chart release
func (r *Release) Delete(name string) error {
	ok, err := r.canDelete(name)
	if !ok {
		if err != nil {
			return err
		}
		return nil
	}

	_, err = r.HelmClient.DeleteRelease(name, k8shelm.DeletePurge(true))
	if err != nil {
		r.logger.Log("error", fmt.Sprintf("Release deletion error: %#v", err))
		return err
	}
	r.logger.Log("info", fmt.Sprintf("Release deleted: [%s]", name))
	return nil
}

// annotateResources annotates each of the resources created (or updated)
// by the release so that we can spot them.
func (r *Release) annotateResources(release *hapi_release.Release, fhr flux_v1beta1.HelmRelease) {
	objs := releaseManifestToUnstructured(release.Manifest, r.logger)
	for namespace, res := range namespacedResourceMap(objs, release.Namespace) {
		args := []string{"annotate", "--overwrite"}
		args = append(args, "--namespace", namespace)
		args = append(args, res...)
		args = append(args, fluxk8s.AntecedentAnnotation+"="+fhrResourceID(fhr).String())

		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()

		cmd := exec.CommandContext(ctx, "kubectl", args...)
		output, err := cmd.CombinedOutput()
		if err != nil {
			r.logger.Log("output", string(output), "err", err)
		}
	}
}

// fhrResourceID constructs a flux.ResourceID for a HelmRelease resource.
func fhrResourceID(fhr flux_v1beta1.HelmRelease) flux.ResourceID {
	return flux.MakeResourceID(fhr.Namespace, "HelmRelease", fhr.Name)
}

// Merges source and destination `chartutils.Values`, preferring values from the source Values
// This is slightly adapted from https://github.com/helm/helm/blob/master/cmd/helm/install.go#L329
func mergeValues(dest, src chartutil.Values) chartutil.Values {
	for k, v := range src {
		// If the key doesn't exist already, then just set the key to that value
		if _, exists := dest[k]; !exists {
			dest[k] = v
			continue
		}
		nextMap, ok := v.(map[string]interface{})
		// If it isn't another map, overwrite the value
		if !ok {
			dest[k] = v
			continue
		}
		// Edge case: If the key exists in the destination, but isn't a map
		destMap, isMap := dest[k].(map[string]interface{})
		// If the source map has a map for this key, prefer it
		if !isMap {
			dest[k] = v
			continue
		}
		// If we got to this point, it is a map in both, so merge them
		dest[k] = mergeValues(destMap, nextMap)
	}
	return dest
}

// releaseManifestToUnstructured turns a string containing YAML
// manifests into an array of Unstructured objects.
func releaseManifestToUnstructured(manifest string, logger log.Logger) []unstructured.Unstructured {
	manifests := helmutil.SplitManifests(manifest)
	var objs []unstructured.Unstructured
	for _, manifest := range manifests {
		bytes, err := yaml.YAMLToJSON([]byte(manifest))
		if err != nil {
			logger.Log("err", err)
			continue
		}

		var u unstructured.Unstructured
		if err := u.UnmarshalJSON(bytes); err != nil {
			logger.Log("err", err)
			continue
		}

		// Helm charts may include list kinds, we are only interested in
		// the items on those lists.
		if u.IsList() {
			l, err := u.ToList()
			if err != nil {
				logger.Log("err", err)
				continue
			}
			objs = append(objs, l.Items...)
			continue
		}

		objs = append(objs, u)
	}
	return objs
}

// namespacedResourceMap iterates over the given objects and maps the
// resource identifier against the namespace from the object, if no
// namespace is present (either because the object kind has no namespace
// or it belongs to the release namespace) it gets mapped against the
// given release namespace.
func namespacedResourceMap(objs []unstructured.Unstructured, releaseNamespace string) map[string][]string {
	resources := make(map[string][]string)
	for _, obj := range objs {
		namespace := obj.GetNamespace()
		if namespace == "" {
			namespace = releaseNamespace
		}
		resource := obj.GetKind() + "/" + obj.GetName()
		resources[namespace] = append(resources[namespace], resource)
	}
	return resources
}
<|endoftext|>


<commit_before>package self_test

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"time"

	"github.com/lucas-clemente/quic-go/h2quic"
	"github.com/lucas-clemente/quic-go/integrationtests/tools/testserver"
	"github.com/lucas-clemente/quic-go/internal/protocol"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
)

var _ = Describe("Client tests", func() {
	var client *http.Client
	supportedVersions := append([]protocol.VersionNumber{}, protocol.SupportedVersions...)

	BeforeEach(func() {
		err := os.Setenv("HOSTALIASES", "quic.clemente.io 127.0.0.1")
		Expect(err).ToNot(HaveOccurred())
		addr, err := net.ResolveUDPAddr("udp4", "quic.clemente.io:0")
		Expect(err).ToNot(HaveOccurred())
		if addr.String() != "127.0.0.1:0" {
			Fail("quic.clemente.io does not resolve to 127.0.0.1. Consider adding it to /etc/hosts.")
		}
		client = &http.Client{
			Transport: &h2quic.RoundTripper{},
		}
		testserver.StartQuicServer()
	})

	AfterEach(func() {
		testserver.StopQuicServer()
		protocol.SupportedVersions = supportedVersions
	})

	for _, v := range supportedVersions {
		version := v

		Context(fmt.Sprintf("with quic version %d", version), func() {
			BeforeEach(func() {
				protocol.SupportedVersions = []protocol.VersionNumber{version}
			})

			It("downloads a hello", func() {
				resp, err := client.Get("https://quic.clemente.io:" + testserver.Port() + "/hello")
				Expect(err).ToNot(HaveOccurred())
				Expect(resp.StatusCode).To(Equal(200))
				body, err := ioutil.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second))
				Expect(err).ToNot(HaveOccurred())
				Expect(string(body)).To(Equal("Hello, World!\n"))
			})

			It("downloads a small file", func() {
				resp, err := client.Get("https://quic.clemente.io:" + testserver.Port() + "/prdata")
				Expect(err).ToNot(HaveOccurred())
				Expect(resp.StatusCode).To(Equal(200))
				body, err := ioutil.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second))
				Expect(err).ToNot(HaveOccurred())
				Expect(body).To(Equal(testserver.PRData))
			})

			It("downloads a large file", func() {
				resp, err := client.Get("https://quic.clemente.io:" + testserver.Port() + "/prdatalong")
				Expect(err).ToNot(HaveOccurred())
				Expect(resp.StatusCode).To(Equal(200))
				body, err := ioutil.ReadAll(gbytes.TimeoutReader(resp.Body, 20*time.Second))
				Expect(err).ToNot(HaveOccurred())
				Expect(body).To(Equal(testserver.PRDataLong))
			})

			It("uploads a file", func() {
				resp, err := client.Post(
					"https://quic.clemente.io:"+testserver.Port()+"/echo",
					"text/plain",
					bytes.NewReader(testserver.PRData),
				)
				Expect(err).ToNot(HaveOccurred())
				Expect(resp.StatusCode).To(Equal(200))
				body, err := ioutil.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second))
				Expect(err).ToNot(HaveOccurred())
				Expect(bytes.Equal(body, testserver.PRData)).To(BeTrue())
			})
		})
	}
})
<commit_msg>use the quic.Config to set the QUIC version in client integration tests<commit_after>package self_test

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"time"

	quic "github.com/lucas-clemente/quic-go"
	"github.com/lucas-clemente/quic-go/h2quic"
	"github.com/lucas-clemente/quic-go/integrationtests/tools/testserver"
	"github.com/lucas-clemente/quic-go/internal/protocol"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
)

var _ = Describe("Client tests", func() {
	var client *http.Client

	BeforeEach(func() {
		err := os.Setenv("HOSTALIASES", "quic.clemente.io 127.0.0.1")
		Expect(err).ToNot(HaveOccurred())
		addr, err := net.ResolveUDPAddr("udp4", "quic.clemente.io:0")
		Expect(err).ToNot(HaveOccurred())
		if addr.String() != "127.0.0.1:0" {
			Fail("quic.clemente.io does not resolve to 127.0.0.1. Consider adding it to /etc/hosts.")
		}
		testserver.StartQuicServer()
	})

	AfterEach(func() {
		testserver.StopQuicServer()
	})

	for _, v := range protocol.SupportedVersions {
		version := v

		Context(fmt.Sprintf("with quic version %d", version), func() {
			BeforeEach(func() {
				client = &http.Client{
					Transport: &h2quic.RoundTripper{
						QuicConfig: &quic.Config{
							Versions: []protocol.VersionNumber{version},
						},
					},
				}
			})

			It("downloads a hello", func() {
				resp, err := client.Get("https://quic.clemente.io:" + testserver.Port() + "/hello")
				Expect(err).ToNot(HaveOccurred())
				Expect(resp.StatusCode).To(Equal(200))
				body, err := ioutil.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second))
				Expect(err).ToNot(HaveOccurred())
				Expect(string(body)).To(Equal("Hello, World!\n"))
			})

			It("downloads a small file", func() {
				resp, err := client.Get("https://quic.clemente.io:" + testserver.Port() + "/prdata")
				Expect(err).ToNot(HaveOccurred())
				Expect(resp.StatusCode).To(Equal(200))
				body, err := ioutil.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second))
				Expect(err).ToNot(HaveOccurred())
				Expect(body).To(Equal(testserver.PRData))
			})

			It("downloads a large file", func() {
				resp, err := client.Get("https://quic.clemente.io:" + testserver.Port() + "/prdatalong")
				Expect(err).ToNot(HaveOccurred())
				Expect(resp.StatusCode).To(Equal(200))
				body, err := ioutil.ReadAll(gbytes.TimeoutReader(resp.Body, 20*time.Second))
				Expect(err).ToNot(HaveOccurred())
				Expect(body).To(Equal(testserver.PRDataLong))
			})

			It("uploads a file", func() {
				resp, err := client.Post(
					"https://quic.clemente.io:"+testserver.Port()+"/echo",
					"text/plain",
					bytes.NewReader(testserver.PRData),
				)
				Expect(err).ToNot(HaveOccurred())
				Expect(resp.StatusCode).To(Equal(200))
				body, err := ioutil.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second))
				Expect(err).ToNot(HaveOccurred())
				Expect(bytes.Equal(body, testserver.PRData)).To(BeTrue())
			})
		})
	}
})
<|endoftext|>