<commit_before>/*
   PulseHA - HA Cluster Daemon
   Copyright (C) 2017-2019  Andrew Zak <andrew@linux.com>

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

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

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package config

import (
	"encoding/json"
	"errors"
	log "github.com/Sirupsen/logrus"
	"github.com/Syleron/PulseHA/src/jsonHelper"
	"github.com/Syleron/PulseHA/src/utils"
	"io/ioutil"
	"os"
	"sync"
)

type Config struct {
	Pulse   Local               `json:"pulseha"`
	Groups  map[string][]string `json:"floating_ip_groups"`
	Nodes   map[string]Node     `json:"nodes"`
	sync.Mutex
}

type Local struct {
	HealthCheckInterval int    `json:"hcs_interval"`
	FailOverInterval    int    `json:"fos_interval"`
	FailOverLimit       int    `json:"fo_limit"`
	LocalNode           string `json:"local_node"`
	ClusterToken        string `json:"cluster_token"`
	LoggingLevel        string `json:"logging_level"`
}

type Nodes struct {
	Nodes map[string]Node
}

type Node struct {
	IP       string              `json:"bind_address"`
	Port     string              `json:"bind_port"`
	IPGroups map[string][]string `json:"group_assignments"`
}

/**
 * Returns a copy of the config
 */
func (c *Config) GetConfig() Config {
	return *c
}

/**
 * Sets the local node name
 */
func (c *Config) SetLocalNode() error {
	hostname, err := utils.GetHostname()
	if err != nil {
		return errors.New("cannot set local node because unable to get local hostname")
	}
	log.Debugf("Config:setLocalNode Hostname is: %s", hostname)
	c.Pulse.LocalNode = hostname
	return nil
}

/**

 */
func (c *Config) NodeCount() int {
	return len(c.Nodes)
}

/**
 * Return the local node name
 */
func (c *Config) GetLocalNode() string {
	return c.Pulse.LocalNode
}

/**
 * Function used to load the config
 */
func (c *Config) Load() {
	c.Lock()
	defer c.Unlock()
	b, err := ioutil.ReadFile("/etc/pulseha/config.json")
	if err != nil {
		log.Fatalf("Error reading config file: %s", err)
	}
	err = json.Unmarshal([]byte(b), &c)
	if err != nil {
		log.Fatalf("Unable to unmarshal config: %s", err)
	}
	if err != nil {
		log.Fatal("Unable to load config.json. Either it doesn't exist or there may be a permissions issue")
	}
	err = c.SetLocalNode()
	if err != nil {
		log.Fatalf("The local Hostname does not match the configuration")
	}
}

/**
 * Function used to save the config
 */
func (c *Config) Save() {
	log.Debug("Config:Save() Saving config..")
	c.Lock()
	defer c.Unlock()
	// Validate before we save
	c.Validate()
	// Convert struct back to JSON format
	configJSON, _ := json.MarshalIndent(c, "", "    ")
	// Save back to file
	err := ioutil.WriteFile("/etc/pulseha/config.json", configJSON, 0644)
	// Check for errors
	if err != nil {
		log.Error("Unable to save config.json. Either it doesn't exist or there may be a permissions issue")
		os.Exit(1)
	}
}

/**
 * Reload the config file into memory.
 * Note: Need to clear memory value before calling Load()
 */
func (c *Config) Reload() {
	log.Info("Reloading PulseHA config")
	c.Load()
}

/**
 *
 */
func (c *Config) Validate() {
	var success bool = true

	// Make sure our groups section is valid
	if c.Groups == nil {
		log.Fatal("Unable to load Groups section of the config")
		success = false
	}

	// Make sure our nodes section is valid
	if c.Nodes == nil {
		log.Fatal("Unable to load Nodes section of the config")
		success = false
	}

	// if we are in a cluster.. does our hostname exist?
	if c.ClusterCheck() {
		for name, _ := range c.Nodes {
			if _, ok := c.Nodes[name]; !ok {
				log.Fatal("Hostname mismatch. Localhost does not exist in cluster config")
				success = false
			}
		}
	}

	if c.Pulse.FailOverInterval < 1000 || c.Pulse.FailOverLimit < 1000 || c.Pulse.HealthCheckInterval < 1000 {
		log.Fatal("Please make sure the interval and limit values in your config are valid millisecond values of at least 1 second")
		success = false
	}

	if c.Pulse.FailOverLimit < c.Pulse.FailOverInterval {
		log.Fatal("The fos_interval value must be a smaller value then your fo_limit")
		success = false
	}

	// TODO: Check if our hostname exists in the cluster config
	// TODO: Check if we have valid network interface names

	// Handles if shit hits the roof
	if success == false {
		// log why we exited?
		os.Exit(1)
	}
}

/**
 *
 */
func (c *Config) LocalNode() Node {
	hostname, err := utils.GetHostname()
	if err != nil {
		return Node{}
	}
	return c.Nodes[hostname]
}

/**
 * Private - Check to see if we are in a configured cluster or not.
 */
func (c *Config) ClusterCheck() bool {
	total := len(c.Nodes)
	if total > 0 {
		// if there is only one node we can assume it's ours
		if total == 1 {
			// make sure we have a bind IP/Port or we are not in a cluster
			hostname, err := utils.GetHostname()
			if err != nil {
				return false
			}
			if c.Nodes[hostname].IP == "" && c.Nodes[hostname].Port == "" {
				return false
			}
		}
		return true
	}
	return false
}

/**
Returns the interface the group is assigned to
*/
func (c *Config) GetGroupIface(node string, groupName string) string {
	for nodeName, n := range c.Nodes {
		if nodeName == node {
			for iface, groups := range n.IPGroups {
				for _, group := range groups {
					if group == groupName {
						return iface
					}
				}
			}
		}
	}
	return ""
}

/**
Instantiate, setup and return our Config
*/
func GetConfig() *Config {
	cfg := Config{}
	cfg.Load()
	cfg.Validate()
	return &cfg
}

/**
Returns the hostname for a node based of it's IP address
*/
func (c *Config) GetNodeHostnameByAddress(address string) (string, error) {
	for name, node := range c.Nodes {
		if node.IP == address {
			return name, nil
		}
	}
	return "", errors.New("unable to find node with IP address " + address)
}

// UpdateValue - Update a key's value
func (c *Config) UpdateValue(key string, value string) error {
	err := jsonHelper.SetStructFieldByTag(key, value, c.Pulse)
	if err != nil {
		return err
	}
	// Save our config with the updated info
	c.Save()
	return  nil
}<commit_msg>updated configupdate function to use the config pointer<commit_after>/*
   PulseHA - HA Cluster Daemon
   Copyright (C) 2017-2019  Andrew Zak <andrew@linux.com>

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

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

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package config

import (
	"encoding/json"
	"errors"
	log "github.com/Sirupsen/logrus"
	"github.com/Syleron/PulseHA/src/jsonHelper"
	"github.com/Syleron/PulseHA/src/utils"
	"io/ioutil"
	"os"
	"sync"
)

type Config struct {
	Pulse   Local               `json:"pulseha"`
	Groups  map[string][]string `json:"floating_ip_groups"`
	Nodes   map[string]Node     `json:"nodes"`
	sync.Mutex
}

type Local struct {
	HealthCheckInterval int    `json:"hcs_interval"`
	FailOverInterval    int    `json:"fos_interval"`
	FailOverLimit       int    `json:"fo_limit"`
	LocalNode           string `json:"local_node"`
	ClusterToken        string `json:"cluster_token"`
	LoggingLevel        string `json:"logging_level"`
}

type Nodes struct {
	Nodes map[string]Node
}

type Node struct {
	IP       string              `json:"bind_address"`
	Port     string              `json:"bind_port"`
	IPGroups map[string][]string `json:"group_assignments"`
}

/**
 * Returns a copy of the config
 */
func (c *Config) GetConfig() Config {
	return *c
}

/**
 * Sets the local node name
 */
func (c *Config) SetLocalNode() error {
	hostname, err := utils.GetHostname()
	if err != nil {
		return errors.New("cannot set local node because unable to get local hostname")
	}
	log.Debugf("Config:setLocalNode Hostname is: %s", hostname)
	c.Pulse.LocalNode = hostname
	return nil
}

/**

 */
func (c *Config) NodeCount() int {
	return len(c.Nodes)
}

/**
 * Return the local node name
 */
func (c *Config) GetLocalNode() string {
	return c.Pulse.LocalNode
}

/**
 * Function used to load the config
 */
func (c *Config) Load() {
	c.Lock()
	defer c.Unlock()
	b, err := ioutil.ReadFile("/etc/pulseha/config.json")
	if err != nil {
		log.Fatalf("Error reading config file: %s", err)
	}
	err = json.Unmarshal([]byte(b), &c)
	if err != nil {
		log.Fatalf("Unable to unmarshal config: %s", err)
	}
	if err != nil {
		log.Fatal("Unable to load config.json. Either it doesn't exist or there may be a permissions issue")
	}
	err = c.SetLocalNode()
	if err != nil {
		log.Fatalf("The local Hostname does not match the configuration")
	}
}

/**
 * Function used to save the config
 */
func (c *Config) Save() {
	log.Debug("Config:Save() Saving config..")
	c.Lock()
	defer c.Unlock()
	// Validate before we save
	c.Validate()
	// Convert struct back to JSON format
	configJSON, _ := json.MarshalIndent(c, "", "    ")
	// Save back to file
	err := ioutil.WriteFile("/etc/pulseha/config.json", configJSON, 0644)
	// Check for errors
	if err != nil {
		log.Error("Unable to save config.json. Either it doesn't exist or there may be a permissions issue")
		os.Exit(1)
	}
}

/**
 * Reload the config file into memory.
 * Note: Need to clear memory value before calling Load()
 */
func (c *Config) Reload() {
	log.Info("Reloading PulseHA config")
	c.Load()
}

/**
 *
 */
func (c *Config) Validate() {
	var success bool = true

	// Make sure our groups section is valid
	if c.Groups == nil {
		log.Fatal("Unable to load Groups section of the config")
		success = false
	}

	// Make sure our nodes section is valid
	if c.Nodes == nil {
		log.Fatal("Unable to load Nodes section of the config")
		success = false
	}

	// if we are in a cluster.. does our hostname exist?
	if c.ClusterCheck() {
		for name, _ := range c.Nodes {
			if _, ok := c.Nodes[name]; !ok {
				log.Fatal("Hostname mismatch. Localhost does not exist in cluster config")
				success = false
			}
		}
	}

	if c.Pulse.FailOverInterval < 1000 || c.Pulse.FailOverLimit < 1000 || c.Pulse.HealthCheckInterval < 1000 {
		log.Fatal("Please make sure the interval and limit values in your config are valid millisecond values of at least 1 second")
		success = false
	}

	if c.Pulse.FailOverLimit < c.Pulse.FailOverInterval {
		log.Fatal("The fos_interval value must be a smaller value then your fo_limit")
		success = false
	}

	// TODO: Check if our hostname exists in the cluster config
	// TODO: Check if we have valid network interface names

	// Handles if shit hits the roof
	if success == false {
		// log why we exited?
		os.Exit(1)
	}
}

/**
 *
 */
func (c *Config) LocalNode() Node {
	hostname, err := utils.GetHostname()
	if err != nil {
		return Node{}
	}
	return c.Nodes[hostname]
}

/**
 * Private - Check to see if we are in a configured cluster or not.
 */
func (c *Config) ClusterCheck() bool {
	total := len(c.Nodes)
	if total > 0 {
		// if there is only one node we can assume it's ours
		if total == 1 {
			// make sure we have a bind IP/Port or we are not in a cluster
			hostname, err := utils.GetHostname()
			if err != nil {
				return false
			}
			if c.Nodes[hostname].IP == "" && c.Nodes[hostname].Port == "" {
				return false
			}
		}
		return true
	}
	return false
}

/**
Returns the interface the group is assigned to
*/
func (c *Config) GetGroupIface(node string, groupName string) string {
	for nodeName, n := range c.Nodes {
		if nodeName == node {
			for iface, groups := range n.IPGroups {
				for _, group := range groups {
					if group == groupName {
						return iface
					}
				}
			}
		}
	}
	return ""
}

/**
Instantiate, setup and return our Config
*/
func GetConfig() *Config {
	cfg := Config{}
	cfg.Load()
	cfg.Validate()
	return &cfg
}

/**
Returns the hostname for a node based of it's IP address
*/
func (c *Config) GetNodeHostnameByAddress(address string) (string, error) {
	for name, node := range c.Nodes {
		if node.IP == address {
			return name, nil
		}
	}
	return "", errors.New("unable to find node with IP address " + address)
}

// UpdateValue - Update a key's value
func (c *Config) UpdateValue(key string, value string) error {
	err := jsonHelper.SetStructFieldByTag(key, value, &c.Pulse)
	if err != nil {
		return err
	}
	// Save our config with the updated info
	c.Save()
	return  nil
}<|endoftext|>


