<commit_before>// +build !android

package nk

/*
#cgo CFLAGS: -DNK_INCLUDE_FIXED_TYPES -DNK_INCLUDE_STANDARD_IO -DNK_INCLUDE_DEFAULT_ALLOCATOR -DNK_INCLUDE_FONT_BAKING -DNK_INCLUDE_DEFAULT_FONT -DNK_INCLUDE_VERTEX_BUFFER_OUTPUT -Wno-implicit-function-declaration
#cgo windows LDFLAGS: -Wl,--allow-multiple-definition
#include <string.h>

#include "nuklear.h"
*/
import "C"
import (
	"unsafe"

	"github.com/veandco/go-sdl2/sdl"
)

type PlatformInitOption int

const (
	PlatformDefault PlatformInitOption = iota
	PlatformInstallCallbacks
)

func NkPlatformInit(win *sdl.Window, context sdl.GLContext, opt PlatformInitOption) *Context {
	state.win = win
	// if opt == PlatformInstallCallbacks {
	// 	win.SetScrollCallback(func(w *glfw.Window, xoff float64, yoff float64) {
	// 		state.scroll += float32(yoff)
	// 	})
	// 	win.SetCharCallback(func(w *glfw.Window, char rune) {
	// 		if len(state.text) < 256 { // NK_GLFW_TEXT_MAX
	// 			state.text += string(char)
	// 		}
	// 	})
	// }

	state.ctx = NewContext()
	NkInitDefault(state.ctx, nil)
	deviceCreate()
	return state.ctx

	// TODO(xlab): clipboard
	// state.ctx.clip.copy = nk_glfw3_clipbard_copy;
	// state.ctx.clip.paste = nk_glfw3_clipbard_paste;
	// state.ctx.clip.userdata = nk_handle_ptr(0);
}

func NkPlatformShutdown() {
	NkFontAtlasClear(state.atlas)
	NkFree(state.ctx)
	deviceDestroy()
	state = nil
}

func NkFontStashBegin(atlas **FontAtlas) {
	state.atlas = NewFontAtlas()
	NkFontAtlasInitDefault(state.atlas)
	NkFontAtlasBegin(state.atlas)
	*atlas = state.atlas
}

func NkFontStashEnd() {
	var width, height int32
	image := NkFontAtlasBake(state.atlas, &width, &height, FontAtlasRgba32)
	deviceUploadAtlas(image, width, height)
	NkFontAtlasEnd(state.atlas, NkHandleId(int32(state.ogl.font_tex)), &state.ogl.null)
	if font := state.atlas.DefaultFont(); font != nil {
		NkStyleSetFont(state.ctx, font.Handle())
	}
}

func NkPlatformNewFrame() {
	win := state.win
	ctx := state.ctx
	state.width, state.height = win.GetSize()
	state.display_width, state.display_height = win.GetSize()
	state.fbScaleX = float32(state.display_width) / float32(state.width)
	state.fbScaleY = float32(state.display_height) / float32(state.height)

	NkInputBegin(ctx)
	for _, r := range state.text {
		NkInputUnicode(ctx, Rune(r))
	}

	// optional grabbing behavior
	m := ctx.Input().Mouse()
	if m.Grab() {
		sdl.SetRelativeMouseMode(true)
	} else if m.Ungrab() {
		sdl.SetRelativeMouseMode(false)
	}

	// NkInputKey(ctx, KeyDel, keyPressed(win, glfw.KeyDelete))
	// NkInputKey(ctx, KeyEnter, keyPressed(win, glfw.KeyEnter))
	// NkInputKey(ctx, KeyTab, keyPressed(win, glfw.KeyTab))
	// NkInputKey(ctx, KeyBackspace, keyPressed(win, glfw.KeyBackspace))
	// NkInputKey(ctx, KeyUp, keyPressed(win, glfw.KeyUp))
	// NkInputKey(ctx, KeyDown, keyPressed(win, glfw.KeyDown))
	// NkInputKey(ctx, KeyTextStart, keyPressed(win, glfw.KeyHome))
	// NkInputKey(ctx, KeyTextEnd, keyPressed(win, glfw.KeyEnd))
	// NkInputKey(ctx, KeyScrollStart, keyPressed(win, glfw.KeyHome))
	// NkInputKey(ctx, KeyScrollEnd, keyPressed(win, glfw.KeyEnd))
	// NkInputKey(ctx, KeyScrollUp, keyPressed(win, glfw.KeyPageUp))
	// NkInputKey(ctx, KeyScrollDown, keyPressed(win, glfw.KeyPageDown))
	// NkInputKey(ctx, KeyShift, keysPressed(win, glfw.KeyLeftShift, glfw.KeyRightShift))
	// if keysPressed(win, glfw.KeyLeftControl, glfw.KeyRightControl) > 0 {
	// 	NkInputKey(ctx, KeyCopy, keyPressed(win, glfw.KeyC))
	// 	NkInputKey(ctx, KeyPaste, keyPressed(win, glfw.KeyV))
	// 	NkInputKey(ctx, KeyCut, keyPressed(win, glfw.KeyX))
	// 	NkInputKey(ctx, KeyTextUndo, keyPressed(win, glfw.KeyZ))
	// 	NkInputKey(ctx, KeyTextRedo, keyPressed(win, glfw.KeyR))
	// 	NkInputKey(ctx, KeyTextWordLeft, keyPressed(win, glfw.KeyLeft))
	// 	NkInputKey(ctx, KeyTextWordRight, keyPressed(win, glfw.KeyRight))
	// 	NkInputKey(ctx, KeyTextLineStart, keyPressed(win, glfw.KeyB))
	// 	NkInputKey(ctx, KeyTextLineEnd, keyPressed(win, glfw.KeyE))
	// } else {
	// 	NkInputKey(ctx, KeyLeft, keyPressed(win, glfw.KeyLeft))
	// 	NkInputKey(ctx, KeyRight, keyPressed(win, glfw.KeyRight))
	// 	NkInputKey(ctx, KeyCopy, 0)
	// 	NkInputKey(ctx, KeyPaste, 0)
	// 	NkInputKey(ctx, KeyCut, 0)
	// 	NkInputKey(ctx, KeyShift, 0)
	// }
	// x, y := win.GetCursorPos()
	// NkInputMotion(ctx, int32(x), int32(y))
	// if m := ctx.Input().Mouse(); m.Grabbed() {
	// 	prevX, prevY := m.Prev()
	// 	win.SetCursorPos(float64(prevX), float64(prevY))
	// 	m.SetPos(prevX, prevY)
	// }

	// NkInputButton(ctx, ButtonLeft, int32(x), int32(y), buttonPressed(win, glfw.MouseButtonLeft))
	// NkInputButton(ctx, ButtonMiddle, int32(x), int32(y), buttonPressed(win, glfw.MouseButtonMiddle))
	// NkInputButton(ctx, ButtonRight, int32(x), int32(y), buttonPressed(win, glfw.MouseButtonRight))
	NkInputScroll(ctx, state.scroll)
	NkInputEnd(ctx)
	state.text = ""
	state.scroll = 0
}

var (
	sizeofDrawIndex = unsafe.Sizeof(DrawIndex(0))
	emptyVertex     = platformVertex{}
)

type platformVertex struct {
	position [2]float32
	uv       [2]float32
	col      [4]Byte
}

const (
	platformVertexSize  = unsafe.Sizeof(platformVertex{})
	platformVertexAlign = unsafe.Alignof(platformVertex{})
)

type platformState struct {
	win *sdl.Window

	width          int
	height         int
	display_width  int
	display_height int

	ogl   *platformDevice
	ctx   *Context
	atlas *FontAtlas

	fbScaleX float32
	fbScaleY float32

	text   string
	scroll float32
}

func NkPlatformDisplayHandle() *sdl.Window {
	if state != nil {
		return state.win
	}
	return nil
}

// func keyPressed(win *sdl.Window, key glfw.Key) int32 {
// 	if win.GetKey(key) == glfw.Press {
// 		return 1
// 	}
// 	return 0
// }

// func buttonPressed(win *glfw.Window, button glfw.MouseButton) int32 {
// 	if win.GetMouseButton(button) == glfw.Press {
// 		return 1
// 	}
// 	return 0
// }

// func keysPressed(win *glfw.Window, keys ...glfw.Key) int32 {
// 	for i := range keys {
// 		if win.GetKey(keys[i]) == glfw.Press {
// 			return 1
// 		}
// 	}
// 	return 0
// }
<commit_msg>Use SDL keyboard functions<commit_after>// +build !android

package nk

/*
#cgo CFLAGS: -DNK_INCLUDE_FIXED_TYPES -DNK_INCLUDE_STANDARD_IO -DNK_INCLUDE_DEFAULT_ALLOCATOR -DNK_INCLUDE_FONT_BAKING -DNK_INCLUDE_DEFAULT_FONT -DNK_INCLUDE_VERTEX_BUFFER_OUTPUT -Wno-implicit-function-declaration
#cgo windows LDFLAGS: -Wl,--allow-multiple-definition
#include <string.h>

#include "nuklear.h"
*/
import "C"
import (
	"unsafe"

	"github.com/veandco/go-sdl2/sdl"
)

type PlatformInitOption int

const (
	PlatformDefault PlatformInitOption = iota
	PlatformInstallCallbacks
)

func NkPlatformInit(win *sdl.Window, context sdl.GLContext, opt PlatformInitOption) *Context {
	state.win = win
	// if opt == PlatformInstallCallbacks {
	// 	win.SetScrollCallback(func(w *glfw.Window, xoff float64, yoff float64) {
	// 		state.scroll += float32(yoff)
	// 	})
	// 	win.SetCharCallback(func(w *glfw.Window, char rune) {
	// 		if len(state.text) < 256 { // NK_GLFW_TEXT_MAX
	// 			state.text += string(char)
	// 		}
	// 	})
	// }

	state.ctx = NewContext()
	NkInitDefault(state.ctx, nil)
	deviceCreate()
	return state.ctx
}

func NkPlatformShutdown() {
	NkFontAtlasClear(state.atlas)
	NkFree(state.ctx)
	deviceDestroy()
	state = nil
}

func NkFontStashBegin(atlas **FontAtlas) {
	state.atlas = NewFontAtlas()
	NkFontAtlasInitDefault(state.atlas)
	NkFontAtlasBegin(state.atlas)
	*atlas = state.atlas
}

func NkFontStashEnd() {
	var width, height int32
	image := NkFontAtlasBake(state.atlas, &width, &height, FontAtlasRgba32)
	deviceUploadAtlas(image, width, height)
	NkFontAtlasEnd(state.atlas, NkHandleId(int32(state.ogl.font_tex)), &state.ogl.null)
	if font := state.atlas.DefaultFont(); font != nil {
		NkStyleSetFont(state.ctx, font.Handle())
	}
}

