<commit_before>package common

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"unicode"

	"github.com/ryanuber/columnize"
)

type errfunc func() error

// AppRoot returns the app root path
func AppRoot(appName string) string {
	dokkuRoot := MustGetEnv("DOKKU_ROOT")
	return fmt.Sprintf("%v/%v", dokkuRoot, appName)
}

// AppHostRoot returns the app root path
func AppHostRoot(appName string) string {
	dokkuHostRoot := MustGetEnv("DOKKU_HOST_ROOT")
	return fmt.Sprintf("%v/%v", dokkuHostRoot, appName)
}

// AskForDestructiveConfirmation checks for confirmation on destructive actions
func AskForDestructiveConfirmation(name string, objectType string) error {
	LogWarn("WARNING: Potentially Destructive Action")
	LogWarn(fmt.Sprintf("This command will destroy %v %v.", objectType, name))
	LogWarn(fmt.Sprintf("To proceed, type \"%v\"", name))
	fmt.Print("> ")
	var response string
	_, err := fmt.Scanln(&response)
	if err != nil {
		return err
	}

	if response != name {
		LogStderr("Confirmation did not match test. Aborted.")
		os.Exit(1)
		return nil
	}

	return nil
}

// CommandUsage outputs help for a command
func CommandUsage(helpHeader string, helpContent string) {
	config := columnize.DefaultConfig()
	config.Delim = ","
	config.Prefix = "    "
	config.Empty = ""
	content := strings.Split(helpContent, "\n")[1:]
	fmt.Println(helpHeader)
	fmt.Println(columnize.Format(content, config))
}

// GetAppScheduler fetches the scheduler for a given application
func GetAppScheduler(appName string) string {
	if appName == "--global" {
		appName = ""
	}

	b, _ := PlugnTriggerOutput("config-get", []string{appName, "DOKKU_SCHEDULER"}...)
	value := string(b[:])
	if value != "" {
		return value
	}

	b, _ = PlugnTriggerOutput("config-get-global", []string{"DOKKU_SCHEDULER"}...)
	value = string(b[:])
	if value != "" {
		return value
	}

	return "docker-local"
}

// GetDeployingAppImageName returns deploying image identifier for a given app, tag tuple. validate if tag is presented
func GetDeployingAppImageName(appName, imageTag, imageRepo string) (imageName string) {
	if appName == "" {
		LogFail("(GetDeployingAppImageName) APP must not be empty")
	}

	b, err := PlugnTriggerOutput("deployed-app-repository", []string{appName}...)
	if err != nil {
		LogFail(err.Error())
	}
	imageRemoteRepository := string(b[:])

	b, err = PlugnTriggerOutput("deployed-app-image-tag", []string{appName}...)
	if err != nil {
		LogFail(err.Error())
	}
	newImageTag := string(b[:])

	b, err = PlugnTriggerOutput("deployed-app-image-repo", []string{appName}...)
	if err != nil {
		LogFail(err.Error())
	}
	newImageRepo := string(b[:])

	if newImageRepo != "" {
		imageRepo = newImageRepo
	}
	if newImageTag != "" {
		imageTag = newImageTag
	}
	if imageRepo == "" {
		imageRepo = GetAppImageRepo(appName)
	}
	if imageTag == "" {
		imageTag = "latest"
	}

	imageName = fmt.Sprintf("%s%s:%s", imageRemoteRepository, imageRepo, imageTag)
	if !VerifyImage(imageName) {
		LogFail(fmt.Sprintf("App image (%s) not found", imageName))
	}
	return
}

// GetAppImageRepo is the central definition of a dokku image repo pattern
func GetAppImageRepo(appName string) string {
	return strings.Join([]string{"dokku", appName}, "/")
}

// GetAppContainerIDs returns a list of docker container ids for given app and optional container_type
func GetAppContainerIDs(appName string, containerType string) ([]string, error) {
	var containerIDs []string
	if err := VerifyAppName(appName); err != nil {
		return containerIDs, err
	}

	appRoot := AppRoot(appName)
	containerFilePath := fmt.Sprintf("%v/CONTAINER", appRoot)
	_, err := os.Stat(containerFilePath)
	if !os.IsNotExist(err) {
		containerIDs = append(containerIDs, ReadFirstLine(containerFilePath))
	}

	containerPattern := fmt.Sprintf("%v/CONTAINER.*", appRoot)
	if containerType != "" {
		containerPattern = fmt.Sprintf("%v/CONTAINER.%v.*", appRoot, containerType)
		if strings.Contains(".", containerType) {
			containerPattern = fmt.Sprintf("%v/CONTAINER.%v", appRoot, containerType)
		}
	}

	files, _ := filepath.Glob(containerPattern)
	for _, containerFile := range files {
		containerIDs = append(containerIDs, ReadFirstLine(containerFile))
	}

	return containerIDs, nil
}

// GetAppRunningContainerIDs return a list of running docker container ids for given app and optional container_type
func GetAppRunningContainerIDs(appName string, containerType string) ([]string, error) {
	var runningContainerIDs []string
	if err := VerifyAppName(appName); err != nil {
		return runningContainerIDs, err
	}

	if !IsDeployed(appName) {
		LogFail(fmt.Sprintf("App %v has not been deployed", appName))
	}

	containerIDs, err := GetAppContainerIDs(appName, containerType)
	if err != nil {
		return runningContainerIDs, nil
	}
	for _, containerID := range containerIDs {
		if ContainerIsRunning(containerID) {
			runningContainerIDs = append(runningContainerIDs, containerID)
		}
	}

	return runningContainerIDs, nil
}

// GetRunningImageTag retrieves current image tag for a given app and returns empty string if no deployed containers are found
func GetRunningImageTag(appName string) (string, error) {
	if err := VerifyAppName(appName); err != nil {
		return "", err
	}

	containerIDs, err := GetAppContainerIDs(appName, "")
	if err != nil {
		return "", err
	}

	for _, containerID := range containerIDs {
		if image, err := DockerInspect(containerID, "{{ .Config.Image }}"); err == nil {
			return strings.Split(image, ":")[1], nil
		}
	}

	return "", errors.New("No image tag found")
}

// DokkuApps returns a list of all local apps
func DokkuApps() (apps []string, err error) {
	dokkuRoot := MustGetEnv("DOKKU_ROOT")
	files, err := ioutil.ReadDir(dokkuRoot)
	if err != nil {
		err = fmt.Errorf("You haven't deployed any applications yet")
		return
	}

	for _, f := range files {
		appRoot := AppRoot(f.Name())
		if !DirectoryExists(appRoot) {
			continue
		}
		if strings.HasPrefix(f.Name(), ".") {
			continue
		}
		apps = append(apps, f.Name())
	}

	if len(apps) == 0 {
		err = fmt.Errorf("You haven't deployed any applications yet")
		return
	}

	return
}

// GetAppImageName returns image identifier for a given app, tag tuple. validate if tag is presented
func GetAppImageName(appName, imageTag, imageRepo string) (imageName string) {
	err := VerifyAppName(appName)
	if err != nil {
		LogFail(err.Error())
	}

	if imageRepo == "" {
		imageRepo = GetAppImageRepo(appName)
	}

	if imageTag == "" {
		imageName = fmt.Sprintf("%v:latest", imageRepo)
	} else {
		imageName = fmt.Sprintf("%v:%v", imageRepo, imageTag)
		if !VerifyImage(imageName) {
			LogFail(fmt.Sprintf("App image (%s) not found", imageName))
		}
	}
	return
}

// IsDeployed returns true if given app has a running container
func IsDeployed(appName string) bool {
	files, err := ioutil.ReadDir(AppRoot(appName))
	if err != nil {
		return false
	}

	for _, f := range files {
		if f.Name() == "CONTAINER" || strings.HasPrefix(f.Name(), "CONTAINER.") {
			return true
		}
	}
	return false
}

// MustGetEnv returns env variable or fails if it's not set
func MustGetEnv(key string) (val string) {
	val = os.Getenv(key)
	if val == "" {
		LogFail(fmt.Sprintf("%s not set!", key))
	}
	return
}

// GetenvWithDefault returns env variable or defaultValue if it's not set
func GetenvWithDefault(key string, defaultValue string) (val string) {
	val = os.Getenv(key)
	if val == "" {
		val = defaultValue
	}
	return
}

// ReportSingleApp is an internal function that displays a report for an app
func ReportSingleApp(reportType string, appName string, infoFlag string, infoFlags map[string]string, trimPrefix bool, uppercaseFirstCharacter bool) error {
	flags := []string{}
	for key := range infoFlags {
		flags = append(flags, key)
	}
	sort.Strings(flags)

	if len(infoFlag) == 0 {
		LogInfo2Quiet(fmt.Sprintf("%s %v information", appName, reportType))
		for _, k := range flags {
			v := infoFlags[k]
			prefix := "--"
			if trimPrefix {
				prefix = fmt.Sprintf("--%v-", reportType)
			}

			key := strings.Replace(strings.Replace(strings.TrimPrefix(k, prefix), "-", " ", -1), ".", " ", -1)

			if uppercaseFirstCharacter {
				key = UcFirst(key)
			}

			LogVerbose(fmt.Sprintf("%s%s", RightPad(fmt.Sprintf("%s:", key), 31, " "), v))
		}
		return nil
	}

	for _, k := range flags {
		if infoFlag == k {
			v := infoFlags[k]
			fmt.Println(v)
			return nil
		}
	}

	keys := reflect.ValueOf(infoFlags).MapKeys()
	strkeys := make([]string, len(keys))
	for i := 0; i < len(keys); i++ {
		strkeys[i] = keys[i].String()
	}

	return fmt.Errorf("Invalid flag passed, valid flags: %s", strings.Join(strkeys, ", "))
}

// RightPad right-pads the string with pad up to len runes
func RightPad(str string, length int, pad string) string {
	return str + times(pad, length-len(str))
}

// ShiftString removes the first and returns that entry as well as the rest of the list
func ShiftString(a []string) (string, []string) {
	if len(a) == 0 {
		return "", a
	}

	return a[0], a[1:]
}

// StripInlineComments removes bash-style comment from input line
func StripInlineComments(text string) string {
	b := []byte(text)
	re := regexp.MustCompile("(?s)#.*")
	b = re.ReplaceAll(b, nil)
	return strings.TrimSpace(string(b))
}

// SuppressOutput suppresses the output of a function unless there is an error
func SuppressOutput(f errfunc) error {
	rescueStdout := os.Stdout
	r, w, _ := os.Pipe()
	os.Stdout = w

	err := f()

	w.Close()
	out, _ := ioutil.ReadAll(r)
	os.Stdout = rescueStdout

	if err != nil {
		fmt.Printf(string(out[:]))
	}

	return err
}

// ToBool returns a bool value for a given string
func ToBool(s string) bool {
	return s == "true"
}

// ToInt returns an int value for a given string
func ToInt(s string, defaultValue int) int {
	i, err := strconv.Atoi(s)
	if err != nil {
		return defaultValue
	}

	return i
}

// UcFirst uppercases the first character in a string
func UcFirst(str string) string {
	for i, v := range str {
		return string(unicode.ToUpper(v)) + str[i+1:]
	}
	return ""
}

// IsValidAppName verifies app name format
func IsValidAppName(appName string) error {
	if appName == "" {
		return fmt.Errorf("APP must not be null")
	}

	r, _ := regexp.Compile("^[a-z0-9][^/:_A-Z]*$")
	if r.MatchString(appName) {
		return nil
	}

	appRoot := AppRoot(appName)
	if DirectoryExists(appRoot) {
		os.RemoveAll(appRoot)
	}

	return errors.New("App name must begin with lowercase alphanumeric character, and cannot include uppercase characters, colons, or underscores")
}

// VerifyAppName verifies app name format and app existence"
func VerifyAppName(appName string) error {
	if err := IsValidAppName(appName); err != nil {
		return err
	}

	appRoot := AppRoot(appName)
	if !DirectoryExists(appRoot) {
		return fmt.Errorf("App %s does not exist", appName)
	}

	return nil
}

func times(str string, n int) (out string) {
	for i := 0; i < n; i++ {
		out += str
	}
	return
}
<commit_msg>fix: sync code to bash implementation<commit_after>package common

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"unicode"

	"github.com/ryanuber/columnize"
)

type errfunc func() error

// AppRoot returns the app root path
func AppRoot(appName string) string {
	dokkuRoot := MustGetEnv("DOKKU_ROOT")
	return fmt.Sprintf("%v/%v", dokkuRoot, appName)
}

// AppHostRoot returns the app root path
func AppHostRoot(appName string) string {
	dokkuHostRoot := MustGetEnv("DOKKU_HOST_ROOT")
	return fmt.Sprintf("%v/%v", dokkuHostRoot, appName)
}

