<commit_before>package sql

import (
	"database/sql"
	"fmt"
	"github.com/cayleygraph/cayley/clog"
	"github.com/cayleygraph/cayley/graph"
	"github.com/cayleygraph/cayley/quad"
	"github.com/lib/pq"
	"strconv"
	"strings"
)

const flavorCockroach = "cockroach"

func init() {
	RegisterFlavor(Flavor{
		Name:   flavorCockroach,
		Driver: flavorPostgres,
		NodesTable: `CREATE TABLE nodes (
	hash BYTEA PRIMARY KEY,
	value BYTEA,
	value_string TEXT,
	datatype TEXT,
	language TEXT,
	iri BOOLEAN,
	bnode BOOLEAN,
	value_int BIGINT,
	value_bool BOOLEAN,
	value_float double precision,
	value_time timestamp with time zone,
	FAMILY fhash (hash),
	FAMILY fvalue (value, value_string, datatype, language, iri, bnode,
		value_int, value_bool, value_float, value_time)
);`,
		QuadsTable: `CREATE TABLE quads (
	horizon BIGSERIAL PRIMARY KEY,
	subject_hash BYTEA NOT NULL,
	predicate_hash BYTEA NOT NULL,
	object_hash BYTEA NOT NULL,
	label_hash BYTEA,
	id BIGINT,
	ts timestamp
);`,
		FieldQuote:  '"',
		Placeholder: func(n int) string { return fmt.Sprintf("$%d", n) },
		Indexes: func(options graph.Options) []string {
			return []string{
				`CREATE UNIQUE INDEX spol_unique ON quads (subject_hash, predicate_hash, object_hash, label_hash);`,
				`CREATE UNIQUE INDEX spo_unique ON quads (subject_hash, predicate_hash, object_hash);`,
				`CREATE INDEX spo_index ON quads (subject_hash);`,
				`CREATE INDEX pos_index ON quads (predicate_hash);`,
				`CREATE INDEX osp_index ON quads (object_hash);`,
				//`ALTER TABLE quads ADD CONSTRAINT subject_hash_fk FOREIGN KEY (subject_hash) REFERENCES nodes (hash);`,
				//`ALTER TABLE quads ADD CONSTRAINT predicate_hash_fk FOREIGN KEY (predicate_hash) REFERENCES nodes (hash);`,
				//`ALTER TABLE quads ADD CONSTRAINT object_hash_fk FOREIGN KEY (object_hash) REFERENCES nodes (hash);`,
				//`ALTER TABLE quads ADD CONSTRAINT label_hash_fk FOREIGN KEY (label_hash) REFERENCES nodes (hash);`,
			}
		},
		Error: func(err error) error {
			e, ok := err.(*pq.Error)
			if !ok {
				return err
			}
			switch e.Code {
			case "42P07":
				return graph.ErrDatabaseExists
			}
			return err
		},
		//Estimated: func(table string) string{
		//	return "SELECT reltuples::BIGINT AS estimate FROM pg_class WHERE relname='"+table+"';"
		//},
		RunTx: runTxCockroach,
	})
}

