<commit_before>package http

import (
	"net/http"

	"github.com/filebrowser/filebrowser/v2/settings"
	"github.com/filebrowser/filebrowser/v2/storage"
	"github.com/gorilla/mux"
)

type modifyRequest struct {
	What  string   `json:"what"`  // Answer to: what data type?
	Which []string `json:"which"` // Answer to: which fields?
}

func NewHandler(storage *storage.Storage, server *settings.Server) (http.Handler, error) {
	server.Clean()

	r := mux.NewRouter()
	index, static := getStaticHandlers(storage, server)

	monkey := func(fn handleFunc, prefix string) http.Handler {
		return handle(fn, prefix, storage, server)
	}

	r.PathPrefix("/static").Handler(static)
	r.NotFoundHandler = index

	api := r.PathPrefix("/api").Subrouter()

	api.Handle("/login", monkey(loginHandler, ""))
	api.Handle("/signup", monkey(signupHandler, ""))
	api.Handle("/renew", monkey(renewHandler, ""))

	users := api.PathPrefix("/users").Subrouter()
	users.Handle("", monkey(usersGetHandler, "")).Methods("GET")
	users.Handle("", monkey(userPostHandler, "")).Methods("POST")
	users.Handle("/{id:[0-9]+}", monkey(userPutHandler, "")).Methods("PUT")
	users.Handle("/{id:[0-9]+}", monkey(userGetHandler, "")).Methods("GET")
	users.Handle("/{id:[0-9]+}", monkey(userDeleteHandler, "")).Methods("DELETE")

	api.PathPrefix("/resources").Handler(monkey(resourceGetHandler, "/api/resources")).Methods("GET")
	api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler, "/api/resources")).Methods("DELETE")
	api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/api/resources")).Methods("POST")
	api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/api/resources")).Methods("PUT")
	api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler, "/api/resources")).Methods("PATCH")

	api.PathPrefix("/share").Handler(monkey(shareGetsHandler, "/api/share")).Methods("GET")
	api.PathPrefix("/share").Handler(monkey(sharePostHandler, "/api/share")).Methods("POST")
	api.PathPrefix("/share").Handler(monkey(shareDeleteHandler, "/api/share")).Methods("DELETE")

	api.Handle("/settings", monkey(settingsGetHandler, "")).Methods("GET")
	api.Handle("/settings", monkey(settingsPutHandler, "")).Methods("PUT")

	api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET")
	api.PathPrefix("/command").Handler(monkey(commandsHandler, "/api/command")).Methods("GET")
	api.PathPrefix("/search").Handler(monkey(searchHandler, "/api/search")).Methods("GET")

	public := api.PathPrefix("/public").Subrouter()
	public.PathPrefix("/dl").Handler(monkey(publicDlHandler, "/api/public/dl/")).Methods("GET")
	public.PathPrefix("/share").Handler(monkey(publicShareHandler, "/api/public/share/")).Methods("GET")

	return stripPrefix(server.BaseURL, r), nil
}
<commit_msg>fixes: requiring a trailing slash (#669)<commit_after>package http

import (
	"net/http"

	"github.com/filebrowser/filebrowser/v2/settings"
	"github.com/filebrowser/filebrowser/v2/storage"
	"github.com/gorilla/mux"
)

type modifyRequest struct {
	What  string   `json:"what"`  // Answer to: what data type?
	Which []string `json:"which"` // Answer to: which fields?
}

func NewHandler(storage *storage.Storage, server *settings.Server) (http.Handler, error) {
	server.Clean()

	r := mux.NewRouter()
	index, static := getStaticHandlers(storage, server)

	// NOTE: This fixes the issue where it would redirect if people did not put a
	// trailing slash in the end. I hate this decision since this allows some awful
	// URLs https://www.gorillatoolkit.org/pkg/mux#Router.SkipClean
	r = r.SkipClean(true)

	monkey := func(fn handleFunc, prefix string) http.Handler {
		return handle(fn, prefix, storage, server)
	}

	r.PathPrefix("/static").Handler(static)
	r.NotFoundHandler = index

	api := r.PathPrefix("/api").Subrouter()

	api.Handle("/login", monkey(loginHandler, ""))
	api.Handle("/signup", monkey(signupHandler, ""))
	api.Handle("/renew", monkey(renewHandler, ""))

	users := api.PathPrefix("/users").Subrouter()
	users.Handle("", monkey(usersGetHandler, "")).Methods("GET")
	users.Handle("", monkey(userPostHandler, "")).Methods("POST")
	users.Handle("/{id:[0-9]+}", monkey(userPutHandler, "")).Methods("PUT")
	users.Handle("/{id:[0-9]+}", monkey(userGetHandler, "")).Methods("GET")
	users.Handle("/{id:[0-9]+}", monkey(userDeleteHandler, "")).Methods("DELETE")

	api.PathPrefix("/resources").Handler(monkey(resourceGetHandler, "/api/resources")).Methods("GET")
	api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler, "/api/resources")).Methods("DELETE")
	api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/api/resources")).Methods("POST")
	api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/api/resources")).Methods("PUT")
	api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler, "/api/resources")).Methods("PATCH")

	api.PathPrefix("/share").Handler(monkey(shareGetsHandler, "/api/share")).Methods("GET")
	api.PathPrefix("/share").Handler(monkey(sharePostHandler, "/api/share")).Methods("POST")
	api.PathPrefix("/share").Handler(monkey(shareDeleteHandler, "/api/share")).Methods("DELETE")

	api.Handle("/settings", monkey(settingsGetHandler, "")).Methods("GET")
	api.Handle("/settings", monkey(settingsPutHandler, "")).Methods("PUT")

	api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET")
	api.PathPrefix("/command").Handler(monkey(commandsHandler, "/api/command")).Methods("GET")
	api.PathPrefix("/search").Handler(monkey(searchHandler, "/api/search")).Methods("GET")

	public := api.PathPrefix("/public").Subrouter()
	public.PathPrefix("/dl").Handler(monkey(publicDlHandler, "/api/public/dl/")).Methods("GET")
	public.PathPrefix("/share").Handler(monkey(publicShareHandler, "/api/public/share/")).Methods("GET")

	return stripPrefix(server.BaseURL, r), nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/go-chi/render"
)

type Services struct {
	log *log.Logger
}

func NewHandlerFunc(host string, logger *log.Logger) func(http.ResponseWriter, *http.Request) {

	services := Services{log: logger}
	var _ = services

	return func(w http.ResponseWriter, r *http.Request) {
		data := struct {
			Message string
			Date    time.Time
		}{
			fmt.Sprintf("Hello world from %s", host),
			time.Now(),
		}
		render.JSON(w, r, &data)
	}
}
<commit_msg>add: comment<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/go-chi/render"
)

// Services is the global services context
type Services struct {
	log *log.Logger
}

func NewHandlerFunc(host string, logger *log.Logger) func(http.ResponseWriter, *http.Request) {

	services := Services{log: logger}
	var _ = services

	return func(w http.ResponseWriter, r *http.Request) {
		data := struct {
			Message string
			Date    time.Time
		}{
			fmt.Sprintf("Hello world from %s", host),
			time.Now(),
		}
		render.JSON(w, r, &data)
	}
}
<|endoftext|>


<commit_before>package fibgo_test

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo"
	fib "github.com/uudashr/fibgo"
)

func TestNumbers(t *testing.T) {
	cases := []struct {
		limit  string
		code   int
		result []int
	}{
		{limit: "", code: http.StatusOK, result: []int{0, 1, 1, 2, 3, 5, 8, 13, 21, 34}},
		{limit: "-1", code: http.StatusBadRequest, result: []int{}},
		{limit: "non-number", code: http.StatusBadRequest, result: []int{}},
		{limit: "0", code: http.StatusOK, result: []int{}},
		{limit: "1", code: http.StatusOK, result: []int{0}},
		{limit: "2", code: http.StatusOK, result: []int{0, 1}},
		{limit: "4", code: http.StatusOK, result: []int{0, 1, 1, 2}},
		{limit: "9", code: http.StatusOK, result: []int{0, 1, 1, 2, 3, 5, 8, 13, 21}},
		{limit: "10", code: http.StatusOK, result: []int{0, 1, 1, 2, 3, 5, 8, 13, 21, 34}},
	}

	handler := fib.NewHTTPHandler()

	for i, c := range cases {
		limit := c.limit
		expect := c.result
		code := c.code

		req, err := http.NewRequest(echo.GET, "/numbers", nil)
		if err != nil {
			t.Error("err:", err, "case#:", i)
			t.FailNow()
		}

		if limit != "" {
			params := req.URL.Query()
			params.Add("limit", limit)
			req.URL.RawQuery = params.Encode()
		}

		rec := httptest.NewRecorder()
		handler.ServeHTTP(rec, req)

		if got, want := rec.Code, code; got != want {
			t.Error("got:", got, "want:", want, "case#:", i)
			t.FailNow()
		}

		if rec.Code != http.StatusOK {
			continue
		}

		var result []int
		if err := json.NewDecoder(rec.Body).Decode(&result); err != nil {
			t.Error("err:", err, "case#:", i)
			t.FailNow()
		}

		if got, want := len(result), len(expect); got != want {
			t.Error("len(result):", got, "len(expect):", want, "case#:", i)
			t.FailNow()
		}

		for ri, got := range result {
			if want := expect[ri]; got != want {
				t.Error("result[ri]:", got, "expect[ri]:", want, "ri:", i, "case#:", i)
				t.FailNow()
			}
		}
	}
}

func TestNumbers_N(t *testing.T) {
	nums := []int{0, 1, 1, 2, 3, 5, 8, 13, 21, 34}

	handler := fib.NewHTTPHandler()
	for i, v := range nums {

		req, err := http.NewRequest(echo.GET, fmt.Sprintf("/numbers/%d", i), nil)
		if err != nil {
			t.Error("err:", err, "i:", i)
			t.FailNow()
		}

		rec := httptest.NewRecorder()
		handler.ServeHTTP(rec, req)

		if got, want := rec.Code, http.StatusOK; got != want {
			t.Error("got:", got, "want:", want, "i:", i)
			t.FailNow()
		}

		var result int
		if err := json.NewDecoder(rec.Body).Decode(&result); err != nil {
			t.Error("err:", err, "i:", i)
			t.FailNow()
		}

		if got, want := result, v; got != want {
			t.Error("got:", got, "want:", want, "i:", i)
			t.FailNow()
		}
	}
}

func TestNumbers_nonNumberN_error(t *testing.T) {
	params := []string{"asdf", "872y34h", "2.4"}

	handler := fib.NewHTTPHandler()
	for i, p := range params {

		req, err := http.NewRequest(echo.GET, fmt.Sprintf("/numbers/%s", p), nil)
		if err != nil {
			t.Error("err:", err, "case#:", i)
			t.FailNow()
		}

		rec := httptest.NewRecorder()
		handler.ServeHTTP(rec, req)

		if got, want := rec.Code, http.StatusBadRequest; got != want {
			t.Error("got:", got, "want:", want, "case#:", i)
			t.FailNow()
		}
	}
}

func TestNumbers_negativeN_error(t *testing.T) {
	params := []string{"-2", "-10", "-1"}

	handler := fib.NewHTTPHandler()
	for i, p := range params {

		req, err := http.NewRequest(echo.GET, fmt.Sprintf("/numbers/%s", p), nil)
		if err != nil {
			t.Error("err:", err, "case#:", i)
			t.FailNow()
		}

		rec := httptest.NewRecorder()
		handler.ServeHTTP(rec, req)

		if got, want := rec.Code, http.StatusBadRequest; got != want {
			t.Error("got:", got, "want:", want, "case#:", i)
			t.FailNow()
		}
	}
}
<commit_msg>Fix test code<commit_after>package fibgo_test

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo"
	fib "github.com/uudashr/fibgo"
)

func TestNumbers(t *testing.T) {
	cases := []struct {
		limit  string
		code   int
		result []int
	}{
		{limit: "", code: http.StatusOK, result: []int{0, 1, 1, 2, 3, 5, 8, 13, 21, 34}},
		{limit: "-1", code: http.StatusBadRequest, result: []int{}},
		{limit: "non-number", code: http.StatusBadRequest, result: []int{}},
		{limit: "0", code: http.StatusOK, result: []int{}},
		{limit: "1", code: http.StatusOK, result: []int{0}},
		{limit: "2", code: http.StatusOK, result: []int{0, 1}},
		{limit: "4", code: http.StatusOK, result: []int{0, 1, 1, 2}},
		{limit: "9", code: http.StatusOK, result: []int{0, 1, 1, 2, 3, 5, 8, 13, 21}},
		{limit: "10", code: http.StatusOK, result: []int{0, 1, 1, 2, 3, 5, 8, 13, 21, 34}},
	}

	handler := fib.NewHTTPHandler()

	for i, c := range cases {
		limit := c.limit
		expect := c.result
		code := c.code

		req, err := http.NewRequest(echo.GET, "/numbers", nil)
		if err != nil {
			t.Error("err:", err, "case#:", i)
			t.FailNow()
		}

		if limit != "" {
			params := req.URL.Query()
			params.Add("limit", limit)
			req.URL.RawQuery = params.Encode()
		}

		rec := httptest.NewRecorder()
		handler.ServeHTTP(rec, req)

		if got, want := rec.Code, code; got != want {
			t.Error("got:", got, "want:", want, "limit:", limit)
			t.FailNow()
		}

		if got, want := rec.Code, code; got != want {
			t.Error("got:", got, "want:", want)
			t.FailNow()
		}

		if rec.Code != http.StatusOK {
			continue
		}

		var result []int
		if err := json.NewDecoder(rec.Body).Decode(&result); err != nil {
			t.Error("err:", err, "limit:", limit)
			t.FailNow()
		}

		if got, want := len(result), len(expect); got != want {
			t.Error("len(result):", got, "len(expect):", want, "limit:", limit)
			t.FailNow()
		}

		for ri, got := range result {
			if want := expect[ri]; got != want {
				t.Error("result[ri]:", got, "expect[ri]:", want, "ri:", i, "limit:", limit)
				t.FailNow()
			}
		}
	}
}

