<commit_before>package codewriter

import (
	"bufio"
	"fmt"
	"io"
	"strconv"
	"strings"
	"sync"
)

// binary representation of logical values
const (
	bitTrue  = -1
	bitFalse = 0
)

// baseLabel is a base name of labels.
const baseLabel = "LABEL"

// Mneumonic is a mneumonic of an instruction.
type Mneumonic string

// Mneumonics.
const (
	Add  Mneumonic = "add"
	Sub            = "sub"
	Neg            = "neg"
	Eq             = "eq"
	Gt             = "gt"
	Lt             = "lt"
	And            = "and"
	Or             = "or"
	Not            = "not"
	Push           = "push"
	Pop            = "pop"
)

// Segment is a memory segment.
type Segment string

// Memory segments.
const (
	Argument Segment = "argument"
	Local            = "local"
	Static           = "static"
	Constant         = "constant"
	This             = "this"
	That             = "that"
	Pointer          = "pointer"
	Temp             = "temp"
)

// CodeWriter converts VM commands to Hack assembly codes and write them out to a destination.
type CodeWriter struct {
	err      error
	dest     io.Writer
	buf      *bufio.Writer
	filename string

	mu  sync.Mutex
	cnt int
}

// New creates a new CodeWriter that writes converted codes to dest.
func New(dest io.Writer) *CodeWriter {
	return &CodeWriter{
		dest: dest,
		buf:  bufio.NewWriter(dest),
	}
}

// SetFileName sets an input VM file name and writes it to the output file as comment.
func (cw *CodeWriter) SetFileName(filename string) error {
	cw.filename = filename

	comment := fmt.Sprintf("// %s\n", filename)
	_, err := cw.buf.WriteString(comment)
	return err
}

// WriteArithmetic converts the given arithmetic command to assembly code and writes it out.
func (cw *CodeWriter) WriteArithmetic(cmd string) error {
	switch cmd {
	case "neg", "not":
		cw.unary(cmd)
	case "add", "sub", "and", "or":
		cw.binary(cmd)
	case "eq", "gt", "lt":
		cw.compare(cmd)
	default:
		cw.err = fmt.Errorf("unknown command: %s", cmd)
	}

	if cw.err != nil {
		return fmt.Errorf("error writing code: %s", cw.err.Error())
	}
	return nil
}

// WritePushPop converts the given push or pop command to assembly code and writes it out.
func (cw *CodeWriter) WritePushPop(cmd, seg string, idx uint) error {
	switch cmd {
	case "push":
		return cw.push(seg, idx)
	case "pop":
		return cw.pop(seg, idx)
	default:
		return fmt.Errorf("unknown command: %s", cmd)
	}
}

// Close flushes bufferred data to the destination and closes it.
// Note that no data is written to the destination until Close is called.
func (cw *CodeWriter) Close() error {
	defer func() {
		if cl, ok := cw.dest.(io.Closer); ok {
			_ = cl.Close()
		}
	}()

	// write the end infinite loop
	if e := cw.end(); e != nil {
		return fmt.Errorf("error writing the end infinite loop: %v", e)
	}

	if e := cw.buf.Flush(); e != nil {
		return fmt.Errorf("error flushing bufferred data: %s", e)
	}
	return nil
}

// end writes the end infinite loop.
func (cw *CodeWriter) end() error {
	cw.lcmd("END")
	cw.acmd("END")
	cw.ccmdj("", "0", "JMP")
	return cw.err
}

// push converts the given push command to assembly and writes it out.
func (cw *CodeWriter) push(seg string, idx uint) error {
	switch seg {
	case "constant":
		cw.pushVal(idx)
	case "local":
		cw.push0("LCL", idx, false)
	case "argument":
		cw.push0("ARG", idx, false)
	case "this":
		cw.push0("THIS", idx, false)
	case "that":
		cw.push0("THAT", idx, false)
	case "temp":
		// temp: R5 ~ R12
		cw.push0("R5", idx, true)
		// pointer: R3 ~ R4
	case "pointer":
		cw.push0("R3", idx, true)
	default:
		return fmt.Errorf("unknown segment: %s", seg)
	}

	return cw.err
}

// pop converts the given pop command to assembly and writes it out.
func (cw *CodeWriter) pop(seg string, idx uint) error {
	switch seg {
	case "local":
		cw.pop0("LCL", idx, false)
	case "argument":
		cw.pop0("ARG", idx, false)
	case "this":
		cw.pop0("THIS", idx, false)
	case "that":
		cw.pop0("THAT", idx, false)
	case "temp":
		// temp: R5 ~ R12
		cw.pop0("R5", idx, true)
	case "pointer":
		// pointer R3 ~ R4
		cw.pop0("R3", idx, true)
	default:
		return fmt.Errorf("unknown segment: %s", seg)
	}

	return cw.err
}

// unary writes a unary operation for a value at the top of the stack.
// cmd must be one of the following:
//   - "neg"
//   - "not"
func (cw *CodeWriter) unary(cmd string) {
	var op string
	switch cmd {
	case "neg":
		op = "-"
	case "not":
		op = "!"
	}

	cw.decrSP()
	cw.ccmd("M", op+"M")
	cw.incrSP()
}

// binary writes a binary operation for two values at the top of the stack.
// cmd must be one of the following:
//   - "add"
//   - "sub"
//   - "and"
//   - "or"
func (cw *CodeWriter) binary(cmd string) {
	var op string
	switch cmd {
	case "add":
		op = "D+M"
	case "sub":
		op = "M-D"
	case "and":
		op = "D&M"
	case "or":
		op = "D|M"
	}

	cw.popStack()
	cw.decrSP()
	cw.ccmd("M", op)
	cw.incrSP()
}

// compare writes a comparison operation for two values at the top of the stack.
// cmd must be one of the following:
//   - "eq"
//   - "gt"
//   - "lt"
func (cw *CodeWriter) compare(cmd string) {
	// JEQ, JGT, JLT
	op := "J" + strings.ToUpper(cmd)
	label1, label2 := cw.label(), cw.label()

	cw.popStack()
	cw.decrSP()
	cw.ccmd("D", "M-D")
	cw.acmd(label1)
	cw.ccmdj("", "D", op)
	cw.loadToSP(bitFalse)
	cw.acmd(label2)
	cw.ccmdj("", "0", "JMP")
	cw.lcmd(label1)
	cw.loadToSP(bitTrue)
	cw.lcmd(label2)
	cw.incrSP()
}

// label returns a label.
func (cw *CodeWriter) label() string {
	defer cw.countUp()
	return baseLabel + strconv.Itoa(cw.cnt)
}

// countUp counts up an internal counter.
func (cw *CodeWriter) countUp() {
	cw.mu.Lock()
	defer cw.mu.Unlock()
	cw.cnt++
}

// pushVal pushes v to the top of the stack. Internally,
// it assgins v to *SP and increments SP.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) pushVal(v uint) {
	cw.loadToSP(int(v))
	cw.incrSP()
}

// push0 pushes a value of symb to the top of the stack.
// If direct is true a value in symb is pushed directly,
// otherwise a value pointed by an address in symb indirectly.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) push0(symb string, idx uint, direct bool) {
	cw.loadSeg(symb, idx, direct)
	cw.ccmd("D", "M")
	cw.saveTo("SP")
	cw.incrSP()
}

// pop0 pops a value from the top of the stack to symb.
// If direct is true a value in symb is popped directly,
// otherwise a value pointed by an address in symb indirectly.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) pop0(symb string, idx uint, direct bool) {
	tmpreg := "R13"

	cw.loadSeg(symb, idx, direct)
	cw.acmd(tmpreg)
	cw.ccmd("M", "D")
	cw.popStack()
	cw.saveTo(tmpreg)
}

// loadSeg load a value of the symb segment to D.
// If direct is true a value in symb is loaded directly,
// otherwise a value pointed by an address in symb indirectly.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) loadSeg(symb string, idx uint, direct bool) {
	var m string
	if direct {
		m = "A"
	} else {
		m = "M"
	}

	cw.acmd(idx)
	cw.ccmd("D", "A")
	cw.acmd(symb)
	cw.ccmd("AD", "D+"+m)
}

// saveTo save the value of D to addr.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) saveTo(addr string) {
	cw.acmd(addr)
	cw.ccmd("A", "M")
	cw.ccmd("M", "D")
}

// loadToSP loads v to *SP. v should be greater than or equal -1 (v >= -1).
func (cw *CodeWriter) loadToSP(v int) {
	if v < 0 {
		cw.acmd("SP")
		cw.ccmd("A", "M")
		cw.ccmd("M", strconv.Itoa(v))
		return
	}

	cw.acmd(v)
	cw.ccmd("D", "A")
	cw.saveTo("SP")
}

// popStack pops a value at the top of the stack. Internally,
// it decrements SP and assigns a value pointed by SP to D.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) popStack() {
	cw.decrSP()
	cw.ccmd("D", "M")
}

// incrSP increments SP and sets the current address to it.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) incrSP() {
	cw.sp("+")
}

// decrSP increments SP and sets the current address to it.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) decrSP() {
	cw.sp("-")
}

// sp controls the position of SP and sets the current address to it.
// op must be one of the following:
//   "+": SP++
//   "-": SP--
func (cw *CodeWriter) sp(op string) {
	cw.acmd("SP")
	cw.ccmd("AM", "M"+op+"1")
}

// acmd writes @ command. If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) acmd(addr interface{}) {
	if cw.err != nil {
		return
	}

	_, cw.err = cw.buf.WriteString("@" + fmt.Sprintf("%v", addr) + "\n")
}

// ccmd writes C command with no jump. If an error occurs, it is set at cw.err.
func (cw *CodeWriter) ccmd(dest, comp string) {
	cw.ccmdj(dest, comp, "")
}

// ccmdj writes C command with jump. If an error occurs, it is set at cw.err.
func (cw *CodeWriter) ccmdj(dest, comp, jump string) {
	if cw.err != nil {
		return
	}

	// allocate a slice whose length is len(dest=comp;jump\n)
	opc := make([]byte, 0, len(dest)+1+len(comp)+1+len(jump)+1)

	// append `dest=`
	if dest != "" {
		opc = append(append(opc, dest...), '=')
	}

	// append comp
	opc = append(opc, comp...)

	// append `;jump`
	if jump != "" {
		opc = append(append(opc, ';'), jump...)
	}

	// append \n
	opc = append(opc, '\n')

	_, cw.err = cw.buf.Write(opc)
}

// lcmd writes label command. If an error occurs, it is set at cw.err.
func (cw *CodeWriter) lcmd(label string) {
	if cw.err != nil {
		return
	}

	_, cw.err = cw.buf.WriteString("(" + label + ")\n")
}
<commit_msg>07/codewriter: add a TODO comment<commit_after>package codewriter