func runTxCockroach(tx *sql.Tx, in []graph.Delta, opts graph.IgnoreOpts) error {
	//allAdds := true
	//for _, d := range in {
	//	if d.Action != graph.Add {
	//		allAdds = false
	//	}
	//}
	//if allAdds && !opts.IgnoreDup {
	//	return qs.copyFrom(tx, in, opts)
	//}

	end := ";"
	if true || opts.IgnoreDup {
		end = " ON CONFLICT (subject_hash, predicate_hash, object_hash) DO NOTHING;"
	}

	var (
		insertQuad  *sql.Stmt
		insertValue map[int]*sql.Stmt     // prepared statements for each value type
		inserted    map[NodeHash]struct{} // tracks already inserted values

		deleteQuad   *sql.Stmt
		deleteTriple *sql.Stmt
	)

	var err error
	for _, d := range in {
		switch d.Action {
		case graph.Add:
			if insertQuad == nil {
				insertQuad, err = tx.Prepare(`INSERT INTO quads(subject_hash, predicate_hash, object_hash, label_hash, id, ts) VALUES ($1, $2, $3, $4, $5, $6)` + end)
				if err != nil {
					return err
				}
				insertValue = make(map[int]*sql.Stmt)
				inserted = make(map[NodeHash]struct{}, len(in))
			}
			var hs, hp, ho, hl NodeHash
			for _, dir := range quad.Directions {
				v := d.Quad.Get(dir)
				if v == nil {
					continue
				}
				h := hashOf(v)
				switch dir {
				case quad.Subject:
					hs = h
				case quad.Predicate:
					hp = h
				case quad.Object:
					ho = h
				case quad.Label:
					hl = h
				}
				if !h.Valid() {
					continue
				} else if _, ok := inserted[h]; ok {
					continue
				}
				nodeKey, values, err := nodeValues(h, v)
				if err != nil {
					return err
				}
				stmt, ok := insertValue[nodeKey]
				if !ok {
					var ph = make([]string, len(values)-1)
					for i := range ph {
						ph[i] = "$" + strconv.FormatInt(int64(i)+2, 10)
					}
					stmt, err = tx.Prepare(`INSERT INTO nodes(hash, ` +
						strings.Join(nodeInsertColumns[nodeKey], ", ") +
						`) VALUES ($1, ` +
						strings.Join(ph, ", ") +
						`) ON CONFLICT (hash) DO NOTHING;`)
					if err != nil {
						return err
					}
					insertValue[nodeKey] = stmt
				}
				_, err = stmt.Exec(values...)
				if err != nil {
					clog.Errorf("couldn't exec INSERT statement: %v", err)
					return err
				}
				inserted[h] = struct{}{}
			}
			_, err := insertQuad.Exec(
				hs.toSQL(), hp.toSQL(), ho.toSQL(), hl.toSQL(),
				d.ID.Int(),
				d.Timestamp,
			)
			if err != nil {
				clog.Errorf("couldn't exec INSERT statement: %v", err)
				return err
			}
		case graph.Delete:
			if deleteQuad == nil {
				deleteQuad, err = tx.Prepare(`DELETE FROM quads WHERE subject_hash=$1 and predicate_hash=$2 and object_hash=$3 and label_hash=$4;`)
				if err != nil {
					return err
				}
				deleteTriple, err = tx.Prepare(`DELETE FROM quads WHERE subject_hash=$1 and predicate_hash=$2 and object_hash=$3 and label_hash is null;`)
				if err != nil {
					return err
				}
			}
			var result sql.Result
			if d.Quad.Label == nil {
				result, err = deleteTriple.Exec(hashOf(d.Quad.Subject).toSQL(), hashOf(d.Quad.Predicate).toSQL(), hashOf(d.Quad.Object).toSQL())
			} else {
				result, err = deleteQuad.Exec(hashOf(d.Quad.Subject).toSQL(), hashOf(d.Quad.Predicate).toSQL(), hashOf(d.Quad.Object).toSQL(), hashOf(d.Quad.Label).toSQL())
			}
			if err != nil {
				clog.Errorf("couldn't exec DELETE statement: %v", err)
				return err
			}
			affected, err := result.RowsAffected()
			if err != nil {
				clog.Errorf("couldn't get DELETE RowsAffected: %v", err)
				return err
			}
			if affected != 1 && !opts.IgnoreMissing {
				return graph.ErrQuadNotExist
			}
		default:
			panic("unknown action")
		}
	}
	return nil
}
<commit_msg>CockroachDB transaction retries (#534)<commit_after>package sql

import (
	"database/sql"
	"fmt"
	"strconv"
	"strings"

	"github.com/cayleygraph/cayley/clog"
	"github.com/cayleygraph/cayley/graph"
	"github.com/cayleygraph/cayley/quad"
	"github.com/lib/pq"
)

const flavorCockroach = "cockroach"

func init() {
	RegisterFlavor(Flavor{
		Name:   flavorCockroach,
		Driver: flavorPostgres,
		NodesTable: `CREATE TABLE nodes (
	hash BYTEA PRIMARY KEY,
	value BYTEA,
	value_string TEXT,
	datatype TEXT,
	language TEXT,
	iri BOOLEAN,
	bnode BOOLEAN,
	value_int BIGINT,
	value_bool BOOLEAN,
	value_float double precision,
	value_time timestamp with time zone,
	FAMILY fhash (hash),
	FAMILY fvalue (value, value_string, datatype, language, iri, bnode,
		value_int, value_bool, value_float, value_time)
);`,
		QuadsTable: `CREATE TABLE quads (
	horizon BIGSERIAL PRIMARY KEY,
	subject_hash BYTEA NOT NULL,
	predicate_hash BYTEA NOT NULL,
	object_hash BYTEA NOT NULL,
	label_hash BYTEA,
	id BIGINT,
	ts timestamp
);`,
		FieldQuote:  '"',
		Placeholder: func(n int) string { return fmt.Sprintf("$%d", n) },
		Indexes: func(options graph.Options) []string {
			return []string{
				`CREATE UNIQUE INDEX spol_unique ON quads (subject_hash, predicate_hash, object_hash, label_hash);`,
				`CREATE UNIQUE INDEX spo_unique ON quads (subject_hash, predicate_hash, object_hash);`,
				`CREATE INDEX spo_index ON quads (subject_hash);`,
				`CREATE INDEX pos_index ON quads (predicate_hash);`,
				`CREATE INDEX osp_index ON quads (object_hash);`,
				//`ALTER TABLE quads ADD CONSTRAINT subject_hash_fk FOREIGN KEY (subject_hash) REFERENCES nodes (hash);`,
				//`ALTER TABLE quads ADD CONSTRAINT predicate_hash_fk FOREIGN KEY (predicate_hash) REFERENCES nodes (hash);`,
				//`ALTER TABLE quads ADD CONSTRAINT object_hash_fk FOREIGN KEY (object_hash) REFERENCES nodes (hash);`,
				//`ALTER TABLE quads ADD CONSTRAINT label_hash_fk FOREIGN KEY (label_hash) REFERENCES nodes (hash);`,
			}
		},
		Error: func(err error) error {
			e, ok := err.(*pq.Error)
			if !ok {
				return err
			}
			switch e.Code {
			case "42P07":
				return graph.ErrDatabaseExists
			}
			return err
		},
		//Estimated: func(table string) string{
		//	return "SELECT reltuples::BIGINT AS estimate FROM pg_class WHERE relname='"+table+"';"
		//},
		RunTx: runTxCockroach,
	})
}

// AmbiguousCommitError represents an error that left a transaction in an
// ambiguous state: unclear if it committed or not.
type AmbiguousCommitError struct {
	error
}

// runTxCockroach runs the transaction and will retry in case of a retryable error.
// https://www.cockroachlabs.com/docs/transactions.html#client-side-transaction-retries
func runTxCockroach(tx *sql.Tx, in []graph.Delta, opts graph.IgnoreOpts) error {
	// Specify that we intend to retry this txn in case of CockroachDB retryable
	// errors.
	if _, err := tx.Exec("SAVEPOINT cockroach_restart"); err != nil {
		return err
	}

	for {
		released := false

		err := tryRunTxCockroach(tx, in, opts)

		if err == nil {
			// RELEASE acts like COMMIT in CockroachDB. We use it since it gives us an
			// opportunity to react to retryable errors, whereas tx.Commit() doesn't.
			released = true
			if _, err = tx.Exec("RELEASE SAVEPOINT cockroach_restart"); err == nil {
				return nil
			}
		}
		// We got an error; let's see if it's a retryable one and, if so, restart. We look
		// for either the standard PG errcode SerializationFailureError:40001 or the Cockroach extension
		// errcode RetriableError:CR000. The Cockroach extension has been removed server-side, but support
		// for it has been left here for now to maintain backwards compatibility.
		pqErr, ok := err.(*pq.Error)
		if retryable := ok && (pqErr.Code == "CR000" || pqErr.Code == "40001"); !retryable {
			if released {
				err = &AmbiguousCommitError{err}
			}
			return err
		}
		if _, err = tx.Exec("ROLLBACK TO SAVEPOINT cockroach_restart"); err != nil {
			return err
		}
	}
}

// tryRunTxCockroach runs the transaction (without retrying).
// For automatic retry upon retryable error use runTxCockroach
func tryRunTxCockroach(tx *sql.Tx, in []graph.Delta, opts graph.IgnoreOpts) error {
	//allAdds := true
	//for _, d := range in {
	//	if d.Action != graph.Add {
	//		allAdds = false
	//	}
	//}
	//if allAdds && !opts.IgnoreDup {
	//	return qs.copyFrom(tx, in, opts)
	//}

	end := ";"
	if true || opts.IgnoreDup {
		end = " ON CONFLICT (subject_hash, predicate_hash, object_hash) DO NOTHING;"
	}

	var (
		insertQuad  *sql.Stmt
		insertValue map[int]*sql.Stmt     // prepared statements for each value type
		inserted    map[NodeHash]struct{} // tracks already inserted values

		deleteQuad   *sql.Stmt
		deleteTriple *sql.Stmt
	)

	var err error
	for _, d := range in {
		switch d.Action {
		case graph.Add:
			if insertQuad == nil {
				insertQuad, err = tx.Prepare(`INSERT INTO quads(subject_hash, predicate_hash, object_hash, label_hash, id, ts) VALUES ($1, $2, $3, $4, $5, $6)` + end)
				if err != nil {
					return err
				}
				insertValue = make(map[int]*sql.Stmt)
				inserted = make(map[NodeHash]struct{}, len(in))
			}
			var hs, hp, ho, hl NodeHash
			for _, dir := range quad.Directions {
				v := d.Quad.Get(dir)
				if v == nil {
					continue
				}
				h := hashOf(v)
				switch dir {
				case quad.Subject:
					hs = h
				case quad.Predicate:
					hp = h
				case quad.Object:
					ho = h
				case quad.Label:
					hl = h
				}
				if !h.Valid() {
					continue
				} else if _, ok := inserted[h]; ok {
					continue
				}
				nodeKey, values, err := nodeValues(h, v)
				if err != nil {
					return err
				}
				stmt, ok := insertValue[nodeKey]
				if !ok {
					var ph = make([]string, len(values)-1)
					for i := range ph {
						ph[i] = "$" + strconv.FormatInt(int64(i)+2, 10)
					}
					stmt, err = tx.Prepare(`INSERT INTO nodes(hash, ` +
						strings.Join(nodeInsertColumns[nodeKey], ", ") +
						`) VALUES ($1, ` +
						strings.Join(ph, ", ") +
						`) ON CONFLICT (hash) DO NOTHING;`)
					if err != nil {
						return err
					}
					insertValue[nodeKey] = stmt
				}
				_, err = stmt.Exec(values...)
				if err != nil {
					clog.Errorf("couldn't exec INSERT statement: %v", err)
					return err
				}
				inserted[h] = struct{}{}
			}
			_, err := insertQuad.Exec(
				hs.toSQL(), hp.toSQL(), ho.toSQL(), hl.toSQL(),
				d.ID.Int(),
				d.Timestamp,
			)
			if err != nil {
				clog.Errorf("couldn't exec INSERT statement: %v", err)
				return err
			}
		case graph.Delete:
			if deleteQuad == nil {
				deleteQuad, err = tx.Prepare(`DELETE FROM quads WHERE subject_hash=$1 and predicate_hash=$2 and object_hash=$3 and label_hash=$4;`)
				if err != nil {
					return err
				}
				deleteTriple, err = tx.Prepare(`DELETE FROM quads WHERE subject_hash=$1 and predicate_hash=$2 and object_hash=$3 and label_hash is null;`)
				if err != nil {
					return err
				}
			}
			var result sql.Result
			if d.Quad.Label == nil {
				result, err = deleteTriple.Exec(hashOf(d.Quad.Subject).toSQL(), hashOf(d.Quad.Predicate).toSQL(), hashOf(d.Quad.Object).toSQL())
			} else {
				result, err = deleteQuad.Exec(hashOf(d.Quad.Subject).toSQL(), hashOf(d.Quad.Predicate).toSQL(), hashOf(d.Quad.Object).toSQL(), hashOf(d.Quad.Label).toSQL())
			}
			if err != nil {
				clog.Errorf("couldn't exec DELETE statement: %v", err)
				return err
			}
			affected, err := result.RowsAffected()
			if err != nil {
				clog.Errorf("couldn't get DELETE RowsAffected: %v", err)
				return err
			}
			if affected != 1 && !opts.IgnoreMissing {
				return graph.ErrQuadNotExist
			}
		default:
			panic("unknown action")
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"encoding/binary"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"regexp"
	"runtime"
	"time"

	"github.com/lomik/graphite-clickhouse/autocomplete"
	"github.com/lomik/graphite-clickhouse/config"
	"github.com/lomik/graphite-clickhouse/find"
	"github.com/lomik/graphite-clickhouse/helper/version"
	"github.com/lomik/graphite-clickhouse/index"
	"github.com/lomik/graphite-clickhouse/prometheus"
	"github.com/lomik/graphite-clickhouse/render"
	"github.com/lomik/graphite-clickhouse/tagger"
	"github.com/lomik/zapwriter"
	"go.uber.org/zap"

	_ "net/http/pprof"
)

// Version of graphite-clickhouse
const Version = "0.9.0"

func init() {
	version.Version = Version
}

type LogResponseWriter struct {
	http.ResponseWriter
	status int
}

func (w *LogResponseWriter) WriteHeader(status int) {
	w.status = status
	w.ResponseWriter.WriteHeader(status)
}

func (w *LogResponseWriter) Status() int {
	if w.status == 0 {
		return http.StatusOK
	}
	return w.status
}

func WrapResponseWriter(w http.ResponseWriter) *LogResponseWriter {
	if wrapped, ok := w.(*LogResponseWriter); ok {
		return wrapped
	}
	return &LogResponseWriter{ResponseWriter: w}
}

var requestIdRegexp *regexp.Regexp = regexp.MustCompile("^[a-zA-Z0-9_.-]+$")

func Handler(logger *zap.Logger, handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		writer := WrapResponseWriter(w)

		requestID := r.Header.Get("X-Request-Id")
		if requestID == "" || !requestIdRegexp.MatchString(requestID) {
			var b [16]byte
			binary.LittleEndian.PutUint64(b[:], rand.Uint64())
			binary.LittleEndian.PutUint64(b[8:], rand.Uint64())
			requestID = fmt.Sprintf("%x", b)
		}

		logger := logger.With(zap.String("request_id", requestID))

		r = r.WithContext(
			context.WithValue(
				context.WithValue(
					r.Context(),
					"logger",
					logger,
				),
				"requestID",
				requestID,
			),
		)

		start := time.Now()
		handler.ServeHTTP(writer, r)
		d := time.Since(start)
		logger.Info("access",
			zap.Duration("time", d),
			zap.String("method", r.Method),
			zap.String("url", r.URL.String()),
			zap.String("peer", r.RemoteAddr),
			zap.Int("status", writer.Status()),
		)
	})
}

func main() {
	rand.Seed(time.Now().UnixNano())

	var err error

	/* CONFIG start */

	configFile := flag.String("config", "/etc/graphite-clickhouse/graphite-clickhouse.conf", "Filename of config")
	printDefaultConfig := flag.Bool("config-print-default", false, "Print default config")
	checkConfig := flag.Bool("check-config", false, "Check config and exit")
	buildTags := flag.Bool("tags", false, "Build tags table")
	pprof := flag.String("pprof", "", "Additional pprof listen addr for non-server modes (tagger, etc..)")

	printVersion := flag.Bool("version", false, "Print version")

	flag.Parse()

	if *printVersion {
		fmt.Print(Version)
		return
	}

	if *printDefaultConfig {
		if err = config.PrintDefaultConfig(); err != nil {
			log.Fatal(err)
		}
		return
	}

	cfg, err := config.ReadConfig(*configFile)
	if err != nil {
		log.Fatal(err)
	}

	// config parsed successfully. Exit in check-only mode
	if *checkConfig {
		return
	}

	if err = zapwriter.ApplyConfig(cfg.Logging); err != nil {
		log.Fatal(err)
	}

	runtime.GOMAXPROCS(cfg.Common.MaxCPU)

	/* CONFIG end */

	if pprof != nil && *pprof != "" {
		go func() { log.Fatal(http.ListenAndServe(*pprof, nil)) }()
	}

	/* CONSOLE COMMANDS start */
	if *buildTags {
		if err := tagger.Make(cfg); err != nil {
			log.Fatal(err)
		}
		return
	}

	/* CONSOLE COMMANDS end */

	prometheusHandler := prometheus.NewHandler(cfg)

	http.Handle("/metrics/find/", Handler(zapwriter.Default(), find.NewHandler(cfg)))
	http.Handle("/metrics/index.json", Handler(zapwriter.Default(), index.NewHandler(cfg)))
	http.Handle("/render/", Handler(zapwriter.Default(), render.NewHandler(cfg)))
	http.Handle("/read", Handler(zapwriter.Default(), prometheusHandler))
	http.Handle("/api/v1/", Handler(zapwriter.Default(), prometheusHandler))
	http.Handle("/tags/autoComplete/tags", Handler(zapwriter.Default(), autocomplete.NewTags(cfg)))
	http.Handle("/tags/autoComplete/values", Handler(zapwriter.Default(), autocomplete.NewValues(cfg)))
	http.HandleFunc("/debug/config", func(w http.ResponseWriter, r *http.Request) {
		b, err := json.MarshalIndent(cfg, "", "  ")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Write(b)
	})

	http.Handle("/", Handler(zapwriter.Default(), http.HandlerFunc(http.NotFound)))

	log.Fatal(http.ListenAndServe(cfg.Common.Listen, nil))
}
<commit_msg>version 0.10.0<commit_after>package main

import (
	"context"
	"encoding/binary"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"regexp"
	"runtime"
	"time"

	"github.com/lomik/graphite-clickhouse/autocomplete"
	"github.com/lomik/graphite-clickhouse/config"
	"github.com/lomik/graphite-clickhouse/find"
	"github.com/lomik/graphite-clickhouse/helper/version"
	"github.com/lomik/graphite-clickhouse/index"
	"github.com/lomik/graphite-clickhouse/prometheus"
	"github.com/lomik/graphite-clickhouse/render"
	"github.com/lomik/graphite-clickhouse/tagger"
	"github.com/lomik/zapwriter"
	"go.uber.org/zap"

	_ "net/http/pprof"
)

// Version of graphite-clickhouse
const Version = "0.10.0"

func init() {
	version.Version = Version
}

type LogResponseWriter struct {
	http.ResponseWriter
	status int
}

func (w *LogResponseWriter) WriteHeader(status int) {
	w.status = status
	w.ResponseWriter.WriteHeader(status)
}

func (w *LogResponseWriter) Status() int {
	if w.status == 0 {
		return http.StatusOK
	}
	return w.status
}

func WrapResponseWriter(w http.ResponseWriter) *LogResponseWriter {
	if wrapped, ok := w.(*LogResponseWriter); ok {
		return wrapped
	}
	return &LogResponseWriter{ResponseWriter: w}
}

var requestIdRegexp *regexp.Regexp = regexp.MustCompile("^[a-zA-Z0-9_.-]+$")

func Handler(logger *zap.Logger, handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		writer := WrapResponseWriter(w)

		requestID := r.Header.Get("X-Request-Id")
		if requestID == "" || !requestIdRegexp.MatchString(requestID) {
			var b [16]byte
			binary.LittleEndian.PutUint64(b[:], rand.Uint64())
			binary.LittleEndian.PutUint64(b[8:], rand.Uint64())
			requestID = fmt.Sprintf("%x", b)
		}

		logger := logger.With(zap.String("request_id", requestID))

		r = r.WithContext(
			context.WithValue(
				context.WithValue(
					r.Context(),
					"logger",
					logger,
				),
				"requestID",
				requestID,
			),
		)

		start := time.Now()
		handler.ServeHTTP(writer, r)
		d := time.Since(start)
		logger.Info("access",
			zap.Duration("time", d),
			zap.String("method", r.Method),
			zap.String("url", r.URL.String()),
			zap.String("peer", r.RemoteAddr),
			zap.Int("status", writer.Status()),
		)
	})
}

func main() {
	rand.Seed(time.Now().UnixNano())

	var err error

	/* CONFIG start */

	configFile := flag.String("config", "/etc/graphite-clickhouse/graphite-clickhouse.conf", "Filename of config")
	printDefaultConfig := flag.Bool("config-print-default", false, "Print default config")
	checkConfig := flag.Bool("check-config", false, "Check config and exit")
	buildTags := flag.Bool("tags", false, "Build tags table")
	pprof := flag.String("pprof", "", "Additional pprof listen addr for non-server modes (tagger, etc..)")

	printVersion := flag.Bool("version", false, "Print version")

	flag.Parse()

	if *printVersion {
		fmt.Print(Version)
		return
	}

	if *printDefaultConfig {
		if err = config.PrintDefaultConfig(); err != nil {
			log.Fatal(err)
		}
		return
	}

	cfg, err := config.ReadConfig(*configFile)
	if err != nil {
		log.Fatal(err)
	}

	// config parsed successfully. Exit in check-only mode
	if *checkConfig {
		return
	}

	if err = zapwriter.ApplyConfig(cfg.Logging); err != nil {
		log.Fatal(err)
	}

	runtime.GOMAXPROCS(cfg.Common.MaxCPU)

	/* CONFIG end */

	if pprof != nil && *pprof != "" {
		go func() { log.Fatal(http.ListenAndServe(*pprof, nil)) }()
	}

	/* CONSOLE COMMANDS start */
	if *buildTags {
		if err := tagger.Make(cfg); err != nil {
			log.Fatal(err)
		}
		return
	}

	/* CONSOLE COMMANDS end */

	prometheusHandler := prometheus.NewHandler(cfg)

	http.Handle("/metrics/find/", Handler(zapwriter.Default(), find.NewHandler(cfg)))
	http.Handle("/metrics/index.json", Handler(zapwriter.Default(), index.NewHandler(cfg)))
	http.Handle("/render/", Handler(zapwriter.Default(), render.NewHandler(cfg)))
	http.Handle("/read", Handler(zapwriter.Default(), prometheusHandler))
	http.Handle("/api/v1/", Handler(zapwriter.Default(), prometheusHandler))
	http.Handle("/tags/autoComplete/tags", Handler(zapwriter.Default(), autocomplete.NewTags(cfg)))
	http.Handle("/tags/autoComplete/values", Handler(zapwriter.Default(), autocomplete.NewValues(cfg)))
	http.HandleFunc("/debug/config", func(w http.ResponseWriter, r *http.Request) {
		b, err := json.MarshalIndent(cfg, "", "  ")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Write(b)
	})

	http.Handle("/", Handler(zapwriter.Default(), http.HandlerFunc(http.NotFound)))

	log.Fatal(http.ListenAndServe(cfg.Common.Listen, nil))
}
<|endoftext|>


