<commit_before>package stripe

import (
	"encoding/json"

	"fmt"
	"net/url"
)

// CardBrand is the list of allowed values for the card's brand.
// Allowed values are "Unknown", "Visa", "American Express", "MasterCard", "Discover"
// "JCB", "Diners Club".
type CardBrand string

// Verification is the list of allowed verification responses.
// Allowed values are "pass", "fail", "unchecked", "unavailabe".
type Verification string

// CardFunding is the list of allowed values for the card's funding.
// Allowed values are "credit", "debit", "prepaid", "unknown".
type CardFunding string

// CardParams is the set of parameters that can be used when creating or updating a card.
// For more details see https://stripe.com/docs/api#create_card and https://stripe.com/docs/api#update_card.
type CardParams struct {
	Params
	Token                                         string
	Customer, Recipient                           string
	Name, Number, Month, Year, CVC                string
	Address1, Address2, City, State, Zip, Country string
}

// CardListParams is the set of parameters that can be used when listing cards.
// For more details see https://stripe.com/docs/api#list_cards.
type CardListParams struct {
	ListParams
	Customer, Recipient string
}

// Card is the resource representing a Stripe credit/debit card.
// For more details see https://stripe.com/docs/api#cards.
type Card struct {
	ID            string       `json:"id"`
	Month         uint8        `json:"exp_month"`
	Year          uint16       `json:"exp_year"`
	Fingerprint   string       `json:"fingerprint"`
	Funding       CardFunding  `json:"funding"`
	LastFour      string       `json:"last4"`
	Brand         CardBrand    `json:"brand"`
	City          string       `json:"address_city"`
	Country       string       `json:"address_country"`
	Address1      string       `json:"address_line1"`
	Address1Check Verification `json:"address_line1_check"`
	Address2      string       `json:"address_line2"`
	State         string       `json:"address_state"`
	Zip           string       `json:"address_zip"`
	ZipCheck      Verification `json:"address_zip_check"`
	CardCountry   string       `json:"country"`
	Customer      *Customer    `json:"customer"`
	CVCCheck      Verification `json:"cvc_check"`
	Name          string       `json:"name"`
	Recipient     *Recipient   `json:"recipient"`
	DynLastFour   string       `json:"dynamic_last_four"`
}

// CardList is a list object for cards.
type CardList struct {
	ListMeta
	Values []*Card `json:"data"`
}

// AppendDetails adds the card's details to the query string values.
// When creating a new card, the parameters are passed as a dictionary, but
// on updates they are simply the parameter name.
func (c *CardParams) AppendDetails(values *url.Values, creating bool) {
	if creating {
		if len(c.Token) > 0 {
			values.Add("card", c.Token)
		} else {
			values.Add("card[object]", "card")
			values.Add("card[number]", c.Number)
			values.Add("card[exp_month]", c.Month)
			values.Add("card[exp_year]", c.Year)

			if len(c.CVC) > 0 {
				values.Add("card[cvc]", c.CVC)
			}
		}
	}

	if len(c.Name) > 0 {
		if creating {
			values.Add("card[name]", c.Name)
		} else {
			values.Add("name", c.Name)
		}
	}

	if len(c.Address1) > 0 {
		if creating {
			values.Add("card[address_line1]", c.Address1)
		} else {
			values.Add("address_line1", c.Address1)
		}
	}

	if len(c.Address2) > 0 {
		if creating {
			values.Add("card[address_line2]", c.Address2)
		} else {
			values.Add("address_line2", c.Address2)
		}
	}

	if len(c.City) > 0 {
		if creating {
			values.Add("card[address_city]", c.City)
		} else {
			values.Add("address_city", c.City)
		}
	}

	if len(c.State) > 0 {
		if creating {
			values.Add("card[address_state]", c.State)
		} else {
			values.Add("address_state", c.State)
		}
	}

	if len(c.Zip) > 0 {
		if creating {
			values.Add("card[address_zip]", c.Zip)
		} else {
			values.Add("address_zip", c.Zip)
		}
	}

	if len(c.Country) > 0 {
		if creating {
			values.Add("card[address_country]", c.Country)
		} else {
			values.Add("address_country", c.Country)
		}
	}
}

// Display human readable representation of a Card.
func (c *Card) Display() string {
	return fmt.Sprintf("%s ending in %s", c.Brand, c.LastFour)
}

// UnmarshalJSON handles deserialization of a Card.
// This custom unmarshaling is needed because the resulting
// property may be an id or the full struct if it was expanded.
func (c *Card) UnmarshalJSON(data []byte) error {
	type card Card
	var cc card
	err := json.Unmarshal(data, &cc)
	if err == nil {
		*c = Card(cc)
	} else {
		// the id is surrounded by "\" characters, so strip them
		c.ID = string(data[1 : len(data)-1])
	}

	return nil
}
<commit_msg>s/last_four/last4<commit_after>package stripe

import (
	"encoding/json"

	"fmt"
	"net/url"
)

// CardBrand is the list of allowed values for the card's brand.
// Allowed values are "Unknown", "Visa", "American Express", "MasterCard", "Discover"
// "JCB", "Diners Club".
type CardBrand string

// Verification is the list of allowed verification responses.
// Allowed values are "pass", "fail", "unchecked", "unavailabe".
type Verification string

// CardFunding is the list of allowed values for the card's funding.
// Allowed values are "credit", "debit", "prepaid", "unknown".
type CardFunding string

// CardParams is the set of parameters that can be used when creating or updating a card.
// For more details see https://stripe.com/docs/api#create_card and https://stripe.com/docs/api#update_card.
type CardParams struct {
	Params
	Token                                         string
	Customer, Recipient                           string
	Name, Number, Month, Year, CVC                string
	Address1, Address2, City, State, Zip, Country string
}

// CardListParams is the set of parameters that can be used when listing cards.
// For more details see https://stripe.com/docs/api#list_cards.
type CardListParams struct {
	ListParams
	Customer, Recipient string
}

// Card is the resource representing a Stripe credit/debit card.
// For more details see https://stripe.com/docs/api#cards.
type Card struct {
	ID            string       `json:"id"`
	Month         uint8        `json:"exp_month"`
	Year          uint16       `json:"exp_year"`
	Fingerprint   string       `json:"fingerprint"`
	Funding       CardFunding  `json:"funding"`
	LastFour      string       `json:"last4"`
	Brand         CardBrand    `json:"brand"`
	City          string       `json:"address_city"`
	Country       string       `json:"address_country"`
	Address1      string       `json:"address_line1"`
	Address1Check Verification `json:"address_line1_check"`
	Address2      string       `json:"address_line2"`
	State         string       `json:"address_state"`
	Zip           string       `json:"address_zip"`
	ZipCheck      Verification `json:"address_zip_check"`
	CardCountry   string       `json:"country"`
	Customer      *Customer    `json:"customer"`
	CVCCheck      Verification `json:"cvc_check"`
	Name          string       `json:"name"`
	Recipient     *Recipient   `json:"recipient"`
	DynLastFour   string       `json:"dynamic_last4"`
}

// CardList is a list object for cards.
type CardList struct {
	ListMeta
	Values []*Card `json:"data"`
}

// AppendDetails adds the card's details to the query string values.
// When creating a new card, the parameters are passed as a dictionary, but
// on updates they are simply the parameter name.
func (c *CardParams) AppendDetails(values *url.Values, creating bool) {
	if creating {
		if len(c.Token) > 0 {
			values.Add("card", c.Token)
		} else {
			values.Add("card[object]", "card")
			values.Add("card[number]", c.Number)
			values.Add("card[exp_month]", c.Month)
			values.Add("card[exp_year]", c.Year)

			if len(c.CVC) > 0 {
				values.Add("card[cvc]", c.CVC)
			}
		}
	}

	if len(c.Name) > 0 {
		if creating {
			values.Add("card[name]", c.Name)
		} else {
			values.Add("name", c.Name)
		}
	}

	if len(c.Address1) > 0 {
		if creating {
			values.Add("card[address_line1]", c.Address1)
		} else {
			values.Add("address_line1", c.Address1)
		}
	}

	if len(c.Address2) > 0 {
		if creating {
			values.Add("card[address_line2]", c.Address2)
		} else {
			values.Add("address_line2", c.Address2)
		}
	}

	if len(c.City) > 0 {
		if creating {
			values.Add("card[address_city]", c.City)
		} else {
			values.Add("address_city", c.City)
		}
	}

	if len(c.State) > 0 {
		if creating {
			values.Add("card[address_state]", c.State)
		} else {
			values.Add("address_state", c.State)
		}
	}

	if len(c.Zip) > 0 {
		if creating {
			values.Add("card[address_zip]", c.Zip)
		} else {
			values.Add("address_zip", c.Zip)
		}
	}

	if len(c.Country) > 0 {
		if creating {
			values.Add("card[address_country]", c.Country)
		} else {
			values.Add("address_country", c.Country)
		}
	}
}

// Display human readable representation of a Card.
func (c *Card) Display() string {
	return fmt.Sprintf("%s ending in %s", c.Brand, c.LastFour)
}

