<commit_before>package config

import (
	"errors"
	"fmt"
	"os"

	"github.com/pivotalservices/cf-mgmt/cloudcontroller"
	"github.com/pivotalservices/cf-mgmt/ldap"
	"github.com/pivotalservices/cf-mgmt/organization"
	"github.com/pivotalservices/cf-mgmt/space"
	"github.com/pivotalservices/cf-mgmt/utils"
	"github.com/xchapter7x/lo"
)

//Manager -
type Manager interface {
	AddOrgToConfig(orgConfig *OrgConfig) (err error)
	AddSpaceToConfig(spaceConfig *SpaceConfig) (err error)
	CreateConfigIfNotExists(uaaOrigin string) error
	DeleteConfigIfExists() error
}

//DefaultManager -
type DefaultManager struct {
	ConfigDir string
}

//OrgConfig Describes attributes for an org
type OrgConfig struct {
	OrgName                string
	OrgBillingMgrLDAPGrp   string
	OrgMgrLDAPGrp          string
	OrgAuditorLDAPGrp      string
	OrgBillingMgrUAAUsers  []string
	OrgMgrUAAUsers         []string
	OrgAuditorUAAUsers     []string
	OrgBillingMgrLDAPUsers []string
	OrgMgrLDAPUsers        []string
	OrgAuditorLDAPUsers    []string
	OrgQuota               cloudcontroller.QuotaEntity
}

//SpaceConfig Describes attributes for a space
type SpaceConfig struct {
	OrgName               string
	SpaceName             string
	SpaceDevLDAPGrp       string
	SpaceMgrLDAPGrp       string
	SpaceAuditorLDAPGrp   string
	SpaceDevUAAUsers      []string
	SpaceMgrUAAUsers      []string
	SpaceAuditorUAAUsers  []string
	SpaceDevLDAPUsers     []string
	SpaceMgrLDAPUsers     []string
	SpaceAuditorLDAPUsers []string
	SpaceQuota            cloudcontroller.QuotaEntity
	AllowSSH              bool
}

//NewManager -
func NewManager(configDir string) Manager {
	return &DefaultManager{
		ConfigDir: configDir,
	}
}

//AddOrgToConfig -
func (m *DefaultManager) AddOrgToConfig(orgConfig *OrgConfig) (err error) {
	orgList := &organization.InputOrgs{}
	orgFileName := fmt.Sprintf("%s/orgs.yml", m.ConfigDir)
	orgName := orgConfig.OrgName
	if orgName == "" {
		err = errors.New("Cannot have an empty org name")
		return
	}

	orgQuota := orgConfig.OrgQuota
	if err = utils.NewDefaultManager().LoadFile(orgFileName, orgList); err == nil {
		if orgList.Contains(orgName) {
			lo.G.Infof("%s already added to config", orgName)
		} else {
			lo.G.Infof("Adding org: %s ", orgName)
			orgList.Orgs = append(orgList.Orgs, orgName)
			if err = utils.NewDefaultManager().WriteFile(orgFileName, orgList); err == nil {
				if err = os.MkdirAll(fmt.Sprintf("%s/%s", m.ConfigDir, orgName), 0755); err == nil {
					orgConfigYml := &organization.InputUpdateOrgs{
						Org:                     orgName,
						BillingManager:          organization.UserMgmt{LdapGroup: orgConfig.OrgBillingMgrLDAPGrp, Users: orgConfig.OrgBillingMgrUAAUsers, LdapUsers: orgConfig.OrgBillingMgrLDAPUsers},
						Manager:                 organization.UserMgmt{LdapGroup: orgConfig.OrgMgrLDAPGrp, Users: orgConfig.OrgMgrUAAUsers, LdapUsers: orgConfig.OrgMgrLDAPUsers},
						Auditor:                 organization.UserMgmt{LdapGroup: orgConfig.OrgAuditorLDAPGrp, Users: orgConfig.OrgAuditorUAAUsers, LdapUsers: orgConfig.OrgAuditorLDAPUsers},
						EnableOrgQuota:          orgQuota.IsQuotaEnabled(),
						MemoryLimit:             orgQuota.GetMemoryLimit(),
						InstanceMemoryLimit:     orgQuota.GetInstanceMemoryLimit(),
						TotalRoutes:             orgQuota.GetTotalRoutes(),
						TotalServices:           orgQuota.GetTotalServices(),
						PaidServicePlansAllowed: orgQuota.IsPaidServicesAllowed(),
						RemoveUsers:             true,
					}
					utils.NewDefaultManager().WriteFile(fmt.Sprintf("%s/%s/orgConfig.yml", m.ConfigDir, orgName), orgConfigYml)
					spaces := &space.InputCreateSpaces{
						Org: orgName,
					}
					utils.NewDefaultManager().WriteFile(fmt.Sprintf("%s/%s/spaces.yml", m.ConfigDir, orgName), spaces)
				}
			}
		}
	}
	return
}

//AddSpaceToConfig -
func (m *DefaultManager) AddSpaceToConfig(spaceConfig *SpaceConfig) (err error) {
	spaceList := &space.InputCreateSpaces{}
	spaceName := spaceConfig.SpaceName
	orgName := spaceConfig.OrgName
	spaceFileName := fmt.Sprintf("%s/%s/spaces.yml", m.ConfigDir, orgName)
	spaceQuota := spaceConfig.SpaceQuota
	if err = utils.NewDefaultManager().LoadFile(spaceFileName, spaceList); err == nil {
		if spaceList.Contains(spaceName) {
			lo.G.Infof("%s already added to config", spaceName)
		} else {
			lo.G.Infof("Adding space: %s ", spaceName)
			spaceList.Spaces = append(spaceList.Spaces, spaceName)
			if err = utils.NewDefaultManager().WriteFile(spaceFileName, spaceList); err == nil {
				if err = os.MkdirAll(fmt.Sprintf("%s/%s/%s", m.ConfigDir, orgName, spaceName), 0755); err == nil {
					spaceConfigYml := &space.InputUpdateSpaces{
						Org:                     orgName,
						Space:                   spaceName,
						Developer:               space.UserMgmt{LdapGroup: spaceConfig.SpaceDevLDAPGrp, Users: spaceConfig.SpaceDevUAAUsers, LdapUsers: spaceConfig.SpaceDevLDAPUsers},
						Manager:                 space.UserMgmt{LdapGroup: spaceConfig.SpaceMgrLDAPGrp, Users: spaceConfig.SpaceMgrUAAUsers, LdapUsers: spaceConfig.SpaceMgrLDAPUsers},
						Auditor:                 space.UserMgmt{LdapGroup: spaceConfig.SpaceAuditorLDAPGrp, Users: spaceConfig.SpaceAuditorUAAUsers, LdapUsers: spaceConfig.SpaceAuditorLDAPUsers},
						EnableSpaceQuota:        spaceQuota.IsQuotaEnabled(),
						MemoryLimit:             spaceQuota.GetMemoryLimit(),
						InstanceMemoryLimit:     spaceQuota.GetInstanceMemoryLimit(),
						TotalRoutes:             spaceQuota.GetTotalRoutes(),
						TotalServices:           spaceQuota.GetTotalServices(),
						PaidServicePlansAllowed: spaceQuota.IsPaidServicesAllowed(),
						RemoveUsers:             true,
						AllowSSH:                spaceConfig.AllowSSH,
					}
					utils.NewDefaultManager().WriteFile(fmt.Sprintf("%s/%s/%s/spaceConfig.yml", m.ConfigDir, orgName, spaceName), spaceConfigYml)
					utils.NewDefaultManager().WriteFileBytes(fmt.Sprintf("%s/%s/%s/security-group.json", m.ConfigDir, orgName, spaceName), []byte("[]"))
				}
			}
		}
	}
	return
}

//CreateConfigIfNotExists Create org and space config directory. If directory already exists, it is left as is
func (m *DefaultManager) CreateConfigIfNotExists(uaaOrigin string) error {
	var err error
	utilsManager := utils.NewDefaultManager()
	if !utilsManager.FileOrDirectoryExists(m.ConfigDir) {
		if err = os.MkdirAll(m.ConfigDir, 0755); err == nil {
			lo.G.Infof("Config directory %s created", m.ConfigDir)
			utilsManager.WriteFile(fmt.Sprintf("%s/ldap.yml", m.ConfigDir), &ldap.Config{TLS: false, Origin: uaaOrigin})
			utilsManager.WriteFile(fmt.Sprintf("%s/orgs.yml", m.ConfigDir), &organization.InputOrgs{})
			utilsManager.WriteFile(fmt.Sprintf("%s/spaceDefaults.yml", m.ConfigDir), &space.ConfigSpaceDefaults{})
		} else {
			lo.G.Errorf("Error creating config directory %s. Error : %s", m.ConfigDir, err)
		}
	} else {
		lo.G.Infof("Config directory %s already exists, skipping creation", m.ConfigDir)
	}
	return err
}

//DeleteConfigIfExists Deletes config directory if it exists
func (m *DefaultManager) DeleteConfigIfExists() error {
	var err error
	utilsManager := utils.NewDefaultManager()
	if utilsManager.FileOrDirectoryExists(m.ConfigDir) {
		err = os.RemoveAll(m.ConfigDir)
		if err != nil {
			lo.G.Errorf("Error deleting config folder. Error : %s", err)
			return err
		}
		lo.G.Info("Config directory deleted")
	} else {
		lo.G.Infof("%s doesn't exists, nothing to delete", m.ConfigDir)
	}
	return err
}
<commit_msg>config cleanup<commit_after>package config

import (
	"errors"
	"fmt"
	"os"

	"github.com/pivotalservices/cf-mgmt/cloudcontroller"
	"github.com/pivotalservices/cf-mgmt/ldap"
	"github.com/pivotalservices/cf-mgmt/organization"
	"github.com/pivotalservices/cf-mgmt/space"
	"github.com/pivotalservices/cf-mgmt/utils"
	"github.com/xchapter7x/lo"
)

//Manager -
type Manager interface {
	AddOrgToConfig(orgConfig *OrgConfig) (err error)
	AddSpaceToConfig(spaceConfig *SpaceConfig) (err error)
	CreateConfigIfNotExists(uaaOrigin string) error
	DeleteConfigIfExists() error
}

//DefaultManager -
type DefaultManager struct {
	ConfigDir string
}

//OrgConfig Describes attributes for an org
type OrgConfig struct {
	OrgName                string
	OrgBillingMgrLDAPGrp   string
	OrgMgrLDAPGrp          string
	OrgAuditorLDAPGrp      string
	OrgBillingMgrUAAUsers  []string
	OrgMgrUAAUsers         []string
	OrgAuditorUAAUsers     []string
	OrgBillingMgrLDAPUsers []string
	OrgMgrLDAPUsers        []string
	OrgAuditorLDAPUsers    []string
	OrgQuota               cloudcontroller.QuotaEntity
}

//SpaceConfig Describes attributes for a space
type SpaceConfig struct {
	OrgName               string
	SpaceName             string
	SpaceDevLDAPGrp       string
	SpaceMgrLDAPGrp       string
	SpaceAuditorLDAPGrp   string
	SpaceDevUAAUsers      []string
	SpaceMgrUAAUsers      []string
	SpaceAuditorUAAUsers  []string
	SpaceDevLDAPUsers     []string
	SpaceMgrLDAPUsers     []string
	SpaceAuditorLDAPUsers []string
	SpaceQuota            cloudcontroller.QuotaEntity
	AllowSSH              bool
}

//NewManager -
func NewManager(configDir string) Manager {
	return &DefaultManager{
		ConfigDir: configDir,
	}
}

