<commit_before>// Copyright 2015 PLUMgrid
//
// 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.

// vim: set ts=8:sts=8:sw=8:noet

package hive

var iomoduleH string = `
#include <bcc/proto.h>
#include <uapi/linux/pkt_cls.h>

enum {
  RX_OK,
  RX_REDIRECT,
  RX_DROP,
  RX_RECIRCULATE,
  RX_ERROR,
};

struct type_value {
  u64 type:8;
  u64 value:56;
};
struct metadata {
  // An array of type/value pairs for the module to do with as it pleases. The
  // array is initialized to zero when the event first enters the module chain.
  // The values are preserved across modules.
  struct type_value data[8];

  // A field reserved for use by the wrapper and helper functions.
  u32 flags;

  // The length of the packet currently being processed. Read-only.
  u32 pktlen;

  // The module id currently processing the packet.
  int module_id;

  // The interface on which a packet was received. Numbering is local to the
  // module.
  int in_ifc;

  // If the module intends to forward the packet, it must call pkt_redirect to
  // set this field to determine the next-hop.
  int redir_ifc;

  int clone_ifc;
};

// iomodule must implement this function to attach to the networking stack
static int handle_rx(void *pkt, struct metadata *md);

static int pkt_redirect(void *pkt, struct metadata *md, int ifc);
static int pkt_mirror(void *pkt, struct metadata *md, int ifc);
static int pkt_drop(void *pkt, struct metadata *md);
`

var patchC string = `
BPF_TABLE("array", int, struct metadata, metadata, 8);
BPF_TABLE_EXPORT(metadata);

BPF_TABLE("prog", int, int, modules, 1024);

struct link_key {
  int module_id;
  int ifc;
};

struct link {
  int module_id;
  int ifc;
  u64 packets;
  u64 bytes;
};

BPF_TABLE("hash", struct link_key, struct link, links, 1024);

// Invoke the next module in the chain.
// When next module is a bpf function and is successfully invoked, this
// function never returns.
static int invoke_module(struct __sk_buff *skb, struct metadata *md, struct link *link) {
  if (link->module_id != 0) {
    md->in_ifc = link->ifc;
    md->module_id = link->module_id;
    modules.call(skb, md->module_id);
  } else {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0)
    bpf_redirect(link->ifc, 0);
    return TC_ACT_REDIRECT;
#else
    bpf_clone_redirect(skb, link->ifc, 0);
    return TC_ACT_SHOT;
#endif
  }
  return TC_ACT_SHOT;
}

int recv_netdev(struct __sk_buff *skb) {
  int md_id = bpf_get_smp_processor_id();
  struct metadata *md = metadata.lookup(&md_id);
  if (!md)
    return TC_ACT_SHOT;

  skb->cb[0] = md_id;

  struct link_key lkey = {
    .module_id = 0,
    .ifc = skb->ifindex,
  };
  struct link *link = links.lookup(&lkey);
  if (!link) {
    bpf_trace_printk("recv_netdev: miss\n");
    return TC_ACT_SHOT;
  }

  *md = (struct metadata){
    .pktlen = skb->len,
  };
  return invoke_module(skb, md, link);
}

int recv_tailcall(struct __sk_buff *skb) {
  int md_id = skb->cb[0];
  struct metadata *md = metadata.lookup(&md_id);
  if (!md)
    return TC_ACT_SHOT;

  struct link_key lkey = {
    .module_id = md->module_id,
    .ifc = md->redir_ifc,
  };
  struct link *link = links.lookup(&lkey);
  if (!link)
    return TC_ACT_SHOT;

  return invoke_module(skb, md, link);
}
`

var wrapperC string = `
BPF_TABLE("extern", int, struct metadata, metadata, 8);
BPF_TABLE("prog", int, int, modules, 2);

int handle_rx_wrapper(struct __sk_buff *skb) {
  int md_id = skb->cb[0];
  struct metadata *md = metadata.lookup(&md_id);
  if (!md) {
    bpf_trace_printk("metadata lookup failed\n");
    return TC_ACT_SHOT;
  }
  // copy to stack in cases llvm spills map pointers to stack
  //struct metadata local_md = *md;
  //local_md.flags = 0;
  //local_md.redir_ifc = 0;
  //local_md.clone_ifc = 0;
  md->flags = 0;
  md->redir_ifc = 0;
  md->clone_ifc = 0;

  int rc = handle_rx(skb, md);

  // TODO: implementation
  switch (rc) {
    case RX_OK:
      break;
    case RX_REDIRECT:
      break;
    case RX_RECIRCULATE:
      modules.call(skb, 1);
      break;
    case RX_DROP:
      return TC_ACT_SHOT;
  }
  //metadata.update(&md_id, &local_md);
  modules.call(skb, 0);
  return TC_ACT_SHOT;
}

static int pkt_redirect(void *pkt, struct metadata *md, int ifc) {
  md->redir_ifc = ifc;
  return TC_ACT_OK;
}

static int pkt_mirror(void *pkt, struct metadata *md, int ifc) {
  md->clone_ifc = ifc;
  return TC_ACT_OK;
}
`
<commit_msg>Update patch panel to allow un-redirected packets<commit_after>// Copyright 2015 PLUMgrid
//
// 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.

// vim: set ts=8:sts=8:sw=8:noet

package hive

var iomoduleH string = `
#include <bcc/proto.h>
#include <uapi/linux/pkt_cls.h>

enum {
  RX_OK,
  RX_REDIRECT,
  RX_DROP,
  RX_RECIRCULATE,
  RX_ERROR,
};

struct type_value {
  u64 type:8;
  u64 value:56;
};
struct metadata {
  // An array of type/value pairs for the module to do with as it pleases. The
  // array is initialized to zero when the event first enters the module chain.
  // The values are preserved across modules.
  struct type_value data[8];

  // A field reserved for use by the wrapper and helper functions.
  u32 flags;

  // The length of the packet currently being processed. Read-only.
  u32 pktlen;

  // The module id currently processing the packet.
  int module_id;

  // The interface on which a packet was received. Numbering is local to the
  // module.
  int in_ifc;

  // If the module intends to forward the packet, it must call pkt_redirect to
  // set this field to determine the next-hop.
  int redir_ifc;

  int clone_ifc;
};

// iomodule must implement this function to attach to the networking stack
static int handle_rx(void *pkt, struct metadata *md);

static int pkt_redirect(void *pkt, struct metadata *md, int ifc);
static int pkt_mirror(void *pkt, struct metadata *md, int ifc);
static int pkt_drop(void *pkt, struct metadata *md);
`

var patchC string = `
BPF_TABLE("array", int, struct metadata, metadata, 8);
BPF_TABLE_EXPORT(metadata);

BPF_TABLE("prog", int, int, modules, 1024);

struct link_key {
  int module_id;
  int ifc;
};

struct link {
  int module_id;
  int ifc;
  u64 packets;
  u64 bytes;
};

BPF_TABLE("hash", struct link_key, struct link, links, 1024);

// Invoke the next module in the chain.
// When next module is a bpf function and is successfully invoked, this
// function never returns.
static int invoke_module(struct __sk_buff *skb, struct metadata *md, struct link *link) {
  if (link->module_id != 0) {
    md->in_ifc = link->ifc;
    md->module_id = link->module_id;
    modules.call(skb, md->module_id);
  } else {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0)
    bpf_redirect(link->ifc, 0);
    return TC_ACT_REDIRECT;
#else
    bpf_clone_redirect(skb, link->ifc, 0);
    return TC_ACT_SHOT;
#endif
  }
  return TC_ACT_SHOT;
}

int recv_netdev(struct __sk_buff *skb) {
  int md_id = bpf_get_smp_processor_id();
  struct metadata *md = metadata.lookup(&md_id);
  if (!md)
    return TC_ACT_SHOT;

  skb->cb[0] = md_id;

  struct link_key lkey = {
    .module_id = 0,
    .ifc = skb->ifindex,
  };
  struct link *link = links.lookup(&lkey);
  if (!link) {
    bpf_trace_printk("recv_netdev: miss\n");
    return TC_ACT_SHOT;
  }

  *md = (struct metadata){
    .pktlen = skb->len,
  };
  return invoke_module(skb, md, link);
}

int recv_tailcall(struct __sk_buff *skb) {
  int md_id = skb->cb[0];
  struct metadata *md = metadata.lookup(&md_id);
  if (!md)
    return TC_ACT_SHOT;

  if (md->redir_ifc) {
    struct link_key lkey = {
      .module_id = md->module_id,
      .ifc = md->redir_ifc,
    };
    struct link *link = links.lookup(&lkey);
    if (!link)
      return TC_ACT_SHOT;
    return invoke_module(skb, md, link);
  }
  return TC_ACT_OK;
}
`

var wrapperC string = `
BPF_TABLE("extern", int, struct metadata, metadata, 8);
BPF_TABLE("prog", int, int, modules, 2);

int handle_rx_wrapper(struct __sk_buff *skb) {
  int md_id = skb->cb[0];
  struct metadata *md = metadata.lookup(&md_id);
  if (!md) {
    bpf_trace_printk("metadata lookup failed\n");
    return TC_ACT_SHOT;
  }
  // copy to stack in cases llvm spills map pointers to stack
  //struct metadata local_md = *md;
  //local_md.flags = 0;
  //local_md.redir_ifc = 0;
  //local_md.clone_ifc = 0;
  md->flags = 0;
  md->redir_ifc = 0;
  md->clone_ifc = 0;

  int rc = handle_rx(skb, md);

  // TODO: implementation
  switch (rc) {
    case RX_OK:
      break;
    case RX_REDIRECT:
      break;
    case RX_RECIRCULATE:
      modules.call(skb, 1);
      break;
    case RX_DROP:
      return TC_ACT_SHOT;
  }
  //metadata.update(&md_id, &local_md);
  modules.call(skb, 0);
  return TC_ACT_SHOT;
}

static int pkt_redirect(void *pkt, struct metadata *md, int ifc) {
  md->redir_ifc = ifc;
  return TC_ACT_OK;
}

static int pkt_mirror(void *pkt, struct metadata *md, int ifc) {
  md->clone_ifc = ifc;
  return TC_ACT_OK;
}
`
<|endoftext|>


<commit_before>// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license

package irc

import (
	"encoding/json"
	"fmt"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/oragono/oragono/irc/utils"
	"github.com/tidwall/buntdb"
)

const (
	keyDlineEntry = "bans.dlinev2 %s"
)

// IPBanInfo holds info about an IP/net ban.
type IPBanInfo struct {
	// Reason is the ban reason.
	Reason string `json:"reason"`
	// OperReason is an oper ban reason.
	OperReason string `json:"oper_reason"`
	// OperName is the oper who set the ban.
	OperName string `json:"oper_name"`
	// time of ban creation
	TimeCreated time.Time
	// duration of the ban; 0 means "permanent"
	Duration time.Duration
}

func (info IPBanInfo) timeLeft() time.Duration {
	return time.Until(info.TimeCreated.Add(info.Duration))
}

func (info IPBanInfo) TimeLeft() string {
	if info.Duration == 0 {
		return "indefinite"
	} else {
		return info.timeLeft().Truncate(time.Second).String()
	}
}

// BanMessage returns the ban message.
func (info IPBanInfo) BanMessage(message string) string {
	message = fmt.Sprintf(message, info.Reason)
	if info.Duration != 0 {
		message += fmt.Sprintf(" [%s]", info.TimeLeft())
	}
	return message
}

// dLineNet contains the net itself and expiration time for a given network.
type dLineNet struct {
	// Network is the network that is blocked.
	// This is always an IPv6 CIDR; IPv4 CIDRs are translated with the 4-in-6 prefix,
	// individual IPv4 and IPV6 addresses are translated to the relevant /128.
	Network net.IPNet
	// Info contains information on the ban.
	Info IPBanInfo
}

// DLineManager manages and dlines.
type DLineManager struct {
	sync.RWMutex                // tier 1
	persistenceMutex sync.Mutex // tier 2
	// networks that are dlined:
	// XXX: the keys of this map (which are also the database persistence keys)
	// are the human-readable representations returned by NetToNormalizedString
	networks map[string]dLineNet
	// this keeps track of expiration timers for temporary bans
	expirationTimers map[string]*time.Timer
	server           *Server
}

// NewDLineManager returns a new DLineManager.
func NewDLineManager(server *Server) *DLineManager {
	var dm DLineManager
	dm.networks = make(map[string]dLineNet)
	dm.expirationTimers = make(map[string]*time.Timer)
	dm.server = server

	dm.loadFromDatastore()

	return &dm
}

// AllBans returns all bans (for use with APIs, etc).
func (dm *DLineManager) AllBans() map[string]IPBanInfo {
	allb := make(map[string]IPBanInfo)

	dm.RLock()
	defer dm.RUnlock()

	// map keys are already the human-readable forms, just return a copy of the map
	for key, info := range dm.networks {
		allb[key] = info.Info
	}

	return allb
}