// UnmarshalJSON handles deserialization of a Card.
// This custom unmarshaling is needed because the resulting
// property may be an id or the full struct if it was expanded.
func (c *Card) UnmarshalJSON(data []byte) error {
	type card Card
	var cc card
	err := json.Unmarshal(data, &cc)
	if err == nil {
		*c = Card(cc)
	} else {
		// the id is surrounded by "\" characters, so strip them
		c.ID = string(data[1 : len(data)-1])
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"
)

func contains(slice []string, str string) bool {
	for _, elem := range slice {
		if elem == str {
			return true
		}
	}
	return false
}

type cmd interface {
	Action() []byte
	Help() string
	Names() []string
}

type cmdMatcher interface {
	Match(name string) bool
}

type cmdUp struct{}

func (c *cmdUp) Action() []byte {
	return []byte("u")
}

func (c *cmdUp) Help() string {
	return ""
}

func (c *cmdUp) Names() []string {
	return []string{"up", "start"}
}

type cmdDown struct{}

func (c *cmdDown) Action() []byte {
	return []byte("d")
}

func (c *cmdDown) Help() string {
	return ""
}

func (c *cmdDown) Names() []string {
	return []string{"down", "stop"}
}

type cmdRestart struct{}

func (c *cmdRestart) Action() []byte {
	return []byte("tcu")
}

func (c *cmdRestart) Help() string {
	return ""
}

func (c *cmdRestart) Names() []string {
	return []string{"restart"}
}

type cmdSignal struct {
	action []byte
}

func (c *cmdSignal) Action() []byte {
	return c.action
}

func (c *cmdSignal) Help() string {
	return ""
}

func (c *cmdSignal) Names() []string {
	return []string{
		"pause", "cont", "hup", "alarm", "interrupt",
		"quit", "1", "2", "term", "kill",
	}
}

func (c *cmdSignal) Match(name string) bool {
	for _, s := range c.Names() {
		if name == s || name == s[0:1] {
			c.action = []byte(name[0:1])
			return true
		}
		if name == "reload" {
			c.action = []byte("h")
			return true
		}
	}
	return false
}

func cmdAll() []cmd {
	return []cmd{
		&cmdUp{},
		&cmdDown{},
		&cmdRestart{},
		&cmdSignal{},
	}
}

func cmdMatch(name string) cmd {
	for _, cmd := range cmdAll() {
		m, ok := cmd.(cmdMatcher)
		if (ok && m.Match(name)) || contains(cmd.Names(), name) || string(cmd.Action()) == name {
			return cmd
		}
	}
	return nil
}

func cmdMatchName(partialName string) []string {
	res := []string{}
	for _, cmd := range cmdAll() {
		for _, name := range cmd.Names() {
			if strings.HasPrefix(name, partialName) {
				res = append(res, fmt.Sprintf("%s ", name))
			}
		}
	}
	return res
}
<commit_msg>meaningful help<commit_after>package main

import (
	"fmt"
	"strings"
)

func contains(slice []string, str string) bool {
	for _, elem := range slice {
		if elem == str {
			return true
		}
	}
	return false
}

type cmd interface {
	Action() []byte
	Help() string
	Names() []string
}

type cmdMatcher interface {
	Match(name string) bool
}

type cmdUp struct{}

func (c *cmdUp) Action() []byte {
	return []byte("u")
}

func (c *cmdUp) Help() string {
	return strings.TrimSpace(`
up NAMES...   Starts service(s) with matching NAMES.
              NAMES support globing with '*' and '?'.
`)
}

func (c *cmdUp) Names() []string {
	return []string{"up", "start"}
}

type cmdDown struct{}

func (c *cmdDown) Action() []byte {
	return []byte("d")
}

func (c *cmdDown) Help() string {
	return strings.TrimSpace(`
down NAMES...   Stops service(s) with matching NAMES.
                NAMES support globing with '*' and '?'.
`)
}

func (c *cmdDown) Names() []string {
	return []string{"down", "stop"}
}

type cmdRestart struct{}

func (c *cmdRestart) Action() []byte {
	return []byte("tcu")
}

func (c *cmdRestart) Help() string {
	return strings.TrimSpace(`
restart NAMES...   Restarts service(s) with matching NAMES.
                   NAMES support globing with '*' and '?'.
                   Waits up to 7 seconds for the service to get back up, then
                   reports TIMEOUT.
`)
}

func (c *cmdRestart) Names() []string {
	return []string{"restart"}
}

type cmdSignal struct {
	action string
}

func (c *cmdSignal) Action() []byte {
	if c.action == "" {
		return nil
	}
	if c.action == "reload" {
		return []byte{'h'}
	}
	return []byte(c.action[0:1])
}

func (c *cmdSignal) Help() string {
	m := map[byte]string{
		'p': "STOP", 'c': "CONT", 'h': "HUP", 'r': "HUP", 'a': "ALRM", 'i': "INT",
		'q': "QUIT", '1': "USR1", '2': "USR2", 't': "TERM", 'k': "KILL",
	}
	return fmt.Sprintf(strings.TrimSpace(`
%s NAMES...   Sends signal '%s' to service(s) with matching NAMES.
%-[3]*s            NAMES support globing with '*' and '?'.
`), c.action, m[c.action[0]], len(c.action), "")
}

func (c *cmdSignal) Names() []string {
	return []string{
		"pause", "cont", "hup", "reload", "alarm",
		"interrupt", "quit", "1", "2", "term", "kill",
	}
}

func (c *cmdSignal) Match(name string) bool {
	for _, s := range c.Names() {
		if name == s || name == s[0:1] {
			c.action = s
			return true
		}
	}
	return false
}

func cmdAll() []cmd {
	return []cmd{
		&cmdUp{},
		&cmdDown{},
		&cmdRestart{},
		&cmdSignal{},
	}
}

func cmdMatch(name string) cmd {
	for _, cmd := range cmdAll() {
		m, ok := cmd.(cmdMatcher)
		if (ok && m.Match(name)) || contains(cmd.Names(), name) || string(cmd.Action()) == name {
			return cmd
		}
	}
	return nil
}

func cmdMatchName(partialName string) []string {
	res := []string{}
	for _, cmd := range cmdAll() {
		for _, name := range cmd.Names() {
			if strings.HasPrefix(name, partialName) {
				res = append(res, fmt.Sprintf("%s ", name))
			}
		}
	}
	return res
}
<|endoftext|>


<commit_before>package config

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
	"strconv"
	"time"

	"golang.org/x/crypto/scrypt"

	"github.com/lxc/lxd/lxd/config"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/shared/validate"
)

// Config holds cluster-wide configuration values.
type Config struct {
	tx *db.ClusterTx // DB transaction the values in this config are bound to.
	m  config.Map    // Low-level map holding the config values.
}

// Load loads a new Config object with the current cluster configuration
// values fetched from the database.
func Load(tx *db.ClusterTx) (*Config, error) {
	// Load current raw values from the database, any error is fatal.
	values, err := tx.Config()
	if err != nil {
		return nil, fmt.Errorf("cannot fetch node config from database: %w", err)
	}

	m, err := config.SafeLoad(ConfigSchema, values)
	if err != nil {
		return nil, fmt.Errorf("failed to load node config: %w", err)
	}

	return &Config{tx: tx, m: m}, nil
}

// BackupsCompressionAlgorithm returns the compression algorithm to use for backups.
func (c *Config) BackupsCompressionAlgorithm() string {
	return c.m.GetString("backups.compression_algorithm")
}

// MetricsAuthentication checks whether metrics API requires authentication.
func (c *Config) MetricsAuthentication() bool {
	return c.m.GetBool("core.metrics_authentication")
}

// BGPASN returns the BGP ASN setting.
func (c *Config) BGPASN() int64 {
	return c.m.GetInt64("core.bgp_asn")
}

// HTTPSAllowedHeaders returns the relevant CORS setting.
func (c *Config) HTTPSAllowedHeaders() string {
	return c.m.GetString("core.https_allowed_headers")
}

// HTTPSAllowedMethods returns the relevant CORS setting.
func (c *Config) HTTPSAllowedMethods() string {
	return c.m.GetString("core.https_allowed_methods")
}

// HTTPSAllowedOrigin returns the relevant CORS setting.
func (c *Config) HTTPSAllowedOrigin() string {
	return c.m.GetString("core.https_allowed_origin")
}

// HTTPSAllowedCredentials returns the relevant CORS setting.
func (c *Config) HTTPSAllowedCredentials() bool {
	return c.m.GetBool("core.https_allowed_credentials")
}

// TrustPassword returns the LXD trust password for authenticating clients.
func (c *Config) TrustPassword() string {
	return c.m.GetString("core.trust_password")
}

// TrustCACertificates returns whether client certificates are checked
// against a CA.
func (c *Config) TrustCACertificates() bool {
	return c.m.GetBool("core.trust_ca_certificates")
}

// CandidServer returns all the Candid settings needed to connect to a server.
func (c *Config) CandidServer() (string, string, int64, string) {
	return c.m.GetString("candid.api.url"),
		c.m.GetString("candid.api.key"),
		c.m.GetInt64("candid.expiry"),
		c.m.GetString("candid.domains")
}

// RBACServer returns all the Candid settings needed to connect to a server.
func (c *Config) RBACServer() (string, string, int64, string, string, string, string) {
	return c.m.GetString("rbac.api.url"),
		c.m.GetString("rbac.api.key"),
		c.m.GetInt64("rbac.expiry"),
		c.m.GetString("rbac.agent.url"),
		c.m.GetString("rbac.agent.username"),
		c.m.GetString("rbac.agent.private_key"),
		c.m.GetString("rbac.agent.public_key")
}

// ProxyHTTPS returns the configured HTTPS proxy, if any.
func (c *Config) ProxyHTTPS() string {
	return c.m.GetString("core.proxy_https")
}

// ProxyHTTP returns the configured HTTP proxy, if any.
func (c *Config) ProxyHTTP() string {
	return c.m.GetString("core.proxy_http")
}

// ProxyIgnoreHosts returns the configured ignore-hosts proxy setting, if any.
func (c *Config) ProxyIgnoreHosts() string {
	return c.m.GetString("core.proxy_ignore_hosts")
}

// HTTPSTrustedProxy returns the configured HTTPS trusted proxy setting, if any.
func (c *Config) HTTPSTrustedProxy() string {
	return c.m.GetString("core.https_trusted_proxy")
}

// MAASController the configured MAAS url and key, if any.
func (c *Config) MAASController() (string, string) {
	url := c.m.GetString("maas.api.url")
	key := c.m.GetString("maas.api.key")
	return url, key
}

// OfflineThreshold returns the configured heartbeat threshold, i.e. the
// number of seconds before after which an unresponsive node is considered
// offline..
func (c *Config) OfflineThreshold() time.Duration {
	n := c.m.GetInt64("cluster.offline_threshold")
	return time.Duration(n) * time.Second
}

// ImagesMinimalReplica returns the numbers of nodes for cluster images replication.
func (c *Config) ImagesMinimalReplica() int64 {
	return c.m.GetInt64("cluster.images_minimal_replica")
}

// MaxVoters returns the maximum number of members in a cluster that will be
// assigned the voter role.
func (c *Config) MaxVoters() int64 {
	return c.m.GetInt64("cluster.max_voters")
}

// MaxStandBy returns the maximum number of standby members in a cluster that
// will be assigned the stand-by role.
func (c *Config) MaxStandBy() int64 {
	return c.m.GetInt64("cluster.max_standby")
}

// NetworkOVNIntegrationBridge returns the integration OVS bridge to use for OVN networks.
func (c *Config) NetworkOVNIntegrationBridge() string {
	return c.m.GetString("network.ovn.integration_bridge")
}

// NetworkOVNNorthboundConnection returns the OVN northbound database connection string for OVN networks.
func (c *Config) NetworkOVNNorthboundConnection() string {
	return c.m.GetString("network.ovn.northbound_connection")
}

// ShutdownTimeout returns the number of minutes to wait for running operation to complete
// before LXD server shut down.
func (c *Config) ShutdownTimeout() time.Duration {
	n := c.m.GetInt64("core.shutdown_timeout")
	return time.Duration(n) * time.Minute
}

// ImagesDefaultArchitecture returns the default architecture.
func (c *Config) ImagesDefaultArchitecture() string {
	return c.m.GetString("images.default_architecture")
}

// ImagesCompressionAlgorithm returns the compression algorithm to use for images.
func (c *Config) ImagesCompressionAlgorithm() string {
	return c.m.GetString("images.compression_algorithm")
}

// ImagesAutoUpdateCached returns whether or not to auto update cached images.
func (c *Config) ImagesAutoUpdateCached() bool {
	return c.m.GetBool("images.auto_update_cached")
}

// ImagesAutoUpdateIntervalHours returns interval in hours at which to look for update to cached images.
func (c *Config) ImagesAutoUpdateIntervalHours() int64 {
	return c.m.GetInt64("images.auto_update_interval")
}

// ImagesRemoteCacheExpiryDays returns the number of days after which an unused cached remote image will be flushed.
func (c *Config) ImagesRemoteCacheExpiryDays() int64 {
	return c.m.GetInt64("images.remote_cache_expiry")
}

// InstancesNICHostname returns hostname mode to use for instance NICs.
func (c *Config) InstancesNICHostname() string {
	return c.m.GetString("instances.nic.host_name")
}

// Dump current configuration keys and their values. Keys with values matching
// their defaults are omitted.
func (c *Config) Dump() map[string]any {
	return c.m.Dump()
}

// Replace the current configuration with the given values.
//
// Return what has actually changed.
func (c *Config) Replace(values map[string]any) (map[string]string, error) {
	return c.update(values)
}

// Patch changes only the configuration keys in the given map.
//
// Return what has actually changed.
func (c *Config) Patch(patch map[string]any) (map[string]string, error) {
	values := c.Dump() // Use current values as defaults
	for name, value := range patch {
		values[name] = value
	}

	return c.update(values)
}

