<commit_before>// +build linux

package oci

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/containers/libpod/pkg/cgroups"
	"github.com/cri-o/cri-o/utils"
	"github.com/opencontainers/runc/libcontainer"
	rspec "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
)

func createUnitName(prefix, name string) string {
	return fmt.Sprintf("%s-%s.scope", prefix, name)
}

func createConmonUnitName(name string) string {
	return createUnitName("crio-conmon", name)
}

func (r *runtimeOCI) createContainerPlatform(c *Container, cgroupParent string, pid int) {
	// Move conmon to specified cgroup
	if r.config.ConmonCgroup == "pod" || r.config.ConmonCgroup == "" {
		if r.config.CgroupManager == SystemdCgroupsManager {
			logrus.Debugf("Running conmon under slice %s and unitName %s", cgroupParent, createConmonUnitName(c.id))
			if err := utils.RunUnderSystemdScope(pid, cgroupParent, createConmonUnitName(c.id)); err != nil {
				logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err)
			}
		}

		control, err := cgroups.New(filepath.Join(cgroupParent, "/crio-conmon-"+c.id), &rspec.LinuxResources{})
		if err != nil {
			logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err)
		}

		if control != nil {
			// Here we should defer a crio-connmon- cgroup hierarchy deletion, but it will
			// always fail as conmon's pid is still there.
			// Fortunately, kubelet takes care of deleting this for us, so the leak will
			// only happens in corner case where one does a manual deletion of the container
			// through e.g. runc. This should be handled by implementing a conmon monitoring
			// routine that does the cgroup cleanup once conmon is terminated.
			if err := control.AddPid(pid); err != nil {
				logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err)
			}
		}
	} else if strings.HasSuffix(r.config.ConmonCgroup, ".slice") {
		logrus.Debugf("Running conmon under custom slice %s and unitName %s", r.config.ConmonCgroup, createConmonUnitName(c.id))
		if err := utils.RunUnderSystemdScope(pid, r.config.ConmonCgroup, createConmonUnitName(c.id)); err != nil {
			logrus.Warnf("Failed to add conmon to custom systemd sandbox cgroup: %v", err)
		}
	}
}

func sysProcAttrPlatform() *syscall.SysProcAttr {
	return &syscall.SysProcAttr{
		Setpgid: true,
	}
}

// newPipe creates a unix socket pair for communication
func newPipe() (parent, child *os.File, err error) {
	fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
	if err != nil {
		return nil, nil, err
	}
	return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil
}

func loadFactory(root string) (libcontainer.Factory, error) {
	abs, err := filepath.Abs(root)
	if err != nil {
		return nil, err
	}
	cgroupManager := libcontainer.Cgroupfs
	return libcontainer.New(abs, cgroupManager, libcontainer.CriuPath(""))
}

// libcontainerStats gets the stats for the container with the given id from runc/libcontainer
func (r *runtimeOCI) libcontainerStats(ctr *Container) (*libcontainer.Stats, error) {
	factory, err := loadFactory(r.root)
	if err != nil {
		return nil, err
	}
	container, err := factory.Load(ctr.ID())
	if err != nil {
		return nil, err
	}
	return container.Stats()
}

func (r *runtimeOCI) containerStats(ctr *Container) (*ContainerStats, error) {
	libcontainerStats, err := r.libcontainerStats(ctr)
	if err != nil {
		return nil, err
	}
	cgroupStats := libcontainerStats.CgroupStats
	stats := new(ContainerStats)
	stats.Container = ctr.ID()
	stats.CPUNano = cgroupStats.CpuStats.CpuUsage.TotalUsage
	stats.SystemNano = time.Now().UnixNano()
	stats.CPU = calculateCPUPercent(libcontainerStats)
	stats.MemUsage = cgroupStats.MemoryStats.Usage.Usage
	stats.MemLimit = getMemLimit(cgroupStats.MemoryStats.Usage.Limit)
	stats.MemPerc = float64(stats.MemUsage) / float64(stats.MemLimit)
	stats.PIDs = cgroupStats.PidsStats.Current
	stats.BlockInput, stats.BlockOutput = calculateBlockIO(libcontainerStats)
	stats.NetInput, stats.NetOutput = getContainerNetIO(libcontainerStats)

	return stats, nil
}

func metricsToCtrStats(c *Container, m *cgroups.Metrics) *ContainerStats {
	var (
		cpu         float64
		cpuNano     uint64
		memUsage    uint64
		memLimit    uint64
		memPerc     float64
		netInput    uint64
		netOutput   uint64
		blockInput  uint64
		blockOutput uint64
		pids        uint64
	)

	if m != nil {
		pids = m.Pids.Current

		cpuNano = m.CPU.Usage.Total
		cpu = genericCalculateCPUPercent(cpuNano, m.CPU.Usage.PerCPU)

		memUsage = m.Memory.Usage.Usage
		memLimit = getMemLimit(m.Memory.Usage.Limit)
		memPerc = float64(memUsage) / float64(memLimit)

		for _, entry := range m.Blkio.IoServiceBytesRecursive {
			switch strings.ToLower(entry.Op) {
			case "read":
				blockInput += entry.Value
			case "write":
				blockOutput += entry.Value
			}
		}
	}

	return &ContainerStats{
		Container:   c.ID(),
		CPU:         cpu,
		CPUNano:     cpuNano,
		SystemNano:  time.Now().UnixNano(),
		MemUsage:    memUsage,
		MemLimit:    memLimit,
		MemPerc:     memPerc,
		NetInput:    netInput,
		NetOutput:   netOutput,
		BlockInput:  blockInput,
		BlockOutput: blockOutput,
		PIDs:        pids,
	}
}
<commit_msg>conmon cgroups: don't create cgroupfs group for systemd<commit_after>// +build linux

package oci

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/containers/libpod/pkg/cgroups"
	"github.com/cri-o/cri-o/utils"
	"github.com/opencontainers/runc/libcontainer"
	rspec "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
)

func createUnitName(prefix, name string) string {
	return fmt.Sprintf("%s-%s.scope", prefix, name)
}

func createConmonUnitName(name string) string {
	return createUnitName("crio-conmon", name)
}

func (r *runtimeOCI) createContainerPlatform(c *Container, cgroupParent string, pid int) {
	// Move conmon to specified cgroup
	if r.config.ConmonCgroup == "pod" || r.config.ConmonCgroup == "" {
		switch r.config.CgroupManager {
		case SystemdCgroupsManager:
			logrus.Debugf("Running conmon under slice %s and unitName %s", cgroupParent, createConmonUnitName(c.id))
			if err := utils.RunUnderSystemdScope(pid, cgroupParent, createConmonUnitName(c.id)); err != nil {
				logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err)
			}
		case CgroupfsCgroupsManager:
			control, err := cgroups.New(filepath.Join(cgroupParent, "/crio-conmon-"+c.id), &rspec.LinuxResources{})
			if err != nil {
				logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err)
			}
			if control == nil {
				break
			}
			// Here we should defer a crio-connmon- cgroup hierarchy deletion, but it will
			// always fail as conmon's pid is still there.
			// Fortunately, kubelet takes care of deleting this for us, so the leak will
			// only happens in corner case where one does a manual deletion of the container
			// through e.g. runc. This should be handled by implementing a conmon monitoring
			// routine that does the cgroup cleanup once conmon is terminated.
			if err := control.AddPid(pid); err != nil {
				logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err)
			}
		default:
			// error for an unknown cgroups manager
			logrus.Errorf("unknown cgroups manager %q for sandbox cgroup", r.config.CgroupManager)
		}
	} else if strings.HasSuffix(r.config.ConmonCgroup, ".slice") {
		logrus.Debugf("Running conmon under custom slice %s and unitName %s", r.config.ConmonCgroup, createConmonUnitName(c.id))
		if err := utils.RunUnderSystemdScope(pid, r.config.ConmonCgroup, createConmonUnitName(c.id)); err != nil {
			logrus.Warnf("Failed to add conmon to custom systemd sandbox cgroup: %v", err)
		}
	}
}

func sysProcAttrPlatform() *syscall.SysProcAttr {
	return &syscall.SysProcAttr{
		Setpgid: true,
	}
}

// newPipe creates a unix socket pair for communication
func newPipe() (parent, child *os.File, err error) {
	fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
	if err != nil {
		return nil, nil, err
	}
	return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil
}

func loadFactory(root string) (libcontainer.Factory, error) {
	abs, err := filepath.Abs(root)
	if err != nil {
		return nil, err
	}
	cgroupManager := libcontainer.Cgroupfs
	return libcontainer.New(abs, cgroupManager, libcontainer.CriuPath(""))
}

// libcontainerStats gets the stats for the container with the given id from runc/libcontainer
func (r *runtimeOCI) libcontainerStats(ctr *Container) (*libcontainer.Stats, error) {
	factory, err := loadFactory(r.root)
	if err != nil {
		return nil, err
	}
	container, err := factory.Load(ctr.ID())
	if err != nil {
		return nil, err
	}
	return container.Stats()
}

func (r *runtimeOCI) containerStats(ctr *Container) (*ContainerStats, error) {
	libcontainerStats, err := r.libcontainerStats(ctr)
	if err != nil {
		return nil, err
	}
	cgroupStats := libcontainerStats.CgroupStats
	stats := new(ContainerStats)
	stats.Container = ctr.ID()
	stats.CPUNano = cgroupStats.CpuStats.CpuUsage.TotalUsage
	stats.SystemNano = time.Now().UnixNano()
	stats.CPU = calculateCPUPercent(libcontainerStats)
	stats.MemUsage = cgroupStats.MemoryStats.Usage.Usage
	stats.MemLimit = getMemLimit(cgroupStats.MemoryStats.Usage.Limit)
	stats.MemPerc = float64(stats.MemUsage) / float64(stats.MemLimit)
	stats.PIDs = cgroupStats.PidsStats.Current
	stats.BlockInput, stats.BlockOutput = calculateBlockIO(libcontainerStats)
	stats.NetInput, stats.NetOutput = getContainerNetIO(libcontainerStats)

	return stats, nil
}

func metricsToCtrStats(c *Container, m *cgroups.Metrics) *ContainerStats {
	var (
		cpu         float64
		cpuNano     uint64
		memUsage    uint64
		memLimit    uint64
		memPerc     float64
		netInput    uint64
		netOutput   uint64
		blockInput  uint64
		blockOutput uint64
		pids        uint64
	)

	if m != nil {
		pids = m.Pids.Current

		cpuNano = m.CPU.Usage.Total
		cpu = genericCalculateCPUPercent(cpuNano, m.CPU.Usage.PerCPU)

		memUsage = m.Memory.Usage.Usage
		memLimit = getMemLimit(m.Memory.Usage.Limit)
		memPerc = float64(memUsage) / float64(memLimit)

		for _, entry := range m.Blkio.IoServiceBytesRecursive {
			switch strings.ToLower(entry.Op) {
			case "read":
				blockInput += entry.Value
			case "write":
				blockOutput += entry.Value
			}
		}
	}

	return &ContainerStats{
		Container:   c.ID(),
		CPU:         cpu,
		CPUNano:     cpuNano,
		SystemNano:  time.Now().UnixNano(),
		MemUsage:    memUsage,
		MemLimit:    memLimit,
		MemPerc:     memPerc,
		NetInput:    netInput,
		NetOutput:   netOutput,
		BlockInput:  blockInput,
		BlockOutput: blockOutput,
		PIDs:        pids,
	}
}
<|endoftext|>


<commit_before>package gochimp3

import "fmt"

const (
	segments_path       = "/lists/%s/segments"
	single_segment_path = segments_path + "/%s"
)

type ListOfSegments struct {
	baseList

	Segments []Segment `json:"segments"`
	ListID   string    `json:"list_id"`
}

type SegmentRequest struct {
	Name          string         `json:"name"`
	StaticSegment []string       `json:"static_segments"`
	Options       SegmentOptions `json:"options"`
}

type Segment struct {
	SegmentRequest

	ID          string `json:"id"`
	MemberCount int    `json:"member_count"`
	Type        string `json:"type"`
	CreatedAt   string `json:"created_at"`
	UpdatedAt   string `json:"updated_at"`
	ListID      string `json:"list_id"`

	withLinks
}

type SegmentOptions struct {
	Match      string               `json:"match"`
	Conditions []SegmentConditional `json:"conditions"`
}

// SegmentConditional represents parameters to filter by
type SegmentConditional struct {
	Field string  `json:"field"`
	OP    string  `json:"op"`
	Value float64 `json:"value"`
}

type SegmentQueryParams struct {
	ExtendedQueryParams

	Type            string
	SinceCreatedAt  string
	BeforeCreatedAt string
	SinceUpdatedAt  string
	BeforeUpdatedAt string
}

func (q SegmentQueryParams) Params() map[string]string {
	m := q.ExtendedQueryParams.Params()

	m["type"] = q.Type
	m["since_created_at"] = q.SinceCreatedAt
	m["since_updated_at"] = q.SinceUpdatedAt
	m["before_created_at"] = q.BeforeCreatedAt
	m["before_updated_at"] = q.BeforeUpdatedAt

	return m
}

func (list ListResponse) GetSegments(params *SegmentQueryParams) (*ListOfSegments, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(segments_path, list.ID)
	response := new(ListOfSegments)

	return response, list.api.Request("GET", endpoint, params, nil, response)
}

func (list ListResponse) GetSegment(id string, params *BasicQueryParams) (*Segment, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_segment_path, list.ID, id)
	response := new(Segment)

	return response, list.api.Request("GET", endpoint, params, nil, response)
}

