<commit_before>package kontrol

import (
	"database/sql"
	"errors"
	"fmt"
	"regexp"
	"strings"

	"github.com/hashicorp/go-version"
	_ "github.com/lib/pq"

	"github.com/koding/kite"
	kontrolprotocol "github.com/koding/kite/kontrol/protocol"
	"github.com/koding/kite/protocol"
	"github.com/koding/logging"
)

// Postgres holds Postgresql database related configuration
type PostgresConfig struct {
	Host     string
	Port     int
	Username string
	Password string
	DBName   string
}

type Postgres struct {
	DB  *sql.DB
	Log logging.Logger
}

func NewPostgres(conf *PostgresConfig, log kite.Logger) *Postgres {
	if conf.Port == 0 {
		conf.Port = 5432
	}

	if conf.Host == "" {
		conf.Host = "localhost"
	}

	if conf.DBName == "" {
		conf.DBName = "test"
	}

	connString := fmt.Sprintf(
		"host=%s port=%d dbname=%s sslmode=disable",
		conf.Host, conf.Port, conf.DBName,
	)

	if conf.Password != "" {
		connString += " password=" + conf.Password
	}

	if conf.Username != "" {
		connString += " user=" + conf.Username
	}

	fmt.Printf("connString %+v\n", connString)

	db, err := sql.Open("postgres", connString)
	if err != nil {
		panic(err)
	}

	// enable the ltree module which we are going to use, any error means it's
	// failed so there is no sense to continue, panic!
	enableTree := `CREATE EXTENSION IF NOT EXISTS ltree`
	if _, err := db.Exec(enableTree); err != nil {
		panic(err)
	}

	// create our initial kites table
	// * kite is going to be our ltree
	// * url is cointaining the kite's register url
	// * id is going to be kites' unique id (which also exists in the ltree
	//   path). We are adding it as a primary key so each kite with the full
	//   path can only exist once.
	// * created_at and updated_at are updated at creation and updating (like
	//  if the URL has changed)
	// Some notes:
	// * path label can only contain a sequence of alphanumeric characters
	//   and underscores. So for example a version string of "1.0.4" needs to
	//   be converted to "1_0_4" or uuid of 1111-2222-3333-4444 needs to be
	//   converted to 1111_2222_3333_4444.
	table := `CREATE TABLE IF NOT EXISTS kites (
		kite ltree NOT NULL,
		url text NOT NULL,
		id uuid PRIMARY KEY,
		created_at timestamptz NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
		updated_at timestamptz NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC')
	);`

	if _, err := db.Exec(table); err != nil {
		panic(err)
	}

	// We enable index on the kite field. We don't return on errors because the
	// operator `IF NOT EXISTS` doesn't work for index creation, therefore we
	// assume the indexes might be already created.
	enableGistIndex := `CREATE INDEX kite_path_gist_idx ON kites USING GIST(kite)`
	enableBtreeIndex := `CREATE INDEX kite_path_btree_idx ON kites USING BTREE(kite)`

	if _, err := db.Exec(enableGistIndex); err != nil {
		log.Warning("postgres: enable gist index: ", err)
	}

	if _, err := db.Exec(enableBtreeIndex); err != nil {
		log.Warning("postgres: enable btree index: ", err)
	}

	return &Postgres{
		DB: db,
	}
}

func (p *Postgres) Get(query *protocol.KontrolQuery) (Kites, error) {
	// only let query with usernames, otherwise the whole tree will be fetched
	// which is not good for us
	if query.Username == "" {
		return nil, errors.New("username is not specified in query")
	}

	// NewVersion returns an error if it's a constraint, like: ">= 1.0, < 1.4"
	_, err := version.NewVersion(query.Version)
	if err != nil && query.Version != "" {
		return nil, errors.New("version constraint is not implemented")
	}

	path := ltreePath(query)

	// TODO: see how a prepared statements perfoms out
	rows, err := p.DB.Query(`SELECT kite, url FROM kites WHERE kite <@ $1`, path)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var kitePath string
	var url string

	kites := make(Kites, 0)

	for rows.Next() {
		err := rows.Scan(&kitePath, &url)
		if err != nil {
			return nil, err
		}

		kiteProt, err := kiteFromPath(kitePath)
		if err != nil {
			return nil, err
		}

		kites = append(kites, &protocol.KiteWithToken{
			Kite: *kiteProt,
			URL:  url,
		})

		fmt.Printf("kitePath %+v\n", kitePath)
		fmt.Printf("url %+v\n", url)
	}

	if err := rows.Err(); err != nil {
		return nil, err
	}

	kites.Shuffle()

	return kites, nil
}

func (p *Postgres) Add(kiteProt *protocol.Kite, value *kontrolprotocol.RegisterValue) error {
	_, err := p.DB.Exec("INSERT into kites(kite, url, id) VALUES($1, $2, $3)",
		ltreePath(kiteProt.Query()),
		value.URL,
		kiteProt.ID,
	)
	return err
}

func (p *Postgres) Update(kiteProt *protocol.Kite, value *kontrolprotocol.RegisterValue) error {
	// TODO: also consider just usting WHERE id = kiteProt.ID, see how it's
	// perfoms out
	_, err := p.DB.Exec(`UPDATE kites SET url = $1, updated_at = (now() at time zone 'utc')
	WHERE kite ~ $2`,
		value.URL, ltreePath(kiteProt.Query()))

	return err
}

func (p *Postgres) Delete(kite *protocol.Kite) error {
	return errors.New("DELETE is not implemented")
}

// ltreeLabel satisfies a invalid ltree definition of a label in path.
// According to the definition it is: "A label is a sequence of alphanumeric
// characters and underscores (for example, in C locale the characters
// A-Za-z0-9_ are allowed). Labels must be less than 256 bytes long."
//
// We could express one character with "[A-Za-z0-9_]", a word with
// "[A-Za-z0-9_]+". However we want to catch words that are not valid labels so
// we negate them with the "^" character, so it will be : "[^[A-Za-z0-9_]]+".
// Finally we cann use the POSIX character class: [:word:] which is:
// "Alphanumeric characters plus "_"", so the final regexp will be
// "[^[:word]]+"
var invalidLabelRe = regexp.MustCompile("[^[:word:]]+")

// ltreePath returns a query path to be used with the ltree module in postgress
// in the form of "username.environment.kitename.version.region.hostname.id"
func ltreePath(query *protocol.KontrolQuery) string {
	path := ""
	fields := query.Fields()

	// we stop for the first empty value
	for _, key := range keyOrder {
		v := fields[key]
		if v == "" {
			break
		}

		// replace anything that doesn't match the definition for a ltree path
		// label with a underscore, so the version "0.0.1" will be "0_0_1", or
		// uuid of "1111-2222-3333-4444" will be converted to
		// 1111_2222_3333_4444. Strings that satisifies the requirement are
		// untouched.
		v = invalidLabelRe.ReplaceAllLiteralString(v, "_")

		path = path + v + "."
	}

	// remove the latest dot which causes an invalid query
	path = strings.TrimSuffix(path, ".")
	return path
}

// kiteFromPath returns a Query from the given ltree path label
func kiteFromPath(path string) (*protocol.Kite, error) {
	fields := strings.Split(path, ".")

	if len(fields) != 7 {
		return nil, fmt.Errorf("invalid ltree path: %s", path)
	}

	// those labels were converted by us, therefore convert them back
	version := strings.Replace(fields[3], "_", ".", -1)
	id := strings.Replace(fields[6], "_", "-", -1)

	return &protocol.Kite{
		Username:    fields[0],
		Environment: fields[1],
		Name:        fields[2],
		Version:     version,
		Region:      fields[4],
		Hostname:    fields[5],
		ID:          id,
	}, nil

}
<commit_msg>kontrol/postgres: some improvements to prevent malformed inputs<commit_after>package kontrol

import (
	"database/sql"
	"errors"
	"fmt"
	"net/url"
	"regexp"
	"strings"

	"github.com/hashicorp/go-version"
	_ "github.com/lib/pq"

	"github.com/koding/kite"
	kontrolprotocol "github.com/koding/kite/kontrol/protocol"
	"github.com/koding/kite/protocol"
	"github.com/koding/logging"
)

// Postgres holds Postgresql database related configuration
type PostgresConfig struct {
	Host     string
	Port     int
	Username string
	Password string
	DBName   string
}

type Postgres struct {
	DB  *sql.DB
	Log logging.Logger
}

func NewPostgres(conf *PostgresConfig, log kite.Logger) *Postgres {
	if conf.Port == 0 {
		conf.Port = 5432
	}

	if conf.Host == "" {
		conf.Host = "localhost"
	}

	if conf.DBName == "" {
		conf.DBName = "test"
	}

	connString := fmt.Sprintf(
		"host=%s port=%d dbname=%s sslmode=disable",
		conf.Host, conf.Port, conf.DBName,
	)

	if conf.Password != "" {
		connString += " password=" + conf.Password
	}

	if conf.Username != "" {
		connString += " user=" + conf.Username
	}

	log.Info("connString %+v\n", connString)

	db, err := sql.Open("postgres", connString)
	if err != nil {
		panic(err)
	}

	// enable the ltree module which we are going to use, any error means it's
	// failed so there is no sense to continue, panic!
	enableTree := `CREATE EXTENSION IF NOT EXISTS ltree`
	if _, err := db.Exec(enableTree); err != nil {
		panic(err)
	}

	// create our initial kites table
	// * kite is going to be our ltree
	// * url is cointaining the kite's register url
	// * id is going to be kites' unique id (which also exists in the ltree
	//   path). We are adding it as a primary key so each kite with the full
	//   path can only exist once.
	// * created_at and updated_at are updated at creation and updating (like
	//  if the URL has changed)
	// Some notes:
	// * path label can only contain a sequence of alphanumeric characters
	//   and underscores. So for example a version string of "1.0.4" needs to
	//   be converted to "1_0_4" or uuid of 1111-2222-3333-4444 needs to be
	//   converted to 1111_2222_3333_4444.
	table := `CREATE TABLE IF NOT EXISTS kites (
		kite ltree NOT NULL,
		url text NOT NULL,
		id uuid PRIMARY KEY,
		created_at timestamptz NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
		updated_at timestamptz NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC')
	);`

	if _, err := db.Exec(table); err != nil {
		panic(err)
	}

	// We enable index on the kite field. We don't return on errors because the
	// operator `IF NOT EXISTS` doesn't work for index creation, therefore we
	// assume the indexes might be already created.
	enableGistIndex := `CREATE INDEX kite_path_gist_idx ON kites USING GIST(kite)`
	enableBtreeIndex := `CREATE INDEX kite_path_btree_idx ON kites USING BTREE(kite)`

	if _, err := db.Exec(enableGistIndex); err != nil {
		log.Warning("postgres: enable gist index: ", err)
	}

	if _, err := db.Exec(enableBtreeIndex); err != nil {
		log.Warning("postgres: enable btree index: ", err)
	}

	return &Postgres{
		DB: db,
	}
}

