<commit_before>package platform

import (
	"fmt"
	"os"
	"path"
	"path/filepath"

	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/drud/ddev/pkg/cms/config"
	"github.com/drud/ddev/pkg/cms/model"
	"github.com/drud/ddev/pkg/ddevapp"
	"github.com/drud/drud-go/utils/dockerutil"
	"github.com/drud/drud-go/utils/network"
	"github.com/drud/drud-go/utils/stringutil"
	"github.com/drud/drud-go/utils/system"
	"github.com/lextoumbourou/goodhosts"
)

// LocalApp implements the AppBase interface local development apps
type LocalApp struct {
	AppBase
	AppConfig *ddevapp.Config
}

// NewLocalApp creates a new LocalApp based on any application root specified by appRoot
func NewLocalApp(appRoot string) *LocalApp {
	app := &LocalApp{}
	config, err := ddevapp.NewConfig(appRoot)
	app.AppConfig = config

	err = PrepLocalSiteDirs(appRoot)
	if err != nil {
		log.Fatalln(err)
	}
	return app
}

// GetType returns the application type as a (lowercase) string
func (l *LocalApp) GetType() string {
	return strings.ToLower(l.AppConfig.AppType)
}

// Init populates LocalApp settings based on the current working directory.
func (l *LocalApp) Init() error {
	basePath := l.AbsPath()
	config, err := ddevapp.NewConfig(basePath)
	if err != nil {
		return err
	}
	l.AppConfig = config

	err = PrepLocalSiteDirs(basePath)
	if err != nil {
		log.Fatalln(err)
	}

	return nil
}

// AbsPath return the full path from root to the app directory
func (l LocalApp) AbsPath() string {
	cwd, err := os.Getwd()
	if err != nil {
		log.Printf("Error determining the current directory: %s", err)
	}

	appPath, err := CheckForConf(cwd)
	if err != nil {
		log.Fatalf("Unable to determine the application for this command - have you run 'ddev config'? Error: %s", err)
	}

	return appPath
}

// GetName returns the  name for local app
func (l LocalApp) GetName() string {
	return l.AppConfig.Name
}

// ContainerPrefix returns the base name for local app containers
func (l LocalApp) ContainerPrefix() string {
	return "local"
}

// ContainerName returns the base name for local app containers
func (l LocalApp) ContainerName() string {
	return fmt.Sprintf("%s-%s", l.ContainerPrefix(), l.GetName())
}

// GetResources downloads external data for this app
func (l *LocalApp) GetResources() error {

	fmt.Println("Getting Resources.")
	err := l.GetArchive()
	if err != nil {
		log.Println(err)
		fmt.Println(fmt.Errorf("Error retrieving site resources: %s", err))
	}

	return nil
}

// GetArchive downloads external data
func (l *LocalApp) GetArchive() error {
	name := fmt.Sprintf("production-%s.tar.gz", l.GetName())
	basePath := l.AbsPath()
	archive := path.Join(basePath, ".ddev", name)

	if system.FileExists(archive) {
		l.Archive = archive
	}
	return nil
}

// DockerComposeYAMLPath returns the absolute path to where the docker-compose.yaml should exist for this app configuration.
// This is a bit redundant, but is here to avoid having to expose too many details of AppConfig.
func (l LocalApp) DockerComposeYAMLPath() string {
	return l.AppConfig.DockerComposeYAMLPath()
}

// UnpackResources takes the archive from the GetResources method and
// unarchives it. Then the contents are moved to their proper locations.
func (l LocalApp) UnpackResources() error {
	basePath := l.AbsPath()
	fileDir := ""

	if l.GetType() == "wordpress" {
		fileDir = "content/uploads"
	} else if l.GetType() == "drupal7" || l.GetType() == "drupal8" {
		fileDir = "sites/default/files"
	}

	out, err := system.RunCommand(
		"tar",
		[]string{
			"-xzvf",
			l.Archive,
			"-C", path.Join(basePath, ".ddev", "files"),
			"--exclude=sites/default/settings.php",
			"--exclude=docroot/wp-config.php",
		},
	)
	if err != nil {
		fmt.Println(out)
		return err
	}

	err = os.Rename(
		path.Join(basePath, ".ddev", "files", l.GetName()+".sql"),
		path.Join(basePath, ".ddev", "data", "data.sql"),
	)
	if err != nil {
		return err
	}

	// Ensure sites/default is readable.
	if l.GetType() == "drupal7" || l.GetType() == "drupal8" {
		os.Chmod(path.Join(basePath, ".ddev", "files", "docroot", "sites", "default"), 0755)
	}

	rsyncFrom := path.Join(basePath, ".ddev", "files", "docroot", fileDir)
	rsyncTo := path.Join(basePath, "docroot", fileDir)
	out, err = system.RunCommand(
		"rsync",
		[]string{"-avz", "--recursive", rsyncFrom + "/", rsyncTo},
	)
	if err != nil {
		return fmt.Errorf("%s - %s", err.Error(), string(out))
	}

	// Ensure extracted files are writable so they can be removed when we're done.
	out, err = system.RunCommand(
		"chmod",
		[]string{"-R", "ugo+rw", path.Join(basePath, ".ddev", "files")},
	)
	if err != nil {
		return fmt.Errorf("%s - %s", err.Error(), string(out))
	}
	defer os.RemoveAll(path.Join(basePath, ".ddev", "files"))

	return nil
}

// Start initiates docker-compose up
func (l LocalApp) Start() error {
	composePath := l.DockerComposeYAMLPath()
	l.DockerEnv()

	// Write docker-compose.yaml (if it doesn't exist).
	// If the user went through the `ddev config` process it will be written already, but
	// we also do it here in the case of a manually created `.ddev/config.yaml` file.
	if !system.FileExists(l.AppConfig.DockerComposeYAMLPath()) {
		err := l.AppConfig.WriteDockerComposeConfig()
		if err != nil {
			return err
		}
	}

	EnsureDockerRouter()

	err := l.AddHostsEntry()
	if err != nil {
		log.Fatal(err)
	}

	cmdArgs := []string{"-f", composePath, "pull"}
	_, err = system.RunCommand("docker-compose", cmdArgs)
	if err != nil {
		return err
	}

	return dockerutil.DockerCompose(
		"-f", composePath,
		"up",
		"-d",
	)
}

// DockerEnv sets environment variables for a docker-compose run.
func (l LocalApp) DockerEnv() {
	envVars := map[string]string{
		"COMPOSE_PROJECT_NAME": "ddev-" + l.AppConfig.Name,
		"DDEV_SITENAME":        l.AppConfig.Name,
		"DDEV_DBIMAGE":         l.AppConfig.DBImage,
		"DDEV_WEBIMAGE":        l.AppConfig.WebImage,
		"DDEV_APPROOT":         l.AppConfig.AppRoot,
		"DDEV_DOCROOT":         filepath.Join(l.AppConfig.AppRoot, l.AppConfig.Docroot),
		"DDEV_URL":             l.URL(),
		"DDEV_HOSTNAME":        l.HostName(),
	}

	// Only set values if they don't already exist in env.
	for k, v := range envVars {
		if os.Getenv(k) == "" {

			log.WithFields(log.Fields{
				"Key":   k,
				"Value": v,
			}).Debug("Setting DockerEnv variable")

			err := os.Setenv(k, v)
			// @ TODO: I have no idea what a Setenv error would even look like, so I'm not sure what
			// to do other than notify the user.
			if err != nil {
				fmt.Printf("Could not set the environment variable %s=%s: %v\n", k, v, err)
			}
		}
	}
}

// Stop initiates docker-compose stop
func (l LocalApp) Stop() error {
	composePath := l.DockerComposeYAMLPath()
	l.DockerEnv()

	if !dockerutil.IsRunning(l.ContainerName()+"-db") && !dockerutil.IsRunning(l.ContainerName()+"-web") && !ComposeFileExists(&l) {
		return fmt.Errorf("site does not exist or is malformed")
	}

	return dockerutil.DockerCompose(
		"-f", composePath,
		"stop",
	)
}

// Wait ensures that the app appears to be read before returning
func (l *LocalApp) Wait() (string, error) {
	o := network.NewHTTPOptions("http://127.0.0.1/healthcheck")
	o.Timeout = 90
	o.Headers["Host"] = l.HostName()
	err := network.EnsureHTTPStatus(o)
	if err != nil {
		return "", fmt.Errorf("200 Was not returned from the web container")
	}

	return l.URL(), nil
}

// FindPorts retrieves the public ports for db and web containers
func (l *LocalApp) FindPorts() error {
	var err error
	l.WebPublicPort, err = GetPodPort(l.ContainerName() + "-web")
	if err != nil {
		return err
	}

	l.DbPublicPort, err = GetPodPort(l.ContainerName() + "-db")
	return err
}

// Config creates the apps config file adding things like database host, name, and password
// as well as other sensitive data like salts.
func (l *LocalApp) Config() error {
	basePath := l.AbsPath()

	err := l.FindPorts()
	if err != nil {
		return err
	}

	settingsFilePath := ""
	if l.GetType() == "drupal7" || l.GetType() == "drupal8" {
		log.Printf("Drupal site. Creating settings.php file.")
		settingsFilePath = path.Join(basePath, "docroot/sites/default/settings.php")
		drupalConfig := model.NewDrupalConfig()
		drupalConfig.DatabaseHost = "db"
		if drupalConfig.HashSalt == "" {
			drupalConfig.HashSalt = stringutil.RandomString(64)
		}
		if l.GetType() == "drupal8" {
			drupalConfig.IsDrupal8 = true
		}

		drupalConfig.DeployURL = l.URL()
		err = config.WriteDrupalConfig(drupalConfig, settingsFilePath)
		if err != nil {
			log.Fatalln(err)
		}

		// Setup a custom settings file for use with drush.
		dbPort, err := GetPodPort(l.ContainerName() + "-db")
		if err != nil {
			return err
		}

		drushSettingsPath := path.Join(basePath, "drush.settings.php")
		drushConfig := model.NewDrushConfig()
		drushConfig.DatabasePort = dbPort
		if l.GetType() == "drupal8" {
			drushConfig.IsDrupal8 = true
		}
		err = config.WriteDrushConfig(drushConfig, drushSettingsPath)

		if err != nil {
			log.Fatalln(err)
		}
	} else if l.GetType() == "wordpress" {
		log.Printf("WordPress site. Creating wp-config.php file.")
		settingsFilePath = path.Join(basePath, "docroot/wp-config.php")
		wpConfig := model.NewWordpressConfig()
		wpConfig.DatabaseHost = "db"
		wpConfig.DeployURL = l.URL()
		wpConfig.AuthKey = stringutil.RandomString(64)
		wpConfig.AuthSalt = stringutil.RandomString(64)
		wpConfig.LoggedInKey = stringutil.RandomString(64)
		wpConfig.LoggedInSalt = stringutil.RandomString(64)
		wpConfig.NonceKey = stringutil.RandomString(64)
		wpConfig.NonceSalt = stringutil.RandomString(64)
		wpConfig.SecureAuthKey = stringutil.RandomString(64)
		wpConfig.SecureAuthSalt = stringutil.RandomString(64)
		err = config.WriteWordpressConfig(wpConfig, settingsFilePath)
		if err != nil {
			log.Fatalln(err)
		}
	}
	return nil
}

// Down stops the docker containers for the local project.
func (l *LocalApp) Down() error {
	composePath := l.DockerComposeYAMLPath()
	l.DockerEnv()
	err := dockerutil.DockerCompose(
		"-f", composePath,
		"down",
	)
	if err != nil {
		return Cleanup(l)
	}

	return nil
}

// URL returns the URL for a given application.
func (l *LocalApp) URL() string {
	return "http://" + l.AppConfig.Hostname()
}

// HostName returns the hostname of a given application.
func (l *LocalApp) HostName() string {
	return l.AppConfig.Hostname()
}