func TestNumbers_N(t *testing.T) {
	nums := []int{0, 1, 1, 2, 3, 5, 8, 13, 21, 34}

	handler := fib.NewHTTPHandler()
	for i, v := range nums {

		req, err := http.NewRequest(echo.GET, fmt.Sprintf("/numbers/%d", i), nil)
		if err != nil {
			t.Error("err:", err, "i:", i)
			t.FailNow()
		}

		rec := httptest.NewRecorder()
		handler.ServeHTTP(rec, req)

		if got, want := rec.Code, http.StatusOK; got != want {
			t.Error("got:", got, "want:", want, "i:", i)
			t.FailNow()
		}

		var result int
		if err := json.NewDecoder(rec.Body).Decode(&result); err != nil {
			t.Error("err:", err, "i:", i)
			t.FailNow()
		}

		if got, want := result, v; got != want {
			t.Error("got:", got, "want:", want, "i:", i)
			t.FailNow()
		}
	}
}

func TestNumbers_nonNumberN_error(t *testing.T) {
	params := []string{"asdf", "872y34h", "2.4"}

	handler := fib.NewHTTPHandler()
	for i, p := range params {

		req, err := http.NewRequest(echo.GET, fmt.Sprintf("/numbers/%s", p), nil)
		if err != nil {
			t.Error("err:", err, "case#:", i)
			t.FailNow()
		}

		rec := httptest.NewRecorder()
		handler.ServeHTTP(rec, req)

		if got, want := rec.Code, http.StatusBadRequest; got != want {
			t.Error("got:", got, "want:", want, "case#:", i)
			t.FailNow()
		}
	}
}

func TestNumbers_negativeN_error(t *testing.T) {
	params := []string{"-2", "-10", "-1"}

	handler := fib.NewHTTPHandler()
	for i, p := range params {

		req, err := http.NewRequest(echo.GET, fmt.Sprintf("/numbers/%s", p), nil)
		if err != nil {
			t.Error("err:", err, "case#:", i)
			t.FailNow()
		}

		rec := httptest.NewRecorder()
		handler.ServeHTTP(rec, req)

		if got, want := rec.Code, http.StatusBadRequest; got != want {
			t.Error("got:", got, "want:", want, "case#:", i)
			t.FailNow()
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"net/http"

	"github.com/gilcrest/go-api-basic/handler"
	"github.com/gorilla/mux"
	"github.com/justinas/alice"
)

// newRouter sets up the mux.Router and registers routes to URL paths
func newRouter(hdl *handler.AppHandler) *mux.Router {

	// I should take this as a dependency, but need to do some work with wire
	rtr := mux.NewRouter()

	// I should take this as a dependency, but need to do some work with wire
	c := alice.New()

	// add Standard Handler chain and zerolog logger to Context
	c = hdl.AddStandardHandlerChain(c)

	// send Router through PathPrefix method to validate any standard
	// subroutes you may want for your APIs. e.g. I always want to be
	// sure that every request has "/api" as part of it's path prefix
	// without having to put it into every handle path in my various
	// routing functions
	rtr = rtr.PathPrefix("/api").Subrouter()

	// Match only POST requests at /api/v1/movies
	// with Content-Type header = application/json
	rtr.Handle("/v1/movies",
		c.Append(hdl.AccessTokenHandler).
			Then(http.HandlerFunc(hdl.CreateMovie))).
		Methods("POST").
		Headers("Content-Type", "application/json")

	// Match only PUT requests having an ID at /api/v1/movies/{id}
	// with the Content-Type header = application/json
	rtr.Handle("/v1/movies/{id}",
		c.Append(hdl.AccessTokenHandler).
			Then(http.HandlerFunc(hdl.UpdateMovie))).
		Methods("PUT").
		Headers("Content-Type", "application/json")

	// Match only DELETE requests having an ID at /api/v1/movies/{id}
	rtr.Handle("/v1/movies/{id}",
		c.Append(hdl.AccessTokenHandler).
			Then(http.HandlerFunc(hdl.DeleteMovie))).
		Methods("DELETE")

	// Match only GET requests having an ID at /api/v1/movies/{id}
	rtr.Handle("/v1/movies/{id}",
		c.Append(hdl.AccessTokenHandler).
			Then(http.HandlerFunc(hdl.FindByID))).
		Methods("GET")

	// Match only GET requests /api/v1/movies
	rtr.Handle("/v1/movies",
		c.Append(hdl.AccessTokenHandler).
			Then(http.HandlerFunc(hdl.FindAll))).
		Methods("GET")

	// Match only GET requests at /api/v1/ping
	rtr.Handle("/v1/ping",
		c.Append(hdl.AccessTokenHandler).
			Then(http.HandlerFunc(hdl.Ping))).
		Methods("GET")

	return rtr
}
<commit_msg>remove authorization for ping<commit_after>package main

import (
	"net/http"

	"github.com/gilcrest/go-api-basic/handler"
	"github.com/gorilla/mux"
	"github.com/justinas/alice"
)

// newRouter sets up the mux.Router and registers routes to URL paths
func newRouter(hdl *handler.AppHandler) *mux.Router {

	// I should take this as a dependency, but need to do some work with wire
	rtr := mux.NewRouter()

	// I should take this as a dependency, but need to do some work with wire
	c := alice.New()

	// add Standard Handler chain and zerolog logger to Context
	c = hdl.AddStandardHandlerChain(c)

	// send Router through PathPrefix method to validate any standard
	// subroutes you may want for your APIs. e.g. I always want to be
	// sure that every request has "/api" as part of it's path prefix
	// without having to put it into every handle path in my various
	// routing functions
	rtr = rtr.PathPrefix("/api").Subrouter()

	// Match only POST requests at /api/v1/movies
	// with Content-Type header = application/json
	rtr.Handle("/v1/movies",
		c.Append(hdl.AccessTokenHandler).
			Then(http.HandlerFunc(hdl.CreateMovie))).
		Methods("POST").
		Headers("Content-Type", "application/json")

	// Match only PUT requests having an ID at /api/v1/movies/{id}
	// with the Content-Type header = application/json
	rtr.Handle("/v1/movies/{id}",
		c.Append(hdl.AccessTokenHandler).
			Then(http.HandlerFunc(hdl.UpdateMovie))).
		Methods("PUT").
		Headers("Content-Type", "application/json")

	// Match only DELETE requests having an ID at /api/v1/movies/{id}
	rtr.Handle("/v1/movies/{id}",
		c.Append(hdl.AccessTokenHandler).
			Then(http.HandlerFunc(hdl.DeleteMovie))).
		Methods("DELETE")

	// Match only GET requests having an ID at /api/v1/movies/{id}
	rtr.Handle("/v1/movies/{id}",
		c.Append(hdl.AccessTokenHandler).
			Then(http.HandlerFunc(hdl.FindByID))).
		Methods("GET")

	// Match only GET requests /api/v1/movies
	rtr.Handle("/v1/movies",
		c.Append(hdl.AccessTokenHandler).
			Then(http.HandlerFunc(hdl.FindAll))).
		Methods("GET")

	// Match only GET requests at /api/v1/ping
	rtr.Handle("/v1/ping",
		c.Then(http.HandlerFunc(hdl.Ping))).
		Methods("GET")

	return rtr
}
<|endoftext|>


<commit_before>// Copyright 2021 The Ebiten 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.

//go:build example
// +build example

package main

import (
	"fmt"
	"math"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/ebitenutil"
	"github.com/hajimehoshi/ebiten/v2/inpututil"
)

// Game is an isometric demo game.
type Game struct {
	w, h         int
	currentLevel *Level

	camX, camY float64
	camScale   float64
	camScaleTo float64

	mousePanX, mousePanY int
}

// NewGame returns a new isometric demo Game.
func NewGame() (*Game, error) {
	l, err := NewLevel()
	if err != nil {
		return nil, fmt.Errorf("failed to create new level: %s", err)
	}

	g := &Game{
		currentLevel: l,
		camScale:     1,
		camScaleTo:   1,
		mousePanX:    math.MinInt32,
		mousePanY:    math.MinInt32,
	}
	return g, nil
}

// Update reads current user input and updates the Game state.
func (g *Game) Update() error {
	// Update target zoom level.
	var scrollY float64
	if ebiten.IsKeyPressed(ebiten.KeyC) || ebiten.IsKeyPressed(ebiten.KeyPageDown) {
		scrollY = -0.25
	} else if ebiten.IsKeyPressed(ebiten.KeyE) || ebiten.IsKeyPressed(ebiten.KeyPageUp) {
		scrollY = .25
	} else {
		_, scrollY = ebiten.Wheel()
		if scrollY < -1 {
			scrollY = -1
		} else if scrollY > 1 {
			scrollY = 1
		}
	}
	g.camScaleTo += scrollY * (g.camScaleTo / 7)

	// Clamp target zoom level.
	if g.camScaleTo < 0.01 {
		g.camScaleTo = 0.01
	} else if g.camScaleTo > 100 {
		g.camScaleTo = 100
	}

	// Smooth zoom transition.
	div := 10.0
	if g.camScaleTo > g.camScale {
		g.camScale += (g.camScaleTo - g.camScale) / div
	} else if g.camScaleTo < g.camScale {
		g.camScale -= (g.camScale - g.camScaleTo) / div
	}

	// Pan camera via keyboard.
	pan := 7.0 / g.camScale
	if ebiten.IsKeyPressed(ebiten.KeyLeft) || ebiten.IsKeyPressed(ebiten.KeyA) {
		g.camX -= pan
	}
	if ebiten.IsKeyPressed(ebiten.KeyRight) || ebiten.IsKeyPressed(ebiten.KeyD) {
		g.camX += pan
	}
	if ebiten.IsKeyPressed(ebiten.KeyDown) || ebiten.IsKeyPressed(ebiten.KeyS) {
		g.camY -= pan
	}
	if ebiten.IsKeyPressed(ebiten.KeyUp) || ebiten.IsKeyPressed(ebiten.KeyW) {
		g.camY += pan
	}
	// Pan camera via mouse.
	if ebiten.IsMouseButtonPressed(ebiten.MouseButtonRight) {
		if g.mousePanX == math.MinInt32 && g.mousePanY == math.MinInt32 {
			g.mousePanX, g.mousePanY = ebiten.CursorPosition()
		} else {
			x, y := ebiten.CursorPosition()
			dx, dy := float64(g.mousePanX-x)*(pan/100), float64(g.mousePanY-y)*(pan/100)
			g.camX, g.camY = g.camX-dx, g.camY+dy
		}
	} else if g.mousePanX != math.MinInt32 || g.mousePanY != math.MinInt32 {
		g.mousePanX, g.mousePanY = math.MinInt32, math.MinInt32
	}

	// Clamp camera position.
	worldWidth := float64(g.currentLevel.w * g.currentLevel.tileSize / 2)
	worldHeight := float64(g.currentLevel.h * g.currentLevel.tileSize / 2)
	if g.camX < worldWidth*-1 {
		g.camX = worldWidth * -1
	} else if g.camX > worldWidth {
		g.camX = worldWidth
	}
	if g.camY < worldHeight*-1 {
		g.camY = worldHeight * -1
	} else if g.camY > 0 {
		g.camY = 0
	}

	// Randomize level.
	if inpututil.IsKeyJustPressed(ebiten.KeyR) {
		l, err := NewLevel()
		if err != nil {
			return fmt.Errorf("failed to create new level: %s", err)
		}

		g.currentLevel = l
	}

	return nil
}

// Draw draws the Game on the screen.
func (g *Game) Draw(screen *ebiten.Image) {
	// Render level.
	g.renderLevel(screen)

	// Print game info.
	ebitenutil.DebugPrint(screen, fmt.Sprintf("KEYS WASD EC R\nFPS  %0.0f\nTPS  %0.0f\nSCA  %0.2f\nPOS  %0.0f,%0.0f", ebiten.CurrentFPS(), ebiten.CurrentTPS(), g.camScale, g.camX, g.camY))
}

// Layout is called when the Game's layout changes.
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
	g.w, g.h = outsideWidth, outsideHeight
	return g.w, g.h
}

// cartesianToIso transforms cartesian coordinates into isometric coordinates.
func (g *Game) cartesianToIso(x, y float64) (float64, float64) {
	tileSize := g.currentLevel.tileSize
	ix := (x - y) * float64(tileSize/2)
	iy := (x + y) * float64(tileSize/4)
	return ix, iy
}

/*
This function might be useful for those who want to modify this example.

// isoToCartesian transforms isometric coordinates into cartesian coordinates.
func (g *Game) isoToCartesian(x, y float64) (float64, float64) {
	tileSize := g.currentLevel.tileSize
	cx := (x/float64(tileSize/2) + y/float64(tileSize/4)) / 2
	cy := (y/float64(tileSize/4) - (x / float64(tileSize/2))) / 2
	return cx, cy
}
*/

// renderLevel draws the current Level on the screen.
func (g *Game) renderLevel(screen *ebiten.Image) {
	op := &ebiten.DrawImageOptions{}

	var t *Tile
	for y := 0; y < g.currentLevel.h; y++ {
		for x := 0; x < g.currentLevel.w; x++ {
			xi, yi := g.cartesianToIso(float64(x), float64(y))

			// Skip drawing off-screen tiles.
			padding := float64(g.currentLevel.tileSize) * g.camScale
			drawX, drawY := ((xi-g.camX)*g.camScale)+float64(g.w/2.0), ((yi+g.camY)*g.camScale)+float64(g.h/2.0)
			if drawX+padding < 0 || drawY+padding < 0 || drawX > float64(g.w) || drawY > float64(g.h) {
				continue
			}

			t = g.currentLevel.tiles[y][x]
			if t == nil {
				continue // No tile at this position.
			}

			op.GeoM.Reset()
			// Move to current isometric position.
			op.GeoM.Translate(xi, yi)
			// Translate camera position.
			op.GeoM.Translate(-g.camX, g.camY)
			// Zoom.
			op.GeoM.Scale(g.camScale, g.camScale)
			// Center.
			op.GeoM.Translate(float64(g.w/2.0), float64(g.h/2.0))

			t.Draw(screen, op)
		}
	}
}
<commit_msg>examples/isometric: Refactoring<commit_after>// Copyright 2021 The Ebiten 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.

//go:build example
// +build example

package main

import (
	"fmt"
	"math"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/ebitenutil"
	"github.com/hajimehoshi/ebiten/v2/inpututil"
)

// Game is an isometric demo game.
type Game struct {
	w, h         int
	currentLevel *Level

	camX, camY float64
	camScale   float64
	camScaleTo float64

	mousePanX, mousePanY int
}

// NewGame returns a new isometric demo Game.
func NewGame() (*Game, error) {
	l, err := NewLevel()
	if err != nil {
		return nil, fmt.Errorf("failed to create new level: %s", err)
	}

	g := &Game{
		currentLevel: l,
		camScale:     1,
		camScaleTo:   1,
		mousePanX:    math.MinInt32,
		mousePanY:    math.MinInt32,
	}
	return g, nil
}

// Update reads current user input and updates the Game state.
func (g *Game) Update() error {
	// Update target zoom level.
	var scrollY float64
	if ebiten.IsKeyPressed(ebiten.KeyC) || ebiten.IsKeyPressed(ebiten.KeyPageDown) {
		scrollY = -0.25
	} else if ebiten.IsKeyPressed(ebiten.KeyE) || ebiten.IsKeyPressed(ebiten.KeyPageUp) {
		scrollY = .25
	} else {
		_, scrollY = ebiten.Wheel()
		if scrollY < -1 {
			scrollY = -1
		} else if scrollY > 1 {
			scrollY = 1
		}
	}
	g.camScaleTo += scrollY * (g.camScaleTo / 7)

	// Clamp target zoom level.
	if g.camScaleTo < 0.01 {
		g.camScaleTo = 0.01
	} else if g.camScaleTo > 100 {
		g.camScaleTo = 100
	}

	// Smooth zoom transition.
	div := 10.0
	if g.camScaleTo > g.camScale {
		g.camScale += (g.camScaleTo - g.camScale) / div
	} else if g.camScaleTo < g.camScale {
		g.camScale -= (g.camScale - g.camScaleTo) / div
	}

	// Pan camera via keyboard.
	pan := 7.0 / g.camScale
	if ebiten.IsKeyPressed(ebiten.KeyLeft) || ebiten.IsKeyPressed(ebiten.KeyA) {
		g.camX -= pan
	}
	if ebiten.IsKeyPressed(ebiten.KeyRight) || ebiten.IsKeyPressed(ebiten.KeyD) {
		g.camX += pan
	}
	if ebiten.IsKeyPressed(ebiten.KeyDown) || ebiten.IsKeyPressed(ebiten.KeyS) {
		g.camY -= pan
	}
	if ebiten.IsKeyPressed(ebiten.KeyUp) || ebiten.IsKeyPressed(ebiten.KeyW) {
		g.camY += pan
	}
	// Pan camera via mouse.
	if ebiten.IsMouseButtonPressed(ebiten.MouseButtonRight) {
		if g.mousePanX == math.MinInt32 && g.mousePanY == math.MinInt32 {
			g.mousePanX, g.mousePanY = ebiten.CursorPosition()
		} else {
			x, y := ebiten.CursorPosition()
			dx, dy := float64(g.mousePanX-x)*(pan/100), float64(g.mousePanY-y)*(pan/100)
			g.camX, g.camY = g.camX-dx, g.camY+dy
		}
	} else if g.mousePanX != math.MinInt32 || g.mousePanY != math.MinInt32 {
		g.mousePanX, g.mousePanY = math.MinInt32, math.MinInt32
	}

	// Clamp camera position.
	worldWidth := float64(g.currentLevel.w * g.currentLevel.tileSize / 2)
	worldHeight := float64(g.currentLevel.h * g.currentLevel.tileSize / 2)
	if g.camX < worldWidth*-1 {
		g.camX = worldWidth * -1
	} else if g.camX > worldWidth {
		g.camX = worldWidth
	}
	if g.camY < worldHeight*-1 {
		g.camY = worldHeight * -1
	} else if g.camY > 0 {
		g.camY = 0
	}

	// Randomize level.
	if inpututil.IsKeyJustPressed(ebiten.KeyR) {
		l, err := NewLevel()
		if err != nil {
			return fmt.Errorf("failed to create new level: %s", err)
		}

		g.currentLevel = l
	}

	return nil
}

// Draw draws the Game on the screen.
func (g *Game) Draw(screen *ebiten.Image) {
	// Render level.
	g.renderLevel(screen)

	// Print game info.
	ebitenutil.DebugPrint(screen, fmt.Sprintf("KEYS WASD EC R\nFPS  %0.0f\nTPS  %0.0f\nSCA  %0.2f\nPOS  %0.0f,%0.0f", ebiten.CurrentFPS(), ebiten.CurrentTPS(), g.camScale, g.camX, g.camY))
}

// Layout is called when the Game's layout changes.
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
	g.w, g.h = outsideWidth, outsideHeight
	return g.w, g.h
}

