<commit_before>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package cmd

import (
	"fmt"
	"io"

	"k8s.io/apiserver/pkg/util/flag"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/kubernetes/pkg/kubectl/cmd/auth"
	cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config"
	"k8s.io/kubernetes/pkg/kubectl/cmd/rollout"
	"k8s.io/kubernetes/pkg/kubectl/cmd/set"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/util/i18n"

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

const (
	bashCompletionFunc = `# call kubectl get $1,
__kubectl_override_flag_list=(kubeconfig cluster user context namespace server)
__kubectl_override_flags()
{
    local ${__kubectl_override_flag_list[*]} two_word_of of
    for w in "${words[@]}"; do
        if [ -n "${two_word_of}" ]; then
            eval "${two_word_of}=\"--${two_word_of}=\${w}\""
            two_word_of=
            continue
        fi
        for of in "${__kubectl_override_flag_list[@]}"; do
            case "${w}" in
                --${of}=*)
                    eval "${of}=\"${w}\""
                    ;;
                --${of})
                    two_word_of="${of}"
                    ;;
            esac
        done
        if [ "${w}" == "--all-namespaces" ]; then
            namespace="--all-namespaces"
        fi
    done
    for of in "${__kubectl_override_flag_list[@]}"; do
        if eval "test -n \"\$${of}\""; then
            eval "echo \${${of}}"
        fi
    done
}

__kubectl_get_namespaces()
{
    local template kubectl_out
    template="{{ range .items  }}{{ .metadata.name }} {{ end }}"
    if kubectl_out=$(kubectl get -o template --template="${template}" namespace 2>/dev/null); then
        COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
    fi
}

__kubectl_config_get_contexts()
{
    __kubectl_parse_config "contexts"
}

__kubectl_config_get_clusters()
{
    __kubectl_parse_config "clusters"
}

__kubectl_config_get_users()
{
    __kubectl_parse_config "users"
}

# $1 has to be "contexts", "clusters" or "users"
__kubectl_config_get()
{
    local template kubectl_out
    template="{{ range .$1  }}{{ .name }} {{ end }}"
    if kubectl_out=$(kubectl config $(__kubectl_override_flags) -o template --template="${template}" view 2>/dev/null); then
        COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
    fi
}

__kubectl_parse_get()
{
    local template
    template="{{ range .items  }}{{ .metadata.name }} {{ end }}"
    local kubectl_out
    if kubectl_out=$(kubectl get $(__kubectl_override_flags) -o template --template="${template}" "$1" 2>/dev/null); then
        COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
    fi
}

__kubectl_get_resource()
{
    if [[ ${#nouns[@]} -eq 0 ]]; then
        return 1
    fi
    __kubectl_parse_get "${nouns[${#nouns[@]} -1]}"
}

__kubectl_get_resource_pod()
{
    __kubectl_parse_get "pod"
}

__kubectl_get_resource_rc()
{
    __kubectl_parse_get "rc"
}

__kubectl_get_resource_node()
{
    __kubectl_parse_get "node"
}

# $1 is the name of the pod we want to get the list of containers inside
__kubectl_get_containers()
{
    local template
    template="{{ range .spec.containers  }}{{ .name }} {{ end }}"
    __debug "${FUNCNAME} nouns are ${nouns[*]}"

    local len="${#nouns[@]}"
    if [[ ${len} -ne 1 ]]; then
        return
    fi
    local last=${nouns[${len} -1]}
    local kubectl_out
    if kubectl_out=$(kubectl get $(__kubectl_override_flags) -o template --template="${template}" pods "${last}" 2>/dev/null); then
        COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
    fi
}

# Require both a pod and a container to be specified
__kubectl_require_pod_and_container()
{
    if [[ ${#nouns[@]} -eq 0 ]]; then
        __kubectl_parse_get pods
        return 0
    fi;
    __kubectl_get_containers
    return 0
}

__custom_func() {
    case ${last_command} in
        kubectl_get | kubectl_describe | kubectl_delete | kubectl_label | kubectl_stop | kubectl_edit | kubectl_patch |\
        kubectl_annotate | kubectl_expose | kubectl_scale | kubectl_autoscale | kubectl_taint | kubectl_rollout_*)
            __kubectl_get_resource
            return
            ;;
        kubectl_logs | kubectl_attach)
            __kubectl_require_pod_and_container
            return
            ;;
        kubectl_exec | kubectl_port-forward | kubectl_top_pod)
            __kubectl_get_resource_pod
            return
            ;;
        kubectl_rolling-update)
            __kubectl_get_resource_rc
            return
            ;;
        kubectl_cordon | kubectl_uncordon | kubectl_drain | kubectl_top_node)
            __kubectl_get_resource_node
            return
            ;;
        kubectl_config_use-context)
            __kubectl_config_get_contexts
            return
            ;;
        *)
            ;;
    esac
}
`

	// If you add a resource to this list, please also take a look at pkg/kubectl/kubectl.go
	// and add a short forms entry in expandResourceShortcut() when appropriate.
	// TODO: This should be populated using the discovery information from apiserver.
	validResources = `Valid resource types include:

    * all
    * certificatesigningrequests (aka 'csr')
    * clusterrolebindings
    * clusterroles
    * clusters (valid only for federation apiservers)
    * componentstatuses (aka 'cs')
    * configmaps (aka 'cm')
    * cronjobs
    * daemonsets (aka 'ds')
    * deployments (aka 'deploy')
    * endpoints (aka 'ep')
    * events (aka 'ev')
    * horizontalpodautoscalers (aka 'hpa')
    * ingresses (aka 'ing')
    * jobs
    * limitranges (aka 'limits')
    * namespaces (aka 'ns')
    * networkpolicies (aka 'netpol')
    * nodes (aka 'no')
    * persistentvolumeclaims (aka 'pvc')
    * persistentvolumes (aka 'pv')
    * poddisruptionbudgets (aka 'pdb')
    * podpreset
    * pods (aka 'po')
    * podsecuritypolicies (aka 'psp')
    * podtemplates
    * replicasets (aka 'rs')
    * replicationcontrollers (aka 'rc')
    * resourcequotas (aka 'quota')
    * rolebindings
    * roles
    * secrets
    * serviceaccounts (aka 'sa')
    * services (aka 'svc')
    * statefulsets
    * storageclasses
    * thirdpartyresources
    `
)

var (
	bash_completion_flags = map[string]string{
		"namespace": "__kubectl_get_namespaces",
		"context":   "__kubectl_config_get_contexts",
		"cluster":   "__kubectl_config_get_clusters",
		"user":      "__kubectl_config_get_users",
	}
)

// NewKubectlCommand creates the `kubectl` command and its nested children.
func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Command {
	// Parent command to which all subcommands are added.
	cmds := &cobra.Command{
		Use:   "kubectl",
		Short: i18n.T("kubectl controls the Kubernetes cluster manager"),
		Long: templates.LongDesc(`
      kubectl controls the Kubernetes cluster manager.

      Find more information at https://github.com/kubernetes/kubernetes.`),
		Run: runHelp,
		BashCompletionFunction: bashCompletionFunc,
	}

	f.BindFlags(cmds.PersistentFlags())
	f.BindExternalFlags(cmds.PersistentFlags())

	// Sending in 'nil' for the getLanguageFn() results in using
	// the LANG environment variable.
	//
	// TODO: Consider adding a flag or file preference for setting
	// the language, instead of just loading from the LANG env. variable.
	i18n.LoadTranslations("kubectl", nil)

	// From this point and forward we get warnings on flags that contain "_" separators
	cmds.SetGlobalNormalizationFunc(flag.WarnWordSepNormalizeFunc)

	groups := templates.CommandGroups{
		{
			Message: "Basic Commands (Beginner):",
			Commands: []*cobra.Command{
				NewCmdCreate(f, out, err),
				NewCmdExposeService(f, out),
				NewCmdRun(f, in, out, err),
				deprecatedCmd("run-container", NewCmdRun(f, in, out, err)),
				set.NewCmdSet(f, out, err),
			},
		},
		{
			Message: "Basic Commands (Intermediate):",
			Commands: []*cobra.Command{
				NewCmdGet(f, out, err),
				NewCmdExplain(f, out, err),
				NewCmdEdit(f, out, err),
				NewCmdDelete(f, out, err),
			},
		},
		{
			Message: "Deploy Commands:",
			Commands: []*cobra.Command{
				rollout.NewCmdRollout(f, out, err),
				NewCmdRollingUpdate(f, out),
				deprecatedCmd("rollingupdate", NewCmdRollingUpdate(f, out)),
				NewCmdScale(f, out),
				deprecatedCmd("resize", NewCmdScale(f, out)),
				NewCmdAutoscale(f, out),
			},
		},
		{
			Message: "Cluster Management Commands:",
			Commands: []*cobra.Command{
				NewCmdCertificate(f, out),
				NewCmdClusterInfo(f, out),
				deprecatedCmd("clusterinfo", NewCmdClusterInfo(f, out)),
				NewCmdTop(f, out, err),
				NewCmdCordon(f, out),
				NewCmdUncordon(f, out),
				NewCmdDrain(f, out, err),
				NewCmdTaint(f, out),
			},
		},
		{
			Message: "Troubleshooting and Debugging Commands:",
			Commands: []*cobra.Command{
				NewCmdDescribe(f, out, err),
				NewCmdLogs(f, out),
				NewCmdAttach(f, in, out, err),
				NewCmdExec(f, in, out, err),
				NewCmdPortForward(f, out, err),
				NewCmdProxy(f, out),
				NewCmdCp(f, out, err),
				auth.NewCmdAuth(f, out, err),
			},
		},
		{
			Message: "Advanced Commands:",
			Commands: []*cobra.Command{
				NewCmdApply(f, out, err),
				NewCmdPatch(f, out),
				NewCmdReplace(f, out),
				deprecatedCmd("update", NewCmdReplace(f, out)),
				NewCmdConvert(f, out),
			},
		},
		{
			Message: "Settings Commands:",
			Commands: []*cobra.Command{
				NewCmdLabel(f, out),
				NewCmdAnnotate(f, out),
				NewCmdCompletion(out, ""),
			},
		},
	}
	groups.Add(cmds)

	filters := []string{
		"options",
		Deprecated("kubectl", "delete", cmds, NewCmdStop(f, out)),
	}
	templates.ActsAsRootCommand(cmds, filters, groups...)

	for name, completion := range bash_completion_flags {
		if cmds.Flag(name) != nil {
			if cmds.Flag(name).Annotations == nil {
				cmds.Flag(name).Annotations = map[string][]string{}
			}
			cmds.Flag(name).Annotations[cobra.BashCompCustom] = append(
				cmds.Flag(name).Annotations[cobra.BashCompCustom],
				completion,
			)
		}
	}

	cmds.AddCommand(cmdconfig.NewCmdConfig(clientcmd.NewDefaultPathOptions(), out, err))
	cmds.AddCommand(NewCmdPlugin(f, in, out, err))
	cmds.AddCommand(NewCmdVersion(f, out))
	cmds.AddCommand(NewCmdApiVersions(f, out))
	cmds.AddCommand(deprecatedCmd("apiversions", NewCmdApiVersions(f, out)))
	cmds.AddCommand(NewCmdOptions())

	return cmds
}

func runHelp(cmd *cobra.Command, args []string) {
	cmd.Help()
}

func printDeprecationWarning(command, alias string) {
	glog.Warningf("%s is DEPRECATED and will be removed in a future version. Use %s instead.", alias, command)
}

// Create a constructor for a command that redirects to another command, but
// first prints a deprecation message.
func deprecatedCmd(deprecatedVersion string, cmd *cobra.Command) *cobra.Command {
	// Have to be careful here because Cobra automatically extracts the name
	// of the command from the .Use field.
	originalName := cmd.Name()

	cmd.Use = deprecatedVersion
	cmd.Deprecated = fmt.Sprintf("use %q instead", originalName)
	cmd.Hidden = true
	return cmd
}

func Deprecated(baseName, to string, parent, cmd *cobra.Command) string {
	cmd.Long = fmt.Sprintf("Deprecated: This command is deprecated, all its functionalities are covered by \"%s %s\"", baseName, to)
	cmd.Short = fmt.Sprintf("Deprecated: %s", to)
	parent.AddCommand(cmd)
	return cmd.Name()
}
<commit_msg>kubectl: improve docstring on deprecatedCmd<commit_after>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package cmd

import (
	"fmt"
	"io"

	"k8s.io/apiserver/pkg/util/flag"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/kubernetes/pkg/kubectl/cmd/auth"
	cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config"
	"k8s.io/kubernetes/pkg/kubectl/cmd/rollout"
	"k8s.io/kubernetes/pkg/kubectl/cmd/set"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/util/i18n"

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

const (
	bashCompletionFunc = `# call kubectl get $1,
__kubectl_override_flag_list=(kubeconfig cluster user context namespace server)
__kubectl_override_flags()
{
    local ${__kubectl_override_flag_list[*]} two_word_of of
    for w in "${words[@]}"; do
        if [ -n "${two_word_of}" ]; then
            eval "${two_word_of}=\"--${two_word_of}=\${w}\""
            two_word_of=
            continue
        fi
        for of in "${__kubectl_override_flag_list[@]}"; do
            case "${w}" in
                --${of}=*)
                    eval "${of}=\"${w}\""
                    ;;
                --${of})
                    two_word_of="${of}"
                    ;;
            esac
        done
        if [ "${w}" == "--all-namespaces" ]; then
            namespace="--all-namespaces"
        fi
    done
    for of in "${__kubectl_override_flag_list[@]}"; do
        if eval "test -n \"\$${of}\""; then
            eval "echo \${${of}}"
        fi
    done
}

__kubectl_get_namespaces()
{
    local template kubectl_out
    template="{{ range .items  }}{{ .metadata.name }} {{ end }}"
    if kubectl_out=$(kubectl get -o template --template="${template}" namespace 2>/dev/null); then
        COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
    fi
}

__kubectl_config_get_contexts()
{
    __kubectl_parse_config "contexts"
}

__kubectl_config_get_clusters()
{
    __kubectl_parse_config "clusters"
}

__kubectl_config_get_users()
{
    __kubectl_parse_config "users"
}

# $1 has to be "contexts", "clusters" or "users"
__kubectl_config_get()
{
    local template kubectl_out
    template="{{ range .$1  }}{{ .name }} {{ end }}"
    if kubectl_out=$(kubectl config $(__kubectl_override_flags) -o template --template="${template}" view 2>/dev/null); then
        COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
    fi
}

__kubectl_parse_get()
{
    local template
    template="{{ range .items  }}{{ .metadata.name }} {{ end }}"
    local kubectl_out
    if kubectl_out=$(kubectl get $(__kubectl_override_flags) -o template --template="${template}" "$1" 2>/dev/null); then
        COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
    fi
}

__kubectl_get_resource()
{
    if [[ ${#nouns[@]} -eq 0 ]]; then
        return 1
    fi
    __kubectl_parse_get "${nouns[${#nouns[@]} -1]}"
}

__kubectl_get_resource_pod()
{
    __kubectl_parse_get "pod"
}

__kubectl_get_resource_rc()
{
    __kubectl_parse_get "rc"
}

__kubectl_get_resource_node()
{
    __kubectl_parse_get "node"
}

# $1 is the name of the pod we want to get the list of containers inside
__kubectl_get_containers()
{
    local template
    template="{{ range .spec.containers  }}{{ .name }} {{ end }}"
    __debug "${FUNCNAME} nouns are ${nouns[*]}"

    local len="${#nouns[@]}"
    if [[ ${len} -ne 1 ]]; then
        return
    fi
    local last=${nouns[${len} -1]}
    local kubectl_out
    if kubectl_out=$(kubectl get $(__kubectl_override_flags) -o template --template="${template}" pods "${last}" 2>/dev/null); then
        COMPREPLY=( $( compgen -W "${kubectl_out[*]}" -- "$cur" ) )
    fi
}

# Require both a pod and a container to be specified
__kubectl_require_pod_and_container()
{
    if [[ ${#nouns[@]} -eq 0 ]]; then
        __kubectl_parse_get pods
        return 0
    fi;
    __kubectl_get_containers
    return 0
}

__custom_func() {
    case ${last_command} in
        kubectl_get | kubectl_describe | kubectl_delete | kubectl_label | kubectl_stop | kubectl_edit | kubectl_patch |\
        kubectl_annotate | kubectl_expose | kubectl_scale | kubectl_autoscale | kubectl_taint | kubectl_rollout_*)
            __kubectl_get_resource
            return
            ;;
        kubectl_logs | kubectl_attach)
            __kubectl_require_pod_and_container
            return
            ;;
        kubectl_exec | kubectl_port-forward | kubectl_top_pod)
            __kubectl_get_resource_pod
            return
            ;;
        kubectl_rolling-update)
            __kubectl_get_resource_rc
            return
            ;;
        kubectl_cordon | kubectl_uncordon | kubectl_drain | kubectl_top_node)
            __kubectl_get_resource_node
            return
            ;;
        kubectl_config_use-context)
            __kubectl_config_get_contexts
            return
            ;;
        *)
            ;;
    esac
}
`

	// If you add a resource to this list, please also take a look at pkg/kubectl/kubectl.go
	// and add a short forms entry in expandResourceShortcut() when appropriate.
	// TODO: This should be populated using the discovery information from apiserver.
	validResources = `Valid resource types include:

    * all
    * certificatesigningrequests (aka 'csr')
    * clusterrolebindings
    * clusterroles
    * clusters (valid only for federation apiservers)
    * componentstatuses (aka 'cs')
    * configmaps (aka 'cm')
    * cronjobs
    * daemonsets (aka 'ds')
    * deployments (aka 'deploy')
    * endpoints (aka 'ep')
    * events (aka 'ev')
    * horizontalpodautoscalers (aka 'hpa')
    * ingresses (aka 'ing')
    * jobs
    * limitranges (aka 'limits')
    * namespaces (aka 'ns')
    * networkpolicies (aka 'netpol')
    * nodes (aka 'no')
    * persistentvolumeclaims (aka 'pvc')
    * persistentvolumes (aka 'pv')
    * poddisruptionbudgets (aka 'pdb')
    * podpreset
    * pods (aka 'po')
    * podsecuritypolicies (aka 'psp')
    * podtemplates
    * replicasets (aka 'rs')
    * replicationcontrollers (aka 'rc')
    * resourcequotas (aka 'quota')
    * rolebindings
    * roles
    * secrets
    * serviceaccounts (aka 'sa')
    * services (aka 'svc')
    * statefulsets
    * storageclasses
    * thirdpartyresources
    `
)

var (
	bash_completion_flags = map[string]string{
		"namespace": "__kubectl_get_namespaces",
		"context":   "__kubectl_config_get_contexts",
		"cluster":   "__kubectl_config_get_clusters",
		"user":      "__kubectl_config_get_users",
	}
)

// NewKubectlCommand creates the `kubectl` command and its nested children.
func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cobra.Command {
	// Parent command to which all subcommands are added.
	cmds := &cobra.Command{
		Use:   "kubectl",
		Short: i18n.T("kubectl controls the Kubernetes cluster manager"),
		Long: templates.LongDesc(`
      kubectl controls the Kubernetes cluster manager.

      Find more information at https://github.com/kubernetes/kubernetes.`),
		Run: runHelp,
		BashCompletionFunction: bashCompletionFunc,
	}

	f.BindFlags(cmds.PersistentFlags())
	f.BindExternalFlags(cmds.PersistentFlags())

	// Sending in 'nil' for the getLanguageFn() results in using
	// the LANG environment variable.
	//
	// TODO: Consider adding a flag or file preference for setting
	// the language, instead of just loading from the LANG env. variable.
	i18n.LoadTranslations("kubectl", nil)

	// From this point and forward we get warnings on flags that contain "_" separators
	cmds.SetGlobalNormalizationFunc(flag.WarnWordSepNormalizeFunc)

	groups := templates.CommandGroups{
		{
			Message: "Basic Commands (Beginner):",
			Commands: []*cobra.Command{
				NewCmdCreate(f, out, err),
				NewCmdExposeService(f, out),
				NewCmdRun(f, in, out, err),
				deprecatedCmd("run-container", NewCmdRun(f, in, out, err)),
				set.NewCmdSet(f, out, err),
			},
		},
		{
			Message: "Basic Commands (Intermediate):",
			Commands: []*cobra.Command{
				NewCmdGet(f, out, err),
				NewCmdExplain(f, out, err),
				NewCmdEdit(f, out, err),
				NewCmdDelete(f, out, err),
			},
		},
		{
			Message: "Deploy Commands:",
			Commands: []*cobra.Command{
				rollout.NewCmdRollout(f, out, err),
				NewCmdRollingUpdate(f, out),
				deprecatedCmd("rollingupdate", NewCmdRollingUpdate(f, out)),
				NewCmdScale(f, out),
				deprecatedCmd("resize", NewCmdScale(f, out)),
				NewCmdAutoscale(f, out),
			},
		},
		{
			Message: "Cluster Management Commands:",
			Commands: []*cobra.Command{
				NewCmdCertificate(f, out),
				NewCmdClusterInfo(f, out),
				deprecatedCmd("clusterinfo", NewCmdClusterInfo(f, out)),
				NewCmdTop(f, out, err),
				NewCmdCordon(f, out),
				NewCmdUncordon(f, out),
				NewCmdDrain(f, out, err),
				NewCmdTaint(f, out),
			},
		},
		{
			Message: "Troubleshooting and Debugging Commands:",
			Commands: []*cobra.Command{
				NewCmdDescribe(f, out, err),
				NewCmdLogs(f, out),
				NewCmdAttach(f, in, out, err),
				NewCmdExec(f, in, out, err),
				NewCmdPortForward(f, out, err),
				NewCmdProxy(f, out),
				NewCmdCp(f, out, err),
				auth.NewCmdAuth(f, out, err),
			},
		},
		{
			Message: "Advanced Commands:",
			Commands: []*cobra.Command{
				NewCmdApply(f, out, err),
				NewCmdPatch(f, out),
				NewCmdReplace(f, out),
				deprecatedCmd("update", NewCmdReplace(f, out)),
				NewCmdConvert(f, out),
			},
		},
		{
			Message: "Settings Commands:",
			Commands: []*cobra.Command{
				NewCmdLabel(f, out),
				NewCmdAnnotate(f, out),
				NewCmdCompletion(out, ""),
			},
		},
	}
	groups.Add(cmds)

	filters := []string{
		"options",
		Deprecated("kubectl", "delete", cmds, NewCmdStop(f, out)),
	}
	templates.ActsAsRootCommand(cmds, filters, groups...)

	for name, completion := range bash_completion_flags {
		if cmds.Flag(name) != nil {
			if cmds.Flag(name).Annotations == nil {
				cmds.Flag(name).Annotations = map[string][]string{}
			}
			cmds.Flag(name).Annotations[cobra.BashCompCustom] = append(
				cmds.Flag(name).Annotations[cobra.BashCompCustom],
				completion,
			)
		}
	}

	cmds.AddCommand(cmdconfig.NewCmdConfig(clientcmd.NewDefaultPathOptions(), out, err))
	cmds.AddCommand(NewCmdPlugin(f, in, out, err))
	cmds.AddCommand(NewCmdVersion(f, out))
	cmds.AddCommand(NewCmdApiVersions(f, out))
	cmds.AddCommand(deprecatedCmd("apiversions", NewCmdApiVersions(f, out)))
	cmds.AddCommand(NewCmdOptions())

	return cmds
}

func runHelp(cmd *cobra.Command, args []string) {
	cmd.Help()
}

func printDeprecationWarning(command, alias string) {
	glog.Warningf("%s is DEPRECATED and will be removed in a future version. Use %s instead.", alias, command)
}

// deprecatedCmd is intended to be used to create a "wrapper" command around an
// existing command. The wrapper works the same but prints a deprecation
// message before running.
func deprecatedCmd(deprecatedVersion string, cmd *cobra.Command) *cobra.Command {
	// Have to be careful here because Cobra automatically extracts the name
	// of the command from the .Use field.
	originalName := cmd.Name()

	cmd.Use = deprecatedVersion
	cmd.Deprecated = fmt.Sprintf("use %q instead", originalName)
	cmd.Hidden = true
	return cmd
}

func Deprecated(baseName, to string, parent, cmd *cobra.Command) string {
	cmd.Long = fmt.Sprintf("Deprecated: This command is deprecated, all its functionalities are covered by \"%s %s\"", baseName, to)
	cmd.Short = fmt.Sprintf("Deprecated: %s", to)
	parent.AddCommand(cmd)
	return cmd.Name()
}
<|endoftext|>


<commit_before>// Copyright (C) 2014 Constantin Schomburg <me@cschomburg.com>
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package natural

import (
	"bytes"
	"regexp"
	"sort"
	"strings"

	"github.com/sarifsystems/sarif/sarif"
)

var reMatchVars = regexp.MustCompile(`\\\[([\w ]+)\\\]`)

func buildRegexp(field string) string {
	matches := reMatchVars.FindStringSubmatch(field)
	field = matches[1]
	field = strings.Replace(field, " ", "", -1)
	return `(?P<` + field + `>("[^"]*"|[^"].*))`
}

type RegularParser struct {
	rules []*SentenceRule
}

func NewRegularParser() *RegularParser {
	return &RegularParser{
		make([]*SentenceRule, 0),
	}
}

func (p *RegularParser) Parse(s string) (sarif.Message, bool) {
	s = strings.TrimRight(s, ".?! ")
	for _, r := range p.rules {
		if msg, ok := r.Parse(s); ok {
			return msg, ok
		}
	}
	return sarif.Message{}, false
}

func (p *RegularParser) Learn(rule, action string) error {
	r, err := CompileSentenceRule(rule, action)
	if err != nil {
		return err
	}
	p.rules = append(p.rules, r)
	sort.Sort(byPriority(p.rules))
	return nil
}

type SentenceRuleSet map[string]string

func (p *RegularParser) Rules() SentenceRuleSet {
	s := SentenceRuleSet{}
	for _, r := range p.rules {
		s[r.Rule] = r.Action
	}
	return s
}

func (p *RegularParser) Load(s SentenceRuleSet) error {
	for rule, action := range s {
		if err := p.Learn(rule, action); err != nil {
			return err
		}
	}
	return nil
}

type SentenceRule struct {
	Rule     string
	Action   string
	Regexp   *regexp.Regexp
	Priority int
}

type byPriority []*SentenceRule

func (a byPriority) Len() int           { return len(a) }
func (a byPriority) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byPriority) Less(i, j int) bool { return a[i].Priority > a[j].Priority }

var specialBytes = []byte(`\.+*?()|[]{}^$`)

func quoteMeta(s string) string {
	b := make([]byte, 2*len(s))

	j := 0
	for i := 0; i < len(s); i++ {
		if bytes.IndexByte(specialBytes, s[i]) >= 0 {
			b[j] = '\\'
			j++
		}
		b[j] = s[i]
		j++
	}
	return string(b[0:j])
}

func CompileSentenceRule(s, action string) (r *SentenceRule, err error) {
	s = strings.ToLower(strings.TrimRight(s, ".?! "))
	r = &SentenceRule{s, action, nil, 0}
	s = quoteMeta(s)
	s = reMatchVars.ReplaceAllStringFunc(s, buildRegexp)
	s = `^(?i)` + s + `$`

	r.Regexp, err = regexp.Compile(s)
	if err != nil {
		return nil, err
	}

	r.Priority = len(s) - 10*r.Regexp.NumSubexp()
	return r, nil
}

func (r *SentenceRule) Parse(s string) (sarif.Message, bool) {
	msg := sarif.Message{}
	match := r.Regexp.FindStringSubmatch(s)
	if match == nil {
		return msg, false
	}

	msg.Action = r.Action
	p := make(map[string]string)
	for i, field := range r.Regexp.SubexpNames() {
		if field == "" {
			continue
		}
		v := TrimQuotes(match[i])
		if field == "text" {
			msg.Text = strings.TrimSpace(msg.Text + " " + v)
			continue
		}
		if field == "_action" {
			msg.Action = strings.TrimSpace(msg.Action + "/" + v)
			continue
		}
		p[field] = v
	}
	if len(p) > 0 {
		msg.EncodePayload(p)
	}
	return msg, true
}
<commit_msg>Natural: Make regular rules ungreedy.<commit_after>// Copyright (C) 2014 Constantin Schomburg <me@cschomburg.com>
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package natural

import (
	"bytes"
	"regexp"
	"sort"
	"strings"

	"github.com/sarifsystems/sarif/sarif"
)

var reMatchVars = regexp.MustCompile(`\\\[([\w ]+)\\\]`)

func buildRegexp(field string) string {
	matches := reMatchVars.FindStringSubmatch(field)
	field = matches[1]
	field = strings.Replace(field, " ", "", -1)
	return `(?P<` + field + `>("[^"]+"|[^"]+?))`
}

type RegularParser struct {
	rules []*SentenceRule
}

func NewRegularParser() *RegularParser {
	return &RegularParser{
		make([]*SentenceRule, 0),
	}
}

func (p *RegularParser) Parse(s string) (sarif.Message, bool) {
	s = strings.TrimRight(s, ".?! ")
	for _, r := range p.rules {
		if msg, ok := r.Parse(s); ok {
			return msg, ok
		}
	}
	return sarif.Message{}, false
}

func (p *RegularParser) Learn(rule, action string) error {
	r, err := CompileSentenceRule(rule, action)
	if err != nil {
		return err
	}
	p.rules = append(p.rules, r)
	sort.Sort(byPriority(p.rules))
	return nil
}

type SentenceRuleSet map[string]string

func (p *RegularParser) Rules() SentenceRuleSet {
	s := SentenceRuleSet{}
	for _, r := range p.rules {
		s[r.Rule] = r.Action
	}
	return s
}

func (p *RegularParser) Load(s SentenceRuleSet) error {
	for rule, action := range s {
		if err := p.Learn(rule, action); err != nil {
			return err
		}
	}
	return nil
}

type SentenceRule struct {
	Rule     string
	Action   string
	Regexp   *regexp.Regexp
	Priority int
}

type byPriority []*SentenceRule

func (a byPriority) Len() int           { return len(a) }
func (a byPriority) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a byPriority) Less(i, j int) bool { return a[i].Priority > a[j].Priority }

