<commit_before>package main

// SnapRequest data model
type SnapRequest struct {
	TransactionDetails TransactionDetails `json:"transaction_details"`
	CreditCard         CreditCard         `json:"credit_card"`
	ItemDetails        []ItemDetails      `json:"item_details"`
	CustomerDetails    CustomerDetails    `json:"customer_details"`
	Expiry             Expiry             `json:"expiry"`
}

// TransactionDetails data model
type TransactionDetails struct {
	OrderID     string `json:"order_id"`
	GrossAmount int    `json:"gross_amount"`
}

// CreditCard data model
type CreditCard struct {
	Secure        bool        `json:"secure,omitempty"`
	Channel       string      `json:"channel,omitempty"`
	Bank          string      `json:"bank,omitempty"`
	Installment   Installment `json:"installment,omitempty"`
	WhitelistBins []string    `json:"whitelist_bins,omitempty"`
}

// Installment data model
type Installment struct {
	Required bool  `json:"required,omitempty"`
	Terms    Terms `json:"terms,omitempty"`
}

// Terms for Installment data model
type Terms struct {
	BNI     []int `json:"bni,omitempty"`
	Mandiri []int `json:"mandiri,omitempty"`
	CIMB    []int `json:"cimb,omitempty"`
	BCA     []int `json:"bca,omitempty"`
	Offline []int `json:"offline,omitempty"`
}

// ItemDetails data model
type ItemDetails struct {
	ID       string `json:"id"`
	Price    int    `json:"price"`
	Quantity int    `json:"quantity"`
	Name     string `json:"name"`
}

// CustomerDetails data model
type CustomerDetails struct {
	FirstName       string  `json:"first_name"`
	LastName        string  `json:"last_name"`
	Email           string  `json:"email"`
	Phone           string  `json:"phone"`
	BillingAddress  Address `json:"billing_address"`
	ShippingAddress Address `json:"shipping_address"`
}

// Address data model
type Address struct {
	FirstName   string `json:"first_name"`
	LastName    string `json:"last_name"`
	Email       string `json:"email"`
	Phone       string `json:"phone"`
	Address     string `json:"address"`
	City        string `json:"city"`
	PostalCode  string `json:"postal_code"`
	CountryCode string `json:"country_code"`
}

// Expiry data model
type Expiry struct {
	StartTime string `json:"start_time"`
	Unit      string `json:"unit"`
	Duration  int    `json:"duration"`
}

// Card data model
type Card struct {
	UserID     string `json:"user_id,omitempty"`
	SavedToken string `json:"saved_token_id"`
	MaskedCard string `json:"masked_card"`
	StatusCode string `json:"status_code"`
}

// JsonCard data model
type JsonCard struct {
	SavedToken string `json:"saved_token_id"`
	MaskedCard string `json:"masked_card"`
	StatusCode string `json:"status_code"`
}
<commit_msg>Fix data model<commit_after>package main

// SnapRequest data model
type SnapRequest struct {
	TransactionDetails TransactionDetails `json:"transaction_details"`
	CreditCard         CreditCard         `json:"credit_card"`
	ItemDetails        []ItemDetails      `json:"item_details"`
	CustomerDetails    CustomerDetails    `json:"customer_details"`
	Expiry             Expiry             `json:"expiry"`
}

// TransactionDetails data model
type TransactionDetails struct {
	OrderID     string `json:"order_id"`
	GrossAmount int    `json:"gross_amount"`
}

// CreditCard data model
type CreditCard struct {
	Secure        bool        `json:"secure,omitempty"`
	Channel       string      `json:"channel,omitempty"`
	Bank          string      `json:"bank,omitempty"`
	Installment   Installment `json:"installment,omitempty"`
	WhitelistBins []string    `json:"whitelist_bins,omitempty"`
}

// Installment data model
type Installment struct {
	Required bool  `json:"required,omitempty"`
	Terms    Terms `json:"terms,omitempty"`
}

// Terms for Installment data model
type Terms struct {
	BNI     []int `json:"bni,omitempty"`
	Mandiri []int `json:"mandiri,omitempty"`
	CIMB    []int `json:"cimb,omitempty"`
	BCA     []int `json:"bca,omitempty"`
	Offline []int `json:"offline,omitempty"`
}

// ItemDetails data model
type ItemDetails struct {
	ID       string `json:"id"`
	Price    int    `json:"price"`
	Quantity int    `json:"quantity"`
	Name     string `json:"name"`
}

// CustomerDetails data model
type CustomerDetails struct {
	FirstName       string  `json:"first_name"`
	LastName        string  `json:"last_name"`
	Email           string  `json:"email"`
	Phone           string  `json:"phone"`
	BillingAddress  Address `json:"billing_address"`
	ShippingAddress Address `json:"shipping_address"`
}

// Address data model
type Address struct {
	FirstName   string `json:"first_name"`
	LastName    string `json:"last_name"`
	Email       string `json:"email"`
	Phone       string `json:"phone"`
	Address     string `json:"address"`
	City        string `json:"city"`
	PostalCode  string `json:"postal_code"`
	CountryCode string `json:"country_code"`
}

// Expiry data model
type Expiry struct {
	StartTime string `json:"start_time"`
	Unit      string `json:"unit"`
	Duration  int    `json:"duration"`
}

// Card data model
type Card struct {
	UserID     string `json:"user_id,omitempty"`
	SavedToken string `json:"token_id"`
	MaskedCard string `json:"cardhash"`
	StatusCode string `json:"status_code"`
}

// JsonCard data model
type JsonCard struct {
	SavedToken string `json:"token_id"`
	MaskedCard string `json:"cardhash"`
	StatusCode string `json:"status_code"`
}
<|endoftext|>


<commit_before>package main

import "testing"

func TestSample(t *testing.T) {
	t.Run("origin", func(t *testing.T) {
		f := NewEditor("foobar")
		compare(t, "foobar", f.String())
	})

	t.Run("insert", func(t *testing.T) {
		f := NewEditor("foobar")
		compare(t, "fobazobar", f.Insert(2, "baz").String())
	})

	t.Run("append", func(t *testing.T) {
		f := NewEditor("foobar")
		compare(t, "foobarbaz", f.Insert(6, "baz").String())
	})

	t.Run("delete", func(t *testing.T) {
		f := NewEditor("foobar")
		compare(t, "far", f.Delete(1, 3).String())
	})
}

func compare(t *testing.T, exp, got string) {
	if got != exp {
		t.Errorf("Expect: %q; got %q", exp, got)
	}
}
<commit_msg>Add more tests for task02<commit_after>package main

import "testing"

func TestSample(t *testing.T) {
	t.Run("origin", func(t *testing.T) {
		f := NewEditor("foobar")
		compare(t, "foobar", f.String())
	})

	t.Run("insert", func(t *testing.T) {
		f := NewEditor("foobar")
		compare(t, "fobazobar", f.Insert(2, "baz").String())
	})

	t.Run("insert_out_of_bounds_position", func(t *testing.T) {
		f := NewEditor("foo").Insert(453, ".")
		compare(t, "foo.", f.String())
	})

	t.Run("append", func(t *testing.T) {
		f := NewEditor("foobar")
		compare(t, "foobarbaz", f.Insert(6, "baz").String())
	})

	t.Run("insert_append_front_and_append_back", func(t *testing.T) {
		f := NewEditor("A large span of text")
		f.Insert(16, "an English ").Insert(2, "very ").Insert(36, " message.").Insert(0, "This is ")
		compare(t, "This is A very large span of an English text message.", f.String())
	})

	t.Run("delete", func(t *testing.T) {
		f := NewEditor("foobar")
		compare(t, "far", f.Delete(1, 3).String())
	})

	t.Run("delete_out_of_bounds_offset", func(t *testing.T) {
		f := NewEditor("foo").Delete(300, 1)
		compare(t, "foo", f.String())
	})

	t.Run("delete_out_of_bounds_length", func(t *testing.T) {
		f := NewEditor("foo").Delete(1, 3)
		compare(t, "f", f.String())
	})

	t.Run("delete_where_single_partial_piece_is_affected", func(t *testing.T) {
		f := NewEditor("A large span of text")
		f.Insert(16, "an English ").Insert(2, "very ").Insert(36, " message.").Insert(0, "This is ")
		f.Delete(12, 2)
		compare(t, "This is A ve large span of an English text message.", f.String())
	})

	t.Run("delete_where_single_whole_piece_is_affected", func(t *testing.T) {
		f := NewEditor("A large span of text")
		f.Insert(16, "an English ").Insert(2, "very ").Insert(36, " message.").Insert(0, "This is ")
		f.Delete(10, 5)
		compare(t, "This is A large span of an English text message.", f.String())
	})

	t.Run("delete_where_adjacent_pieces_are_affected", func(t *testing.T) {
		f := NewEditor("A large span of text")
		f.Insert(16, "an English ").Insert(2, "very ").Insert(36, " message.").Insert(0, "This is ")
		f.Delete(12, 8)
		compare(t, "This is A ve span of an English text message.", f.String())
	})

	t.Run("delete_where_multiple_pieces_are_affected", func(t *testing.T) {
		f := NewEditor("A span of text")
		f.Insert(10, "English ")
		f.Delete(1, 20)
		compare(t, "At", f.String())
	})

	t.Run("delete_where_multiple_pieces_are_affected_multiple_inserts", func(t *testing.T) {
		f := NewEditor("A large span of text")
		f.Insert(16, "an English ").Insert(2, "very ").Insert(36, " message.").Insert(0, "This is ")
		f.Delete(12, 27)
		compare(t, "This is A ve text message.", f.String())
	})

	t.Run("undo", func(t *testing.T) {
		f := NewEditor("A span of text")
		f.Insert(10, "English ").Insert(0, "This is ").Undo()
		compare(t, "A span of English text", f.String())
	})

	t.Run("undo_original", func(t *testing.T) {
		f := NewEditor("A span of text").Undo().Undo().Undo()
		compare(t, "A span of text", f.String())
	})

	t.Run("redo", func(t *testing.T) {
		f := NewEditor("A span of text")
		f.Insert(10, "English ").Insert(0, "This is ").Undo().Undo().Redo()
		compare(t, "A span of English text", f.String())
	})

	t.Run("redundant_redo", func(t *testing.T) {
		f := NewEditor("A span of text")
		f.Insert(10, "English ").Undo().Redo().Redo().Redo()
		compare(t, "A span of English text", f.String())
	})
}

func compare(t *testing.T, exp, got string) {
	if got != exp {
		t.Errorf("Expect: %q; got %q", exp, got)
	}
}
<|endoftext|>


<commit_before>/*
Copyright (C) 2013 Lucy

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.package main
*/

package main

import (
	"encoding/binary"
	"flag"
	"fmt"
	"math"
	"math/cmplx"
	"os"

	"github.com/jackvalmadre/go-fftw"
	"github.com/nsf/termbox-go"
)

var (
	color = flag.String("c", "blue", "which color to use")
	dim   = flag.Bool("d", false, "don't use bold")

	step = flag.Int("step", 2,
		"number of samples to average in each column (for wave)")
	scale = flag.Float64("scale", 3,
		"scale divisor (for spectrum)")

	file = flag.String("f", "/tmp/mpd.fifo",
		"where to read fifo output from")
	vis = flag.String("v", "wave",
		"choose visualization (spectrum or wave)")
)

var colors = map[string]termbox.Attribute{
	"default": termbox.ColorDefault,
	"black":   termbox.ColorBlack,
	"red":     termbox.ColorRed,
	"green":   termbox.ColorGreen,
	"yellow":  termbox.ColorYellow,
	"blue":    termbox.ColorBlue,
	"magenta": termbox.ColorMagenta,
	"cyan":    termbox.ColorCyan,
	"white":   termbox.ColorWhite,
}

var on termbox.Attribute
var off = termbox.ColorDefault

var dbuf [][]bool

