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

// cloudsql-proxy can be used as a proxy to Cloud SQL databases. It supports
// connecting to many instances and authenticating via different means.
// Specifically, a list of instances may be provided on the command line, in
// GCE metadata (for VMs), or provided during connection time via a
// FUSE-mounted directory. See flags for a more specific explanation.
package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/certs"
	"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/fuse"
	"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy"

	"golang.org/x/net/context"
	"golang.org/x/oauth2"
	goauth "golang.org/x/oauth2/google"
	sqladmin "google.golang.org/api/sqladmin/v1beta4"
	"google.golang.org/cloud/compute/metadata"
)

var (
	version = flag.Bool("version", false, "Print the version of the proxy and exit")

	checkRegion = flag.Bool("check_region", false, `If specified, the 'region' portion of the connection string is required for
UNIX socket-based connections.`)

	// Settings for how to choose which instance to connect to.
	dir      = flag.String("dir", "", "Directory to use for placing UNIX sockets representing database instances")
	projects = flag.String("projects", "", `Open sockets for each Cloud SQL Instance in the projects specified
(comma-separated list)`)
	instances = flag.String("instances", "", `Comma-separated list of fully qualified instances (project:region:name)
to connect to. If the name has the suffix '=tcp:port', a TCP server is opened
on the specified port to proxy to that instance. Otherwise, one socket file per
instance is opened in 'dir'. Not compatible with -fuse`)
	instanceSrc = flag.String("instances_metadata", "", `If provided, it is treated as a path to a metadata value which
is polled for a comma-separated list of instances to connect to. For example,
to use the instance metadata value named 'cloud-sql-instances' you would
provide 'instance/attributes/cloud-sql-instances'. Not compatible with -fuse`)
	useFuse = flag.Bool("fuse", false, `Mount a directory at 'dir' using FUSE for accessing instances. Note that the
directory at 'dir' must be empty before this program is started.`)
	fuseTmp = flag.String("fuse_tmp", defaultTmp, `Used as a temporary directory if -fuse is set. Note that files in this directory
can be removed automatically by this program.`)

	// Settings for authentication.
	token     = flag.String("token", "", "When set, the proxy uses this Bearer token for authorization.")
	tokenFile = flag.String("credential_file", "", `If provided, this json file will be used to retrieve Service Account credentials.
You may set the GOOGLE_APPLICATION_CREDENTIALS environment variable for the same effect.`)
)

const (
	host = "https://www.googleapis.com/sql/v1beta4/"
	port = 3307
)

func init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, `
The Cloud SQL Proxy allows simple, secure connectivity to Google Cloud SQL. It
is a long-running process that opens local sockets (either TCP or UNIX sockets)
according to the flags passed to it. A local application connects to a Cloud
SQL instance by using the corresponding socket.

Authorization:
  * On Google Compute Engine, the default service account is used.
    The Cloud SQL API must be enabled for the VM.

  * When gcloud is installed on the local machine, the "active account" is used
    for authentication. Run 'gcloud auth list' to see which accounts are
    installed on your local machine and 'gcloud config list account' to view
    the active account.

  * To configure the proxy using a service account, pass the -credential_file
    flag or set the GOOGLE_APPLICATION_CREDENTIALS environment variable. This
    will override gcloud or GCE credentials (if they exist).

Connection:
  -instances
    To connect to a specific list of instances, set the instances flag to a
    comma-separated list of instance connection strings. For example:

           -instances my-project:my-region:my-instance

    For connectivity over TCP, you must specify a tcp port as part of the
    instance string. For example, the following example opens a loopback TCP
    socket on port 3306, which will be proxied to connect to the instance
    'my-instance' in project 'my-project':

            -instances my-project:my-region:my-instance=tcp:3306

     When connecting over TCP, the -instances flag is required.

  -projects
    To direct the proxy to connect to all instances in a specific project, set
    the projects flag:

       -projects my-project

  -fuse
    If your local environment has FUSE installed, you can specify the -fuse
    flag to avoid the requirement to specify instances in advance. With FUSE,
    any attempts to open a UNIX socket in the directory specified by -dir
    automatically creates that socket and connects to the corresponding
    instance.

  -dir
    When using UNIX sockets (the default for systems which support them), the
    Proxy places the sockets in the directory specified by the -dir flag.

Automatic instance discovery:
    If gcloud is installed on the local machine and no instance connection flags
    are specified, the proxy connects to all instances in the gcloud active
    project, Run 'gcloud config list project' to display the active project.

Information for all flags:
`)
		flag.VisitAll(func(f *flag.Flag) {
			usage := strings.Replace(f.Usage, "\n", "\n    ", -1)
			fmt.Fprintf(os.Stderr, "  -%s\n    %s\n\n", f.Name, usage)
		})
	}
}

const sqlScope = "https://www.googleapis.com/auth/sqlservice.admin"

var defaultTmp = filepath.Join(os.TempDir(), "cloudsql-proxy-tmp")

// See https://github.com/GoogleCloudPlatform/gcloud-golang/issues/194
func onGCE() bool {
	res, err := http.Get("http://metadata.google.internal")
	if err != nil {
		return false
	}
	return res.Header.Get("Metadata-Flavor") == "Google"
}

var versionString = "NO_VERSION_SET"

const accountErrorSuffix = `Please create a new VM with Cloud SQL access (scope) enabled under "Identity and API access". Alternatively, create a new "service account key" and specify it using the -credentials_file parameter`

func checkFlags(onGCE bool) error {
	if !onGCE {
		if *instanceSrc != "" {
			return errors.New("-instances_metadata unsupported outside of Google Compute Engine")
		}
		return nil
	}
	scopes, err := metadata.Scopes("default")
	if err != nil {
		if _, ok := err.(metadata.NotDefinedError); ok {
			return errors.New("no service account found for this Compute Engine VM. " + accountErrorSuffix)
		}
		return fmt.Errorf("error checking scopes: %T %v | %+v", err, err, err)
	}

	ok := false
	for _, sc := range scopes {
		if sc == "https://www.googleapis.com/auth/sqladmin" || sc == "https://www.googleapis.com/auth/cloud-platform" {
			ok = true
			break
		}
	}
	if !ok {
		return errors.New(`the default Compute Engine service account is not configured with sufficient permissions to access the Cloud SQL API from this VM. ` + accountErrorSuffix)
	}
	return nil
}

func authenticatedClient(ctx context.Context) (*http.Client, error) {
	if f := *tokenFile; f != "" {
		all, err := ioutil.ReadFile(f)
		if err != nil {
			return nil, fmt.Errorf("invalid json file %q: %v", f, err)
		}
		cfg, err := goauth.JWTConfigFromJSON(all, sqlScope)
		if err != nil {
			return nil, fmt.Errorf("invalid json file %q: %v", f, err)
		}
		return cfg.Client(ctx), nil
	} else if tok := *token; tok != "" {
		src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: tok})
		return oauth2.NewClient(ctx, src), nil
	}

	return goauth.DefaultClient(ctx, sqlScope)
}

func stringList(s string) []string {
	spl := strings.Split(s, ",")
	if len(spl) == 1 && spl[0] == "" {
		return nil
	}
	return spl
}

func listInstances(ctx context.Context, cl *http.Client, projects []string) ([]string, error) {
	if len(projects) == 0 {
		// No projects requested.
		return nil, nil
	}

	sql, err := sqladmin.New(cl)
	if err != nil {
		return nil, err
	}

	ch := make(chan string)
	var wg sync.WaitGroup
	wg.Add(len(projects))
	for _, proj := range projects {
		proj := proj
		go func() {
			err := sql.Instances.List(proj).Pages(ctx, func(r *sqladmin.InstancesListResponse) error {
				for _, in := range r.Items {
					// The Proxy is only support on Second Gen
					if in.BackendType == "SECOND_GEN" {
						ch <- fmt.Sprintf("%s:%s:%s", in.Project, in.Region, in.Name)
					}
				}
				return nil
			})
			if err != nil {
				log.Printf("Error listing instances in %v: %v", proj, err)
			}
			wg.Done()
		}()
	}
	go func() {
		wg.Wait()
		close(ch)
	}()
	var ret []string
	for x := range ch {
		ret = append(ret, x)
	}
	if len(ret) == 0 {
		return nil, fmt.Errorf("no Cloud SQL Instances found in these projects: %v", projects)
	}
	return ret, nil
}

func gcloudProject() []string {
	buf := new(bytes.Buffer)
	cmd := exec.Command("gcloud", "--format", "json", "config", "list", "core/project")
	cmd.Stdout = buf

	if err := cmd.Run(); err != nil {
		if strings.Contains(err.Error(), "executable file not found") {
			// gcloud not installed; ignore the error
			return nil
		}
		log.Printf("Error detecting gcloud project: %v", err)
		return nil
	}

	var data struct {
		Core struct {
			Project string
		}
	}

	if err := json.Unmarshal(buf.Bytes(), &data); err != nil {
		log.Printf("Failed to unmarshal bytes from gcloud: %v", err)
		log.Printf("   gcloud returned:\n%s", buf)
		return nil
	}

	log.Printf("Using gcloud's active project: %v", data.Core.Project)
	return []string{data.Core.Project}
}

// Main executes the main function of the proxy, allowing it to be called from tests.
//
// Setting timeout to a value greater than 0 causes the process to panic after
// that amount of time. This is to sidestep an issue where sending a Signal to
// the process (via the SSH library) doesn't seem to have an effect, and
// closing the SSH session causes the process to get leaked. This timeout will
// at least cause the proxy to exit eventually.
func Main(timeout time.Duration) {
	if timeout > 0 {
		go func() {
			time.Sleep(timeout)
			panic("timeout exceeded")
		}()
	}
	main()
}

func main() {
	flag.Parse()

	if *version {
		fmt.Println("Cloud SQL Proxy:", versionString)
		return
	}

	instList := stringList(*instances)
	projList := stringList(*projects)
	// TODO: it'd be really great to consolidate flag verification in one place.
	if len(instList) == 0 && *instanceSrc == "" && len(projList) == 0 && !*useFuse {
		projList = gcloudProject()
	}

	onGCE := onGCE()
	if err := checkFlags(onGCE); err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()
	client, err := authenticatedClient(ctx)
	if err != nil {
		log.Fatal(err)
	}

	ins, err := listInstances(ctx, client, projList)
	if err != nil {
		log.Fatal(err)
	}
	instList = append(instList, ins...)

	cfgs, err := CreateInstanceConfigs(*dir, *useFuse, instList, *instanceSrc)
	if err != nil {
		log.Fatal(err)
	}

	// All active connections are stored in this variable.
	connset := proxy.NewConnSet()

	// Initialize a source of new connections to Cloud SQL instances.
	var connSrc <-chan proxy.Conn
	if *useFuse {
		c, fuse, err := fuse.NewConnSrc(*dir, *fuseTmp, connset)
		if err != nil {
			log.Fatalf("Could not start fuse directory at %q: %v", *dir, err)
		}
		connSrc = c
		defer fuse.Close()
	} else {
		updates := make(chan string)
		if *instanceSrc != "" {
			go func() {
				for {
					err := metadata.Subscribe(*instanceSrc, func(v string, ok bool) error {
						if ok {
							updates <- v
						}
						return nil
					})
					if err != nil {
						log.Print(err)
					}
					time.Sleep(5 * time.Second)
				}
			}()
		}

		c, err := WatchInstances(*dir, cfgs, updates)
		if err != nil {
			log.Fatal(err)
		}
		connSrc = c
	}

	log.Print("Ready for new connections")

	src, err := certs.NewCertSource(host, client, *checkRegion)
	if err != nil {
		log.Fatal(err)
	}

	(&proxy.Client{
		Port:  port,
		Certs: src,
		Conns: connset,
	}).Run(connSrc)
}
<commit_msg>Replacing incorrect scope name<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// cloudsql-proxy can be used as a proxy to Cloud SQL databases. It supports
// connecting to many instances and authenticating via different means.
// Specifically, a list of instances may be provided on the command line, in
// GCE metadata (for VMs), or provided during connection time via a
// FUSE-mounted directory. See flags for a more specific explanation.
package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/certs"
	"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/fuse"
	"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy"

	"golang.org/x/net/context"
	"golang.org/x/oauth2"
	goauth "golang.org/x/oauth2/google"
	sqladmin "google.golang.org/api/sqladmin/v1beta4"
	"google.golang.org/cloud/compute/metadata"
)