func (list ListResponse) CreateSegment(body *SegmentRequest) (*Segment, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(segments_path, list.ID)
	response := new(Segment)

	return response, list.api.Request("POST", endpoint, nil, &body, response)
}

func (list ListResponse) UpdateSegment(id string, body *SegmentRequest) (*Segment, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_segment_path, list.ID, id)
	response := new(Segment)

	return response, list.api.Request("PATCH", endpoint, nil, &body, response)
}

func (list ListResponse) DeleteSegment(id string) (bool, error) {
	if err := list.CanMakeRequest(); err != nil {
		return false, err
	}

	endpoint := fmt.Sprintf(single_segment_path, list.ID, id)
	return list.api.RequestOk("DELETE", endpoint)
}
<commit_msg>Change type of ID on the Segment struct<commit_after>package gochimp3

import "fmt"

const (
	segments_path       = "/lists/%s/segments"
	single_segment_path = segments_path + "/%s"
)

type ListOfSegments struct {
	baseList

	Segments []Segment `json:"segments"`
	ListID   string    `json:"list_id"`
}

type SegmentRequest struct {
	Name          string         `json:"name"`
	StaticSegment []string       `json:"static_segments"`
	Options       SegmentOptions `json:"options"`
}

type Segment struct {
	SegmentRequest

	ID          int `json:"id"`
	MemberCount int    `json:"member_count"`
	Type        string `json:"type"`
	CreatedAt   string `json:"created_at"`
	UpdatedAt   string `json:"updated_at"`
	ListID      string `json:"list_id"`

	withLinks
}

type SegmentOptions struct {
	Match      string               `json:"match"`
	Conditions []SegmentConditional `json:"conditions"`
}

// SegmentConditional represents parameters to filter by
type SegmentConditional struct {
	Field string  `json:"field"`
	OP    string  `json:"op"`
	Value float64 `json:"value"`
}

type SegmentQueryParams struct {
	ExtendedQueryParams

	Type            string
	SinceCreatedAt  string
	BeforeCreatedAt string
	SinceUpdatedAt  string
	BeforeUpdatedAt string
}

func (q SegmentQueryParams) Params() map[string]string {
	m := q.ExtendedQueryParams.Params()

	m["type"] = q.Type
	m["since_created_at"] = q.SinceCreatedAt
	m["since_updated_at"] = q.SinceUpdatedAt
	m["before_created_at"] = q.BeforeCreatedAt
	m["before_updated_at"] = q.BeforeUpdatedAt

	return m
}

func (list ListResponse) GetSegments(params *SegmentQueryParams) (*ListOfSegments, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(segments_path, list.ID)
	response := new(ListOfSegments)

	return response, list.api.Request("GET", endpoint, params, nil, response)
}

func (list ListResponse) GetSegment(id string, params *BasicQueryParams) (*Segment, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_segment_path, list.ID, id)
	response := new(Segment)

	return response, list.api.Request("GET", endpoint, params, nil, response)
}

func (list ListResponse) CreateSegment(body *SegmentRequest) (*Segment, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(segments_path, list.ID)
	response := new(Segment)

	return response, list.api.Request("POST", endpoint, nil, &body, response)
}

func (list ListResponse) UpdateSegment(id string, body *SegmentRequest) (*Segment, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_segment_path, list.ID, id)
	response := new(Segment)

	return response, list.api.Request("PATCH", endpoint, nil, &body, response)
}

func (list ListResponse) DeleteSegment(id string) (bool, error) {
	if err := list.CanMakeRequest(); err != nil {
		return false, err
	}

	endpoint := fmt.Sprintf(single_segment_path, list.ID, id)
	return list.api.RequestOk("DELETE", endpoint)
}
<|endoftext|>


<commit_before>package cmd

import (
	"github.com/privacybydesign/irmago/server/keyshare/taskserver"
	"github.com/sietseringers/cobra"
	"github.com/sietseringers/viper"
)

var keyshareTaskCmd = &cobra.Command{
	Use:   "task",
	Short: "IRMA keyshare background task server",
	Run: func(command *cobra.Command, args []string) {
		conf := configureKeyshareTask(command)

		task, err := taskserver.New(conf)
		if err != nil {
			die("", err)
		}

		task.CleanupEmails()
		task.CleanupTokens()
		task.CleanupAccounts()
		task.ExpireAccounts()
	},
}

func init() {
	keyshareRoot.AddCommand(keyshareTaskCmd)

	flags := keyshareTaskCmd.Flags()
	flags.SortFlags = false

	flags.StringP("config", "c", "", "path to configuration file")

	flags.String("db", "", "Database server connection string")
	flags.Lookup("db").Header = `Database configuration`

	flags.Int("expiry-delay", 365, "Number of days of inactivity until account expires")
	flags.Int("delete-delay", 30, "Number of days until expired account should be deleted")
	flags.Lookup("expiry-delay").Header = `Time period configuraiton`

	flags.String("email-server", "", "Email server to use for sending email address confirmation emails")
	flags.String("email-hostname", "", "Hostname used in email server tls certificate (leave empty when mail server does not use tls)")
	flags.String("email-username", "", "Username to use when authenticating with email server")
	flags.String("email-password", "", "Password to use when authenticating with email server")
	flags.String("email-from", "", "Email address to use as sender address")
	flags.String("default-language", "en", "Default language, used as fallback when users prefered language is not available")
	flags.StringToString("expired-email-subject", nil, "Translated subject lines for the expired account email")
	flags.StringToString("expired-email-files", nil, "Translated emails for the expired account email")
	flags.Lookup("email-server").Header = `Email configuration (leave empty to disable sending emails)`

	flags.CountP("verbose", "v", "verbose (repeatable)")
	flags.BoolP("quiet", "q", false, "quiet")
	flags.Bool("log-json", false, "Log in JSON format")
	flags.Lookup("verbose").Header = `Other options`
}

func configureKeyshareTask(cmd *cobra.Command) *taskserver.Configuration {
	readConfig(cmd, "keysharetasks", "keyshare task daemon", []string{".", "/etc/keysharetasks"}, nil)

	return &taskserver.Configuration{
		EmailConfiguration: configureEmail(),

		DBConnstring: viper.GetString("db-connstring"),

		ExpiryDelay: viper.GetInt("expiry-delay"),
		DeleteDelay: viper.GetInt("delete-delay"),

		DeleteExpiredAccountSubject: viper.GetStringMapString("expired-email-subject"),
		DeleteExpiredAccountFiles:   viper.GetStringMapString("expired-email-files"),

		Verbose: viper.GetInt("verbose"),
		Quiet:   viper.GetBool("quiet"),
		LogJSON: viper.GetBool("log-json"),
		Logger:  logger,
	}
}
<commit_msg>feat: modify irma keyshare task description<commit_after>package cmd

import (
	"github.com/privacybydesign/irmago/server/keyshare/taskserver"
	"github.com/sietseringers/cobra"
	"github.com/sietseringers/viper"
)

var keyshareTaskCmd = &cobra.Command{
	Use:   "task",
	Short: "Perform IRMA keyshare background tasks",
	Run: func(command *cobra.Command, args []string) {
		conf := configureKeyshareTask(command)

		task, err := taskserver.New(conf)
		if err != nil {
			die("", err)
		}

		task.CleanupEmails()
		task.CleanupTokens()
		task.CleanupAccounts()
		task.ExpireAccounts()
	},
}

func init() {
	keyshareRoot.AddCommand(keyshareTaskCmd)

	flags := keyshareTaskCmd.Flags()
	flags.SortFlags = false

	flags.StringP("config", "c", "", "path to configuration file")

	flags.String("db", "", "Database server connection string")
	flags.Lookup("db").Header = `Database configuration`

	flags.Int("expiry-delay", 365, "Number of days of inactivity until account expires")
	flags.Int("delete-delay", 30, "Number of days until expired account should be deleted")
	flags.Lookup("expiry-delay").Header = `Time period configuraiton`

	flags.String("email-server", "", "Email server to use for sending email address confirmation emails")
	flags.String("email-hostname", "", "Hostname used in email server tls certificate (leave empty when mail server does not use tls)")
	flags.String("email-username", "", "Username to use when authenticating with email server")
	flags.String("email-password", "", "Password to use when authenticating with email server")
	flags.String("email-from", "", "Email address to use as sender address")
	flags.String("default-language", "en", "Default language, used as fallback when users prefered language is not available")
	flags.StringToString("expired-email-subject", nil, "Translated subject lines for the expired account email")
	flags.StringToString("expired-email-files", nil, "Translated emails for the expired account email")
	flags.Lookup("email-server").Header = `Email configuration (leave empty to disable sending emails)`

	flags.CountP("verbose", "v", "verbose (repeatable)")
	flags.BoolP("quiet", "q", false, "quiet")
	flags.Bool("log-json", false, "Log in JSON format")
	flags.Lookup("verbose").Header = `Other options`
}

func configureKeyshareTask(cmd *cobra.Command) *taskserver.Configuration {
	readConfig(cmd, "keysharetasks", "keyshare tasks", []string{".", "/etc/keysharetasks"}, nil)

	return &taskserver.Configuration{
		EmailConfiguration: configureEmail(),

		DBConnstring: viper.GetString("db-connstring"),

		ExpiryDelay: viper.GetInt("expiry-delay"),
		DeleteDelay: viper.GetInt("delete-delay"),

		DeleteExpiredAccountSubject: viper.GetStringMapString("expired-email-subject"),
		DeleteExpiredAccountFiles:   viper.GetStringMapString("expired-email-files"),

		Verbose: viper.GetInt("verbose"),
		Quiet:   viper.GetBool("quiet"),
		LogJSON: viper.GetBool("log-json"),
		Logger:  logger,
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"testing"
)

func Test_checkService(t *testing.T) {
	tests := []struct {
		name       string
		service    string
		config     string
		want       bool
		wantConfig string
	}{
		// TODO: Add test cases.
		{name: "checkService(), fService = geofabrik", service: "geofabrik", want: true},
		{name: "checkService(), fService = openstreetmap.fr", service: "openstreetmap.fr", config: "./geofabrik.yml", want: true, wantConfig: "./openstreetmap.fr.yml"},
		{name: "checkService(), fService = anothermap", service: "anothermap", want: false},
		{name: "checkService(), fService = \"\"", service: "", want: false},
	}
	for _, tt := range tests {
		*fService = tt.service
		t.Run(tt.name, func(t *testing.T) {
			if tt.config != "" {
				*fConfig = tt.config
			}
			if got := checkService(); got != tt.want {
				t.Errorf("checkService() = %v, want %v", got, tt.want)
			}
			if tt.wantConfig != "" && *fConfig != tt.wantConfig {
				t.Errorf("checkService() haven't change fConfig, want %v have %v", tt.wantConfig, *fConfig)
			}
		})
	}
}

/*func Benchmark_listAllRegions_parse_geofabrik_yml(b *testing.B) {
	// run the Fib function b.N times
	c, _ := loadConfig("./geofabrik.yml")
	for n := 0; n < b.N; n++ {
		listAllRegions(*c, "")
	}
}*/
/*
func Benchmark_listAllRegions_parse_geofabrik_yml_md(b *testing.B) {
	// run the Fib function b.N times
	c, _ := loadConfig("./geofabrik.yml")
	for n := 0; n < b.N; n++ {
		listAllRegions(*c, "Markdown")
	}
}
*/
func Test_catch(t *testing.T) {
	type args struct {
		err error
	}
	tests := []struct {
		name string
		args args
	}{
		// TODO: Add test cases.
		{name: "should panic", args: args{err: fmt.Errorf("test")}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			defer func() {
				if r := recover(); r != nil {
					_, err := fmt.Println("Recovered in f", r)
					if err != nil {
						t.Errorf(err.Error())
					}
				}
			}()
			catch(tt.args.err)
			t.Errorf("The code did not panic")
		})
	}
}

