<commit_before>// Copyright 2016-2017 Authors of Cilium
//
// 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 endpointmanager

import (
	"context"
	"fmt"
	"sync"

	"github.com/cilium/cilium/pkg/endpoint"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/metrics"
	"github.com/cilium/cilium/pkg/policy"

	"github.com/prometheus/client_golang/prometheus"
)

var (
	log = logging.DefaultLogger

	// mutex protects endpoints and endpointsAux
	mutex lock.RWMutex

	// endpoints is the global list of endpoints indexed by ID. mutex must
	// be held to read and write.
	endpoints    = map[uint16]*endpoint.Endpoint{}
	endpointsAux = map[string]*endpoint.Endpoint{}
)

func init() {
	// EndpointCount is a function used to collect this metric. We cannot
	// increment/decrement a gauge since we invoke Remove gratuitiously and that
	// would result in negative counts.
	// It must be thread-safe.
	metrics.EndpointCount = prometheus.NewGaugeFunc(prometheus.GaugeOpts{
		Namespace: metrics.Namespace,
		Name:      "endpoint_count",
		Help:      "Number of endpoints managed by this agent",
	},
		func() float64 { return float64(len(GetEndpoints())) },
	)
	metrics.MustRegister(metrics.EndpointCount)
}

// Insert inserts the endpoint into the global maps.
// Must be called with ep.Mutex.RLock held.
func Insert(ep *endpoint.Endpoint) {
	mutex.Lock()
	endpoints[ep.ID] = ep
	updateReferences(ep)
	mutex.Unlock()
}

// Lookup looks up the endpoint by prefix id
func Lookup(id string) (*endpoint.Endpoint, error) {
	mutex.RLock()
	defer mutex.RUnlock()

	prefix, eid, err := endpoint.ParseID(id)
	if err != nil {
		return nil, err
	}

	switch prefix {
	case endpoint.CiliumLocalIdPrefix:
		n, err := endpoint.ParseCiliumID(id)
		if err != nil {
			return nil, err
		}
		return lookupCiliumID(uint16(n)), nil

	case endpoint.CiliumGlobalIdPrefix:
		return nil, fmt.Errorf("Unsupported id format for now")

	case endpoint.ContainerIdPrefix:
		return lookupDockerID(eid), nil

	case endpoint.DockerEndpointPrefix:
		return lookupDockerEndpoint(eid), nil

	case endpoint.ContainerNamePrefix:
		return lookupDockerContainerName(eid), nil

	case endpoint.PodNamePrefix:
		return lookupPodNameLocked(eid), nil

	case endpoint.IPv4Prefix:
		return lookupIPv4(eid), nil

	default:
		return nil, fmt.Errorf("Unknown endpoint prefix %s", prefix)
	}
}

// LookupCiliumID looks up endpoint by endpoint ID
func LookupCiliumID(id uint16) *endpoint.Endpoint {
	mutex.RLock()
	ep := lookupCiliumID(id)
	mutex.RUnlock()
	return ep
}

// LookupDockerID looks up endpoint by Docker ID
func LookupDockerID(id string) *endpoint.Endpoint {
	mutex.RLock()
	ep := lookupDockerID(id)
	mutex.RUnlock()
	return ep
}

// LookupIPv4 looks up endpoint by IPv4 address
func LookupIPv4(ipv4 string) *endpoint.Endpoint {
	mutex.RLock()
	ep := lookupIPv4(ipv4)
	mutex.RUnlock()
	return ep
}

// UpdateReferences makes an endpoint available by all possible reference
// fields as available for this endpoint (containerID, IPv4 address, ...)
// Must be called with ep.Mutex.RLock held.
func UpdateReferences(ep *endpoint.Endpoint) {
	mutex.Lock()
	defer mutex.Unlock()
	updateReferences(ep)
}

// Remove removes the endpoint from the global maps.
// Must be called with ep.Mutex.RLock held.
func Remove(ep *endpoint.Endpoint) {
	mutex.Lock()
	defer mutex.Unlock()
	delete(endpoints, ep.ID)

	if ep.DockerID != "" {
		delete(endpointsAux, endpoint.NewID(endpoint.ContainerIdPrefix, ep.DockerID))
	}

	if ep.DockerEndpointID != "" {
		delete(endpointsAux, endpoint.NewID(endpoint.DockerEndpointPrefix, ep.DockerEndpointID))
	}

	if ep.IPv4.String() != "" {
		delete(endpointsAux, endpoint.NewID(endpoint.IPv4Prefix, ep.IPv4.String()))
	}

	if ep.ContainerName != "" {
		delete(endpointsAux, endpoint.NewID(endpoint.ContainerNamePrefix, ep.ContainerName))
	}

	if podName := ep.GetK8sNamespaceAndPodNameLocked(); podName != "" {
		delete(endpointsAux, endpoint.NewID(endpoint.PodNamePrefix, podName))
	}
}

// lookupCiliumID looks up endpoint by endpoint ID
func lookupCiliumID(id uint16) *endpoint.Endpoint {
	if ep, ok := endpoints[id]; ok {
		return ep
	}
	return nil
}

func lookupDockerEndpoint(id string) *endpoint.Endpoint {
	if ep, ok := endpointsAux[endpoint.NewID(endpoint.DockerEndpointPrefix, id)]; ok {
		return ep
	}
	return nil
}

func lookupPodNameLocked(name string) *endpoint.Endpoint {
	if ep, ok := endpointsAux[endpoint.NewID(endpoint.PodNamePrefix, name)]; ok {
		return ep
	}
	return nil
}

func lookupDockerContainerName(name string) *endpoint.Endpoint {
	if ep, ok := endpointsAux[endpoint.NewID(endpoint.ContainerNamePrefix, name)]; ok {
		return ep
	}
	return nil
}

func lookupIPv4(ipv4 string) *endpoint.Endpoint {
	if ep, ok := endpointsAux[endpoint.NewID(endpoint.IPv4Prefix, ipv4)]; ok {
		return ep
	}
	return nil
}

func lookupDockerID(id string) *endpoint.Endpoint {
	if ep, ok := endpointsAux[endpoint.NewID(endpoint.ContainerIdPrefix, id)]; ok {
		return ep
	}
	return nil
}

func linkContainerID(ep *endpoint.Endpoint) {
	endpointsAux[endpoint.NewID(endpoint.ContainerIdPrefix, ep.DockerID)] = ep
}

// UpdateReferences updates the mappings of various values to their corresponding
// endpoints, such as DockerID, Docker Container Name, Pod Name, etc.
func updateReferences(ep *endpoint.Endpoint) {
	if ep.DockerID != "" {
		linkContainerID(ep)
	}

	if ep.DockerEndpointID != "" {
		endpointsAux[endpoint.NewID(endpoint.DockerEndpointPrefix, ep.DockerEndpointID)] = ep
	}

	if ep.IPv4.String() != "" {
		endpointsAux[endpoint.NewID(endpoint.IPv4Prefix, ep.IPv4.String())] = ep
	}

	if ep.ContainerName != "" {
		endpointsAux[endpoint.NewID(endpoint.ContainerNamePrefix, ep.ContainerName)] = ep
	}

	if podName := ep.GetK8sNamespaceAndPodNameLocked(); podName != "" {
		endpointsAux[endpoint.NewID(endpoint.PodNamePrefix, podName)] = ep
	}
}

// TriggerPolicyUpdates calls TriggerPolicyUpdatesLocked for each endpoint and
// regenerates as required. During this process, the endpoint list is locked
// and cannot be modified.
// Returns a waiting group that can be used to know when all the endpoints are
// regenerated.
func TriggerPolicyUpdates(owner endpoint.Owner) *sync.WaitGroup {
	var wg sync.WaitGroup

	eps := GetEndpoints()
	wg.Add(len(eps))

	for _, ep := range eps {
		go func(ep *endpoint.Endpoint, wg *sync.WaitGroup) {
			ep.Mutex.Lock()
			policyChanges, ctCleaned, err := ep.TriggerPolicyUpdatesLocked(owner, nil)
			regen := false
			if err == nil && policyChanges {
				// Regenerate only if state transition succeeds
				regen = ep.SetStateLocked(endpoint.StateWaitingToRegenerate, "Triggering endpoint regeneration due to policy updates")
			}
			ep.Mutex.Unlock()

			if err != nil {
				log.WithError(err).Warn("Error while handling policy updates for endpoint")
				ep.LogStatus(endpoint.Policy, endpoint.Failure, "Error while handling policy updates for endpoint: "+err.Error())
			} else {
				// Wait for endpoint CT clean has complete before
				// regenerating endpoint.
				ctCleaned.Wait()
				if !policyChanges {
					ep.LogStatusOK(endpoint.Policy, "Endpoint policy update skipped because no changes were needed")
				} else if regen {
					// Regenerate logs status according to the build success/failure
					<-ep.Regenerate(owner, "endpoint policy updated & changes were needed")
				} // else policy changed, but can't regenerate => do not change status
			}
			wg.Done()
		}(ep, &wg)
	}

	return &wg
}

// HasGlobalCT returns true if the endpoints have a global CT, false otherwise.
func HasGlobalCT() bool {
	eps := GetEndpoints()
	for _, e := range eps {
		e.RLock()
		globalCT := e.Consumable != nil && !e.Opts.IsEnabled(endpoint.OptionConntrackLocal)
		e.RUnlock()
		if globalCT {
			return true
		}
	}
	return false
}

// GetEndpoints returns a slice of all endpoints present in endpoint manager.
func GetEndpoints() []*endpoint.Endpoint {
	mutex.RLock()
	eps := make([]*endpoint.Endpoint, 0, len(endpoints))
	for _, ep := range endpoints {
		eps = append(eps, ep)
	}
	mutex.RUnlock()
	return eps
}

// AddEndpoint takes the prepared endpoint object and starts managing it.
func AddEndpoint(owner endpoint.Owner, ep *endpoint.Endpoint, reason string) error {
	ep.RunK8sCiliumEndpointSync() // start the k8s update controller

	alwaysEnforce := policy.GetPolicyEnabled() == endpoint.AlwaysEnforce
	ep.Opts.Set(endpoint.OptionIngressPolicy, alwaysEnforce)
	ep.Opts.Set(endpoint.OptionEgressPolicy, alwaysEnforce)

	if err := ep.CreateDirectory(); err != nil {
		return err
	}

	// Regenerate immediately if ready or waiting for identity
	ep.Mutex.Lock()
	build := false
	state := ep.GetStateLocked()
	// Note that the endpoint state can initially also be "creating", and the
	// initial build will not be done yet in that case. A following PATCH
	// request will be needed to change the state and trigger bpf build.
	if state == endpoint.StateReady {
		ep.SetStateLocked(endpoint.StateWaitingToRegenerate, reason)
		build = true
	} else if state == endpoint.StateWaitingForIdentity {
		// state not changed if it is "waiting-for-identity",
		// but we still trigger the initial build.
		build = true
	}
	ep.Mutex.Unlock()
	if build {
		if err := ep.RegenerateWait(owner, reason); err != nil {
			ep.RemoveDirectory()
			return err
		}
	}

	ep.Mutex.RLock()
	Insert(ep)
	ep.Mutex.RUnlock()

	return nil
}

// WaitForEndpointsAtPolicyRev waits for all endpoints which existed at the time
// this function is called to be at a given policy revision.
// New endpoints appearing while waiting are ignored.
func WaitForEndpointsAtPolicyRev(ctx context.Context, rev uint64) error {
	eps := GetEndpoints()
	for i := range eps {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-eps[i].WaitForPolicyRevision(ctx, rev):
		}
	}
	return nil
}
<commit_msg>k8s: k8s CEP controller runs for restored endpoints<commit_after>// Copyright 2016-2017 Authors of Cilium
//
// 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 endpointmanager

import (
	"context"
	"fmt"
	"sync"

	"github.com/cilium/cilium/pkg/endpoint"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging"
	"github.com/cilium/cilium/pkg/metrics"
	"github.com/cilium/cilium/pkg/policy"

	"github.com/prometheus/client_golang/prometheus"
)

