<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package params

import (
	"bytes"
	"encoding/json"
	"fmt"
	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/constraints"
	"launchpad.net/juju-core/instance"
)

// Error holds the error result of a single operation.
type Error struct {
	Message string
	Code    string
}

// ErrorCode implements rpc.ErrorCoder interface.
func (e Error) ErrorCode() string {
	return e.Code
}

// Error implements the error interface.
func (e Error) Error() string {
	return e.Message
}

// GoString implements fmt.GoStringer.  It means that a *Error shows its
// contents correctly when printed with %#v.
func (e Error) GoString() string {
	return fmt.Sprintf("&params.Error{%q, %q}", e.Code, e.Message)
}

// ErrorResults holds the results of calling a bulk operation which
// mutates multiple entites, like Machiner.SetStatus. The order and
// number of elements matches the entities specified in the request.
type ErrorResults struct {
	// Errors contains errors occured while performing each operation (if any).
	Errors []*Error
}

// Machines holds the arguments for making an API call working on
// multiple machine entities.
type Machines struct {
	Ids []string
}

// MachineSetStatus holds a machine id, status and extra info.
type MachineSetStatus struct {
	Id     string
	Status Status
	Info   string
}

// MachinesSetStatus holds the parameters for making a Machiner.SetStatus call.
type MachinesSetStatus struct {
	Machines []MachineSetStatus
}

// NotifyWatchResult holds an NotifyWatcher id and an error (if any).
type NotifyWatchResult struct {
	NotifyWatcherId string
	Error           *Error
}

// NotifyWatchResults holds the results for any API call which ends up
// returning a list of Entity Watchers
type NotifyWatchResults struct {
	Results []NotifyWatchResult
}

// AddRelation holds the parameters for making the AddRelation call.
// The endpoints specified are unordered.
type AddRelation struct {
	Endpoints []string
}

// AddRelationResults holds the results of a AddRelation call. The Endpoints
// field maps service names to the involved endpoints.
type AddRelationResults struct {
	Endpoints map[string]charm.Relation
}

// DestroyRelation holds the parameters for making the DestroyRelation call.
// The endpoints specified are unordered.
type DestroyRelation struct {
	Endpoints []string
}

// Life describes the lifecycle state of an entity ("alive", "dying"
// or "dead").
type Life string

const (
	Alive Life = "alive"
	Dying Life = "dying"
	Dead  Life = "dead"
)

// MachineLifeResult holds the result of Machiner.Life for a single machine.
type MachineLifeResult struct {
	Life  Life
	Error *Error
}

// MachinesLifeResults holds the results of a Machiner.Life call.
type MachinesLifeResults struct {
	Machines []MachineLifeResult
}

// MachineAgentGetMachinesResults holds the results of a
// machineagent.API.GetMachines call.
type MachineAgentGetMachinesResults struct {
	Machines []MachineAgentGetMachinesResult
}

// MachineJob values define responsibilities that machines may be
// expected to fulfil.
type MachineJob string

const (
	JobHostUnits     MachineJob = "JobHostUnits"
	JobManageEnviron MachineJob = "JobManageEnviron"
	JobManageState   MachineJob = "JobManageState"
)

// MachineAgentGetMachinesResult holds the results of a
// machineagent.API.GetMachines call for a single machine.
type MachineAgentGetMachinesResult struct {
	Life  Life
	Jobs  []MachineJob
	Error *Error
}

// ServiceDeploy holds the parameters for making the ServiceDeploy call.
type ServiceDeploy struct {
	ServiceName    string
	CharmUrl       string
	NumUnits       int
	Config         map[string]string
	ConfigYAML     string // Takes precedence over config if both are present.
	Constraints    constraints.Value
	ForceMachineId string
}

// ServiceSetCharm sets the charm for a given service.
type ServiceSetCharm struct {
	ServiceName string
	CharmUrl    string
	Force       bool
}

// ServiceExpose holds the parameters for making the ServiceExpose call.
type ServiceExpose struct {
	ServiceName string
}

// ServiceSet holds the parameters for a ServiceSet
// command. Options contains the configuration data.
type ServiceSet struct {
	ServiceName string
	Options     map[string]string
}

// ServiceSetYAML holds the parameters for
// a ServiceSetYAML command. Config contains the
// configuration data in YAML format.
type ServiceSetYAML struct {
	ServiceName string
	Config      string
}

// ServiceGet holds parameters for making the ServiceGet call.
type ServiceGet struct {
	ServiceName string
}

// ServiceGetResults holds results of the ServiceGet call.
type ServiceGetResults struct {
	Service     string
	Charm       string
	Config      map[string]interface{}
	Constraints constraints.Value
}

// ServiceUnexpose holds parameters for the ServiceUnexpose call.
type ServiceUnexpose struct {
	ServiceName string
}

// Resolved holds parameters for the Resolved call.
type Resolved struct {
	UnitName string
	Retry    bool
}

// ResolvedResults holds results of the Resolved call.
type ResolvedResults struct {
	Service  string
	Charm    string
	Settings map[string]interface{}
}

// AddServiceUnitsResults holds the names of the units added by the
// AddServiceUnits call.
type AddServiceUnitsResults struct {
	Units []string
}

// AddServiceUnits holds parameters for the AddUnits call.
type AddServiceUnits struct {
	ServiceName string
	NumUnits    int
}

// DestroyServiceUnits holds parameters for the DestroyUnits call.
type DestroyServiceUnits struct {
	UnitNames []string
}

// ServiceDestroy holds the parameters for making the ServiceDestroy call.
type ServiceDestroy struct {
	ServiceName string
}

// PasswordChanges holds the parameters for making a SetPasswords call.
type PasswordChanges struct {
	Changes []PasswordChange
}

// PasswordChange specifies a password change for the entity
// with the given tag.
type PasswordChange struct {
	Tag      string
	Password string
}

// Creds holds credentials for identifying an entity.
type Creds struct {
	AuthTag  string
	Password string
}

// NotifyWatcherId holds the id of an NotifyWatcher.
type NotifyWatcherId struct {
	NotifyWatcherId string
}

// LifecycleWatchResults holds the results of API calls
// that watch the lifecycle of a set of objects.
// It is used both for the initial Watch request
// and for subsequent Next requests.
type LifecycleWatchResults struct {
	// LifeCycleWatcherId holds the id of the newly
	// created watcher. It will be empty for a Next
	// request.
	LifecycleWatcherId string

	// Ids holds the list of entity ids.
	// For a Watch request, it holds all entity ids being
	// watched; for a Next request, it holds the ids of those
	// that have changed.
	Ids []string
}

// EnvironConfigWatchResults holds the result of
// State.WatchEnvironConfig(): id of the created EnvironConfigWatcher,
// along with the current environment configuration. It is also used
// for the result of EnvironConfigWatcher.Next(), when it contains the
// changed config (EnvironConfigWatcherId will be empty in this case).
type EnvironConfigWatchResults struct {
	EnvironConfigWatcherId string
	Config                 map[string]interface{}
}

// AllWatcherId holds the id of an AllWatcher.
type AllWatcherId struct {
	AllWatcherId string
}

// AllWatcherNextResults holds deltas returned from calling AllWatcher.Next().
type AllWatcherNextResults struct {
	Deltas []Delta
}

// Password holds a password.
type Password struct {
	Password string
}

// Unit holds details of a unit.
type Unit struct {
	DeployerTag string
	// TODO(rog) other unit attributes.
}

// User holds details of a user.
type User struct {
	// This is a placeholder for any information
	// that may be associated with a user in the
	// future.
}

// GetAnnotationsResults holds annotations associated with an entity.
type GetAnnotationsResults struct {
	Annotations map[string]string
}

// GetAnnotations stores parameters for making the GetAnnotations call.
type GetAnnotations struct {
	Tag string
}

// SetAnnotations stores parameters for making the SetAnnotations call.
type SetAnnotations struct {
	Tag   string
	Pairs map[string]string
}

// GetServiceConstraints stores parameters for making the GetServiceConstraints call.
type GetServiceConstraints struct {
	ServiceName string
}

// GetServiceConstraintsResults holds results of the GetServiceConstraints call.
type GetServiceConstraintsResults struct {
	Constraints constraints.Value
}

// SetServiceConstraints stores parameters for making the SetServiceConstraints call.
type SetServiceConstraints struct {
	ServiceName string
	Constraints constraints.Value
}

// CharmInfo stores parameters for a CharmInfo call.
type CharmInfo struct {
	CharmURL string
}

// Delta holds details of a change to the environment.
type Delta struct {
	// If Removed is true, the entity has been removed;
	// otherwise it has been created or changed.
	Removed bool
	// Entity holds data about the entity that has changed.
	Entity EntityInfo
}

// MarshalJSON implements json.Marshaler.
func (d *Delta) MarshalJSON() ([]byte, error) {
	b, err := json.Marshal(d.Entity)
	if err != nil {
		return nil, err
	}
	var buf bytes.Buffer
	buf.WriteByte('[')
	c := "change"
	if d.Removed {
		c = "remove"
	}
	fmt.Fprintf(&buf, "%q,%q,", d.Entity.EntityId().Kind, c)
	buf.Write(b)
	buf.WriteByte(']')
	return buf.Bytes(), nil
}

// UnmarshalJSON implements json.Unmarshaler.
func (d *Delta) UnmarshalJSON(data []byte) error {
	var elements []json.RawMessage
	if err := json.Unmarshal(data, &elements); err != nil {
		return err
	}
	if len(elements) != 3 {
		return fmt.Errorf(
			"Expected 3 elements in top-level of JSON but got %d",
			len(elements))
	}
	var entityKind, operation string
	if err := json.Unmarshal(elements[0], &entityKind); err != nil {
		return err
	}
	if err := json.Unmarshal(elements[1], &operation); err != nil {
		return err
	}
	if operation == "remove" {
		d.Removed = true
	} else if operation != "change" {
		return fmt.Errorf("Unexpected operation %q", operation)
	}
	switch entityKind {
	case "machine":
		d.Entity = new(MachineInfo)
	case "service":
		d.Entity = new(ServiceInfo)
	case "unit":
		d.Entity = new(UnitInfo)
	case "relation":
		d.Entity = new(RelationInfo)
	case "annotation":
		d.Entity = new(AnnotationInfo)
	default:
		return fmt.Errorf("Unexpected entity name %q", entityKind)
	}
	if err := json.Unmarshal(elements[2], &d.Entity); err != nil {
		return err
	}
	return nil
}

// EntityInfo is implemented by all entity Info types.
type EntityInfo interface {
	// EntityId returns an identifier that will uniquely
	// identify the entity within its kind
	EntityId() EntityId
}

// IMPORTANT NOTE: the types below are direct subsets of the entity docs
// held in mongo, as defined in the state package (serviceDoc,
// machineDoc etc).
// In particular, the document marshalled into mongo
// must unmarshal correctly into these documents.
// If the format of a field in a document is changed in mongo, or
// a field is removed and it coincides with one of the
// fields below, a similar change must be made here.
//
// MachineInfo corresponds with state.machineDoc.
// ServiceInfo corresponds with state.serviceDoc.
// UnitInfo corresponds with state.unitDoc.
// RelationInfo corresponds with state.relationDoc.
// AnnotationInfo corresponds with state.annotatorDoc.

var (
	_ EntityInfo = (*MachineInfo)(nil)
	_ EntityInfo = (*ServiceInfo)(nil)
	_ EntityInfo = (*UnitInfo)(nil)
	_ EntityInfo = (*RelationInfo)(nil)
	_ EntityInfo = (*AnnotationInfo)(nil)
)

type EntityId struct {
	Kind string
	Id   interface{}
}

// MachineInfo holds the information about a Machine
// that is watched by StateWatcher.
type MachineInfo struct {
	Id         string `bson:"_id"`
	InstanceId string
	Status     Status
	StatusInfo string
}

