<commit_before>package cli

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"path"
	"strings"
)

var (
	dcosAuthToken      string
	dcosUrl            string
	serviceName        string
	tlsAllowUnverified bool
	tlsCACertPath      string
)

func HTTPGet(urlPath string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPRequest("GET", urlPath)))
}
func HTTPGetQuery(urlPath, urlQuery string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPQueryRequest("GET", urlPath, urlQuery)))
}
func HTTPGetData(urlPath, payload, contentType string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPDataRequest("GET", urlPath, payload, contentType)))
}
func HTTPGetJSON(urlPath, jsonPayload string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPJSONRequest("GET", urlPath, jsonPayload)))
}

func HTTPDelete(urlPath string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPRequest("DELETE", urlPath)))
}
func HTTPDeleteQuery(urlPath, urlQuery string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPQueryRequest("DELETE", urlPath, urlQuery)))
}
func HTTPDeleteData(urlPath, payload, contentType string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPDataRequest("DELETE", urlPath, payload, contentType)))
}
func HTTPDeleteJSON(urlPath, jsonPayload string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPJSONRequest("DELETE", urlPath, jsonPayload)))
}

func HTTPPost(urlPath string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPRequest("POST", urlPath)))
}
func HTTPPostQuery(urlPath, urlQuery string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPQueryRequest("POST", urlPath, urlQuery)))
}
func HTTPPostData(urlPath, payload, contentType string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPDataRequest("POST", urlPath, payload, contentType)))
}
func HTTPPostJSON(urlPath, jsonPayload string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPJSONRequest("POST", urlPath, jsonPayload)))
}

func HTTPPut(urlPath string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPRequest("PUT", urlPath)))
}
func HTTPPutQuery(urlPath, urlQuery string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPQueryRequest("PUT", urlPath, urlQuery)))
}
func HTTPPutData(urlPath, payload, contentType string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPDataRequest("PUT", urlPath, payload, contentType)))
}
func HTTPPutJSON(urlPath, jsonPayload string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPJSONRequest("PUT", urlPath, jsonPayload)))
}

func HTTPQuery(request *http.Request) *http.Response {
	// get CA settings from CLI (local flags override CLI):
	cliVerifySetting := OptionalCLIConfigValue("core.ssl_verify")
	if strings.EqualFold(cliVerifySetting, "false") {
		// 'false': disable cert validation
		tlsAllowUnverified = true
	} else if strings.EqualFold(cliVerifySetting, "true") {
		// 'true': require validation against default CAs
		// (leave tlsAllowUnverified alone: already defaults to false)
	} else if len(cliVerifySetting) != 0 {
		// '<other string>': path to local/custom cert file
		if len(tlsCACertPath) == 0 {
			tlsCACertPath = cliVerifySetting
		}
	} else {
		// this shouldn't happen: 'auth login' requires a non-empty setting.
		// play it safe and leave cert verification enabled by default.
	}

	// allow unverified certs if user manually set the flag, or if it's configured that way in CLI:
	tlsConfig := &tls.Config{InsecureSkipVerify: tlsAllowUnverified}

	// import custom cert if user manually set the flag, or if it's configured in CLI:
	if len(tlsCACertPath) != 0 {
		// include custom CA cert as verified
		cert, err := ioutil.ReadFile(tlsCACertPath)
		if err != nil {
			log.Fatalf("Unable to read from CA certificate file %s: %s", tlsCACertPath, err)
		}
		certPool := x509.NewCertPool()
		certPool.AppendCertsFromPEM(cert)
		tlsConfig.RootCAs = certPool
	}

	client := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}
	var err interface{}
	response, err := client.Do(request)
	switch err.(type) {
	case *url.Error:
		// extract wrapped error
		err = err.(*url.Error).Err
	}
	if err != nil {
		switch err.(type) {
		case x509.UnknownAuthorityError:
			// custom suggestions for a certificate error:
			log.Printf("HTTP %s Query for %s failed: %s", request.Method, request.URL, err)
			log.Printf("- Is someone intercepting the connection to steal your credentials?")
			log.Printf("- Is the cluster CA certificate configured correctly? Check 'dcos config show core.ssl_verify'.")
			log.Fatalf("- To ignore the unvalidated certificate and force your command (INSECURE), use --force-insecure")
		default:
			log.Printf("HTTP %s Query for %s failed: %s", request.Method, request.URL, err)
			log.Printf("- Is 'core.dcos_url' set correctly? Check 'dcos config show core.dcos_url'.")
			log.Fatalf("- Is 'core.dcos_acs_token' set correctly? Run 'dcos auth login' to log in.")
		}
	}
	if response.StatusCode == 401 {
		log.Printf("Got 401 Unauthorized response from %s", request.URL)
		log.Fatalf("- Bad auth token? Run 'dcos auth login' to log in.")
	}
	if Verbose {
		log.Printf("Response: %s (%d bytes)", response.Status, response.ContentLength)
	}
	return response
}

func CheckHTTPResponse(response *http.Response) *http.Response {
	switch {
	case response.StatusCode == 500:
		log.Printf("HTTP %s Query for %s failed: %s",
			response.Request.Method, response.Request.URL, response.Status)
		log.Printf("- Did you provide the correct service name? Currently using '%s', specify a different name with '--name=<name>'.", serviceName)
		log.Fatalf("- Was the service recently installed? It may still be initializing, Wait a bit and try again.")
	case response.StatusCode < 200 || response.StatusCode >= 300:
		log.Fatalf("HTTP %s Query for %s failed: %s",
			response.Request.Method, response.Request.URL, response.Status)
	}
	return response
}

func CreateHTTPJSONRequest(method, urlPath, jsonPayload string) *http.Request {
	return CreateHTTPDataRequest(method, urlPath, jsonPayload, "application/json")
}

func CreateHTTPDataRequest(method, urlPath, jsonPayload, contentType string) *http.Request {
	return CreateHTTPRawRequest(method, urlPath, "", jsonPayload, contentType)
}

func CreateHTTPQueryRequest(method, urlPath, urlQuery string) *http.Request {
	return CreateHTTPRawRequest(method, urlPath, urlQuery, "", "")
}

func CreateHTTPRequest(method, urlPath string) *http.Request {
	return CreateHTTPRawRequest(method, urlPath, "", "", "")
}

func CreateHTTPRawRequest(method, urlPath, urlQuery, payload, contentType string) *http.Request {
	// get data from CLI, if overrides were not provided by user:
	if len(dcosUrl) == 0 {
		dcosUrl = RequiredCLIConfigValue(
			"core.dcos_url",
			"DC/OS Cluster URL",
			"Run 'dcos config set core.dcos_url http://your-cluster.com' to configure.")
	}
	parsedUrl, err := url.Parse(dcosUrl)
	if err != nil {
		log.Fatalf("Unable to parse DC/OS Cluster URL '%s': %s", dcosUrl, err)
	}
	if len(dcosAuthToken) == 0 {
		dcosAuthToken = RequiredCLIConfigValue(
			"core.dcos_acs_token",
			"DC/OS Authentication Token",
			"Run 'dcos auth login' to log in to the cluster.")
	}
	parsedUrl.Path = path.Join("service", serviceName, urlPath)
	parsedUrl.RawQuery = urlQuery
	if Verbose {
		log.Printf("HTTP Query: %s %s", method, parsedUrl)
		if len(payload) != 0 {
			log.Printf("  Payload: %s", payload)
		}
	}
	request, err := http.NewRequest(method, parsedUrl.String(), bytes.NewReader([]byte(payload)))
	if err != nil {
		log.Fatalf("Failed to create HTTP %s request for %s: %s", method, parsedUrl, err)
	}
	request.Header.Set("Authorization", fmt.Sprintf("token=%s", dcosAuthToken))
	if len(contentType) != 0 {
		request.Header.Set("Content-Type", contentType)
	}
	return request
}
<commit_msg>Support DC/OS clusters which have auth disabled (#101)<commit_after>package cli

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"path"
	"strings"
)

var (
	dcosAuthToken      string
	dcosUrl            string
	serviceName        string
	tlsAllowUnverified bool
	tlsCACertPath      string
)

func HTTPGet(urlPath string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPRequest("GET", urlPath)))
}
func HTTPGetQuery(urlPath, urlQuery string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPQueryRequest("GET", urlPath, urlQuery)))
}
func HTTPGetData(urlPath, payload, contentType string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPDataRequest("GET", urlPath, payload, contentType)))
}
func HTTPGetJSON(urlPath, jsonPayload string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPJSONRequest("GET", urlPath, jsonPayload)))
}

func HTTPDelete(urlPath string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPRequest("DELETE", urlPath)))
}
func HTTPDeleteQuery(urlPath, urlQuery string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPQueryRequest("DELETE", urlPath, urlQuery)))
}
func HTTPDeleteData(urlPath, payload, contentType string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPDataRequest("DELETE", urlPath, payload, contentType)))
}
func HTTPDeleteJSON(urlPath, jsonPayload string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPJSONRequest("DELETE", urlPath, jsonPayload)))
}

func HTTPPost(urlPath string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPRequest("POST", urlPath)))
}
func HTTPPostQuery(urlPath, urlQuery string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPQueryRequest("POST", urlPath, urlQuery)))
}
func HTTPPostData(urlPath, payload, contentType string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPDataRequest("POST", urlPath, payload, contentType)))
}
func HTTPPostJSON(urlPath, jsonPayload string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPJSONRequest("POST", urlPath, jsonPayload)))
}

func HTTPPut(urlPath string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPRequest("PUT", urlPath)))
}
func HTTPPutQuery(urlPath, urlQuery string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPQueryRequest("PUT", urlPath, urlQuery)))
}
func HTTPPutData(urlPath, payload, contentType string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPDataRequest("PUT", urlPath, payload, contentType)))
}
func HTTPPutJSON(urlPath, jsonPayload string) *http.Response {
	return CheckHTTPResponse(HTTPQuery(CreateHTTPJSONRequest("PUT", urlPath, jsonPayload)))
}

func HTTPQuery(request *http.Request) *http.Response {
	// get CA settings from CLI (local flags override CLI):
	cliVerifySetting := OptionalCLIConfigValue("core.ssl_verify")
	if strings.EqualFold(cliVerifySetting, "false") {
		// 'false': disable cert validation
		tlsAllowUnverified = true
	} else if strings.EqualFold(cliVerifySetting, "true") {
		// 'true': require validation against default CAs
		// (leave tlsAllowUnverified alone: already defaults to false)
	} else if len(cliVerifySetting) != 0 {
		// '<other string>': path to local/custom cert file
		if len(tlsCACertPath) == 0 {
			tlsCACertPath = cliVerifySetting
		}
	} else {
		// this shouldn't happen: 'auth login' requires a non-empty setting.
		// play it safe and leave cert verification enabled by default.
	}

	// allow unverified certs if user manually set the flag, or if it's configured that way in CLI:
	tlsConfig := &tls.Config{InsecureSkipVerify: tlsAllowUnverified}

	// import custom cert if user manually set the flag, or if it's configured in CLI:
	if len(tlsCACertPath) != 0 {
		// include custom CA cert as verified
		cert, err := ioutil.ReadFile(tlsCACertPath)
		if err != nil {
			log.Fatalf("Unable to read from CA certificate file %s: %s", tlsCACertPath, err)
		}
		certPool := x509.NewCertPool()
		certPool.AppendCertsFromPEM(cert)
		tlsConfig.RootCAs = certPool
	}

	client := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}
	var err interface{}
	response, err := client.Do(request)
	switch err.(type) {
	case *url.Error:
		// extract wrapped error
		err = err.(*url.Error).Err
	}
	if err != nil {
		switch err.(type) {
		case x509.UnknownAuthorityError:
			// custom suggestions for a certificate error:
			log.Printf("HTTP %s Query for %s failed: %s", request.Method, request.URL, err)
			log.Printf("- Is someone intercepting the connection to steal your credentials?")
			log.Printf("- Is the cluster CA certificate configured correctly? Check 'dcos config show core.ssl_verify'.")
			log.Fatalf("- To ignore the unvalidated certificate and force your command (INSECURE), use --force-insecure")
		default:
			log.Printf("HTTP %s Query for %s failed: %s", request.Method, request.URL, err)
			log.Printf("- Is 'core.dcos_url' set correctly? Check 'dcos config show core.dcos_url'.")
			log.Fatalf("- Is 'core.dcos_acs_token' set correctly? Run 'dcos auth login' to log in.")
		}
	}
	if Verbose {
		log.Printf("Response: %s (%d bytes)", response.Status, response.ContentLength)
	}
	return response
}