// AskForDestructiveConfirmation checks for confirmation on destructive actions
func AskForDestructiveConfirmation(name string, objectType string) error {
	LogWarn("WARNING: Potentially Destructive Action")
	LogWarn(fmt.Sprintf("This command will destroy %v %v.", objectType, name))
	LogWarn(fmt.Sprintf("To proceed, type \"%v\"", name))
	fmt.Print("> ")
	var response string
	_, err := fmt.Scanln(&response)
	if err != nil {
		return err
	}

	if response != name {
		LogStderr("Confirmation did not match test. Aborted.")
		os.Exit(1)
		return nil
	}

	return nil
}

// CommandUsage outputs help for a command
func CommandUsage(helpHeader string, helpContent string) {
	config := columnize.DefaultConfig()
	config.Delim = ","
	config.Prefix = "    "
	config.Empty = ""
	content := strings.Split(helpContent, "\n")[1:]
	fmt.Println(helpHeader)
	fmt.Println(columnize.Format(content, config))
}

// GetAppScheduler fetches the scheduler for a given application
func GetAppScheduler(appName string) string {
	if appName == "--global" {
		appName = ""
	}

	b, _ := PlugnTriggerOutput("config-get", []string{appName, "DOKKU_SCHEDULER"}...)
	value := string(b[:])
	if value != "" {
		return value
	}

	b, _ = PlugnTriggerOutput("config-get-global", []string{"DOKKU_SCHEDULER"}...)
	value = string(b[:])
	if value != "" {
		return value
	}

	return "docker-local"
}

// GetDeployingAppImageName returns deploying image identifier for a given app, tag tuple. validate if tag is presented
func GetDeployingAppImageName(appName, imageTag, imageRepo string) (imageName string) {
	if appName == "" {
		LogFail("(GetDeployingAppImageName) APP must not be empty")
	}

	b, err := PlugnTriggerOutput("deployed-app-repository", []string{appName}...)
	if err != nil {
		LogFail(err.Error())
	}
	imageRemoteRepository := string(b[:])

	b, err = PlugnTriggerOutput("deployed-app-image-tag", []string{appName}...)
	if err != nil {
		LogFail(err.Error())
	}
	newImageTag := string(b[:])

	b, err = PlugnTriggerOutput("deployed-app-image-repo", []string{appName}...)
	if err != nil {
		LogFail(err.Error())
	}
	newImageRepo := string(b[:])

	if newImageRepo != "" {
		imageRepo = newImageRepo
	}
	if newImageTag != "" {
		imageTag = newImageTag
	}
	if imageRepo == "" {
		imageRepo = GetAppImageRepo(appName)
	}
	if imageTag == "" {
		imageTag = "latest"
	}

	imageName = fmt.Sprintf("%s%s:%s", imageRemoteRepository, imageRepo, imageTag)
	if !VerifyImage(imageName) {
		LogFail(fmt.Sprintf("App image (%s) not found", imageName))
	}
	return
}

// GetAppImageRepo is the central definition of a dokku image repo pattern
func GetAppImageRepo(appName string) string {
	return strings.Join([]string{"dokku", appName}, "/")
}

// GetAppContainerIDs returns a list of docker container ids for given app and optional container_type
func GetAppContainerIDs(appName string, containerType string) ([]string, error) {
	var containerIDs []string
	if err := VerifyAppName(appName); err != nil {
		return containerIDs, err
	}

	appRoot := AppRoot(appName)
	containerFilePath := fmt.Sprintf("%v/CONTAINER", appRoot)
	_, err := os.Stat(containerFilePath)
	if !os.IsNotExist(err) {
		containerIDs = append(containerIDs, ReadFirstLine(containerFilePath))
	}

	containerPattern := fmt.Sprintf("%v/CONTAINER.*", appRoot)
	if containerType != "" {
		containerPattern = fmt.Sprintf("%v/CONTAINER.%v.*", appRoot, containerType)
		if strings.Contains(".", containerType) {
			containerPattern = fmt.Sprintf("%v/CONTAINER.%v", appRoot, containerType)
		}
	}

	files, _ := filepath.Glob(containerPattern)
	for _, containerFile := range files {
		containerIDs = append(containerIDs, ReadFirstLine(containerFile))
	}

	return containerIDs, nil
}

// GetAppRunningContainerIDs return a list of running docker container ids for given app and optional container_type
func GetAppRunningContainerIDs(appName string, containerType string) ([]string, error) {
	var runningContainerIDs []string
	if err := VerifyAppName(appName); err != nil {
		return runningContainerIDs, err
	}

	if !IsDeployed(appName) {
		LogFail(fmt.Sprintf("App %v has not been deployed", appName))
	}

	containerIDs, err := GetAppContainerIDs(appName, containerType)
	if err != nil {
		return runningContainerIDs, nil
	}
	for _, containerID := range containerIDs {
		if ContainerIsRunning(containerID) {
			runningContainerIDs = append(runningContainerIDs, containerID)
		}
	}

	return runningContainerIDs, nil
}

// GetRunningImageTag retrieves current image tag for a given app and returns empty string if no deployed containers are found
func GetRunningImageTag(appName string) (string, error) {
	if err := VerifyAppName(appName); err != nil {
		return "", err
	}

	containerIDs, err := GetAppContainerIDs(appName, "")
	if err != nil {
		return "", err
	}

	for _, containerID := range containerIDs {
		if image, err := DockerInspect(containerID, "{{ .Config.Image }}"); err == nil {
			return strings.Split(image, ":")[1], nil
		}
	}

	return "", errors.New("No image tag found")
}

// DokkuApps returns a list of all local apps
func DokkuApps() (apps []string, err error) {
	dokkuRoot := MustGetEnv("DOKKU_ROOT")
	files, err := ioutil.ReadDir(dokkuRoot)
	if err != nil {
		err = fmt.Errorf("You haven't deployed any applications yet")
		return
	}

	for _, f := range files {
		appRoot := AppRoot(f.Name())
		if !DirectoryExists(appRoot) {
			continue
		}
		if strings.HasPrefix(f.Name(), ".") {
			continue
		}
		apps = append(apps, f.Name())
	}

	if len(apps) == 0 {
		err = fmt.Errorf("You haven't deployed any applications yet")
		return
	}

	return
}

// GetAppImageName returns image identifier for a given app, tag tuple. validate if tag is presented
func GetAppImageName(appName, imageTag, imageRepo string) (imageName string) {
	err := VerifyAppName(appName)
	if err != nil {
		LogFail(err.Error())
	}

	if imageRepo == "" {
		imageRepo = GetAppImageRepo(appName)
	}

	if imageTag == "" {
		imageName = fmt.Sprintf("%v:latest", imageRepo)
	} else {
		imageName = fmt.Sprintf("%v:%v", imageRepo, imageTag)
		if !VerifyImage(imageName) {
			LogFail(fmt.Sprintf("App image (%s) not found", imageName))
		}
	}
	return
}

// IsDeployed returns true if given app has a running container
func IsDeployed(appName string) bool {
	scheduler := GetAppScheduler(appName)
	_, err := PlugnTriggerOutput("scheduler-is-deployed", []string{scheduler, appName}...)
	return err == nil
}

// MustGetEnv returns env variable or fails if it's not set
func MustGetEnv(key string) (val string) {
	val = os.Getenv(key)
	if val == "" {
		LogFail(fmt.Sprintf("%s not set!", key))
	}
	return
}

// GetenvWithDefault returns env variable or defaultValue if it's not set
func GetenvWithDefault(key string, defaultValue string) (val string) {
	val = os.Getenv(key)
	if val == "" {
		val = defaultValue
	}
	return
}

// ReportSingleApp is an internal function that displays a report for an app
func ReportSingleApp(reportType string, appName string, infoFlag string, infoFlags map[string]string, trimPrefix bool, uppercaseFirstCharacter bool) error {
	flags := []string{}
	for key := range infoFlags {
		flags = append(flags, key)
	}
	sort.Strings(flags)

	if len(infoFlag) == 0 {
		LogInfo2Quiet(fmt.Sprintf("%s %v information", appName, reportType))
		for _, k := range flags {
			v := infoFlags[k]
			prefix := "--"
			if trimPrefix {
				prefix = fmt.Sprintf("--%v-", reportType)
			}

			key := strings.Replace(strings.Replace(strings.TrimPrefix(k, prefix), "-", " ", -1), ".", " ", -1)

			if uppercaseFirstCharacter {
				key = UcFirst(key)
			}

			LogVerbose(fmt.Sprintf("%s%s", RightPad(fmt.Sprintf("%s:", key), 31, " "), v))
		}
		return nil
	}

	for _, k := range flags {
		if infoFlag == k {
			v := infoFlags[k]
			fmt.Println(v)
			return nil
		}
	}

	keys := reflect.ValueOf(infoFlags).MapKeys()
	strkeys := make([]string, len(keys))
	for i := 0; i < len(keys); i++ {
		strkeys[i] = keys[i].String()
	}

	return fmt.Errorf("Invalid flag passed, valid flags: %s", strings.Join(strkeys, ", "))
}

// RightPad right-pads the string with pad up to len runes
func RightPad(str string, length int, pad string) string {
	return str + times(pad, length-len(str))
}

// ShiftString removes the first and returns that entry as well as the rest of the list
func ShiftString(a []string) (string, []string) {
	if len(a) == 0 {
		return "", a
	}

	return a[0], a[1:]
}

// StripInlineComments removes bash-style comment from input line
func StripInlineComments(text string) string {
	b := []byte(text)
	re := regexp.MustCompile("(?s)#.*")
	b = re.ReplaceAll(b, nil)
	return strings.TrimSpace(string(b))
}

// SuppressOutput suppresses the output of a function unless there is an error
func SuppressOutput(f errfunc) error {
	rescueStdout := os.Stdout
	r, w, _ := os.Pipe()
	os.Stdout = w

	err := f()

	w.Close()
	out, _ := ioutil.ReadAll(r)
	os.Stdout = rescueStdout

	if err != nil {
		fmt.Printf(string(out[:]))
	}

	return err
}

// ToBool returns a bool value for a given string
func ToBool(s string) bool {
	return s == "true"
}

// ToInt returns an int value for a given string
func ToInt(s string, defaultValue int) int {
	i, err := strconv.Atoi(s)
	if err != nil {
		return defaultValue
	}

	return i
}

// UcFirst uppercases the first character in a string
func UcFirst(str string) string {
	for i, v := range str {
		return string(unicode.ToUpper(v)) + str[i+1:]
	}
	return ""
}

// IsValidAppName verifies app name format
func IsValidAppName(appName string) error {
	if appName == "" {
		return fmt.Errorf("APP must not be null")
	}

	r, _ := regexp.Compile("^[a-z0-9][^/:_A-Z]*$")
	if r.MatchString(appName) {
		return nil
	}

	appRoot := AppRoot(appName)
	if DirectoryExists(appRoot) {
		os.RemoveAll(appRoot)
	}

	return errors.New("App name must begin with lowercase alphanumeric character, and cannot include uppercase characters, colons, or underscores")
}

// VerifyAppName verifies app name format and app existence"
func VerifyAppName(appName string) error {
	if err := IsValidAppName(appName); err != nil {
		return err
	}

	appRoot := AppRoot(appName)
	if !DirectoryExists(appRoot) {
		return fmt.Errorf("App %s does not exist", appName)
	}

	return nil
}

func times(str string, n int) (out string) {
	for i := 0; i < n; i++ {
		out += str
	}
	return
}
<|endoftext|>


<commit_before>// Copyright (c) 2018-2018 Laurent Moussault. All rights reserved.
// Licensed under a simplified BSD license (see LICENSE file).

package pixel_test

import (
	"bufio"
	"os"
	"testing"

	"github.com/drakmaniso/glam/key"

	"github.com/drakmaniso/glam"
	"github.com/drakmaniso/glam/palette"
	"github.com/drakmaniso/glam/pixel"
)

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

var (
	tinela9    = pixel.NewFont("fonts/tinela9")
	monozela10 = pixel.NewFont("fonts/monozela10")
	simpela10  = pixel.NewFont("fonts/simpela10")
	simpela12  = pixel.NewFont("fonts/simpela12")
	cozela10   = pixel.NewFont("fonts/cozela10")
	cozela12   = pixel.NewFont("fonts/cozela12")
	chaotela12  = pixel.NewFont("fonts/chaotela12")
	font       = monozela10
)

var (
	fntInterline     = int16(18)
	fntLetterSpacing = int16(0)
)

var (
	fntText []string
	fntCode []string
	fntShow []string
	fntLine int
)

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