//AddOrgToConfig -
func (m *DefaultManager) AddOrgToConfig(orgConfig *OrgConfig) error {
	orgList := &organization.InputOrgs{}
	orgFileName := fmt.Sprintf("%s/orgs.yml", m.ConfigDir) // TODO filepath.Join
	orgName := orgConfig.OrgName
	if orgName == "" {
		return errors.New("cannot have an empty org name")
	}

	mgr := utils.NewDefaultManager()
	err := mgr.LoadFile(orgFileName, orgList)
	if err != nil {
		return err
	}

	if orgList.Contains(orgName) {
		lo.G.Infof("%s already added to config", orgName)
		return nil
	}
	lo.G.Infof("Adding org: %s ", orgName)
	orgList.Orgs = append(orgList.Orgs, orgName)
	if err = mgr.WriteFile(orgFileName, orgList); err != nil {
		return err
	}

	if err = os.MkdirAll(fmt.Sprintf("%s/%s", m.ConfigDir, orgName), 0755); err != nil {
		return err
	}
	orgConfigYml := &organization.InputUpdateOrgs{
		Org:                     orgName,
		BillingManager:          newUserMgmt(orgConfig.OrgBillingMgrLDAPGrp, orgConfig.OrgBillingMgrUAAUsers, orgConfig.OrgBillingMgrLDAPUsers),
		Manager:                 newUserMgmt(orgConfig.OrgMgrLDAPGrp, orgConfig.OrgMgrUAAUsers, orgConfig.OrgMgrLDAPUsers),
		Auditor:                 newUserMgmt(orgConfig.OrgAuditorLDAPGrp, orgConfig.OrgAuditorUAAUsers, orgConfig.OrgAuditorLDAPUsers),
		EnableOrgQuota:          orgConfig.OrgQuota.IsQuotaEnabled(),
		MemoryLimit:             orgConfig.OrgQuota.GetMemoryLimit(),
		InstanceMemoryLimit:     orgConfig.OrgQuota.GetInstanceMemoryLimit(),
		TotalRoutes:             orgConfig.OrgQuota.GetTotalRoutes(),
		TotalServices:           orgConfig.OrgQuota.GetTotalServices(),
		PaidServicePlansAllowed: orgConfig.OrgQuota.IsPaidServicesAllowed(),
		RemoveUsers:             true,
	}
	mgr.WriteFile(fmt.Sprintf("%s/%s/orgConfig.yml", m.ConfigDir, orgName), orgConfigYml) // TODO: filepath.Join
	spaces := &space.InputCreateSpaces{
		Org: orgName,
	}
	mgr.WriteFile(fmt.Sprintf("%s/%s/spaces.yml", m.ConfigDir, orgName), spaces) // TODO: filepath.Join
	return nil
}

func newUserMgmt(ldapGroup string, users, ldapUsers []string) organization.UserMgmt {
	return organization.UserMgmt{
		LdapGroup: ldapGroup,
		Users:     users,
		LdapUsers: ldapUsers,
	}
}

//AddSpaceToConfig -
func (m *DefaultManager) AddSpaceToConfig(spaceConfig *SpaceConfig) error {
	orgName := spaceConfig.OrgName
	spaceFileName := fmt.Sprintf("%s/%s/spaces.yml", m.ConfigDir, orgName) // TODO: filepath.Join
	spaceList := &space.InputCreateSpaces{}
	spaceName := spaceConfig.SpaceName
	mgr := utils.NewDefaultManager()

	if err := mgr.LoadFile(spaceFileName, spaceList); err != nil {
		return err
	}
	if spaceList.Contains(spaceName) {
		lo.G.Infof("%s already added to config", spaceName)
		return nil
	}
	lo.G.Infof("Adding space: %s ", spaceName)
	spaceList.Spaces = append(spaceList.Spaces, spaceName)
	if err := mgr.WriteFile(spaceFileName, spaceList); err != nil {
		return err
	}
	if err := os.MkdirAll(fmt.Sprintf("%s/%s/%s", m.ConfigDir, orgName, spaceName), 0755); err != nil {
		return err
	}
	spaceConfigYml := &space.InputUpdateSpaces{
		Org:                     orgName,
		Space:                   spaceName,
		Developer:               space.UserMgmt{LdapGroup: spaceConfig.SpaceDevLDAPGrp, Users: spaceConfig.SpaceDevUAAUsers, LdapUsers: spaceConfig.SpaceDevLDAPUsers},
		Manager:                 space.UserMgmt{LdapGroup: spaceConfig.SpaceMgrLDAPGrp, Users: spaceConfig.SpaceMgrUAAUsers, LdapUsers: spaceConfig.SpaceMgrLDAPUsers},
		Auditor:                 space.UserMgmt{LdapGroup: spaceConfig.SpaceAuditorLDAPGrp, Users: spaceConfig.SpaceAuditorUAAUsers, LdapUsers: spaceConfig.SpaceAuditorLDAPUsers},
		EnableSpaceQuota:        spaceConfig.SpaceQuota.IsQuotaEnabled(),
		MemoryLimit:             spaceConfig.SpaceQuota.GetMemoryLimit(),
		InstanceMemoryLimit:     spaceConfig.SpaceQuota.GetInstanceMemoryLimit(),
		TotalRoutes:             spaceConfig.SpaceQuota.GetTotalRoutes(),
		TotalServices:           spaceConfig.SpaceQuota.GetTotalServices(),
		PaidServicePlansAllowed: spaceConfig.SpaceQuota.IsPaidServicesAllowed(),
		RemoveUsers:             true,
		AllowSSH:                spaceConfig.AllowSSH,
	}
	mgr.WriteFile(fmt.Sprintf("%s/%s/%s/spaceConfig.yml", m.ConfigDir, orgName, spaceName), spaceConfigYml)
	mgr.WriteFileBytes(fmt.Sprintf("%s/%s/%s/security-group.json", m.ConfigDir, orgName, spaceName), []byte("[]"))
	return nil
}

//CreateConfigIfNotExists Create org and space config directory. If directory already exists, it is left as is
func (m *DefaultManager) CreateConfigIfNotExists(uaaOrigin string) error {
	mgr := utils.NewDefaultManager()
	if mgr.FileOrDirectoryExists(m.ConfigDir) {
		lo.G.Infof("Config directory %s already exists, skipping creation", m.ConfigDir)
		return nil
	}
	if err := os.MkdirAll(m.ConfigDir, 0755); err != nil {
		lo.G.Errorf("Error creating config directory %s. Error : %s", m.ConfigDir, err)
		return fmt.Errorf("cannot create directory %s: %v", m.ConfigDir, err)
	}
	lo.G.Infof("Config directory %s created", m.ConfigDir)
	mgr.WriteFile(fmt.Sprintf("%s/ldap.yml", m.ConfigDir), &ldap.Config{TLS: false, Origin: uaaOrigin})
	mgr.WriteFile(fmt.Sprintf("%s/orgs.yml", m.ConfigDir), &organization.InputOrgs{})
	mgr.WriteFile(fmt.Sprintf("%s/spaceDefaults.yml", m.ConfigDir), &space.ConfigSpaceDefaults{})
	return nil
}

//DeleteConfigIfExists Deletes config directory if it exists
func (m *DefaultManager) DeleteConfigIfExists() error {
	utilsManager := utils.NewDefaultManager()
	if !utilsManager.FileOrDirectoryExists(m.ConfigDir) {
		lo.G.Infof("%s doesn't exists, nothing to delete", m.ConfigDir)
		return nil
	}
	if err := os.RemoveAll(m.ConfigDir); err != nil {
		lo.G.Errorf("Error deleting config folder. Error: %s", err)
		return fmt.Errorf("cannot delete %s: %v", m.ConfigDir, err)
	}
	lo.G.Info("Config directory deleted")
	return nil
}
<|endoftext|>


<commit_before>package client

import (
	"fmt"
	"io"

	"github.com/subsilent/kappa/server"
)

type ColorCodes struct {
	// Foreground colors
	Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte

	// Light Foreground colors
	LightGrey, LightRed, LightGreen, LightYellow, LightBlue, LightMagenta, LightCyan, LightWhite []byte

	// Reset all attributes
	Reset []byte
}

const keyEscape = 27

// ANSI colors
var DefaultColorCodes = ColorCodes{
	Black:   []byte{keyEscape, '[', '3', '0', 'm'},
	Red:     []byte{keyEscape, '[', '3', '1', 'm'},
	Green:   []byte{keyEscape, '[', '3', '2', 'm'},
	Yellow:  []byte{keyEscape, '[', '3', '3', 'm'},
	Blue:    []byte{keyEscape, '[', '3', '4', 'm'},
	Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
	Cyan:    []byte{keyEscape, '[', '3', '6', 'm'},
	White:   []byte{keyEscape, '[', '3', '7', 'm'},

	LightGrey:    []byte{keyEscape, '[', '9', '0', 'm'},
	LightRed:     []byte{keyEscape, '[', '9', '1', 'm'},
	LightGreen:   []byte{keyEscape, '[', '9', '2', 'm'},
	LightYellow:  []byte{keyEscape, '[', '9', '3', 'm'},
	LightBlue:    []byte{keyEscape, '[', '9', '4', 'm'},
	LightMagenta: []byte{keyEscape, '[', '9', '5', 'm'},
	LightCyan:    []byte{keyEscape, '[', '9', '6', 'm'},
	LightWhite:   []byte{keyEscape, '[', '9', '7', 'm'},
	Reset:        []byte{keyEscape, '[', '0', 'm'},
}

// ResponseWriter writes data and status codes to the client
type ResponseWriter struct {
	Colors      ColorCodes
	Writer      io.Writer
	StatusCodes map[server.StatusCode]string
}

func (r *ResponseWriter) colorCode(color []byte, code server.StatusCode, format string, args ...interface{}) {

	// Set color
	r.Writer.Write(color)

	// Write error name and code
	if t, ok := r.StatusCodes[code]; ok {
		r.Writer.Write([]byte(fmt.Sprintf(" %s (%d)", t, int(code))))
	} else {
		r.Writer.Write([]byte(fmt.Sprintf(" Unknown (%d)", int(code))))
	}

	// Write the error message if there is one
	if len(format) > 0 {
		r.Writer.Write([]byte(": "))
		fmt.Fprintf(r.Writer, format, args...)
	}

	// Reset terminal colors
	r.Writer.Write(r.Colors.Reset)
	r.Writer.Write([]byte("\r\n"))
}

// Fail writes the error status code to the Writer
func (r *ResponseWriter) Fail(code server.StatusCode, format string, args ...interface{}) {
	r.colorCode(r.Colors.LightRed, code, format, args...)
}

// Success writes the status code to the Writer
func (r *ResponseWriter) Success(code server.StatusCode, format string, args ...interface{}) {
	r.colorCode(r.Colors.LightGreen, code, format, args...)
}

// Write is a pass through function into the underlying Writer
func (r *ResponseWriter) Write(data []byte) (int, error) {
	return r.Writer.Write(data)
}
<commit_msg>Fix pkg name and imports<commit_after>package common

import (
	"fmt"
	"io"

	"github.com/subsilent/kappa/server"
)

type ColorCodes struct {
	// Foreground colors
	Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte

	// Light Foreground colors
	LightGrey, LightRed, LightGreen, LightYellow, LightBlue, LightMagenta, LightCyan, LightWhite []byte

	// Reset all attributes
	Reset []byte
}

const keyEscape = 27

// ANSI colors
var DefaultColorCodes = ColorCodes{
	Black:   []byte{keyEscape, '[', '3', '0', 'm'},
	Red:     []byte{keyEscape, '[', '3', '1', 'm'},
	Green:   []byte{keyEscape, '[', '3', '2', 'm'},
	Yellow:  []byte{keyEscape, '[', '3', '3', 'm'},
	Blue:    []byte{keyEscape, '[', '3', '4', 'm'},
	Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
	Cyan:    []byte{keyEscape, '[', '3', '6', 'm'},
	White:   []byte{keyEscape, '[', '3', '7', 'm'},

	LightGrey:    []byte{keyEscape, '[', '9', '0', 'm'},
	LightRed:     []byte{keyEscape, '[', '9', '1', 'm'},
	LightGreen:   []byte{keyEscape, '[', '9', '2', 'm'},
	LightYellow:  []byte{keyEscape, '[', '9', '3', 'm'},
	LightBlue:    []byte{keyEscape, '[', '9', '4', 'm'},
	LightMagenta: []byte{keyEscape, '[', '9', '5', 'm'},
	LightCyan:    []byte{keyEscape, '[', '9', '6', 'm'},
	LightWhite:   []byte{keyEscape, '[', '9', '7', 'm'},
	Reset:        []byte{keyEscape, '[', '0', 'm'},
}

// ResponseWriter writes data and status codes to the client
type ResponseWriter struct {
	Colors      ColorCodes
	Writer      io.Writer
	StatusCodes map[server.StatusCode]string
}

func (r *ResponseWriter) colorCode(color []byte, code StatusCode, format string, args ...interface{}) {

	// Set color
	r.Writer.Write(color)

	// Write error name and code
	if t, ok := r.StatusCodes[code]; ok {
		r.Writer.Write([]byte(fmt.Sprintf(" %s (%d)", t, int(code))))
	} else {
		r.Writer.Write([]byte(fmt.Sprintf(" Unknown (%d)", int(code))))
	}

	// Write the error message if there is one
	if len(format) > 0 {
		r.Writer.Write([]byte(": "))
		fmt.Fprintf(r.Writer, format, args...)
	}

	// Reset terminal colors
	r.Writer.Write(r.Colors.Reset)
	r.Writer.Write([]byte("\r\n"))
}