import (
	"bufio"
	"fmt"
	"io"
	"strconv"
	"strings"
	"sync"
)

// binary representation of logical values
const (
	bitTrue  = -1
	bitFalse = 0
)

// baseLabel is a base name of labels.
const baseLabel = "LABEL"

// Mneumonic is a mneumonic of an instruction.
type Mneumonic string

// Mneumonics.
const (
	Add  Mneumonic = "add"
	Sub            = "sub"
	Neg            = "neg"
	Eq             = "eq"
	Gt             = "gt"
	Lt             = "lt"
	And            = "and"
	Or             = "or"
	Not            = "not"
	Push           = "push"
	Pop            = "pop"
)

// Segment is a memory segment.
type Segment string

// Memory segments.
const (
	Argument Segment = "argument"
	Local            = "local"
	Static           = "static"
	Constant         = "constant"
	This             = "this"
	That             = "that"
	Pointer          = "pointer"
	Temp             = "temp"
)

// CodeWriter converts VM commands to Hack assembly codes and write them out to a destination.
type CodeWriter struct {
	err      error
	dest     io.Writer
	buf      *bufio.Writer
	filename string

	mu  sync.Mutex
	cnt int
}

// New creates a new CodeWriter that writes converted codes to dest.
func New(dest io.Writer) *CodeWriter {
	return &CodeWriter{
		dest: dest,
		buf:  bufio.NewWriter(dest),
	}
}

// SetFileName sets an input VM file name and writes it to the output file as comment.
func (cw *CodeWriter) SetFileName(filename string) error {
	cw.filename = filename

	// TODO print an absolute path or just a base file name,
	// or a file name if it's a file and dir/file if it's a dir.
	comment := fmt.Sprintf("// %s\n", filename)
	_, err := cw.buf.WriteString(comment)
	return err
}

// WriteArithmetic converts the given arithmetic command to assembly code and writes it out.
func (cw *CodeWriter) WriteArithmetic(cmd string) error {
	switch cmd {
	case "neg", "not":
		cw.unary(cmd)
	case "add", "sub", "and", "or":
		cw.binary(cmd)
	case "eq", "gt", "lt":
		cw.compare(cmd)
	default:
		cw.err = fmt.Errorf("unknown command: %s", cmd)
	}

	if cw.err != nil {
		return fmt.Errorf("error writing code: %s", cw.err.Error())
	}
	return nil
}

// WritePushPop converts the given push or pop command to assembly code and writes it out.
func (cw *CodeWriter) WritePushPop(cmd, seg string, idx uint) error {
	switch cmd {
	case "push":
		return cw.push(seg, idx)
	case "pop":
		return cw.pop(seg, idx)
	default:
		return fmt.Errorf("unknown command: %s", cmd)
	}
}

// Close flushes bufferred data to the destination and closes it.
// Note that no data is written to the destination until Close is called.
func (cw *CodeWriter) Close() error {
	defer func() {
		if cl, ok := cw.dest.(io.Closer); ok {
			_ = cl.Close()
		}
	}()

	// write the end infinite loop
	if e := cw.end(); e != nil {
		return fmt.Errorf("error writing the end infinite loop: %v", e)
	}

	if e := cw.buf.Flush(); e != nil {
		return fmt.Errorf("error flushing bufferred data: %s", e)
	}
	return nil
}

// end writes the end infinite loop.
func (cw *CodeWriter) end() error {
	cw.lcmd("END")
	cw.acmd("END")
	cw.ccmdj("", "0", "JMP")
	return cw.err
}

// push converts the given push command to assembly and writes it out.
func (cw *CodeWriter) push(seg string, idx uint) error {
	switch seg {
	case "constant":
		cw.pushVal(idx)
	case "local":
		cw.push0("LCL", idx, false)
	case "argument":
		cw.push0("ARG", idx, false)
	case "this":
		cw.push0("THIS", idx, false)
	case "that":
		cw.push0("THAT", idx, false)
	case "temp":
		// temp: R5 ~ R12
		cw.push0("R5", idx, true)
		// pointer: R3 ~ R4
	case "pointer":
		cw.push0("R3", idx, true)
	default:
		return fmt.Errorf("unknown segment: %s", seg)
	}

	return cw.err
}

// pop converts the given pop command to assembly and writes it out.
func (cw *CodeWriter) pop(seg string, idx uint) error {
	switch seg {
	case "local":
		cw.pop0("LCL", idx, false)
	case "argument":
		cw.pop0("ARG", idx, false)
	case "this":
		cw.pop0("THIS", idx, false)
	case "that":
		cw.pop0("THAT", idx, false)
	case "temp":
		// temp: R5 ~ R12
		cw.pop0("R5", idx, true)
	case "pointer":
		// pointer R3 ~ R4
		cw.pop0("R3", idx, true)
	default:
		return fmt.Errorf("unknown segment: %s", seg)
	}

	return cw.err
}

// unary writes a unary operation for a value at the top of the stack.
// cmd must be one of the following:
//   - "neg"
//   - "not"
func (cw *CodeWriter) unary(cmd string) {
	var op string
	switch cmd {
	case "neg":
		op = "-"
	case "not":
		op = "!"
	}

	cw.decrSP()
	cw.ccmd("M", op+"M")
	cw.incrSP()
}

// binary writes a binary operation for two values at the top of the stack.
// cmd must be one of the following:
//   - "add"
//   - "sub"
//   - "and"
//   - "or"
func (cw *CodeWriter) binary(cmd string) {
	var op string
	switch cmd {
	case "add":
		op = "D+M"
	case "sub":
		op = "M-D"
	case "and":
		op = "D&M"
	case "or":
		op = "D|M"
	}

	cw.popStack()
	cw.decrSP()
	cw.ccmd("M", op)
	cw.incrSP()
}

// compare writes a comparison operation for two values at the top of the stack.
// cmd must be one of the following:
//   - "eq"
//   - "gt"
//   - "lt"
func (cw *CodeWriter) compare(cmd string) {
	// JEQ, JGT, JLT
	op := "J" + strings.ToUpper(cmd)
	label1, label2 := cw.label(), cw.label()

	cw.popStack()
	cw.decrSP()
	cw.ccmd("D", "M-D")
	cw.acmd(label1)
	cw.ccmdj("", "D", op)
	cw.loadToSP(bitFalse)
	cw.acmd(label2)
	cw.ccmdj("", "0", "JMP")
	cw.lcmd(label1)
	cw.loadToSP(bitTrue)
	cw.lcmd(label2)
	cw.incrSP()
}

// label returns a label.
func (cw *CodeWriter) label() string {
	defer cw.countUp()
	return baseLabel + strconv.Itoa(cw.cnt)
}

// countUp counts up an internal counter.
func (cw *CodeWriter) countUp() {
	cw.mu.Lock()
	defer cw.mu.Unlock()
	cw.cnt++
}

// pushVal pushes v to the top of the stack. Internally,
// it assgins v to *SP and increments SP.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) pushVal(v uint) {
	cw.loadToSP(int(v))
	cw.incrSP()
}

// push0 pushes a value of symb to the top of the stack.
// If direct is true a value in symb is pushed directly,
// otherwise a value pointed by an address in symb indirectly.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) push0(symb string, idx uint, direct bool) {
	cw.loadSeg(symb, idx, direct)
	cw.ccmd("D", "M")
	cw.saveTo("SP")
	cw.incrSP()
}

// pop0 pops a value from the top of the stack to symb.
// If direct is true a value in symb is popped directly,
// otherwise a value pointed by an address in symb indirectly.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) pop0(symb string, idx uint, direct bool) {
	tmpreg := "R13"

	cw.loadSeg(symb, idx, direct)
	cw.acmd(tmpreg)
	cw.ccmd("M", "D")
	cw.popStack()
	cw.saveTo(tmpreg)
}

// loadSeg load a value of the symb segment to D.
// If direct is true a value in symb is loaded directly,
// otherwise a value pointed by an address in symb indirectly.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) loadSeg(symb string, idx uint, direct bool) {
	var m string
	if direct {
		m = "A"
	} else {
		m = "M"
	}

	cw.acmd(idx)
	cw.ccmd("D", "A")
	cw.acmd(symb)
	cw.ccmd("AD", "D+"+m)
}

// saveTo save the value of D to addr.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) saveTo(addr string) {
	cw.acmd(addr)
	cw.ccmd("A", "M")
	cw.ccmd("M", "D")
}

// loadToSP loads v to *SP. v should be greater than or equal -1 (v >= -1).
func (cw *CodeWriter) loadToSP(v int) {
	if v < 0 {
		cw.acmd("SP")
		cw.ccmd("A", "M")
		cw.ccmd("M", strconv.Itoa(v))
		return
	}

	cw.acmd(v)
	cw.ccmd("D", "A")
	cw.saveTo("SP")
}

// popStack pops a value at the top of the stack. Internally,
// it decrements SP and assigns a value pointed by SP to D.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) popStack() {
	cw.decrSP()
	cw.ccmd("D", "M")
}

// incrSP increments SP and sets the current address to it.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) incrSP() {
	cw.sp("+")
}

// decrSP increments SP and sets the current address to it.
// If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) decrSP() {
	cw.sp("-")
}

// sp controls the position of SP and sets the current address to it.
// op must be one of the following:
//   "+": SP++
//   "-": SP--
func (cw *CodeWriter) sp(op string) {
	cw.acmd("SP")
	cw.ccmd("AM", "M"+op+"1")
}

// acmd writes @ command. If an error occurs and cw.err is nil, it is set at cw.err.
func (cw *CodeWriter) acmd(addr interface{}) {
	if cw.err != nil {
		return
	}

	_, cw.err = cw.buf.WriteString("@" + fmt.Sprintf("%v", addr) + "\n")
}

// ccmd writes C command with no jump. If an error occurs, it is set at cw.err.
func (cw *CodeWriter) ccmd(dest, comp string) {
	cw.ccmdj(dest, comp, "")
}

// ccmdj writes C command with jump. If an error occurs, it is set at cw.err.
func (cw *CodeWriter) ccmdj(dest, comp, jump string) {
	if cw.err != nil {
		return
	}

	// allocate a slice whose length is len(dest=comp;jump\n)
	opc := make([]byte, 0, len(dest)+1+len(comp)+1+len(jump)+1)

	// append `dest=`
	if dest != "" {
		opc = append(append(opc, dest...), '=')
	}

	// append comp
	opc = append(opc, comp...)

	// append `;jump`
	if jump != "" {
		opc = append(append(opc, ';'), jump...)
	}

	// append \n
	opc = append(opc, '\n')

	_, cw.err = cw.buf.Write(opc)
}