func TestFont_load(t *testing.T) {
	do(func() {
		err := glam.Run(fntLoop{})
		if err != nil {
			t.Error(err)
		}
	})
}

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

type fntLoop struct {
	glam.Handlers
}

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

func (fntLoop) Enter() error {
	f, err := os.Open(glam.Path() + "frankenstein_full.txt")
	if err != nil {
		return err
	}
	s := bufio.NewScanner(f)
	for s.Scan() {
		fntText = append(fntText, s.Text())
	}
	f, err = os.Open(glam.Path() + "sourcecode.txt")
	if err != nil {
		return err
	}
	s = bufio.NewScanner(f)
	for s.Scan() {
		fntCode = append(fntCode, s.Text())
	}
	fntShow = fntText
	palette.Load("C64")
	curBg = palette.Find("white")
	curFg = palette.Find("black")
	cursor.ColorShift(curFg - 1)
	return nil
}

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

func (fntLoop) Update() error {
	return nil
}

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

func (fntLoop) Draw() error {
	curScreen.Clear(curBg)

	cursor.Locate(16, font.Height()+2, 0)

	cursor.Font(font)
	cursor.LetterSpacing(fntLetterSpacing)
	// cursor.Interline(fntInterline)

	_, y, _ := cursor.Position()

	for l := fntLine; l < len(fntShow) && y < curScreen.Size().Y; l++ {
		cursor.Println(fntShow[l])
		_, y, _ = cursor.Position()
	}

	cursor.Locate(curScreen.Size().X-96, 16, 0)
	cursor.Printf("Line %d", fntLine)

	curScreen.Display()
	return nil
}

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

func (fl fntLoop) KeyDown(l key.Label, p key.Position) {
	switch l {
	case key.LabelSpace:
		if fntShow[0] == fntText[0] {
			fntShow = fntCode
			curBg = palette.Find("black")
			curFg = palette.Find("green")
		} else {
			fntShow = fntText
			curBg = palette.Find("white")
			curFg = palette.Find("black")
		}
		cursor.ColorShift(curFg - 1)
		fntLine = 0
	case key.Label1:
		font = tinela9
	case key.Label2:
		font = monozela10
	case key.Label3:
		font = simpela10
	case key.Label4:
		font = cozela10
	case key.Label5:
		font = simpela12
	case key.Label6:
		font = cozela12
	case key.Label7:
		font = chaotela12
	case key.Label0:
		font = pixel.Font(0)
	case key.LabelKPDivide:
		fntLetterSpacing--
	case key.LabelKPMultiply:
		fntLetterSpacing++
	case key.LabelKPMinus:
		fntInterline--
	case key.LabelKPPlus:
		fntInterline++
	case key.LabelPageDown:
		fntLine += 40
		if fntLine > len(fntShow) - 1 {
			fntLine = len(fntShow) - 1
		}
	case key.LabelPageUp:
		fntLine -= 40
		if fntLine < 0 {
			fntLine = 0
		}
	default:
		fl.Handlers.KeyDown(l, p)
	}
}

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

func (fntLoop) MouseWheel(_, dy int32) {
	fntLine -= int(dy)
	if fntLine < 0 {
		fntLine = 0
	} else if fntLine > len(fntShow)-1 {
		fntLine = len(fntShow) - 1
	}
}

//------------------------------------------------------------------------------
<commit_msg>Fix test file<commit_after>// Copyright (c) 2018-2018 Laurent Moussault. All rights reserved.
// Licensed under a simplified BSD license (see LICENSE file).

package pixel_test

import (
	"bufio"
	"os"
	"testing"

	"github.com/drakmaniso/glam/key"

	"github.com/drakmaniso/glam"
	"github.com/drakmaniso/glam/palette"
	"github.com/drakmaniso/glam/pixel"
)

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

var (
	tinela9    = pixel.NewFont("fonts/tinela9")
	monozela10 = pixel.NewFont("fonts/monozela10")
	simpela10  = pixel.NewFont("fonts/simpela10")
	simpela12  = pixel.NewFont("fonts/simpela12")
	cozela10   = pixel.NewFont("fonts/cozela10")
	cozela12   = pixel.NewFont("fonts/cozela12")
	chaotela12 = pixel.NewFont("fonts/chaotela12")
	font       = monozela10
)

var (
	fntInterline     = int16(18)
	fntLetterSpacing = int16(0)
)

var (
	fntText []string
	fntCode []string
	fntShow []string
	fntLine int
)

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

func TestFont_load(t *testing.T) {
	do(func() {
		err := glam.Run(fntLoop{})
		if err != nil {
			t.Error(err)
		}
	})
}

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

type fntLoop struct {
	glam.Handlers
}

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

func (fntLoop) Enter() error {
	f, err := os.Open(glam.Path() + "frankenstein.txt")
	if err != nil {
		return err
	}
	s := bufio.NewScanner(f)
	for s.Scan() {
		fntText = append(fntText, s.Text())
	}
	f, err = os.Open(glam.Path() + "sourcecode.txt")
	if err != nil {
		return err
	}
	s = bufio.NewScanner(f)
	for s.Scan() {
		fntCode = append(fntCode, s.Text())
	}
	fntShow = fntText
	palette.Load("C64")
	curBg = palette.Find("white")
	curFg = palette.Find("black")
	cursor.ColorShift(curFg - 1)
	return nil
}

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

func (fntLoop) Update() error {
	return nil
}

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

func (fntLoop) Draw() error {
	curScreen.Clear(curBg)

	cursor.Locate(16, font.Height()+2, 0)

	cursor.Font(font)
	cursor.LetterSpacing(fntLetterSpacing)
	// cursor.Interline(fntInterline)

	_, y, _ := cursor.Position()

	for l := fntLine; l < len(fntShow) && y < curScreen.Size().Y; l++ {
		cursor.Println(fntShow[l])
		_, y, _ = cursor.Position()
	}

	cursor.Locate(curScreen.Size().X-96, 16, 0)
	cursor.Printf("Line %d", fntLine)

	curScreen.Display()
	return nil
}

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

func (fl fntLoop) KeyDown(l key.Label, p key.Position) {
	switch l {
	case key.LabelSpace:
		if fntShow[0] == fntText[0] {
			fntShow = fntCode
			curBg = palette.Find("black")
			curFg = palette.Find("green")
		} else {
			fntShow = fntText
			curBg = palette.Find("white")
			curFg = palette.Find("black")
		}
		cursor.ColorShift(curFg - 1)
		fntLine = 0
	case key.Label1:
		font = tinela9
	case key.Label2:
		font = monozela10
	case key.Label3:
		font = simpela10
	case key.Label4:
		font = cozela10
	case key.Label5:
		font = simpela12
	case key.Label6:
		font = cozela12
	case key.Label7:
		font = chaotela12
	case key.Label0:
		font = pixel.Font(0)
	case key.LabelKPDivide:
		fntLetterSpacing--
	case key.LabelKPMultiply:
		fntLetterSpacing++
	case key.LabelKPMinus:
		fntInterline--
	case key.LabelKPPlus:
		fntInterline++
	case key.LabelPageDown:
		fntLine += 40
		if fntLine > len(fntShow)-1 {
			fntLine = len(fntShow) - 1
		}
	case key.LabelPageUp:
		fntLine -= 40
		if fntLine < 0 {
			fntLine = 0
		}
	default:
		fl.Handlers.KeyDown(l, p)
	}
}

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

func (fntLoop) MouseWheel(_, dy int32) {
	fntLine -= int(dy)
	if fntLine < 0 {
		fntLine = 0
	} else if fntLine > len(fntShow)-1 {
		fntLine = len(fntShow) - 1
	}
}

//------------------------------------------------------------------------------
<|endoftext|>


<commit_before>// (c) Copyright IBM Corp. 2021
// (c) Copyright Instana Inc. 2020

package instana

import (
	"bufio"
	"context"
	"mime/multipart"
	"net"
	"net/http"
	"net/textproto"
	"net/url"

	ot "github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
	otlog "github.com/opentracing/opentracing-go/log"
)

// TracingHandlerFunc is an HTTP middleware that captures the tracing data and ensures
// trace context propagation via OpenTracing headers. The pathTemplate parameter, when provided,
// will be added to the span as a template string used to match the route containing variables, regular
// expressions, etc.
//
// The wrapped handler will also propagate the W3C trace context (https://www.w3.org/TR/trace-context/)
// if found in request.
func TracingHandlerFunc(sensor *Sensor, pathTemplate string, handler http.HandlerFunc) http.HandlerFunc {
	return TracingNamedHandlerFunc(sensor, "", pathTemplate, handler)
}

// TracingNamedHandlerFunc is an HTTP middleware that similarly to instana.TracingHandlerFunc() captures the tracing data,
// while allowing to provide a unique route indetifier to be associated with each request.
func TracingNamedHandlerFunc(sensor *Sensor, routeID, pathTemplate string, handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		ctx := req.Context()

		opts := []ot.StartSpanOption{
			ext.SpanKindRPCServer,
			ot.Tags{
				"http.host":     req.Host,
				"http.method":   req.Method,
				"http.protocol": req.URL.Scheme,
				"http.path":     req.URL.Path,
				"http.route_id": routeID,
			},
		}

		tracer := sensor.Tracer()
		if ps, ok := SpanFromContext(req.Context()); ok {
			tracer = ps.Tracer()
			opts = append(opts, ot.ChildOf(ps.Context()))
		}

		wireContext, err := tracer.Extract(ot.HTTPHeaders, ot.HTTPHeadersCarrier(req.Header))
		switch err {
		case nil:
			opts = append(opts, ext.RPCServerOption(wireContext))
		case ot.ErrSpanContextNotFound:
			sensor.Logger().Debug("no span context provided with ", req.Method, " ", req.URL.Path)
		case ot.ErrUnsupportedFormat:
			sensor.Logger().Info("unsupported span context format provided with ", req.Method, " ", req.URL.Path)
		default:
			sensor.Logger().Warn("failed to extract span context from the request:", err)
		}

		if req.Header.Get(FieldSynthetic) == "1" {
			opts = append(opts, syntheticCall())
		}

		if pathTemplate != "" && req.URL.Path != pathTemplate {
			opts = append(opts, ot.Tag{Key: "http.path_tpl", Value: pathTemplate})
		}

		span := tracer.StartSpan("g.http", opts...)
		defer span.Finish()

		var collectableHTTPHeaders []string
		if t, ok := tracer.(Tracer); ok {
			opts := t.Options()
			collectableHTTPHeaders = opts.CollectableHTTPHeaders

			params := collectHTTPParams(req, opts.Secrets)
			if len(params) > 0 {
				span.SetTag("http.params", params.Encode())
			}
		}

		collectedHeaders := make(map[string]string)
		// make sure collected headers are sent in case of panic/error
		defer func() {
			if len(collectedHeaders) > 0 {
				span.SetTag("http.header", collectedHeaders)
			}
		}()

		// collect request headers
		for _, h := range collectableHTTPHeaders {
			if v := req.Header.Get(h); v != "" {
				collectedHeaders[h] = v
			}
		}

		defer func() {
			// Be sure to capture any kind of panic/error
			if err := recover(); err != nil {
				if e, ok := err.(error); ok {
					span.SetTag("http.error", e.Error())
					span.LogFields(otlog.Error(e))
				} else {
					span.SetTag("http.error", err)
					span.LogFields(otlog.Object("error", err))
				}

				span.SetTag(string(ext.HTTPStatusCode), http.StatusInternalServerError)

				// re-throw the panic
				panic(err)
			}
		}()

		wrapped := &statusCodeRecorder{ResponseWriter: w}
		tracer.Inject(span.Context(), ot.HTTPHeaders, ot.HTTPHeadersCarrier(wrapped.Header()))

		handler(wrapped, req.WithContext(ContextWithSpan(ctx, span)))

		// collect response headers
		for _, h := range collectableHTTPHeaders {
			if v := wrapped.Header().Get(h); v != "" {
				collectedHeaders[h] = v
			}
		}

		if wrapped.Status > 0 {
			if wrapped.Status >= http.StatusInternalServerError {
				statusText := http.StatusText(wrapped.Status)

				span.SetTag("http.error", statusText)
				span.LogFields(otlog.Object("error", statusText))
			}

			span.SetTag("http.status", wrapped.Status)
		}
	}
}

