<commit_before>/*
Copyright 2019 - The TXTDirect 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 txtdirect

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

// Path contains the data that are needed to redirect path requests
type Path struct {
	rw   http.ResponseWriter
	req  *http.Request
	c    Config
	path string
	rec  record
}

// RegexRecords connects each zone address to a RegexRecord
// which contains raw TXT record and the re= field
type RegexRecords map[string]RegexRecord

// RegexRecord holds the TXT record and re= field of a predefined regex record
type RegexRecord struct {
	TXT        string
	Regex      string
	Submatches []string
}

// PathRegex is the default regex to parse request's path
// It can be replaced using the re= field in the records
var PathRegex = regexp.MustCompile("\\/([A-Za-z0-9-._~!$'()*+,;=:@]+)")

// FromRegex parses the from= field
var FromRegex = regexp.MustCompile("\\/\\$(\\d+)")

// GroupRegex parses the re= field to find the regex groups
var GroupRegex = regexp.MustCompile("P<[a-zA-Z]+[a-zA-Z0-9]*>")

// GroupOrderRegex finds the order of regex groups inside re=
var GroupOrderRegex = regexp.MustCompile("P<([a-zA-Z]+[a-zA-Z0-9]*)>")

// NewPath returns an instance of Path struct using the given data
func NewPath(w http.ResponseWriter, r *http.Request, path string, rec record, c Config) *Path {
	return &Path{
		rw:   w,
		req:  r,
		path: path,
		rec:  rec,
		c:    c,
	}
}

// Redirect finds and returns the final record
func (p *Path) Redirect() *record {
	zone, from, pathSlice, err := zoneFromPath(p.req, p.rec)
	rec, err := getFinalRecord(zone, from, p.c, p.rw, p.req, pathSlice)
	*p.req = *rec.addToContext(p.req)
	if err != nil {
		log.Print("Fallback is triggered because an error has occurred: ", err)
		fallback(p.rw, p.req, "to", p.rec.Code, p.c)
		return nil
	}

	if rec.Type == "path" {
		p.rec = rec
		return p.Redirect()
	}

	return &rec
}

// RedirectRoot redirects the request to record's root= field
// if the path is empty or "/". If the root= field is empty too
// fallback will be triggered.
func (p *Path) RedirectRoot() error {
	if p.rec.Root == "" {
		fallback(p.rw, p.req, "to", p.rec.Code, p.c)
		return nil
	}
	log.Printf("[txtdirect]: %s > %s", p.req.Host+p.req.URL.Path, p.rec.Root)
	if p.rec.Code == http.StatusMovedPermanently {
		p.rw.Header().Add("Cache-Control", fmt.Sprintf("max-age=%d", status301CacheAge))
	}
	p.rw.Header().Add("Status-Code", strconv.Itoa(p.rec.Code))
	http.Redirect(p.rw, p.req, p.rec.Root, p.rec.Code)
	if p.c.Prometheus.Enable {
		RequestsByStatus.WithLabelValues(p.req.Host, strconv.Itoa(p.rec.Code)).Add(1)
	}
	return nil
}

// SpecificRecord finds the most specific match using the custom regexes from subzones
// It goes through all the custom regexes specified in each subzone and uses the
// most specific match to return the final record.
func (p *Path) SpecificRecord() (*record, error) {
	// Iterate subzones and parse the records
	regexes, err := p.fetchRegexes()
	if err != nil {
		return nil, err
	}

	record, err := p.specificMatch(regexes)
	if err != nil {
		return nil, err
	}

	return record, nil
}

func (p *Path) specificMatch(regexes RegexRecords) (*record, error) {
	recordMatch := make(map[int]RegexRecord)

	// Run each regex on the path and list them in a map
	for _, zone := range regexes {
		regex, err := regexp.Compile(zone.Regex)
		if err != nil {
			return nil, fmt.Errorf("Couldn't compile the regex: %s", err.Error())
		}
		matches := regex.FindAllStringSubmatch(p.path, -1)
		if len(matches) > 0 {
			zone.Submatches = matches[0]
		}
		recordMatch[len(zone.Submatches)] = zone
	}

	// Sort the map keys to find the most specific match
	var keys []int
	for k := range recordMatch {
		keys = append(keys, k)
	}
	sort.Ints(keys)

	// Add the most specific match's path slice to the request context to use in placeholders
	*p.req = *p.req.WithContext(context.WithValue(p.req.Context(), "regexMatches", recordMatch[keys[len(keys)-1]].Submatches))

	var rec record
	var err error
	if rec, err = ParseRecord(recordMatch[keys[len(keys)-1]].TXT, p.rw, p.req, p.c); err != nil {
		return nil, fmt.Errorf("Could not parse record: %s", err)
	}

	return &rec, nil
}

func (p *Path) fetchRegexes() (RegexRecords, error) {
	regexes := make(RegexRecords)
	for i, loop := 1, true; loop != false; i++ {
		txts, err := query(fmt.Sprintf("%d.%s", i, p.req.Host), p.req.Context(), p.c)
		if err != nil && len(regexes) >= 1 {
			break
		}
		if err != nil {
			return nil, fmt.Errorf("Couldn't fetch the subzones for predefined regex: %s", err.Error())
		}

		if !strings.Contains(txts[0], "re=") {
			return nil, fmt.Errorf("Couldn't find the re= field in records: %s", err.Error())
		}

		// Extract the re= field from record and add it to the map
		regexes[fmt.Sprintf("%d.%s", i, p.req.Host)] = RegexRecord{
			TXT:   txts[0],
			Regex: strings.TrimPrefix(strings.Split(txts[0][strings.Index(txts[0], "re="):], ";")[0], "re="),
		}
	}

	return regexes, nil
}

// zoneFromPath generates a DNS zone with the given request's path and host
// It will use custom regex to parse the path if it's provided in
// the given record.
func zoneFromPath(r *http.Request, rec record) (string, int, []string, error) {
	path := r.URL.Path

	// Check if request is from a Git client
	if strings.HasPrefix(r.Header.Get("User-Agent"), "git") {
		path = path[:strings.Index(path, "/info/refs")]
	}

	path = fmt.Sprintf("%s?%s", path, r.URL.RawQuery)

	// Normalize the path to follow RFC1034 rules
	if strings.ContainsAny(path, ".") {
		path = strings.Replace(path, ".", "-", -1)
	}

	pathSubmatchs := PathRegex.FindAllStringSubmatch(path, -1)
	if rec.Re != "" {
		// Compile the record regex and find path submatches
		CustomRegex, err := regexp.Compile(rec.Re)
		if err != nil {
			log.Printf("<%s> [txtdirect]: the given regex doesn't work as expected: %s", time.Now().String(), rec.Re)
		}
		pathSubmatchs = CustomRegex.FindAllStringSubmatch(path, -1)

		// Only generate the zone if the custom regex contains a group
		if GroupRegex.MatchString(rec.Re) {
			//
			pathSlice := []string{}
			unordered := make(map[string]string)
			for _, item := range pathSubmatchs[0] {
				pathSlice = append(pathSlice, item)
			}

			// Order the path slice using groups order in custom regex
			order := GroupOrderRegex.FindAllStringSubmatch(rec.Re, -1)
			for i, group := range order {
				unordered[group[1]] = pathSlice[i+1]
			}

			url := sortMap(unordered)
			*r = *r.WithContext(context.WithValue(r.Context(), "regexMatches", unordered))
			reverse(url)
			from := len(pathSlice)
			url = append(url, r.Host)
			url = append([]string{basezone}, url...)
			return strings.Join(url, "."), from, pathSlice, nil
		}
	}
	pathSlice := []string{}
	for _, v := range pathSubmatchs {
		pathSlice = append(pathSlice, v[1])
	}
	*r = *r.WithContext(context.WithValue(r.Context(), "regexMatches", pathSlice))
	if len(pathSlice) < 1 && rec.Re != "" {
		return "", 0, []string{}, fmt.Errorf("custom regex doesn't work on %s", path)
	}
	from := len(pathSlice)
	if rec.From != "" {
		fromSubmatch := FromRegex.FindAllStringSubmatch(rec.From, -1)
		if len(fromSubmatch) != len(pathSlice) {
			return "", 0, []string{}, fmt.Errorf("length of path doesn't match with length of from= in record")
		}
		fromSlice := make(map[int]string)
		for k, v := range fromSubmatch {
			index, _ := strconv.Atoi(v[1])
			fromSlice[index] = pathSlice[k]
		}

		keys := []int{}
		for k := range fromSlice {
			keys = append(keys, k)
		}
		if len(keys) != len(pathSlice) {
			return "", 0, []string{}, fmt.Errorf("length of path doesn't match with length of from= in record")
		}
		generatedPath := []string{}

		sort.Sort(sort.Reverse(sort.IntSlice(keys)))

		for _, k := range keys {
			generatedPath = append(generatedPath, fromSlice[k])
		}

		url := append(generatedPath, r.Host)
		url = append([]string{basezone}, url...)
		return strings.Join(url, "."), from, pathSlice, nil
	}
	ps := pathSlice
	reverse(pathSlice)
	url := append(pathSlice, r.Host)
	url = append([]string{basezone}, url...)
	return strings.Join(url, "."), from, ps, nil
}

// getFinalRecord finds the final TXT record for the given zone.
// It will try wildcards if the first zone return error
func getFinalRecord(zone string, from int, c Config, w http.ResponseWriter, r *http.Request, pathSlice []string) (record, error) {
	txts, err := query(zone, r.Context(), c)
	if err != nil {
		// if nothing found, jump into wildcards
		for i := 1; i <= from && len(txts) == 0; i++ {
			zoneSlice := strings.Split(zone, ".")
			zoneSlice[i] = "_"
			zone = strings.Join(zoneSlice, ".")
			txts, err = query(zone, r.Context(), c)
		}
	}
	if err != nil || len(txts) == 0 {
		return record{}, fmt.Errorf("could not get TXT record: %s", err)
	}

	txts[0], err = parsePlaceholders(txts[0], r, pathSlice)
	var rec record
	if rec, err = ParseRecord(txts[0], w, r, c); err != nil {
		return rec, fmt.Errorf("could not parse record: %s", err)
	}

	if rec.Type == "path" {
		records := r.Context().Value("records").([]record)
		parent := records[len(records)-1]

		// Use the parent's custom regex if available
		if rec.Re == "" && parent.Re != "" {
			rec.Re = parent.Re
		}

		return rec, nil
	}

	return rec, nil
}

// reverse reverses the order of the array
func reverse(input []string) {
	last := len(input) - 1
	for i := 0; i < len(input)/2; i++ {
		input[i], input[last-i] = input[last-i], input[i]
	}
}

func sortMap(m map[string]string) []string {
	var keys []string
	var result []string
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, k := range keys {
		result = append(result, m[k])
	}
	return result
}
<commit_msg>(path): Normalize path after adding it to context<commit_after>/*
Copyright 2019 - The TXTDirect 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 txtdirect

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

// Path contains the data that are needed to redirect path requests
type Path struct {
	rw   http.ResponseWriter
	req  *http.Request
	c    Config
	path string
	rec  record
}

// RegexRecords connects each zone address to a RegexRecord
// which contains raw TXT record and the re= field
type RegexRecords map[string]RegexRecord

// RegexRecord holds the TXT record and re= field of a predefined regex record
type RegexRecord struct {
	TXT        string
	Regex      string
	Submatches []string
}

// PathRegex is the default regex to parse request's path
// It can be replaced using the re= field in the records
var PathRegex = regexp.MustCompile("\\/([A-Za-z0-9-._~!$'()*+,;=:@]+)")

// FromRegex parses the from= field
var FromRegex = regexp.MustCompile("\\/\\$(\\d+)")

// GroupRegex parses the re= field to find the regex groups
var GroupRegex = regexp.MustCompile("P<[a-zA-Z]+[a-zA-Z0-9]*>")

// GroupOrderRegex finds the order of regex groups inside re=
var GroupOrderRegex = regexp.MustCompile("P<([a-zA-Z]+[a-zA-Z0-9]*)>")

// NewPath returns an instance of Path struct using the given data
func NewPath(w http.ResponseWriter, r *http.Request, path string, rec record, c Config) *Path {
	return &Path{
		rw:   w,
		req:  r,
		path: path,
		rec:  rec,
		c:    c,
	}
}

// Redirect finds and returns the final record
func (p *Path) Redirect() *record {
	zone, from, pathSlice, err := zoneFromPath(p.req, p.rec)
	rec, err := getFinalRecord(zone, from, p.c, p.rw, p.req, pathSlice)
	*p.req = *rec.addToContext(p.req)
	if err != nil {
		log.Print("Fallback is triggered because an error has occurred: ", err)
		fallback(p.rw, p.req, "to", p.rec.Code, p.c)
		return nil
	}

	if rec.Type == "path" {
		p.rec = rec
		return p.Redirect()
	}

	return &rec
}

// RedirectRoot redirects the request to record's root= field
// if the path is empty or "/". If the root= field is empty too
// fallback will be triggered.
func (p *Path) RedirectRoot() error {
	if p.rec.Root == "" {
		fallback(p.rw, p.req, "to", p.rec.Code, p.c)
		return nil
	}
	log.Printf("[txtdirect]: %s > %s", p.req.Host+p.req.URL.Path, p.rec.Root)
	if p.rec.Code == http.StatusMovedPermanently {
		p.rw.Header().Add("Cache-Control", fmt.Sprintf("max-age=%d", status301CacheAge))
	}
	p.rw.Header().Add("Status-Code", strconv.Itoa(p.rec.Code))
	http.Redirect(p.rw, p.req, p.rec.Root, p.rec.Code)
	if p.c.Prometheus.Enable {
		RequestsByStatus.WithLabelValues(p.req.Host, strconv.Itoa(p.rec.Code)).Add(1)
	}
	return nil
}

// SpecificRecord finds the most specific match using the custom regexes from subzones
// It goes through all the custom regexes specified in each subzone and uses the
// most specific match to return the final record.
func (p *Path) SpecificRecord() (*record, error) {
	// Iterate subzones and parse the records
	regexes, err := p.fetchRegexes()
	if err != nil {
		return nil, err
	}

	record, err := p.specificMatch(regexes)
	if err != nil {
		return nil, err
	}

	return record, nil
}

func (p *Path) specificMatch(regexes RegexRecords) (*record, error) {
	recordMatch := make(map[int]RegexRecord)

	// Run each regex on the path and list them in a map
	for _, zone := range regexes {
		regex, err := regexp.Compile(zone.Regex)
		if err != nil {
			return nil, fmt.Errorf("Couldn't compile the regex: %s", err.Error())
		}
		matches := regex.FindAllStringSubmatch(p.path, -1)
		if len(matches) > 0 {
			zone.Submatches = matches[0]
		}
		recordMatch[len(zone.Submatches)] = zone
	}

	// Sort the map keys to find the most specific match
	var keys []int
	for k := range recordMatch {
		keys = append(keys, k)
	}
	sort.Ints(keys)

	// Add the most specific match's path slice to the request context to use in placeholders
	*p.req = *p.req.WithContext(context.WithValue(p.req.Context(), "regexMatches", recordMatch[keys[len(keys)-1]].Submatches))

	var rec record
	var err error
	if rec, err = ParseRecord(recordMatch[keys[len(keys)-1]].TXT, p.rw, p.req, p.c); err != nil {
		return nil, fmt.Errorf("Could not parse record: %s", err)
	}

	return &rec, nil
}

func (p *Path) fetchRegexes() (RegexRecords, error) {
	regexes := make(RegexRecords)
	for i, loop := 1, true; loop != false; i++ {
		txts, err := query(fmt.Sprintf("%d.%s", i, p.req.Host), p.req.Context(), p.c)
		if err != nil && len(regexes) >= 1 {
			break
		}
		if err != nil {
			return nil, fmt.Errorf("Couldn't fetch the subzones for predefined regex: %s", err.Error())
		}

		if !strings.Contains(txts[0], "re=") {
			return nil, fmt.Errorf("Couldn't find the re= field in records: %s", err.Error())
		}

		// Extract the re= field from record and add it to the map
		regexes[fmt.Sprintf("%d.%s", i, p.req.Host)] = RegexRecord{
			TXT:   txts[0],
			Regex: strings.TrimPrefix(strings.Split(txts[0][strings.Index(txts[0], "re="):], ";")[0], "re="),
		}
	}

	return regexes, nil
}

// zoneFromPath generates a DNS zone with the given request's path and host
// It will use custom regex to parse the path if it's provided in
// the given record.
func zoneFromPath(r *http.Request, rec record) (string, int, []string, error) {
	path := r.URL.Path

	// Check if request is from a Git client
	if strings.HasPrefix(r.Header.Get("User-Agent"), "git") {
		path = path[:strings.Index(path, "/info/refs")]
	}

	path = fmt.Sprintf("%s?%s", path, r.URL.RawQuery)

	pathSubmatchs := PathRegex.FindAllStringSubmatch(path, -1)
	if rec.Re != "" {
		// Compile the record regex and find path submatches
		CustomRegex, err := regexp.Compile(rec.Re)
		if err != nil {
			log.Printf("<%s> [txtdirect]: the given regex doesn't work as expected: %s", time.Now().String(), rec.Re)
		}
		pathSubmatchs = CustomRegex.FindAllStringSubmatch(path, -1)

		// Only generate the zone if the custom regex contains a group
		if GroupRegex.MatchString(rec.Re) {
			//
			pathSlice := []string{}
			unordered := make(map[string]string)
			for _, item := range pathSubmatchs[0] {
				pathSlice = append(pathSlice, item)
			}

			// Order the path slice using groups order in custom regex
			order := GroupOrderRegex.FindAllStringSubmatch(rec.Re, -1)
			for i, group := range order {
				unordered[group[1]] = pathSlice[i+1]
			}

			url := sortMap(unordered)
			*r = *r.WithContext(context.WithValue(r.Context(), "regexMatches", unordered))
			url = normalize(url)
			reverse(url)
			from := len(pathSlice)
			url = append(url, r.Host)
			url = append([]string{basezone}, url...)
			return strings.Join(url, "."), from, pathSlice, nil
		}
	}
	pathSlice := []string{}
	for _, v := range pathSubmatchs {
		pathSlice = append(pathSlice, v[1])
	}
	*r = *r.WithContext(context.WithValue(r.Context(), "regexMatches", pathSlice))
	if len(pathSlice) < 1 && rec.Re != "" {
		return "", 0, []string{}, fmt.Errorf("custom regex doesn't work on %s", path)
	}
	pathSlice = normalize(pathSlice)
	from := len(pathSlice)
	if rec.From != "" {
		fromSubmatch := FromRegex.FindAllStringSubmatch(rec.From, -1)
		if len(fromSubmatch) != len(pathSlice) {
			return "", 0, []string{}, fmt.Errorf("length of path doesn't match with length of from= in record")
		}
		fromSlice := make(map[int]string)
		for k, v := range fromSubmatch {
			index, _ := strconv.Atoi(v[1])
			fromSlice[index] = pathSlice[k]
		}

		keys := []int{}
		for k := range fromSlice {
			keys = append(keys, k)
		}
		if len(keys) != len(pathSlice) {
			return "", 0, []string{}, fmt.Errorf("length of path doesn't match with length of from= in record")
		}
		generatedPath := []string{}

		sort.Sort(sort.Reverse(sort.IntSlice(keys)))

		for _, k := range keys {
			generatedPath = append(generatedPath, fromSlice[k])
		}

		url := append(generatedPath, r.Host)
		url = append([]string{basezone}, url...)
		return strings.Join(url, "."), from, pathSlice, nil
	}
	ps := pathSlice
	reverse(pathSlice)
	url := append(pathSlice, r.Host)
	url = append([]string{basezone}, url...)
	return strings.Join(url, "."), from, ps, nil
}

// getFinalRecord finds the final TXT record for the given zone.
// It will try wildcards if the first zone return error
func getFinalRecord(zone string, from int, c Config, w http.ResponseWriter, r *http.Request, pathSlice []string) (record, error) {
	txts, err := query(zone, r.Context(), c)
	if err != nil {
		// if nothing found, jump into wildcards
		for i := 1; i <= from && len(txts) == 0; i++ {
			zoneSlice := strings.Split(zone, ".")
			zoneSlice[i] = "_"
			zone = strings.Join(zoneSlice, ".")
			txts, err = query(zone, r.Context(), c)
		}
	}
	if err != nil || len(txts) == 0 {
		return record{}, fmt.Errorf("could not get TXT record: %s", err)
	}

	txts[0], err = parsePlaceholders(txts[0], r, pathSlice)
	var rec record
	if rec, err = ParseRecord(txts[0], w, r, c); err != nil {
		return rec, fmt.Errorf("could not parse record: %s", err)
	}

	if rec.Type == "path" {
		records := r.Context().Value("records").([]record)
		parent := records[len(records)-1]

		// Use the parent's custom regex if available
		if rec.Re == "" && parent.Re != "" {
			rec.Re = parent.Re
		}

		return rec, nil
	}

	return rec, nil
}

// reverse reverses the order of the array
func reverse(input []string) {
	last := len(input) - 1
	for i := 0; i < len(input)/2; i++ {
		input[i], input[last-i] = input[last-i], input[i]
	}
}

func sortMap(m map[string]string) []string {
	var keys []string
	var result []string
	for k := range m {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, k := range keys {
		result = append(result, m[k])
	}
	return result
}

// Normalize the path to follow RFC1034 rules
func normalize(input []string) []string {
	var result []string
	for _, value := range input {
		if strings.ContainsAny(value, ".") {
			value = strings.Replace(value, ".", "-", -1)
		}
		result = append(result, value)
	}
	return result
}
<|endoftext|>


<commit_before>// Copyright 2010 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 "syscall"

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

func knownFolderPath(id CSIDL) (string, error) {
	var buf [MAX_PATH]uint16

	if !ShGetSpecialFolderPath(0, &buf[0], id, false) {
		return "", newError("ShGetSpecialFolderPath failed")
	}

	return syscall.UTF16ToString(buf[0:]), nil
}

func AppDataPath() (string, error) {
	return knownFolderPath(CSIDL_APPDATA)
}

func CommonAppDataPath() (string, error) {
	return knownFolderPath(CSIDL_COMMON_APPDATA)
}

func LocalAppDataPath() (string, error) {
	return knownFolderPath(CSIDL_LOCAL_APPDATA)
}

func DriveNames() ([]string, error) {
	bufLen := GetLogicalDriveStrings(0, nil)
	if bufLen == 0 {
		return nil, lastError("GetLogicalDriveStrings")
	}
	buf := make([]uint16, bufLen+1)

	bufLen = GetLogicalDriveStrings(bufLen+1, &buf[0])
	if bufLen == 0 {
		return nil, lastError("GetLogicalDriveStrings")
	}

	var names []string

	for i := 0; i < len(buf)-2; {
		name := syscall.UTF16ToString(buf[i:])
		names = append(names, name)
		i += len(name) + 1
	}

	return names, nil
}
<commit_msg>Whoops, forgot one file in previous commit.<commit_after>// Copyright 2010 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 "syscall"

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

func knownFolderPath(id CSIDL) (string, error) {
	var buf [MAX_PATH]uint16

	if !SHGetSpecialFolderPath(0, &buf[0], id, false) {
		return "", newError("SHGetSpecialFolderPath failed")
	}

	return syscall.UTF16ToString(buf[0:]), nil
}

func AppDataPath() (string, error) {
	return knownFolderPath(CSIDL_APPDATA)
}

func CommonAppDataPath() (string, error) {
	return knownFolderPath(CSIDL_COMMON_APPDATA)
}

func LocalAppDataPath() (string, error) {
	return knownFolderPath(CSIDL_LOCAL_APPDATA)
}

func DriveNames() ([]string, error) {
	bufLen := GetLogicalDriveStrings(0, nil)
	if bufLen == 0 {
		return nil, lastError("GetLogicalDriveStrings")
	}
	buf := make([]uint16, bufLen+1)

	bufLen = GetLogicalDriveStrings(bufLen+1, &buf[0])
	if bufLen == 0 {
		return nil, lastError("GetLogicalDriveStrings")
	}

	var names []string

	for i := 0; i < len(buf)-2; {
		name := syscall.UTF16ToString(buf[i:])
		names = append(names, name)
		i += len(name) + 1
	}

	return names, nil
}
<|endoftext|>


<commit_before>package peco

import (
	"os"
	"sync"
	"time"

	"golang.org/x/net/context"

	"github.com/google/btree"
	"github.com/peco/peco/hub"
	"github.com/peco/peco/internal/util"
	"github.com/peco/peco/pipeline"
	"github.com/peco/peco/sig"
	"github.com/pkg/errors"
)

const version = "v0.3.6"

// Global variable that bridges the "screen", so testing is easier
var screen Screen = Termbox{}

// Inputseq is a list of keys that the user typed
type Inputseq []string

func (is *Inputseq) Add(s string) {
	*is = append(*is, s)
}

func (is Inputseq) KeyNames() []string {
	return is
}

func (is Inputseq) Len() int {
	return len(is)
}

func (is *Inputseq) Reset() {
	*is = []string(nil)
}

// Peco is the global object containing everything required to run peco.
// It also contains the global state of the program.
type Peco struct {
	Argv []string
	hub  *hub.Hub

	activeLineBuffer Buffer
	args             []string
	caret            Caret
	// Config contains the values read in from config file
	config                  Config
	ctx                     context.Context
	filters                 FilterSet
	keymap                  Keymap
	enableSep               bool     // Enable parsing on separators
	inputseq                Inputseq // current key sequence (just the names)
	layoutType              string
	location                Location
	prompt                  string
	query                   Query
	queryExecDelay          time.Duration
	queryExecMutex          sync.Mutex
	queryExecTimer          *time.Timer
	resultCh                chan Line
	selection               *Selection
	selectionRangeStart     int
	singleKeyJumpMode       bool
	singleKeyJumpPrefixes   []rune
	singleKeyJumpShowPrefix bool
	styles                  StyleSet

	Options CLIOptions

	// Source is where we buffer input. It gets reused when a new query is
	// executed.
	source *Source

	// cancelFunc is called for Exit()
	cancelFunc func()
	// Errors are stored here
	err error
}

func New() *Peco {
	return &Peco{
		activeLineBuffer:    &MemoryBuffer{}, // XXX revisit this
		selection:           NewSelection(),
		selectionRangeStart: invalidSelectionRange,
	}
}

func (p Peco) Styles() *StyleSet {
	return &p.styles
}

func (p Peco) Prompt() string {
	return p.prompt
}

func (p Peco) Inputseq() *Inputseq {
	return &p.inputseq
}

func (p Peco) Context() context.Context {
	return p.ctx
}

func (p Peco) CurrentLineBuffer() LineBuffer {
	return nil // XXX DUMMY
}

func (p Peco) LayoutType() string {
	return p.layoutType
}

func (p *Peco) Location() *Location {
	return &p.location
}

func (p Peco) ResultCh() chan Line {
	return p.resultCh
}

func (p *Peco) SetResultCh(ch chan Line) {
	p.resultCh = ch
}

func (p Peco) Selection() *Selection {
	return p.selection
}

func (p Peco) SelectionRangeStart() int {
	return p.selectionRangeStart
}

func (p Peco) SetSelectionRangeStart(s int) {
	if s >= invalidSelectionRange {
		p.selectionRangeStart = s
	}
}

func (p Peco) RangeMode() bool {
	return p.selectionRangeStart != invalidSelectionRange
}

func (p Peco) SingleKeyJumpShowPrefix() bool {
	return p.singleKeyJumpShowPrefix
}

func (p Peco) SingleKeyJumpPrefixes() []rune {
	return p.singleKeyJumpPrefixes
}

func (p Peco) SingleKeyJumpMode() bool {
	return p.singleKeyJumpMode
}

func (p *Peco) SetSingleKeyJumpMode(b bool) {
	p.singleKeyJumpMode = b
}

func (p *Peco) ToggleSingleKeyJumpMode() {
	p.singleKeyJumpMode = !p.singleKeyJumpMode
}

func (p *Peco) SingleKeyJumpIndex(ch rune) (uint, bool) {
	// FIXME: use p.keyjump or something instead of p.config
	n, ok := p.config.SingleKeyJump.PrefixMap[ch]
	return n, ok
}

func (p *Peco) Source() pipeline.Source {
	return p.source
}

func (p *Peco) Filters() *FilterSet {
	return &p.filters
}

func (p Peco) Query() *Query {
	return &p.query
}

func (p Peco) QueryExecDelay() time.Duration {
	return p.queryExecDelay
}

func (p Peco) Caret() *Caret {
	return &p.caret
}

func (p *Peco) Hub() *hub.Hub {
	return p.hub
}

func (p *Peco) Err() error {
	return p.err
}

func (p *Peco) Exit(err error) {
	p.err = err
	if cf := p.cancelFunc; cf != nil {
		cf()
	}
}

func (p Peco) Keymap() Keymap {
	return p.keymap
}

func (p *Peco) Setup() error {
	if err := parseCommandLine(&p.Options, &p.args, p.Argv); err != nil {
		return errors.Wrap(err, "failed to parse command line")
	}

	// Read config
	if err := readConfig(&p.config, p.Options.OptRcfile); err != nil {
		return errors.Wrap(err, "failed to setup configuration")
	}

	// Take Args, Config, Options, and apply the configuration to
	// the peco object
	if err := p.ApplyConfig(); err != nil {
		return errors.Wrap(err, "failed to apply configuration")
	}

	// XXX p.Keymap et al should be initialized around here

	// Setup source buffer
	src, err := p.SetupSource()
	if err != nil {
		return errors.Wrap(err, "failed to setup input source")
	}
	p.source = src

	p.hub = hub.New(5)

	return nil
}

func (p *Peco) Run() error {
	if err := p.Setup(); err != nil {
		return errors.Wrap(err, "failed to setup peco")
	}

	var ctx context.Context
	var cancel func()

	ctx, cancel = context.WithCancel(context.Background())

	// keep *this* ctx (not the Background one), as calling `cancel`
	// only affects the wrapped context
	p.ctx = ctx

	// remember this cancel func so p.Exit works (XXX requires locking?)
	p.cancelFunc = cancel

	loopers := []interface {
		Loop(ctx context.Context, cancel func()) error
	}{
		NewInput(p, p.Keymap(), screen.PollEvent()),
		NewView(p),
		sig.New(sig.SigReceivedHandlerFunc(func(sig os.Signal) {
			p.Exit(errors.New("received signal: " + sig.String()))
		})),
	}

	for _, l := range loopers {
		go l.Loop(ctx, cancel)
	}

	trace("peco is now ready, go go go!")
	<-ctx.Done()

	return p.Err()
}

func parseCommandLine(opts *CLIOptions, args *[]string, argv []string) error {
	remaining, err := opts.parse(argv)
	if err != nil {
		return errors.Wrap(err, "failed to parse command line options")
	}
	*args = remaining

	return nil
}

func (p *Peco) SetupSource() (*Source, error) {
	var in *os.File
	var err error
	switch {
	case len(p.args) > 0:
		in, err = os.Open(p.args[0])
		if err != nil {
			return nil, errors.Wrap(err, "failed to open file for input")
		}
	case !util.IsTty(os.Stdin.Fd()):
		in = os.Stdin
	default:
		return nil, errors.Wrap(err, "error: You must supply something to work with via filename or stdin")
	}
	defer in.Close()

	src := NewSource(in, p.enableSep)
	// Block until we receive something from `in`
	go src.Setup(p)
	<-src.Ready()

	return src, nil
}

func readConfig(cfg *Config, filename string) error {
	if filename != "" {
		if err := cfg.ReadFilename(filename); err != nil {
			return errors.Wrap(err, "failed to read config file")
		}
	}

	return nil
}

func (p *Peco) populateCommandList() error {
	for _, v := range p.config.Command {
		if len(v.Args) == 0 {
			continue
		}
		makeCommandAction(&v).Register("ExecuteCommand." + v.Name)
	}

	return nil
}

func (p *Peco) ApplyConfig() error {
	// If layoutType is not set and is set in the config, set it
	if p.layoutType == "" {
		if v := p.config.Layout; v != "" {
			p.layoutType = v
		} else {
			p.layoutType = DefaultLayoutType
		}
	}

	if err := p.populateCommandList(); err != nil {
		return err
	}

	return nil
}

func (p Peco) ActiveLineBuffer() Buffer {
	return p.activeLineBuffer
}

func (p *Peco) ResetActiveLineBuffer() {
	p.activeLineBuffer = p.source
}

func (p *Peco) ExecQuery() bool {
	trace("Peco.ExecQuery: START")
	defer trace("Peco.ExecQuery: END")

	// If this is an empty query, reset the display to show
	// the raw source buffer
	q := p.Query()
	if q.Len() <= 0 {
		if p.ActiveLineBuffer() != nil {
			p.ResetActiveLineBuffer()
			return true
		}
		return false
	}

	delay := p.QueryExecDelay()
	if delay <= 0 {
		// No delay, execute immediately
		p.Hub().SendQuery(q.String())
		return true
	}

	p.queryExecMutex.Lock()
	defer p.queryExecMutex.Unlock()

	if p.queryExecTimer != nil {
		return true
	}

	// Wait $delay millisecs before sending the query
	// if a new input comes in, batch them up
	p.queryExecTimer = time.AfterFunc(delay, func() {
		p.Hub().SendQuery(q.String())

		p.queryExecMutex.Lock()
		defer p.queryExecMutex.Unlock()

		p.queryExecTimer = nil
	})
	return true
}

func (p *Peco) collectResults() {
	// In rare cases where the result channel is not setup
	// prior to call to this method, bail out
	if p.resultCh == nil {
		return
	}

	p.selection.Ascend(func(it btree.Item) bool {
		p.resultCh <- it.(Line)
		return true
	})
	close(p.resultCh)
}
<commit_msg>look at args[1], not args[0]<commit_after>package peco

import (
	"os"
	"sync"
	"time"

	"golang.org/x/net/context"

	"github.com/google/btree"
	"github.com/peco/peco/hub"
	"github.com/peco/peco/internal/util"
	"github.com/peco/peco/pipeline"
	"github.com/peco/peco/sig"
	"github.com/pkg/errors"
)

const version = "v0.3.6"

// Global variable that bridges the "screen", so testing is easier
var screen Screen = Termbox{}

// Inputseq is a list of keys that the user typed
type Inputseq []string

func (is *Inputseq) Add(s string) {
	*is = append(*is, s)
}

func (is Inputseq) KeyNames() []string {
	return is
}

func (is Inputseq) Len() int {
	return len(is)
}

func (is *Inputseq) Reset() {
	*is = []string(nil)
}

// Peco is the global object containing everything required to run peco.
// It also contains the global state of the program.
type Peco struct {
	Argv []string
	hub  *hub.Hub

	activeLineBuffer Buffer
	args             []string
	caret            Caret
	// Config contains the values read in from config file
	config                  Config
	ctx                     context.Context
	filters                 FilterSet
	keymap                  Keymap
	enableSep               bool     // Enable parsing on separators
	inputseq                Inputseq // current key sequence (just the names)
	layoutType              string
	location                Location
	prompt                  string
	query                   Query
	queryExecDelay          time.Duration
	queryExecMutex          sync.Mutex
	queryExecTimer          *time.Timer
	resultCh                chan Line
	selection               *Selection
	selectionRangeStart     int
	singleKeyJumpMode       bool
	singleKeyJumpPrefixes   []rune
	singleKeyJumpShowPrefix bool
	styles                  StyleSet

	Options CLIOptions

	// Source is where we buffer input. It gets reused when a new query is
	// executed.
	source *Source

	// cancelFunc is called for Exit()
	cancelFunc func()
	// Errors are stored here
	err error
}

func New() *Peco {
	return &Peco{
		activeLineBuffer:    &MemoryBuffer{}, // XXX revisit this
		selection:           NewSelection(),
		selectionRangeStart: invalidSelectionRange,
	}
}

func (p Peco) Styles() *StyleSet {
	return &p.styles
}

func (p Peco) Prompt() string {
	return p.prompt
}

func (p Peco) Inputseq() *Inputseq {
	return &p.inputseq
}

func (p Peco) Context() context.Context {
	return p.ctx
}

func (p Peco) CurrentLineBuffer() LineBuffer {
	return nil // XXX DUMMY
}

func (p Peco) LayoutType() string {
	return p.layoutType
}

func (p *Peco) Location() *Location {
	return &p.location
}

func (p Peco) ResultCh() chan Line {
	return p.resultCh
}

func (p *Peco) SetResultCh(ch chan Line) {
	p.resultCh = ch
}

func (p Peco) Selection() *Selection {
	return p.selection
}

func (p Peco) SelectionRangeStart() int {
	return p.selectionRangeStart
}

func (p Peco) SetSelectionRangeStart(s int) {
	if s >= invalidSelectionRange {
		p.selectionRangeStart = s
	}
}

func (p Peco) RangeMode() bool {
	return p.selectionRangeStart != invalidSelectionRange
}

func (p Peco) SingleKeyJumpShowPrefix() bool {
	return p.singleKeyJumpShowPrefix
}

func (p Peco) SingleKeyJumpPrefixes() []rune {
	return p.singleKeyJumpPrefixes
}

func (p Peco) SingleKeyJumpMode() bool {
	return p.singleKeyJumpMode
}

func (p *Peco) SetSingleKeyJumpMode(b bool) {
	p.singleKeyJumpMode = b
}

func (p *Peco) ToggleSingleKeyJumpMode() {
	p.singleKeyJumpMode = !p.singleKeyJumpMode
}

func (p *Peco) SingleKeyJumpIndex(ch rune) (uint, bool) {
	// FIXME: use p.keyjump or something instead of p.config
	n, ok := p.config.SingleKeyJump.PrefixMap[ch]
	return n, ok
}

func (p *Peco) Source() pipeline.Source {
	return p.source
}

func (p *Peco) Filters() *FilterSet {
	return &p.filters
}

func (p Peco) Query() *Query {
	return &p.query
}

func (p Peco) QueryExecDelay() time.Duration {
	return p.queryExecDelay
}

func (p Peco) Caret() *Caret {
	return &p.caret
}

func (p *Peco) Hub() *hub.Hub {
	return p.hub
}

func (p *Peco) Err() error {
	return p.err
}

func (p *Peco) Exit(err error) {
	p.err = err
	if cf := p.cancelFunc; cf != nil {
		cf()
	}
}

func (p Peco) Keymap() Keymap {
	return p.keymap
}

func (p *Peco) Setup() error {
	if err := parseCommandLine(&p.Options, &p.args, p.Argv); err != nil {
		return errors.Wrap(err, "failed to parse command line")
	}

	// Read config
	if err := readConfig(&p.config, p.Options.OptRcfile); err != nil {
		return errors.Wrap(err, "failed to setup configuration")
	}

	// Take Args, Config, Options, and apply the configuration to
	// the peco object
	if err := p.ApplyConfig(); err != nil {
		return errors.Wrap(err, "failed to apply configuration")
	}

	// XXX p.Keymap et al should be initialized around here

	// Setup source buffer
	src, err := p.SetupSource()
	if err != nil {
		return errors.Wrap(err, "failed to setup input source")
	}
	p.source = src

	p.hub = hub.New(5)

	return nil
}

func (p *Peco) Run() error {
	if err := p.Setup(); err != nil {
		return errors.Wrap(err, "failed to setup peco")
	}

	var ctx context.Context
	var cancel func()

	ctx, cancel = context.WithCancel(context.Background())

	// keep *this* ctx (not the Background one), as calling `cancel`
	// only affects the wrapped context
	p.ctx = ctx

	// remember this cancel func so p.Exit works (XXX requires locking?)
	p.cancelFunc = cancel

	loopers := []interface {
		Loop(ctx context.Context, cancel func()) error
	}{
		NewInput(p, p.Keymap(), screen.PollEvent()),
		NewView(p),
		sig.New(sig.SigReceivedHandlerFunc(func(sig os.Signal) {
			p.Exit(errors.New("received signal: " + sig.String()))
		})),
	}

	for _, l := range loopers {
		go l.Loop(ctx, cancel)
	}

	trace("peco is now ready, go go go!")
	<-ctx.Done()

	return p.Err()
}

func parseCommandLine(opts *CLIOptions, args *[]string, argv []string) error {
	remaining, err := opts.parse(argv)
	if err != nil {
		return errors.Wrap(err, "failed to parse command line options")
	}
	*args = remaining

	return nil
}

func (p *Peco) SetupSource() (*Source, error) {
	var in *os.File
	var err error
	switch {
	case len(p.args) > 1:
		in, err = os.Open(p.args[1])
		if err != nil {
			return nil, errors.Wrap(err, "failed to open file for input")
		}
	case !util.IsTty(os.Stdin.Fd()):
		in = os.Stdin
	default:
		return nil, errors.Wrap(err, "error: You must supply something to work with via filename or stdin")
	}
	defer in.Close()

	src := NewSource(in, p.enableSep)
	// Block until we receive something from `in`
	go src.Setup(p)
	<-src.Ready()

	return src, nil
}

func readConfig(cfg *Config, filename string) error {
	if filename != "" {
		if err := cfg.ReadFilename(filename); err != nil {
			return errors.Wrap(err, "failed to read config file")
		}
	}

	return nil
}

func (p *Peco) populateCommandList() error {
	for _, v := range p.config.Command {
		if len(v.Args) == 0 {
			continue
		}
		makeCommandAction(&v).Register("ExecuteCommand." + v.Name)
	}

	return nil
}

func (p *Peco) ApplyConfig() error {
	// If layoutType is not set and is set in the config, set it
	if p.layoutType == "" {
		if v := p.config.Layout; v != "" {
			p.layoutType = v
		} else {
			p.layoutType = DefaultLayoutType
		}
	}

	if err := p.populateCommandList(); err != nil {
		return err
	}

	return nil
}

func (p Peco) ActiveLineBuffer() Buffer {
	return p.activeLineBuffer
}

func (p *Peco) ResetActiveLineBuffer() {
	p.activeLineBuffer = p.source
}

func (p *Peco) ExecQuery() bool {
	trace("Peco.ExecQuery: START")
	defer trace("Peco.ExecQuery: END")

	// If this is an empty query, reset the display to show
	// the raw source buffer
	q := p.Query()
	if q.Len() <= 0 {
		if p.ActiveLineBuffer() != nil {
			p.ResetActiveLineBuffer()
			return true
		}
		return false
	}

	delay := p.QueryExecDelay()
	if delay <= 0 {
		// No delay, execute immediately
		p.Hub().SendQuery(q.String())
		return true
	}

	p.queryExecMutex.Lock()
	defer p.queryExecMutex.Unlock()

	if p.queryExecTimer != nil {
		return true
	}

	// Wait $delay millisecs before sending the query
	// if a new input comes in, batch them up
	p.queryExecTimer = time.AfterFunc(delay, func() {
		p.Hub().SendQuery(q.String())

		p.queryExecMutex.Lock()
		defer p.queryExecMutex.Unlock()

		p.queryExecTimer = nil
	})
	return true
}

func (p *Peco) collectResults() {
	// In rare cases where the result channel is not setup
	// prior to call to this method, bail out
	if p.resultCh == nil {
		return
	}

	p.selection.Ascend(func(it btree.Item) bool {
		p.resultCh <- it.(Line)
		return true
	})
	close(p.resultCh)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/valyala/fasthttp"
	"net"
	"net/url"
	"os"
	"time"
)

var bytesSlash = []byte("/") // heap optimization
var byteZero = []byte{0}     // heap optimization

// HTTPClient is a reusable HTTP Client.
type FastHTTPClient struct {
	HTTPClientCommon
	client fasthttp.Client
}

// NewHTTPClient creates a new HTTPClient.
func NewFastHTTPClient(host string, debug int, dialTimeout time.Duration, readTimeout time.Duration, writeTimeout time.Duration) *FastHTTPClient {
	return &FastHTTPClient{
		client: fasthttp.Client{
			Name: "query_benchmarker",
			Dial: func(addr string) (net.Conn, error) {
				return fasthttp.DialTimeout(addr, dialTimeout)
			},
			MaxIdleConnDuration: idleConnectionTimeout,
			ReadTimeout: readTimeout,
			WriteTimeout: writeTimeout,
		},
		HTTPClientCommon: HTTPClientCommon{
			Host:       []byte(host),
			HostString: host,
			debug:      debug,
		},
	}
}

// Do performs the action specified by the given Query. It uses fasthttp, and
// tries to minimize heap allocations.
func (w *FastHTTPClient) Do(q *Query, opts *HTTPClientDoOptions) (lag float64, err error) {
	// populate uri from the reusable byte slice:
	uri := make([]byte, 0, 100)
	uri = append(uri, w.Host...)
	uri = append(uri, bytesSlash...)
	uri = append(uri, q.Path...)

	// populate a request with data from the Query:
	req := fasthttp.AcquireRequest()
	defer fasthttp.ReleaseRequest(req)

	req.Header.SetMethodBytes(q.Method)
	req.Header.SetRequestURIBytes(uri)
	req.SetBody(q.Body)
	// Perform the request while tracking latency:
	resp := fasthttp.AcquireResponse()
	defer fasthttp.ReleaseResponse(resp)
	start := time.Now()
	err = w.client.Do(req, resp)
	lag = float64(time.Since(start).Nanoseconds()) / 1e6 // milliseconds

	if (err != nil || resp.StatusCode() != fasthttp.StatusOK) && opts.Debug == 5 {
		values, _ := url.ParseQuery(string(uri))
		fmt.Printf("debug: url: %s, path %s, parsed url - %s\n", string(uri), q.Path, values)
	}

	// Check that the status code was 200 OK:
	if err == nil {
		sc := resp.StatusCode()
		if sc != fasthttp.StatusOK {
			err = fmt.Errorf("Invalid write response (status %d): %s", sc, resp.Body())
			return
		}
	}

	if opts != nil {
		// Print debug messages, if applicable:
		switch opts.Debug {
		case 1:
			fmt.Fprintf(os.Stderr, "debug: %s in %7.2fms\n", q.HumanLabel, lag)
		case 2:
			fmt.Fprintf(os.Stderr, "debug: %s in %7.2fms -- %s\n", q.HumanLabel, lag, q.HumanDescription)
		case 3:
			fmt.Fprintf(os.Stderr, "debug: %s in %7.2fms -- %s\n", q.HumanLabel, lag, q.HumanDescription)
			fmt.Fprintf(os.Stderr, "debug:   request: %s\n", string(q.String()))
		case 4:
			fmt.Fprintf(os.Stderr, "debug: %s in %7.2fms -- %s\n", q.HumanLabel, lag, q.HumanDescription)
			fmt.Fprintf(os.Stderr, "debug:   request: %s\n", string(q.String()))
			fmt.Fprintf(os.Stderr, "debug:   response: %s\n", string(resp.Body()))
		default:
		}

		// Pretty print JSON responses, if applicable:
		if opts.PrettyPrintResponses {
			// InfluxQL responses are in JSON and can be pretty-printed here.
			// Flux responses are just simple CSV.

			prefix := fmt.Sprintf("ID %d: ", q.ID)
			if json.Valid(resp.Body()) {
				var pretty bytes.Buffer
				err = json.Indent(&pretty, resp.Body(), prefix, "  ")
				if err != nil {
					return
				}

				_, err = fmt.Fprintf(os.Stderr, "%s%s\n", prefix, pretty)
				if err != nil {
					return
				}
			} else {
				_, err = fmt.Fprintf(os.Stderr, "%s%s\n", prefix, resp.Body())
				if err != nil {
					return
				}
			}
		}
	}

	return lag, size, err
}

func (w *FastHTTPClient) HostString() string {
	return w.HTTPClientCommon.HostString
}

func (w *FastHTTPClient) Ping() {
	req := fasthttp.AcquireRequest()
	defer fasthttp.ReleaseRequest(req)
	req.Header.SetMethod("GET")
	req.Header.SetRequestURI(w.HTTPClientCommon.HostString + "/ping")
	resp := fasthttp.AcquireResponse()
	defer fasthttp.ReleaseResponse(resp)
	_ = w.client.Do(req, resp)
}
<commit_msg>fixing compilation error<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/valyala/fasthttp"
	"net"
	"net/url"
	"os"
	"time"
)

var bytesSlash = []byte("/") // heap optimization
var byteZero = []byte{0}     // heap optimization

// HTTPClient is a reusable HTTP Client.
type FastHTTPClient struct {
	HTTPClientCommon
	client fasthttp.Client
}

// NewHTTPClient creates a new HTTPClient.
func NewFastHTTPClient(host string, debug int, dialTimeout time.Duration, readTimeout time.Duration, writeTimeout time.Duration) *FastHTTPClient {
	return &FastHTTPClient{
		client: fasthttp.Client{
			Name: "query_benchmarker",
			Dial: func(addr string) (net.Conn, error) {
				return fasthttp.DialTimeout(addr, dialTimeout)
			},
			MaxIdleConnDuration: idleConnectionTimeout,
			ReadTimeout: readTimeout,
			WriteTimeout: writeTimeout,
		},
		HTTPClientCommon: HTTPClientCommon{
			Host:       []byte(host),
			HostString: host,
			debug:      debug,
		},
	}
}

// Do performs the action specified by the given Query. It uses fasthttp, and
// tries to minimize heap allocations.
func (w *FastHTTPClient) Do(q *Query, opts *HTTPClientDoOptions) (lag float64, err error) {
	// populate uri from the reusable byte slice:
	uri := make([]byte, 0, 100)
	uri = append(uri, w.Host...)
	uri = append(uri, bytesSlash...)
	uri = append(uri, q.Path...)

	// populate a request with data from the Query:
	req := fasthttp.AcquireRequest()
	defer fasthttp.ReleaseRequest(req)

	req.Header.SetMethodBytes(q.Method)
	req.Header.SetRequestURIBytes(uri)
	req.SetBody(q.Body)
	// Perform the request while tracking latency:
	resp := fasthttp.AcquireResponse()
	defer fasthttp.ReleaseResponse(resp)
	start := time.Now()
	err = w.client.Do(req, resp)
	lag = float64(time.Since(start).Nanoseconds()) / 1e6 // milliseconds

	if (err != nil || resp.StatusCode() != fasthttp.StatusOK) && opts.Debug == 5 {
		values, _ := url.ParseQuery(string(uri))
		fmt.Printf("debug: url: %s, path %s, parsed url - %s\n", string(uri), q.Path, values)
	}

	// Check that the status code was 200 OK:
	if err == nil {
		sc := resp.StatusCode()
		if sc != fasthttp.StatusOK {
			err = fmt.Errorf("Invalid write response (status %d): %s", sc, resp.Body())
			return
		}
	}

	if opts != nil {
		// Print debug messages, if applicable:
		switch opts.Debug {
		case 1:
			fmt.Fprintf(os.Stderr, "debug: %s in %7.2fms\n", q.HumanLabel, lag)
		case 2:
			fmt.Fprintf(os.Stderr, "debug: %s in %7.2fms -- %s\n", q.HumanLabel, lag, q.HumanDescription)
		case 3:
			fmt.Fprintf(os.Stderr, "debug: %s in %7.2fms -- %s\n", q.HumanLabel, lag, q.HumanDescription)
			fmt.Fprintf(os.Stderr, "debug:   request: %s\n", string(q.String()))
		case 4:
			fmt.Fprintf(os.Stderr, "debug: %s in %7.2fms -- %s\n", q.HumanLabel, lag, q.HumanDescription)
			fmt.Fprintf(os.Stderr, "debug:   request: %s\n", string(q.String()))
			fmt.Fprintf(os.Stderr, "debug:   response: %s\n", string(resp.Body()))
		default:
		}

		// Pretty print JSON responses, if applicable:
		if opts.PrettyPrintResponses {
			// InfluxQL responses are in JSON and can be pretty-printed here.
			// Flux responses are just simple CSV.

			prefix := fmt.Sprintf("ID %d: ", q.ID)
			if json.Valid(resp.Body()) {
				var pretty bytes.Buffer
				err = json.Indent(&pretty, resp.Body(), prefix, "  ")
				if err != nil {
					return
				}

				_, err = fmt.Fprintf(os.Stderr, "%s%s\n", prefix, pretty)
				if err != nil {
					return
				}
			} else {
				_, err = fmt.Fprintf(os.Stderr, "%s%s\n", prefix, resp.Body())
				if err != nil {
					return
				}
			}
		}
	}

	return lag, err
}

func (w *FastHTTPClient) HostString() string {
	return w.HTTPClientCommon.HostString
}

func (w *FastHTTPClient) Ping() {
	req := fasthttp.AcquireRequest()
	defer fasthttp.ReleaseRequest(req)
	req.Header.SetMethod("GET")
	req.Header.SetRequestURI(w.HTTPClientCommon.HostString + "/ping")
	resp := fasthttp.AcquireResponse()
	defer fasthttp.ReleaseResponse(resp)
	_ = w.client.Do(req, resp)
}
<|endoftext|>


<commit_before>package tfjson

// PlanFormatVersion is the version of the JSON plan format that is
// supported by this package.
const PlanFormatVersion = "0.1"

// ResourceMode is a string representation of the resource type found
// in certain fields in the plan.
type ResourceMode string

const (
	// DataSourceMode is the resource mode for data sources.
	DataSourceMode ResourceMode = "data"

	// ManagedResourceMode is the resource mode for managed resources.
	ManagedResourceMode ResourceMode = "resource"
)

// Plan represents the entire contents of an output Terraform plan.
type Plan struct {
	// The version of the plan format. This should always match the
	// PlanFormatVersion constant in this package, or else an unmarshal
	// will be unstable.
	FormatVersion string `json:"format_version,omitempty"`

	// The common state representation of resources within this plan.
	// This is a product of the existing state merged with the diff for
	// this plan.
	PlannedValues StateValues `json:"planned_values,omitempty"`

	// The change operations for resources and data sources within this
	// plan.
	ResourceChanges []ResourceChange `json:"resource_changes,omitempty"`

	// The change operations for outputs within this plan.
	OutputChanges map[string]Change `json:"output_changes,omitempty"`

	// The Terraform state prior to the plan operation. This is the
	// same format as PlannedValues, without the current diff merged.
	PriorState State `json:"prior_state,omitempty"`

	// The Terraform configuration used to make the plan.
	// Config Config `json:"configuration,omitempty"`
}

// ResourceChange is a description of an individual change action
// that Terraform plans to use to move from the prior state to a new
// state matching the configuration.
type ResourceChange struct {
	// The absolute resource address.
	Address string `json:"address,omitempty"`

	// The module portion of the above address. Omitted if the instance
	// is in the root module.
	ModuleAddress string `json:"module_address,omitempty"`

	// The resource mode.
	Mode ResourceMode `json:"mode,omitempty"`

	// The resource type, example: "aws_instance" for aws_instance.foo.
	Type string `json:"type,omitempty"`

	// The resource name, example: "foo" for aws_instance.foo.
	Name string `json:"name,omitempty"`

	// The instance key for any resources that have been created using
	// "count" or "for_each". If neither of these apply the key will be
	// empty.
	//
	// This value can be either an integer (int) or a string.
	Index interface{} `json:"index,omitempty"`

	// If set, indicates that this action applies to a "deposed" object
	// of the given instance rather than to its "current" object.
	// Omitted for changes to the current object.
	Deposed bool `json:"deposed,omitempty"`

	// The data describing the change that will be made to this object.
	Change Change `json:"change,omitempty"`
}

// Change is the representation of a proposed change for an object.
type Change struct {
	// The action to be carried out by this change.
	Actions Actions `json:"actions,omitempty"`

	// Before and After are representations of the object value both
	// before and after the action. For create and delete actions,
	// either Before or After is unset (respectively). For no-op
	// actions, both values will be identical. After will be incomplete
	// if there are values within it that won't be known until after
	// apply.
	Before map[string]interface{} `json:"before,omitempty"`
	After  map[string]interface{} `json:"after,omitempty"`

	// A deep object of booleans that denotes any values that are
	// unknown in a resource. These values were previously referred to
	// as "computed" values.
	//
	// If the value cannot be found in this map, then its value should
	// be available within After, so long as the operation supports it.
	AfterUnknown map[string]interface{} `json:"after_unknown,omitempty"`
}
<commit_msg>Resource mode for managed resources is "managed"<commit_after>package tfjson

// PlanFormatVersion is the version of the JSON plan format that is
// supported by this package.
const PlanFormatVersion = "0.1"

// ResourceMode is a string representation of the resource type found
// in certain fields in the plan.
type ResourceMode string

const (
	// DataSourceMode is the resource mode for data sources.
	DataSourceMode ResourceMode = "data"

	// ManagedResourceMode is the resource mode for managed resources.
	ManagedResourceMode ResourceMode = "managed"
)

// Plan represents the entire contents of an output Terraform plan.
type Plan struct {
	// The version of the plan format. This should always match the
	// PlanFormatVersion constant in this package, or else an unmarshal
	// will be unstable.
	FormatVersion string `json:"format_version,omitempty"`

	// The common state representation of resources within this plan.
	// This is a product of the existing state merged with the diff for
	// this plan.
	PlannedValues StateValues `json:"planned_values,omitempty"`

	// The change operations for resources and data sources within this
	// plan.
	ResourceChanges []ResourceChange `json:"resource_changes,omitempty"`

	// The change operations for outputs within this plan.
	OutputChanges map[string]Change `json:"output_changes,omitempty"`

	// The Terraform state prior to the plan operation. This is the
	// same format as PlannedValues, without the current diff merged.
	PriorState State `json:"prior_state,omitempty"`

	// The Terraform configuration used to make the plan.
	// Config Config `json:"configuration,omitempty"`
}

// ResourceChange is a description of an individual change action
// that Terraform plans to use to move from the prior state to a new
// state matching the configuration.
type ResourceChange struct {
	// The absolute resource address.
	Address string `json:"address,omitempty"`

	// The module portion of the above address. Omitted if the instance
	// is in the root module.
	ModuleAddress string `json:"module_address,omitempty"`

	// The resource mode.
	Mode ResourceMode `json:"mode,omitempty"`

	// The resource type, example: "aws_instance" for aws_instance.foo.
	Type string `json:"type,omitempty"`

	// The resource name, example: "foo" for aws_instance.foo.
	Name string `json:"name,omitempty"`

	// The instance key for any resources that have been created using
	// "count" or "for_each". If neither of these apply the key will be
	// empty.
	//
	// This value can be either an integer (int) or a string.
	Index interface{} `json:"index,omitempty"`

	// If set, indicates that this action applies to a "deposed" object
	// of the given instance rather than to its "current" object.
	// Omitted for changes to the current object.
	Deposed bool `json:"deposed,omitempty"`

	// The data describing the change that will be made to this object.
	Change Change `json:"change,omitempty"`
}

// Change is the representation of a proposed change for an object.
type Change struct {
	// The action to be carried out by this change.
	Actions Actions `json:"actions,omitempty"`

	// Before and After are representations of the object value both
	// before and after the action. For create and delete actions,
	// either Before or After is unset (respectively). For no-op
	// actions, both values will be identical. After will be incomplete
	// if there are values within it that won't be known until after
	// apply.
	Before map[string]interface{} `json:"before,omitempty"`
	After  map[string]interface{} `json:"after,omitempty"`

	// A deep object of booleans that denotes any values that are
	// unknown in a resource. These values were previously referred to
	// as "computed" values.
	//
	// If the value cannot be found in this map, then its value should
	// be available within After, so long as the operation supports it.
	AfterUnknown map[string]interface{} `json:"after_unknown,omitempty"`
}
<|endoftext|>


<commit_before>package doorman

import (
	"bytes"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
)

func TestMain(m *testing.M) {
	//Set Gin to Test Mode
	gin.SetMode(gin.TestMode)
	// Run the other tests
	os.Exit(m.Run())
}

func sampleDoorman() *LadonDoorman {
	doorman := New([]string{"../sample.yaml"})
	doorman.LoadPolicies()
	return doorman
}

func loadTempFiles(contents ...string) (*LadonDoorman, error) {
	var filenames []string
	for _, content := range contents {
		tmpfile, _ := ioutil.TempFile("", "")
		defer os.Remove(tmpfile.Name()) // clean up
		tmpfile.Write([]byte(content))
		tmpfile.Close()
		filenames = append(filenames, tmpfile.Name())
	}
	w := New(filenames)
	err := w.LoadPolicies()
	return w, err
}

func TestLoadBadPolicies(t *testing.T) {
	// Missing file
	w := New([]string{"/tmp/unknown.yaml"})
	err := w.LoadPolicies()
	assert.NotNil(t, err)

	// Empty file
	_, err = loadTempFiles("")
	assert.NotNil(t, err)

	// Bad YAML
	_, err = loadTempFiles("$\\--xx")
	assert.NotNil(t, err)

	// Empty audience
	_, err = loadTempFiles(`
audience:
policies:
  -
    id: "1"
    effect: allow
`)
	assert.NotNil(t, err)

	// Empty policies
	_, err = loadTempFiles(`
audience: a
policies:
`)
	assert.Nil(t, err)

	// Bad audience
	_, err = loadTempFiles(`
audience: 1
policies:
  -
    id: "1"
    effect: allow
`)
	assert.NotNil(t, err)

	// Bad policies conditions
	_, err = loadTempFiles(`
audience: a
policies:
  -
    id: "1"
    conditions:
      - a
      - b
`)
	assert.NotNil(t, err)

	// Duplicated policy ID
	_, err = loadTempFiles(`
audience: a
policies:
  -
    id: "1"
    effect: allow
  -
    id: "1"
    effect: deny
`)
	assert.NotNil(t, err)

	// Duplicated audience
	_, err = loadTempFiles(`
audience: a
policies:
  -
    id: "1"
    effect: allow
`, `
audience: a
policies:
  -
    id: "1"
    effect: allow
`)
	assert.NotNil(t, err)
}

func TestLoadFolder(t *testing.T) {
	tmpfile, _ := ioutil.TempFile("", "")
	defer os.Remove(tmpfile.Name()) // clean up

	// Create temp dir
	dir, err := ioutil.TempDir("", "example")
	assert.Nil(t, err)
	defer os.RemoveAll(dir)

	// Create sample file
	testfile := filepath.Join(dir, "test.yaml")
	defer os.Remove(testfile)
	err = ioutil.WriteFile(testfile, []byte(`
audience: a
policies:
  -
    id: "1"
    action: read
    effect: allow
`), 0666)

	w := New([]string{testfile})
	err = w.LoadPolicies()
	assert.Nil(t, err)
	assert.Equal(t, len(w.configs["a"].Policies), 1)
}

func TestLoadGithub(t *testing.T) {
	// Unsupported URL
	w := New([]string{"https://bitbucket.org/test.yaml"})
	err := w.LoadPolicies()
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "no appropriate loader found")

	// Unsupported folder.
	w = New([]string{"https://github.com/moz/ops/configs/"})
	err = w.LoadPolicies()
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "not supported")

	// Bad URL
	w = New([]string{"https://github.com/moz/ops/config.yaml"})
	err = w.LoadPolicies()
	assert.NotNil(t, err)

	// Bad file
	w = New([]string{"https://github.com/leplatrem/iam/raw/06a2531/main.go"})
	err = w.LoadPolicies()
	assert.NotNil(t, err)

	// Good URL
	w = New([]string{"https://github.com/leplatrem/iam/raw/06a2531/sample.yaml"})
	err = w.LoadPolicies()
	assert.Nil(t, err)
	assert.Equal(t, len(w.configs["https://sample.yaml"].Tags), 1)
	assert.Equal(t, len(w.configs["https://sample.yaml"].Policies), 6)
}

func TestLoadTags(t *testing.T) {
	d, err := loadTempFiles(`
audience: a
tags:
  admins:
    - alice@mit.edu
    - ldap|bob
  editors:
    - mathieu@mozilla.com
policies:
  -
    id: "1"
    effect: allow
`)
	assert.Nil(t, err)
	assert.Equal(t, len(d.configs["a"].Tags), 2)
	assert.Equal(t, len(d.configs["a"].Tags["admins"]), 2)
	assert.Equal(t, len(d.configs["a"].Tags["editors"]), 1)
}

func TestReloadPolicies(t *testing.T) {
	doorman := sampleDoorman()
	loaded, _ := doorman.configs["https://sample.yaml"].ladon.Manager.GetAll(0, maxInt)
	assert.Equal(t, 6, len(loaded))

	// Second load.
	doorman.LoadPolicies()
	loaded, _ = doorman.configs["https://sample.yaml"].ladon.Manager.GetAll(0, maxInt)
	assert.Equal(t, 6, len(loaded))

	// Load bad policies, does not affect existing.
	doorman.policiesSources = []string{"/tmp/unknown.yaml"}
	doorman.LoadPolicies()
	_, ok := doorman.configs["https://sample.yaml"]
	assert.True(t, ok)
}

func TestIsAllowed(t *testing.T) {
	doorman := sampleDoorman()

	// Policy #1
	request := &Request{
		Principals: Principals{"userid:foo"},
		Action:     "update",
		Resource:   "server.org/blocklist:onecrl",
	}

	// Check audience
	allowed := doorman.IsAllowed("https://sample.yaml", request)
	assert.True(t, allowed)
	allowed = doorman.IsAllowed("https://bad.audience", request)
	assert.False(t, allowed)
}

func TestExpandPrincipals(t *testing.T) {
	doorman := sampleDoorman()

	// Expand principals from tags
	principals := doorman.ExpandPrincipals("https://sample.yaml", Principals{"userid:maria"})
	assert.Equal(t, principals, Principals{"userid:maria", "tag:admins"})
}

func TestDoormanAllowed(t *testing.T) {
	doorman := sampleDoorman()

	for _, request := range []*Request{
		// Policy #1
		{
			Principals: []string{"userid:foo"},
			Action:     "update",
			Resource:   "server.org/blocklist:onecrl",
		},
		// Policy #2
		{
			Principals: []string{"userid:foo"},
			Action:     "update",
			Resource:   "server.org/blocklist:onecrl",
			Context: Context{
				"planet": "Mars", // "mars" is case-sensitive
			},
		},
		// Policy #3
		{
			Principals: []string{"userid:foo"},
			Action:     "read",
			Resource:   "server.org/blocklist:onecrl",
			Context: Context{
				"ip": "127.0.0.1",
			},
		},
		// Policy #4
		{
			Principals: []string{"userid:bilbo"},
			Action:     "wear",
			Resource:   "ring",
			Context: Context{
				"owner": "userid:bilbo",
			},
		},
		// Policy #5
		{
			Principals: []string{"group:admins"},
			Action:     "create",
			Resource:   "dns://",
			Context: Context{
				"domain": "kinto.mozilla.org",
			},
		},
	} {
		assert.Equal(t, true, doorman.IsAllowed("https://sample.yaml", request))
	}
}

func TestDoormanNotAllowed(t *testing.T) {
	doorman := sampleDoorman()

	for _, request := range []*Request{
		// Policy #1
		{
			Principals: []string{"userid:foo"},
			Action:     "delete",
			Resource:   "server.org/blocklist:onecrl",
		},
		// Policy #2
		{
			Principals: []string{"userid:foo"},
			Action:     "update",
			Resource:   "server.org/blocklist:onecrl",
			Context: Context{
				"planet": "mars",
			},
		},
		// Policy #3
		{
			Principals: []string{"userid:foo"},
			Action:     "read",
			Resource:   "server.org/blocklist:onecrl",
			Context: Context{
				"ip": "10.0.0.1",
			},
		},
		// Policy #4
		{
			Principals: []string{"userid:gollum"},
			Action:     "wear",
			Resource:   "ring",
			Context: Context{
				"owner": "bilbo",
			},
		},
		// Policy #5
		{
			Principals: []string{"group:admins"},
			Action:     "create",
			Resource:   "dns://",
			Context: Context{
				"domain": "kinto-storage.org",
			},
		},
		// Default
		{},
	} {
		assert.Equal(t, false, doorman.IsAllowed("https://sample.yaml", request))
	}
}

func TestDoormanAuditLogger(t *testing.T) {
	doorman := sampleDoorman()

	var buf bytes.Buffer
	doorman.auditLogger().logger.Out = &buf
	defer func() {
		doorman.auditLogger().logger.Out = os.Stdout
	}()

	// Logs when audience is bad.
	doorman.IsAllowed("bad audience", &Request{})
	assert.Contains(t, buf.String(), "\"allowed\":false")

	audience := "https://sample.yaml"

	// Logs policies.
	buf.Reset()
	doorman.IsAllowed(audience, &Request{
		Principals: []string{"userid:any"},
		Action:     "any",
		Resource:   "any",
		Context: Context{
			"planet": "mars",
		},
	})
	assert.Contains(t, buf.String(), "\"allowed\":false")
	assert.Contains(t, buf.String(), "\"policies\":[\"2\"]")

	buf.Reset()
	doorman.IsAllowed(audience, &Request{
		Principals: []string{"userid:foo"},
		Action:     "update",
		Resource:   "server.org/blocklist:onecrl",
	})
	assert.Contains(t, buf.String(), "\"allowed\":true")
	assert.Contains(t, buf.String(), "\"policies\":[\"1\"]")
}
<commit_msg>Fix folder test<commit_after>package doorman

import (
	"bytes"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
)

func TestMain(m *testing.M) {
	//Set Gin to Test Mode
	gin.SetMode(gin.TestMode)
	// Run the other tests
	os.Exit(m.Run())
}

func sampleDoorman() *LadonDoorman {
	doorman := New([]string{"../sample.yaml"})
	doorman.LoadPolicies()
	return doorman
}

func loadTempFiles(contents ...string) (*LadonDoorman, error) {
	var filenames []string
	for _, content := range contents {
		tmpfile, _ := ioutil.TempFile("", "")
		defer os.Remove(tmpfile.Name()) // clean up
		tmpfile.Write([]byte(content))
		tmpfile.Close()
		filenames = append(filenames, tmpfile.Name())
	}
	w := New(filenames)
	err := w.LoadPolicies()
	return w, err
}

func TestLoadBadPolicies(t *testing.T) {
	// Missing file
	w := New([]string{"/tmp/unknown.yaml"})
	err := w.LoadPolicies()
	assert.NotNil(t, err)

	// Empty file
	_, err = loadTempFiles("")
	assert.NotNil(t, err)

	// Bad YAML
	_, err = loadTempFiles("$\\--xx")
	assert.NotNil(t, err)

	// Empty audience
	_, err = loadTempFiles(`
audience:
policies:
  -
    id: "1"
    effect: allow
`)
	assert.NotNil(t, err)

	// Empty policies
	_, err = loadTempFiles(`
audience: a
policies:
`)
	assert.Nil(t, err)

	// Bad audience
	_, err = loadTempFiles(`
audience: 1
policies:
  -
    id: "1"
    effect: allow
`)
	assert.NotNil(t, err)

	// Bad policies conditions
	_, err = loadTempFiles(`
audience: a
policies:
  -
    id: "1"
    conditions:
      - a
      - b
`)
	assert.NotNil(t, err)

	// Duplicated policy ID
	_, err = loadTempFiles(`
audience: a
policies:
  -
    id: "1"
    effect: allow
  -
    id: "1"
    effect: deny
`)
	assert.NotNil(t, err)

	// Duplicated audience
	_, err = loadTempFiles(`
audience: a
policies:
  -
    id: "1"
    effect: allow
`, `
audience: a
policies:
  -
    id: "1"
    effect: allow
`)
	assert.NotNil(t, err)
}

func TestLoadFolder(t *testing.T) {
	// Create temp dir
	dir, err := ioutil.TempDir("", "example")
	assert.Nil(t, err)
	defer os.RemoveAll(dir)
	// Create subdir (to be skipped)
	subdir, err := ioutil.TempDir(dir, "ignored")
	assert.Nil(t, err)
	defer os.RemoveAll(subdir)

	// Create sample file
	testfile := filepath.Join(dir, "test.yaml")
	defer os.Remove(testfile)
	err = ioutil.WriteFile(testfile, []byte(`
audience: a
policies:
  -
    id: "1"
    action: read
    effect: allow
`), 0666)

	w := New([]string{dir})
	err = w.LoadPolicies()
	assert.Nil(t, err)
	assert.Equal(t, len(w.configs["a"].Policies), 1)
}

func TestLoadGithub(t *testing.T) {
	// Unsupported URL
	w := New([]string{"https://bitbucket.org/test.yaml"})
	err := w.LoadPolicies()
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "no appropriate loader found")

	// Unsupported folder.
	w = New([]string{"https://github.com/moz/ops/configs/"})
	err = w.LoadPolicies()
	assert.NotNil(t, err)
	assert.Contains(t, err.Error(), "not supported")

	// Bad URL
	w = New([]string{"ftp://github.com/moz/ops/config.yaml"})
	err = w.LoadPolicies()
	assert.NotNil(t, err)

	// Bad file
	w = New([]string{"https://github.com/leplatrem/iam/raw/06a2531/main.go"})
	err = w.LoadPolicies()
	assert.NotNil(t, err)

	// Good URL
	w = New([]string{"https://github.com/leplatrem/iam/raw/06a2531/sample.yaml"})
	err = w.LoadPolicies()
	assert.Nil(t, err)
	assert.Equal(t, len(w.configs["https://sample.yaml"].Tags), 1)
	assert.Equal(t, len(w.configs["https://sample.yaml"].Policies), 6)
}

func TestLoadTags(t *testing.T) {
	d, err := loadTempFiles(`
audience: a
tags:
  admins:
    - alice@mit.edu
    - ldap|bob
  editors:
    - mathieu@mozilla.com
policies:
  -
    id: "1"
    effect: allow
`)
	assert.Nil(t, err)
	assert.Equal(t, len(d.configs["a"].Tags), 2)
	assert.Equal(t, len(d.configs["a"].Tags["admins"]), 2)
	assert.Equal(t, len(d.configs["a"].Tags["editors"]), 1)
}

func TestReloadPolicies(t *testing.T) {
	doorman := sampleDoorman()
	loaded, _ := doorman.configs["https://sample.yaml"].ladon.Manager.GetAll(0, maxInt)
	assert.Equal(t, 6, len(loaded))

	// Second load.
	doorman.LoadPolicies()
	loaded, _ = doorman.configs["https://sample.yaml"].ladon.Manager.GetAll(0, maxInt)
	assert.Equal(t, 6, len(loaded))

	// Load bad policies, does not affect existing.
	doorman.policiesSources = []string{"/tmp/unknown.yaml"}
	doorman.LoadPolicies()
	_, ok := doorman.configs["https://sample.yaml"]
	assert.True(t, ok)
}

func TestIsAllowed(t *testing.T) {
	doorman := sampleDoorman()

	// Policy #1
	request := &Request{
		Principals: Principals{"userid:foo"},
		Action:     "update",
		Resource:   "server.org/blocklist:onecrl",
	}

	// Check audience
	allowed := doorman.IsAllowed("https://sample.yaml", request)
	assert.True(t, allowed)
	allowed = doorman.IsAllowed("https://bad.audience", request)
	assert.False(t, allowed)
}

func TestExpandPrincipals(t *testing.T) {
	doorman := sampleDoorman()

	// Expand principals from tags
	principals := doorman.ExpandPrincipals("https://sample.yaml", Principals{"userid:maria"})
	assert.Equal(t, principals, Principals{"userid:maria", "tag:admins"})
}

func TestDoormanAllowed(t *testing.T) {
	doorman := sampleDoorman()

	for _, request := range []*Request{
		// Policy #1
		{
			Principals: []string{"userid:foo"},
			Action:     "update",
			Resource:   "server.org/blocklist:onecrl",
		},
		// Policy #2
		{
			Principals: []string{"userid:foo"},
			Action:     "update",
			Resource:   "server.org/blocklist:onecrl",
			Context: Context{
				"planet": "Mars", // "mars" is case-sensitive
			},
		},
		// Policy #3
		{
			Principals: []string{"userid:foo"},
			Action:     "read",
			Resource:   "server.org/blocklist:onecrl",
			Context: Context{
				"ip": "127.0.0.1",
			},
		},
		// Policy #4
		{
			Principals: []string{"userid:bilbo"},
			Action:     "wear",
			Resource:   "ring",
			Context: Context{
				"owner": "userid:bilbo",
			},
		},
		// Policy #5
		{
			Principals: []string{"group:admins"},
			Action:     "create",
			Resource:   "dns://",
			Context: Context{
				"domain": "kinto.mozilla.org",
			},
		},
	} {
		assert.Equal(t, true, doorman.IsAllowed("https://sample.yaml", request))
	}
}

func TestDoormanNotAllowed(t *testing.T) {
	doorman := sampleDoorman()

	for _, request := range []*Request{
		// Policy #1
		{
			Principals: []string{"userid:foo"},
			Action:     "delete",
			Resource:   "server.org/blocklist:onecrl",
		},
		// Policy #2
		{
			Principals: []string{"userid:foo"},
			Action:     "update",
			Resource:   "server.org/blocklist:onecrl",
			Context: Context{
				"planet": "mars",
			},
		},
		// Policy #3
		{
			Principals: []string{"userid:foo"},
			Action:     "read",
			Resource:   "server.org/blocklist:onecrl",
			Context: Context{
				"ip": "10.0.0.1",
			},
		},
		// Policy #4
		{
			Principals: []string{"userid:gollum"},
			Action:     "wear",
			Resource:   "ring",
			Context: Context{
				"owner": "bilbo",
			},
		},
		// Policy #5
		{
			Principals: []string{"group:admins"},
			Action:     "create",
			Resource:   "dns://",
			Context: Context{
				"domain": "kinto-storage.org",
			},
		},
		// Default
		{},
	} {
		assert.Equal(t, false, doorman.IsAllowed("https://sample.yaml", request))
	}
}

func TestDoormanAuditLogger(t *testing.T) {
	doorman := sampleDoorman()

	var buf bytes.Buffer
	doorman.auditLogger().logger.Out = &buf
	defer func() {
		doorman.auditLogger().logger.Out = os.Stdout
	}()

	// Logs when audience is bad.
	doorman.IsAllowed("bad audience", &Request{})
	assert.Contains(t, buf.String(), "\"allowed\":false")

	audience := "https://sample.yaml"

	// Logs policies.
	buf.Reset()
	doorman.IsAllowed(audience, &Request{
		Principals: []string{"userid:any"},
		Action:     "any",
		Resource:   "any",
		Context: Context{
			"planet": "mars",
		},
	})
	assert.Contains(t, buf.String(), "\"allowed\":false")
	assert.Contains(t, buf.String(), "\"policies\":[\"2\"]")

	buf.Reset()
	doorman.IsAllowed(audience, &Request{
		Principals: []string{"userid:foo"},
		Action:     "update",
		Resource:   "server.org/blocklist:onecrl",
	})
	assert.Contains(t, buf.String(), "\"allowed\":true")
	assert.Contains(t, buf.String(), "\"policies\":[\"1\"]")
}
<|endoftext|>


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

/*
	Package fmt implements formatted I/O with functions analogous
	to C's printf and scanf.  The format 'verbs' are derived from C's but
	are simpler.

	Printing:

	The verbs:

	General:
		%v	the value in a default format.
			when printing structs, the plus flag (%+v) adds field names
		%#v	a Go-syntax representation of the value
		%T	a Go-syntax representation of the type of the value

	Boolean:
		%t	the word true or false
	Integer:
		%b	base 2
		%c	the character represented by the corresponding Unicode code point
		%d	base 10
		%o	base 8
		%x	base 16, with lower-case letters for a-f
		%X	base 16, with upper-case letters for A-F
	Floating-point and complex constituents:
		%e	scientific notation, e.g. -1234.456e+78
		%E	scientific notation, e.g. -1234.456E+78
		%f	decimal point but no exponent, e.g. 123.456
		%g	whichever of %e or %f produces more compact output
		%G	whichever of %E or %f produces more compact output
	String and slice of bytes:
		%s	the uninterpreted bytes of the string or slice
		%q	a double-quoted string safely escaped with Go syntax
		%x	base 16 notation with two characters per byte
	Pointer:
		%p	base 16 notation, with leading 0x

	There is no 'u' flag.  Integers are printed unsigned if they have unsigned type.
	Similarly, there is no need to specify the size of the operand (int8, int64).

	For numeric values, the width and precision flags control
	formatting; width sets the width of the field, precision the
	number of places after the decimal, if appropriate.  The
	format %6.2f prints 123.45. The width of a field is the number
	of Unicode code points in the string. This differs from C's printf where
	the field width is the number of bytes.

	Other flags:
		+	always print a sign for numeric values
		-	pad with spaces on the right rather than the left (left-justify the field)
		#	alternate format: add leading 0 for octal (%#o), 0x for hex (%#x);
			0X for hex (%#X); suppress 0x for %p (%#p);
			print a raw (backquoted) string if possible for %q (%#q)
		' '	(space) leave a space for elided sign in numbers (% d);
			put spaces between bytes printing strings or slices in hex (% x)
		0	pad with leading zeros rather than spaces

	For each Printf-like function, there is also a Print function
	that takes no format and is equivalent to saying %v for every
	operand.  Another variant Println inserts blanks between
	operands and appends a newline.

	Regardless of the verb, if an operand is an interface value,
	the internal concrete value is used, not the interface itself.
	Thus:
		var i interface{} = 23;
		fmt.Printf("%v\n", i);
	will print 23.

	If an operand implements interface Formatter, that interface
	can be used for fine control of formatting.

	If an operand implements method String() string that method
	will be used to conver the object to a string, which will then
	be formatted as required by the verb (if any). To avoid
	recursion in cases such as
		type X int
		func (x X) String() string { return Sprintf("%d", x) }
	cast the value before recurring:
		func (x X) String() string { return Sprintf("%d", int(x)) }

	Scanning:

	An analogous set of functions scans formatted text to yield
	values.  Scan, Scanf and Scanln read from os.Stdin; Fscan,
	Fscanf and Fscanln read from a specified os.Reader; Sscan,
	Sscanf and Sscanln read from an argument string.  Sscanln,
	Fscanln and Sscanln stop scanning at a newline and require that
	the items be followed by one; Sscanf, Fscanf and Sscanf require
	newlines in the input to match newlines in the format; the other
	routines treat newlines as spaces.

	Scanf, Fscanf, and Sscanf parse the arguments according to a
	format string, analogous to that of Printf.  For example, "%x"
	will scan an integer as a hexadecimal number, and %v will scan
	the default representation format for the value.

	The formats behave analogously to those of Printf with the
	following exceptions:

	%p is not implemented
	%T is not implemented
	%e %E %f %F %g %g are all equivalent and scan any floating
		point or complex value
	%s and %v on strings scan a space-delimited token

	Width is interpreted in the input text (%5s means at most
	five runes of input will be read to scan a string) but there
	is no syntax for scanning with a precision (no %5.2f, just
	%5f).

	When scanning with a format, all non-empty runs of space
	characters (except newline) are equivalent to a single
	space in both the format and the input.  With that proviso,
	text in the format string must match the input text; scanning
	stops if it does not, with the return value of the function
	indicating the number of arguments scanned.

	In all the scanning functions, if an operand implements method
	Scan (that is, it implements the Scanner interface) that
	method will be used to scan the text for that operand.  Also,
	if the number of arguments scanned is less than the number of
	arguments provided, an error is returned.

	All arguments to be scanned must be either pointers to basic
	types or implementations of the Scanner interface.
*/
package fmt
<commit_msg>fmt: fix typo in package comment. No semantic change.<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
	Package fmt implements formatted I/O with functions analogous
	to C's printf and scanf.  The format 'verbs' are derived from C's but
	are simpler.

	Printing:

	The verbs:

	General:
		%v	the value in a default format.
			when printing structs, the plus flag (%+v) adds field names
		%#v	a Go-syntax representation of the value
		%T	a Go-syntax representation of the type of the value

	Boolean:
		%t	the word true or false
	Integer:
		%b	base 2
		%c	the character represented by the corresponding Unicode code point
		%d	base 10
		%o	base 8
		%x	base 16, with lower-case letters for a-f
		%X	base 16, with upper-case letters for A-F
	Floating-point and complex constituents:
		%e	scientific notation, e.g. -1234.456e+78
		%E	scientific notation, e.g. -1234.456E+78
		%f	decimal point but no exponent, e.g. 123.456
		%g	whichever of %e or %f produces more compact output
		%G	whichever of %E or %f produces more compact output
	String and slice of bytes:
		%s	the uninterpreted bytes of the string or slice
		%q	a double-quoted string safely escaped with Go syntax
		%x	base 16 notation with two characters per byte
	Pointer:
		%p	base 16 notation, with leading 0x

	There is no 'u' flag.  Integers are printed unsigned if they have unsigned type.
	Similarly, there is no need to specify the size of the operand (int8, int64).

	For numeric values, the width and precision flags control
	formatting; width sets the width of the field, precision the
	number of places after the decimal, if appropriate.  The
	format %6.2f prints 123.45. The width of a field is the number
	of Unicode code points in the string. This differs from C's printf where
	the field width is the number of bytes.

	Other flags:
		+	always print a sign for numeric values
		-	pad with spaces on the right rather than the left (left-justify the field)
		#	alternate format: add leading 0 for octal (%#o), 0x for hex (%#x);
			0X for hex (%#X); suppress 0x for %p (%#p);
			print a raw (backquoted) string if possible for %q (%#q)
		' '	(space) leave a space for elided sign in numbers (% d);
			put spaces between bytes printing strings or slices in hex (% x)
		0	pad with leading zeros rather than spaces

	For each Printf-like function, there is also a Print function
	that takes no format and is equivalent to saying %v for every
	operand.  Another variant Println inserts blanks between
	operands and appends a newline.

	Regardless of the verb, if an operand is an interface value,
	the internal concrete value is used, not the interface itself.
	Thus:
		var i interface{} = 23;
		fmt.Printf("%v\n", i);
	will print 23.

	If an operand implements interface Formatter, that interface
	can be used for fine control of formatting.

	If an operand implements method String() string that method
	will be used to convert the object to a string, which will then
	be formatted as required by the verb (if any). To avoid
	recursion in cases such as
		type X int
		func (x X) String() string { return Sprintf("%d", x) }
	cast the value before recurring:
		func (x X) String() string { return Sprintf("%d", int(x)) }

	Scanning:

	An analogous set of functions scans formatted text to yield
	values.  Scan, Scanf and Scanln read from os.Stdin; Fscan,
	Fscanf and Fscanln read from a specified os.Reader; Sscan,
	Sscanf and Sscanln read from an argument string.  Sscanln,
	Fscanln and Sscanln stop scanning at a newline and require that
	the items be followed by one; Sscanf, Fscanf and Sscanf require
	newlines in the input to match newlines in the format; the other
	routines treat newlines as spaces.

	Scanf, Fscanf, and Sscanf parse the arguments according to a
	format string, analogous to that of Printf.  For example, "%x"
	will scan an integer as a hexadecimal number, and %v will scan
	the default representation format for the value.

	The formats behave analogously to those of Printf with the
	following exceptions:

	%p is not implemented
	%T is not implemented
	%e %E %f %F %g %g are all equivalent and scan any floating
		point or complex value
	%s and %v on strings scan a space-delimited token

	Width is interpreted in the input text (%5s means at most
	five runes of input will be read to scan a string) but there
	is no syntax for scanning with a precision (no %5.2f, just
	%5f).

	When scanning with a format, all non-empty runs of space
	characters (except newline) are equivalent to a single
	space in both the format and the input.  With that proviso,
	text in the format string must match the input text; scanning
	stops if it does not, with the return value of the function
	indicating the number of arguments scanned.

	In all the scanning functions, if an operand implements method
	Scan (that is, it implements the Scanner interface) that
	method will be used to scan the text for that operand.  Also,
	if the number of arguments scanned is less than the number of
	arguments provided, an error is returned.

	All arguments to be scanned must be either pointers to basic
	types or implementations of the Scanner interface.

	Note: Fscan etc.  can read one character past the input
	they return, which means that a loop calling a scan routine
	may skip some of the input.  This is usually a problem only
	when there is no space between input values.
*/
package fmt
<|endoftext|>


<commit_before>package sandbox

import (
	"arp147/clock"
	"arp147/input"
	"arp147/logging"
	"arp147/ui"
	"engo.io/ecs"
	"engo.io/engo"
	"engo.io/engo/common"
	"image/color"
)

type SandboxScene struct {
	//
}

func (*SandboxScene) Type() string {
	return "SandboxScene"
}

func (scene *SandboxScene) Preload() {
	// Load files needed for the scene. If we can't load the files, everything
	// will be fucked up and we should just stop.
	if err := engo.Files.Load(
		"textures/space.png",
	); err != nil {
		logging.Stderr.Fatal("Could not preload files: ", err)
	}
}

func (scene *SandboxScene) Setup(world *ecs.World) {
	// Add systems needed for the scene.
	world.AddSystem(&common.RenderSystem{})
	world.AddSystem(&input.InputSystem{})
	world.AddSystem(&clock.ClockSystem{})
	world.AddSystem(&ui.LabelUpdateSystem{})

	// Tile the world background.
	ui.TileWorld(world, "textures/space.png")

	// Setup supported input for the scene
	scene.SetupInput()

	// Add HUD
	ui.NewLabel(ui.Label{
		Text:      clock.String(),
		Updatable: clock.String,
		Position:  ui.Position{engo.Point{10, 10}, ui.PositionTopLeft},
		Font:      ui.PrimaryFont,
		FgColor:   color.White,
		BgColor:   color.Transparent,
		Size:      16,
	}).AddToWorld(world)
}
<commit_msg>Add the default ship to the sandbox scene for now<commit_after>package sandbox

import (
	"arp147/clock"
	"arp147/input"
	"arp147/logging"
	"arp147/ships"
	"arp147/ui"
	"engo.io/ecs"
	"engo.io/engo"
	"engo.io/engo/common"
	"image/color"
)

type SandboxScene struct {
	ship *ships.TheGerschkin
}

func (*SandboxScene) Type() string {
	return "SandboxScene"
}

func (scene *SandboxScene) Preload() {
	// Load files needed for the scene. If we can't load the files, everything
	// will be fucked up and we should just stop.
	if err := engo.Files.Load(
		"textures/space.png",
	); err != nil {
		logging.Stderr.Fatal("Could not preload files: ", err)
	}

	// Create a new Gerschkin ship for this scene.
	scene.ship = ships.NewGerschkin()
	scene.ship.Position = ui.Position{
		engo.Point{0, 0},
		ui.PositionCenterCenter,
	}
}

func (scene *SandboxScene) Setup(world *ecs.World) {
	// Add systems needed for the scene.
	world.AddSystem(&common.RenderSystem{})
	world.AddSystem(&input.InputSystem{})
	world.AddSystem(&clock.ClockSystem{})
	world.AddSystem(&ui.LabelUpdateSystem{})

	// Tile the world background.
	ui.TileWorld(world, "textures/space.png")

	// Setup supported input for the scene
	scene.SetupInput()

	// Add HUD
	ui.NewLabel(ui.Label{
		Text:      clock.String(),
		Updatable: clock.String,
		Position:  ui.Position{engo.Point{10, 10}, ui.PositionTopLeft},
		Font:      ui.PrimaryFont,
		FgColor:   color.White,
		BgColor:   color.Transparent,
		Size:      16,
	}).AddToWorld(world)

	// Add our ship to the world
	scene.ship.AddToWorld(world)
}
<|endoftext|>


<commit_before>package dockerengine

import (
	"os"
	"testing"

	"github.com/taskcluster/taskcluster-worker/engines/enginetest"
)

// Image and tag used in test cases below
const (
	dockerImageRepository = "alpine"
	dockerImageTag        = "3.6"
)

var provider = &enginetest.EngineProvider{
	Engine: "docker",
	Config: `{}`,
}

func TestMain(m *testing.M) {
	provider.SetupEngine()
	result := 1
	func() {
		defer provider.TearDownEngine()
		result = m.Run()
	}()
	os.Exit(result)
}

func TestLogging(t *testing.T) {
	c := enginetest.LoggingTestCase{
		EngineProvider: provider,
		Target:         "hello-world",
		TargetPayload: `{
			"command": ["sh", "-c", "echo 'hello-world' && true"],
			"image": {
				"repository": "` + dockerImageRepository + `",
				"tag": "` + dockerImageTag + `"
			}
		}`,
		FailingPayload: `{
			"command": ["sh", "-c", "echo 'hello-world' && false"],
			"image": {
				"repository": "` + dockerImageRepository + `",
				"tag": "` + dockerImageTag + `"
			}
		}`,
		SilentPayload: `{
			"command": ["sh", "-c", "echo 'no hello' && true"],
			"image": {
				"repository": "` + dockerImageRepository + `",
				"tag": "` + dockerImageTag + `"
			}
		}`,
	}

	c.Test()
}

func TestKill(t *testing.T) {
	c := enginetest.KillTestCase{
		EngineProvider: provider,
		Target:         `hello-world`,
		Payload: `{
			"command": ["sh", "-c", "echo 'hello-world' && sleep 30 && true"],
			"image": {
				"repository": "` + dockerImageRepository + `",
				"tag": "` + dockerImageTag + `"
			}
		}`,
	}

	c.Test()
}

func TestEnvironmentVariables(t *testing.T) {
	c := enginetest.EnvVarTestCase{
		EngineProvider: provider,
		VariableName:   "TEST_ENV_VAR",
		InvalidVariableNames: []string{
			"#=#",
		},
		Payload: `{
			"command": ["sh", "-c", "echo $TEST_ENV_VAR && true"],
			"image": {
				"repository": "` + dockerImageRepository + `",
				"tag": "` + dockerImageTag + `"
			}
		}`,
	}

	c.Test()
}

func TestArtifacts(t *testing.T) {
	c := enginetest.ArtifactTestCase{
		EngineProvider:     provider,
		Text:               "[hello-world]",
		TextFilePath:       "folder/hello.txt",
		FileNotFoundPath:   "no-such-file.txt",
		FolderNotFoundPath: "no-such-folder/",
		NestedFolderFiles: []string{
			"hello.txt",
			"sub-folder/hello2.txt",
		},
		NestedFolderPath: "folder/",
		Payload: `{
			"command": ["sh", "-ec", "mkdir -p folder/sub-folder; echo '[hello-world]' > folder/hello.txt; echo '[hello-world]' > folder/sub-folder/hello2.txt"],
			"image": {
				"repository": "` + dockerImageRepository + `",
				"tag": "` + dockerImageTag + `"
			}
		}`,
	}

	c.Test()
}
<commit_msg>only run docker-engine tests when docker tag is set<commit_after>// +build docker

package dockerengine

import (
	"os"
	"testing"

	"github.com/taskcluster/taskcluster-worker/engines/enginetest"
)

// Image and tag used in test cases below
const (
	dockerImageRepository = "alpine"
	dockerImageTag        = "3.6"
)

var provider = &enginetest.EngineProvider{
	Engine: "docker",
	Config: `{}`,
}

func TestMain(m *testing.M) {
	provider.SetupEngine()
	result := 1
	func() {
		defer provider.TearDownEngine()
		result = m.Run()
	}()
	os.Exit(result)
}

func TestLogging(t *testing.T) {
	c := enginetest.LoggingTestCase{
		EngineProvider: provider,
		Target:         "hello-world",
		TargetPayload: `{
			"command": ["sh", "-c", "echo 'hello-world' && true"],
			"image": {
				"repository": "` + dockerImageRepository + `",
				"tag": "` + dockerImageTag + `"
			}
		}`,
		FailingPayload: `{
			"command": ["sh", "-c", "echo 'hello-world' && false"],
			"image": {
				"repository": "` + dockerImageRepository + `",
				"tag": "` + dockerImageTag + `"
			}
		}`,
		SilentPayload: `{
			"command": ["sh", "-c", "echo 'no hello' && true"],
			"image": {
				"repository": "` + dockerImageRepository + `",
				"tag": "` + dockerImageTag + `"
			}
		}`,
	}

	c.Test()
}

func TestKill(t *testing.T) {
	c := enginetest.KillTestCase{
		EngineProvider: provider,
		Target:         `hello-world`,
		Payload: `{
			"command": ["sh", "-c", "echo 'hello-world' && sleep 30 && true"],
			"image": {
				"repository": "` + dockerImageRepository + `",
				"tag": "` + dockerImageTag + `"
			}
		}`,
	}

	c.Test()
}

func TestEnvironmentVariables(t *testing.T) {
	c := enginetest.EnvVarTestCase{
		EngineProvider: provider,
		VariableName:   "TEST_ENV_VAR",
		InvalidVariableNames: []string{
			"#=#",
		},
		Payload: `{
			"command": ["sh", "-c", "echo $TEST_ENV_VAR && true"],
			"image": {
				"repository": "` + dockerImageRepository + `",
				"tag": "` + dockerImageTag + `"
			}
		}`,
	}

	c.Test()
}

func TestArtifacts(t *testing.T) {
	c := enginetest.ArtifactTestCase{
		EngineProvider:     provider,
		Text:               "[hello-world]",
		TextFilePath:       "folder/hello.txt",
		FileNotFoundPath:   "no-such-file.txt",
		FolderNotFoundPath: "no-such-folder/",
		NestedFolderFiles: []string{
			"hello.txt",
			"sub-folder/hello2.txt",
		},
		NestedFolderPath: "folder/",
		Payload: `{
			"command": ["sh", "-ec", "mkdir -p folder/sub-folder; echo '[hello-world]' > folder/hello.txt; echo '[hello-world]' > folder/sub-folder/hello2.txt"],
			"image": {
				"repository": "` + dockerImageRepository + `",
				"tag": "` + dockerImageTag + `"
			}
		}`,
	}

	c.Test()
}
<|endoftext|>