// AddHostsEntry will add the local site URL to the local hostfile.
func (l *LocalApp) AddHostsEntry() error {
	if os.Getenv("DRUD_NONINTERACTIVE") != "" {
		fmt.Printf("DRUD_NONINTERACTIVE is set. If this message is not in a test you may want to add the following entry to your host file:\n127.0.0.1 %s\n", l.HostName())
		return nil
	}

	hosts, err := goodhosts.NewHosts()
	if err != nil {
		log.Fatalf("could not open hostfile. %s", err)
	}
	if hosts.Has("127.0.0.1", l.HostName()) {
		return nil
	}

	fmt.Println("\n\n\nAdding hostfile entry. You will be prompted for your password.")
	hostnameArgs := []string{"ddev", "hostname", l.HostName(), "127.0.0.1"}
	err = system.RunCommandPipe("sudo", hostnameArgs)
	return err
}
<commit_msg>Remove docker-compose pull from startup as we no longer need it (#115)<commit_after>package platform

import (
	"fmt"
	"os"
	"path"
	"path/filepath"

	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/drud/ddev/pkg/cms/config"
	"github.com/drud/ddev/pkg/cms/model"
	"github.com/drud/ddev/pkg/ddevapp"
	"github.com/drud/drud-go/utils/dockerutil"
	"github.com/drud/drud-go/utils/network"
	"github.com/drud/drud-go/utils/stringutil"
	"github.com/drud/drud-go/utils/system"
	"github.com/lextoumbourou/goodhosts"
)

// LocalApp implements the AppBase interface local development apps
type LocalApp struct {
	AppBase
	AppConfig *ddevapp.Config
}

// NewLocalApp creates a new LocalApp based on any application root specified by appRoot
func NewLocalApp(appRoot string) *LocalApp {
	app := &LocalApp{}
	config, err := ddevapp.NewConfig(appRoot)
	app.AppConfig = config

	err = PrepLocalSiteDirs(appRoot)
	if err != nil {
		log.Fatalln(err)
	}
	return app
}

// GetType returns the application type as a (lowercase) string
func (l *LocalApp) GetType() string {
	return strings.ToLower(l.AppConfig.AppType)
}

// Init populates LocalApp settings based on the current working directory.
func (l *LocalApp) Init() error {
	basePath := l.AbsPath()
	config, err := ddevapp.NewConfig(basePath)
	if err != nil {
		return err
	}
	l.AppConfig = config

	err = PrepLocalSiteDirs(basePath)
	if err != nil {
		log.Fatalln(err)
	}

	return nil
}

// AbsPath return the full path from root to the app directory
func (l LocalApp) AbsPath() string {
	cwd, err := os.Getwd()
	if err != nil {
		log.Printf("Error determining the current directory: %s", err)
	}

	appPath, err := CheckForConf(cwd)
	if err != nil {
		log.Fatalf("Unable to determine the application for this command - have you run 'ddev config'? Error: %s", err)
	}

	return appPath
}

// GetName returns the  name for local app
func (l LocalApp) GetName() string {
	return l.AppConfig.Name
}

// ContainerPrefix returns the base name for local app containers
func (l LocalApp) ContainerPrefix() string {
	return "local"
}

// ContainerName returns the base name for local app containers
func (l LocalApp) ContainerName() string {
	return fmt.Sprintf("%s-%s", l.ContainerPrefix(), l.GetName())
}

// GetResources downloads external data for this app
func (l *LocalApp) GetResources() error {

	fmt.Println("Getting Resources.")
	err := l.GetArchive()
	if err != nil {
		log.Println(err)
		fmt.Println(fmt.Errorf("Error retrieving site resources: %s", err))
	}

	return nil
}

// GetArchive downloads external data
func (l *LocalApp) GetArchive() error {
	name := fmt.Sprintf("production-%s.tar.gz", l.GetName())
	basePath := l.AbsPath()
	archive := path.Join(basePath, ".ddev", name)

	if system.FileExists(archive) {
		l.Archive = archive
	}
	return nil
}

// DockerComposeYAMLPath returns the absolute path to where the docker-compose.yaml should exist for this app configuration.
// This is a bit redundant, but is here to avoid having to expose too many details of AppConfig.
func (l LocalApp) DockerComposeYAMLPath() string {
	return l.AppConfig.DockerComposeYAMLPath()
}

// UnpackResources takes the archive from the GetResources method and
// unarchives it. Then the contents are moved to their proper locations.
func (l LocalApp) UnpackResources() error {
	basePath := l.AbsPath()
	fileDir := ""

	if l.GetType() == "wordpress" {
		fileDir = "content/uploads"
	} else if l.GetType() == "drupal7" || l.GetType() == "drupal8" {
		fileDir = "sites/default/files"
	}

	out, err := system.RunCommand(
		"tar",
		[]string{
			"-xzvf",
			l.Archive,
			"-C", path.Join(basePath, ".ddev", "files"),
			"--exclude=sites/default/settings.php",
			"--exclude=docroot/wp-config.php",
		},
	)
	if err != nil {
		fmt.Println(out)
		return err
	}

	err = os.Rename(
		path.Join(basePath, ".ddev", "files", l.GetName()+".sql"),
		path.Join(basePath, ".ddev", "data", "data.sql"),
	)
	if err != nil {
		return err
	}

	// Ensure sites/default is readable.
	if l.GetType() == "drupal7" || l.GetType() == "drupal8" {
		os.Chmod(path.Join(basePath, ".ddev", "files", "docroot", "sites", "default"), 0755)
	}

	rsyncFrom := path.Join(basePath, ".ddev", "files", "docroot", fileDir)
	rsyncTo := path.Join(basePath, "docroot", fileDir)
	out, err = system.RunCommand(
		"rsync",
		[]string{"-avz", "--recursive", rsyncFrom + "/", rsyncTo},
	)
	if err != nil {
		return fmt.Errorf("%s - %s", err.Error(), string(out))
	}

	// Ensure extracted files are writable so they can be removed when we're done.
	out, err = system.RunCommand(
		"chmod",
		[]string{"-R", "ugo+rw", path.Join(basePath, ".ddev", "files")},
	)
	if err != nil {
		return fmt.Errorf("%s - %s", err.Error(), string(out))
	}
	defer os.RemoveAll(path.Join(basePath, ".ddev", "files"))

	return nil
}

// Start initiates docker-compose up
func (l LocalApp) Start() error {
	composePath := l.DockerComposeYAMLPath()
	l.DockerEnv()

	// Write docker-compose.yaml (if it doesn't exist).
	// If the user went through the `ddev config` process it will be written already, but
	// we also do it here in the case of a manually created `.ddev/config.yaml` file.
	if !system.FileExists(l.AppConfig.DockerComposeYAMLPath()) {
		err := l.AppConfig.WriteDockerComposeConfig()
		if err != nil {
			return err
		}
	}

	EnsureDockerRouter()

	err := l.AddHostsEntry()
	if err != nil {
		log.Fatal(err)
	}

	return dockerutil.DockerCompose(
		"-f", composePath,
		"up",
		"-d",
	)
}

// DockerEnv sets environment variables for a docker-compose run.
func (l LocalApp) DockerEnv() {
	envVars := map[string]string{
		"COMPOSE_PROJECT_NAME": "ddev-" + l.AppConfig.Name,
		"DDEV_SITENAME":        l.AppConfig.Name,
		"DDEV_DBIMAGE":         l.AppConfig.DBImage,
		"DDEV_WEBIMAGE":        l.AppConfig.WebImage,
		"DDEV_APPROOT":         l.AppConfig.AppRoot,
		"DDEV_DOCROOT":         filepath.Join(l.AppConfig.AppRoot, l.AppConfig.Docroot),
		"DDEV_URL":             l.URL(),
		"DDEV_HOSTNAME":        l.HostName(),
	}

	// Only set values if they don't already exist in env.
	for k, v := range envVars {
		if os.Getenv(k) == "" {

			log.WithFields(log.Fields{
				"Key":   k,
				"Value": v,
			}).Debug("Setting DockerEnv variable")

			err := os.Setenv(k, v)
			// @ TODO: I have no idea what a Setenv error would even look like, so I'm not sure what
			// to do other than notify the user.
			if err != nil {
				fmt.Printf("Could not set the environment variable %s=%s: %v\n", k, v, err)
			}
		}
	}
}

// Stop initiates docker-compose stop
func (l LocalApp) Stop() error {
	composePath := l.DockerComposeYAMLPath()
	l.DockerEnv()

	if !dockerutil.IsRunning(l.ContainerName()+"-db") && !dockerutil.IsRunning(l.ContainerName()+"-web") && !ComposeFileExists(&l) {
		return fmt.Errorf("site does not exist or is malformed")
	}

	return dockerutil.DockerCompose(
		"-f", composePath,
		"stop",
	)
}

// Wait ensures that the app appears to be read before returning
func (l *LocalApp) Wait() (string, error) {
	o := network.NewHTTPOptions("http://127.0.0.1/healthcheck")
	o.Timeout = 90
	o.Headers["Host"] = l.HostName()
	err := network.EnsureHTTPStatus(o)
	if err != nil {
		return "", fmt.Errorf("200 Was not returned from the web container")
	}

	return l.URL(), nil
}

// FindPorts retrieves the public ports for db and web containers
func (l *LocalApp) FindPorts() error {
	var err error
	l.WebPublicPort, err = GetPodPort(l.ContainerName() + "-web")
	if err != nil {
		return err
	}

	l.DbPublicPort, err = GetPodPort(l.ContainerName() + "-db")
	return err
}

// Config creates the apps config file adding things like database host, name, and password
// as well as other sensitive data like salts.
func (l *LocalApp) Config() error {
	basePath := l.AbsPath()

	err := l.FindPorts()
	if err != nil {
		return err
	}

	settingsFilePath := ""
	if l.GetType() == "drupal7" || l.GetType() == "drupal8" {
		log.Printf("Drupal site. Creating settings.php file.")
		settingsFilePath = path.Join(basePath, "docroot/sites/default/settings.php")
		drupalConfig := model.NewDrupalConfig()
		drupalConfig.DatabaseHost = "db"
		if drupalConfig.HashSalt == "" {
			drupalConfig.HashSalt = stringutil.RandomString(64)
		}
		if l.GetType() == "drupal8" {
			drupalConfig.IsDrupal8 = true
		}

		drupalConfig.DeployURL = l.URL()
		err = config.WriteDrupalConfig(drupalConfig, settingsFilePath)
		if err != nil {
			log.Fatalln(err)
		}

		// Setup a custom settings file for use with drush.
		dbPort, err := GetPodPort(l.ContainerName() + "-db")
		if err != nil {
			return err
		}

		drushSettingsPath := path.Join(basePath, "drush.settings.php")
		drushConfig := model.NewDrushConfig()
		drushConfig.DatabasePort = dbPort
		if l.GetType() == "drupal8" {
			drushConfig.IsDrupal8 = true
		}
		err = config.WriteDrushConfig(drushConfig, drushSettingsPath)

		if err != nil {
			log.Fatalln(err)
		}
	} else if l.GetType() == "wordpress" {
		log.Printf("WordPress site. Creating wp-config.php file.")
		settingsFilePath = path.Join(basePath, "docroot/wp-config.php")
		wpConfig := model.NewWordpressConfig()
		wpConfig.DatabaseHost = "db"
		wpConfig.DeployURL = l.URL()
		wpConfig.AuthKey = stringutil.RandomString(64)
		wpConfig.AuthSalt = stringutil.RandomString(64)
		wpConfig.LoggedInKey = stringutil.RandomString(64)
		wpConfig.LoggedInSalt = stringutil.RandomString(64)
		wpConfig.NonceKey = stringutil.RandomString(64)
		wpConfig.NonceSalt = stringutil.RandomString(64)
		wpConfig.SecureAuthKey = stringutil.RandomString(64)
		wpConfig.SecureAuthSalt = stringutil.RandomString(64)
		err = config.WriteWordpressConfig(wpConfig, settingsFilePath)
		if err != nil {
			log.Fatalln(err)
		}
	}
	return nil
}

// Down stops the docker containers for the local project.
func (l *LocalApp) Down() error {
	composePath := l.DockerComposeYAMLPath()
	l.DockerEnv()
	err := dockerutil.DockerCompose(
		"-f", composePath,
		"down",
	)
	if err != nil {
		return Cleanup(l)
	}

	return nil
}

// URL returns the URL for a given application.
func (l *LocalApp) URL() string {
	return "http://" + l.AppConfig.Hostname()
}

// HostName returns the hostname of a given application.
func (l *LocalApp) HostName() string {
	return l.AppConfig.Hostname()
}

// AddHostsEntry will add the local site URL to the local hostfile.
func (l *LocalApp) AddHostsEntry() error {
	if os.Getenv("DRUD_NONINTERACTIVE") != "" {
		fmt.Printf("DRUD_NONINTERACTIVE is set. If this message is not in a test you may want to add the following entry to your host file:\n127.0.0.1 %s\n", l.HostName())
		return nil
	}

	hosts, err := goodhosts.NewHosts()
	if err != nil {
		log.Fatalf("could not open hostfile. %s", err)
	}
	if hosts.Has("127.0.0.1", l.HostName()) {
		return nil
	}

	fmt.Println("\n\n\nAdding hostfile entry. You will be prompted for your password.")
	hostnameArgs := []string{"ddev", "hostname", l.HostName(), "127.0.0.1"}
	err = system.RunCommandPipe("sudo", hostnameArgs)
	return err
}
<|endoftext|>


<commit_before>package config

import (
	"flag"
	"io/ioutil"
	"path/filepath"

	"github.com/weaveworks/common/server"
	"gopkg.in/yaml.v2"

	"github.com/grafana/loki/pkg/promtail/client"
	"github.com/grafana/loki/pkg/promtail/positions"
	"github.com/grafana/loki/pkg/promtail/scrape"
	"github.com/grafana/loki/pkg/promtail/targets"
)

// Config for promtail, describing what files to watch.
type Config struct {
	ServerConfig    server.Config    `yaml:"server,omitempty"`
	ClientConfig    client.Config    `yaml:"client,omitempty"`
	PositionsConfig positions.Config `yaml:"positions,omitempty"`
	ScrapeConfig    []scrape.Config  `yaml:"scrape_configs,omitempty"`
	TargetConfig    targets.Config   `yaml:"target_config,omitempty"`
}

// RegisterFlags registers flags.
func (c *Config) RegisterFlags(f *flag.FlagSet) {
	c.ServerConfig.RegisterFlags(f)
	c.ClientConfig.RegisterFlags(f)
	c.PositionsConfig.RegisterFlags(f)
	c.TargetConfig.RegisterFlags(f)
}

// LoadConfig loads config from a file.
func LoadConfig(filename string) (*Config, error) {
	buf, err := ioutil.ReadFile(filepath.Clean(filename))
	if err != nil {
		return nil, err
	}

	var cfg Config
	if err := yaml.UnmarshalStrict(buf, &cfg); err != nil {
		return nil, err
	}

	return &cfg, nil
}
<commit_msg>Remove useless func (#472)<commit_after>package config

import (
	"flag"

	"github.com/weaveworks/common/server"

	"github.com/grafana/loki/pkg/promtail/client"
	"github.com/grafana/loki/pkg/promtail/positions"
	"github.com/grafana/loki/pkg/promtail/scrape"
	"github.com/grafana/loki/pkg/promtail/targets"
)

// Config for promtail, describing what files to watch.
type Config struct {
	ServerConfig    server.Config    `yaml:"server,omitempty"`
	ClientConfig    client.Config    `yaml:"client,omitempty"`
	PositionsConfig positions.Config `yaml:"positions,omitempty"`
	ScrapeConfig    []scrape.Config  `yaml:"scrape_configs,omitempty"`
	TargetConfig    targets.Config   `yaml:"target_config,omitempty"`
}

// RegisterFlags registers flags.
func (c *Config) RegisterFlags(f *flag.FlagSet) {
	c.ServerConfig.RegisterFlags(f)
	c.ClientConfig.RegisterFlags(f)
	c.PositionsConfig.RegisterFlags(f)
	c.TargetConfig.RegisterFlags(f)
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes Authors.

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

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

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

package server

import (
	"context"
	"math/rand"
	"sync"
	"time"

	"k8s.io/klog/v2"
	client "sigs.k8s.io/apiserver-network-proxy/konnectivity-client/proto/client"
	"sigs.k8s.io/apiserver-network-proxy/proto/agent"
)

type Backend interface {
	Send(p *client.Packet) error
	Context() context.Context
}

var _ Backend = &backend{}
var _Backend = new(agent.AgentService_ConnectServer)

type backend struct {
	// TODO: this is a multi-writer single-reader pattern, it's tricky to
	// write it using channel. Let's worry about performance later.
	mu   sync.Mutex // mu protects conn
	conn agent.AgentService_ConnectServer
}

func (b *backend) Send(p *client.Packet) error {
	b.mu.Lock()
	defer b.mu.Unlock()
	return b.conn.Send(p)
}

func (b *backend) Context() context.Context {
	// TODO: does Context require lock protection?
	return b.conn.Context()
}

func newBackend(conn agent.AgentService_ConnectServer) *backend {
	return &backend{conn: conn}
}

// BackendManager is an interface to manage backend connections, i.e.,
// connection to the proxy agents.
type BackendManager interface {
	// Backend returns a single backend.
	Backend() (Backend, error)
	// AddBackend adds a backend.
	AddBackend(agentID string, conn agent.AgentService_ConnectServer)
	// RemoveBackend removes a backend.
	RemoveBackend(agentID string, conn agent.AgentService_ConnectServer)
}

var _ BackendManager = &DefaultBackendManager{}

// DefaultBackendManager is the default backend manager.
type DefaultBackendManager struct {
	mu sync.RWMutex //protects the following
	// A map between agentID and its grpc connections.
	// For a given agent, ProxyServer prefers backends[agentID][0] to send
	// traffic, because backends[agentID][1:] are more likely to be closed
	// by the agent to deduplicate connections to the same server.
	backends map[string][]*backend
	// agentID is tracked in this slice to enable randomly picking an
	// agentID in the Backend() method. There is no reliable way to
	// randomly pick a key from a map (in this case, the backends) in
	// Golang.
	agentIDs []string
	random   *rand.Rand
}

// NewDefaultBackendManager returns a DefaultBackendManager.
func NewDefaultBackendManager() *DefaultBackendManager {
	return &DefaultBackendManager{
		backends: make(map[string][]*backend),
		random:   rand.New(rand.NewSource(time.Now().UnixNano())),
	}
}

// AddBackend adds a backend.
func (s *DefaultBackendManager) AddBackend(agentID string, conn agent.AgentService_ConnectServer) {
	klog.Infof("register Backend %v for agentID %s", conn, agentID)
	s.mu.Lock()
	defer s.mu.Unlock()
	_, ok := s.backends[agentID]
	if ok {
		for _, v := range s.backends[agentID] {
			if v.conn == conn {
				klog.Warningf("this should not happen. Adding existing connection %v for agentID %s", conn, agentID)
				return
			}
		}
		s.backends[agentID] = append(s.backends[agentID], newBackend(conn))
		return
	}
	s.backends[agentID] = []*backend{newBackend(conn)}
	s.agentIDs = append(s.agentIDs, agentID)
}

// RemoveBackend removes a backend.
func (s *DefaultBackendManager) RemoveBackend(agentID string, conn agent.AgentService_ConnectServer) {
	klog.Infof("remove Backend %v for agentID %s", conn, agentID)
	s.mu.Lock()
	defer s.mu.Unlock()
	backends, ok := s.backends[agentID]
	if !ok {
		klog.Warningf("can't find agentID %s in the backends", agentID)
		return
	}
	var found bool
	for i, c := range backends {
		if c.conn == conn {
			s.backends[agentID] = append(s.backends[agentID][:i], s.backends[agentID][i+1:]...)
			if i == 0 && len(s.backends[agentID]) != 0 {
				klog.Warningf("this should not happen. Removed connection %v that is not the first connection, remaining connections are %v", conn, s.backends[agentID])
			}
			found = true
		}
	}
	if len(s.backends[agentID]) == 0 {
		delete(s.backends, agentID)
		for i := range s.agentIDs {
			if s.agentIDs[i] == agentID {
				s.agentIDs[i] = s.agentIDs[len(s.agentIDs)-1]
				s.agentIDs = s.agentIDs[:len(s.agentIDs)-1]
				break
			}
		}
	}
	if !found {
		klog.Errorf("can't find conn %v for agentID %s in the backends", conn, agentID)
	}
}

// ErrNotFound indicates that no backend can be found.
type ErrNotFound struct{}

// Error returns the error message.
func (e *ErrNotFound) Error() string {
	return "No backend available"
}

// Backend returns a random backend.
func (s *DefaultBackendManager) Backend() (Backend, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	if len(s.backends) == 0 {
		return nil, &ErrNotFound{}
	}
	agentID := s.agentIDs[s.random.Intn(len(s.agentIDs))]
	klog.Infof("pick agentID=%s as backend", agentID)
	// always return the first connection to an agent, because the agent
	// will close later connections if there are multiple.
	return s.backends[agentID][0], nil
}
<commit_msg>fixes minor interface assertion<commit_after>/*
Copyright 2020 The Kubernetes Authors.

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

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

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

package server

import (
	"context"
	"math/rand"
	"sync"
	"time"

	"k8s.io/klog/v2"
	client "sigs.k8s.io/apiserver-network-proxy/konnectivity-client/proto/client"
	"sigs.k8s.io/apiserver-network-proxy/proto/agent"
)

type Backend interface {
	Send(p *client.Packet) error
	Context() context.Context
}

var _ Backend = &backend{}
var _ Backend = agent.AgentService_ConnectServer(nil)

type backend struct {
	// TODO: this is a multi-writer single-reader pattern, it's tricky to
	// write it using channel. Let's worry about performance later.
	mu   sync.Mutex // mu protects conn
	conn agent.AgentService_ConnectServer
}

func (b *backend) Send(p *client.Packet) error {
	b.mu.Lock()
	defer b.mu.Unlock()
	return b.conn.Send(p)
}

func (b *backend) Context() context.Context {
	// TODO: does Context require lock protection?
	return b.conn.Context()
}

func newBackend(conn agent.AgentService_ConnectServer) *backend {
	return &backend{conn: conn}
}

// BackendManager is an interface to manage backend connections, i.e.,
// connection to the proxy agents.
type BackendManager interface {
	// Backend returns a single backend.
	Backend() (Backend, error)
	// AddBackend adds a backend.
	AddBackend(agentID string, conn agent.AgentService_ConnectServer)
	// RemoveBackend removes a backend.
	RemoveBackend(agentID string, conn agent.AgentService_ConnectServer)
}

var _ BackendManager = &DefaultBackendManager{}

// DefaultBackendManager is the default backend manager.
type DefaultBackendManager struct {
	mu sync.RWMutex //protects the following
	// A map between agentID and its grpc connections.
	// For a given agent, ProxyServer prefers backends[agentID][0] to send
	// traffic, because backends[agentID][1:] are more likely to be closed
	// by the agent to deduplicate connections to the same server.
	backends map[string][]*backend
	// agentID is tracked in this slice to enable randomly picking an
	// agentID in the Backend() method. There is no reliable way to
	// randomly pick a key from a map (in this case, the backends) in
	// Golang.
	agentIDs []string
	random   *rand.Rand
}

// NewDefaultBackendManager returns a DefaultBackendManager.
func NewDefaultBackendManager() *DefaultBackendManager {
	return &DefaultBackendManager{
		backends: make(map[string][]*backend),
		random:   rand.New(rand.NewSource(time.Now().UnixNano())),
	}
}

// AddBackend adds a backend.
func (s *DefaultBackendManager) AddBackend(agentID string, conn agent.AgentService_ConnectServer) {
	klog.Infof("register Backend %v for agentID %s", conn, agentID)
	s.mu.Lock()
	defer s.mu.Unlock()
	_, ok := s.backends[agentID]
	if ok {
		for _, v := range s.backends[agentID] {
			if v.conn == conn {
				klog.Warningf("this should not happen. Adding existing connection %v for agentID %s", conn, agentID)
				return
			}
		}
		s.backends[agentID] = append(s.backends[agentID], newBackend(conn))
		return
	}
	s.backends[agentID] = []*backend{newBackend(conn)}
	s.agentIDs = append(s.agentIDs, agentID)
}

// RemoveBackend removes a backend.
func (s *DefaultBackendManager) RemoveBackend(agentID string, conn agent.AgentService_ConnectServer) {
	klog.Infof("remove Backend %v for agentID %s", conn, agentID)
	s.mu.Lock()
	defer s.mu.Unlock()
	backends, ok := s.backends[agentID]
	if !ok {
		klog.Warningf("can't find agentID %s in the backends", agentID)
		return
	}
	var found bool
	for i, c := range backends {
		if c.conn == conn {
			s.backends[agentID] = append(s.backends[agentID][:i], s.backends[agentID][i+1:]...)
			if i == 0 && len(s.backends[agentID]) != 0 {
				klog.Warningf("this should not happen. Removed connection %v that is not the first connection, remaining connections are %v", conn, s.backends[agentID])
			}
			found = true
		}
	}
	if len(s.backends[agentID]) == 0 {
		delete(s.backends, agentID)
		for i := range s.agentIDs {
			if s.agentIDs[i] == agentID {
				s.agentIDs[i] = s.agentIDs[len(s.agentIDs)-1]
				s.agentIDs = s.agentIDs[:len(s.agentIDs)-1]
				break
			}
		}
	}
	if !found {
		klog.Errorf("can't find conn %v for agentID %s in the backends", conn, agentID)
	}
}

// ErrNotFound indicates that no backend can be found.
type ErrNotFound struct{}

// Error returns the error message.
func (e *ErrNotFound) Error() string {
	return "No backend available"
}

// Backend returns a random backend.
func (s *DefaultBackendManager) Backend() (Backend, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	if len(s.backends) == 0 {
		return nil, &ErrNotFound{}
	}
	agentID := s.agentIDs[s.random.Intn(len(s.agentIDs))]
	klog.Infof("pick agentID=%s as backend", agentID)
	// always return the first connection to an agent, because the agent
	// will close later connections if there are multiple.
	return s.backends[agentID][0], nil
}
<|endoftext|>


<commit_before>package opentsdb

import (
  "fmt"
  "path"
  "strings"
  "context"
  "strconv"

  "net/url"
  "net/http"
  "io/ioutil"
  //"net/http/httputil"
  "encoding/json"

  "gopkg.in/guregu/null.v3"

	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/tsdb"
  "github.com/grafana/grafana/pkg/setting"
)

type OpenTsdbExecutor struct {
	*tsdb.DataSourceInfo
}

func NewOpenTsdbExecutor(dsInfo *tsdb.DataSourceInfo) tsdb.Executor {
	return &OpenTsdbExecutor{dsInfo}
}

var (
	plog       log.Logger
	HttpClient http.Client
)

func init() {
	plog = log.New("tsdb.opentsdb")
	tsdb.RegisterExecutor("opentsdb", NewOpenTsdbExecutor)
}

func (e *OpenTsdbExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, queryContext *tsdb.QueryContext) *tsdb.BatchResult {
	result := &tsdb.BatchResult{}

  var tsdbQuery OpenTsdbQuery

  tsdbQuery.Start = queryContext.TimeRange.GetFromAsMsEpoch()
  tsdbQuery.End = queryContext.TimeRange.GetToAsMsEpoch()

  for _, query := range queries {
    tsdbQuery.Queries = []OpenTsdbMetric {
      OpenTsdbMetric{
        Metric:      query.Model.Get("metric").MustString(),
        Aggregator:  query.Model.Get("aggregator").MustString(),
      },
    }
  }

  if setting.Env == setting.DEV {
    plog.Debug("OpenTsdb request", "params", tsdbQuery)
  }

  req, err := e.createRequest(tsdbQuery)
  if err != nil {
    result.Error = err
    return result
  }

  res, err := HttpClient.Do(req)
  if err != nil {
    result.Error = err
    return result
  }

  queryResult, err := e.parseResponse(tsdbQuery, res)
  if err != nil {
    return result.WithError(err)
  }

  result.QueryResults = queryResult
  return result
}

func (e *OpenTsdbExecutor) createRequest(data OpenTsdbQuery) (*http.Request, error) {
  u, _ := url.Parse(e.Url)
  u.Path = path.Join(u.Path, "api/query")

  postData, err := json.Marshal(data)

  req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(string(postData)))
  if err != nil {
    plog.Info("Failed to create request", "error", err)
    return nil, fmt.Errorf("Failed to create request. error: %v", err)
  }

  req.Header.Set("Content-Type", "application/json")
  if e.BasicAuth {
    req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword)
  }

  /*
  requestDump, err := httputil.DumpRequest(req, true)
  if err != nil {
    fmt.Println(err)
  }
  fmt.Println(string(requestDump))
  */
  return req, err
}

func (e *OpenTsdbExecutor) parseResponse(query OpenTsdbQuery, res *http.Response) (map[string]*tsdb.QueryResult, error) {

  queryResults := make(map[string]*tsdb.QueryResult)
  queryRes := tsdb.NewQueryResult()

  body, err := ioutil.ReadAll(res.Body)
  defer res.Body.Close()
  if err != nil {
    return nil, err
  }

  if res.StatusCode/100 != 2 {
    plog.Info("Request failed", "status", res.Status, "body", string(body))
    return nil, fmt.Errorf("Request failed status: %v", res.Status)
  }

  var data []OpenTsdbResponse
  err = json.Unmarshal(body, &data)
  if err != nil {
    plog.Info("Failed to unmarshal opentsdb response", "error", err, "status", res.Status, "body", string(body))
    return nil, err
  }

  for _, val := range data {
    series := tsdb.TimeSeries{
      Name: val.Metric,
    }

    for timeString, value := range val.DataPoints {
      timestamp, err := strconv.ParseFloat(timeString, 64)
      if err != nil {
        plog.Info("Failed to unmarshal opentsdb timestamp", "timestamp", timeString)
        return nil, err
      }
      series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(value), timestamp))
    }

    queryRes.Series = append(queryRes.Series, &series)
  }

  queryResults["A"] = queryRes
  return queryResults, nil
}<commit_msg>Resolved conflicts<commit_after>package opentsdb