var (
	version = flag.Bool("version", false, "Print the version of the proxy and exit")

	checkRegion = flag.Bool("check_region", false, `If specified, the 'region' portion of the connection string is required for
UNIX socket-based connections.`)

	// Settings for how to choose which instance to connect to.
	dir      = flag.String("dir", "", "Directory to use for placing UNIX sockets representing database instances")
	projects = flag.String("projects", "", `Open sockets for each Cloud SQL Instance in the projects specified
(comma-separated list)`)
	instances = flag.String("instances", "", `Comma-separated list of fully qualified instances (project:region:name)
to connect to. If the name has the suffix '=tcp:port', a TCP server is opened
on the specified port to proxy to that instance. Otherwise, one socket file per
instance is opened in 'dir'. Not compatible with -fuse`)
	instanceSrc = flag.String("instances_metadata", "", `If provided, it is treated as a path to a metadata value which
is polled for a comma-separated list of instances to connect to. For example,
to use the instance metadata value named 'cloud-sql-instances' you would
provide 'instance/attributes/cloud-sql-instances'. Not compatible with -fuse`)
	useFuse = flag.Bool("fuse", false, `Mount a directory at 'dir' using FUSE for accessing instances. Note that the
directory at 'dir' must be empty before this program is started.`)
	fuseTmp = flag.String("fuse_tmp", defaultTmp, `Used as a temporary directory if -fuse is set. Note that files in this directory
can be removed automatically by this program.`)

	// Settings for authentication.
	token     = flag.String("token", "", "When set, the proxy uses this Bearer token for authorization.")
	tokenFile = flag.String("credential_file", "", `If provided, this json file will be used to retrieve Service Account credentials.
You may set the GOOGLE_APPLICATION_CREDENTIALS environment variable for the same effect.`)
)

const (
	host = "https://www.googleapis.com/sql/v1beta4/"
	port = 3307
)

func init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, `
The Cloud SQL Proxy allows simple, secure connectivity to Google Cloud SQL. It
is a long-running process that opens local sockets (either TCP or UNIX sockets)
according to the flags passed to it. A local application connects to a Cloud
SQL instance by using the corresponding socket.

Authorization:
  * On Google Compute Engine, the default service account is used.
    The Cloud SQL API must be enabled for the VM.

  * When gcloud is installed on the local machine, the "active account" is used
    for authentication. Run 'gcloud auth list' to see which accounts are
    installed on your local machine and 'gcloud config list account' to view
    the active account.

  * To configure the proxy using a service account, pass the -credential_file
    flag or set the GOOGLE_APPLICATION_CREDENTIALS environment variable. This
    will override gcloud or GCE credentials (if they exist).

Connection:
  -instances
    To connect to a specific list of instances, set the instances flag to a
    comma-separated list of instance connection strings. For example:

           -instances my-project:my-region:my-instance

    For connectivity over TCP, you must specify a tcp port as part of the
    instance string. For example, the following example opens a loopback TCP
    socket on port 3306, which will be proxied to connect to the instance
    'my-instance' in project 'my-project':

            -instances my-project:my-region:my-instance=tcp:3306

     When connecting over TCP, the -instances flag is required.

  -projects
    To direct the proxy to connect to all instances in a specific project, set
    the projects flag:

       -projects my-project

  -fuse
    If your local environment has FUSE installed, you can specify the -fuse
    flag to avoid the requirement to specify instances in advance. With FUSE,
    any attempts to open a UNIX socket in the directory specified by -dir
    automatically creates that socket and connects to the corresponding
    instance.

  -dir
    When using UNIX sockets (the default for systems which support them), the
    Proxy places the sockets in the directory specified by the -dir flag.

Automatic instance discovery:
    If gcloud is installed on the local machine and no instance connection flags
    are specified, the proxy connects to all instances in the gcloud active
    project, Run 'gcloud config list project' to display the active project.

Information for all flags:
`)
		flag.VisitAll(func(f *flag.Flag) {
			usage := strings.Replace(f.Usage, "\n", "\n    ", -1)
			fmt.Fprintf(os.Stderr, "  -%s\n    %s\n\n", f.Name, usage)
		})
	}
}

const sqlScope = "https://www.googleapis.com/auth/sqlservice.admin"

var defaultTmp = filepath.Join(os.TempDir(), "cloudsql-proxy-tmp")

// See https://github.com/GoogleCloudPlatform/gcloud-golang/issues/194
func onGCE() bool {
	res, err := http.Get("http://metadata.google.internal")
	if err != nil {
		return false
	}
	return res.Header.Get("Metadata-Flavor") == "Google"
}

var versionString = "NO_VERSION_SET"

const accountErrorSuffix = `Please create a new VM with Cloud SQL access (scope) enabled under "Identity and API access". Alternatively, create a new "service account key" and specify it using the -credentials_file parameter`

func checkFlags(onGCE bool) error {
	if !onGCE {
		if *instanceSrc != "" {
			return errors.New("-instances_metadata unsupported outside of Google Compute Engine")
		}
		return nil
	}
	scopes, err := metadata.Scopes("default")
	if err != nil {
		if _, ok := err.(metadata.NotDefinedError); ok {
			return errors.New("no service account found for this Compute Engine VM. " + accountErrorSuffix)
		}
		return fmt.Errorf("error checking scopes: %T %v | %+v", err, err, err)
	}

	ok := false
	for _, sc := range scopes {
		if sc == sqlScope || sc == "https://www.googleapis.com/auth/cloud-platform" {
			ok = true
			break
		}
	}
	if !ok {
		return errors.New(`the default Compute Engine service account is not configured with sufficient permissions to access the Cloud SQL API from this VM. ` + accountErrorSuffix)
	}
	return nil
}

func authenticatedClient(ctx context.Context) (*http.Client, error) {
	if f := *tokenFile; f != "" {
		all, err := ioutil.ReadFile(f)
		if err != nil {
			return nil, fmt.Errorf("invalid json file %q: %v", f, err)
		}
		cfg, err := goauth.JWTConfigFromJSON(all, sqlScope)
		if err != nil {
			return nil, fmt.Errorf("invalid json file %q: %v", f, err)
		}
		return cfg.Client(ctx), nil
	} else if tok := *token; tok != "" {
		src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: tok})
		return oauth2.NewClient(ctx, src), nil
	}

	return goauth.DefaultClient(ctx, sqlScope)
}

func stringList(s string) []string {
	spl := strings.Split(s, ",")
	if len(spl) == 1 && spl[0] == "" {
		return nil
	}
	return spl
}

func listInstances(ctx context.Context, cl *http.Client, projects []string) ([]string, error) {
	if len(projects) == 0 {
		// No projects requested.
		return nil, nil
	}

	sql, err := sqladmin.New(cl)
	if err != nil {
		return nil, err
	}

	ch := make(chan string)
	var wg sync.WaitGroup
	wg.Add(len(projects))
	for _, proj := range projects {
		proj := proj
		go func() {
			err := sql.Instances.List(proj).Pages(ctx, func(r *sqladmin.InstancesListResponse) error {
				for _, in := range r.Items {
					// The Proxy is only support on Second Gen
					if in.BackendType == "SECOND_GEN" {
						ch <- fmt.Sprintf("%s:%s:%s", in.Project, in.Region, in.Name)
					}
				}
				return nil
			})
			if err != nil {
				log.Printf("Error listing instances in %v: %v", proj, err)
			}
			wg.Done()
		}()
	}
	go func() {
		wg.Wait()
		close(ch)
	}()
	var ret []string
	for x := range ch {
		ret = append(ret, x)
	}
	if len(ret) == 0 {
		return nil, fmt.Errorf("no Cloud SQL Instances found in these projects: %v", projects)
	}
	return ret, nil
}

func gcloudProject() []string {
	buf := new(bytes.Buffer)
	cmd := exec.Command("gcloud", "--format", "json", "config", "list", "core/project")
	cmd.Stdout = buf

	if err := cmd.Run(); err != nil {
		if strings.Contains(err.Error(), "executable file not found") {
			// gcloud not installed; ignore the error
			return nil
		}
		log.Printf("Error detecting gcloud project: %v", err)
		return nil
	}

	var data struct {
		Core struct {
			Project string
		}
	}

	if err := json.Unmarshal(buf.Bytes(), &data); err != nil {
		log.Printf("Failed to unmarshal bytes from gcloud: %v", err)
		log.Printf("   gcloud returned:\n%s", buf)
		return nil
	}

	log.Printf("Using gcloud's active project: %v", data.Core.Project)
	return []string{data.Core.Project}
}

// Main executes the main function of the proxy, allowing it to be called from tests.
//
// Setting timeout to a value greater than 0 causes the process to panic after
// that amount of time. This is to sidestep an issue where sending a Signal to
// the process (via the SSH library) doesn't seem to have an effect, and
// closing the SSH session causes the process to get leaked. This timeout will
// at least cause the proxy to exit eventually.
func Main(timeout time.Duration) {
	if timeout > 0 {
		go func() {
			time.Sleep(timeout)
			panic("timeout exceeded")
		}()
	}
	main()
}

func main() {
	flag.Parse()

	if *version {
		fmt.Println("Cloud SQL Proxy:", versionString)
		return
	}

	instList := stringList(*instances)
	projList := stringList(*projects)
	// TODO: it'd be really great to consolidate flag verification in one place.
	if len(instList) == 0 && *instanceSrc == "" && len(projList) == 0 && !*useFuse {
		projList = gcloudProject()
	}

	onGCE := onGCE()
	if err := checkFlags(onGCE); err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()
	client, err := authenticatedClient(ctx)
	if err != nil {
		log.Fatal(err)
	}

	ins, err := listInstances(ctx, client, projList)
	if err != nil {
		log.Fatal(err)
	}
	instList = append(instList, ins...)

	cfgs, err := CreateInstanceConfigs(*dir, *useFuse, instList, *instanceSrc)
	if err != nil {
		log.Fatal(err)
	}

	// All active connections are stored in this variable.
	connset := proxy.NewConnSet()

	// Initialize a source of new connections to Cloud SQL instances.
	var connSrc <-chan proxy.Conn
	if *useFuse {
		c, fuse, err := fuse.NewConnSrc(*dir, *fuseTmp, connset)
		if err != nil {
			log.Fatalf("Could not start fuse directory at %q: %v", *dir, err)
		}
		connSrc = c
		defer fuse.Close()
	} else {
		updates := make(chan string)
		if *instanceSrc != "" {
			go func() {
				for {
					err := metadata.Subscribe(*instanceSrc, func(v string, ok bool) error {
						if ok {
							updates <- v
						}
						return nil
					})
					if err != nil {
						log.Print(err)
					}
					time.Sleep(5 * time.Second)
				}
			}()
		}

		c, err := WatchInstances(*dir, cfgs, updates)
		if err != nil {
			log.Fatal(err)
		}
		connSrc = c
	}

	log.Print("Ready for new connections")

	src, err := certs.NewCertSource(host, client, *checkRegion)
	if err != nil {
		log.Fatal(err)
	}

	(&proxy.Client{
		Port:  port,
		Certs: src,
		Conns: connset,
	}).Run(connSrc)
}
<|endoftext|>


<commit_before>// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.

package termui

import (
	"fmt"
)

// This is the implemetation of multi-colored or stacked bar graph.  This is different from default barGraph which is implemented in bar.go
// Multi-Colored-BarChart creates multiple bars in a widget:
/*
   bc := termui.NewMBarChart()
   data := make([][]int, 2)
   data[0] := []int{3, 2, 5, 7, 9, 4}
   data[1] := []int{7, 8, 5, 3, 1, 6}
   bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
   bc.BorderLabel = "Bar Chart"
   bc.Data = data
   bc.Width = 26
   bc.Height = 10
   bc.DataLabels = bclabels
   bc.TextColor = termui.ColorGreen
   bc.BarColor = termui.ColorRed
   bc.NumColor = termui.ColorYellow
*/
type MBarChart struct {
	Block
	BarColor   [NumberofColors]Attribute
	TextColor  Attribute
	NumColor   [NumberofColors]Attribute
	Data       [NumberofColors][]int
	DataLabels []string
	BarWidth   int
	BarGap     int
	labels     [][]rune
	dataNum    [NumberofColors][][]rune
	numBar     int
	scale      float64
	max        int
	minDataLen int
	numStack   int
	ShowScale  bool
	maxScale   []rune
}

// NewBarChart returns a new *BarChart with current theme.
func NewMBarChart() *MBarChart {
	bc := &MBarChart{Block: *NewBlock()}
	bc.BarColor[0] = ThemeAttr("mbarchart.bar.bg")
	bc.NumColor[0] = ThemeAttr("mbarchart.num.fg")
	bc.TextColor = ThemeAttr("mbarchart.text.fg")
	bc.BarGap = 1
	bc.BarWidth = 3
	return bc
}

