<commit_before>package client

import (
	"errors"
	. "github.com/zubairhamed/betwixt/api"
	"github.com/zubairhamed/betwixt/core"
	"github.com/zubairhamed/betwixt/core/request"
	. "github.com/zubairhamed/canopus"
	. "github.com/zubairhamed/go-commons/network"
	"log"
	"net"
)

func NewDefaultClient(local string, remote string, registry Registry) *DefaultClient {
	localAddr, err := net.ResolveUDPAddr("udp", local)
	IfErrFatal(err)

	remoteAddr, err := net.ResolveUDPAddr("udp", remote)
	IfErrFatal(err)

	coapServer := NewServer(localAddr, remoteAddr)

	// Create Mandatory
	c := &DefaultClient{
		coapServer:     coapServer,
		enabledObjects: make(map[LWM2MObjectType]Object),
		registry: registry,
	}

	mandatory := registry.GetMandatory()
	for _, o := range mandatory {
		c.EnableObject(o.GetType(), nil)
	}

	return c
}

type DefaultClient struct {
	coapServer     *CoapServer
	registry       Registry
	enabledObjects map[LWM2MObjectType]Object
	path           string

	// Events
	evtOnStartup      FnOnStartup
	evtOnRead         FnOnRead
	evtOnWrite        FnOnWrite
	evtOnExecute      FnOnExecute
	evtOnRegistered   FnOnRegistered
	evtOnDeregistered FnOnDeregistered
	evtOnError        FnOnError
}

// Operations
func (c *DefaultClient) Register(name string) string {
	if len(name) > 10 {
		log.Fatal("Client#Register: Client name can not exceed 10 characters")
	}

	req := NewRequest(TYPE_CONFIRMABLE, POST, GenerateMessageId())

	req.SetStringPayload(core.BuildModelResourceStringPayload(c.enabledObjects))
	req.SetRequestURI("rd")
	req.SetUriQuery("ep", name)
	resp, err := c.coapServer.Send(req)

	path := ""
	if err != nil {
		log.Println(err)
	} else {
		PrintMessage(resp.GetMessage())

		path = resp.GetMessage().GetLocationPath()
	}
	c.path = path

	return path
}

func (c *DefaultClient) SetEnabler(t LWM2MObjectType, e ObjectEnabler) {
	_, ok := c.enabledObjects[t]
	if ok {
		c.enabledObjects[t].SetEnabler(e)
	}
}

func (c *DefaultClient) GetEnabledObjects() map[LWM2MObjectType]Object {
	return c.enabledObjects
}

func (c *DefaultClient) GetRegistry() Registry {
	return c.registry
}

func (c *DefaultClient) Deregister() {
	req := NewRequest(TYPE_CONFIRMABLE, DELETE, GenerateMessageId())

	req.SetRequestURI(c.path)
	resp, err := c.coapServer.Send(req)

	if err != nil {
		log.Println(err)
	} else {
		PrintMessage(resp.GetMessage())
	}
}

func (c *DefaultClient) Update() {

}

func (c *DefaultClient) AddResource() {

}

func (c *DefaultClient) AddObject() {

}

func (c *DefaultClient) UseRegistry(reg Registry) {
	c.registry = reg
}

func (c *DefaultClient) EnableObject(t LWM2MObjectType, e ObjectEnabler) error {
	_, ok := c.enabledObjects[t]
	if !ok {
		if c.registry == nil {
			return errors.New("No registry found/set")
		}
		c.enabledObjects[t] = core.NewObject(t, e, c.registry)

		return nil
	} else {
		return errors.New("Object already enabled")
	}
}

func (c *DefaultClient) AddObjectInstance(t LWM2MObjectType, instance int) error {
	o := c.enabledObjects[t]
	if o != nil {
		o.AddInstance(instance)

		return nil
 	}
	return errors.New("Attempting to add a nil instance")
}

func (c *DefaultClient) AddObjectInstances(t LWM2MObjectType, instances ...int) {
	for _, o := range instances {
		c.AddObjectInstance(t, o)
	}
}

func (c *DefaultClient) GetObject(n LWM2MObjectType) Object {
	return c.enabledObjects[n]
}

func (c *DefaultClient) validate() {

}

func (c *DefaultClient) Start() {
	c.validate()

	s := c.coapServer
	s.OnStartup(func(evt *Event) {
		if c.evtOnStartup != nil {
			c.evtOnStartup()
		}
	})

	s.NewRoute("{obj}/{inst}/{rsrc}", GET, c.handleReadRequest)
	s.NewRoute("{obj}/{inst}", GET, c.handleReadRequest)
	s.NewRoute("{obj}", GET, c.handleReadRequest)

	s.NewRoute("{obj}/{inst}/{rsrc}", PUT, c.handleWriteRequest)
	s.NewRoute("{obj}/{inst}", PUT, c.handleWriteRequest)

	s.NewRoute("{obj}/{inst}", DELETE, c.handleDeleteRequest)

	s.NewRoute("{obj}/{inst}/{rsrc}", POST, c.handleExecuteRequest)
	s.NewRoute("{obj}/{inst}", POST, c.handleCreateRequest)

	c.coapServer.Start()
}

func (c *DefaultClient) handleCreateRequest(r Request) Response {
	req := r.(*CoapRequest)
	attrResource := req.GetAttribute("rsrc")
	objectId := req.GetAttributeAsInt("obj")
	instanceId := req.GetAttributeAsInt("inst")

	var resourceId = -1

	if attrResource != "" {
		resourceId = req.GetAttributeAsInt("rsrc")
	}

	t := LWM2MObjectType(objectId)
	obj := c.GetObject(t)
	enabler := obj.GetEnabler()

	msg := NewMessageOfType(TYPE_ACKNOWLEDGEMENT, req.GetMessage().MessageId)
	msg.Token = req.GetMessage().Token
	msg.Payload = NewEmptyPayload()

	if enabler != nil {
		lwReq := request.Default(req, OPERATIONTYPE_CREATE)
		response := enabler.OnCreate(instanceId, resourceId, lwReq)
		msg.Code = response.GetResponseCode()
	} else {
		msg.Code = COAPCODE_405_METHOD_NOT_ALLOWED
	}
	return NewResponseWithMessage(msg)
}

func (c *DefaultClient) handleReadRequest(r Request) Response {
	req := r.(*CoapRequest)
	attrResource := req.GetAttribute("rsrc")
	objectId := req.GetAttributeAsInt("obj")
	instanceId := req.GetAttributeAsInt("inst")

	var resourceId = -1

	if attrResource != "" {
		resourceId = req.GetAttributeAsInt("rsrc")
	}

	t := LWM2MObjectType(objectId)
	obj := c.GetObject(t)
	enabler := obj.GetEnabler()

	msg := NewMessageOfType(TYPE_ACKNOWLEDGEMENT, req.GetMessage().MessageId)
	msg.Token = req.GetMessage().Token

	if enabler != nil {
		model := obj.GetModel()
		resource := model.GetResource(resourceId)

		if resource == nil {
			// TODO: Return TLV of Object Instance
			msg.Code = COAPCODE_404_NOT_FOUND
		} else {
			if !core.IsReadableResource(resource) {
				msg.Code = COAPCODE_405_METHOD_NOT_ALLOWED
			} else {
				lwReq := request.Default(req, OPERATIONTYPE_READ)
				response := enabler.OnRead(instanceId, resourceId, lwReq)
				log.Println(response)

				val := response.GetResponseValue()
				msg.Code = response.GetResponseCode()
				msg.Payload = NewBytesPayload(val.GetBytes())
			}
		}
	} else {
		msg.Code = COAPCODE_405_METHOD_NOT_ALLOWED
	}
	return NewResponseWithMessage(msg)
}

func (c *DefaultClient) handleDeleteRequest(r Request) Response {
	req := r.(*CoapRequest)
	objectId := req.GetAttributeAsInt("obj")
	instanceId := req.GetAttributeAsInt("inst")

	t := LWM2MObjectType(objectId)
	enabler := c.GetObject(t).GetEnabler()

	msg := NewMessageOfType(TYPE_ACKNOWLEDGEMENT, req.GetMessage().MessageId)
	msg.Token = req.GetMessage().Token
	msg.Payload = NewEmptyPayload()

	if enabler != nil {
		lwReq := request.Default(req, OPERATIONTYPE_DELETE)

		response := enabler.OnDelete(instanceId, lwReq)
		msg.Code = response.GetResponseCode()
	} else {
		msg.Code = COAPCODE_405_METHOD_NOT_ALLOWED
	}
	return NewResponseWithMessage(msg)
}

func (c *DefaultClient) handleDiscoverRequest() {

}

func (c *DefaultClient) handleObserveRequest() {

}

func (c *DefaultClient) handleWriteRequest(r Request) Response {
	req := r.(*CoapRequest)
	attrResource := req.GetAttribute("rsrc")
	objectId := req.GetAttributeAsInt("obj")
	instanceId := req.GetAttributeAsInt("inst")

	log.Println(req.GetMessage().Payload)

	var resourceId = -1

	if attrResource != "" {
		resourceId = req.GetAttributeAsInt("rsrc")
	}

	t := LWM2MObjectType(objectId)
	obj := c.GetObject(t)
	enabler := obj.GetEnabler()

	msg := NewMessageOfType(TYPE_ACKNOWLEDGEMENT, req.GetMessage().MessageId)
	msg.Token = req.GetMessage().Token
	msg.Payload = NewEmptyPayload()

	if enabler != nil {
		model := obj.GetModel()
		resource := model.GetResource(resourceId)
		if resource == nil {
			// TODO Write to Object Instance
			msg.Code = COAPCODE_404_NOT_FOUND
		} else {
			if !core.IsWritableResource(resource) {
				msg.Code = COAPCODE_405_METHOD_NOT_ALLOWED
			} else {
				lwReq := request.Default(req, OPERATIONTYPE_WRITE)
				response := enabler.OnWrite(instanceId, resourceId, lwReq)
				msg.Code = response.GetResponseCode()
			}
		}
	} else {
		msg.Code = COAPCODE_404_NOT_FOUND
	}
	return NewResponseWithMessage(msg)
}

func (c *DefaultClient) handleExecuteRequest(r Request) Response {
	req := r.(*CoapRequest)
	attrResource := req.GetAttribute("rsrc")
	objectId := req.GetAttributeAsInt("obj")
	instanceId := req.GetAttributeAsInt("inst")

	var resourceId = -1

	if attrResource != "" {
		resourceId = req.GetAttributeAsInt("rsrc")
	}

	t := LWM2MObjectType(objectId)
	obj := c.GetObject(t)
	enabler := obj.GetEnabler()

	msg := NewMessageOfType(TYPE_ACKNOWLEDGEMENT, req.GetMessage().MessageId)
	msg.Token = req.GetMessage().Token
	msg.Payload = NewEmptyPayload()

	if enabler != nil {
		model := obj.GetModel()
		resource := model.GetResource(resourceId)
		if resource == nil {
			msg.Code = COAPCODE_404_NOT_FOUND
		}

		if !core.IsExecutableResource(resource) {
			log.Println("ExecutableResource?", resource.GetOperations())
			msg.Code = COAPCODE_405_METHOD_NOT_ALLOWED
		} else {
			lwReq := request.Default(req, OPERATIONTYPE_EXECUTE)
			response := enabler.OnExecute(instanceId, resourceId, lwReq)
			msg.Code = response.GetResponseCode()
		}
	} else {
		msg.Code = COAPCODE_404_NOT_FOUND
	}

	return NewResponseWithMessage(msg)
}

// Events
func (c *DefaultClient) OnStartup(fn FnOnStartup) {
	c.evtOnStartup = fn
}

func (c *DefaultClient) OnRead(fn FnOnRead) {
	c.evtOnRead = fn
}

func (c *DefaultClient) OnWrite(fn FnOnWrite) {
	c.evtOnWrite = fn
}

func (c *DefaultClient) OnExecute(fn FnOnExecute) {
	c.evtOnExecute = fn
}

func (c *DefaultClient) OnRegistered(fn FnOnRegistered) {
	c.evtOnRegistered = fn
}

func (c *DefaultClient) OnDeregistered(fn FnOnDeregistered) {
	c.evtOnDeregistered = fn
}

func (c *DefaultClient) OnError(fn FnOnError) {
	c.evtOnError = fn
}
<commit_msg>Remove unwanted logs<commit_after>package client

import (
	"errors"
	. "github.com/zubairhamed/betwixt/api"
	"github.com/zubairhamed/betwixt/core"
	"github.com/zubairhamed/betwixt/core/request"
	. "github.com/zubairhamed/canopus"
	. "github.com/zubairhamed/go-commons/network"
	"log"
	"net"
)

