<commit_before>// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package replacement

import (
	"errors"
	"fmt"
	"strings"

	"sigs.k8s.io/kustomize/api/internal/utils"
	"sigs.k8s.io/kustomize/api/resource"
	"sigs.k8s.io/kustomize/api/types"
	"sigs.k8s.io/kustomize/kyaml/resid"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

type Filter struct {
	Replacements []types.Replacement `json:"replacements,omitempty" yaml:"replacements,omitempty"`
}

// Filter replaces values of targets with values from sources
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
	for i, r := range f.Replacements {
		if r.Source == nil || r.Targets == nil {
			return nil, fmt.Errorf("replacements must specify a source and at least one target")
		}
		value, err := getReplacement(nodes, &f.Replacements[i])
		if err != nil {
			return nil, err
		}
		nodes, err = applyReplacement(nodes, value, r.Targets)
		if err != nil {
			return nil, err
		}
	}
	return nodes, nil
}

func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, targets []*types.TargetSelector) ([]*yaml.RNode, error) {
	for _, t := range targets {
		if t.Select == nil {
			return nil, fmt.Errorf("target must specify resources to select")
		}
		if len(t.FieldPaths) == 0 {
			t.FieldPaths = []string{types.DefaultReplacementFieldPath}
		}
		for _, n := range nodes {
			ids, err := utils.MakeResIds(n)
			if err != nil {
				return nil, err
			}

			// filter targets by label and annotation selectors
			selectByAnnoAndLabel, err := selectByAnnoAndLabel(n, t)
			if err != nil {
				return nil, err
			}
			if !selectByAnnoAndLabel {
				continue
			}

			// filter targets by matching resource IDs
			for i, id := range ids {
				if id.IsSelectedBy(t.Select.ResId) && !rejectId(t.Reject, &ids[i]) {
					err := applyToNode(n, value, t)
					if err != nil {
						return nil, err
					}
					break
				}
			}
		}
	}
	return nodes, nil
}

func selectByAnnoAndLabel(n *yaml.RNode, t *types.TargetSelector) (bool, error) {
	if matchesSelect, err := matchesAnnoAndLabelSelector(n, t.Select); !matchesSelect || err != nil {
		return false, err
	}
	for _, reject := range t.Reject {
		if reject.AnnotationSelector == "" && reject.LabelSelector == "" {
			continue
		}
		if m, err := matchesAnnoAndLabelSelector(n, reject); m || err != nil {
			return false, err
		}
	}
	return true, nil
}

func matchesAnnoAndLabelSelector(n *yaml.RNode, selector *types.Selector) (bool, error) {
	r := resource.Resource{RNode: *n}
	annoMatch, err := r.MatchesAnnotationSelector(selector.AnnotationSelector)
	if err != nil {
		return false, err
	}
	labelMatch, err := r.MatchesLabelSelector(selector.LabelSelector)
	if err != nil {
		return false, err
	}
	return annoMatch && labelMatch, nil
}

func rejectId(rejects []*types.Selector, id *resid.ResId) bool {
	for _, r := range rejects {
		if !r.ResId.IsEmpty() && id.IsSelectedBy(r.ResId) {
			return true
		}
	}
	return false
}

func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.TargetSelector) error {
	for _, fp := range target.FieldPaths {
		fieldPath := utils.SmarterPathSplitter(fp, ".")
		var t *yaml.RNode
		var err error
		if target.Options != nil && target.Options.Create {
			// create option is not supported in a wildcard matching.
			// Because, PathMatcher is not supported create option.
			// So, if create option is set, we fallback to PathGetter.
			for _, f := range fieldPath {
				if f == "*" {
					return errors.New("cannot support create option in a multi-value target")
				}
			}
			t, err = node.Pipe(yaml.LookupCreate(value.YNode().Kind, fieldPath...))
		} else {
			t, err = node.Pipe(&yaml.PathMatcher{Path: fieldPath})
		}
		if err != nil {
			return err
		}
		if t != nil {
			if err = applyToOneNode(target.Options, t, value); err != nil {
				return err
			}
		}
	}
	return nil
}

func applyToOneNode(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNode) error {
	if len(t.YNode().Content) == 0 {
		if err := setTargetValue(options, t, value); err != nil {
			return err
		}
		return nil
	}

	for _, scalarNode := range t.YNode().Content {
		rn := yaml.NewRNode(scalarNode)
		if err := setTargetValue(options, rn, value); err != nil {
			return err
		}
	}

	return nil
}

func setTargetValue(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNode) error {
	value = value.Copy()
	if options != nil && options.Delimiter != "" {
		if t.YNode().Kind != yaml.ScalarNode {
			return fmt.Errorf("delimiter option can only be used with scalar nodes")
		}
		tv := strings.Split(t.YNode().Value, options.Delimiter)
		v := yaml.GetValue(value)
		// TODO: Add a way to remove an element
		switch {
		case options.Index < 0: // prefix
			tv = append([]string{v}, tv...)
		case options.Index >= len(tv): // suffix
			tv = append(tv, v)
		default: // replace an element
			tv[options.Index] = v
		}
		value.YNode().Value = strings.Join(tv, options.Delimiter)
	}

	if t.YNode().Kind == yaml.ScalarNode {
		// For scalar, only copy the value (leave any type intact to auto-convert int->string or string->int)
		t.YNode().Value = value.YNode().Value
	} else {
		t.SetYNode(value.YNode())
	}

	return nil
}

func getReplacement(nodes []*yaml.RNode, r *types.Replacement) (*yaml.RNode, error) {
	source, err := selectSourceNode(nodes, r.Source)
	if err != nil {
		return nil, err
	}

	if r.Source.FieldPath == "" {
		r.Source.FieldPath = types.DefaultReplacementFieldPath
	}
	fieldPath := utils.SmarterPathSplitter(r.Source.FieldPath, ".")

	rn, err := source.Pipe(yaml.Lookup(fieldPath...))
	if err != nil {
		return nil, err
	}
	if rn.IsNilOrEmpty() {
		return nil, fmt.Errorf("fieldPath `%s` is missing for replacement source %s", r.Source.FieldPath, r.Source.ResId)
	}

	return getRefinedValue(r.Source.Options, rn)
}

func getRefinedValue(options *types.FieldOptions, rn *yaml.RNode) (*yaml.RNode, error) {
	if options == nil || options.Delimiter == "" {
		return rn, nil
	}
	if rn.YNode().Kind != yaml.ScalarNode {
		return nil, fmt.Errorf("delimiter option can only be used with scalar nodes")
	}
	value := strings.Split(yaml.GetValue(rn), options.Delimiter)
	if options.Index >= len(value) || options.Index < 0 {
		return nil, fmt.Errorf("options.index %d is out of bounds for value %s", options.Index, yaml.GetValue(rn))
	}
	n := rn.Copy()
	n.YNode().Value = value[options.Index]
	return n, nil
}

// selectSourceNode finds the node that matches the selector, returning
// an error if multiple or none are found
func selectSourceNode(nodes []*yaml.RNode, selector *types.SourceSelector) (*yaml.RNode, error) {
	var matches []*yaml.RNode
	for _, n := range nodes {
		ids, err := utils.MakeResIds(n)
		if err != nil {
			return nil, err
		}
		for _, id := range ids {
			if id.IsSelectedBy(selector.ResId) {
				if len(matches) > 0 {
					return nil, fmt.Errorf(
						"multiple matches for selector %s", selector)
				}
				matches = append(matches, n)
				break
			}
		}
	}
	if len(matches) == 0 {
		return nil, fmt.Errorf("nothing selected by %s", selector)
	}
	return matches[0], nil
}
<commit_msg>fix lint error to nolint:goerr113<commit_after>// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package replacement

import (
	"errors"
	"fmt"
	"strings"

	"sigs.k8s.io/kustomize/api/internal/utils"
	"sigs.k8s.io/kustomize/api/resource"
	"sigs.k8s.io/kustomize/api/types"
	"sigs.k8s.io/kustomize/kyaml/resid"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

type Filter struct {
	Replacements []types.Replacement `json:"replacements,omitempty" yaml:"replacements,omitempty"`
}

// Filter replaces values of targets with values from sources
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
	for i, r := range f.Replacements {
		if r.Source == nil || r.Targets == nil {
			return nil, fmt.Errorf("replacements must specify a source and at least one target")
		}
		value, err := getReplacement(nodes, &f.Replacements[i])
		if err != nil {
			return nil, err
		}
		nodes, err = applyReplacement(nodes, value, r.Targets)
		if err != nil {
			return nil, err
		}
	}
	return nodes, nil
}

func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, targets []*types.TargetSelector) ([]*yaml.RNode, error) {
	for _, t := range targets {
		if t.Select == nil {
			return nil, fmt.Errorf("target must specify resources to select")
		}
		if len(t.FieldPaths) == 0 {
			t.FieldPaths = []string{types.DefaultReplacementFieldPath}
		}
		for _, n := range nodes {
			ids, err := utils.MakeResIds(n)
			if err != nil {
				return nil, err
			}

			// filter targets by label and annotation selectors
			selectByAnnoAndLabel, err := selectByAnnoAndLabel(n, t)
			if err != nil {
				return nil, err
			}
			if !selectByAnnoAndLabel {
				continue
			}

			// filter targets by matching resource IDs
			for i, id := range ids {
				if id.IsSelectedBy(t.Select.ResId) && !rejectId(t.Reject, &ids[i]) {
					err := applyToNode(n, value, t)
					if err != nil {
						return nil, err
					}
					break
				}
			}
		}
	}
	return nodes, nil
}

func selectByAnnoAndLabel(n *yaml.RNode, t *types.TargetSelector) (bool, error) {
	if matchesSelect, err := matchesAnnoAndLabelSelector(n, t.Select); !matchesSelect || err != nil {
		return false, err
	}
	for _, reject := range t.Reject {
		if reject.AnnotationSelector == "" && reject.LabelSelector == "" {
			continue
		}
		if m, err := matchesAnnoAndLabelSelector(n, reject); m || err != nil {
			return false, err
		}
	}
	return true, nil
}

func matchesAnnoAndLabelSelector(n *yaml.RNode, selector *types.Selector) (bool, error) {
	r := resource.Resource{RNode: *n}
	annoMatch, err := r.MatchesAnnotationSelector(selector.AnnotationSelector)
	if err != nil {
		return false, err
	}
	labelMatch, err := r.MatchesLabelSelector(selector.LabelSelector)
	if err != nil {
		return false, err
	}
	return annoMatch && labelMatch, nil
}

func rejectId(rejects []*types.Selector, id *resid.ResId) bool {
	for _, r := range rejects {
		if !r.ResId.IsEmpty() && id.IsSelectedBy(r.ResId) {
			return true
		}
	}
	return false
}