func (i *MachineInfo) EntityId() EntityId {
	return EntityId{
		Kind: "machine",
		Id:   i.Id,
	}
}

type ServiceInfo struct {
	Name        string `bson:"_id"`
	Exposed     bool
	CharmURL    string
	Life        Life
	Constraints constraints.Value
	Config      map[string]interface{}
}

func (i *ServiceInfo) EntityId() EntityId {
	return EntityId{
		Kind: "service",
		Id:   i.Name,
	}
}

type UnitInfo struct {
	Name           string `bson:"_id"`
	Service        string
	Series         string
	CharmURL       string
	PublicAddress  string
	PrivateAddress string
	MachineId      string
	Ports          []instance.Port
	Status         Status
	StatusInfo     string
}

func (i *UnitInfo) EntityId() EntityId {
	return EntityId{
		Kind: "unit",
		Id:   i.Name,
	}
}

type Endpoint struct {
	ServiceName string
	Relation    charm.Relation
}

type RelationInfo struct {
	Key       string `bson:"_id"`
	Endpoints []Endpoint
}

func (i *RelationInfo) EntityId() EntityId {
	return EntityId{
		Kind: "relation",
		Id:   i.Key,
	}
}

type AnnotationInfo struct {
	Tag         string
	Annotations map[string]string
}

func (i *AnnotationInfo) EntityId() EntityId {
	return EntityId{
		Kind: "annotation",
		Id:   i.Tag,
	}
}
<commit_msg>doc fix from mgz<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package params

import (
	"bytes"
	"encoding/json"
	"fmt"
	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/constraints"
	"launchpad.net/juju-core/instance"
)

// Error holds the error result of a single operation.
type Error struct {
	Message string
	Code    string
}

// ErrorCode implements rpc.ErrorCoder interface.
func (e Error) ErrorCode() string {
	return e.Code
}

// Error implements the error interface.
func (e Error) Error() string {
	return e.Message
}

// GoString implements fmt.GoStringer.  It means that a *Error shows its
// contents correctly when printed with %#v.
func (e Error) GoString() string {
	return fmt.Sprintf("&params.Error{%q, %q}", e.Code, e.Message)
}

// ErrorResults holds the results of calling a bulk operation which
// mutates multiple entites, like Machiner.SetStatus. The order and
// number of elements matches the entities specified in the request.
type ErrorResults struct {
	// Errors contains errors occured while performing each operation (if any).
	Errors []*Error
}

// Machines holds the arguments for making an API call working on
// multiple machine entities.
type Machines struct {
	Ids []string
}

// MachineSetStatus holds a machine id, status and extra info.
type MachineSetStatus struct {
	Id     string
	Status Status
	Info   string
}

// MachinesSetStatus holds the parameters for making a Machiner.SetStatus call.
type MachinesSetStatus struct {
	Machines []MachineSetStatus
}

// NotifyWatchResult holds an NotifyWatcher id and an error (if any).
type NotifyWatchResult struct {
	NotifyWatcherId string
	Error           *Error
}

// NotifyWatchResults holds the results for any API call which ends up
// returning a list of NotifyWatchers
type NotifyWatchResults struct {
	Results []NotifyWatchResult
}

// AddRelation holds the parameters for making the AddRelation call.
// The endpoints specified are unordered.
type AddRelation struct {
	Endpoints []string
}

// AddRelationResults holds the results of a AddRelation call. The Endpoints
// field maps service names to the involved endpoints.
type AddRelationResults struct {
	Endpoints map[string]charm.Relation
}

// DestroyRelation holds the parameters for making the DestroyRelation call.
// The endpoints specified are unordered.
type DestroyRelation struct {
	Endpoints []string
}

// Life describes the lifecycle state of an entity ("alive", "dying"
// or "dead").
type Life string

const (
	Alive Life = "alive"
	Dying Life = "dying"
	Dead  Life = "dead"
)

// MachineLifeResult holds the result of Machiner.Life for a single machine.
type MachineLifeResult struct {
	Life  Life
	Error *Error
}

// MachinesLifeResults holds the results of a Machiner.Life call.
type MachinesLifeResults struct {
	Machines []MachineLifeResult
}

// MachineAgentGetMachinesResults holds the results of a
// machineagent.API.GetMachines call.
type MachineAgentGetMachinesResults struct {
	Machines []MachineAgentGetMachinesResult
}

// MachineJob values define responsibilities that machines may be
// expected to fulfil.
type MachineJob string

const (
	JobHostUnits     MachineJob = "JobHostUnits"
	JobManageEnviron MachineJob = "JobManageEnviron"
	JobManageState   MachineJob = "JobManageState"
)

// MachineAgentGetMachinesResult holds the results of a
// machineagent.API.GetMachines call for a single machine.
type MachineAgentGetMachinesResult struct {
	Life  Life
	Jobs  []MachineJob
	Error *Error
}

// ServiceDeploy holds the parameters for making the ServiceDeploy call.
type ServiceDeploy struct {
	ServiceName    string
	CharmUrl       string
	NumUnits       int
	Config         map[string]string
	ConfigYAML     string // Takes precedence over config if both are present.
	Constraints    constraints.Value
	ForceMachineId string
}

// ServiceSetCharm sets the charm for a given service.
type ServiceSetCharm struct {
	ServiceName string
	CharmUrl    string
	Force       bool
}

// ServiceExpose holds the parameters for making the ServiceExpose call.
type ServiceExpose struct {
	ServiceName string
}

// ServiceSet holds the parameters for a ServiceSet
// command. Options contains the configuration data.
type ServiceSet struct {
	ServiceName string
	Options     map[string]string
}

// ServiceSetYAML holds the parameters for
// a ServiceSetYAML command. Config contains the
// configuration data in YAML format.
type ServiceSetYAML struct {
	ServiceName string
	Config      string
}

// ServiceGet holds parameters for making the ServiceGet call.
type ServiceGet struct {
	ServiceName string
}

// ServiceGetResults holds results of the ServiceGet call.
type ServiceGetResults struct {
	Service     string
	Charm       string
	Config      map[string]interface{}
	Constraints constraints.Value
}

// ServiceUnexpose holds parameters for the ServiceUnexpose call.
type ServiceUnexpose struct {
	ServiceName string
}

// Resolved holds parameters for the Resolved call.
type Resolved struct {
	UnitName string
	Retry    bool
}

// ResolvedResults holds results of the Resolved call.
type ResolvedResults struct {
	Service  string
	Charm    string
	Settings map[string]interface{}
}

// AddServiceUnitsResults holds the names of the units added by the
// AddServiceUnits call.
type AddServiceUnitsResults struct {
	Units []string
}

// AddServiceUnits holds parameters for the AddUnits call.
type AddServiceUnits struct {
	ServiceName string
	NumUnits    int
}

// DestroyServiceUnits holds parameters for the DestroyUnits call.
type DestroyServiceUnits struct {
	UnitNames []string
}

// ServiceDestroy holds the parameters for making the ServiceDestroy call.
type ServiceDestroy struct {
	ServiceName string
}

// PasswordChanges holds the parameters for making a SetPasswords call.
type PasswordChanges struct {
	Changes []PasswordChange
}

// PasswordChange specifies a password change for the entity
// with the given tag.
type PasswordChange struct {
	Tag      string
	Password string
}

// Creds holds credentials for identifying an entity.
type Creds struct {
	AuthTag  string
	Password string
}

// NotifyWatcherId holds the id of an NotifyWatcher.
type NotifyWatcherId struct {
	NotifyWatcherId string
}

// LifecycleWatchResults holds the results of API calls
// that watch the lifecycle of a set of objects.
// It is used both for the initial Watch request
// and for subsequent Next requests.
type LifecycleWatchResults struct {
	// LifeCycleWatcherId holds the id of the newly
	// created watcher. It will be empty for a Next
	// request.
	LifecycleWatcherId string

	// Ids holds the list of entity ids.
	// For a Watch request, it holds all entity ids being
	// watched; for a Next request, it holds the ids of those
	// that have changed.
	Ids []string
}

// EnvironConfigWatchResults holds the result of
// State.WatchEnvironConfig(): id of the created EnvironConfigWatcher,
// along with the current environment configuration. It is also used
// for the result of EnvironConfigWatcher.Next(), when it contains the
// changed config (EnvironConfigWatcherId will be empty in this case).
type EnvironConfigWatchResults struct {
	EnvironConfigWatcherId string
	Config                 map[string]interface{}
}

// AllWatcherId holds the id of an AllWatcher.
type AllWatcherId struct {
	AllWatcherId string
}

// AllWatcherNextResults holds deltas returned from calling AllWatcher.Next().
type AllWatcherNextResults struct {
	Deltas []Delta
}

// Password holds a password.
type Password struct {
	Password string
}

// Unit holds details of a unit.
type Unit struct {
	DeployerTag string
	// TODO(rog) other unit attributes.
}

// User holds details of a user.
type User struct {
	// This is a placeholder for any information
	// that may be associated with a user in the
	// future.
}

// GetAnnotationsResults holds annotations associated with an entity.
type GetAnnotationsResults struct {
	Annotations map[string]string
}

// GetAnnotations stores parameters for making the GetAnnotations call.
type GetAnnotations struct {
	Tag string
}

// SetAnnotations stores parameters for making the SetAnnotations call.
type SetAnnotations struct {
	Tag   string
	Pairs map[string]string
}

// GetServiceConstraints stores parameters for making the GetServiceConstraints call.
type GetServiceConstraints struct {
	ServiceName string
}

// GetServiceConstraintsResults holds results of the GetServiceConstraints call.
type GetServiceConstraintsResults struct {
	Constraints constraints.Value
}

// SetServiceConstraints stores parameters for making the SetServiceConstraints call.
type SetServiceConstraints struct {
	ServiceName string
	Constraints constraints.Value
}

// CharmInfo stores parameters for a CharmInfo call.
type CharmInfo struct {
	CharmURL string
}

// Delta holds details of a change to the environment.
type Delta struct {
	// If Removed is true, the entity has been removed;
	// otherwise it has been created or changed.
	Removed bool
	// Entity holds data about the entity that has changed.
	Entity EntityInfo
}

// MarshalJSON implements json.Marshaler.
func (d *Delta) MarshalJSON() ([]byte, error) {
	b, err := json.Marshal(d.Entity)
	if err != nil {
		return nil, err
	}
	var buf bytes.Buffer
	buf.WriteByte('[')
	c := "change"
	if d.Removed {
		c = "remove"
	}
	fmt.Fprintf(&buf, "%q,%q,", d.Entity.EntityId().Kind, c)
	buf.Write(b)
	buf.WriteByte(']')
	return buf.Bytes(), nil
}

// UnmarshalJSON implements json.Unmarshaler.
func (d *Delta) UnmarshalJSON(data []byte) error {
	var elements []json.RawMessage
	if err := json.Unmarshal(data, &elements); err != nil {
		return err
	}
	if len(elements) != 3 {
		return fmt.Errorf(
			"Expected 3 elements in top-level of JSON but got %d",
			len(elements))
	}
	var entityKind, operation string
	if err := json.Unmarshal(elements[0], &entityKind); err != nil {
		return err
	}
	if err := json.Unmarshal(elements[1], &operation); err != nil {
		return err
	}
	if operation == "remove" {
		d.Removed = true
	} else if operation != "change" {
		return fmt.Errorf("Unexpected operation %q", operation)
	}
	switch entityKind {
	case "machine":
		d.Entity = new(MachineInfo)
	case "service":
		d.Entity = new(ServiceInfo)
	case "unit":
		d.Entity = new(UnitInfo)
	case "relation":
		d.Entity = new(RelationInfo)
	case "annotation":
		d.Entity = new(AnnotationInfo)
	default:
		return fmt.Errorf("Unexpected entity name %q", entityKind)
	}
	if err := json.Unmarshal(elements[2], &d.Entity); err != nil {
		return err
	}
	return nil
}