// AddNetwork adds a network to the blocked list.
func (dm *DLineManager) AddNetwork(network net.IPNet, duration time.Duration, reason, operReason, operName string) error {
	dm.persistenceMutex.Lock()
	defer dm.persistenceMutex.Unlock()

	// assemble ban info
	info := IPBanInfo{
		Reason:      reason,
		OperReason:  operReason,
		OperName:    operName,
		TimeCreated: time.Now().UTC(),
		Duration:    duration,
	}

	id := dm.addNetworkInternal(network, info)
	return dm.persistDline(id, info)
}

func (dm *DLineManager) addNetworkInternal(network net.IPNet, info IPBanInfo) (id string) {
	network = utils.NormalizeNet(network)
	id = utils.NetToNormalizedString(network)

	var timeLeft time.Duration
	if info.Duration != 0 {
		timeLeft = info.timeLeft()
		if timeLeft <= 0 {
			return
		}
	}

	dm.Lock()
	defer dm.Unlock()

	dm.networks[id] = dLineNet{
		Network: network,
		Info:    info,
	}

	dm.cancelTimer(id)

	if info.Duration == 0 {
		return
	}

	// set up new expiration timer
	timeCreated := info.TimeCreated
	processExpiration := func() {
		dm.Lock()
		defer dm.Unlock()

		netBan, ok := dm.networks[id]
		if ok && netBan.Info.TimeCreated.Equal(timeCreated) {
			delete(dm.networks, id)
			// TODO(slingamn) here's where we'd remove it from the radix tree
			delete(dm.expirationTimers, id)
		}
	}
	dm.expirationTimers[id] = time.AfterFunc(timeLeft, processExpiration)

	return
}

func (dm *DLineManager) cancelTimer(id string) {
	oldTimer := dm.expirationTimers[id]
	if oldTimer != nil {
		oldTimer.Stop()
		delete(dm.expirationTimers, id)
	}
}

func (dm *DLineManager) persistDline(id string, info IPBanInfo) error {
	// save in datastore
	dlineKey := fmt.Sprintf(keyDlineEntry, id)
	// assemble json from ban info
	b, err := json.Marshal(info)
	if err != nil {
		dm.server.logger.Error("internal", "couldn't marshal d-line", err.Error())
		return err
	}
	bstr := string(b)
	var setOptions *buntdb.SetOptions
	if info.Duration != 0 {
		setOptions = &buntdb.SetOptions{Expires: true, TTL: info.Duration}
	}

	err = dm.server.store.Update(func(tx *buntdb.Tx) error {
		_, _, err := tx.Set(dlineKey, bstr, setOptions)
		return err
	})
	if err != nil {
		dm.server.logger.Error("internal", "couldn't store d-line", err.Error())
	}
	return err
}

func (dm *DLineManager) unpersistDline(id string) error {
	dlineKey := fmt.Sprintf(keyDlineEntry, id)
	return dm.server.store.Update(func(tx *buntdb.Tx) error {
		_, err := tx.Delete(dlineKey)
		return err
	})
}

// RemoveNetwork removes a network from the blocked list.
func (dm *DLineManager) RemoveNetwork(network net.IPNet) error {
	dm.persistenceMutex.Lock()
	defer dm.persistenceMutex.Unlock()

	id := utils.NetToNormalizedString(utils.NormalizeNet(network))

	present := func() bool {
		dm.Lock()
		defer dm.Unlock()
		_, ok := dm.networks[id]
		delete(dm.networks, id)
		dm.cancelTimer(id)
		return ok
	}()

	if !present {
		return errNoExistingBan
	}

	return dm.unpersistDline(id)
}

// AddIP adds an IP address to the blocked list.
func (dm *DLineManager) AddIP(addr net.IP, duration time.Duration, reason, operReason, operName string) error {
	return dm.AddNetwork(utils.NormalizeIPToNet(addr), duration, reason, operReason, operName)
}

// RemoveIP removes an IP address from the blocked list.
func (dm *DLineManager) RemoveIP(addr net.IP) error {
	return dm.RemoveNetwork(utils.NormalizeIPToNet(addr))
}

// CheckIP returns whether or not an IP address was banned, and how long it is banned for.
func (dm *DLineManager) CheckIP(addr net.IP) (isBanned bool, info IPBanInfo) {
	addr = addr.To16() // almost certainly unnecessary

	dm.RLock()
	defer dm.RUnlock()

	// check networks
	// TODO(slingamn) use a radix tree as the data plane for this
	for _, netBan := range dm.networks {
		if netBan.Network.Contains(addr) {
			return true, netBan.Info
		}
	}
	// no matches!
	isBanned = false
	return
}

func (dm *DLineManager) loadFromDatastore() {
	dlinePrefix := fmt.Sprintf(keyDlineEntry, "")
	dm.server.store.View(func(tx *buntdb.Tx) error {
		tx.AscendGreaterOrEqual("", dlinePrefix, func(key, value string) bool {
			if !strings.HasPrefix(key, dlinePrefix) {
				return false
			}

			// get address name
			key = strings.TrimPrefix(key, dlinePrefix)

			// load addr/net
			hostNet, err := utils.NormalizedNetFromString(key)
			if err != nil {
				dm.server.logger.Error("internal", "bad dline cidr", err.Error())
				return true
			}

			// load ban info
			var info IPBanInfo
			err = json.Unmarshal([]byte(value), &info)
			if err != nil {
				dm.server.logger.Error("internal", "bad dline data", err.Error())
				return true
			}

			// set opername if it isn't already set
			if info.OperName == "" {
				info.OperName = dm.server.name
			}

			// add to the server
			dm.addNetworkInternal(hostNet, info)

			return true
		})
		return nil
	})
}

func (s *Server) loadDLines() {
	s.dlines = NewDLineManager(s)
}
<commit_msg>fix #671<commit_after>// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license

package irc

import (
	"encoding/json"
	"fmt"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/oragono/oragono/irc/utils"
	"github.com/tidwall/buntdb"
)

const (
	keyDlineEntry = "bans.dlinev2 %s"
)

// IPBanInfo holds info about an IP/net ban.
type IPBanInfo struct {
	// Reason is the ban reason.
	Reason string `json:"reason"`
	// OperReason is an oper ban reason.
	OperReason string `json:"oper_reason"`
	// OperName is the oper who set the ban.
	OperName string `json:"oper_name"`
	// time of ban creation
	TimeCreated time.Time
	// duration of the ban; 0 means "permanent"
	Duration time.Duration
}

func (info IPBanInfo) timeLeft() time.Duration {
	return time.Until(info.TimeCreated.Add(info.Duration))
}

func (info IPBanInfo) TimeLeft() string {
	if info.Duration == 0 {
		return "indefinite"
	} else {
		return info.timeLeft().Truncate(time.Second).String()
	}
}

// BanMessage returns the ban message.
func (info IPBanInfo) BanMessage(message string) string {
	message = fmt.Sprintf(message, info.Reason)
	if info.Duration != 0 {
		message += fmt.Sprintf(" [%s]", info.TimeLeft())
	}
	return message
}

// dLineNet contains the net itself and expiration time for a given network.
type dLineNet struct {
	// Network is the network that is blocked.
	// This is always an IPv6 CIDR; IPv4 CIDRs are translated with the 4-in-6 prefix,
	// individual IPv4 and IPV6 addresses are translated to the relevant /128.
	Network net.IPNet
	// Info contains information on the ban.
	Info IPBanInfo
}

// DLineManager manages and dlines.
type DLineManager struct {
	sync.RWMutex                // tier 1
	persistenceMutex sync.Mutex // tier 2
	// networks that are dlined:
	// XXX: the keys of this map (which are also the database persistence keys)
	// are the human-readable representations returned by NetToNormalizedString
	networks map[string]dLineNet
	// this keeps track of expiration timers for temporary bans
	expirationTimers map[string]*time.Timer
	server           *Server
}

// NewDLineManager returns a new DLineManager.
func NewDLineManager(server *Server) *DLineManager {
	var dm DLineManager
	dm.networks = make(map[string]dLineNet)
	dm.expirationTimers = make(map[string]*time.Timer)
	dm.server = server

	dm.loadFromDatastore()

	return &dm
}

// AllBans returns all bans (for use with APIs, etc).
func (dm *DLineManager) AllBans() map[string]IPBanInfo {
	allb := make(map[string]IPBanInfo)

	dm.RLock()
	defer dm.RUnlock()

	// map keys are already the human-readable forms, just return a copy of the map
	for key, info := range dm.networks {
		allb[key] = info.Info
	}

	return allb
}

// AddNetwork adds a network to the blocked list.
func (dm *DLineManager) AddNetwork(network net.IPNet, duration time.Duration, reason, operReason, operName string) error {
	dm.persistenceMutex.Lock()
	defer dm.persistenceMutex.Unlock()

	// assemble ban info
	info := IPBanInfo{
		Reason:      reason,
		OperReason:  operReason,
		OperName:    operName,
		TimeCreated: time.Now().UTC(),
		Duration:    duration,
	}

	id := dm.addNetworkInternal(network, info)
	return dm.persistDline(id, info)
}

func (dm *DLineManager) addNetworkInternal(network net.IPNet, info IPBanInfo) (id string) {
	network = utils.NormalizeNet(network)
	id = utils.NetToNormalizedString(network)

	var timeLeft time.Duration
	if info.Duration != 0 {
		timeLeft = info.timeLeft()
		if timeLeft <= 0 {
			return
		}
	}

	dm.Lock()
	defer dm.Unlock()

	dm.networks[id] = dLineNet{
		Network: network,
		Info:    info,
	}

	dm.cancelTimer(id)

	if info.Duration == 0 {
		return
	}

	// set up new expiration timer
	timeCreated := info.TimeCreated
	processExpiration := func() {
		dm.Lock()
		defer dm.Unlock()

		netBan, ok := dm.networks[id]
		if ok && netBan.Info.TimeCreated.Equal(timeCreated) {
			delete(dm.networks, id)
			// TODO(slingamn) here's where we'd remove it from the radix tree
			delete(dm.expirationTimers, id)
		}
	}
	dm.expirationTimers[id] = time.AfterFunc(timeLeft, processExpiration)

	return
}

func (dm *DLineManager) cancelTimer(id string) {
	oldTimer := dm.expirationTimers[id]
	if oldTimer != nil {
		oldTimer.Stop()
		delete(dm.expirationTimers, id)
	}
}

func (dm *DLineManager) persistDline(id string, info IPBanInfo) error {
	// save in datastore
	dlineKey := fmt.Sprintf(keyDlineEntry, id)
	// assemble json from ban info
	b, err := json.Marshal(info)
	if err != nil {
		dm.server.logger.Error("internal", "couldn't marshal d-line", err.Error())
		return err
	}
	bstr := string(b)
	var setOptions *buntdb.SetOptions
	if info.Duration != 0 {
		setOptions = &buntdb.SetOptions{Expires: true, TTL: info.Duration}
	}

	err = dm.server.store.Update(func(tx *buntdb.Tx) error {
		_, _, err := tx.Set(dlineKey, bstr, setOptions)
		return err
	})
	if err != nil {
		dm.server.logger.Error("internal", "couldn't store d-line", err.Error())
	}
	return err
}

func (dm *DLineManager) unpersistDline(id string) error {
	dlineKey := fmt.Sprintf(keyDlineEntry, id)
	return dm.server.store.Update(func(tx *buntdb.Tx) error {
		_, err := tx.Delete(dlineKey)
		return err
	})
}

// RemoveNetwork removes a network from the blocked list.
func (dm *DLineManager) RemoveNetwork(network net.IPNet) error {
	dm.persistenceMutex.Lock()
	defer dm.persistenceMutex.Unlock()

	id := utils.NetToNormalizedString(utils.NormalizeNet(network))

	present := func() bool {
		dm.Lock()
		defer dm.Unlock()
		_, ok := dm.networks[id]
		delete(dm.networks, id)
		dm.cancelTimer(id)
		return ok
	}()

	if !present {
		return errNoExistingBan
	}

	return dm.unpersistDline(id)
}

// AddIP adds an IP address to the blocked list.
func (dm *DLineManager) AddIP(addr net.IP, duration time.Duration, reason, operReason, operName string) error {
	return dm.AddNetwork(utils.NormalizeIPToNet(addr), duration, reason, operReason, operName)
}

// RemoveIP removes an IP address from the blocked list.
func (dm *DLineManager) RemoveIP(addr net.IP) error {
	return dm.RemoveNetwork(utils.NormalizeIPToNet(addr))
}

// CheckIP returns whether or not an IP address was banned, and how long it is banned for.
func (dm *DLineManager) CheckIP(addr net.IP) (isBanned bool, info IPBanInfo) {
	addr = addr.To16() // almost certainly unnecessary
	if addr.IsLoopback() {
		return // #671
	}

	dm.RLock()
	defer dm.RUnlock()

	// check networks
	// TODO(slingamn) use a radix tree as the data plane for this
	for _, netBan := range dm.networks {
		if netBan.Network.Contains(addr) {
			return true, netBan.Info
		}
	}
	// no matches!
	isBanned = false
	return
}