func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.TargetSelector) error {
	for _, fp := range target.FieldPaths {
		fieldPath := utils.SmarterPathSplitter(fp, ".")
		var t *yaml.RNode
		var err error
		if target.Options != nil && target.Options.Create {
			// create option is not supported in a wildcard matching.
			// Because, PathMatcher is not supported create option.
			// So, if create option is set, we fallback to PathGetter.
			for _, f := range fieldPath {
				if f == "*" {
					return errors.New("cannot support create option in a multi-value target") //nolint:goerr113
				}
			}
			t, err = node.Pipe(yaml.LookupCreate(value.YNode().Kind, fieldPath...))
		} else {
			t, err = node.Pipe(&yaml.PathMatcher{Path: fieldPath})
		}
		if err != nil {
			return err
		}
		if t != nil {
			if err = applyToOneNode(target.Options, t, value); err != nil {
				return err
			}
		}
	}
	return nil
}

func applyToOneNode(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNode) error {
	if len(t.YNode().Content) == 0 {
		if err := setTargetValue(options, t, value); err != nil {
			return err
		}
		return nil
	}

	for _, scalarNode := range t.YNode().Content {
		rn := yaml.NewRNode(scalarNode)
		if err := setTargetValue(options, rn, value); err != nil {
			return err
		}
	}

	return nil
}

func setTargetValue(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNode) error {
	value = value.Copy()
	if options != nil && options.Delimiter != "" {
		if t.YNode().Kind != yaml.ScalarNode {
			return fmt.Errorf("delimiter option can only be used with scalar nodes")
		}
		tv := strings.Split(t.YNode().Value, options.Delimiter)
		v := yaml.GetValue(value)
		// TODO: Add a way to remove an element
		switch {
		case options.Index < 0: // prefix
			tv = append([]string{v}, tv...)
		case options.Index >= len(tv): // suffix
			tv = append(tv, v)
		default: // replace an element
			tv[options.Index] = v
		}
		value.YNode().Value = strings.Join(tv, options.Delimiter)
	}

	if t.YNode().Kind == yaml.ScalarNode {
		// For scalar, only copy the value (leave any type intact to auto-convert int->string or string->int)
		t.YNode().Value = value.YNode().Value
	} else {
		t.SetYNode(value.YNode())
	}

	return nil
}

func getReplacement(nodes []*yaml.RNode, r *types.Replacement) (*yaml.RNode, error) {
	source, err := selectSourceNode(nodes, r.Source)
	if err != nil {
		return nil, err
	}

	if r.Source.FieldPath == "" {
		r.Source.FieldPath = types.DefaultReplacementFieldPath
	}
	fieldPath := utils.SmarterPathSplitter(r.Source.FieldPath, ".")

	rn, err := source.Pipe(yaml.Lookup(fieldPath...))
	if err != nil {
		return nil, err
	}
	if rn.IsNilOrEmpty() {
		return nil, fmt.Errorf("fieldPath `%s` is missing for replacement source %s", r.Source.FieldPath, r.Source.ResId)
	}

	return getRefinedValue(r.Source.Options, rn)
}

func getRefinedValue(options *types.FieldOptions, rn *yaml.RNode) (*yaml.RNode, error) {
	if options == nil || options.Delimiter == "" {
		return rn, nil
	}
	if rn.YNode().Kind != yaml.ScalarNode {
		return nil, fmt.Errorf("delimiter option can only be used with scalar nodes")
	}
	value := strings.Split(yaml.GetValue(rn), options.Delimiter)
	if options.Index >= len(value) || options.Index < 0 {
		return nil, fmt.Errorf("options.index %d is out of bounds for value %s", options.Index, yaml.GetValue(rn))
	}
	n := rn.Copy()
	n.YNode().Value = value[options.Index]
	return n, nil
}

// selectSourceNode finds the node that matches the selector, returning
// an error if multiple or none are found
func selectSourceNode(nodes []*yaml.RNode, selector *types.SourceSelector) (*yaml.RNode, error) {
	var matches []*yaml.RNode
	for _, n := range nodes {
		ids, err := utils.MakeResIds(n)
		if err != nil {
			return nil, err
		}
		for _, id := range ids {
			if id.IsSelectedBy(selector.ResId) {
				if len(matches) > 0 {
					return nil, fmt.Errorf(
						"multiple matches for selector %s", selector)
				}
				matches = append(matches, n)
				break
			}
		}
	}
	if len(matches) == 0 {
		return nil, fmt.Errorf("nothing selected by %s", selector)
	}
	return matches[0], nil
}
<|endoftext|>


<commit_before>package shoboi

import (
	"errors"
	"net/http"
	"strconv"
	"strings"

	"github.com/parnurzeal/gorequest"
)

// SearchResult ...
type SearchResult struct {
	Titles map[string]*AnimeSearchResult `json:"Titles"`
}

// AnimeSearchResult ...
type AnimeSearchResult struct {
	TID           string `json:"TID"`
	TitleJapanese string `json:"Title"`
	ShortTitle    string `json:"ShortTitle"`
	TitleHiragana string `json:"TitleYomi"`
	TitleEnglish  string `json:"TitleEN"`
	Cat           string `json:"Cat"`
	FirstCh       string `json:"FirstCh"`
	FirstYear     string `json:"FirstYear"`
	FirstMonth    string `json:"FirstMonth"`
	FirstEndYear  string `json:"FirstEndYear"`
	FirstEndMonth string `json:"FirstEndMonth"`
	TitleFlag     string `json:"TitleFlag"`
	Comment       string `json:"Comment"`
	Search        int    `json:"Search"`
	Programs      []struct {
		PID         string      `json:"PID"`
		TID         string      `json:"TID"`
		StTime      string      `json:"StTime"`
		EdTime      string      `json:"EdTime"`
		ChID        string      `json:"ChID"`
		StOffset    string      `json:"StOffset"`
		Count       interface{} `json:"Count"`
		ProgComment string      `json:"ProgComment"`
		SubTitle    string      `json:"SubTitle"`
		ChName      string      `json:"ChName"`
	} `json:"Programs"`
}

// SearchAnime ...
func SearchAnime(title string) (tid string, err error) {
	title = strings.ToLower(title)
	searchResult := &SearchResult{}
	resp, _, errs := gorequest.New().Get("http://cal.syoboi.jp/json?Req=TitleSearch&Search=" + title + "&Limit=15").EndStruct(searchResult)

	if len(errs) > 0 {
		return "", errs[0]
	}

	if resp.StatusCode != http.StatusOK {
		return "", errors.New("Invalid status code: " + strconv.Itoa(resp.StatusCode))
	}

	if searchResult == nil || searchResult.Titles == nil {
		return "", errors.New("Invalid data: Titles is nil")
	}

	for _, anime := range searchResult.Titles {
		if strings.ToLower(anime.TitleJapanese) == title || strings.ToLower(anime.TitleEnglish) == title {
			return anime.TID, nil
		}
	}

	return "", nil
}
<commit_msg>Improved API<commit_after>package shoboi

import (
	"errors"
	"net/http"
	"strconv"
	"strings"

	"github.com/parnurzeal/gorequest"
)

// SearchResult ...
type SearchResult struct {
	Titles map[string]*AnimeSearchResult `json:"Titles"`
}

// AnimeSearchResult ...
type AnimeSearchResult struct {
	TID           string `json:"TID"`
	TitleJapanese string `json:"Title"`
	ShortTitle    string `json:"ShortTitle"`
	TitleHiragana string `json:"TitleYomi"`
	TitleEnglish  string `json:"TitleEN"`
	Cat           string `json:"Cat"`
	FirstCh       string `json:"FirstCh"`
	FirstYear     string `json:"FirstYear"`
	FirstMonth    string `json:"FirstMonth"`
	FirstEndYear  string `json:"FirstEndYear"`
	FirstEndMonth string `json:"FirstEndMonth"`
	TitleFlag     string `json:"TitleFlag"`
	Comment       string `json:"Comment"`
	Search        int    `json:"Search"`
	Programs      []struct {
		PID         string      `json:"PID"`
		TID         string      `json:"TID"`
		StTime      string      `json:"StTime"`
		EdTime      string      `json:"EdTime"`
		ChID        string      `json:"ChID"`
		StOffset    string      `json:"StOffset"`
		Count       interface{} `json:"Count"`
		ProgComment string      `json:"ProgComment"`
		SubTitle    string      `json:"SubTitle"`
		ChName      string      `json:"ChName"`
	} `json:"Programs"`
}

// SearchAnime ...
func SearchAnime(title string) (anime *AnimeSearchResult, err error) {
	title = strings.ToLower(title)
	searchResult := &SearchResult{}
	resp, _, errs := gorequest.New().Get("http://cal.syoboi.jp/json?Req=TitleSearch&Search=" + title + "&Limit=15").EndStruct(searchResult)

	if len(errs) > 0 {
		return nil, errs[0]
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New("Invalid status code: " + strconv.Itoa(resp.StatusCode))
	}

	if searchResult == nil || searchResult.Titles == nil {
		return nil, nil
	}

	for _, anime := range searchResult.Titles {
		if strings.ToLower(anime.TitleJapanese) == title || strings.ToLower(anime.TitleEnglish) == title {
			return anime, nil
		}
	}

	return nil, nil
}
<|endoftext|>


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

package levenshtein

import (
	"encoding/binary"
	"fmt"
	"unicode"

	unicode_utf8 "unicode/utf8"

	"github.com/couchbase/vellum/utf8"
)

type dfa struct {
	states statesStack
}

type state struct {
	next  []int
	match bool
}

func (s *state) String() string {
	rv := "  |"
	for i := 0; i < 16; i++ {
		rv += fmt.Sprintf("% 5x", i)
	}
	rv += "\n"
	for i := 0; i < len(s.next); i++ {
		if i%16 == 0 {
			rv += fmt.Sprintf("%x |", i/16)
		}
		if s.next[i] != 0 {
			rv += fmt.Sprintf("% 5d", s.next[i])
		} else {
			rv += "    -"
		}
		if i%16 == 15 {
			rv += "\n"
		}
	}
	return rv
}

type dfaBuilder struct {
	dfa    *dfa
	lev    *dynamicLevenshtein
	cache  map[string]int
	keyBuf []byte

	sequences  utf8.Sequences
	rangeStack utf8.RangeStack
	startBytes []byte
	endBytes   []byte
	nexts      []int
}

func newDfaBuilder(lev *dynamicLevenshtein) *dfaBuilder {
	dfab := &dfaBuilder{
		dfa: &dfa{
			states: make([]state, 0, 16),
		},
		lev:        lev,
		cache:      make(map[string]int, 1024),
		startBytes: make([]byte, unicode_utf8.UTFMax),
		endBytes:   make([]byte, unicode_utf8.UTFMax),
	}
	_, dfab.nexts = dfab.newState(false, nil) // create state 0, invalid
	return dfab
}