// RoundTripper wraps an existing http.RoundTripper and injects the tracing headers into the outgoing request.
// If the original RoundTripper is nil, the http.DefaultTransport will be used.
func RoundTripper(sensor *Sensor, original http.RoundTripper) http.RoundTripper {
	return tracingRoundTripper(func(req *http.Request) (*http.Response, error) {
		if original == nil {
			original = http.DefaultTransport
		}

		ctx := req.Context()
		parentSpan, ok := SpanFromContext(ctx)
		if !ok {
			// don't trace the exit call if there was no entry span provided
			return original.RoundTrip(req)
		}

		sanitizedURL := cloneURL(req.URL)
		sanitizedURL.RawQuery = ""
		sanitizedURL.User = nil

		span := sensor.Tracer().StartSpan("http",
			ext.SpanKindRPCClient,
			ot.ChildOf(parentSpan.Context()),
			ot.Tags{
				"http.url":    sanitizedURL.String(),
				"http.method": req.Method,
			})
		defer span.Finish()

		// clone the request since the RoundTrip should not modify the original one
		req = cloneRequest(ContextWithSpan(ctx, span), req)
		sensor.Tracer().Inject(span.Context(), ot.HTTPHeaders, ot.HTTPHeadersCarrier(req.Header))

		var collectableHTTPHeaders []string
		if t, ok := sensor.Tracer().(Tracer); ok {
			opts := t.Options()
			collectableHTTPHeaders = opts.CollectableHTTPHeaders

			params := collectHTTPParams(req, opts.Secrets)
			if len(params) > 0 {
				span.SetTag("http.params", params.Encode())
			}
		}

		collectedHeaders := make(map[string]string)
		// make sure collected headers are sent in case of panic/error
		defer func() {
			if len(collectedHeaders) > 0 {
				span.SetTag("http.header", collectedHeaders)
			}
		}()

		// collect request headers
		for _, h := range collectableHTTPHeaders {
			if v := req.Header.Get(h); v != "" {
				collectedHeaders[h] = v
			}
		}

		resp, err := original.RoundTrip(req)
		if err != nil {
			span.SetTag("http.error", err.Error())
			span.LogFields(otlog.Error(err))
			return resp, err
		}

		// collect response headers
		for _, h := range collectableHTTPHeaders {
			if v := resp.Header.Get(h); v != "" {
				collectedHeaders[h] = v
			}
		}

		span.SetTag(string(ext.HTTPStatusCode), resp.StatusCode)

		return resp, err
	})
}

// statusCodeRecorder is a wrapper over http.ResponseWriter to spy the returned status code
type statusCodeRecorder struct {
	http.ResponseWriter
	Status int
}

func (rec *statusCodeRecorder) SetStatus(status int) {
	rec.Status = status
}

func (rec *statusCodeRecorder) WriteHeader(status int) {
	rec.SetStatus(status)
	rec.ResponseWriter.WriteHeader(status)
}

func (rec *statusCodeRecorder) Write(b []byte) (int, error) {
	if rec.Status == 0 {
		rec.SetStatus(http.StatusOK)
	}

	return rec.ResponseWriter.Write(b)
}

// statusCodeRecorderHTTP10 is a wrapper over http.ResponseWriter similar to statusCodeRecorder, but
// also implementing http.Hijaker
type statusCodeRecorderHTTP10 = statusCodeRecorder

func (rec *statusCodeRecorderHTTP10) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return rec.ResponseWriter.(http.Hijacker).Hijack()
}

type tracingRoundTripper func(*http.Request) (*http.Response, error)

func (rt tracingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	return rt(req)
}

func collectHTTPParams(req *http.Request, matcher Matcher) url.Values {
	params := cloneURLValues(req.URL.Query())

	for k := range params {
		if matcher.Match(k) {
			params[k] = []string{"<redacted>"}
		}
	}

	return params
}

// The following code is ported from $GOROOT/src/net/http/clone.go with minor changes
// for compatibility with Go versions prior to 1.13
//
// Copyright 2019 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.

func cloneRequest(ctx context.Context, r *http.Request) *http.Request {
	r2 := new(http.Request)
	*r2 = *r
	r2 = r2.WithContext(ctx)

	r2.URL = cloneURL(r.URL)
	if r.Header != nil {
		r2.Header = cloneHeader(r.Header)
	}

	if r.Trailer != nil {
		r2.Trailer = cloneHeader(r.Trailer)
	}

	if s := r.TransferEncoding; s != nil {
		s2 := make([]string, len(s))
		copy(s2, s)
		r2.TransferEncoding = s
	}

	r2.Form = cloneURLValues(r.Form)
	r2.PostForm = cloneURLValues(r.PostForm)
	r2.MultipartForm = cloneMultipartForm(r.MultipartForm)

	return r2
}

func cloneURLValues(v url.Values) url.Values {
	if v == nil {
		return nil
	}

	// http.Header and url.Values have the same representation, so temporarily
	// treat it like http.Header, which does have a clone:

	return url.Values(cloneHeader(http.Header(v)))
}

func cloneURL(u *url.URL) *url.URL {
	if u == nil {
		return nil
	}

	u2 := new(url.URL)
	*u2 = *u

	if u.User != nil {
		u2.User = new(url.Userinfo)
		*u2.User = *u.User
	}

	return u2
}

func cloneMultipartForm(f *multipart.Form) *multipart.Form {
	if f == nil {
		return nil
	}

	f2 := &multipart.Form{
		Value: (map[string][]string)(cloneHeader(http.Header(f.Value))),
	}

	if f.File != nil {
		m := make(map[string][]*multipart.FileHeader)
		for k, vv := range f.File {
			vv2 := make([]*multipart.FileHeader, len(vv))
			for i, v := range vv {
				vv2[i] = cloneMultipartFileHeader(v)
			}
			m[k] = vv2

		}

		f2.File = m
	}

	return f2
}

func cloneMultipartFileHeader(fh *multipart.FileHeader) *multipart.FileHeader {
	if fh == nil {
		return nil
	}

	fh2 := new(multipart.FileHeader)
	*fh2 = *fh

	fh2.Header = textproto.MIMEHeader(cloneHeader(http.Header(fh.Header)))

	return fh2
}

// The following code is ported from $GOROOT/src/net/http/header.go with minor changes
// for compatibility with Go versions prior to 1.13
//
// Copyright 2019 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.

func cloneHeader(h http.Header) http.Header {
	if h == nil {
		return nil
	}

	// Find total number of values.
	nv := 0
	for _, vv := range h {
		nv += len(vv)
	}
	sv := make([]string, nv) // shared backing array for headers' values
	h2 := make(http.Header, len(h))
	for k, vv := range h {
		n := copy(sv, vv)
		h2[k] = sv[:n:n]
		sv = sv[n:]
	}
	return h2
}
<commit_msg>Make sure the HTTP wrapper implements http.Hijacker if the underlying http.ResponseWriter does<commit_after>// (c) Copyright IBM Corp. 2021
// (c) Copyright Instana Inc. 2020

package instana

import (
	"bufio"
	"context"
	"mime/multipart"
	"net"
	"net/http"
	"net/textproto"
	"net/url"

	ot "github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
	otlog "github.com/opentracing/opentracing-go/log"
)

// TracingHandlerFunc is an HTTP middleware that captures the tracing data and ensures
// trace context propagation via OpenTracing headers. The pathTemplate parameter, when provided,
// will be added to the span as a template string used to match the route containing variables, regular
// expressions, etc.
//
// The wrapped handler will also propagate the W3C trace context (https://www.w3.org/TR/trace-context/)
// if found in request.
func TracingHandlerFunc(sensor *Sensor, pathTemplate string, handler http.HandlerFunc) http.HandlerFunc {
	return TracingNamedHandlerFunc(sensor, "", pathTemplate, handler)
}

// TracingNamedHandlerFunc is an HTTP middleware that similarly to instana.TracingHandlerFunc() captures the tracing data,
// while allowing to provide a unique route indetifier to be associated with each request.
func TracingNamedHandlerFunc(sensor *Sensor, routeID, pathTemplate string, handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		ctx := req.Context()

		opts := []ot.StartSpanOption{
			ext.SpanKindRPCServer,
			ot.Tags{
				"http.host":     req.Host,
				"http.method":   req.Method,
				"http.protocol": req.URL.Scheme,
				"http.path":     req.URL.Path,
				"http.route_id": routeID,
			},
		}

		tracer := sensor.Tracer()
		if ps, ok := SpanFromContext(req.Context()); ok {
			tracer = ps.Tracer()
			opts = append(opts, ot.ChildOf(ps.Context()))
		}

		wireContext, err := tracer.Extract(ot.HTTPHeaders, ot.HTTPHeadersCarrier(req.Header))
		switch err {
		case nil:
			opts = append(opts, ext.RPCServerOption(wireContext))
		case ot.ErrSpanContextNotFound:
			sensor.Logger().Debug("no span context provided with ", req.Method, " ", req.URL.Path)
		case ot.ErrUnsupportedFormat:
			sensor.Logger().Info("unsupported span context format provided with ", req.Method, " ", req.URL.Path)
		default:
			sensor.Logger().Warn("failed to extract span context from the request:", err)
		}

		if req.Header.Get(FieldSynthetic) == "1" {
			opts = append(opts, syntheticCall())
		}

		if pathTemplate != "" && req.URL.Path != pathTemplate {
			opts = append(opts, ot.Tag{Key: "http.path_tpl", Value: pathTemplate})
		}

		span := tracer.StartSpan("g.http", opts...)
		defer span.Finish()

		var collectableHTTPHeaders []string
		if t, ok := tracer.(Tracer); ok {
			opts := t.Options()
			collectableHTTPHeaders = opts.CollectableHTTPHeaders

			params := collectHTTPParams(req, opts.Secrets)
			if len(params) > 0 {
				span.SetTag("http.params", params.Encode())
			}
		}

		collectedHeaders := make(map[string]string)
		// make sure collected headers are sent in case of panic/error
		defer func() {
			if len(collectedHeaders) > 0 {
				span.SetTag("http.header", collectedHeaders)
			}
		}()

		// collect request headers
		for _, h := range collectableHTTPHeaders {
			if v := req.Header.Get(h); v != "" {
				collectedHeaders[h] = v
			}
		}

		defer func() {
			// Be sure to capture any kind of panic/error
			if err := recover(); err != nil {
				if e, ok := err.(error); ok {
					span.SetTag("http.error", e.Error())
					span.LogFields(otlog.Error(e))
				} else {
					span.SetTag("http.error", err)
					span.LogFields(otlog.Object("error", err))
				}

				span.SetTag(string(ext.HTTPStatusCode), http.StatusInternalServerError)

				// re-throw the panic
				panic(err)
			}
		}()

		wrapped := wrapResponseWriter(w)
		tracer.Inject(span.Context(), ot.HTTPHeaders, ot.HTTPHeadersCarrier(wrapped.Header()))

		handler(wrapped, req.WithContext(ContextWithSpan(ctx, span)))

		// collect response headers
		for _, h := range collectableHTTPHeaders {
			if v := wrapped.Header().Get(h); v != "" {
				collectedHeaders[h] = v
			}
		}

		if wrapped.Status() > 0 {
			if wrapped.Status() >= http.StatusInternalServerError {
				statusText := http.StatusText(wrapped.Status())

				span.SetTag("http.error", statusText)
				span.LogFields(otlog.Object("error", statusText))
			}

			span.SetTag("http.status", wrapped.Status())
		}
	}
}

// RoundTripper wraps an existing http.RoundTripper and injects the tracing headers into the outgoing request.
// If the original RoundTripper is nil, the http.DefaultTransport will be used.
func RoundTripper(sensor *Sensor, original http.RoundTripper) http.RoundTripper {
	return tracingRoundTripper(func(req *http.Request) (*http.Response, error) {
		if original == nil {
			original = http.DefaultTransport
		}

		ctx := req.Context()
		parentSpan, ok := SpanFromContext(ctx)
		if !ok {
			// don't trace the exit call if there was no entry span provided
			return original.RoundTrip(req)
		}

		sanitizedURL := cloneURL(req.URL)
		sanitizedURL.RawQuery = ""
		sanitizedURL.User = nil

		span := sensor.Tracer().StartSpan("http",
			ext.SpanKindRPCClient,
			ot.ChildOf(parentSpan.Context()),
			ot.Tags{
				"http.url":    sanitizedURL.String(),
				"http.method": req.Method,
			})
		defer span.Finish()

		// clone the request since the RoundTrip should not modify the original one
		req = cloneRequest(ContextWithSpan(ctx, span), req)
		sensor.Tracer().Inject(span.Context(), ot.HTTPHeaders, ot.HTTPHeadersCarrier(req.Header))

		var collectableHTTPHeaders []string
		if t, ok := sensor.Tracer().(Tracer); ok {
			opts := t.Options()
			collectableHTTPHeaders = opts.CollectableHTTPHeaders

			params := collectHTTPParams(req, opts.Secrets)
			if len(params) > 0 {
				span.SetTag("http.params", params.Encode())
			}
		}

		collectedHeaders := make(map[string]string)
		// make sure collected headers are sent in case of panic/error
		defer func() {
			if len(collectedHeaders) > 0 {
				span.SetTag("http.header", collectedHeaders)
			}
		}()

		// collect request headers
		for _, h := range collectableHTTPHeaders {
			if v := req.Header.Get(h); v != "" {
				collectedHeaders[h] = v
			}
		}

		resp, err := original.RoundTrip(req)
		if err != nil {
			span.SetTag("http.error", err.Error())
			span.LogFields(otlog.Error(err))
			return resp, err
		}

		// collect response headers
		for _, h := range collectableHTTPHeaders {
			if v := resp.Header.Get(h); v != "" {
				collectedHeaders[h] = v
			}
		}

		span.SetTag(string(ext.HTTPStatusCode), resp.StatusCode)

		return resp, err
	})
}

