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

package k8s

import (
	k8sconst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
	"github.com/cilium/cilium/pkg/labels"
	"github.com/cilium/cilium/pkg/policy"
	"github.com/cilium/cilium/pkg/policy/api"

	networkingv1 "k8s.io/api/networking/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// GetPolicyLabelsv1 extracts the name of policy name
func GetPolicyLabelsv1(np *networkingv1.NetworkPolicy) labels.LabelArray {
	policyName := np.Annotations[AnnotationName]
	if policyName == "" {
		policyName = np.Name
	}

	ns := k8sconst.ExtractNamespace(&np.ObjectMeta)

	return k8sconst.GetPolicyLabels(ns, policyName)
}

func parseNetworkPolicyPeer(namespace string, peer *networkingv1.NetworkPolicyPeer) *api.EndpointSelector {
	var labelSelector *metav1.LabelSelector

	// Only one or the other can be set, not both
	if peer.PodSelector != nil {
		labelSelector = peer.PodSelector
		if peer.PodSelector.MatchLabels == nil {
			peer.PodSelector.MatchLabels = map[string]string{}
		}
		// The PodSelector should only reflect to the same namespace
		// the policy is being stored, thus we add the namespace to
		// the MatchLabels map.
		peer.PodSelector.MatchLabels[k8sconst.PodNamespaceLabel] = namespace
	} else if peer.NamespaceSelector != nil {
		labelSelector = peer.NamespaceSelector
		matchLabels := map[string]string{}
		// We use our own special label prefix for namespace metadata,
		// thus we need to prefix that prefix to all NamespaceSelector.MatchLabels
		for k, v := range peer.NamespaceSelector.MatchLabels {
			matchLabels[policy.JoinPath(PodNamespaceMetaLabels, k)] = v
		}
		peer.NamespaceSelector.MatchLabels = matchLabels

		// We use our own special label prefix for namespace metadata,
		// thus we need to prefix that prefix to all NamespaceSelector.MatchLabels
		for i, lsr := range peer.NamespaceSelector.MatchExpressions {
			lsr.Key = policy.JoinPath(PodNamespaceMetaLabels, lsr.Key)
			peer.NamespaceSelector.MatchExpressions[i] = lsr
		}
	} else {
		// Neither PodSelector nor NamespaceSelector set.
		return nil
	}

	selector := api.NewESFromK8sLabelSelector(labels.LabelSourceK8sKeyPrefix, labelSelector)
	return &selector
}

// ParseNetworkPolicy parses a k8s NetworkPolicy. Returns a list of
// Cilium policy rules that can be added, along with an error if there was an
// error sanitizing the rules.
func ParseNetworkPolicy(np *networkingv1.NetworkPolicy) (api.Rules, error) {
	ingresses := []api.IngressRule{}
	egresses := []api.EgressRule{}

	namespace := k8sconst.ExtractNamespace(&np.ObjectMeta)
	for _, iRule := range np.Spec.Ingress {
		ingress := api.IngressRule{}
		if iRule.From != nil && len(iRule.From) > 0 {
			for _, rule := range iRule.From {
				endpointSelector := parseNetworkPolicyPeer(namespace, &rule)

				// Case where no label-based selectors were in rule.
				if endpointSelector != nil {
					ingress.FromEndpoints = append(ingress.FromEndpoints, *endpointSelector)
				}

				// Parse CIDR-based parts of rule.
				if rule.IPBlock != nil {
					ingress.FromCIDRSet = append(ingress.FromCIDRSet, ipBlockToCIDRRule(rule.IPBlock))
				}
			}
		}

		if iRule.Ports != nil && len(iRule.Ports) > 0 {
			ingress.ToPorts = parsePorts(iRule.Ports)
		} else if iRule.From == nil || len(iRule.From) == 0 {
			// Based on NetworkPolicyIngressRule docs:
			//   From []NetworkPolicyPeer
			//   If this field is empty or missing, this rule matches all
			//   sources (traffic not restricted by source).
			all := api.NewESFromLabels(
				labels.NewLabel(labels.IDNameAll, "", labels.LabelSourceReserved),
			)
			ingress.FromEndpoints = append(ingress.FromEndpoints, all)
		}

		ingresses = append(ingresses, ingress)
	}

	for _, eRule := range np.Spec.Egress {
		egress := api.EgressRule{}
		if eRule.To != nil && len(eRule.To) > 0 {
			for _, rule := range eRule.To {
				if rule.NamespaceSelector != nil || rule.PodSelector != nil {
					// TODO: GH-2095
					log.Warning("Cilium does not support PodSelector or NamespaceSelector for K8s Egress rules")
				}
				if rule.IPBlock != nil {
					egress.ToCIDRSet = append(egress.ToCIDRSet, ipBlockToCIDRRule(rule.IPBlock))
				}
			}
		}
		egresses = append(egresses, egress)
	}

	if np.Spec.PodSelector.MatchLabels == nil {
		np.Spec.PodSelector.MatchLabels = map[string]string{}
	}
	np.Spec.PodSelector.MatchLabels[k8sconst.PodNamespaceLabel] = namespace

	rule := &api.Rule{
		EndpointSelector: api.NewESFromK8sLabelSelector(labels.LabelSourceK8sKeyPrefix, &np.Spec.PodSelector),
		Labels:           GetPolicyLabelsv1(np),
		Ingress:          ingresses,
		Egress:           egresses,
	}

	if err := rule.Sanitize(); err != nil {
		return nil, err
	}

	return api.Rules{rule}, nil
}

func ipBlockToCIDRRule(block *networkingv1.IPBlock) api.CIDRRule {
	cidrRule := api.CIDRRule{}
	cidrRule.Cidr = api.CIDR(block.CIDR)
	for _, v := range block.Except {
		cidrRule.ExceptCIDRs = append(cidrRule.ExceptCIDRs, api.CIDR(v))
	}
	return cidrRule
}

// Converts list of K8s NetworkPolicyPorts to Cilium PortRules.
// Assumes that provided list of NetworkPolicyPorts is not nil.
func parsePorts(ports []networkingv1.NetworkPolicyPort) []api.PortRule {
	portRules := []api.PortRule{}
	for _, port := range ports {
		if port.Protocol == nil && port.Port == nil {
			continue
		}

		protocol := api.ProtoTCP
		if port.Protocol != nil {
			protocol, _ = api.ParseL4Proto(string(*port.Protocol))
		}

		portStr := ""
		if port.Port != nil {
			portStr = port.Port.String()
		}

		portRule := api.PortRule{
			Ports: []api.PortProtocol{
				{Port: portStr, Protocol: protocol},
			},
		}

		portRules = append(portRules, portRule)
	}

	return portRules
}
<commit_msg>pkg/k8s: log when NetworkPolicy does not have Pod / Namespace Selector<commit_after>// Copyright 2016-2017 Authors of Cilium
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package k8s

import (
	k8sconst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
	"github.com/cilium/cilium/pkg/labels"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/policy"
	"github.com/cilium/cilium/pkg/policy/api"

	networkingv1 "k8s.io/api/networking/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// GetPolicyLabelsv1 extracts the name of policy name
func GetPolicyLabelsv1(np *networkingv1.NetworkPolicy) labels.LabelArray {
	policyName := np.Annotations[AnnotationName]
	if policyName == "" {
		policyName = np.Name
	}

	ns := k8sconst.ExtractNamespace(&np.ObjectMeta)

	return k8sconst.GetPolicyLabels(ns, policyName)
}

func parseNetworkPolicyPeer(namespace string, peer *networkingv1.NetworkPolicyPeer) *api.EndpointSelector {
	var labelSelector *metav1.LabelSelector

	// Only one or the other can be set, not both
	if peer.PodSelector != nil {
		labelSelector = peer.PodSelector
		if peer.PodSelector.MatchLabels == nil {
			peer.PodSelector.MatchLabels = map[string]string{}
		}
		// The PodSelector should only reflect to the same namespace
		// the policy is being stored, thus we add the namespace to
		// the MatchLabels map.
		peer.PodSelector.MatchLabels[k8sconst.PodNamespaceLabel] = namespace
	} else if peer.NamespaceSelector != nil {
		labelSelector = peer.NamespaceSelector
		matchLabels := map[string]string{}
		// We use our own special label prefix for namespace metadata,
		// thus we need to prefix that prefix to all NamespaceSelector.MatchLabels
		for k, v := range peer.NamespaceSelector.MatchLabels {
			matchLabels[policy.JoinPath(PodNamespaceMetaLabels, k)] = v
		}
		peer.NamespaceSelector.MatchLabels = matchLabels

		// We use our own special label prefix for namespace metadata,
		// thus we need to prefix that prefix to all NamespaceSelector.MatchLabels
		for i, lsr := range peer.NamespaceSelector.MatchExpressions {
			lsr.Key = policy.JoinPath(PodNamespaceMetaLabels, lsr.Key)
			peer.NamespaceSelector.MatchExpressions[i] = lsr
		}
	} else {
		// Neither PodSelector nor NamespaceSelector set.
		return nil
	}

	selector := api.NewESFromK8sLabelSelector(labels.LabelSourceK8sKeyPrefix, labelSelector)
	return &selector
}

// ParseNetworkPolicy parses a k8s NetworkPolicy. Returns a list of
// Cilium policy rules that can be added, along with an error if there was an
// error sanitizing the rules.
func ParseNetworkPolicy(np *networkingv1.NetworkPolicy) (api.Rules, error) {
	ingresses := []api.IngressRule{}
	egresses := []api.EgressRule{}

	namespace := k8sconst.ExtractNamespace(&np.ObjectMeta)
	for _, iRule := range np.Spec.Ingress {
		ingress := api.IngressRule{}
		if iRule.From != nil && len(iRule.From) > 0 {
			for _, rule := range iRule.From {
				endpointSelector := parseNetworkPolicyPeer(namespace, &rule)

				if endpointSelector != nil {
					ingress.FromEndpoints = append(ingress.FromEndpoints, *endpointSelector)
				} else {
					// No label-based selectors were in NetworkPolicyPeer.
					log.WithField(logfields.K8sNetworkPolicyName, np.Name).Debug("NetworkPolicyPeer does not have PodSelector or NamespaceSelector")
				}

				// Parse CIDR-based parts of rule.
				if rule.IPBlock != nil {
					ingress.FromCIDRSet = append(ingress.FromCIDRSet, ipBlockToCIDRRule(rule.IPBlock))
				}
			}
		}

		if iRule.Ports != nil && len(iRule.Ports) > 0 {
			ingress.ToPorts = parsePorts(iRule.Ports)
		} else if iRule.From == nil || len(iRule.From) == 0 {
			// Based on NetworkPolicyIngressRule docs:
			//   From []NetworkPolicyPeer
			//   If this field is empty or missing, this rule matches all
			//   sources (traffic not restricted by source).
			all := api.NewESFromLabels(
				labels.NewLabel(labels.IDNameAll, "", labels.LabelSourceReserved),
			)
			ingress.FromEndpoints = append(ingress.FromEndpoints, all)
		}

		ingresses = append(ingresses, ingress)
	}

	for _, eRule := range np.Spec.Egress {
		egress := api.EgressRule{}
		if eRule.To != nil && len(eRule.To) > 0 {
			for _, rule := range eRule.To {
				if rule.NamespaceSelector != nil || rule.PodSelector != nil {
					// TODO: GH-2095
					log.Warning("Cilium does not support PodSelector or NamespaceSelector for K8s Egress rules")
				}
				if rule.IPBlock != nil {
					egress.ToCIDRSet = append(egress.ToCIDRSet, ipBlockToCIDRRule(rule.IPBlock))
				}
			}
		}
		egresses = append(egresses, egress)
	}

	if np.Spec.PodSelector.MatchLabels == nil {
		np.Spec.PodSelector.MatchLabels = map[string]string{}
	}
	np.Spec.PodSelector.MatchLabels[k8sconst.PodNamespaceLabel] = namespace

	rule := &api.Rule{
		EndpointSelector: api.NewESFromK8sLabelSelector(labels.LabelSourceK8sKeyPrefix, &np.Spec.PodSelector),
		Labels:           GetPolicyLabelsv1(np),
		Ingress:          ingresses,
		Egress:           egresses,
	}

	if err := rule.Sanitize(); err != nil {
		return nil, err
	}

	return api.Rules{rule}, nil
}

func ipBlockToCIDRRule(block *networkingv1.IPBlock) api.CIDRRule {
	cidrRule := api.CIDRRule{}
	cidrRule.Cidr = api.CIDR(block.CIDR)
	for _, v := range block.Except {
		cidrRule.ExceptCIDRs = append(cidrRule.ExceptCIDRs, api.CIDR(v))
	}
	return cidrRule
}

// Converts list of K8s NetworkPolicyPorts to Cilium PortRules.
// Assumes that provided list of NetworkPolicyPorts is not nil.
func parsePorts(ports []networkingv1.NetworkPolicyPort) []api.PortRule {
	portRules := []api.PortRule{}
	for _, port := range ports {
		if port.Protocol == nil && port.Port == nil {
			continue
		}

		protocol := api.ProtoTCP
		if port.Protocol != nil {
			protocol, _ = api.ParseL4Proto(string(*port.Protocol))
		}

		portStr := ""
		if port.Port != nil {
			portStr = port.Port.String()
		}

		portRule := api.PortRule{
			Ports: []api.PortProtocol{
				{Port: portStr, Protocol: protocol},
			},
		}

		portRules = append(portRules, portRule)
	}

	return portRules
}
<|endoftext|>


<commit_before>package ginkgo

import (
	"flag"
	"testing"
	"time"
)

var suiteRandomSeed = flag.Int64("seed", time.Now().Unix(), "The seed used to randomize the spec suite.")
var suiteRandomizeAllSpecs = flag.Bool("randomizeAllSpecs", false, "If set, ginkgo will randomize all specs together.  By default, ginkgo only randomizes the top level Describe/Context groups.")
var reporterNoColor = flag.Bool("noColor", false, "If set, suppress color output in default reporter.")
var reporterSlowSpecThreshold = flag.Float64("slowSpecThreshold", 5.0, "(in seconds) Specs that take longer to run than this threshold are flagged as slow by the default reporter (default: 5 seconds).")
var reporterNoisyPendings = flag.Bool("noisyPendings", true, "If set, shout about pending tests.")

var globalSuite *suite

func init() {
	globalSuite = newSuite()
}

func RunSpecs(t *testing.T, description string) {
	reporter := newDefaultReporter(*reporterNoColor, *reporterSlowSpecThreshold, *reporterNoisyPendings)
	RunSpecsWithCustomReporter(t, description, reporter)
}

func RunSpecsWithCustomReporter(t *testing.T, description string, reporter Reporter) {
	globalSuite.run(t, description, *suiteRandomSeed, *suiteRandomizeAllSpecs, reporter)
}

type Done chan<- interface{}

func Fail(message string, callerSkip ...int) {
	skip := 0
	if len(callerSkip) > 0 {
		skip = callerSkip[0]
	}
	globalSuite.fail(message, skip)
}

func Describe(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypeNone, generateCodeLocation(1))
	return true
}

func FDescribe(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypeFocused, generateCodeLocation(1))
	return true
}