func Test_hashFileMD5(t *testing.T) {
	type args struct {
		filePath string
	}
	tests := []struct {
		name    string
		args    args
		want    string
		wantErr bool
	}{
		// TODO: Add test cases.
		{name: "Check with LICENSE file", args: args{filePath: "./LICENSE"}, want: "65d26fcc2f35ea6a181ac777e42db1ea", wantErr: false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := hashFileMD5(tt.args.filePath)
			if (err != nil) != tt.wantErr {
				t.Errorf("hashFileMD5(%v) error = %v, wantErr %v", tt.args.filePath, err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("hashFileMD5() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Benchmark_hashFileMD5_LICENSE(b *testing.B) {
	for n := 0; n < b.N; n++ {
		hashFileMD5("./LICENSE")
	}
}
func Benchmark_controlHash_LICENSE(b *testing.B) {
	hash, _ := hashFileMD5("./LICENSE")
	hashfile := "/tmp/download-geofabrik-test.hash"
	ioutil.WriteFile(hashfile, []byte(hash), 0644)
	for n := 0; n < b.N; n++ {
		controlHash(hashfile, hash)
	}
}

func Test_controlHash(t *testing.T) {
	type args struct {
		hashfile string
		hash     string
	}
	tests := []struct {
		name       string
		args       args
		want       bool
		wantErr    bool
		fileToHash string
	}{
		// TODO: Add test cases.
		{name: "Check with LICENSE file", fileToHash: "./LICENSE", args: args{hashfile: "/tmp/download-geofabrik-test.hash", hash: "65d26fcc2f35ea6a181ac777e42db1ea"}, want: true, wantErr: false},
		{name: "Check with LICENSE file wrong hash", fileToHash: "./LICENSE", args: args{hashfile: "/tmp/download-geofabrik-test.hash", hash: "65d26fcc2f35ea6a181ac777e42db1eb"}, want: false, wantErr: false},
	}
	for _, tt := range tests {
		hash, _ := hashFileMD5(tt.fileToHash)
		ioutil.WriteFile(tt.args.hashfile, []byte(hash), 0644)
		t.Run(tt.name, func(t *testing.T) {
			got, err := controlHash(tt.args.hashfile, tt.args.hash)
			if (err != nil) != tt.wantErr {
				t.Errorf("controlHash() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("controlHash() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_downloadChecksum(t *testing.T) {
	*fQuiet = true // be silent!
	type args struct {
		format string
	}
	tests := []struct {
		name     string
		fConfig  string
		dCheck   bool
		delement string
		args     args
		want     bool
	}{
		// TODO: Add test cases.
		{name: "dCheck = false andorra.osm.pbf from geofabrik", dCheck: false, fConfig: "./geofabrik.yml", delement: "andorra", args: args{format: "osm.pbf"}, want: false},
		{name: "dCheck = true andorra.osm.pbf from geofabrik", fConfig: "./geofabrik.yml", dCheck: true, delement: "andorra", args: args{format: "osm.pbf"}, want: true},
	}
	for _, tt := range tests {
		*dCheck = tt.dCheck
		*fConfig = tt.fConfig
		*delement = tt.delement
		t.Run(tt.name, func(t *testing.T) {
			// Download file
			configPtr, err := loadConfig(*fConfig)
			if err != nil {
				t.Error(err)
			}
			myElem, err := findElem(configPtr, *delement)
			if err != nil {
				t.Error(err)
			}
			myURL, err := elem2URL(configPtr, myElem, tt.args.format)
			if err != nil {
				t.Error(err)
			}
			downloadFromURL(myURL, *delement+"."+tt.args.format)
			// now real test
			if got := downloadChecksum(tt.args.format); got != tt.want {
				t.Errorf("downloadChecksum() = %v, want %v", got, tt.want)
			}
			os.Remove("andorra.osm.pbf.md5") // clean
		})
	}
}
<commit_msg>refactor(download): Speedup downloading checksum test<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"testing"
)

func Test_checkService(t *testing.T) {
	tests := []struct {
		name       string
		service    string
		config     string
		want       bool
		wantConfig string
	}{
		// TODO: Add test cases.
		{name: "checkService(), fService = geofabrik", service: "geofabrik", want: true},
		{name: "checkService(), fService = openstreetmap.fr", service: "openstreetmap.fr", config: "./geofabrik.yml", want: true, wantConfig: "./openstreetmap.fr.yml"},
		{name: "checkService(), fService = anothermap", service: "anothermap", want: false},
		{name: "checkService(), fService = \"\"", service: "", want: false},
	}
	for _, tt := range tests {
		*fService = tt.service
		t.Run(tt.name, func(t *testing.T) {
			if tt.config != "" {
				*fConfig = tt.config
			}
			if got := checkService(); got != tt.want {
				t.Errorf("checkService() = %v, want %v", got, tt.want)
			}
			if tt.wantConfig != "" && *fConfig != tt.wantConfig {
				t.Errorf("checkService() haven't change fConfig, want %v have %v", tt.wantConfig, *fConfig)
			}
		})
	}
}

/*func Benchmark_listAllRegions_parse_geofabrik_yml(b *testing.B) {
	// run the Fib function b.N times
	c, _ := loadConfig("./geofabrik.yml")
	for n := 0; n < b.N; n++ {
		listAllRegions(*c, "")
	}
}*/
/*
func Benchmark_listAllRegions_parse_geofabrik_yml_md(b *testing.B) {
	// run the Fib function b.N times
	c, _ := loadConfig("./geofabrik.yml")
	for n := 0; n < b.N; n++ {
		listAllRegions(*c, "Markdown")
	}
}
*/
func Test_catch(t *testing.T) {
	type args struct {
		err error
	}
	tests := []struct {
		name string
		args args
	}{
		// TODO: Add test cases.
		{name: "should panic", args: args{err: fmt.Errorf("test")}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			defer func() {
				if r := recover(); r != nil {
					_, err := fmt.Println("Recovered in f", r)
					if err != nil {
						t.Errorf(err.Error())
					}
				}
			}()
			catch(tt.args.err)
			t.Errorf("The code did not panic")
		})
	}
}

func Test_hashFileMD5(t *testing.T) {
	type args struct {
		filePath string
	}
	tests := []struct {
		name    string
		args    args
		want    string
		wantErr bool
	}{
		// TODO: Add test cases.
		{name: "Check with LICENSE file", args: args{filePath: "./LICENSE"}, want: "65d26fcc2f35ea6a181ac777e42db1ea", wantErr: false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := hashFileMD5(tt.args.filePath)
			if (err != nil) != tt.wantErr {
				t.Errorf("hashFileMD5(%v) error = %v, wantErr %v", tt.args.filePath, err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("hashFileMD5() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Benchmark_hashFileMD5_LICENSE(b *testing.B) {
	for n := 0; n < b.N; n++ {
		hashFileMD5("./LICENSE")
	}
}
func Benchmark_controlHash_LICENSE(b *testing.B) {
	hash, _ := hashFileMD5("./LICENSE")
	hashfile := "/tmp/download-geofabrik-test.hash"
	ioutil.WriteFile(hashfile, []byte(hash), 0644)
	for n := 0; n < b.N; n++ {
		controlHash(hashfile, hash)
	}
}

func Test_controlHash(t *testing.T) {
	type args struct {
		hashfile string
		hash     string
	}
	tests := []struct {
		name       string
		args       args
		want       bool
		wantErr    bool
		fileToHash string
	}{
		// TODO: Add test cases.
		{name: "Check with LICENSE file", fileToHash: "./LICENSE", args: args{hashfile: "/tmp/download-geofabrik-test.hash", hash: "65d26fcc2f35ea6a181ac777e42db1ea"}, want: true, wantErr: false},
		{name: "Check with LICENSE file wrong hash", fileToHash: "./LICENSE", args: args{hashfile: "/tmp/download-geofabrik-test.hash", hash: "65d26fcc2f35ea6a181ac777e42db1eb"}, want: false, wantErr: false},
	}
	for _, tt := range tests {
		hash, _ := hashFileMD5(tt.fileToHash)
		ioutil.WriteFile(tt.args.hashfile, []byte(hash), 0644)
		t.Run(tt.name, func(t *testing.T) {
			got, err := controlHash(tt.args.hashfile, tt.args.hash)
			if (err != nil) != tt.wantErr {
				t.Errorf("controlHash() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("controlHash() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_downloadChecksum(t *testing.T) {
	*fQuiet = true // be silent!
	type args struct {
		format string
	}
	tests := []struct {
		name     string
		fConfig  string
		dCheck   bool
		delement string
		args     args
		want     bool
	}{
		// TODO: Add test cases.
		{name: "dCheck = false andorra.osm.pbf from geofabrik", dCheck: false, fConfig: "./geofabrik.yml", delement: "andorra", args: args{format: "osm.pbf"}, want: false},
		{name: "dCheck = true andorra.osm.pbf from geofabrik", fConfig: "./geofabrik.yml", dCheck: true, delement: "andorra", args: args{format: "osm.pbf"}, want: true},
	}
	for _, tt := range tests {
		*dCheck = tt.dCheck
		*fConfig = tt.fConfig
		*delement = tt.delement
		t.Run(tt.name, func(t *testing.T) {
			if *dCheck { // If I want to compare checksum, Download file
				configPtr, err := loadConfig(*fConfig)
				if err != nil {
					t.Error(err)
				}
				myElem, err := findElem(configPtr, *delement)
				if err != nil {
					t.Error(err)
				}
				myURL, err := elem2URL(configPtr, myElem, tt.args.format)
				if err != nil {
					t.Error(err)
				}
				downloadFromURL(myURL, *delement+"."+tt.args.format)
			}
			// now real test
			if got := downloadChecksum(tt.args.format); got != tt.want {
				t.Errorf("downloadChecksum() = %v, want %v", got, tt.want)
			}
			os.Remove("andorra.osm.pbf.md5") // clean
		})
	}
}
<|endoftext|>


<commit_before>package main

import (
	"archive/zip"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"path"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/aerokube/selenoid/session"
	"github.com/docker/docker/api/types"
	"golang.org/x/net/websocket"
)

var (
	httpClient *http.Client = &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}
	num     uint64
	numLock sync.Mutex
)

type request struct {
	*http.Request
}

type sess struct {
	addr string
	id   string
}

// TODO There is simpler way to do this
func (r request) localaddr() string {
	addr := r.Context().Value(http.LocalAddrContextKey).(net.Addr).String()
	_, port, _ := net.SplitHostPort(addr)
	return net.JoinHostPort("127.0.0.1", port)
}

func (r request) session(id string) *sess {
	return &sess{r.localaddr(), id}
}

func (s *sess) url() string {
	return fmt.Sprintf("http://%s/wd/hub/session/%s", s.addr, s.id)
}

type caps struct {
	Name             string `json:"browserName"`
	Version          string `json:"version"`
	ScreenResolution string `json:"screenResolution"`
	VNC              bool   `json:"enableVNC"`
}

func jsonError(w http.ResponseWriter, msg string, code int) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	json.NewEncoder(w).Encode(
		map[string]interface{}{
			"value": map[string]string{
				"message": msg,
			},
			"status": 13,
		})
}

func (s *sess) Delete() {
	log.Printf("[SESSION_TIMED_OUT] [%s]\n", s.id)
	r, err := http.NewRequest(http.MethodDelete, s.url(), nil)
	if err != nil {
		log.Printf("[DELETE_FAILED] [%s] [%v]\n", s.id, err)
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), sessionDeleteTimeout)
	defer cancel()
	resp, err := httpClient.Do(r.WithContext(ctx))
	if resp != nil {
		defer resp.Body.Close()
	}
	if err == nil && resp.StatusCode == http.StatusOK {
		return
	}
	if err != nil {
		log.Printf("[DELETE_FAILED] [%s] [%v]\n", s.id, err)
	} else {
		log.Printf("[DELETE_FAILED] [%s] [%s]\n", s.id, resp.Status)
	}
}

func serial() uint64 {
	numLock.Lock()
	defer numLock.Unlock()
	id := num
	num++
	return id
}

func create(w http.ResponseWriter, r *http.Request) {
	sessionStartTime := time.Now()
	id := serial()
	quota, _, ok := r.BasicAuth()
	if !ok {
		quota = "unknown"
	}
	body, err := ioutil.ReadAll(r.Body)
	r.Body.Close()
	if err != nil {
		log.Printf("[%d] [ERROR_READING_REQUEST] [%s] [%v]\n", id, quota, err)
		jsonError(w, err.Error(), http.StatusBadRequest)
		queue.Drop()
		return
	}
	var browser struct {
		Caps caps `json:"desiredCapabilities"`
	}
	err = json.Unmarshal(body, &browser)
	if err != nil {
		log.Printf("[%d] [BAD_JSON_FORMAT] [%s] [%v]\n", id, quota, err)
		jsonError(w, err.Error(), http.StatusBadRequest)
		queue.Drop()
		return
	}
	resolution, err := getScreenResolution(browser.Caps.ScreenResolution)
	if err != nil {
		log.Printf("[%d] [BAD_SCREEN_RESOLUTION] [%s] [%s]\n", id, quota, browser.Caps.ScreenResolution)
		jsonError(w, err.Error(), http.StatusBadRequest)
		queue.Drop()
		return
	}
	browser.Caps.ScreenResolution = resolution
	starter, ok := manager.Find(browser.Caps.Name, &browser.Caps.Version, browser.Caps.ScreenResolution, browser.Caps.VNC, id)
	if !ok {
		log.Printf("[%d] [ENVIRONMENT_NOT_AVAILABLE] [%s] [%s-%s]\n", id, quota, browser.Caps.Name, browser.Caps.Version)
		jsonError(w, "Requested environment is not available", http.StatusBadRequest)
		queue.Drop()
		return
	}
	u, container, vnc, cancel, err := starter.StartWithCancel()
	if err != nil {
		log.Printf("[%d] [SERVICE_STARTUP_FAILED] [%s] [%v]\n", id, quota, err)
		jsonError(w, err.Error(), http.StatusInternalServerError)
		queue.Drop()
		return
	}
	var resp *http.Response
	i := 1
	for ; ; i++ {
		r.URL.Host, r.URL.Path = u.Host, path.Join(u.Path, r.URL.Path)
		req, _ := http.NewRequest(http.MethodPost, r.URL.String(), bytes.NewReader(body))
		ctx, done := context.WithTimeout(r.Context(), newSessionAttemptTimeout)
		defer done()
		log.Printf("[%d] [SESSION_ATTEMPTED] [%s] [%s] [%d]\n", id, quota, u.String(), i)
		rsp, err := httpClient.Do(req.WithContext(ctx))
		select {
		case <-ctx.Done():
			if rsp != nil {
				rsp.Body.Close()
			}
			switch ctx.Err() {
			case context.DeadlineExceeded:
				log.Printf("[%d] [SESSION_ATTEMPT_TIMED_OUT] [%s]\n", id, quota)
				continue
			case context.Canceled:
				log.Printf("[%d] [CLIENT_DISCONNECTED] [%s]\n", id, quota)
				queue.Drop()
				cancel()
				return
			}
		default:
		}
		if err != nil {
			if rsp != nil {
				rsp.Body.Close()
			}
			log.Printf("[%d] [SESSION_FAILED] [%s] - [%s]\n", id, u.String(), err)
			jsonError(w, err.Error(), http.StatusInternalServerError)
			queue.Drop()
			cancel()
			return
		}
		if rsp.StatusCode == http.StatusNotFound && u.Path == "" {
			u.Path = "/wd/hub"
			continue
		}
		resp = rsp
		break
	}
	defer resp.Body.Close()
	var s struct {
		Value struct {
			ID string `json:"sessionId"`
		}
		ID string `json:"sessionId"`
	}
	location := resp.Header.Get("Location")
	if location != "" {
		l, err := url.Parse(location)
		if err == nil {
			fragments := strings.Split(l.Path, "/")
			s.ID = fragments[len(fragments)-1]
			u := &url.URL{
				Scheme: "http",
				Host:   hostname,
				Path:   path.Join("/wd/hub/session", s.ID),
			}
			w.Header().Add("Location", u.String())
			w.WriteHeader(resp.StatusCode)
		}
	} else {
		tee := io.TeeReader(resp.Body, w)
		w.WriteHeader(resp.StatusCode)
		json.NewDecoder(tee).Decode(&s)
		if s.ID == "" {
			s.ID = s.Value.ID
		}
	}
	if s.ID == "" {
		log.Printf("[%d] [SESSION_FAILED] [%s] [Bad response from %s - %v]\n", id, quota, u.String(), resp.Status)
		queue.Drop()
		cancel()
		return
	}
	sessions.Put(s.ID, &session.Session{
		Quota:     quota,
		Browser:   browser.Caps.Name,
		Version:   browser.Caps.Version,
		URL:       u,
		Container: container,
		VNC:       vnc,
		Screen:    browser.Caps.ScreenResolution,
		Cancel:    cancel,
		Timeout: onTimeout(timeout, func() {
			request{r}.session(s.ID).Delete()
		})})
	queue.Create()
	log.Printf("[%d] [SESSION_CREATED] [%s] [%s] [%s] [%d] [%v]\n", id, quota, s.ID, u, i, time.Since(sessionStartTime))
}

func getScreenResolution(input string) (string, error) {
	if input == "" {
		return "1920x1080x24", nil
	}
	fullFormat := regexp.MustCompile(`^[0-9]+x[0-9]+x(8|16|24)$`)
	shortFormat := regexp.MustCompile(`^[0-9]+x[0-9]+$`)
	if fullFormat.MatchString(input) {
		return input, nil
	}
	if shortFormat.MatchString(input) {
		return fmt.Sprintf("%sx24", input), nil
	}
	return "", fmt.Errorf(
		"Malformed screenResolution capability: %s. Correct format is WxH (1920x1080) or WxHxD (1920x1080x24).",
		input,
	)
}

func proxy(w http.ResponseWriter, r *http.Request) {
	done := make(chan func())
	go func(w http.ResponseWriter, r *http.Request) {
		cancel := func() {}
		defer func() {
			done <- cancel
		}()
		(&httputil.ReverseProxy{
			Director: func(r *http.Request) {
				fragments := strings.Split(r.URL.Path, "/")
				id := fragments[2]
				sess, ok := sessions.Get(id)
				if ok {
					sess.Lock.Lock()
					defer sess.Lock.Unlock()
					close(sess.Timeout)
					if r.Method == http.MethodDelete && len(fragments) == 3 {
						if enableFileUpload {
							os.RemoveAll(path.Join(os.TempDir(), id))
						}
						cancel = sess.Cancel
						sessions.Remove(id)
						queue.Release()
						log.Printf("[SESSION_DELETED] [%s]\n", id)
					} else {
						sess.Timeout = onTimeout(timeout, func() {
							request{r}.session(id).Delete()
						})
						if len(fragments) == 4 && fragments[len(fragments)-1] == "file" && enableFileUpload {
							r.Header.Set("X-Selenoid-File", path.Join(os.TempDir(), id))
							r.URL.Path = "/file"
							return
						}
					}
					r.URL.Host, r.URL.Path = sess.URL.Host, path.Clean(sess.URL.Path+r.URL.Path)
					return
				}
				r.URL.Path = "/error"
			},
		}).ServeHTTP(w, r)
	}(w, r)
	go (<-done)()
}

func fileUpload(w http.ResponseWriter, r *http.Request) {
	var jsonRequest struct {
		File []byte `json:"file"`
	}
	err := json.NewDecoder(r.Body).Decode(&jsonRequest)
	if err != nil {
		jsonError(w, err.Error(), http.StatusBadRequest)
		return
	}
	z, err := zip.NewReader(bytes.NewReader(jsonRequest.File), int64(len(jsonRequest.File)))
	if err != nil {
		jsonError(w, err.Error(), http.StatusBadRequest)
		return
	}
	if len(z.File) != 1 {
		jsonError(w, fmt.Sprintf("Expected there to be only 1 file. There were: %s", len(z.File)), http.StatusBadRequest)
		return
	}
	file := z.File[0]
	src, err := file.Open()
	if err != nil {
		jsonError(w, err.Error(), http.StatusBadRequest)
		return
	}
	defer src.Close()
	dir := r.Header.Get("X-Selenoid-File")
	err = os.Mkdir(dir, 0755)
	if err != nil {
		jsonError(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fileName := path.Join(dir, file.Name)
	dst, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		jsonError(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer dst.Close()
	_, err = io.Copy(dst, src)
	if err != nil {
		jsonError(w, err.Error(), http.StatusInternalServerError)
		return
	}

	reply := struct {
		V string `json:"value"`
	}{
		V: fileName,
	}
	json.NewEncoder(w).Encode(reply)
}

func vnc(wsconn *websocket.Conn) {
	defer wsconn.Close()
	sid := strings.Split(wsconn.Request().URL.Path, "/")[2]
	sess, ok := sessions.Get(sid)
	if ok {
		if sess.VNC != "" {
			log.Printf("[VNC_ENABLED] [%s]\n", sid)
			conn, err := net.Dial("tcp", sess.VNC)
			if err != nil {
				log.Printf("[VNC_ERROR] [%v]\n", err)
				return
			}
			defer conn.Close()
			wsconn.PayloadType = websocket.BinaryFrame
			go io.Copy(wsconn, conn)
			io.Copy(conn, wsconn)
			log.Printf("[VNC_CLIENT_DISCONNECTED] [%s]\n", sid)
		} else {
			log.Printf("[VNC_NOT_ENABLED] [%s]\n", sid)
		}
	} else {
		log.Printf("[SESSION_NOT_FOUND] [%s]\n", sid)
	}
}

func logs(wsconn *websocket.Conn) {
	defer wsconn.Close()
	sid := strings.Split(wsconn.Request().URL.Path, "/")[2]
	sess, ok := sessions.Get(sid)
	if ok && sess.Container != "" {
		log.Printf("[CONTAINER_LOGS] [%s]\n", sess.Container)
		r, err := cli.ContainerLogs(context.Background(), sess.Container, types.ContainerLogsOptions{
			ShowStdout: true,
			ShowStderr: true,
			Follow:     true,
		})
		if err != nil {
			log.Printf("[CONTAINER_LOGS_ERROR] [%v]\n", err)
			return
		}
		defer r.Close()
		wsconn.PayloadType = websocket.BinaryFrame
		io.Copy(wsconn, r)
		log.Printf("[WEBSOCCKET_CLIENT_DISCONNECTED] [%s]\n", sid)
	} else {
		log.Printf("[SESSION_NOT_FOUND] [%s]\n", sid)
	}
}

func onTimeout(t time.Duration, f func()) chan struct{} {
	cancel := make(chan struct{})
	go func(cancel chan struct{}) {
		select {
		case <-time.After(t):
			f()
		case <-cancel:
		}
	}(cancel)
	return cancel
}
<commit_msg>Fixed filepaths<commit_after>package main

import (
	"archive/zip"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/aerokube/selenoid/session"
	"github.com/docker/docker/api/types"
	"golang.org/x/net/websocket"
)

var (
	httpClient *http.Client = &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}
	num     uint64
	numLock sync.Mutex
)

type request struct {
	*http.Request
}

type sess struct {
	addr string
	id   string
}

// TODO There is simpler way to do this
func (r request) localaddr() string {
	addr := r.Context().Value(http.LocalAddrContextKey).(net.Addr).String()
	_, port, _ := net.SplitHostPort(addr)
	return net.JoinHostPort("127.0.0.1", port)
}

func (r request) session(id string) *sess {
	return &sess{r.localaddr(), id}
}

func (s *sess) url() string {
	return fmt.Sprintf("http://%s/wd/hub/session/%s", s.addr, s.id)
}

type caps struct {
	Name             string `json:"browserName"`
	Version          string `json:"version"`
	ScreenResolution string `json:"screenResolution"`
	VNC              bool   `json:"enableVNC"`
}

func jsonError(w http.ResponseWriter, msg string, code int) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	json.NewEncoder(w).Encode(
		map[string]interface{}{
			"value": map[string]string{
				"message": msg,
			},
			"status": 13,
		})
}

func (s *sess) Delete() {
	log.Printf("[SESSION_TIMED_OUT] [%s]\n", s.id)
	r, err := http.NewRequest(http.MethodDelete, s.url(), nil)
	if err != nil {
		log.Printf("[DELETE_FAILED] [%s] [%v]\n", s.id, err)
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), sessionDeleteTimeout)
	defer cancel()
	resp, err := httpClient.Do(r.WithContext(ctx))
	if resp != nil {
		defer resp.Body.Close()
	}
	if err == nil && resp.StatusCode == http.StatusOK {
		return
	}
	if err != nil {
		log.Printf("[DELETE_FAILED] [%s] [%v]\n", s.id, err)
	} else {
		log.Printf("[DELETE_FAILED] [%s] [%s]\n", s.id, resp.Status)
	}
}

func serial() uint64 {
	numLock.Lock()
	defer numLock.Unlock()
	id := num
	num++
	return id
}

func create(w http.ResponseWriter, r *http.Request) {
	sessionStartTime := time.Now()
	id := serial()
	quota, _, ok := r.BasicAuth()
	if !ok {
		quota = "unknown"
	}
	body, err := ioutil.ReadAll(r.Body)
	r.Body.Close()
	if err != nil {
		log.Printf("[%d] [ERROR_READING_REQUEST] [%s] [%v]\n", id, quota, err)
		jsonError(w, err.Error(), http.StatusBadRequest)
		queue.Drop()
		return
	}
	var browser struct {
		Caps caps `json:"desiredCapabilities"`
	}
	err = json.Unmarshal(body, &browser)
	if err != nil {
		log.Printf("[%d] [BAD_JSON_FORMAT] [%s] [%v]\n", id, quota, err)
		jsonError(w, err.Error(), http.StatusBadRequest)
		queue.Drop()
		return
	}
	resolution, err := getScreenResolution(browser.Caps.ScreenResolution)
	if err != nil {
		log.Printf("[%d] [BAD_SCREEN_RESOLUTION] [%s] [%s]\n", id, quota, browser.Caps.ScreenResolution)
		jsonError(w, err.Error(), http.StatusBadRequest)
		queue.Drop()
		return
	}
	browser.Caps.ScreenResolution = resolution
	starter, ok := manager.Find(browser.Caps.Name, &browser.Caps.Version, browser.Caps.ScreenResolution, browser.Caps.VNC, id)
	if !ok {
		log.Printf("[%d] [ENVIRONMENT_NOT_AVAILABLE] [%s] [%s-%s]\n", id, quota, browser.Caps.Name, browser.Caps.Version)
		jsonError(w, "Requested environment is not available", http.StatusBadRequest)
		queue.Drop()
		return
	}
	u, container, vnc, cancel, err := starter.StartWithCancel()
	if err != nil {
		log.Printf("[%d] [SERVICE_STARTUP_FAILED] [%s] [%v]\n", id, quota, err)
		jsonError(w, err.Error(), http.StatusInternalServerError)
		queue.Drop()
		return
	}
	var resp *http.Response
	i := 1
	for ; ; i++ {
		r.URL.Host, r.URL.Path = u.Host, path.Join(u.Path, r.URL.Path)
		req, _ := http.NewRequest(http.MethodPost, r.URL.String(), bytes.NewReader(body))
		ctx, done := context.WithTimeout(r.Context(), newSessionAttemptTimeout)
		defer done()
		log.Printf("[%d] [SESSION_ATTEMPTED] [%s] [%s] [%d]\n", id, quota, u.String(), i)
		rsp, err := httpClient.Do(req.WithContext(ctx))
		select {
		case <-ctx.Done():
			if rsp != nil {
				rsp.Body.Close()
			}
			switch ctx.Err() {
			case context.DeadlineExceeded:
				log.Printf("[%d] [SESSION_ATTEMPT_TIMED_OUT] [%s]\n", id, quota)
				continue
			case context.Canceled:
				log.Printf("[%d] [CLIENT_DISCONNECTED] [%s]\n", id, quota)
				queue.Drop()
				cancel()
				return
			}
		default:
		}
		if err != nil {
			if rsp != nil {
				rsp.Body.Close()
			}
			log.Printf("[%d] [SESSION_FAILED] [%s] - [%s]\n", id, u.String(), err)
			jsonError(w, err.Error(), http.StatusInternalServerError)
			queue.Drop()
			cancel()
			return
		}
		if rsp.StatusCode == http.StatusNotFound && u.Path == "" {
			u.Path = "/wd/hub"
			continue
		}
		resp = rsp
		break
	}
	defer resp.Body.Close()
	var s struct {
		Value struct {
			ID string `json:"sessionId"`
		}
		ID string `json:"sessionId"`
	}
	location := resp.Header.Get("Location")
	if location != "" {
		l, err := url.Parse(location)
		if err == nil {
			fragments := strings.Split(l.Path, "/")
			s.ID = fragments[len(fragments)-1]
			u := &url.URL{
				Scheme: "http",
				Host:   hostname,
				Path:   path.Join("/wd/hub/session", s.ID),
			}
			w.Header().Add("Location", u.String())
			w.WriteHeader(resp.StatusCode)
		}
	} else {
		tee := io.TeeReader(resp.Body, w)
		w.WriteHeader(resp.StatusCode)
		json.NewDecoder(tee).Decode(&s)
		if s.ID == "" {
			s.ID = s.Value.ID
		}
	}
	if s.ID == "" {
		log.Printf("[%d] [SESSION_FAILED] [%s] [Bad response from %s - %v]\n", id, quota, u.String(), resp.Status)
		queue.Drop()
		cancel()
		return
	}
	sessions.Put(s.ID, &session.Session{
		Quota:     quota,
		Browser:   browser.Caps.Name,
		Version:   browser.Caps.Version,
		URL:       u,
		Container: container,
		VNC:       vnc,
		Screen:    browser.Caps.ScreenResolution,
		Cancel:    cancel,
		Timeout: onTimeout(timeout, func() {
			request{r}.session(s.ID).Delete()
		})})
	queue.Create()
	log.Printf("[%d] [SESSION_CREATED] [%s] [%s] [%s] [%d] [%v]\n", id, quota, s.ID, u, i, time.Since(sessionStartTime))
}

func getScreenResolution(input string) (string, error) {
	if input == "" {
		return "1920x1080x24", nil
	}
	fullFormat := regexp.MustCompile(`^[0-9]+x[0-9]+x(8|16|24)$`)
	shortFormat := regexp.MustCompile(`^[0-9]+x[0-9]+$`)
	if fullFormat.MatchString(input) {
		return input, nil
	}
	if shortFormat.MatchString(input) {
		return fmt.Sprintf("%sx24", input), nil
	}
	return "", fmt.Errorf(
		"Malformed screenResolution capability: %s. Correct format is WxH (1920x1080) or WxHxD (1920x1080x24).",
		input,
	)
}

func proxy(w http.ResponseWriter, r *http.Request) {
	done := make(chan func())
	go func(w http.ResponseWriter, r *http.Request) {
		cancel := func() {}
		defer func() {
			done <- cancel
		}()
		(&httputil.ReverseProxy{
			Director: func(r *http.Request) {
				fragments := strings.Split(r.URL.Path, "/")
				id := fragments[2]
				sess, ok := sessions.Get(id)
				if ok {
					sess.Lock.Lock()
					defer sess.Lock.Unlock()
					close(sess.Timeout)
					if r.Method == http.MethodDelete && len(fragments) == 3 {
						if enableFileUpload {
							os.RemoveAll(filepath.Join(os.TempDir(), id))
						}
						cancel = sess.Cancel
						sessions.Remove(id)
						queue.Release()
						log.Printf("[SESSION_DELETED] [%s]\n", id)
					} else {
						sess.Timeout = onTimeout(timeout, func() {
							request{r}.session(id).Delete()
						})
						if len(fragments) == 4 && fragments[len(fragments)-1] == "file" && enableFileUpload {
							r.Header.Set("X-Selenoid-File", filepath.Join(os.TempDir(), id))
							r.URL.Path = "/file"
							return
						}
					}
					r.URL.Host, r.URL.Path = sess.URL.Host, path.Clean(sess.URL.Path+r.URL.Path)
					return
				}
				r.URL.Path = "/error"
			},
		}).ServeHTTP(w, r)
	}(w, r)
	go (<-done)()
}

func fileUpload(w http.ResponseWriter, r *http.Request) {
	var jsonRequest struct {
		File []byte `json:"file"`
	}
	err := json.NewDecoder(r.Body).Decode(&jsonRequest)
	if err != nil {
		jsonError(w, err.Error(), http.StatusBadRequest)
		return
	}
	z, err := zip.NewReader(bytes.NewReader(jsonRequest.File), int64(len(jsonRequest.File)))
	if err != nil {
		jsonError(w, err.Error(), http.StatusBadRequest)
		return
	}
	if len(z.File) != 1 {
		jsonError(w, fmt.Sprintf("Expected there to be only 1 file. There were: %s", len(z.File)), http.StatusBadRequest)
		return
	}
	file := z.File[0]
	src, err := file.Open()
	if err != nil {
		jsonError(w, err.Error(), http.StatusBadRequest)
		return
	}
	defer src.Close()
	dir := r.Header.Get("X-Selenoid-File")
	err = os.Mkdir(dir, 0755)
	if err != nil {
		jsonError(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fileName := filepath.Join(dir, file.Name)
	dst, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		jsonError(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer dst.Close()
	_, err = io.Copy(dst, src)
	if err != nil {
		jsonError(w, err.Error(), http.StatusInternalServerError)
		return
	}

	reply := struct {
		V string `json:"value"`
	}{
		V: fileName,
	}
	json.NewEncoder(w).Encode(reply)
}

func vnc(wsconn *websocket.Conn) {
	defer wsconn.Close()
	sid := strings.Split(wsconn.Request().URL.Path, "/")[2]
	sess, ok := sessions.Get(sid)
	if ok {
		if sess.VNC != "" {
			log.Printf("[VNC_ENABLED] [%s]\n", sid)
			conn, err := net.Dial("tcp", sess.VNC)
			if err != nil {
				log.Printf("[VNC_ERROR] [%v]\n", err)
				return
			}
			defer conn.Close()
			wsconn.PayloadType = websocket.BinaryFrame
			go io.Copy(wsconn, conn)
			io.Copy(conn, wsconn)
			log.Printf("[VNC_CLIENT_DISCONNECTED] [%s]\n", sid)
		} else {
			log.Printf("[VNC_NOT_ENABLED] [%s]\n", sid)
		}
	} else {
		log.Printf("[SESSION_NOT_FOUND] [%s]\n", sid)
	}
}

func logs(wsconn *websocket.Conn) {
	defer wsconn.Close()
	sid := strings.Split(wsconn.Request().URL.Path, "/")[2]
	sess, ok := sessions.Get(sid)
	if ok && sess.Container != "" {
		log.Printf("[CONTAINER_LOGS] [%s]\n", sess.Container)
		r, err := cli.ContainerLogs(context.Background(), sess.Container, types.ContainerLogsOptions{
			ShowStdout: true,
			ShowStderr: true,
			Follow:     true,
		})
		if err != nil {
			log.Printf("[CONTAINER_LOGS_ERROR] [%v]\n", err)
			return
		}
		defer r.Close()
		wsconn.PayloadType = websocket.BinaryFrame
		io.Copy(wsconn, r)
		log.Printf("[WEBSOCCKET_CLIENT_DISCONNECTED] [%s]\n", sid)
	} else {
		log.Printf("[SESSION_NOT_FOUND] [%s]\n", sid)
	}
}

func onTimeout(t time.Duration, f func()) chan struct{} {
	cancel := make(chan struct{})
	go func(cancel chan struct{}) {
		select {
		case <-time.After(t):
			f()
		case <-cancel:
		}
	}(cancel)
	return cancel
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"github.com/caiofilipini/encurtador/url"
	"net/http"
	"os"
	"strings"
)

var (
	porta   string
	urlBase string
	ids     chan string
)

func init() {
	dominio := lerConfig("DOMINIO", "localhost")
	porta = lerConfig("PORTA", "8888")
	urlBase = fmt.Sprintf("http://%s:%s", dominio, porta)
}

type Headers map[string]string

func Encurtador(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		responderCom(w, http.StatusMethodNotAllowed, Headers{"Allow": "POST"})
		return
	}

	url, err := url.BuscarOuCriarNovaUrl(extrairUrl(r))

	if err != nil {
		responderCom(w, http.StatusBadRequest, nil)
		return
	}

	responderCom(w, http.StatusCreated, Headers{
		"Location": fmt.Sprintf("%s/r/%s", urlBase, url.Id),
		"Link":     fmt.Sprintf("<%s/api/stats/%s>; rel=\"stats\"", urlBase, url.Id),
	})
}

func Redirecionador(w http.ResponseWriter, r *http.Request) {
	id := extrairId(r)

	if url := url.Buscar(id); url != nil {
		http.Redirect(w, r, url.Destino, http.StatusMovedPermanently)
		ids <- id
	} else {
		http.NotFound(w, r)
	}
}

func Visualizador(w http.ResponseWriter, r *http.Request) {
	id := extrairId(r)

	if url := url.Buscar(id); url != nil {
		json, err := json.Marshal(url.Stats())

		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		responderComJSON(w, string(json))
	} else {
		http.NotFound(w, r)
	}
}

func responderCom(w http.ResponseWriter, status int, headers Headers) {
	for k, v := range headers {
		w.Header().Set(k, v)
	}
	w.WriteHeader(status)
}

func responderComJSON(w http.ResponseWriter, resposta string) {
	responderCom(w, http.StatusOK, Headers{"Content-Type": "application/json"})
	fmt.Fprintf(w, resposta)
}

func lerConfig(config string, valorPadrao string) string {
	if d := os.Getenv(config); d != "" {
		return d
	}
	return valorPadrao
}

func extrairUrl(r *http.Request) string {
	rawBody := make([]byte, r.ContentLength, r.ContentLength)
	r.Body.Read(rawBody)
	return string(rawBody)
}

func extrairId(r *http.Request) string {
	caminho := strings.Split(r.URL.Path, "/")
	return caminho[len(caminho)-1]
}

func registrarEstatisticas(ids chan string) {
	for id := range ids {
		url.RegistrarClick(id)
		fmt.Printf("Click registrado com sucesso para %s.\n", id)
	}
}

func main() {
	ids = make(chan string)
	defer close(ids)
	go registrarEstatisticas(ids)

	http.HandleFunc("/r/", Redirecionador)
	http.HandleFunc("/api/encurtar", Encurtador)
	http.HandleFunc("/api/stats/", Visualizador)

	http.ListenAndServe(":"+porta, nil)
}
<commit_msg>Extraindo função genérica para tratar URLs ou retornar 404.<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"github.com/caiofilipini/encurtador/url"
	"net/http"
	"os"
	"strings"
)

var (
	porta   string
	urlBase string
	ids     chan string
)

func init() {
	dominio := lerConfig("DOMINIO", "localhost")
	porta = lerConfig("PORTA", "8888")
	urlBase = fmt.Sprintf("http://%s:%s", dominio, porta)
}

type Headers map[string]string

func Encurtador(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		responderCom(w, http.StatusMethodNotAllowed, Headers{"Allow": "POST"})
		return
	}

	url, err := url.BuscarOuCriarNovaUrl(extrairUrl(r))

	if err != nil {
		responderCom(w, http.StatusBadRequest, nil)
		return
	}

	responderCom(w, http.StatusCreated, Headers{
		"Location": fmt.Sprintf("%s/r/%s", urlBase, url.Id),
		"Link":     fmt.Sprintf("<%s/api/stats/%s>; rel=\"stats\"", urlBase, url.Id),
	})
}

func Redirecionador(w http.ResponseWriter, r *http.Request) {
	buscarUrlEExecutar(w, r, func(url *url.Url) {
		http.Redirect(w, r, url.Destino, http.StatusMovedPermanently)
		ids <- url.Id
	})
}

func Visualizador(w http.ResponseWriter, r *http.Request) {
	buscarUrlEExecutar(w, r, func(url *url.Url) {
		json, err := json.Marshal(url.Stats())

		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		responderComJSON(w, string(json))
	})
}

func buscarUrlEExecutar(w http.ResponseWriter, r *http.Request, executor func(*url.Url)) {
	caminho := strings.Split(r.URL.Path, "/")
	id := caminho[len(caminho)-1]

	if url := url.Buscar(id); url != nil {
		executor(url)
	} else {
		http.NotFound(w, r)
	}
}

func responderCom(w http.ResponseWriter, status int, headers Headers) {
	for k, v := range headers {
		w.Header().Set(k, v)
	}
	w.WriteHeader(status)
}

func responderComJSON(w http.ResponseWriter, resposta string) {
	responderCom(w, http.StatusOK, Headers{"Content-Type": "application/json"})
	fmt.Fprintf(w, resposta)
}

func lerConfig(config string, valorPadrao string) string {
	if d := os.Getenv(config); d != "" {
		return d
	}
	return valorPadrao
}

func extrairUrl(r *http.Request) string {
	rawBody := make([]byte, r.ContentLength, r.ContentLength)
	r.Body.Read(rawBody)
	return string(rawBody)
}

func registrarEstatisticas(ids chan string) {
	for id := range ids {
		url.RegistrarClick(id)
		fmt.Printf("Click registrado com sucesso para %s.\n", id)
	}
}

func main() {
	ids = make(chan string)
	defer close(ids)
	go registrarEstatisticas(ids)

	http.HandleFunc("/r/", Redirecionador)
	http.HandleFunc("/api/encurtar", Encurtador)
	http.HandleFunc("/api/stats/", Visualizador)

	http.ListenAndServe(":"+porta, nil)
}
<|endoftext|>


<commit_before>package terminal

import (
	"log"
	"math"
	"strings"

	"engo.io/ecs"
	"engo.io/engo"
	"engo.io/engo/common"
	"github.com/lukevers/arp147/input"
	"github.com/lukevers/arp147/terminal/filesystem"
	"github.com/lukevers/arp147/ui"
)

// TerminalSystem is a scrollable, visual and text input-able system.
type TerminalSystem struct {
	pages map[int]*page
	page  int

	world *ecs.World
	vfs   *filesystem.VirtualFS
}

type TerminalViewer struct {
	ecs.BasicEntity
	common.RenderComponent
	common.SpaceComponent
}

// Remove is called whenever an Entity is removed from the World, in order to
// remove it from this sytem as well.
func (*TerminalSystem) Remove(ecs.BasicEntity) {
	// TODO
}

// Update is ran every frame, with `dt` being the time in seconds since the
// last frame.
func (*TerminalSystem) Update(dt float32) {
	// TODO
}

// New is the initialisation of the System.
func (ts *TerminalSystem) New(w *ecs.World) {
	ts.vfs = filesystem.New(ts.WriteError)
	ts.world = w
	ts.pages = make(map[int]*page)
	ts.pages[ts.page] = &page{
		lines: make(map[int]*line),
		line:  0,
	}

	ts.pages[ts.page].lines[ts.pages[ts.page].line] = &line{}
	ts.pages[ts.page].lines[ts.pages[ts.page].line].prefix(ts.delegateKeyPress)

	ts.registerKeys()
	ts.addBackground(w)

	log.Println("TerminalSystem initialized")
}

func (ts *TerminalSystem) addBackground(w *ecs.World) {
	bkg1 := &TerminalViewer{BasicEntity: ecs.NewBasic()}
	bkg2 := &TerminalViewer{BasicEntity: ecs.NewBasic()}
	bkg3 := &TerminalViewer{BasicEntity: ecs.NewBasic()}

	bkg1.SpaceComponent = common.SpaceComponent{
		Position: engo.Point{X: 0, Y: 0},
		Width:    800,
		Height:   800,
	}

	tbkg1, err := common.LoadedSprite("textures/bkg_t1.jpg")
	if err != nil {
		log.Fatal("Unable to load texture: " + err.Error())
	}

	bkg1.RenderComponent = common.RenderComponent{
		Drawable: tbkg1,
		Scale:    engo.Point{X: 1, Y: 1},
	}

	bkg2.SpaceComponent = common.SpaceComponent{
		Position: engo.Point{X: 800, Y: 0},
		Width:    400,
		Height:   400,
	}

	tbkg2, err := common.LoadedSprite("textures/bkg_t2.jpg")
	if err != nil {
		log.Fatal("Unable to load texture: " + err.Error())
	}

	bkg2.RenderComponent = common.RenderComponent{
		Drawable: tbkg2,
		Scale:    engo.Point{X: 1, Y: 1},
	}

	bkg3.SpaceComponent = common.SpaceComponent{
		Position: engo.Point{X: 800, Y: 400},
		Width:    400,
		Height:   400,
	}

	tbkg3, err := common.LoadedSprite("textures/bkg_t3.jpg")
	if err != nil {
		log.Fatal("Unable to load texture: " + err.Error())
	}

	bkg3.RenderComponent = common.RenderComponent{
		Drawable: tbkg3,
		Scale:    engo.Point{X: 1, Y: 1},
	}

	for _, system := range w.Systems() {
		switch sys := system.(type) {
		case *common.RenderSystem:
			sys.Add(&bkg1.BasicEntity, &bkg1.RenderComponent, &bkg1.SpaceComponent)
			sys.Add(&bkg2.BasicEntity, &bkg2.RenderComponent, &bkg2.SpaceComponent)
			sys.Add(&bkg3.BasicEntity, &bkg3.RenderComponent, &bkg3.SpaceComponent)
		}
	}
}

func (ts *TerminalSystem) registerKeys() {
	input.RegisterKeys([]input.Key{
		input.Key{
			Name: "terminal-keys",
			Keys: []engo.Key{
				engo.KeyA,
				engo.KeyB,
				engo.KeyC,
				engo.KeyD,
				engo.KeyE,
				engo.KeyF,
				engo.KeyG,
				engo.KeyH,
				engo.KeyI,
				engo.KeyJ,
				engo.KeyK,
				engo.KeyL,
				engo.KeyM,
				engo.KeyN,
				engo.KeyO,
				engo.KeyP,
				engo.KeyQ,
				engo.KeyR,
				engo.KeyS,
				engo.KeyT,
				engo.KeyU,
				engo.KeyV,
				engo.KeyW,
				engo.KeyX,
				engo.KeyY,
				engo.KeyZ,

				engo.KeyZero,
				engo.KeyOne,
				engo.KeyTwo,
				engo.KeyThree,
				engo.KeyFour,
				engo.KeyFive,
				engo.KeySix,
				engo.KeySeven,
				engo.KeyEight,
				engo.KeyNine,

				engo.KeyBackspace,
				engo.KeyEnter,
				engo.KeySpace,
				engo.KeyTab,
				engo.KeyEscape,

				engo.KeyDash,
				engo.KeyGrave,
				engo.KeyApostrophe,
				engo.KeySemicolon,
				engo.KeyEquals,
				engo.KeyComma,
				engo.KeyPeriod,
				engo.KeySlash,
				engo.KeyBackslash,
				engo.KeyLeftBracket,
				engo.KeyRightBracket,

				engo.KeyArrowUp,
				engo.KeyArrowDown,
			},
			OnPress: ts.delegateKeyPress,
		},
	})
}

func (ts *TerminalSystem) delegateKeyPress(key engo.Key, mods *input.Modifiers) {
	if ts.pages[ts.page] == nil {
		ts.pages[ts.page] = &page{
			lines: make(map[int]*line),
			line:  0,
		}
	}

	if ts.pages[ts.page].lines[ts.pages[ts.page].line] == nil {
		ts.pages[ts.page].lines[ts.pages[ts.page].line] = &line{}
		ts.pages[ts.page].lines[ts.pages[ts.page].line].prefix(ts.delegateKeyPress)
	}

	length := len(ts.pages[ts.page].lines[ts.pages[ts.page].line].text)
	prefixCount := ts.pages[ts.page].lines[ts.pages[ts.page].line].prefixCount
	switch key {
	case engo.KeyBackspace:
		if ts.pages[ts.page].readonly && !mods.Output {
			break
		}

		if (length - prefixCount) > 0 {
			ts.pages[ts.page].lines[ts.pages[ts.page].line].text = ts.pages[ts.page].lines[ts.pages[ts.page].line].text[0 : length-1]
			ts.pages[ts.page].lines[ts.pages[ts.page].line].chars[length-1].Remove(ts.world)
			ts.pages[ts.page].lines[ts.pages[ts.page].line].chars = ts.pages[ts.page].lines[ts.pages[ts.page].line].chars[0 : length-1]
		}
	case engo.KeyArrowUp:
		// yoffset := float32(ts.pages[ts.page].line * int(16))
		// if yoffset > 704 && ts.pages[ts.page].enil > 0 {
		// 	ts.pages[ts.page].pushScreenDown()
		// }
	case engo.KeyArrowDown:
		// yoffset := float32(ts.pages[ts.page].line * 16)
		// if yoffset > 704 {
		// 	ts.pages[ts.page].pushScreenUp()
		// }
	case engo.KeyEscape:
		if ts.pages[ts.page].escapable {
			ts.pages[ts.page].hide()
			delete(ts.pages, ts.page)
			ts.page--
			ts.pages[ts.page].show()

			ts.pages[ts.page].lines[ts.pages[ts.page].line] = &line{}
			ts.pages[ts.page].lines[ts.pages[ts.page].line].prefix(ts.delegateKeyPress)
		}
	case engo.KeyEnter:
		if ts.pages[ts.page].readonly && !mods.Output {
			break
		}

		ts.pages[ts.page].lines[ts.pages[ts.page].line].locked = true

		xoffset := ts.getXoffset()
		if xoffset > 710 {
			ts.pages[ts.page].line += int(math.Floor(float64(xoffset)/710)) + 1
		} else {
			ts.pages[ts.page].line++
		}

		yoffset := float32(ts.pages[ts.page].line * 16)
		if yoffset > 704 {
			ts.pages[ts.page].pushScreenUp()
		}

		if !mods.Ignore {
			ts.pages[ts.page].lines[ts.pages[ts.page].line-1].evaluate(ts)
		}

		// Add a new line after everything
		ts.pages[ts.page].lines[ts.pages[ts.page].line] = &line{}
		if !mods.Ignore {
			ts.pages[ts.page].lines[ts.pages[ts.page].line].prefix(ts.delegateKeyPress)
		}
	default:
		if ts.pages[ts.page].readonly && !mods.Output {
			break
		}

		symbol := ""
		if mods.Line == nil {
			symbol = input.KeyToString(key, mods)
		} else {
			symbol = *mods.Line
		}

		ts.pages[ts.page].lines[ts.pages[ts.page].line].text = append(ts.pages[ts.page].lines[ts.pages[ts.page].line].text, symbol)
		char := ui.NewText(symbol)

		push := false
		var xoffset, yoffset float32
		xoffset = ts.getXoffset()
		yoffset = float32(ts.pages[ts.page].lineOffset() * 16)

		if xoffset >= 710 {
			lines := int(math.Floor(float64(xoffset) / 710))

			xoffset = xoffset - float32(707*lines)
			yoffset += float32(16) * float32(lines)

			if yoffset > 704 {
				push = true
			}
		}

		char.X = 35 + xoffset
		char.Y = 35 + yoffset

		char.Insert(ts.world)
		ts.pages[ts.page].lines[ts.pages[ts.page].line].chars = append(ts.pages[ts.page].lines[ts.pages[ts.page].line].chars, char)

		if push {
			ts.pages[ts.page].pushScreenUp()
		}
	}
}

func (ts *TerminalSystem) getXoffset() float32 {
	if ts.pages[ts.page].lines[ts.pages[ts.page].line] == nil {
		return 0
	}

	return float32(len(ts.pages[ts.page].lines[ts.pages[ts.page].line].text)*int(16)) * .65
}

func (ts *TerminalSystem) WriteLine(str string) {
	ts.pages[ts.page].lines[ts.pages[ts.page].line] = &line{}

	line := ""
	for _, char := range strings.Split(str, "") {
		if char == "\t" {
			char = "    "
		}

		line += char
	}

	ts.delegateKeyPress(engo.Key(-1), &input.Modifiers{Output: true, Line: &line})
	ts.delegateKeyPress(engo.KeyEnter, &input.Modifiers{Ignore: true, Output: true})
}

func (ts *TerminalSystem) WriteError(err error) {
	for _, line := range strings.Split(err.Error(), "\n") {
		ts.WriteLine(line)
	}
}
<commit_msg>Fix issues with \t breaking typing<commit_after>package terminal

import (
	"log"
	"math"
	"strings"

	"engo.io/ecs"
	"engo.io/engo"
	"engo.io/engo/common"
	"github.com/lukevers/arp147/input"
	"github.com/lukevers/arp147/terminal/filesystem"
	"github.com/lukevers/arp147/ui"
)

// TerminalSystem is a scrollable, visual and text input-able system.
type TerminalSystem struct {
	pages map[int]*page
	page  int

	world *ecs.World
	vfs   *filesystem.VirtualFS
}

type TerminalViewer struct {
	ecs.BasicEntity
	common.RenderComponent
	common.SpaceComponent
}

// Remove is called whenever an Entity is removed from the World, in order to
// remove it from this sytem as well.
func (*TerminalSystem) Remove(ecs.BasicEntity) {
	// TODO
}

// Update is ran every frame, with `dt` being the time in seconds since the
// last frame.
func (*TerminalSystem) Update(dt float32) {
	// TODO
}

// New is the initialisation of the System.
func (ts *TerminalSystem) New(w *ecs.World) {
	ts.vfs = filesystem.New(ts.WriteError)
	ts.world = w
	ts.pages = make(map[int]*page)
	ts.pages[ts.page] = &page{
		lines: make(map[int]*line),
		line:  0,
	}

	ts.pages[ts.page].lines[ts.pages[ts.page].line] = &line{}
	ts.pages[ts.page].lines[ts.pages[ts.page].line].prefix(ts.delegateKeyPress)

	ts.registerKeys()
	ts.addBackground(w)

	log.Println("TerminalSystem initialized")
}

func (ts *TerminalSystem) addBackground(w *ecs.World) {
	bkg1 := &TerminalViewer{BasicEntity: ecs.NewBasic()}
	bkg2 := &TerminalViewer{BasicEntity: ecs.NewBasic()}
	bkg3 := &TerminalViewer{BasicEntity: ecs.NewBasic()}

	bkg1.SpaceComponent = common.SpaceComponent{
		Position: engo.Point{X: 0, Y: 0},
		Width:    800,
		Height:   800,
	}

	tbkg1, err := common.LoadedSprite("textures/bkg_t1.jpg")
	if err != nil {
		log.Fatal("Unable to load texture: " + err.Error())
	}

	bkg1.RenderComponent = common.RenderComponent{
		Drawable: tbkg1,
		Scale:    engo.Point{X: 1, Y: 1},
	}

	bkg2.SpaceComponent = common.SpaceComponent{
		Position: engo.Point{X: 800, Y: 0},
		Width:    400,
		Height:   400,
	}

	tbkg2, err := common.LoadedSprite("textures/bkg_t2.jpg")
	if err != nil {
		log.Fatal("Unable to load texture: " + err.Error())
	}

	bkg2.RenderComponent = common.RenderComponent{
		Drawable: tbkg2,
		Scale:    engo.Point{X: 1, Y: 1},
	}

	bkg3.SpaceComponent = common.SpaceComponent{
		Position: engo.Point{X: 800, Y: 400},
		Width:    400,
		Height:   400,
	}

	tbkg3, err := common.LoadedSprite("textures/bkg_t3.jpg")
	if err != nil {
		log.Fatal("Unable to load texture: " + err.Error())
	}

	bkg3.RenderComponent = common.RenderComponent{
		Drawable: tbkg3,
		Scale:    engo.Point{X: 1, Y: 1},
	}

	for _, system := range w.Systems() {
		switch sys := system.(type) {
		case *common.RenderSystem:
			sys.Add(&bkg1.BasicEntity, &bkg1.RenderComponent, &bkg1.SpaceComponent)
			sys.Add(&bkg2.BasicEntity, &bkg2.RenderComponent, &bkg2.SpaceComponent)
			sys.Add(&bkg3.BasicEntity, &bkg3.RenderComponent, &bkg3.SpaceComponent)
		}
	}
}

func (ts *TerminalSystem) registerKeys() {
	input.RegisterKeys([]input.Key{
		input.Key{
			Name: "terminal-keys",
			Keys: []engo.Key{
				engo.KeyA,
				engo.KeyB,
				engo.KeyC,
				engo.KeyD,
				engo.KeyE,
				engo.KeyF,
				engo.KeyG,
				engo.KeyH,
				engo.KeyI,
				engo.KeyJ,
				engo.KeyK,
				engo.KeyL,
				engo.KeyM,
				engo.KeyN,
				engo.KeyO,
				engo.KeyP,
				engo.KeyQ,
				engo.KeyR,
				engo.KeyS,
				engo.KeyT,
				engo.KeyU,
				engo.KeyV,
				engo.KeyW,
				engo.KeyX,
				engo.KeyY,
				engo.KeyZ,

				engo.KeyZero,
				engo.KeyOne,
				engo.KeyTwo,
				engo.KeyThree,
				engo.KeyFour,
				engo.KeyFive,
				engo.KeySix,
				engo.KeySeven,
				engo.KeyEight,
				engo.KeyNine,

				engo.KeyBackspace,
				engo.KeyEnter,
				engo.KeySpace,
				engo.KeyTab,
				engo.KeyEscape,

				engo.KeyDash,
				engo.KeyGrave,
				engo.KeyApostrophe,
				engo.KeySemicolon,
				engo.KeyEquals,
				engo.KeyComma,
				engo.KeyPeriod,
				engo.KeySlash,
				engo.KeyBackslash,
				engo.KeyLeftBracket,
				engo.KeyRightBracket,

				engo.KeyArrowUp,
				engo.KeyArrowDown,
			},
			OnPress: ts.delegateKeyPress,
		},
	})
}

func (ts *TerminalSystem) delegateKeyPress(key engo.Key, mods *input.Modifiers) {
	if ts.pages[ts.page] == nil {
		ts.pages[ts.page] = &page{
			lines: make(map[int]*line),
			line:  0,
		}
	}

	if ts.pages[ts.page].lines[ts.pages[ts.page].line] == nil {
		ts.pages[ts.page].lines[ts.pages[ts.page].line] = &line{}
		ts.pages[ts.page].lines[ts.pages[ts.page].line].prefix(ts.delegateKeyPress)
	}

	length := len(ts.pages[ts.page].lines[ts.pages[ts.page].line].text)
	prefixCount := ts.pages[ts.page].lines[ts.pages[ts.page].line].prefixCount
	switch key {
	case engo.KeyTab:
		// TODO: auto-completion?
	case engo.KeyBackspace:
		if ts.pages[ts.page].readonly && !mods.Output {
			break
		}

		if (length - prefixCount) > 0 {
			ts.pages[ts.page].lines[ts.pages[ts.page].line].text = ts.pages[ts.page].lines[ts.pages[ts.page].line].text[0 : length-1]
			ts.pages[ts.page].lines[ts.pages[ts.page].line].chars[length-1].Remove(ts.world)
			ts.pages[ts.page].lines[ts.pages[ts.page].line].chars = ts.pages[ts.page].lines[ts.pages[ts.page].line].chars[0 : length-1]
		}
	case engo.KeyArrowUp:
		// yoffset := float32(ts.pages[ts.page].line * int(16))
		// if yoffset > 704 && ts.pages[ts.page].enil > 0 {
		// 	ts.pages[ts.page].pushScreenDown()
		// }
	case engo.KeyArrowDown:
		// yoffset := float32(ts.pages[ts.page].line * 16)
		// if yoffset > 704 {
		// 	ts.pages[ts.page].pushScreenUp()
		// }
	case engo.KeyEscape:
		if ts.pages[ts.page].escapable {
			ts.pages[ts.page].hide()
			delete(ts.pages, ts.page)
			ts.page--
			ts.pages[ts.page].show()

			ts.pages[ts.page].lines[ts.pages[ts.page].line] = &line{}
			ts.pages[ts.page].lines[ts.pages[ts.page].line].prefix(ts.delegateKeyPress)
		}
	case engo.KeyEnter:
		if ts.pages[ts.page].readonly && !mods.Output {
			break
		}

		ts.pages[ts.page].lines[ts.pages[ts.page].line].locked = true

		xoffset := ts.getXoffset()
		if xoffset > 710 {
			ts.pages[ts.page].line += int(math.Floor(float64(xoffset)/710)) + 1
		} else {
			ts.pages[ts.page].line++
		}

		yoffset := float32(ts.pages[ts.page].line * 16)
		if yoffset > 704 {
			ts.pages[ts.page].pushScreenUp()
		}

		if !mods.Ignore {
			ts.pages[ts.page].lines[ts.pages[ts.page].line-1].evaluate(ts)
		}

		// Add a new line after everything
		ts.pages[ts.page].lines[ts.pages[ts.page].line] = &line{}
		if !mods.Ignore {
			ts.pages[ts.page].lines[ts.pages[ts.page].line].prefix(ts.delegateKeyPress)
		}
	default:
		if ts.pages[ts.page].readonly && !mods.Output {
			break
		}

		symbol := ""
		if mods.Line == nil {
			symbol = input.KeyToString(key, mods)
		} else {
			symbol = *mods.Line
		}

		ts.pages[ts.page].lines[ts.pages[ts.page].line].text = append(ts.pages[ts.page].lines[ts.pages[ts.page].line].text, symbol)
		char := ui.NewText(symbol)

		push := false
		var xoffset, yoffset float32
		xoffset = ts.getXoffset()
		yoffset = float32(ts.pages[ts.page].lineOffset() * 16)

		if xoffset >= 710 {
			lines := int(math.Floor(float64(xoffset) / 710))

			xoffset = xoffset - float32(707*lines)
			yoffset += float32(16) * float32(lines)

			if yoffset > 704 {
				push = true
			}
		}

		char.X = 35 + xoffset
		char.Y = 35 + yoffset

		char.Insert(ts.world)
		ts.pages[ts.page].lines[ts.pages[ts.page].line].chars = append(ts.pages[ts.page].lines[ts.pages[ts.page].line].chars, char)

		if push {
			ts.pages[ts.page].pushScreenUp()
		}
	}
}

func (ts *TerminalSystem) getXoffset() float32 {
	if ts.pages[ts.page].lines[ts.pages[ts.page].line] == nil {
		return 0
	}

	return float32(len(ts.pages[ts.page].lines[ts.pages[ts.page].line].text)*int(16)) * .65
}

func (ts *TerminalSystem) WriteLine(str string) {
	ts.pages[ts.page].lines[ts.pages[ts.page].line] = &line{}

	line := ""
	for _, char := range strings.Split(str, "") {
		if char == "\t" {
			char = "    "
		}

		line += char
	}

	ts.delegateKeyPress(engo.Key(-1), &input.Modifiers{Output: true, Line: &line})
	ts.delegateKeyPress(engo.KeyEnter, &input.Modifiers{Ignore: true, Output: true})
}

func (ts *TerminalSystem) WriteError(err error) {
	for _, line := range strings.Split(err.Error(), "\n") {
		ts.WriteLine(line)
	}
}
<|endoftext|>


<commit_before>package terraform

import (
	"fmt"
	"log"
	"strings"
	"sync"

	"github.com/hashicorp/terraform/dag"
)

// RootModuleName is the name given to the root module implicitly.
const RootModuleName = "root"

// RootModulePath is the path for the root module.
var RootModulePath = []string{RootModuleName}

// Graph represents the graph that Terraform uses to represent resources
// and their dependencies. Each graph represents only one module, but it
// can contain further modules, which themselves have their own graph.
type Graph struct {
	// Graph is the actual DAG. This is embedded so you can call the DAG
	// methods directly.
	dag.AcyclicGraph

	// Path is the path in the module tree that this Graph represents.
	// The root is represented by a single element list containing
	// RootModuleName
	Path []string

	// annotations are the annotations that are added to vertices. Annotations
	// are arbitrary metadata taht is used for various logic. Annotations
	// should have unique keys that are referenced via constants.
	annotations map[dag.Vertex]map[string]interface{}

	// dependableMap is a lookaside table for fast lookups for connecting
	// dependencies by their GraphNodeDependable value to avoid O(n^3)-like
	// situations and turn them into O(1) with respect to the number of new
	// edges.
	dependableMap map[string]dag.Vertex

	once sync.Once
}

// Annotations returns the annotations that are configured for the
// given vertex. The map is guaranteed to be non-nil but may be empty.
//
// The returned map may be modified to modify the annotations of the
// vertex.
func (g *Graph) Annotations(v dag.Vertex) map[string]interface{} {
	g.once.Do(g.init)

	// If this vertex isn't in the graph, then just return an empty map
	if !g.HasVertex(v) {
		return map[string]interface{}{}
	}

	// Get the map, if it doesn't exist yet then initialize it
	m, ok := g.annotations[v]
	if !ok {
		m = make(map[string]interface{})
		g.annotations[v] = m
	}

	return m
}

// Add is the same as dag.Graph.Add.
func (g *Graph) Add(v dag.Vertex) dag.Vertex {
	g.once.Do(g.init)

	// Call upwards to add it to the actual graph
	g.Graph.Add(v)

	// If this is a depend-able node, then store the lookaside info
	if dv, ok := v.(GraphNodeDependable); ok {
		for _, n := range dv.DependableName() {
			g.dependableMap[n] = v
		}
	}

	// If this initializes annotations, then do that
	if av, ok := v.(GraphNodeAnnotationInit); ok {
		as := g.Annotations(v)
		for k, v := range av.AnnotationInit() {
			as[k] = v
		}
	}

	return v
}

// Remove is the same as dag.Graph.Remove
func (g *Graph) Remove(v dag.Vertex) dag.Vertex {
	g.once.Do(g.init)

	// If this is a depend-able node, then remove the lookaside info
	if dv, ok := v.(GraphNodeDependable); ok {
		for _, n := range dv.DependableName() {
			delete(g.dependableMap, n)
		}
	}

	// Remove the annotations
	delete(g.annotations, v)

	// Call upwards to remove it from the actual graph
	return g.Graph.Remove(v)
}

// Replace is the same as dag.Graph.Replace
func (g *Graph) Replace(o, n dag.Vertex) bool {
	g.once.Do(g.init)

	// Go through and update our lookaside to point to the new vertex
	for k, v := range g.dependableMap {
		if v == o {
			if _, ok := n.(GraphNodeDependable); ok {
				g.dependableMap[k] = n
			} else {
				delete(g.dependableMap, k)
			}
		}
	}

	// Move the annotation if it exists
	if m, ok := g.annotations[o]; ok {
		g.annotations[n] = m
		delete(g.annotations, o)
	}

	return g.Graph.Replace(o, n)
}

// ConnectDependent connects a GraphNodeDependent to all of its
// GraphNodeDependables. It returns the list of dependents it was
// unable to connect to.
func (g *Graph) ConnectDependent(raw dag.Vertex) []string {
	v, ok := raw.(GraphNodeDependent)
	if !ok {
		return nil
	}

	return g.ConnectTo(v, v.DependentOn())
}

// ConnectDependents goes through the graph, connecting all the
// GraphNodeDependents to GraphNodeDependables. This is safe to call
// multiple times.
//
// To get details on whether dependencies could be found/made, the more
// specific ConnectDependent should be used.
func (g *Graph) ConnectDependents() {
	for _, v := range g.Vertices() {
		if dv, ok := v.(GraphNodeDependent); ok {
			g.ConnectDependent(dv)
		}
	}
}

// ConnectFrom creates an edge by finding the source from a DependableName
// and connecting it to the specific vertex.
func (g *Graph) ConnectFrom(source string, target dag.Vertex) {
	g.once.Do(g.init)

	if source := g.dependableMap[source]; source != nil {
		g.Connect(dag.BasicEdge(source, target))
	}
}

// ConnectTo connects a vertex to a raw string of targets that are the
// result of DependableName, and returns the list of targets that are missing.
func (g *Graph) ConnectTo(v dag.Vertex, targets []string) []string {
	g.once.Do(g.init)

	var missing []string
	for _, t := range targets {
		if dest := g.dependableMap[t]; dest != nil {
			g.Connect(dag.BasicEdge(v, dest))
		} else {
			missing = append(missing, t)
		}
	}

	return missing
}

// Dependable finds the vertices in the graph that have the given dependable
// names and returns them.
func (g *Graph) Dependable(n string) dag.Vertex {
	// TODO: do we need this?
	return nil
}

// Walk walks the graph with the given walker for callbacks. The graph
// will be walked with full parallelism, so the walker should expect
// to be called in concurrently.
func (g *Graph) Walk(walker GraphWalker) error {
	return g.walk(walker)
}

func (g *Graph) init() {
	if g.annotations == nil {
		g.annotations = make(map[dag.Vertex]map[string]interface{})
	}

	if g.dependableMap == nil {
		g.dependableMap = make(map[string]dag.Vertex)
	}
}

func (g *Graph) walk(walker GraphWalker) error {
	// The callbacks for enter/exiting a graph
	ctx := walker.EnterPath(g.Path)
	defer walker.ExitPath(g.Path)

	// Get the path for logs
	path := strings.Join(ctx.Path(), ".")

	// Walk the graph.
	var walkFn dag.WalkFunc
	walkFn = func(v dag.Vertex) (rerr error) {
		log.Printf("[DEBUG] vertex %s.%s: walking", path, dag.VertexName(v))

		walker.EnterVertex(v)
		defer func() { walker.ExitVertex(v, rerr) }()

		// vertexCtx is the context that we use when evaluating. This
		// is normally the context of our graph but can be overridden
		// with a GraphNodeSubPath impl.
		vertexCtx := ctx
		if pn, ok := v.(GraphNodeSubPath); ok && len(pn.Path()) > 0 {
			vertexCtx = walker.EnterPath(normalizeModulePath(pn.Path()))
			defer walker.ExitPath(pn.Path())
		}

		// If the node is eval-able, then evaluate it.
		if ev, ok := v.(GraphNodeEvalable); ok {
			tree := ev.EvalTree()
			if tree == nil {
				panic(fmt.Sprintf(
					"%s.%s (%T): nil eval tree", path, dag.VertexName(v), v))
			}

			// Allow the walker to change our tree if needed. Eval,
			// then callback with the output.
			log.Printf("[DEBUG] vertex '%s.%s': evaluating", path, dag.VertexName(v))
			tree = walker.EnterEvalTree(v, tree)
			output, err := Eval(tree, vertexCtx)
			if rerr = walker.ExitEvalTree(v, output, err); rerr != nil {
				return
			}
		}

		// If the node is dynamically expanded, then expand it
		if ev, ok := v.(GraphNodeDynamicExpandable); ok {
			log.Printf(
				"[DEBUG] vertex '%s.%s': expanding/walking dynamic subgraph",
				path,
				dag.VertexName(v))
			g, err := ev.DynamicExpand(vertexCtx)
			if err != nil {
				rerr = err
				return
			}
			if g != nil {
				// Walk the subgraph
				if rerr = g.walk(walker); rerr != nil {
					return
				}
			}
		}

		// If the node has a subgraph, then walk the subgraph
		if sn, ok := v.(GraphNodeSubgraph); ok {
			log.Printf(
				"[DEBUG] vertex '%s.%s': walking subgraph",
				path,
				dag.VertexName(v))

			if rerr = sn.Subgraph().walk(walker); rerr != nil {
				return
			}
		}

		return nil
	}

	return g.AcyclicGraph.Walk(walkFn)
}

// GraphNodeAnnotationInit is an interface that allows a node to
// initialize it's annotations.
//
// AnnotationInit will be called _once_ when the node is added to a
// graph for the first time and is expected to return it's initial
// annotations.
type GraphNodeAnnotationInit interface {
	AnnotationInit() map[string]interface{}
}

// GraphNodeDependable is an interface which says that a node can be
// depended on (an edge can be placed between this node and another) according
// to the well-known name returned by DependableName.
//
// DependableName can return multiple names it is known by.
type GraphNodeDependable interface {
	DependableName() []string
}

// GraphNodeDependent is an interface which says that a node depends
// on another GraphNodeDependable by some name. By implementing this
// interface, Graph.ConnectDependents() can be called multiple times
// safely and efficiently.
type GraphNodeDependent interface {
	DependentOn() []string
}
<commit_msg>Missed a spot where panic: could still happen<commit_after>package terraform

import (
	"fmt"
	"log"
	"strings"
	"sync"

	"github.com/hashicorp/terraform/dag"
)

// RootModuleName is the name given to the root module implicitly.
const RootModuleName = "root"

// RootModulePath is the path for the root module.
var RootModulePath = []string{RootModuleName}

// Graph represents the graph that Terraform uses to represent resources
// and their dependencies. Each graph represents only one module, but it
// can contain further modules, which themselves have their own graph.
type Graph struct {
	// Graph is the actual DAG. This is embedded so you can call the DAG
	// methods directly.
	dag.AcyclicGraph

	// Path is the path in the module tree that this Graph represents.
	// The root is represented by a single element list containing
	// RootModuleName
	Path []string

	// annotations are the annotations that are added to vertices. Annotations
	// are arbitrary metadata taht is used for various logic. Annotations
	// should have unique keys that are referenced via constants.
	annotations map[dag.Vertex]map[string]interface{}

	// dependableMap is a lookaside table for fast lookups for connecting
	// dependencies by their GraphNodeDependable value to avoid O(n^3)-like
	// situations and turn them into O(1) with respect to the number of new
	// edges.
	dependableMap map[string]dag.Vertex

	once sync.Once
}

// Annotations returns the annotations that are configured for the
// given vertex. The map is guaranteed to be non-nil but may be empty.
//
// The returned map may be modified to modify the annotations of the
// vertex.
func (g *Graph) Annotations(v dag.Vertex) map[string]interface{} {
	g.once.Do(g.init)

	// If this vertex isn't in the graph, then just return an empty map
	if !g.HasVertex(v) {
		return map[string]interface{}{}
	}

	// Get the map, if it doesn't exist yet then initialize it
	m, ok := g.annotations[v]
	if !ok {
		m = make(map[string]interface{})
		g.annotations[v] = m
	}

	return m
}

// Add is the same as dag.Graph.Add.
func (g *Graph) Add(v dag.Vertex) dag.Vertex {
	g.once.Do(g.init)

	// Call upwards to add it to the actual graph
	g.Graph.Add(v)

	// If this is a depend-able node, then store the lookaside info
	if dv, ok := v.(GraphNodeDependable); ok {
		for _, n := range dv.DependableName() {
			g.dependableMap[n] = v
		}
	}

	// If this initializes annotations, then do that
	if av, ok := v.(GraphNodeAnnotationInit); ok {
		as := g.Annotations(v)
		for k, v := range av.AnnotationInit() {
			as[k] = v
		}
	}

	return v
}

// Remove is the same as dag.Graph.Remove
func (g *Graph) Remove(v dag.Vertex) dag.Vertex {
	g.once.Do(g.init)

	// If this is a depend-able node, then remove the lookaside info
	if dv, ok := v.(GraphNodeDependable); ok {
		for _, n := range dv.DependableName() {
			delete(g.dependableMap, n)
		}
	}

	// Remove the annotations
	delete(g.annotations, v)

	// Call upwards to remove it from the actual graph
	return g.Graph.Remove(v)
}

// Replace is the same as dag.Graph.Replace
func (g *Graph) Replace(o, n dag.Vertex) bool {
	g.once.Do(g.init)

	// Go through and update our lookaside to point to the new vertex
	for k, v := range g.dependableMap {
		if v == o {
			if _, ok := n.(GraphNodeDependable); ok {
				g.dependableMap[k] = n
			} else {
				delete(g.dependableMap, k)
			}
		}
	}

	// Move the annotation if it exists
	if m, ok := g.annotations[o]; ok {
		g.annotations[n] = m
		delete(g.annotations, o)
	}

	return g.Graph.Replace(o, n)
}

// ConnectDependent connects a GraphNodeDependent to all of its
// GraphNodeDependables. It returns the list of dependents it was
// unable to connect to.
func (g *Graph) ConnectDependent(raw dag.Vertex) []string {
	v, ok := raw.(GraphNodeDependent)
	if !ok {
		return nil
	}

	return g.ConnectTo(v, v.DependentOn())
}

// ConnectDependents goes through the graph, connecting all the
// GraphNodeDependents to GraphNodeDependables. This is safe to call
// multiple times.
//
// To get details on whether dependencies could be found/made, the more
// specific ConnectDependent should be used.
func (g *Graph) ConnectDependents() {
	for _, v := range g.Vertices() {
		if dv, ok := v.(GraphNodeDependent); ok {
			g.ConnectDependent(dv)
		}
	}
}

// ConnectFrom creates an edge by finding the source from a DependableName
// and connecting it to the specific vertex.
func (g *Graph) ConnectFrom(source string, target dag.Vertex) {
	g.once.Do(g.init)

	if source := g.dependableMap[source]; source != nil {
		g.Connect(dag.BasicEdge(source, target))
	}
}

// ConnectTo connects a vertex to a raw string of targets that are the
// result of DependableName, and returns the list of targets that are missing.
func (g *Graph) ConnectTo(v dag.Vertex, targets []string) []string {
	g.once.Do(g.init)

	var missing []string
	for _, t := range targets {
		if dest := g.dependableMap[t]; dest != nil {
			g.Connect(dag.BasicEdge(v, dest))
		} else {
			missing = append(missing, t)
		}
	}

	return missing
}

// Dependable finds the vertices in the graph that have the given dependable
// names and returns them.
func (g *Graph) Dependable(n string) dag.Vertex {
	// TODO: do we need this?
	return nil
}

// Walk walks the graph with the given walker for callbacks. The graph
// will be walked with full parallelism, so the walker should expect
// to be called in concurrently.
func (g *Graph) Walk(walker GraphWalker) error {
	return g.walk(walker)
}

func (g *Graph) init() {
	if g.annotations == nil {
		g.annotations = make(map[dag.Vertex]map[string]interface{})
	}

	if g.dependableMap == nil {
		g.dependableMap = make(map[string]dag.Vertex)
	}
}

func (g *Graph) walk(walker GraphWalker) error {
	// The callbacks for enter/exiting a graph
	ctx := walker.EnterPath(g.Path)
	defer walker.ExitPath(g.Path)

	// Get the path for logs
	path := strings.Join(ctx.Path(), ".")

	// Walk the graph.
	var walkFn dag.WalkFunc
	walkFn = func(v dag.Vertex) (rerr error) {
		log.Printf("[DEBUG] vertex '%s.%s': walking", path, dag.VertexName(v))

		walker.EnterVertex(v)
		defer func() { walker.ExitVertex(v, rerr) }()

		// vertexCtx is the context that we use when evaluating. This
		// is normally the context of our graph but can be overridden
		// with a GraphNodeSubPath impl.
		vertexCtx := ctx
		if pn, ok := v.(GraphNodeSubPath); ok && len(pn.Path()) > 0 {
			vertexCtx = walker.EnterPath(normalizeModulePath(pn.Path()))
			defer walker.ExitPath(pn.Path())
		}

		// If the node is eval-able, then evaluate it.
		if ev, ok := v.(GraphNodeEvalable); ok {
			tree := ev.EvalTree()
			if tree == nil {
				panic(fmt.Sprintf(
					"%s.%s (%T): nil eval tree", path, dag.VertexName(v), v))
			}

			// Allow the walker to change our tree if needed. Eval,
			// then callback with the output.
			log.Printf("[DEBUG] vertex '%s.%s': evaluating", path, dag.VertexName(v))
			tree = walker.EnterEvalTree(v, tree)
			output, err := Eval(tree, vertexCtx)
			if rerr = walker.ExitEvalTree(v, output, err); rerr != nil {
				return
			}
		}

		// If the node is dynamically expanded, then expand it
		if ev, ok := v.(GraphNodeDynamicExpandable); ok {
			log.Printf(
				"[DEBUG] vertex '%s.%s': expanding/walking dynamic subgraph",
				path,
				dag.VertexName(v))
			g, err := ev.DynamicExpand(vertexCtx)
			if err != nil {
				rerr = err
				return
			}
			if g != nil {
				// Walk the subgraph
				if rerr = g.walk(walker); rerr != nil {
					return
				}
			}
		}

		// If the node has a subgraph, then walk the subgraph
		if sn, ok := v.(GraphNodeSubgraph); ok {
			log.Printf(
				"[DEBUG] vertex '%s.%s': walking subgraph",
				path,
				dag.VertexName(v))

			if rerr = sn.Subgraph().walk(walker); rerr != nil {
				return
			}
		}

		return nil
	}

	return g.AcyclicGraph.Walk(walkFn)
}

// GraphNodeAnnotationInit is an interface that allows a node to
// initialize it's annotations.
//
// AnnotationInit will be called _once_ when the node is added to a
// graph for the first time and is expected to return it's initial
// annotations.
type GraphNodeAnnotationInit interface {
	AnnotationInit() map[string]interface{}
}

// GraphNodeDependable is an interface which says that a node can be
// depended on (an edge can be placed between this node and another) according
// to the well-known name returned by DependableName.
//
// DependableName can return multiple names it is known by.
type GraphNodeDependable interface {
	DependableName() []string
}

// GraphNodeDependent is an interface which says that a node depends
// on another GraphNodeDependable by some name. By implementing this
// interface, Graph.ConnectDependents() can be called multiple times
// safely and efficiently.
type GraphNodeDependent interface {
	DependentOn() []string
}
<|endoftext|>