func (dm *DLineManager) loadFromDatastore() {
	dlinePrefix := fmt.Sprintf(keyDlineEntry, "")
	dm.server.store.View(func(tx *buntdb.Tx) error {
		tx.AscendGreaterOrEqual("", dlinePrefix, func(key, value string) bool {
			if !strings.HasPrefix(key, dlinePrefix) {
				return false
			}

			// get address name
			key = strings.TrimPrefix(key, dlinePrefix)

			// load addr/net
			hostNet, err := utils.NormalizedNetFromString(key)
			if err != nil {
				dm.server.logger.Error("internal", "bad dline cidr", err.Error())
				return true
			}

			// load ban info
			var info IPBanInfo
			err = json.Unmarshal([]byte(value), &info)
			if err != nil {
				dm.server.logger.Error("internal", "bad dline data", err.Error())
				return true
			}

			// set opername if it isn't already set
			if info.OperName == "" {
				info.OperName = dm.server.name
			}

			// add to the server
			dm.addNetworkInternal(hostNet, info)

			return true
		})
		return nil
	})
}

func (s *Server) loadDLines() {
	s.dlines = NewDLineManager(s)
}
<|endoftext|>


<commit_before>package git

import (
	"fmt"
	"os"
	"os/exec"
	"strings"

	version "github.com/hashicorp/go-version"
	"github.com/sirupsen/logrus"
)

// Commits returns a set of commits.
// If commitrange is a git still range 12345...54321, then it will be isolated set of commits.
// If commitrange is a single commit, all ancestor commits up through the hash provided.
// If commitrange is an empty commit range, then nil is returned.
func Commits(commitrange string) ([]CommitEntry, error) {
	cmdArgs := []string{"git", "rev-list", commitrange}
	if debug() {
		logrus.Infof("[git] cmd: %q", strings.Join(cmdArgs, " "))
	}
	output, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).Output()
	if err != nil {
		logrus.Errorf("mm[git] cmd: %q", strings.Join(cmdArgs, " "))
		return nil, err
	}
	if len(output) == 0 {
		return nil, nil
	}
	commitHashes := strings.Split(strings.TrimSpace(string(output)), "\n")
	commits := make([]CommitEntry, len(commitHashes))
	for i, commitHash := range commitHashes {
		c, err := LogCommit(commitHash)
		if err != nil {
			return commits, err
		}
		commits[i] = *c
	}
	return commits, nil
}

// FieldNames are for the formating and rendering of the CommitEntry structs.
// Keys here are from git log pretty format "format:..."
var FieldNames = map[string]string{
	"%h":  "abbreviated_commit",
	"%p":  "abbreviated_parent",
	"%t":  "abbreviated_tree",
	"%aD": "author_date",
	"%aE": "author_email",
	"%aN": "author_name",
	"%b":  "body",
	"%H":  "commit",
	"%N":  "commit_notes",
	"%cD": "committer_date",
	"%cE": "committer_email",
	"%cN": "committer_name",
	"%e":  "encoding",
	"%P":  "parent",
	"%D":  "refs",
	"%f":  "sanitized_subject_line",
	"%GS": "signer",
	"%GK": "signer_key",
	"%s":  "subject",
	"%G?": "verification_flag",
}

func gitVersion() (string, error) {
	cmd := exec.Command("git", "version")
	cmd.Stderr = os.Stderr
	buf, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.Fields(string(buf))[2], nil
}

// https://github.com/vbatts/git-validation/issues/37
var versionWithExcludes = "1.9.5"

func gitVersionNewerThan(otherV string) (bool, error) {
	gv, err := gitVersion()
	if err != nil {
		return false, err
	}
	v1, err := version.NewVersion(gv)
	if err != nil {
		return false, err
	}
	v2, err := version.NewVersion(otherV)
	if err != nil {
		return false, err
	}
	return v2.Equal(v1) || v2.LessThan(v1), nil
}

// Check warns if changes introduce whitespace errors.
// Returns non-zero if any issues are found.
func Check(commit string) ([]byte, error) {
	args := []string{
		"--no-pager", "log", "--check",
		fmt.Sprintf("%s^..%s", commit, commit),
	}
	if excludeEnvList := os.Getenv("GIT_CHECK_EXCLUDE"); excludeEnvList != "" {
		gitNewEnough, err := gitVersionNewerThan(versionWithExcludes)
		if err != nil {
			return nil, err
		}
		if gitNewEnough {
			excludeList := strings.Split(excludeEnvList, ":")
			for _, exclude := range excludeList {
				if exclude == "" {
					continue
				}
				args = append(args, "--", ".", fmt.Sprintf(":(exclude)%s", exclude))
			}
		}
	}
	cmd := exec.Command("git", args...)
	if debug() {
		logrus.Infof("[git] cmd: %q", strings.Join(cmd.Args, " "))
	}
	cmd.Stderr = os.Stderr
	return cmd.Output()
}

// Show returns the diff of a commit.
//
// NOTE: This could be expensive for very large commits.
func Show(commit string) ([]byte, error) {
	cmd := exec.Command("git", "--no-pager", "show", commit)
	if debug() {
		logrus.Infof("[git] cmd: %q", strings.Join(cmd.Args, " "))
	}
	cmd.Stderr = os.Stderr
	return cmd.Output()
}

// CommitEntry represents a single commit's information from `git`.
// See also FieldNames
type CommitEntry map[string]string

// LogCommit assembles the full information on a commit from its commit hash
func LogCommit(commit string) (*CommitEntry, error) {
	c := CommitEntry{}
	for k, v := range FieldNames {
		cmd := exec.Command("git", "--no-pager", "log", "-1", `--pretty=format:`+k+``, commit)
		if debug() {
			logrus.Infof("[git] cmd: %q", strings.Join(cmd.Args, " "))
		}
		cmd.Stderr = os.Stderr
		out, err := cmd.Output()
		if err != nil {
			logrus.Errorf("[git] cmd: %q", strings.Join(cmd.Args, " "))
			return nil, err
		}
		c[v] = strings.TrimSpace(string(out))
	}

	return &c, nil
}

func debug() bool {
	return len(os.Getenv("DEBUG")) > 0
}

// FetchHeadCommit returns the hash of FETCH_HEAD
func FetchHeadCommit() (string, error) {
	cmdArgs := []string{"git", "--no-pager", "rev-parse", "--verify", "FETCH_HEAD"}
	if debug() {
		logrus.Infof("[git] cmd: %q", strings.Join(cmdArgs, " "))
	}
	output, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).Output()
	if err != nil {
		logrus.Errorf("[git] cmd: %q", strings.Join(cmdArgs, " "))
		return "", err
	}
	return strings.TrimSpace(string(output)), nil
}

// HeadCommit returns the hash of HEAD
func HeadCommit() (string, error) {
	cmdArgs := []string{"git", "--no-pager", "rev-parse", "--verify", "HEAD"}
	if debug() {
		logrus.Infof("[git] cmd: %q", strings.Join(cmdArgs, " "))
	}
	output, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).Output()
	if err != nil {
		logrus.Errorf("[git] cmd: %q", strings.Join(cmdArgs, " "))
		return "", err
	}
	return strings.TrimSpace(string(output)), nil
}
<commit_msg>Replace CRLF in all commit messages which currently causes DCO check to fail<commit_after>package git

import (
	"fmt"
	"os"
	"os/exec"
	"strings"

	version "github.com/hashicorp/go-version"
	"github.com/sirupsen/logrus"
)

// Commits returns a set of commits.
// If commitrange is a git still range 12345...54321, then it will be isolated set of commits.
// If commitrange is a single commit, all ancestor commits up through the hash provided.
// If commitrange is an empty commit range, then nil is returned.
func Commits(commitrange string) ([]CommitEntry, error) {
	cmdArgs := []string{"git", "rev-list", commitrange}
	if debug() {
		logrus.Infof("[git] cmd: %q", strings.Join(cmdArgs, " "))
	}
	output, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).Output()
	if err != nil {
		logrus.Errorf("mm[git] cmd: %q", strings.Join(cmdArgs, " "))
		return nil, err
	}
	if len(output) == 0 {
		return nil, nil
	}
	commitHashes := strings.Split(strings.TrimSpace(string(output)), "\n")
	commits := make([]CommitEntry, len(commitHashes))
	for i, commitHash := range commitHashes {
		c, err := LogCommit(commitHash)
		if err != nil {
			return commits, err
		}
		commits[i] = *c
	}
	return commits, nil
}

// FieldNames are for the formating and rendering of the CommitEntry structs.
// Keys here are from git log pretty format "format:..."
var FieldNames = map[string]string{
	"%h":  "abbreviated_commit",
	"%p":  "abbreviated_parent",
	"%t":  "abbreviated_tree",
	"%aD": "author_date",
	"%aE": "author_email",
	"%aN": "author_name",
	"%b":  "body",
	"%H":  "commit",
	"%N":  "commit_notes",
	"%cD": "committer_date",
	"%cE": "committer_email",
	"%cN": "committer_name",
	"%e":  "encoding",
	"%P":  "parent",
	"%D":  "refs",
	"%f":  "sanitized_subject_line",
	"%GS": "signer",
	"%GK": "signer_key",
	"%s":  "subject",
	"%G?": "verification_flag",
}

func gitVersion() (string, error) {
	cmd := exec.Command("git", "version")
	cmd.Stderr = os.Stderr
	buf, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.Fields(string(buf))[2], nil
}

// https://github.com/vbatts/git-validation/issues/37
var versionWithExcludes = "1.9.5"

func gitVersionNewerThan(otherV string) (bool, error) {
	gv, err := gitVersion()
	if err != nil {
		return false, err
	}
	v1, err := version.NewVersion(gv)
	if err != nil {
		return false, err
	}
	v2, err := version.NewVersion(otherV)
	if err != nil {
		return false, err
	}
	return v2.Equal(v1) || v2.LessThan(v1), nil
}

// Check warns if changes introduce whitespace errors.
// Returns non-zero if any issues are found.
func Check(commit string) ([]byte, error) {
	args := []string{
		"--no-pager", "log", "--check",
		fmt.Sprintf("%s^..%s", commit, commit),
	}
	if excludeEnvList := os.Getenv("GIT_CHECK_EXCLUDE"); excludeEnvList != "" {
		gitNewEnough, err := gitVersionNewerThan(versionWithExcludes)
		if err != nil {
			return nil, err
		}
		if gitNewEnough {
			excludeList := strings.Split(excludeEnvList, ":")
			for _, exclude := range excludeList {
				if exclude == "" {
					continue
				}
				args = append(args, "--", ".", fmt.Sprintf(":(exclude)%s", exclude))
			}
		}
	}
	cmd := exec.Command("git", args...)
	if debug() {
		logrus.Infof("[git] cmd: %q", strings.Join(cmd.Args, " "))
	}
	cmd.Stderr = os.Stderr
	return cmd.Output()
}

// Show returns the diff of a commit.
//
// NOTE: This could be expensive for very large commits.
func Show(commit string) ([]byte, error) {
	cmd := exec.Command("git", "--no-pager", "show", commit)
	if debug() {
		logrus.Infof("[git] cmd: %q", strings.Join(cmd.Args, " "))
	}
	cmd.Stderr = os.Stderr
	return cmd.Output()
}

// CommitEntry represents a single commit's information from `git`.
// See also FieldNames
type CommitEntry map[string]string

// LogCommit assembles the full information on a commit from its commit hash
func LogCommit(commit string) (*CommitEntry, error) {
	c := CommitEntry{}
	for k, v := range FieldNames {
		cmd := exec.Command("git", "--no-pager", "log", "-1", `--pretty=format:`+k+``, commit)
		if debug() {
			logrus.Infof("[git] cmd: %q", strings.Join(cmd.Args, " "))
		}
		cmd.Stderr = os.Stderr
		out, err := cmd.Output()
		if err != nil {
			logrus.Errorf("[git] cmd: %q", strings.Join(cmd.Args, " "))
			return nil, err
		}
		commitMessage := strings.ReplaceAll(string(out), "\r\n", "\n")
		c[v] = strings.TrimSpace(commitMessage)
	}

	return &c, nil
}

func debug() bool {
	return len(os.Getenv("DEBUG")) > 0
}

// FetchHeadCommit returns the hash of FETCH_HEAD
func FetchHeadCommit() (string, error) {
	cmdArgs := []string{"git", "--no-pager", "rev-parse", "--verify", "FETCH_HEAD"}
	if debug() {
		logrus.Infof("[git] cmd: %q", strings.Join(cmdArgs, " "))
	}
	output, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).Output()
	if err != nil {
		logrus.Errorf("[git] cmd: %q", strings.Join(cmdArgs, " "))
		return "", err
	}
	return strings.TrimSpace(string(output)), nil
}