func (b *dfaBuilder) build() (*dfa, error) {
	var stack intsStack
	stack = stack.Push(b.lev.start())
	seen := make(map[int]struct{})

	var levState []int
	stack, levState = stack.Pop()
	for levState != nil {
		dfaSi := b.cachedState(levState)
		mmToSi, mmMismatchState, err := b.addMismatchUtf8States(dfaSi, levState)
		if err != nil {
			return nil, err
		}
		if mmToSi != 0 {
			if _, ok := seen[mmToSi]; !ok {
				seen[mmToSi] = struct{}{}
				stack = stack.Push(mmMismatchState)
			}
		}

		i := 0
		for _, r := range b.lev.query {
			if uint(levState[i]) > b.lev.distance {
				i++
				continue
			}
			levNext := b.lev.accept(levState, &r)
			nextSi := b.cachedState(levNext)
			if nextSi != 0 {
				err = b.addUtf8Sequences(true, dfaSi, nextSi, r, r)
				if err != nil {
					return nil, err
				}
				if _, ok := seen[nextSi]; !ok {
					seen[nextSi] = struct{}{}
					stack = stack.Push(levNext)
				}
			}
			i++
		}

		if len(b.dfa.states) > StateLimit {
			return nil, ErrTooManyStates
		}

		stack, levState = stack.Pop()
	}

	return b.dfa, nil
}

func (b *dfaBuilder) cachedState(levState []int) int {
	rv, _ := b.cached(levState)
	return rv
}

func levStateKey(levState []int, buf []byte) []byte {
	if cap(buf) < 8*len(levState) {
		buf = make([]byte, 8*len(levState))
	} else {
		buf = buf[0 : 8*len(levState)]
	}
	for i, state := range levState {
		binary.LittleEndian.PutUint64(buf[i*8:], uint64(state))
	}
	return buf
}

func (b *dfaBuilder) cached(levState []int) (int, bool) {
	if !b.lev.canMatch(levState) {
		return 0, true
	}
	b.keyBuf = levStateKey(levState, b.keyBuf)
	v, ok := b.cache[string(b.keyBuf)]
	if ok {
		return v, true
	}
	match := b.lev.isMatch(levState)
	b.dfa.states = append(b.dfa.states, state{
		next:  make([]int, 256),
		match: match,
	})
	newV := len(b.dfa.states) - 1
	b.cache[string(b.keyBuf)] = newV
	return newV, false
}

func (b *dfaBuilder) addMismatchUtf8States(fromSi int, levState []int) (int, []int, error) {
	mmState := b.lev.accept(levState, nil)
	toSi, _ := b.cached(mmState)
	if toSi == 0 {
		return 0, nil, nil
	}
	err := b.addUtf8Sequences(false, fromSi, toSi, 0, unicode.MaxRune)
	if err != nil {
		return 0, nil, err
	}
	return toSi, mmState, nil
}

func (b *dfaBuilder) addUtf8Sequences(overwrite bool, fromSi, toSi int, fromChar, toChar rune) (
	err error) {
	b.sequences, b.rangeStack, err = utf8.NewSequencesPrealloc(fromChar, toChar,
		b.sequences, b.rangeStack, b.startBytes, b.endBytes)
	if err != nil {
		return err
	}
	for _, seq := range b.sequences {
		fsi := fromSi
		for _, utf8r := range seq[:len(seq)-1] {
			var tsi int
			tsi, b.nexts = b.newState(false, b.nexts)
			b.addUtf8Range(overwrite, fsi, tsi, utf8r)
			fsi = tsi
		}
		b.addUtf8Range(overwrite, fsi, toSi, seq[len(seq)-1])
	}
	return nil
}

func (b *dfaBuilder) addUtf8Range(overwrite bool, from, to int, rang utf8.Range) {
	fromNext := b.dfa.states[from].next
	if overwrite {
		for by := rang.Start; by <= rang.End; by++ {
			fromNext[by] = to
		}
	} else {
		for by := rang.Start; by <= rang.End; by++ {
			if fromNext[by] == 0 {
				fromNext[by] = to
			}
		}
	}
}