func (p *Postgres) Get(query *protocol.KontrolQuery) (Kites, error) {
	// only let query with usernames, otherwise the whole tree will be fetched
	// which is not good for us
	if query.Username == "" {
		return nil, errors.New("username is not specified in query")
	}

	// NewVersion returns an error if it's a constraint, like: ">= 1.0, < 1.4"
	_, err := version.NewVersion(query.Version)
	if err != nil && query.Version != "" {
		return nil, errors.New("version constraint is not implemented")
	}

	path := ltreePath(query)

	stmt, err := p.DB.Prepare(`SELECT kite, url FROM kites WHERE kite <@ $1`)
	if err != nil {
		return nil, err
	}
	defer stmt.Close()

	rows, err := stmt.Query(path)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var kitePath string
	var url string

	kites := make(Kites, 0)

	for rows.Next() {
		err := rows.Scan(&kitePath, &url)
		if err != nil {
			return nil, err
		}

		kiteProt, err := kiteFromPath(kitePath)
		if err != nil {
			return nil, err
		}

		kites = append(kites, &protocol.KiteWithToken{
			Kite: *kiteProt,
			URL:  url,
		})

		fmt.Printf("kitePath %+v\n", kitePath)
		fmt.Printf("url %+v\n", url)
	}

	if err := rows.Err(); err != nil {
		return nil, err
	}

	kites.Shuffle()

	return kites, nil
}

func (p *Postgres) Add(kiteProt *protocol.Kite, value *kontrolprotocol.RegisterValue) error {
	// check that the incoming url is valid to prevent malformed input
	_, err := url.Parse(value.URL)
	if err != nil {
		return err
	}

	_, err = p.DB.Exec("INSERT into kites(kite, url, id) VALUES($1, $2, $3)",
		ltreePath(kiteProt.Query()),
		value.URL,
		kiteProt.ID,
	)
	return err
}

func (p *Postgres) Update(kiteProt *protocol.Kite, value *kontrolprotocol.RegisterValue) error {
	// check that the incoming url is valid to prevent malformed input
	_, err := url.Parse(value.URL)
	if err != nil {
		return err
	}

	// TODO: also consider just usting WHERE id = kiteProt.ID, see how it's
	// perfoms out
	_, err = p.DB.Exec(`UPDATE kites SET url = $1, updated_at = (now() at time zone 'utc')
	WHERE kite ~ $2`,
		value.URL, ltreePath(kiteProt.Query()))

	return err
}

func (p *Postgres) Delete(kite *protocol.Kite) error {
	return errors.New("DELETE is not implemented")
}

// ltreeLabel satisfies a invalid ltree definition of a label in path.
// According to the definition it is: "A label is a sequence of alphanumeric
// characters and underscores (for example, in C locale the characters
// A-Za-z0-9_ are allowed). Labels must be less than 256 bytes long."
//
// We could express one character with "[A-Za-z0-9_]", a word with
// "[A-Za-z0-9_]+". However we want to catch words that are not valid labels so
// we negate them with the "^" character, so it will be : "[^[A-Za-z0-9_]]+".
// Finally we cann use the POSIX character class: [:word:] which is:
// "Alphanumeric characters plus "_"", so the final regexp will be
// "[^[:word]]+"
var invalidLabelRe = regexp.MustCompile("[^[:word:]]+")

// ltreePath returns a query path to be used with the ltree module in postgress
// in the form of "username.environment.kitename.version.region.hostname.id"
func ltreePath(query *protocol.KontrolQuery) string {
	path := ""
	fields := query.Fields()

	// we stop for the first empty value
	for _, key := range keyOrder {
		v := fields[key]
		if v == "" {
			break
		}

		// replace anything that doesn't match the definition for a ltree path
		// label with a underscore, so the version "0.0.1" will be "0_0_1", or
		// uuid of "1111-2222-3333-4444" will be converted to
		// 1111_2222_3333_4444. Strings that satisifies the requirement are
		// untouched.
		v = invalidLabelRe.ReplaceAllLiteralString(v, "_")

		path = path + v + "."
	}

	// remove the latest dot which causes an invalid query
	path = strings.TrimSuffix(path, ".")
	return path
}

// kiteFromPath returns a Query from the given ltree path label
func kiteFromPath(path string) (*protocol.Kite, error) {
	fields := strings.Split(path, ".")

	if len(fields) != 7 {
		return nil, fmt.Errorf("invalid ltree path: %s", path)
	}

	// those labels were converted by us, therefore convert them back
	version := strings.Replace(fields[3], "_", ".", -1)
	id := strings.Replace(fields[6], "_", "-", -1)

	return &protocol.Kite{
		Username:    fields[0],
		Environment: fields[1],
		Name:        fields[2],
		Version:     version,
		Region:      fields[4],
		Hostname:    fields[5],
		ID:          id,
	}, nil

}
<|endoftext|>


<commit_before>package in

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"strconv"

	"terraform-resource/encoder"
	"terraform-resource/logger"
	"terraform-resource/models"
	"terraform-resource/storage"
	"terraform-resource/terraform"
)

type Runner struct {
	OutputDir string
	LogWriter io.Writer
}

type EnvNotFoundError error

var ErrOutputModule error = errors.New("the `output_module` feature was removed in Terraform 0.12.0, you must now explicitly declare all outputs in the root module")

func (r Runner) Run(req models.InRequest) (models.InResponse, error) {
	if err := req.Version.Validate(); err != nil {
		return models.InResponse{}, fmt.Errorf("Invalid Version request: %s", err)
	}

	envName := req.Version.EnvName
	nameFilepath := path.Join(r.OutputDir, "name")
	if err := ioutil.WriteFile(nameFilepath, []byte(envName), 0644); err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to create name file at path '%s': %s", nameFilepath, err)
	}

	if req.Params.Action == models.DestroyAction {
		resp := models.InResponse{
			Version: req.Version,
		}
		return resp, nil
	}

	if err := req.Source.Validate(); err != nil {
		return models.InResponse{}, err
	}

	tmpDir, err := ioutil.TempDir(os.TempDir(), "terraform-resource-in")
	if err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to create tmp dir at '%s'", os.TempDir())
	}
	defer os.RemoveAll(tmpDir)

	var resp models.InResponse
	if req.Source.BackendType != "" && req.Source.MigratedFromStorage != (storage.Model{}) {
		resp, err = r.inWithMigratedFromStorage(req, tmpDir)
	} else if req.Source.BackendType != "" {
		resp, err = r.inWithBackend(req, tmpDir)
	} else {
		resp, err = r.inWithLegacyStorage(req, tmpDir)
	}
	if err != nil {
		return models.InResponse{}, err
	}

	if err = r.writeNameToFile(req.Version.EnvName); err != nil {
		return models.InResponse{}, err
	}

	return resp, nil
}

func (r Runner) inWithMigratedFromStorage(req models.InRequest, tmpDir string) (models.InResponse, error) {
	resp, err := r.inWithBackend(req, tmpDir)
	if err == nil {
		return resp, nil
	}

	if _, ok := err.(EnvNotFoundError); ok {
		req.Source.Storage = req.Source.MigratedFromStorage
		return r.inWithLegacyStorage(req, tmpDir)
	}

	return models.InResponse{}, err
}

func (r Runner) inWithBackend(req models.InRequest, tmpDir string) (models.InResponse, error) {
	terraformModel := req.Source.Terraform
	if err := terraformModel.Validate(); err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to validate terraform Model: %s", err)
	}
	terraformModel.Source = "."
	if req.Params.OutputModule != "" {
		return models.InResponse{}, ErrOutputModule
	}

	targetEnvName := req.Version.EnvName

	client := terraform.NewClient(
		terraformModel,
		r.LogWriter,
	)

	if err := client.InitWithBackend(); err != nil {
		return models.InResponse{}, err
	}

	if err := r.ensureEnvExistsInBackend(targetEnvName, client); err != nil {
		return models.InResponse{}, err
	}

	tfOutput, err := client.Output(targetEnvName)
	if err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to parse terraform output.\nError: %s", err)
	}
	result := terraform.Result{
		Output: tfOutput,
	}

	if err = r.writeRawOutputToFile(result); err != nil {
		return models.InResponse{}, err
	}

	if req.Params.OutputStatefile {
		if err = r.writeBackendStateToFile(targetEnvName, client); err != nil {
			return models.InResponse{}, err
		}
	}
	if req.Params.OutputJSONPlanfile {
		if err = r.writeJSONPlanToFile(targetEnvName+"-plan", client); err != nil {
			return models.InResponse{}, err
		}
	}

	stateVersion, err := client.CurrentStateVersion(targetEnvName)
	if err != nil {
		return models.InResponse{}, err
	}

	metadata, err := r.sanitizedOutput(result, client)
	if err != nil {
		return models.InResponse{}, err
	}

	resp := models.InResponse{
		Version: models.Version{
			EnvName: targetEnvName,
			Serial:  strconv.Itoa(stateVersion.Serial),
			Lineage: stateVersion.Lineage,
		},
		Metadata: metadata,
	}
	return resp, nil
}

func (r Runner) ensureEnvExistsInBackend(envName string, client terraform.Client) error {
	spaces, err := client.WorkspaceList()
	if err != nil {
		return err
	}
	foundEnv := false
	for _, space := range spaces {
		if space == envName {
			foundEnv = true
		}
	}
	if !foundEnv {
		return EnvNotFoundError(fmt.Errorf(
			"Workspace '%s' does not exist in backend."+
				"\nIf you intended to run the `destroy` action, add `put.get_params.action: destroy`."+
				"\nThis is a temporary requirement until Concourse supports a `delete` step.",
			envName,
		))
	}

	return nil
}

func (r Runner) writeNameToFile(envName string) error {
	nameFilepath := path.Join(r.OutputDir, "name")
	return ioutil.WriteFile(nameFilepath, []byte(envName), 0644)
}

func (r Runner) writeRawOutputToFile(result terraform.Result) error {
	outputFilepath := path.Join(r.OutputDir, "metadata")
	outputFile, err := os.Create(outputFilepath)
	if err != nil {
		return fmt.Errorf("Failed to create output file at path '%s': %s", outputFilepath, err)
	}

	if err = encoder.NewJSONEncoder(outputFile).Encode(result.RawOutput()); err != nil {
		return fmt.Errorf("Failed to write output file: %s", err)
	}

	return nil
}

func (r Runner) writeBackendStateToFile(envName string, client terraform.Client) error {
	stateFilePath := path.Join(r.OutputDir, "terraform.tfstate")
	stateContents, err := client.StatePull(envName)
	if err != nil {
		return err
	}
	return ioutil.WriteFile(stateFilePath, stateContents, 0777)
}