func main() {
	flag.Parse()
	var ok bool
	on, ok = colors[*color]
	if !ok {
		die("unknown color " + *color)
	}
	if !*dim {
		on = on | termbox.AttrBold
	}

	var draw func(chan int16)
	switch *vis {
	case "spectrum":
		draw = drawSpectrum
	case "wave":
		draw = drawWave
	default:
		fmt.Fprintf(os.Stderr, "mpdviz: unknown visualization %s\n"+
			"supported visualizations: spectrum, wave\n", *vis)
		return
	}

	file, err := os.Open(*file)
	if err != nil {
		die(err)
	}

	err = termbox.Init()
	if err != nil {
		die(err)
	}
	defer termbox.Close()

	clear()

	ch := make(chan int16, 128)
	go draw(ch)

	go func() {
		for {
			var i int16
			binary.Read(file, binary.LittleEndian, &i)
			ch <- i
		}
	}()

	// input handler
	for {
		ev := termbox.PollEvent()
		if ev.Ch == 0 && ev.Key == termbox.KeyCtrlC {
			return
		}
	}

}

func flush(both, upc, downc rune) {
	w, h := len(dbuf[0]), len(dbuf)
	for x := 0; x < h; x++ {
		for y := 0; y < w; y++ {
			if y%2 != 0 {
				continue
			}

			up, down := dbuf[x][y], dbuf[x][y+1]
			switch {
			case up && down:
				termbox.SetCell(x, y/2, both, on, off)
			case up:
				termbox.SetCell(x, y/2, upc, on, off)
			case down:
				termbox.SetCell(x, y/2, downc, on, off)
			}
		}
	}
	termbox.Flush()
}

func clear() {
	termbox.Clear(0, 0)
	w, h := termbox.Size()
	h *= 2
	dbuf = make([][]bool, w)
	for i := 0; i < w; i++ {
		dbuf[i] = make([]bool, h)
		for j := 0; j < h; j++ {
			dbuf[i][j] = false
		}
	}
}

func drawWave(c chan int16) {
	for pos := 0; ; pos++ {
		w, h := len(dbuf), len(dbuf[0])
		if pos >= w {
			flush('█', '▀', '▄')
			clear()
			pos = 0
		}

		var v float64
		for i := 0; i < *step; i++ {
			v += float64(<-c)
		}

		half_h := float64(h / 2)
		v = (v/float64(*step))/(32768/half_h) + half_h
		dbuf[pos][int(v)] = true
	}
}

func drawSpectrum(c chan int16) {
	var (
		samples = 2048
		resn    = samples/2 + 1
		mag     = make([]float64, resn)
		in      = make([]float64, samples)
		out     = fftw.Alloc1d(resn)
		plan    = fftw.PlanDftR2C1d(in, out, fftw.Estimate)
	)

	// TODO: improve efficiency, possibly dither more frames
	for {
		w, h := len(dbuf), len(dbuf[0])
		for i := 0; i < samples; i++ {
			in[i] = float64(<-c)
		}

		plan.Execute()
		for i := 0; i < resn; i++ {
			mag[i] = cmplx.Abs(out[i]) / 1e5 * float64(h) / *scale
		}

		mlen := resn / w
		for i := 0; i < w; i++ {
			v := 0.0
			for _, m := range mag[mlen*i:][:mlen] {
				v += m
			}
			v /= float64(mlen)
			v = math.Min(float64(h), v)
			for j := h - 1; j > h-int(v); j-- {
				dbuf[i][j] = true
			}
		}

		flush('┃', '╹', '╻')
		clear()
	}
}

func die(args ...interface{}) {
	fmt.Fprintf(os.Stderr, "mpdviz: %s\n", fmt.Sprint(args...))
	os.Exit(1)
}
<commit_msg>spectrum: auto scale number of samples based on terminal size<commit_after>/*
Copyright (C) 2013 Lucy

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.package main
*/

package main

import (
	"encoding/binary"
	"flag"
	"fmt"
	"math"
	"math/cmplx"
	"os"

	"github.com/jackvalmadre/go-fftw"
	"github.com/nsf/termbox-go"
)

var (
	color = flag.String("c", "blue", "which color to use")
	dim   = flag.Bool("d", false, "don't use bold")

	step = flag.Int("step", 2,
		"number of samples to average in each column (for wave)")
	scale = flag.Float64("scale", 3,
		"scale divisor (for spectrum)")

	file = flag.String("f", "/tmp/mpd.fifo",
		"where to read fifo output from")
	vis = flag.String("v", "wave",
		"choose visualization (spectrum or wave)")
)

var colors = map[string]termbox.Attribute{
	"default": termbox.ColorDefault,
	"black":   termbox.ColorBlack,
	"red":     termbox.ColorRed,
	"green":   termbox.ColorGreen,
	"yellow":  termbox.ColorYellow,
	"blue":    termbox.ColorBlue,
	"magenta": termbox.ColorMagenta,
	"cyan":    termbox.ColorCyan,
	"white":   termbox.ColorWhite,
}

var on termbox.Attribute
var off = termbox.ColorDefault

var dbuf [][]bool

func main() {
	flag.Parse()
	var ok bool
	on, ok = colors[*color]
	if !ok {
		die("unknown color " + *color)
	}
	if !*dim {
		on = on | termbox.AttrBold
	}

	var draw func(chan int16)
	switch *vis {
	case "spectrum":
		draw = drawSpectrum
	case "wave":
		draw = drawWave
	default:
		fmt.Fprintf(os.Stderr, "mpdviz: unknown visualization %s\n"+
			"supported visualizations: spectrum, wave\n", *vis)
		return
	}

	file, err := os.Open(*file)
	if err != nil {
		die(err)
	}

	err = termbox.Init()
	if err != nil {
		die(err)
	}
	defer termbox.Close()

	clear()

	ch := make(chan int16, 128)
	go draw(ch)

	go func() {
		for {
			var i int16
			binary.Read(file, binary.LittleEndian, &i)
			ch <- i
		}
	}()

	// input handler
	for {
		ev := termbox.PollEvent()
		if ev.Ch == 0 && ev.Key == termbox.KeyCtrlC {
			return
		}
	}

}

func flush(both, upc, downc rune) {
	w, h := len(dbuf[0]), len(dbuf)
	for x := 0; x < h; x++ {
		for y := 0; y < w; y++ {
			if y%2 != 0 {
				continue
			}

			up, down := dbuf[x][y], dbuf[x][y+1]
			switch {
			case up && down:
				termbox.SetCell(x, y/2, both, on, off)
			case up:
				termbox.SetCell(x, y/2, upc, on, off)
			case down:
				termbox.SetCell(x, y/2, downc, on, off)
			}
		}
	}
	termbox.Flush()
}

func clear() {
	termbox.Clear(0, 0)
	w, h := termbox.Size()
	h *= 2
	dbuf = make([][]bool, w)
	for i := 0; i < w; i++ {
		dbuf[i] = make([]bool, h)
		for j := 0; j < h; j++ {
			dbuf[i][j] = false
		}
	}
}

func drawWave(c chan int16) {
	for pos := 0; ; pos++ {
		w, h := len(dbuf), len(dbuf[0])
		if pos >= w {
			flush('█', '▀', '▄')
			clear()
			pos = 0
		}

		var v float64
		for i := 0; i < *step; i++ {
			v += float64(<-c)
		}

		half_h := float64(h / 2)
		v = (v/float64(*step))/(32768/half_h) + half_h
		dbuf[pos][int(v)] = true
	}
}

func drawSpectrum(c chan int16) {
	var (
		samples = 128
		resn    = samples/2 + 1
		mag     = make([]float64, resn)
		in      = make([]float64, samples)
		out     = fftw.Alloc1d(resn)
		plan    = fftw.PlanDftR2C1d(in, out, fftw.Estimate)
	)

	for {
		w, h := len(dbuf), len(dbuf[0])
		if w2 := w * 2; samples != w2 {
			fftw.Free1d(out)
			samples = w2
			resn = w2/2 + 1
			mag = make([]float64, resn)
			in = make([]float64, w2)
			out = fftw.Alloc1d(resn)
			plan = fftw.PlanDftR2C1d(in, out, fftw.Estimate)
		}

		for i := 0; i < samples; i++ {
			in[i] = float64(<-c)
		}

		plan.Execute()
		for i := 0; i < resn; i++ {
			mag[i] = cmplx.Abs(out[i]) / 1e5 * float64(h) / *scale
		}

		mlen := resn / w
		for i := 0; i < w; i++ {
			v := 0.0
			for _, m := range mag[mlen*i:][:mlen] {
				v += m
			}
			v /= float64(mlen)
			v = math.Min(float64(h), v)
			for j := h - 1; j > h-int(v); j-- {
				dbuf[i][j] = true
			}
		}

		flush('┃', '╹', '╻')
		clear()
	}
}

func die(args ...interface{}) {
	fmt.Fprintf(os.Stderr, "mpdviz: %s\n", fmt.Sprint(args...))
	os.Exit(1)
}
<|endoftext|>


<commit_before>package srv

import (
    "net"
    "log"
    "fmt"
    "strings"
    "strconv"
    "errors"
    "crypto/tls"
    "crypto/x509"
    "io/ioutil"
    "github.com/robmcl4/Mycroft-Core-Go/mycroft/app"
    "github.com/robmcl4/Mycroft-Core-Go/mycroft/cmd"
    "github.com/robmcl4/Mycroft-Core-Go/mycroft/dispatch"
    "github.com/robmcl4/Mycroft-Core-Go/mycroft/registry"
)

// Starts listening for client connections.
// When new applications connect it will launch listeners in their own goroutine.
func StartListen(port int, useTls bool, crtPath string, keyPath string, sname string) (error) {
    addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", port))
    if err != nil {
        return err
    }

    var l net.Listener

    l, err = net.ListenTCP("tcp", addr)
    if err != nil {
        return err
    }

    if useTls {
        cert, err := tls.LoadX509KeyPair(crtPath, keyPath)
        if err != nil {
            return err
        }
        conf := tls.Config{}

        certs := make([]tls.Certificate, 1)
        certs[0] = cert
        conf.Certificates = certs

        cp := x509.NewCertPool()
        caCert, err := ioutil.ReadFile(crtPath)
        if err != nil {
            return err
        }
        if !cp.AppendCertsFromPEM(caCert) {
            return errors.New("Could not append PEM cert")
        }
        conf.RootCAs = cp

        conf.ServerName = sname

        conf.ClientAuth = tls.RequireAndVerifyClientCert

        conf.ClientCAs = cp

        l = tls.NewListener(l, &conf)
    }

    defer l.Close() // at the end of this method close the connection
    log.Println("Starting listen loop")
    for {
        a, err := acceptApp(l)
        if err != nil {
            return err
        } else {
            log.Println("Got connection")
            go ListenForCommands(a)
        }
    }
    return nil
}

// Listen for and accept a new application connection
func acceptApp(lnr net.Listener) (*app.App, error) {
    conn, err := lnr.Accept()
    if err != nil {
        return nil, err
    }
    ret := app.NewApp()
    ret.Connection = conn
    return ret, nil
}

// Start listening for commands through this app's connection.
// NOTE: this should likely only be called as a goroutine.
func ListenForCommands(a *app.App) {
    defer closeApp(a)
    smallBuff := make([]byte, 200)
    smallBuffI := 0
    for smallBuffI < len(smallBuff) {
        innerBuff := make([]byte, 1)
        _, err := a.Connection.Read(innerBuff)
        if err != nil {
            log.Println("ERROR:", err)
            return
        }
        smallBuff[smallBuffI] = innerBuff[0]
        smallBuffI += 1
        str := string(smallBuff[:smallBuffI])
        if len(str) > 0 && strings.HasSuffix(str, "\n") {
            // whoa we found a message length! read it
            var msgLen int64
            msgLen, err = strconv.ParseInt(str[:len(str)-1], 10, 64)
            if err != nil {
                log.Printf("ERROR: could not parse '%s': %s\n", str, err.Error())
                return
            }
            msgBuff := make([]byte, msgLen)
            n, err := a.Connection.Read(msgBuff)
            if err != nil {
                log.Println("ERROR:", err)
                return
            }
            cmd := cmd.ParseCommand(a, msgBuff[:n])
            if err != nil {
                log.Println("ERROR:", err)
                return
            }
            dispatch.Enqueue(cmd)
            smallBuff = make([]byte, 200)
            smallBuffI = 0
        }
    }
    log.Printf("Closing connection to app, garbage was read")
}

