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

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/gorilla/mux"
	"github.com/mattes/migrate/migrate"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/sapcc/limes/pkg/api"
	"github.com/sapcc/limes/pkg/collector"
	"github.com/sapcc/limes/pkg/db"
	"github.com/sapcc/limes/pkg/limes"
	"github.com/sapcc/limes/pkg/util"

	_ "github.com/mattes/migrate/driver/postgres"
	_ "github.com/sapcc/limes/pkg/plugins"
)

var discoverInterval = 3 * time.Minute

func main() {
	//first two arguments must be task name and configuration file
	if len(os.Args) < 3 {
		printUsageAndExit()
	}
	taskName, configPath := os.Args[1], os.Args[2]

	//load configuration
	config := limes.NewConfiguration(configPath)

	//handle migrate task specially; it's the only one that does not need the
	//standard database and Keystone connections
	if taskName == "migrate" {
		taskMigrate(config)
		return
	}

	//all other tasks have the <cluster-id> as os.Args[3]
	if len(os.Args) < 4 {
		printUsageAndExit()
	}
	clusterID, remainingArgs := os.Args[3], os.Args[4:]

	//connect to database
	err := db.Init(config.Database)
	if err != nil {
		util.LogFatal(err.Error())
	}

	//connect to cluster
	cluster, exists := config.Clusters[clusterID]
	if !exists {
		util.LogFatal("no such cluster configured: " + clusterID)
	}
	driver, err := limes.NewDriver(cluster)
	if err != nil {
		util.LogFatal(err.Error())
	}

	//select task
	var task func(limes.Configuration, limes.Driver, []string) error
	switch taskName {
	case "collect":
		task = taskCollect
	case "serve":
		task = taskServe
	case "sync-with-elektra":
		task = taskSyncWithElektra
	default:
		printUsageAndExit()
	}

	//run task
	err = task(config, driver, remainingArgs)
	if err != nil {
		util.LogFatal(err.Error())
	}
}

var usageMessage = strings.Replace(strings.TrimSpace(`
Usage:
\t%s migrate <config-file>
\t%s (collect|serve) <config-file> <cluster-id>
\t%s sync-with-elektra <config-file> <cluster-id> <elektra-dump-url>
`), `\t`, "\t", -1) + "\n"

func printUsageAndExit() {
	fmt.Fprintf(os.Stderr, usageMessage, os.Args[0], os.Args[0], os.Args[0])
	os.Exit(1)
}

////////////////////////////////////////////////////////////////////////////////
// task: migrate

func taskMigrate(config limes.Configuration) {
	errs, ok := migrate.UpSync(config.Database.Location, config.Database.MigrationsPath)
	if !ok {
		util.LogError("migration failed, see errors on stderr")
		for _, err := range errs {
			fmt.Fprintln(os.Stderr, err.Error())
		}
		os.Exit(1)
	}
}

var dbNotExistErrRx = regexp.MustCompile(`^pq: database "([^"]+)" does not exist$`)

func createDatabaseIfNotExist(config limes.Configuration) error {
	//check if the database exists
	db, err := sql.Open("postgres", config.Database.Location)
	if err == nil {
		//apparently the "database does not exist" error only occurs when trying to issue the first statement
		_, err = db.Exec("SELECT 1")
	}
	if err == nil {
		//nothing to do
		return db.Close()
	}
	match := dbNotExistErrRx.FindStringSubmatch(err.Error())
	if match == nil {
		//unexpected error
		return err
	}
	dbName := match[1]

	//remove the database name from the connection URL
	dbURL, err := url.Parse(config.Database.Location)
	if err != nil {
		return err
	}

	dbURL.Path = "/"
	db, err = sql.Open("postgres", dbURL.String())
	if err != nil {
		return err
	}
	defer db.Close()

	_, err = db.Exec("CREATE DATABASE " + dbName)
	return err
}

////////////////////////////////////////////////////////////////////////////////
// task: collect

func taskCollect(config limes.Configuration, driver limes.Driver, args []string) error {
	if len(args) != 0 {
		printUsageAndExit()
	}
	cluster := driver.Cluster()

	//start scraping threads (NOTE: Many people use a pair of sync.WaitGroup and
	//stop channel to shutdown threads in a controlled manner. I decided against
	//that for now, and instead construct worker threads in such a way that they
	//can be terminated at any time without leaving the system in an inconsistent
	//state, mostly through usage of DB transactions.)
	for _, plugin := range cluster.QuotaPlugins {
		c := collector.NewCollector(driver, plugin)
		go c.Scrape()
	}

	//start those collector threads which operate over all services simultaneously
	c := collector.NewCollector(driver, nil)
	go c.CheckConsistency()
	go c.ScanCapacity()
	go func() {
		for {
			_, err := collector.ScanDomains(driver, collector.ScanDomainsOpts{ScanAllProjects: true})
			if err != nil {
				util.LogError(err.Error())
			}
			time.Sleep(discoverInterval)
		}
	}()

	//use main thread to emit Prometheus metrics
	if config.Collector.ExposeDataMetrics {
		prometheus.MustRegister(&collector.DataMetricsCollector{ClusterID: driver.Cluster().ID})
	}
	http.Handle("/metrics", promhttp.Handler())
	return http.ListenAndServe(config.Collector.MetricsListenAddress, nil)
}

////////////////////////////////////////////////////////////////////////////////
// task: serve

func taskServe(config limes.Configuration, driver limes.Driver, args []string) error {
	if len(args) != 0 {
		printUsageAndExit()
	}

	mainRouter := mux.NewRouter()

	//hook up the v1 API (this code is structured so that a newer API version can
	//be added easily later)
	v1Router, v1VersionData := api.NewV1Router(driver, config)
	mainRouter.PathPrefix("/v1/").Handler(v1Router)

	//add the version advertisement that lists all available API versions
	mainRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		allVersions := struct {
			Versions []api.VersionData `json:"versions"`
		}{[]api.VersionData{v1VersionData}}
		api.ReturnJSON(w, 300, allVersions)
	})

	//add Prometheus instrumentation
	http.Handle("/metrics", promhttp.Handler())
	http.HandleFunc("/", prometheus.InstrumentHandler("limes-serve", mainRouter))

	//start HTTP server
	return http.ListenAndServe(config.API.ListenAddress, nil)
}

////////////////////////////////////////////////////////////////////////////////
// task: sync-with-elektra

func taskSyncWithElektra(config limes.Configuration, driver limes.Driver, args []string) error {
	if len(args) != 1 {
		printUsageAndExit()
	}

	http.DefaultClient.Transport = http.DefaultTransport
	for {
		util.LogInfo("Starting sync...")
		err := syncWithElektra(driver, args[0], false)
		if err == nil {
			util.LogInfo("Done.")
		} else {
			util.LogError(err.Error())
		}
		time.Sleep(15 * time.Minute)
	}
}

//Entry is an entry in the JSON returned by Elektra.
type Entry struct {
	DomainUUID   string `json:"domain_id"`
	ProjectUUID  string `json:"project_id"`
	ServiceType  string `json:"service"`
	ResourceName string `json:"resource"`
	Quota        uint64 `json:"approved_quota"`
}

func syncWithElektra(driver limes.Driver, elektraURL string, freshToken bool) error {
	//build request
	req, err := http.NewRequest("GET", elektraURL, nil)
	if err != nil {
		return err
	}
	req.Header.Set("X-Auth-Token", driver.Client().TokenID)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	//check response
	switch {
	case resp.StatusCode == 200:
		//continue parsing the response below
	case resp.StatusCode == 401 && !freshToken:
		//our token might have expired - fetch a new one and retry
		err := driver.Client().ReauthFunc()
		if err != nil {
			return err
		}
		return syncWithElektra(driver, elektraURL, true)
	default:
		//unexpected response
		return fmt.Errorf("GET expected 200, got %s", resp.Status)
	}

	//parse JSON
	var entries []Entry
	err = json.NewDecoder(resp.Body).Decode(&entries)
	if err != nil {
		return err
	}

	//store data
	tx, err := db.DB.Begin()
	if err != nil {
		return err
	}
	defer db.RollbackUnlessCommitted(tx)

	stmtProject, err := tx.Prepare(`
		UPDATE project_resources SET quota = $1
		WHERE  name = $2 AND service_id IN (
			SELECT ps.id FROM project_services ps JOIN projects p ON ps.project_id = p.id
			WHERE  ps.type = $3 AND p.uuid = $4
		)
	`)
	if err != nil {
		return err
	}
	defer stmtProject.Close()

	stmtDomain, err := tx.Prepare(`
		SELECT ds.id FROM domain_services ds JOIN domains d ON ds.domain_id = d.id
		WHERE  ds.type = $1 AND d.uuid = $2
	`)
	if err != nil {
		return err
	}
	defer stmtDomain.Close()

	for _, entry := range entries {
		if entry.ProjectUUID != "" {
			_, err = stmtProject.Exec(entry.Quota, entry.ResourceName, entry.ServiceType, entry.ProjectUUID)
			if err != nil {
				return err
			}
			continue
		}

		var serviceID int64
		err = stmtDomain.QueryRow(entry.ServiceType, entry.DomainUUID).Scan(&serviceID)
		if err != nil {
			if err == sql.ErrNoRows {
				//skip services not yet supported by Limes
				continue
			}
			return err
		}

		record := &db.DomainResource{
			ServiceID: serviceID,
			Name:      entry.ResourceName,
			Quota:     entry.Quota,
		}

		rowsAffected, err := tx.Update(record)
		if err != nil {
			return err
		}
		if rowsAffected == 0 {
			err = tx.Insert(record)
			if err != nil {
				return err
			}
		}

	}
	return tx.Commit()
}
<commit_msg>add log message when starting to listen<commit_after>/*******************************************************************************
*
* Copyright 2017 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, you may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*******************************************************************************/

package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/gorilla/mux"
	"github.com/mattes/migrate/migrate"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/sapcc/limes/pkg/api"
	"github.com/sapcc/limes/pkg/collector"
	"github.com/sapcc/limes/pkg/db"
	"github.com/sapcc/limes/pkg/limes"
	"github.com/sapcc/limes/pkg/util"

	_ "github.com/mattes/migrate/driver/postgres"
	_ "github.com/sapcc/limes/pkg/plugins"
)

var discoverInterval = 3 * time.Minute

func main() {
	//first two arguments must be task name and configuration file
	if len(os.Args) < 3 {
		printUsageAndExit()
	}
	taskName, configPath := os.Args[1], os.Args[2]

	//load configuration
	config := limes.NewConfiguration(configPath)

	//handle migrate task specially; it's the only one that does not need the
	//standard database and Keystone connections
	if taskName == "migrate" {
		taskMigrate(config)
		return
	}

	//all other tasks have the <cluster-id> as os.Args[3]
	if len(os.Args) < 4 {
		printUsageAndExit()
	}
	clusterID, remainingArgs := os.Args[3], os.Args[4:]

	//connect to database
	err := db.Init(config.Database)
	if err != nil {
		util.LogFatal(err.Error())
	}

	//connect to cluster
	cluster, exists := config.Clusters[clusterID]
	if !exists {
		util.LogFatal("no such cluster configured: " + clusterID)
	}
	driver, err := limes.NewDriver(cluster)
	if err != nil {
		util.LogFatal(err.Error())
	}

	//select task
	var task func(limes.Configuration, limes.Driver, []string) error
	switch taskName {
	case "collect":
		task = taskCollect
	case "serve":
		task = taskServe
	case "sync-with-elektra":
		task = taskSyncWithElektra
	default:
		printUsageAndExit()
	}

	//run task
	err = task(config, driver, remainingArgs)
	if err != nil {
		util.LogFatal(err.Error())
	}
}

var usageMessage = strings.Replace(strings.TrimSpace(`
Usage:
\t%s migrate <config-file>
\t%s (collect|serve) <config-file> <cluster-id>
\t%s sync-with-elektra <config-file> <cluster-id> <elektra-dump-url>
`), `\t`, "\t", -1) + "\n"

func printUsageAndExit() {
	fmt.Fprintf(os.Stderr, usageMessage, os.Args[0], os.Args[0], os.Args[0])
	os.Exit(1)
}

////////////////////////////////////////////////////////////////////////////////
// task: migrate

func taskMigrate(config limes.Configuration) {
	errs, ok := migrate.UpSync(config.Database.Location, config.Database.MigrationsPath)
	if !ok {
		util.LogError("migration failed, see errors on stderr")
		for _, err := range errs {
			fmt.Fprintln(os.Stderr, err.Error())
		}
		os.Exit(1)
	}
}

var dbNotExistErrRx = regexp.MustCompile(`^pq: database "([^"]+)" does not exist$`)

func createDatabaseIfNotExist(config limes.Configuration) error {
	//check if the database exists
	db, err := sql.Open("postgres", config.Database.Location)
	if err == nil {
		//apparently the "database does not exist" error only occurs when trying to issue the first statement
		_, err = db.Exec("SELECT 1")
	}
	if err == nil {
		//nothing to do
		return db.Close()
	}
	match := dbNotExistErrRx.FindStringSubmatch(err.Error())
	if match == nil {
		//unexpected error
		return err
	}
	dbName := match[1]

	//remove the database name from the connection URL
	dbURL, err := url.Parse(config.Database.Location)
	if err != nil {
		return err
	}

	dbURL.Path = "/"
	db, err = sql.Open("postgres", dbURL.String())
	if err != nil {
		return err
	}
	defer db.Close()

	_, err = db.Exec("CREATE DATABASE " + dbName)
	return err
}