func (r Runner) writeJSONPlanToFile(envName string, client terraform.Client) error {

	stateContents, err := client.StatePull(envName)
	if err != nil {
		return err
	}

	tfOutput := models.TfState{}
	if err = json.Unmarshal(stateContents, &tfOutput); err != nil {
		return fmt.Errorf("Failed to unmarshal JSON output.\nError: %s\nOutput: %s", err, stateContents)
	}

	planFilePath := path.Join(r.OutputDir, "plan.json")

	var encodedPlan string
	if val, ok := tfOutput.Outputs[models.PlanContentJSON]; ok {
		encodedPlan = val["value"].(string)
	} else {
		return fmt.Errorf("state has no output for key %s", models.PlanContentJSON)
	}
	decodedPlan, err := base64.StdEncoding.DecodeString(encodedPlan)
	if err != nil {
		return err
	}

	return ioutil.WriteFile(planFilePath, decodedPlan, 0644)
}

func (r Runner) writeLegacyStateToFile(localStatefilePath string) error {
	stateFilePath := path.Join(r.OutputDir, "terraform.tfstate")
	stateContents, err := ioutil.ReadFile(localStatefilePath)
	if err != nil {
		return err
	}
	return ioutil.WriteFile(stateFilePath, stateContents, 0777)
}

func (r Runner) sanitizedOutput(result terraform.Result, client terraform.Client) ([]models.MetadataField, error) {
	metadata := []models.MetadataField{}
	for key, value := range result.SanitizedOutput() {
		metadata = append(metadata, models.MetadataField{
			Name:  key,
			Value: value,
		})
	}

	tfVersion, err := client.Version()
	if err != nil {
		return nil, err
	}
	return append(metadata, models.MetadataField{
		Name:  "terraform_version",
		Value: tfVersion,
	}), nil
}

func (r Runner) inWithLegacyStorage(req models.InRequest, tmpDir string) (models.InResponse, error) {
	logger := logger.Logger{
		Sink: r.LogWriter,
	}
	logger.Warn(fmt.Sprintf("%s\n", storage.DeprecationWarning))

	if req.Version.IsPlan() {
		resp := models.InResponse{
			Version: req.Version,
		}
		return resp, nil
	}

	stateFile, err := r.stateFileFromLegacyStorage(req, tmpDir)
	if err != nil {
		return models.InResponse{}, err
	}

	terraformModel := models.Terraform{
		StateFileLocalPath:  stateFile.LocalPath,
		StateFileRemotePath: stateFile.RemotePath,
	}

	if req.Params.OutputModule != "" {
		return models.InResponse{}, ErrOutputModule
	}

	if err := terraformModel.Validate(); err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to validate terraform Model: %s", err)
	}

	client := terraform.NewClient(
		terraformModel,
		r.LogWriter,
	)

	storageVersion, err := stateFile.Download()
	if err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to download state file from storage backend: %s", err)
	}
	version := models.NewVersionFromLegacyStorage(storageVersion)

	if err = client.InitWithoutBackend(); err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to initialize terraform.\nError: %s", err)
	}

	tfOutput, err := client.OutputWithLegacyStorage()
	if err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to parse terraform output.\nError: %s", err)
	}
	result := terraform.Result{
		Output: tfOutput,
	}

	if err = r.writeRawOutputToFile(result); err != nil {
		return models.InResponse{}, err
	}

	if req.Params.OutputStatefile {
		if err = r.writeLegacyStateToFile(terraformModel.StateFileLocalPath); err != nil {
			return models.InResponse{}, err
		}
	}

	metadata, err := r.sanitizedOutput(result, client)
	if err != nil {
		return models.InResponse{}, err
	}

	resp := models.InResponse{
		Version:  version,
		Metadata: metadata,
	}
	return resp, nil

}

func (r Runner) stateFileFromLegacyStorage(req models.InRequest, tmpDir string) (storage.StateFile, error) {
	storageModel := req.Source.Storage
	if err := storageModel.Validate(); err != nil {
		return storage.StateFile{}, fmt.Errorf("Failed to validate storage Model: %s", err)
	}
	storageDriver := storage.BuildDriver(storageModel)

	stateFile := storage.StateFile{
		LocalPath:     path.Join(tmpDir, "terraform.tfstate"),
		RemotePath:    fmt.Sprintf("%s.tfstate", req.Version.EnvName),
		StorageDriver: storageDriver,
	}

	existsAsTainted, err := stateFile.ExistsAsTainted()
	if err != nil {
		return storage.StateFile{}, fmt.Errorf("Failed to check for tainted state file: %s", err)
	}
	if existsAsTainted {
		stateFile = stateFile.ConvertToTainted()
	}

	exists, err := stateFile.Exists()
	if err != nil {
		return storage.StateFile{}, fmt.Errorf("Failed to check for existing state file: %s", err)
	}
	if !exists {
		return storage.StateFile{}, EnvNotFoundError(fmt.Errorf(
			"State file does not exist with key '%s'."+
				"\nIf you intended to run the `destroy` action, add `put.get_params.action: destroy`."+
				"\nThis is a temporary requirement until Concourse supports a `delete` step.",
			stateFile.RemotePath,
		))
	}
	return stateFile, nil
}
<commit_msg>fixed in_backend_test.go:297 test<commit_after>package in

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"strconv"

	"terraform-resource/encoder"
	"terraform-resource/logger"
	"terraform-resource/models"
	"terraform-resource/storage"
	"terraform-resource/terraform"
)

type Runner struct {
	OutputDir string
	LogWriter io.Writer
}

type EnvNotFoundError error

var ErrOutputModule error = errors.New("the `output_module` feature was removed in Terraform 0.12.0, you must now explicitly declare all outputs in the root module")

func (r Runner) Run(req models.InRequest) (models.InResponse, error) {
	if err := req.Version.Validate(); err != nil {
		return models.InResponse{}, fmt.Errorf("Invalid Version request: %s", err)
	}

	envName := req.Version.EnvName
	nameFilepath := path.Join(r.OutputDir, "name")
	if err := ioutil.WriteFile(nameFilepath, []byte(envName), 0644); err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to create name file at path '%s': %s", nameFilepath, err)
	}

	if req.Params.Action == models.DestroyAction {
		resp := models.InResponse{
			Version: req.Version,
		}
		return resp, nil
	}

	if err := req.Source.Validate(); err != nil {
		return models.InResponse{}, err
	}

	tmpDir, err := ioutil.TempDir(os.TempDir(), "terraform-resource-in")
	if err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to create tmp dir at '%s'", os.TempDir())
	}
	defer os.RemoveAll(tmpDir)

	var resp models.InResponse
	if req.Source.BackendType != "" && req.Source.MigratedFromStorage != (storage.Model{}) {
		resp, err = r.inWithMigratedFromStorage(req, tmpDir)
	} else if req.Source.BackendType != "" {
		resp, err = r.inWithBackend(req, tmpDir)
	} else {
		resp, err = r.inWithLegacyStorage(req, tmpDir)
	}
	if err != nil {
		return models.InResponse{}, err
	}

	if err = r.writeNameToFile(req.Version.EnvName); err != nil {
		return models.InResponse{}, err
	}

	return resp, nil
}

func (r Runner) inWithMigratedFromStorage(req models.InRequest, tmpDir string) (models.InResponse, error) {
	resp, err := r.inWithBackend(req, tmpDir)
	if err == nil {
		return resp, nil
	}

	if _, ok := err.(EnvNotFoundError); ok {
		req.Source.Storage = req.Source.MigratedFromStorage
		return r.inWithLegacyStorage(req, tmpDir)
	}

	return models.InResponse{}, err
}

func (r Runner) inWithBackend(req models.InRequest, tmpDir string) (models.InResponse, error) {
	if req.Version.IsPlan() && req.Params.OutputJSONPlanfile == false {
		resp := models.InResponse{
			Version: req.Version,
		}
		return resp, nil
	}

	terraformModel := req.Source.Terraform
	if err := terraformModel.Validate(); err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to validate terraform Model: %s", err)
	}
	terraformModel.Source = "."
	if req.Params.OutputModule != "" {
		return models.InResponse{}, ErrOutputModule
	}

	targetEnvName := req.Version.EnvName

	client := terraform.NewClient(
		terraformModel,
		r.LogWriter,
	)

	if err := client.InitWithBackend(); err != nil {
		return models.InResponse{}, err
	}

	if err := r.ensureEnvExistsInBackend(targetEnvName, client); err != nil {
		return models.InResponse{}, err
	}

	tfOutput, err := client.Output(targetEnvName)
	if err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to parse terraform output.\nError: %s", err)
	}
	result := terraform.Result{
		Output: tfOutput,
	}

	if err = r.writeRawOutputToFile(result); err != nil {
		return models.InResponse{}, err
	}

	if req.Params.OutputStatefile {
		if err = r.writeBackendStateToFile(targetEnvName, client); err != nil {
			return models.InResponse{}, err
		}
	}
	stateVersion, err := client.CurrentStateVersion(targetEnvName)
	if err != nil {
		return models.InResponse{}, err
	}

	metadata, err := r.sanitizedOutput(result, client)
	if err != nil {
		return models.InResponse{}, err
	}

	if req.Params.OutputJSONPlanfile {
		if err = r.writeJSONPlanToFile(targetEnvName+"-plan", client); err != nil {
			return models.InResponse{}, err
		}
		metadata = nil
	}

	resp := models.InResponse{
		Version: models.Version{
			EnvName: targetEnvName,
			Serial:  strconv.Itoa(stateVersion.Serial),
			Lineage: stateVersion.Lineage,
		},
		Metadata: metadata,
	}
	return resp, nil
}

func (r Runner) ensureEnvExistsInBackend(envName string, client terraform.Client) error {
	spaces, err := client.WorkspaceList()
	if err != nil {
		return err
	}
	foundEnv := false
	for _, space := range spaces {
		if space == envName {
			foundEnv = true
		}
	}
	if !foundEnv {
		return EnvNotFoundError(fmt.Errorf(
			"Workspace '%s' does not exist in backend."+
				"\nIf you intended to run the `destroy` action, add `put.get_params.action: destroy`."+
				"\nThis is a temporary requirement until Concourse supports a `delete` step.",
			envName,
		))
	}

	return nil
}

func (r Runner) writeNameToFile(envName string) error {
	nameFilepath := path.Join(r.OutputDir, "name")
	return ioutil.WriteFile(nameFilepath, []byte(envName), 0644)
}

func (r Runner) writeRawOutputToFile(result terraform.Result) error {
	outputFilepath := path.Join(r.OutputDir, "metadata")
	outputFile, err := os.Create(outputFilepath)
	if err != nil {
		return fmt.Errorf("Failed to create output file at path '%s': %s", outputFilepath, err)
	}

	if err = encoder.NewJSONEncoder(outputFile).Encode(result.RawOutput()); err != nil {
		return fmt.Errorf("Failed to write output file: %s", err)
	}

	return nil
}

func (r Runner) writeBackendStateToFile(envName string, client terraform.Client) error {
	stateFilePath := path.Join(r.OutputDir, "terraform.tfstate")
	stateContents, err := client.StatePull(envName)
	if err != nil {
		return err
	}
	return ioutil.WriteFile(stateFilePath, stateContents, 0777)
}