// EntityInfo is implemented by all entity Info types.
type EntityInfo interface {
	// EntityId returns an identifier that will uniquely
	// identify the entity within its kind
	EntityId() EntityId
}

// IMPORTANT NOTE: the types below are direct subsets of the entity docs
// held in mongo, as defined in the state package (serviceDoc,
// machineDoc etc).
// In particular, the document marshalled into mongo
// must unmarshal correctly into these documents.
// If the format of a field in a document is changed in mongo, or
// a field is removed and it coincides with one of the
// fields below, a similar change must be made here.
//
// MachineInfo corresponds with state.machineDoc.
// ServiceInfo corresponds with state.serviceDoc.
// UnitInfo corresponds with state.unitDoc.
// RelationInfo corresponds with state.relationDoc.
// AnnotationInfo corresponds with state.annotatorDoc.

var (
	_ EntityInfo = (*MachineInfo)(nil)
	_ EntityInfo = (*ServiceInfo)(nil)
	_ EntityInfo = (*UnitInfo)(nil)
	_ EntityInfo = (*RelationInfo)(nil)
	_ EntityInfo = (*AnnotationInfo)(nil)
)

type EntityId struct {
	Kind string
	Id   interface{}
}

// MachineInfo holds the information about a Machine
// that is watched by StateWatcher.
type MachineInfo struct {
	Id         string `bson:"_id"`
	InstanceId string
	Status     Status
	StatusInfo string
}

func (i *MachineInfo) EntityId() EntityId {
	return EntityId{
		Kind: "machine",
		Id:   i.Id,
	}
}

type ServiceInfo struct {
	Name        string `bson:"_id"`
	Exposed     bool
	CharmURL    string
	Life        Life
	Constraints constraints.Value
	Config      map[string]interface{}
}

func (i *ServiceInfo) EntityId() EntityId {
	return EntityId{
		Kind: "service",
		Id:   i.Name,
	}
}

type UnitInfo struct {
	Name           string `bson:"_id"`
	Service        string
	Series         string
	CharmURL       string
	PublicAddress  string
	PrivateAddress string
	MachineId      string
	Ports          []instance.Port
	Status         Status
	StatusInfo     string
}

func (i *UnitInfo) EntityId() EntityId {
	return EntityId{
		Kind: "unit",
		Id:   i.Name,
	}
}

type Endpoint struct {
	ServiceName string
	Relation    charm.Relation
}

type RelationInfo struct {
	Key       string `bson:"_id"`
	Endpoints []Endpoint
}

func (i *RelationInfo) EntityId() EntityId {
	return EntityId{
		Kind: "relation",
		Id:   i.Key,
	}
}

type AnnotationInfo struct {
	Tag         string
	Annotations map[string]string
}

func (i *AnnotationInfo) EntityId() EntityId {
	return EntityId{
		Kind: "annotation",
		Id:   i.Tag,
	}
}
<|endoftext|>


<commit_before>package boleto

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"html/template"

	"github.com/boombuler/barcode"
	"github.com/boombuler/barcode/twooffive"

	"image/jpeg"

	"github.com/mundipagg/boleto-api/models"
	"github.com/mundipagg/boleto-api/tmpl"
)

const templateBoleto = `

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

    <link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">

    {{if eq .Format "html"}}

            <button class="no-print btnDefault print" onclick="window.print()">

                &nbspImprimir
            </button>
            <button class="no-print btnDefault print" onclick="window.location='./boleto?fmt=pdf&id={{.ID}}'">

                &nbspGerar PDF
            </button>

    {{end}}
    {{template "boletoForm" .}}

	{{template "boletoForm" .}}

`

const boletoForm = `
{{define "boletoForm"}}

                    {{.BankLogo}}

                {{.BankNumber}}
                {{.DigitableLine}}

                    Local de Pagamento

                    ATÉ O VENCIMENTO EM QUALQUER BANCO OU CORRESPONDENTE NÃO BANCÁRIO, APÓS O VENCIMENTO, PAGUE EM QUALQUER BANCO OU CORRESPONDENTE NÃO BANCÁRIO

                    Data de Vencimento

{{.Boleto.Title.ExpireDateTime | brdate}}

                    Nome do Beneficiário / CNPJ / CPF / Endereço:

                            {{.Boleto.Recipient.Name}}
                            {{.Boleto.Recipient.Document.Type}} {{fmtDoc .Boleto.Recipient.Document}}

                    {{.Boleto.Recipient.Address.Street}},
                    {{.Boleto.Recipient.Address.Number}} -
                    {{.Boleto.Recipient.Address.District}},
                    {{.Boleto.Recipient.Address.StateCode}} -
                    {{.Boleto.Recipient.Address.ZipCode}}

                    Agência/Código Beneficiário

{{.Boleto.Agreement.Agency}}/{{.Boleto.Agreement.Account}}-{{.Boleto.Agreement.AccountDigit}}

                    Data do Documento

{{today | brdate}}

                    Num. do Documento

{{.Boleto.Title.DocumentNumber}}

                    Espécie doc

DM

                    Aceite

N

                    Data Processamento

{{today | brdate}}

                    Carteira/Nosso Número

17/{{.Boleto.Title.OurNumber}}

                    Uso do Banco

                    Carteira

17

                    Espécie

R$

                    Quantidade

N

                    Valor

{{fmtNumber .Boleto.Title.AmountInCents}}

                    (=) Valor do Documento

{{fmtNumber .Boleto.Title.AmountInCents}}

                    Instruções de responsabilidade do BENEFICIÁRIO. Qualquer dúvida sobre este boleto contate o beneficiário.

{{.Boleto.Title.Instructions}}

                    (-) Descontos/Abatimento

                    (+) Juros/Multa

                    (=) Valor Pago

                            Nome do Pagador:  {{.Boleto.Buyer.Name}}
                            CNPJ/CPF:  {{fmtDoc .Boleto.Buyer.Document}}

                            Endereço:  {{.Boleto.Buyer.Address.Street}} {{.Boleto.Buyer.Address.Number}}, {{.Boleto.Buyer.Address.District}} - {{.Boleto.Buyer.Address.City}}, {{.Boleto.Buyer.Address.StateCode}} - {{.Boleto.Buyer.Address.ZipCode}}

                            Sacador/Avalista:
                            CNPJ/CPF:

	{{end}}
`

//HTML renderiza HTML do boleto
func HTML(boleto models.BoletoView, format string) string {
	b := tmpl.New()
	c := boleto.BankNumber
	fmt.Println(c)
	boleto.BankLogo = template.HTML(logoSantander)
	boleto.Format = format
	bcode, _ := twooffive.Encode(boleto.Barcode, true)
	orgBounds := bcode.Bounds()
	orgWidth := orgBounds.Max.X - orgBounds.Min.X
	img, _ := barcode.Scale(bcode, orgWidth, 50)
	buf := new(bytes.Buffer)
	err := jpeg.Encode(buf, img, nil)
	boleto.Barcode64 = base64.StdEncoding.EncodeToString(buf.Bytes())
	s, err := b.From(boleto).To(templateBoleto).Transform(boletoForm)
	if err != nil {
		fmt.Println(err)
	}
	return s
}

//Get retorna estrategia de acordo com o banco ou erro caso o banco não exista
func GetLogo(number models.BankNumber) string {
	switch number {
	case models.BancoDoBrasil:
		return logoBB
	case models.Santander:
		return logoSantander
	default:
		return fmt.Sprintf("Banco %d não existe", number)
	}
}<commit_msg>Alter function of selector logo<commit_after>package boleto

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"html/template"

	"github.com/boombuler/barcode"
	"github.com/boombuler/barcode/twooffive"

	"image/jpeg"

	"github.com/mundipagg/boleto-api/models"
	"github.com/mundipagg/boleto-api/tmpl"
)

const templateBoleto = `

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

    <link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">

    {{if eq .Format "html"}}

            <button class="no-print btnDefault print" onclick="window.print()">

                &nbspImprimir
            </button>
            <button class="no-print btnDefault print" onclick="window.location='./boleto?fmt=pdf&id={{.ID}}'">

                &nbspGerar PDF
            </button>

    {{end}}
    {{template "boletoForm" .}}

	{{template "boletoForm" .}}

`

const boletoForm = `
{{define "boletoForm"}}

                    {{.BankLogo}}

                {{.BankNumber}}
                {{.DigitableLine}}

                    Local de Pagamento

                    ATÉ O VENCIMENTO EM QUALQUER BANCO OU CORRESPONDENTE NÃO BANCÁRIO, APÓS O VENCIMENTO, PAGUE EM QUALQUER BANCO OU CORRESPONDENTE NÃO BANCÁRIO

                    Data de Vencimento

{{.Boleto.Title.ExpireDateTime | brdate}}

                    Nome do Beneficiário / CNPJ / CPF / Endereço:

                            {{.Boleto.Recipient.Name}}
                            {{.Boleto.Recipient.Document.Type}} {{fmtDoc .Boleto.Recipient.Document}}

                    {{.Boleto.Recipient.Address.Street}},
                    {{.Boleto.Recipient.Address.Number}} -
                    {{.Boleto.Recipient.Address.District}},
                    {{.Boleto.Recipient.Address.StateCode}} -
                    {{.Boleto.Recipient.Address.ZipCode}}

                    Agência/Código Beneficiário

{{.Boleto.Agreement.Agency}}/{{.Boleto.Agreement.Account}}-{{.Boleto.Agreement.AccountDigit}}

                    Data do Documento

{{today | brdate}}

                    Num. do Documento

{{.Boleto.Title.DocumentNumber}}

                    Espécie doc

DM

                    Aceite

N

                    Data Processamento

{{today | brdate}}

                    Carteira/Nosso Número

17/{{.Boleto.Title.OurNumber}}

                    Uso do Banco

                    Carteira

17

                    Espécie

R$

                    Quantidade

N

                    Valor

{{fmtNumber .Boleto.Title.AmountInCents}}

                    (=) Valor do Documento

{{fmtNumber .Boleto.Title.AmountInCents}}

                    Instruções de responsabilidade do BENEFICIÁRIO. Qualquer dúvida sobre este boleto contate o beneficiário.

{{.Boleto.Title.Instructions}}

                    (-) Descontos/Abatimento

                    (+) Juros/Multa

                    (=) Valor Pago

                            Nome do Pagador:  {{.Boleto.Buyer.Name}}
                            CNPJ/CPF:  {{fmtDoc .Boleto.Buyer.Document}}

                            Endereço:  {{.Boleto.Buyer.Address.Street}} {{.Boleto.Buyer.Address.Number}}, {{.Boleto.Buyer.Address.District}} - {{.Boleto.Buyer.Address.City}}, {{.Boleto.Buyer.Address.StateCode}} - {{.Boleto.Buyer.Address.ZipCode}}

                            Sacador/Avalista:
                            CNPJ/CPF:

	{{end}}
`

//HTML renderiza HTML do boleto
func HTML(boleto models.BoletoView, format string) string {
	b := tmpl.New()
	boleto.BankLogo = template.HTML(GetLogo(boleto.Boleto.BankNumber))
	boleto.Format = format
	bcode, _ := twooffive.Encode(boleto.Barcode, true)
	orgBounds := bcode.Bounds()
	orgWidth := orgBounds.Max.X - orgBounds.Min.X
	img, _ := barcode.Scale(bcode, orgWidth, 50)
	buf := new(bytes.Buffer)
	err := jpeg.Encode(buf, img, nil)
	boleto.Barcode64 = base64.StdEncoding.EncodeToString(buf.Bytes())
	s, err := b.From(boleto).To(templateBoleto).Transform(boletoForm)
	if err != nil {
		fmt.Println(err)
	}
	return s
}

