<commit_before>package v1alpha1

import (
	"context"
	"fmt"
	"strings"

	"github.com/caicloud/nirvana/log"
	"github.com/caicloud/nirvana/service"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/rand"

	"github.com/caicloud/cyclone/pkg/apis/cyclone/v1alpha1"
	"github.com/caicloud/cyclone/pkg/meta"
	api "github.com/caicloud/cyclone/pkg/server/apis/v1alpha1"
	"github.com/caicloud/cyclone/pkg/server/biz/scm"
	"github.com/caicloud/cyclone/pkg/server/biz/scm/bitbucket"
	"github.com/caicloud/cyclone/pkg/server/biz/scm/github"
	"github.com/caicloud/cyclone/pkg/server/biz/scm/gitlab"
	"github.com/caicloud/cyclone/pkg/server/common"
	"github.com/caicloud/cyclone/pkg/server/handler"
	"github.com/caicloud/cyclone/pkg/util/cerr"
)

const (
	succeededMsg = "Successfully triggered"

	ignoredMsg = "Is ignored"
)

func newWebhookResponse(msg string) api.WebhookResponse {
	return api.WebhookResponse{
		Message: msg,
	}
}

// HandleWebhook handles webhooks from integrated systems.
func HandleWebhook(ctx context.Context, tenant, integration string) (api.WebhookResponse, error) {
	request := service.HTTPContextFrom(ctx).Request()

	repos, err := getReposFromSecret(tenant, integration)
	if err != nil {
		log.Error(err)
		return newWebhookResponse(err.Error()), err
	}

	if len(repos) == 0 {
		return newWebhookResponse(ignoredMsg), nil
	}

	in, err := getIntegration(tenant, integration)
	if err != nil {
		return newWebhookResponse(err.Error()), err
	}

	triggered := false
	if request.Header.Get(github.EventTypeHeader) != "" {
		if data := github.ParseEvent(in.Spec.SCM, request); data != nil {
			if wfts, ok := repos[data.Repo]; ok {
				for _, wft := range wfts {
					log.Infof("Trigger workflow trigger %s", wft)
					if err = createWorkflowRun(tenant, wft, data); err != nil {
						log.Error(err)
					}
				}
			}
			triggered = true
		}
	}

	if request.Header.Get(gitlab.EventTypeHeader) != "" {
		if data := gitlab.ParseEvent(request); data != nil {
			if wfts, ok := repos[data.Repo]; ok {
				for _, wft := range wfts {
					log.Infof("Trigger workflow trigger %s", wft)
					if err = createWorkflowRun(tenant, wft, data); err != nil {
						log.Error(err)
					}
				}
			}
			triggered = true
		}
	}

	if request.Header.Get(bitbucket.EventTypeHeader) != "" {
		if data := bitbucket.ParseEvent(in.Spec.SCM, request); data != nil {
			if wfts, ok := repos[data.Repo]; ok {
				for _, wft := range wfts {
					log.Infof("Trigger workflow trigger %s", wft)
					if err = createWorkflowRun(tenant, wft, data); err != nil {
						log.Error(err)
					}
				}
			}
			triggered = true
		}
	}

	if triggered {
		return newWebhookResponse(succeededMsg), nil
	}

	return newWebhookResponse(ignoredMsg), nil
}

func createWorkflowRun(tenant, wftName string, data *scm.EventData) error {
	ns := common.TenantNamespace(tenant)
	wft, err := handler.K8sClient.CycloneV1alpha1().WorkflowTriggers(ns).Get(wftName, metav1.GetOptions{})
	if err != nil {
		return cerr.ConvertK8sError(err)
	}

	var project string
	if wft.Labels != nil {
		project = wft.Labels[meta.LabelProjectName]
	}
	if project == "" {
		return fmt.Errorf("Failed to get project from workflowtrigger labels")
	}

	wfName := wft.Spec.WorkflowRef.Name
	if wfName == "" {
		return fmt.Errorf("Workflow reference of workflowtrigger is empty")
	}

	trigger := false
	var tag string
	st := wft.Spec.SCM
	switch data.Type {
	case scm.TagReleaseEventType:
		if st.TagRelease.Enabled {
			trigger = true
			tag = data.Ref
			splitTags := strings.Split(data.Ref, "/")
			if len(splitTags) == 3 {
				tag = splitTags[2]
			}
		}
	case scm.PushEventType:
		trimmedBranch := data.Branch
		if index := strings.LastIndex(data.Branch, "/"); index >= 0 {
			trimmedBranch = trimmedBranch[index+1:]
		}
		for _, branch := range st.Push.Branches {
			if branch == trimmedBranch {
				trigger = true
				break
			}
		}
	case scm.PullRequestEventType:
		if st.PullRequest.Enabled {
			trigger = true
		}
	case scm.PullRequestCommentEventType:
		for _, comment := range st.PullRequestComment.Comments {
			if comment == data.Comment {
				trigger = true
			}
		}
	}

	if !trigger {
		return nil
	}

	log.Infof("Trigger wft %s with event data: %v", wftName, data)

	name := fmt.Sprintf("%s-%s", wfName, rand.String(5))
	alias := name
	if tag != "" {
		alias = tag
	}

	// Create workflowrun.
	wfr := &v1alpha1.WorkflowRun{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
			Annotations: map[string]string{
				meta.AnnotationWorkflowRunTrigger: string(data.Type),
				meta.AnnotationAlias:              alias,
			},
			Labels: map[string]string{
				meta.LabelProjectName:  project,
				meta.LabelWorkflowName: wfName,
			},
		},
		Spec: wft.Spec.WorkflowRunSpec,
	}

	wfr.Annotations, err = setSCMEventData(wfr.Annotations, data)
	if err != nil {
		return err
	}

	// Set "Tag" and "SCM_REVISION" for all resource configs if they are empty.
	for _, r := range wft.Spec.WorkflowRunSpec.Resources {
		for i, p := range r.Parameters {
			if p.Name == "TAG" && (p.Value == nil || *p.Value == "") {
				r.Parameters[i].Value = &tag
			}

			if p.Name == "SCM_REVISION" && (p.Value == nil || *p.Value == "") {
				r.Parameters[i].Value = &data.Ref
			}
		}
	}

	// Set "Tag" for all stage configs.
	for _, s := range wft.Spec.WorkflowRunSpec.Stages {
		for i, p := range s.Parameters {
			if p.Name == "tag" && (p.Value == nil || *p.Value == "") {
				s.Parameters[i].Value = &tag
			}
		}
	}

	_, err = handler.K8sClient.CycloneV1alpha1().WorkflowRuns(ns).Create(wfr)
	if err != nil {
		return cerr.ConvertK8sError(err)
	}

	return nil
}
<commit_msg>fix: tag does not take effect when there is a default value (#967)<commit_after>package v1alpha1

import (
	"context"
	"fmt"
	"strings"

	"github.com/caicloud/nirvana/log"
	"github.com/caicloud/nirvana/service"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/rand"

	"github.com/caicloud/cyclone/pkg/apis/cyclone/v1alpha1"
	"github.com/caicloud/cyclone/pkg/meta"
	api "github.com/caicloud/cyclone/pkg/server/apis/v1alpha1"
	"github.com/caicloud/cyclone/pkg/server/biz/scm"
	"github.com/caicloud/cyclone/pkg/server/biz/scm/bitbucket"
	"github.com/caicloud/cyclone/pkg/server/biz/scm/github"
	"github.com/caicloud/cyclone/pkg/server/biz/scm/gitlab"
	"github.com/caicloud/cyclone/pkg/server/common"
	"github.com/caicloud/cyclone/pkg/server/handler"
	"github.com/caicloud/cyclone/pkg/util/cerr"
)

const (
	succeededMsg = "Successfully triggered"

	ignoredMsg = "Is ignored"
)

func newWebhookResponse(msg string) api.WebhookResponse {
	return api.WebhookResponse{
		Message: msg,
	}
}

// HandleWebhook handles webhooks from integrated systems.
func HandleWebhook(ctx context.Context, tenant, integration string) (api.WebhookResponse, error) {
	request := service.HTTPContextFrom(ctx).Request()

	repos, err := getReposFromSecret(tenant, integration)
	if err != nil {
		log.Error(err)
		return newWebhookResponse(err.Error()), err
	}

	if len(repos) == 0 {
		return newWebhookResponse(ignoredMsg), nil
	}

	in, err := getIntegration(tenant, integration)
	if err != nil {
		return newWebhookResponse(err.Error()), err
	}

	triggered := false
	if request.Header.Get(github.EventTypeHeader) != "" {
		if data := github.ParseEvent(in.Spec.SCM, request); data != nil {
			if wfts, ok := repos[data.Repo]; ok {
				for _, wft := range wfts {
					log.Infof("Trigger workflow trigger %s", wft)
					if err = createWorkflowRun(tenant, wft, data); err != nil {
						log.Error(err)
					}
				}
			}
			triggered = true
		}
	}

	if request.Header.Get(gitlab.EventTypeHeader) != "" {
		if data := gitlab.ParseEvent(request); data != nil {
			if wfts, ok := repos[data.Repo]; ok {
				for _, wft := range wfts {
					log.Infof("Trigger workflow trigger %s", wft)
					if err = createWorkflowRun(tenant, wft, data); err != nil {
						log.Error(err)
					}
				}
			}
			triggered = true
		}
	}

	if request.Header.Get(bitbucket.EventTypeHeader) != "" {
		if data := bitbucket.ParseEvent(in.Spec.SCM, request); data != nil {
			if wfts, ok := repos[data.Repo]; ok {
				for _, wft := range wfts {
					log.Infof("Trigger workflow trigger %s", wft)
					if err = createWorkflowRun(tenant, wft, data); err != nil {
						log.Error(err)
					}
				}
			}
			triggered = true
		}
	}

	if triggered {
		return newWebhookResponse(succeededMsg), nil
	}

	return newWebhookResponse(ignoredMsg), nil
}

func createWorkflowRun(tenant, wftName string, data *scm.EventData) error {
	ns := common.TenantNamespace(tenant)
	wft, err := handler.K8sClient.CycloneV1alpha1().WorkflowTriggers(ns).Get(wftName, metav1.GetOptions{})
	if err != nil {
		return cerr.ConvertK8sError(err)
	}

	var project string
	if wft.Labels != nil {
		project = wft.Labels[meta.LabelProjectName]
	}
	if project == "" {
		return fmt.Errorf("failed to get project from workflowtrigger labels")
	}

	wfName := wft.Spec.WorkflowRef.Name
	if wfName == "" {
		return fmt.Errorf("workflow reference of workflowtrigger is empty")
	}

	trigger := false
	var tag string
	st := wft.Spec.SCM
	switch data.Type {
	case scm.TagReleaseEventType:
		if st.TagRelease.Enabled {
			trigger = true
			tag = data.Ref
			splitTags := strings.Split(data.Ref, "/")
			if len(splitTags) == 3 {
				tag = splitTags[2]
			}
		}
	case scm.PushEventType:
		trimmedBranch := data.Branch
		if index := strings.LastIndex(data.Branch, "/"); index >= 0 {
			trimmedBranch = trimmedBranch[index+1:]
		}
		for _, branch := range st.Push.Branches {
			if branch == trimmedBranch {
				trigger = true
				break
			}
		}
	case scm.PullRequestEventType:
		if st.PullRequest.Enabled {
			trigger = true
		}
	case scm.PullRequestCommentEventType:
		for _, comment := range st.PullRequestComment.Comments {
			if comment == data.Comment {
				trigger = true
			}
		}
	}

	if !trigger {
		return nil
	}

	log.Infof("Trigger wft %s with event data: %v", wftName, data)

	name := fmt.Sprintf("%s-%s", wfName, rand.String(5))
	alias := name
	if tag != "" {
		alias = tag
	}

	// Create workflowrun.
	wfr := &v1alpha1.WorkflowRun{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
			Annotations: map[string]string{
				meta.AnnotationWorkflowRunTrigger: string(data.Type),
				meta.AnnotationAlias:              alias,
			},
			Labels: map[string]string{
				meta.LabelProjectName:  project,
				meta.LabelWorkflowName: wfName,
			},
		},
		Spec: wft.Spec.WorkflowRunSpec,
	}

	wfr.Annotations, err = setSCMEventData(wfr.Annotations, data)
	if err != nil {
		return err
	}

	// Set "Tag" and "SCM_REVISION" for all resource configs.
	for _, r := range wft.Spec.WorkflowRunSpec.Resources {
		for i, p := range r.Parameters {
			if p.Name == "TAG" && tag != "" {
				r.Parameters[i].Value = &tag
			}

			if p.Name == "SCM_REVISION" && data.Ref != "" {
				r.Parameters[i].Value = &data.Ref
			}
		}
	}

	// Set "Tag" for all stage configs.
	for _, s := range wft.Spec.WorkflowRunSpec.Stages {
		for i, p := range s.Parameters {
			if p.Name == "tag" && tag != "" {
				s.Parameters[i].Value = &tag
			}
		}
	}

	_, err = handler.K8sClient.CycloneV1alpha1().WorkflowRuns(ns).Create(wfr)
	if err != nil {
		return cerr.ConvertK8sError(err)
	}

	return nil
}
<|endoftext|>


