<commit_before>package testutil

import (
	"bytes"
	"database/sql"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/kr/pretty"
	"github.com/lucapette/deloominator/pkg/config"
	"github.com/lucapette/deloominator/pkg/db"
)

type DBTemplate struct {
	Name string
}

func fixturePath(t *testing.T, fixture string) string {
	_, filename, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatalf("problems recovering caller information")
	}

	return filepath.Join(filepath.Dir(filename), "fixtures", fixture)
}

func WriteFixture(t *testing.T, fixture, content string) {
	err := ioutil.WriteFile(fixturePath(t, fixture), []byte(content), 0644)
	if err != nil {
		t.Fatal(err)
	}
}

func LoadFixture(t *testing.T, fixture string) string {
	content, err := ioutil.ReadFile(fixturePath(t, fixture))
	if err != nil {
		t.Fatal(err)
	}

	return string(content)
}

func ParseFixture(t *testing.T, w io.Writer, fixture string, data interface{}) {
	tmpl := template.Must(template.New(fixture).Parse(LoadFixture(t, fixture)))

	err := tmpl.Execute(w, data)
	if err != nil {
		t.Fatal(err)
	}
}

func randName() string {
	return fmt.Sprintf("%s_%s", config.BinaryName, strconv.Itoa(int(time.Now().UnixNano()+int64(os.Getpid()))))
}

func SetupPG(t *testing.T) (string, func()) {
	randName := randName()

	cfg, err := getTestConfig()
	if err != nil {
		t.Fatalf("could not get test config: %v", err)
	}

	dsn := fmt.Sprintf("postgres://%s:%s@localhost/%s?sslmode=disable", cfg.PGUser, cfg.PGPass, randName)

	tmpfile, err := ioutil.TempFile("", "db_test")
	if err != nil {
		t.Fatalf("could not create temp file: %v", err)
	}
	defer func() {
		err = os.Remove(tmpfile.Name())
		if err != nil {
			t.Fatalf("could not remove temp file: %v", err)
		}
	}()

	ParseFixture(t, tmpfile, "postgres.sql", DBTemplate{Name: randName})

	if output, err := exec.Command("psql", "-f", tmpfile.Name()).CombinedOutput(); err != nil {
		t.Fatalf("%s\ncould not run psql: %v", output, err)
	}

	return dsn, func() {
		db, err := sql.Open("postgres",
			fmt.Sprintf("postgres://%s:%s@localhost/?sslmode=disable", cfg.PGUser, cfg.PGPass))
		if err != nil {
			t.Fatalf("could not open postgres database: %v", err)
		}

		if _, err = db.Exec(fmt.Sprintf("DROP DATABASE %s;", randName)); err != nil {
			t.Fatalf("could not drop database %s: %v", randName, err)
		}

		if err := db.Close(); err != nil {
			t.Fatalf("could not close database %s: %v", randName, err)
		}
	}
}

func SetupMySQL(t *testing.T) (string, func()) {
	randName := randName()

	cfg, err := getTestConfig()
	if err != nil {
		t.Fatalf("could not get test config: %v", err)
	}

	dsn := fmt.Sprintf("mysql://%s:%s@/%s", cfg.MysqlUser, cfg.MysqlPass, randName)

	query := bytes.Buffer{}
	ParseFixture(t, &query, "mysql.sql", DBTemplate{Name: randName})

	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@/?multiStatements=true", cfg.MysqlUser, cfg.MysqlPass))
	if err != nil {
		t.Fatalf("could not open mysql database %s: %v", randName, err)
	}

	if _, err := db.Exec(query.String()); err != nil {
		t.Fatalf("could not execute %s on mysql database %s: %v", query.String(), randName, err)
	}

	return dsn, func() {
		if _, err := db.Exec(fmt.Sprintf("DROP DATABASE %s;", randName)); err != nil {
			t.Fatalf("could not drop mysql database %s: %v", randName, err)
		}

		if err = db.Close(); err != nil {
			t.Fatalf("could not close mysql database %s: %v", randName, err)
		}
	}
}

func LoadData(t *testing.T, ds *db.DataSource, table string, data db.QueryResult) {
	query := &bytes.Buffer{}

	columns := make([]string, len(data.Columns))
	for i, col := range data.Columns {
		columns[i] = col.Name
	}

	rows := make([]string, len(data.Rows))
	for i, r := range data.Rows {
		row := &bytes.Buffer{}

		cells := make([]string, len(r))
		for i, c := range r {
			cells[i] = fmt.Sprintf("'%s'", c.Value)
		}

		fmt.Fprintf(row, "(%s)", strings.Join(cells, ","))

		rows[i] = row.String()
	}

	fmt.Fprintf(query, "insert into %s (%s) values %s",
		table,
		strings.Join(columns, ","),
		strings.Join(rows, ","),
	)

	if _, err := ds.Query(query.String()); err != nil {
		t.Fatalf("could not execute query %s on db %s: %v", query.String(), ds.Name(), err)
	}
}

func SetupDataSources(t *testing.T) (db.DataSources, func()) {
	dsnPG, cleanupPG := SetupPG(t)
	dsnMySQL, cleanupMySQL := SetupMySQL(t)

	sources := []string{dsnMySQL, dsnPG}

	dataSources, err := db.NewDataSources(sources)
	if err != nil {
		t.Fatal("could not create DataSources from %v: %v", sources, err)
	}

	return dataSources, func() {
		dataSources.Close()
		cleanupPG()
		cleanupMySQL()
	}
}

// Diff returns a string of differences between expected and actual if any
func Diff(expected, actual interface{}) []string {
	return pretty.Diff(expected, actual)
}
<commit_msg>Use fatalf<commit_after>package testutil

import (
	"bytes"
	"database/sql"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/kr/pretty"
	"github.com/lucapette/deloominator/pkg/config"
	"github.com/lucapette/deloominator/pkg/db"
)

type DBTemplate struct {
	Name string
}

func fixturePath(t *testing.T, fixture string) string {
	_, filename, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatalf("problems recovering caller information")
	}

	return filepath.Join(filepath.Dir(filename), "fixtures", fixture)
}

func WriteFixture(t *testing.T, fixture, content string) {
	err := ioutil.WriteFile(fixturePath(t, fixture), []byte(content), 0644)
	if err != nil {
		t.Fatal(err)
	}
}

func LoadFixture(t *testing.T, fixture string) string {
	content, err := ioutil.ReadFile(fixturePath(t, fixture))
	if err != nil {
		t.Fatal(err)
	}

	return string(content)
}

func ParseFixture(t *testing.T, w io.Writer, fixture string, data interface{}) {
	tmpl := template.Must(template.New(fixture).Parse(LoadFixture(t, fixture)))

	err := tmpl.Execute(w, data)
	if err != nil {
		t.Fatal(err)
	}
}

func randName() string {
	return fmt.Sprintf("%s_%s", config.BinaryName, strconv.Itoa(int(time.Now().UnixNano()+int64(os.Getpid()))))
}

func SetupPG(t *testing.T) (string, func()) {
	randName := randName()

	cfg, err := getTestConfig()
	if err != nil {
		t.Fatalf("could not get test config: %v", err)
	}

	dsn := fmt.Sprintf("postgres://%s:%s@localhost/%s?sslmode=disable", cfg.PGUser, cfg.PGPass, randName)

	tmpfile, err := ioutil.TempFile("", "db_test")
	if err != nil {
		t.Fatalf("could not create temp file: %v", err)
	}
	defer func() {
		err = os.Remove(tmpfile.Name())
		if err != nil {
			t.Fatalf("could not remove temp file: %v", err)
		}
	}()

	ParseFixture(t, tmpfile, "postgres.sql", DBTemplate{Name: randName})

	if output, err := exec.Command("psql", "-f", tmpfile.Name()).CombinedOutput(); err != nil {
		t.Fatalf("%s\ncould not run psql: %v", output, err)
	}

	return dsn, func() {
		db, err := sql.Open("postgres",
			fmt.Sprintf("postgres://%s:%s@localhost/?sslmode=disable", cfg.PGUser, cfg.PGPass))
		if err != nil {
			t.Fatalf("could not open postgres database: %v", err)
		}

		if _, err = db.Exec(fmt.Sprintf("DROP DATABASE %s;", randName)); err != nil {
			t.Fatalf("could not drop database %s: %v", randName, err)
		}

		if err := db.Close(); err != nil {
			t.Fatalf("could not close database %s: %v", randName, err)
		}
	}
}

func SetupMySQL(t *testing.T) (string, func()) {
	randName := randName()

	cfg, err := getTestConfig()
	if err != nil {
		t.Fatalf("could not get test config: %v", err)
	}

	dsn := fmt.Sprintf("mysql://%s:%s@/%s", cfg.MysqlUser, cfg.MysqlPass, randName)

	query := bytes.Buffer{}
	ParseFixture(t, &query, "mysql.sql", DBTemplate{Name: randName})

	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@/?multiStatements=true", cfg.MysqlUser, cfg.MysqlPass))
	if err != nil {
		t.Fatalf("could not open mysql database %s: %v", randName, err)
	}

	if _, err := db.Exec(query.String()); err != nil {
		t.Fatalf("could not execute %s on mysql database %s: %v", query.String(), randName, err)
	}

	return dsn, func() {
		if _, err := db.Exec(fmt.Sprintf("DROP DATABASE %s;", randName)); err != nil {
			t.Fatalf("could not drop mysql database %s: %v", randName, err)
		}

		if err = db.Close(); err != nil {
			t.Fatalf("could not close mysql database %s: %v", randName, err)
		}
	}
}

func LoadData(t *testing.T, ds *db.DataSource, table string, data db.QueryResult) {
	query := &bytes.Buffer{}

	columns := make([]string, len(data.Columns))
	for i, col := range data.Columns {
		columns[i] = col.Name
	}

	rows := make([]string, len(data.Rows))
	for i, r := range data.Rows {
		row := &bytes.Buffer{}

		cells := make([]string, len(r))
		for i, c := range r {
			cells[i] = fmt.Sprintf("'%s'", c.Value)
		}

		fmt.Fprintf(row, "(%s)", strings.Join(cells, ","))

		rows[i] = row.String()
	}

	fmt.Fprintf(query, "insert into %s (%s) values %s",
		table,
		strings.Join(columns, ","),
		strings.Join(rows, ","),
	)

	if _, err := ds.Query(query.String()); err != nil {
		t.Fatalf("could not execute query %s on db %s: %v", query.String(), ds.Name(), err)
	}
}

func SetupDataSources(t *testing.T) (db.DataSources, func()) {
	dsnPG, cleanupPG := SetupPG(t)
	dsnMySQL, cleanupMySQL := SetupMySQL(t)

	sources := []string{dsnMySQL, dsnPG}

	dataSources, err := db.NewDataSources(sources)
	if err != nil {
		t.Fatalf("could not create DataSources from %v: %v", sources, err)
	}

	return dataSources, func() {
		dataSources.Close()
		cleanupPG()
		cleanupMySQL()
	}
}

// Diff returns a string of differences between expected and actual if any
func Diff(expected, actual interface{}) []string {
	return pretty.Diff(expected, actual)
}
<|endoftext|>