// perform all operations required to close this app
// this really should be somewhere else in the code, but i can't figure out where
// since most places would lead to circular references
func closeApp(a *app.App) {
    a.Connection.Close()
    sc, _ := cmd.NewStatusChange(a, app.STATUS_DOWN, nil)
    if a.Manifest != nil {
        registry.Remove(a)
        dispatch.Enqueue(sc)
        log.Printf("Closing application %s", a.Manifest.InstanceId)
    } else {
        log.Printf("Closing application")
    }
}
<commit_msg>documented & modularized server<commit_after>// Package srv implements the main Mycroft-core network listener.
package srv

import (
    "net"
    "log"
    "fmt"
    "strings"
    "strconv"
    "errors"
    "crypto/tls"
    "crypto/x509"
    "io/ioutil"
    "github.com/robmcl4/Mycroft-Core-Go/mycroft/app"
    "github.com/robmcl4/Mycroft-Core-Go/mycroft/cmd"
    "github.com/robmcl4/Mycroft-Core-Go/mycroft/dispatch"
    "github.com/robmcl4/Mycroft-Core-Go/mycroft/registry"
)

// Starts listening for client connections.
// When a new application connects, launches listeners in a goroutine.
// Returns an error when error occurs.
func StartListen(port int, useTls bool, crtPath string, keyPath string, sname string) (error) {
    // Create a listening address
    addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", port))
    if err != nil {
        return err
    }

    // start a new server and listen on the address
    var l net.Listener
    l, err = net.ListenTCP("tcp", addr)
    if err != nil {
        return err
    }

    // wrap with TLS if required
    if useTls {
        cert, err := tls.LoadX509KeyPair(crtPath, keyPath)
        if err != nil {
            return err
        }
        conf := tls.Config{}

        certs := make([]tls.Certificate, 1)
        certs[0] = cert
        conf.Certificates = certs

        cp := x509.NewCertPool()
        caCert, err := ioutil.ReadFile(crtPath)
        if err != nil {
            return err
        }
        if !cp.AppendCertsFromPEM(caCert) {
            return errors.New("Could not append PEM cert")
        }
        conf.RootCAs = cp

        conf.ServerName = sname

        conf.ClientAuth = tls.RequireAndVerifyClientCert

        conf.ClientCAs = cp

        l = tls.NewListener(l, &conf)
    }

    // at the end of this function close the server connection
    defer l.Close()

    log.Println("Starting listen loop")
    for {
        a, err := acceptApp(l)
        if err != nil {
            return err
        } else {
            log.Println("Got connection")
            go ListenForCommands(a)
        }
    }
    return nil
}

// Listens for and accepts a new application connection
// Returns a reference to the App which was accepted
func acceptApp(lnr net.Listener) (*app.App, error) {
    conn, err := lnr.Accept()
    if err != nil {
        return nil, err
    }
    ret := app.NewApp()
    ret.Connection = conn
    return ret, nil
}

// Starts listening for commands through the given app's connection.
// Since this is a blocking function, it should likely be called in
// a goroutine.
// At the end of the function, closes the application's network resources.
func ListenForCommands(a *app.App) {
    defer closeApp(a)

    // loop forever consuming messages
    for {
        // get the next command
        cmd, err := getCommand(a)
        if err != nil {
            log.Println("ERROR:", err)
            return
        }

        // enqueue the command
        dispatch.Enqueue(cmd)
    }
}

// Gets the next command from the application.
// Returns the command and an error, if one occured.
func getCommand(a *app.App) (*cmd.Command, error) {
    // get the message length
    msgLen, err := getMsgLen(a)
    if err != nil {
        return nil, err
    }

    // get the message body
    msgBuff := make([]byte, msgLen)
    totalRead := int64(0)
    // loop until we've read enough bytes
    for totalRead < msgLen {
        n, err := a.Connection.Read(msgBuff[totalRead:])
        if err != nil {
            return nil, err
        }
        totalRead += int64(n)
    }

    // we have the body, parse the command
    cmd := cmd.ParseCommand(a, msgBuff)
    if err != nil {
        return nil, err
    }
    return cmd, nil
}

// Gets the message length of the next message to be received by this application.
// Returns the message length and an error, if any occured.
func getMsgLen(a *app.App) (int64, error) {
    // create a small buffer to store the bytes read
    smallBuff := make([]byte, 200)
    smallBuffI := 0
    // loop while the buffer is not full
    for smallBuffI < len(smallBuff) {
        // read one byte
        innerBuff := make([]byte, 1)
        _, err := a.Connection.Read(innerBuff)
        if err != nil {
            return 0, err
        }
        // store that byte
        smallBuff[smallBuffI] = innerBuff[0]
        smallBuffI += 1
        // convert to string, see if it ends in newline
        str := string(smallBuff[:smallBuffI])
        if len(str) > 0 && strings.HasSuffix(str, "\n") {
            // this may be a valid message length
            var msgLen int64
            // parses using base 10, 64 bits
            msgLen, err = strconv.ParseInt(str[:len(str)-1], 10, 64)
            if err != nil {
                return 0, err
            }
            // it parsed, return
            return msgLen, nil
        }
    }
    return 0, errors.New("Message length exceeded 200 byte buffer.")
}

// Performs all operations required to close this app.
// Closes the network resource, queues a new STATUS_DOWN,
// removes from the registry, and logs the close.
func closeApp(a *app.App) {
    // this really should be somewhere else in the code, but i can't figure out where
    // since most places would lead to circular references
    a.Connection.Close()
    sc, _ := cmd.NewStatusChange(a, app.STATUS_DOWN, nil)
    if a.Manifest != nil {
        registry.Remove(a)
        dispatch.Enqueue(sc)
        log.Printf("Closing application %s", a.Manifest.InstanceId)
    } else {
        log.Printf("Closing application")
    }
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/kyma-project/test-infra/development/pkg/sets"
	"github.com/kyma-project/test-infra/development/pkg/tags"
	"io"
	"io/fs"
	errutil "k8s.io/apimachinery/pkg/util/errors"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strconv"
	"strings"
)

type options struct {
	Config
	configPath string
	context    string
	dockerfile string
	envFile    string
	name       string
	variant    string
	logDir     string
	silent     bool
	isCI       bool
	tags       sets.Strings
	platforms  sets.Strings
}

const (
	PlatformLinuxAmd64 = "linux/amd64"
	PlatformLinuxArm64 = "linux/arm64"
)

// parseVariable returns a build-arg.
// Keys are set to upper-case.
func parseVariable(key, value string) string {
	k := strings.TrimSpace(key)
	return k + "=" + strings.TrimSpace(value)
}