// Fail writes the error status code to the Writer
func (r *ResponseWriter) Fail(code StatusCode, format string, args ...interface{}) {
	r.colorCode(r.Colors.LightRed, code, format, args...)
}

// Success writes the status code to the Writer
func (r *ResponseWriter) Success(code StatusCode, format string, args ...interface{}) {
	r.colorCode(r.Colors.LightGreen, code, format, args...)
}

// Write is a pass through function into the underlying Writer
func (r *ResponseWriter) Write(data []byte) (int, error) {
	return r.Writer.Write(data)
}
<|endoftext|>


<commit_before>package thuder

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
)

//PullAndPush does push and pulls based on given configurations, it uses Processors
func PullAndPush(hc *HostConfig, mc *MediaConfig, debug io.Writer) error {
	if debug == nil {
		debug = ioutil.Discard
	}

	actions := make(chan action, 8)
	apply := func(p *Processor) {
		go p.Do()
		for {
			a := <-actions
			if len(a.from) == 0 {
				return
			}
			LogP("Appling %v actions to %v.\n", len(a.from), a.to)
			err := applyAction(a)
			if err != nil {
				fmt.Fprintln(debug, err)
			}
		}
	}

	p, err := NewPullingProcessor(mc.Pulls, hc.PullTarget(), actions)
	if err != nil {
		return err
	}
	apply(p)

	/*
		p, err = NewProcessor(mc.Pushes, "/", actions)
		if err != nil {
			return err
		}
		apply(p)
	*/

	syncWriteCache()

	return nil
}

//Processor does the recursive, depth first, processing of directories
type Processor struct {
	stack   []layer
	actions chan<- action // a buffered channal of queued actions to take
}

//joinSub is filePath.Join with additional special charcter handling
func joinSub(parent, sub string) string {
	if filepath.Separator == '\\' && len(sub) > 1 && sub[1] == ':' {
		if len(sub) > 2 {
			sub = sub[0:1] + sub[2:]
		} else {
			sub = sub[0:1]
		}
	}
	return filepath.Join(parent, sub)
}

//LogP is the handler for logging live progress, in the form of fmt.Printf
var LogP = func(format string, a ...interface{}) {
	fmt.Printf(format, a...)
}

//NewPullingProcessor create a new Processor for pulling dirs from host to media.
func NewPullingProcessor(dirs []string, pullTo string, actions chan<- action) (*Processor, error) {
	var stack []layer
	for _, fullname := range dirs {
		rootNode, err := NewRootNode(fullname, false)
		if err != nil {
			return nil, err
		}
		to := joinSub(pullTo, fullname)
		LogP("Pulls dir %v to %v.\n", fullname, to)
		err = fs.MkdirAll(to, 0755)
		if err != nil {
			return nil, err
		}
		stack = append(stack, layer{from: []Node{*rootNode}, to: to})
	}
	p := Processor{
		stack:   stack,
		actions: actions,
	}
	return &p, nil
}

func NewPushingProcessor(hc *HostConfig, actions chan<- action) (*Processor, error) {
	var stack []layer
	sources, isDeletes := hc.PushSources()
	for _, root := range hc.PushRoots() {
		var nodes []Node
		for i := 0; i < len(sources); i++ {
			from := joinSub(sources[i], root)
			node, err := NewRootNode(from, isDeletes[i])
			if err != nil {
				LogP("Pushes skipped from %v because error %v.\n", from, err)
				continue
			}
			nodes = append(nodes, *node)
		}
		stack = append(stack, layer{from: nodes, to: root})
	}
	p := Processor{
		stack:   stack,
		actions: actions,
	}
	return &p, nil
}

//NewProcessor create a new Processor
func newProcessor(dirs []string, to string, actions chan<- action) (*Processor, error) {
	var sources []Node
	for _, fullname := range dirs {
		rootNode, err := NewRootNode(fullname, false)
		if err != nil {
			return nil, err
		}
		sources = append(sources, *rootNode)
	}
	p := Processor{
		stack: []layer{
			layer{from: sources, to: to},
		},
		actions: actions,
	}
	return &p, nil
}

//String returns string debugging
func (p *Processor) String() string {
	b := bytes.NewBufferString("{stack:[")
	for _, l := range p.stack {
		b.WriteString("\n\t" + l.String())
	}
	b.WriteString("]")
	return b.String()
}

//layer is a layer in a Processor's stack
type layer struct {
	from []Node
	to   string
}

//String returns string debugging
func (l layer) String() string {
	b := bytes.NewBufferString("{from:[")
	for _, n := range l.from {
		b.WriteString(n.String() + " ")
	}
	b.WriteString("] to:" + l.to + "}")
	return b.String()
}

///action is an action to be done to the file system
type action struct {
	from []Node
	to   string
}

func applyAction(a action) []error {
	var errs []error
	for i := len(a.from) - 1; i >= 0; i-- {
		n := a.from[i]
		err := applyNode(n, a.to)
		if err != nil {
			errs = append(errs, err)
		}
	}
	return errs
}

func applyNode(n Node, to string) error {
	target := filepath.Join(to, n.info.Name())
	if n.IsDelete() {
		//fmt.Println("remove", n.info.Name())
		return fs.RemoveAll(target)
	}
	if n.IsDir() {
		//fmt.Println("mkdir", n.info.Name())
		return fs.Mkdir(target, n.FileMode())
	}
	//fmt.Println("copy", n.info.Name())
	return atomicCopy(n, to)
}

//Do make the Processor process the stack until done
func (p *Processor) Do() {
	for p.doOnce() {
	}
	p.actions <- action{} //empty action means done
}

// returns true until there is nothing left to do
func (p *Processor) doOnce() bool {
	//fmt.Println(p)
	top := len(p.stack) - 1
	if top < 0 {
		return false
	}
	var l layer
	p.stack, l = p.stack[:top], p.stack[top] //pop from stack

	c := NewCollection()
	for _, node := range l.from {
		err := c.Add(&node)
		if err != nil {
			p.logError(node.FullName(), err)
		}
	}
	deletes, changedfiles, dirs, err := c.GetAppliedTo(l.to)
	if err != nil {
		p.logError(l.to, err)
		// continue even on error
	}
	a := action{to: l.to}
	if len(deletes) > 0 {
		a.from = deletes
		p.actions <- a
	}
	if len(changedfiles) > 0 {
		a.from = changedfiles
		p.actions <- a
	}
	if len(dirs) > 0 {
		for _, d := range dirs {
			last := d[len(d)-1]

			newLayer := layer{
				from: d,
				to:   filepath.Join(l.to, last.info.Name()),
			}
			p.stack = append(p.stack, newLayer)
		}
	}
	return true
}

func (p *Processor) logError(dir string, err error) {
	//todo: change this to a file on removalbe media
	fmt.Fprintln(os.Stderr, dir, err)
}
<commit_msg>add pushing feature in pull and push<commit_after>package thuder

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
)

//PullAndPush does push and pulls based on given configurations, it uses Processors
//
//TODO: push filtering
func PullAndPush(hc *HostConfig, mc *MediaConfig, debug io.Writer) error {
	if debug == nil {
		debug = ioutil.Discard
	}

	actions := make(chan action, 8)
	apply := func(p *Processor) {
		go p.Do()
		for {
			a := <-actions
			if len(a.from) == 0 {
				return
			}
			LogP("Appling %v actions to %v.\n", len(a.from), a.to)
			err := applyAction(a)
			if err != nil {
				fmt.Fprintln(debug, err)
			}
		}
	}

	p, err := NewPullingProcessor(mc.Pulls, hc.PullTarget(), actions)
	if err != nil {
		return err
	}
	apply(p)

	p, err = NewPushingProcessor(hc, actions)
	if err != nil {
		return err
	}
	apply(p)

	syncWriteCache()

	return nil
}

//Processor does the recursive, depth first, processing of directories
type Processor struct {
	stack   []layer
	actions chan<- action // a buffered channal of queued actions to take
}

//joinSub is filePath.Join with additional special charcter handling
func joinSub(parent, sub string) string {
	if filepath.Separator == '\\' && len(sub) > 1 && sub[1] == ':' {
		if len(sub) > 2 {
			sub = sub[0:1] + sub[2:]
		} else {
			sub = sub[0:1]
		}
	}
	return filepath.Join(parent, sub)
}

//LogP is the handler for logging live progress, in the form of fmt.Printf
var LogP = func(format string, a ...interface{}) {
	fmt.Printf(format, a...)
}

//NewPullingProcessor create a new Processor for pulling dirs from host to media.
func NewPullingProcessor(dirs []string, pullTo string, actions chan<- action) (*Processor, error) {
	var stack []layer
	for _, fullname := range dirs {
		rootNode, err := NewRootNode(fullname, false)
		if err != nil {
			return nil, err
		}
		to := joinSub(pullTo, fullname)
		LogP("Pulls dir %v to %v.\n", fullname, to)
		err = fs.MkdirAll(to, 0755)
		if err != nil {
			return nil, err
		}
		stack = append(stack, layer{from: []Node{*rootNode}, to: to})
	}
	p := Processor{
		stack:   stack,
		actions: actions,
	}
	return &p, nil
}

func NewPushingProcessor(hc *HostConfig, actions chan<- action) (*Processor, error) {
	var stack []layer
	sources, isDeletes := hc.PushSources()
	for _, root := range hc.PushRoots() {
		var nodes []Node
		for i := 0; i < len(sources); i++ {
			from := joinSub(sources[i], root)
			node, err := NewRootNode(from, isDeletes[i])
			if err != nil {
				//LogP("Pushes skipped from %v because error %v.\n", from, err)
				continue
			} else {
				LogP("pushes from %v to %v.\n", from, root)
			}
			nodes = append(nodes, *node)
		}
		stack = append(stack, layer{from: nodes, to: root})
	}
	p := Processor{
		stack:   stack,
		actions: actions,
	}
	return &p, nil
}

//NewProcessor create a new Processor
func newProcessor(dirs []string, to string, actions chan<- action) (*Processor, error) {
	var sources []Node
	for _, fullname := range dirs {
		rootNode, err := NewRootNode(fullname, false)
		if err != nil {
			return nil, err
		}
		sources = append(sources, *rootNode)
	}
	p := Processor{
		stack: []layer{
			layer{from: sources, to: to},
		},
		actions: actions,
	}
	return &p, nil
}

//String returns string debugging
func (p *Processor) String() string {
	b := bytes.NewBufferString("{stack:[")
	for _, l := range p.stack {
		b.WriteString("\n\t" + l.String())
	}
	b.WriteString("]")
	return b.String()
}

//layer is a layer in a Processor's stack
type layer struct {
	from []Node
	to   string
}

//String returns string debugging
func (l layer) String() string {
	b := bytes.NewBufferString("{from:[")
	for _, n := range l.from {
		b.WriteString(n.String() + " ")
	}
	b.WriteString("] to:" + l.to + "}")
	return b.String()
}

///action is an action to be done to the file system
type action struct {
	from []Node
	to   string
}

func applyAction(a action) []error {
	var errs []error
	for i := len(a.from) - 1; i >= 0; i-- {
		n := a.from[i]
		err := applyNode(n, a.to)
		if err != nil {
			errs = append(errs, err)
		}
	}
	return errs
}

func applyNode(n Node, to string) error {
	target := filepath.Join(to, n.info.Name())
	if n.IsDelete() {
		//fmt.Println("remove", n.info.Name())
		return fs.RemoveAll(target)
	}
	if n.IsDir() {
		//fmt.Println("mkdir", n.info.Name())
		return fs.Mkdir(target, n.FileMode())
	}
	//fmt.Println("copy", n.info.Name())
	return atomicCopy(n, to)
}

//Do make the Processor process the stack until done
func (p *Processor) Do() {
	for p.doOnce() {
	}
	p.actions <- action{} //empty action means done
}