////////////////////////////////////////////////////////////////////////////////
// task: collect

func taskCollect(config limes.Configuration, driver limes.Driver, args []string) error {
	if len(args) != 0 {
		printUsageAndExit()
	}
	cluster := driver.Cluster()

	//start scraping threads (NOTE: Many people use a pair of sync.WaitGroup and
	//stop channel to shutdown threads in a controlled manner. I decided against
	//that for now, and instead construct worker threads in such a way that they
	//can be terminated at any time without leaving the system in an inconsistent
	//state, mostly through usage of DB transactions.)
	for _, plugin := range cluster.QuotaPlugins {
		c := collector.NewCollector(driver, plugin)
		go c.Scrape()
	}

	//start those collector threads which operate over all services simultaneously
	c := collector.NewCollector(driver, nil)
	go c.CheckConsistency()
	go c.ScanCapacity()
	go func() {
		for {
			_, err := collector.ScanDomains(driver, collector.ScanDomainsOpts{ScanAllProjects: true})
			if err != nil {
				util.LogError(err.Error())
			}
			time.Sleep(discoverInterval)
		}
	}()

	//use main thread to emit Prometheus metrics
	if config.Collector.ExposeDataMetrics {
		prometheus.MustRegister(&collector.DataMetricsCollector{ClusterID: driver.Cluster().ID})
	}
	http.Handle("/metrics", promhttp.Handler())
	util.LogInfo("listening on " + config.Collector.MetricsListenAddress)
	return http.ListenAndServe(config.Collector.MetricsListenAddress, nil)
}

////////////////////////////////////////////////////////////////////////////////
// task: serve

func taskServe(config limes.Configuration, driver limes.Driver, args []string) error {
	if len(args) != 0 {
		printUsageAndExit()
	}

	mainRouter := mux.NewRouter()

	//hook up the v1 API (this code is structured so that a newer API version can
	//be added easily later)
	v1Router, v1VersionData := api.NewV1Router(driver, config)
	mainRouter.PathPrefix("/v1/").Handler(v1Router)

	//add the version advertisement that lists all available API versions
	mainRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		allVersions := struct {
			Versions []api.VersionData `json:"versions"`
		}{[]api.VersionData{v1VersionData}}
		api.ReturnJSON(w, 300, allVersions)
	})

	//add Prometheus instrumentation
	http.Handle("/metrics", promhttp.Handler())
	http.HandleFunc("/", prometheus.InstrumentHandler("limes-serve", mainRouter))

	//start HTTP server
	util.LogInfo("listening on " + config.API.ListenAddress)
	return http.ListenAndServe(config.API.ListenAddress, nil)
}

////////////////////////////////////////////////////////////////////////////////
// task: sync-with-elektra

func taskSyncWithElektra(config limes.Configuration, driver limes.Driver, args []string) error {
	if len(args) != 1 {
		printUsageAndExit()
	}

	http.DefaultClient.Transport = http.DefaultTransport
	for {
		util.LogInfo("Starting sync...")
		err := syncWithElektra(driver, args[0], false)
		if err == nil {
			util.LogInfo("Done.")
		} else {
			util.LogError(err.Error())
		}
		time.Sleep(15 * time.Minute)
	}
}

//Entry is an entry in the JSON returned by Elektra.
type Entry struct {
	DomainUUID   string `json:"domain_id"`
	ProjectUUID  string `json:"project_id"`
	ServiceType  string `json:"service"`
	ResourceName string `json:"resource"`
	Quota        uint64 `json:"approved_quota"`
}

func syncWithElektra(driver limes.Driver, elektraURL string, freshToken bool) error {
	//build request
	req, err := http.NewRequest("GET", elektraURL, nil)
	if err != nil {
		return err
	}
	req.Header.Set("X-Auth-Token", driver.Client().TokenID)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	//check response
	switch {
	case resp.StatusCode == 200:
		//continue parsing the response below
	case resp.StatusCode == 401 && !freshToken:
		//our token might have expired - fetch a new one and retry
		err := driver.Client().ReauthFunc()
		if err != nil {
			return err
		}
		return syncWithElektra(driver, elektraURL, true)
	default:
		//unexpected response
		return fmt.Errorf("GET expected 200, got %s", resp.Status)
	}

	//parse JSON
	var entries []Entry
	err = json.NewDecoder(resp.Body).Decode(&entries)
	if err != nil {
		return err
	}

	//store data
	tx, err := db.DB.Begin()
	if err != nil {
		return err
	}
	defer db.RollbackUnlessCommitted(tx)

	stmtProject, err := tx.Prepare(`
		UPDATE project_resources SET quota = $1
		WHERE  name = $2 AND service_id IN (
			SELECT ps.id FROM project_services ps JOIN projects p ON ps.project_id = p.id
			WHERE  ps.type = $3 AND p.uuid = $4
		)
	`)
	if err != nil {
		return err
	}
	defer stmtProject.Close()

	stmtDomain, err := tx.Prepare(`
		SELECT ds.id FROM domain_services ds JOIN domains d ON ds.domain_id = d.id
		WHERE  ds.type = $1 AND d.uuid = $2
	`)
	if err != nil {
		return err
	}
	defer stmtDomain.Close()

	for _, entry := range entries {
		if entry.ProjectUUID != "" {
			_, err = stmtProject.Exec(entry.Quota, entry.ResourceName, entry.ServiceType, entry.ProjectUUID)
			if err != nil {
				return err
			}
			continue
		}

		var serviceID int64
		err = stmtDomain.QueryRow(entry.ServiceType, entry.DomainUUID).Scan(&serviceID)
		if err != nil {
			if err == sql.ErrNoRows {
				//skip services not yet supported by Limes
				continue
			}
			return err
		}

		record := &db.DomainResource{
			ServiceID: serviceID,
			Name:      entry.ResourceName,
			Quota:     entry.Quota,
		}

		rowsAffected, err := tx.Update(record)
		if err != nil {
			return err
		}
		if rowsAffected == 0 {
			err = tx.Insert(record)
			if err != nil {
				return err
			}
		}

	}
	return tx.Commit()
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"io"
	"os"
	"runtime"
	"strings"

	"github.com/opencontainers/runc/libcontainer/seccomp"
	"github.com/opencontainers/runtime-spec/specs-go"

	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

// version must be set from the contents of VERSION file by go build's
// -X main.version= option in the Makefile.
var version = "unknown"

// gitCommit will be the hash that the binary was built from
// and will be populated by the Makefile
var gitCommit = ""

const (
	specConfig = "config.json"
	usage      = `Open Container Initiative runtime

runc is a command line client for running applications packaged according to
the Open Container Initiative (OCI) format and is a compliant implementation of the
Open Container Initiative specification.

runc integrates well with existing process supervisors to provide a production
container runtime environment for applications. It can be used with your
existing process monitoring tools and the container will be spawned as a
direct child of the process supervisor.

Containers are configured using bundles. A bundle for a container is a directory
that includes a specification file named "` + specConfig + `" and a root filesystem.
The root filesystem contains the contents of the container.

To start a new instance of a container:

    # runc run [ -b bundle ] <container-id>

Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host. Providing the bundle directory using "-b" is optional. The default
value for "bundle" is the current directory.`
)

func main() {
	app := cli.NewApp()
	app.Name = "runc"
	app.Usage = usage

	v := []string{version}

	if gitCommit != "" {
		v = append(v, "commit: "+gitCommit)
	}
	v = append(v, "spec: "+specs.Version)
	v = append(v, "go: "+runtime.Version())

	major, minor, micro := seccomp.Version()
	if major+minor+micro > 0 {
		v = append(v, fmt.Sprintf("libseccomp: %d.%d.%d", major, minor, micro))
	}
	app.Version = strings.Join(v, "\n")

	xdgRuntimeDir := ""
	root := "/run/runc"
	if shouldHonorXDGRuntimeDir() {
		if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
			root = runtimeDir + "/runc"
			xdgRuntimeDir = root
		}
	}

	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "debug",
			Usage: "enable debug logging",
		},
		cli.StringFlag{
			Name:  "log",
			Value: "",
			Usage: "set the log file to write runc logs to (default is '/dev/stderr')",
		},
		cli.StringFlag{
			Name:  "log-format",
			Value: "text",
			Usage: "set the log format ('text' (default), or 'json')",
		},
		cli.StringFlag{
			Name:  "root",
			Value: root,
			Usage: "root directory for storage of container state (this should be located in tmpfs)",
		},
		cli.StringFlag{
			Name:  "criu",
			Value: "criu",
			Usage: "path to the criu binary used for checkpoint and restore",
		},
		cli.BoolFlag{
			Name:  "systemd-cgroup",
			Usage: "enable systemd cgroup support, expects cgroupsPath to be of form \"slice:prefix:name\" for e.g. \"system.slice:runc:434234\"",
		},
		cli.StringFlag{
			Name:  "rootless",
			Value: "auto",
			Usage: "ignore cgroup permission errors ('true', 'false', or 'auto')",
		},
	}
	app.Commands = []cli.Command{
		checkpointCommand,
		createCommand,
		deleteCommand,
		eventsCommand,
		execCommand,
		killCommand,
		listCommand,
		pauseCommand,
		psCommand,
		restoreCommand,
		resumeCommand,
		runCommand,
		specCommand,
		startCommand,
		stateCommand,
		updateCommand,
	}
	app.Before = func(context *cli.Context) error {
		if !context.IsSet("root") && xdgRuntimeDir != "" {
			// According to the XDG specification, we need to set anything in
			// XDG_RUNTIME_DIR to have a sticky bit if we don't want it to get
			// auto-pruned.
			if err := os.MkdirAll(root, 0o700); err != nil {
				fmt.Fprintln(os.Stderr, "the path in $XDG_RUNTIME_DIR must be writable by the user")
				fatal(err)
			}
			if err := os.Chmod(root, os.FileMode(0o700)|os.ModeSticky); err != nil {
				fmt.Fprintln(os.Stderr, "you should check permission of the path in $XDG_RUNTIME_DIR")
				fatal(err)
			}
		}
		if err := reviseRootDir(context); err != nil {
			return err
		}

		return configLogrus(context)
	}

	// If the command returns an error, cli takes upon itself to print
	// the error on cli.ErrWriter and exit.
	// Use our own writer here to ensure the log gets sent to the right location.
	cli.ErrWriter = &FatalWriter{cli.ErrWriter}
	if err := app.Run(os.Args); err != nil {
		fatal(err)
	}
}

type FatalWriter struct {
	cliErrWriter io.Writer
}

func (f *FatalWriter) Write(p []byte) (n int, err error) {
	logrus.Error(string(p))
	if !logrusToStderr() {
		return f.cliErrWriter.Write(p)
	}
	return len(p), nil
}

func configLogrus(context *cli.Context) error {
	if context.GlobalBool("debug") {
		logrus.SetLevel(logrus.DebugLevel)
		logrus.SetReportCaller(true)
	}

	switch f := context.GlobalString("log-format"); f {
	case "":
		// do nothing
	case "text":
		// do nothing
	case "json":
		logrus.SetFormatter(new(logrus.JSONFormatter))
	default:
		return errors.New("invalid log-format: " + f)
	}

	if file := context.GlobalString("log"); file != "" {
		f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0o644)
		if err != nil {
			return err
		}
		logrus.SetOutput(f)
	}

	return nil
}
<commit_msg>runc --debug: shorter caller info<commit_after>package main

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"

	"github.com/opencontainers/runc/libcontainer/seccomp"
	"github.com/opencontainers/runtime-spec/specs-go"

	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

// version must be set from the contents of VERSION file by go build's
// -X main.version= option in the Makefile.
var version = "unknown"

// gitCommit will be the hash that the binary was built from
// and will be populated by the Makefile
var gitCommit = ""

const (
	specConfig = "config.json"
	usage      = `Open Container Initiative runtime

runc is a command line client for running applications packaged according to
the Open Container Initiative (OCI) format and is a compliant implementation of the
Open Container Initiative specification.

runc integrates well with existing process supervisors to provide a production
container runtime environment for applications. It can be used with your
existing process monitoring tools and the container will be spawned as a
direct child of the process supervisor.

Containers are configured using bundles. A bundle for a container is a directory
that includes a specification file named "` + specConfig + `" and a root filesystem.
The root filesystem contains the contents of the container.

To start a new instance of a container:

    # runc run [ -b bundle ] <container-id>

Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host. Providing the bundle directory using "-b" is optional. The default
value for "bundle" is the current directory.`
)