// lcmd writes label command. If an error occurs, it is set at cw.err.
func (cw *CodeWriter) lcmd(label string) {
	if cw.err != nil {
		return
	}

	_, cw.err = cw.buf.WriteString("(" + label + ")\n")
}
<|endoftext|>


<commit_before><commit_msg>httputil: dont indent JSON<commit_after><|endoftext|>


<commit_before>//
// Copyright 2017, Sander van Harmelen
//
// 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 gitlab

import (
	"bytes"
	"fmt"
	"net/url"
	"strconv"
	"time"
)

// RepositoryFilesService handles communication with the repository files
// related methods of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type RepositoryFilesService struct {
	client *Client
}

// File represents a GitLab repository file.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type File struct {
	FileName string `json:"file_name"`
	FilePath string `json:"file_path"`
	Size     int    `json:"size"`
	Encoding string `json:"encoding"`
	Content  string `json:"content"`
	Ref      string `json:"ref"`
	BlobID   string `json:"blob_id"`
	CommitID string `json:"commit_id"`
	SHA256   string `json:"content_sha256"`
}

func (r File) String() string {
	return Stringify(r)
}

// GetFileOptions represents the available GetFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
type GetFileOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetFile allows you to receive information about a file in repository like
// name, size, content. Note that file content is Base64 encoded.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
func (s *RepositoryFilesService) GetFile(pid interface{}, fileName string, opt *GetFileOptions, options ...RequestOptionFunc) (*File, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	f := new(File)
	resp, err := s.client.Do(req, f)
	if err != nil {
		return nil, resp, err
	}

	return f, resp, err
}

// GetFileMetaDataOptions represents the available GetFileMetaData() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
type GetFileMetaDataOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetFileMetaData allows you to receive meta information about a file in
// repository like name, size.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
func (s *RepositoryFilesService) GetFileMetaData(pid interface{}, fileName string, opt *GetFileMetaDataOptions, options ...RequestOptionFunc) (*File, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest("HEAD", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	resp, err := s.client.Do(req, nil)
	if err != nil {
		return nil, resp, err
	}

	f := &File{
		BlobID:   resp.Header.Get("X-Gitlab-Blob-Id"),
		CommitID: resp.Header.Get("X-Gitlab-Last-Commit-Id"),
		Encoding: resp.Header.Get("X-Gitlab-Encoding"),
		FileName: resp.Header.Get("X-Gitlab-File-Name"),
		FilePath: resp.Header.Get("X-Gitlab-File-Path"),
		Ref:      resp.Header.Get("X-Gitlab-Ref"),
		SHA256:   resp.Header.Get("X-Gitlab-Content-Sha256"),
	}

	if sizeString := resp.Header.Get("X-Gitlab-Size"); sizeString != "" {
		f.Size, err = strconv.Atoi(sizeString)
		if err != nil {
			return nil, resp, err
		}
	}

	return f, resp, err
}

// FileBlameRange represents one item of blame information.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html#get-file-blame-from-repository
type FileBlameRange struct {
	Commit struct {
		ID             string     `json:"id"`
		ParentIDs      []string   `json:"parent_ids"`
		Message        string     `json:"message"`
		AuthoredDate   *time.Time `json:"authored_date"`
		AuthorName     string     `json:"author_name"`
		AuthorEmail    string     `json:"author_email"`
		CommittedDate  *time.Time `json:"committed_date"`
		CommitterName  string     `json:"committer_name"`
		CommitterEmail string     `json:"committer_email"`
	} `json:"commit"`
	Lines []string `json:"lines"`
}

func (b FileBlameRange) String() string {
	return Stringify(b)
}

// GetFileBlameOptions represents the available GetFileBlame() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-blame-from-repository
type GetFileBlameOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetFileBlame allows you to receive blame information.
// Each blame range contains lines and corresponding commit info.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-blame-from-repository
func (s *RepositoryFilesService) GetFileBlame(pid interface{}, fileName string, opt *GetFileBlameOptions, options ...OptionFunc) ([]*FileBlameRange, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s/blame",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var b []*FileBlameRange
	resp, err := s.client.Do(req, &b)
	if err != nil {
		return nil, resp, err
	}

	return b, resp, err
}

// GetRawFileOptions represents the available GetRawFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository
type GetRawFileOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetRawFile allows you to receive the raw file in repository.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository
func (s *RepositoryFilesService) GetRawFile(pid interface{}, fileName string, opt *GetRawFileOptions, options ...RequestOptionFunc) ([]byte, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s/raw",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var f bytes.Buffer
	resp, err := s.client.Do(req, &f)
	if err != nil {
		return nil, resp, err
	}

	return f.Bytes(), resp, err
}

// FileInfo represents file details of a GitLab repository file.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type FileInfo struct {
	FilePath string `json:"file_path"`
	Branch   string `json:"branch"`
}

func (r FileInfo) String() string {
	return Stringify(r)
}

// CreateFileOptions represents the available CreateFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository
type CreateFileOptions struct {
	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
	Encoding      *string `url:"encoding,omitempty" json:"encoding,omitempty"`
	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
	Content       *string `url:"content,omitempty" json:"content,omitempty"`
	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
}

// CreateFile creates a new file in a repository.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository
func (s *RepositoryFilesService) CreateFile(pid interface{}, fileName string, opt *CreateFileOptions, options ...RequestOptionFunc) (*FileInfo, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	f := new(FileInfo)
	resp, err := s.client.Do(req, f)
	if err != nil {
		return nil, resp, err
	}

	return f, resp, err
}

// UpdateFileOptions represents the available UpdateFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository
type UpdateFileOptions struct {
	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
	Encoding      *string `url:"encoding,omitempty" json:"encoding,omitempty"`
	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
	Content       *string `url:"content,omitempty" json:"content,omitempty"`
	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
	LastCommitID  *string `url:"last_commit_id,omitempty" json:"last_commit_id,omitempty"`
}

// UpdateFile updates an existing file in a repository
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository
func (s *RepositoryFilesService) UpdateFile(pid interface{}, fileName string, opt *UpdateFileOptions, options ...RequestOptionFunc) (*FileInfo, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	f := new(FileInfo)
	resp, err := s.client.Do(req, f)
	if err != nil {
		return nil, resp, err
	}

	return f, resp, err
}

// DeleteFileOptions represents the available DeleteFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository
type DeleteFileOptions struct {
	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
}

// DeleteFile deletes an existing file in a repository
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository
func (s *RepositoryFilesService) DeleteFile(pid interface{}, fileName string, opt *DeleteFileOptions, options ...RequestOptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest("DELETE", u, opt, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<commit_msg>Update RP so it can be merged...<commit_after>//
// Copyright 2017, Sander van Harmelen
//
// 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 gitlab

import (
	"bytes"
	"fmt"
	"net/url"
	"strconv"
	"time"
)

// RepositoryFilesService handles communication with the repository files
// related methods of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type RepositoryFilesService struct {
	client *Client
}

// File represents a GitLab repository file.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type File struct {
	FileName string `json:"file_name"`
	FilePath string `json:"file_path"`
	Size     int    `json:"size"`
	Encoding string `json:"encoding"`
	Content  string `json:"content"`
	Ref      string `json:"ref"`
	BlobID   string `json:"blob_id"`
	CommitID string `json:"commit_id"`
	SHA256   string `json:"content_sha256"`
}

func (r File) String() string {
	return Stringify(r)
}

// GetFileOptions represents the available GetFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
type GetFileOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetFile allows you to receive information about a file in repository like
// name, size, content. Note that file content is Base64 encoded.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
func (s *RepositoryFilesService) GetFile(pid interface{}, fileName string, opt *GetFileOptions, options ...RequestOptionFunc) (*File, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	f := new(File)
	resp, err := s.client.Do(req, f)
	if err != nil {
		return nil, resp, err
	}

	return f, resp, err
}

// GetFileMetaDataOptions represents the available GetFileMetaData() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
type GetFileMetaDataOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetFileMetaData allows you to receive meta information about a file in
// repository like name, size.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
func (s *RepositoryFilesService) GetFileMetaData(pid interface{}, fileName string, opt *GetFileMetaDataOptions, options ...RequestOptionFunc) (*File, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest("HEAD", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	resp, err := s.client.Do(req, nil)
	if err != nil {
		return nil, resp, err
	}

	f := &File{
		BlobID:   resp.Header.Get("X-Gitlab-Blob-Id"),
		CommitID: resp.Header.Get("X-Gitlab-Last-Commit-Id"),
		Encoding: resp.Header.Get("X-Gitlab-Encoding"),
		FileName: resp.Header.Get("X-Gitlab-File-Name"),
		FilePath: resp.Header.Get("X-Gitlab-File-Path"),
		Ref:      resp.Header.Get("X-Gitlab-Ref"),
		SHA256:   resp.Header.Get("X-Gitlab-Content-Sha256"),
	}

	if sizeString := resp.Header.Get("X-Gitlab-Size"); sizeString != "" {
		f.Size, err = strconv.Atoi(sizeString)
		if err != nil {
			return nil, resp, err
		}
	}

	return f, resp, err
}

// FileBlameRange represents one item of blame information.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type FileBlameRange struct {
	Commit struct {
		ID             string     `json:"id"`
		ParentIDs      []string   `json:"parent_ids"`
		Message        string     `json:"message"`
		AuthoredDate   *time.Time `json:"authored_date"`
		AuthorName     string     `json:"author_name"`
		AuthorEmail    string     `json:"author_email"`
		CommittedDate  *time.Time `json:"committed_date"`
		CommitterName  string     `json:"committer_name"`
		CommitterEmail string     `json:"committer_email"`
	} `json:"commit"`
	Lines []string `json:"lines"`
}

func (b FileBlameRange) String() string {
	return Stringify(b)
}

// GetFileBlameOptions represents the available GetFileBlame() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-blame-from-repository
type GetFileBlameOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetFileBlame allows you to receive blame information. Each blame range
// contains lines and corresponding commit info.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-file-blame-from-repository
func (s *RepositoryFilesService) GetFileBlame(pid interface{}, file string, opt *GetFileBlameOptions, options ...RequestOptionFunc) ([]*FileBlameRange, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s/blame",
		pathEscape(project),
		url.PathEscape(file),
	)

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var br []*FileBlameRange
	resp, err := s.client.Do(req, &br)
	if err != nil {
		return nil, resp, err
	}

	return br, resp, err
}

// GetRawFileOptions represents the available GetRawFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository
type GetRawFileOptions struct {
	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
}

// GetRawFile allows you to receive the raw file in repository.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository
func (s *RepositoryFilesService) GetRawFile(pid interface{}, fileName string, opt *GetRawFileOptions, options ...RequestOptionFunc) ([]byte, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s/raw",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var f bytes.Buffer
	resp, err := s.client.Do(req, &f)
	if err != nil {
		return nil, resp, err
	}

	return f.Bytes(), resp, err
}