<commit_before>package info

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"regexp"
	"time"

	"github.com/koding/klient/info/publicip"
)

const (
	// The whois server WhoisQuery uses by default.
	whoisServer string = "whois.arin.net"

	// Default timeout for the whoisQuery
	whoisTimeout time.Duration = 5 * time.Second
)

// ProviderChecker funcs check the local machine to assert whether or
// not the current VM is is of that specific Provider.
type ProviderChecker func(whois string) (isProvider bool, err error)

type ProviderName int

const (
	// UnknownProvider is the zero value of the ProviderName type.
	UnknownProvider ProviderName = iota

	AWS
	Azure
	DigitalOcean
	GoogleCloud
	HPCloud
	Joylent
	Rackspace
	SoftLayer
)

func (pn ProviderName) String() string {
	switch pn {
	case AWS:
		return "AWS"
	case Azure:
		return "Azure"
	case DigitalOcean:
		return "DigitalOcean"
	case GoogleCloud:
		return "GoogleCloud"
	case HPCloud:
		return "HPCloud"
	case Joylent:
		return "Joylent"
	case Rackspace:
		return "Rackspace"
	case SoftLayer:
		return "SoftLayer"
	default:
		return "UnknownProvider"
	}
}

// DefaultProviderCheckers is a map of each ProviderName and the
// corresponding checker.
var DefaultProviderCheckers = map[ProviderName]ProviderChecker{
	AWS:          CheckAWS,
	Azure:        CheckAzure,
	DigitalOcean: CheckDigitalOcean,
	GoogleCloud:  CheckGoogleCloud,
	Joylent:      CheckJoylent,
	Rackspace:    CheckRackspace,
	SoftLayer:    CheckSoftLayer,
}

// CheckProvider uses the current machine's IP and runs a whois on it,
// then feeds the whois to all DefaultProviderCheckers.
func CheckProvider() (ProviderName, error) {
	// Get the IP of this machine, to whois against
	ip, err := publicip.PublicIP()
	if err != nil {
		return UnknownProvider, err
	}

	// Get the whois of the current vm's IP
	whois, err := WhoisQuery(ip.String(), whoisServer, whoisTimeout)
	if err != nil {
		return UnknownProvider, err
	}

	return checkProvider(DefaultProviderCheckers, whois)
}

// checkProvider implements the testable functionality of CheckProvider.
// Ie, a pure func, aside from any impurities passed in via checkers.
func checkProvider(checkers map[ProviderName]ProviderChecker, whois string) (
	ProviderName, error) {

	for providerName, checker := range checkers {
		isProvider, err := checker(whois)
		if err != nil {
			return UnknownProvider, err
		}

		if isProvider == true {
			return providerName, nil
		}
	}

	return UnknownProvider, nil
}

// generateChecker returns a ProviderChecker matching one or more whois
// regexp objects against the typical ProviderChecker whois.
func generateChecker(res ...*regexp.Regexp) ProviderChecker {
	return func(whois string) (bool, error) {
		if whois == "" {
			return false, errors.New("generateChecker: Whois is required")
		}

		for _, re := range res {
			if !re.MatchString(whois) {
				return false, nil
			}
		}

		return true, nil
	}
}

// CheckDigitalOcean is a ProviderChecker for DigitalOcean
func CheckDigitalOcean(_ string) (bool, error) {
	return checkDigitalOcean("http://169.254.169.254/metadata/v1/hostname")
}

// checkDigitalOcean implements the testable functionality of
// CheckDigitalOcean by quering the given DigitalOcean API address
// and if it returns 404, the check fails.
func checkDigitalOcean(metadataApi string) (bool, error) {
	res, err := http.Get(metadataApi)
	if err != nil {
		return false, err
	}

	return res.StatusCode == http.StatusOK, nil
}

// CheckAWS is a generic whois checker for Amazon
var CheckAWS ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)amazon`))

var CheckAzure ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)azure`))

var CheckGoogleCloud ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)google\s*cloud`))

var CheckHPCloud ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)hp\s*cloud`))

var CheckJoylent ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)joylent`))

var CheckRackspace ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)rackspace`))

var CheckSoftLayer ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)softlayer`))

// WhoisQuery is a simple func to query a whois service with the (limited)
// whois protocol.
//
// It's worth noting that because the whois protocol is so basic, the
// response can be formatted in any way. Because of this, WhoisQuery has to
// simply return the entire response to the caller - and is unable to
// marshall/etc the response in any meaningful format.
func WhoisQuery(query, server string, timeout time.Duration) (string, error) {
	host := net.JoinHostPort(server, "43")
	conn, err := net.DialTimeout("tcp", host, timeout)
	if err != nil {
		return "", err
	}
	defer conn.Close()

	// Query the whois server with the ip or domain given to this func,
	// as per Whois spec.
	_, err = conn.Write([]byte(fmt.Sprintf("%s\r\n", query)))
	if err != nil {
		return "", err
	}

	// After the query, the server will respond with the unformatted data.
	// Read it all and return it.
	b, err := ioutil.ReadAll(conn)
	if err != nil {
		return "", err
	}

	return string(b), nil
}
<commit_msg>styleguide: Changed format of generateChecker arguments<commit_after>package info

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"regexp"
	"time"

	"github.com/koding/klient/info/publicip"
)

const (
	// The whois server WhoisQuery uses by default.
	whoisServer string = "whois.arin.net"

	// Default timeout for the whoisQuery
	whoisTimeout time.Duration = 5 * time.Second
)

// ProviderChecker funcs check the local machine to assert whether or
// not the current VM is is of that specific Provider.
type ProviderChecker func(whois string) (isProvider bool, err error)

type ProviderName int

const (
	// UnknownProvider is the zero value of the ProviderName type.
	UnknownProvider ProviderName = iota

	AWS
	Azure
	DigitalOcean
	GoogleCloud
	HPCloud
	Joylent
	Rackspace
	SoftLayer
)

func (pn ProviderName) String() string {
	switch pn {
	case AWS:
		return "AWS"
	case Azure:
		return "Azure"
	case DigitalOcean:
		return "DigitalOcean"
	case GoogleCloud:
		return "GoogleCloud"
	case HPCloud:
		return "HPCloud"
	case Joylent:
		return "Joylent"
	case Rackspace:
		return "Rackspace"
	case SoftLayer:
		return "SoftLayer"
	default:
		return "UnknownProvider"
	}
}

// DefaultProviderCheckers is a map of each ProviderName and the
// corresponding checker.
var DefaultProviderCheckers = map[ProviderName]ProviderChecker{
	AWS:          CheckAWS,
	Azure:        CheckAzure,
	DigitalOcean: CheckDigitalOcean,
	GoogleCloud:  CheckGoogleCloud,
	Joylent:      CheckJoylent,
	Rackspace:    CheckRackspace,
	SoftLayer:    CheckSoftLayer,
}

// CheckProvider uses the current machine's IP and runs a whois on it,
// then feeds the whois to all DefaultProviderCheckers.
func CheckProvider() (ProviderName, error) {
	// Get the IP of this machine, to whois against
	ip, err := publicip.PublicIP()
	if err != nil {
		return UnknownProvider, err
	}

	// Get the whois of the current vm's IP
	whois, err := WhoisQuery(ip.String(), whoisServer, whoisTimeout)
	if err != nil {
		return UnknownProvider, err
	}

	return checkProvider(DefaultProviderCheckers, whois)
}

// checkProvider implements the testable functionality of CheckProvider.
// Ie, a pure func, aside from any impurities passed in via checkers.
func checkProvider(checkers map[ProviderName]ProviderChecker, whois string) (
	ProviderName, error) {

	for providerName, checker := range checkers {
		isProvider, err := checker(whois)
		if err != nil {
			return UnknownProvider, err
		}

		if isProvider == true {
			return providerName, nil
		}
	}

	return UnknownProvider, nil
}

// generateChecker returns a ProviderChecker matching one or more whois
// regexp objects against the typical ProviderChecker whois.
func generateChecker(res ...*regexp.Regexp) ProviderChecker {
	return func(whois string) (bool, error) {
		if whois == "" {
			return false, errors.New("generateChecker: Whois is required")
		}

		for _, re := range res {
			if !re.MatchString(whois) {
				return false, nil
			}
		}

		return true, nil
	}
}

// CheckDigitalOcean is a ProviderChecker for DigitalOcean
func CheckDigitalOcean(_ string) (bool, error) {
	return checkDigitalOcean("http://169.254.169.254/metadata/v1/hostname")
}

// checkDigitalOcean implements the testable functionality of
// CheckDigitalOcean by quering the given DigitalOcean API address
// and if it returns 404, the check fails.
func checkDigitalOcean(metadataApi string) (bool, error) {
	res, err := http.Get(metadataApi)
	if err != nil {
		return false, err
	}

	return res.StatusCode == http.StatusOK, nil
}

// CheckAWS is a generic whois checker for Amazon
var CheckAWS ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)amazon`),
)

var CheckAzure ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)azure`),
)

var CheckGoogleCloud ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)google\s*cloud`),
)

var CheckHPCloud ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)hp\s*cloud`),
)

var CheckJoylent ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)joylent`),
)