func main() {
	app := cli.NewApp()
	app.Name = "runc"
	app.Usage = usage

	v := []string{version}

	if gitCommit != "" {
		v = append(v, "commit: "+gitCommit)
	}
	v = append(v, "spec: "+specs.Version)
	v = append(v, "go: "+runtime.Version())

	major, minor, micro := seccomp.Version()
	if major+minor+micro > 0 {
		v = append(v, fmt.Sprintf("libseccomp: %d.%d.%d", major, minor, micro))
	}
	app.Version = strings.Join(v, "\n")

	xdgRuntimeDir := ""
	root := "/run/runc"
	if shouldHonorXDGRuntimeDir() {
		if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
			root = runtimeDir + "/runc"
			xdgRuntimeDir = root
		}
	}

	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "debug",
			Usage: "enable debug logging",
		},
		cli.StringFlag{
			Name:  "log",
			Value: "",
			Usage: "set the log file to write runc logs to (default is '/dev/stderr')",
		},
		cli.StringFlag{
			Name:  "log-format",
			Value: "text",
			Usage: "set the log format ('text' (default), or 'json')",
		},
		cli.StringFlag{
			Name:  "root",
			Value: root,
			Usage: "root directory for storage of container state (this should be located in tmpfs)",
		},
		cli.StringFlag{
			Name:  "criu",
			Value: "criu",
			Usage: "path to the criu binary used for checkpoint and restore",
		},
		cli.BoolFlag{
			Name:  "systemd-cgroup",
			Usage: "enable systemd cgroup support, expects cgroupsPath to be of form \"slice:prefix:name\" for e.g. \"system.slice:runc:434234\"",
		},
		cli.StringFlag{
			Name:  "rootless",
			Value: "auto",
			Usage: "ignore cgroup permission errors ('true', 'false', or 'auto')",
		},
	}
	app.Commands = []cli.Command{
		checkpointCommand,
		createCommand,
		deleteCommand,
		eventsCommand,
		execCommand,
		killCommand,
		listCommand,
		pauseCommand,
		psCommand,
		restoreCommand,
		resumeCommand,
		runCommand,
		specCommand,
		startCommand,
		stateCommand,
		updateCommand,
	}
	app.Before = func(context *cli.Context) error {
		if !context.IsSet("root") && xdgRuntimeDir != "" {
			// According to the XDG specification, we need to set anything in
			// XDG_RUNTIME_DIR to have a sticky bit if we don't want it to get
			// auto-pruned.
			if err := os.MkdirAll(root, 0o700); err != nil {
				fmt.Fprintln(os.Stderr, "the path in $XDG_RUNTIME_DIR must be writable by the user")
				fatal(err)
			}
			if err := os.Chmod(root, os.FileMode(0o700)|os.ModeSticky); err != nil {
				fmt.Fprintln(os.Stderr, "you should check permission of the path in $XDG_RUNTIME_DIR")
				fatal(err)
			}
		}
		if err := reviseRootDir(context); err != nil {
			return err
		}

		return configLogrus(context)
	}

	// If the command returns an error, cli takes upon itself to print
	// the error on cli.ErrWriter and exit.
	// Use our own writer here to ensure the log gets sent to the right location.
	cli.ErrWriter = &FatalWriter{cli.ErrWriter}
	if err := app.Run(os.Args); err != nil {
		fatal(err)
	}
}

type FatalWriter struct {
	cliErrWriter io.Writer
}

func (f *FatalWriter) Write(p []byte) (n int, err error) {
	logrus.Error(string(p))
	if !logrusToStderr() {
		return f.cliErrWriter.Write(p)
	}
	return len(p), nil
}

func configLogrus(context *cli.Context) error {
	if context.GlobalBool("debug") {
		logrus.SetLevel(logrus.DebugLevel)
		logrus.SetReportCaller(true)
		// Shorten function and file names reported by the logger, by
		// trimming common "github.com/opencontainers/runc" prefix.
		// This is only done for text formatter.
		_, file, _, _ := runtime.Caller(0)
		prefix := filepath.Dir(file) + "/"
		logrus.SetFormatter(&logrus.TextFormatter{
			CallerPrettyfier: func(f *runtime.Frame) (string, string) {
				function := strings.TrimPrefix(f.Function, prefix) + "()"
				fileLine := strings.TrimPrefix(f.File, prefix) + ":" + strconv.Itoa(f.Line)
				return function, fileLine
			},
		})
	}

	switch f := context.GlobalString("log-format"); f {
	case "":
		// do nothing
	case "text":
		// do nothing
	case "json":
		logrus.SetFormatter(new(logrus.JSONFormatter))
	default:
		return errors.New("invalid log-format: " + f)
	}

	if file := context.GlobalString("log"); file != "" {
		f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0o644)
		if err != nil {
			return err
		}
		logrus.SetOutput(f)
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"flag"
	"log"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/bradfitz/http2"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/context"
	"github.com/phyber/negroni-gzip/gzip"
)

// ResponseWriter wrapper to catch 404s
type html5mode struct {
	w http.ResponseWriter
	r *http.Request
}

var webRoot string
var notFoundPath string

func redir(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	if r.TLS == nil {
		http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently)
	} else {
		next(w, r)
	}
}

func main() {
	var err error
	var port, portTLS int
	var logFile, errLogFile string
	var logger *negroni.Logger
	var errLogger *log.Logger

	html5mode := flag.Bool("html5mode", false, "On HTTP 404, serve index.html. Used with AngularJS html5mode.")
	nocacheIndex := flag.Bool("nocacheIndex", false, "Include HTTP 'Cache-Control' headers that request index.html not to be cached.")
	flag.StringVar(&notFoundPath, "404Path", "/404", "If request matches this path and file exists, then contents will be served with 404 status. Used with AngularJS html5mode.")
	flag.StringVar(&webRoot, "d", "public", "root directory of website")
	flag.StringVar(&logFile, "l", "", "log requests to a file. Defaults to stdout")
	flag.StringVar(&errLogFile, "e", "", "log errors to a file. Defaults to stdout")
	certfile := flag.String("certFile", "", "SSL certificate filename")
	keyfile := flag.String("keyFile", "", "SSL key filename")
	useGzip := flag.Bool("gzip", true, "gzip compress content")
	flag.IntVar(&port, "p", 8080, "HTTP port")
	flag.IntVar(&portTLS, "s", 8081, "HTTPS port")
	forceTLS := flag.Bool("forceTLS", false, "Force HTTPS")
	stripPrefix := flag.String("stripPrefix", "", "path to strip from incoming requests")
	flag.Parse()

	_, err = os.Lstat(webRoot)
	if err != nil {
		log.Fatalf("error opening webroot: %s\n", err)
	} else {
		log.Printf("using webroot '%s'\n", webRoot)
	}

	mux := http.NewServeMux()

	if *stripPrefix != "" {
		log.Printf("stripPrefix '%s'\n", *stripPrefix)
		// Handle either case of trailing slash
		stripPrefixTrim := strings.TrimRight(*stripPrefix, "/")
		mux.Handle(stripPrefixTrim+"/", http.StripPrefix(stripPrefixTrim+"/", http.FileServer(http.Dir(webRoot))))
	} else {
		mux.Handle("/", http.FileServer(http.Dir(webRoot)))

	}

	if logFile != "" {
		f, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalf("error opening logfile '%s', %s\n", logFile, err)
		}
		log.Printf("writing to logfile '%s'\n", logFile)
		logger = negroni.NewLogger()
		logger.Logger = log.New(f, "", log.LstdFlags)
	} else {
		logger = negroni.NewLogger()
	}

	if errLogFile != "" {
		f, err := os.OpenFile(errLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalf("error opening logfile '%s', %s\n", errLogFile, err)
		}
		log.Printf("writing errors to logfile '%s'\n", errLogFile)
		errLogger = log.New(f, "", log.LstdFlags)
	}

	n := negroni.New(
		negroni.NewRecovery(),
		logger,
	)

	if *forceTLS && *certfile != "" && *keyfile != "" {
		log.Printf("Force TLS enabled\n")
		n.Use(negroni.HandlerFunc(redir))
	}
	if *useGzip {
		log.Printf("Gzip enabled\n")
		n.Use(gzip.Gzip(gzip.DefaultCompression))
	}

	if *html5mode {
		log.Printf("HTML5 mode enabled\n")
		n.Use(negroni.HandlerFunc(html5ModeMiddleware))
	}

	if *nocacheIndex {
		log.Printf("Including Cache-Control for index.html\n")
		n.Use(negroni.HandlerFunc(nocacheIndexMiddleware))
	}

	n.UseHandler(mux)

	if *certfile != "" && *keyfile != "" {
		go func() {
			log.Printf("HTTPS listening on port %d\n", portTLS)
			config := &tls.Config{}
			// only support TLS (mitigate against POODLE attack)
			config.MinVersion = tls.VersionTLS10
			//Use only modern ciphers
			config.CipherSuites = []uint16{tls.TLS_RSA_WITH_AES_128_CBC_SHA,
				tls.TLS_RSA_WITH_AES_256_CBC_SHA,
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
				tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
				tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
				tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}
			server := &http.Server{Addr: ":" + strconv.Itoa(portTLS), Handler: n, TLSConfig: config}
			if errLogger != nil {
				server.ErrorLog = errLogger
			}
			http2.ConfigureServer(server, nil)
			log.Fatal(server.ListenAndServeTLS(*certfile, *keyfile))
		}()
	} else {
		log.Printf("HTTPS not started. Please supply SSL certificate and key")
	}

	log.Printf("HTTP listening on port %d\n", port)
	server := &http.Server{Addr: ":" + strconv.Itoa(port), Handler: n}
	if errLogger != nil {
		server.ErrorLog = errLogger
	}

	// Most browsers don't talk HTTP/2 on non-SSL, so leave this off for now
	//http2.ConfigureServer(server, nil)

	log.Fatal(server.ListenAndServe())
}

// This should come before any static file-serving middleware
func nocacheIndexMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	if path.Base(r.URL.Path) == "/" {
		rw.Header().Set("Cache-Control", "no-cache")
	}
	next(rw, r)
}

// This should come before any static file-serving middleware
func html5ModeMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	h5m := html5mode{}
	h5m.w = rw
	h5m.r = r
	next(h5m, r)
}

func (sr html5mode) Header() http.Header { return sr.w.Header() }
func (sr html5mode) Write(d []byte) (int, error) {
	if _, ok := context.GetOk(sr.r, "html5modeWritten"); ok {
		return 0, nil
	} else {
		return sr.w.Write(d)
	}
}

func (sr html5mode) WriteHeader(status int) {
	if status == 200 && sr.r.URL.Path == notFoundPath {
		// Serve 'not found' path, using hard 404 error
		sr.w.WriteHeader(404)
		return
	} else if status == 404 {
		// Serve contents of index.html if request isn't for a file
		// Required for Angular.js html5mode
		if path.Ext(sr.r.URL.Path) == "" {
			sr.w.Header().Del("Content-Type")
			http.ServeFile(sr.w, sr.r, webRoot+"/index.html")
			context.Set(sr.r, "html5modeWritten", true)
			return
		}
	}
	sr.w.WriteHeader(status)
}
<commit_msg>Add -prerender option<commit_after>package main

import (
	"crypto/tls"
	"flag"
	"log"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/bradfitz/http2"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/context"
	"github.com/phyber/negroni-gzip/gzip"
	"github.com/tampajohn/prerender"
)

// ResponseWriter wrapper to catch 404s
type html5mode struct {
	w http.ResponseWriter
	r *http.Request
}

var webRoot string
var notFoundPath string

func redir(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	if r.TLS == nil {
		http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently)
	} else {
		next(w, r)
	}
}