var specialBytes = []byte(`\.+*?()|[]{}^$`)

func quoteMeta(s string) string {
	b := make([]byte, 2*len(s))

	j := 0
	for i := 0; i < len(s); i++ {
		if bytes.IndexByte(specialBytes, s[i]) >= 0 {
			b[j] = '\\'
			j++
		}
		b[j] = s[i]
		j++
	}
	return string(b[0:j])
}

func CompileSentenceRule(s, action string) (r *SentenceRule, err error) {
	s = strings.ToLower(strings.TrimRight(s, ".?! "))
	r = &SentenceRule{s, action, nil, 0}
	s = quoteMeta(s)
	s = reMatchVars.ReplaceAllStringFunc(s, buildRegexp)
	s = `^(?i)` + s + `$`

	r.Regexp, err = regexp.Compile(s)
	if err != nil {
		return nil, err
	}

	r.Priority = len(s) - 10*r.Regexp.NumSubexp()
	return r, nil
}

func (r *SentenceRule) Parse(s string) (sarif.Message, bool) {
	msg := sarif.Message{}
	match := r.Regexp.FindStringSubmatch(s)
	if match == nil {
		return msg, false
	}

	msg.Action = r.Action
	p := make(map[string]string)
	for i, field := range r.Regexp.SubexpNames() {
		if field == "" {
			continue
		}
		v := TrimQuotes(match[i])
		if field == "text" {
			msg.Text = strings.TrimSpace(msg.Text + " " + v)
			continue
		}
		if field == "_action" {
			msg.Action = strings.TrimSpace(msg.Action + "/" + v)
			continue
		}
		p[field] = v
	}
	if len(p) > 0 {
		msg.EncodePayload(p)
	}
	return msg, true
}
<|endoftext|>


