<commit_before>package describe

import (
	"fmt"
	"sort"
	"strings"
	"text/tabwriter"

	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	kctl "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"

	buildapi "github.com/openshift/origin/pkg/build/api"
	"github.com/openshift/origin/pkg/client"
	templateapi "github.com/openshift/origin/pkg/template/api"
)

func DescriberFor(kind string, c *client.Client, kclient kclient.Interface, host string) (kctl.Describer, bool) {
	switch kind {
	case "Build":
		return &BuildDescriber{c}, true
	case "BuildConfig":
		return &BuildConfigDescriber{c, host}, true
	case "Deployment":
		return &DeploymentDescriber{c}, true
	case "DeploymentConfig":
		return NewDeploymentConfigDescriber(c, kclient), true
	case "Image":
		return &ImageDescriber{c}, true
	case "ImageRepository":
		return &ImageRepositoryDescriber{c}, true
	case "Route":
		return &RouteDescriber{c}, true
	case "Project":
		return &ProjectDescriber{c}, true
	case "Template":
		return &TemplateDescriber{c, meta.NewAccessor(), kapi.Scheme, nil}, true
	case "Policy":
		return &PolicyDescriber{c}, true
	case "PolicyBinding":
		return &PolicyBindingDescriber{c}, true
	}
	return nil, false
}

// BuildDescriber generates information about a build
type BuildDescriber struct {
	client.Interface
}

func (d *BuildDescriber) DescribeUser(out *tabwriter.Writer, label string, u buildapi.SourceControlUser) {
	if len(u.Name) > 0 && len(u.Email) > 0 {
		formatString(out, label, fmt.Sprintf("%s <%s>", u.Name, u.Email))
		return
	}
	if len(u.Name) > 0 {
		formatString(out, label, u.Name)
		return
	}
	if len(u.Email) > 0 {
		formatString(out, label, u.Email)
	}
}

func (d *BuildDescriber) DescribeParameters(p buildapi.BuildParameters, out *tabwriter.Writer) {
	formatString(out, "Strategy", p.Strategy.Type)
	switch p.Strategy.Type {
	case buildapi.DockerBuildStrategyType:
		if p.Strategy.DockerStrategy != nil && p.Strategy.DockerStrategy.NoCache {
			formatString(out, "No Cache", "yes")
		}
		if p.Strategy.DockerStrategy != nil {
			formatString(out, "BaseImage", p.Strategy.DockerStrategy.BaseImage)
		}
	case buildapi.STIBuildStrategyType:
		formatString(out, "Builder Image", p.Strategy.STIStrategy.Image)
		if p.Strategy.STIStrategy.Incremental {
			formatString(out, "Incremental Build", "yes")
		}
	case buildapi.CustomBuildStrategyType:
		formatString(out, "Builder Image", p.Strategy.CustomStrategy.Image)
		if p.Strategy.CustomStrategy.ExposeDockerSocket {
			formatString(out, "Expose Docker Socket", "yes")
		}
		if len(p.Strategy.CustomStrategy.Env) != 0 {
			formatString(out, "Environment", formatLabels(convertEnv(p.Strategy.CustomStrategy.Env)))
		}
	}
	formatString(out, "Source Type", p.Source.Type)
	if p.Source.Git != nil {
		formatString(out, "URL", p.Source.Git.URI)
		if len(p.Source.Git.Ref) > 0 {
			formatString(out, "Ref", p.Source.Git.Ref)
		}
		if len(p.Source.ContextDir) > 0 {
			formatString(out, "ContextDir", p.Source.ContextDir)
		}
	}
	if p.Output.To != nil {
		if p.Output.To.Namespace != "" {
			formatString(out, "Output to", fmt.Sprintf("%s/%s", p.Output.To.Namespace, p.Output.To.Name))
		} else {
			formatString(out, "Output to", p.Output.To.Name)
		}
	}

	formatString(out, "Output Spec", p.Output.DockerImageReference)
	if p.Revision != nil && p.Revision.Type == buildapi.BuildSourceGit && p.Revision.Git != nil {
		formatString(out, "Git Commit", p.Revision.Git.Commit)
		d.DescribeUser(out, "Revision Author", p.Revision.Git.Author)
		d.DescribeUser(out, "Revision Committer", p.Revision.Git.Committer)
		if len(p.Revision.Git.Message) > 0 {
			formatString(out, "Revision Message", p.Revision.Git.Message)
		}
	}
}

func (d *BuildDescriber) Describe(namespace, name string) (string, error) {
	c := d.Builds(namespace)
	build, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, build.ObjectMeta)
		formatString(out, "Status", bold(build.Status))
		formatString(out, "Build Pod", build.PodName)
		d.DescribeParameters(build.Parameters, out)
		return nil
	})
}

// BuildConfigDescriber generates information about a buildConfig
type BuildConfigDescriber struct {
	client.Interface
	// TODO: this is broken, webhook URL generation should be done by client interface using
	// the string value
	host string
}

// DescribeTriggers generates information about the triggers associated with a buildconfig
func (d *BuildConfigDescriber) DescribeTriggers(bc *buildapi.BuildConfig, host string, out *tabwriter.Writer) {
	webhooks := webhookURL(bc, host)
	for whType, whURL := range webhooks {
		t := strings.Title(whType)
		formatString(out, "Webhook "+t, whURL)
	}
	for _, trigger := range bc.Triggers {
		if trigger.Type != buildapi.ImageChangeBuildTriggerType {
			continue
		}
		if trigger.ImageChange.From.Namespace != "" {
			formatString(out, "Image Repository Trigger", fmt.Sprintf("%s/%s", trigger.ImageChange.From.Namespace, trigger.ImageChange.From.Name))
		} else {
			formatString(out, "Image Repository Trigger", trigger.ImageChange.From.Name)
		}
		formatString(out, "- Tag", trigger.ImageChange.Tag)
		formatString(out, "- Image", trigger.ImageChange.Image)
		formatString(out, "- LastTriggeredImageID", trigger.ImageChange.LastTriggeredImageID)
	}
}

func (d *BuildConfigDescriber) Describe(namespace, name string) (string, error) {
	c := d.BuildConfigs(namespace)
	buildConfig, err := c.Get(name)
	if err != nil {
		return "", err
	}

	buildDescriber := &BuildDescriber{}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, buildConfig.ObjectMeta)
		buildDescriber.DescribeParameters(buildConfig.Parameters, out)
		d.DescribeTriggers(buildConfig, d.host, out)
		return nil
	})
}

// ImageDescriber generates information about a Image
type ImageDescriber struct {
	client.Interface
}

func (d *ImageDescriber) Describe(namespace, name string) (string, error) {
	c := d.Images(namespace)
	image, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, image.ObjectMeta)
		formatString(out, "Docker Image", image.DockerImageReference)
		return nil
	})
}

// ImageRepositoryDescriber generates information about a ImageRepository
type ImageRepositoryDescriber struct {
	client.Interface
}

func (d *ImageRepositoryDescriber) Describe(namespace, name string) (string, error) {
	c := d.ImageRepositories(namespace)
	imageRepository, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, imageRepository.ObjectMeta)
		formatString(out, "Tags", formatLabels(imageRepository.Tags))
		formatString(out, "Registry", imageRepository.Status.DockerImageRepository)
		return nil
	})
}

// RouteDescriber generates information about a Route
type RouteDescriber struct {
	client.Interface
}

func (d *RouteDescriber) Describe(namespace, name string) (string, error) {
	c := d.Routes(namespace)
	route, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, route.ObjectMeta)
		formatString(out, "Host", route.Host)
		formatString(out, "Path", route.Path)
		formatString(out, "Service", route.ServiceName)
		return nil
	})
}

// ProjectDescriber generates information about a Project
type ProjectDescriber struct {
	client.Interface
}

func (d *ProjectDescriber) Describe(namespace, name string) (string, error) {
	c := d.Projects()
	project, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, project.ObjectMeta)
		formatString(out, "Display Name", project.DisplayName)
		return nil
	})
}

// PolicyDescriber generates information about a Project
type PolicyDescriber struct {
	client.Interface
}

// TODO make something a lot prettier
func (d *PolicyDescriber) Describe(namespace, name string) (string, error) {
	c := d.Policies(namespace)
	policy, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, policy.ObjectMeta)
		formatString(out, "Last Modified", policy.LastModified)

		// display the rules in a consistent order
		sortedKeys := make([]string, 0)
		for key := range policy.Roles {
			sortedKeys = append(sortedKeys, key)
		}
		sort.Strings(sortedKeys)

		for _, key := range sortedKeys {
			role := policy.Roles[key]
			fmt.Fprint(out, key+"\tVerbs\tResources\tExtension\n")
			for _, rule := range role.Rules {
				extensionString := ""
				if rule.AttributeRestrictions != (runtime.EmbeddedObject{}) {
					extensionString = fmt.Sprintf("%v", rule.AttributeRestrictions)
				}

				fmt.Fprintf(out, "%v\t%v\t%v\t%v\n",
					"",
					rule.Verbs,
					rule.Resources,
					extensionString)

			}
		}

		return nil
	})
}

// PolicyBindingDescriber generates information about a Project
type PolicyBindingDescriber struct {
	client.Interface
}

// TODO make something a lot prettier
func (d *PolicyBindingDescriber) Describe(namespace, name string) (string, error) {
	c := d.PolicyBindings(namespace)
	policyBinding, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, policyBinding.ObjectMeta)
		formatString(out, "Last Modified", policyBinding.LastModified)
		formatString(out, "Policy", policyBinding.PolicyRef.Namespace)

		// display the rules in a consistent order
		sortedKeys := make([]string, 0)
		for key := range policyBinding.RoleBindings {
			sortedKeys = append(sortedKeys, key)
		}
		sort.Strings(sortedKeys)

		for _, key := range sortedKeys {
			roleBinding := policyBinding.RoleBindings[key]
			formatString(out, "RoleBinding["+key+"]", " ")
			formatString(out, "\tRole", roleBinding.RoleRef.Name)
			formatString(out, "\tUsers", roleBinding.Users.List())
			formatString(out, "\tGroups", roleBinding.Groups.List())
		}

		return nil
	})
}

// TemplateDescriber generates information about a template
type TemplateDescriber struct {
	client.Interface
	meta.MetadataAccessor
	runtime.ObjectTyper
	DescribeObject func(obj runtime.Object, out *tabwriter.Writer) (bool, error)
}

func (d *TemplateDescriber) DescribeParameters(params []templateapi.Parameter, out *tabwriter.Writer) {
	formatString(out, "Parameters", " ")
	indent := "    "
	for _, p := range params {
		formatString(out, indent+"Name", p.Name)
		formatString(out, indent+"Description", p.Description)
		if len(p.Generate) == 0 {
			formatString(out, indent+"Value", p.Value)
			continue
		}
		if len(p.Value) > 0 {
			formatString(out, indent+"Value", p.Value)
			formatString(out, indent+"Generated (ignored)", p.Generate)
			formatString(out, indent+"From", p.From)
		} else {
			formatString(out, indent+"Generated", p.Generate)
			formatString(out, indent+"From", p.From)
		}
		out.Write([]byte("\n"))
	}
}