// runInBuildKit prepares command execution and handles gathering logs from BuildKit-enabled run
// This function is used only in customized environment
func runInBuildKit(o options, name string, destinations, platforms []string, buildArgs map[string]string) error {
	dockerfile := filepath.Base(o.dockerfile)
	dockerfileDir := filepath.Dir(o.dockerfile)
	args := []string{
		"build", "--frontend=dockerfile.v0",
		"--local", "context=" + o.context,
		"--local", "dockerfile=" + filepath.Join(o.context, dockerfileDir),
		"--opt", "filename=" + dockerfile,
	}

	// output definition, multiple images support
	args = append(args, "--output", "type=image,\"name="+strings.Join(destinations, ",")+"\",push=true")

	// build-args
	for k, v := range buildArgs {
		args = append(args, "--opt", "build-arg:"+parseVariable(k, v))
	}

	if len(platforms) > 0 {
		args = append(args, "--opt", "platform="+strings.Join(platforms, ","))
	}

	//if o.Config.Cache.Enabled {
	//	// NYI
	//}

	cmd := exec.Command("buildctl-daemonless.sh", args...)

	var outw []io.Writer
	var errw []io.Writer

	if !o.silent {
		outw = append(outw, os.Stdout)
		errw = append(errw, os.Stderr)
	}

	f, err := os.Create(filepath.Join(o.logDir, strings.TrimSpace("build_"+strings.TrimSpace(name)+".log")))
	if err != nil {
		return fmt.Errorf("could not create log file: %w", err)
	}

	outw = append(outw, f)
	errw = append(errw, f)

	cmd.Stdout = io.MultiWriter(outw...)
	cmd.Stderr = io.MultiWriter(errw...)

	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

// runInKaniko prepares command execution and handles gathering logs to file
func runInKaniko(o options, name string, destinations, platforms []string, buildArgs map[string]string) error {
	args := []string{
		"--context=" + o.context,
		"--dockerfile=" + o.dockerfile,
	}
	for _, dst := range destinations {
		args = append(args, "--destination="+dst)
	}

	for k, v := range buildArgs {
		args = append(args, "--build-arg="+parseVariable(k, v))
	}

	if len(platforms) > 0 {
		fmt.Println("'--platform' parameter not supported in kaniko-mode. Use buildkit-enabled image")
	}

	if o.Config.Cache.Enabled {
		args = append(args, "--cache="+strconv.FormatBool(o.Cache.Enabled),
			"--cache-copy-layers="+strconv.FormatBool(o.Cache.CacheCopyLayers),
			"--cache-run-layers="+strconv.FormatBool(o.Cache.CacheRunLayers),
			"--cache-repo="+o.Cache.CacheRepo)
	}

	if o.Config.LogFormat != "" {
		args = append(args, "--log-format="+o.Config.LogFormat)
	}

	if o.Config.Reproducible {
		args = append(args, "--reproducible=true")
	}

	cmd := exec.Command("/kaniko/executor", args...)

	var outw []io.Writer
	var errw []io.Writer

	if !o.silent {
		outw = append(outw, os.Stdout)
		errw = append(errw, os.Stderr)
	}

	f, err := os.Create(filepath.Join(o.logDir, strings.TrimSpace("build_"+strings.TrimSpace(name)+".log")))
	if err != nil {
		return fmt.Errorf("could not create log file: %w", err)
	}

	outw = append(outw, f)
	errw = append(errw, f)

	cmd.Stdout = io.MultiWriter(outw...)
	cmd.Stderr = io.MultiWriter(errw...)

	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

func runBuildJob(o options, vs Variants) error {
	runFunc := runInKaniko
	if os.Getenv("USE_BUILDKIT") == "true" {
		runFunc = runInBuildKit
	}
	var sha, pr string
	var err error
	repo := o.Config.Registry
	if o.isCI {
		presubmit := os.Getenv("JOB_TYPE") == "presubmit"
		if presubmit {
			if len(o.DevRegistry) > 0 {
				repo = o.DevRegistry
			}
			if n := os.Getenv("PULL_NUMBER"); n != "" {
				pr = n
			}
		}

		if c := os.Getenv("PULL_BASE_SHA"); c != "" {
			sha = c
		}
	}

	// if sha is still not set, fail the pipeline
	if sha == "" {
		return fmt.Errorf("'sha' could not be determined")
	}

	parsedTags, err := getTags(pr, sha, append(o.tags, o.TagTemplate))
	if err != nil {
		return err
	}
	envMap, err := loadEnv(os.DirFS("/"), o.envFile)
	if err != nil {
		return fmt.Errorf("load env: %w", err)
	}
	if len(vs) == 0 {
		// variants.yaml file not present or either empty. Run single build.
		destinations := gatherDestinations(repo, o.name, parsedTags)
		fmt.Println("Starting build for image: ", strings.Join(destinations, ", "))
		err = runFunc(o, "build", destinations, o.platforms, envMap)
		if err != nil {
			return fmt.Errorf("build encountered error: %w", err)
		}
		fmt.Println("Successfully built image:", strings.Join(destinations, ", "))
	}

	var errs []error
	for variant, env := range vs {
		var variantTags []string
		for _, tag := range parsedTags {
			variantTags = append(variantTags, tag+"-"+variant)
		}
		destinations := gatherDestinations(repo, o.name, variantTags)
		fmt.Println("Starting build for image: ", strings.Join(destinations, ", "))
		// (@Ressetkk): When variants provided, build doesn't use env files.
		// Similar logic should be provided once variants are fixed.
		if err := runFunc(o, variant, destinations, o.platforms, env); err != nil {
			errs = append(errs, fmt.Errorf("job %s ended with error: %w", variant, err))
			fmt.Printf("Job '%s' ended with error: %s.\n", variant, err)
		} else {
			fmt.Println("Successfully built image:", strings.Join(destinations, ", "))
			fmt.Printf("Job '%s' finished successfully.\n", variant)
		}
	}
	return errutil.NewAggregate(errs)
}

func getTags(pr, sha string, templates []string) ([]string, error) {
	// (Ressetkk): PR tag should not be hardcoded, in the future we have to find a way to parametrize it
	if pr != "" {
		// assume we are using PR number, build tag as 'PR-XXXX'
		return []string{"PR-" + pr}, nil
	}
	// build a tag from commit SHA
	tagger, err := tags.NewTagger(templates, tags.CommitSHA(sha))
	if err != nil {
		return nil, fmt.Errorf("get tagger: %w", err)
	}
	p, err := tagger.ParseTags()
	if err != nil {
		return nil, fmt.Errorf("build tag: %w", err)
	}
	return p, nil
}

func gatherDestinations(repo []string, name string, tags []string) []string {
	var dst []string
	for _, t := range tags {
		for _, r := range repo {
			image := path.Join(r, name)
			dst = append(dst, image+":"+strings.ReplaceAll(t, " ", "-"))
		}
	}
	return dst
}

// validateOptions handles options validation. All checks should be provided here
func validateOptions(o options) error {
	var errs []error
	if o.context == "" {
		errs = append(errs, fmt.Errorf("flag '--context' is missing"))
	}
	if o.name == "" {
		errs = append(errs, fmt.Errorf("flag '--name' is missing"))
	}
	if o.dockerfile == "" {
		errs = append(errs, fmt.Errorf("flag '--dockerfile' is missing"))
	}
	return errutil.NewAggregate(errs)
}

// loadEnv loads environment variables into application runtime from key=value list
func loadEnv(vfs fs.FS, envFile string) (map[string]string, error) {
	f, err := vfs.Open(envFile)
	if err != nil {
		return nil, fmt.Errorf("open env file: %w", err)
	}
	s := bufio.NewScanner(f)
	vars := make(map[string]string)
	for s.Scan() {
		kv := s.Text()
		sp := strings.SplitN(kv, "=", 2)
		key, val := sp[0], sp[1]
		if len(sp) > 2 {
			return nil, fmt.Errorf("env var split incorrectly: 2 != %v", len(sp))
		}
		if _, ok := os.LookupEnv(key); ok {
			// do not override env variable if it's already present in the runtime
			// do not include in vars map since dev should not have access to it anyway
			continue
		}
		err := os.Setenv(key, val)
		if err != nil {
			return nil, fmt.Errorf("setenv: %w", err)
		}
		// add value to the vars that will be injected as build args
		vars[key] = val
	}
	return vars, nil
}

func (o *options) gatherOptions(fs *flag.FlagSet) *flag.FlagSet {
	fs.BoolVar(&o.silent, "silent", false, "Do not push build logs to stdout")
	fs.StringVar(&o.configPath, "config", "/config/image-builder-config.yaml", "Path to application config file")
	fs.StringVar(&o.context, "context", ".", "Path to build directory context")
	fs.StringVar(&o.envFile, "env-file", "", "Path to file with environment variables to be loaded in build")
	fs.StringVar(&o.name, "name", "", "Name of the image to be built")
	fs.StringVar(&o.dockerfile, "dockerfile", "Dockerfile", "Path to Dockerfile file relative to context")
	fs.StringVar(&o.variant, "variant", "", "If variants.yaml file is present, define which variant should be built. If variants.yaml is not present, this flag will be ignored")
	fs.StringVar(&o.logDir, "log-dir", "/logs/artifacts", "Path to logs directory where GCB logs will be stored")
	fs.Var(&o.tags, "tag", "Additional tag that the image will be tagged")
	fs.Var(&o.platforms, "platform", "Only supported with BuildKit. Platform of the image that is built")
	return fs
}

func main() {
	fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	o := options{isCI: os.Getenv("CI") == "true"}
	o.gatherOptions(fs)
	if err := fs.Parse(os.Args[1:]); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	if o.configPath == "" {
		fmt.Println("'--config' flag is missing or has empty value, please provide the path to valid 'config.yaml' file")
		os.Exit(1)
	}
	c, err := os.ReadFile(o.configPath)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if err := o.ParseConfig(c); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// validate if options provided by flags and config file are fine
	if err := validateOptions(o); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	context, err := filepath.Abs(o.context)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	variantsFile := filepath.Join(context, filepath.Dir(o.dockerfile), "variants.yaml")
	variant, err := GetVariants(o.variant, variantsFile, os.ReadFile)
	if err != nil {
		if !os.IsNotExist(err) {
			fmt.Println(err)
			os.Exit(1)
		}
	}
	err = runBuildJob(o, variant)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	fmt.Println("Job's done.")
}
<commit_msg>Add initial cache support to buildkit image-builder (#6164)<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/kyma-project/test-infra/development/pkg/sets"
	"github.com/kyma-project/test-infra/development/pkg/tags"
	"io"
	"io/fs"
	errutil "k8s.io/apimachinery/pkg/util/errors"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strconv"
	"strings"
)

type options struct {
	Config
	configPath string
	context    string
	dockerfile string
	envFile    string
	name       string
	variant    string
	logDir     string
	silent     bool
	isCI       bool
	tags       sets.Strings
	platforms  sets.Strings
}

const (
	PlatformLinuxAmd64 = "linux/amd64"
	PlatformLinuxArm64 = "linux/arm64"
)

// parseVariable returns a build-arg.
// Keys are set to upper-case.
func parseVariable(key, value string) string {
	k := strings.TrimSpace(key)
	return k + "=" + strings.TrimSpace(value)
}

// runInBuildKit prepares command execution and handles gathering logs from BuildKit-enabled run
// This function is used only in customized environment
func runInBuildKit(o options, name string, destinations, platforms []string, buildArgs map[string]string) error {
	dockerfile := filepath.Base(o.dockerfile)
	dockerfileDir := filepath.Dir(o.dockerfile)
	args := []string{
		"build", "--frontend=dockerfile.v0",
		"--local", "context=" + o.context,
		"--local", "dockerfile=" + filepath.Join(o.context, dockerfileDir),
		"--opt", "filename=" + dockerfile,
	}

	// output definition, multiple images support
	args = append(args, "--output", "type=image,\"name="+strings.Join(destinations, ",")+"\",push=true")

	// build-args
	for k, v := range buildArgs {
		args = append(args, "--opt", "build-arg:"+parseVariable(k, v))
	}

	if len(platforms) > 0 {
		args = append(args, "--opt", "platform="+strings.Join(platforms, ","))
	}

	if o.Cache.Enabled {
		// TODO (@Ressetkk): Implement multiple caches, see https://github.com/moby/buildkit#export-cache
		args = append(args,
			"--export-cache", "type=registry,ref="+o.Cache.CacheRepo,
			"--import-cache", "type=registry,"+o.Cache.CacheRepo)
	}

	cmd := exec.Command("buildctl-daemonless.sh", args...)

	var outw []io.Writer
	var errw []io.Writer

	if !o.silent {
		outw = append(outw, os.Stdout)
		errw = append(errw, os.Stderr)
	}

	f, err := os.Create(filepath.Join(o.logDir, strings.TrimSpace("build_"+strings.TrimSpace(name)+".log")))
	if err != nil {
		return fmt.Errorf("could not create log file: %w", err)
	}

	outw = append(outw, f)
	errw = append(errw, f)

	cmd.Stdout = io.MultiWriter(outw...)
	cmd.Stderr = io.MultiWriter(errw...)

	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

// runInKaniko prepares command execution and handles gathering logs to file
func runInKaniko(o options, name string, destinations, platforms []string, buildArgs map[string]string) error {
	args := []string{
		"--context=" + o.context,
		"--dockerfile=" + o.dockerfile,
	}
	for _, dst := range destinations {
		args = append(args, "--destination="+dst)
	}

	for k, v := range buildArgs {
		args = append(args, "--build-arg="+parseVariable(k, v))
	}

	if len(platforms) > 0 {
		fmt.Println("'--platform' parameter not supported in kaniko-mode. Use buildkit-enabled image")
	}

	if o.Config.Cache.Enabled {
		args = append(args, "--cache="+strconv.FormatBool(o.Cache.Enabled),
			"--cache-copy-layers="+strconv.FormatBool(o.Cache.CacheCopyLayers),
			"--cache-run-layers="+strconv.FormatBool(o.Cache.CacheRunLayers),
			"--cache-repo="+o.Cache.CacheRepo)
	}

	if o.Config.LogFormat != "" {
		args = append(args, "--log-format="+o.Config.LogFormat)
	}

	if o.Config.Reproducible {
		args = append(args, "--reproducible=true")
	}

	cmd := exec.Command("/kaniko/executor", args...)

	var outw []io.Writer
	var errw []io.Writer

	if !o.silent {
		outw = append(outw, os.Stdout)
		errw = append(errw, os.Stderr)
	}

	f, err := os.Create(filepath.Join(o.logDir, strings.TrimSpace("build_"+strings.TrimSpace(name)+".log")))
	if err != nil {
		return fmt.Errorf("could not create log file: %w", err)
	}

	outw = append(outw, f)
	errw = append(errw, f)

	cmd.Stdout = io.MultiWriter(outw...)
	cmd.Stderr = io.MultiWriter(errw...)

	if err := cmd.Run(); err != nil {
		return err
	}
	return nil
}

func runBuildJob(o options, vs Variants) error {
	runFunc := runInKaniko
	if os.Getenv("USE_BUILDKIT") == "true" {
		runFunc = runInBuildKit
	}
	var sha, pr string
	var err error
	repo := o.Config.Registry
	if o.isCI {
		presubmit := os.Getenv("JOB_TYPE") == "presubmit"
		if presubmit {
			if len(o.DevRegistry) > 0 {
				repo = o.DevRegistry
			}
			if n := os.Getenv("PULL_NUMBER"); n != "" {
				pr = n
			}
		}

		if c := os.Getenv("PULL_BASE_SHA"); c != "" {
			sha = c
		}
	}

	// if sha is still not set, fail the pipeline
	if sha == "" {
		return fmt.Errorf("'sha' could not be determined")
	}

	parsedTags, err := getTags(pr, sha, append(o.tags, o.TagTemplate))
	if err != nil {
		return err
	}
	envMap, err := loadEnv(os.DirFS("/"), o.envFile)
	if err != nil {
		return fmt.Errorf("load env: %w", err)
	}
	if len(vs) == 0 {
		// variants.yaml file not present or either empty. Run single build.
		destinations := gatherDestinations(repo, o.name, parsedTags)
		fmt.Println("Starting build for image: ", strings.Join(destinations, ", "))
		err = runFunc(o, "build", destinations, o.platforms, envMap)
		if err != nil {
			return fmt.Errorf("build encountered error: %w", err)
		}
		fmt.Println("Successfully built image:", strings.Join(destinations, ", "))
	}

	var errs []error
	for variant, env := range vs {
		var variantTags []string
		for _, tag := range parsedTags {
			variantTags = append(variantTags, tag+"-"+variant)
		}
		destinations := gatherDestinations(repo, o.name, variantTags)
		fmt.Println("Starting build for image: ", strings.Join(destinations, ", "))
		// (@Ressetkk): When variants provided, build doesn't use env files.
		// Similar logic should be provided once variants are fixed.
		if err := runFunc(o, variant, destinations, o.platforms, env); err != nil {
			errs = append(errs, fmt.Errorf("job %s ended with error: %w", variant, err))
			fmt.Printf("Job '%s' ended with error: %s.\n", variant, err)
		} else {
			fmt.Println("Successfully built image:", strings.Join(destinations, ", "))
			fmt.Printf("Job '%s' finished successfully.\n", variant)
		}
	}
	return errutil.NewAggregate(errs)
}

func getTags(pr, sha string, templates []string) ([]string, error) {
	// (Ressetkk): PR tag should not be hardcoded, in the future we have to find a way to parametrize it
	if pr != "" {
		// assume we are using PR number, build tag as 'PR-XXXX'
		return []string{"PR-" + pr}, nil
	}
	// build a tag from commit SHA
	tagger, err := tags.NewTagger(templates, tags.CommitSHA(sha))
	if err != nil {
		return nil, fmt.Errorf("get tagger: %w", err)
	}
	p, err := tagger.ParseTags()
	if err != nil {
		return nil, fmt.Errorf("build tag: %w", err)
	}
	return p, nil
}

func gatherDestinations(repo []string, name string, tags []string) []string {
	var dst []string
	for _, t := range tags {
		for _, r := range repo {
			image := path.Join(r, name)
			dst = append(dst, image+":"+strings.ReplaceAll(t, " ", "-"))
		}
	}
	return dst
}

// validateOptions handles options validation. All checks should be provided here
func validateOptions(o options) error {
	var errs []error
	if o.context == "" {
		errs = append(errs, fmt.Errorf("flag '--context' is missing"))
	}
	if o.name == "" {
		errs = append(errs, fmt.Errorf("flag '--name' is missing"))
	}
	if o.dockerfile == "" {
		errs = append(errs, fmt.Errorf("flag '--dockerfile' is missing"))
	}
	return errutil.NewAggregate(errs)
}

// loadEnv loads environment variables into application runtime from key=value list
func loadEnv(vfs fs.FS, envFile string) (map[string]string, error) {
	f, err := vfs.Open(envFile)
	if err != nil {
		return nil, fmt.Errorf("open env file: %w", err)
	}
	s := bufio.NewScanner(f)
	vars := make(map[string]string)
	for s.Scan() {
		kv := s.Text()
		sp := strings.SplitN(kv, "=", 2)
		key, val := sp[0], sp[1]
		if len(sp) > 2 {
			return nil, fmt.Errorf("env var split incorrectly: 2 != %v", len(sp))
		}
		if _, ok := os.LookupEnv(key); ok {
			// do not override env variable if it's already present in the runtime
			// do not include in vars map since dev should not have access to it anyway
			continue
		}
		err := os.Setenv(key, val)
		if err != nil {
			return nil, fmt.Errorf("setenv: %w", err)
		}
		// add value to the vars that will be injected as build args
		vars[key] = val
	}
	return vars, nil
}

func (o *options) gatherOptions(fs *flag.FlagSet) *flag.FlagSet {
	fs.BoolVar(&o.silent, "silent", false, "Do not push build logs to stdout")
	fs.StringVar(&o.configPath, "config", "/config/image-builder-config.yaml", "Path to application config file")
	fs.StringVar(&o.context, "context", ".", "Path to build directory context")
	fs.StringVar(&o.envFile, "env-file", "", "Path to file with environment variables to be loaded in build")
	fs.StringVar(&o.name, "name", "", "Name of the image to be built")
	fs.StringVar(&o.dockerfile, "dockerfile", "Dockerfile", "Path to Dockerfile file relative to context")
	fs.StringVar(&o.variant, "variant", "", "If variants.yaml file is present, define which variant should be built. If variants.yaml is not present, this flag will be ignored")
	fs.StringVar(&o.logDir, "log-dir", "/logs/artifacts", "Path to logs directory where GCB logs will be stored")
	fs.Var(&o.tags, "tag", "Additional tag that the image will be tagged")
	fs.Var(&o.platforms, "platform", "Only supported with BuildKit. Platform of the image that is built")
	return fs
}

func main() {
	fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	o := options{isCI: os.Getenv("CI") == "true"}
	o.gatherOptions(fs)
	if err := fs.Parse(os.Args[1:]); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	if o.configPath == "" {
		fmt.Println("'--config' flag is missing or has empty value, please provide the path to valid 'config.yaml' file")
		os.Exit(1)
	}
	c, err := os.ReadFile(o.configPath)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if err := o.ParseConfig(c); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// validate if options provided by flags and config file are fine
	if err := validateOptions(o); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	context, err := filepath.Abs(o.context)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	variantsFile := filepath.Join(context, filepath.Dir(o.dockerfile), "variants.yaml")
	variant, err := GetVariants(o.variant, variantsFile, os.ReadFile)
	if err != nil {
		if !os.IsNotExist(err) {
			fmt.Println(err)
			os.Exit(1)
		}
	}
	err = runBuildJob(o, variant)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	fmt.Println("Job's done.")
}
<|endoftext|>


<commit_before>package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    "log"
    "io"
    "io/ioutil"
    re "regexp"
    "strconv"
    "os"
    "os/exec"
    "sort"
    s "strings"
    "sync"
    "jfb/svgxml"
    _ "github.com/lib/pq"
)

type Config struct {
    Colours map[string]string                       `json:"colours"`
    Maps    map[string]map[string]map[string]string `json:"maps"`
}

type DbConfig struct {
    Server      map[string]string       `json:"db_server"`
    Creds       map[string]string       `json:"db_creds"`
    Schema      map[string]string       `json:"db_schema"`
}

// set up integer array sorting
type IntArray []int
func (list IntArray) Len() int          { return len(list) }
func (list IntArray) Swap(a, b int)     { list[a], list[b] = list[b], list[a] }
func (list IntArray) Less(a, b int) bool { return list[a] < list[b] }

// suck in count data
func db_data() (map[string]int, map[string]int) {
    var dbconfig        DbConfig

    jsoncfg, err := ioutil.ReadFile("dbconfig.json")
    if err != nil {
        panic(err)
    }

    err = json.Unmarshal(jsoncfg, &dbconfig)
    if err != nil {
        fmt.Fprintf(os.Stderr, "DB config unmarshal: ")
        panic(err)
    }

    state_counts :=     make(map[string]int)
    county_counts :=    make(map[string]int)

    dbh, err := sql.Open(dbconfig.Server["dbtype"],
        dbconfig.Server["dbtype"] + "://" + dbconfig.Creds["username"] + ":" +
        dbconfig.Creds["password"] + "@" + dbconfig.Server["dbhost"] + "/" +
        dbconfig.Server["dbname"] + dbconfig.Server["dbopts"])
    if err != nil {
        log.Fatal(err)
    }

    query :=
            "select " +
                dbconfig.Schema["state_column"] + ", " +
                dbconfig.Schema["county_column"] + ", " +
                dbconfig.Schema["tally_column"] +
            "from " +
                dbconfig.Schema["tables"] + " " +
            dbconfig.Schema["where"] + " " +
            dbconfig.Schema["group_by"]
    rows, err := dbh.Query(query)
    // fmt.Println(query)
    if err != nil {
        log.Fatal(err)
    }

    defer rows.Close()
    for rows.Next() {
        var state, county string
        var count int
        if err := rows.Scan(&state, &county, &count); err != nil {
            log.Fatal(err)
        }
        state_counts[state] += count
        state_county_key := s.Replace(state + " " + county, " ", "_", -1)
        county_counts[state_county_key] = count
    }
    if err := rows.Err(); err != nil {
        log.Fatal(err)
    }

    return state_counts, county_counts

}

func colour_svgdata(mapsvg []byte, data map[string]int, re_fill *re.Regexp, colours map[string]string, mincount []int) (string) {
    mapsvg_obj := svgxml.XML2SVG(mapsvg)
    if mapsvg_obj == nil {
        return ":SVGERR"
    }

    for id, count := range data {
        for _, mc := range mincount {
            if count >= mc {
                element := svgxml.FindPathById(mapsvg_obj, id)
                if element != nil {
                    element.Style = string(re_fill.ReplaceAll([]byte(element.Style), []byte("${1}" + colours[strconv.Itoa(mc)])))
                } else {
                    fmt.Fprintf(os.Stderr, "'%s' not found\n", id)
                }
            }
        }
    }
    return string(svgxml.SVG2XML(mapsvg_obj, true))
}

func main() {

    var config  Config
    var wg      sync.WaitGroup

    jsoncfg, err := ioutil.ReadFile("config.json")
    if err != nil {
        panic(err)
    }

    err = json.Unmarshal(jsoncfg, &config)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Config unmarshal: ")
        panic(err)
    }

    // make sorted list of keys (minimum counts) for later comparisons
    mincount := make([]int, len(config.Colours))
    i := 0
    for k, _ := range config.Colours {
        k_i, _ := strconv.ParseInt(k, 0, 64)
        mincount[i] = int(k_i)
        i++
    }

    sort.Sort(IntArray(mincount))

    re_fill, err := re.Compile(`(fill:#)......`)
    if err != nil {
        panic(err)
    }

    re_svgext, err := re.Compile(`\.svg$`)
    if err != nil {
        panic(err)
    }

    state_data, county_data := db_data()

    for maptype, mapset := range config.Maps {
        var data map[string]int

        if maptype == "states" {
            data = state_data
        } else {
            data = county_data
        }

        for infile, attrs := range mapset {
            wg.Add(1)
            go func(srcfile, dstfile, outsize string) {

                defer wg.Done()
                mapsvg, err := ioutil.ReadFile(srcfile)
                if err != nil {
                    fmt.Fprintf(os.Stderr, "can't read '" + srcfile + "': " + err.Error())
                    return
                }
                svg_coloured := colour_svgdata(mapsvg, data, re_fill, config.Colours, mincount)
                if svg_coloured == ":SVGERR" {
                    fmt.Fprintf(os.Stderr, "can't create SVG object from " + srcfile)
                    return
                }
                ret := re_svgext.Find([]byte(dstfile))
                if ret == nil {
                    // going to call ImageMagick's 'convert' because I can't find
                    // a damn SVG package that can write to a non-SVG image and I
                    // don't have the chops to write one.
                    cmd := exec.Command("convert", "svg:-", "-scale", outsize, dstfile)
                    convert_stdin, err := cmd.StdinPipe()
                    if err != nil {
                        log.Fatal(err)
                    }
                    go func() {
                        defer convert_stdin.Close()
                        io.WriteString(convert_stdin, svg_coloured)
                    }()
                    _, err = cmd.CombinedOutput()
                    if err != nil {
                        log.Fatal(err)
                    }
                } else {
                    // just going back to an SVG file
                    err := ioutil.WriteFile(dstfile, []byte(svg_coloured), 0666)
                    if err != nil {
                        fmt.Fprintf(os.Stderr, "can't write to '" + dstfile + "': " + err.Error())
                        return
                    }
                }

            }(infile, attrs["outfile"], attrs["outsize"])
        }
    }

    wg.Wait()

    // fmt.Println(string(svgxml.SVG2XML(mapsvg_obj, true)))

}

// ex:ai:sw=4:ts=8:
<commit_msg>add'l modeline for vim; no-tabs-kludge modeline for vi<commit_after>package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    "log"
    "io"
    "io/ioutil"
    re "regexp"
    "strconv"
    "os"
    "os/exec"
    "sort"
    s "strings"
    "sync"
    "jfb/svgxml"
    _ "github.com/lib/pq"
)