func CheckHTTPResponse(response *http.Response) *http.Response {
	switch {
	case response.StatusCode == 401:
		log.Printf("Got 401 Unauthorized response from %s", response.Request.URL)
		log.Fatalf("- Bad auth token? Run 'dcos auth login' to log in.")
	case response.StatusCode == 500:
		log.Printf("HTTP %s Query for %s failed: %s",
			response.Request.Method, response.Request.URL, response.Status)
		log.Printf("- Did you provide the correct service name? Currently using '%s', specify a different name with '--name=<name>'.", serviceName)
		log.Fatalf("- Was the service recently installed? It may still be initializing, Wait a bit and try again.")
	case response.StatusCode < 200 || response.StatusCode >= 300:
		log.Fatalf("HTTP %s Query for %s failed: %s",
			response.Request.Method, response.Request.URL, response.Status)
	}
	return response
}

func CreateHTTPJSONRequest(method, urlPath, jsonPayload string) *http.Request {
	return CreateHTTPDataRequest(method, urlPath, jsonPayload, "application/json")
}

func CreateHTTPDataRequest(method, urlPath, jsonPayload, contentType string) *http.Request {
	return CreateHTTPRawRequest(method, urlPath, "", jsonPayload, contentType)
}

func CreateHTTPQueryRequest(method, urlPath, urlQuery string) *http.Request {
	return CreateHTTPRawRequest(method, urlPath, urlQuery, "", "")
}

func CreateHTTPRequest(method, urlPath string) *http.Request {
	return CreateHTTPRawRequest(method, urlPath, "", "", "")
}

func CreateHTTPRawRequest(method, urlPath, urlQuery, payload, contentType string) *http.Request {
	// get data from CLI, if overrides were not provided by user:
	if len(dcosUrl) == 0 {
		dcosUrl = RequiredCLIConfigValue(
			"core.dcos_url",
			"DC/OS Cluster URL",
			"Run 'dcos config set core.dcos_url http://your-cluster.com' to configure.")
	}
	parsedUrl, err := url.Parse(dcosUrl)
	if err != nil {
		log.Fatalf("Unable to parse DC/OS Cluster URL '%s': %s", dcosUrl, err)
	}
	if len(dcosAuthToken) == 0 {
		// if the token wasnt manually provided by the user, try to fetch it from the main CLI.
		// this value is optional: clusters can be configured to not require any auth
		dcosAuthToken = OptionalCLIConfigValue("core.dcos_acs_token")
	}
	parsedUrl.Path = path.Join("service", serviceName, urlPath)
	parsedUrl.RawQuery = urlQuery
	if Verbose {
		log.Printf("HTTP Query: %s %s", method, parsedUrl)
		if len(payload) != 0 {
			log.Printf("  Payload: %s", payload)
		}
	}
	request, err := http.NewRequest(method, parsedUrl.String(), bytes.NewReader([]byte(payload)))
	if err != nil {
		log.Fatalf("Failed to create HTTP %s request for %s: %s", method, parsedUrl, err)
	}
	if len(dcosAuthToken) != 0 {
		request.Header.Set("Authorization", fmt.Sprintf("token=%s", dcosAuthToken))
	}
	if len(contentType) != 0 {
		request.Header.Set("Content-Type", contentType)
	}
	return request
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"text/tabwriter"
	"time"
	"unicode"

	"github.com/docker/go-units"
	cfg "github.com/flynn/flynn/cli/config"
	"github.com/flynn/flynn/controller/client"
	"github.com/flynn/flynn/pkg/shutdown"
	"github.com/flynn/flynn/pkg/version"
	"github.com/flynn/go-docopt"
)

var (
	flagCluster = os.Getenv("FLYNN_CLUSTER")
	flagApp     string
)

func main() {
	defer shutdown.Exit()

	log.SetFlags(0)

	usage := `
usage: flynn [-a <app>] [-c <cluster>] <command> [<args>...]

Options:
	-a <app>
	-c <cluster>
	-h, --help

Commands:
	help        show usage for a specific command
	install     install flynn
	cluster     manage clusters
	create      create an app
	delete      delete an app
	apps        list apps
	info        show app information
	ps          list jobs
	kill        kill a job
	log         get app log
	scale       change formation
	run         run a job
	env         manage env variables
	limit       manage resource limits
	meta        manage app metadata
	route       manage routes
	pg          manage postgres database
	mysql       manage mysql database
	redis       manage redis database
	provider    manage resource providers
	remote      manage git remotes
	resource    provision a new resource
	release     add a docker image release
	deployment  list deployments
	export      export app data
	import      create app from exported data
	version     show flynn version

See 'flynn help <command>' for more information on a specific command.
`[1:]
	args, _ := docopt.Parse(usage, nil, true, version.String(), true)

	cmd := args.String["<command>"]
	cmdArgs := args.All["<args>"].([]string)

	if cmd == "help" {
		if len(cmdArgs) == 0 { // `flynn help`
			fmt.Println(usage)
			return
		} else if cmdArgs[0] == "--json" {
			cmds := make(map[string]string)
			for name, cmd := range commands {
				cmds[name] = cmd.usage
			}
			out, err := json.MarshalIndent(cmds, "", "\t")
			if err != nil {
				shutdown.Fatal(err)
			}
			fmt.Println(string(out))
			return
		} else { // `flynn help <command>`
			cmd = cmdArgs[0]
			cmdArgs = make([]string, 1)
			cmdArgs[0] = "--help"
		}
	}
	// Run the update command as early as possible to avoid the possibility of
	// installations being stranded without updates due to errors in other code
	if cmd == "update" {
		if err := runUpdate(); err != nil {
			shutdown.Fatal(err)
		}
		return
	} else {
		defer updater.backgroundRun() // doesn't run if os.Exit is called
	}

	// Set the cluster config name
	if args.String["-c"] != "" {
		flagCluster = args.String["-c"]
	}

	flagApp = args.String["-a"]
	if flagApp != "" {
		if err := readConfig(); err != nil {
			shutdown.Fatal(err)
		}

		if ra, err := appFromGitRemote(flagApp); err == nil {
			clusterConf = ra.Cluster
			flagApp = ra.Name
		}
	}

	if err := runCommand(cmd, cmdArgs); err != nil {
		log.Println(err)
		shutdown.ExitWithCode(1)
		return
	}
}

type command struct {
	usage     string
	f         interface{}
	optsFirst bool
}

var commands = make(map[string]*command)

func register(cmd string, f interface{}, usage string) *command {
	switch f.(type) {
	case func(*docopt.Args, controller.Client) error, func(*docopt.Args) error, func() error, func():
	default:
		panic(fmt.Sprintf("invalid command function %s '%T'", cmd, f))
	}
	c := &command{usage: strings.TrimLeftFunc(usage, unicode.IsSpace), f: f}
	commands[cmd] = c
	return c
}

func runCommand(name string, args []string) (err error) {
	argv := make([]string, 1, 1+len(args))
	argv[0] = name
	argv = append(argv, args...)

	cmd, ok := commands[name]
	if !ok {
		return fmt.Errorf("%s is not a flynn command. See 'flynn help'", name)
	}
	parsedArgs, err := docopt.Parse(cmd.usage, argv, true, "", cmd.optsFirst)
	if err != nil {
		return err
	}

	switch f := cmd.f.(type) {
	case func(*docopt.Args, controller.Client) error:
		// create client and run command
		client, err := getClusterClient()
		if err != nil {
			shutdown.Fatal(err)
		}

		return f(parsedArgs, client)
	case func(*docopt.Args) error:
		return f(parsedArgs)
	case func() error:
		return f()
	case func():
		f()
		return nil
	}

	return fmt.Errorf("unexpected command type %T", cmd.f)
}

var config *cfg.Config
var clusterConf *cfg.Cluster

func configPath() string {
	return cfg.DefaultPath()
}

func readConfig() (err error) {
	if config != nil {
		return nil
	}
	config, err = cfg.ReadFile(configPath())
	if os.IsNotExist(err) {
		err = nil
	}
	if config.Upgrade() {
		if err := config.SaveTo(configPath()); err != nil {
			return fmt.Errorf("Error saving upgraded config: %s", err)
		}
	}
	return
}

func getClusterClient() (controller.Client, error) {
	cluster, err := getCluster()
	if err != nil {
		return nil, err
	}
	return cluster.Client()
}

var ErrNoClusters = errors.New("no clusters configured")

func getCluster() (*cfg.Cluster, error) {
	app() // try to look up and cache app/cluster from git remotes
	if clusterConf != nil {
		return clusterConf, nil
	}
	if err := readConfig(); err != nil {
		return nil, err
	}
	if len(config.Clusters) == 0 {
		return nil, ErrNoClusters
	}
	name := flagCluster
	// Get the default cluster
	if name == "" {
		name = config.Default
	}
	// Default cluster not set, pick the first one
	if name == "" {
		clusterConf = config.Clusters[0]
		return clusterConf, nil
	}
	for _, s := range config.Clusters {
		if s.Name == name {
			clusterConf = s
			return s, nil
		}
	}
	return nil, fmt.Errorf("unknown cluster %q", name)
}

func app() (string, error) {
	if flagApp != "" {
		return flagApp, nil
	}
	if app := os.Getenv("FLYNN_APP"); app != "" {
		flagApp = app
		return app, nil
	}
	if err := readConfig(); err != nil {
		return "", err
	}

	ra, err := appFromGitRemote(remoteFromGitConfig())
	if err != nil {
		return "", err
	}
	if ra == nil {
		return "", errors.New("no app found, run from a repo with a flynn remote or specify one with -a")
	}
	clusterConf = ra.Cluster
	flagApp = ra.Name
	return ra.Name, nil
}

func mustApp() string {
	name, err := app()
	if err != nil {
		log.Println(err)
		shutdown.ExitWithCode(1)
	}
	return name
}

func tabWriter() *tabwriter.Writer {
	return tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
}

func humanTime(ts *time.Time) string {
	if ts == nil || ts.IsZero() {
		return ""
	}
	return units.HumanDuration(time.Now().UTC().Sub(*ts)) + " ago"
}

func listRec(w io.Writer, a ...interface{}) {
	for i, x := range a {
		fmt.Fprint(w, x)
		if i+1 < len(a) {
			w.Write([]byte{'\t'})
		} else {
			w.Write([]byte{'\n'})
		}
	}
}
<commit_msg>cli: correct summary of the release command in the help<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"text/tabwriter"
	"time"
	"unicode"

	"github.com/docker/go-units"
	cfg "github.com/flynn/flynn/cli/config"
	"github.com/flynn/flynn/controller/client"
	"github.com/flynn/flynn/pkg/shutdown"
	"github.com/flynn/flynn/pkg/version"
	"github.com/flynn/go-docopt"
)

var (
	flagCluster = os.Getenv("FLYNN_CLUSTER")
	flagApp     string
)