func NewDefaultClient(local string, remote string, registry Registry) *DefaultClient {
	localAddr, err := net.ResolveUDPAddr("udp", local)
	IfErrFatal(err)

	remoteAddr, err := net.ResolveUDPAddr("udp", remote)
	IfErrFatal(err)

	coapServer := NewServer(localAddr, remoteAddr)

	// Create Mandatory
	c := &DefaultClient{
		coapServer:     coapServer,
		enabledObjects: make(map[LWM2MObjectType]Object),
		registry: registry,
	}

	mandatory := registry.GetMandatory()
	for _, o := range mandatory {
		c.EnableObject(o.GetType(), nil)
	}

	return c
}

type DefaultClient struct {
	coapServer     *CoapServer
	registry       Registry
	enabledObjects map[LWM2MObjectType]Object
	path           string

	// Events
	evtOnStartup      FnOnStartup
	evtOnRead         FnOnRead
	evtOnWrite        FnOnWrite
	evtOnExecute      FnOnExecute
	evtOnRegistered   FnOnRegistered
	evtOnDeregistered FnOnDeregistered
	evtOnError        FnOnError
}

// Operations
func (c *DefaultClient) Register(name string) string {
	if len(name) > 10 {
		log.Fatal("Client#Register: Client name can not exceed 10 characters")
	}

	req := NewRequest(TYPE_CONFIRMABLE, POST, GenerateMessageId())

	req.SetStringPayload(core.BuildModelResourceStringPayload(c.enabledObjects))
	req.SetRequestURI("rd")
	req.SetUriQuery("ep", name)
	resp, err := c.coapServer.Send(req)

	path := ""
	if err != nil {
		log.Println(err)
	} else {
		PrintMessage(resp.GetMessage())

		path = resp.GetMessage().GetLocationPath()
	}
	c.path = path

	return path
}

func (c *DefaultClient) SetEnabler(t LWM2MObjectType, e ObjectEnabler) {
	_, ok := c.enabledObjects[t]
	if ok {
		c.enabledObjects[t].SetEnabler(e)
	}
}

func (c *DefaultClient) GetEnabledObjects() map[LWM2MObjectType]Object {
	return c.enabledObjects
}

func (c *DefaultClient) GetRegistry() Registry {
	return c.registry
}

func (c *DefaultClient) Deregister() {
	req := NewRequest(TYPE_CONFIRMABLE, DELETE, GenerateMessageId())

	req.SetRequestURI(c.path)
	resp, err := c.coapServer.Send(req)

	if err != nil {
		log.Println(err)
	} else {
		PrintMessage(resp.GetMessage())
	}
}

func (c *DefaultClient) Update() {

}

func (c *DefaultClient) AddResource() {

}

func (c *DefaultClient) AddObject() {

}

func (c *DefaultClient) UseRegistry(reg Registry) {
	c.registry = reg
}

func (c *DefaultClient) EnableObject(t LWM2MObjectType, e ObjectEnabler) error {
	_, ok := c.enabledObjects[t]
	if !ok {
		if c.registry == nil {
			return errors.New("No registry found/set")
		}
		c.enabledObjects[t] = core.NewObject(t, e, c.registry)

		return nil
	} else {
		return errors.New("Object already enabled")
	}
}

func (c *DefaultClient) AddObjectInstance(t LWM2MObjectType, instance int) error {
	o := c.enabledObjects[t]
	if o != nil {
		o.AddInstance(instance)

		return nil
 	}
	return errors.New("Attempting to add a nil instance")
}

func (c *DefaultClient) AddObjectInstances(t LWM2MObjectType, instances ...int) {
	for _, o := range instances {
		c.AddObjectInstance(t, o)
	}
}

func (c *DefaultClient) GetObject(n LWM2MObjectType) Object {
	return c.enabledObjects[n]
}

func (c *DefaultClient) validate() {

}

func (c *DefaultClient) Start() {
	c.validate()

	s := c.coapServer
	s.OnStartup(func(evt *Event) {
		if c.evtOnStartup != nil {
			c.evtOnStartup()
		}
	})

	s.NewRoute("{obj}/{inst}/{rsrc}", GET, c.handleReadRequest)
	s.NewRoute("{obj}/{inst}", GET, c.handleReadRequest)
	s.NewRoute("{obj}", GET, c.handleReadRequest)

	s.NewRoute("{obj}/{inst}/{rsrc}", PUT, c.handleWriteRequest)
	s.NewRoute("{obj}/{inst}", PUT, c.handleWriteRequest)

	s.NewRoute("{obj}/{inst}", DELETE, c.handleDeleteRequest)

	s.NewRoute("{obj}/{inst}/{rsrc}", POST, c.handleExecuteRequest)
	s.NewRoute("{obj}/{inst}", POST, c.handleCreateRequest)

	c.coapServer.Start()
}

func (c *DefaultClient) handleCreateRequest(r Request) Response {
	req := r.(*CoapRequest)
	attrResource := req.GetAttribute("rsrc")
	objectId := req.GetAttributeAsInt("obj")
	instanceId := req.GetAttributeAsInt("inst")

	var resourceId = -1

	if attrResource != "" {
		resourceId = req.GetAttributeAsInt("rsrc")
	}

	t := LWM2MObjectType(objectId)
	obj := c.GetObject(t)
	enabler := obj.GetEnabler()

	msg := NewMessageOfType(TYPE_ACKNOWLEDGEMENT, req.GetMessage().MessageId)
	msg.Token = req.GetMessage().Token
	msg.Payload = NewEmptyPayload()

	if enabler != nil {
		lwReq := request.Default(req, OPERATIONTYPE_CREATE)
		response := enabler.OnCreate(instanceId, resourceId, lwReq)
		msg.Code = response.GetResponseCode()
	} else {
		msg.Code = COAPCODE_405_METHOD_NOT_ALLOWED
	}
	return NewResponseWithMessage(msg)
}

func (c *DefaultClient) handleReadRequest(r Request) Response {
	req := r.(*CoapRequest)
	attrResource := req.GetAttribute("rsrc")
	objectId := req.GetAttributeAsInt("obj")
	instanceId := req.GetAttributeAsInt("inst")

	var resourceId = -1

	if attrResource != "" {
		resourceId = req.GetAttributeAsInt("rsrc")
	}

	t := LWM2MObjectType(objectId)
	obj := c.GetObject(t)
	enabler := obj.GetEnabler()

	msg := NewMessageOfType(TYPE_ACKNOWLEDGEMENT, req.GetMessage().MessageId)
	msg.Token = req.GetMessage().Token

	if enabler != nil {
		model := obj.GetModel()
		resource := model.GetResource(resourceId)

		if resource == nil {
			// TODO: Return TLV of Object Instance
			msg.Code = COAPCODE_404_NOT_FOUND
		} else {
			if !core.IsReadableResource(resource) {
				msg.Code = COAPCODE_405_METHOD_NOT_ALLOWED
			} else {
				lwReq := request.Default(req, OPERATIONTYPE_READ)
				response := enabler.OnRead(instanceId, resourceId, lwReq)

				val := response.GetResponseValue()
				msg.Code = response.GetResponseCode()
				msg.Payload = NewBytesPayload(val.GetBytes())
			}
		}
	} else {
		msg.Code = COAPCODE_405_METHOD_NOT_ALLOWED
	}
	return NewResponseWithMessage(msg)
}

func (c *DefaultClient) handleDeleteRequest(r Request) Response {
	req := r.(*CoapRequest)
	objectId := req.GetAttributeAsInt("obj")
	instanceId := req.GetAttributeAsInt("inst")

	t := LWM2MObjectType(objectId)
	enabler := c.GetObject(t).GetEnabler()

	msg := NewMessageOfType(TYPE_ACKNOWLEDGEMENT, req.GetMessage().MessageId)
	msg.Token = req.GetMessage().Token
	msg.Payload = NewEmptyPayload()

	if enabler != nil {
		lwReq := request.Default(req, OPERATIONTYPE_DELETE)

		response := enabler.OnDelete(instanceId, lwReq)
		msg.Code = response.GetResponseCode()
	} else {
		msg.Code = COAPCODE_405_METHOD_NOT_ALLOWED
	}
	return NewResponseWithMessage(msg)
}

func (c *DefaultClient) handleDiscoverRequest() {

}

func (c *DefaultClient) handleObserveRequest() {

}

func (c *DefaultClient) handleWriteRequest(r Request) Response {
	req := r.(*CoapRequest)
	attrResource := req.GetAttribute("rsrc")
	objectId := req.GetAttributeAsInt("obj")
	instanceId := req.GetAttributeAsInt("inst")

	log.Println(req.GetMessage().Payload)

	var resourceId = -1

	if attrResource != "" {
		resourceId = req.GetAttributeAsInt("rsrc")
	}

	t := LWM2MObjectType(objectId)
	obj := c.GetObject(t)
	enabler := obj.GetEnabler()

	msg := NewMessageOfType(TYPE_ACKNOWLEDGEMENT, req.GetMessage().MessageId)
	msg.Token = req.GetMessage().Token
	msg.Payload = NewEmptyPayload()

	if enabler != nil {
		model := obj.GetModel()
		resource := model.GetResource(resourceId)
		if resource == nil {
			// TODO Write to Object Instance
			msg.Code = COAPCODE_404_NOT_FOUND
		} else {
			if !core.IsWritableResource(resource) {
				msg.Code = COAPCODE_405_METHOD_NOT_ALLOWED
			} else {
				lwReq := request.Default(req, OPERATIONTYPE_WRITE)
				response := enabler.OnWrite(instanceId, resourceId, lwReq)
				msg.Code = response.GetResponseCode()
			}
		}
	} else {
		msg.Code = COAPCODE_404_NOT_FOUND
	}
	return NewResponseWithMessage(msg)
}

func (c *DefaultClient) handleExecuteRequest(r Request) Response {
	req := r.(*CoapRequest)
	attrResource := req.GetAttribute("rsrc")
	objectId := req.GetAttributeAsInt("obj")
	instanceId := req.GetAttributeAsInt("inst")

	var resourceId = -1

	if attrResource != "" {
		resourceId = req.GetAttributeAsInt("rsrc")
	}

	t := LWM2MObjectType(objectId)
	obj := c.GetObject(t)
	enabler := obj.GetEnabler()

	msg := NewMessageOfType(TYPE_ACKNOWLEDGEMENT, req.GetMessage().MessageId)
	msg.Token = req.GetMessage().Token
	msg.Payload = NewEmptyPayload()

	if enabler != nil {
		model := obj.GetModel()
		resource := model.GetResource(resourceId)
		if resource == nil {
			msg.Code = COAPCODE_404_NOT_FOUND
		}

		if !core.IsExecutableResource(resource) {
			msg.Code = COAPCODE_405_METHOD_NOT_ALLOWED
		} else {
			lwReq := request.Default(req, OPERATIONTYPE_EXECUTE)
			response := enabler.OnExecute(instanceId, resourceId, lwReq)
			msg.Code = response.GetResponseCode()
		}
	} else {
		msg.Code = COAPCODE_404_NOT_FOUND
	}

	return NewResponseWithMessage(msg)
}

// Events
func (c *DefaultClient) OnStartup(fn FnOnStartup) {
	c.evtOnStartup = fn
}

func (c *DefaultClient) OnRead(fn FnOnRead) {
	c.evtOnRead = fn
}

func (c *DefaultClient) OnWrite(fn FnOnWrite) {
	c.evtOnWrite = fn
}

func (c *DefaultClient) OnExecute(fn FnOnExecute) {
	c.evtOnExecute = fn
}

func (c *DefaultClient) OnRegistered(fn FnOnRegistered) {
	c.evtOnRegistered = fn
}

func (c *DefaultClient) OnDeregistered(fn FnOnDeregistered) {
	c.evtOnDeregistered = fn
}

func (c *DefaultClient) OnError(fn FnOnError) {
	c.evtOnError = fn
}
<|endoftext|>


<commit_before>// Copyright 2015 OpsGenie. All rights reserved.
// Use of this source code is governed by a Apache Software
// license that can be found in the LICENSE file.

/*
Package client manages the creation of API clients.
API user first creates a pointer of type OpsGenieClient. Following that
he/she can set some configurations for HTTP communication layer by setting
a proxy definition and/or transport layer options.

Introduction

The most fundamental and general use case is being able to access the
OpsGenie Web API by coding a Go program.
The program -by mean of a client application- can send OpsGenie Web API
the requests using the 'client' package in a higher level. For the programmer
of the client application, that reduces the number of LoCs.
Besides it will result a less error-prone application and reduce
the complexity by hiding the low-level networking, error-handling and
byte-processing calls.

Package client has ports for all entry points to the Web API.
The OpsGenie Web API is structured in JSON-bodied
calls (except the file attachment).
*/
package client