func (bc *MBarChart) layout() {
	bc.numBar = bc.innerArea.Dx() / (bc.BarGap + bc.BarWidth)
	bc.labels = make([][]rune, bc.numBar)
	DataLen := 0
	LabelLen := len(bc.DataLabels)
	bc.minDataLen = 9999 //Set this to some very hight value so that we find the minimum one We want to know which array among data[][] has got the least length

	// We need to know how many stack/data array data[0] , data[1] are there
	for i := 0; i < len(bc.Data); i++ {
		if bc.Data[i] == nil {
			break
		}
		DataLen++
	}
	bc.numStack = DataLen

	//We need to know what is the mimimum size of data array data[0] could have 10 elements data[1] could have only 5, so we plot only 5 bar graphs

	for i := 0; i < DataLen; i++ {
		if bc.minDataLen > len(bc.Data[i]) {
			bc.minDataLen = len(bc.Data[i])
		}
	}

	if LabelLen > bc.minDataLen {
		LabelLen = bc.minDataLen
	}

	for i := 0; i < LabelLen && i < bc.numBar; i++ {
		bc.labels[i] = trimStr2Runes(bc.DataLabels[i], bc.BarWidth)
	}

	for i := 0; i < bc.numStack; i++ {
		bc.dataNum[i] = make([][]rune, len(bc.Data[i]))
		//For each stack of bar calcualte the rune
		for j := 0; j < LabelLen && i < bc.numBar; j++ {
			n := bc.Data[i][j]
			s := fmt.Sprint(n)
			bc.dataNum[i][j] = trimStr2Runes(s, bc.BarWidth)
		}
		//If color is not defined by default then populate a color that is different from the prevous bar
		if bc.BarColor[i] == ColorDefault && bc.NumColor[i] == ColorDefault {
			if i == 0 {
				bc.BarColor[i] = ColorBlack
			} else {
				bc.BarColor[i] = bc.BarColor[i-1] + 1
				if bc.BarColor[i] > NumberofColors {
					bc.BarColor[i] = ColorBlack
				}
			}
			bc.NumColor[i] = (NumberofColors + 1) - bc.BarColor[i] //Make NumColor opposite of barColor for visibility
		}
	}

	//If Max value is not set then we have to populate, this time the max value will be max(sum(d1[0],d2[0],d3[0]) .... sum(d1[n], d2[n], d3[n]))

	if bc.max == 0 {
		bc.max = -1
	}
	for i := 0; i < bc.minDataLen && i < LabelLen; i++ {
		var dsum int
		for j := 0; j < bc.numStack; j++ {
			dsum += bc.Data[j][i]
		}
		if dsum > bc.max {
			bc.max = dsum
		}
	}

	//Finally Calculate max sale
	if bc.ShowScale {
		s := fmt.Sprintf("%d", bc.max)
		bc.maxScale = trimStr2Runes(s, len(s))
		bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-2)
	} else {
		bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-1)
	}

}

func (bc *MBarChart) SetMax(max int) {

	if max > 0 {
		bc.max = max
	}
}

// Buffer implements Bufferer interface.
func (bc *MBarChart) Buffer() Buffer {
	buf := bc.Block.Buffer()
	bc.layout()
	var oftX int

	for i := 0; i < bc.numBar && i < bc.minDataLen && i < len(bc.DataLabels); i++ {
		ph := 0 //Previous Height to stack up
		oftX = i * (bc.BarWidth + bc.BarGap)
		for i1 := 0; i1 < bc.numStack; i1++ {
			h := int(float64(bc.Data[i1][i]) / bc.scale)
			// plot bars
			for j := 0; j < bc.BarWidth; j++ {
				for k := 0; k < h; k++ {
					c := Cell{
						Ch: ' ',
						Bg: bc.BarColor[i1],
					}
					if bc.BarColor[i1] == ColorDefault { // when color is default, space char treated as transparent!
						c.Bg |= AttrReverse
					}
					x := bc.innerArea.Min.X + i*(bc.BarWidth+bc.BarGap) + j
					y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - k - ph
					buf.Set(x, y, c)

				}
			}
			ph += h
		}
		// plot text
		for j, k := 0, 0; j < len(bc.labels[i]); j++ {
			w := charWidth(bc.labels[i][j])
			c := Cell{
				Ch: bc.labels[i][j],
				Bg: bc.Bg,
				Fg: bc.TextColor,
			}
			y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1
			x := bc.innerArea.Max.X + oftX + ((bc.BarWidth - len(bc.labels[i])) / 2) + k
			buf.Set(x, y, c)
			k += w
		}
		// plot num
		ph = 0 //re-initialize previous height
		for i1 := 0; i1 < bc.numStack; i1++ {
			h := int(float64(bc.Data[i1][i]) / bc.scale)
			for j := 0; j < len(bc.dataNum[i1][i]) && h > 0; j++ {
				c := Cell{
					Ch: bc.dataNum[i1][i][j],
					Fg: bc.NumColor[i1],
					Bg: bc.BarColor[i1],
				}
				if bc.BarColor[i1] == ColorDefault { // the same as above
					c.Bg |= AttrReverse
				}
				if h == 0 {
					c.Bg = bc.Bg
				}
				x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i1][i]))/2 + j
				y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - ph
				buf.Set(x, y, c)
			}
			ph += h
		}
	}

	if bc.ShowScale {
		//Currently bar graph only supprts data range from 0 to MAX
		//Plot 0
		c := Cell{
			Ch: '0',
			Bg: bc.Bg,
			Fg: bc.TextColor,
		}

		y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2
		x := bc.X
		buf.Set(x, y, c)

		//Plot the maximum sacle value
		for i := 0; i < len(bc.maxScale); i++ {
			c := Cell{
				Ch: bc.maxScale[i],
				Bg: bc.Bg,
				Fg: bc.TextColor,
			}

			y := bc.innerArea.Min.Y
			x := bc.X + i

			buf.Set(x, y, c)
		}

	}

	return buf
}
<commit_msg>spelling: calculate<commit_after>// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.

package termui

import (
	"fmt"
)

// This is the implemetation of multi-colored or stacked bar graph.  This is different from default barGraph which is implemented in bar.go
// Multi-Colored-BarChart creates multiple bars in a widget:
/*
   bc := termui.NewMBarChart()
   data := make([][]int, 2)
   data[0] := []int{3, 2, 5, 7, 9, 4}
   data[1] := []int{7, 8, 5, 3, 1, 6}
   bclabels := []string{"S0", "S1", "S2", "S3", "S4", "S5"}
   bc.BorderLabel = "Bar Chart"
   bc.Data = data
   bc.Width = 26
   bc.Height = 10
   bc.DataLabels = bclabels
   bc.TextColor = termui.ColorGreen
   bc.BarColor = termui.ColorRed
   bc.NumColor = termui.ColorYellow
*/
type MBarChart struct {
	Block
	BarColor   [NumberofColors]Attribute
	TextColor  Attribute
	NumColor   [NumberofColors]Attribute
	Data       [NumberofColors][]int
	DataLabels []string
	BarWidth   int
	BarGap     int
	labels     [][]rune
	dataNum    [NumberofColors][][]rune
	numBar     int
	scale      float64
	max        int
	minDataLen int
	numStack   int
	ShowScale  bool
	maxScale   []rune
}

// NewBarChart returns a new *BarChart with current theme.
func NewMBarChart() *MBarChart {
	bc := &MBarChart{Block: *NewBlock()}
	bc.BarColor[0] = ThemeAttr("mbarchart.bar.bg")
	bc.NumColor[0] = ThemeAttr("mbarchart.num.fg")
	bc.TextColor = ThemeAttr("mbarchart.text.fg")
	bc.BarGap = 1
	bc.BarWidth = 3
	return bc
}

func (bc *MBarChart) layout() {
	bc.numBar = bc.innerArea.Dx() / (bc.BarGap + bc.BarWidth)
	bc.labels = make([][]rune, bc.numBar)
	DataLen := 0
	LabelLen := len(bc.DataLabels)
	bc.minDataLen = 9999 //Set this to some very hight value so that we find the minimum one We want to know which array among data[][] has got the least length

	// We need to know how many stack/data array data[0] , data[1] are there
	for i := 0; i < len(bc.Data); i++ {
		if bc.Data[i] == nil {
			break
		}
		DataLen++
	}
	bc.numStack = DataLen

	//We need to know what is the mimimum size of data array data[0] could have 10 elements data[1] could have only 5, so we plot only 5 bar graphs

	for i := 0; i < DataLen; i++ {
		if bc.minDataLen > len(bc.Data[i]) {
			bc.minDataLen = len(bc.Data[i])
		}
	}

	if LabelLen > bc.minDataLen {
		LabelLen = bc.minDataLen
	}

	for i := 0; i < LabelLen && i < bc.numBar; i++ {
		bc.labels[i] = trimStr2Runes(bc.DataLabels[i], bc.BarWidth)
	}

	for i := 0; i < bc.numStack; i++ {
		bc.dataNum[i] = make([][]rune, len(bc.Data[i]))
		//For each stack of bar calculate the rune
		for j := 0; j < LabelLen && i < bc.numBar; j++ {
			n := bc.Data[i][j]
			s := fmt.Sprint(n)
			bc.dataNum[i][j] = trimStr2Runes(s, bc.BarWidth)
		}
		//If color is not defined by default then populate a color that is different from the prevous bar
		if bc.BarColor[i] == ColorDefault && bc.NumColor[i] == ColorDefault {
			if i == 0 {
				bc.BarColor[i] = ColorBlack
			} else {
				bc.BarColor[i] = bc.BarColor[i-1] + 1
				if bc.BarColor[i] > NumberofColors {
					bc.BarColor[i] = ColorBlack
				}
			}
			bc.NumColor[i] = (NumberofColors + 1) - bc.BarColor[i] //Make NumColor opposite of barColor for visibility
		}
	}

	//If Max value is not set then we have to populate, this time the max value will be max(sum(d1[0],d2[0],d3[0]) .... sum(d1[n], d2[n], d3[n]))

	if bc.max == 0 {
		bc.max = -1
	}
	for i := 0; i < bc.minDataLen && i < LabelLen; i++ {
		var dsum int
		for j := 0; j < bc.numStack; j++ {
			dsum += bc.Data[j][i]
		}
		if dsum > bc.max {
			bc.max = dsum
		}
	}

	//Finally Calculate max sale
	if bc.ShowScale {
		s := fmt.Sprintf("%d", bc.max)
		bc.maxScale = trimStr2Runes(s, len(s))
		bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-2)
	} else {
		bc.scale = float64(bc.max) / float64(bc.innerArea.Dy()-1)
	}

}

func (bc *MBarChart) SetMax(max int) {

	if max > 0 {
		bc.max = max
	}
}

// Buffer implements Bufferer interface.
func (bc *MBarChart) Buffer() Buffer {
	buf := bc.Block.Buffer()
	bc.layout()
	var oftX int

	for i := 0; i < bc.numBar && i < bc.minDataLen && i < len(bc.DataLabels); i++ {
		ph := 0 //Previous Height to stack up
		oftX = i * (bc.BarWidth + bc.BarGap)
		for i1 := 0; i1 < bc.numStack; i1++ {
			h := int(float64(bc.Data[i1][i]) / bc.scale)
			// plot bars
			for j := 0; j < bc.BarWidth; j++ {
				for k := 0; k < h; k++ {
					c := Cell{
						Ch: ' ',
						Bg: bc.BarColor[i1],
					}
					if bc.BarColor[i1] == ColorDefault { // when color is default, space char treated as transparent!
						c.Bg |= AttrReverse
					}
					x := bc.innerArea.Min.X + i*(bc.BarWidth+bc.BarGap) + j
					y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - k - ph
					buf.Set(x, y, c)

				}
			}
			ph += h
		}
		// plot text
		for j, k := 0, 0; j < len(bc.labels[i]); j++ {
			w := charWidth(bc.labels[i][j])
			c := Cell{
				Ch: bc.labels[i][j],
				Bg: bc.Bg,
				Fg: bc.TextColor,
			}
			y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 1
			x := bc.innerArea.Max.X + oftX + ((bc.BarWidth - len(bc.labels[i])) / 2) + k
			buf.Set(x, y, c)
			k += w
		}
		// plot num
		ph = 0 //re-initialize previous height
		for i1 := 0; i1 < bc.numStack; i1++ {
			h := int(float64(bc.Data[i1][i]) / bc.scale)
			for j := 0; j < len(bc.dataNum[i1][i]) && h > 0; j++ {
				c := Cell{
					Ch: bc.dataNum[i1][i][j],
					Fg: bc.NumColor[i1],
					Bg: bc.BarColor[i1],
				}
				if bc.BarColor[i1] == ColorDefault { // the same as above
					c.Bg |= AttrReverse
				}
				if h == 0 {
					c.Bg = bc.Bg
				}
				x := bc.innerArea.Min.X + oftX + (bc.BarWidth-len(bc.dataNum[i1][i]))/2 + j
				y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2 - ph
				buf.Set(x, y, c)
			}
			ph += h
		}
	}

	if bc.ShowScale {
		//Currently bar graph only supprts data range from 0 to MAX
		//Plot 0
		c := Cell{
			Ch: '0',
			Bg: bc.Bg,
			Fg: bc.TextColor,
		}

		y := bc.innerArea.Min.Y + bc.innerArea.Dy() - 2
		x := bc.X
		buf.Set(x, y, c)

		//Plot the maximum sacle value
		for i := 0; i < len(bc.maxScale); i++ {
			c := Cell{
				Ch: bc.maxScale[i],
				Bg: bc.Bg,
				Fg: bc.TextColor,
			}

			y := bc.innerArea.Min.Y
			x := bc.X + i

			buf.Set(x, y, c)
		}

	}

	return buf
}
<|endoftext|>