func (c *Config) update(values map[string]any) (map[string]string, error) {
	changed, err := c.m.Change(values)
	if err != nil {
		return nil, err
	}

	err = c.tx.UpdateClusterConfig(changed)
	if err != nil {
		return nil, fmt.Errorf("cannot persist configuration changes: %w", err)
	}

	return changed, nil
}

func configGet(cluster *db.Cluster) (*Config, error) {
	var config *Config
	err := cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
		var err error
		config, err = Load(tx)
		return err
	})
	return config, err
}

// ConfigSchema defines available server configuration keys.
var ConfigSchema = config.Schema{
	"backups.compression_algorithm":  {Default: "gzip", Validator: validate.IsCompressionAlgorithm},
	"cluster.offline_threshold":      {Type: config.Int64, Default: offlineThresholdDefault(), Validator: offlineThresholdValidator},
	"cluster.images_minimal_replica": {Type: config.Int64, Default: "3", Validator: imageMinimalReplicaValidator},
	"cluster.max_voters":             {Type: config.Int64, Default: "3", Validator: maxVotersValidator},
	"cluster.max_standby":            {Type: config.Int64, Default: "2", Validator: maxStandByValidator},
	"core.metrics_authentication":    {Type: config.Bool, Default: "true"},
	"core.bgp_asn":                   {Type: config.Int64, Default: "0", Validator: validate.Optional(validate.IsInRange(0, 4294967294))},
	"core.https_allowed_headers":     {},
	"core.https_allowed_methods":     {},
	"core.https_allowed_origin":      {},
	"core.https_allowed_credentials": {Type: config.Bool},
	"core.https_trusted_proxy":       {},
	"core.proxy_http":                {},
	"core.proxy_https":               {},
	"core.proxy_ignore_hosts":        {},
	"core.shutdown_timeout":          {Type: config.Int64, Default: "5"},
	"core.trust_password":            {Hidden: true, Setter: passwordSetter},
	"core.trust_ca_certificates":     {Type: config.Bool},
	"candid.api.key":                 {},
	"candid.api.url":                 {},
	"candid.domains":                 {},
	"candid.expiry":                  {Type: config.Int64, Default: "3600"},
	"images.auto_update_cached":      {Type: config.Bool, Default: "true"},
	"images.auto_update_interval":    {Type: config.Int64, Default: "6"},
	"images.compression_algorithm":   {Default: "gzip", Validator: validate.IsCompressionAlgorithm},
	"images.default_architecture":    {Validator: validate.Optional(validate.IsArchitecture)},
	"images.remote_cache_expiry":     {Type: config.Int64, Default: "10"},
	"instances.nic.host_name":        {Validator: validate.Optional(validate.IsOneOf("random", "mac"))},
	"maas.api.key":                   {},
	"maas.api.url":                   {},
	"rbac.agent.url":                 {},
	"rbac.agent.username":            {},
	"rbac.agent.private_key":         {},
	"rbac.agent.public_key":          {},
	"rbac.api.expiry":                {Type: config.Int64, Default: "3600"},
	"rbac.api.key":                   {},
	"rbac.api.url":                   {},
	"rbac.expiry":                    {Type: config.Int64, Default: "3600"},

	// OVN networking global keys.
	"network.ovn.integration_bridge":    {Default: "br-int"},
	"network.ovn.northbound_connection": {Default: "unix:/var/run/ovn/ovnnb_db.sock"},
}

func offlineThresholdDefault() string {
	return strconv.Itoa(db.DefaultOfflineThreshold)
}

func offlineThresholdValidator(value string) error {
	minThreshold := 10

	// Ensure that the given value is greater than the heartbeat interval,
	// which is the lower bound granularity of the offline check.
	threshold, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Offline threshold is not a number")
	}

	if threshold <= minThreshold {
		return fmt.Errorf("Value must be greater than '%d'", minThreshold)
	}

	return nil
}

func imageMinimalReplicaValidator(value string) error {
	count, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Minimal image replica count is not a number")
	}

	if count < 1 && count != -1 {
		return fmt.Errorf("Invalid value for image replica count")
	}

	return nil
}

func maxVotersValidator(value string) error {
	n, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Value is not a number")
	}

	if n < 3 || n%2 != 1 {
		return fmt.Errorf("Value must be an odd number equal to or higher than 3")
	}

	return nil
}

func maxStandByValidator(value string) error {
	n, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Value is not a number")
	}

	if n < 0 || n > 5 {
		return fmt.Errorf("Value must be between 0 and 5")
	}

	return nil
}

func passwordSetter(value string) (string, error) {
	// Nothing to do on unset
	if value == "" {
		return value, nil
	}

	// Hash the password
	buf := make([]byte, 32)
	_, err := io.ReadFull(rand.Reader, buf)
	if err != nil {
		return "", err
	}

	hash, err := scrypt.Key([]byte(value), buf, 1<<14, 8, 1, 64)
	if err != nil {
		return "", err
	}

	buf = append(buf, hash...)
	value = hex.EncodeToString(buf)

	return value, nil
}
<commit_msg>lxd/cluster/config/config: Removes unused configGet function<commit_after>package config

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
	"strconv"
	"time"

	"golang.org/x/crypto/scrypt"

	"github.com/lxc/lxd/lxd/config"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/shared/validate"
)

// Config holds cluster-wide configuration values.
type Config struct {
	tx *db.ClusterTx // DB transaction the values in this config are bound to.
	m  config.Map    // Low-level map holding the config values.
}

// Load loads a new Config object with the current cluster configuration
// values fetched from the database.
func Load(tx *db.ClusterTx) (*Config, error) {
	// Load current raw values from the database, any error is fatal.
	values, err := tx.Config()
	if err != nil {
		return nil, fmt.Errorf("cannot fetch node config from database: %w", err)
	}

	m, err := config.SafeLoad(ConfigSchema, values)
	if err != nil {
		return nil, fmt.Errorf("failed to load node config: %w", err)
	}

	return &Config{tx: tx, m: m}, nil
}

// BackupsCompressionAlgorithm returns the compression algorithm to use for backups.
func (c *Config) BackupsCompressionAlgorithm() string {
	return c.m.GetString("backups.compression_algorithm")
}

// MetricsAuthentication checks whether metrics API requires authentication.
func (c *Config) MetricsAuthentication() bool {
	return c.m.GetBool("core.metrics_authentication")
}

// BGPASN returns the BGP ASN setting.
func (c *Config) BGPASN() int64 {
	return c.m.GetInt64("core.bgp_asn")
}

// HTTPSAllowedHeaders returns the relevant CORS setting.
func (c *Config) HTTPSAllowedHeaders() string {
	return c.m.GetString("core.https_allowed_headers")
}

// HTTPSAllowedMethods returns the relevant CORS setting.
func (c *Config) HTTPSAllowedMethods() string {
	return c.m.GetString("core.https_allowed_methods")
}

// HTTPSAllowedOrigin returns the relevant CORS setting.
func (c *Config) HTTPSAllowedOrigin() string {
	return c.m.GetString("core.https_allowed_origin")
}

// HTTPSAllowedCredentials returns the relevant CORS setting.
func (c *Config) HTTPSAllowedCredentials() bool {
	return c.m.GetBool("core.https_allowed_credentials")
}

// TrustPassword returns the LXD trust password for authenticating clients.
func (c *Config) TrustPassword() string {
	return c.m.GetString("core.trust_password")
}

// TrustCACertificates returns whether client certificates are checked
// against a CA.
func (c *Config) TrustCACertificates() bool {
	return c.m.GetBool("core.trust_ca_certificates")
}

// CandidServer returns all the Candid settings needed to connect to a server.
func (c *Config) CandidServer() (string, string, int64, string) {
	return c.m.GetString("candid.api.url"),
		c.m.GetString("candid.api.key"),
		c.m.GetInt64("candid.expiry"),
		c.m.GetString("candid.domains")
}

// RBACServer returns all the Candid settings needed to connect to a server.
func (c *Config) RBACServer() (string, string, int64, string, string, string, string) {
	return c.m.GetString("rbac.api.url"),
		c.m.GetString("rbac.api.key"),
		c.m.GetInt64("rbac.expiry"),
		c.m.GetString("rbac.agent.url"),
		c.m.GetString("rbac.agent.username"),
		c.m.GetString("rbac.agent.private_key"),
		c.m.GetString("rbac.agent.public_key")
}

// ProxyHTTPS returns the configured HTTPS proxy, if any.
func (c *Config) ProxyHTTPS() string {
	return c.m.GetString("core.proxy_https")
}

// ProxyHTTP returns the configured HTTP proxy, if any.
func (c *Config) ProxyHTTP() string {
	return c.m.GetString("core.proxy_http")
}

// ProxyIgnoreHosts returns the configured ignore-hosts proxy setting, if any.
func (c *Config) ProxyIgnoreHosts() string {
	return c.m.GetString("core.proxy_ignore_hosts")
}

// HTTPSTrustedProxy returns the configured HTTPS trusted proxy setting, if any.
func (c *Config) HTTPSTrustedProxy() string {
	return c.m.GetString("core.https_trusted_proxy")
}

// MAASController the configured MAAS url and key, if any.
func (c *Config) MAASController() (string, string) {
	url := c.m.GetString("maas.api.url")
	key := c.m.GetString("maas.api.key")
	return url, key
}

// OfflineThreshold returns the configured heartbeat threshold, i.e. the
// number of seconds before after which an unresponsive node is considered
// offline..
func (c *Config) OfflineThreshold() time.Duration {
	n := c.m.GetInt64("cluster.offline_threshold")
	return time.Duration(n) * time.Second
}

// ImagesMinimalReplica returns the numbers of nodes for cluster images replication.
func (c *Config) ImagesMinimalReplica() int64 {
	return c.m.GetInt64("cluster.images_minimal_replica")
}

// MaxVoters returns the maximum number of members in a cluster that will be
// assigned the voter role.
func (c *Config) MaxVoters() int64 {
	return c.m.GetInt64("cluster.max_voters")
}

// MaxStandBy returns the maximum number of standby members in a cluster that
// will be assigned the stand-by role.
func (c *Config) MaxStandBy() int64 {
	return c.m.GetInt64("cluster.max_standby")
}

// NetworkOVNIntegrationBridge returns the integration OVS bridge to use for OVN networks.
func (c *Config) NetworkOVNIntegrationBridge() string {
	return c.m.GetString("network.ovn.integration_bridge")
}

// NetworkOVNNorthboundConnection returns the OVN northbound database connection string for OVN networks.
func (c *Config) NetworkOVNNorthboundConnection() string {
	return c.m.GetString("network.ovn.northbound_connection")
}

// ShutdownTimeout returns the number of minutes to wait for running operation to complete
// before LXD server shut down.
func (c *Config) ShutdownTimeout() time.Duration {
	n := c.m.GetInt64("core.shutdown_timeout")
	return time.Duration(n) * time.Minute
}

// ImagesDefaultArchitecture returns the default architecture.
func (c *Config) ImagesDefaultArchitecture() string {
	return c.m.GetString("images.default_architecture")
}

// ImagesCompressionAlgorithm returns the compression algorithm to use for images.
func (c *Config) ImagesCompressionAlgorithm() string {
	return c.m.GetString("images.compression_algorithm")
}

// ImagesAutoUpdateCached returns whether or not to auto update cached images.
func (c *Config) ImagesAutoUpdateCached() bool {
	return c.m.GetBool("images.auto_update_cached")
}

// ImagesAutoUpdateIntervalHours returns interval in hours at which to look for update to cached images.
func (c *Config) ImagesAutoUpdateIntervalHours() int64 {
	return c.m.GetInt64("images.auto_update_interval")
}