func (d *TemplateDescriber) DescribeObjects(objects []runtime.Object, labels map[string]string, out *tabwriter.Writer) {
	formatString(out, "Objects", " ")

	indent := "    "
	for _, obj := range objects {
		if d.DescribeObject != nil {
			if ok, _ := d.DescribeObject(obj, out); ok {
				out.Write([]byte("\n"))
				continue
			}
		}

		_, kind, _ := d.ObjectTyper.ObjectVersionAndKind(obj)
		meta := kapi.ObjectMeta{}
		meta.Name, _ = d.MetadataAccessor.Name(obj)
		meta.Annotations, _ = d.MetadataAccessor.Annotations(obj)
		meta.Labels, _ = d.MetadataAccessor.Labels(obj)
		fmt.Fprintf(out, fmt.Sprintf("%s%s\t%s\n", indent, kind, meta.Name))
		if len(meta.Labels) > 0 {
			formatString(out, indent+"Labels", formatLabels(meta.Labels))
		}
		formatAnnotations(out, meta, indent)
	}
	if len(labels) > 0 {
		out.Write([]byte("\n"))
		formatString(out, indent+"Common Labels", formatLabels(labels))
	}
}

func (d *TemplateDescriber) Describe(namespace, name string) (string, error) {
	c := d.Templates(namespace)
	template, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, template.ObjectMeta)
		out.Write([]byte("\n"))
		out.Flush()
		d.DescribeParameters(template.Parameters, out)
		out.Write([]byte("\n"))
		d.DescribeObjects(template.Objects, template.ObjectLabels, out)
		return nil
	})
}
<commit_msg>pretty up policy describer<commit_after>package describe

import (
	"fmt"
	"reflect"
	"strings"
	"text/tabwriter"

	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	kctl "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"

	buildapi "github.com/openshift/origin/pkg/build/api"
	"github.com/openshift/origin/pkg/client"
	templateapi "github.com/openshift/origin/pkg/template/api"
)

func DescriberFor(kind string, c *client.Client, kclient kclient.Interface, host string) (kctl.Describer, bool) {
	switch kind {
	case "Build":
		return &BuildDescriber{c}, true
	case "BuildConfig":
		return &BuildConfigDescriber{c, host}, true
	case "Deployment":
		return &DeploymentDescriber{c}, true
	case "DeploymentConfig":
		return NewDeploymentConfigDescriber(c, kclient), true
	case "Image":
		return &ImageDescriber{c}, true
	case "ImageRepository":
		return &ImageRepositoryDescriber{c}, true
	case "Route":
		return &RouteDescriber{c}, true
	case "Project":
		return &ProjectDescriber{c}, true
	case "Template":
		return &TemplateDescriber{c, meta.NewAccessor(), kapi.Scheme, nil}, true
	case "Policy":
		return &PolicyDescriber{c}, true
	case "PolicyBinding":
		return &PolicyBindingDescriber{c}, true
	}
	return nil, false
}

// BuildDescriber generates information about a build
type BuildDescriber struct {
	client.Interface
}

func (d *BuildDescriber) DescribeUser(out *tabwriter.Writer, label string, u buildapi.SourceControlUser) {
	if len(u.Name) > 0 && len(u.Email) > 0 {
		formatString(out, label, fmt.Sprintf("%s <%s>", u.Name, u.Email))
		return
	}
	if len(u.Name) > 0 {
		formatString(out, label, u.Name)
		return
	}
	if len(u.Email) > 0 {
		formatString(out, label, u.Email)
	}
}

func (d *BuildDescriber) DescribeParameters(p buildapi.BuildParameters, out *tabwriter.Writer) {
	formatString(out, "Strategy", p.Strategy.Type)
	switch p.Strategy.Type {
	case buildapi.DockerBuildStrategyType:
		if p.Strategy.DockerStrategy != nil && p.Strategy.DockerStrategy.NoCache {
			formatString(out, "No Cache", "yes")
		}
		if p.Strategy.DockerStrategy != nil {
			formatString(out, "BaseImage", p.Strategy.DockerStrategy.BaseImage)
		}
	case buildapi.STIBuildStrategyType:
		formatString(out, "Builder Image", p.Strategy.STIStrategy.Image)
		if p.Strategy.STIStrategy.Incremental {
			formatString(out, "Incremental Build", "yes")
		}
	case buildapi.CustomBuildStrategyType:
		formatString(out, "Builder Image", p.Strategy.CustomStrategy.Image)
		if p.Strategy.CustomStrategy.ExposeDockerSocket {
			formatString(out, "Expose Docker Socket", "yes")
		}
		if len(p.Strategy.CustomStrategy.Env) != 0 {
			formatString(out, "Environment", formatLabels(convertEnv(p.Strategy.CustomStrategy.Env)))
		}
	}
	formatString(out, "Source Type", p.Source.Type)
	if p.Source.Git != nil {
		formatString(out, "URL", p.Source.Git.URI)
		if len(p.Source.Git.Ref) > 0 {
			formatString(out, "Ref", p.Source.Git.Ref)
		}
		if len(p.Source.ContextDir) > 0 {
			formatString(out, "ContextDir", p.Source.ContextDir)
		}
	}
	if p.Output.To != nil {
		if p.Output.To.Namespace != "" {
			formatString(out, "Output to", fmt.Sprintf("%s/%s", p.Output.To.Namespace, p.Output.To.Name))
		} else {
			formatString(out, "Output to", p.Output.To.Name)
		}
	}

	formatString(out, "Output Spec", p.Output.DockerImageReference)
	if p.Revision != nil && p.Revision.Type == buildapi.BuildSourceGit && p.Revision.Git != nil {
		formatString(out, "Git Commit", p.Revision.Git.Commit)
		d.DescribeUser(out, "Revision Author", p.Revision.Git.Author)
		d.DescribeUser(out, "Revision Committer", p.Revision.Git.Committer)
		if len(p.Revision.Git.Message) > 0 {
			formatString(out, "Revision Message", p.Revision.Git.Message)
		}
	}
}

func (d *BuildDescriber) Describe(namespace, name string) (string, error) {
	c := d.Builds(namespace)
	build, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, build.ObjectMeta)
		formatString(out, "Status", bold(build.Status))
		formatString(out, "Build Pod", build.PodName)
		d.DescribeParameters(build.Parameters, out)
		return nil
	})
}

// BuildConfigDescriber generates information about a buildConfig
type BuildConfigDescriber struct {
	client.Interface
	// TODO: this is broken, webhook URL generation should be done by client interface using
	// the string value
	host string
}

// DescribeTriggers generates information about the triggers associated with a buildconfig
func (d *BuildConfigDescriber) DescribeTriggers(bc *buildapi.BuildConfig, host string, out *tabwriter.Writer) {
	webhooks := webhookURL(bc, host)
	for whType, whURL := range webhooks {
		t := strings.Title(whType)
		formatString(out, "Webhook "+t, whURL)
	}
	for _, trigger := range bc.Triggers {
		if trigger.Type != buildapi.ImageChangeBuildTriggerType {
			continue
		}
		if trigger.ImageChange.From.Namespace != "" {
			formatString(out, "Image Repository Trigger", fmt.Sprintf("%s/%s", trigger.ImageChange.From.Namespace, trigger.ImageChange.From.Name))
		} else {
			formatString(out, "Image Repository Trigger", trigger.ImageChange.From.Name)
		}
		formatString(out, "- Tag", trigger.ImageChange.Tag)
		formatString(out, "- Image", trigger.ImageChange.Image)
		formatString(out, "- LastTriggeredImageID", trigger.ImageChange.LastTriggeredImageID)
	}
}

func (d *BuildConfigDescriber) Describe(namespace, name string) (string, error) {
	c := d.BuildConfigs(namespace)
	buildConfig, err := c.Get(name)
	if err != nil {
		return "", err
	}

	buildDescriber := &BuildDescriber{}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, buildConfig.ObjectMeta)
		buildDescriber.DescribeParameters(buildConfig.Parameters, out)
		d.DescribeTriggers(buildConfig, d.host, out)
		return nil
	})
}

// ImageDescriber generates information about a Image
type ImageDescriber struct {
	client.Interface
}

func (d *ImageDescriber) Describe(namespace, name string) (string, error) {
	c := d.Images(namespace)
	image, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, image.ObjectMeta)
		formatString(out, "Docker Image", image.DockerImageReference)
		return nil
	})
}

// ImageRepositoryDescriber generates information about a ImageRepository
type ImageRepositoryDescriber struct {
	client.Interface
}

func (d *ImageRepositoryDescriber) Describe(namespace, name string) (string, error) {
	c := d.ImageRepositories(namespace)
	imageRepository, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, imageRepository.ObjectMeta)
		formatString(out, "Tags", formatLabels(imageRepository.Tags))
		formatString(out, "Registry", imageRepository.Status.DockerImageRepository)
		return nil
	})
}

// RouteDescriber generates information about a Route
type RouteDescriber struct {
	client.Interface
}

func (d *RouteDescriber) Describe(namespace, name string) (string, error) {
	c := d.Routes(namespace)
	route, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, route.ObjectMeta)
		formatString(out, "Host", route.Host)
		formatString(out, "Path", route.Path)
		formatString(out, "Service", route.ServiceName)
		return nil
	})
}

// ProjectDescriber generates information about a Project
type ProjectDescriber struct {
	client.Interface
}

func (d *ProjectDescriber) Describe(namespace, name string) (string, error) {
	c := d.Projects()
	project, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, project.ObjectMeta)
		formatString(out, "Display Name", project.DisplayName)
		return nil
	})
}

// PolicyDescriber generates information about a Project
type PolicyDescriber struct {
	client.Interface
}

// TODO make something a lot prettier
func (d *PolicyDescriber) Describe(namespace, name string) (string, error) {
	c := d.Policies(namespace)
	policy, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, policy.ObjectMeta)
		formatString(out, "Last Modified", policy.LastModified)

		// using .List() here because I always want the sorted order that it provides
		for _, key := range util.KeySet(reflect.ValueOf(policy.Roles)).List() {
			role := policy.Roles[key]
			fmt.Fprint(out, key+"\tVerbs\tResources\tExtension\n")
			for _, rule := range role.Rules {
				extensionString := ""
				if rule.AttributeRestrictions != (runtime.EmbeddedObject{}) {
					extensionString = fmt.Sprintf("%v", rule.AttributeRestrictions)
				}

				fmt.Fprintf(out, "%v\t%v\t%v\t%v\n",
					"",
					rule.Verbs.List(),
					rule.Resources.List(),
					extensionString)

			}
		}

		return nil
	})
}

// PolicyBindingDescriber generates information about a Project
type PolicyBindingDescriber struct {
	client.Interface
}

// TODO make something a lot prettier
func (d *PolicyBindingDescriber) Describe(namespace, name string) (string, error) {
	c := d.PolicyBindings(namespace)
	policyBinding, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, policyBinding.ObjectMeta)
		formatString(out, "Last Modified", policyBinding.LastModified)
		formatString(out, "Policy", policyBinding.PolicyRef.Namespace)

		// using .List() here because I always want the sorted order that it provides
		for _, key := range util.KeySet(reflect.ValueOf(policyBinding.RoleBindings)).List() {
			roleBinding := policyBinding.RoleBindings[key]
			formatString(out, "RoleBinding["+key+"]", " ")
			formatString(out, "\tRole", roleBinding.RoleRef.Name)
			formatString(out, "\tUsers", roleBinding.Users.List())
			formatString(out, "\tGroups", roleBinding.Groups.List())
		}

		return nil
	})
}

// TemplateDescriber generates information about a template
type TemplateDescriber struct {
	client.Interface
	meta.MetadataAccessor
	runtime.ObjectTyper
	DescribeObject func(obj runtime.Object, out *tabwriter.Writer) (bool, error)
}