<commit_before>// Copyright (c) 2019, the Drone Plugins project authors.
// Please see the AUTHORS file for details. All rights reserved.
// Use of this source code is governed by an Apache 2.0 license that can be
// found in the LICENSE file.

package npm

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"os/exec"
	"os/user"
	"path"
	"strings"

	"github.com/sirupsen/logrus"
)

type (
	// Settings for the Plugin.
	Settings struct {
		Username              string
		Password              string
		Token                 string
		Email                 string
		Registry              string
		Folder                string
		FailOnVersionConflict bool
		Tag                   string
		Access                string
	}

	npmPackage struct {
		Name    string    `json:"name"`
		Version string    `json:"version"`
		Config  npmConfig `json:"publishConfig"`
	}

	npmConfig struct {
		Registry string `json:"registry"`
	}
)

// globalRegistry defines the default NPM registry.
const globalRegistry = "https://registry.npmjs.org/"

func (p *pluginImpl) Validate() error {
	// Check authentication options
	if len(p.settings.Token) == 0 {
		if len(p.settings.Username) == 0 {
			return fmt.Errorf("No username provided")
		}
		if len(p.settings.Email) == 0 {
			return fmt.Errorf("No email address provided")
		}
		if len(p.settings.Password) == 0 {
			return fmt.Errorf("No password provided")
		}

		logrus.WithFields(logrus.Fields{
			"username": p.settings.Username,
			"email":    p.settings.Email,
		}).Info("Specified credentials")
	} else {
		logrus.Info("Token credentials being used")
	}

	// Verify package.json file
	npm, err := readPackageFile(p.settings.Folder)
	if err != nil {
		return fmt.Errorf("Invalid package.json %w", err)
	}

	// Verify the same registry is being used
	if len(p.settings.Registry) == 0 {
		p.settings.Registry = globalRegistry
	}

	if strings.Compare(p.settings.Registry, npm.Config.Registry) != 0 {
		return fmt.Errorf("Registry values do not match .drone.yml: %s package.json: %s", p.settings.Registry, npm.Config.Registry)
	}

	p.npm = npm

	return nil
}

func (p *pluginImpl) Exec() error {
	// Write the npmrc file
	if err := p.writeNpmrc(); err != nil {
		return fmt.Errorf("Could not create npmrc %w", err)
	}

	// Attempt authentication
	if err := p.authenticate(); err != nil {
		return fmt.Errorf("Could not authenticate %w", err)
	}

	// Determine whether to publish
	publish, err := p.shouldPublishPackage()

	if err != nil {
		return fmt.Errorf("Could not determine if package should be published %w", err)
	}

	if publish {
		logrus.Info("Publishing package")
		if err = runCommand(publishCommand(p.settings), p.settings.Folder); err != nil {
			return fmt.Errorf("Could not publish package %w", err)
		}
	} else {
		logrus.Info("Not publishing package")
	}

	return nil
}

/// writeNpmrc creates a .npmrc in the folder for authentication
func (p *pluginImpl) writeNpmrc() error {
	var f func(settings Settings) string
    if len(p.settings.Token) == 0 {
		logrus.WithFields(logrus.Fields{
			"username": p.settings.Username,
			"email":    p.settings.Email,
		}).Info("Specified credentials")
        f = npmrcContentsUsernamePassword
	} else {
		logrus.Info("Token credentials being used")
		f = npmrcContentsToken
	}

	// write npmrc file
	home := "/root"
	user, err := user.Current()
	if err == nil {
		home = user.HomeDir
	}
	npmrcPath := path.Join(home, ".npmrc")

	logrus.WithField("path", npmrcPath).Info("Writing npmrc")

	return ioutil.WriteFile(npmrcPath, []byte(f(p.settings)), 0644)
}

/// shouldPublishPackage determines if the package should be published
func (p *pluginImpl) shouldPublishPackage() (bool, error) {
	cmd := packageVersionsCommand(p.npm.Name)
	cmd.Dir = p.settings.Folder

	trace(cmd)
	out, err := cmd.CombinedOutput()

	// see if there was an error
	// if there is an error its likely due to the package never being published
	if err == nil {
		// parse the json output
		var versions []string
		err = json.Unmarshal(out, &versions)

		if err != nil {
			logrus.Debug("Could not parse into array of string. Likely single value")

			var version string
			err := json.Unmarshal(out, &version)

			if err != nil {
				return false, err
			}

			versions = append(versions, version)
		}

		for _, value := range versions {
			logrus.WithField("version", value).Debug("Found version of package")

			if strings.Compare(p.npm.Version, value) == 0 {
				logrus.Info("Version found in the registry")
				if p.settings.FailOnVersionConflict {
					return false, fmt.Errorf("Cannot publish package due to version conflict")
				}
				return false, nil
			}
		}

		logrus.Info("Version not found in the registry")
	} else {
		logrus.Info("Name was not found in the registry")
	}

	return true, nil
}