var CheckRackspace ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)rackspace`),
)

var CheckSoftLayer ProviderChecker = generateChecker(
	regexp.MustCompile(`(?i)softlayer`),
)

// WhoisQuery is a simple func to query a whois service with the (limited)
// whois protocol.
//
// It's worth noting that because the whois protocol is so basic, the
// response can be formatted in any way. Because of this, WhoisQuery has to
// simply return the entire response to the caller - and is unable to
// marshall/etc the response in any meaningful format.
func WhoisQuery(query, server string, timeout time.Duration) (string, error) {
	host := net.JoinHostPort(server, "43")
	conn, err := net.DialTimeout("tcp", host, timeout)
	if err != nil {
		return "", err
	}
	defer conn.Close()

	// Query the whois server with the ip or domain given to this func,
	// as per Whois spec.
	_, err = conn.Write([]byte(fmt.Sprintf("%s\r\n", query)))
	if err != nil {
		return "", err
	}

	// After the query, the server will respond with the unformatted data.
	// Read it all and return it.
	b, err := ioutil.ReadAll(conn)
	if err != nil {
		return "", err
	}

	return string(b), nil
}
<|endoftext|>


<commit_before>/*
  This file is part of go-ethereum

  go-ethereum is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  go-ethereum is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with go-ethereum.  If not, see <http://www.gnu.org/licenses/>.
*/
package rpc

import (
	"encoding/json"
	"fmt"
	"math/big"
	"strings"

	"github.com/ethereum/go-ethereum/common"
)

type hexdata struct {
	data []byte
}

func (d *hexdata) String() string {
	return "0x" + common.Bytes2Hex(d.data)
}

func (d *hexdata) MarshalJSON() ([]byte, error) {
	return json.Marshal(d.String())
}

func (d *hexdata) UnmarshalJSON(b []byte) (err error) {
	d.data = common.FromHex(string(b))
	return nil
}

func newHexData(input interface{}) *hexdata {
	d := new(hexdata)

	switch input.(type) {
	case []byte:
		d.data = input.([]byte)
	case common.Hash:
		d.data = input.(common.Hash).Bytes()
	case *common.Hash:
		d.data = input.(*common.Hash).Bytes()
	case common.Address:
		d.data = input.(common.Address).Bytes()
	case *common.Address:
		d.data = input.(*common.Address).Bytes()
	case *big.Int:
		d.data = input.(*big.Int).Bytes()
	case int64:
		d.data = big.NewInt(input.(int64)).Bytes()
	case uint64:
		d.data = big.NewInt(int64(input.(uint64))).Bytes()
	case int:
		d.data = big.NewInt(int64(input.(int))).Bytes()
	case uint:
		d.data = big.NewInt(int64(input.(uint))).Bytes()
	case string: // hexstring
		d.data = common.Big(input.(string)).Bytes()
	default:
		d.data = nil
	}

	return d
}

type hexnum struct {
	data []byte
}

func (d *hexnum) String() string {
	// Get hex string from bytes
	out := common.Bytes2Hex(d.data)
	// Trim leading 0s
	out = strings.Trim(out, "0")
	// Output "0x0" when value is 0
	if len(out) == 0 {
		out = "0"
	}
	return "0x" + out
}

func (d *hexnum) MarshalJSON() ([]byte, error) {
	return json.Marshal(d.String())
}

func (d *hexnum) UnmarshalJSON(b []byte) (err error) {
	d.data = common.FromHex(string(b))
	return nil
}

func newHexNum(input interface{}) *hexnum {
	d := new(hexnum)

	d.data = newHexData(input).data

	return d
}

type InvalidTypeError struct {
	method string
	msg    string
}

func (e *InvalidTypeError) Error() string {
	return fmt.Sprintf("invalid type on field %s: %s", e.method, e.msg)
}

func NewInvalidTypeError(method, msg string) *InvalidTypeError {
	return &InvalidTypeError{
		method: method,
		msg:    msg,
	}
}

type InsufficientParamsError struct {
	have int
	want int
}

func (e *InsufficientParamsError) Error() string {
	return fmt.Sprintf("insufficient params, want %d have %d", e.want, e.have)
}

func NewInsufficientParamsError(have int, want int) *InsufficientParamsError {
	return &InsufficientParamsError{
		have: have,
		want: want,
	}
}

type NotImplementedError struct {
	Method string
}

func (e *NotImplementedError) Error() string {
	return fmt.Sprintf("%s method not implemented", e.Method)
}

func NewNotImplementedError(method string) *NotImplementedError {
	return &NotImplementedError{
		Method: method,
	}
}

type DecodeParamError struct {
	err string
}

func (e *DecodeParamError) Error() string {
	return fmt.Sprintf("could not decode, %s", e.err)

}

func NewDecodeParamError(errstr string) error {
	return &DecodeParamError{
		err: errstr,
	}
}

type ValidationError struct {
	ParamName string
	msg       string
}

func (e *ValidationError) Error() string {
	return fmt.Sprintf("%s not valid, %s", e.ParamName, e.msg)
}

func NewValidationError(param string, msg string) error {
	return &ValidationError{
		ParamName: param,
		msg:       msg,
	}
}

type RpcRequest struct {
	Id      interface{}     `json:"id"`
	Jsonrpc string          `json:"jsonrpc"`
	Method  string          `json:"method"`
	Params  json.RawMessage `json:"params"`
}

type RpcSuccessResponse struct {
	Id      interface{} `json:"id"`
	Jsonrpc string      `json:"jsonrpc"`
	Result  interface{} `json:"result"`
}

type RpcErrorResponse struct {
	Id      interface{}     `json:"id"`
	Jsonrpc string          `json:"jsonrpc"`
	Error   *RpcErrorObject `json:"error"`
}

type RpcErrorObject struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	// Data    interface{} `json:"data"`
}
<commit_msg>Remove extra type assetion<commit_after>/*
  This file is part of go-ethereum

  go-ethereum is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  go-ethereum is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with go-ethereum.  If not, see <http://www.gnu.org/licenses/>.
*/
package rpc

import (
	"encoding/json"
	"fmt"
	"math/big"
	"strings"

	"github.com/ethereum/go-ethereum/common"
)

type hexdata struct {
	data []byte
}

func (d *hexdata) String() string {
	return "0x" + common.Bytes2Hex(d.data)
}

func (d *hexdata) MarshalJSON() ([]byte, error) {
	return json.Marshal(d.String())
}

func (d *hexdata) UnmarshalJSON(b []byte) (err error) {
	d.data = common.FromHex(string(b))
	return nil
}

func newHexData(input interface{}) *hexdata {
	d := new(hexdata)

	switch input := input.(type) {
	case []byte:
		d.data = input
	case common.Hash:
		d.data = input.Bytes()
	case *common.Hash:
		d.data = input.Bytes()
	case common.Address:
		d.data = input.Bytes()
	case *common.Address:
		d.data = input.Bytes()
	case *big.Int:
		d.data = input.Bytes()
	case int64:
		d.data = big.NewInt(input).Bytes()
	case uint64:
		d.data = big.NewInt(int64(input)).Bytes()
	case int:
		d.data = big.NewInt(int64(input)).Bytes()
	case uint:
		d.data = big.NewInt(int64(input)).Bytes()
	case string: // hexstring
		d.data = common.Big(input).Bytes()
	default:
		d.data = nil
	}

	return d
}

type hexnum struct {
	data []byte
}

func (d *hexnum) String() string {
	// Get hex string from bytes
	out := common.Bytes2Hex(d.data)
	// Trim leading 0s
	out = strings.Trim(out, "0")
	// Output "0x0" when value is 0
	if len(out) == 0 {
		out = "0"
	}
	return "0x" + out
}

func (d *hexnum) MarshalJSON() ([]byte, error) {
	return json.Marshal(d.String())
}

func (d *hexnum) UnmarshalJSON(b []byte) (err error) {
	d.data = common.FromHex(string(b))
	return nil
}

func newHexNum(input interface{}) *hexnum {
	d := new(hexnum)

	d.data = newHexData(input).data

	return d
}

type InvalidTypeError struct {
	method string
	msg    string
}

func (e *InvalidTypeError) Error() string {
	return fmt.Sprintf("invalid type on field %s: %s", e.method, e.msg)
}

func NewInvalidTypeError(method, msg string) *InvalidTypeError {
	return &InvalidTypeError{
		method: method,
		msg:    msg,
	}
}

type InsufficientParamsError struct {
	have int
	want int
}

func (e *InsufficientParamsError) Error() string {
	return fmt.Sprintf("insufficient params, want %d have %d", e.want, e.have)
}

func NewInsufficientParamsError(have int, want int) *InsufficientParamsError {
	return &InsufficientParamsError{
		have: have,
		want: want,
	}
}

type NotImplementedError struct {
	Method string
}

func (e *NotImplementedError) Error() string {
	return fmt.Sprintf("%s method not implemented", e.Method)
}

func NewNotImplementedError(method string) *NotImplementedError {
	return &NotImplementedError{
		Method: method,
	}
}

type DecodeParamError struct {
	err string
}

func (e *DecodeParamError) Error() string {
	return fmt.Sprintf("could not decode, %s", e.err)

}

func NewDecodeParamError(errstr string) error {
	return &DecodeParamError{
		err: errstr,
	}
}

type ValidationError struct {
	ParamName string
	msg       string
}

func (e *ValidationError) Error() string {
	return fmt.Sprintf("%s not valid, %s", e.ParamName, e.msg)
}

func NewValidationError(param string, msg string) error {
	return &ValidationError{
		ParamName: param,
		msg:       msg,
	}
}

type RpcRequest struct {
	Id      interface{}     `json:"id"`
	Jsonrpc string          `json:"jsonrpc"`
	Method  string          `json:"method"`
	Params  json.RawMessage `json:"params"`
}

type RpcSuccessResponse struct {
	Id      interface{} `json:"id"`
	Jsonrpc string      `json:"jsonrpc"`
	Result  interface{} `json:"result"`
}

type RpcErrorResponse struct {
	Id      interface{}     `json:"id"`
	Jsonrpc string          `json:"jsonrpc"`
	Error   *RpcErrorObject `json:"error"`
}

type RpcErrorObject struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	// Data    interface{} `json:"data"`
}
<|endoftext|>


<commit_before>package exec

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"

	"code.cloudfoundry.org/lager"
	"code.cloudfoundry.org/lager/lagerctx"
	"sigs.k8s.io/yaml"

	"github.com/concourse/baggageclaim"
	"github.com/concourse/concourse/atc"
	"github.com/concourse/concourse/atc/exec/artifact"
	"github.com/concourse/concourse/atc/exec/build"
	"github.com/concourse/concourse/atc/worker"
	"github.com/concourse/concourse/vars"
)

// VarStep loads a value from a file and sets it as a build-local var.
type VarStep struct {
	planID    atc.PlanID
	plan      atc.VarPlan
	metadata  StepMetadata
	delegate  BuildStepDelegate
	client    worker.Client
	succeeded bool
}

func NewVarStep(
	planID atc.PlanID,
	plan atc.VarPlan,
	metadata StepMetadata,
	delegate BuildStepDelegate,
	client worker.Client,
) Step {
	return &VarStep{
		planID:   planID,
		plan:     plan,
		metadata: metadata,
		delegate: delegate,
		client:   client,
	}
}

type UnspecifiedVarStepFileError struct {
	File string
}

// Error returns a human-friendly error message.
func (err UnspecifiedVarStepFileError) Error() string {
	return fmt.Sprintf("file '%s' does not specify where the file lives", err.File)
}

type InvalidLocalVarFile struct {
	File string
	Format string
	Err error
}

func (err InvalidLocalVarFile) Error() string {
	return fmt.Sprintf("failed to parse %s in format %s: %s", err.File, err.Format, err.Err.Error())
}

func (step *VarStep) Run(ctx context.Context, state RunState) error {
	logger := lagerctx.FromContext(ctx)
	logger = logger.Session("var-step", lager.Data{
		"step-name": step.plan.Name,
		"job-id":    step.metadata.JobID,
	})

	step.delegate.Initializing(logger)
	stdout := step.delegate.Stdout()
	stderr := step.delegate.Stderr()

	fmt.Fprintln(stderr, "\x1b[1;33mWARNING: the var step is experimental and subject to change!\x1b[0m")
	fmt.Fprintln(stderr, "")
	fmt.Fprintln(stderr, "\x1b[33mfollow RFC #42 for updates: https://github.com/concourse/rfcs/pull/42\x1b[0m")
	fmt.Fprintln(stderr, "")

	step.delegate.Starting(logger)

	varFromFile, err := step.fetchVars(ctx, logger, step.plan.Name, step.plan.File, state)
	if err != nil {
		return err
	}
	fmt.Fprintf(stdout, "var %s fetched.\n", step.plan.Name)

	step.delegate.Variables().AddVar(varFromFile)
	fmt.Fprintf(stdout, "added var %s to build.\n", step.plan.Name)

	step.succeeded = true
	step.delegate.Finished(logger, step.succeeded)

	return nil
}

func (step *VarStep) Succeeded() bool {
	return step.succeeded
}

func (step *VarStep) fetchVars(
	ctx context.Context,
	logger lager.Logger,
	varName string,
	file string,
	state RunState,
) (vars.Variables, error) {

	segs := strings.SplitN(file, "/", 2)
	if len(segs) != 2 {
		return nil, UnspecifiedVarStepFileError{file}
	}

	artifactName := segs[0]
	filePath := segs[1]

	format, err := step.fileFormat(file)
	if err != nil {
		return nil, err
	}
	logger.Debug("figure-out-format", lager.Data{"format": format})

	art, found := state.ArtifactRepository().ArtifactFor(build.ArtifactName(artifactName))
	if !found {
		return nil, UnknownArtifactSourceError{build.ArtifactName(artifactName), filePath}
	}

	stream, err := step.client.StreamFileFromArtifact(ctx, logger, art, filePath)
	if err != nil {
		if err == baggageclaim.ErrFileNotFound {
			return nil, artifact.FileNotFoundError{
				Name:     artifactName,
				FilePath: filePath,
			}
		}

		return nil, err
	}

	byteConfig, err := ioutil.ReadAll(stream)
	if err != nil {
		return nil, err
	}

	if step.plan.Dump {
		fmt.Fprintf(step.delegate.Stdout(),
			"=== begin dump input file %s ===\n%s\n=== end dump ===\n\n",
			step.plan.File, string(byteConfig))
	}

	varFromFile := vars.StaticVariables{}
	switch format {
	case "json":
		value := map[string]interface{}{}
		err = json.Unmarshal(byteConfig, &value)
		if err != nil {
			return nil, InvalidLocalVarFile{file, "json", err}
		}
		varFromFile[varName] = value
	case "yml", "yaml":
		value := map[string]interface{}{}
		err = yaml.Unmarshal(byteConfig, &value)
		if err != nil {
			return nil, InvalidLocalVarFile{file, "yaml", err}
		}
		varFromFile[varName] = value
	case "raw":
		varFromFile[varName] = string(byteConfig)
	default:
		return nil, fmt.Errorf("unknown format %s, should never happen, ", format)
	}

	return varFromFile, nil
}

func (step *VarStep) fileFormat(file string) (string, error) {
	if step.isValidFormat(step.plan.Format) {
		return step.plan.Format, nil
	} else if step.plan.Format != "" {
		return "", fmt.Errorf("invalid format %s", step.plan.Format)
	}

	fileExt := filepath.Ext(file)
	format := strings.TrimPrefix(fileExt, ".")
	if step.isValidFormat(format) {
		return format, nil
	}

	return "raw", nil
}

func (step *VarStep) isValidFormat(format string) bool {
	switch format {
	case "raw", "yml", "yaml", "json":
		return true
	}
	return false
}
<commit_msg>Correct RFC # in warning message.<commit_after>package exec

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"

	"code.cloudfoundry.org/lager"
	"code.cloudfoundry.org/lager/lagerctx"
	"sigs.k8s.io/yaml"

	"github.com/concourse/baggageclaim"
	"github.com/concourse/concourse/atc"
	"github.com/concourse/concourse/atc/exec/artifact"
	"github.com/concourse/concourse/atc/exec/build"
	"github.com/concourse/concourse/atc/worker"
	"github.com/concourse/concourse/vars"
)

// VarStep loads a value from a file and sets it as a build-local var.
type VarStep struct {
	planID    atc.PlanID
	plan      atc.VarPlan
	metadata  StepMetadata
	delegate  BuildStepDelegate
	client    worker.Client
	succeeded bool
}

func NewVarStep(
	planID atc.PlanID,
	plan atc.VarPlan,
	metadata StepMetadata,
	delegate BuildStepDelegate,
	client worker.Client,
) Step {
	return &VarStep{
		planID:   planID,
		plan:     plan,
		metadata: metadata,
		delegate: delegate,
		client:   client,
	}
}

type UnspecifiedVarStepFileError struct {
	File string
}

// Error returns a human-friendly error message.
func (err UnspecifiedVarStepFileError) Error() string {
	return fmt.Sprintf("file '%s' does not specify where the file lives", err.File)
}

type InvalidLocalVarFile struct {
	File string
	Format string
	Err error
}

func (err InvalidLocalVarFile) Error() string {
	return fmt.Sprintf("failed to parse %s in format %s: %s", err.File, err.Format, err.Err.Error())
}

func (step *VarStep) Run(ctx context.Context, state RunState) error {
	logger := lagerctx.FromContext(ctx)
	logger = logger.Session("var-step", lager.Data{
		"step-name": step.plan.Name,
		"job-id":    step.metadata.JobID,
	})

	step.delegate.Initializing(logger)
	stdout := step.delegate.Stdout()
	stderr := step.delegate.Stderr()

	fmt.Fprintln(stderr, "\x1b[1;33mWARNING: the var step is experimental and subject to change!\x1b[0m")
	fmt.Fprintln(stderr, "")
	fmt.Fprintln(stderr, "\x1b[33mfollow RFC #27 for updates: https://github.com/concourse/rfcs/pull/27\x1b[0m")
	fmt.Fprintln(stderr, "")

	step.delegate.Starting(logger)

	varFromFile, err := step.fetchVars(ctx, logger, step.plan.Name, step.plan.File, state)
	if err != nil {
		return err
	}
	fmt.Fprintf(stdout, "var %s fetched.\n", step.plan.Name)

	step.delegate.Variables().AddVar(varFromFile)
	fmt.Fprintf(stdout, "added var %s to build.\n", step.plan.Name)

	step.succeeded = true
	step.delegate.Finished(logger, step.succeeded)

	return nil
}

func (step *VarStep) Succeeded() bool {
	return step.succeeded
}

func (step *VarStep) fetchVars(
	ctx context.Context,
	logger lager.Logger,
	varName string,
	file string,
	state RunState,
) (vars.Variables, error) {

	segs := strings.SplitN(file, "/", 2)
	if len(segs) != 2 {
		return nil, UnspecifiedVarStepFileError{file}
	}

	artifactName := segs[0]
	filePath := segs[1]

	format, err := step.fileFormat(file)
	if err != nil {
		return nil, err
	}
	logger.Debug("figure-out-format", lager.Data{"format": format})

	art, found := state.ArtifactRepository().ArtifactFor(build.ArtifactName(artifactName))
	if !found {
		return nil, UnknownArtifactSourceError{build.ArtifactName(artifactName), filePath}
	}

	stream, err := step.client.StreamFileFromArtifact(ctx, logger, art, filePath)
	if err != nil {
		if err == baggageclaim.ErrFileNotFound {
			return nil, artifact.FileNotFoundError{
				Name:     artifactName,
				FilePath: filePath,
			}
		}

		return nil, err
	}

	byteConfig, err := ioutil.ReadAll(stream)
	if err != nil {
		return nil, err
	}

	if step.plan.Dump {
		fmt.Fprintf(step.delegate.Stdout(),
			"=== begin dump input file %s ===\n%s\n=== end dump ===\n\n",
			step.plan.File, string(byteConfig))
	}

	varFromFile := vars.StaticVariables{}
	switch format {
	case "json":
		value := map[string]interface{}{}
		err = json.Unmarshal(byteConfig, &value)
		if err != nil {
			return nil, InvalidLocalVarFile{file, "json", err}
		}
		varFromFile[varName] = value
	case "yml", "yaml":
		value := map[string]interface{}{}
		err = yaml.Unmarshal(byteConfig, &value)
		if err != nil {
			return nil, InvalidLocalVarFile{file, "yaml", err}
		}
		varFromFile[varName] = value
	case "raw":
		varFromFile[varName] = string(byteConfig)
	default:
		return nil, fmt.Errorf("unknown format %s, should never happen, ", format)
	}

	return varFromFile, nil
}

func (step *VarStep) fileFormat(file string) (string, error) {
	if step.isValidFormat(step.plan.Format) {
		return step.plan.Format, nil
	} else if step.plan.Format != "" {
		return "", fmt.Errorf("invalid format %s", step.plan.Format)
	}

	fileExt := filepath.Ext(file)
	format := strings.TrimPrefix(fileExt, ".")
	if step.isValidFormat(format) {
		return format, nil
	}

	return "raw", nil
}

func (step *VarStep) isValidFormat(format string) bool {
	switch format {
	case "raw", "yml", "yaml", "json":
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package hashers

import (
	"crypto/sha1"
	"fmt"
	"io"
	"strings"
)

type SHA1PasswordHasher struct{}

func (p *SHA1PasswordHasher) Encode(password string, salt string) string {
	h := sha1.New()
	io.WriteString(h, salt+password)
	hash := fmt.Sprintf("%s", h.Sum(nil))
	return fmt.Sprintf("%s%s%s%s%s", p.Algorithm(), HASH_SEPARATOR, salt, HASH_SEPARATOR, hash)
}

func (p *SHA1PasswordHasher) Algorithm() string {
	return "sha1"
}

func (p *SHA1PasswordHasher) Verify(password string, encoded string) bool {
	results := strings.Split(encoded, HASH_SEPARATOR)

	attempt := p.Encode(results[1], results[2])

	return encoded == attempt
}

func (p *SHA1PasswordHasher) SafeSummary(encoded string) PasswordSummary {
	results := strings.Split(encoded, HASH_SEPARATOR)

	return PasswordSummary{
		Algorithm: p.Algorithm(),
		Salt:      results[1],
		Hash:      results[2],
	}
}

func (p *SHA1PasswordHasher) Salt() string {
	return RandomString(12)
}
<commit_msg>Refactor SHA1 hasher.<commit_after>package hashers

import (
	"crypto/sha1"
	"fmt"
	"strings"
)

// SHA1PasswordHasher is the SHA1 password hasher.
type SHA1PasswordHasher struct{}

// Encode encodes the given password (adding the given salt) then returns encoded.
func (p *SHA1PasswordHasher) Encode(password string, salt string) string {
	return fmt.Sprintf("%s%s%s%s%s",
		p.Algorithm(),
		HASH_SEPARATOR,
		salt,
		HASH_SEPARATOR,
		fmt.Sprintf("%x", sha1.Sum([]byte(salt+password))))
}

// Algorithm returns the algorithm name of this hasher.
func (p *SHA1PasswordHasher) Algorithm() string {
	return "sha1"
}

// Verify takes the raw password and the encoded one, then checks if they match.
func (p *SHA1PasswordHasher) Verify(password string, encoded string) bool {
	results := strings.Split(encoded, HASH_SEPARATOR)
	attempt := p.Encode(password, results[1])
	return encoded == attempt
}

// SafeSummary returns a summary of the encoded password.
func (p *SHA1PasswordHasher) SafeSummary(encoded string) PasswordSummary {
	results := strings.Split(encoded, HASH_SEPARATOR)
	return PasswordSummary{
		Algorithm: p.Algorithm(),
		Salt:      results[1],
		Hash:      results[2],
	}
}

// Salt returns the default salt (which defaults to a random 12 characters string).
func (p *SHA1PasswordHasher) Salt() string {
	return RandomString(12)
}
<|endoftext|>


<commit_before>package auth

import (
	"crypto/elliptic"
	"math/big"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestRetrievingIAPJSONWebKeys(t *testing.T) {
	t.Run("ReturnsKeyByKeyID", func(t *testing.T) {

		// act (if fails get new kid from https://www.gstatic.com/iap/verify/public_key-jwk and update expectancies until it works)
		publicKey, err := GetCachedIAPJWK("f9R3yg")

		if assert.Nil(t, err) {
			assert.Equal(t, elliptic.P256(), publicKey.Curve)

			expectedX := new(big.Int)
			expectedX, _ = expectedX.SetString("33754992528993959342082873952071099444905807959681776349240807143574023195992", 10)

			if assert.Equal(t, expectedX, publicKey.X) {

				expectedY := new(big.Int)
				expectedY, _ = expectedY.SetString("30017756976983295626595109856839943719662421701617989535808220756803905010317", 10)

				assert.Equal(t, expectedY, publicKey.Y)
			}
		}
	})
}

func TestRetrievingGoogleJSONWebKeys(t *testing.T) {
	t.Run("ReturnsKeyByKeyID", func(t *testing.T) {

		// act (if fails get new kid from https://www.googleapis.com/oauth2/v3/certs and update expectancies until it works)
		publicKey, err := GetCachedGoogleJWK("3db3ed6b9574ee3fcd9f149e59ff0eef4f932153")

		if assert.Nil(t, err) {
			expectedN, _ := new(big.Int).SetString("27349905058855127968386103083394866213815847157226658207829268545600181950377300862481176817912409670858609137610020055997633413328762561137991178831777091094547887792847449826917401187028086955683997109466088616161972480428803322222835274139626412339095605534896733458293924418586435954201369261155962062592044320146667266607982957182509068548596713693647536087590222980421957308422730890043002602024506478770355300469677715328888224830981932761345728416332941464505853097186519865782757670005375943325117046535555363673051705356588671170776656604497778320950692720571503196071281550014910248825942892005693136500731", 10)

			if assert.Equal(t, expectedN, publicKey.N) {

				expectedY := 65537
				assert.Equal(t, expectedY, publicKey.E)
			}
		}
	})
}
<commit_msg>fix jwk test<commit_after>package auth

import (
	"crypto/elliptic"
	"math/big"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestRetrievingIAPJSONWebKeys(t *testing.T) {
	t.Run("ReturnsKeyByKeyID", func(t *testing.T) {

		// act (if fails get new kid from https://www.gstatic.com/iap/verify/public_key-jwk and update expectancies until it works)
		publicKey, err := GetCachedIAPJWK("f9R3yg")

		if assert.Nil(t, err) {
			assert.Equal(t, elliptic.P256(), publicKey.Curve)

			expectedX := new(big.Int)
			expectedX, _ = expectedX.SetString("33754992528993959342082873952071099444905807959681776349240807143574023195992", 10)

			if assert.Equal(t, expectedX, publicKey.X) {

				expectedY := new(big.Int)
				expectedY, _ = expectedY.SetString("30017756976983295626595109856839943719662421701617989535808220756803905010317", 10)

				assert.Equal(t, expectedY, publicKey.Y)
			}
		}
	})
}

func TestRetrievingGoogleJSONWebKeys(t *testing.T) {
	t.Run("ReturnsKeyByKeyID", func(t *testing.T) {

		// act (if fails get new kid from https://www.googleapis.com/oauth2/v3/certs and update expectancies until it works)
		publicKey, err := GetCachedGoogleJWK("8a63fe71e53067524cbbc6a3a58463b3864c0787")

		if assert.Nil(t, err) {
			expectedN, _ := new(big.Int).SetString("25462559455305279680930073421592071197511990287506517669360420279184745872586657725021452515269520146350819564451481782481370877474015012644315401094281618027294450648211279315323383963323280881195199616541480725506185280537045392579172217469708416229939638263143347431343065229798478771677971367582487218074544084254020686612325960018034590870438044226067081637870371374187583678628509611893006399564569618872788960399498828632291292980618664016645484666281281482487267313176806989149990379724322487728594770747718601853180225819982327160867532914586273610030333603460178770482455846057212938295965297224366206842521", 10)

			if assert.Equal(t, expectedN, publicKey.N) {

				expectedY := 65537
				assert.Equal(t, expectedY, publicKey.E)
			}
		}
	})
}
<|endoftext|>


<commit_before>package common

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestVariableString(t *testing.T) {
	v := BuildVariable{"key", "value", false}
	assert.Equal(t, "key=value", v.String())
}

func TestPublicVariables(t *testing.T) {
	v1 := BuildVariable{"key", "value", false}
	v2 := BuildVariable{"public", "value", true}
	v3 := BuildVariable{"private", "value", false}
	all := BuildVariables{v1, v2, v3}
	public := all.Public()
	assert.Contains(t, public, v2)
	assert.NotContains(t, public, v1)
	assert.NotContains(t, public, v3)
}

func TestListVariables(t *testing.T) {
	v := BuildVariables{{"key", "value", false}}
	assert.Equal(t, []string{"key=value"}, v.StringList())
}

func TestGetVariable(t *testing.T) {
	v1 := BuildVariable{"key", "key_value", false}
	v2 := BuildVariable{"public", "public_value", true}
	v3 := BuildVariable{"private", "private_value", false}
	all := BuildVariables{v1, v2, v3}

	assert.Equal(t, "public_value", all.Get("public"))
	assert.Empty(t, all.Get("other"))
}

func TestParseVariable(t *testing.T) {
	v, err := ParseVariable("key=value=value2")
	assert.NoError(t, err)
	assert.Equal(t, BuildVariable{"key", "value=value2", false}, v)
}

func TestInvalidParseVariable(t *testing.T) {
	_, err := ParseVariable("some_other_key")
	assert.Error(t, err)
}

func TestVariablesExpansion(t *testing.T) {
	all := BuildVariables{
		{"key", "value_of_$public", false},
		{"public", "some_value", true},
		{"private", "value_of_${public}", false},
		{"public", "value_of_$undefined", true},
	}

	expanded := all.Expand()
	assert.Len(t, expanded, 4)
	assert.Equal(t, expanded.Get("key"), "value_of_value_of_$undefined")
	assert.Equal(t, expanded.Get("public"), "value_of_")
	assert.Equal(t, expanded.Get("private"), "value_of_value_of_$undefined")
	assert.Equal(t, expanded.ExpandValue("${public} ${private}"), "value_of_ value_of_value_of_$undefined")
}
<commit_msg>Fix compilation error<commit_after>package common

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestVariableString(t *testing.T) {
	v := BuildVariable{"key", "value", false, false}
	assert.Equal(t, "key=value", v.String())
}

func TestPublicAndInternalVariables(t *testing.T) {
	v1 := BuildVariable{"key", "value", false, false}
	v2 := BuildVariable{"public", "value", true, false}
	v3 := BuildVariable{"private", "value", false, true}
	all := BuildVariables{v1, v2, v3}
	public := all.PublicOrInternal()
	assert.NotContains(t, public, v1)
	assert.Contains(t, public, v2)
	assert.Contains(t, public, v3)
}

func TestListVariables(t *testing.T) {
	v := BuildVariables{{"key", "value", false, false}}
	assert.Equal(t, []string{"key=value"}, v.StringList())
}

func TestGetVariable(t *testing.T) {
	v1 := BuildVariable{"key", "key_value", false, false}
	v2 := BuildVariable{"public", "public_value", true, false}
	v3 := BuildVariable{"private", "private_value", false, false}
	all := BuildVariables{v1, v2, v3}

	assert.Equal(t, "public_value", all.Get("public"))
	assert.Empty(t, all.Get("other"))
}

func TestParseVariable(t *testing.T) {
	v, err := ParseVariable("key=value=value2")
	assert.NoError(t, err)
	assert.Equal(t, BuildVariable{"key", "value=value2", false, false}, v)
}

func TestInvalidParseVariable(t *testing.T) {
	_, err := ParseVariable("some_other_key")
	assert.Error(t, err)
}

func TestVariablesExpansion(t *testing.T) {
	all := BuildVariables{
		{"key", "value_of_$public", false, false},
		{"public", "some_value", true, false},
		{"private", "value_of_${public}", false, false},
		{"public", "value_of_$undefined", true, false},
	}

	expanded := all.Expand()
	assert.Len(t, expanded, 4)
	assert.Equal(t, expanded.Get("key"), "value_of_value_of_$undefined")
	assert.Equal(t, expanded.Get("public"), "value_of_")
	assert.Equal(t, expanded.Get("private"), "value_of_value_of_$undefined")
	assert.Equal(t, expanded.ExpandValue("${public} ${private}"), "value_of_ value_of_value_of_$undefined")
}
<|endoftext|>


<commit_before>//
// Copyright (c) 2015 SUSE LLC. All rights reserved.
//
// 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 main

import (
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"os"
)

const (
	baseProductLoc string = "/etc/products.d/baseproduct"
)

type InstalledProduct struct {
	Identifier string `xml:"name"`
	Version string `xml:"version`
	Arch string `xml:"arch"`
}