func (r Runner) writeJSONPlanToFile(envName string, client terraform.Client) error {

	stateContents, err := client.StatePull(envName)
	if err != nil {
		return err
	}

	tfOutput := models.TfState{}
	if err = json.Unmarshal(stateContents, &tfOutput); err != nil {
		return fmt.Errorf("Failed to unmarshal JSON output.\nError: %s\nOutput: %s", err, stateContents)
	}

	planFilePath := path.Join(r.OutputDir, "plan.json")

	var encodedPlan string
	if val, ok := tfOutput.Outputs[models.PlanContentJSON]; ok {
		encodedPlan = val["value"].(string)
	} else {
		return fmt.Errorf("state has no output for key %s", models.PlanContentJSON)
	}
	decodedPlan, err := base64.StdEncoding.DecodeString(encodedPlan)
	if err != nil {
		return err
	}

	return ioutil.WriteFile(planFilePath, decodedPlan, 0644)
}

func (r Runner) writeLegacyStateToFile(localStatefilePath string) error {
	stateFilePath := path.Join(r.OutputDir, "terraform.tfstate")
	stateContents, err := ioutil.ReadFile(localStatefilePath)
	if err != nil {
		return err
	}
	return ioutil.WriteFile(stateFilePath, stateContents, 0777)
}

func (r Runner) sanitizedOutput(result terraform.Result, client terraform.Client) ([]models.MetadataField, error) {
	metadata := []models.MetadataField{}
	for key, value := range result.SanitizedOutput() {
		metadata = append(metadata, models.MetadataField{
			Name:  key,
			Value: value,
		})
	}

	tfVersion, err := client.Version()
	if err != nil {
		return nil, err
	}
	return append(metadata, models.MetadataField{
		Name:  "terraform_version",
		Value: tfVersion,
	}), nil
}

func (r Runner) inWithLegacyStorage(req models.InRequest, tmpDir string) (models.InResponse, error) {
	logger := logger.Logger{
		Sink: r.LogWriter,
	}
	logger.Warn(fmt.Sprintf("%s\n", storage.DeprecationWarning))

	if req.Version.IsPlan() {
		resp := models.InResponse{
			Version: req.Version,
		}
		return resp, nil
	}

	stateFile, err := r.stateFileFromLegacyStorage(req, tmpDir)
	if err != nil {
		return models.InResponse{}, err
	}

	terraformModel := models.Terraform{
		StateFileLocalPath:  stateFile.LocalPath,
		StateFileRemotePath: stateFile.RemotePath,
	}

	if req.Params.OutputModule != "" {
		return models.InResponse{}, ErrOutputModule
	}

	if err := terraformModel.Validate(); err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to validate terraform Model: %s", err)
	}

	client := terraform.NewClient(
		terraformModel,
		r.LogWriter,
	)

	storageVersion, err := stateFile.Download()
	if err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to download state file from storage backend: %s", err)
	}
	version := models.NewVersionFromLegacyStorage(storageVersion)

	if err = client.InitWithoutBackend(); err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to initialize terraform.\nError: %s", err)
	}

	tfOutput, err := client.OutputWithLegacyStorage()
	if err != nil {
		return models.InResponse{}, fmt.Errorf("Failed to parse terraform output.\nError: %s", err)
	}
	result := terraform.Result{
		Output: tfOutput,
	}

	if err = r.writeRawOutputToFile(result); err != nil {
		return models.InResponse{}, err
	}

	if req.Params.OutputStatefile {
		if err = r.writeLegacyStateToFile(terraformModel.StateFileLocalPath); err != nil {
			return models.InResponse{}, err
		}
	}

	metadata, err := r.sanitizedOutput(result, client)
	if err != nil {
		return models.InResponse{}, err
	}

	resp := models.InResponse{
		Version:  version,
		Metadata: metadata,
	}
	return resp, nil

}

func (r Runner) stateFileFromLegacyStorage(req models.InRequest, tmpDir string) (storage.StateFile, error) {
	storageModel := req.Source.Storage
	if err := storageModel.Validate(); err != nil {
		return storage.StateFile{}, fmt.Errorf("Failed to validate storage Model: %s", err)
	}
	storageDriver := storage.BuildDriver(storageModel)

	stateFile := storage.StateFile{
		LocalPath:     path.Join(tmpDir, "terraform.tfstate"),
		RemotePath:    fmt.Sprintf("%s.tfstate", req.Version.EnvName),
		StorageDriver: storageDriver,
	}

	existsAsTainted, err := stateFile.ExistsAsTainted()
	if err != nil {
		return storage.StateFile{}, fmt.Errorf("Failed to check for tainted state file: %s", err)
	}
	if existsAsTainted {
		stateFile = stateFile.ConvertToTainted()
	}

	exists, err := stateFile.Exists()
	if err != nil {
		return storage.StateFile{}, fmt.Errorf("Failed to check for existing state file: %s", err)
	}
	if !exists {
		return storage.StateFile{}, EnvNotFoundError(fmt.Errorf(
			"State file does not exist with key '%s'."+
				"\nIf you intended to run the `destroy` action, add `put.get_params.action: destroy`."+
				"\nThis is a temporary requirement until Concourse supports a `delete` step.",
			stateFile.RemotePath,
		))
	}
	return stateFile, nil
}
<|endoftext|>


<commit_before>package registry

import "testing"

func TestNewMongoStore(t *testing.T) {
    var MONGODB_URL string = "127.0.0.1"
    var MONGODB_DB string = "binder_registery_tests"
    var MONGODB_COL string = "templates"
    store := NewMongoStore(MONGODB_URL, MONGODB_DB, MONGODB_COL)

    if (store.err != nil) {
        t.Error("NewMongoStore raised error:", store.err);
    }

    if (store.connection.FullName != (MONGODB_DB + "." + MONGODB_COL)) {
       t.Error("Connected to wrong database:", store.connection.FullName)
    }
}

func TestMongoRegisterTemplate(t *testing.T) {
    var MONGODB_URL string = "127.0.0.1"
    var MONGODB_DB string = "binder_registery_tests"
    var MONGODB_COL string = "templates"
    store := NewMongoStore(MONGODB_URL, MONGODB_DB, MONGODB_COL)

    tmpl := Template{
        Name: "MongoTest",
        ImageName: "jupyter/mongo_test",
    }

    registered, err := store.RegisterTemplate(tmpl)

    if (err != nil) {
        t.Error("Error when registering template: ", err)
    }

    if (registered == Template{}) {
        t.Error("Template was not registered properly")
    }

    equals(t, registered.Name, tmpl.Name)
    equals(t, registered.ImageName, tmpl.ImageName)
}

func TestMongoGetTemplate(t * testing.T) {
    var MONGODB_URL string = "127.0.0.1"
    var MONGODB_DB string = "binder_registery_tests"
    var MONGODB_COL string = "templates"
    store := NewMongoStore(MONGODB_URL, MONGODB_DB, MONGODB_COL)

    name := "MongoTest"
    tmpl, err := store.GetTemplate(name)

    if (err != nil) {
        t.Error("Error when getting template: ", err)
    }

    equals(t, tmpl.Name, name)
}
<commit_msg>Added tests for ListTemplates in Mongo<commit_after>package registry

import "testing"

func TestNewMongoStore(t *testing.T) {
    var MONGODB_URL string = "127.0.0.1"
    var MONGODB_DB string = "binder_registery_tests"
    var MONGODB_COL string = "templates"
    store := NewMongoStore(MONGODB_URL, MONGODB_DB, MONGODB_COL)

    if (store.err != nil) {
        t.Error("NewMongoStore raised error:", store.err);
    }

    if (store.connection.FullName != (MONGODB_DB + "." + MONGODB_COL)) {
       t.Error("Connected to wrong database:", store.connection.FullName)
    }
}

func TestMongoRegisterTemplate(t *testing.T) {
    var MONGODB_URL string = "127.0.0.1"
    var MONGODB_DB string = "binder_registery_tests"
    var MONGODB_COL string = "templates"
    store := NewMongoStore(MONGODB_URL, MONGODB_DB, MONGODB_COL)

    tmpl := Template{
        Name: "MongoTest",
        ImageName: "jupyter/mongo_test",
    }

    registered, err := store.RegisterTemplate(tmpl)

    if (err != nil) {
        t.Error("Error when registering template: ", err)
    }

    if (registered == Template{}) {
        t.Error("Template was not registered properly")
    }

    equals(t, registered.Name, tmpl.Name)
    equals(t, registered.ImageName, tmpl.ImageName)
}

func TestMongoGetTemplate(t * testing.T) {
    var MONGODB_URL string = "127.0.0.1"
    var MONGODB_DB string = "binder_registery_tests"
    var MONGODB_COL string = "templates"
    store := NewMongoStore(MONGODB_URL, MONGODB_DB, MONGODB_COL)

    name := "MongoTest"
    tmpl, err := store.GetTemplate(name)

    if (err != nil) {
        t.Error("Error when getting template: ", err)
    }

    equals(t, tmpl.Name, name)
}

func TestMongoListTemplates(t *testing.T) {
    var MONGODB_URL string = "127.0.0.1"
    var MONGODB_DB string = "binder_registery_tests"
    var MONGODB_COL string = "templates"
    store := NewMongoStore(MONGODB_URL, MONGODB_DB, MONGODB_COL)

    results, err := store.ListTemplates()

    if (err != nil) {
        t.Error("Error when listing templates: ", err)
    }

    if (len(results) == 0) {
        t.Error("ListTemplates did not find any tempaltes!")
    }
}
<|endoftext|>


<commit_before>package monitor

import (
	"encoding/json"
	"errors"
	"fmt"
	"path"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/9corp/9volt/alerter"
	"github.com/9corp/9volt/config"
	"github.com/9corp/9volt/dal"
	"github.com/9corp/9volt/state"
	"github.com/9corp/9volt/util"
)

const (
	START int = iota
	STOP

	GOROUTINE_ID_LENGTH = 8
	MAX_PORT            = 65536
)

type IMonitor interface {
	Run() error
	Stop()
	Identify() string
	Validate() error
}

type Monitor struct {
	Config             *config.Config
	Identifier         string
	runningMonitorLock *sync.Mutex
	runningMonitors    map[string]IMonitor
	MessageChannel     chan *alerter.Message
	StateChannel       chan *state.Message
	SupportedMonitors  map[string]func(*RootMonitorConfig) IMonitor // monitor name : NewXMonitor
	MemberID           string
}

type RootMonitorConfig struct {
	GID            string // goroutine id
	Name           string // monitor config name in member dir
	ConfigName     string // monitor config name in monitor dir
	MemberID       string
	Config         *MonitorConfig
	MessageChannel chan *alerter.Message
	StateChannel   chan *state.Message
	StopChannel    chan bool
	Ticker         *time.Ticker
}