import (
	"errors"
	"time"
)

// OpsGenie Go SDK performs HTTP calls to the Web API.
// The Web API is designated by a URL so called an endpoint
const ENDPOINT_URL string = "https://api.opsgenie.com"

const DEFAULT_CONNECTION_TIMEOUT_IN_SECONDS time.Duration = 1
const DEFAULT_MAX_RETRY_ATTEMPTS int = 1
const TIME_SLEEP_BETWEEN_REQUESTS time.Duration = 500 * time.Millisecond

// OpsGenieClient is a general data type used for:
// 	- authenticating callers through their api keys and
// 	- instanciating "alert" and "heartbeat" clients
//	- setting HTTP transport layer configurations
type OpsGenieClient struct {
	proxy *ClientProxyConfiguration
	httpTransportSettings *HttpTransportSettings
	apiKey string
}
// Setters:
//	- proxy
//	- http transport layer conf
//	- api key
func (cli *OpsGenieClient) SetClientProxyConfiguration(conf *ClientProxyConfiguration) {
	cli.proxy = conf
}

func (cli *OpsGenieClient) SetHttpTransportSettings(settings *HttpTransportSettings) {
	cli.httpTransportSettings = settings
}

func (cli *OpsGenieClient) SetApiKey(key string) error {
	if key == "" {
		return errors.New("API Key can not be empty")
	}
	cli.apiKey = key
	return nil
}

// Instanciates a new OpsGenieAlertClient
// and sets the api key to be used alongside the execution.
func (cli *OpsGenieClient) Alert() (*OpsGenieAlertClient, error) {
	if cli.apiKey == "" {
		return nil, errors.New("API Key should be set first")
	}
	alertClient := new (OpsGenieAlertClient)
	alertClient.apiKey = cli.apiKey
	if cli.proxy != nil {
		alertClient.proxy = cli.proxy.ToString()
	}
	alertClient.SetConnectionTimeout( DEFAULT_CONNECTION_TIMEOUT_IN_SECONDS * time.Second )
	alertClient.SetMaxRetryAttempts( DEFAULT_MAX_RETRY_ATTEMPTS )

	if cli.httpTransportSettings != nil {
		if cli.httpTransportSettings.ConnectionTimeout > 0 {
			alertClient.SetConnectionTimeout( cli.httpTransportSettings.ConnectionTimeout )
		}
		if cli.httpTransportSettings.MaxRetryAttempts > 0 {
			alertClient.SetMaxRetryAttempts(cli.httpTransportSettings.MaxRetryAttempts)
		}
	}
	return alertClient, nil
}
// Instanciates a new OpsGenieHeartbeatClient
// and sets the api key to be used alongside the execution.
func (cli *OpsGenieClient) Heartbeat() (*OpsGenieHeartbeatClient, error) {
	if cli.apiKey == "" {
		return nil, errors.New("API Key should be set first")
	}
	heartbeatClient := new (OpsGenieHeartbeatClient)
	heartbeatClient.apiKey = cli.apiKey
	if cli.proxy != nil {
		heartbeatClient.proxy = cli.proxy.ToString()
	}
	heartbeatClient.SetConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT_IN_SECONDS * time.Second)
	heartbeatClient.SetMaxRetryAttempts(DEFAULT_MAX_RETRY_ATTEMPTS)
	if cli.httpTransportSettings != nil {
		if cli.httpTransportSettings.ConnectionTimeout > 0 {
			heartbeatClient.SetConnectionTimeout(cli.httpTransportSettings.ConnectionTimeout)
		}
	}

	return heartbeatClient, nil
}
// Instanciates a new OpsGenieIntegrationClient
// and sets the api key to be used alongside the execution.
func (cli *OpsGenieClient) Integration() (*OpsGenieIntegrationClient, error) {
	if cli.apiKey == "" {
		return nil, errors.New("API Key should be set first")
	}
	integrationClient := new (OpsGenieIntegrationClient)
	integrationClient.apiKey = cli.apiKey
	if cli.proxy != nil {
		integrationClient.proxy = cli.proxy.ToString()
	}
	integrationClient.SetConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT_IN_SECONDS * time.Second)
	integrationClient.SetMaxRetryAttempts(DEFAULT_MAX_RETRY_ATTEMPTS)
	if cli.httpTransportSettings != nil {
		if cli.httpTransportSettings.ConnectionTimeout > 0 {
			integrationClient.SetConnectionTimeout(cli.httpTransportSettings.ConnectionTimeout)
		}
	}
	return integrationClient, nil
}

// Instanciates a new OpsGeniePolicyClient
// and sets the api key to be used alongside the execution.
func (cli *OpsGenieClient) Policy() (*OpsGeniePolicyClient, error) {
	if cli.apiKey == "" {
		return nil, errors.New("API Key should be set first")
	}
	policyClient := new (OpsGeniePolicyClient)
	policyClient.apiKey = cli.apiKey
	if cli.proxy != nil {
		policyClient.proxy = cli.proxy.ToString()
	}
	policyClient.SetConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT_IN_SECONDS * time.Second)
	policyClient.SetMaxRetryAttempts(DEFAULT_MAX_RETRY_ATTEMPTS)
	if cli.httpTransportSettings != nil {
		if cli.httpTransportSettings.ConnectionTimeout > 0 {
			policyClient.SetConnectionTimeout(cli.httpTransportSettings.ConnectionTimeout)
		}
	}
	return policyClient, nil
}
<commit_msg>more comments added<commit_after>// Copyright 2015 OpsGenie. All rights reserved.
// Use of this source code is governed by a Apache Software
// license that can be found in the LICENSE file.

/*
	Package client manages the creation of API clients.
	API user first creates a pointer of type OpsGenieClient. Following that
	he/she can set some configurations for HTTP communication layer by setting
	a proxy definition and/or transport layer options.

	Introduction

	The most fundamental and general use case is being able to access the
	OpsGenie Web API by coding a Go program.
	The program -by mean of a client application- can send OpsGenie Web API
	the requests using the 'client' package in a higher level. For the programmer
	of the client application, that reduces the number of LoCs.
	Besides it will result a less error-prone application and reduce
	the complexity by hiding the low-level networking, error-handling and
	byte-processing calls.

	Package client has ports for all entry points to the Web API.
	The OpsGenie Web API is structured in JSON-bodied
	calls (except the file attachment).
*/
package client

import (
	"errors"
	"time"
)

// OpsGenie Go SDK performs HTTP calls to the Web API.
// The Web API is designated by a URL so called an endpoint
const ENDPOINT_URL string = "https://api.opsgenie.com"

const DEFAULT_CONNECTION_TIMEOUT_IN_SECONDS time.Duration = 1
const DEFAULT_MAX_RETRY_ATTEMPTS int = 1
const TIME_SLEEP_BETWEEN_REQUESTS time.Duration = 500 * time.Millisecond

// OpsGenieClient is a general data type used for:
// 	- authenticating callers through their api keys and
// 	- instanciating "alert" and "heartbeat" clients
//	- setting HTTP transport layer configurations
type OpsGenieClient struct {
	proxy *ClientProxyConfiguration
	httpTransportSettings *HttpTransportSettings
	apiKey string
}
// Setters:
//	- proxy
//	- http transport layer conf
//	- api key
func (cli *OpsGenieClient) SetClientProxyConfiguration(conf *ClientProxyConfiguration) {
	cli.proxy = conf
}

func (cli *OpsGenieClient) SetHttpTransportSettings(settings *HttpTransportSettings) {
	cli.httpTransportSettings = settings
}

func (cli *OpsGenieClient) SetApiKey(key string) error {
	if key == "" {
		return errors.New("API Key can not be empty")
	}
	cli.apiKey = key
	return nil
}

// Instanciates a new OpsGenieAlertClient
// and sets the api key to be used alongside the execution.
func (cli *OpsGenieClient) Alert() (*OpsGenieAlertClient, error) {
	if cli.apiKey == "" {
		return nil, errors.New("API Key should be set first")
	}
	alertClient := new (OpsGenieAlertClient)
	alertClient.apiKey = cli.apiKey
	if cli.proxy != nil {
		alertClient.proxy = cli.proxy.ToString()
	}
	alertClient.SetConnectionTimeout( DEFAULT_CONNECTION_TIMEOUT_IN_SECONDS * time.Second )
	alertClient.SetMaxRetryAttempts( DEFAULT_MAX_RETRY_ATTEMPTS )

	if cli.httpTransportSettings != nil {
		if cli.httpTransportSettings.ConnectionTimeout > 0 {
			alertClient.SetConnectionTimeout( cli.httpTransportSettings.ConnectionTimeout )
		}
		if cli.httpTransportSettings.MaxRetryAttempts > 0 {
			alertClient.SetMaxRetryAttempts(cli.httpTransportSettings.MaxRetryAttempts)
		}
	}
	return alertClient, nil
}
// Instanciates a new OpsGenieHeartbeatClient
// and sets the api key to be used alongside the execution.
func (cli *OpsGenieClient) Heartbeat() (*OpsGenieHeartbeatClient, error) {
	if cli.apiKey == "" {
		return nil, errors.New("API Key should be set first")
	}
	heartbeatClient := new (OpsGenieHeartbeatClient)
	heartbeatClient.apiKey = cli.apiKey
	if cli.proxy != nil {
		heartbeatClient.proxy = cli.proxy.ToString()
	}
	heartbeatClient.SetConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT_IN_SECONDS * time.Second)
	heartbeatClient.SetMaxRetryAttempts(DEFAULT_MAX_RETRY_ATTEMPTS)
	if cli.httpTransportSettings != nil {
		if cli.httpTransportSettings.ConnectionTimeout > 0 {
			heartbeatClient.SetConnectionTimeout(cli.httpTransportSettings.ConnectionTimeout)
		}
	}

	return heartbeatClient, nil
}
// Instanciates a new OpsGenieIntegrationClient
// and sets the api key to be used alongside the execution.
func (cli *OpsGenieClient) Integration() (*OpsGenieIntegrationClient, error) {
	if cli.apiKey == "" {
		return nil, errors.New("API Key should be set first")
	}
	integrationClient := new (OpsGenieIntegrationClient)
	integrationClient.apiKey = cli.apiKey
	if cli.proxy != nil {
		integrationClient.proxy = cli.proxy.ToString()
	}
	integrationClient.SetConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT_IN_SECONDS * time.Second)
	integrationClient.SetMaxRetryAttempts(DEFAULT_MAX_RETRY_ATTEMPTS)
	if cli.httpTransportSettings != nil {
		if cli.httpTransportSettings.ConnectionTimeout > 0 {
			integrationClient.SetConnectionTimeout(cli.httpTransportSettings.ConnectionTimeout)
		}
	}
	return integrationClient, nil
}

// Instanciates a new OpsGeniePolicyClient
// and sets the api key to be used alongside the execution.
func (cli *OpsGenieClient) Policy() (*OpsGeniePolicyClient, error) {
	if cli.apiKey == "" {
		return nil, errors.New("API Key should be set first")
	}
	policyClient := new (OpsGeniePolicyClient)
	policyClient.apiKey = cli.apiKey
	if cli.proxy != nil {
		policyClient.proxy = cli.proxy.ToString()
	}
	policyClient.SetConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT_IN_SECONDS * time.Second)
	policyClient.SetMaxRetryAttempts(DEFAULT_MAX_RETRY_ATTEMPTS)
	if cli.httpTransportSettings != nil {
		if cli.httpTransportSettings.ConnectionTimeout > 0 {
			policyClient.SetConnectionTimeout(cli.httpTransportSettings.ConnectionTimeout)
		}
	}
	return policyClient, nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"

	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/s3"
)

type awsHostContext struct {
	Creds  *credentials.Credentials
	ID     string
	Region string
}

// DEBUG enable debugging output using -debug flag
var DEBUG bool

func debug(m ...interface{}) {
	if DEBUG {
		fmt.Println(m...)
	}
}

func main() {
	// currently not using any of the client flags
	_ = flag.String("u", "", "The user account")
	_ = flag.String("k", "", "The public key proffered by the client")
	_ = flag.String("t", "", "The key encryption type")
	_ = flag.String("f", "", "The fingerprint of the public key")
	groupTag := flag.String("group_tag", "access-groups", "The instance tag with csv ssh access groups in it")
	s3Bucket := flag.String("s3_bucket", "keys", "The bucket where the access group public keys are stored")
	s3Region := flag.String("s3_region", "eu-west-1", "The region in which the bucket is located")
	debugOutput := flag.Bool("debug", false, "Enable debug output")
	flag.Parse()

	if *debugOutput {
		DEBUG = true
		debug("debug output enabled")
		debug("grouptag: ", *groupTag)
		debug("bucket:   ", *s3Bucket)
		debug("region:   ", *s3Region)
		debug("")
	}

	// get aws host context
	hctx, err := getAwsHostContext()
	if err != nil {
		debug(err.Error())
		os.Exit(1)
	}

	// get the list of access groups the intance belongs to
	accessGroups, err := getInstanceAccessGroups(hctx, *groupTag)
	if err != nil {
		debug(err.Error())
		os.Exit(1)
	}

	// get the authorized_keys files for those instances
	authorizedKeys, err := getAccessKeys(hctx, *s3Bucket, *s3Region, accessGroups)
	if err != nil {
		debug(err.Error())
		os.Exit(1)
	}

	// print all authorized_keys
	debug("print all group keys:")
	for _, k := range authorizedKeys {
		fmt.Print(k)
	}
}