func NkPlatformNewFrame() {
	win := state.win
	ctx := state.ctx
	state.width, state.height = win.GetSize()
	state.display_width, state.display_height = win.GetSize()
	state.fbScaleX = float32(state.display_width) / float32(state.width)
	state.fbScaleY = float32(state.display_height) / float32(state.height)

	NkInputBegin(ctx)
	for _, r := range state.text {
		NkInputUnicode(ctx, Rune(r))
	}

	// optional grabbing behavior
	m := ctx.Input().Mouse()
	if m.Grab() {
		sdl.SetRelativeMouseMode(true)
	} else if m.Ungrab() {
		sdl.SetRelativeMouseMode(false)
	}

	keys := sdl.GetKeyboardState()

	NkInputKey(ctx, KeyDel, int32(keys[sdl.SCANCODE_DELETE]))
	NkInputKey(ctx, KeyEnter, int32(keys[sdl.SCANCODE_RETURN]))
	NkInputKey(ctx, KeyTab, int32(keys[sdl.SCANCODE_TAB]))
	NkInputKey(ctx, KeyBackspace, int32(keys[sdl.SCANCODE_BACKSPACE]))
	NkInputKey(ctx, KeyUp, int32(keys[sdl.SCANCODE_UP]))
	NkInputKey(ctx, KeyDown, int32(keys[sdl.SCANCODE_DOWN]))
	NkInputKey(ctx, KeyTextStart, int32(keys[sdl.SCANCODE_HOME]))
	NkInputKey(ctx, KeyTextEnd, int32(keys[sdl.SCANCODE_END]))
	NkInputKey(ctx, KeyScrollStart, int32(keys[sdl.SCANCODE_HOME]))
	NkInputKey(ctx, KeyScrollEnd, int32(keys[sdl.SCANCODE_END]))
	NkInputKey(ctx, KeyScrollUp, int32(keys[sdl.SCANCODE_PAGEUP]))
	NkInputKey(ctx, KeyScrollDown, int32(keys[sdl.SCANCODE_PAGEDOWN]))

	shiftHeld := int32(0)
	if keys[sdl.KMOD_LSHIFT] == 1 || keys[sdl.KMOD_RSHIFT] == 1 {
		shiftHeld = int32(1)
	}
	NkInputKey(ctx, KeyShift, shiftHeld)

	controlHeld := false
	if keys[sdl.KMOD_LCTRL] == 1 || keys[sdl.KMOD_RCTRL] == 1 {
		controlHeld = true
	}

	if controlHeld {
		NkInputKey(ctx, KeyCopy, int32(keys[sdl.SCANCODE_C]))
		NkInputKey(ctx, KeyPaste, int32(keys[sdl.SCANCODE_V]))
		NkInputKey(ctx, KeyCut, int32(keys[sdl.SCANCODE_X]))
		NkInputKey(ctx, KeyTextUndo, int32(keys[sdl.SCANCODE_Z]))
		NkInputKey(ctx, KeyTextRedo, int32(keys[sdl.SCANCODE_R]))
		NkInputKey(ctx, KeyTextWordLeft, int32(keys[sdl.SCANCODE_LEFT]))
		NkInputKey(ctx, KeyTextWordRight, int32(keys[sdl.SCANCODE_RIGHT]))
		NkInputKey(ctx, KeyTextLineStart, int32(keys[sdl.SCANCODE_B]))
		NkInputKey(ctx, KeyTextLineEnd, int32(keys[sdl.SCANCODE_E]))
	} else {
		NkInputKey(ctx, KeyLeft, int32(keys[sdl.SCANCODE_LEFT]))
		NkInputKey(ctx, KeyRight, int32(keys[sdl.SCANCODE_RIGHT]))
		NkInputKey(ctx, KeyCopy, 0)
		NkInputKey(ctx, KeyPaste, 0)
		NkInputKey(ctx, KeyCut, 0)
		NkInputKey(ctx, KeyShift, 0)
	}

	x, y, mouseState := sdl.GetMouseState()
	NkInputMotion(ctx, int32(x), int32(y))
	// if m := ctx.Input().Mouse(); m.Grabbed() {
	// 	prevX, prevY := m.Prev()
	// 	win.SetCursorPos(float64(prevX), float64(prevY))
	// 	m.SetPos(prevX, prevY)
	// }

	NkInputButton(ctx, ButtonLeft, int32(x), int32(y), int32(mouseState&sdl.ButtonLMask()))
	NkInputButton(ctx, ButtonMiddle, int32(x), int32(y), int32(mouseState&sdl.ButtonMMask()))
	NkInputButton(ctx, ButtonRight, int32(x), int32(y), int32(mouseState&sdl.ButtonRMask()))

	NkInputScroll(ctx, state.scroll)
	NkInputEnd(ctx)
	state.text = ""
	state.scroll = 0
}

var (
	sizeofDrawIndex = unsafe.Sizeof(DrawIndex(0))
	emptyVertex     = platformVertex{}
)

type platformVertex struct {
	position [2]float32
	uv       [2]float32
	col      [4]Byte
}

const (
	platformVertexSize  = unsafe.Sizeof(platformVertex{})
	platformVertexAlign = unsafe.Alignof(platformVertex{})
)

type platformState struct {
	win *sdl.Window

	width          int
	height         int
	display_width  int
	display_height int

	ogl   *platformDevice
	ctx   *Context
	atlas *FontAtlas

	fbScaleX float32
	fbScaleY float32

	text   string
	scroll float32
}

func NkPlatformDisplayHandle() *sdl.Window {
	if state != nil {
		return state.win
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"os"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
)

const (
	FAILURE_PACKAGE_YML_BLANK      = "./test/blank.yml"
	FAILURE_PACKAGE_YML_REPO_BLANK = "./test/repo-blank.yml"
)

func before() (*CLI, *bytes.Buffer, *bytes.Buffer) {
	outStream, errStream := new(bytes.Buffer), new(bytes.Buffer)
	cli := NewCli()
	cli.logger.Reset(outStream, errStream)
	return cli, outStream, errStream
}

func TestInstallSuccess(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install %s -u %s -p %s", os.Getenv("REPOSITORY"), os.Getenv("USERNAME"), os.Getenv("PASSWORD")), " ")
	cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, fmt.Sprintf("Clone repository from https://github.com/%s (branch: %s)", os.Getenv("REPOSITORY"), "master"))
	assert.Contains(t, outString, "Check Deploy Result...")
	assert.Contains(t, outString, "Deploy is successful")
}

func TestInstallFailureNoUsername(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install %s -p %s", os.Getenv("REPOSITORY"), os.Getenv("PASSWORD")), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "Username is required")
}

func TestInstallFailureNoPassword(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install %s -u %s", os.Getenv("REPOSITORY"), os.Getenv("USERNAME")), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "Password is required")
}

func TestInstallFailureNoRepository(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install -u %s -p %s", os.Getenv("USERNAME"), os.Getenv("PASSWORD")), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "Repository not specified")
}

func TestInstallFailureNoPackageYML(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install -u %s -p %s -P %s", os.Getenv("USERNAME"), os.Getenv("PASSWORD"), "NOPACKAGE.yml"), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "open NOPACKAGE.yml: no such file or directory")
}

func TestInstallFailureInvalidCredentials(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install %s -u hoge -p fuga", os.Getenv("REPOSITORY")), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "INVALID_LOGIN: Invalid username, password, security token; or user locked out.")
}

func TestInstallFailurePackageYmlBlank(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install -u %s -p %s -P %s", os.Getenv("USERNAME"), os.Getenv("PASSWORD"), FAILURE_PACKAGE_YML_BLANK), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "Repository not specified")
}

func TestInstallFailurePackageYmlRepoBlank(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install -u %s -p %s -P %s", os.Getenv("USERNAME"), os.Getenv("PASSWORD"), FAILURE_PACKAGE_YML_REPO_BLANK), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "Repository not specified")
}
<commit_msg>Add test code for sub directory<commit_after>package main

import (
	"bytes"
	"fmt"
	"os"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
)

const (
	FAILURE_PACKAGE_YML_BLANK      = "./test/blank.yml"
	FAILURE_PACKAGE_YML_REPO_BLANK = "./test/repo-blank.yml"
)

func before() (*CLI, *bytes.Buffer, *bytes.Buffer) {
	outStream, errStream := new(bytes.Buffer), new(bytes.Buffer)
	cli := NewCli()
	cli.logger.Reset(outStream, errStream)
	return cli, outStream, errStream
}

func TestInstallSuccess(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install %s -u %s -p %s", os.Getenv("REPOSITORY"), os.Getenv("USERNAME"), os.Getenv("PASSWORD")), " ")
	cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, fmt.Sprintf("Clone repository from https://github.com/%s (branch: %s)", os.Getenv("REPOSITORY"), "master"))
	assert.Contains(t, outString, "Check Deploy Result...")
	assert.Contains(t, outString, "Deploy is successful")
}

func TestInstallSuccessForSubdir(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install %s -u %s -p %s", os.Getenv("REPOSITORY_SUBDIR"), os.Getenv("USERNAME"), os.Getenv("PASSWORD")), " ")
	cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, fmt.Sprintf("Clone repository from https://github.com/"))
	assert.Contains(t, outString, "Check Deploy Result...")
	assert.Contains(t, outString, "Deploy is successful")
}

func TestInstallFailureNoUsername(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install %s -p %s", os.Getenv("REPOSITORY"), os.Getenv("PASSWORD")), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "Username is required")
}

func TestInstallFailureNoPassword(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install %s -u %s", os.Getenv("REPOSITORY"), os.Getenv("USERNAME")), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "Password is required")
}

func TestInstallFailureNoRepository(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install -u %s -p %s", os.Getenv("USERNAME"), os.Getenv("PASSWORD")), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "Repository not specified")
}

func TestInstallFailureNoPackageYML(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install -u %s -p %s -P %s", os.Getenv("USERNAME"), os.Getenv("PASSWORD"), "NOPACKAGE.yml"), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "open NOPACKAGE.yml: no such file or directory")
}

func TestInstallFailureInvalidCredentials(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install %s -u hoge -p fuga", os.Getenv("REPOSITORY")), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "INVALID_LOGIN: Invalid username, password, security token; or user locked out.")
}

func TestInstallFailurePackageYmlBlank(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install -u %s -p %s -P %s", os.Getenv("USERNAME"), os.Getenv("PASSWORD"), FAILURE_PACKAGE_YML_BLANK), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "Repository not specified")
}

func TestInstallFailurePackageYmlRepoBlank(t *testing.T) {
	cli, outStream, _ := before()
	args := strings.Split(fmt.Sprintf("spm install -u %s -p %s -P %s", os.Getenv("USERNAME"), os.Getenv("PASSWORD"), FAILURE_PACKAGE_YML_REPO_BLANK), " ")
	_ = cli.Run(args)
	outString := outStream.String()
	assert.Contains(t, outString, "Repository not specified")
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"text/template"
	"time"
)

type MarathonTasks struct {
	Tasks []struct {
		AppId              string `json:"appId"`
		HealthCheckResults []struct {
			Alive bool `json:"alive"`
		} `json:"healthCheckResults"`
		Host         string  `json:"host"`
		Id           string  `json:"id"`
		Ports        []int64 `json:"ports"`
		ServicePorts []int64 `json:"servicePorts"`
		StagedAt     string  `json:"stagedAt"`
		StartedAt    string  `json:"startedAt"`
		Version      string  `json:"version"`
	} `json:"tasks"`
}

type MarathonApps struct {
	Apps []struct {
		Id           string            `json:"id"`
		Labels       map[string]string `json:"labels"`
		Env          map[string]string `json:"env"`
		HealthChecks []interface{}     `json:"healthChecks"`
	} `json:"apps"`
}

func eventStream() {
	go func() {
		client := &http.Client{
			Timeout:   0 * time.Second,
			Transport: tr,
		}
		ticker := time.NewTicker(1 * time.Second)
		for _ = range ticker.C {
			req, err := http.NewRequest("GET", endpoint+"/v2/events", nil)
			if err != nil {
				log.Printf("Unable to create event stream request: %s\n", err)
				continue
			}
			req.Header.Set("Accept", "text/event-stream")
			if config.User != "" {
				req.SetBasicAuth(config.User, config.Pass)
			}
			resp, err := client.Do(req)
			if err != nil {
				log.Printf("Unable to access Marathon event stream: %s\n", err)
				continue
			}
			reader := bufio.NewReader(resp.Body)
			for {
				line, err := reader.ReadString('\n')
				if err != nil {
					if err != io.EOF {
						log.Printf("Error reading Marathon event: %s\n", err)
					}
					resp.Body.Close()
					break
				}
				if !strings.HasPrefix(line, "event: ") {
					continue
				}
				log.Printf("Marathon event %s received. Triggering new update.", strings.TrimSpace(line[6:]))
				select {
				case eventqueue <- true: // Add reload to our queue channel, unless it is full of course.
				default:
					log.Println("queue is full")
				}

			}
			resp.Body.Close()
			log.Println("Event stream connection was closed. Re-opening...")
		}
	}()
}

func endpointHealth() {
	go func() {
		ticker := time.NewTicker(20 * time.Second)
		for {
			select {
			case <-ticker.C:
				for _, ep := range config.Marathon {
					client := &http.Client{
						Timeout:   5 * time.Second,
						Transport: tr,
					}
					req, err := http.NewRequest("GET", ep+"/ping", nil)
					if err != nil {
						log.Printf("An error occurred creating endpoint health request: %s\n", err.Error())
						continue
					}
					if config.User != "" {
						req.SetBasicAuth(config.User, config.Pass)
					}
					resp, err := client.Do(req)
					if err != nil {
						log.Printf("Endpoint %s is down: %s\n", ep, err.Error())
						continue
					}
					resp.Body.Close()
					if resp.StatusCode != 200 {
						log.Printf("Endpoint %s is down: status code %d\n", ep, resp.StatusCode)
						continue
					}
					endpoint = ep
					log.Printf("Endpoint %s is active.\n", ep)
					break // no need to continue now.
				}
			}
		}
	}()
}