<commit_before>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tabletserver

import (
	"fmt"
	"sync"
	"time"

	log "github.com/golang/glog"
	"github.com/youtube/vitess/go/sqltypes"
	"github.com/youtube/vitess/go/stats"
	"github.com/youtube/vitess/go/sync2"
	"github.com/youtube/vitess/go/tb"
	"github.com/youtube/vitess/go/vt/binlog"
	blproto "github.com/youtube/vitess/go/vt/binlog/proto"
	"github.com/youtube/vitess/go/vt/mysqlctl"
	"github.com/youtube/vitess/go/vt/sqlparser"
	"github.com/youtube/vitess/go/vt/tabletserver/proto"
)

// RowcacheInvalidator runs the service to invalidate
// the rowcache based on binlog events.
type RowcacheInvalidator struct {
	qe  *QueryEngine
	svm sync2.ServiceManager

	// mu mainly protects access to evs by Open and Close.
	mu        sync.Mutex
	dbname    string
	mysqld    *mysqlctl.Mysqld
	evs       *binlog.EventStreamer
	Timestamp sync2.AtomicInt64
	GroupId   sync2.AtomicInt64
}

// NewRowcacheInvalidator creates a new RowcacheInvalidator.
// Just like QueryEngine, this is a singleton class.
// You must call this only once.
func NewRowcacheInvalidator(qe *QueryEngine) *RowcacheInvalidator {
	rci := &RowcacheInvalidator{qe: qe}
	stats.Publish("RowcacheInvalidatorState", stats.StringFunc(rci.svm.StateName))
	stats.Publish("RowcacheInvalidatorPosition", stats.IntFunc(rci.GroupId.Get))
	stats.Publish("RowcacheInvalidatorTimestamp", stats.IntFunc(rci.Timestamp.Get))
	return rci
}

// Open runs the invalidation loop.
func (rci *RowcacheInvalidator) Open(dbname string, mysqld *mysqlctl.Mysqld) {
	rp, err := mysqld.MasterStatus()
	if err != nil {
		panic(NewTabletError(FATAL, "Rowcache invalidator aborting: cannot determine replication position: %v", err))
	}
	if mysqld.Cnf().BinLogPath == "" {
		panic(NewTabletError(FATAL, "Rowcache invalidator aborting: binlog path not specified"))
	}

	ok := rci.svm.Go(func(_ *sync2.ServiceManager) {
		rci.mu.Lock()
		rci.dbname = dbname
		rci.mysqld = mysqld
		rci.evs = binlog.NewEventStreamer(dbname, mysqld.Cnf().BinLogPath)
		rci.GroupId.Set(rp.MasterLogGroupId)
		rci.mu.Unlock()

		rci.run()

		rci.mu.Lock()
		rci.evs = nil
		rci.mu.Unlock()
	})
	if ok {
		log.Infof("Rowcache invalidator starting, dbname: %s, path: %s, logfile: %s, position: %d", dbname, mysqld.Cnf().BinLogPath, rp.MasterLogFile, rp.MasterLogPosition)
	} else {
		log.Infof("Rowcache invalidator already running")
	}
}

// Close terminates the invalidation loop. It returns only of the
// loop has terminated.
func (rci *RowcacheInvalidator) Close() {
	rci.mu.Lock()
	if rci.evs == nil {
		log.Infof("Rowcache is not running")
		rci.mu.Unlock()
		return
	}
	// This will cause the event streamer to exit, but run
	// may still be running.
	rci.evs.Stop()
	rci.mu.Unlock()
	// Stop will wait for run and rci to shutdown, which will set
	// evs to nil. So, we need to release the lock before this.
	rci.svm.Stop()
}

func (rci *RowcacheInvalidator) run() {
	for {
		// We wrap this code in a func so we can catch all panics.
		// If an error is returned, we log it, wait 1 second, and retry.
		// This loop can only be stopped by calling Close.
		err := func() (inner error) {
			defer func() {
				if x := recover(); x != nil {
					inner = fmt.Errorf("%v: uncaught panic:\n%s", x, tb.Stack(4))
				}
			}()
			rp, err := rci.mysqld.BinlogInfo(rci.GroupId.Get())
			if err != nil {
				return err
			}
			return rci.evs.Stream(rp.MasterLogFile, int64(rp.MasterLogPosition), func(reply *blproto.StreamEvent) error {
				rci.processEvent(reply)
				return nil
			})
		}()
		if err == nil {
			break
		}
		log.Errorf("binlog.ServeUpdateStream returned err '%v', retrying in 1 second.", err.Error())
		internalErrors.Add("Invalidation", 1)
		time.Sleep(1 * time.Second)
	}
	log.Infof("Rowcache invalidator stopped")
}

func handleInvalidationError(event *blproto.StreamEvent) {
	if x := recover(); x != nil {
		terr, ok := x.(*TabletError)
		if !ok {
			log.Errorf("Uncaught panic for %+v:\n%v\n%s", event, x, tb.Stack(4))
			internalErrors.Add("Panic", 1)
			return
		}
		log.Errorf("%v: %+v", terr, event)
		internalErrors.Add("Invalidation", 1)
	}
}

func (rci *RowcacheInvalidator) processEvent(event *blproto.StreamEvent) {
	defer handleInvalidationError(event)
	switch event.Category {
	case "DDL":
		rci.qe.InvalidateForDDL(&proto.DDLInvalidate{DDL: event.Sql})
		rci.Timestamp.Set(event.Timestamp)
	case "DML":
		rci.handleDmlEvent(event)
		rci.Timestamp.Set(event.Timestamp)
	case "ERR":
		dbname, err := sqlparser.GetDBName(event.Sql)
		if err != nil || dbname == "" || dbname == rci.dbname {
			log.Errorf("Unrecognized: %s", event.Sql)
			internalErrors.Add("Invalidation", 1)
		} else {
			infoErrors.Add("Invalidation", 1)
		}
		rci.Timestamp.Set(event.Timestamp)
	case "POS":
		rci.GroupId.Set(event.GroupId)
	default:
		log.Errorf("unknown event: %#v", event)
		internalErrors.Add("Invalidation", 1)
	}
}

func (rci *RowcacheInvalidator) handleDmlEvent(event *blproto.StreamEvent) {
	dml := new(proto.DmlType)
	dml.Table = event.TableName
	dml.Keys = make([]string, 0, len(event.PKValues))
	sqlTypeKeys := make([]sqltypes.Value, 0, len(event.PKColNames))
	for _, pkTuple := range event.PKValues {
		sqlTypeKeys = sqlTypeKeys[:0]
		for _, pkVal := range pkTuple {
			key, err := sqltypes.BuildValue(pkVal)
			if err != nil {
				log.Errorf("Error building invalidation key for %#v: '%v'", event, err)
				internalErrors.Add("Invalidation", 1)
				return
			}
			sqlTypeKeys = append(sqlTypeKeys, key)
		}
		invalidateKey := buildKey(sqlTypeKeys)
		if invalidateKey != "" {
			dml.Keys = append(dml.Keys, invalidateKey)
		}
	}
	rci.qe.InvalidateForDml(dml)
}
<commit_msg>invalidator: info error count bug fix<commit_after>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tabletserver

import (
	"fmt"
	"sync"
	"time"

	log "github.com/golang/glog"
	"github.com/youtube/vitess/go/sqltypes"
	"github.com/youtube/vitess/go/stats"
	"github.com/youtube/vitess/go/sync2"
	"github.com/youtube/vitess/go/tb"
	"github.com/youtube/vitess/go/vt/binlog"
	blproto "github.com/youtube/vitess/go/vt/binlog/proto"
	"github.com/youtube/vitess/go/vt/mysqlctl"
	"github.com/youtube/vitess/go/vt/sqlparser"
	"github.com/youtube/vitess/go/vt/tabletserver/proto"
)

// RowcacheInvalidator runs the service to invalidate
// the rowcache based on binlog events.
type RowcacheInvalidator struct {
	qe  *QueryEngine
	svm sync2.ServiceManager

	// mu mainly protects access to evs by Open and Close.
	mu        sync.Mutex
	dbname    string
	mysqld    *mysqlctl.Mysqld
	evs       *binlog.EventStreamer
	Timestamp sync2.AtomicInt64
	GroupId   sync2.AtomicInt64
}

// NewRowcacheInvalidator creates a new RowcacheInvalidator.
// Just like QueryEngine, this is a singleton class.
// You must call this only once.
func NewRowcacheInvalidator(qe *QueryEngine) *RowcacheInvalidator {
	rci := &RowcacheInvalidator{qe: qe}
	stats.Publish("RowcacheInvalidatorState", stats.StringFunc(rci.svm.StateName))
	stats.Publish("RowcacheInvalidatorPosition", stats.IntFunc(rci.GroupId.Get))
	stats.Publish("RowcacheInvalidatorTimestamp", stats.IntFunc(rci.Timestamp.Get))
	return rci
}

// Open runs the invalidation loop.
func (rci *RowcacheInvalidator) Open(dbname string, mysqld *mysqlctl.Mysqld) {
	rp, err := mysqld.MasterStatus()
	if err != nil {
		panic(NewTabletError(FATAL, "Rowcache invalidator aborting: cannot determine replication position: %v", err))
	}
	if mysqld.Cnf().BinLogPath == "" {
		panic(NewTabletError(FATAL, "Rowcache invalidator aborting: binlog path not specified"))
	}

	ok := rci.svm.Go(func(_ *sync2.ServiceManager) {
		rci.mu.Lock()
		rci.dbname = dbname
		rci.mysqld = mysqld
		rci.evs = binlog.NewEventStreamer(dbname, mysqld.Cnf().BinLogPath)
		rci.GroupId.Set(rp.MasterLogGroupId)
		rci.mu.Unlock()

		rci.run()

		rci.mu.Lock()
		rci.evs = nil
		rci.mu.Unlock()
	})
	if ok {
		log.Infof("Rowcache invalidator starting, dbname: %s, path: %s, logfile: %s, position: %d", dbname, mysqld.Cnf().BinLogPath, rp.MasterLogFile, rp.MasterLogPosition)
	} else {
		log.Infof("Rowcache invalidator already running")
	}
}

// Close terminates the invalidation loop. It returns only of the
// loop has terminated.
func (rci *RowcacheInvalidator) Close() {
	rci.mu.Lock()
	if rci.evs == nil {
		log.Infof("Rowcache is not running")
		rci.mu.Unlock()
		return
	}
	// This will cause the event streamer to exit, but run
	// may still be running.
	rci.evs.Stop()
	rci.mu.Unlock()
	// Stop will wait for run and rci to shutdown, which will set
	// evs to nil. So, we need to release the lock before this.
	rci.svm.Stop()
}