<commit_before>/*
Copyright 2018 Google LLC

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

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

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

package util

import (
	"fmt"
	"net/http"
	"net/url"
	"os"
	"os/user"
	"path/filepath"
	"strings"

	"github.com/GoogleContainerTools/kaniko/pkg/constants"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/moby/buildkit/frontend/dockerfile/instructions"
	"github.com/moby/buildkit/frontend/dockerfile/parser"
	"github.com/moby/buildkit/frontend/dockerfile/shell"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// ResolveEnvironmentReplacementList resolves a list of values by calling resolveEnvironmentReplacement
func ResolveEnvironmentReplacementList(values, envs []string, isFilepath bool) ([]string, error) {
	var resolvedValues []string
	for _, value := range values {
		resolved, err := ResolveEnvironmentReplacement(value, envs, isFilepath)
		logrus.Debugf("Resolved %s to %s", value, resolved)
		if err != nil {
			return nil, err
		}
		resolvedValues = append(resolvedValues, resolved)
	}
	return resolvedValues, nil
}

// ResolveEnvironmentReplacement resolves replacing env variables in some text from envs
// It takes in a string representation of the command, the value to be resolved, and a list of envs (config.Env)
// Ex: value = $foo/newdir, envs = [foo=/foodir], then this should return /foodir/newdir
// The dockerfile/shell package handles processing env values
// It handles escape characters and supports expansion from the config.Env array
// Shlex handles some of the following use cases (these and more are tested in integration tests)
// ""a'b'c"" -> "a'b'c"
// "Rex\ The\ Dog \" -> "Rex The Dog"
// "a\"b" -> "a"b"
func ResolveEnvironmentReplacement(value string, envs []string, isFilepath bool) (string, error) {
	shlex := shell.NewLex(parser.DefaultEscapeToken)
	fp, err := shlex.ProcessWord(value, envs)
	// Check after replacement if value is a remote URL
	if !isFilepath || IsSrcRemoteFileURL(fp) {
		return fp, err
	}
	if err != nil {
		return "", err
	}
	fp = filepath.Clean(fp)
	if IsDestDir(value) && !IsDestDir(fp) {
		fp = fp + "/"
	}
	return fp, nil
}

func ResolveEnvAndWildcards(sd instructions.SourcesAndDest, buildcontext string, envs []string) ([]string, string, error) {
	// First, resolve any environment replacement
	resolvedEnvs, err := ResolveEnvironmentReplacementList(sd, envs, true)
	if err != nil {
		return nil, "", errors.Wrap(err, "failed to resolve environment")
	}
	if len(resolvedEnvs) == 0 {
		return nil, "", errors.New("resolved envs is empty")
	}
	dest := resolvedEnvs[len(resolvedEnvs)-1]
	// Resolve wildcards and get a list of resolved sources
	srcs, err := ResolveSources(resolvedEnvs[0:len(resolvedEnvs)-1], buildcontext)
	if err != nil {
		return nil, "", errors.Wrap(err, "failed to resolve sources")
	}
	err = IsSrcsValid(sd, srcs, buildcontext)
	return srcs, dest, err
}

// ContainsWildcards returns true if any entry in paths contains wildcards
func ContainsWildcards(paths []string) bool {
	for _, path := range paths {
		if strings.ContainsAny(path, "*?[") {
			return true
		}
	}
	return false
}

// ResolveSources resolves the given sources if the sources contains wildcards
// It returns a list of resolved sources
func ResolveSources(srcs []string, root string) ([]string, error) {
	// If sources contain wildcards, we first need to resolve them to actual paths
	if !ContainsWildcards(srcs) {
		return srcs, nil
	}
	logrus.Infof("Resolving srcs %v...", srcs)
	files, err := RelativeFiles("", root)
	if err != nil {
		return nil, err
	}
	resolved, err := matchSources(srcs, files)
	if err != nil {
		return nil, err
	}
	logrus.Debugf("Resolved sources to %v", resolved)
	return resolved, nil
}

// matchSources returns a list of sources that match wildcards
func matchSources(srcs, files []string) ([]string, error) {
	var matchedSources []string
	for _, src := range srcs {
		if IsSrcRemoteFileURL(src) {
			matchedSources = append(matchedSources, src)
			continue
		}
		src = filepath.Clean(src)
		for _, file := range files {
			if filepath.IsAbs(src) {
				file = filepath.Join(constants.RootDir, file)
			}
			matched, err := filepath.Match(src, file)
			if err != nil {
				return nil, err
			}
			if matched || src == file {
				matchedSources = append(matchedSources, file)
			}
		}
	}
	return matchedSources, nil
}

func IsDestDir(path string) bool {
	// try to stat the path
	fileInfo, err := os.Stat(path)
	if err != nil {
		// fall back to string-based determination
		return strings.HasSuffix(path, "/") || path == "."
	}
	// if it's a real path, check the fs response
	return fileInfo.IsDir()
}

// DestinationFilepath returns the destination filepath from the build context to the image filesystem
// If source is a file:
//	If dest is a dir, copy it to /dest/relpath
// 	If dest is a file, copy directly to dest
// If source is a dir:
//	Assume dest is also a dir, and copy to dest/
// If dest is not an absolute filepath, add /cwd to the beginning
func DestinationFilepath(src, dest, cwd string) (string, error) {
	_, srcFileName := filepath.Split(src)
	newDest := dest

	if IsDestDir(newDest) {
		newDest = filepath.Join(newDest, srcFileName)
	}

	if !filepath.IsAbs(newDest) {
		newDest = filepath.Join(cwd, newDest)
	}

	if len(srcFileName) <= 0 && !strings.HasSuffix(newDest, "/") {
		newDest += "/"
	}

	return newDest, nil
}

// URLDestinationFilepath gives the destination a file from a remote URL should be saved to
func URLDestinationFilepath(rawurl, dest, cwd string, envs []string) (string, error) {
	if !IsDestDir(dest) {
		if !filepath.IsAbs(dest) {
			return filepath.Join(cwd, dest), nil
		}
		return dest, nil
	}
	urlBase := filepath.Base(rawurl)
	urlBase, err := ResolveEnvironmentReplacement(urlBase, envs, true)
	if err != nil {
		return "", err
	}
	destPath := filepath.Join(dest, urlBase)

	if !filepath.IsAbs(dest) {
		destPath = filepath.Join(cwd, destPath)
	}
	return destPath, nil
}

func IsSrcsValid(srcsAndDest instructions.SourcesAndDest, resolvedSources []string, root string) error {
	srcs := srcsAndDest[:len(srcsAndDest)-1]
	dest := srcsAndDest[len(srcsAndDest)-1]

	if !ContainsWildcards(srcs) {
		totalSrcs := 0
		for _, src := range srcs {
			if ExcludeFile(src, root) {
				continue
			}
			totalSrcs++
		}
		if totalSrcs > 1 && !IsDestDir(dest) {
			return errors.New("when specifying multiple sources in a COPY command, destination must be a directory and end in '/'")
		}
	}

	// If there is only one source and it's a directory, docker assumes the dest is a directory
	if len(resolvedSources) == 1 {
		if IsSrcRemoteFileURL(resolvedSources[0]) {
			return nil
		}
		path := filepath.Join(root, resolvedSources[0])
		fi, err := os.Lstat(path)
		if err != nil {
			return errors.Wrap(err, fmt.Sprintf("failed to get fileinfo for %v", path))
		}
		if fi.IsDir() {
			return nil
		}
	}

	totalFiles := 0
	for _, src := range resolvedSources {
		if IsSrcRemoteFileURL(src) {
			totalFiles++
			continue
		}
		src = filepath.Clean(src)
		files, err := RelativeFiles(src, root)
		if err != nil {
			return errors.Wrap(err, "failed to get relative files")
		}
		for _, file := range files {
			if ExcludeFile(file, root) {
				continue
			}
			totalFiles++
		}
	}
	if totalFiles == 0 {
		return errors.New("copy failed: no source files specified")
	}
	// If there are wildcards, and the destination is a file, there must be exactly one file to copy over,
	// Otherwise, return an error
	if !IsDestDir(dest) && totalFiles > 1 {
		return errors.New("when specifying multiple sources in a COPY command, destination must be a directory and end in '/'")
	}
	return nil
}

func IsSrcRemoteFileURL(rawurl string) bool {
	_, err := url.ParseRequestURI(rawurl)
	if err != nil {
		return false
	}
	_, err = http.Get(rawurl)
	return err == nil
}

func UpdateConfigEnv(newEnvs []instructions.KeyValuePair, config *v1.Config, replacementEnvs []string) error {
	for index, pair := range newEnvs {
		expandedKey, err := ResolveEnvironmentReplacement(pair.Key, replacementEnvs, false)
		if err != nil {
			return err
		}
		expandedValue, err := ResolveEnvironmentReplacement(pair.Value, replacementEnvs, false)
		if err != nil {
			return err
		}
		newEnvs[index] = instructions.KeyValuePair{
			Key:   expandedKey,
			Value: expandedValue,
		}
	}

	// First, convert config.Env array to []instruction.KeyValuePair
	var kvps []instructions.KeyValuePair
	for _, env := range config.Env {
		entry := strings.SplitN(env, "=", 2)
		kvps = append(kvps, instructions.KeyValuePair{
			Key:   entry[0],
			Value: entry[1],
		})
	}
	// Iterate through new environment variables, and replace existing keys
	// We can't use a map because we need to preserve the order of the environment variables
Loop:
	for _, newEnv := range newEnvs {
		for index, kvp := range kvps {
			// If key exists, replace the KeyValuePair...
			if kvp.Key == newEnv.Key {
				logrus.Debugf("Replacing environment variable %v with %v in config", kvp, newEnv)
				kvps[index] = newEnv
				continue Loop
			}
		}
		// ... Else, append it as a new env variable
		kvps = append(kvps, newEnv)
	}
	// Convert back to array and set in config
	envArray := []string{}
	for _, kvp := range kvps {
		entry := kvp.Key + "=" + kvp.Value
		envArray = append(envArray, entry)
	}
	config.Env = envArray
	return nil
}

func GetUserFromUsername(userStr string, groupStr string) (string, string, error) {
	// Lookup by username
	userObj, err := user.Lookup(userStr)
	if err != nil {
		if _, ok := err.(user.UnknownUserError); !ok {
			return "", "", err
		}
		// Lookup by id
		userObj, err = user.LookupId(userStr)
		if err != nil {
			return "", "", err
		}
	}

	// Same dance with groups
	var group *user.Group
	if groupStr != "" {
		group, err = user.LookupGroup(groupStr)
		if err != nil {
			if _, ok := err.(user.UnknownGroupError); !ok {
				return "", "", err
			}
			group, err = user.LookupGroupId(groupStr)
			if err != nil {
				return "", "", err
			}
		}
	}

	uid := userObj.Uid
	gid := ""
	if group != nil {
		gid = group.Gid
	}

	return uid, gid, nil
}

func Lookup(userStr string) (*user.User, error) {
	userObj, err := user.Lookup(userStr)
	if err != nil {
		if _, ok := err.(user.UnknownUserError); !ok {
			return nil, err
		}
		// Lookup by id
		userObj, err = user.LookupId(userStr)
		if err != nil {
			return nil, err
		}
	}
	return userObj, nil
}
<commit_msg>remove duplicate code<commit_after>/*
Copyright 2018 Google LLC

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

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

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

package util

import (
	"fmt"
	"net/http"
	"net/url"
	"os"
	"os/user"
	"path/filepath"
	"strings"

	"github.com/GoogleContainerTools/kaniko/pkg/constants"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/moby/buildkit/frontend/dockerfile/instructions"
	"github.com/moby/buildkit/frontend/dockerfile/parser"
	"github.com/moby/buildkit/frontend/dockerfile/shell"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// ResolveEnvironmentReplacementList resolves a list of values by calling resolveEnvironmentReplacement
func ResolveEnvironmentReplacementList(values, envs []string, isFilepath bool) ([]string, error) {
	var resolvedValues []string
	for _, value := range values {
		resolved, err := ResolveEnvironmentReplacement(value, envs, isFilepath)
		logrus.Debugf("Resolved %s to %s", value, resolved)
		if err != nil {
			return nil, err
		}
		resolvedValues = append(resolvedValues, resolved)
	}
	return resolvedValues, nil
}

// ResolveEnvironmentReplacement resolves replacing env variables in some text from envs
// It takes in a string representation of the command, the value to be resolved, and a list of envs (config.Env)
// Ex: value = $foo/newdir, envs = [foo=/foodir], then this should return /foodir/newdir
// The dockerfile/shell package handles processing env values
// It handles escape characters and supports expansion from the config.Env array
// Shlex handles some of the following use cases (these and more are tested in integration tests)
// ""a'b'c"" -> "a'b'c"
// "Rex\ The\ Dog \" -> "Rex The Dog"
// "a\"b" -> "a"b"
func ResolveEnvironmentReplacement(value string, envs []string, isFilepath bool) (string, error) {
	shlex := shell.NewLex(parser.DefaultEscapeToken)
	fp, err := shlex.ProcessWord(value, envs)
	// Check after replacement if value is a remote URL
	if !isFilepath || IsSrcRemoteFileURL(fp) {
		return fp, err
	}
	if err != nil {
		return "", err
	}
	fp = filepath.Clean(fp)
	if IsDestDir(value) && !IsDestDir(fp) {
		fp = fp + "/"
	}
	return fp, nil
}

func ResolveEnvAndWildcards(sd instructions.SourcesAndDest, buildcontext string, envs []string) ([]string, string, error) {
	// First, resolve any environment replacement
	resolvedEnvs, err := ResolveEnvironmentReplacementList(sd, envs, true)
	if err != nil {
		return nil, "", errors.Wrap(err, "failed to resolve environment")
	}
	if len(resolvedEnvs) == 0 {
		return nil, "", errors.New("resolved envs is empty")
	}
	dest := resolvedEnvs[len(resolvedEnvs)-1]
	// Resolve wildcards and get a list of resolved sources
	srcs, err := ResolveSources(resolvedEnvs[0:len(resolvedEnvs)-1], buildcontext)
	if err != nil {
		return nil, "", errors.Wrap(err, "failed to resolve sources")
	}
	err = IsSrcsValid(sd, srcs, buildcontext)
	return srcs, dest, err
}

// ContainsWildcards returns true if any entry in paths contains wildcards
func ContainsWildcards(paths []string) bool {
	for _, path := range paths {
		if strings.ContainsAny(path, "*?[") {
			return true
		}
	}
	return false
}

// ResolveSources resolves the given sources if the sources contains wildcards
// It returns a list of resolved sources
func ResolveSources(srcs []string, root string) ([]string, error) {
	// If sources contain wildcards, we first need to resolve them to actual paths
	if !ContainsWildcards(srcs) {
		return srcs, nil
	}
	logrus.Infof("Resolving srcs %v...", srcs)
	files, err := RelativeFiles("", root)
	if err != nil {
		return nil, err
	}
	resolved, err := matchSources(srcs, files)
	if err != nil {
		return nil, err
	}
	logrus.Debugf("Resolved sources to %v", resolved)
	return resolved, nil
}

// matchSources returns a list of sources that match wildcards
func matchSources(srcs, files []string) ([]string, error) {
	var matchedSources []string
	for _, src := range srcs {
		if IsSrcRemoteFileURL(src) {
			matchedSources = append(matchedSources, src)
			continue
		}
		src = filepath.Clean(src)
		for _, file := range files {
			if filepath.IsAbs(src) {
				file = filepath.Join(constants.RootDir, file)
			}
			matched, err := filepath.Match(src, file)
			if err != nil {
				return nil, err
			}
			if matched || src == file {
				matchedSources = append(matchedSources, file)
			}
		}
	}
	return matchedSources, nil
}

func IsDestDir(path string) bool {
	// try to stat the path
	fileInfo, err := os.Stat(path)
	if err != nil {
		// fall back to string-based determination
		return strings.HasSuffix(path, "/") || path == "."
	}
	// if it's a real path, check the fs response
	return fileInfo.IsDir()
}

// DestinationFilepath returns the destination filepath from the build context to the image filesystem
// If source is a file:
//	If dest is a dir, copy it to /dest/relpath
// 	If dest is a file, copy directly to dest
// If source is a dir:
//	Assume dest is also a dir, and copy to dest/
// If dest is not an absolute filepath, add /cwd to the beginning
func DestinationFilepath(src, dest, cwd string) (string, error) {
	_, srcFileName := filepath.Split(src)
	newDest := dest

	if IsDestDir(newDest) {
		newDest = filepath.Join(newDest, srcFileName)
	}

	if !filepath.IsAbs(newDest) {
		newDest = filepath.Join(cwd, newDest)
	}

	if len(srcFileName) <= 0 && !strings.HasSuffix(newDest, "/") {
		newDest += "/"
	}

	return newDest, nil
}

// URLDestinationFilepath gives the destination a file from a remote URL should be saved to
func URLDestinationFilepath(rawurl, dest, cwd string, envs []string) (string, error) {
	if !IsDestDir(dest) {
		if !filepath.IsAbs(dest) {
			return filepath.Join(cwd, dest), nil
		}
		return dest, nil
	}
	urlBase := filepath.Base(rawurl)
	urlBase, err := ResolveEnvironmentReplacement(urlBase, envs, true)
	if err != nil {
		return "", err
	}
	destPath := filepath.Join(dest, urlBase)

	if !filepath.IsAbs(dest) {
		destPath = filepath.Join(cwd, destPath)
	}
	return destPath, nil
}

func IsSrcsValid(srcsAndDest instructions.SourcesAndDest, resolvedSources []string, root string) error {
	srcs := srcsAndDest[:len(srcsAndDest)-1]
	dest := srcsAndDest[len(srcsAndDest)-1]

	if !ContainsWildcards(srcs) {
		totalSrcs := 0
		for _, src := range srcs {
			if ExcludeFile(src, root) {
				continue
			}
			totalSrcs++
		}
		if totalSrcs > 1 && !IsDestDir(dest) {
			return errors.New("when specifying multiple sources in a COPY command, destination must be a directory and end in '/'")
		}
	}

	// If there is only one source and it's a directory, docker assumes the dest is a directory
	if len(resolvedSources) == 1 {
		if IsSrcRemoteFileURL(resolvedSources[0]) {
			return nil
		}
		path := filepath.Join(root, resolvedSources[0])
		fi, err := os.Lstat(path)
		if err != nil {
			return errors.Wrap(err, fmt.Sprintf("failed to get fileinfo for %v", path))
		}
		if fi.IsDir() {
			return nil
		}
	}

	totalFiles := 0
	for _, src := range resolvedSources {
		if IsSrcRemoteFileURL(src) {
			totalFiles++
			continue
		}
		src = filepath.Clean(src)
		files, err := RelativeFiles(src, root)
		if err != nil {
			return errors.Wrap(err, "failed to get relative files")
		}
		for _, file := range files {
			if ExcludeFile(file, root) {
				continue
			}
			totalFiles++
		}
	}
	if totalFiles == 0 {
		return errors.New("copy failed: no source files specified")
	}
	// If there are wildcards, and the destination is a file, there must be exactly one file to copy over,
	// Otherwise, return an error
	if !IsDestDir(dest) && totalFiles > 1 {
		return errors.New("when specifying multiple sources in a COPY command, destination must be a directory and end in '/'")
	}
	return nil
}

func IsSrcRemoteFileURL(rawurl string) bool {
	_, err := url.ParseRequestURI(rawurl)
	if err != nil {
		return false
	}
	_, err = http.Get(rawurl)
	return err == nil
}

func UpdateConfigEnv(newEnvs []instructions.KeyValuePair, config *v1.Config, replacementEnvs []string) error {
	for index, pair := range newEnvs {
		expandedKey, err := ResolveEnvironmentReplacement(pair.Key, replacementEnvs, false)
		if err != nil {
			return err
		}
		expandedValue, err := ResolveEnvironmentReplacement(pair.Value, replacementEnvs, false)
		if err != nil {
			return err
		}
		newEnvs[index] = instructions.KeyValuePair{
			Key:   expandedKey,
			Value: expandedValue,
		}
	}

	// First, convert config.Env array to []instruction.KeyValuePair
	var kvps []instructions.KeyValuePair
	for _, env := range config.Env {
		entry := strings.SplitN(env, "=", 2)
		kvps = append(kvps, instructions.KeyValuePair{
			Key:   entry[0],
			Value: entry[1],
		})
	}
	// Iterate through new environment variables, and replace existing keys
	// We can't use a map because we need to preserve the order of the environment variables
Loop:
	for _, newEnv := range newEnvs {
		for index, kvp := range kvps {
			// If key exists, replace the KeyValuePair...
			if kvp.Key == newEnv.Key {
				logrus.Debugf("Replacing environment variable %v with %v in config", kvp, newEnv)
				kvps[index] = newEnv
				continue Loop
			}
		}
		// ... Else, append it as a new env variable
		kvps = append(kvps, newEnv)
	}
	// Convert back to array and set in config
	envArray := []string{}
	for _, kvp := range kvps {
		entry := kvp.Key + "=" + kvp.Value
		envArray = append(envArray, entry)
	}
	config.Env = envArray
	return nil
}

func GetUserFromUsername(userStr string, groupStr string) (string, string, error) {
	// Lookup by username
	userObj, err := Lookup(userStr)
	if err != nil {
		return "", "", err
	}

	// Same dance with groups
	var group *user.Group
	if groupStr != "" {
		group, err = user.LookupGroup(groupStr)
		if err != nil {
			if _, ok := err.(user.UnknownGroupError); !ok {
				return "", "", err
			}
			group, err = user.LookupGroupId(groupStr)
			if err != nil {
				return "", "", err
			}
		}
	}

	uid := userObj.Uid
	gid := ""
	if group != nil {
		gid = group.Gid
	}

	return uid, gid, nil
}

func Lookup(userStr string) (*user.User, error) {
	userObj, err := user.Lookup(userStr)
	if err != nil {
		if _, ok := err.(user.UnknownUserError); !ok {
			return nil, err
		}
		// Lookup by id
		userObj, err = user.LookupId(userStr)
		if err != nil {
			return nil, err
		}
	}
	return userObj, nil
}
<|endoftext|>


<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package validate

import (
	"errors"
	"fmt"
	"github.com/apache/incubator-servicecomb-service-center/pkg/util"
	"reflect"
	"regexp"
	"strconv"
	"unicode/utf8"
)

type ValidateRule struct {
	Min    int
	Max    int
	Length int
	Regexp *regexp.Regexp
}

func (v *ValidateRule) String() string {
	idx, arr := 0, [4]string{}
	if v.Min != 0 {
		arr[idx] = fmt.Sprintf("Min: %d", v.Min)
		idx++
	}
	if v.Max != 0 {
		arr[idx] = fmt.Sprintf("Max: %d", v.Max)
		idx++
	}
	if v.Length != 0 {
		arr[idx] = fmt.Sprintf("Length: %d", v.Length)
		idx++
	}
	if v.Regexp != nil {
		arr[idx] = fmt.Sprintf("Regexp: %s", v.Regexp)
		idx++
	}
	return "{" + util.StringJoin(arr[:idx], ",") + "}"
}

func (v *ValidateRule) Match(s interface{}) bool {
	var invalid bool = false
	sv := reflect.ValueOf(s)
	if v.Min > 0 && !invalid {
		switch sv.Kind() {
		case reflect.String:
			invalid = len(sv.String()) < v.Min
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			invalid = sv.Int() < int64(v.Min)
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
			invalid = sv.Uint() < uint64(v.Min)
		case reflect.Float32, reflect.Float64:
			invalid = sv.Float() < float64(v.Min)
		case reflect.Slice, reflect.Map, reflect.Array:
			invalid = sv.Len() < v.Min
		default:
			invalid = false
		}
	}
	if v.Max > 0 && !invalid {
		switch sv.Kind() {
		case reflect.String:
			invalid = utf8.RuneCountInString(sv.String()) > v.Max
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			invalid = sv.Int() > int64(v.Max)
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
			invalid = sv.Uint() > uint64(v.Max)
		case reflect.Float32, reflect.Float64:
			invalid = sv.Float() > float64(v.Max)
		case reflect.Slice, reflect.Map, reflect.Array:
			invalid = sv.Len() > v.Max
		default:
			invalid = false
		}
	}
	if v.Length > 0 && !invalid {
		switch sv.Kind() {
		case reflect.Slice, reflect.Map, reflect.Array:
			invalid = sv.Len() > v.Length
		case reflect.String:
			invalid = utf8.RuneCountInString(sv.String()) > v.Length
		default:
			invalid = false
		}
	}
	if v.Regexp != nil && !invalid {
		switch sv.Kind() {
		case reflect.Map:
			itemV := &ValidateRule{
				Regexp: v.Regexp,
			}
			keys := sv.MapKeys()
			for _, key := range keys {
				if !itemV.Match(key.Interface()) {
					invalid = true
					break
				}
				if !itemV.Match(sv.MapIndex(key).Interface()) {
					invalid = true
					break
				}
			}
		case reflect.Slice, reflect.Array:
			itemV := &ValidateRule{
				Regexp: v.Regexp,
			}
			for i, l := 0, sv.Len(); i < l; i++ {
				if !itemV.Match(sv.Index(i).Interface()) {
					invalid = true
					break
				}
			}
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
			reflect.Float32, reflect.Float64:
			var str string
			switch sv.Kind() {
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				str = strconv.FormatInt(sv.Int(), 10)
			case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
				str = strconv.FormatUint(sv.Uint(), 10)
			case reflect.Float32, reflect.Float64:
				str = strconv.FormatFloat(sv.Float(), 'f', -1, 64)
			}
			invalid = !v.Regexp.MatchString(str)
		default:
			str, ok := s.(string)
			if ok {
				invalid = !v.Regexp.MatchString(str)
			} else {
				invalid = true
			}
		}
	}
	return !invalid
}

type Validator struct {
	rules map[string](*ValidateRule)
	subs  map[string](*Validator)
}

func (v *Validator) GetRule(name string) *ValidateRule {
	if v.rules == nil {
		return nil
	}
	return v.rules[name]
}

func (v *Validator) AddRule(name string, rule *ValidateRule) {
	if v.rules == nil {
		v.rules = make(map[string](*ValidateRule))
	}
	v.rules[name] = rule
}

func (v *Validator) AddRules(in map[string](*ValidateRule)) {
	if len(in) == 0 {
		return
	}
	for key, value := range in {
		v.AddRule(key, value)
	}
}

func (v *Validator) AddSub(name string, s *Validator) {
	if v.subs == nil {
		v.subs = make(map[string](*Validator))
	}
	v.subs[name] = s
}

func (v *Validator) GetRules() map[string](*ValidateRule) {
	return v.rules
}

func (v *Validator) Validate(s interface{}) error {
	sv := reflect.ValueOf(s)
	if sv.Kind() == reflect.Ptr && !sv.IsNil() {
		return v.Validate(sv.Elem().Interface())
	}
	if sv.Kind() == reflect.Slice && !sv.IsNil() {
		for i, l := 0, sv.Len(); i < l; i++ {
			err := v.Validate(sv.Index(i).Interface())
			if err != nil {
				return err
			}
		}
		return nil
	}
	if sv.Kind() != reflect.Struct {
		return errors.New("not support validate type")
	}

	st := util.LoadStruct(s)
	for i, l := 0, sv.NumField(); i < l; i++ {
		field := sv.Field(i)
		fieldName := st.Fields[i].Name
		validator, ok := v.subs[fieldName]
		if ok {
			if (field.Kind() != reflect.Ptr && field.Kind() != reflect.Slice) || field.IsNil() {
				continue
			}
			err := validator.Validate(field.Interface())
			if err != nil {
				return err
			}
		}
		validate, ok := v.rules[fieldName]
		if ok {
			fi := field.Interface()
			if field.Kind() == reflect.Ptr && !field.IsNil() {
				fi = field.Elem().Interface()
				fsv := reflect.ValueOf(fi)
				if fsv.Kind() == reflect.Struct {
					err := v.Validate(fi)
					if err != nil {
						return err
					}
					continue
				}
			}
			// TODO null pointer如何校验
			if field.Kind() != reflect.Ptr && !validate.Match(fi) {
				if filter(fieldName) {
					return fmt.Errorf("The field '%s.%s' value does not match rule: %s", st.Type.Name(), fieldName, validate)
				}
				return fmt.Errorf("The field '%s.%s' value(%v) does not match rule: %s", st.Type.Name(), fieldName, fi, validate)
			}
		}
	}
	return nil
}

var (
	BLACK_LIST_FOR_PRINT = map[string]struct{}{
		"Properties": {},
	}
)

func filter(fieldName string) bool {
	if _, ok := BLACK_LIST_FOR_PRINT[fieldName]; ok {
		return true
	}
	return false
}
<commit_msg>[SCB-376] Optimize validate parameter log print (#317)<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package validate

import (
	"errors"
	"fmt"
	"github.com/apache/incubator-servicecomb-service-center/pkg/util"
	"reflect"
	"regexp"
	"strconv"
	"unicode/utf8"
)

type ValidateRule struct {
	Min    int
	Max    int
	Length int
	Regexp *regexp.Regexp
}

func (v *ValidateRule) String() string {
	idx, arr := 0, [4]string{}
	if v.Min != 0 {
		arr[idx] = fmt.Sprintf("Min: %d", v.Min)
		idx++
	}
	if v.Max != 0 {
		arr[idx] = fmt.Sprintf("Max: %d", v.Max)
		idx++
	}
	if v.Length != 0 {
		arr[idx] = fmt.Sprintf("Length: %d", v.Length)
		idx++
	}
	if v.Regexp != nil {
		arr[idx] = fmt.Sprintf("Regexp: %s", v.Regexp)
		idx++
	}
	return "{" + util.StringJoin(arr[:idx], ",") + "}"
}

func (v *ValidateRule) Match(s interface{}) (ok bool, invalidValue interface{}) {
	invalidValue = s
	var invalid bool
	sv := reflect.ValueOf(s)
	if v.Min > 0 && !invalid {
		switch sv.Kind() {
		case reflect.String:
			invalid = len(sv.String()) < v.Min
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			invalid = sv.Int() < int64(v.Min)
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
			invalid = sv.Uint() < uint64(v.Min)
		case reflect.Float32, reflect.Float64:
			invalid = sv.Float() < float64(v.Min)
		case reflect.Slice, reflect.Map, reflect.Array:
			invalid = sv.Len() < v.Min
		default:
			invalid = false
		}
	}
	if v.Max > 0 && !invalid {
		switch sv.Kind() {
		case reflect.String:
			invalid = utf8.RuneCountInString(sv.String()) > v.Max
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			invalid = sv.Int() > int64(v.Max)
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
			invalid = sv.Uint() > uint64(v.Max)
		case reflect.Float32, reflect.Float64:
			invalid = sv.Float() > float64(v.Max)
		case reflect.Slice, reflect.Map, reflect.Array:
			invalid = sv.Len() > v.Max
		default:
			invalid = false
		}
	}
	if v.Length > 0 && !invalid {
		switch sv.Kind() {
		case reflect.Slice, reflect.Map, reflect.Array:
			invalid = sv.Len() > v.Length
		case reflect.String:
			invalid = utf8.RuneCountInString(sv.String()) > v.Length
		default:
			invalid = false
		}
	}
	if v.Regexp != nil && !invalid {
		switch sv.Kind() {
		case reflect.Map:
			itemV := &ValidateRule{
				Regexp: v.Regexp,
			}
			keys := sv.MapKeys()
			for _, key := range keys {
				if ok, v := itemV.Match(key.Interface()); !ok {
					invalid = true
					invalidValue = v
					break
				}
				if ok, v := itemV.Match(sv.MapIndex(key).Interface()); !ok{
					invalid = true
					invalidValue = v
					break
				}
			}
		case reflect.Slice, reflect.Array:
			itemV := &ValidateRule{
				Regexp: v.Regexp,
			}
			for i, l := 0, sv.Len(); i < l; i++ {
				if ok, v := itemV.Match(sv.Index(i).Interface()) ; !ok{
					invalid = true
					invalidValue = v
					break
				}
			}
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
			reflect.Float32, reflect.Float64:
			var str string
			switch sv.Kind() {
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				str = strconv.FormatInt(sv.Int(), 10)
			case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
				str = strconv.FormatUint(sv.Uint(), 10)
			case reflect.Float32, reflect.Float64:
				str = strconv.FormatFloat(sv.Float(), 'f', -1, 64)
			}
			invalid = !v.Regexp.MatchString(str)
		default:
			str, ok := s.(string)
			if ok {
				invalid = !v.Regexp.MatchString(str)
			} else {
				invalid = true
			}
		}
	}
	ok = !invalid
	return
}

type Validator struct {
	rules map[string](*ValidateRule)
	subs  map[string](*Validator)
}

func (v *Validator) GetRule(name string) *ValidateRule {
	if v.rules == nil {
		return nil
	}
	return v.rules[name]
}

func (v *Validator) AddRule(name string, rule *ValidateRule) {
	if v.rules == nil {
		v.rules = make(map[string](*ValidateRule))
	}
	v.rules[name] = rule
}

func (v *Validator) AddRules(in map[string](*ValidateRule)) {
	if len(in) == 0 {
		return
	}
	for key, value := range in {
		v.AddRule(key, value)
	}
}

func (v *Validator) AddSub(name string, s *Validator) {
	if v.subs == nil {
		v.subs = make(map[string](*Validator))
	}
	v.subs[name] = s
}

func (v *Validator) GetRules() map[string](*ValidateRule) {
	return v.rules
}

func (v *Validator) Validate(s interface{}) error {
	sv := reflect.ValueOf(s)
	if sv.Kind() == reflect.Ptr && !sv.IsNil() {
		return v.Validate(sv.Elem().Interface())
	}
	if sv.Kind() == reflect.Slice && !sv.IsNil() {
		for i, l := 0, sv.Len(); i < l; i++ {
			err := v.Validate(sv.Index(i).Interface())
			if err != nil {
				return err
			}
		}
		return nil
	}
	if sv.Kind() != reflect.Struct {
		return errors.New("not support validate type")
	}

	st := util.LoadStruct(s)
	for i, l := 0, sv.NumField(); i < l; i++ {
		field := sv.Field(i)
		fieldName := st.Fields[i].Name
		validator, ok := v.subs[fieldName]
		if ok {
			if (field.Kind() != reflect.Ptr && field.Kind() != reflect.Slice) || field.IsNil() {
				continue
			}
			err := validator.Validate(field.Interface())
			if err != nil {
				return err
			}
		}
		validate, ok := v.rules[fieldName]
		if ok {
			fi := field.Interface()
			if field.Kind() == reflect.Ptr && !field.IsNil() {
				fi = field.Elem().Interface()
				fsv := reflect.ValueOf(fi)
				if fsv.Kind() == reflect.Struct {
					err := v.Validate(fi)
					if err != nil {
						return err
					}
					continue
				}
			}
			// TODO null pointer如何校验
			if field.Kind() != reflect.Ptr{
				ok, invalidValue := validate.Match(fi)
				if !ok {
					if filter(fieldName) {
						return fmt.Errorf("The field '%s.%s' value does not match rule: %s", st.Type.Name(), fieldName, validate)
					}
					return fmt.Errorf("The field '%s.%s' value(%v) does not match rule: %s", st.Type.Name(), fieldName, invalidValue, validate)
				}
			}
		}
	}
	return nil
}

var (
	BLACK_LIST_FOR_PRINT = map[string]struct{}{
		"Properties": {},
	}
)

func filter(fieldName string) bool {
	if _, ok := BLACK_LIST_FOR_PRINT[fieldName]; ok {
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"log"
	"io"
//	"io/ioutil"
	"net/http"
	"net/url"
	"os/exec"
	"flag"
	"reflect"
	"fmt"
	"github.com/kr/pty"
	"path"
	"strings"
	"time"
	"math/rand"
	"crypto/sha256"
	"bytes"
	"text/tabwriter"
)

func (docker *Docker) CmdUsage(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
	fmt.Fprintf(stdout, "Usage: docker COMMAND [arg...]\n\nCommands:\n")
	for _, cmd := range [][]interface{}{
		{"run", "Run a command in a container"},
		{"list", "Display a list of containers"},
		{"layers", "Display a list of layers"},
		{"download", "Download a layer from a remote location"},
		{"upload", "Upload a layer"},
		{"wait", "Wait for the state of a container to change"},
		{"stop", "Stop a running container"},
		{"logs", "Fetch the logs of a container"},
		{"export", "Extract changes to a container's filesystem into a new layer"},
		{"attach", "Attach to the standard inputs and outputs of a running container"},
		{"info", "Display system-wide information"},
	} {
		fmt.Fprintf(stdout, "    %-10.10s%s\n", cmd...)
	}
	return nil
}

func (docker *Docker) CmdLayers(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
	w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
	fmt.Fprintf(w, "ID\tNAME\tSIZE\tADDED\n")
	for _, layer := range docker.layers {
		fmt.Fprintf(w, "%s\t%s\t%.1fM\t%s ago\n", layer.Id, layer.Name, float32(layer.Size) / 1024 / 1024, humanDuration(time.Now().Sub(layer.Added)))
	}
	w.Flush()
	return nil
}

func (docker *Docker) CmdUpload(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
	layer := Layer{Id: randomId(), Name: args[0], Added: time.Now(), Size: uint(rand.Int31n(142 * 1024 * 1024))}
	docker.layers = append(docker.layers, layer)
	fmt.Fprintf(stdout, "New layer: %s %s %.1fM\n", layer.Id, layer.Name, float32(layer.Size) / 1024 / 1024)
	return nil
}

func (docker *Docker) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
	if len(docker.layers) == 0 {
		return errors.New("No layers")
	}
	container := Container{
		Id:	randomId(),
		Cmd:	args[0],
		Args:	args[1:],
		Created: time.Now(),
		Layers:	docker.layers[:1],
		FilesChanged: uint(rand.Int31n(42)),
		BytesChanged: uint(rand.Int31n(24 * 1024 * 1024)),
	}
	docker.containers = append(docker.containers, container)
	cmd := exec.Command(args[0], args[1:]...)
	cmd_stdin, cmd_stdout, err := startCommand(cmd, false)
	if err != nil {
		return err
	}
	copy_out := Go(func() error {
		_, err := io.Copy(stdout, cmd_stdout)
		return err
	})
	copy_in := Go(func() error {
		//_, err := io.Copy(cmd_stdin, stdin)
		cmd_stdin.Close()
		stdin.Close()
		//return err
		return nil
	})
	if err := cmd.Wait(); err != nil {
		return err
	}
	if err := <-copy_in; err != nil {
		return err
	}
	if err := <-copy_out; err != nil {
		return err
	}
	return nil
}

func startCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadCloser, error) {
	if interactive {
		term, err := pty.Start(cmd)
		if err != nil {
			return nil, nil, err
		}
		return term, term, nil
	}
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, nil, err
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, nil, err
	}
	if err := cmd.Start(); err != nil {
		return nil, nil, err
	}
	return stdin, stdout, nil
}

func (docker *Docker) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
	var longestCol int
	for _, container := range docker.containers {
		if l := len(container.CmdString()); l > longestCol {
			longestCol = l
		}
	}
	if longestCol > 50 {
		longestCol = 50
	} else if longestCol < 5 {
		longestCol = 8
	}
	tpl := "%-16s   %-*.*s   %-6s   %-25s   %10s   %-s\n"
	fmt.Fprintf(stdout, tpl, "ID", longestCol, longestCol, "CMD", "STATUS", "CREATED", "CHANGES", "LAYERS")
	for _, container := range docker.containers {
		var layers []string
		for _, layer := range container.Layers {
			layers = append(layers, layer.Name)
		}
		fmt.Fprintf(stdout, tpl,
			/* ID */	container.Id,
			/* CMD */	longestCol, longestCol, container.CmdString(),
			/* STATUS */	"?",
			/* CREATED */	humanDuration(time.Now().Sub(container.Created)) + " ago",
			/* CHANGES */	fmt.Sprintf("%.1fM", float32(container.BytesChanged) / 1024 / 1024),
			/* LAYERS */	strings.Join(layers, ", "))
	}
	return nil
}