// HeadCommit returns the hash of HEAD
func HeadCommit() (string, error) {
	cmdArgs := []string{"git", "--no-pager", "rev-parse", "--verify", "HEAD"}
	if debug() {
		logrus.Infof("[git] cmd: %q", strings.Join(cmdArgs, " "))
	}
	output, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).Output()
	if err != nil {
		logrus.Errorf("[git] cmd: %q", strings.Join(cmdArgs, " "))
		return "", err
	}
	return strings.TrimSpace(string(output)), nil
}
<|endoftext|>


<commit_before>package metrics

import (
	"bytes"
	"context"
	"io"
	"net"
	"net/http"
	"os"
	"time"

	"golang.org/x/net/context/ctxhttp"

	"github.com/cenkalti/backoff"
	"github.com/koding/kite"
)

// Publisher sends DD events to agent.
type Publisher struct {
	conn            io.WriteCloser
	metricsEndpoint string
}

// NewPublisherWithConn creates new Publisher for sending the incoming events to
// DataDog agent.
func NewPublisherWithConn(conn io.WriteCloser, metricsEndpoint string) *Publisher {
	return &Publisher{
		conn:            conn,
		metricsEndpoint: metricsEndpoint,
	}
}

// NewPublisher creates a new publisher with default DD agent address.
func NewPublisher(metricsEndpoint string) (*Publisher, error) {
	udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8125")
	if err != nil {
		return nil, err
	}

	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		return nil, err
	}

	return NewPublisherWithConn(conn, metricsEndpoint), nil
}

// PublishRequest represents a request type for "metrics.publish" kloud's
// kite method.
type PublishRequest struct {
	Data GzippedPayload `json:"data"`
}

// Pattern returns the endpoint name for kite.
func (Publisher) Pattern() string {
	return "metrics.publish"
}

// Publish is a kite.Handler for "metrics.publish" kite method.
func (p *Publisher) Publish(r *kite.Request) (interface{}, error) {
	var req PublishRequest
	argOne := r.Args.One()
	if err := argOne.Unmarshal(&req); err != nil {
		return nil, err
	}

	for _, data := range req.Data {
		_, err := p.conn.Write(data)
		if err == nil {
			continue
		}

		if errP, ok := err.(*net.OpError); ok {
			if _, ok := errP.Err.(*os.SyscallError); ok {
				continue
			}
		}

		return nil, err
	}

	go publishToCountly(p.metricsEndpoint, argOne.Raw, r.LocalKite.Log)

	return nil, nil
}

// Close closes the underlying connection.
func (p *Publisher) Close() error {
	return p.conn.Close()
}

func publishToCountly(url string, r []byte, log kite.Logger) error {
	req := bytes.NewReader(r)

	ticker := backoff.NewTicker(newBackoff())
	defer ticker.Stop()

	var err error
	var retry bool
	for range ticker.C {
		// each request should not take more than 5 sec
		ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
		defer cancel()

		retry, err = shouldRetry(ctxhttp.Post(ctx, nil, url, "application/json", req))
		if err != nil {
			log.Error("Err while publishing metrics: %s", err)
		}

		if retry {
			log.Error("err while operating will retry...")
			continue
		}

		break
	}

	return err
}

func shouldRetry(resp *http.Response, err error) (bool, error) {
	if err != nil {
		return true, err
	}
	// Check the response code. We retry on 500-range responses to allow
	// the server time to recover, as 500's are typically not permanent
	// errors and may relate to outages on the server side. This will catch
	// invalid response codes as well, like 0 and 999.
	if resp.StatusCode == 0 || resp.StatusCode >= 500 {
		return true, nil
	}

	return false, nil
}

func newBackoff() backoff.BackOff {
	bo := backoff.NewExponentialBackOff()
	bo.InitialInterval = time.Millisecond * 250
	bo.MaxInterval = time.Second * 1
	bo.MaxElapsedTime = time.Minute * 2
	return bo
}
<commit_msg>kites/metrics: seek to first part whenever we try resending the body<commit_after>package metrics

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"time"

	"golang.org/x/net/context/ctxhttp"

	"github.com/cenkalti/backoff"
	"github.com/koding/kite"
)

// Publisher sends DD events to agent.
type Publisher struct {
	conn            io.WriteCloser
	metricsEndpoint string
}

// NewPublisherWithConn creates new Publisher for sending the incoming events to
// DataDog agent.
func NewPublisherWithConn(conn io.WriteCloser, metricsEndpoint string) *Publisher {
	return &Publisher{
		conn:            conn,
		metricsEndpoint: metricsEndpoint,
	}
}

// NewPublisher creates a new publisher with default DD agent address.
func NewPublisher(metricsEndpoint string) (*Publisher, error) {
	udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8125")
	if err != nil {
		return nil, err
	}

	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		return nil, err
	}

	return NewPublisherWithConn(conn, metricsEndpoint), nil
}

// PublishRequest represents a request type for "metrics.publish" kloud's
// kite method.
type PublishRequest struct {
	Data GzippedPayload `json:"data"`
}

// Pattern returns the endpoint name for kite.
func (Publisher) Pattern() string {
	return "metrics.publish"
}

// Publish is a kite.Handler for "metrics.publish" kite method.
func (p *Publisher) Publish(r *kite.Request) (interface{}, error) {
	var req PublishRequest
	argOne := r.Args.One()
	if err := argOne.Unmarshal(&req); err != nil {
		return nil, err
	}

	for _, data := range req.Data {
		_, err := p.conn.Write(data)
		if err == nil {
			continue
		}

		if errP, ok := err.(*net.OpError); ok {
			if _, ok := errP.Err.(*os.SyscallError); ok {
				continue
			}
		}

		return nil, err
	}

	go publishToCountly(p.metricsEndpoint, argOne.Raw, r.LocalKite.Log)

	return nil, nil
}

// Close closes the underlying connection.
func (p *Publisher) Close() error {
	return p.conn.Close()
}

func publishToCountly(url string, r []byte, log kite.Logger) error {
	req := bytes.NewReader(r)

	ticker := backoff.NewTicker(newBackoff())
	defer ticker.Stop()

	var err error
	var retry bool
	for range ticker.C {
		// each request should not take more than 5 sec
		ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
		defer cancel()

		if _, err := req.Seek(0, io.SeekStart); err != nil {
			return fmt.Errorf("failed to seek body: %v", err)
		}

		retry, err = shouldRetry(ctxhttp.Post(ctx, nil, url, "application/json", req))
		if err != nil {
			log.Error("Err while publishing metrics: %s", err)
		}

		if retry {
			log.Error("err while operating will retry...")
			continue
		}

		break
	}

	return err
}

func shouldRetry(resp *http.Response, err error) (bool, error) {
	if err != nil {
		return true, err
	}
	// Check the response code. We retry on 500-range responses to allow
	// the server time to recover, as 500's are typically not permanent
	// errors and may relate to outages on the server side. This will catch
	// invalid response codes as well, like 0 and 999.
	if resp.StatusCode == 0 || resp.StatusCode >= 500 {
		return true, nil
	}

	return false, nil
}

func newBackoff() backoff.BackOff {
	bo := backoff.NewExponentialBackOff()
	bo.InitialInterval = time.Millisecond * 250
	bo.MaxInterval = time.Second * 1
	bo.MaxElapsedTime = time.Minute * 2
	return bo
}
<|endoftext|>


<commit_before>package main

import (
	"math/rand"
	"socialapi/models"
	"testing"

	. "github.com/smartystreets/goconvey/convey"
)

func TestGroupChannel(t *testing.T) {
	Convey("while testing group channel", t, func() {

		Convey("channel should be there", func() {
			account := models.NewAccount()
			account.OldId = AccountOldId.Hex()
			account, err := createAccount(account)
			So(err, ShouldBeNil)
			So(account, ShouldNotBeNil)

			channel1, err := createChannelByGroupNameAndType(account.Id, "testgroup", models.Channel_TYPE_GROUP)
			So(err, ShouldBeNil)
			So(channel1, ShouldNotBeNil)

			channel2, err := createChannelByGroupNameAndType(account.Id, "testgroup", models.Channel_TYPE_GROUP)
			So(err, ShouldBeNil)
			So(channel2, ShouldNotBeNil)

			So(channel1.Id, ShouldEqual, channel2.Id)
		})

		Convey("owner should be able to update it", func() {
			account := models.NewAccount()
			account.OldId = AccountOldId.Hex()
			account, err := createAccount(account)
			So(err, ShouldBeNil)
			So(account, ShouldNotBeNil)

			channel1, err := createChannelByGroupNameAndType(account.Id, "testgroup", models.Channel_TYPE_GROUP)
			So(err, ShouldBeNil)
			So(channel1, ShouldNotBeNil)
			// fetching channel returns creator id
			_, err = updateChannel(channel1)
			So(err, ShouldBeNil)
		})

		Convey("normal user should not be able to update it", func() {
			account := models.NewAccount()
			account.OldId = AccountOldId.Hex()
			account, err := createAccount(account)
			So(err, ShouldBeNil)
			So(account, ShouldNotBeNil)

			channel1, err := createChannelByGroupNameAndType(account.Id, "testgroup", models.Channel_TYPE_GROUP)
			So(err, ShouldBeNil)
			So(channel1, ShouldNotBeNil)

			channel1.CreatorId = rand.Int63()
			_, err = updateChannel(channel1)
			So(err, ShouldNotBeNil)
		})

		Convey("owner cant delete it", func() {
			account := models.NewAccount()
			account.OldId = AccountOldId.Hex()
			account, err := createAccount(account)
			So(err, ShouldBeNil)
			So(account, ShouldNotBeNil)

			channel1, err := createChannelByGroupNameAndType(account.Id, "testgroup", models.Channel_TYPE_GROUP)
			So(err, ShouldBeNil)
			So(channel1, ShouldNotBeNil)

			err = deleteChannel(account.Id, channel1.Id)
			So(err, ShouldNotBeNil)
		})

		Convey("normal user cant delete it", func() {
			account := models.NewAccount()
			account.OldId = AccountOldId.Hex()
			account, err := createAccount(account)
			So(err, ShouldBeNil)
			So(account, ShouldNotBeNil)

			channel1, err := createChannelByGroupNameAndType(account.Id, "testgroup", models.Channel_TYPE_GROUP)
			So(err, ShouldBeNil)
			So(channel1, ShouldNotBeNil)

			err = deleteChannel(rand.Int63(), channel1.Id)
			So(err, ShouldNotBeNil)
		})

		Convey("member can post status update", nil)

		Convey("non-member can not post status update", nil)

		Convey("member can post status update", nil)

		Convey("non-member can not post status update", nil)

	})
}
<commit_msg>Social: add a to-do test<commit_after>package main

import (
	"math/rand"
	"socialapi/models"
	"testing"

	. "github.com/smartystreets/goconvey/convey"
)