// returns true until there is nothing left to do
func (p *Processor) doOnce() bool {
	//fmt.Println(p)
	top := len(p.stack) - 1
	if top < 0 {
		return false
	}
	var l layer
	p.stack, l = p.stack[:top], p.stack[top] //pop from stack

	c := NewCollection()
	for _, node := range l.from {
		err := c.Add(&node)
		if err != nil {
			p.logError(node.FullName(), err)
		}
	}
	deletes, changedfiles, dirs, err := c.GetAppliedTo(l.to)
	if err != nil {
		p.logError(l.to, err)
		// continue even on error
	}
	a := action{to: l.to}
	if len(deletes) > 0 {
		a.from = deletes
		p.actions <- a
	}
	if len(changedfiles) > 0 {
		a.from = changedfiles
		p.actions <- a
	}
	if len(dirs) > 0 {
		for _, d := range dirs {
			last := d[len(d)-1]

			newLayer := layer{
				from: d,
				to:   filepath.Join(l.to, last.info.Name()),
			}
			p.stack = append(p.stack, newLayer)
		}
	}
	return true
}

func (p *Processor) logError(dir string, err error) {
	//todo: change this to a file on removalbe media
	fmt.Fprintln(os.Stderr, dir, err)
}
<|endoftext|>


<commit_before>package commons

import (
	"bufio"
	"fmt"
	"io"
	"path/filepath"
	"regexp"
)

var hl7SplitToken = regexp.MustCompile("(\\r(\\n|\\x1c)+(\\n\\r)?|$)")

const scanBufferSize = 10 * 1024 * 1024

// GetHl7Files finds all hl7 files in the current directory and returns the file names as a slice of strings
func GetHl7Files() (matches []string, err error) {
	pattern := "*.hl7"
	if matches, err = filepath.Glob(pattern); err == nil {
		for _, v := range matches {
			fmt.Printf("found %v\n", v)
		}
	}
	return matches, err
}

// CrLfSplit implements a split function to deal with hl7 messages in a file, terminated by cr/lf and an optional second lf
func crLfSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if atEOF && len(data) == 0 { // end of file
	} else {
		loc := hl7SplitToken.FindIndex(data) // found record delimiter
		if loc != nil || atEOF {
			return loc[1], data[0:loc[0]], nil
		}
	}
	return advance, token, err // no cr/lf found, either the end or get bigger data and look again
}

func NewBufScanner(r io.Reader) *bufio.Scanner {
	b := bufio.NewScanner(r)
	buf := make([]byte, scanBufferSize)
	b.Buffer(buf, scanBufferSize)
	b.Split(crLfSplit)
	return b
}
<commit_msg>feat: better counting of found files<commit_after>package commons

import (
	"bufio"
	"fmt"
	"io"
	"path/filepath"
	"regexp"
)

var hl7SplitToken = regexp.MustCompile("(\\r(\\n|\\x1c)+(\\n\\r)?|$)")

const scanBufferSize = 10 * 1024 * 1024

// GetHl7Files finds all hl7 files in the current directory and returns the file names as a slice of strings
func GetHl7Files() (matches []string, err error) {
	pattern := "*.hl7"
	fileCnt := 0
	fmt.Println("")
	if matches, err = filepath.Glob(pattern); err == nil {
		for fileCnt, _ = range matches {
			fileCnt++
			if fileCnt == 1 || fileCnt%1000 == 0 {
				fmt.Printf("\rfound %v", fileCnt)
			}
		}
	}
	if fileCnt != 1 && fileCnt%1000 != 0 {
		fmt.Printf("\rfound %v", fileCnt)
	}
	fmt.Println("")
	return matches, err
}

// CrLfSplit implements a split function to deal with hl7 messages in a file, terminated by cr/lf and an optional second lf
func crLfSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if atEOF && len(data) == 0 { // end of file
	} else {
		loc := hl7SplitToken.FindIndex(data) // found record delimiter
		if loc != nil || atEOF {
			return loc[1], data[0:loc[0]], nil
		}
	}
	return advance, token, err // no cr/lf found, either the end or get bigger data and look again
}

func NewBufScanner(r io.ReadCloser) *bufio.Scanner {
	b := bufio.NewScanner(r)
	buf := make([]byte, scanBufferSize)
	b.Buffer(buf, scanBufferSize)
	b.Split(crLfSplit)
	return b
}
<|endoftext|>


<commit_before>package integration_test

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
	"gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing/transport/ssh"
)

const (
	slackRateLimitDelay  = 200 * time.Millisecond
	slackRequestAttempts = 5
)

var _ = Describe("Claimer", func() {
	var (
		apiToken       string
		channelId      string
		repoUrl        string
		deployKey      string
		botId          string
		userApiToken   string
		username       string
		userId         string
		otherChannelId string
		runCommand     func(string) string
		startClaimer   func(string)
		gitDir         string
	)

	BeforeSuite(func() {
		claimer, err := gexec.Build(filepath.Join("github.com", "mdelillo", "claimer"))
		Expect(err).NotTo(HaveOccurred())

		apiToken = getEnv("CLAIMER_TEST_API_TOKEN")
		channelId = getEnv("CLAIMER_TEST_CHANNEL_ID")
		repoUrl = getEnv("CLAIMER_TEST_REPO_URL")
		deployKey = getEnv("CLAIMER_TEST_DEPLOY_KEY")
		botId = getEnv("CLAIMER_TEST_BOT_ID")
		userApiToken = getEnv("CLAIMER_TEST_USER_API_TOKEN")
		username = getEnv("CLAIMER_TEST_USERNAME")
		userId = getEnv("CLAIMER_TEST_USER_ID")
		otherChannelId = getEnv("CLAIMER_TEST_OTHER_CHANNEL_ID")

		runCommand = func(command string) string {
			message := fmt.Sprintf("<@%s> %s", botId, command)
			postSlackMessage(message, channelId, userApiToken)
			EventuallyWithOffset(1, func() string { return latestSlackMessage(channelId, apiToken) }, "10s").
				ShouldNot(Equal(message), fmt.Sprintf(`Did not get response from command "%s"`, command))
			return latestSlackMessage(channelId, apiToken)
		}
		startClaimer = func(translationFile string) {
			args := []string{
				"-apiToken", apiToken,
				"-channelId", channelId,
				"-repoUrl", repoUrl,
				"-deployKey", deployKey,
			}
			if translationFile != "" {
				args = append(args, "-translationFile", translationFile)
			}
			cmd := exec.Command(claimer, args...)
			session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
			ExpectWithOffset(1, err).NotTo(HaveOccurred())
			Eventually(session, "20s").Should(gbytes.Say("Listening for messages"))
		}
	})

	BeforeEach(func() {
		var err error
		gitDir, err = ioutil.TempDir("", "claimer-integration-tests")
		Expect(err).NotTo(HaveOccurred())

		key, err := ssh.NewPublicKeys("git", []byte(deployKey), "")
		Expect(err).NotTo(HaveOccurred())
		_, err = git.PlainClone(gitDir, false, &git.CloneOptions{
			URL:  repoUrl,
			Auth: key,
		})
		Expect(err).NotTo(HaveOccurred())

		resetClaimerTestPool(gitDir, deployKey)
	})

	AfterEach(func() {
		gexec.KillAndWait()
		resetClaimerTestPool(gitDir, deployKey)
		Expect(os.RemoveAll(gitDir)).To(Succeed())
	})

	AfterSuite(func() {
		gexec.CleanupBuildArtifacts()
	})

	It("claims, releases, and shows status of locks", func() {
		startClaimer("")

		Expect(runCommand("help")).To(ContainSubstring("Available commands:"))

		Expect(runCommand("status")).To(Equal("*Claimed by you:* \n*Claimed by others:* pool-3\n*Unclaimed:* pool-1"))

		Expect(runCommand("claim pool-1")).To(Equal("Claimed pool-1"))
		updateGitRepo(gitDir, deployKey)
		Expect(filepath.Join(gitDir, "pool-1", "claimed", "lock-a")).To(BeAnExistingFile())
		Expect(filepath.Join(gitDir, "pool-1", "unclaimed", "lock-a")).NotTo(BeAnExistingFile())

		status := runCommand("status")
		Expect(status).To(ContainSubstring("*Claimed by you:* pool-1\n"))
		Expect(status).NotTo(MatchRegexp(`\*Unclaimed:\*.*pool-1`))

		Expect(runCommand("claim pool-1")).To(Equal("pool-1 is already claimed"))

		Expect(runCommand("release pool-1")).To(Equal("Released pool-1"))
		updateGitRepo(gitDir, deployKey)
		Expect(filepath.Join(gitDir, "pool-1", "unclaimed", "lock-a")).To(BeAnExistingFile())
		Expect(filepath.Join(gitDir, "pool-1", "claimed", "lock-a")).NotTo(BeAnExistingFile())

		status = runCommand("status")
		Expect(status).To(ContainSubstring("*Claimed by you:* \n"))
		Expect(status).To(MatchRegexp(`\*Unclaimed:\*.*pool-1`))

		Expect(runCommand("release pool-1")).To(Equal("pool-1 is not claimed"))

		Expect(runCommand("claim non-existent-pool")).To(Equal("non-existent-pool does not exist"))

		Expect(runCommand("claim")).To(Equal("must specify pool to claim"))

		Expect(runCommand("release non-existent-pool")).To(Equal("non-existent-pool does not exist"))

		Expect(runCommand("release")).To(Equal("must specify pool to release"))

		Expect(runCommand("unknown-command")).To(Equal("Unknown command. Try `@claimer help` to see usage."))
	})

	It("shows the owner of a lock", func() {
		startClaimer("")

		Expect(runCommand("owner pool-1")).To(Equal("pool-1 is not claimed"))

		claimTime := time.Now()
		Expect(runCommand("claim pool-1")).To(Equal("Claimed pool-1"))

		owner := runCommand("owner pool-1")
		ownerPrefix := fmt.Sprintf("pool-1 was claimed by %s on ", username)
		Expect(owner).To(HavePrefix(ownerPrefix))

		date := strings.TrimPrefix(owner, ownerPrefix)
		parsedDate, err := time.Parse("Mon Jan 2 15:04:05 2006 -0700", date)
		Expect(err).NotTo(HaveOccurred())
		Expect(parsedDate).To(BeTemporally("~", claimTime, 10*time.Second))

		Expect(runCommand("release pool-1")).To(Equal("Released pool-1"))

		Expect(runCommand("claim pool-1 some message")).To(Equal("Claimed pool-1"))

		Expect(runCommand("owner pool-1")).To(HaveSuffix(" (some message)"))

		Expect(runCommand("owner")).To(Equal("must specify pool"))
	})

	It("notifies users who have claimed locks", func() {
		startClaimer("")

		Expect(runCommand("claim pool-1")).To(Equal("Claimed pool-1"))

		notification := runCommand("notify")
		Expect(notification).To(ContainSubstring("Currently claimed locks, please release if not in use:\n"))
		Expect(notification).To(ContainSubstring(fmt.Sprintf("<@%s>: pool-1", userId)))
	})

	It("creates and destroys locks", func() {
		startClaimer("")

		Expect(runCommand("create new-pool")).To(Equal("Created new-pool"))

		updateGitRepo(gitDir, deployKey)
		Expect(filepath.Join(gitDir, "new-pool", "unclaimed", "new-pool")).To(BeAnExistingFile())

		Expect(runCommand("status")).To(MatchRegexp(`\*Unclaimed:\*.*new-pool`))

		Expect(runCommand("create new-pool")).To(Equal("new-pool already exists"))

		Expect(runCommand("destroy new-pool")).To(Equal("Destroyed new-pool"))

		updateGitRepo(gitDir, deployKey)
		Expect(filepath.Join(gitDir, "new-pool")).NotTo(BeADirectory())

		Expect(runCommand("destroy new-pool")).To(Equal("new-pool does not exist"))

		Expect(runCommand("status")).NotTo(MatchRegexp(`\*Unclaimed:\*.*new-pool`))

		Expect(runCommand("create")).To(Equal("must specify name of pool to create"))

		Expect(runCommand("destroy")).To(Equal("must specify pool to destroy"))
	})

	It("does not respond in other channels", func() {
		startClaimer("")

		postSlackMessage(fmt.Sprintf("<@%s> help", botId), otherChannelId, userApiToken)

		Consistently(func() string { return latestSlackMessage(otherChannelId, apiToken) }, "10s").
			Should(Equal(fmt.Sprintf("<@%s> help", botId)))
	})

	Context("when a translation file is provided", func() {
		var translationFilePath string

		BeforeEach(func() {
			translationFile, err := ioutil.TempFile("", "claimer-integration-tests")
			Expect(err).NotTo(HaveOccurred())
			translationFilePath = translationFile.Name()
		})

		AfterEach(func() {
			Expect(os.Remove(translationFilePath)).To(Succeed())
		})

		It("responds with a message from the given translation file", func() {
			translations := "help: {header: foo}"
			Expect(ioutil.WriteFile(translationFilePath, []byte(translations), 0644)).To(Succeed())

			startClaimer(translationFilePath)

			Expect(runCommand("help")).To(HavePrefix("foo"))

			Expect(runCommand("status")).To(ContainSubstring("Claimed by you:"))
		})
	})
})