func main() {
	var err error
	var port, portTLS int
	var logFile, errLogFile string
	var logger *negroni.Logger
	var errLogger *log.Logger

	html5mode := flag.Bool("html5mode", false, "On HTTP 404, serve index.html. Used with AngularJS html5mode.")
	nocacheIndex := flag.Bool("nocacheIndex", false, "Include HTTP 'Cache-Control' headers that request index.html not to be cached.")
	usePrerender := flag.Bool("prerender", false, "Enable prerender.io JS rendering service.")
	flag.StringVar(&notFoundPath, "404Path", "/404", "If request matches this path and file exists, then contents will be served with 404 status. Used with AngularJS html5mode.")
	flag.StringVar(&webRoot, "d", "public", "root directory of website")
	flag.StringVar(&logFile, "l", "", "log requests to a file. Defaults to stdout")
	flag.StringVar(&errLogFile, "e", "", "log errors to a file. Defaults to stdout")
	certfile := flag.String("certFile", "", "SSL certificate filename")
	keyfile := flag.String("keyFile", "", "SSL key filename")
	useGzip := flag.Bool("gzip", true, "gzip compress content")
	flag.IntVar(&port, "p", 8080, "HTTP port")
	flag.IntVar(&portTLS, "s", 8081, "HTTPS port")
	forceTLS := flag.Bool("forceTLS", false, "Force HTTPS")
	stripPrefix := flag.String("stripPrefix", "", "path to strip from incoming requests")
	flag.Parse()

	_, err = os.Lstat(webRoot)
	if err != nil {
		log.Fatalf("error opening webroot: %s\n", err)
	} else {
		log.Printf("using webroot '%s'\n", webRoot)
	}

	mux := http.NewServeMux()

	if *stripPrefix != "" {
		log.Printf("stripPrefix '%s'\n", *stripPrefix)
		// Handle either case of trailing slash
		stripPrefixTrim := strings.TrimRight(*stripPrefix, "/")
		mux.Handle(stripPrefixTrim+"/", http.StripPrefix(stripPrefixTrim+"/", http.FileServer(http.Dir(webRoot))))
	} else {
		mux.Handle("/", http.FileServer(http.Dir(webRoot)))

	}

	if logFile != "" {
		f, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalf("error opening logfile '%s', %s\n", logFile, err)
		}
		log.Printf("writing to logfile '%s'\n", logFile)
		logger = negroni.NewLogger()
		logger.Logger = log.New(f, "", log.LstdFlags)
	} else {
		logger = negroni.NewLogger()
	}

	if errLogFile != "" {
		f, err := os.OpenFile(errLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalf("error opening logfile '%s', %s\n", errLogFile, err)
		}
		log.Printf("writing errors to logfile '%s'\n", errLogFile)
		errLogger = log.New(f, "", log.LstdFlags)
	}

	n := negroni.New(
		negroni.NewRecovery(),
		logger,
	)

	if *forceTLS && *certfile != "" && *keyfile != "" {
		log.Printf("Force TLS enabled\n")
		n.Use(negroni.HandlerFunc(redir))
	}

	if *usePrerender {
		log.Printf("Enabling prerender.io service\n")
		//prOpt := prerender.NewOptions()
		//prOpt.WhiteList = append(prOpt.WhiteList, *regexp.MustCompile(`.*`))
		n.Use(prerender.NewOptions().NewPrerender())
	}

	if *useGzip {
		log.Printf("Gzip enabled\n")
		n.Use(gzip.Gzip(gzip.DefaultCompression))
	}

	if *html5mode {
		log.Printf("HTML5 mode enabled\n")
		n.Use(negroni.HandlerFunc(html5ModeMiddleware))
	}

	if *nocacheIndex {
		log.Printf("Including Cache-Control for index.html\n")
		n.Use(negroni.HandlerFunc(nocacheIndexMiddleware))
	}

	n.UseHandler(mux)

	if *certfile != "" && *keyfile != "" {
		go func() {
			log.Printf("HTTPS listening on port %d\n", portTLS)
			config := &tls.Config{}
			// only support TLS (mitigate against POODLE attack)
			config.MinVersion = tls.VersionTLS10
			//Use only modern ciphers
			config.CipherSuites = []uint16{tls.TLS_RSA_WITH_AES_128_CBC_SHA,
				tls.TLS_RSA_WITH_AES_256_CBC_SHA,
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
				tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
				tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
				tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}
			server := &http.Server{Addr: ":" + strconv.Itoa(portTLS), Handler: n, TLSConfig: config}
			if errLogger != nil {
				server.ErrorLog = errLogger
			}
			http2.ConfigureServer(server, nil)
			log.Fatal(server.ListenAndServeTLS(*certfile, *keyfile))
		}()
	} else {
		log.Printf("HTTPS not started. Please supply SSL certificate and key")
	}

	log.Printf("HTTP listening on port %d\n", port)
	server := &http.Server{Addr: ":" + strconv.Itoa(port), Handler: n}
	if errLogger != nil {
		server.ErrorLog = errLogger
	}

	// Most browsers don't talk HTTP/2 on non-SSL, so leave this off for now
	//http2.ConfigureServer(server, nil)

	log.Fatal(server.ListenAndServe())
}

// This should come before any static file-serving middleware
func nocacheIndexMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	if path.Base(r.URL.Path) == "/" {
		rw.Header().Set("Cache-Control", "no-cache")
	}
	next(rw, r)
}

// This should come before any static file-serving middleware
func html5ModeMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	h5m := html5mode{}
	h5m.w = rw
	h5m.r = r
	next(h5m, r)
}

func (sr html5mode) Header() http.Header { return sr.w.Header() }
func (sr html5mode) Write(d []byte) (int, error) {
	if _, ok := context.GetOk(sr.r, "html5modeWritten"); ok {
		return 0, nil
	} else {
		return sr.w.Write(d)
	}
}

func (sr html5mode) WriteHeader(status int) {
	if status == 200 && sr.r.URL.Path == notFoundPath {
		// Serve 'not found' path, using hard 404 error
		sr.w.WriteHeader(404)
		return
	} else if status == 404 {
		// Serve contents of index.html if request isn't for a file
		// Required for Angular.js html5mode
		if path.Ext(sr.r.URL.Path) == "" {
			sr.w.Header().Del("Content-Type")
			http.ServeFile(sr.w, sr.r, webRoot+"/index.html")
			context.Set(sr.r, "html5modeWritten", true)
			return
		}
	}
	sr.w.WriteHeader(status)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"log"
	"net"
	"path/filepath"
	"strings"
	"time"

	etcdErr "github.com/coreos/etcd/error"
	"github.com/coreos/go-etcd/etcd"
	"github.com/miekg/dns"
)

type (
	server struct {
		etcd   *etcd.Client
		domain string
		prefix string
		ttl    uint32
	}

	record struct {
		Priority uint16 `json:"priority"`
		Weight   uint16 `json:"weight"`
		Port     uint16 `json:"port"`
		Target   string `json:"target"`
	}

	node struct {
		IP net.IP `json:"ip"`
	}
)

func isKeyNotFound(err error) bool {
	e, ok := err.(*etcd.EtcdError)
	return ok && e.ErrorCode == etcdErr.EcodeKeyNotFound
}

func nameError(w dns.ResponseWriter, req *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(req)
	m.SetRcode(req, dns.RcodeNameError)
	_ = w.WriteMsg(m)
}

func (s *server) GetNode(name string) (*node, error) {
	name = strings.TrimSuffix(name, ".nodes.")
	key := filepath.Join("/", s.prefix, "nodes", name)

	resp, err := s.etcd.Get(key, false, false)

	if err != nil {
		return nil, err
	}

	var n node
	err = json.Unmarshal([]byte(resp.Node.Value), &n)
	if err != nil {
		return nil, err
	}

	if n.IP == nil {
		return nil, nil
	}
	return &n, nil
}

func (s *server) GetService(name string) ([]*record, error) {
	name = strings.TrimSuffix(name, ".services.")

	key := filepath.Join("/", s.prefix, "services", name)

	resp, err := s.etcd.Get(key, false, true)

	if err != nil {
		return nil, err
	}

	records := make([]*record, 0, len(resp.Node.Nodes))

	for _, n := range resp.Node.Nodes {
		var rec record
		err := json.Unmarshal([]byte(n.Value), &rec)
		if err != nil {
			log.Printf("json.Unmarshal failed for %s: %s", n.Key, err)
			continue
		}

		// should match against a regex?
		if rec.Target == "" {
			continue
		}

		records = append(records, &rec)
	}

	return records, nil
}

func (s *server) ServicesA(w dns.ResponseWriter, r *dns.Msg, name string) (*dns.Msg, error) {
	records, err := s.GetService(name)

	if err != nil {
		return nil, err
	}

	if len(records) == 0 {
		return nil, nil
	}
	m := &dns.Msg{}
	m.SetReply(r)

	header := dns.RR_Header{
		Name:   r.Question[0].Name,
		Rrtype: r.Question[0].Qtype,
		Class:  r.Question[0].Qclass,
		Ttl:    s.ttl,
	}

	m.Answer = make([]dns.RR, 0, len(records))
	for _, record := range records {
		// any target that contains a "." is assumed to not be a node
		if strings.Contains(record.Target, ".") {
			continue
		}

		node, err := s.GetNode(record.Target)
		if err != nil {
			continue
		}

		answer := &dns.A{
			Hdr: header,
			A:   node.IP,
		}

		m.Answer = append(m.Answer, answer)
	}

	return m, nil

}

func (s *server) ServicesSRV(w dns.ResponseWriter, r *dns.Msg, name string) (*dns.Msg, error) {
	records, err := s.GetService(name)
	if err != nil {
		return nil, err
	}

	if len(records) == 0 {
		return nil, nil
	}

	m := &dns.Msg{}
	m.SetReply(r)

	header := dns.RR_Header{
		Name:   r.Question[0].Name,
		Rrtype: r.Question[0].Qtype,
		Class:  r.Question[0].Qclass,
		Ttl:    s.ttl,
	}

	m.Answer = make([]dns.RR, 0, len(records))
	m.Extra = make([]dns.RR, 0, len(records))
	for _, record := range records {

		isNode := false

		target := record.Target
		if !strings.Contains(target, ".") {
			target = strings.Join([]string{record.Target, s.domain}, ".")
			isNode = true
			// should we make sure it exists before we add it?
		}

		answer := &dns.SRV{
			Hdr:      header,
			Priority: record.Priority,
			Weight:   record.Weight,
			Port:     record.Port,
			Target:   target,
		}

		m.Answer = append(m.Answer, answer)

		if !isNode {
			continue
		}

		node, err := s.GetNode(record.Target)
		if err != nil {
			// just skip it
			continue
		}

		extra := &dns.A{
			Hdr: dns.RR_Header{
				Name:   target,
				Rrtype: dns.TypeA,
				Class:  r.Question[0].Qclass,
				Ttl:    s.ttl,
			},
			A: node.IP,
		}

		m.Extra = append(m.Extra, extra)
	}

	return m, nil

}

func (s *server) NodesA(w dns.ResponseWriter, r *dns.Msg, name string) (*dns.Msg, error) {
	node, err := s.GetNode(name)

	if err != nil {
		return nil, err
	}

	m := &dns.Msg{}
	m.SetReply(r)

	header := dns.RR_Header{
		Name:   r.Question[0].Name,
		Rrtype: r.Question[0].Qtype,
		Class:  r.Question[0].Qclass,
		Ttl:    s.ttl,
	}

	m.Answer = []dns.RR{
		&dns.A{
			Hdr: header,
			A:   node.IP,
		},
	}

	return m, nil

}
func (s *server) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {

	question := r.Question[0].Name
	name := strings.TrimSuffix(question, s.domain)
	parts := strings.Split(name, ".")
	parts = parts[:len(parts)-1]

	if len(parts) != 2 {
		log.Printf("invalid query: %s", question)
		nameError(w, r)
		return
	}

	rType := parts[1]
	qType := r.Question[0].Qtype

	var m *dns.Msg
	var err error
	switch rType {
	case "services":
		switch qType {
		case dns.TypeA:
			m, err = s.ServicesA(w, r, name)
		case dns.TypeSRV:
			m, err = s.ServicesSRV(w, r, name)
		}
	case "nodes":
		switch qType {
		case dns.TypeA:
			m, err = s.NodesA(w, r, name)
		}
	}

	if err != nil {
		log.Println(err)
		if isKeyNotFound(err) {
			nameError(w, r)
		} else {
			dns.HandleFailed(w, r)
		}
		return
	}

	if m == nil {
		log.Printf("%s: not found", question)
		nameError(w, r)
		return
	}

	_ = w.WriteMsg(m)
}