func TestGroupChannel(t *testing.T) {
	Convey("while testing group channel", t, func() {

		Convey("channel should be there", func() {
			account := models.NewAccount()
			account.OldId = AccountOldId.Hex()
			account, err := createAccount(account)
			So(err, ShouldBeNil)
			So(account, ShouldNotBeNil)

			channel1, err := createChannelByGroupNameAndType(account.Id, "testgroup", models.Channel_TYPE_GROUP)
			So(err, ShouldBeNil)
			So(channel1, ShouldNotBeNil)

			channel2, err := createChannelByGroupNameAndType(account.Id, "testgroup", models.Channel_TYPE_GROUP)
			So(err, ShouldBeNil)
			So(channel2, ShouldNotBeNil)

			So(channel1.Id, ShouldEqual, channel2.Id)
		})

		Convey("owner should be able to update it", func() {
			account := models.NewAccount()
			account.OldId = AccountOldId.Hex()
			account, err := createAccount(account)
			So(err, ShouldBeNil)
			So(account, ShouldNotBeNil)

			channel1, err := createChannelByGroupNameAndType(account.Id, "testgroup", models.Channel_TYPE_GROUP)
			So(err, ShouldBeNil)
			So(channel1, ShouldNotBeNil)
			// fetching channel returns creator id
			_, err = updateChannel(channel1)
			So(err, ShouldBeNil)
		})

		Convey("owner should only be able to update name and purpose of the channel", nil)

		Convey("normal user should not be able to update it", func() {
			account := models.NewAccount()
			account.OldId = AccountOldId.Hex()
			account, err := createAccount(account)
			So(err, ShouldBeNil)
			So(account, ShouldNotBeNil)

			channel1, err := createChannelByGroupNameAndType(account.Id, "testgroup", models.Channel_TYPE_GROUP)
			So(err, ShouldBeNil)
			So(channel1, ShouldNotBeNil)

			channel1.CreatorId = rand.Int63()
			_, err = updateChannel(channel1)
			So(err, ShouldNotBeNil)
		})

		Convey("owner cant delete it", func() {
			account := models.NewAccount()
			account.OldId = AccountOldId.Hex()
			account, err := createAccount(account)
			So(err, ShouldBeNil)
			So(account, ShouldNotBeNil)

			channel1, err := createChannelByGroupNameAndType(account.Id, "testgroup", models.Channel_TYPE_GROUP)
			So(err, ShouldBeNil)
			So(channel1, ShouldNotBeNil)

			err = deleteChannel(account.Id, channel1.Id)
			So(err, ShouldNotBeNil)
		})

		Convey("normal user cant delete it", func() {
			account := models.NewAccount()
			account.OldId = AccountOldId.Hex()
			account, err := createAccount(account)
			So(err, ShouldBeNil)
			So(account, ShouldNotBeNil)

			channel1, err := createChannelByGroupNameAndType(account.Id, "testgroup", models.Channel_TYPE_GROUP)
			So(err, ShouldBeNil)
			So(channel1, ShouldNotBeNil)

			err = deleteChannel(rand.Int63(), channel1.Id)
			So(err, ShouldNotBeNil)
		})

		Convey("member can post status update", nil)

		Convey("non-member can not post status update", nil)

		Convey("member can post status update", nil)

		Convey("non-member can not post status update", nil)

	})
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"sync"
	"time"

	"github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
	"github.com/Microsoft/hcsshim/internal/hcs"
	"github.com/Microsoft/hcsshim/internal/uvm"
	eventstypes "github.com/containerd/containerd/api/events"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/runtime"
	"github.com/containerd/containerd/runtime/v2/task"
	"github.com/opencontainers/runtime-spec/specs-go"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// newWcowPodSandboxTask creates a fake WCOW task with a fake WCOW `init`
// process as a performance optimization rather than creating an actual
// container and process since it is not needed to hold open any namespaces like
// the equivalent on Linux.
//
// It is assumed that this is the only fake WCOW task and that this task owns
// `parent`. When the fake WCOW `init` process exits via `Signal` `parent` will
// be forcibly closed by this task.
func newWcowPodSandboxTask(ctx context.Context, events publisher, id, bundle string, parent *uvm.UtilityVM) shimTask {
	logrus.WithFields(logrus.Fields{
		"tid": id,
	}).Debug("newWcowPodSandboxTask")

	wpst := &wcowPodSandboxTask{
		events: events,
		id:     id,
		init:   newWcowPodSandboxExec(ctx, events, id, bundle),
		closed: make(chan struct{}),
	}
	if parent != nil {
		// We have (and own) a parent UVM. Listen for its exit and forcibly
		// close this task. This is not expected but in the event of a UVM crash
		// we need to handle this case.
		go func() {
			werr := parent.Wait()
			if werr != nil && !hcs.IsAlreadyClosed(werr) {
				logrus.WithFields(logrus.Fields{
					"tid":           id,
					logrus.ErrorKey: werr,
				}).Error("newWcowPodSandboxTask - UVM Wait failed")
			}
			// The UVM came down. Force transition the init task (if it wasn't
			// already) to unblock any waiters since the platform wont send any
			// events for this fake process.
			wpst.init.ForceExit(1)

			// Close the host and event the exit.
			wpst.close()
		}()
	}
	// In the normal case the `Signal` call from the caller killed this fake
	// init process.
	go func() {
		// Wait for it to exit on its own
		wpst.init.Wait(context.Background())

		// Close the host and event the exit
		wpst.close()
	}()
	return wpst
}

var _ = (shimTask)(&wcowPodSandboxTask{})

// wcowPodSandboxTask is a special task type that actually holds no real
// resources due to various design differences between Linux/Windows.
//
// For more information on why we can have this stub and in what invariant cases
// it makes sense please see `wcowPodExec`.
//
// Note: If this is a Hypervisor Isolated WCOW sandbox then we do actually track
// the lifetime of the UVM for a WCOW POD but the UVM will have no WCOW
// container/exec init representing the actual POD Sandbox task.
type wcowPodSandboxTask struct {
	events publisher
	// id is the id of this task when it is created.
	//
	// It MUST be treated as read only in the liftetime of the task.
	id string
	// init is the init process of the container.
	//
	// Note: the invariant `container state == init.State()` MUST be true. IE:
	// if the init process exits the container as a whole and all exec's MUST
	// exit.
	//
	// It MUST be treated as read only in the lifetime of the task.
	init *wcowPodSandboxExec
	// host is the hosting VM for this task if hypervisor isolated. If
	// `host==nil` this is an Argon task so no UVM cleanup is required.
	host *uvm.UtilityVM

	closed    chan struct{}
	closeOnce sync.Once
}

func (wpst *wcowPodSandboxTask) ID() string {
	return wpst.id
}

func (wpst *wcowPodSandboxTask) CreateExec(ctx context.Context, req *task.ExecProcessRequest, s *specs.Process) error {
	logrus.WithFields(logrus.Fields{
		"tid": wpst.id,
		"eid": req.ID,
	}).Debug("wcowPodSandboxTask::CreateExec")

	return errors.Wrap(errdefs.ErrNotImplemented, "WCOW Pod task should never issue exec")
}

func (wpst *wcowPodSandboxTask) GetExec(eid string) (shimExec, error) {
	if eid == "" {
		return wpst.init, nil
	}
	// Cannot exec in an a WCOW sandbox container so all non-init calls fail here.
	return nil, errors.Wrapf(errdefs.ErrNotFound, "exec: '%s' in task: '%s' not found", eid, wpst.id)
}

func (wpst *wcowPodSandboxTask) KillExec(ctx context.Context, eid string, signal uint32, all bool) error {
	logrus.WithFields(logrus.Fields{
		"tid":    wpst.id,
		"eid":    eid,
		"signal": signal,
		"all":    all,
	}).Debug("wcowPodSandboxTask::KillExec")

	e, err := wpst.GetExec(eid)
	if err != nil {
		return err
	}
	if all && eid != "" {
		return errors.Wrapf(errdefs.ErrFailedPrecondition, "cannot signal all for non-empty exec: '%s'", eid)
	}
	err = e.Kill(ctx, signal)
	if err != nil {
		return err
	}
	return nil
}

func (wpst *wcowPodSandboxTask) DeleteExec(ctx context.Context, eid string) (int, uint32, time.Time, error) {
	logrus.WithFields(logrus.Fields{
		"tid": wpst.id,
		"eid": eid,
	}).Debug("wcowPodSandboxTask::DeleteExec")

	e, err := wpst.GetExec(eid)
	if err != nil {
		return 0, 0, time.Time{}, err
	}
	state := e.State()
	if state != shimExecStateExited {
		return 0, 0, time.Time{}, newExecInvalidStateError(wpst.id, eid, state, "delete")
	}
	status := e.Status()

	// Publish the deleted event
	wpst.events(
		runtime.TaskDeleteEventTopic,
		&eventstypes.TaskDelete{
			ContainerID: wpst.id,
			ID:          eid,
			Pid:         status.Pid,
			ExitStatus:  status.ExitStatus,
			ExitedAt:    status.ExitedAt,
		})

	return int(status.Pid), status.ExitStatus, status.ExitedAt, nil
}

func (wpst *wcowPodSandboxTask) Pids(ctx context.Context) ([]options.ProcessDetails, error) {
	logrus.WithFields(logrus.Fields{
		"tid": wpst.id,
	}).Debug("wcowPodSandboxTask::Pids")

	return []options.ProcessDetails{
		{
			ProcessID: uint32(wpst.init.Pid()),
			ExecID:    wpst.init.ID(),
		},
	}, nil
}

func (wpst *wcowPodSandboxTask) Wait(ctx context.Context) *task.StateResponse {
	<-wpst.closed
	return wpst.init.Wait(ctx)
}

// close safely closes the hosting UVM. Because of the specialty of this task it
// is assumed that this is always the owner of `wpst.host`. Once closed and all
// resources released it events the `runtime.TaskExitEventTopic` for all
// upstream listeners.
//
// This call is idempotent and safe to call multiple times.
func (wpst *wcowPodSandboxTask) close() {
	wpst.closeOnce.Do(func() {
		if err := wpst.host.Close(); !hcs.IsAlreadyClosed(err) {
			logrus.WithFields(logrus.Fields{
				"tid":           wpst.id,
				logrus.ErrorKey: err,
			}).Error("wcowPodSandboxTask::close - failed host vm shutdown")
		}
		// Send the `init` exec exit notification always.
		exit := wpst.init.Status()
		wpst.events(
			runtime.TaskExitEventTopic,
			&eventstypes.TaskExit{
				ContainerID: wpst.id,
				ID:          exit.ID,
				Pid:         uint32(exit.Pid),
				ExitStatus:  exit.ExitStatus,
				ExitedAt:    exit.ExitedAt,
			})
		close(wpst.closed)
	})
}
<commit_msg>Fix panic in WCOW Hypervisor activations<commit_after>package main

import (
	"context"
	"sync"
	"time"

	"github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
	"github.com/Microsoft/hcsshim/internal/hcs"
	"github.com/Microsoft/hcsshim/internal/uvm"
	eventstypes "github.com/containerd/containerd/api/events"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/runtime"
	"github.com/containerd/containerd/runtime/v2/task"
	"github.com/opencontainers/runtime-spec/specs-go"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// newWcowPodSandboxTask creates a fake WCOW task with a fake WCOW `init`
// process as a performance optimization rather than creating an actual
// container and process since it is not needed to hold open any namespaces like
// the equivalent on Linux.
//
// It is assumed that this is the only fake WCOW task and that this task owns
// `parent`. When the fake WCOW `init` process exits via `Signal` `parent` will
// be forcibly closed by this task.
func newWcowPodSandboxTask(ctx context.Context, events publisher, id, bundle string, parent *uvm.UtilityVM) shimTask {
	logrus.WithFields(logrus.Fields{
		"tid": id,
	}).Debug("newWcowPodSandboxTask")

	wpst := &wcowPodSandboxTask{
		events: events,
		id:     id,
		init:   newWcowPodSandboxExec(ctx, events, id, bundle),
		host:   parent,
		closed: make(chan struct{}),
	}
	if parent != nil {
		// We have (and own) a parent UVM. Listen for its exit and forcibly
		// close this task. This is not expected but in the event of a UVM crash
		// we need to handle this case.
		go func() {
			werr := parent.Wait()
			if werr != nil && !hcs.IsAlreadyClosed(werr) {
				logrus.WithFields(logrus.Fields{
					"tid":           id,
					logrus.ErrorKey: werr,
				}).Error("newWcowPodSandboxTask - UVM Wait failed")
			}
			// The UVM came down. Force transition the init task (if it wasn't
			// already) to unblock any waiters since the platform wont send any
			// events for this fake process.
			wpst.init.ForceExit(1)

			// Close the host and event the exit.
			wpst.close()
		}()
	}
	// In the normal case the `Signal` call from the caller killed this fake
	// init process.
	go func() {
		// Wait for it to exit on its own
		wpst.init.Wait(context.Background())

		// Close the host and event the exit
		wpst.close()
	}()
	return wpst
}

var _ = (shimTask)(&wcowPodSandboxTask{})

// wcowPodSandboxTask is a special task type that actually holds no real
// resources due to various design differences between Linux/Windows.
//
// For more information on why we can have this stub and in what invariant cases
// it makes sense please see `wcowPodExec`.
//
// Note: If this is a Hypervisor Isolated WCOW sandbox then we do actually track
// the lifetime of the UVM for a WCOW POD but the UVM will have no WCOW
// container/exec init representing the actual POD Sandbox task.
type wcowPodSandboxTask struct {
	events publisher
	// id is the id of this task when it is created.
	//
	// It MUST be treated as read only in the liftetime of the task.
	id string
	// init is the init process of the container.
	//
	// Note: the invariant `container state == init.State()` MUST be true. IE:
	// if the init process exits the container as a whole and all exec's MUST
	// exit.
	//
	// It MUST be treated as read only in the lifetime of the task.
	init *wcowPodSandboxExec
	// host is the hosting VM for this task if hypervisor isolated. If
	// `host==nil` this is an Argon task so no UVM cleanup is required.
	host *uvm.UtilityVM

	closed    chan struct{}
	closeOnce sync.Once
}

func (wpst *wcowPodSandboxTask) ID() string {
	return wpst.id
}

func (wpst *wcowPodSandboxTask) CreateExec(ctx context.Context, req *task.ExecProcessRequest, s *specs.Process) error {
	logrus.WithFields(logrus.Fields{
		"tid": wpst.id,
		"eid": req.ID,
	}).Debug("wcowPodSandboxTask::CreateExec")

	return errors.Wrap(errdefs.ErrNotImplemented, "WCOW Pod task should never issue exec")
}

func (wpst *wcowPodSandboxTask) GetExec(eid string) (shimExec, error) {
	if eid == "" {
		return wpst.init, nil
	}
	// Cannot exec in an a WCOW sandbox container so all non-init calls fail here.
	return nil, errors.Wrapf(errdefs.ErrNotFound, "exec: '%s' in task: '%s' not found", eid, wpst.id)
}

func (wpst *wcowPodSandboxTask) KillExec(ctx context.Context, eid string, signal uint32, all bool) error {
	logrus.WithFields(logrus.Fields{
		"tid":    wpst.id,
		"eid":    eid,
		"signal": signal,
		"all":    all,
	}).Debug("wcowPodSandboxTask::KillExec")

	e, err := wpst.GetExec(eid)
	if err != nil {
		return err
	}
	if all && eid != "" {
		return errors.Wrapf(errdefs.ErrFailedPrecondition, "cannot signal all for non-empty exec: '%s'", eid)
	}
	err = e.Kill(ctx, signal)
	if err != nil {
		return err
	}
	return nil
}

func (wpst *wcowPodSandboxTask) DeleteExec(ctx context.Context, eid string) (int, uint32, time.Time, error) {
	logrus.WithFields(logrus.Fields{
		"tid": wpst.id,
		"eid": eid,
	}).Debug("wcowPodSandboxTask::DeleteExec")

	e, err := wpst.GetExec(eid)
	if err != nil {
		return 0, 0, time.Time{}, err
	}
	state := e.State()
	if state != shimExecStateExited {
		return 0, 0, time.Time{}, newExecInvalidStateError(wpst.id, eid, state, "delete")
	}
	status := e.Status()

	// Publish the deleted event
	wpst.events(
		runtime.TaskDeleteEventTopic,
		&eventstypes.TaskDelete{
			ContainerID: wpst.id,
			ID:          eid,
			Pid:         status.Pid,
			ExitStatus:  status.ExitStatus,
			ExitedAt:    status.ExitedAt,
		})

	return int(status.Pid), status.ExitStatus, status.ExitedAt, nil
}

func (wpst *wcowPodSandboxTask) Pids(ctx context.Context) ([]options.ProcessDetails, error) {
	logrus.WithFields(logrus.Fields{
		"tid": wpst.id,
	}).Debug("wcowPodSandboxTask::Pids")

	return []options.ProcessDetails{
		{
			ProcessID: uint32(wpst.init.Pid()),
			ExecID:    wpst.init.ID(),
		},
	}, nil
}

func (wpst *wcowPodSandboxTask) Wait(ctx context.Context) *task.StateResponse {
	<-wpst.closed
	return wpst.init.Wait(ctx)
}

// close safely closes the hosting UVM. Because of the specialty of this task it
// is assumed that this is always the owner of `wpst.host`. Once closed and all
// resources released it events the `runtime.TaskExitEventTopic` for all
// upstream listeners.
//
// This call is idempotent and safe to call multiple times.
func (wpst *wcowPodSandboxTask) close() {
	wpst.closeOnce.Do(func() {
		logrus.WithFields(logrus.Fields{
			"tid": wpst.id,
		}).Debug("wcowPodSandboxTask::close")

		if wpst.host != nil {
			if err := wpst.host.Close(); !hcs.IsAlreadyClosed(err) {
				logrus.WithFields(logrus.Fields{
					"tid":           wpst.id,
					logrus.ErrorKey: err,
				}).Error("wcowPodSandboxTask::close - failed host vm shutdown")
			}
		}
		// Send the `init` exec exit notification always.
		exit := wpst.init.Status()
		wpst.events(
			runtime.TaskExitEventTopic,
			&eventstypes.TaskExit{
				ContainerID: wpst.id,
				ID:          exit.ID,
				Pid:         uint32(exit.Pid),
				ExitStatus:  exit.ExitStatus,
				ExitedAt:    exit.ExitedAt,
			})
		close(wpst.closed)
	})
}
<|endoftext|>


<commit_before>package pgx_test

import (
	"github.com/jackc/pgx"
	"testing"
)

func TestHstoreTranscode(t *testing.T) {
	t.Parallel()

	conn := mustConnect(t, *defaultConnConfig)
	defer closeConn(t, conn)

	type test struct {
		hstore      pgx.Hstore
		description string
	}

	tests := []test{
		{pgx.Hstore{}, "empty"},
		{pgx.Hstore{"foo": "bar"}, "single key/value"},
		{pgx.Hstore{"foo": "bar", "baz": "quz"}, "multiple key/values"},
		{pgx.Hstore{"NULL": "bar"}, `string "NULL" key`},
		{pgx.Hstore{"foo": "NULL"}, `string "NULL" value`},
	}

	specialStringTests := []struct {
		input       string
		description string
	}{
		{`"`, `double quote (")`},
		{`'`, `single quote (')`},
		{`\`, `backslash (\)`},
		{`\\`, `multiple backslashes (\\)`},
		{`=>`, `separator (=>)`},
		{` `, `space`},
		{`\ / / \\ => " ' " '`, `multiple special characters`},
	}
	for _, sst := range specialStringTests {
		tests = append(tests, test{pgx.Hstore{sst.input + "foo": "bar"}, "key with " + sst.description + " at beginning"})
		tests = append(tests, test{pgx.Hstore{"foo" + sst.input + "foo": "bar"}, "key with " + sst.description + " in middle"})
		tests = append(tests, test{pgx.Hstore{"foo" + sst.input: "bar"}, "key with " + sst.description + " at end"})
		tests = append(tests, test{pgx.Hstore{sst.input: "bar"}, "key is " + sst.description})

		tests = append(tests, test{pgx.Hstore{"foo": sst.input + "bar"}, "value with " + sst.description + " at beginning"})
		tests = append(tests, test{pgx.Hstore{"foo": "bar" + sst.input + "bar"}, "value with " + sst.description + " in middle"})
		tests = append(tests, test{pgx.Hstore{"foo": "bar" + sst.input}, "value with " + sst.description + " at end"})
		tests = append(tests, test{pgx.Hstore{"foo": sst.input}, "value is " + sst.description})
	}

	for _, tt := range tests {
		var result pgx.Hstore
		err := conn.QueryRow("select $1::hstore", tt.hstore).Scan(&result)
		if err != nil {
			t.Errorf(`%s: QueryRow.Scan returned an error: %v`, tt.description, err)
		}

		for key, inValue := range tt.hstore {
			outValue, ok := result[key]
			if ok {
				if inValue != outValue {
					t.Errorf(`%s: Key %s mismatch - expected %s, received %s`, tt.description, key, inValue, outValue)
				}
			} else {
				t.Errorf(`%s: Missing key %s`, tt.description, key)
			}
		}

		ensureConnValid(t, conn)
	}
}

func TestNullHstoreTranscode(t *testing.T) {
	t.Parallel()

	conn := mustConnect(t, *defaultConnConfig)
	defer closeConn(t, conn)

	type test struct {
		nullHstore  pgx.NullHstore
		description string
	}

	tests := []test{
		{pgx.NullHstore{}, "null"},
		{pgx.NullHstore{Valid: true}, "empty"},
		{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{"bar", true}},
			Valid:  true},
			"single key/value"},
		{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{"bar", true}, "baz": pgx.NullString{"quz", true}},
			Valid:  true},
			"multiple key/values"},
		{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"NULL": pgx.NullString{"bar", true}},
			Valid:  true},
			`string "NULL" key`},
		{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{"NULL", true}},
			Valid:  true},
			`string "NULL" value`},
		{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{"", false}},
			Valid:  true},
			`NULL value`},
	}

	specialStringTests := []struct {
		input       string
		description string
	}{
		{`"`, `double quote (")`},
		{`'`, `single quote (')`},
		{`\`, `backslash (\)`},
		{`\\`, `multiple backslashes (\\)`},
		{`=>`, `separator (=>)`},
		{` `, `space`},
		{`\ / / \\ => " ' " '`, `multiple special characters`},
	}
	for _, sst := range specialStringTests {
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{sst.input + "foo": pgx.NullString{"bar", true}},
			Valid:  true},
			"key with " + sst.description + " at beginning"})
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo" + sst.input + "foo": pgx.NullString{"bar", true}},
			Valid:  true},
			"key with " + sst.description + " in middle"})
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo" + sst.input: pgx.NullString{"bar", true}},
			Valid:  true},
			"key with " + sst.description + " at end"})
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{sst.input: pgx.NullString{"bar", true}},
			Valid:  true},
			"key is " + sst.description})

		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{sst.input + "bar", true}},
			Valid:  true},
			"value with " + sst.description + " at beginning"})
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{"bar" + sst.input + "bar", true}},
			Valid:  true},
			"value with " + sst.description + " in middle"})
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{"bar" + sst.input, true}},
			Valid:  true},
			"value with " + sst.description + " at end"})
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{sst.input, true}},
			Valid:  true},
			"value is " + sst.description})
	}

	for _, tt := range tests {
		var result pgx.NullHstore
		err := conn.QueryRow("select $1::hstore", tt.nullHstore).Scan(&result)
		if err != nil {
			t.Errorf(`%s: QueryRow.Scan returned an error: %v`, tt.description, err)
		}

		if result.Valid != tt.nullHstore.Valid {
			t.Errorf(`%s: Valid mismatch - expected %v, received %v`, tt.description, tt.nullHstore.Valid, result.Valid)
		}

		for key, inValue := range tt.nullHstore.Hstore {
			outValue, ok := result.Hstore[key]
			if ok {
				if inValue != outValue {
					t.Errorf(`%s: Key %s mismatch - expected %v, received %v`, tt.description, key, inValue, outValue)
				}
			} else {
				t.Errorf(`%s: Missing key %s`, tt.description, key)
			}
		}

		ensureConnValid(t, conn)
	}
}
<commit_msg>Fix go vet identified composite leteral uses unkeyed fields<commit_after>package pgx_test