type wrappedResponseWriter interface {
	http.ResponseWriter
	Status() int
}

func wrapResponseWriter(w http.ResponseWriter) wrappedResponseWriter {
	if _, ok := w.(http.Hijacker); ok {
		return &statusCodeRecorderHTTP10{
			ResponseWriter: w,
		}
	}

	return &statusCodeRecorder{
		ResponseWriter: w,
	}
}

// statusCodeRecorder is a wrapper over http.ResponseWriter to spy the returned status code
type statusCodeRecorder struct {
	http.ResponseWriter
	status int
}

func (rec *statusCodeRecorder) SetStatus(status int) {
	rec.status = status
}

func (rec *statusCodeRecorder) WriteHeader(status int) {
	rec.SetStatus(status)
	rec.ResponseWriter.WriteHeader(status)
}

func (rec *statusCodeRecorder) Write(b []byte) (int, error) {
	if rec.status == 0 {
		rec.SetStatus(http.StatusOK)
	}

	return rec.ResponseWriter.Write(b)
}

func (rec *statusCodeRecorder) Status() int {
	return rec.status
}

// statusCodeRecorderHTTP10 is a wrapper over http.ResponseWriter similar to statusCodeRecorder, but
// also implementing http.Hijaker
type statusCodeRecorderHTTP10 = statusCodeRecorder

func (rec *statusCodeRecorderHTTP10) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return rec.ResponseWriter.(http.Hijacker).Hijack()
}

type tracingRoundTripper func(*http.Request) (*http.Response, error)

func (rt tracingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	return rt(req)
}

func collectHTTPParams(req *http.Request, matcher Matcher) url.Values {
	params := cloneURLValues(req.URL.Query())

	for k := range params {
		if matcher.Match(k) {
			params[k] = []string{"<redacted>"}
		}
	}

	return params
}

// The following code is ported from $GOROOT/src/net/http/clone.go with minor changes
// for compatibility with Go versions prior to 1.13
//
// Copyright 2019 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.

func cloneRequest(ctx context.Context, r *http.Request) *http.Request {
	r2 := new(http.Request)
	*r2 = *r
	r2 = r2.WithContext(ctx)

	r2.URL = cloneURL(r.URL)
	if r.Header != nil {
		r2.Header = cloneHeader(r.Header)
	}

	if r.Trailer != nil {
		r2.Trailer = cloneHeader(r.Trailer)
	}

	if s := r.TransferEncoding; s != nil {
		s2 := make([]string, len(s))
		copy(s2, s)
		r2.TransferEncoding = s
	}

	r2.Form = cloneURLValues(r.Form)
	r2.PostForm = cloneURLValues(r.PostForm)
	r2.MultipartForm = cloneMultipartForm(r.MultipartForm)

	return r2
}

func cloneURLValues(v url.Values) url.Values {
	if v == nil {
		return nil
	}

	// http.Header and url.Values have the same representation, so temporarily
	// treat it like http.Header, which does have a clone:

	return url.Values(cloneHeader(http.Header(v)))
}

func cloneURL(u *url.URL) *url.URL {
	if u == nil {
		return nil
	}

	u2 := new(url.URL)
	*u2 = *u

	if u.User != nil {
		u2.User = new(url.Userinfo)
		*u2.User = *u.User
	}

	return u2
}

func cloneMultipartForm(f *multipart.Form) *multipart.Form {
	if f == nil {
		return nil
	}

	f2 := &multipart.Form{
		Value: (map[string][]string)(cloneHeader(http.Header(f.Value))),
	}

	if f.File != nil {
		m := make(map[string][]*multipart.FileHeader)
		for k, vv := range f.File {
			vv2 := make([]*multipart.FileHeader, len(vv))
			for i, v := range vv {
				vv2[i] = cloneMultipartFileHeader(v)
			}
			m[k] = vv2

		}

		f2.File = m
	}

	return f2
}

func cloneMultipartFileHeader(fh *multipart.FileHeader) *multipart.FileHeader {
	if fh == nil {
		return nil
	}

	fh2 := new(multipart.FileHeader)
	*fh2 = *fh

	fh2.Header = textproto.MIMEHeader(cloneHeader(http.Header(fh.Header)))

	return fh2
}

// The following code is ported from $GOROOT/src/net/http/header.go with minor changes
// for compatibility with Go versions prior to 1.13
//
// Copyright 2019 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.

func cloneHeader(h http.Header) http.Header {
	if h == nil {
		return nil
	}

	// Find total number of values.
	nv := 0
	for _, vv := range h {
		nv += len(vv)
	}
	sv := make([]string, nv) // shared backing array for headers' values
	h2 := make(http.Header, len(h))
	for k, vv := range h {
		n := copy(sv, vv)
		h2[k] = sv[:n:n]
		sv = sv[n:]
	}
	return h2
}
<|endoftext|>


<commit_before>package action

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"

	"github.com/gopasspw/gopass/pkg/ctxutil"
	"github.com/gopasspw/gopass/pkg/termutil"
	"github.com/gopasspw/gopass/pkg/tree"

	"github.com/fatih/color"
	shellquote "github.com/kballard/go-shellquote"
	"github.com/pkg/errors"
	"github.com/urfave/cli/v2"
)

// List all secrets as a tree. If the filter argument is non-empty
// display only those that have this prefix
func (s *Action) List(ctx context.Context, c *cli.Context) error {
	filter := c.Args().First()
	flat := c.Bool("flat")
	stripPrefix := c.Bool("strip-prefix")
	folders := c.Bool("folders")
	// we only support listing folders in flat mode currently
	if folders {
		flat = true
	}

	ctx = s.Store.WithConfig(ctx, filter)

	l, err := s.Store.Tree(ctx)
	if err != nil {
		return ExitError(ctx, ExitList, err, "failed to list store: %s", err)
	}

	//set limit to len of store to loop over all values later
	limit := l.Len()
	if c.IsSet("limit") {
		limit = c.Int("limit")
	}

	if filter == "" {
		return s.listAll(ctx, l, limit, flat, folders)
	}

	return s.listFiltered(ctx, l, limit, flat, folders, stripPrefix, filter)
}

func (s *Action) listFiltered(ctx context.Context, l tree.Tree, limit int, flat, folders, stripPrefix bool, filter string) error {
	subtree, err := l.FindFolder(filter)
	if err != nil {
		return ExitError(ctx, ExitNotFound, nil, "Entry '%s' not found", filter)
	}

	// SetRoot formats the root entry properly
	subtree.SetRoot(true)
	subtree.SetName(filter)
	if flat {
		sep := "/"
		if strings.HasSuffix(filter, "/") {
			sep = ""
		}
		listOver := subtree.List
		if folders {
			listOver = subtree.ListFolders
		}
		for _, e := range listOver(limit) {
			if stripPrefix {
				fmt.Fprintln(stdout, e)
				continue
			}
			fmt.Fprintln(stdout, filter+sep+e)
		}
		return nil
	}

	// we may need to redirect stdout for the pager support
	so, buf := redirectPager(ctx, subtree)

	fmt.Fprintln(so, subtree.Format(limit))
	if buf != nil {
		if err := s.pager(ctx, buf); err != nil {
			return ExitError(ctx, ExitUnknown, err, "failed to invoke pager: %s", err)
		}
	}
	return nil
}

// redirectPager returns a redirected io.Writer if the output would exceed
// the terminal size
func redirectPager(ctx context.Context, subtree tree.Tree) (io.Writer, *bytes.Buffer) {
	if ctxutil.IsNoPager(ctx) {
		return stdout, nil
	}
	rows, _ := termutil.GetTermsize()
	if rows <= 0 {
		return stdout, nil
	}
	if subtree == nil || subtree.Len() < rows {
		return stdout, nil
	}
	color.NoColor = true
	buf := &bytes.Buffer{}
	return buf, buf
}

// listAll will unconditionally list all entries, used if no filter is given
func (s *Action) listAll(ctx context.Context, l tree.Tree, limit int, flat, folders bool) error {
	if flat {
		listOver := l.List
		if folders {
			listOver = l.ListFolders
		}
		for _, e := range listOver(limit) {
			fmt.Fprintln(stdout, e)
		}
		return nil
	}

	// we may need to redirect stdout for the pager support
	so, buf := redirectPager(ctx, l)

	fmt.Fprintln(so, l.Format(limit))
	if buf != nil {
		if err := s.pager(ctx, buf); err != nil {
			return ExitError(ctx, ExitUnknown, err, "failed to invoke pager: %s", err)
		}
	}
	return nil
}

// pager invokes the default pager with the given content
func (s *Action) pager(ctx context.Context, buf io.Reader) error {
	pager := os.Getenv("PAGER")
	if pager == "" {
		fmt.Fprintln(stdout, buf)
		return nil
	}

	args, err := shellquote.Split(pager)
	if err != nil {
		return errors.Wrapf(err, "failed to split pager command")
	}

	cmd := exec.CommandContext(ctx, args[0], args[1:]...)
	cmd.Stdin = buf
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	return cmd.Run()
}
<commit_msg>Do not disable output color if no PAGER is available (#1293)<commit_after>package action

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"

	"github.com/fatih/color"
	"github.com/gopasspw/gopass/pkg/ctxutil"
	"github.com/gopasspw/gopass/pkg/termutil"
	"github.com/gopasspw/gopass/pkg/tree"

	shellquote "github.com/kballard/go-shellquote"
	"github.com/pkg/errors"
	"github.com/urfave/cli/v2"
)

// List all secrets as a tree. If the filter argument is non-empty
// display only those that have this prefix
func (s *Action) List(ctx context.Context, c *cli.Context) error {
	filter := c.Args().First()
	flat := c.Bool("flat")
	stripPrefix := c.Bool("strip-prefix")
	folders := c.Bool("folders")
	// we only support listing folders in flat mode currently
	if folders {
		flat = true
	}

	ctx = s.Store.WithConfig(ctx, filter)

	l, err := s.Store.Tree(ctx)
	if err != nil {
		return ExitError(ctx, ExitList, err, "failed to list store: %s", err)
	}

	//set limit to len of store to loop over all values later
	limit := l.Len()
	if c.IsSet("limit") {
		limit = c.Int("limit")
	}

	if filter == "" {
		return s.listAll(ctx, l, limit, flat, folders)
	}

	return s.listFiltered(ctx, l, limit, flat, folders, stripPrefix, filter)
}

func (s *Action) listFiltered(ctx context.Context, l tree.Tree, limit int, flat, folders, stripPrefix bool, filter string) error {
	subtree, err := l.FindFolder(filter)
	if err != nil {
		return ExitError(ctx, ExitNotFound, nil, "Entry '%s' not found", filter)
	}

	// SetRoot formats the root entry properly
	subtree.SetRoot(true)
	subtree.SetName(filter)
	if flat {
		sep := "/"
		if strings.HasSuffix(filter, "/") {
			sep = ""
		}
		listOver := subtree.List
		if folders {
			listOver = subtree.ListFolders
		}
		for _, e := range listOver(limit) {
			if stripPrefix {
				fmt.Fprintln(stdout, e)
				continue
			}
			fmt.Fprintln(stdout, filter+sep+e)
		}
		return nil
	}

	// we may need to redirect stdout for the pager support
	so, buf := redirectPager(ctx, subtree)

	fmt.Fprintln(so, subtree.Format(limit))
	if buf != nil {
		if err := s.pager(ctx, buf); err != nil {
			return ExitError(ctx, ExitUnknown, err, "failed to invoke pager: %s", err)
		}
	}
	return nil
}