// FileInfo represents file details of a GitLab repository file.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
type FileInfo struct {
	FilePath string `json:"file_path"`
	Branch   string `json:"branch"`
}

func (r FileInfo) String() string {
	return Stringify(r)
}

// CreateFileOptions represents the available CreateFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository
type CreateFileOptions struct {
	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
	Encoding      *string `url:"encoding,omitempty" json:"encoding,omitempty"`
	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
	Content       *string `url:"content,omitempty" json:"content,omitempty"`
	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
}

// CreateFile creates a new file in a repository.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository
func (s *RepositoryFilesService) CreateFile(pid interface{}, fileName string, opt *CreateFileOptions, options ...RequestOptionFunc) (*FileInfo, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	f := new(FileInfo)
	resp, err := s.client.Do(req, f)
	if err != nil {
		return nil, resp, err
	}

	return f, resp, err
}

// UpdateFileOptions represents the available UpdateFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository
type UpdateFileOptions struct {
	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
	Encoding      *string `url:"encoding,omitempty" json:"encoding,omitempty"`
	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
	Content       *string `url:"content,omitempty" json:"content,omitempty"`
	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
	LastCommitID  *string `url:"last_commit_id,omitempty" json:"last_commit_id,omitempty"`
}

// UpdateFile updates an existing file in a repository
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository
func (s *RepositoryFilesService) UpdateFile(pid interface{}, fileName string, opt *UpdateFileOptions, options ...RequestOptionFunc) (*FileInfo, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	f := new(FileInfo)
	resp, err := s.client.Do(req, f)
	if err != nil {
		return nil, resp, err
	}

	return f, resp, err
}

// DeleteFileOptions represents the available DeleteFile() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository
type DeleteFileOptions struct {
	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
}