// parses installed product data
func ParseInstalledProduct(reader io.Reader) (InstalledProduct, error) {
	xmlData, err := ioutil.ReadAll(reader)
	if err != nil {
		return InstalledProduct{}, fmt.Errorf("Can't read base product file: %v", err.Error())
	}

	var p InstalledProduct
	xml.Unmarshal(xmlData, &p)
	if err != nil {
		return InstalledProduct{}, fmt.Errorf("Can't parse base product file: %v", err.Error())
	}

	return p, nil
}

// read the product file from the standard location
func ReadInstalledProduct() (InstalledProduct, error) {
	if _, err := os.Stat(baseProductLoc); os.IsNotExist(err) {
		return InstalledProduct{}, fmt.Errorf("No base product detected")
	}

	xmlFile, err := os.Open(baseProductLoc)
	if err != nil {
		return InstalledProduct{}, fmt.Errorf("Can't open base product file: %v", err.Error())
	}
	defer xmlFile.Close()

	return ParseInstalledProduct(xmlFile)
}
<commit_msg>typo in annotation<commit_after>//
// Copyright (c) 2015 SUSE LLC. All rights reserved.
//
// 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 main

import (
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"os"
)

const (
	baseProductLoc string = "/etc/products.d/baseproduct"
)

type InstalledProduct struct {
	Identifier string `xml:"name"`
	Version string `xml:"version"`
	Arch string `xml:"arch"`
}