<commit_before>package plugin

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

	"github.com/fagongzi/gateway/pkg/filter"
	"github.com/fagongzi/gateway/pkg/pb/metapb"
	"github.com/fagongzi/log"
	"github.com/robertkrimen/otto"
)

const (
	// PluginConstructor every plugin needs a constructor
	PluginConstructor = "NewPlugin"
	// PluginPre filter pre method
	PluginPre = "pre"
	// PluginPost filter post method
	PluginPost = "post"
	// PluginPostErr filter post error method
	PluginPostErr = "postErr"
)

// Runtime plugin runtime
type Runtime struct {
	filter.BaseFilter

	meta                           *metapb.Plugin
	vm                             *otto.Otto
	this                           otto.Value
	preFunc, postFunc, postErrFunc otto.Value
}

// NewRuntime return a runtime
func NewRuntime(meta *metapb.Plugin) (*Runtime, error) {
	vm := otto.New()
	_, err := vm.Run(string(meta.Content))
	if err != nil {
		return nil, err
	}

	// add require for using go module
	vm.Set("require", Require)

	// exec constructor
	plugin, err := vm.Get(PluginConstructor)
	if err != nil {
		return nil, err
	}
	if !plugin.IsFunction() {
		return nil, fmt.Errorf("plugin constructor must be a function")
	}

	this, err := plugin.Call(plugin, string(meta.Cfg))
	if err != nil {
		return nil, err
	}
	if !this.IsObject() {
		return nil, fmt.Errorf("plugin constructor must be return an object")
	}

	// fetch plugin methods
	preFunc, err := this.Object().Get(PluginPre)
	if err != nil {
		return nil, err
	}

	postFunc, err := this.Object().Get(PluginPost)
	if err != nil {
		return nil, err
	}

	postErrFunc, err := this.Object().Get(PluginPostErr)
	if err != nil {
		return nil, err
	}

	return &Runtime{
		meta:        meta,
		vm:          vm,
		this:        this,
		preFunc:     preFunc,
		postFunc:    postFunc,
		postErrFunc: postErrFunc,
	}, nil
}

// Pre filter pre method
func (rt *Runtime) Pre(c *Ctx) (int, error) {
	if rt.preFunc.IsUndefined() {
		return rt.BaseFilter.Pre(c.delegate)
	}

	value, err := rt.preFunc.Call(rt.this, c)
	if err != nil {
		log.Errorf("plugin %d/%s:%d plugin pre func failed with %+v",
			rt.meta.ID,
			rt.meta.Name,
			rt.meta.Version,
			err)
		return http.StatusInternalServerError, err
	}

	if !value.IsObject() {
		return http.StatusInternalServerError, fmt.Errorf("unexpect js plugin returned %+v", value)
	}

	return parsePluginReturn(value.Object())
}

// Post filter post method
func (rt *Runtime) Post(c *Ctx) (int, error) {
	if rt.postFunc.IsUndefined() {
		return rt.BaseFilter.Post(c.delegate)
	}

	value, err := rt.postFunc.Call(rt.this, c)
	if err != nil {
		log.Errorf("plugin %d/%s:%d plugin post func failed with %+v",
			rt.meta.ID,
			rt.meta.Name,
			rt.meta.Version,
			err)
		return http.StatusInternalServerError, err
	}

	if !value.IsObject() {
		return http.StatusInternalServerError, fmt.Errorf("unexpect js plugin returned %+v", value)
	}

	return parsePluginReturn(value.Object())
}

// PostErr filter post error method
func (rt *Runtime) PostErr(c *Ctx) {
	if rt.postErrFunc.IsUndefined() {
		rt.BaseFilter.PostErr(c.delegate)
		return
	}

	_, err := rt.postErrFunc.Call(rt.this, c)
	if err != nil {
		log.Errorf("plugin %d/%s:%d plugin post error func failed with %+v",
			rt.meta.ID,
			rt.meta.Name,
			rt.meta.Version,
			err)
	}
}

func parsePluginReturn(value *otto.Object) (int, error) {
	code, err := value.Get("code")
	if err != nil {
		return http.StatusInternalServerError, err
	}
	if !code.IsNumber() {
		return http.StatusInternalServerError, fmt.Errorf("js plugin result code must be number")
	}
	statusCode, err := code.ToInteger()
	if err != nil {
		return http.StatusInternalServerError, err
	}

	e, err := value.Get("error")
	if err != nil {
		return http.StatusInternalServerError, err
	}
	if e.IsDefined() && !e.IsString() {
		return http.StatusInternalServerError, fmt.Errorf("js plugin result error must be string")
	}

	if e.IsUndefined() {
		return int(statusCode), nil
	}

	errMsg, err := e.ToString()
	if err != nil {
		return http.StatusInternalServerError, err
	}

	return int(statusCode), errors.New(errMsg)
}
<commit_msg>dev: update const<commit_after>package plugin

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

	"github.com/fagongzi/gateway/pkg/filter"
	"github.com/fagongzi/gateway/pkg/pb/metapb"
	"github.com/fagongzi/log"
	"github.com/robertkrimen/otto"
)

const (
	// PluginConstructor every plugin needs a constructor
	PluginConstructor = "NewPlugin"
	// PluginPre filter pre method
	PluginPre = "pre"
	// PluginPost filter post method
	PluginPost = "post"
	// PluginPostErr filter post error method
	PluginPostErr = "postErr"

	// PluginReturnCodeFieldName code field name in return json object
	PluginReturnCodeFieldName = "code"
	// PluginReturnErrorFieldName error field name in return json object
	PluginReturnErrorFieldName = "error"
)

// Runtime plugin runtime
type Runtime struct {
	filter.BaseFilter

	meta                           *metapb.Plugin
	vm                             *otto.Otto
	this                           otto.Value
	preFunc, postFunc, postErrFunc otto.Value
}

// NewRuntime return a runtime
func NewRuntime(meta *metapb.Plugin) (*Runtime, error) {
	vm := otto.New()
	_, err := vm.Run(string(meta.Content))
	if err != nil {
		return nil, err
	}

	// add require for using go module
	vm.Set("require", Require)

	// exec constructor
	plugin, err := vm.Get(PluginConstructor)
	if err != nil {
		return nil, err
	}
	if !plugin.IsFunction() {
		return nil, fmt.Errorf("plugin constructor must be a function")
	}

	this, err := plugin.Call(plugin, string(meta.Cfg))
	if err != nil {
		return nil, err
	}
	if !this.IsObject() {
		return nil, fmt.Errorf("plugin constructor must be return an object")
	}

	// fetch plugin methods
	preFunc, err := this.Object().Get(PluginPre)
	if err != nil {
		return nil, err
	}

	postFunc, err := this.Object().Get(PluginPost)
	if err != nil {
		return nil, err
	}

	postErrFunc, err := this.Object().Get(PluginPostErr)
	if err != nil {
		return nil, err
	}

	return &Runtime{
		meta:        meta,
		vm:          vm,
		this:        this,
		preFunc:     preFunc,
		postFunc:    postFunc,
		postErrFunc: postErrFunc,
	}, nil
}

// Pre filter pre method
func (rt *Runtime) Pre(c *Ctx) (int, error) {
	if rt.preFunc.IsUndefined() {
		return rt.BaseFilter.Pre(c.delegate)
	}

	value, err := rt.preFunc.Call(rt.this, c)
	if err != nil {
		log.Errorf("plugin %d/%s:%d plugin pre func failed with %+v",
			rt.meta.ID,
			rt.meta.Name,
			rt.meta.Version,
			err)
		return http.StatusInternalServerError, err
	}

	if !value.IsObject() {
		return http.StatusInternalServerError, fmt.Errorf("unexpect js plugin returned %+v", value)
	}

	return parsePluginReturn(value.Object())
}

// Post filter post method
func (rt *Runtime) Post(c *Ctx) (int, error) {
	if rt.postFunc.IsUndefined() {
		return rt.BaseFilter.Post(c.delegate)
	}

	value, err := rt.postFunc.Call(rt.this, c)
	if err != nil {
		log.Errorf("plugin %d/%s:%d plugin post func failed with %+v",
			rt.meta.ID,
			rt.meta.Name,
			rt.meta.Version,
			err)
		return http.StatusInternalServerError, err
	}

	if !value.IsObject() {
		return http.StatusInternalServerError, fmt.Errorf("unexpect js plugin returned %+v", value)
	}

	return parsePluginReturn(value.Object())
}

// PostErr filter post error method
func (rt *Runtime) PostErr(c *Ctx) {
	if rt.postErrFunc.IsUndefined() {
		rt.BaseFilter.PostErr(c.delegate)
		return
	}

	_, err := rt.postErrFunc.Call(rt.this, c)
	if err != nil {
		log.Errorf("plugin %d/%s:%d plugin post error func failed with %+v",
			rt.meta.ID,
			rt.meta.Name,
			rt.meta.Version,
			err)
	}
}

func parsePluginReturn(value *otto.Object) (int, error) {
	code, err := value.Get(PluginReturnCodeFieldName)
	if err != nil {
		return http.StatusInternalServerError, err
	}
	if !code.IsNumber() {
		return http.StatusInternalServerError, fmt.Errorf("js plugin result code must be number")
	}
	statusCode, err := code.ToInteger()
	if err != nil {
		return http.StatusInternalServerError, err
	}

	e, err := value.Get(PluginReturnErrorFieldName)
	if err != nil {
		return http.StatusInternalServerError, err
	}
	if e.IsDefined() && !e.IsString() {
		return http.StatusInternalServerError, fmt.Errorf("js plugin result error must be string")
	}

	if e.IsUndefined() {
		return int(statusCode), nil
	}

	errMsg, err := e.ToString()
	if err != nil {
		return http.StatusInternalServerError, err
	}

	return int(statusCode), errors.New(errMsg)
}
<|endoftext|>


<commit_before>package sync

func propagateExecutability(ancestor, source, target *Entry) {
	// If target is nil, then we don't have anything to propagate to, so bail.
	if target == nil {
		return
	}

	// Handle based on target kind.
	if target.Kind == EntryKind_Directory {
		ancestorContents := ancestor.GetContents()
		sourceContents := source.GetContents()
		targetContents := target.GetContents()
		for name := range targetContents {
			propagateExecutability(ancestorContents[name], sourceContents[name], targetContents[name])
		}
	} else if target.Kind == EntryKind_File {
		if source != nil && source.Kind == EntryKind_File {
			target.Executable = source.Executable
		} else if ancestor != nil && ancestor.Kind == EntryKind_File {
			target.Executable = ancestor.Executable
		}
	}
}