// ImagesRemoteCacheExpiryDays returns the number of days after which an unused cached remote image will be flushed.
func (c *Config) ImagesRemoteCacheExpiryDays() int64 {
	return c.m.GetInt64("images.remote_cache_expiry")
}

// InstancesNICHostname returns hostname mode to use for instance NICs.
func (c *Config) InstancesNICHostname() string {
	return c.m.GetString("instances.nic.host_name")
}

// Dump current configuration keys and their values. Keys with values matching
// their defaults are omitted.
func (c *Config) Dump() map[string]any {
	return c.m.Dump()
}

// Replace the current configuration with the given values.
//
// Return what has actually changed.
func (c *Config) Replace(values map[string]any) (map[string]string, error) {
	return c.update(values)
}

// Patch changes only the configuration keys in the given map.
//
// Return what has actually changed.
func (c *Config) Patch(patch map[string]any) (map[string]string, error) {
	values := c.Dump() // Use current values as defaults
	for name, value := range patch {
		values[name] = value
	}

	return c.update(values)
}

func (c *Config) update(values map[string]any) (map[string]string, error) {
	changed, err := c.m.Change(values)
	if err != nil {
		return nil, err
	}

	err = c.tx.UpdateClusterConfig(changed)
	if err != nil {
		return nil, fmt.Errorf("cannot persist configuration changes: %w", err)
	}

	return changed, nil
}

// ConfigSchema defines available server configuration keys.
var ConfigSchema = config.Schema{
	"backups.compression_algorithm":  {Default: "gzip", Validator: validate.IsCompressionAlgorithm},
	"cluster.offline_threshold":      {Type: config.Int64, Default: offlineThresholdDefault(), Validator: offlineThresholdValidator},
	"cluster.images_minimal_replica": {Type: config.Int64, Default: "3", Validator: imageMinimalReplicaValidator},
	"cluster.max_voters":             {Type: config.Int64, Default: "3", Validator: maxVotersValidator},
	"cluster.max_standby":            {Type: config.Int64, Default: "2", Validator: maxStandByValidator},
	"core.metrics_authentication":    {Type: config.Bool, Default: "true"},
	"core.bgp_asn":                   {Type: config.Int64, Default: "0", Validator: validate.Optional(validate.IsInRange(0, 4294967294))},
	"core.https_allowed_headers":     {},
	"core.https_allowed_methods":     {},
	"core.https_allowed_origin":      {},
	"core.https_allowed_credentials": {Type: config.Bool},
	"core.https_trusted_proxy":       {},
	"core.proxy_http":                {},
	"core.proxy_https":               {},
	"core.proxy_ignore_hosts":        {},
	"core.shutdown_timeout":          {Type: config.Int64, Default: "5"},
	"core.trust_password":            {Hidden: true, Setter: passwordSetter},
	"core.trust_ca_certificates":     {Type: config.Bool},
	"candid.api.key":                 {},
	"candid.api.url":                 {},
	"candid.domains":                 {},
	"candid.expiry":                  {Type: config.Int64, Default: "3600"},
	"images.auto_update_cached":      {Type: config.Bool, Default: "true"},
	"images.auto_update_interval":    {Type: config.Int64, Default: "6"},
	"images.compression_algorithm":   {Default: "gzip", Validator: validate.IsCompressionAlgorithm},
	"images.default_architecture":    {Validator: validate.Optional(validate.IsArchitecture)},
	"images.remote_cache_expiry":     {Type: config.Int64, Default: "10"},
	"instances.nic.host_name":        {Validator: validate.Optional(validate.IsOneOf("random", "mac"))},
	"maas.api.key":                   {},
	"maas.api.url":                   {},
	"rbac.agent.url":                 {},
	"rbac.agent.username":            {},
	"rbac.agent.private_key":         {},
	"rbac.agent.public_key":          {},
	"rbac.api.expiry":                {Type: config.Int64, Default: "3600"},
	"rbac.api.key":                   {},
	"rbac.api.url":                   {},
	"rbac.expiry":                    {Type: config.Int64, Default: "3600"},

	// OVN networking global keys.
	"network.ovn.integration_bridge":    {Default: "br-int"},
	"network.ovn.northbound_connection": {Default: "unix:/var/run/ovn/ovnnb_db.sock"},
}

func offlineThresholdDefault() string {
	return strconv.Itoa(db.DefaultOfflineThreshold)
}

func offlineThresholdValidator(value string) error {
	minThreshold := 10

	// Ensure that the given value is greater than the heartbeat interval,
	// which is the lower bound granularity of the offline check.
	threshold, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Offline threshold is not a number")
	}

	if threshold <= minThreshold {
		return fmt.Errorf("Value must be greater than '%d'", minThreshold)
	}

	return nil
}

func imageMinimalReplicaValidator(value string) error {
	count, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Minimal image replica count is not a number")
	}

	if count < 1 && count != -1 {
		return fmt.Errorf("Invalid value for image replica count")
	}

	return nil
}

func maxVotersValidator(value string) error {
	n, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Value is not a number")
	}

	if n < 3 || n%2 != 1 {
		return fmt.Errorf("Value must be an odd number equal to or higher than 3")
	}

	return nil
}

func maxStandByValidator(value string) error {
	n, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("Value is not a number")
	}

	if n < 0 || n > 5 {
		return fmt.Errorf("Value must be between 0 and 5")
	}

	return nil
}

func passwordSetter(value string) (string, error) {
	// Nothing to do on unset
	if value == "" {
		return value, nil
	}

	// Hash the password
	buf := make([]byte, 32)
	_, err := io.ReadFull(rand.Reader, buf)
	if err != nil {
		return "", err
	}

	hash, err := scrypt.Key([]byte(value), buf, 1<<14, 8, 1, 64)
	if err != nil {
		return "", err
	}

	buf = append(buf, hash...)
	value = hex.EncodeToString(buf)

	return value, nil
}
<|endoftext|>


<commit_before>package oauth2

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

	"github.com/tarent/loginsrv/model"
)

//var googleAPI = "https://www.googleapis.com/plus/v1"

var googleUserinfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo"

func init() {
	RegisterProvider(providerGoogle)
}

type GoogleUser struct {
	Name               string `json:"name"`
	Email              string `json:"email"`
	EmailVerified      bool   `json:"email_verified"`
	Picture            string `json:"picture"`
	HostedGsuiteDomain string `json:"hd"`
}

var providerGoogle = Provider{
	Name:          "google",
	AuthURL:       "https://accounts.google.com/o/oauth2/v2/auth",
	TokenURL:      "https://www.googleapis.com/oauth2/v4/token",
	DefaultScopes: "email profile",
	GetUserInfo: func(token TokenInfo) (model.UserInfo, string, error) {
		gu := GoogleUser{}
		url := fmt.Sprintf("%v?access_token=%v", googleUserinfoEndpoint, token.AccessToken)
		resp, err := http.Get(url)

		if err != nil {
			return model.UserInfo{}, "", err
		}
		defer resp.Body.Close()

		if !strings.Contains(resp.Header.Get("Content-Type"), "application/json") {
			return model.UserInfo{}, "", fmt.Errorf("wrong content-type on google get user info: %v", resp.Header.Get("Content-Type"))
		}

		if resp.StatusCode != 200 {
			return model.UserInfo{}, "", fmt.Errorf("got http status %v on google get user info", resp.StatusCode)
		}

		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return model.UserInfo{}, "", fmt.Errorf("error reading google get user info: %v", err)
		}

		err = json.Unmarshal(b, &gu)
		if err != nil {
			return model.UserInfo{}, "", fmt.Errorf("error parsing google get user info: %v", err)
		}

		if len(gu.Email) == 0 {
			return model.UserInfo{}, "", fmt.Errorf("invalid google response: no email address returned.")
		}

		if !gu.EmailVerified {
			return model.UserInfo{}, "", fmt.Errorf("invalid google response: users email address not verified by google.")
		}

		return model.UserInfo{
			Sub:     gu.Email,
			Picture: gu.Picture,
			Name:    gu.Name,
			Email:   gu.Email,
			Origin:  "google",
			Domain:  gu.HostedGsuiteDomain,
		}, string(b), nil
	},
}
<commit_msg>remove commented code<commit_after>package oauth2

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

	"github.com/tarent/loginsrv/model"
)

var googleUserinfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo"

func init() {
	RegisterProvider(providerGoogle)
}

type GoogleUser struct {
	Name               string `json:"name"`
	Email              string `json:"email"`
	EmailVerified      bool   `json:"email_verified"`
	Picture            string `json:"picture"`
	HostedGsuiteDomain string `json:"hd"`
}

var providerGoogle = Provider{
	Name:          "google",
	AuthURL:       "https://accounts.google.com/o/oauth2/v2/auth",
	TokenURL:      "https://www.googleapis.com/oauth2/v4/token",
	DefaultScopes: "email profile",
	GetUserInfo: func(token TokenInfo) (model.UserInfo, string, error) {
		gu := GoogleUser{}
		url := fmt.Sprintf("%v?access_token=%v", googleUserinfoEndpoint, token.AccessToken)
		resp, err := http.Get(url)

		if err != nil {
			return model.UserInfo{}, "", err
		}
		defer resp.Body.Close()

		if !strings.Contains(resp.Header.Get("Content-Type"), "application/json") {
			return model.UserInfo{}, "", fmt.Errorf("wrong content-type on google get user info: %v", resp.Header.Get("Content-Type"))
		}

		if resp.StatusCode != 200 {
			return model.UserInfo{}, "", fmt.Errorf("got http status %v on google get user info", resp.StatusCode)
		}

		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return model.UserInfo{}, "", fmt.Errorf("error reading google get user info: %v", err)
		}

		err = json.Unmarshal(b, &gu)
		if err != nil {
			return model.UserInfo{}, "", fmt.Errorf("error parsing google get user info: %v", err)
		}

		if len(gu.Email) == 0 {
			return model.UserInfo{}, "", fmt.Errorf("invalid google response: no email address returned.")
		}

		if !gu.EmailVerified {
			return model.UserInfo{}, "", fmt.Errorf("invalid google response: users email address not verified by google.")
		}

		return model.UserInfo{
			Sub:     gu.Email,
			Picture: gu.Picture,
			Name:    gu.Name,
			Email:   gu.Email,
			Origin:  "google",
			Domain:  gu.HostedGsuiteDomain,
		}, string(b), nil
	},
}
<|endoftext|>


<commit_before>package urlvalues

import (
	"reflect"

	"github.com/go-pg/pg/internal/struct_filter"
)

type afterDecodeURLValues interface {
	AfterDecodeURLValues(values Values) error
}

func Decode(value afterDecodeURLValues, values Values) error {
	strct := reflect.Indirect(reflect.ValueOf(value))
	meta := struct_filter.GetStruct(strct.Type())

	for name, values := range values {
		field := meta.Field(name)
		if field != nil && !field.NoDecode() {
			err := field.Scan(field.Value(strct), values)
			if err != nil {
				return err
			}
		}
	}

	if value, ok := value.(afterDecodeURLValues); ok {
		return value.AfterDecodeURLValues(values)
	}

	return nil
}
<commit_msg>urlvalues: ignore [] in name<commit_after>package urlvalues

import (
	"reflect"
	"strings"

	"github.com/go-pg/pg/internal/struct_filter"
)

type afterDecodeURLValues interface {
	AfterDecodeURLValues(values Values) error
}