// TODO: This should probably be split up between each individual check type
type MonitorConfig struct {
	// Generic attributes that fit more than one monitor type
	Type        string              `json:"type"`                  // 'tcp', 'http', 'ssh', 'exec', 'icmp', 'dns'
	Description string              `json:"description,omitempty"` // optional
	Host        string              `json:"host,omitempty"`        // required for all checks except 'exec'
	Interval    util.CustomDuration `json:"interval,omitempty"`
	Timeout     util.CustomDuration `json:"timeout,omitempty"`
	Port        int                 `json:"port,omitempty"`   // works for all checks except 'icmp' and 'exec'
	Expect      string              `json:"expect,omitempty"` // works for 'tcp', 'ssh', 'http', 'exec' checks except 'icmp'
	Disable     bool                `json:"disable,omitempty"`
	Tags        []string            `json:"tags,omitempty"`
	MemberTag   string              `json:"member-tag,omitempty"` // lock a check to specific member(s)

	// TCP specific attributes
	TCPSend         string              `json:"send,omitempty"`
	TCPReadTimeout  util.CustomDuration `json:"read-timeout,omitempty"`
	TCPWriteTimeout util.CustomDuration `json:"write-timeout,omitempty"`
	TCPReadSize     int                 `json:"read-size,omitempty"`

	// HTTP specific attributes
	HTTPURL         string `json:"url,omitempty"`
	HTTPMethod      string `json:"method,omitempty"`
	HTTPSSL         bool   `json:"ssl,omitempty"`
	HTTPStatusCode  int    `json:"status-code,omitempty"`
	HTTPRequestBody string `json:"request-body,omitempty"` // Only used if 'Method' is 'GET'

	// Exec specific attributes
	ExecCommand    string   `json:"command,omitempty"`
	ExecArgs       []string `json:"args,omitempty"`
	ExecReturnCode int      `json:"return-code,omitempty"`

	// DNS specific attributes
	DnsTarget        string              `json:"dns-target, omitempty"`
	DnsRecordType    string              `json:"dns-record-type, omitempty"`
	DnsMaxTime       util.CustomDuration `json:"dns-max-time, omitempty"`
	DnsExpectedCount int                 `json:"dns-expected-count, omitempty"`

	// Alerting related configuration
	WarningThreshold  int      `json:"warning-threshold,omitempty"`  // how many times a check must fail before a warning alert is emitted
	CriticalThreshold int      `json:"critical-threshold,omitempty"` // how many times a check must fail before a critical alert is emitted
	WarningAlerter    []string `json:"warning-alerter,omitempty"`    // these alerters will be contacted when a warning threshold is hit
	CriticalAlerter   []string `json:"critical-alerter,omitempty"`   // these alerters will be contacted when a critical threshold is hit
}

type Response struct{}

func New(cfg *config.Config, messageChannel chan *alerter.Message, stateChannel chan *state.Message) *Monitor {
	return &Monitor{
		Identifier:     "monitor",
		Config:         cfg,
		MessageChannel: messageChannel,
		StateChannel:   stateChannel,
		MemberID:       cfg.MemberID,
		SupportedMonitors: map[string]func(*RootMonitorConfig) IMonitor{
			"dns":  func(cfg *RootMonitorConfig) IMonitor { return NewDnsMonitor(cfg) },
			"exec": func(cfg *RootMonitorConfig) IMonitor { return NewExecMonitor(cfg) },
			"http": func(cfg *RootMonitorConfig) IMonitor { return NewHTTPMonitor(cfg) },
			"tcp":  func(cfg *RootMonitorConfig) IMonitor { return NewTCPMonitor(cfg) },
		},
		runningMonitors:    make(map[string]IMonitor, 0),
		runningMonitorLock: &sync.Mutex{},
	}
}

// Start/stop or restart a monitor with a specific config
func (m *Monitor) Handle(action int, monitorName, monitorConfigLocation string) error {
	// if stop action, check if we have a running instance of the check, if not, return an error
	if action == STOP {
		if m.monitorRunning(monitorName) {
			log.Debugf("%v: Stopping running monitor '%v'...", m.Identifier, monitorName)
			return m.stop(monitorName)
		}

		log.Errorf("%v: Asked to stop monitor '%v' but monitor is not running!", m.Identifier, monitorName)
		return fmt.Errorf("Asked to stop monitor %v but monitor is not running", monitorName)
	}

	// fetch fresh configuration from etcd
	monitorConfig, err := m.fetchMonitorConfig(monitorConfigLocation)
	if err != nil {
		m.Config.EQClient.AddWithErrorLog("error",
			fmt.Sprintf("%v: Unable to fetch monitor configuration for '%v' (%v): %v",
				m.Identifier, monitorName, monitorConfigLocation, err.Error()))
		return err
	}

	// validate monitor configuration
	if err := m.validateMonitorConfig(monitorConfig); err != nil {
		m.Config.EQClient.AddWithErrorLog("error",
			fmt.Sprintf("%v: Unable to validate monitor config for '%v' (%v): %v",
				m.Identifier, monitorName, monitorConfigLocation, err.Error()))
		return fmt.Errorf("Unable to validate monitor configuration for %v: %v", monitorName, err.Error())
	}

	// if check already running, stop it
	if m.monitorRunning(monitorName) {
		log.Debugf("%v: Monitor '%v' already running. Stopping it first...", m.Identifier, monitorName)

		if err := m.stop(monitorName); err != nil {
			m.Config.EQClient.AddWithErrorLog("error",
				fmt.Sprintf("%v: Unable to stop running monitor '%v': %v", m.Identifier, monitorName, err.Error()))
			return fmt.Errorf("Unable to stop running monitor %v: %v", monitorName, err.Error())
		}
	}

	// If check is disabled, do not start it back up
	if monitorConfig.Disable {
		log.Debugf("%v: '%v' is disabled. No further action will be taken.", m.Identifier, monitorName)
		return nil
	}

	// start check with new monitor configuration
	log.Debugf("%v: Starting new monitor for %v...", m.Identifier, monitorName)
	if err := m.start(monitorName, monitorConfigLocation, monitorConfig); err != nil {
		m.Config.EQClient.AddWithErrorLog("error",
			fmt.Sprintf("%v: Unable to start new monitor '%v': %v", m.Identifier, monitorName, err.Error()))
		return fmt.Errorf("Unable to start new monitor %v: %v", monitorName, err.Error())
	}

	log.Debugf("%v: Successfully started new monitor %v!", m.Identifier, monitorName)

	return nil
}

// Perform the actual stop of a given monitor; update running monitor slice
func (m *Monitor) stop(monitorName string) error {
	// Stop the given monitor
	m.runningMonitorLock.Lock()
	defer m.runningMonitorLock.Unlock()

	// Double check
	if _, ok := m.runningMonitors[monitorName]; !ok {
		return fmt.Errorf("%v: Unable to stop monitor '%v' - this monitor is NOT running!", m.Identifier, monitorName)
	}

	// Stop the actual check
	m.runningMonitors[monitorName].Stop()

	// Remove it from runningMonitors
	delete(m.runningMonitors, monitorName)

	return nil
}

// Perform the actual start of a monitor; update running monitor slice
func (m *Monitor) start(monitorName, monitorConfigLocation string, monitorConfig *MonitorConfig) error {
	// Let's be overly safe and ensure this monitor type exists
	if _, ok := m.SupportedMonitors[monitorConfig.Type]; !ok {
		return fmt.Errorf("%v: No such monitor type found '%v'", m.Identifier, monitorConfig.Type)
	}

	// Create a new monitor instance
	newMonitor := m.SupportedMonitors[monitorConfig.Type](
		&RootMonitorConfig{
			Name:           monitorName,
			ConfigName:     path.Base(monitorConfigLocation),
			GID:            util.RandomString(GOROUTINE_ID_LENGTH, false),
			Config:         monitorConfig,
			MemberID:       m.MemberID,
			MessageChannel: m.MessageChannel,
			StateChannel:   m.StateChannel,
			StopChannel:    make(chan bool, 1),
			Ticker:         time.NewTicker(time.Duration(monitorConfig.Interval)),
		},
	)

	// Do check-specific validation
	if err := newMonitor.Validate(); err != nil {
		return fmt.Errorf("%v: '%v' failed '%v' monitor config validation: %v",
			m.Identifier, path.Base(monitorConfigLocation), monitorConfig.Type, err.Error())
	}

	m.runningMonitorLock.Lock()
	defer m.runningMonitorLock.Unlock()

	// Add monitor to runningMonitors
	m.runningMonitors[monitorName] = newMonitor

	// Launch the monitor
	go m.runningMonitors[monitorName].Run()

	return nil
}

// Determine if given `monitorName` is in `runningMonitors`
func (m *Monitor) monitorRunning(monitorName string) bool {
	m.runningMonitorLock.Lock()
	defer m.runningMonitorLock.Unlock()

	for k := range m.runningMonitors {
		if k == monitorName {
			return true
		}
	}

	return false
}

// Top level mnitor config validation
func (m *Monitor) validateMonitorConfig(monitorConfig *MonitorConfig) error {
	if monitorConfig.Interval.String() == "0s" {
		return errors.New("'interval' must be > 0s")
	}

	if monitorConfig.CriticalThreshold == 0 {
		return errors.New("'critical-threshold' must be non-zero")
	}

	// TODO: Logic for this should be changed/fixed at some point
	if monitorConfig.WarningThreshold > monitorConfig.CriticalThreshold {
		return errors.New("'warning-threshold' cannot be larger than 'CriticalThreshold'")
	}

	// TODO: It should be possible to NOT have a WarningAlerter setting (and just
	// have a `CriticalAlerter` setting)
	if len(monitorConfig.WarningAlerter) == 0 {
		return errors.New("'warning-alerter' list must contain at least one entry")
	}

	if len(monitorConfig.CriticalAlerter) == 0 {
		return errors.New("'critical-alerter' list must contain at least one entry")
	}

	if monitorConfig.Port > MAX_PORT {
		return fmt.Errorf("'port' must be between 0 and %v", MAX_PORT)
	}

	return nil
}

// Wrapper for fetching (and unmarshaling) MonitorConfig by etcd location
func (m *Monitor) fetchMonitorConfig(monitorConfigLocation string) (*MonitorConfig, error) {
	monitorConfigData, err := m.Config.DalClient.Get(monitorConfigLocation, &dal.GetOptions{
		NoPrefix: true,
	})
	if err != nil {
		return nil, fmt.Errorf("Unable to fetch monitor configuration for '%v': %v", monitorConfigLocation, err.Error())
	}

	if _, ok := monitorConfigData[monitorConfigLocation]; !ok {
		return nil, errors.New("Returned monitor config data missing... bug?")
	}

	var monitorConfig *MonitorConfig

	if err := json.Unmarshal([]byte(monitorConfigData[monitorConfigLocation]), &monitorConfig); err != nil {
		return nil, fmt.Errorf("Unable to unmarshal fetched monitorConfig for '%v': %v", monitorConfigLocation, err.Error())
	}

	return monitorConfig, nil
}
<commit_msg>omitempty's had a space<commit_after>package monitor

import (
	"encoding/json"
	"errors"
	"fmt"
	"path"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/9corp/9volt/alerter"
	"github.com/9corp/9volt/config"
	"github.com/9corp/9volt/dal"
	"github.com/9corp/9volt/state"
	"github.com/9corp/9volt/util"
)