func (rci *RowcacheInvalidator) run() {
	for {
		// We wrap this code in a func so we can catch all panics.
		// If an error is returned, we log it, wait 1 second, and retry.
		// This loop can only be stopped by calling Close.
		err := func() (inner error) {
			defer func() {
				if x := recover(); x != nil {
					inner = fmt.Errorf("%v: uncaught panic:\n%s", x, tb.Stack(4))
				}
			}()
			rp, err := rci.mysqld.BinlogInfo(rci.GroupId.Get())
			if err != nil {
				return err
			}
			return rci.evs.Stream(rp.MasterLogFile, int64(rp.MasterLogPosition), func(reply *blproto.StreamEvent) error {
				rci.processEvent(reply)
				return nil
			})
		}()
		if err == nil {
			break
		}
		log.Errorf("binlog.ServeUpdateStream returned err '%v', retrying in 1 second.", err.Error())
		internalErrors.Add("Invalidation", 1)
		time.Sleep(1 * time.Second)
	}
	log.Infof("Rowcache invalidator stopped")
}

func handleInvalidationError(event *blproto.StreamEvent) {
	if x := recover(); x != nil {
		terr, ok := x.(*TabletError)
		if !ok {
			log.Errorf("Uncaught panic for %+v:\n%v\n%s", event, x, tb.Stack(4))
			internalErrors.Add("Panic", 1)
			return
		}
		log.Errorf("%v: %+v", terr, event)
		internalErrors.Add("Invalidation", 1)
	}
}

func (rci *RowcacheInvalidator) processEvent(event *blproto.StreamEvent) {
	defer handleInvalidationError(event)
	switch event.Category {
	case "DDL":
		rci.qe.InvalidateForDDL(&proto.DDLInvalidate{DDL: event.Sql})
		rci.Timestamp.Set(event.Timestamp)
	case "DML":
		rci.handleDmlEvent(event)
		rci.Timestamp.Set(event.Timestamp)
	case "ERR":
		dbname, err := sqlparser.GetDBName(event.Sql)
		if err != nil || dbname == "" || dbname == rci.dbname {
			log.Errorf("Unrecognized: %s", event.Sql)
			internalErrors.Add("Invalidation", 1)
		}
		rci.Timestamp.Set(event.Timestamp)
	case "POS":
		rci.GroupId.Set(event.GroupId)
	default:
		log.Errorf("unknown event: %#v", event)
		internalErrors.Add("Invalidation", 1)
	}
}

func (rci *RowcacheInvalidator) handleDmlEvent(event *blproto.StreamEvent) {
	dml := new(proto.DmlType)
	dml.Table = event.TableName
	dml.Keys = make([]string, 0, len(event.PKValues))
	sqlTypeKeys := make([]sqltypes.Value, 0, len(event.PKColNames))
	for _, pkTuple := range event.PKValues {
		sqlTypeKeys = sqlTypeKeys[:0]
		for _, pkVal := range pkTuple {
			key, err := sqltypes.BuildValue(pkVal)
			if err != nil {
				log.Errorf("Error building invalidation key for %#v: '%v'", event, err)
				internalErrors.Add("Invalidation", 1)
				return
			}
			sqlTypeKeys = append(sqlTypeKeys, key)
		}
		invalidateKey := buildKey(sqlTypeKeys)
		if invalidateKey != "" {
			dml.Keys = append(dml.Keys, invalidateKey)
		}
	}
	rci.qe.InvalidateForDml(dml)
}
<|endoftext|>


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

package acme

import (
	"errors"
	"fmt"
	"net/http"
	"time"
)

// ACME server response statuses used to describe Account, Authorization, and Challenge states.
const (
	StatusUnknown     = "unknown"
	StatusPending     = "pending"
	StatusProcessing  = "processing"
	StatusValid       = "valid"
	StatusInvalid     = "invalid"
	StatusRevoked     = "revoked"
	StatusDeactivated = "deactivated"
)

// CRLReasonCode identifies the reason for a certificate revocation.
type CRLReasonCode int

// CRL reason codes as defined in RFC 5280.
const (
	CRLReasonUnspecified          CRLReasonCode = 0
	CRLReasonKeyCompromise        CRLReasonCode = 1
	CRLReasonCACompromise         CRLReasonCode = 2
	CRLReasonAffiliationChanged   CRLReasonCode = 3
	CRLReasonSuperseded           CRLReasonCode = 4
	CRLReasonCessationOfOperation CRLReasonCode = 5
	CRLReasonCertificateHold      CRLReasonCode = 6
	CRLReasonRemoveFromCRL        CRLReasonCode = 8
	CRLReasonPrivilegeWithdrawn   CRLReasonCode = 9
	CRLReasonAACompromise         CRLReasonCode = 10
)

// ErrUnsupportedKey is returned when an unsupported key type is encountered.
var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")

// Error is an ACME error as defined in RFC 7807, Problem Details for HTTP APIs.
type Error struct {
	// StatusCode is The HTTP status code generated by the origin server.
	StatusCode int

	// Type is a URI that identifies the problem type, typically in
	// a "urn:ietf:params:acme:error:xxx" form.
	Type string

	// Detail is a human-readable explanation specific to this occurrence of the problem.
	Detail string

	// Subproblems is an optional list of additional error information, usually
	// indicating problems with specific identifiers during authorization.
	Subproblems []Subproblem

	// Header is the original server error response headers.
	// It may be nil.
	Header http.Header
}

func (e *Error) Error() string {
	return fmt.Sprintf("acme: %s: %s", e.Type, e.Detail)
}

// An Subproblem is additional error detail that is included in an Error,
// usually indicating a problem with a specific identifier during authorization.
type Subproblem struct {
	// Type is a URI that identifies the subproblem type, typically in
	// "urn:ietf:params:acme:error:xxx" form.
	Type string

	// Detail is a human-readable explanation specific to this occurrence of the
	// subproblem.
	Detail string

	// Identifier optionally indicates the identifier that this subproblem is about.
	Identifier *AuthzID
}

// OrderInvalidError is returned when an order is marked as invalid.
type OrderInvalidError struct {
	// Order is the order that is invalid.
	Order *Order
}

func (e OrderInvalidError) Error() string {
	if e.Order == nil || e.Order.Error == nil {
		return "acme: order is invalid"
	}
	return fmt.Sprintf("acme: invalid order (%s): %s", e.Order.Error.Type, e.Order.Error.Detail)
}

// OrderPendingError is returned when an order is still pending after an
// attempted finalization.
type OrderPendingError struct {
	// Order is the order that is pending.
	Order *Order
}

func (e OrderPendingError) Error() string {
	return "acme: order is pending due to incomplete authorization"
}

// AuthorizationError is returned when an authorization is marked as invalid.
type AuthorizationError struct {
	// Authorization is the authorization that is invalid.
	Authorization *Authorization
}

func (e AuthorizationError) Error() string {
	if e.Authorization == nil {
		return "acme: authorization is invalid"
	}
	return fmt.Sprintf("acme: authorization for identifier %s is %s", e.Authorization.Identifier.Value, e.Authorization.Status)
}

// RateLimit reports whether err represents a rate limit error and
// any Retry-After duration returned by the server.
//
// See the following for more details on rate limiting:
// https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-6.5
func RateLimit(err error) (time.Time, bool) {
	e, ok := err.(*Error)
	if !ok || e.Type != "urn:ietf:params:acme:error:rateLimited" {
		return time.Time{}, false
	}
	if e.Header == nil {
		return time.Time{}, true
	}
	return retryAfter(e.Header.Get("Retry-After")), true
}

// Account is a user account. It is associated with a private key.
type Account struct {
	// URL uniquely identifies the account.
	URL string

	// Status is the status of the account. Valid values are StatusValid,
	// StatusDeactivated, and StatusRevoked.
	Status string

	// Contact is a list of URLs that the server can use to contact the client
	// for issues related to this account.
	Contact []string

	// TermsAgreed indicates agreement with the terms of service. It is not
	// modifiable after account creation.
	TermsAgreed bool

	// OrdersURL is the URL used to fetch a list of orders submitted by this
	// account.
	OrdersURL string
}

// Directory is ACME server discovery data.
type Directory struct {
	// NewNonceURL is used to retrieve new nonces.
	NewNonceURL string

	// NewAccountURL is used to create new accounts.
	NewAccountURL string

	// NewOrderURL is used to create new orders.
	NewOrderURL string

	// NewAuthzURL is used to create new authorizations.
	NewAuthzURL string

	// RevokeCertURL is used to revoke a certificate.
	RevokeCertURL string

	// KeyChangeURL is used to change the account key.
	KeyChangeURL string

	// Terms is a URL identifying the current terms of service.
	Terms string

	// Website is an HTTP or HTTPS URL locating a website
	// providing more information about the ACME server.
	Website string

	// CAA consists of lowercase hostname elements, which the ACME server
	// recognises as referring to itself for the purposes of CAA record validation
	// as defined in RFC6844.
	CAA []string

	// ExternalAccountRequired, if true, indicates that the CA requires that all
	// new account requests include an ExternalAccountBinding field associating
	// the new account with an external account.
	ExternalAccountRequired bool
}

// NewOrder creates a new order with the domains provided, suitable for creating
// a TLS certificate order with CreateOrder.
func NewOrder(domains ...string) *Order {
	o := &Order{Identifiers: make([]AuthzID, len(domains))}
	for i, d := range domains {
		o.Identifiers[i] = AuthzID{
			Type:  "dns",
			Value: d,
		}
	}
	return o
}

// An Order represents a request for a certificate and is used to track the
// progress through to issuance.
type Order struct {
	// URL uniquely identifies the order.
	URL string

	// Status is the status of the order. It will be one of StatusPending,
	// StatusProcessing, StatusValid, and StatusInvalid.
	Status string

	// Expires is the teimstamp after which the server will consider the order invalid.
	Expires time.Time

	// Identifiers is a list of identifiers that the order pertains to.
	Identifiers []AuthzID

	// NotBefore is an optional requested value of the notBefore field in the certificate.
	NotBefore time.Time

	// NotAfter is an optional requested value of the notAfter field in the certificate.
	NotAfter time.Time

	// Error is the error that occurred while processing the order, if any.
	Error *Error

	// Authorizations is a list of URLs for authorizations that the client needs
	// to complete before the requested certificate can be issued. For
	// valid/invalid orders, these are the authorizations that were completed.
	Authorizations []string

	// FinalizeURL is the URL that is used to finalize the Order.
	FinalizeURL string

	// CertificateURL is the URL for the certificate that has been issued in
	// response to this order.
	CertificateURL string

	// RetryAfter is the timestamp, if any, to wait for before fetching this
	// order again.
	RetryAfter time.Time
}

// A Challenge is a CA challenge for an identifier.
type Challenge struct {
	// Type is the challenge type, e.g. "http-01" or "dns-01".
	Type string

	// URL is the URL where a challenge response can be posted.
	URL string

	// Token is a random value that uniquely identifies the challenge.
	Token string

	// Validated is the time at which the server validated this challenge.
	Validated time.Time

	// Status identifies the status of this challenge. Valid values are
	// StatusPending, StatusValid, and StatusInvalid.
	Status string

	// Error indicates the errors that occurred while the server was validating
	// this challenge.
	Error *Error
}

// Authorization encodes an authorization response.
type Authorization struct {
	// URL uniquely identifies the authorization.
	URL string

	// Status is the status of the authorization. Valid values are
	// StatusPending, StatusProcessing, StatusValid, StatusInvalid, and
	// StatusRevoked.
	Status string

	// Identifier is the identifier that the account is authorized to represent.
	Identifier AuthzID

	// Wildcard is true if the authorization is for the base domain of a wildcard identifier.
	Wildcard bool

	// Expires is the timestamp after which the server will consider this authorization invalid.
	Expires time.Time

	// Challenges is the list of challenges that the client can fulfill in order
	// to prove posession of the identifier. For valid/invalid authorizations,
	// this is the list of challenges that were used.
	Challenges []*Challenge
}

// AuthzID is an identifier that an account is authorized to represent.
type AuthzID struct {
	Type  string // The type of identifier, e.g. "dns".
	Value string // The identifier itself, e.g. "example.org".
}

type wireAuthzID struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

// wireAuthz is ACME JSON representation of Authorization objects.
type wireAuthz struct {
	Status     string
	Challenges []wireChallenge
	Expires    time.Time
	Identifier struct {
		Type  string
		Value string
	}
	Wildcard bool
}

func (z *wireAuthz) authorization(url string) *Authorization {
	a := &Authorization{
		URL:        url,
		Status:     z.Status,
		Expires:    z.Expires,
		Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
		Wildcard:   z.Wildcard,
		Challenges: make([]*Challenge, len(z.Challenges)),
	}
	for i, v := range z.Challenges {
		a.Challenges[i] = v.challenge()
	}
	return a
}