func main() {
	rand.Seed(time.Now().UTC().UnixNano())
	flag.Parse()
	if err := http.ListenAndServe(":4242", new(Docker)); err != nil {
		log.Fatal(err)
	}
}

func (docker *Docker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	cmd, args := URLToCall(r.URL)
	if cmd == "" {
		docker.CmdUsage(r.Body, w, "")
		return
	}
	method := docker.getMethod(cmd)
	if method == nil {
		docker.CmdUsage(r.Body, w, cmd)
	} else {
		err := method(r.Body, w, args...)
		if err != nil {
			fmt.Fprintf(w, "Error: %s\n", err)
		}
	}
}

func (docker *Docker) getMethod(name string) Cmd {
	methodName := "Cmd"+strings.ToUpper(name[:1])+strings.ToLower(name[1:])
	method, exists := reflect.TypeOf(docker).MethodByName(methodName)
	if !exists {
		return nil
	}
	return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
		ret := method.Func.CallSlice([]reflect.Value{
			reflect.ValueOf(docker),
			reflect.ValueOf(stdin),
			reflect.ValueOf(stdout),
			reflect.ValueOf(args),
		})[0].Interface()
		if ret == nil {
			return nil
		}
		return ret.(error)
	}
}

func Go(f func() error) chan error {
	ch := make(chan error)
	go func() {
		ch <- f()
	}()
	return ch
}