// DeleteFile deletes an existing file in a repository
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository
func (s *RepositoryFilesService) DeleteFile(pid interface{}, fileName string, opt *DeleteFileOptions, options ...RequestOptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf(
		"projects/%s/repository/files/%s",
		pathEscape(project),
		url.PathEscape(fileName),
	)

	req, err := s.client.NewRequest("DELETE", u, opt, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<|endoftext|>


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

package bootstrap_test

import (
	"fmt"
	stdtesting "testing"

	gc "launchpad.net/gocheck"

	"launchpad.net/juju-core/constraints"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/bootstrap"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/environs/localstorage"
	envtesting "launchpad.net/juju-core/environs/testing"
	"launchpad.net/juju-core/provider/dummy"
	coretesting "launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/tools"
	"launchpad.net/juju-core/version"
)

func Test(t *stdtesting.T) {
	gc.TestingT(t)
}

const (
	useDefaultKeys = true
	noKeysDefined  = false
)

type bootstrapSuite struct {
	home *coretesting.FakeHome
	coretesting.LoggingSuite
}

func TestPackage(t *stdtesting.T) {
	gc.TestingT(t)
}

var _ = gc.Suite(&bootstrapSuite{})

func (s *bootstrapSuite) SetUpTest(c *gc.C) {
	s.LoggingSuite.SetUpTest(c)
	s.home = coretesting.MakeFakeHomeNoEnvironments(c, "foo")
}

func (s *bootstrapSuite) TearDownTest(c *gc.C) {
	s.home.Restore()
}

func (s *bootstrapSuite) TestBootstrapNeedsSettings(c *gc.C) {
	env := newEnviron("bar", noKeysDefined)
	cleanup := setDummyStorage(c, env)
	defer cleanup()
	fixEnv := func(key string, value interface{}) {
		cfg, err := env.Config().Apply(map[string]interface{}{
			key: value,
		})
		c.Assert(err, gc.IsNil)
		env.cfg = cfg
	}

	err := bootstrap.Bootstrap(env, constraints.Value{})
	c.Assert(err, gc.ErrorMatches, "environment configuration has no admin-secret")

	fixEnv("admin-secret", "whatever")
	err = bootstrap.Bootstrap(env, constraints.Value{})
	c.Assert(err, gc.ErrorMatches, "environment configuration has no ca-cert")

	fixEnv("ca-cert", coretesting.CACert)
	err = bootstrap.Bootstrap(env, constraints.Value{})
	c.Assert(err, gc.ErrorMatches, "environment configuration has no ca-private-key")

	fixEnv("ca-private-key", coretesting.CAKey)
	uploadTools(c, env)
	err = bootstrap.Bootstrap(env, constraints.Value{})
	c.Assert(err, gc.IsNil)
}

func uploadTools(c *gc.C, env environs.Environ) {
	usefulVersion := version.Current
	usefulVersion.Series = env.Config().DefaultSeries()
	envtesting.UploadFakeToolsVersion(c, env.Storage(), usefulVersion)
}

func (s *bootstrapSuite) TestBootstrapEmptyConstraints(c *gc.C) {
	env := newEnviron("foo", useDefaultKeys)
	cleanup := setDummyStorage(c, env)
	defer cleanup()
	uploadTools(c, env)
	err := bootstrap.Bootstrap(env, constraints.Value{})
	c.Assert(err, gc.IsNil)
	c.Assert(env.bootstrapCount, gc.Equals, 1)
	c.Assert(env.constraints, gc.DeepEquals, constraints.Value{})
}

func (s *bootstrapSuite) TestBootstrapSpecifiedConstraints(c *gc.C) {
	env := newEnviron("foo", useDefaultKeys)
	cleanup := setDummyStorage(c, env)
	defer cleanup()
	uploadTools(c, env)
	cons := constraints.MustParse("cpu-cores=2 mem=4G")
	err := bootstrap.Bootstrap(env, cons)
	c.Assert(err, gc.IsNil)
	c.Assert(env.bootstrapCount, gc.Equals, 1)
	c.Assert(env.constraints, gc.DeepEquals, cons)
}

var bootstrapSetAgentVersionTests = []envtesting.BootstrapToolsTest{
	{
		Info:          "released cli with dev setting picks newest matching 1",
		Available:     envtesting.V100Xall,
		CliVersion:    envtesting.V100q32,
		DefaultSeries: "precise",
		Development:   true,
		Expect:        []version.Binary{envtesting.V1001p64},
	}, {
		Info:          "released cli with dev setting picks newest matching 2",
		Available:     envtesting.V1all,
		CliVersion:    envtesting.V120q64,
		DefaultSeries: "precise",
		Development:   true,
		Arch:          "i386",
		Expect:        []version.Binary{envtesting.V120p32},
	}, {
		Info:          "dev cli picks newest matching 1",
		Available:     envtesting.V110Xall,
		CliVersion:    envtesting.V110q32,
		DefaultSeries: "precise",
		Expect:        []version.Binary{envtesting.V1101p64},
	}, {
		Info:          "dev cli picks newest matching 2",
		Available:     envtesting.V1all,
		CliVersion:    envtesting.V120q64,
		DefaultSeries: "precise",
		Arch:          "i386",
		Expect:        []version.Binary{envtesting.V120p32},
	}}

func (s *bootstrapSuite) TestBootstrapTools(c *gc.C) {
	allTests := append(envtesting.BootstrapToolsTests, bootstrapSetAgentVersionTests...)
	for i, test := range allTests {
		c.Logf("\ntest %d: %s", i, test.Info)
		dummy.Reset()
		attrs := map[string]interface{}{
			"name":            "test",
			"type":            "dummy",
			"state-server":    false,
			"admin-secret":    "a-secret",
			"authorized-keys": "i-am-a-key",
			"ca-cert":         coretesting.CACert,
			"ca-private-key":  coretesting.CAKey,
			"development":     test.Development,
			"default-series":  test.DefaultSeries,
		}
		if test.AgentVersion != version.Zero {
			attrs["agent-version"] = test.AgentVersion.String()
		}
		env, err := environs.NewFromAttrs(attrs)
		c.Assert(err, gc.IsNil)
		env, err = environs.Prepare(env.Config())
		c.Assert(err, gc.IsNil)
		envtesting.RemoveAllTools(c, env)

		version.Current = test.CliVersion
		for _, vers := range test.Available {
			envtesting.UploadFakeToolsVersion(c, env.Storage(), vers)
		}

		cons := constraints.Value{}
		if test.Arch != "" {
			cons = constraints.MustParse("arch=" + test.Arch)
		}
		err = bootstrap.Bootstrap(env, cons)
		if test.Err != nil {
			c.Check(err, gc.ErrorMatches, test.Err.Error())
			continue
		} else {
			c.Assert(err, gc.IsNil)
		}
		unique := map[version.Number]bool{}
		for _, expected := range test.Expect {
			unique[expected.Number] = true
		}
		for expectAgentVersion := range unique {
			agentVersion, ok := env.Config().AgentVersion()
			c.Check(ok, gc.Equals, true)
			c.Check(agentVersion, gc.Equals, expectAgentVersion)
		}
	}
}

func (s *bootstrapSuite) TestBootstrapNeedsTools(c *gc.C) {
	env := newEnviron("foo", useDefaultKeys)
	cleanup := setDummyStorage(c, env)
	defer cleanup()
	envtesting.RemoveFakeTools(c, env.Storage())
	err := bootstrap.Bootstrap(env, constraints.Value{})
	c.Check(err, gc.ErrorMatches, "cannot find bootstrap tools: no tools available")
}

type bootstrapEnviron struct {
	name             string
	cfg              *config.Config
	environs.Environ // stub out all methods we don't care about.

	// The following fields are filled in when Bootstrap is called.
	bootstrapCount int
	constraints    constraints.Value
	storage        environs.Storage
}

func newEnviron(name string, defaultKeys bool) *bootstrapEnviron {
	m := map[string]interface{}{
		"name":            name,
		"type":            "test",
		"ca-cert":         "",
		"ca-private-key":  "",
		"authorized-keys": "",
	}
	if defaultKeys {
		m["ca-cert"] = coretesting.CACert
		m["ca-private-key"] = coretesting.CAKey
		m["admin-secret"] = version.Current.Number.String()
		m["authorized-keys"] = "foo"
	}
	cfg, err := config.New(m)
	if err != nil {
		panic(fmt.Errorf("cannot create config from %#v: %v", m, err))
	}
	return &bootstrapEnviron{
		name: name,
		cfg:  cfg,
	}
}

// setDummyStorage injects the local provider's fake storage implementation
// into the given environment, so that tests can manipulate storage as if it
// were real.
// Returns a cleanup function that must be called when done with the storage.
func setDummyStorage(c *gc.C, env *bootstrapEnviron) func() {
	listener, err := localstorage.Serve("127.0.0.1:0", c.MkDir())
	c.Assert(err, gc.IsNil)
	env.storage = localstorage.Client(listener.Addr().String())
	envtesting.UploadFakeTools(c, env.storage)
	return func() { listener.Close() }
}

func (e *bootstrapEnviron) Name() string {
	return e.name
}

func (e *bootstrapEnviron) Bootstrap(cons constraints.Value, possibleTools tools.List, machineID string) error {
	e.bootstrapCount++
	e.constraints = cons
	return nil
}

func (e *bootstrapEnviron) Config() *config.Config {
	return e.cfg
}

func (e *bootstrapEnviron) SetConfig(cfg *config.Config) error {
	e.cfg = cfg
	return nil
}

func (e *bootstrapEnviron) Storage() environs.Storage {
	return e.storage
}

func (e *bootstrapEnviron) PublicStorage() environs.StorageReader {
	return environs.EmptyStorage
}
<commit_msg>Fix tests<commit_after>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package bootstrap_test

import (
	"fmt"
	stdtesting "testing"

	gc "launchpad.net/gocheck"

	"launchpad.net/juju-core/constraints"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/bootstrap"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/environs/localstorage"
	envtesting "launchpad.net/juju-core/environs/testing"
	"launchpad.net/juju-core/provider/dummy"
	coretesting "launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/tools"
	"launchpad.net/juju-core/version"
)

func Test(t *stdtesting.T) {
	gc.TestingT(t)
}

const (
	useDefaultKeys = true
	noKeysDefined  = false
)

type bootstrapSuite struct {
	home *coretesting.FakeHome
	coretesting.LoggingSuite
}

func TestPackage(t *stdtesting.T) {
	gc.TestingT(t)
}

var _ = gc.Suite(&bootstrapSuite{})

func (s *bootstrapSuite) SetUpTest(c *gc.C) {
	s.LoggingSuite.SetUpTest(c)
	s.home = coretesting.MakeFakeHomeNoEnvironments(c, "foo")
}

func (s *bootstrapSuite) TearDownTest(c *gc.C) {
	s.home.Restore()
}

func (s *bootstrapSuite) TestBootstrapNeedsSettings(c *gc.C) {
	env := newEnviron("bar", noKeysDefined)
	cleanup := setDummyStorage(c, env)
	defer cleanup()
	fixEnv := func(key string, value interface{}) {
		cfg, err := env.Config().Apply(map[string]interface{}{
			key: value,
		})
		c.Assert(err, gc.IsNil)
		env.cfg = cfg
	}

	err := bootstrap.Bootstrap(env, constraints.Value{})
	c.Assert(err, gc.ErrorMatches, "environment configuration has no admin-secret")

	fixEnv("admin-secret", "whatever")
	err = bootstrap.Bootstrap(env, constraints.Value{})
	c.Assert(err, gc.ErrorMatches, "environment configuration has no ca-cert")

	fixEnv("ca-cert", coretesting.CACert)
	err = bootstrap.Bootstrap(env, constraints.Value{})
	c.Assert(err, gc.ErrorMatches, "environment configuration has no ca-private-key")

	fixEnv("ca-private-key", coretesting.CAKey)
	uploadTools(c, env)
	err = bootstrap.Bootstrap(env, constraints.Value{})
	c.Assert(err, gc.IsNil)
}

func uploadTools(c *gc.C, env environs.Environ) {
	usefulVersion := version.Current
	usefulVersion.Series = env.Config().DefaultSeries()
	envtesting.UploadFakeToolsVersion(c, env.Storage(), usefulVersion)
}

func (s *bootstrapSuite) TestBootstrapEmptyConstraints(c *gc.C) {
	env := newEnviron("foo", useDefaultKeys)
	cleanup := setDummyStorage(c, env)
	defer cleanup()
	uploadTools(c, env)
	err := bootstrap.Bootstrap(env, constraints.Value{})
	c.Assert(err, gc.IsNil)
	c.Assert(env.bootstrapCount, gc.Equals, 1)
	c.Assert(env.constraints, gc.DeepEquals, constraints.Value{})
}

func (s *bootstrapSuite) TestBootstrapSpecifiedConstraints(c *gc.C) {
	env := newEnviron("foo", useDefaultKeys)
	cleanup := setDummyStorage(c, env)
	defer cleanup()
	uploadTools(c, env)
	cons := constraints.MustParse("cpu-cores=2 mem=4G")
	err := bootstrap.Bootstrap(env, cons)
	c.Assert(err, gc.IsNil)
	c.Assert(env.bootstrapCount, gc.Equals, 1)
	c.Assert(env.constraints, gc.DeepEquals, cons)
}

var bootstrapSetAgentVersionTests = []envtesting.BootstrapToolsTest{
	{
		Info:          "released cli with dev setting picks newest matching 1",
		Available:     envtesting.V100Xall,
		CliVersion:    envtesting.V100q32,
		DefaultSeries: "precise",
		Development:   true,
		Expect:        []version.Binary{envtesting.V1001p64},
	}, {
		Info:          "released cli with dev setting picks newest matching 2",
		Available:     envtesting.V1all,
		CliVersion:    envtesting.V120q64,
		DefaultSeries: "precise",
		Development:   true,
		Arch:          "i386",
		Expect:        []version.Binary{envtesting.V120p32},
	}, {
		Info:          "dev cli picks newest matching 1",
		Available:     envtesting.V110Xall,
		CliVersion:    envtesting.V110q32,
		DefaultSeries: "precise",
		Expect:        []version.Binary{envtesting.V1101p64},
	}, {
		Info:          "dev cli picks newest matching 2",
		Available:     envtesting.V1all,
		CliVersion:    envtesting.V120q64,
		DefaultSeries: "precise",
		Arch:          "i386",
		Expect:        []version.Binary{envtesting.V120p32},
	}}

func (s *bootstrapSuite) TestBootstrapTools(c *gc.C) {
	allTests := append(envtesting.BootstrapToolsTests, bootstrapSetAgentVersionTests...)
	for i, test := range allTests {
		c.Logf("\ntest %d: %s", i, test.Info)
		dummy.Reset()
		attrs := map[string]interface{}{
			"name":            "test",
			"type":            "dummy",
			"state-server":    false,
			"admin-secret":    "a-secret",
			"authorized-keys": "i-am-a-key",
			"ca-cert":         coretesting.CACert,
			"ca-private-key":  coretesting.CAKey,
			"development":     test.Development,
			"default-series":  test.DefaultSeries,
		}
		if test.AgentVersion != version.Zero {
			attrs["agent-version"] = test.AgentVersion.String()
		}
		env, err := environs.NewFromAttrs(attrs)
		c.Assert(err, gc.IsNil)
		env, err = environs.Prepare(env.Config())
		c.Assert(err, gc.IsNil)
		envtesting.RemoveAllTools(c, env)

		version.Current = test.CliVersion
		for _, vers := range test.Available {
			envtesting.UploadFakeToolsVersion(c, env.Storage(), vers)
		}

		cons := constraints.Value{}
		if test.Arch != "" {
			cons = constraints.MustParse("arch=" + test.Arch)
		}
		err = bootstrap.Bootstrap(env, cons)
		if test.Err != nil {
			c.Check(err, gc.ErrorMatches, ".*"+test.Err.Error())
			continue
		} else {
			c.Assert(err, gc.IsNil)
		}
		unique := map[version.Number]bool{}
		for _, expected := range test.Expect {
			unique[expected.Number] = true
		}
		for expectAgentVersion := range unique {
			agentVersion, ok := env.Config().AgentVersion()
			c.Check(ok, gc.Equals, true)
			c.Check(agentVersion, gc.Equals, expectAgentVersion)
		}
	}
}

func (s *bootstrapSuite) TestBootstrapNeedsTools(c *gc.C) {
	env := newEnviron("foo", useDefaultKeys)
	cleanup := setDummyStorage(c, env)
	defer cleanup()
	envtesting.RemoveFakeTools(c, env.Storage())
	err := bootstrap.Bootstrap(env, constraints.Value{})
	c.Check(err, gc.ErrorMatches, "cannot find bootstrap tools: no tools available")
}

type bootstrapEnviron struct {
	name             string
	cfg              *config.Config
	environs.Environ // stub out all methods we don't care about.

	// The following fields are filled in when Bootstrap is called.
	bootstrapCount int
	constraints    constraints.Value
	storage        environs.Storage
}

func newEnviron(name string, defaultKeys bool) *bootstrapEnviron {
	m := map[string]interface{}{
		"name":            name,
		"type":            "test",
		"ca-cert":         "",
		"ca-private-key":  "",
		"authorized-keys": "",
	}
	if defaultKeys {
		m["ca-cert"] = coretesting.CACert
		m["ca-private-key"] = coretesting.CAKey
		m["admin-secret"] = version.Current.Number.String()
		m["authorized-keys"] = "foo"
	}
	cfg, err := config.New(m)
	if err != nil {
		panic(fmt.Errorf("cannot create config from %#v: %v", m, err))
	}
	return &bootstrapEnviron{
		name: name,
		cfg:  cfg,
	}
}

// setDummyStorage injects the local provider's fake storage implementation
// into the given environment, so that tests can manipulate storage as if it
// were real.
// Returns a cleanup function that must be called when done with the storage.
func setDummyStorage(c *gc.C, env *bootstrapEnviron) func() {
	listener, err := localstorage.Serve("127.0.0.1:0", c.MkDir())
	c.Assert(err, gc.IsNil)
	env.storage = localstorage.Client(listener.Addr().String())
	envtesting.UploadFakeTools(c, env.storage)
	return func() { listener.Close() }
}

func (e *bootstrapEnviron) Name() string {
	return e.name
}

func (e *bootstrapEnviron) Bootstrap(cons constraints.Value, possibleTools tools.List, machineID string) error {
	e.bootstrapCount++
	e.constraints = cons
	return nil
}

func (e *bootstrapEnviron) Config() *config.Config {
	return e.cfg
}

func (e *bootstrapEnviron) SetConfig(cfg *config.Config) error {
	e.cfg = cfg
	return nil
}

func (e *bootstrapEnviron) Storage() environs.Storage {
	return e.storage
}

func (e *bootstrapEnviron) PublicStorage() environs.StorageReader {
	return environs.EmptyStorage
}
<|endoftext|>


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

package simplestreams

import (
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/juju/errors"
	"github.com/juju/utils"
)

// A DataSource retrieves simplestreams metadata.
type DataSource interface {
	// Description describes the origin of this datasource.
	// eg agent-metadata-url, cloud storage, keystone catalog etc.
	Description() string
	// Fetch loads the data at the specified relative path. It returns a reader from which
	// the data can be retrieved as well as the full URL of the file. The full URL is typically
	// used in log messages to help diagnose issues accessing the data.
	Fetch(path string) (io.ReadCloser, string, error)
	// URL returns the full URL of the path, as applicable to this datasource.
	// This method is used primarily for logging purposes.
	URL(path string) (string, error)
	// SetAllowRetry sets the flag which determines if the datasource will retry fetching the metadata
	// if it is not immediately available.
	SetAllowRetry(allow bool)
}

// A urlDataSource retrieves data from an HTTP URL.
type urlDataSource struct {
	description          string
	baseURL              string
	hostnameVerification utils.SSLHostnameVerification
}

// NewURLDataSource returns a new datasource reading from the specified baseURL.
func NewURLDataSource(description, baseURL string, hostnameVerification utils.SSLHostnameVerification) DataSource {
	return &urlDataSource{
		description:          description,
		baseURL:              baseURL,
		hostnameVerification: hostnameVerification,
	}
}

// Description is defined in simplestreams.DataSource.
func (u *urlDataSource) Description() string {
	return u.description
}

func (u *urlDataSource) GoString() string {
	return fmt.Sprintf("%v: urlDataSource(%q)", u.description, u.baseURL)
}

// urlJoin returns baseURL + relpath making sure to have a '/' inbetween them
// This doesn't try to do anything fancy with URL query or parameter bits
// It also doesn't use path.Join because that normalizes slashes, and you need
// to keep both slashes in 'http://'.
func urlJoin(baseURL, relpath string) string {
	if strings.HasSuffix(baseURL, "/") {
		return baseURL + relpath
	}
	return baseURL + "/" + relpath
}

// Fetch is defined in simplestreams.DataSource.
func (h *urlDataSource) Fetch(path string) (io.ReadCloser, string, error) {
	dataURL := urlJoin(h.baseURL, path)
	client := utils.GetHTTPClient(h.hostnameVerification)
	// dataURL can be http:// or file://
	// MakeFileURL will only modify the URL if it's a file URL
	dataURL = utils.MakeFileURL(dataURL)
	resp, err := client.Get(dataURL)
	if err != nil {
		logger.Tracef("Got error requesting %q: %v", dataURL, err)
		return nil, dataURL, errors.NotFoundf("invalid URL %q", dataURL)
	}
	if resp.StatusCode == http.StatusNotFound {
		return nil, dataURL, errors.NotFoundf("cannot find URL %q", dataURL)
	}
	if resp.StatusCode == http.StatusUnauthorized {
		return nil, dataURL, errors.Unauthorizedf("unauthorised access to URL %q", dataURL)
	}
	if resp.StatusCode != http.StatusOK {
		return nil, dataURL, fmt.Errorf("cannot access URL %q, %q", dataURL, resp.Status)
	}
	return resp.Body, dataURL, nil
}

// URL is defined in simplestreams.DataSource.
func (h *urlDataSource) URL(path string) (string, error) {
	return utils.MakeFileURL(urlJoin(h.baseURL, path)), nil
}

// SetAllowRetry is defined in simplestreams.DataSource.
func (h *urlDataSource) SetAllowRetry(allow bool) {
	// This is a NOOP for url datasources.
}
<commit_msg>simplestreams: Ensure HTTP resp.Body will always get closed<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package simplestreams

import (
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/juju/errors"
	"github.com/juju/utils"
)

// A DataSource retrieves simplestreams metadata.
type DataSource interface {
	// Description describes the origin of this datasource.
	// eg agent-metadata-url, cloud storage, keystone catalog etc.
	Description() string
	// Fetch loads the data at the specified relative path. It returns a reader from which
	// the data can be retrieved as well as the full URL of the file. The full URL is typically
	// used in log messages to help diagnose issues accessing the data.
	Fetch(path string) (io.ReadCloser, string, error)
	// URL returns the full URL of the path, as applicable to this datasource.
	// This method is used primarily for logging purposes.
	URL(path string) (string, error)
	// SetAllowRetry sets the flag which determines if the datasource will retry fetching the metadata
	// if it is not immediately available.
	SetAllowRetry(allow bool)
}

// A urlDataSource retrieves data from an HTTP URL.
type urlDataSource struct {
	description          string
	baseURL              string
	hostnameVerification utils.SSLHostnameVerification
}

// NewURLDataSource returns a new datasource reading from the specified baseURL.
func NewURLDataSource(description, baseURL string, hostnameVerification utils.SSLHostnameVerification) DataSource {
	return &urlDataSource{
		description:          description,
		baseURL:              baseURL,
		hostnameVerification: hostnameVerification,
	}
}

// Description is defined in simplestreams.DataSource.
func (u *urlDataSource) Description() string {
	return u.description
}

func (u *urlDataSource) GoString() string {
	return fmt.Sprintf("%v: urlDataSource(%q)", u.description, u.baseURL)
}

// urlJoin returns baseURL + relpath making sure to have a '/' inbetween them
// This doesn't try to do anything fancy with URL query or parameter bits
// It also doesn't use path.Join because that normalizes slashes, and you need
// to keep both slashes in 'http://'.
func urlJoin(baseURL, relpath string) string {
	if strings.HasSuffix(baseURL, "/") {
		return baseURL + relpath
	}
	return baseURL + "/" + relpath
}

// Fetch is defined in simplestreams.DataSource.
func (h *urlDataSource) Fetch(path string) (io.ReadCloser, string, error) {
	dataURL := urlJoin(h.baseURL, path)
	client := utils.GetHTTPClient(h.hostnameVerification)
	// dataURL can be http:// or file://
	// MakeFileURL will only modify the URL if it's a file URL
	dataURL = utils.MakeFileURL(dataURL)
	resp, err := client.Get(dataURL)
	if err != nil {
		logger.Tracef("Got error requesting %q: %v", dataURL, err)
		return nil, dataURL, errors.NotFoundf("invalid URL %q", dataURL)
	}
	if resp.StatusCode == http.StatusNotFound {
		resp.Body.Close()
		return nil, dataURL, errors.NotFoundf("cannot find URL %q", dataURL)
	}
	if resp.StatusCode == http.StatusUnauthorized {
		resp.Body.Close()
		return nil, dataURL, errors.Unauthorizedf("unauthorised access to URL %q", dataURL)
	}
	if resp.StatusCode != http.StatusOK {
		resp.Body.Close()
		return nil, dataURL, fmt.Errorf("cannot access URL %q, %q", dataURL, resp.Status)
	}
	return resp.Body, dataURL, nil
}

// URL is defined in simplestreams.DataSource.
func (h *urlDataSource) URL(path string) (string, error) {
	return utils.MakeFileURL(urlJoin(h.baseURL, path)), nil
}

// SetAllowRetry is defined in simplestreams.DataSource.
func (h *urlDataSource) SetAllowRetry(allow bool) {
	// This is a NOOP for url datasources.
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"strings"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/sns"
)

const awsSNSPendingConfirmationMessage = "pending confirmation"
const awsSNSPendingConfirmationMessageWithoutSpaces = "pendingconfirmation"

func resourceAwsSnsTopicSubscription() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsSnsTopicSubscriptionCreate,
		Read:   resourceAwsSnsTopicSubscriptionRead,
		Update: resourceAwsSnsTopicSubscriptionUpdate,
		Delete: resourceAwsSnsTopicSubscriptionDelete,

		Schema: map[string]*schema.Schema{
			"protocol": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					forbidden := []string{"email", "sms"}
					for _, f := range forbidden {
						if strings.Contains(value, f) {
							errors = append(
								errors,
								fmt.Errorf("Unsupported protocol (%s) for SNS Topic", value),
							)
						}
					}
					return
				},
			},
			"endpoint": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"endpoint_auto_confirms": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			"confirmation_timeout_in_minutes": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  1,
			},
			"topic_arn": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"delivery_policy": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"raw_message_delivery": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			"arn": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceAwsSnsTopicSubscriptionCreate(d *schema.ResourceData, meta interface{}) error {
	snsconn := meta.(*AWSClient).snsconn

	output, err := subscribeToSNSTopic(d, snsconn)

	if err != nil {
		return err
	}

	if subscriptionHasPendingConfirmation(output.SubscriptionArn) {
		log.Printf("[WARN] Invalid SNS Subscription, received a \"%s\" ARN", awsSNSPendingConfirmationMessage)
		return nil
	}

	log.Printf("New subscription ARN: %s", *output.SubscriptionArn)
	d.SetId(*output.SubscriptionArn)

	// Write the ARN to the 'arn' field for export
	d.Set("arn", *output.SubscriptionArn)

	return resourceAwsSnsTopicSubscriptionUpdate(d, meta)
}