// wireChallenge is ACME JSON challenge representation.
type wireChallenge struct {
	URL       string
	Type      string
	Token     string
	Status    string
	Validated time.Time
	Error     *Error
}

func (c *wireChallenge) challenge() *Challenge {
	v := &Challenge{
		URL:       c.URL,
		Type:      c.Type,
		Token:     c.Token,
		Status:    c.Status,
		Validated: c.Validated,
		Error:     c.Error,
	}
	if v.Status == "" {
		v.Status = StatusUnknown
	}
	return v
}

// wireError is a subset of fields of the Problem Details object
// as described in https://tools.ietf.org/html/rfc7807#section-3.1.
type wireError struct {
	Status      int
	Type        string
	Detail      string
	Subproblems []Subproblem
}

func (e *wireError) error(h http.Header) *Error {
	return &Error{
		StatusCode:  e.Status,
		Type:        e.Type,
		Detail:      e.Detail,
		Subproblems: e.Subproblems,
		Header:      h,
	}
}

type wireOrder struct {
	Status         string
	Expires        time.Time
	Identifiers    []AuthzID
	NotBefore      time.Time
	NotAfter       time.Time
	Error          *Error
	Authorizations []string
	Finalize       string
	Certificate    string
}

func (o *wireOrder) order(url string, retryHeader string) *Order {
	return &Order{
		URL:            url,
		Status:         o.Status,
		Expires:        o.Expires,
		Identifiers:    o.Identifiers,
		NotBefore:      o.NotBefore,
		NotAfter:       o.NotAfter,
		Error:          o.Error,
		Authorizations: o.Authorizations,
		FinalizeURL:    o.Finalize,
		CertificateURL: o.Certificate,
		RetryAfter:     retryAfter(retryHeader),
	}
}
<commit_msg>Re-add StatusReady const<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package acme

import (
	"errors"
	"fmt"
	"net/http"
	"time"
)

// ACME server response statuses used to describe Account, Authorization, and Challenge states.
const (
	StatusUnknown     = "unknown"
	StatusPending     = "pending"
	StatusProcessing  = "processing"
	StatusValid       = "valid"
	StatusInvalid     = "invalid"
	StatusRevoked     = "revoked"
	StatusDeactivated = "deactivated"
	StatusReady       = "ready"
)

// CRLReasonCode identifies the reason for a certificate revocation.
type CRLReasonCode int

// CRL reason codes as defined in RFC 5280.
const (
	CRLReasonUnspecified          CRLReasonCode = 0
	CRLReasonKeyCompromise        CRLReasonCode = 1
	CRLReasonCACompromise         CRLReasonCode = 2
	CRLReasonAffiliationChanged   CRLReasonCode = 3
	CRLReasonSuperseded           CRLReasonCode = 4
	CRLReasonCessationOfOperation CRLReasonCode = 5
	CRLReasonCertificateHold      CRLReasonCode = 6
	CRLReasonRemoveFromCRL        CRLReasonCode = 8
	CRLReasonPrivilegeWithdrawn   CRLReasonCode = 9
	CRLReasonAACompromise         CRLReasonCode = 10
)

// ErrUnsupportedKey is returned when an unsupported key type is encountered.
var ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")

// Error is an ACME error as defined in RFC 7807, Problem Details for HTTP APIs.
type Error struct {
	// StatusCode is The HTTP status code generated by the origin server.
	StatusCode int

	// Type is a URI that identifies the problem type, typically in
	// a "urn:ietf:params:acme:error:xxx" form.
	Type string

	// Detail is a human-readable explanation specific to this occurrence of the problem.
	Detail string

	// Subproblems is an optional list of additional error information, usually
	// indicating problems with specific identifiers during authorization.
	Subproblems []Subproblem

	// Header is the original server error response headers.
	// It may be nil.
	Header http.Header
}

func (e *Error) Error() string {
	return fmt.Sprintf("acme: %s: %s", e.Type, e.Detail)
}

// An Subproblem is additional error detail that is included in an Error,
// usually indicating a problem with a specific identifier during authorization.
type Subproblem struct {
	// Type is a URI that identifies the subproblem type, typically in
	// "urn:ietf:params:acme:error:xxx" form.
	Type string

	// Detail is a human-readable explanation specific to this occurrence of the
	// subproblem.
	Detail string

	// Identifier optionally indicates the identifier that this subproblem is about.
	Identifier *AuthzID
}

// OrderInvalidError is returned when an order is marked as invalid.
type OrderInvalidError struct {
	// Order is the order that is invalid.
	Order *Order
}

func (e OrderInvalidError) Error() string {
	if e.Order == nil || e.Order.Error == nil {
		return "acme: order is invalid"
	}
	return fmt.Sprintf("acme: invalid order (%s): %s", e.Order.Error.Type, e.Order.Error.Detail)
}

// OrderPendingError is returned when an order is still pending after an
// attempted finalization.
type OrderPendingError struct {
	// Order is the order that is pending.
	Order *Order
}

func (e OrderPendingError) Error() string {
	return "acme: order is pending due to incomplete authorization"
}

// AuthorizationError is returned when an authorization is marked as invalid.
type AuthorizationError struct {
	// Authorization is the authorization that is invalid.
	Authorization *Authorization
}

func (e AuthorizationError) Error() string {
	if e.Authorization == nil {
		return "acme: authorization is invalid"
	}
	return fmt.Sprintf("acme: authorization for identifier %s is %s", e.Authorization.Identifier.Value, e.Authorization.Status)
}

// RateLimit reports whether err represents a rate limit error and
// any Retry-After duration returned by the server.
//
// See the following for more details on rate limiting:
// https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-6.5
func RateLimit(err error) (time.Time, bool) {
	e, ok := err.(*Error)
	if !ok || e.Type != "urn:ietf:params:acme:error:rateLimited" {
		return time.Time{}, false
	}
	if e.Header == nil {
		return time.Time{}, true
	}
	return retryAfter(e.Header.Get("Retry-After")), true
}

// Account is a user account. It is associated with a private key.
type Account struct {
	// URL uniquely identifies the account.
	URL string

	// Status is the status of the account. Valid values are StatusValid,
	// StatusDeactivated, and StatusRevoked.
	Status string

	// Contact is a list of URLs that the server can use to contact the client
	// for issues related to this account.
	Contact []string

	// TermsAgreed indicates agreement with the terms of service. It is not
	// modifiable after account creation.
	TermsAgreed bool

	// OrdersURL is the URL used to fetch a list of orders submitted by this
	// account.
	OrdersURL string
}

// Directory is ACME server discovery data.
type Directory struct {
	// NewNonceURL is used to retrieve new nonces.
	NewNonceURL string

	// NewAccountURL is used to create new accounts.
	NewAccountURL string

	// NewOrderURL is used to create new orders.
	NewOrderURL string

	// NewAuthzURL is used to create new authorizations.
	NewAuthzURL string

	// RevokeCertURL is used to revoke a certificate.
	RevokeCertURL string

	// KeyChangeURL is used to change the account key.
	KeyChangeURL string

	// Terms is a URL identifying the current terms of service.
	Terms string

	// Website is an HTTP or HTTPS URL locating a website
	// providing more information about the ACME server.
	Website string

	// CAA consists of lowercase hostname elements, which the ACME server
	// recognises as referring to itself for the purposes of CAA record validation
	// as defined in RFC6844.
	CAA []string

	// ExternalAccountRequired, if true, indicates that the CA requires that all
	// new account requests include an ExternalAccountBinding field associating
	// the new account with an external account.
	ExternalAccountRequired bool
}

// NewOrder creates a new order with the domains provided, suitable for creating
// a TLS certificate order with CreateOrder.
func NewOrder(domains ...string) *Order {
	o := &Order{Identifiers: make([]AuthzID, len(domains))}
	for i, d := range domains {
		o.Identifiers[i] = AuthzID{
			Type:  "dns",
			Value: d,
		}
	}
	return o
}

// An Order represents a request for a certificate and is used to track the
// progress through to issuance.
type Order struct {
	// URL uniquely identifies the order.
	URL string

	// Status is the status of the order. It will be one of StatusPending,
	// StatusReady, StatusProcessing, StatusValid, and StatusInvalid.
	Status string

	// Expires is the teimstamp after which the server will consider the order invalid.
	Expires time.Time

	// Identifiers is a list of identifiers that the order pertains to.
	Identifiers []AuthzID

	// NotBefore is an optional requested value of the notBefore field in the certificate.
	NotBefore time.Time

	// NotAfter is an optional requested value of the notAfter field in the certificate.
	NotAfter time.Time

	// Error is the error that occurred while processing the order, if any.
	Error *Error

	// Authorizations is a list of URLs for authorizations that the client needs
	// to complete before the requested certificate can be issued. For
	// valid/invalid orders, these are the authorizations that were completed.
	Authorizations []string

	// FinalizeURL is the URL that is used to finalize the Order.
	FinalizeURL string

	// CertificateURL is the URL for the certificate that has been issued in
	// response to this order.
	CertificateURL string

	// RetryAfter is the timestamp, if any, to wait for before fetching this
	// order again.
	RetryAfter time.Time
}

// A Challenge is a CA challenge for an identifier.
type Challenge struct {
	// Type is the challenge type, e.g. "http-01" or "dns-01".
	Type string

	// URL is the URL where a challenge response can be posted.
	URL string

	// Token is a random value that uniquely identifies the challenge.
	Token string

	// Validated is the time at which the server validated this challenge.
	Validated time.Time

	// Status identifies the status of this challenge. Valid values are
	// StatusPending, StatusValid, and StatusInvalid.
	Status string

	// Error indicates the errors that occurred while the server was validating
	// this challenge.
	Error *Error
}

// Authorization encodes an authorization response.
type Authorization struct {
	// URL uniquely identifies the authorization.
	URL string

	// Status is the status of the authorization. Valid values are
	// StatusPending, StatusProcessing, StatusValid, StatusInvalid, and
	// StatusRevoked.
	Status string

	// Identifier is the identifier that the account is authorized to represent.
	Identifier AuthzID

	// Wildcard is true if the authorization is for the base domain of a wildcard identifier.
	Wildcard bool

	// Expires is the timestamp after which the server will consider this authorization invalid.
	Expires time.Time

	// Challenges is the list of challenges that the client can fulfill in order
	// to prove posession of the identifier. For valid/invalid authorizations,
	// this is the list of challenges that were used.
	Challenges []*Challenge
}

// AuthzID is an identifier that an account is authorized to represent.
type AuthzID struct {
	Type  string // The type of identifier, e.g. "dns".
	Value string // The identifier itself, e.g. "example.org".
}

type wireAuthzID struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

// wireAuthz is ACME JSON representation of Authorization objects.
type wireAuthz struct {
	Status     string
	Challenges []wireChallenge
	Expires    time.Time
	Identifier struct {
		Type  string
		Value string
	}
	Wildcard bool
}

func (z *wireAuthz) authorization(url string) *Authorization {
	a := &Authorization{
		URL:        url,
		Status:     z.Status,
		Expires:    z.Expires,
		Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
		Wildcard:   z.Wildcard,
		Challenges: make([]*Challenge, len(z.Challenges)),
	}
	for i, v := range z.Challenges {
		a.Challenges[i] = v.challenge()
	}
	return a
}

// wireChallenge is ACME JSON challenge representation.
type wireChallenge struct {
	URL       string
	Type      string
	Token     string
	Status    string
	Validated time.Time
	Error     *Error
}

func (c *wireChallenge) challenge() *Challenge {
	v := &Challenge{
		URL:       c.URL,
		Type:      c.Type,
		Token:     c.Token,
		Status:    c.Status,
		Validated: c.Validated,
		Error:     c.Error,
	}
	if v.Status == "" {
		v.Status = StatusUnknown
	}
	return v
}

// wireError is a subset of fields of the Problem Details object
// as described in https://tools.ietf.org/html/rfc7807#section-3.1.
type wireError struct {
	Status      int
	Type        string
	Detail      string
	Subproblems []Subproblem
}

func (e *wireError) error(h http.Header) *Error {
	return &Error{
		StatusCode:  e.Status,
		Type:        e.Type,
		Detail:      e.Detail,
		Subproblems: e.Subproblems,
		Header:      h,
	}
}

type wireOrder struct {
	Status         string
	Expires        time.Time
	Identifiers    []AuthzID
	NotBefore      time.Time
	NotAfter       time.Time
	Error          *Error
	Authorizations []string
	Finalize       string
	Certificate    string
}