var (
	log = logging.DefaultLogger

	// mutex protects endpoints and endpointsAux
	mutex lock.RWMutex

	// endpoints is the global list of endpoints indexed by ID. mutex must
	// be held to read and write.
	endpoints    = map[uint16]*endpoint.Endpoint{}
	endpointsAux = map[string]*endpoint.Endpoint{}
)

func init() {
	// EndpointCount is a function used to collect this metric. We cannot
	// increment/decrement a gauge since we invoke Remove gratuitiously and that
	// would result in negative counts.
	// It must be thread-safe.
	metrics.EndpointCount = prometheus.NewGaugeFunc(prometheus.GaugeOpts{
		Namespace: metrics.Namespace,
		Name:      "endpoint_count",
		Help:      "Number of endpoints managed by this agent",
	},
		func() float64 { return float64(len(GetEndpoints())) },
	)
	metrics.MustRegister(metrics.EndpointCount)
}

// Insert inserts the endpoint into the global maps.
// Must be called with ep.Mutex.RLock held.
func Insert(ep *endpoint.Endpoint) {
	mutex.Lock()
	defer mutex.Unlock()

	endpoints[ep.ID] = ep
	updateReferences(ep)
	ep.RunK8sCiliumEndpointSync() // start the k8s update controller
}

// Lookup looks up the endpoint by prefix id
func Lookup(id string) (*endpoint.Endpoint, error) {
	mutex.RLock()
	defer mutex.RUnlock()

	prefix, eid, err := endpoint.ParseID(id)
	if err != nil {
		return nil, err
	}

	switch prefix {
	case endpoint.CiliumLocalIdPrefix:
		n, err := endpoint.ParseCiliumID(id)
		if err != nil {
			return nil, err
		}
		return lookupCiliumID(uint16(n)), nil

	case endpoint.CiliumGlobalIdPrefix:
		return nil, fmt.Errorf("Unsupported id format for now")

	case endpoint.ContainerIdPrefix:
		return lookupDockerID(eid), nil

	case endpoint.DockerEndpointPrefix:
		return lookupDockerEndpoint(eid), nil

	case endpoint.ContainerNamePrefix:
		return lookupDockerContainerName(eid), nil

	case endpoint.PodNamePrefix:
		return lookupPodNameLocked(eid), nil

	case endpoint.IPv4Prefix:
		return lookupIPv4(eid), nil

	default:
		return nil, fmt.Errorf("Unknown endpoint prefix %s", prefix)
	}
}

// LookupCiliumID looks up endpoint by endpoint ID
func LookupCiliumID(id uint16) *endpoint.Endpoint {
	mutex.RLock()
	ep := lookupCiliumID(id)
	mutex.RUnlock()
	return ep
}

// LookupDockerID looks up endpoint by Docker ID
func LookupDockerID(id string) *endpoint.Endpoint {
	mutex.RLock()
	ep := lookupDockerID(id)
	mutex.RUnlock()
	return ep
}

// LookupIPv4 looks up endpoint by IPv4 address
func LookupIPv4(ipv4 string) *endpoint.Endpoint {
	mutex.RLock()
	ep := lookupIPv4(ipv4)
	mutex.RUnlock()
	return ep
}

// UpdateReferences makes an endpoint available by all possible reference
// fields as available for this endpoint (containerID, IPv4 address, ...)
// Must be called with ep.Mutex.RLock held.
func UpdateReferences(ep *endpoint.Endpoint) {
	mutex.Lock()
	defer mutex.Unlock()
	updateReferences(ep)
}

// Remove removes the endpoint from the global maps.
// Must be called with ep.Mutex.RLock held.
func Remove(ep *endpoint.Endpoint) {
	mutex.Lock()
	defer mutex.Unlock()
	delete(endpoints, ep.ID)

	if ep.DockerID != "" {
		delete(endpointsAux, endpoint.NewID(endpoint.ContainerIdPrefix, ep.DockerID))
	}

	if ep.DockerEndpointID != "" {
		delete(endpointsAux, endpoint.NewID(endpoint.DockerEndpointPrefix, ep.DockerEndpointID))
	}

	if ep.IPv4.String() != "" {
		delete(endpointsAux, endpoint.NewID(endpoint.IPv4Prefix, ep.IPv4.String()))
	}

	if ep.ContainerName != "" {
		delete(endpointsAux, endpoint.NewID(endpoint.ContainerNamePrefix, ep.ContainerName))
	}

	if podName := ep.GetK8sNamespaceAndPodNameLocked(); podName != "" {
		delete(endpointsAux, endpoint.NewID(endpoint.PodNamePrefix, podName))
	}
}

// lookupCiliumID looks up endpoint by endpoint ID
func lookupCiliumID(id uint16) *endpoint.Endpoint {
	if ep, ok := endpoints[id]; ok {
		return ep
	}
	return nil
}

func lookupDockerEndpoint(id string) *endpoint.Endpoint {
	if ep, ok := endpointsAux[endpoint.NewID(endpoint.DockerEndpointPrefix, id)]; ok {
		return ep
	}
	return nil
}

func lookupPodNameLocked(name string) *endpoint.Endpoint {
	if ep, ok := endpointsAux[endpoint.NewID(endpoint.PodNamePrefix, name)]; ok {
		return ep
	}
	return nil
}

func lookupDockerContainerName(name string) *endpoint.Endpoint {
	if ep, ok := endpointsAux[endpoint.NewID(endpoint.ContainerNamePrefix, name)]; ok {
		return ep
	}
	return nil
}

func lookupIPv4(ipv4 string) *endpoint.Endpoint {
	if ep, ok := endpointsAux[endpoint.NewID(endpoint.IPv4Prefix, ipv4)]; ok {
		return ep
	}
	return nil
}

func lookupDockerID(id string) *endpoint.Endpoint {
	if ep, ok := endpointsAux[endpoint.NewID(endpoint.ContainerIdPrefix, id)]; ok {
		return ep
	}
	return nil
}

func linkContainerID(ep *endpoint.Endpoint) {
	endpointsAux[endpoint.NewID(endpoint.ContainerIdPrefix, ep.DockerID)] = ep
}

// UpdateReferences updates the mappings of various values to their corresponding
// endpoints, such as DockerID, Docker Container Name, Pod Name, etc.
func updateReferences(ep *endpoint.Endpoint) {
	if ep.DockerID != "" {
		linkContainerID(ep)
	}

	if ep.DockerEndpointID != "" {
		endpointsAux[endpoint.NewID(endpoint.DockerEndpointPrefix, ep.DockerEndpointID)] = ep
	}

	if ep.IPv4.String() != "" {
		endpointsAux[endpoint.NewID(endpoint.IPv4Prefix, ep.IPv4.String())] = ep
	}

	if ep.ContainerName != "" {
		endpointsAux[endpoint.NewID(endpoint.ContainerNamePrefix, ep.ContainerName)] = ep
	}

	if podName := ep.GetK8sNamespaceAndPodNameLocked(); podName != "" {
		endpointsAux[endpoint.NewID(endpoint.PodNamePrefix, podName)] = ep
	}
}

// TriggerPolicyUpdates calls TriggerPolicyUpdatesLocked for each endpoint and
// regenerates as required. During this process, the endpoint list is locked
// and cannot be modified.
// Returns a waiting group that can be used to know when all the endpoints are
// regenerated.
func TriggerPolicyUpdates(owner endpoint.Owner) *sync.WaitGroup {
	var wg sync.WaitGroup

	eps := GetEndpoints()
	wg.Add(len(eps))

	for _, ep := range eps {
		go func(ep *endpoint.Endpoint, wg *sync.WaitGroup) {
			ep.Mutex.Lock()
			policyChanges, ctCleaned, err := ep.TriggerPolicyUpdatesLocked(owner, nil)
			regen := false
			if err == nil && policyChanges {
				// Regenerate only if state transition succeeds
				regen = ep.SetStateLocked(endpoint.StateWaitingToRegenerate, "Triggering endpoint regeneration due to policy updates")
			}
			ep.Mutex.Unlock()

			if err != nil {
				log.WithError(err).Warn("Error while handling policy updates for endpoint")
				ep.LogStatus(endpoint.Policy, endpoint.Failure, "Error while handling policy updates for endpoint: "+err.Error())
			} else {
				// Wait for endpoint CT clean has complete before
				// regenerating endpoint.
				ctCleaned.Wait()
				if !policyChanges {
					ep.LogStatusOK(endpoint.Policy, "Endpoint policy update skipped because no changes were needed")
				} else if regen {
					// Regenerate logs status according to the build success/failure
					<-ep.Regenerate(owner, "endpoint policy updated & changes were needed")
				} // else policy changed, but can't regenerate => do not change status
			}
			wg.Done()
		}(ep, &wg)
	}

	return &wg
}

// HasGlobalCT returns true if the endpoints have a global CT, false otherwise.
func HasGlobalCT() bool {
	eps := GetEndpoints()
	for _, e := range eps {
		e.RLock()
		globalCT := e.Consumable != nil && !e.Opts.IsEnabled(endpoint.OptionConntrackLocal)
		e.RUnlock()
		if globalCT {
			return true
		}
	}
	return false
}

// GetEndpoints returns a slice of all endpoints present in endpoint manager.
func GetEndpoints() []*endpoint.Endpoint {
	mutex.RLock()
	eps := make([]*endpoint.Endpoint, 0, len(endpoints))
	for _, ep := range endpoints {
		eps = append(eps, ep)
	}
	mutex.RUnlock()
	return eps
}

// AddEndpoint takes the prepared endpoint object and starts managing it.
func AddEndpoint(owner endpoint.Owner, ep *endpoint.Endpoint, reason string) error {
	alwaysEnforce := policy.GetPolicyEnabled() == endpoint.AlwaysEnforce
	ep.Opts.Set(endpoint.OptionIngressPolicy, alwaysEnforce)
	ep.Opts.Set(endpoint.OptionEgressPolicy, alwaysEnforce)

	if err := ep.CreateDirectory(); err != nil {
		return err
	}

	// Regenerate immediately if ready or waiting for identity
	ep.Mutex.Lock()
	build := false
	state := ep.GetStateLocked()
	// Note that the endpoint state can initially also be "creating", and the
	// initial build will not be done yet in that case. A following PATCH
	// request will be needed to change the state and trigger bpf build.
	if state == endpoint.StateReady {
		ep.SetStateLocked(endpoint.StateWaitingToRegenerate, reason)
		build = true
	} else if state == endpoint.StateWaitingForIdentity {
		// state not changed if it is "waiting-for-identity",
		// but we still trigger the initial build.
		build = true
	}
	ep.Mutex.Unlock()
	if build {
		if err := ep.RegenerateWait(owner, reason); err != nil {
			ep.RemoveDirectory()
			return err
		}
	}

	ep.Mutex.RLock()
	Insert(ep)
	ep.Mutex.RUnlock()

	return nil
}

// WaitForEndpointsAtPolicyRev waits for all endpoints which existed at the time
// this function is called to be at a given policy revision.
// New endpoints appearing while waiting are ignored.
func WaitForEndpointsAtPolicyRev(ctx context.Context, rev uint64) error {
	eps := GetEndpoints()
	for i := range eps {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-eps[i].WaitForPolicyRevision(ctx, rev):
		}
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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 cmd

import (
	"fmt"
	"io"
	"strconv"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	utilnet "k8s.io/apimachinery/pkg/util/net"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/genericclioptions/resource"
	restclient "k8s.io/client-go/rest"
	api "k8s.io/kubernetes/pkg/apis/core"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/scheme"
	"k8s.io/kubernetes/pkg/kubectl/util/i18n"

	ct "github.com/daviddengcn/go-colortext"
	"github.com/spf13/cobra"
)

var (
	longDescr = templates.LongDesc(i18n.T(`
  Display addresses of the master and services with label kubernetes.io/cluster-service=true
  To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.`))

	clusterinfoExample = templates.Examples(i18n.T(`
		# Print the address of the master and cluster services
		kubectl cluster-info`))
)

type ClusterInfoOptions struct {
	genericclioptions.IOStreams

	Namespace string

	Builder *resource.Builder
	Client  *restclient.Config
}

func NewCmdClusterInfo(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := &ClusterInfoOptions{
		IOStreams: ioStreams,
	}

	cmd := &cobra.Command{
		Use:     "cluster-info",
		Short:   i18n.T("Display cluster info"),
		Long:    longDescr,
		Example: clusterinfoExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd))
			cmdutil.CheckErr(o.Run())
		},
	}
	cmd.AddCommand(NewCmdClusterInfoDump(f, ioStreams))
	return cmd
}