const (
	START int = iota
	STOP

	GOROUTINE_ID_LENGTH = 8
	MAX_PORT            = 65536
)

type IMonitor interface {
	Run() error
	Stop()
	Identify() string
	Validate() error
}

type Monitor struct {
	Config             *config.Config
	Identifier         string
	runningMonitorLock *sync.Mutex
	runningMonitors    map[string]IMonitor
	MessageChannel     chan *alerter.Message
	StateChannel       chan *state.Message
	SupportedMonitors  map[string]func(*RootMonitorConfig) IMonitor // monitor name : NewXMonitor
	MemberID           string
}

type RootMonitorConfig struct {
	GID            string // goroutine id
	Name           string // monitor config name in member dir
	ConfigName     string // monitor config name in monitor dir
	MemberID       string
	Config         *MonitorConfig
	MessageChannel chan *alerter.Message
	StateChannel   chan *state.Message
	StopChannel    chan bool
	Ticker         *time.Ticker
}

// TODO: This should probably be split up between each individual check type
type MonitorConfig struct {
	// Generic attributes that fit more than one monitor type
	Type        string              `json:"type"`                  // 'tcp', 'http', 'ssh', 'exec', 'icmp', 'dns'
	Description string              `json:"description,omitempty"` // optional
	Host        string              `json:"host,omitempty"`        // required for all checks except 'exec'
	Interval    util.CustomDuration `json:"interval,omitempty"`
	Timeout     util.CustomDuration `json:"timeout,omitempty"`
	Port        int                 `json:"port,omitempty"`   // works for all checks except 'icmp' and 'exec'
	Expect      string              `json:"expect,omitempty"` // works for 'tcp', 'ssh', 'http', 'exec' checks except 'icmp'
	Disable     bool                `json:"disable,omitempty"`
	Tags        []string            `json:"tags,omitempty"`
	MemberTag   string              `json:"member-tag,omitempty"` // lock a check to specific member(s)

	// TCP specific attributes
	TCPSend         string              `json:"send,omitempty"`
	TCPReadTimeout  util.CustomDuration `json:"read-timeout,omitempty"`
	TCPWriteTimeout util.CustomDuration `json:"write-timeout,omitempty"`
	TCPReadSize     int                 `json:"read-size,omitempty"`

	// HTTP specific attributes
	HTTPURL         string `json:"url,omitempty"`
	HTTPMethod      string `json:"method,omitempty"`
	HTTPSSL         bool   `json:"ssl,omitempty"`
	HTTPStatusCode  int    `json:"status-code,omitempty"`
	HTTPRequestBody string `json:"request-body,omitempty"` // Only used if 'Method' is 'GET'

	// Exec specific attributes
	ExecCommand    string   `json:"command,omitempty"`
	ExecArgs       []string `json:"args,omitempty"`
	ExecReturnCode int      `json:"return-code,omitempty"`

	// DNS specific attributes
	DnsTarget        string              `json:"dns-target,omitempty"`
	DnsRecordType    string              `json:"dns-record-type,omitempty"`
	DnsMaxTime       util.CustomDuration `json:"dns-max-time,omitempty"`
	DnsExpectedCount int                 `json:"dns-expected-count,omitempty"`

	// Alerting related configuration
	WarningThreshold  int      `json:"warning-threshold,omitempty"`  // how many times a check must fail before a warning alert is emitted
	CriticalThreshold int      `json:"critical-threshold,omitempty"` // how many times a check must fail before a critical alert is emitted
	WarningAlerter    []string `json:"warning-alerter,omitempty"`    // these alerters will be contacted when a warning threshold is hit
	CriticalAlerter   []string `json:"critical-alerter,omitempty"`   // these alerters will be contacted when a critical threshold is hit
}

type Response struct{}

func New(cfg *config.Config, messageChannel chan *alerter.Message, stateChannel chan *state.Message) *Monitor {
	return &Monitor{
		Identifier:     "monitor",
		Config:         cfg,
		MessageChannel: messageChannel,
		StateChannel:   stateChannel,
		MemberID:       cfg.MemberID,
		SupportedMonitors: map[string]func(*RootMonitorConfig) IMonitor{
			"dns":  func(cfg *RootMonitorConfig) IMonitor { return NewDnsMonitor(cfg) },
			"exec": func(cfg *RootMonitorConfig) IMonitor { return NewExecMonitor(cfg) },
			"http": func(cfg *RootMonitorConfig) IMonitor { return NewHTTPMonitor(cfg) },
			"tcp":  func(cfg *RootMonitorConfig) IMonitor { return NewTCPMonitor(cfg) },
		},
		runningMonitors:    make(map[string]IMonitor, 0),
		runningMonitorLock: &sync.Mutex{},
	}
}

// Start/stop or restart a monitor with a specific config
func (m *Monitor) Handle(action int, monitorName, monitorConfigLocation string) error {
	// if stop action, check if we have a running instance of the check, if not, return an error
	if action == STOP {
		if m.monitorRunning(monitorName) {
			log.Debugf("%v: Stopping running monitor '%v'...", m.Identifier, monitorName)
			return m.stop(monitorName)
		}

		log.Errorf("%v: Asked to stop monitor '%v' but monitor is not running!", m.Identifier, monitorName)
		return fmt.Errorf("Asked to stop monitor %v but monitor is not running", monitorName)
	}

	// fetch fresh configuration from etcd
	monitorConfig, err := m.fetchMonitorConfig(monitorConfigLocation)
	if err != nil {
		m.Config.EQClient.AddWithErrorLog("error",
			fmt.Sprintf("%v: Unable to fetch monitor configuration for '%v' (%v): %v",
				m.Identifier, monitorName, monitorConfigLocation, err.Error()))
		return err
	}

	// validate monitor configuration
	if err := m.validateMonitorConfig(monitorConfig); err != nil {
		m.Config.EQClient.AddWithErrorLog("error",
			fmt.Sprintf("%v: Unable to validate monitor config for '%v' (%v): %v",
				m.Identifier, monitorName, monitorConfigLocation, err.Error()))
		return fmt.Errorf("Unable to validate monitor configuration for %v: %v", monitorName, err.Error())
	}

	// if check already running, stop it
	if m.monitorRunning(monitorName) {
		log.Debugf("%v: Monitor '%v' already running. Stopping it first...", m.Identifier, monitorName)

		if err := m.stop(monitorName); err != nil {
			m.Config.EQClient.AddWithErrorLog("error",
				fmt.Sprintf("%v: Unable to stop running monitor '%v': %v", m.Identifier, monitorName, err.Error()))
			return fmt.Errorf("Unable to stop running monitor %v: %v", monitorName, err.Error())
		}
	}

	// If check is disabled, do not start it back up
	if monitorConfig.Disable {
		log.Debugf("%v: '%v' is disabled. No further action will be taken.", m.Identifier, monitorName)
		return nil
	}

	// start check with new monitor configuration
	log.Debugf("%v: Starting new monitor for %v...", m.Identifier, monitorName)
	if err := m.start(monitorName, monitorConfigLocation, monitorConfig); err != nil {
		m.Config.EQClient.AddWithErrorLog("error",
			fmt.Sprintf("%v: Unable to start new monitor '%v': %v", m.Identifier, monitorName, err.Error()))
		return fmt.Errorf("Unable to start new monitor %v: %v", monitorName, err.Error())
	}

	log.Debugf("%v: Successfully started new monitor %v!", m.Identifier, monitorName)

	return nil
}

// Perform the actual stop of a given monitor; update running monitor slice
func (m *Monitor) stop(monitorName string) error {
	// Stop the given monitor
	m.runningMonitorLock.Lock()
	defer m.runningMonitorLock.Unlock()

	// Double check
	if _, ok := m.runningMonitors[monitorName]; !ok {
		return fmt.Errorf("%v: Unable to stop monitor '%v' - this monitor is NOT running!", m.Identifier, monitorName)
	}

	// Stop the actual check
	m.runningMonitors[monitorName].Stop()

	// Remove it from runningMonitors
	delete(m.runningMonitors, monitorName)

	return nil
}

// Perform the actual start of a monitor; update running monitor slice
func (m *Monitor) start(monitorName, monitorConfigLocation string, monitorConfig *MonitorConfig) error {
	// Let's be overly safe and ensure this monitor type exists
	if _, ok := m.SupportedMonitors[monitorConfig.Type]; !ok {
		return fmt.Errorf("%v: No such monitor type found '%v'", m.Identifier, monitorConfig.Type)
	}

	// Create a new monitor instance
	newMonitor := m.SupportedMonitors[monitorConfig.Type](
		&RootMonitorConfig{
			Name:           monitorName,
			ConfigName:     path.Base(monitorConfigLocation),
			GID:            util.RandomString(GOROUTINE_ID_LENGTH, false),
			Config:         monitorConfig,
			MemberID:       m.MemberID,
			MessageChannel: m.MessageChannel,
			StateChannel:   m.StateChannel,
			StopChannel:    make(chan bool, 1),
			Ticker:         time.NewTicker(time.Duration(monitorConfig.Interval)),
		},
	)

	// Do check-specific validation
	if err := newMonitor.Validate(); err != nil {
		return fmt.Errorf("%v: '%v' failed '%v' monitor config validation: %v",
			m.Identifier, path.Base(monitorConfigLocation), monitorConfig.Type, err.Error())
	}

	m.runningMonitorLock.Lock()
	defer m.runningMonitorLock.Unlock()

	// Add monitor to runningMonitors
	m.runningMonitors[monitorName] = newMonitor

	// Launch the monitor
	go m.runningMonitors[monitorName].Run()

	return nil
}

// Determine if given `monitorName` is in `runningMonitors`
func (m *Monitor) monitorRunning(monitorName string) bool {
	m.runningMonitorLock.Lock()
	defer m.runningMonitorLock.Unlock()

	for k := range m.runningMonitors {
		if k == monitorName {
			return true
		}
	}

	return false
}

// Top level mnitor config validation
func (m *Monitor) validateMonitorConfig(monitorConfig *MonitorConfig) error {
	if monitorConfig.Interval.String() == "0s" {
		return errors.New("'interval' must be > 0s")
	}

	if monitorConfig.CriticalThreshold == 0 {
		return errors.New("'critical-threshold' must be non-zero")
	}

	// TODO: Logic for this should be changed/fixed at some point
	if monitorConfig.WarningThreshold > monitorConfig.CriticalThreshold {
		return errors.New("'warning-threshold' cannot be larger than 'CriticalThreshold'")
	}

	// TODO: It should be possible to NOT have a WarningAlerter setting (and just
	// have a `CriticalAlerter` setting)
	if len(monitorConfig.WarningAlerter) == 0 {
		return errors.New("'warning-alerter' list must contain at least one entry")
	}

	if len(monitorConfig.CriticalAlerter) == 0 {
		return errors.New("'critical-alerter' list must contain at least one entry")
	}

	if monitorConfig.Port > MAX_PORT {
		return fmt.Errorf("'port' must be between 0 and %v", MAX_PORT)
	}

	return nil
}