func (o *wireOrder) order(url string, retryHeader string) *Order {
	return &Order{
		URL:            url,
		Status:         o.Status,
		Expires:        o.Expires,
		Identifiers:    o.Identifiers,
		NotBefore:      o.NotBefore,
		NotAfter:       o.NotAfter,
		Error:          o.Error,
		Authorizations: o.Authorizations,
		FinalizeURL:    o.Finalize,
		CertificateURL: o.Certificate,
		RetryAfter:     retryAfter(retryHeader),
	}
}
<|endoftext|>


<commit_before>package main

import (
	"io"
)

type Entry interface {
	// Needs Fixed
	NeedsFixed() bool

	// Return an Entry that has been fixed
	FixedEntry() Entry

	// Returns a byte slice of the entry w/o fixes
	Bytes() []byte

	// Returns an io.Reader for the entry w/o fixes
	NewReader() io.Reader
}

// A fix for an entry
type EntryFix interface {
	// Returns byte slice that has been fixed
	Fix(io.Reader) []byte
}

func NewEntry(r io.Reader) (Entry, error) {
	return nil, nil
}

func NewEntryFromFile(filepath string) (Entry, error) {
	return nil, nil
}
<commit_msg>Skeleton implementation of the Entry interface<commit_after>package main

import (
	"bytes"
	"io"
)

type Entry interface {
	// Needs Fixed
	NeedsFixed() bool

	// Return an Entry that has been fixed
	FixedEntry() Entry

	// Returns a byte slice of the entry w/o fixes
	Bytes() []byte

	// Returns an io.Reader for the entry w/o fixes
	NewReader() io.Reader
}

// A fix for an entry
type EntryFix interface {
	// Returns byte slice that has been fixed
	Fix(io.Reader) []byte
}

type entryCaseNeedsFixed struct {
	bytes []byte
	fixes []EntryFix
}

func (e entryCaseNeedsFixed) NeedsFixed() bool { return len(e.fixes) > 0 }
func (e entryCaseNeedsFixed) FixedEntry() Entry {
	return e
}

func (e entryCaseNeedsFixed) Bytes() []byte { return e.bytes }
func (e entryCaseNeedsFixed) NewReader() io.Reader {
	return bytes.NewReader(e.bytes)
}

type entryCaseCurrent struct {
	bytes []byte
}

func (e entryCaseCurrent) NeedsFixed() bool     { return false }
func (e entryCaseCurrent) FixedEntry() Entry    { return e }
func (e entryCaseCurrent) Bytes() []byte        { return e.bytes }
func (e entryCaseCurrent) NewReader() io.Reader { return bytes.NewReader(e.bytes) }

func NewEntry(r io.Reader) (Entry, error) {
	return nil, nil
}

func NewEntryFromFile(filepath string) (Entry, error) {
	return nil, nil
}
<|endoftext|>


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

package storage

import (
	"github.com/coreos/mantle/Godeps/_workspace/src/golang.org/x/net/context"

	"github.com/coreos/mantle/lang/worker"
)

// Arbitrary limit on the number of concurrent jobs
const maxWorkers = 12

func Sync(ctx context.Context, src, dst *Bucket) error {
	wg := worker.NewWorkerGroup(ctx, maxWorkers)
	for _, srcObj := range src.Objects() {
		obj := srcObj // for the sake of the closure
		worker := func(c context.Context) error {
			name := dst.AddPrefix(src.TrimPrefix(obj.Name))
			return dst.Copy(c, obj, name)
		}
		if err := wg.Start(worker); err != nil {
			return wg.WaitError(err)
		}
	}
	return wg.Wait()
}
<commit_msg>storage: add support for deleting extra objects during sync<commit_after>// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package storage

import (
	"github.com/coreos/mantle/Godeps/_workspace/src/golang.org/x/net/context"

	"github.com/coreos/mantle/lang/worker"
)

// Arbitrary limit on the number of concurrent jobs
const maxWorkers = 12

func Sync(ctx context.Context, src, dst *Bucket) error {
	// Assemble a set of existing objects which may be deleted.
	oldNames := make(map[string]struct{})
	for _, oldObj := range dst.Objects() {
		oldNames[oldObj.Name] = struct{}{}
	}

	wg := worker.NewWorkerGroup(ctx, maxWorkers)
	for _, srcObj := range src.Objects() {
		obj := srcObj // for the sake of the closure
		name := dst.AddPrefix(src.TrimPrefix(obj.Name))

		worker := func(c context.Context) error {
			return dst.Copy(c, obj, name)
		}
		if err := wg.Start(worker); err != nil {
			return wg.WaitError(err)
		}

		// Drop from set of deletion candidates.
		delete(oldNames, name)
	}

	for oldName := range oldNames {
		name := oldName // for the sake of the closure
		worker := func(c context.Context) error {
			return dst.Delete(c, name)
		}
		if err := wg.Start(worker); err != nil {
			return wg.WaitError(err)
		}
	}

	return wg.Wait()
}
<|endoftext|>


<commit_before>package main

import (
	"net"
	"fmt"
	"encoding/hex"
	"os"
	"bytes"
	"log"
	"strconv"
	"math"
)

// Hex values of the byte arrays required in string form
const (
	initStreamValues     = "00000000000000000000010000000300000000000000000000006800000001000000100000000N0000000100000000UUUUUUUUUU000000000000000000000000000001000000000000010124000000PPPPPPPPPPPP00009cc9c805000000000400010004000000a8c9c80500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
	successfulAuthValues = "1000000000000000"
	failedAuthValues     = "0800000004000000"
)

// newStreamConnection creates and sets up a new TCP connection
func newStreamConnection(channel *int) *net.TCPConn {
	// Get the stream initialization bytes
	streamInitData := initStreamBytes(channel)

	// Set up a new connection
	conn, err := net.DialTCP("tcp", nil, config.source)
	if err != nil {
		log.Fatalln("Dialing the DVR failed:", err.Error())
	}

	// Send the stream initialization byte array
	log.Println( "Sending DVR stream initialization byte array.")
	_, err = conn.Write(streamInitData)
	if err != nil {
		conn.Close()
		log.Fatalln("Writing stream init to DVR failed: ", err.Error())
	}

	// Check authentication response
	data := make([]byte, 8)
	_, err = conn.Read(data)
	if err != nil {
		conn.Close()
		log.Fatalln("Unable to read DVR authentication response: ", err.Error())
	}

	// Check if authenticated
	successfulAuthBytes, _ := hex.DecodeString(successfulAuthValues)
	failedAuthBytes, _ := hex.DecodeString(failedAuthValues)
	if bytes.Equal(data, successfulAuthBytes) {
		conn.Close()
		log.Println("DVR authentication successful.")
	} else if bytes.Equal(data, failedAuthBytes) {
		conn.Close()
		log.Fatalln("DVR authentication failed due to invalid credentials.")
	} else {
		conn.Close()
		log.Fatalln("DVR authentication failed due to unknown reason.")
	}

	// Return the stream
	return conn
}

// initStreamBytes returns the byte array required to initialize a stream
func initStreamBytes(channel *int) []byte {
	hexValues := initStreamValues

	channelStartPos := 77
	channelEndPos := 78

	// Convert channel from 1, 2, 3, 4 to 1, 2, 4, 8 respectively
	parsedChannel := int(math.Exp2(float64(*channel - 1)))
	hexValues = hexValues[:channelStartPos] + fmt.Sprintf("%d", parsedChannel) + hexValues[channelEndPos:]

	// Parse username
	for i, v := range config.user {
		startPos := 94 + 2*i
		endPos := 96 + 2*i
		hexValues = hexValues[:startPos] + fmt.Sprintf("%x", int(v)) + hexValues[endPos:]
	}

	// Parse password
	for i, v := range config.pass {
		startPos := 158 + 2*i
		endPos := 160 + 2*i
		hexValues = hexValues[:startPos] + fmt.Sprintf("%x", int(v)) + hexValues[endPos:]
	}

	// Decode the hex string into a byte array
	byteArray, err := hex.DecodeString(hexValues)
	if err != nil {
		log.Fatalln("Unable to decode stream initialization values to byte array: ", err.Error())
	}
	return byteArray
}

// StreamToServer streams the video to the server
func StreamToServer(channel *int) {
	defer wg.Done()

	conn := newStreamConnection(channel)
	defer conn.Close()

	// Create a client
	c := Client(channel)

	// Start the client handler to receive messages
	go Handle(c)

	// Get the main camera stream
	for {
		data := make([]byte, socketBufferSize)
		n, err := conn.Read(data)
		if err != nil {
			log.Panicln("Error occurred while reading from DVR stream connection: ", err.Error())
		}

		c.send <- data[:n]
	}
}

func StreamToStdout(channel *int) {
	defer wg.Done()

	conn := newStreamConnection(channel)
	defer conn.Close()

	for {
		data := make([]byte, socketBufferSize)
		n, err := conn.Read(data)
		if err != nil {
			log.Panicln("Error occurred while reading from DVR stream connection: ", err.Error())
		}

		log.Printf( "%s", data[:n])
	}
}

func StreamToFile(channel *int) {
	defer wg.Done()

	// Attempt to create output file
	fileName := "swann_" + strconv.Itoa(*channel)
	file, err := os.Create(fileName)
	if err != nil {
		log.Fatalln("Error occured while attempting to open output file: ", err.Error())
	}

	// Open file for writing
	file, err = os.OpenFile(fileName, os.O_WRONLY, 0644)
	if err != nil {
		log.Fatalln(err)
	}
	print("Writing to: " + fileName)
	defer file.Close()

	conn := newStreamConnection(channel)
	defer conn.Close()

	for {
		data := make([]byte, socketBufferSize)
		n, err := conn.Read(data)
		if err != nil {
			log.Panicln("Error occurred while reading from DVR stream connection: ", err.Error())
		}

		file.Write(data[:n])
	}
}
<commit_msg>Fix accidental closing of DVR connection upon successful auth<commit_after>package main

import (
	"net"
	"fmt"
	"encoding/hex"
	"os"
	"bytes"
	"log"
	"strconv"
	"math"
)

// Hex values of the byte arrays required in string form
const (
	initStreamValues     = "00000000000000000000010000000300000000000000000000006800000001000000100000000N0000000100000000UUUUUUUUUU000000000000000000000000000001000000000000010124000000PPPPPPPPPPPP00009cc9c805000000000400010004000000a8c9c80500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
	successfulAuthValues = "1000000000000000"
	failedAuthValues     = "0800000004000000"
)

// newStreamConnection creates and sets up a new TCP connection
func newStreamConnection(channel *int) *net.TCPConn {
	// Get the stream initialization bytes
	streamInitData := initStreamBytes(channel)

	// Set up a new connection
	conn, err := net.DialTCP("tcp", nil, config.source)
	if err != nil {
		log.Fatalln("Dialing the DVR failed:", err.Error())
	}

	// Send the stream initialization byte array
	log.Println("Sending DVR stream initialization byte array.")
	_, err = conn.Write(streamInitData)
	if err != nil {
		conn.Close()
		log.Fatalln("Writing stream init to DVR failed: ", err.Error())
	}

	// Check authentication response
	data := make([]byte, 8)
	_, err = conn.Read(data)
	if err != nil {
		conn.Close()
		log.Fatalln("Unable to read DVR authentication response: ", err.Error())
	}

	// Check if authenticated
	successfulAuthBytes, _ := hex.DecodeString(successfulAuthValues)
	failedAuthBytes, _ := hex.DecodeString(failedAuthValues)
	if bytes.Equal(data, successfulAuthBytes) {
		log.Println("DVR authentication successful.")
	} else if bytes.Equal(data, failedAuthBytes) {
		conn.Close()
		log.Fatalln("DVR authentication failed due to invalid credentials.")
	} else {
		conn.Close()
		log.Fatalln("DVR authentication failed due to unknown reason.")
	}

	// Return the stream
	return conn
}

// initStreamBytes returns the byte array required to initialize a stream
func initStreamBytes(channel *int) []byte {
	hexValues := initStreamValues

	channelStartPos := 77
	channelEndPos := 78

	// Convert channel from 1, 2, 3, 4 to 1, 2, 4, 8 respectively
	parsedChannel := int(math.Exp2(float64(*channel - 1)))
	hexValues = hexValues[:channelStartPos] + fmt.Sprintf("%d", parsedChannel) + hexValues[channelEndPos:]

	// Parse username
	for i, v := range config.user {
		startPos := 94 + 2*i
		endPos := 96 + 2*i
		hexValues = hexValues[:startPos] + fmt.Sprintf("%x", int(v)) + hexValues[endPos:]
	}

	// Parse password
	for i, v := range config.pass {
		startPos := 158 + 2*i
		endPos := 160 + 2*i
		hexValues = hexValues[:startPos] + fmt.Sprintf("%x", int(v)) + hexValues[endPos:]
	}

	// Decode the hex string into a byte array
	byteArray, err := hex.DecodeString(hexValues)
	if err != nil {
		log.Fatalln("Unable to decode stream initialization values to byte array: ", err.Error())
	}
	return byteArray
}