/// authenticate atempts to authenticate with the NPM registry.
func (p *pluginImpl) authenticate() error {
	var cmds []*exec.Cmd

	// Write the version command
	cmds = append(cmds, versionCommand())

	// write registry command
	if p.settings.Registry != globalRegistry {
		cmds = append(cmds, registryCommand(p.settings.Registry))
	}

	// Write auth command
	cmds = append(cmds, alwaysAuthCommand())

	// Write skip verify command
	if p.network.SkipVerify {
		cmds = append(cmds, skipVerifyCommand())
	}

	// Write whoami command to verify credentials
	cmds = append(cmds, whoamiCommand())

	// Run commands
	err := runCommands(cmds, p.settings.Folder)

	if err != nil {
		return err
	}

	return nil
}

/// readPackageFile reads the package file at the given path.
func readPackageFile(folder string) (*npmPackage, error) {
	// Verify package.json file exists
	packagePath := path.Join(folder, "package.json")
	info, err := os.Stat(packagePath)

	if os.IsNotExist(err) {
		return nil, fmt.Errorf("No package.json at %s %w", packagePath, err)
	}
	if info.IsDir() {
		return nil, fmt.Errorf("The package.json at %s is a directory", packagePath)
	}

	// Read the file
	file, err := ioutil.ReadFile(packagePath)
	if err != nil {
		return nil, fmt.Errorf("Could not read package.json at %s %w", packagePath, err)
	}

	// Unmarshal the json data
	npm := npmPackage{}
	err = json.Unmarshal(file, &npm)
	if err != nil {
		return nil, err
	}

	// Make sure values are present
	if len(npm.Name) == 0 {
		return nil, fmt.Errorf("No package name present")
	}
	if len(npm.Version) == 0 {
		return nil, fmt.Errorf("No package version present")
	}

	// Set the default registry
	if len(npm.Config.Registry) == 0 {
		npm.Config.Registry = globalRegistry
	}

	logrus.WithFields(logrus.Fields{
		"name":    npm.Name,
		"version": npm.Version,
		"path":    packagePath,
	}).Info("Found package.json")

	return &npm, nil
}

// npmrcContentsUsernamePassword creates the contents from a username and
// password
func npmrcContentsUsernamePassword(config Settings) string {
	// get the base64 encoded string
	authString := fmt.Sprintf("%s:%s", config.Username, config.Password)
	encoded := base64.StdEncoding.EncodeToString([]byte(authString))

	// create the file contents
	return fmt.Sprintf("_auth = %s\nemail = %s", encoded, config.Email)
}

/// Writes npmrc contents when using a token
func npmrcContentsToken(config Settings) string {
	registry, _ := url.Parse(config.Registry)
	registry.Scheme = "" // Reset the scheme to empty. This makes it so we will get a protocol relative URL.
	registryString := registry.String()

	if !strings.HasSuffix(registryString, "/") {
		registryString = registryString + "/"
	}
	return fmt.Sprintf("%s:_authToken=%s", registryString, config.Token)
}

// versionCommand gets the npm version
func versionCommand() *exec.Cmd {
	return exec.Command("npm", "--version")
}

// registryCommand sets the NPM registry.
func registryCommand(registry string) *exec.Cmd {
	return exec.Command("npm", "config", "set", "registry", registry)
}

// alwaysAuthCommand forces authentication.
func alwaysAuthCommand() *exec.Cmd {
	return exec.Command("npm", "config", "set", "always-auth", "true")
}

// skipVerifyCommand disables ssl verification.
func skipVerifyCommand() *exec.Cmd {
	return exec.Command("npm", "config", "set", "strict-ssl", "false")
}

// whoamiCommand creates a command that gets the currently logged in user.
func whoamiCommand() *exec.Cmd {
	return exec.Command("npm", "whoami")
}

// packageVersionsCommand gets the versions of the npm package.
func packageVersionsCommand(name string) *exec.Cmd {
	return exec.Command("npm", "view", name, "versions", "--json")
}

// publishCommand runs the publish command
func publishCommand(settings Settings) *exec.Cmd {
	commandArgs := []string{"publish"}

	if len(settings.Tag) != 0 {
		commandArgs = append(commandArgs, "--tag", settings.Tag)
	}

	if len(settings.Access) != 0 {
		commandArgs = append(commandArgs, "--access", settings.Access)
	}

	return exec.Command("npm", commandArgs...)
}

// trace writes each command to standard error (preceded by a ‘$ ’) before it
// is executed. Used for debugging your build.
func trace(cmd *exec.Cmd) {
	fmt.Fprintf(os.Stdout, "+ %s\n", strings.Join(cmd.Args, " "))
}

// runCommands executes the list of cmds in the given directory.
func runCommands(cmds []*exec.Cmd, dir string) error {
	for _, cmd := range cmds {
		err := runCommand(cmd, dir)

		if err != nil {
			return err
		}
	}

	return nil
}

func runCommand(cmd *exec.Cmd, dir string) error {
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = dir
	trace(cmd)

	return cmd.Run()
}
<commit_msg>Formating<commit_after>// Copyright (c) 2019, the Drone Plugins project authors.
// Please see the AUTHORS file for details. All rights reserved.
// Use of this source code is governed by an Apache 2.0 license that can be
// found in the LICENSE file.

package npm

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"os/exec"
	"os/user"
	"path"
	"strings"

	"github.com/sirupsen/logrus"
)

type (
	// Settings for the Plugin.
	Settings struct {
		Username              string
		Password              string
		Token                 string
		Email                 string
		Registry              string
		Folder                string
		FailOnVersionConflict bool
		Tag                   string
		Access                string
	}

	npmPackage struct {
		Name    string    `json:"name"`
		Version string    `json:"version"`
		Config  npmConfig `json:"publishConfig"`
	}

	npmConfig struct {
		Registry string `json:"registry"`
	}
)

// globalRegistry defines the default NPM registry.
const globalRegistry = "https://registry.npmjs.org/"

func (p *pluginImpl) Validate() error {
	// Check authentication options
	if len(p.settings.Token) == 0 {
		if len(p.settings.Username) == 0 {
			return fmt.Errorf("No username provided")
		}
		if len(p.settings.Email) == 0 {
			return fmt.Errorf("No email address provided")
		}
		if len(p.settings.Password) == 0 {
			return fmt.Errorf("No password provided")
		}

		logrus.WithFields(logrus.Fields{
			"username": p.settings.Username,
			"email":    p.settings.Email,
		}).Info("Specified credentials")
	} else {
		logrus.Info("Token credentials being used")
	}

	// Verify package.json file
	npm, err := readPackageFile(p.settings.Folder)
	if err != nil {
		return fmt.Errorf("Invalid package.json %w", err)
	}

	// Verify the same registry is being used
	if len(p.settings.Registry) == 0 {
		p.settings.Registry = globalRegistry
	}

	if strings.Compare(p.settings.Registry, npm.Config.Registry) != 0 {
		return fmt.Errorf("Registry values do not match .drone.yml: %s package.json: %s", p.settings.Registry, npm.Config.Registry)
	}

	p.npm = npm

	return nil
}

func (p *pluginImpl) Exec() error {
	// Write the npmrc file
	if err := p.writeNpmrc(); err != nil {
		return fmt.Errorf("Could not create npmrc %w", err)
	}

	// Attempt authentication
	if err := p.authenticate(); err != nil {
		return fmt.Errorf("Could not authenticate %w", err)
	}

	// Determine whether to publish
	publish, err := p.shouldPublishPackage()

	if err != nil {
		return fmt.Errorf("Could not determine if package should be published %w", err)
	}

	if publish {
		logrus.Info("Publishing package")
		if err = runCommand(publishCommand(p.settings), p.settings.Folder); err != nil {
			return fmt.Errorf("Could not publish package %w", err)
		}
	} else {
		logrus.Info("Not publishing package")
	}

	return nil
}

/// writeNpmrc creates a .npmrc in the folder for authentication
func (p *pluginImpl) writeNpmrc() error {
	var f func(settings Settings) string
	if len(p.settings.Token) == 0 {
		logrus.WithFields(logrus.Fields{
			"username": p.settings.Username,
			"email":    p.settings.Email,
		}).Info("Specified credentials")
		f = npmrcContentsUsernamePassword
	} else {
		logrus.Info("Token credentials being used")
		f = npmrcContentsToken
	}

	// write npmrc file
	home := "/root"
	user, err := user.Current()
	if err == nil {
		home = user.HomeDir
	}
	npmrcPath := path.Join(home, ".npmrc")

	logrus.WithField("path", npmrcPath).Info("Writing npmrc")

	return ioutil.WriteFile(npmrcPath, []byte(f(p.settings)), 0644)
}

/// shouldPublishPackage determines if the package should be published
func (p *pluginImpl) shouldPublishPackage() (bool, error) {
	cmd := packageVersionsCommand(p.npm.Name)
	cmd.Dir = p.settings.Folder

	trace(cmd)
	out, err := cmd.CombinedOutput()

	// see if there was an error
	// if there is an error its likely due to the package never being published
	if err == nil {
		// parse the json output
		var versions []string
		err = json.Unmarshal(out, &versions)

		if err != nil {
			logrus.Debug("Could not parse into array of string. Likely single value")

			var version string
			err := json.Unmarshal(out, &version)

			if err != nil {
				return false, err
			}

			versions = append(versions, version)
		}

		for _, value := range versions {
			logrus.WithField("version", value).Debug("Found version of package")

			if strings.Compare(p.npm.Version, value) == 0 {
				logrus.Info("Version found in the registry")
				if p.settings.FailOnVersionConflict {
					return false, fmt.Errorf("Cannot publish package due to version conflict")
				}
				return false, nil
			}
		}

		logrus.Info("Version not found in the registry")
	} else {
		logrus.Info("Name was not found in the registry")
	}

	return true, nil
}

/// authenticate atempts to authenticate with the NPM registry.
func (p *pluginImpl) authenticate() error {
	var cmds []*exec.Cmd

	// Write the version command
	cmds = append(cmds, versionCommand())

	// write registry command
	if p.settings.Registry != globalRegistry {
		cmds = append(cmds, registryCommand(p.settings.Registry))
	}

	// Write auth command
	cmds = append(cmds, alwaysAuthCommand())

	// Write skip verify command
	if p.network.SkipVerify {
		cmds = append(cmds, skipVerifyCommand())
	}

	// Write whoami command to verify credentials
	cmds = append(cmds, whoamiCommand())

	// Run commands
	err := runCommands(cmds, p.settings.Folder)

	if err != nil {
		return err
	}

	return nil
}

/// readPackageFile reads the package file at the given path.
func readPackageFile(folder string) (*npmPackage, error) {
	// Verify package.json file exists
	packagePath := path.Join(folder, "package.json")
	info, err := os.Stat(packagePath)

	if os.IsNotExist(err) {
		return nil, fmt.Errorf("No package.json at %s %w", packagePath, err)
	}
	if info.IsDir() {
		return nil, fmt.Errorf("The package.json at %s is a directory", packagePath)
	}

	// Read the file
	file, err := ioutil.ReadFile(packagePath)
	if err != nil {
		return nil, fmt.Errorf("Could not read package.json at %s %w", packagePath, err)
	}

	// Unmarshal the json data
	npm := npmPackage{}
	err = json.Unmarshal(file, &npm)
	if err != nil {
		return nil, err
	}

	// Make sure values are present
	if len(npm.Name) == 0 {
		return nil, fmt.Errorf("No package name present")
	}
	if len(npm.Version) == 0 {
		return nil, fmt.Errorf("No package version present")
	}

	// Set the default registry
	if len(npm.Config.Registry) == 0 {
		npm.Config.Registry = globalRegistry
	}

	logrus.WithFields(logrus.Fields{
		"name":    npm.Name,
		"version": npm.Version,
		"path":    packagePath,
	}).Info("Found package.json")

	return &npm, nil
}