func (o *ClusterInfoOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
	var err error
	o.Client, err = f.ToRESTConfig()
	if err != nil {
		return err
	}

	cmdNamespace := cmdutil.GetFlagString(cmd, "namespace")
	if cmdNamespace == "" {
		cmdNamespace = metav1.NamespaceSystem
	}
	o.Namespace = cmdNamespace

	o.Builder = f.NewBuilder()
	return nil
}

func (o *ClusterInfoOptions) Run() error {
	// TODO use generalized labels once they are implemented (#341)
	b := o.Builder.
		WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
		NamespaceParam(o.Namespace).DefaultNamespace().
		LabelSelectorParam("kubernetes.io/cluster-service=true").
		ResourceTypeOrNameArgs(false, []string{"services"}...).
		Latest()
	err := b.Do().Visit(func(r *resource.Info, err error) error {
		if err != nil {
			return err
		}
		printService(o.Out, "Kubernetes master", o.Client.Host)

		services := r.Object.(*api.ServiceList).Items
		for _, service := range services {
			var link string
			if len(service.Status.LoadBalancer.Ingress) > 0 {
				ingress := service.Status.LoadBalancer.Ingress[0]
				ip := ingress.IP
				if ip == "" {
					ip = ingress.Hostname
				}
				for _, port := range service.Spec.Ports {
					link += "http://" + ip + ":" + strconv.Itoa(int(port.Port)) + " "
				}
			} else {
				name := service.ObjectMeta.Name

				if len(service.Spec.Ports) > 0 {
					port := service.Spec.Ports[0]

					// guess if the scheme is https
					scheme := ""
					if port.Name == "https" || port.Port == 443 {
						scheme = "https"
					}

					// format is <scheme>:<service-name>:<service-port-name>
					name = utilnet.JoinSchemeNamePort(scheme, service.ObjectMeta.Name, port.Name)
				}

				if len(o.Client.GroupVersion.Group) == 0 {
					link = o.Client.Host + "/api/" + o.Client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy"
				} else {
					link = o.Client.Host + "/api/" + o.Client.GroupVersion.Group + "/" + o.Client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy"

				}
			}
			name := service.ObjectMeta.Labels["kubernetes.io/name"]
			if len(name) == 0 {
				name = service.ObjectMeta.Name
			}
			printService(o.Out, name, link)
		}
		return nil
	})
	o.Out.Write([]byte("\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\n"))
	return err

	// TODO consider printing more information about cluster
}

func printService(out io.Writer, name, link string) {
	ct.ChangeColor(ct.Green, false, ct.None, false)
	fmt.Fprint(out, name)
	ct.ResetColor()
	fmt.Fprint(out, " is running at ")
	ct.ChangeColor(ct.Yellow, false, ct.None, false)
	fmt.Fprint(out, link)
	ct.ResetColor()
	fmt.Fprintln(out, "")
}
<commit_msg>Change service from internal version to external version<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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 cmd

import (
	"fmt"
	"io"
	"strconv"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	utilnet "k8s.io/apimachinery/pkg/util/net"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/genericclioptions/resource"
	restclient "k8s.io/client-go/rest"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/scheme"
	"k8s.io/kubernetes/pkg/kubectl/util/i18n"

	ct "github.com/daviddengcn/go-colortext"
	"github.com/spf13/cobra"
)

var (
	longDescr = templates.LongDesc(i18n.T(`
  Display addresses of the master and services with label kubernetes.io/cluster-service=true
  To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.`))

	clusterinfoExample = templates.Examples(i18n.T(`
		# Print the address of the master and cluster services
		kubectl cluster-info`))
)

type ClusterInfoOptions struct {
	genericclioptions.IOStreams

	Namespace string

	Builder *resource.Builder
	Client  *restclient.Config
}

func NewCmdClusterInfo(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := &ClusterInfoOptions{
		IOStreams: ioStreams,
	}

	cmd := &cobra.Command{
		Use:     "cluster-info",
		Short:   i18n.T("Display cluster info"),
		Long:    longDescr,
		Example: clusterinfoExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd))
			cmdutil.CheckErr(o.Run())
		},
	}
	cmd.AddCommand(NewCmdClusterInfoDump(f, ioStreams))
	return cmd
}

func (o *ClusterInfoOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
	var err error
	o.Client, err = f.ToRESTConfig()
	if err != nil {
		return err
	}

	cmdNamespace := cmdutil.GetFlagString(cmd, "namespace")
	if cmdNamespace == "" {
		cmdNamespace = metav1.NamespaceSystem
	}
	o.Namespace = cmdNamespace

	o.Builder = f.NewBuilder()
	return nil
}

func (o *ClusterInfoOptions) Run() error {
	// TODO use generalized labels once they are implemented (#341)
	b := o.Builder.
		WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
		NamespaceParam(o.Namespace).DefaultNamespace().
		LabelSelectorParam("kubernetes.io/cluster-service=true").
		ResourceTypeOrNameArgs(false, []string{"services"}...).
		Latest()
	err := b.Do().Visit(func(r *resource.Info, err error) error {
		if err != nil {
			return err
		}
		printService(o.Out, "Kubernetes master", o.Client.Host)

		services := r.Object.(*corev1.ServiceList).Items
		for _, service := range services {
			var link string
			if len(service.Status.LoadBalancer.Ingress) > 0 {
				ingress := service.Status.LoadBalancer.Ingress[0]
				ip := ingress.IP
				if ip == "" {
					ip = ingress.Hostname
				}
				for _, port := range service.Spec.Ports {
					link += "http://" + ip + ":" + strconv.Itoa(int(port.Port)) + " "
				}
			} else {
				name := service.ObjectMeta.Name

				if len(service.Spec.Ports) > 0 {
					port := service.Spec.Ports[0]

					// guess if the scheme is https
					scheme := ""
					if port.Name == "https" || port.Port == 443 {
						scheme = "https"
					}

					// format is <scheme>:<service-name>:<service-port-name>
					name = utilnet.JoinSchemeNamePort(scheme, service.ObjectMeta.Name, port.Name)
				}

				if len(o.Client.GroupVersion.Group) == 0 {
					link = o.Client.Host + "/api/" + o.Client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy"
				} else {
					link = o.Client.Host + "/api/" + o.Client.GroupVersion.Group + "/" + o.Client.GroupVersion.Version + "/namespaces/" + service.ObjectMeta.Namespace + "/services/" + name + "/proxy"

				}
			}
			name := service.ObjectMeta.Labels["kubernetes.io/name"]
			if len(name) == 0 {
				name = service.ObjectMeta.Name
			}
			printService(o.Out, name, link)
		}
		return nil
	})
	o.Out.Write([]byte("\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\n"))
	return err

	// TODO consider printing more information about cluster
}

func printService(out io.Writer, name, link string) {
	ct.ChangeColor(ct.Green, false, ct.None, false)
	fmt.Fprint(out, name)
	ct.ResetColor()
	fmt.Fprint(out, " is running at ")
	ct.ChangeColor(ct.Yellow, false, ct.None, false)
	fmt.Fprint(out, link)
	ct.ResetColor()
	fmt.Fprintln(out, "")
}
<|endoftext|>


<commit_before>// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements.  See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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 provision

import (
	"reflect"
	"testing"
)

type s struct {
	A int    `json:"a,omitempty"`
	B string `json:"b,omitempty"`
	C bool   `json:"c,omitempty"`
	D *s     `json:"d,omitempty"`
}