func eventWorker() {
	go func() {
		// a ticker channel to limit reloads to marathon, 1s is enough for now.
		ticker := time.NewTicker(1 * time.Second)
		for {
			select {
			case <-ticker.C:
				<-eventqueue
				start := time.Now()
				err := reload()
				elapsed := time.Since(start)
				if err != nil {
					log.Println("config update failed")
					if config.Statsd != "" {
						go func() {
							hostname, _ := os.Hostname()
							statsd.Counter(1.0, "nixy."+hostname+".reload.failed", 1)
						}()
					}
				} else {
					log.Printf("config update took %s\n", elapsed)
					if config.Statsd != "" {
						go func(elapsed time.Duration) {
							hostname, _ := os.Hostname()
							statsd.Counter(1.0, "nixy."+hostname+".reload.success", 1)
							statsd.Timing(1.0, "nixy."+hostname+".reload.time", elapsed)
						}(elapsed)
					}
				}
			}
		}
	}()
}

func fetchApps(jsontasks *MarathonTasks, jsonapps *MarathonApps) error {
	client := &http.Client{
		Timeout:   5 * time.Second,
		Transport: tr,
	}
	// take advantage of goroutines and run both reqs concurrent.
	appschn := make(chan error)
	taskschn := make(chan error)
	go func() {
		req, err := http.NewRequest("GET", endpoint+"/v2/tasks", nil)
		if err != nil {
			taskschn <- err
			return
		}
		req.Header.Set("Accept", "application/json")
		if config.User != "" {
			req.SetBasicAuth(config.User, config.Pass)
		}
		resp, err := client.Do(req)
		if err != nil {
			taskschn <- err
			return
		}
		defer resp.Body.Close()
		decoder := json.NewDecoder(resp.Body)
		err = decoder.Decode(&jsontasks)
		if err != nil {
			taskschn <- err
			return
		}
		taskschn <- nil
	}()
	go func() {
		req, err := http.NewRequest("GET", endpoint+"/v2/apps", nil)
		if err != nil {
			appschn <- err
			return
		}
		req.Header.Set("Accept", "application/json")
		if config.User != "" {
			req.SetBasicAuth(config.User, config.Pass)
		}
		resp, err := client.Do(req)
		if err != nil {
			appschn <- err
			return
		}
		defer resp.Body.Close()
		decoder := json.NewDecoder(resp.Body)
		err = decoder.Decode(&jsonapps)
		if err != nil {
			appschn <- err
			return
		}
		appschn <- nil
	}()
	appserr := <-appschn
	taskserr := <-taskschn
	if appserr != nil {
		return appserr
	}
	if taskserr != nil {
		return taskserr
	}
	return nil
}

func syncApps(jsontasks *MarathonTasks, jsonapps *MarathonApps) {
	config.Lock()
	defer config.Unlock()
	config.Apps = make(map[string]App)
	for _, app := range jsonapps.Apps {
		for _, task := range jsontasks.Tasks {
			if task.AppId != app.Id {
				continue
			}
			// Lets skip tasks that does not expose any ports.
			if len(task.Ports) == 0 {
				continue
			}
			if len(app.HealthChecks) > 0 {
				if len(task.HealthCheckResults) == 0 {
					// this means tasks is being deployed but not yet monitored as alive. Assume down.
					continue
				}
				alive := true
				for _, health := range task.HealthCheckResults {
					// check if health check is alive
					if health.Alive == false {
						alive = false
					}
				}
				if alive != true {
					// at least one health check has failed. Assume down.
					continue
				}
			}
			if s, ok := config.Apps[app.Id]; ok {
				s.Tasks = append(s.Tasks, task.Host+":"+strconv.FormatInt(task.Ports[0], 10))
				config.Apps[app.Id] = s
			} else {
				var newapp = App{}
				newapp.Tasks = []string{task.Host + ":" + strconv.FormatInt(task.Ports[0], 10)}
				// Create a valid hostname of app id.
				if s, ok := app.Labels["subdomain"]; ok {
					newapp.Host = s
				} else if s, ok := app.Labels["moxy_subdomain"]; ok {
					// to be compatible with moxy
					newapp.Host = s
				} else {
					re := regexp.MustCompile("[^0-9a-z-]")
					newapp.Host = re.ReplaceAllLiteralString(app.Id, "")
				}
				newapp.Labels = app.Labels
				newapp.Env = app.Env
				config.Apps[app.Id] = newapp
			}
		}

	}

}

func writeConf() error {
	t, err := template.New(filepath.Base(config.Nginx_template)).ParseFiles(config.Nginx_template)
	if err != nil {
		return err
	}
	f, err := os.Create(config.Nginx_config)
	defer f.Close()
	if err != nil {
		return err
	}
	err = t.Execute(f, config)
	if err != nil {
		return err
	}
	return nil
}

func checkTmpl() error {
	t, err := template.New(filepath.Base(config.Nginx_template)).ParseFiles(config.Nginx_template)
	if err != nil {
		return err
	}
	err = t.Execute(ioutil.Discard, config)
	if err != nil {
		return err
	}
	return nil
}

func checkConf() error {
	cmd := exec.Command(config.Nginx_cmd, "-c", config.Nginx_config, "-t")
	var stderr bytes.Buffer
	cmd.Stderr = &stderr
	err := cmd.Run() // will wait for command to return
	if err != nil {
		msg := fmt.Sprint(err) + ": " + stderr.String()
		errstd := errors.New(msg)
		return errstd
	}
	return nil
}

func reloadNginx() error {
	cmd := exec.Command(config.Nginx_cmd, "-s", "reload")
	var stderr bytes.Buffer
	cmd.Stderr = &stderr
	err := cmd.Run() // will wait for command to return
	if err != nil {
		msg := fmt.Sprint(err) + ": " + stderr.String()
		errstd := errors.New(msg)
		return errstd
	}
	return nil
}

func reload() error {
	jsontasks := MarathonTasks{}
	jsonapps := MarathonApps{}
	err := fetchApps(&jsontasks, &jsonapps)
	if err != nil {
		log.Println("Unable to sync from Marathon:", err)
		return err
	}
	syncApps(&jsontasks, &jsonapps)
	err = writeConf()
	if err != nil {
		log.Println("Unable to generate nginx config:", err)
		return err
	}
	err = reloadNginx()
	if err != nil {
		log.Println("Unable to reload nginx:", err)
		return err
	}
	return nil
}
<commit_msg>bug fix, in case of duplicate subdomain<commit_after>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"text/template"
	"time"
)

type MarathonTasks struct {
	Tasks []struct {
		AppId              string `json:"appId"`
		HealthCheckResults []struct {
			Alive bool `json:"alive"`
		} `json:"healthCheckResults"`
		Host         string  `json:"host"`
		Id           string  `json:"id"`
		Ports        []int64 `json:"ports"`
		ServicePorts []int64 `json:"servicePorts"`
		StagedAt     string  `json:"stagedAt"`
		StartedAt    string  `json:"startedAt"`
		Version      string  `json:"version"`
	} `json:"tasks"`
}

type MarathonApps struct {
	Apps []struct {
		Id           string            `json:"id"`
		Labels       map[string]string `json:"labels"`
		Env          map[string]string `json:"env"`
		HealthChecks []interface{}     `json:"healthChecks"`
	} `json:"apps"`
}

func eventStream() {
	go func() {
		client := &http.Client{
			Timeout:   0 * time.Second,
			Transport: tr,
		}
		ticker := time.NewTicker(1 * time.Second)
		for _ = range ticker.C {
			req, err := http.NewRequest("GET", endpoint+"/v2/events", nil)
			if err != nil {
				log.Printf("Unable to create event stream request: %s\n", err)
				continue
			}
			req.Header.Set("Accept", "text/event-stream")
			if config.User != "" {
				req.SetBasicAuth(config.User, config.Pass)
			}
			resp, err := client.Do(req)
			if err != nil {
				log.Printf("Unable to access Marathon event stream: %s\n", err)
				continue
			}
			reader := bufio.NewReader(resp.Body)
			for {
				line, err := reader.ReadString('\n')
				if err != nil {
					if err != io.EOF {
						log.Printf("Error reading Marathon event: %s\n", err)
					}
					resp.Body.Close()
					break
				}
				if !strings.HasPrefix(line, "event: ") {
					continue
				}
				log.Printf("Marathon event %s received. Triggering new update.", strings.TrimSpace(line[6:]))
				select {
				case eventqueue <- true: // Add reload to our queue channel, unless it is full of course.
				default:
					log.Println("queue is full")
				}

			}
			resp.Body.Close()
			log.Println("Event stream connection was closed. Re-opening...")
		}
	}()
}

func endpointHealth() {
	go func() {
		ticker := time.NewTicker(10 * time.Second)
		for {
			select {
			case <-ticker.C:
				for _, ep := range config.Marathon {
					client := &http.Client{
						Timeout:   5 * time.Second,
						Transport: tr,
					}
					req, err := http.NewRequest("GET", ep+"/ping", nil)
					if err != nil {
						log.Printf("An error occurred creating endpoint health request: %s\n", err.Error())
						continue
					}
					if config.User != "" {
						req.SetBasicAuth(config.User, config.Pass)
					}
					resp, err := client.Do(req)
					if err != nil {
						log.Printf("Endpoint %s is down: %s\n", ep, err.Error())
						continue
					}
					resp.Body.Close()
					if resp.StatusCode != 200 {
						log.Printf("Endpoint %s is down: status code %d\n", ep, resp.StatusCode)
						continue
					}
					if endpoint != ep {
						endpoint = ep
						log.Printf("Endpoint %s is now active.\n", ep)
					}
					break // no need to continue now.
				}
			}
		}
	}()
}

func eventWorker() {
	go func() {
		// a ticker channel to limit reloads to marathon, 1s is enough for now.
		ticker := time.NewTicker(1 * time.Second)
		for {
			select {
			case <-ticker.C:
				<-eventqueue
				start := time.Now()
				err := reload()
				elapsed := time.Since(start)
				if err != nil {
					log.Println("config update failed")
					if config.Statsd != "" {
						go func() {
							hostname, _ := os.Hostname()
							statsd.Counter(1.0, "nixy."+hostname+".reload.failed", 1)
						}()
					}
				} else {
					log.Printf("config update took %s\n", elapsed)
					if config.Statsd != "" {
						go func(elapsed time.Duration) {
							hostname, _ := os.Hostname()
							statsd.Counter(1.0, "nixy."+hostname+".reload.success", 1)
							statsd.Timing(1.0, "nixy."+hostname+".reload.time", elapsed)
						}(elapsed)
					}
				}
			}
		}
	}()
}

func fetchApps(jsontasks *MarathonTasks, jsonapps *MarathonApps) error {
	client := &http.Client{
		Timeout:   5 * time.Second,
		Transport: tr,
	}
	// take advantage of goroutines and run both reqs concurrent.
	appschn := make(chan error)
	taskschn := make(chan error)
	go func() {
		req, err := http.NewRequest("GET", endpoint+"/v2/tasks", nil)
		if err != nil {
			taskschn <- err
			return
		}
		req.Header.Set("Accept", "application/json")
		if config.User != "" {
			req.SetBasicAuth(config.User, config.Pass)
		}
		resp, err := client.Do(req)
		if err != nil {
			taskschn <- err
			return
		}
		defer resp.Body.Close()
		decoder := json.NewDecoder(resp.Body)
		err = decoder.Decode(&jsontasks)
		if err != nil {
			taskschn <- err
			return
		}
		taskschn <- nil
	}()
	go func() {
		req, err := http.NewRequest("GET", endpoint+"/v2/apps", nil)
		if err != nil {
			appschn <- err
			return
		}
		req.Header.Set("Accept", "application/json")
		if config.User != "" {
			req.SetBasicAuth(config.User, config.Pass)
		}
		resp, err := client.Do(req)
		if err != nil {
			appschn <- err
			return
		}
		defer resp.Body.Close()
		decoder := json.NewDecoder(resp.Body)
		err = decoder.Decode(&jsonapps)
		if err != nil {
			appschn <- err
			return
		}
		appschn <- nil
	}()
	appserr := <-appschn
	taskserr := <-taskschn
	if appserr != nil {
		return appserr
	}
	if taskserr != nil {
		return taskserr
	}
	return nil
}