// cartesianToIso transforms cartesian coordinates into isometric coordinates.
func (g *Game) cartesianToIso(x, y float64) (float64, float64) {
	tileSize := g.currentLevel.tileSize
	ix := (x - y) * float64(tileSize/2)
	iy := (x + y) * float64(tileSize/4)
	return ix, iy
}

/*
This function might be useful for those who want to modify this example.

// isoToCartesian transforms isometric coordinates into cartesian coordinates.
func (g *Game) isoToCartesian(x, y float64) (float64, float64) {
	tileSize := g.currentLevel.tileSize
	cx := (x/float64(tileSize/2) + y/float64(tileSize/4)) / 2
	cy := (y/float64(tileSize/4) - (x / float64(tileSize/2))) / 2
	return cx, cy
}
*/

// renderLevel draws the current Level on the screen.
func (g *Game) renderLevel(screen *ebiten.Image) {
	op := &ebiten.DrawImageOptions{}
	padding := float64(g.currentLevel.tileSize) * g.camScale
	cx, cy := float64(g.w/2), float64(g.h/2)

	var t *Tile
	for y := 0; y < g.currentLevel.h; y++ {
		for x := 0; x < g.currentLevel.w; x++ {
			xi, yi := g.cartesianToIso(float64(x), float64(y))

			// Skip drawing off-screen tiles.
			drawX, drawY := ((xi-g.camX)*g.camScale)+cx, ((yi+g.camY)*g.camScale)+cy
			if drawX+padding < 0 || drawY+padding < 0 || drawX > float64(g.w) || drawY > float64(g.h) {
				continue
			}

			t = g.currentLevel.tiles[y][x]
			if t == nil {
				continue // No tile at this position.
			}

			op.GeoM.Reset()
			// Move to current isometric position.
			op.GeoM.Translate(xi, yi)
			// Translate camera position.
			op.GeoM.Translate(-g.camX, g.camY)
			// Zoom.
			op.GeoM.Scale(g.camScale, g.camScale)
			// Center.
			op.GeoM.Translate(cx, cy)

			t.Draw(screen, op)
		}
	}
}
<|endoftext|>


<commit_before>package boomer

import (
	"fmt"
	"log"
	"os"
	"runtime/debug"
	"sync/atomic"
	"time"
)

const (
	stateInit     = "ready"
	stateHatching = "hatching"
	stateRunning  = "running"
	stateStopped  = "stopped"
	stateQuitting = "quitting"
)

const (
	slaveReportInterval = 3 * time.Second
)

// Task is like locust's task.
// when boomer receive start message, it will spawn several goroutines to run Task.Fn.
type Task struct {
	Weight int
	Fn     func()
	Name   string
}

type runner struct {
	tasks            []*Task
	numClients       int32
	hatchRate        int
	stopChannel      chan bool
	shutdownSignal   chan bool
	state            string
	masterHost       string
	masterPort       int
	client           client
	nodeID           string
	hatchType        string
	rateLimiter      rateLimiter
	rateLimitEnabled bool
}

func newRunner(tasks []*Task, rateLimiter rateLimiter, hatchType string) (r *runner) {
	r = &runner{
		tasks:     tasks,
		hatchType: hatchType,
	}
	r.nodeID = getNodeID()
	r.shutdownSignal = make(chan bool)

	if rateLimiter != nil {
		r.rateLimitEnabled = true
		r.rateLimiter = rateLimiter
	}

	if hatchType != "asap" && hatchType != "smooth" {
		log.Fatalf("Wrong hatch-type, expected asap or smooth, was %s\n", hatchType)
	}

	return r
}

func (r *runner) safeRun(fn func()) {
	defer func() {
		// don't panic
		err := recover()
		if err != nil {
			stackTrace := debug.Stack()
			errMsg := fmt.Sprintf("%v", err)
			os.Stderr.Write([]byte(errMsg))
			os.Stderr.Write([]byte("\n"))
			os.Stderr.Write(stackTrace)
			RecordFailure("unknown", "panic", int64(0), errMsg)
		}
	}()
	fn()
}

func (r *runner) spawnGoRoutines(spawnCount int, quit chan bool) {
	log.Println("Hatching and swarming", spawnCount, "clients at the rate", r.hatchRate, "clients/s...")

	weightSum := 0
	for _, task := range r.tasks {
		weightSum += task.Weight
	}

	for _, task := range r.tasks {
		percent := float64(task.Weight) / float64(weightSum)
		amount := int(round(float64(spawnCount)*percent, .5, 0))

		if weightSum == 0 {
			amount = int(float64(spawnCount) / float64(len(r.tasks)))
		}

		for i := 1; i <= amount; i++ {
			select {
			case <-quit:
				// quit hatching goroutine
				return
			default:
				if r.hatchType == "smooth" {
					time.Sleep(time.Duration(1000000/r.hatchRate) * time.Microsecond)
				} else if i%r.hatchRate == 0 {
					time.Sleep(1 * time.Second)
				}

				atomic.AddInt32(&r.numClients, 1)
				go func(fn func()) {
					for {
						select {
						case <-quit:
							return
						default:
							if r.rateLimitEnabled {
								blocked := r.rateLimiter.acquire()
								if !blocked {
									r.safeRun(fn)
								}
							} else {
								r.safeRun(fn)
							}
						}
					}
				}(task.Fn)
			}
		}
	}

	r.hatchComplete()
}

func (r *runner) startHatching(spawnCount int, hatchRate int) {
	defaultStats.clearStatsChannel <- true
	r.stopChannel = make(chan bool)

	r.hatchRate = hatchRate
	r.numClients = 0
	go r.spawnGoRoutines(spawnCount, r.stopChannel)
}

func (r *runner) hatchComplete() {
	data := make(map[string]interface{})
	data["count"] = r.numClients
	r.client.sendChannel() <- newMessage("hatch_complete", data, r.nodeID)
}

func (r *runner) onQuiting() {
	if r.state != stateQuitting {
		r.client.sendChannel() <- newMessage("quit", nil, r.nodeID)
	}
}

func (r *runner) stop() {
	// publish the boomer stop event
	// user's code can subscribe to this event and do thins like cleaning up
	Events.Publish("boomer:stop")

	// stop previous goroutines without blocking
	// those goroutines will exit when r.safeRun returns
	close(r.stopChannel)
	if r.rateLimitEnabled {
		r.rateLimiter.stop()
	}
}

func (r *runner) close() {
	if r.client != nil {
		r.client.close()
	}
	close(r.shutdownSignal)
}

func (r *runner) onHatchMessage(msg *message) {
	r.client.sendChannel() <- newMessage("hatching", nil, r.nodeID)
	rate, _ := msg.Data["hatch_rate"]
	clients, _ := msg.Data["num_clients"]
	hatchRate := int(rate.(float64))
	workers := 0
	if _, ok := clients.(uint64); ok {
		workers = int(clients.(uint64))
	} else {
		workers = int(clients.(int64))
	}
	if workers == 0 || hatchRate == 0 {
		log.Printf("Invalid hatch message from master, num_clients is %d, hatch_rate is %d\n",
			workers, hatchRate)
	} else {
		if r.rateLimitEnabled {
			r.rateLimiter.start()
		}
		r.startHatching(workers, hatchRate)
	}
}