//Get retorna estrategia de acordo com o banco ou erro caso o banco não exista
func GetLogo(number models.BankNumber) string {
	switch number {
	case models.BancoDoBrasil:
		return logoBB
	case models.Santander:
		return logoSantander
	default:
		return fmt.Sprintf("Banco %d não existe", number)
	}
}<|endoftext|>


<commit_before>/*
// Copyright (c) 2016 Intel Corporation
//
// 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.
*/

/* TODO

5. Install kernel
12. Make most output from osprepare optional
*/

package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"syscall"
)

func init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s:\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "%s [prepare|start|stop|quit|status|connect|delete]\n\n", os.Args[0])
		fmt.Fprintln(os.Stderr, "- prepare : creates a new VM")
		fmt.Fprintln(os.Stderr, "- start : boots a stopped VM")
		fmt.Fprintln(os.Stderr, "- stop : cleanly powers down a running VM")
		fmt.Fprintln(os.Stderr, "- quit : quits a running VM")
		fmt.Fprintln(os.Stderr, "- status : prints status information about the ciao-down VM")
		fmt.Fprintln(os.Stderr, "- connect : connects to the VM via SSH")
		fmt.Fprintln(os.Stderr, "- delete : shuts down and deletes the VM")
	}
}

func vmFlags(fs *flag.FlagSet, memGB, CPUs *int) {
	*memGB, *CPUs = getMemAndCpus()
	fs.IntVar(memGB, "mem", *memGB, "Gigabytes of RAM allocated to VM")
	fs.IntVar(CPUs, "cpus", *CPUs, "VCPUs assignged to VM")
}

func prepareFlags() (memGB int, CPUs int, debug bool, err error) {
	fs := flag.NewFlagSet("prepare", flag.ExitOnError)
	vmFlags(fs, &memGB, &CPUs)
	fs.BoolVar(&debug, "debug", false, "Enables debug mode")
	if err := fs.Parse(flag.Args()[1:]); err != nil {
		return -1, -1, false, err
	}

	return memGB, CPUs, debug, nil
}

func startFlags() (memGB int, CPUs int, err error) {
	fs := flag.NewFlagSet("start", flag.ExitOnError)
	vmFlags(fs, &memGB, &CPUs)
	if err := fs.Parse(flag.Args()[1:]); err != nil {
		return -1, -1, err
	}

	return memGB, CPUs, nil
}

func downloadProgress(p progress) {
	if p.totalMB >= 0 {
		fmt.Printf("Downloaded %d MB of %d\n", p.downloadedMB, p.totalMB)
	} else {
		fmt.Printf("Downloaded %d MB\n", p.downloadedMB)
	}
}

func prepare(ctx context.Context, errCh chan error) {
	if !hostSupportsNestedKVM() {
		errCh <- fmt.Errorf("Nested KVM is not enabled.  Please enable try again.")
		return
	}

	fmt.Println("Checking environment")

	memGB, CPUs, debug, err := prepareFlags()
	if err != nil {
		errCh <- err
		return
	}

	ws, err := prepareEnv(ctx)
	if err != nil {
		errCh <- err
		return
	}

	_, err = os.Stat(ws.instanceDir)
	if err == nil {
		errCh <- fmt.Errorf("instance already exists")
		return
	}

	fmt.Println("Installing host dependencies")
	installDeps(ctx)

	err = os.MkdirAll(ws.instanceDir, 0755)
	if err != nil {
		errCh <- fmt.Errorf("unable to create cache dir: %v", err)
		return
	}

	failed := true
	defer func() {
		if failed {
			_ = os.RemoveAll(ws.instanceDir)
		}
	}()

	qcowPath, err := downloadUbuntu(ctx, ws.ciaoDir, downloadProgress)
	if err != nil {
		errCh <- err
		return
	}

	err = buildISOImage(ctx, ws.instanceDir, ws, debug)
	if err != nil {
		errCh <- err
		return
	}

	err = createRootfs(ctx, qcowPath, ws.instanceDir)
	if err != nil {
		errCh <- err
		return
	}

	fmt.Printf("Booting VM with %d GB RAM and %d cpus\n", memGB, CPUs)

	err = bootVM(ctx, ws, memGB, CPUs)
	if err != nil {
		errCh <- err
		return
	}

	err = manageInstallation(ctx, ws.instanceDir, ws)
	if err != nil {
		errCh <- err
		return
	}
	errCh <- nil
	failed = false
	fmt.Println("VM successfully created!")
	fmt.Println("Type ciao-down connect to start using it.")
}

func start(ctx context.Context, errCh chan error) {
	if !hostSupportsNestedKVM() {
		errCh <- fmt.Errorf("Nested KVM is not enabled.  Please enable try again.")
		return
	}

	memGB, CPUs, err := startFlags()
	if err != nil {
		errCh <- err
		return
	}

	ws, err := prepareEnv(ctx)
	if err != nil {
		errCh <- err
		return
	}

	fmt.Printf("Booting VM with %d GB RAM and %d cpus\n", memGB, CPUs)

	err = bootVM(ctx, ws, memGB, CPUs)
	if err != nil {
		errCh <- err
		return
	}

	if err == nil {
		fmt.Println("VM Started")
	}

	errCh <- err
}

func stop(ctx context.Context, errCh chan error) {
	ws, err := prepareEnv(ctx)
	if err != nil {
		errCh <- err
		return
	}

	err = stopVM(ctx, ws.instanceDir)
	if err != nil {
		errCh <- err
		return
	}

	if err == nil {
		fmt.Println("VM Stopped")
	}

	errCh <- err
}

func quit(ctx context.Context, errCh chan error) {
	ws, err := prepareEnv(ctx)
	if err != nil {
		errCh <- err
		return
	}

	err = quitVM(ctx, ws.instanceDir)
	if err != nil {
		errCh <- err
		return
	}

	if err == nil {
		fmt.Println("VM Quit")
	}

	errCh <- err
}

func status(ctx context.Context, errCh chan error) {
	ws, err := prepareEnv(ctx)
	if err != nil {
		errCh <- err
		return
	}

	statusVM(ctx, ws.instanceDir)
	errCh <- err
}

func connect(errCh chan error) {
	path, err := exec.LookPath("ssh")
	if err != nil {
		errCh <- fmt.Errorf("Unable to locate ssh binary")
	}

	err = syscall.Exec(path, []string{path, "127.0.0.1", "-p", "10022"},
		os.Environ())
	errCh <- err
}

func delete(ctx context.Context, errCh chan error) {
	ws, err := prepareEnv(ctx)
	if err != nil {
		errCh <- err
		return
	}

	_ = quitVM(ctx, ws.instanceDir)
	err = os.RemoveAll(ws.instanceDir)
	if err != nil {
		errCh <- fmt.Errorf("unable to delete instance: %v", err)
		return
	}

	knownHosts := path.Join(ws.Home, ".ssh", "known_hosts")
	err = exec.Command("ssh-keygen", "-f", knownHosts, "-R", "[127.0.0.1]:10022").Run()
	if err != nil {
		fmt.Println("Failed to remove VM entry from known_hosts")
	}

	errCh <- nil
}

func runCommand(signalCh <-chan os.Signal) error {
	var err error

	errCh := make(chan error)
	ctx, cancelFunc := context.WithCancel(context.Background())
	switch os.Args[1] {
	case "prepare":
		go prepare(ctx, errCh)
	case "start":
		go start(ctx, errCh)
	case "stop":
		go stop(ctx, errCh)
	case "quit":
		go quit(ctx, errCh)
	case "status":
		go status(ctx, errCh)
	case "connect":
		go connect(errCh)
	case "delete":
		go delete(ctx, errCh)
	}
	select {
	case <-signalCh:
		cancelFunc()
		err = <-errCh
	case err = <-errCh:
		cancelFunc()
	}

	return err
}

func main() {
	flag.Parse()
	if len(os.Args) < 2 ||
		!(os.Args[1] == "prepare" || os.Args[1] == "start" || os.Args[1] == "stop" ||
			os.Args[1] == "quit" || os.Args[1] == "status" ||
			os.Args[1] == "connect" || os.Args[1] == "delete") {
		flag.Usage()
		os.Exit(1)
	}

	signalCh := make(chan os.Signal, 1)
	signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	if err := runCommand(signalCh); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}
<commit_msg>ciao-down: Remove unneeded if statements<commit_after>/*
// Copyright (c) 2016 Intel Corporation
//
// 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.
*/

/* TODO

5. Install kernel
12. Make most output from osprepare optional
*/

package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"syscall"
)

func init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s:\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "%s [prepare|start|stop|quit|status|connect|delete]\n\n", os.Args[0])
		fmt.Fprintln(os.Stderr, "- prepare : creates a new VM")
		fmt.Fprintln(os.Stderr, "- start : boots a stopped VM")
		fmt.Fprintln(os.Stderr, "- stop : cleanly powers down a running VM")
		fmt.Fprintln(os.Stderr, "- quit : quits a running VM")
		fmt.Fprintln(os.Stderr, "- status : prints status information about the ciao-down VM")
		fmt.Fprintln(os.Stderr, "- connect : connects to the VM via SSH")
		fmt.Fprintln(os.Stderr, "- delete : shuts down and deletes the VM")
	}
}

func vmFlags(fs *flag.FlagSet, memGB, CPUs *int) {
	*memGB, *CPUs = getMemAndCpus()
	fs.IntVar(memGB, "mem", *memGB, "Gigabytes of RAM allocated to VM")
	fs.IntVar(CPUs, "cpus", *CPUs, "VCPUs assignged to VM")
}

func prepareFlags() (memGB int, CPUs int, debug bool, err error) {
	fs := flag.NewFlagSet("prepare", flag.ExitOnError)
	vmFlags(fs, &memGB, &CPUs)
	fs.BoolVar(&debug, "debug", false, "Enables debug mode")
	if err := fs.Parse(flag.Args()[1:]); err != nil {
		return -1, -1, false, err
	}

	return memGB, CPUs, debug, nil
}

func startFlags() (memGB int, CPUs int, err error) {
	fs := flag.NewFlagSet("start", flag.ExitOnError)
	vmFlags(fs, &memGB, &CPUs)
	if err := fs.Parse(flag.Args()[1:]); err != nil {
		return -1, -1, err
	}

	return memGB, CPUs, nil
}

func downloadProgress(p progress) {
	if p.totalMB >= 0 {
		fmt.Printf("Downloaded %d MB of %d\n", p.downloadedMB, p.totalMB)
	} else {
		fmt.Printf("Downloaded %d MB\n", p.downloadedMB)
	}
}

func prepare(ctx context.Context, errCh chan error) {
	if !hostSupportsNestedKVM() {
		errCh <- fmt.Errorf("Nested KVM is not enabled.  Please enable try again.")
		return
	}

	fmt.Println("Checking environment")

	memGB, CPUs, debug, err := prepareFlags()
	if err != nil {
		errCh <- err
		return
	}

	ws, err := prepareEnv(ctx)
	if err != nil {
		errCh <- err
		return
	}

	_, err = os.Stat(ws.instanceDir)
	if err == nil {
		errCh <- fmt.Errorf("instance already exists")
		return
	}

	fmt.Println("Installing host dependencies")
	installDeps(ctx)

	err = os.MkdirAll(ws.instanceDir, 0755)
	if err != nil {
		errCh <- fmt.Errorf("unable to create cache dir: %v", err)
		return
	}

	failed := true
	defer func() {
		if failed {
			_ = os.RemoveAll(ws.instanceDir)
		}
	}()

	qcowPath, err := downloadUbuntu(ctx, ws.ciaoDir, downloadProgress)
	if err != nil {
		errCh <- err
		return
	}

	err = buildISOImage(ctx, ws.instanceDir, ws, debug)
	if err != nil {
		errCh <- err
		return
	}

	err = createRootfs(ctx, qcowPath, ws.instanceDir)
	if err != nil {
		errCh <- err
		return
	}

	fmt.Printf("Booting VM with %d GB RAM and %d cpus\n", memGB, CPUs)

	err = bootVM(ctx, ws, memGB, CPUs)
	if err != nil {
		errCh <- err
		return
	}

	err = manageInstallation(ctx, ws.instanceDir, ws)
	if err != nil {
		errCh <- err
		return
	}
	errCh <- nil
	failed = false
	fmt.Println("VM successfully created!")
	fmt.Println("Type ciao-down connect to start using it.")
}