// parses installed product data
func ParseInstalledProduct(reader io.Reader) (InstalledProduct, error) {
	xmlData, err := ioutil.ReadAll(reader)
	if err != nil {
		return InstalledProduct{}, fmt.Errorf("Can't read base product file: %v", err.Error())
	}

	var p InstalledProduct
	xml.Unmarshal(xmlData, &p)
	if err != nil {
		return InstalledProduct{}, fmt.Errorf("Can't parse base product file: %v", err.Error())
	}

	return p, nil
}

// read the product file from the standard location
func ReadInstalledProduct() (InstalledProduct, error) {
	if _, err := os.Stat(baseProductLoc); os.IsNotExist(err) {
		return InstalledProduct{}, fmt.Errorf("No base product detected")
	}

	xmlFile, err := os.Open(baseProductLoc)
	if err != nil {
		return InstalledProduct{}, fmt.Errorf("Can't open base product file: %v", err.Error())
	}
	defer xmlFile.Close()

	return ParseInstalledProduct(xmlFile)
}
<|endoftext|>


<commit_before>// Copyright 2020 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
//
// 	https://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 safehttp

import (
	"fmt"
	"log"
	"net/http"
)

// The HTTP request methods defined by RFC.
const (
	MethodConnect = "CONNECT" // RFC 7231, 4.3.6
	MethodDelete  = "DELETE"  // RFC 7231, 4.3.5
	MethodGet     = "GET"     // RFC 7231, 4.3.1
	MethodHead    = "HEAD"    // RFC 7231, 4.3.2
	MethodOptions = "OPTIONS" // RFC 7231, 4.3.7
	MethodPatch   = "PATCH"   // RFC 5789
	MethodPost    = "POST"    // RFC 7231, 4.3.3
	MethodPut     = "PUT"     // RFC 7231, 4.3.4
	MethodTrace   = "TRACE"   // RFC 7231, 4.3.8
)

// ServeMux is an HTTP request multiplexer. It matches the URL of each incoming
// request against a list of registered patterns and calls the handler for
// the pattern that most closely matches the URL.
//
// Patterns names are fixed, rooted paths, like "/favicon.ico", or rooted
// subtrees like "/images/" (note the trailing slash). Longer patterns take
// precedence over shorter ones, so that if there are handlers registered for
// both "/images/" and "/images/thumbnails/", the latter handler will be called
// for paths beginning "/images/thumbnails/" and the former will receive
// requests for any other paths in the "/images/" subtree.
//
// Note that since a pattern ending in a slash names a rooted subtree, the
// pattern "/" matches all paths not matched by other registered patterns,
// not just the URL with Path == "/".
//
// If a subtree has been registered and a request is received naming the subtree
// root without its trailing slash, ServeMux redirects that request to
// the subtree root (adding the trailing slash). This behavior can be overridden
// with a separate registration for the path without the trailing slash. For
// example, registering "/images/" causes ServeMux to redirect a request for
// "/images" to "/images/", unless "/images" has been registered separately.
//
// Patterns may optionally begin with a host name, restricting matches to URLs
// on that host only.  Host-specific patterns take precedence over general
// patterns, so that a handler might register for the two patterns "/codesearch"
// and "codesearch.google.com/" without also taking over requests for
// "http://www.google.com/".
//
// ServeMux also takes care of sanitizing the URL request path and the Host
// header, stripping the port number and redirecting any request containing . or
// .. elements or repeated slashes to an equivalent, cleaner URL.
//
// Multiple handlers can be registered for a single pattern, as long as they
// handle different HTTP methods.
type ServeMux struct {
	mux      *http.ServeMux
	handlers map[string]*registeredHandler

	dispatcher       Dispatcher
	interceptors     []Interceptor
	methodNotAllowed handlerConfig
}

// ServeHTTP dispatches the request to the handler whose method matches the
// incoming request and whose pattern most closely matches the request URL.
//
//  For each incoming request:
//  - [Before Phase] Interceptor.Before methods are called for every installed
//    interceptor, until an interceptor writes to a ResponseWriter (including
//    errors) or panics,
//  - the handler is called after a [Before Phase] if no writes or panics occured,
//  - the handler triggers the [Commit Phase] by writing to the ResponseWriter,
//  - [Commit Phase] Interceptor.Commit methods run for every interceptor whose
//    Before method was called,
//  - [Dispatcher Phase] after the [Commit Phase], the Dispatcher's appropriate
//    write method is called; the Dispatcher is responsible for determining whether
//    the response is indeed safe and writing it,
//  - if the handler attempts to write more than once, it is treated as an
//    unrecoverable error; the request processing ends abrubptly with a panic and
//    nothing else happens (note: this will change as soon as [After Phase] is
//    introduced)
//
// Interceptors should NOT rely on the order they're run.
func (m *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	m.mux.ServeHTTP(w, r)
}

// Handle registers a handler for the given pattern and method. If a handler is
// registered twice for the same pattern and method, Build will panic.
//
// InterceptorConfigs can be passed in order to modify the behavior of the
// interceptors on a registered handler. Passing an InterceptorConfig whose
// corresponding Interceptor was not installed will produce no effect. If
// multiple configurations are passed for the same Interceptor, Mux will panic.
func (m *ServeMux) Handle(pattern string, method string, h Handler, cfgs ...InterceptorConfig) {
	if m.handlers[pattern] == nil {
		m.handlers[pattern] = &registeredHandler{
			pattern:          pattern,
			methodNotAllowed: m.methodNotAllowed,
			methods:          make(map[string]handlerConfig),
		}
		m.mux.Handle(pattern, m.handlers[pattern])
	}
	m.handlers[pattern].handleMethod(method,
		handlerConfig{
			Dispatcher:   m.dispatcher,
			Handler:      h,
			Interceptors: configureInterceptors(m.interceptors, cfgs),
		})
}

// ServeMuxConfig is a builder for ServeMux.
type ServeMuxConfig struct {
	dispatcher   Dispatcher
	interceptors []Interceptor

	methodNotAllowed     Handler
	methodNotAllowedCfgs []InterceptorConfig
}

// NewServeMuxConfig crates a ServeMuxConfig with the provided Dispatcher. If
// the provided Dispatcher is nil, the DefaultDispatcher is used.
func NewServeMuxConfig(disp Dispatcher) *ServeMuxConfig {
	if disp == nil {
		disp = &DefaultDispatcher{}
	}
	return &ServeMuxConfig{
		dispatcher:       disp,
		methodNotAllowed: HandlerFunc(defaultMethotNotAllowed),
	}
}

// HandleMethodNotAllowed registers a handler that runs when a given method is
// not allowed for a registered path.
func (s *ServeMuxConfig) HandleMethodNotAllowed(h Handler, cfgs ...InterceptorConfig) {
	s.methodNotAllowed = h
	s.methodNotAllowedCfgs = cfgs
}

var defaultMethotNotAllowed = HandlerFunc(func(w ResponseWriter, req *IncomingRequest) Result {
	return w.WriteError(StatusMethodNotAllowed)
})

// Intercept installs the given interceptors.
//
// Interceptors order is respected and interceptors are always run in the
// order they've been installed.
//
// Calling Intercept multiple times is valid. Interceptors that are added last
// will run last.
func (s *ServeMuxConfig) Intercept(is ...Interceptor) {
	s.interceptors = append(s.interceptors, is...)
}

// Mux returns the ServeMux with a copy of the current configuration.
func (s *ServeMuxConfig) Mux() *ServeMux {
	freezeLocalDev = true
	if IsLocalDev() {
		log.Println("Warning: creating safehttp.Mux in dev mode. This configuration is not valid for production use")
	}

	if s.dispatcher == nil {
		panic("Use NewServeMuxConfig instead of creating ServeMuxConfig using a composite literal.")
	}

	methodNotAllowed := handlerConfig{
		Dispatcher:   s.dispatcher,
		Handler:      s.methodNotAllowed,
		Interceptors: configureInterceptors(s.interceptors, s.methodNotAllowedCfgs),
	}

	m := &ServeMux{
		mux:              http.NewServeMux(),
		handlers:         make(map[string]*registeredHandler),
		dispatcher:       s.dispatcher,
		interceptors:     s.interceptors,
		methodNotAllowed: methodNotAllowed,
	}
	return m
}

// Clone creates a copy of the current config.
// This can be used to create several instances of Mux that share the same set of
// plugins.
func (s *ServeMuxConfig) Clone() *ServeMuxConfig {
	c := &ServeMuxConfig{
		dispatcher:       s.dispatcher,
		interceptors:     make([]Interceptor, len(s.interceptors)),
		methodNotAllowed: s.methodNotAllowed,
	}
	copy(c.interceptors, s.interceptors)
	copy(c.methodNotAllowedCfgs, s.methodNotAllowedCfgs)
	return c
}

type registeredHandler struct {
	pattern          string
	methods          map[string]handlerConfig
	methodNotAllowed handlerConfig
}