func getEnv(name string) string {
	value, ok := os.LookupEnv(name)
	if !ok {
		Fail(fmt.Sprintf("%s must be set", name))
	}
	return value
}

func postSlackMessage(text, channelId, apiToken string) {
	_, err := slackPostForm("https://slack.com/api/chat.postMessage", url.Values{
		"token":   {apiToken},
		"channel": {channelId},
		"text":    {text},
		"as_user": {"true"},
	})
	Expect(err).NotTo(HaveOccurred())
}

func latestSlackMessage(channelId, apiToken string) string {
	body, err := slackPostForm("https://slack.com/api/channels.history", url.Values{
		"token":   {apiToken},
		"channel": {channelId},
		"count":   {"1"},
	})
	Expect(err).NotTo(HaveOccurred())
	var slackResponse struct {
		Messages []struct {
			Text string
		}
	}
	Expect(json.Unmarshal(body, &slackResponse)).To(Succeed())

	return slackResponse.Messages[0].Text
}

func slackPostForm(url string, values url.Values) ([]byte, error) {
	delay := slackRateLimitDelay
	for i := 0; i < slackRequestAttempts; i++ {
		time.Sleep(delay)

		body, err := postForm(url, values)
		if err != nil {
			return nil, err
		}

		var slackResponse struct {
			Ok    bool
			Error string
		}
		if err := json.Unmarshal(body, &slackResponse); err != nil {
			return nil, err
		}

		if slackResponse.Ok {
			return body, nil
		} else if slackResponse.Error != "ratelimited" {
			return nil, fmt.Errorf("Slack request failed: %s", slackResponse.Error)
		}

		delay *= 2
	}
	return nil, fmt.Errorf("Slack request failed %d times", slackRequestAttempts)
}

func postForm(url string, values url.Values) ([]byte, error) {
	response, err := http.PostForm(url, values)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	return ioutil.ReadAll(response.Body)
}

func updateGitRepo(gitDir, deployKey string) {
	runGitCommand(gitDir, deployKey, "fetch")
	runGitCommand(gitDir, deployKey, "reset", "--hard", "origin/master")
}

func resetClaimerTestPool(gitDir, deployKey string) {
	runGitCommand(gitDir, deployKey, "checkout", "master")
	runGitCommand(gitDir, deployKey, "reset", "--hard", "initial-state")
	runGitCommand(gitDir, deployKey, "push", "--force", "origin", "master")
}

func runGitCommand(dir, deployKey string, args ...string) {
	deployKeyDir, err := ioutil.TempDir("", "claimer-integration-test-deploy-key")
	Expect(err).NotTo(HaveOccurred())
	defer os.RemoveAll(deployKeyDir)

	deployKeyPath := filepath.Join(deployKeyDir, "key.pem")
	Expect(ioutil.WriteFile(deployKeyPath, []byte(deployKey), 0600)).To(Succeed())

	cmd := exec.Command("git", args...)
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), fmt.Sprintf(`GIT_SSH_COMMAND=/usr/bin/ssh -i %s`, deployKeyPath))
	output, err := cmd.CombinedOutput()
	ExpectWithOffset(1, err).NotTo(HaveOccurred(), fmt.Sprintf("Error running git command: %s", string(output)))
}
<commit_msg>Bump command timeout for slower workstations<commit_after>package integration_test

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
	"gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing/transport/ssh"
)

const (
	slackRateLimitDelay  = 200 * time.Millisecond
	slackRequestAttempts = 5
)

var _ = Describe("Claimer", func() {
	var (
		apiToken       string
		channelId      string
		repoUrl        string
		deployKey      string
		botId          string
		userApiToken   string
		username       string
		userId         string
		otherChannelId string
		runCommand     func(string) string
		startClaimer   func(string)
		gitDir         string
	)

	BeforeSuite(func() {
		claimer, err := gexec.Build(filepath.Join("github.com", "mdelillo", "claimer"))
		Expect(err).NotTo(HaveOccurred())

		apiToken = getEnv("CLAIMER_TEST_API_TOKEN")
		channelId = getEnv("CLAIMER_TEST_CHANNEL_ID")
		repoUrl = getEnv("CLAIMER_TEST_REPO_URL")
		deployKey = getEnv("CLAIMER_TEST_DEPLOY_KEY")
		botId = getEnv("CLAIMER_TEST_BOT_ID")
		userApiToken = getEnv("CLAIMER_TEST_USER_API_TOKEN")
		username = getEnv("CLAIMER_TEST_USERNAME")
		userId = getEnv("CLAIMER_TEST_USER_ID")
		otherChannelId = getEnv("CLAIMER_TEST_OTHER_CHANNEL_ID")

		runCommand = func(command string) string {
			message := fmt.Sprintf("<@%s> %s", botId, command)
			postSlackMessage(message, channelId, userApiToken)
			EventuallyWithOffset(1, func() string { return latestSlackMessage(channelId, apiToken) }, "20s").
				ShouldNot(Equal(message), fmt.Sprintf(`Did not get response from command "%s"`, command))
			return latestSlackMessage(channelId, apiToken)
		}
		startClaimer = func(translationFile string) {
			args := []string{
				"-apiToken", apiToken,
				"-channelId", channelId,
				"-repoUrl", repoUrl,
				"-deployKey", deployKey,
			}
			if translationFile != "" {
				args = append(args, "-translationFile", translationFile)
			}
			cmd := exec.Command(claimer, args...)
			session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
			ExpectWithOffset(1, err).NotTo(HaveOccurred())
			Eventually(session, "20s").Should(gbytes.Say("Listening for messages"))
		}
	})

	BeforeEach(func() {
		var err error
		gitDir, err = ioutil.TempDir("", "claimer-integration-tests")
		Expect(err).NotTo(HaveOccurred())

		key, err := ssh.NewPublicKeys("git", []byte(deployKey), "")
		Expect(err).NotTo(HaveOccurred())
		_, err = git.PlainClone(gitDir, false, &git.CloneOptions{
			URL:  repoUrl,
			Auth: key,
		})
		Expect(err).NotTo(HaveOccurred())

		resetClaimerTestPool(gitDir, deployKey)
	})

	AfterEach(func() {
		gexec.KillAndWait()
		resetClaimerTestPool(gitDir, deployKey)
		Expect(os.RemoveAll(gitDir)).To(Succeed())
	})

	AfterSuite(func() {
		gexec.CleanupBuildArtifacts()
	})

	It("claims, releases, and shows status of locks", func() {
		startClaimer("")

		Expect(runCommand("help")).To(ContainSubstring("Available commands:"))

		Expect(runCommand("status")).To(Equal("*Claimed by you:* \n*Claimed by others:* pool-3\n*Unclaimed:* pool-1"))

		Expect(runCommand("claim pool-1")).To(Equal("Claimed pool-1"))
		updateGitRepo(gitDir, deployKey)
		Expect(filepath.Join(gitDir, "pool-1", "claimed", "lock-a")).To(BeAnExistingFile())
		Expect(filepath.Join(gitDir, "pool-1", "unclaimed", "lock-a")).NotTo(BeAnExistingFile())

		status := runCommand("status")
		Expect(status).To(ContainSubstring("*Claimed by you:* pool-1\n"))
		Expect(status).NotTo(MatchRegexp(`\*Unclaimed:\*.*pool-1`))

		Expect(runCommand("claim pool-1")).To(Equal("pool-1 is already claimed"))

		Expect(runCommand("release pool-1")).To(Equal("Released pool-1"))
		updateGitRepo(gitDir, deployKey)
		Expect(filepath.Join(gitDir, "pool-1", "unclaimed", "lock-a")).To(BeAnExistingFile())
		Expect(filepath.Join(gitDir, "pool-1", "claimed", "lock-a")).NotTo(BeAnExistingFile())

		status = runCommand("status")
		Expect(status).To(ContainSubstring("*Claimed by you:* \n"))
		Expect(status).To(MatchRegexp(`\*Unclaimed:\*.*pool-1`))

		Expect(runCommand("release pool-1")).To(Equal("pool-1 is not claimed"))

		Expect(runCommand("claim non-existent-pool")).To(Equal("non-existent-pool does not exist"))

		Expect(runCommand("claim")).To(Equal("must specify pool to claim"))

		Expect(runCommand("release non-existent-pool")).To(Equal("non-existent-pool does not exist"))

		Expect(runCommand("release")).To(Equal("must specify pool to release"))

		Expect(runCommand("unknown-command")).To(Equal("Unknown command. Try `@claimer help` to see usage."))
	})

	It("shows the owner of a lock", func() {
		startClaimer("")

		Expect(runCommand("owner pool-1")).To(Equal("pool-1 is not claimed"))

		claimTime := time.Now()
		Expect(runCommand("claim pool-1")).To(Equal("Claimed pool-1"))

		owner := runCommand("owner pool-1")
		ownerPrefix := fmt.Sprintf("pool-1 was claimed by %s on ", username)
		Expect(owner).To(HavePrefix(ownerPrefix))

		date := strings.TrimPrefix(owner, ownerPrefix)
		parsedDate, err := time.Parse("Mon Jan 2 15:04:05 2006 -0700", date)
		Expect(err).NotTo(HaveOccurred())
		Expect(parsedDate).To(BeTemporally("~", claimTime, 10*time.Second))

		Expect(runCommand("release pool-1")).To(Equal("Released pool-1"))

		Expect(runCommand("claim pool-1 some message")).To(Equal("Claimed pool-1"))

		Expect(runCommand("owner pool-1")).To(HaveSuffix(" (some message)"))

		Expect(runCommand("owner")).To(Equal("must specify pool"))
	})

	It("notifies users who have claimed locks", func() {
		startClaimer("")

		Expect(runCommand("claim pool-1")).To(Equal("Claimed pool-1"))

		notification := runCommand("notify")
		Expect(notification).To(ContainSubstring("Currently claimed locks, please release if not in use:\n"))
		Expect(notification).To(ContainSubstring(fmt.Sprintf("<@%s>: pool-1", userId)))
	})

	It("creates and destroys locks", func() {
		startClaimer("")

		Expect(runCommand("create new-pool")).To(Equal("Created new-pool"))

		updateGitRepo(gitDir, deployKey)
		Expect(filepath.Join(gitDir, "new-pool", "unclaimed", "new-pool")).To(BeAnExistingFile())

		Expect(runCommand("status")).To(MatchRegexp(`\*Unclaimed:\*.*new-pool`))

		Expect(runCommand("create new-pool")).To(Equal("new-pool already exists"))

		Expect(runCommand("destroy new-pool")).To(Equal("Destroyed new-pool"))

		updateGitRepo(gitDir, deployKey)
		Expect(filepath.Join(gitDir, "new-pool")).NotTo(BeADirectory())

		Expect(runCommand("destroy new-pool")).To(Equal("new-pool does not exist"))

		Expect(runCommand("status")).NotTo(MatchRegexp(`\*Unclaimed:\*.*new-pool`))

		Expect(runCommand("create")).To(Equal("must specify name of pool to create"))

		Expect(runCommand("destroy")).To(Equal("must specify pool to destroy"))
	})

	It("does not respond in other channels", func() {
		startClaimer("")

		postSlackMessage(fmt.Sprintf("<@%s> help", botId), otherChannelId, userApiToken)

		Consistently(func() string { return latestSlackMessage(otherChannelId, apiToken) }, "10s").
			Should(Equal(fmt.Sprintf("<@%s> help", botId)))
	})

	Context("when a translation file is provided", func() {
		var translationFilePath string

		BeforeEach(func() {
			translationFile, err := ioutil.TempFile("", "claimer-integration-tests")
			Expect(err).NotTo(HaveOccurred())
			translationFilePath = translationFile.Name()
		})

		AfterEach(func() {
			Expect(os.Remove(translationFilePath)).To(Succeed())
		})

		It("responds with a message from the given translation file", func() {
			translations := `help: {header: "foo\n"}`
			Expect(ioutil.WriteFile(translationFilePath, []byte(translations), 0644)).To(Succeed())

			startClaimer(translationFilePath)

			Expect(runCommand("help")).To(HavePrefix("foo"))

			Expect(runCommand("status")).To(ContainSubstring("Claimed by you:"))
		})
	})
})