func main() {

	ttl := flag.Uint("ttl", 0, "DNS TTL for responses")
	domain := flag.String("domain", "local.", "domain - must end with '.'")
	eaddr := flag.String("etcd", "http://localhost:4001", "etcd address")
	prefix := flag.String("prefix", "/", "etcd prefix")
	address := flag.String("address", ":15353", "UDP address to listen")
	flag.Parse()

	e := etcd.NewClient(([]string{*eaddr}))

	s := &server{
		etcd:   e,
		domain: *domain,
		prefix: *prefix,
		ttl:    uint32(*ttl),
	}

	dns.Handle(s.domain, s)

	server := &dns.Server{
		Addr:         *address,
		Net:          "udp",
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	log.Fatal(server.ListenAndServe())

}
<commit_msg>lowercase the question<commit_after>package main

import (
	"encoding/json"
	"flag"
	"log"
	"net"
	"path/filepath"
	"strings"
	"time"

	etcdErr "github.com/coreos/etcd/error"
	"github.com/coreos/go-etcd/etcd"
	"github.com/miekg/dns"
)

type (
	server struct {
		etcd   *etcd.Client
		domain string
		prefix string
		ttl    uint32
	}

	record struct {
		Priority uint16 `json:"priority"`
		Weight   uint16 `json:"weight"`
		Port     uint16 `json:"port"`
		Target   string `json:"target"`
	}

	node struct {
		IP net.IP `json:"ip"`
	}
)

func isKeyNotFound(err error) bool {
	e, ok := err.(*etcd.EtcdError)
	return ok && e.ErrorCode == etcdErr.EcodeKeyNotFound
}

func nameError(w dns.ResponseWriter, req *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(req)
	m.SetRcode(req, dns.RcodeNameError)
	_ = w.WriteMsg(m)
}

func (s *server) GetNode(name string) (*node, error) {
	name = strings.TrimSuffix(name, ".nodes.")
	key := filepath.Join("/", s.prefix, "nodes", name)

	resp, err := s.etcd.Get(key, false, false)

	if err != nil {
		return nil, err
	}

	var n node
	err = json.Unmarshal([]byte(resp.Node.Value), &n)
	if err != nil {
		return nil, err
	}

	if n.IP == nil {
		return nil, nil
	}
	return &n, nil
}

func (s *server) GetService(name string) ([]*record, error) {
	name = strings.TrimSuffix(name, ".services.")

	key := filepath.Join("/", s.prefix, "services", name)

	resp, err := s.etcd.Get(key, false, true)

	if err != nil {
		return nil, err
	}

	records := make([]*record, 0, len(resp.Node.Nodes))

	for _, n := range resp.Node.Nodes {
		var rec record
		err := json.Unmarshal([]byte(n.Value), &rec)
		if err != nil {
			log.Printf("json.Unmarshal failed for %s: %s", n.Key, err)
			continue
		}

		// should match against a regex?
		if rec.Target == "" {
			continue
		}

		records = append(records, &rec)
	}

	return records, nil
}

func (s *server) ServicesA(w dns.ResponseWriter, r *dns.Msg, name string) (*dns.Msg, error) {
	records, err := s.GetService(name)

	if err != nil {
		return nil, err
	}

	if len(records) == 0 {
		return nil, nil
	}
	m := &dns.Msg{}
	m.SetReply(r)

	header := dns.RR_Header{
		Name:   r.Question[0].Name,
		Rrtype: r.Question[0].Qtype,
		Class:  r.Question[0].Qclass,
		Ttl:    s.ttl,
	}

	m.Answer = make([]dns.RR, 0, len(records))
	for _, record := range records {
		// any target that contains a "." is assumed to not be a node
		if strings.Contains(record.Target, ".") {
			continue
		}

		node, err := s.GetNode(record.Target)
		if err != nil {
			continue
		}

		answer := &dns.A{
			Hdr: header,
			A:   node.IP,
		}

		m.Answer = append(m.Answer, answer)
	}

	return m, nil

}

func (s *server) ServicesSRV(w dns.ResponseWriter, r *dns.Msg, name string) (*dns.Msg, error) {
	records, err := s.GetService(name)
	if err != nil {
		return nil, err
	}

	if len(records) == 0 {
		return nil, nil
	}

	m := &dns.Msg{}
	m.SetReply(r)

	header := dns.RR_Header{
		Name:   r.Question[0].Name,
		Rrtype: r.Question[0].Qtype,
		Class:  r.Question[0].Qclass,
		Ttl:    s.ttl,
	}

	m.Answer = make([]dns.RR, 0, len(records))
	m.Extra = make([]dns.RR, 0, len(records))
	for _, record := range records {

		isNode := false

		target := record.Target
		if !strings.Contains(target, ".") {
			target = strings.Join([]string{record.Target, s.domain}, ".")
			isNode = true
			// should we make sure it exists before we add it?
		}

		answer := &dns.SRV{
			Hdr:      header,
			Priority: record.Priority,
			Weight:   record.Weight,
			Port:     record.Port,
			Target:   target,
		}

		m.Answer = append(m.Answer, answer)

		if !isNode {
			continue
		}

		node, err := s.GetNode(record.Target)
		if err != nil {
			// just skip it
			continue
		}

		extra := &dns.A{
			Hdr: dns.RR_Header{
				Name:   target,
				Rrtype: dns.TypeA,
				Class:  r.Question[0].Qclass,
				Ttl:    s.ttl,
			},
			A: node.IP,
		}

		m.Extra = append(m.Extra, extra)
	}

	return m, nil

}

func (s *server) NodesA(w dns.ResponseWriter, r *dns.Msg, name string) (*dns.Msg, error) {
	node, err := s.GetNode(name)

	if err != nil {
		return nil, err
	}

	m := &dns.Msg{}
	m.SetReply(r)

	header := dns.RR_Header{
		Name:   r.Question[0].Name,
		Rrtype: r.Question[0].Qtype,
		Class:  r.Question[0].Qclass,
		Ttl:    s.ttl,
	}

	m.Answer = []dns.RR{
		&dns.A{
			Hdr: header,
			A:   node.IP,
		},
	}

	return m, nil

}
func (s *server) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {

	question := strings.ToLower(r.Question[0].Name)
	name := strings.TrimSuffix(question, s.domain)
	parts := strings.Split(name, ".")
	parts = parts[:len(parts)-1]

	if len(parts) != 2 {
		log.Printf("invalid query: %s", question)
		nameError(w, r)
		return
	}

	rType := parts[1]
	qType := r.Question[0].Qtype

	var m *dns.Msg
	var err error
	switch rType {
	case "services":
		switch qType {
		case dns.TypeA:
			m, err = s.ServicesA(w, r, name)
		case dns.TypeSRV:
			m, err = s.ServicesSRV(w, r, name)
		}
	case "nodes":
		switch qType {
		case dns.TypeA:
			m, err = s.NodesA(w, r, name)
		}
	}

	if err != nil {
		log.Println(err)
		if isKeyNotFound(err) {
			nameError(w, r)
		} else {
			dns.HandleFailed(w, r)
		}
		return
	}

	if m == nil {
		log.Printf("%s: not found", question)
		nameError(w, r)
		return
	}

	_ = w.WriteMsg(m)
}

func main() {

	ttl := flag.Uint("ttl", 0, "DNS TTL for responses")
	domain := flag.String("domain", "local.", "domain - must end with '.'")
	eaddr := flag.String("etcd", "http://localhost:4001", "etcd address")
	prefix := flag.String("prefix", "/", "etcd prefix")
	address := flag.String("address", ":15353", "UDP address to listen")
	flag.Parse()

	e := etcd.NewClient(([]string{*eaddr}))

	s := &server{
		etcd:   e,
		domain: *domain,
		prefix: *prefix,
		ttl:    uint32(*ttl),
	}

	dns.Handle(s.domain, s)

	server := &dns.Server{
		Addr:         *address,
		Net:          "udp",
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	log.Fatal(server.ListenAndServe())

}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"log"
	"os"

	"github.com/codegangsta/cli"
	"github.com/ricbra/rabbitmq-cli-consumer/command"
	"github.com/ricbra/rabbitmq-cli-consumer/config"
	"github.com/ricbra/rabbitmq-cli-consumer/consumer"
)

func main() {
	app := cli.NewApp()
	app.Name = "rabbitmq-cli-consumer"
	app.Usage = "Consume RabbitMQ easily to any cli program"
	app.Author = "Richard van den Brand"
	app.Email = "richard@vandenbrand.org"
	app.Version = "1.2.0"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "executable, e",
			Usage: "Location of executable",
		},
		cli.StringFlag{
			Name:  "configuration, c",
			Usage: "Location of configuration file",
		},
		cli.BoolFlag{
			Name:  "verbose, V",
			Usage: "Enable verbose mode (logs to stdout and stderr)",
		},
		cli.BoolFlag{
			Name:  "include, i",
			Usage: "Include metadata. Passes message as JSON data including headers, properties and message body.",
		},
		cli.BoolFlag{
			Name:  "strict-exit-code",
			Usage: "Strict exit code processing will rise a fatal error if exit code is different from allowed onces.",
		},
	}
	app.Action = func(c *cli.Context) {
		if c.String("configuration") == "" && c.String("executable") == "" {
			cli.ShowAppHelp(c)
			os.Exit(1)
		}

		verbose := c.Bool("verbose")

		logger := log.New(os.Stderr, "", log.Ldate|log.Ltime)
		cfg, err := config.LoadAndParse(c.String("configuration"))

		if err != nil {
			logger.Fatalf("Failed parsing configuration: %s\n", err)
		}

		errLogger, err := createLogger(cfg.Logs.Error, verbose, os.Stderr)
		if err != nil {
			logger.Fatalf("Failed creating error log: %s", err)
		}

		infLogger, err := createLogger(cfg.Logs.Info, verbose, os.Stdout)
		if err != nil {
			logger.Fatalf("Failed creating info log: %s", err)
		}

		factory := command.Factory(c.String("executable"))

		client, err := consumer.New(cfg, factory, errLogger, infLogger)
		if err != nil {
			errLogger.Fatalf("Failed creating consumer: %s", err)
		}
		client.IncludeMetadata = c.Bool("include")
		client.StrictExitCode = c.Bool("strict-exit-code")

		client.Consume()
	}

	app.Run(os.Args)
}

func createLogger(filename string, verbose bool, out io.Writer) (*log.Logger, error) {
	file, err := os.OpenFile(filename, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660)

	if err != nil {
		return nil, err
	}

	var writers = []io.Writer{
		file,
	}

	if verbose {
		writers = append(writers, out)
	}

	return log.New(io.MultiWriter(writers...), "", log.Ldate|log.Ltime), nil
}
<commit_msg>Prepared 1.3.0<commit_after>package main

import (
	"io"
	"log"
	"os"

	"github.com/codegangsta/cli"
	"github.com/ricbra/rabbitmq-cli-consumer/command"
	"github.com/ricbra/rabbitmq-cli-consumer/config"
	"github.com/ricbra/rabbitmq-cli-consumer/consumer"
)

func main() {
	app := cli.NewApp()
	app.Name = "rabbitmq-cli-consumer"
	app.Usage = "Consume RabbitMQ easily to any cli program"
	app.Author = "Richard van den Brand"
	app.Email = "richard@vandenbrand.org"
	app.Version = "1.3.0"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "executable, e",
			Usage: "Location of executable",
		},
		cli.StringFlag{
			Name:  "configuration, c",
			Usage: "Location of configuration file",
		},
		cli.BoolFlag{
			Name:  "verbose, V",
			Usage: "Enable verbose mode (logs to stdout and stderr)",
		},
		cli.BoolFlag{
			Name:  "include, i",
			Usage: "Include metadata. Passes message as JSON data including headers, properties and message body.",
		},
		cli.BoolFlag{
			Name:  "strict-exit-code",
			Usage: "Strict exit code processing will rise a fatal error if exit code is different from allowed onces.",
		},
	}
	app.Action = func(c *cli.Context) {
		if c.String("configuration") == "" && c.String("executable") == "" {
			cli.ShowAppHelp(c)
			os.Exit(1)
		}

		verbose := c.Bool("verbose")

		logger := log.New(os.Stderr, "", log.Ldate|log.Ltime)
		cfg, err := config.LoadAndParse(c.String("configuration"))

		if err != nil {
			logger.Fatalf("Failed parsing configuration: %s\n", err)
		}

		errLogger, err := createLogger(cfg.Logs.Error, verbose, os.Stderr)
		if err != nil {
			logger.Fatalf("Failed creating error log: %s", err)
		}

		infLogger, err := createLogger(cfg.Logs.Info, verbose, os.Stdout)
		if err != nil {
			logger.Fatalf("Failed creating info log: %s", err)
		}

		factory := command.Factory(c.String("executable"))

		client, err := consumer.New(cfg, factory, errLogger, infLogger)
		if err != nil {
			errLogger.Fatalf("Failed creating consumer: %s", err)
		}
		client.IncludeMetadata = c.Bool("include")
		client.StrictExitCode = c.Bool("strict-exit-code")

		client.Consume()
	}

	app.Run(os.Args)
}

func createLogger(filename string, verbose bool, out io.Writer) (*log.Logger, error) {
	file, err := os.OpenFile(filename, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660)

	if err != nil {
		return nil, err
	}

	var writers = []io.Writer{
		file,
	}

	if verbose {
		writers = append(writers, out)
	}

	return log.New(io.MultiWriter(writers...), "", log.Ldate|log.Ltime), nil
}
<|endoftext|>


<commit_before>package gorequest

import (
  "fmt"
  "net/http"
  "io/ioutil"
  _"encoding/json"
  "strings"
)

type Options struct{
  url string
  method string
  body string
  json string
}

func Get(url string) (error, *http.Response, string){
  client := &http.Client{}
  req, err := http.NewRequest("GET", url, nil)
  resp, err:= client.Do(req)
  if err!=nil{
    return err, nil,""
  }
  defer resp.Body.Close()
  body, err := ioutil.ReadAll(resp.Body)
  return nil, resp, string(body)
}

func CustomRequest(options Options) (error, *http.Response, string){
  fmt.Println(options)
  var (
    req *http.Request
    err error
    resp *http.Response
  )
  client := &http.Client{}
  if options.method == "POST" {
    if options.json != ""{
      content := strings.NewReader(options.json)
      req, err = http.NewRequest(options.method, options.url, content)
      req.Header.Set("Content-Type","application/json")
    }
  }
  resp, err = client.Do(req)
  if err!=nil{
    return err, nil, ""
  }
  defer resp.Body.Close()
  body, err := ioutil.ReadAll(resp.Body)
  return nil, resp, string(body)
}

func main(){
  /*err, response, body:= Get("http://localhost:1337")
  if err==nil && response.StatusCode == 200 {
    fmt.Println(body)
  }
  fmt.Println(err, response, body)*/
  options:= Options{ url: "http://localhost:1337", method:"POST", body:"hello", json:`{ "hello":"hello"}`}
  CustomRequest(options)

  /*client:= &http.Client{}
  req,_ := http.NewRequest("GET", "http://localhost:1337", nil)
  req.Header.Add("Content-Type","application/json")
  fmt.Println("main",req)
  res, _ :=  client.Do(req)
  fmt.Println(res.Body)
  /*const jsonStream =`{"sn":"sn1"}`
  reader:=strings.NewReader(jsonStream)
  resp,_ := http.Post("http://localhost:1337", "application/json", reader)
  defer resp.Body.Close()
  body,_ :=ioutil.ReadAll(resp.Body)
  fmt.Println(resp)
  fmt.Println(string(body))*/
}
<commit_msg>Change option variable to global (camelletter)<commit_after>package gorequest

import (
  "fmt"
  "net/http"
  "io/ioutil"
  _"encoding/json"
  "strings"
)

type Options struct{
  Url string
  Method string
  Body string
  Json string
}

func Get(url string) (error, *http.Response, string){
  client := &http.Client{}
  req, err := http.NewRequest("GET", url, nil)
  resp, err:= client.Do(req)
  if err!=nil{
    return err, nil,""
  }
  defer resp.Body.Close()
  body, err := ioutil.ReadAll(resp.Body)
  return nil, resp, string(body)
}