// Runner acts as a state machine, and runs in one goroutine without any lock.
func (r *runner) onMessage(msg *message) {
	if msg.Type == "quit" {
		log.Println("Got quit message from master, shutting down...")
		r.state = stateQuitting
		Events.Publish("boomer:quit")
		os.Exit(0)
	}

	switch r.state {
	case stateInit:
		if msg.Type == "hatch" {
			r.state = stateHatching
			r.onHatchMessage(msg)
			r.state = stateRunning
		}
	case stateHatching:
		fallthrough
	case stateRunning:
		switch msg.Type {
		case "hatch":
			r.state = stateHatching
			r.stop()
			r.onHatchMessage(msg)
			r.state = stateRunning
		case "stop":
			r.stop()
			r.state = stateStopped
			log.Println("Recv stop message from master, all the goroutines are stopped")
			r.client.sendChannel() <- newMessage("client_stopped", nil, r.nodeID)
			r.client.sendChannel() <- newMessage("client_ready", nil, r.nodeID)
		}
	case stateStopped:
		if msg.Type == "hatch" {
			r.state = stateHatching
			r.onHatchMessage(msg)
			r.state = stateRunning
		}
	}
}

func (r *runner) startListener() {
	go func() {
		for {
			select {
			case msg := <-r.client.recvChannel():
				r.onMessage(msg)
			case <-r.shutdownSignal:
				return
			}
		}
	}()
}

func (r *runner) getReady() {
	r.state = stateInit
	r.client = newClient(r.masterHost, r.masterPort)
	r.client.connect()

	// listen to master
	r.startListener()

	// tell master, I'm ready
	r.client.sendChannel() <- newMessage("client_ready", nil, r.nodeID)

	// report to master
	go func() {
		for {
			select {
			case data := <-defaultStats.messageToRunner:
				data["user_count"] = r.numClients
				r.client.sendChannel() <- newMessage("stats", data, r.nodeID)
			case <-r.shutdownSignal:
				return
			}
		}
	}()

	Events.Subscribe("boomer:quit", r.onQuiting)
}
<commit_msg>don't report to master if no tests are running, fixes #30<commit_after>package boomer

import (
	"fmt"
	"log"
	"os"
	"runtime/debug"
	"sync/atomic"
	"time"
)

const (
	stateInit     = "ready"
	stateHatching = "hatching"
	stateRunning  = "running"
	stateStopped  = "stopped"
	stateQuitting = "quitting"
)

const (
	slaveReportInterval = 3 * time.Second
)

// Task is like locust's task.
// when boomer receive start message, it will spawn several goroutines to run Task.Fn.
type Task struct {
	Weight int
	Fn     func()
	Name   string
}

type runner struct {
	tasks            []*Task
	numClients       int32
	hatchRate        int
	stopChannel      chan bool
	shutdownSignal   chan bool
	state            string
	masterHost       string
	masterPort       int
	client           client
	nodeID           string
	hatchType        string
	rateLimiter      rateLimiter
	rateLimitEnabled bool
}

func newRunner(tasks []*Task, rateLimiter rateLimiter, hatchType string) (r *runner) {
	r = &runner{
		tasks:     tasks,
		hatchType: hatchType,
	}
	r.nodeID = getNodeID()
	r.shutdownSignal = make(chan bool)

	if rateLimiter != nil {
		r.rateLimitEnabled = true
		r.rateLimiter = rateLimiter
	}

	if hatchType != "asap" && hatchType != "smooth" {
		log.Fatalf("Wrong hatch-type, expected asap or smooth, was %s\n", hatchType)
	}

	return r
}

func (r *runner) safeRun(fn func()) {
	defer func() {
		// don't panic
		err := recover()
		if err != nil {
			stackTrace := debug.Stack()
			errMsg := fmt.Sprintf("%v", err)
			os.Stderr.Write([]byte(errMsg))
			os.Stderr.Write([]byte("\n"))
			os.Stderr.Write(stackTrace)
			RecordFailure("unknown", "panic", int64(0), errMsg)
		}
	}()
	fn()
}

func (r *runner) spawnGoRoutines(spawnCount int, quit chan bool) {
	log.Println("Hatching and swarming", spawnCount, "clients at the rate", r.hatchRate, "clients/s...")

	weightSum := 0
	for _, task := range r.tasks {
		weightSum += task.Weight
	}

	for _, task := range r.tasks {
		percent := float64(task.Weight) / float64(weightSum)
		amount := int(round(float64(spawnCount)*percent, .5, 0))

		if weightSum == 0 {
			amount = int(float64(spawnCount) / float64(len(r.tasks)))
		}

		for i := 1; i <= amount; i++ {
			select {
			case <-quit:
				// quit hatching goroutine
				return
			default:
				if r.hatchType == "smooth" {
					time.Sleep(time.Duration(1000000/r.hatchRate) * time.Microsecond)
				} else if i%r.hatchRate == 0 {
					time.Sleep(1 * time.Second)
				}

				atomic.AddInt32(&r.numClients, 1)
				go func(fn func()) {
					for {
						select {
						case <-quit:
							return
						default:
							if r.rateLimitEnabled {
								blocked := r.rateLimiter.acquire()
								if !blocked {
									r.safeRun(fn)
								}
							} else {
								r.safeRun(fn)
							}
						}
					}
				}(task.Fn)
			}
		}
	}

	r.hatchComplete()
}

func (r *runner) startHatching(spawnCount int, hatchRate int) {
	defaultStats.clearStatsChannel <- true
	r.stopChannel = make(chan bool)

	r.hatchRate = hatchRate
	r.numClients = 0
	go r.spawnGoRoutines(spawnCount, r.stopChannel)
}

func (r *runner) hatchComplete() {
	data := make(map[string]interface{})
	data["count"] = r.numClients
	r.client.sendChannel() <- newMessage("hatch_complete", data, r.nodeID)
}

func (r *runner) onQuiting() {
	if r.state != stateQuitting {
		r.client.sendChannel() <- newMessage("quit", nil, r.nodeID)
	}
}

func (r *runner) stop() {
	// publish the boomer stop event
	// user's code can subscribe to this event and do thins like cleaning up
	Events.Publish("boomer:stop")

	// stop previous goroutines without blocking
	// those goroutines will exit when r.safeRun returns
	close(r.stopChannel)
	if r.rateLimitEnabled {
		r.rateLimiter.stop()
	}
}

func (r *runner) close() {
	if r.client != nil {
		r.client.close()
	}
	close(r.shutdownSignal)
}

func (r *runner) onHatchMessage(msg *message) {
	r.client.sendChannel() <- newMessage("hatching", nil, r.nodeID)
	rate, _ := msg.Data["hatch_rate"]
	clients, _ := msg.Data["num_clients"]
	hatchRate := int(rate.(float64))
	workers := 0
	if _, ok := clients.(uint64); ok {
		workers = int(clients.(uint64))
	} else {
		workers = int(clients.(int64))
	}
	if workers == 0 || hatchRate == 0 {
		log.Printf("Invalid hatch message from master, num_clients is %d, hatch_rate is %d\n",
			workers, hatchRate)
	} else {
		if r.rateLimitEnabled {
			r.rateLimiter.start()
		}
		r.startHatching(workers, hatchRate)
	}
}

// Runner acts as a state machine, and runs in one goroutine without any lock.
func (r *runner) onMessage(msg *message) {
	if msg.Type == "quit" {
		log.Println("Got quit message from master, shutting down...")
		r.state = stateQuitting
		Events.Publish("boomer:quit")
		os.Exit(0)
	}

	switch r.state {
	case stateInit:
		if msg.Type == "hatch" {
			r.state = stateHatching
			r.onHatchMessage(msg)
			r.state = stateRunning
		}
	case stateHatching:
		fallthrough
	case stateRunning:
		switch msg.Type {
		case "hatch":
			r.state = stateHatching
			r.stop()
			r.onHatchMessage(msg)
			r.state = stateRunning
		case "stop":
			r.stop()
			r.state = stateStopped
			log.Println("Recv stop message from master, all the goroutines are stopped")
			r.client.sendChannel() <- newMessage("client_stopped", nil, r.nodeID)
			r.client.sendChannel() <- newMessage("client_ready", nil, r.nodeID)
		}
	case stateStopped:
		if msg.Type == "hatch" {
			r.state = stateHatching
			r.onHatchMessage(msg)
			r.state = stateRunning
		}
	}
}

func (r *runner) startListener() {
	go func() {
		for {
			select {
			case msg := <-r.client.recvChannel():
				r.onMessage(msg)
			case <-r.shutdownSignal:
				return
			}
		}
	}()
}

func (r *runner) getReady() {
	r.state = stateInit
	r.client = newClient(r.masterHost, r.masterPort)
	r.client.connect()

	// listen to master
	r.startListener()

	// tell master, I'm ready
	r.client.sendChannel() <- newMessage("client_ready", nil, r.nodeID)

	// report to master
	go func() {
		for {
			select {
			case data := <-defaultStats.messageToRunner:
				if r.state == stateInit || r.state == stateStopped {
					continue
				}
				data["user_count"] = r.numClients
				r.client.sendChannel() <- newMessage("stats", data, r.nodeID)
			case <-r.shutdownSignal:
				return
			}
		}
	}()

	Events.Subscribe("boomer:quit", r.onQuiting)
}
<|endoftext|>


<commit_before>package fuse

import (
	"log"
	"os"
)

var _ = log.Println

type DefaultNodeFileSystem struct {
}

func (me *DefaultNodeFileSystem) OnUnmount() {
}

func (me *DefaultNodeFileSystem) OnMount(conn *FileSystemConnector) {

}

func (me *DefaultNodeFileSystem) StatFs() *StatfsOut {
	return nil
}
func (me *DefaultNodeFileSystem) Root() FsNode {
	return new(DefaultFsNode)
}

////////////////////////////////////////////////////////////////
// FsNode default

type DefaultFsNode struct {
	inode *Inode
}

func (me *DefaultFsNode) SetInode(node *Inode) {
	if me.inode != nil {
		panic("already have Inode")
	}
	me.inode = node
}

func (me *DefaultFsNode) Inode() *Inode {
	return me.inode
}

func (me *DefaultFsNode) OnForget() {
}

func (me *DefaultFsNode) Lookup(name string, context *Context) (fi *os.FileInfo, node FsNode, code Status) {
	return nil, nil, ENOENT
}

func (me *DefaultFsNode) Access(mode uint32, context *Context) (code Status) {
	return ENOSYS
}

func (me *DefaultFsNode) Readlink(c *Context) ([]byte, Status) {
	return nil, ENOSYS
}

func (me *DefaultFsNode) Mknod(name string, mode uint32, dev uint32, context *Context) (fi *os.FileInfo, newNode FsNode, code Status) {
	return nil, nil, ENOSYS
}
func (me *DefaultFsNode) Mkdir(name string, mode uint32, context *Context) (fi *os.FileInfo, newNode FsNode, code Status) {
	return nil, nil, ENOSYS
}
func (me *DefaultFsNode) Unlink(name string, context *Context) (code Status) {
	return ENOSYS
}
func (me *DefaultFsNode) Rmdir(name string, context *Context) (code Status) {
	return ENOSYS
}
func (me *DefaultFsNode) Symlink(name string, content string, context *Context) (fi *os.FileInfo, newNode FsNode, code Status) {
	return nil, nil, ENOSYS
}

func (me *DefaultFsNode) Rename(oldName string, newParent FsNode, newName string, context *Context) (code Status) {
	return ENOSYS
}

func (me *DefaultFsNode) Link(name string, existing FsNode, context *Context) (fi *os.FileInfo, newNode FsNode, code Status) {
	return nil, nil, ENOSYS
}

func (me *DefaultFsNode) Create(name string, flags uint32, mode uint32, context *Context) (file File, fi *os.FileInfo, newNode FsNode, code Status) {
	return nil, nil, nil, ENOSYS
}

func (me *DefaultFsNode) Open(flags uint32, context *Context) (file File, code Status) {
	return nil, ENOSYS
}

func (me *DefaultFsNode) Flush(file File, openFlags uint32, context *Context) (code Status) {
	return ENOSYS
}

func (me *DefaultFsNode) OpenDir(context *Context) (chan DirEntry, Status) {
	ch := me.Inode().Children()
	s := make(chan DirEntry, len(ch))
	for name, child := range ch {
		fi, code  := child.FsNode().GetAttr(nil, context)
		if code.Ok() {
			log.Printf("mode %o", fi.Mode)
			s <- DirEntry{Name: name, Mode: fi.Mode}
		}
	}
	close(s)
	return s, OK
}

func (me *DefaultFsNode) GetXAttr(attribute string, context *Context) (data []byte, code Status) {
	return nil, ENOSYS
}

func (me *DefaultFsNode) RemoveXAttr(attr string, context *Context) Status {
	return ENOSYS
}

func (me *DefaultFsNode) SetXAttr(attr string, data []byte, flags int, context *Context) Status {
	return ENOSYS
}

func (me *DefaultFsNode) ListXAttr(context *Context) (attrs []string, code Status) {
	return nil, ENOSYS
}

func (me *DefaultFsNode) GetAttr(file File, context *Context) (fi *os.FileInfo, code Status) {
	return nil, ENOSYS
}

func (me *DefaultFsNode) Chmod(file File, perms uint32, context *Context) (code Status) {
	return ENOSYS
}