func getEnv(name string) string {
	value, ok := os.LookupEnv(name)
	if !ok {
		Fail(fmt.Sprintf("%s must be set", name))
	}
	return value
}

func postSlackMessage(text, channelId, apiToken string) {
	_, err := slackPostForm("https://slack.com/api/chat.postMessage", url.Values{
		"token":   {apiToken},
		"channel": {channelId},
		"text":    {text},
		"as_user": {"true"},
	})
	Expect(err).NotTo(HaveOccurred())
}

func latestSlackMessage(channelId, apiToken string) string {
	body, err := slackPostForm("https://slack.com/api/channels.history", url.Values{
		"token":   {apiToken},
		"channel": {channelId},
		"count":   {"1"},
	})
	Expect(err).NotTo(HaveOccurred())
	var slackResponse struct {
		Messages []struct {
			Text string
		}
	}
	Expect(json.Unmarshal(body, &slackResponse)).To(Succeed())

	return slackResponse.Messages[0].Text
}

func slackPostForm(url string, values url.Values) ([]byte, error) {
	delay := slackRateLimitDelay
	for i := 0; i < slackRequestAttempts; i++ {
		time.Sleep(delay)

		body, err := postForm(url, values)
		if err != nil {
			return nil, err
		}

		var slackResponse struct {
			Ok    bool
			Error string
		}
		if err := json.Unmarshal(body, &slackResponse); err != nil {
			return nil, err
		}

		if slackResponse.Ok {
			return body, nil
		} else if slackResponse.Error != "ratelimited" {
			return nil, fmt.Errorf("Slack request failed: %s", slackResponse.Error)
		}

		delay *= 2
	}
	return nil, fmt.Errorf("Slack request failed %d times", slackRequestAttempts)
}

func postForm(url string, values url.Values) ([]byte, error) {
	response, err := http.PostForm(url, values)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	return ioutil.ReadAll(response.Body)
}

func updateGitRepo(gitDir, deployKey string) {
	runGitCommand(gitDir, deployKey, "fetch")
	runGitCommand(gitDir, deployKey, "reset", "--hard", "origin/master")
}

func resetClaimerTestPool(gitDir, deployKey string) {
	runGitCommand(gitDir, deployKey, "checkout", "master")
	runGitCommand(gitDir, deployKey, "reset", "--hard", "initial-state")
	runGitCommand(gitDir, deployKey, "push", "--force", "origin", "master")
}

func runGitCommand(dir, deployKey string, args ...string) {
	deployKeyDir, err := ioutil.TempDir("", "claimer-integration-test-deploy-key")
	Expect(err).NotTo(HaveOccurred())
	defer os.RemoveAll(deployKeyDir)

	deployKeyPath := filepath.Join(deployKeyDir, "key.pem")
	Expect(ioutil.WriteFile(deployKeyPath, []byte(deployKey), 0600)).To(Succeed())

	cmd := exec.Command("git", args...)
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), fmt.Sprintf(`GIT_SSH_COMMAND=/usr/bin/ssh -i %s`, deployKeyPath))
	output, err := cmd.CombinedOutput()
	ExpectWithOffset(1, err).NotTo(HaveOccurred(), fmt.Sprintf("Error running git command: %s", string(output)))
}
<|endoftext|>


<commit_before>//go:build e2e
// +build e2e

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"
	"testing"

	tfe "github.com/hashicorp/go-tfe"
)

var terraformVersion string
var terraformBin string
var cliConfigFileEnv string

var tfeClient *tfe.Client
var tfeHostname string
var tfeToken string

func TestMain(m *testing.M) {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	if !accTest() {
		// if TF_ACC is not set, we want to skip all these tests.
		return
	}
	teardown := setup()
	code := m.Run()
	teardown()

	os.Exit(code)
}

func accTest() bool {
	// TF_ACC is set when we want to run acceptance tests, meaning it relies on
	// network access.
	return os.Getenv("TF_ACC") != ""
}

func setup() func() {
	setTfeClient()
	teardown := setupBinary()
	setVersion()
	ensureVersionExists()

	return func() {
		teardown()
	}
}

func setTfeClient() {
	hostname := os.Getenv("TFE_HOSTNAME")
	token := os.Getenv("TFE_TOKEN")
	if hostname == "" {
		log.Fatalf("hostname cannot be empty")
	}
	if token == "" {
		log.Fatalf("token cannot be empty")
	}
	tfeHostname = hostname
	tfeToken = token

	cfg := &tfe.Config{
		Address: fmt.Sprintf("https://%s", hostname),
		Token:   token,
	}

	// Create a new TFE client.
	client, err := tfe.NewClient(cfg)
	if err != nil {
		log.Fatal(err)
	}
	tfeClient = client
}

func setupBinary() func() {
	log.Println("Setting up terraform binary")
	tmpTerraformBinaryDir, err := ioutil.TempDir("", "terraform-test")
	if err != nil {
		log.Fatal(err)
	}
	log.Println(tmpTerraformBinaryDir)
	currentDir, err := os.Getwd()
	defer os.Chdir(currentDir)
	if err != nil {
		log.Fatal(err)
	}
	// Getting top level dir
	dirPaths := strings.Split(currentDir, "/")
	log.Println(currentDir)
	topLevel := len(dirPaths) - 3
	topDir := strings.Join(dirPaths[0:topLevel], "/")

	if err := os.Chdir(topDir); err != nil {
		log.Fatal(err)
	}

	cmd := exec.Command("go", "build", "-o", tmpTerraformBinaryDir)
	err = cmd.Run()
	if err != nil {
		log.Fatal(err)
	}

	credFile := fmt.Sprintf("%s/dev.tfrc", tmpTerraformBinaryDir)
	writeCredRC(credFile)

	terraformBin = fmt.Sprintf("%s/terraform", tmpTerraformBinaryDir)
	cliConfigFileEnv = fmt.Sprintf("TF_CLI_CONFIG_FILE=%s", credFile)

	return func() {
		os.RemoveAll(tmpTerraformBinaryDir)
	}
}

func setVersion() {
	log.Println("Retrieving version")
	cmd := exec.Command(terraformBin, "version", "-json")
	out, err := cmd.Output()
	if err != nil {
		log.Fatal(fmt.Sprintf("Could not output terraform version: %v", err))
	}
	var data map[string]interface{}
	if err := json.Unmarshal(out, &data); err != nil {
		log.Fatal(fmt.Sprintf("Could not unmarshal version output: %v", err))
	}

	out, err = exec.Command("git", "rev-parse", "HEAD").Output()
	if err != nil {
		log.Fatal(fmt.Sprintf("Could not execute go build command: %v", err))
	}

	hash := string(out)[0:8]

	terraformVersion = fmt.Sprintf("%s-%s", data["terraform_version"].(string), hash)
}

func ensureVersionExists() {
	opts := tfe.AdminTerraformVersionsListOptions{
		ListOptions: tfe.ListOptions{
			PageNumber: 1,
			PageSize:   100,
		},
	}
	hasVersion := false

findTfVersion:
	for {
		tfVersionList, err := tfeClient.Admin.TerraformVersions.List(context.Background(), opts)
		if err != nil {
			log.Fatalf("Could not retrieve list of terraform versions: %v", err)
		}
		for _, item := range tfVersionList.Items {
			if item.Version == terraformVersion {
				hasVersion = true
				break findTfVersion
			}
		}

		// Exit the loop when we've seen all pages.
		if tfVersionList.CurrentPage >= tfVersionList.TotalPages {
			break
		}

		// Update the page number to get the next page.
		opts.PageNumber = tfVersionList.NextPage
	}

	if !hasVersion {
		log.Fatalf("Terraform Version %s does not exist in the list. Please add it.", terraformVersion)
	}
}

func writeCredRC(file string) {
	creds := credentialBlock()
	f, err := os.Create(file)
	if err != nil {
		log.Fatal(err)
	}
	_, err = f.WriteString(creds)
	if err != nil {
		log.Fatal(err)
	}
	f.Close()
}

func credentialBlock() string {
	return fmt.Sprintf(`
credentials "%s" {
  token = "%s"
}`, tfeHostname, tfeToken)
}
<commit_msg>Fix terraform version in cloud e2e tests to ignore prerelease. (#29)<commit_after>//go:build e2e
// +build e2e

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"
	"testing"

	tfe "github.com/hashicorp/go-tfe"
)

var terraformVersion string
var terraformBin string
var cliConfigFileEnv string

var tfeClient *tfe.Client
var tfeHostname string
var tfeToken string

func TestMain(m *testing.M) {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	if !accTest() {
		// if TF_ACC is not set, we want to skip all these tests.
		return
	}
	teardown := setup()
	code := m.Run()
	teardown()

	os.Exit(code)
}

func accTest() bool {
	// TF_ACC is set when we want to run acceptance tests, meaning it relies on
	// network access.
	return os.Getenv("TF_ACC") != ""
}

func setup() func() {
	setTfeClient()
	teardown := setupBinary()
	setVersion()
	ensureVersionExists()

	return func() {
		teardown()
	}
}

func setTfeClient() {
	hostname := os.Getenv("TFE_HOSTNAME")
	token := os.Getenv("TFE_TOKEN")
	if hostname == "" {
		log.Fatal("hostname cannot be empty")
	}
	if token == "" {
		log.Fatal("token cannot be empty")
	}
	tfeHostname = hostname
	tfeToken = token

	cfg := &tfe.Config{
		Address: fmt.Sprintf("https://%s", hostname),
		Token:   token,
	}

	// Create a new TFE client.
	client, err := tfe.NewClient(cfg)
	if err != nil {
		log.Fatal(err)
	}
	tfeClient = client
}

func setupBinary() func() {
	log.Println("Setting up terraform binary")
	tmpTerraformBinaryDir, err := ioutil.TempDir("", "terraform-test")
	if err != nil {
		log.Fatal(err)
	}
	log.Println(tmpTerraformBinaryDir)
	currentDir, err := os.Getwd()
	defer os.Chdir(currentDir)
	if err != nil {
		log.Fatal(err)
	}
	// Getting top level dir
	dirPaths := strings.Split(currentDir, "/")
	log.Println(currentDir)
	topLevel := len(dirPaths) - 3
	topDir := strings.Join(dirPaths[0:topLevel], "/")

	if err := os.Chdir(topDir); err != nil {
		log.Fatal(err)
	}

	cmd := exec.Command("go", "build", "-o", tmpTerraformBinaryDir)
	err = cmd.Run()
	if err != nil {
		log.Fatal(err)
	}

	credFile := fmt.Sprintf("%s/dev.tfrc", tmpTerraformBinaryDir)
	writeCredRC(credFile)

	terraformBin = fmt.Sprintf("%s/terraform", tmpTerraformBinaryDir)
	cliConfigFileEnv = fmt.Sprintf("TF_CLI_CONFIG_FILE=%s", credFile)

	return func() {
		os.RemoveAll(tmpTerraformBinaryDir)
	}
}

func setVersion() {
	log.Println("Retrieving version")
	cmd := exec.Command(terraformBin, "version", "-json")
	out, err := cmd.Output()
	if err != nil {
		log.Fatal(fmt.Sprintf("Could not output terraform version: %v", err))
	}
	var data map[string]interface{}
	if err := json.Unmarshal(out, &data); err != nil {
		log.Fatal(fmt.Sprintf("Could not unmarshal version output: %v", err))
	}

	out, err = exec.Command("git", "rev-parse", "HEAD").Output()
	if err != nil {
		log.Fatal(fmt.Sprintf("Could not execute go build command: %v", err))
	}

	hash := string(out)[0:8]

	fullVersion := data["terraform_version"].(string)
	version := strings.Split(fullVersion, "-")[0]
	terraformVersion = fmt.Sprintf("%s-%s", version, hash)
}

func ensureVersionExists() {
	opts := tfe.AdminTerraformVersionsListOptions{
		ListOptions: tfe.ListOptions{
			PageNumber: 1,
			PageSize:   100,
		},
	}
	hasVersion := false

findTfVersion:
	for {
		tfVersionList, err := tfeClient.Admin.TerraformVersions.List(context.Background(), opts)
		if err != nil {
			log.Fatalf("Could not retrieve list of terraform versions: %v", err)
		}
		for _, item := range tfVersionList.Items {
			if item.Version == terraformVersion {
				hasVersion = true
				break findTfVersion
			}
		}

		// Exit the loop when we've seen all pages.
		if tfVersionList.CurrentPage >= tfVersionList.TotalPages {
			break
		}

		// Update the page number to get the next page.
		opts.PageNumber = tfVersionList.NextPage
	}

	if !hasVersion {
		log.Fatalf("Terraform Version %s does not exist in the list. Please add it.", terraformVersion)
	}
}