func getAwsHostContext() (*awsHostContext, error) {
	//setup
	hctx := new(awsHostContext)
	cfg := aws.NewConfig()
	svc := ec2metadata.New(session.New(), cfg)
	p := &ec2rolecreds.EC2RoleProvider{
		Client: svc,
	}
	//get creds
	hctx.Creds = credentials.NewCredentials(p)
	//get instance id
	id, err := svc.GetMetadata("instance-id")
	if err != nil {
		return nil, err
	}
	hctx.ID = id
	debug("instance-id:", id)
	//get region
	region, err := svc.Region()
	if err != nil {
		return nil, err
	}
	hctx.Region = region
	debug("region:     ", region)
	return hctx, nil
}

func getInstanceAccessGroups(hctx *awsHostContext, tag string) ([]string, error) {
	// setup
	svc := ec2.New(session.New(aws.NewConfig().WithCredentials(hctx.Creds).WithRegion(hctx.Region)))
	params := &ec2.DescribeTagsInput{
		Filters: []*ec2.Filter{
			{
				Name:   aws.String("resource-id"),
				Values: []*string{aws.String(hctx.ID)},
			},
			{
				Name:   aws.String("key"),
				Values: []*string{aws.String(tag)},
			},
		},
	}
	// fetch tags
	debug("requesting tag: " + tag + ", from resource: " + hctx.ID)
	resp, err := svc.DescribeTags(params)
	if err != nil {
		return []string{}, err
	}
	debug("received:", resp.Tags)

	// valiade response
	if len(resp.Tags) == 0 {
		return []string{}, errors.New("tag " + tag + " does not exist!")
	}
	// parse and return response
	var out []string
	for _, s := range strings.Split(*resp.Tags[0].Value, ",") {
		out = append(out, strings.TrimSpace(s))
	}
	debug("access groups:", out)
	return out, nil
}

func getAccessKeys(hctx *awsHostContext, s3Bucket, s3Region string, accessGroups []string) ([]string, error) {
	// setup
	svc := s3.New(session.New(aws.NewConfig().WithCredentials(hctx.Creds).WithRegion(s3Region)))

	var out []string
	// fetch authorized_keys files
	for _, group := range accessGroups {
		params := &s3.GetObjectInput{
			Bucket: aws.String(s3Bucket),
			Key:    aws.String(group + "/authorized_keys"), // Required
		}
		debug("requesting authorized_keys file: s3://" + s3Bucket + "/" + group + "/authorized_keys")
		resp, err := svc.GetObject(params)
		if err != nil {
			debug(err.Error())
			continue
		}
		debug("retrieved, appending to output")
		if b, err := ioutil.ReadAll(resp.Body); err == nil {
			out = append(out, string(b))
		}
	}

	return out, nil
}
<commit_msg>Fix newline issue with multiple groups. Closes #8<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"

	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/s3"
)

type awsHostContext struct {
	Creds  *credentials.Credentials
	ID     string
	Region string
}

// DEBUG enable debugging output using -debug flag
var DEBUG bool

func debug(m ...interface{}) {
	if DEBUG {
		fmt.Println(m...)
	}
}

func main() {
	// currently not using any of the client flags
	_ = flag.String("u", "", "The user account")
	_ = flag.String("k", "", "The public key proffered by the client")
	_ = flag.String("t", "", "The key encryption type")
	_ = flag.String("f", "", "The fingerprint of the public key")
	groupTag := flag.String("group_tag", "access-groups", "The instance tag with csv ssh access groups in it")
	s3Bucket := flag.String("s3_bucket", "keys", "The bucket where the access group public keys are stored")
	s3Region := flag.String("s3_region", "eu-west-1", "The region in which the bucket is located")
	debugOutput := flag.Bool("debug", false, "Enable debug output")
	flag.Parse()

	if *debugOutput {
		DEBUG = true
		debug("debug output enabled")
		debug("grouptag: ", *groupTag)
		debug("bucket:   ", *s3Bucket)
		debug("region:   ", *s3Region)
		debug("")
	}

	// get aws host context
	hctx, err := getAwsHostContext()
	if err != nil {
		debug(err.Error())
		os.Exit(1)
	}

	// get the list of access groups the intance belongs to
	accessGroups, err := getInstanceAccessGroups(hctx, *groupTag)
	if err != nil {
		debug(err.Error())
		os.Exit(1)
	}

	// get the authorized_keys files for those instances
	authorizedKeys, err := getAccessKeys(hctx, *s3Bucket, *s3Region, accessGroups)
	if err != nil {
		debug(err.Error())
		os.Exit(1)
	}

	// print all authorized_keys
	debug("print all group keys:")
	for _, k := range authorizedKeys {
		fmt.Print(k)
	}
}

func getAwsHostContext() (*awsHostContext, error) {
	//setup
	hctx := new(awsHostContext)
	cfg := aws.NewConfig()
	svc := ec2metadata.New(session.New(), cfg)
	p := &ec2rolecreds.EC2RoleProvider{
		Client: svc,
	}
	//get creds
	hctx.Creds = credentials.NewCredentials(p)
	//get instance id
	id, err := svc.GetMetadata("instance-id")
	if err != nil {
		return nil, err
	}
	hctx.ID = id
	debug("instance-id:", id)
	//get region
	region, err := svc.Region()
	if err != nil {
		return nil, err
	}
	hctx.Region = region
	debug("region:     ", region)
	return hctx, nil
}

func getInstanceAccessGroups(hctx *awsHostContext, tag string) ([]string, error) {
	// setup
	svc := ec2.New(session.New(aws.NewConfig().WithCredentials(hctx.Creds).WithRegion(hctx.Region)))
	params := &ec2.DescribeTagsInput{
		Filters: []*ec2.Filter{
			{
				Name:   aws.String("resource-id"),
				Values: []*string{aws.String(hctx.ID)},
			},
			{
				Name:   aws.String("key"),
				Values: []*string{aws.String(tag)},
			},
		},
	}
	// fetch tags
	debug("requesting tag: " + tag + ", from resource: " + hctx.ID)
	resp, err := svc.DescribeTags(params)
	if err != nil {
		return []string{}, err
	}
	debug("received:", resp.Tags)

	// valiade response
	if len(resp.Tags) == 0 {
		return []string{}, errors.New("tag " + tag + " does not exist!")
	}
	// parse and return response
	var out []string
	for _, s := range strings.Split(*resp.Tags[0].Value, ",") {
		out = append(out, strings.TrimSpace(s))
	}
	debug("access groups:", out)
	return out, nil
}

func getAccessKeys(hctx *awsHostContext, s3Bucket, s3Region string, accessGroups []string) ([]string, error) {
	// setup
	svc := s3.New(session.New(aws.NewConfig().WithCredentials(hctx.Creds).WithRegion(s3Region)))

	var out []string
	// fetch authorized_keys files
	for _, group := range accessGroups {
		params := &s3.GetObjectInput{
			Bucket: aws.String(s3Bucket),
			Key:    aws.String(group + "/authorized_keys"), // Required
		}
		debug("requesting authorized_keys file: s3://" + s3Bucket + "/" + group + "/authorized_keys")
		resp, err := svc.GetObject(params)
		if err != nil {
			debug(err.Error())
			continue
		}
		debug("retrieved, appending to output")
		if b, err := ioutil.ReadAll(resp.Body); err == nil {
			out = append(out, "\n# Group: "+group+"\n"+string(b))
		}
	}

	return out, nil
}
<|endoftext|>


<commit_before>package etcdfs

import(
  "log"
  "strings"

  "github.com/hanwen/go-fuse/fuse"
  "github.com/hanwen/go-fuse/fuse/nodefs"
  "github.com/hanwen/go-fuse/fuse/pathfs"

  "github.com/coreos/go-etcd/etcd"
)

type EtcdFs struct {
  pathfs.FileSystem
  EtcdEndpoint string
}

func (me *EtcdFs) NewEtcdClient() *etcd.Client {
  return etcd.NewClient([]string{me.EtcdEndpoint})
}

func (me *EtcdFs) Unlink(name string, context *fuse.Context) (code fuse.Status) {
  if name == "" {
    return fuse.OK
  }

  _, err := me.NewEtcdClient().Delete(name, false)

  if err != nil {
    log.Println(err)
    return fuse.ENOENT
  }

  return fuse.OK
}

func (me *EtcdFs) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
  if name == "" {
    return fuse.OK
  }

  _, err := me.NewEtcdClient().RawDelete(name, true, true)

  if err != nil {
    log.Println(err)
    return fuse.ENOENT
  }

  return fuse.OK
}

func (me *EtcdFs) Create(name string, flags uint32, mode uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
  _, err := me.NewEtcdClient().Set(name, "", 0)

  if err != nil {
    log.Println("Create Error:", err)
    return nil, fuse.ENOENT
  }

  return NewEtcdFile(me.NewEtcdClient(), name), fuse.OK
}

func (me *EtcdFs) Mkdir(name string, mode uint32, context *fuse.Context) fuse.Status {
  if name == "" {
    return fuse.OK
  }

  _, err := me.NewEtcdClient().CreateDir(name, 0)

  if err != nil {
    log.Println(err)
    return fuse.ENOENT
  }

  return fuse.OK
}

func (me *EtcdFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
  if name == "" {
    return &fuse.Attr{
      Mode: fuse.S_IFDIR | 0666,
    }, fuse.OK
  }

  res, err := me.NewEtcdClient().Get(name, false, false)

  if err != nil {
    return nil, fuse.ENOENT
  }

  var attr fuse.Attr

  if res.Node.Dir {
    attr = fuse.Attr{
      Mode: fuse.S_IFDIR | 0666,
    }
  } else {
    attr = fuse.Attr{
      Mode: fuse.S_IFREG | 0666, Size: uint64(len(res.Node.Value)),
    }
  }

  return &attr, fuse.OK
}

func (me *EtcdFs) OpenDir(name string, context *fuse.Context) (c []fuse.DirEntry, code fuse.Status) {
  res, err := me.NewEtcdClient().Get(name, false, false)

  if err != nil {
    log.Println("OpenDir Error:", err)
    return nil, fuse.ENOENT
  }

  entries := []fuse.DirEntry{}

  for _, e := range(res.Node.Nodes) {
    chunks := strings.Split(e.Key, "/")
    file := chunks[len(chunks)-1]
    if e.Dir {
      entries = append(entries, fuse.DirEntry{Name: file, Mode: fuse.S_IFDIR})
    } else {
      entries = append(entries, fuse.DirEntry{Name: file, Mode: fuse.S_IFREG})
    }
  }

  return entries, fuse.OK
}

func (me *EtcdFs) Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
  _, err := me.NewEtcdClient().Get(name, false, false)

  if err != nil {
    log.Println("Open Error:", err)
    return nil, fuse.ENOENT
  }

  return NewEtcdFile(me.NewEtcdClient(), name), fuse.OK
}

<commit_msg>Minimal (non-atomic) rename.<commit_after>package etcdfs

import(
  "log"
  "strings"

  "github.com/hanwen/go-fuse/fuse"
  "github.com/hanwen/go-fuse/fuse/nodefs"
  "github.com/hanwen/go-fuse/fuse/pathfs"

  "github.com/coreos/go-etcd/etcd"
)

type EtcdFs struct {
  pathfs.FileSystem
  EtcdEndpoint string
}

func (me *EtcdFs) NewEtcdClient() *etcd.Client {
  return etcd.NewClient([]string{me.EtcdEndpoint})
}

func (me *EtcdFs) Unlink(name string, context *fuse.Context) (code fuse.Status) {
  if name == "" {
    return fuse.OK
  }

  _, err := me.NewEtcdClient().Delete(name, false)

  if err != nil {
    log.Println(err)
    return fuse.ENOENT
  }

  return fuse.OK
}

func (me *EtcdFs) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
  if name == "" {
    return fuse.OK
  }

  _, err := me.NewEtcdClient().RawDelete(name, true, true)

  if err != nil {
    log.Println(err)
    return fuse.ENOENT
  }

  return fuse.OK
}