// Wrapper for fetching (and unmarshaling) MonitorConfig by etcd location
func (m *Monitor) fetchMonitorConfig(monitorConfigLocation string) (*MonitorConfig, error) {
	monitorConfigData, err := m.Config.DalClient.Get(monitorConfigLocation, &dal.GetOptions{
		NoPrefix: true,
	})
	if err != nil {
		return nil, fmt.Errorf("Unable to fetch monitor configuration for '%v': %v", monitorConfigLocation, err.Error())
	}

	if _, ok := monitorConfigData[monitorConfigLocation]; !ok {
		return nil, errors.New("Returned monitor config data missing... bug?")
	}

	var monitorConfig *MonitorConfig

	if err := json.Unmarshal([]byte(monitorConfigData[monitorConfigLocation]), &monitorConfig); err != nil {
		return nil, fmt.Errorf("Unable to unmarshal fetched monitorConfig for '%v': %v", monitorConfigLocation, err.Error())
	}

	return monitorConfig, nil
}
<|endoftext|>


<commit_before><commit_msg>disk.go: Return the correct data for action result (#199)<commit_after><|endoftext|>


<commit_before><commit_msg>Test decoding of carb ratios<commit_after><|endoftext|>


<commit_before><commit_msg>sleep 1 second before retrying after server error<commit_after><|endoftext|>


<commit_before><commit_msg>modify name info<commit_after><|endoftext|>


<commit_before><commit_msg>Add cpuprofile option<commit_after><|endoftext|>


<commit_before><commit_msg>Fix return status<commit_after><|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"github.com/jung-kurt/gofpdf"
)

const version = "0.2.0-dev"

type KeyInformation struct {
	x      float64
	y      float64
	width  float64
	height float64

	use bool
}
type KeyStruct struct {
	symbol       string
	isFunction   bool
	isModifier   bool
	isLEDControl bool
}
type Scan struct {
	line        int
	lineHead    int
	inKeymaps   bool
	layerNumber int
	keys        [3][]string
	ks          [3][]KeyStruct
	m           map[string]string
}

func (self *Scan) Init() {
	self.line = 1
	self.lineHead = 0
	self.inKeymaps = false
	self.layerNumber = 0
	self.m = map[string]string{"KC_EQL": "=",
		"KC_DELT": "Del", "KC_BSPC": "BkSp",
		"KC_TRNS": "", "KC_ENT": "Enter", "KC_1": "1",
		"KC_2": "2", "KC_3": "3", "KC_4": "4", "KC_5": "5", "KC_6": "6",
		"KC_7": "7", "KC_8": "8", "KC_9": "9", "KC_0": "0",
		"KC_A": "A", "KC_B": "B", "KC_C": "C", "KC_D": "D",
		"KC_E": "E", "KC_F": "F", "KC_G": "G", "KC_H": "H",
		"KC_I": "I", "KC_J": "J", "KC_K": "K", "KC_L": "L",
		"KC_M": "M", "KC_N": "N", "KC_O": "O", "KC_P": "P",
		"KC_Q": "Q", "KC_R": "R", "KC_S": "S", "KC_T": "T",
		"KC_U": "U", "KC_V": "V", "KC_W": "W", "KC_X": "X",
		"KC_Y": "Y", "KC_Z": "Z",
		"KC_EXLM": "!", "KC_AT": "@", "KC_LCBR": "{", "KC_RCBR": "}", "KC_PIPE": "|",
		"KC_HASH": "#", "KC_DLR": "$", "KC_LPRN": "(", "KC_RPRN": ")", "KC_GRV": "`",
		"KC_PERC": "%", "KC_CIRC": "^", "KC_LBRC": "[", "KC_RBRC": "]", "KC_TILD": "~",
		"KC_PLUS": "+", "KC_ASTR": "*", "KC_DOT": ".", "KC_AMPR": "&",
		"KC_MINS": "-", "KC_BSLS": "\\", "KC_RSFT": "RSft",
		"KC_MUTE": "Mute", "RGB_HUD": "Hue-", "RGB_HUI": "Hue+",
		"KC_F1": "F1", "KC_F2": "F2", "KC_F3": "F3", "KC_F4": "F4",
		"KC_F5": "F5", "KC_F6": "F6", "KC_F7": "F7", "KC_F8": "F8",
		"KC_F9": "F9", "KC_F10": "F10", "KC_F11": "F11", "KC_F12": "F12",
		"KC_UP": "UP", "KC_DOWN": "DOWN", "KC_LEFT": "LEFT", "KC_RGHT": "RIGHT",
		"KC_MS_U": "MsUp", "KC_MS_D": "MsDown", "KC_MS_L": "MsLeft", "KC_MS_R": "MsRght",
		"KC_BTN1": "Lclk", "KC_BTN2": "Rclk",
		"RGB_TOG": "Toggle", "RGB_SLD": "Solid",
		"RGB_VAD": "Brightness-", "RGB_VAI": "Brightness+", "RGB_MOD": "Animat",
		"KC_LSFT": "LSft", "KC_SPC": "SPC",
		"KC_VOLU": "VolUp", "KC_VOLD": "VolDn", "KC_MPRV": "Prev", "KC_MNXT": "Next",
		"KC_HOME": "Home", "KC_END": "End", "KC_PGUP": "PgUp", "KC_PGDN": "PgDn",
		"KC_MPLY": "Play", "KC_TAB": "Tab",
		"KC_WBAK": "BrowserBack", "KC_COMM": ",", "KC_QUOT": "'",
		"KC_LALT": "LAlt", "KC_RALT": "RAlt"}
}

func (self *Scan) Err(s int) {
	fmt.Printf("\n!!Error!!%d\n", s)
}

func (self *Scan) GetDisplayName(key string) string {
	_, ok := self.m[key]
	if ok {
		return self.m[key]
	} else {
		return key
	}
}

// color schema reference with http://www.color-hex.com/
func (self *Scan) DrawKey(pdf *gofpdf.Fpdf, curx float64, cury float64, ki KeyInformation, k int, keyindex int, lineHt float64) {
	r, g, b := pdf.GetFillColor()
	var drawType = "D"
	if self.ks[k][keyindex].isFunction {
		pdf.SetFillColor(169, 222, 255)
		drawType = "FD"
	}
	if self.ks[k][keyindex].isModifier {
		pdf.SetFillColor(207, 253, 169)
		drawType = "FD"
	}
	if self.ks[k][keyindex].isLEDControl {
		pdf.SetFillColor(250, 253, 188)
		drawType = "FD"
	}

	pdf.Rect(curx+ki.x, cury+float64(k*90)+ki.y, ki.width, ki.height, drawType)
	pdf.SetXY(curx+ki.x, cury+float64(k*90)+ki.y)
	pdf.Cell(0, 0+lineHt, self.GetDisplayName(self.keys[k][keyindex]))
	pdf.SetFillColor(r, g, b)
}

func (self *Scan) DrawKeyMain(pdf *gofpdf.Fpdf, curx float64, cury float64, ki KeyInformation, k int, keyindex int, lineHt float64, j int, tfrx float64, tfry float64) {
	if j > 4 {
		pdf.TransformBegin()
		pdf.TransformRotate(tfrx, tfry, 97+90*float64(k))
	}
	self.DrawKey(pdf, curx, cury, ki, k, keyindex, lineHt)
	if j > 4 {
		pdf.TransformEnd()
	}
}

func (self *Scan) Output() {
	pdf := gofpdf.New("P", "mm", "A4", "")
	pdf.SetFont("Arial", "", 10)
	pdf.AddPage()

	curx, cury := pdf.GetXY()
	_, lineHt := pdf.GetFontSize()

	//
	cols := [14]float64{0, 0, -0.25, -0.375, -0.25, 0, 0,
		0, 0, -0.25, -0.375, -0.25, 0, 0}
	var lkil []KeyInformation
	var rkil []KeyInformation
	for j := 0; j < 8; j++ {
		for i := 0; i < 7; i++ {
			var ltmp = KeyInformation{x: float64(i * 10), y: float64(j * 10), width: float64(10), height: float64(10), use: true}
			var rtmp = KeyInformation{x: float64((i + 13) * 10), y: float64(j * 10), width: float64(10), height: float64(10), use: true}
			if j < 6 {
				ltmp.y = ltmp.y + cols[i]*10
				rtmp.y = rtmp.y + cols[i+7]*10
			}
			if i == 0 {
				ltmp.x = ltmp.x - 5
				ltmp.width = 15
			}
			if i == 6 {
				rtmp.width = 15
			}
			lkil = append(lkil, ltmp)
			rkil = append(rkil, rtmp)
		}
	}
	//
	lkil[20].use = false
	rkil[14].use = false
	lkil[33].use = false
	rkil[29].use = false
	lkil[34].use = false
	rkil[28].use = false
	//
	for j := 5; j < 8; j++ {
		for i := 0; i < 4; i++ {
			lkil[j*7+i].use = false
			rkil[j*7+(6-i)].use = false
		}
	}
	//
	lkil[39].use = false
	rkil[37].use = false
	lkil[46].use = false
	rkil[44].use = false
	lkil[47].use = false
	rkil[43].use = false
	//
	lkil[53].height = float64(20)
	lkil[53].y = lkil[53].y - 10
	rkil[51].height = float64(20)
	rkil[51].y = lkil[51].y - 10
	lkil[54].height = float64(20)
	lkil[54].y = lkil[54].y - 10
	rkil[50].height = float64(20)
	rkil[50].y = lkil[50].y - 10
	//
	lkil[13].height = float64(15)
	rkil[7].height = float64(15)
	lkil[27].height = float64(15)
	rkil[21].height = float64(15)
	lkil[27].y = lkil[27].y - 5
	rkil[21].y = rkil[21].y - 5
	//
	lkil[28].x = lkil[28].x + 5
	lkil[28].width = 10
	rkil[34].width = 10
	// right

	for k := 0; k < 3; k++ {
		var keyindex int = 0
		for j := 0; j < 8; j++ {
			for i := 0; i < 7; i++ {
				var ki = lkil[j*7+i]
				if ki.use {
					self.DrawKeyMain(pdf, curx, cury, ki, k, keyindex, lineHt, j, -30, 97)
					keyindex = keyindex + 1
				}
			}
		}
		for j := 0; j < 8; j++ {
			for i := 0; i < 7; i++ {
				var ki = rkil[j*7+i]
				if ki.use {
					self.DrawKeyMain(pdf, curx, cury, ki, k, keyindex, lineHt, j, 30, 113)
					keyindex = keyindex + 1
				}
			}
		}
	}
	pdf.Output(os.Stdout)
}