func start(ctx context.Context, errCh chan error) {
	if !hostSupportsNestedKVM() {
		errCh <- fmt.Errorf("Nested KVM is not enabled.  Please enable try again.")
		return
	}

	memGB, CPUs, err := startFlags()
	if err != nil {
		errCh <- err
		return
	}

	ws, err := prepareEnv(ctx)
	if err != nil {
		errCh <- err
		return
	}

	fmt.Printf("Booting VM with %d GB RAM and %d cpus\n", memGB, CPUs)

	err = bootVM(ctx, ws, memGB, CPUs)
	if err != nil {
		errCh <- err
		return
	}

	fmt.Println("VM Started")

	errCh <- err
}

func stop(ctx context.Context, errCh chan error) {
	ws, err := prepareEnv(ctx)
	if err != nil {
		errCh <- err
		return
	}

	err = stopVM(ctx, ws.instanceDir)
	if err != nil {
		errCh <- err
		return
	}

	fmt.Println("VM Stopped")

	errCh <- err
}

func quit(ctx context.Context, errCh chan error) {
	ws, err := prepareEnv(ctx)
	if err != nil {
		errCh <- err
		return
	}

	err = quitVM(ctx, ws.instanceDir)
	if err != nil {
		errCh <- err
		return
	}

	fmt.Println("VM Quit")

	errCh <- err
}

func status(ctx context.Context, errCh chan error) {
	ws, err := prepareEnv(ctx)
	if err != nil {
		errCh <- err
		return
	}

	statusVM(ctx, ws.instanceDir)
	errCh <- err
}

func connect(errCh chan error) {
	path, err := exec.LookPath("ssh")
	if err != nil {
		errCh <- fmt.Errorf("Unable to locate ssh binary")
	}

	err = syscall.Exec(path, []string{path, "127.0.0.1", "-p", "10022"},
		os.Environ())
	errCh <- err
}

func delete(ctx context.Context, errCh chan error) {
	ws, err := prepareEnv(ctx)
	if err != nil {
		errCh <- err
		return
	}

	_ = quitVM(ctx, ws.instanceDir)
	err = os.RemoveAll(ws.instanceDir)
	if err != nil {
		errCh <- fmt.Errorf("unable to delete instance: %v", err)
		return
	}

	knownHosts := path.Join(ws.Home, ".ssh", "known_hosts")
	err = exec.Command("ssh-keygen", "-f", knownHosts, "-R", "[127.0.0.1]:10022").Run()
	if err != nil {
		fmt.Println("Failed to remove VM entry from known_hosts")
	}

	errCh <- nil
}

func runCommand(signalCh <-chan os.Signal) error {
	var err error

	errCh := make(chan error)
	ctx, cancelFunc := context.WithCancel(context.Background())
	switch os.Args[1] {
	case "prepare":
		go prepare(ctx, errCh)
	case "start":
		go start(ctx, errCh)
	case "stop":
		go stop(ctx, errCh)
	case "quit":
		go quit(ctx, errCh)
	case "status":
		go status(ctx, errCh)
	case "connect":
		go connect(errCh)
	case "delete":
		go delete(ctx, errCh)
	}
	select {
	case <-signalCh:
		cancelFunc()
		err = <-errCh
	case err = <-errCh:
		cancelFunc()
	}

	return err
}

func main() {
	flag.Parse()
	if len(os.Args) < 2 ||
		!(os.Args[1] == "prepare" || os.Args[1] == "start" || os.Args[1] == "stop" ||
			os.Args[1] == "quit" || os.Args[1] == "status" ||
			os.Args[1] == "connect" || os.Args[1] == "delete") {
		flag.Usage()
		os.Exit(1)
	}

	signalCh := make(chan os.Signal, 1)
	signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	if err := runCommand(signalCh); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package bot

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/FederationOfFathers/dashboard/db"
	"github.com/FederationOfFathers/dashboard/messaging"
	"github.com/bwmarrin/discordgo"
	"github.com/honeycombio/beeline-go"
	"go.uber.org/zap"
	"google.golang.org/api/youtube/v3"
)

type DiscordAPI struct {
	Config         DiscordCfg
	discord        *discordgo.Session
	assignmentMsgs map[string]map[string]string
	yt             *youtube.Service
}

type DiscordCfg struct {
	ClientId        string         `yaml:"appClientId"`
	Token           string         `yaml:"botToken"`
	StreamChannelId string         `yaml:"streamChannelId"`
	GuildId         string         `yaml:"guildId"`
	RoleCfg         DiscordRoleCfg `yaml:"roleConfig"`
}

type GuildChannels struct {
	Categories []ChannelCategory
}

type ChannelCategory struct {
	ID       string
	Name     string
	Channels []*Channel
}

type Channel struct {
	ID   string
	Name string
}

var discordApi *DiscordAPI

func NewDiscordAPI(cfg DiscordCfg, yt *youtube.Service) *DiscordAPI {
	return &DiscordAPI{
		Config: cfg,
		yt:     yt,
	}
}

// StartDiscord starts Discord API bot
func StartDiscord(cfg DiscordCfg, yt *youtube.Service) *DiscordAPI {
	discordApi = NewDiscordAPI(cfg, yt)
	discordApi.Connect()
	if cfg.RoleCfg.ChannelId != "" {
		discordApi.StartRoleHandlers()
	}

	// register slash command
	discordApi.registerSlashStream()

	//add handlers
	discordApi.discord.AddHandler(discordApi.slashCommandHandlers)
	discordApi.discord.AddHandler(discordApi.roleAssignmentHandler)
	discordApi.discord.AddHandler(discordApi.teamCommandHandler)
	discordApi.discord.AddHandler(discordApi.verifiedEventsHandler)

	//go discordApi.setChannelAssignMessage()

	discordApi.discord.UpdateGameStatus(0, "ui.fofgaming.com | !team")

	// data cache
	data.load()
	populateLists()
	go mindLists()

	return discordApi

}

// verifiedEventsHandler checks if the user is verified before running the handler
func (d *DiscordAPI) verifiedEventsHandler(s *discordgo.Session, event *discordgo.MessageCreate) {
	if event.GuildID != d.Config.GuildId {
		return
	}
	fields := strings.Fields(event.Content)
	if len(fields) < 1 {
		return
	}

	switch fields[0] {
	case channelCommand:
		d.tempChannelCommandHandler(s, event)
	}
}

// MindGuild starts routines to monitor Discord things like channels
func (d *DiscordAPI) MindGuild() {
	go func() {

		d.mindChannelList()
		ticker := time.NewTicker(1 * time.Minute)
		for range ticker.C {
			// get channels and save them to the db
			d.mindChannelList()
		}

	}()

}

func (d *DiscordAPI) mindChannelList() {

	_, span := beeline.StartSpan(context.Background(), "discord.MindChannelList")
	defer span.Send()

	channels := d.guildChannels()
	if err := saveChannelsToDB(channels); err == nil {
		// purge old channels if no errors on save
		DB.PurgeOldEventChannels(-1 * time.Minute)
	}

}

func (d *DiscordAPI) guildChannels() *GuildChannels {
	guildChannels := &GuildChannels{
		Categories: []ChannelCategory{
			{ID: "", Name: ""},
		},
	}

	channels, err := d.discord.GuildChannels(d.Config.GuildId)
	if err != nil {
		Logger.Error("unable to get guild channels", zap.Error(err))
	}

	var textCh []discordgo.Channel
	// get the categories
	for _, ch := range channels {
		switch ch.Type {
		case discordgo.ChannelTypeGuildCategory: // create categories
			category := &ChannelCategory{
				ID:   ch.ID,
				Name: ch.Name,
			}
			guildChannels.Categories = append(guildChannels.Categories, *category)
		case discordgo.ChannelTypeGuildText: // store text channels for iteration
			textCh = append(textCh, *ch)
		}

	}

	// sort the text channels
	for _, ch := range textCh {
		parentID := ch.ParentID
		for i, cat := range guildChannels.Categories { // find a the parent category and add it
			if cat.ID == parentID {
				tCh := &Channel{
					ID:   ch.ID,
					Name: ch.Name,
				}
				guildChannels.Categories[i].Channels = append(guildChannels.Categories[i].Channels, tCh)
			}
		}
	}

	return guildChannels
}

func (d *DiscordAPI) teamCommandHandler(s *discordgo.Session, event *discordgo.MessageCreate) {
	if event.GuildID != d.Config.GuildId {
		return
	}
	switch event.Content {
	case "!team":
		d.sendTeamToolLink(event)
	}
}

func (d DiscordAPI) sendTeamToolLink(m *discordgo.MessageCreate) {
	_, _ = d.discord.ChannelMessageSend(m.ChannelID, "FoF Team Tool -> https://ui.fofgaming.com")
}

// FindIDByUsername searches the server for a user with the specified username. Returns the ID and username
func (d *DiscordAPI) FindIDByUsername(username string) (string, string) {
	return d.FindIDByUsernameStartingAt(username, "0")
}

// FindGuildRole searches the configured guild roles to find the one that matches the given roleID
func (d *DiscordAPI) FindGuildRole(roleID string) (*discordgo.Role, error) {
	roles, err := d.discord.GuildRoles(d.Config.GuildId)
	if err != nil {
		return nil, err
	}

	for _, role := range roles {
		if role.ID == roleID {
			return role, nil
		}
	}

	return nil, fmt.Errorf("No matching role found")
}

func (d *DiscordAPI) FindGuildRoleByName(name string) (*discordgo.Role, error) {
	roles, err := d.discord.GuildRoles(d.Config.GuildId)
	if err != nil {
		return nil, err
	}

	for _, role := range roles {
		if role.Name == name {
			return role, nil
		}
	}

	return nil, fmt.Errorf("No matching role found")
}

// FindIDByUsernameStartingAt searches the server for a user with the specified username starting at the id/snowflake. Returns the ID and username
func (d *DiscordAPI) FindIDByUsernameStartingAt(username string, snowflake string) (string, string) {
	members, err := d.discord.GuildMembers(d.Config.GuildId, snowflake, 1000)
	if err != nil {
		Logger.Error("unable to get guild members list", zap.Error(err))
	}

	// if no members, we've iterated through all members
	if len(members) <= 0 {
		return "", ""
	}

	// search for the member in the current list
	usernameParts := strings.SplitN(username, "#", 2)
	maxID := snowflake
	for _, member := range members {
		maxID = member.User.ID
		// return matching usrname/discriminator combo
		if strings.ToLower(member.User.Username) == strings.ToLower(usernameParts[0]) && member.User.Discriminator == usernameParts[1] {
			return member.User.ID, member.Nick
		}
	}

	// recursion to keep searching
	return d.FindIDByUsernameStartingAt(username, maxID)
}

// Connect Needs to be called before any other API function work
func (d *DiscordAPI) Connect() {
	dg, err := discordgo.New("Bot " + d.Config.Token)
	if err != nil {
		Logger.Error("Unable to create discord connection", zap.Error(err))
		return
	}

	d.discord = dg
	dg.Open()
}

// Needs to be called to disconnect from discord
func (d *DiscordAPI) Shutdown() {
	Logger.Warn("Discord is shutting down")
	d.discord.Close()
}