import (
	"github.com/jackc/pgx"
	"testing"
)

func TestHstoreTranscode(t *testing.T) {
	t.Parallel()

	conn := mustConnect(t, *defaultConnConfig)
	defer closeConn(t, conn)

	type test struct {
		hstore      pgx.Hstore
		description string
	}

	tests := []test{
		{pgx.Hstore{}, "empty"},
		{pgx.Hstore{"foo": "bar"}, "single key/value"},
		{pgx.Hstore{"foo": "bar", "baz": "quz"}, "multiple key/values"},
		{pgx.Hstore{"NULL": "bar"}, `string "NULL" key`},
		{pgx.Hstore{"foo": "NULL"}, `string "NULL" value`},
	}

	specialStringTests := []struct {
		input       string
		description string
	}{
		{`"`, `double quote (")`},
		{`'`, `single quote (')`},
		{`\`, `backslash (\)`},
		{`\\`, `multiple backslashes (\\)`},
		{`=>`, `separator (=>)`},
		{` `, `space`},
		{`\ / / \\ => " ' " '`, `multiple special characters`},
	}
	for _, sst := range specialStringTests {
		tests = append(tests, test{pgx.Hstore{sst.input + "foo": "bar"}, "key with " + sst.description + " at beginning"})
		tests = append(tests, test{pgx.Hstore{"foo" + sst.input + "foo": "bar"}, "key with " + sst.description + " in middle"})
		tests = append(tests, test{pgx.Hstore{"foo" + sst.input: "bar"}, "key with " + sst.description + " at end"})
		tests = append(tests, test{pgx.Hstore{sst.input: "bar"}, "key is " + sst.description})

		tests = append(tests, test{pgx.Hstore{"foo": sst.input + "bar"}, "value with " + sst.description + " at beginning"})
		tests = append(tests, test{pgx.Hstore{"foo": "bar" + sst.input + "bar"}, "value with " + sst.description + " in middle"})
		tests = append(tests, test{pgx.Hstore{"foo": "bar" + sst.input}, "value with " + sst.description + " at end"})
		tests = append(tests, test{pgx.Hstore{"foo": sst.input}, "value is " + sst.description})
	}

	for _, tt := range tests {
		var result pgx.Hstore
		err := conn.QueryRow("select $1::hstore", tt.hstore).Scan(&result)
		if err != nil {
			t.Errorf(`%s: QueryRow.Scan returned an error: %v`, tt.description, err)
		}

		for key, inValue := range tt.hstore {
			outValue, ok := result[key]
			if ok {
				if inValue != outValue {
					t.Errorf(`%s: Key %s mismatch - expected %s, received %s`, tt.description, key, inValue, outValue)
				}
			} else {
				t.Errorf(`%s: Missing key %s`, tt.description, key)
			}
		}

		ensureConnValid(t, conn)
	}
}

func TestNullHstoreTranscode(t *testing.T) {
	t.Parallel()

	conn := mustConnect(t, *defaultConnConfig)
	defer closeConn(t, conn)

	type test struct {
		nullHstore  pgx.NullHstore
		description string
	}

	tests := []test{
		{pgx.NullHstore{}, "null"},
		{pgx.NullHstore{Valid: true}, "empty"},
		{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{String: "bar", Valid: true}},
			Valid:  true},
			"single key/value"},
		{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{String: "bar", Valid: true}, "baz": pgx.NullString{String: "quz", Valid: true}},
			Valid:  true},
			"multiple key/values"},
		{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"NULL": pgx.NullString{String: "bar", Valid: true}},
			Valid:  true},
			`string "NULL" key`},
		{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{String: "NULL", Valid: true}},
			Valid:  true},
			`string "NULL" value`},
		{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{String: "", Valid: false}},
			Valid:  true},
			`NULL value`},
	}

	specialStringTests := []struct {
		input       string
		description string
	}{
		{`"`, `double quote (")`},
		{`'`, `single quote (')`},
		{`\`, `backslash (\)`},
		{`\\`, `multiple backslashes (\\)`},
		{`=>`, `separator (=>)`},
		{` `, `space`},
		{`\ / / \\ => " ' " '`, `multiple special characters`},
	}
	for _, sst := range specialStringTests {
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{sst.input + "foo": pgx.NullString{String: "bar", Valid: true}},
			Valid:  true},
			"key with " + sst.description + " at beginning"})
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo" + sst.input + "foo": pgx.NullString{String: "bar", Valid: true}},
			Valid:  true},
			"key with " + sst.description + " in middle"})
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo" + sst.input: pgx.NullString{String: "bar", Valid: true}},
			Valid:  true},
			"key with " + sst.description + " at end"})
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{sst.input: pgx.NullString{String: "bar", Valid: true}},
			Valid:  true},
			"key is " + sst.description})

		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{String: sst.input + "bar", Valid: true}},
			Valid:  true},
			"value with " + sst.description + " at beginning"})
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{String: "bar" + sst.input + "bar", Valid: true}},
			Valid:  true},
			"value with " + sst.description + " in middle"})
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{String: "bar" + sst.input, Valid: true}},
			Valid:  true},
			"value with " + sst.description + " at end"})
		tests = append(tests, test{pgx.NullHstore{
			Hstore: map[string]pgx.NullString{"foo": pgx.NullString{String: sst.input, Valid: true}},
			Valid:  true},
			"value is " + sst.description})
	}

	for _, tt := range tests {
		var result pgx.NullHstore
		err := conn.QueryRow("select $1::hstore", tt.nullHstore).Scan(&result)
		if err != nil {
			t.Errorf(`%s: QueryRow.Scan returned an error: %v`, tt.description, err)
		}

		if result.Valid != tt.nullHstore.Valid {
			t.Errorf(`%s: Valid mismatch - expected %v, received %v`, tt.description, tt.nullHstore.Valid, result.Valid)
		}

		for key, inValue := range tt.nullHstore.Hstore {
			outValue, ok := result.Hstore[key]
			if ok {
				if inValue != outValue {
					t.Errorf(`%s: Key %s mismatch - expected %v, received %v`, tt.description, key, inValue, outValue)
				}
			} else {
				t.Errorf(`%s: Missing key %s`, tt.description, key)
			}
		}

		ensureConnValid(t, conn)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Jetstack cert-manager contributors.

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 validation

import (
	"fmt"
	"net"

	"k8s.io/apimachinery/pkg/util/validation/field"

	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
)

// Validation functions for cert-manager v1alpha1 Certificate types

func ValidateCertificateSpec(crt *v1alpha1.CertificateSpec, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}
	if crt.SecretName == "" {
		el = append(el, field.Required(fldPath.Child("secretName"), "must be specified"))
	}

	el = append(el, validateIssuerRef(crt.IssuerRef, fldPath)...)

	if len(crt.CommonName) == 0 && len(crt.DNSNames) == 0 {
		el = append(el, field.Required(fldPath.Child("dnsNames"), "at least one dnsName is required if commonName is not set"))
	}
	// if a common name has been specified, ensure it is no longer than 64 chars
	if len(crt.CommonName) > 64 {
		el = append(el, field.TooLong(fldPath.Child("commonName"), crt.CommonName, 64))
	}
	// if the common name has *not* been specified, ensure the first dnsName is no longer than 64 chars
	// as it will be used as the commonName
	if crt.CommonName == "" && len(crt.DNSNames) > 0 && len(crt.DNSNames[0]) > 64 {
		el = append(el, field.TooLong(fldPath.Child("dnsNames").Index(0), crt.DNSNames[0], 64))
	}

	if len(crt.IPAddresses) > 0 {
		el = append(el, validateIPAddresses(crt, fldPath)...)
	}
	if crt.ACME != nil {
		el = append(el, validateACMEConfigForAllDNSNames(crt, fldPath)...)
		el = append(el, ValidateACMECertificateConfig(crt.ACME, fldPath.Child("acme"))...)
	}
	if crt.KeySize < 0 {
		el = append(el, field.Invalid(fldPath.Child("keySize"), crt.KeySize, "cannot be less than zero"))
	}
	switch crt.KeyAlgorithm {
	case v1alpha1.KeyAlgorithm(""):
	case v1alpha1.RSAKeyAlgorithm:
		if crt.KeySize > 0 && (crt.KeySize < 2048 || crt.KeySize > 8192) {
			el = append(el, field.Invalid(fldPath.Child("keySize"), crt.KeySize, "must be between 2048 & 8192 for rsa keyAlgorithm"))
		}
	case v1alpha1.ECDSAKeyAlgorithm:
		if crt.KeySize > 0 && crt.KeySize != 256 && crt.KeySize != 384 && crt.KeySize != 521 {
			el = append(el, field.NotSupported(fldPath.Child("keySize"), crt.KeySize, []string{"256", "384", "521"}))
		}
	default:
		el = append(el, field.Invalid(fldPath.Child("keyAlgorithm"), crt.KeyAlgorithm, "must be either empty or one of rsa or ecdsa"))
	}

	if crt.Duration != nil || crt.RenewBefore != nil {
		el = append(el, ValidateDuration(crt, fldPath)...)
	}

	switch crt.KeyEncoding {
	case v1alpha1.KeyEncoding(""), v1alpha1.PKCS1, v1alpha1.PKCS8:
	default:
		el = append(el, field.Invalid(fldPath.Child("keyEncoding"), crt.KeyEncoding, "must be either empty or one of pkcs1 or pkcs8"))
	}
	return el
}