func (me *EtcdFs) Create(name string, flags uint32, mode uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
  _, err := me.NewEtcdClient().Set(name, "", 0)

  if err != nil {
    log.Println("Create Error:", err)
    return nil, fuse.ENOENT
  }

  return NewEtcdFile(me.NewEtcdClient(), name), fuse.OK
}

func (me *EtcdFs) Mkdir(name string, mode uint32, context *fuse.Context) fuse.Status {
  if name == "" {
    return fuse.OK
  }

  _, err := me.NewEtcdClient().CreateDir(name, 0)

  if err != nil {
    log.Println(err)
    return fuse.ENOENT
  }

  return fuse.OK
}

func (me *EtcdFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
  if name == "" {
    return &fuse.Attr{
      Mode: fuse.S_IFDIR | 0666,
    }, fuse.OK
  }

  res, err := me.NewEtcdClient().Get(name, false, false)

  if err != nil {
    return nil, fuse.ENOENT
  }

  var attr fuse.Attr

  if res.Node.Dir {
    attr = fuse.Attr{
      Mode: fuse.S_IFDIR | 0666,
    }
  } else {
    attr = fuse.Attr{
      Mode: fuse.S_IFREG | 0666, Size: uint64(len(res.Node.Value)),
    }
  }

  return &attr, fuse.OK
}

func (me *EtcdFs) OpenDir(name string, context *fuse.Context) (c []fuse.DirEntry, code fuse.Status) {
  res, err := me.NewEtcdClient().Get(name, false, false)

  if err != nil {
    log.Println("OpenDir Error:", err)
    return nil, fuse.ENOENT
  }

  entries := []fuse.DirEntry{}

  for _, e := range(res.Node.Nodes) {
    chunks := strings.Split(e.Key, "/")
    file := chunks[len(chunks)-1]
    if e.Dir {
      entries = append(entries, fuse.DirEntry{Name: file, Mode: fuse.S_IFDIR})
    } else {
      entries = append(entries, fuse.DirEntry{Name: file, Mode: fuse.S_IFREG})
    }
  }

  return entries, fuse.OK
}

func (me *EtcdFs) Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
  _, err := me.NewEtcdClient().Get(name, false, false)

  if err != nil {
    log.Println("Open Error:", err)
    return nil, fuse.ENOENT
  }

  return NewEtcdFile(me.NewEtcdClient(), name), fuse.OK
}

func (me *EtcdFs) Rename(oldName string, newName string, context *fuse.Context) (code fuse.Status) {
  etcdClient, err := me.NewEtcdClient()
  content := etcdClient.Get(oldName, false, false)
  _, err := etcdClient().Set(newName, content, 0)
  _, err := etcdClient.Delete(oldName, false)
}
<|endoftext|>


<commit_before>// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package api

import (
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	ldap "github.com/go-ldap/ldap/v3"
	"github.com/syncthing/syncthing/lib/config"
	"github.com/syncthing/syncthing/lib/events"
	"github.com/syncthing/syncthing/lib/rand"
	"github.com/syncthing/syncthing/lib/sync"
	"golang.org/x/crypto/bcrypt"
)

var (
	sessions    = make(map[string]bool)
	sessionsMut = sync.NewMutex()
)

func emitLoginAttempt(success bool, username, address string, evLogger events.Logger) {
	evLogger.Log(events.LoginAttempt, map[string]interface{}{
		"success":       success,
		"username":      username,
		"remoteAddress": address,
	})
}

func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfiguration, ldapCfg config.LDAPConfiguration, next http.Handler, evLogger events.Logger) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if guiCfg.IsValidAPIKey(r.Header.Get("X-API-Key")) {
			next.ServeHTTP(w, r)
			return
		}

		cookie, err := r.Cookie(cookieName)
		if err == nil && cookie != nil {
			sessionsMut.Lock()
			_, ok := sessions[cookie.Value]
			sessionsMut.Unlock()
			if ok {
				next.ServeHTTP(w, r)
				return
			}
		}

		l.Debugln("Sessionless HTTP request with authentication; this is expensive.")

		error := func() {
			time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond)
			w.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"")
			http.Error(w, "Not Authorized", http.StatusUnauthorized)
		}

		hdr := r.Header.Get("Authorization")
		if !strings.HasPrefix(hdr, "Basic ") {
			error()
			return
		}

		hdr = hdr[6:]
		bs, err := base64.StdEncoding.DecodeString(hdr)
		if err != nil {
			error()
			return
		}

		fields := bytes.SplitN(bs, []byte(":"), 2)
		if len(fields) != 2 {
			error()
			return
		}

		username := string(fields[0])
		password := string(fields[1])

		authOk := auth(username, password, guiCfg, ldapCfg)
		if !authOk {
			usernameIso := string(iso88591ToUTF8([]byte(username)))
			passwordIso := string(iso88591ToUTF8([]byte(password)))
			authOk = auth(usernameIso, passwordIso, guiCfg, ldapCfg)
			if authOk {
				username = usernameIso
			}
		}

		if !authOk {
			emitLoginAttempt(false, username, r.RemoteAddr, evLogger)
			error()
			return
		}

		sessionid := rand.String(32)
		sessionsMut.Lock()
		sessions[sessionid] = true
		sessionsMut.Unlock()
		http.SetCookie(w, &http.Cookie{
			Name:   cookieName,
			Value:  sessionid,
			MaxAge: 0,
		})

		emitLoginAttempt(true, username, r.RemoteAddr, evLogger)
		next.ServeHTTP(w, r)
	})
}

func auth(username string, password string, guiCfg config.GUIConfiguration, ldapCfg config.LDAPConfiguration) bool {
	if guiCfg.AuthMode == config.AuthModeLDAP {
		return authLDAP(username, password, ldapCfg)
	} else {
		return authStatic(username, password, guiCfg.User, guiCfg.Password)
	}
}

func authStatic(username string, password string, configUser string, configPassword string) bool {
	configPasswordBytes := []byte(configPassword)
	passwordBytes := []byte(password)
	return bcrypt.CompareHashAndPassword(configPasswordBytes, passwordBytes) == nil && username == configUser
}

func authLDAP(username string, password string, cfg config.LDAPConfiguration) bool {
	address := cfg.Address
	hostname, _, err := net.SplitHostPort(address)
	if err != nil {
		hostname = address
	}
	var connection *ldap.Conn
	if cfg.Transport == config.LDAPTransportTLS {
		connection, err = ldap.DialTLS("tcp", address, &tls.Config{
			ServerName:         hostname,
			InsecureSkipVerify: cfg.InsecureSkipVerify,
		})
	} else {
		connection, err = ldap.Dial("tcp", address)
	}

	if err != nil {
		l.Warnln("LDAP Dial:", err)
		return false
	}

	if cfg.Transport == config.LDAPTransportStartTLS {
		err = connection.StartTLS(&tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify})
		if err != nil {
			l.Warnln("LDAP Start TLS:", err)
			return false
		}
	}

	defer connection.Close()

	err = connection.Bind(fmt.Sprintf(cfg.BindDN, username), password)
	if err != nil {
		l.Warnln("LDAP Bind:", err)
		return false
	}

	if cfg.SearchFilter == "" && cfg.SearchBaseDN == "" {
		// We're done here.
		return true
	}

	if cfg.SearchFilter == "" || cfg.SearchBaseDN == "" {
		l.Warnln("LDAP configuration: both searchFilter and searchBaseDN must be set, or neither.")
		return false
	}

	// If a search filter and search base is set we do an LDAP search for
	// the user. If this matches precisely one user then we are good to go.
	// The search filter uses the same %s interpolation as the bind DN.

	searchString := fmt.Sprintf(cfg.SearchFilter, username)
	const sizeLimit = 2  // we search for up to two users -- we only want to match one, so getting any number >1 is a failure.
	const timeLimit = 60 // Search for up to a minute...
	searchReq := ldap.NewSearchRequest(cfg.SearchBaseDN, ldap.ScopeWholeSubtree, ldap.DerefFindingBaseObj, sizeLimit, timeLimit, false, searchString, nil, nil)

	res, err := connection.Search(searchReq)
	if err != nil {
		l.Warnln("LDAP Search:", err)
		return false
	}
	if len(res.Entries) != 1 {
		l.Infof("Wrong number of LDAP search results, %d != 1", len(res.Entries))
		return false
	}

	return true
}

// Convert an ISO-8859-1 encoded byte string to UTF-8. Works by the
// principle that ISO-8859-1 bytes are equivalent to unicode code points,
// that a rune slice is a list of code points, and that stringifying a slice
// of runes generates UTF-8 in Go.
func iso88591ToUTF8(s []byte) []byte {
	runes := make([]rune, len(s))
	for i := range s {
		runes[i] = rune(s[i])
	}
	return []byte(string(runes))
}
<commit_msg>api: Log API authorization failures. (#7575)<commit_after>// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package api

import (
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	ldap "github.com/go-ldap/ldap/v3"
	"github.com/syncthing/syncthing/lib/config"
	"github.com/syncthing/syncthing/lib/events"
	"github.com/syncthing/syncthing/lib/rand"
	"github.com/syncthing/syncthing/lib/sync"
	"golang.org/x/crypto/bcrypt"
)

var (
	sessions    = make(map[string]bool)
	sessionsMut = sync.NewMutex()
)

func emitLoginAttempt(success bool, username, address string, evLogger events.Logger) {
	evLogger.Log(events.LoginAttempt, map[string]interface{}{
		"success":       success,
		"username":      username,
		"remoteAddress": address,
	})
	if !success {
		l.Infof("Wrong credentials supplied during API authorization from %s", address)
	}
}

func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfiguration, ldapCfg config.LDAPConfiguration, next http.Handler, evLogger events.Logger) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if guiCfg.IsValidAPIKey(r.Header.Get("X-API-Key")) {
			next.ServeHTTP(w, r)
			return
		}

		cookie, err := r.Cookie(cookieName)
		if err == nil && cookie != nil {
			sessionsMut.Lock()
			_, ok := sessions[cookie.Value]
			sessionsMut.Unlock()
			if ok {
				next.ServeHTTP(w, r)
				return
			}
		}

		l.Debugln("Sessionless HTTP request with authentication; this is expensive.")

		error := func() {
			time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond)
			w.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"")
			http.Error(w, "Not Authorized", http.StatusUnauthorized)
		}

		hdr := r.Header.Get("Authorization")
		if !strings.HasPrefix(hdr, "Basic ") {
			error()
			return
		}

		hdr = hdr[6:]
		bs, err := base64.StdEncoding.DecodeString(hdr)
		if err != nil {
			error()
			return
		}

		fields := bytes.SplitN(bs, []byte(":"), 2)
		if len(fields) != 2 {
			error()
			return
		}

		username := string(fields[0])
		password := string(fields[1])

		authOk := auth(username, password, guiCfg, ldapCfg)
		if !authOk {
			usernameIso := string(iso88591ToUTF8([]byte(username)))
			passwordIso := string(iso88591ToUTF8([]byte(password)))
			authOk = auth(usernameIso, passwordIso, guiCfg, ldapCfg)
			if authOk {
				username = usernameIso
			}
		}

		if !authOk {
			emitLoginAttempt(false, username, r.RemoteAddr, evLogger)
			error()
			return
		}

		sessionid := rand.String(32)
		sessionsMut.Lock()
		sessions[sessionid] = true
		sessionsMut.Unlock()
		http.SetCookie(w, &http.Cookie{
			Name:   cookieName,
			Value:  sessionid,
			MaxAge: 0,
		})

		emitLoginAttempt(true, username, r.RemoteAddr, evLogger)
		next.ServeHTTP(w, r)
	})
}

func auth(username string, password string, guiCfg config.GUIConfiguration, ldapCfg config.LDAPConfiguration) bool {
	if guiCfg.AuthMode == config.AuthModeLDAP {
		return authLDAP(username, password, ldapCfg)
	} else {
		return authStatic(username, password, guiCfg.User, guiCfg.Password)
	}
}

func authStatic(username string, password string, configUser string, configPassword string) bool {
	configPasswordBytes := []byte(configPassword)
	passwordBytes := []byte(password)
	return bcrypt.CompareHashAndPassword(configPasswordBytes, passwordBytes) == nil && username == configUser
}

