<commit_before>// build

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

package p

func bar() {
	f := func() {}
	foo(&f)
}

//go:noinline
func foo(f *func()) func() {
	return *f
}
<commit_msg>test: change issue10441.go from "build" to "compile"<commit_after>// compile

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

package p

func bar() {
	f := func() {}
	foo(&f)
}

//go:noinline
func foo(f *func()) func() {
	return *f
}
<|endoftext|>


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

// Process plain text into HTML.
//	- h2's are made from lines followed by a line "----\n"
//	- tab-indented blocks become
 blocks
//	- blank lines become
 marks
//	- "quoted strings" become quoted strings

package main

import (
	"bufio"
	"bytes"
	"log"
	"os"
)

var (
	lines   = make([][]byte, 0, 10000) // assume big enough
	linebuf = make([]byte, 10000)      // assume big enough

	empty   = []byte("")
	newline = []byte("\n")
	tab     = []byte("\t")
	quote   = []byte(`"`)

	sectionMarker = []byte("----\n")
	preStart      = []byte("
")
	preEnd        = []byte("
\n")
	pp            = []byte("
\n")
)

func main() {
	read()
	headings()
	paragraphs()
	coalesce(preStart, foldPre)
	coalesce(tab, foldTabs)
	quotes()
	write()
}

func read() {
	b := bufio.NewReader(os.Stdin)
	for {
		line, err := b.ReadBytes('\n')
		if err == os.EOF {
			break
		}
		if err != nil {
			log.Fatal(err)
		}
		n := len(lines)
		lines = lines[0 : n+1]
		lines[n] = line
	}
}

func write() {
	b := bufio.NewWriter(os.Stdout)
	for _, line := range lines {
		b.Write(expandTabs(line))
	}
	b.Flush()
}

// each time prefix is found on a line, call fold and replace
// line with return value from fold.
func coalesce(prefix []byte, fold func(i int) (n int, line []byte)) {
	j := 0 // output line number goes up by one each loop
	for i := 0; i < len(lines); {
		if bytes.HasPrefix(lines[i], prefix) {
			nlines, block := fold(i)
			lines[j] = block
			i += nlines
		} else {
			lines[j] = lines[i]
			i++
		}
		j++
	}
	lines = lines[0:j]
}

// return the
 block as a single slice
func foldPre(i int) (n int, line []byte) {
	buf := new(bytes.Buffer)
	for i < len(lines) {
		buf.Write(lines[i])
		n++
		if bytes.Equal(lines[i], preEnd) {
			break
		}
		i++
	}
	return n, buf.Bytes()
}

// return the tab-indented block as a single
-bounded slice
func foldTabs(i int) (n int, line []byte) {
	buf := new(bytes.Buffer)
	buf.WriteString("
\n")
	for i < len(lines) {
		if !bytes.HasPrefix(lines[i], tab) {
			break
		}
		buf.Write(lines[i])
		n++
		i++
	}
	buf.WriteString("
\n")
	return n, buf.Bytes()
}

func headings() {
	b := bufio.NewWriter(os.Stdout)
	for i, l := range lines {
		if i > 0 && bytes.Equal(l, sectionMarker) {
			lines[i-1] = []byte("
" + string(trim(lines[i-1])) + "
\n")
			lines[i] = empty
		}
	}
	b.Flush()
}

func paragraphs() {
	for i, l := range lines {
		if bytes.Equal(l, newline) {
			lines[i] = pp
		}
	}
}

func quotes() {
	for i, l := range lines {
		lines[i] = codeQuotes(l)
	}
}

func codeQuotes(l []byte) []byte {
	if bytes.HasPrefix(l, preStart) {
		return l
	}
	n := bytes.Index(l, quote)
	if n < 0 {
		return l
	}
	buf := new(bytes.Buffer)
	inQuote := false
	for _, c := range l {
		if c == '"' {
			if inQuote {
				buf.WriteString("")
			} else {
				buf.WriteString("")
			}
			inQuote = !inQuote
		} else {
			buf.WriteByte(c)
		}
	}
	return buf.Bytes()
}

// drop trailing newline
func trim(l []byte) []byte {
	n := len(l)
	if n > 0 && l[n-1] == '\n' {
		return l[0 : n-1]
	}
	return l
}

// expand tabs to 4 spaces. don't worry about columns.
func expandTabs(l []byte) []byte {
	j := 0 // position in linebuf.
	for _, c := range l {
		if c == '\t' {
			for k := 0; k < 4; k++ {
				linebuf[j] = ' '
				j++
			}
		} else {
			linebuf[j] = c
			j++
		}
	}
	return linebuf[0:j]
}
<commit_msg>makehtml: use append The program is old and missed its opportunity.<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Process plain text into HTML.
//	- h2's are made from lines followed by a line "----\n"
//	- tab-indented blocks become
 blocks
//	- blank lines become
 marks
//	- "quoted strings" become quoted strings

package main

import (
	"bufio"
	"bytes"
	"log"
	"os"
)

var (
	lines = make([][]byte, 0, 2000) // probably big enough; grows if not

	empty   = []byte("")
	newline = []byte("\n")
	tab     = []byte("\t")
	quote   = []byte(`"`)
	indent  = []byte{' ', ' ', ' ', ' '}

	sectionMarker = []byte("----\n")
	preStart      = []byte("
")
	preEnd        = []byte("
\n")
	pp            = []byte("
\n")
)

func main() {
	read()
	headings()
	paragraphs()
	coalesce(preStart, foldPre)
	coalesce(tab, foldTabs)
	quotes()
	write()
}

func read() {
	b := bufio.NewReader(os.Stdin)
	for {
		line, err := b.ReadBytes('\n')
		if err == os.EOF {
			break
		}
		if err != nil {
			log.Fatal(err)
		}
		lines = append(lines, line)
	}
}

func write() {
	b := bufio.NewWriter(os.Stdout)
	for _, line := range lines {
		b.Write(expandTabs(line))
	}
	b.Flush()
}

// each time prefix is found on a line, call fold and replace
// line with return value from fold.
func coalesce(prefix []byte, fold func(i int) (n int, line []byte)) {
	j := 0 // output line number goes up by one each loop
	for i := 0; i < len(lines); {
		if bytes.HasPrefix(lines[i], prefix) {
			nlines, block := fold(i)
			lines[j] = block
			i += nlines
		} else {
			lines[j] = lines[i]
			i++
		}
		j++
	}
	lines = lines[0:j]
}

// return the
 block as a single slice
func foldPre(i int) (n int, line []byte) {
	buf := new(bytes.Buffer)
	for i < len(lines) {
		buf.Write(lines[i])
		n++
		if bytes.Equal(lines[i], preEnd) {
			break
		}
		i++
	}
	return n, buf.Bytes()
}

// return the tab-indented block as a single
-bounded slice
func foldTabs(i int) (n int, line []byte) {
	buf := new(bytes.Buffer)
	buf.WriteString("
\n")
	for i < len(lines) {
		if !bytes.HasPrefix(lines[i], tab) {
			break
		}
		buf.Write(lines[i])
		n++
		i++
	}
	buf.WriteString("
\n")
	return n, buf.Bytes()
}

func headings() {
	b := bufio.NewWriter(os.Stdout)
	for i, l := range lines {
		if i > 0 && bytes.Equal(l, sectionMarker) {
			lines[i-1] = []byte("
" + string(trim(lines[i-1])) + "
\n")
			lines[i] = empty
		}
	}
	b.Flush()
}

func paragraphs() {
	for i, l := range lines {
		if bytes.Equal(l, newline) {
			lines[i] = pp
		}
	}
}

func quotes() {
	for i, l := range lines {
		lines[i] = codeQuotes(l)
	}
}

func codeQuotes(l []byte) []byte {
	if bytes.HasPrefix(l, preStart) {
		return l
	}
	n := bytes.Index(l, quote)
	if n < 0 {
		return l
	}
	buf := new(bytes.Buffer)
	inQuote := false
	for _, c := range l {
		if c == '"' {
			if inQuote {
				buf.WriteString("")
			} else {
				buf.WriteString("")
			}
			inQuote = !inQuote
		} else {
			buf.WriteByte(c)
		}
	}
	return buf.Bytes()
}

// drop trailing newline
func trim(l []byte) []byte {
	n := len(l)
	if n > 0 && l[n-1] == '\n' {
		return l[0 : n-1]
	}
	return l
}

// expand tabs to spaces. don't worry about columns.
func expandTabs(l []byte) []byte {
	return bytes.Replace(l, tab, indent, -1)
}
<|endoftext|>


<commit_before><commit_msg>Add InsertAVP function to the Message type<commit_after><|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)

package fuseutil

import (
	"bazil.org/fuse"
	fusefs "bazil.org/fuse/fs"
	"golang.org/x/net/context"
)

// A struct representing the status of a mount operation, with methods for
// waiting on the mount to complete, waiting for unmounting, and causing
// unmounting.
type MountedFileSystem struct {
	dir string

	// The result to return from WaitForReady. Not valid until the channel is closed.
	readyStatus          error
	readyStatusAvailable chan struct{}
}

// Wait until the mount point is ready to be used. After a successful return
// from this function, the contents of the mounted file system should be
// visible in the directory supplied to NewMountPoint. May be called multiple
// times.
func (mfs *MountedFileSystem) WaitForReady(ctx context.Context) error {
	select {
	case <-mfs.readyStatusAvailable:
		return mfs.readyStatus
	case <-ctx.Done():
		return ctx.Err()
	}
}

// Block until the file system has been unmounted. The return value will be
// non-nil if anything unexpected happened while mounting or serving. May be
// called multiple times.
func (mfs *MountedFileSystem) Join() error

// Attempt to unmount the file system. Use Join to wait for it to actually be
// unmounted. You must first call WaitForReady to ensure there is no race with
// mounting.
func (mfs *MountedFileSystem) Unmount() error

// Runs in the background.
func (mfs *MountedFileSystem) mountAndServe(
	fs fusefs.FS,
	options []fuse.MountOption)

// Attempt to mount the supplied file system on the given directory.
// mfs.WaitForReady() must be called to find out whether the mount was
// successful.
func MountFileSystem(
	dir string,
	fs fusefs.FS,
	options ...fuse.MountOption) (mfs *MountedFileSystem) {
	// Initialize the struct.
	mfs = &MountedFileSystem{
		dir:                  dir,
		readyStatusAvailable: make(chan struct{}),
	}

	// Mount in the background.
	go mfs.mountAndServe(fs, options)

	return mfs
}
<commit_msg>Implemented Join.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)

package fuseutil

import (
	"bazil.org/fuse"
	fusefs "bazil.org/fuse/fs"
	"golang.org/x/net/context"
)

// A struct representing the status of a mount operation, with methods for
// waiting on the mount to complete, waiting for unmounting, and causing
// unmounting.
type MountedFileSystem struct {
	dir string

	// The result to return from WaitForReady. Not valid until the channel is
	// closed.
	readyStatus          error
	readyStatusAvailable chan struct{}

	// The result to return from Join. Not valid until the channel is closed.
	joinStatus          error
	joinStatusAvailable chan struct{}
}

// Wait until the mount point is ready to be used. After a successful return
// from this function, the contents of the mounted file system should be
// visible in the directory supplied to NewMountPoint. May be called multiple
// times.
func (mfs *MountedFileSystem) WaitForReady(ctx context.Context) error {
	select {
	case <-mfs.readyStatusAvailable:
		return mfs.readyStatus
	case <-ctx.Done():
		return ctx.Err()
	}
}

// Block until the file system has been unmounted. The return value will be
// non-nil if anything unexpected happened while mounting or serving. May be
// called multiple times.
func (mfs *MountedFileSystem) Join(ctx context.Context) error {
	select {
	case <-mfs.joinStatusAvailable:
		return mfs.joinStatus
	case <-ctx.Done():
		return ctx.Err()
	}
}

// Attempt to unmount the file system. Use Join to wait for it to actually be
// unmounted. You must first call WaitForReady to ensure there is no race with
// mounting.
func (mfs *MountedFileSystem) Unmount() error

// Runs in the background.
func (mfs *MountedFileSystem) mountAndServe(
	fs fusefs.FS,
	options []fuse.MountOption)

// Attempt to mount the supplied file system on the given directory.
// mfs.WaitForReady() must be called to find out whether the mount was
// successful.
func MountFileSystem(
	dir string,
	fs fusefs.FS,
	options ...fuse.MountOption) (mfs *MountedFileSystem) {
	// Initialize the struct.
	mfs = &MountedFileSystem{
		dir:                  dir,
		readyStatusAvailable: make(chan struct{}),
	}

	// Mount in the background.
	go mfs.mountAndServe(fs, options)

	return mfs
}
<|endoftext|>


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

package runtime

import "unsafe"

// Called to initialize a new m (including the bootstrap m).
// Called on the parent thread (main thread in case of bootstrap), can allocate memory.
func mpreinit(mp *m) {
	// Initialize stack and goroutine for note handling.
	mp.gsignal = malg(32 * 1024)
	mp.gsignal.m = mp
	mp.notesig = (*int8)(mallocgc(_ERRMAX, nil, _FlagNoScan))
	// Initialize stack for handling strings from the
	// errstr system call, as used in package syscall.
	mp.errstr = (*byte)(mallocgc(_ERRMAX, nil, _FlagNoScan))
}

// Called to initialize a new m (including the bootstrap m).
// Called on the new thread, can not allocate memory.
func minit() {
	// Mask all SSE floating-point exceptions
	// when running on the 64-bit kernel.
	setfpmasks()
}

// Called from dropm to undo the effect of an minit.
func unminit() {
}

var sysstat = []byte("/dev/sysstat\x00")

func getproccount() int32 {
	var buf [2048]byte
	fd := open(&sysstat[0], _OREAD, 0)
	if fd < 0 {
		return 1
	}
	ncpu := int32(0)
	for {
		n := read(fd, unsafe.Pointer(&buf), int32(len(buf)))
		if n <= 0 {
			break
		}
		for i := int32(0); i < n; i++ {
			if buf[i] == '\n' {
				ncpu++
			}
		}
	}
	close(fd)
	if ncpu == 0 {
		ncpu = 1
	}
	return ncpu
}

var pid = []byte("#c/pid\x00")

func getpid() uint64 {
	var b [20]byte
	fd := open(&pid[0], 0, 0)
	if fd >= 0 {
		read(fd, unsafe.Pointer(&b), int32(len(b)))
		close(fd)
	}
	c := b[:]
	for c[0] == ' ' || c[0] == '\t' {
		c = c[1:]
	}
	return uint64(atoi(c))
}

func osinit() {
	initBloc()
	ncpu = getproccount()
	getg().m.procid = getpid()
	notify(unsafe.Pointer(funcPC(sigtramp)))
}

func crash() {
	notify(nil)
	*(*int)(nil) = 0
}

var random_dev = []byte("/dev/random\x00")

//go:nosplit
func getRandomData(r []byte) {
	fd := open(&random_dev[0], 0 /* O_RDONLY */, 0)
	n := read(fd, unsafe.Pointer(&r[0]), int32(len(r)))
	close(fd)
	extendRandom(r, int(n))
}

func goenvs() {
}

func initsig() {
}

//go:nosplit
func osyield() {
	sleep(0)
}

//go:nosplit
func usleep(µs uint32) {
	ms := int32(µs / 1000)
	if ms == 0 {
		ms = 1
	}
	sleep(ms)
}

//go:nosplit
func nanotime() int64 {
	var scratch int64
	ns := nsec(&scratch)
	// TODO(aram): remove hack after I fix _nsec in the pc64 kernel.
	if ns == 0 {
		return scratch
	}
	return ns
}

//go:nosplit
func itoa(buf []byte, val uint64) []byte {
	i := len(buf) - 1
	for val >= 10 {
		buf[i] = byte(val%10 + '0')
		i--
		val /= 10
	}
	buf[i] = byte(val + '0')
	return buf[i:]
}

var goexits = []byte("go: exit ")

func goexitsall(status *byte) {
	var buf [_ERRMAX]byte
	n := copy(buf[:], goexits)
	n = copy(buf[n:], gostringnocopy(status))
	pid := getpid()
	for mp := (*m)(atomicloadp(unsafe.Pointer(&allm))); mp != nil; mp = mp.alllink {
		if mp.procid != pid {
			postnote(mp.procid, buf[:])
		}
	}
}

var procdir = []byte("/proc/")
var notefile = []byte("/note\x00")

func postnote(pid uint64, msg []byte) int {
	var buf [128]byte
	var tmp [32]byte
	n := copy(buf[:], procdir)
	n += copy(buf[n:], itoa(tmp[:], pid))
	copy(buf[n:], notefile)
	fd := open(&buf[0], _OWRITE, 0)
	if fd < 0 {
		return -1
	}
	len := findnull(&msg[0])
	if write(uintptr(fd), (unsafe.Pointer)(&msg[0]), int32(len)) != int64(len) {
		close(fd)
		return -1
	}
	close(fd)
	return 0
}

//go:nosplit
func exit(e int) {
	var status []byte
	if e == 0 {
		status = []byte("\x00")
	} else {
		// build error string
		var tmp [32]byte
		status = []byte(gostringnocopy(&itoa(tmp[:], uint64(e))[0]) + "\x00")
	}
	goexitsall(&status[0])
	exits(&status[0])
}

func newosproc(mp *m, stk unsafe.Pointer) {
	if false {
		print("newosproc mp=", mp, " ostk=", &mp, "\n")
	}
	pid := rfork(_RFPROC | _RFMEM | _RFNOWAIT)
	if pid < 0 {
		gothrow("newosproc: rfork failed")
	}
	if pid == 0 {
		tstart_plan9(mp)
	}
}

//go:nosplit
func semacreate() uintptr {
	return 1
}

//go:nosplit
func semasleep(ns int64) int {
	_g_ := getg()
	if ns >= 0 {
		ms := timediv(ns, 1000000, nil)
		if ms == 0 {
			ms = 1
		}
		ret := plan9_tsemacquire(&_g_.m.waitsemacount, ms)
		if ret == 1 {
			return 0 // success
		}
		return -1 // timeout or interrupted
	}
	for plan9_semacquire(&_g_.m.waitsemacount, 1) < 0 {
		// interrupted; try again (c.f. lock_sema.go)
	}
	return 0 // success
}

//go:nosplit
func semawakeup(mp *m) {
	plan9_semrelease(&mp.waitsemacount, 1)
}

//go:nosplit
func read(fd int32, buf unsafe.Pointer, n int32) int32 {
	return pread(fd, buf, n, -1)
}

//go:nosplit
func write(fd uintptr, buf unsafe.Pointer, n int32) int64 {
	return int64(pwrite(int32(fd), buf, n, -1))
}

func memlimit() uint64 {
	return 0
}

var _badsignal = []byte("runtime: signal received on thread not created by Go.\n")

// This runs on a foreign stack, without an m or a g.  No stack split.
//go:nosplit
func badsignal2() {
	pwrite(2, unsafe.Pointer(&_badsignal[0]), int32(len(_badsignal)), -1)
	exits(&_badsignal[0])
}

func atoi(b []byte) int {
	n := 0
	for len(b) > 0 && '0' <= b[0] && b[0] <= '9' {
		n = n*10 + int(b[0]) - '0'
		b = b[1:]
	}
	return n
}
<commit_msg>runtime: fix exit on Plan 9<commit_after>// Copyright 2010 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package runtime

import "unsafe"

// Called to initialize a new m (including the bootstrap m).
// Called on the parent thread (main thread in case of bootstrap), can allocate memory.
func mpreinit(mp *m) {
	// Initialize stack and goroutine for note handling.
	mp.gsignal = malg(32 * 1024)
	mp.gsignal.m = mp
	mp.notesig = (*int8)(mallocgc(_ERRMAX, nil, _FlagNoScan))
	// Initialize stack for handling strings from the
	// errstr system call, as used in package syscall.
	mp.errstr = (*byte)(mallocgc(_ERRMAX, nil, _FlagNoScan))
}

// Called to initialize a new m (including the bootstrap m).
// Called on the new thread, can not allocate memory.
func minit() {
	// Mask all SSE floating-point exceptions
	// when running on the 64-bit kernel.
	setfpmasks()
}

// Called from dropm to undo the effect of an minit.
func unminit() {
}

var sysstat = []byte("/dev/sysstat\x00")

func getproccount() int32 {
	var buf [2048]byte
	fd := open(&sysstat[0], _OREAD, 0)
	if fd < 0 {
		return 1
	}
	ncpu := int32(0)
	for {
		n := read(fd, unsafe.Pointer(&buf), int32(len(buf)))
		if n <= 0 {
			break
		}
		for i := int32(0); i < n; i++ {
			if buf[i] == '\n' {
				ncpu++
			}
		}
	}
	close(fd)
	if ncpu == 0 {
		ncpu = 1
	}
	return ncpu
}

var pid = []byte("#c/pid\x00")

func getpid() uint64 {
	var b [20]byte
	fd := open(&pid[0], 0, 0)
	if fd >= 0 {
		read(fd, unsafe.Pointer(&b), int32(len(b)))
		close(fd)
	}
	c := b[:]
	for c[0] == ' ' || c[0] == '\t' {
		c = c[1:]
	}
	return uint64(atoi(c))
}

func osinit() {
	initBloc()
	ncpu = getproccount()
	getg().m.procid = getpid()
	notify(unsafe.Pointer(funcPC(sigtramp)))
}

func crash() {
	notify(nil)
	*(*int)(nil) = 0
}

var random_dev = []byte("/dev/random\x00")

//go:nosplit
func getRandomData(r []byte) {
	fd := open(&random_dev[0], 0 /* O_RDONLY */, 0)
	n := read(fd, unsafe.Pointer(&r[0]), int32(len(r)))
	close(fd)
	extendRandom(r, int(n))
}

func goenvs() {
}

func initsig() {
}

//go:nosplit
func osyield() {
	sleep(0)
}

//go:nosplit
func usleep(µs uint32) {
	ms := int32(µs / 1000)
	if ms == 0 {
		ms = 1
	}
	sleep(ms)
}

//go:nosplit
func nanotime() int64 {
	var scratch int64
	ns := nsec(&scratch)
	// TODO(aram): remove hack after I fix _nsec in the pc64 kernel.
	if ns == 0 {
		return scratch
	}
	return ns
}

//go:nosplit
func itoa(buf []byte, val uint64) []byte {
	i := len(buf) - 1
	for val >= 10 {
		buf[i] = byte(val%10 + '0')
		i--
		val /= 10
	}
	buf[i] = byte(val + '0')
	return buf[i:]
}

var goexits = []byte("go: exit ")

func goexitsall(status *byte) {
	var buf [_ERRMAX]byte
	n := copy(buf[:], goexits)
	n = copy(buf[n:], gostringnocopy(status))
	pid := getpid()
	for mp := (*m)(atomicloadp(unsafe.Pointer(&allm))); mp != nil; mp = mp.alllink {
		if mp.procid != pid {
			postnote(mp.procid, buf[:])
		}
	}
}

var procdir = []byte("/proc/")
var notefile = []byte("/note\x00")

func postnote(pid uint64, msg []byte) int {
	var buf [128]byte
	var tmp [32]byte
	n := copy(buf[:], procdir)
	n += copy(buf[n:], itoa(tmp[:], pid))
	copy(buf[n:], notefile)
	fd := open(&buf[0], _OWRITE, 0)
	if fd < 0 {
		return -1
	}
	len := findnull(&msg[0])
	if write(uintptr(fd), (unsafe.Pointer)(&msg[0]), int32(len)) != int64(len) {
		close(fd)
		return -1
	}
	close(fd)
	return 0
}

//go:nosplit
func exit(e int) {
	var status []byte
	if e == 0 {
		status = []byte("\x00")
	} else {
		// build error string
		var tmp [32]byte
		status = []byte(gostringnocopy(&itoa(tmp[:len(tmp)-1], uint64(e))[0]))
	}
	goexitsall(&status[0])
	exits(&status[0])
}

func newosproc(mp *m, stk unsafe.Pointer) {
	if false {
		print("newosproc mp=", mp, " ostk=", &mp, "\n")
	}
	pid := rfork(_RFPROC | _RFMEM | _RFNOWAIT)
	if pid < 0 {
		gothrow("newosproc: rfork failed")
	}
	if pid == 0 {
		tstart_plan9(mp)
	}
}

//go:nosplit
func semacreate() uintptr {
	return 1
}

//go:nosplit
func semasleep(ns int64) int {
	_g_ := getg()
	if ns >= 0 {
		ms := timediv(ns, 1000000, nil)
		if ms == 0 {
			ms = 1
		}
		ret := plan9_tsemacquire(&_g_.m.waitsemacount, ms)
		if ret == 1 {
			return 0 // success
		}
		return -1 // timeout or interrupted
	}
	for plan9_semacquire(&_g_.m.waitsemacount, 1) < 0 {
		// interrupted; try again (c.f. lock_sema.go)
	}
	return 0 // success
}

//go:nosplit
func semawakeup(mp *m) {
	plan9_semrelease(&mp.waitsemacount, 1)
}

//go:nosplit
func read(fd int32, buf unsafe.Pointer, n int32) int32 {
	return pread(fd, buf, n, -1)
}

//go:nosplit
func write(fd uintptr, buf unsafe.Pointer, n int32) int64 {
	return int64(pwrite(int32(fd), buf, n, -1))
}

func memlimit() uint64 {
	return 0
}

var _badsignal = []byte("runtime: signal received on thread not created by Go.\n")

// This runs on a foreign stack, without an m or a g.  No stack split.
//go:nosplit
func badsignal2() {
	pwrite(2, unsafe.Pointer(&_badsignal[0]), int32(len(_badsignal)), -1)
	exits(&_badsignal[0])
}

func atoi(b []byte) int {
	n := 0
	for len(b) > 0 && '0' <= b[0] && b[0] <= '9' {
		n = n*10 + int(b[0]) - '0'
		b = b[1:]
	}
	return n
}
<|endoftext|>


<commit_before>package nodejs

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"github.com/blang/semver"
	"github.com/markelog/archive"
	"github.com/markelog/cprf"
	"gopkg.in/cavaliercoder/grab.v1"

	"github.com/markelog/eclectica/io"
	"github.com/markelog/eclectica/variables"
)

var (
	minimalForYarn, _ = semver.Make("6.0.0")
	version           = "1.16.0"
	yarnURL           = fmt.Sprintf(
		"https://yarnpkg.com/downloads/%s/yarn-v%s.tar.gz",
		version,
		version,
	)
)

func (node Node) download(path string) (err error) {
	_, err = grab.Get(path, yarnURL)
	if err != nil {
		return
	}

	return
}

func (node Node) isYarnPossible() bool {
	version, _ := semver.Make(node.Version)

	return version.GTE(minimalForYarn)
}

// Yarn does everything that needs to be done to install yarn
func (node Node) Yarn() (ok bool, err error) {
	var (
		path       = variables.Path("node", node.Version)
		modules    = filepath.Join(path, "lib/node_modules")
		archived   = filepath.Join(variables.TempDir(), "yarn-archived")
		unarchived = filepath.Join(variables.TempDir(), "yarn-unarchived")
		from       = filepath.Join(unarchived, fmt.Sprintf("yarn-v%s/", version))
		dest       = filepath.Join(modules, "yarn")
	)

	if node.isYarnPossible() == false {
		return true, errors.New("\"" + node.Version + "\" version is not supported by yarn")
	}

	if _, statErr := os.Stat(unarchived); statErr != nil {
		err = node.download(archived)
		if err != nil {
			return
		}

		err = archive.Extract(archived, unarchived)
		if err != nil {
			return
		}
	}

	err = cprf.Copy(from+"/", dest)
	if err != nil {
		return
	}

	os.RemoveAll(archived)

	current := filepath.Join(modules, "yarn/bin/yarn.js")
	base := filepath.Join(path, "bin/yarn")

	err = io.Symlink(base, current)
	if err != nil {
		return
	}

	return true, nil
}
<commit_msg>Bump default yarn version<commit_after>package nodejs

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"github.com/blang/semver"
	"github.com/markelog/archive"
	"github.com/markelog/cprf"
	"gopkg.in/cavaliercoder/grab.v1"

	"github.com/markelog/eclectica/io"
	"github.com/markelog/eclectica/variables"
)

var (
	minimalForYarn, _ = semver.Make("6.0.0")
	version           = "1.19.1"
	yarnURL           = fmt.Sprintf(
		"https://yarnpkg.com/downloads/%s/yarn-v%s.tar.gz",
		version,
		version,
	)
)

func (node Node) download(path string) (err error) {
	_, err = grab.Get(path, yarnURL)
	if err != nil {
		return
	}

	return
}

func (node Node) isYarnPossible() bool {
	version, _ := semver.Make(node.Version)

	return version.GTE(minimalForYarn)
}

// Yarn does everything that needs to be done to install yarn
func (node Node) Yarn() (ok bool, err error) {
	var (
		path       = variables.Path("node", node.Version)
		modules    = filepath.Join(path, "lib/node_modules")
		archived   = filepath.Join(variables.TempDir(), "yarn-archived")
		unarchived = filepath.Join(variables.TempDir(), "yarn-unarchived")
		from       = filepath.Join(unarchived, fmt.Sprintf("yarn-v%s/", version))
		dest       = filepath.Join(modules, "yarn")
	)

	if node.isYarnPossible() == false {
		return true, errors.New("\"" + node.Version + "\" version is not supported by yarn")
	}

	if _, statErr := os.Stat(unarchived); statErr != nil {
		err = node.download(archived)
		if err != nil {
			return
		}

		err = archive.Extract(archived, unarchived)
		if err != nil {
			return
		}
	}

	err = cprf.Copy(from+"/", dest)
	if err != nil {
		return
	}

	os.RemoveAll(archived)

	current := filepath.Join(modules, "yarn/bin/yarn.js")
	base := filepath.Join(path, "bin/yarn")

	err = io.Symlink(base, current)
	if err != nil {
		return
	}

	return true, nil
}
<|endoftext|>


<commit_before>package lib

import (
	"fmt"
	"github.com/subutai-io/base/agent/config"
	"github.com/subutai-io/base/agent/lib/net/p2p"
	"github.com/subutai-io/base/agent/log"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
)

func P2P(c, d, u, l, p bool, args []string) {
	if c {
		if len(args) > 8 {
			p2p.Create(args[4], args[5], args[6], args[7], args[8])
		} else {
			fmt.Println("Wrong usage")
		}
	} else if u {
		if len(args) > 6 {
			p2p.UpdateKey(args[4], args[5], args[6])
		} else {
			fmt.Println("Wrong usage")
		}
	} else if d {
		if len(args) > 4 {
			p2p.Remove(args[4])
		} else {
			fmt.Println("Wrong usage")
		}
	} else if p {
		if len(args) > 4 {
			p2p.Peers(args[4])
		} else {
			fmt.Println("Wrong usage")
		}
	} else if l {
		p2p.Print()
	}
}

func LxcManagementNetwork(args []string) {
	if len(args) < 3 {
		log.Error("Not enough arguments")
	}
	switch args[2] {
	case "-v", "--listvnimap":
		displayVNIMap()
	case "-r", "--removetunnel":
		removeTunnel(args[3])
	case "-T", "--creategateway":
		return
	case "-M", "--removevni":
		return
	case "-E", "--reservevni":
		reserveVNI(args[3], args[4], args[5])
	case "-m", "--createvnimap":
		createVNIMap(args[3], args[4], args[5], args[6])
	case "-c", "--createtunnel":
		createTunnel(args[3], args[4], args[5])
	case "-l", "--listtunnel":
		listTunnel()
	case "-Z", "--vniop":
		switch args[3] {
		case "deleteall":
			return
		case "delete":
			return
		case "list":
			listVNI()
		}
	}
}

func createFile() {
	if _, err := os.Stat(config.Agent.DataPrefix + "var/subutai-network/"); os.IsNotExist(err) {
		log.Check(log.FatalLevel, "Creating network data folder", os.MkdirAll(config.Agent.DataPrefix+"var/subutai-network", 0755))
	}
	if _, err := os.Stat(config.Agent.DataPrefix + "var/subutai-network/vni_reserve"); os.IsNotExist(err) {
		_, err = os.Create(config.Agent.DataPrefix + "var/subutai-network/vni_reserve")
		log.Check(log.ErrorLevel, "Creating VNI file", err)
	}
}

func listVNI() {
	createFile()
	f, err := ioutil.ReadFile(config.Agent.DataPrefix + "/var/subutai-network/vni_reserve")
	log.Check(log.ErrorLevel, "Reading "+config.Agent.DataPrefix+"/var/subutai-network/vni_reserve", err)

	for _, v := range strings.Split(string(f), "\n") {
		s := strings.Fields(v)
		if len(s) > 2 {
			fmt.Printf("%s,%s,%s\n", s[0], s[1], s[2])
		}
	}
}

func checkVNI(vni, vlan, envid string) {
	f, _ := ioutil.ReadFile(config.Agent.DataPrefix + "var/subutai-network/vni_reserve")
	lines := strings.Split(string(f), "\n")
	for _, v := range lines {
		if v == vni+" "+vlan+" "+envid {
			log.Error("Reservation already exist")
		}
	}
}

func reserveVNI(vni, vlan, envid string) {
	createFile()
	checkVNI(vni, vlan, envid)
	f, err := os.OpenFile(config.Agent.DataPrefix+"var/subutai-network/vni_reserve", os.O_APPEND|os.O_WRONLY, 0600)
	log.Check(log.ErrorLevel, "Appending line", err)

	defer f.Close()

	if _, err = f.WriteString(vni + " " + vlan + " " + envid + "\n"); err != nil {
		log.Error("Writing reserver")
	}
}

func listTunnel() {
	println("List of Tunnels\n--------")
	ret, err := exec.Command("ovs-vsctl", "show").CombinedOutput()
	log.Check(log.FatalLevel, "Getting OVS interfaces list", err)
	ports := strings.Split(string(ret), "\n")

	for k, port := range ports {
		if strings.Contains(string(port), "remote_ip") {
			iface := strings.Fields(ports[k-2])
			tunnel := strings.Trim(iface[1], "\"")
			addr := strings.Fields(port)
			fmt.Println(tunnel + "-" + strings.Trim(strings.Trim(addr[3], "remote_ip="), "\","))
		}
	}

}

func createTunnel(tunnel, addr, tunType string) {
	ifTunExist(tunnel)

	log.Check(log.FatalLevel, "Creating tunnel port",
		exec.Command("ovs-vsctl", "--may-exist", "add-port", "wan", tunnel, "--", "set", "interface", tunnel, "type="+tunType,
			"options:stp_enable=true", "options:key=flow", "options:remote_ip="+addr).Run())
}

func ifTunExist(name string) {
	ret, err := exec.Command("ovs-vsctl", "list-ports", "wan").CombinedOutput()
	log.Check(log.FatalLevel, "Getting port list", err)
	ports := strings.Split(string(ret), "\n")

	for _, port := range ports {
		if port == name {
			log.Error("Tunnel port " + name + " is already exists")
		}
	}
}

func displayVNIMap() {
	// tunnel1	8880164	100	04c088b9-e2f5-40b3-bd6c-2305b9a88058
	ret, err := exec.Command("ovs-vsctl", "show").CombinedOutput()
	log.Check(log.FatalLevel, "Getting OVS interfaces list", err)
	ports := strings.Split(string(ret), "\n")

	for k, port := range ports {
		if strings.Contains(string(port), "env") {
			iface := strings.Fields(ports[k-2])
			tunname := strings.Trim(iface[1], "\"")
			tag := strings.Fields(ports[k-3])[1]
			addr := strings.Fields(port)
			// ip := strings.Trim(strings.Trim(addr[3], "remote_ip="), "\",")
			key := strings.Trim(strings.Trim(addr[2], "key="), "\",")
			env := strings.Trim(strings.Trim(addr[1], "{env="), "\",")
			fmt.Println(tunname + " " + key + " " + tag + " " + env)
		}
	}
}

func createVNIMap(tunnel, vni, vlan, envid string) {
	log.Check(log.FatalLevel, "MakeVNIMap set interface: ",
		exec.Command("ovs-vsctl", "--if-exists", "set", "interface", tunnel, "options:key="+vni, "options:env="+envid).Run())
	log.Check(log.FatalLevel, "MakeVNIMap set port: ",
		exec.Command("ovs-vsctl", "--if-exists", "set", "port", tunnel, "tag="+vlan).Run())

}

func removeTunnel(tunnel string) {
	log.Check(log.WarnLevel, "Removing port "+tunnel,
		exec.Command("ovs-vsctl", "--if-exists", "del-port", tunnel).Run())
}

func delTunById(envId string) {
	ret, err := exec.Command("ovs-vsctl", "show").CombinedOutput()
	log.Check(log.FatalLevel, "Getting OVS interfaces list", err)
	ports := strings.Split(string(ret), "\n")

	for k, port := range ports {
		if strings.Contains(string(port), envId) {
			tunnel := strings.Split(ports[k-2], "\"")[1]
			log.Check(log.WarnLevel, "Removing port "+tunnel,
				exec.Command("ovs-vsctl", "--if-exists", "del-port", tunnel).Run())
		}
	}

}

func ClearVlan(vlan string) {
	var lines []string
	f, err := ioutil.ReadFile(config.Agent.DataPrefix + "/var/subutai-network/vni_reserve")
	if !log.Check(log.DebugLevel, "Reading "+config.Agent.DataPrefix+"/var/subutai-network/vni_reserve", err) {
		lines = strings.Split(string(f), "\n")
		for k, v := range lines {
			s := strings.Fields(v)
			if len(s) > 2 && s[1] == vlan {
				delTunById(s[2])
				p2p.Remove(s[2])
				lines[k] = ""
			}
		}
	}
	err = ioutil.WriteFile(config.Agent.DataPrefix+"/var/subutai-network/vni_reserve", []byte(strings.Join(lines, "\n")), 0744)
	log.Check(log.WarnLevel, "config.Agent.DataPrefix + /var/subutai-network/vni_reserve delete vni", err)
}
<commit_msg>Println replaced with fmt to get rid of stderr<commit_after>package lib

import (
	"fmt"
	"github.com/subutai-io/base/agent/config"
	"github.com/subutai-io/base/agent/lib/net/p2p"
	"github.com/subutai-io/base/agent/log"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
)

func P2P(c, d, u, l, p bool, args []string) {
	if c {
		if len(args) > 8 {
			p2p.Create(args[4], args[5], args[6], args[7], args[8])
		} else {
			fmt.Println("Wrong usage")
		}
	} else if u {
		if len(args) > 6 {
			p2p.UpdateKey(args[4], args[5], args[6])
		} else {
			fmt.Println("Wrong usage")
		}
	} else if d {
		if len(args) > 4 {
			p2p.Remove(args[4])
		} else {
			fmt.Println("Wrong usage")
		}
	} else if p {
		if len(args) > 4 {
			p2p.Peers(args[4])
		} else {
			fmt.Println("Wrong usage")
		}
	} else if l {
		p2p.Print()
	}
}

func LxcManagementNetwork(args []string) {
	if len(args) < 3 {
		log.Error("Not enough arguments")
	}
	switch args[2] {
	case "-v", "--listvnimap":
		displayVNIMap()
	case "-r", "--removetunnel":
		removeTunnel(args[3])
	case "-T", "--creategateway":
		return
	case "-M", "--removevni":
		return
	case "-E", "--reservevni":
		reserveVNI(args[3], args[4], args[5])
	case "-m", "--createvnimap":
		createVNIMap(args[3], args[4], args[5], args[6])
	case "-c", "--createtunnel":
		createTunnel(args[3], args[4], args[5])
	case "-l", "--listtunnel":
		listTunnel()
	case "-Z", "--vniop":
		switch args[3] {
		case "deleteall":
			return
		case "delete":
			return
		case "list":
			listVNI()
		}
	}
}

func createFile() {
	if _, err := os.Stat(config.Agent.DataPrefix + "var/subutai-network/"); os.IsNotExist(err) {
		log.Check(log.FatalLevel, "Creating network data folder", os.MkdirAll(config.Agent.DataPrefix+"var/subutai-network", 0755))
	}
	if _, err := os.Stat(config.Agent.DataPrefix + "var/subutai-network/vni_reserve"); os.IsNotExist(err) {
		_, err = os.Create(config.Agent.DataPrefix + "var/subutai-network/vni_reserve")
		log.Check(log.ErrorLevel, "Creating VNI file", err)
	}
}

func listVNI() {
	createFile()
	f, err := ioutil.ReadFile(config.Agent.DataPrefix + "/var/subutai-network/vni_reserve")
	log.Check(log.ErrorLevel, "Reading "+config.Agent.DataPrefix+"/var/subutai-network/vni_reserve", err)

	for _, v := range strings.Split(string(f), "\n") {
		s := strings.Fields(v)
		if len(s) > 2 {
			fmt.Printf("%s,%s,%s\n", s[0], s[1], s[2])
		}
	}
}

func checkVNI(vni, vlan, envid string) {
	f, _ := ioutil.ReadFile(config.Agent.DataPrefix + "var/subutai-network/vni_reserve")
	lines := strings.Split(string(f), "\n")
	for _, v := range lines {
		if v == vni+" "+vlan+" "+envid {
			log.Error("Reservation already exist")
		}
	}
}

func reserveVNI(vni, vlan, envid string) {
	createFile()
	checkVNI(vni, vlan, envid)
	f, err := os.OpenFile(config.Agent.DataPrefix+"var/subutai-network/vni_reserve", os.O_APPEND|os.O_WRONLY, 0600)
	log.Check(log.ErrorLevel, "Appending line", err)

	defer f.Close()

	if _, err = f.WriteString(vni + " " + vlan + " " + envid + "\n"); err != nil {
		log.Error("Writing reserver")
	}
}

func listTunnel() {
	fmt.Println("List of Tunnels\n--------")
	ret, err := exec.Command("ovs-vsctl", "show").CombinedOutput()
	log.Check(log.FatalLevel, "Getting OVS interfaces list", err)
	ports := strings.Split(string(ret), "\n")

	for k, port := range ports {
		if strings.Contains(string(port), "remote_ip") {
			iface := strings.Fields(ports[k-2])
			tunnel := strings.Trim(iface[1], "\"")
			addr := strings.Fields(port)
			fmt.Println(tunnel + "-" + strings.Trim(strings.Trim(addr[3], "remote_ip="), "\","))
		}
	}

}

func createTunnel(tunnel, addr, tunType string) {
	ifTunExist(tunnel)

	log.Check(log.FatalLevel, "Creating tunnel port",
		exec.Command("ovs-vsctl", "--may-exist", "add-port", "wan", tunnel, "--", "set", "interface", tunnel, "type="+tunType,
			"options:stp_enable=true", "options:key=flow", "options:remote_ip="+addr).Run())
}

func ifTunExist(name string) {
	ret, err := exec.Command("ovs-vsctl", "list-ports", "wan").CombinedOutput()
	log.Check(log.FatalLevel, "Getting port list", err)
	ports := strings.Split(string(ret), "\n")

	for _, port := range ports {
		if port == name {
			log.Error("Tunnel port " + name + " is already exists")
		}
	}
}

func displayVNIMap() {
	// tunnel1	8880164	100	04c088b9-e2f5-40b3-bd6c-2305b9a88058
	ret, err := exec.Command("ovs-vsctl", "show").CombinedOutput()
	log.Check(log.FatalLevel, "Getting OVS interfaces list", err)
	ports := strings.Split(string(ret), "\n")

	for k, port := range ports {
		if strings.Contains(string(port), "env") {
			iface := strings.Fields(ports[k-2])
			tunname := strings.Trim(iface[1], "\"")
			tag := strings.Fields(ports[k-3])[1]
			addr := strings.Fields(port)
			// ip := strings.Trim(strings.Trim(addr[3], "remote_ip="), "\",")
			key := strings.Trim(strings.Trim(addr[2], "key="), "\",")
			env := strings.Trim(strings.Trim(addr[1], "{env="), "\",")
			fmt.Println(tunname + " " + key + " " + tag + " " + env)
		}
	}
}

func createVNIMap(tunnel, vni, vlan, envid string) {
	log.Check(log.FatalLevel, "MakeVNIMap set interface: ",
		exec.Command("ovs-vsctl", "--if-exists", "set", "interface", tunnel, "options:key="+vni, "options:env="+envid).Run())
	log.Check(log.FatalLevel, "MakeVNIMap set port: ",
		exec.Command("ovs-vsctl", "--if-exists", "set", "port", tunnel, "tag="+vlan).Run())

}

func removeTunnel(tunnel string) {
	log.Check(log.WarnLevel, "Removing port "+tunnel,
		exec.Command("ovs-vsctl", "--if-exists", "del-port", tunnel).Run())
}

func delTunById(envId string) {
	ret, err := exec.Command("ovs-vsctl", "show").CombinedOutput()
	log.Check(log.FatalLevel, "Getting OVS interfaces list", err)
	ports := strings.Split(string(ret), "\n")

	for k, port := range ports {
		if strings.Contains(string(port), envId) {
			tunnel := strings.Split(ports[k-2], "\"")[1]
			log.Check(log.WarnLevel, "Removing port "+tunnel,
				exec.Command("ovs-vsctl", "--if-exists", "del-port", tunnel).Run())
		}
	}

}

func ClearVlan(vlan string) {
	var lines []string
	f, err := ioutil.ReadFile(config.Agent.DataPrefix + "/var/subutai-network/vni_reserve")
	if !log.Check(log.DebugLevel, "Reading "+config.Agent.DataPrefix+"/var/subutai-network/vni_reserve", err) {
		lines = strings.Split(string(f), "\n")
		for k, v := range lines {
			s := strings.Fields(v)
			if len(s) > 2 && s[1] == vlan {
				delTunById(s[2])
				p2p.Remove(s[2])
				lines[k] = ""
			}
		}
	}
	err = ioutil.WriteFile(config.Agent.DataPrefix+"/var/subutai-network/vni_reserve", []byte(strings.Join(lines, "\n")), 0744)
	log.Check(log.WarnLevel, "config.Agent.DataPrefix + /var/subutai-network/vni_reserve delete vni", err)
}
<|endoftext|>


<commit_before>package apps

import (
	"bytes"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"

	"github.com/cozy/cozy-stack/pkg/apps"
	"github.com/cozy/cozy-stack/pkg/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/instance"
	"github.com/cozy/cozy-stack/pkg/intents"
	"github.com/cozy/cozy-stack/pkg/sessions"
	"github.com/cozy/cozy-stack/web/middlewares"
	"github.com/cozy/cozy-stack/web/permissions"
	"github.com/cozy/cozy-stack/web/statik"
	"github.com/labstack/echo"
)

// Serve is an handler for serving files from the VFS for a client-side app
func Serve(c echo.Context) error {
	method := c.Request().Method
	if method != "GET" && method != "HEAD" {
		return echo.NewHTTPError(http.StatusMethodNotAllowed, "Method "+method+" not allowed")
	}

	i := middlewares.GetInstance(c)
	slug := c.Get("slug").(string)

	if (!i.OnboardingFinished && slug != consts.OnboardingSlug && slug != consts.CollectSlug) ||
		(i.OnboardingFinished && slug == consts.OnboardingSlug) {
		return c.Redirect(http.StatusFound, i.PageURL("/", nil))
	}

	if config.GetConfig().Subdomains == config.FlatSubdomains {
		if code := c.QueryParam("code"); code != "" {
			return tryAuthWithSessionCode(c, i, code, slug)
		}
	}

	app, err := apps.GetWebappBySlug(i, slug)
	if err != nil {
		switch err {
		case apps.ErrNotFound:
			return echo.NewHTTPError(http.StatusNotFound, "Application not found")
		case apps.ErrInvalidSlugName:
			return echo.NewHTTPError(http.StatusBadRequest, err.Error())
		default:
			return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
		}
	}

	switch app.State() {
	case apps.Installed:
		return c.Redirect(http.StatusFound, i.PageURL("/auth/authorize/app", url.Values{
			"slug": {slug},
		}))
	case apps.Ready:
		return ServeAppFile(c, i, i.AppsFileServer(), app)
	default:
		return echo.NewHTTPError(http.StatusServiceUnavailable, "Application is not ready")
	}
}

// handleIntent will allow iframes from another app if the current app is
// opened as an intent
func handleIntent(c echo.Context, i *instance.Instance, slug, intentID string) {
	intent := &intents.Intent{}
	if err := couchdb.GetDoc(i, consts.Intents, intentID, intent); err != nil {
		return
	}
	allowed := false
	for _, service := range intent.Services {
		if slug == service.Slug {
			allowed = true
		}
	}
	if !allowed {
		return
	}
	parts := strings.SplitN(intent.Client, "/", 2)
	if len(parts) < 2 || parts[0] != consts.Apps {
		return
	}
	from := i.SubDomain(parts[1]).String()
	hdr := fmt.Sprintf("%s %s", middlewares.XFrameAllowFrom, from)
	c.Response().Header().Set(echo.HeaderXFrameOptions, hdr)
}

// ServeAppFile will serve the requested file using the specified application
// manifest and apps.FileServer context.
//
// It can be used to serve file application in another context than the VFS,
// for instance for tests or development puposes where we want to serve an
// application that is not installed on the user's instance. However this
// procedure should not be used for standard applications, use the Serve method
// for that.
func ServeAppFile(c echo.Context, i *instance.Instance, fs apps.FileServer, app *apps.WebappManifest) error {
	slug := app.Slug()
	route, file := app.FindRoute(path.Clean(c.Request().URL.Path))
	if route.NotFound() {
		return echo.NewHTTPError(http.StatusNotFound, "Page not found")
	}
	if file == "" {
		file = route.Index
	}

	var needAuth bool
	if len(i.RegisterToken) > 0 && file == route.Index {
		if slug != consts.OnboardingSlug || !permissions.CheckRegisterToken(c, i) {
			return c.Redirect(http.StatusFound, i.PageURL("/", nil))
		}
		needAuth = false
	} else if slug == consts.OnboardingSlug && file == route.Index {
		needAuth = true
	} else {
		needAuth = !route.Public
	}

	session, isLoggedIn := middlewares.GetSession(c)
	if needAuth && !isLoggedIn {
		if file != route.Index {
			return echo.NewHTTPError(http.StatusUnauthorized, "You must be authenticated")
		}
		reqURL := c.Request().URL
		subdomain := i.SubDomain(slug)
		subdomain.Path = reqURL.Path
		subdomain.RawQuery = reqURL.RawQuery
		subdomain.Fragment = reqURL.Fragment
		redirect := url.Values{
			"redirect": {subdomain.String()},
		}
		return c.Redirect(http.StatusFound, i.PageURL("/auth/login", redirect))
	}

	filepath := path.Join("/", route.Folder, file)
	version := app.Version()

	if file != route.Index {
		// If file is not the index, it is considered an asset of the application
		// (JS, image, ...). For theses assets we check if it contains an unique
		// identifier to help caching. In such case, a long cache (1 year) is set.
		//
		// A unique identifier is matched when the file base contains a "long"
		// hexadecimal subpart between '.', of at least 10 characters: for instance
		// "app.badf00dbadf00d.js".
		if _, id := statik.ExtractAssetID(file); id != "" {
			c.Response().Header().Set("Cache-Control", "max-age=31536000")
		}

		err := fs.ServeFileContent(c.Response(), c.Request(), slug, version, filepath)
		if os.IsNotExist(err) {
			return echo.NewHTTPError(http.StatusNotFound, "Asset not found")
		}
		if err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, err)
		}

		return nil
	}

	if intentID := c.QueryParam("intent"); intentID != "" {
		handleIntent(c, i, slug, intentID)
	}

	// For index file, we inject the locale, the stack domain, and a token if the
	// user is connected
	content, err := fs.Open(slug, version, filepath)
	if err != nil {
		return err
	}
	defer content.Close()

	buf, err := ioutil.ReadAll(content)
	if err != nil {
		return err
	}

	tmpl, err := template.New(file).Parse(string(buf))
	if err != nil {
		i.Logger().Warnf("[apps] %s cannot be parsed as a template: %s", file, err)
		return fs.ServeFileContent(c.Response(), c.Request(), slug, version, filepath)
	}

	var token string
	if isLoggedIn {
		token = i.BuildAppToken(app, session.ID())
	} else {
		token = c.QueryParam("sharecode")
	}

	tracking := "false"
	settings, err := i.SettingsDocument()
	if err == nil {
		if t, ok := settings.M["tracking"].(string); ok {
			tracking = t
		}
	}

	res := c.Response()
	res.Header().Set("Content-Type", "text/html; charset=utf-8")
	res.Header().Set("Cache-Control", "private, no-store, must-revalidate")
	res.WriteHeader(http.StatusOK)
	return tmpl.Execute(res, echo.Map{
		"Token":        token,
		"Domain":       i.Domain,
		"Locale":       i.Locale,
		"AppName":      app.Name,
		"AppEditor":    app.Editor,
		"IconPath":     app.Icon,
		"CozyBar":      cozybar(i),
		"CozyClientJS": cozyclientjs(i),
		"Tracking":     tracking,
	})
}

func tryAuthWithSessionCode(c echo.Context, i *instance.Instance, value, slug string) error {
	u := c.Request().URL
	u.Scheme = i.Scheme()
	u.Host = c.Request().Host
	if !middlewares.IsLoggedIn(c) {
		if code := sessions.FindCode(value, u.Host); code != nil {
			session, err := sessions.Get(i, code.SessionID)
			if err == nil {
				cookie, err := session.ToAppCookie(u.Host, slug)
				if err == nil {
					c.SetCookie(cookie)
				}
			}
		}
	}
	q := u.Query()
	q.Del("code")
	u.RawQuery = q.Encode()
	return c.Redirect(http.StatusFound, u.String())
}

var assetHelper func(domain, file string) string
var clientTemplate *template.Template
var barTemplate *template.Template

func init() {
	h := statik.NewHandler(statik.Options{Prefix: "/assets"})
	assetHelper = func(domain, file string) string {
		file = h.AssetPath(file)
		if domain != "" {
			return "//" + domain + file
		}
		return file
	}

	funcsMap := template.FuncMap{
		"split": strings.Split,
		"asset": assetHelper,
	}

	clientTemplate = template.Must(template.New("cozy-client-js").Funcs(funcsMap).Parse(`` +
		`<script defer src="{{asset .Domain "/js/cozy-client.min.js"}}"></script>`,
	))

	barTemplate = template.Must(template.New("cozy-bar").Funcs(funcsMap).Parse(`` +
		`<link rel="stylesheet" type="text/css" href="{{asset .Domain "/fonts/fonts.css"}}">` +
		`<link rel="stylesheet" type="text/css" href="{{asset .Domain "/css/cozy-bar.min.css"}}">` +
		`<script defer src="{{asset .Domain "/js/cozy-bar.min.js"}}"></script>`,
	))
}

func cozyclientjs(i *instance.Instance) template.HTML {
	buf := new(bytes.Buffer)
	err := clientTemplate.Execute(buf, echo.Map{"Domain": i.Domain})
	if err != nil {
		return template.HTML("")
	}
	return template.HTML(buf.String()) // #nosec
}

func cozybar(i *instance.Instance) template.HTML {
	buf := new(bytes.Buffer)
	err := barTemplate.Execute(buf, echo.Map{"Domain": i.Domain})
	if err != nil {
		return template.HTML("")
	}
	return template.HTML(buf.String()) // #nosec
}
<commit_msg>Add "immutable" cache-control<commit_after>package apps

import (
	"bytes"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"

	"github.com/cozy/cozy-stack/pkg/apps"
	"github.com/cozy/cozy-stack/pkg/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/instance"
	"github.com/cozy/cozy-stack/pkg/intents"
	"github.com/cozy/cozy-stack/pkg/sessions"
	"github.com/cozy/cozy-stack/web/middlewares"
	"github.com/cozy/cozy-stack/web/permissions"
	"github.com/cozy/cozy-stack/web/statik"
	"github.com/labstack/echo"
)

// Serve is an handler for serving files from the VFS for a client-side app
func Serve(c echo.Context) error {
	method := c.Request().Method
	if method != "GET" && method != "HEAD" {
		return echo.NewHTTPError(http.StatusMethodNotAllowed, "Method "+method+" not allowed")
	}

	i := middlewares.GetInstance(c)
	slug := c.Get("slug").(string)

	if (!i.OnboardingFinished && slug != consts.OnboardingSlug && slug != consts.CollectSlug) ||
		(i.OnboardingFinished && slug == consts.OnboardingSlug) {
		return c.Redirect(http.StatusFound, i.PageURL("/", nil))
	}

	if config.GetConfig().Subdomains == config.FlatSubdomains {
		if code := c.QueryParam("code"); code != "" {
			return tryAuthWithSessionCode(c, i, code, slug)
		}
	}

	app, err := apps.GetWebappBySlug(i, slug)
	if err != nil {
		switch err {
		case apps.ErrNotFound:
			return echo.NewHTTPError(http.StatusNotFound, "Application not found")
		case apps.ErrInvalidSlugName:
			return echo.NewHTTPError(http.StatusBadRequest, err.Error())
		default:
			return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
		}
	}

	switch app.State() {
	case apps.Installed:
		return c.Redirect(http.StatusFound, i.PageURL("/auth/authorize/app", url.Values{
			"slug": {slug},
		}))
	case apps.Ready:
		return ServeAppFile(c, i, i.AppsFileServer(), app)
	default:
		return echo.NewHTTPError(http.StatusServiceUnavailable, "Application is not ready")
	}
}

// handleIntent will allow iframes from another app if the current app is
// opened as an intent
func handleIntent(c echo.Context, i *instance.Instance, slug, intentID string) {
	intent := &intents.Intent{}
	if err := couchdb.GetDoc(i, consts.Intents, intentID, intent); err != nil {
		return
	}
	allowed := false
	for _, service := range intent.Services {
		if slug == service.Slug {
			allowed = true
		}
	}
	if !allowed {
		return
	}
	parts := strings.SplitN(intent.Client, "/", 2)
	if len(parts) < 2 || parts[0] != consts.Apps {
		return
	}
	from := i.SubDomain(parts[1]).String()
	hdr := fmt.Sprintf("%s %s", middlewares.XFrameAllowFrom, from)
	c.Response().Header().Set(echo.HeaderXFrameOptions, hdr)
}

// ServeAppFile will serve the requested file using the specified application
// manifest and apps.FileServer context.
//
// It can be used to serve file application in another context than the VFS,
// for instance for tests or development puposes where we want to serve an
// application that is not installed on the user's instance. However this
// procedure should not be used for standard applications, use the Serve method
// for that.
func ServeAppFile(c echo.Context, i *instance.Instance, fs apps.FileServer, app *apps.WebappManifest) error {
	slug := app.Slug()
	route, file := app.FindRoute(path.Clean(c.Request().URL.Path))
	if route.NotFound() {
		return echo.NewHTTPError(http.StatusNotFound, "Page not found")
	}
	if file == "" {
		file = route.Index
	}

	var needAuth bool
	if len(i.RegisterToken) > 0 && file == route.Index {
		if slug != consts.OnboardingSlug || !permissions.CheckRegisterToken(c, i) {
			return c.Redirect(http.StatusFound, i.PageURL("/", nil))
		}
		needAuth = false
	} else if slug == consts.OnboardingSlug && file == route.Index {
		needAuth = true
	} else {
		needAuth = !route.Public
	}

	session, isLoggedIn := middlewares.GetSession(c)
	if needAuth && !isLoggedIn {
		if file != route.Index {
			return echo.NewHTTPError(http.StatusUnauthorized, "You must be authenticated")
		}
		reqURL := c.Request().URL
		subdomain := i.SubDomain(slug)
		subdomain.Path = reqURL.Path
		subdomain.RawQuery = reqURL.RawQuery
		subdomain.Fragment = reqURL.Fragment
		redirect := url.Values{
			"redirect": {subdomain.String()},
		}
		return c.Redirect(http.StatusFound, i.PageURL("/auth/login", redirect))
	}

	filepath := path.Join("/", route.Folder, file)
	version := app.Version()

	if file != route.Index {
		// If file is not the index, it is considered an asset of the application
		// (JS, image, ...). For theses assets we check if it contains an unique
		// identifier to help caching. In such case, a long cache (1 year) is set.
		//
		// A unique identifier is matched when the file base contains a "long"
		// hexadecimal subpart between '.', of at least 10 characters: for instance
		// "app.badf00dbadf00d.js".
		if _, id := statik.ExtractAssetID(file); id != "" {
			c.Response().Header().Set("Cache-Control", "max-age=31536000, immutable")
		}

		err := fs.ServeFileContent(c.Response(), c.Request(), slug, version, filepath)
		if os.IsNotExist(err) {
			return echo.NewHTTPError(http.StatusNotFound, "Asset not found")
		}
		if err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, err)
		}

		return nil
	}

	if intentID := c.QueryParam("intent"); intentID != "" {
		handleIntent(c, i, slug, intentID)
	}

	// For index file, we inject the locale, the stack domain, and a token if the
	// user is connected
	content, err := fs.Open(slug, version, filepath)
	if err != nil {
		return err
	}
	defer content.Close()

	buf, err := ioutil.ReadAll(content)
	if err != nil {
		return err
	}

	tmpl, err := template.New(file).Parse(string(buf))
	if err != nil {
		i.Logger().Warnf("[apps] %s cannot be parsed as a template: %s", file, err)
		return fs.ServeFileContent(c.Response(), c.Request(), slug, version, filepath)
	}

	var token string
	if isLoggedIn {
		token = i.BuildAppToken(app, session.ID())
	} else {
		token = c.QueryParam("sharecode")
	}

	tracking := "false"
	settings, err := i.SettingsDocument()
	if err == nil {
		if t, ok := settings.M["tracking"].(string); ok {
			tracking = t
		}
	}

	res := c.Response()
	res.Header().Set("Content-Type", "text/html; charset=utf-8")
	res.Header().Set("Cache-Control", "private, no-store, must-revalidate")
	res.WriteHeader(http.StatusOK)
	return tmpl.Execute(res, echo.Map{
		"Token":        token,
		"Domain":       i.Domain,
		"Locale":       i.Locale,
		"AppName":      app.Name,
		"AppEditor":    app.Editor,
		"IconPath":     app.Icon,
		"CozyBar":      cozybar(i),
		"CozyClientJS": cozyclientjs(i),
		"Tracking":     tracking,
	})
}

func tryAuthWithSessionCode(c echo.Context, i *instance.Instance, value, slug string) error {
	u := c.Request().URL
	u.Scheme = i.Scheme()
	u.Host = c.Request().Host
	if !middlewares.IsLoggedIn(c) {
		if code := sessions.FindCode(value, u.Host); code != nil {
			session, err := sessions.Get(i, code.SessionID)
			if err == nil {
				cookie, err := session.ToAppCookie(u.Host, slug)
				if err == nil {
					c.SetCookie(cookie)
				}
			}
		}
	}
	q := u.Query()
	q.Del("code")
	u.RawQuery = q.Encode()
	return c.Redirect(http.StatusFound, u.String())
}

var assetHelper func(domain, file string) string
var clientTemplate *template.Template
var barTemplate *template.Template

func init() {
	h := statik.NewHandler(statik.Options{Prefix: "/assets"})
	assetHelper = func(domain, file string) string {
		file = h.AssetPath(file)
		if domain != "" {
			return "//" + domain + file
		}
		return file
	}

	funcsMap := template.FuncMap{
		"split": strings.Split,
		"asset": assetHelper,
	}

	clientTemplate = template.Must(template.New("cozy-client-js").Funcs(funcsMap).Parse(`` +
		`<script defer src="{{asset .Domain "/js/cozy-client.min.js"}}"></script>`,
	))

	barTemplate = template.Must(template.New("cozy-bar").Funcs(funcsMap).Parse(`` +
		`<link rel="stylesheet" type="text/css" href="{{asset .Domain "/fonts/fonts.css"}}">` +
		`<link rel="stylesheet" type="text/css" href="{{asset .Domain "/css/cozy-bar.min.css"}}">` +
		`<script defer src="{{asset .Domain "/js/cozy-bar.min.js"}}"></script>`,
	))
}

func cozyclientjs(i *instance.Instance) template.HTML {
	buf := new(bytes.Buffer)
	err := clientTemplate.Execute(buf, echo.Map{"Domain": i.Domain})
	if err != nil {
		return template.HTML("")
	}
	return template.HTML(buf.String()) // #nosec
}

func cozybar(i *instance.Instance) template.HTML {
	buf := new(bytes.Buffer)
	err := barTemplate.Execute(buf, echo.Map{"Domain": i.Domain})
	if err != nil {
		return template.HTML("")
	}
	return template.HTML(buf.String()) // #nosec
}
<|endoftext|>


<commit_before>package search

import (
	"fmt"
	"net/http"

	"github.com/algolia/algoliasearch-client-go/v3/algolia/call"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/errs"
	iopt "github.com/algolia/algoliasearch-client-go/v3/algolia/internal/opt"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/transport"
)

// Index provides methods to interact with the Algolia Search API on a single
// index.
type Index struct {
	appID        string
	name         string
	maxBatchSize int
	client       *Client
	transport    *transport.Transport
}

func newIndex(client *Client, name string) *Index {
	return &Index{
		appID:        client.appID,
		client:       client,
		name:         name,
		maxBatchSize: client.maxBatchSize,
		transport:    client.transport,
	}
}

func (i *Index) path(format string, a ...interface{}) string {
	prefix := fmt.Sprintf("/1/indexes/%s", i.name)
	suffix := fmt.Sprintf(format, a...)
	return prefix + suffix
}

// WaitTask blocks until the task identified by the given taskID is completed on
// Algolia engine.
func (i *Index) WaitTask(taskID int) error {
	return waitWithRetry(func() (bool, error) {
		res, err := i.GetStatus(taskID)
		if err != nil {
			return true, err
		}
		if res.Status == "published" {
			return true, nil
		}
		return false, nil
	})
}

func (i *Index) operation(destination, op string, opts ...interface{}) (res UpdateTaskRes, err error) {
	var scopes []string
	if opt := iopt.ExtractScopes(opts...); opt != nil {
		scopes = opt.Get()
	}
	req := IndexOperation{
		Destination: destination,
		Operation:   op,
		Scopes:      scopes,
	}
	path := i.path("/operation")
	err = i.transport.Request(&res, http.MethodPost, path, req, call.Write, opts...)
	res.wait = i.WaitTask
	return
}

// GetAppID returns the Algolia application ID on where the current index
// leaves.
func (i *Index) GetAppID() string {
	return i.appID
}

// ClearObjects deletes all the records of the index.
func (i *Index) ClearObjects(opts ...interface{}) (res UpdateTaskRes, err error) {
	path := i.path("/clear")
	err = i.transport.Request(&res, http.MethodPost, path, nil, call.Write, opts...)
	res.wait = i.WaitTask
	return
}

// Delete removes the entire index. After this call, new indexing calls can be
// sent with the same index instance.
func (i *Index) Delete(opts ...interface{}) (res DeleteTaskRes, err error) {
	path := i.path("")
	err = i.transport.Request(&res, http.MethodDelete, path, nil, call.Write, opts...)
	res.wait = i.WaitTask
	return
}

// GetStatus retrieves the task status according to the Algolia engine for the
// given task.
func (i *Index) GetStatus(taskID int) (res TaskStatusRes, err error) {
	path := i.path("/task/%d", taskID)
	err = i.transport.Request(&res, http.MethodGet, path, nil, call.Read)
	return
}

// Exists returns whether an initialized index exists or not, along with a nil
// error. When encountering a network error, a non-nil error is returned along
// with false.
func (i *Index) Exists() (bool, error) {
	_, err := i.GetSettings()
	_, ok := errs.IsAlgoliaErr(err)
	if !ok && err != nil {
		return false, err
	}
	_, ok = errs.IsAlgoliaErrWithCode(err, http.StatusNotFound)
	return !ok, nil
}
<commit_msg>fix(search): correctly encode index name in URIs (#574)<commit_after>package search

import (
	"fmt"
	"net/http"
	"net/url"

	"github.com/algolia/algoliasearch-client-go/v3/algolia/call"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/errs"
	iopt "github.com/algolia/algoliasearch-client-go/v3/algolia/internal/opt"
	"github.com/algolia/algoliasearch-client-go/v3/algolia/transport"
)

// Index provides methods to interact with the Algolia Search API on a single
// index.
type Index struct {
	appID        string
	name         string
	maxBatchSize int
	client       *Client
	transport    *transport.Transport
}

func newIndex(client *Client, name string) *Index {
	return &Index{
		appID:        client.appID,
		client:       client,
		name:         name,
		maxBatchSize: client.maxBatchSize,
		transport:    client.transport,
	}
}

func (i *Index) path(format string, a ...interface{}) string {
	prefix := fmt.Sprintf("/1/indexes/%s", url.QueryEscape(i.name))
	suffix := fmt.Sprintf(format, a...)
	return prefix + suffix
}

// WaitTask blocks until the task identified by the given taskID is completed on
// Algolia engine.
func (i *Index) WaitTask(taskID int) error {
	return waitWithRetry(func() (bool, error) {
		res, err := i.GetStatus(taskID)
		if err != nil {
			return true, err
		}
		if res.Status == "published" {
			return true, nil
		}
		return false, nil
	})
}

func (i *Index) operation(destination, op string, opts ...interface{}) (res UpdateTaskRes, err error) {
	var scopes []string
	if opt := iopt.ExtractScopes(opts...); opt != nil {
		scopes = opt.Get()
	}
	req := IndexOperation{
		Destination: destination,
		Operation:   op,
		Scopes:      scopes,
	}
	path := i.path("/operation")
	err = i.transport.Request(&res, http.MethodPost, path, req, call.Write, opts...)
	res.wait = i.WaitTask
	return
}

// GetAppID returns the Algolia application ID on where the current index
// leaves.
func (i *Index) GetAppID() string {
	return i.appID
}

// ClearObjects deletes all the records of the index.
func (i *Index) ClearObjects(opts ...interface{}) (res UpdateTaskRes, err error) {
	path := i.path("/clear")
	err = i.transport.Request(&res, http.MethodPost, path, nil, call.Write, opts...)
	res.wait = i.WaitTask
	return
}

// Delete removes the entire index. After this call, new indexing calls can be
// sent with the same index instance.
func (i *Index) Delete(opts ...interface{}) (res DeleteTaskRes, err error) {
	path := i.path("")
	err = i.transport.Request(&res, http.MethodDelete, path, nil, call.Write, opts...)
	res.wait = i.WaitTask
	return
}

// GetStatus retrieves the task status according to the Algolia engine for the
// given task.
func (i *Index) GetStatus(taskID int) (res TaskStatusRes, err error) {
	path := i.path("/task/%d", taskID)
	err = i.transport.Request(&res, http.MethodGet, path, nil, call.Read)
	return
}

// Exists returns whether an initialized index exists or not, along with a nil
// error. When encountering a network error, a non-nil error is returned along
// with false.
func (i *Index) Exists() (bool, error) {
	_, err := i.GetSettings()
	_, ok := errs.IsAlgoliaErr(err)
	if !ok && err != nil {
		return false, err
	}
	_, ok = errs.IsAlgoliaErrWithCode(err, http.StatusNotFound)
	return !ok, nil
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"os"
	"runtime"
	"sync"
	"time"

	contracts "github.com/estafette/estafette-ci-contracts"
	manifest "github.com/estafette/estafette-ci-manifest"
	"github.com/opentracing/opentracing-go"
	"github.com/rs/zerolog/log"
)

// PipelineRunner is the interface for running the pipeline steps
type PipelineRunner interface {
	runStage(context.Context, string, map[string]string, manifest.EstafetteStage) (estafetteStageRunResult, error)
	runStages(context.Context, []*manifest.EstafetteStage, string, map[string]string) (estafetteRunStagesResult, error)
	runParallelStages(context.Context, []*manifest.EstafetteStage, string, map[string]string) (estafetteStageRunResult, error)
	stopPipelineOnCancellation()
}

type pipelineRunnerImpl struct {
	envvarHelper        EnvvarHelper
	whenEvaluator       WhenEvaluator
	dockerRunner        DockerRunner
	runAsJob            bool
	cancellationChannel chan struct{}
	canceled            bool
}

// NewPipelineRunner returns a new PipelineRunner
func NewPipelineRunner(envvarHelper EnvvarHelper, whenEvaluator WhenEvaluator, dockerRunner DockerRunner, runAsJob bool, cancellationChannel chan struct{}) PipelineRunner {
	return &pipelineRunnerImpl{
		envvarHelper:        envvarHelper,
		whenEvaluator:       whenEvaluator,
		dockerRunner:        dockerRunner,
		runAsJob:            runAsJob,
		cancellationChannel: cancellationChannel,
	}
}

type estafetteStageRunResult struct {
	Stage                 manifest.EstafetteStage
	RunIndex              int
	IsDockerImagePulled   bool
	IsTrustedImage        bool
	DockerImageSize       int64
	DockerPullDuration    time.Duration
	DockerPullError       error
	DockerRunDuration     time.Duration
	DockerRunError        error
	OtherError            error
	ExitCode              int64
	Status                string
	LogLines              []contracts.BuildLogLine
	Canceled              bool
	ParallelStagesResults []estafetteStageRunResult
}

// Errors combines the different type of errors that occurred during this pipeline stage
func (result *estafetteStageRunResult) Errors() (errors []error) {

	if result.DockerPullError != nil {
		errors = append(errors, result.DockerPullError)
	}

	if result.DockerRunError != nil {
		errors = append(errors, result.DockerRunError)
	}

	if result.OtherError != nil {
		errors = append(errors, result.OtherError)
	}

	for _, pr := range result.ParallelStagesResults {
		if pr.RunIndex == pr.Stage.Retries && pr.HasErrors() {
			errors = append(errors, pr.Errors()...)
		}
	}

	return errors
}

// HasErrors indicates whether any errors happened in this pipeline stage
func (result *estafetteStageRunResult) HasErrors() bool {

	errors := result.Errors()

	return len(errors) > 0
}

func (pr *pipelineRunnerImpl) runStage(ctx context.Context, dir string, envvars map[string]string, p manifest.EstafetteStage) (result estafetteStageRunResult, err error) {

	span, ctx := opentracing.StartSpanFromContext(ctx, "RunStage")
	defer span.Finish()
	span.SetTag("stage", p.Name)

	p.ContainerImage = os.Expand(p.ContainerImage, pr.envvarHelper.getEstafetteEnv)

	result.Stage = p
	result.LogLines = make([]contracts.BuildLogLine, 0)

	log.Info().Msgf("[%v] Starting stage '%v'", p.Name, p.Name)

	if p.ContainerImage != "" {
		result.IsDockerImagePulled = pr.dockerRunner.isDockerImagePulled(p)
		result.IsTrustedImage = pr.dockerRunner.isTrustedImage(p)

		if !result.IsDockerImagePulled || runtime.GOOS == "windows" {

			// pull docker image
			dockerPullStart := time.Now()
			result.DockerPullError = pr.dockerRunner.runDockerPull(ctx, p)
			result.DockerPullDuration = time.Since(dockerPullStart)
			if result.DockerPullError != nil {
				return result, result.DockerPullError
			}
		}

		// set docker image size
		size, err := pr.dockerRunner.getDockerImageSize(p)
		if err != nil {
			return result, err
		}
		result.DockerImageSize = size
	}

	// log tailing - start stage
	if pr.runAsJob {
		tailLogLine := contracts.TailLogLine{
			Step:         p.Name,
			Image:        getBuildLogStepDockerImage(result),
			AutoInjected: &result.Stage.AutoInjected,
		}

		// log as json, to be tailed when looking at live logs from gui
		log.Info().Interface("tailLogLine", tailLogLine).Msg("")
	}

	// run commands in docker container
	dockerRunStart := time.Now()

	if len(p.ParallelStages) > 0 {
		innerResult, err := pr.runParallelStages(ctx, p.ParallelStages, dir, envvars)

		result.ParallelStagesResults = innerResult.ParallelStagesResults
		result.Canceled = innerResult.Canceled
		result.DockerRunError = err
	} else {
		result.LogLines, result.ExitCode, result.Canceled, result.DockerRunError = pr.dockerRunner.runDockerRun(ctx, dir, envvars, p)
	}

	result.DockerRunDuration = time.Since(dockerRunStart)

	// log tailing - finalize stage
	if pr.runAsJob {

		status := "SUCCEEDED"
		if result.DockerRunError != nil {
			status = "FAILED"
		}
		if result.Canceled {
			status = "CANCELED"
		}

		tailLogLine := contracts.TailLogLine{
			Step:     p.Name,
			Duration: &result.DockerRunDuration,
			ExitCode: &result.ExitCode,
			Status:   &status,
		}

		// log as json, to be tailed when looking at live logs from gui
		log.Info().Interface("tailLogLine", tailLogLine).Msg("")
	}

	if result.DockerRunError != nil {
		err = result.DockerRunError
	}

	if result.Canceled {
		log.Info().Msgf("[%v] Canceled pipeline '%v'", p.Name, p.Name)
	} else if err != nil {
		log.Warn().Err(err).Msgf("[%v] Pipeline '%v' container failed", p.Name, p.Name)
	} else {
		log.Info().Msgf("[%v] Finished pipeline '%v' successfully", p.Name, p.Name)
	}

	return
}

type estafetteRunStagesResult struct {
	StageResults []estafetteStageRunResult
	canceled     bool
}

// AggregatedErrors combines the different type of errors that occurred during this pipeline stage
func (result *estafetteRunStagesResult) AggregatedErrors() (errors []error) {

	for _, pr := range result.StageResults {
		if pr.RunIndex == pr.Stage.Retries && pr.HasErrors() {
			errors = append(errors, pr.Errors()...)
		}
	}

	return errors
}

// HasAggregatedErrors indicates whether any errors happened in all pipeline stages excluding retried stages that succeeded eventually
func (result *estafetteRunStagesResult) HasAggregatedErrors() bool {

	errors := result.AggregatedErrors()

	return len(errors) > 0
}

func (pr *pipelineRunnerImpl) runStages(ctx context.Context, stages []*manifest.EstafetteStage, dir string, envvars map[string]string) (result estafetteRunStagesResult, err error) {

	span, ctx := opentracing.StartSpanFromContext(ctx, "RunStages")
	defer span.Finish()

	// set default build status if not set
	err = pr.envvarHelper.initBuildStatus()
	if err != nil {
		return
	}

	if len(stages) == 0 {
		return result, fmt.Errorf("Manifest has no stages, failing the build")
	}

	for _, p := range stages {

		// handle cancellation happening in between stages
		if pr.canceled {
			result.canceled = pr.canceled
			return
		}

		whenEvaluationResult, err := pr.whenEvaluator.evaluate(p.Name, p.When, pr.whenEvaluator.getParameters())
		if err != nil {
			result.canceled = pr.canceled
			return result, err
		}

		if whenEvaluationResult {

			runIndex := 0
			for runIndex <= p.Retries {

				r, err := pr.runStage(ctx, dir, envvars, *p)
				r.RunIndex = runIndex

				// if canceled during stage stop further execution
				if r.Canceled || pr.canceled {
					r.Status = "CANCELED"
					result.StageResults = append(result.StageResults, r)
					result.canceled = r.Canceled
					return result, nil
				}

				if err != nil {

					// add error to log lines
					r.LogLines = append(r.LogLines, contracts.BuildLogLine{
						LineNumber: 1,
						Timestamp:  time.Now().UTC(),
						StreamType: "stderr",
						Text:       err.Error(),
					})

					// set 'failed' build status
					if runIndex == p.Retries {
						pr.envvarHelper.setEstafetteEnv("ESTAFETTE_BUILD_STATUS", "failed")
						envvars[pr.envvarHelper.getEstafetteEnvvarName("ESTAFETTE_BUILD_STATUS")] = "failed"
					}

					r.Status = "FAILED"
					r.OtherError = err

					result.StageResults = append(result.StageResults, r)

					runIndex++

					continue
				}

				// set 'succeeded' build status
				r.Status = "SUCCEEDED"

				result.StageResults = append(result.StageResults, r)

				break
			}

		} else {

			// if an error has happened in one of the previous steps or the when expression evaluates to false we still want to render the following steps in the result table
			r := estafetteStageRunResult{
				Stage:  *p,
				Status: "SKIPPED",
			}

			result.StageResults = append(result.StageResults, r)

			continue
		}
	}

	result.canceled = pr.canceled

	return
}

func (pr *pipelineRunnerImpl) runParallelStages(ctx context.Context, stages []*manifest.EstafetteStage, dir string, envvars map[string]string) (result estafetteStageRunResult, err error) {

	span, ctx := opentracing.StartSpanFromContext(ctx, "RunStages")
	defer span.Finish()

	// set default build status if not set
	err = pr.envvarHelper.initBuildStatus()
	if err != nil {
		return
	}

	if len(stages) == 0 {
		return result, fmt.Errorf("Manifest has no stages, failing the build")
	}

	var wg sync.WaitGroup
	wg.Add(len(stages))

	results := make(chan estafetteStageRunResult, len(stages))
	errors := make(chan error, len(stages))

	for _, p := range stages {

		go func(ctx context.Context, p *manifest.EstafetteStage, dir string, envvars map[string]string) {
			defer wg.Done()

			// handle cancellation happening in between stages
			if pr.canceled {
				return
			}

			whenEvaluationResult, err := pr.whenEvaluator.evaluate(p.Name, p.When, pr.whenEvaluator.getParameters())
			if err != nil {
				errors <- err
				return
			}

			if whenEvaluationResult {

				runIndex := 0
				for runIndex <= p.Retries {

					r, err := pr.runStage(ctx, dir, envvars, *p)
					r.RunIndex = runIndex

					// if canceled during stage stop further execution
					if r.Canceled || pr.canceled {
						r.Status = "CANCELED"

						results <- r

						return
					}

					if err != nil {

						// add error to log lines
						r.LogLines = append(r.LogLines, contracts.BuildLogLine{
							LineNumber: 1,
							Timestamp:  time.Now().UTC(),
							StreamType: "stderr",
							Text:       err.Error(),
						})

						// set 'failed' build status
						if runIndex == p.Retries {
							pr.envvarHelper.setEstafetteEnv("ESTAFETTE_BUILD_STATUS", "failed")
							envvars[pr.envvarHelper.getEstafetteEnvvarName("ESTAFETTE_BUILD_STATUS")] = "failed"
						}

						r.Status = "FAILED"
						r.OtherError = err

						results <- r

						runIndex++

						continue
					}

					// set 'succeeded' build status
					r.Status = "SUCCEEDED"

					results <- r

					break
				}

			} else {

				// if an error has happened in one of the previous steps or the when expression evaluates to false we still want to render the following steps in the result table
				r := estafetteStageRunResult{
					Stage:  *p,
					Status: "SKIPPED",
				}

				results <- r
			}
		}(ctx, p, dir, envvars)
	}

	wg.Wait()

	result.Canceled = pr.canceled

	close(results)
	for r := range results {
		result.ParallelStagesResults = append(result.ParallelStagesResults, r)
	}

	close(errors)
	for e := range errors {
		err = e
		return
	}

	return
}

func (pr *pipelineRunnerImpl) stopPipelineOnCancellation() {
	// wait for cancellation
	<-pr.cancellationChannel

	pr.canceled = true
}
<commit_msg>set correct outer stage status<commit_after>package main

import (
	"context"
	"fmt"
	"os"
	"runtime"
	"sync"
	"time"

	contracts "github.com/estafette/estafette-ci-contracts"
	manifest "github.com/estafette/estafette-ci-manifest"
	"github.com/opentracing/opentracing-go"
	"github.com/rs/zerolog/log"
)

// PipelineRunner is the interface for running the pipeline steps
type PipelineRunner interface {
	runStage(context.Context, string, map[string]string, manifest.EstafetteStage) (estafetteStageRunResult, error)
	runStages(context.Context, []*manifest.EstafetteStage, string, map[string]string) (estafetteRunStagesResult, error)
	runParallelStages(context.Context, []*manifest.EstafetteStage, string, map[string]string) (estafetteStageRunResult, error)
	stopPipelineOnCancellation()
}

type pipelineRunnerImpl struct {
	envvarHelper        EnvvarHelper
	whenEvaluator       WhenEvaluator
	dockerRunner        DockerRunner
	runAsJob            bool
	cancellationChannel chan struct{}
	canceled            bool
}

// NewPipelineRunner returns a new PipelineRunner
func NewPipelineRunner(envvarHelper EnvvarHelper, whenEvaluator WhenEvaluator, dockerRunner DockerRunner, runAsJob bool, cancellationChannel chan struct{}) PipelineRunner {
	return &pipelineRunnerImpl{
		envvarHelper:        envvarHelper,
		whenEvaluator:       whenEvaluator,
		dockerRunner:        dockerRunner,
		runAsJob:            runAsJob,
		cancellationChannel: cancellationChannel,
	}
}

type estafetteStageRunResult struct {
	Stage                 manifest.EstafetteStage
	RunIndex              int
	IsDockerImagePulled   bool
	IsTrustedImage        bool
	DockerImageSize       int64
	DockerPullDuration    time.Duration
	DockerPullError       error
	DockerRunDuration     time.Duration
	DockerRunError        error
	OtherError            error
	ExitCode              int64
	Status                string
	LogLines              []contracts.BuildLogLine
	Canceled              bool
	ParallelStagesResults []estafetteStageRunResult
}

// Errors combines the different type of errors that occurred during this pipeline stage
func (result *estafetteStageRunResult) Errors() (errors []error) {

	if result.DockerPullError != nil {
		errors = append(errors, result.DockerPullError)
	}

	if result.DockerRunError != nil {
		errors = append(errors, result.DockerRunError)
	}

	if result.OtherError != nil {
		errors = append(errors, result.OtherError)
	}

	for _, pr := range result.ParallelStagesResults {
		if pr.RunIndex == pr.Stage.Retries && pr.HasErrors() {
			errors = append(errors, pr.Errors()...)
		}
	}

	return errors
}

// HasErrors indicates whether any errors happened in this pipeline stage
func (result *estafetteStageRunResult) HasErrors() bool {

	errors := result.Errors()

	return len(errors) > 0
}

func (pr *pipelineRunnerImpl) runStage(ctx context.Context, dir string, envvars map[string]string, p manifest.EstafetteStage) (result estafetteStageRunResult, err error) {

	span, ctx := opentracing.StartSpanFromContext(ctx, "RunStage")
	defer span.Finish()
	span.SetTag("stage", p.Name)

	p.ContainerImage = os.Expand(p.ContainerImage, pr.envvarHelper.getEstafetteEnv)

	result.Stage = p
	result.LogLines = make([]contracts.BuildLogLine, 0)

	log.Info().Msgf("[%v] Starting stage '%v'", p.Name, p.Name)

	if p.ContainerImage != "" {
		result.IsDockerImagePulled = pr.dockerRunner.isDockerImagePulled(p)
		result.IsTrustedImage = pr.dockerRunner.isTrustedImage(p)

		if !result.IsDockerImagePulled || runtime.GOOS == "windows" {

			// pull docker image
			dockerPullStart := time.Now()
			result.DockerPullError = pr.dockerRunner.runDockerPull(ctx, p)
			result.DockerPullDuration = time.Since(dockerPullStart)
			if result.DockerPullError != nil {
				return result, result.DockerPullError
			}
		}

		// set docker image size
		size, err := pr.dockerRunner.getDockerImageSize(p)
		if err != nil {
			return result, err
		}
		result.DockerImageSize = size
	}

	// log tailing - start stage
	if pr.runAsJob {
		tailLogLine := contracts.TailLogLine{
			Step:         p.Name,
			Image:        getBuildLogStepDockerImage(result),
			AutoInjected: &result.Stage.AutoInjected,
		}

		// log as json, to be tailed when looking at live logs from gui
		log.Info().Interface("tailLogLine", tailLogLine).Msg("")
	}

	// run commands in docker container
	dockerRunStart := time.Now()

	if len(p.ParallelStages) > 0 {
		innerResult, err := pr.runParallelStages(ctx, p.ParallelStages, dir, envvars)

		result.ParallelStagesResults = innerResult.ParallelStagesResults
		result.Canceled = innerResult.Canceled
		result.DockerRunError = err
	} else {
		result.LogLines, result.ExitCode, result.Canceled, result.DockerRunError = pr.dockerRunner.runDockerRun(ctx, dir, envvars, p)
	}

	result.DockerRunDuration = time.Since(dockerRunStart)

	// log tailing - finalize stage
	if pr.runAsJob {

		status := "SUCCEEDED"
		if result.DockerRunError != nil {
			status = "FAILED"
		}
		if result.Canceled {
			status = "CANCELED"
		}

		tailLogLine := contracts.TailLogLine{
			Step:     p.Name,
			Duration: &result.DockerRunDuration,
			ExitCode: &result.ExitCode,
			Status:   &status,
		}

		// log as json, to be tailed when looking at live logs from gui
		log.Info().Interface("tailLogLine", tailLogLine).Msg("")
	}

	if result.DockerRunError != nil {
		err = result.DockerRunError
	}

	if result.Canceled {
		log.Info().Msgf("[%v] Canceled pipeline '%v'", p.Name, p.Name)
	} else if err != nil {
		log.Warn().Err(err).Msgf("[%v] Pipeline '%v' container failed", p.Name, p.Name)
	} else {
		log.Info().Msgf("[%v] Finished pipeline '%v' successfully", p.Name, p.Name)
	}

	return
}

type estafetteRunStagesResult struct {
	StageResults []estafetteStageRunResult
	canceled     bool
}

// AggregatedErrors combines the different type of errors that occurred during this pipeline stage
func (result *estafetteRunStagesResult) AggregatedErrors() (errors []error) {

	for _, pr := range result.StageResults {
		if pr.RunIndex == pr.Stage.Retries && pr.HasErrors() {
			errors = append(errors, pr.Errors()...)
		}
	}

	return errors
}

// HasAggregatedErrors indicates whether any errors happened in all pipeline stages excluding retried stages that succeeded eventually
func (result *estafetteRunStagesResult) HasAggregatedErrors() bool {

	errors := result.AggregatedErrors()

	return len(errors) > 0
}

func (pr *pipelineRunnerImpl) runStages(ctx context.Context, stages []*manifest.EstafetteStage, dir string, envvars map[string]string) (result estafetteRunStagesResult, err error) {

	span, ctx := opentracing.StartSpanFromContext(ctx, "RunStages")
	defer span.Finish()

	// set default build status if not set
	err = pr.envvarHelper.initBuildStatus()
	if err != nil {
		return
	}

	if len(stages) == 0 {
		return result, fmt.Errorf("Manifest has no stages, failing the build")
	}

	for _, p := range stages {

		// handle cancellation happening in between stages
		if pr.canceled {
			result.canceled = pr.canceled
			return
		}

		whenEvaluationResult, err := pr.whenEvaluator.evaluate(p.Name, p.When, pr.whenEvaluator.getParameters())
		if err != nil {
			result.canceled = pr.canceled
			return result, err
		}

		if whenEvaluationResult {

			runIndex := 0
			for runIndex <= p.Retries {

				r, err := pr.runStage(ctx, dir, envvars, *p)
				r.RunIndex = runIndex

				// if canceled during stage stop further execution
				if r.Canceled || pr.canceled {
					r.Status = "CANCELED"
					result.StageResults = append(result.StageResults, r)
					result.canceled = true
					return result, nil
				}

				if err != nil {

					// add error to log lines
					r.LogLines = append(r.LogLines, contracts.BuildLogLine{
						LineNumber: 1,
						Timestamp:  time.Now().UTC(),
						StreamType: "stderr",
						Text:       err.Error(),
					})

					// set 'failed' build status
					if runIndex == p.Retries {
						pr.envvarHelper.setEstafetteEnv("ESTAFETTE_BUILD_STATUS", "failed")
						envvars[pr.envvarHelper.getEstafetteEnvvarName("ESTAFETTE_BUILD_STATUS")] = "failed"
					}

					r.Status = "FAILED"
					r.OtherError = err

					result.StageResults = append(result.StageResults, r)

					runIndex++

					continue
				}

				// set 'succeeded' build status
				r.Status = "SUCCEEDED"

				result.StageResults = append(result.StageResults, r)

				break
			}

		} else {

			// if an error has happened in one of the previous steps or the when expression evaluates to false we still want to render the following steps in the result table
			r := estafetteStageRunResult{
				Stage:  *p,
				Status: "SKIPPED",
			}

			result.StageResults = append(result.StageResults, r)

			continue
		}
	}

	result.canceled = pr.canceled

	return
}

func (pr *pipelineRunnerImpl) runParallelStages(ctx context.Context, stages []*manifest.EstafetteStage, dir string, envvars map[string]string) (result estafetteStageRunResult, err error) {

	span, ctx := opentracing.StartSpanFromContext(ctx, "RunStages")
	defer span.Finish()

	// set default build status if not set
	err = pr.envvarHelper.initBuildStatus()
	if err != nil {
		return
	}

	if len(stages) == 0 {
		return result, fmt.Errorf("Manifest has no stages, failing the build")
	}

	var wg sync.WaitGroup
	wg.Add(len(stages))

	results := make(chan estafetteStageRunResult, len(stages))
	errors := make(chan error, len(stages))

	for _, p := range stages {

		go func(ctx context.Context, p *manifest.EstafetteStage, dir string, envvars map[string]string) {
			defer wg.Done()

			// handle cancellation happening in between stages
			if pr.canceled {
				return
			}

			whenEvaluationResult, err := pr.whenEvaluator.evaluate(p.Name, p.When, pr.whenEvaluator.getParameters())
			if err != nil {
				errors <- err
				return
			}

			if whenEvaluationResult {

				runIndex := 0
				for runIndex <= p.Retries {

					r, err := pr.runStage(ctx, dir, envvars, *p)
					r.RunIndex = runIndex

					// if canceled during stage stop further execution
					if r.Canceled || pr.canceled {
						r.Status = "CANCELED"

						results <- r

						return
					}

					if err != nil {

						// add error to log lines
						r.LogLines = append(r.LogLines, contracts.BuildLogLine{
							LineNumber: 1,
							Timestamp:  time.Now().UTC(),
							StreamType: "stderr",
							Text:       err.Error(),
						})

						r.Status = "FAILED"
						r.OtherError = err

						results <- r

						runIndex++

						continue
					}

					// set 'succeeded' build status
					r.Status = "SUCCEEDED"

					results <- r

					break
				}

			} else {

				// if an error has happened in one of the previous steps or the when expression evaluates to false we still want to render the following steps in the result table
				r := estafetteStageRunResult{
					Stage:  *p,
					Status: "SKIPPED",
				}

				results <- r
			}
		}(ctx, p, dir, envvars)
	}

	wg.Wait()

	result.Canceled = pr.canceled

	close(results)
	result.Status = "SUCCEEDED"
	for r := range results {
		result.ParallelStagesResults = append(result.ParallelStagesResults, r)

		// if canceled during one of the stages stop further execution
		if r.Canceled || pr.canceled {
			result.Status = "CANCELED"
			result.Canceled = true
		}

		// ensure outer stage gets correct status
		if !result.Canceled && r.Status == "FAILED" {
			result.Status = r.Status

			pr.envvarHelper.setEstafetteEnv("ESTAFETTE_BUILD_STATUS", "failed")
		}
	}

	close(errors)
	for e := range errors {
		err = e
		result.OtherError = err
		return
	}

	return
}

func (pr *pipelineRunnerImpl) stopPipelineOnCancellation() {
	// wait for cancellation
	<-pr.cancellationChannel

	pr.canceled = true
}
<|endoftext|>


<commit_before><commit_msg>Added some comments to Response<commit_after><|endoftext|>


<commit_before>package assets

import (
	"os"
)

func ExampleGenerator() {
	g := Generator{}

	if err := g.AddDir("."); err != nil {
		panic(err)
	}

	// This will write a go file to standard out. The generated go file
	// will reside in the g.PackageName package and will contain a
	// single variable g.VariableName of type assets.FileSystem containing
	// the whole file system.
	g.Write(os.Stdout)
}
<commit_msg>example fix<commit_after>package assets

import (
	"os"
)

func ExampleGenerator() {
	g := Generator{}

	if err := g.Add("."); err != nil {
		panic(err)
	}

	// This will write a go file to standard out. The generated go file
	// will reside in the g.PackageName package and will contain a
	// single variable g.VariableName of type assets.FileSystem containing
	// the whole file system.
	g.Write(os.Stdout)
}
<|endoftext|>


<commit_before>package stackit

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/pkg/errors"
	"fmt"
	"time"
)

type StackitUpInput struct {
	RoleARN          string
	StackPolicyBody  string
	TemplateBody     string
	PreviousTemplate bool
	Parameters       []*cloudformation.Parameter
	Tags             map[string]string
	NotificationARNs []string
	PopulateMissing  bool
}

func (s *Stackit) populateMissing(input *StackitUpInput) error {
	stack, _ := s.Describe()

	maybeAddParam := func(name string) {
		for _, param := range input.Parameters {
			if *param.ParameterKey == name {
				return
			}
		}
		input.Parameters = append(input.Parameters, &cloudformation.Parameter{
			ParameterKey:     &name,
			UsePreviousValue: aws.Bool(true),
		})
	}

	if len(input.TemplateBody) == 0 {
		input.PreviousTemplate = true

		for _, param := range stack.Parameters {
			maybeAddParam(*param.ParameterKey)
		}
	} else {
		resp, err := s.api.ValidateTemplate(&cloudformation.ValidateTemplateInput{TemplateBody: &input.TemplateBody})
		if err != nil {
			return err
		}

		for _, param := range resp.Parameters {
			maybeAddParam(*param.ParameterKey)
		}
	}

	return nil
}

func (s *Stackit) EnsureStackReady(events chan<- TailStackEvent) error {
	stack, err := s.Describe()
	if err != nil {
		s.error(err, events)
		return err
	}

	if stack != nil { // stack already exists
		if !IsTerminalStatus(*stack.StackStatus) {
			s.PollStackEvents("", func(event TailStackEvent) {
				events <- event
			})
		}

		stack, err = s.Describe()
		if err != nil {
			s.error(err, events)
			return err
		}

		if *stack.StackStatus == "CREATE_FAILED" || *stack.StackStatus == "ROLLBACK_COMPLETE" {
			token := generateToken()
			s.api.DeleteStack(&cloudformation.DeleteStackInput{StackName: &s.stackId, ClientRequestToken: &token})
			s.PollStackEvents(token, func(event TailStackEvent) {
				events <- event
			})
		}
	}

	return nil
}

func (s *Stackit) Up(input StackitUpInput, events chan<- TailStackEvent) {
	s.stackId = ""
	stack, err := s.Describe()
	if err != nil {
		s.error(err, events)
	}

	token := generateToken()

	createInput := &cloudformation.CreateChangeSetInput{
		ChangeSetName:       aws.String(fmt.Sprintf("csid-%d", time.Now().Unix())),
		StackName:           &s.stackName,
		Capabilities:        aws.StringSlice([]string{"CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"}),
		Parameters:          input.Parameters,
		Tags:                mapToTags(input.Tags),
		NotificationARNs:    aws.StringSlice(input.NotificationARNs),
		ClientToken:         &token,
		UsePreviousTemplate: &input.PreviousTemplate,
	}

	if len(input.TemplateBody) > 0 {
		createInput.TemplateBody = &input.TemplateBody
	}

	if len(input.RoleARN) > 0 {
		createInput.RoleARN = &input.RoleARN
	}

	if stack != nil { // stack already exists
		createInput.ChangeSetType = aws.String(cloudformation.ChangeSetTypeUpdate)

		if input.PopulateMissing {
			err := s.populateMissing(&input)
			if err != nil {
				s.error(errors.Wrap(err, "populating missing parameters"), events)
				return
			}
		}
	} else {
		createInput.ChangeSetType = aws.String(cloudformation.ChangeSetTypeCreate)
	}

	resp, err := s.api.CreateChangeSet(createInput)
	if err != nil {
		s.error(errors.Wrap(err, "creating change set"), events)
		return
	}

	s.stackId = *resp.StackId

	change, err := s.waitForChangeset(resp.Id)
	isNoop := change != nil && len(change.Changes) == 0

	if isNoop { // update is a no-op, nothing to change
		s.api.DeleteChangeSet(&cloudformation.DeleteChangeSetInput{ChangeSetName: resp.Id})
	}

	if err != nil {
		s.error(err, events)
		return
	} else if isNoop {
		close(events)
		return
	}

	_, err = s.api.ExecuteChangeSet(&cloudformation.ExecuteChangeSetInput{
		ChangeSetName:      resp.Id,
		ClientRequestToken: &token,
	})
	if err != nil {
		s.error(errors.Wrap(err, "executing change set"), events)
		return
	}

	s.PollStackEvents(token, func(event TailStackEvent) {
		events <- event
	})
	close(events)
}

<commit_msg>Cleanup empty stacks stuck in REVIEW_IN_PROGRESS state<commit_after>package stackit

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/pkg/errors"
	"fmt"
	"time"
)

type StackitUpInput struct {
	RoleARN          string
	StackPolicyBody  string
	TemplateBody     string
	PreviousTemplate bool
	Parameters       []*cloudformation.Parameter
	Tags             map[string]string
	NotificationARNs []string
	PopulateMissing  bool
}

func (s *Stackit) populateMissing(input *StackitUpInput) error {
	stack, _ := s.Describe()

	maybeAddParam := func(name string) {
		for _, param := range input.Parameters {
			if *param.ParameterKey == name {
				return
			}
		}
		input.Parameters = append(input.Parameters, &cloudformation.Parameter{
			ParameterKey:     &name,
			UsePreviousValue: aws.Bool(true),
		})
	}

	if len(input.TemplateBody) == 0 {
		input.PreviousTemplate = true

		for _, param := range stack.Parameters {
			maybeAddParam(*param.ParameterKey)
		}
	} else {
		resp, err := s.api.ValidateTemplate(&cloudformation.ValidateTemplateInput{TemplateBody: &input.TemplateBody})
		if err != nil {
			return err
		}

		for _, param := range resp.Parameters {
			maybeAddParam(*param.ParameterKey)
		}
	}

	return nil
}

func (s *Stackit) EnsureStackReady(events chan<- TailStackEvent) error {
	stack, err := s.Describe()
	if err != nil {
		s.error(err, events)
		return err
	}

	cleanup := func() {
		token := generateToken()
		s.api.DeleteStack(&cloudformation.DeleteStackInput{StackName: &s.stackId, ClientRequestToken: &token})
		s.PollStackEvents(token, func(event TailStackEvent) {
			events <- event
		})
	}

	if stack != nil { // stack already exists
		if !IsTerminalStatus(*stack.StackStatus) && *stack.StackStatus != "REVIEW_IN_PROGRESS" {
			s.PollStackEvents("", func(event TailStackEvent) {
				events <- event
			})
		}

		stack, err = s.Describe()
		if err != nil {
			s.error(err, events)
			return err
		}

		if *stack.StackStatus == "CREATE_FAILED" || *stack.StackStatus == "ROLLBACK_COMPLETE" {
			cleanup()
		} else if *stack.StackStatus == "REVIEW_IN_PROGRESS" {
			resp, err := s.api.ListStackResources(&cloudformation.ListStackResourcesInput{StackName: &s.stackId})
			if err != nil {
				s.error(err, events)
				return err
			}
			if len(resp.StackResourceSummaries) == 0 {
				cleanup()
			}
		}
	}

	return nil
}

func (s *Stackit) Up(input StackitUpInput, events chan<- TailStackEvent) {
	s.stackId = ""
	stack, err := s.Describe()
	if err != nil {
		s.error(err, events)
	}

	token := generateToken()

	createInput := &cloudformation.CreateChangeSetInput{
		ChangeSetName:       aws.String(fmt.Sprintf("csid-%d", time.Now().Unix())),
		StackName:           &s.stackName,
		Capabilities:        aws.StringSlice([]string{"CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"}),
		Parameters:          input.Parameters,
		Tags:                mapToTags(input.Tags),
		NotificationARNs:    aws.StringSlice(input.NotificationARNs),
		ClientToken:         &token,
		UsePreviousTemplate: &input.PreviousTemplate,
	}

	if len(input.TemplateBody) > 0 {
		createInput.TemplateBody = &input.TemplateBody
	}

	if len(input.RoleARN) > 0 {
		createInput.RoleARN = &input.RoleARN
	}

	if stack != nil { // stack already exists
		createInput.ChangeSetType = aws.String(cloudformation.ChangeSetTypeUpdate)

		if input.PopulateMissing {
			err := s.populateMissing(&input)
			if err != nil {
				s.error(errors.Wrap(err, "populating missing parameters"), events)
				return
			}
		}
	} else {
		createInput.ChangeSetType = aws.String(cloudformation.ChangeSetTypeCreate)
	}

	resp, err := s.api.CreateChangeSet(createInput)
	if err != nil {
		s.error(errors.Wrap(err, "creating change set"), events)
		return
	}

	s.stackId = *resp.StackId

	change, err := s.waitForChangeset(resp.Id)
	isNoop := change != nil && len(change.Changes) == 0

	if isNoop { // update is a no-op, nothing to change
		s.api.DeleteChangeSet(&cloudformation.DeleteChangeSetInput{ChangeSetName: resp.Id})
	}

	if err != nil {
		s.error(err, events)
		return
	} else if isNoop {
		close(events)
		return
	}

	_, err = s.api.ExecuteChangeSet(&cloudformation.ExecuteChangeSetInput{
		ChangeSetName:      resp.Id,
		ClientRequestToken: &token,
	})
	if err != nil {
		s.error(errors.Wrap(err, "executing change set"), events)
		return
	}

	s.PollStackEvents(token, func(event TailStackEvent) {
		events <- event
	})
	close(events)
}
<|endoftext|>