type Docker struct {
	layers		[]Layer
	containers	[]Container
}

type Layer struct {
	Id	string
	Name	string
	Added	time.Time
	Size	uint
}

type Container struct {
	Id	string
	Cmd	string
	Args	[]string
	Layers	[]Layer
	Created	time.Time
	FilesChanged uint
	BytesChanged uint
}

func (c *Container) CmdString() string {
	return strings.Join(append([]string{c.Cmd}, c.Args...), " ")
}

type Cmd func(io.ReadCloser, io.Writer, ...string) error
type CmdMethod func(*Docker, io.ReadCloser, io.Writer, ...string) error

// Use this key to encode an RPC call into an URL,
// eg. domain.tld/path/to/method?q=get_user&q=gordon
const ARG_URL_KEY = "q"

func URLToCall(u *url.URL) (method string, args []string) {
	return path.Base(u.Path), u.Query()[ARG_URL_KEY]
}

func randomBytes() io.Reader {
	return bytes.NewBuffer([]byte(fmt.Sprintf("%x", rand.Int())))
}

func ComputeId(content io.Reader) (string, error) {
	h := sha256.New()
	if _, err := io.Copy(h, content); err != nil {
		return "", err
	}
	return fmt.Sprintf("%x", h.Sum(nil)[:8]), nil
}