func authLDAP(username string, password string, cfg config.LDAPConfiguration) bool {
	address := cfg.Address
	hostname, _, err := net.SplitHostPort(address)
	if err != nil {
		hostname = address
	}
	var connection *ldap.Conn
	if cfg.Transport == config.LDAPTransportTLS {
		connection, err = ldap.DialTLS("tcp", address, &tls.Config{
			ServerName:         hostname,
			InsecureSkipVerify: cfg.InsecureSkipVerify,
		})
	} else {
		connection, err = ldap.Dial("tcp", address)
	}

	if err != nil {
		l.Warnln("LDAP Dial:", err)
		return false
	}

	if cfg.Transport == config.LDAPTransportStartTLS {
		err = connection.StartTLS(&tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify})
		if err != nil {
			l.Warnln("LDAP Start TLS:", err)
			return false
		}
	}

	defer connection.Close()

	err = connection.Bind(fmt.Sprintf(cfg.BindDN, username), password)
	if err != nil {
		l.Warnln("LDAP Bind:", err)
		return false
	}

	if cfg.SearchFilter == "" && cfg.SearchBaseDN == "" {
		// We're done here.
		return true
	}

	if cfg.SearchFilter == "" || cfg.SearchBaseDN == "" {
		l.Warnln("LDAP configuration: both searchFilter and searchBaseDN must be set, or neither.")
		return false
	}

	// If a search filter and search base is set we do an LDAP search for
	// the user. If this matches precisely one user then we are good to go.
	// The search filter uses the same %s interpolation as the bind DN.

	searchString := fmt.Sprintf(cfg.SearchFilter, username)
	const sizeLimit = 2  // we search for up to two users -- we only want to match one, so getting any number >1 is a failure.
	const timeLimit = 60 // Search for up to a minute...
	searchReq := ldap.NewSearchRequest(cfg.SearchBaseDN, ldap.ScopeWholeSubtree, ldap.DerefFindingBaseObj, sizeLimit, timeLimit, false, searchString, nil, nil)

	res, err := connection.Search(searchReq)
	if err != nil {
		l.Warnln("LDAP Search:", err)
		return false
	}
	if len(res.Entries) != 1 {
		l.Infof("Wrong number of LDAP search results, %d != 1", len(res.Entries))
		return false
	}

	return true
}

// Convert an ISO-8859-1 encoded byte string to UTF-8. Works by the
// principle that ISO-8859-1 bytes are equivalent to unicode code points,
// that a rune slice is a list of code points, and that stringifying a slice
// of runes generates UTF-8 in Go.
func iso88591ToUTF8(s []byte) []byte {
	runes := make([]rune, len(s))
	for i := range s {
		runes[i] = rune(s[i])
	}
	return []byte(string(runes))
}
<|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 acmechallenges

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"
	"k8s.io/client-go/util/workqueue"

	"github.com/jetstack/cert-manager/pkg/acme/accounts"
	cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	cmacmelisters "github.com/jetstack/cert-manager/pkg/client/listers/acme/v1"
	cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1"
	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/controller/acmechallenges/scheduler"
	"github.com/jetstack/cert-manager/pkg/issuer"
	"github.com/jetstack/cert-manager/pkg/issuer/acme/dns"
	"github.com/jetstack/cert-manager/pkg/issuer/acme/http"
	logf "github.com/jetstack/cert-manager/pkg/logs"
)

type controller struct {
	// issuer helper is used to obtain references to issuers, used by Sync()
	helper issuer.Helper

	// used to fetch ACME clients used in the controller
	accountRegistry accounts.Getter

	// all the listers used by this controller
	challengeLister     cmacmelisters.ChallengeLister
	issuerLister        cmlisters.IssuerLister
	clusterIssuerLister cmlisters.ClusterIssuerLister
	secretLister        corelisters.SecretLister

	// ACME challenge solvers are instantiated once at the time of controller
	// construction.
	// This also allows for easy mocking of the different challenge mechanisms.
	dnsSolver  solver
	httpSolver solver
	// scheduler marks challenges as Processing=true if they can be scheduled
	// for processing. This job runs periodically every N seconds, so it cannot
	// be constructed as a traditional controller.
	scheduler *scheduler.Scheduler

	// used to record Events about resources to the API
	recorder record.EventRecorder
	// clientset used to update cert-manager API resources
	cmClient cmclient.Interface

	// maintain a reference to the workqueue for this controller
	// so the handleOwnedResource method can enqueue resources
	queue workqueue.RateLimitingInterface

	// logger to be used by this controller
	log logr.Logger

	dns01Nameservers []string

	DNS01CheckRetryPeriod time.Duration
}

func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) {
	// construct a new named logger to be reused throughout the controller
	c.log = logf.FromContext(ctx.RootContext, ControllerName)

	// create a queue used to queue up items to be processed
	c.queue = workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*5, time.Minute*30), ControllerName)

	// obtain references to all the informers used by this controller
	challengeInformer := ctx.SharedInformerFactory.Acme().V1().Challenges()
	issuerInformer := ctx.SharedInformerFactory.Certmanager().V1().Issuers()
	secretInformer := ctx.KubeSharedInformerFactory.Core().V1().Secrets()
	// we register these informers here so the HTTP01 solver has a synced
	// cache when managing pod/service/ingress resources
	podInformer := ctx.KubeSharedInformerFactory.Core().V1().Pods()
	serviceInformer := ctx.KubeSharedInformerFactory.Core().V1().Services()
	ingressInformer := ctx.KubeSharedInformerFactory.Extensions().V1beta1().Ingresses()
	// build a list of InformerSynced functions that will be returned by the Register method.
	// the controller will only begin processing items once all of these informers have synced.
	mustSync := []cache.InformerSynced{
		challengeInformer.Informer().HasSynced,
		issuerInformer.Informer().HasSynced,
		secretInformer.Informer().HasSynced,
		podInformer.Informer().HasSynced,
		serviceInformer.Informer().HasSynced,
		ingressInformer.Informer().HasSynced,
	}

	// set all the references to the listers for used by the Sync function
	c.challengeLister = challengeInformer.Lister()
	c.issuerLister = issuerInformer.Lister()
	c.secretLister = secretInformer.Lister()

	// if we are running in non-namespaced mode (i.e. --namespace=""), we also
	// register event handlers and obtain a lister for clusterissuers.
	if ctx.Namespace == "" {
		clusterIssuerInformer := ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers()
		mustSync = append(mustSync, clusterIssuerInformer.Informer().HasSynced)
		c.clusterIssuerLister = clusterIssuerInformer.Lister()
	}

	// register handler functions
	challengeInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue})

	c.helper = issuer.NewHelper(c.issuerLister, c.clusterIssuerLister)
	c.scheduler = scheduler.New(logf.NewContext(ctx.RootContext, c.log), c.challengeLister, ctx.SchedulerOptions.MaxConcurrentChallenges)
	c.recorder = ctx.Recorder
	c.cmClient = ctx.CMClient
	c.httpSolver = http.NewSolver(ctx)
	c.accountRegistry = ctx.ACMEOptions.AccountRegistry

	var err error
	c.dnsSolver, err = dns.NewSolver(ctx)
	if err != nil {
		return nil, nil, err
	}

	// read options from context
	c.dns01Nameservers = ctx.ACMEOptions.DNS01Nameservers
	c.DNS01CheckRetryPeriod = ctx.ACMEOptions.DNS01CheckRetryPeriod

	return c.queue, mustSync, nil
}

// MaxChallengesPerSchedule is the maximum number of challenges that can be
// scheduled with a single call to the scheduler.
// This provides a very crude rate limit on how many challenges we will schedule
// per second. It may be better to remove this altogether in favour of some
// other method of rate limiting creations.
// TODO: make this configurable
const MaxChallengesPerSchedule = 20

// runScheduler will execute the scheduler's ScheduleN function to determine
// which, if any, challenges should be rescheduled.
// TODO: it should also only re-run the scheduler if a change to challenges has
// been observed, to save needless work
func (c *controller) runScheduler(ctx context.Context) {
	log := logf.FromContext(ctx, "scheduler")

	toSchedule, err := c.scheduler.ScheduleN(MaxChallengesPerSchedule)
	if err != nil {
		log.Error(err, "error determining set of challenges that should be scheduled for processing")
		return
	}

	for _, ch := range toSchedule {
		log := logf.WithResource(log, ch)
		ch = ch.DeepCopy()
		ch.Status.Processing = true

		_, err := c.cmClient.AcmeV1().Challenges(ch.Namespace).UpdateStatus(context.TODO(), ch, metav1.UpdateOptions{})
		if err != nil {
			log.Error(err, "error scheduling challenge for processing")
			return
		}

		c.recorder.Event(ch, corev1.EventTypeNormal, "Started", "Challenge scheduled for processing")
	}

	if len(toSchedule) > 0 {
		log.V(logf.DebugLevel).Info("scheduled challenges for processing", "number_scheduled", len(toSchedule))
	}
}

func (c *controller) ProcessItem(ctx context.Context, key string) error {
	log := logf.FromContext(ctx)
	namespace, name, err := cache.SplitMetaNamespaceKey(key)
	if err != nil {
		log.Error(err, "invalid resource key")
		return nil
	}

	ch, err := c.challengeLister.Challenges(namespace).Get(name)

	if err != nil {
		if k8sErrors.IsNotFound(err) {
			log.Error(err, "challenge in work queue no longer exists")
			return nil
		}

		return err
	}

	ctx = logf.NewContext(ctx, logf.WithResource(log, ch))
	return c.Sync(ctx, ch)
}

const (
	ControllerName = "challenges"
)

func init() {
	controllerpkg.Register(ControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
		c := &controller{}
		return controllerpkg.NewBuilder(ctx, ControllerName).
			For(c).
			With(c.runScheduler, time.Second).
			Complete()
	})
}
<commit_msg>Switch informer to networking<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 acmechallenges

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"
	"k8s.io/client-go/util/workqueue"

	"github.com/jetstack/cert-manager/pkg/acme/accounts"
	cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	cmacmelisters "github.com/jetstack/cert-manager/pkg/client/listers/acme/v1"
	cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1"
	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/controller/acmechallenges/scheduler"
	"github.com/jetstack/cert-manager/pkg/issuer"
	"github.com/jetstack/cert-manager/pkg/issuer/acme/dns"
	"github.com/jetstack/cert-manager/pkg/issuer/acme/http"
	logf "github.com/jetstack/cert-manager/pkg/logs"
)

type controller struct {
	// issuer helper is used to obtain references to issuers, used by Sync()
	helper issuer.Helper

	// used to fetch ACME clients used in the controller
	accountRegistry accounts.Getter

	// all the listers used by this controller
	challengeLister     cmacmelisters.ChallengeLister
	issuerLister        cmlisters.IssuerLister
	clusterIssuerLister cmlisters.ClusterIssuerLister
	secretLister        corelisters.SecretLister

	// ACME challenge solvers are instantiated once at the time of controller
	// construction.
	// This also allows for easy mocking of the different challenge mechanisms.
	dnsSolver  solver
	httpSolver solver
	// scheduler marks challenges as Processing=true if they can be scheduled
	// for processing. This job runs periodically every N seconds, so it cannot
	// be constructed as a traditional controller.
	scheduler *scheduler.Scheduler

	// used to record Events about resources to the API
	recorder record.EventRecorder
	// clientset used to update cert-manager API resources
	cmClient cmclient.Interface

	// maintain a reference to the workqueue for this controller
	// so the handleOwnedResource method can enqueue resources
	queue workqueue.RateLimitingInterface

	// logger to be used by this controller
	log logr.Logger

	dns01Nameservers []string

	DNS01CheckRetryPeriod time.Duration
}

func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) {
	// construct a new named logger to be reused throughout the controller
	c.log = logf.FromContext(ctx.RootContext, ControllerName)

	// create a queue used to queue up items to be processed
	c.queue = workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*5, time.Minute*30), ControllerName)

	// obtain references to all the informers used by this controller
	challengeInformer := ctx.SharedInformerFactory.Acme().V1().Challenges()
	issuerInformer := ctx.SharedInformerFactory.Certmanager().V1().Issuers()
	secretInformer := ctx.KubeSharedInformerFactory.Core().V1().Secrets()
	// we register these informers here so the HTTP01 solver has a synced
	// cache when managing pod/service/ingress resources
	podInformer := ctx.KubeSharedInformerFactory.Core().V1().Pods()
	serviceInformer := ctx.KubeSharedInformerFactory.Core().V1().Services()
	ingressInformer := ctx.KubeSharedInformerFactory.Networking().V1beta1().Ingresses()
	// build a list of InformerSynced functions that will be returned by the Register method.
	// the controller will only begin processing items once all of these informers have synced.
	mustSync := []cache.InformerSynced{
		challengeInformer.Informer().HasSynced,
		issuerInformer.Informer().HasSynced,
		secretInformer.Informer().HasSynced,
		podInformer.Informer().HasSynced,
		serviceInformer.Informer().HasSynced,
		ingressInformer.Informer().HasSynced,
	}

	// set all the references to the listers for used by the Sync function
	c.challengeLister = challengeInformer.Lister()
	c.issuerLister = issuerInformer.Lister()
	c.secretLister = secretInformer.Lister()

	// if we are running in non-namespaced mode (i.e. --namespace=""), we also
	// register event handlers and obtain a lister for clusterissuers.
	if ctx.Namespace == "" {
		clusterIssuerInformer := ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers()
		mustSync = append(mustSync, clusterIssuerInformer.Informer().HasSynced)
		c.clusterIssuerLister = clusterIssuerInformer.Lister()
	}

	// register handler functions
	challengeInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue})

	c.helper = issuer.NewHelper(c.issuerLister, c.clusterIssuerLister)
	c.scheduler = scheduler.New(logf.NewContext(ctx.RootContext, c.log), c.challengeLister, ctx.SchedulerOptions.MaxConcurrentChallenges)
	c.recorder = ctx.Recorder
	c.cmClient = ctx.CMClient
	c.httpSolver = http.NewSolver(ctx)
	c.accountRegistry = ctx.ACMEOptions.AccountRegistry

	var err error
	c.dnsSolver, err = dns.NewSolver(ctx)
	if err != nil {
		return nil, nil, err
	}

	// read options from context
	c.dns01Nameservers = ctx.ACMEOptions.DNS01Nameservers
	c.DNS01CheckRetryPeriod = ctx.ACMEOptions.DNS01CheckRetryPeriod

	return c.queue, mustSync, nil
}