// SendDM sends a DM to a user from the bot
func (d *DiscordAPI) SendDM(userID string, message string) {
	if ch, err := d.discord.UserChannelCreate(userID); err != nil {
		Logger.Error("Unable to create DM", zap.String("userID", userID), zap.Error(err))
	} else {
		_, err := d.discord.ChannelMessageSend(ch.ID, message)
		if err != nil {
			Logger.Error("unable to send DM", zap.String("userID", userID), zap.String("message", message), zap.Error(err))
		}
	}
}

func (d DiscordAPI) PostStreamMessage(sm messaging.StreamMessage) error {
	if d.discord == nil {
		return fmt.Errorf("discord API not connected")
	}
	if d.Config.StreamChannelId == "" {
		return fmt.Errorf("stream channel id not configured")
	}
	author := discordgo.MessageEmbedAuthor{
		Name:    fmt.Sprintf("%s is live!", sm.Username),
		IconURL: sm.UserLogo,
	}

	footer := discordgo.MessageEmbedFooter{
		Text:    fmt.Sprintf("%s | %s", sm.Platform, sm.Timestamp),
		IconURL: sm.PlatformLogo,
	}
	messageEmbed := discordgo.MessageEmbed{
		Description: sm.Description,
		Color:       sm.PlatformColorInt,
		URL:         sm.URL,
		Author:      &author,
		Image: &discordgo.MessageEmbedImage{
			URL:    sm.ThumbnailURL,
			Width:  320,
			Height: 180,
		},
		Footer: &footer,
		Fields: []*discordgo.MessageEmbedField{
			{
				Name:   "Game",
				Value:  sm.Game,
				Inline: false,
			},
			{
				Name:   "Stream URL",
				Value:  sm.URL,
				Inline: false,
			},
		},
	}

	_, err := d.discord.ChannelMessageSendComplex(d.Config.StreamChannelId, &discordgo.MessageSend{
		Content: fmt.Sprintf("%s is streaming **%s**\n%s", sm.Username, sm.Game, sm.URL),
		Embed:   &messageEmbed,
	})
	return err
}

// PostNewEventMessage
func (d *DiscordAPI) PostNewEventMessage(e *db.Event) error {

	host := e.Host()
	if host == "" {
		host = "Someone"
	}

	return d.postEventMessage(e, fmt.Sprintf("🌟 %s has created a new event", host))

}

// PostJoinEventMessage sends a new message to Discord when a user joins an event
func (d *DiscordAPI) PostJoinEventMessage(e *db.Event, member string) error {

	return d.postEventMessage(e, fmt.Sprintf("🔹 %s has joined an event", member))

}

func (d *DiscordAPI) postEventMessage(e *db.Event, title string) error {
	if d.discord == nil {
		return fmt.Errorf("discord API not connected")
	}
	var members []string
	for _, eMember := range e.Members {
		m, err := DB.MemberByID(eMember.MemberID)
		if err != nil {
			Logger.Error("unable to get member", zap.Int("id", eMember.MemberID), zap.Error(err))
		}
		members = append(members, m.Name)
	}

	loc, _ := time.LoadLocation("America/New_York") // show times in EST

	messageEmbed := discordgo.MessageEmbed{
		Title:       title,
		Description: fmt.Sprintf("[***%s*** [%s]](https://ui.fofgaming.com)\ngo to [https://ui.fofgaming.com](https://ui.fofgaming.com) to join, find more events, or create your own", e.Title, e.When.In(loc).Format("1/2, 3:04 PM MST")),
		Color:       0x007BFF,
		Fields: []*discordgo.MessageEmbedField{
			{
				Name:   fmt.Sprintf("Members (%d/%d)", len(e.Members), e.Need),
				Value:  strings.Join(members, ", "),
				Inline: false,
			},
		},
	}

	_, err := d.discord.ChannelMessageSendEmbed(e.EventChannelID, &messageEmbed)
	if err != nil {
		Logger.Error("unable to send discord message", zap.Error(err), zap.Any("message", messageEmbed), zap.Any("event", e))
	}

	return err
}

func saveChannelsToDB(gc *GuildChannels) error {
	var err error
	for _, cat := range gc.Categories {
		for _, ch := range cat.Channels {
			dbEventChannel := &db.EventChannel{
				ID:                  ch.ID,
				ChannelCategoryName: cat.Name,
				ChannelCategoryID:   cat.ID,
				ChannelName:         ch.Name,
				UpdatedAt:           time.Now(),
			}

			if err1 := DB.SaveEventChannel(dbEventChannel); err1 != nil {
				err = err1
				Logger.Error("unable to save event channel data", zap.Error(err1))
			}
		}
	}

	return err
}

func userIDFromMention(mention string) string {
	return strings.Trim(mention[2:len(mention)-1], "!")
}

func channelIDFromChannelLink(channelLink string) string {
	return strings.Trim(channelLink[2:len(channelLink)-1], "!")
}
<commit_msg>Update formatting of live stream message<commit_after>package bot

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/FederationOfFathers/dashboard/db"
	"github.com/FederationOfFathers/dashboard/messaging"
	"github.com/bwmarrin/discordgo"
	"github.com/honeycombio/beeline-go"
	"go.uber.org/zap"
	"google.golang.org/api/youtube/v3"
)

type DiscordAPI struct {
	Config         DiscordCfg
	discord        *discordgo.Session
	assignmentMsgs map[string]map[string]string
	yt             *youtube.Service
}

type DiscordCfg struct {
	ClientId        string         `yaml:"appClientId"`
	Token           string         `yaml:"botToken"`
	StreamChannelId string         `yaml:"streamChannelId"`
	GuildId         string         `yaml:"guildId"`
	RoleCfg         DiscordRoleCfg `yaml:"roleConfig"`
}

type GuildChannels struct {
	Categories []ChannelCategory
}

type ChannelCategory struct {
	ID       string
	Name     string
	Channels []*Channel
}

type Channel struct {
	ID   string
	Name string
}

var discordApi *DiscordAPI

func NewDiscordAPI(cfg DiscordCfg, yt *youtube.Service) *DiscordAPI {
	return &DiscordAPI{
		Config: cfg,
		yt:     yt,
	}
}

// StartDiscord starts Discord API bot
func StartDiscord(cfg DiscordCfg, yt *youtube.Service) *DiscordAPI {
	discordApi = NewDiscordAPI(cfg, yt)
	discordApi.Connect()
	if cfg.RoleCfg.ChannelId != "" {
		discordApi.StartRoleHandlers()
	}

	// register slash command
	discordApi.registerSlashStream()

	//add handlers
	discordApi.discord.AddHandler(discordApi.slashCommandHandlers)
	discordApi.discord.AddHandler(discordApi.roleAssignmentHandler)
	discordApi.discord.AddHandler(discordApi.teamCommandHandler)
	discordApi.discord.AddHandler(discordApi.verifiedEventsHandler)

	//go discordApi.setChannelAssignMessage()

	discordApi.discord.UpdateGameStatus(0, "ui.fofgaming.com | !team")

	// data cache
	data.load()
	populateLists()
	go mindLists()

	return discordApi

}

// verifiedEventsHandler checks if the user is verified before running the handler
func (d *DiscordAPI) verifiedEventsHandler(s *discordgo.Session, event *discordgo.MessageCreate) {
	if event.GuildID != d.Config.GuildId {
		return
	}
	fields := strings.Fields(event.Content)
	if len(fields) < 1 {
		return
	}

	switch fields[0] {
	case channelCommand:
		d.tempChannelCommandHandler(s, event)
	}
}

// MindGuild starts routines to monitor Discord things like channels
func (d *DiscordAPI) MindGuild() {
	go func() {

		d.mindChannelList()
		ticker := time.NewTicker(1 * time.Minute)
		for range ticker.C {
			// get channels and save them to the db
			d.mindChannelList()
		}

	}()

}

func (d *DiscordAPI) mindChannelList() {

	_, span := beeline.StartSpan(context.Background(), "discord.MindChannelList")
	defer span.Send()

	channels := d.guildChannels()
	if err := saveChannelsToDB(channels); err == nil {
		// purge old channels if no errors on save
		DB.PurgeOldEventChannels(-1 * time.Minute)
	}

}

func (d *DiscordAPI) guildChannels() *GuildChannels {
	guildChannels := &GuildChannels{
		Categories: []ChannelCategory{
			{ID: "", Name: ""},
		},
	}

	channels, err := d.discord.GuildChannels(d.Config.GuildId)
	if err != nil {
		Logger.Error("unable to get guild channels", zap.Error(err))
	}

	var textCh []discordgo.Channel
	// get the categories
	for _, ch := range channels {
		switch ch.Type {
		case discordgo.ChannelTypeGuildCategory: // create categories
			category := &ChannelCategory{
				ID:   ch.ID,
				Name: ch.Name,
			}
			guildChannels.Categories = append(guildChannels.Categories, *category)
		case discordgo.ChannelTypeGuildText: // store text channels for iteration
			textCh = append(textCh, *ch)
		}

	}

	// sort the text channels
	for _, ch := range textCh {
		parentID := ch.ParentID
		for i, cat := range guildChannels.Categories { // find a the parent category and add it
			if cat.ID == parentID {
				tCh := &Channel{
					ID:   ch.ID,
					Name: ch.Name,
				}
				guildChannels.Categories[i].Channels = append(guildChannels.Categories[i].Channels, tCh)
			}
		}
	}

	return guildChannels
}

func (d *DiscordAPI) teamCommandHandler(s *discordgo.Session, event *discordgo.MessageCreate) {
	if event.GuildID != d.Config.GuildId {
		return
	}
	switch event.Content {
	case "!team":
		d.sendTeamToolLink(event)
	}
}

func (d DiscordAPI) sendTeamToolLink(m *discordgo.MessageCreate) {
	_, _ = d.discord.ChannelMessageSend(m.ChannelID, "FoF Team Tool -> https://ui.fofgaming.com")
}

// FindIDByUsername searches the server for a user with the specified username. Returns the ID and username
func (d *DiscordAPI) FindIDByUsername(username string) (string, string) {
	return d.FindIDByUsernameStartingAt(username, "0")
}

// FindGuildRole searches the configured guild roles to find the one that matches the given roleID
func (d *DiscordAPI) FindGuildRole(roleID string) (*discordgo.Role, error) {
	roles, err := d.discord.GuildRoles(d.Config.GuildId)
	if err != nil {
		return nil, err
	}

	for _, role := range roles {
		if role.ID == roleID {
			return role, nil
		}
	}

	return nil, fmt.Errorf("No matching role found")
}

func (d *DiscordAPI) FindGuildRoleByName(name string) (*discordgo.Role, error) {
	roles, err := d.discord.GuildRoles(d.Config.GuildId)
	if err != nil {
		return nil, err
	}

	for _, role := range roles {
		if role.Name == name {
			return role, nil
		}
	}

	return nil, fmt.Errorf("No matching role found")
}

// FindIDByUsernameStartingAt searches the server for a user with the specified username starting at the id/snowflake. Returns the ID and username
func (d *DiscordAPI) FindIDByUsernameStartingAt(username string, snowflake string) (string, string) {
	members, err := d.discord.GuildMembers(d.Config.GuildId, snowflake, 1000)
	if err != nil {
		Logger.Error("unable to get guild members list", zap.Error(err))
	}

	// if no members, we've iterated through all members
	if len(members) <= 0 {
		return "", ""
	}

	// search for the member in the current list
	usernameParts := strings.SplitN(username, "#", 2)
	maxID := snowflake
	for _, member := range members {
		maxID = member.User.ID
		// return matching usrname/discriminator combo
		if strings.ToLower(member.User.Username) == strings.ToLower(usernameParts[0]) && member.User.Discriminator == usernameParts[1] {
			return member.User.ID, member.Nick
		}
	}

	// recursion to keep searching
	return d.FindIDByUsernameStartingAt(username, maxID)
}

// Connect Needs to be called before any other API function work
func (d *DiscordAPI) Connect() {
	dg, err := discordgo.New("Bot " + d.Config.Token)
	if err != nil {
		Logger.Error("Unable to create discord connection", zap.Error(err))
		return
	}

	d.discord = dg
	dg.Open()
}