func randomId() string {
	id, _ := ComputeId(randomBytes()) // can't fail
	return id
}

func humanDuration(d time.Duration) string {
	if seconds := int(d.Seconds()); seconds < 1 {
		return "Less than a second"
	} else if seconds < 60 {
		return fmt.Sprintf("%d seconds", seconds)
	} else if minutes := int(d.Minutes()); minutes == 1 {
		return "About a minute"
	} else if minutes < 60 {
		return fmt.Sprintf("%d minutes", minutes)
	} else if hours := int(d.Hours()); hours  == 1{
		return "About an hour"
	} else if hours < 48 {
		return fmt.Sprintf("%d hours", hours)
	} else if hours < 24 * 7 * 2 {
		return fmt.Sprintf("%d days", hours / 24)
	} else if hours < 24 * 30 * 3 {
		return fmt.Sprintf("%d weeks", hours / 24 / 7)
	} else if hours < 24 * 365 * 2 {
		return fmt.Sprintf("%d months", hours / 24 / 30)
	}
	return fmt.Sprintf("%d years", d.Hours() / 24 / 365)
}
<commit_msg>dockerd: log commands<commit_after>package main

import (
	"errors"
	"log"
	"io"
//	"io/ioutil"
	"net/http"
	"net/url"
	"os/exec"
	"flag"
	"reflect"
	"fmt"
	"github.com/kr/pty"
	"path"
	"strings"
	"time"
	"math/rand"
	"crypto/sha256"
	"bytes"
	"text/tabwriter"
)

func (docker *Docker) CmdUsage(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
	fmt.Fprintf(stdout, "Usage: docker COMMAND [arg...]\n\nCommands:\n")
	for _, cmd := range [][]interface{}{
		{"run", "Run a command in a container"},
		{"list", "Display a list of containers"},
		{"layers", "Display a list of layers"},
		{"download", "Download a layer from a remote location"},
		{"upload", "Upload a layer"},
		{"wait", "Wait for the state of a container to change"},
		{"stop", "Stop a running container"},
		{"logs", "Fetch the logs of a container"},
		{"export", "Extract changes to a container's filesystem into a new layer"},
		{"attach", "Attach to the standard inputs and outputs of a running container"},
		{"info", "Display system-wide information"},
	} {
		fmt.Fprintf(stdout, "    %-10.10s%s\n", cmd...)
	}
	return nil
}

func (docker *Docker) CmdLayers(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
	w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
	fmt.Fprintf(w, "ID\tNAME\tSIZE\tADDED\n")
	for _, layer := range docker.layers {
		fmt.Fprintf(w, "%s\t%s\t%.1fM\t%s ago\n", layer.Id, layer.Name, float32(layer.Size) / 1024 / 1024, humanDuration(time.Now().Sub(layer.Added)))
	}
	w.Flush()
	return nil
}

func (docker *Docker) CmdUpload(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
	layer := Layer{Id: randomId(), Name: args[0], Added: time.Now(), Size: uint(rand.Int31n(142 * 1024 * 1024))}
	docker.layers = append(docker.layers, layer)
	fmt.Fprintf(stdout, "New layer: %s %s %.1fM\n", layer.Id, layer.Name, float32(layer.Size) / 1024 / 1024)
	return nil
}

func (docker *Docker) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
	if len(docker.layers) == 0 {
		return errors.New("No layers")
	}
	container := Container{
		Id:	randomId(),
		Cmd:	args[0],
		Args:	args[1:],
		Created: time.Now(),
		Layers:	docker.layers[:1],
		FilesChanged: uint(rand.Int31n(42)),
		BytesChanged: uint(rand.Int31n(24 * 1024 * 1024)),
	}
	docker.containers = append(docker.containers, container)
	cmd := exec.Command(args[0], args[1:]...)
	cmd_stdin, cmd_stdout, err := startCommand(cmd, false)
	if err != nil {
		return err
	}
	copy_out := Go(func() error {
		_, err := io.Copy(stdout, cmd_stdout)
		return err
	})
	copy_in := Go(func() error {
		//_, err := io.Copy(cmd_stdin, stdin)
		cmd_stdin.Close()
		stdin.Close()
		//return err
		return nil
	})
	if err := cmd.Wait(); err != nil {
		return err
	}
	if err := <-copy_in; err != nil {
		return err
	}
	if err := <-copy_out; err != nil {
		return err
	}
	return nil
}

func startCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadCloser, error) {
	if interactive {
		term, err := pty.Start(cmd)
		if err != nil {
			return nil, nil, err
		}
		return term, term, nil
	}
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, nil, err
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, nil, err
	}
	if err := cmd.Start(); err != nil {
		return nil, nil, err
	}
	return stdin, stdout, nil
}