func (rh *registeredHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	cfg, ok := rh.methods[r.Method]
	if !ok {
		cfg = rh.methodNotAllowed
	}
	processRequest(cfg, w, r)
}

func (rh *registeredHandler) handleMethod(method string, cfg handlerConfig) {
	if _, exists := rh.methods[method]; exists {
		panic(fmt.Sprintf("double registration of (pattern = %q, method = %q)", rh.pattern, method))
	}
	rh.methods[method] = cfg
}

func configureInterceptors(interceptors []Interceptor, cfgs []InterceptorConfig) []configuredInterceptor {
	var its []configuredInterceptor
	for _, it := range interceptors {
		var matches []InterceptorConfig
		for _, c := range cfgs {
			if it.Match(c) {
				matches = append(matches, c)
			}
		}

		if len(matches) > 1 {
			msg := fmt.Sprintf("multiple configurations specified for interceptor %T: ", it)
			for _, match := range matches {
				msg += fmt.Sprintf("%#v", match)
			}
			panic(msg)
		}

		var cfg InterceptorConfig
		if len(matches) == 1 {
			cfg = matches[0]
		}
		its = append(its, configuredInterceptor{interceptor: it, config: cfg})
	}
	return its
}
<commit_msg>append to nil slices instead of calling copy()<commit_after>// Copyright 2020 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
//
// 	https://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 safehttp

import (
	"fmt"
	"log"
	"net/http"
)

// The HTTP request methods defined by RFC.
const (
	MethodConnect = "CONNECT" // RFC 7231, 4.3.6
	MethodDelete  = "DELETE"  // RFC 7231, 4.3.5
	MethodGet     = "GET"     // RFC 7231, 4.3.1
	MethodHead    = "HEAD"    // RFC 7231, 4.3.2
	MethodOptions = "OPTIONS" // RFC 7231, 4.3.7
	MethodPatch   = "PATCH"   // RFC 5789
	MethodPost    = "POST"    // RFC 7231, 4.3.3
	MethodPut     = "PUT"     // RFC 7231, 4.3.4
	MethodTrace   = "TRACE"   // RFC 7231, 4.3.8
)

// ServeMux is an HTTP request multiplexer. It matches the URL of each incoming
// request against a list of registered patterns and calls the handler for
// the pattern that most closely matches the URL.
//
// Patterns names are fixed, rooted paths, like "/favicon.ico", or rooted
// subtrees like "/images/" (note the trailing slash). Longer patterns take
// precedence over shorter ones, so that if there are handlers registered for
// both "/images/" and "/images/thumbnails/", the latter handler will be called
// for paths beginning "/images/thumbnails/" and the former will receive
// requests for any other paths in the "/images/" subtree.
//
// Note that since a pattern ending in a slash names a rooted subtree, the
// pattern "/" matches all paths not matched by other registered patterns,
// not just the URL with Path == "/".
//
// If a subtree has been registered and a request is received naming the subtree
// root without its trailing slash, ServeMux redirects that request to
// the subtree root (adding the trailing slash). This behavior can be overridden
// with a separate registration for the path without the trailing slash. For
// example, registering "/images/" causes ServeMux to redirect a request for
// "/images" to "/images/", unless "/images" has been registered separately.
//
// Patterns may optionally begin with a host name, restricting matches to URLs
// on that host only.  Host-specific patterns take precedence over general
// patterns, so that a handler might register for the two patterns "/codesearch"
// and "codesearch.google.com/" without also taking over requests for
// "http://www.google.com/".
//
// ServeMux also takes care of sanitizing the URL request path and the Host
// header, stripping the port number and redirecting any request containing . or
// .. elements or repeated slashes to an equivalent, cleaner URL.
//
// Multiple handlers can be registered for a single pattern, as long as they
// handle different HTTP methods.
type ServeMux struct {
	mux      *http.ServeMux
	handlers map[string]*registeredHandler

	dispatcher       Dispatcher
	interceptors     []Interceptor
	methodNotAllowed handlerConfig
}

// ServeHTTP dispatches the request to the handler whose method matches the
// incoming request and whose pattern most closely matches the request URL.
//
//  For each incoming request:
//  - [Before Phase] Interceptor.Before methods are called for every installed
//    interceptor, until an interceptor writes to a ResponseWriter (including
//    errors) or panics,
//  - the handler is called after a [Before Phase] if no writes or panics occured,
//  - the handler triggers the [Commit Phase] by writing to the ResponseWriter,
//  - [Commit Phase] Interceptor.Commit methods run for every interceptor whose
//    Before method was called,
//  - [Dispatcher Phase] after the [Commit Phase], the Dispatcher's appropriate
//    write method is called; the Dispatcher is responsible for determining whether
//    the response is indeed safe and writing it,
//  - if the handler attempts to write more than once, it is treated as an
//    unrecoverable error; the request processing ends abrubptly with a panic and
//    nothing else happens (note: this will change as soon as [After Phase] is
//    introduced)
//
// Interceptors should NOT rely on the order they're run.
func (m *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	m.mux.ServeHTTP(w, r)
}

// Handle registers a handler for the given pattern and method. If a handler is
// registered twice for the same pattern and method, Build will panic.
//
// InterceptorConfigs can be passed in order to modify the behavior of the
// interceptors on a registered handler. Passing an InterceptorConfig whose
// corresponding Interceptor was not installed will produce no effect. If
// multiple configurations are passed for the same Interceptor, Mux will panic.
func (m *ServeMux) Handle(pattern string, method string, h Handler, cfgs ...InterceptorConfig) {
	if m.handlers[pattern] == nil {
		m.handlers[pattern] = &registeredHandler{
			pattern:          pattern,
			methodNotAllowed: m.methodNotAllowed,
			methods:          make(map[string]handlerConfig),
		}
		m.mux.Handle(pattern, m.handlers[pattern])
	}
	m.handlers[pattern].handleMethod(method,
		handlerConfig{
			Dispatcher:   m.dispatcher,
			Handler:      h,
			Interceptors: configureInterceptors(m.interceptors, cfgs),
		})
}

// ServeMuxConfig is a builder for ServeMux.
type ServeMuxConfig struct {
	dispatcher   Dispatcher
	interceptors []Interceptor

	methodNotAllowed     Handler
	methodNotAllowedCfgs []InterceptorConfig
}

// NewServeMuxConfig crates a ServeMuxConfig with the provided Dispatcher. If
// the provided Dispatcher is nil, the DefaultDispatcher is used.
func NewServeMuxConfig(disp Dispatcher) *ServeMuxConfig {
	if disp == nil {
		disp = &DefaultDispatcher{}
	}
	return &ServeMuxConfig{
		dispatcher:       disp,
		methodNotAllowed: HandlerFunc(defaultMethotNotAllowed),
	}
}

// HandleMethodNotAllowed registers a handler that runs when a given method is
// not allowed for a registered path.
func (s *ServeMuxConfig) HandleMethodNotAllowed(h Handler, cfgs ...InterceptorConfig) {
	s.methodNotAllowed = h
	s.methodNotAllowedCfgs = cfgs
}

var defaultMethotNotAllowed = HandlerFunc(func(w ResponseWriter, req *IncomingRequest) Result {
	return w.WriteError(StatusMethodNotAllowed)
})

// Intercept installs the given interceptors.
//
// Interceptors order is respected and interceptors are always run in the
// order they've been installed.
//
// Calling Intercept multiple times is valid. Interceptors that are added last
// will run last.
func (s *ServeMuxConfig) Intercept(is ...Interceptor) {
	s.interceptors = append(s.interceptors, is...)
}

// Mux returns the ServeMux with a copy of the current configuration.
func (s *ServeMuxConfig) Mux() *ServeMux {
	freezeLocalDev = true
	if IsLocalDev() {
		log.Println("Warning: creating safehttp.Mux in dev mode. This configuration is not valid for production use")
	}

	if s.dispatcher == nil {
		panic("Use NewServeMuxConfig instead of creating ServeMuxConfig using a composite literal.")
	}

	methodNotAllowed := handlerConfig{
		Dispatcher:   s.dispatcher,
		Handler:      s.methodNotAllowed,
		Interceptors: configureInterceptors(s.interceptors, s.methodNotAllowedCfgs),
	}

	m := &ServeMux{
		mux:              http.NewServeMux(),
		handlers:         make(map[string]*registeredHandler),
		dispatcher:       s.dispatcher,
		interceptors:     s.interceptors,
		methodNotAllowed: methodNotAllowed,
	}
	return m
}

// Clone creates a copy of the current config.
// This can be used to create several instances of Mux that share the same set of
// plugins.
func (s *ServeMuxConfig) Clone() *ServeMuxConfig {
	return &ServeMuxConfig{
		dispatcher:           s.dispatcher,
		interceptors:         append([]Interceptor(nil), s.interceptors...),
		methodNotAllowed:     s.methodNotAllowed,
		methodNotAllowedCfgs: append([]InterceptorConfig(nil), s.methodNotAllowedCfgs...),
	}
}

type registeredHandler struct {
	pattern          string
	methods          map[string]handlerConfig
	methodNotAllowed handlerConfig
}

func (rh *registeredHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	cfg, ok := rh.methods[r.Method]
	if !ok {
		cfg = rh.methodNotAllowed
	}
	processRequest(cfg, w, r)
}

func (rh *registeredHandler) handleMethod(method string, cfg handlerConfig) {
	if _, exists := rh.methods[method]; exists {
		panic(fmt.Sprintf("double registration of (pattern = %q, method = %q)", rh.pattern, method))
	}
	rh.methods[method] = cfg
}