func (d *TemplateDescriber) DescribeParameters(params []templateapi.Parameter, out *tabwriter.Writer) {
	formatString(out, "Parameters", " ")
	indent := "    "
	for _, p := range params {
		formatString(out, indent+"Name", p.Name)
		formatString(out, indent+"Description", p.Description)
		if len(p.Generate) == 0 {
			formatString(out, indent+"Value", p.Value)
			continue
		}
		if len(p.Value) > 0 {
			formatString(out, indent+"Value", p.Value)
			formatString(out, indent+"Generated (ignored)", p.Generate)
			formatString(out, indent+"From", p.From)
		} else {
			formatString(out, indent+"Generated", p.Generate)
			formatString(out, indent+"From", p.From)
		}
		out.Write([]byte("\n"))
	}
}

func (d *TemplateDescriber) DescribeObjects(objects []runtime.Object, labels map[string]string, out *tabwriter.Writer) {
	formatString(out, "Objects", " ")

	indent := "    "
	for _, obj := range objects {
		if d.DescribeObject != nil {
			if ok, _ := d.DescribeObject(obj, out); ok {
				out.Write([]byte("\n"))
				continue
			}
		}

		_, kind, _ := d.ObjectTyper.ObjectVersionAndKind(obj)
		meta := kapi.ObjectMeta{}
		meta.Name, _ = d.MetadataAccessor.Name(obj)
		meta.Annotations, _ = d.MetadataAccessor.Annotations(obj)
		meta.Labels, _ = d.MetadataAccessor.Labels(obj)
		fmt.Fprintf(out, fmt.Sprintf("%s%s\t%s\n", indent, kind, meta.Name))
		if len(meta.Labels) > 0 {
			formatString(out, indent+"Labels", formatLabels(meta.Labels))
		}
		formatAnnotations(out, meta, indent)
	}
	if len(labels) > 0 {
		out.Write([]byte("\n"))
		formatString(out, indent+"Common Labels", formatLabels(labels))
	}
}

func (d *TemplateDescriber) Describe(namespace, name string) (string, error) {
	c := d.Templates(namespace)
	template, err := c.Get(name)
	if err != nil {
		return "", err
	}

	return tabbedString(func(out *tabwriter.Writer) error {
		formatMeta(out, template.ObjectMeta)
		out.Write([]byte("\n"))
		out.Flush()
		d.DescribeParameters(template.Parameters, out)
		out.Write([]byte("\n"))
		d.DescribeObjects(template.Objects, template.ObjectLabels, out)
		return nil
	})
}
<|endoftext|>