func (me *DefaultFsNode) Chown(file File, uid uint32, gid uint32, context *Context) (code Status) {
	return ENOSYS
}

func (me *DefaultFsNode) Truncate(file File, size uint64, context *Context) (code Status) {
	return ENOSYS
}

func (me *DefaultFsNode) Utimens(file File, atime uint64, mtime uint64, context *Context) (code Status) {
	return ENOSYS
}
<commit_msg>Provide a DefaultFsNode.GetAttr()<commit_after>package fuse

import (
	"log"
	"os"
)

var _ = log.Println

type DefaultNodeFileSystem struct {
}

func (me *DefaultNodeFileSystem) OnUnmount() {
}

func (me *DefaultNodeFileSystem) OnMount(conn *FileSystemConnector) {

}

func (me *DefaultNodeFileSystem) StatFs() *StatfsOut {
	return nil
}

func (me *DefaultNodeFileSystem) Root() FsNode {
	return new(DefaultFsNode)
}

////////////////////////////////////////////////////////////////
// FsNode default

type DefaultFsNode struct {
	inode *Inode
}

func (me *DefaultFsNode) SetInode(node *Inode) {
	if me.inode != nil {
		panic("already have Inode")
	}
	me.inode = node
}

func (me *DefaultFsNode) Inode() *Inode {
	return me.inode
}

func (me *DefaultFsNode) OnForget() {
}

func (me *DefaultFsNode) Lookup(name string, context *Context) (fi *os.FileInfo, node FsNode, code Status) {
	return nil, nil, ENOENT
}

func (me *DefaultFsNode) Access(mode uint32, context *Context) (code Status) {
	return ENOSYS
}

func (me *DefaultFsNode) Readlink(c *Context) ([]byte, Status) {
	return nil, ENOSYS
}

func (me *DefaultFsNode) Mknod(name string, mode uint32, dev uint32, context *Context) (fi *os.FileInfo, newNode FsNode, code Status) {
	return nil, nil, ENOSYS
}
func (me *DefaultFsNode) Mkdir(name string, mode uint32, context *Context) (fi *os.FileInfo, newNode FsNode, code Status) {
	return nil, nil, ENOSYS
}
func (me *DefaultFsNode) Unlink(name string, context *Context) (code Status) {
	return ENOSYS
}
func (me *DefaultFsNode) Rmdir(name string, context *Context) (code Status) {
	return ENOSYS
}
func (me *DefaultFsNode) Symlink(name string, content string, context *Context) (fi *os.FileInfo, newNode FsNode, code Status) {
	return nil, nil, ENOSYS
}

func (me *DefaultFsNode) Rename(oldName string, newParent FsNode, newName string, context *Context) (code Status) {
	return ENOSYS
}

func (me *DefaultFsNode) Link(name string, existing FsNode, context *Context) (fi *os.FileInfo, newNode FsNode, code Status) {
	return nil, nil, ENOSYS
}

func (me *DefaultFsNode) Create(name string, flags uint32, mode uint32, context *Context) (file File, fi *os.FileInfo, newNode FsNode, code Status) {
	return nil, nil, nil, ENOSYS
}

func (me *DefaultFsNode) Open(flags uint32, context *Context) (file File, code Status) {
	return nil, ENOSYS
}

func (me *DefaultFsNode) Flush(file File, openFlags uint32, context *Context) (code Status) {
	return ENOSYS
}

func (me *DefaultFsNode) OpenDir(context *Context) (chan DirEntry, Status) {
	ch := me.Inode().Children()
	s := make(chan DirEntry, len(ch))
	for name, child := range ch {
		fi, code  := child.FsNode().GetAttr(nil, context)
		if code.Ok() {
			log.Printf("mode %o", fi.Mode)
			s <- DirEntry{Name: name, Mode: fi.Mode}
		}
	}
	close(s)
	return s, OK
}

func (me *DefaultFsNode) GetXAttr(attribute string, context *Context) (data []byte, code Status) {
	return nil, ENOSYS
}

func (me *DefaultFsNode) RemoveXAttr(attr string, context *Context) Status {
	return ENOSYS
}

func (me *DefaultFsNode) SetXAttr(attr string, data []byte, flags int, context *Context) Status {
	return ENOSYS
}

func (me *DefaultFsNode) ListXAttr(context *Context) (attrs []string, code Status) {
	return nil, ENOSYS
}

func (me *DefaultFsNode) GetAttr(file File, context *Context) (fi *os.FileInfo, code Status) {
	if me.Inode().IsDir() {
		return &os.FileInfo{Mode: S_IFDIR | 0755}, OK
	}
	return &os.FileInfo{Mode: S_IFREG | 0644}, OK
}

func (me *DefaultFsNode) Chmod(file File, perms uint32, context *Context) (code Status) {
	return ENOSYS
}

func (me *DefaultFsNode) Chown(file File, uid uint32, gid uint32, context *Context) (code Status) {
	return ENOSYS
}

func (me *DefaultFsNode) Truncate(file File, size uint64, context *Context) (code Status) {
	return ENOSYS
}

func (me *DefaultFsNode) Utimens(file File, atime uint64, mtime uint64, context *Context) (code Status) {
	return ENOSYS
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"io"
	"os/exec"
	"strings"
	"sync"
)

func cmdWithInput(template []string, placeholder, input string) *exec.Cmd {
	argv := make([]string, len(template))
	for i := range argv {
		argv[i] = strings.Replace(template[i], placeholder, input, -1)
	}
	return exec.Command(argv[0], argv[1:]...)
}

type Runner struct {
	cmd      *exec.Cmd
	stdinbuf *bytes.Buffer
	wg       *sync.WaitGroup
}

func NewRunner(template []string, placeholder, input string, stdin *bytes.Buffer) *Runner {
	cmd := cmdWithInput(template, placeholder, input)

	return &Runner{
		cmd:      cmd,
		stdinbuf: stdin,
		wg:       &sync.WaitGroup{},
	}
}

func (r *Runner) Run() (<-chan string, <-chan string, error) {
	stdout, err := r.cmd.StdoutPipe()
	if err != nil {
		return nil, nil, err
	}

	stderr, err := r.cmd.StderrPipe()
	if err != nil {
		stdout.Close()
		return nil, nil, err
	}

	r.wg.Add(1)
	outch := r.streamOutput(stdout, r.wg)
	r.wg.Add(1)
	errch := r.streamOutput(stderr, r.wg)

	if r.stdinbuf.Len() != 0 {
		r.cmd.Stdin = bytes.NewBuffer(r.stdinbuf.Bytes())
	}

	err = r.cmd.Start()
	if err != nil {
		return nil, nil, err
	}

	return outch, errch, nil
}

func (r *Runner) streamOutput(rc io.ReadCloser, wg *sync.WaitGroup) <-chan string {
	ch := make(chan string)
	reader := bufio.NewReader(rc)

	go func() {
		for {
			line, err := reader.ReadBytes('\n')
			if s := string(line); s != "" {
				ch <- s
			}
			if err != nil {
				break
			}
		}
		rc.Close()
		close(ch)
		wg.Done()
	}()

	return ch
}

func (r *Runner) KillWait() {
	r.cmd.Process.Kill()
	r.Wait()
}

func (r *Runner) Wait() {
	r.wg.Wait()
	r.cmd.Wait()
}
<commit_msg>Use Add(2) instead of two Add(1) for WaitGroup<commit_after>package main

import (
	"bufio"
	"bytes"
	"io"
	"os/exec"
	"strings"
	"sync"
)

func cmdWithInput(template []string, placeholder, input string) *exec.Cmd {
	argv := make([]string, len(template))
	for i := range argv {
		argv[i] = strings.Replace(template[i], placeholder, input, -1)
	}
	return exec.Command(argv[0], argv[1:]...)
}

type Runner struct {
	cmd      *exec.Cmd
	stdinbuf *bytes.Buffer
	wg       *sync.WaitGroup
}

func NewRunner(template []string, placeholder, input string, stdin *bytes.Buffer) *Runner {
	cmd := cmdWithInput(template, placeholder, input)

	return &Runner{
		cmd:      cmd,
		stdinbuf: stdin,
		wg:       &sync.WaitGroup{},
	}
}

func (r *Runner) Run() (<-chan string, <-chan string, error) {
	stdout, err := r.cmd.StdoutPipe()
	if err != nil {
		return nil, nil, err
	}

	stderr, err := r.cmd.StderrPipe()
	if err != nil {
		stdout.Close()
		return nil, nil, err
	}

	r.wg.Add(2)
	outch := r.streamOutput(stdout, r.wg)
	errch := r.streamOutput(stderr, r.wg)

	if r.stdinbuf.Len() != 0 {
		r.cmd.Stdin = bytes.NewBuffer(r.stdinbuf.Bytes())
	}

	err = r.cmd.Start()
	if err != nil {
		return nil, nil, err
	}

	return outch, errch, nil
}

func (r *Runner) streamOutput(rc io.ReadCloser, wg *sync.WaitGroup) <-chan string {
	ch := make(chan string)
	reader := bufio.NewReader(rc)

	go func() {
		for {
			line, err := reader.ReadBytes('\n')
			if s := string(line); s != "" {
				ch <- s
			}
			if err != nil {
				break
			}
		}
		rc.Close()
		close(ch)
		wg.Done()
	}()

	return ch
}

func (r *Runner) KillWait() {
	r.cmd.Process.Kill()
	r.Wait()
}

func (r *Runner) Wait() {
	r.wg.Wait()
	r.cmd.Wait()
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 gcs

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"sync"
	"time"

	"golang.org/x/net/context"
)

var fDebug = flag.Bool(
	"gcs.debug",
	false,
	"Write FUSE debugging messages to stderr.")

var gLogger *log.Logger
var gLoggerOnce sync.Once

func initLogger() {
	const flags = log.Ldate | log.Ltime | log.Lmicroseconds
	gLogger = log.New(os.Stderr, "gcs: ", flags)
}

// If debugging is enabled, wrap the supplied bucket in a layer that prints
// debug messages.
func newDebugBucket(wrapped Bucket) (b Bucket) {
	b = wrapped

	if *fDebug {
		gLoggerOnce.Do(initLogger)
		b = &debugBucket{
			logger:  gLogger,
			wrapped: b,
		}

		return
	}

	return
}

type debugBucket struct {
	logger  *log.Logger
	wrapped Bucket
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func (b *debugBucket) mintRequestID() (id uint64) {
	panic("TODO")
}

func (b *debugBucket) requestLogf(
	id uint64,
	format string,
	v ...interface{}) {
	panic("TODO")
}

func (b *debugBucket) startRequest(
	format string,
	v ...interface{}) (id uint64, desc string, start time.Time) {
	start = time.Now()
	id = b.mintRequestID()
	desc = fmt.Sprintf(format, v...)
	return
}

func (b *debugBucket) finishRequest(
	id uint64,
	desc string,
	start time.Time,
	err *error)

////////////////////////////////////////////////////////////////////////
// Bucket interface
////////////////////////////////////////////////////////////////////////

func (b *debugBucket) Name() string {
	return b.wrapped.Name()
}

func (b *debugBucket) NewReader(
	ctx context.Context,
	req *ReadObjectRequest) (rc io.ReadCloser, err error) {
	err = errors.New("TODO: NewReader")
	return
}

func (b *debugBucket) CreateObject(
	ctx context.Context,
	req *CreateObjectRequest) (o *Object, err error) {
	id, desc, start := b.startRequest("CreateObject(%q)", req.Name)
	defer b.finishRequest(id, desc, start, &err)

	o, err = b.wrapped.CreateObject(ctx, req)
	return
}

func (b *debugBucket) StatObject(
	ctx context.Context,
	req *StatObjectRequest) (o *Object, err error) {
	err = errors.New("TODO")
	return
}

func (b *debugBucket) ListObjects(
	ctx context.Context,
	req *ListObjectsRequest) (listing *Listing, err error) {
	err = errors.New("TODO")
	return
}

func (b *debugBucket) UpdateObject(
	ctx context.Context,
	req *UpdateObjectRequest) (o *Object, err error) {
	err = errors.New("TODO")
	return
}

func (b *debugBucket) DeleteObject(
	ctx context.Context,
	name string) (err error) {
	err = errors.New("TODO")
	return
}
<commit_msg>debugBucket.finishRequest<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 gcs

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"sync"
	"time"

	"golang.org/x/net/context"
)

var fDebug = flag.Bool(
	"gcs.debug",
	false,
	"Write FUSE debugging messages to stderr.")

var gLogger *log.Logger
var gLoggerOnce sync.Once

func initLogger() {
	const flags = log.Ldate | log.Ltime | log.Lmicroseconds
	gLogger = log.New(os.Stderr, "gcs: ", flags)
}

// If debugging is enabled, wrap the supplied bucket in a layer that prints
// debug messages.
func newDebugBucket(wrapped Bucket) (b Bucket) {
	b = wrapped

	if *fDebug {
		gLoggerOnce.Do(initLogger)
		b = &debugBucket{
			logger:  gLogger,
			wrapped: b,
		}

		return
	}

	return
}