// TestConversions verifies that we can process proto structs via JSON.
func TestConversions(t *testing.T) {
	tests := []s{
		s{},
		s{A: 2},
		s{B: "foo"},
		s{C: true},
		s{D: &s{A: 3}},
		s{A: 1, B: "bar", C: true, D: &s{A: 3, B: "baz"}},
	}

	for _, test := range tests {
		enc, err := OptionsToProto(test)
		if err != nil {
			t.Errorf("Failed to marshal %v: %v", test, err)
		}
		var ret s
		if err := ProtoToOptions(enc, &ret); err != nil {
			t.Errorf("Failed to unmarshal %v from %v: %v", test, enc, err)
		}
		if !reflect.DeepEqual(test, ret) {
			t.Errorf("Unmarshal(Marshal(%v)) = %v, want %v", test, ret, test)
		}
	}
}
<commit_msg>[BEAM-13909] improve coverage of Provision package (#17014)<commit_after>// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements.  See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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 provision

import (
	"context"
	"fmt"
	"log"
	"net"
	"reflect"
	"sync"
	"testing"

	fnpb "github.com/apache/beam/sdks/v2/go/pkg/beam/model/fnexecution_v1"
	"google.golang.org/grpc"
)

type s struct {
	A int    `json:"a,omitempty"`
	B string `json:"b,omitempty"`
	C bool   `json:"c,omitempty"`
	D *s     `json:"d,omitempty"`
}

// TestConversions verifies that we can process proto structs via JSON.
func TestConversions(t *testing.T) {
	tests := []s{
		s{},
		s{A: 2},
		s{B: "foo"},
		s{C: true},
		s{D: &s{A: 3}},
		s{A: 1, B: "bar", C: true, D: &s{A: 3, B: "baz"}},
	}

	for _, test := range tests {
		enc, err := OptionsToProto(test)
		if err != nil {
			t.Errorf("Failed to marshal %v: %v", test, err)
		}
		var ret s
		if err := ProtoToOptions(enc, &ret); err != nil {
			t.Errorf("Failed to unmarshal %v from %v: %v", test, enc, err)
		}
		if !reflect.DeepEqual(test, ret) {
			t.Errorf("Unmarshal(Marshal(%v)) = %v, want %v", test, ret, test)
		}
	}
}

type ProvisionServiceServicer struct {
	fnpb.UnimplementedProvisionServiceServer
}

func (p ProvisionServiceServicer) GetProvisionInfo(ctx context.Context, req *fnpb.GetProvisionInfoRequest) (*fnpb.GetProvisionInfoResponse, error) {
	return &fnpb.GetProvisionInfoResponse{Info: &fnpb.ProvisionInfo{RetrievalToken: "token"}}, nil
}

func setup(addr *string, wg *sync.WaitGroup) {
	l, err := net.Listen("tcp", ":0")
	defer l.Close()
	if err != nil {
		log.Fatalf("failed to find an open port: %v", err)
	}
	port := l.Addr().(*net.TCPAddr).Port
	*addr = fmt.Sprintf(":%d", port)

	server := grpc.NewServer()
	defer server.Stop()

	prs := &ProvisionServiceServicer{}
	fnpb.RegisterProvisionServiceServer(server, prs)

	wg.Done()

	if err := server.Serve(l); err != nil {
		log.Fatalf("cannot serve the server: %v", err)
	}
}

func TestProvisionInfo(t *testing.T) {

	endpoint := ""
	var wg sync.WaitGroup
	wg.Add(1)
	go setup(&endpoint, &wg)
	wg.Wait()

	got, err := Info(context.Background(), endpoint)
	if err != nil {
		t.Errorf("error in response: %v", err)
	}
	want := &fnpb.ProvisionInfo{RetrievalToken: "token"}
	if got.GetRetrievalToken() != want.GetRetrievalToken() {
		t.Errorf("provision.Info() = %v, want %v", got, want)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.

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 merge_test

import (
	"fmt"
	"reflect"

	"sigs.k8s.io/structured-merge-diff/fieldpath"
	"sigs.k8s.io/structured-merge-diff/merge"
	"sigs.k8s.io/structured-merge-diff/typed"
)

// State of the current test in terms of live object. One can check at
// any time that Live and Managers match the expectations.
type State struct {
	Live     *typed.TypedValue
	Parser   *typed.ParseableType
	Managers fieldpath.ManagedFields
	Updater  *merge.Updater
}

func (s *State) checkInit() error {
	if s.Live == nil {
		obj, err := s.Parser.New()
		if err != nil {
			return fmt.Errorf("failed to create new empty object: %v", err)
		}
		s.Live = &obj
	}
	return nil
}

// Update the current state with the passed in object
func (s *State) Update(obj typed.YAMLObject, manager string) error {
	if err := s.checkInit(); err != nil {
		return err
	}
	tv, err := s.Parser.FromYAML(obj)
	managers, err := s.Updater.Update(*s.Live, tv, s.Managers, manager)
	if err != nil {
		return err
	}
	s.Live = &tv
	s.Managers = managers

	return nil
}

// Apply the passed in object to the current state
func (s *State) Apply(obj typed.YAMLObject, manager string, force bool) error {
	if err := s.checkInit(); err != nil {
		return err
	}
	tv, err := s.Parser.FromYAML(obj)
	if err != nil {
		return err
	}
	new, managers, err := s.Updater.Apply(*s.Live, tv, s.Managers, manager, force)
	if err != nil {
		return err
	}
	s.Live = &new
	s.Managers = managers

	return nil
}

// CompareLive takes a YAML string and returns the comparison with the
// current live object or an error.
func (s *State) CompareLive(obj typed.YAMLObject) (*typed.Comparison, error) {
	if err := s.checkInit(); err != nil {
		return nil, err
	}
	tv, err := s.Parser.FromYAML(obj)
	if err != nil {
		return nil, err
	}
	return s.Live.Compare(tv)
}

// dummyConverter doesn't convert, it just returns the same exact object no matter what.
type dummyConverter struct{}

// Convert returns the object given in input, not doing any conversion.
func (dummyConverter) Convert(v typed.TypedValue, version fieldpath.APIVersion) (typed.TypedValue, error) {
	return v, nil
}

// Operation is a step that will run when building a table-driven test.
type Operation interface {
	run(*State) error
}

// Apply is a type of operation. It is a non-forced apply run by a
// manager with a given object. Since non-forced apply operation can
// conflict, the user can specify the expected conflicts. If conflicts
// don't match, an error will occur.
type Apply struct {
	Manager   string
	Object    typed.YAMLObject
	Conflicts merge.Conflicts
}

var _ Operation = &Apply{}

func (a Apply) run(state *State) error {
	err := state.Apply(a.Object, a.Manager, false)
	if (err != nil || a.Conflicts != nil) && !reflect.DeepEqual(err, a.Conflicts) {
		return fmt.Errorf("expected conflicts: %v, got %v", a.Conflicts, err)
	}
	return nil

}

// ForceApply is a type of operation. It is a forced-apply run by a
// manager with a given object. Any error will be returned.
type ForceApply struct {
	Manager string
	Object  typed.YAMLObject
}

var _ Operation = &ForceApply{}

func (f ForceApply) run(state *State) error {
	return state.Apply(f.Object, f.Manager, true)
}

// Update is a type of operation. It is a controller type of
// update. Errors are passed along.
type Update struct {
	Manager string
	Object  typed.YAMLObject
}

var _ Operation = &Update{}

func (u Update) run(state *State) error {
	return state.Update(u.Object, u.Manager)
}

// TestCase is the list of operations that need to be run, as well as
// the object/managedfields as they are supposed to look like after all
// the operations have been successfully performed. If Object/Managed is
// not specified, then the comparison is not performed (any object or
// managed field will pass). Any error (conflicts aside) happen while
// running the operation, that error will be returned right away.
type TestCase struct {
	// Ops is the list of operations to run sequentially
	Ops []Operation
	// Object, if not empty, is the object as it's expected to
	// be after all the operations are run.
	Object typed.YAMLObject
	// Managed, if not nil, is the ManagedFields as expected
	// after all operations are run.
	Managed fieldpath.ManagedFields
}

// Test runs the test-case using the given parser.
func (tc TestCase) Test(parser *typed.ParseableType) error {
	state := State{
		Updater: &merge.Updater{Converter: &dummyConverter{}},
		Parser:  parser,
	}
	// We currently don't have any test that converts, we can take
	// care of that later.
	for i, ops := range tc.Ops {
		err := ops.run(&state)
		if err != nil {
			return fmt.Errorf("failed operation %d: %v", i, err)
		}
	}

	// If LastObject was specified, compare it with LiveState
	if tc.Object != typed.YAMLObject("") {
		comparison, err := state.CompareLive(tc.Object)
		if err != nil {
			return fmt.Errorf("failed to compare live with config: %v", err)
		}
		if !comparison.IsSame() {
			return fmt.Errorf("expected live and config to be the same: %v", comparison)
		}
	}

	if tc.Managed != nil {
		if diff := state.Managers.Difference(tc.Managed); len(diff) != 0 {
			return fmt.Errorf("expected Managers to be %v, got %v", tc.Managed, state.Managers)
		}
	}

	return nil
}
<commit_msg>add tab helper<commit_after>/*
Copyright 2018 The Kubernetes Authors.

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 merge_test

import (
	"bytes"
	"fmt"
	"reflect"
	"testing"

	"sigs.k8s.io/structured-merge-diff/fieldpath"
	"sigs.k8s.io/structured-merge-diff/merge"
	"sigs.k8s.io/structured-merge-diff/typed"
)

// State of the current test in terms of live object. One can check at
// any time that Live and Managers match the expectations.
type State struct {
	Live     *typed.TypedValue
	Parser   *typed.ParseableType
	Managers fieldpath.ManagedFields
	Updater  *merge.Updater
}

// FixTabsOrDie counts the number of tab characters preceding the first line in
// the given yaml object. It removes that many tabs from every line. It then
// converts remaining tabs to spaces (two spaces per tab). It panics (it's a
// test funtion) if it finds mixed tabs and spaces in front of a line, or if
// some line has fewer tabs than the first line.
//
// The purpose of this is to make it easier to read tests.
func FixTabsOrDie(in typed.YAMLObject) typed.YAMLObject {
	lines := bytes.Split([]byte(in), []byte{'\n'})
	if len(lines[0]) == 0 && len(lines) > 1 {
		lines = lines[1:]
	}
	prefix := 0
	for _, c := range lines[0] {
		if c != '\t' {
			break
		}
		prefix++
	}
	tabsToRemove := bytes.Repeat([]byte{'\t'}, prefix)

	for i := range lines {
		line := lines[i]
		if i == len(lines)-1 && len(line) <= prefix && bytes.HasPrefix(tabsToRemove, line) {
			// It's OK for the last line to be blank (trailing \n)
			lines[i] = []byte{}
			break
		}
		if !bytes.HasPrefix(line, tabsToRemove) {
			panic(fmt.Sprintf("line %v doesn't have %v tabs as a prefix:\n%s", i, prefix, in))
		}
		line = line[prefix:]
		indent := 0
		for _, c := range line {
			if c == ' ' {
				panic(fmt.Sprintf("line %v has mixed tabs and spaces:\n%v", i, in))
			}
			if c != '\t' {
				break
			}
			indent++
		}
		lines[i] = append(bytes.Repeat([]byte{' ', ' '}, indent), line[indent:]...)
	}
	return typed.YAMLObject(bytes.Join(lines, []byte{'\n'}))
}

func TestFixTabs(t *testing.T) {
	cases := []struct {
		in, out     typed.YAMLObject
		shouldPanic bool
	}{{
		in:  "\t\ta\n\t\t\tb\n",
		out: "a\n  b\n",
	}, {
		in:  "\n\t\ta\n\t\t\tb\n",
		out: "a\n  b\n",
	}, {
		in:  "\n\t\ta\n\t\t\tb\n\t",
		out: "a\n  b\n",
	}, {
		in:          "\t\ta\n\t\t  b\n",
		shouldPanic: true,
	}, {
		in:          "\t\ta\n\tb\n",
		shouldPanic: true,
	}}

	for i := range cases {
		tt := cases[i]
		t.Run(fmt.Sprintf("%v - %v", i, []byte(tt.in)), func(t *testing.T) {
			if tt.shouldPanic {
				defer func() {
					if x := recover(); x == nil {
						t.Errorf("expected a panic, but didn't get one")
					}
				}()
			}
			got := FixTabsOrDie(tt.in)
			if e, a := tt.out, got; e != a {
				t.Errorf("got %v", []byte(a))
			}
			if bytes.Contains([]byte(got), []byte{'\t'}) {
				t.Error("contained a tab")
			}
		})
	}
}

func (s *State) checkInit() error {
	if s.Live == nil {
		obj, err := s.Parser.New()
		if err != nil {
			return fmt.Errorf("failed to create new empty object: %v", err)
		}
		s.Live = &obj
	}
	return nil
}

// Update the current state with the passed in object
func (s *State) Update(obj typed.YAMLObject, manager string) error {
	obj = FixTabsOrDie(obj)
	if err := s.checkInit(); err != nil {
		return err
	}
	tv, err := s.Parser.FromYAML(obj)
	managers, err := s.Updater.Update(*s.Live, tv, s.Managers, manager)
	if err != nil {
		return err
	}
	s.Live = &tv
	s.Managers = managers

	return nil
}

// Apply the passed in object to the current state
func (s *State) Apply(obj typed.YAMLObject, manager string, force bool) error {
	obj = FixTabsOrDie(obj)
	if err := s.checkInit(); err != nil {
		return err
	}
	tv, err := s.Parser.FromYAML(obj)
	if err != nil {
		return err
	}
	new, managers, err := s.Updater.Apply(*s.Live, tv, s.Managers, manager, force)
	if err != nil {
		return err
	}
	s.Live = &new
	s.Managers = managers

	return nil
}

// CompareLive takes a YAML string and returns the comparison with the
// current live object or an error.
func (s *State) CompareLive(obj typed.YAMLObject) (*typed.Comparison, error) {
	obj = FixTabsOrDie(obj)
	if err := s.checkInit(); err != nil {
		return nil, err
	}
	tv, err := s.Parser.FromYAML(obj)
	if err != nil {
		return nil, err
	}
	return s.Live.Compare(tv)
}

// dummyConverter doesn't convert, it just returns the same exact object no matter what.
type dummyConverter struct{}

// Convert returns the object given in input, not doing any conversion.
func (dummyConverter) Convert(v typed.TypedValue, version fieldpath.APIVersion) (typed.TypedValue, error) {
	return v, nil
}

// Operation is a step that will run when building a table-driven test.
type Operation interface {
	run(*State) error
}

// Apply is a type of operation. It is a non-forced apply run by a
// manager with a given object. Since non-forced apply operation can
// conflict, the user can specify the expected conflicts. If conflicts
// don't match, an error will occur.
type Apply struct {
	Manager   string
	Object    typed.YAMLObject
	Conflicts merge.Conflicts
}

var _ Operation = &Apply{}

func (a Apply) run(state *State) error {
	err := state.Apply(a.Object, a.Manager, false)
	if (err != nil || a.Conflicts != nil) && !reflect.DeepEqual(err, a.Conflicts) {
		return fmt.Errorf("expected conflicts: %v, got %v", a.Conflicts, err)
	}
	return nil

}

// ForceApply is a type of operation. It is a forced-apply run by a
// manager with a given object. Any error will be returned.
type ForceApply struct {
	Manager string
	Object  typed.YAMLObject
}

var _ Operation = &ForceApply{}

func (f ForceApply) run(state *State) error {
	return state.Apply(f.Object, f.Manager, true)
}

// Update is a type of operation. It is a controller type of
// update. Errors are passed along.
type Update struct {
	Manager string
	Object  typed.YAMLObject
}

var _ Operation = &Update{}

func (u Update) run(state *State) error {
	return state.Update(u.Object, u.Manager)
}

// TestCase is the list of operations that need to be run, as well as
// the object/managedfields as they are supposed to look like after all
// the operations have been successfully performed. If Object/Managed is
// not specified, then the comparison is not performed (any object or
// managed field will pass). Any error (conflicts aside) happen while
// running the operation, that error will be returned right away.
type TestCase struct {
	// Ops is the list of operations to run sequentially
	Ops []Operation
	// Object, if not empty, is the object as it's expected to
	// be after all the operations are run.
	Object typed.YAMLObject
	// Managed, if not nil, is the ManagedFields as expected
	// after all operations are run.
	Managed fieldpath.ManagedFields
}

// Test runs the test-case using the given parser.
func (tc TestCase) Test(parser *typed.ParseableType) error {
	state := State{
		Updater: &merge.Updater{Converter: &dummyConverter{}},
		Parser:  parser,
	}
	// We currently don't have any test that converts, we can take
	// care of that later.
	for i, ops := range tc.Ops {
		err := ops.run(&state)
		if err != nil {
			return fmt.Errorf("failed operation %d: %v", i, err)
		}
	}

	// If LastObject was specified, compare it with LiveState
	if tc.Object != typed.YAMLObject("") {
		comparison, err := state.CompareLive(tc.Object)
		if err != nil {
			return fmt.Errorf("failed to compare live with config: %v", err)
		}
		if !comparison.IsSame() {
			return fmt.Errorf("expected live and config to be the same: %v", comparison)
		}
	}

	if tc.Managed != nil {
		if diff := state.Managers.Difference(tc.Managed); len(diff) != 0 {
			return fmt.Errorf("expected Managers to be %v, got %v", tc.Managed, state.Managers)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package data

type TransactionFlag uint32
type LedgerEntryFlag uint32

// Transaction Flags
const (
	//Universal flags
	TxCanonicalSignature TransactionFlag = 0x80000000

	// Payment flags
	TxNoDirectRipple TransactionFlag = 0x00010000
	TxPartialPayment TransactionFlag = 0x00020000
	TxLimitQuality   TransactionFlag = 0x00040000
	TxCircle         TransactionFlag = 0x00080000 // Not implemented

	// AccountSet flags
	TxSetRequireDest   TransactionFlag = 0x00000001
	TxSetRequireAuth   TransactionFlag = 0x00000002
	TxSetDisallowXRP   TransactionFlag = 0x00000003
	TxSetDisableMaster TransactionFlag = 0x00000004
	TxNoFreeze         TransactionFlag = 0x00000006
	TxGlobalFreeze     TransactionFlag = 0x00000007
	TxRequireDestTag   TransactionFlag = 0x00010000
	TxOptionalDestTag  TransactionFlag = 0x00020000
	TxRequireAuth      TransactionFlag = 0x00040000
	TxDisallowXRP      TransactionFlag = 0x00100000
	TxAllowXRP         TransactionFlag = 0x00200000

	// OfferCreate flags
	TxPassive           TransactionFlag = 0x00010000
	TxImmediateOrCancel TransactionFlag = 0x00020000
	TxFillOrKill        TransactionFlag = 0x00040000
	TxSell              TransactionFlag = 0x00080000

	// TrustSet flags
	TxSetAuth       TransactionFlag = 0x00010000
	TxSetNoRipple   TransactionFlag = 0x00020000
	TxClearNoRipple TransactionFlag = 0x00040000
	TxSetFreeze     TransactionFlag = 0x00100000
	TxClearFreeze   TransactionFlag = 0x00200000
)

// Ledger entry flags
const (
	// AccountRoot flags
	LsPasswordSpent  LedgerEntryFlag = 0x00010000
	LsRequireDestTag LedgerEntryFlag = 0x00020000
	LsRequireAuth    LedgerEntryFlag = 0x00040000
	LsDisallowXRP    LedgerEntryFlag = 0x00080000
	LsDisableMaster  LedgerEntryFlag = 0x00100000
	LsNoFreeze       LedgerEntryFlag = 0x00200000

	// Offer flags
	LsPassive LedgerEntryFlag = 0x00010000
	LsSell    LedgerEntryFlag = 0x00020000

	// RippleState flags
	LsLowReserve   LedgerEntryFlag = 0x00010000
	LsHighReserve  LedgerEntryFlag = 0x00020000
	LsLowAuth      LedgerEntryFlag = 0x00040000
	LsHighAuth     LedgerEntryFlag = 0x00080000
	LsLowNoRipple  LedgerEntryFlag = 0x00100000
	LsHighNoRipple LedgerEntryFlag = 0x00200000
	LsLowFreeze    LedgerEntryFlag = 0x00400000
	LsHighFreeze   LedgerEntryFlag = 0x00800000
)
<commit_msg>Add flag explainers<commit_after>package data

import (
	"fmt"
)

type TransactionFlag uint32
type LedgerEntryFlag uint32

// Transaction Flags
const (
	//Universal flags
	TxCanonicalSignature TransactionFlag = 0x80000000

	// Payment flags
	TxNoDirectRipple TransactionFlag = 0x00010000
	TxPartialPayment TransactionFlag = 0x00020000
	TxLimitQuality   TransactionFlag = 0x00040000
	TxCircle         TransactionFlag = 0x00080000 // Not implemented

	// AccountSet flags
	TxSetRequireDest   TransactionFlag = 0x00000001
	TxSetRequireAuth   TransactionFlag = 0x00000002
	TxSetDisallowXRP   TransactionFlag = 0x00000003
	TxSetDisableMaster TransactionFlag = 0x00000004
	TxNoFreeze         TransactionFlag = 0x00000006
	TxGlobalFreeze     TransactionFlag = 0x00000007
	TxRequireDestTag   TransactionFlag = 0x00010000
	TxOptionalDestTag  TransactionFlag = 0x00020000
	TxRequireAuth      TransactionFlag = 0x00040000
	TxDisallowXRP      TransactionFlag = 0x00100000
	TxAllowXRP         TransactionFlag = 0x00200000

	// OfferCreate flags
	TxPassive           TransactionFlag = 0x00010000
	TxImmediateOrCancel TransactionFlag = 0x00020000
	TxFillOrKill        TransactionFlag = 0x00040000
	TxSell              TransactionFlag = 0x00080000

	// TrustSet flags
	TxSetAuth       TransactionFlag = 0x00010000
	TxSetNoRipple   TransactionFlag = 0x00020000
	TxClearNoRipple TransactionFlag = 0x00040000
	TxSetFreeze     TransactionFlag = 0x00100000
	TxClearFreeze   TransactionFlag = 0x00200000
)

// Ledger entry flags
const (
	// AccountRoot flags
	LsPasswordSpent  LedgerEntryFlag = 0x00010000
	LsRequireDestTag LedgerEntryFlag = 0x00020000
	LsRequireAuth    LedgerEntryFlag = 0x00040000
	LsDisallowXRP    LedgerEntryFlag = 0x00080000
	LsDisableMaster  LedgerEntryFlag = 0x00100000
	LsNoFreeze       LedgerEntryFlag = 0x00200000

	// Offer flags
	LsPassive LedgerEntryFlag = 0x00010000
	LsSell    LedgerEntryFlag = 0x00020000

	// RippleState flags
	LsLowReserve   LedgerEntryFlag = 0x00010000
	LsHighReserve  LedgerEntryFlag = 0x00020000
	LsLowAuth      LedgerEntryFlag = 0x00040000
	LsHighAuth     LedgerEntryFlag = 0x00080000
	LsLowNoRipple  LedgerEntryFlag = 0x00100000
	LsHighNoRipple LedgerEntryFlag = 0x00200000
	LsLowFreeze    LedgerEntryFlag = 0x00400000
	LsHighFreeze   LedgerEntryFlag = 0x00800000
)

var txFlagNames = map[TransactionType][]struct {
	Flag TransactionFlag
	Name string
}{
	PAYMENT: {
		{TxNoDirectRipple, "NoDirectRipple"},
		{TxPartialPayment, "PartialPayment"},
		{TxLimitQuality, "LimitQuality"},
		{TxCircle, "Circle"},
	},
	ACCOUNT_SET: {
		{TxSetRequireDest, "SetRequireDest"},
		{TxSetRequireAuth, "SetRequireAuth"},
		{TxSetDisallowXRP, "SetDisallowXRP"},
		{TxSetDisableMaster, "SetDisableMaster"},
		{TxNoFreeze, "NoFreeze"},
		{TxGlobalFreeze, "GlobalFreeze"},
		{TxRequireDestTag, "RequireDestTag"},
		{TxOptionalDestTag, "OptionalDestTag"},
		{TxRequireAuth, "RequireAuth"},
		{TxDisallowXRP, "DisallowXRP"},
		{TxAllowXRP, "AllowXRP"},
	},
	OFFER_CREATE: {
		{TxPassive, "Passive"},
		{TxImmediateOrCancel, "ImmediateOrCancel"},
		{TxFillOrKill, "FillOrKill"},
		{TxSell, "Sell"},
	},
	TRUST_SET: {
		{TxSetAuth, "SetAuth"},
		{TxSetNoRipple, "SetNoRipple"},
		{TxClearNoRipple, "ClearNoRipple"},
		{TxSetFreeze, "SetFreeze"},
		{TxClearFreeze, "ClearFreeze"},
	},
}

var leFlagNames = map[LedgerEntryType][]struct {
	Flag LedgerEntryFlag
	Name string
}{
	ACCOUNT_ROOT: {
		{LsPasswordSpent, "PasswordSpent"},
		{LsRequireDestTag, "RequireDestTag"},
		{LsRequireAuth, "RequireAuth"},
		{LsDisallowXRP, "DisallowXRP"},
		{LsDisableMaster, "DisableMaster"},
		{LsNoFreeze, "NoFreeze"},
	},
	OFFER: {
		{LsPassive, "Passive"},
		{LsSell, "Sell"},
	},
	RIPPLE_STATE: {
		{LsLowReserve, "LowReserve"},
		{LsHighReserve, "HighReserve"},
		{LsLowAuth, "LowAuth"},
		{LsHighAuth, "HighAuth"},
		{LsLowNoRipple, "LowNoRipple"},
		{LsHighNoRipple, "HighNoRipple"},
		{LsLowFreeze, "LowFreeze"},
		{LsHighFreeze, "HighFreeze"},
	},
}

func (f TransactionFlag) String() string {
	return fmt.Sprintf("%08X", uint32(f))
}

func (f LedgerEntryFlag) String() string {
	return fmt.Sprintf("%08X", uint32(f))
}

func (f TransactionFlag) Explain(tx Transaction) []string {
	var flags []string
	if f&TxCanonicalSignature > 0 {
		flags = append(flags, "CanonicalSignature")
	}
	for _, n := range txFlagNames[tx.GetTransactionType()] {
		if f&n.Flag > 0 {
			flags = append(flags, n.Name)
		}
	}
	return flags
}

func (f LedgerEntryFlag) Explain(le LedgerEntry) []string {
	var flags []string
	for _, n := range leFlagNames[le.GetLedgerEntryType()] {
		if f&n.Flag > 0 {
			flags = append(flags, n.Name)
		}
	}
	return flags
}
<|endoftext|>


<commit_before>package arn

const (
	// SortByAiringDate sorts your watching list by airing date.
	SortByAiringDate = "airing date"

	// SortByTitle sorts your watching list alphabetically.
	SortByTitle = "title"

	// SortByRating sorts your watching list by rating.
	SortByRating = "rating"
)

const (
	// TitleLanguageCanonical ...
	TitleLanguageCanonical = "canonical"

	// TitleLanguageRomaji ...
	TitleLanguageRomaji = "romaji"

	// TitleLanguageEnglish ...
	TitleLanguageEnglish = "english"

	// TitleLanguageJapanese ...
	TitleLanguageJapanese = "japanese"
)

// Settings ...
type Settings struct {
	UserID        string               `json:"userId"`
	SortBy        string               `json:"sortBy"`
	TitleLanguage string               `json:"titleLanguage" editable:"true"`
	Providers     ServiceProviders     `json:"providers"`
	Avatar        AvatarSettings       `json:"avatar"`
	Format        FormatSettings       `json:"format"`
	Notification  NotificationSettings `json:"notification"`
	Theme         string               `json:"theme" editable:"true"`
}

// NotificationSettings ...
type NotificationSettings struct {
	Email                string `json:"email"`
	NewFollowers         bool   `json:"newFollowers" editable:"true"`
	AnimeEpisodeReleases bool   `json:"animeEpisodeReleases" editable:"true"`
	AnimeFinished        bool   `json:"animeFinished" editable:"true"`
	ForumLikes           bool   `json:"forumLikes" editable:"true"`
	GroupPostLikes       bool   `json:"groupPostLikes" editable:"true"`
	QuoteLikes           bool   `json:"quoteLikes" editable:"true"`
	SoundTrackLikes      bool   `json:"soundTrackLikes" editable:"true"`
}

// FormatSettings ...
type FormatSettings struct {
	RatingsPrecision int `json:"ratingsPrecision" editable:"true"`
}

// ServiceProviders ...
type ServiceProviders struct {
	Anime string `json:"anime"`
}

// AvatarSettings ...
type AvatarSettings struct {
	Source    string `json:"source" editable:"true"`
	SourceURL string `json:"sourceUrl" editable:"true"`
}

// NewSettings ...
func NewSettings(user *User) *Settings {
	return &Settings{
		UserID:        user.ID,
		SortBy:        SortByAiringDate,
		TitleLanguage: TitleLanguageCanonical,
		Providers: ServiceProviders{
			Anime: "",
		},
		Avatar: AvatarSettings{
			Source:    "",
			SourceURL: "",
		},
		Theme:        "light",
		Notification: DefaultNotificationSettings(),
	}
}

// DefaultNotificationSettings returns the default notification settings.
func DefaultNotificationSettings() NotificationSettings {
	return NotificationSettings{
		Email:                "",
		NewFollowers:         true,
		AnimeEpisodeReleases: true,
		AnimeFinished:        false,
		ForumLikes:           true,
		GroupPostLikes:       true,
		QuoteLikes:           true,
		SoundTrackLikes:      true,
	}
}

// GetSettings ...
func GetSettings(userID string) (*Settings, error) {
	obj, err := DB.Get("Settings", userID)

	if err != nil {
		return nil, err
	}

	return obj.(*Settings), nil
}

// User returns the user object for the settings.
func (settings *Settings) User() *User {
	user, _ := GetUser(settings.UserID)
	return user
}
<commit_msg>Added editor filter settings<commit_after>package arn

import "fmt"

const (
	// SortByAiringDate sorts your watching list by airing date.
	SortByAiringDate = "airing date"

	// SortByTitle sorts your watching list alphabetically.
	SortByTitle = "title"

	// SortByRating sorts your watching list by rating.
	SortByRating = "rating"
)

const (
	// TitleLanguageCanonical ...
	TitleLanguageCanonical = "canonical"

	// TitleLanguageRomaji ...
	TitleLanguageRomaji = "romaji"

	// TitleLanguageEnglish ...
	TitleLanguageEnglish = "english"

	// TitleLanguageJapanese ...
	TitleLanguageJapanese = "japanese"
)

// Settings ...
type Settings struct {
	UserID        string               `json:"userId"`
	SortBy        string               `json:"sortBy"`
	TitleLanguage string               `json:"titleLanguage" editable:"true"`
	Providers     ServiceProviders     `json:"providers"`
	Avatar        AvatarSettings       `json:"avatar"`
	Format        FormatSettings       `json:"format"`
	Notification  NotificationSettings `json:"notification"`
	Editor        EditorSettings       `json:"editor"`
	Theme         string               `json:"theme" editable:"true"`
}

// NotificationSettings ...
type NotificationSettings struct {
	Email                string `json:"email"`
	NewFollowers         bool   `json:"newFollowers" editable:"true"`
	AnimeEpisodeReleases bool   `json:"animeEpisodeReleases" editable:"true"`
	AnimeFinished        bool   `json:"animeFinished" editable:"true"`
	ForumLikes           bool   `json:"forumLikes" editable:"true"`
	GroupPostLikes       bool   `json:"groupPostLikes" editable:"true"`
	QuoteLikes           bool   `json:"quoteLikes" editable:"true"`
	SoundTrackLikes      bool   `json:"soundTrackLikes" editable:"true"`
}

// EditorSettings ...
type EditorSettings struct {
	Filter EditorFilterSettings `json:"filter"`
}

// EditorFilterSettings ...
type EditorFilterSettings struct {
	Year   string `json:"year" editable:"true"`
	Status string `json:"status" editable:"true"`
	Type   string `json:"type" editable:"true"`
}

// Suffix returns the URL suffix.
func (filter *EditorFilterSettings) Suffix() string {
	year := filter.Year
	status := filter.Status
	typ := filter.Type

	if year == "" {
		year = "any"
	}

	if status == "" {
		status = "any"
	}

	if typ == "" {
		typ = "any"
	}

	return fmt.Sprintf("/%s/%s/%s", year, status, typ)
}

// FormatSettings ...
type FormatSettings struct {
	RatingsPrecision int `json:"ratingsPrecision" editable:"true"`
}

// ServiceProviders ...
type ServiceProviders struct {
	Anime string `json:"anime"`
}

// AvatarSettings ...
type AvatarSettings struct {
	Source    string `json:"source" editable:"true"`
	SourceURL string `json:"sourceUrl" editable:"true"`
}

// NewSettings ...
func NewSettings(user *User) *Settings {
	return &Settings{
		UserID:        user.ID,
		SortBy:        SortByAiringDate,
		TitleLanguage: TitleLanguageCanonical,
		Providers: ServiceProviders{
			Anime: "",
		},
		Avatar: AvatarSettings{
			Source:    "",
			SourceURL: "",
		},
		Notification: DefaultNotificationSettings(),
		Theme:        "light",
	}
}

// DefaultNotificationSettings returns the default notification settings.
func DefaultNotificationSettings() NotificationSettings {
	return NotificationSettings{
		Email:                "",
		NewFollowers:         true,
		AnimeEpisodeReleases: true,
		AnimeFinished:        false,
		ForumLikes:           true,
		GroupPostLikes:       true,
		QuoteLikes:           true,
		SoundTrackLikes:      true,
	}
}

// GetSettings ...
func GetSettings(userID string) (*Settings, error) {
	obj, err := DB.Get("Settings", userID)

	if err != nil {
		return nil, err
	}

	return obj.(*Settings), nil
}

// User returns the user object for the settings.
func (settings *Settings) User() *User {
	user, _ := GetUser(settings.UserID)
	return user
}
<|endoftext|>


<commit_before>package gateway

import (
	"errors"
	"sync"
	"sync/atomic"

	"github.com/NebulousLabs/Sia/encoding"
	"github.com/NebulousLabs/Sia/modules"
)

type rpcID [8]byte

func (id rpcID) String() string {
	for i := range id {
		if id[i] == 0 {
			id[i] = ' '
		}
	}
	return string(id[:])
}

// handlerName truncates a string to 8 bytes. If len(name) < 8, the remaining
// bytes are 0. A handlerName is specified at the beginning of each network
// call, indicating which function should handle the connection.
func handlerName(name string) (id rpcID) {
	copy(id[:], name)
	return
}

// RPC calls an RPC on the given address. RPC cannot be called on an address
// that the Gateway is not connected to.
func (g *Gateway) RPC(addr modules.NetAddress, name string, fn modules.RPCFunc) error {
	g.log.Printf("INFO: calling RPC \"%v\" on %v", name, addr)
	id := g.mu.RLock()
	peer, ok := g.peers[addr]
	g.mu.RUnlock(id)
	if !ok {
		return errors.New("can't call RPC on unconnected peer " + string(addr))
	}

	conn, err := peer.open()
	if err != nil {
		return err
	}
	defer conn.Close()

	// write header
	if err := encoding.WriteObject(conn, handlerName(name)); err != nil {
		return err
	}
	// call fn
	err = fn(conn)
	if err != nil {
		// TODO: log error here?
		// give peer a strike
		atomic.AddUint32(&peer.strikes, 1)
	}
	return err
}

// RegisterRPC registers a function as an RPC handler for a given identifier.
// To call an RPC, use gateway.RPC, supplying the same identifier given to
// RegisterRPC. Identifiers should always use PascalCase.
func (g *Gateway) RegisterRPC(name string, fn modules.RPCFunc) {
	id := g.mu.Lock()
	defer g.mu.Unlock(id)
	g.handlerMap[handlerName(name)] = fn
}

// listenPeer listens for new streams on a peer connection and serves them via
// threadedHandleConn.
func (g *Gateway) listenPeer(p *peer) {
	for {
		conn, err := p.accept()
		if err != nil {
			g.log.Println("WARN: lost connection to peer", p.addr)
			break
		}

		// it is the handler's responsibility to close the connection
		go g.threadedHandleConn(conn)
	}
	g.Disconnect(p.addr)
}

// threadedHandleConn reads header data from a connection, then routes it to the
// appropriate handler for further processing.
func (g *Gateway) threadedHandleConn(conn modules.PeerConn) {
	defer conn.Close()
	var id rpcID
	if err := encoding.ReadObject(conn, &id, 8); err != nil {
		g.log.Printf("WARN: could not read RPC identifier from incoming conn %v: %v", conn.CallbackAddr(), err)
		return
	}
	// call registered handler for this ID
	lockid := g.mu.RLock()
	fn, ok := g.handlerMap[id]
	g.mu.RUnlock(lockid)
	if !ok {
		// TODO: write this error to conn?
		g.log.Printf("WARN: incoming conn %v requested unknown RPC \"%v\"", conn.CallbackAddr(), id)
		return
	}

	g.log.Printf("INFO: handling RPC \"%v\" from %v", id, conn.CallbackAddr())
	if err := fn(conn); err != nil {
		g.log.Printf("WARN: incoming RPC \"%v\" failed: %v", id, err)
	}
}

// Broadcast calls an RPC on all of the peers in the Gateway's peer list. The
// calls are run in parallel. Broadcasts are restricted to "one-way" RPCs,
// which simply write an object and disconnect. This is why Broadcast takes an
// interface{} instead of an RPCFunc.
func (g *Gateway) Broadcast(name string, obj interface{}) {
	peers := g.Peers()
	g.log.Printf("INFO: broadcasting RPC \"%v\" to %v peers", name, len(peers))

	// only encode obj once, instead of using WriteObject
	enc := encoding.Marshal(obj)
	fn := func(conn modules.PeerConn) error {
		return encoding.WritePrefix(conn, enc)
	}

	var wg sync.WaitGroup
	wg.Add(len(peers))
	for _, addr := range peers {
		go func(addr modules.NetAddress) {
			err := g.RPC(addr, name, fn)
			if err != nil {
				g.log.Printf("WARN: broadcast: calling RPC \"%v\" on peer %v returned error: %v", name, addr, err)
			}
			wg.Done()
		}(addr)
	}
	wg.Wait()
}
<commit_msg>retry broadcast after 10s<commit_after>package gateway

import (
	"errors"
	"sync"
	"sync/atomic"
	"time"

	"github.com/NebulousLabs/Sia/encoding"
	"github.com/NebulousLabs/Sia/modules"
)

type rpcID [8]byte

func (id rpcID) String() string {
	for i := range id {
		if id[i] == 0 {
			id[i] = ' '
		}
	}
	return string(id[:])
}

// handlerName truncates a string to 8 bytes. If len(name) < 8, the remaining
// bytes are 0. A handlerName is specified at the beginning of each network
// call, indicating which function should handle the connection.
func handlerName(name string) (id rpcID) {
	copy(id[:], name)
	return
}

// RPC calls an RPC on the given address. RPC cannot be called on an address
// that the Gateway is not connected to.
func (g *Gateway) RPC(addr modules.NetAddress, name string, fn modules.RPCFunc) error {
	g.log.Printf("INFO: calling RPC \"%v\" on %v", name, addr)
	id := g.mu.RLock()
	peer, ok := g.peers[addr]
	g.mu.RUnlock(id)
	if !ok {
		return errors.New("can't call RPC on unconnected peer " + string(addr))
	}

	conn, err := peer.open()
	if err != nil {
		return err
	}
	defer conn.Close()

	// write header
	if err := encoding.WriteObject(conn, handlerName(name)); err != nil {
		return err
	}
	// call fn
	err = fn(conn)
	if err != nil {
		// TODO: log error here?
		// give peer a strike
		atomic.AddUint32(&peer.strikes, 1)
	}
	return err
}

// RegisterRPC registers a function as an RPC handler for a given identifier.
// To call an RPC, use gateway.RPC, supplying the same identifier given to
// RegisterRPC. Identifiers should always use PascalCase.
func (g *Gateway) RegisterRPC(name string, fn modules.RPCFunc) {
	id := g.mu.Lock()
	defer g.mu.Unlock(id)
	g.handlerMap[handlerName(name)] = fn
}

// listenPeer listens for new streams on a peer connection and serves them via
// threadedHandleConn.
func (g *Gateway) listenPeer(p *peer) {
	for {
		conn, err := p.accept()
		if err != nil {
			g.log.Println("WARN: lost connection to peer", p.addr)
			break
		}

		// it is the handler's responsibility to close the connection
		go g.threadedHandleConn(conn)
	}
	g.Disconnect(p.addr)
}

// threadedHandleConn reads header data from a connection, then routes it to the
// appropriate handler for further processing.
func (g *Gateway) threadedHandleConn(conn modules.PeerConn) {
	defer conn.Close()
	var id rpcID
	if err := encoding.ReadObject(conn, &id, 8); err != nil {
		g.log.Printf("WARN: could not read RPC identifier from incoming conn %v: %v", conn.CallbackAddr(), err)
		return
	}
	// call registered handler for this ID
	lockid := g.mu.RLock()
	fn, ok := g.handlerMap[id]
	g.mu.RUnlock(lockid)
	if !ok {
		// TODO: write this error to conn?
		g.log.Printf("WARN: incoming conn %v requested unknown RPC \"%v\"", conn.CallbackAddr(), id)
		return
	}

	g.log.Printf("INFO: handling RPC \"%v\" from %v", id, conn.CallbackAddr())
	if err := fn(conn); err != nil {
		g.log.Printf("WARN: incoming RPC \"%v\" failed: %v", id, err)
	}
}

// Broadcast calls an RPC on all of the peers in the Gateway's peer list. The
// calls are run in parallel. Broadcasts are restricted to "one-way" RPCs,
// which simply write an object and disconnect. This is why Broadcast takes an
// interface{} instead of an RPCFunc.
func (g *Gateway) Broadcast(name string, obj interface{}) {
	peers := g.Peers()
	g.log.Printf("INFO: broadcasting RPC \"%v\" to %v peers", name, len(peers))

	// only encode obj once, instead of using WriteObject
	enc := encoding.Marshal(obj)
	fn := func(conn modules.PeerConn) error {
		return encoding.WritePrefix(conn, enc)
	}

	var wg sync.WaitGroup
	wg.Add(len(peers))
	for _, addr := range peers {
		go func(addr modules.NetAddress) {
			err := g.RPC(addr, name, fn)
			if err != nil {
				g.log.Printf("WARN: broadcast: calling RPC \"%v\" on peer %v returned error: %v", name, addr, err)
				// try one more time before giving up
				time.Sleep(10 * time.Second)
				g.RPC(addr, name, fn)
			}
			wg.Done()
		}(addr)
	}
	wg.Wait()
}
<|endoftext|>


<commit_before>package gateway

import (
	"errors"
	"sync"
	"time"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/encoding"
	"github.com/NebulousLabs/Sia/modules"
)

type rpcID [8]byte

func (id rpcID) String() string {
	for i := range id {
		if id[i] == 0 {
			id[i] = ' '
		}
	}
	return string(id[:])
}

// handlerName truncates a string to 8 bytes. If len(name) < 8, the remaining
// bytes are 0. A handlerName is specified at the beginning of each network
// call, indicating which function should handle the connection.
func handlerName(name string) (id rpcID) {
	copy(id[:], name)
	return
}

// RPC calls an RPC on the given address. RPC cannot be called on an address
// that the Gateway is not connected to.
func (g *Gateway) RPC(addr modules.NetAddress, name string, fn modules.RPCFunc) error {
	if err := g.threads.Add(); err != nil {
		return err
	}
	defer g.threads.Done()

	g.mu.RLock()
	peer, ok := g.peers[addr]
	g.mu.RUnlock()
	if !ok {
		return errors.New("can't call RPC on unconnected peer " + string(addr))
	}

	conn, err := peer.open()
	if err != nil {
		return err
	}
	defer conn.Close()

	// write header
	if err := encoding.WriteObject(conn, handlerName(name)); err != nil {
		return err
	}
	// call fn
	return fn(conn)
}

// RegisterRPC registers an RPCFunc as a handler for a given identifier. To
// call an RPC, use gateway.RPC, supplying the same identifier given to
// RegisterRPC. Identifiers should always use PascalCase. The first 8
// characters of an identifier should be unique, as the identifier used
// internally is truncated to 8 bytes.
func (g *Gateway) RegisterRPC(name string, fn modules.RPCFunc) {
	g.mu.Lock()
	defer g.mu.Unlock()
	if _, ok := g.handlers[handlerName(name)]; ok {
		build.Critical("RPC already registered: " + name)
	}
	g.handlers[handlerName(name)] = fn
}

// UnregisterRPC unregisters an RPC and removes the corresponding RPCFunc from
// g.handlers. Future calls to the RPC by peers will fail.
func (g *Gateway) UnregisterRPC(name string) {
	g.mu.Lock()
	defer g.mu.Unlock()
	if _, ok := g.handlers[handlerName(name)]; !ok {
		build.Critical("RPC not registered: " + name)
	}
	delete(g.handlers, handlerName(name))
}

// RegisterConnectCall registers a name and RPCFunc to be called on a peer
// upon connecting.
func (g *Gateway) RegisterConnectCall(name string, fn modules.RPCFunc) {
	g.mu.Lock()
	defer g.mu.Unlock()
	if _, ok := g.initRPCs[name]; ok {
		build.Critical("ConnectCall already registered: " + name)
	}
	g.initRPCs[name] = fn
}

// UnregisterConnectCall unregisters an on-connect call and removes the
// corresponding RPCFunc from g.initRPCs. Future connections to peers will not
// trigger the RPC to be called on them.
func (g *Gateway) UnregisterConnectCall(name string) {
	g.mu.Lock()
	defer g.mu.Unlock()
	if _, ok := g.initRPCs[name]; !ok {
		build.Critical("ConnectCall not registered: " + name)
	}
	delete(g.initRPCs, name)
}

// threadedListenPeer listens for new streams on a peer connection and serves them via
// threadedHandleConn.
func (g *Gateway) threadedListenPeer(p *peer) {
	if g.threads.Add() != nil {
		return
	}
	defer g.threads.Done()

	// Disconnect from the peer when the stop signal is received.
	if g.threads.Add() != nil {
		return
	}
	go func() {
		defer g.threads.Done()
		<-g.threads.StopChan()
		g.mu.Lock()
		delete(g.peers, p.NetAddress)
		g.mu.Unlock()
		if err := p.sess.Close(); err != nil {
			g.log.Printf("WARN: error disconnecting from peer %q: %v", p.NetAddress, err)
		}
	}()

	for {
		conn, err := p.accept()
		if err != nil {
			g.log.Println("WARN: lost connection to peer", p.NetAddress)
			break
		}

		// it is the handler's responsibility to close the connection
		go g.threadedHandleConn(conn)
	}
	g.Disconnect(p.NetAddress)
}

// threadedHandleConn reads header data from a connection, then routes it to the
// appropriate handler for further processing.
func (g *Gateway) threadedHandleConn(conn modules.PeerConn) {
	defer conn.Close()

	if g.threads.Add() != nil {
		return
	}
	defer g.threads.Done()

	var id rpcID
	if err := encoding.ReadObject(conn, &id, 8); err != nil {
		return
	}
	// call registered handler for this ID
	g.mu.RLock()
	fn, ok := g.handlers[id]
	g.mu.RUnlock()
	if !ok {
		g.log.Printf("WARN: incoming conn %v requested unknown RPC \"%v\"", conn.RemoteAddr(), id)
		return
	}
	if build.DEBUG {
		g.log.Printf("INFO: incoming conn %v requested RPC \"%v\"", conn.RemoteAddr(), id)
	}

	// call fn
	err := fn(conn)
	// don't log benign errors
	if err == modules.ErrDuplicateTransactionSet || err == modules.ErrBlockKnown {
		err = nil
	}
	if err != nil {
		g.log.Printf("WARN: incoming RPC \"%v\" from conn %v failed: %v", id, conn.RemoteAddr(), err)
	}
}

// Broadcast calls an RPC on all of the specified peers. The calls are run in
// parallel. Broadcasts are restricted to "one-way" RPCs, which simply write an
// object and disconnect. This is why Broadcast takes an interface{} instead of
// an RPCFunc.
func (g *Gateway) Broadcast(name string, obj interface{}, peers []modules.Peer) {
	if g.threads.Add() != nil {
		return
	}
	defer g.threads.Done()

	g.log.Printf("INFO: broadcasting RPC \"%v\" to %v peers", name, len(peers))

	// only encode obj once, instead of using WriteObject
	enc := encoding.Marshal(obj)
	fn := func(conn modules.PeerConn) error {
		return encoding.WritePrefix(conn, enc)
	}

	var wg sync.WaitGroup
	wg.Add(len(peers))
	for _, p := range peers {
		go func(addr modules.NetAddress) {
			err := g.RPC(addr, name, fn)
			if err != nil {
				g.log.Debugf("WARN: broadcasting RPC %q to peer %q failed (attempting again in 10 seconds): %v", name, addr, err)
				// try one more time before giving up
				select {
				case <-time.After(10 * time.Second):
				case <-g.threads.StopChan():
					return
				}
				err := g.RPC(addr, name, fn)
				if err != nil {
					g.log.Debugf("WARN: broadcasting RPC %q to peer %q failed twice: %v", name, addr, err)
				}
			}
			wg.Done()
		}(p.NetAddress)
	}
	wg.Wait()
}
<commit_msg>Fix WaitGroup deadlock<commit_after>package gateway

import (
	"errors"
	"sync"
	"time"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/encoding"
	"github.com/NebulousLabs/Sia/modules"
)

type rpcID [8]byte

func (id rpcID) String() string {
	for i := range id {
		if id[i] == 0 {
			id[i] = ' '
		}
	}
	return string(id[:])
}

// handlerName truncates a string to 8 bytes. If len(name) < 8, the remaining
// bytes are 0. A handlerName is specified at the beginning of each network
// call, indicating which function should handle the connection.
func handlerName(name string) (id rpcID) {
	copy(id[:], name)
	return
}

// RPC calls an RPC on the given address. RPC cannot be called on an address
// that the Gateway is not connected to.
func (g *Gateway) RPC(addr modules.NetAddress, name string, fn modules.RPCFunc) error {
	if err := g.threads.Add(); err != nil {
		return err
	}
	defer g.threads.Done()

	g.mu.RLock()
	peer, ok := g.peers[addr]
	g.mu.RUnlock()
	if !ok {
		return errors.New("can't call RPC on unconnected peer " + string(addr))
	}

	conn, err := peer.open()
	if err != nil {
		return err
	}
	defer conn.Close()

	// write header
	if err := encoding.WriteObject(conn, handlerName(name)); err != nil {
		return err
	}
	// call fn
	return fn(conn)
}

// RegisterRPC registers an RPCFunc as a handler for a given identifier. To
// call an RPC, use gateway.RPC, supplying the same identifier given to
// RegisterRPC. Identifiers should always use PascalCase. The first 8
// characters of an identifier should be unique, as the identifier used
// internally is truncated to 8 bytes.
func (g *Gateway) RegisterRPC(name string, fn modules.RPCFunc) {
	g.mu.Lock()
	defer g.mu.Unlock()
	if _, ok := g.handlers[handlerName(name)]; ok {
		build.Critical("RPC already registered: " + name)
	}
	g.handlers[handlerName(name)] = fn
}

// UnregisterRPC unregisters an RPC and removes the corresponding RPCFunc from
// g.handlers. Future calls to the RPC by peers will fail.
func (g *Gateway) UnregisterRPC(name string) {
	g.mu.Lock()
	defer g.mu.Unlock()
	if _, ok := g.handlers[handlerName(name)]; !ok {
		build.Critical("RPC not registered: " + name)
	}
	delete(g.handlers, handlerName(name))
}

// RegisterConnectCall registers a name and RPCFunc to be called on a peer
// upon connecting.
func (g *Gateway) RegisterConnectCall(name string, fn modules.RPCFunc) {
	g.mu.Lock()
	defer g.mu.Unlock()
	if _, ok := g.initRPCs[name]; ok {
		build.Critical("ConnectCall already registered: " + name)
	}
	g.initRPCs[name] = fn
}

// UnregisterConnectCall unregisters an on-connect call and removes the
// corresponding RPCFunc from g.initRPCs. Future connections to peers will not
// trigger the RPC to be called on them.
func (g *Gateway) UnregisterConnectCall(name string) {
	g.mu.Lock()
	defer g.mu.Unlock()
	if _, ok := g.initRPCs[name]; !ok {
		build.Critical("ConnectCall not registered: " + name)
	}
	delete(g.initRPCs, name)
}

// threadedListenPeer listens for new streams on a peer connection and serves them via
// threadedHandleConn.
func (g *Gateway) threadedListenPeer(p *peer) {
	if g.threads.Add() != nil {
		return
	}
	defer g.threads.Done()

	// Disconnect from the peer when the stop signal is received.
	if g.threads.Add() != nil {
		return
	}
	go func() {
		defer g.threads.Done()
		<-g.threads.StopChan()
		g.mu.Lock()
		delete(g.peers, p.NetAddress)
		g.mu.Unlock()
		if err := p.sess.Close(); err != nil {
			g.log.Printf("WARN: error disconnecting from peer %q: %v", p.NetAddress, err)
		}
	}()

	for {
		conn, err := p.accept()
		if err != nil {
			g.log.Println("WARN: lost connection to peer", p.NetAddress)
			break
		}

		// it is the handler's responsibility to close the connection
		go g.threadedHandleConn(conn)
	}
	g.Disconnect(p.NetAddress)
}

// threadedHandleConn reads header data from a connection, then routes it to the
// appropriate handler for further processing.
func (g *Gateway) threadedHandleConn(conn modules.PeerConn) {
	defer conn.Close()

	if g.threads.Add() != nil {
		return
	}
	defer g.threads.Done()

	var id rpcID
	if err := encoding.ReadObject(conn, &id, 8); err != nil {
		return
	}
	// call registered handler for this ID
	g.mu.RLock()
	fn, ok := g.handlers[id]
	g.mu.RUnlock()
	if !ok {
		g.log.Printf("WARN: incoming conn %v requested unknown RPC \"%v\"", conn.RemoteAddr(), id)
		return
	}
	if build.DEBUG {
		g.log.Printf("INFO: incoming conn %v requested RPC \"%v\"", conn.RemoteAddr(), id)
	}

	// call fn
	err := fn(conn)
	// don't log benign errors
	if err == modules.ErrDuplicateTransactionSet || err == modules.ErrBlockKnown {
		err = nil
	}
	if err != nil {
		g.log.Printf("WARN: incoming RPC \"%v\" from conn %v failed: %v", id, conn.RemoteAddr(), err)
	}
}

// Broadcast calls an RPC on all of the specified peers. The calls are run in
// parallel. Broadcasts are restricted to "one-way" RPCs, which simply write an
// object and disconnect. This is why Broadcast takes an interface{} instead of
// an RPCFunc.
func (g *Gateway) Broadcast(name string, obj interface{}, peers []modules.Peer) {
	if g.threads.Add() != nil {
		return
	}
	defer g.threads.Done()

	g.log.Printf("INFO: broadcasting RPC \"%v\" to %v peers", name, len(peers))

	// only encode obj once, instead of using WriteObject
	enc := encoding.Marshal(obj)
	fn := func(conn modules.PeerConn) error {
		return encoding.WritePrefix(conn, enc)
	}

	var wg sync.WaitGroup
	for _, p := range peers {
		wg.Add(1)
		go func(addr modules.NetAddress) {
			defer wg.Done()
			err := g.RPC(addr, name, fn)
			if err != nil {
				g.log.Debugf("WARN: broadcasting RPC %q to peer %q failed (attempting again in 10 seconds): %v", name, addr, err)
				// try one more time before giving up
				select {
				case <-time.After(10 * time.Second):
				case <-g.threads.StopChan():
					return
				}
				err := g.RPC(addr, name, fn)
				if err != nil {
					g.log.Debugf("WARN: broadcasting RPC %q to peer %q failed twice: %v", name, addr, err)
				}
			}
		}(p.NetAddress)
	}
	wg.Wait()
}
<|endoftext|>


<commit_before>// Copyright 2015 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 dl

// TODO(adg): refactor this to use the tools/godoc/static template.

const templateHTML = `
{{define "root"}}
<!DOCTYPE html>

<meta charset="utf-8">
<meta name="description" content="Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#00ADD8">
<title>Downloads - The Go Programming Language</title>
<link href="https://fonts.googleapis.com/css?family=Work+Sans:600|Roboto:400,700" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Product+Sans&text=Supported%20by%20Google&display=swap" rel="stylesheet">
<link type="text/css" rel="stylesheet" href="/lib/godoc/style.css">

    <button class="Header-menuButton js-headerMenuButton" aria-label="Main menu" aria-expanded="false">

    </button>

Documents

Packages

The Project

Help

      {{if not .GoogleCN}}

Blog

Play

      {{end}}

        <form class="HeaderSearch" role="search" action="/search">
          <input class="HeaderSearch-input"
                type="search"
                name="q"
                placeholder="Search"
                aria-label="Search"
                autocapitalize="off"
                autocomplete="off"
                autocorrect="off"
                spellcheck="false"
                required>
          <button class="HeaderSearch-submit">
            <svg class="HeaderSearch-icon" width="24" height="24" viewBox="0 0 24 24"><title>Search</title><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
          </button>
        </form>

Downloads

After downloading a binary release suitable for your system,
please follow the installation instructions.

If you are building from source,
follow the source installation instructions.

See the release history for more
information about Go releases.

  As of Go 1.13, the go command by default downloads and authenticates
  modules using the Go module mirror and Go checksum database run by Google. See
  https://proxy.golang.org/privacy
  for privacy information about these services and the
  go command documentation
  for configuration details including how to disable the use of these servers or use
  different ones.

{{with .Featured}}

Featured downloads

{{range .}}
{{template "download" .}}
{{end}}
{{end}}

{{with .Stable}}

Stable versions

{{template "releases" .}}
{{end}}

{{with .Unstable}}

Unstable version

{{template "releases" .}}
{{end}}

{{with .Archive}}

Archived versions ▹

Archived versions ▾

    {{template "releases" .}}

{{end}}

Copyright

Terms of Service

Privacy Policy

Report a website issue

    Supported by Google

{{end}}

{{define "releases"}}
{{range .}}

{{.Version}} ▹

{{.Version}} ▾

		{{if .Stable}}{{else}}

This is an unstable version of Go. Use with caution.

If you already have Go installed, you can install this version by running:

go get golang.org/dl/{{.Version}}

Then, use the {{.Version}} command instead of the go command to use {{.Version}}.

		{{end}}
		{{template "files" .}}

{{end}}
{{end}}

{{define "files"}}

  File name
  Kind
  OS
  Arch
  Size
  {{/* Use the checksum type of the first file for the column heading. */}}
  {{(index .Files 0).ChecksumType}} Checksum

{{if .SplitPortTable}}
  {{range .Files}}{{if .PrimaryPort}}{{template "file" .}}{{end}}{{end}}

  {{/* TODO(cbro): add a link to an explanatory doc page */}}

Other Ports

  {{range .Files}}{{if not .PrimaryPort}}{{template "file" .}}{{end}}{{end}}
{{else}}
  {{range .Files}}{{template "file" .}}{{end}}
{{end}}

{{end}}

{{define "file"}}

  {{.Filename}}
  {{pretty .Kind}}
  {{.PrettyOS}}
  {{pretty .Arch}}
  {{.PrettySize}}
  <tt>{{.PrettyChecksum}}</tt>

{{end}}

{{define "download"}}

{{.Platform}}

{{with .Requirements}}
{{.}}
{{end}}

  {{.Filename}}
  {{if .Size}}({{.PrettySize}}){{end}}

{{end}}
`
<commit_msg>internal/dl: update tmpl.go to be consistent<commit_after>// Copyright 2015 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 dl

// TODO(adg): refactor this to use the tools/godoc/static template.

const templateHTML = `
{{define "root"}}
<!DOCTYPE html>

<meta charset="utf-8">
<meta name="description" content="Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#00ADD8">
<title>Downloads - The Go Programming Language</title>
<link href="https://fonts.googleapis.com/css?family=Work+Sans:600|Roboto:400,700" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Product+Sans&text=Supported%20by%20Google&display=swap" rel="stylesheet">
<link type="text/css" rel="stylesheet" href="/lib/godoc/style.css">

    Black Lives Matter.
    Support the Equal Justice Initiative.

    <button class="Header-menuButton js-headerMenuButton" aria-label="Main menu" aria-expanded="false">

    </button>

Documents

Packages

The Project

Help

      {{if not .GoogleCN}}

Blog

Play

      {{end}}

        <form class="HeaderSearch" role="search" action="/search">
          <input class="HeaderSearch-input"
                type="search"
                name="q"
                placeholder="Search"
                aria-label="Search"
                autocapitalize="off"
                autocomplete="off"
                autocorrect="off"
                spellcheck="false"
                required>
          <button class="HeaderSearch-submit">
            <svg class="HeaderSearch-icon" width="24" height="24" viewBox="0 0 24 24"><title>Search</title><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/><path d="M0 0h24v24H0z" fill="none"/></svg>
          </button>
        </form>

Downloads

After downloading a binary release suitable for your system,
please follow the installation instructions.

If you are building from source,
follow the source installation instructions.

See the release history for more
information about Go releases.

  As of Go 1.13, the go command by default downloads and authenticates
  modules using the Go module mirror and Go checksum database run by Google. See
  https://proxy.golang.org/privacy
  for privacy information about these services and the
  go command documentation
  for configuration details including how to disable the use of these servers or use
  different ones.

{{with .Featured}}

Featured downloads

{{range .}}
{{template "download" .}}
{{end}}
{{end}}

{{with .Stable}}

Stable versions

{{template "releases" .}}
{{end}}

{{with .Unstable}}

Unstable version

{{template "releases" .}}
{{end}}

{{with .Archive}}

Archived versions ▹

Archived versions ▾

    {{template "releases" .}}

{{end}}

Copyright

Terms of Service

Privacy Policy

Report a website issue

    Supported by Google

{{end}}

{{define "releases"}}
{{range .}}

{{.Version}} ▹

{{.Version}} ▾

		{{if .Stable}}{{else}}

This is an unstable version of Go. Use with caution.

If you already have Go installed, you can install this version by running:

go get golang.org/dl/{{.Version}}

Then, use the {{.Version}} command instead of the go command to use {{.Version}}.

		{{end}}
		{{template "files" .}}

{{end}}
{{end}}

{{define "files"}}

  File name
  Kind
  OS
  Arch
  Size
  {{/* Use the checksum type of the first file for the column heading. */}}
  {{(index .Files 0).ChecksumType}} Checksum

{{if .SplitPortTable}}
  {{range .Files}}{{if .PrimaryPort}}{{template "file" .}}{{end}}{{end}}

  {{/* TODO(cbro): add a link to an explanatory doc page */}}

Other Ports

  {{range .Files}}{{if not .PrimaryPort}}{{template "file" .}}{{end}}{{end}}
{{else}}
  {{range .Files}}{{template "file" .}}{{end}}
{{end}}

{{end}}

{{define "file"}}

  {{.Filename}}
  {{pretty .Kind}}
  {{.PrettyOS}}
  {{pretty .Arch}}
  {{.PrettySize}}
  <tt>{{.PrettyChecksum}}</tt>

{{end}}

{{define "download"}}

{{.Platform}}

{{with .Requirements}}
{{.}}
{{end}}

  {{.Filename}}
  {{if .Size}}({{.PrettySize}}){{end}}

{{end}}
`
<|endoftext|>