func (b *dfaBuilder) newState(match bool, prealloc []int) (int, []int) {
	if len(prealloc) < 256 {
		prealloc = make([]int, 16384)
	}
	next := prealloc[0:256]
	prealloc = prealloc[256:]

	b.dfa.states = append(b.dfa.states, state{
		next:  next,
		match: match,
	})

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

package levenshtein

import (
	"encoding/binary"
	"fmt"
	"unicode"

	unicode_utf8 "unicode/utf8"

	"github.com/couchbase/vellum/utf8"
)

type dfa struct {
	states statesStack
}

type state struct {
	next  []int
	match bool
}

func (s *state) String() string {
	rv := "  |"
	for i := 0; i < 16; i++ {
		rv += fmt.Sprintf("% 5x", i)
	}
	rv += "\n"
	for i := 0; i < len(s.next); i++ {
		if i%16 == 0 {
			rv += fmt.Sprintf("%x |", i/16)
		}
		if s.next[i] != 0 {
			rv += fmt.Sprintf("% 5d", s.next[i])
		} else {
			rv += "    -"
		}
		if i%16 == 15 {
			rv += "\n"
		}
	}
	return rv
}

type dfaBuilder struct {
	dfa    *dfa
	lev    *dynamicLevenshtein
	cache  map[string]int
	keyBuf []byte

	sequences  utf8.Sequences
	rangeStack utf8.RangeStack
	startBytes []byte
	endBytes   []byte
	nexts      []int
}

func newDfaBuilder(lev *dynamicLevenshtein) *dfaBuilder {
	dfab := &dfaBuilder{
		dfa: &dfa{
			states: make([]state, 0, 16),
		},
		lev:        lev,
		cache:      make(map[string]int, 1024),
		startBytes: make([]byte, unicode_utf8.UTFMax),
		endBytes:   make([]byte, unicode_utf8.UTFMax),
	}
	_, dfab.nexts = dfab.newState(false, nil) // create state 0, invalid
	return dfab
}

func (b *dfaBuilder) build() (*dfa, error) {
	var stack intsStack
	stack = stack.Push(b.lev.start())
	seen := make(map[int]struct{})

	var levState []int
	stack, levState = stack.Pop()
	for levState != nil {
		dfaSi := b.cachedState(levState)
		mmToSi, mmMismatchState, err := b.addMismatchUtf8States(dfaSi, levState)
		if err != nil {
			return nil, err
		}
		if mmToSi != 0 {
			if _, ok := seen[mmToSi]; !ok {
				seen[mmToSi] = struct{}{}
				stack = stack.Push(mmMismatchState)
			}
		}

		i := 0
		for _, r := range b.lev.query {
			if uint(levState[i]) > b.lev.distance {
				i++
				continue
			}
			levNext := b.lev.accept(levState, &r)
			nextSi := b.cachedState(levNext)
			if nextSi != 0 {
				err = b.addUtf8RuneRange(true, dfaSi, nextSi, r, r)
				if err != nil {
					return nil, err
				}
				if _, ok := seen[nextSi]; !ok {
					seen[nextSi] = struct{}{}
					stack = stack.Push(levNext)
				}
			}
			i++
		}

		if len(b.dfa.states) > StateLimit {
			return nil, ErrTooManyStates
		}

		stack, levState = stack.Pop()
	}

	return b.dfa, nil
}

func (b *dfaBuilder) cachedState(levState []int) int {
	rv, _ := b.cached(levState)
	return rv
}

func levStateKey(levState []int, buf []byte) []byte {
	if cap(buf) < 8*len(levState) {
		buf = make([]byte, 8*len(levState))
	} else {
		buf = buf[0 : 8*len(levState)]
	}
	for i, state := range levState {
		binary.LittleEndian.PutUint64(buf[i*8:], uint64(state))
	}
	return buf
}

func (b *dfaBuilder) cached(levState []int) (int, bool) {
	if !b.lev.canMatch(levState) {
		return 0, true
	}
	b.keyBuf = levStateKey(levState, b.keyBuf)
	v, ok := b.cache[string(b.keyBuf)]
	if ok {
		return v, true
	}
	match := b.lev.isMatch(levState)
	b.dfa.states = append(b.dfa.states, state{
		next:  make([]int, 256),
		match: match,
	})
	newV := len(b.dfa.states) - 1
	b.cache[string(b.keyBuf)] = newV
	return newV, false
}

func (b *dfaBuilder) addMismatchUtf8States(fromSi int, levState []int) (int, []int, error) {
	mmState := b.lev.accept(levState, nil)
	toSi, _ := b.cached(mmState)
	if toSi == 0 {
		return 0, nil, nil
	}
	err := b.addUtf8RuneRange(false, fromSi, toSi, 0, unicode.MaxRune)
	if err != nil {
		return 0, nil, err
	}
	return toSi, mmState, nil
}

func (b *dfaBuilder) addUtf8RuneRange(overwrite bool, fromSi, toSi int,
	fromChar, toChar rune) (
	err error) {
	b.sequences, b.rangeStack, err = utf8.NewSequencesPrealloc(fromChar, toChar,
		b.sequences, b.rangeStack, b.startBytes, b.endBytes)
	if err != nil {
		return err
	}

	b.addUtf8Sequences(overwrite, fromSi, toSi, b.sequences)

	return nil
}

func (b *dfaBuilder) addUtf8Sequences(overwrite bool, fromSi, toSi int,
	sequences utf8.Sequences) {
	for _, seq := range sequences {
		fsi := fromSi
		for _, utf8r := range seq[:len(seq)-1] {
			var tsi int
			tsi, b.nexts = b.newState(false, b.nexts)
			b.addUtf8Range(overwrite, fsi, tsi, utf8r)
			fsi = tsi
		}
		b.addUtf8Range(overwrite, fsi, toSi, seq[len(seq)-1])
	}
}

func (b *dfaBuilder) addUtf8Range(overwrite bool, from, to int, rang utf8.Range) {
	fromNext := b.dfa.states[from].next
	if overwrite {
		for by := rang.Start; by <= rang.End; by++ {
			fromNext[by] = to
		}
	} else {
		for by := rang.Start; by <= rang.End; by++ {
			if fromNext[by] == 0 {
				fromNext[by] = to
			}
		}
	}
}

func (b *dfaBuilder) newState(match bool, prealloc []int) (int, []int) {
	if len(prealloc) < 256 {
		prealloc = make([]int, 16384)
	}
	next := prealloc[0:256]
	prealloc = prealloc[256:]

	b.dfa.states = append(b.dfa.states, state{
		next:  next,
		match: match,
	})

	return len(b.dfa.states) - 1, prealloc
}
<|endoftext|>


<commit_before>// +build integ
// Copyright Istio 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 prometheus

import (
	"context"
	"fmt"
	"strconv"
	"testing"

	"golang.org/x/sync/errgroup"

	"istio.io/istio/pkg/config/protocol"
	"istio.io/istio/pkg/test/framework"
	"istio.io/istio/pkg/test/framework/components/echo"
	"istio.io/istio/pkg/test/framework/components/echo/echoboot"
	"istio.io/istio/pkg/test/framework/components/istio"
	"istio.io/istio/pkg/test/framework/components/istio/ingress"
	"istio.io/istio/pkg/test/framework/components/namespace"
	"istio.io/istio/pkg/test/framework/components/prometheus"
	"istio.io/istio/pkg/test/framework/components/telemetry"
	"istio.io/istio/pkg/test/framework/features"
	"istio.io/istio/pkg/test/framework/resource"
	"istio.io/istio/pkg/test/util/retry"
	util "istio.io/istio/tests/integration/telemetry"
)

var (
	client, server    echo.Instances
	nonInjectedServer echo.Instances
	ist               istio.Instance
	appNsInst         namespace.Instance
	promInst          prometheus.Instance
	ingr              []ingress.Instance
)

// GetIstioInstance gets Istio instance.
func GetIstioInstance() *istio.Instance {
	return &ist
}

// GetAppNamespace gets bookinfo instance.
func GetAppNamespace() namespace.Instance {
	return appNsInst
}

// GetPromInstance gets prometheus instance.
func GetPromInstance() prometheus.Instance {
	return promInst
}

// GetIstioInstance gets Istio instance.
func GetIngressInstance() []ingress.Instance {
	return ingr
}

func GetClientInstances() echo.Instances {
	return client
}

func GetServerInstances() echo.Instances {
	return server
}

// TestStatsFilter includes common test logic for stats and metadataexchange filters running
// with nullvm and wasm runtime.
func TestStatsFilter(t *testing.T, feature features.Feature) {
	framework.NewTest(t).
		Features(feature).
		Run(func(ctx framework.TestContext) {
			g, _ := errgroup.WithContext(context.Background())
			for _, cltInstance := range client {
				g.Go(func() error {
					err := retry.UntilSuccess(func() error {
						if err := SendTraffic(cltInstance); err != nil {
							return err
						}
						c := cltInstance.Config().Cluster
						sourceCluster := "Kubernetes"
						if len(ctx.Clusters()) > 1 {
							sourceCluster = c.Name()
						}
						sourceQuery, destinationQuery, appQuery := buildQuery(sourceCluster)
						// Query client side metrics
						if _, err := QueryPrometheus(t, c, sourceQuery, GetPromInstance()); err != nil {
							t.Logf("prometheus values for istio_requests_total for cluster %v: \n%s", c, util.PromDump(c, promInst, "istio_requests_total"))
							return err
						}
						// Query client side metrics for non-injected server
						outOfMeshServerQuery := buildOutOfMeshServerQuery(sourceCluster)
						if _, err := QueryPrometheus(t, c, outOfMeshServerQuery, GetPromInstance()); err != nil {
							t.Logf("prometheus values for istio_requests_total for cluster %v: \n%s", c, util.PromDump(c, promInst, "istio_requests_total"))
							return err
						}
						// Query server side metrics.
						if _, err := QueryPrometheus(t, c, destinationQuery, GetPromInstance()); err != nil {
							t.Logf("prometheus values for istio_requests_total for cluster %v: \n%s", c, util.PromDump(c, promInst, "istio_requests_total"))
							return err
						}
						// This query will continue to increase due to readiness probe; don't wait for it to converge
						if err := QueryFirstPrometheus(t, c, appQuery, GetPromInstance()); err != nil {
							t.Logf("prometheus values for istio_echo_http_requests_total for cluster %v: \n%s", c, util.PromDump(c, promInst, "istio_echo_http_requests_total"))
							return err
						}

						return nil
					}, retry.Delay(telemetry.RetryDelay), retry.Timeout(telemetry.RetryTimeout))
					if err != nil {
						return err
					}
					return nil
				})
			}
			if err := g.Wait(); err != nil {
				t.Fatalf("test failed: %v", err)
			}
		})
}

// TestStatsTCPFilter includes common test logic for stats and metadataexchange filters running
// with nullvm and wasm runtime for TCP.
func TestStatsTCPFilter(t *testing.T, feature features.Feature) {
	framework.NewTest(t).
		Features(feature).
		Run(func(ctx framework.TestContext) {
			g, _ := errgroup.WithContext(context.Background())
			for _, cltInstance := range client {
				g.Go(func() error {
					err := retry.UntilSuccess(func() error {
						if err := SendTCPTraffic(cltInstance); err != nil {
							return err
						}
						c := cltInstance.Config().Cluster
						sourceCluster := "Kubernetes"
						if len(ctx.Clusters()) > 1 {
							sourceCluster = c.Name()
						}
						destinationQuery := buildTCPQuery(sourceCluster)
						if _, err := QueryPrometheus(t, c, destinationQuery, GetPromInstance()); err != nil {
							t.Logf("prometheus values for istio_tcp_connections_opened_total: \n%s", util.PromDump(c, promInst, "istio_tcp_connections_opened_total"))
							return err
						}

						return nil
					}, retry.Delay(telemetry.RetryDelay), retry.Timeout(telemetry.RetryTimeout))
					if err != nil {
						return err
					}
					return nil
				})
			}
			if err := g.Wait(); err != nil {
				t.Fatalf("test failed: %v", err)
			}
		})
}

// TestSetup set up echo app for stats testing.
func TestSetup(ctx resource.Context) (err error) {
	appNsInst, err = namespace.New(ctx, namespace.Config{
		Prefix: "echo",
		Inject: true,
	})
	if err != nil {
		return
	}

	echos, err := echoboot.NewBuilder(ctx).
		WithClusters(ctx.Clusters()...).
		With(nil, echo.Config{
			Service:   "client",
			Namespace: appNsInst,
			Ports:     nil,
			Subsets:   []echo.SubsetConfig{{}},
		}).
		With(nil, echo.Config{
			Service:   "server",
			Namespace: appNsInst,
			Subsets:   []echo.SubsetConfig{{}},
			Ports: []echo.Port{
				{
					Name:         "http",
					Protocol:     protocol.HTTP,
					InstancePort: 8090,
				},
				{
					Name:     "tcp",
					Protocol: protocol.TCP,
					// We use a port > 1024 to not require root
					InstancePort: 9000,
					ServicePort:  9000,
				},
			},
		}).
		With(nil, echo.Config{
			Service:   "server-no-sidecar",
			Namespace: appNsInst,
			Subsets: []echo.SubsetConfig{
				{
					Annotations: map[echo.Annotation]*echo.AnnotationValue{
						echo.SidecarInject: {
							Value: strconv.FormatBool(false),
						},
					},
				},
			},
			Ports: []echo.Port{
				{
					Name:         "http",
					Protocol:     protocol.HTTP,
					InstancePort: 8090,
				},
				{
					Name:     "tcp",
					Protocol: protocol.TCP,
					// We use a port > 1024 to not require root
					InstancePort: 9000,
					ServicePort:  9000,
				},
			},
		}).Build()
	if err != nil {
		return err
	}
	for _, c := range ctx.Clusters() {
		ingr = append(ingr, ist.IngressFor(c))
	}
	client = echos.Match(echo.Service("client"))
	server = echos.Match(echo.Service("server"))
	nonInjectedServer = echos.Match(echo.Service("server-no-sidecar"))
	promInst, err = prometheus.New(ctx, prometheus.Config{})
	if err != nil {
		return
	}
	return nil
}

// SendTraffic makes a client call to the "server" service on the http port.
func SendTraffic(cltInstance echo.Instance) error {
	_, err := cltInstance.Call(echo.CallOptions{
		Target:    server[0],
		PortName:  "http",
		Count:     util.RequestCountMultipler * len(server),
		Validator: echo.ExpectOK(),
	})
	if err != nil {
		return err
	}
	_, err = cltInstance.Call(echo.CallOptions{
		Target:   nonInjectedServer[0],
		PortName: "http",
		Count:    util.RequestCountMultipler * len(nonInjectedServer),
	})
	if err != nil {
		return err
	}
	return nil
}

// SendTCPTraffic makes a client call to the "server" service on the tcp port.
func SendTCPTraffic(cltInstance echo.Instance) error {
	_, err := cltInstance.Call(echo.CallOptions{
		Target:   server[0],
		PortName: "tcp",
		Count:    util.RequestCountMultipler * len(server),
	})
	if err != nil {
		return err
	}
	return nil
}

// BuildQueryCommon is the shared function to construct prom query for istio_request_total metric.
func BuildQueryCommon(labels map[string]string, ns string) (sourceQuery, destinationQuery, appQuery string) {
	sourceQuery = `istio_requests_total{reporter="source",`
	destinationQuery = `istio_requests_total{reporter="destination",`

	for k, v := range labels {
		sourceQuery += fmt.Sprintf(`%s=%q,`, k, v)
		destinationQuery += fmt.Sprintf(`%s=%q,`, k, v)
	}
	sourceQuery += "}"
	destinationQuery += "}"
	appQuery += `istio_echo_http_requests_total{kubernetes_namespace="` + ns + `"}`
	return
}

func buildQuery(sourceCluster string) (sourceQuery, destinationQuery, appQuery string) {
	ns := GetAppNamespace()
	labels := map[string]string{
		"request_protocol":               "http",
		"response_code":                  "200",
		"destination_app":                "server",
		"destination_version":            "v1",
		"destination_service":            "server." + ns.Name() + ".svc.cluster.local",
		"destination_service_name":       "server",
		"destination_workload_namespace": ns.Name(),
		"destination_service_namespace":  ns.Name(),
		"source_app":                     "client",
		"source_version":                 "v1",
		"source_workload":                "client-v1",
		"source_workload_namespace":      ns.Name(),
		"source_cluster":                 sourceCluster,
	}

	return BuildQueryCommon(labels, ns.Name())
}

func buildOutOfMeshServerQuery(sourceCluster string) string {
	ns := GetAppNamespace()
	labels := map[string]string{
		"request_protocol": "http",
		"response_code":    "200",
		// For out of mesh server, client side metrics rely on endpoint resource metadata
		// to fill in workload labels. To limit size of endpoint resource, we only populate
		// workload name and namespace, canonical service name and version in endpoint metadata.
		// Thus destination_app and destination_version labels are unknown.
		"destination_app":                "unknown",
		"destination_version":            "unknown",
		"destination_service":            "server-no-sidecar." + ns.Name() + ".svc.cluster.local",
		"destination_service_name":       "server-no-sidecar",
		"destination_workload_namespace": ns.Name(),
		"destination_service_namespace":  ns.Name(),
		"source_app":                     "client",
		"source_version":                 "v1",
		"source_workload":                "client-v1",
		"source_workload_namespace":      ns.Name(),
		"source_cluster":                 sourceCluster,
	}

	q := `istio_requests_total{reporter="source",`

	for k, v := range labels {
		q += fmt.Sprintf(`%s=%q,`, k, v)
	}
	q += "}"
	return q
}

func buildTCPQuery(sourceCluster string) (destinationQuery string) {
	ns := GetAppNamespace()
	destinationQuery = `istio_tcp_connections_opened_total{reporter="destination",`
	labels := map[string]string{
		"request_protocol":               "tcp",
		"destination_service_name":       "server",
		"destination_canonical_revision": "v1",
		"destination_canonical_service":  "server",
		"destination_app":                "server",
		"destination_version":            "v1",
		"destination_workload_namespace": ns.Name(),
		"destination_service_namespace":  ns.Name(),
		"source_app":                     "client",
		"source_version":                 "v1",
		"source_workload":                "client-v1",
		"source_workload_namespace":      ns.Name(),
		"source_cluster":                 sourceCluster,
	}
	for k, v := range labels {
		destinationQuery += fmt.Sprintf(`%s=%q,`, k, v)
	}
	destinationQuery += "}"
	return
}
<commit_msg>fix stats filter test: shadow var for goroutine (#30788)<commit_after>// +build integ
// Copyright Istio 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 prometheus

import (
	"context"
	"fmt"
	"strconv"
	"testing"

	"golang.org/x/sync/errgroup"

	"istio.io/istio/pkg/config/protocol"
	"istio.io/istio/pkg/test/framework"
	"istio.io/istio/pkg/test/framework/components/echo"
	"istio.io/istio/pkg/test/framework/components/echo/echoboot"
	"istio.io/istio/pkg/test/framework/components/istio"
	"istio.io/istio/pkg/test/framework/components/istio/ingress"
	"istio.io/istio/pkg/test/framework/components/namespace"
	"istio.io/istio/pkg/test/framework/components/prometheus"
	"istio.io/istio/pkg/test/framework/components/telemetry"
	"istio.io/istio/pkg/test/framework/features"
	"istio.io/istio/pkg/test/framework/resource"
	"istio.io/istio/pkg/test/util/retry"
	util "istio.io/istio/tests/integration/telemetry"
)

var (
	client, server    echo.Instances
	nonInjectedServer echo.Instances
	ist               istio.Instance
	appNsInst         namespace.Instance
	promInst          prometheus.Instance
	ingr              []ingress.Instance
)

// GetIstioInstance gets Istio instance.
func GetIstioInstance() *istio.Instance {
	return &ist
}

// GetAppNamespace gets bookinfo instance.
func GetAppNamespace() namespace.Instance {
	return appNsInst
}

// GetPromInstance gets prometheus instance.
func GetPromInstance() prometheus.Instance {
	return promInst
}

// GetIstioInstance gets Istio instance.
func GetIngressInstance() []ingress.Instance {
	return ingr
}

func GetClientInstances() echo.Instances {
	return client
}

func GetServerInstances() echo.Instances {
	return server
}

// TestStatsFilter includes common test logic for stats and metadataexchange filters running
// with nullvm and wasm runtime.
func TestStatsFilter(t *testing.T, feature features.Feature) {
	framework.NewTest(t).
		Features(feature).
		Run(func(ctx framework.TestContext) {
			g, _ := errgroup.WithContext(context.Background())
			for _, cltInstance := range client {
				cltInstance := cltInstance
				g.Go(func() error {
					err := retry.UntilSuccess(func() error {
						if err := SendTraffic(cltInstance); err != nil {
							return err
						}
						c := cltInstance.Config().Cluster
						sourceCluster := "Kubernetes"
						if len(ctx.Clusters()) > 1 {
							sourceCluster = c.Name()
						}
						sourceQuery, destinationQuery, appQuery := buildQuery(sourceCluster)
						// Query client side metrics
						if _, err := QueryPrometheus(t, c, sourceQuery, GetPromInstance()); err != nil {
							t.Logf("prometheus values for istio_requests_total for cluster %v: \n%s", c, util.PromDump(c, promInst, "istio_requests_total"))
							return err
						}
						// Query client side metrics for non-injected server
						outOfMeshServerQuery := buildOutOfMeshServerQuery(sourceCluster)
						if _, err := QueryPrometheus(t, c, outOfMeshServerQuery, GetPromInstance()); err != nil {
							t.Logf("prometheus values for istio_requests_total for cluster %v: \n%s", c, util.PromDump(c, promInst, "istio_requests_total"))
							return err
						}
						// Query server side metrics.
						if _, err := QueryPrometheus(t, c, destinationQuery, GetPromInstance()); err != nil {
							t.Logf("prometheus values for istio_requests_total for cluster %v: \n%s", c, util.PromDump(c, promInst, "istio_requests_total"))
							return err
						}
						// This query will continue to increase due to readiness probe; don't wait for it to converge
						if err := QueryFirstPrometheus(t, c, appQuery, GetPromInstance()); err != nil {
							t.Logf("prometheus values for istio_echo_http_requests_total for cluster %v: \n%s", c, util.PromDump(c, promInst, "istio_echo_http_requests_total"))
							return err
						}

						return nil
					}, retry.Delay(telemetry.RetryDelay), retry.Timeout(telemetry.RetryTimeout))
					if err != nil {
						return err
					}
					return nil
				})
			}
			if err := g.Wait(); err != nil {
				t.Fatalf("test failed: %v", err)
			}
		})
}

// TestStatsTCPFilter includes common test logic for stats and metadataexchange filters running
// with nullvm and wasm runtime for TCP.
func TestStatsTCPFilter(t *testing.T, feature features.Feature) {
	framework.NewTest(t).
		Features(feature).
		Run(func(ctx framework.TestContext) {
			g, _ := errgroup.WithContext(context.Background())
			for _, cltInstance := range client {
				g.Go(func() error {
					err := retry.UntilSuccess(func() error {
						if err := SendTCPTraffic(cltInstance); err != nil {
							return err
						}
						c := cltInstance.Config().Cluster
						sourceCluster := "Kubernetes"
						if len(ctx.Clusters()) > 1 {
							sourceCluster = c.Name()
						}
						destinationQuery := buildTCPQuery(sourceCluster)
						if _, err := QueryPrometheus(t, c, destinationQuery, GetPromInstance()); err != nil {
							t.Logf("prometheus values for istio_tcp_connections_opened_total: \n%s", util.PromDump(c, promInst, "istio_tcp_connections_opened_total"))
							return err
						}

						return nil
					}, retry.Delay(telemetry.RetryDelay), retry.Timeout(telemetry.RetryTimeout))
					if err != nil {
						return err
					}
					return nil
				})
			}
			if err := g.Wait(); err != nil {
				t.Fatalf("test failed: %v", err)
			}
		})
}

// TestSetup set up echo app for stats testing.
func TestSetup(ctx resource.Context) (err error) {
	appNsInst, err = namespace.New(ctx, namespace.Config{
		Prefix: "echo",
		Inject: true,
	})
	if err != nil {
		return
	}

	echos, err := echoboot.NewBuilder(ctx).
		WithClusters(ctx.Clusters()...).
		With(nil, echo.Config{
			Service:   "client",
			Namespace: appNsInst,
			Ports:     nil,
			Subsets:   []echo.SubsetConfig{{}},
		}).
		With(nil, echo.Config{
			Service:   "server",
			Namespace: appNsInst,
			Subsets:   []echo.SubsetConfig{{}},
			Ports: []echo.Port{
				{
					Name:         "http",
					Protocol:     protocol.HTTP,
					InstancePort: 8090,
				},
				{
					Name:     "tcp",
					Protocol: protocol.TCP,
					// We use a port > 1024 to not require root
					InstancePort: 9000,
					ServicePort:  9000,
				},
			},
		}).
		With(nil, echo.Config{
			Service:   "server-no-sidecar",
			Namespace: appNsInst,
			Subsets: []echo.SubsetConfig{
				{
					Annotations: map[echo.Annotation]*echo.AnnotationValue{
						echo.SidecarInject: {
							Value: strconv.FormatBool(false),
						},
					},
				},
			},
			Ports: []echo.Port{
				{
					Name:         "http",
					Protocol:     protocol.HTTP,
					InstancePort: 8090,
				},
				{
					Name:     "tcp",
					Protocol: protocol.TCP,
					// We use a port > 1024 to not require root
					InstancePort: 9000,
					ServicePort:  9000,
				},
			},
		}).Build()
	if err != nil {
		return err
	}
	for _, c := range ctx.Clusters() {
		ingr = append(ingr, ist.IngressFor(c))
	}
	client = echos.Match(echo.Service("client"))
	server = echos.Match(echo.Service("server"))
	nonInjectedServer = echos.Match(echo.Service("server-no-sidecar"))
	promInst, err = prometheus.New(ctx, prometheus.Config{})
	if err != nil {
		return
	}
	return nil
}

// SendTraffic makes a client call to the "server" service on the http port.
func SendTraffic(cltInstance echo.Instance) error {
	_, err := cltInstance.Call(echo.CallOptions{
		Target:    server[0],
		PortName:  "http",
		Count:     util.RequestCountMultipler * len(server),
		Validator: echo.ExpectOK(),
	})
	if err != nil {
		return err
	}
	_, err = cltInstance.Call(echo.CallOptions{
		Target:   nonInjectedServer[0],
		PortName: "http",
		Count:    util.RequestCountMultipler * len(nonInjectedServer),
	})
	if err != nil {
		return err
	}
	return nil
}

// SendTCPTraffic makes a client call to the "server" service on the tcp port.
func SendTCPTraffic(cltInstance echo.Instance) error {
	_, err := cltInstance.Call(echo.CallOptions{
		Target:   server[0],
		PortName: "tcp",
		Count:    util.RequestCountMultipler * len(server),
	})
	if err != nil {
		return err
	}
	return nil
}

// BuildQueryCommon is the shared function to construct prom query for istio_request_total metric.
func BuildQueryCommon(labels map[string]string, ns string) (sourceQuery, destinationQuery, appQuery string) {
	sourceQuery = `istio_requests_total{reporter="source",`
	destinationQuery = `istio_requests_total{reporter="destination",`

	for k, v := range labels {
		sourceQuery += fmt.Sprintf(`%s=%q,`, k, v)
		destinationQuery += fmt.Sprintf(`%s=%q,`, k, v)
	}
	sourceQuery += "}"
	destinationQuery += "}"
	appQuery += `istio_echo_http_requests_total{kubernetes_namespace="` + ns + `"}`
	return
}

func buildQuery(sourceCluster string) (sourceQuery, destinationQuery, appQuery string) {
	ns := GetAppNamespace()
	labels := map[string]string{
		"request_protocol":               "http",
		"response_code":                  "200",
		"destination_app":                "server",
		"destination_version":            "v1",
		"destination_service":            "server." + ns.Name() + ".svc.cluster.local",
		"destination_service_name":       "server",
		"destination_workload_namespace": ns.Name(),
		"destination_service_namespace":  ns.Name(),
		"source_app":                     "client",
		"source_version":                 "v1",
		"source_workload":                "client-v1",
		"source_workload_namespace":      ns.Name(),
		"source_cluster":                 sourceCluster,
	}

	return BuildQueryCommon(labels, ns.Name())
}

func buildOutOfMeshServerQuery(sourceCluster string) string {
	ns := GetAppNamespace()
	labels := map[string]string{
		"request_protocol": "http",
		"response_code":    "200",
		// For out of mesh server, client side metrics rely on endpoint resource metadata
		// to fill in workload labels. To limit size of endpoint resource, we only populate
		// workload name and namespace, canonical service name and version in endpoint metadata.
		// Thus destination_app and destination_version labels are unknown.
		"destination_app":                "unknown",
		"destination_version":            "unknown",
		"destination_service":            "server-no-sidecar." + ns.Name() + ".svc.cluster.local",
		"destination_service_name":       "server-no-sidecar",
		"destination_workload_namespace": ns.Name(),
		"destination_service_namespace":  ns.Name(),
		"source_app":                     "client",
		"source_version":                 "v1",
		"source_workload":                "client-v1",
		"source_workload_namespace":      ns.Name(),
		"source_cluster":                 sourceCluster,
	}

	q := `istio_requests_total{reporter="source",`

	for k, v := range labels {
		q += fmt.Sprintf(`%s=%q,`, k, v)
	}
	q += "}"
	return q
}

func buildTCPQuery(sourceCluster string) (destinationQuery string) {
	ns := GetAppNamespace()
	destinationQuery = `istio_tcp_connections_opened_total{reporter="destination",`
	labels := map[string]string{
		"request_protocol":               "tcp",
		"destination_service_name":       "server",
		"destination_canonical_revision": "v1",
		"destination_canonical_service":  "server",
		"destination_app":                "server",
		"destination_version":            "v1",
		"destination_workload_namespace": ns.Name(),
		"destination_service_namespace":  ns.Name(),
		"source_app":                     "client",
		"source_version":                 "v1",
		"source_workload":                "client-v1",
		"source_workload_namespace":      ns.Name(),
		"source_cluster":                 sourceCluster,
	}
	for k, v := range labels {
		destinationQuery += fmt.Sprintf(`%s=%q,`, k, v)
	}
	destinationQuery += "}"
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package filters

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"reflect"
	"sync"
	"testing"

	"k8s.io/apiserver/pkg/authentication/user"
	"k8s.io/apiserver/pkg/authorization/authorizer"
	"k8s.io/apiserver/pkg/endpoints/request"
	authenticationapi "k8s.io/client-go/pkg/apis/authentication/v1"
)

type impersonateAuthorizer struct{}

func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
	user := a.GetUser()

	switch {
	case user.GetName() == "system:admin":
		return true, "", nil

	case user.GetName() == "tester":
		return false, "", fmt.Errorf("works on my machine")

	case user.GetName() == "deny-me":
		return false, "denied", nil
	}

	if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "wheel" && a.GetVerb() == "impersonate" && a.GetResource() == "users" {
		return true, "", nil
	}

	if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "sa-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" {
		return true, "", nil
	}

	if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "regular-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "users" {
		return true, "", nil
	}

	if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "group-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "groups" {
		return true, "", nil
	}

	if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-scopes" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" {
		return true, "", nil
	}

	if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-particular-scopes" &&
		a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" && a.GetName() == "scope-a" {
		return true, "", nil
	}

	if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-project" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "project" {
		return true, "", nil
	}

	return false, "deny by default", nil
}