func main() {
	if len(os.Args) < 2 {
		fmt.Printf("%v FILE\n", os.Args[0])
		os.Exit(1)
	}
	file, err := os.Open(os.Args[1])
	if err != nil {
		log.Fatal(err)
	}

	buffer, err := ioutil.ReadAll(file)
	if err != nil {
		log.Fatal(err)
	}

	file.Close()

	parser := &Parser{Buffer: string(buffer)}
	parser.Init()
	parser.s.Init()
	err2 := parser.Parse()

	if err2 != nil {
		fmt.Println(err2)
	} else {
		parser.Execute()
		parser.s.Output()
	}
}
<commit_msg>Fix right hand keyboard X coordinate<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"github.com/jung-kurt/gofpdf"
)

const version = "0.2.0-dev"

type KeyInformation struct {
	x      float64
	y      float64
	width  float64
	height float64

	use bool
}
type KeyStruct struct {
	symbol       string
	isFunction   bool
	isModifier   bool
	isLEDControl bool
}
type Scan struct {
	line        int
	lineHead    int
	inKeymaps   bool
	layerNumber int
	keys        [3][]string
	ks          [3][]KeyStruct
	m           map[string]string
}

func (self *Scan) Init() {
	self.line = 1
	self.lineHead = 0
	self.inKeymaps = false
	self.layerNumber = 0
	self.m = map[string]string{"KC_EQL": "=",
		"KC_DELT": "Del", "KC_BSPC": "BkSp",
		"KC_TRNS": "", "KC_ENT": "Enter", "KC_1": "1",
		"KC_2": "2", "KC_3": "3", "KC_4": "4", "KC_5": "5", "KC_6": "6",
		"KC_7": "7", "KC_8": "8", "KC_9": "9", "KC_0": "0",
		"KC_A": "A", "KC_B": "B", "KC_C": "C", "KC_D": "D",
		"KC_E": "E", "KC_F": "F", "KC_G": "G", "KC_H": "H",
		"KC_I": "I", "KC_J": "J", "KC_K": "K", "KC_L": "L",
		"KC_M": "M", "KC_N": "N", "KC_O": "O", "KC_P": "P",
		"KC_Q": "Q", "KC_R": "R", "KC_S": "S", "KC_T": "T",
		"KC_U": "U", "KC_V": "V", "KC_W": "W", "KC_X": "X",
		"KC_Y": "Y", "KC_Z": "Z",
		"KC_EXLM": "!", "KC_AT": "@", "KC_LCBR": "{", "KC_RCBR": "}", "KC_PIPE": "|",
		"KC_HASH": "#", "KC_DLR": "$", "KC_LPRN": "(", "KC_RPRN": ")", "KC_GRV": "`",
		"KC_PERC": "%", "KC_CIRC": "^", "KC_LBRC": "[", "KC_RBRC": "]", "KC_TILD": "~",
		"KC_PLUS": "+", "KC_ASTR": "*", "KC_DOT": ".", "KC_AMPR": "&",
		"KC_MINS": "-", "KC_BSLS": "\\", "KC_RSFT": "RSft",
		"KC_MUTE": "Mute", "RGB_HUD": "Hue-", "RGB_HUI": "Hue+",
		"KC_F1": "F1", "KC_F2": "F2", "KC_F3": "F3", "KC_F4": "F4",
		"KC_F5": "F5", "KC_F6": "F6", "KC_F7": "F7", "KC_F8": "F8",
		"KC_F9": "F9", "KC_F10": "F10", "KC_F11": "F11", "KC_F12": "F12",
		"KC_UP": "UP", "KC_DOWN": "DOWN", "KC_LEFT": "LEFT", "KC_RGHT": "RIGHT",
		"KC_MS_U": "MsUp", "KC_MS_D": "MsDown", "KC_MS_L": "MsLeft", "KC_MS_R": "MsRght",
		"KC_BTN1": "Lclk", "KC_BTN2": "Rclk",
		"RGB_TOG": "Toggle", "RGB_SLD": "Solid",
		"RGB_VAD": "Brightness-", "RGB_VAI": "Brightness+", "RGB_MOD": "Animat",
		"KC_LSFT": "LSft", "KC_SPC": "SPC",
		"KC_VOLU": "VolUp", "KC_VOLD": "VolDn", "KC_MPRV": "Prev", "KC_MNXT": "Next",
		"KC_HOME": "Home", "KC_END": "End", "KC_PGUP": "PgUp", "KC_PGDN": "PgDn",
		"KC_MPLY": "Play", "KC_TAB": "Tab",
		"KC_WBAK": "BrowserBack", "KC_COMM": ",", "KC_QUOT": "'",
		"KC_LALT": "LAlt", "KC_RALT": "RAlt"}
}

func (self *Scan) Err(s int) {
	fmt.Printf("\n!!Error!!%d\n", s)
}

func (self *Scan) GetDisplayName(key string) string {
	_, ok := self.m[key]
	if ok {
		return self.m[key]
	} else {
		return key
	}
}

// color schema reference with http://www.color-hex.com/
func (self *Scan) DrawKey(pdf *gofpdf.Fpdf, curx float64, cury float64, ki KeyInformation, k int, keyindex int, lineHt float64) {
	r, g, b := pdf.GetFillColor()
	var drawType = "D"
	if self.ks[k][keyindex].isFunction {
		pdf.SetFillColor(169, 222, 255)
		drawType = "FD"
	}
	if self.ks[k][keyindex].isModifier {
		pdf.SetFillColor(207, 253, 169)
		drawType = "FD"
	}
	if self.ks[k][keyindex].isLEDControl {
		pdf.SetFillColor(250, 253, 188)
		drawType = "FD"
	}

	pdf.Rect(curx+ki.x, cury+float64(k*90)+ki.y, ki.width, ki.height, drawType)
	pdf.SetXY(curx+ki.x, cury+float64(k*90)+ki.y)
	pdf.Cell(0, 0+lineHt, self.GetDisplayName(self.keys[k][keyindex]))
	pdf.SetFillColor(r, g, b)
}

func (self *Scan) DrawKeyMain(pdf *gofpdf.Fpdf, curx float64, cury float64, ki KeyInformation, k int, keyindex int, lineHt float64, j int, tfrx float64, tfry float64) {
	if j > 4 {
		pdf.TransformBegin()
		pdf.TransformRotate(tfrx, tfry, 97+90*float64(k))
	}
	self.DrawKey(pdf, curx, cury, ki, k, keyindex, lineHt)
	if j > 4 {
		pdf.TransformEnd()
	}
}

func (self *Scan) Output() {
	pdf := gofpdf.New("P", "mm", "A4", "")
	pdf.SetFont("Arial", "", 10)
	pdf.AddPage()

	curx, cury := pdf.GetXY()
	_, lineHt := pdf.GetFontSize()

	//
	cols := [14]float64{0, 0, -0.25, -0.375, -0.25, 0, 0,
		0, 0, -0.25, -0.375, -0.25, 0, 0}
	var lkil []KeyInformation
	var rkil []KeyInformation
	for j := 0; j < 8; j++ {
		for i := 0; i < 7; i++ {
			var ltmp = KeyInformation{x: float64(i * 10), y: float64(j * 10), width: float64(10), height: float64(10), use: true}
			var rtmp = KeyInformation{x: float64((i + 13) * 10), y: float64(j * 10), width: float64(10), height: float64(10), use: true}
			if j < 6 {
				ltmp.y = ltmp.y + cols[i]*10
				rtmp.y = rtmp.y + cols[i+7]*10
			}
			if i == 0 {
				ltmp.x = ltmp.x - 5
				ltmp.width = 15
			}
			if i == 6 {
				rtmp.width = 15
			}
			lkil = append(lkil, ltmp)
			rkil = append(rkil, rtmp)
		}
	}
	//
	lkil[20].use = false
	rkil[14].use = false
	lkil[33].use = false
	rkil[29].use = false
	lkil[34].use = false
	rkil[28].use = false
	//
	for j := 5; j < 8; j++ {
		for i := 0; i < 4; i++ {
			lkil[j*7+i].use = false
			rkil[j*7+(6-i)].use = false
		}
	}
	//
	lkil[39].use = false
	rkil[37].use = false
	lkil[46].use = false
	rkil[44].use = false
	lkil[47].use = false
	rkil[43].use = false
	//
	lkil[53].height = float64(20)
	lkil[53].y = lkil[53].y - 10
	rkil[51].height = float64(20)
	rkil[51].y = lkil[51].y - 10
	lkil[54].height = float64(20)
	lkil[54].y = lkil[54].y - 10
	rkil[50].height = float64(20)
	rkil[50].y = lkil[50].y - 10
	//
	lkil[13].height = float64(15)
	rkil[7].height = float64(15)
	lkil[27].height = float64(15)
	rkil[21].height = float64(15)
	lkil[27].y = lkil[27].y - 5
	rkil[21].y = rkil[21].y - 5
	//
	lkil[28].x = lkil[28].x + 5
	lkil[28].width = 10
	rkil[34].width = 10
	// right

	for k := 0; k < 3; k++ {
		var keyindex int = 0
		for j := 0; j < 8; j++ {
			for i := 0; i < 7; i++ {
				var ki = lkil[j*7+i]
				if ki.use {
					self.DrawKeyMain(pdf, curx, cury, ki, k, keyindex, lineHt, j, -30, 97)
					keyindex = keyindex + 1
				}
			}
		}
		for j := 0; j < 8; j++ {
			for i := 0; i < 7; i++ {
				var ki = rkil[j*7+i]
				if ki.use {
					self.DrawKeyMain(pdf, 0, cury, ki, k, keyindex, lineHt, j, 30, 113)
					keyindex = keyindex + 1
				}
			}
		}
	}
	pdf.Output(os.Stdout)
}

func main() {
	if len(os.Args) < 2 {
		fmt.Printf("%v FILE\n", os.Args[0])
		os.Exit(1)
	}
	file, err := os.Open(os.Args[1])
	if err != nil {
		log.Fatal(err)
	}

	buffer, err := ioutil.ReadAll(file)
	if err != nil {
		log.Fatal(err)
	}

	file.Close()

	parser := &Parser{Buffer: string(buffer)}
	parser.Init()
	parser.s.Init()
	err2 := parser.Parse()

	if err2 != nil {
		fmt.Println(err2)
	} else {
		parser.Execute()
		parser.s.Output()
	}
}
<|endoftext|>


<commit_before>package chrome

import (
	"encoding/json"
	"testing"
)

func TestEmptyCapabilities(t *testing.T) {
	data, err := json.Marshal(Capabilities{})
	if err != nil {
		t.Fatalf("json.Marshal(Capabilities{}) return error: %v", err)
	}
	got, want := string(data), "{}"
	if got != want {
		t.Fatalf("json.Marshal(Capabilities{}) = %q, want %q", got, want)
	}
}
<commit_msg>Fix the TestEmptyCapabilities test<commit_after>package chrome

import (
	"encoding/json"
	"testing"
)

func TestEmptyCapabilities(t *testing.T) {
	data, err := json.Marshal(Capabilities{})
	if err != nil {
		t.Fatalf("json.Marshal(Capabilities{}) return error: %v", err)
	}
	got, want := string(data), `{"w3c":false}`
	if got != want {
		t.Fatalf("json.Marshal(Capabilities{}) = %q, want %q", got, want)
	}
}
<|endoftext|>