// MaxChallengesPerSchedule is the maximum number of challenges that can be
// scheduled with a single call to the scheduler.
// This provides a very crude rate limit on how many challenges we will schedule
// per second. It may be better to remove this altogether in favour of some
// other method of rate limiting creations.
// TODO: make this configurable
const MaxChallengesPerSchedule = 20

// runScheduler will execute the scheduler's ScheduleN function to determine
// which, if any, challenges should be rescheduled.
// TODO: it should also only re-run the scheduler if a change to challenges has
// been observed, to save needless work
func (c *controller) runScheduler(ctx context.Context) {
	log := logf.FromContext(ctx, "scheduler")

	toSchedule, err := c.scheduler.ScheduleN(MaxChallengesPerSchedule)
	if err != nil {
		log.Error(err, "error determining set of challenges that should be scheduled for processing")
		return
	}

	for _, ch := range toSchedule {
		log := logf.WithResource(log, ch)
		ch = ch.DeepCopy()
		ch.Status.Processing = true

		_, err := c.cmClient.AcmeV1().Challenges(ch.Namespace).UpdateStatus(context.TODO(), ch, metav1.UpdateOptions{})
		if err != nil {
			log.Error(err, "error scheduling challenge for processing")
			return
		}

		c.recorder.Event(ch, corev1.EventTypeNormal, "Started", "Challenge scheduled for processing")
	}

	if len(toSchedule) > 0 {
		log.V(logf.DebugLevel).Info("scheduled challenges for processing", "number_scheduled", len(toSchedule))
	}
}

func (c *controller) ProcessItem(ctx context.Context, key string) error {
	log := logf.FromContext(ctx)
	namespace, name, err := cache.SplitMetaNamespaceKey(key)
	if err != nil {
		log.Error(err, "invalid resource key")
		return nil
	}

	ch, err := c.challengeLister.Challenges(namespace).Get(name)

	if err != nil {
		if k8sErrors.IsNotFound(err) {
			log.Error(err, "challenge in work queue no longer exists")
			return nil
		}

		return err
	}

	ctx = logf.NewContext(ctx, logf.WithResource(log, ch))
	return c.Sync(ctx, ch)
}

const (
	ControllerName = "challenges"
)

func init() {
	controllerpkg.Register(ControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
		c := &controller{}
		return controllerpkg.NewBuilder(ctx, ControllerName).
			For(c).
			With(c.runScheduler, time.Second).
			Complete()
	})
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package testing

import (
	"fmt"
	"math/rand"
	"sync"
	"time"

	"github.com/containerd/containerd"
	"github.com/containerd/containerd/api/services/execution"
	"github.com/containerd/containerd/api/types/container"
	googleprotobuf "github.com/golang/protobuf/ptypes/empty"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
)

var containerNotExistError = grpc.Errorf(codes.Unknown, containerd.ErrContainerNotExist.Error())

// CalledDetail is the struct contains called function name and arguments.
type CalledDetail struct {
	// Name of the function called.
	Name string
	// Argument of the function called.
	Argument interface{}
}

var _ execution.ContainerService_EventsClient = &EventClient{}

// EventClient is a test implementation of execution.ContainerService_EventsClient
type EventClient struct {
	Events chan *container.Event
	grpc.ClientStream
}

// Recv is a test implementation of Recv
func (cli *EventClient) Recv() (*container.Event, error) {
	event, ok := <-cli.Events
	if !ok {
		return nil, fmt.Errorf("event channel closed")
	}
	return event, nil
}

// FakeExecutionClient is a simple fake execution client, so that cri-containerd
// can be run for testing without requiring a real containerd setup.
type FakeExecutionClient struct {
	sync.Mutex
	called        []CalledDetail
	errors        map[string]error
	ContainerList map[string]container.Container
	eventsQueue   chan *container.Event
	eventClients  []*EventClient
}

var _ execution.ContainerServiceClient = &FakeExecutionClient{}

// NewFakeExecutionClient creates a FakeExecutionClient
func NewFakeExecutionClient() *FakeExecutionClient {
	return &FakeExecutionClient{
		errors:        make(map[string]error),
		ContainerList: make(map[string]container.Container),
	}
}

// Stop the fake execution service. Needed when event is enabled.
func (f *FakeExecutionClient) Stop() {
	if f.eventsQueue != nil {
		close(f.eventsQueue)
	}
	f.Lock()
	defer f.Unlock()
	for _, client := range f.eventClients {
		close(client.Events)
	}
}

// WithEvents setup events publisher for FakeExecutionClient
func (f *FakeExecutionClient) WithEvents() *FakeExecutionClient {
	f.eventsQueue = make(chan *container.Event, 1024)
	go func() {
		for e := range f.eventsQueue {
			f.Lock()
			for _, client := range f.eventClients {
				client.Events <- e
			}
			f.Unlock()
		}
	}()
	return f
}

func (f *FakeExecutionClient) getError(op string) error {
	err, ok := f.errors[op]
	if ok {
		delete(f.errors, op)
		return err
	}
	return nil
}

// InjectError inject error for call
func (f *FakeExecutionClient) InjectError(fn string, err error) {
	f.Lock()
	defer f.Unlock()
	f.errors[fn] = err
}

// InjectErrors inject errors for calls
func (f *FakeExecutionClient) InjectErrors(errs map[string]error) {
	f.Lock()
	defer f.Unlock()
	for fn, err := range errs {
		f.errors[fn] = err
	}
}

// ClearErrors clear errors for call
func (f *FakeExecutionClient) ClearErrors() {
	f.Lock()
	defer f.Unlock()
	f.errors = make(map[string]error)
}

func generatePid() uint32 {
	rand.Seed(time.Now().Unix())
	randPid := uint32(rand.Intn(1000))
	return randPid
}

func (f *FakeExecutionClient) sendEvent(event *container.Event) {
	if f.eventsQueue != nil {
		f.eventsQueue <- event
	}
}

func (f *FakeExecutionClient) appendCalled(name string, argument interface{}) {
	call := CalledDetail{Name: name, Argument: argument}
	f.called = append(f.called, call)
}

// GetCalledNames get names of call
func (f *FakeExecutionClient) GetCalledNames() []string {
	f.Lock()
	defer f.Unlock()
	names := []string{}
	for _, detail := range f.called {
		names = append(names, detail.Name)
	}
	return names
}

// ClearCalls clear all call detail.
func (f *FakeExecutionClient) ClearCalls() {
	f.Lock()
	defer f.Unlock()
	f.called = []CalledDetail{}
}

// GetCalledDetails get detail of each call.
func (f *FakeExecutionClient) GetCalledDetails() []CalledDetail {
	f.Lock()
	defer f.Unlock()
	// Copy the list and return.
	return append([]CalledDetail{}, f.called...)
}

// SetFakeContainers injects fake containers.
func (f *FakeExecutionClient) SetFakeContainers(containers []container.Container) {
	f.Lock()
	defer f.Unlock()
	for _, c := range containers {
		f.ContainerList[c.ID] = c
	}
}

// Create is a test implementation of execution.Create.
func (f *FakeExecutionClient) Create(ctx context.Context, createOpts *execution.CreateRequest, opts ...grpc.CallOption) (*execution.CreateResponse, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("create", createOpts)
	if err := f.getError("create"); err != nil {
		return nil, err
	}
	_, ok := f.ContainerList[createOpts.ID]
	if ok {
		return nil, containerd.ErrContainerExists
	}
	pid := generatePid()
	f.ContainerList[createOpts.ID] = container.Container{
		ID:     createOpts.ID,
		Pid:    pid,
		Status: container.Status_CREATED,
	}
	f.sendEvent(&container.Event{
		ID:   createOpts.ID,
		Type: container.Event_CREATE,
		Pid:  pid,
	})
	return &execution.CreateResponse{
		ID:  createOpts.ID,
		Pid: pid,
	}, nil
}

// Start is a test implementation of execution.Start
func (f *FakeExecutionClient) Start(ctx context.Context, startOpts *execution.StartRequest, opts ...grpc.CallOption) (*googleprotobuf.Empty, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("start", startOpts)
	if err := f.getError("start"); err != nil {
		return nil, err
	}
	c, ok := f.ContainerList[startOpts.ID]
	if !ok {
		return nil, containerNotExistError
	}
	f.sendEvent(&container.Event{
		ID:   c.ID,
		Type: container.Event_START,
		Pid:  c.Pid,
	})
	switch c.Status {
	case container.Status_CREATED:
		c.Status = container.Status_RUNNING
		f.ContainerList[startOpts.ID] = c
		return &googleprotobuf.Empty{}, nil
	case container.Status_STOPPED:
		return &googleprotobuf.Empty{}, fmt.Errorf("cannot start a container that has stopped")
	case container.Status_RUNNING:
		return &googleprotobuf.Empty{}, fmt.Errorf("cannot start an already running container")
	default:
		return &googleprotobuf.Empty{}, fmt.Errorf("cannot start a container in the %s state", c.Status)
	}
}

// Delete is a test implementation of execution.Delete
func (f *FakeExecutionClient) Delete(ctx context.Context, deleteOpts *execution.DeleteRequest, opts ...grpc.CallOption) (*execution.DeleteResponse, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("delete", deleteOpts)
	if err := f.getError("delete"); err != nil {
		return nil, err
	}
	c, ok := f.ContainerList[deleteOpts.ID]
	if !ok {
		return nil, containerNotExistError
	}
	delete(f.ContainerList, deleteOpts.ID)
	f.sendEvent(&container.Event{
		ID:   c.ID,
		Type: container.Event_EXIT,
		Pid:  c.Pid,
	})
	return nil, nil
}

// Info is a test implementation of execution.Info
func (f *FakeExecutionClient) Info(ctx context.Context, infoOpts *execution.InfoRequest, opts ...grpc.CallOption) (*container.Container, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("info", infoOpts)
	if err := f.getError("info"); err != nil {
		return nil, err
	}
	c, ok := f.ContainerList[infoOpts.ID]
	if !ok {
		return nil, containerNotExistError
	}
	return &c, nil
}

// List is a test implementation of execution.List
func (f *FakeExecutionClient) List(ctx context.Context, listOpts *execution.ListRequest, opts ...grpc.CallOption) (*execution.ListResponse, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("list", listOpts)
	if err := f.getError("list"); err != nil {
		return nil, err
	}
	resp := &execution.ListResponse{}
	for _, c := range f.ContainerList {
		resp.Containers = append(resp.Containers, &container.Container{
			ID:     c.ID,
			Pid:    c.Pid,
			Status: c.Status,
		})
	}
	return resp, nil
}

// Kill is a test implementation of execution.Kill
func (f *FakeExecutionClient) Kill(ctx context.Context, killOpts *execution.KillRequest, opts ...grpc.CallOption) (*googleprotobuf.Empty, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("kill", killOpts)
	if err := f.getError("kill"); err != nil {
		return nil, err
	}
	c, ok := f.ContainerList[killOpts.ID]
	if !ok {
		return nil, containerNotExistError
	}
	c.Status = container.Status_STOPPED
	f.ContainerList[killOpts.ID] = c
	f.sendEvent(&container.Event{
		ID:   c.ID,
		Type: container.Event_EXIT,
		Pid:  c.Pid,
	})
	return &googleprotobuf.Empty{}, nil
}