func PropagateExecutability(ancestor, source, target *Entry) *Entry {
	// Create a copy of the snapshot that we can mutate.
	result := target.Copy()

	// Perform propagation.
	propagateExecutability(ancestor, source, result)

	// Done.
	return result
}

func stripExecutability(snapshot *Entry) {
	// If the entry is nil, then there's nothing to strip.
	if snapshot == nil {
		return
	}

	// Handle the propagation based on entry kind.
	if snapshot.Kind == EntryKind_Directory {
		for _, entry := range snapshot.Contents {
			stripExecutability(entry)
		}
	} else if snapshot.Kind == EntryKind_File {
		snapshot.Executable = false
	}
}

func StripExecutability(snapshot *Entry) *Entry {
	// Create a copy of the snapshot that we can mutate.
	result := snapshot.Copy()

	// Perform stripping.
	stripExecutability(result)

	// Done.
	return result
}
<commit_msg>Restricted executability propagation to content match.<commit_after>package sync

import (
	"bytes"
)

func propagateExecutability(ancestor, source, target *Entry) {
	// If target is nil, then we don't have anything to propagate to, so bail.
	if target == nil {
		return
	}

	// Handle based on target kind.
	if target.Kind == EntryKind_Directory {
		ancestorContents := ancestor.GetContents()
		sourceContents := source.GetContents()
		targetContents := target.GetContents()
		for name := range targetContents {
			propagateExecutability(ancestorContents[name], sourceContents[name], targetContents[name])
		}
	} else if target.Kind == EntryKind_File {
		if source != nil && source.Kind == EntryKind_File && bytes.Equal(source.Digest, target.Digest) {
			target.Executable = source.Executable
		} else if ancestor != nil && ancestor.Kind == EntryKind_File && bytes.Equal(ancestor.Digest, target.Digest) {
			target.Executable = ancestor.Executable
		}
	}
}

func PropagateExecutability(ancestor, source, target *Entry) *Entry {
	// Create a copy of the snapshot that we can mutate.
	result := target.Copy()

	// Perform propagation.
	propagateExecutability(ancestor, source, result)

	// Done.
	return result
}

func stripExecutability(snapshot *Entry) {
	// If the entry is nil, then there's nothing to strip.
	if snapshot == nil {
		return
	}

	// Handle the propagation based on entry kind.
	if snapshot.Kind == EntryKind_Directory {
		for _, entry := range snapshot.Contents {
			stripExecutability(entry)
		}
	} else if snapshot.Kind == EntryKind_File {
		snapshot.Executable = false
	}
}

func StripExecutability(snapshot *Entry) *Entry {
	// Create a copy of the snapshot that we can mutate.
	result := snapshot.Copy()

	// Perform stripping.
	stripExecutability(result)

	// Done.
	return result
}
<|endoftext|>


<commit_before>package server

import (
	"net/http"
	"sort"
	"strings"
	"time"

	"github.com/facette/facette/pkg/library"
	"github.com/facette/facette/pkg/utils"
	"github.com/facette/facette/thirdparty/github.com/fatih/set"
)

func (server *Server) serveCatalog(writer http.ResponseWriter, request *http.Request) {
	setHTTPCacheHeaders(writer)

	if strings.HasPrefix(request.URL.Path, urlCatalogPath+"origins/") {
		server.serveOrigin(writer, request)
	} else if strings.HasPrefix(request.URL.Path, urlCatalogPath+"sources/") {
		server.serveSource(writer, request)
	} else if strings.HasPrefix(request.URL.Path, urlCatalogPath+"metrics/") {
		server.serveMetric(writer, request)
	} else {
		server.serveResponse(writer, nil, http.StatusNotFound)
	}
}

func (server *Server) serveOrigin(writer http.ResponseWriter, request *http.Request) {
	originName := strings.TrimPrefix(request.URL.Path, urlCatalogPath+"origins/")

	if originName == "" {
		server.serveOriginList(writer, request)
		return
	}

	if response, status := server.parseShowRequest(writer, request); status != http.StatusOK {
		server.serveResponse(writer, response, status)
		return
	} else if _, ok := server.Catalog.Origins[originName]; !ok {
		server.serveResponse(writer, serverResponse{mesgResourceNotFound}, http.StatusNotFound)
		return
	}

	response := OriginResponse{
		Name:      originName,
		Connector: server.Config.Origins[originName].Connector["type"],
		Updated:   server.Catalog.Updated.Format(time.RFC3339),
	}

	server.serveResponse(writer, response, http.StatusOK)
}

func (server *Server) serveOriginList(writer http.ResponseWriter, request *http.Request) {
	var offset, limit int

	if response, status := server.parseListRequest(writer, request, &offset, &limit); status != http.StatusOK {
		server.serveResponse(writer, response, status)
		return
	}

	originSet := set.New(set.ThreadSafe)

	for _, origin := range server.Catalog.Origins {
		if request.FormValue("filter") != "" && !utils.FilterMatch(request.FormValue("filter"), origin.Name) {
			continue
		}

		originSet.Add(origin.Name)
	}

	response := &listResponse{
		list:   StringListResponse(set.StringSlice(originSet)),
		offset: offset,
		limit:  limit,
	}

	server.applyResponseLimit(writer, request, response)

	server.serveResponse(writer, response.list, http.StatusOK)
}

func (server *Server) serveSource(writer http.ResponseWriter, request *http.Request) {
	sourceName := strings.TrimPrefix(request.URL.Path, urlCatalogPath+"sources/")

	if sourceName == "" {
		server.serveSourceList(writer, request)
		return
	} else if response, status := server.parseShowRequest(writer, request); status != http.StatusOK {
		server.serveResponse(writer, response, status)
		return
	}

	originSet := set.New(set.ThreadSafe)

	for _, origin := range server.Catalog.Origins {
		if _, ok := origin.Sources[sourceName]; ok {
			originSet.Add(origin.Name)
		}
	}

	if originSet.Size() == 0 {
		server.serveResponse(writer, serverResponse{mesgResourceNotFound}, http.StatusNotFound)
		return
	}

	origins := set.StringSlice(originSet)
	sort.Strings(origins)

	response := SourceResponse{
		Name:    sourceName,
		Origins: origins,
		Updated: server.Catalog.Updated.Format(time.RFC3339),
	}

	server.serveResponse(writer, response, http.StatusOK)
}

func (server *Server) serveSourceList(writer http.ResponseWriter, request *http.Request) {
	var offset, limit int

	if response, status := server.parseListRequest(writer, request, &offset, &limit); status != http.StatusOK {
		server.serveResponse(writer, response, status)
		return
	}

	originName := request.FormValue("origin")

	sourceSet := set.New(set.ThreadSafe)

	for _, origin := range server.Catalog.Origins {
		if originName != "" && origin.Name != originName {
			continue
		}

		for key := range origin.Sources {
			if request.FormValue("filter") != "" && !utils.FilterMatch(request.FormValue("filter"), key) {
				continue
			}

			sourceSet.Add(key)
		}
	}

	response := &listResponse{
		list:   StringListResponse(set.StringSlice(sourceSet)),
		offset: offset,
		limit:  limit,
	}

	server.applyResponseLimit(writer, request, response)

	server.serveResponse(writer, response.list, http.StatusOK)
}

func (server *Server) serveMetric(writer http.ResponseWriter, request *http.Request) {
	metricName := strings.TrimPrefix(request.URL.Path, urlCatalogPath+"metrics/")

	if metricName == "" {
		server.serveMetricList(writer, request)
		return
	} else if response, status := server.parseShowRequest(writer, request); status != http.StatusOK {
		server.serveResponse(writer, response, status)
		return
	}

	originSet := set.New(set.ThreadSafe)
	sourceSet := set.New(set.ThreadSafe)

	for _, origin := range server.Catalog.Origins {
		for _, source := range origin.Sources {
			if _, ok := source.Metrics[metricName]; ok {
				originSet.Add(origin.Name)
				sourceSet.Add(source.Name)
			}
		}
	}

	if originSet.Size() == 0 {
		server.serveResponse(writer, serverResponse{mesgResourceNotFound}, http.StatusNotFound)
		return
	}

	origins := set.StringSlice(originSet)
	sort.Strings(origins)

	sources := set.StringSlice(sourceSet)
	sort.Strings(sources)

	response := MetricResponse{
		Name:    metricName,
		Origins: origins,
		Sources: sources,
		Updated: server.Catalog.Updated.Format(time.RFC3339),
	}

	server.serveResponse(writer, response, http.StatusOK)
}

func (server *Server) serveMetricList(writer http.ResponseWriter, request *http.Request) {
	var offset, limit int

	if response, status := server.parseListRequest(writer, request, &offset, &limit); status != http.StatusOK {
		server.serveResponse(writer, response, status)
		return
	}

	originName := request.FormValue("origin")
	sourceName := request.FormValue("source")

	sourceSet := set.New(set.ThreadSafe)

	if strings.HasPrefix(sourceName, library.LibraryGroupPrefix) {
		for _, entryName := range server.Library.ExpandGroup(
			strings.TrimPrefix(sourceName, library.LibraryGroupPrefix),
			library.LibraryItemSourceGroup,
		) {
			sourceSet.Add(entryName)
		}
	} else if sourceName != "" {
		sourceSet.Add(sourceName)
	}

	metricSet := set.New(set.ThreadSafe)

	for _, origin := range server.Catalog.Origins {
		if originName != "" && origin.Name != originName {
			continue
		}

		for _, source := range origin.Sources {
			if sourceName != "" && sourceSet.IsEmpty() || !sourceSet.IsEmpty() && !sourceSet.Has(source.Name) {
				continue
			}

			for key := range source.Metrics {
				if request.FormValue("filter") != "" && !utils.FilterMatch(request.FormValue("filter"), key) {
					continue
				}

				metricSet.Add(key)
			}
		}
	}

	response := &listResponse{
		list:   StringListResponse(set.StringSlice(metricSet)),
		offset: offset,
		limit:  limit,
	}

	server.applyResponseLimit(writer, request, response)

	server.serveResponse(writer, response.list, http.StatusOK)
}
<commit_msg>Implement /catalog/ API request handling<commit_after>package server

import (
	"net/http"
	"sort"
	"strings"
	"time"

	"github.com/facette/facette/pkg/library"
	"github.com/facette/facette/pkg/utils"
	"github.com/facette/facette/thirdparty/github.com/fatih/set"
)

func (server *Server) serveCatalog(writer http.ResponseWriter, request *http.Request) {
	setHTTPCacheHeaders(writer)

	if request.URL.Path == urlCatalogPath {
		server.serveFullCatalog(writer, request)
	} else if strings.HasPrefix(request.URL.Path, urlCatalogPath+"origins/") {
		server.serveOrigin(writer, request)
	} else if strings.HasPrefix(request.URL.Path, urlCatalogPath+"sources/") {
		server.serveSource(writer, request)
	} else if strings.HasPrefix(request.URL.Path, urlCatalogPath+"metrics/") {
		server.serveMetric(writer, request)
	} else {
		server.serveResponse(writer, nil, http.StatusNotFound)
	}
}

func (server *Server) serveFullCatalog(writer http.ResponseWriter, request *http.Request) {
	catalog := make(map[string]map[string][]string)

	for originName, origin := range server.Catalog.Origins {
		catalog[originName] = make(map[string][]string)

		for sourceName, sources := range origin.Sources {
			catalog[originName][sourceName] = make([]string, 0)

			for metricName, _ := range sources.Metrics {
				catalog[originName][sourceName] = append(catalog[originName][sourceName], metricName)
			}

			sort.Strings(catalog[originName][sourceName])
		}
	}

	server.serveResponse(writer, catalog, http.StatusOK)
}