type Config struct {
    Colours map[string]string                       `json:"colours"`
    Maps    map[string]map[string]map[string]string `json:"maps"`
}

type DbConfig struct {
    Server      map[string]string       `json:"db_server"`
    Creds       map[string]string       `json:"db_creds"`
    Schema      map[string]string       `json:"db_schema"`
}

// set up integer array sorting
type IntArray []int
func (list IntArray) Len() int          { return len(list) }
func (list IntArray) Swap(a, b int)     { list[a], list[b] = list[b], list[a] }
func (list IntArray) Less(a, b int) bool { return list[a] < list[b] }

// suck in count data
func db_data() (map[string]int, map[string]int) {
    var dbconfig        DbConfig

    jsoncfg, err := ioutil.ReadFile("dbconfig.json")
    if err != nil {
        panic(err)
    }

    err = json.Unmarshal(jsoncfg, &dbconfig)
    if err != nil {
        fmt.Fprintf(os.Stderr, "DB config unmarshal: ")
        panic(err)
    }

    state_counts :=     make(map[string]int)
    county_counts :=    make(map[string]int)

    dbh, err := sql.Open(dbconfig.Server["dbtype"],
        dbconfig.Server["dbtype"] + "://" + dbconfig.Creds["username"] + ":" +
        dbconfig.Creds["password"] + "@" + dbconfig.Server["dbhost"] + "/" +
        dbconfig.Server["dbname"] + dbconfig.Server["dbopts"])
    if err != nil {
        log.Fatal(err)
    }

    query :=
            "select " +
                dbconfig.Schema["state_column"] + ", " +
                dbconfig.Schema["county_column"] + ", " +
                dbconfig.Schema["tally_column"] +
            "from " +
                dbconfig.Schema["tables"] + " " +
            dbconfig.Schema["where"] + " " +
            dbconfig.Schema["group_by"]
    rows, err := dbh.Query(query)
    // fmt.Println(query)
    if err != nil {
        log.Fatal(err)
    }

    defer rows.Close()
    for rows.Next() {
        var state, county string
        var count int
        if err := rows.Scan(&state, &county, &count); err != nil {
            log.Fatal(err)
        }
        state_counts[state] += count
        state_county_key := s.Replace(state + " " + county, " ", "_", -1)
        county_counts[state_county_key] = count
    }
    if err := rows.Err(); err != nil {
        log.Fatal(err)
    }

    return state_counts, county_counts

}