func PDescribe(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypePending, generateCodeLocation(1))
	return true
}

func XDescribe(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypePending, generateCodeLocation(1))
	return true
}

func Context(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypeNone, generateCodeLocation(1))
	return true
}

func FContext(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypeFocused, generateCodeLocation(1))
	return true
}

func PContext(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypePending, generateCodeLocation(1))
	return true
}

func XContext(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypePending, generateCodeLocation(1))
	return true
}

func It(text string, body interface{}, timeout ...float64) bool {
	globalSuite.pushItNode(text, body, flagTypeNone, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func FIt(text string, body interface{}, timeout ...float64) bool {
	globalSuite.pushItNode(text, body, flagTypeFocused, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func PIt(text string, body interface{}, timeout ...float64) bool {
	globalSuite.pushItNode(text, body, flagTypePending, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func XIt(text string, body interface{}, timeout ...float64) bool {
	globalSuite.pushItNode(text, body, flagTypePending, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func BeforeEach(body interface{}, timeout ...float64) bool {
	globalSuite.pushBeforeEachNode(body, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func JustBeforeEach(body interface{}, timeout ...float64) bool {
	globalSuite.pushJustBeforeEachNode(body, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func AfterEach(body interface{}, timeout ...float64) bool {
	globalSuite.pushAfterEachNode(body, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func parseTimeout(timeout ...float64) time.Duration {
	if len(timeout) == 0 {
		return time.Duration(5 * time.Second)
	} else {
		return time.Duration(timeout[0] * float64(time.Second))
	}
}
<commit_msg>namespaced flags<commit_after>package ginkgo

import (
	"flag"
	"testing"
	"time"
)

var suiteRandomSeed = flag.Int64("ginkgo.seed", time.Now().Unix(), "The seed used to randomize the spec suite.")
var suiteRandomizeAllSpecs = flag.Bool("ginkgo.randomizeAllSpecs", false, "If set, ginkgo will randomize all specs together.  By default, ginkgo only randomizes the top level Describe/Context groups.")
var reporterNoColor = flag.Bool("ginkgo.noColor", false, "If set, suppress color output in default reporter.")
var reporterSlowSpecThreshold = flag.Float64("ginkgo.slowSpecThreshold", 5.0, "(in seconds) Specs that take longer to run than this threshold are flagged as slow by the default reporter (default: 5 seconds).")
var reporterNoisyPendings = flag.Bool("ginkgo.noisyPendings", true, "If set, default reporter will shout about pending tests.")

var globalSuite *suite

func init() {
	globalSuite = newSuite()
}

func RunSpecs(t *testing.T, description string) {
	reporter := newDefaultReporter(*reporterNoColor, *reporterSlowSpecThreshold, *reporterNoisyPendings)
	RunSpecsWithCustomReporter(t, description, reporter)
}

func RunSpecsWithCustomReporter(t *testing.T, description string, reporter Reporter) {
	globalSuite.run(t, description, *suiteRandomSeed, *suiteRandomizeAllSpecs, reporter)
}

type Done chan<- interface{}

func Fail(message string, callerSkip ...int) {
	skip := 0
	if len(callerSkip) > 0 {
		skip = callerSkip[0]
	}
	globalSuite.fail(message, skip)
}

func Describe(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypeNone, generateCodeLocation(1))
	return true
}

func FDescribe(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypeFocused, generateCodeLocation(1))
	return true
}

func PDescribe(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypePending, generateCodeLocation(1))
	return true
}

func XDescribe(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypePending, generateCodeLocation(1))
	return true
}

func Context(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypeNone, generateCodeLocation(1))
	return true
}

func FContext(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypeFocused, generateCodeLocation(1))
	return true
}

func PContext(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypePending, generateCodeLocation(1))
	return true
}

func XContext(text string, body func()) bool {
	globalSuite.pushContainerNode(text, body, flagTypePending, generateCodeLocation(1))
	return true
}

func It(text string, body interface{}, timeout ...float64) bool {
	globalSuite.pushItNode(text, body, flagTypeNone, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func FIt(text string, body interface{}, timeout ...float64) bool {
	globalSuite.pushItNode(text, body, flagTypeFocused, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func PIt(text string, body interface{}, timeout ...float64) bool {
	globalSuite.pushItNode(text, body, flagTypePending, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func XIt(text string, body interface{}, timeout ...float64) bool {
	globalSuite.pushItNode(text, body, flagTypePending, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func BeforeEach(body interface{}, timeout ...float64) bool {
	globalSuite.pushBeforeEachNode(body, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func JustBeforeEach(body interface{}, timeout ...float64) bool {
	globalSuite.pushJustBeforeEachNode(body, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func AfterEach(body interface{}, timeout ...float64) bool {
	globalSuite.pushAfterEachNode(body, generateCodeLocation(1), parseTimeout(timeout...))
	return true
}

func parseTimeout(timeout ...float64) time.Duration {
	if len(timeout) == 0 {
		return time.Duration(5 * time.Second)
	} else {
		return time.Duration(timeout[0] * float64(time.Second))
	}
}
<|endoftext|>


<commit_before>package ingress

import (
	kubeModels "github.com/containerum/kube-client/pkg/model"
)

type Rule struct {
	Host      string   `json:"host"`
	TLSSecret string   `json:"tls_secret,omitempty"`
	Paths     PathList `json:"paths"`
}

func RuleFromKube(kubeRule kubeModels.Rule) Rule {
	rule := Rule{
		Host:  kubeRule.Host,
		Paths: PathListFromKube(kubeRule.Path),
	}

	if kubeRule.TLSSecret != nil {
		rule.TLSSecret = *kubeRule.TLSSecret
	}
	return rule
}

func (rule Rule) ToKube() kubeModels.Rule {
	kubeRule := kubeModels.Rule{
		Host: rule.Host,
		Path: rule.Paths.ToKube(),
	}
	if rule.TLSSecret != "" {
		kubeRule.TLSSecret = &rule.TLSSecret
	}
	return kubeRule
}

func (rule Rule) Copy() Rule {
	return Rule{
		Host:      rule.Host,
		TLSSecret: rule.TLSSecret,
		Paths:     rule.Paths.Copy(),
	}
}

type RuleList []Rule

func RuleListFromKube(kubeList []kubeModels.Rule) RuleList {
	var list RuleList = make([]Rule, 0, len(kubeList))
	for _, rule := range kubeList {
		list = append(list, RuleFromKube(rule))
	}
	return list
}

func (list RuleList) ToKube() []kubeModels.Rule {
	kubeList := make([]kubeModels.Rule, 0, len(list))
	for _, rule := range list {
		kubeList = append(kubeList, rule.ToKube())
	}
	return kubeList
}

func (list RuleList) Len() int {
	return len(list)
}

func (list RuleList) Empty() bool {
	return list.Len() == 0
}

func (list RuleList) Head() Rule {
	if list.Empty() {
		return Rule{}
	}
	return list[0].Copy()
}

func (list RuleList) Copy() RuleList {
	cp := append(RuleList{}, list...)
	for i, rule := range cp {
		cp[i] = rule.Copy()
	}
	return cp
}

func (list RuleList) Delete(i int) RuleList {
	cp := list.Copy()
	return append(cp[:i], cp[i+1:]...)
}

func (list RuleList) Append(rules ...Rule) RuleList {
	return append(list.Copy(), rules...)
}

func (list RuleList) Hosts() []string {
	hosts := make([]string, 0, len(list))
	for _, rule := range list {
		hosts = append(hosts, rule.Host)
	}
	return hosts
}

func (list RuleList) Paths() PathList {
	var paths = make(PathList, 0, len(list))
	for _, rule := range list {
		paths = append(paths, rule.Paths.Copy()...)
	}
	return paths
}

func (list RuleList) Services() []Service {
	var services = make([]Service, 0, len(list))
	for _, rule := range list {
		services = append(services, rule.Paths.Services()...)
	}
	return services
}

func (list RuleList) ServicesNames() []string {
	var services = make([]string, 0, len(list))
	for _, rule := range list {
		services = append(services, rule.Paths.ServicesNames()...)
	}
	return services
}

func (list RuleList) ServicesTableView() []string {
	var services = make([]string, 0, len(list))
	for _, rule := range list {
		services = append(services, rule.Paths.ServicesTableView()...)
	}
	return services
}
<commit_msg>add empty service list signalisation<commit_after>package ingress

import (
	kubeModels "github.com/containerum/kube-client/pkg/model"
)

type Rule struct {
	Host      string   `json:"host"`
	TLSSecret string   `json:"tls_secret,omitempty"`
	Paths     PathList `json:"paths"`
}

func RuleFromKube(kubeRule kubeModels.Rule) Rule {
	rule := Rule{
		Host:  kubeRule.Host,
		Paths: PathListFromKube(kubeRule.Path),
	}

	if kubeRule.TLSSecret != nil {
		rule.TLSSecret = *kubeRule.TLSSecret
	}
	return rule
}

func (rule Rule) ToKube() kubeModels.Rule {
	kubeRule := kubeModels.Rule{
		Host: rule.Host,
		Path: rule.Paths.ToKube(),
	}
	if rule.TLSSecret != "" {
		kubeRule.TLSSecret = &rule.TLSSecret
	}
	return kubeRule
}

func (rule Rule) Copy() Rule {
	return Rule{
		Host:      rule.Host,
		TLSSecret: rule.TLSSecret,
		Paths:     rule.Paths.Copy(),
	}
}

type RuleList []Rule

func RuleListFromKube(kubeList []kubeModels.Rule) RuleList {
	var list RuleList = make([]Rule, 0, len(kubeList))
	for _, rule := range kubeList {
		list = append(list, RuleFromKube(rule))
	}
	return list
}

func (list RuleList) ToKube() []kubeModels.Rule {
	kubeList := make([]kubeModels.Rule, 0, len(list))
	for _, rule := range list {
		kubeList = append(kubeList, rule.ToKube())
	}
	return kubeList
}

func (list RuleList) Len() int {
	return len(list)
}

func (list RuleList) Empty() bool {
	return list.Len() == 0
}

func (list RuleList) Head() Rule {
	if list.Empty() {
		return Rule{}
	}
	return list[0].Copy()
}

func (list RuleList) Copy() RuleList {
	cp := append(RuleList{}, list...)
	for i, rule := range cp {
		cp[i] = rule.Copy()
	}
	return cp
}

func (list RuleList) Delete(i int) RuleList {
	cp := list.Copy()
	return append(cp[:i], cp[i+1:]...)
}

func (list RuleList) Append(rules ...Rule) RuleList {
	return append(list.Copy(), rules...)
}

func (list RuleList) Hosts() []string {
	hosts := make([]string, 0, len(list))
	for _, rule := range list {
		hosts = append(hosts, rule.Host)
	}
	return hosts
}

func (list RuleList) Paths() PathList {
	var paths = make(PathList, 0, len(list))
	for _, rule := range list {
		paths = append(paths, rule.Paths.Copy()...)
	}
	return paths
}

func (list RuleList) Services() []Service {
	var services = make([]Service, 0, len(list))
	for _, rule := range list {
		services = append(services, rule.Paths.Services()...)
	}
	return services
}

func (list RuleList) ServicesNames() []string {
	var services = make([]string, 0, len(list))
	for _, rule := range list {
		services = append(services, rule.Paths.ServicesNames()...)
	}
	return services
}

func (list RuleList) ServicesTableView() []string {
	var services = make([]string, 0, len(list))
	for _, rule := range list {
		services = append(services, rule.Paths.ServicesTableView()...)
	}
	if len(services) == 0 {
		return []string{"!MISSING SERVICE!"}
	}
	return services
}
<|endoftext|>


<commit_before>//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package snapshot

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

// Attempting to tar up pseudofiles like /proc/cpuinfo is an exercise in
// futility. Notably, the pseudofiles, when read by syscalls, do not return the
// number of bytes read. This causes the tar writer to write zero-length files.
//
// Instead, it is necessary to build a directory structure in a tmpdir and
// create actual files with copies of the pseudofile contents

// CloneTreeInto copies all the pseudofiles that ghw will consume into the root
// `scratchDir`, preserving the hieratchy.
func CloneTreeInto(scratchDir string) error {
	err := setupScratchDir(scratchDir)
	if err != nil {
		return err
	}
	fileSpecs := ExpectedCloneContent()
	return CopyFilesInto(fileSpecs, scratchDir, nil)
}

// ExpectedCloneContent return a slice of glob patterns which represent the pseudofiles
// ghw cares about.
// The intended usage of this function is to validate a clone tree, checking that the
// content matches the expectations.
// Beware: the content is host-specific, because the content pertaining some subsystems,
// most notably PCI, is host-specific and unpredictable.
func ExpectedCloneContent() []string {
	fileSpecs := ExpectedCloneStaticContent()
	fileSpecs = append(fileSpecs, ExpectedCloneNetContent()...)
	fileSpecs = append(fileSpecs, ExpectedClonePCIContent()...)
	fileSpecs = append(fileSpecs, ExpectedCloneGPUContent()...)
	return fileSpecs
}

// ValidateClonedTree checks the content of a cloned tree, whose root is `clonedDir`,
// against a slice of glob specs which must be included in the cloned tree.
// Is not wrong, and this functions doesn't enforce this, that the cloned tree includes
// more files than the necessary; ghw will just ignore the files it doesn't care about.
// Returns a slice of glob patters expected (given) but not found in the cloned tree,
// and the error during the validation (if any).
func ValidateClonedTree(fileSpecs []string, clonedDir string) ([]string, error) {
	missing := []string{}
	for _, fileSpec := range fileSpecs {
		matches, err := filepath.Glob(filepath.Join(clonedDir, fileSpec))
		if err != nil {
			return missing, err
		}
		if len(matches) == 0 {
			missing = append(missing, fileSpec)
		}
	}
	return missing, nil
}

// CopyFileOptions allows to finetune the behaviour of the CopyFilesInto function
type CopyFileOptions struct {
	// IsSymlinkFn allows to control the behaviour when handling a symlink.
	// If this hook returns true, the source file is treated as symlink: the cloned
	// tree will thus contain a symlink, with its path adjusted to match the relative
	// path inside the cloned tree. If return false, the symlink will be deferred.
	// The easiest use case of this hook is if you want to avoid symlinks in your cloned
	// tree (having duplicated content). In this case you can just add a function
	// which always return false.
	IsSymlinkFn func(path string, info os.FileInfo) bool
	// ShouldCreateDirFn allows to control if empty directories listed as clone
	// content should be created or not. When creating snapshots, empty directories
	// are most often useless (but also harmless). Because of this, directories are only
	// created as side effect of copying the files which are inside, and thus directories
	// are never empty. The only notable exception are device driver on linux: in this
	// case, for a number of technical/historical reasons, we care about the directory
	// name, but not about the files which are inside.
	// Hence, this is the only case on which ghw clones empty directories.
	ShouldCreateDirFn func(path string, info os.FileInfo) bool
}

// CopyFilesInto copies all the given glob files specs in the given `destDir` directory,
// preserving the directory structure. This means you can provide a deeply nested filespec
// like
// - /some/deeply/nested/file*
// and you DO NOT need to build the tree incrementally like
// - /some/
// - /some/deeply/
// ...
// all glob patterns supported in `filepath.Glob` are supported.
func CopyFilesInto(fileSpecs []string, destDir string, opts *CopyFileOptions) error {
	if opts == nil {
		opts = &CopyFileOptions{
			IsSymlinkFn:       isSymlink,
			ShouldCreateDirFn: isDriversDir,
		}
	}
	for _, fileSpec := range fileSpecs {
		trace("copying spec: %q\n", fileSpec)
		matches, err := filepath.Glob(fileSpec)
		if err != nil {
			return err
		}
		if err := copyFileTreeInto(matches, destDir, opts); err != nil {
			return err
		}
	}
	return nil
}

func copyFileTreeInto(paths []string, destDir string, opts *CopyFileOptions) error {
	for _, path := range paths {
		trace("  copying path: %q\n", path)
		baseDir := filepath.Dir(path)
		if err := os.MkdirAll(filepath.Join(destDir, baseDir), os.ModePerm); err != nil {
			return err
		}

		fi, err := os.Lstat(path)
		if err != nil {
			return err
		}
		// directories must be listed explicitly and created separately.
		// In the future we may want to expose this decision as hook point in
		// CopyFileOptions, when clear use cases emerge.
		destPath := filepath.Join(destDir, path)
		if fi.IsDir() {
			if opts.ShouldCreateDirFn(path, fi) {
				if err := os.MkdirAll(destPath, os.ModePerm); err != nil {
					return err
				}
			} else {
				trace("expanded glob path %q is a directory - skipped\n", path)
			}
			continue
		}
		if opts.IsSymlinkFn(path, fi) {
			trace("    copying link: %q -> %q\n", path, destPath)
			if err := copyLink(path, destPath); err != nil {
				return err
			}
		} else {
			trace("    copying file: %q -> %q\n", path, destPath)
			if err := copyPseudoFile(path, destPath); err != nil {
				return err
			}
		}
	}
	return nil
}

func isSymlink(path string, fi os.FileInfo) bool {
	return fi.Mode()&os.ModeSymlink != 0
}

func isDriversDir(path string, fi os.FileInfo) bool {
	return strings.Contains(path, "drivers")
}

func copyLink(path, targetPath string) error {
	target, err := os.Readlink(path)
	if err != nil {
		return err
	}
	trace("      symlink %q -> %q\n", target, targetPath)
	if err := os.Symlink(target, targetPath); err != nil {
		if errors.Is(err, os.ErrExist) {
			return nil
		}
		return err
	}

	return nil
}

func copyPseudoFile(path, targetPath string) error {
	buf, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	trace("creating %s\n", targetPath)
	f, err := os.Create(targetPath)
	if err != nil {
		return err
	}
	if _, err = f.Write(buf); err != nil {
		return err
	}
	f.Close()
	return nil
}
<commit_msg>Ignore permission deined errors when creating a snapshot<commit_after>//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package snapshot

import (
	"errors"
	"io/fs"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

// Attempting to tar up pseudofiles like /proc/cpuinfo is an exercise in
// futility. Notably, the pseudofiles, when read by syscalls, do not return the
// number of bytes read. This causes the tar writer to write zero-length files.
//
// Instead, it is necessary to build a directory structure in a tmpdir and
// create actual files with copies of the pseudofile contents

// CloneTreeInto copies all the pseudofiles that ghw will consume into the root
// `scratchDir`, preserving the hieratchy.
func CloneTreeInto(scratchDir string) error {
	err := setupScratchDir(scratchDir)
	if err != nil {
		return err
	}
	fileSpecs := ExpectedCloneContent()
	return CopyFilesInto(fileSpecs, scratchDir, nil)
}

// ExpectedCloneContent return a slice of glob patterns which represent the pseudofiles
// ghw cares about.
// The intended usage of this function is to validate a clone tree, checking that the
// content matches the expectations.
// Beware: the content is host-specific, because the content pertaining some subsystems,
// most notably PCI, is host-specific and unpredictable.
func ExpectedCloneContent() []string {
	fileSpecs := ExpectedCloneStaticContent()
	fileSpecs = append(fileSpecs, ExpectedCloneNetContent()...)
	fileSpecs = append(fileSpecs, ExpectedClonePCIContent()...)
	fileSpecs = append(fileSpecs, ExpectedCloneGPUContent()...)
	return fileSpecs
}

// ValidateClonedTree checks the content of a cloned tree, whose root is `clonedDir`,
// against a slice of glob specs which must be included in the cloned tree.
// Is not wrong, and this functions doesn't enforce this, that the cloned tree includes
// more files than the necessary; ghw will just ignore the files it doesn't care about.
// Returns a slice of glob patters expected (given) but not found in the cloned tree,
// and the error during the validation (if any).
func ValidateClonedTree(fileSpecs []string, clonedDir string) ([]string, error) {
	missing := []string{}
	for _, fileSpec := range fileSpecs {
		matches, err := filepath.Glob(filepath.Join(clonedDir, fileSpec))
		if err != nil {
			return missing, err
		}
		if len(matches) == 0 {
			missing = append(missing, fileSpec)
		}
	}
	return missing, nil
}

// CopyFileOptions allows to finetune the behaviour of the CopyFilesInto function
type CopyFileOptions struct {
	// IsSymlinkFn allows to control the behaviour when handling a symlink.
	// If this hook returns true, the source file is treated as symlink: the cloned
	// tree will thus contain a symlink, with its path adjusted to match the relative
	// path inside the cloned tree. If return false, the symlink will be deferred.
	// The easiest use case of this hook is if you want to avoid symlinks in your cloned
	// tree (having duplicated content). In this case you can just add a function
	// which always return false.
	IsSymlinkFn func(path string, info os.FileInfo) bool
	// ShouldCreateDirFn allows to control if empty directories listed as clone
	// content should be created or not. When creating snapshots, empty directories
	// are most often useless (but also harmless). Because of this, directories are only
	// created as side effect of copying the files which are inside, and thus directories
	// are never empty. The only notable exception are device driver on linux: in this
	// case, for a number of technical/historical reasons, we care about the directory
	// name, but not about the files which are inside.
	// Hence, this is the only case on which ghw clones empty directories.
	ShouldCreateDirFn func(path string, info os.FileInfo) bool
}

// CopyFilesInto copies all the given glob files specs in the given `destDir` directory,
// preserving the directory structure. This means you can provide a deeply nested filespec
// like
// - /some/deeply/nested/file*
// and you DO NOT need to build the tree incrementally like
// - /some/
// - /some/deeply/
// ...
// all glob patterns supported in `filepath.Glob` are supported.
func CopyFilesInto(fileSpecs []string, destDir string, opts *CopyFileOptions) error {
	if opts == nil {
		opts = &CopyFileOptions{
			IsSymlinkFn:       isSymlink,
			ShouldCreateDirFn: isDriversDir,
		}
	}
	for _, fileSpec := range fileSpecs {
		trace("copying spec: %q\n", fileSpec)
		matches, err := filepath.Glob(fileSpec)
		if err != nil {
			return err
		}
		if err := copyFileTreeInto(matches, destDir, opts); err != nil {
			return err
		}
	}
	return nil
}

func copyFileTreeInto(paths []string, destDir string, opts *CopyFileOptions) error {
	for _, path := range paths {
		trace("  copying path: %q\n", path)
		baseDir := filepath.Dir(path)
		if err := os.MkdirAll(filepath.Join(destDir, baseDir), os.ModePerm); err != nil {
			return err
		}

		fi, err := os.Lstat(path)
		if err != nil {
			return err
		}
		// directories must be listed explicitly and created separately.
		// In the future we may want to expose this decision as hook point in
		// CopyFileOptions, when clear use cases emerge.
		destPath := filepath.Join(destDir, path)
		if fi.IsDir() {
			if opts.ShouldCreateDirFn(path, fi) {
				if err := os.MkdirAll(destPath, os.ModePerm); err != nil {
					return err
				}
			} else {
				trace("expanded glob path %q is a directory - skipped\n", path)
			}
			continue
		}
		if opts.IsSymlinkFn(path, fi) {
			trace("    copying link: %q -> %q\n", path, destPath)
			if err := copyLink(path, destPath); err != nil {
				return err
			}
		} else {
			trace("    copying file: %q -> %q\n", path, destPath)
			if err := copyPseudoFile(path, destPath); err != nil && !errors.Is(err, fs.ErrPermission) {
				return err
			}
		}
	}
	return nil
}

func isSymlink(path string, fi os.FileInfo) bool {
	return fi.Mode()&os.ModeSymlink != 0
}

func isDriversDir(path string, fi os.FileInfo) bool {
	return strings.Contains(path, "drivers")
}

func copyLink(path, targetPath string) error {
	target, err := os.Readlink(path)
	if err != nil {
		return err
	}
	trace("      symlink %q -> %q\n", target, targetPath)
	if err := os.Symlink(target, targetPath); err != nil {
		if errors.Is(err, os.ErrExist) {
			return nil
		}
		return err
	}

	return nil
}

func copyPseudoFile(path, targetPath string) error {
	buf, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	trace("creating %s\n", targetPath)
	f, err := os.Create(targetPath)
	if err != nil {
		return err
	}
	if _, err = f.Write(buf); err != nil {
		return err
	}
	f.Close()
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/appcelerator/amp/api/client"
	"github.com/appcelerator/amp/cmd/amp/cli"
	"github.com/spf13/cobra"
)

var (
	// Version is set with a linker flag (see Makefile)
	Version string

	// Build is set with a linker flag (see Makefile)
	Build string

	// AMP manages the connection and state for the client
	AMP *client.AMP

	// Config is used by command implementations to access the computed client configuration.
	Config     client.Configuration
	configFile string
	verbose    bool
	serverAddr string

	// RootCmd is the base command for the CLI.
	RootCmd = &cobra.Command{
		Use:   "amp",
		Short: "AMP CLI",
		Long:  `AMP CLI.`,
	}
)

// All main does is process commands and flags and invoke the app
func main() {
	fmt.Printf("amp (cli version: %s, build: %s)\n", Version, Build)

	cobra.OnInitialize(func() {
		InitConfig(configFile, &Config, verbose, serverAddr)
		fmt.Println("Server: " + Config.ServerAddress)
		AMP = client.NewAMP(&Config)
		AMP.Connect()
		cli.AtExit(func() {
			if AMP != nil {
				AMP.Disconnect()
			}
		})
	})

	// configCmd represents the Config command
	configCmd := &cobra.Command{
		Use:   "config",
		Short: "Display the current configuration",
		Long:  `Display the current configuration.`,
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println(Config)
		},
	}

	RootCmd.SetUsageTemplate(usageTemplate)
	RootCmd.SetHelpTemplate(helpTemplate)

	RootCmd.PersistentFlags().StringVar(&configFile, "Config", "", "Config file (default is $HOME/.amp.yaml)")
	RootCmd.PersistentFlags().String("target", "local", `target environment ("local"|"virtualbox"|"aws")`)
	RootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, `verbose output`)
	RootCmd.PersistentFlags().StringVar(&serverAddr, "server", client.DefaultServerAddress, "Server address")

	RootCmd.AddCommand(configCmd)

	if err := RootCmd.Execute(); err != nil {
		fmt.Println(err)
		cli.Exit(-1)
	}
	cli.Exit(0)
}

var usageTemplate = `Usage:	{{if not .HasSubCommands}}{{.UseLine}}{{end}}{{if .HasSubCommands}}{{ .CommandPath}} COMMAND{{end}}

{{ .Short | trim }}{{if gt .Aliases 0}}

Aliases:
  {{.NameAndAliases}}{{end}}{{if .HasExample}}

Examples:
{{ .Example }}{{end}}{{if .HasFlags}}

Options:
{{.Flags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasAvailableSubCommands}}

Commands:{{range .Commands}}{{if .IsAvailableCommand}}
  {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }}

Run '{{.CommandPath}} COMMAND --help' for more information on a command.{{end}}
`

var helpTemplate = `
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
<commit_msg>Reformatting the Root Cmd (#212)<commit_after>package main

import (
	"fmt"

	"github.com/appcelerator/amp/api/client"
	"github.com/appcelerator/amp/cmd/amp/cli"
	"github.com/spf13/cobra"
)

var (
	// Version is set with a linker flag (see Makefile)
	Version string

	// Build is set with a linker flag (see Makefile)
	Build string

	// AMP manages the connection and state for the client
	AMP *client.AMP

	// Config is used by command implementations to access the computed client configuration.
	Config     client.Configuration
	configFile string
	verbose    bool
	serverAddr string

	// RootCmd is the base command for the CLI.
	RootCmd = &cobra.Command{
		Use:   "amp",
		Short: "AMP CLI",
		Long:  `AMP CLI.`,
	}
)

// All main does is process commands and flags and invoke the app
func main() {
	fmt.Printf("amp (cli version: %s, build: %s)\n", Version, Build)

	cobra.OnInitialize(func() {
		InitConfig(configFile, &Config, verbose, serverAddr)
		fmt.Println("Server: " + Config.ServerAddress)
		AMP = client.NewAMP(&Config)
		AMP.Connect()
		cli.AtExit(func() {
			if AMP != nil {
				AMP.Disconnect()
			}
		})
	})

	// configCmd represents the Config command
	configCmd := &cobra.Command{
		Use:   "config",
		Short: "Display the current configuration",
		Long:  `Display the current configuration.`,
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println(Config)
		},
	}

	RootCmd.SetUsageTemplate(usageTemplate)
	RootCmd.SetHelpTemplate(helpTemplate)

	RootCmd.PersistentFlags().StringVar(&configFile, "Config", "", "Config file (default is $HOME/.amp.yaml)")
	RootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, `Verbose output`)
	RootCmd.PersistentFlags().StringVar(&serverAddr, "server", client.DefaultServerAddress, "Server address")

	RootCmd.AddCommand(configCmd)

	if err := RootCmd.Execute(); err != nil {
		fmt.Println(err)
		cli.Exit(-1)
	}
	cli.Exit(0)
}

var usageTemplate = `Usage:	{{if not .HasSubCommands}}{{.UseLine}}{{end}}{{if .HasSubCommands}}{{ .CommandPath}} COMMAND{{end}}

{{ .Short | trim }}{{if gt .Aliases 0}}

Aliases:
  {{.NameAndAliases}}{{end}}{{if .HasExample}}

Examples:
{{ .Example }}{{end}}{{if .HasFlags}}

Options:
{{.Flags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasAvailableSubCommands}}

Commands:{{range .Commands}}{{if .IsAvailableCommand}}
  {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }}

Run '{{.CommandPath}} COMMAND --help' for more information on a command.{{end}}
`

var helpTemplate = `
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
<|endoftext|>


<commit_before>package cmd

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	netURL "net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/exercism/cli/api"
	"github.com/exercism/cli/config"
	"github.com/exercism/cli/workspace"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

// downloadCmd represents the download command
var downloadCmd = &cobra.Command{
	Use:     "download",
	Aliases: []string{"d"},
	Short:   "Download an exercise.",
	Long: `Download an exercise.

You may download an exercise to work on. If you've already
started working on it, the command will also download your
latest solution.

Download other people's solutions by providing the UUID.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		cfg := config.NewConfig()

		v := viper.New()
		v.AddConfigPath(cfg.Dir)
		v.SetConfigName("user")
		v.SetConfigType("json")
		// Ignore error. If the file doesn't exist, that is fine.
		_ = v.ReadInConfig()
		cfg.UserViperConfig = v

		return runDownload(cfg, cmd.Flags(), args)
	},
}

func runDownload(cfg config.Config, flags *pflag.FlagSet, args []string) error {
	usrCfg := cfg.UserViperConfig
	if err := validateUserConfig(usrCfg); err != nil {
		return err
	}

	identifier, err := downloadIdentifier(flags)
	if err != nil {
		return err
	}
	url := fmt.Sprintf("%s/solutions/%s", usrCfg.GetString("apibaseurl"), identifier)

	client, err := api.NewClient(usrCfg.GetString("token"), usrCfg.GetString("apibaseurl"))
	if err != nil {
		return err
	}

	req, err := client.NewRequest("GET", url, nil)
	if err != nil {
		return err
	}

	req, err = addQueryToDownloadRequest(flags, req)
	if err != nil {
		return err
	}

	res, err := client.Do(req)
	if err != nil {
		return err
	}

	var payload downloadPayload
	defer res.Body.Close()
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		return fmt.Errorf("unable to parse API response - %s", err)
	}

	if res.StatusCode == http.StatusUnauthorized {
		siteURL := config.InferSiteURL(usrCfg.GetString("apibaseurl"))
		return fmt.Errorf("unauthorized request. Please run the configure command. You can find your API token at %s/my/settings", siteURL)
	}

	if res.StatusCode != http.StatusOK {
		switch payload.Error.Type {
		case "track_ambiguous":
			return fmt.Errorf("%s: %s", payload.Error.Message, strings.Join(payload.Error.PossibleTrackIDs, ", "))
		default:
			return errors.New(payload.Error.Message)
		}
	}

	metadata := payload.metadata()
	dir := metadata.Exercise(usrCfg.GetString("workspace")).MetadataDir()

	if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
		return err
	}

	err = metadata.Write(dir)
	if err != nil {
		return err
	}

	for _, file := range payload.Solution.Files {
		unparsedURL := fmt.Sprintf("%s%s", payload.Solution.FileDownloadBaseURL, file)
		parsedURL, err := netURL.ParseRequestURI(unparsedURL)

		if err != nil {
			return err
		}

		url := parsedURL.String()

		req, err := client.NewRequest("GET", url, nil)
		if err != nil {
			return err
		}

		res, err := client.Do(req)
		if err != nil {
			return err
		}
		defer res.Body.Close()

		if res.StatusCode != http.StatusOK {
			// TODO: deal with it
			continue
		}
		// Don't bother with empty files.
		if res.Header.Get("Content-Length") == "0" {
			continue
		}

		// TODO: if there's a collision, interactively resolve (show diff, ask if overwrite).
		// TODO: handle --force flag to overwrite without asking.

		// Work around a path bug due to an early design decision (later reversed) to
		// allow numeric suffixes for exercise directories, allowing people to have
		// multiple parallel versions of an exercise.
		pattern := fmt.Sprintf(`\A.*[/\\]%s-\d*/`, metadata.ExerciseSlug)
		rgxNumericSuffix := regexp.MustCompile(pattern)
		if rgxNumericSuffix.MatchString(file) {
			file = string(rgxNumericSuffix.ReplaceAll([]byte(file), []byte("")))
		}

		// Rewrite paths submitted with an older, buggy client where the Windows path is being treated as part of the filename.
		file = strings.Replace(file, "\\", "/", -1)

		relativePath := filepath.FromSlash(file)
		dir := filepath.Join(metadata.Dir, filepath.Dir(relativePath))
		os.MkdirAll(dir, os.FileMode(0755))

		f, err := os.Create(filepath.Join(metadata.Dir, relativePath))
		if err != nil {
			return err
		}
		defer f.Close()
		_, err = io.Copy(f, res.Body)
		if err != nil {
			return err
		}
	}
	fmt.Fprintf(Err, "\nDownloaded to\n")
	fmt.Fprintf(Out, "%s\n", metadata.Dir)
	return nil
}

type downloadPayload struct {
	Solution struct {
		ID   string `json:"id"`
		URL  string `json:"url"`
		Team struct {
			Name string `json:"name"`
			Slug string `json:"slug"`
		} `json:"team"`
		User struct {
			Handle      string `json:"handle"`
			IsRequester bool   `json:"is_requester"`
		} `json:"user"`
		Exercise struct {
			ID              string `json:"id"`
			InstructionsURL string `json:"instructions_url"`
			AutoApprove     bool   `json:"auto_approve"`
			Track           struct {
				ID       string `json:"id"`
				Language string `json:"language"`
			} `json:"track"`
		} `json:"exercise"`
		FileDownloadBaseURL string   `json:"file_download_base_url"`
		Files               []string `json:"files"`
		Iteration           struct {
			SubmittedAt *string `json:"submitted_at"`
		}
	} `json:"solution"`
	Error struct {
		Type             string   `json:"type"`
		Message          string   `json:"message"`
		PossibleTrackIDs []string `json:"possible_track_ids"`
	} `json:"error,omitempty"`
}

func (dp downloadPayload) metadata() workspace.ExerciseMetadata {
	return workspace.ExerciseMetadata{
		AutoApprove:  dp.Solution.Exercise.AutoApprove,
		Track:        dp.Solution.Exercise.Track.ID,
		Team:         dp.Solution.Team.Slug,
		ExerciseSlug: dp.Solution.Exercise.ID,
		ID:           dp.Solution.ID,
		URL:          dp.Solution.URL,
		Handle:       dp.Solution.User.Handle,
		IsRequester:  dp.Solution.User.IsRequester,
	}
}

// downloadIdentifier is the variable for the URI to initiate an exercise download.
func downloadIdentifier(flags *pflag.FlagSet) (string, error) {
	uuid, err := flags.GetString("uuid")
	if err != nil {
		return "", err
	}
	slug, err := flags.GetString("exercise")
	if err != nil {
		return "", err
	}
	if uuid != "" && slug != "" || uuid == slug {
		return "", errors.New("need an --exercise name or a solution --uuid")
	}

	identifier := "latest"
	if uuid != "" {
		identifier = uuid
	}
	return identifier, nil
}

func addQueryToDownloadRequest(flags *pflag.FlagSet, req *http.Request) (*http.Request, error) {
	uuid, err := flags.GetString("uuid")
	if err != nil {
		return req, err
	}
	slug, err := flags.GetString("exercise")
	if err != nil {
		return req, err
	}
	track, err := flags.GetString("track")
	if err != nil {
		return req, err
	}

	team, err := flags.GetString("team")
	if err != nil {
		return req, err
	}

	if uuid == "" {
		q := req.URL.Query()
		q.Add("exercise_id", slug)
		if track != "" {
			q.Add("track_id", track)
		}
		if team != "" {
			q.Add("team_id", team)
		}
		req.URL.RawQuery = q.Encode()
	}
	return req, nil
}

func setupDownloadFlags(flags *pflag.FlagSet) {
	flags.StringP("uuid", "u", "", "the solution UUID")
	flags.StringP("track", "t", "", "the track ID")
	flags.StringP("exercise", "e", "", "the exercise slug")
	flags.StringP("team", "T", "", "the team slug")
}

func init() {
	RootCmd.AddCommand(downloadCmd)
	setupDownloadFlags(downloadCmd.Flags())
}
<commit_msg>Add download type<commit_after>package cmd

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	netURL "net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/exercism/cli/api"
	"github.com/exercism/cli/config"
	"github.com/exercism/cli/workspace"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

// downloadCmd represents the download command
var downloadCmd = &cobra.Command{
	Use:     "download",
	Aliases: []string{"d"},
	Short:   "Download an exercise.",
	Long: `Download an exercise.

You may download an exercise to work on. If you've already
started working on it, the command will also download your
latest solution.

Download other people's solutions by providing the UUID.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		cfg := config.NewConfig()

		v := viper.New()
		v.AddConfigPath(cfg.Dir)
		v.SetConfigName("user")
		v.SetConfigType("json")
		// Ignore error. If the file doesn't exist, that is fine.
		_ = v.ReadInConfig()
		cfg.UserViperConfig = v

		return runDownload(cfg, cmd.Flags(), args)
	},
}

func runDownload(cfg config.Config, flags *pflag.FlagSet, args []string) error {
	usrCfg := cfg.UserViperConfig
	if err := validateUserConfig(usrCfg); err != nil {
		return err
	}

	download, err := newDownload(flags, usrCfg)
	if err != nil {
		return err
	}

	metadata := download.payload.metadata()
	dir := metadata.Exercise(usrCfg.GetString("workspace")).MetadataDir()

	if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
		return err
	}

	if err := metadata.Write(dir); err != nil {
		return err
	}

	client, err := api.NewClient(usrCfg.GetString("token"), usrCfg.GetString("apibaseurl"))
	if err != nil {
		return err
	}

	for _, file := range download.payload.Solution.Files {
		unparsedURL := fmt.Sprintf("%s%s", download.payload.Solution.FileDownloadBaseURL, file)
		parsedURL, err := netURL.ParseRequestURI(unparsedURL)

		if err != nil {
			return err
		}

		url := parsedURL.String()

		req, err := client.NewRequest("GET", url, nil)
		if err != nil {
			return err
		}

		res, err := client.Do(req)
		if err != nil {
			return err
		}
		defer res.Body.Close()

		if res.StatusCode != http.StatusOK {
			// TODO: deal with it
			continue
		}
		// Don't bother with empty files.
		if res.Header.Get("Content-Length") == "0" {
			continue
		}

		// TODO: if there's a collision, interactively resolve (show diff, ask if overwrite).
		// TODO: handle --force flag to overwrite without asking.

		// Work around a path bug due to an early design decision (later reversed) to
		// allow numeric suffixes for exercise directories, allowing people to have
		// multiple parallel versions of an exercise.
		pattern := fmt.Sprintf(`\A.*[/\\]%s-\d*/`, metadata.ExerciseSlug)
		rgxNumericSuffix := regexp.MustCompile(pattern)
		if rgxNumericSuffix.MatchString(file) {
			file = string(rgxNumericSuffix.ReplaceAll([]byte(file), []byte("")))
		}

		// Rewrite paths submitted with an older, buggy client where the Windows path is being treated as part of the filename.
		file = strings.Replace(file, "\\", "/", -1)

		relativePath := filepath.FromSlash(file)
		dir := filepath.Join(metadata.Dir, filepath.Dir(relativePath))
		os.MkdirAll(dir, os.FileMode(0755))

		f, err := os.Create(filepath.Join(metadata.Dir, relativePath))
		if err != nil {
			return err
		}
		defer f.Close()
		_, err = io.Copy(f, res.Body)
		if err != nil {
			return err
		}
	}
	fmt.Fprintf(Err, "\nDownloaded to\n")
	fmt.Fprintf(Out, "%s\n", metadata.Dir)
	return nil
}

type download struct {
	// either/or
	slug, uuid string

	// user config
	token, apibaseurl, workspace string

	// optional
	track, team string

	payload *downloadPayload
}

func newDownload(flags *pflag.FlagSet, usrCfg *viper.Viper) (*download, error) {
	var err error
	d := &download{}
	d.uuid, err = flags.GetString("uuid")
	if err != nil {
		return nil, err
	}
	d.slug, err = flags.GetString("exercise")
	if err != nil {
		return nil, err
	}
	d.track, err = flags.GetString("track")
	if err != nil {
		return nil, err
	}
	d.team, err = flags.GetString("team")
	if err != nil {
		return nil, err
	}

	d.token = usrCfg.GetString("token")
	d.apibaseurl = usrCfg.GetString("apibaseurl")
	d.workspace = usrCfg.GetString("workspace")

	if err = d.needsSlugXorUUID(); err != nil {
		return nil, err
	}
	if err = d.needsUserConfigValues(); err != nil {
		return nil, err
	}
	if err = d.needsSlugWhenGivenTrackOrTeam(); err != nil {
		return nil, err
	}

	client, err := api.NewClient(d.token, d.apibaseurl)
	if err != nil {
		return nil, err
	}

	req, err := client.NewRequest("GET", d.url(), nil)
	if err != nil {
		return nil, err
	}
	d.buildQueryParams(req.URL)

	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if err := json.NewDecoder(res.Body).Decode(&d.payload); err != nil {
		return nil, fmt.Errorf("unable to parse API response - %s", err)
	}

	if res.StatusCode == http.StatusUnauthorized {
		return nil, fmt.Errorf(
			"unauthorized request. Please run the configure command. You can find your API token at %s/my/settings",
			config.InferSiteURL(d.apibaseurl),
		)
	}
	if res.StatusCode != http.StatusOK {
		switch d.payload.Error.Type {
		case "track_ambiguous":
			return nil, fmt.Errorf(
				"%s: %s",
				d.payload.Error.Message,
				strings.Join(d.payload.Error.PossibleTrackIDs, ", "),
			)
		default:
			return nil, errors.New(d.payload.Error.Message)
		}
	}

	return d, d.validate()
}

func (d download) url() string {
	id := "latest"
	if d.uuid != "" {
		id = d.uuid
	}
	return fmt.Sprintf("%s/solutions/%s", d.apibaseurl, id)
}

func (d download) buildQueryParams(url *netURL.URL) {
	query := url.Query()
	if d.slug != "" {
		query.Add("exercise_id", d.slug)
		if d.track != "" {
			query.Add("track_id", d.track)
		}
		if d.team != "" {
			query.Add("team_id", d.team)
		}
	}
	url.RawQuery = query.Encode()
}

func (d download) validate() error {
	if d.payload.Solution.ID == "" {
		return errors.New("download missing ID")
	}
	if d.payload.Error.Message != "" {
		return errors.New(d.payload.Error.Message)
	}
	return nil
}

// needsSlugXorUUID checks the presence of slug XOR uuid.
func (d download) needsSlugXorUUID() error {
	if d.slug != "" && d.uuid != "" || d.uuid == d.slug {
		return errors.New("need an --exercise name or a solution --uuid")
	}
	return nil
}

// needsUserConfigValues checks the presence of required values from the user config.
func (d download) needsUserConfigValues() error {
	errMsg := "missing required user config: '%s'"
	if d.token == "" {
		return fmt.Errorf(errMsg, "token")
	}
	if d.apibaseurl == "" {
		return fmt.Errorf(errMsg, "apibaseurl")
	}
	if d.workspace == "" {
		return fmt.Errorf(errMsg, "workspace")
	}
	return nil
}

// needsSlugWhenGivenTrackOrTeam ensures that track/team arguments are also given with a slug.
// (track/team meaningless when given a uuid).
func (d download) needsSlugWhenGivenTrackOrTeam() error {
	if (d.team != "" || d.track != "") && d.slug == "" {
		return errors.New("--track or --team requires --exercise (not --uuid)")
	}
	return nil
}

type downloadPayload struct {
	Solution struct {
		ID   string `json:"id"`
		URL  string `json:"url"`
		Team struct {
			Name string `json:"name"`
			Slug string `json:"slug"`
		} `json:"team"`
		User struct {
			Handle      string `json:"handle"`
			IsRequester bool   `json:"is_requester"`
		} `json:"user"`
		Exercise struct {
			ID              string `json:"id"`
			InstructionsURL string `json:"instructions_url"`
			AutoApprove     bool   `json:"auto_approve"`
			Track           struct {
				ID       string `json:"id"`
				Language string `json:"language"`
			} `json:"track"`
		} `json:"exercise"`
		FileDownloadBaseURL string   `json:"file_download_base_url"`
		Files               []string `json:"files"`
		Iteration           struct {
			SubmittedAt *string `json:"submitted_at"`
		}
	} `json:"solution"`
	Error struct {
		Type             string   `json:"type"`
		Message          string   `json:"message"`
		PossibleTrackIDs []string `json:"possible_track_ids"`
	} `json:"error,omitempty"`
}

func (dp downloadPayload) metadata() workspace.ExerciseMetadata {
	return workspace.ExerciseMetadata{
		AutoApprove:  dp.Solution.Exercise.AutoApprove,
		Track:        dp.Solution.Exercise.Track.ID,
		Team:         dp.Solution.Team.Slug,
		ExerciseSlug: dp.Solution.Exercise.ID,
		ID:           dp.Solution.ID,
		URL:          dp.Solution.URL,
		Handle:       dp.Solution.User.Handle,
		IsRequester:  dp.Solution.User.IsRequester,
	}
}

func setupDownloadFlags(flags *pflag.FlagSet) {
	flags.StringP("uuid", "u", "", "the solution UUID")
	flags.StringP("track", "t", "", "the track ID")
	flags.StringP("exercise", "e", "", "the exercise slug")
	flags.StringP("team", "T", "", "the team slug")
}

func init() {
	RootCmd.AddCommand(downloadCmd)
	setupDownloadFlags(downloadCmd.Flags())
}
<|endoftext|>


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

package main

import (
	"bytes"
	"expvar"
	"flag"
	"fmt"
	"math/rand"
	"os"
	"os/signal"
	"path"
	"runtime"
	"runtime/pprof"
	"sort"
	"strings"
	"time"

	log "github.com/couchbase/clog"

	"github.com/couchbaselabs/cbgt"
	"github.com/couchbaselabs/cbgt/cmd"
	"github.com/couchbaselabs/cbgt/rebalance"
)

var VERSION = "v0.0.0"

var expvars = expvar.NewMap("stats")

func main() {
	flag.Parse()

	if flags.Help {
		flag.Usage()
		os.Exit(2)
	}

	if flags.Version {
		fmt.Printf("%s main: %s, data: %s\n",
			path.Base(os.Args[0]), VERSION, cbgt.VERSION)
		os.Exit(0)
	}

	if os.Getenv("GOMAXPROCS") == "" {
		runtime.GOMAXPROCS(runtime.NumCPU())
	}

	log.Printf("main: %s started (%s/%s)",
		os.Args[0], VERSION, cbgt.VERSION)

	rand.Seed(time.Now().UTC().UnixNano())

	go dumpOnSignalForPlatform()

	MainWelcome(flagAliases)

	nodesToRemove := []string(nil)
	if len(flags.RemoveNodes) > 0 {
		nodesToRemove = strings.Split(flags.RemoveNodes, ",")
	}

	bindHttp := "NO-BIND-HTTP"
	register := "unchanged"
	dataDir := "NO-DATA-DIR"

	// If cfg is down, we error, leaving it to some user-supplied
	// outside watchdog to backoff and restart/retry.
	cfg, err := cmd.MainCfg("mcp", flags.CfgConnect,
		bindHttp, register, dataDir)
	if err != nil {
		if err == cmd.ErrorBindHttp {
			log.Fatalf("%v", err)
			return
		}
		log.Fatalf("main: could not start cfg, cfgConnect: %s, err: %v\n"+
			"  Please check that your -cfg/-cfgConnect parameter (%q)\n"+
			"  is correct and/or that your configuration provider\n"+
			"  is available.",
			flags.CfgConnect, err, flags.CfgConnect)
		return
	}

	r, err := rebalance.StartRebalance(cbgt.VERSION, cfg, flags.Server,
		nodesToRemove,
		rebalance.RebalanceOptions{
			DryRun:  flags.DryRun,
			Verbose: flags.Verbose,
		})
	if err != nil {
		log.Fatalf("main: StartRebalance, err: %v", err)
		return
	}

	reportProgress(r)

	r.Stop()

	log.Printf("main: done")
}

// ------------------------------------------------------------

type ProgressEntry struct {
	pindex, sourcePartition, node string // Immutable.

	stateOp     rebalance.StateOp
	initUUIDSeq cbgt.UUIDSeq
	currUUIDSeq cbgt.UUIDSeq
	wantUUIDSeq cbgt.UUIDSeq
}

func reportProgress(r *rebalance.Rebalancer) {
	var lastEmit string

	maxNodeLen := 0
	maxPIndexLen := 0

	seenNodes := map[string]bool{}
	seenNodesSorted := []string(nil)

	// Map of pindex -> (source) partition -> node -> *ProgressEntry
	progressEntries := map[string]map[string]map[string]*ProgressEntry{}

	seenPIndexes := map[string]bool{}
	seenPIndexesSorted := []string(nil)

	updateProgressEntry := func(pindex, sourcePartition, node string,
		cb func(*ProgressEntry)) {
		if !seenNodes[node] {
			seenNodes[node] = true
			seenNodesSorted = append(seenNodesSorted, node)
			sort.Strings(seenNodesSorted)

			if maxNodeLen < len(node) {
				maxNodeLen = len(node)
			}
		}

		if maxPIndexLen < len(pindex) {
			maxPIndexLen = len(pindex)
		}

		sourcePartitions, exists := progressEntries[pindex]
		if !exists || sourcePartitions == nil {
			sourcePartitions = map[string]map[string]*ProgressEntry{}
			progressEntries[pindex] = sourcePartitions
		}

		nodes, exists := sourcePartitions[sourcePartition]
		if !exists || nodes == nil {
			nodes = map[string]*ProgressEntry{}
			sourcePartitions[sourcePartition] = nodes
		}

		progressEntry, exists := nodes[node]
		if !exists || progressEntry == nil {
			progressEntry = &ProgressEntry{
				pindex:          pindex,
				sourcePartition: sourcePartition,
				node:            node,
			}
			nodes[node] = progressEntry
		}

		cb(progressEntry)

		// TODO: Check UUID matches, too.

		if !seenPIndexes[pindex] {
			seenPIndexes[pindex] = true
			seenPIndexesSorted =
				append(seenPIndexesSorted, pindex)

			sort.Strings(seenPIndexesSorted)
		}
	}

	for progress := range r.ProgressCh() {
		if progress.Index == "" {
			r.Log("main: progress: %+v", progress)
			continue
		}

		if progress.Error != nil {
			r.Log("main: error, progress: %+v", progress)
			continue
		}

		r.Visit(func(
			currStates rebalance.CurrStates,
			currSeqs rebalance.CurrSeqs,
			wantSeqs rebalance.WantSeqs) {
			for _, pindexes := range currStates {
				for pindex, nodes := range pindexes {
					for node, stateOp := range nodes {
						updateProgressEntry(pindex, "", node,
							func(pe *ProgressEntry) {
								pe.stateOp = stateOp
							})
					}
				}
			}

			for pindex, sourcePartitions := range currSeqs {
				for sourcePartition, nodes := range sourcePartitions {
					for node, currUUIDSeq := range nodes {
						updateProgressEntry(pindex,
							sourcePartition, node,
							func(pe *ProgressEntry) {
								pe.currUUIDSeq = currUUIDSeq

								if pe.initUUIDSeq.UUID == "" {
									pe.initUUIDSeq = currUUIDSeq
								}
							})
					}
				}
			}

			for pindex, sourcePartitions := range wantSeqs {
				for sourcePartition, nodes := range sourcePartitions {
					for node, wantUUIDSeq := range nodes {
						updateProgressEntry(pindex,
							sourcePartition, node,
							func(pe *ProgressEntry) {
								pe.wantUUIDSeq = wantUUIDSeq
							})
					}
				}
			}

			// ----------------------------------------

			var b bytes.Buffer

			written, _ := b.Write([]byte("%%%"))
			for i := written; i < maxPIndexLen; i++ {
				b.WriteByte(' ')
			}
			b.WriteByte(' ')

			for i, seenNode := range seenNodesSorted {
				if i > 0 {
					b.WriteByte(' ')
				}
				b.Write([]byte(seenNode))
			}
			b.WriteByte('\n')

			for _, seenPIndex := range seenPIndexesSorted {
				b.Write([]byte(" %                  "))
				b.Write([]byte(seenPIndex))

				for _, seenNode := range seenNodesSorted {
					b.WriteByte(' ')

					sourcePartitions, exists :=
						progressEntries[seenPIndex]
					if !exists || sourcePartitions == nil {
						emitNodeEntry(&b, nil, nil, maxNodeLen)
						continue
					}

					nodes, exists := sourcePartitions[""]
					if !exists || nodes == nil {
						emitNodeEntry(&b, nil, nil, maxNodeLen)
						continue
					}

					pe, exists := nodes[seenNode]
					if !exists || pe == nil {
						emitNodeEntry(&b, nil, nil, maxNodeLen)
						continue
					}

					emitNodeEntry(&b, pe, sourcePartitions, maxNodeLen)
				}

				b.WriteByte('\n')
			}

			currEmit := b.String()
			if currEmit != lastEmit {
				r.Log("%s", currEmit)
			}

			lastEmit = currEmit
		})
	}
}

var opMap = map[string]string{
	"":        ".",
	"add":     "+",
	"del":     "-",
	"promote": "P",
	"demote":  "D",
}

func emitNodeEntry(b *bytes.Buffer,
	pe *ProgressEntry,
	sourcePartitions map[string]map[string]*ProgressEntry,
	maxNodeLen int) {
	written := 0

	totPct := 0.0 // To compute average pct.
	numPct := 0

	if pe != nil &&
		sourcePartitions != nil {
		written, _ = b.Write([]byte(opMap[pe.stateOp.Op]))

		for sourcePartition, nodes := range sourcePartitions {
			if sourcePartition == "" {
				continue
			}

			pex := nodes[pe.node]
			if pex == nil || pex.wantUUIDSeq.UUID == "" {
				continue
			}

			if pex.wantUUIDSeq.Seq <= pex.currUUIDSeq.Seq {
				totPct = totPct + 1.0
				numPct = numPct + 1
				continue
			}

			n := pex.currUUIDSeq.Seq - pex.initUUIDSeq.Seq
			d := pex.wantUUIDSeq.Seq - pex.initUUIDSeq.Seq
			if d > 0 {
				pct := float64(n) / float64(d)
				totPct = totPct + pct
				numPct = numPct + 1
			}
		}
	} else {
		b.WriteByte('.')
		written = 1
	}

	if numPct > 0 {
		avgPct := totPct / float64(numPct)

		n, _ := fmt.Fprintf(b, " %.1f%%", avgPct*100.0)
		written = written + n
	}

	for i := written; i < maxNodeLen; i++ {
		b.WriteByte(' ')
	}
}

// ------------------------------------------------------------

func MainWelcome(flagAliases map[string][]string) {
	flag.VisitAll(func(f *flag.Flag) {
		if flagAliases[f.Name] != nil {
			log.Printf("  -%s=%q\n", f.Name, f.Value)
		}
	})
	log.Printf("  GOMAXPROCS=%d", runtime.GOMAXPROCS(-1))
}

func dumpOnSignal(signals ...os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, signals...)
	for _ = range c {
		log.Printf("dump: goroutine...")
		pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
		log.Printf("dump: heap...")
		pprof.Lookup("heap").WriteTo(os.Stderr, 1)
	}
}
<commit_msg>progressTable helper func in mcp<commit_after>//  Copyright (c) 2015 Couchbase, Inc.
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the
//  License. You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//  Unless required by applicable law or agreed to in writing,
//  software distributed under the License is distributed on an "AS
//  IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//  express or implied. See the License for the specific language
//  governing permissions and limitations under the License.

package main

import (
	"bytes"
	"expvar"
	"flag"
	"fmt"
	"math/rand"
	"os"
	"os/signal"
	"path"
	"runtime"
	"runtime/pprof"
	"sort"
	"strings"
	"time"

	log "github.com/couchbase/clog"

	"github.com/couchbaselabs/cbgt"
	"github.com/couchbaselabs/cbgt/cmd"
	"github.com/couchbaselabs/cbgt/rebalance"
)

var VERSION = "v0.0.0"

var expvars = expvar.NewMap("stats")

func main() {
	flag.Parse()

	if flags.Help {
		flag.Usage()
		os.Exit(2)
	}

	if flags.Version {
		fmt.Printf("%s main: %s, data: %s\n",
			path.Base(os.Args[0]), VERSION, cbgt.VERSION)
		os.Exit(0)
	}

	if os.Getenv("GOMAXPROCS") == "" {
		runtime.GOMAXPROCS(runtime.NumCPU())
	}

	log.Printf("main: %s started (%s/%s)",
		os.Args[0], VERSION, cbgt.VERSION)

	rand.Seed(time.Now().UTC().UnixNano())

	go dumpOnSignalForPlatform()

	MainWelcome(flagAliases)

	nodesToRemove := []string(nil)
	if len(flags.RemoveNodes) > 0 {
		nodesToRemove = strings.Split(flags.RemoveNodes, ",")
	}

	bindHttp := "NO-BIND-HTTP"
	register := "unchanged"
	dataDir := "NO-DATA-DIR"

	// If cfg is down, we error, leaving it to some user-supplied
	// outside watchdog to backoff and restart/retry.
	cfg, err := cmd.MainCfg("mcp", flags.CfgConnect,
		bindHttp, register, dataDir)
	if err != nil {
		if err == cmd.ErrorBindHttp {
			log.Fatalf("%v", err)
			return
		}
		log.Fatalf("main: could not start cfg, cfgConnect: %s, err: %v\n"+
			"  Please check that your -cfg/-cfgConnect parameter (%q)\n"+
			"  is correct and/or that your configuration provider\n"+
			"  is available.",
			flags.CfgConnect, err, flags.CfgConnect)
		return
	}

	r, err := rebalance.StartRebalance(cbgt.VERSION, cfg, flags.Server,
		nodesToRemove,
		rebalance.RebalanceOptions{
			DryRun:  flags.DryRun,
			Verbose: flags.Verbose,
		})
	if err != nil {
		log.Fatalf("main: StartRebalance, err: %v", err)
		return
	}

	reportProgress(r)

	r.Stop()

	log.Printf("main: done")
}

// ------------------------------------------------------------

type ProgressEntry struct {
	pindex, sourcePartition, node string // Immutable.

	stateOp     rebalance.StateOp
	initUUIDSeq cbgt.UUIDSeq
	currUUIDSeq cbgt.UUIDSeq
	wantUUIDSeq cbgt.UUIDSeq
}

func reportProgress(r *rebalance.Rebalancer) {
	var lastEmit string

	maxNodeLen := 0
	maxPIndexLen := 0

	seenNodes := map[string]bool{}
	seenNodesSorted := []string(nil)

	// Map of pindex -> (source) partition -> node -> *ProgressEntry
	progressEntries := map[string]map[string]map[string]*ProgressEntry{}

	seenPIndexes := map[string]bool{}
	seenPIndexesSorted := []string(nil)

	updateProgressEntry := func(pindex, sourcePartition, node string,
		cb func(*ProgressEntry)) {
		if !seenNodes[node] {
			seenNodes[node] = true
			seenNodesSorted = append(seenNodesSorted, node)
			sort.Strings(seenNodesSorted)

			if maxNodeLen < len(node) {
				maxNodeLen = len(node)
			}
		}

		if maxPIndexLen < len(pindex) {
			maxPIndexLen = len(pindex)
		}

		sourcePartitions, exists := progressEntries[pindex]
		if !exists || sourcePartitions == nil {
			sourcePartitions = map[string]map[string]*ProgressEntry{}
			progressEntries[pindex] = sourcePartitions
		}

		nodes, exists := sourcePartitions[sourcePartition]
		if !exists || nodes == nil {
			nodes = map[string]*ProgressEntry{}
			sourcePartitions[sourcePartition] = nodes
		}

		progressEntry, exists := nodes[node]
		if !exists || progressEntry == nil {
			progressEntry = &ProgressEntry{
				pindex:          pindex,
				sourcePartition: sourcePartition,
				node:            node,
			}
			nodes[node] = progressEntry
		}

		cb(progressEntry)

		// TODO: Check UUID matches, too.

		if !seenPIndexes[pindex] {
			seenPIndexes[pindex] = true
			seenPIndexesSorted =
				append(seenPIndexesSorted, pindex)

			sort.Strings(seenPIndexesSorted)
		}
	}

	for progress := range r.ProgressCh() {
		if progress.Index == "" {
			r.Log("main: progress: %+v", progress)
			continue
		}

		if progress.Error != nil {
			r.Log("main: error, progress: %+v", progress)
			continue
		}

		r.Visit(func(
			currStates rebalance.CurrStates,
			currSeqs rebalance.CurrSeqs,
			wantSeqs rebalance.WantSeqs) {
			for _, pindexes := range currStates {
				for pindex, nodes := range pindexes {
					for node, stateOp := range nodes {
						updateProgressEntry(pindex, "", node,
							func(pe *ProgressEntry) {
								pe.stateOp = stateOp
							})
					}
				}
			}

			for pindex, sourcePartitions := range currSeqs {
				for sourcePartition, nodes := range sourcePartitions {
					for node, currUUIDSeq := range nodes {
						updateProgressEntry(pindex,
							sourcePartition, node,
							func(pe *ProgressEntry) {
								pe.currUUIDSeq = currUUIDSeq

								if pe.initUUIDSeq.UUID == "" {
									pe.initUUIDSeq = currUUIDSeq
								}
							})
					}
				}
			}

			for pindex, sourcePartitions := range wantSeqs {
				for sourcePartition, nodes := range sourcePartitions {
					for node, wantUUIDSeq := range nodes {
						updateProgressEntry(pindex,
							sourcePartition, node,
							func(pe *ProgressEntry) {
								pe.wantUUIDSeq = wantUUIDSeq
							})
					}
				}
			}

			currEmit := progressTable(maxNodeLen, maxPIndexLen,
				seenNodes,
				seenNodesSorted,
				seenPIndexes,
				seenPIndexesSorted,
				progressEntries)
			if currEmit != lastEmit {
				r.Log("%s", currEmit)
			}

			lastEmit = currEmit
		})
	}
}

func progressTable(maxNodeLen, maxPIndexLen int,
	seenNodes map[string]bool,
	seenNodesSorted []string,
	seenPIndexes map[string]bool,
	seenPIndexesSorted []string,
	progressEntries map[string]map[string]map[string]*ProgressEntry,
) string {
	var b bytes.Buffer

	written, _ := b.Write([]byte("%%%"))
	for i := written; i < maxPIndexLen; i++ {
		b.WriteByte(' ')
	}
	b.WriteByte(' ')

	for i, seenNode := range seenNodesSorted {
		if i > 0 {
			b.WriteByte(' ')
		}
		b.Write([]byte(seenNode))
	}
	b.WriteByte('\n')

	for _, seenPIndex := range seenPIndexesSorted {
		b.Write([]byte(" %                  "))
		b.Write([]byte(seenPIndex))

		for _, seenNode := range seenNodesSorted {
			b.WriteByte(' ')

			sourcePartitions, exists :=
				progressEntries[seenPIndex]
			if !exists || sourcePartitions == nil {
				progressCell(&b, nil, nil, maxNodeLen)
				continue
			}

			nodes, exists := sourcePartitions[""]
			if !exists || nodes == nil {
				progressCell(&b, nil, nil, maxNodeLen)
				continue
			}

			pe, exists := nodes[seenNode]
			if !exists || pe == nil {
				progressCell(&b, nil, nil, maxNodeLen)
				continue
			}

			progressCell(&b, pe, sourcePartitions, maxNodeLen)
		}

		b.WriteByte('\n')
	}

	return b.String()
}

var opMap = map[string]string{
	"":        ".",
	"add":     "+",
	"del":     "-",
	"promote": "P",
	"demote":  "D",
}

func progressCell(b *bytes.Buffer,
	pe *ProgressEntry,
	sourcePartitions map[string]map[string]*ProgressEntry,
	maxNodeLen int) {
	written := 0

	totPct := 0.0 // To compute average pct.
	numPct := 0

	if pe != nil &&
		sourcePartitions != nil {
		written, _ = b.Write([]byte(opMap[pe.stateOp.Op]))

		for sourcePartition, nodes := range sourcePartitions {
			if sourcePartition == "" {
				continue
			}

			pex := nodes[pe.node]
			if pex == nil || pex.wantUUIDSeq.UUID == "" {
				continue
			}

			if pex.wantUUIDSeq.Seq <= pex.currUUIDSeq.Seq {
				totPct = totPct + 1.0
				numPct = numPct + 1
				continue
			}

			n := pex.currUUIDSeq.Seq - pex.initUUIDSeq.Seq
			d := pex.wantUUIDSeq.Seq - pex.initUUIDSeq.Seq
			if d > 0 {
				pct := float64(n) / float64(d)
				totPct = totPct + pct
				numPct = numPct + 1
			}
		}
	} else {
		b.WriteByte('.')
		written = 1
	}

	if numPct > 0 {
		avgPct := totPct / float64(numPct)

		n, _ := fmt.Fprintf(b, " %.1f%%", avgPct*100.0)
		written = written + n
	}

	for i := written; i < maxNodeLen; i++ {
		b.WriteByte(' ')
	}
}

// ------------------------------------------------------------

func MainWelcome(flagAliases map[string][]string) {
	flag.VisitAll(func(f *flag.Flag) {
		if flagAliases[f.Name] != nil {
			log.Printf("  -%s=%q\n", f.Name, f.Value)
		}
	})
	log.Printf("  GOMAXPROCS=%d", runtime.GOMAXPROCS(-1))
}

func dumpOnSignal(signals ...os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, signals...)
	for _ = range c {
		log.Printf("dump: goroutine...")
		pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
		log.Printf("dump: heap...")
		pprof.Lookup("heap").WriteTo(os.Stderr, 1)
	}
}
<|endoftext|>


<commit_before>package cmd

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/spf13/afero"
)

func init() {
	appFS = afero.NewMemMapFs()
}

func TestGetAppPath(t *testing.T) {
	t.Parallel()

	gopath := os.Getenv("GOPATH")
	os.Setenv("GOPATH", "testpath/test")

	appPath, _, importPath, appName, appEnvName, err := getAppPath([]string{".", "/templatepath"})
	if err == nil {
		t.Errorf("expected error, but got none: %s - %s", appPath, appName)
	}

	appPath, _, importPath, appName, appEnvName, err = getAppPath([]string{"/", "/templatepath"})
	if err == nil {
		t.Errorf("expected error, but got none: %s - %s", appPath, appName)
	}

	appPath, _, importPath, appName, appEnvName, err = getAppPath([]string{"/test", "/templatepath"})
	if err != nil {
		t.Error(err)
	}
	if appPath != "testpath/test/src/test" {
		t.Errorf("mismatch, got %s", appPath)
	}
	if appName != "test" {
		t.Errorf("mismatch, got %s", appName)
	}
	if appEnvName != "TEST" {
		t.Errorf("mismatch, got %s", appEnvName)
	}
	if importPath != "/test" {
		t.Errorf("mismatch, got %s", importPath)
	}

	appPath, _, importPath, appName, appEnvName, err = getAppPath([]string{"./stuff/test"}, "/templatepath")
	if err != nil {
		t.Error(err)
	}
	if appPath != "testpath/test/src/stuff/test" {
		t.Errorf("mismatch, got %s", appPath)
	}
	if appName != "test" {
		t.Errorf("mismatch, got %s", appName)
	}
	if appEnvName != "TEST" {
		t.Errorf("mismatch, got %s", appEnvName)
	}
	if importPath != "stuff/test" {
		t.Errorf("mismatch, got %s", importPath)
	}

	os.Setenv("GOPATH", gopath)
}

func TestGetProcessedPaths(t *testing.T) {
	t.Parallel()

	cfg := newConfig{
		AppPath: "/test/myapp",
		AppName: "myapp",
	}

	inPath := "/lol/" + templatesDirectory + "/file.tmpl"
	cleanPath, fullPath := getProcessedPaths(inPath, "/", cfg)
	if cleanPath != "myapp/file" {
		t.Error("mismatch:", cleanPath)
	}
	if fullPath != "/test/myapp/file" {
		t.Error("mismatch:", fullPath)
	}

	cfg.AppPath = "myapp"
	cfg.AppName = "myapp"

	cleanPath, fullPath = getProcessedPaths(inPath, "/", cfg)
	if cleanPath != "myapp/file" {
		t.Error("mismatch:", cleanPath)
	}
	if fullPath != "myapp/file" {
		t.Error("mismatch:", fullPath)
	}
}

func TestProcessSkips(t *testing.T) {
	cfg := newConfig{
		NoReadme:      true,
		NoConfig:      true,
		NoFontAwesome: true,
		Bootstrap:     "none",
		NoBootstrapJS: true,
		NoSessions:    true,
		NoGulp:        true,
		NoLiveReload:  true,
	}

	// check skip basedir
	err := appFS.MkdirAll("/templates", 0755)
	if err != nil {
		t.Fatal(err)
	}
	info, err := appFS.Stat("/templates")
	if err != nil {
		t.Fatal(err)
	}
	skip, _ := processSkips(cfg, "/templates", "/templates", info)
	if skip == false {
		t.Error("expected to skip base path")
	}

	// check skip skipDirs slice
	err = appFS.MkdirAll("/templates/i18n", 0755)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/templates/i18n")
	if err != nil {
		t.Fatal(err)
	}
	skip, err = processSkips(cfg, "/templates", "/templates/i18n", info)
	if skip != true || err == nil {
		t.Error("expected to skip skipDir and receive skipdir err")
	}

	// check skip readme
	f, err := appFS.Create("/templates/README.md")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/README.md", info)
	if skip != true {
		t.Error("expected to skip skip readme")
	}

	// check skip livereload.js
	f, err = appFS.Create("/templates/assets/vendor/js/livereload.js")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/assets/vendor/js/livereload.js", info)
	if skip != true {
		t.Error("expected to skip livereload.js")
	}

	// check skip gulp
	f, err = appFS.Create("/templates/gulpfile.js")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/gulpfile.js", info)
	if skip != true {
		t.Error("expected to skip gulpfile.js")
	}
	f, err = appFS.Create("/templates/package.json.tmpl")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/pacakage.json.tmpl", info)
	if skip != true {
		t.Error("expected to skip package.json.tmpl")
	}

	// check skip app/sessions.go.tmpl
	f, err = appFS.Create("/templates/app/sessions.go.tmpl")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/app/sessions.go.tmpl", info)
	if skip != true {
		t.Error("expected to skip skip sessions.go.tmpl")
	}

	// check skip config.toml
	f, err = appFS.Create("/templates/config.toml")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/config.toml", info)
	if skip != true {
		t.Error("expected to skip skip config.toml")
	}

	// check no-skip regular go file
	f, err = appFS.Create("/templates/file.go")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/file.go", info)
	if skip == true {
		t.Error("did not expect skip")
	}

	appFS = afero.NewMemMapFs()
}

func TestNewCmdWalk(t *testing.T) {
	cfg := newConfig{
		AppPath: "/my/app",
		AppName: "app",
		Silent:  true,
	}

	// test skip
	err := appFS.MkdirAll("/templates/i18n", 0755)
	if err != nil {
		t.Fatal(err)
	}
	info, err := appFS.Stat("/templates/i18n")
	if err != nil {
		t.Fatal(err)
	}
	err = newCmdWalk(cfg, "/templates", "/templates/i18n", info, nil)
	if err == nil {
		t.Fatal("expected error but got nil")
	}
	if err != filepath.SkipDir {
		t.Fatalf("expected error type filepath.SkipDir, but got %#v", err)
	}

	// check go file write
	err = afero.WriteFile(appFS, "/templates/file.go", []byte("hello"), 0644)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/templates/file.go")
	if err != nil {
		t.Fatal(err)
	}
	err = newCmdWalk(cfg, "/templates", "/templates/file.go", info, nil)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/my/app/file.go")
	if err != nil {
		t.Fatal(err)
	}
	if info.IsDir() || info.Size() != 5 {
		t.Fatalf("Expected isdir false and size to be 5, got %t and %d", info.IsDir(), info.Size())
	}

	// check template file write
	err = afero.WriteFile(appFS, "/templates/template.go.tmpl", []byte(`package  {{.AppName}}`), 0644)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/templates/template.go.tmpl")
	if err != nil {
		t.Fatal(err)
	}
	err = newCmdWalk(cfg, "/templates", "/templates/template.go.tmpl", info, nil)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/my/app/template.go")
	if err != nil {
		t.Fatal(err)
	}
	if info.IsDir() || info.Size() != int64(len("package app\n")) {
		b, err := afero.ReadFile(appFS, "/my/app/template.go")
		if err != nil {
			t.Fatal(err)
		}
		t.Fatalf("Expected isdir false and size to be %d, got %t and %d, value: %q", len("package app\n"), info.IsDir(), info.Size(), string(b))
	}

	// check dir write
	err = appFS.MkdirAll("/templates/stuff", 0755)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/templates/stuff")
	if err != nil {
		t.Fatal(err)
	}
	err = newCmdWalk(cfg, "/templates", "/templates/stuff", info, nil)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/my/app/stuff")
	if err != nil {
		t.Fatal(err)
	}
	if !info.IsDir() {
		t.Fatalf("Expected isdir true, got %t", info.IsDir())
	}

	appFS = afero.NewMemMapFs()
}

func TestGenerateTLSCerts(t *testing.T) {
	cfg := newConfig{
		AppPath:       "/out/spiders",
		AppName:       "spiders",
		TLSCommonName: "dragons",
		Silent:        true,
	}

	err := generateTLSCerts(cfg)
	if err != nil {
		t.Fatal(err)
	}

	info, err := appFS.Stat("/out/spiders/cert.pem")
	if err != nil {
		t.Fatal(err)
	}
	if info.Size() == 0 {
		t.Error("expected non-0 size for cert file")
	}

	info, err = appFS.Stat("/out/spiders/private.key")
	if err != nil {
		t.Fatal(err)
	}
	if info.Size() == 0 {
		t.Error("expected non-0 size for private key file")
	}

	appFS = afero.NewMemMapFs()
}
<commit_msg>fix broken test<commit_after>package cmd

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/spf13/afero"
)

func init() {
	appFS = afero.NewMemMapFs()
}

func TestGetAppPath(t *testing.T) {
	t.Parallel()

	gopath := os.Getenv("GOPATH")
	os.Setenv("GOPATH", "testpath/test")

	appPath, _, importPath, appName, appEnvName, err := getAppPath([]string{".", "/templatepath"})
	if err == nil {
		t.Errorf("expected error, but got none: %s - %s", appPath, appName)
	}

	appPath, _, importPath, appName, appEnvName, err = getAppPath([]string{"/", "/templatepath"})
	if err == nil {
		t.Errorf("expected error, but got none: %s - %s", appPath, appName)
	}

	appPath, _, importPath, appName, appEnvName, err = getAppPath([]string{"/test", "/templatepath"})
	if err != nil {
		t.Error(err)
	}
	if appPath != "testpath/test/src/test" {
		t.Errorf("mismatch, got %s", appPath)
	}
	if appName != "test" {
		t.Errorf("mismatch, got %s", appName)
	}
	if appEnvName != "TEST" {
		t.Errorf("mismatch, got %s", appEnvName)
	}
	if importPath != "/test" {
		t.Errorf("mismatch, got %s", importPath)
	}

	appPath, _, importPath, appName, appEnvName, err = getAppPath([]string{"./stuff/test", "/templatepath"})
	if err != nil {
		t.Error(err)
	}
	if appPath != "testpath/test/src/stuff/test" {
		t.Errorf("mismatch, got %s", appPath)
	}
	if appName != "test" {
		t.Errorf("mismatch, got %s", appName)
	}
	if appEnvName != "TEST" {
		t.Errorf("mismatch, got %s", appEnvName)
	}
	if importPath != "stuff/test" {
		t.Errorf("mismatch, got %s", importPath)
	}

	os.Setenv("GOPATH", gopath)
}

func TestGetProcessedPaths(t *testing.T) {
	t.Parallel()

	cfg := newConfig{
		AppPath: "/test/myapp",
		AppName: "myapp",
	}

	inPath := "/lol/" + templatesDirectory + "/file.tmpl"
	cleanPath, fullPath := getProcessedPaths(inPath, "/", cfg)
	if cleanPath != "myapp/file" {
		t.Error("mismatch:", cleanPath)
	}
	if fullPath != "/test/myapp/file" {
		t.Error("mismatch:", fullPath)
	}

	cfg.AppPath = "myapp"
	cfg.AppName = "myapp"

	cleanPath, fullPath = getProcessedPaths(inPath, "/", cfg)
	if cleanPath != "myapp/file" {
		t.Error("mismatch:", cleanPath)
	}
	if fullPath != "myapp/file" {
		t.Error("mismatch:", fullPath)
	}
}

func TestProcessSkips(t *testing.T) {
	cfg := newConfig{
		NoReadme:      true,
		NoConfig:      true,
		NoFontAwesome: true,
		Bootstrap:     "none",
		NoBootstrapJS: true,
		NoSessions:    true,
		NoGulp:        true,
		NoLiveReload:  true,
	}

	// check skip basedir
	err := appFS.MkdirAll("/templates", 0755)
	if err != nil {
		t.Fatal(err)
	}
	info, err := appFS.Stat("/templates")
	if err != nil {
		t.Fatal(err)
	}
	skip, _ := processSkips(cfg, "/templates", "/templates", info)
	if skip == false {
		t.Error("expected to skip base path")
	}

	// check skip skipDirs slice
	err = appFS.MkdirAll("/templates/i18n", 0755)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/templates/i18n")
	if err != nil {
		t.Fatal(err)
	}
	skip, err = processSkips(cfg, "/templates", "/templates/i18n", info)
	if skip != true || err == nil {
		t.Error("expected to skip skipDir and receive skipdir err")
	}

	// check skip readme
	f, err := appFS.Create("/templates/README.md")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/README.md", info)
	if skip != true {
		t.Error("expected to skip skip readme")
	}

	// check skip livereload.js
	f, err = appFS.Create("/templates/assets/vendor/js/livereload.js")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/assets/vendor/js/livereload.js", info)
	if skip != true {
		t.Error("expected to skip livereload.js")
	}

	// check skip gulp
	f, err = appFS.Create("/templates/gulpfile.js")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/gulpfile.js", info)
	if skip != true {
		t.Error("expected to skip gulpfile.js")
	}
	f, err = appFS.Create("/templates/package.json.tmpl")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/pacakage.json.tmpl", info)
	if skip != true {
		t.Error("expected to skip package.json.tmpl")
	}

	// check skip app/sessions.go.tmpl
	f, err = appFS.Create("/templates/app/sessions.go.tmpl")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/app/sessions.go.tmpl", info)
	if skip != true {
		t.Error("expected to skip skip sessions.go.tmpl")
	}

	// check skip config.toml
	f, err = appFS.Create("/templates/config.toml")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/config.toml", info)
	if skip != true {
		t.Error("expected to skip skip config.toml")
	}

	// check no-skip regular go file
	f, err = appFS.Create("/templates/file.go")
	if err != nil {
		t.Fatal(err)
	}
	info, err = f.Stat()
	if err != nil {
		t.Fatal(err)
	}
	skip, _ = processSkips(cfg, "/templates", "/templates/file.go", info)
	if skip == true {
		t.Error("did not expect skip")
	}

	appFS = afero.NewMemMapFs()
}

func TestNewCmdWalk(t *testing.T) {
	cfg := newConfig{
		AppPath: "/my/app",
		AppName: "app",
		Silent:  true,
	}

	// test skip
	err := appFS.MkdirAll("/templates/i18n", 0755)
	if err != nil {
		t.Fatal(err)
	}
	info, err := appFS.Stat("/templates/i18n")
	if err != nil {
		t.Fatal(err)
	}
	err = newCmdWalk(cfg, "/templates", "/templates/i18n", info, nil)
	if err == nil {
		t.Fatal("expected error but got nil")
	}
	if err != filepath.SkipDir {
		t.Fatalf("expected error type filepath.SkipDir, but got %#v", err)
	}

	// check go file write
	err = afero.WriteFile(appFS, "/templates/file.go", []byte("hello"), 0644)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/templates/file.go")
	if err != nil {
		t.Fatal(err)
	}
	err = newCmdWalk(cfg, "/templates", "/templates/file.go", info, nil)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/my/app/file.go")
	if err != nil {
		t.Fatal(err)
	}
	if info.IsDir() || info.Size() != 5 {
		t.Fatalf("Expected isdir false and size to be 5, got %t and %d", info.IsDir(), info.Size())
	}

	// check template file write
	err = afero.WriteFile(appFS, "/templates/template.go.tmpl", []byte(`package  {{.AppName}}`), 0644)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/templates/template.go.tmpl")
	if err != nil {
		t.Fatal(err)
	}
	err = newCmdWalk(cfg, "/templates", "/templates/template.go.tmpl", info, nil)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/my/app/template.go")
	if err != nil {
		t.Fatal(err)
	}
	if info.IsDir() || info.Size() != int64(len("package app\n")) {
		b, err := afero.ReadFile(appFS, "/my/app/template.go")
		if err != nil {
			t.Fatal(err)
		}
		t.Fatalf("Expected isdir false and size to be %d, got %t and %d, value: %q", len("package app\n"), info.IsDir(), info.Size(), string(b))
	}

	// check dir write
	err = appFS.MkdirAll("/templates/stuff", 0755)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/templates/stuff")
	if err != nil {
		t.Fatal(err)
	}
	err = newCmdWalk(cfg, "/templates", "/templates/stuff", info, nil)
	if err != nil {
		t.Fatal(err)
	}
	info, err = appFS.Stat("/my/app/stuff")
	if err != nil {
		t.Fatal(err)
	}
	if !info.IsDir() {
		t.Fatalf("Expected isdir true, got %t", info.IsDir())
	}

	appFS = afero.NewMemMapFs()
}

func TestGenerateTLSCerts(t *testing.T) {
	cfg := newConfig{
		AppPath:       "/out/spiders",
		AppName:       "spiders",
		TLSCommonName: "dragons",
		Silent:        true,
	}

	err := generateTLSCerts(cfg)
	if err != nil {
		t.Fatal(err)
	}

	info, err := appFS.Stat("/out/spiders/cert.pem")
	if err != nil {
		t.Fatal(err)
	}
	if info.Size() == 0 {
		t.Error("expected non-0 size for cert file")
	}

	info, err = appFS.Stat("/out/spiders/private.key")
	if err != nil {
		t.Fatal(err)
	}
	if info.Size() == 0 {
		t.Error("expected non-0 size for private key file")
	}

	appFS = afero.NewMemMapFs()
}
<|endoftext|>