func (docker *Docker) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
	var longestCol int
	for _, container := range docker.containers {
		if l := len(container.CmdString()); l > longestCol {
			longestCol = l
		}
	}
	if longestCol > 50 {
		longestCol = 50
	} else if longestCol < 5 {
		longestCol = 8
	}
	tpl := "%-16s   %-*.*s   %-6s   %-25s   %10s   %-s\n"
	fmt.Fprintf(stdout, tpl, "ID", longestCol, longestCol, "CMD", "STATUS", "CREATED", "CHANGES", "LAYERS")
	for _, container := range docker.containers {
		var layers []string
		for _, layer := range container.Layers {
			layers = append(layers, layer.Name)
		}
		fmt.Fprintf(stdout, tpl,
			/* ID */	container.Id,
			/* CMD */	longestCol, longestCol, container.CmdString(),
			/* STATUS */	"?",
			/* CREATED */	humanDuration(time.Now().Sub(container.Created)) + " ago",
			/* CHANGES */	fmt.Sprintf("%.1fM", float32(container.BytesChanged) / 1024 / 1024),
			/* LAYERS */	strings.Join(layers, ", "))
	}
	return nil
}

func main() {
	rand.Seed(time.Now().UTC().UnixNano())
	flag.Parse()
	if err := http.ListenAndServe(":4242", new(Docker)); err != nil {
		log.Fatal(err)
	}
}

func (docker *Docker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	cmd, args := URLToCall(r.URL)
	log.Printf("%s\n", strings.Join(append(append([]string{"docker"}, cmd), args...), " "))
	if cmd == "" {
		docker.CmdUsage(r.Body, w, "")
		return
	}
	method := docker.getMethod(cmd)
	if method == nil {
		docker.CmdUsage(r.Body, w, cmd)
	} else {
		err := method(r.Body, w, args...)
		if err != nil {
			fmt.Fprintf(w, "Error: %s\n", err)
		}
	}
}

func (docker *Docker) getMethod(name string) Cmd {
	methodName := "Cmd"+strings.ToUpper(name[:1])+strings.ToLower(name[1:])
	method, exists := reflect.TypeOf(docker).MethodByName(methodName)
	if !exists {
		return nil
	}
	return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
		ret := method.Func.CallSlice([]reflect.Value{
			reflect.ValueOf(docker),
			reflect.ValueOf(stdin),
			reflect.ValueOf(stdout),
			reflect.ValueOf(args),
		})[0].Interface()
		if ret == nil {
			return nil
		}
		return ret.(error)
	}
}

func Go(f func() error) chan error {
	ch := make(chan error)
	go func() {
		ch <- f()
	}()
	return ch
}

type Docker struct {
	layers		[]Layer
	containers	[]Container
}

type Layer struct {
	Id	string
	Name	string
	Added	time.Time
	Size	uint
}

type Container struct {
	Id	string
	Cmd	string
	Args	[]string
	Layers	[]Layer
	Created	time.Time
	FilesChanged uint
	BytesChanged uint
}

func (c *Container) CmdString() string {
	return strings.Join(append([]string{c.Cmd}, c.Args...), " ")
}

type Cmd func(io.ReadCloser, io.Writer, ...string) error
type CmdMethod func(*Docker, io.ReadCloser, io.Writer, ...string) error

// Use this key to encode an RPC call into an URL,
// eg. domain.tld/path/to/method?q=get_user&q=gordon
const ARG_URL_KEY = "q"

func URLToCall(u *url.URL) (method string, args []string) {
	return path.Base(u.Path), u.Query()[ARG_URL_KEY]
}

func randomBytes() io.Reader {
	return bytes.NewBuffer([]byte(fmt.Sprintf("%x", rand.Int())))
}

func ComputeId(content io.Reader) (string, error) {
	h := sha256.New()
	if _, err := io.Copy(h, content); err != nil {
		return "", err
	}
	return fmt.Sprintf("%x", h.Sum(nil)[:8]), nil
}

func randomId() string {
	id, _ := ComputeId(randomBytes()) // can't fail
	return id
}

func humanDuration(d time.Duration) string {
	if seconds := int(d.Seconds()); seconds < 1 {
		return "Less than a second"
	} else if seconds < 60 {
		return fmt.Sprintf("%d seconds", seconds)
	} else if minutes := int(d.Minutes()); minutes == 1 {
		return "About a minute"
	} else if minutes < 60 {
		return fmt.Sprintf("%d minutes", minutes)
	} else if hours := int(d.Hours()); hours  == 1{
		return "About an hour"
	} else if hours < 48 {
		return fmt.Sprintf("%d hours", hours)
	} else if hours < 24 * 7 * 2 {
		return fmt.Sprintf("%d days", hours / 24)
	} else if hours < 24 * 30 * 3 {
		return fmt.Sprintf("%d weeks", hours / 24 / 7)
	} else if hours < 24 * 365 * 2 {
		return fmt.Sprintf("%d months", hours / 24 / 30)
	}
	return fmt.Sprintf("%d years", d.Hours() / 24 / 365)
}
<|endoftext|>


<commit_before><commit_msg>cmd/webhook: add -config flag<commit_after><|endoftext|>


<commit_before>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package collector

import (
	"fmt"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/app"
	"github.com/globocom/tsuru/log"
	"github.com/globocom/tsuru/provision"
	stdlog "log"
	"log/syslog"
	"os"
	"time"
)

func collect(ticker <-chan time.Time) {
	for _ = range ticker {
		log.Print("Collecting status from provisioner")
		units, err := app.Provisioner.CollectStatus()
		if err != nil {
			log.Printf("Failed to collect status within the provisioner: %s.", err)
			continue
		}
		update(units)
	}
}

func fatal(err error) {
	fmt.Fprintln(os.Stderr, err)
	log.Fatal(err)
}