import (
  "fmt"
  "path"
  "strings"
  "context"
  "strconv"

  "net/url"
  "net/http"
  "io/ioutil"
  //"net/http/httputil"
  "encoding/json"

  "gopkg.in/guregu/null.v3"

	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/tsdb"
  "github.com/grafana/grafana/pkg/setting"
)

type OpenTsdbExecutor struct {
	*tsdb.DataSourceInfo
}

func NewOpenTsdbExecutor(dsInfo *tsdb.DataSourceInfo) tsdb.Executor {
	return &OpenTsdbExecutor{dsInfo}
}

var (
	plog       log.Logger
	HttpClient http.Client
)

func init() {
	plog = log.New("tsdb.opentsdb")
	tsdb.RegisterExecutor("opentsdb", NewOpenTsdbExecutor)
}

func (e *OpenTsdbExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, queryContext *tsdb.QueryContext) *tsdb.BatchResult {
	result := &tsdb.BatchResult{}

  var tsdbQuery OpenTsdbQuery

  tsdbQuery.Start = queryContext.TimeRange.GetFromAsMsEpoch()
  tsdbQuery.End = queryContext.TimeRange.GetToAsMsEpoch()

  for _, query := range queries {
    tsdbQuery.Queries = []OpenTsdbMetric {
      OpenTsdbMetric{
        Metric:      query.Model.Get("metric").MustString(),
        Aggregator:  query.Model.Get("aggregator").MustString(),
      },
    }
  }

  if setting.Env == setting.DEV {
    plog.Debug("OpenTsdb request", "params", tsdbQuery)
  }

  req, err := e.createRequest(tsdbQuery)
  if err != nil {
    result.Error = err
    return result
  }

  res, err := HttpClient.Do(req)
  if err != nil {
    result.Error = err
    return result
  }

  queryResult, err := e.parseResponse(tsdbQuery, res)
  if err != nil {
    return result.WithError(err)
  }

  result.QueryResults = queryResult
  return result
}