func writeCredRC(file string) {
	creds := credentialBlock()
	f, err := os.Create(file)
	if err != nil {
		log.Fatal(err)
	}
	_, err = f.WriteString(creds)
	if err != nil {
		log.Fatal(err)
	}
	f.Close()
}

func credentialBlock() string {
	return fmt.Sprintf(`
credentials "%s" {
  token = "%s"
}`, tfeHostname, tfeToken)
}
<|endoftext|>


<commit_before>// +build linux

package common

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

func DoSysctrl(mib string) ([]string, error) {
	sysctl, err := exec.LookPath("sysctl")
	if err != nil {
		return []string{}, err
	}
	cmd := exec.Command(sysctl, "-n", mib)
	cmd.Env = getSysctrlEnv(os.Environ())
	out, err := cmd.Output()
	if err != nil {
		return []string{}, err
	}
	v := strings.Replace(string(out), "{ ", "", 1)
	v = strings.Replace(string(v), " }", "", 1)
	values := strings.Fields(string(v))

	return values, nil
}

func NumProcs() (uint64, error) {
	f, err := os.Open(HostProc())
	if err != nil {
		return 0, err
	}
	defer f.Close()

	list, err := f.Readdirnames(-1)
	if err != nil {
		return 0, err
	}
	return uint64(len(list)), err
}

// cachedBootTime must be accessed via atomic.Load/StoreUint64
var cachedBootTime uint64

func BootTimeWithContext(ctx context.Context) (uint64, error) {
	t := atomic.LoadUint64(&cachedBootTime)
	if t != 0 {
		return t, nil
	}

	system, role, err := Virtualization()
	if err != nil {
		return 0, err
	}

	statFile := "stat"
	if system == "lxc" && role == "guest" {
		// if lxc, /proc/uptime is used.
		statFile = "uptime"
	} else if system == "docker" && role == "guest" {
		// also docker, guest
		statFile = "uptime"
	}

	filename := HostProc(statFile)
	lines, err := ReadLines(filename)
	if err != nil {
		return 0, err
	}

	if statFile == "stat" {
		for _, line := range lines {
			if strings.HasPrefix(line, "btime") {
				f := strings.Fields(line)
				if len(f) != 2 {
					return 0, fmt.Errorf("wrong btime format")
				}
				b, err := strconv.ParseInt(f[1], 10, 64)
				if err != nil {
					return 0, err
				}
				t = uint64(b)
				atomic.StoreUint64(&cachedBootTime, t)
				return t, nil
			}
		}
	} else if statFile == "uptime" {
		if len(lines) != 1 {
			return 0, fmt.Errorf("wrong uptime format")
		}
		f := strings.Fields(lines[0])
		b, err := strconv.ParseFloat(f[0], 64)
		if err != nil {
			return 0, err
		}
		t = uint64(time.Now().Unix()) - uint64(b)
		atomic.StoreUint64(&cachedBootTime, t)
		return t, nil
	}

	return 0, fmt.Errorf("could not find btime")
}

func Virtualization() (string, string, error) {
	return VirtualizationWithContext(context.Background())
}

func VirtualizationWithContext(ctx context.Context) (string, string, error) {
	var system string
	var role string

	filename := HostProc("xen")
	if PathExists(filename) {
		system = "xen"
		role = "guest" // assume guest

		if PathExists(filepath.Join(filename, "capabilities")) {
			contents, err := ReadLines(filepath.Join(filename, "capabilities"))
			if err == nil {
				if StringsContains(contents, "control_d") {
					role = "host"
				}
			}
		}
	}

	filename = HostProc("modules")
	if PathExists(filename) {
		contents, err := ReadLines(filename)
		if err == nil {
			if StringsContains(contents, "kvm") {
				system = "kvm"
				role = "host"
			} else if StringsContains(contents, "vboxdrv") {
				system = "vbox"
				role = "host"
			} else if StringsContains(contents, "vboxguest") {
				system = "vbox"
				role = "guest"
			} else if StringsContains(contents, "vmware") {
				system = "vmware"
				role = "guest"
			}
		}
	}

	filename = HostProc("cpuinfo")
	if PathExists(filename) {
		contents, err := ReadLines(filename)
		if err == nil {
			if StringsContains(contents, "QEMU Virtual CPU") ||
				StringsContains(contents, "Common KVM processor") ||
				StringsContains(contents, "Common 32-bit KVM processor") {
				system = "kvm"
				role = "guest"
			}
		}
	}

	filename = HostProc("bus/pci/devices")
	if PathExists(filename) {
		contents, err := ReadLines(filename)
		if err == nil {
			if StringsContains(contents, "virtio-pci") {
				role = "guest"
			}
		}
	}

	filename = HostProc()
	if PathExists(filepath.Join(filename, "bc", "0")) {
		system = "openvz"
		role = "host"
	} else if PathExists(filepath.Join(filename, "vz")) {
		system = "openvz"
		role = "guest"
	}

	// not use dmidecode because it requires root
	if PathExists(filepath.Join(filename, "self", "status")) {
		contents, err := ReadLines(filepath.Join(filename, "self", "status"))
		if err == nil {

			if StringsContains(contents, "s_context:") ||
				StringsContains(contents, "VxID:") {
				system = "linux-vserver"
			}
			// TODO: guest or host
		}
	}

	if PathExists(filepath.Join(filename, "self", "cgroup")) {
		contents, err := ReadLines(filepath.Join(filename, "self", "cgroup"))
		if err == nil {
			if StringsContains(contents, "lxc") {
				system = "lxc"
				role = "guest"
			} else if StringsContains(contents, "docker") {
				system = "docker"
				role = "guest"
			} else if StringsContains(contents, "machine-rkt") {
				system = "rkt"
				role = "guest"
			} else if PathExists("/usr/bin/lxc-version") {
				system = "lxc"
				role = "host"
			}
		}
	}

	if PathExists(HostEtc("os-release")) {
		p, _, err := GetOSRelease()
		if err == nil && p == "coreos" {
			system = "rkt" // Is it true?
			role = "host"
		}
	}
	return system, role, nil
}

func GetOSRelease() (platform string, version string, err error) {
	contents, err := ReadLines(HostEtc("os-release"))
	if err != nil {
		return "", "", nil // return empty
	}
	for _, line := range contents {
		field := strings.Split(line, "=")
		if len(field) < 2 {
			continue
		}
		switch field[0] {
		case "ID": // use ID for lowercase
			platform = field[1]
		case "VERSION":
			version = field[1]
		}
	}
	return platform, version, nil
}
<commit_msg>trim quotes when reading from os-release<commit_after>// +build linux

package common

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

func DoSysctrl(mib string) ([]string, error) {
	sysctl, err := exec.LookPath("sysctl")
	if err != nil {
		return []string{}, err
	}
	cmd := exec.Command(sysctl, "-n", mib)
	cmd.Env = getSysctrlEnv(os.Environ())
	out, err := cmd.Output()
	if err != nil {
		return []string{}, err
	}
	v := strings.Replace(string(out), "{ ", "", 1)
	v = strings.Replace(string(v), " }", "", 1)
	values := strings.Fields(string(v))

	return values, nil
}

func NumProcs() (uint64, error) {
	f, err := os.Open(HostProc())
	if err != nil {
		return 0, err
	}
	defer f.Close()

	list, err := f.Readdirnames(-1)
	if err != nil {
		return 0, err
	}
	return uint64(len(list)), err
}

// cachedBootTime must be accessed via atomic.Load/StoreUint64
var cachedBootTime uint64

func BootTimeWithContext(ctx context.Context) (uint64, error) {
	t := atomic.LoadUint64(&cachedBootTime)
	if t != 0 {
		return t, nil
	}

	system, role, err := Virtualization()
	if err != nil {
		return 0, err
	}

	statFile := "stat"
	if system == "lxc" && role == "guest" {
		// if lxc, /proc/uptime is used.
		statFile = "uptime"
	} else if system == "docker" && role == "guest" {
		// also docker, guest
		statFile = "uptime"
	}

	filename := HostProc(statFile)
	lines, err := ReadLines(filename)
	if err != nil {
		return 0, err
	}

	if statFile == "stat" {
		for _, line := range lines {
			if strings.HasPrefix(line, "btime") {
				f := strings.Fields(line)
				if len(f) != 2 {
					return 0, fmt.Errorf("wrong btime format")
				}
				b, err := strconv.ParseInt(f[1], 10, 64)
				if err != nil {
					return 0, err
				}
				t = uint64(b)
				atomic.StoreUint64(&cachedBootTime, t)
				return t, nil
			}
		}
	} else if statFile == "uptime" {
		if len(lines) != 1 {
			return 0, fmt.Errorf("wrong uptime format")
		}
		f := strings.Fields(lines[0])
		b, err := strconv.ParseFloat(f[0], 64)
		if err != nil {
			return 0, err
		}
		t = uint64(time.Now().Unix()) - uint64(b)
		atomic.StoreUint64(&cachedBootTime, t)
		return t, nil
	}

	return 0, fmt.Errorf("could not find btime")
}

func Virtualization() (string, string, error) {
	return VirtualizationWithContext(context.Background())
}

func VirtualizationWithContext(ctx context.Context) (string, string, error) {
	var system string
	var role string

	filename := HostProc("xen")
	if PathExists(filename) {
		system = "xen"
		role = "guest" // assume guest

		if PathExists(filepath.Join(filename, "capabilities")) {
			contents, err := ReadLines(filepath.Join(filename, "capabilities"))
			if err == nil {
				if StringsContains(contents, "control_d") {
					role = "host"
				}
			}
		}
	}

	filename = HostProc("modules")
	if PathExists(filename) {
		contents, err := ReadLines(filename)
		if err == nil {
			if StringsContains(contents, "kvm") {
				system = "kvm"
				role = "host"
			} else if StringsContains(contents, "vboxdrv") {
				system = "vbox"
				role = "host"
			} else if StringsContains(contents, "vboxguest") {
				system = "vbox"
				role = "guest"
			} else if StringsContains(contents, "vmware") {
				system = "vmware"
				role = "guest"
			}
		}
	}

	filename = HostProc("cpuinfo")
	if PathExists(filename) {
		contents, err := ReadLines(filename)
		if err == nil {
			if StringsContains(contents, "QEMU Virtual CPU") ||
				StringsContains(contents, "Common KVM processor") ||
				StringsContains(contents, "Common 32-bit KVM processor") {
				system = "kvm"
				role = "guest"
			}
		}
	}

	filename = HostProc("bus/pci/devices")
	if PathExists(filename) {
		contents, err := ReadLines(filename)
		if err == nil {
			if StringsContains(contents, "virtio-pci") {
				role = "guest"
			}
		}
	}

	filename = HostProc()
	if PathExists(filepath.Join(filename, "bc", "0")) {
		system = "openvz"
		role = "host"
	} else if PathExists(filepath.Join(filename, "vz")) {
		system = "openvz"
		role = "guest"
	}

	// not use dmidecode because it requires root
	if PathExists(filepath.Join(filename, "self", "status")) {
		contents, err := ReadLines(filepath.Join(filename, "self", "status"))
		if err == nil {

			if StringsContains(contents, "s_context:") ||
				StringsContains(contents, "VxID:") {
				system = "linux-vserver"
			}
			// TODO: guest or host
		}
	}

	if PathExists(filepath.Join(filename, "self", "cgroup")) {
		contents, err := ReadLines(filepath.Join(filename, "self", "cgroup"))
		if err == nil {
			if StringsContains(contents, "lxc") {
				system = "lxc"
				role = "guest"
			} else if StringsContains(contents, "docker") {
				system = "docker"
				role = "guest"
			} else if StringsContains(contents, "machine-rkt") {
				system = "rkt"
				role = "guest"
			} else if PathExists("/usr/bin/lxc-version") {
				system = "lxc"
				role = "host"
			}
		}
	}

	if PathExists(HostEtc("os-release")) {
		p, _, err := GetOSRelease()
		if err == nil && p == "coreos" {
			system = "rkt" // Is it true?
			role = "host"
		}
	}
	return system, role, nil
}