func colour_svgdata(mapsvg []byte, data map[string]int, re_fill *re.Regexp, colours map[string]string, mincount []int) (string) {
    mapsvg_obj := svgxml.XML2SVG(mapsvg)
    if mapsvg_obj == nil {
        return ":SVGERR"
    }

    for id, count := range data {
        for _, mc := range mincount {
            if count >= mc {
                element := svgxml.FindPathById(mapsvg_obj, id)
                if element != nil {
                    element.Style = string(re_fill.ReplaceAll([]byte(element.Style), []byte("${1}" + colours[strconv.Itoa(mc)])))
                } else {
                    fmt.Fprintf(os.Stderr, "'%s' not found\n", id)
                }
            }
        }
    }
    return string(svgxml.SVG2XML(mapsvg_obj, true))
}

func main() {

    var config  Config
    var wg      sync.WaitGroup

    jsoncfg, err := ioutil.ReadFile("config.json")
    if err != nil {
        panic(err)
    }

    err = json.Unmarshal(jsoncfg, &config)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Config unmarshal: ")
        panic(err)
    }

    // make sorted list of keys (minimum counts) for later comparisons
    mincount := make([]int, len(config.Colours))
    i := 0
    for k, _ := range config.Colours {
        k_i, _ := strconv.ParseInt(k, 0, 64)
        mincount[i] = int(k_i)
        i++
    }

    sort.Sort(IntArray(mincount))

    re_fill, err := re.Compile(`(fill:#)......`)
    if err != nil {
        panic(err)
    }

    re_svgext, err := re.Compile(`\.svg$`)
    if err != nil {
        panic(err)
    }

    state_data, county_data := db_data()

    for maptype, mapset := range config.Maps {
        var data map[string]int

        if maptype == "states" {
            data = state_data
        } else {
            data = county_data
        }

        for infile, attrs := range mapset {
            wg.Add(1)
            go func(srcfile, dstfile, outsize string) {

                defer wg.Done()
                mapsvg, err := ioutil.ReadFile(srcfile)
                if err != nil {
                    fmt.Fprintf(os.Stderr, "can't read '" + srcfile + "': " + err.Error())
                    return
                }
                svg_coloured := colour_svgdata(mapsvg, data, re_fill, config.Colours, mincount)
                if svg_coloured == ":SVGERR" {
                    fmt.Fprintf(os.Stderr, "can't create SVG object from " + srcfile)
                    return
                }
                ret := re_svgext.Find([]byte(dstfile))
                if ret == nil {
                    // going to call ImageMagick's 'convert' because I can't find
                    // a damn SVG package that can write to a non-SVG image and I
                    // don't have the chops to write one.
                    cmd := exec.Command("convert", "svg:-", "-scale", outsize, dstfile)
                    convert_stdin, err := cmd.StdinPipe()
                    if err != nil {
                        log.Fatal(err)
                    }
                    go func() {
                        defer convert_stdin.Close()
                        io.WriteString(convert_stdin, svg_coloured)
                    }()
                    _, err = cmd.CombinedOutput()
                    if err != nil {
                        log.Fatal(err)
                    }
                } else {
                    // just going back to an SVG file
                    err := ioutil.WriteFile(dstfile, []byte(svg_coloured), 0666)
                    if err != nil {
                        fmt.Fprintf(os.Stderr, "can't write to '" + dstfile + "': " + err.Error())
                        return
                    }
                }

            }(infile, attrs["outfile"], attrs["outsize"])
        }
    }

    wg.Wait()

    // fmt.Println(string(svgxml.SVG2XML(mapsvg_obj, true)))

}

// vim:ts=4:et:
// ex:ai:sw=4:ts=1000:
<|endoftext|>


<commit_before>package models

import (
	"archive/zip"
	"bytes"
	"encoding/xml"
	"errors"
	"io/ioutil"
	"os"
	"regexp"

	"github.com/DHowett/go-plist"
	"github.com/shogo82148/androidbinary"
)

var reInfoPlist = regexp.MustCompile(`/[^/]+/Info\.plist`)

// a BundleInfo is information of an application package(apk file, ipa file, etc.)
type BundleInfo struct {
	Version      string
	Identifier   string
	PlatformType BundlePlatformType
}

type androidManifest struct {
	XMLName     xml.Name `xml:"manifest"`
	VersionName string   `xml:"http://schemas.android.com/apk/res/android versionName,attr"`
}

type iosInfo struct {
	CFBundleVersion    string `plist:"CFBundleVersion"`
	CFBundleIdentifier string `plist:"CFBundleIdentifier"`
}

type BundleParseError struct {
	Offset int64
}

func (e *BundleParseError) Error() string {
	return "cannot parse application package file"
}

func NewBundleInfo(file *os.File, platformType BundlePlatformType) (*BundleInfo, error) {
	stat, err := file.Stat()
	if err != nil {
		return nil, err
	}

	reader, err := zip.NewReader(file, stat.Size())
	if err != nil {
		return nil, err
	}

	// search system files
	var xmlFile *zip.File   // apk system file
	var plistFile *zip.File // ipa system file
	for _, f := range reader.File {
		switch {
		case f.Name == "AndroidManifest.xml":
			xmlFile = f
		case reInfoPlist.MatchString(f):
			plistFile = f
		}
	}

	// parse an apk file
	if platformType == BundlePlatformTypeAndroid {
		bundleInfo, err := parseApkFile(xmlFile)
		return bundleInfo, err
	}

	// parse an ipa file
	if platformType == BundlePlatformTypeIOS {
		bundleInfo, err := parseIpaFile(plistFile)
		return bundleInfo, err
	}

	return nil, errors.New("unknown platform")
}

func parseApkFile(xmlFile *zip.File) (*BundleInfo, error) {
	if xmlFile == nil {
		return nil, errors.New("AndroidManifest.xml is not found")
	}

	manifest, err := parseAndroidManifest(xmlFile)
	if err != nil {
		return nil, err
	}

	bundleInfo := &BundleInfo{}
	bundleInfo.Version = manifest.VersionName
	bundleInfo.PlatformType = BundlePlatformTypeAndroid

	return bundleInfo, nil
}

func parseAndroidManifest(xmlFile *zip.File) (*androidManifest, error) {
	rc, err := xmlFile.Open()
	if err != nil {
		return nil, err
	}
	defer rc.Close()

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

	xmlContent, err := androidbinary.NewXMLFile(bytes.NewReader(buf))
	if err != nil {
		return nil, err
	}

	decoder := xml.NewDecoder(xmlContent.Reader())
	manifest := &androidManifest{}
	if err := decoder.Decode(manifest); err != nil {
		return nil, err
	}

	return manifest, nil
}

func parseIpaFile(plistFile *zip.File) (*BundleInfo, error) {
	if plistFile == nil {
		return nil, errors.New("info.plist is not found")
	}

	rc, err := plistFile.Open()
	if err != nil {
		return nil, err
	}
	defer rc.Close()

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

	info := &iosInfo{}
	_, err = plist.Unmarshal(buf, info)
	if err != nil {
		return nil, err
	}

	bundleInfo := &BundleInfo{}
	bundleInfo.Version = info.CFBundleVersion
	bundleInfo.Identifier = info.CFBundleIdentifier
	bundleInfo.PlatformType = BundlePlatformTypeIOS

	return bundleInfo, nil
}
<commit_msg>Payloadというディレクトリの中にはいります<commit_after>package models

import (
	"archive/zip"
	"bytes"
	"encoding/xml"
	"errors"
	"io/ioutil"
	"os"
	"regexp"

	"github.com/DHowett/go-plist"
	"github.com/shogo82148/androidbinary"
)

var reInfoPlist = regexp.MustCompile(`Payload/[^/]+/Info\.plist`)

// a BundleInfo is information of an application package(apk file, ipa file, etc.)
type BundleInfo struct {
	Version      string
	Identifier   string
	PlatformType BundlePlatformType
}

type androidManifest struct {
	XMLName     xml.Name `xml:"manifest"`
	VersionName string   `xml:"http://schemas.android.com/apk/res/android versionName,attr"`
}

type iosInfo struct {
	CFBundleVersion    string `plist:"CFBundleVersion"`
	CFBundleIdentifier string `plist:"CFBundleIdentifier"`
}

type BundleParseError struct {
	Offset int64
}

func (e *BundleParseError) Error() string {
	return "cannot parse application package file"
}

func NewBundleInfo(file *os.File, platformType BundlePlatformType) (*BundleInfo, error) {
	stat, err := file.Stat()
	if err != nil {
		return nil, err
	}

	reader, err := zip.NewReader(file, stat.Size())
	if err != nil {
		return nil, err
	}

	// search system files
	var xmlFile *zip.File   // apk system file
	var plistFile *zip.File // ipa system file
	for _, f := range reader.File {
		switch {
		case f.Name == "AndroidManifest.xml":
			xmlFile = f
		case reInfoPlist.MatchString(f):
			plistFile = f
		}
	}

	// parse an apk file
	if platformType == BundlePlatformTypeAndroid {
		bundleInfo, err := parseApkFile(xmlFile)
		return bundleInfo, err
	}

	// parse an ipa file
	if platformType == BundlePlatformTypeIOS {
		bundleInfo, err := parseIpaFile(plistFile)
		return bundleInfo, err
	}

	return nil, errors.New("unknown platform")
}

func parseApkFile(xmlFile *zip.File) (*BundleInfo, error) {
	if xmlFile == nil {
		return nil, errors.New("AndroidManifest.xml is not found")
	}

	manifest, err := parseAndroidManifest(xmlFile)
	if err != nil {
		return nil, err
	}

	bundleInfo := &BundleInfo{}
	bundleInfo.Version = manifest.VersionName
	bundleInfo.PlatformType = BundlePlatformTypeAndroid

	return bundleInfo, nil
}