func (e *OpenTsdbExecutor) createRequest(data OpenTsdbQuery) (*http.Request, error) {
  u, _ := url.Parse(e.Url)
  u.Path = path.Join(u.Path, "api/query")

  postData, err := json.Marshal(data)

  req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(string(postData)))
  if err != nil {
    plog.Info("Failed to create request", "error", err)
    return nil, fmt.Errorf("Failed to create request. error: %v", err)
  }

  req.Header.Set("Content-Type", "application/json")
  if e.BasicAuth {
    req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword)
  }

  /*
  requestDump, err := httputil.DumpRequest(req, true)
  if err != nil {
    fmt.Println(err)
  }
  fmt.Println(string(requestDump))
  */
  return req, err
}

func (e *OpenTsdbExecutor) parseResponse(query OpenTsdbQuery, res *http.Response) (map[string]*tsdb.QueryResult, error) {

  queryResults := make(map[string]*tsdb.QueryResult)
  queryRes := tsdb.NewQueryResult()

  body, err := ioutil.ReadAll(res.Body)
  defer res.Body.Close()
  if err != nil {
    return nil, err
  }

  if res.StatusCode/100 != 2 {
    plog.Info("Request failed", "status", res.Status, "body", string(body))
    return nil, fmt.Errorf("Request failed status: %v", res.Status)
  }

  var data []OpenTsdbResponse
  err = json.Unmarshal(body, &data)
  if err != nil {
    plog.Info("Failed to unmarshal opentsdb response", "error", err, "status", res.Status, "body", string(body))
    return nil, err
  }

  for _, val := range data {
    series := tsdb.TimeSeries{
      Name: val.Metric,
    }

    for timeString, value := range val.DataPoints {
      timestamp, err := strconv.ParseFloat(timeString, 64)
      if err != nil {
        plog.Info("Failed to unmarshal opentsdb timestamp", "timestamp", timeString)
        return nil, err
      }
      series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(value), timestamp))
    }

    queryRes.Series = append(queryRes.Series, &series)
  }

  queryResults["A"] = queryRes
  return queryResults, nil
}
<|endoftext|>


<commit_before>package providers

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"

	"github.com/bitly/oauth2_proxy/cookie"
)

func (p *ProviderData) Redeem(redirectURL, code string) (s *SessionState, err error) {
	if code == "" {
		err = errors.New("missing code")
		return
	}

	params := url.Values{}
	params.Add("redirect_uri", redirectURL)
	params.Add("client_id", p.ClientID)
	params.Add("client_secret", p.ClientSecret)
	params.Add("code", code)
	params.Add("grant_type", "authorization_code")
	if p.ProtectedResource != nil && p.ProtectedResource.String() != "" {
		params.Add("resource", p.ProtectedResource.String())
	}

	var req *http.Request
	req, err = http.NewRequest("POST", p.RedeemURL.String(), bytes.NewBufferString(params.Encode()))
	if err != nil {
		return
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	var resp *http.Response
	resp, err = http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	var body []byte
	body, err = ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return
	}

	if resp.StatusCode != 200 {
		err = fmt.Errorf("got %d from %q %s", resp.StatusCode, p.RedeemURL.String(), body)
		return
	}

	// blindly try json and x-www-form-urlencoded
	var jsonResponse struct {
		AccessToken string `json:"access_token"`
	}
	err = json.Unmarshal(body, &jsonResponse)
	if err == nil {
		s = &SessionState{
			AccessToken: jsonResponse.AccessToken,
		}
		return
	}

	var v url.Values
	v, err = url.ParseQuery(string(body))
	if err != nil {
		return
	}
	if a := v.Get("access_token"); a != "" {
		s = &SessionState{AccessToken: a}
	} else {
		err = fmt.Errorf("no access token found %s", body)
	}
	return
}

// GetLoginURL with typical oauth parameters
func (p *ProviderData) GetLoginURL(redirectURI, state string) string {
	var a url.URL
	a = *p.LoginURL
	params, _ := url.ParseQuery(a.RawQuery)
	params.Set("redirect_uri", redirectURI)
	params.Set("approval_prompt", p.ApprovalPrompt)
	params.Add("scope", p.Scope)
	params.Set("client_id", p.ClientID)
	params.Set("response_type", "code")
	params.Add("state", state)
	a.RawQuery = params.Encode()
	return a.String()
}

// CookieForSession serializes a session state for storage in a cookie
func (p *ProviderData) CookieForSession(s *SessionState, c *cookie.Cipher) (string, error) {
	return s.EncodeSessionState(c)
}

// SessionFromCookie deserializes a session from a cookie value
func (p *ProviderData) SessionFromCookie(v string, c *cookie.Cipher) (s *SessionState, err error) {
	return DecodeSessionState(v, c)
}

func (p *ProviderData) GetEmailAddress(s *SessionState) (string, error) {
	return "", errors.New("not implemented")
}

// ValidateGroup validates that the provided email exists in the configured provider
// email group(s).
func (p *ProviderData) ValidateGroup(email string) bool {
	return true
}

func (p *ProviderData) ValidateSessionState(s *SessionState) bool {
	return validateToken(p, s.AccessToken, nil)
}

// RefreshSessionIfNeeded
func (p *ProviderData) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
	return false, nil
}
<commit_msg>fixed url<commit_after>package providers

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"

	"github.com/bitly/oauth2_proxy/cookie"
)