func (server *Server) serveOrigin(writer http.ResponseWriter, request *http.Request) {
	originName := strings.TrimPrefix(request.URL.Path, urlCatalogPath+"origins/")

	if originName == "" {
		server.serveOriginList(writer, request)
		return
	}

	if response, status := server.parseShowRequest(writer, request); status != http.StatusOK {
		server.serveResponse(writer, response, status)
		return
	} else if _, ok := server.Catalog.Origins[originName]; !ok {
		server.serveResponse(writer, serverResponse{mesgResourceNotFound}, http.StatusNotFound)
		return
	}

	response := OriginResponse{
		Name:      originName,
		Connector: server.Config.Origins[originName].Connector["type"],
		Updated:   server.Catalog.Updated.Format(time.RFC3339),
	}

	server.serveResponse(writer, response, http.StatusOK)
}

func (server *Server) serveOriginList(writer http.ResponseWriter, request *http.Request) {
	var offset, limit int

	if response, status := server.parseListRequest(writer, request, &offset, &limit); status != http.StatusOK {
		server.serveResponse(writer, response, status)
		return
	}

	originSet := set.New(set.ThreadSafe)

	for _, origin := range server.Catalog.Origins {
		if request.FormValue("filter") != "" && !utils.FilterMatch(request.FormValue("filter"), origin.Name) {
			continue
		}

		originSet.Add(origin.Name)
	}

	response := &listResponse{
		list:   StringListResponse(set.StringSlice(originSet)),
		offset: offset,
		limit:  limit,
	}

	server.applyResponseLimit(writer, request, response)

	server.serveResponse(writer, response.list, http.StatusOK)
}

func (server *Server) serveSource(writer http.ResponseWriter, request *http.Request) {
	sourceName := strings.TrimPrefix(request.URL.Path, urlCatalogPath+"sources/")

	if sourceName == "" {
		server.serveSourceList(writer, request)
		return
	} else if response, status := server.parseShowRequest(writer, request); status != http.StatusOK {
		server.serveResponse(writer, response, status)
		return
	}

	originSet := set.New(set.ThreadSafe)

	for _, origin := range server.Catalog.Origins {
		if _, ok := origin.Sources[sourceName]; ok {
			originSet.Add(origin.Name)
		}
	}

	if originSet.Size() == 0 {
		server.serveResponse(writer, serverResponse{mesgResourceNotFound}, http.StatusNotFound)
		return
	}

	origins := set.StringSlice(originSet)
	sort.Strings(origins)

	response := SourceResponse{
		Name:    sourceName,
		Origins: origins,
		Updated: server.Catalog.Updated.Format(time.RFC3339),
	}

	server.serveResponse(writer, response, http.StatusOK)
}

func (server *Server) serveSourceList(writer http.ResponseWriter, request *http.Request) {
	var offset, limit int

	if response, status := server.parseListRequest(writer, request, &offset, &limit); status != http.StatusOK {
		server.serveResponse(writer, response, status)
		return
	}

	originName := request.FormValue("origin")

	sourceSet := set.New(set.ThreadSafe)

	for _, origin := range server.Catalog.Origins {
		if originName != "" && origin.Name != originName {
			continue
		}

		for key := range origin.Sources {
			if request.FormValue("filter") != "" && !utils.FilterMatch(request.FormValue("filter"), key) {
				continue
			}

			sourceSet.Add(key)
		}
	}

	response := &listResponse{
		list:   StringListResponse(set.StringSlice(sourceSet)),
		offset: offset,
		limit:  limit,
	}

	server.applyResponseLimit(writer, request, response)

	server.serveResponse(writer, response.list, http.StatusOK)
}

func (server *Server) serveMetric(writer http.ResponseWriter, request *http.Request) {
	metricName := strings.TrimPrefix(request.URL.Path, urlCatalogPath+"metrics/")

	if metricName == "" {
		server.serveMetricList(writer, request)
		return
	} else if response, status := server.parseShowRequest(writer, request); status != http.StatusOK {
		server.serveResponse(writer, response, status)
		return
	}

	originSet := set.New(set.ThreadSafe)
	sourceSet := set.New(set.ThreadSafe)

	for _, origin := range server.Catalog.Origins {
		for _, source := range origin.Sources {
			if _, ok := source.Metrics[metricName]; ok {
				originSet.Add(origin.Name)
				sourceSet.Add(source.Name)
			}
		}
	}

	if originSet.Size() == 0 {
		server.serveResponse(writer, serverResponse{mesgResourceNotFound}, http.StatusNotFound)
		return
	}

	origins := set.StringSlice(originSet)
	sort.Strings(origins)

	sources := set.StringSlice(sourceSet)
	sort.Strings(sources)

	response := MetricResponse{
		Name:    metricName,
		Origins: origins,
		Sources: sources,
		Updated: server.Catalog.Updated.Format(time.RFC3339),
	}

	server.serveResponse(writer, response, http.StatusOK)
}

func (server *Server) serveMetricList(writer http.ResponseWriter, request *http.Request) {
	var offset, limit int

	if response, status := server.parseListRequest(writer, request, &offset, &limit); status != http.StatusOK {
		server.serveResponse(writer, response, status)
		return
	}

	originName := request.FormValue("origin")
	sourceName := request.FormValue("source")

	sourceSet := set.New(set.ThreadSafe)

	if strings.HasPrefix(sourceName, library.LibraryGroupPrefix) {
		for _, entryName := range server.Library.ExpandGroup(
			strings.TrimPrefix(sourceName, library.LibraryGroupPrefix),
			library.LibraryItemSourceGroup,
		) {
			sourceSet.Add(entryName)
		}
	} else if sourceName != "" {
		sourceSet.Add(sourceName)
	}

	metricSet := set.New(set.ThreadSafe)

	for _, origin := range server.Catalog.Origins {
		if originName != "" && origin.Name != originName {
			continue
		}

		for _, source := range origin.Sources {
			if sourceName != "" && sourceSet.IsEmpty() || !sourceSet.IsEmpty() && !sourceSet.Has(source.Name) {
				continue
			}

			for key := range source.Metrics {
				if request.FormValue("filter") != "" && !utils.FilterMatch(request.FormValue("filter"), key) {
					continue
				}

				metricSet.Add(key)
			}
		}
	}

	response := &listResponse{
		list:   StringListResponse(set.StringSlice(metricSet)),
		offset: offset,
		limit:  limit,
	}

	server.applyResponseLimit(writer, request, response)

	server.serveResponse(writer, response.list, http.StatusOK)
}
<|endoftext|>


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

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

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

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

package tiller

import (
	"sort"
)

// SortOrder is an ordering of Kinds.
type SortOrder []string

// InstallOrder is the order in which manifests should be installed (by Kind).
//
// Those occurring earlier in the list get installed before those occurring later in the list.
var InstallOrder SortOrder = []string{
	"Namespace",
	"ResourceQuota",
	"LimitRange",
	"Secret",
	"ConfigMap",
	"PersistentVolume",
	"PersistentVolumeClaim",
	"ServiceAccount",
	"ClusterRole",
	"ClusterRoleBinding",
	"Role",
	"RoleBinding",
	"Service",
	"DaemonSet",
	"Pod",
	"ReplicationController",
	"ReplicaSet",
	"Deployment",
	"StatefulSet",
	"Job",
	"CronJob",
	"Ingress",
}

// UninstallOrder is the order in which manifests should be uninstalled (by Kind).
//
// Those occurring earlier in the list get uninstalled before those occurring later in the list.
var UninstallOrder SortOrder = []string{
	"Ingress",
	"Service",
	"CronJob",
	"Job",
	"StatefulSet",
	"Deployment",
	"ReplicaSet",
	"ReplicationController",
	"Pod",
	"DaemonSet",
	"RoleBinding",
	"Role",
	"ClusterRoleBinding",
	"ClusterRole",
	"ServiceAccount",
	"PersistentVolumeClaim",
	"PersistentVolume",
	"ConfigMap",
	"Secret",
	"LimitRange",
	"ResourceQuota",
	"Namespace",
}

// sortByKind does an in-place sort of manifests by Kind.
//
// Results are sorted by 'ordering'
func sortByKind(manifests []manifest, ordering SortOrder) []manifest {
	ks := newKindSorter(manifests, ordering)
	sort.Sort(ks)
	return ks.manifests
}

type kindSorter struct {
	ordering  map[string]int
	manifests []manifest
}

func newKindSorter(m []manifest, s SortOrder) *kindSorter {
	o := make(map[string]int, len(s))
	for v, k := range s {
		o[k] = v
	}

	return &kindSorter{
		manifests: m,
		ordering:  o,
	}
}

func (k *kindSorter) Len() int { return len(k.manifests) }

func (k *kindSorter) Swap(i, j int) { k.manifests[i], k.manifests[j] = k.manifests[j], k.manifests[i] }

func (k *kindSorter) Less(i, j int) bool {
	a := k.manifests[i]
	b := k.manifests[j]
	first, ok := k.ordering[a.head.Kind]
	if !ok {
		// Unknown is always last
		return false
	}
	second, ok := k.ordering[b.head.Kind]
	if !ok {
		return true
	}
	return first < second
}
<commit_msg>Adding APIService to the sort order<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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

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

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

package tiller

import (
	"sort"
)

// SortOrder is an ordering of Kinds.
type SortOrder []string

// InstallOrder is the order in which manifests should be installed (by Kind).
//
// Those occurring earlier in the list get installed before those occurring later in the list.
var InstallOrder SortOrder = []string{
	"Namespace",
	"ResourceQuota",
	"LimitRange",
	"Secret",
	"ConfigMap",
	"PersistentVolume",
	"PersistentVolumeClaim",
	"ServiceAccount",
	"ClusterRole",
	"ClusterRoleBinding",
	"Role",
	"RoleBinding",
	"Service",
	"DaemonSet",
	"Pod",
	"ReplicationController",
	"ReplicaSet",
	"Deployment",
	"StatefulSet",
	"Job",
	"CronJob",
	"Ingress",
	"APIService",
}

// UninstallOrder is the order in which manifests should be uninstalled (by Kind).
//
// Those occurring earlier in the list get uninstalled before those occurring later in the list.
var UninstallOrder SortOrder = []string{
	"APIService",
	"Ingress",
	"Service",
	"CronJob",
	"Job",
	"StatefulSet",
	"Deployment",
	"ReplicaSet",
	"ReplicationController",
	"Pod",
	"DaemonSet",
	"RoleBinding",
	"Role",
	"ClusterRoleBinding",
	"ClusterRole",
	"ServiceAccount",
	"PersistentVolumeClaim",
	"PersistentVolume",
	"ConfigMap",
	"Secret",
	"LimitRange",
	"ResourceQuota",
	"Namespace",
}

// sortByKind does an in-place sort of manifests by Kind.
//
// Results are sorted by 'ordering'
func sortByKind(manifests []manifest, ordering SortOrder) []manifest {
	ks := newKindSorter(manifests, ordering)
	sort.Sort(ks)
	return ks.manifests
}

type kindSorter struct {
	ordering  map[string]int
	manifests []manifest
}

func newKindSorter(m []manifest, s SortOrder) *kindSorter {
	o := make(map[string]int, len(s))
	for v, k := range s {
		o[k] = v
	}

	return &kindSorter{
		manifests: m,
		ordering:  o,
	}
}

func (k *kindSorter) Len() int { return len(k.manifests) }

func (k *kindSorter) Swap(i, j int) { k.manifests[i], k.manifests[j] = k.manifests[j], k.manifests[i] }

func (k *kindSorter) Less(i, j int) bool {
	a := k.manifests[i]
	b := k.manifests[j]
	first, ok := k.ordering[a.head.Kind]
	if !ok {
		// Unknown is always last
		return false
	}
	second, ok := k.ordering[b.head.Kind]
	if !ok {
		return true
	}
	return first < second
}
<|endoftext|>


<commit_before>package alerting

import (
	"testing"

	"github.com/grafana/grafana/pkg/services/alerting/alertstates"
	"github.com/grafana/grafana/pkg/services/alerting/transformers"
	"github.com/grafana/grafana/pkg/tsdb"
	. "github.com/smartystreets/goconvey/convey"
)