func resourceAwsSnsTopicSubscriptionUpdate(d *schema.ResourceData, meta interface{}) error {
	snsconn := meta.(*AWSClient).snsconn

	// If any changes happened, un-subscribe and re-subscribe
	if d.HasChange("protocol") || d.HasChange("endpoint") || d.HasChange("topic_arn") {
		log.Printf("[DEBUG] Updating subscription %s", d.Id())
		// Unsubscribe
		_, err := snsconn.Unsubscribe(&sns.UnsubscribeInput{
			SubscriptionArn: aws.String(d.Id()),
		})

		if err != nil {
			return fmt.Errorf("Error unsubscribing from SNS topic: %s", err)
		}

		// Re-subscribe and set id
		output, err := subscribeToSNSTopic(d, snsconn)
		d.SetId(*output.SubscriptionArn)
		d.Set("arn", *output.SubscriptionArn)
	}

	if d.HasChange("raw_message_delivery") {
		_, n := d.GetChange("raw_message_delivery")

		attrValue := "false"

		if n.(bool) {
			attrValue = "true"
		}

		req := &sns.SetSubscriptionAttributesInput{
			SubscriptionArn: aws.String(d.Id()),
			AttributeName:   aws.String("RawMessageDelivery"),
			AttributeValue:  aws.String(attrValue),
		}
		_, err := snsconn.SetSubscriptionAttributes(req)

		if err != nil {
			return fmt.Errorf("Unable to set raw message delivery attribute on subscription")
		}
	}

	return resourceAwsSnsTopicSubscriptionRead(d, meta)
}

func resourceAwsSnsTopicSubscriptionRead(d *schema.ResourceData, meta interface{}) error {
	snsconn := meta.(*AWSClient).snsconn

	log.Printf("[DEBUG] Loading subscription %s", d.Id())

	attributeOutput, err := snsconn.GetSubscriptionAttributes(&sns.GetSubscriptionAttributesInput{
		SubscriptionArn: aws.String(d.Id()),
	})
	if err != nil {
		return err
	}

	if attributeOutput.Attributes != nil && len(attributeOutput.Attributes) > 0 {
		attrHash := attributeOutput.Attributes
		log.Printf("[DEBUG] raw message delivery: %s", *attrHash["RawMessageDelivery"])
		if *attrHash["RawMessageDelivery"] == "true" {
			d.Set("raw_message_delivery", true)
		} else {
			d.Set("raw_message_delivery", false)
		}
	}

	return nil
}