func (p *ProviderData) Redeem(redirectURL, code string) (s *SessionState, err error) {
	if code == "" {
		err = errors.New("missing code")
		return
	}

	params := url.Values{}
	params.Add("redirect_uri", redirectURL)
	params.Add("client_id", p.ClientID)
	params.Add("client_secret", p.ClientSecret)
	params.Add("code", code)
	params.Add("grant_type", "authorization_code")
	if p.ProtectedResource != nil && p.ProtectedResource.String() != "" {
		params.Add("resource", p.ProtectedResource.String())
	}

	var req *http.Request
	req, err = http.NewRequest("POST", p.RedeemURL.String(), bytes.NewBufferString(params.Encode()))
	if err != nil {
		return
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	var resp *http.Response
	resp, err = http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	var body []byte
	body, err = ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return
	}

	if resp.StatusCode != 200 {
		err = fmt.Errorf("got %d from %q %s", resp.StatusCode, p.RedeemURL.String(), body)
		return
	}

	// blindly try json and x-www-form-urlencoded
	var jsonResponse struct {
		AccessToken string `json:"access_token"`
	}
	err = json.Unmarshal(body, &jsonResponse)
	if err == nil {
		s = &SessionState{
			AccessToken: jsonResponse.AccessToken,
		}
		return
	}

	var v url.Values
	v, err = url.ParseQuery(string(body))
	if err != nil {
		return
	}
	if a := v.Get("access_token"); a != "" {
		s = &SessionState{AccessToken: a}
	} else {
		err = fmt.Errorf("no access token found %s", body)
	}
	return
}

// GetLoginURL with typical oauth parameters
func (p *ProviderData) GetLoginURL(redirectURI, state string) string {
	var a url.URL
	a = *p.LoginURL
	params, _ := url.ParseQuery(a.RawQuery)
	params.Set("redirect_uri", redurectURI)
	//params.Set("approval_prompt", p.ApprovalPrompt)
	//params.Add("scope", "")
	params.Set("client_id", p.ClientID)
	params.Set("response_type", "ID_token")
	params.Add("state", state)
	a.RawQuery = params.Encode()
	return a.String()
}

// CookieForSession serializes a session state for storage in a cookie
func (p *ProviderData) CookieForSession(s *SessionState, c *cookie.Cipher) (string, error) {
	return s.EncodeSessionState(c)
}

// SessionFromCookie deserializes a session from a cookie value
func (p *ProviderData) SessionFromCookie(v string, c *cookie.Cipher) (s *SessionState, err error) {
	return DecodeSessionState(v, c)
}

func (p *ProviderData) GetEmailAddress(s *SessionState) (string, error) {
	return "", errors.New("not implemented")
}

// ValidateGroup validates that the provided email exists in the configured provider
// email group(s).
func (p *ProviderData) ValidateGroup(email string) bool {
	return true
}

func (p *ProviderData) ValidateSessionState(s *SessionState) bool {
	return validateToken(p, s.AccessToken, nil)
}

// RefreshSessionIfNeeded
func (p *ProviderData) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
	return false, nil
}
<|endoftext|>


<commit_before>package action

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/hokaccha/sprinkler/utils"
	"github.com/sourcegraph/go-selenium"
)

func actionLog(name string, message string, args ...interface{}) {
	utils.Debug("Run action "+name+" - "+message, args...)
}

type ActionRunner interface {
	Run(interface{}) error
	New(*ActionOpts)
	GetResult() *ActionResult
}

type ActionOpts struct {
	Wd      selenium.WebDriver
	Name    string
	BaseDir string
	Params  interface{}
}

type ActionResult struct {
	IsAssert  bool
	Successed bool
	Message   string
}

type ActionRunnerGetter func() ActionRunner

var actionMap = map[string]ActionRunnerGetter{}

func RegisterAction(name string, fn ActionRunnerGetter) {
	actionMap[name] = fn
}

func RunAction(opts *ActionOpts) (*ActionResult, error) {
	runnerFn, ok := actionMap[opts.Name]

	if !ok {
		return nil, fmt.Errorf("invalid action: %s", opts.Name)
	}

	runner := runnerFn()
	runner.New(opts)
	err := runner.Run(opts.Params)

	if err != nil {
		return nil, err
	}

	return runner.GetResult(), nil
}

type ActionBase struct {
	Wd      selenium.WebDriver
	Name    string
	Result  *ActionResult
	BaseDir string
}

func (a *ActionBase) New(opts *ActionOpts) {
	a.Wd = opts.Wd
	a.Name = opts.Name
	a.BaseDir = opts.BaseDir
	a.Result = new(ActionResult)
}

func (a *ActionBase) GetResult() *ActionResult {
	return a.Result
}

func (a *ActionBase) parseParams(data interface{}, v interface{}) error {
	var err error
	rv := reflect.ValueOf(v)
	vv := rv.Elem()
	vt := vv.Type()

	if rv.Kind() != reflect.Ptr || vv.Kind() != reflect.Struct {
		panic("invalid argument")
	}

	switch data.(type) {
	case map[interface{}]interface{}:
		m := map[string]interface{}{}

		for key, val := range data.(map[interface{}]interface{}) {
			m[key.(string)] = val
		}

		for i := 0; i < vt.NumField(); i++ {
			structField := vt.Field(i)
			structValue := vv.Field(i)
			val, ok := m[string(structField.Tag)]

			if !ok {
				continue
			}

			err = setValue(structField, structValue, reflect.ValueOf(val))

			if err != nil {
				return err
			}

		}
	default:
		structField := vt.Field(0)
		structValue := vv.Field(0)
		err = setValue(structField, structValue, reflect.ValueOf(data))

		if err != nil {
			return err
		}
	}

	return nil
}

func setValue(structField reflect.StructField, structValue, val reflect.Value) error {
	errFmt := "invalid parameter: %s must be %s"
	tag := string(structField.Tag)

	if structValue.Kind() == reflect.Ptr {
		e := structValue.Type().Elem()
		if e.Kind() != val.Kind() {
			return fmt.Errorf(errFmt, tag, e.Kind())
		}
		elem := reflect.New(e)
		elem.Elem().Set(val)
		structValue.Set(elem)
	} else {
		if structValue.Kind() != val.Kind() {
			return fmt.Errorf(errFmt, tag, structValue.Kind())
		}
		structValue.Set(val)
	}

	return nil
}

func (a *ActionBase) findElement(selector string) (selenium.WebElement, error) {
	if selector == "" {
		return nil, fmt.Errorf("selector is not defined")
	}

	return a.Wd.FindElement(selenium.ByCSSSelector, selector)
}

func (a *ActionBase) findElements(selector string) ([]selenium.WebElement, error) {
	if selector == "" {
		return nil, fmt.Errorf("selector is not defined")
	}

	return a.Wd.FindElements(selenium.ByCSSSelector, selector)
}

func (a *ActionBase) getAttribute(selector, name string) (string, error) {
	if name == "" {
		return "", fmt.Errorf("Attribute name is not defined")
	}

	el, err := a.findElement(selector)

	if err != nil {
		return "", err
	}

	value, err := el.GetAttribute(name)

	if err != nil {
		return "", err
	}

	return value, nil
}

func (a *ActionBase) assertEqual(subject, actual, expected string) {
	message := fmt.Sprintf("%s is '%s'", subject, expected)
	ok := actual == expected

	if !ok {
		message = errMsg(message, actual)
	}

	a.assert(ok, message)
}

func (a *ActionBase) assertContain(subject, actual, expected string) {
	message := fmt.Sprintf("%s contains '%s'", subject, expected)
	ok := strings.Contains(actual, expected)

	if !ok {
		message = errMsg(message, actual)
	}

	a.assert(ok, message)
}

func (a *ActionBase) assertPresent(subject, actual, expected string) {
	message := fmt.Sprintf("%s has '%s'", subject, expected)
	ok := utils.IsContained(strings.Fields(actual), expected)

	if !ok {
		message = errMsg(message, actual)
	}

	a.assert(ok, message)
}

func (a *ActionBase) assert(ok bool, message string) {
	a.Result.IsAssert = true

	if ok {
		a.Result.Successed = true
		a.Result.Message = fmt.Sprintf("%s - %s", utils.Green("OK"), message)
	} else {
		a.Result.Message = fmt.Sprintf("%s - %s", utils.Red("NG"), message)
	}
}

func errMsg(message, actual string) string {
	return fmt.Sprintf("%s - %s: %s", message, utils.Yellow("Actual"), actual)
}
<commit_msg>Fix error message<commit_after>package action

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/hokaccha/sprinkler/utils"
	"github.com/sourcegraph/go-selenium"
)

func actionLog(name string, message string, args ...interface{}) {
	utils.Debug("Run action "+name+" - "+message, args...)
}

type ActionRunner interface {
	Run(interface{}) error
	New(*ActionOpts)
	GetResult() *ActionResult
}

type ActionOpts struct {
	Wd      selenium.WebDriver
	Name    string
	BaseDir string
	Params  interface{}
}

type ActionResult struct {
	IsAssert  bool
	Successed bool
	Message   string
}

type ActionRunnerGetter func() ActionRunner

var actionMap = map[string]ActionRunnerGetter{}

func RegisterAction(name string, fn ActionRunnerGetter) {
	actionMap[name] = fn
}

func RunAction(opts *ActionOpts) (*ActionResult, error) {
	runnerFn, ok := actionMap[opts.Name]

	if !ok {
		return nil, fmt.Errorf("unknown action: %s", opts.Name)
	}

	runner := runnerFn()
	runner.New(opts)
	err := runner.Run(opts.Params)

	if err != nil {
		return nil, err
	}

	return runner.GetResult(), nil
}

type ActionBase struct {
	Wd      selenium.WebDriver
	Name    string
	Result  *ActionResult
	BaseDir string
}

func (a *ActionBase) New(opts *ActionOpts) {
	a.Wd = opts.Wd
	a.Name = opts.Name
	a.BaseDir = opts.BaseDir
	a.Result = new(ActionResult)
}

func (a *ActionBase) GetResult() *ActionResult {
	return a.Result
}

func (a *ActionBase) parseParams(data interface{}, v interface{}) error {
	var err error
	rv := reflect.ValueOf(v)
	vv := rv.Elem()
	vt := vv.Type()

	if rv.Kind() != reflect.Ptr || vv.Kind() != reflect.Struct {
		panic("invalid argument")
	}

	switch data.(type) {
	case map[interface{}]interface{}:
		m := map[string]interface{}{}

		for key, val := range data.(map[interface{}]interface{}) {
			m[key.(string)] = val
		}

		for i := 0; i < vt.NumField(); i++ {
			structField := vt.Field(i)
			structValue := vv.Field(i)
			val, ok := m[string(structField.Tag)]

			if !ok {
				continue
			}

			err = setValue(structField, structValue, reflect.ValueOf(val))

			if err != nil {
				return err
			}

		}
	default:
		structField := vt.Field(0)
		structValue := vv.Field(0)
		err = setValue(structField, structValue, reflect.ValueOf(data))

		if err != nil {
			return err
		}
	}

	return nil
}

func setValue(structField reflect.StructField, structValue, val reflect.Value) error {
	errFmt := "invalid parameter: %s must be %s"
	tag := string(structField.Tag)

	if structValue.Kind() == reflect.Ptr {
		e := structValue.Type().Elem()
		if e.Kind() != val.Kind() {
			return fmt.Errorf(errFmt, tag, e.Kind())
		}
		elem := reflect.New(e)
		elem.Elem().Set(val)
		structValue.Set(elem)
	} else {
		if structValue.Kind() != val.Kind() {
			return fmt.Errorf(errFmt, tag, structValue.Kind())
		}
		structValue.Set(val)
	}

	return nil
}

func (a *ActionBase) findElement(selector string) (selenium.WebElement, error) {
	if selector == "" {
		return nil, fmt.Errorf("selector is not defined")
	}

	return a.Wd.FindElement(selenium.ByCSSSelector, selector)
}

func (a *ActionBase) findElements(selector string) ([]selenium.WebElement, error) {
	if selector == "" {
		return nil, fmt.Errorf("selector is not defined")
	}

	return a.Wd.FindElements(selenium.ByCSSSelector, selector)
}