func CustomRequest(options Options) (error, *http.Response, string){
  fmt.Println(options)
  var (
    req *http.Request
    err error
    resp *http.Response
  )
  client := &http.Client{}
  if options.Method == "POST" {
    if options.Json != ""{
      content := strings.NewReader(options.Json)
      req, err = http.NewRequest(options.Method, options.Url, content)
      req.Header.Set("Content-Type","application/json")
    }
  }
  resp, err = client.Do(req)
  if err!=nil{
    return err, nil, ""
  }
  defer resp.Body.Close()
  body, err := ioutil.ReadAll(resp.Body)
  return nil, resp, string(body)
}

func main(){
  /*err, response, body:= Get("http://localhost:1337")
  if err==nil && response.StatusCode == 200 {
    fmt.Println(body)
  }
  fmt.Println(err, response, body)*/
  options:= Options{ Url: "http://localhost:1337", Method:"POST", Body:"hello", Json:`{ "hello":"hello"}`}
  CustomRequest(options)

  /*client:= &http.Client{}
  req,_ := http.NewRequest("GET", "http://localhost:1337", nil)
  req.Header.Add("Content-Type","application/json")
  fmt.Println("main",req)
  res, _ :=  client.Do(req)
  fmt.Println(res.Body)
  /*const jsonStream =`{"sn":"sn1"}`
  reader:=strings.NewReader(jsonStream)
  resp,_ := http.Post("http://localhost:1337", "application/json", reader)
  defer resp.Body.Close()
  body,_ :=ioutil.ReadAll(resp.Body)
  fmt.Println(resp)
  fmt.Println(string(body))*/
}
<|endoftext|>