func main() {
	defer shutdown.Exit()

	log.SetFlags(0)

	usage := `
usage: flynn [-a <app>] [-c <cluster>] <command> [<args>...]

Options:
	-a <app>
	-c <cluster>
	-h, --help

Commands:
	help        show usage for a specific command
	install     install flynn
	cluster     manage clusters
	create      create an app
	delete      delete an app
	apps        list apps
	info        show app information
	ps          list jobs
	kill        kill a job
	log         get app log
	scale       change formation
	run         run a job
	env         manage env variables
	limit       manage resource limits
	meta        manage app metadata
	route       manage routes
	pg          manage postgres database
	mysql       manage mysql database
	redis       manage redis database
	provider    manage resource providers
	remote      manage git remotes
	resource    provision a new resource
	release     manage app releases
	deployment  list deployments
	export      export app data
	import      create app from exported data
	version     show flynn version

See 'flynn help <command>' for more information on a specific command.
`[1:]
	args, _ := docopt.Parse(usage, nil, true, version.String(), true)

	cmd := args.String["<command>"]
	cmdArgs := args.All["<args>"].([]string)

	if cmd == "help" {
		if len(cmdArgs) == 0 { // `flynn help`
			fmt.Println(usage)
			return
		} else if cmdArgs[0] == "--json" {
			cmds := make(map[string]string)
			for name, cmd := range commands {
				cmds[name] = cmd.usage
			}
			out, err := json.MarshalIndent(cmds, "", "\t")
			if err != nil {
				shutdown.Fatal(err)
			}
			fmt.Println(string(out))
			return
		} else { // `flynn help <command>`
			cmd = cmdArgs[0]
			cmdArgs = make([]string, 1)
			cmdArgs[0] = "--help"
		}
	}
	// Run the update command as early as possible to avoid the possibility of
	// installations being stranded without updates due to errors in other code
	if cmd == "update" {
		if err := runUpdate(); err != nil {
			shutdown.Fatal(err)
		}
		return
	} else {
		defer updater.backgroundRun() // doesn't run if os.Exit is called
	}

	// Set the cluster config name
	if args.String["-c"] != "" {
		flagCluster = args.String["-c"]
	}

	flagApp = args.String["-a"]
	if flagApp != "" {
		if err := readConfig(); err != nil {
			shutdown.Fatal(err)
		}

		if ra, err := appFromGitRemote(flagApp); err == nil {
			clusterConf = ra.Cluster
			flagApp = ra.Name
		}
	}

	if err := runCommand(cmd, cmdArgs); err != nil {
		log.Println(err)
		shutdown.ExitWithCode(1)
		return
	}
}

type command struct {
	usage     string
	f         interface{}
	optsFirst bool
}

var commands = make(map[string]*command)

func register(cmd string, f interface{}, usage string) *command {
	switch f.(type) {
	case func(*docopt.Args, controller.Client) error, func(*docopt.Args) error, func() error, func():
	default:
		panic(fmt.Sprintf("invalid command function %s '%T'", cmd, f))
	}
	c := &command{usage: strings.TrimLeftFunc(usage, unicode.IsSpace), f: f}
	commands[cmd] = c
	return c
}

func runCommand(name string, args []string) (err error) {
	argv := make([]string, 1, 1+len(args))
	argv[0] = name
	argv = append(argv, args...)

	cmd, ok := commands[name]
	if !ok {
		return fmt.Errorf("%s is not a flynn command. See 'flynn help'", name)
	}
	parsedArgs, err := docopt.Parse(cmd.usage, argv, true, "", cmd.optsFirst)
	if err != nil {
		return err
	}

	switch f := cmd.f.(type) {
	case func(*docopt.Args, controller.Client) error:
		// create client and run command
		client, err := getClusterClient()
		if err != nil {
			shutdown.Fatal(err)
		}

		return f(parsedArgs, client)
	case func(*docopt.Args) error:
		return f(parsedArgs)
	case func() error:
		return f()
	case func():
		f()
		return nil
	}

	return fmt.Errorf("unexpected command type %T", cmd.f)
}

var config *cfg.Config
var clusterConf *cfg.Cluster

func configPath() string {
	return cfg.DefaultPath()
}

func readConfig() (err error) {
	if config != nil {
		return nil
	}
	config, err = cfg.ReadFile(configPath())
	if os.IsNotExist(err) {
		err = nil
	}
	if config.Upgrade() {
		if err := config.SaveTo(configPath()); err != nil {
			return fmt.Errorf("Error saving upgraded config: %s", err)
		}
	}
	return
}

func getClusterClient() (controller.Client, error) {
	cluster, err := getCluster()
	if err != nil {
		return nil, err
	}
	return cluster.Client()
}

var ErrNoClusters = errors.New("no clusters configured")

func getCluster() (*cfg.Cluster, error) {
	app() // try to look up and cache app/cluster from git remotes
	if clusterConf != nil {
		return clusterConf, nil
	}
	if err := readConfig(); err != nil {
		return nil, err
	}
	if len(config.Clusters) == 0 {
		return nil, ErrNoClusters
	}
	name := flagCluster
	// Get the default cluster
	if name == "" {
		name = config.Default
	}
	// Default cluster not set, pick the first one
	if name == "" {
		clusterConf = config.Clusters[0]
		return clusterConf, nil
	}
	for _, s := range config.Clusters {
		if s.Name == name {
			clusterConf = s
			return s, nil
		}
	}
	return nil, fmt.Errorf("unknown cluster %q", name)
}

func app() (string, error) {
	if flagApp != "" {
		return flagApp, nil
	}
	if app := os.Getenv("FLYNN_APP"); app != "" {
		flagApp = app
		return app, nil
	}
	if err := readConfig(); err != nil {
		return "", err
	}

	ra, err := appFromGitRemote(remoteFromGitConfig())
	if err != nil {
		return "", err
	}
	if ra == nil {
		return "", errors.New("no app found, run from a repo with a flynn remote or specify one with -a")
	}
	clusterConf = ra.Cluster
	flagApp = ra.Name
	return ra.Name, nil
}

func mustApp() string {
	name, err := app()
	if err != nil {
		log.Println(err)
		shutdown.ExitWithCode(1)
	}
	return name
}

func tabWriter() *tabwriter.Writer {
	return tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
}

func humanTime(ts *time.Time) string {
	if ts == nil || ts.IsZero() {
		return ""
	}
	return units.HumanDuration(time.Now().UTC().Sub(*ts)) + " ago"
}