func parseAndroidManifest(xmlFile *zip.File) (*androidManifest, error) {
	rc, err := xmlFile.Open()
	if err != nil {
		return nil, err
	}
	defer rc.Close()

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

	xmlContent, err := androidbinary.NewXMLFile(bytes.NewReader(buf))
	if err != nil {
		return nil, err
	}

	decoder := xml.NewDecoder(xmlContent.Reader())
	manifest := &androidManifest{}
	if err := decoder.Decode(manifest); err != nil {
		return nil, err
	}

	return manifest, nil
}

func parseIpaFile(plistFile *zip.File) (*BundleInfo, error) {
	if plistFile == nil {
		return nil, errors.New("info.plist is not found")
	}

	rc, err := plistFile.Open()
	if err != nil {
		return nil, err
	}
	defer rc.Close()

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

	info := &iosInfo{}
	_, err = plist.Unmarshal(buf, info)
	if err != nil {
		return nil, err
	}

	bundleInfo := &BundleInfo{}
	bundleInfo.Version = info.CFBundleVersion
	bundleInfo.Identifier = info.CFBundleIdentifier
	bundleInfo.PlatformType = BundlePlatformTypeIOS

	return bundleInfo, nil
}
<|endoftext|>


<commit_before>// Copyright 2017-2021 The Usacloud Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package database

import (
	"github.com/sacloud/usacloud/pkg/cmd/ccol"
	"github.com/sacloud/usacloud/pkg/cmd/cflag"
	"github.com/sacloud/usacloud/pkg/cmd/core"
	"github.com/sacloud/usacloud/pkg/output"
)

var listParametersCommand = &core.Command{
	Name:               "list-parameters",
	Aliases:            []string{"list-parameter"},
	Category:           "basic",
	Order:              500,
	ServiceFuncAltName: "ListParameter",
	NoProgress:         true,
	SelectorType:       core.SelectorTypeRequireMulti,

	ColumnDefs: []output.ColumnDef{
		ccol.Zone,
		ccol.ID,
		{
			Name: "Key",
		},
		{
			Name:     "CurrentValue",
			Template: "{{ .Value }}",
		},
		{
			Name:     "Type",
			Template: "{{ .Meta.Type }}",
		},
		{
			Name:     "About",
			Template: "{{ .Meta.Text | ellipsis 30 }}",
		},
		{
			Name:     "Example",
			Template: "{{ .Meta.Example }}",
		},
		{
			Name:     "Reboot",
			Template: "{{ .Meta.Reboot}}",
		},
	},

	ParameterInitializer: func() interface{} {
		return newListParametersParameter()
	},
}

type listParametersParameter struct {
	cflag.ZoneParameter   `cli:",squash" mapconv:",squash"`
	cflag.IDParameter     `cli:",squash" mapconv:",squash"`
	cflag.CommonParameter `cli:",squash" mapconv:"-"`
	cflag.OutputParameter `cli:",squash" mapconv:"-"`
}

func newListParametersParameter() *listParametersParameter {
	return &listParametersParameter{}
}

func init() {
	Resource.AddCommand(listParametersCommand)
}
<commit_msg>database: list-parameter: to_single_line<commit_after>// Copyright 2017-2021 The Usacloud Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package database

import (
	"github.com/sacloud/usacloud/pkg/cmd/ccol"
	"github.com/sacloud/usacloud/pkg/cmd/cflag"
	"github.com/sacloud/usacloud/pkg/cmd/core"
	"github.com/sacloud/usacloud/pkg/output"
)

var listParametersCommand = &core.Command{
	Name:               "list-parameters",
	Aliases:            []string{"list-parameter"},
	Category:           "basic",
	Order:              500,
	ServiceFuncAltName: "ListParameter",
	NoProgress:         true,
	SelectorType:       core.SelectorTypeRequireMulti,

	ColumnDefs: []output.ColumnDef{
		ccol.Zone,
		ccol.ID,
		{
			Name: "Key",
		},
		{
			Name:     "CurrentValue",
			Template: "{{ .Value }}",
		},
		{
			Name:     "Type",
			Template: "{{ .Meta.Type }}",
		},
		{
			Name:     "About",
			Template: "{{ .Meta.Text | to_single_line | ellipsis 30 }}",
		},
		{
			Name:     "Example",
			Template: "{{ .Meta.Example }}",
		},
		{
			Name:     "Reboot",
			Template: "{{ .Meta.Reboot}}",
		},
	},

	ParameterInitializer: func() interface{} {
		return newListParametersParameter()
	},
}

type listParametersParameter struct {
	cflag.ZoneParameter   `cli:",squash" mapconv:",squash"`
	cflag.IDParameter     `cli:",squash" mapconv:",squash"`
	cflag.CommonParameter `cli:",squash" mapconv:"-"`
	cflag.OutputParameter `cli:",squash" mapconv:"-"`
}

func newListParametersParameter() *listParametersParameter {
	return &listParametersParameter{}
}

func init() {
	Resource.AddCommand(listParametersCommand)
}
<|endoftext|>


<commit_before>package fundingoffer_test

import (
	"reflect"
	"testing"

	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/fundingoffer"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestNewFundingOfferFromRaw(t *testing.T) {
	t.Run("invalid arguments", func(t *testing.T) {
		payload := []interface{}{652606505}

		got, err := fundingoffer.FromRaw(payload)
		require.NotNil(t, err)
		require.Nil(t, got)
	})

	t.Run("valid arguments", func(t *testing.T) {
		payload := []interface{}{
			652606505,
			"fETH",
			1574000611000,
			1574000611000,
			0.29797676,
			0.29797676,
			"LIMIT",
			nil,
			nil,
			0,
			"ACTIVE",
			nil,
			nil,
			nil,
			0.0002,
			2,
			1,
			nil,
			nil,
			0,
			nil,
		}

		got, err := fundingoffer.FromRaw(payload)
		require.Nil(t, err)

		expected := &fundingoffer.Offer{
			ID:         652606505,
			Symbol:     "fETH",
			MTSCreated: 1574000611000,
			MTSUpdated: 1574000611000,
			Amount:     0.29797676,
			AmountOrig: 0.29797676,
			Type:       "LIMIT",
			Flags:      0,
			Status:     "ACTIVE",
			Rate:       0.0002,
			Period:     2,
			Notify:     true,
			Hidden:     false,
			Insure:     false,
			Renew:      false,
			RateReal:   0,
		}
		assert.Equal(t, expected, got)
	})
}

func TestFundingOfferSnapshotFromRaw(t *testing.T) {
	t.Run("invalid arguments", func(t *testing.T) {
		payload := []interface{}{}
		got, err := fundingoffer.SnapshotFromRaw(payload)
		require.NotNil(t, err)
		require.Nil(t, got)
	})

	t.Run("partially valid arguments", func(t *testing.T) {
		payload := []interface{}{
			[]interface{}{
				652606505,
				"fETH",
				1574000611000,
				1574000611000,
				0.29797676,
				0.29797676,
				"LIMIT",
				nil,
				nil,
				0,
				"ACTIVE",
				nil,
				nil,
				nil,
				0.0002,
				2,
				1,
				nil,
				nil,
				0,
				nil,
			},
			[]interface{}{652606506},
		}
		got, err := fundingoffer.SnapshotFromRaw(payload)
		require.NotNil(t, err)
		require.Nil(t, got)
	})

	t.Run("valid arguments", func(t *testing.T) {
		payload := []interface{}{
			[]interface{}{
				652606505,
				"fETH",
				1574000611000,
				1574000611000,
				0.29797676,
				0.29797676,
				"LIMIT",
				nil,
				nil,
				0,
				"ACTIVE",
				nil,
				nil,
				nil,
				0.0002,
				2,
				1,
				nil,
				nil,
				0,
				nil,
			},
			[]interface{}{
				652606506,
				"fETH",
				1574000611000,
				1574000611000,
				0.29797676,
				0.29797676,
				"LIMIT",
				nil,
				nil,
				0,
				"ACTIVE",
				nil,
				nil,
				nil,
				0.0002,
				2,
				1,
				1,
				nil,
				0,
				nil,
			},
		}

		got, err := fundingoffer.SnapshotFromRaw(payload)
		require.Nil(t, err)

		expected := &fundingoffer.Snapshot{
			Snapshot: []*fundingoffer.Offer{
				{
					ID:         652606505,
					Symbol:     "fETH",
					MTSCreated: 1574000611000,
					MTSUpdated: 1574000611000,
					Amount:     0.29797676,
					AmountOrig: 0.29797676,
					Type:       "LIMIT",
					Flags:      0,
					Status:     "ACTIVE",
					Rate:       0.0002,
					Period:     2,
					Notify:     true,
					Hidden:     false,
					Insure:     false,
					Renew:      false,
					RateReal:   0,
				},
				{
					ID:         652606506,
					Symbol:     "fETH",
					MTSCreated: 1574000611000,
					MTSUpdated: 1574000611000,
					Amount:     0.29797676,
					AmountOrig: 0.29797676,
					Type:       "LIMIT",
					Flags:      0,
					Status:     "ACTIVE",
					Rate:       0.0002,
					Period:     2,
					Notify:     true,
					Hidden:     true,
					Insure:     false,
					Renew:      false,
					RateReal:   0,
				},
			},
		}

		assert.Equal(t, expected, got)
	})
}

func TestFundingOfferCancelRequest(t *testing.T) {
	t.Run("MarshalJSON", func(t *testing.T) {
		focr := fundingoffer.CancelRequest{ID: 123}

		got, err := focr.MarshalJSON()
		require.Nil(t, err)

		expected := "[0, \"foc\", null, {\"id\":123}]"
		assert.Equal(t, expected, string(got))
	})
}

func TestFundingOfferSubmitRequest(t *testing.T) {
	t.Run("MarshalJSON", func(t *testing.T) {
		fosr := fundingoffer.SubmitRequest{
			Type:   "LIMIT",
			Symbol: "fETH",
			Amount: 0.29797676,
			Rate:   0.0002,
			Period: 2,
		}
		got, err := fosr.MarshalJSON()
		require.Nil(t, err)

		expected := "[0, \"fon\", null, {\"type\":\"LIMIT\",\"symbol\":\"fETH\",\"amount\":\"0.29797676\",\"rate\":\"0.0002\",\"period\":2}]"
		assert.Equal(t, expected, string(got))
	})
}

func TestCancelFromRaw(t *testing.T) {
	pld := []interface{}{
		652606505, "fETH", 1574000611000, 1574000611000, 0.29797676, 0.29797676, "LIMIT",
		nil, nil, 0, "ACTIVE", nil, nil, nil, 0.0002, 2, 1, nil, nil, 0, nil,
	}

	expected := "fundingoffer.Cancel"
	o, err := fundingoffer.CancelFromRaw(pld)
	assert.Nil(t, err)

	got := reflect.TypeOf(o).String()
	assert.Equal(t, expected, got)
}

func TestNewFromRaw(t *testing.T) {
	pld := []interface{}{
		652606505, "fETH", 1574000611000, 1574000611000, 0.29797676, 0.29797676, "LIMIT",
		nil, nil, 0, "ACTIVE", nil, nil, nil, 0.0002, 2, 1, nil, nil, 0, nil,
	}

	expected := "fundingoffer.New"
	o, err := fundingoffer.NewFromRaw(pld)
	assert.Nil(t, err)

	got := reflect.TypeOf(o).String()
	assert.Equal(t, expected, got)
}

func TestUpdateFromRaw(t *testing.T) {
	pld := []interface{}{
		652606505, "fETH", 1574000611000, 1574000611000, 0.29797676, 0.29797676, "LIMIT",
		nil, nil, 0, "ACTIVE", nil, nil, nil, 0.0002, 2, 1, nil, nil, 0, nil,
	}

	expected := "fundingoffer.Update"
	o, err := fundingoffer.UpdateFromRaw(pld)
	assert.Nil(t, err)

	got := reflect.TypeOf(o).String()
	assert.Equal(t, expected, got)
}
<commit_msg>better test coverage for funding offer<commit_after>package fundingoffer_test