type debugBucket struct {
	logger  *log.Logger
	wrapped Bucket
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func (b *debugBucket) mintRequestID() (id uint64) {
	panic("TODO")
}

func (b *debugBucket) requestLogf(
	id uint64,
	format string,
	v ...interface{}) {
	panic("TODO")
}

func (b *debugBucket) startRequest(
	format string,
	v ...interface{}) (id uint64, desc string, start time.Time) {
	start = time.Now()
	id = b.mintRequestID()
	desc = fmt.Sprintf(format, v...)

	b.requestLogf(id, "<- %s", desc)
	return
}

func (b *debugBucket) finishRequest(
	id uint64,
	desc string,
	start time.Time,
	err *error) {
	duration := time.Since(start)

	errDesc := "OK"
	if *err != nil {
		errDesc = (*err).Error()
	}

	b.requestLogf(id, "-> %s (%v): %s", desc, duration, errDesc)
}

////////////////////////////////////////////////////////////////////////
// Bucket interface
////////////////////////////////////////////////////////////////////////

func (b *debugBucket) Name() string {
	return b.wrapped.Name()
}

func (b *debugBucket) NewReader(
	ctx context.Context,
	req *ReadObjectRequest) (rc io.ReadCloser, err error) {
	err = errors.New("TODO: NewReader")
	return
}

func (b *debugBucket) CreateObject(
	ctx context.Context,
	req *CreateObjectRequest) (o *Object, err error) {
	id, desc, start := b.startRequest("CreateObject(%q)", req.Name)
	defer b.finishRequest(id, desc, start, &err)

	o, err = b.wrapped.CreateObject(ctx, req)
	return
}

func (b *debugBucket) StatObject(
	ctx context.Context,
	req *StatObjectRequest) (o *Object, err error) {
	err = errors.New("TODO")
	return
}

func (b *debugBucket) ListObjects(
	ctx context.Context,
	req *ListObjectsRequest) (listing *Listing, err error) {
	err = errors.New("TODO")
	return
}

func (b *debugBucket) UpdateObject(
	ctx context.Context,
	req *UpdateObjectRequest) (o *Object, err error) {
	err = errors.New("TODO")
	return
}

func (b *debugBucket) DeleteObject(
	ctx context.Context,
	name string) (err error) {
	err = errors.New("TODO")
	return
}
<|endoftext|>


<commit_before>package v1

import (
	"bytes"
	"encoding/json"
	"fmt"

	"github.com/atlassian/smith/pkg/apis/smith"

	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	k8s_json "k8s.io/apimachinery/pkg/util/json"
)

type BundleConditionType string

// These are valid conditions of a Bundle.
const (
	BundleInProgress BundleConditionType = "InProgress"
	BundleReady      BundleConditionType = "Ready"
	BundleError      BundleConditionType = "Error"
)

const (
	BundleReasonTerminalError  = "TerminalError"
	BundleReasonRetriableError = "RetriableError"
)

type ResourceConditionType string

// These are valid conditions of a resource.
const (
	ResourceBlocked    ResourceConditionType = "Blocked"
	ResourceInProgress ResourceConditionType = "InProgress"
	ResourceReady      ResourceConditionType = "Ready"
	ResourceError      ResourceConditionType = "Error"
)

const (
	// Blocked condition reasons

	ResourceReasonDependenciesNotReady = "DependenciesNotReady"
	ResourceReasonOtherResourceError   = "OtherResourceError"

	// Error condition reasons

	ResourceReasonTerminalError  = "TerminalError"
	ResourceReasonRetriableError = "RetriableError"
)

type ConditionStatus string

// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes
// can't decide if a resource is in the condition or not. In the future, we could add other
// intermediate conditions, e.g. ConditionDegraded.
const (
	ConditionTrue    ConditionStatus = "True"
	ConditionFalse   ConditionStatus = "False"
	ConditionUnknown ConditionStatus = "Unknown"
)

const (
	BundleResourceSingular = "bundle"
	BundleResourcePlural   = "bundles"
	BundleResourceVersion  = "v1"
	BundleResourceKind     = "Bundle"

	BundleResourceGroupVersion = smith.GroupName + "/" + BundleResourceVersion

	BundleResourceName = BundleResourcePlural + "." + smith.GroupName
)

var BundleGVK = SchemeGroupVersion.WithKind(BundleResourceKind)

// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type BundleList struct {
	meta_v1.TypeMeta `json:",inline"`
	// Standard list metadata.
	meta_v1.ListMeta `json:"metadata,omitempty"`

	// Items is a list of bundles.
	Items []Bundle `json:"items"`
}

// +genclient
// +genclient:noStatus

// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Bundle describes a resources bundle.
type Bundle struct {
	meta_v1.TypeMeta `json:",inline"`

	// Standard object metadata
	meta_v1.ObjectMeta `json:"metadata,omitempty"`

	// Spec is the specification of the desired behavior of the Bundle.
	Spec BundleSpec `json:"spec,omitempty"`

	// Status is most recently observed status of the Bundle.
	Status BundleStatus `json:"status,omitempty"`
}

func (b *Bundle) GetCondition(conditionType BundleConditionType) (int, *BundleCondition) {
	for i := range b.Status.Conditions {
		condition := &b.Status.Conditions[i]
		if condition.Type == conditionType {
			return i, condition
		}
	}
	return -1, nil
}

// +k8s:deepcopy-gen=true
type BundleSpec struct {
	Resources []Resource `json:"resources"`
}

// +k8s:deepcopy-gen=true
// BundleCondition describes the state of a bundle at a certain point.
type BundleCondition struct {
	// Type of Bundle condition.
	Type BundleConditionType `json:"type"`
	// Status of the condition.
	Status ConditionStatus `json:"status"`
	// The last time this condition was updated.
	LastUpdateTime meta_v1.Time `json:"lastUpdateTime,omitempty"`
	// Last time the condition transitioned from one status to another.
	LastTransitionTime meta_v1.Time `json:"lastTransitionTime,omitempty"`
	// The reason for the condition's last transition.
	Reason string `json:"reason,omitempty"`
	// A human readable message indicating details about the transition.
	Message string `json:"message,omitempty"`
}

// +k8s:deepcopy-gen=true
// BundleStatus represents the latest available observations of a Bundle's current state.
type BundleStatus struct {
	Conditions       []BundleCondition `json:"conditions,omitempty"`
	ResourceStatuses []ResourceStatus  `json:"resourceStatuses,omitempty"`
}

func (bs *BundleStatus) String() string {
	first := true
	var buf bytes.Buffer
	buf.WriteByte('[')
	for _, cond := range bs.Conditions {
		if first {
			first = false
		} else {
			buf.WriteByte('|')
		}
		buf.WriteString(string(cond.Type))
		buf.WriteByte(' ')
		buf.WriteString(string(cond.Status))
		if cond.Reason != "" {
			buf.WriteByte(' ')
			buf.WriteByte('"')
			buf.WriteString(cond.Reason)
			buf.WriteByte('"')
		}
		if cond.Message != "" {
			buf.WriteByte(' ')
			buf.WriteByte('"')
			buf.WriteString(cond.Message)
			buf.WriteByte('"')
		}
	}
	buf.WriteByte(']')
	return buf.String()
}

func (bs *BundleStatus) GetResourceStatus(resName ResourceName) (int, *ResourceStatus) {
	for i := range bs.ResourceStatuses {
		resStatus := &bs.ResourceStatuses[i]
		if resStatus.Name == resName {
			return i, resStatus
		}
	}
	return -1, nil
}

// ResourceName is a reference to another Resource in the same bundle.
type ResourceName string

// PluginName is a name of a plugin to be invoked.
type PluginName string

// +k8s:deepcopy-gen=true
// Resource describes an object that should be provisioned.
type Resource struct {
	// Name of the resource for references.
	Name ResourceName `json:"name"`

	// Explicit dependencies.
	DependsOn []ResourceName `json:"dependsOn,omitempty"`

	Spec ResourceSpec `json:"spec"`
}

// +k8s:deepcopy-gen=true
// ResourceSpec is a union type - either object of plugin can be specified.
type ResourceSpec struct {
	Object runtime.Object `json:"object,omitempty"`
	Plugin *PluginSpec    `json:"plugin,omitempty"`
}

func (rs *ResourceSpec) UnmarshalJSON(data []byte) error {
	var res struct {
		Object *unstructured.Unstructured `json:"object,omitempty"`
		Plugin *PluginSpec                `json:"plugin,omitempty"`
	}
	err := k8s_json.Unmarshal(data, &res)
	if err != nil {
		return err
	}

	// If we blindly do this copy, we get nils with type info (i.e. != nil)
	if res.Object != nil {
		rs.Object = res.Object
	}

	rs.Plugin = res.Plugin
	return nil
}

// PluginSpec holds the specification for a plugin.
type PluginSpec struct {
	Name       PluginName             `json:"name"`
	ObjectName string                 `json:"objectName"`
	Spec       map[string]interface{} `json:"spec,omitempty"`
}

// DeepCopyInto is an deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PluginSpec) DeepCopyInto(out *PluginSpec) {
	*out = *in
	out.Spec = deepCopyJSONValue(in.Spec).(map[string]interface{})
}

// DeepCopy is an deepcopy function, copying the receiver, creating a new PluginSpec.
func (in *PluginSpec) DeepCopy() *PluginSpec {
	if in == nil {
		return nil
	}
	out := new(PluginSpec)
	in.DeepCopyInto(out)
	return out
}

// +k8s:deepcopy-gen=true
type ResourceStatus struct {
	Name       ResourceName        `json:"name"`
	Conditions []ResourceCondition `json:"conditions,omitempty"`
}

func (rs *ResourceStatus) GetCondition(conditionType ResourceConditionType) (int, *ResourceCondition) {
	for i := range rs.Conditions {
		resCond := &rs.Conditions[i]
		if resCond.Type == conditionType {
			return i, resCond
		}
	}
	return -1, nil
}

// +k8s:deepcopy-gen=true
// ResourceCondition describes the state of a resource at a certain point.
type ResourceCondition struct {
	// Type of Resource condition.
	Type ResourceConditionType `json:"type"`
	// Status of the condition.
	Status ConditionStatus `json:"status"`
	// The last time this condition was updated.
	LastUpdateTime meta_v1.Time `json:"lastUpdateTime,omitempty"`
	// Last time the condition transitioned from one status to another.
	LastTransitionTime meta_v1.Time `json:"lastTransitionTime,omitempty"`
	// The reason for the condition's last transition.
	Reason string `json:"reason,omitempty"`
	// A human readable message indicating details about the transition.
	Message string `json:"message,omitempty"`
}

// TODO Temporary copy of k/k/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go:449 DeepCopyJSONValue()
// Remove once on 1.9 api machinery
func deepCopyJSONValue(x interface{}) interface{} {
	switch x := x.(type) {
	case map[string]interface{}:
		clone := make(map[string]interface{}, len(x))
		for k, v := range x {
			clone[k] = deepCopyJSONValue(v)
		}
		return clone
	case []interface{}:
		clone := make([]interface{}, len(x))
		for i, v := range x {
			clone[i] = deepCopyJSONValue(v)
		}
		return clone
	case string, int64, bool, float64, nil, json.Number:
		return x
	default:
		panic(fmt.Errorf("cannot deep copy %T", x))
	}
}
<commit_msg>Use upstream json deep copying function<commit_after>package v1

import (
	"bytes"

	"github.com/atlassian/smith/pkg/apis/smith"

	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	k8s_json "k8s.io/apimachinery/pkg/util/json"
)

type BundleConditionType string

// These are valid conditions of a Bundle.
const (
	BundleInProgress BundleConditionType = "InProgress"
	BundleReady      BundleConditionType = "Ready"
	BundleError      BundleConditionType = "Error"
)

const (
	BundleReasonTerminalError  = "TerminalError"
	BundleReasonRetriableError = "RetriableError"
)

type ResourceConditionType string

// These are valid conditions of a resource.
const (
	ResourceBlocked    ResourceConditionType = "Blocked"
	ResourceInProgress ResourceConditionType = "InProgress"
	ResourceReady      ResourceConditionType = "Ready"
	ResourceError      ResourceConditionType = "Error"
)

const (
	// Blocked condition reasons

	ResourceReasonDependenciesNotReady = "DependenciesNotReady"
	ResourceReasonOtherResourceError   = "OtherResourceError"

	// Error condition reasons

	ResourceReasonTerminalError  = "TerminalError"
	ResourceReasonRetriableError = "RetriableError"
)

type ConditionStatus string

// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes
// can't decide if a resource is in the condition or not. In the future, we could add other
// intermediate conditions, e.g. ConditionDegraded.
const (
	ConditionTrue    ConditionStatus = "True"
	ConditionFalse   ConditionStatus = "False"
	ConditionUnknown ConditionStatus = "Unknown"
)

const (
	BundleResourceSingular = "bundle"
	BundleResourcePlural   = "bundles"
	BundleResourceVersion  = "v1"
	BundleResourceKind     = "Bundle"

	BundleResourceGroupVersion = smith.GroupName + "/" + BundleResourceVersion

	BundleResourceName = BundleResourcePlural + "." + smith.GroupName
)

var BundleGVK = SchemeGroupVersion.WithKind(BundleResourceKind)

// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type BundleList struct {
	meta_v1.TypeMeta `json:",inline"`
	// Standard list metadata.
	meta_v1.ListMeta `json:"metadata,omitempty"`

	// Items is a list of bundles.
	Items []Bundle `json:"items"`
}

// +genclient
// +genclient:noStatus

// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Bundle describes a resources bundle.
type Bundle struct {
	meta_v1.TypeMeta `json:",inline"`

	// Standard object metadata
	meta_v1.ObjectMeta `json:"metadata,omitempty"`

	// Spec is the specification of the desired behavior of the Bundle.
	Spec BundleSpec `json:"spec,omitempty"`

	// Status is most recently observed status of the Bundle.
	Status BundleStatus `json:"status,omitempty"`
}