// Events is a test implementation of execution.Events
func (f *FakeExecutionClient) Events(ctx context.Context, eventsOpts *execution.EventsRequest, opts ...grpc.CallOption) (execution.ContainerService_EventsClient, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("events", eventsOpts)
	if err := f.getError("events"); err != nil {
		return nil, err
	}
	var client = &EventClient{
		Events: make(chan *container.Event, 100),
	}
	f.eventClients = append(f.eventClients, client)
	return client, nil
}

// Exec is a test implementation of execution.Exec
func (f *FakeExecutionClient) Exec(ctx context.Context, execOpts *execution.ExecRequest, opts ...grpc.CallOption) (*execution.ExecResponse, error) {
	// TODO: implement Exec()
	return nil, nil
}

// Pty is a test implementation of execution.Pty
func (f *FakeExecutionClient) Pty(ctx context.Context, ptyOpts *execution.PtyRequest, opts ...grpc.CallOption) (*googleprotobuf.Empty, error) {
	// TODO: implement Pty()
	return nil, nil
}

// CloseStdin is a test implementation of execution.CloseStdin
func (f *FakeExecutionClient) CloseStdin(ctx context.Context, closeStdinOpts *execution.CloseStdinRequest, opts ...grpc.CallOption) (*googleprotobuf.Empty, error) {
	// TODO: implement CloseStdin()
	return nil, nil
}

// Pause is a test implementation of execution.Pause
func (f *FakeExecutionClient) Pause(ctx context.Context, in *execution.PauseRequest, opts ...grpc.CallOption) (*googleprotobuf.Empty, error) {
	// TODO: implement Pause()
	return nil, nil
}

// Resume is a test implementation of execution.Resume
func (f *FakeExecutionClient) Resume(ctx context.Context, in *execution.ResumeRequest, opts ...grpc.CallOption) (*googleprotobuf.Empty, error) {
	// TODO: implement Resume()
	return nil, nil
}
<commit_msg>Fix a race that fake execution client sends event to closed channel.<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package testing

import (
	"fmt"
	"math/rand"
	"sync"
	"time"

	"github.com/containerd/containerd"
	"github.com/containerd/containerd/api/services/execution"
	"github.com/containerd/containerd/api/types/container"
	googleprotobuf "github.com/golang/protobuf/ptypes/empty"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
)

var containerNotExistError = grpc.Errorf(codes.Unknown, containerd.ErrContainerNotExist.Error())

// CalledDetail is the struct contains called function name and arguments.
type CalledDetail struct {
	// Name of the function called.
	Name string
	// Argument of the function called.
	Argument interface{}
}

var _ execution.ContainerService_EventsClient = &EventClient{}

// EventClient is a test implementation of execution.ContainerService_EventsClient
type EventClient struct {
	Events chan *container.Event
	grpc.ClientStream
}

// Recv is a test implementation of Recv
func (cli *EventClient) Recv() (*container.Event, error) {
	event, ok := <-cli.Events
	if !ok {
		return nil, fmt.Errorf("event channel closed")
	}
	return event, nil
}

// FakeExecutionClient is a simple fake execution client, so that cri-containerd
// can be run for testing without requiring a real containerd setup.
type FakeExecutionClient struct {
	sync.Mutex
	called        []CalledDetail
	errors        map[string]error
	ContainerList map[string]container.Container
	eventsQueue   chan *container.Event
	eventClients  []*EventClient
}

var _ execution.ContainerServiceClient = &FakeExecutionClient{}

// NewFakeExecutionClient creates a FakeExecutionClient
func NewFakeExecutionClient() *FakeExecutionClient {
	return &FakeExecutionClient{
		errors:        make(map[string]error),
		ContainerList: make(map[string]container.Container),
	}
}

// Stop the fake execution service. Needed when event is enabled.
func (f *FakeExecutionClient) Stop() {
	if f.eventsQueue != nil {
		close(f.eventsQueue)
	}
	f.Lock()
	defer f.Unlock()
	for _, client := range f.eventClients {
		close(client.Events)
	}
	f.eventClients = nil
}

// WithEvents setup events publisher for FakeExecutionClient
func (f *FakeExecutionClient) WithEvents() *FakeExecutionClient {
	f.eventsQueue = make(chan *container.Event, 1024)
	go func() {
		for e := range f.eventsQueue {
			f.Lock()
			for _, client := range f.eventClients {
				client.Events <- e
			}
			f.Unlock()
		}
	}()
	return f
}

func (f *FakeExecutionClient) getError(op string) error {
	err, ok := f.errors[op]
	if ok {
		delete(f.errors, op)
		return err
	}
	return nil
}

// InjectError inject error for call
func (f *FakeExecutionClient) InjectError(fn string, err error) {
	f.Lock()
	defer f.Unlock()
	f.errors[fn] = err
}

// InjectErrors inject errors for calls
func (f *FakeExecutionClient) InjectErrors(errs map[string]error) {
	f.Lock()
	defer f.Unlock()
	for fn, err := range errs {
		f.errors[fn] = err
	}
}

// ClearErrors clear errors for call
func (f *FakeExecutionClient) ClearErrors() {
	f.Lock()
	defer f.Unlock()
	f.errors = make(map[string]error)
}

func generatePid() uint32 {
	rand.Seed(time.Now().Unix())
	randPid := uint32(rand.Intn(1000))
	return randPid
}

func (f *FakeExecutionClient) sendEvent(event *container.Event) {
	if f.eventsQueue != nil {
		f.eventsQueue <- event
	}
}

func (f *FakeExecutionClient) appendCalled(name string, argument interface{}) {
	call := CalledDetail{Name: name, Argument: argument}
	f.called = append(f.called, call)
}

// GetCalledNames get names of call
func (f *FakeExecutionClient) GetCalledNames() []string {
	f.Lock()
	defer f.Unlock()
	names := []string{}
	for _, detail := range f.called {
		names = append(names, detail.Name)
	}
	return names
}

// ClearCalls clear all call detail.
func (f *FakeExecutionClient) ClearCalls() {
	f.Lock()
	defer f.Unlock()
	f.called = []CalledDetail{}
}

// GetCalledDetails get detail of each call.
func (f *FakeExecutionClient) GetCalledDetails() []CalledDetail {
	f.Lock()
	defer f.Unlock()
	// Copy the list and return.
	return append([]CalledDetail{}, f.called...)
}

// SetFakeContainers injects fake containers.
func (f *FakeExecutionClient) SetFakeContainers(containers []container.Container) {
	f.Lock()
	defer f.Unlock()
	for _, c := range containers {
		f.ContainerList[c.ID] = c
	}
}

// Create is a test implementation of execution.Create.
func (f *FakeExecutionClient) Create(ctx context.Context, createOpts *execution.CreateRequest, opts ...grpc.CallOption) (*execution.CreateResponse, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("create", createOpts)
	if err := f.getError("create"); err != nil {
		return nil, err
	}
	_, ok := f.ContainerList[createOpts.ID]
	if ok {
		return nil, containerd.ErrContainerExists
	}
	pid := generatePid()
	f.ContainerList[createOpts.ID] = container.Container{
		ID:     createOpts.ID,
		Pid:    pid,
		Status: container.Status_CREATED,
	}
	f.sendEvent(&container.Event{
		ID:   createOpts.ID,
		Type: container.Event_CREATE,
		Pid:  pid,
	})
	return &execution.CreateResponse{
		ID:  createOpts.ID,
		Pid: pid,
	}, nil
}

// Start is a test implementation of execution.Start
func (f *FakeExecutionClient) Start(ctx context.Context, startOpts *execution.StartRequest, opts ...grpc.CallOption) (*googleprotobuf.Empty, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("start", startOpts)
	if err := f.getError("start"); err != nil {
		return nil, err
	}
	c, ok := f.ContainerList[startOpts.ID]
	if !ok {
		return nil, containerNotExistError
	}
	f.sendEvent(&container.Event{
		ID:   c.ID,
		Type: container.Event_START,
		Pid:  c.Pid,
	})
	switch c.Status {
	case container.Status_CREATED:
		c.Status = container.Status_RUNNING
		f.ContainerList[startOpts.ID] = c
		return &googleprotobuf.Empty{}, nil
	case container.Status_STOPPED:
		return &googleprotobuf.Empty{}, fmt.Errorf("cannot start a container that has stopped")
	case container.Status_RUNNING:
		return &googleprotobuf.Empty{}, fmt.Errorf("cannot start an already running container")
	default:
		return &googleprotobuf.Empty{}, fmt.Errorf("cannot start a container in the %s state", c.Status)
	}
}

// Delete is a test implementation of execution.Delete
func (f *FakeExecutionClient) Delete(ctx context.Context, deleteOpts *execution.DeleteRequest, opts ...grpc.CallOption) (*execution.DeleteResponse, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("delete", deleteOpts)
	if err := f.getError("delete"); err != nil {
		return nil, err
	}
	c, ok := f.ContainerList[deleteOpts.ID]
	if !ok {
		return nil, containerNotExistError
	}
	delete(f.ContainerList, deleteOpts.ID)
	f.sendEvent(&container.Event{
		ID:   c.ID,
		Type: container.Event_EXIT,
		Pid:  c.Pid,
	})
	return nil, nil
}

// Info is a test implementation of execution.Info
func (f *FakeExecutionClient) Info(ctx context.Context, infoOpts *execution.InfoRequest, opts ...grpc.CallOption) (*container.Container, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("info", infoOpts)
	if err := f.getError("info"); err != nil {
		return nil, err
	}
	c, ok := f.ContainerList[infoOpts.ID]
	if !ok {
		return nil, containerNotExistError
	}
	return &c, nil
}

// List is a test implementation of execution.List
func (f *FakeExecutionClient) List(ctx context.Context, listOpts *execution.ListRequest, opts ...grpc.CallOption) (*execution.ListResponse, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("list", listOpts)
	if err := f.getError("list"); err != nil {
		return nil, err
	}
	resp := &execution.ListResponse{}
	for _, c := range f.ContainerList {
		resp.Containers = append(resp.Containers, &container.Container{
			ID:     c.ID,
			Pid:    c.Pid,
			Status: c.Status,
		})
	}
	return resp, nil
}

// Kill is a test implementation of execution.Kill
func (f *FakeExecutionClient) Kill(ctx context.Context, killOpts *execution.KillRequest, opts ...grpc.CallOption) (*googleprotobuf.Empty, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("kill", killOpts)
	if err := f.getError("kill"); err != nil {
		return nil, err
	}
	c, ok := f.ContainerList[killOpts.ID]
	if !ok {
		return nil, containerNotExistError
	}
	c.Status = container.Status_STOPPED
	f.ContainerList[killOpts.ID] = c
	f.sendEvent(&container.Event{
		ID:   c.ID,
		Type: container.Event_EXIT,
		Pid:  c.Pid,
	})
	return &googleprotobuf.Empty{}, nil
}

// Events is a test implementation of execution.Events
func (f *FakeExecutionClient) Events(ctx context.Context, eventsOpts *execution.EventsRequest, opts ...grpc.CallOption) (execution.ContainerService_EventsClient, error) {
	f.Lock()
	defer f.Unlock()
	f.appendCalled("events", eventsOpts)
	if err := f.getError("events"); err != nil {
		return nil, err
	}
	var client = &EventClient{
		Events: make(chan *container.Event, 100),
	}
	f.eventClients = append(f.eventClients, client)
	return client, nil
}

// Exec is a test implementation of execution.Exec
func (f *FakeExecutionClient) Exec(ctx context.Context, execOpts *execution.ExecRequest, opts ...grpc.CallOption) (*execution.ExecResponse, error) {
	// TODO: implement Exec()
	return nil, nil
}

// Pty is a test implementation of execution.Pty
func (f *FakeExecutionClient) Pty(ctx context.Context, ptyOpts *execution.PtyRequest, opts ...grpc.CallOption) (*googleprotobuf.Empty, error) {
	// TODO: implement Pty()
	return nil, nil
}

// CloseStdin is a test implementation of execution.CloseStdin
func (f *FakeExecutionClient) CloseStdin(ctx context.Context, closeStdinOpts *execution.CloseStdinRequest, opts ...grpc.CallOption) (*googleprotobuf.Empty, error) {
	// TODO: implement CloseStdin()
	return nil, nil
}

// Pause is a test implementation of execution.Pause
func (f *FakeExecutionClient) Pause(ctx context.Context, in *execution.PauseRequest, opts ...grpc.CallOption) (*googleprotobuf.Empty, error) {
	// TODO: implement Pause()
	return nil, nil
}

// Resume is a test implementation of execution.Resume
func (f *FakeExecutionClient) Resume(ctx context.Context, in *execution.ResumeRequest, opts ...grpc.CallOption) (*googleprotobuf.Empty, error) {
	// TODO: implement Resume()
	return nil, nil
}
<|endoftext|>