func (a *ActionBase) getAttribute(selector, name string) (string, error) {
	if name == "" {
		return "", fmt.Errorf("Attribute name is not defined")
	}

	el, err := a.findElement(selector)

	if err != nil {
		return "", err
	}

	value, err := el.GetAttribute(name)

	if err != nil {
		return "", err
	}

	return value, nil
}

func (a *ActionBase) assertEqual(subject, actual, expected string) {
	message := fmt.Sprintf("%s is '%s'", subject, expected)
	ok := actual == expected

	if !ok {
		message = errMsg(message, actual)
	}

	a.assert(ok, message)
}

func (a *ActionBase) assertContain(subject, actual, expected string) {
	message := fmt.Sprintf("%s contains '%s'", subject, expected)
	ok := strings.Contains(actual, expected)

	if !ok {
		message = errMsg(message, actual)
	}

	a.assert(ok, message)
}

func (a *ActionBase) assertPresent(subject, actual, expected string) {
	message := fmt.Sprintf("%s has '%s'", subject, expected)
	ok := utils.IsContained(strings.Fields(actual), expected)

	if !ok {
		message = errMsg(message, actual)
	}

	a.assert(ok, message)
}

func (a *ActionBase) assert(ok bool, message string) {
	a.Result.IsAssert = true

	if ok {
		a.Result.Successed = true
		a.Result.Message = fmt.Sprintf("%s - %s", utils.Green("OK"), message)
	} else {
		a.Result.Message = fmt.Sprintf("%s - %s", utils.Red("NG"), message)
	}
}

func errMsg(message, actual string) string {
	return fmt.Sprintf("%s - %s: %s", message, utils.Yellow("Actual"), actual)
}
<|endoftext|>


<commit_before>package replicaset

import (
	"fmt"
	"testing"
	"time"

	gitjujutesting "github.com/juju/testing"
	jc "github.com/juju/testing/checkers"
	"github.com/juju/utils"
	"labix.org/v2/mgo"
	gc "launchpad.net/gocheck"

	coretesting "github.com/juju/juju/testing"
)

const rsName = "juju"

func TestPackage(t *testing.T) {
	gc.TestingT(t)
}

type MongoSuite struct {
	coretesting.BaseSuite
	root *gitjujutesting.MgoInstance
}

func newServer(c *gc.C) *gitjujutesting.MgoInstance {
	inst := &gitjujutesting.MgoInstance{Params: []string{"--replSet", rsName}}
	err := inst.Start(coretesting.Certs)
	c.Assert(err, gc.IsNil)

	session, err := inst.DialDirect()
	if err != nil {
		inst.Destroy()
		c.Fatalf("error dialing mongo server: %v", err.Error())
	}
	defer session.Close()

	session.SetMode(mgo.Monotonic, true)
	if err = session.Ping(); err != nil {
		inst.Destroy()
		c.Fatalf("error pinging mongo server: %v", err.Error())
	}
	return inst
}

var _ = gc.Suite(&MongoSuite{})

func (s *MongoSuite) SetUpTest(c *gc.C) {
	s.BaseSuite.SetUpTest(c)
	s.root = newServer(c)
	s.dialAndTestInitiate(c, s.root, s.root.Addr())
}

func (s *MongoSuite) TearDownTest(c *gc.C) {
	s.root.Destroy()
	s.BaseSuite.TearDownTest(c)
}

var initialTags = map[string]string{"foo": "bar"}

func (s *MongoSuite) dialAndTestInitiate(c *gc.C, inst *gitjujutesting.MgoInstance, addr string) {
	session := inst.MustDialDirect()
	defer session.Close()

	mode := session.Mode()
	err := Initiate(session, addr, rsName, initialTags)
	c.Assert(err, gc.IsNil)

	// make sure we haven't messed with the session's mode
	c.Assert(session.Mode(), gc.Equals, mode)

	// Ids start at 1 for us, so we can differentiate between set and unset
	expectedMembers := []Member{Member{Id: 1, Address: addr, Tags: initialTags}}

	// need to set mode to strong so that we wait for the write to succeed
	// before reading and thus ensure that we're getting consistent reads.
	session.SetMode(mgo.Strong, false)

	mems, err := CurrentMembers(session)
	c.Assert(err, gc.IsNil)
	c.Assert(mems, jc.DeepEquals, expectedMembers)

	// now add some data so we get a more real-life test
	loadData(session, c)
}

func (s *MongoSuite) TestInitiateWaitsForStatus(c *gc.C) {
	s.root.Destroy()

	// create a new server that hasn't been initiated
	s.root = newServer(c)
	session := s.root.MustDialDirect()
	defer session.Close()

	i := 0
	mockStatus := func(session *mgo.Session) (*Status, error) {
		status := &Status{}
		var err error
		i += 1
		if i < 20 {
			err = fmt.Errorf("bang!")
		} else if i > 20 {
			// when i == 20 then len(status.Members) == 0
			// so we will be called one more time until we populate
			// Members
			status.Members = append(status.Members, MemberStatus{Id: 1})
		}
		return status, err
	}

	s.PatchValue(&getCurrentStatus, mockStatus)
	Initiate(session, s.root.Addr(), rsName, initialTags)
	c.Assert(i, gc.Equals, 21)
}

func loadData(session *mgo.Session, c *gc.C) {
	type foo struct {
		Name    string
		Address string
		Count   int
	}

	for col := 0; col < 10; col++ {
		foos := make([]foo, 10000)
		for n := range foos {
			foos[n] = foo{
				Name:    fmt.Sprintf("name_%d_%d", col, n),
				Address: fmt.Sprintf("address_%d_%d", col, n),
				Count:   n * (col + 1),
			}
		}

		err := session.DB("testing").C(fmt.Sprintf("data%d", col)).Insert(foos)
		c.Assert(err, gc.IsNil)
	}
}

func attemptLoop(c *gc.C, strategy utils.AttemptStrategy, desc string, f func() error) {
	var err error
	start := time.Now()
	attemptCount := 0
	for attempt := strategy.Start(); attempt.Next(); {
		attemptCount += 1
		if err = f(); err == nil || !attempt.HasNext() {
			break
		}
		c.Logf("%s failed: %v", desc, err)
	}
	c.Logf("%s: %d attempts in %s", desc, attemptCount, time.Since(start))
	c.Assert(err, gc.IsNil)
}

func (s *MongoSuite) TestAddRemoveSetIPv6(c *gc.C) {
	root := newServer(c)
	defer root.Destroy()
	// Note: we use the ::1:port format because mongo doesn't understand
	// [::1]:port
	getAddr := func(inst *gitjujutesting.MgoInstance) string {
		return fmt.Sprintf("::1:%v", inst.Port())
	}
	s.dialAndTestInitiate(c, root, getAddr(root))
	s.assertAddRemoveSet(c, root, getAddr)
}

func (s *MongoSuite) TestAddRemoveSet(c *gc.C) {
	getAddr := func(inst *gitjujutesting.MgoInstance) string {
		return inst.Addr()
	}
	s.assertAddRemoveSet(c, s.root, getAddr)
}

func (s *MongoSuite) assertAddRemoveSet(c *gc.C, root *gitjujutesting.MgoInstance, getAddr func(*gitjujutesting.MgoInstance) string) {
	session := root.MustDial()
	defer session.Close()

	members := make([]Member, 0, 5)

	// Add should be idempotent, so re-adding root here shouldn't result in
	// two copies of root in the replica set
	members = append(members, Member{Address: getAddr(root), Tags: initialTags})

	instances := make([]*gitjujutesting.MgoInstance, 5)
	instances[0] = root
	for i := 1; i < len(instances); i++ {
		inst := newServer(c)
		instances[i] = inst
		defer inst.Destroy()
		defer func() {
			err := Remove(session, getAddr(inst))
			c.Assert(err, gc.IsNil)
		}()
		key := fmt.Sprintf("key%d", i)
		val := fmt.Sprintf("val%d", i)
		tags := map[string]string{key: val}
		members = append(members, Member{Address: getAddr(inst), Tags: tags})
	}

	// We allow for up to 2m per operation, since Add, Set, etc. call
	// replSetReconfig which may cause primary renegotiation. According
	// to the Mongo docs, "typically this is 10-20 seconds, but could be
	// as long as a minute or more."
	//
	// Note that the delay is set at 500ms to cater for relatively quick
	// operations without thrashing on those that take longer.
	strategy := utils.AttemptStrategy{Total: time.Minute * 2, Delay: time.Millisecond * 500}
	attemptLoop(c, strategy, "Add()", func() error {
		return Add(session, members...)
	})

	expectedMembers := make([]Member, len(members))
	for i, m := range members {
		// Ids should start at 1 (for the root) and go up
		m.Id = i + 1
		expectedMembers[i] = m
	}

	var cfg *Config
	attemptLoop(c, strategy, "CurrentConfig()", func() error {
		var err error
		cfg, err = CurrentConfig(session)
		return err
	})
	c.Assert(cfg.Name, gc.Equals, rsName)
	// 2 since we already changed it once
	c.Assert(cfg.Version, gc.Equals, 2)

	mems := cfg.Members
	c.Assert(mems, jc.DeepEquals, expectedMembers)

	// Now remove the last two Members...
	attemptLoop(c, strategy, "Remove()", func() error {
		return Remove(session, members[3].Address, members[4].Address)
	})
	expectedMembers = expectedMembers[0:3]

	// ... and confirm that CurrentMembers reflects the removal.
	attemptLoop(c, strategy, "CurrentMembers()", func() error {
		var err error
		mems, err = CurrentMembers(session)
		return err
	})
	c.Assert(mems, jc.DeepEquals, expectedMembers)

	// now let's mix it up and set the new members to a mix of the previous
	// plus the new arbiter
	mems = []Member{members[3], mems[2], mems[0], members[4]}
	attemptLoop(c, strategy, "Set()", func() error {
		err := Set(session, mems)
		if err != nil {
			c.Logf("current session mode: %v", session.Mode())
			session.Refresh()
		}
		return err
	})

	attemptLoop(c, strategy, "Ping()", func() error {
		// can dial whichever replica address here, mongo will figure it out
		if session != nil {
			session.Close()
		}
		session = instances[0].MustDialDirect()
		return session.Ping()
	})

	// any new members will get an id of max(other_ids...)+1
	expectedMembers = []Member{members[3], expectedMembers[2], expectedMembers[0], members[4]}
	expectedMembers[0].Id = 4
	expectedMembers[3].Id = 5

	attemptLoop(c, strategy, "CurrentMembers()", func() error {
		var err error
		mems, err = CurrentMembers(session)
		return err
	})
	c.Assert(mems, jc.DeepEquals, expectedMembers)
}

func (s *MongoSuite) TestIsMaster(c *gc.C) {
	session := s.root.MustDial()
	defer session.Close()

	expected := IsMasterResults{
		// The following fields hold information about the specific mongodb node.
		IsMaster:  true,
		Secondary: false,
		Arbiter:   false,
		Address:   s.root.Addr(),
		LocalTime: time.Time{},

		// The following fields hold information about the replica set.
		ReplicaSetName: rsName,
		Addresses:      []string{s.root.Addr()},
		Arbiters:       nil,
		PrimaryAddress: s.root.Addr(),
	}

	res, err := IsMaster(session)
	c.Assert(err, gc.IsNil)
	c.Check(closeEnough(res.LocalTime, time.Now()), gc.Equals, true)
	res.LocalTime = time.Time{}
	c.Check(*res, jc.DeepEquals, expected)
}

func (s *MongoSuite) TestMasterHostPort(c *gc.C) {
	session := s.root.MustDial()
	defer session.Close()

	expected := s.root.Addr()
	result, err := MasterHostPort(session)

	c.Logf("TestMasterHostPort expected: %v, got: %v", expected, result)
	c.Assert(err, gc.IsNil)
	c.Assert(result, gc.Equals, expected)
}

func (s *MongoSuite) TestMasterHostPortOnUnconfiguredReplicaSet(c *gc.C) {
	inst := &gitjujutesting.MgoInstance{}
	err := inst.Start(coretesting.Certs)
	c.Assert(err, gc.IsNil)
	defer inst.Destroy()
	session := inst.MustDial()
	hp, err := MasterHostPort(session)
	c.Assert(err, gc.Equals, ErrMasterNotConfigured)
	c.Assert(hp, gc.Equals, "")
}