func (b *Bundle) GetCondition(conditionType BundleConditionType) (int, *BundleCondition) {
	for i := range b.Status.Conditions {
		condition := &b.Status.Conditions[i]
		if condition.Type == conditionType {
			return i, condition
		}
	}
	return -1, nil
}

// +k8s:deepcopy-gen=true
type BundleSpec struct {
	Resources []Resource `json:"resources"`
}

// +k8s:deepcopy-gen=true
// BundleCondition describes the state of a bundle at a certain point.
type BundleCondition struct {
	// Type of Bundle condition.
	Type BundleConditionType `json:"type"`
	// Status of the condition.
	Status ConditionStatus `json:"status"`
	// The last time this condition was updated.
	LastUpdateTime meta_v1.Time `json:"lastUpdateTime,omitempty"`
	// Last time the condition transitioned from one status to another.
	LastTransitionTime meta_v1.Time `json:"lastTransitionTime,omitempty"`
	// The reason for the condition's last transition.
	Reason string `json:"reason,omitempty"`
	// A human readable message indicating details about the transition.
	Message string `json:"message,omitempty"`
}

// +k8s:deepcopy-gen=true
// BundleStatus represents the latest available observations of a Bundle's current state.
type BundleStatus struct {
	Conditions       []BundleCondition `json:"conditions,omitempty"`
	ResourceStatuses []ResourceStatus  `json:"resourceStatuses,omitempty"`
}

func (bs *BundleStatus) String() string {
	first := true
	var buf bytes.Buffer
	buf.WriteByte('[')
	for _, cond := range bs.Conditions {
		if first {
			first = false
		} else {
			buf.WriteByte('|')
		}
		buf.WriteString(string(cond.Type))
		buf.WriteByte(' ')
		buf.WriteString(string(cond.Status))
		if cond.Reason != "" {
			buf.WriteByte(' ')
			buf.WriteByte('"')
			buf.WriteString(cond.Reason)
			buf.WriteByte('"')
		}
		if cond.Message != "" {
			buf.WriteByte(' ')
			buf.WriteByte('"')
			buf.WriteString(cond.Message)
			buf.WriteByte('"')
		}
	}
	buf.WriteByte(']')
	return buf.String()
}

func (bs *BundleStatus) GetResourceStatus(resName ResourceName) (int, *ResourceStatus) {
	for i := range bs.ResourceStatuses {
		resStatus := &bs.ResourceStatuses[i]
		if resStatus.Name == resName {
			return i, resStatus
		}
	}
	return -1, nil
}

// ResourceName is a reference to another Resource in the same bundle.
type ResourceName string

// PluginName is a name of a plugin to be invoked.
type PluginName string

// +k8s:deepcopy-gen=true
// Resource describes an object that should be provisioned.
type Resource struct {
	// Name of the resource for references.
	Name ResourceName `json:"name"`

	// Explicit dependencies.
	DependsOn []ResourceName `json:"dependsOn,omitempty"`

	Spec ResourceSpec `json:"spec"`
}

// +k8s:deepcopy-gen=true
// ResourceSpec is a union type - either object of plugin can be specified.
type ResourceSpec struct {
	Object runtime.Object `json:"object,omitempty"`
	Plugin *PluginSpec    `json:"plugin,omitempty"`
}

func (rs *ResourceSpec) UnmarshalJSON(data []byte) error {
	var res struct {
		Object *unstructured.Unstructured `json:"object,omitempty"`
		Plugin *PluginSpec                `json:"plugin,omitempty"`
	}
	err := k8s_json.Unmarshal(data, &res)
	if err != nil {
		return err
	}

	// If we blindly do this copy, we get nils with type info (i.e. != nil)
	if res.Object != nil {
		rs.Object = res.Object
	}

	rs.Plugin = res.Plugin
	return nil
}

// PluginSpec holds the specification for a plugin.
type PluginSpec struct {
	Name       PluginName             `json:"name"`
	ObjectName string                 `json:"objectName"`
	Spec       map[string]interface{} `json:"spec,omitempty"`
}

// DeepCopyInto is an deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PluginSpec) DeepCopyInto(out *PluginSpec) {
	*out = *in
	out.Spec = runtime.DeepCopyJSON(in.Spec)
}

// DeepCopy is an deepcopy function, copying the receiver, creating a new PluginSpec.
func (in *PluginSpec) DeepCopy() *PluginSpec {
	if in == nil {
		return nil
	}
	out := new(PluginSpec)
	in.DeepCopyInto(out)
	return out
}

// +k8s:deepcopy-gen=true
type ResourceStatus struct {
	Name       ResourceName        `json:"name"`
	Conditions []ResourceCondition `json:"conditions,omitempty"`
}

func (rs *ResourceStatus) GetCondition(conditionType ResourceConditionType) (int, *ResourceCondition) {
	for i := range rs.Conditions {
		resCond := &rs.Conditions[i]
		if resCond.Type == conditionType {
			return i, resCond
		}
	}
	return -1, nil
}

// +k8s:deepcopy-gen=true
// ResourceCondition describes the state of a resource at a certain point.
type ResourceCondition struct {
	// Type of Resource condition.
	Type ResourceConditionType `json:"type"`
	// Status of the condition.
	Status ConditionStatus `json:"status"`
	// The last time this condition was updated.
	LastUpdateTime meta_v1.Time `json:"lastUpdateTime,omitempty"`
	// Last time the condition transitioned from one status to another.
	LastTransitionTime meta_v1.Time `json:"lastTransitionTime,omitempty"`
	// The reason for the condition's last transition.
	Reason string `json:"reason,omitempty"`
	// A human readable message indicating details about the transition.
	Message string `json:"message,omitempty"`
}
<|endoftext|>


<commit_before>package stage

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/werf/logboek"

	"github.com/werf/werf/pkg/config"
	"github.com/werf/werf/pkg/container_runtime"
	"github.com/werf/werf/pkg/docker"
	imagePkg "github.com/werf/werf/pkg/image"
	"github.com/werf/werf/pkg/stapel"
	"github.com/werf/werf/pkg/storage"
	"github.com/werf/werf/pkg/util"
)

type getImportsOptions struct {
	Before StageName
	After  StageName
}

func getImports(imageBaseConfig *config.StapelImageBase, options *getImportsOptions) []*config.Import {
	var imports []*config.Import
	for _, elm := range imageBaseConfig.Import {
		if options.Before != "" && elm.Before != "" && elm.Before == string(options.Before) {
			imports = append(imports, elm)
		} else if options.After != "" && elm.After != "" && elm.After == string(options.After) {
			imports = append(imports, elm)
		}
	}

	return imports
}

func newImportsStage(imports []*config.Import, name StageName, baseStageOptions *NewBaseStageOptions) *ImportsStage {
	s := &ImportsStage{}
	s.imports = imports
	s.BaseStage = newBaseStage(name, baseStageOptions)
	return s
}

type ImportsStage struct {
	*BaseStage

	imports []*config.Import
}

func (s *ImportsStage) GetDependencies(ctx context.Context, c Conveyor, _, _ container_runtime.ImageInterface) (string, error) {
	var args []string

	for ind, elm := range s.imports {
		var sourceChecksum string
		var err error
		if err := logboek.Context(ctx).Info().LogProcess("Getting import %d source checksum ...", ind).DoError(func() error {
			sourceChecksum, err = s.getImportSourceChecksum(ctx, c, elm)
			return err
		}); err != nil {
			return "", fmt.Errorf("unable to get import %d source checksum: %s", ind, err)
		}

		args = append(args, sourceChecksum)
		args = append(args, elm.To)
		args = append(args, elm.Group, elm.Owner)
	}

	return util.Sha256Hash(args...), nil
}

func (s *ImportsStage) PrepareImage(ctx context.Context, c Conveyor, _, image container_runtime.ImageInterface) error {
	for _, elm := range s.imports {
		sourceImageName := getSourceImageName(elm)
		srv, err := c.GetImportServer(ctx, sourceImageName, elm.Stage)
		if err != nil {
			return fmt.Errorf("unable to get import server for image %q: %s", sourceImageName, err)
		}

		command := srv.GetCopyCommand(ctx, elm)
		image.Container().AddServiceRunCommands(command)

		imageServiceCommitChangeOptions := image.Container().ServiceCommitChangeOptions()

		labelKey := imagePkg.WerfImportChecksumLabelPrefix + getImportID(elm)

		importSourceID := getImportSourceID(c, elm)
		importMetadata, err := c.GetImportMetadata(ctx, s.projectName, importSourceID)
		if err != nil {
			return fmt.Errorf("unable to get import source checksum: %s", err)
		} else if importMetadata == nil {
			panic(fmt.Sprintf("import metadata %s not found", importSourceID))
		}
		labelValue := importMetadata.Checksum

		imageServiceCommitChangeOptions.AddLabel(map[string]string{labelKey: labelValue})
	}

	return nil
}

func (s *ImportsStage) getImportSourceChecksum(ctx context.Context, c Conveyor, importElm *config.Import) (string, error) {
	importSourceID := getImportSourceID(c, importElm)
	importMetadata, err := c.GetImportMetadata(ctx, s.projectName, importSourceID)
	if err != nil {
		return "", fmt.Errorf("unable to get import metadata: %s", err)
	}

	if importMetadata == nil {
		checksum, err := s.generateImportChecksum(ctx, c, importElm)
		if err != nil {
			return "", fmt.Errorf("unable to generate import source checksum: %s", err)
		}

		sourceImageID := getSourceImageID(c, importElm)
		importMetadata = &storage.ImportMetadata{
			ImportSourceID: importSourceID,
			SourceImageID:  sourceImageID,
			Checksum:       checksum,
		}

		if err := c.PutImportMetadata(ctx, s.projectName, importMetadata); err != nil {
			return "", fmt.Errorf("unable to put import metadata: %s", err)
		}
	}

	return importMetadata.Checksum, nil
}

func (s *ImportsStage) generateImportChecksum(ctx context.Context, c Conveyor, importElm *config.Import) (string, error) {
	sourceImageDockerImageName := getSourceImageDockerImageName(c, importElm)
	importSourceID := getImportSourceID(c, importElm)

	stapelContainerName, err := stapel.GetOrCreateContainer(ctx)
	if err != nil {
		return "", err
	}

	importHostTmpDir := filepath.Join(s.imageTmpDir, string(s.Name()), "imports", importSourceID)
	importContainerDir := s.containerWerfDir

	importScriptHostTmpPath := filepath.Join(importHostTmpDir, "script.sh")
	resultChecksumHostTmpPath := filepath.Join(importHostTmpDir, "checksum")
	importScriptContainerPath := filepath.Join(importContainerDir, "script.sh")
	resultChecksumContainerPath := filepath.Join(importContainerDir, "checksum")

	command := generateChecksumCommand(importElm.Add, importElm.IncludePaths, importElm.ExcludePaths, resultChecksumContainerPath)
	if err := stapel.CreateScript(importScriptHostTmpPath, []string{command}); err != nil {
		return "", fmt.Errorf("unable to create script: %s", err)
	}

	if debugImportSourceChecksum() {
		fmt.Println(command)
	}

	runArgs := []string{
		"--rm",
		"--user=0:0",
		"--workdir=/",
		fmt.Sprintf("--volumes-from=%s", stapelContainerName),
		fmt.Sprintf("--volume=%s:%s", importHostTmpDir, importContainerDir),
		fmt.Sprintf("--entrypoint=%s", stapel.BashBinPath()),
		sourceImageDockerImageName,
		importScriptContainerPath,
	}

	if output, err := docker.CliRun_RecordedOutput(ctx, runArgs...); err != nil {
		logboek.Context(ctx).Error().LogF("%s", output)
		return "", err
	}

	data, err := ioutil.ReadFile(resultChecksumHostTmpPath)
	if err != nil {
		return "", fmt.Errorf("unable to read file with import source checksum: %s", err)
	}

	checksum := strings.TrimSpace(string(data))
	return checksum, nil
}

func generateChecksumCommand(from string, includePaths, excludePaths []string, resultChecksumPath string) string {
	findCommandParts := append([]string{}, stapel.FindBinPath(), from, "-type", "f")

	var nameArgs []string
	for _, includePath := range includePaths {
		nameArgs = append(nameArgs, fmt.Sprintf("-wholename \"%s\"", path.Join(from, includePath)))
	}

	for _, excludePath := range excludePaths {
		nameArgs = append(nameArgs, fmt.Sprintf("! -wholename \"%s\"", path.Join(from, excludePath)))
	}

	if len(nameArgs) != 0 {
		findCommandParts = append(findCommandParts, fmt.Sprintf("\\( %s \\)", strings.Join(nameArgs, " -and ")))
	}

	findCommand := strings.Join(findCommandParts, " ")

	sortCommandParts := append([]string{}, stapel.SortBinPath(), "-n")
	sortCommand := strings.Join(sortCommandParts, " ")

	xargsCommandParts := append([]string{}, stapel.XargsBinPath(), stapel.Md5sumBinPath())
	xargsCommand := strings.Join(xargsCommandParts, " ")

	md5SumCommand := stapel.Md5sumBinPath()

	cutCommandParts := append([]string{}, stapel.CutBinPath(), "-d", "' '", "-f", "1")
	cutCommand := strings.Join(cutCommandParts, " ")

	commands := append([]string{}, findCommand, sortCommand, xargsCommand, md5SumCommand, cutCommand)
	command := fmt.Sprintf("%s > %s", strings.Join(commands, " | "), resultChecksumPath)

	return command
}