// validateACMEConfigForAllDNSNames will ensure that if the provided Certificate
// specifies any ACME configuration, all domains listed on the Certificate have
// a configuration entry.
func validateACMEConfigForAllDNSNames(a *v1alpha1.CertificateSpec, fldPath *field.Path) field.ErrorList {
	if a.ACME == nil {
		return nil
	}
	el := field.ErrorList{}
	acmeFldPath := fldPath.Child("acme")
	errFn := func(s string) string {
		return fmt.Sprintf("no ACME solver configuration specified for domain %q", s)
	}
	if a.CommonName != "" {
		cfg := v1alpha1.ConfigForDomain(a.ACME.Config, a.CommonName)
		if cfg == nil || len(cfg.Domains) == 0 {
			el = append(el, field.Required(acmeFldPath.Child("config"), errFn(a.CommonName)))
		}
	}
	for _, d := range a.DNSNames {
		cfg := v1alpha1.ConfigForDomain(a.ACME.Config, d)
		if cfg == nil || len(cfg.Domains) == 0 {
			el = append(el, field.Required(acmeFldPath.Child("config"), errFn(d)))
		}
	}
	return el
}

func ValidateCertificate(crt *v1alpha1.Certificate) field.ErrorList {
	allErrs := ValidateCertificateSpec(&crt.Spec, field.NewPath("spec"))
	return allErrs
}

func validateIssuerRef(issuerRef v1alpha1.ObjectReference, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}

	issuerRefPath := fldPath.Child("issuerRef")
	if issuerRef.Name == "" {
		el = append(el, field.Required(issuerRefPath.Child("name"), "must be specified"))
	}
	switch issuerRef.Kind {
	case "":
	case "Issuer", "ClusterIssuer":
	default:
		el = append(el, field.Invalid(issuerRefPath.Child("kind"), issuerRef.Kind, "must be one of Issuer or ClusterIssuer"))
	}

	return el
}

func validateIPAddresses(a *v1alpha1.CertificateSpec, fldPath *field.Path) field.ErrorList {
	if len(a.IPAddresses) <= 0 {
		return nil
	}
	el := field.ErrorList{}
	for i, d := range a.IPAddresses {
		ip := net.ParseIP(d)
		if ip == nil {
			el = append(el, field.Invalid(fldPath.Child("ipAddresses").Index(i), d, "invalid IP address"))
		}
	}
	return el
}

func ValidateACMECertificateConfig(a *v1alpha1.ACMECertificateConfig, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}
	for i, cfg := range a.Config {
		el = append(el, ValidateDomainSolverConfig(&cfg, fldPath.Child("config").Index(i))...)
	}
	return el
}

func ValidateDomainSolverConfig(a *v1alpha1.DomainSolverConfig, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}
	if len(a.Domains) == 0 {
		el = append(el, field.Required(fldPath.Child("domains"), "at least one domain must be specified"))
	}
	numTypes := 0
	if a.DNS01 != nil {
		numTypes++
		el = append(el, ValidateDNS01SolverConfig(a.DNS01, fldPath.Child("dns01"))...)
	}
	if a.HTTP01 != nil {
		if numTypes > 0 {
			el = append(el, field.Forbidden(fldPath.Child("http01"), "may not specify more than one solver type"))
		} else {
			numTypes++
			el = append(el, ValidateHTTP01SolverConfig(a.HTTP01, fldPath.Child("http01"))...)
		}
	}
	if numTypes == 0 {
		el = append(el, field.Required(fldPath, "at least one solver must be configured"))
	}
	return el
}

func ValidateDNS01SolverConfig(a *v1alpha1.DNS01SolverConfig, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}
	if a.Provider == "" {
		el = append(el, field.Required(fldPath.Child("provider"), "provider name must be set"))
	}
	return el
}

func ValidateHTTP01SolverConfig(a *v1alpha1.HTTP01SolverConfig, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}
	if a.Ingress != "" && a.IngressClass != nil {
		el = append(el, field.Forbidden(fldPath, "only one of 'ingress' and 'ingressClass' should be specified"))
	}
	// TODO: ensure 'ingress' is a valid resource name (i.e. DNS name)
	return el
}

func ValidateDuration(crt *v1alpha1.CertificateSpec, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}

	duration := v1alpha1.DefaultCertificateDuration
	if crt.Duration != nil {
		duration = crt.Duration.Duration
	}
	renewBefore := v1alpha1.DefaultRenewBefore
	if crt.RenewBefore != nil {
		renewBefore = crt.RenewBefore.Duration
	}
	if duration < v1alpha1.MinimumCertificateDuration {
		el = append(el, field.Invalid(fldPath.Child("duration"), duration, fmt.Sprintf("certificate duration must be greater than %s", v1alpha1.MinimumCertificateDuration)))
	}
	if renewBefore < v1alpha1.MinimumRenewBefore {
		el = append(el, field.Invalid(fldPath.Child("renewBefore"), renewBefore, fmt.Sprintf("certificate renewBefore must be greater than %s", v1alpha1.MinimumRenewBefore)))
	}
	if duration <= renewBefore {
		el = append(el, field.Invalid(fldPath.Child("renewBefore"), renewBefore, fmt.Sprintf("certificate duration %s must be greater than renewBefore %s", duration, renewBefore)))
	}
	return el
}
<commit_msg>Do not validate issuerRef's Kind field if group is not recognised<commit_after>/*
Copyright 2019 The Jetstack cert-manager contributors.

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 validation

import (
	"fmt"
	"net"

	"k8s.io/apimachinery/pkg/util/validation/field"

	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
)

// Validation functions for cert-manager v1alpha1 Certificate types

func ValidateCertificateSpec(crt *v1alpha1.CertificateSpec, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}
	if crt.SecretName == "" {
		el = append(el, field.Required(fldPath.Child("secretName"), "must be specified"))
	}

	el = append(el, validateIssuerRef(crt.IssuerRef, fldPath)...)

	if len(crt.CommonName) == 0 && len(crt.DNSNames) == 0 {
		el = append(el, field.Required(fldPath.Child("dnsNames"), "at least one dnsName is required if commonName is not set"))
	}
	// if a common name has been specified, ensure it is no longer than 64 chars
	if len(crt.CommonName) > 64 {
		el = append(el, field.TooLong(fldPath.Child("commonName"), crt.CommonName, 64))
	}
	// if the common name has *not* been specified, ensure the first dnsName is no longer than 64 chars
	// as it will be used as the commonName
	if crt.CommonName == "" && len(crt.DNSNames) > 0 && len(crt.DNSNames[0]) > 64 {
		el = append(el, field.TooLong(fldPath.Child("dnsNames").Index(0), crt.DNSNames[0], 64))
	}

	if len(crt.IPAddresses) > 0 {
		el = append(el, validateIPAddresses(crt, fldPath)...)
	}
	if crt.ACME != nil {
		el = append(el, validateACMEConfigForAllDNSNames(crt, fldPath)...)
		el = append(el, ValidateACMECertificateConfig(crt.ACME, fldPath.Child("acme"))...)
	}
	if crt.KeySize < 0 {
		el = append(el, field.Invalid(fldPath.Child("keySize"), crt.KeySize, "cannot be less than zero"))
	}
	switch crt.KeyAlgorithm {
	case v1alpha1.KeyAlgorithm(""):
	case v1alpha1.RSAKeyAlgorithm:
		if crt.KeySize > 0 && (crt.KeySize < 2048 || crt.KeySize > 8192) {
			el = append(el, field.Invalid(fldPath.Child("keySize"), crt.KeySize, "must be between 2048 & 8192 for rsa keyAlgorithm"))
		}
	case v1alpha1.ECDSAKeyAlgorithm:
		if crt.KeySize > 0 && crt.KeySize != 256 && crt.KeySize != 384 && crt.KeySize != 521 {
			el = append(el, field.NotSupported(fldPath.Child("keySize"), crt.KeySize, []string{"256", "384", "521"}))
		}
	default:
		el = append(el, field.Invalid(fldPath.Child("keyAlgorithm"), crt.KeyAlgorithm, "must be either empty or one of rsa or ecdsa"))
	}

	if crt.Duration != nil || crt.RenewBefore != nil {
		el = append(el, ValidateDuration(crt, fldPath)...)
	}

	switch crt.KeyEncoding {
	case v1alpha1.KeyEncoding(""), v1alpha1.PKCS1, v1alpha1.PKCS8:
	default:
		el = append(el, field.Invalid(fldPath.Child("keyEncoding"), crt.KeyEncoding, "must be either empty or one of pkcs1 or pkcs8"))
	}
	return el
}