func (s *MongoSuite) TestCurrentStatus(c *gc.C) {
	session := s.root.MustDial()
	defer session.Close()

	inst1 := newServer(c)
	defer inst1.Destroy()
	defer Remove(session, inst1.Addr())

	inst2 := newServer(c)
	defer inst2.Destroy()
	defer Remove(session, inst2.Addr())

	var err error
	strategy := utils.AttemptStrategy{Total: time.Minute * 2, Delay: time.Millisecond * 500}
	attempt := strategy.Start()
	for attempt.Next() {
		err = Add(session, Member{Address: inst1.Addr()}, Member{Address: inst2.Addr()})
		if err == nil || !attempt.HasNext() {
			break
		}
	}
	c.Assert(err, gc.IsNil)

	expected := &Status{
		Name: rsName,
		Members: []MemberStatus{{
			Id:      1,
			Address: s.root.Addr(),
			Self:    true,
			ErrMsg:  "",
			Healthy: true,
			State:   PrimaryState,
		}, {
			Id:      2,
			Address: inst1.Addr(),
			Self:    false,
			ErrMsg:  "",
			Healthy: true,
			State:   SecondaryState,
		}, {
			Id:      3,
			Address: inst2.Addr(),
			Self:    false,
			ErrMsg:  "",
			Healthy: true,
			State:   SecondaryState,
		}},
	}

	strategy.Total = time.Second * 90
	attempt = strategy.Start()
	var res *Status
	for attempt.Next() {
		var err error
		res, err = CurrentStatus(session)
		if err != nil {
			if !attempt.HasNext() {
				c.Errorf("Couldn't get status before timeout, got err: %v", err)
				return
			} else {
				// try again
				continue
			}
		}

		if res.Members[0].State == PrimaryState &&
			res.Members[1].State == SecondaryState &&
			res.Members[2].State == SecondaryState {
			break
		}
		if !attempt.HasNext() {
			c.Errorf("Servers did not get into final state before timeout.  Status: %#v", res)
			return
		}
	}

	for x, _ := range res.Members {
		// non-empty uptime and ping
		c.Check(res.Members[x].Uptime, gc.Not(gc.Equals), 0)

		// ping is always going to be zero since we're on localhost
		// so we can't really test it right now

		// now overwrite Uptime so it won't throw off DeepEquals
		res.Members[x].Uptime = 0
	}
	c.Check(res, jc.DeepEquals, expected)
}

func closeEnough(expected, obtained time.Time) bool {
	t := obtained.Sub(expected)
	return (-500*time.Millisecond) < t && t < (500*time.Millisecond)
}
<commit_msg>replicaset: rework the test to not start two mongodb replicas during the IPV6 test<commit_after>package replicaset

import (
	"fmt"
	"testing"
	"time"

	gitjujutesting "github.com/juju/testing"
	jc "github.com/juju/testing/checkers"
	"github.com/juju/utils"
	"labix.org/v2/mgo"
	gc "launchpad.net/gocheck"

	coretesting "github.com/juju/juju/testing"
)

const rsName = "juju"

func TestPackage(t *testing.T) {
	gc.TestingT(t)
}

type MongoSuite struct {
	coretesting.BaseSuite
	root *gitjujutesting.MgoInstance
}

func newServer(c *gc.C) *gitjujutesting.MgoInstance {
	inst := &gitjujutesting.MgoInstance{Params: []string{"--replSet", rsName}}
	err := inst.Start(coretesting.Certs)
	c.Assert(err, gc.IsNil)

	session, err := inst.DialDirect()
	if err != nil {
		inst.Destroy()
		c.Fatalf("error dialing mongo server: %v", err.Error())
	}
	defer session.Close()

	session.SetMode(mgo.Monotonic, true)
	if err = session.Ping(); err != nil {
		inst.Destroy()
		c.Fatalf("error pinging mongo server: %v", err.Error())
	}
	return inst
}

var _ = gc.Suite(&MongoSuite{})

func (s *MongoSuite) SetUpTest(c *gc.C) {
	s.BaseSuite.SetUpTest(c)
	s.root = newServer(c)
	dialAndTestInitiate(c, s.root, s.root.Addr())
}

func (s *MongoSuite) TearDownTest(c *gc.C) {
	s.root.Destroy()
	s.BaseSuite.TearDownTest(c)
}

var initialTags = map[string]string{"foo": "bar"}

func dialAndTestInitiate(c *gc.C, inst *gitjujutesting.MgoInstance, addr string) {
	session := inst.MustDialDirect()
	defer session.Close()

	mode := session.Mode()
	err := Initiate(session, addr, rsName, initialTags)
	c.Assert(err, gc.IsNil)

	// make sure we haven't messed with the session's mode
	c.Assert(session.Mode(), gc.Equals, mode)

	// Ids start at 1 for us, so we can differentiate between set and unset
	expectedMembers := []Member{Member{Id: 1, Address: addr, Tags: initialTags}}

	// need to set mode to strong so that we wait for the write to succeed
	// before reading and thus ensure that we're getting consistent reads.
	session.SetMode(mgo.Strong, false)

	mems, err := CurrentMembers(session)
	c.Assert(err, gc.IsNil)
	c.Assert(mems, jc.DeepEquals, expectedMembers)

	// now add some data so we get a more real-life test
	loadData(session, c)
}

func (s *MongoSuite) TestInitiateWaitsForStatus(c *gc.C) {
	s.root.Destroy()

	// create a new server that hasn't been initiated
	s.root = newServer(c)
	session := s.root.MustDialDirect()
	defer session.Close()

	i := 0
	mockStatus := func(session *mgo.Session) (*Status, error) {
		status := &Status{}
		var err error
		i += 1
		if i < 20 {
			err = fmt.Errorf("bang!")
		} else if i > 20 {
			// when i == 20 then len(status.Members) == 0
			// so we will be called one more time until we populate
			// Members
			status.Members = append(status.Members, MemberStatus{Id: 1})
		}
		return status, err
	}

	s.PatchValue(&getCurrentStatus, mockStatus)
	Initiate(session, s.root.Addr(), rsName, initialTags)
	c.Assert(i, gc.Equals, 21)
}

func loadData(session *mgo.Session, c *gc.C) {
	type foo struct {
		Name    string
		Address string
		Count   int
	}

	for col := 0; col < 10; col++ {
		foos := make([]foo, 10000)
		for n := range foos {
			foos[n] = foo{
				Name:    fmt.Sprintf("name_%d_%d", col, n),
				Address: fmt.Sprintf("address_%d_%d", col, n),
				Count:   n * (col + 1),
			}
		}

		err := session.DB("testing").C(fmt.Sprintf("data%d", col)).Insert(foos)
		c.Assert(err, gc.IsNil)
	}
}

func attemptLoop(c *gc.C, strategy utils.AttemptStrategy, desc string, f func() error) {
	var err error
	start := time.Now()
	attemptCount := 0
	for attempt := strategy.Start(); attempt.Next(); {
		attemptCount += 1
		if err = f(); err == nil || !attempt.HasNext() {
			break
		}
		c.Logf("%s failed: %v", desc, err)
	}
	c.Logf("%s: %d attempts in %s", desc, attemptCount, time.Since(start))
	c.Assert(err, gc.IsNil)
}

type MongoIPV6Suite struct {
	coretesting.BaseSuite
}

var _ = gc.Suite(&MongoIPV6Suite{})

func (s *MongoIPV6Suite) TestAddRemoveSetIPv6(c *gc.C) {
	root := newServer(c)
	defer root.Destroy()
	// Note: we use the ::1:port format because mongo doesn't understand
	// [::1]:port
	getAddr := func(inst *gitjujutesting.MgoInstance) string {
		return fmt.Sprintf("::1:%v", inst.Port())
	}
	dialAndTestInitiate(c, root, getAddr(root))
	assertAddRemoveSet(c, root, getAddr)
}

func (s *MongoSuite) TestAddRemoveSet(c *gc.C) {
	getAddr := func(inst *gitjujutesting.MgoInstance) string {
		return inst.Addr()
	}
	assertAddRemoveSet(c, s.root, getAddr)
}

func assertAddRemoveSet(c *gc.C, root *gitjujutesting.MgoInstance, getAddr func(*gitjujutesting.MgoInstance) string) {
	session := root.MustDial()
	defer session.Close()

	members := make([]Member, 0, 5)

	// Add should be idempotent, so re-adding root here shouldn't result in
	// two copies of root in the replica set
	members = append(members, Member{Address: getAddr(root), Tags: initialTags})

	instances := make([]*gitjujutesting.MgoInstance, 5)
	instances[0] = root
	for i := 1; i < len(instances); i++ {
		inst := newServer(c)
		instances[i] = inst
		defer inst.Destroy()
		defer func() {
			err := Remove(session, getAddr(inst))
			c.Assert(err, gc.IsNil)
		}()
		key := fmt.Sprintf("key%d", i)
		val := fmt.Sprintf("val%d", i)
		tags := map[string]string{key: val}
		members = append(members, Member{Address: getAddr(inst), Tags: tags})
	}

	// We allow for up to 2m per operation, since Add, Set, etc. call
	// replSetReconfig which may cause primary renegotiation. According
	// to the Mongo docs, "typically this is 10-20 seconds, but could be
	// as long as a minute or more."
	//
	// Note that the delay is set at 500ms to cater for relatively quick
	// operations without thrashing on those that take longer.
	strategy := utils.AttemptStrategy{Total: time.Minute * 2, Delay: time.Millisecond * 500}
	attemptLoop(c, strategy, "Add()", func() error {
		return Add(session, members...)
	})

	expectedMembers := make([]Member, len(members))
	for i, m := range members {
		// Ids should start at 1 (for the root) and go up
		m.Id = i + 1
		expectedMembers[i] = m
	}

	var cfg *Config
	attemptLoop(c, strategy, "CurrentConfig()", func() error {
		var err error
		cfg, err = CurrentConfig(session)
		return err
	})
	c.Assert(cfg.Name, gc.Equals, rsName)
	// 2 since we already changed it once
	c.Assert(cfg.Version, gc.Equals, 2)

	mems := cfg.Members
	c.Assert(mems, jc.DeepEquals, expectedMembers)

	// Now remove the last two Members...
	attemptLoop(c, strategy, "Remove()", func() error {
		return Remove(session, members[3].Address, members[4].Address)
	})
	expectedMembers = expectedMembers[0:3]

	// ... and confirm that CurrentMembers reflects the removal.
	attemptLoop(c, strategy, "CurrentMembers()", func() error {
		var err error
		mems, err = CurrentMembers(session)
		return err
	})
	c.Assert(mems, jc.DeepEquals, expectedMembers)

	// now let's mix it up and set the new members to a mix of the previous
	// plus the new arbiter
	mems = []Member{members[3], mems[2], mems[0], members[4]}
	attemptLoop(c, strategy, "Set()", func() error {
		err := Set(session, mems)
		if err != nil {
			c.Logf("current session mode: %v", session.Mode())
			session.Refresh()
		}
		return err
	})

	attemptLoop(c, strategy, "Ping()", func() error {
		// can dial whichever replica address here, mongo will figure it out
		if session != nil {
			session.Close()
		}
		session = instances[0].MustDialDirect()
		return session.Ping()
	})

	// any new members will get an id of max(other_ids...)+1
	expectedMembers = []Member{members[3], expectedMembers[2], expectedMembers[0], members[4]}
	expectedMembers[0].Id = 4
	expectedMembers[3].Id = 5

	attemptLoop(c, strategy, "CurrentMembers()", func() error {
		var err error
		mems, err = CurrentMembers(session)
		return err
	})
	c.Assert(mems, jc.DeepEquals, expectedMembers)
}

func (s *MongoSuite) TestIsMaster(c *gc.C) {
	session := s.root.MustDial()
	defer session.Close()

	expected := IsMasterResults{
		// The following fields hold information about the specific mongodb node.
		IsMaster:  true,
		Secondary: false,
		Arbiter:   false,
		Address:   s.root.Addr(),
		LocalTime: time.Time{},

		// The following fields hold information about the replica set.
		ReplicaSetName: rsName,
		Addresses:      []string{s.root.Addr()},
		Arbiters:       nil,
		PrimaryAddress: s.root.Addr(),
	}

	res, err := IsMaster(session)
	c.Assert(err, gc.IsNil)
	c.Check(closeEnough(res.LocalTime, time.Now()), gc.Equals, true)
	res.LocalTime = time.Time{}
	c.Check(*res, jc.DeepEquals, expected)
}