func syncApps(jsontasks *MarathonTasks, jsonapps *MarathonApps) {
	config.Lock()
	defer config.Unlock()
	config.Apps = make(map[string]App)
	for _, app := range jsonapps.Apps {
		for _, task := range jsontasks.Tasks {
			if task.AppId != app.Id {
				continue
			}
			// Lets skip tasks that does not expose any ports.
			if len(task.Ports) == 0 {
				continue
			}
			if len(app.HealthChecks) > 0 {
				if len(task.HealthCheckResults) == 0 {
					// this means tasks is being deployed but not yet monitored as alive. Assume down.
					continue
				}
				alive := true
				for _, health := range task.HealthCheckResults {
					// check if health check is alive
					if health.Alive == false {
						alive = false
					}
				}
				if alive != true {
					// at least one health check has failed. Assume down.
					continue
				}
			}
			if s, ok := config.Apps[app.Id]; ok {
				s.Tasks = append(s.Tasks, task.Host+":"+strconv.FormatInt(task.Ports[0], 10))
				config.Apps[app.Id] = s
			} else {
				var newapp = App{}
				newapp.Tasks = []string{task.Host + ":" + strconv.FormatInt(task.Ports[0], 10)}
				// Create a valid hostname of app id.
				if s, ok := app.Labels["subdomain"]; ok {
					newapp.Host = s
				} else if s, ok := app.Labels["moxy_subdomain"]; ok {
					// to be compatible with moxy
					newapp.Host = s
				} else {
					re := regexp.MustCompile("[^0-9a-z-]")
					newapp.Host = re.ReplaceAllLiteralString(app.Id, "")
				}
				for k, v := range config.Apps {
					if newapp.Host == v.Host {
						log.Printf("%s and %s share same subdomain '%s', ignoring %s.", k, app.Id, v.Host, app.Id)
						continue
					}
				}
				newapp.Labels = app.Labels
				newapp.Env = app.Env
				config.Apps[app.Id] = newapp
			}
		}

	}

}

func writeConf() error {
	t, err := template.New(filepath.Base(config.Nginx_template)).ParseFiles(config.Nginx_template)
	if err != nil {
		return err
	}
	f, err := os.Create(config.Nginx_config)
	defer f.Close()
	if err != nil {
		return err
	}
	err = t.Execute(f, config)
	if err != nil {
		return err
	}
	return nil
}

func checkTmpl() error {
	t, err := template.New(filepath.Base(config.Nginx_template)).ParseFiles(config.Nginx_template)
	if err != nil {
		return err
	}
	err = t.Execute(ioutil.Discard, config)
	if err != nil {
		return err
	}
	return nil
}

func checkConf() error {
	cmd := exec.Command(config.Nginx_cmd, "-c", config.Nginx_config, "-t")
	var stderr bytes.Buffer
	cmd.Stderr = &stderr
	err := cmd.Run() // will wait for command to return
	if err != nil {
		msg := fmt.Sprint(err) + ": " + stderr.String()
		errstd := errors.New(msg)
		return errstd
	}
	return nil
}

func reloadNginx() error {
	cmd := exec.Command(config.Nginx_cmd, "-s", "reload")
	var stderr bytes.Buffer
	cmd.Stderr = &stderr
	err := cmd.Run() // will wait for command to return
	if err != nil {
		msg := fmt.Sprint(err) + ": " + stderr.String()
		errstd := errors.New(msg)
		return errstd
	}
	return nil
}