func getImportID(importElm *config.Import) string {
	return util.Sha256Hash(
		"ImageName", importElm.ImageName,
		"ArtifactName", importElm.ArtifactName,
		"Stage", importElm.Stage,
		"After", importElm.After,
		"Before", importElm.Before,
		"Add", importElm.Add,
		"To", importElm.To,
		"Group", importElm.Group,
		"Owner", importElm.Owner,
		"IncludePaths", strings.Join(importElm.IncludePaths, "///"),
		"ExcludePaths", strings.Join(importElm.ExcludePaths, "///"),
	)
}

func getImportSourceID(c Conveyor, importElm *config.Import) string {
	return util.Sha256Hash(
		"SourceImageContentDigest", getSourceImageContentDigest(c, importElm),
		"Add", importElm.Add,
		"IncludePaths", strings.Join(importElm.IncludePaths, "///"),
		"ExcludePaths", strings.Join(importElm.ExcludePaths, "///"),
	)
}

func getSourceImageDockerImageName(c Conveyor, importElm *config.Import) string {
	sourceImageName := getSourceImageName(importElm)

	var sourceImageDockerImageName string
	if importElm.Stage == "" {
		sourceImageDockerImageName = c.GetImageNameForLastImageStage(sourceImageName)
	} else {
		sourceImageDockerImageName = c.GetImageNameForImageStage(sourceImageName, importElm.Stage)
	}

	return sourceImageDockerImageName
}

func getSourceImageID(c Conveyor, importElm *config.Import) string {
	sourceImageName := getSourceImageName(importElm)

	var sourceImageID string
	if importElm.Stage == "" {
		sourceImageID = c.GetImageIDForLastImageStage(sourceImageName)
	} else {
		sourceImageID = c.GetImageIDForImageStage(sourceImageName, importElm.Stage)
	}

	return sourceImageID
}

func getSourceImageContentDigest(c Conveyor, importElm *config.Import) string {
	sourceImageName := getSourceImageName(importElm)

	var sourceImageContentDigest string
	if importElm.Stage == "" {
		sourceImageContentDigest = c.GetImageContentDigest(sourceImageName)
	} else {
		sourceImageContentDigest = c.GetImageStageContentDigest(sourceImageName, importElm.Stage)
	}

	return sourceImageContentDigest
}

func getSourceImageName(importElm *config.Import) string {
	var sourceImageName string
	if importElm.ImageName != "" {
		sourceImageName = importElm.ImageName
	} else {
		sourceImageName = importElm.ArtifactName
	}

	return sourceImageName
}

func debugImportSourceChecksum() bool {
	return os.Getenv("WERF_DEBUG_IMPORT_SOURCE_CHECKSUM") == "1"
}
<commit_msg>[stapel, imports] Update debug message<commit_after>package stage

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/werf/logboek"

	"github.com/werf/werf/pkg/config"
	"github.com/werf/werf/pkg/container_runtime"
	"github.com/werf/werf/pkg/docker"
	imagePkg "github.com/werf/werf/pkg/image"
	"github.com/werf/werf/pkg/stapel"
	"github.com/werf/werf/pkg/storage"
	"github.com/werf/werf/pkg/util"
)

type getImportsOptions struct {
	Before StageName
	After  StageName
}

func getImports(imageBaseConfig *config.StapelImageBase, options *getImportsOptions) []*config.Import {
	var imports []*config.Import
	for _, elm := range imageBaseConfig.Import {
		if options.Before != "" && elm.Before != "" && elm.Before == string(options.Before) {
			imports = append(imports, elm)
		} else if options.After != "" && elm.After != "" && elm.After == string(options.After) {
			imports = append(imports, elm)
		}
	}

	return imports
}

func newImportsStage(imports []*config.Import, name StageName, baseStageOptions *NewBaseStageOptions) *ImportsStage {
	s := &ImportsStage{}
	s.imports = imports
	s.BaseStage = newBaseStage(name, baseStageOptions)
	return s
}

type ImportsStage struct {
	*BaseStage

	imports []*config.Import
}

func (s *ImportsStage) GetDependencies(ctx context.Context, c Conveyor, _, _ container_runtime.ImageInterface) (string, error) {
	var args []string

	for ind, elm := range s.imports {
		var sourceChecksum string
		var err error
		if err := logboek.Context(ctx).Info().LogProcess("Getting import %d source checksum ...", ind).DoError(func() error {
			sourceChecksum, err = s.getImportSourceChecksum(ctx, c, elm)
			return err
		}); err != nil {
			return "", fmt.Errorf("unable to get import %d source checksum: %s", ind, err)
		}

		args = append(args, sourceChecksum)
		args = append(args, elm.To)
		args = append(args, elm.Group, elm.Owner)
	}

	return util.Sha256Hash(args...), nil
}

func (s *ImportsStage) PrepareImage(ctx context.Context, c Conveyor, _, image container_runtime.ImageInterface) error {
	for _, elm := range s.imports {
		sourceImageName := getSourceImageName(elm)
		srv, err := c.GetImportServer(ctx, sourceImageName, elm.Stage)
		if err != nil {
			return fmt.Errorf("unable to get import server for image %q: %s", sourceImageName, err)
		}

		command := srv.GetCopyCommand(ctx, elm)
		image.Container().AddServiceRunCommands(command)

		imageServiceCommitChangeOptions := image.Container().ServiceCommitChangeOptions()

		labelKey := imagePkg.WerfImportChecksumLabelPrefix + getImportID(elm)

		importSourceID := getImportSourceID(c, elm)
		importMetadata, err := c.GetImportMetadata(ctx, s.projectName, importSourceID)
		if err != nil {
			return fmt.Errorf("unable to get import source checksum: %s", err)
		} else if importMetadata == nil {
			panic(fmt.Sprintf("import metadata %s not found", importSourceID))
		}
		labelValue := importMetadata.Checksum

		imageServiceCommitChangeOptions.AddLabel(map[string]string{labelKey: labelValue})
	}

	return nil
}

func (s *ImportsStage) getImportSourceChecksum(ctx context.Context, c Conveyor, importElm *config.Import) (string, error) {
	importSourceID := getImportSourceID(c, importElm)
	importMetadata, err := c.GetImportMetadata(ctx, s.projectName, importSourceID)
	if err != nil {
		return "", fmt.Errorf("unable to get import metadata: %s", err)
	}

	if importMetadata == nil {
		checksum, err := s.generateImportChecksum(ctx, c, importElm)
		if err != nil {
			return "", fmt.Errorf("unable to generate import source checksum: %s", err)
		}

		sourceImageID := getSourceImageID(c, importElm)
		importMetadata = &storage.ImportMetadata{
			ImportSourceID: importSourceID,
			SourceImageID:  sourceImageID,
			Checksum:       checksum,
		}

		if err := c.PutImportMetadata(ctx, s.projectName, importMetadata); err != nil {
			return "", fmt.Errorf("unable to put import metadata: %s", err)
		}
	}

	return importMetadata.Checksum, nil
}

func (s *ImportsStage) generateImportChecksum(ctx context.Context, c Conveyor, importElm *config.Import) (string, error) {
	sourceImageDockerImageName := getSourceImageDockerImageName(c, importElm)
	importSourceID := getImportSourceID(c, importElm)

	stapelContainerName, err := stapel.GetOrCreateContainer(ctx)
	if err != nil {
		return "", err
	}

	importHostTmpDir := filepath.Join(s.imageTmpDir, string(s.Name()), "imports", importSourceID)
	importContainerDir := s.containerWerfDir

	importScriptHostTmpPath := filepath.Join(importHostTmpDir, "script.sh")
	resultChecksumHostTmpPath := filepath.Join(importHostTmpDir, "checksum")
	importScriptContainerPath := filepath.Join(importContainerDir, "script.sh")
	resultChecksumContainerPath := filepath.Join(importContainerDir, "checksum")

	command := generateChecksumCommand(importElm.Add, importElm.IncludePaths, importElm.ExcludePaths, resultChecksumContainerPath)
	if err := stapel.CreateScript(importScriptHostTmpPath, []string{command}); err != nil {
		return "", fmt.Errorf("unable to create script: %s", err)
	}

	runArgs := []string{
		"--rm",
		"--user=0:0",
		"--workdir=/",
		fmt.Sprintf("--volumes-from=%s", stapelContainerName),
		fmt.Sprintf("--volume=%s:%s", importHostTmpDir, importContainerDir),
		fmt.Sprintf("--entrypoint=%s", stapel.BashBinPath()),
		sourceImageDockerImageName,
		importScriptContainerPath,
	}

	if debugImportSourceChecksum() {
		fmt.Println(runArgs)
	}

	if output, err := docker.CliRun_RecordedOutput(ctx, runArgs...); err != nil {
		logboek.Context(ctx).Error().LogF("%s", output)
		return "", err
	}

	data, err := ioutil.ReadFile(resultChecksumHostTmpPath)
	if err != nil {
		return "", fmt.Errorf("unable to read file with import source checksum: %s", err)
	}

	checksum := strings.TrimSpace(string(data))
	return checksum, nil
}

func generateChecksumCommand(from string, includePaths, excludePaths []string, resultChecksumPath string) string {
	findCommandParts := append([]string{}, stapel.FindBinPath(), from, "-type", "f")

	var nameArgs []string
	for _, includePath := range includePaths {
		nameArgs = append(nameArgs, fmt.Sprintf("-wholename \"%s\"", path.Join(from, includePath)))
	}

	for _, excludePath := range excludePaths {
		nameArgs = append(nameArgs, fmt.Sprintf("! -wholename \"%s\"", path.Join(from, excludePath)))
	}

	if len(nameArgs) != 0 {
		findCommandParts = append(findCommandParts, fmt.Sprintf("\\( %s \\)", strings.Join(nameArgs, " -and ")))
	}

	findCommand := strings.Join(findCommandParts, " ")

	sortCommandParts := append([]string{}, stapel.SortBinPath(), "-n")
	sortCommand := strings.Join(sortCommandParts, " ")

	xargsCommandParts := append([]string{}, stapel.XargsBinPath(), stapel.Md5sumBinPath())
	xargsCommand := strings.Join(xargsCommandParts, " ")

	md5SumCommand := stapel.Md5sumBinPath()

	cutCommandParts := append([]string{}, stapel.CutBinPath(), "-d", "' '", "-f", "1")
	cutCommand := strings.Join(cutCommandParts, " ")

	commands := append([]string{}, findCommand, sortCommand, xargsCommand, md5SumCommand, cutCommand)
	command := fmt.Sprintf("%s > %s", strings.Join(commands, " | "), resultChecksumPath)

	return command
}

func getImportID(importElm *config.Import) string {
	return util.Sha256Hash(
		"ImageName", importElm.ImageName,
		"ArtifactName", importElm.ArtifactName,
		"Stage", importElm.Stage,
		"After", importElm.After,
		"Before", importElm.Before,
		"Add", importElm.Add,
		"To", importElm.To,
		"Group", importElm.Group,
		"Owner", importElm.Owner,
		"IncludePaths", strings.Join(importElm.IncludePaths, "///"),
		"ExcludePaths", strings.Join(importElm.ExcludePaths, "///"),
	)
}

func getImportSourceID(c Conveyor, importElm *config.Import) string {
	return util.Sha256Hash(
		"SourceImageContentDigest", getSourceImageContentDigest(c, importElm),
		"Add", importElm.Add,
		"IncludePaths", strings.Join(importElm.IncludePaths, "///"),
		"ExcludePaths", strings.Join(importElm.ExcludePaths, "///"),
	)
}

func getSourceImageDockerImageName(c Conveyor, importElm *config.Import) string {
	sourceImageName := getSourceImageName(importElm)

	var sourceImageDockerImageName string
	if importElm.Stage == "" {
		sourceImageDockerImageName = c.GetImageNameForLastImageStage(sourceImageName)
	} else {
		sourceImageDockerImageName = c.GetImageNameForImageStage(sourceImageName, importElm.Stage)
	}

	return sourceImageDockerImageName
}

func getSourceImageID(c Conveyor, importElm *config.Import) string {
	sourceImageName := getSourceImageName(importElm)

	var sourceImageID string
	if importElm.Stage == "" {
		sourceImageID = c.GetImageIDForLastImageStage(sourceImageName)
	} else {
		sourceImageID = c.GetImageIDForImageStage(sourceImageName, importElm.Stage)
	}

	return sourceImageID
}

func getSourceImageContentDigest(c Conveyor, importElm *config.Import) string {
	sourceImageName := getSourceImageName(importElm)

	var sourceImageContentDigest string
	if importElm.Stage == "" {
		sourceImageContentDigest = c.GetImageContentDigest(sourceImageName)
	} else {
		sourceImageContentDigest = c.GetImageStageContentDigest(sourceImageName, importElm.Stage)
	}

	return sourceImageContentDigest
}

func getSourceImageName(importElm *config.Import) string {
	var sourceImageName string
	if importElm.ImageName != "" {
		sourceImageName = importElm.ImageName
	} else {
		sourceImageName = importElm.ArtifactName
	}

	return sourceImageName
}

func debugImportSourceChecksum() bool {
	return os.Getenv("WERF_DEBUG_IMPORT_SOURCE_CHECKSUM") == "1"
}
<|endoftext|>