func Decode(value afterDecodeURLValues, values Values) error {
	strct := reflect.Indirect(reflect.ValueOf(value))
	meta := struct_filter.GetStruct(strct.Type())

	for name, values := range values {
		if strings.HasSuffix(name, "[]") {
			name = name[:len(name)-2]
		}

		field := meta.Field(name)
		if field != nil && !field.NoDecode() {
			err := field.Scan(field.Value(strct), values)
			if err != nil {
				return err
			}
		}
	}

	if value, ok := value.(afterDecodeURLValues); ok {
		return value.AfterDecodeURLValues(values)
	}

	return nil
}
<|endoftext|>


<commit_before>package bundle

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/mgoltzsche/cntnr/pkg/atomic"
	exterrors "github.com/mgoltzsche/cntnr/pkg/errors"
	"github.com/mgoltzsche/cntnr/pkg/lock"
	digest "github.com/opencontainers/go-digest"
	rspecs "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/pkg/errors"
)

const ANNOTATION_BUNDLE_ID = "com.github.mgoltzsche.cntnr.bundle.id"

type Bundle struct {
	id      string
	dir     string
	created time.Time
}

type SpecGenerator interface {
	Spec(rootfs string) (*rspecs.Spec, error)
}

func NewBundle(dir string) (r Bundle, err error) {
	r.id = filepath.Base(dir)
	f, err := os.Stat(dir)
	if err != nil {
		return r, errors.Wrap(err, "open bundle")
	}
	if !f.IsDir() {
		return r, errors.Errorf("open bundle: no directory provided but %s", dir)
	}
	r.dir = dir
	r.created = f.ModTime()
	return
}

func (b *Bundle) ID() string {
	return b.id
}

func (b *Bundle) Dir() string {
	return b.dir
}

func (b *Bundle) Created() time.Time {
	return b.created
}

func (b *Bundle) loadSpec() (r rspecs.Spec, err error) {
	file := filepath.Join(b.dir, "config.json")
	c, err := ioutil.ReadFile(file)
	if err == nil {
		err = json.Unmarshal(c, &r)
	}
	if err != nil {
		err = errors.Errorf("bundle %q spec: %s", b.id, err)
	}
	return
}

func (b *Bundle) Image() *digest.Digest {
	if imgIdb, err := ioutil.ReadFile(b.imageFile()); err == nil {
		d, err := digest.Parse(strings.Trim(string(imgIdb), " \n"))
		if err == nil {
			return &d
		}
	}
	return nil
}

func (b *Bundle) imageFile() string {
	return filepath.Join(b.dir, "rootfs.image")
}

func (b *Bundle) Lock() (*LockedBundle, error) {
	return OpenLockedBundle(*b)
}

// Update mod time so that gc doesn't touch it for a while
func (b *Bundle) resetExpiryTime() (err error) {
	configFile := filepath.Join(b.dir)
	now := time.Now()
	if e := os.Chtimes(configFile, now, now); e != nil && !os.IsNotExist(e) {
		err = errors.New("reset bundle expiry time: " + e.Error())
	}
	return
}

func (b *Bundle) GC(before time.Time) (r bool, err error) {
	defer exterrors.Wrapd(&err, "bundle gc check")
	st, err := os.Stat(b.dir)
	if err != nil {
		return false, errors.New(err.Error())
	}
	if st.ModTime().Before(before) {
		var bl *lock.Lockfile
		bl, err = lockBundle(b)
		if err != nil {
			return true, err
		}
		defer func() {
			err = exterrors.Append(err, bl.Unlock())
		}()
		if st, err = os.Stat(b.dir); err != nil {
			return true, err
		}
		if st.ModTime().Before(before) {
			if err = deleteBundle(b.dir); err != nil {
				return true, err
			}
			return true, err
		}
	}
	return
}

type LockedBundle struct {
	bundle Bundle
	spec   *rspecs.Spec
	image  *digest.Digest
	lock   *lock.Lockfile
}

func OpenLockedBundle(bundle Bundle) (*LockedBundle, error) {
	lck, err := lockBundle(&bundle)
	if err != nil {
		return nil, err
	}
	if err := bundle.resetExpiryTime(); err != nil {
		return nil, errors.Wrap(err, "lock bundle")
	}
	return &LockedBundle{bundle, nil, nil, lck}, nil
}

func CreateLockedBundle(dir string, spec SpecGenerator, image BundleImage, update bool) (r *LockedBundle, err error) {
	defer exterrors.Wrapd(&err, "create bundle")

	// Create bundle
	id := ""
	if id == "" {
		id = filepath.Base(dir)
	}
	bundle := Bundle{id, dir, time.Now()}

	// Lock bundle
	lck, err := lockBundle(&bundle)
	if err != nil {
		return nil, err
	}
	r = &LockedBundle{bundle, nil, nil, lck}
	defer func() {
		if err != nil {
			err = exterrors.Append(err, r.Close())
		}
	}()

	// Create or update bundle
	err = os.Mkdir(dir, 0770)
	exists := err != nil && os.IsExist(err)
	updateRootfs := true

	if exists {
		if !update {
			return nil, errors.Errorf("bundle %q directory already exists", id)
		}
		lastImageId := bundle.Image()
		rootfs := filepath.Join(dir, "rootfs")
		if _, e := os.Stat(rootfs); e == nil && (lastImageId == nil && image == nil || lastImageId != nil && *lastImageId == image.ID()) {
			updateRootfs = false
		}
	} else {
		if err != nil {
			return
		}
		if !update {
			defer func() {
				if err != nil {
					err = exterrors.Append(err, os.RemoveAll(dir))
				}
			}()
		}
	}

	// Update rootfs if not exists or image changed
	if updateRootfs {
		if err = r.UpdateRootfs(image); err != nil {
			return
		}
	}

	// TODO: resolve user/group names here
	// Write spec
	if err = r.SetSpec(spec); err != nil {
		return
	}

	return
}

func (b *LockedBundle) Close() (err error) {
	if b.lock != nil {
		err = b.bundle.resetExpiryTime()
		err = exterrors.Append(err, errors.Wrap(b.lock.Unlock(), "unlock bundle"))
		b.lock = nil
	}
	return
}

func (b *LockedBundle) ID() string {
	return b.bundle.id
}

func (b *LockedBundle) Dir() string {
	return b.bundle.dir
}

func (b *LockedBundle) Spec() (*rspecs.Spec, error) {
	if b.spec == nil {
		spec, err := b.bundle.loadSpec()
		if err != nil {
			return nil, err
		}
		b.spec = &spec
	}
	return b.spec, nil
}

func (b *LockedBundle) UpdateRootfs(image BundleImage) (err error) {
	rootfs := filepath.Join(b.Dir(), "rootfs")
	var imgId *digest.Digest
	if image != nil {
		id := image.ID()
		imgId = &id
	}
	if err = createRootfs(rootfs, image); err != nil {
		return
	}
	return b.SetParentImageId(imgId)
}

func createRootfs(rootfs string, image BundleImage) (err error) {
	if err = os.RemoveAll(rootfs); err == nil || os.IsNotExist(err) {
		return image.Unpack(rootfs)
	}
	if err != nil {
		err = errors.New("create bundle rootfs: " + err.Error())
	}
	return
}

func (b *LockedBundle) SetSpec(specgen SpecGenerator) (err error) {
	spec, err := specgen.Spec(filepath.Join(b.Dir(), "rootfs"))
	if err != nil {
		return errors.Wrap(err, "set bundle spec")
	}
	if spec.Root != nil {
		spec.Root.Path = "rootfs"
	}
	if spec.Annotations == nil {
		spec.Annotations = map[string]string{}
	}
	spec.Annotations[ANNOTATION_BUNDLE_ID] = b.ID()
	confFile := filepath.Join(b.Dir(), "config.json")
	if _, err = atomic.WriteJson(confFile, spec); err != nil {
		err = errors.Wrapf(err, "update bundle %q spec", b.ID())
	}
	b.spec = spec
	return
}

/*func createVolumeDirectories(spec *rspecs.Spec, dir string) (err error) {
	if spec != nil && spec.Mounts != nil {
		for _, mount := range spec.Mounts {
			if mount.Type == "bind" {
				src := mount.Source
				if !filepath.IsAbs(src) {
					src = filepath.Join(dir, src)
				}
				relsrc := filepath.Clean(mount.Source)
				if _, err = os.Stat(src); os.IsNotExist(err) && !filepath.IsAbs(relsrc) && strings.Index(relsrc, "..") != 0 {
					err = errors.Errorf("bind mount source %q does not exist", mount.Source)
					if err = os.MkdirAll(src, 0755); err != nil {
						err = errors.New(err.Error())
						break
					}
				} else if err != nil {
					break
				}
			}
		}
	}
	err = errors.Wrap(err, "volume directories")
	return
}*/

// Reads image ID from cached spec
func (b *LockedBundle) Image() *digest.Digest {
	if b.image == nil {
		b.image = b.bundle.Image()
	}
	return b.image
}

func (b *LockedBundle) SetParentImageId(imageID *digest.Digest) (err error) {
	if imageID == nil {
		if e := os.Remove(b.bundle.imageFile()); e != nil && !os.IsNotExist(e) {
			err = errors.New(e.Error())
		}
	} else {
		_, err = atomic.WriteFile(b.bundle.imageFile(), bytes.NewBufferString((*imageID).String()))
	}
	if err == nil {
		b.image = imageID
	} else {
		err = errors.Wrap(err, "set bundle's parent image id")
	}
	return
}

func (b *LockedBundle) Delete() (err error) {
	err = deleteBundle(b.Dir())
	err = exterrors.Append(err, b.Close())
	return
}

func lockBundle(bundle *Bundle) (l *lock.Lockfile, err error) {
	// TODO: use tmpfs for lock file
	if l, err = lock.LockFile(filepath.Clean(bundle.dir) + ".lock"); err == nil {
		err = l.TryLock()
	}
	return l, errors.Wrap(err, "lock bundle")
}

func deleteBundle(dir string) (err error) {
	if err = os.RemoveAll(dir); err != nil {
		err = errors.New("delete bundle: " + err.Error())
	}
	return
}
<commit_msg>Fixed implicit anonymous volume creation (when within bundle dir)<commit_after>package bundle

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/mgoltzsche/cntnr/pkg/atomic"
	exterrors "github.com/mgoltzsche/cntnr/pkg/errors"
	"github.com/mgoltzsche/cntnr/pkg/lock"
	digest "github.com/opencontainers/go-digest"
	rspecs "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/pkg/errors"
)

const ANNOTATION_BUNDLE_ID = "com.github.mgoltzsche.cntnr.bundle.id"

type Bundle struct {
	id      string
	dir     string
	created time.Time
}

type SpecGenerator interface {
	Spec(rootfs string) (*rspecs.Spec, error)
}

func NewBundle(dir string) (r Bundle, err error) {
	r.id = filepath.Base(dir)
	f, err := os.Stat(dir)
	if err != nil {
		return r, errors.Wrap(err, "open bundle")
	}
	if !f.IsDir() {
		return r, errors.Errorf("open bundle: no directory provided but %s", dir)
	}
	r.dir = dir
	r.created = f.ModTime()
	return
}

func (b *Bundle) ID() string {
	return b.id
}

func (b *Bundle) Dir() string {
	return b.dir
}

func (b *Bundle) Created() time.Time {
	return b.created
}

func (b *Bundle) loadSpec() (r rspecs.Spec, err error) {
	file := filepath.Join(b.dir, "config.json")
	c, err := ioutil.ReadFile(file)
	if err == nil {
		err = json.Unmarshal(c, &r)
	}
	if err != nil {
		err = errors.Errorf("bundle %q spec: %s", b.id, err)
	}
	return
}