func reload() error {
	jsontasks := MarathonTasks{}
	jsonapps := MarathonApps{}
	err := fetchApps(&jsontasks, &jsonapps)
	if err != nil {
		log.Println("Unable to sync from Marathon:", err)
		return err
	}
	syncApps(&jsontasks, &jsonapps)
	err = writeConf()
	if err != nil {
		log.Println("Unable to generate nginx config:", err)
		return err
	}
	err = reloadNginx()
	if err != nil {
		log.Println("Unable to reload nginx:", err)
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package channel

import (
	"encoding/hex"
	"errors"
	"log"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/codedust/go-tox"
	"github.com/xamino/tox-dynboot"
)

/*
Channel is a wrapper of the gotox wrapper that creates and manages the underlying Tox
instance.

TODO all callbacks will block, need to avoid that especially when user interaction is required
*/
type Channel struct {
	tox                *gotox.Tox
	callbacks          Callbacks
	wg                 sync.WaitGroup
	stop               chan bool
	transfers          map[uint32]*os.File
	transfersFilesizes map[uint32]uint64
}

/*
Create and starts a new tox channel that continously runs in the background
until this object is destroyed.
*/
func Create(name string, toxdata []byte, callbacks Callbacks) (*Channel, error) {
	if name == "" {
		return nil, errors.New("CreateChannel called with no name!")
	}
	var init bool
	var channel = &Channel{}
	var options *gotox.Options
	var err error

	// prepare for file transfers
	channel.transfers = make(map[uint32]*os.File)
	channel.transfersFilesizes = make(map[uint32]uint64)

	// this decides whether we are initiating a new connection or using an existing one
	if toxdata == nil {
		options = &gotox.Options{
			true, true,
			gotox.TOX_PROXY_TYPE_NONE, "127.0.0.1", 5555, 0, 0, 0,
			gotox.TOX_SAVEDATA_TYPE_NONE, nil}
		init = true
	} else {
		options = &gotox.Options{
			true, true,
			gotox.TOX_PROXY_TYPE_NONE, "127.0.0.1", 5555, 0, 0, 0,
			gotox.TOX_SAVEDATA_TYPE_TOX_SAVE, toxdata}
		init = false
	}
	channel.tox, err = gotox.New(options)
	if err != nil {
		return nil, err
	}
	if init {
		channel.tox.SelfSetName(name)
		channel.tox.SelfSetStatusMessage("Tin Peer")
	}
	err = channel.tox.SelfSetStatus(gotox.TOX_USERSTATUS_NONE)
	// Register our callbacks
	channel.tox.CallbackFriendRequest(channel.onFriendRequest)
	channel.tox.CallbackFriendMessage(channel.onFriendMessage)
	channel.tox.CallbackFileRecvControl(channel.onFileRecvControl)
	channel.tox.CallbackFileRecv(channel.onFileRecv)
	channel.tox.CallbackFileRecvChunk(channel.onFileRecvChunk)
	channel.tox.CallbackFileChunkRequest(channel.onFileChunkRequest)
	// some things must only be done if first start
	if init {
		// Bootstrap
		toxNode, err := toxdynboot.FetchFirstAlive(200 * time.Millisecond)
		if err != nil {
			return nil, err
		}
		err = channel.tox.Bootstrap(toxNode.IPv4, toxNode.Port, toxNode.PublicKey)
		if err != nil {
			return nil, err
		}
	}
	// register callbacks
	channel.callbacks = callbacks
	// now to run it:
	channel.wg.Add(1)
	channel.stop = make(chan bool, 1)
	go channel.run()
	return channel, nil
}

// --- public methods here ---

/*
Close shuts down the channel.
*/
func (channel *Channel) Close() {
	// send stop signal
	channel.stop <- false
	// wait for it to close
	channel.wg.Wait()
	// kill tox
	channel.tox.Kill()
}

/*
Address of the Tox instance.
*/
func (channel *Channel) Address() (string, error) {
	address, err := channel.tox.SelfGetAddress()
	if err != nil {
		return "", err
	}
	return strings.ToUpper(hex.EncodeToString(address)), nil
}

/*
ToxData returns the underlying current representation of the tox data. Can be
used to store a Tox instance to disk.
*/
func (channel *Channel) ToxData() ([]byte, error) {
	return channel.tox.GetSavedata()
}

/*
Send a message to the given peer address.
*/
func (channel *Channel) Send(address, message string) error {
	if ok, _ := channel.IsOnline(address); !ok {
		return errOffline
	}
	// find friend id to send to
	key, err := hex.DecodeString(address)
	if err != nil {
		return err
	}
	id, err := channel.tox.FriendByPublicKey(key)
	if err != nil {
		return err
	}
	// returns message ID but we currently don't use it
	_, err = channel.tox.FriendSendMessage(id, gotox.TOX_MESSAGE_TYPE_NORMAL, message)
	return err
}

/*
SendFile sends a file to the given address. NOTE: Will block until done!
*/
func (channel *Channel) SendFile(address string, data []byte) error {
	if ok, _ := channel.IsOnline(address); !ok {
		return errOffline
	}
	// find friend id to send to
	key, err := hex.DecodeString(address)
	if err != nil {
		return err
	}
	id, err := channel.tox.FriendByPublicKey(key)
	if err != nil {
		return err
	}
	err = channel.tox.FileControl(id, false, 5, gotox.TOX_FILE_CONTROL_RESUME, data)
	if err != nil {
		log.Println(err.Error())
	}
	return errors.New("not implemented")
}

/*
AcceptConnection accepts the given address as a connection partner.
*/
func (channel *Channel) AcceptConnection(address string) error {
	publicKey, err := hex.DecodeString(address)
	if err != nil {
		return err
	}
	// ignore friendnumber
	_, err = channel.tox.FriendAddNorequest(publicKey)
	return err
}

/*
RequestConnection sends a friend request to the given address with the sending
peer information as the message for bootstrapping.
*/
func (channel *Channel) RequestConnection(address, message string) error {
	publicKey, err := hex.DecodeString(address)
	if err != nil {
		return err
	}
	// send non blocking friend request
	_, err = channel.tox.FriendAdd(publicKey, message)
	return err
}

/*
IsOnline checks whether the given address is currently reachable.
*/
func (channel *Channel) IsOnline(address string) (bool, error) {
	publicKey, err := hex.DecodeString(address)
	if err != nil {
		return false, err
	}
	num, err := channel.tox.FriendByPublicKey(publicKey)
	if err != nil {
		return false, err
	}
	status, err := channel.tox.FriendGetConnectionStatus(num)
	if err != nil {
		return false, err
	}
	return status != gotox.TOX_CONNECTION_NONE, nil
}

/*
NameOf the key associated to the given address.
*/
func (channel *Channel) NameOf(address string) (string, error) {
	publicKey, err := hex.DecodeString(address)
	if err != nil {
		return "", err
	}
	num, err := channel.tox.FriendByPublicKey(publicKey)
	if err != nil {
		return "", err
	}
	name, err := channel.tox.FriendGetName(num)
	if err != nil {
		return "", err
	}
	return name, nil
}

// --- private methods here ---

/*
run is the background go routine method that keeps the Tox instance iterating
until Close() is called.
*/
func (channel *Channel) run() {
	for {
		temp, _ := channel.tox.IterationInterval()
		intervall := time.Duration(temp) * time.Millisecond
		select {
		case <-channel.stop:
			channel.wg.Done()
			return
		case <-time.Tick(intervall):
			err := channel.tox.Iterate()
			if err != nil {
				// TODO what do we do here? Can we cleanly close the channel and
				// catch the error further up?
				log.Println(err.Error())
			}
		} // select
	} // for
}

/*
addressOf given friend number.
*/
func (channel *Channel) addressOf(friendnumber uint32) (string, error) {
	publicKey, err := channel.tox.FriendGetPublickey(friendnumber)
	if err != nil {
		return "", errLostAddress
	}
	return hex.EncodeToString(publicKey), nil
}

/*
onFriendRequest calls the appropriate callback, wrapping it sanely for our purposes.
*/
func (channel *Channel) onFriendRequest(t *gotox.Tox, publicKey []byte, message string) {
	if channel.callbacks != nil {
		channel.callbacks.OnNewConnection(hex.EncodeToString(publicKey), message)
	} else {
		log.Println("Error: callbacks are nil!")
	}
}

/*
onFriendMessage calls the appropriate callback, wrapping it sanely for our purposes.
*/
func (channel *Channel) onFriendMessage(t *gotox.Tox, friendnumber uint32, messagetype gotox.ToxMessageType, message string) {
	/*TODO make sensible*/
	if messagetype == gotox.TOX_MESSAGE_TYPE_NORMAL {
		if channel.callbacks != nil {
			address, err := channel.addressOf(friendnumber)
			if err != nil {
				log.Println(err.Error())
				address = illegalAddress
			}
			channel.callbacks.OnMessage(address, message)
		} else {
			log.Println("Error: callbacks are nil!")
		}
	}
}

/*
TODO implement and comment
*/
func (channel *Channel) onFileRecvControl(t *gotox.Tox, friendnumber uint32, filenumber uint32, fileControl gotox.ToxFileControl) {
	log.Printf("File control: %#+v\n", fileControl)
	if fileControl == gotox.TOX_FILE_CONTROL_CANCEL {
		log.Println("Transfer was canceled!")
		// free resources
		delete(channel.transfers, filenumber)
		delete(channel.transfersFilesizes, filenumber)
	}
}

/*
TODO implement and comment
*/
func (channel *Channel) onFileRecv(t *gotox.Tox, friendnumber uint32, filenumber uint32, kind gotox.ToxFileKind, filesize uint64, filename string) {
	address, err := channel.addressOf(friendnumber)
	if err != nil {
		log.Println(err.Error())
		address = illegalAddress
	}
	// use callback to check whether to accept from Tinzenite
	accept, path := channel.callbacks.OnAllowFile(address, filename)
	if !accept {
		return
	}
	// accept file send request if we come to here
	t.FileControl(friendnumber, true, filenumber, gotox.TOX_FILE_CONTROL_RESUME, nil)
	// create file at correct location
	/*TODO how are pause & resume handled?*/
	f, _ := os.Create(path)
	// Append f to the map[uint8]*os.File
	channel.transfers[filenumber] = f
	channel.transfersFilesizes[filenumber] = filesize
}

/*
TODO implement and comment
*/
func (channel *Channel) onFileRecvChunk(t *gotox.Tox, friendnumber uint32, filenumber uint32, position uint64, data []byte) {
	// Write data to the hopefully valid *File handle
	if f, exists := channel.transfers[filenumber]; exists {
		f.WriteAt(data, (int64)(position))
	} else {
		log.Println("File doesn't seem to exist!")
		return
	}
	// this means the file has been completey received
	if position == channel.transfersFilesizes[filenumber] {
		// ensure file is written
		f := channel.transfers[filenumber]
		err := f.Sync()
		if err != nil {
			log.Println("Disk error: " + err.Error())
			return
		}
		pathelements := strings.Split(f.Name(), "/")
		f.Close()
		// free resources
		delete(channel.transfers, filenumber)
		delete(channel.transfersFilesizes, filenumber)
		// callback with file name / identification
		address, _ := channel.addressOf(friendnumber)
		name := pathelements[len(pathelements)-1]
		path := strings.Join(pathelements, "/")
		channel.callbacks.OnFileReceived(address, path, name)
	}
}

/*
TODO implement and comment
*/
func (channel *Channel) onFileChunkRequest(tox *gotox.Tox, friendnumber uint32, filenumber uint32, position uint64, length uint64) {
	log.Println("Received chunk request!")
}
<commit_msg>implemented file sending<commit_after>package channel

import (
	"encoding/hex"
	"errors"
	"fmt"
	"log"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/codedust/go-tox"
	"github.com/xamino/tox-dynboot"
)

/*
Channel is a wrapper of the gotox wrapper that creates and manages the underlying Tox
instance.

TODO all callbacks will block, need to avoid that especially when user interaction is required
*/
type Channel struct {
	tox                *gotox.Tox
	callbacks          Callbacks
	wg                 sync.WaitGroup
	stop               chan bool
	transfers          map[uint32]*os.File
	transfersFilesizes map[uint32]uint64
}

/*
Create and starts a new tox channel that continously runs in the background
until this object is destroyed.
*/
func Create(name string, toxdata []byte, callbacks Callbacks) (*Channel, error) {
	if name == "" {
		return nil, errors.New("CreateChannel called with no name!")
	}
	var init bool
	var channel = &Channel{}
	var options *gotox.Options
	var err error

	// prepare for file transfers
	channel.transfers = make(map[uint32]*os.File)
	channel.transfersFilesizes = make(map[uint32]uint64)

	// this decides whether we are initiating a new connection or using an existing one
	if toxdata == nil {
		options = &gotox.Options{
			true, true,
			gotox.TOX_PROXY_TYPE_NONE, "127.0.0.1", 5555, 0, 0, 0,
			gotox.TOX_SAVEDATA_TYPE_NONE, nil}
		init = true
	} else {
		options = &gotox.Options{
			true, true,
			gotox.TOX_PROXY_TYPE_NONE, "127.0.0.1", 5555, 0, 0, 0,
			gotox.TOX_SAVEDATA_TYPE_TOX_SAVE, toxdata}
		init = false
	}
	channel.tox, err = gotox.New(options)
	if err != nil {
		return nil, err
	}
	if init {
		channel.tox.SelfSetName(name)
		channel.tox.SelfSetStatusMessage("Tin Peer")
	}
	err = channel.tox.SelfSetStatus(gotox.TOX_USERSTATUS_NONE)
	// Register our callbacks
	channel.tox.CallbackFriendRequest(channel.onFriendRequest)
	channel.tox.CallbackFriendMessage(channel.onFriendMessage)
	channel.tox.CallbackFileRecvControl(channel.onFileRecvControl)
	channel.tox.CallbackFileRecv(channel.onFileRecv)
	channel.tox.CallbackFileRecvChunk(channel.onFileRecvChunk)
	channel.tox.CallbackFileChunkRequest(channel.onFileChunkRequest)
	// some things must only be done if first start
	if init {
		// Bootstrap
		toxNode, err := toxdynboot.FetchFirstAlive(200 * time.Millisecond)
		if err != nil {
			return nil, err
		}
		err = channel.tox.Bootstrap(toxNode.IPv4, toxNode.Port, toxNode.PublicKey)
		if err != nil {
			return nil, err
		}
	}
	// register callbacks
	channel.callbacks = callbacks
	// now to run it:
	channel.wg.Add(1)
	channel.stop = make(chan bool, 1)
	go channel.run()
	return channel, nil
}

// --- public methods here ---

/*
Close shuts down the channel.
*/
func (channel *Channel) Close() {
	// send stop signal
	channel.stop <- false
	// wait for it to close
	channel.wg.Wait()
	// kill tox
	channel.tox.Kill()
}

/*
Address of the Tox instance.
*/
func (channel *Channel) Address() (string, error) {
	address, err := channel.tox.SelfGetAddress()
	if err != nil {
		return "", err
	}
	return strings.ToUpper(hex.EncodeToString(address)), nil
}

/*
ToxData returns the underlying current representation of the tox data. Can be
used to store a Tox instance to disk.
*/
func (channel *Channel) ToxData() ([]byte, error) {
	return channel.tox.GetSavedata()
}

/*
Send a message to the given peer address.
*/
func (channel *Channel) Send(address, message string) error {
	if ok, _ := channel.IsOnline(address); !ok {
		return errOffline
	}
	// find friend id to send to
	key, err := hex.DecodeString(address)
	if err != nil {
		return err
	}
	id, err := channel.tox.FriendByPublicKey(key)
	if err != nil {
		return err
	}
	// returns message ID but we currently don't use it
	_, err = channel.tox.FriendSendMessage(id, gotox.TOX_MESSAGE_TYPE_NORMAL, message)
	return err
}

/*
SendFile sends a file to the given address. NOTE: Will block until done!
*/
func (channel *Channel) SendFile(address string, path string, identification string) error {
	if ok, _ := channel.IsOnline(address); !ok {
		return errOffline
	}
	// find friend id to send to
	key, err := hex.DecodeString(address)
	if err != nil {
		return err
	}
	id, err := channel.tox.FriendByPublicKey(key)
	if err != nil {
		return err
	}
	// get file
	file, err := os.Open(path)
	if err != nil {
		return err
	}
	// do NOT close file! must be done elsewhere since we may need it later
	// get file size
	stat, err := os.Lstat(path)
	if err != nil {
		file.Close()
		return err
	}
	size := uint64(stat.Size())
	// prepare send (file will be transmitted via filechunk)
	fileNumber, err := channel.tox.FileSend(id, gotox.TOX_FILE_KIND_DATA, size, nil, identification)
	if err != nil {
		file.Close()
		return err
	}
	channel.transfers[fileNumber] = file
	channel.transfersFilesizes[fileNumber] = size
	return nil
}

/*
AcceptConnection accepts the given address as a connection partner.
*/
func (channel *Channel) AcceptConnection(address string) error {
	publicKey, err := hex.DecodeString(address)
	if err != nil {
		return err
	}
	// ignore friendnumber
	_, err = channel.tox.FriendAddNorequest(publicKey)
	return err
}

/*
RequestConnection sends a friend request to the given address with the sending
peer information as the message for bootstrapping.
*/
func (channel *Channel) RequestConnection(address, message string) error {
	publicKey, err := hex.DecodeString(address)
	if err != nil {
		return err
	}
	// send non blocking friend request
	_, err = channel.tox.FriendAdd(publicKey, message)
	return err
}

/*
IsOnline checks whether the given address is currently reachable.
*/
func (channel *Channel) IsOnline(address string) (bool, error) {
	publicKey, err := hex.DecodeString(address)
	if err != nil {
		return false, err
	}
	num, err := channel.tox.FriendByPublicKey(publicKey)
	if err != nil {
		return false, err
	}
	status, err := channel.tox.FriendGetConnectionStatus(num)
	if err != nil {
		return false, err
	}
	return status != gotox.TOX_CONNECTION_NONE, nil
}

/*
NameOf the key associated to the given address.
*/
func (channel *Channel) NameOf(address string) (string, error) {
	publicKey, err := hex.DecodeString(address)
	if err != nil {
		return "", err
	}
	num, err := channel.tox.FriendByPublicKey(publicKey)
	if err != nil {
		return "", err
	}
	name, err := channel.tox.FriendGetName(num)
	if err != nil {
		return "", err
	}
	return name, nil
}

// --- private methods here ---

/*
run is the background go routine method that keeps the Tox instance iterating
until Close() is called.
*/
func (channel *Channel) run() {
	for {
		temp, _ := channel.tox.IterationInterval()
		intervall := time.Duration(temp) * time.Millisecond
		select {
		case <-channel.stop:
			channel.wg.Done()
			return
		case <-time.Tick(intervall):
			err := channel.tox.Iterate()
			if err != nil {
				// TODO what do we do here? Can we cleanly close the channel and
				// catch the error further up?
				log.Println(err.Error())
			}
		} // select
	} // for
}

/*
addressOf given friend number.
*/
func (channel *Channel) addressOf(friendnumber uint32) (string, error) {
	publicKey, err := channel.tox.FriendGetPublickey(friendnumber)
	if err != nil {
		return "", errLostAddress
	}
	return hex.EncodeToString(publicKey), nil
}

/*
onFriendRequest calls the appropriate callback, wrapping it sanely for our purposes.
*/
func (channel *Channel) onFriendRequest(_ *gotox.Tox, publicKey []byte, message string) {
	if channel.callbacks != nil {
		channel.callbacks.OnNewConnection(hex.EncodeToString(publicKey), message)
	} else {
		log.Println("Error: callbacks are nil!")
	}
}

/*
onFriendMessage calls the appropriate callback, wrapping it sanely for our purposes.
*/
func (channel *Channel) onFriendMessage(_ *gotox.Tox, friendnumber uint32, messagetype gotox.ToxMessageType, message string) {
	/*TODO make sensible*/
	if messagetype == gotox.TOX_MESSAGE_TYPE_NORMAL {
		if channel.callbacks != nil {
			address, err := channel.addressOf(friendnumber)
			if err != nil {
				log.Println(err.Error())
				address = illegalAddress
			}
			channel.callbacks.OnMessage(address, message)
		} else {
			log.Println("Error: callbacks are nil!")
		}
	}
}

/*
TODO implement and comment
*/
func (channel *Channel) onFileRecvControl(_ *gotox.Tox, friendnumber uint32, filenumber uint32, fileControl gotox.ToxFileControl) {
	// we only explicitely need to handle cancel because we then have to remove resources
	if fileControl == gotox.TOX_FILE_CONTROL_CANCEL {
		log.Println("Transfer was canceled!")
		// free resources
		channel.transfers[filenumber].Close()
		delete(channel.transfers, filenumber)
		delete(channel.transfersFilesizes, filenumber)
	}
}

/*
TODO implement and comment
*/
func (channel *Channel) onFileRecv(_ *gotox.Tox, friendnumber uint32, filenumber uint32, kind gotox.ToxFileKind, filesize uint64, filename string) {
	address, err := channel.addressOf(friendnumber)
	if err != nil {
		log.Println(err.Error())
		address = illegalAddress
	}
	// use callback to check whether to accept from Tinzenite
	accept, path := channel.callbacks.OnAllowFile(address, filename)
	if !accept {
		return
	}
	// accept file send request if we come to here
	channel.tox.FileControl(friendnumber, true, filenumber, gotox.TOX_FILE_CONTROL_RESUME, nil)
	// create file at correct location
	/*TODO how are pause & resume handled?*/
	f, _ := os.Create(path)
	// Append f to the map[uint8]*os.File
	channel.transfers[filenumber] = f
	channel.transfersFilesizes[filenumber] = filesize
}

/*
onFileRecvChunk is called when a chunk of a file is received. Writes the data to
the correct file.
*/
func (channel *Channel) onFileRecvChunk(_ *gotox.Tox, friendnumber uint32, filenumber uint32, position uint64, data []byte) {
	// Write data to the hopefully valid *File handle
	if f, exists := channel.transfers[filenumber]; exists {
		f.WriteAt(data, (int64)(position))
	} else {
		log.Println("File doesn't seem to exist!")
		return
	}
	// this means the file has been completey received
	if position == channel.transfersFilesizes[filenumber] {
		// ensure file is written
		f := channel.transfers[filenumber]
		err := f.Sync()
		if err != nil {
			log.Println("Disk error: " + err.Error())
			return
		}
		pathelements := strings.Split(f.Name(), "/")
		f.Close()
		// free resources
		delete(channel.transfers, filenumber)
		delete(channel.transfersFilesizes, filenumber)
		// callback with file name / identification
		address, _ := channel.addressOf(friendnumber)
		name := pathelements[len(pathelements)-1]
		path := strings.Join(pathelements, "/")
		channel.callbacks.OnFileReceived(address, path, name)
	}
}

/*
onFileChunkRequest is called when a chunk must be sent.
*/
func (channel *Channel) onFileChunkRequest(_ *gotox.Tox, friendNumber uint32, fileNumber uint32, position uint64, length uint64) {
	size, ok := channel.transfersFilesizes[fileNumber]
	// sanity check
	if !ok {
		log.Println("Failed to read from channel.transfers!")
		return
	}
	// recalculate length if near end of file
	if length+position > size {
		length = size - position
	}
	// get bytes to send
	data := make([]byte, length)
	_, err := channel.transfers[fileNumber].ReadAt(data, int64(position))
	if err != nil {
		fmt.Println("Error reading file: " + err.Error())
		return
	}
	// send
	err = channel.tox.FileSendChunk(friendNumber, fileNumber, position, data)
	if err != nil {
		log.Println("File send error: " + err.Error())
	}
}
<|endoftext|>


<commit_before>package resources

import (
	"encoding/json"
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudcontrolapi"
	"github.com/google/uuid"
	"github.com/pkg/errors"
	"github.com/rebuy-de/aws-nuke/pkg/types"
	"github.com/sirupsen/logrus"
)

func init() {
	// It is required to manually define Cloud Control API targets, because
	// existing configs that already filter old-style resources could break,
	// because the resource is also available via Cloud Control.
	//
	// To get an overview of available cloud control resource types run this
	// command in the repo root:
	//     go run ./dev/list-cloudcontrol
	registerCloudControl("AWS::AppFlow::ConnectorProfile")
	registerCloudControl("AWS::AppFlow::Flow")
	registerCloudControl("AWS::AppRunner::Service")
	registerCloudControl("AWS::ApplicationInsights::Application")
	registerCloudControl("AWS::Athena::DataCatalog")
	registerCloudControl("AWS::Backup::Framework")
	registerCloudControl("AWS::MWAA::Environment")
	registerCloudControl("AWS::Synthetics::Canary")
	registerCloudControl("AWS::Timestream::Database")
	registerCloudControl("AWS::Timestream::ScheduledQuery")
	registerCloudControl("AWS::Timestream::Table")
	registerCloudControl("AWS::Transfer::Workflow")
}

func NewListCloudControlResource(typeName string) func(*session.Session) ([]Resource, error) {
	return func(sess *session.Session) ([]Resource, error) {
		svc := cloudcontrolapi.New(sess)

		params := &cloudcontrolapi.ListResourcesInput{
			TypeName: aws.String(typeName),
		}
		resources := make([]Resource, 0)
		err := svc.ListResourcesPages(params, func(page *cloudcontrolapi.ListResourcesOutput, lastPage bool) bool {
			for _, desc := range page.ResourceDescriptions {
				identifier := aws.StringValue(desc.Identifier)

				properties, err := cloudControlParseProperties(aws.StringValue(desc.Properties))
				if err != nil {
					logrus.
						WithError(errors.WithStack(err)).
						WithField("type-name", typeName).
						WithField("identifier", identifier).
						Error("failed to parse cloud control properties")
					continue
				}
				properties = properties.Set("Identifier", identifier)
				resources = append(resources, &CloudControlResource{
					svc:         svc,
					clientToken: uuid.New().String(),
					typeName:    typeName,
					identifier:  identifier,
					properties:  properties,
				})
			}

			return true
		})

		if err != nil {
			return nil, err
		}

		return resources, nil
	}
}

func cloudControlParseProperties(payload string) (types.Properties, error) {
	// Warning: The implementation of this function is not very straighforward,
	// because the aws-nuke filter functions expect a very rigid structure and
	// the properties from the Cloud Control API are very dynamic.

	propMap := map[string]interface{}{}
	err := json.Unmarshal([]byte(payload), &propMap)
	if err != nil {
		return nil, err
	}
	properties := types.NewProperties()
	for name, value := range propMap {
		switch v := value.(type) {
		case string:
			properties = properties.Set(name, v)
		case []interface{}:
			for _, value2 := range v {
				switch v2 := value2.(type) {
				case string:
					properties.Set(
						fmt.Sprintf("%s.[%q]", name, v2),
						true,
					)
				case map[string]interface{}:
					if len(v2) == 2 && v2["Key"] != nil && v2["Value"] != nil {
						properties.Set(
							fmt.Sprintf("%s.[%q]", name, v2["Key"]),
							v2["Value"],
						)
					} else {
						logrus.
							WithField("value", fmt.Sprintf("%q", v)).
							Debugf("nested cloud control property type []%T is not supported", value)
					}
				default:
					logrus.
						WithField("value", fmt.Sprintf("%q", v)).
						Debugf("nested cloud control property type []%T is not supported", value)
				}
			}

		default:
			// We cannot rely on the default handling of
			// properties.Set, because it would fall back to
			// fmt.Sprintf. Since the cloud control properties are
			// nested it would create properties that are not
			// suitable for filtering. Therefore we have to
			// implemented more sophisticated parsing.
			logrus.
				WithField("value", fmt.Sprintf("%q", v)).
				Debugf("cloud control property type %T is not supported", v)
		}
	}

	return properties, nil
}

type CloudControlResource struct {
	svc         *cloudcontrolapi.CloudControlApi
	clientToken string
	typeName    string
	identifier  string
	properties  types.Properties
}

func (r *CloudControlResource) String() string {
	return r.identifier
}

func (i *CloudControlResource) Remove() error {
	_, err := i.svc.DeleteResource(&cloudcontrolapi.DeleteResourceInput{
		ClientToken: &i.clientToken,
		Identifier:  &i.identifier,
		TypeName:    &i.typeName,
	})
	return err
}

func (r *CloudControlResource) Properties() types.Properties {
	return r.properties
}
<commit_msg>disable AWS::Athena::DataCatalog (#768)<commit_after>package resources

import (
	"encoding/json"
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudcontrolapi"
	"github.com/google/uuid"
	"github.com/pkg/errors"
	"github.com/rebuy-de/aws-nuke/pkg/types"
	"github.com/sirupsen/logrus"
)

func init() {
	// It is required to manually define Cloud Control API targets, because
	// existing configs that already filter old-style resources could break,
	// because the resource is also available via Cloud Control.
	//
	// To get an overview of available cloud control resource types run this
	// command in the repo root:
	//     go run ./dev/list-cloudcontrol
	registerCloudControl("AWS::AppFlow::ConnectorProfile")
	registerCloudControl("AWS::AppFlow::Flow")
	registerCloudControl("AWS::AppRunner::Service")
	registerCloudControl("AWS::ApplicationInsights::Application")
	registerCloudControl("AWS::Backup::Framework")
	registerCloudControl("AWS::MWAA::Environment")
	registerCloudControl("AWS::Synthetics::Canary")
	registerCloudControl("AWS::Timestream::Database")
	registerCloudControl("AWS::Timestream::ScheduledQuery")
	registerCloudControl("AWS::Timestream::Table")
	registerCloudControl("AWS::Transfer::Workflow")
}

func NewListCloudControlResource(typeName string) func(*session.Session) ([]Resource, error) {
	return func(sess *session.Session) ([]Resource, error) {
		svc := cloudcontrolapi.New(sess)

		params := &cloudcontrolapi.ListResourcesInput{
			TypeName: aws.String(typeName),
		}
		resources := make([]Resource, 0)
		err := svc.ListResourcesPages(params, func(page *cloudcontrolapi.ListResourcesOutput, lastPage bool) bool {
			for _, desc := range page.ResourceDescriptions {
				identifier := aws.StringValue(desc.Identifier)

				properties, err := cloudControlParseProperties(aws.StringValue(desc.Properties))
				if err != nil {
					logrus.
						WithError(errors.WithStack(err)).
						WithField("type-name", typeName).
						WithField("identifier", identifier).
						Error("failed to parse cloud control properties")
					continue
				}
				properties = properties.Set("Identifier", identifier)
				resources = append(resources, &CloudControlResource{
					svc:         svc,
					clientToken: uuid.New().String(),
					typeName:    typeName,
					identifier:  identifier,
					properties:  properties,
				})
			}

			return true
		})

		if err != nil {
			return nil, err
		}

		return resources, nil
	}
}

func cloudControlParseProperties(payload string) (types.Properties, error) {
	// Warning: The implementation of this function is not very straighforward,
	// because the aws-nuke filter functions expect a very rigid structure and
	// the properties from the Cloud Control API are very dynamic.

	propMap := map[string]interface{}{}
	err := json.Unmarshal([]byte(payload), &propMap)
	if err != nil {
		return nil, err
	}
	properties := types.NewProperties()
	for name, value := range propMap {
		switch v := value.(type) {
		case string:
			properties = properties.Set(name, v)
		case []interface{}:
			for _, value2 := range v {
				switch v2 := value2.(type) {
				case string:
					properties.Set(
						fmt.Sprintf("%s.[%q]", name, v2),
						true,
					)
				case map[string]interface{}:
					if len(v2) == 2 && v2["Key"] != nil && v2["Value"] != nil {
						properties.Set(
							fmt.Sprintf("%s.[%q]", name, v2["Key"]),
							v2["Value"],
						)
					} else {
						logrus.
							WithField("value", fmt.Sprintf("%q", v)).
							Debugf("nested cloud control property type []%T is not supported", value)
					}
				default:
					logrus.
						WithField("value", fmt.Sprintf("%q", v)).
						Debugf("nested cloud control property type []%T is not supported", value)
				}
			}

		default:
			// We cannot rely on the default handling of
			// properties.Set, because it would fall back to
			// fmt.Sprintf. Since the cloud control properties are
			// nested it would create properties that are not
			// suitable for filtering. Therefore we have to
			// implemented more sophisticated parsing.
			logrus.
				WithField("value", fmt.Sprintf("%q", v)).
				Debugf("cloud control property type %T is not supported", v)
		}
	}

	return properties, nil
}

type CloudControlResource struct {
	svc         *cloudcontrolapi.CloudControlApi
	clientToken string
	typeName    string
	identifier  string
	properties  types.Properties
}

func (r *CloudControlResource) String() string {
	return r.identifier
}

func (i *CloudControlResource) Remove() error {
	_, err := i.svc.DeleteResource(&cloudcontrolapi.DeleteResourceInput{
		ClientToken: &i.clientToken,
		Identifier:  &i.identifier,
		TypeName:    &i.typeName,
	})
	return err
}

func (r *CloudControlResource) Properties() types.Properties {
	return r.properties
}
<|endoftext|>


<commit_before>// Implement Slack notifications.
package notification

import (
	"bytes"
	"encoding/json"
	"leeroy/config"
	"leeroy/logging"
	"log"
	"net/http"
)

type slackPayload struct {
	Channel  string `json:"channel"`
	Username string `json:"username"`
	Text     string `json:"text"`
}

// Send a notification to Slack
func slack(c *config.Config, j *logging.Job) {
	message, err := buildSlack(c, j)

	_, err = http.Post(
		c.SlackEndpoint,
		"application/json",
		bytes.NewReader(message),
	)

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

// Build the payload to send to Slack.
func buildSlack(c *config.Config, j *logging.Job) ([]byte, error) {
	payload := slackPayload{
		Channel:  c.SlackChannel,
		Username: "CI",
	}

	message := "Repo: " + j.URL + " Branch: " + j.Branch
	message = message + " Pushed by " + j.Name + " <" + j.Email + "> "

	if j.Success() == true {
		message = message + "build was successful"
	} else {
		message = message + "build failed"
	}

	payload.Text = message

	marsh, err := json.Marshal(payload)

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

	return marsh, err
}
<commit_msg>refactor notification/slack<commit_after>// Implement Slack notifications.
package notification

import (
	"bytes"
	"encoding/json"
	"fmt"
	"leeroy/config"
	"leeroy/logging"
	"log"
	"net/http"
)

type slackPayload struct {
	Channel  string `json:"channel"`
	Username string `json:"username"`
	Text     string `json:"text"`
}

// Send a notification to Slack
func slack(c *config.Config, j *logging.Job) {
	m, err := buildSlack(c, j)

	_, err = http.Post(
		c.SlackEndpoint,
		"application/json",
		bytes.NewReader(m),
	)

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

// Build the payload to send to Slack.
func buildSlack(c *config.Config, j *logging.Job) ([]byte, error) {
	p := slackPayload{
		Channel:  c.SlackChannel,
		Username: "CI",
	}

	m := fmt.Sprintf(
		"Repo: %s - %s by %s <%s> -> %s\nBuild: %s",
		j.URL,
		j.Branch,
		j.Name,
		j.Email,
		j.Status(),
		j.StatusURL(c.URL),
	)

	p.Text = m

	marsh, err := json.Marshal(p)

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

	return marsh, err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/smtp"
)

type Notifier interface {
	SendProblem(problem Problem) error
}

type EmailNotifier struct {
	Contacts []*EmailContact
	Relay    SMTPRelay
}

func (n *EmailNotifier) SendProblem(problem Problem) error {
	content := "A Problem occured: " + problem.Description + "\r\n"
	if problem.ReplicaSet != nil {
		content += fmt.Sprintf("Replica Set id: %d \r\n", *problem.ReplicaSet)
	}
	if problem.Slave != nil {
		content += fmt.Sprintf("Slave id: %d \r\n", *problem.Slave)
	}
	content += "Detailed Description: " + problem.LongDescription + "\r\n"
	subject := "Subject: [MAMID] Problem: " + problem.Description
	msg := []byte("From: " + n.Relay.MailFrom + "\r\n" +
		subject + "\r\n" +
		content)
	return n.sendMailToContacts(msg)
}

func (n *EmailNotifier) sendMailToContacts(msg []byte) error {
	var to []string
	for i := 0; i < len(n.Contacts); i++ {
		to = append(to, n.Contacts[i].Address)
	}
	err := smtp.SendMail(
		n.Relay.Hostname,
		nil,
		n.Relay.MailFrom,
		to,
		msg)
	return err
}
<commit_msg>ADD: notifier: To: header<commit_after>package main

import (
	"fmt"
	"net/smtp"
)

type Notifier interface {
	SendProblem(problem Problem) error
}

type EmailNotifier struct {
	Contacts []*EmailContact
	Relay    SMTPRelay
}

func (n *EmailNotifier) SendProblem(problem Problem) error {
	content := "A Problem occured: " + problem.Description + "\r\n"
	if problem.ReplicaSet != nil {
		content += fmt.Sprintf("Replica Set id: %d \r\n", *problem.ReplicaSet)
	}
	if problem.Slave != nil {
		content += fmt.Sprintf("Slave id: %d \r\n", *problem.Slave)
	}
	content += "Detailed Description: " + problem.LongDescription + "\r\n"
	subject := "Subject: [MAMID] Problem: " + problem.Description
	msg := "From: " + n.Relay.MailFrom + "\r\n" +
		subject + "\r\n" +
		content
	return n.sendMailToContacts(msg)
}

func (n *EmailNotifier) sendMailToContacts(msg string) error {
	for i := 0; i < len(n.Contacts); i++ {
		err := smtp.SendMail(
			n.Relay.Hostname,
			nil,
			n.Relay.MailFrom,
			[]string{n.Contacts[i].Address},
			[]byte("To: "+n.Contacts[i].Address+"\r\n"+msg))
		if err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Christian Saide <Supernomad>
// Licensed under the MPL-2.0, for details see https://github.com/Supernomad/quantum/blob/master/LICENSE

package socket

import (
	"math/rand"
	"net"
	"syscall"
	"testing"

	"github.com/Supernomad/quantum/common"
)

func benchmarkWrite(sock Socket, payload *common.Payload, queue int, b *testing.B) {
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		if !sock.Write(payload, queue) {
			b.Fatal("Failed to write")
		}
	}
}

func BenchmarkWrite(b *testing.B) {
	addr := net.ParseIP("127.0.0.1")
	paddr := net.ParseIP("127.0.0.2")

	sa := &syscall.SockaddrInet4{Port: 1099}
	copy(sa.Addr[:], addr[:])

	payloadAddr := &syscall.SockaddrInet4{Port: 1099}
	copy(payloadAddr.Addr[:], paddr[:])

	cfg := &common.Config{
		NumWorkers: 1,
		ListenAddr: sa,
	}
	sock, err := New(UDPSocket, cfg)
	if err != nil {
		b.Fatal(err)
	}

	buf := make([]byte, common.MaxPacketLength)
	rand.Read(buf)

	payload := common.NewTunPayload(buf, common.MTU)
	payload.Sockaddr = payloadAddr
	benchmarkWrite(sock, payload, 0, b)
}
<commit_msg>Added unit tests for the socket module<commit_after>// Copyright (c) 2016 Christian Saide <Supernomad>
// Licensed under the MPL-2.0, for details see https://github.com/Supernomad/quantum/blob/master/LICENSE

package socket

import (
	"math/rand"
	"net"
	"syscall"
	"testing"

	"github.com/Supernomad/quantum/common"
)

func benchmarkWrite(sock Socket, payload *common.Payload, queue int, b *testing.B) {
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		if !sock.Write(payload, queue) {
			b.Fatal("Failed to write")
		}
	}
}

func BenchmarkWrite(b *testing.B) {
	addr := net.ParseIP("127.0.0.1")
	paddr := net.ParseIP("127.0.0.2")

	sa := &syscall.SockaddrInet4{Port: 1099}
	copy(sa.Addr[:], addr[:])

	payloadAddr := &syscall.SockaddrInet4{Port: 1099}
	copy(payloadAddr.Addr[:], paddr[:])

	cfg := &common.Config{
		NumWorkers: 1,
		ListenAddr: sa,
	}
	sock, err := New(UDPSocket, cfg)
	if err != nil {
		b.Fatal(err)
	}

	buf := make([]byte, common.MaxPacketLength)
	rand.Read(buf)

	payload := common.NewTunPayload(buf, common.MTU)
	payload.Sockaddr = payloadAddr
	benchmarkWrite(sock, payload, 0, b)
}

func TestMock(t *testing.T) {
	mock, _ := New(MOCKSocket, &common.Config{})
	buf := make([]byte, common.MaxPacketLength)

	payload, ok := mock.Read(buf, 0)
	if payload == nil || !ok {
		t.Fatal("Mock Read should always return a valid payload and nil error.")
	}

	if !mock.Write(payload, 0) {
		t.Fatal("Mock Write should always return true.")
	}

	if mock.Queues() != nil {
		t.Fatal("Mock Queues should always return nil.")
	}

	if mock.Close() != nil {
		t.Fatal("Mock Close should always return nil.")
	}
}

func TestUDP(t *testing.T) {
	addr := net.ParseIP("127.0.0.1")
	paddr := net.ParseIP("127.0.0.1")

	sa := &syscall.SockaddrInet4{Port: 1099}
	copy(sa.Addr[:], addr[:])

	payloadAddr := &syscall.SockaddrInet4{Port: 1099}
	copy(payloadAddr.Addr[:], paddr[:])

	cfg := &common.Config{
		NumWorkers: 1,
		ListenAddr: sa,
	}
	sock, err := New(UDPSocket, cfg)
	if err != nil {
		t.Fatal(err)
	}

	buf := make([]byte, common.MaxPacketLength)
	rand.Read(buf)

	payload := common.NewTunPayload(buf, common.MTU)
	payload.Sockaddr = payloadAddr
	if !sock.Write(payload, 0) {
		t.Fatal("Sock Write failed")
	}

	payload, ok := sock.Read(buf, 0)
	if !ok {
		t.Fatal("Sock Read failed")
	}

	if queues := sock.Queues(); len(queues) != 1 {
		t.Fatal("Sock Queues didn't return correctly")
	}

	if err := sock.Close(); err != nil {
		t.Fatal("Sock Close errored")
	}
}
<|endoftext|>


<commit_before>// This source file is part of the Packet Guardian project.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// +build dbmysql dball

package common

import (
	"database/sql"
	"errors"
	"fmt"
	"strings"

	"github.com/go-sql-driver/mysql" // MySQL driver
)

func init() {
	dbInits["mysql"] = func(d *DatabaseAccessor, c *Config) error {
		var err error
		if c.Database.Port == 0 {
			c.Database.Port = 3306
		}
		mc := &mysql.Config{
			User:              c.Database.Username,
			Passwd:            c.Database.Password,
			Addr:              fmt.Sprintf("%s:%d", c.Database.Address, c.Database.Port),
			DBName:            c.Database.Name,
			Strict:            true,
			InterpolateParams: true,
		}
		d.DB, err = sql.Open("mysql", mc.FormatDSN())
		if err != nil {
			return err
		}

		err = d.DB.Ping()
		if err != nil {
			return err
		}

		d.Driver = "mysql"

		// Check the SQL mode, the user is responsible for setting it
		row := d.DB.QueryRow(`SELECT @@GLOBAL.sql_mode`)

		mode := ""
		if err := row.Scan(&mode); err != nil {
			return err
		}

		ansiOK := strings.Contains(mode, "ANSI")
		tradOK := strings.Contains(mode, "TRADITIONAL")

		if !ansiOK || !tradOK {
			return errors.New("MySQL must be in ANSI,TRADITIONAL mode. Please set the global mode or edit the my.cnf file to enable ANSI,TRADITIONAL sql_mode.")
		}
		return err
	}
}
<commit_msg>Don't enforce traditional sql_mode<commit_after>// This source file is part of the Packet Guardian project.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// +build dbmysql dball

package common

import (
	"database/sql"
	"errors"
	"fmt"
	"strings"

	"github.com/go-sql-driver/mysql" // MySQL driver
)

func init() {
	dbInits["mysql"] = func(d *DatabaseAccessor, c *Config) error {
		var err error
		if c.Database.Port == 0 {
			c.Database.Port = 3306
		}
		mc := &mysql.Config{
			User:              c.Database.Username,
			Passwd:            c.Database.Password,
			Addr:              fmt.Sprintf("%s:%d", c.Database.Address, c.Database.Port),
			DBName:            c.Database.Name,
			Strict:            true,
			InterpolateParams: true,
		}
		d.DB, err = sql.Open("mysql", mc.FormatDSN())
		if err != nil {
			return err
		}

		err = d.DB.Ping()
		if err != nil {
			return err
		}

		d.Driver = "mysql"

		// Check the SQL mode, the user is responsible for setting it
		row := d.DB.QueryRow(`SELECT @@GLOBAL.sql_mode`)

		mode := ""
		if err := row.Scan(&mode); err != nil {
			return err
		}

		ansiOK := strings.Contains(mode, "ANSI")

		if !ansiOK {
			return errors.New("MySQL must be in ANSI mode. Please set the global mode or edit the my.cnf file to enable ANSI sql_mode.")
		}
		return err
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"

	"github.com/thrasher-/gocryptotrader/common"
	"github.com/thrasher-/gocryptotrader/config"
)

// EncryptOrDecrypt returns a string from a boolean
func EncryptOrDecrypt(encrypt bool) string {
	if encrypt {
		return "encrypted"
	}
	return "decrypted"
}

func main() {
	var inFile, outFile, key string
	var encrypt bool
	var err error
	configFile := config.GetFilePath("")
	flag.StringVar(&inFile, "infile", configFile, "The config input file to process.")
	flag.StringVar(&outFile, "outfile", configFile+".out", "The config output file.")
	flag.BoolVar(&encrypt, "encrypt", true, "Wether to encrypt or decrypt.")
	flag.StringVar(&key, "key", "", "The key to use for AES encryption.")
	flag.Parse()

	log.Println("GoCryptoTrader: config-helper tool.")

	if key == "" {
		result, errf := config.PromptForConfigKey()
		if errf != nil {
			log.Fatal("Unable to obtain encryption/decryption key.")
		}
		key = string(result)
	}

	file, err := common.ReadFile(inFile)
	if err != nil {
		log.Fatalf("Unable to read input file %s. Error: %s.", inFile, err)
	}

	if config.ConfirmECS(file) && encrypt {
		log.Println("File is already encrypted. Decrypting..")
		encrypt = false
	}

	if !config.ConfirmECS(file) && !encrypt {
		var result interface{}
		errf := config.ConfirmConfigJSON(file, result)
		if errf != nil {
			log.Fatal("File isn't in JSON format")
		}
		log.Println("File is already decrypted. Encrypting..")
		encrypt = true
	}

	var data []byte
	if encrypt {
		data, err = config.EncryptConfigFile(file, []byte(key))
		if err != nil {
			log.Fatalf("Unable to encrypt config data. Error: %s.", err)
		}
	} else {
		data, err = config.DecryptConfigFile(file, []byte(key))
		if err != nil {
			log.Fatalf("Unable to decrypt config data. Error: %s.", err)
		}
	}

	err = common.WriteFile(outFile, data)
	if err != nil {
		log.Fatalf("Unable to write output file %s. Error: %s", outFile, err)
	}
	log.Printf(
		"Successfully %s input file %s and wrote output to %s.\n",
		EncryptOrDecrypt(encrypt), inFile, outFile,
	)
}
<commit_msg>Update config.go<commit_after>package main

import (
	"flag"
	"log"

	"github.com/thrasher-/gocryptotrader/common"
	"github.com/thrasher-/gocryptotrader/config"
)

// EncryptOrDecrypt returns a string from a boolean
func EncryptOrDecrypt(encrypt bool) string {
	if encrypt {
		return "encrypted"
	}
	return "decrypted"
}

func main() {
	var inFile, outFile, key string
	var encrypt bool
	var err error
	configFile := config.GetFilePath("")
	flag.StringVar(&inFile, "infile", configFile, "The config input file to process.")
	flag.StringVar(&outFile, "outfile", configFile+".out", "The config output file.")
	flag.BoolVar(&encrypt, "encrypt", true, "Whether to encrypt or decrypt.")
	flag.StringVar(&key, "key", "", "The key to use for AES encryption.")
	flag.Parse()

	log.Println("GoCryptoTrader: config-helper tool.")

	if key == "" {
		result, errf := config.PromptForConfigKey()
		if errf != nil {
			log.Fatal("Unable to obtain encryption/decryption key.")
		}
		key = string(result)
	}

	file, err := common.ReadFile(inFile)
	if err != nil {
		log.Fatalf("Unable to read input file %s. Error: %s.", inFile, err)
	}

	if config.ConfirmECS(file) && encrypt {
		log.Println("File is already encrypted. Decrypting..")
		encrypt = false
	}

	if !config.ConfirmECS(file) && !encrypt {
		var result interface{}
		errf := config.ConfirmConfigJSON(file, result)
		if errf != nil {
			log.Fatal("File isn't in JSON format")
		}
		log.Println("File is already decrypted. Encrypting..")
		encrypt = true
	}

	var data []byte
	if encrypt {
		data, err = config.EncryptConfigFile(file, []byte(key))
		if err != nil {
			log.Fatalf("Unable to encrypt config data. Error: %s.", err)
		}
	} else {
		data, err = config.DecryptConfigFile(file, []byte(key))
		if err != nil {
			log.Fatalf("Unable to decrypt config data. Error: %s.", err)
		}
	}

	err = common.WriteFile(outFile, data)
	if err != nil {
		log.Fatalf("Unable to write output file %s. Error: %s", outFile, err)
	}
	log.Printf(
		"Successfully %s input file %s and wrote output to %s.\n",
		EncryptOrDecrypt(encrypt), inFile, outFile,
	)
}
<|endoftext|>


<commit_before>package main

// This script polls ETCD and builds a Chef deploy script.

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/coreos/go-etcd/etcd"
	"github.com/gengo/goship/lib"
)

var (
	knifePath  = flag.String("s", ".chef/knife.rb", "KnifePath (.chef/knife.rb)")
	chefPath   = flag.String("c", "/srv/http/gengo/devops-tools/daidokoro", "Chef Path (required)")
	pemKey     = flag.String("k", "/home/deployer/.ssh/dszydlowski.pem", "PEM Key (default /home/deployer/.ssh/dszydlowski.pem)")
	deployUser = flag.String("u", "ubuntu", "deploy user (default /ubuntu)")
	deployProj = flag.String("p", "", "project (required)")
	deployEnv  = flag.String("e", "", "environment (required)")
)

func execCmd(icmd string) {
	os.Chdir(*chefPath)

	parts := strings.Fields(icmd)
	head := parts[0]
	parts = parts[1:len(parts)]

	cmd := exec.Command(head, parts...)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		log.Fatal(err)
	}
	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}
	scanner := bufio.NewScanner(stdout)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		log.Printf("Error reading standard output stream: %s", err)
	}
	scanner = bufio.NewScanner(stderr)
	for scanner.Scan() {
		log.Println(scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		log.Printf("Error reading standard error stream: %s", err)
	}
	if err := cmd.Wait(); err != nil {
		log.Fatalf("Error waiting for Chef to complete %s", err)
	}
}

func main() {
	flag.Parse()
	c, err := goship.ParseETCD(etcd.NewClient([]string{"http://127.0.0.1:4001"}))
	if err != nil {
		log.Fatalf("Error parsing ETCD: %s", err)
	}
	projectEnv, err := goship.EnvironmentFromName(c.Projects, *deployProj, *deployEnv)
	if err != nil {
		log.Fatalf("Error getting project %s %s %s", *deployProj, *deployEnv, err)
	}
	log.Printf("Deploying project name: %s environment Name: %s", *deployEnv, projectEnv.Name)
	servers := projectEnv.Hosts
	for _, h := range servers {
		d := "knife solo cook -c " + *knifePath + " -i " + *pemKey + " " + *deployUser + "@" + h.URI
		log.Printf("Deploying to server: %s", h.URI)
		log.Printf("Preparing Knife command: %s", d)
		execCmd(d)
	}
}
<commit_msg>Add cookbook presync to deploy.go<commit_after>package main

// This script polls ETCD and builds a Chef deploy script.

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"

	"code.google.com/p/goauth2/oauth"
	"github.com/coreos/go-etcd/etcd"
	"github.com/gengo/goship/lib"
	"github.com/google/go-github/github"
)

var (
	knifePath  = flag.String("s", ".chef/knife.rb", "KnifePath (.chef/knife.rb)")
	chefRepo   = flag.String("r", "/srv/http/gengo/devops-tools/", "Chef Repo (/srv/http/gengo/devops-tools/)")
	chefPath   = flag.String("c", "/srv/http/gengo/devops-tools/daidokoro", "Chef Path (/srv/http/gengo/devops-tools/daidokoro)")
	pemKey     = flag.String("k", "/home/deployer/.ssh/dszydlowski.pem", "PEM Key (default /home/deployer/.ssh/dszydlowski.pem)")
	deployUser = flag.String("u", "ubuntu", "deploy user (default /ubuntu)")
	deployProj = flag.String("p", "", "project (required)")
	deployEnv  = flag.String("e", "", "environment (required)")
	pullOnly   = flag.Bool("o", false, "chef update only (default false)")
	skipUpdate = flag.Bool("m", false, "skip the chef update (default false)")
)

// gitHubPaginationLimit is the default pagination limit for requests to the GitHub API that return multiple items.
const (
	gitHubPaginationLimit = 30
	gitHubAPITokenEnvVar  = "GITHUB_API_TOKEN"
)

// updateChefRepo ensures the lates chef cookbooks are pulled before deploying.
// Checks github first and ignores pull if already up to date.
func updateChefRepo(deployUser string) {
	githubToken := os.Getenv(gitHubAPITokenEnvVar)
	t := &oauth.Transport{
		Token: &oauth.Token{AccessToken: githubToken},
	}
	client := github.NewClient(t.Client())
	s := "git --git-dir=" + *chefRepo + "/.git rev-parse HEAD"
	localHash, _ := execCmd(s)
	commits, _, err := client.Repositories.ListCommits("Gengo", "devops-tools", nil)
	if err != nil {
		log.Fatal("ERROR:  failed to get commits from GitHub: Please try again later ", err)
	}
	remoteHash := *commits[0].SHA
	if localHash == remoteHash {
		log.Printf("Local Chef is up to date: Skipping Sync")
	} else {
		log.Printf("Chef is not up to date: \n %s does not equal %s", localHash, remoteHash)
		log.Println("Updating devops-tools")
		s := "git --git-dir=" + *chefRepo + "/.git pull origin master"
		_, err := execCmd(s)
		if err != nil {
			log.Fatal("ERROR:  Failed to pull latest devops_tools: ", err)
		}
		log.Println("Devops Tools Updated")
	}
}

func execCmd(icmd string) (output string, err error) {
	os.Chdir(*chefPath)

	parts := strings.Fields(icmd)
	head := parts[0]
	parts = parts[1:len(parts)]

	cmd := exec.Command(head, parts...)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		log.Fatal(err)
	}
	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}
	scanner := bufio.NewScanner(stdout)
	for scanner.Scan() {
		o := scanner.Text()
		output += o
		fmt.Println(o)

	}
	if err := scanner.Err(); err != nil {
		log.Printf("Error reading standard output stream: %s", err)
	}
	scanner = bufio.NewScanner(stderr)
	for scanner.Scan() {
		log.Println(scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		log.Printf("Error reading standard error stream: %s", err)
	}
	if err := cmd.Wait(); err != nil {
		log.Fatalf("Error waiting for Chef to complete %s", err)
	}
	return output, err
}

func main() {
	flag.Parse()
	if *skipUpdate == false {
		updateChefRepo(*deployUser)
	}
	if *pullOnly == false {
		c, err := goship.ParseETCD(etcd.NewClient([]string{"http://127.0.0.1:4001"}))
		if err != nil {
			log.Fatalf("Error parsing ETCD: %s", err)
		}
		projectEnv, err := goship.EnvironmentFromName(c.Projects, *deployProj, *deployEnv)
		if err != nil {
			log.Fatalf("Error getting project %s %s %s", *deployProj, *deployEnv, err)
		}
		log.Printf("Deploying project name: %s environment Name: %s", *deployEnv, projectEnv.Name)
		servers := projectEnv.Hosts
		for _, h := range servers {
			d := "knife solo cook -c " + *knifePath + " -i " + *pemKey + " --no-host-key-verify " + *deployUser + "@" + h.URI
			log.Printf("Deploying to server: %s", h.URI)
			log.Printf("Preparing Knife command: %s", d)
			_, err := execCmd(d)
			if err != nil {
				log.Fatalf("Error Executing command %s", err)
			}
		}
	}

}
<|endoftext|>