// npmrcContentsUsernamePassword creates the contents from a username and
// password
func npmrcContentsUsernamePassword(config Settings) string {
	// get the base64 encoded string
	authString := fmt.Sprintf("%s:%s", config.Username, config.Password)
	encoded := base64.StdEncoding.EncodeToString([]byte(authString))

	// create the file contents
	return fmt.Sprintf("_auth = %s\nemail = %s", encoded, config.Email)
}

/// Writes npmrc contents when using a token
func npmrcContentsToken(config Settings) string {
	registry, _ := url.Parse(config.Registry)
	registry.Scheme = "" // Reset the scheme to empty. This makes it so we will get a protocol relative URL.
	registryString := registry.String()

	if !strings.HasSuffix(registryString, "/") {
		registryString = registryString + "/"
	}
	return fmt.Sprintf("%s:_authToken=%s", registryString, config.Token)
}

// versionCommand gets the npm version
func versionCommand() *exec.Cmd {
	return exec.Command("npm", "--version")
}

// registryCommand sets the NPM registry.
func registryCommand(registry string) *exec.Cmd {
	return exec.Command("npm", "config", "set", "registry", registry)
}

// alwaysAuthCommand forces authentication.
func alwaysAuthCommand() *exec.Cmd {
	return exec.Command("npm", "config", "set", "always-auth", "true")
}

// skipVerifyCommand disables ssl verification.
func skipVerifyCommand() *exec.Cmd {
	return exec.Command("npm", "config", "set", "strict-ssl", "false")
}

// whoamiCommand creates a command that gets the currently logged in user.
func whoamiCommand() *exec.Cmd {
	return exec.Command("npm", "whoami")
}

// packageVersionsCommand gets the versions of the npm package.
func packageVersionsCommand(name string) *exec.Cmd {
	return exec.Command("npm", "view", name, "versions", "--json")
}

// publishCommand runs the publish command
func publishCommand(settings Settings) *exec.Cmd {
	commandArgs := []string{"publish"}

	if len(settings.Tag) != 0 {
		commandArgs = append(commandArgs, "--tag", settings.Tag)
	}

	if len(settings.Access) != 0 {
		commandArgs = append(commandArgs, "--access", settings.Access)
	}

	return exec.Command("npm", commandArgs...)
}

// trace writes each command to standard error (preceded by a ‘$ ’) before it
// is executed. Used for debugging your build.
func trace(cmd *exec.Cmd) {
	fmt.Fprintf(os.Stdout, "+ %s\n", strings.Join(cmd.Args, " "))
}

// runCommands executes the list of cmds in the given directory.
func runCommands(cmds []*exec.Cmd, dir string) error {
	for _, cmd := range cmds {
		err := runCommand(cmd, dir)

		if err != nil {
			return err
		}
	}

	return nil
}

func runCommand(cmd *exec.Cmd, dir string) error {
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = dir
	trace(cmd)

	return cmd.Run()
}
<|endoftext|>