func listRec(w io.Writer, a ...interface{}) {
	for i, x := range a {
		fmt.Fprint(w, x)
		if i+1 < len(a) {
			w.Write([]byte{'\t'})
		} else {
			w.Write([]byte{'\n'})
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"mime"
	"os"
	"path/filepath"

	"github.com/SYNQfm/SYNQ-Golang/helper"
	"github.com/SYNQfm/SYNQ-Golang/synq"
	"github.com/SYNQfm/helpers/common"
)

var cli common.Cli

func init() {
	cli = common.NewCli()
	cli.DefaultSetup("for v2 'upload', get_video', for v1 : details, upload_info, upload, create, uploader_info, uploader, query or create_and_then_multipart_upload", "upload")
	cli.String("version", "v2", "version to use")
	cli.String("api_key", "", "pass the synq api key")
	cli.String("upload_url", synq.DEFAULT_UPLOADER_URL, "upload url to use")
	cli.String("user", "", "user to use")
	cli.String("password", "", "password to use")
	cli.String("video_id", "", "video id to access")
	cli.String("asset_id", "", "asset id to access")
	cli.String("file", "", "path to file you want to upload or userdata")
	cli.String("query", "", "query string to use")
	cli.Parse()
}

func handleError(err error) {
	if err != nil {
		log.Printf("Error : %s\n", err.Error())
		os.Exit(1)
	}
}

func handleV2(api synq.ApiV2) {
	vid := cli.GetString("video_id")
	aid := cli.GetString("asset_id")
	switch cli.Command {
	case "upload":
		var asset synq.Asset
		var err error
		upload_url := cli.GetString("upload_url")
		if upload_url == "" {
			err = errors.New("missing upload_url")
			handleError(err)
		}
		api.UploadUrl = upload_url
		file := cli.GetString("file")
		if file == "" {
			err = errors.New("file missing")
			handleError(err)
		}
		ext := filepath.Ext(file)
		ctype := mime.TypeByExtension(ext)
		if ctype == "" {
			handleError(errors.New("can not find cypte for " + ext))
		}
		if aid == "" {
			video, err := helper.LoadVideoV2(vid, cli, api)
			if err == nil {
				var found synq.Asset
				for _, a := range video.Assets {
					if ctype == a.Type {
						found = a
						break
					}
				}
				if found.Id != "" {
					log.Printf("using existing asset %s for '%s'\n", found.Id, ctype)
					asset = found
				} else {
					log.Printf("creating new asset with ctype '%s'\n", ctype)
					asset, err = video.CreateAssetForUpload(ctype)
				}
			}
		} else {
			log.Printf("getting existing asset %s\n", aid)
			asset, err = helper.LoadAsset(aid, cli, api)
		}
		handleError(err)
		params := synq.UnicornParam{
			Ctype:   ctype,
			AssetId: asset.Id,
		}
		up, e := helper.LoadUploadParameters(asset.VideoId, params, cli, api)
		handleError(e)
		log.Printf("Got upload params for %s", up.Key)
		asset.UploadParameters = up

		cli.Printf("uploading file %s\n", file)
		if !cli.Simulate {
			err = asset.UploadFile(file)
			handleError(err)
			log.Printf("uploaded file %s\n", file)
		}
	case "get_raw_videos":
		log.Printf("getting all videos (raw format)")
		videos, err := api.GetRawVideos("")
		handleError(err)
		log.Printf("found %d\n", len(videos))
		bytes, _ := json.Marshal(videos)
		ioutil.WriteFile(cli.CacheDir+"/raw.json", bytes, 0755)
	case "get_video":
		log.Printf("getting video %s\n", vid)
		video, err := api.GetVideo(vid)
		handleError(err)
		log.Println(video.Display())
	case "update":
		id := "4a15e1fc-a422-466d-8cad-677c1605983c"
		video, _ := api.GetVideo(id)
		log.Printf("Got video %s", video.Id)
		video.CompletenessScore = 10.1
		err := video.Update()
		if err != nil {
			log.Printf("Got error %s", err.Error)
		} else {
			log.Printf("Got video score %.1f\n", video.CompletenessScore)
		}
	default:
		handleError(errors.New("unknown command '" + cli.Command + "'"))
	}
}

func handleV1(api synq.Api) {
	var video synq.Video
	var err error
	vid := cli.GetString("video_id")
	file := cli.GetString("file")
	switch cli.Command {
	case "details":
		if vid == "" {
			log.Println("missing video id")
			os.Exit(1)
		}
		log.Printf("getting video %s\n", vid)
		video, err = api.GetVideo(vid)
	case "upload_info":
		log.Printf("Getting upload info for %s\n", vid)
		video.Api = &api
		video.Id = vid
		err = video.GetUploadInfo()
	case "query":
		q := cli.GetString("query")
		videos, err := api.Query(q)
		handleError(err)
		log.Printf("Found %d videos\n", len(videos))
		for _, video := range videos {
			log.Println(video.Display())
		}
		os.Exit(0)
	case "upload":
		if file == "" {
			log.Println("missing 'file'")
			os.Exit(1)
		}
		log.Printf("uploading file '%s'\n", file)
		video.Api = &api
		video.Id = vid
		err = video.UploadFile(file)
	case "create":
		log.Printf("Creating new video")
		if file != "" {
			log.Printf("loading userdata file from %s\n", file)
			bytes, err := ioutil.ReadFile(file)
			if err == nil {
				userdata := make(map[string]interface{})
				json.Unmarshal(bytes, &userdata)
				video, err = api.Create(userdata)
			}
		} else {
			video, err = api.Create()
		}
	case "uploader_info":
		if vid == "" {
			log.Println("missing video id")
			os.Exit(1)
		}
		video.Api = &api
		video.Id = vid
		err = video.GetUploaderInfo()
		if err != nil {
			log.Println(err)
			os.Exit(1)
		}
		log.Println("uploader_url:", video.UploaderInfo["uploader_url"])
		os.Exit(0)
	case "uploader":
		if file == "" {
			log.Println("missing 'file'")
			os.Exit(1)
		}
		if vid == "" {
			log.Println("missing video id")
			os.Exit(1)
		}
		video.Api = &api
		video.Id = vid

		log.Printf("uploading file '%s'\n", file)
		err = video.MultipartUpload(file)
		handleError(err)

		video, err = api.GetVideo(video.Id)
	case "create_and_then_multipart_upload":
		if file == "" {
			log.Println("missing 'file'")
			os.Exit(1)
		}

		log.Printf("Creating new video")
		video, err = api.Create()
		handleError(err)

		log.Printf("uploading file '%s'\n", file)
		err = video.MultipartUpload(file)
		handleError(err)

		video, err = api.GetVideo(video.Id)
	default:
		err = errors.New("unknown command '" + cli.Command + "'")
	}
	handleError(err)
	log.Println(video.Display())
}

func main() {
	user := cli.GetString("user")
	password := cli.GetString("password")
	if user != "" && password != "" {
		api, err := synq.Login(user, password)
		handleError(err)
		handleV2(api)
	} else {
		api_key := cli.GetString("api_key")
		if api_key == "" {
			log.Println("missing api_key")
			os.Exit(1)
		}
		if cli.GetString("version") == "v2" {
			api := synq.NewV2(api_key)
			handleV2(api)
		} else {
			api := synq.NewV1(api_key)
			handleV1(api)
		}
	}
}
<commit_msg>use timeout, share get raw videos and videos<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"mime"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/SYNQfm/SYNQ-Golang/helper"
	"github.com/SYNQfm/SYNQ-Golang/synq"
	"github.com/SYNQfm/helpers/common"
)

var cli common.Cli

func init() {
	cli = common.NewCli()
	cli.DefaultSetup("for v2 'upload', get_video', for v1 : details, upload_info, upload, create, uploader_info, uploader, query or create_and_then_multipart_upload", "upload")
	cli.String("version", "v2", "version to use")
	cli.String("api_key", "", "pass the synq api key")
	cli.String("upload_url", synq.DEFAULT_UPLOADER_URL, "upload url to use")
	cli.String("user", "", "user to use")
	cli.String("password", "", "password to use")
	cli.String("video_id", "", "video id to access")
	cli.String("asset_id", "", "asset id to access")
	cli.String("file", "", "path to file you want to upload or userdata")
	cli.String("query", "", "query string to use")
	cli.Parse()
}

func handleError(err error) {
	if err != nil {
		log.Printf("Error : %s\n", err.Error())
		os.Exit(1)
	}
}

func handleV2(api synq.ApiV2) {
	vid := cli.GetString("video_id")
	aid := cli.GetString("asset_id")
	ret := common.NewRet(cli.Command)
	switch cli.Command {
	case "upload":
		var asset synq.Asset
		var err error
		upload_url := cli.GetString("upload_url")
		if upload_url == "" {
			err = errors.New("missing upload_url")
			handleError(err)
		}
		api.UploadUrl = upload_url
		file := cli.GetString("file")
		if file == "" {
			err = errors.New("file missing")
			handleError(err)
		}
		ext := filepath.Ext(file)
		ctype := mime.TypeByExtension(ext)
		if ctype == "" {
			handleError(errors.New("can not find cypte for " + ext))
		}
		if aid == "" {
			video, err := helper.LoadVideoV2(vid, cli, api)
			if err == nil {
				var found synq.Asset
				for _, a := range video.Assets {
					if ctype == a.Type {
						found = a
						break
					}
				}
				if found.Id != "" {
					log.Printf("using existing asset %s for '%s'\n", found.Id, ctype)
					asset = found
				} else {
					log.Printf("creating new asset with ctype '%s'\n", ctype)
					asset, err = video.CreateAssetForUpload(ctype)
				}
			}
		} else {
			log.Printf("getting existing asset %s\n", aid)
			asset, err = helper.LoadAsset(aid, cli, api)
		}
		handleError(err)
		params := synq.UnicornParam{
			Ctype:   ctype,
			AssetId: asset.Id,
		}
		up, e := helper.LoadUploadParameters(asset.VideoId, params, cli, api)
		handleError(e)
		log.Printf("Got upload params for %s", up.Key)
		asset.UploadParameters = up

		cli.Printf("uploading file %s\n", file)
		if !cli.Simulate {
			err = asset.UploadFile(file)
			handleError(err)
			log.Printf("uploaded file %s\n", file)
		}
	case "get_raw_videos",
		"get_videos":
		api.PageSize = 500
		raw := strings.Contains(cli.Command, "raw")
		jstr := fmt.Sprintf("getting all videos (page size %d)", api.PageSize)
		name := "videos"
		if raw {
			str = str + " (raw format)"
			name = name + "_raw"
		}
		var bytes []byte
		var err error
		vidCt := 0
		log.Println(str)
		if raw {
			var videos []json.RawMessage
			videos, err = api.GetRawVideos("")
			vidCt = len(videos)
			bytes, _ = json.Marshal(videos)
		} else {
			var videos []synq.VideoV2
			videos, err = api.GetVideos("")
			bytes, _ = json.Marshal(videos)
		}
		handleError(err)
		log.Printf("found %d\n", vidCt)
		ret.AddFor("videos", vidCt)
		ret.AddDurFor("videos", time.Since(ret.Start))
		ioutil.WriteFile(cli.CacheDir+"/"+name+".json", bytes, 0755)
	case "update":
		id := "4a15e1fc-a422-466d-8cad-677c1605983c"
		video, _ := api.GetVideo(id)
		log.Printf("Got video %s", video.Id)
		video.CompletenessScore = 10.1
		err := video.Update()
		if err != nil {
			log.Printf("Got error %s", err.Error)
		} else {
			log.Printf("Got video score %.1f\n", video.CompletenessScore)
		}
	default:
		handleError(errors.New("unknown command '" + cli.Command + "'"))
	}
	log.Println(ret.String())
}

func handleV1(api synq.Api) {
	var video synq.Video
	var err error
	vid := cli.GetString("video_id")
	file := cli.GetString("file")
	switch cli.Command {
	case "details":
		if vid == "" {
			log.Println("missing video id")
			os.Exit(1)
		}
		log.Printf("getting video %s\n", vid)
		video, err = api.GetVideo(vid)
	case "upload_info":
		log.Printf("Getting upload info for %s\n", vid)
		video.Api = &api
		video.Id = vid
		err = video.GetUploadInfo()
	case "query":
		q := cli.GetString("query")
		videos, err := api.Query(q)
		handleError(err)
		log.Printf("Found %d videos\n", len(videos))
		for _, video := range videos {
			log.Println(video.Display())
		}
		os.Exit(0)
	case "upload":
		if file == "" {
			log.Println("missing 'file'")
			os.Exit(1)
		}
		log.Printf("uploading file '%s'\n", file)
		video.Api = &api
		video.Id = vid
		err = video.UploadFile(file)
	case "create":
		log.Printf("Creating new video")
		if file != "" {
			log.Printf("loading userdata file from %s\n", file)
			bytes, err := ioutil.ReadFile(file)
			if err == nil {
				userdata := make(map[string]interface{})
				json.Unmarshal(bytes, &userdata)
				video, err = api.Create(userdata)
			}
		} else {
			video, err = api.Create()
		}
	case "uploader_info":
		if vid == "" {
			log.Println("missing video id")
			os.Exit(1)
		}
		video.Api = &api
		video.Id = vid
		err = video.GetUploaderInfo()
		if err != nil {
			log.Println(err)
			os.Exit(1)
		}
		log.Println("uploader_url:", video.UploaderInfo["uploader_url"])
		os.Exit(0)
	case "uploader":
		if file == "" {
			log.Println("missing 'file'")
			os.Exit(1)
		}
		if vid == "" {
			log.Println("missing video id")
			os.Exit(1)
		}
		video.Api = &api
		video.Id = vid

		log.Printf("uploading file '%s'\n", file)
		err = video.MultipartUpload(file)
		handleError(err)

		video, err = api.GetVideo(video.Id)
	case "create_and_then_multipart_upload":
		if file == "" {
			log.Println("missing 'file'")
			os.Exit(1)
		}

		log.Printf("Creating new video")
		video, err = api.Create()
		handleError(err)

		log.Printf("uploading file '%s'\n", file)
		err = video.MultipartUpload(file)
		handleError(err)

		video, err = api.GetVideo(video.Id)
	default:
		err = errors.New("unknown command '" + cli.Command + "'")
	}
	handleError(err)
	log.Println(video.Display())
}

func main() {
	user := cli.GetString("user")
	password := cli.GetString("password")
	if user != "" && password != "" {
		api, err := synq.Login(user, password)
		handleError(err)
		handleV2(api)
	} else {
		api_key := cli.GetString("api_key")
		if api_key == "" {
			log.Println("missing api_key")
			os.Exit(1)
		}
		if cli.GetString("version") == "v2" {
			api := synq.NewV2(api_key, cli.Timeout)
			handleV2(api)
		} else {
			api := synq.NewV1(api_key, cli.Timeout)
			handleV1(api)
		}
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
	"github.com/hashicorp/terraform/helper/schema"
)

func dataSourceAwsCloudwatchLogGroup() *schema.Resource {
	return &schema.Resource{
		Read: dataSourceAwsCloudwatchLogGroupRead,

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"creation_time": {
				Type:     schema.TypeInt,
				Computed: true,
			},
		},
	}
}

func dataSourceAwsCloudwatchLogGroupRead(d *schema.ResourceData, meta interface{}) error {
	name := aws.String(d.Get("name").(string))
	conn := meta.(*AWSClient).cloudwatchlogsconn

	input := &cloudwatchlogs.DescribeLogGroupsInput{
		LogGroupNamePrefix: name,
	}

	resp, err := conn.DescribeLogGroups(input)
	if err != nil {
		return err
	}

	var logGroup *cloudwatchlogs.LogGroup

	for _, lg := range resp.LogGroups {
		if *lg.LogGroupName == *name {
			logGroup = lg
			break
		}
	}

	if logGroup == nil {
		return fmt.Errorf("No log group named %s found\n", *name)
	}

	d.SetId(*logGroup.LogGroupName)
	d.Set("arn", logGroup.Arn)
	d.Set("creation_time", logGroup.CreationTime)

	return nil
}
<commit_msg>PR feedback<commit_after>package aws

import (
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
	"github.com/hashicorp/terraform/helper/schema"
)

func dataSourceAwsCloudwatchLogGroup() *schema.Resource {
	return &schema.Resource{
		Read: dataSourceAwsCloudwatchLogGroupRead,

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
			},
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"creation_time": {
				Type:     schema.TypeInt,
				Computed: true,
			},
		},
	}
}

func dataSourceAwsCloudwatchLogGroupRead(d *schema.ResourceData, meta interface{}) error {
	name := d.Get("name").(string)
	conn := meta.(*AWSClient).cloudwatchlogsconn

	input := &cloudwatchlogs.DescribeLogGroupsInput{
		LogGroupNamePrefix: aws.String(name),
	}

	var logGroup *cloudwatchlogs.LogGroup
	// iterate over the pages of log groups until we find the one we are looking for
	err := conn.DescribeLogGroupsPages(input,
		func(resp *cloudwatchlogs.DescribeLogGroupsOutput, _ bool) bool {
			for _, lg := range resp.LogGroups {
				if aws.StringValue(lg.LogGroupName) == name {
					logGroup = lg
					return false
				}
			}
			return true
		})

	if err != nil {
		return err
	}

	if logGroup == nil {
		return fmt.Errorf("No log group named %s found\n", name)
	}

	d.SetId(name)
	d.Set("arn", logGroup.Arn)
	d.Set("creation_time", logGroup.CreationTime)

	return nil
}
<|endoftext|>


<commit_before>package aws

import (
	"bytes"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/neptune"
)

// We can only modify 20 parameters at a time, so walk them until
// we've got them all.
const maxParams = 20

func resourceAwsNeptuneParameterGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsNeptuneParameterGroupCreate,
		Read:   resourceAwsNeptuneParameterGroupRead,
		Update: resourceAwsNeptuneParameterGroupUpdate,
		Delete: resourceAwsNeptuneParameterGroupDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},
		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				ForceNew: true,
				Required: true,
				StateFunc: func(val interface{}) string {
					return strings.ToLower(val.(string))
				},
			},
			"family": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"description": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Default:  "Managed by Terraform",
			},
			"parameter": {
				Type:     schema.TypeSet,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"name": {
							Type:     schema.TypeString,
							Required: true,
						},
						"value": {
							Type:     schema.TypeString,
							Required: true,
						},
						"apply_method": {
							Type:     schema.TypeString,
							Optional: true,
							Default:  "immediate",
							// this parameter is not actually state, but a
							// meta-parameter describing how the RDS API call
							// to modify the parameter group should be made.
							// Future reads of the resource from AWS don't tell
							// us what we used for apply_method previously, so
							// by squashing state to an empty string we avoid
							// needing to do an update for every future run.
							StateFunc: func(interface{}) string { return "" },
						},
					},
				},
				Set: resourceAwsNeptuneParameterHash,
			},
		},
	}
}

func resourceAwsNeptuneParameterGroupCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).neptuneconn

	createOpts := neptune.CreateDBParameterGroupInput{
		DBParameterGroupName:   aws.String(d.Get("name").(string)),
		DBParameterGroupFamily: aws.String(d.Get("family").(string)),
		Description:            aws.String(d.Get("description").(string)),
	}

	log.Printf("[DEBUG] Create Neptune Parameter Group: %#v", createOpts)
	resp, err := conn.CreateDBParameterGroup(&createOpts)
	if err != nil {
		return fmt.Errorf("Error creating Neptune Parameter Group: %s", err)
	}

	d.Partial(true)
	d.SetPartial("name")
	d.SetPartial("family")
	d.SetPartial("description")
	d.Partial(false)

	d.SetId(*resp.DBParameterGroup.DBParameterGroupName)
	log.Printf("[INFO] Neptune Parameter Group ID: %s", d.Id())

	return resourceAwsNeptuneParameterGroupUpdate(d, meta)
}

func resourceAwsNeptuneParameterGroupRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).neptuneconn

	describeOpts := neptune.DescribeDBParameterGroupsInput{
		DBParameterGroupName: aws.String(d.Id()),
	}

	describeResp, err := conn.DescribeDBParameterGroups(&describeOpts)
	if err != nil {
		if isAWSErr(err, neptune.ErrCodeDBParameterGroupNotFoundFault, "") {
			log.Printf("[WARN] Neptune Parameter Group (%s) not found, removing from state", d.Id())
			d.SetId("")
			return nil
		}
		return err
	}

	if len(describeResp.DBParameterGroups) != 1 ||
		*describeResp.DBParameterGroups[0].DBParameterGroupName != d.Id() {
		return fmt.Errorf("Unable to find Parameter Group: %#v", describeResp.DBParameterGroups)
	}

	d.Set("name", describeResp.DBParameterGroups[0].DBParameterGroupName)
	d.Set("family", describeResp.DBParameterGroups[0].DBParameterGroupFamily)
	d.Set("description", describeResp.DBParameterGroups[0].Description)

	// Only include user customized parameters as there's hundreds of system/default ones
	describeParametersOpts := neptune.DescribeDBParametersInput{
		DBParameterGroupName: aws.String(d.Id()),
		Source:               aws.String("user"),
	}

	describeParametersResp, err := conn.DescribeDBParameters(&describeParametersOpts)
	if err != nil {
		return err
	}

	if err := d.Set("parameter", flattenNeptuneParameters(describeParametersResp.Parameters)); err != nil {
		return fmt.Errorf("error setting parameter: %s", err)
	}

	return nil
}

func resourceAwsNeptuneParameterGroupUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).neptuneconn

	d.Partial(true)

	if d.HasChange("parameter") {
		o, n := d.GetChange("parameter")
		if o == nil {
			o = new(schema.Set)
		}
		if n == nil {
			n = new(schema.Set)
		}

		os := o.(*schema.Set)
		ns := n.(*schema.Set)

		toRemove, err := expandNeptuneParameters(os.Difference(ns).List())
		if err != nil {
			return err
		}

		log.Printf("[DEBUG] Parameters to remove: %#v", toRemove)

		toAdd, err := expandNeptuneParameters(ns.Difference(os).List())
		if err != nil {
			return err
		}

		log.Printf("[DEBUG] Parameters to add: %#v", toAdd)

		for len(toRemove) > 0 {
			paramsToModify := make([]*neptune.Parameter, 0)
			if len(toRemove) <= maxParams {
				paramsToModify, toRemove = toRemove[:], nil
			} else {
				paramsToModify, toRemove = toRemove[:maxParams], toRemove[maxParams:]
			}
			resetOpts := neptune.ResetDBParameterGroupInput{
				DBParameterGroupName: aws.String(d.Get("name").(string)),
				Parameters:           paramsToModify,
			}

			log.Printf("[DEBUG] Reset Neptune Parameter Group: %s", resetOpts)
			err := resource.Retry(30*time.Second, func() *resource.RetryError {
				_, err = conn.ResetDBParameterGroup(&resetOpts)
				if err != nil {
					if isAWSErr(err, "InvalidDBParameterGroupState", " has pending changes") {
						return resource.RetryableError(err)
					}
					return resource.NonRetryableError(err)
				}
				return nil
			})
			if err != nil {
				return fmt.Errorf("Error resetting Neptune Parameter Group: %s", err)
			}
		}

		for len(toAdd) > 0 {
			paramsToModify := make([]*neptune.Parameter, 0)
			if len(toAdd) <= maxParams {
				paramsToModify, toAdd = toAdd[:], nil
			} else {
				paramsToModify, toAdd = toAdd[:maxParams], toAdd[maxParams:]
			}
			modifyOpts := neptune.ModifyDBParameterGroupInput{
				DBParameterGroupName: aws.String(d.Get("name").(string)),
				Parameters:           paramsToModify,
			}

			log.Printf("[DEBUG] Modify Neptune Parameter Group: %s", modifyOpts)
			_, err = conn.ModifyDBParameterGroup(&modifyOpts)
			if err != nil {
				return fmt.Errorf("Error modifying Neptune Parameter Group: %s", err)
			}
		}

		d.SetPartial("parameter")
	}

	d.Partial(false)

	return resourceAwsNeptuneParameterGroupRead(d, meta)
}

func resourceAwsNeptuneParameterGroupDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).neptuneconn

	return resource.Retry(3*time.Minute, func() *resource.RetryError {
		deleteOpts := neptune.DeleteDBParameterGroupInput{
			DBParameterGroupName: aws.String(d.Id()),
		}
		_, err := conn.DeleteDBParameterGroup(&deleteOpts)
		if err != nil {
			if isAWSErr(err, neptune.ErrCodeDBParameterGroupNotFoundFault, "") {
				return nil
			}
			if isAWSErr(err, neptune.ErrCodeInvalidDBParameterGroupStateFault, "") {
				return resource.RetryableError(err)
			}
			return resource.NonRetryableError(err)
		}
		return nil
	})
}

func resourceAwsNeptuneParameterHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
	// Store the value as a lower case string, to match how we store them in flattenParameters
	buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["value"].(string))))

	return hashcode.String(buf.String())
}
<commit_msg>Using Neptune Describe Params pages<commit_after>package aws

import (
	"bytes"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/neptune"
)

// We can only modify 20 parameters at a time, so walk them until
// we've got them all.
const maxParams = 20

func resourceAwsNeptuneParameterGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsNeptuneParameterGroupCreate,
		Read:   resourceAwsNeptuneParameterGroupRead,
		Update: resourceAwsNeptuneParameterGroupUpdate,
		Delete: resourceAwsNeptuneParameterGroupDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},
		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				ForceNew: true,
				Required: true,
				StateFunc: func(val interface{}) string {
					return strings.ToLower(val.(string))
				},
			},
			"family": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"description": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Default:  "Managed by Terraform",
			},
			"parameter": {
				Type:     schema.TypeSet,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"name": {
							Type:     schema.TypeString,
							Required: true,
						},
						"value": {
							Type:     schema.TypeString,
							Required: true,
						},
						"apply_method": {
							Type:     schema.TypeString,
							Optional: true,
							Default:  "immediate",
							// this parameter is not actually state, but a
							// meta-parameter describing how the RDS API call
							// to modify the parameter group should be made.
							// Future reads of the resource from AWS don't tell
							// us what we used for apply_method previously, so
							// by squashing state to an empty string we avoid
							// needing to do an update for every future run.
							StateFunc: func(interface{}) string { return "" },
						},
					},
				},
				Set: resourceAwsNeptuneParameterHash,
			},
		},
	}
}

func resourceAwsNeptuneParameterGroupCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).neptuneconn

	createOpts := neptune.CreateDBParameterGroupInput{
		DBParameterGroupName:   aws.String(d.Get("name").(string)),
		DBParameterGroupFamily: aws.String(d.Get("family").(string)),
		Description:            aws.String(d.Get("description").(string)),
	}

	log.Printf("[DEBUG] Create Neptune Parameter Group: %#v", createOpts)
	resp, err := conn.CreateDBParameterGroup(&createOpts)
	if err != nil {
		return fmt.Errorf("Error creating Neptune Parameter Group: %s", err)
	}

	d.Partial(true)
	d.SetPartial("name")
	d.SetPartial("family")
	d.SetPartial("description")
	d.Partial(false)

	d.SetId(*resp.DBParameterGroup.DBParameterGroupName)
	log.Printf("[INFO] Neptune Parameter Group ID: %s", d.Id())

	return resourceAwsNeptuneParameterGroupUpdate(d, meta)
}

func resourceAwsNeptuneParameterGroupRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).neptuneconn

	describeOpts := neptune.DescribeDBParameterGroupsInput{
		DBParameterGroupName: aws.String(d.Id()),
	}

	describeResp, err := conn.DescribeDBParameterGroups(&describeOpts)
	if err != nil {
		if isAWSErr(err, neptune.ErrCodeDBParameterGroupNotFoundFault, "") {
			log.Printf("[WARN] Neptune Parameter Group (%s) not found, removing from state", d.Id())
			d.SetId("")
			return nil
		}
		return err
	}

	if len(describeResp.DBParameterGroups) != 1 ||
		*describeResp.DBParameterGroups[0].DBParameterGroupName != d.Id() {
		return fmt.Errorf("Unable to find Parameter Group: %#v", describeResp.DBParameterGroups)
	}

	d.Set("name", describeResp.DBParameterGroups[0].DBParameterGroupName)
	d.Set("family", describeResp.DBParameterGroups[0].DBParameterGroupFamily)
	d.Set("description", describeResp.DBParameterGroups[0].Description)

	// Only include user customized parameters as there's hundreds of system/default ones
	describeParametersOpts := neptune.DescribeDBParametersInput{
		DBParameterGroupName: aws.String(d.Id()),
		Source:               aws.String("user"),
	}

	var parameters []*neptune.Parameter
	err = conn.DescribeDBParametersPages(&describeParametersOpts,
		func(describeParametersResp *neptune.DescribeDBParametersOutput, lastPage bool) bool {
			parameters = append(parameters, describeParametersResp.Parameters...)
			return !lastPage
		})
	if err != nil {
		return err
	}

	if err := d.Set("parameter", flattenNeptuneParameters(parameters)); err != nil {
		return fmt.Errorf("error setting parameter: %s", err)
	}

	return nil
}

func resourceAwsNeptuneParameterGroupUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).neptuneconn

	d.Partial(true)

	if d.HasChange("parameter") {
		o, n := d.GetChange("parameter")
		if o == nil {
			o = new(schema.Set)
		}
		if n == nil {
			n = new(schema.Set)
		}

		os := o.(*schema.Set)
		ns := n.(*schema.Set)

		toRemove, err := expandNeptuneParameters(os.Difference(ns).List())
		if err != nil {
			return err
		}

		log.Printf("[DEBUG] Parameters to remove: %#v", toRemove)

		toAdd, err := expandNeptuneParameters(ns.Difference(os).List())
		if err != nil {
			return err
		}

		log.Printf("[DEBUG] Parameters to add: %#v", toAdd)

		for len(toRemove) > 0 {
			paramsToModify := make([]*neptune.Parameter, 0)
			if len(toRemove) <= maxParams {
				paramsToModify, toRemove = toRemove[:], nil
			} else {
				paramsToModify, toRemove = toRemove[:maxParams], toRemove[maxParams:]
			}
			resetOpts := neptune.ResetDBParameterGroupInput{
				DBParameterGroupName: aws.String(d.Get("name").(string)),
				Parameters:           paramsToModify,
			}

			log.Printf("[DEBUG] Reset Neptune Parameter Group: %s", resetOpts)
			err := resource.Retry(30*time.Second, func() *resource.RetryError {
				_, err = conn.ResetDBParameterGroup(&resetOpts)
				if err != nil {
					if isAWSErr(err, "InvalidDBParameterGroupState", " has pending changes") {
						return resource.RetryableError(err)
					}
					return resource.NonRetryableError(err)
				}
				return nil
			})
			if err != nil {
				return fmt.Errorf("Error resetting Neptune Parameter Group: %s", err)
			}
		}

		for len(toAdd) > 0 {
			paramsToModify := make([]*neptune.Parameter, 0)
			if len(toAdd) <= maxParams {
				paramsToModify, toAdd = toAdd[:], nil
			} else {
				paramsToModify, toAdd = toAdd[:maxParams], toAdd[maxParams:]
			}
			modifyOpts := neptune.ModifyDBParameterGroupInput{
				DBParameterGroupName: aws.String(d.Get("name").(string)),
				Parameters:           paramsToModify,
			}

			log.Printf("[DEBUG] Modify Neptune Parameter Group: %s", modifyOpts)
			_, err = conn.ModifyDBParameterGroup(&modifyOpts)
			if err != nil {
				return fmt.Errorf("Error modifying Neptune Parameter Group: %s", err)
			}
		}

		d.SetPartial("parameter")
	}

	d.Partial(false)

	return resourceAwsNeptuneParameterGroupRead(d, meta)
}

func resourceAwsNeptuneParameterGroupDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).neptuneconn

	return resource.Retry(3*time.Minute, func() *resource.RetryError {
		deleteOpts := neptune.DeleteDBParameterGroupInput{
			DBParameterGroupName: aws.String(d.Id()),
		}
		_, err := conn.DeleteDBParameterGroup(&deleteOpts)
		if err != nil {
			if isAWSErr(err, neptune.ErrCodeDBParameterGroupNotFoundFault, "") {
				return nil
			}
			if isAWSErr(err, neptune.ErrCodeInvalidDBParameterGroupStateFault, "") {
				return resource.RetryableError(err)
			}
			return resource.NonRetryableError(err)
		}
		return nil
	})
}

func resourceAwsNeptuneParameterHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
	// Store the value as a lower case string, to match how we store them in flattenParameters
	buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["value"].(string))))

	return hashcode.String(buf.String())
}
<|endoftext|>


<commit_before>package aws

import (
	"bytes"
	"fmt"
	"log"
	"regexp"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/redshift"
	"github.com/hashicorp/go-multierror"
	"github.com/hashicorp/terraform-plugin-sdk/helper/hashcode"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)

func resourceAwsRedshiftSecurityGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsRedshiftSecurityGroupCreate,
		Read:   resourceAwsRedshiftSecurityGroupRead,
		Update: resourceAwsRedshiftSecurityGroupUpdate,
		Delete: resourceAwsRedshiftSecurityGroupDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: validation.All(
					validation.StringLenBetween(1, 255),
					validation.StringNotInSlice([]string{"default"}, false),
					validation.StringMatch(regexp.MustCompile(`^[0-9a-z-]+$`), "must contain only lowercase alphanumeric characters and hyphens"),
				),
			},

			"description": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Default:  "Managed by Terraform",
			},

			"ingress": {
				Type:     schema.TypeSet,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"cidr": {
							Type:     schema.TypeString,
							Optional: true,
						},

						"security_group_name": {
							Type:     schema.TypeString,
							Optional: true,
							Computed: true,
						},

						"security_group_owner_id": {
							Type:     schema.TypeString,
							Optional: true,
							Computed: true,
						},
					},
				},
				Set: resourceAwsRedshiftSecurityGroupIngressHash,
			},
		},
	}
}

func resourceAwsRedshiftSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	var err error
	var errs []error

	name := d.Get("name").(string)
	desc := d.Get("description").(string)
	sgInput := &redshift.CreateClusterSecurityGroupInput{
		ClusterSecurityGroupName: aws.String(name),
		Description:              aws.String(desc),
	}
	log.Printf("[DEBUG] Redshift security group create: name: %s, description: %s", name, desc)
	_, err = conn.CreateClusterSecurityGroup(sgInput)
	if err != nil {
		return fmt.Errorf("Error creating RedshiftSecurityGroup: %s", err)
	}

	d.SetId(d.Get("name").(string))

	log.Printf("[INFO] Redshift Security Group ID: %s", d.Id())
	sg, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)
	if err != nil {
		return err
	}

	ingresses := d.Get("ingress").(*schema.Set)
	for _, ing := range ingresses.List() {
		err := resourceAwsRedshiftSecurityGroupAuthorizeRule(ing, *sg.ClusterSecurityGroupName, conn)
		if err != nil {
			errs = append(errs, err)
		}
	}

	if len(errs) > 0 {
		return &multierror.Error{Errors: errs}
	}

	log.Println("[INFO] Waiting for Redshift Security Group Ingress Authorizations to be authorized")
	stateConf := &resource.StateChangeConf{
		Pending: []string{"authorizing"},
		Target:  []string{"authorized"},
		Refresh: resourceAwsRedshiftSecurityGroupStateRefreshFunc(d, meta),
		Timeout: 10 * time.Minute,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return err
	}

	return resourceAwsRedshiftSecurityGroupRead(d, meta)
}

func resourceAwsRedshiftSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
	sg, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)
	if err != nil {
		return err
	}

	rules := &schema.Set{
		F: resourceAwsRedshiftSecurityGroupIngressHash,
	}

	for _, v := range sg.IPRanges {
		rule := map[string]interface{}{"cidr": aws.StringValue(v.CIDRIP)}
		rules.Add(rule)
	}

	for _, g := range sg.EC2SecurityGroups {
		rule := map[string]interface{}{
			"security_group_name":     aws.StringValue(g.EC2SecurityGroupName),
			"security_group_owner_id": aws.StringValue(g.EC2SecurityGroupOwnerId),
		}
		rules.Add(rule)
	}

	d.Set("ingress", rules)
	d.Set("name", sg.ClusterSecurityGroupName)
	d.Set("description", sg.Description)

	return nil
}

func resourceAwsRedshiftSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	if d.HasChange("ingress") {
		o, n := d.GetChange("ingress")
		if o == nil {
			o = new(schema.Set)
		}
		if n == nil {
			n = new(schema.Set)
		}

		os := o.(*schema.Set)
		ns := n.(*schema.Set)

		removeIngressRules, err := expandRedshiftSGRevokeIngress(os.Difference(ns).List())
		if err != nil {
			return err
		}
		if len(removeIngressRules) > 0 {
			for _, r := range removeIngressRules {
				r.ClusterSecurityGroupName = aws.String(d.Id())

				_, err := conn.RevokeClusterSecurityGroupIngress(&r)
				if err != nil {
					return err
				}
			}
		}

		addIngressRules, err := expandRedshiftSGAuthorizeIngress(ns.Difference(os).List())
		if err != nil {
			return err
		}
		if len(addIngressRules) > 0 {
			for _, r := range addIngressRules {
				r.ClusterSecurityGroupName = aws.String(d.Id())

				_, err := conn.AuthorizeClusterSecurityGroupIngress(&r)
				if err != nil {
					return err
				}
			}
		}

	}
	return resourceAwsRedshiftSecurityGroupRead(d, meta)
}

func resourceAwsRedshiftSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	log.Printf("[DEBUG] Redshift Security Group destroy: %v", d.Id())
	opts := redshift.DeleteClusterSecurityGroupInput{
		ClusterSecurityGroupName: aws.String(d.Id()),
	}

	log.Printf("[DEBUG] Redshift Security Group destroy configuration: %v", opts)
	_, err := conn.DeleteClusterSecurityGroup(&opts)

	if err != nil {
		newerr, ok := err.(awserr.Error)
		if ok && newerr.Code() == "InvalidRedshiftSecurityGroup.NotFound" {
			return nil
		}
		return err
	}

	return nil
}

func resourceAwsRedshiftSecurityGroupRetrieve(d *schema.ResourceData, meta interface{}) (*redshift.ClusterSecurityGroup, error) {
	conn := meta.(*AWSClient).redshiftconn

	opts := redshift.DescribeClusterSecurityGroupsInput{
		ClusterSecurityGroupName: aws.String(d.Id()),
	}

	log.Printf("[DEBUG] Redshift Security Group describe configuration: %#v", opts)

	resp, err := conn.DescribeClusterSecurityGroups(&opts)

	if err != nil {
		return nil, fmt.Errorf("Error retrieving Redshift Security Groups: %s", err)
	}

	if len(resp.ClusterSecurityGroups) != 1 ||
		*resp.ClusterSecurityGroups[0].ClusterSecurityGroupName != d.Id() {
		return nil, fmt.Errorf("Unable to find Redshift Security Group: %#v", resp.ClusterSecurityGroups)
	}

	return resp.ClusterSecurityGroups[0], nil
}

func resourceAwsRedshiftSecurityGroupIngressHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})

	if v, ok := m["cidr"]; ok {
		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
	}

	if v, ok := m["security_group_name"]; ok {
		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
	}

	if v, ok := m["security_group_owner_id"]; ok {
		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
	}

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

func resourceAwsRedshiftSecurityGroupAuthorizeRule(ingress interface{}, redshiftSecurityGroupName string, conn *redshift.Redshift) error {
	ing := ingress.(map[string]interface{})

	opts := redshift.AuthorizeClusterSecurityGroupIngressInput{
		ClusterSecurityGroupName: aws.String(redshiftSecurityGroupName),
	}

	if attr, ok := ing["cidr"]; ok && attr != "" {
		opts.CIDRIP = aws.String(attr.(string))
	}

	if attr, ok := ing["security_group_name"]; ok && attr != "" {
		opts.EC2SecurityGroupName = aws.String(attr.(string))
	}

	if attr, ok := ing["security_group_owner_id"]; ok && attr != "" {
		opts.EC2SecurityGroupOwnerId = aws.String(attr.(string))
	}

	log.Printf("[DEBUG] Authorize ingress rule configuration: %#v", opts)
	_, err := conn.AuthorizeClusterSecurityGroupIngress(&opts)

	if err != nil {
		return fmt.Errorf("Error authorizing security group ingress: %s", err)
	}

	return nil
}

func resourceAwsRedshiftSecurityGroupStateRefreshFunc(
	d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		v, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)

		if err != nil {
			log.Printf("Error on retrieving Redshift Security Group when waiting: %s", err)
			return nil, "", err
		}

		statuses := make([]string, 0, len(v.EC2SecurityGroups)+len(v.IPRanges))
		for _, ec2g := range v.EC2SecurityGroups {
			statuses = append(statuses, *ec2g.Status)
		}
		for _, ips := range v.IPRanges {
			statuses = append(statuses, *ips.Status)
		}

		for _, stat := range statuses {
			// Not done
			if stat != "authorized" {
				return nil, "authorizing", nil
			}
		}

		return v, "authorized", nil
	}
}

func expandRedshiftSGAuthorizeIngress(configured []interface{}) ([]redshift.AuthorizeClusterSecurityGroupIngressInput, error) {
	var ingress []redshift.AuthorizeClusterSecurityGroupIngressInput

	// Loop over our configured parameters and create
	// an array of aws-sdk-go compatible objects
	for _, pRaw := range configured {
		data := pRaw.(map[string]interface{})

		i := redshift.AuthorizeClusterSecurityGroupIngressInput{}

		if v, ok := data["cidr"]; ok {
			i.CIDRIP = aws.String(v.(string))
		}

		if v, ok := data["security_group_name"]; ok {
			i.EC2SecurityGroupName = aws.String(v.(string))
		}

		if v, ok := data["security_group_owner_id"]; ok {
			i.EC2SecurityGroupOwnerId = aws.String(v.(string))
		}

		ingress = append(ingress, i)
	}

	return ingress, nil
}

func expandRedshiftSGRevokeIngress(configured []interface{}) ([]redshift.RevokeClusterSecurityGroupIngressInput, error) {
	var ingress []redshift.RevokeClusterSecurityGroupIngressInput

	// Loop over our configured parameters and create
	// an array of aws-sdk-go compatible objects
	for _, pRaw := range configured {
		data := pRaw.(map[string]interface{})

		i := redshift.RevokeClusterSecurityGroupIngressInput{}

		if v, ok := data["cidr"]; ok {
			i.CIDRIP = aws.String(v.(string))
		}

		if v, ok := data["security_group_name"]; ok {
			i.EC2SecurityGroupName = aws.String(v.(string))
		}

		if v, ok := data["security_group_owner_id"]; ok {
			i.EC2SecurityGroupOwnerId = aws.String(v.(string))
		}

		ingress = append(ingress, i)
	}

	return ingress, nil
}
<commit_msg>remove unused error return param<commit_after>package aws

import (
	"bytes"
	"fmt"
	"log"
	"regexp"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/redshift"
	"github.com/hashicorp/go-multierror"
	"github.com/hashicorp/terraform-plugin-sdk/helper/hashcode"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)

func resourceAwsRedshiftSecurityGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsRedshiftSecurityGroupCreate,
		Read:   resourceAwsRedshiftSecurityGroupRead,
		Update: resourceAwsRedshiftSecurityGroupUpdate,
		Delete: resourceAwsRedshiftSecurityGroupDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: validation.All(
					validation.StringLenBetween(1, 255),
					validation.StringNotInSlice([]string{"default"}, false),
					validation.StringMatch(regexp.MustCompile(`^[0-9a-z-]+$`), "must contain only lowercase alphanumeric characters and hyphens"),
				),
			},

			"description": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Default:  "Managed by Terraform",
			},

			"ingress": {
				Type:     schema.TypeSet,
				Required: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"cidr": {
							Type:     schema.TypeString,
							Optional: true,
						},

						"security_group_name": {
							Type:     schema.TypeString,
							Optional: true,
							Computed: true,
						},

						"security_group_owner_id": {
							Type:     schema.TypeString,
							Optional: true,
							Computed: true,
						},
					},
				},
				Set: resourceAwsRedshiftSecurityGroupIngressHash,
			},
		},
	}
}

func resourceAwsRedshiftSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	var err error
	var errs []error

	name := d.Get("name").(string)
	desc := d.Get("description").(string)
	sgInput := &redshift.CreateClusterSecurityGroupInput{
		ClusterSecurityGroupName: aws.String(name),
		Description:              aws.String(desc),
	}
	log.Printf("[DEBUG] Redshift security group create: name: %s, description: %s", name, desc)
	_, err = conn.CreateClusterSecurityGroup(sgInput)
	if err != nil {
		return fmt.Errorf("Error creating RedshiftSecurityGroup: %s", err)
	}

	d.SetId(d.Get("name").(string))

	log.Printf("[INFO] Redshift Security Group ID: %s", d.Id())
	sg, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)
	if err != nil {
		return err
	}

	ingresses := d.Get("ingress").(*schema.Set)
	for _, ing := range ingresses.List() {
		err := resourceAwsRedshiftSecurityGroupAuthorizeRule(ing, *sg.ClusterSecurityGroupName, conn)
		if err != nil {
			errs = append(errs, err)
		}
	}

	if len(errs) > 0 {
		return &multierror.Error{Errors: errs}
	}

	log.Println("[INFO] Waiting for Redshift Security Group Ingress Authorizations to be authorized")
	stateConf := &resource.StateChangeConf{
		Pending: []string{"authorizing"},
		Target:  []string{"authorized"},
		Refresh: resourceAwsRedshiftSecurityGroupStateRefreshFunc(d, meta),
		Timeout: 10 * time.Minute,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return err
	}

	return resourceAwsRedshiftSecurityGroupRead(d, meta)
}

func resourceAwsRedshiftSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
	sg, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)
	if err != nil {
		return err
	}

	rules := &schema.Set{
		F: resourceAwsRedshiftSecurityGroupIngressHash,
	}

	for _, v := range sg.IPRanges {
		rule := map[string]interface{}{"cidr": aws.StringValue(v.CIDRIP)}
		rules.Add(rule)
	}

	for _, g := range sg.EC2SecurityGroups {
		rule := map[string]interface{}{
			"security_group_name":     aws.StringValue(g.EC2SecurityGroupName),
			"security_group_owner_id": aws.StringValue(g.EC2SecurityGroupOwnerId),
		}
		rules.Add(rule)
	}

	d.Set("ingress", rules)
	d.Set("name", sg.ClusterSecurityGroupName)
	d.Set("description", sg.Description)

	return nil
}

func resourceAwsRedshiftSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	if d.HasChange("ingress") {
		o, n := d.GetChange("ingress")
		if o == nil {
			o = new(schema.Set)
		}
		if n == nil {
			n = new(schema.Set)
		}

		os := o.(*schema.Set)
		ns := n.(*schema.Set)

		removeIngressRules := expandRedshiftSGRevokeIngress(os.Difference(ns).List())
		if len(removeIngressRules) > 0 {
			for _, r := range removeIngressRules {
				r.ClusterSecurityGroupName = aws.String(d.Id())

				_, err := conn.RevokeClusterSecurityGroupIngress(&r)
				if err != nil {
					return err
				}
			}
		}

		addIngressRules := expandRedshiftSGAuthorizeIngress(ns.Difference(os).List())
		if len(addIngressRules) > 0 {
			for _, r := range addIngressRules {
				r.ClusterSecurityGroupName = aws.String(d.Id())

				_, err := conn.AuthorizeClusterSecurityGroupIngress(&r)
				if err != nil {
					return err
				}
			}
		}

	}
	return resourceAwsRedshiftSecurityGroupRead(d, meta)
}

func resourceAwsRedshiftSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).redshiftconn

	log.Printf("[DEBUG] Redshift Security Group destroy: %v", d.Id())
	opts := redshift.DeleteClusterSecurityGroupInput{
		ClusterSecurityGroupName: aws.String(d.Id()),
	}

	log.Printf("[DEBUG] Redshift Security Group destroy configuration: %v", opts)
	_, err := conn.DeleteClusterSecurityGroup(&opts)

	if err != nil {
		newerr, ok := err.(awserr.Error)
		if ok && newerr.Code() == "InvalidRedshiftSecurityGroup.NotFound" {
			return nil
		}
		return err
	}

	return nil
}

func resourceAwsRedshiftSecurityGroupRetrieve(d *schema.ResourceData, meta interface{}) (*redshift.ClusterSecurityGroup, error) {
	conn := meta.(*AWSClient).redshiftconn

	opts := redshift.DescribeClusterSecurityGroupsInput{
		ClusterSecurityGroupName: aws.String(d.Id()),
	}

	log.Printf("[DEBUG] Redshift Security Group describe configuration: %#v", opts)

	resp, err := conn.DescribeClusterSecurityGroups(&opts)

	if err != nil {
		return nil, fmt.Errorf("Error retrieving Redshift Security Groups: %s", err)
	}

	if len(resp.ClusterSecurityGroups) != 1 ||
		*resp.ClusterSecurityGroups[0].ClusterSecurityGroupName != d.Id() {
		return nil, fmt.Errorf("Unable to find Redshift Security Group: %#v", resp.ClusterSecurityGroups)
	}

	return resp.ClusterSecurityGroups[0], nil
}

func resourceAwsRedshiftSecurityGroupIngressHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})

	if v, ok := m["cidr"]; ok {
		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
	}

	if v, ok := m["security_group_name"]; ok {
		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
	}

	if v, ok := m["security_group_owner_id"]; ok {
		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
	}

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

func resourceAwsRedshiftSecurityGroupAuthorizeRule(ingress interface{}, redshiftSecurityGroupName string, conn *redshift.Redshift) error {
	ing := ingress.(map[string]interface{})

	opts := redshift.AuthorizeClusterSecurityGroupIngressInput{
		ClusterSecurityGroupName: aws.String(redshiftSecurityGroupName),
	}

	if attr, ok := ing["cidr"]; ok && attr != "" {
		opts.CIDRIP = aws.String(attr.(string))
	}

	if attr, ok := ing["security_group_name"]; ok && attr != "" {
		opts.EC2SecurityGroupName = aws.String(attr.(string))
	}

	if attr, ok := ing["security_group_owner_id"]; ok && attr != "" {
		opts.EC2SecurityGroupOwnerId = aws.String(attr.(string))
	}

	log.Printf("[DEBUG] Authorize ingress rule configuration: %#v", opts)
	_, err := conn.AuthorizeClusterSecurityGroupIngress(&opts)

	if err != nil {
		return fmt.Errorf("Error authorizing security group ingress: %s", err)
	}

	return nil
}

func resourceAwsRedshiftSecurityGroupStateRefreshFunc(
	d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		v, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta)

		if err != nil {
			log.Printf("Error on retrieving Redshift Security Group when waiting: %s", err)
			return nil, "", err
		}

		statuses := make([]string, 0, len(v.EC2SecurityGroups)+len(v.IPRanges))
		for _, ec2g := range v.EC2SecurityGroups {
			statuses = append(statuses, *ec2g.Status)
		}
		for _, ips := range v.IPRanges {
			statuses = append(statuses, *ips.Status)
		}

		for _, stat := range statuses {
			// Not done
			if stat != "authorized" {
				return nil, "authorizing", nil
			}
		}

		return v, "authorized", nil
	}
}

func expandRedshiftSGAuthorizeIngress(configured []interface{}) []redshift.AuthorizeClusterSecurityGroupIngressInput {
	var ingress []redshift.AuthorizeClusterSecurityGroupIngressInput

	// Loop over our configured parameters and create
	// an array of aws-sdk-go compatible objects
	for _, pRaw := range configured {
		data := pRaw.(map[string]interface{})

		i := redshift.AuthorizeClusterSecurityGroupIngressInput{}

		if v, ok := data["cidr"]; ok {
			i.CIDRIP = aws.String(v.(string))
		}

		if v, ok := data["security_group_name"]; ok {
			i.EC2SecurityGroupName = aws.String(v.(string))
		}

		if v, ok := data["security_group_owner_id"]; ok {
			i.EC2SecurityGroupOwnerId = aws.String(v.(string))
		}

		ingress = append(ingress, i)
	}

	return ingress
}

func expandRedshiftSGRevokeIngress(configured []interface{}) []redshift.RevokeClusterSecurityGroupIngressInput {
	var ingress []redshift.RevokeClusterSecurityGroupIngressInput

	// Loop over our configured parameters and create
	// an array of aws-sdk-go compatible objects
	for _, pRaw := range configured {
		data := pRaw.(map[string]interface{})

		i := redshift.RevokeClusterSecurityGroupIngressInput{}

		if v, ok := data["cidr"]; ok {
			i.CIDRIP = aws.String(v.(string))
		}

		if v, ok := data["security_group_name"]; ok {
			i.EC2SecurityGroupName = aws.String(v.(string))
		}

		if v, ok := data["security_group_owner_id"]; ok {
			i.EC2SecurityGroupOwnerId = aws.String(v.(string))
		}

		ingress = append(ingress, i)
	}

	return ingress
}
<|endoftext|>


<commit_before>package events

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"sync"
	"time"
)

// LimitExceeded is returned when the number of simultaneous connections to
// Urban Airship's Event API is exceeded. The API responds with a 402 Payment
// Required status which is translated into this error.
var LimitExceeded = errors.New("request was rate limited")

// Event is the envelope for a single even from Urban Airship's event stream.
// Users should inspect the Event's Type and call the corresponding method to
// receive a typed event body.
type Event struct {
	// ID uniquely identifies the event.
	ID       string    `json:"id"`
	Type     Type      `json:"type"`
	Occurred time.Time `json:"occurred"`

	// Processed is when the event was ingested by Urban Airship. There may be
	// lag between when the event occurred, and when it was processed.
	Processed time.Time `json:"processed"`

	// Offset is the event's location in the stream. Used to resume the stream
	// after severing a connection. Clients should store this value for the case
	// that the connection is severed.
	Offset uint64 `json:"offset,string"`

	// Body is the raw event body. Use the Type specific methods to unmarshal the
	// body.
	Body   json.RawMessage `json:"body"`
	Device *Device         `json:"device,omitempty"`
}

type Push struct {
	// PushID is the unique identifier for the push, included in responses to the
	// push API.
	PushID string `json:"push_id"`

	// GroupID is an optional identifier of the group this push is associated
	// with; group IDs are created by both automation and push to local time.
	GroupID string `json:"group_id"`
}

type PushBody struct {
	Push

	// Payload is the specification of the push as sent via the API.
	Payload []byte `json:"payload"`
}

func (e *Event) PushBody() (*PushBody, error) {
	if e.Type != TypePush {
		return nil, WrongType
	}
	p := PushBody{}
	if err := json.Unmarshal(e.Body, &p); err != nil {
		return nil, err
	}
	return &p, nil
}