<commit_before>/*
Copyright 2017 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 auth

import (
	"errors"
	"io"

	"github.com/golang/glog"
	"github.com/spf13/cobra"

	"k8s.io/kubernetes/pkg/apis/rbac"
	internalcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
	internalrbacclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/resource"
	"k8s.io/kubernetes/pkg/registry/rbac/reconciliation"
)

// ReconcileOptions is the start of the data required to perform the operation.  As new fields are added, add them here instead of
// referencing the cmd.Flags()
type ReconcileOptions struct {
	ResourceBuilder *resource.Builder
	RBACClient      internalrbacclient.RbacInterface
	CoreClient      internalcoreclient.CoreInterface

	Print func(*resource.Info) error

	Out io.Writer
	Err io.Writer
}

var (
	reconcileLong = templates.LongDesc(`
		Reconciles rules for RBAC Role, RoleBinding, ClusterRole, and ClusterRole binding objects.

		This is preferred to 'apply' for RBAC resources so that proper rule coverage checks are done.`)

	reconcileExample = templates.Examples(`
		# Reconcile rbac resources from a file
		kubectl auth reconcile -f my-rbac-rules.yaml`)
)

func NewCmdReconcile(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
	fileOptions := &resource.FilenameOptions{}
	o := &ReconcileOptions{
		Out: out,
		Err: err,
	}

	cmd := &cobra.Command{
		Use:     "reconcile -f FILENAME",
		Short:   "Reconciles rules for RBAC Role, RoleBinding, ClusterRole, and ClusterRole binding objects",
		Long:    reconcileLong,
		Example: reconcileExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(cmd, f, args, fileOptions))
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.RunReconcile())
		},
	}

	cmdutil.AddPrinterFlags(cmd)
	usage := "identifying the resource to reconcile."
	cmdutil.AddFilenameOptionFlags(cmd, fileOptions, usage)
	cmd.MarkFlagRequired("filename")

	return cmd
}

func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args []string, options *resource.FilenameOptions) error {
	if len(args) > 0 {
		return errors.New("no arguments are allowed")
	}

	namespace, enforceNamespace, err := f.DefaultNamespace()
	if err != nil {
		return err
	}
	o.ResourceBuilder = f.NewBuilder().
		ContinueOnError().
		NamespaceParam(namespace).DefaultNamespace().
		FilenameParam(enforceNamespace, options).
		Flatten()

	client, err := f.ClientSet()
	if err != nil {
		return err
	}
	o.RBACClient = client.Rbac()
	o.CoreClient = client.Core()

	mapper, _ := f.Object()
	dryRun := false
	output := cmdutil.GetFlagString(cmd, "output")
	shortOutput := output == "name"
	o.Print = func(info *resource.Info) error {
		if len(output) > 0 && !shortOutput {
			return cmdutil.PrintResourceInfoForCommand(cmd, info, f, o.Out)
		}
		cmdutil.PrintSuccess(mapper, shortOutput, o.Out, info.Mapping.Resource, info.Name, dryRun, "reconciled")
		return nil
	}

	return nil
}

func (o *ReconcileOptions) Validate() error {
	return nil
}

func (o *ReconcileOptions) RunReconcile() error {
	r := o.ResourceBuilder.Do()
	err := r.Err()
	if err != nil {
		return err
	}

	err = r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}

		// shallowInfoCopy this is used to later twiddle the Object for printing
		// we really need more straightforward printing options
		shallowInfoCopy := *info

		switch t := info.Object.(type) {
		case *rbac.Role:
			reconcileOptions := reconciliation.ReconcileRoleOptions{
				Confirm:                true,
				RemoveExtraPermissions: false,
				Role: reconciliation.RoleRuleOwner{Role: t},
				Client: reconciliation.RoleModifier{
					NamespaceClient: o.CoreClient.Namespaces(),
					Client:          o.RBACClient,
				},
			}
			result, err := reconcileOptions.Run()
			if err != nil {
				return err
			}
			shallowInfoCopy.Object = result.Role.GetObject()
			o.Print(&shallowInfoCopy)

		case *rbac.ClusterRole:
			reconcileOptions := reconciliation.ReconcileRoleOptions{
				Confirm:                true,
				RemoveExtraPermissions: false,
				Role: reconciliation.ClusterRoleRuleOwner{ClusterRole: t},
				Client: reconciliation.ClusterRoleModifier{
					Client: o.RBACClient.ClusterRoles(),
				},
			}
			result, err := reconcileOptions.Run()
			if err != nil {
				return err
			}
			shallowInfoCopy.Object = result.Role.GetObject()
			o.Print(&shallowInfoCopy)

		case *rbac.RoleBinding:
			reconcileOptions := reconciliation.ReconcileRoleBindingOptions{
				Confirm:             true,
				RemoveExtraSubjects: false,
				RoleBinding:         reconciliation.RoleBindingAdapter{RoleBinding: t},
				Client: reconciliation.RoleBindingClientAdapter{
					Client:          o.RBACClient,
					NamespaceClient: o.CoreClient.Namespaces(),
				},
			}
			result, err := reconcileOptions.Run()
			if err != nil {
				return err
			}
			shallowInfoCopy.Object = result.RoleBinding.GetObject()
			o.Print(&shallowInfoCopy)

		case *rbac.ClusterRoleBinding:
			reconcileOptions := reconciliation.ReconcileRoleBindingOptions{
				Confirm:             true,
				RemoveExtraSubjects: false,
				RoleBinding:         reconciliation.ClusterRoleBindingAdapter{ClusterRoleBinding: t},
				Client: reconciliation.ClusterRoleBindingClientAdapter{
					Client: o.RBACClient.ClusterRoleBindings(),
				},
			}
			result, err := reconcileOptions.Run()
			if err != nil {
				return err
			}
			shallowInfoCopy.Object = result.RoleBinding.GetObject()
			o.Print(&shallowInfoCopy)

		default:
			glog.V(1).Infof("skipping %#v", info.Object.GetObjectKind())
			// skip ignored resources
		}

		return nil
	})

	return err
}
<commit_msg>Make ReconcileOptions reusable<commit_after>/*
Copyright 2017 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 auth

import (
	"errors"
	"io"

	"github.com/golang/glog"
	"github.com/spf13/cobra"

	"k8s.io/kubernetes/pkg/apis/rbac"
	internalcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
	internalrbacclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/resource"
	"k8s.io/kubernetes/pkg/registry/rbac/reconciliation"
)

// ReconcileOptions is the start of the data required to perform the operation.  As new fields are added, add them here instead of
// referencing the cmd.Flags()
type ReconcileOptions struct {
	Visitor         resource.Visitor
	RBACClient      internalrbacclient.RbacInterface
	NamespaceClient internalcoreclient.NamespaceInterface

	Print func(*resource.Info) error

	Out io.Writer
	Err io.Writer
}

var (
	reconcileLong = templates.LongDesc(`
		Reconciles rules for RBAC Role, RoleBinding, ClusterRole, and ClusterRole binding objects.

		This is preferred to 'apply' for RBAC resources so that proper rule coverage checks are done.`)

	reconcileExample = templates.Examples(`
		# Reconcile rbac resources from a file
		kubectl auth reconcile -f my-rbac-rules.yaml`)
)

func NewCmdReconcile(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
	fileOptions := &resource.FilenameOptions{}
	o := &ReconcileOptions{
		Out: out,
		Err: err,
	}

	cmd := &cobra.Command{
		Use:     "reconcile -f FILENAME",
		Short:   "Reconciles rules for RBAC Role, RoleBinding, ClusterRole, and ClusterRole binding objects",
		Long:    reconcileLong,
		Example: reconcileExample,
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(cmd, f, args, fileOptions))
			cmdutil.CheckErr(o.Validate())
			cmdutil.CheckErr(o.RunReconcile())
		},
	}

	cmdutil.AddPrinterFlags(cmd)
	usage := "identifying the resource to reconcile."
	cmdutil.AddFilenameOptionFlags(cmd, fileOptions, usage)
	cmd.MarkFlagRequired("filename")

	return cmd
}

func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args []string, options *resource.FilenameOptions) error {
	if len(args) > 0 {
		return errors.New("no arguments are allowed")
	}

	namespace, enforceNamespace, err := f.DefaultNamespace()
	if err != nil {
		return err
	}

	r := f.NewBuilder().
		ContinueOnError().
		NamespaceParam(namespace).DefaultNamespace().
		FilenameParam(enforceNamespace, options).
		Flatten().
		Do()
	if err := r.Err(); err != nil {
		return err
	}
	o.Visitor = r

	client, err := f.ClientSet()
	if err != nil {
		return err
	}
	o.RBACClient = client.Rbac()
	o.NamespaceClient = client.Core().Namespaces()

	mapper, _ := f.Object()
	dryRun := false
	output := cmdutil.GetFlagString(cmd, "output")
	shortOutput := output == "name"
	o.Print = func(info *resource.Info) error {
		if len(output) > 0 && !shortOutput {
			return cmdutil.PrintResourceInfoForCommand(cmd, info, f, o.Out)
		}
		cmdutil.PrintSuccess(mapper, shortOutput, o.Out, info.Mapping.Resource, info.Name, dryRun, "reconciled")
		return nil
	}

	return nil
}

func (o *ReconcileOptions) Validate() error {
	if o.Visitor == nil {
		return errors.New("ReconcileOptions.Visitor must be set")
	}
	if o.RBACClient == nil {
		return errors.New("ReconcileOptions.RBACClient must be set")
	}
	if o.NamespaceClient == nil {
		return errors.New("ReconcileOptions.NamespaceClient must be set")
	}
	if o.Print == nil {
		return errors.New("ReconcileOptions.Print must be set")
	}
	if o.Out == nil {
		return errors.New("ReconcileOptions.Out must be set")
	}
	if o.Err == nil {
		return errors.New("ReconcileOptions.Err must be set")
	}
	return nil
}

func (o *ReconcileOptions) RunReconcile() error {
	return o.Visitor.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}

		// shallowInfoCopy this is used to later twiddle the Object for printing
		// we really need more straightforward printing options
		shallowInfoCopy := *info

		switch t := info.Object.(type) {
		case *rbac.Role:
			reconcileOptions := reconciliation.ReconcileRoleOptions{
				Confirm:                true,
				RemoveExtraPermissions: false,
				Role: reconciliation.RoleRuleOwner{Role: t},
				Client: reconciliation.RoleModifier{
					NamespaceClient: o.NamespaceClient,
					Client:          o.RBACClient,
				},
			}
			result, err := reconcileOptions.Run()
			if err != nil {
				return err
			}
			shallowInfoCopy.Object = result.Role.GetObject()
			o.Print(&shallowInfoCopy)

		case *rbac.ClusterRole:
			reconcileOptions := reconciliation.ReconcileRoleOptions{
				Confirm:                true,
				RemoveExtraPermissions: false,
				Role: reconciliation.ClusterRoleRuleOwner{ClusterRole: t},
				Client: reconciliation.ClusterRoleModifier{
					Client: o.RBACClient.ClusterRoles(),
				},
			}
			result, err := reconcileOptions.Run()
			if err != nil {
				return err
			}
			shallowInfoCopy.Object = result.Role.GetObject()
			o.Print(&shallowInfoCopy)

		case *rbac.RoleBinding:
			reconcileOptions := reconciliation.ReconcileRoleBindingOptions{
				Confirm:             true,
				RemoveExtraSubjects: false,
				RoleBinding:         reconciliation.RoleBindingAdapter{RoleBinding: t},
				Client: reconciliation.RoleBindingClientAdapter{
					Client:          o.RBACClient,
					NamespaceClient: o.NamespaceClient,
				},
			}
			result, err := reconcileOptions.Run()
			if err != nil {
				return err
			}
			shallowInfoCopy.Object = result.RoleBinding.GetObject()
			o.Print(&shallowInfoCopy)

		case *rbac.ClusterRoleBinding:
			reconcileOptions := reconciliation.ReconcileRoleBindingOptions{
				Confirm:             true,
				RemoveExtraSubjects: false,
				RoleBinding:         reconciliation.ClusterRoleBindingAdapter{ClusterRoleBinding: t},
				Client: reconciliation.ClusterRoleBindingClientAdapter{
					Client: o.RBACClient.ClusterRoleBindings(),
				},
			}
			result, err := reconcileOptions.Run()
			if err != nil {
				return err
			}
			shallowInfoCopy.Object = result.RoleBinding.GetObject()
			o.Print(&shallowInfoCopy)

		default:
			glog.V(1).Infof("skipping %#v", info.Object.GetObjectKind())
			// skip ignored resources
		}

		return nil
	})
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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

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

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

package stats

import (
	"k8s.io/kubernetes/pkg/api/resource"
	"k8s.io/kubernetes/pkg/api/unversioned"
)

// Summary is a top-level container for holding NodeStats and PodStats.
type Summary struct {
	// The time the most recent data included in this summary was collect at, rounded to the nearest
	// second.
	Time unversioned.Time `json:"time"`
	// Overall node stats.
	Node NodeStats `json:"node"`
	// Per-pod stats.
	Pods []PodStats `json:"pods"`
}

// NodeStats holds node-level unprocessed sample stats.
type NodeStats struct {
	// Reference to the measured Node.
	NodeName string `json:"nodeName"`
	// Stats of system daemons tracked as raw containers.
	// The system containers are named according to the SystemContainer* constants.
	SystemContainers []ContainerStats `json:"systemContainers,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
	// Stats pertaining to CPU resources.
	CPU *CPUStats `json:"cpu,omitempty"`
	// Stats pertaining to memory (RAM) resources.
	Memory *MemoryStats `json:"memory,omitempty"`
	// Stats pertaining to network resources.
	Network *NetworkStats `json:"network,omitempty"`
}

const (
	// Container name for the system container tracking Kubelet usage.
	SystemContainerKubelet = "kubelet"
	// Container name for the system container tracking the runtime (e.g. docker or rkt) usage.
	SystemContainerRuntime = "runtime"
	// Container name for the system container tracking non-kubernetes processes.
	SystemContainerMisc = "misc"
)

// PodStats holds pod-level unprocessed sample stats.
type PodStats struct {
	// Reference to the measured Pod.
	PodRef NonLocalObjectReference `json:"podRef"`
	// Stats of containers in the measured pod.
	Containers []ContainerStats `json:"containers" patchStrategy:"merge" patchMergeKey:"name"`
	// Stats pertaining to network resources.
	Network *NetworkStats `json:"network,omitempty"`
}

// ContainerStats holds container-level unprocessed sample stats.
type ContainerStats struct {
	// Reference to the measured container.
	Name string `json:"name"`
	// Stats pertaining to CPU resources.
	CPU *CPUStats `json:"cpu,omitempty"`
	// Stats pertaining to memory (RAM) resources.
	Memory *MemoryStats `json:"memory,omitempty"`
}

// NonLocalObjectReference contains enough information to locate the referenced object.
type NonLocalObjectReference struct {
	Name      string `json:"name"`
	Namespace string `json:"namespace"`
}

// NetworkStats contains data about network resources.
type NetworkStats struct {
	// Cumulative count of bytes received.
	RxBytes *resource.Quantity `json:"rxBytes,omitempty"`
	// Cumulative count of receive errors encountered.
	RxErrors *int64 `json:"rxErrors,omitempty"`
	// Cumulative count of bytes transmitted.
	TxBytes *resource.Quantity `json:"txBytes,omitempty"`
	// Cumulative count of transmit errors encountered.
	TxErrors *int64 `json:"txErrors,omitempty"`
}

// CPUStats contains data about CPU usage.
type CPUStats struct {
	// Total CPU usage (sum of all cores) averaged over the sample window.
	// The "core" unit can be interpreted as CPU core-seconds per second.
	UsageCores *resource.Quantity `json:"usageCores,omitempty"`
	// Cumulative CPU usage (sum of all cores) since object creation.
	UsageCoreSeconds *resource.Quantity `json:"usageCoreSeconds,omitempty"`
}

// MemoryStats contains data about memory usage.
type MemoryStats struct {
	// Total memory in use. This includes all memory regardless of when it was accessed.
	UsageBytes *resource.Quantity `json:"usageBytes,omitempty"`
	// The amount of working set memory. This includes recently accessed memory,
	// dirty memory, and kernel memory. UsageBytes is <= TotalBytes.
	WorkingSetBytes *resource.Quantity `json:"workingSetBytes,omitempty"`
	// Cumulative number of minor page faults.
	PageFaults *int64 `json:"pageFaults,omitempty"`
	// Cumulative number of major page faults.
	MajorPageFaults *int64 `json:"majorPageFaults,omitempty"`
}
<commit_msg>Define stats api fields for exporting filesystem metrics #17331<commit_after>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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

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

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

package stats

import (
	"k8s.io/kubernetes/pkg/api/resource"
	"k8s.io/kubernetes/pkg/api/unversioned"
)

// Summary is a top-level container for holding NodeStats and PodStats.
type Summary struct {
	// The time the most recent data included in this summary was collect at, rounded to the nearest
	// second.
	Time unversioned.Time `json:"time"`
	// Overall node stats.
	Node NodeStats `json:"node"`
	// Per-pod stats.
	Pods []PodStats `json:"pods"`
}

// NodeStats holds node-level unprocessed sample stats.
type NodeStats struct {
	// Reference to the measured Node.
	NodeName string `json:"nodeName"`
	// Stats of system daemons tracked as raw containers.
	// The system containers are named according to the SystemContainer* constants.
	SystemContainers []ContainerStats `json:"systemContainers,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
	// Stats pertaining to CPU resources.
	CPU *CPUStats `json:"cpu,omitempty"`
	// Stats pertaining to memory (RAM) resources.
	Memory *MemoryStats `json:"memory,omitempty"`
	// Stats pertaining to network resources.
	Network *NetworkStats `json:"network,omitempty"`
	// Stats pertaining to total usage of filesystem resources on the rootfs used by node k8s components.
	// NodeFs.Used is the total bytes used on the filesystem.
	Fs *FsStats `json:"fs,omitempty"`
}

const (
	// Container name for the system container tracking Kubelet usage.
	SystemContainerKubelet = "kubelet"
	// Container name for the system container tracking the runtime (e.g. docker or rkt) usage.
	SystemContainerRuntime = "runtime"
	// Container name for the system container tracking non-kubernetes processes.
	SystemContainerMisc = "misc"
)

// PodStats holds pod-level unprocessed sample stats.
type PodStats struct {
	// Reference to the measured Pod.
	PodRef NonLocalObjectReference `json:"podRef"`
	// Stats of containers in the measured pod.
	Containers []ContainerStats `json:"containers" patchStrategy:"merge" patchMergeKey:"name"`
	// Stats pertaining to network resources.
	Network *NetworkStats `json:"network,omitempty"`
	// Stats pertaining to volume usage of filesystem resources.
	// VolumeStats.UsedBytes is the number of bytes used by the Volume
	VolumeStats []VolumeStats `json:"volume,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
}

// ContainerStats holds container-level unprocessed sample stats.
type ContainerStats struct {
	// Reference to the measured container.
	Name string `json:"name"`
	// Stats pertaining to CPU resources.
	CPU *CPUStats `json:"cpu,omitempty"`
	// Stats pertaining to memory (RAM) resources.
	Memory *MemoryStats `json:"memory,omitempty"`
	// Stats pertaining to container rootfs usage of filesystem resources.
	// Rootfs.UsedBytes is the number of bytes used for the container write layer.
	Rootfs *FsStats `json:"rootfs,omitempty"`
	// Stats pertaining to container logs usage of filesystem resources.
	// Logs.UsedBytes is the number of bytes used for the container logs.
	Logs *FsStats `json:"logs,omitempty"`
}

// NonLocalObjectReference contains enough information to locate the referenced object.
type NonLocalObjectReference struct {
	Name      string `json:"name"`
	Namespace string `json:"namespace"`
}

// NetworkStats contains data about network resources.
type NetworkStats struct {
	// Cumulative count of bytes received.
	RxBytes *resource.Quantity `json:"rxBytes,omitempty"`
	// Cumulative count of receive errors encountered.
	RxErrors *int64 `json:"rxErrors,omitempty"`
	// Cumulative count of bytes transmitted.
	TxBytes *resource.Quantity `json:"txBytes,omitempty"`
	// Cumulative count of transmit errors encountered.
	TxErrors *int64 `json:"txErrors,omitempty"`
}

// CPUStats contains data about CPU usage.
type CPUStats struct {
	// Total CPU usage (sum of all cores) averaged over the sample window.
	// The "core" unit can be interpreted as CPU core-seconds per second.
	UsageCores *resource.Quantity `json:"usageCores,omitempty"`
	// Cumulative CPU usage (sum of all cores) since object creation.
	UsageCoreSeconds *resource.Quantity `json:"usageCoreSeconds,omitempty"`
}

// MemoryStats contains data about memory usage.
type MemoryStats struct {
	// Total memory in use. This includes all memory regardless of when it was accessed.
	UsageBytes *resource.Quantity `json:"usageBytes,omitempty"`
	// The amount of working set memory. This includes recently accessed memory,
	// dirty memory, and kernel memory. UsageBytes is <= TotalBytes.
	WorkingSetBytes *resource.Quantity `json:"workingSetBytes,omitempty"`
	// Cumulative number of minor page faults.
	PageFaults *int64 `json:"pageFaults,omitempty"`
	// Cumulative number of major page faults.
	MajorPageFaults *int64 `json:"majorPageFaults,omitempty"`
}

// VolumeStats contains data about Volume filesystem usage.
type VolumeStats struct {
	// Embedded FsStats
	FsStats
	// Name is the name given to the Volume
	Name string `json:"name,omitempty"`
}

// FsStats contains data about filesystem usage.
type FsStats struct {
	// AvailableBytes represents the storage space available (bytes) for the filesystem.
	AvailableBytes *resource.Quantity `json:"availableBytes,omitempty"`
	// CapacityBytes represents the total capacity (bytes) of the filesystems underlying storage.
	CapacityBytes *resource.Quantity `json:"capacityBytes,omitempty"`
	// UsedBytes represents the bytes used for a specific task on the filesystem.
	// This may differ from the total bytes used on the filesystem and may not equal CapacityBytes - AvailableBytes.
	// e.g. For ContainerStats.Rootfs this is the bytes used by the container rootfs on the filesystem.
	UsedBytes *resource.Quantity `json:"usedBytes,omitempty"`
}
<|endoftext|>


<commit_before>package swarm

import (
	"fmt"

	log "github.com/Sirupsen/logrus"
	docker_types "github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/infrakit/pkg/discovery"
	group_types "github.com/docker/infrakit/pkg/plugin/group/types"
	"github.com/docker/infrakit/pkg/plugin/metadata"
	"github.com/docker/infrakit/pkg/spi/instance"
	"github.com/docker/infrakit/pkg/template"
	"github.com/docker/infrakit/pkg/types"
	"github.com/docker/infrakit/pkg/util/docker"
	"golang.org/x/net/context"
)

// NewWorkerFlavor creates a flavor.Plugin that creates manager and worker nodes connected in a swarm.
func NewWorkerFlavor(plugins func() discovery.Plugins, connect func(Spec) (docker.APIClientCloser, error),
	templ *template.Template,
	stop <-chan struct{}) *WorkerFlavor {

	base := &baseFlavor{initScript: templ, getDockerClient: connect, plugins: plugins}
	base.metadataPlugin = metadata.NewPluginFromChannel(base.runMetadataSnapshot(stop))
	return &WorkerFlavor{baseFlavor: base}
}

// WorkerFlavor implements the flavor and metadata plugins
type WorkerFlavor struct {
	*baseFlavor
}

// Prepare sets up the provisioner / instance plugin's spec based on information about the swarm to join.
func (s *WorkerFlavor) Prepare(flavorProperties *types.Any, instanceSpec instance.Spec,
	allocation group_types.AllocationMethod,
	index group_types.Index) (instance.Spec, error) {
	return s.baseFlavor.prepare("worker", flavorProperties, instanceSpec, allocation, index)
}

// Drain in the case of worker will force a node removal in the swarm.
func (s *WorkerFlavor) Drain(flavorProperties *types.Any, inst instance.Description) error {
	if flavorProperties == nil {
		return fmt.Errorf("missing config")
	}

	spec := Spec{}
	err := flavorProperties.Decode(&spec)
	if err != nil {
		return err
	}

	link := types.NewLinkFromMap(inst.Tags)
	if !link.Valid() {
		return fmt.Errorf("Unable to drain %s without an association tag", inst.ID)
	}

	filter := filters.NewArgs()
	filter.Add("label", fmt.Sprintf("%s=%s", link.Label(), link.Value()))

	dockerClient, err := s.baseFlavor.getDockerClient(spec)
	if err != nil {
		return err
	}
	defer dockerClient.Close()

	nodes, err := dockerClient.NodeList(context.Background(), docker_types.NodeListOptions{Filters: filter})
	if err != nil {
		return err
	}

	switch {
	case len(nodes) == 0:
		return fmt.Errorf("Unable to drain %s, not found in swarm", inst.ID)

	case len(nodes) == 1:
		log.Debugln("Docker NodeRemove", nodes[0].ID)
		err := dockerClient.NodeRemove(
			context.Background(),
			nodes[0].ID,
			docker_types.NodeRemoveOptions{Force: true})
		if err != nil {
			return err
		}

		return nil

	default:
		return fmt.Errorf("Expected at most one node with label %s, but found %s", link.Value(), nodes)
	}
}
<commit_msg>Fix CI issue with returned type (#706)<commit_after>package swarm

import (
	"fmt"

	log "github.com/Sirupsen/logrus"
	docker_types "github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/infrakit/pkg/discovery"
	group_types "github.com/docker/infrakit/pkg/plugin/group/types"
	"github.com/docker/infrakit/pkg/plugin/metadata"
	"github.com/docker/infrakit/pkg/spi/instance"
	"github.com/docker/infrakit/pkg/template"
	"github.com/docker/infrakit/pkg/types"
	"github.com/docker/infrakit/pkg/util/docker"
	"golang.org/x/net/context"
)

// NewWorkerFlavor creates a flavor.Plugin that creates manager and worker nodes connected in a swarm.
func NewWorkerFlavor(plugins func() discovery.Plugins, connect func(Spec) (docker.APIClientCloser, error),
	templ *template.Template,
	stop <-chan struct{}) *WorkerFlavor {

	base := &baseFlavor{initScript: templ, getDockerClient: connect, plugins: plugins}
	base.metadataPlugin = metadata.NewPluginFromChannel(base.runMetadataSnapshot(stop))
	return &WorkerFlavor{baseFlavor: base}
}

// WorkerFlavor implements the flavor and metadata plugins
type WorkerFlavor struct {
	*baseFlavor
}

// Prepare sets up the provisioner / instance plugin's spec based on information about the swarm to join.
func (s *WorkerFlavor) Prepare(flavorProperties *types.Any, instanceSpec instance.Spec,
	allocation group_types.AllocationMethod,
	index group_types.Index) (instance.Spec, error) {
	return s.baseFlavor.prepare("worker", flavorProperties, instanceSpec, allocation, index)
}

// Drain in the case of worker will force a node removal in the swarm.
func (s *WorkerFlavor) Drain(flavorProperties *types.Any, inst instance.Description) error {
	if flavorProperties == nil {
		return fmt.Errorf("missing config")
	}

	spec := Spec{}
	err := flavorProperties.Decode(&spec)
	if err != nil {
		return err
	}

	link := types.NewLinkFromMap(inst.Tags)
	if !link.Valid() {
		return fmt.Errorf("Unable to drain %s without an association tag", inst.ID)
	}

	filter := filters.NewArgs()
	filter.Add("label", fmt.Sprintf("%s=%s", link.Label(), link.Value()))

	dockerClient, err := s.baseFlavor.getDockerClient(spec)
	if err != nil {
		return err
	}
	defer dockerClient.Close()

	nodes, err := dockerClient.NodeList(context.Background(), docker_types.NodeListOptions{Filters: filter})
	if err != nil {
		return err
	}

	switch {
	case len(nodes) == 0:
		return fmt.Errorf("Unable to drain %s, not found in swarm", inst.ID)

	case len(nodes) == 1:
		log.Debugln("Docker NodeRemove", nodes[0].ID)
		err := dockerClient.NodeRemove(
			context.Background(),
			nodes[0].ID,
			docker_types.NodeRemoveOptions{Force: true})
		if err != nil {
			return err
		}

		return nil

	default:
		return fmt.Errorf("Expected at most one node with label %s, but found %v", link.Value(), nodes)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2017 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 server

import (
	"bytes"
	"fmt"
	"io"
	"os/exec"
	"strings"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/net/context"
	runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"

	ctrdutil "github.com/containerd/cri/pkg/containerd/util"
	sandboxstore "github.com/containerd/cri/pkg/store/sandbox"
)

// PortForward prepares a streaming endpoint to forward ports from a PodSandbox, and returns the address.
func (c *criService) PortForward(ctx context.Context, r *runtime.PortForwardRequest) (retRes *runtime.PortForwardResponse, retErr error) {
	// TODO(random-liu): Run a socat container inside the sandbox to do portforward.
	sandbox, err := c.sandboxStore.Get(r.GetPodSandboxId())
	if err != nil {
		return nil, errors.Wrapf(err, "failed to find sandbox %q", r.GetPodSandboxId())
	}
	if sandbox.Status.Get().State != sandboxstore.StateReady {
		return nil, errors.New("sandbox container is not running")
	}
	// TODO(random-liu): Verify that ports are exposed.
	return c.streamServer.GetPortForward(r)
}

// portForward requires `nsenter` and `socat` on the node, it uses `nsenter` to enter the
// sandbox namespace, and run `socat` inside the namespace to forward stream for a specific
// port. The `socat` command keeps running until it exits or client disconnect.
func (c *criService) portForward(id string, port int32, stream io.ReadWriteCloser) error {
	s, err := c.sandboxStore.Get(id)
	if err != nil {
		return errors.Wrapf(err, "failed to find sandbox %q in store", id)
	}
	t, err := s.Container.Task(ctrdutil.NamespacedContext(), nil)
	if err != nil {
		return errors.Wrap(err, "failed to get sandbox container task")
	}
	pid := t.Pid()

	socat, err := exec.LookPath("socat")
	if err != nil {
		return errors.Wrap(err, "failed to find socat")
	}

	// Check following links for meaning of the options:
	// * socat: https://linux.die.net/man/1/socat
	// * nsenter: http://man7.org/linux/man-pages/man1/nsenter.1.html
	args := []string{"-t", fmt.Sprintf("%d", pid), "-n", socat,
		"-", fmt.Sprintf("TCP4:localhost:%d", port)}

	nsenter, err := exec.LookPath("nsenter")
	if err != nil {
		return errors.Wrap(err, "failed to find nsenter")
	}

	logrus.Infof("Executing port forwarding command: %s %s", nsenter, strings.Join(args, " "))

	cmd := exec.Command(nsenter, args...)
	cmd.Stdout = stream

	stderr := new(bytes.Buffer)
	cmd.Stderr = stderr

	// If we use Stdin, command.Run() won't return until the goroutine that's copying
	// from stream finishes. Unfortunately, if you have a client like telnet connected
	// via port forwarding, as long as the user's telnet client is connected to the user's
	// local listener that port forwarding sets up, the telnet session never exits. This
	// means that even if socat has finished running, command.Run() won't ever return
	// (because the client still has the connection and stream open).
	//
	// The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe
	// when the command (socat) exits.
	in, err := cmd.StdinPipe()
	if err != nil {
		return errors.Wrap(err, "failed to create stdin pipe")
	}
	go func() {
		if _, err := io.Copy(in, stream); err != nil {
			logrus.WithError(err).Errorf("Failed to copy port forward input for %q port %d", id, port)
		}
		in.Close()
		logrus.Debugf("Finish copy port forward input for %q port %d: %v", id, port)
	}()

	if err := cmd.Run(); err != nil {
		return errors.Errorf("nsenter command returns error: %v, stderr: %q", err, stderr.String())
	}

	logrus.Infof("Finish port forwarding for %q port %d", id, port)

	return nil
}
<commit_msg>Fix a log output.<commit_after>/*
Copyright 2017 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 server

import (
	"bytes"
	"fmt"
	"io"
	"os/exec"
	"strings"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/net/context"
	runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"

	ctrdutil "github.com/containerd/cri/pkg/containerd/util"
	sandboxstore "github.com/containerd/cri/pkg/store/sandbox"
)

// PortForward prepares a streaming endpoint to forward ports from a PodSandbox, and returns the address.
func (c *criService) PortForward(ctx context.Context, r *runtime.PortForwardRequest) (retRes *runtime.PortForwardResponse, retErr error) {
	// TODO(random-liu): Run a socat container inside the sandbox to do portforward.
	sandbox, err := c.sandboxStore.Get(r.GetPodSandboxId())
	if err != nil {
		return nil, errors.Wrapf(err, "failed to find sandbox %q", r.GetPodSandboxId())
	}
	if sandbox.Status.Get().State != sandboxstore.StateReady {
		return nil, errors.New("sandbox container is not running")
	}
	// TODO(random-liu): Verify that ports are exposed.
	return c.streamServer.GetPortForward(r)
}

// portForward requires `nsenter` and `socat` on the node, it uses `nsenter` to enter the
// sandbox namespace, and run `socat` inside the namespace to forward stream for a specific
// port. The `socat` command keeps running until it exits or client disconnect.
func (c *criService) portForward(id string, port int32, stream io.ReadWriteCloser) error {
	s, err := c.sandboxStore.Get(id)
	if err != nil {
		return errors.Wrapf(err, "failed to find sandbox %q in store", id)
	}
	t, err := s.Container.Task(ctrdutil.NamespacedContext(), nil)
	if err != nil {
		return errors.Wrap(err, "failed to get sandbox container task")
	}
	pid := t.Pid()

	socat, err := exec.LookPath("socat")
	if err != nil {
		return errors.Wrap(err, "failed to find socat")
	}

	// Check following links for meaning of the options:
	// * socat: https://linux.die.net/man/1/socat
	// * nsenter: http://man7.org/linux/man-pages/man1/nsenter.1.html
	args := []string{"-t", fmt.Sprintf("%d", pid), "-n", socat,
		"-", fmt.Sprintf("TCP4:localhost:%d", port)}

	nsenter, err := exec.LookPath("nsenter")
	if err != nil {
		return errors.Wrap(err, "failed to find nsenter")
	}

	logrus.Infof("Executing port forwarding command: %s %s", nsenter, strings.Join(args, " "))

	cmd := exec.Command(nsenter, args...)
	cmd.Stdout = stream

	stderr := new(bytes.Buffer)
	cmd.Stderr = stderr

	// If we use Stdin, command.Run() won't return until the goroutine that's copying
	// from stream finishes. Unfortunately, if you have a client like telnet connected
	// via port forwarding, as long as the user's telnet client is connected to the user's
	// local listener that port forwarding sets up, the telnet session never exits. This
	// means that even if socat has finished running, command.Run() won't ever return
	// (because the client still has the connection and stream open).
	//
	// The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe
	// when the command (socat) exits.
	in, err := cmd.StdinPipe()
	if err != nil {
		return errors.Wrap(err, "failed to create stdin pipe")
	}
	go func() {
		if _, err := io.Copy(in, stream); err != nil {
			logrus.WithError(err).Errorf("Failed to copy port forward input for %q port %d", id, port)
		}
		in.Close()
		logrus.Debugf("Finish copy port forward input for %q port %d", id, port)
	}()

	if err := cmd.Run(); err != nil {
		return errors.Errorf("nsenter command returns error: %v, stderr: %q", err, stderr.String())
	}

	logrus.Infof("Finish port forwarding for %q port %d", id, port)

	return nil
}
<|endoftext|>


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

// This file implements the Mapping data structure.

package main

import (
	"fmt"
	"io"
	"path"
	"path/filepath"
	"sort"
	"strings"
)

// A Mapping object maps relative paths (e.g. from URLs)
// to absolute paths (of the file system) and vice versa.
//
// A Mapping object consists of a list of individual mappings
// of the form: prefix -> path which are interpreted as follows:
// A relative path of the form prefix/tail is to be mapped to
// the absolute path/tail, if that absolute path exists in the file
// system. Given a Mapping object, a relative path is mapped to an
// absolute path by trying each of the individual mappings in order,
// until a valid mapping is found. For instance, for the mapping:
//
//	user   -> /home/user
//	public -> /home/user/public
//	public -> /home/build/public
//
// the relative paths below are mapped to absolute paths as follows:
//
//	user/foo                -> /home/user/foo
//	public/net/rpc/file1.go -> /home/user/public/net/rpc/file1.go
//
// If there is no /home/user/public/net/rpc/file2.go, the next public
// mapping entry is used to map the relative path to:
//
//	public/net/rpc/file2.go -> /home/build/public/net/rpc/file2.go
//
// (assuming that file exists).
//
// Each individual mapping also has a RWValue associated with it that
// may be used to store mapping-specific information. See the Iterate
// method.
//
type Mapping struct {
	list     []mapping
	prefixes []string // lazily computed from list
}

type mapping struct {
	prefix, path string
	value        *RWValue
}

// Init initializes the Mapping from a list of paths.
// Empty paths are ignored; relative paths are assumed to be relative to
// the current working directory and converted to absolute paths.
// For each path of the form:
//
//	dirname/localname
//
// a mapping
//
//	localname -> path
//
// is added to the Mapping object, in the order of occurrence.
// For instance, under Unix, the argument:
//
//	/home/user:/home/build/public
//
// leads to the following mapping:
//
//	user   -> /home/user
//	public -> /home/build/public
//
func (m *Mapping) Init(paths []string) {
	pathlist := canonicalizePaths(paths, nil)
	list := make([]mapping, len(pathlist))

	// create mapping list
	for i, path := range pathlist {
		_, prefix := filepath.Split(path)
		list[i] = mapping{prefix, path, new(RWValue)}
	}

	m.list = list
}

// IsEmpty returns true if there are no mappings specified.
func (m *Mapping) IsEmpty() bool { return len(m.list) == 0 }

// PrefixList returns a list of all prefixes, with duplicates removed.
// For instance, for the mapping:
//
//	user   -> /home/user
//	public -> /home/user/public
//	public -> /home/build/public
//
// the prefix list is:
//
//	user, public
//
func (m *Mapping) PrefixList() []string {
	// compute the list lazily
	if m.prefixes == nil {
		list := make([]string, len(m.list))

		// populate list
		for i, e := range m.list {
			list[i] = e.prefix
		}

		// sort the list and remove duplicate entries
		sort.Strings(list)
		i := 0
		prev := ""
		for _, path := range list {
			if path != prev {
				list[i] = path
				i++
				prev = path
			}
		}

		m.prefixes = list[0:i]
	}

	return m.prefixes
}

// Fprint prints the mapping.
func (m *Mapping) Fprint(w io.Writer) {
	for _, e := range m.list {
		fmt.Fprintf(w, "\t%s -> %s\n", e.prefix, e.path)
	}
}

func splitFirst(path string) (head, tail string) {
	i := strings.Index(path, string(filepath.Separator))
	if i > 0 {
		// 0 < i < len(path)
		return path[0:i], path[i+1:]
	}
	return "", path
}

// ToAbsolute maps a slash-separated relative path to an absolute filesystem
// path using the Mapping specified by the receiver. If the path cannot
// be mapped, the empty string is returned.
//
func (m *Mapping) ToAbsolute(spath string) string {
	fpath := filepath.FromSlash(spath)
	prefix, tail := splitFirst(fpath)
	for _, e := range m.list {
		switch {
		case e.prefix == prefix:
			// use tail
		case e.prefix == "":
			tail = fpath
		default:
			continue // no match
		}
		abspath := filepath.Join(e.path, tail)
		if _, err := fs.Stat(abspath); err == nil {
			return abspath
		}
	}

	return "" // no match
}

// ToRelative maps an absolute filesystem path to a relative slash-separated
// path using the Mapping specified by the receiver. If the path cannot
// be mapped, the empty string is returned.
//
func (m *Mapping) ToRelative(fpath string) string {
	for _, e := range m.list {
		if strings.HasPrefix(fpath, e.path) {
			spath := filepath.ToSlash(fpath)
			// /absolute/prefix/foo -> prefix/foo
			return path.Join(e.prefix, spath[len(e.path):]) // Join will remove a trailing '/'
		}
	}
	return "" // no match
}

// Iterate calls f for each path and RWValue in the mapping (in uspecified order)
// until f returns false.
//
func (m *Mapping) Iterate(f func(path string, value *RWValue) bool) {
	for _, e := range m.list {
		if !f(e.path, e.value) {
			return
		}
	}
}
<commit_msg>godoc: fix ToAbsolute mapping<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file implements the Mapping data structure.

package main

import (
	"fmt"
	"io"
	"path"
	"path/filepath"
	"sort"
	"strings"
)

// A Mapping object maps relative paths (e.g. from URLs)
// to absolute paths (of the file system) and vice versa.
//
// A Mapping object consists of a list of individual mappings
// of the form: prefix -> path which are interpreted as follows:
// A relative path of the form prefix/tail is to be mapped to
// the absolute path/tail, if that absolute path exists in the file
// system. Given a Mapping object, a relative path is mapped to an
// absolute path by trying each of the individual mappings in order,
// until a valid mapping is found. For instance, for the mapping:
//
//	user   -> /home/user
//	public -> /home/user/public
//	public -> /home/build/public
//
// the relative paths below are mapped to absolute paths as follows:
//
//	user/foo                -> /home/user/foo
//	public/net/rpc/file1.go -> /home/user/public/net/rpc/file1.go
//
// If there is no /home/user/public/net/rpc/file2.go, the next public
// mapping entry is used to map the relative path to:
//
//	public/net/rpc/file2.go -> /home/build/public/net/rpc/file2.go
//
// (assuming that file exists).
//
// Each individual mapping also has a RWValue associated with it that
// may be used to store mapping-specific information. See the Iterate
// method.
//
type Mapping struct {
	list     []mapping
	prefixes []string // lazily computed from list
}

type mapping struct {
	prefix, path string
	value        *RWValue
}

// Init initializes the Mapping from a list of paths.
// Empty paths are ignored; relative paths are assumed to be relative to
// the current working directory and converted to absolute paths.
// For each path of the form:
//
//	dirname/localname
//
// a mapping
//
//	localname -> path
//
// is added to the Mapping object, in the order of occurrence.
// For instance, under Unix, the argument:
//
//	/home/user:/home/build/public
//
// leads to the following mapping:
//
//	user   -> /home/user
//	public -> /home/build/public
//
func (m *Mapping) Init(paths []string) {
	pathlist := canonicalizePaths(paths, nil)
	list := make([]mapping, len(pathlist))

	// create mapping list
	for i, path := range pathlist {
		_, prefix := filepath.Split(path)
		list[i] = mapping{prefix, path, new(RWValue)}
	}

	m.list = list
}

// IsEmpty returns true if there are no mappings specified.
func (m *Mapping) IsEmpty() bool { return len(m.list) == 0 }

// PrefixList returns a list of all prefixes, with duplicates removed.
// For instance, for the mapping:
//
//	user   -> /home/user
//	public -> /home/user/public
//	public -> /home/build/public
//
// the prefix list is:
//
//	user, public
//
func (m *Mapping) PrefixList() []string {
	// compute the list lazily
	if m.prefixes == nil {
		list := make([]string, len(m.list))

		// populate list
		for i, e := range m.list {
			list[i] = e.prefix
		}

		// sort the list and remove duplicate entries
		sort.Strings(list)
		i := 0
		prev := ""
		for _, path := range list {
			if path != prev {
				list[i] = path
				i++
				prev = path
			}
		}

		m.prefixes = list[0:i]
	}

	return m.prefixes
}

// Fprint prints the mapping.
func (m *Mapping) Fprint(w io.Writer) {
	for _, e := range m.list {
		fmt.Fprintf(w, "\t%s -> %s\n", e.prefix, e.path)
	}
}

// splitFirst splits a path at the first path separator and returns
// the path's head (the top-most directory specified by the path) and
// its tail (the rest of the path). If there is no path separator,
// splitFirst returns path as head, and the the empty string as tail.
// Specifically, splitFirst("foo") == splitFirst("foo/").
//
func splitFirst(path string) (head, tail string) {
	if i := strings.Index(path, string(filepath.Separator)); i > 0 {
		// 0 < i < len(path)
		return path[0:i], path[i+1:]
	}
	return path, ""
}

// ToAbsolute maps a slash-separated relative path to an absolute filesystem
// path using the Mapping specified by the receiver. If the path cannot
// be mapped, the empty string is returned.
//
func (m *Mapping) ToAbsolute(spath string) string {
	fpath := filepath.FromSlash(spath)
	prefix, tail := splitFirst(fpath)
	for _, e := range m.list {
		if e.prefix == prefix {
			// found potential mapping
			abspath := filepath.Join(e.path, tail)
			if _, err := fs.Stat(abspath); err == nil {
				return abspath
			}
		}
	}
	return "" // no match
}

// ToRelative maps an absolute filesystem path to a relative slash-separated
// path using the Mapping specified by the receiver. If the path cannot
// be mapped, the empty string is returned.
//
func (m *Mapping) ToRelative(fpath string) string {
	for _, e := range m.list {
		if strings.HasPrefix(fpath, e.path) {
			spath := filepath.ToSlash(fpath)
			// /absolute/prefix/foo -> prefix/foo
			return path.Join(e.prefix, spath[len(e.path):]) // Join will remove a trailing '/'
		}
	}
	return "" // no match
}

// Iterate calls f for each path and RWValue in the mapping (in uspecified order)
// until f returns false.
//
func (m *Mapping) Iterate(f func(path string, value *RWValue) bool) {
	for _, e := range m.list {
		if !f(e.path, e.value) {
			return
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2021 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 (
	"bytes"
	"fmt"
	"internal/testenv"
	"os"
	"os/exec"
	"path/filepath"
	"testing"
)

// Issues 43830, 46295
func TestCGOLTO(t *testing.T) {
	testenv.MustHaveCGO(t)
	testenv.MustHaveGoBuild(t)

	t.Parallel()

	for _, cc := range []string{"gcc", "clang"} {
		for test := 0; test < 2; test++ {
			t.Run(fmt.Sprintf("%s-%d", cc, test), func(t *testing.T) {
				testCGOLTO(t, cc, test)
			})
		}
	}
}

const test1_main = `
package main

/*
extern int myadd(int, int);
int c_add(int a, int b) {
	return myadd(a, b);
}
*/
import "C"