func TestAlertingExecutor(t *testing.T) {
	Convey("Test alert execution", t, func() {
		executor := NewExecutor()

		Convey("single time serie", func() {
			Convey("Show return ok since avg is above 2", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("avg"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Ok)
			})

			Convey("Show return critical since below 2", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: "<"},
					Transformer: transformers.NewAggregationTransformer("avg"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Critical)
			})

			Convey("Show return critical since sum is above 10", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("sum"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Critical)
			})

			Convey("Show return ok since avg is below 10", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("avg"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Ok)
			})

			Convey("Show return ok since min is below 10", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("avg"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}, {9, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Ok)
			})

			Convey("Show return ok since max is above 10", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("max"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{1, 0}, {11, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Critical)
			})

		})

		Convey("muliple time series", func() {
			Convey("both are ok", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("avg"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
					tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Ok)
			})

			Convey("first serie is good, second is critical", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("avg"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
					tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Critical)
			})
		})
	})
}
<commit_msg>test(alerting): make sure the worst state is captured<commit_after>package alerting

import (
	"testing"

	"github.com/grafana/grafana/pkg/services/alerting/alertstates"
	"github.com/grafana/grafana/pkg/services/alerting/transformers"
	"github.com/grafana/grafana/pkg/tsdb"
	. "github.com/smartystreets/goconvey/convey"
)

func TestAlertingExecutor(t *testing.T) {
	Convey("Test alert execution", t, func() {
		executor := NewExecutor()

		Convey("single time serie", func() {
			Convey("Show return ok since avg is above 2", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("avg"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Ok)
			})

			Convey("Show return critical since below 2", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: "<"},
					Transformer: transformers.NewAggregationTransformer("avg"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Critical)
			})

			Convey("Show return critical since sum is above 10", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("sum"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Critical)
			})

			Convey("Show return ok since avg is below 10", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("avg"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Ok)
			})

			Convey("Show return ok since min is below 10", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("avg"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}, {9, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Ok)
			})

			Convey("Show return ok since max is above 10", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("max"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{6, 0}, {11, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Critical)
			})

		})

		Convey("muliple time series", func() {
			Convey("both are ok", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("avg"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
					tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Ok)
			})

			Convey("first serie is good, second is critical", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("avg"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
					tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Critical)
			})

			Convey("first serie is warn, second is critical", func() {
				rule := &AlertRule{
					Critical:    Level{Level: 10, Operator: ">"},
					Warning:     Level{Level: 5, Operator: ">"},
					Transformer: transformers.NewAggregationTransformer("avg"),
				}

				timeSeries := []*tsdb.TimeSeries{
					tsdb.NewTimeSeries("test1", [][2]float64{{6, 0}}),
					tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}}),
				}

				result := executor.evaluateRule(rule, timeSeries)
				So(result.State, ShouldEqual, alertstates.Critical)
			})
		})
	})
}
<|endoftext|>


<commit_before>package sync

import (
	"crypto/sha1"
	"hash"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"testing"

	"github.com/pkg/errors"
)

func testCreateScanCycleWithTemporaryDirectory(
	temporaryDirectory string,
	entry *Entry,
	contentMap map[string][]byte,
	ignores []string,
	symlinkMode SymlinkMode,
	expectEqual bool,
) error {
	// Create test content on disk and defer its removal.
	root, parent, err := testTransitionCreate(temporaryDirectory, entry, contentMap, false)
	if err != nil {
		return errors.Wrap(err, "unable to create test content")
	}
	defer os.RemoveAll(parent)

	// Create a hasher.
	hasher := newTestHasher()

	// Perform a scan.
	snapshot, preservesExecutability, cache, err := Scan(root, hasher, nil, ignores, symlinkMode)
	if !preservesExecutability {
		snapshot = PropagateExecutability(nil, entry, snapshot)
	}
	if err != nil {
		return errors.Wrap(err, "unable to perform scan")
	} else if cache == nil {
		return errors.New("nil cache returned")
	} else if expectEqual && !snapshot.Equal(entry) {
		return errors.New("snapshot not equal to expected")
	} else if !expectEqual && snapshot.Equal(entry) {
		return errors.New("snapshot should not have equaled original")
	}

	// Success.
	return nil
}

func testCreateScanCycle(
	entry *Entry,
	contentMap map[string][]byte,
	ignores []string,
	symlinkMode SymlinkMode,
	expectEqual bool,
) error {
	// Run the underlying test for each of our temporary directories.
	for _, temporaryDirectory := range testingTemporaryDirectories() {
		if err := testCreateScanCycleWithTemporaryDirectory(
			temporaryDirectory,
			entry,
			contentMap,
			ignores,
			symlinkMode,
			expectEqual,
		); err != nil {
			return err
		}
	}

	// Success.
	return nil
}