func (b *Bundle) Image() *digest.Digest {
	if imgIdb, err := ioutil.ReadFile(b.imageFile()); err == nil {
		d, err := digest.Parse(strings.Trim(string(imgIdb), " \n"))
		if err == nil {
			return &d
		}
	}
	return nil
}

func (b *Bundle) imageFile() string {
	return filepath.Join(b.dir, "rootfs.image")
}

func (b *Bundle) Lock() (*LockedBundle, error) {
	return OpenLockedBundle(*b)
}

// Update mod time so that gc doesn't touch it for a while
func (b *Bundle) resetExpiryTime() (err error) {
	configFile := filepath.Join(b.dir)
	now := time.Now()
	if e := os.Chtimes(configFile, now, now); e != nil && !os.IsNotExist(e) {
		err = errors.New("reset bundle expiry time: " + e.Error())
	}
	return
}

func (b *Bundle) GC(before time.Time) (r bool, err error) {
	defer exterrors.Wrapd(&err, "bundle gc check")
	st, err := os.Stat(b.dir)
	if err != nil {
		return false, errors.New(err.Error())
	}
	if st.ModTime().Before(before) {
		var bl *lock.Lockfile
		bl, err = lockBundle(b)
		if err != nil {
			return true, err
		}
		defer func() {
			err = exterrors.Append(err, bl.Unlock())
		}()
		if st, err = os.Stat(b.dir); err != nil {
			return true, err
		}
		if st.ModTime().Before(before) {
			if err = deleteBundle(b.dir); err != nil {
				return true, err
			}
			return true, err
		}
	}
	return
}

type LockedBundle struct {
	bundle Bundle
	spec   *rspecs.Spec
	image  *digest.Digest
	lock   *lock.Lockfile
}

func OpenLockedBundle(bundle Bundle) (*LockedBundle, error) {
	lck, err := lockBundle(&bundle)
	if err != nil {
		return nil, err
	}
	if err := bundle.resetExpiryTime(); err != nil {
		return nil, errors.Wrap(err, "lock bundle")
	}
	return &LockedBundle{bundle, nil, nil, lck}, nil
}

func CreateLockedBundle(dir string, spec SpecGenerator, image BundleImage, update bool) (r *LockedBundle, err error) {
	defer exterrors.Wrapd(&err, "create bundle")

	// Create bundle
	id := ""
	if id == "" {
		id = filepath.Base(dir)
	}
	bundle := Bundle{id, dir, time.Now()}

	// Lock bundle
	lck, err := lockBundle(&bundle)
	if err != nil {
		return nil, err
	}
	r = &LockedBundle{bundle, nil, nil, lck}
	defer func() {
		if err != nil {
			err = exterrors.Append(err, r.Close())
		}
	}()

	// Create or update bundle
	err = os.Mkdir(dir, 0770)
	exists := err != nil && os.IsExist(err)
	updateRootfs := true

	if exists {
		if !update {
			return nil, errors.Errorf("bundle %q directory already exists", id)
		}
		lastImageId := bundle.Image()
		rootfs := filepath.Join(dir, "rootfs")
		if _, e := os.Stat(rootfs); e == nil && (lastImageId == nil && image == nil || lastImageId != nil && *lastImageId == image.ID()) {
			updateRootfs = false
		}
	} else {
		if err != nil {
			return
		}
		if !update {
			defer func() {
				if err != nil {
					err = exterrors.Append(err, os.RemoveAll(dir))
				}
			}()
		}
	}

	// Update rootfs if not exists or image changed
	if updateRootfs {
		if err = r.UpdateRootfs(image); err != nil {
			return
		}
	}

	// TODO: resolve user/group names here
	// Write spec
	if err = r.SetSpec(spec); err != nil {
		return
	}

	return
}

func (b *LockedBundle) Close() (err error) {
	if b.lock != nil {
		err = b.bundle.resetExpiryTime()
		err = exterrors.Append(err, errors.Wrap(b.lock.Unlock(), "unlock bundle"))
		b.lock = nil
	}
	return
}

func (b *LockedBundle) ID() string {
	return b.bundle.id
}

func (b *LockedBundle) Dir() string {
	return b.bundle.dir
}

func (b *LockedBundle) Spec() (*rspecs.Spec, error) {
	if b.spec == nil {
		spec, err := b.bundle.loadSpec()
		if err != nil {
			return nil, err
		}
		b.spec = &spec
	}
	return b.spec, nil
}

func (b *LockedBundle) UpdateRootfs(image BundleImage) (err error) {
	rootfs := filepath.Join(b.Dir(), "rootfs")
	var imgId *digest.Digest
	if image != nil {
		id := image.ID()
		imgId = &id
	}
	if err = createRootfs(rootfs, image); err != nil {
		return
	}
	return b.SetParentImageId(imgId)
}

func createRootfs(rootfs string, image BundleImage) (err error) {
	if err = os.RemoveAll(rootfs); err == nil || os.IsNotExist(err) {
		return image.Unpack(rootfs)
	}
	if err != nil {
		err = errors.New("create bundle rootfs: " + err.Error())
	}
	return
}

func (b *LockedBundle) SetSpec(specgen SpecGenerator) (err error) {
	spec, err := specgen.Spec(filepath.Join(b.Dir(), "rootfs"))
	if err == nil {
		err = createVolumeDirectories(spec, b.Dir())
	}
	if err != nil {
		return errors.Wrap(err, "set bundle spec")
	}
	if spec.Root != nil {
		spec.Root.Path = "rootfs"
	}
	if spec.Annotations == nil {
		spec.Annotations = map[string]string{}
	}
	spec.Annotations[ANNOTATION_BUNDLE_ID] = b.ID()
	confFile := filepath.Join(b.Dir(), "config.json")
	if _, err = atomic.WriteJson(confFile, spec); err != nil {
		err = errors.Wrapf(err, "write bundle %q spec", b.ID())
	}
	b.spec = spec
	return
}

func createVolumeDirectories(spec *rspecs.Spec, dir string) (err error) {
	if spec != nil && spec.Mounts != nil {
		for _, mount := range spec.Mounts {
			if mount.Type == "bind" {
				src := mount.Source
				if !filepath.IsAbs(src) {
					src = filepath.Join(dir, src)
				}
				relsrc := filepath.Clean(mount.Source)
				if _, err = os.Stat(src); os.IsNotExist(err) {
					withinBundleDir := !filepath.IsAbs(relsrc) && strings.Index(relsrc+string(filepath.Separator), ".."+string(filepath.Separator)) != 0
					if withinBundleDir {
						if err = os.MkdirAll(src, 0755); err != nil {
							break
						}
					} else {
						err = errors.Errorf("bind mount source %q does not exist", mount.Source)
					}
				} else if err != nil {
					break
				}
			}
		}
	}
	if err != nil {
		err = errors.New("volume directories: " + err.Error())
	}
	return
}

// Reads image ID from cached spec
func (b *LockedBundle) Image() *digest.Digest {
	if b.image == nil {
		b.image = b.bundle.Image()
	}
	return b.image
}

func (b *LockedBundle) SetParentImageId(imageID *digest.Digest) (err error) {
	if imageID == nil {
		if e := os.Remove(b.bundle.imageFile()); e != nil && !os.IsNotExist(e) {
			err = errors.New(e.Error())
		}
	} else {
		_, err = atomic.WriteFile(b.bundle.imageFile(), bytes.NewBufferString((*imageID).String()))
	}
	if err == nil {
		b.image = imageID
	} else {
		err = errors.Wrap(err, "set bundle's parent image id")
	}
	return
}

func (b *LockedBundle) Delete() (err error) {
	err = deleteBundle(b.Dir())
	err = exterrors.Append(err, b.Close())
	return
}

func lockBundle(bundle *Bundle) (l *lock.Lockfile, err error) {
	// TODO: use tmpfs for lock file
	if l, err = lock.LockFile(filepath.Clean(bundle.dir) + ".lock"); err == nil {
		err = l.TryLock()
	}
	return l, errors.Wrap(err, "lock bundle")
}

func deleteBundle(dir string) (err error) {
	if err = os.RemoveAll(dir); err != nil {
		err = errors.New("delete bundle: " + err.Error())
	}
	return
}
<|endoftext|>


<commit_before>package server

var (
	buildExceptions = [][]string{
		{}, // #0 - n/a

		// -------------------
		// Treasure Room items
		// -------------------

		// #1 - Cricket's Body
		// Cain - The range down causes Cain to barely be able to hit anything
		// Samson - ???
		// Azazel - The Brimstone beam inherits the split shots but they are not very good
		// Tainted Azazel - The Brimstone beam inherits the split shots but they are not very good
		{"Cain", "Samson", "Azazel", "Tainted Azazel"},

		{}, // #2 - Cricket's Head

		// #3 - Dead Eye
		// Azazel - The Brimstone beam prevents it from working
		// Lilith - It is hard to be accurate when shots come from Incubus
		// Keeper - It is hard to be accurate with triple shot
		// Tainted Azazel - The Brimstone beam prevents it from working
		// Tainted Keeper - It is hard to be accurate with triple shot
		{"Azazel", "Lilith", "Keeper", "Tainted Azazel", "Tainted Keeper"},

		// #4 - Death's Touch
		// The Forgotten - The piercing shots do nothing for the bone club
		{"The Forgotten"},

		// #5 - Dr. Fetus
		{},

		// #6 - Ipecac
		// Azazel - The short-range brimstone causes self-damage
		{"Azazel"},

		{}, // #7 - Magic Mushroom
		{}, // #8 - Mom's Knife
		{}, // #9 - Polyphemus
		{}, // #10 - Proptosis
		{}, // #11 - Tech.5
		{}, // #12 - Tech X
		{}, // #13 - C Section

		// ----------------
		// Devil Room items
		// ----------------

		{}, // #14 - Brimstone
		{}, // #15 - Maw of the Void

		// ----------------
		// Angel Room items
		// ----------------

		// #16 - Crown of Light
		// Eve - Eve cannot use the razor with this start
		{"Eve"},

		{}, // #17 - Sacred Heart
		{}, // #18 - Spirit Sword
		{}, // #19 - Revelation

		// -----------------
		// Secret Room items
		// -----------------

		{}, // #20 - Epic Fetus

		// ------------
		// Custom items
		// ------------

		{}, // #21 - Sawblade

		// ------
		// Builds
		// ------

		{}, // #22 - 20/20 + The Inner Eye
		{}, // #23 - Chocolate Milk + Steven
		{}, // #24 - Godhead + Cupid's Arrow
		{}, // #25 - Haemolacria + The Sad Onion

		// #26 - Incubus + Incubus
		// The Forgotten - The extra Incubus with bone clubs are not very helpful
		{"The Forgotten"},

		// #27 - Monstro's Lung + The Sad Onion
		// Keeper - The charge rate is too low to be very useful
		// Tainted Keeper - The charge rate is too low to be very useful
		{"Keeper", "Tainted Keeper"},

		{}, // #28 - Technology + A Lump of Coal
		{}, // #29 - Twisted Pair + Twisted Pair
		{}, // #30 - Pointy Rib + Eve's Mascara

		// #31 - Fire Mind + Mysterious Liquid + 13 Luck
		// Azazel - The synergy is only useful with a tear build
		// The Forgotten - The synergy is only useful with a tear build
		// Tainted Azazel - The synergy is only useful with a tear build
		{"Azazel", "The Forgotten", "Tainted Azazel"},

		// #32 - Eye of the Occult + Loki's Horns + 15 Luck
		// Azazel - The synergy is only useful with a tear build
		// The Forgotten - The synergy is only useful with a tear build
		// Tainted Azazel - The synergy is only useful with a tear build
		{"Azazel", "The Forgotten", "Tainted Azazel"},

		{}, // #33 - Distant Admiration + Friend Zone + Forever Alone + BFFS!
	}
)
<commit_msg>updating builds<commit_after>package server