func TestImpersonationFilter(t *testing.T) {
	testCases := []struct {
		name                    string
		user                    user.Info
		impersonationUser       string
		impersonationGroups     []string
		impersonationUserExtras map[string][]string
		expectedUser            user.Info
		expectedCode            int
	}{
		{
			name: "not-impersonating",
			user: &user.DefaultInfo{
				Name: "tester",
			},
			expectedUser: &user.DefaultInfo{
				Name: "tester",
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "impersonating-error",
			user: &user.DefaultInfo{
				Name: "tester",
			},
			impersonationUser: "anyone",
			expectedUser: &user.DefaultInfo{
				Name: "tester",
			},
			expectedCode: http.StatusForbidden,
		},
		{
			name: "impersonating-group-without-user",
			user: &user.DefaultInfo{
				Name: "tester",
			},
			impersonationGroups: []string{"some-group"},
			expectedUser: &user.DefaultInfo{
				Name: "tester",
			},
			expectedCode: http.StatusInternalServerError,
		},
		{
			name: "impersonating-extra-without-user",
			user: &user.DefaultInfo{
				Name: "tester",
			},
			impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}},
			expectedUser: &user.DefaultInfo{
				Name: "tester",
			},
			expectedCode: http.StatusInternalServerError,
		},
		{
			name: "disallowed-group",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel"},
			},
			impersonationUser:   "system:admin",
			impersonationGroups: []string{"some-group"},
			expectedUser: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel"},
			},
			expectedCode: http.StatusForbidden,
		},
		{
			name: "allowed-group",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel", "group-impersonater"},
			},
			impersonationUser:   "system:admin",
			impersonationGroups: []string{"some-group"},
			expectedUser: &user.DefaultInfo{
				Name:   "system:admin",
				Groups: []string{"some-group"},
				Extra:  map[string][]string{},
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "disallowed-userextra-1",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel"},
			},
			impersonationUser:       "system:admin",
			impersonationGroups:     []string{"some-group"},
			impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}},
			expectedUser: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel"},
			},
			expectedCode: http.StatusForbidden,
		},
		{
			name: "disallowed-userextra-2",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel", "extra-setter-project"},
			},
			impersonationUser:       "system:admin",
			impersonationGroups:     []string{"some-group"},
			impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}},
			expectedUser: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel", "extra-setter-project"},
			},
			expectedCode: http.StatusForbidden,
		},
		{
			name: "disallowed-userextra-3",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel", "extra-setter-particular-scopes"},
			},
			impersonationUser:       "system:admin",
			impersonationGroups:     []string{"some-group"},
			impersonationUserExtras: map[string][]string{"scopes": {"scope-a", "scope-b"}},
			expectedUser: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel", "extra-setter-particular-scopes"},
			},
			expectedCode: http.StatusForbidden,
		},
		{
			name: "allowed-userextras",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel", "extra-setter-scopes"},
			},
			impersonationUser:       "system:admin",
			impersonationUserExtras: map[string][]string{"scopes": {"scope-a", "scope-b"}},
			expectedUser: &user.DefaultInfo{
				Name:   "system:admin",
				Groups: []string{"system:authenticated"},
				Extra:  map[string][]string{"scopes": {"scope-a", "scope-b"}},
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "allowed-users-impersonation",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"regular-impersonater"},
			},
			impersonationUser: "tester",
			expectedUser: &user.DefaultInfo{
				Name:   "tester",
				Groups: []string{"system:authenticated"},
				Extra:  map[string][]string{},
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "disallowed-impersonating",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"sa-impersonater"},
			},
			impersonationUser: "tester",
			expectedUser: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"sa-impersonater"},
			},
			expectedCode: http.StatusForbidden,
		},
		{
			name: "allowed-sa-impersonating",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"sa-impersonater"},
				Extra:  map[string][]string{},
			},
			impersonationUser: "system:serviceaccount:foo:default",
			expectedUser: &user.DefaultInfo{
				Name:   "system:serviceaccount:foo:default",
				Groups: []string{"system:serviceaccounts", "system:serviceaccounts:foo", "system:authenticated"},
				Extra:  map[string][]string{},
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "anonymous-username-prevents-adding-authenticated-group",
			user: &user.DefaultInfo{
				Name: "system:admin",
			},
			impersonationUser: "system:anonymous",
			expectedUser: &user.DefaultInfo{
				Name:   "system:anonymous",
				Groups: []string{},
				Extra:  map[string][]string{},
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "unauthenticated-group-prevents-adding-authenticated-group",
			user: &user.DefaultInfo{
				Name: "system:admin",
			},
			impersonationUser:   "unknown",
			impersonationGroups: []string{"system:unauthenticated"},
			expectedUser: &user.DefaultInfo{
				Name:   "unknown",
				Groups: []string{"system:unauthenticated"},
				Extra:  map[string][]string{},
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "unauthenticated-group-prevents-double-adding-authenticated-group",
			user: &user.DefaultInfo{
				Name: "system:admin",
			},
			impersonationUser:   "unknown",
			impersonationGroups: []string{"system:authenticated"},
			expectedUser: &user.DefaultInfo{
				Name:   "unknown",
				Groups: []string{"system:authenticated"},
				Extra:  map[string][]string{},
			},
			expectedCode: http.StatusOK,
		},
	}

	requestContextMapper := request.NewRequestContextMapper()
	var ctx request.Context
	var actualUser user.Info
	var lock sync.Mutex

	doNothingHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		currentCtx, _ := requestContextMapper.Get(req)
		user, exists := request.UserFrom(currentCtx)
		if !exists {
			actualUser = nil
			return
		}

		actualUser = user
	})
	handler := func(delegate http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			defer func() {
				if r := recover(); r != nil {
					t.Errorf("Recovered %v", r)
				}
			}()
			lock.Lock()
			defer lock.Unlock()
			requestContextMapper.Update(req, ctx)
			currentCtx, _ := requestContextMapper.Get(req)

			user, exists := request.UserFrom(currentCtx)
			if !exists {
				actualUser = nil
				return
			} else {
				actualUser = user
			}

			delegate.ServeHTTP(w, req)
		})
	}(WithImpersonation(doNothingHandler, requestContextMapper, impersonateAuthorizer{}))
	handler = request.WithRequestContext(handler, requestContextMapper)

	server := httptest.NewServer(handler)
	defer server.Close()

	for _, tc := range testCases {
		func() {
			lock.Lock()
			defer lock.Unlock()
			ctx = request.WithUser(request.NewContext(), tc.user)
		}()

		req, err := http.NewRequest("GET", server.URL, nil)
		if err != nil {
			t.Errorf("%s: unexpected error: %v", tc.name, err)
			continue
		}
		req.Header.Add(authenticationapi.ImpersonateUserHeader, tc.impersonationUser)
		for _, group := range tc.impersonationGroups {
			req.Header.Add(authenticationapi.ImpersonateGroupHeader, group)
		}
		for extraKey, values := range tc.impersonationUserExtras {
			for _, value := range values {
				req.Header.Add(authenticationapi.ImpersonateUserExtraHeaderPrefix+extraKey, value)
			}
		}

		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Errorf("%s: unexpected error: %v", tc.name, err)
			continue
		}
		if resp.StatusCode != tc.expectedCode {
			t.Errorf("%s: expected %v, actual %v", tc.name, tc.expectedCode, resp.StatusCode)
			continue
		}

		if !reflect.DeepEqual(actualUser, tc.expectedUser) {
			t.Errorf("%s: expected %#v, actual %#v", tc.name, tc.expectedUser, actualUser)
			continue
		}
	}
}
<commit_msg>test header removal for impersonation<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package filters

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"reflect"
	"strings"
	"sync"
	"testing"

	"k8s.io/apiserver/pkg/authentication/user"
	"k8s.io/apiserver/pkg/authorization/authorizer"
	"k8s.io/apiserver/pkg/endpoints/request"
	authenticationapi "k8s.io/client-go/pkg/apis/authentication/v1"
)