func TestScanNilRoot(t *testing.T) {
	if err := testCreateScanCycle(testNilEntry, nil, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanFile1Root(t *testing.T) {
	if err := testCreateScanCycle(testFile1Entry, testFile1ContentMap, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanFile2Root(t *testing.T) {
	if err := testCreateScanCycle(testFile2Entry, testFile2ContentMap, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanFile3Root(t *testing.T) {
	if err := testCreateScanCycle(testFile3Entry, testFile3ContentMap, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanDirectory1Root(t *testing.T) {
	if err := testCreateScanCycle(testDirectory1Entry, testDirectory1ContentMap, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanDirectory2Root(t *testing.T) {
	if err := testCreateScanCycle(testDirectory2Entry, testDirectory2ContentMap, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanDirectory3Root(t *testing.T) {
	if err := testCreateScanCycle(testDirectory3Entry, testDirectory3ContentMap, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanDirectorySaneSymlinkSane(t *testing.T) {
	if err := testCreateScanCycle(testDirectoryWithSaneSymlink, nil, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("sane symlink not allowed inside root with sane symlink mode:", err)
	}
}

func TestScanDirectorySaneSymlinkIgnore(t *testing.T) {
	if err := testCreateScanCycle(testDirectoryWithSaneSymlink, nil, nil, SymlinkMode_SymlinkIgnore, false); err != nil {
		t.Error("sane symlink not allowed inside root with ignore symlink mode:", err)
	}
}

func TestScanDirectorySaneSymlinkPOSIXRaw(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if err := testCreateScanCycle(testDirectoryWithSaneSymlink, nil, nil, SymlinkMode_SymlinkPOSIXRaw, true); err != nil {
		t.Error("sane symlink not allowed inside root with POSIX raw symlink mode:", err)
	}
}

func TestScanDirectoryInvalidSymlinkNotSane(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if testCreateScanCycle(testDirectoryWithInvalidSymlink, nil, nil, SymlinkMode_SymlinkPortable, true) == nil {
		t.Error("invalid symlink allowed inside root with sane symlink mode")
	}
}

func TestScanDirectoryInvalidSymlinkIgnore(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if err := testCreateScanCycle(testDirectoryWithInvalidSymlink, nil, nil, SymlinkMode_SymlinkIgnore, false); err != nil {
		t.Error("invalid symlink not allowed inside root with ignore symlink mode:", err)
	}
}

func TestScanDirectoryInvalidSymlinkPOSIXRaw(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if err := testCreateScanCycle(testDirectoryWithInvalidSymlink, nil, nil, SymlinkMode_SymlinkPOSIXRaw, true); err != nil {
		t.Error("invalid symlink not allowed inside root with POSIX raw symlink mode:", err)
	}
}

func TestScanDirectoryEscapingSymlinkSane(t *testing.T) {
	if testCreateScanCycle(testDirectoryWithEscapingSymlink, nil, nil, SymlinkMode_SymlinkPortable, true) == nil {
		t.Error("escaping symlink allowed inside root with sane symlink mode")
	}
}

func TestScanDirectoryEscapingSymlinkIgnore(t *testing.T) {
	if err := testCreateScanCycle(testDirectoryWithEscapingSymlink, nil, nil, SymlinkMode_SymlinkIgnore, false); err != nil {
		t.Error("escaping symlink not allowed inside root with ignore symlink mode:", err)
	}
}

func TestScanDirectoryEscapingSymlinkPOSIXRaw(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if err := testCreateScanCycle(testDirectoryWithEscapingSymlink, nil, nil, SymlinkMode_SymlinkPOSIXRaw, true); err != nil {
		t.Error("escaping symlink not allowed inside root with POSIX raw symlink mode:", err)
	}
}

func TestScanDirectoryAbsoluteSymlinkSane(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if testCreateScanCycle(testDirectoryWithAbsoluteSymlink, nil, nil, SymlinkMode_SymlinkPortable, true) == nil {
		t.Error("escaping symlink allowed inside root with sane symlink mode")
	}
}

func TestScanDirectoryAbsoluteSymlinkIgnore(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if err := testCreateScanCycle(testDirectoryWithAbsoluteSymlink, nil, nil, SymlinkMode_SymlinkIgnore, false); err != nil {
		t.Error("escaping symlink not allowed inside root with ignore symlink mode:", err)
	}
}

func TestScanDirectoryAbsoluteSymlinkPOSIXRaw(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if err := testCreateScanCycle(testDirectoryWithAbsoluteSymlink, nil, nil, SymlinkMode_SymlinkPOSIXRaw, true); err != nil {
		t.Error("escaping symlink not allowed inside root with POSIX raw symlink mode:", err)
	}
}

func TestScanPOSIXRawNotAllowedOnWindows(t *testing.T) {
	if runtime.GOOS != "windows" {
		t.Skip()
	}
	if testCreateScanCycle(testDirectoryWithSaneSymlink, nil, nil, SymlinkMode_SymlinkPOSIXRaw, true) == nil {
		t.Error("POSIX raw symlink mode allowed for scan on Windows")
	}
}

func TestScanSymlinkRoot(t *testing.T) {
	// Create a temporary directory and defer its cleanup.
	parent, err := ioutil.TempDir("", "mutagen_simulated")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(parent)

	// Compute the symlink path.
	root := filepath.Join(parent, "root")

	// Create a symlink inside the parent.
	if err := os.Symlink("relative", root); err != nil {
		t.Fatal("unable to create symlink:", err)
	}

	// Attempt a scan of the symlink.
	if _, _, _, err := Scan(root, sha1.New(), nil, nil, SymlinkMode_SymlinkPortable); err == nil {
		t.Error("scan of symlink root allowed")
	}
}

func TestScanInvalidIgnores(t *testing.T) {
	if testCreateScanCycle(testDirectory1Entry, testDirectory1ContentMap, []string{""}, SymlinkMode_SymlinkPortable, true) == nil {
		t.Error("scan allowed with invalid ignore specification")
	}
}

func TestScanIgnore(t *testing.T) {
	if err := testCreateScanCycle(testDirectory1Entry, testDirectory1ContentMap, []string{"second directory"}, SymlinkMode_SymlinkPortable, false); err != nil {
		t.Error("unexpected result when ignoring directory:", err)
	}
}

func TestScanIgnoreDirectory(t *testing.T) {
	if err := testCreateScanCycle(testDirectory1Entry, testDirectory1ContentMap, []string{"directory/"}, SymlinkMode_SymlinkPortable, false); err != nil {
		t.Error("unexpected result when ignoring directory:", err)
	}
}

func TestScanFileNotIgnoredOnDirectorySpecification(t *testing.T) {
	if err := testCreateScanCycle(testDirectory1Entry, testDirectory1ContentMap, []string{"file/"}, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("unexpected result when ignoring directory:", err)
	}
}

func TestScanSubfileNotIgnoredOnRootSpecification(t *testing.T) {
	if err := testCreateScanCycle(testDirectory1Entry, testDirectory1ContentMap, []string{"/subfile.exe"}, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("unexpected result when ignoring directory:", err)
	}
}

// rescanHashProxy wraps an instance of and implements hash.Hash, but it signals
// a test error if any hashing occurs. It is a test fixture for
// TestEfficientRescan.
type rescanHashProxy struct {
	hash.Hash
	t *testing.T
}

// Sum implements hash.Hash's Sum method, delegating to the underlying hash, but
// signals an error if invoked.
func (p *rescanHashProxy) Sum(b []byte) []byte {
	p.t.Error("rehashing occurred")
	return p.Hash.Sum(b)
}

func TestEfficientRescan(t *testing.T) {
	// Create test content on disk and defer its removal. We only test on the
	// default temporary directory.
	// TODO: Should we test with other temporary directories? We might want to
	// be on the lookout for filesystems where (e.g.) modification times aren't
	// consistent, but we already consider this an invariant, so I'm not sure we
	// need to check other filesystems. This test is more about verifying our
	// use of the cache.
	root, parent, err := testTransitionCreate("", testDirectory1Entry, testDirectory1ContentMap, false)
	if err != nil {
		t.Fatal("unable to create test content on disk:", err)
	}
	defer os.RemoveAll(parent)

	// Create a hasher.
	hasher := newTestHasher()

	// Create an initial snapshot and validate the results.
	snapshot, preservesExecutability, cache, err := Scan(root, hasher, nil, nil, SymlinkMode_SymlinkPortable)
	if !preservesExecutability {
		snapshot = PropagateExecutability(nil, testDirectory1Entry, snapshot)
	}
	if err != nil {
		t.Fatal("unable to create snapshot:", err)
	} else if cache == nil {
		t.Fatal("nil cache returned")
	} else if !snapshot.Equal(testDirectory1Entry) {
		t.Error("snapshot did not match expected")
	}

	// Attempt a rescan and ensure that no hashing occurs.
	hasher = &rescanHashProxy{hasher, t}
	snapshot, preservesExecutability, cache, err = Scan(root, hasher, cache, nil, SymlinkMode_SymlinkPortable)
	if !preservesExecutability {
		snapshot = PropagateExecutability(nil, testDirectory1Entry, snapshot)
	}
	if err != nil {
		t.Fatal("unable to rescan:", err)
	} else if cache == nil {
		t.Fatal("nil second cache returned")
	} else if !snapshot.Equal(testDirectory1Entry) {
		t.Error("second snapshot did not match expected")
	}
}
<commit_msg>Added cross-device scan test.<commit_after>package sync

import (
	"crypto/sha1"
	"hash"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"testing"

	"github.com/pkg/errors"
)

func testCreateScanCycleWithTemporaryDirectory(
	temporaryDirectory string,
	entry *Entry,
	contentMap map[string][]byte,
	ignores []string,
	symlinkMode SymlinkMode,
	expectEqual bool,
) error {
	// Create test content on disk and defer its removal.
	root, parent, err := testTransitionCreate(temporaryDirectory, entry, contentMap, false)
	if err != nil {
		return errors.Wrap(err, "unable to create test content")
	}
	defer os.RemoveAll(parent)

	// Create a hasher.
	hasher := newTestHasher()

	// Perform a scan.
	snapshot, preservesExecutability, cache, err := Scan(root, hasher, nil, ignores, symlinkMode)
	if !preservesExecutability {
		snapshot = PropagateExecutability(nil, entry, snapshot)
	}
	if err != nil {
		return errors.Wrap(err, "unable to perform scan")
	} else if cache == nil {
		return errors.New("nil cache returned")
	} else if expectEqual && !snapshot.Equal(entry) {
		return errors.New("snapshot not equal to expected")
	} else if !expectEqual && snapshot.Equal(entry) {
		return errors.New("snapshot should not have equaled original")
	}

	// Success.
	return nil
}

func testCreateScanCycle(
	entry *Entry,
	contentMap map[string][]byte,
	ignores []string,
	symlinkMode SymlinkMode,
	expectEqual bool,
) error {
	// Run the underlying test for each of our temporary directories.
	for _, temporaryDirectory := range testingTemporaryDirectories() {
		if err := testCreateScanCycleWithTemporaryDirectory(
			temporaryDirectory,
			entry,
			contentMap,
			ignores,
			symlinkMode,
			expectEqual,
		); err != nil {
			return err
		}
	}

	// Success.
	return nil
}

func TestScanNilRoot(t *testing.T) {
	if err := testCreateScanCycle(testNilEntry, nil, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanFile1Root(t *testing.T) {
	if err := testCreateScanCycle(testFile1Entry, testFile1ContentMap, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanFile2Root(t *testing.T) {
	if err := testCreateScanCycle(testFile2Entry, testFile2ContentMap, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanFile3Root(t *testing.T) {
	if err := testCreateScanCycle(testFile3Entry, testFile3ContentMap, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanDirectory1Root(t *testing.T) {
	if err := testCreateScanCycle(testDirectory1Entry, testDirectory1ContentMap, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanDirectory2Root(t *testing.T) {
	if err := testCreateScanCycle(testDirectory2Entry, testDirectory2ContentMap, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanDirectory3Root(t *testing.T) {
	if err := testCreateScanCycle(testDirectory3Entry, testDirectory3ContentMap, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("creation/scan cycle failed:", err)
	}
}

func TestScanDirectorySaneSymlinkSane(t *testing.T) {
	if err := testCreateScanCycle(testDirectoryWithSaneSymlink, nil, nil, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("sane symlink not allowed inside root with sane symlink mode:", err)
	}
}

func TestScanDirectorySaneSymlinkIgnore(t *testing.T) {
	if err := testCreateScanCycle(testDirectoryWithSaneSymlink, nil, nil, SymlinkMode_SymlinkIgnore, false); err != nil {
		t.Error("sane symlink not allowed inside root with ignore symlink mode:", err)
	}
}

func TestScanDirectorySaneSymlinkPOSIXRaw(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if err := testCreateScanCycle(testDirectoryWithSaneSymlink, nil, nil, SymlinkMode_SymlinkPOSIXRaw, true); err != nil {
		t.Error("sane symlink not allowed inside root with POSIX raw symlink mode:", err)
	}
}

func TestScanDirectoryInvalidSymlinkNotSane(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if testCreateScanCycle(testDirectoryWithInvalidSymlink, nil, nil, SymlinkMode_SymlinkPortable, true) == nil {
		t.Error("invalid symlink allowed inside root with sane symlink mode")
	}
}

func TestScanDirectoryInvalidSymlinkIgnore(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if err := testCreateScanCycle(testDirectoryWithInvalidSymlink, nil, nil, SymlinkMode_SymlinkIgnore, false); err != nil {
		t.Error("invalid symlink not allowed inside root with ignore symlink mode:", err)
	}
}

func TestScanDirectoryInvalidSymlinkPOSIXRaw(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if err := testCreateScanCycle(testDirectoryWithInvalidSymlink, nil, nil, SymlinkMode_SymlinkPOSIXRaw, true); err != nil {
		t.Error("invalid symlink not allowed inside root with POSIX raw symlink mode:", err)
	}
}

func TestScanDirectoryEscapingSymlinkSane(t *testing.T) {
	if testCreateScanCycle(testDirectoryWithEscapingSymlink, nil, nil, SymlinkMode_SymlinkPortable, true) == nil {
		t.Error("escaping symlink allowed inside root with sane symlink mode")
	}
}

func TestScanDirectoryEscapingSymlinkIgnore(t *testing.T) {
	if err := testCreateScanCycle(testDirectoryWithEscapingSymlink, nil, nil, SymlinkMode_SymlinkIgnore, false); err != nil {
		t.Error("escaping symlink not allowed inside root with ignore symlink mode:", err)
	}
}

func TestScanDirectoryEscapingSymlinkPOSIXRaw(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if err := testCreateScanCycle(testDirectoryWithEscapingSymlink, nil, nil, SymlinkMode_SymlinkPOSIXRaw, true); err != nil {
		t.Error("escaping symlink not allowed inside root with POSIX raw symlink mode:", err)
	}
}

func TestScanDirectoryAbsoluteSymlinkSane(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if testCreateScanCycle(testDirectoryWithAbsoluteSymlink, nil, nil, SymlinkMode_SymlinkPortable, true) == nil {
		t.Error("escaping symlink allowed inside root with sane symlink mode")
	}
}

func TestScanDirectoryAbsoluteSymlinkIgnore(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if err := testCreateScanCycle(testDirectoryWithAbsoluteSymlink, nil, nil, SymlinkMode_SymlinkIgnore, false); err != nil {
		t.Error("escaping symlink not allowed inside root with ignore symlink mode:", err)
	}
}

func TestScanDirectoryAbsoluteSymlinkPOSIXRaw(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip()
	}
	if err := testCreateScanCycle(testDirectoryWithAbsoluteSymlink, nil, nil, SymlinkMode_SymlinkPOSIXRaw, true); err != nil {
		t.Error("escaping symlink not allowed inside root with POSIX raw symlink mode:", err)
	}
}

func TestScanPOSIXRawNotAllowedOnWindows(t *testing.T) {
	if runtime.GOOS != "windows" {
		t.Skip()
	}
	if testCreateScanCycle(testDirectoryWithSaneSymlink, nil, nil, SymlinkMode_SymlinkPOSIXRaw, true) == nil {
		t.Error("POSIX raw symlink mode allowed for scan on Windows")
	}
}

func TestScanSymlinkRoot(t *testing.T) {
	// Create a temporary directory and defer its cleanup.
	parent, err := ioutil.TempDir("", "mutagen_simulated")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(parent)

	// Compute the symlink path.
	root := filepath.Join(parent, "root")

	// Create a symlink inside the parent.
	if err := os.Symlink("relative", root); err != nil {
		t.Fatal("unable to create symlink:", err)
	}

	// Attempt a scan of the symlink.
	if _, _, _, err := Scan(root, sha1.New(), nil, nil, SymlinkMode_SymlinkPortable); err == nil {
		t.Error("scan of symlink root allowed")
	}
}

func TestScanInvalidIgnores(t *testing.T) {
	if testCreateScanCycle(testDirectory1Entry, testDirectory1ContentMap, []string{""}, SymlinkMode_SymlinkPortable, true) == nil {
		t.Error("scan allowed with invalid ignore specification")
	}
}

func TestScanIgnore(t *testing.T) {
	if err := testCreateScanCycle(testDirectory1Entry, testDirectory1ContentMap, []string{"second directory"}, SymlinkMode_SymlinkPortable, false); err != nil {
		t.Error("unexpected result when ignoring directory:", err)
	}
}

func TestScanIgnoreDirectory(t *testing.T) {
	if err := testCreateScanCycle(testDirectory1Entry, testDirectory1ContentMap, []string{"directory/"}, SymlinkMode_SymlinkPortable, false); err != nil {
		t.Error("unexpected result when ignoring directory:", err)
	}
}

func TestScanFileNotIgnoredOnDirectorySpecification(t *testing.T) {
	if err := testCreateScanCycle(testDirectory1Entry, testDirectory1ContentMap, []string{"file/"}, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("unexpected result when ignoring directory:", err)
	}
}

func TestScanSubfileNotIgnoredOnRootSpecification(t *testing.T) {
	if err := testCreateScanCycle(testDirectory1Entry, testDirectory1ContentMap, []string{"/subfile.exe"}, SymlinkMode_SymlinkPortable, true); err != nil {
		t.Error("unexpected result when ignoring directory:", err)
	}
}

// rescanHashProxy wraps an instance of and implements hash.Hash, but it signals
// a test error if any hashing occurs. It is a test fixture for
// TestEfficientRescan.
type rescanHashProxy struct {
	hash.Hash
	t *testing.T
}

// Sum implements hash.Hash's Sum method, delegating to the underlying hash, but
// signals an error if invoked.
func (p *rescanHashProxy) Sum(b []byte) []byte {
	p.t.Error("rehashing occurred")
	return p.Hash.Sum(b)
}

func TestEfficientRescan(t *testing.T) {
	// Create test content on disk and defer its removal. We only test on the
	// default temporary directory.
	// TODO: Should we test with other temporary directories? We might want to
	// be on the lookout for filesystems where (e.g.) modification times aren't
	// consistent, but we already consider this an invariant, so I'm not sure we
	// need to check other filesystems. This test is more about verifying our
	// use of the cache.
	root, parent, err := testTransitionCreate("", testDirectory1Entry, testDirectory1ContentMap, false)
	if err != nil {
		t.Fatal("unable to create test content on disk:", err)
	}
	defer os.RemoveAll(parent)

	// Create a hasher.
	hasher := newTestHasher()

	// Create an initial snapshot and validate the results.
	snapshot, preservesExecutability, cache, err := Scan(root, hasher, nil, nil, SymlinkMode_SymlinkPortable)
	if !preservesExecutability {
		snapshot = PropagateExecutability(nil, testDirectory1Entry, snapshot)
	}
	if err != nil {
		t.Fatal("unable to create snapshot:", err)
	} else if cache == nil {
		t.Fatal("nil cache returned")
	} else if !snapshot.Equal(testDirectory1Entry) {
		t.Error("snapshot did not match expected")
	}

	// Attempt a rescan and ensure that no hashing occurs.
	hasher = &rescanHashProxy{hasher, t}
	snapshot, preservesExecutability, cache, err = Scan(root, hasher, cache, nil, SymlinkMode_SymlinkPortable)
	if !preservesExecutability {
		snapshot = PropagateExecutability(nil, testDirectory1Entry, snapshot)
	}
	if err != nil {
		t.Fatal("unable to rescan:", err)
	} else if cache == nil {
		t.Fatal("nil second cache returned")
	} else if !snapshot.Equal(testDirectory1Entry) {
		t.Error("second snapshot did not match expected")
	}
}

func TestScanCrossDeviceFail(t *testing.T) {
	// Skip if we don't have the subroot.
	subroot := os.Getenv("MUTAGEN_TEST_FAT32_SUBROOT")
	if subroot == "" {
		t.Skip()
	}

	// Compute the subroot parent.
	parent := filepath.Dir(subroot)

	// Create a hasher.
	hasher := newTestHasher()

	// Perform a scan and ensure that it fails.
	if _, _, _, err := Scan(parent, hasher, nil, nil, SymlinkMode_SymlinkPortable); err == nil {
		t.Error("scan across device boundary did not fail")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"os/user"
	"path"
	"runtime"
	"strings"
	"time"

	"github.com/pkg/errors"

	"gopkg.in/yaml.v2"
)

type app struct {
	filenameWithPath string
	kubectlUser      string
	kubeloginAlias   string
	kubeloginServer  string
}

var (
	aliasFlag              string
	userFlag               string
	kubeloginServerBaseURL string
	doneChannel            chan bool
	usageMessage           = `Kubelogin Usage:

  One time login:
    kubelogin login --server-url=https://kubelogin.example.com --kubectl-user=user

  Configure an alias (shortcut):
    kubelogin config --alias=example --server-url=https://kubelogin.example.com --kubectl-user=example_oidc

  Use an alias:
    kubelogin login example`
)

//AliasConfig contains the structure of what's in the config file
type AliasConfig struct {
	Alias       string `yaml:"alias"`
	BaseURL     string `yaml:"server-url"`
	KubectlUser string `yaml:"kubectl-user"`
}

type Config struct {
	Aliases []*AliasConfig `yaml:"aliases"`
}

func findFreePort() (string, error) {
	server, err := net.Listen("tcp", ":0")
	if err != nil {
		return "", err
	}
	defer server.Close()
	hostString := server.Addr().String()
	_, portString, err := net.SplitHostPort(hostString)
	if err != nil {
		return "", err
	}
	return portString, nil
}

func (app *app) makeExchange(token string) error {
	url := fmt.Sprintf("%s/exchange?token=%s", app.kubeloginServer, token)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Printf("Unable to create request. %s", err)
		return err
	}
	client := http.DefaultClient
	res, err := client.Do(req)
	if err != nil {
		log.Printf("Unable to make request. %s", err)
		return err
	}
	if res.StatusCode != http.StatusOK {
		log.Fatalf("Failed to retrieve token from kubelogin server. Please try again or contact your administrator")
	}
	defer res.Body.Close()
	jwt, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Printf("Unable to read response body. %s", err)
		return err
	}
	if err := app.configureKubectl(string(jwt)); err != nil {
		log.Printf("Error when setting credentials: %v", err)
		return err
	}
	return nil
}

func (app *app) tokenHandler(w http.ResponseWriter, r *http.Request) {
	token := r.FormValue("token")
	if err := app.makeExchange(token); err != nil {
		log.Fatalf("Could not exchange token for jwt %v", err)
	}
	fmt.Fprint(w, "You are now logged in! You can close this window")
	doneChannel <- true
}

func (app *app) configureKubectl(jwt string) error {
	configCmd := exec.Command("kubectl", "config", "set-credentials", app.kubectlUser, "--token="+jwt)
	if err := configCmd.Run(); err != nil {
		return err
	}
	return nil
}

func (app *app) generateAuthURL() (string, string, error) {
	portNum, err := findFreePort()
	if err != nil {
		log.Print("err, could not find an open port")
		return "", "", err
	}

	loginURL := fmt.Sprintf("%s/login?port=%s", app.kubeloginServer, portNum)

	return loginURL, portNum, nil
}

func createMux(app app) *http.ServeMux {
	newMux := http.NewServeMux()
	newMux.HandleFunc("/exchange/", app.tokenHandler)
	newMux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(""))
		return
	})
	return newMux
}

func generateURLAndListenForServerResponse(app app) {
	loginURL, portNum, err := app.generateAuthURL()
	if err != nil {
		log.Fatal(err.Error())
	}
	doneChannel = make(chan bool)
	go func() {
		l, err := net.Listen("tcp", ":"+portNum)
		if err != nil {
			fmt.Printf("Error listening on port: %s. Error: %v\n", portNum, err)
			os.Exit(1)
		}
		if runtime.GOOS == "darwin" {
			// On OS X, run the `open` CLI to use the default browser to open the login URL.
			fmt.Printf("Opening %s ...\n", loginURL)
			err := exec.Command("open", loginURL).Run()
			if err != nil {
				fmt.Printf("Error opening; please open the URL manually.\n", loginURL)
			}
		} else {
			fmt.Printf("Follow this URL to log into auth provider: %s\n", loginURL)
		}
		if err = http.Serve(l, createMux(app)); err != nil {
			fmt.Printf("Error listening on port: %s. Error: %v\n", portNum, err)
			os.Exit(1)
		}
	}()
	<-doneChannel
	fmt.Println("You are now logged in! Enjoy kubectl-ing!")
	time.Sleep(1 * time.Second)
}

func setFlags(command *flag.FlagSet, loginCmd bool) {
	if !loginCmd {
		command.StringVar(&aliasFlag, "alias", "default", "alias name in the config file, used for an easy login")
	}
	command.StringVar(&userFlag, "kubectl-user", "kubelogin_user", "in kubectl config, username used to store credentials")
	command.StringVar(&kubeloginServerBaseURL, "server-url", "", "base URL of the kubelogin server, ex: https://kubelogin.example.com")
}
func (app *app) getConfigSettings(alias string) error {
	yamlFile, err := ioutil.ReadFile(app.filenameWithPath)
	if err != nil {
		return errors.Wrap(err, "failed to read config file for login use")
	}
	var config Config
	if err := yaml.Unmarshal(yamlFile, &config); err != nil {
		return errors.Wrap(err, "failed to unmarshal yaml file for login use")
	}

	aliasConfig, ok := config.aliasSearch(alias)
	if !ok {
		return errors.New("Could not find specified alias, check spelling or use the config verb to create an alias")
	}
	app.kubectlUser = aliasConfig.KubectlUser
	app.kubeloginServer = aliasConfig.BaseURL
	return nil
}

func (config *Config) aliasSearch(alias string) (*AliasConfig, bool) {
	for index, aliases := range config.Aliases {
		if alias == aliases.Alias {
			return config.Aliases[index], true
		}
	}
	return nil, false
}

func (config *Config) createConfig(onDiskFile string, aliasConfig AliasConfig) error {
	log.Print("Couldn't find config file in root directory. Creating config file...")
	_, e := os.Stat(onDiskFile) // Does config file exist?
	if os.IsNotExist(e) {       // Create file
		fh, err := os.Create(onDiskFile)
		if err != nil {
			return errors.Wrap(err, "failed to create file in root directory")
		}
		fh.Close()
	}

	log.Print("Config file created, setting config values...")
	config.Aliases = make([]*AliasConfig, 0)
	config.appendAlias(aliasConfig)
	if err := config.writeToFile(onDiskFile); err != nil {
		log.Fatal(err)
	}
	log.Print("File configured")
	return nil
}

func (config *Config) newAliasConfig(kubeloginrcAlias, loginServerURL, kubectlUser string) AliasConfig {
	newConfig := AliasConfig{
		BaseURL:     loginServerURL,
		Alias:       kubeloginrcAlias,
		KubectlUser: kubectlUser,
	}
	return newConfig
}

func (config *Config) appendAlias(aliasConfig AliasConfig) {
	config.Aliases = append(config.Aliases, &aliasConfig)
}

func (config *Config) writeToFile(onDiskFile string) error {
	marshaledYaml, err := yaml.Marshal(config)
	if err != nil {
		return errors.Wrap(err, "failed to marshal alias yaml")
	}
	if err := ioutil.WriteFile(onDiskFile, marshaledYaml, 0600); err != nil {
		return errors.Wrap(err, "failed to write to kubeloginrc file with the alias")
	}
	log.Printf(string(marshaledYaml))
	return nil
}

func (config *Config) updateAlias(aliasConfig *AliasConfig, loginServerURL *url.URL, onDiskFile string) error {
	aliasConfig.KubectlUser = userFlag
	aliasConfig.BaseURL = loginServerURL.String()
	if err := config.writeToFile(onDiskFile); err != nil {
		log.Fatal(err)
	}
	log.Print("Alias updated")
	return nil
}

func (app *app) configureFile(kubeloginrcAlias string, loginServerURL *url.URL, kubectlUser string) error {
	var config Config
	aliasConfig := config.newAliasConfig(kubeloginrcAlias, loginServerURL.String(), kubectlUser)
	yamlFile, err := ioutil.ReadFile(app.filenameWithPath)
	if err != nil {
		if err := config.createConfig(app.filenameWithPath, aliasConfig); err != nil {
			return err
		}
		return nil
	}
	if err := yaml.Unmarshal(yamlFile, &config); err != nil {
		return errors.Wrap(err, "failed to unmarshal yaml file")
	}
	foundAliasConfig, ok := config.aliasSearch(aliasFlag)
	if !ok {
		newConfig := config.newAliasConfig(kubeloginrcAlias, loginServerURL.String(), kubectlUser)
		config.appendAlias(newConfig)
		if err := config.writeToFile(app.filenameWithPath); err != nil {
			log.Fatal(err)
		}
		log.Print("New Alias configured")
		return nil
	}
	if err := config.updateAlias(foundAliasConfig, loginServerURL, app.filenameWithPath); err != nil {
		return err
	}
	return nil
}

func main() {
	var app app
	loginCommmand := flag.NewFlagSet("login", flag.ExitOnError)
	setFlags(loginCommmand, true)
	configCommand := flag.NewFlagSet("config", flag.ExitOnError)
	setFlags(configCommand, false)
	user, err := user.Current()
	if err != nil {
		log.Fatalf("Could not determine current user of this system. Err: %v", err)
	}
	app.filenameWithPath = path.Join(user.HomeDir, "/.kubeloginrc.yaml")
	if len(os.Args) < 3 {
		fmt.Println(usageMessage)
		os.Exit(1)
	}
	switch os.Args[1] {
	case "login":
		if !strings.HasPrefix(os.Args[2], "--") {
			//use alias to extract needed information
			if err := app.getConfigSettings(os.Args[2]); err != nil {
				log.Fatal(err)
			}
			generateURLAndListenForServerResponse(app)
		} else {
			loginCommmand.Parse(os.Args[2:])
			if loginCommmand.Parsed() {
				if kubeloginServerBaseURL == "" {
					log.Fatal("--server-url must be set!")
				}
				app.kubectlUser = userFlag
				app.kubeloginServer = kubeloginServerBaseURL
				generateURLAndListenForServerResponse(app)
			}
		}
	case "config":
		configCommand.Parse(os.Args[2:])
		if configCommand.Parsed() {
			if kubeloginServerBaseURL == "" {
				log.Fatal("--server-url must be set!")
			}
			verifiedServerURL, err := url.ParseRequestURI(kubeloginServerBaseURL)
			if err != nil {
				log.Fatalf("Invalid URL given: %v | Err: %v", kubeloginServerBaseURL, err)
			}

			if err := app.configureFile(aliasFlag, verifiedServerURL, userFlag); err != nil {
				log.Fatal(err)
			}
			os.Exit(0)
		}
	default:
		fmt.Println(usageMessage)
		os.Exit(1)
	}
}
<commit_msg>Use full path for exec<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"os/user"
	"path"
	"runtime"
	"strings"
	"time"

	"github.com/pkg/errors"

	"gopkg.in/yaml.v2"
)

type app struct {
	filenameWithPath string
	kubectlUser      string
	kubeloginAlias   string
	kubeloginServer  string
}

var (
	aliasFlag              string
	userFlag               string
	kubeloginServerBaseURL string
	doneChannel            chan bool
	usageMessage           = `Kubelogin Usage:

  One time login:
    kubelogin login --server-url=https://kubelogin.example.com --kubectl-user=user

  Configure an alias (shortcut):
    kubelogin config --alias=example --server-url=https://kubelogin.example.com --kubectl-user=example_oidc

  Use an alias:
    kubelogin login example`
)

//AliasConfig contains the structure of what's in the config file
type AliasConfig struct {
	Alias       string `yaml:"alias"`
	BaseURL     string `yaml:"server-url"`
	KubectlUser string `yaml:"kubectl-user"`
}

type Config struct {
	Aliases []*AliasConfig `yaml:"aliases"`
}

func findFreePort() (string, error) {
	server, err := net.Listen("tcp", ":0")
	if err != nil {
		return "", err
	}
	defer server.Close()
	hostString := server.Addr().String()
	_, portString, err := net.SplitHostPort(hostString)
	if err != nil {
		return "", err
	}
	return portString, nil
}

func (app *app) makeExchange(token string) error {
	url := fmt.Sprintf("%s/exchange?token=%s", app.kubeloginServer, token)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Printf("Unable to create request. %s", err)
		return err
	}
	client := http.DefaultClient
	res, err := client.Do(req)
	if err != nil {
		log.Printf("Unable to make request. %s", err)
		return err
	}
	if res.StatusCode != http.StatusOK {
		log.Fatalf("Failed to retrieve token from kubelogin server. Please try again or contact your administrator")
	}
	defer res.Body.Close()
	jwt, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Printf("Unable to read response body. %s", err)
		return err
	}
	if err := app.configureKubectl(string(jwt)); err != nil {
		log.Printf("Error when setting credentials: %v", err)
		return err
	}
	return nil
}

func (app *app) tokenHandler(w http.ResponseWriter, r *http.Request) {
	token := r.FormValue("token")
	if err := app.makeExchange(token); err != nil {
		log.Fatalf("Could not exchange token for jwt %v", err)
	}
	fmt.Fprint(w, "You are now logged in! You can close this window")
	doneChannel <- true
}

func (app *app) configureKubectl(jwt string) error {
	configCmd := exec.Command("kubectl", "config", "set-credentials", app.kubectlUser, "--token="+jwt)
	if err := configCmd.Run(); err != nil {
		return err
	}
	return nil
}

func (app *app) generateAuthURL() (string, string, error) {
	portNum, err := findFreePort()
	if err != nil {
		log.Print("err, could not find an open port")
		return "", "", err
	}

	loginURL := fmt.Sprintf("%s/login?port=%s", app.kubeloginServer, portNum)

	return loginURL, portNum, nil
}

func createMux(app app) *http.ServeMux {
	newMux := http.NewServeMux()
	newMux.HandleFunc("/exchange/", app.tokenHandler)
	newMux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(""))
		return
	})
	return newMux
}

func generateURLAndListenForServerResponse(app app) {
	loginURL, portNum, err := app.generateAuthURL()
	if err != nil {
		log.Fatal(err.Error())
	}
	doneChannel = make(chan bool)
	go func() {
		l, err := net.Listen("tcp", ":"+portNum)
		if err != nil {
			fmt.Printf("Error listening on port: %s. Error: %v\n", portNum, err)
			os.Exit(1)
		}
		if runtime.GOOS == "darwin" {
			// On OS X, run the `open` CLI to use the default browser to open the login URL.
			fmt.Printf("Opening %s ...\n", loginURL)
			err := exec.Command("/usr/bin/open", loginURL).Run()
			if err != nil {
				fmt.Printf("Error opening; please open the URL manually.\n", loginURL)
			}
		} else {
			fmt.Printf("Follow this URL to log into auth provider: %s\n", loginURL)
		}
		if err = http.Serve(l, createMux(app)); err != nil {
			fmt.Printf("Error listening on port: %s. Error: %v\n", portNum, err)
			os.Exit(1)
		}
	}()
	<-doneChannel
	fmt.Println("You are now logged in! Enjoy kubectl-ing!")
	time.Sleep(1 * time.Second)
}

func setFlags(command *flag.FlagSet, loginCmd bool) {
	if !loginCmd {
		command.StringVar(&aliasFlag, "alias", "default", "alias name in the config file, used for an easy login")
	}
	command.StringVar(&userFlag, "kubectl-user", "kubelogin_user", "in kubectl config, username used to store credentials")
	command.StringVar(&kubeloginServerBaseURL, "server-url", "", "base URL of the kubelogin server, ex: https://kubelogin.example.com")
}
func (app *app) getConfigSettings(alias string) error {
	yamlFile, err := ioutil.ReadFile(app.filenameWithPath)
	if err != nil {
		return errors.Wrap(err, "failed to read config file for login use")
	}
	var config Config
	if err := yaml.Unmarshal(yamlFile, &config); err != nil {
		return errors.Wrap(err, "failed to unmarshal yaml file for login use")
	}

	aliasConfig, ok := config.aliasSearch(alias)
	if !ok {
		return errors.New("Could not find specified alias, check spelling or use the config verb to create an alias")
	}
	app.kubectlUser = aliasConfig.KubectlUser
	app.kubeloginServer = aliasConfig.BaseURL
	return nil
}

func (config *Config) aliasSearch(alias string) (*AliasConfig, bool) {
	for index, aliases := range config.Aliases {
		if alias == aliases.Alias {
			return config.Aliases[index], true
		}
	}
	return nil, false
}

func (config *Config) createConfig(onDiskFile string, aliasConfig AliasConfig) error {
	log.Print("Couldn't find config file in root directory. Creating config file...")
	_, e := os.Stat(onDiskFile) // Does config file exist?
	if os.IsNotExist(e) {       // Create file
		fh, err := os.Create(onDiskFile)
		if err != nil {
			return errors.Wrap(err, "failed to create file in root directory")
		}
		fh.Close()
	}

	log.Print("Config file created, setting config values...")
	config.Aliases = make([]*AliasConfig, 0)
	config.appendAlias(aliasConfig)
	if err := config.writeToFile(onDiskFile); err != nil {
		log.Fatal(err)
	}
	log.Print("File configured")
	return nil
}

func (config *Config) newAliasConfig(kubeloginrcAlias, loginServerURL, kubectlUser string) AliasConfig {
	newConfig := AliasConfig{
		BaseURL:     loginServerURL,
		Alias:       kubeloginrcAlias,
		KubectlUser: kubectlUser,
	}
	return newConfig
}

func (config *Config) appendAlias(aliasConfig AliasConfig) {
	config.Aliases = append(config.Aliases, &aliasConfig)
}

func (config *Config) writeToFile(onDiskFile string) error {
	marshaledYaml, err := yaml.Marshal(config)
	if err != nil {
		return errors.Wrap(err, "failed to marshal alias yaml")
	}
	if err := ioutil.WriteFile(onDiskFile, marshaledYaml, 0600); err != nil {
		return errors.Wrap(err, "failed to write to kubeloginrc file with the alias")
	}
	log.Printf(string(marshaledYaml))
	return nil
}

func (config *Config) updateAlias(aliasConfig *AliasConfig, loginServerURL *url.URL, onDiskFile string) error {
	aliasConfig.KubectlUser = userFlag
	aliasConfig.BaseURL = loginServerURL.String()
	if err := config.writeToFile(onDiskFile); err != nil {
		log.Fatal(err)
	}
	log.Print("Alias updated")
	return nil
}

func (app *app) configureFile(kubeloginrcAlias string, loginServerURL *url.URL, kubectlUser string) error {
	var config Config
	aliasConfig := config.newAliasConfig(kubeloginrcAlias, loginServerURL.String(), kubectlUser)
	yamlFile, err := ioutil.ReadFile(app.filenameWithPath)
	if err != nil {
		if err := config.createConfig(app.filenameWithPath, aliasConfig); err != nil {
			return err
		}
		return nil
	}
	if err := yaml.Unmarshal(yamlFile, &config); err != nil {
		return errors.Wrap(err, "failed to unmarshal yaml file")
	}
	foundAliasConfig, ok := config.aliasSearch(aliasFlag)
	if !ok {
		newConfig := config.newAliasConfig(kubeloginrcAlias, loginServerURL.String(), kubectlUser)
		config.appendAlias(newConfig)
		if err := config.writeToFile(app.filenameWithPath); err != nil {
			log.Fatal(err)
		}
		log.Print("New Alias configured")
		return nil
	}
	if err := config.updateAlias(foundAliasConfig, loginServerURL, app.filenameWithPath); err != nil {
		return err
	}
	return nil
}

func main() {
	var app app
	loginCommmand := flag.NewFlagSet("login", flag.ExitOnError)
	setFlags(loginCommmand, true)
	configCommand := flag.NewFlagSet("config", flag.ExitOnError)
	setFlags(configCommand, false)
	user, err := user.Current()
	if err != nil {
		log.Fatalf("Could not determine current user of this system. Err: %v", err)
	}
	app.filenameWithPath = path.Join(user.HomeDir, "/.kubeloginrc.yaml")
	if len(os.Args) < 3 {
		fmt.Println(usageMessage)
		os.Exit(1)
	}
	switch os.Args[1] {
	case "login":
		if !strings.HasPrefix(os.Args[2], "--") {
			//use alias to extract needed information
			if err := app.getConfigSettings(os.Args[2]); err != nil {
				log.Fatal(err)
			}
			generateURLAndListenForServerResponse(app)
		} else {
			loginCommmand.Parse(os.Args[2:])
			if loginCommmand.Parsed() {
				if kubeloginServerBaseURL == "" {
					log.Fatal("--server-url must be set!")
				}
				app.kubectlUser = userFlag
				app.kubeloginServer = kubeloginServerBaseURL
				generateURLAndListenForServerResponse(app)
			}
		}
	case "config":
		configCommand.Parse(os.Args[2:])
		if configCommand.Parsed() {
			if kubeloginServerBaseURL == "" {
				log.Fatal("--server-url must be set!")
			}
			verifiedServerURL, err := url.ParseRequestURI(kubeloginServerBaseURL)
			if err != nil {
				log.Fatalf("Invalid URL given: %v | Err: %v", kubeloginServerBaseURL, err)
			}

			if err := app.configureFile(aliasFlag, verifiedServerURL, userFlag); err != nil {
				log.Fatal(err)
			}
			os.Exit(0)
		}
	default:
		fmt.Println(usageMessage)
		os.Exit(1)
	}
}
<|endoftext|>