func resourceAwsSnsTopicSubscriptionDelete(d *schema.ResourceData, meta interface{}) error {
	snsconn := meta.(*AWSClient).snsconn

	log.Printf("[DEBUG] SNS delete topic subscription: %s", d.Id())
	_, err := snsconn.Unsubscribe(&sns.UnsubscribeInput{
		SubscriptionArn: aws.String(d.Id()),
	})
	if err != nil {
		return err
	}
	return nil
}

func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns.SubscribeOutput, err error) {
	protocol := d.Get("protocol").(string)
	endpoint := d.Get("endpoint").(string)
	topic_arn := d.Get("topic_arn").(string)
	endpoint_auto_confirms := d.Get("endpoint_auto_confirms").(bool)
	confirmation_timeout_in_minutes := d.Get("confirmation_timeout_in_minutes").(int)

	if strings.Contains(protocol, "http") && !endpoint_auto_confirms {
		return nil, fmt.Errorf("Protocol http/https is only supported for endpoints which auto confirms!")
	}

	log.Printf("[DEBUG] SNS create topic subscription: %s (%s) @ '%s'", endpoint, protocol, topic_arn)

	req := &sns.SubscribeInput{
		Protocol: aws.String(protocol),
		Endpoint: aws.String(endpoint),
		TopicArn: aws.String(topic_arn),
	}

	output, err = snsconn.Subscribe(req)
	if err != nil {
		return nil, fmt.Errorf("Error creating SNS topic: %s", err)
	}

	log.Printf("[DEBUG] Finished subscribing to topic %s with subscription arn %s", topic_arn, *output.SubscriptionArn)

	if strings.Contains(protocol, "http") && subscriptionHasPendingConfirmation(output.SubscriptionArn) {

		log.Printf("[DEBUG] SNS create topic subscription is pending so fetching the subscription list for topic : %s (%s) @ '%s'", endpoint, protocol, topic_arn)

		err = resource.Retry(time.Duration(confirmation_timeout_in_minutes)*time.Minute, func() *resource.RetryError {

			subscription, err := findSubscriptionByNonID(d, snsconn)

			if subscription != nil {
				output.SubscriptionArn = subscription.SubscriptionArn
				return nil
			}

			if err != nil {
				return resource.RetryableError(
					fmt.Errorf("Error fetching subscriptions for SNS topic %s: %s", topic_arn, err))
			}

			return resource.RetryableError(
				fmt.Errorf("Endpoint (%s) did not autoconfirm the subscription for topic %s", endpoint, topic_arn))
		})

		if err != nil {
			return nil, err
		}
	}

	log.Printf("[DEBUG] Created new subscription! %s", *output.SubscriptionArn)
	return output, nil
}

// finds a subscription using protocol, endpoint and topic_arn (which is a key in sns subscription)
func findSubscriptionByNonID(d *schema.ResourceData, snsconn *sns.SNS) (*sns.Subscription, error) {
	protocol := d.Get("protocol").(string)
	endpoint := d.Get("endpoint").(string)
	topic_arn := d.Get("topic_arn").(string)

	req := &sns.ListSubscriptionsByTopicInput{
		TopicArn: aws.String(topic_arn),
	}

	for {

		res, err := snsconn.ListSubscriptionsByTopic(req)

		if err != nil {
			return nil, fmt.Errorf("Error fetching subscripitions for topic %s : %s", topic_arn, err)
		}

		for _, subscription := range res.Subscriptions {
			log.Printf("[DEBUG] check subscription with EndPoint %s, Protocol %s,  topicARN %s and SubscriptionARN %s", *subscription.Endpoint, *subscription.Protocol, *subscription.TopicArn, *subscription.SubscriptionArn)
			if *subscription.Endpoint == endpoint && *subscription.Protocol == protocol && *subscription.TopicArn == topic_arn && !subscriptionHasPendingConfirmation(subscription.SubscriptionArn) {
				return subscription, nil
			}
		}

		// if there are more than 100 subscriptions then go to the next 100 otherwise return nil
		if res.NextToken != nil {
			req.NextToken = res.NextToken
		} else {
			return nil, nil
		}
	}
}

// returns true if arn is nil or has both pending and confirmation words in the arn
func subscriptionHasPendingConfirmation(arn *string) bool {
	if arn != nil && !strings.Contains(strings.Replace(strings.ToLower(*arn), " ", "", -1), awsSNSPendingConfirmationMessageWithoutSpaces) {
		return false
	}

	return true
}
<commit_msg>provider/aws: Updating state when `aws_sns_topic_subscription` is (#6629)<commit_after>package aws

import (
	"fmt"
	"log"
	"strings"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/sns"
)

const awsSNSPendingConfirmationMessage = "pending confirmation"
const awsSNSPendingConfirmationMessageWithoutSpaces = "pendingconfirmation"

func resourceAwsSnsTopicSubscription() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsSnsTopicSubscriptionCreate,
		Read:   resourceAwsSnsTopicSubscriptionRead,
		Update: resourceAwsSnsTopicSubscriptionUpdate,
		Delete: resourceAwsSnsTopicSubscriptionDelete,

		Schema: map[string]*schema.Schema{
			"protocol": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: false,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					forbidden := []string{"email", "sms"}
					for _, f := range forbidden {
						if strings.Contains(value, f) {
							errors = append(
								errors,
								fmt.Errorf("Unsupported protocol (%s) for SNS Topic", value),
							)
						}
					}
					return
				},
			},
			"endpoint": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"endpoint_auto_confirms": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			"confirmation_timeout_in_minutes": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  1,
			},
			"topic_arn": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"delivery_policy": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"raw_message_delivery": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			"arn": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceAwsSnsTopicSubscriptionCreate(d *schema.ResourceData, meta interface{}) error {
	snsconn := meta.(*AWSClient).snsconn

	output, err := subscribeToSNSTopic(d, snsconn)

	if err != nil {
		return err
	}

	if subscriptionHasPendingConfirmation(output.SubscriptionArn) {
		log.Printf("[WARN] Invalid SNS Subscription, received a \"%s\" ARN", awsSNSPendingConfirmationMessage)
		return nil
	}

	log.Printf("New subscription ARN: %s", *output.SubscriptionArn)
	d.SetId(*output.SubscriptionArn)

	// Write the ARN to the 'arn' field for export
	d.Set("arn", *output.SubscriptionArn)

	return resourceAwsSnsTopicSubscriptionUpdate(d, meta)
}

func resourceAwsSnsTopicSubscriptionUpdate(d *schema.ResourceData, meta interface{}) error {
	snsconn := meta.(*AWSClient).snsconn

	// If any changes happened, un-subscribe and re-subscribe
	if d.HasChange("protocol") || d.HasChange("endpoint") || d.HasChange("topic_arn") {
		log.Printf("[DEBUG] Updating subscription %s", d.Id())
		// Unsubscribe
		_, err := snsconn.Unsubscribe(&sns.UnsubscribeInput{
			SubscriptionArn: aws.String(d.Id()),
		})

		if err != nil {
			return fmt.Errorf("Error unsubscribing from SNS topic: %s", err)
		}

		// Re-subscribe and set id
		output, err := subscribeToSNSTopic(d, snsconn)
		d.SetId(*output.SubscriptionArn)
		d.Set("arn", *output.SubscriptionArn)
	}

	if d.HasChange("raw_message_delivery") {
		_, n := d.GetChange("raw_message_delivery")

		attrValue := "false"

		if n.(bool) {
			attrValue = "true"
		}

		req := &sns.SetSubscriptionAttributesInput{
			SubscriptionArn: aws.String(d.Id()),
			AttributeName:   aws.String("RawMessageDelivery"),
			AttributeValue:  aws.String(attrValue),
		}
		_, err := snsconn.SetSubscriptionAttributes(req)

		if err != nil {
			return fmt.Errorf("Unable to set raw message delivery attribute on subscription")
		}
	}

	return resourceAwsSnsTopicSubscriptionRead(d, meta)
}

func resourceAwsSnsTopicSubscriptionRead(d *schema.ResourceData, meta interface{}) error {
	snsconn := meta.(*AWSClient).snsconn

	log.Printf("[DEBUG] Loading subscription %s", d.Id())

	attributeOutput, err := snsconn.GetSubscriptionAttributes(&sns.GetSubscriptionAttributesInput{
		SubscriptionArn: aws.String(d.Id()),
	})
	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NotFound" {
			log.Printf("[WARN] SNS Topic Subscription (%s) not found, error code (404)", d.Id())
			d.SetId("")
			return nil
		}

		return err
	}

	if attributeOutput.Attributes != nil && len(attributeOutput.Attributes) > 0 {
		attrHash := attributeOutput.Attributes
		log.Printf("[DEBUG] raw message delivery: %s", *attrHash["RawMessageDelivery"])
		if *attrHash["RawMessageDelivery"] == "true" {
			d.Set("raw_message_delivery", true)
		} else {
			d.Set("raw_message_delivery", false)
		}
	}

	return nil
}

func resourceAwsSnsTopicSubscriptionDelete(d *schema.ResourceData, meta interface{}) error {
	snsconn := meta.(*AWSClient).snsconn

	log.Printf("[DEBUG] SNS delete topic subscription: %s", d.Id())
	_, err := snsconn.Unsubscribe(&sns.UnsubscribeInput{
		SubscriptionArn: aws.String(d.Id()),
	})
	if err != nil {
		return err
	}
	return nil
}