// StreamToServer streams the video to the server
func StreamToServer(channel *int) {
	defer wg.Done()

	conn := newStreamConnection(channel)
	defer conn.Close()

	// Create a client
	c := Client(channel)

	// Start the client handler to receive messages
	go Handle(c)

	// Get the main camera stream
	for {
		data := make([]byte, socketBufferSize)
		n, err := conn.Read(data)
		if err != nil {
			log.Panicln("Error occurred while reading from DVR stream connection: ", err.Error())
		}

		c.send <- data[:n]
	}
}

func StreamToStdout(channel *int) {
	defer wg.Done()

	conn := newStreamConnection(channel)
	defer conn.Close()

	for {
		data := make([]byte, socketBufferSize)
		n, err := conn.Read(data)
		if err != nil {
			log.Panicln("Error occurred while reading from DVR stream connection: ", err.Error())
		}

		log.Printf("%s", data[:n])
	}
}

func StreamToFile(channel *int) {
	defer wg.Done()

	// Attempt to create output file
	fileName := "swann_" + strconv.Itoa(*channel)
	file, err := os.Create(fileName)
	if err != nil {
		log.Fatalln("Error occured while attempting to open output file: ", err.Error())
	}

	// Open file for writing
	file, err = os.OpenFile(fileName, os.O_WRONLY, 0644)
	if err != nil {
		log.Fatalln(err)
	}
	print("Writing to: " + fileName)
	defer file.Close()

	conn := newStreamConnection(channel)
	defer conn.Close()

	for {
		data := make([]byte, socketBufferSize)
		n, err := conn.Read(data)
		if err != nil {
			log.Panicln("Error occurred while reading from DVR stream connection: ", err.Error())
		}

		file.Write(data[:n])
	}
}
<|endoftext|>


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

package main

import (
	"encoding/json"
	"log"
	"os"

	"github.com/google/go-cmp/cmp"
	"golang.org/x/vuln/vulncheck"
)

const usage = `test helper for examining the output of running govulncheck on k8s@v1.15.11.

Example usage: ./k8s [path to output file]
`

func main() {
	if len(os.Args) != 2 {
		log.Fatal("Incorrect number of expected command line arguments", usage)
	}
	out := os.Args[1]

	outJson, err := os.ReadFile(out)
	if err != nil {
		log.Fatal("Failed to read:", out)
	}

	var r vulncheck.Result
	if err := json.Unmarshal(outJson, &r); err != nil {
		log.Fatal("Failed to load json into vulncheck.Result:", err)
	}

	if len(r.Vulns) != 48 {
		log.Fatalf("want 48 vulns; got %d", len(r.Vulns))
	}

	type vuln struct {
		pkg    string
		symbol string
	}
	calledVulns := make(map[vuln]bool)
	for _, v := range r.Vulns {
		calledVulns[vuln{v.PkgPath, v.Symbol}] = true
	}

	want := map[vuln]bool{
		{"github.com/containernetworking/cni/pkg/invoke", "FindInPath"}:                         true,
		{"github.com/evanphx/json-patch", "partialArray.add"}:                                   true,
		{"github.com/opencontainers/selinux/go-selinux", "CurrentLabel"}:                        true,
		{"github.com/opencontainers/selinux/go-selinux", "FileLabel"}:                           true,
		{"github.com/opencontainers/selinux/go-selinux", "GetEnabled"}:                          true,
		{"github.com/opencontainers/selinux/go-selinux", "SetFileLabel"}:                        true,
		{"github.com/opencontainers/selinux/go-selinux", "getSelinuxMountPoint"}:                true,
		{"github.com/opencontainers/selinux/go-selinux", "lgetxattr"}:                           true,
		{"github.com/opencontainers/selinux/go-selinux", "lsetxattr"}:                           true,
		{"github.com/opencontainers/selinux/go-selinux", "readCon"}:                             true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.getEnabled"}:             true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.getSELinuxfs"}:           true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.setEnable"}:              true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.setSELinuxfs"}:           true,
		{"github.com/prometheus/client_golang/prometheus/promhttp", "Handler"}:                  true,
		{"github.com/prometheus/client_golang/prometheus/promhttp", "HandlerFor"}:               true,
		{"github.com/prometheus/client_golang/prometheus/promhttp", "InstrumentHandlerCounter"}: true,
		{"github.com/prometheus/client_golang/prometheus/promhttp", "InstrumentMetricHandler"}:  true,
		{"golang.org/x/crypto/cryptobyte", "Builder.AddBytes"}:                                  true,
		{"golang.org/x/crypto/cryptobyte", "Builder.AddUint16LengthPrefixed"}:                   true,
		{"golang.org/x/crypto/cryptobyte", "Builder.Bytes"}:                                     true,
		{"golang.org/x/crypto/cryptobyte", "Builder.add"}:                                       true,
		{"golang.org/x/crypto/cryptobyte", "Builder.addLengthPrefixed"}:                         true,
		{"golang.org/x/crypto/cryptobyte", "Builder.callContinuation"}:                          true,
		{"golang.org/x/crypto/cryptobyte", "Builder.flushChild"}:                                true,
		{"golang.org/x/crypto/cryptobyte", "NewBuilder"}:                                        true,
		{"golang.org/x/crypto/cryptobyte", "String.Empty"}:                                      true,
		{"golang.org/x/crypto/cryptobyte", "String.PeekASN1Tag"}:                                true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadASN1"}:                                   true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadAnyASN1"}:                                true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadBytes"}:                                  true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadOptionalASN1"}:                           true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadUint16LengthPrefixed"}:                   true,
		{"golang.org/x/crypto/cryptobyte", "String.Skip"}:                                       true,
		{"golang.org/x/crypto/cryptobyte", "String.read"}:                                       true,
		{"golang.org/x/crypto/cryptobyte", "String.readASN1"}:                                   true,
		{"golang.org/x/crypto/cryptobyte", "String.readLengthPrefixed"}:                         true,
		{"golang.org/x/crypto/cryptobyte", "String.readUnsigned"}:                               true,
		{"golang.org/x/crypto/salsa20/salsa", "XORKeyStream"}:                                   true,
		{"golang.org/x/crypto/ssh", "NewPublicKey"}:                                             true,
		{"golang.org/x/crypto/ssh", "ed25519PublicKey.Verify"}:                                  true,
		{"golang.org/x/crypto/ssh", "parseED25519"}:                                             true,
		{"golang.org/x/net/http/httpguts", "HeaderValuesContainsToken"}:                         true,
		{"golang.org/x/net/http/httpguts", "headerValueContainsToken"}:                          true,
		{"golang.org/x/net/http2", "Server.ServeConn"}:                                          true,
		{"golang.org/x/net/http2", "serverConn.canonicalHeader"}:                                true,
		{"golang.org/x/text/encoding/unicode", "bomOverride.Transform"}:                         true,
		{"golang.org/x/text/encoding/unicode", "utf16Decoder.Transform"}:                        true,
	}

	if diff := cmp.Diff(want, calledVulns); diff != "" {
		log.Fatalf("reachable vulnerable symbols mismatch (-want, +got):\n%s", diff)
	}
}
<commit_msg>cmd/govulncheck/integration: address vulndb changes<commit_after>// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"log"
	"os"

	"github.com/google/go-cmp/cmp"
	"golang.org/x/vuln/vulncheck"
)

const usage = `test helper for examining the output of running govulncheck on k8s@v1.15.11.

Example usage: ./k8s [path to output file]
`

func main() {
	if len(os.Args) != 2 {
		log.Fatal("Incorrect number of expected command line arguments", usage)
	}
	out := os.Args[1]

	outJson, err := os.ReadFile(out)
	if err != nil {
		log.Fatal("Failed to read:", out)
	}

	var r vulncheck.Result
	if err := json.Unmarshal(outJson, &r); err != nil {
		log.Fatal("Failed to load json into vulncheck.Result:", err)
	}

	if len(r.Vulns) != 44 {
		log.Fatalf("want 44 vulns; got %d", len(r.Vulns))
	}

	type vuln struct {
		pkg    string
		symbol string
	}
	calledVulns := make(map[vuln]bool)
	for _, v := range r.Vulns {
		calledVulns[vuln{v.PkgPath, v.Symbol}] = true
	}

	want := map[vuln]bool{
		{"github.com/containernetworking/cni/pkg/invoke", "FindInPath"}:               true,
		{"github.com/evanphx/json-patch", "partialArray.add"}:                         true,
		{"github.com/opencontainers/selinux/go-selinux", "CurrentLabel"}:              true,
		{"github.com/opencontainers/selinux/go-selinux", "FileLabel"}:                 true,
		{"github.com/opencontainers/selinux/go-selinux", "GetEnabled"}:                true,
		{"github.com/opencontainers/selinux/go-selinux", "SetFileLabel"}:              true,
		{"github.com/opencontainers/selinux/go-selinux", "getSelinuxMountPoint"}:      true,
		{"github.com/opencontainers/selinux/go-selinux", "lgetxattr"}:                 true,
		{"github.com/opencontainers/selinux/go-selinux", "lsetxattr"}:                 true,
		{"github.com/opencontainers/selinux/go-selinux", "readCon"}:                   true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.getEnabled"}:   true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.getSELinuxfs"}: true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.setEnable"}:    true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.setSELinuxfs"}: true,
		{"golang.org/x/crypto/cryptobyte", "Builder.AddBytes"}:                        true,
		{"golang.org/x/crypto/cryptobyte", "Builder.AddUint16LengthPrefixed"}:         true,
		{"golang.org/x/crypto/cryptobyte", "Builder.Bytes"}:                           true,
		{"golang.org/x/crypto/cryptobyte", "Builder.add"}:                             true,
		{"golang.org/x/crypto/cryptobyte", "Builder.addLengthPrefixed"}:               true,
		{"golang.org/x/crypto/cryptobyte", "Builder.callContinuation"}:                true,
		{"golang.org/x/crypto/cryptobyte", "Builder.flushChild"}:                      true,
		{"golang.org/x/crypto/cryptobyte", "NewBuilder"}:                              true,
		{"golang.org/x/crypto/cryptobyte", "String.Empty"}:                            true,
		{"golang.org/x/crypto/cryptobyte", "String.PeekASN1Tag"}:                      true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadASN1"}:                         true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadAnyASN1"}:                      true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadBytes"}:                        true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadOptionalASN1"}:                 true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadUint16LengthPrefixed"}:         true,
		{"golang.org/x/crypto/cryptobyte", "String.Skip"}:                             true,
		{"golang.org/x/crypto/cryptobyte", "String.read"}:                             true,
		{"golang.org/x/crypto/cryptobyte", "String.readASN1"}:                         true,
		{"golang.org/x/crypto/cryptobyte", "String.readLengthPrefixed"}:               true,
		{"golang.org/x/crypto/cryptobyte", "String.readUnsigned"}:                     true,
		{"golang.org/x/crypto/salsa20/salsa", "XORKeyStream"}:                         true,
		{"golang.org/x/crypto/ssh", "NewPublicKey"}:                                   true,
		{"golang.org/x/crypto/ssh", "ed25519PublicKey.Verify"}:                        true,
		{"golang.org/x/crypto/ssh", "parseED25519"}:                                   true,
		{"golang.org/x/net/http/httpguts", "HeaderValuesContainsToken"}:               true,
		{"golang.org/x/net/http/httpguts", "headerValueContainsToken"}:                true,
		{"golang.org/x/net/http2", "Server.ServeConn"}:                                true,
		{"golang.org/x/net/http2", "serverConn.canonicalHeader"}:                      true,
		{"golang.org/x/text/encoding/unicode", "bomOverride.Transform"}:               true,
		{"golang.org/x/text/encoding/unicode", "utf16Decoder.Transform"}:              true,
	}

	if diff := cmp.Diff(want, calledVulns); diff != "" {
		log.Fatalf("reachable vulnerable symbols mismatch (-want, +got):\n%s", diff)
	}
}
<|endoftext|>