func Run(flags map[string]interface{}) {
	logger, err := syslog.NewLogger(syslog.LOG_INFO, stdlog.LstdFlags)
	if err != nil {
		stdlog.Fatal(err)
	}
	log.SetLogger(logger)
	configFile, ok := flags["config"].(string)
	if !ok {
		configFile = "/etc/tsuru/tsuru.conf"
	}
	dry, ok := flags["dry"].(bool)
	if !ok {
		dry = false
	}
	err = config.ReadAndWatchConfigFile(configFile)
	if err != nil {
		fatal(err)
	}
	connString, err := config.GetString("database:url")
	if err != nil {
		fatal(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		fatal(err)
	}
	fmt.Printf("Using the database %q from the server %q.\n\n", dbName, connString)
	if !dry {
		provisioner, err := config.GetString("provisioner")
		if err != nil {
			fmt.Printf("Warning: %q didn't declare a provisioner, using default provisioner.\n", configFile)
			provisioner = "juju"
		}
		app.Provisioner, err = provision.Get(provisioner)
		if err != nil {
			fatal(err)
		}
		fmt.Printf("Using %q provisioner.\n\n", provisioner)

		ticker := time.Tick(time.Minute)
		fmt.Println("tsuru collector agent started...")
		collect(ticker)
	}
}
<commit_msg>collector/runner: using log.Init.<commit_after>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package collector

import (
	"fmt"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/app"
	"github.com/globocom/tsuru/log"
	"github.com/globocom/tsuru/provision"
	"os"
	"time"
)

func collect(ticker <-chan time.Time) {
	for _ = range ticker {
		log.Print("Collecting status from provisioner")
		units, err := app.Provisioner.CollectStatus()
		if err != nil {
			log.Printf("Failed to collect status within the provisioner: %s.", err)
			continue
		}
		update(units)
	}
}

func fatal(err error) {
	fmt.Fprintln(os.Stderr, err)
	log.Fatal(err)
}

func Run(flags map[string]interface{}) {
	configFile, ok := flags["config"].(string)
	if !ok {
		configFile = "/etc/tsuru/tsuru.conf"
	}
	log.Init()
	dry, ok := flags["dry"].(bool)
	if !ok {
		dry = false
	}
	err := config.ReadAndWatchConfigFile(configFile)
	if err != nil {
		fatal(err)
	}
	connString, err := config.GetString("database:url")
	if err != nil {
		fatal(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		fatal(err)
	}
	fmt.Printf("Using the database %q from the server %q.\n\n", dbName, connString)
	if !dry {
		provisioner, err := config.GetString("provisioner")
		if err != nil {
			fmt.Printf("Warning: %q didn't declare a provisioner, using default provisioner.\n", configFile)
			provisioner = "juju"
		}
		app.Provisioner, err = provision.Get(provisioner)
		if err != nil {
			fatal(err)
		}
		fmt.Printf("Using %q provisioner.\n\n", provisioner)

		ticker := time.Tick(time.Minute)
		fmt.Println("tsuru collector agent started...")
		collect(ticker)
	}
}
<|endoftext|>


<commit_before>package command

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type CurrencyQuery struct {
	XMLName xml.Name `xml:"query"`
	Rates   []struct {
		ID   string  `xml:"id,attr"`
		Rate float64 `xml:"Rate"`
	} `xml:"results>rate"`
}

type rateCache struct {
	rate      float64
	cached_at time.Time
}

type currencyRatio struct {
	rate float64
	unit string
}

var (
	command_re *regexp.Regexp       = regexp.MustCompile("(\\d+(?:\\.\\d+)?)\\s*([^\\s=]+)(\\s*=\\s*[?]?\\s*([^\\s=]+))?")
	rate_cache map[string]rateCache = map[string]rateCache{}
	alias_map map[string]string = map[string]string {
		"사대강": "4대강",
		"K달탐사": "k달탐사",
		"케이달탐사": "k달탐사",
		"K알파고": "k알파고",
		"케이알파고": "k알파고",
	}
	special_currency_map map[string]currencyRatio = map[string]currencyRatio{
		"개리엇": currencyRatio{ 30000000, "USD" },
		"4대강" : currencyRatio{ 22000000000000, "KRW" },
		"팝픽" : currencyRatio{ 344, "KRW" },
		"k달탐사" : currencyRatio{ 10000000000, "KRW" },
		"k알파고" : currencyRatio{ 30000000000, "KRW" },
		"최저시급": currencyRatio{ 6030, "KRW" },
	}
)

func applyAlias(unit *string) {
	if alias, ok := alias_map[*unit]; ok {
		*unit = alias
	}
}

func CurrencyCommand(req Request) *Response {
	ret := new(Response)
	ret.ResponseType = ephemeral

	matched := command_re.FindStringSubmatch(strings.TrimSpace(req.Text))
	if matched == nil {
		ret.Text = fmt.Sprintf("Format error. You typed \"%s\"", req.Text)
		return ret
	}

	original_value, _ := strconv.ParseFloat(matched[1], 64)
	if matched[3] == "" {
		matched[4] = "KRW"
	}

	src_value := original_value
	// make upper...
	matched[2] = strings.ToUpper(matched[2])
	applyAlias(&matched[2])
	matched[4] = strings.ToUpper(matched[4])
	applyAlias(&matched[4])

	src_unit := matched[2]
	target_unit := matched[4]
	if info, ok := special_currency_map[src_unit]; ok {
		src_value = src_value * info.rate
		src_unit = info.unit
	}
	if info, ok := special_currency_map[target_unit]; ok {
		src_value = src_value / info.rate
		target_unit = info.unit
	}
	key := fmt.Sprintf("%s%s", src_unit, target_unit)

	rate := 0.0
	if cached, ok := rate_cache[key]; ok {
		if time.Now().Sub(cached.cached_at).Minutes() < 10 {
			rate = cached.rate
		}
	}

	if rate == 0.0 {
		url := fmt.Sprintf("http://query.yahooapis.com/v1/public/yql?q=%s&env=%s",
			strings.Replace(
				url.QueryEscape(
					fmt.Sprintf("select * from yahoo.finance.xchange where pair in (\"%s\")", key)),
				"+", "%20", -1),
			url.QueryEscape("store://datatables.org/alltableswithkeys"))

		resp, err := http.Get(url)
		if err != nil {
			ret.Text = fmt.Sprintf("YQL error : %q", err)
			return ret
		}

		var query CurrencyQuery
		buf := new(bytes.Buffer)
		buf.ReadFrom(resp.Body)
		b := buf.Bytes()
		err = xml.Unmarshal(b, &query)
		if err != nil {
			ret.Text = fmt.Sprintf("Result parsing error : %q", err)
			return ret
		}

		for _, v := range query.Rates {
			if v.ID == key {
				rate = v.Rate
				break
			}
		}

		if rate != 0.0 {
			rate_cache[key] = rateCache{
				rate:      rate,
				cached_at: time.Now(),
			}
		} else {
			ret.Text = "Unknown Error"
			return ret
		}
	}

	ret.ResponseType = deffered_in_channel

	ret.Attachments = []Attachment{
		Attachment{
			Color: Color{r: 33, g: 108, b: 42},
			Text:  fmt.Sprintf("%.2f %s = %.2f %s", original_value, matched[2], src_value*rate, matched[4]),
		},
	}

	return ret
}
<commit_msg>add tmax_hacking<commit_after>package command

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type CurrencyQuery struct {
	XMLName xml.Name `xml:"query"`
	Rates   []struct {
		ID   string  `xml:"id,attr"`
		Rate float64 `xml:"Rate"`
	} `xml:"results>rate"`
}

type rateCache struct {
	rate      float64
	cached_at time.Time
}

type currencyRatio struct {
	rate float64
	unit string
}

var (
	command_re *regexp.Regexp       = regexp.MustCompile("(\\d+(?:\\.\\d+)?)\\s*([^\\s=]+)(\\s*=\\s*[?]?\\s*([^\\s=]+))?")
	rate_cache map[string]rateCache = map[string]rateCache{}
	alias_map map[string]string = map[string]string {
		"사대강": "4대강",
		"K달탐사": "k달탐사",
		"케이달탐사": "k달탐사",
		"K알파고": "k알파고",
		"케이알파고": "k알파고",
		"tmax해킹": "티맥스해킹",
		"TMAX해킹": "티맥스해킹",
	}
	special_currency_map map[string]currencyRatio = map[string]currencyRatio{
		"개리엇": currencyRatio{ 30000000, "USD" },
		"4대강" : currencyRatio{ 22000000000000, "KRW" },
		"팝픽" : currencyRatio{ 344, "KRW" },
		"k달탐사" : currencyRatio{ 10000000000, "KRW" },
		"k알파고" : currencyRatio{ 30000000000, "KRW" },
		"최저시급": currencyRatio{ 6030, "KRW" },
		"티맥스해킹": currencyRatio{ 100000000, "KRW" },
	}
)

func applyAlias(unit *string) {
	if alias, ok := alias_map[*unit]; ok {
		*unit = alias
	}
}

func CurrencyCommand(req Request) *Response {
	ret := new(Response)
	ret.ResponseType = ephemeral

	matched := command_re.FindStringSubmatch(strings.TrimSpace(req.Text))
	if matched == nil {
		ret.Text = fmt.Sprintf("Format error. You typed \"%s\"", req.Text)
		return ret
	}

	original_value, _ := strconv.ParseFloat(matched[1], 64)
	if matched[3] == "" {
		matched[4] = "KRW"
	}

	src_value := original_value
	// make upper...
	matched[2] = strings.ToUpper(matched[2])
	applyAlias(&matched[2])
	matched[4] = strings.ToUpper(matched[4])
	applyAlias(&matched[4])

	src_unit := matched[2]
	target_unit := matched[4]
	if info, ok := special_currency_map[src_unit]; ok {
		src_value = src_value * info.rate
		src_unit = info.unit
	}
	if info, ok := special_currency_map[target_unit]; ok {
		src_value = src_value / info.rate
		target_unit = info.unit
	}
	key := fmt.Sprintf("%s%s", src_unit, target_unit)

	rate := 0.0
	if cached, ok := rate_cache[key]; ok {
		if time.Now().Sub(cached.cached_at).Minutes() < 10 {
			rate = cached.rate
		}
	}

	if rate == 0.0 {
		url := fmt.Sprintf("http://query.yahooapis.com/v1/public/yql?q=%s&env=%s",
			strings.Replace(
				url.QueryEscape(
					fmt.Sprintf("select * from yahoo.finance.xchange where pair in (\"%s\")", key)),
				"+", "%20", -1),
			url.QueryEscape("store://datatables.org/alltableswithkeys"))

		resp, err := http.Get(url)
		if err != nil {
			ret.Text = fmt.Sprintf("YQL error : %q", err)
			return ret
		}

		var query CurrencyQuery
		buf := new(bytes.Buffer)
		buf.ReadFrom(resp.Body)
		b := buf.Bytes()
		err = xml.Unmarshal(b, &query)
		if err != nil {
			ret.Text = fmt.Sprintf("Result parsing error : %q", err)
			return ret
		}

		for _, v := range query.Rates {
			if v.ID == key {
				rate = v.Rate
				break
			}
		}

		if rate != 0.0 {
			rate_cache[key] = rateCache{
				rate:      rate,
				cached_at: time.Now(),
			}
		} else {
			ret.Text = "Unknown Error"
			return ret
		}
	}

	ret.ResponseType = deffered_in_channel

	ret.Attachments = []Attachment{
		Attachment{
			Color: Color{r: 33, g: 108, b: 42},
			Text:  fmt.Sprintf("%.2f %s = %.2f %s", original_value, matched[2], src_value*rate, matched[4]),
		},
	}

	return ret
}
<|endoftext|>


<commit_before>package content

import (
	"encoding/json"
	"time"

	"github.com/Financial-Times/go-logger"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	tid "github.com/Financial-Times/transactionid-utils-go"
	"github.com/jmcvetta/neoism"
)

var contentTypesWithNoBody = map[string]bool{
	"Content":        true,
	"Article":        true,
	"Video":          true,
	"Graphic":        true,
	"Audio":          true,
	"ContentPackage": true,
}

// CypherDriver - CypherDriver
type service struct {
	conn neoutils.NeoConnection
}

//NewCypherDriver instantiate driver
func NewCypherContentService(cypherRunner neoutils.NeoConnection) service {
	return service{cypherRunner}
}

//Initialise ensure constraint on content uuid
func (cd service) Initialise() error {

	return cd.conn.EnsureConstraints(map[string]string{
		"Content": "uuid"})
}

// Check - Feeds into the Healthcheck and checks whether we can connect to Neo and that the datastore isn't empty and
// also checks if we are connected to leader/writable node
func (cd service) Check() error {
	writableErr := neoutils.CheckWritable(cd.conn)
	if writableErr != nil {
		return writableErr
	}

	return neoutils.Check(cd.conn)
}

// Read - reads a content given a UUID
func (cd service) Read(uuid string, transId string) (interface{}, bool, error) {
	var results []struct {
		content
	}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Content {uuid:{uuid}})
			OPTIONAL MATCH (sp:Thing)-[rel1:IS_CURATED_FOR]->(n)
			OPTIONAL MATCH (n)-[rel2:CONTAINS]->(cp:Thing)
			WITH n,sp,cp
			return  n.uuid as uuid,
				n.title as title,
				n.publishedDate as publishedDate,
				sp.uuid as storyPackage,
				cp.uuid as contentPackage`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
		Result: &results,
	}

	err := cd.conn.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return content{}, false, err
	}

	if len(results) == 0 {
		return content{}, false, nil
	}

	result := results[0]

	contentItem := content{
		UUID:           result.UUID,
		Title:          result.Title,
		PublishedDate:  result.PublishedDate,
		StoryPackage:   result.StoryPackage,
		ContentPackage: result.ContentPackage,
	}
	return contentItem, true, nil
}

//Write - Writes a content node
func (cd service) Write(thing interface{}, transId string) error {
	c := thing.(content)

	// Letting through only articles (which have body), live blogs, content packages, graphics, videos and audios (which don't have a body)
	if c.Body == "" && !contentTypesWithNoBody[c.Type] {
		logger.WithField(tid.TransactionIDKey, transId).
			Infof("There is no body with this content item therefore assuming is it not an Article: %v", c.UUID)
		return nil
	}

	params := map[string]interface{}{
		"uuid": c.UUID,
	}

	if c.Title != "" {
		params["title"] = c.Title
		params["prefLabel"] = c.Title
	}

	if c.PublishedDate != "" {
		params["publishedDate"] = c.PublishedDate
		datetimeEpoch, err := time.Parse(time.RFC3339, c.PublishedDate)

		if err != nil {
			return err
		}

		params["publishedDateEpoch"] = datetimeEpoch.Unix()
	}

	deleteEntityRelationshipsQuery := &neoism.CypherQuery{
		Statement: `MATCH (t:Thing {uuid:{uuid}})
				OPTIONAL MATCH (c:Thing)-[rel1:IS_CURATED_FOR]->(t)
				OPTIONAL MATCH (cp:Thing)<-[rel2:CONTAINS]-(t)
				DELETE rel1, rel2`,
		Parameters: map[string]interface{}{
			"uuid": c.UUID,
		},
	}

	queries := []*neoism.CypherQuery{deleteEntityRelationshipsQuery}

	labels := `:Content`

	if c.StoryPackage != "" {
		logger.WithField(tid.TransactionIDKey, transId).
			Infof("There is a story package with uuid=%v attached to Article with uuid=%v", c.StoryPackage, c.UUID)
		addStoryPackageRelationQuery := addStoryPackageRelationQuery(c.UUID, c.StoryPackage)
		queries = append(queries, addStoryPackageRelationQuery)
	}

	if c.ContentPackage != "" {
		logger.WithField(tid.TransactionIDKey, transId).
			Infof("There is a content package with uuid=%v attached to Article with uuid=%v", c.ContentPackage, c.UUID)
		addContentPackageRelationQuery := addContentPackageRelationQuery(c.UUID, c.ContentPackage)
		queries = append(queries, addContentPackageRelationQuery)
		labels = labels + `:ContentPackage`
	}

	statement := `MERGE (n:Thing {uuid: {uuid}})
		      set n={allprops}
		      set n ` + labels

	writeContentQuery := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"uuid":     c.UUID,
			"allprops": params,
		},
	}

	queries = append(queries, writeContentQuery)
	err := cd.conn.CypherBatch(queries)
	if err != nil {
		logger.WithMonitoringEvent("SaveNeo4j", transId, c.Type).WithField("uuid", c.UUID).WithError(err).Errorf("error: the query could not be executed")
	} else {
		logger.WithMonitoringEvent("SaveNeo4j", transId, c.Type).WithField("uuid", c.UUID).Info("the query was successfully executed")
	}
	return err
}

func addStoryPackageRelationQuery(articleUUID, packageUUID string) *neoism.CypherQuery {
	statement := `	MERGE(sp:Thing{uuid:{packageUuid}})
			MERGE(c:Thing{uuid:{contentUuid}})
			MERGE(c)<-[rel:IS_CURATED_FOR]-(sp)`

	query := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"packageUuid": packageUUID,
			"contentUuid": articleUUID,
		},
	}
	return query
}

func addContentPackageRelationQuery(articleUUID, packageUUID string) *neoism.CypherQuery {
	statement := `	MERGE(cp:Thing{uuid:{packageUuid}})
			MERGE(c:Thing{uuid:{contentUuid}})
			MERGE(c)-[rel:CONTAINS]->(cp)`

	query := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"packageUuid": packageUUID,
			"contentUuid": articleUUID,
		},
	}
	return query
}

//Delete - Deletes a content
func (cd service) Delete(uuid string, transId string) (bool, error) {
	clearNode := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (sp:Thing)-[rel1:IS_CURATED_FOR]->(p)
			OPTIONAL MATCH (p)-[rel2:CONTAINS]->(contained_cp:Thing)
			OPTIONAL MATCH (containing_cp:Thing)-[rel3:CONTAINS]->(p)
			REMOVE p:Content
			DELETE rel1, rel2, rel3
			SET p={props}
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
			"props": map[string]interface{}{
				"uuid": uuid,
			},
		},
		IncludeStats: true,
	}

	removeNodeIfUnused := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (p)-[a]-(x)
			WITH p, count(a) AS relCount
			WHERE relCount = 0
			DELETE p
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
	}

	err := cd.conn.CypherBatch([]*neoism.CypherQuery{clearNode, removeNodeIfUnused})
	if err != nil {
		logger.WithMonitoringEvent("SaveNeo4j", transId, "").WithField("uuid", uuid).WithError(err).Error("error: the delete query could not be executed")
		return false, err
	}

	s1, err := clearNode.Stats()
	if err != nil {
		return false, err
	}

	var deleted bool
	if s1.ContainsUpdates && s1.LabelsRemoved > 0 {
		deleted = true
	}
	logger.WithMonitoringEvent("SaveNeo4j", transId, "").WithField("uuid", uuid).Info("the delete query was successfully executed")
	return deleted, err
}

// DecodeJSON - Decodes JSON into content
func (cd service) DecodeJSON(dec *json.Decoder) (interface{}, string, error) {
	c := content{}
	err := dec.Decode(&c)
	return c, c.UUID, err

}

// Count - Returns a count of the number of content in this Neo instance
func (cd service) Count() (int, error) {

	var results []struct {
		Count int `json:"c"`
	}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Content) return count(n) as c`,
		Result:    &results,
	}

	err := cd.conn.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return 0, err
	}

	return results[0].Count, nil
}
<commit_msg>Add LiveBlogPackage label for some content packages<commit_after>package content