var (
	// This must be kept in sync with the build exceptions in "isaac-tournament-bot"
	buildExceptions = [][]string{
		{}, // #0 - n/a

		// -------------------
		// Treasure Room items
		// -------------------

		// #1 - Cricket's Body
		// Azazel - The Brimstone beam inherits the split shots but they are not very good
		// Tainted Azazel - The Brimstone beam inherits the split shots but they are not very good
		{"Azazel", "Tainted Azazel"},

		{}, // #2 - Cricket's Head

		// #3 - Dead Eye
		// Azazel - The Brimstone beam prevents it from working
		// Lilith - It is hard to be accurate when shots come from Incubus
		// Keeper - It is hard to be accurate with triple shot
		// Tainted Azazel - The Brimstone beam prevents it from working
		// Tainted Keeper - It is hard to be accurate with quad shot
		{"Azazel", "Lilith", "Keeper", "Tainted Azazel", "Tainted Keeper"},

		// #4 - Death's Touch
		// The Forgotten - The piercing shots do nothing for the bone club
		// Tainted Forgotten - The piercing shots do nothing for the bone club
		{"The Forgotten", "Tainted Forgotten"},

		// #5 - Dr. Fetus
		// Tainted Forgotten - Very annoying to use with the skeleton body
		{"Tainted Forgotten"},

		// #6 - Ipecac
		// Azazel - The short-range brimstone causes self-damage
		// Tainted Eve - Can cause unavoidable damage if a clot is behind you or shoots at an obstacle near you
		{"Azazel", "Tainted Eve"},

		{}, // #7 - Magic Mushroom
		{}, // #8 - Mom's Knife
		{}, // #9 - Polyphemus
		{}, // #10 - Proptosis
		{}, // #11 - Tech.5
		{}, // #12 - Tech X
		{}, // #13 - C Section

		// ----------------
		// Devil Room items
		// ----------------

		// #14 - Brimstone
		// Tainted Lilith - Gello fires the brim very slowly and auto-fire is not always accurate
		{"Tainted Lilith"},

		{}, // #15 - Maw of the Void

		// ----------------
		// Angel Room items
		// ----------------

		// #16 - Crown of Light
		// Eve - Eve cannot use the razor with this start
		// Tainted Magdalene - Crown is never full with the depleting hearts
		// Tainted Eve - Crown can't be active with the clot mechanic
		{"Eve", "Tainted Magdalene", "Tainted Eve"},

		{}, // #17 - Sacred Heart

		// #18 - Spirit Sword
		// Lilith - Annoying because the sword goes to Incubus
		// The Forgotten - No synergy with the bone
		// Tainted Forgotten - No synergy with the bone
		{"Lilith", "The Forgotten", "Tainted Forgotten"},

		{}, // #19 - Revelation

		// -----------------
		// Secret Room items
		// -----------------

		// #20 - Epic Fetus
		// Tainted Lilith - The target keeps moving and you can't control it, making it impossible to target enemies
		{"Tainted Lilith"},

		// ------------
		// Custom items
		// ------------

		// #21 - Sawblade
		// Bethany - Very complicated to play orbitals with her because she can't protect herself from losing the deal with soul hearts
		// Tainted Eve - Impossible to play orbitals with Tainted Eve's clots, they will disappear very quickly
		// Tainted Lost - With his health mechanic, it is too dangerous to use orbitals
		{"Bethany", "Tainted Eve", "Tainted Lost"},

		// ------
		// Builds
		// ------

		{}, // #22 - 20/20 + The Inner Eye
		{}, // #23 - Chocolate Milk + Steven

		// #24 - Godhead + Cupid's Arrow
		// Azazel - Small damage up for a tears down, resulting in a loss of DPS overall
		// The Forgotten - Does nothing with the bone club
		// Tainted Forgotten - Does nothing with the bone club
		{"Azazel", "The Forgotten", "Tainted Forgotten"},

		{}, // #25 - Haemolacria + The Sad Onion
		{}, // #26 - Incubus + Incubus

		// #27 - Monstro's Lung + The Sad Onion
		// Keeper - Huge tears down, resulting in a loss of DPS overall
		// Tainted Keeper - Huge tears down, resulting in a loss of DPS overall
		{"Keeper", "Tainted Keeper"},

		{}, // #28 - Technology + A Lump of Coal
		{}, // #29 - Twisted Pair + Twisted Pair
		{}, // #30 - Pointy Rib + Eve's Mascara

		// #31 - Fire Mind + Mysterious Liquid + 13 Luck
		// Azazel - The synergy is only useful with a tear build
		// The Forgotten - The synergy is only useful with a tear build
		// Tainted Azazel - The synergy is only useful with a tear build
		// Tainted Lost - Too dangerous to be synergistic
		// Tainted Forgotten - The synergy is only useful with a tear build
		{"Azazel", "The Forgotten", "Tainted Azazel", "Tainted Lost", "Tainted Forgotten"},

		// #32 - Eye of the Occult + Loki's Horns + 15 Luck
		// Azazel - Homing brimstone is too powerful, resulting in a build with a low-skill requirement
		// The Forgotten - It is only a damage up on the bone club
		// Tainted Azazel - Homing brimstone is too powerful, resulting in a build with a low-skill requirement
		// Tainted Forgotten - It is only a damage up on the bone club
		{"Azazel", "The Forgotten", "Tainted Azazel", "Tainted Forgotten"},

		{}, // #33 - Distant Admiration + Friend Zone + Forever Alone + BFFS!
	}
)
<|endoftext|>


<commit_before>package utils

import "testing"

//import "fmt"

var wrote string

type testWriter struct {
}

func (self testWriter) Write(p []byte) (n int, err error) {
	wrote = string(p)
	return len(p), nil
}

type testReader struct {
	toRead string
}

func (self testReader) Read(p []byte) (n int, err error) {
	for i := 0; i < len(self.toRead); i++ {
		p[i] = self.toRead[i]
	}

	p[len(self.toRead)] = '\n'

	return len(self.toRead) + 1, nil
}

type testReadWriter struct {
	testReader
	testWriter
}

func Test_WriteLine(t *testing.T) {
	var writer testWriter

	line := "This is a line"
	want := line + "\r\n"

	WriteLine(writer, line)

	if wrote != want {
		t.Errorf("WriteLine(%q) == %q, want %q", line, wrote, want)
	}
}

func Test_Simplify(t *testing.T) {
	var tests = []struct {
		s, want string
	}{
		{" Test1", "test1"},
		{"TesT2 ", "test2"},
		{" tESt3 ", "test3"},
		{"\tTeSt4\t", "test4"},
		{"    teSt5     \n", "test5"},
		{"   \t \t TEST6 \n \t", "test6"},
	}

	for _, c := range tests {
		got := Simplify(c.s)
		if got != c.want {
			t.Errorf("Simplify(%q) == %q, want %q", c.s, got, c.want)
		}
	}
}

func Test_GetRawUserInput(t *testing.T) {
	var readWriter testReadWriter

	var tests = []struct {
		input, output string
	}{
		{"Test1", "Test1"},
		{"TEST2", "TEST2"},
		{" test3 ", " test3 "},
		{"\tTeSt4\t", "\tTeSt4\t"},
		{"x", ""},
		{"X", ""},
	}

	for _, test := range tests {
		readWriter.toRead = test.input
		line := GetRawUserInput(readWriter, ">")
		if line != test.output {
			t.Errorf("GetRawUserInput(%q) == %q, want %q", test.input, line, test.output)
		}
	}
}

func Test_GetUserInput(t *testing.T) {
	var readWriter testReadWriter

	var tests = []struct {
		input, output string
	}{
		{"Test1", "test1"},
		{"TEST2", "test2"},
		{"\tTeSt3\t", "test3"},
		{"    teSt4     \n", "test4"},
		{"   \t \t TEST5 \n \t", "test5"},
		{"x", ""},
		{"X", ""},
	}

	for _, test := range tests {
		readWriter.toRead = test.input
		line := GetUserInput(readWriter, ">")
		if line != test.output {
			t.Errorf("GetUserInput(%q) == %q, want %q", test.input, line, test.output)
		}
	}
}

func Test_HandleError(t *testing.T) {
	// TODO try using recover
	HandleError(nil)
}

func Test_FormatName(t *testing.T) {
	var tests = []struct {
		input  string
		output string
	}{
		{"", ""},
		{"joe", "Joe"},
		{"ASDF", "Asdf"},
		{"Bob", "Bob"},
		{"aBcDeFg", "Abcdefg"},
	}

	for _, test := range tests {
		result := FormatName(test.input)
		if result != test.output {
			t.Errorf("FormatName(%s) == %s, want %s", test.input, result, test.output)
		}
	}
}

func Test_ValidateName(t *testing.T) {
	var tests = []struct {
		input  string
		output bool
	}{
		{"t", false},
		{"te", false},
		{"tes", true},
		{"te1", false},
		{"test", true},
		{"testing", true},
		{"*(!(@#*$", false},
		{"Abc1abc", false},
		{"123456789012", false},
		{"aslsidjfljll", true},
		{"1slsidjfljll", false},
		{"aslsidjfljl3", false},
	}

	for _, test := range tests {
		result := (ValidateName(test.input) == nil)
		if result != test.output {
			t.Errorf("ValidateName(%q) == %q, want %q", test.input, result, test.output)
		}
	}
}

func Test_BestMatch(t *testing.T) {
	searchList := []string{"", "Foo", "Bar", "Joe", "Bob", "Abcdef", "Abc", "QrStUv"}

	var tests = []struct {
		input  string
		output int
	}{
		{"f", 1},
		{"B", -2},
		{"alseifjlfji", -1},
		{"AB", -2},
		{"aBc", 6},
		{"AbCd", 5},
		{"q", 7},
		{"jo", 3},
	}

	for _, test := range tests {
		result := BestMatch(test.input, searchList)
		if result != test.output {
			t.Errorf("BestMatch(%v) == %v, want %v", test.input, result, test.output)
		}
	}
}

// vim:nocindent
<commit_msg>Added unit test for utils.Argify<commit_after>package utils

import "testing"

//import "fmt"

var wrote string

type testWriter struct {
}

func (self testWriter) Write(p []byte) (n int, err error) {
	wrote = string(p)
	return len(p), nil
}

type testReader struct {
	toRead string
}

func (self testReader) Read(p []byte) (n int, err error) {
	for i := 0; i < len(self.toRead); i++ {
		p[i] = self.toRead[i]
	}

	p[len(self.toRead)] = '\n'

	return len(self.toRead) + 1, nil
}

type testReadWriter struct {
	testReader
	testWriter
}

func Test_WriteLine(t *testing.T) {
	var writer testWriter

	line := "This is a line"
	want := line + "\r\n"

	WriteLine(writer, line)

	if wrote != want {
		t.Errorf("WriteLine(%q) == %q, want %q", line, wrote, want)
	}
}