func configureInterceptors(interceptors []Interceptor, cfgs []InterceptorConfig) []configuredInterceptor {
	var its []configuredInterceptor
	for _, it := range interceptors {
		var matches []InterceptorConfig
		for _, c := range cfgs {
			if it.Match(c) {
				matches = append(matches, c)
			}
		}

		if len(matches) > 1 {
			msg := fmt.Sprintf("multiple configurations specified for interceptor %T: ", it)
			for _, match := range matches {
				msg += fmt.Sprintf("%#v", match)
			}
			panic(msg)
		}

		var cfg InterceptorConfig
		if len(matches) == 1 {
			cfg = matches[0]
		}
		its = append(its, configuredInterceptor{interceptor: it, config: cfg})
	}
	return its
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

package cmd

import (
	"io"
	"net/http"
	"strconv"
	"strings"

	"github.com/issue9/cmdopt"
	"github.com/issue9/errwrap"
	"github.com/issue9/rands"

	"github.com/caixw/apidoc/v7"
	"github.com/caixw/apidoc/v7/core"
	"github.com/caixw/apidoc/v7/internal/locale"
)

// servers 参数
type (
	servers map[string]string
	size    apidoc.Range
	slice   []string
)

func (s servers) Get() interface{} {
	return map[string]string(s)
}

func (s servers) Set(v string) error {
	pairs := strings.Split(v, ",")
	for _, pair := range pairs {
		index := strings.IndexByte(pair, '=')
		if index <= 0 {
			return locale.NewError(locale.ErrInvalidValue)
		}

		var v string
		if index < len(pair) {
			v = pair[index+1:]
		}
		s[strings.TrimSpace(pair[:index])] = v
	}

	return nil
}

func (s servers) String() string {
	if len(s) == 0 {
		return ""
	}

	var buf errwrap.Buffer
	for k, v := range s {
		buf.WString(k).WByte('=').WString(v).WByte(',')
	}
	buf.Truncate(buf.Len() - 1)
	if buf.Err != nil {
		panic(buf.Err)
	}
	return buf.String()
}

func (r size) Get() interface{} {
	return r
}

func (r *size) Set(v string) (err error) {
	pairs := strings.Split(v, ",")
	if len(pairs) != 2 {
		return locale.NewError(locale.ErrInvalidFormat)
	}

	left := strings.TrimSpace(pairs[0])
	if len(left) == 0 {
		return locale.NewError(locale.ErrInvalidFormat)
	}
	if r.Min, err = strconv.Atoi(left); err != nil {
		return err
	}

	right := strings.TrimSpace(pairs[1])
	if len(right) == 0 {
		return locale.NewError(locale.ErrInvalidFormat)
	}
	if r.Max, err = strconv.Atoi(right); err != nil {
		return err
	}

	return nil
}

func (r *size) String() string {
	return strconv.Itoa(r.Min) + "," + strconv.Itoa(r.Max)
}

func (s slice) Get() interface{} {
	return []string(s)
}

func (s *slice) Set(v string) (err error) {
	*s = strings.Split(v, ",")
	return nil
}

func (s *slice) String() string {
	return strings.Join(*s, ",")
}

var (
	mockPort         string
	mockServers      = make(servers, 0)
	mockStringAlpha  string
	mockOptions      = &apidoc.MockOptions{}
	mockPath         = uri("./")
	mockSliceSize    = &size{Min: 5, Max: 10}
	mockNumberSize   = &size{Min: 100, Max: 10000}
	mockStringSize   = &size{Min: 50, Max: 1024}
	mockUsernameSize = &size{Min: 5, Max: 8}
	mockEmailDomains = &slice{"example.com"}
	mockURLDomains   = &slice{"https://example.com"}
)

func initMock(command *cmdopt.CmdOpt) {
	fs := command.New("mock", locale.Sprintf(locale.CmdMockUsage), doMock)
	fs.StringVar(&mockPort, "p", ":8080", locale.Sprintf(locale.FlagMockPortUsage))
	fs.Var(mockServers, "s", locale.Sprintf(locale.FlagMockServersUsage))
	fs.Var(&mockPath, "path", locale.Sprintf(locale.FlagMockPathUsage))

	fs.StringVar(&mockOptions.Indent, "indent", "\t", locale.Sprintf(locale.FlagMockIndentUsage))

	fs.Var(mockSliceSize, "slice.size", locale.Sprintf(locale.FlagMockSliceSizeUsage))

	fs.Var(mockNumberSize, "num.size", locale.Sprintf(locale.FlagMockNumSliceUsage))
	fs.BoolVar(&mockOptions.EnableFloat, "num.float", false, locale.Sprintf(locale.FlagMockNumFloatUsage))

	fs.Var(mockStringSize, "string.size", locale.Sprintf(locale.FlagMockStringSizeUsage))
	fs.StringVar(&mockStringAlpha, "string.alpha", string(rands.AlphaNumber), locale.Sprintf(locale.FlagMockStringAlphaUsage))

	fs.Var(mockUsernameSize, "email.username", locale.Sprintf(locale.FlagMockUsernameSizeUsage))
	fs.Var(mockEmailDomains, "email.domains", locale.Sprintf(locale.FlagMockEmailDomainsUsage))
	fs.Var(mockURLDomains, "url.domains", locale.Sprintf(locale.FlagMockURLDomainsUsage))

	fs.StringVar(&mockOptions.ImageBasePrefix, "image.prefix", "/__image__", locale.Sprintf(locale.FlagMockImagePrefixUsage))
}

func doMock(io.Writer) error {
	h := core.NewMessageHandler(messageHandle)
	defer h.Stop()

	mockOptions.Servers = mockServers
	mockOptions.StringAlpha = []byte(mockStringAlpha)
	mockOptions.SliceSize = apidoc.Range(*mockSliceSize)
	mockOptions.NumberSize = apidoc.Range(*mockNumberSize)
	mockOptions.StringSize = apidoc.Range(*mockStringSize)
	mockOptions.EmailUsernameSize = apidoc.Range(*mockUsernameSize)
	handler, err := apidoc.MockFile(h, core.URI(mockPath), mockOptions)
	if err != nil {
		return err
	}

	url := "http://localhost" + mockPort
	h.Locale(core.Succ, locale.ServerStart, url)

	return http.ListenAndServe(mockPort, handler)
}
<commit_msg>refactor(internal/cmd): 调整 mock 子命令的 s 为 servers<commit_after>// SPDX-License-Identifier: MIT

package cmd

import (
	"io"
	"net/http"
	"strconv"
	"strings"

	"github.com/issue9/cmdopt"
	"github.com/issue9/errwrap"
	"github.com/issue9/rands"

	"github.com/caixw/apidoc/v7"
	"github.com/caixw/apidoc/v7/core"
	"github.com/caixw/apidoc/v7/internal/locale"
)

// servers 参数
type (
	servers map[string]string
	size    apidoc.Range
	slice   []string
)

func (s servers) Get() interface{} {
	return map[string]string(s)
}

func (s servers) Set(v string) error {
	pairs := strings.Split(v, ",")
	for _, pair := range pairs {
		index := strings.IndexByte(pair, '=')
		if index <= 0 {
			return locale.NewError(locale.ErrInvalidValue)
		}

		var v string
		if index < len(pair) {
			v = pair[index+1:]
		}
		s[strings.TrimSpace(pair[:index])] = v
	}

	return nil
}

func (s servers) String() string {
	if len(s) == 0 {
		return ""
	}

	var buf errwrap.Buffer
	for k, v := range s {
		buf.WString(k).WByte('=').WString(v).WByte(',')
	}
	buf.Truncate(buf.Len() - 1)
	if buf.Err != nil {
		panic(buf.Err)
	}
	return buf.String()
}

func (r size) Get() interface{} {
	return r
}

func (r *size) Set(v string) (err error) {
	pairs := strings.Split(v, ",")
	if len(pairs) != 2 {
		return locale.NewError(locale.ErrInvalidFormat)
	}

	left := strings.TrimSpace(pairs[0])
	if len(left) == 0 {
		return locale.NewError(locale.ErrInvalidFormat)
	}
	if r.Min, err = strconv.Atoi(left); err != nil {
		return err
	}

	right := strings.TrimSpace(pairs[1])
	if len(right) == 0 {
		return locale.NewError(locale.ErrInvalidFormat)
	}
	if r.Max, err = strconv.Atoi(right); err != nil {
		return err
	}

	return nil
}

func (r *size) String() string {
	return strconv.Itoa(r.Min) + "," + strconv.Itoa(r.Max)
}

func (s slice) Get() interface{} {
	return []string(s)
}

func (s *slice) Set(v string) (err error) {
	*s = strings.Split(v, ",")
	return nil
}

func (s *slice) String() string {
	return strings.Join(*s, ",")
}

var (
	mockPort         string
	mockServers      = make(servers, 0)
	mockStringAlpha  string
	mockOptions      = &apidoc.MockOptions{}
	mockPath         = uri("./")
	mockSliceSize    = &size{Min: 5, Max: 10}
	mockNumberSize   = &size{Min: 100, Max: 10000}
	mockStringSize   = &size{Min: 50, Max: 1024}
	mockUsernameSize = &size{Min: 5, Max: 8}
	mockEmailDomains = &slice{"example.com"}
	mockURLDomains   = &slice{"https://example.com"}
)

func initMock(command *cmdopt.CmdOpt) {
	fs := command.New("mock", locale.Sprintf(locale.CmdMockUsage), doMock)
	fs.StringVar(&mockPort, "p", ":8080", locale.Sprintf(locale.FlagMockPortUsage))
	fs.Var(mockServers, "servers", locale.Sprintf(locale.FlagMockServersUsage))
	fs.Var(&mockPath, "path", locale.Sprintf(locale.FlagMockPathUsage))

	fs.StringVar(&mockOptions.Indent, "indent", "\t", locale.Sprintf(locale.FlagMockIndentUsage))

	fs.Var(mockSliceSize, "slice.size", locale.Sprintf(locale.FlagMockSliceSizeUsage))

	fs.Var(mockNumberSize, "num.size", locale.Sprintf(locale.FlagMockNumSliceUsage))
	fs.BoolVar(&mockOptions.EnableFloat, "num.float", false, locale.Sprintf(locale.FlagMockNumFloatUsage))

	fs.Var(mockStringSize, "string.size", locale.Sprintf(locale.FlagMockStringSizeUsage))
	fs.StringVar(&mockStringAlpha, "string.alpha", string(rands.AlphaNumber), locale.Sprintf(locale.FlagMockStringAlphaUsage))

	fs.Var(mockUsernameSize, "email.username", locale.Sprintf(locale.FlagMockUsernameSizeUsage))
	fs.Var(mockEmailDomains, "email.domains", locale.Sprintf(locale.FlagMockEmailDomainsUsage))
	fs.Var(mockURLDomains, "url.domains", locale.Sprintf(locale.FlagMockURLDomainsUsage))

	fs.StringVar(&mockOptions.ImageBasePrefix, "image.prefix", "/__image__", locale.Sprintf(locale.FlagMockImagePrefixUsage))
}

func doMock(io.Writer) error {
	h := core.NewMessageHandler(messageHandle)
	defer h.Stop()

	mockOptions.Servers = mockServers
	mockOptions.StringAlpha = []byte(mockStringAlpha)
	mockOptions.SliceSize = apidoc.Range(*mockSliceSize)
	mockOptions.NumberSize = apidoc.Range(*mockNumberSize)
	mockOptions.StringSize = apidoc.Range(*mockStringSize)
	mockOptions.EmailUsernameSize = apidoc.Range(*mockUsernameSize)
	handler, err := apidoc.MockFile(h, core.URI(mockPath), mockOptions)
	if err != nil {
		return err
	}

	url := "http://localhost" + mockPort
	h.Locale(core.Succ, locale.ServerStart, url)

	return http.ListenAndServe(mockPort, handler)
}
<|endoftext|>


<commit_before>package service

import (
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/oleksandr/bonjour"

	"github.com/subparlabs/bonjourno/log"
)

var (
	nameRe = regexp.MustCompile("[^a-zA-Z0-9-_]")
)

type Service struct {
	host string
	port int

	messages chan string
	stop     chan struct{}
	wg       sync.WaitGroup
}

func New(host string, port int) *Service {
	s := &Service{
		host: host,
		port: port,

		messages: make(chan string),
		stop:     make(chan struct{}),
	}

	s.wg.Add(1)
	go s.start()

	return s
}

func (s *Service) Say(msg string) {
	msg = strings.TrimSpace(msg)

	// The Finder sidebar cuts off somewhere under 20, maybe less, but
	// browsing to the share in "Network" shows somewhere around 40.
	if len(msg) > 40 {
		msg = msg[:40]
	}

	// Some characters cause the service to be ignored completely. Not sure
	// which, so make a conservative conversion.
	// TODO: look up the spec and only replace actually invalid chars
	msg = nameRe.ReplaceAllString(msg, "-")

	s.messages <- msg
}

func (s *Service) Stop() {
	defer s.wg.Wait()

	log.Info("Stopping service")
	close(s.stop)
	s.stop = nil
}

func (s *Service) start() {
	defer s.wg.Done()

	var bonj *bonjour.Server
	defer s.stopBonjour(bonj)

	var err error
	var msg string

	for {
		select {
		case newMsg := <-s.messages:
			if newMsg != msg {
				msg = newMsg

				s.stopBonjour(bonj)

				log.Info("Registering service", "name", msg, "host", s.host, "port", s.port)
				bonj, err = bonjour.RegisterProxy(
					msg,
					"_afpovertcp._tcp", "local",
					s.port, s.host, s.host,
					nil, nil)
				if err != nil || bonj == nil {
					log.Error("Failed to register service with bonjour", "err", err)
					bonj = nil
				}
			}
		case <-s.stop:
			log.Info("Ending bonjour-updating routine")
			return
		}
	}
}

func (s *Service) stopBonjour(bonj *bonjour.Server) {
	if bonj == nil {
		return
	}

	s.wg.Add(1)
	go func() {
		defer s.wg.Done()

		log.Info("Shutting down bonjour service")
		bonj.Shutdown()

		// I guess bonjour wants us to wait some unspecied
		// amount? This is what blocking or channels are for :/
		waitTime := time.Second * 5
		log.Info("Waiting for bonjour service to clean itself up", "waitTime", waitTime)
		time.Sleep(waitTime)
	}()
}
<commit_msg>Fix cleaning up final bonjour service<commit_after>package service

import (
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/oleksandr/bonjour"

	"github.com/subparlabs/bonjourno/log"
)

var (
	nameRe = regexp.MustCompile("[^a-zA-Z0-9-_]")
)

type Service struct {
	host string
	port int

	messages chan string
	stop     chan struct{}
	wg       sync.WaitGroup
}

func New(host string, port int) *Service {
	s := &Service{
		host: host,
		port: port,

		messages: make(chan string),
		stop:     make(chan struct{}),
	}

	s.wg.Add(1)
	go s.start()

	return s
}

func (s *Service) Say(msg string) {
	msg = strings.TrimSpace(msg)

	// The Finder sidebar cuts off somewhere under 20, maybe less, but
	// browsing to the share in "Network" shows somewhere around 40.
	if len(msg) > 40 {
		msg = msg[:40]
	}

	// Some characters cause the service to be ignored completely. Not sure
	// which, so make a conservative conversion.
	// TODO: look up the spec and only replace actually invalid chars
	msg = nameRe.ReplaceAllString(msg, "-")

	s.messages <- msg
}

func (s *Service) Stop() {
	defer s.wg.Wait()

	log.Info("Stopping service")
	close(s.stop)
	s.stop = nil
}

func (s *Service) start() {
	defer s.wg.Done()

	var bonj *bonjour.Server
	defer func(b **bonjour.Server) {
		s.stopBonjour(*b)
	}(&bonj)

	var err error
	var msg string

	for {
		select {
		case newMsg := <-s.messages:
			if newMsg != msg {
				msg = newMsg

				s.stopBonjour(bonj)

				log.Info("Registering service", "name", msg, "host", s.host, "port", s.port)
				bonj, err = bonjour.RegisterProxy(
					msg,
					"_afpovertcp._tcp", "local",
					s.port, s.host, s.host,
					nil, nil)
				if err != nil || bonj == nil {
					log.Error("Failed to register service with bonjour", "err", err)
					bonj = nil
				}
			}
		case <-s.stop:
			log.Info("Ending bonjour-updating routine")
			return
		}
	}
}

func (s *Service) stopBonjour(bonj *bonjour.Server) {
	if bonj == nil {
		return
	}

	s.wg.Add(1)
	go func() {
		defer s.wg.Done()

		log.Info("Shutting down bonjour service")
		bonj.Shutdown()

		// I guess bonjour wants us to wait some unspecied
		// amount? This is what blocking or channels are for :/
		waitTime := time.Second * 5
		log.Info("Waiting for bonjour service to clean itself up", "waitTime", waitTime)
		time.Sleep(waitTime)
	}()
}
<|endoftext|>