import (
	"reflect"
	"testing"

	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/fundingoffer"
	"github.com/stretchr/testify/assert"
)

func TestFromRaw(t *testing.T) {
	cases := map[string]struct {
		pld      []interface{}
		expected *fundingoffer.Offer
		err      func(*testing.T, error)
	}{
		"invalid pld": {
			pld:      []interface{}{"exchange"},
			expected: nil,
			err: func(t *testing.T, err error) {
				assert.NotNil(t, err)
			},
		},
		"rest active funding offer": {
			pld: []interface{}{
				652606505, "fETH", 1574000611000, 1574000611000, 0.29797676, 0.29797676,
				"LIMIT", nil, nil, 0, "ACTIVE", nil, nil, nil, 0.0002, 2, 0, nil, nil, 0, nil,
			},
			expected: &fundingoffer.Offer{
				ID:         652606505,
				Symbol:     "fETH",
				MTSCreated: 1574000611000,
				MTSUpdated: 1574000611000,
				Amount:     0.29797676,
				AmountOrig: 0.29797676,
				Type:       "LIMIT",
				Status:     "ACTIVE",
				Rate:       0.0002,
				Period:     2,
				Notify:     false,
				Hidden:     false,
				Insure:     false,
				Renew:      false,
				RateReal:   0,
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"rest submit funding offer": {
			pld: []interface{}{
				604366339, "fUSD", 1568713496502, 1568713496502, 50, 50, "LIMIT", nil,
				nil, nil, "ACTIVE", nil, nil, nil, 0.00002, 2, false, nil, nil, false, nil,
			},
			expected: &fundingoffer.Offer{
				ID:         604366339,
				Symbol:     "fUSD",
				MTSCreated: 1568713496502,
				MTSUpdated: 1568713496502,
				Amount:     50,
				AmountOrig: 50,
				Type:       "LIMIT",
				Status:     "ACTIVE",
				Rate:       2e-05,
				Period:     2,
				Notify:     false,
				Hidden:     false,
				Insure:     false,
				Renew:      false,
				RateReal:   0,
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"rest cancel funding offer": {
			pld: []interface{}{
				604393839, "fUSD", 1568716545000, 1568716545000, 50, 50, "LIMIT", nil,
				nil, nil, "ACTIVE", nil, nil, nil, 0.06, 2, false, nil, nil, false, nil,
			},
			expected: &fundingoffer.Offer{
				ID:         604393839,
				Symbol:     "fUSD",
				MTSCreated: 1568716545000,
				MTSUpdated: 1568716545000,
				Amount:     50,
				AmountOrig: 50,
				Type:       "LIMIT",
				Status:     "ACTIVE",
				Rate:       0.06,
				Period:     2,
				Notify:     false,
				Hidden:     false,
				Insure:     false,
				Renew:      false,
				RateReal:   0,
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"rest funding offer hist item": {
			pld: []interface{}{
				653170899, "fUSD", 1574072620000, 1574072620000, 0,
				-57.9, nil, nil, nil, nil, "EXECUTED at 0.0368% (57.9)",
				nil, nil, nil, 0.000369, 2, 0, 0, nil, nil, nil,
			},
			expected: &fundingoffer.Offer{
				ID:         653170899,
				Symbol:     "fUSD",
				MTSCreated: 1574072620000,
				MTSUpdated: 1574072620000,
				Amount:     0,
				AmountOrig: -57.9,
				Status:     "EXECUTED at 0.0368% (57.9)",
				Rate:       0.000369,
				Period:     2,
				Notify:     false,
				Hidden:     false,
				Insure:     false,
				Renew:      false,
				RateReal:   0,
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"ws fon fou foc": {
			pld: []interface{}{
				41238747, "fUST", 1575026670000, 1575026670000, 5000, 5000, "LIMIT", nil,
				nil, 0, "ACTIVE", nil, nil, nil, 0.006000000000000001, 30, 0, 0, nil, 0, nil,
			},
			expected: &fundingoffer.Offer{
				ID:         41238747,
				Symbol:     "fUST",
				MTSCreated: 1575026670000,
				MTSUpdated: 1575026670000,
				Amount:     5000,
				AmountOrig: 5000,
				Type:       "LIMIT",
				Status:     "ACTIVE",
				Rate:       0.006000000000000001,
				Period:     30,
				Notify:     false,
				Hidden:     false,
				Insure:     false,
				Renew:      false,
				RateReal:   0,
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"ws fos item": {
			pld: []interface{}{
				41237920, "fETH", 1573912039000, 1573912039000, 0.5, 0.5, "LIMIT",
				nil, nil, 0, "ACTIVE", nil, nil, nil, 0.0024, 2, 0, 0, nil, 0, nil,
			},
			expected: &fundingoffer.Offer{
				ID:         41237920,
				Symbol:     "fETH",
				MTSCreated: 1573912039000,
				MTSUpdated: 1573912039000,
				Amount:     0.5,
				AmountOrig: 0.5,
				Type:       "LIMIT",
				Status:     "ACTIVE",
				Rate:       0.0024,
				Period:     2,
				Notify:     false,
				Hidden:     false,
				Insure:     false,
				Renew:      false,
				RateReal:   0,
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
	}

	for k, v := range cases {
		t.Run(k, func(t *testing.T) {
			got, err := fundingoffer.FromRaw(v.pld)
			v.err(t, err)
			assert.Equal(t, v.expected, got)
		})
	}
}

func TestSnapshotFromRaw(t *testing.T) {
	cases := map[string]struct {
		pld      []interface{}
		expected *fundingoffer.Snapshot
		err      func(*testing.T, error)
	}{
		"invalid pld": {
			pld:      []interface{}{},
			expected: nil,
			err: func(t *testing.T, err error) {
				assert.NotNil(t, err)
			},
		},
		"rest funding offer hist": {
			pld: []interface{}{
				[]interface{}{
					653170899, "fUSD", 1574072620000, 1574072620000, 0, -57.9, nil, nil, nil, nil,
					"EXECUTED at 0.0368% (57.9)", nil, nil, nil, 0.000369, 2, 0, 0, nil, nil, nil,
				},
			},
			expected: &fundingoffer.Snapshot{
				Snapshot: []*fundingoffer.Offer{
					{
						ID:         653170899,
						Symbol:     "fUSD",
						MTSCreated: 1574072620000,
						MTSUpdated: 1574072620000,
						Amount:     0,
						AmountOrig: -57.9,
						Status:     "EXECUTED at 0.0368% (57.9)",
						Rate:       0.000369,
						Period:     2,
						Notify:     false,
						Hidden:     false,
						Insure:     false,
						Renew:      false,
						RateReal:   0,
					},
				},
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
		"ws fos": {
			pld: []interface{}{
				[]interface{}{
					41237920, "fETH", 1573912039000, 1573912039000, 0.5, 0.5, "LIMIT",
					nil, nil, 0, "ACTIVE", nil, nil, nil, 0.0024, 2, 0, 0, nil, 0, nil,
				},
			},
			expected: &fundingoffer.Snapshot{
				Snapshot: []*fundingoffer.Offer{
					{
						ID:         41237920,
						Symbol:     "fETH",
						MTSCreated: 1573912039000,
						MTSUpdated: 1573912039000,
						Amount:     0.5,
						AmountOrig: 0.5,
						Type:       "LIMIT",
						Status:     "ACTIVE",
						Rate:       0.0024,
						Period:     2,
						Notify:     false,
						Hidden:     false,
						Insure:     false,
						Renew:      false,
						RateReal:   0,
					},
				},
			},
			err: func(t *testing.T, err error) {
				assert.Nil(t, err)
			},
		},
	}

	for k, v := range cases {
		t.Run(k, func(t *testing.T) {
			got, err := fundingoffer.SnapshotFromRaw(v.pld)
			v.err(t, err)
			assert.Equal(t, v.expected, got)
		})
	}
}

func TestCancelFromRaw(t *testing.T) {
	pld := []interface{}{
		652606505, "fETH", 1574000611000, 1574000611000, 0.29797676, 0.29797676, "LIMIT",
		nil, nil, 0, "ACTIVE", nil, nil, nil, 0.0002, 2, 1, nil, nil, 0, nil,
	}

	expected := "fundingoffer.Cancel"
	o, err := fundingoffer.CancelFromRaw(pld)
	assert.Nil(t, err)

	got := reflect.TypeOf(o).String()
	assert.Equal(t, expected, got)
}

func TestNewFromRaw(t *testing.T) {
	pld := []interface{}{
		652606505, "fETH", 1574000611000, 1574000611000, 0.29797676, 0.29797676, "LIMIT",
		nil, nil, 0, "ACTIVE", nil, nil, nil, 0.0002, 2, 1, nil, nil, 0, nil,
	}

	expected := "fundingoffer.New"
	o, err := fundingoffer.NewFromRaw(pld)
	assert.Nil(t, err)

	got := reflect.TypeOf(o).String()
	assert.Equal(t, expected, got)
}

func TestUpdateFromRaw(t *testing.T) {
	pld := []interface{}{
		652606505, "fETH", 1574000611000, 1574000611000, 0.29797676, 0.29797676, "LIMIT",
		nil, nil, 0, "ACTIVE", nil, nil, nil, 0.0002, 2, 1, nil, nil, 0, nil,
	}

	expected := "fundingoffer.Update"
	o, err := fundingoffer.UpdateFromRaw(pld)
	assert.Nil(t, err)

	got := reflect.TypeOf(o).String()
	assert.Equal(t, expected, got)
}
<|endoftext|>


<commit_before>package migrations

import (
	"database/sql"
	"fmt"

	"github.com/BurntSushi/migration"
)

func CreateEventIDSequencesForInFlightBuilds(tx migration.LimitedTx) error {
	cursor := 0

	for {
		var id, eventIDStart int
		var guid, endpoint string

		err := tx.QueryRow(`
      SELECT id, max(event_id)
      FROM builds
      LEFT JOIN build_events
      ON build_id = id
      WHERE id > $1
      AND status = 'started'
      GROUP BY id
      ORDER BY id ASC
      LIMIT 1
    `, cursor).Scan(&id, &guid, &endpoint)
		if err != nil {
			if err == sql.ErrNoRows {
				break
			}

			return err
		}

		cursor = id

		_, err = tx.Exec(fmt.Sprintf(`
      CREATE SEQUENCE %s START WITH %d
    `, buildEventSeq(id), eventIDStart))
		if err != nil {
			continue
		}
	}

	return nil
}

func buildEventSeq(buildID int) string {
	return fmt.Sprintf("build_event_id_seq_%d", buildID)
}
<commit_msg>fix up event id sequence migration<commit_after>package migrations

import (
	"database/sql"
	"fmt"

	"github.com/BurntSushi/migration"
)

func CreateEventIDSequencesForInFlightBuilds(tx migration.LimitedTx) error {
	cursor := 0

	for {
		var id, eventIDStart int

		err := tx.QueryRow(`
      SELECT id, max(event_id)
      FROM builds
      LEFT JOIN build_events
      ON build_id = id
      WHERE id > $1
      AND status = 'started'
      GROUP BY id
      ORDER BY id ASC
      LIMIT 1
    `, cursor).Scan(&id, &eventIDStart)
		if err != nil {
			if err == sql.ErrNoRows {
				break
			}

			return err
		}

		cursor = id

		_, err = tx.Exec(fmt.Sprintf(`
      CREATE SEQUENCE %s START WITH %d
    `, buildEventSeq(id), eventIDStart))
		if err != nil {
			return err
		}
	}

	return nil
}

func buildEventSeq(buildID int) string {
	return fmt.Sprintf("build_event_id_seq_%d", buildID)
}
<|endoftext|>