<commit_before>/*
 * main.go - or-ctl-filter
 * Copyright (C) 2014  Yawning Angel
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// or-ctl-filter is a Tor Control Port filter in the spirit of
// "control-port-filter" by the Whonix developers.  It is more limited as the
// only use case considered is "I want to run Tor Browser on my desktop with a
// system tor service and have 'about:tor' and 'New Identity' work while
// disallowing scary control port commands".  But on a positive note, it's not
// a collection of bash and doesn't call netcat.
package main

import (
	"bufio"
	"encoding/hex"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"sync"
)

const (
	defaultLogFile = "or-ctl-filter.log"

	controlSocketFile = "/var/run/tor/control"
	torControlAddr    = "127.0.0.1:9151" // Match ControlPort in torrc-defaults.

	cmdProtocolInfo = "PROTOCOLINFO"
	cmdAuthenticate = "AUTHENTICATE"
	cmdGetInfo      = "GETINFO"
	cmdSignal       = "SIGNAL"

	argSignalNewnym = "NEWNYM"
	argGetinfoSocks = "net/listeners/socks"

	respProtocolInfoAuth       = "250-AUTH"
	respProtocolInfoMethods    = "METHODS="
	respProtocolInfoCookieFile = "COOKIEFILE="

	authMethodNull       = "NULL"
	authMethodCookie     = "COOKIE"
	authMethodSafeCookie = "SAFECOOKIE"

	errAuthenticationRequired = "514 Authentication required\n"
	errUnrecognizedCommand    = "510 Unrecognized command\n"
)

var filteredControlAddr *net.UnixAddr
var enableLogging bool
var logFile string

func readAuthCookie(path string) ([]byte, error) {
	// Read the cookie auth file.
	cookie, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("reading cookie auth file: %s", err)
	}
	return cookie, nil
}

func authenticate(torConn net.Conn, torConnReader *bufio.Reader, appConn net.Conn, appConnReader *bufio.Reader) error {
	var canNull, canCookie, canSafeCookie bool
	var cookiePath string

	// Figure out the best auth method, and where the cookie is if any.
	protocolInfoReq := []byte(fmt.Sprintf("%s\n", cmdProtocolInfo))
	if _, err := torConn.Write(protocolInfoReq); err != nil {
		return fmt.Errorf("writing tor PROTOCOLINFO request:%s", err)
	}
	for {
		line, err := torConnReader.ReadBytes('\n')
		if err != nil {
			return fmt.Errorf("reading tor PROTOCOLINFO response:%s", err)
		}
		lineStr := strings.TrimSpace(string(line))
		log.Printf("P<-T: [%s]\n", lineStr)
		if !strings.HasPrefix(lineStr, "250") {
			return fmt.Errorf("parsing tor PROTOCOLINFO response")
		} else if lineStr == "250 OK" {
			break
		}
		splitResp := strings.SplitN(lineStr, " ", 3)
		if splitResp[0] == respProtocolInfoAuth {
			if len(splitResp) == 1 {
				continue
			}

			methodsStr := strings.TrimPrefix(splitResp[1], respProtocolInfoMethods)
			if methodsStr == splitResp[1] {
				continue
			}
			methods := strings.Split(methodsStr, ",")
			for _, method := range methods {
				switch method {
				case authMethodNull:
					canNull = true
				case authMethodCookie:
					canCookie = true
				case authMethodSafeCookie:
					canSafeCookie = true
				}
			}
			if (canCookie || canSafeCookie) && len(splitResp) == 3 {
				cookiePathStr := strings.TrimPrefix(splitResp[2], respProtocolInfoCookieFile)
				if cookiePathStr == splitResp[2] {
					continue
				}
				cookiePath, err = strconv.Unquote(cookiePathStr)
				if err != nil {
					continue
				}
			}
		}
	}

	// Authenticate using the best possible authentication method.
	var authReq []byte
	if canNull {
		authReq = []byte(fmt.Sprintf("%s\n", cmdAuthenticate))
	} else if (canCookie || canSafeCookie) && (cookiePath != "") {
		// Read the auth cookie.
		cookie, err := readAuthCookie(cookiePath)
		if err != nil {
			return err
		}
		if canSafeCookie && false {
			// TODO: SAFECOOKIE support.
		} else if canCookie {
			cookieStr := hex.EncodeToString(cookie)
			authReq = []byte(fmt.Sprintf("%s %s\n", cmdAuthenticate, cookieStr))
		} else {
			return fmt.Errorf("SAFECOOKIE required, not implemented yet")
		}
	}
	if _, err := torConn.Write(authReq); err != nil {
		return fmt.Errorf("writing tor AUTHENTICATE request:%s", err)
	}
	authResp, err := torConnReader.ReadBytes('\n')
	if err != nil {
		return fmt.Errorf("reading tor AUTHENTICATE response:%s", err)
	}

	// "Authenticate" the application.
	authReq, err = appConnReader.ReadBytes('\n')
	if err != nil {
		return fmt.Errorf("reading app AUTHENTICATE request:%s", err)
	}
	splitReq := strings.SplitN(string(authReq), " ", 2)
	if strings.ToUpper(splitReq[0]) != cmdAuthenticate { // TODO: PROTOCOLINFO/AUTHCHALLENGE/QUIT?
		appConn.Write([]byte(errAuthenticationRequired))
		return fmt.Errorf("invalid app command: '%s'", splitReq[0])
	}
	if _, err = appConn.Write(authResp); err != nil {
		return fmt.Errorf("writing app AUTHENTICATE response:%s", err)
	}
	return nil
}

func syncedWrite(l *sync.Mutex, conn net.Conn, buf []byte) (int, error) {
	l.Lock()
	defer l.Unlock()
	return conn.Write(buf)
}

func validateCmdSignal(splitReq []string) bool {
	if len(splitReq) != 2 {
		log.Printf("A->T: Filtering SIGNAL with invalid args\n")
		return false
	}
	if splitReq[1] != argSignalNewnym {
		log.Printf("A->T: Filtering SIGNAL: [%s]\n", splitReq[1])
		return false
	}
	return true
}

func validateCmdGetinfo(splitReq []string) bool {
	if len(splitReq) != 2 {
		log.Printf("A->T: Filtering GETINFO with unexpected args\n")
		return false
	}
	if splitReq[1] != argGetinfoSocks {
		log.Printf("A->T: Filtering GETINFO: [%s]\n", splitReq[1])
		return false
	}
	return true
}

func filterConnection(appConn net.Conn) {
	defer appConn.Close()

	clientAddr := appConn.RemoteAddr()
	log.Printf("New app connection from: %s\n", clientAddr)

	torConn, err := net.DialUnix("unix", nil, filteredControlAddr)
	if err != nil {
		log.Printf("Failed to connect to the tor control port: %s\n", err)
		return
	}
	defer torConn.Close()

	// Authenticate with the real control port, and wait for the application to
	// authenticate.
	torConnReader := bufio.NewReader(torConn)
	appConnReader := bufio.NewReader(appConn)
	if err = authenticate(torConn, torConnReader, appConn, appConnReader); err != nil {
		log.Printf("Failed to authenticate: %s\n", err)
	}

	// Start filtering commands as appropriate.
	errChan := make(chan error, 2)
	var wg sync.WaitGroup
	wg.Add(2)

	var appConnLock sync.Mutex
	writeAppConn := func(b []byte) (int, error) {
		appConnLock.Lock()
		defer appConnLock.Unlock()
		return appConn.Write(b)
	}

	// Just proxy tor to application chatter.
	go func() {
		defer wg.Done()
		defer appConn.Close()
		defer torConn.Close()

		for {
			line, err := torConnReader.ReadBytes('\n')
			if err != nil {
				errChan <- err
				break
			}
			lineStr := strings.TrimSpace(string(line))
			log.Printf("A<-T: [%s]\n", lineStr)

			if _, err = writeAppConn(line); err != nil {
				errChan <- err
				break
			}
		}
	}()

	// Filter and selectively proxy or deny application to tor chatter.
	go func() {
		defer wg.Done()
		defer torConn.Close()
		defer appConn.Close()

		for {
			line, err := appConnReader.ReadBytes('\n')
			if err != nil {
				errChan <- err
				break
			}
			lineStr := strings.TrimSpace(string(line))
			log.Printf("A->T: [%s]\n", lineStr)

			// Filter out commands that aren't "required" for Tor Browser to
			// work with SKIP_LAUNCH etc set.
			allow := false // Default deny, yo.
			splitReq := strings.SplitN(lineStr, " ", 2)
			cmd := strings.ToUpper(splitReq[0])
			switch cmd {
			case cmdGetInfo:
				allow = validateCmdGetinfo(splitReq)
			case cmdSignal:
				allow = validateCmdSignal(splitReq)
			default:
				log.Printf("A->T: Filtering command: [%s]\n", cmd)
			}

			if allow {
				if _, err = torConn.Write(line); err != nil {
					errChan <- err
					break
				}
			} else {
				if _, err = writeAppConn([]byte(errUnrecognizedCommand)); err != nil {
					errChan <- err
					break
				}
			}
		}
	}()

	wg.Wait()
	if len(errChan) > 0 {
		err = <-errChan
		log.Printf("Closed client connection from: %s: %s\n", clientAddr, err)
	} else {
		log.Printf("Closed client connection from: %s\n", clientAddr)
	}
}

func main() {
	var err error

	flag.BoolVar(&enableLogging, "enable-logging", false, "enable logging")
	flag.StringVar(&logFile, "log-file", defaultLogFile, "log file")
	flag.Parse()

	// Deal with logging.
	if !enableLogging {
		log.SetOutput(ioutil.Discard)
	} else if logFile != "" {
		f, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
		if err != nil {
			log.Fatalf("Failed to create log file: %s\n", err)
		}
		log.SetOutput(f)
	}

	filteredControlAddr, err = net.ResolveUnixAddr("unix", controlSocketFile)
	if err != nil {
		log.Fatalf("Failed to resolve the control port: %s\n", err)
	}

	// Initialize the listener
	ln, err := net.Listen("tcp", torControlAddr)
	if err != nil {
		log.Fatalf("Failed to listen on the filter port: %s\n", err)
	}
	defer ln.Close()

	// Listen for incoming connections, and dispatch workers.
	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Printf("Failed to Accept(): %s\n", err)
			continue
		}
		go filterConnection(conn)
	}
}
<commit_msg>Whoops.  Handle failures to determine auth method.<commit_after>/*
 * main.go - or-ctl-filter
 * Copyright (C) 2014  Yawning Angel
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// or-ctl-filter is a Tor Control Port filter in the spirit of
// "control-port-filter" by the Whonix developers.  It is more limited as the
// only use case considered is "I want to run Tor Browser on my desktop with a
// system tor service and have 'about:tor' and 'New Identity' work while
// disallowing scary control port commands".  But on a positive note, it's not
// a collection of bash and doesn't call netcat.
package main

import (
	"bufio"
	"encoding/hex"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"sync"
)

const (
	defaultLogFile = "or-ctl-filter.log"

	controlSocketFile = "/var/run/tor/control"
	torControlAddr    = "127.0.0.1:9151" // Match ControlPort in torrc-defaults.

	cmdProtocolInfo = "PROTOCOLINFO"
	cmdAuthenticate = "AUTHENTICATE"
	cmdGetInfo      = "GETINFO"
	cmdSignal       = "SIGNAL"

	argSignalNewnym = "NEWNYM"
	argGetinfoSocks = "net/listeners/socks"

	respProtocolInfoAuth       = "250-AUTH"
	respProtocolInfoMethods    = "METHODS="
	respProtocolInfoCookieFile = "COOKIEFILE="

	authMethodNull       = "NULL"
	authMethodCookie     = "COOKIE"
	authMethodSafeCookie = "SAFECOOKIE"

	errAuthenticationRequired = "514 Authentication required\n"
	errUnrecognizedCommand    = "510 Unrecognized command\n"
)

var filteredControlAddr *net.UnixAddr
var enableLogging bool
var logFile string

func readAuthCookie(path string) ([]byte, error) {
	// Read the cookie auth file.
	cookie, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("reading cookie auth file: %s", err)
	}
	return cookie, nil
}

func authenticate(torConn net.Conn, torConnReader *bufio.Reader, appConn net.Conn, appConnReader *bufio.Reader) error {
	var canNull, canCookie, canSafeCookie bool
	var cookiePath string

	// Figure out the best auth method, and where the cookie is if any.
	protocolInfoReq := []byte(fmt.Sprintf("%s\n", cmdProtocolInfo))
	if _, err := torConn.Write(protocolInfoReq); err != nil {
		return fmt.Errorf("writing tor PROTOCOLINFO request:%s", err)
	}
	for {
		line, err := torConnReader.ReadBytes('\n')
		if err != nil {
			return fmt.Errorf("reading tor PROTOCOLINFO response:%s", err)
		}
		lineStr := strings.TrimSpace(string(line))
		log.Printf("P<-T: [%s]\n", lineStr)
		if !strings.HasPrefix(lineStr, "250") {
			return fmt.Errorf("parsing tor PROTOCOLINFO response")
		} else if lineStr == "250 OK" {
			break
		}
		splitResp := strings.SplitN(lineStr, " ", 3)
		if splitResp[0] == respProtocolInfoAuth {
			if len(splitResp) == 1 {
				continue
			}

			methodsStr := strings.TrimPrefix(splitResp[1], respProtocolInfoMethods)
			if methodsStr == splitResp[1] {
				continue
			}
			methods := strings.Split(methodsStr, ",")
			for _, method := range methods {
				switch method {
				case authMethodNull:
					canNull = true
				case authMethodCookie:
					canCookie = true
				case authMethodSafeCookie:
					canSafeCookie = true
				}
			}
			if (canCookie || canSafeCookie) && len(splitResp) == 3 {
				cookiePathStr := strings.TrimPrefix(splitResp[2], respProtocolInfoCookieFile)
				if cookiePathStr == splitResp[2] {
					continue
				}
				cookiePath, err = strconv.Unquote(cookiePathStr)
				if err != nil {
					continue
				}
			}
		}
	}

	// Authenticate using the best possible authentication method.
	var authReq []byte
	if canNull {
		authReq = []byte(fmt.Sprintf("%s\n", cmdAuthenticate))
	} else if (canCookie || canSafeCookie) && (cookiePath != "") {
		// Read the auth cookie.
		cookie, err := readAuthCookie(cookiePath)
		if err != nil {
			return err
		}
		if canSafeCookie && false {
			// TODO: SAFECOOKIE support.
		} else if canCookie {
			cookieStr := hex.EncodeToString(cookie)
			authReq = []byte(fmt.Sprintf("%s %s\n", cmdAuthenticate, cookieStr))
		} else {
			return fmt.Errorf("SAFECOOKIE required, not implemented yet")
		}
	} else {
		return fmt.Errorf("no supported authentication methods")
	}
	if _, err := torConn.Write(authReq); err != nil {
		return fmt.Errorf("writing tor AUTHENTICATE request:%s", err)
	}
	authResp, err := torConnReader.ReadBytes('\n')
	if err != nil {
		return fmt.Errorf("reading tor AUTHENTICATE response:%s", err)
	}

	// "Authenticate" the application.
	authReq, err = appConnReader.ReadBytes('\n')
	if err != nil {
		return fmt.Errorf("reading app AUTHENTICATE request:%s", err)
	}
	splitReq := strings.SplitN(string(authReq), " ", 2)
	if strings.ToUpper(splitReq[0]) != cmdAuthenticate { // TODO: PROTOCOLINFO/AUTHCHALLENGE/QUIT?
		appConn.Write([]byte(errAuthenticationRequired))
		return fmt.Errorf("invalid app command: '%s'", splitReq[0])
	}
	if _, err = appConn.Write(authResp); err != nil {
		return fmt.Errorf("writing app AUTHENTICATE response:%s", err)
	}
	return nil
}

func syncedWrite(l *sync.Mutex, conn net.Conn, buf []byte) (int, error) {
	l.Lock()
	defer l.Unlock()
	return conn.Write(buf)
}

func validateCmdSignal(splitReq []string) bool {
	if len(splitReq) != 2 {
		log.Printf("A->T: Filtering SIGNAL with invalid args\n")
		return false
	}
	if splitReq[1] != argSignalNewnym {
		log.Printf("A->T: Filtering SIGNAL: [%s]\n", splitReq[1])
		return false
	}
	return true
}

func validateCmdGetinfo(splitReq []string) bool {
	if len(splitReq) != 2 {
		log.Printf("A->T: Filtering GETINFO with unexpected args\n")
		return false
	}
	if splitReq[1] != argGetinfoSocks {
		log.Printf("A->T: Filtering GETINFO: [%s]\n", splitReq[1])
		return false
	}
	return true
}

func filterConnection(appConn net.Conn) {
	defer appConn.Close()

	clientAddr := appConn.RemoteAddr()
	log.Printf("New app connection from: %s\n", clientAddr)

	torConn, err := net.DialUnix("unix", nil, filteredControlAddr)
	if err != nil {
		log.Printf("Failed to connect to the tor control port: %s\n", err)
		return
	}
	defer torConn.Close()

	// Authenticate with the real control port, and wait for the application to
	// authenticate.
	torConnReader := bufio.NewReader(torConn)
	appConnReader := bufio.NewReader(appConn)
	if err = authenticate(torConn, torConnReader, appConn, appConnReader); err != nil {
		log.Printf("Failed to authenticate: %s\n", err)
	}

	// Start filtering commands as appropriate.
	errChan := make(chan error, 2)
	var wg sync.WaitGroup
	wg.Add(2)

	var appConnLock sync.Mutex
	writeAppConn := func(b []byte) (int, error) {
		appConnLock.Lock()
		defer appConnLock.Unlock()
		return appConn.Write(b)
	}

	// Just proxy tor to application chatter.
	go func() {
		defer wg.Done()
		defer appConn.Close()
		defer torConn.Close()

		for {
			line, err := torConnReader.ReadBytes('\n')
			if err != nil {
				errChan <- err
				break
			}
			lineStr := strings.TrimSpace(string(line))
			log.Printf("A<-T: [%s]\n", lineStr)

			if _, err = writeAppConn(line); err != nil {
				errChan <- err
				break
			}
		}
	}()

	// Filter and selectively proxy or deny application to tor chatter.
	go func() {
		defer wg.Done()
		defer torConn.Close()
		defer appConn.Close()

		for {
			line, err := appConnReader.ReadBytes('\n')
			if err != nil {
				errChan <- err
				break
			}
			lineStr := strings.TrimSpace(string(line))
			log.Printf("A->T: [%s]\n", lineStr)

			// Filter out commands that aren't "required" for Tor Browser to
			// work with SKIP_LAUNCH etc set.
			allow := false // Default deny, yo.
			splitReq := strings.SplitN(lineStr, " ", 2)
			cmd := strings.ToUpper(splitReq[0])
			switch cmd {
			case cmdGetInfo:
				allow = validateCmdGetinfo(splitReq)
			case cmdSignal:
				allow = validateCmdSignal(splitReq)
			default:
				log.Printf("A->T: Filtering command: [%s]\n", cmd)
			}

			if allow {
				if _, err = torConn.Write(line); err != nil {
					errChan <- err
					break
				}
			} else {
				if _, err = writeAppConn([]byte(errUnrecognizedCommand)); err != nil {
					errChan <- err
					break
				}
			}
		}
	}()

	wg.Wait()
	if len(errChan) > 0 {
		err = <-errChan
		log.Printf("Closed client connection from: %s: %s\n", clientAddr, err)
	} else {
		log.Printf("Closed client connection from: %s\n", clientAddr)
	}
}

func main() {
	var err error

	flag.BoolVar(&enableLogging, "enable-logging", false, "enable logging")
	flag.StringVar(&logFile, "log-file", defaultLogFile, "log file")
	flag.Parse()

	// Deal with logging.
	if !enableLogging {
		log.SetOutput(ioutil.Discard)
	} else if logFile != "" {
		f, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
		if err != nil {
			log.Fatalf("Failed to create log file: %s\n", err)
		}
		log.SetOutput(f)
	}

	filteredControlAddr, err = net.ResolveUnixAddr("unix", controlSocketFile)
	if err != nil {
		log.Fatalf("Failed to resolve the control port: %s\n", err)
	}

	// Initialize the listener
	ln, err := net.Listen("tcp", torControlAddr)
	if err != nil {
		log.Fatalf("Failed to listen on the filter port: %s\n", err)
	}
	defer ln.Close()

	// Listen for incoming connections, and dispatch workers.
	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Printf("Failed to Accept(): %s\n", err)
			continue
		}
		go filterConnection(conn)
	}
}
<|endoftext|>


<commit_before>package main

//go:generate esc -o templates.go ./templates

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/bukalapak/snowboard/adapter/drafter"
	"github.com/bukalapak/snowboard/adapter/drafterc"
	snowboard "github.com/bukalapak/snowboard/parser"
	"github.com/fsnotify/fsnotify"
	"github.com/urfave/cli"
)

var versionStr string
var (
	engine  snowboard.Parser
	engineC snowboard.Parser
)

func main() {
	engine = drafter.Engine{}
	engineC = drafterc.Engine{}

	cli.VersionPrinter = func(c *cli.Context) {
		fmt.Fprintf(c.App.Writer, "Snowboard version: %s\n", c.App.Version)
		fmt.Fprintf(c.App.Writer, "Drafter version: %s\n", engine.Version())
	}

	if versionStr == "" {
		versionStr = "HEAD"
	}

	app := cli.NewApp()
	app.Name = "snowboard"
	app.Usage = "API blueprint toolkit"
	app.Version = versionStr
	app.Commands = []cli.Command{
		{
			Name:  "lint",
			Usage: "Validate API blueprint",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "i",
					Usage: "API blueprint file",
				},
				cli.BoolFlag{
					Name:  "u",
					Usage: "Use line and row number instead of charater index",
				},
			},
			Action: func(c *cli.Context) error {
				return validate(c, c.String("i"), c.Bool("u"))
			},
		},
		{
			Name:  "html",
			Usage: "Render HTML documentation",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "i",
					Usage: "API blueprint file",
				},
				cli.StringFlag{
					Name:  "o",
					Value: "index.html",
					Usage: "HTML file",
				},
				cli.StringFlag{
					Name:  "t",
					Value: "alpha",
					Usage: "Template for HTML documentation",
				},
				cli.BoolFlag{
					Name:  "s",
					Usage: "Serve HTML via HTTP server",
				},
				cli.StringFlag{
					Name:  "b",
					Value: "127.0.0.1:8088",
					Usage: "HTTP server listen address",
				},
			},
			Action: func(c *cli.Context) error {
				if c.Bool("s") {
					return watchHTML(c, c.String("i"), c.String("o"), c.String("t"), c.String("b"))
				}

				return renderHTML(c, c.String("i"), c.String("o"), c.String("t"))
			},
		},
		{
			Name:  "apib",
			Usage: "Render API blueprint",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "i",
					Usage: "API blueprint file",
				},
				cli.StringFlag{
					Name:  "o",
					Usage: "API blueprint output file",
				},
			},
			Action: func(c *cli.Context) error {
				return renderAPIB(c, c.String("i"), c.String("o"))
			},
		},
		{
			Name:  "mock",
			Usage: "Run Mock server",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "i",
					Usage: "API blueprint file",
				},
				cli.StringFlag{
					Name:  "b",
					Value: "127.0.0.1:8087",
					Usage: "HTTP server listen address",
				},
			},
			Action: func(c *cli.Context) error {
				return serveMock(c, c.String("b"), c.String("i"))
			},
		},
		{
			Name:  "adapter",
			Usage: "Snowboard adapter",
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "c",
					Usage: "Copy snowboard adapter to desired location",
				},
				cli.StringFlag{
					Name:  "p",
					Value: ".",
					Usage: "Adapter location dir",
				},
			},
			Action: func(c *cli.Context) error {
				if c.Bool("c") {
					return installAdapters(c, c.String("p"))
				}

				return nil
			},
		},
	}
	app.Run(os.Args)
}

func readFile(fn string) ([]byte, error) {
	info, err := os.Stat(fn)
	if err != nil {
		return nil, errors.New("File is not exist")
	}

	if info.IsDir() {
		return nil, errors.New("File is a directory")
	}

	return ioutil.ReadFile(fn)
}

func readTemplate(fn string) ([]byte, error) {
	tf, err := readFile(fn)
	if err == nil {
		return tf, nil
	}

	fs := FS(false)
	ff, err := fs.Open("/templates/" + fn + ".html")
	if err != nil {
		return nil, err
	}

	defer ff.Close()
	return ioutil.ReadAll(ff)
}

func renderHTML(c *cli.Context, input, output, tplFile string) error {
	bp, err := snowboard.Load(input, engine)
	if err != nil {
		return err
	}

	of, err := os.Create(output)
	if err != nil {
		return err
	}
	defer of.Close()

	tf, err := readTemplate(tplFile)
	if err != nil {
		return err
	}

	err = snowboard.HTML(string(tf), of, bp)
	if err != nil {
		return err
	}

	fmt.Fprintln(c.App.Writer, "HTML has been generated!")
	return nil
}

func renderAPIB(c *cli.Context, input, output string) error {
	b, err := snowboard.Read(input)
	if err != nil {
		return err
	}

	of, err := os.Create(output)
	if err != nil {
		return err
	}
	defer of.Close()

	_, err = io.Copy(of, bytes.NewReader(b))
	if err != nil {
		return err
	}

	fmt.Fprintln(c.App.Writer, "API blueprint has been generated!")
	return nil
}

func validate(c *cli.Context, input string, lineNum bool) error {
	b, err := readFile(input)
	if err != nil {
		return err
	}

	bf := bytes.NewReader(b)

	if !lineNum {
		out, err := snowboard.Validate(bf, engine)
		if err != nil {
			return err
		}

		if out == nil {
			fmt.Fprintln(c.App.Writer, "OK")
			return nil
		}

		var buf bytes.Buffer

		s := "--------"
		w := tabwriter.NewWriter(&buf, 8, 0, 0, ' ', tabwriter.Debug)
		fmt.Fprintln(w, "Row\tCol\tDescription")
		fmt.Fprintf(w, "%s\t%s\t%s\n", s, s, strings.Repeat(s, 8))

		for _, n := range out.Annotations {
			for _, m := range n.SourceMaps {
				fmt.Fprintf(w, "%d\t%d\t%s\n", m.Row, m.Col, n.Description)
			}
		}

		w.Flush()

		if len(out.Annotations) > 0 {
			return errors.New(buf.String())
		}
	} else {
		b, err := engineC.Validate(bf)
		if err != nil {
			return err
		}

		var buf bytes.Buffer

		w := tabwriter.NewWriter(&buf, 8, 0, 0, ' ', tabwriter.Debug)
		fmt.Fprintln(w, "Location\tSeverity\tDescription")

		fmt.Fprintf(w, "%s\t%s\t%s\n", dash(42), dash(16), dash(80))

		if string(b) == "" {
			fmt.Fprintln(c.App.Writer, "OK")
			return nil
		}

		ns := strings.Split(string(b), "\n")

		for _, n := range ns {
			nn := strings.SplitN(n, "; ", 2)
			mm := strings.SplitN(nn[0], "  ", 2)
			fmt.Fprintf(w, "%s\t%s\t%s\n", nn[1], mm[0], mm[1])
		}

		w.Flush()
		return errors.New(buf.String())
	}

	return nil
}

func dash(n int) string {
	return strings.Repeat("-", n)
}

func watchHTML(c *cli.Context, input, output, tplFile, bind string) error {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}
	defer watcher.Close()

	done := make(chan bool)
	go func() {
		for {
			select {
			case event := <-watcher.Events:
				if event.Op&fsnotify.Write == fsnotify.Write {
					renderHTML(c, input, output, tplFile)
				}
			case err := <-watcher.Errors:
				fmt.Fprintln(c.App.Writer, err)
			}
		}
	}()

	err = watcher.Add(input)
	if err != nil {
		return err
	}

	if _, err = os.Stat(tplFile); err == nil {
		err = watcher.Add(tplFile)
		if err != nil {
			return err
		}
	}

	renderHTML(c, input, output, tplFile)
	serveHTML(bind, output)

	<-done

	return nil
}

func serveHTML(bind, output string) error {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, output)
	})

	return http.ListenAndServe(bind, nil)
}

func serveMock(c *cli.Context, bind, input string) error {
	bp, err := snowboard.Load(input, engine)
	if err != nil {
		return err
	}

	fmt.Fprintf(c.App.Writer, "Mock server is ready. Use %s\n", bind)
	fmt.Fprintln(c.App.Writer, "Available Routes:")

	ms := snowboard.Mock(bp)
	for _, m := range ms {
		fmt.Fprintf(c.App.Writer, "%s\t%d\t%s\n", m.Method, m.StatusCode, m.Pattern)
	}

	h := snowboard.MockHandler(ms)
	return http.ListenAndServe(bind, h)
}

func installAdapters(c *cli.Context, dir string) error {
	n := drafterc.Engine{}
	name, err := n.CopyExec(dir)
	if err != nil {
		return err
	}

	fmt.Fprintln(c.App.Writer, "Snowboard adapter installed!\nLocation: "+name)
	return nil
}
<commit_msg>separate lint output to stderr and stdout, fixes #16<commit_after>package main

//go:generate esc -o templates.go ./templates

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/bukalapak/snowboard/adapter/drafter"
	"github.com/bukalapak/snowboard/adapter/drafterc"
	snowboard "github.com/bukalapak/snowboard/parser"
	"github.com/fsnotify/fsnotify"
	xerrors "github.com/pkg/errors"
	"github.com/urfave/cli"
)

var versionStr string
var (
	engine  snowboard.Parser
	engineC snowboard.Parser
)

func main() {
	engine = drafter.Engine{}
	engineC = drafterc.Engine{}

	cli.VersionPrinter = func(c *cli.Context) {
		fmt.Fprintf(c.App.Writer, "Snowboard version: %s\n", c.App.Version)
		fmt.Fprintf(c.App.Writer, "Drafter version: %s\n", engine.Version())
	}

	if versionStr == "" {
		versionStr = "HEAD"
	}

	app := cli.NewApp()
	app.Name = "snowboard"
	app.Usage = "API blueprint toolkit"
	app.Version = versionStr
	app.Commands = []cli.Command{
		{
			Name:  "lint",
			Usage: "Validate API blueprint",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "i",
					Usage: "API blueprint file",
				},
				cli.BoolFlag{
					Name:  "u",
					Usage: "Use line and row number instead of charater index",
				},
			},
			Action: func(c *cli.Context) error {
				err := validate(c, c.String("i"), c.Bool("u"))

				if strings.Contains(err.Error(), "read failed") {
					return xerrors.Cause(err)
				}

				io.WriteString(c.App.Writer, err.Error())
				return nil
			},
		},
		{
			Name:  "html",
			Usage: "Render HTML documentation",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "i",
					Usage: "API blueprint file",
				},
				cli.StringFlag{
					Name:  "o",
					Value: "index.html",
					Usage: "HTML file",
				},
				cli.StringFlag{
					Name:  "t",
					Value: "alpha",
					Usage: "Template for HTML documentation",
				},
				cli.BoolFlag{
					Name:  "s",
					Usage: "Serve HTML via HTTP server",
				},
				cli.StringFlag{
					Name:  "b",
					Value: "127.0.0.1:8088",
					Usage: "HTTP server listen address",
				},
			},
			Action: func(c *cli.Context) error {
				if c.Bool("s") {
					return watchHTML(c, c.String("i"), c.String("o"), c.String("t"), c.String("b"))
				}

				return renderHTML(c, c.String("i"), c.String("o"), c.String("t"))
			},
		},
		{
			Name:  "apib",
			Usage: "Render API blueprint",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "i",
					Usage: "API blueprint file",
				},
				cli.StringFlag{
					Name:  "o",
					Usage: "API blueprint output file",
				},
			},
			Action: func(c *cli.Context) error {
				return renderAPIB(c, c.String("i"), c.String("o"))
			},
		},
		{
			Name:  "mock",
			Usage: "Run Mock server",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "i",
					Usage: "API blueprint file",
				},
				cli.StringFlag{
					Name:  "b",
					Value: "127.0.0.1:8087",
					Usage: "HTTP server listen address",
				},
			},
			Action: func(c *cli.Context) error {
				return serveMock(c, c.String("b"), c.String("i"))
			},
		},
		{
			Name:  "adapter",
			Usage: "Snowboard adapter",
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "c",
					Usage: "Copy snowboard adapter to desired location",
				},
				cli.StringFlag{
					Name:  "p",
					Value: ".",
					Usage: "Adapter location dir",
				},
			},
			Action: func(c *cli.Context) error {
				if c.Bool("c") {
					return installAdapters(c, c.String("p"))
				}

				return nil
			},
		},
	}
	app.Run(os.Args)
}

func readFile(fn string) ([]byte, error) {
	info, err := os.Stat(fn)
	if err != nil {
		return nil, errors.New("File is not exist")
	}

	if info.IsDir() {
		return nil, errors.New("File is a directory")
	}

	return ioutil.ReadFile(fn)
}

func readTemplate(fn string) ([]byte, error) {
	tf, err := readFile(fn)
	if err == nil {
		return tf, nil
	}

	fs := FS(false)
	ff, err := fs.Open("/templates/" + fn + ".html")
	if err != nil {
		return nil, err
	}

	defer ff.Close()
	return ioutil.ReadAll(ff)
}

func renderHTML(c *cli.Context, input, output, tplFile string) error {
	bp, err := snowboard.Load(input, engine)
	if err != nil {
		return err
	}

	of, err := os.Create(output)
	if err != nil {
		return err
	}
	defer of.Close()

	tf, err := readTemplate(tplFile)
	if err != nil {
		return err
	}

	err = snowboard.HTML(string(tf), of, bp)
	if err != nil {
		return err
	}

	fmt.Fprintln(c.App.Writer, "HTML has been generated!")
	return nil
}

func renderAPIB(c *cli.Context, input, output string) error {
	b, err := snowboard.Read(input)
	if err != nil {
		return err
	}

	of, err := os.Create(output)
	if err != nil {
		return err
	}
	defer of.Close()

	_, err = io.Copy(of, bytes.NewReader(b))
	if err != nil {
		return err
	}

	fmt.Fprintln(c.App.Writer, "API blueprint has been generated!")
	return nil
}

func validate(c *cli.Context, input string, lineNum bool) error {
	b, err := readFile(input)
	if err != nil {
		return xerrors.Wrap(err, "read failed")
	}

	bf := bytes.NewReader(b)

	if !lineNum {
		out, err := snowboard.Validate(bf, engine)
		if err != nil {
			return err
		}

		if out == nil {
			fmt.Fprintln(c.App.Writer, "OK")
			return nil
		}

		var buf bytes.Buffer

		s := "--------"
		w := tabwriter.NewWriter(&buf, 8, 0, 0, ' ', tabwriter.Debug)
		fmt.Fprintln(w, "Row\tCol\tDescription")
		fmt.Fprintf(w, "%s\t%s\t%s\n", s, s, strings.Repeat(s, 8))

		for _, n := range out.Annotations {
			for _, m := range n.SourceMaps {
				fmt.Fprintf(w, "%d\t%d\t%s\n", m.Row, m.Col, n.Description)
			}
		}

		w.Flush()

		if len(out.Annotations) > 0 {
			return errors.New(buf.String())
		}
	} else {
		b, err := engineC.Validate(bf)
		if err != nil {
			return err
		}

		var buf bytes.Buffer

		w := tabwriter.NewWriter(&buf, 8, 0, 0, ' ', tabwriter.Debug)
		fmt.Fprintln(w, "Location\tSeverity\tDescription")

		fmt.Fprintf(w, "%s\t%s\t%s\n", dash(42), dash(16), dash(80))

		if string(b) == "" {
			fmt.Fprintln(c.App.Writer, "OK")
			return nil
		}

		ns := strings.Split(string(b), "\n")

		for _, n := range ns {
			nn := strings.SplitN(n, "; ", 2)
			mm := strings.SplitN(nn[0], "  ", 2)
			fmt.Fprintf(w, "%s\t%s\t%s\n", nn[1], mm[0], mm[1])
		}

		w.Flush()
		return errors.New(buf.String())
	}

	return nil
}

func dash(n int) string {
	return strings.Repeat("-", n)
}

func watchHTML(c *cli.Context, input, output, tplFile, bind string) error {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}
	defer watcher.Close()

	done := make(chan bool)
	go func() {
		for {
			select {
			case event := <-watcher.Events:
				if event.Op&fsnotify.Write == fsnotify.Write {
					renderHTML(c, input, output, tplFile)
				}
			case err := <-watcher.Errors:
				fmt.Fprintln(c.App.Writer, err)
			}
		}
	}()

	err = watcher.Add(input)
	if err != nil {
		return err
	}

	if _, err = os.Stat(tplFile); err == nil {
		err = watcher.Add(tplFile)
		if err != nil {
			return err
		}
	}

	renderHTML(c, input, output, tplFile)
	serveHTML(bind, output)

	<-done

	return nil
}

func serveHTML(bind, output string) error {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, output)
	})

	return http.ListenAndServe(bind, nil)
}

func serveMock(c *cli.Context, bind, input string) error {
	bp, err := snowboard.Load(input, engine)
	if err != nil {
		return err
	}

	fmt.Fprintf(c.App.Writer, "Mock server is ready. Use %s\n", bind)
	fmt.Fprintln(c.App.Writer, "Available Routes:")

	ms := snowboard.Mock(bp)
	for _, m := range ms {
		fmt.Fprintf(c.App.Writer, "%s\t%d\t%s\n", m.Method, m.StatusCode, m.Pattern)
	}

	h := snowboard.MockHandler(ms)
	return http.ListenAndServe(bind, h)
}

func installAdapters(c *cli.Context, dir string) error {
	n := drafterc.Engine{}
	name, err := n.CopyExec(dir)
	if err != nil {
		return err
	}

	fmt.Fprintln(c.App.Writer, "Snowboard adapter installed!\nLocation: "+name)
	return nil
}
<|endoftext|>