func main() {
	println(C.c_add(1, 2))
}
`

const test1_add = `
package main

import "C"

/* test */

//export myadd
func myadd(a C.int, b C.int) C.int {
	return a + b
}
`

const test2_main = `
package main

import "fmt"

/*
#include <stdio.h>

void hello(void) {
  printf("hello\n");
}
*/
import "C"

func main() {
	hello := C.hello
	fmt.Printf("%v\n", hello)
}
`

func testCGOLTO(t *testing.T, cc string, test int) {
	t.Parallel()

	if _, err := exec.LookPath(cc); err != nil {
		t.Skipf("no %s compiler", cc)
	}

	dir := t.TempDir()

	writeTempFile := func(name, contents string) {
		if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0644); err != nil {
			t.Fatal(err)
		}
	}

	writeTempFile("go.mod", "module cgolto\n")

	switch test {
	case 0:
		writeTempFile("main.go", test1_main)
		writeTempFile("add.go", test1_add)
	case 1:
		writeTempFile("main.go", test2_main)
	default:
		t.Fatalf("bad case %d", test)
	}

	cmd := exec.Command(testenv.GoToolPath(t), "build")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(),
		"CC="+cc,
		"CGO_CFLAGS=-flto",
	)

	t.Log("go build")
	out, err := cmd.CombinedOutput()
	t.Logf("%s", out)

	if err != nil {
		t.Logf("go build failed: %v", err)

		// Error messages we've seen indicating that LTO is not supported.
		var noLTO = []string{
			`unrecognized command line option "-flto"`,
			"unable to pass LLVM bit-code files to linker",
			"file not recognized: File format not recognized",
			"LTO support has not been enabled",
		}
		for _, msg := range noLTO {
			if bytes.Contains(out, []byte(msg)) {
				t.Skipf("C compiler %v does not support LTO", cc)
			}
		}

		t.Error("failed")
	}
}
<commit_msg>cmd/link: recognize clang linker error in testCGOLTO<commit_after>// Copyright 2021 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 (
	"bytes"
	"fmt"
	"internal/testenv"
	"os"
	"os/exec"
	"path/filepath"
	"testing"
)

// Issues 43830, 46295
func TestCGOLTO(t *testing.T) {
	testenv.MustHaveCGO(t)
	testenv.MustHaveGoBuild(t)

	t.Parallel()

	for _, cc := range []string{"gcc", "clang"} {
		for test := 0; test < 2; test++ {
			t.Run(fmt.Sprintf("%s-%d", cc, test), func(t *testing.T) {
				testCGOLTO(t, cc, test)
			})
		}
	}
}

const test1_main = `
package main