// Needs to be called to disconnect from discord
func (d *DiscordAPI) Shutdown() {
	Logger.Warn("Discord is shutting down")
	d.discord.Close()
}

// SendDM sends a DM to a user from the bot
func (d *DiscordAPI) SendDM(userID string, message string) {
	if ch, err := d.discord.UserChannelCreate(userID); err != nil {
		Logger.Error("Unable to create DM", zap.String("userID", userID), zap.Error(err))
	} else {
		_, err := d.discord.ChannelMessageSend(ch.ID, message)
		if err != nil {
			Logger.Error("unable to send DM", zap.String("userID", userID), zap.String("message", message), zap.Error(err))
		}
	}
}

func (d DiscordAPI) PostStreamMessage(sm messaging.StreamMessage) error {
	if d.discord == nil {
		return fmt.Errorf("discord API not connected")
	}
	if d.Config.StreamChannelId == "" {
		return fmt.Errorf("stream channel id not configured")
	}
	author := discordgo.MessageEmbedAuthor{
		Name:    sm.Username,
		IconURL: sm.UserLogo,
	}

	footer := discordgo.MessageEmbedFooter{
		Text:    fmt.Sprintf("%s | %s", sm.Platform, sm.Timestamp),
		IconURL: sm.PlatformLogo,
	}
	messageEmbed := discordgo.MessageEmbed{
		Description: fmt.Sprintf("%s\n%s", sm.Description, sm.URL),
		Color:       sm.PlatformColorInt,
		URL:         sm.URL,
		Author:      &author,
		Image: &discordgo.MessageEmbedImage{
			URL:    sm.ThumbnailURL,
			Width:  320,
			Height: 180,
		},
		Footer: &footer,
	}

	_, err := d.discord.ChannelMessageSendComplex(d.Config.StreamChannelId, &discordgo.MessageSend{
		Content: fmt.Sprintf("%s is Live!\n**%s**\n%s", sm.Username, sm.Description, sm.URL),
		Embed:   &messageEmbed,
	})
	return err
}

// PostNewEventMessage
func (d *DiscordAPI) PostNewEventMessage(e *db.Event) error {

	host := e.Host()
	if host == "" {
		host = "Someone"
	}

	return d.postEventMessage(e, fmt.Sprintf("🌟 %s has created a new event", host))

}

// PostJoinEventMessage sends a new message to Discord when a user joins an event
func (d *DiscordAPI) PostJoinEventMessage(e *db.Event, member string) error {

	return d.postEventMessage(e, fmt.Sprintf("🔹 %s has joined an event", member))

}

func (d *DiscordAPI) postEventMessage(e *db.Event, title string) error {
	if d.discord == nil {
		return fmt.Errorf("discord API not connected")
	}
	var members []string
	for _, eMember := range e.Members {
		m, err := DB.MemberByID(eMember.MemberID)
		if err != nil {
			Logger.Error("unable to get member", zap.Int("id", eMember.MemberID), zap.Error(err))
		}
		members = append(members, m.Name)
	}

	loc, _ := time.LoadLocation("America/New_York") // show times in EST

	messageEmbed := discordgo.MessageEmbed{
		Title:       title,
		Description: fmt.Sprintf("[***%s*** [%s]](https://ui.fofgaming.com)\ngo to [https://ui.fofgaming.com](https://ui.fofgaming.com) to join, find more events, or create your own", e.Title, e.When.In(loc).Format("1/2, 3:04 PM MST")),
		Color:       0x007BFF,
		Fields: []*discordgo.MessageEmbedField{
			{
				Name:   fmt.Sprintf("Members (%d/%d)", len(e.Members), e.Need),
				Value:  strings.Join(members, ", "),
				Inline: false,
			},
		},
	}

	_, err := d.discord.ChannelMessageSendEmbed(e.EventChannelID, &messageEmbed)
	if err != nil {
		Logger.Error("unable to send discord message", zap.Error(err), zap.Any("message", messageEmbed), zap.Any("event", e))
	}

	return err
}

func saveChannelsToDB(gc *GuildChannels) error {
	var err error
	for _, cat := range gc.Categories {
		for _, ch := range cat.Channels {
			dbEventChannel := &db.EventChannel{
				ID:                  ch.ID,
				ChannelCategoryName: cat.Name,
				ChannelCategoryID:   cat.ID,
				ChannelName:         ch.Name,
				UpdatedAt:           time.Now(),
			}

			if err1 := DB.SaveEventChannel(dbEventChannel); err1 != nil {
				err = err1
				Logger.Error("unable to save event channel data", zap.Error(err1))
			}
		}
	}

	return err
}

func userIDFromMention(mention string) string {
	return strings.Trim(mention[2:len(mention)-1], "!")
}

func channelIDFromChannelLink(channelLink string) string {
	return strings.Trim(channelLink[2:len(channelLink)-1], "!")
}
<|endoftext|>


<commit_before>/*
Package bridgeproxy provides a framework for writing proxies that connect
through one or more upstream proxies (called Peer below).

There are three main entry functions that can be used:

1. Serve() provides access to the last peer under the given address. This can
be used to implement a TLS-decrypting proxy server: Just specify a HTTPS
proxy as the last peer, and it will be available as an HTTP proxy on the
chosen address.

2. ListenTLS() provides a way to HIJACK TLS requests: A client connecting to
the specified address will be connected via the peers to the address it
indicates via SNI (Server Name Indication) in the TLS handshake

3. HTTPProxyHandler() constructs a http.Handler that can be used with
http.ListenAndServe() to create an HTTP proxy that accepts CONNECT, GET,
HEAD, and possibly other types of requests.
*/
package bridgeproxy

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"net/url"
)

// Peer is a server we are connecting to. This can either be an
// intermediate http(s) proxy server or the final server we want
// to connect to.
type Peer struct {
	TLSConfig    *tls.Config         // nil if unencrypted, valid config otherwise
	HostName     string              // The hostname to connect to
	Port         int                 // The port to connect to on the hostname
	ConnectExtra map[string][]string // Extra headers to send after the CONNECT line
}

// copyAndClose copies bytes from src to dst and closes both afterwards
func copyAndClose(dst io.WriteCloser, src io.ReadCloser) {
	if _, err := io.Copy(dst, src); err != nil {
		log.Println("Could not forward:", err)
	}
	src.Close()
	dst.Close()
}

// httpConnectResponseConn wraps a connection with a reader so we can read
// the response code first and then read the rest from the reader.
type httpConnectResponseConn struct {
	net.Conn
	io.ReadCloser
}

// Read should read from the reader, not the connection
func (conn *httpConnectResponseConn) Read(b []byte) (int, error) {
	return conn.ReadCloser.Read(b)
}

// Close should close the body not the connection
func (conn *httpConnectResponseConn) Close() error {
	return conn.ReadCloser.Close()
}

// doHTTPConnect issues an HTTP CONNECT request on a connection. It
// always returns a connection, but may also return an error.
//
// The parameter peer describes the peer we want to connect to
// The parameter activePeer is the latest peer we connected to in this chain
func doHTTPConnect(connection net.Conn, peer Peer, activePeer Peer) (net.Conn, error) {
	req := http.Request{
		Method: "CONNECT",
		URL:    &url.URL{Path: fmt.Sprintf("%s:%d", peer.HostName, peer.Port)},
		Header: http.Header(activePeer.ConnectExtra),
	}

	if err := req.Write(connection); err != nil {
		return connection, fmt.Errorf("connecting to %s: %s", peer.HostName, err.Error())
	}

	res, err := http.ReadResponse(bufio.NewReader(connection), &req)
	switch {
	case err != nil:
		return connection, fmt.Errorf("reading response: connecting to %s: %s", peer.HostName, err.Error())
	case res.StatusCode != 200:
		return connection, fmt.Errorf("invalid status code: connecting to %s: %d", peer.HostName, res.StatusCode)
	}

	return &httpConnectResponseConn{connection, res.Body}, nil
}

// DialProxy dials a proxy using the given slice of peers. It returns a
// network connection and error. Even if an error is returned, there may
// be a network connection that needs to be closed.
func DialProxy(peers []Peer) (net.Conn, error) {
	var connection net.Conn
	var err error
	for i, peer := range peers {
		// The first peer has to be dialed, others happen via connect
		if i == 0 {
			connection, err = net.Dial("tcp", fmt.Sprintf("%s:%d", peer.HostName, peer.Port))
		} else {
			connection, err = doHTTPConnect(connection, peer, peers[i-1])
		}
		if err != nil {
			return connection, err
		}

		if peer.TLSConfig != nil {
			tlsConnection := tls.Client(connection, peer.TLSConfig)
			if err := tlsConnection.Handshake(); err != nil {
				return connection, fmt.Errorf("handshake with %s failed: %s", peer.HostName, err)
			}
			connection = tlsConnection
		}
	}
	return connection, nil
}
<commit_msg>Revert "Revert "Establish connections in the background""<commit_after>/*
Package bridgeproxy provides a framework for writing proxies that connect
through one or more upstream proxies (called Peer below).

There are three main entry functions that can be used:

1. Serve() provides access to the last peer under the given address. This can
be used to implement a TLS-decrypting proxy server: Just specify a HTTPS
proxy as the last peer, and it will be available as an HTTP proxy on the
chosen address.

2. ListenTLS() provides a way to HIJACK TLS requests: A client connecting to
the specified address will be connected via the peers to the address it
indicates via SNI (Server Name Indication) in the TLS handshake

3. HTTPProxyHandler() constructs a http.Handler that can be used with
http.ListenAndServe() to create an HTTP proxy that accepts CONNECT, GET,
HEAD, and possibly other types of requests.
*/
package bridgeproxy

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"net/url"
	"time"
)

// Peer is a server we are connecting to. This can either be an
// intermediate http(s) proxy server or the final server we want
// to connect to.
type Peer struct {
	TLSConfig    *tls.Config         // nil if unencrypted, valid config otherwise
	HostName     string              // The hostname to connect to
	Port         int                 // The port to connect to on the hostname
	ConnectExtra map[string][]string // Extra headers to send after the CONNECT line
}

// copyAndClose copies bytes from src to dst and closes both afterwards
func copyAndClose(dst io.WriteCloser, src io.ReadCloser) {
	if _, err := io.Copy(dst, src); err != nil {
		log.Println("Could not forward:", err)
	}
	src.Close()
	dst.Close()
}

// httpConnectResponseConn wraps a connection with a reader so we can read
// the response code first and then read the rest from the reader.
type httpConnectResponseConn struct {
	net.Conn
	io.ReadCloser
}

// Read should read from the reader, not the connection
func (conn *httpConnectResponseConn) Read(b []byte) (int, error) {
	return conn.ReadCloser.Read(b)
}

// Close should close the body not the connection
func (conn *httpConnectResponseConn) Close() error {
	return conn.ReadCloser.Close()
}

// doHTTPConnect issues an HTTP CONNECT request on a connection. It
// always returns a connection, but may also return an error.
//
// The parameter peer describes the peer we want to connect to
// The parameter activePeer is the latest peer we connected to in this chain
func doHTTPConnect(connection net.Conn, peer Peer, activePeer Peer) (net.Conn, error) {
	req := http.Request{
		Method: "CONNECT",
		URL:    &url.URL{Path: fmt.Sprintf("%s:%d", peer.HostName, peer.Port)},
		Header: http.Header(activePeer.ConnectExtra),
	}

	if err := req.Write(connection); err != nil {
		return connection, fmt.Errorf("connecting to %s: %s", peer.HostName, err.Error())
	}

	res, err := http.ReadResponse(bufio.NewReader(connection), &req)
	switch {
	case err != nil:
		return connection, fmt.Errorf("reading response: connecting to %s: %s", peer.HostName, err.Error())
	case res.StatusCode != 200:
		return connection, fmt.Errorf("invalid status code: connecting to %s: %d", peer.HostName, res.StatusCode)
	}

	return &httpConnectResponseConn{connection, res.Body}, nil
}