// redirectPager returns a redirected io.Writer if the output would exceed
// the terminal size
func redirectPager(ctx context.Context, subtree tree.Tree) (io.Writer, *bytes.Buffer) {
	if ctxutil.IsNoPager(ctx) {
		return stdout, nil
	}
	rows, _ := termutil.GetTermsize()
	if rows <= 0 {
		return stdout, nil
	}
	if subtree == nil || subtree.Len() < rows {
		return stdout, nil
	}
	if pager := os.Getenv("PAGER"); pager == "" {
		return stdout, nil
	}
	color.NoColor = true
	buf := &bytes.Buffer{}
	return buf, buf
}

// listAll will unconditionally list all entries, used if no filter is given
func (s *Action) listAll(ctx context.Context, l tree.Tree, limit int, flat, folders bool) error {
	if flat {
		listOver := l.List
		if folders {
			listOver = l.ListFolders
		}
		for _, e := range listOver(limit) {
			fmt.Fprintln(stdout, e)
		}
		return nil
	}

	// we may need to redirect stdout for the pager support
	so, buf := redirectPager(ctx, l)

	fmt.Fprintln(so, l.Format(limit))
	if buf != nil {
		if err := s.pager(ctx, buf); err != nil {
			return ExitError(ctx, ExitUnknown, err, "failed to invoke pager: %s", err)
		}
	}
	return nil
}