<commit_before>/*
Copyright 2021 The Kubernetes Authors.

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 fieldmanager

import (
	"context"
	"fmt"
	"os"
	"reflect"
	"strconv"
	"time"

	"k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/conversion"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apiserver/pkg/endpoints/metrics"
	"k8s.io/klog/v2"
)

func determineIgnoreNonSemanticUpdatesEnabled() bool {
	if ignoreNonSemanticUpdatesString, exists := os.LookupEnv("KUBE_APISERVER_IGNORE_NON_SEMANTIC_UPDATES"); exists {
		if ret, err := strconv.ParseBool(ignoreNonSemanticUpdatesString); err == nil {
			return ret
		} else {
			klog.Errorf("failed to parse envar KUBE_APISERVER_IGNORE_NON_SEMANTIC_UPDATES: %v", err)
		}
	}

	// enabled by default
	return true
}

var (
	ignoreNonSemanticUpdatesEnabled = determineIgnoreNonSemanticUpdatesEnabled()
)

var ignoreTimestampEqualities = func() conversion.Equalities {
	var eqs = equality.Semantic.Copy()

	err := eqs.AddFunc(
		func(a, b metav1.ManagedFieldsEntry) bool {
			// Two objects' managed fields are equivalent if, ignoring timestamp,
			//	the objects are deeply equal.
			a.Time = nil
			b.Time = nil
			return reflect.DeepEqual(a, b)
		},
	)

	if err != nil {
		panic(err)
	}

	return eqs
}()

// IgnoreManagedFieldsTimestampsTransformer reverts timestamp updates
// if the non-managed parts of the object are equivalent
func IgnoreManagedFieldsTimestampsTransformer(
	_ context.Context,
	newObj runtime.Object,
	oldObj runtime.Object,
) (res runtime.Object, err error) {
	if !ignoreNonSemanticUpdatesEnabled {
		return newObj, nil
	}

	outcome := "unequal_objects_fast"
	start := time.Now()
	err = nil
	res = nil

	defer func() {
		if err != nil {
			outcome = "error"
		}

		metrics.RecordTimestampComparisonLatency(outcome, time.Since(start))
	}()

	// If managedFields modulo timestamps are unchanged
	//		and
	//	rest of object is unchanged
	//		then
	//	revert any changes to timestamps in managed fields
	//		(to prevent spurious ResourceVersion bump)
	//
	// Procecure:
	// Do a quicker check to see if just managed fields modulo timestamps are
	//	unchanged. If so, then do the full, slower check.
	//
	// In most cases which actually update the object, the managed fields modulo
	//	timestamp check will fail, and we will be able to return early.
	//
	// In other cases, the managed fields may be exactly the same,
	// 	except for timestamp, but the objects are the different. This is the
	//	slow path which checks the full object.
	oldAccessor, err := meta.Accessor(oldObj)
	if err != nil {
		return nil, fmt.Errorf("failed to acquire accessor for oldObj: %v", err)
	}

	accessor, err := meta.Accessor(newObj)
	if err != nil {
		return nil, fmt.Errorf("failed to acquire accessor for newObj: %v", err)
	}

	oldManagedFields := oldAccessor.GetManagedFields()
	newManagedFields := accessor.GetManagedFields()

	if len(oldManagedFields) != len(newManagedFields) {
		// Return early if any managed fields entry was added/removed.
		// We want to retain user expectation that even if they write to a field
		// whose value did not change, they will still result as the field
		// manager at the end.
		return newObj, nil
	} else if len(newManagedFields) == 0 {
		// This transformation only makes sense when managedFields are
		// non-empty
		return newObj, nil
	}

	// This transformation only makes sense if the managed fields has at least one
	// changed timestamp; and are otherwise equal. Return early if there are no
	// changed timestamps.
	allTimesUnchanged := true
	for i, e := range newManagedFields {
		if !e.Time.Equal(oldManagedFields[i].Time) {
			allTimesUnchanged = false
			break
		}
	}

	if allTimesUnchanged {
		return newObj, nil
	}

	// This condition ensures the managed fields are always compared first. If
	//	this check fails, the if statement will short circuit. If the check
	// 	succeeds the slow path is taken which compares entire objects.
	if !ignoreTimestampEqualities.DeepEqualWithNilDifferentFromEmpty(oldManagedFields, newManagedFields) {
		return newObj, nil
	}

	if ignoreTimestampEqualities.DeepEqualWithNilDifferentFromEmpty(newObj, oldObj) {
		// Remove any changed timestamps, so that timestamp is not the only
		// change seen by etcd.
		//
		// newManagedFields is known to be exactly pairwise equal to
		// oldManagedFields except for timestamps.
		//
		// Simply replace possibly changed new timestamps with their old values.
		for idx := 0; idx < len(oldManagedFields); idx++ {
			newManagedFields[idx].Time = oldManagedFields[idx].Time
		}

		accessor.SetManagedFields(newManagedFields)
		outcome = "equal_objects"
		return newObj, nil
	}

	outcome = "unequal_objects_slow"
	return newObj, nil
}
<commit_msg>use more apt name for flag<commit_after>/*
Copyright 2021 The Kubernetes Authors.

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 fieldmanager

import (
	"context"
	"fmt"
	"os"
	"reflect"
	"strconv"
	"time"

	"k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/conversion"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apiserver/pkg/endpoints/metrics"
	"k8s.io/klog/v2"
)

func determineAvoidNoopTimestampUpdatesEnabled() bool {
	if avoidNoopTimestampUpdatesString, exists := os.LookupEnv("KUBE_APISERVER_AVOID_NOOP_SSA_TIMESTAMP_UPDATES"); exists {
		if ret, err := strconv.ParseBool(avoidNoopTimestampUpdatesString); err == nil {
			return ret
		} else {
			klog.Errorf("failed to parse envar KUBE_APISERVER_AVOID_NOOP_SSA_TIMESTAMP_UPDATES: %v", err)
		}
	}

	// enabled by default
	return true
}

var (
	avoidNoopTimestampUpdatesEnabled = determineAvoidNoopTimestampUpdatesEnabled()
)

var avoidTimestampEqualities = func() conversion.Equalities {
	var eqs = equality.Semantic.Copy()

	err := eqs.AddFunc(
		func(a, b metav1.ManagedFieldsEntry) bool {
			// Two objects' managed fields are equivalent if, ignoring timestamp,
			//	the objects are deeply equal.
			a.Time = nil
			b.Time = nil
			return reflect.DeepEqual(a, b)
		},
	)

	if err != nil {
		panic(err)
	}

	return eqs
}()

// IgnoreManagedFieldsTimestampsTransformer reverts timestamp updates
// if the non-managed parts of the object are equivalent
func IgnoreManagedFieldsTimestampsTransformer(
	_ context.Context,
	newObj runtime.Object,
	oldObj runtime.Object,
) (res runtime.Object, err error) {
	if !avoidNoopTimestampUpdatesEnabled {
		return newObj, nil
	}

	outcome := "unequal_objects_fast"
	start := time.Now()
	err = nil
	res = nil

	defer func() {
		if err != nil {
			outcome = "error"
		}

		metrics.RecordTimestampComparisonLatency(outcome, time.Since(start))
	}()

	// If managedFields modulo timestamps are unchanged
	//		and
	//	rest of object is unchanged
	//		then
	//	revert any changes to timestamps in managed fields
	//		(to prevent spurious ResourceVersion bump)
	//
	// Procecure:
	// Do a quicker check to see if just managed fields modulo timestamps are
	//	unchanged. If so, then do the full, slower check.
	//
	// In most cases which actually update the object, the managed fields modulo
	//	timestamp check will fail, and we will be able to return early.
	//
	// In other cases, the managed fields may be exactly the same,
	// 	except for timestamp, but the objects are the different. This is the
	//	slow path which checks the full object.
	oldAccessor, err := meta.Accessor(oldObj)
	if err != nil {
		return nil, fmt.Errorf("failed to acquire accessor for oldObj: %v", err)
	}

	accessor, err := meta.Accessor(newObj)
	if err != nil {
		return nil, fmt.Errorf("failed to acquire accessor for newObj: %v", err)
	}

	oldManagedFields := oldAccessor.GetManagedFields()
	newManagedFields := accessor.GetManagedFields()

	if len(oldManagedFields) != len(newManagedFields) {
		// Return early if any managed fields entry was added/removed.
		// We want to retain user expectation that even if they write to a field
		// whose value did not change, they will still result as the field
		// manager at the end.
		return newObj, nil
	} else if len(newManagedFields) == 0 {
		// This transformation only makes sense when managedFields are
		// non-empty
		return newObj, nil
	}

	// This transformation only makes sense if the managed fields has at least one
	// changed timestamp; and are otherwise equal. Return early if there are no
	// changed timestamps.
	allTimesUnchanged := true
	for i, e := range newManagedFields {
		if !e.Time.Equal(oldManagedFields[i].Time) {
			allTimesUnchanged = false
			break
		}
	}

	if allTimesUnchanged {
		return newObj, nil
	}

	// This condition ensures the managed fields are always compared first. If
	//	this check fails, the if statement will short circuit. If the check
	// 	succeeds the slow path is taken which compares entire objects.
	if !avoidTimestampEqualities.DeepEqualWithNilDifferentFromEmpty(oldManagedFields, newManagedFields) {
		return newObj, nil
	}

	if avoidTimestampEqualities.DeepEqualWithNilDifferentFromEmpty(newObj, oldObj) {
		// Remove any changed timestamps, so that timestamp is not the only
		// change seen by etcd.
		//
		// newManagedFields is known to be exactly pairwise equal to
		// oldManagedFields except for timestamps.
		//
		// Simply replace possibly changed new timestamps with their old values.
		for idx := 0; idx < len(oldManagedFields); idx++ {
			newManagedFields[idx].Time = oldManagedFields[idx].Time
		}

		accessor.SetManagedFields(newManagedFields)
		outcome = "equal_objects"
		return newObj, nil
	}

	outcome = "unequal_objects_slow"
	return newObj, nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"os"

	"github.com/cloudfoundry/cli/plugin"
	"github.com/concourse/autopilot/rewind"
)

func fatalIf(err error) {
	if err != nil {
		fmt.Fprintln(os.Stdout, "error:", err)
		os.Exit(1)
	}
}

func main() {
	plugin.Start(&AutopilotPlugin{})
}

type AutopilotPlugin struct{}

func (plugin AutopilotPlugin) Run(cliConnection plugin.CliConnection, args []string) {
	appRepo := NewApplicationRepo(cliConnection)
	appName, manifestPath, appPath, err := ParseArgs(args)
	fatalIf(err)

	venerableAppName := appName + "-venerable"

	actions := rewind.Actions{
		Actions: []rewind.Action{
			// rename
			{
				Forward: func() error {
					return appRepo.RenameApplication(appName, venerableAppName)
				},
			},

			// push
			{
				Forward: func() error {
					return appRepo.PushApplication(manifestPath, appPath)
				},
				ReversePrevious: func() error {
					return appRepo.RenameApplication(venerableAppName, appName)
				},
			},

			// delete
			{
				Forward: func() error {
					return appRepo.DeleteApplication(venerableAppName)
				},
			},
		},
		RewindFailureMessage: "Oh no. Something's gone wrong. I've tried to roll back but you should check to see if everything is OK.",
	}

	err = actions.Execute()
	fatalIf(err)

	fmt.Println()
	fmt.Println("A new version of your application has successfully been pushed!")
	fmt.Println()

	err = appRepo.ListApplications()
	fatalIf(err)
}

func (AutopilotPlugin) GetMetadata() plugin.PluginMetadata {
	return plugin.PluginMetadata{
		Name: "autopilot",
		Commands: []plugin.Command{
			{
				Name:     "zero-downtime-push",
				HelpText: "Perform a zero-downtime push of an application over the top of an old one",
			},
		},
	}
}

func ParseArgs(args []string) (string, string, string, error) {
	flags := flag.NewFlagSet("zero-downtime-push", flag.ContinueOnError)
	manifestPath := flags.String("f", "", "path to an application manifest")
	appPath := flags.String("p", "", "path to application files")

	err := flags.Parse(args[2:])
	if err != nil {
		return "", "", "", err
	}

	appName := args[1]

	if *manifestPath == "" {
		return "", "", "", ErrNoManifest
	}

	return appName, *manifestPath, *appPath, nil
}

var ErrNoManifest = errors.New("a manifest is required to push this application")

type ApplicationRepo struct {
	conn plugin.CliConnection
}

func NewApplicationRepo(conn plugin.CliConnection) *ApplicationRepo {
	return &ApplicationRepo{
		conn: conn,
	}
}

func (repo *ApplicationRepo) RenameApplication(oldName, newName string) error {
	_, err := repo.conn.CliCommand("rename", oldName, newName)
	return err
}

func (repo *ApplicationRepo) PushApplication(manifestPath, appPath string) error {
	args := []string{"push", "-f", manifestPath}

	if appPath != "" {
		args = append(args, "-p", appPath)
	}

	_, err := repo.conn.CliCommand(args...)
	return err
}

func (repo *ApplicationRepo) DeleteApplication(appName string) error {
	_, err := repo.conn.CliCommand("delete", appName, "-f")
	return err
}

func (repo *ApplicationRepo) ListApplications() error {
	_, err := repo.conn.CliCommand("apps")
	return err
}
<commit_msg>adding usage details to help output<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"os"

	"github.com/cloudfoundry/cli/plugin"
	"github.com/concourse/autopilot/rewind"
)

func fatalIf(err error) {
	if err != nil {
		fmt.Fprintln(os.Stdout, "error:", err)
		os.Exit(1)
	}
}

func main() {
	plugin.Start(&AutopilotPlugin{})
}

type AutopilotPlugin struct{}

func (plugin AutopilotPlugin) Run(cliConnection plugin.CliConnection, args []string) {
	appRepo := NewApplicationRepo(cliConnection)
	appName, manifestPath, appPath, err := ParseArgs(args)
	fatalIf(err)

	venerableAppName := appName + "-venerable"

	actions := rewind.Actions{
		Actions: []rewind.Action{
			// rename
			{
				Forward: func() error {
					return appRepo.RenameApplication(appName, venerableAppName)
				},
			},

			// push
			{
				Forward: func() error {
					return appRepo.PushApplication(manifestPath, appPath)
				},
				ReversePrevious: func() error {
					return appRepo.RenameApplication(venerableAppName, appName)
				},
			},

			// delete
			{
				Forward: func() error {
					return appRepo.DeleteApplication(venerableAppName)
				},
			},
		},
		RewindFailureMessage: "Oh no. Something's gone wrong. I've tried to roll back but you should check to see if everything is OK.",
	}

	err = actions.Execute()
	fatalIf(err)

	fmt.Println()
	fmt.Println("A new version of your application has successfully been pushed!")
	fmt.Println()

	err = appRepo.ListApplications()
	fatalIf(err)
}

func (AutopilotPlugin) GetMetadata() plugin.PluginMetadata {
	return plugin.PluginMetadata{
		Name: "autopilot",
		Commands: []plugin.Command{
			{
				Name:     "zero-downtime-push",
				HelpText: "Perform a zero-downtime push of an application over the top of an old one",
				UsageDetails: plugin.Usage {
					Usage: "$ cf zero-downtime-push application-to-replace \\ \n \t-f path/to/new_manifest.yml \\ \n \t-p path/to/new/path",
        },
			},
		},
	}
}

func ParseArgs(args []string) (string, string, string, error) {
	flags := flag.NewFlagSet("zero-downtime-push", flag.ContinueOnError)
	manifestPath := flags.String("f", "", "path to an application manifest")
	appPath := flags.String("p", "", "path to application files")

	err := flags.Parse(args[2:])
	if err != nil {
		return "", "", "", err
	}

	appName := args[1]

	if *manifestPath == "" {
		return "", "", "", ErrNoManifest
	}

	return appName, *manifestPath, *appPath, nil
}

var ErrNoManifest = errors.New("a manifest is required to push this application")

type ApplicationRepo struct {
	conn plugin.CliConnection
}

func NewApplicationRepo(conn plugin.CliConnection) *ApplicationRepo {
	return &ApplicationRepo{
		conn: conn,
	}
}

func (repo *ApplicationRepo) RenameApplication(oldName, newName string) error {
	_, err := repo.conn.CliCommand("rename", oldName, newName)
	return err
}

func (repo *ApplicationRepo) PushApplication(manifestPath, appPath string) error {
	args := []string{"push", "-f", manifestPath}

	if appPath != "" {
		args = append(args, "-p", appPath)
	}

	_, err := repo.conn.CliCommand(args...)
	return err
}

func (repo *ApplicationRepo) DeleteApplication(appName string) error {
	_, err := repo.conn.CliCommand("delete", appName, "-f")
	return err
}

func (repo *ApplicationRepo) ListApplications() error {
	_, err := repo.conn.CliCommand("apps")
	return err
}
<|endoftext|>


<commit_before>// LoL Cruncher - A Historical League of Legends Statistics Tracker
// Copyright (C) 2015  Jason Chu (1lann) 1lanncontact@gmail.com

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

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

// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package database

import (
	"cruncher/app/models/dataFormat"
	"errors"
	// "fmt"
	r "github.com/dancannon/gorethink"
	"github.com/revel/revel"
	"time"
)

var LastPlayerUpdate = time.Now()

var (
	ErrDisconnected      = errors.New("database: disconnected")
	ErrNoResults         = errors.New("database: no results")
	ErrInsertDiscrepancy = errors.New("database: insert discrepancy")
)

func GetBrowserPlayers() ([]dataFormat.Player, error) {
	if !IsConnected {
		go Connect()
		return []dataFormat.Player{}, ErrDisconnected
	}

	c, err := r.Table("players").OrderBy(r.OrderByOpts{"nn"}).
		Pluck("sn", "r").Run(activeSession)
	if isDisconnected(err) {
		return []dataFormat.Player{}, ErrDisconnected
	} else if err != nil {
		return []dataFormat.Player{}, err
	}

	results := []dataFormat.Player{}
	err = c.All(&results)
	c.Close()
	if isDisconnected(err) {
		return []dataFormat.Player{}, ErrDisconnected
	} else if err == r.ErrEmptyResult {
		return []dataFormat.Player{}, ErrNoResults
	} else if err != nil {
		return []dataFormat.Player{}, err
	}

	return results, nil
}

func GetSummonerData(name string, region string) (dataFormat.PlayerData,
	error) {
	if !IsConnected {
		go Connect()
		return dataFormat.PlayerData{}, ErrDisconnected
	}

	name = dataFormat.NormalizeName(name)
	c, err := r.Table("players").
		GetAllByIndex("nr", []string{name, region}).AtIndex(0).
		Merge(func(row r.Term) interface{} {
		return map[string]interface{}{
			"detailed": r.DB("cruncher").Table("detailed").
				GetAllByIndex("ip", row.Field("id")).CoerceTo("array"),
			"basic": r.DB("cruncher").Table("basic").
				GetAllByIndex("ip", row.Field("id")).CoerceTo("array"),
		}
	}).Run(activeSession)

	if isDisconnected(err) {
		return dataFormat.PlayerData{}, ErrDisconnected
	} else if err != nil {
		return dataFormat.PlayerData{}, err
	}

	playerData := dataFormat.PlayerData{}

	err = c.One(&playerData)
	c.Close()
	if isDisconnected(err) {
		return dataFormat.PlayerData{}, ErrDisconnected
	} else if err == r.ErrEmptyResult {
		return dataFormat.PlayerData{}, ErrNoResults
	} else if err != nil {
		return dataFormat.PlayerData{}, err
	}

	return playerData, nil
}

func AddToDetailedPlayer(details dataFormat.DetailedNumberOf) error {
	if !IsConnected {
		go Connect()
		return ErrDisconnected
	}

	resp, err := r.Table("detailed").
		GetAllByIndex("ip", details.InternalPlayerId).Filter(
		map[string]string{
			"p": details.TimePeriod,
			"q": details.Queue,
		}).Update(
		map[string]interface{}{
			"w":  r.Row.Field("w").Add(details.Wins),
			"l":  r.Row.Field("l").Add(details.Losses),
			"t":  r.Row.Field("t").Add(details.TimePlayed),
			"k":  r.Row.Field("k").Add(details.Kills),
			"a":  r.Row.Field("a").Add(details.Assists),
			"d":  r.Row.Field("d").Add(details.Deaths),
			"dk": r.Row.Field("dk").Add(details.DoubleKills),
			"tk": r.Row.Field("tk").Add(details.TripleKills),
			"qk": r.Row.Field("qk").Add(details.QuadraKills),
			"pk": r.Row.Field("pk").Add(details.PentaKills),
			"g":  r.Row.Field("g").Add(details.GoldEarned),
			"m":  r.Row.Field("m").Add(details.MinionsKilled),
			"n":  r.Row.Field("n").Add(details.MonstersKilled),
			"wp": r.Row.Field("wp").Add(details.WardsPlaced),
			"wk": r.Row.Field("wk").Add(details.WardsKilled),
			"b": map[string]interface{}{
				"w": r.Row.Field("b").Field("w").Add(details.Blue.Wins),
				"l": r.Row.Field("b").Field("l").Add(details.Blue.Losses),
			}, "r": map[string]interface{}{
				"w": r.Row.Field("b").Field("w").Add(details.Red.Wins),
				"l": r.Row.Field("b").Field("l").Add(details.Red.Losses),
			},
		}).RunWrite(activeSession)

	if isDisconnected(err) {
		return ErrDisconnected
	} else if err != nil {
		return err
	}

	if resp.Replaced+resp.Unchanged == 0 {
		// Doesn't exist, insert new
		resp, err := r.Table("detailed").Insert(details).RunWrite(activeSession)
		if isDisconnected(err) {
			return ErrDisconnected
		} else if err != nil {
			return err
		} else if resp.Inserted == 0 {
			return ErrInsertDiscrepancy
		}
	}

	return nil
}

func AddToBasicPlayer(details dataFormat.BasicNumberOf) error {
	if !IsConnected {
		go Connect()
		return ErrDisconnected
	}

	resp, err := r.Table("basic").
		GetAllByIndex("ip", details.InternalPlayerId).Filter(
		map[string]string{
			"p": details.TimePeriod,
			"q": details.Queue,
			"c": details.Champion,
		}).Update(
		map[string]interface{}{
			"w":  r.Row.Field("w").Add(details.Wins),
			"l":  r.Row.Field("l").Add(details.Losses),
			"t":  r.Row.Field("t").Add(details.TimePlayed),
			"k":  r.Row.Field("k").Add(details.Kills),
			"a":  r.Row.Field("a").Add(details.Assists),
			"d":  r.Row.Field("d").Add(details.Deaths),
			"g":  r.Row.Field("g").Add(details.GoldEarned),
			"m":  r.Row.Field("m").Add(details.MinionsKilled),
			"n":  r.Row.Field("n").Add(details.MonstersKilled),
			"wp": r.Row.Field("wp").Add(details.WardsPlaced),
		}).RunWrite(activeSession)

	if isDisconnected(err) {
		return ErrDisconnected
	} else if err != nil {
		return err
	}

	if resp.Replaced+resp.Unchanged == 0 {
		// Doesn't exist, insert new
		resp, err := r.Table("basic").Insert(details).RunWrite(activeSession)
		if isDisconnected(err) {
			return ErrDisconnected
		} else if err != nil {
			return err
		} else if resp.Inserted == 0 {
			return ErrInsertDiscrepancy
		}
	}

	return nil
}

func DeletePlayer(player dataFormat.Player) error {
	if !IsConnected {
		go Connect()
		return ErrDisconnected
	}

	_, err := r.Table("players").GetAllByIndex("pi", player.SummonerId).
		Filter(map[string]string{"r": player.Region}).Delete().
		RunWrite(activeSession)
	if err != nil {
		return err
	}

	LastPlayerUpdate = time.Now()

	return nil
}

func CreatePlayer(player dataFormat.Player) (string, error) {
	if !IsConnected {
		go Connect()
		return "", ErrDisconnected
	}

	// Check if player exists already
	c, err := r.Table("players").GetAllByIndex("pi", player.SummonerId).
		Filter(map[string]string{"r": player.Region}).Field("id").
		Run(activeSession)

	if isDisconnected(err) {
		return "", ErrDisconnected
	} else if err != nil {
		return "", err
	}

	internalId := ""
	err = c.One(&internalId)
	c.Close()

	LastPlayerUpdate = time.Now()

	if err == nil {
		// Update new summoner name
		revel.WARN.Println("database: updating player summoner name for " +
			internalId)
		_, err := r.Table("players").Get(internalId).Update(
			map[string]string{
				"sn": player.SummonerName,
				"nn": player.NormalizedName,
			}).RunWrite(activeSession)
		if isDisconnected(err) {
			return "", ErrDisconnected
		} else if err != nil {
			return "", err
		}

		return internalId, nil
	} else if isDisconnected(err) {
		return "", ErrDisconnected
	} else if err != r.ErrEmptyResult {
		return "", err
	}

	changes, err := r.Table("players").Insert(player).RunWrite(activeSession)
	if isDisconnected(err) {
		return "", ErrDisconnected
	} else if err != nil {
		return "", err
	}

	if len(changes.GeneratedKeys) == 0 {
		return "", errors.New("database: missing generated keys")
	}

	return changes.GeneratedKeys[0], nil
}

func GetUpdatePlayers() ([]dataFormat.Player, error) {
	if !IsConnected {
		go Connect()
		return []dataFormat.Player{}, ErrDisconnected
	}

	c, err := r.Table("players").Between(r.MinVal, time.Now(),
		r.BetweenOpts{Index: "nu"}).
		OrderBy(r.OrderByOpts{Index: "nu"}).Pluck("p", "pi", "r", "id").
		Limit(1000).Run(activeSession)

	if isDisconnected(err) {
		return []dataFormat.Player{}, ErrDisconnected
	} else if err != nil {
		return []dataFormat.Player{}, err
	}

	players := []dataFormat.Player{}
	err = c.All(&players)

	if isDisconnected(err) {
		return []dataFormat.Player{}, ErrDisconnected
	} else if err != nil {
		return []dataFormat.Player{}, err
	}

	return players, nil
}

func GetLongUpdatePlayers() ([]dataFormat.Player, error) {
	if !IsConnected {
		go Connect()
		return []dataFormat.Player{}, ErrDisconnected
	}

	c, err := r.Table("players").Between(r.MinVal, time.Now(),
		r.BetweenOpts{Index: "nl"}).
		OrderBy(r.OrderByOpts{Index: "nl"}).Pluck("p", "pi", "r", "id").
		Limit(1000).Run(activeSession)

	if isDisconnected(err) {
		return []dataFormat.Player{}, ErrDisconnected
	} else if err != nil {
		return []dataFormat.Player{}, err
	}

	players := []dataFormat.Player{}
	err = c.All(&players)

	if isDisconnected(err) {
		return []dataFormat.Player{}, ErrDisconnected
	} else if err != nil {
		return []dataFormat.Player{}, err
	}

	return players, nil
}

func UpdatePlayerInformation(player dataFormat.Player, data interface{}) error {
	if !IsConnected {
		go Connect()
		return ErrDisconnected
	}

	_, err := r.Table("players").GetAllByIndex("id", player.InternalId).
		Update(data).RunWrite(activeSession)
	if isDisconnected(err) {
		return ErrDisconnected
	} else if err != nil {
		return err
	}

	LastPlayerUpdate = time.Now()

	return nil
}
<commit_msg>Fixed bug not allowing new users<commit_after>// LoL Cruncher - A Historical League of Legends Statistics Tracker
// Copyright (C) 2015  Jason Chu (1lann) 1lanncontact@gmail.com

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

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

// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package database

import (
	"cruncher/app/models/dataFormat"
	"errors"
	// "fmt"
	r "github.com/dancannon/gorethink"
	"github.com/revel/revel"
	"strings"
	"time"
)

var LastPlayerUpdate = time.Now()

var (
	ErrDisconnected      = errors.New("database: disconnected")
	ErrNoResults         = errors.New("database: no results")
	ErrInsertDiscrepancy = errors.New("database: insert discrepancy")
)

func GetBrowserPlayers() ([]dataFormat.Player, error) {
	if !IsConnected {
		go Connect()
		return []dataFormat.Player{}, ErrDisconnected
	}

	c, err := r.Table("players").OrderBy(r.OrderByOpts{"nn"}).
		Pluck("sn", "r").Run(activeSession)
	if isDisconnected(err) {
		return []dataFormat.Player{}, ErrDisconnected
	} else if err != nil {
		return []dataFormat.Player{}, err
	}

	results := []dataFormat.Player{}
	err = c.All(&results)
	c.Close()
	if isDisconnected(err) {
		return []dataFormat.Player{}, ErrDisconnected
	} else if err == r.ErrEmptyResult {
		return []dataFormat.Player{}, ErrNoResults
	} else if err != nil {
		return []dataFormat.Player{}, err
	}

	return results, nil
}

func GetSummonerData(name string, region string) (dataFormat.PlayerData,
	error) {
	if !IsConnected {
		go Connect()
		return dataFormat.PlayerData{}, ErrDisconnected
	}

	name = dataFormat.NormalizeName(name)
	c, err := r.Table("players").
		GetAllByIndex("nr", []string{name, region}).AtIndex(0).
		Merge(func(row r.Term) interface{} {
		return map[string]interface{}{
			"detailed": r.DB("cruncher").Table("detailed").
				GetAllByIndex("ip", row.Field("id")).CoerceTo("array"),
			"basic": r.DB("cruncher").Table("basic").
				GetAllByIndex("ip", row.Field("id")).CoerceTo("array"),
		}
	}).Run(activeSession)

	if isDisconnected(err) {
		return dataFormat.PlayerData{}, ErrDisconnected
	} else if err != nil {
		return dataFormat.PlayerData{}, err
	}

	playerData := dataFormat.PlayerData{}

	err = c.One(&playerData)
	c.Close()
	if isDisconnected(err) {
		return dataFormat.PlayerData{}, ErrDisconnected
	} else if err == r.ErrEmptyResult {
		return dataFormat.PlayerData{}, ErrNoResults
	} else if strings.Contains(err.Error(), "gorethink: Index out of bounds: 0 in:") {
		return dataFormat.PlayerData{}, ErrNoResults
	} else if err != nil {
		return dataFormat.PlayerData{}, err
	}

	return playerData, nil
}

func AddToDetailedPlayer(details dataFormat.DetailedNumberOf) error {
	if !IsConnected {
		go Connect()
		return ErrDisconnected
	}

	resp, err := r.Table("detailed").
		GetAllByIndex("ip", details.InternalPlayerId).Filter(
		map[string]string{
			"p": details.TimePeriod,
			"q": details.Queue,
		}).Update(
		map[string]interface{}{
			"w":  r.Row.Field("w").Add(details.Wins),
			"l":  r.Row.Field("l").Add(details.Losses),
			"t":  r.Row.Field("t").Add(details.TimePlayed),
			"k":  r.Row.Field("k").Add(details.Kills),
			"a":  r.Row.Field("a").Add(details.Assists),
			"d":  r.Row.Field("d").Add(details.Deaths),
			"dk": r.Row.Field("dk").Add(details.DoubleKills),
			"tk": r.Row.Field("tk").Add(details.TripleKills),
			"qk": r.Row.Field("qk").Add(details.QuadraKills),
			"pk": r.Row.Field("pk").Add(details.PentaKills),
			"g":  r.Row.Field("g").Add(details.GoldEarned),
			"m":  r.Row.Field("m").Add(details.MinionsKilled),
			"n":  r.Row.Field("n").Add(details.MonstersKilled),
			"wp": r.Row.Field("wp").Add(details.WardsPlaced),
			"wk": r.Row.Field("wk").Add(details.WardsKilled),
			"b": map[string]interface{}{
				"w": r.Row.Field("b").Field("w").Add(details.Blue.Wins),
				"l": r.Row.Field("b").Field("l").Add(details.Blue.Losses),
			}, "r": map[string]interface{}{
				"w": r.Row.Field("b").Field("w").Add(details.Red.Wins),
				"l": r.Row.Field("b").Field("l").Add(details.Red.Losses),
			},
		}).RunWrite(activeSession)

	if isDisconnected(err) {
		return ErrDisconnected
	} else if err != nil {
		return err
	}

	if resp.Replaced+resp.Unchanged == 0 {
		// Doesn't exist, insert new
		resp, err := r.Table("detailed").Insert(details).RunWrite(activeSession)
		if isDisconnected(err) {
			return ErrDisconnected
		} else if err != nil {
			return err
		} else if resp.Inserted == 0 {
			return ErrInsertDiscrepancy
		}
	}

	return nil
}

func AddToBasicPlayer(details dataFormat.BasicNumberOf) error {
	if !IsConnected {
		go Connect()
		return ErrDisconnected
	}

	resp, err := r.Table("basic").
		GetAllByIndex("ip", details.InternalPlayerId).Filter(
		map[string]string{
			"p": details.TimePeriod,
			"q": details.Queue,
			"c": details.Champion,
		}).Update(
		map[string]interface{}{
			"w":  r.Row.Field("w").Add(details.Wins),
			"l":  r.Row.Field("l").Add(details.Losses),
			"t":  r.Row.Field("t").Add(details.TimePlayed),
			"k":  r.Row.Field("k").Add(details.Kills),
			"a":  r.Row.Field("a").Add(details.Assists),
			"d":  r.Row.Field("d").Add(details.Deaths),
			"g":  r.Row.Field("g").Add(details.GoldEarned),
			"m":  r.Row.Field("m").Add(details.MinionsKilled),
			"n":  r.Row.Field("n").Add(details.MonstersKilled),
			"wp": r.Row.Field("wp").Add(details.WardsPlaced),
		}).RunWrite(activeSession)

	if isDisconnected(err) {
		return ErrDisconnected
	} else if err != nil {
		return err
	}

	if resp.Replaced+resp.Unchanged == 0 {
		// Doesn't exist, insert new
		resp, err := r.Table("basic").Insert(details).RunWrite(activeSession)
		if isDisconnected(err) {
			return ErrDisconnected
		} else if err != nil {
			return err
		} else if resp.Inserted == 0 {
			return ErrInsertDiscrepancy
		}
	}

	return nil
}

func DeletePlayer(player dataFormat.Player) error {
	if !IsConnected {
		go Connect()
		return ErrDisconnected
	}

	_, err := r.Table("players").GetAllByIndex("pi", player.SummonerId).
		Filter(map[string]string{"r": player.Region}).Delete().
		RunWrite(activeSession)
	if err != nil {
		return err
	}

	LastPlayerUpdate = time.Now()

	return nil
}

func CreatePlayer(player dataFormat.Player) (string, error) {
	if !IsConnected {
		go Connect()
		return "", ErrDisconnected
	}

	// Check if player exists already
	c, err := r.Table("players").GetAllByIndex("pi", player.SummonerId).
		Filter(map[string]string{"r": player.Region}).Field("id").
		Run(activeSession)

	if isDisconnected(err) {
		return "", ErrDisconnected
	} else if err != nil {
		return "", err
	}

	internalId := ""
	err = c.One(&internalId)
	c.Close()

	LastPlayerUpdate = time.Now()

	if err == nil {
		// Update new summoner name
		revel.WARN.Println("database: updating player summoner name for " +
			internalId)
		_, err := r.Table("players").Get(internalId).Update(
			map[string]string{
				"sn": player.SummonerName,
				"nn": player.NormalizedName,
			}).RunWrite(activeSession)
		if isDisconnected(err) {
			return "", ErrDisconnected
		} else if err != nil {
			return "", err
		}

		return internalId, nil
	} else if isDisconnected(err) {
		return "", ErrDisconnected
	} else if err != r.ErrEmptyResult {
		return "", err
	}

	changes, err := r.Table("players").Insert(player).RunWrite(activeSession)
	if isDisconnected(err) {
		return "", ErrDisconnected
	} else if err != nil {
		return "", err
	}

	if len(changes.GeneratedKeys) == 0 {
		return "", errors.New("database: missing generated keys")
	}

	return changes.GeneratedKeys[0], nil
}

func GetUpdatePlayers() ([]dataFormat.Player, error) {
	if !IsConnected {
		go Connect()
		return []dataFormat.Player{}, ErrDisconnected
	}

	c, err := r.Table("players").Between(r.MinVal, time.Now(),
		r.BetweenOpts{Index: "nu"}).
		OrderBy(r.OrderByOpts{Index: "nu"}).Pluck("p", "pi", "r", "id").
		Limit(1000).Run(activeSession)

	if isDisconnected(err) {
		return []dataFormat.Player{}, ErrDisconnected
	} else if err != nil {
		return []dataFormat.Player{}, err
	}

	players := []dataFormat.Player{}
	err = c.All(&players)

	if isDisconnected(err) {
		return []dataFormat.Player{}, ErrDisconnected
	} else if err != nil {
		return []dataFormat.Player{}, err
	}

	return players, nil
}

func GetLongUpdatePlayers() ([]dataFormat.Player, error) {
	if !IsConnected {
		go Connect()
		return []dataFormat.Player{}, ErrDisconnected
	}

	c, err := r.Table("players").Between(r.MinVal, time.Now(),
		r.BetweenOpts{Index: "nl"}).
		OrderBy(r.OrderByOpts{Index: "nl"}).Pluck("p", "pi", "r", "id").
		Limit(1000).Run(activeSession)

	if isDisconnected(err) {
		return []dataFormat.Player{}, ErrDisconnected
	} else if err != nil {
		return []dataFormat.Player{}, err
	}

	players := []dataFormat.Player{}
	err = c.All(&players)

	if isDisconnected(err) {
		return []dataFormat.Player{}, ErrDisconnected
	} else if err != nil {
		return []dataFormat.Player{}, err
	}

	return players, nil
}

func UpdatePlayerInformation(player dataFormat.Player, data interface{}) error {
	if !IsConnected {
		go Connect()
		return ErrDisconnected
	}

	_, err := r.Table("players").GetAllByIndex("id", player.InternalId).
		Update(data).RunWrite(activeSession)
	if isDisconnected(err) {
		return ErrDisconnected
	} else if err != nil {
		return err
	}

	LastPlayerUpdate = time.Now()

	return nil
}
<|endoftext|>


<commit_before>package syssetup

import (
	"io/ioutil"
	"os"
	"os/exec"

	"github.com/sirupsen/logrus"
)

func loadKernelModule(moduleName string) {
	if _, err := os.Stat("/sys/module/" + moduleName); err == nil {
		logrus.Infof("module %s was already loaded", moduleName)
		return
	}

	if err := exec.Command("modprobe", moduleName).Run(); err != nil {
		logrus.Warnf("failed to start %s module", moduleName)
	}
}

func enableSystemControl(file string) {
	if err := ioutil.WriteFile(file, []byte("1"), 0640); err != nil {
		logrus.Warnf("failed to write value 1 at %s: %v", file, err)
	}
}

func Configure() {
	loadKernelModule("overlay")
	loadKernelModule("nf_conntrack")
	loadKernelModule("br_netfilter")

	enableSystemControl("/proc/sys/net/ipv4/ip_forward")
	enableSystemControl("/proc/sys/net/ipv6/conf/all/forwarding")
	enableSystemControl("/proc/sys/net/bridge/bridge-nf-call-iptables")
	enableSystemControl("/proc/sys/net/bridge/bridge-nf-call-ip6tables")
}
<commit_msg>Default device net config enables ip forwarding<commit_after>package syssetup

import (
	"io/ioutil"
	"os"
	"os/exec"

	"github.com/sirupsen/logrus"
)

func loadKernelModule(moduleName string) {
	if _, err := os.Stat("/sys/module/" + moduleName); err == nil {
		logrus.Infof("module %s was already loaded", moduleName)
		return
	}

	if err := exec.Command("modprobe", moduleName).Run(); err != nil {
		logrus.Warnf("failed to start %s module", moduleName)
	}
}

func enableSystemControl(file string) {
	if err := ioutil.WriteFile(file, []byte("1"), 0640); err != nil {
		logrus.Warnf("failed to write value 1 at %s: %v", file, err)
	}
}

func Configure() {
	loadKernelModule("overlay")
	loadKernelModule("nf_conntrack")
	loadKernelModule("br_netfilter")

	// Kernel is inconsistent about how devconf is configured for
	// new network namespaces between ipv4 and ipv6. Make sure to
	// enable forwarding on all and default for both ipv4 and ipv8.
	enableSystemControl("/proc/sys/net/ipv4/conf/all/forwarding")
	enableSystemControl("/proc/sys/net/ipv4/conf/default/forwarding")
	enableSystemControl("/proc/sys/net/ipv6/conf/all/forwarding")
	enableSystemControl("/proc/sys/net/ipv6/conf/default/forwarding")
	enableSystemControl("/proc/sys/net/bridge/bridge-nf-call-iptables")
	enableSystemControl("/proc/sys/net/bridge/bridge-nf-call-ip6tables")
}
<|endoftext|>


<commit_before>package builder

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	docker "github.com/fsouza/go-dockerclient"

	s2igit "github.com/openshift/source-to-image/pkg/scm/git"

	"github.com/openshift/origin/pkg/build/api"
	"github.com/openshift/origin/pkg/build/builder/cmd/dockercfg"
	"github.com/openshift/origin/pkg/generate/git"
	"github.com/openshift/source-to-image/pkg/tar"
)

const (
	// initialURLCheckTimeout is the initial timeout used to check the
	// source URL.  If fetching the URL exceeds the timeout, then a longer
	// timeout will be tried until the fetch either succeeds or the build
	// itself times out.
	initialURLCheckTimeout = 16 * time.Second

	// timeoutIncrementFactor is the factor to use when increasing
	// the timeout after each unsuccessful try
	timeoutIncrementFactor = 4
)

type gitAuthError string
type gitNotFoundError string

func (e gitAuthError) Error() string {
	return fmt.Sprintf("failed to fetch requested repository %q with provided credentials", string(e))
}

func (e gitNotFoundError) Error() string {
	return fmt.Sprintf("requested repository %q not found", string(e))
}

// fetchSource retrieves the inputs defined by the build source into the
// provided directory, or returns an error if retrieval is not possible.
func fetchSource(dockerClient DockerClient, dir string, build *api.Build, urlTimeout time.Duration, in io.Reader, gitClient GitClient) (*git.SourceInfo, error) {
	hasGitSource := false

	// expect to receive input from STDIN
	if err := extractInputBinary(in, build.Spec.Source.Binary, dir); err != nil {
		return nil, err
	}

	// may retrieve source from Git
	hasGitSource, err := extractGitSource(gitClient, build.Spec.Source.Git, build.Spec.Revision, dir, urlTimeout)
	if err != nil {
		return nil, err
	}

	var sourceInfo *git.SourceInfo
	if hasGitSource {
		var errs []error
		sourceInfo, errs = gitClient.GetInfo(dir)
		if len(errs) > 0 {
			for _, e := range errs {
				glog.V(0).Infof("error: Unable to retrieve Git info: %v", e)
			}
		}
	}

	forcePull := false
	switch {
	case build.Spec.Strategy.SourceStrategy != nil:
		forcePull = build.Spec.Strategy.SourceStrategy.ForcePull
	case build.Spec.Strategy.DockerStrategy != nil:
		forcePull = build.Spec.Strategy.DockerStrategy.ForcePull
	case build.Spec.Strategy.CustomStrategy != nil:
		forcePull = build.Spec.Strategy.CustomStrategy.ForcePull
	}
	// extract source from an Image if specified
	for i, image := range build.Spec.Source.Images {
		imageSecretIndex := i
		if image.PullSecret == nil {
			imageSecretIndex = -1
		}
		err := extractSourceFromImage(dockerClient, image.From.Name, dir, imageSecretIndex, image.Paths, forcePull)
		if err != nil {
			return nil, err
		}
	}

	// a Dockerfile has been specified, create or overwrite into the destination
	if dockerfileSource := build.Spec.Source.Dockerfile; dockerfileSource != nil {
		baseDir := dir
		// if a context dir has been defined and we cloned source, overwrite the destination
		if hasGitSource && len(build.Spec.Source.ContextDir) != 0 {
			baseDir = filepath.Join(baseDir, build.Spec.Source.ContextDir)
		}
		return sourceInfo, ioutil.WriteFile(filepath.Join(baseDir, "Dockerfile"), []byte(*dockerfileSource), 0660)
	}

	return sourceInfo, nil
}

// checkRemoteGit validates the specified Git URL. It returns GitNotFoundError
// when the remote repository not found and GitAuthenticationError when the
// remote repository failed to authenticate.
// Since this is calling the 'git' binary, the proxy settings should be
// available for this command.
func checkRemoteGit(gitClient GitClient, url string, initialTimeout time.Duration) error {

	var (
		out    string
		errOut string
		err    error
	)

	timeout := initialTimeout
	for {
		glog.V(4).Infof("git ls-remote --heads %s", url)
		out, errOut, err = gitClient.TimedListRemote(timeout, url, "--heads")
		if len(out) != 0 {
			glog.V(4).Infof(out)
		}
		if len(errOut) != 0 {
			glog.V(4).Infof(errOut)
		}
		if err != nil {
			if _, ok := err.(*git.TimeoutError); ok {
				timeout = timeout * timeoutIncrementFactor
				glog.Infof("WARNING: timed out waiting for git server, will wait %s", timeout)
				continue
			}
		}
		break
	}
	if err != nil {
		combinedOut := out + errOut
		switch {
		case strings.Contains(combinedOut, "Authentication failed"):
			return gitAuthError(url)
		case strings.Contains(combinedOut, "not found"):
			return gitNotFoundError(url)
		}
	}
	return err
}

// checkSourceURI performs a check on the URI associated with the build
// to make sure that it is valid.
func checkSourceURI(gitClient GitClient, rawurl string, timeout time.Duration) error {
	ok, err := s2igit.New().ValidCloneSpec(rawurl)
	if err != nil {
		return fmt.Errorf("Invalid git source url %q: %v", rawurl, err)
	}
	if !ok {
		return fmt.Errorf("Invalid git source url: %s", rawurl)
	}
	return checkRemoteGit(gitClient, rawurl, timeout)
}

// extractInputBinary processes the provided input stream as directed by BinaryBuildSource
// into dir.
func extractInputBinary(in io.Reader, source *api.BinaryBuildSource, dir string) error {
	if source == nil {
		return nil
	}

	var path string
	if len(source.AsFile) > 0 {
		glog.V(0).Infof("Receiving source from STDIN as file %s", source.AsFile)
		path = filepath.Join(dir, source.AsFile)

		f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0664)
		if err != nil {
			return err
		}
		defer f.Close()
		n, err := io.Copy(f, os.Stdin)
		if err != nil {
			return err
		}
		glog.V(4).Infof("Received %d bytes into %s", n, path)
		return nil
	}

	glog.V(0).Infof("Receiving source from STDIN as archive ...")

	// use bsdtar to process the incoming archive and convert it to a tar stream (since bsdtar autodetects and handles various archive formats)
	// use gnu tar to extract that tar stream to work around the bsdtar (libarchive) bug https://github.com/libarchive/libarchive/issues/746
	cmd := exec.Command("sh", "-o", "pipefail", "-c", `bsdtar -cf - @- | tar xf - -m -C "$0"`, dir)
	cmd.Stdin = in
	out, err := cmd.CombinedOutput()
	if err != nil {
		glog.V(0).Infof("Extracting...\n%s", string(out))
		return fmt.Errorf("unable to extract binary build input, must be a zip, tar, or gzipped tar, or specified as a file: %v", err)
	}
	return nil
}

func extractGitSource(gitClient GitClient, gitSource *api.GitBuildSource, revision *api.SourceRevision, dir string, timeout time.Duration) (bool, error) {
	if gitSource == nil {
		return false, nil
	}

	glog.V(0).Infof("Cloning %q ...", gitSource.URI)

	// Check source URI, trying to connect to the server only if not using a proxy.
	if err := checkSourceURI(gitClient, gitSource.URI, timeout); err != nil {
		return true, err
	}

	// check if we specify a commit, ref, or branch to check out
	usingRef := len(gitSource.Ref) != 0 || (revision != nil && revision.Git != nil && len(revision.Git.Commit) != 0)

	// Only use the quiet flag if Verbosity is not 5 or greater
	quiet := !glog.Is(5)
	if err := gitClient.CloneWithOptions(dir, gitSource.URI, git.CloneOptions{Recursive: !usingRef, Quiet: quiet, Shallow: !usingRef}); err != nil {
		return true, err
	}

	// if we specify a commit, ref, or branch to checkout, do so, and update submodules
	if usingRef {
		commit := gitSource.Ref

		if revision != nil && revision.Git != nil && revision.Git.Commit != "" {
			commit = revision.Git.Commit
		}

		if err := gitClient.Checkout(dir, commit); err != nil {
			return true, err
		}

		// Recursively update --init
		if err := gitClient.SubmoduleUpdate(dir, true, true); err != nil {
			return true, err
		}
	}

	if glog.Is(0) {
		if information, gitErr := gitClient.GetInfo(dir); gitErr == nil {
			glog.Infof("\tCommit:\t%s (%s)\n", information.CommitID, information.Message)
			glog.Infof("\tAuthor:\t%s <%s>\n", information.AuthorName, information.AuthorEmail)
			glog.Infof("\tDate:\t%s\n", information.Date)
		}
	}

	return true, nil
}

func copyImageSource(dockerClient DockerClient, containerID, sourceDir, destDir string, tarHelper tar.Tar) error {
	// Setup destination directory
	fi, err := os.Stat(destDir)
	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}
		glog.V(4).Infof("Creating image destination directory: %s", destDir)
		err := os.MkdirAll(destDir, 0644)
		if err != nil {
			return err
		}
	} else {
		if !fi.IsDir() {
			return fmt.Errorf("destination %s must be a directory", destDir)
		}
	}

	tempFile, err := ioutil.TempFile("", "imgsrc")
	if err != nil {
		return err
	}
	glog.V(4).Infof("Downloading source from path %s in container %s to temporary archive %s", sourceDir, containerID, tempFile.Name())
	err = dockerClient.DownloadFromContainer(containerID, docker.DownloadFromContainerOptions{
		OutputStream: tempFile,
		Path:         sourceDir,
	})
	if err != nil {
		tempFile.Close()
		return err
	}
	if err := tempFile.Close(); err != nil {
		return err
	}

	// Extract the created tar file to the destination directory
	file, err := os.Open(tempFile.Name())
	if err != nil {
		return err
	}
	defer file.Close()

	glog.V(4).Infof("Extracting temporary tar %s to directory %s", tempFile.Name(), destDir)
	var tarOutput io.Writer
	if glog.Is(4) {
		tarOutput = os.Stdout
	}
	return tarHelper.ExtractTarStreamWithLogging(destDir, file, tarOutput)
}

func extractSourceFromImage(dockerClient DockerClient, image, buildDir string, imageSecretIndex int, paths []api.ImageSourcePath, forcePull bool) error {
	glog.V(4).Infof("Extracting image source from %s", image)

	dockerAuth := docker.AuthConfiguration{}
	if imageSecretIndex != -1 {
		pullSecret := os.Getenv(fmt.Sprintf("%s%d", dockercfg.PullSourceAuthType, imageSecretIndex))
		if len(pullSecret) > 0 {
			authPresent := false
			dockerAuth, authPresent = dockercfg.NewHelper().GetDockerAuth(image, fmt.Sprintf("%s%d", dockercfg.PullSourceAuthType, imageSecretIndex))
			if authPresent {
				glog.V(5).Infof("Registry server Address: %s", dockerAuth.ServerAddress)
				glog.V(5).Infof("Registry server User Name: %s", dockerAuth.Username)
				glog.V(5).Infof("Registry server Email: %s", dockerAuth.Email)
				passwordPresent := "<<empty>>"
				if len(dockerAuth.Password) > 0 {
					passwordPresent = "<<non-empty>>"
				}
				glog.V(5).Infof("Registry server Password: %s", passwordPresent)
			}
		}
	}

	exists := true
	if !forcePull {
		_, err := dockerClient.InspectImage(image)
		if err != nil && err == docker.ErrNoSuchImage {
			exists = false
		} else if err != nil {
			return err
		}
	}

	if !exists || forcePull {
		glog.V(0).Infof("Pulling image %q ...", image)
		if err := dockerClient.PullImage(docker.PullImageOptions{Repository: image}, dockerAuth); err != nil {
			return fmt.Errorf("error pulling image %v: %v", image, err)
		}
	}

	containerConfig := &docker.Config{Image: image}
	if inspect, err := dockerClient.InspectImage(image); err != nil {
		return err
	} else {
		// In case the Docker image does not specify the entrypoint
		if len(inspect.Config.Entrypoint) == 0 && len(inspect.Config.Cmd) == 0 {
			containerConfig.Entrypoint = []string{"/fake-entrypoint"}
		}
	}

	// Create container to copy from
	container, err := dockerClient.CreateContainer(docker.CreateContainerOptions{Config: containerConfig})
	if err != nil {
		return fmt.Errorf("error creating source image container: %v", err)
	}
	defer dockerClient.RemoveContainer(docker.RemoveContainerOptions{ID: container.ID})

	tarHelper := tar.New()
	tarHelper.SetExclusionPattern(nil)

	for _, path := range paths {
		glog.V(4).Infof("Extracting path %s from container %s to %s", path.SourcePath, container.ID, path.DestinationDir)
		err := copyImageSource(dockerClient, container.ID, path.SourcePath, filepath.Join(buildDir, path.DestinationDir), tarHelper)
		if err != nil {
			return fmt.Errorf("error copying source path %s to %s: %v", path.SourcePath, path.DestinationDir, err)
		}
	}

	return nil
}
<commit_msg>Now check for length on error array<commit_after>package builder

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	docker "github.com/fsouza/go-dockerclient"

	s2igit "github.com/openshift/source-to-image/pkg/scm/git"

	"github.com/openshift/origin/pkg/build/api"
	"github.com/openshift/origin/pkg/build/builder/cmd/dockercfg"
	"github.com/openshift/origin/pkg/generate/git"
	"github.com/openshift/source-to-image/pkg/tar"
)

const (
	// initialURLCheckTimeout is the initial timeout used to check the
	// source URL.  If fetching the URL exceeds the timeout, then a longer
	// timeout will be tried until the fetch either succeeds or the build
	// itself times out.
	initialURLCheckTimeout = 16 * time.Second

	// timeoutIncrementFactor is the factor to use when increasing
	// the timeout after each unsuccessful try
	timeoutIncrementFactor = 4
)

type gitAuthError string
type gitNotFoundError string

func (e gitAuthError) Error() string {
	return fmt.Sprintf("failed to fetch requested repository %q with provided credentials", string(e))
}

func (e gitNotFoundError) Error() string {
	return fmt.Sprintf("requested repository %q not found", string(e))
}

// fetchSource retrieves the inputs defined by the build source into the
// provided directory, or returns an error if retrieval is not possible.
func fetchSource(dockerClient DockerClient, dir string, build *api.Build, urlTimeout time.Duration, in io.Reader, gitClient GitClient) (*git.SourceInfo, error) {
	hasGitSource := false

	// expect to receive input from STDIN
	if err := extractInputBinary(in, build.Spec.Source.Binary, dir); err != nil {
		return nil, err
	}

	// may retrieve source from Git
	hasGitSource, err := extractGitSource(gitClient, build.Spec.Source.Git, build.Spec.Revision, dir, urlTimeout)
	if err != nil {
		return nil, err
	}

	var sourceInfo *git.SourceInfo
	if hasGitSource {
		var errs []error
		sourceInfo, errs = gitClient.GetInfo(dir)
		if len(errs) > 0 {
			for _, e := range errs {
				glog.V(0).Infof("error: Unable to retrieve Git info: %v", e)
			}
		}
	}

	forcePull := false
	switch {
	case build.Spec.Strategy.SourceStrategy != nil:
		forcePull = build.Spec.Strategy.SourceStrategy.ForcePull
	case build.Spec.Strategy.DockerStrategy != nil:
		forcePull = build.Spec.Strategy.DockerStrategy.ForcePull
	case build.Spec.Strategy.CustomStrategy != nil:
		forcePull = build.Spec.Strategy.CustomStrategy.ForcePull
	}
	// extract source from an Image if specified
	for i, image := range build.Spec.Source.Images {
		imageSecretIndex := i
		if image.PullSecret == nil {
			imageSecretIndex = -1
		}
		err := extractSourceFromImage(dockerClient, image.From.Name, dir, imageSecretIndex, image.Paths, forcePull)
		if err != nil {
			return nil, err
		}
	}

	// a Dockerfile has been specified, create or overwrite into the destination
	if dockerfileSource := build.Spec.Source.Dockerfile; dockerfileSource != nil {
		baseDir := dir
		// if a context dir has been defined and we cloned source, overwrite the destination
		if hasGitSource && len(build.Spec.Source.ContextDir) != 0 {
			baseDir = filepath.Join(baseDir, build.Spec.Source.ContextDir)
		}
		return sourceInfo, ioutil.WriteFile(filepath.Join(baseDir, "Dockerfile"), []byte(*dockerfileSource), 0660)
	}

	return sourceInfo, nil
}

// checkRemoteGit validates the specified Git URL. It returns GitNotFoundError
// when the remote repository not found and GitAuthenticationError when the
// remote repository failed to authenticate.
// Since this is calling the 'git' binary, the proxy settings should be
// available for this command.
func checkRemoteGit(gitClient GitClient, url string, initialTimeout time.Duration) error {

	var (
		out    string
		errOut string
		err    error
	)

	timeout := initialTimeout
	for {
		glog.V(4).Infof("git ls-remote --heads %s", url)
		out, errOut, err = gitClient.TimedListRemote(timeout, url, "--heads")
		if len(out) != 0 {
			glog.V(4).Infof(out)
		}
		if len(errOut) != 0 {
			glog.V(4).Infof(errOut)
		}
		if err != nil {
			if _, ok := err.(*git.TimeoutError); ok {
				timeout = timeout * timeoutIncrementFactor
				glog.Infof("WARNING: timed out waiting for git server, will wait %s", timeout)
				continue
			}
		}
		break
	}
	if err != nil {
		combinedOut := out + errOut
		switch {
		case strings.Contains(combinedOut, "Authentication failed"):
			return gitAuthError(url)
		case strings.Contains(combinedOut, "not found"):
			return gitNotFoundError(url)
		}
	}
	return err
}

// checkSourceURI performs a check on the URI associated with the build
// to make sure that it is valid.
func checkSourceURI(gitClient GitClient, rawurl string, timeout time.Duration) error {
	ok, err := s2igit.New().ValidCloneSpec(rawurl)
	if err != nil {
		return fmt.Errorf("Invalid git source url %q: %v", rawurl, err)
	}
	if !ok {
		return fmt.Errorf("Invalid git source url: %s", rawurl)
	}
	return checkRemoteGit(gitClient, rawurl, timeout)
}

// extractInputBinary processes the provided input stream as directed by BinaryBuildSource
// into dir.
func extractInputBinary(in io.Reader, source *api.BinaryBuildSource, dir string) error {
	if source == nil {
		return nil
	}

	var path string
	if len(source.AsFile) > 0 {
		glog.V(0).Infof("Receiving source from STDIN as file %s", source.AsFile)
		path = filepath.Join(dir, source.AsFile)

		f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0664)
		if err != nil {
			return err
		}
		defer f.Close()
		n, err := io.Copy(f, os.Stdin)
		if err != nil {
			return err
		}
		glog.V(4).Infof("Received %d bytes into %s", n, path)
		return nil
	}

	glog.V(0).Infof("Receiving source from STDIN as archive ...")

	// use bsdtar to process the incoming archive and convert it to a tar stream (since bsdtar autodetects and handles various archive formats)
	// use gnu tar to extract that tar stream to work around the bsdtar (libarchive) bug https://github.com/libarchive/libarchive/issues/746
	cmd := exec.Command("sh", "-o", "pipefail", "-c", `bsdtar -cf - @- | tar xf - -m -C "$0"`, dir)
	cmd.Stdin = in
	out, err := cmd.CombinedOutput()
	if err != nil {
		glog.V(0).Infof("Extracting...\n%s", string(out))
		return fmt.Errorf("unable to extract binary build input, must be a zip, tar, or gzipped tar, or specified as a file: %v", err)
	}
	return nil
}

func extractGitSource(gitClient GitClient, gitSource *api.GitBuildSource, revision *api.SourceRevision, dir string, timeout time.Duration) (bool, error) {
	if gitSource == nil {
		return false, nil
	}

	glog.V(0).Infof("Cloning %q ...", gitSource.URI)

	// Check source URI, trying to connect to the server only if not using a proxy.
	if err := checkSourceURI(gitClient, gitSource.URI, timeout); err != nil {
		return true, err
	}

	// check if we specify a commit, ref, or branch to check out
	usingRef := len(gitSource.Ref) != 0 || (revision != nil && revision.Git != nil && len(revision.Git.Commit) != 0)

	// Only use the quiet flag if Verbosity is not 5 or greater
	quiet := !glog.Is(5)
	if err := gitClient.CloneWithOptions(dir, gitSource.URI, git.CloneOptions{Recursive: !usingRef, Quiet: quiet, Shallow: !usingRef}); err != nil {
		return true, err
	}

	// if we specify a commit, ref, or branch to checkout, do so, and update submodules
	if usingRef {
		commit := gitSource.Ref

		if revision != nil && revision.Git != nil && revision.Git.Commit != "" {
			commit = revision.Git.Commit
		}

		if err := gitClient.Checkout(dir, commit); err != nil {
			return true, err
		}

		// Recursively update --init
		if err := gitClient.SubmoduleUpdate(dir, true, true); err != nil {
			return true, err
		}
	}

	if glog.Is(0) {
		if information, gitErr := gitClient.GetInfo(dir); len(gitErr) == 0 {
			glog.Infof("\tCommit:\t%s (%s)\n", information.CommitID, information.Message)
			glog.Infof("\tAuthor:\t%s <%s>\n", information.AuthorName, information.AuthorEmail)
			glog.Infof("\tDate:\t%s\n", information.Date)
		}
	}

	return true, nil
}

func copyImageSource(dockerClient DockerClient, containerID, sourceDir, destDir string, tarHelper tar.Tar) error {
	// Setup destination directory
	fi, err := os.Stat(destDir)
	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}
		glog.V(4).Infof("Creating image destination directory: %s", destDir)
		err := os.MkdirAll(destDir, 0644)
		if err != nil {
			return err
		}
	} else {
		if !fi.IsDir() {
			return fmt.Errorf("destination %s must be a directory", destDir)
		}
	}

	tempFile, err := ioutil.TempFile("", "imgsrc")
	if err != nil {
		return err
	}
	glog.V(4).Infof("Downloading source from path %s in container %s to temporary archive %s", sourceDir, containerID, tempFile.Name())
	err = dockerClient.DownloadFromContainer(containerID, docker.DownloadFromContainerOptions{
		OutputStream: tempFile,
		Path:         sourceDir,
	})
	if err != nil {
		tempFile.Close()
		return err
	}
	if err := tempFile.Close(); err != nil {
		return err
	}

	// Extract the created tar file to the destination directory
	file, err := os.Open(tempFile.Name())
	if err != nil {
		return err
	}
	defer file.Close()

	glog.V(4).Infof("Extracting temporary tar %s to directory %s", tempFile.Name(), destDir)
	var tarOutput io.Writer
	if glog.Is(4) {
		tarOutput = os.Stdout
	}
	return tarHelper.ExtractTarStreamWithLogging(destDir, file, tarOutput)
}

func extractSourceFromImage(dockerClient DockerClient, image, buildDir string, imageSecretIndex int, paths []api.ImageSourcePath, forcePull bool) error {
	glog.V(4).Infof("Extracting image source from %s", image)

	dockerAuth := docker.AuthConfiguration{}
	if imageSecretIndex != -1 {
		pullSecret := os.Getenv(fmt.Sprintf("%s%d", dockercfg.PullSourceAuthType, imageSecretIndex))
		if len(pullSecret) > 0 {
			authPresent := false
			dockerAuth, authPresent = dockercfg.NewHelper().GetDockerAuth(image, fmt.Sprintf("%s%d", dockercfg.PullSourceAuthType, imageSecretIndex))
			if authPresent {
				glog.V(5).Infof("Registry server Address: %s", dockerAuth.ServerAddress)
				glog.V(5).Infof("Registry server User Name: %s", dockerAuth.Username)
				glog.V(5).Infof("Registry server Email: %s", dockerAuth.Email)
				passwordPresent := "<<empty>>"
				if len(dockerAuth.Password) > 0 {
					passwordPresent = "<<non-empty>>"
				}
				glog.V(5).Infof("Registry server Password: %s", passwordPresent)
			}
		}
	}

	exists := true
	if !forcePull {
		_, err := dockerClient.InspectImage(image)
		if err != nil && err == docker.ErrNoSuchImage {
			exists = false
		} else if err != nil {
			return err
		}
	}

	if !exists || forcePull {
		glog.V(0).Infof("Pulling image %q ...", image)
		if err := dockerClient.PullImage(docker.PullImageOptions{Repository: image}, dockerAuth); err != nil {
			return fmt.Errorf("error pulling image %v: %v", image, err)
		}
	}

	containerConfig := &docker.Config{Image: image}
	if inspect, err := dockerClient.InspectImage(image); err != nil {
		return err
	} else {
		// In case the Docker image does not specify the entrypoint
		if len(inspect.Config.Entrypoint) == 0 && len(inspect.Config.Cmd) == 0 {
			containerConfig.Entrypoint = []string{"/fake-entrypoint"}
		}
	}

	// Create container to copy from
	container, err := dockerClient.CreateContainer(docker.CreateContainerOptions{Config: containerConfig})
	if err != nil {
		return fmt.Errorf("error creating source image container: %v", err)
	}
	defer dockerClient.RemoveContainer(docker.RemoveContainerOptions{ID: container.ID})

	tarHelper := tar.New()
	tarHelper.SetExclusionPattern(nil)

	for _, path := range paths {
		glog.V(4).Infof("Extracting path %s from container %s to %s", path.SourcePath, container.ID, path.DestinationDir)
		err := copyImageSource(dockerClient, container.ID, path.SourcePath, filepath.Join(buildDir, path.DestinationDir), tarHelper)
		if err != nil {
			return fmt.Errorf("error copying source path %s to %s: %v", path.SourcePath, path.DestinationDir, err)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package convert_test

import (
	"testing"

	"github.com/bitfinexcom/bitfinex-api-go/pkg/convert"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestItfToStrSlice(t *testing.T) {
	t.Run("invalid slice arguments", func(t *testing.T) {
		payload := []interface{}{123, 234, 345}
		got, err := convert.ItfToStrSlice(payload)
		require.NotNil(t, err)
		require.Nil(t, got)
	})

	t.Run("non slice arguments", func(t *testing.T) {
		payload := "123"
		got, err := convert.ItfToStrSlice(payload)
		require.Nil(t, err)
		assert.Equal(t, []string{}, got)
	})

	t.Run("valid arguments", func(t *testing.T) {
		payload := []interface{}{"foo", "bar", "baz"}
		got, err := convert.ItfToStrSlice(payload)
		expected := []string{"foo", "bar", "baz"}
		require.Nil(t, err)
		assert.Equal(t, expected, got)
	})
}

func TestToInt(t *testing.T) {
	t.Run("valid int argument", func(t *testing.T) {
		payload := 1234
		expected := 1234
		got := convert.ToInt(payload)
		assert.Equal(t, expected, got)
	})

	t.Run("valid string int", func(t *testing.T) {
		payload := "1"
		expected := 1
		got := convert.ToInt(payload)
		assert.Equal(t, expected, got)
	})

	t.Run("float64", func(t *testing.T) {
		var payload float64 = 1234
		expected := 1234
		got := convert.ToInt(payload)
		assert.Equal(t, expected, got)
	})

	t.Run("invalid string int", func(t *testing.T) {
		payload := "foo"
		expected := 0
		got := convert.ToInt(payload)
		assert.Equal(t, expected, got)
	})
}

func TestToInterface(t *testing.T) {
	payload := []float64{1.1234, 2.1234}
	expected := []interface{}{1.1234, 2.1234}
	got := convert.ToInterface(payload)
	assert.Equal(t, expected, got)
}
<commit_msg>convert.F64ValOrZero test coverage<commit_after>package convert_test

import (
	"testing"

	"github.com/bitfinexcom/bitfinex-api-go/pkg/convert"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestItfToStrSlice(t *testing.T) {
	t.Run("invalid slice arguments", func(t *testing.T) {
		payload := []interface{}{123, 234, 345}
		got, err := convert.ItfToStrSlice(payload)
		require.NotNil(t, err)
		require.Nil(t, got)
	})

	t.Run("non slice arguments", func(t *testing.T) {
		payload := "123"
		got, err := convert.ItfToStrSlice(payload)
		require.Nil(t, err)
		assert.Equal(t, []string{}, got)
	})

	t.Run("valid arguments", func(t *testing.T) {
		payload := []interface{}{"foo", "bar", "baz"}
		got, err := convert.ItfToStrSlice(payload)
		expected := []string{"foo", "bar", "baz"}
		require.Nil(t, err)
		assert.Equal(t, expected, got)
	})
}

func TestToInt(t *testing.T) {
	t.Run("valid int argument", func(t *testing.T) {
		payload := 1234
		expected := 1234
		got := convert.ToInt(payload)
		assert.Equal(t, expected, got)
	})

	t.Run("valid string int", func(t *testing.T) {
		payload := "1"
		expected := 1
		got := convert.ToInt(payload)
		assert.Equal(t, expected, got)
	})

	t.Run("float64", func(t *testing.T) {
		var payload float64 = 1234
		expected := 1234
		got := convert.ToInt(payload)
		assert.Equal(t, expected, got)
	})

	t.Run("invalid string int", func(t *testing.T) {
		payload := "foo"
		expected := 0
		got := convert.ToInt(payload)
		assert.Equal(t, expected, got)
	})
}

func TestToInterface(t *testing.T) {
	payload := []float64{1.1234, 2.1234}
	expected := []interface{}{1.1234, 2.1234}
	got := convert.ToInterface(payload)
	assert.Equal(t, expected, got)
}

func TestF64ValOrZero(t *testing.T) {
	t.Run("converts int to float64", func(t *testing.T) {
		var expected float64 = 910
		got := convert.F64ValOrZero(910)
		assert.Equal(t, expected, got)
	})

	t.Run("converts float64 to float64", func(t *testing.T) {
		var expected float64 = 910.1234
		got := convert.F64ValOrZero(float64(910.1234))
		assert.Equal(t, expected, got)
	})
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Knative Authors.

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 ingress

import (
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"k8s.io/apimachinery/pkg/util/sets"
	"knative.dev/networking/pkg/apis/networking/v1alpha1"
)

func TestGetExpandedHosts(t *testing.T) {
	for _, test := range []struct {
		name  string
		hosts sets.String
		want  sets.String
	}{{
		name:  "cluster local service in non-default namespace",
		hosts: sets.NewString("service.name-space.svc.cluster.local"),
		want: sets.NewString(
			"service.name-space",
			"service.name-space.svc",
			"service.name-space.svc.cluster.local",
		),
	}, {
		name:  "cluster local service in all-numeric namespace",
		hosts: sets.NewString("service.1234.svc.cluster.local"),
		want: sets.NewString(
			"service.1234.svc",
			"service.1234.svc.cluster.local",
		),
	}, {
		name:  "funky namespace",
		hosts: sets.NewString("service.1-1.svc.cluster.local"),
		want: sets.NewString(
			"service.1-1",
			"service.1-1.svc",
			"service.1-1.svc.cluster.local",
		),
	}, {
		name: "cluster local service somehow has a very long tld",
		hosts: sets.NewString(
			"service." + strings.Repeat("s", 64) + ".svc.cluster.local",
		),
		want: sets.NewString(
			"service."+strings.Repeat("s", 64)+".svc",
			"service."+strings.Repeat("s", 64)+".svc.cluster.local",
		),
	}, {
		name:  "example.com service",
		hosts: sets.NewString("foo.bar.example.com"),
		want: sets.NewString(
			"foo.bar.example.com",
		),
	}, {
		name:  "default.example.com service",
		hosts: sets.NewString("foo.default.example.com"),
		want:  sets.NewString("foo.default.example.com"),
	}, {
		name: "mix",
		hosts: sets.NewString(
			"foo.default.example.com",
			"foo.default.svc.cluster.local",
		),
		want: sets.NewString(
			"foo.default",
			"foo.default.example.com",
			"foo.default.svc",
			"foo.default.svc.cluster.local",
		),
	}} {
		t.Run(test.name, func(t *testing.T) {
			got := ExpandedHosts(test.hosts)
			if !got.Equal(test.want) {
				t.Errorf("ExpandedHosts diff(-want +got):\n%s", cmp.Diff(got.List(), test.want.List()))
			}
		})
	}
}

func TestInsertProbe(t *testing.T) {
	tests := []struct {
		name    string
		ingress *v1alpha1.Ingress
		want    string
	}{{
		name: "with rules, no append header",
		ingress: &v1alpha1.Ingress{
			Spec: v1alpha1.IngressSpec{
				Rules: []v1alpha1.IngressRule{{
					Hosts: []string{
						"example.com",
					},
					HTTP: &v1alpha1.HTTPIngressRuleValue{
						Paths: []v1alpha1.HTTPIngressPath{{
							Splits: []v1alpha1.IngressBackendSplit{{
								IngressBackend: v1alpha1.IngressBackend{
									ServiceName: "blah",
								},
							}},
						}},
					},
				}},
			},
		},
		want: "a25000a350642c8abef53078b329bd043e18758f6063c1172d53b04e14fcf5c1",
	}, {
		name: "with rules, with append header",
		ingress: &v1alpha1.Ingress{
			Spec: v1alpha1.IngressSpec{
				Rules: []v1alpha1.IngressRule{{
					Hosts: []string{
						"example.com",
					},
					HTTP: &v1alpha1.HTTPIngressRuleValue{
						Paths: []v1alpha1.HTTPIngressPath{{
							Splits: []v1alpha1.IngressBackendSplit{{
								IngressBackend: v1alpha1.IngressBackend{
									ServiceName: "blah",
								},
								AppendHeaders: map[string]string{
									"Foo": "bar",
								},
							}},
						}},
					},
				}},
			},
		},
		want: "6b652c7abed871354affd4a9cb699d33816f24541fac942149b91ad872fe63ca",
	}}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			beforePaths := len(test.ingress.Spec.Rules[0].HTTP.Paths)
			beforeAppHdr := len(test.ingress.Spec.Rules[0].HTTP.Paths[0].AppendHeaders)
			beforeMtchHdr := len(test.ingress.Spec.Rules[0].HTTP.Paths[0].Headers)
			got, err := InsertProbe(test.ingress)
			if err != nil {
				t.Error("InsertProbe() =", err)
			}
			if got != test.want {
				t.Errorf("InsertProbe() = %s, wanted %s", got, test.want)
			}

			afterPaths := len(test.ingress.Spec.Rules[0].HTTP.Paths)
			if beforePaths+beforePaths != afterPaths {
				t.Errorf("InsertProbe() %d paths, wanted %d", afterPaths, beforePaths+beforePaths)
			}

			// Check the matches at the beginning.
			afterAppHdr := len(test.ingress.Spec.Rules[0].HTTP.Paths[0].AppendHeaders)
			if beforeAppHdr+1 != afterAppHdr {
				t.Errorf("InsertProbe() left %d headers, wanted %d", afterAppHdr, beforeAppHdr+1)
			}
			afterMtchHdr := len(test.ingress.Spec.Rules[0].HTTP.Paths[0].Headers)
			if beforeMtchHdr+1 != afterMtchHdr {
				t.Errorf("InsertProbe() left %d header matches, wanted %d", afterMtchHdr, beforeMtchHdr+1)
			}

			// Check the matches at the end
			afterAppHdr = len(test.ingress.Spec.Rules[0].HTTP.Paths[afterPaths-1].AppendHeaders)
			if beforeAppHdr != afterAppHdr {
				t.Errorf("InsertProbe() left %d headers, wanted %d", afterAppHdr, beforeAppHdr)
			}
			afterMtchHdr = len(test.ingress.Spec.Rules[0].HTTP.Paths[afterPaths-1].Headers)
			if beforeMtchHdr != afterMtchHdr {
				t.Errorf("InsertProbe() left %d header matches, wanted %d", afterMtchHdr, beforeMtchHdr)
			}
		})
	}
}

func TestHostsPerVisibility(t *testing.T) {
	tests := []struct {
		name    string
		ingress *v1alpha1.Ingress
		in      map[v1alpha1.IngressVisibility]sets.String
		want    map[string]sets.String
	}{{
		name: "external rule",
		ingress: &v1alpha1.Ingress{
			Spec: v1alpha1.IngressSpec{
				Rules: []v1alpha1.IngressRule{{
					Hosts: []string{
						"example.com",
						"foo.bar.svc.cluster.local",
					},
					HTTP: &v1alpha1.HTTPIngressRuleValue{
						Paths: []v1alpha1.HTTPIngressPath{{
							Splits: []v1alpha1.IngressBackendSplit{{
								IngressBackend: v1alpha1.IngressBackend{
									ServiceName: "blah",
								},
								AppendHeaders: map[string]string{
									"Foo": "bar",
								},
							}},
						}},
					},
					Visibility: v1alpha1.IngressVisibilityExternalIP,
				}},
			},
		},
		in: map[v1alpha1.IngressVisibility]sets.String{
			v1alpha1.IngressVisibilityExternalIP:   sets.NewString("foo"),
			v1alpha1.IngressVisibilityClusterLocal: sets.NewString("bar", "baz"),
		},
		want: map[string]sets.String{
			"foo": sets.NewString(
				"example.com",
				"foo.bar.svc.cluster.local",
				"foo.bar.svc",
				"foo.bar",
			),
		},
	}, {
		name: "internal rule",
		ingress: &v1alpha1.Ingress{
			Spec: v1alpha1.IngressSpec{
				Rules: []v1alpha1.IngressRule{{
					Hosts: []string{
						"foo.bar.svc.cluster.local",
					},
					HTTP: &v1alpha1.HTTPIngressRuleValue{
						Paths: []v1alpha1.HTTPIngressPath{{
							Splits: []v1alpha1.IngressBackendSplit{{
								IngressBackend: v1alpha1.IngressBackend{
									ServiceName: "blah",
								},
								AppendHeaders: map[string]string{
									"Foo": "bar",
								},
							}},
						}},
					},
					Visibility: v1alpha1.IngressVisibilityClusterLocal,
				}},
			},
		},
		in: map[v1alpha1.IngressVisibility]sets.String{
			v1alpha1.IngressVisibilityExternalIP:   sets.NewString("foo"),
			v1alpha1.IngressVisibilityClusterLocal: sets.NewString("bar", "baz"),
		},
		want: map[string]sets.String{
			"bar": sets.NewString(
				"foo.bar.svc.cluster.local",
				"foo.bar.svc",
				"foo.bar",
			),
			"baz": sets.NewString(
				"foo.bar.svc.cluster.local",
				"foo.bar.svc",
				"foo.bar",
			),
		},
	}}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			got := HostsPerVisibility(test.ingress, test.in)
			if !cmp.Equal(got, test.want) {
				t.Error("HostsPerVisibility (-want, +got) =", cmp.Diff(test.want, got))
			}
		})
	}
}
<commit_msg>Test InsertProbe when Ingress missing HTTP block (#198)<commit_after>/*
Copyright 2019 The Knative Authors.

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 ingress

import (
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"k8s.io/apimachinery/pkg/util/sets"
	"knative.dev/networking/pkg/apis/networking/v1alpha1"
)

func TestGetExpandedHosts(t *testing.T) {
	for _, test := range []struct {
		name  string
		hosts sets.String
		want  sets.String
	}{{
		name:  "cluster local service in non-default namespace",
		hosts: sets.NewString("service.name-space.svc.cluster.local"),
		want: sets.NewString(
			"service.name-space",
			"service.name-space.svc",
			"service.name-space.svc.cluster.local",
		),
	}, {
		name:  "cluster local service in all-numeric namespace",
		hosts: sets.NewString("service.1234.svc.cluster.local"),
		want: sets.NewString(
			"service.1234.svc",
			"service.1234.svc.cluster.local",
		),
	}, {
		name:  "funky namespace",
		hosts: sets.NewString("service.1-1.svc.cluster.local"),
		want: sets.NewString(
			"service.1-1",
			"service.1-1.svc",
			"service.1-1.svc.cluster.local",
		),
	}, {
		name: "cluster local service somehow has a very long tld",
		hosts: sets.NewString(
			"service." + strings.Repeat("s", 64) + ".svc.cluster.local",
		),
		want: sets.NewString(
			"service."+strings.Repeat("s", 64)+".svc",
			"service."+strings.Repeat("s", 64)+".svc.cluster.local",
		),
	}, {
		name:  "example.com service",
		hosts: sets.NewString("foo.bar.example.com"),
		want: sets.NewString(
			"foo.bar.example.com",
		),
	}, {
		name:  "default.example.com service",
		hosts: sets.NewString("foo.default.example.com"),
		want:  sets.NewString("foo.default.example.com"),
	}, {
		name: "mix",
		hosts: sets.NewString(
			"foo.default.example.com",
			"foo.default.svc.cluster.local",
		),
		want: sets.NewString(
			"foo.default",
			"foo.default.example.com",
			"foo.default.svc",
			"foo.default.svc.cluster.local",
		),
	}} {
		t.Run(test.name, func(t *testing.T) {
			got := ExpandedHosts(test.hosts)
			if !got.Equal(test.want) {
				t.Errorf("ExpandedHosts diff(-want +got):\n%s", cmp.Diff(got.List(), test.want.List()))
			}
		})
	}
}

func TestInsertProbe(t *testing.T) {
	tests := []struct {
		name    string
		ingress *v1alpha1.Ingress
		want    string
		wantErr bool
	}{{
		name: "with rules, no append header",
		ingress: &v1alpha1.Ingress{
			Spec: v1alpha1.IngressSpec{
				Rules: []v1alpha1.IngressRule{{
					Hosts: []string{
						"example.com",
					},
					HTTP: &v1alpha1.HTTPIngressRuleValue{
						Paths: []v1alpha1.HTTPIngressPath{{
							Splits: []v1alpha1.IngressBackendSplit{{
								IngressBackend: v1alpha1.IngressBackend{
									ServiceName: "blah",
								},
							}},
						}},
					},
				}},
			},
		},
		want: "a25000a350642c8abef53078b329bd043e18758f6063c1172d53b04e14fcf5c1",
	}, {
		name: "with rules, with append header",
		ingress: &v1alpha1.Ingress{
			Spec: v1alpha1.IngressSpec{
				Rules: []v1alpha1.IngressRule{{
					Hosts: []string{
						"example.com",
					},
					HTTP: &v1alpha1.HTTPIngressRuleValue{
						Paths: []v1alpha1.HTTPIngressPath{{
							Splits: []v1alpha1.IngressBackendSplit{{
								IngressBackend: v1alpha1.IngressBackend{
									ServiceName: "blah",
								},
								AppendHeaders: map[string]string{
									"Foo": "bar",
								},
							}},
						}},
					},
				}},
			},
		},
		want: "6b652c7abed871354affd4a9cb699d33816f24541fac942149b91ad872fe63ca",
	}, {
		name: "rule missing HTTP block",
		ingress: &v1alpha1.Ingress{
			Spec: v1alpha1.IngressSpec{
				Rules: []v1alpha1.IngressRule{{
					Hosts: []string{
						"example.com",
					},
				}},
			},
		},
		wantErr: true,
	}}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			ingress := test.ingress.DeepCopy()
			got, err := InsertProbe(test.ingress)
			if test.wantErr == (err == nil) {
				t.Errorf("InsertProbe() err = %v, wantErr = %t", err, test.wantErr)
			}
			if err != nil {
				return
			}
			beforePaths := len(ingress.Spec.Rules[0].HTTP.Paths)
			beforeAppHdr := len(ingress.Spec.Rules[0].HTTP.Paths[0].AppendHeaders)
			beforeMtchHdr := len(ingress.Spec.Rules[0].HTTP.Paths[0].Headers)
			if got != test.want {
				t.Errorf("InsertProbe() = %s, wanted %s", got, test.want)
			}

			afterPaths := len(test.ingress.Spec.Rules[0].HTTP.Paths)
			if beforePaths+beforePaths != afterPaths {
				t.Errorf("InsertProbe() %d paths, wanted %d", afterPaths, beforePaths+beforePaths)
			}

			// Check the matches at the beginning.
			afterAppHdr := len(test.ingress.Spec.Rules[0].HTTP.Paths[0].AppendHeaders)
			if beforeAppHdr+1 != afterAppHdr {
				t.Errorf("InsertProbe() left %d headers, wanted %d", afterAppHdr, beforeAppHdr+1)
			}
			afterMtchHdr := len(test.ingress.Spec.Rules[0].HTTP.Paths[0].Headers)
			if beforeMtchHdr+1 != afterMtchHdr {
				t.Errorf("InsertProbe() left %d header matches, wanted %d", afterMtchHdr, beforeMtchHdr+1)
			}

			// Check the matches at the end
			afterAppHdr = len(test.ingress.Spec.Rules[0].HTTP.Paths[afterPaths-1].AppendHeaders)
			if beforeAppHdr != afterAppHdr {
				t.Errorf("InsertProbe() left %d headers, wanted %d", afterAppHdr, beforeAppHdr)
			}
			afterMtchHdr = len(test.ingress.Spec.Rules[0].HTTP.Paths[afterPaths-1].Headers)
			if beforeMtchHdr != afterMtchHdr {
				t.Errorf("InsertProbe() left %d header matches, wanted %d", afterMtchHdr, beforeMtchHdr)
			}
		})
	}
}

func TestHostsPerVisibility(t *testing.T) {
	tests := []struct {
		name    string
		ingress *v1alpha1.Ingress
		in      map[v1alpha1.IngressVisibility]sets.String
		want    map[string]sets.String
	}{{
		name: "external rule",
		ingress: &v1alpha1.Ingress{
			Spec: v1alpha1.IngressSpec{
				Rules: []v1alpha1.IngressRule{{
					Hosts: []string{
						"example.com",
						"foo.bar.svc.cluster.local",
					},
					HTTP: &v1alpha1.HTTPIngressRuleValue{
						Paths: []v1alpha1.HTTPIngressPath{{
							Splits: []v1alpha1.IngressBackendSplit{{
								IngressBackend: v1alpha1.IngressBackend{
									ServiceName: "blah",
								},
								AppendHeaders: map[string]string{
									"Foo": "bar",
								},
							}},
						}},
					},
					Visibility: v1alpha1.IngressVisibilityExternalIP,
				}},
			},
		},
		in: map[v1alpha1.IngressVisibility]sets.String{
			v1alpha1.IngressVisibilityExternalIP:   sets.NewString("foo"),
			v1alpha1.IngressVisibilityClusterLocal: sets.NewString("bar", "baz"),
		},
		want: map[string]sets.String{
			"foo": sets.NewString(
				"example.com",
				"foo.bar.svc.cluster.local",
				"foo.bar.svc",
				"foo.bar",
			),
		},
	}, {
		name: "internal rule",
		ingress: &v1alpha1.Ingress{
			Spec: v1alpha1.IngressSpec{
				Rules: []v1alpha1.IngressRule{{
					Hosts: []string{
						"foo.bar.svc.cluster.local",
					},
					HTTP: &v1alpha1.HTTPIngressRuleValue{
						Paths: []v1alpha1.HTTPIngressPath{{
							Splits: []v1alpha1.IngressBackendSplit{{
								IngressBackend: v1alpha1.IngressBackend{
									ServiceName: "blah",
								},
								AppendHeaders: map[string]string{
									"Foo": "bar",
								},
							}},
						}},
					},
					Visibility: v1alpha1.IngressVisibilityClusterLocal,
				}},
			},
		},
		in: map[v1alpha1.IngressVisibility]sets.String{
			v1alpha1.IngressVisibilityExternalIP:   sets.NewString("foo"),
			v1alpha1.IngressVisibilityClusterLocal: sets.NewString("bar", "baz"),
		},
		want: map[string]sets.String{
			"bar": sets.NewString(
				"foo.bar.svc.cluster.local",
				"foo.bar.svc",
				"foo.bar",
			),
			"baz": sets.NewString(
				"foo.bar.svc.cluster.local",
				"foo.bar.svc",
				"foo.bar",
			),
		},
	}}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			got := HostsPerVisibility(test.ingress, test.in)
			if !cmp.Equal(got, test.want) {
				t.Error("HostsPerVisibility (-want, +got) =", cmp.Diff(test.want, got))
			}
		})
	}
}
<|endoftext|>