// validateACMEConfigForAllDNSNames will ensure that if the provided Certificate
// specifies any ACME configuration, all domains listed on the Certificate have
// a configuration entry.
func validateACMEConfigForAllDNSNames(a *v1alpha1.CertificateSpec, fldPath *field.Path) field.ErrorList {
	if a.ACME == nil {
		return nil
	}
	el := field.ErrorList{}
	acmeFldPath := fldPath.Child("acme")
	errFn := func(s string) string {
		return fmt.Sprintf("no ACME solver configuration specified for domain %q", s)
	}
	if a.CommonName != "" {
		cfg := v1alpha1.ConfigForDomain(a.ACME.Config, a.CommonName)
		if cfg == nil || len(cfg.Domains) == 0 {
			el = append(el, field.Required(acmeFldPath.Child("config"), errFn(a.CommonName)))
		}
	}
	for _, d := range a.DNSNames {
		cfg := v1alpha1.ConfigForDomain(a.ACME.Config, d)
		if cfg == nil || len(cfg.Domains) == 0 {
			el = append(el, field.Required(acmeFldPath.Child("config"), errFn(d)))
		}
	}
	return el
}

func ValidateCertificate(crt *v1alpha1.Certificate) field.ErrorList {
	allErrs := ValidateCertificateSpec(&crt.Spec, field.NewPath("spec"))
	return allErrs
}

func validateIssuerRef(issuerRef v1alpha1.ObjectReference, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}

	issuerRefPath := fldPath.Child("issuerRef")
	if issuerRef.Name == "" {
		el = append(el, field.Required(issuerRefPath.Child("name"), "must be specified"))
	}
	if issuerRef.Group == "" || issuerRef.Group == v1alpha1.SchemeGroupVersion.Group {
		switch issuerRef.Kind {
		case "":
		case "Issuer", "ClusterIssuer":
		default:
			el = append(el, field.Invalid(issuerRefPath.Child("kind"), issuerRef.Kind, "must be one of Issuer or ClusterIssuer"))
		}
	}
	return el
}

func validateIPAddresses(a *v1alpha1.CertificateSpec, fldPath *field.Path) field.ErrorList {
	if len(a.IPAddresses) <= 0 {
		return nil
	}
	el := field.ErrorList{}
	for i, d := range a.IPAddresses {
		ip := net.ParseIP(d)
		if ip == nil {
			el = append(el, field.Invalid(fldPath.Child("ipAddresses").Index(i), d, "invalid IP address"))
		}
	}
	return el
}

func ValidateACMECertificateConfig(a *v1alpha1.ACMECertificateConfig, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}
	for i, cfg := range a.Config {
		el = append(el, ValidateDomainSolverConfig(&cfg, fldPath.Child("config").Index(i))...)
	}
	return el
}

func ValidateDomainSolverConfig(a *v1alpha1.DomainSolverConfig, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}
	if len(a.Domains) == 0 {
		el = append(el, field.Required(fldPath.Child("domains"), "at least one domain must be specified"))
	}
	numTypes := 0
	if a.DNS01 != nil {
		numTypes++
		el = append(el, ValidateDNS01SolverConfig(a.DNS01, fldPath.Child("dns01"))...)
	}
	if a.HTTP01 != nil {
		if numTypes > 0 {
			el = append(el, field.Forbidden(fldPath.Child("http01"), "may not specify more than one solver type"))
		} else {
			numTypes++
			el = append(el, ValidateHTTP01SolverConfig(a.HTTP01, fldPath.Child("http01"))...)
		}
	}
	if numTypes == 0 {
		el = append(el, field.Required(fldPath, "at least one solver must be configured"))
	}
	return el
}

func ValidateDNS01SolverConfig(a *v1alpha1.DNS01SolverConfig, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}
	if a.Provider == "" {
		el = append(el, field.Required(fldPath.Child("provider"), "provider name must be set"))
	}
	return el
}

func ValidateHTTP01SolverConfig(a *v1alpha1.HTTP01SolverConfig, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}
	if a.Ingress != "" && a.IngressClass != nil {
		el = append(el, field.Forbidden(fldPath, "only one of 'ingress' and 'ingressClass' should be specified"))
	}
	// TODO: ensure 'ingress' is a valid resource name (i.e. DNS name)
	return el
}

func ValidateDuration(crt *v1alpha1.CertificateSpec, fldPath *field.Path) field.ErrorList {
	el := field.ErrorList{}

	duration := v1alpha1.DefaultCertificateDuration
	if crt.Duration != nil {
		duration = crt.Duration.Duration
	}
	renewBefore := v1alpha1.DefaultRenewBefore
	if crt.RenewBefore != nil {
		renewBefore = crt.RenewBefore.Duration
	}
	if duration < v1alpha1.MinimumCertificateDuration {
		el = append(el, field.Invalid(fldPath.Child("duration"), duration, fmt.Sprintf("certificate duration must be greater than %s", v1alpha1.MinimumCertificateDuration)))
	}
	if renewBefore < v1alpha1.MinimumRenewBefore {
		el = append(el, field.Invalid(fldPath.Child("renewBefore"), renewBefore, fmt.Sprintf("certificate renewBefore must be greater than %s", v1alpha1.MinimumRenewBefore)))
	}
	if duration <= renewBefore {
		el = append(el, field.Invalid(fldPath.Child("renewBefore"), renewBefore, fmt.Sprintf("certificate duration %s must be greater than renewBefore %s", duration, renewBefore)))
	}
	return el
}
<|endoftext|>


<commit_before>package kubernetes

import (
	"fmt"
	"sync"

	kitlog "github.com/go-kit/kit/log"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/cache"

	kubernikus_v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	"github.com/sapcc/kubernikus/pkg/util"
)

type SharedClientFactory interface {
	ClientFor(k *kubernikus_v1.Kluster) (clientset kubernetes.Interface, err error)
}

type sharedClientFactory struct {
	clients         *sync.Map
	clientInterface kubernetes.Interface
	Logger          kitlog.Logger
}

func NewSharedClientFactory(client kubernetes.Interface, klusterEvents cache.SharedIndexInformer, logger kitlog.Logger) SharedClientFactory {
	factory := &sharedClientFactory{
		clients:         new(sync.Map),
		clientInterface: client,
		Logger:          kitlog.With(logger, "client", "kubernetes"),
	}

	if klusterEvents != nil {
		klusterEvents.AddEventHandler(cache.ResourceEventHandlerFuncs{
			DeleteFunc: func(obj interface{}) {
				if kluster, ok := obj.(*kubernikus_v1.Kluster); ok {
					factory.clients.Delete(kluster.GetUID())
					factory.Logger.Log(
						"msg", "deleted shared kubernetes client",
						"kluster", kluster.GetName(),
						"project", kluster.Account(),
						"v", 2,
					)
				}
			},
		})
	}

	return factory
}

func (f *sharedClientFactory) ClientFor(k *kubernikus_v1.Kluster) (clientset kubernetes.Interface, err error) {
	defer func() {
		f.Logger.Log(
			"msg", "created shared kubernetes client",
			"kluster", k.GetName(),
			"project", k.Account(),
			"v", 2,
			"err", err,
		)
	}()

	if client, found := f.clients.Load(k.GetUID()); found {
		return client.(kubernetes.Interface), nil
	}

	secret, err := util.KlusterSecret(f.clientInterface, k)
	if err != nil {
		return nil, err
	}

	c := rest.Config{
		Host: fmt.Sprintf("https://%s:6443", k.Name), // access kubernetes api directly by service name (bypass sni sniffer)
		TLSClientConfig: rest.TLSClientConfig{
			CertData: []byte(secret.ApiserverClientsClusterAdminCertificate),
			KeyData:  []byte(secret.ApiserverClientsClusterAdminPrivateKey),
			CAData:   []byte(secret.TLSCACertificate),
		},
	}

	clientset, err = kubernetes.NewForConfig(&c)
	if err != nil {
		return nil, err
	}
	//Ensure the client can actually talk to before saving it to the cache
	if _, err := clientset.Discovery().ServerVersion(); err != nil {
		return nil, err
	}

	f.clients.Store(k.GetUID(), clientset)
	return clientset, nil

}

type MockSharedClientFactory struct {
	Clientset kubernetes.Interface
}

func (m *MockSharedClientFactory) ClientFor(k *kubernikus_v1.Kluster) (kubernetes.Interface, error) {
	return m.Clientset, nil

}
<commit_msg>Fix shared kubernetes client in dev mode<commit_after>package kubernetes

import (
	"fmt"
	"os"
	"sync"

	kitlog "github.com/go-kit/kit/log"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/cache"

	kubernikus_v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	"github.com/sapcc/kubernikus/pkg/util"
)

type SharedClientFactory interface {
	ClientFor(k *kubernikus_v1.Kluster) (clientset kubernetes.Interface, err error)
}

type sharedClientFactory struct {
	clients         *sync.Map
	clientInterface kubernetes.Interface
	Logger          kitlog.Logger
}

func NewSharedClientFactory(client kubernetes.Interface, klusterEvents cache.SharedIndexInformer, logger kitlog.Logger) SharedClientFactory {
	factory := &sharedClientFactory{
		clients:         new(sync.Map),
		clientInterface: client,
		Logger:          kitlog.With(logger, "client", "kubernetes"),
	}

	if klusterEvents != nil {
		klusterEvents.AddEventHandler(cache.ResourceEventHandlerFuncs{
			DeleteFunc: func(obj interface{}) {
				if kluster, ok := obj.(*kubernikus_v1.Kluster); ok {
					factory.clients.Delete(kluster.GetUID())
					factory.Logger.Log(
						"msg", "deleted shared kubernetes client",
						"kluster", kluster.GetName(),
						"project", kluster.Account(),
						"v", 2,
					)
				}
			},
		})
	}

	return factory
}

func (f *sharedClientFactory) ClientFor(k *kubernikus_v1.Kluster) (clientset kubernetes.Interface, err error) {
	defer func() {
		f.Logger.Log(
			"msg", "created shared kubernetes client",
			"kluster", k.GetName(),
			"project", k.Account(),
			"v", 2,
			"err", err,
		)
	}()

	if client, found := f.clients.Load(k.GetUID()); found {
		return client.(kubernetes.Interface), nil
	}

	secret, err := util.KlusterSecret(f.clientInterface, k)
	if err != nil {
		return nil, err
	}

	// If run inside a kubernetes cluster we want to bypass the sni proxy and access the api service directly
	// if we run outside (dev) we fall back to using the fqdn that is exposed by the sni ingress controller
	apiHost := fmt.Sprintf("https://%s:6443", k.Name)
	if os.Getenv("KUBERNETES_SERVICE_HOST") == "" {
		apiHost = k.Status.Apiserver
	}

	c := rest.Config{
		Host: apiHost,
		TLSClientConfig: rest.TLSClientConfig{
			CertData: []byte(secret.ApiserverClientsClusterAdminCertificate),
			KeyData:  []byte(secret.ApiserverClientsClusterAdminPrivateKey),
			CAData:   []byte(secret.TLSCACertificate),
		},
	}

	clientset, err = kubernetes.NewForConfig(&c)
	if err != nil {
		return nil, err
	}
	//Ensure the client can actually talk to before saving it to the cache
	if _, err := clientset.Discovery().ServerVersion(); err != nil {
		return nil, err
	}

	f.clients.Store(k.GetUID(), clientset)
	return clientset, nil

}

type MockSharedClientFactory struct {
	Clientset kubernetes.Interface
}

func (m *MockSharedClientFactory) ClientFor(k *kubernikus_v1.Kluster) (kubernetes.Interface, error) {
	return m.Clientset, nil

}
<|endoftext|>