// DialProxyInternal dials a proxy using the given slice of peers. It returns a
// network connection and error. Even if an error is returned, there may
// be a network connection that needs to be closed.
func DialProxyInternal(peers []Peer) (net.Conn, error) {
	var connection net.Conn
	var err error
	for i, peer := range peers {
		// The first peer has to be dialed, others happen via connect
		if i == 0 {
			connection, err = net.Dial("tcp", fmt.Sprintf("%s:%d", peer.HostName, peer.Port))
		} else {
			connection, err = doHTTPConnect(connection, peer, peers[i-1])
		}
		if err != nil {
			return connection, err
		}

		if peer.TLSConfig != nil {
			tlsConnection := tls.Client(connection, peer.TLSConfig)
			if err := tlsConnection.Handshake(); err != nil {
				return connection, fmt.Errorf("handshake with %s failed: %s", peer.HostName, err)
			}
			connection = tlsConnection
		}
	}
	return connection, nil
}

type connResult struct {
	c net.Conn
	e error
}

var tcpConnections = make(map[string]chan connResult)

// DialProxy is a buffered version of DialProxyInternal(). It keeps a channel for a given list of peers
// and generates new connections in a background goroutine, thus removing the overhead for establishing
// new connections for all except the first one (and occassional timed out ones).
func DialProxy(peers []Peer) (net.Conn, error) {
	a := time.Now()
	peersAsString := ""
	for _, peer := range peers {
		peersAsString += fmt.Sprintf("%s:%d/", peer.HostName, peer.Port)
	}
	chn, ok := tcpConnections[peersAsString]
	if !ok {
		chn = make(chan connResult)
		tcpConnections[peersAsString] = chn

		go func() {
			for {
				a := time.Now()
				conn, err := DialProxyInternal(peers)
				log.Printf("Established %s in the background in %s", peersAsString, time.Now().Sub(a))
				chn <- connResult{conn, err}
			}
		}()
	}

	for {
		res := <-chn
		// Discard closed connections
		if _, err := res.c.Read(make([]byte, 0, 0)); err != nil {
			log.Printf("Discarding: %s", err)
			continue
		}
		if res.e != nil {
			return nil, res.e
		}
		log.Printf("Fully established %s in %s", peersAsString, time.Now().Sub(a))
		return res.c, nil
	}
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// http://golang.org/src/os/stat_wasm.go

package atime

import (
	"os"
	"syscall"
	"time"
)

func timespecToTime(ts int64) time.Time {
	return time.Unix(int64(ts), 0)
}

func atime(fi os.FileInfo) time.Time {
	return timespecToTime(fi.Sys().(*syscall.Stat_t).Atime)
}
<commit_msg>fix: add nsec in wasm<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// http://golang.org/src/os/stat_wasm.go

package atime

import (
	"os"
	"syscall"
	"time"
)

func timespecToTime(sec, nsec int64) time.Time {
	return time.Unix(sec, nsec)
}

func atime(fi os.FileInfo) time.Time {
	stat := fi.Sys().(*syscall.Stat_t)
	return timespecToTime(stat.Atime, stat.AtimeNsec)
}
<|endoftext|>


<commit_before>package saml

// AttributesMap is a type that provides methods for working with SAML
// attributes.
type AttributesMap map[string][]string

// NewAttributesMap creates an attribute map given a third party assertion.
func NewAttributesMap(assertion *Assertion) *AttributesMap {
	props := make(AttributesMap)
	if assertion == nil {
		return &props
	}

	if assertion.Subject != nil && assertion.Subject.NameID != nil {
		props[assertion.Subject.NameID.Format] = []string{assertion.Subject.NameID.Value}
	}

	if assertion.AttributeStatement != nil {
		for _, attr := range assertion.AttributeStatement.Attributes {
			values := []string{}
			for _, value := range attr.Values {
				values = append(values, value.Value)
			}
			key := attr.Name
			if key == "" {
				key = attr.FriendlyName
			}
			props[key] = values
		}
		return &props
	}

	return &props
}

// Get returns the first value of the given attribute, if any.
func (a *AttributesMap) Get(name string) string {
	if v, ok := (map[string][]string)(*a)[name]; ok {
		return v[0]
	}
	return ""
}
<commit_msg>Fix panic on missing assertion attribute<commit_after>package saml

// AttributesMap is a type that provides methods for working with SAML
// attributes.
type AttributesMap map[string][]string

// NewAttributesMap creates an attribute map given a third party assertion.
func NewAttributesMap(assertion *Assertion) *AttributesMap {
	props := make(AttributesMap)
	if assertion == nil {
		return &props
	}

	if assertion.Subject != nil && assertion.Subject.NameID != nil {
		props[assertion.Subject.NameID.Format] = []string{assertion.Subject.NameID.Value}
	}

	if assertion.AttributeStatement != nil {
		for _, attr := range assertion.AttributeStatement.Attributes {
			values := []string{}
			for _, value := range attr.Values {
				values = append(values, value.Value)
			}
			key := attr.Name
			if key == "" {
				key = attr.FriendlyName
			}
			props[key] = values
		}
		return &props
	}

	return &props
}

// Get returns the first value of the given attribute, if any.
func (a *AttributesMap) Get(name string) string {
	if v, ok := (map[string][]string)(*a)[name]; ok {
		if len(v) > 0 {
			return v[0]
		}
	}
	return ""
}
<|endoftext|>


<commit_before>// Buffer a http stream to a file for reading by the player

package buffer

import (
	"bufio"
	"io"
	"io/ioutil"
	"net/http"
	"os"

	"player/logger"

	"github.com/djherbis/buffer"
)

// HTTP Buffer
type HTTP struct {
	// Exported Fields
	Response *http.Response // HTTP Response Object containing the HTTP Stream
	// Unexported Fields
	file     *os.File        // Buffer temporary file
	buffer   buffer.BufferAt // Internal Buffer
	buffered int             // Amount buffered
}

// Read from the buffer
func (h *HTTP) Read(b []byte) (int, error) {
	// If we try and read before we have a buffer return an error
	// that we don't yet have a buffer
	if h.buffer == nil {
		return 0, io.ErrShortBuffer
	}
	// Wait for buffer to fill
	if h.buffered < 64*1024 {
		return 0, io.ErrShortBuffer
	}
	// If we are at the end of the buffer return EOF
	if h.buffer.Len() == 0 {
		return 0, io.EOF
	}
	// Read from the buffer
	return h.buffer.Read(b)
}

// Closes and removes the temporary buffer file
func (h *HTTP) Close() error {
	if h.file != nil {
		if err := h.file.Close(); err != nil {
			logger.WithError(err).Error("error closing buffer file")
			return err
		}
		if err := os.Remove(h.file.Name()); err != nil {
			logger.WithError(err).Error("error removing buffer file")
			return err
		}
	}
	return nil
}

// Buffer the http Response into a temporary file for reading
func (h *HTTP) Buffer() error {
	f := logger.F{"size": h.Response.ContentLength}
	logger.WithFields(f).Debug("start http buffer")
	defer logger.WithFields(f).Debug("finished buffering")
	defer h.Response.Body.Close() // Close the HTTP Response body once we are done
	// Make the buffer
	if err := h.mkBuffer(); err != nil {
		return err
	}
	var eof bool
	data := make([]byte, 1024*8) // Read response data into here
	writer := bufio.NewWriter(h.buffer)
	for {
		// Read data from response body
		rn, err := h.Response.Body.Read(data)
		if err != nil {
			switch err {
			case io.EOF:
				eof = true
			default:
				logger.WithError(err).Error("http response body read error")
				return err
			}
		}
		// Write body data to buffer
		wn, err := writer.Write(data[:rn])
		if err != nil {
			logger.WithError(err).Error("buffer write error")
			return err
		}
		h.buffered += wn
		if eof {
			return nil
		}
	}
}

// Makes a buffer to store the HTTP stream to a temporary file
func (h *HTTP) mkBuffer() error {
	logger.Debug("make buffer")
	f, err := ioutil.TempFile(os.TempDir(), "sfmplayer.buffer")
	if err != nil {
		return err
	}
	logger.WithField("path", f.Name()).Debug("tmp file created")
	b := buffer.NewFile(h.Response.ContentLength, f)
	h.buffer = b
	h.file = f
	return nil
}

// Construct a new HTTP Buffer for a HTTP Response
func HTTPBuffer(rsp *http.Response) *HTTP {
	return &HTTP{
		Response: rsp,
	}
}
<commit_msg>Log buffer errors<commit_after>// Buffer a http stream to a file for reading by the player

package buffer

import (
	"bufio"
	"io"
	"io/ioutil"
	"net/http"
	"os"

	"player/logger"

	"github.com/djherbis/buffer"
)

// HTTP Buffer
type HTTP struct {
	// Exported Fields
	Response *http.Response // HTTP Response Object containing the HTTP Stream
	// Unexported Fields
	file     *os.File        // Buffer temporary file
	buffer   buffer.BufferAt // Internal Buffer
	buffered int             // Amount buffered
}

// Read from the buffer
func (h *HTTP) Read(b []byte) (int, error) {
	// If we try and read before we have a buffer return an error
	// that we don't yet have a buffer
	if h.buffer == nil {
		return 0, io.ErrShortBuffer
	}
	// Wait for buffer to fill
	if h.buffered < 64*1024 {
		return 0, io.ErrShortBuffer
	}
	// If we are at the end of the buffer return EOF
	if h.buffer.Len() == 0 {
		return 0, io.EOF
	}
	// Read from the buffer
	return h.buffer.Read(b)
}

// Closes and removes the temporary buffer file
func (h *HTTP) Close() error {
	if h.file != nil {
		if err := h.file.Close(); err != nil {
			logger.WithError(err).Error("error closing buffer file")
			return err
		}
		if err := os.Remove(h.file.Name()); err != nil {
			logger.WithError(err).Error("error removing buffer file")
			return err
		}
	}
	return nil
}

// Buffer the http Response into a temporary file for reading
func (h *HTTP) Buffer() error {
	f := logger.F{"size": h.Response.ContentLength}
	logger.WithFields(f).Debug("start http buffer")
	defer logger.WithFields(f).Debug("finished buffering")
	defer h.Response.Body.Close() // Close the HTTP Response body once we are done
	// Make the buffer
	if err := h.mkBuffer(); err != nil {
		return err
	}
	var eof bool
	data := make([]byte, 1024*8) // Read response data into here
	writer := bufio.NewWriter(h.buffer)
	for {
		// Read data from response body
		rn, err := h.Response.Body.Read(data)
		if err != nil {
			switch err {
			case io.EOF:
				logger.WithError(err).Error("http response EOF")
				eof = true
			default:
				logger.WithError(err).Error("http response body read error")
				return err
			}
		}
		// Write body data to buffer
		wn, err := writer.Write(data[:rn])
		if err != nil {
			logger.WithError(err).Error("buffer write error")
		}
		h.buffered += wn
		if eof {
			return nil
		}
	}
}

// Makes a buffer to store the HTTP stream to a temporary file
func (h *HTTP) mkBuffer() error {
	logger.Debug("make buffer")
	f, err := ioutil.TempFile(os.TempDir(), "sfmplayer.buffer")
	if err != nil {
		return err
	}
	logger.WithField("path", f.Name()).Debug("tmp file created")
	b := buffer.NewFile(h.Response.ContentLength, f)
	h.buffer = b
	h.file = f
	return nil
}

// Construct a new HTTP Buffer for a HTTP Response
func HTTPBuffer(rsp *http.Response) *HTTP {
	return &HTTP{
		Response: rsp,
	}
}
<|endoftext|>