type Open struct {
	// LastReceived contains the push identifier of the last notification Urban
	// Airship attempted to deliver to this device, if known. It may also include
	// a group identifier if the push was scheduled to the device’s local time or
	// if the push was an automation rule.
	//
	//TODO sync with post-2015-05-27 docs
	LastReceived *Push `json:"last_push_received,omitempty"`

	// ConvertingPush is present if the event was associated with a push. An
	// object containing the push ID of that notification. It may also include a
	// group ID if the push was a push to local time or automation rule.
	//
	//TODO sync with post-2015-05-27 docs
	ConvertingPush *Push `json:"converting_push,omitempty"`

	// SessionID is an identifier for the "session" of user activity. This key
	// will be absent if the application was initialized while backgrounded.
	SessionID string `json:"session_id"`
}

// Open returns an Open struct for OPEN events. Non-OPEN events will return
// the WrongType error.
func (e *Event) Open() (*Open, error) {
	if e.Type != TypeOpen {
		return nil, WrongType
	}
	o := Open{}
	if err := json.Unmarshal(e.Body, &o); err != nil {
		return nil, err
	}
	return &o, nil
}

// Send events are emitted for each device identified by the audience selection
// of a push. device will be present in the event to specify which channel
// received the push.
type Send struct {
	Push

	// VariantID is only present if the notification was sent as part of an
	// experiment.  Identifies the payload ultimately sent to a device.
	VariantID string `json:"variant_id,omitempty"`
}

// Send returns a Send struct for SEND events. Non-SEND events will return the
// WrongType error.
func (e *Event) Send() (*Send, error) {
	if e.Type != TypeSend {
		return nil, WrongType
	}
	s := Send{}
	if err := json.Unmarshal(e.Body, &s); err != nil {
		return nil, err
	}
	return &s, nil
}

// Close events are emitted Each time a user closes the application. Note that
// close events are often latent, as they may not be delivered over the network
// until much later.
type Close struct {
	SessionID string `json:"session_id"`
}

func (e *Event) Close() (*Close, error) {
	if e.Type != TypeClose {
		return nil, WrongType
	}
	c := Close{}
	if err := json.Unmarshal(e.Body, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

// TagChange events are emitted Each time a tag is added or removed from a
// channel.
type TagChange struct {
	// Add maps tag groups to tags. The set of tag group/tag pairs in this object
	// define the tags added to the device.
	Add map[string][]string `json:"add"`

	// Remove maps tag groups to tags. The set of tag group/tag pairs in this
	// object define the tags removed from the device.
	Remove map[string][]string `json:"remove"`

	// Current maps tag groups to tags. The set of tag group/tag pairs in this
	// object define the current state of the device AFTER the mutation has taken
	// effect.
	Current map[string][]string `json:"current"`
}

func (e *Event) TagChange() (*TagChange, error) {
	if e.Type != TypeTagChange {
		return nil, WrongType
	}
	t := TagChange{}
	if err := json.Unmarshal(e.Body, &t); err != nil {
		return nil, err
	}
	return &t, nil
}

// Location events include the latitude and longitude of the device.
type Location struct {
	Lat json.Number `json:"latitude"`
	Lon json.Number `json:"longitude"`

	// Foreground indicates whether the application was foregrounded when the
	// event fired.
	Foreground bool `json:"foreground"`
}

func (e *Event) Location() (*Location, error) {
	if e.Type != TypeLocation {
		return nil, WrongType
	}
	loc := Location{}
	if err := json.Unmarshal(e.Body, &loc); err != nil {
		return nil, err
	}
	return &loc, nil
}

// Response streams Events from a Fetch call.
type Response struct {
	// ID is the UA-Operation-Id header from Urban Airship's response.
	ID string

	out  chan *Event
	body io.ReadCloser

	mu     *sync.Mutex
	closed chan struct{}
	err    error
}

// NewResponse creates an events iterator from an http.Response. Fetch is a
// shortcut for creating a Response, but users can manually create a Response
// from a custom HTTP request with this function.
func NewResponse(resp *http.Response) (*Response, error) {
	if resp.StatusCode == 402 {
		return nil, LimitExceeded
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("unexpected non-200 response: %d", resp.StatusCode)
	}
	r := &Response{
		ID:     resp.Header.Get("UA-Operation-Id"),
		out:    make(chan *Event, 10), // provide some buffering
		body:   resp.Body,
		mu:     new(sync.Mutex),
		closed: make(chan struct{}),
	}
	go func() {
		dec := json.NewDecoder(r.body)
		for {
			var ev Event
			if err := dec.Decode(&ev); err != nil {
				r.mu.Lock()
				defer r.mu.Unlock()
				r.err = err
				close(r.out)
				return
			}
			select {
			case r.out <- &ev:
			case <-r.closed:
				return
			}
		}
	}()
	return r, nil
}

// Events returns a chan that emits Events until closed. Events is safe for
// concurrent calls and shares an underlying chan. This means events are not
// duplicated between multiple receivers.
func (r *Response) Events() <-chan *Event { return r.out }

// Close the events stream. Safe to call concurrently.
func (r *Response) Close() {
	r.mu.Lock()
	defer r.mu.Unlock()
	select {
	case <-r.closed:
		return
	default:
		close(r.closed)
		r.body.Close()
	}
}

// Err returns the error which caused the event stream to end or nil. May be
// checked when the chan returned by Events() is closed. Safe for concurrent
// access.
func (r *Response) Err() error {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.err
}
<commit_msg>close response channel before body<commit_after>package events

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"sync"
	"time"
)

// LimitExceeded is returned when the number of simultaneous connections to
// Urban Airship's Event API is exceeded. The API responds with a 402 Payment
// Required status which is translated into this error.
var LimitExceeded = errors.New("request was rate limited")

// Event is the envelope for a single even from Urban Airship's event stream.
// Users should inspect the Event's Type and call the corresponding method to
// receive a typed event body.
type Event struct {
	// ID uniquely identifies the event.
	ID       string    `json:"id"`
	Type     Type      `json:"type"`
	Occurred time.Time `json:"occurred"`

	// Processed is when the event was ingested by Urban Airship. There may be
	// lag between when the event occurred, and when it was processed.
	Processed time.Time `json:"processed"`

	// Offset is the event's location in the stream. Used to resume the stream
	// after severing a connection. Clients should store this value for the case
	// that the connection is severed.
	Offset uint64 `json:"offset,string"`

	// Body is the raw event body. Use the Type specific methods to unmarshal the
	// body.
	Body   json.RawMessage `json:"body"`
	Device *Device         `json:"device,omitempty"`
}

type Push struct {
	// PushID is the unique identifier for the push, included in responses to the
	// push API.
	PushID string `json:"push_id"`

	// GroupID is an optional identifier of the group this push is associated
	// with; group IDs are created by both automation and push to local time.
	GroupID string `json:"group_id"`
}

type PushBody struct {
	Push

	// Payload is the specification of the push as sent via the API.
	Payload []byte `json:"payload"`
}

func (e *Event) PushBody() (*PushBody, error) {
	if e.Type != TypePush {
		return nil, WrongType
	}
	p := PushBody{}
	if err := json.Unmarshal(e.Body, &p); err != nil {
		return nil, err
	}
	return &p, nil
}

type Open struct {
	// LastReceived contains the push identifier of the last notification Urban
	// Airship attempted to deliver to this device, if known. It may also include
	// a group identifier if the push was scheduled to the device’s local time or
	// if the push was an automation rule.
	//
	//TODO sync with post-2015-05-27 docs
	LastReceived *Push `json:"last_push_received,omitempty"`

	// ConvertingPush is present if the event was associated with a push. An
	// object containing the push ID of that notification. It may also include a
	// group ID if the push was a push to local time or automation rule.
	//
	//TODO sync with post-2015-05-27 docs
	ConvertingPush *Push `json:"converting_push,omitempty"`

	// SessionID is an identifier for the "session" of user activity. This key
	// will be absent if the application was initialized while backgrounded.
	SessionID string `json:"session_id"`
}

// Open returns an Open struct for OPEN events. Non-OPEN events will return
// the WrongType error.
func (e *Event) Open() (*Open, error) {
	if e.Type != TypeOpen {
		return nil, WrongType
	}
	o := Open{}
	if err := json.Unmarshal(e.Body, &o); err != nil {
		return nil, err
	}
	return &o, nil
}

// Send events are emitted for each device identified by the audience selection
// of a push. device will be present in the event to specify which channel
// received the push.
type Send struct {
	Push

	// VariantID is only present if the notification was sent as part of an
	// experiment.  Identifies the payload ultimately sent to a device.
	VariantID string `json:"variant_id,omitempty"`
}

// Send returns a Send struct for SEND events. Non-SEND events will return the
// WrongType error.
func (e *Event) Send() (*Send, error) {
	if e.Type != TypeSend {
		return nil, WrongType
	}
	s := Send{}
	if err := json.Unmarshal(e.Body, &s); err != nil {
		return nil, err
	}
	return &s, nil
}

// Close events are emitted Each time a user closes the application. Note that
// close events are often latent, as they may not be delivered over the network
// until much later.
type Close struct {
	SessionID string `json:"session_id"`
}

func (e *Event) Close() (*Close, error) {
	if e.Type != TypeClose {
		return nil, WrongType
	}
	c := Close{}
	if err := json.Unmarshal(e.Body, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

// TagChange events are emitted Each time a tag is added or removed from a
// channel.
type TagChange struct {
	// Add maps tag groups to tags. The set of tag group/tag pairs in this object
	// define the tags added to the device.
	Add map[string][]string `json:"add"`

	// Remove maps tag groups to tags. The set of tag group/tag pairs in this
	// object define the tags removed from the device.
	Remove map[string][]string `json:"remove"`

	// Current maps tag groups to tags. The set of tag group/tag pairs in this
	// object define the current state of the device AFTER the mutation has taken
	// effect.
	Current map[string][]string `json:"current"`
}

func (e *Event) TagChange() (*TagChange, error) {
	if e.Type != TypeTagChange {
		return nil, WrongType
	}
	t := TagChange{}
	if err := json.Unmarshal(e.Body, &t); err != nil {
		return nil, err
	}
	return &t, nil
}

// Location events include the latitude and longitude of the device.
type Location struct {
	Lat json.Number `json:"latitude"`
	Lon json.Number `json:"longitude"`

	// Foreground indicates whether the application was foregrounded when the
	// event fired.
	Foreground bool `json:"foreground"`
}

func (e *Event) Location() (*Location, error) {
	if e.Type != TypeLocation {
		return nil, WrongType
	}
	loc := Location{}
	if err := json.Unmarshal(e.Body, &loc); err != nil {
		return nil, err
	}
	return &loc, nil
}

// Response streams Events from a Fetch call.
type Response struct {
	// ID is the UA-Operation-Id header from Urban Airship's response.
	ID string

	out  chan *Event
	body io.ReadCloser

	mu     *sync.Mutex
	closed chan struct{}
	err    error
}

// NewResponse creates an events iterator from an http.Response. Fetch is a
// shortcut for creating a Response, but users can manually create a Response
// from a custom HTTP request with this function.
func NewResponse(resp *http.Response) (*Response, error) {
	if resp.StatusCode == 402 {
		return nil, LimitExceeded
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("unexpected non-200 response: %d", resp.StatusCode)
	}
	r := &Response{
		ID:     resp.Header.Get("UA-Operation-Id"),
		out:    make(chan *Event, 10), // provide some buffering
		body:   resp.Body,
		mu:     new(sync.Mutex),
		closed: make(chan struct{}),
	}
	go func() {
		dec := json.NewDecoder(r.body)
		for {
			var ev Event
			if err := dec.Decode(&ev); err != nil {
				r.mu.Lock()
				defer r.mu.Unlock()
				r.err = err
				close(r.out)
				return
			}
			select {
			case r.out <- &ev:
			case <-r.closed:
				return
			}
		}
	}()
	return r, nil
}

// Events returns a chan that emits Events until closed. Events is safe for
// concurrent calls and shares an underlying chan. This means events are not
// duplicated between multiple receivers.
func (r *Response) Events() <-chan *Event { return r.out }

// Close the events stream. Safe to call concurrently.
func (r *Response) Close() {
	r.mu.Lock()
	defer r.mu.Unlock()
	select {
	case <-r.closed:
		return
	default:
		r.body.Close()
		close(r.closed)
	}
}

// Err returns the error which caused the event stream to end or nil. May be
// checked when the chan returned by Events() is closed. Safe for concurrent
// access.
func (r *Response) Err() error {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.err
}
<|endoftext|>