type impersonateAuthorizer struct{}

func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (authorized bool, reason string, err error) {
	user := a.GetUser()

	switch {
	case user.GetName() == "system:admin":
		return true, "", nil

	case user.GetName() == "tester":
		return false, "", fmt.Errorf("works on my machine")

	case user.GetName() == "deny-me":
		return false, "denied", nil
	}

	if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "wheel" && a.GetVerb() == "impersonate" && a.GetResource() == "users" {
		return true, "", nil
	}

	if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "sa-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" {
		return true, "", nil
	}

	if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "regular-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "users" {
		return true, "", nil
	}

	if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "group-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "groups" {
		return true, "", nil
	}

	if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-scopes" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" {
		return true, "", nil
	}

	if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-particular-scopes" &&
		a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "scopes" && a.GetName() == "scope-a" {
		return true, "", nil
	}

	if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "extra-setter-project" && a.GetVerb() == "impersonate" && a.GetResource() == "userextras" && a.GetSubresource() == "project" {
		return true, "", nil
	}

	return false, "deny by default", nil
}

func TestImpersonationFilter(t *testing.T) {
	testCases := []struct {
		name                    string
		user                    user.Info
		impersonationUser       string
		impersonationGroups     []string
		impersonationUserExtras map[string][]string
		expectedUser            user.Info
		expectedCode            int
	}{
		{
			name: "not-impersonating",
			user: &user.DefaultInfo{
				Name: "tester",
			},
			expectedUser: &user.DefaultInfo{
				Name: "tester",
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "impersonating-error",
			user: &user.DefaultInfo{
				Name: "tester",
			},
			impersonationUser: "anyone",
			expectedUser: &user.DefaultInfo{
				Name: "tester",
			},
			expectedCode: http.StatusForbidden,
		},
		{
			name: "impersonating-group-without-user",
			user: &user.DefaultInfo{
				Name: "tester",
			},
			impersonationGroups: []string{"some-group"},
			expectedUser: &user.DefaultInfo{
				Name: "tester",
			},
			expectedCode: http.StatusInternalServerError,
		},
		{
			name: "impersonating-extra-without-user",
			user: &user.DefaultInfo{
				Name: "tester",
			},
			impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}},
			expectedUser: &user.DefaultInfo{
				Name: "tester",
			},
			expectedCode: http.StatusInternalServerError,
		},
		{
			name: "disallowed-group",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel"},
			},
			impersonationUser:   "system:admin",
			impersonationGroups: []string{"some-group"},
			expectedUser: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel"},
			},
			expectedCode: http.StatusForbidden,
		},
		{
			name: "allowed-group",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel", "group-impersonater"},
			},
			impersonationUser:   "system:admin",
			impersonationGroups: []string{"some-group"},
			expectedUser: &user.DefaultInfo{
				Name:   "system:admin",
				Groups: []string{"some-group"},
				Extra:  map[string][]string{},
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "disallowed-userextra-1",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel"},
			},
			impersonationUser:       "system:admin",
			impersonationGroups:     []string{"some-group"},
			impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}},
			expectedUser: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel"},
			},
			expectedCode: http.StatusForbidden,
		},
		{
			name: "disallowed-userextra-2",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel", "extra-setter-project"},
			},
			impersonationUser:       "system:admin",
			impersonationGroups:     []string{"some-group"},
			impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}},
			expectedUser: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel", "extra-setter-project"},
			},
			expectedCode: http.StatusForbidden,
		},
		{
			name: "disallowed-userextra-3",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel", "extra-setter-particular-scopes"},
			},
			impersonationUser:       "system:admin",
			impersonationGroups:     []string{"some-group"},
			impersonationUserExtras: map[string][]string{"scopes": {"scope-a", "scope-b"}},
			expectedUser: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel", "extra-setter-particular-scopes"},
			},
			expectedCode: http.StatusForbidden,
		},
		{
			name: "allowed-userextras",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"wheel", "extra-setter-scopes"},
			},
			impersonationUser:       "system:admin",
			impersonationUserExtras: map[string][]string{"scopes": {"scope-a", "scope-b"}},
			expectedUser: &user.DefaultInfo{
				Name:   "system:admin",
				Groups: []string{"system:authenticated"},
				Extra:  map[string][]string{"scopes": {"scope-a", "scope-b"}},
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "allowed-users-impersonation",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"regular-impersonater"},
			},
			impersonationUser: "tester",
			expectedUser: &user.DefaultInfo{
				Name:   "tester",
				Groups: []string{"system:authenticated"},
				Extra:  map[string][]string{},
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "disallowed-impersonating",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"sa-impersonater"},
			},
			impersonationUser: "tester",
			expectedUser: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"sa-impersonater"},
			},
			expectedCode: http.StatusForbidden,
		},
		{
			name: "allowed-sa-impersonating",
			user: &user.DefaultInfo{
				Name:   "dev",
				Groups: []string{"sa-impersonater"},
				Extra:  map[string][]string{},
			},
			impersonationUser: "system:serviceaccount:foo:default",
			expectedUser: &user.DefaultInfo{
				Name:   "system:serviceaccount:foo:default",
				Groups: []string{"system:serviceaccounts", "system:serviceaccounts:foo", "system:authenticated"},
				Extra:  map[string][]string{},
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "anonymous-username-prevents-adding-authenticated-group",
			user: &user.DefaultInfo{
				Name: "system:admin",
			},
			impersonationUser: "system:anonymous",
			expectedUser: &user.DefaultInfo{
				Name:   "system:anonymous",
				Groups: []string{},
				Extra:  map[string][]string{},
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "unauthenticated-group-prevents-adding-authenticated-group",
			user: &user.DefaultInfo{
				Name: "system:admin",
			},
			impersonationUser:   "unknown",
			impersonationGroups: []string{"system:unauthenticated"},
			expectedUser: &user.DefaultInfo{
				Name:   "unknown",
				Groups: []string{"system:unauthenticated"},
				Extra:  map[string][]string{},
			},
			expectedCode: http.StatusOK,
		},
		{
			name: "unauthenticated-group-prevents-double-adding-authenticated-group",
			user: &user.DefaultInfo{
				Name: "system:admin",
			},
			impersonationUser:   "unknown",
			impersonationGroups: []string{"system:authenticated"},
			expectedUser: &user.DefaultInfo{
				Name:   "unknown",
				Groups: []string{"system:authenticated"},
				Extra:  map[string][]string{},
			},
			expectedCode: http.StatusOK,
		},
	}

	requestContextMapper := request.NewRequestContextMapper()
	var ctx request.Context
	var actualUser user.Info
	var lock sync.Mutex

	doNothingHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		currentCtx, _ := requestContextMapper.Get(req)
		user, exists := request.UserFrom(currentCtx)
		if !exists {
			actualUser = nil
			return
		}

		actualUser = user

		if _, ok := req.Header[authenticationapi.ImpersonateUserHeader]; ok {
			t.Fatal("user header still present")
		}
		if _, ok := req.Header[authenticationapi.ImpersonateGroupHeader]; ok {
			t.Fatal("group header still present")
		}
		for key := range req.Header {
			if strings.HasPrefix(key, authenticationapi.ImpersonateUserExtraHeaderPrefix) {
				t.Fatalf("extra header still present: %v", key)
			}
		}

	})
	handler := func(delegate http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			defer func() {
				if r := recover(); r != nil {
					t.Errorf("Recovered %v", r)
				}
			}()
			lock.Lock()
			defer lock.Unlock()
			requestContextMapper.Update(req, ctx)
			currentCtx, _ := requestContextMapper.Get(req)

			user, exists := request.UserFrom(currentCtx)
			if !exists {
				actualUser = nil
				return
			} else {
				actualUser = user
			}

			delegate.ServeHTTP(w, req)
		})
	}(WithImpersonation(doNothingHandler, requestContextMapper, impersonateAuthorizer{}))
	handler = request.WithRequestContext(handler, requestContextMapper)

	server := httptest.NewServer(handler)
	defer server.Close()

	for _, tc := range testCases {
		func() {
			lock.Lock()
			defer lock.Unlock()
			ctx = request.WithUser(request.NewContext(), tc.user)
		}()

		req, err := http.NewRequest("GET", server.URL, nil)
		if err != nil {
			t.Errorf("%s: unexpected error: %v", tc.name, err)
			continue
		}
		if len(tc.impersonationUser) > 0 {
			req.Header.Add(authenticationapi.ImpersonateUserHeader, tc.impersonationUser)
		}
		for _, group := range tc.impersonationGroups {
			req.Header.Add(authenticationapi.ImpersonateGroupHeader, group)
		}
		for extraKey, values := range tc.impersonationUserExtras {
			for _, value := range values {
				req.Header.Add(authenticationapi.ImpersonateUserExtraHeaderPrefix+extraKey, value)
			}
		}

		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Errorf("%s: unexpected error: %v", tc.name, err)
			continue
		}
		if resp.StatusCode != tc.expectedCode {
			t.Errorf("%s: expected %v, actual %v", tc.name, tc.expectedCode, resp.StatusCode)
			continue
		}

		if !reflect.DeepEqual(actualUser, tc.expectedUser) {
			t.Errorf("%s: expected %#v, actual %#v", tc.name, tc.expectedUser, actualUser)
			continue
		}
	}
}
<|endoftext|>


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