// pager invokes the default pager with the given content
func (s *Action) pager(ctx context.Context, buf io.Reader) error {
	pager := os.Getenv("PAGER")
	if pager == "" {
		fmt.Fprintln(stdout, buf)
		return nil
	}

	args, err := shellquote.Split(pager)
	if err != nil {
		return errors.Wrapf(err, "failed to split pager command")
	}

	cmd := exec.CommandContext(ctx, args[0], args[1:]...)
	cmd.Stdin = buf
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

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


<commit_before>/*
Copyright The Helm 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 action

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"

	"helm.sh/helm/v3/pkg/chartutil"
	"helm.sh/helm/v3/pkg/cli"
	"helm.sh/helm/v3/pkg/downloader"
	"helm.sh/helm/v3/pkg/getter"
	"helm.sh/helm/v3/pkg/repo"
)

// Pull is the action for checking a given release's information.
//
// It provides the implementation of 'helm pull'.
type Pull struct {
	ChartPathOptions

	Settings *cli.EnvSettings // TODO: refactor this out of pkg/action

	Devel       bool
	Untar       bool
	VerifyLater bool
	UntarDir    string
	DestDir     string
	cfg         *Configuration
}

type PullOpt func(*Pull)

func WithConfig(cfg *Configuration) PullOpt {
	return func(p *Pull) {
		p.cfg = cfg
	}
}

// NewPull creates a new Pull object.
func NewPull() *Pull {
	return NewPullWithOpts()
}

// NewPull creates a new pull, with configuration options.
func NewPullWithOpts(opts ...PullOpt) *Pull {
	p := &Pull{}
	for _, fn := range opts {
		fn(p)
	}

	return p
}

// Run executes 'helm pull' against the given release.
func (p *Pull) Run(chartRef string) (string, error) {
	var out strings.Builder

	c := downloader.ChartDownloader{
		Out:     &out,
		Keyring: p.Keyring,
		Verify:  downloader.VerifyNever,
		Getters: getter.All(p.Settings),
		Options: []getter.Option{
			getter.WithBasicAuth(p.Username, p.Password),
			getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile),
			getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify),
		},
		RepositoryConfig: p.Settings.RepositoryConfig,
		RepositoryCache:  p.Settings.RepositoryCache,
	}

	if strings.HasPrefix(chartRef, "oci://") {
		if p.Version == "" {
			return out.String(), errors.Errorf("--version flag is explicitly required for OCI registries")
		}

		c.Options = append(c.Options,
			getter.WithRegistryClient(p.cfg.RegistryClient),
			getter.WithTagName(p.Version))
	}

	if p.Verify {
		c.Verify = downloader.VerifyAlways
	} else if p.VerifyLater {
		c.Verify = downloader.VerifyLater
	}

	// If untar is set, we fetch to a tempdir, then untar and copy after
	// verification.
	dest := p.DestDir
	if p.Untar {
		var err error
		dest, err = ioutil.TempDir("", "helm-")
		if err != nil {
			return out.String(), errors.Wrap(err, "failed to untar")
		}
		defer os.RemoveAll(dest)
	}

	if p.RepoURL != "" {
		chartURL, err := repo.FindChartInAuthAndTLSRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, p.InsecureSkipTLSverify, getter.All(p.Settings))
		if err != nil {
			return out.String(), err
		}
		chartRef = chartURL
	}

	saved, v, err := c.DownloadTo(chartRef, p.Version, dest)
	if err != nil {
		return out.String(), err
	}

	if p.Verify {
		for name := range v.SignedBy.Identities {
			fmt.Fprintf(&out, "Signed by: %v\n", name)
		}
		fmt.Fprintf(&out, "Using Key With Fingerprint: %X\n", v.SignedBy.PrimaryKey.Fingerprint)
		fmt.Fprintf(&out, "Chart Hash Verified: %s\n", v.FileHash)
	}

	// After verification, untar the chart into the requested directory.
	if p.Untar {
		ud := p.UntarDir
		if !filepath.IsAbs(ud) {
			ud = filepath.Join(p.DestDir, ud)
		}
		// Let udCheck to check conflict file/dir without replacing ud when untarDir is the current directory(.).
		udCheck := ud
		if udCheck == "." {
			_, udCheck = filepath.Split(chartRef)
		} else {
			_, chartName := filepath.Split(chartRef)
			udCheck = filepath.Join(udCheck, chartName)
		}

		if _, err := os.Stat(udCheck); err != nil {
			if err := os.MkdirAll(udCheck, 0755); err != nil {
				return out.String(), errors.Wrap(err, "failed to untar (mkdir)")
			}

		} else {
			return out.String(), errors.Errorf("failed to untar: a file or directory with the name %s already exists", udCheck)
		}

		return out.String(), chartutil.ExpandFile(ud, saved)
	}
	return out.String(), nil
}
<commit_msg>Fix typo in comment<commit_after>/*
Copyright The Helm 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 action

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"

	"helm.sh/helm/v3/pkg/chartutil"
	"helm.sh/helm/v3/pkg/cli"
	"helm.sh/helm/v3/pkg/downloader"
	"helm.sh/helm/v3/pkg/getter"
	"helm.sh/helm/v3/pkg/repo"
)

// Pull is the action for checking a given release's information.
//
// It provides the implementation of 'helm pull'.
type Pull struct {
	ChartPathOptions

	Settings *cli.EnvSettings // TODO: refactor this out of pkg/action

	Devel       bool
	Untar       bool
	VerifyLater bool
	UntarDir    string
	DestDir     string
	cfg         *Configuration
}

type PullOpt func(*Pull)

func WithConfig(cfg *Configuration) PullOpt {
	return func(p *Pull) {
		p.cfg = cfg
	}
}

// NewPull creates a new Pull object.
func NewPull() *Pull {
	return NewPullWithOpts()
}

// NewPullWithOpts creates a new pull, with configuration options.
func NewPullWithOpts(opts ...PullOpt) *Pull {
	p := &Pull{}
	for _, fn := range opts {
		fn(p)
	}

	return p
}

// Run executes 'helm pull' against the given release.
func (p *Pull) Run(chartRef string) (string, error) {
	var out strings.Builder

	c := downloader.ChartDownloader{
		Out:     &out,
		Keyring: p.Keyring,
		Verify:  downloader.VerifyNever,
		Getters: getter.All(p.Settings),
		Options: []getter.Option{
			getter.WithBasicAuth(p.Username, p.Password),
			getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile),
			getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify),
		},
		RepositoryConfig: p.Settings.RepositoryConfig,
		RepositoryCache:  p.Settings.RepositoryCache,
	}

	if strings.HasPrefix(chartRef, "oci://") {
		if p.Version == "" {
			return out.String(), errors.Errorf("--version flag is explicitly required for OCI registries")
		}

		c.Options = append(c.Options,
			getter.WithRegistryClient(p.cfg.RegistryClient),
			getter.WithTagName(p.Version))
	}

	if p.Verify {
		c.Verify = downloader.VerifyAlways
	} else if p.VerifyLater {
		c.Verify = downloader.VerifyLater
	}

	// If untar is set, we fetch to a tempdir, then untar and copy after
	// verification.
	dest := p.DestDir
	if p.Untar {
		var err error
		dest, err = ioutil.TempDir("", "helm-")
		if err != nil {
			return out.String(), errors.Wrap(err, "failed to untar")
		}
		defer os.RemoveAll(dest)
	}

	if p.RepoURL != "" {
		chartURL, err := repo.FindChartInAuthAndTLSRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, p.InsecureSkipTLSverify, getter.All(p.Settings))
		if err != nil {
			return out.String(), err
		}
		chartRef = chartURL
	}

	saved, v, err := c.DownloadTo(chartRef, p.Version, dest)
	if err != nil {
		return out.String(), err
	}

	if p.Verify {
		for name := range v.SignedBy.Identities {
			fmt.Fprintf(&out, "Signed by: %v\n", name)
		}
		fmt.Fprintf(&out, "Using Key With Fingerprint: %X\n", v.SignedBy.PrimaryKey.Fingerprint)
		fmt.Fprintf(&out, "Chart Hash Verified: %s\n", v.FileHash)
	}

	// After verification, untar the chart into the requested directory.
	if p.Untar {
		ud := p.UntarDir
		if !filepath.IsAbs(ud) {
			ud = filepath.Join(p.DestDir, ud)
		}
		// Let udCheck to check conflict file/dir without replacing ud when untarDir is the current directory(.).
		udCheck := ud
		if udCheck == "." {
			_, udCheck = filepath.Split(chartRef)
		} else {
			_, chartName := filepath.Split(chartRef)
			udCheck = filepath.Join(udCheck, chartName)
		}

		if _, err := os.Stat(udCheck); err != nil {
			if err := os.MkdirAll(udCheck, 0755); err != nil {
				return out.String(), errors.Wrap(err, "failed to untar (mkdir)")
			}

		} else {
			return out.String(), errors.Errorf("failed to untar: a file or directory with the name %s already exists", udCheck)
		}

		return out.String(), chartutil.ExpandFile(ud, saved)
	}
	return out.String(), nil
}
<|endoftext|>


<commit_before>package apps

import (
	"encoding/json"
	"io"
	"path"
	"strings"
	"time"

	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/jobs"
	"github.com/cozy/cozy-stack/pkg/notification"
	"github.com/cozy/cozy-stack/pkg/permissions"
	"github.com/cozy/cozy-stack/pkg/prefixer"
)

// Route is a struct to serve a folder inside an app
type Route struct {
	Folder string `json:"folder"`
	Index  string `json:"index"`
	Public bool   `json:"public"`
}

// NotFound returns true for a blank route (ie not found by FindRoute)
func (c *Route) NotFound() bool { return c.Folder == "" }

// Routes is a map for routing inside an application.
type Routes map[string]Route

// Service is a struct to define a service executed by the stack.
type Service struct {
	name string

	Type           string `json:"type"`
	File           string `json:"file"`
	Debounce       string `json:"debounce"`
	TriggerOptions string `json:"trigger"`
	TriggerID      string `json:"trigger_id"`
}

// Services is a map to define services assciated with an application.
type Services map[string]*Service

// Notifications is a map to define the notifications properties used by the
// application.
type Notifications map[string]notification.Properties

// Locales is a map to define the available locales of the application.
type Locales map[string]interface{}

// Developer is the name and url of a developer.
type Developer struct {
	Name string `json:"name"`
	URL  string `json:"url,omitempty"`
}

// Platform is a supported additional device platform of the application.
type Platform struct {
	Type string `json:"type"`
	URL  string `json:"url"`
}

// Intent is a declaration of a service for other client-side apps
type Intent struct {
	Action string   `json:"action"`
	Types  []string `json:"type"`
	Href   string   `json:"href"`
}

// WebappManifest contains all the informations associated with an installed web
// application.
type WebappManifest struct {
	DocRev string `json:"_rev,omitempty"`

	Name       string `json:"name"`
	NamePrefix string `json:"name_prefix,omitempty"`
	Editor     string `json:"editor"`
	Icon       string `json:"icon"`

	Type        string           `json:"type,omitempty"`
	License     string           `json:"license,omitempty"`
	Language    string           `json:"language,omitempty"`
	Category    string           `json:"category,omitempty"`
	VendorLink  string           `json:"vendor_link"`
	Locales     *json.RawMessage `json:"locales,omitempty"`
	Langs       *json.RawMessage `json:"langs,omitempty"`
	Platforms   *json.RawMessage `json:"platforms,omitempty"`
	Categories  *json.RawMessage `json:"categories,omitempty"`
	Developer   *json.RawMessage `json:"developer,omitempty"`
	Screenshots *json.RawMessage `json:"screenshots,omitempty"`
	Tags        *json.RawMessage `json:"tags,omitempty"`

	DocSlug        string          `json:"slug"`
	DocState       State           `json:"state"`
	DocSource      string          `json:"source"`
	DocVersion     string          `json:"version"`
	DocPermissions permissions.Set `json:"permissions"`

	Intents       []Intent      `json:"intents"`
	Routes        Routes        `json:"routes"`
	Services      Services      `json:"services"`
	Notifications Notifications `json:"notifications"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	Instance SubDomainer `json:"-"` // Used for JSON-API links

	oldServices Services // Used to diff against when updating the app

	Err string `json:"error,omitempty"`
	err error

	// NOTE: Do not forget to propagate changes made to this structure to the
	// structure AppManifest in client/apps.go.
}

// ID is part of the Manifest interface
func (m *WebappManifest) ID() string { return m.DocType() + "/" + m.DocSlug }

// Rev is part of the Manifest interface
func (m *WebappManifest) Rev() string { return m.DocRev }

// DocType is part of the Manifest interface
func (m *WebappManifest) DocType() string { return consts.Apps }

// Clone implements couchdb.Doc
func (m *WebappManifest) Clone() couchdb.Doc {
	cloned := *m

	cloned.Routes = make(Routes, len(m.Routes))
	for k, v := range m.Routes {
		cloned.Routes[k] = v
	}

	cloned.Services = make(Services, len(m.Services))
	for k, v := range m.Services {
		tmp := *v
		cloned.Services[k] = &tmp
	}

	cloned.Notifications = make(Notifications, len(m.Notifications))
	for k, v := range m.Notifications {
		props := (&v).Clone()
		cloned.Notifications[k] = *props
	}

	cloned.Locales = cloneRawMessage(m.Locales)
	cloned.Langs = cloneRawMessage(m.Langs)
	cloned.Platforms = cloneRawMessage(m.Platforms)
	cloned.Categories = cloneRawMessage(m.Categories)
	cloned.Developer = cloneRawMessage(m.Developer)
	cloned.Screenshots = cloneRawMessage(m.Screenshots)
	cloned.Tags = cloneRawMessage(m.Tags)

	cloned.Intents = make([]Intent, len(m.Intents))
	copy(cloned.Intents, m.Intents)

	cloned.DocPermissions = make(permissions.Set, len(m.DocPermissions))
	copy(cloned.DocPermissions, m.DocPermissions)

	return &cloned
}

// SetID is part of the Manifest interface
func (m *WebappManifest) SetID(id string) {}

// SetRev is part of the Manifest interface
func (m *WebappManifest) SetRev(rev string) { m.DocRev = rev }

// Source is part of the Manifest interface
func (m *WebappManifest) Source() string { return m.DocSource }

// Version is part of the Manifest interface
func (m *WebappManifest) Version() string { return m.DocVersion }

// Slug is part of the Manifest interface
func (m *WebappManifest) Slug() string { return m.DocSlug }

// State is part of the Manifest interface
func (m *WebappManifest) State() State { return m.DocState }

// LastUpdate is part of the Manifest interface
func (m *WebappManifest) LastUpdate() time.Time { return m.UpdatedAt }

// SetState is part of the Manifest interface
func (m *WebappManifest) SetState(state State) { m.DocState = state }

// SetVersion is part of the Manifest interface
func (m *WebappManifest) SetVersion(version string) { m.DocVersion = version }

// AppType is part of the Manifest interface
func (m *WebappManifest) AppType() AppType { return Webapp }

// Permissions is part of the Manifest interface
func (m *WebappManifest) Permissions() permissions.Set {
	return m.DocPermissions
}

// SetError is part of the Manifest interface
func (m *WebappManifest) SetError(err error) {
	m.SetState(Errored)
	m.Err = err.Error()
	m.err = err
}

// Error is part of the Manifest interface
func (m *WebappManifest) Error() error { return m.err }

// Match is part of the Manifest interface
func (m *WebappManifest) Match(field, value string) bool {
	switch field {
	case "slug":
		return m.DocSlug == value
	case "state":
		return m.DocState == State(value)
	}
	return false
}

// ReadManifest is part of the Manifest interface
func (m *WebappManifest) ReadManifest(r io.Reader, slug, sourceURL string) error {
	var newManifest WebappManifest
	if err := json.NewDecoder(r).Decode(&newManifest); err != nil {
		return ErrBadManifest
	}

	newManifest.SetID(m.ID())
	newManifest.SetRev(m.Rev())
	newManifest.SetState(m.State())
	newManifest.CreatedAt = m.CreatedAt
	newManifest.Instance = m.Instance
	newManifest.DocSlug = slug
	newManifest.DocSource = sourceURL
	newManifest.oldServices = m.Services
	if newManifest.Routes == nil {
		newManifest.Routes = make(Routes)
		newManifest.Routes["/"] = Route{
			Folder: "/",
			Index:  "index.html",
			Public: false,
		}
	}

	*m = newManifest
	return nil
}

// Create is part of the Manifest interface
func (m *WebappManifest) Create(db prefixer.Prefixer) error {
	if err := diffServices(db, m.Slug(), nil, m.Services); err != nil {
		return err
	}
	m.CreatedAt = time.Now()
	m.UpdatedAt = time.Now()
	if err := couchdb.CreateNamedDocWithDB(db, m); err != nil {
		return err
	}
	_, err := permissions.CreateWebappSet(db, m.Slug(), m.Permissions())
	return err
}

// Update is part of the Manifest interface
func (m *WebappManifest) Update(db prefixer.Prefixer) error {
	if err := diffServices(db, m.Slug(), m.oldServices, m.Services); err != nil {
		return err
	}
	m.UpdatedAt = time.Now()
	if err := couchdb.UpdateDoc(db, m); err != nil {
		return err
	}
	_, err := permissions.UpdateWebappSet(db, m.Slug(), m.Permissions())
	return err
}

// Delete is part of the Manifest interface
func (m *WebappManifest) Delete(db prefixer.Prefixer) error {
	err := diffServices(db, m.Slug(), m.Services, nil)
	if err != nil {
		return err
	}
	err = permissions.DestroyWebapp(db, m.Slug())
	if err != nil && !couchdb.IsNotFoundError(err) {
		return err
	}
	return couchdb.DeleteDoc(db, m)
}

func diffServices(db prefixer.Prefixer, slug string, oldServices, newServices Services) error {
	if oldServices == nil {
		oldServices = make(Services)
	}
	if newServices == nil {
		newServices = make(Services)
	}

	var deleted []*Service
	var created []*Service

	var clone = make(Services)
	for newName, newService := range newServices {
		clone[newName] = newService
		newService.name = newName
	}

	for name, oldService := range oldServices {
		oldService.name = name
		newService, ok := newServices[name]
		if !ok {
			deleted = append(deleted, oldService)
			continue
		}
		delete(clone, name)
		if newService.File != oldService.File ||
			newService.Type != oldService.Type ||
			newService.TriggerOptions != oldService.TriggerOptions ||
			newService.Debounce != oldService.Debounce {
			deleted = append(deleted, oldService)
			created = append(created, newService)
		} else {
			*newService = *oldService
		}
		newService.name = name
	}
	for _, newService := range clone {
		created = append(created, newService)
	}

	sched := jobs.System()
	for _, service := range deleted {
		if err := sched.DeleteTrigger(db, service.TriggerID); err != nil {
			return err
		}
	}

	for _, service := range created {
		var triggerType string
		var triggerArgs string
		triggerOpts := strings.SplitN(service.TriggerOptions, " ", 2)
		if len(triggerOpts) > 0 {
			triggerType = strings.TrimSpace(triggerOpts[0])
		}
		if len(triggerOpts) > 1 {
			triggerArgs = strings.TrimSpace(triggerOpts[1])
		}
		msg := map[string]string{
			"slug": slug,
			"name": service.name,
		}
		trigger, err := jobs.NewTrigger(db, jobs.TriggerInfos{
			Type:       triggerType,
			WorkerType: "service",
			Debounce:   service.Debounce,
			Arguments:  triggerArgs,
		}, msg)
		if err != nil {
			return err
		}
		if err = sched.AddTrigger(trigger); err != nil {
			return err
		}
		service.TriggerID = trigger.ID()
	}

	return nil
}

// FindRoute takes a path, returns the route which matches the best,
// and the part that remains unmatched
func (m *WebappManifest) FindRoute(vpath string) (Route, string) {
	parts := strings.Split(vpath, "/")
	lenParts := len(parts)

	var best Route
	rest := ""
	specificity := 0
	for key, ctx := range m.Routes {
		var keys []string
		if key == "/" {
			keys = []string{""}
		} else {
			keys = strings.Split(key, "/")
		}
		count := len(keys)
		if count > lenParts || count < specificity {
			continue
		}
		if routeMatches(parts, keys) {
			specificity = count
			best = ctx
			rest = path.Join(parts[count:]...)
		}
	}

	return best, rest
}

// FindIntent returns an intent for the given action and type if the manifest has one
func (m *WebappManifest) FindIntent(action, typ string) *Intent {
	for _, intent := range m.Intents {
		if strings.ToUpper(action) != strings.ToUpper(intent.Action) {
			continue
		}
		for _, t := range intent.Types {
			if t == typ {
				return &intent
			}
			// Allow a joker for mime-types like image/*
			if strings.HasSuffix(t, "/*") {
				if strings.SplitN(t, "/", 2)[0] == strings.SplitN(typ, "/", 2)[0] {
					return &intent
				}
			}
		}
	}
	return nil
}

// GetWebappBySlug fetch the WebappManifest from the database given a slug.
func GetWebappBySlug(db prefixer.Prefixer, slug string) (*WebappManifest, error) {
	if slug == "" || !slugReg.MatchString(slug) {
		return nil, ErrInvalidSlugName
	}
	man := &WebappManifest{}
	err := couchdb.GetDoc(db, consts.Apps, consts.Apps+"/"+slug, man)
	if couchdb.IsNotFoundError(err) {
		return nil, ErrNotFound
	}
	if err != nil {
		return nil, err
	}
	return man, nil
}

// ListWebapps returns the list of installed web applications.
//
// TODO: pagination
func ListWebapps(db prefixer.Prefixer) ([]*WebappManifest, error) {
	var docs []*WebappManifest
	req := &couchdb.AllDocsRequest{Limit: 100}
	err := couchdb.GetAllDocs(db, consts.Apps, req, &docs)
	if err != nil {
		return nil, err
	}
	return docs, nil
}

func cloneRawMessage(m *json.RawMessage) *json.RawMessage {
	if m != nil {
		v := make(json.RawMessage, len(*m))
		copy(v, *m)
		return &v
	}
	return nil
}

var _ Manifest = &WebappManifest{}
<commit_msg>Ignore missing triggers when updating an app<commit_after>package apps

import (
	"encoding/json"
	"io"
	"path"
	"strings"
	"time"

	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/jobs"
	"github.com/cozy/cozy-stack/pkg/notification"
	"github.com/cozy/cozy-stack/pkg/permissions"
	"github.com/cozy/cozy-stack/pkg/prefixer"
)

// Route is a struct to serve a folder inside an app
type Route struct {
	Folder string `json:"folder"`
	Index  string `json:"index"`
	Public bool   `json:"public"`
}

// NotFound returns true for a blank route (ie not found by FindRoute)
func (c *Route) NotFound() bool { return c.Folder == "" }

// Routes is a map for routing inside an application.
type Routes map[string]Route

// Service is a struct to define a service executed by the stack.
type Service struct {
	name string

	Type           string `json:"type"`
	File           string `json:"file"`
	Debounce       string `json:"debounce"`
	TriggerOptions string `json:"trigger"`
	TriggerID      string `json:"trigger_id"`
}

// Services is a map to define services assciated with an application.
type Services map[string]*Service

// Notifications is a map to define the notifications properties used by the
// application.
type Notifications map[string]notification.Properties

// Locales is a map to define the available locales of the application.
type Locales map[string]interface{}

// Developer is the name and url of a developer.
type Developer struct {
	Name string `json:"name"`
	URL  string `json:"url,omitempty"`
}

// Platform is a supported additional device platform of the application.
type Platform struct {
	Type string `json:"type"`
	URL  string `json:"url"`
}

// Intent is a declaration of a service for other client-side apps
type Intent struct {
	Action string   `json:"action"`
	Types  []string `json:"type"`
	Href   string   `json:"href"`
}

// WebappManifest contains all the informations associated with an installed web
// application.
type WebappManifest struct {
	DocRev string `json:"_rev,omitempty"`

	Name       string `json:"name"`
	NamePrefix string `json:"name_prefix,omitempty"`
	Editor     string `json:"editor"`
	Icon       string `json:"icon"`

	Type        string           `json:"type,omitempty"`
	License     string           `json:"license,omitempty"`
	Language    string           `json:"language,omitempty"`
	Category    string           `json:"category,omitempty"`
	VendorLink  string           `json:"vendor_link"`
	Locales     *json.RawMessage `json:"locales,omitempty"`
	Langs       *json.RawMessage `json:"langs,omitempty"`
	Platforms   *json.RawMessage `json:"platforms,omitempty"`
	Categories  *json.RawMessage `json:"categories,omitempty"`
	Developer   *json.RawMessage `json:"developer,omitempty"`
	Screenshots *json.RawMessage `json:"screenshots,omitempty"`
	Tags        *json.RawMessage `json:"tags,omitempty"`

	DocSlug        string          `json:"slug"`
	DocState       State           `json:"state"`
	DocSource      string          `json:"source"`
	DocVersion     string          `json:"version"`
	DocPermissions permissions.Set `json:"permissions"`

	Intents       []Intent      `json:"intents"`
	Routes        Routes        `json:"routes"`
	Services      Services      `json:"services"`
	Notifications Notifications `json:"notifications"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	Instance SubDomainer `json:"-"` // Used for JSON-API links

	oldServices Services // Used to diff against when updating the app

	Err string `json:"error,omitempty"`
	err error

	// NOTE: Do not forget to propagate changes made to this structure to the
	// structure AppManifest in client/apps.go.
}

// ID is part of the Manifest interface
func (m *WebappManifest) ID() string { return m.DocType() + "/" + m.DocSlug }

// Rev is part of the Manifest interface
func (m *WebappManifest) Rev() string { return m.DocRev }

// DocType is part of the Manifest interface
func (m *WebappManifest) DocType() string { return consts.Apps }

// Clone implements couchdb.Doc
func (m *WebappManifest) Clone() couchdb.Doc {
	cloned := *m

	cloned.Routes = make(Routes, len(m.Routes))
	for k, v := range m.Routes {
		cloned.Routes[k] = v
	}

	cloned.Services = make(Services, len(m.Services))
	for k, v := range m.Services {
		tmp := *v
		cloned.Services[k] = &tmp
	}

	cloned.Notifications = make(Notifications, len(m.Notifications))
	for k, v := range m.Notifications {
		props := (&v).Clone()
		cloned.Notifications[k] = *props
	}

	cloned.Locales = cloneRawMessage(m.Locales)
	cloned.Langs = cloneRawMessage(m.Langs)
	cloned.Platforms = cloneRawMessage(m.Platforms)
	cloned.Categories = cloneRawMessage(m.Categories)
	cloned.Developer = cloneRawMessage(m.Developer)
	cloned.Screenshots = cloneRawMessage(m.Screenshots)
	cloned.Tags = cloneRawMessage(m.Tags)

	cloned.Intents = make([]Intent, len(m.Intents))
	copy(cloned.Intents, m.Intents)

	cloned.DocPermissions = make(permissions.Set, len(m.DocPermissions))
	copy(cloned.DocPermissions, m.DocPermissions)

	return &cloned
}

// SetID is part of the Manifest interface
func (m *WebappManifest) SetID(id string) {}

// SetRev is part of the Manifest interface
func (m *WebappManifest) SetRev(rev string) { m.DocRev = rev }

// Source is part of the Manifest interface
func (m *WebappManifest) Source() string { return m.DocSource }

// Version is part of the Manifest interface
func (m *WebappManifest) Version() string { return m.DocVersion }

// Slug is part of the Manifest interface
func (m *WebappManifest) Slug() string { return m.DocSlug }

// State is part of the Manifest interface
func (m *WebappManifest) State() State { return m.DocState }

// LastUpdate is part of the Manifest interface
func (m *WebappManifest) LastUpdate() time.Time { return m.UpdatedAt }

// SetState is part of the Manifest interface
func (m *WebappManifest) SetState(state State) { m.DocState = state }

// SetVersion is part of the Manifest interface
func (m *WebappManifest) SetVersion(version string) { m.DocVersion = version }

// AppType is part of the Manifest interface
func (m *WebappManifest) AppType() AppType { return Webapp }

// Permissions is part of the Manifest interface
func (m *WebappManifest) Permissions() permissions.Set {
	return m.DocPermissions
}

// SetError is part of the Manifest interface
func (m *WebappManifest) SetError(err error) {
	m.SetState(Errored)
	m.Err = err.Error()
	m.err = err
}

// Error is part of the Manifest interface
func (m *WebappManifest) Error() error { return m.err }

// Match is part of the Manifest interface
func (m *WebappManifest) Match(field, value string) bool {
	switch field {
	case "slug":
		return m.DocSlug == value
	case "state":
		return m.DocState == State(value)
	}
	return false
}

// ReadManifest is part of the Manifest interface
func (m *WebappManifest) ReadManifest(r io.Reader, slug, sourceURL string) error {
	var newManifest WebappManifest
	if err := json.NewDecoder(r).Decode(&newManifest); err != nil {
		return ErrBadManifest
	}

	newManifest.SetID(m.ID())
	newManifest.SetRev(m.Rev())
	newManifest.SetState(m.State())
	newManifest.CreatedAt = m.CreatedAt
	newManifest.Instance = m.Instance
	newManifest.DocSlug = slug
	newManifest.DocSource = sourceURL
	newManifest.oldServices = m.Services
	if newManifest.Routes == nil {
		newManifest.Routes = make(Routes)
		newManifest.Routes["/"] = Route{
			Folder: "/",
			Index:  "index.html",
			Public: false,
		}
	}

	*m = newManifest
	return nil
}

// Create is part of the Manifest interface
func (m *WebappManifest) Create(db prefixer.Prefixer) error {
	if err := diffServices(db, m.Slug(), nil, m.Services); err != nil {
		return err
	}
	m.CreatedAt = time.Now()
	m.UpdatedAt = time.Now()
	if err := couchdb.CreateNamedDocWithDB(db, m); err != nil {
		return err
	}
	_, err := permissions.CreateWebappSet(db, m.Slug(), m.Permissions())
	return err
}

// Update is part of the Manifest interface
func (m *WebappManifest) Update(db prefixer.Prefixer) error {
	if err := diffServices(db, m.Slug(), m.oldServices, m.Services); err != nil {
		return err
	}
	m.UpdatedAt = time.Now()
	if err := couchdb.UpdateDoc(db, m); err != nil {
		return err
	}
	_, err := permissions.UpdateWebappSet(db, m.Slug(), m.Permissions())
	return err
}

// Delete is part of the Manifest interface
func (m *WebappManifest) Delete(db prefixer.Prefixer) error {
	err := diffServices(db, m.Slug(), m.Services, nil)
	if err != nil {
		return err
	}
	err = permissions.DestroyWebapp(db, m.Slug())
	if err != nil && !couchdb.IsNotFoundError(err) {
		return err
	}
	return couchdb.DeleteDoc(db, m)
}

func diffServices(db prefixer.Prefixer, slug string, oldServices, newServices Services) error {
	if oldServices == nil {
		oldServices = make(Services)
	}
	if newServices == nil {
		newServices = make(Services)
	}

	var deleted []*Service
	var created []*Service

	var clone = make(Services)
	for newName, newService := range newServices {
		clone[newName] = newService
		newService.name = newName
	}

	for name, oldService := range oldServices {
		oldService.name = name
		newService, ok := newServices[name]
		if !ok {
			deleted = append(deleted, oldService)
			continue
		}
		delete(clone, name)
		if newService.File != oldService.File ||
			newService.Type != oldService.Type ||
			newService.TriggerOptions != oldService.TriggerOptions ||
			newService.Debounce != oldService.Debounce {
			deleted = append(deleted, oldService)
			created = append(created, newService)
		} else {
			*newService = *oldService
		}
		newService.name = name
	}
	for _, newService := range clone {
		created = append(created, newService)
	}

	sched := jobs.System()
	for _, service := range deleted {
		if err := sched.DeleteTrigger(db, service.TriggerID); err != nil && err != jobs.ErrNotFoundTrigger {
			return err
		}
	}

	for _, service := range created {
		var triggerType string
		var triggerArgs string
		triggerOpts := strings.SplitN(service.TriggerOptions, " ", 2)
		if len(triggerOpts) > 0 {
			triggerType = strings.TrimSpace(triggerOpts[0])
		}
		if len(triggerOpts) > 1 {
			triggerArgs = strings.TrimSpace(triggerOpts[1])
		}
		msg := map[string]string{
			"slug": slug,
			"name": service.name,
		}
		trigger, err := jobs.NewTrigger(db, jobs.TriggerInfos{
			Type:       triggerType,
			WorkerType: "service",
			Debounce:   service.Debounce,
			Arguments:  triggerArgs,
		}, msg)
		if err != nil {
			return err
		}
		if err = sched.AddTrigger(trigger); err != nil {
			return err
		}
		service.TriggerID = trigger.ID()
	}

	return nil
}

// FindRoute takes a path, returns the route which matches the best,
// and the part that remains unmatched
func (m *WebappManifest) FindRoute(vpath string) (Route, string) {
	parts := strings.Split(vpath, "/")
	lenParts := len(parts)

	var best Route
	rest := ""
	specificity := 0
	for key, ctx := range m.Routes {
		var keys []string
		if key == "/" {
			keys = []string{""}
		} else {
			keys = strings.Split(key, "/")
		}
		count := len(keys)
		if count > lenParts || count < specificity {
			continue
		}
		if routeMatches(parts, keys) {
			specificity = count
			best = ctx
			rest = path.Join(parts[count:]...)
		}
	}

	return best, rest
}

// FindIntent returns an intent for the given action and type if the manifest has one
func (m *WebappManifest) FindIntent(action, typ string) *Intent {
	for _, intent := range m.Intents {
		if strings.ToUpper(action) != strings.ToUpper(intent.Action) {
			continue
		}
		for _, t := range intent.Types {
			if t == typ {
				return &intent
			}
			// Allow a joker for mime-types like image/*
			if strings.HasSuffix(t, "/*") {
				if strings.SplitN(t, "/", 2)[0] == strings.SplitN(typ, "/", 2)[0] {
					return &intent
				}
			}
		}
	}
	return nil
}

// GetWebappBySlug fetch the WebappManifest from the database given a slug.
func GetWebappBySlug(db prefixer.Prefixer, slug string) (*WebappManifest, error) {
	if slug == "" || !slugReg.MatchString(slug) {
		return nil, ErrInvalidSlugName
	}
	man := &WebappManifest{}
	err := couchdb.GetDoc(db, consts.Apps, consts.Apps+"/"+slug, man)
	if couchdb.IsNotFoundError(err) {
		return nil, ErrNotFound
	}
	if err != nil {
		return nil, err
	}
	return man, nil
}

// ListWebapps returns the list of installed web applications.
//
// TODO: pagination
func ListWebapps(db prefixer.Prefixer) ([]*WebappManifest, error) {
	var docs []*WebappManifest
	req := &couchdb.AllDocsRequest{Limit: 100}
	err := couchdb.GetAllDocs(db, consts.Apps, req, &docs)
	if err != nil {
		return nil, err
	}
	return docs, nil
}

func cloneRawMessage(m *json.RawMessage) *json.RawMessage {
	if m != nil {
		v := make(json.RawMessage, len(*m))
		copy(v, *m)
		return &v
	}
	return nil
}

var _ Manifest = &WebappManifest{}
<|endoftext|>


<commit_before>// Copyright 2017 uSwitch
//
// 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 sts

import (
	log "github.com/sirupsen/logrus"
)

func CredentialsFields(identity *RoleIdentity, creds *Credentials) log.Fields {
	return log.Fields{
		"credentials.access.key": creds.AccessKeyId,
		"credentials.expiration": creds.Expiration,
		"credentials.role":       identity.Role,
	}
}
<commit_msg>Log session-name and external-id when logging credentials (#447)<commit_after>// Copyright 2017 uSwitch
//
// 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 sts

import (
	"strings"

	log "github.com/sirupsen/logrus"
)

func CredentialsFields(identity *RoleIdentity, creds *Credentials) log.Fields {
	fields := log.Fields{
		"credentials.access.key": creds.AccessKeyId,
		"credentials.expiration": creds.Expiration,
		"credentials.role":       identity.Role.ARN,
	}

	if identity.SessionName != "" {
		fields["credentials.session-name"] = identity.SessionName
	}

	if identity.ExternalID != "" {
		fields["credentials.external-id"] = strings.Repeat("*", len(identity.ExternalID))
	}

	return fields
}
<|endoftext|>