func Test_Simplify(t *testing.T) {
	var tests = []struct {
		s, want string
	}{
		{" Test1", "test1"},
		{"TesT2 ", "test2"},
		{" tESt3 ", "test3"},
		{"\tTeSt4\t", "test4"},
		{"    teSt5     \n", "test5"},
		{"   \t \t TEST6 \n \t", "test6"},
	}

	for _, c := range tests {
		got := Simplify(c.s)
		if got != c.want {
			t.Errorf("Simplify(%q) == %q, want %q", c.s, got, c.want)
		}
	}
}

func Test_GetRawUserInput(t *testing.T) {
	var readWriter testReadWriter

	var tests = []struct {
		input, output string
	}{
		{"Test1", "Test1"},
		{"TEST2", "TEST2"},
		{" test3 ", " test3 "},
		{"\tTeSt4\t", "\tTeSt4\t"},
		{"x", ""},
		{"X", ""},
	}

	for _, test := range tests {
		readWriter.toRead = test.input
		line := GetRawUserInput(readWriter, ">")
		if line != test.output {
			t.Errorf("GetRawUserInput(%q) == %q, want %q", test.input, line, test.output)
		}
	}
}

func Test_GetUserInput(t *testing.T) {
	var readWriter testReadWriter

	var tests = []struct {
		input, output string
	}{
		{"Test1", "test1"},
		{"TEST2", "test2"},
		{"\tTeSt3\t", "test3"},
		{"    teSt4     \n", "test4"},
		{"   \t \t TEST5 \n \t", "test5"},
		{"x", ""},
		{"X", ""},
	}

	for _, test := range tests {
		readWriter.toRead = test.input
		line := GetUserInput(readWriter, ">")
		if line != test.output {
			t.Errorf("GetUserInput(%q) == %q, want %q", test.input, line, test.output)
		}
	}
}

func Test_HandleError(t *testing.T) {
	// TODO try using recover
	HandleError(nil)
}

func Test_FormatName(t *testing.T) {
	var tests = []struct {
		input  string
		output string
	}{
		{"", ""},
		{"joe", "Joe"},
		{"ASDF", "Asdf"},
		{"Bob", "Bob"},
		{"aBcDeFg", "Abcdefg"},
	}

	for _, test := range tests {
		result := FormatName(test.input)
		if result != test.output {
			t.Errorf("FormatName(%s) == %s, want %s", test.input, result, test.output)
		}
	}
}

func Test_ValidateName(t *testing.T) {
	var tests = []struct {
		input  string
		output bool
	}{
		{"t", false},
		{"te", false},
		{"tes", true},
		{"te1", false},
		{"test", true},
		{"testing", true},
		{"*(!(@#*$", false},
		{"Abc1abc", false},
		{"123456789012", false},
		{"aslsidjfljll", true},
		{"1slsidjfljll", false},
		{"aslsidjfljl3", false},
	}

	for _, test := range tests {
		result := (ValidateName(test.input) == nil)
		if result != test.output {
			t.Errorf("ValidateName(%q) == %q, want %q", test.input, result, test.output)
		}
	}
}

func Test_BestMatch(t *testing.T) {
	searchList := []string{"", "Foo", "Bar", "Joe", "Bob", "Abcdef", "Abc", "QrStUv"}

	var tests = []struct {
		input  string
		output int
	}{
		{"f", 1},
		{"B", -2},
		{"alseifjlfji", -1},
		{"AB", -2},
		{"aBc", 6},
		{"AbCd", 5},
		{"q", 7},
		{"jo", 3},
	}

	for _, test := range tests {
		result := BestMatch(test.input, searchList)
		if result != test.output {
			t.Errorf("BestMatch(%v) == %v, want %v", test.input, result, test.output)
		}
	}
}

func compareStringLists(l1 []string, l2 []string) bool {
	if len(l1) != len(l2) {
		return false
	}

	for i, str := range l1 {
		if str != l2[i] {
			return false
		}
	}

	return true
}

func Test_Argify(t *testing.T) {
	var tests = []struct {
		input   string
		output1 string
		output2 []string
	}{
		{"", "", []string{}},
		{"test", "test", []string{}},
		{"test two", "test", []string{"two"}},
		{"test one two", "test", []string{"one", "two"}},
		{"this is a somewhat longer test that should also work",
			"this", []string{"is", "a", "somewhat", "longer", "test", "that", "should", "also", "work"}},
	}

	for _, test := range tests {
		result1, result2 := Argify(test.input)

		if result1 != test.output1 || compareStringLists(result2, test.output2) == false {
			t.Errorf("Argify(%v) == %v, %v. Want %v, %v", test.input, result1, result2, test.output1, test.output2)
		}
	}
}

// vim:nocindent
<|endoftext|>


<commit_before>package photonstats

import (
	"flag"
	"fmt"
	mp "github.com/mackerelio/go-mackerel-plugin-helper"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"time"
)

const (
	photonUrl    = "https://counter.photonengine.com/Counter/api/data/app/"
	photonRegion = "jp"
	secondsAgo   = 90
)

type PhotonStatsPlugin struct {
	Url        string
	AppId      string
	Region     string
	Token      string
	SecondsAgo int
}

var graphdef = map[string]mp.Graphs{
	"photon.rooms": {
		Label: "Photon Rooms",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "rooms", Label: "room", Diff: false},
		},
	},
	"photon.stats": {
		Label: "Photon Stats",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "ccu", Label: "ccu", Diff: false},
			{Name: "rejects", Label: "reject", Diff: false},
		},
	},
}

func getPhotonStats(p PhotonStatsPlugin, name string) (string, error) {
	endPointUrl := p.Url + p.AppId + "/" + p.Region + "/" + name
	u, err := url.Parse(endPointUrl)
	if err != nil {
		log.Fatal(err)
	}
	q := u.Query()
	now := time.Now()
	end := now.UTC()
	start := end.Add(-time.Duration(p.SecondsAgo) * time.Second)
	q.Set("start", start.Format("2006-01-02T03:04:05"))
	q.Set("end", end.Format("2006-01-02T03:04:05"))
	u.RawQuery = q.Encode()
	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return "", err
	}
	req.Header.Set("Authorization", p.Token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("URL:%s, Range:%s - %s, HTTP status error: %d",
			u.String(), start.Format("2006-01-02T03:04:05"), end.Format("2006-01-02T03:04:05"), resp.StatusCode)
	}
	body, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return "", err
	}
	return string(body[:]), nil
}

// FetchMetrics interface for mackerelplugin
func (u PhotonStatsPlugin) FetchMetrics() (stats map[string]interface{}, err error) {
	ccu, err := getPhotonStats(u, "ccu")
	rooms, err := getPhotonStats(u, "rooms")
	rejects, err := getPhotonStats(u, "rejects")
	if err != nil {
		return nil, err
	}
	return map[string]interface{}{"rooms": rooms, "ccu": ccu, "rejects": rejects}, nil
}

// GraphDefinition interface for mackerelplugin
func (u PhotonStatsPlugin) GraphDefinition() map[string](mp.Graphs) {
	return graphdef
}

func Do() {
	optSecondsAgo := flag.Int("seconds", secondsAgo, "seconds")
	optAppid := flag.String("appid", "", "App Id")
	optUrl := flag.String("url", photonUrl, "Photon analytivs api url")
	optRegion := flag.String("region", photonRegion, "region")
	optToken := flag.String("token", "", "Authorization Token")
	flag.Parse()

	var photon PhotonStatsPlugin
	photon.SecondsAgo = *optSecondsAgo
	photon.AppId = *optAppid
	photon.Url = *optUrl
	photon.Region = *optRegion
	photon.Token = *optToken

	helper := mp.NewMackerelPlugin(photon)
	helper.Run()
}
<commit_msg>channel messages bandwidth<commit_after>package photonstats

import (
	"flag"
	"fmt"
	mp "github.com/mackerelio/go-mackerel-plugin-helper"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"time"
)

const (
	photonUrl    = "https://counter.photonengine.com/Counter/api/data/app/"
	photonRegion = "jp"
	secondsAgo   = 90
)

type PhotonStatsPlugin struct {
	Url        string
	AppId      string
	Region     string
	Token      string
	SecondsAgo int
}

var graphdef = map[string]mp.Graphs{
	"photon.rooms": {
		Label: "Photon Rooms",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "rooms", Label: "room", Diff: false},
		},
	},
	"photon.channel": {
		Label: "Photon Channel",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "channels", Label: "channel", Diff: false},
		},
	},
	"photon.stats": {
		Label: "Photon Stats",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "ccu", Label: "ccu", Diff: false},
			{Name: "rejects", Label: "reject", Diff: false},
		},
	},
	"photon.message": {
		Label: "Photon Message",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "messages", Label: "message", Diff: false},
		},
	},
	"photon.bandwidth": {
		Label: "Photon Bandwidth",
		Unit:  "integer",
		Metrics: []mp.Metrics{
			{Name: "bandwidth", Label: "bandwidth", Diff: false},
			{Name: "bandwidthchat", Label: "bandwidth chat", Diff: false},
		},
	},
}

func getPhotonStats(p PhotonStatsPlugin, name string) (string, error) {
	endPointUrl := p.Url + p.AppId + "/" + p.Region + "/" + name
	u, err := url.Parse(endPointUrl)
	if err != nil {
		log.Fatal(err)
	}
	q := u.Query()
	now := time.Now()
	end := now.UTC()
	start := end.Add(-time.Duration(p.SecondsAgo) * time.Second)
	q.Set("start", start.Format("2006-01-02T03:04:05"))
	q.Set("end", end.Format("2006-01-02T03:04:05"))
	u.RawQuery = q.Encode()
	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return "", err
	}
	req.Header.Set("Authorization", p.Token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("URL:%s, Range:%s - %s, HTTP status error: %d",
			u.String(), start.Format("2006-01-02T03:04:05"), end.Format("2006-01-02T03:04:05"), resp.StatusCode)
	}
	body, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return "", err
	}
	return string(body[:]), nil
}

// FetchMetrics interface for mackerelplugin
func (u PhotonStatsPlugin) FetchMetrics() (stats map[string]interface{}, err error) {
	ccu, err := getPhotonStats(u, "ccu")
	rooms, err := getPhotonStats(u, "rooms")
	channels, err := getPhotonStats(u, "channels")
	rejects, err := getPhotonStats(u, "rejects")
	messages, err := getPhotonStats(u, "messages")
	bandwidth, err := getPhotonStats(u, "bandwidth")
	bandwidthchat, err := getPhotonStats(u, "bandwidthchat")
	if err != nil {
		return nil, err
	}
	return map[string]interface{}{
		"ccu":           ccu,
		"rooms":         rooms,
		"channels":      channels,
		"rejects":       rejects,
		"messages":      messages,
		"bandwidth":     bandwidth,
		"bandwidthchat": bandwidthchat,
	}, nil
}

// GraphDefinition interface for mackerelplugin
func (u PhotonStatsPlugin) GraphDefinition() map[string](mp.Graphs) {
	return graphdef
}

func Do() {
	optSecondsAgo := flag.Int("seconds", secondsAgo, "seconds")
	optAppid := flag.String("appid", "", "App Id")
	optUrl := flag.String("url", photonUrl, "Photon analytivs api url")
	optRegion := flag.String("region", photonRegion, "region")
	optToken := flag.String("token", "", "Authorization Token")
	flag.Parse()

	var photon PhotonStatsPlugin
	photon.SecondsAgo = *optSecondsAgo
	photon.AppId = *optAppid
	photon.Url = *optUrl
	photon.Region = *optRegion
	photon.Token = *optToken

	helper := mp.NewMackerelPlugin(photon)
	helper.Run()
}
<|endoftext|>