package main

import (
	"io/ioutil"
	"net/http"
)

type client struct {
	url string
}

func NewClient(url string) *client {
	c := new(client)
	c.url = url
	return c
}

func (c *client) GetHttpRes() ([]byte, error) {
	return getHttpResponse(c.url)
}

func getHttpResponse(url string) ([]byte, error) {
	client := &http.Client{}
	request, err := http.NewRequest("POST", url, nil)
	response, err := client.Do(request)
	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(response.Body)
	defer response.Body.Close()
	if err != nil {
		return nil, err
	}
	return body, nil
}<commit_msg>Delete Client.go<commit_after><|endoftext|>


<commit_before><commit_msg>Spew debugging<commit_after><|endoftext|>


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

package walk

import (
	"bytes"
	"strings"
)

import (
	. "github.com/lxn/go-winapi"
)

const lineErrorPresenterWindowClass = `\o/ Walk_LineErrorPresenter_Class \o/`

func init() {
	MustRegisterWindowClass(lineErrorPresenterWindowClass)

	lineErrorPresenterBackground, _ = NewSolidColorBrush(RGB(255, 128, 128))
}

var lineErrorPresenterBackground Brush

type LineErrorPresenter struct {
	WidgetBase
	composite    *Composite
	label        *Label
	curWidget    Widget
	widget2error map[Widget]error
}

func NewLineErrorPresenter(parent Container) (*LineErrorPresenter, error) {
	lep := &LineErrorPresenter{widget2error: make(map[Widget]error)}

	if err := InitChildWidget(
		lep,
		parent,
		lineErrorPresenterWindowClass,
		WS_VISIBLE,
		WS_EX_CONTROLPARENT); err != nil {
		return nil, err
	}

	succeeded := false
	defer func() {
		if !succeeded {
			lep.Dispose()
		}
	}()

	var err error

	if lep.composite, err = newCompositeWithStyle(lep, 0); err != nil {
		return nil, err
	}

	l := NewGridLayout()
	l.SetMargins(Margins{2, 2, 2, 2})

	if err = lep.composite.SetLayout(l); err != nil {
		return nil, err
	}

	if lep.label, err = NewLabel(lep.composite); err != nil {
		return nil, err
	}

	l.SetRange(lep.label, Rectangle{0, 0, 1, 1})

	focusCurWidget := func(x, y int, button MouseButton) {
		widget := lep.curWidget

		if button == LeftButton && widget != nil {
			widget.SetFocus()

			if textSel, ok := widget.(textSelectable); ok {
				textSel.SetTextSelection(0, -1)
			}
		}
	}

	lep.MouseDown().Attach(focusCurWidget)
	lep.composite.MouseDown().Attach(focusCurWidget)
	lep.label.MouseDown().Attach(focusCurWidget)

	succeeded = true

	return lep, nil
}

func (*LineErrorPresenter) LayoutFlags() LayoutFlags {
	return GrowableHorz | GreedyHorz
}