import (
	"encoding/json"
	"time"

	"github.com/Financial-Times/go-logger"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	tid "github.com/Financial-Times/transactionid-utils-go"
	"github.com/jmcvetta/neoism"
)

const LiveBlogPackage = "LiveBlogPackage"

var contentTypesWithNoBody = map[string]bool{
	"Content":        true,
	"Article":        true,
	"Video":          true,
	"Graphic":        true,
	"Audio":          true,
	"ContentPackage": true,
	LiveBlogPackage:  true,
}

// CypherDriver - CypherDriver
type service struct {
	conn neoutils.NeoConnection
}

//NewCypherDriver instantiate driver
func NewCypherContentService(cypherRunner neoutils.NeoConnection) service {
	return service{cypherRunner}
}

//Initialise ensure constraint on content uuid
func (cd service) Initialise() error {

	return cd.conn.EnsureConstraints(map[string]string{
		"Content": "uuid"})
}

// Check - Feeds into the Healthcheck and checks whether we can connect to Neo and that the datastore isn't empty and
// also checks if we are connected to leader/writable node
func (cd service) Check() error {
	writableErr := neoutils.CheckWritable(cd.conn)
	if writableErr != nil {
		return writableErr
	}

	return neoutils.Check(cd.conn)
}

// Read - reads a content given a UUID
func (cd service) Read(uuid string, transId string) (interface{}, bool, error) {
	var results []struct {
		content
	}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Content {uuid:{uuid}})
			OPTIONAL MATCH (sp:Thing)-[rel1:IS_CURATED_FOR]->(n)
			OPTIONAL MATCH (n)-[rel2:CONTAINS]->(cp:Thing)
			WITH n,sp,cp
			return  n.uuid as uuid,
				n.title as title,
				n.publishedDate as publishedDate,
				sp.uuid as storyPackage,
				cp.uuid as contentPackage`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
		Result: &results,
	}

	err := cd.conn.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return content{}, false, err
	}

	if len(results) == 0 {
		return content{}, false, nil
	}

	result := results[0]

	contentItem := content{
		UUID:           result.UUID,
		Title:          result.Title,
		PublishedDate:  result.PublishedDate,
		StoryPackage:   result.StoryPackage,
		ContentPackage: result.ContentPackage,
	}
	return contentItem, true, nil
}

//Write - Writes a content node
func (cd service) Write(thing interface{}, transId string) error {
	c := thing.(content)

	// Letting through only articles (which have body), live blogs, content packages, graphics, videos and audios (which don't have a body)
	if c.Body == "" && !contentTypesWithNoBody[c.Type] {
		logger.WithField(tid.TransactionIDKey, transId).
			Infof("There is no body with this content item therefore assuming is it not an Article: %v", c.UUID)
		return nil
	}

	params := map[string]interface{}{
		"uuid": c.UUID,
	}

	if c.Title != "" {
		params["title"] = c.Title
		params["prefLabel"] = c.Title
	}

	if c.PublishedDate != "" {
		params["publishedDate"] = c.PublishedDate
		datetimeEpoch, err := time.Parse(time.RFC3339, c.PublishedDate)

		if err != nil {
			return err
		}

		params["publishedDateEpoch"] = datetimeEpoch.Unix()
	}

	deleteEntityRelationshipsQuery := &neoism.CypherQuery{
		Statement: `MATCH (t:Thing {uuid:{uuid}})
				OPTIONAL MATCH (c:Thing)-[rel1:IS_CURATED_FOR]->(t)
				OPTIONAL MATCH (cp:Thing)<-[rel2:CONTAINS]-(t)
				DELETE rel1, rel2`,
		Parameters: map[string]interface{}{
			"uuid": c.UUID,
		},
	}

	queries := []*neoism.CypherQuery{deleteEntityRelationshipsQuery}

	labels := `:Content`

	if c.StoryPackage != "" {
		logger.WithField(tid.TransactionIDKey, transId).
			Infof("There is a story package with uuid=%v attached to Article with uuid=%v", c.StoryPackage, c.UUID)
		addStoryPackageRelationQuery := addStoryPackageRelationQuery(c.UUID, c.StoryPackage)
		queries = append(queries, addStoryPackageRelationQuery)
	}

	if c.ContentPackage != "" {
		logger.WithField(tid.TransactionIDKey, transId).
			Infof("There is a content package with uuid=%v attached to Article with uuid=%v", c.ContentPackage, c.UUID)
		addContentPackageRelationQuery := addContentPackageRelationQuery(c.UUID, c.ContentPackage)
		queries = append(queries, addContentPackageRelationQuery)

		labels = labels + `:ContentPackage`
		if c.Type == LiveBlogPackage {
			labels = labels + `:` + LiveBlogPackage
		}
	}

	statement := `MERGE (n:Thing {uuid: {uuid}})
		      set n={allprops}
		      set n ` + labels

	writeContentQuery := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"uuid":     c.UUID,
			"allprops": params,
		},
	}

	queries = append(queries, writeContentQuery)
	err := cd.conn.CypherBatch(queries)
	if err != nil {
		logger.WithMonitoringEvent("SaveNeo4j", transId, c.Type).WithField("uuid", c.UUID).WithError(err).Errorf("error: the query could not be executed")
	} else {
		logger.WithMonitoringEvent("SaveNeo4j", transId, c.Type).WithField("uuid", c.UUID).Info("the query was successfully executed")
	}
	return err
}

func addStoryPackageRelationQuery(articleUUID, packageUUID string) *neoism.CypherQuery {
	statement := `	MERGE(sp:Thing{uuid:{packageUuid}})
			MERGE(c:Thing{uuid:{contentUuid}})
			MERGE(c)<-[rel:IS_CURATED_FOR]-(sp)`

	query := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"packageUuid": packageUUID,
			"contentUuid": articleUUID,
		},
	}
	return query
}

func addContentPackageRelationQuery(articleUUID, packageUUID string) *neoism.CypherQuery {
	statement := `	MERGE(cp:Thing{uuid:{packageUuid}})
			MERGE(c:Thing{uuid:{contentUuid}})
			MERGE(c)-[rel:CONTAINS]->(cp)`

	query := &neoism.CypherQuery{
		Statement: statement,
		Parameters: map[string]interface{}{
			"packageUuid": packageUUID,
			"contentUuid": articleUUID,
		},
	}
	return query
}

//Delete - Deletes a content
func (cd service) Delete(uuid string, transId string) (bool, error) {
	clearNode := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (sp:Thing)-[rel1:IS_CURATED_FOR]->(p)
			OPTIONAL MATCH (p)-[rel2:CONTAINS]->(contained_cp:Thing)
			OPTIONAL MATCH (containing_cp:Thing)-[rel3:CONTAINS]->(p)
			REMOVE p:Content
			DELETE rel1, rel2, rel3
			SET p={props}
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
			"props": map[string]interface{}{
				"uuid": uuid,
			},
		},
		IncludeStats: true,
	}

	removeNodeIfUnused := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (p)-[a]-(x)
			WITH p, count(a) AS relCount
			WHERE relCount = 0
			DELETE p
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
	}

	err := cd.conn.CypherBatch([]*neoism.CypherQuery{clearNode, removeNodeIfUnused})
	if err != nil {
		logger.WithMonitoringEvent("SaveNeo4j", transId, "").WithField("uuid", uuid).WithError(err).Error("error: the delete query could not be executed")
		return false, err
	}

	s1, err := clearNode.Stats()
	if err != nil {
		return false, err
	}

	var deleted bool
	if s1.ContainsUpdates && s1.LabelsRemoved > 0 {
		deleted = true
	}
	logger.WithMonitoringEvent("SaveNeo4j", transId, "").WithField("uuid", uuid).Info("the delete query was successfully executed")
	return deleted, err
}

// DecodeJSON - Decodes JSON into content
func (cd service) DecodeJSON(dec *json.Decoder) (interface{}, string, error) {
	c := content{}
	err := dec.Decode(&c)
	return c, c.UUID, err

}

// Count - Returns a count of the number of content in this Neo instance
func (cd service) Count() (int, error) {

	var results []struct {
		Count int `json:"c"`
	}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Content) return count(n) as c`,
		Result:    &results,
	}

	err := cd.conn.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return 0, err
	}

	return results[0].Count, nil
}
<|endoftext|>


<commit_before><commit_msg>adding alu primatives<commit_after><|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	"github.com/eidolon/console"
	"github.com/eidolon/console/parameters"
)

var name = "World"

func main() {
	application := console.NewApplication("eidolon/console", "0.1.0")
	application.AddCommand(console.Command{
		Name:        "greet",
		Description: "Greet's the given user, or the world.",
		Help:        "You don't have to specify a name.",
		Configure: func(definition *console.Definition) {
			definition.AddOption(
				parameters.NewStringValue(&name),
				"-n, --name[=NAME]",
				"Provide a name for the greeting.",
			)
		},
		Execute: func(input *console.Input, output *console.Output) error {
			fmt.Printf("Hello, %s!\n", name)
			return nil
		},
	})

	code := application.Run(os.Args[1:])

	os.Exit(code)
}
<commit_msg>Updated example spec.<commit_after>package main

import (
	"fmt"
	"os"

	"github.com/eidolon/console"
	"github.com/eidolon/console/parameters"
)

var name = "World"

func main() {
	application := console.NewApplication("eidolon/console", "0.1.0")
	application.AddCommand(console.Command{
		Name:        "greet",
		Description: "Greet's the given user, or the world.",
		Help:        "You don't have to specify a name.",
		Configure: func(definition *console.Definition) {
			definition.AddOption(
				parameters.NewStringValue(&name),
				"-n, --name=NAME",
				"Provide a name for the greeting.",
			)
		},
		Execute: func(input *console.Input, output *console.Output) error {
			fmt.Printf("Hello, %s!\n", name)
			return nil
		},
	})

	code := application.Run(os.Args[1:])

	os.Exit(code)
}
<|endoftext|>