<commit_before>/*******************************************************************************
*
* Copyright 2017-2020 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, 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 reports

import (
	"database/sql"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/sapcc/limes"
	"github.com/sapcc/limes/pkg/core"
	"github.com/sapcc/limes/pkg/db"
)

//NOTE: Both queries use LEFT OUTER JOIN to generate at least one result row
//per known project, to ensure that each project gets a report even if its
//resource data and/or rate data is incomplete.
var (
	projectRateLimitReportQuery = db.SimplifyWhitespaceInSQL(`
	SELECT p.uuid, p.name, COALESCE(p.parent_uuid, ''), ps.type, ps.scraped_at, ps.rates_scraped_at, pra.name, pra.rate_limit, pra.window_ns, pra.usage_as_bigint
	  FROM projects p
	  LEFT OUTER JOIN project_services ps ON ps.project_id = p.id {{AND ps.type = $service_type}}
	  LEFT OUTER JOIN project_rates pra ON pra.service_id = ps.id
	 WHERE %s
`)
	projectReportQuery = db.SimplifyWhitespaceInSQL(`
	SELECT p.uuid, p.name, COALESCE(p.parent_uuid, ''), p.has_bursting, ps.type, ps.scraped_at, ps.rates_scraped_at, pr.name, pr.quota, pr.usage, pr.physical_usage, pr.backend_quota, pr.subresources
	  FROM projects p
	  LEFT OUTER JOIN project_services ps ON ps.project_id = p.id {{AND ps.type = $service_type}}
	  LEFT OUTER JOIN project_resources pr ON pr.service_id = ps.id {{AND pr.name = $resource_name}}
	 WHERE %s
`)
)

//GetProjects returns limes.ProjectReport reports for all projects in the given domain or,
//if project is non-nil, for that project only.
//
//Since large domains can contain thousands of project reports, and project
//reports with the highest detail levels can be several MB large, we don't just
//return them all in a big list. Instead, the `submit` callback gets called
//once for each project report once that report is complete.
func GetProjects(cluster *core.Cluster, domain db.Domain, project *db.Project, dbi db.Interface, filter Filter, submit func(*limes.ProjectReport) error) error {
	clusterCanBurst := cluster.Config.Bursting.MaxMultiplier > 0

	fields := map[string]interface{}{"p.domain_id": domain.ID}
	if project != nil {
		fields["p.id"] = project.ID
	}

	projects := make(projects)

	//if rates are requested, fill default rate limits into ProjectServiceReport
	//instances as they are created
	onCreateService := func(serviceReport *limes.ProjectServiceReport) {
		if filter.WithRates {
			if svcConfig, err := cluster.Config.GetServiceConfigurationForType(serviceReport.Type); err == nil {
				if len(svcConfig.RateLimits.ProjectDefault) > 0 {
					serviceReport.Rates = make(limes.ProjectRateLimitReports, len(svcConfig.RateLimits.ProjectDefault))
					for _, rateLimit := range svcConfig.RateLimits.ProjectDefault {
						serviceReport.Rates[rateLimit.Name] = &limes.ProjectRateLimitReport{
							RateInfo: cluster.InfoForRate(serviceReport.Type, rateLimit.Name),
							Limit:    rateLimit.Limit,
							Window:   p2window(rateLimit.Window),
						}
					}
				}
			}
		}
	}

	//phase 1: collect rate data
	//
	//We do this phase first because this data is small enough to easily fit in RAM.

	if filter.WithRates {
		queryStr, joinArgs := filter.PrepareQuery(projectRateLimitReportQuery)
		whereStr, whereArgs := db.BuildSimpleWhereClause(fields, len(joinArgs))
		err := db.ForeachRow(dbi, fmt.Sprintf(queryStr, whereStr), append(joinArgs, whereArgs...), func(rows *sql.Rows) error {
			var (
				projectUUID       string
				projectName       string
				projectParentUUID string
				serviceType       *string
				scrapedAt         *time.Time
				ratesScrapedAt    *time.Time
				rateName          *string
				limit             *uint64
				window            *limes.Window
				usageAsBigint     *string
			)
			err := rows.Scan(
				&projectUUID, &projectName, &projectParentUUID,
				&serviceType, &scrapedAt, &ratesScrapedAt,
				&rateName, &limit, &window, &usageAsBigint,
			)
			if err != nil {
				return err
			}

			_, srvReport, _ := projects.Find(cluster, projectUUID, projectName, projectParentUUID, serviceType, nil, scrapedAt, ratesScrapedAt, onCreateService)
			if srvReport != nil && rateName != nil {
				rateReport := srvReport.Rates[*rateName]

				//we previously created report entries for all rates that have a
				//default limit; create missing report entries for rates that only have
				//a usage
				if rateReport == nil && usageAsBigint != nil && *usageAsBigint != "" && cluster.HasUsageForRate(*serviceType, *rateName) {
					rateReport = &limes.ProjectRateLimitReport{
						RateInfo: cluster.InfoForRate(*serviceType, *rateName),
					}
					srvReport.Rates[*rateName] = rateReport
				}

				if rateReport != nil {
					if usageAsBigint != nil {
						rateReport.UsageAsBigint = *usageAsBigint
					}

					//overwrite the default limit if a different custom limit is
					//configured, but ignore custom limits where there is no default
					//limit
					if rateReport.Limit != 0 && limit != nil && window != nil {
						if rateReport.Limit != *limit || *rateReport.Window != *window {
							rateReport.DefaultLimit = rateReport.Limit
							rateReport.DefaultWindow = rateReport.Window
							rateReport.Limit = *limit
							rateReport.Window = window
						}
					}
				}
			}

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

	//phase 2: collect resource data
	//
	//Data collected in this phase can be very large if subresources are requested.
	//TODO: send reports as soon as they are finished

	if !filter.OnlyRates {
		//avoid collecting the potentially large subresources strings when possible
		queryStr := projectReportQuery
		if !filter.WithSubresources {
			queryStr = strings.Replace(queryStr, "pr.subresources", "''", 1)
		}
		queryStr, joinArgs := filter.PrepareQuery(queryStr)
		whereStr, whereArgs := db.BuildSimpleWhereClause(fields, len(joinArgs))
		err := db.ForeachRow(dbi, fmt.Sprintf(queryStr, whereStr), append(joinArgs, whereArgs...), func(rows *sql.Rows) error {
			var (
				projectUUID        string
				projectName        string
				projectParentUUID  string
				projectHasBursting bool
				serviceType        *string
				scrapedAt          *time.Time
				ratesScrapedAt     *time.Time
				resourceName       *string
				quota              *uint64
				usage              *uint64
				physicalUsage      *uint64
				backendQuota       *int64
				subresources       *string
			)
			err := rows.Scan(
				&projectUUID, &projectName, &projectParentUUID, &projectHasBursting,
				&serviceType, &scrapedAt, &ratesScrapedAt, &resourceName,
				&quota, &usage, &physicalUsage, &backendQuota, &subresources,
			)
			if err != nil {
				return err
			}

			projectReport, _, resReport := projects.Find(cluster, projectUUID, projectName, projectParentUUID, serviceType, resourceName, timeIf(scrapedAt, !filter.OnlyRates), timeIf(ratesScrapedAt, filter.WithRates), onCreateService)
			if projectReport != nil && clusterCanBurst {
				projectReport.Bursting = &limes.ProjectBurstingInfo{
					Enabled:    projectHasBursting,
					Multiplier: cluster.Config.Bursting.MaxMultiplier,
				}
			}

			if resReport != nil {
				subresourcesValue := ""
				if subresources != nil {
					subresourcesValue = *subresources
				}
				resReport.PhysicalUsage = physicalUsage
				resReport.BackendQuota = nil //See below.
				resReport.Subresources = limes.JSONString(subresourcesValue)

				behavior := cluster.BehaviorForResource(*serviceType, *resourceName, domain.Name+"/"+projectName)
				resReport.Scaling = behavior.ToScalingBehavior()
				resReport.Annotations = behavior.Annotations

				if usage != nil {
					resReport.Usage = *usage
				}
				if quota != nil && !resReport.NoQuota {
					resReport.Quota = quota
					resReport.UsableQuota = quota
					if projectHasBursting && clusterCanBurst {
						usableQuota := behavior.MaxBurstMultiplier.ApplyTo(*quota)
						resReport.UsableQuota = &usableQuota
					}
					if backendQuota != nil && (*backendQuota < 0 || uint64(*backendQuota) != *resReport.UsableQuota) {
						resReport.BackendQuota = backendQuota
					}
				}
				if projectHasBursting && clusterCanBurst && quota != nil && usage != nil {
					if *usage > *quota {
						resReport.BurstUsage = *usage - *quota
					}
				}
			}
			return nil
		})
		if err != nil {
			return err
		}
	}

	//flatten result (with stable order to keep the tests happy)
	uuids := make([]string, 0, len(projects))
	for uuid := range projects {
		uuids = append(uuids, uuid)
	}
	sort.Strings(uuids)
	for _, uuid := range uuids {
		err := submit(projects[uuid])
		if err != nil {
			return err
		}
	}

	return nil
}

func p2window(val limes.Window) *limes.Window {
	return &val
}

type projects map[string]*limes.ProjectReport

func (p projects) Find(cluster *core.Cluster, projectUUID, projectName, projectParentUUID string, serviceType, resourceName *string, scrapedAt, ratesScrapedAt *time.Time, onCreateService func(*limes.ProjectServiceReport)) (*limes.ProjectReport, *limes.ProjectServiceReport, *limes.ProjectResourceReport) {
	//Ensure the ProjectReport exists.
	project, exists := p[projectUUID]
	if !exists {
		project = &limes.ProjectReport{
			Name:       projectName,
			UUID:       projectUUID,
			ParentUUID: projectParentUUID,
			Services:   make(limes.ProjectServiceReports),
		}
		p[projectUUID] = project
	}

	if serviceType == nil {
		return project, nil, nil
	}
	// Ensure the ProjectServiceReport exists if the serviceType is given.
	service, exists := project.Services[*serviceType]
	if !exists {
		if !cluster.HasService(*serviceType) {
			return project, nil, nil
		}
		service = &limes.ProjectServiceReport{
			ServiceInfo: cluster.InfoForService(*serviceType),
			Resources:   make(limes.ProjectResourceReports),
			Rates:       make(limes.ProjectRateLimitReports),
		}
		if scrapedAt != nil {
			scrapedAtUnix := scrapedAt.Unix()
			service.ScrapedAt = &scrapedAtUnix
		}
		if ratesScrapedAt != nil {
			ratesScrapedAtUnix := ratesScrapedAt.Unix()
			service.RatesScrapedAt = &ratesScrapedAtUnix
		}
		if onCreateService != nil {
			onCreateService(service)
		}
		project.Services[*serviceType] = service
	}

	//Ensure the ProjectResourceReport exists if the resourceName is given.
	var resource *limes.ProjectResourceReport
	if resourceName != nil {
		resource, exists = service.Resources[*resourceName]
		if !exists && cluster.HasResource(*serviceType, *resourceName) {
			resource = &limes.ProjectResourceReport{
				ResourceInfo: cluster.InfoForResource(*serviceType, *resourceName),
			}
			service.Resources[*resourceName] = resource
		}
	}

	return project, service, resource
}

func timeIf(t *time.Time, cond bool) *time.Time {
	if !cond {
		return nil
	}
	return t
}
<commit_msg>reports.GetProjects(): stream results to caller as soon as possible<commit_after>/*******************************************************************************
*
* Copyright 2017-2020 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, 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 reports

import (
	"database/sql"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/sapcc/limes"
	"github.com/sapcc/limes/pkg/core"
	"github.com/sapcc/limes/pkg/db"
)

//NOTE: Both queries use LEFT OUTER JOIN to generate at least one result row
//per known project, to ensure that each project gets a report even if its
//resource data and/or rate data is incomplete.
//
//The query for resource data uses "ORDER BY p.uuid" to ensure that a) the
//output order is reproducible to keep the tests happy and b) records for the
//same project appear in a cluster, so that the implementation can publish
//completed project reports (and then reclaim their memory usage) as soon as
//possible.
var (
	projectRateLimitReportQuery = db.SimplifyWhitespaceInSQL(`
	SELECT p.uuid, p.name, COALESCE(p.parent_uuid, ''), ps.type, ps.scraped_at, ps.rates_scraped_at, pra.name, pra.rate_limit, pra.window_ns, pra.usage_as_bigint
	  FROM projects p
	  LEFT OUTER JOIN project_services ps ON ps.project_id = p.id {{AND ps.type = $service_type}}
	  LEFT OUTER JOIN project_rates pra ON pra.service_id = ps.id
	 WHERE %s
`)
	projectReportQuery = db.SimplifyWhitespaceInSQL(`
	SELECT p.uuid, p.name, COALESCE(p.parent_uuid, ''), p.has_bursting, ps.type, ps.scraped_at, ps.rates_scraped_at, pr.name, pr.quota, pr.usage, pr.physical_usage, pr.backend_quota, pr.subresources
	  FROM projects p
	  LEFT OUTER JOIN project_services ps ON ps.project_id = p.id {{AND ps.type = $service_type}}
	  LEFT OUTER JOIN project_resources pr ON pr.service_id = ps.id {{AND pr.name = $resource_name}}
	 WHERE %s
	 ORDER BY p.uuid
`)
)

//GetProjects returns limes.ProjectReport reports for all projects in the given domain or,
//if project is non-nil, for that project only.
//
//Since large domains can contain thousands of project reports, and project
//reports with the highest detail levels can be several MB large, we don't just
//return them all in a big list. Instead, the `submit` callback gets called
//once for each project report once that report is complete.
func GetProjects(cluster *core.Cluster, domain db.Domain, project *db.Project, dbi db.Interface, filter Filter, submit func(*limes.ProjectReport) error) error {
	clusterCanBurst := cluster.Config.Bursting.MaxMultiplier > 0

	fields := map[string]interface{}{"p.domain_id": domain.ID}
	if project != nil {
		fields["p.id"] = project.ID
	}

	projects := make(projects)

	//if rates are requested, fill default rate limits into ProjectServiceReport
	//instances as they are created
	onCreateService := func(serviceReport *limes.ProjectServiceReport) {
		if filter.WithRates {
			if svcConfig, err := cluster.Config.GetServiceConfigurationForType(serviceReport.Type); err == nil {
				if len(svcConfig.RateLimits.ProjectDefault) > 0 {
					serviceReport.Rates = make(limes.ProjectRateLimitReports, len(svcConfig.RateLimits.ProjectDefault))
					for _, rateLimit := range svcConfig.RateLimits.ProjectDefault {
						serviceReport.Rates[rateLimit.Name] = &limes.ProjectRateLimitReport{
							RateInfo: cluster.InfoForRate(serviceReport.Type, rateLimit.Name),
							Limit:    rateLimit.Limit,
							Window:   p2window(rateLimit.Window),
						}
					}
				}
			}
		}
	}

	//phase 1: collect rate data
	//
	//We do this phase first because this data is small enough to easily fit in RAM.

	if filter.WithRates {
		queryStr, joinArgs := filter.PrepareQuery(projectRateLimitReportQuery)
		whereStr, whereArgs := db.BuildSimpleWhereClause(fields, len(joinArgs))
		err := db.ForeachRow(dbi, fmt.Sprintf(queryStr, whereStr), append(joinArgs, whereArgs...), func(rows *sql.Rows) error {
			var (
				projectUUID       string
				projectName       string
				projectParentUUID string
				serviceType       *string
				scrapedAt         *time.Time
				ratesScrapedAt    *time.Time
				rateName          *string
				limit             *uint64
				window            *limes.Window
				usageAsBigint     *string
			)
			err := rows.Scan(
				&projectUUID, &projectName, &projectParentUUID,
				&serviceType, &scrapedAt, &ratesScrapedAt,
				&rateName, &limit, &window, &usageAsBigint,
			)
			if err != nil {
				return err
			}

			_, srvReport, _ := projects.Find(cluster, projectUUID, projectName, projectParentUUID, serviceType, nil, scrapedAt, ratesScrapedAt, onCreateService)
			if srvReport != nil && rateName != nil {
				rateReport := srvReport.Rates[*rateName]

				//we previously created report entries for all rates that have a
				//default limit; create missing report entries for rates that only have
				//a usage
				if rateReport == nil && usageAsBigint != nil && *usageAsBigint != "" && cluster.HasUsageForRate(*serviceType, *rateName) {
					rateReport = &limes.ProjectRateLimitReport{
						RateInfo: cluster.InfoForRate(*serviceType, *rateName),
					}
					srvReport.Rates[*rateName] = rateReport
				}

				if rateReport != nil {
					if usageAsBigint != nil {
						rateReport.UsageAsBigint = *usageAsBigint
					}

					//overwrite the default limit if a different custom limit is
					//configured, but ignore custom limits where there is no default
					//limit
					if rateReport.Limit != 0 && limit != nil && window != nil {
						if rateReport.Limit != *limit || *rateReport.Window != *window {
							rateReport.DefaultLimit = rateReport.Limit
							rateReport.DefaultWindow = rateReport.Window
							rateReport.Limit = *limit
							rateReport.Window = window
						}
					}
				}
			}

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

	//phase 2: collect resource data
	//
	//Data collected in this phase can be very large if subresources are requested.
	//To ensure that we don't run OOM, we retrieve data ordered by project and
	//send out a completed project report as soon as the data for the next
	//project comes in.

	if !filter.OnlyRates {
		//avoid collecting the potentially large subresources strings when possible
		queryStr := projectReportQuery
		if !filter.WithSubresources {
			queryStr = strings.Replace(queryStr, "pr.subresources", "''", 1)
		}
		queryStr, joinArgs := filter.PrepareQuery(queryStr)
		whereStr, whereArgs := db.BuildSimpleWhereClause(fields, len(joinArgs))
		var currentProjectUUID string
		err := db.ForeachRow(dbi, fmt.Sprintf(queryStr, whereStr), append(joinArgs, whereArgs...), func(rows *sql.Rows) error {
			var (
				projectUUID        string
				projectName        string
				projectParentUUID  string
				projectHasBursting bool
				serviceType        *string
				scrapedAt          *time.Time
				ratesScrapedAt     *time.Time
				resourceName       *string
				quota              *uint64
				usage              *uint64
				physicalUsage      *uint64
				backendQuota       *int64
				subresources       *string
			)
			err := rows.Scan(
				&projectUUID, &projectName, &projectParentUUID, &projectHasBursting,
				&serviceType, &scrapedAt, &ratesScrapedAt, &resourceName,
				&quota, &usage, &physicalUsage, &backendQuota, &subresources,
			)
			if err != nil {
				return err
			}

			if currentProjectUUID != projectUUID {
				//we're moving to a different project, so it's time to publish the
				//finished project report and then allow for it to be GCd
				if currentProjectUUID != "" {
					submit(projects[currentProjectUUID])
					delete(projects, currentProjectUUID)
				}
				currentProjectUUID = projectUUID
			}

			projectReport, _, resReport := projects.Find(cluster, projectUUID, projectName, projectParentUUID, serviceType, resourceName, timeIf(scrapedAt, !filter.OnlyRates), timeIf(ratesScrapedAt, filter.WithRates), onCreateService)
			if projectReport != nil && clusterCanBurst {
				projectReport.Bursting = &limes.ProjectBurstingInfo{
					Enabled:    projectHasBursting,
					Multiplier: cluster.Config.Bursting.MaxMultiplier,
				}
			}

			if resReport != nil {
				subresourcesValue := ""
				if subresources != nil {
					subresourcesValue = *subresources
				}
				resReport.PhysicalUsage = physicalUsage
				resReport.BackendQuota = nil //See below.
				resReport.Subresources = limes.JSONString(subresourcesValue)

				behavior := cluster.BehaviorForResource(*serviceType, *resourceName, domain.Name+"/"+projectName)
				resReport.Scaling = behavior.ToScalingBehavior()
				resReport.Annotations = behavior.Annotations

				if usage != nil {
					resReport.Usage = *usage
				}
				if quota != nil && !resReport.NoQuota {
					resReport.Quota = quota
					resReport.UsableQuota = quota
					if projectHasBursting && clusterCanBurst {
						usableQuota := behavior.MaxBurstMultiplier.ApplyTo(*quota)
						resReport.UsableQuota = &usableQuota
					}
					if backendQuota != nil && (*backendQuota < 0 || uint64(*backendQuota) != *resReport.UsableQuota) {
						resReport.BackendQuota = backendQuota
					}
				}
				if projectHasBursting && clusterCanBurst && quota != nil && usage != nil {
					if *usage > *quota {
						resReport.BurstUsage = *usage - *quota
					}
				}
			}
			return nil
		})
		if err != nil {
			return err
		}
	}

	//phase 3: submit all remaining reports
	//
	//Either we did have phase 2 and only the last project report remains to be
	//published, or we did not have phase 2 and all project reports need to be
	//published now.

	//submit with stable order to keep the tests happy
	uuids := make([]string, 0, len(projects))
	for uuid := range projects {
		uuids = append(uuids, uuid)
	}
	sort.Strings(uuids)
	for _, uuid := range uuids {
		err := submit(projects[uuid])
		if err != nil {
			return err
		}
	}

	return nil
}

func p2window(val limes.Window) *limes.Window {
	return &val
}

type projects map[string]*limes.ProjectReport

func (p projects) Find(cluster *core.Cluster, projectUUID, projectName, projectParentUUID string, serviceType, resourceName *string, scrapedAt, ratesScrapedAt *time.Time, onCreateService func(*limes.ProjectServiceReport)) (*limes.ProjectReport, *limes.ProjectServiceReport, *limes.ProjectResourceReport) {
	//Ensure the ProjectReport exists.
	project, exists := p[projectUUID]
	if !exists {
		project = &limes.ProjectReport{
			Name:       projectName,
			UUID:       projectUUID,
			ParentUUID: projectParentUUID,
			Services:   make(limes.ProjectServiceReports),
		}
		p[projectUUID] = project
	}

	if serviceType == nil {
		return project, nil, nil
	}
	// Ensure the ProjectServiceReport exists if the serviceType is given.
	service, exists := project.Services[*serviceType]
	if !exists {
		if !cluster.HasService(*serviceType) {
			return project, nil, nil
		}
		service = &limes.ProjectServiceReport{
			ServiceInfo: cluster.InfoForService(*serviceType),
			Resources:   make(limes.ProjectResourceReports),
			Rates:       make(limes.ProjectRateLimitReports),
		}
		if scrapedAt != nil {
			scrapedAtUnix := scrapedAt.Unix()
			service.ScrapedAt = &scrapedAtUnix
		}
		if ratesScrapedAt != nil {
			ratesScrapedAtUnix := ratesScrapedAt.Unix()
			service.RatesScrapedAt = &ratesScrapedAtUnix
		}
		if onCreateService != nil {
			onCreateService(service)
		}
		project.Services[*serviceType] = service
	}

	//Ensure the ProjectResourceReport exists if the resourceName is given.
	var resource *limes.ProjectResourceReport
	if resourceName != nil {
		resource, exists = service.Resources[*resourceName]
		if !exists && cluster.HasResource(*serviceType, *resourceName) {
			resource = &limes.ProjectResourceReport{
				ResourceInfo: cluster.InfoForResource(*serviceType, *resourceName),
			}
			service.Resources[*resourceName] = resource
		}
	}

	return project, service, resource
}

func timeIf(t *time.Time, cond bool) *time.Time {
	if !cond {
		return nil
	}
	return t
}
<|endoftext|>


<commit_before>package sync

import (
	"testing"
)

func TestTransitionDependenciesEmtpy(t *testing.T) {
	if entries, err := TransitionDependencies(nil); err != nil {
		t.Error("transition dependency finding failed for no transitions:", err)
	} else if len(entries) != 0 {
		t.Error("unexpected number of entries for no transitions")
	}
}

func TestTransitionDependenciesInvalid(t *testing.T) {
	root := testDirectory1Entry.Copy()
	root.Contents["directory"].Contents["subfile"].Kind = (EntryKind_Symlink + 1)
	transitions := []*Change{
		{
			Path: "",
			New:  root,
		},
	}
	if _, _, err := TransitionDependencies(transitions); err == nil {
		t.Error("transition dependency finding succeeded for invalid transition")
	}
}

func TestTransitionDependenciesNewNil(t *testing.T) {
	transitions := []*Change{
		{
			Path: "",
			New:  nil,
		},
	}
	if entries, err := TransitionDependencies(transitions); err != nil {
		t.Error("transition dependency finding failed:", err)
	} else if len(entries) != 0 {
		t.Error("unexpected number of entries")
	}
}

func TestTransitionDependenciesNewNonNil(t *testing.T) {
	transitions := []*Change{
		{
			Path: "",
			New:  testDirectory1Entry,
		},
	}
	if entries, err := TransitionDependencies(transitions); err != nil {
		t.Error("transition dependency finding failed:", err)
	} else if len(entries) != 4 {
		t.Error("unexpected number of entries")
	}
}

func TestTransitionDependenciesOnlyExecutableBitChange(t *testing.T) {
	old := testFile2Entry.Copy()
	old.Executable = false
	transitions := []*Change{
		{
			Path: "",
			Old:  old,
			New:  testFile2Entry,
		},
	}
	if entries, err := TransitionDependencies(transitions); err != nil {
		t.Error("transition dependency finding failed:", err)
	} else if len(entries) != 0 {
		t.Error("unexpected number of entries")
	}
}
<commit_msg>Fixed test failure due to TransitionDependencies change.<commit_after>package sync

import (
	"testing"
)

func TestTransitionDependenciesEmtpy(t *testing.T) {
	if entries, err := TransitionDependencies(nil); err != nil {
		t.Error("transition dependency finding failed for no transitions:", err)
	} else if len(entries) != 0 {
		t.Error("unexpected number of entries for no transitions")
	}
}

func TestTransitionDependenciesInvalid(t *testing.T) {
	root := testDirectory1Entry.Copy()
	root.Contents["directory"].Contents["subfile"].Kind = (EntryKind_Symlink + 1)
	transitions := []*Change{
		{
			Path: "",
			New:  root,
		},
	}
	if _, err := TransitionDependencies(transitions); err == nil {
		t.Error("transition dependency finding succeeded for invalid transition")
	}
}

func TestTransitionDependenciesNewNil(t *testing.T) {
	transitions := []*Change{
		{
			Path: "",
			New:  nil,
		},
	}
	if entries, err := TransitionDependencies(transitions); err != nil {
		t.Error("transition dependency finding failed:", err)
	} else if len(entries) != 0 {
		t.Error("unexpected number of entries")
	}
}

func TestTransitionDependenciesNewNonNil(t *testing.T) {
	transitions := []*Change{
		{
			Path: "",
			New:  testDirectory1Entry,
		},
	}
	if entries, err := TransitionDependencies(transitions); err != nil {
		t.Error("transition dependency finding failed:", err)
	} else if len(entries) != 4 {
		t.Error("unexpected number of entries")
	}
}

func TestTransitionDependenciesOnlyExecutableBitChange(t *testing.T) {
	old := testFile2Entry.Copy()
	old.Executable = false
	transitions := []*Change{
		{
			Path: "",
			Old:  old,
			New:  testFile2Entry,
		},
	}
	if entries, err := TransitionDependencies(transitions); err != nil {
		t.Error("transition dependency finding failed:", err)
	} else if len(entries) != 0 {
		t.Error("unexpected number of entries")
	}
}
<|endoftext|>


<commit_before>package tls

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"strings"

	"github.com/containous/traefik/v2/pkg/log"
	"github.com/containous/traefik/v2/pkg/tls/generate"
)

var (
	// MinVersion Map of allowed TLS minimum versions
	MinVersion = map[string]uint16{
		`VersionTLS10`: tls.VersionTLS10,
		`VersionTLS11`: tls.VersionTLS11,
		`VersionTLS12`: tls.VersionTLS12,
		`VersionTLS13`: tls.VersionTLS13,
	}

	// MaxVersion Map of allowed TLS minimum versions
	MaxVersion = map[string]uint16{
		`VersionTLS10`: tls.VersionTLS10,
		`VersionTLS11`: tls.VersionTLS11,
		`VersionTLS12`: tls.VersionTLS12,
		`VersionTLS13`: tls.VersionTLS13,
	}

	// CipherSuites Map of TLS CipherSuites from crypto/tls
	// Available CipherSuites defined at https://golang.org/pkg/crypto/tls/#pkg-constants
	CipherSuites = map[string]uint16{
		`TLS_RSA_WITH_RC4_128_SHA`:                tls.TLS_RSA_WITH_RC4_128_SHA,
		`TLS_RSA_WITH_3DES_EDE_CBC_SHA`:           tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
		`TLS_RSA_WITH_AES_128_CBC_SHA`:            tls.TLS_RSA_WITH_AES_128_CBC_SHA,
		`TLS_RSA_WITH_AES_256_CBC_SHA`:            tls.TLS_RSA_WITH_AES_256_CBC_SHA,
		`TLS_RSA_WITH_AES_128_CBC_SHA256`:         tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
		`TLS_RSA_WITH_AES_128_GCM_SHA256`:         tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
		`TLS_RSA_WITH_AES_256_GCM_SHA384`:         tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
		`TLS_ECDHE_ECDSA_WITH_RC4_128_SHA`:        tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
		`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`:    tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
		`TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`:    tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		`TLS_ECDHE_RSA_WITH_RC4_128_SHA`:          tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
		`TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`:     tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
		`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`:      tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
		`TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`:      tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
		`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
		`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256`:   tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
		`TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`:   tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
		`TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		`TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`:   tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
		`TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
		`TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305`:    tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
		`TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305`:  tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
		"TLS_AES_128_GCM_SHA256":                  tls.TLS_AES_128_GCM_SHA256,
		"TLS_AES_256_GCM_SHA384":                  tls.TLS_AES_256_GCM_SHA384,
		"TLS_CHACHA20_POLY1305_SHA256":            tls.TLS_CHACHA20_POLY1305_SHA256,
		"TLS_FALLBACK_SCSV":                       tls.TLS_FALLBACK_SCSV,
	}

	// CurveIDs is a Map of TLS elliptic curves from crypto/tls
	// Available CurveIDs defined at https://godoc.org/crypto/tls#CurveID,
	// also allowing rfc names defined at https://tools.ietf.org/html/rfc8446#section-4.2.7
	CurveIDs = map[string]tls.CurveID{
		`secp256r1`: tls.CurveP256,
		`CurveP256`: tls.CurveP256,
		`secp384r1`: tls.CurveP384,
		`CurveP384`: tls.CurveP384,
		`secp521r1`: tls.CurveP521,
		`CurveP521`: tls.CurveP521,
		`x25519`:    tls.X25519,
		`X25519`:    tls.X25519,
	}
)

// Certificate holds a SSL cert/key pair
// Certs and Key could be either a file path, or the file content itself
type Certificate struct {
	CertFile FileOrContent `json:"certFile,omitempty" toml:"certFile,omitempty" yaml:"certFile,omitempty"`
	KeyFile  FileOrContent `json:"keyFile,omitempty" toml:"keyFile,omitempty" yaml:"keyFile,omitempty"`
}

// Certificates defines traefik certificates type
// Certs and Keys could be either a file path, or the file content itself
type Certificates []Certificate

// FileOrContent hold a file path or content
type FileOrContent string

func (f FileOrContent) String() string {
	return string(f)
}

// IsPath returns true if the FileOrContent is a file path, otherwise returns false
func (f FileOrContent) IsPath() bool {
	_, err := os.Stat(f.String())
	return err == nil
}

func (f FileOrContent) Read() ([]byte, error) {
	var content []byte
	if f.IsPath() {
		var err error
		content, err = ioutil.ReadFile(f.String())
		if err != nil {
			return nil, err
		}
	} else {
		content = []byte(f)
	}
	return content, nil
}

// CreateTLSConfig creates a TLS config from Certificate structures
func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, error) {
	config := &tls.Config{}
	domainsCertificates := make(map[string]map[string]*tls.Certificate)

	if c.isEmpty() {
		config.Certificates = []tls.Certificate{}

		cert, err := generate.DefaultCertificate()
		if err != nil {
			return nil, err
		}

		config.Certificates = append(config.Certificates, *cert)
	} else {
		for _, certificate := range *c {
			err := certificate.AppendCertificate(domainsCertificates, entryPointName)
			if err != nil {
				log.Errorf("Unable to add a certificate to the entryPoint %q : %v", entryPointName, err)
				continue
			}

			for _, certDom := range domainsCertificates {
				for _, cert := range certDom {
					config.Certificates = append(config.Certificates, *cert)
				}
			}
		}
	}
	return config, nil
}

// isEmpty checks if the certificates list is empty
func (c *Certificates) isEmpty() bool {
	if len(*c) == 0 {
		return true
	}
	var key int
	for _, cert := range *c {
		if len(cert.CertFile.String()) != 0 && len(cert.KeyFile.String()) != 0 {
			break
		}
		key++
	}
	return key == len(*c)
}

// AppendCertificate appends a Certificate to a certificates map keyed by entrypoint.
func (c *Certificate) AppendCertificate(certs map[string]map[string]*tls.Certificate, ep string) error {
	certContent, err := c.CertFile.Read()
	if err != nil {
		return fmt.Errorf("unable to read CertFile : %v", err)
	}

	keyContent, err := c.KeyFile.Read()
	if err != nil {
		return fmt.Errorf("unable to read KeyFile : %v", err)
	}
	tlsCert, err := tls.X509KeyPair(certContent, keyContent)
	if err != nil {
		return fmt.Errorf("unable to generate TLS certificate : %v", err)
	}

	parsedCert, _ := x509.ParseCertificate(tlsCert.Certificate[0])

	var SANs []string
	if parsedCert.Subject.CommonName != "" {
		SANs = append(SANs, strings.ToLower(parsedCert.Subject.CommonName))
	}
	if parsedCert.DNSNames != nil {
		sort.Strings(parsedCert.DNSNames)
		for _, dnsName := range parsedCert.DNSNames {
			if dnsName != parsedCert.Subject.CommonName {
				SANs = append(SANs, strings.ToLower(dnsName))
			}
		}
	}
	if parsedCert.IPAddresses != nil {
		for _, ip := range parsedCert.IPAddresses {
			if ip.String() != parsedCert.Subject.CommonName {
				SANs = append(SANs, strings.ToLower(ip.String()))
			}
		}
	}
	certKey := strings.Join(SANs, ",")

	certExists := false
	if certs[ep] == nil {
		certs[ep] = make(map[string]*tls.Certificate)
	} else {
		for domains := range certs[ep] {
			if domains == certKey {
				certExists = true
				break
			}
		}
	}
	if certExists {
		log.Debugf("Skipping addition of certificate for domain(s) %q, to EntryPoint %s, as it already exists for this Entrypoint.", certKey, ep)
	} else {
		log.Debugf("Adding certificate for domain(s) %s", certKey)
		certs[ep][certKey] = &tlsCert
	}

	return err
}

// GetTruncatedCertificateName truncates the certificate name
func (c *Certificate) GetTruncatedCertificateName() string {
	certName := c.CertFile.String()

	// Truncate certificate information only if it's a well formed certificate content with more than 50 characters
	if !c.CertFile.IsPath() && strings.HasPrefix(certName, certificateHeader) && len(certName) > len(certificateHeader)+50 {
		certName = strings.TrimPrefix(c.CertFile.String(), certificateHeader)[:50]
	}

	return certName
}

// String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics.
func (c *Certificates) String() string {
	if len(*c) == 0 {
		return ""
	}
	var result []string
	for _, certificate := range *c {
		result = append(result, certificate.CertFile.String()+","+certificate.KeyFile.String())
	}
	return strings.Join(result, ";")
}

// Set is the method to set the flag value, part of the flag.Value interface.
// Set's argument is a string to be parsed to set the flag.
// It's a comma-separated list, so we split it.
func (c *Certificates) Set(value string) error {
	certificates := strings.Split(value, ";")
	for _, certificate := range certificates {
		files := strings.Split(certificate, ",")
		if len(files) != 2 {
			return fmt.Errorf("bad certificates format: %s", value)
		}
		*c = append(*c, Certificate{
			CertFile: FileOrContent(files[0]),
			KeyFile:  FileOrContent(files[1]),
		})
	}
	return nil
}

// Type is type of the struct
func (c *Certificates) Type() string {
	return "certificates"
}
<commit_msg>Fix typo in the godoc of TLS option MaxVersion<commit_after>package tls

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"strings"

	"github.com/containous/traefik/v2/pkg/log"
	"github.com/containous/traefik/v2/pkg/tls/generate"
)

var (
	// MinVersion Map of allowed TLS minimum versions
	MinVersion = map[string]uint16{
		`VersionTLS10`: tls.VersionTLS10,
		`VersionTLS11`: tls.VersionTLS11,
		`VersionTLS12`: tls.VersionTLS12,
		`VersionTLS13`: tls.VersionTLS13,
	}

	// MaxVersion Map of allowed TLS maximum versions
	MaxVersion = map[string]uint16{
		`VersionTLS10`: tls.VersionTLS10,
		`VersionTLS11`: tls.VersionTLS11,
		`VersionTLS12`: tls.VersionTLS12,
		`VersionTLS13`: tls.VersionTLS13,
	}

	// CipherSuites Map of TLS CipherSuites from crypto/tls
	// Available CipherSuites defined at https://golang.org/pkg/crypto/tls/#pkg-constants
	CipherSuites = map[string]uint16{
		`TLS_RSA_WITH_RC4_128_SHA`:                tls.TLS_RSA_WITH_RC4_128_SHA,
		`TLS_RSA_WITH_3DES_EDE_CBC_SHA`:           tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
		`TLS_RSA_WITH_AES_128_CBC_SHA`:            tls.TLS_RSA_WITH_AES_128_CBC_SHA,
		`TLS_RSA_WITH_AES_256_CBC_SHA`:            tls.TLS_RSA_WITH_AES_256_CBC_SHA,
		`TLS_RSA_WITH_AES_128_CBC_SHA256`:         tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
		`TLS_RSA_WITH_AES_128_GCM_SHA256`:         tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
		`TLS_RSA_WITH_AES_256_GCM_SHA384`:         tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
		`TLS_ECDHE_ECDSA_WITH_RC4_128_SHA`:        tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
		`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`:    tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
		`TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`:    tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
		`TLS_ECDHE_RSA_WITH_RC4_128_SHA`:          tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
		`TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`:     tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
		`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`:      tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
		`TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`:      tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
		`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
		`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256`:   tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
		`TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`:   tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
		`TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		`TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`:   tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
		`TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
		`TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305`:    tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
		`TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305`:  tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
		"TLS_AES_128_GCM_SHA256":                  tls.TLS_AES_128_GCM_SHA256,
		"TLS_AES_256_GCM_SHA384":                  tls.TLS_AES_256_GCM_SHA384,
		"TLS_CHACHA20_POLY1305_SHA256":            tls.TLS_CHACHA20_POLY1305_SHA256,
		"TLS_FALLBACK_SCSV":                       tls.TLS_FALLBACK_SCSV,
	}

	// CurveIDs is a Map of TLS elliptic curves from crypto/tls
	// Available CurveIDs defined at https://godoc.org/crypto/tls#CurveID,
	// also allowing rfc names defined at https://tools.ietf.org/html/rfc8446#section-4.2.7
	CurveIDs = map[string]tls.CurveID{
		`secp256r1`: tls.CurveP256,
		`CurveP256`: tls.CurveP256,
		`secp384r1`: tls.CurveP384,
		`CurveP384`: tls.CurveP384,
		`secp521r1`: tls.CurveP521,
		`CurveP521`: tls.CurveP521,
		`x25519`:    tls.X25519,
		`X25519`:    tls.X25519,
	}
)

// Certificate holds a SSL cert/key pair
// Certs and Key could be either a file path, or the file content itself
type Certificate struct {
	CertFile FileOrContent `json:"certFile,omitempty" toml:"certFile,omitempty" yaml:"certFile,omitempty"`
	KeyFile  FileOrContent `json:"keyFile,omitempty" toml:"keyFile,omitempty" yaml:"keyFile,omitempty"`
}

// Certificates defines traefik certificates type
// Certs and Keys could be either a file path, or the file content itself
type Certificates []Certificate

// FileOrContent hold a file path or content
type FileOrContent string

func (f FileOrContent) String() string {
	return string(f)
}

// IsPath returns true if the FileOrContent is a file path, otherwise returns false
func (f FileOrContent) IsPath() bool {
	_, err := os.Stat(f.String())
	return err == nil
}

func (f FileOrContent) Read() ([]byte, error) {
	var content []byte
	if f.IsPath() {
		var err error
		content, err = ioutil.ReadFile(f.String())
		if err != nil {
			return nil, err
		}
	} else {
		content = []byte(f)
	}
	return content, nil
}

// CreateTLSConfig creates a TLS config from Certificate structures
func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, error) {
	config := &tls.Config{}
	domainsCertificates := make(map[string]map[string]*tls.Certificate)

	if c.isEmpty() {
		config.Certificates = []tls.Certificate{}

		cert, err := generate.DefaultCertificate()
		if err != nil {
			return nil, err
		}

		config.Certificates = append(config.Certificates, *cert)
	} else {
		for _, certificate := range *c {
			err := certificate.AppendCertificate(domainsCertificates, entryPointName)
			if err != nil {
				log.Errorf("Unable to add a certificate to the entryPoint %q : %v", entryPointName, err)
				continue
			}

			for _, certDom := range domainsCertificates {
				for _, cert := range certDom {
					config.Certificates = append(config.Certificates, *cert)
				}
			}
		}
	}
	return config, nil
}

// isEmpty checks if the certificates list is empty
func (c *Certificates) isEmpty() bool {
	if len(*c) == 0 {
		return true
	}
	var key int
	for _, cert := range *c {
		if len(cert.CertFile.String()) != 0 && len(cert.KeyFile.String()) != 0 {
			break
		}
		key++
	}
	return key == len(*c)
}

// AppendCertificate appends a Certificate to a certificates map keyed by entrypoint.
func (c *Certificate) AppendCertificate(certs map[string]map[string]*tls.Certificate, ep string) error {
	certContent, err := c.CertFile.Read()
	if err != nil {
		return fmt.Errorf("unable to read CertFile : %v", err)
	}

	keyContent, err := c.KeyFile.Read()
	if err != nil {
		return fmt.Errorf("unable to read KeyFile : %v", err)
	}
	tlsCert, err := tls.X509KeyPair(certContent, keyContent)
	if err != nil {
		return fmt.Errorf("unable to generate TLS certificate : %v", err)
	}

	parsedCert, _ := x509.ParseCertificate(tlsCert.Certificate[0])

	var SANs []string
	if parsedCert.Subject.CommonName != "" {
		SANs = append(SANs, strings.ToLower(parsedCert.Subject.CommonName))
	}
	if parsedCert.DNSNames != nil {
		sort.Strings(parsedCert.DNSNames)
		for _, dnsName := range parsedCert.DNSNames {
			if dnsName != parsedCert.Subject.CommonName {
				SANs = append(SANs, strings.ToLower(dnsName))
			}
		}
	}
	if parsedCert.IPAddresses != nil {
		for _, ip := range parsedCert.IPAddresses {
			if ip.String() != parsedCert.Subject.CommonName {
				SANs = append(SANs, strings.ToLower(ip.String()))
			}
		}
	}
	certKey := strings.Join(SANs, ",")

	certExists := false
	if certs[ep] == nil {
		certs[ep] = make(map[string]*tls.Certificate)
	} else {
		for domains := range certs[ep] {
			if domains == certKey {
				certExists = true
				break
			}
		}
	}
	if certExists {
		log.Debugf("Skipping addition of certificate for domain(s) %q, to EntryPoint %s, as it already exists for this Entrypoint.", certKey, ep)
	} else {
		log.Debugf("Adding certificate for domain(s) %s", certKey)
		certs[ep][certKey] = &tlsCert
	}

	return err
}

// GetTruncatedCertificateName truncates the certificate name
func (c *Certificate) GetTruncatedCertificateName() string {
	certName := c.CertFile.String()

	// Truncate certificate information only if it's a well formed certificate content with more than 50 characters
	if !c.CertFile.IsPath() && strings.HasPrefix(certName, certificateHeader) && len(certName) > len(certificateHeader)+50 {
		certName = strings.TrimPrefix(c.CertFile.String(), certificateHeader)[:50]
	}

	return certName
}

// String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics.
func (c *Certificates) String() string {
	if len(*c) == 0 {
		return ""
	}
	var result []string
	for _, certificate := range *c {
		result = append(result, certificate.CertFile.String()+","+certificate.KeyFile.String())
	}
	return strings.Join(result, ";")
}

// Set is the method to set the flag value, part of the flag.Value interface.
// Set's argument is a string to be parsed to set the flag.
// It's a comma-separated list, so we split it.
func (c *Certificates) Set(value string) error {
	certificates := strings.Split(value, ";")
	for _, certificate := range certificates {
		files := strings.Split(certificate, ",")
		if len(files) != 2 {
			return fmt.Errorf("bad certificates format: %s", value)
		}
		*c = append(*c, Certificate{
			CertFile: FileOrContent(files[0]),
			KeyFile:  FileOrContent(files[1]),
		})
	}
	return nil
}

// Type is type of the struct
func (c *Certificates) Type() string {
	return "certificates"
}
<|endoftext|>