func (lep *LineErrorPresenter) MinSizeHint() Size {
	if lep.label == nil {
		return Size{}
	}

	text := lep.label.Text()
	if text == "" {
		text = "gM"
	}

	s := lep.label.calculateTextSizeImpl(text)

	return Size{s.Width + 8, s.Height + 8}
}

func (lep *LineErrorPresenter) SizeHint() Size {
	return lep.MinSizeHint()
}

func (lep *LineErrorPresenter) PresentError(err error, widget Widget) {
	if err == nil {
		delete(lep.widget2error, widget)
	} else {
		lep.widget2error[widget] = err
	}

	var found bool
	walkDescendants(rootWidget(widget), func(w Widget) bool {
		if found {
			return false
		}

		if e, ok := lep.widget2error[w]; ok {
			err, widget, found = e, w, true
		}

		return !found
	})

	if err != nil {
		lep.curWidget = widget
	} else {
		lep.curWidget = nil
	}

	var msg string
	var background Brush

	if err != nil {
		background = lineErrorPresenterBackground

		var labelText string
		if widget != nil {
			parent := widget.Parent()
			if parent != nil {
				children := parent.Children()

				i := children.Index(widget)
				if i > 0 {
					prev := children.At(i - 1)

					if label, ok := prev.(*Label); ok {
						labelText = label.Text()
					}
				}
			}
		}

		buf := new(bytes.Buffer)
		buf.WriteString(labelText)
		if !strings.HasSuffix(labelText, ":") {
			buf.WriteString(":")
		}
		if labelText != "" {
			buf.WriteString(" ")
		}
		buf.WriteString(err.Error())

		msg = buf.String()
	}

	lep.SetBackground(background)
	lep.label.SetText(msg)

	if root := rootWidget(lep); root != nil && root.Handle() != lep.hWnd {
		root.SetBounds(root.Bounds())
	}
}

func (lep *LineErrorPresenter) WndProc(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr {
	switch msg {
	case WM_SIZE, WM_SIZING:
		if lep.composite != nil {
			b := lep.ClientBounds()
			lep.composite.SetBounds(Rectangle{b.X + 2, b.Y + 2, b.Width - 4, b.Height - 4})
		}
	}

	return lep.WidgetBase.WndProc(hwnd, msg, wParam, lParam)
}
<commit_msg>LineErrorPresenter: Don't append ":" to empty label text<commit_after>// Copyright 2012 The Walk Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package walk

import (
	"bytes"
	"strings"
)

import (
	. "github.com/lxn/go-winapi"
)

const lineErrorPresenterWindowClass = `\o/ Walk_LineErrorPresenter_Class \o/`

func init() {
	MustRegisterWindowClass(lineErrorPresenterWindowClass)

	lineErrorPresenterBackground, _ = NewSolidColorBrush(RGB(255, 128, 128))
}

var lineErrorPresenterBackground Brush

type LineErrorPresenter struct {
	WidgetBase
	composite    *Composite
	label        *Label
	curWidget    Widget
	widget2error map[Widget]error
}

func NewLineErrorPresenter(parent Container) (*LineErrorPresenter, error) {
	lep := &LineErrorPresenter{widget2error: make(map[Widget]error)}

	if err := InitChildWidget(
		lep,
		parent,
		lineErrorPresenterWindowClass,
		WS_VISIBLE,
		WS_EX_CONTROLPARENT); err != nil {
		return nil, err
	}

	succeeded := false
	defer func() {
		if !succeeded {
			lep.Dispose()
		}
	}()

	var err error

	if lep.composite, err = newCompositeWithStyle(lep, 0); err != nil {
		return nil, err
	}

	l := NewGridLayout()
	l.SetMargins(Margins{2, 2, 2, 2})

	if err = lep.composite.SetLayout(l); err != nil {
		return nil, err
	}

	if lep.label, err = NewLabel(lep.composite); err != nil {
		return nil, err
	}

	l.SetRange(lep.label, Rectangle{0, 0, 1, 1})

	focusCurWidget := func(x, y int, button MouseButton) {
		widget := lep.curWidget

		if button == LeftButton && widget != nil {
			widget.SetFocus()

			if textSel, ok := widget.(textSelectable); ok {
				textSel.SetTextSelection(0, -1)
			}
		}
	}

	lep.MouseDown().Attach(focusCurWidget)
	lep.composite.MouseDown().Attach(focusCurWidget)
	lep.label.MouseDown().Attach(focusCurWidget)

	succeeded = true

	return lep, nil
}

func (*LineErrorPresenter) LayoutFlags() LayoutFlags {
	return GrowableHorz | GreedyHorz
}

func (lep *LineErrorPresenter) MinSizeHint() Size {
	if lep.label == nil {
		return Size{}
	}

	text := lep.label.Text()
	if text == "" {
		text = "gM"
	}

	s := lep.label.calculateTextSizeImpl(text)

	return Size{s.Width + 8, s.Height + 8}
}

func (lep *LineErrorPresenter) SizeHint() Size {
	return lep.MinSizeHint()
}

func (lep *LineErrorPresenter) PresentError(err error, widget Widget) {
	if err == nil {
		delete(lep.widget2error, widget)
	} else {
		lep.widget2error[widget] = err
	}

	var found bool
	walkDescendants(rootWidget(widget), func(w Widget) bool {
		if found {
			return false
		}

		if e, ok := lep.widget2error[w]; ok {
			err, widget, found = e, w, true
		}

		return !found
	})

	if err != nil {
		lep.curWidget = widget
	} else {
		lep.curWidget = nil
	}

	var msg string
	var background Brush

	if err != nil {
		background = lineErrorPresenterBackground

		var labelText string
		if widget != nil {
			parent := widget.Parent()
			if parent != nil {
				children := parent.Children()

				i := children.Index(widget)
				if i > 0 {
					prev := children.At(i - 1)

					if label, ok := prev.(*Label); ok {
						labelText = label.Text()
					}
				}
			}
		}

		buf := new(bytes.Buffer)
		buf.WriteString(labelText)
		if labelText != "" && !strings.HasSuffix(labelText, ":") {
			buf.WriteString(":")
		}
		if labelText != "" {
			buf.WriteString(" ")
		}
		buf.WriteString(err.Error())

		msg = buf.String()
	}

	lep.SetBackground(background)
	lep.label.SetText(msg)

	if root := rootWidget(lep); root != nil && root.Handle() != lep.hWnd {
		root.SetBounds(root.Bounds())
	}
}

func (lep *LineErrorPresenter) WndProc(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr {
	switch msg {
	case WM_SIZE, WM_SIZING:
		if lep.composite != nil {
			b := lep.ClientBounds()
			lep.composite.SetBounds(Rectangle{b.X + 2, b.Y + 2, b.Width - 4, b.Height - 4})
		}
	}

	return lep.WidgetBase.WndProc(hwnd, msg, wParam, lParam)
}
<|endoftext|>


<commit_before>/*

Below you will find a self-contained Go program which uses the LISTEN / NOTIFY
mechanism to avoid polling the database while waiting for more work to arrive.

    //
    // You can see the program in action by defining a function similar to
    // the following:
    //
    // CREATE OR REPLACE FUNCTION public.get_work()
    //   RETURNS bigint
    //   LANGUAGE sql
    //   AS $$
    //     SELECT CASE WHEN random() >= 0.2 THEN int8 '1' END
    //   $$
    // ;

    package main

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

        "github.com/lib/pq"
    )

    func doWork(db *sql.DB, work int64) {
        // work here
    }

    func getWork(db *sql.DB) {
        for {
            // get work from the database here
            var work sql.NullInt64
            err := db.QueryRow("SELECT get_work()").Scan(&work)
            if err != nil {
                fmt.Println("call to get_work() failed: ", err)
                time.Sleep(10 * time.Second)
                continue
            }
            if !work.Valid {
                // no more work to do
                fmt.Println("ran out of work")
                return
            }

            fmt.Println("starting work on ", work.Int64)
            go doWork(db, work.Int64)
        }
    }

    func waitForNotification(l *pq.Listener) {
        for {
            select {
                case <-l.Notify:
                    fmt.Println("received notification, new work available")
                    return
                case <-time.After(90 * time.Second):
                    go func() {
                        l.Ping()
                    }()
                    // Check if there's more work available, just in case it takes
                    // a while for the Listener to notice connection loss and
                    // reconnect.
                    fmt.Println("received no work for 90 seconds, checking for new work")
                    return
            }
        }
    }

    func main() {
        var conninfo string = ""

        db, err := sql.Open("postgres", conninfo)
        if err != nil {
            panic(err)
        }

        reportProblem := func(ev pq.ListenerEventType, err error) {
            if err != nil {
                fmt.Println(err.Error())
            }
        }

        listener := pq.NewListener(conninfo, 10 * time.Second, time.Minute, reportProblem)
        err = listener.Listen("getwork")
        if err != nil {
            panic(err)
        }

        fmt.Println("entering main loop")
        for {
            // process all available work before waiting for notifications
            getWork(db)
            waitForNotification(listener)
        }
    }

*/
package listen_example
<commit_msg>Remove unneeded for loop from listener example.<commit_after>/*

Below you will find a self-contained Go program which uses the LISTEN / NOTIFY
mechanism to avoid polling the database while waiting for more work to arrive.

    //
    // You can see the program in action by defining a function similar to
    // the following:
    //
    // CREATE OR REPLACE FUNCTION public.get_work()
    //   RETURNS bigint
    //   LANGUAGE sql
    //   AS $$
    //     SELECT CASE WHEN random() >= 0.2 THEN int8 '1' END
    //   $$
    // ;

    package main

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

        "github.com/lib/pq"
    )

    func doWork(db *sql.DB, work int64) {
        // work here
    }

    func getWork(db *sql.DB) {
        for {
            // get work from the database here
            var work sql.NullInt64
            err := db.QueryRow("SELECT get_work()").Scan(&work)
            if err != nil {
                fmt.Println("call to get_work() failed: ", err)
                time.Sleep(10 * time.Second)
                continue
            }
            if !work.Valid {
                // no more work to do
                fmt.Println("ran out of work")
                return
            }

            fmt.Println("starting work on ", work.Int64)
            go doWork(db, work.Int64)
        }
    }

    func waitForNotification(l *pq.Listener) {
        select {
            case <-l.Notify:
                fmt.Println("received notification, new work available")
            case <-time.After(90 * time.Second):
                go l.Ping()
                // Check if there's more work available, just in case it takes
                // a while for the Listener to notice connection loss and
                // reconnect.
                fmt.Println("received no work for 90 seconds, checking for new work")
        }
    }

    func main() {
        var conninfo string = ""

        db, err := sql.Open("postgres", conninfo)
        if err != nil {
            panic(err)
        }

        reportProblem := func(ev pq.ListenerEventType, err error) {
            if err != nil {
                fmt.Println(err.Error())
            }
        }

        listener := pq.NewListener(conninfo, 10 * time.Second, time.Minute, reportProblem)
        err = listener.Listen("getwork")
        if err != nil {
            panic(err)
        }

        fmt.Println("entering main loop")
        for {
            // process all available work before waiting for notifications
            getWork(db)
            waitForNotification(listener)
        }
    }

*/
package listen_example
<|endoftext|>