func (s *MongoSuite) TestMasterHostPort(c *gc.C) {
	session := s.root.MustDial()
	defer session.Close()

	expected := s.root.Addr()
	result, err := MasterHostPort(session)

	c.Logf("TestMasterHostPort expected: %v, got: %v", expected, result)
	c.Assert(err, gc.IsNil)
	c.Assert(result, gc.Equals, expected)
}

func (s *MongoSuite) TestMasterHostPortOnUnconfiguredReplicaSet(c *gc.C) {
	inst := &gitjujutesting.MgoInstance{}
	err := inst.Start(coretesting.Certs)
	c.Assert(err, gc.IsNil)
	defer inst.Destroy()
	session := inst.MustDial()
	hp, err := MasterHostPort(session)
	c.Assert(err, gc.Equals, ErrMasterNotConfigured)
	c.Assert(hp, gc.Equals, "")
}

func (s *MongoSuite) TestCurrentStatus(c *gc.C) {
	session := s.root.MustDial()
	defer session.Close()

	inst1 := newServer(c)
	defer inst1.Destroy()
	defer Remove(session, inst1.Addr())

	inst2 := newServer(c)
	defer inst2.Destroy()
	defer Remove(session, inst2.Addr())

	var err error
	strategy := utils.AttemptStrategy{Total: time.Minute * 2, Delay: time.Millisecond * 500}
	attempt := strategy.Start()
	for attempt.Next() {
		err = Add(session, Member{Address: inst1.Addr()}, Member{Address: inst2.Addr()})
		if err == nil || !attempt.HasNext() {
			break
		}
	}
	c.Assert(err, gc.IsNil)

	expected := &Status{
		Name: rsName,
		Members: []MemberStatus{{
			Id:      1,
			Address: s.root.Addr(),
			Self:    true,
			ErrMsg:  "",
			Healthy: true,
			State:   PrimaryState,
		}, {
			Id:      2,
			Address: inst1.Addr(),
			Self:    false,
			ErrMsg:  "",
			Healthy: true,
			State:   SecondaryState,
		}, {
			Id:      3,
			Address: inst2.Addr(),
			Self:    false,
			ErrMsg:  "",
			Healthy: true,
			State:   SecondaryState,
		}},
	}

	strategy.Total = time.Second * 90
	attempt = strategy.Start()
	var res *Status
	for attempt.Next() {
		var err error
		res, err = CurrentStatus(session)
		if err != nil {
			if !attempt.HasNext() {
				c.Errorf("Couldn't get status before timeout, got err: %v", err)
				return
			} else {
				// try again
				continue
			}
		}

		if res.Members[0].State == PrimaryState &&
			res.Members[1].State == SecondaryState &&
			res.Members[2].State == SecondaryState {
			break
		}
		if !attempt.HasNext() {
			c.Errorf("Servers did not get into final state before timeout.  Status: %#v", res)
			return
		}
	}

	for x, _ := range res.Members {
		// non-empty uptime and ping
		c.Check(res.Members[x].Uptime, gc.Not(gc.Equals), 0)

		// ping is always going to be zero since we're on localhost
		// so we can't really test it right now

		// now overwrite Uptime so it won't throw off DeepEquals
		res.Members[x].Uptime = 0
	}
	c.Check(res, jc.DeepEquals, expected)
}

func closeEnough(expected, obtained time.Time) bool {
	t := obtained.Sub(expected)
	return (-500*time.Millisecond) < t && t < (500*time.Millisecond)
}
<|endoftext|>


<commit_before>package router

import (
	"bufio"
	"github.com/gin-gonic/gin"
	"github.com/pterodactyl/wings/server"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
)

// Returns the contents of a file on the server.
func getServerFileContents(c *gin.Context) {
	s := GetServer(c.Param("server"))

	p, err := url.QueryUnescape(c.Query("file"))
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}
	p = "/" + strings.TrimLeft(p, "/")

	cleaned, err := s.Filesystem.SafePath(p)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
			"error": "The file requested could not be found.",
		})
		return
	}

	st, err := s.Filesystem.Stat(cleaned)
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	if st.Info.IsDir() {
		c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
			"error": "The requested resource was not found on the system.",
		})
		return
	}

	f, err := os.Open(cleaned)
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}
	defer f.Close()

	c.Header("X-Mime-Type", st.Mimetype)
	c.Header("Content-Length", strconv.Itoa(int(st.Info.Size())))

	// If a download parameter is included in the URL go ahead and attach the necessary headers
	// so that the file can be downloaded.
	if c.Query("download") != "" {
		c.Header("Content-Disposition", "attachment; filename="+st.Info.Name())
		c.Header("Content-Type", "application/octet-stream")
	}

	bufio.NewReader(f).WriteTo(c.Writer)
}

// Returns the contents of a directory for a server.
func getServerListDirectory(c *gin.Context) {
	s := GetServer(c.Param("server"))

	d, err := url.QueryUnescape(c.Query("directory"))
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	stats, err := s.Filesystem.ListDirectory(d)
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.JSON(http.StatusOK, stats)
}

// Renames (or moves) a file for a server.
func putServerRenameFile(c *gin.Context) {
	s := GetServer(c.Param("server"))

	var data struct {
		RenameFrom string `json:"rename_from"`
		RenameTo   string `json:"rename_to"`
	}
	// BindJSON sends 400 if the request fails, all we need to do is return
	if err := c.BindJSON(&data); err != nil {
		return
	}

	if data.RenameFrom == "" || data.RenameTo == "" {
		c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
			"error": "Invalid paths were provided, did you forget to provide both a new and old path?",
		})
		return
	}

	if err := s.Filesystem.Rename(data.RenameFrom, data.RenameTo); err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.Status(http.StatusNoContent)
}

// Copies a server file.
func postServerCopyFile(c *gin.Context) {
	s := GetServer(c.Param("server"))

	var data struct {
		Location string `json:"location"`
	}
	// BindJSON sends 400 if the request fails, all we need to do is return
	if err := c.BindJSON(&data); err != nil {
		return
	}

	if err := s.Filesystem.Copy(data.Location); err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.Status(http.StatusNoContent)
}

// Deletes a server file.
func postServerDeleteFile(c *gin.Context) {
	s := GetServer(c.Param("server"))

	var data struct {
		Location string `json:"location"`
	}
	// BindJSON sends 400 if the request fails, all we need to do is return
	if err := c.BindJSON(&data); err != nil {
		return
	}

	if err := s.Filesystem.Delete(data.Location); err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.Status(http.StatusNoContent)
}

// Writes the contents of the request to a file on a server.
func postServerWriteFile(c *gin.Context) {
	s := GetServer(c.Param("server"))

	f, err := url.QueryUnescape(c.Query("file"))
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}
	f = "/" + strings.TrimLeft(f, "/")

	if err := s.Filesystem.Writefile(f, c.Request.Body); err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.Status(http.StatusNoContent)
}

// Create a directory on a server.
func postServerCreateDirectory(c *gin.Context) {
	s := GetServer(c.Param("server"))

	var data struct {
		Name string `json:"name"`
		Path string `json:"path"`
	}
	// BindJSON sends 400 if the request fails, all we need to do is return
	if err := c.BindJSON(&data); err != nil {
		return
	}

	if err := s.Filesystem.CreateDirectory(data.Name, data.Path); err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.Status(http.StatusNoContent)
}

func postServerCompressFiles(c *gin.Context) {
	s := GetServer(c.Param("server"))

	var data struct {
		RootPath string   `json:"root"`
		Paths    []string `json:"paths"`
	}

	if err := c.BindJSON(&data); err != nil {
		return
	}

	f, err := s.Filesystem.CompressFiles(data.RootPath, data.Paths)
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.JSON(http.StatusOK, &server.Stat{
		Info:     f,
		Mimetype: "application/tar+gzip",
	})
}
<commit_msg>Block request if not enough space is available<commit_after>package router

import (
	"bufio"
	"github.com/gin-gonic/gin"
	"github.com/pterodactyl/wings/server"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
)

// Returns the contents of a file on the server.
func getServerFileContents(c *gin.Context) {
	s := GetServer(c.Param("server"))

	p, err := url.QueryUnescape(c.Query("file"))
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}
	p = "/" + strings.TrimLeft(p, "/")

	cleaned, err := s.Filesystem.SafePath(p)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
			"error": "The file requested could not be found.",
		})
		return
	}

	st, err := s.Filesystem.Stat(cleaned)
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	if st.Info.IsDir() {
		c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
			"error": "The requested resource was not found on the system.",
		})
		return
	}

	f, err := os.Open(cleaned)
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}
	defer f.Close()

	c.Header("X-Mime-Type", st.Mimetype)
	c.Header("Content-Length", strconv.Itoa(int(st.Info.Size())))

	// If a download parameter is included in the URL go ahead and attach the necessary headers
	// so that the file can be downloaded.
	if c.Query("download") != "" {
		c.Header("Content-Disposition", "attachment; filename="+st.Info.Name())
		c.Header("Content-Type", "application/octet-stream")
	}

	bufio.NewReader(f).WriteTo(c.Writer)
}

// Returns the contents of a directory for a server.
func getServerListDirectory(c *gin.Context) {
	s := GetServer(c.Param("server"))

	d, err := url.QueryUnescape(c.Query("directory"))
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	stats, err := s.Filesystem.ListDirectory(d)
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.JSON(http.StatusOK, stats)
}

// Renames (or moves) a file for a server.
func putServerRenameFile(c *gin.Context) {
	s := GetServer(c.Param("server"))

	var data struct {
		RenameFrom string `json:"rename_from"`
		RenameTo   string `json:"rename_to"`
	}
	// BindJSON sends 400 if the request fails, all we need to do is return
	if err := c.BindJSON(&data); err != nil {
		return
	}

	if data.RenameFrom == "" || data.RenameTo == "" {
		c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
			"error": "Invalid paths were provided, did you forget to provide both a new and old path?",
		})
		return
	}

	if err := s.Filesystem.Rename(data.RenameFrom, data.RenameTo); err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.Status(http.StatusNoContent)
}

// Copies a server file.
func postServerCopyFile(c *gin.Context) {
	s := GetServer(c.Param("server"))

	var data struct {
		Location string `json:"location"`
	}
	// BindJSON sends 400 if the request fails, all we need to do is return
	if err := c.BindJSON(&data); err != nil {
		return
	}

	if err := s.Filesystem.Copy(data.Location); err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.Status(http.StatusNoContent)
}

// Deletes a server file.
func postServerDeleteFile(c *gin.Context) {
	s := GetServer(c.Param("server"))

	var data struct {
		Location string `json:"location"`
	}
	// BindJSON sends 400 if the request fails, all we need to do is return
	if err := c.BindJSON(&data); err != nil {
		return
	}

	if err := s.Filesystem.Delete(data.Location); err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.Status(http.StatusNoContent)
}

// Writes the contents of the request to a file on a server.
func postServerWriteFile(c *gin.Context) {
	s := GetServer(c.Param("server"))

	f, err := url.QueryUnescape(c.Query("file"))
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}
	f = "/" + strings.TrimLeft(f, "/")

	if err := s.Filesystem.Writefile(f, c.Request.Body); err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.Status(http.StatusNoContent)
}

// Create a directory on a server.
func postServerCreateDirectory(c *gin.Context) {
	s := GetServer(c.Param("server"))

	var data struct {
		Name string `json:"name"`
		Path string `json:"path"`
	}
	// BindJSON sends 400 if the request fails, all we need to do is return
	if err := c.BindJSON(&data); err != nil {
		return
	}

	if err := s.Filesystem.CreateDirectory(data.Name, data.Path); err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.Status(http.StatusNoContent)
}

func postServerCompressFiles(c *gin.Context) {
	s := GetServer(c.Param("server"))

	var data struct {
		RootPath string   `json:"root"`
		Paths    []string `json:"paths"`
	}

	if err := c.BindJSON(&data); err != nil {
		return
	}

	if !s.Filesystem.HasSpaceAvailable() {
		c.AbortWithStatusJSON(http.StatusConflict, gin.H{
			"error": "This server does not have enough available disk space to generate a compressed archive.",
		})
		return
	}

	f, err := s.Filesystem.CompressFiles(data.RootPath, data.Paths)
	if err != nil {
		TrackedServerError(err, s).AbortWithServerError(c)
		return
	}

	c.JSON(http.StatusOK, &server.Stat{
		Info:     f,
		Mimetype: "application/tar+gzip",
	})
}
<|endoftext|>