/*
extern int myadd(int, int);
int c_add(int a, int b) {
	return myadd(a, b);
}
*/
import "C"

func main() {
	println(C.c_add(1, 2))
}
`

const test1_add = `
package main

import "C"

/* test */

//export myadd
func myadd(a C.int, b C.int) C.int {
	return a + b
}
`

const test2_main = `
package main

import "fmt"

/*
#include <stdio.h>

void hello(void) {
  printf("hello\n");
}
*/
import "C"

func main() {
	hello := C.hello
	fmt.Printf("%v\n", hello)
}
`

func testCGOLTO(t *testing.T, cc string, test int) {
	t.Parallel()

	if _, err := exec.LookPath(cc); err != nil {
		t.Skipf("no %s compiler", cc)
	}

	dir := t.TempDir()

	writeTempFile := func(name, contents string) {
		if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0644); err != nil {
			t.Fatal(err)
		}
	}

	writeTempFile("go.mod", "module cgolto\n")

	switch test {
	case 0:
		writeTempFile("main.go", test1_main)
		writeTempFile("add.go", test1_add)
	case 1:
		writeTempFile("main.go", test2_main)
	default:
		t.Fatalf("bad case %d", test)
	}

	cmd := exec.Command(testenv.GoToolPath(t), "build")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(),
		"CC="+cc,
		"CGO_CFLAGS=-flto",
	)

	t.Log("go build")
	out, err := cmd.CombinedOutput()
	t.Logf("%s", out)

	if err != nil {
		t.Logf("go build failed: %v", err)

		// Error messages we've seen indicating that LTO is not supported.
		// These errors come from GCC or clang, not Go.
		var noLTO = []string{
			`unrecognized command line option "-flto"`,
			"unable to pass LLVM bit-code files to linker",
			"file not recognized: File format not recognized",
			"LTO support has not been enabled",
			"linker command failed with exit code",
			"gcc: can't load library",
		}
		for _, msg := range noLTO {
			if bytes.Contains(out, []byte(msg)) {
				t.Skipf("C compiler %v does not support LTO", cc)
			}
		}

		t.Error("failed")
	}
}
<|endoftext|>


<commit_before>// Package violetear - HTTP router
//
// Basic example:
//
//  package main
//
//  import (
//     "fmt"
//     "github.com/nbari/violetear"
//     "log"
//     "net/http"
//  )
//
//  func catchAll(w http.ResponseWriter, r *http.Request) {
//      fmt.Fprintf(w, r.URL.Path[1:])
//  }
//
//  func helloWorld(w http.ResponseWriter, r *http.Request) {
//      fmt.Fprintf(w, r.URL.Path[1:])
//  }
//
//  func handleUUID(w http.ResponseWriter, r *http.Request) {
//      fmt.Fprintf(w, r.URL.Path[1:])
//  }
//
//  func main() {
//      router := violetear.New()
//      router.LogRequests = true
//      router.RequestID = "REQUEST_LOG_ID"
//
//      router.AddRegex(":uuid", `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`)
//
//      router.HandleFunc("*", catchAll)
//      router.HandleFunc("/hello/", helloWorld, "GET,HEAD")
//      router.HandleFunc("/root/:uuid/item", handleUUID, "POST,PUT")
//
//      log.Fatal(http.ListenAndServe(":8080", router))
//  }
//
package violetear

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"regexp"
	"strings"
	"time"
)

type key int

// ParamsKey used for the context
const (
	ParamsKey     key = 0
	versionHeader     = "application/vnd."
)

// Params string/interface map used with context
type Params map[string]interface{}

// Router struct
type Router struct {
	// Routes to be matched
	routes *Trie

	// dynamicRoutes map of dynamic routes and regular expressions
	dynamicRoutes dynamicSet

	// LogRequests yes or no
	LogRequests bool

	// NotFoundHandler configurable http.Handler which is called when no matching
	// route is found. If it is not set, http.NotFound is used.
	NotFoundHandler http.Handler

	// NotAllowedHandler configurable http.Handler which is called when method not allowed.
	NotAllowedHandler http.Handler

	// PanicHandler function to handle panics.
	PanicHandler http.HandlerFunc

	// RequestID name of the header to use or create.
	RequestID string

	// Verbose
	Verbose bool
}

var splitPathRx = regexp.MustCompile(`[^/ ]+`)

// New returns a new initialized router.
func New() *Router {
	return &Router{
		routes:        NewTrie(),
		dynamicRoutes: make(dynamicSet),
		Verbose:       true,
	}
}

// Handle registers the handler for the given pattern (path, http.Handler, methods).
func (v *Router) Handle(path string, handler http.Handler, httpMethods ...string) error {
	var version string
	if i := strings.Index(path, "#"); i != -1 {
		version = path[i+1:]
		path = path[:i]
	}
	pathParts := v.splitPath(path)

	// search for dynamic routes
	for _, p := range pathParts {
		if strings.HasPrefix(p, ":") {
			if _, ok := v.dynamicRoutes[p]; !ok {
				return fmt.Errorf("[%s] not found, need to add it using AddRegex(\"%s\", `your regex`)", p, p)
			}
		}
	}

	// if no methods, accept ALL
	methods := "ALL"
	if len(httpMethods) > 0 && len(strings.TrimSpace(httpMethods[0])) > 0 {
		methods = httpMethods[0]
	}

	if v.Verbose {
		log.Printf("Adding path: %s [%s] %s", path, methods, version)
	}

	if err := v.routes.Set(pathParts, handler, methods, version); err != nil {
		return err
	}
	return nil
}

// HandleFunc add a route to the router (path, http.HandlerFunc, methods)
func (v *Router) HandleFunc(path string, handler http.HandlerFunc, httpMethods ...string) error {
	return v.Handle(path, handler, httpMethods...)
}

// AddRegex adds a ":named" regular expression to the dynamicRoutes
func (v *Router) AddRegex(name, regex string) error {
	return v.dynamicRoutes.Set(name, regex)
}

// MethodNotAllowed default handler for 405
func (v *Router) MethodNotAllowed() http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w,
			http.StatusText(http.StatusMethodNotAllowed),
			http.StatusMethodNotAllowed,
		)
	})
}

// ServeHTTP dispatches the handler registered in the matched path
func (v *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	start := time.Now()
	lw := NewResponseWriter(w)
	params := make(Params)

	// panic handler
	defer func() {
		if err := recover(); err != nil {
			if v.PanicHandler != nil {
				v.PanicHandler(w, r)
			} else {
				http.Error(w, http.StatusText(500), http.StatusInternalServerError)
			}
		}
	}()

	// fill the params map
	setParam := func(k, v string) {
		if param, ok := params[k]; ok {
			switch param.(type) {
			case string:
				param = []string{param.(string), v}
			case []string:
				param = append(param.([]string), v)
			}
			params[k] = param
		} else {
			params[k] = v
		}
	}

	// set version based on the value of "Accept: application/vnd.*"
	version := r.Header.Get("Accept")
	if i := strings.LastIndex(version, versionHeader); i != -1 {
		version = version[len(versionHeader)+i:]
	} else {
		version = ""
	}

	// _ path never empty, defaults to ("/")
	node, path, leaf, _ := v.routes.Get(v.splitPath(r.URL.Path), version)

	// checkMethod check if method is allowed or not
	checkMethod := func(node *Trie, method string) http.Handler {
		if h, ok := node.Handler[method]; ok {
			return h
		}
		if h, ok := node.Handler["ALL"]; ok {
			return h
		}
		if v.NotAllowedHandler != nil {
			return v.NotAllowedHandler
		}
		return v.MethodNotAllowed()
	}

	var match func(node *Trie, path []string, leaf bool) http.Handler

	// match find a handler for the request
	match = func(node *Trie, path []string, leaf bool) http.Handler {
		catchall := false
		if len(node.Handler) > 0 && leaf {
			return checkMethod(node, r.Method)
		} else if node.HasRegex {
			for _, n := range node.Node {
				if strings.HasPrefix(n.path, ":") {
					rx := v.dynamicRoutes[n.path]
					if rx.MatchString(path[0]) {
						// add param to context
						setParam(n.path, path[0])
						path[0] = n.path
						node, path, leaf, _ := node.Get(path, version)
						return match(node, path, leaf)
					}
				}
			}
			if node.HasCatchall {
				catchall = true
			}
		} else if node.HasCatchall {
			catchall = true
		}
		if catchall {
			for _, n := range node.Node {
				if n.path == "*" {
					// add "*" to context
					setParam("*", path[0])
					return checkMethod(n, r.Method)
				}
			}
		}
		// NotFound
		if v.NotFoundHandler != nil {
			return v.NotFoundHandler
		}
		return http.NotFoundHandler()
	}

	// Request-ID
	if v.RequestID != "" {
		if rid := r.Header.Get(v.RequestID); rid != "" {
			lw.Header().Set(v.RequestID, rid)
		}
	}

	//h http.Handler
	h := match(node, path, leaf)

	// dispatch request
	h.ServeHTTP(lw, r.WithContext(context.WithValue(r.Context(), ParamsKey, params)))

	if v.LogRequests {
		log.Printf("%s [%s] %d %d %v %s",
			r.RemoteAddr,
			r.URL,
			lw.Status(),
			lw.Size(),
			time.Since(start),
			lw.Header().Get(v.RequestID))
	}
	return
}

// splitPath returns an slice of the path
func (v *Router) splitPath(p string) []string {
	pathParts := splitPathRx.FindAllString(p, -1)

	// root (empty slice)
	if len(pathParts) == 0 {
		pathParts = append(pathParts, "/")
	}

	return pathParts
}
<commit_msg>replace \"%s\" with %q add comments 	modified:   violetear.go<commit_after>// Package violetear - HTTP router
//
// Basic example:
//
//  package main
//
//  import (
//     "fmt"
//     "github.com/nbari/violetear"
//     "log"
//     "net/http"
//  )
//
//  func catchAll(w http.ResponseWriter, r *http.Request) {
//      fmt.Fprintf(w, r.URL.Path[1:])
//  }
//
//  func helloWorld(w http.ResponseWriter, r *http.Request) {
//      fmt.Fprintf(w, r.URL.Path[1:])
//  }
//
//  func handleUUID(w http.ResponseWriter, r *http.Request) {
//      fmt.Fprintf(w, r.URL.Path[1:])
//  }
//
//  func main() {
//      router := violetear.New()
//      router.LogRequests = true
//      router.RequestID = "REQUEST_LOG_ID"
//
//      router.AddRegex(":uuid", `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`)
//
//      router.HandleFunc("*", catchAll)
//      router.HandleFunc("/hello/", helloWorld, "GET,HEAD")
//      router.HandleFunc("/root/:uuid/item", handleUUID, "POST,PUT")
//
//      log.Fatal(http.ListenAndServe(":8080", router))
//  }
//
package violetear

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"regexp"
	"strings"
	"time"
)

// key int is unexported to prevent collisions with context keys defined in
// other packages.
type key int

// ParamsKey used for the context
const (
	ParamsKey     key = 0
	versionHeader     = "application/vnd."
)

// Params string/interface map used with context
type Params map[string]interface{}

// Router struct
type Router struct {
	// Routes to be matched
	routes *Trie

	// dynamicRoutes map of dynamic routes and regular expressions
	dynamicRoutes dynamicSet

	// LogRequests yes or no
	LogRequests bool

	// NotFoundHandler configurable http.Handler which is called when no matching
	// route is found. If it is not set, http.NotFound is used.
	NotFoundHandler http.Handler

	// NotAllowedHandler configurable http.Handler which is called when method not allowed.
	NotAllowedHandler http.Handler

	// PanicHandler function to handle panics.
	PanicHandler http.HandlerFunc

	// RequestID name of the header to use or create.
	RequestID string

	// Verbose
	Verbose bool
}

var splitPathRx = regexp.MustCompile(`[^/ ]+`)

// New returns a new initialized router.
func New() *Router {
	return &Router{
		routes:        NewTrie(),
		dynamicRoutes: make(dynamicSet),
		Verbose:       true,
	}
}

// Handle registers the handler for the given pattern (path, http.Handler, methods).
func (v *Router) Handle(path string, handler http.Handler, httpMethods ...string) error {
	var version string
	if i := strings.Index(path, "#"); i != -1 {
		version = path[i+1:]
		path = path[:i]
	}
	pathParts := v.splitPath(path)

	// search for dynamic routes
	for _, p := range pathParts {
		if strings.HasPrefix(p, ":") {
			if _, ok := v.dynamicRoutes[p]; !ok {
				return fmt.Errorf("[%s] not found, need to add it using AddRegex(%q, `your regex`)", p, p)
			}
		}
	}

	// if no methods, accept ALL
	methods := "ALL"
	if len(httpMethods) > 0 && len(strings.TrimSpace(httpMethods[0])) > 0 {
		methods = httpMethods[0]
	}

	if v.Verbose {
		log.Printf("Adding path: %s [%s] %s", path, methods, version)
	}

	if err := v.routes.Set(pathParts, handler, methods, version); err != nil {
		return err
	}
	return nil
}

// HandleFunc add a route to the router (path, http.HandlerFunc, methods)
func (v *Router) HandleFunc(path string, handler http.HandlerFunc, httpMethods ...string) error {
	return v.Handle(path, handler, httpMethods...)
}

// AddRegex adds a ":named" regular expression to the dynamicRoutes
func (v *Router) AddRegex(name, regex string) error {
	return v.dynamicRoutes.Set(name, regex)
}

// MethodNotAllowed default handler for 405
func (v *Router) MethodNotAllowed() http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w,
			http.StatusText(http.StatusMethodNotAllowed),
			http.StatusMethodNotAllowed,
		)
	})
}

// ServeHTTP dispatches the handler registered in the matched path
func (v *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	start := time.Now()
	lw := NewResponseWriter(w)
	params := make(Params)

	// panic handler
	defer func() {
		if err := recover(); err != nil {
			if v.PanicHandler != nil {
				v.PanicHandler(w, r)
			} else {
				http.Error(w, http.StatusText(500), http.StatusInternalServerError)
			}
		}
	}()

	// fill the params map
	setParam := func(k, v string) {
		if param, ok := params[k]; ok {
			switch param.(type) {
			case string:
				param = []string{param.(string), v}
			case []string:
				param = append(param.([]string), v)
			}
			params[k] = param
		} else {
			params[k] = v
		}
	}

	// set version based on the value of "Accept: application/vnd.*"
	version := r.Header.Get("Accept")
	if i := strings.LastIndex(version, versionHeader); i != -1 {
		version = version[len(versionHeader)+i:]
	} else {
		version = ""
	}

	// _ path never empty, defaults to ("/")
	node, path, leaf, _ := v.routes.Get(v.splitPath(r.URL.Path), version)

	// checkMethod check if method is allowed or not
	checkMethod := func(node *Trie, method string) http.Handler {
		if h, ok := node.Handler[method]; ok {
			return h
		}
		if h, ok := node.Handler["ALL"]; ok {
			return h
		}
		if v.NotAllowedHandler != nil {
			return v.NotAllowedHandler
		}
		return v.MethodNotAllowed()
	}

	var match func(node *Trie, path []string, leaf bool) http.Handler

	// match find a handler for the request
	match = func(node *Trie, path []string, leaf bool) http.Handler {
		catchall := false
		if len(node.Handler) > 0 && leaf {
			return checkMethod(node, r.Method)
		} else if node.HasRegex {
			for _, n := range node.Node {
				if strings.HasPrefix(n.path, ":") {
					rx := v.dynamicRoutes[n.path]
					if rx.MatchString(path[0]) {
						// add param to context
						setParam(n.path, path[0])
						path[0] = n.path
						node, path, leaf, _ := node.Get(path, version)
						return match(node, path, leaf)
					}
				}
			}
			if node.HasCatchall {
				catchall = true
			}
		} else if node.HasCatchall {
			catchall = true
		}
		if catchall {
			for _, n := range node.Node {
				if n.path == "*" {
					// add "*" to context
					setParam("*", path[0])
					return checkMethod(n, r.Method)
				}
			}
		}
		// NotFound
		if v.NotFoundHandler != nil {
			return v.NotFoundHandler
		}
		return http.NotFoundHandler()
	}

	// Request-ID
	if v.RequestID != "" {
		if rid := r.Header.Get(v.RequestID); rid != "" {
			lw.Header().Set(v.RequestID, rid)
		}
	}

	// h http.Handler
	h := match(node, path, leaf)

	// dispatch request
	h.ServeHTTP(lw, r.WithContext(context.WithValue(r.Context(), ParamsKey, params)))

	if v.LogRequests {
		log.Printf("%s [%s] %d %d %v %s",
			r.RemoteAddr,
			r.URL,
			lw.Status(),
			lw.Size(),
			time.Since(start),
			lw.Header().Get(v.RequestID))
	}
	return
}

// splitPath returns an slice of the path
func (v *Router) splitPath(p string) []string {
	pathParts := splitPathRx.FindAllString(p, -1)

	// root (empty slice)
	if len(pathParts) == 0 {
		pathParts = append(pathParts, "/")
	}

	return pathParts
}
<|endoftext|>