func subscribeToSNSTopic(d *schema.ResourceData, snsconn *sns.SNS) (output *sns.SubscribeOutput, err error) {
	protocol := d.Get("protocol").(string)
	endpoint := d.Get("endpoint").(string)
	topic_arn := d.Get("topic_arn").(string)
	endpoint_auto_confirms := d.Get("endpoint_auto_confirms").(bool)
	confirmation_timeout_in_minutes := d.Get("confirmation_timeout_in_minutes").(int)

	if strings.Contains(protocol, "http") && !endpoint_auto_confirms {
		return nil, fmt.Errorf("Protocol http/https is only supported for endpoints which auto confirms!")
	}

	log.Printf("[DEBUG] SNS create topic subscription: %s (%s) @ '%s'", endpoint, protocol, topic_arn)

	req := &sns.SubscribeInput{
		Protocol: aws.String(protocol),
		Endpoint: aws.String(endpoint),
		TopicArn: aws.String(topic_arn),
	}

	output, err = snsconn.Subscribe(req)
	if err != nil {
		return nil, fmt.Errorf("Error creating SNS topic: %s", err)
	}

	log.Printf("[DEBUG] Finished subscribing to topic %s with subscription arn %s", topic_arn, *output.SubscriptionArn)

	if strings.Contains(protocol, "http") && subscriptionHasPendingConfirmation(output.SubscriptionArn) {

		log.Printf("[DEBUG] SNS create topic subscription is pending so fetching the subscription list for topic : %s (%s) @ '%s'", endpoint, protocol, topic_arn)

		err = resource.Retry(time.Duration(confirmation_timeout_in_minutes)*time.Minute, func() *resource.RetryError {

			subscription, err := findSubscriptionByNonID(d, snsconn)

			if subscription != nil {
				output.SubscriptionArn = subscription.SubscriptionArn
				return nil
			}

			if err != nil {
				return resource.RetryableError(
					fmt.Errorf("Error fetching subscriptions for SNS topic %s: %s", topic_arn, err))
			}

			return resource.RetryableError(
				fmt.Errorf("Endpoint (%s) did not autoconfirm the subscription for topic %s", endpoint, topic_arn))
		})

		if err != nil {
			return nil, err
		}
	}

	log.Printf("[DEBUG] Created new subscription! %s", *output.SubscriptionArn)
	return output, nil
}

// finds a subscription using protocol, endpoint and topic_arn (which is a key in sns subscription)
func findSubscriptionByNonID(d *schema.ResourceData, snsconn *sns.SNS) (*sns.Subscription, error) {
	protocol := d.Get("protocol").(string)
	endpoint := d.Get("endpoint").(string)
	topic_arn := d.Get("topic_arn").(string)

	req := &sns.ListSubscriptionsByTopicInput{
		TopicArn: aws.String(topic_arn),
	}

	for {

		res, err := snsconn.ListSubscriptionsByTopic(req)

		if err != nil {
			return nil, fmt.Errorf("Error fetching subscripitions for topic %s : %s", topic_arn, err)
		}

		for _, subscription := range res.Subscriptions {
			log.Printf("[DEBUG] check subscription with EndPoint %s, Protocol %s,  topicARN %s and SubscriptionARN %s", *subscription.Endpoint, *subscription.Protocol, *subscription.TopicArn, *subscription.SubscriptionArn)
			if *subscription.Endpoint == endpoint && *subscription.Protocol == protocol && *subscription.TopicArn == topic_arn && !subscriptionHasPendingConfirmation(subscription.SubscriptionArn) {
				return subscription, nil
			}
		}

		// if there are more than 100 subscriptions then go to the next 100 otherwise return nil
		if res.NextToken != nil {
			req.NextToken = res.NextToken
		} else {
			return nil, nil
		}
	}
}

// returns true if arn is nil or has both pending and confirmation words in the arn
func subscriptionHasPendingConfirmation(arn *string) bool {
	if arn != nil && !strings.Contains(strings.Replace(strings.ToLower(*arn), " ", "", -1), awsSNSPendingConfirmationMessageWithoutSpaces) {
		return false
	}

	return true
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/kidoman/embd"
	_ "github.com/kidoman/embd/host/all"
	"github.com/stianeikeland/go-rpio"
	"os"
	"strings"
	"time"
)

var (
	mode = 0 // 0=go-rpio,1=embd

	ledRedPin    = 4
	ledYellowPin = 17
	ledGreenPin  = 27
	ledToColor   = map[int]string{}

	ledRed    = rpio.Pin(4)
	ledYellow = rpio.Pin(17)
	ledGreen  = rpio.Pin(27)

	ledRedEmbd, _    = embd.NewDigitalPin(4)
	ledYellowEmbd, _ = embd.NewDigitalPin(17)
	ledGreenEmbd, _  = embd.NewDigitalPin(27)

	ledMapEmbd = map[int]embd.DigitalPin{}
	ledMap     = map[int]rpio.Pin{}
)

func getLEDString(color string) string {
	return "Toggle " + strings.ToUpper(color)
}

func getToggledValue(pin embd.DigitalPin) int {
	val, _ := pin.Read()
	if val == embd.High {
		return embd.Low
	} else {
		return embd.High
	}
}

func toggleLEDEmbd(pin embd.DigitalPin, color string) {
	fmt.Println(getLEDString(color))
	toggledValue := getToggledValue(pin)
	fmt.Println("Val to write", toggledValue)
	embd.DigitalWrite(pin.N(), embd.High)
}

func toggleLED(pin rpio.Pin, color string) {
	fmt.Println(getLEDString(color))
	fmt.Println("Current value:", pin.Read())
	pin.Toggle()
}

func initLEDs() {
	if mode == 1 {
		ledMapEmbd[0] = ledRedEmbd
		ledMapEmbd[1] = ledYellowEmbd
		ledMapEmbd[2] = ledGreenEmbd
		for i := 0; i < len(ledMapEmbd); i++ {
			ledMapEmbd[i].SetDirection(embd.Out)
			embd.DigitalWrite(ledMapEmbd[i].N(), embd.Low)
		}
	} else {
		ledMap[0] = ledRed
		ledMap[1] = ledYellow
		ledMap[2] = ledGreen
		for i := 0; i < len(ledMap); i++ {
			ledMap[i].Output()
			ledMap[i].Low()
		}
	}
	ledToColor[0] = "red"
	ledToColor[1] = "yellow"
	ledToColor[2] = "green"
}

func initGPIO() error {
	if mode == 1 {
		return embd.InitGPIO()
	} else {
		return rpio.Open()
	}
}

func main() {
	fmt.Println("Parsing parameters")
	num := flag.Int("num", 0, "number of blinks")
	modeFromCli := flag.Int("mode", 0, "mode")
	flag.Parse()
	mode = *modeFromCli
	if mode == 1 {
		fmt.Println("Running using Embd.io")
	} else {
		fmt.Println("Running using go-rpio")
	}

	fmt.Println("Number of blinks:", *num)

	var err = initGPIO()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	initLEDs()

	if mode == 1 {
		defer embd.CloseGPIO()
	} else {
		defer rpio.Close()
	}

	for i := 0; i < *num; i++ {
		if mode == 1 {
			toggleLEDEmbd(ledMapEmbd[i%3], ledToColor[i%3])
		} else {
			toggleLED(ledMap[i%3], ledToColor[i%3])
		}
		time.Sleep(time.Second)
	}
}
<commit_msg>embd: fixed pin write<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/kidoman/embd"
	_ "github.com/kidoman/embd/host/all"
	"github.com/stianeikeland/go-rpio"
	"os"
	"strings"
	"time"
)

var (
	mode = 0 // 0=go-rpio,1=embd

	ledRedPin    = 4
	ledYellowPin = 17
	ledGreenPin  = 27
	ledToColor   = map[int]string{}

	ledRed    = rpio.Pin(4)
	ledYellow = rpio.Pin(17)
	ledGreen  = rpio.Pin(27)

	ledRedEmbd, _    = embd.NewDigitalPin(4)
	ledYellowEmbd, _ = embd.NewDigitalPin(17)
	ledGreenEmbd, _  = embd.NewDigitalPin(27)

	ledMapEmbd = map[int]embd.DigitalPin{}
	ledMap     = map[int]rpio.Pin{}
)

func getLEDString(color string) string {
	return "Toggle " + strings.ToUpper(color)
}

func getToggledValue(pin embd.DigitalPin) int {
	val, _ := pin.Read()
	if val == embd.High {
		return embd.Low
	} else {
		return embd.High
	}
}

func toggleLEDEmbd(pin embd.DigitalPin, color string) {
	fmt.Println(getLEDString(color))
	toggledValue := getToggledValue(pin)
	fmt.Println("Val to write", toggledValue)
	embd.DigitalWrite(pin.N(), toggledValue)
}

func toggleLED(pin rpio.Pin, color string) {
	fmt.Println(getLEDString(color))
	fmt.Println("Current value:", pin.Read())
	pin.Toggle()
}

func initLEDs() {
	if mode == 1 {
		ledMapEmbd[0] = ledRedEmbd
		ledMapEmbd[1] = ledYellowEmbd
		ledMapEmbd[2] = ledGreenEmbd
		for i := 0; i < len(ledMapEmbd); i++ {
			ledMapEmbd[i].SetDirection(embd.Out)
			embd.DigitalWrite(ledMapEmbd[i].N(), embd.Low)
		}
	} else {
		ledMap[0] = ledRed
		ledMap[1] = ledYellow
		ledMap[2] = ledGreen
		for i := 0; i < len(ledMap); i++ {
			ledMap[i].Output()
			ledMap[i].Low()
		}
	}
	ledToColor[0] = "red"
	ledToColor[1] = "yellow"
	ledToColor[2] = "green"
}

func initGPIO() error {
	if mode == 1 {
		return embd.InitGPIO()
	} else {
		return rpio.Open()
	}
}

func main() {
	fmt.Println("Parsing parameters")
	num := flag.Int("num", 0, "number of blinks")
	modeFromCli := flag.Int("mode", 0, "mode")
	flag.Parse()
	mode = *modeFromCli
	if mode == 1 {
		fmt.Println("Running using Embd.io")
	} else {
		fmt.Println("Running using go-rpio")
	}

	fmt.Println("Number of blinks:", *num)

	var err = initGPIO()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	initLEDs()

	if mode == 1 {
		defer embd.CloseGPIO()
	} else {
		defer rpio.Close()
	}

	for i := 0; i < *num; i++ {
		if mode == 1 {
			toggleLEDEmbd(ledMapEmbd[i%3], ledToColor[i%3])
		} else {
			toggleLED(ledMap[i%3], ledToColor[i%3])
		}
		time.Sleep(time.Second)
	}
}
<|endoftext|>


<commit_before>package main

import "fmt"
import "github.com/stianeikeland/go-rpio"
import "os"
import "time"

var pin = rpio.Pin(4)

func main() {
    if (err := rpio.Open(): err != nil) {
        fmt.Println(err)
        os.Exit(1)
    }

    defer rpio.Close()

    pin.Output()

    for i := 0; i < 20; i++ {
        pin.Toggle()
        time.Sleep(time.Second)
    }
}
<commit_msg>Error handling<commit_after>package main

import "fmt"
import "github.com/stianeikeland/go-rpio"
import "os"
import "time"

var pin = rpio.Pin(4)

func main() {
    fmt.Println("Opening rpio")

    var err = rpio.Open()

    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    defer rpio.Close()

    fmt.Println("Pin as output")
    pin.Output()

    for i := 0; i < 20; i++ {
        fmt.Println("Toggle")
        pin.Toggle()
        time.Sleep(time.Second)
    }
}
<|endoftext|>