func GetOSRelease() (platform string, version string, err error) {
	contents, err := ReadLines(HostEtc("os-release"))
	if err != nil {
		return "", "", nil // return empty
	}
	for _, line := range contents {
		field := strings.Split(line, "=")
		if len(field) < 2 {
			continue
		}
		switch field[0] {
		case "ID": // use ID for lowercase
			platform = trimQuotes(field[1])
		case "VERSION":
			version = trimQuotes(field[1])
		}
	}
	return platform, version, nil
}

// Remove quotes of the source string
func trimQuotes(s string) string {
	if len(s) >= 2 {
		if s[0] == '"' && s[len(s)-1] == '"' {
			return s[1 : len(s)-1]
		}
	}
	return s
}
<|endoftext|>


<commit_before>// Copyright (C) 2018 Nippon Telegraph and Telephone 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.

package version

import "fmt"

const MAJOR uint = 2
const MINOR uint = 7
const PATCH uint = 0

var COMMIT string = ""
var IDENTIFIER string = ""
var METADATA string = ""

func Version() string {
	var suffix string = ""
	if len(IDENTIFIER) > 0 {
		suffix = fmt.Sprintf("-%s", IDENTIFIER)
	}

	if len(COMMIT) > 0 || len(METADATA) > 0 {
		suffix = suffix + "+"
	}

	if len(COMMIT) > 0 {
		suffix = fmt.Sprintf("%s"+"commit.%s", suffix, COMMIT)

	}

	if len(METADATA) > 0 {
		if len(COMMIT) > 0 {
			suffix = suffix + "."
		}
		suffix = suffix + METADATA
	}

	return fmt.Sprintf("%d.%d.%d%s", MAJOR, MINOR, PATCH, suffix)
}
<commit_msg>v2.8.0<commit_after>// Copyright (C) 2018 Nippon Telegraph and Telephone 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.

package version

import "fmt"

const MAJOR uint = 2
const MINOR uint = 8
const PATCH uint = 0

var COMMIT string = ""
var IDENTIFIER string = ""
var METADATA string = ""

func Version() string {
	var suffix string = ""
	if len(IDENTIFIER) > 0 {
		suffix = fmt.Sprintf("-%s", IDENTIFIER)
	}

	if len(COMMIT) > 0 || len(METADATA) > 0 {
		suffix = suffix + "+"
	}

	if len(COMMIT) > 0 {
		suffix = fmt.Sprintf("%s"+"commit.%s", suffix, COMMIT)

	}

	if len(METADATA) > 0 {
		if len(COMMIT) > 0 {
			suffix = suffix + "."
		}
		suffix = suffix + METADATA
	}

	return fmt.Sprintf("%d.%d.%d%s", MAJOR, MINOR, PATCH, suffix)
}
<|endoftext|>


<commit_before>package config

import (
	"encoding/json"
	"errors"
	"flag"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
)

const SpickSpanConfigFile = "spickspan.json"

var ssconfig string

func init() {
	flag.StringVar(&ssconfig, "ssconfig", ".", "configuration for spickspan")
}

func GetConfig() (Model, error) {
	configFilePath, err := findPathOfConfigFile()
	if err != nil {
		log.Printf("Failed to find the file path of the config file.")
		return Model{}, err
	}
	log.Printf("config file path: %v", configFilePath)
	return ParseConfigFile(configFilePath), nil
}

func ParseConfigFile(filename string) Model {
	data, e := ioutil.ReadFile(filename)
	if e != nil {
		log.Printf("File error: %v\n", e)
		os.Exit(1)
	}
	return parseConfigData(data, filename)
}

func adjustModel(model *Model, configFilePath string) {
	filedir := filepath.Dir(configFilePath)
	//adjustedServices := make(map[string]Service)
	for name, service := range model.Services {
		service.ServiceName = name
		if service.ProjectSrcRoot != "" {
			projectRoot := filepath.Join(filedir, service.ProjectSrcRoot)
			service.ProjectSrcRoot = projectRoot
		}
		//adjustedServices[name] = service
		model.Services[name] = service
	}
}

func parseConfigData(data []byte, configFilePath string) Model {
	var config Model
	json.Unmarshal(data, &config)
	adjustModel(&config, configFilePath)
	log.Printf("Results: %v\n", config)
	return config
}

func findPathOfConfigFile() (string, error) {
	//Path of config file is:
	//  current working directory plus
	//  the value of the -ssconfig flag plus
	//  the file name 'ssconfig.json'.
	//The default value of the -ssconfig file is '.'
	filedir, err := filepath.Abs(ssconfig)
	if err != nil {
		return "", err
	}

	filedir = filepath.Clean(filedir)

	if strings.HasSuffix(filedir, SpickSpanConfigFile) {
		filedir = filepath.Dir(filedir)
	}

	log.Printf("Starting to find config file at %v and up the directory hierarchy.", filedir)
	return findFileInParentDirs(filedir, SpickSpanConfigFile)
}

func findFileInParentDirs(filedir string, filename string) (string, error) {
	fullFileName := filepath.Join(filedir, filename)
	_, err := os.Stat(fullFileName)
	if os.IsNotExist(err) {
		parentFiledir := filepath.Dir(filedir)
		if parentFiledir == filedir {
			return "", errors.New("Could not find config file.")
		}
		return findFileInParentDirs(parentFiledir, filename)
	} else {
		log.Printf("ssconfig file path: %v", fullFileName)
		return fullFileName, nil
	}
}
<commit_msg>validate service name of a source project<commit_after>package config

import (
	"encoding/json"
	"errors"
	"flag"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/essentier/nomockutil"
)

const SpickSpanConfigFile = "spickspan.json"

var ssconfig string

func init() {
	flag.StringVar(&ssconfig, "ssconfig", ".", "configuration for spickspan")
}

func GetConfig() (Model, error) {
	configFilePath, err := findPathOfConfigFile()
	if err != nil {
		log.Printf("Failed to find the file path of the config file.")
		return Model{}, err
	}
	log.Printf("config file path: %v", configFilePath)
	return ParseConfigFile(configFilePath)
}

func ParseConfigFile(filename string) (Model, error) {
	data, e := ioutil.ReadFile(filename)
	if e != nil {
		log.Printf("File error: %v\n", e)
		os.Exit(1)
	}
	return parseConfigData(data, filename)
}

func adjustModel(model *Model, configFilePath string) {
	filedir := filepath.Dir(configFilePath)
	for name, service := range model.Services {
		service.ServiceName = name
		if service.ProjectSrcRoot != "" {
			projectRoot := filepath.Join(filedir, service.ProjectSrcRoot)
			service.ProjectSrcRoot = projectRoot
		}
		model.Services[name] = service
	}
}

func validateModel(model *Model) error {
	for name, service := range model.Services {
		if service.ProjectSrcRoot != "" {
			err := nomockutil.ValidateServiceName(name)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func parseConfigData(data []byte, configFilePath string) (Model, error) {
	var config Model
	err := json.Unmarshal(data, &config)
	if err != nil {
		return config, err
	}

	err = validateModel(&config)
	if err != nil {
		return config, err
	}

	adjustModel(&config, configFilePath)
	log.Printf("SpickSpan configurations: %v\n", config)
	return config, nil
}

func findPathOfConfigFile() (string, error) {
	//Path of config file is:
	//  current working directory plus
	//  the value of the -ssconfig flag plus
	//  the file name 'ssconfig.json'.
	//The default value of the -ssconfig file is '.'
	filedir, err := filepath.Abs(ssconfig)
	if err != nil {
		return "", err
	}

	filedir = filepath.Clean(filedir)

	if strings.HasSuffix(filedir, SpickSpanConfigFile) {
		filedir = filepath.Dir(filedir)
	}

	log.Printf("Starting to find config file at %v and up the directory hierarchy.", filedir)
	return findFileInParentDirs(filedir, SpickSpanConfigFile)
}

func findFileInParentDirs(filedir string, filename string) (string, error) {
	fullFileName := filepath.Join(filedir, filename)
	_, err := os.Stat(fullFileName)
	if os.IsNotExist(err) {
		parentFiledir := filepath.Dir(filedir)
		if parentFiledir == filedir {
			return "", errors.New("Could not find config file.")
		}
		return findFileInParentDirs(parentFiledir, filename)
	} else {
		log.Printf("ssconfig file path: %v", fullFileName)
		return fullFileName, nil
	}
}
<|endoftext|>


<commit_before>package ics

import (
	"errors"
	"io"

	"github.com/MJKWoolnough/parser"
)

type psuedoTokeniser []parser.Phrase

func (p *psuedoTokeniser) GetPhrase() (parser.Phrase, error) {
	if len(*p) == 0 {
		return parser.Phrase{
			Type: parser.PhraseError,
			Data: nil,
		}, io.ErrUnexpectedEOF
	}
	ph := (*p)[0]
	*p = (*p)[1:]
	return ph, nil
}

type AlarmType interface {
	section
	Type() string
}

type Alarm struct {
	AlarmType
}

func (a *Alarm) decode(t tokeniser) error {
	var pt psuedoTokeniser
Loop:
	for {
		ph, err := t.GetPhrase()
		if err != nil {
			return err
		}
		pt = append(pt, ph)
		switch ph.Data[0].Data {
		case "BEGIN":
			return ErrInvalidStructure
		case "ACTION":
			if a.AlarmType != nil {
				return ErrInvalidStructure
			}
			switch ph.Data[len(ph.Data)-1].Data {
			case "AUDIO":
				a.AlarmType = new(AlarmAudio)
			case "DISPLAY":
				a.AlarmType = new(AlarmDisplay)
			case "EMAIL":
				a.AlarmType = new(AlarmEmail)
			}
		case "END":
			break Loop
		}
	}
	if a.AlarmType == nil {
		return ErrMissingAlarmAction
	}
	return a.AlarmType.decode(&pt)
}
func (a *Alarm) valid() bool {
	if a.AlarmType == nil {
		return false
	}
	return a.AlarmType.valid()
}

func (AlarmAudio) Type() string {
	return "AUDIO"
}

func (AlarmDisplay) Type() string {
	return "DISPLAY"
}

func (AlarmEmail) Type() string {
	return "EMAIL"
}

// temporary to compile
type AlarmAudio struct{ section }
type AlarmDisplay struct{ section }
type AlarmEmail struct{ section }

// Errors
var (
	ErrInvalidStructure   = errors.New("invalid structure")
	ErrMissingAlarmAction = errors.New("missing alarm action")
)
<commit_msg>removed temporary type definitions<commit_after>package ics

import (
	"errors"
	"io"

	"github.com/MJKWoolnough/parser"
)

type psuedoTokeniser []parser.Phrase

func (p *psuedoTokeniser) GetPhrase() (parser.Phrase, error) {
	if len(*p) == 0 {
		return parser.Phrase{
			Type: parser.PhraseError,
			Data: nil,
		}, io.ErrUnexpectedEOF
	}
	ph := (*p)[0]
	*p = (*p)[1:]
	return ph, nil
}

type AlarmType interface {
	section
	Type() string
}

type Alarm struct {
	AlarmType
}

func (a *Alarm) decode(t tokeniser) error {
	var pt psuedoTokeniser
Loop:
	for {
		ph, err := t.GetPhrase()
		if err != nil {
			return err
		}
		pt = append(pt, ph)
		switch ph.Data[0].Data {
		case "BEGIN":
			return ErrInvalidStructure
		case "ACTION":
			if a.AlarmType != nil {
				return ErrInvalidStructure
			}
			switch ph.Data[len(ph.Data)-1].Data {
			case "AUDIO":
				a.AlarmType = new(AlarmAudio)
			case "DISPLAY":
				a.AlarmType = new(AlarmDisplay)
			case "EMAIL":
				a.AlarmType = new(AlarmEmail)
			}
		case "END":
			break Loop
		}
	}
	if a.AlarmType == nil {
		return ErrMissingAlarmAction
	}
	return a.AlarmType.decode(&pt)
}
func (a *Alarm) valid() bool {
	if a.AlarmType == nil {
		return false
	}
	return a.AlarmType.valid()
}

func (AlarmAudio) Type() string {
	return "AUDIO"
}

func (AlarmDisplay) Type() string {
	return "DISPLAY"
}

func (AlarmEmail) Type() string {
	return "EMAIL"
}

// Errors
var (
	ErrInvalidStructure   = errors.New("invalid structure")
	ErrMissingAlarmAction = errors.New("missing alarm action")
)
<|endoftext|>