<commit_before>package main

import(
  "fmt"
  "os"
  "io/ioutil"
  "path/filepath"
  "os/exec"
  "strings"
)

const(
  LUNCHY_VERSION = "0.1.0"
)

func printUsage() {
  fmt.Printf("Lunchy %s, the friendly launchctl wrapper\n", LUNCHY_VERSION)
  fmt.Println("Usage: lunchy [start|stop|restart|list|status|install|show|edit] [options]")
}

func findPlists(path string) []string {
  result := []string{}
  files, err := ioutil.ReadDir(path)

  if err != nil {
    return result
  }

  for _, file := range files {
    if !file.IsDir() {
      if (filepath.Ext(file.Name())) == ".plist" {
        name := strings.Replace(file.Name(), ".plist", "", -1)
        result = append(result, name)
      }
    }
  }

  return result
}

func getPlists() []string {
  path := fmt.Sprintf("%s/Library/LaunchAgents", os.Getenv("HOME"))
  files := findPlists(path)

  return files
}

func sliceIncludes(slice []string, match string) bool {
  for _, val := range slice {
    if val == match {
      return true
    }
  }

  return false
}

func printList() {
  for _, file := range getPlists() {
    fmt.Println(file)
  }
}

func printStatus(args []string) {
  out, err := exec.Command("launchctl", "list").Output()

  if err != nil {
    fmt.Println("Failed to execute", err)
    os.Exit(1)
  }

  pattern := ""

  if len(args) == 3 {
    pattern = args[2]
  }

  installed := getPlists()
  lines := strings.Split(strings.TrimSpace(string(out)), "\n")

  for _, line := range lines {
    chunks := strings.Split(line, "\t")

    if len(pattern) > 0 {
      if strings.Index(chunks[2], pattern) != -1 {
        if sliceIncludes(installed, chunks[2]) {
          fmt.Println(line)
        }
      }
    } else {
      if sliceIncludes(installed, chunks[2]) {
        fmt.Println(line)
      }
    }
  }
}

func startDaemons(args []string) {
  if len(args) < 3 {
    fmt.Println("Pattern required")
    os.Exit(1)
  }

  pattern := args[2]

  for _, name := range getPlists() {
    if strings.Index(name, pattern) != -1 {
      startDaemon(name)
    }
  }
}

func startDaemon(name string) {
  path := fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", os.Getenv("HOME"), name)
  _, err := exec.Command("launchctl", "load", path).Output()

  if err != nil {
    fmt.Println("Failed to load", name, ":", err)
    return
  }

  fmt.Println("started", name)
}

func stopDaemons(args []string) {
  if len(args) < 3 {
    fmt.Println("Pattern required")
    os.Exit(1)
  }

  pattern := args[2]

  for _, name := range getPlists() {
    if strings.Index(name, pattern) != -1 {
      stopDaemon(name)
    }
  }
}

func stopDaemon(name string) {
  path := fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", os.Getenv("HOME"), name)
  _, err := exec.Command("launchctl", "unload", path).Output()

  if err != nil {
    fmt.Println("Failed to unload", name, ":", err)
    return
  }

  fmt.Println("stopped", name)
}

func restartDaemons(args []string) {
  if len(args) < 3 {
    fmt.Println("Pattern required")
    os.Exit(1)
  }

  pattern := args[2]

  for _, name := range getPlists() {
    if strings.Index(name, pattern) != -1 {
      stopDaemon(name)
      startDaemon(name)
    }
  }
}

func showPlist(args []string) {
  if len(args) < 3 {
    fmt.Println("Name required")
    os.Exit(1)
  }

  name := args[2]

  for _, plist := range getPlists() {
    if strings.Index(plist, name) != -1 {
      printPlistContent(plist)
      return
    }
  }
}

func printPlistContent(name string) {
  path := fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", os.Getenv("HOME"), name)
  contents, err := ioutil.ReadFile(path)

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

  fmt.Printf(string(contents))
}

func editPlist(args []string) {
  if len(args) < 3 {
    fmt.Println("Name required")
    os.Exit(1)
  }

  name := args[2]

  for _, plist := range getPlists() {
    if strings.Index(plist, name) != -1 {
      editPlistContent(plist)
      return
    }
  }
}

func editPlistContent(name string) {
  path := fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", os.Getenv("HOME"), name)
  editor := os.Getenv("EDITOR")

  if len(editor) == 0 {
    fmt.Println("EDITOR environment variable is not set")
    os.Exit(1)
  }

  cmd := exec.Command(editor, path)

  cmd.Stdin = os.Stdin
  cmd.Stdout = os.Stdout

  cmd.Start()
  cmd.Wait()
}

func main() {
  args := os.Args

  if (len(args) == 1) {
    printUsage()
    os.Exit(1)
  }

  switch args[1] {
  default:
    printUsage()
    os.Exit(1)
  case "list", "ls":
    printList()
    return
  case "status":
    printStatus(args)
    return
  case "start":
    startDaemons(args)
    return
  case "stop":
    stopDaemons(args)
    return
  case "restart":
    restartDaemons(args)
    return
  case "show":
    showPlist(args)
    return
  case "edit":
    editPlist(args)
    return
  }
}<commit_msg>Add command to install a new plist<commit_after>package main

import(
  "fmt"
  "os"
  "io"
  "io/ioutil"
  "path/filepath"
  "os/exec"
  "strings"
)

const(
  LUNCHY_VERSION = "0.1.0"
)

func printUsage() {
  fmt.Printf("Lunchy %s, the friendly launchctl wrapper\n", LUNCHY_VERSION)
  fmt.Println("Usage: lunchy [start|stop|restart|list|status|install|show|edit] [options]")
}

func fileExists(path string) bool {
  _, err := os.Stat(path)
  return err == nil
}

func fileCopy(src string, dst string) error {
  s, err := os.Open(src)
  if err != nil {
    return err
  }

  defer s.Close()

  d, err := os.Create(dst)
  if err != nil {
    return err
  }

  if _, err := io.Copy(d, s); err != nil {
    d.Close()
    return err
  }

  return d.Close()
}

func findPlists(path string) []string {
  result := []string{}
  files, err := ioutil.ReadDir(path)

  if err != nil {
    return result
  }

  for _, file := range files {
    if !file.IsDir() {
      if (filepath.Ext(file.Name())) == ".plist" {
        name := strings.Replace(file.Name(), ".plist", "", -1)
        result = append(result, name)
      }
    }
  }

  return result
}

func getPlists() []string {
  path := fmt.Sprintf("%s/Library/LaunchAgents", os.Getenv("HOME"))
  files := findPlists(path)

  return files
}

func sliceIncludes(slice []string, match string) bool {
  for _, val := range slice {
    if val == match {
      return true
    }
  }

  return false
}

func printList() {
  for _, file := range getPlists() {
    fmt.Println(file)
  }
}

func printStatus(args []string) {
  out, err := exec.Command("launchctl", "list").Output()

  if err != nil {
    fmt.Println("Failed to execute", err)
    os.Exit(1)
  }

  pattern := ""

  if len(args) == 3 {
    pattern = args[2]
  }

  installed := getPlists()
  lines := strings.Split(strings.TrimSpace(string(out)), "\n")

  for _, line := range lines {
    chunks := strings.Split(line, "\t")

    if len(pattern) > 0 {
      if strings.Index(chunks[2], pattern) != -1 {
        if sliceIncludes(installed, chunks[2]) {
          fmt.Println(line)
        }
      }
    } else {
      if sliceIncludes(installed, chunks[2]) {
        fmt.Println(line)
      }
    }
  }
}

func startDaemons(args []string) {
  if len(args) < 3 {
    fmt.Println("Pattern required")
    os.Exit(1)
  }

  pattern := args[2]

  for _, name := range getPlists() {
    if strings.Index(name, pattern) != -1 {
      startDaemon(name)
    }
  }
}

func startDaemon(name string) {
  path := fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", os.Getenv("HOME"), name)
  _, err := exec.Command("launchctl", "load", path).Output()

  if err != nil {
    fmt.Println("Failed to load", name, ":", err)
    return
  }

  fmt.Println("started", name)
}

func stopDaemons(args []string) {
  if len(args) < 3 {
    fmt.Println("Pattern required")
    os.Exit(1)
  }

  pattern := args[2]

  for _, name := range getPlists() {
    if strings.Index(name, pattern) != -1 {
      stopDaemon(name)
    }
  }
}

func stopDaemon(name string) {
  path := fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", os.Getenv("HOME"), name)
  _, err := exec.Command("launchctl", "unload", path).Output()

  if err != nil {
    fmt.Println("Failed to unload", name, ":", err)
    return
  }

  fmt.Println("stopped", name)
}

func restartDaemons(args []string) {
  if len(args) < 3 {
    fmt.Println("Pattern required")
    os.Exit(1)
  }

  pattern := args[2]

  for _, name := range getPlists() {
    if strings.Index(name, pattern) != -1 {
      stopDaemon(name)
      startDaemon(name)
    }
  }
}

func showPlist(args []string) {
  if len(args) < 3 {
    fmt.Println("Name required")
    os.Exit(1)
  }

  name := args[2]

  for _, plist := range getPlists() {
    if strings.Index(plist, name) != -1 {
      printPlistContent(plist)
      return
    }
  }
}

func printPlistContent(name string) {
  path := fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", os.Getenv("HOME"), name)
  contents, err := ioutil.ReadFile(path)

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

  fmt.Printf(string(contents))
}

func editPlist(args []string) {
  if len(args) < 3 {
    fmt.Println("Name required")
    os.Exit(1)
  }

  name := args[2]

  for _, plist := range getPlists() {
    if strings.Index(plist, name) != -1 {
      editPlistContent(plist)
      return
    }
  }
}

func editPlistContent(name string) {
  path := fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", os.Getenv("HOME"), name)
  editor := os.Getenv("EDITOR")

  if len(editor) == 0 {
    fmt.Println("EDITOR environment variable is not set")
    os.Exit(1)
  }

  cmd := exec.Command(editor, path)

  cmd.Stdin = os.Stdin
  cmd.Stdout = os.Stdout

  cmd.Start()
  cmd.Wait()
}

func installPlist(args []string) {
  if len(args) < 3 {
    fmt.Println("Path required")
    os.Exit(1)
  }

  path := args[2]

  if !fileExists(path) {
    fmt.Println("File", path, "does not exist")
    os.Exit(1)
  }

  info, _ := os.Stat(path)
  base_path := fmt.Sprintf("%s/%s", os.Getenv("HOME"), "Library/LaunchAgents")
  new_path := fmt.Sprintf("%s/%s", base_path, info.Name())

  if fileExists(new_path) {
    if os.Remove(new_path) != nil {
      fmt.Println("Failed to delete existing file", new_path)
      os.Exit(1)
    }
  }

  err := fileCopy(path, new_path)

  if err != nil {
    fmt.Println("Failed to copy file")
    os.Exit(1)
  }

  fmt.Println(path, "installed to", base_path)
}

func main() {
  args := os.Args

  if (len(args) == 1) {
    printUsage()
    os.Exit(1)
  }

  switch args[1] {
  default:
    printUsage()
    os.Exit(1)
  case "list", "ls":
    printList()
    return
  case "status":
    printStatus(args)
    return
  case "start":
    startDaemons(args)
    return
  case "stop":
    stopDaemons(args)
    return
  case "restart":
    restartDaemons(args)
    return
  case "show":
    showPlist(args)
    return
  case "edit":
    editPlist(args)
    return
  case "install":
    installPlist(args)
    return
  }
}<|endoftext|>


<commit_before>package console

import (
	"encoding/json"
	"fmt"
	"net/http"
	"path/filepath"
	"strings"

	"github.com/ahmetalpbalkan/go-linq"
	"github.com/aisk/logp"
	"github.com/gorilla/mux"
	"github.com/leancloud/lean-cli/api"
	"github.com/levigross/grequests"
)

var hookNames = map[string]string{
	"__before_save_for_":   "beforeSave",
	"__after_save_for_":    "afterSave",
	"__before_update_for_": "beforeUpdate",
	"__after_update_for_":  "afterUpdate",
	"__before_delete_for_": "beforeDelete",
	"__after_delete_for_":  "afterDelete",
	"__on_login_":          "onLogin",
}

// Server is a struct for develoment console server
type Server struct {
	AppID       string
	AppKey      string
	MasterKey   string
	HookKey     string
	RemoteURL   string
	ConsolePort string
	Errors      chan error
}

func (server *Server) getFunctions() ([]string, error) {
	url := fmt.Sprintf("%s/1.1/functions/_ops/metadatas", server.RemoteURL)
	response, err := grequests.Get(url, &grequests.RequestOptions{
		Headers: map[string]string{
			"x-avoscloud-application-id": server.AppID,
			"x-avoscloud-master-key":     server.MasterKey,
		},
	})
	if err != nil {
		return nil, err
	}

	if !response.Ok {
		return nil, api.NewErrorFromResponse(response)
	}

	result := new(struct {
		Result []string `json:"result"`
	})
	err = response.JSON(result)
	if err != nil {
		return nil, err
	}
	return result.Result, nil
}

func (server *Server) indexHandler(w http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(w, resources["index.html"])
}

func (server *Server) resourcesHandler(w http.ResponseWriter, req *http.Request) {
	resourceName := mux.Vars(req)["resourceName"]
	if resource, ok := resources[resourceName]; ok {
		if filepath.Ext(resourceName) == ".js" {
			w.Header().Set("Content-Type", "application/javascript")
		}
		fmt.Fprintf(w, resource)
	} else {
		http.NotFound(w, req)
	}
}

func (server *Server) appInfoHandler(w http.ResponseWriter, req *http.Request) {
	url := fmt.Sprintf("%s/1.1/functions/_ops/metadatas", server.RemoteURL)
	response, err := grequests.Options(url, &grequests.RequestOptions{})
	if err != nil {
		panic(err)
	}
	if !response.Ok {
		panic(api.NewErrorFromResponse(response))
	}

	content, err := json.Marshal(map[string]interface{}{
		"appId":       server.AppID,
		"appKey":      server.AppKey,
		"masterKey":   server.MasterKey,
		"hookKey":     server.HookKey,
		"sendHookKey": strings.Contains(response.Header.Get("Access-Control-Allow-Headers"), "X-LC-Hook-Key"),
		"remoteUrl":   server.RemoteURL,
		"warnings":    []string{},
	})
	if err != nil {
		panic(err)
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(content)
}

func (server *Server) functionsHandler(w http.ResponseWriter, req *http.Request) {
	functions, err := server.getFunctions()
	if err != nil {
		fmt.Println("get functions error: ", err)
		return
	}

	result := linq.From(functions).Where(func(in interface{}) bool {
		function := in.(string)
		return !strings.HasPrefix(function, "__")
	}).Results()
	if len(result) > 0 {
		result = linq.From(result).OrderBy(func(in interface{}) interface{} {
			function := in.(string)
			if function == "" {
				return " "[0]
			}
			return function[0]
		}).Select(func(in interface{}) interface{} {
			function := in.(string)
			return map[string]string{
				"name": function,
				"sign": signCloudFunc(server.MasterKey, function, timeStamp()),
			}
		}).Results()
	}

	w.Header().Set("Content-Type", "application/json")
	j, err := json.MarshalIndent(result, "", "  ")
	if err != nil {
		panic(err)
	}
	w.Write(j)
}

func (server *Server) classesHandler(w http.ResponseWriter, req *http.Request) {
	functions, err := server.getFunctions()
	if err != nil {
		fmt.Println("get functions error: ", err)
		return
	}

	result := linq.From(functions).Where(func(in interface{}) bool {
		funcName := in.(string)
		for key := range hookNames {
			if strings.HasPrefix(funcName, key) {
				return true
			}
		}
		return false
	}).Select(func(in interface{}) interface{} {
		funcName := in.(string)
		for key := range hookNames {
			if strings.HasPrefix(funcName, key) {
				return strings.TrimPrefix(funcName, key)
			}
		}
		panic("impossible")
	}).Distinct().Results()

	if len(result) > 0 {
		result = linq.From(result).OrderBy(func(in interface{}) interface{} {
			function := in.(string)
			return function[0]
		}).Results()
	}

	w.Header().Set("Content-Type", "application/json")
	j, _ := json.MarshalIndent(result, "", "  ")
	w.Write(j)
}

func (server *Server) classActionHandler(w http.ResponseWriter, req *http.Request) {
	className := mux.Vars(req)["className"]

	functions, err := server.getFunctions()
	if err != nil {
		fmt.Println("get functions error: ", err)
		return
	}

	result := linq.From(functions).Where(func(in interface{}) bool {
		funcName := in.(string)
		if strings.HasPrefix(funcName, "__") && strings.HasSuffix(funcName, className) {
			return true
		}
		return false
	}).Select(func(in interface{}) interface{} {
		funcName := in.(string)
		action := ""
		for key, value := range hookNames {
			if strings.HasPrefix(funcName, key) {
				action = value
			}
		}
		signFuncName := funcName
		if strings.HasPrefix(funcName, "__before") {
			signFuncName = "__before_for_" + className
		} else if strings.HasPrefix(funcName, "__after") {
			signFuncName = "__after_for_" + className
		}
		return map[string]string{
			"className": className,
			"action":    action,
			"sign":      signCloudFunc(server.MasterKey, signFuncName, timeStamp()),
		}
	}).Results()

	w.Header().Set("Content-Type", "application/json")
	j, _ := json.MarshalIndent(result, "", "  ")
	w.Write(j)
}

// Run the dev server
func (server *Server) Run() {
	router := mux.NewRouter()

	router.HandleFunc("/", server.indexHandler)
	router.HandleFunc("/__engine/1/appInfo", server.appInfoHandler)
	router.HandleFunc("/__engine/1/functions", server.functionsHandler)
	router.HandleFunc("/__engine/1/classes", server.classesHandler)
	router.HandleFunc("/__engine/1/classes/{className}/actions", server.classActionHandler)
	router.HandleFunc("/{resourceName}", server.resourcesHandler)

	addr := "localhost:" + server.ConsolePort
	logp.Info("Cloud function debug console (if available) is accessible at: http://" + addr)

	go func() {
		server.Errors <- http.ListenAndServe(addr, router)
	}()
}
<commit_msg>:passport_control: Send CORS headers for OPTIONS request (.Net SDK)<commit_after>package console

import (
	"encoding/json"
	"fmt"
	"net/http"
	"path/filepath"
	"strings"

	"github.com/ahmetalpbalkan/go-linq"
	"github.com/aisk/logp"
	"github.com/gorilla/mux"
	"github.com/leancloud/lean-cli/api"
	"github.com/levigross/grequests"
)

var hookNames = map[string]string{
	"__before_save_for_":   "beforeSave",
	"__after_save_for_":    "afterSave",
	"__before_update_for_": "beforeUpdate",
	"__after_update_for_":  "afterUpdate",
	"__before_delete_for_": "beforeDelete",
	"__after_delete_for_":  "afterDelete",
	"__on_login_":          "onLogin",
}

// Server is a struct for develoment console server
type Server struct {
	AppID       string
	AppKey      string
	MasterKey   string
	HookKey     string
	RemoteURL   string
	ConsolePort string
	Errors      chan error
}

func (server *Server) getFunctions() ([]string, error) {
	url := fmt.Sprintf("%s/1.1/functions/_ops/metadatas", server.RemoteURL)
	response, err := grequests.Get(url, &grequests.RequestOptions{
		Headers: map[string]string{
			"x-avoscloud-application-id": server.AppID,
			"x-avoscloud-master-key":     server.MasterKey,
		},
	})
	if err != nil {
		return nil, err
	}

	if !response.Ok {
		return nil, api.NewErrorFromResponse(response)
	}

	result := new(struct {
		Result []string `json:"result"`
	})
	err = response.JSON(result)
	if err != nil {
		return nil, err
	}
	return result.Result, nil
}

func (server *Server) indexHandler(w http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(w, resources["index.html"])
}

func (server *Server) resourcesHandler(w http.ResponseWriter, req *http.Request) {
	resourceName := mux.Vars(req)["resourceName"]
	if resource, ok := resources[resourceName]; ok {
		if filepath.Ext(resourceName) == ".js" {
			w.Header().Set("Content-Type", "application/javascript")
		}
		fmt.Fprintf(w, resource)
	} else {
		http.NotFound(w, req)
	}
}

func (server *Server) appInfoHandler(w http.ResponseWriter, req *http.Request) {
	url := fmt.Sprintf("%s/1.1/functions/_ops/metadatas", server.RemoteURL)
	response, err := grequests.Options(url, &grequests.RequestOptions{
		Headers: map[string]string{
			"Access-Control-Request-Method": "GET",
			"Origin":                        fmt.Sprint("http://localhost:", server.ConsolePort),
		},
	})
	if err != nil {
		panic(err)
	}
	if !response.Ok {
		panic(api.NewErrorFromResponse(response))
	}

	content, err := json.Marshal(map[string]interface{}{
		"appId":       server.AppID,
		"appKey":      server.AppKey,
		"masterKey":   server.MasterKey,
		"hookKey":     server.HookKey,
		"sendHookKey": strings.Contains(response.Header.Get("Access-Control-Allow-Headers"), "X-LC-Hook-Key"),
		"remoteUrl":   server.RemoteURL,
		"warnings":    []string{},
	})
	if err != nil {
		panic(err)
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(content)
}

func (server *Server) functionsHandler(w http.ResponseWriter, req *http.Request) {
	functions, err := server.getFunctions()
	if err != nil {
		fmt.Println("get functions error: ", err)
		return
	}

	result := linq.From(functions).Where(func(in interface{}) bool {
		function := in.(string)
		return !strings.HasPrefix(function, "__")
	}).Results()
	if len(result) > 0 {
		result = linq.From(result).OrderBy(func(in interface{}) interface{} {
			function := in.(string)
			if function == "" {
				return " "[0]
			}
			return function[0]
		}).Select(func(in interface{}) interface{} {
			function := in.(string)
			return map[string]string{
				"name": function,
				"sign": signCloudFunc(server.MasterKey, function, timeStamp()),
			}
		}).Results()
	}

	w.Header().Set("Content-Type", "application/json")
	j, err := json.MarshalIndent(result, "", "  ")
	if err != nil {
		panic(err)
	}
	w.Write(j)
}

func (server *Server) classesHandler(w http.ResponseWriter, req *http.Request) {
	functions, err := server.getFunctions()
	if err != nil {
		fmt.Println("get functions error: ", err)
		return
	}

	result := linq.From(functions).Where(func(in interface{}) bool {
		funcName := in.(string)
		for key := range hookNames {
			if strings.HasPrefix(funcName, key) {
				return true
			}
		}
		return false
	}).Select(func(in interface{}) interface{} {
		funcName := in.(string)
		for key := range hookNames {
			if strings.HasPrefix(funcName, key) {
				return strings.TrimPrefix(funcName, key)
			}
		}
		panic("impossible")
	}).Distinct().Results()

	if len(result) > 0 {
		result = linq.From(result).OrderBy(func(in interface{}) interface{} {
			function := in.(string)
			return function[0]
		}).Results()
	}

	w.Header().Set("Content-Type", "application/json")
	j, _ := json.MarshalIndent(result, "", "  ")
	w.Write(j)
}

func (server *Server) classActionHandler(w http.ResponseWriter, req *http.Request) {
	className := mux.Vars(req)["className"]

	functions, err := server.getFunctions()
	if err != nil {
		fmt.Println("get functions error: ", err)
		return
	}

	result := linq.From(functions).Where(func(in interface{}) bool {
		funcName := in.(string)
		if strings.HasPrefix(funcName, "__") && strings.HasSuffix(funcName, className) {
			return true
		}
		return false
	}).Select(func(in interface{}) interface{} {
		funcName := in.(string)
		action := ""
		for key, value := range hookNames {
			if strings.HasPrefix(funcName, key) {
				action = value
			}
		}
		signFuncName := funcName
		if strings.HasPrefix(funcName, "__before") {
			signFuncName = "__before_for_" + className
		} else if strings.HasPrefix(funcName, "__after") {
			signFuncName = "__after_for_" + className
		}
		return map[string]string{
			"className": className,
			"action":    action,
			"sign":      signCloudFunc(server.MasterKey, signFuncName, timeStamp()),
		}
	}).Results()

	w.Header().Set("Content-Type", "application/json")
	j, _ := json.MarshalIndent(result, "", "  ")
	w.Write(j)
}

// Run the dev server
func (server *Server) Run() {
	router := mux.NewRouter()

	router.HandleFunc("/", server.indexHandler)
	router.HandleFunc("/__engine/1/appInfo", server.appInfoHandler)
	router.HandleFunc("/__engine/1/functions", server.functionsHandler)
	router.HandleFunc("/__engine/1/classes", server.classesHandler)
	router.HandleFunc("/__engine/1/classes/{className}/actions", server.classActionHandler)
	router.HandleFunc("/{resourceName}", server.resourcesHandler)

	addr := "localhost:" + server.ConsolePort
	logp.Info("Cloud function debug console (if available) is accessible at: http://" + addr)

	go func() {
		server.Errors <- http.ListenAndServe(addr, router)
	}()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	html "html/template"
	"net/smtp"
	text "text/template"

	"github.com/domodwyer/mailyak"
)

const MAIL_TEMPLATE_NAME = "mail_template"
const MAIL_TEMPLATE_HTML = `

This receipt confirms payment of {{.Amount | asMoney}} from {{.Email}} for "{{.Reason}}".

If this information is incorrect, please email acm@umn.edu immediately.

`
const MAIL_TEMPLATE_TEXT = `
This receipt confirms payment of {{.Amount | asMoney}} from {{.Email}} for "{{.Reason}}".

If this information is incorrect, please email acm@umn.edu immediately.
`

var mail_template_html = html.Must(html.New(MAIL_TEMPLATE_NAME).Funcs(html.FuncMap{
	"asMoney": func(cents uint64) string {
		return fmt.Sprintf("$%.2f", float64(cents)/100)
	},
}).Parse(MAIL_TEMPLATE_HTML))
var mail_template_text = text.Must(text.New(MAIL_TEMPLATE_NAME).Funcs(text.FuncMap{
	"asMoney": func(cents uint64) string {
		return fmt.Sprintf("$%.2f", float64(cents)/100)
	},
}).Parse(MAIL_TEMPLATE_TEXT))

func mail(payment Payment) error {
	// Open a connection to the server.
	server := fmt.Sprintf("%s:%s", getenv("SMTP_HOST"), getenv("SMTP_PORT"))
	mail := mailyak.New(server, smtp.PlainAuth("",
		getenv("SMTP_USER"),
		getenv("SMTP_PASS"),
		getenv("SMTP_HOST")))

	// Set up the mail headers.
	mail.From(getenv("SMTP_FROM"))
	mail.FromName("payacmumn")
	mail.To(payment.Email)
	mail.Bcc("acm@umn.edu")
	mail.ReplyTo("acm@umn.edu")
	mail.Subject("Receipt from payacmumn")

	// Render the mail body and return.
	if err := mail_template_html.Execute(mail.HTML(), payment); err != nil {
		return err
	}
	if err := mail_template_text.Execute(mail.Plain(), payment); err != nil {
		return err
	}
	return mail.Send()
}
<commit_msg>Fixes bad mailto link.<commit_after>package main

import (
	"fmt"
	html "html/template"
	"net/smtp"
	text "text/template"

	"github.com/domodwyer/mailyak"
)

const MAIL_TEMPLATE_NAME = "mail_template"
const MAIL_TEMPLATE_HTML = `

This receipt confirms payment of {{.Amount | asMoney}} from {{.Email}} for "{{.Reason}}".

If this information is incorrect, please email acm@umn.edu immediately.

`
const MAIL_TEMPLATE_TEXT = `
This receipt confirms payment of {{.Amount | asMoney}} from {{.Email}} for "{{.Reason}}".

If this information is incorrect, please email acm@umn.edu immediately.
`

var mail_template_html = html.Must(html.New(MAIL_TEMPLATE_NAME).Funcs(html.FuncMap{
	"asMoney": func(cents uint64) string {
		return fmt.Sprintf("$%.2f", float64(cents)/100)
	},
}).Parse(MAIL_TEMPLATE_HTML))
var mail_template_text = text.Must(text.New(MAIL_TEMPLATE_NAME).Funcs(text.FuncMap{
	"asMoney": func(cents uint64) string {
		return fmt.Sprintf("$%.2f", float64(cents)/100)
	},
}).Parse(MAIL_TEMPLATE_TEXT))

func mail(payment Payment) error {
	// Open a connection to the server.
	server := fmt.Sprintf("%s:%s", getenv("SMTP_HOST"), getenv("SMTP_PORT"))
	mail := mailyak.New(server, smtp.PlainAuth("",
		getenv("SMTP_USER"),
		getenv("SMTP_PASS"),
		getenv("SMTP_HOST")))

	// Set up the mail headers.
	mail.From(getenv("SMTP_FROM"))
	mail.FromName("payacmumn")
	mail.To(payment.Email)
	mail.Bcc("acm@umn.edu")
	mail.ReplyTo("acm@umn.edu")
	mail.Subject("Receipt from payacmumn")

	// Render the mail body and return.
	if err := mail_template_html.Execute(mail.HTML(), payment); err != nil {
		return err
	}
	if err := mail_template_text.Execute(mail.Plain(), payment); err != nil {
		return err
	}
	return mail.Send()
}
<|endoftext|>


<commit_before>package consumergroup

import (
	"errors"
	"fmt"
	"reflect"
	"runtime/debug"
	"sync"
	"time"

	"github.com/Shopify/sarama"
	"github.com/meitu/go-zookeeper/zk"
	"github.com/sirupsen/logrus"
)

const (
	cgInit = iota
	cgStart
	cgStopped
)

const (
	restartEvent = 1
)

// ConsumerGroup consume message from Kafka with rebalancing supports
type ConsumerGroup struct {
	name            string
	storage         groupStorage
	topicConsumers  map[string]*topicConsumer
	saramaClients   map[string]sarama.Client
	saramaConsumers map[string]sarama.Consumer

	id          string
	state       int
	wg          sync.WaitGroup
	stopCh      chan struct{}
	triggerCh   chan int
	triggerOnce *sync.Once
	owners      map[string]map[int32]string

	config *Config
	logger *logrus.Logger

	onLoad, onClose []func()
}

// NewConsumerGroup create the ConsumerGroup instance with config
func NewConsumerGroup(config *Config) (*ConsumerGroup, error) {
	if config == nil {
		return nil, errors.New("config can't be empty")
	}
	err := config.validate()
	if err != nil {
		return nil, fmt.Errorf("vaildate config failed, as %s", err)
	}

	cg := new(ConsumerGroup)
	cg.state = cgInit
	cg.config = config
	cg.id = config.ConsumerID
	if cg.id == "" {
		cg.id = genConsumerID()
	}
	cg.name = config.GroupID
	cg.triggerCh = make(chan int)
	cg.topicConsumers = make(map[string]*topicConsumer)
	cg.saramaClients = make(map[string]sarama.Client)
	cg.saramaConsumers = make(map[string]sarama.Consumer)
	cg.onLoad = make([]func(), 0)
	cg.onClose = make([]func(), 0)
	cg.storage = newZKGroupStorage(config.ZkList, config.ZkSessionTimeout)
	cg.logger = logrus.New()
	if _, ok := cg.storage.(*zkGroupStorage); ok {
		cg.storage.(*zkGroupStorage).Chroot(config.Chroot)
	}

	err = cg.initSaramaConsumer()
	if err != nil {
		return nil, fmt.Errorf("init sarama consumer, as %s", err)
	}
	cg.owners = make(map[string]map[int32]string)
	for _, topic := range config.TopicList {
		cg.topicConsumers[topic] = newTopicConsumer(cg, topic)
		cg.owners[topic] = make(map[int32]string)
	}
	return cg, nil
}

func (cg *ConsumerGroup) initSaramaConsumer() error {
	brokerList, err := cg.storage.getBrokerList()
	if err != nil {
		return err
	}
	if len(brokerList) == 0 {
		return errors.New("no broker alive")
	}
	for _, topic := range cg.config.TopicList {
		saramaClient, err := sarama.NewClient(brokerList, cg.config.SaramaConfig)
		if err != nil {
			return err
		}
		saramaConsumer, err := sarama.NewConsumerFromClient(saramaClient)
		if err != nil {
			return err
		}
		cg.saramaClients[topic] = saramaClient
		cg.saramaConsumers[topic] = saramaConsumer
	}
	return nil
}

// Start would register ConsumerGroup, and rebalance would be triggered.
// ConsumerGroup computes the partitions which should be consumed by consumer's num, and start fetching message.
func (cg *ConsumerGroup) Start() error {
	// exit when failed to register the consumer
	err := cg.storage.registerConsumer(cg.name, cg.id, nil)
	if err != nil && err != zk.ErrNodeExists {
		return err
	}
	cg.wg.Add(1)
	go cg.start()
	return nil
}

// Stop would unregister ConsumerGroup, and rebalance would be triggered.
// The partitions which consumed by this ConsumerGroup would be assigned to others.
func (cg *ConsumerGroup) Stop() {
	cg.stop()
	cg.wg.Wait()
}

// SetLogger use to set the user's logger the consumer group
func (cg *ConsumerGroup) SetLogger(l *logrus.Logger) {
	if l != nil {
		cg.logger = l
	}
}

// IsStopped return whether the ConsumerGroup was stopped or not.
func (cg *ConsumerGroup) IsStopped() bool {
	return cg.state == cgStopped
}

func (cg *ConsumerGroup) callRecover() {
	if err := recover(); err != nil {
		cg.logger.WithFields(logrus.Fields{
			"group": cg.name,
			"err":   err,
			"stack": string(debug.Stack()),
		}).Error("Recover panic")
		cg.stop()
	}
}

func (cg *ConsumerGroup) start() {
	var wg sync.WaitGroup

	defer cg.callRecover()
	defer func() {
		cg.state = cgStopped
		err := cg.storage.deleteConsumer(cg.name, cg.id)
		if err != nil {
			cg.logger.WithFields(logrus.Fields{
				"group": cg.name,
				"err":   err,
			}).Error("Failed to delete consumer from zk")
		}
		for _, tc := range cg.topicConsumers {
			close(tc.messages)
			close(tc.errors)
		}
		cg.wg.Done()
	}()

CONSUME_TOPIC_LOOP:
	for {
		cg.logger.WithField("group", cg.name).Info("Consumer group started")
		cg.triggerOnce = new(sync.Once)
		cg.stopCh = make(chan struct{})

		err := cg.watchRebalance()
		if err != nil {
			cg.logger.WithFields(logrus.Fields{
				"group": cg.name,
				"err":   err,
			}).Error("Failed to watch rebalance")
			cg.stop()
			return
		}
		wg.Add(1)
		go func() {
			defer cg.callRecover()
			defer wg.Done()
			cg.autoReconnect(cg.storage.(*zkGroupStorage).sessionTimeout / 3)
		}()
		for _, consumer := range cg.topicConsumers {
			wg.Add(1)
			consumer.start()
			go func(tc *topicConsumer) {
				defer cg.callRecover()
				defer wg.Done()
				tc.wg.Wait()
				cg.logger.WithFields(logrus.Fields{
					"group": tc.group,
					"topic": tc.name,
				}).Info("Stop the topic consumer")
			}(consumer)
		}
		cg.state = cgStart
		for _, onLoadFunc := range cg.onLoad {
			onLoadFunc()
		}
		msg := <-cg.triggerCh
		for _, onCloseFunc := range cg.onClose {
			onCloseFunc()
		}
		switch msg {
		case restartEvent:
			close(cg.stopCh)
			// The stop channel was used to notify partition's consumer to stop consuming when rebalance is triggered.
			// So we should reinit when rebalance was triggered, as it would be closed.
			wg.Wait()
			continue CONSUME_TOPIC_LOOP
		default:
			close(cg.stopCh)
			cg.logger.WithField("group", cg.name).Info("ConsumerGroup is stopping")
			wg.Wait()
			cg.logger.WithField("group", cg.name).Info("ConsumerGroup was stopped")
			return
		}
	}
}

func (cg *ConsumerGroup) stop() {
	cg.triggerOnce.Do(func() { close(cg.triggerCh) })
}

func (cg *ConsumerGroup) triggerRebalance() {
	cg.triggerOnce.Do(func() { cg.triggerCh <- restartEvent })
}

func (cg *ConsumerGroup) getPartitionConsumer(topic string, partition int32, nextOffset int64) (sarama.PartitionConsumer, error) {
	saramaConsumer := cg.saramaConsumers[topic]
	consumer, err := saramaConsumer.ConsumePartition(topic, partition, nextOffset)
	if err == sarama.ErrOffsetOutOfRange {
		nextOffset = cg.config.OffsetAutoReset
		cg.logger.WithFields(logrus.Fields{
			"group":     cg.name,
			"topic":     topic,
			"partition": partition,
			"offset":    nextOffset,
		}).Error("Partition's offset was out of range, use auto-reset")
		consumer, err = saramaConsumer.ConsumePartition(topic, partition, nextOffset)
	}
	return consumer, err
}

// GetMessages was used to get a unbuffered message's channel from specified topic
func (cg *ConsumerGroup) GetMessages(topic string) (<-chan *sarama.ConsumerMessage, bool) {
	if topicConsumer, ok := cg.topicConsumers[topic]; ok {
		return topicConsumer.messages, true
	}
	return nil, false
}

// GetErrors was used to get a unbuffered error's channel from specified topic
func (cg *ConsumerGroup) GetErrors(topic string) (<-chan *sarama.ConsumerError, bool) {
	if topicConsumer, ok := cg.topicConsumers[topic]; ok {
		return topicConsumer.errors, true
	}
	return nil, false
}

// OnLoad load callback function that runs after startup
func (cg *ConsumerGroup) OnLoad(cb func()) {
	cg.onLoad = append(cg.onLoad, cb)
}

// OnClose load callback function that runs before the end
func (cg *ConsumerGroup) OnClose(cb func()) {
	cg.onClose = append(cg.onClose, cb)
}

func (cg *ConsumerGroup) autoReconnect(interval time.Duration) {
	timer := time.NewTimer(interval)
	cg.logger.WithField("group", cg.name).Info("The auto-reconnect consumer thread was started")
	defer cg.logger.WithField("group", cg.name).Info("The auto-reconnect consumer thread was stopped")
	for {
		select {
		case <-cg.stopCh:
			return
		case <-timer.C:
			timer.Reset(interval)
			exist, err := cg.storage.existsConsumer(cg.name, cg.id)
			if err != nil {
				cg.logger.WithFields(logrus.Fields{
					"group": cg.name,
					"err":   err,
				}).Error("Failed to check consumer existence")
				break
			}
			if exist {
				break
			}
			err = cg.storage.registerConsumer(cg.name, cg.id, nil)
			if err != nil {
				cg.logger.WithFields(logrus.Fields{
					"group": cg.name,
					"err":   err,
				}).Error("Failed to re-register consumer")
			}
		}
	}
}

func (cg *ConsumerGroup) watchRebalance() error {
	consumersWatcher, err := cg.storage.watchConsumerList(cg.name)
	if err != nil {
		return err
	}
	topicsChange, topicWatchers, err := cg.watchTopics(cg.config.TopicList)
	if err != nil {
		return err
	}
	go func() {
		defer cg.callRecover()
		cg.logger.WithField("group", cg.name).Info("Rebalance watcher thread was started")
		select {
		case <-consumersWatcher.EvCh:
			cg.triggerRebalance()
			cg.logger.WithField("group", cg.name).Info("Trigger rebalance while consumers was changed")
			for _, tw := range topicWatchers {
				cg.storage.removeWatcher(tw)
			}
		case topic := <-topicsChange:
			cg.triggerRebalance()
			cg.logger.WithFields(logrus.Fields{
				"group": cg.name,
				"topic": topic,
			}).Info("Trigger rebalance while partitions was changed")
			cg.storage.removeWatcher(consumersWatcher)
		case <-cg.stopCh:
		}
		cg.logger.WithField("group", cg.name).Info("Rebalance watcher thread was exited")
	}()
	return nil
}

// CommitOffset is used to commit offset when auto commit was disabled.
func (cg *ConsumerGroup) CommitOffset(topic string, partition int32, offset int64) error {
	if cg.config.OffsetAutoCommitEnable {
		return errors.New("commit offset take effect when offset auto commit was disabled")
	}
	return cg.storage.commitOffset(cg.name, topic, partition, offset)
}

// GetOffsets return the offset in memory for debug
func (cg *ConsumerGroup) GetOffsets() map[string]interface{} {
	topics := make(map[string]interface{})
	for topic, tc := range cg.topicConsumers {
		topics[topic] = tc.getOffsets()
	}
	return topics
}

// Owners return owners of all partitions
func (cg *ConsumerGroup) Owners() map[string]map[int32]string {
	return cg.owners
}

func (cg *ConsumerGroup) watchTopics(topics []string) (<-chan string, []*zk.Watcher, error) {
	ch := make(chan string)
	cases := make([]reflect.SelectCase, len(topics))
	watchers := make([]*zk.Watcher, len(topics))
	for i, topic := range topics {
		w, err := cg.storage.watchTopic(topic)
		if err != nil {
			return nil, nil, err
		}
		watchers[i] = w
		cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(w.EvCh)}
	}
	go func(cases []reflect.SelectCase, ch chan string, topics []string) {
		chosen, _, ok := reflect.Select(cases)
		if !ok {
			//the chosen channel has been closed.
			return
		}
		topic := topics[chosen]
		num, err := cg.storage.getPartitionsNum(topic)
		if err != nil {
			cg.logger.WithFields(logrus.Fields{
				"topic": topic,
				"err":   err,
			}).Error("Failed to get partitions in zookeeper after topic metadata change")
			return
		}
		for {
			saramaClient := cg.saramaClients[topic]
			saramaClient.RefreshMetadata(topic)
			partitions, err := saramaClient.Partitions(topic)
			if err != nil {
				cg.logger.WithFields(logrus.Fields{
					"topic": topic,
					"err":   err,
				}).Error("Failed to get partitions in broker after topic metadata change")
				return
			}
			if len(partitions) == num {
				break
			}
			time.Sleep(100 * time.Millisecond)
		}

		ch <- topics[chosen]
	}(cases, ch, topics)
	return ch, watchers, nil
}
<commit_msg>MOD: make watch rebalance error more clearly<commit_after>package consumergroup

import (
	"errors"
	"fmt"
	"reflect"
	"runtime/debug"
	"sync"
	"time"

	"github.com/Shopify/sarama"
	"github.com/meitu/go-zookeeper/zk"
	"github.com/sirupsen/logrus"
)

const (
	cgInit = iota
	cgStart
	cgStopped
)

const (
	restartEvent = 1
)

// ConsumerGroup consume message from Kafka with rebalancing supports
type ConsumerGroup struct {
	name            string
	storage         groupStorage
	topicConsumers  map[string]*topicConsumer
	saramaClients   map[string]sarama.Client
	saramaConsumers map[string]sarama.Consumer

	id          string
	state       int
	wg          sync.WaitGroup
	stopCh      chan struct{}
	triggerCh   chan int
	triggerOnce *sync.Once
	owners      map[string]map[int32]string

	config *Config
	logger *logrus.Logger

	onLoad, onClose []func()
}

// NewConsumerGroup create the ConsumerGroup instance with config
func NewConsumerGroup(config *Config) (*ConsumerGroup, error) {
	if config == nil {
		return nil, errors.New("config can't be empty")
	}
	err := config.validate()
	if err != nil {
		return nil, fmt.Errorf("vaildate config failed, as %s", err)
	}

	cg := new(ConsumerGroup)
	cg.state = cgInit
	cg.config = config
	cg.id = config.ConsumerID
	if cg.id == "" {
		cg.id = genConsumerID()
	}
	cg.name = config.GroupID
	cg.triggerCh = make(chan int)
	cg.topicConsumers = make(map[string]*topicConsumer)
	cg.saramaClients = make(map[string]sarama.Client)
	cg.saramaConsumers = make(map[string]sarama.Consumer)
	cg.onLoad = make([]func(), 0)
	cg.onClose = make([]func(), 0)
	cg.storage = newZKGroupStorage(config.ZkList, config.ZkSessionTimeout)
	cg.logger = logrus.New()
	if _, ok := cg.storage.(*zkGroupStorage); ok {
		cg.storage.(*zkGroupStorage).Chroot(config.Chroot)
	}

	err = cg.initSaramaConsumer()
	if err != nil {
		return nil, fmt.Errorf("init sarama consumer, as %s", err)
	}
	cg.owners = make(map[string]map[int32]string)
	for _, topic := range config.TopicList {
		cg.topicConsumers[topic] = newTopicConsumer(cg, topic)
		cg.owners[topic] = make(map[int32]string)
	}
	return cg, nil
}

func (cg *ConsumerGroup) initSaramaConsumer() error {
	brokerList, err := cg.storage.getBrokerList()
	if err != nil {
		return err
	}
	if len(brokerList) == 0 {
		return errors.New("no broker alive")
	}
	for _, topic := range cg.config.TopicList {
		saramaClient, err := sarama.NewClient(brokerList, cg.config.SaramaConfig)
		if err != nil {
			return err
		}
		saramaConsumer, err := sarama.NewConsumerFromClient(saramaClient)
		if err != nil {
			return err
		}
		cg.saramaClients[topic] = saramaClient
		cg.saramaConsumers[topic] = saramaConsumer
	}
	return nil
}

// Start would register ConsumerGroup, and rebalance would be triggered.
// ConsumerGroup computes the partitions which should be consumed by consumer's num, and start fetching message.
func (cg *ConsumerGroup) Start() error {
	// exit when failed to register the consumer
	err := cg.storage.registerConsumer(cg.name, cg.id, nil)
	if err != nil && err != zk.ErrNodeExists {
		return err
	}
	cg.wg.Add(1)
	go cg.start()
	return nil
}

// Stop would unregister ConsumerGroup, and rebalance would be triggered.
// The partitions which consumed by this ConsumerGroup would be assigned to others.
func (cg *ConsumerGroup) Stop() {
	cg.stop()
	cg.wg.Wait()
}

// SetLogger use to set the user's logger the consumer group
func (cg *ConsumerGroup) SetLogger(l *logrus.Logger) {
	if l != nil {
		cg.logger = l
	}
}

// IsStopped return whether the ConsumerGroup was stopped or not.
func (cg *ConsumerGroup) IsStopped() bool {
	return cg.state == cgStopped
}

func (cg *ConsumerGroup) callRecover() {
	if err := recover(); err != nil {
		cg.logger.WithFields(logrus.Fields{
			"group": cg.name,
			"err":   err,
			"stack": string(debug.Stack()),
		}).Error("Recover panic")
		cg.stop()
	}
}

func (cg *ConsumerGroup) start() {
	var wg sync.WaitGroup

	defer cg.callRecover()
	defer func() {
		cg.state = cgStopped
		err := cg.storage.deleteConsumer(cg.name, cg.id)
		if err != nil {
			cg.logger.WithFields(logrus.Fields{
				"group": cg.name,
				"err":   err,
			}).Error("Failed to delete consumer from zk")
		}
		for _, tc := range cg.topicConsumers {
			close(tc.messages)
			close(tc.errors)
		}
		cg.wg.Done()
	}()

CONSUME_TOPIC_LOOP:
	for {
		cg.logger.WithField("group", cg.name).Info("Consumer group started")
		cg.triggerOnce = new(sync.Once)
		cg.stopCh = make(chan struct{})

		err := cg.watchRebalance()
		if err != nil {
			cg.logger.WithFields(logrus.Fields{
				"group": cg.name,
				"err":   err,
			}).Error("Failed to watch rebalance")
			cg.stop()
			return
		}
		wg.Add(1)
		go func() {
			defer cg.callRecover()
			defer wg.Done()
			cg.autoReconnect(cg.storage.(*zkGroupStorage).sessionTimeout / 3)
		}()
		for _, consumer := range cg.topicConsumers {
			wg.Add(1)
			consumer.start()
			go func(tc *topicConsumer) {
				defer cg.callRecover()
				defer wg.Done()
				tc.wg.Wait()
				cg.logger.WithFields(logrus.Fields{
					"group": tc.group,
					"topic": tc.name,
				}).Info("Stop the topic consumer")
			}(consumer)
		}
		cg.state = cgStart
		for _, onLoadFunc := range cg.onLoad {
			onLoadFunc()
		}
		msg := <-cg.triggerCh
		for _, onCloseFunc := range cg.onClose {
			onCloseFunc()
		}
		switch msg {
		case restartEvent:
			close(cg.stopCh)
			// The stop channel was used to notify partition's consumer to stop consuming when rebalance is triggered.
			// So we should reinit when rebalance was triggered, as it would be closed.
			wg.Wait()
			continue CONSUME_TOPIC_LOOP
		default:
			close(cg.stopCh)
			cg.logger.WithField("group", cg.name).Info("ConsumerGroup is stopping")
			wg.Wait()
			cg.logger.WithField("group", cg.name).Info("ConsumerGroup was stopped")
			return
		}
	}
}

func (cg *ConsumerGroup) stop() {
	cg.triggerOnce.Do(func() { close(cg.triggerCh) })
}

func (cg *ConsumerGroup) triggerRebalance() {
	cg.triggerOnce.Do(func() { cg.triggerCh <- restartEvent })
}

func (cg *ConsumerGroup) getPartitionConsumer(topic string, partition int32, nextOffset int64) (sarama.PartitionConsumer, error) {
	saramaConsumer := cg.saramaConsumers[topic]
	consumer, err := saramaConsumer.ConsumePartition(topic, partition, nextOffset)
	if err == sarama.ErrOffsetOutOfRange {
		nextOffset = cg.config.OffsetAutoReset
		cg.logger.WithFields(logrus.Fields{
			"group":     cg.name,
			"topic":     topic,
			"partition": partition,
			"offset":    nextOffset,
		}).Error("Partition's offset was out of range, use auto-reset")
		consumer, err = saramaConsumer.ConsumePartition(topic, partition, nextOffset)
	}
	return consumer, err
}

// GetMessages was used to get a unbuffered message's channel from specified topic
func (cg *ConsumerGroup) GetMessages(topic string) (<-chan *sarama.ConsumerMessage, bool) {
	if topicConsumer, ok := cg.topicConsumers[topic]; ok {
		return topicConsumer.messages, true
	}
	return nil, false
}

// GetErrors was used to get a unbuffered error's channel from specified topic
func (cg *ConsumerGroup) GetErrors(topic string) (<-chan *sarama.ConsumerError, bool) {
	if topicConsumer, ok := cg.topicConsumers[topic]; ok {
		return topicConsumer.errors, true
	}
	return nil, false
}

// OnLoad load callback function that runs after startup
func (cg *ConsumerGroup) OnLoad(cb func()) {
	cg.onLoad = append(cg.onLoad, cb)
}

// OnClose load callback function that runs before the end
func (cg *ConsumerGroup) OnClose(cb func()) {
	cg.onClose = append(cg.onClose, cb)
}

func (cg *ConsumerGroup) autoReconnect(interval time.Duration) {
	timer := time.NewTimer(interval)
	cg.logger.WithField("group", cg.name).Info("The auto-reconnect consumer thread was started")
	defer cg.logger.WithField("group", cg.name).Info("The auto-reconnect consumer thread was stopped")
	for {
		select {
		case <-cg.stopCh:
			return
		case <-timer.C:
			timer.Reset(interval)
			exist, err := cg.storage.existsConsumer(cg.name, cg.id)
			if err != nil {
				cg.logger.WithFields(logrus.Fields{
					"group": cg.name,
					"err":   err,
				}).Error("Failed to check consumer existence")
				break
			}
			if exist {
				break
			}
			err = cg.storage.registerConsumer(cg.name, cg.id, nil)
			if err != nil {
				cg.logger.WithFields(logrus.Fields{
					"group": cg.name,
					"err":   err,
				}).Error("Failed to re-register consumer")
			}
		}
	}
}

func (cg *ConsumerGroup) watchRebalance() error {
	consumersWatcher, err := cg.storage.watchConsumerList(cg.name)
	if err != nil {
		return err
	}
	topicsChange, topicWatchers, err := cg.watchTopics(cg.config.TopicList)
	if err != nil {
		return err
	}
	go func() {
		defer cg.callRecover()
		cg.logger.WithField("group", cg.name).Info("Rebalance watcher thread was started")
		select {
		case <-consumersWatcher.EvCh:
			cg.triggerRebalance()
			cg.logger.WithField("group", cg.name).Info("Trigger rebalance while consumers was changed")
			for _, tw := range topicWatchers {
				cg.storage.removeWatcher(tw)
			}
		case topic := <-topicsChange:
			cg.triggerRebalance()
			cg.logger.WithFields(logrus.Fields{
				"group": cg.name,
				"topic": topic,
			}).Info("Trigger rebalance while partitions was changed")
			cg.storage.removeWatcher(consumersWatcher)
		case <-cg.stopCh:
		}
		cg.logger.WithField("group", cg.name).Info("Rebalance watcher thread was exited")
	}()
	return nil
}

// CommitOffset is used to commit offset when auto commit was disabled.
func (cg *ConsumerGroup) CommitOffset(topic string, partition int32, offset int64) error {
	if cg.config.OffsetAutoCommitEnable {
		return errors.New("commit offset take effect when offset auto commit was disabled")
	}
	return cg.storage.commitOffset(cg.name, topic, partition, offset)
}

// GetOffsets return the offset in memory for debug
func (cg *ConsumerGroup) GetOffsets() map[string]interface{} {
	topics := make(map[string]interface{})
	for topic, tc := range cg.topicConsumers {
		topics[topic] = tc.getOffsets()
	}
	return topics
}

// Owners return owners of all partitions
func (cg *ConsumerGroup) Owners() map[string]map[int32]string {
	return cg.owners
}

func (cg *ConsumerGroup) watchTopics(topics []string) (<-chan string, []*zk.Watcher, error) {
	ch := make(chan string)
	cases := make([]reflect.SelectCase, len(topics))
	watchers := make([]*zk.Watcher, len(topics))
	for i, topic := range topics {
		w, err := cg.storage.watchTopic(topic)
		if err != nil {
			return nil, nil, fmt.Errorf("encounter error when watch topic: %s, err: %s", topic, err)
		}
		watchers[i] = w
		cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(w.EvCh)}
	}
	go func(cases []reflect.SelectCase, ch chan string, topics []string) {
		chosen, _, ok := reflect.Select(cases)
		if !ok {
			//the chosen channel has been closed.
			return
		}
		topic := topics[chosen]
		num, err := cg.storage.getPartitionsNum(topic)
		if err != nil {
			cg.logger.WithFields(logrus.Fields{
				"topic": topic,
				"err":   err,
			}).Error("Failed to get partitions in zookeeper after topic metadata change")
			return
		}
		for {
			saramaClient := cg.saramaClients[topic]
			saramaClient.RefreshMetadata(topic)
			partitions, err := saramaClient.Partitions(topic)
			if err != nil {
				cg.logger.WithFields(logrus.Fields{
					"topic": topic,
					"err":   err,
				}).Error("Failed to get partitions in broker after topic metadata change")
				return
			}
			if len(partitions) == num {
				break
			}
			time.Sleep(100 * time.Millisecond)
		}

		ch <- topics[chosen]
	}(cases, ch, topics)
	return ch, watchers, nil
}
<|endoftext|>


<commit_before>/*
   Copyright The containerd Authors.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package containerd

import (
	"context"

	"github.com/containerd/containerd/containers"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/oci"
	"github.com/containerd/containerd/snapshots"
	"github.com/containerd/typeurl"
	"github.com/gogo/protobuf/types"
	"github.com/opencontainers/image-spec/identity"
	"github.com/pkg/errors"
)

// DeleteOpts allows the caller to set options for the deletion of a container
type DeleteOpts func(ctx context.Context, client *Client, c containers.Container) error

// NewContainerOpts allows the caller to set additional options when creating a container
type NewContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error

// UpdateContainerOpts allows the caller to set additional options when updating a container
type UpdateContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error

// InfoOpts controls how container metadata is fetched and returned
type InfoOpts func(*InfoConfig)

// InfoConfig specifies how container metadata is fetched
type InfoConfig struct {
	// Refresh will to a fetch of the latest container metadata
	Refresh bool
}

// WithRuntime allows a user to specify the runtime name and additional options that should
// be used to create tasks for the container
func WithRuntime(name string, options interface{}) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		var (
			any *types.Any
			err error
		)
		if options != nil {
			any, err = typeurl.MarshalAny(options)
			if err != nil {
				return err
			}
		}
		c.Runtime = containers.RuntimeInfo{
			Name:    name,
			Options: any,
		}
		return nil
	}
}

// WithImage sets the provided image as the base for the container
func WithImage(i Image) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		c.Image = i.Name()
		return nil
	}
}

// WithImageName allows setting the image name as the base for the container
func WithImageName(n string) NewContainerOpts {
	return func(ctx context.Context, _ *Client, c *containers.Container) error {
		c.Image = n
		return nil
	}
}

// WithContainerLabels adds the provided labels to the container
func WithContainerLabels(labels map[string]string) NewContainerOpts {
	return func(_ context.Context, _ *Client, c *containers.Container) error {
		c.Labels = labels
		return nil
	}
}

// WithImageStopSignal sets a well-known containerd label (StopSignalLabel)
// on the container for storing the stop signal specified in the OCI image
// config
func WithImageStopSignal(image Image, defaultSignal string) NewContainerOpts {
	return func(ctx context.Context, _ *Client, c *containers.Container) error {
		if c.Labels == nil {
			c.Labels = make(map[string]string)
		}
		stopSignal, err := GetOCIStopSignal(ctx, image, defaultSignal)
		if err != nil {
			return err
		}
		c.Labels[StopSignalLabel] = stopSignal
		return nil
	}
}

// WithSnapshotter sets the provided snapshotter for use by the container
//
// This option must appear before other snapshotter options to have an effect.
func WithSnapshotter(name string) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		c.Snapshotter = name
		return nil
	}
}

// WithSnapshot uses an existing root filesystem for the container
func WithSnapshot(id string) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		// check that the snapshot exists, if not, fail on creation
		var err error
		c.Snapshotter, err = client.resolveSnapshotterName(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		s, err := client.getSnapshotter(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		if _, err := s.Mounts(ctx, id); err != nil {
			return err
		}
		c.SnapshotKey = id
		return nil
	}
}

// WithNewSnapshot allocates a new snapshot to be used by the container as the
// root filesystem in read-write mode
func WithNewSnapshot(id string, i Image, opts ...snapshots.Opt) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		diffIDs, err := i.RootFS(ctx)
		if err != nil {
			return err
		}

		parent := identity.ChainID(diffIDs).String()
		c.Snapshotter, err = client.resolveSnapshotterName(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		s, err := client.getSnapshotter(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		if _, err := s.Prepare(ctx, id, parent, opts...); err != nil {
			return err
		}
		c.SnapshotKey = id
		c.Image = i.Name()
		return nil
	}
}

// WithSnapshotCleanup deletes the rootfs snapshot allocated for the container
func WithSnapshotCleanup(ctx context.Context, client *Client, c containers.Container) error {
	if c.SnapshotKey != "" {
		if c.Snapshotter == "" {
			return errors.Wrapf(errdefs.ErrInvalidArgument, "container.Snapshotter must be set to cleanup rootfs snapshot")
		}
		s, err := client.getSnapshotter(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		if err := s.Remove(ctx, c.SnapshotKey); err != nil && !errdefs.IsNotFound(err) {
			return err
		}
	}
	return nil
}

// WithNewSnapshotView allocates a new snapshot to be used by the container as the
// root filesystem in read-only mode
func WithNewSnapshotView(id string, i Image, opts ...snapshots.Opt) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), client.platform)
		if err != nil {
			return err
		}

		parent := identity.ChainID(diffIDs).String()
		c.Snapshotter, err = client.resolveSnapshotterName(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		s, err := client.getSnapshotter(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		if _, err := s.View(ctx, id, parent, opts...); err != nil {
			return err
		}
		c.SnapshotKey = id
		c.Image = i.Name()
		return nil
	}
}

// WithContainerExtension appends extension data to the container object.
// Use this to decorate the container object with additional data for the client
// integration.
//
// Make sure to register the type of `extension` in the typeurl package via
// `typeurl.Register` or container creation may fail.
func WithContainerExtension(name string, extension interface{}) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		if name == "" {
			return errors.Wrapf(errdefs.ErrInvalidArgument, "extension key must not be zero-length")
		}

		any, err := typeurl.MarshalAny(extension)
		if err != nil {
			if errors.Is(err, typeurl.ErrNotFound) {
				return errors.Wrapf(err, "extension %q is not registered with the typeurl package, see `typeurl.Register`", name)
			}
			return errors.Wrap(err, "error marshalling extension")
		}

		if c.Extensions == nil {
			c.Extensions = make(map[string]types.Any)
		}
		c.Extensions[name] = *any
		return nil
	}
}

// WithNewSpec generates a new spec for a new container
func WithNewSpec(opts ...oci.SpecOpts) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		s, err := oci.GenerateSpec(ctx, client, c, opts...)
		if err != nil {
			return err
		}
		c.Spec, err = typeurl.MarshalAny(s)
		return err
	}
}

// WithSpec sets the provided spec on the container
func WithSpec(s *oci.Spec, opts ...oci.SpecOpts) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		if err := oci.ApplyOpts(ctx, client, c, s, opts...); err != nil {
			return err
		}

		var err error
		c.Spec, err = typeurl.MarshalAny(s)
		return err
	}
}

// WithoutRefreshedMetadata will use the current metadata attached to the container object
func WithoutRefreshedMetadata(i *InfoConfig) {
	i.Refresh = false
}
<commit_msg>container_opts.go: add WithAdditionalContainerLabels<commit_after>/*
   Copyright The containerd Authors.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package containerd

import (
	"context"

	"github.com/containerd/containerd/containers"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/oci"
	"github.com/containerd/containerd/snapshots"
	"github.com/containerd/typeurl"
	"github.com/gogo/protobuf/types"
	"github.com/opencontainers/image-spec/identity"
	"github.com/pkg/errors"
)

// DeleteOpts allows the caller to set options for the deletion of a container
type DeleteOpts func(ctx context.Context, client *Client, c containers.Container) error

// NewContainerOpts allows the caller to set additional options when creating a container
type NewContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error

// UpdateContainerOpts allows the caller to set additional options when updating a container
type UpdateContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error

// InfoOpts controls how container metadata is fetched and returned
type InfoOpts func(*InfoConfig)

// InfoConfig specifies how container metadata is fetched
type InfoConfig struct {
	// Refresh will to a fetch of the latest container metadata
	Refresh bool
}

// WithRuntime allows a user to specify the runtime name and additional options that should
// be used to create tasks for the container
func WithRuntime(name string, options interface{}) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		var (
			any *types.Any
			err error
		)
		if options != nil {
			any, err = typeurl.MarshalAny(options)
			if err != nil {
				return err
			}
		}
		c.Runtime = containers.RuntimeInfo{
			Name:    name,
			Options: any,
		}
		return nil
	}
}

// WithImage sets the provided image as the base for the container
func WithImage(i Image) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		c.Image = i.Name()
		return nil
	}
}

// WithImageName allows setting the image name as the base for the container
func WithImageName(n string) NewContainerOpts {
	return func(ctx context.Context, _ *Client, c *containers.Container) error {
		c.Image = n
		return nil
	}
}

// WithContainerLabels sets the provided labels to the container.
// The existing labels are cleared.
// Use WithAdditionalContainerLabels to preserve the existing labels.
func WithContainerLabels(labels map[string]string) NewContainerOpts {
	return func(_ context.Context, _ *Client, c *containers.Container) error {
		c.Labels = labels
		return nil
	}
}

// WithAdditionalContainerLabels adds the provided labels to the container
// The existing labels are preserved as long as they do not conflict with the added labels.
func WithAdditionalContainerLabels(labels map[string]string) NewContainerOpts {
	return func(_ context.Context, _ *Client, c *containers.Container) error {
		if c.Labels == nil {
			c.Labels = labels
			return nil
		}
		for k, v := range labels {
			c.Labels[k] = v
		}
		return nil
	}
}

// WithImageStopSignal sets a well-known containerd label (StopSignalLabel)
// on the container for storing the stop signal specified in the OCI image
// config
func WithImageStopSignal(image Image, defaultSignal string) NewContainerOpts {
	return func(ctx context.Context, _ *Client, c *containers.Container) error {
		if c.Labels == nil {
			c.Labels = make(map[string]string)
		}
		stopSignal, err := GetOCIStopSignal(ctx, image, defaultSignal)
		if err != nil {
			return err
		}
		c.Labels[StopSignalLabel] = stopSignal
		return nil
	}
}

// WithSnapshotter sets the provided snapshotter for use by the container
//
// This option must appear before other snapshotter options to have an effect.
func WithSnapshotter(name string) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		c.Snapshotter = name
		return nil
	}
}

// WithSnapshot uses an existing root filesystem for the container
func WithSnapshot(id string) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		// check that the snapshot exists, if not, fail on creation
		var err error
		c.Snapshotter, err = client.resolveSnapshotterName(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		s, err := client.getSnapshotter(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		if _, err := s.Mounts(ctx, id); err != nil {
			return err
		}
		c.SnapshotKey = id
		return nil
	}
}

// WithNewSnapshot allocates a new snapshot to be used by the container as the
// root filesystem in read-write mode
func WithNewSnapshot(id string, i Image, opts ...snapshots.Opt) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		diffIDs, err := i.RootFS(ctx)
		if err != nil {
			return err
		}

		parent := identity.ChainID(diffIDs).String()
		c.Snapshotter, err = client.resolveSnapshotterName(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		s, err := client.getSnapshotter(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		if _, err := s.Prepare(ctx, id, parent, opts...); err != nil {
			return err
		}
		c.SnapshotKey = id
		c.Image = i.Name()
		return nil
	}
}

// WithSnapshotCleanup deletes the rootfs snapshot allocated for the container
func WithSnapshotCleanup(ctx context.Context, client *Client, c containers.Container) error {
	if c.SnapshotKey != "" {
		if c.Snapshotter == "" {
			return errors.Wrapf(errdefs.ErrInvalidArgument, "container.Snapshotter must be set to cleanup rootfs snapshot")
		}
		s, err := client.getSnapshotter(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		if err := s.Remove(ctx, c.SnapshotKey); err != nil && !errdefs.IsNotFound(err) {
			return err
		}
	}
	return nil
}

// WithNewSnapshotView allocates a new snapshot to be used by the container as the
// root filesystem in read-only mode
func WithNewSnapshotView(id string, i Image, opts ...snapshots.Opt) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), client.platform)
		if err != nil {
			return err
		}

		parent := identity.ChainID(diffIDs).String()
		c.Snapshotter, err = client.resolveSnapshotterName(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		s, err := client.getSnapshotter(ctx, c.Snapshotter)
		if err != nil {
			return err
		}
		if _, err := s.View(ctx, id, parent, opts...); err != nil {
			return err
		}
		c.SnapshotKey = id
		c.Image = i.Name()
		return nil
	}
}

// WithContainerExtension appends extension data to the container object.
// Use this to decorate the container object with additional data for the client
// integration.
//
// Make sure to register the type of `extension` in the typeurl package via
// `typeurl.Register` or container creation may fail.
func WithContainerExtension(name string, extension interface{}) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		if name == "" {
			return errors.Wrapf(errdefs.ErrInvalidArgument, "extension key must not be zero-length")
		}

		any, err := typeurl.MarshalAny(extension)
		if err != nil {
			if errors.Is(err, typeurl.ErrNotFound) {
				return errors.Wrapf(err, "extension %q is not registered with the typeurl package, see `typeurl.Register`", name)
			}
			return errors.Wrap(err, "error marshalling extension")
		}

		if c.Extensions == nil {
			c.Extensions = make(map[string]types.Any)
		}
		c.Extensions[name] = *any
		return nil
	}
}

// WithNewSpec generates a new spec for a new container
func WithNewSpec(opts ...oci.SpecOpts) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		s, err := oci.GenerateSpec(ctx, client, c, opts...)
		if err != nil {
			return err
		}
		c.Spec, err = typeurl.MarshalAny(s)
		return err
	}
}

// WithSpec sets the provided spec on the container
func WithSpec(s *oci.Spec, opts ...oci.SpecOpts) NewContainerOpts {
	return func(ctx context.Context, client *Client, c *containers.Container) error {
		if err := oci.ApplyOpts(ctx, client, c, s, opts...); err != nil {
			return err
		}

		var err error
		c.Spec, err = typeurl.MarshalAny(s)
		return err
	}
}

// WithoutRefreshedMetadata will use the current metadata attached to the container object
func WithoutRefreshedMetadata(i *InfoConfig) {
	i.Refresh = false
}
<|endoftext|>


<commit_before>// Copyright 2015 RedHat, Inc.
// Copyright 2015 CoreOS, Inc.
//
// 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 sdjournal

import (
	"errors"
	"fmt"
	"io"
	"log"
	"time"
)

var (
	ErrExpired = errors.New("Timeout expired")
)

// JournalReaderConfig represents options to drive the behavior of a JournalReader.
type JournalReaderConfig struct {
	// The Since and NumFromTail options are mutually exclusive and determine
	// where the reading begins within the journal.
	Since       time.Duration // start relative to a Duration from now
	NumFromTail uint64        // start relative to the tail

	// Show only journal entries whose fields match the supplied values. If
	// the array is empty, entries will not be filtered.
	Matches []Match
}

// JournalReader is an io.ReadCloser which provides a simple interface for iterating through the
// systemd journal.
type JournalReader struct {
	journal *Journal
}

// NewJournalReader creates a new JournalReader with configuration options that are similar to the
// systemd journalctl tool's iteration and filtering features.
func NewJournalReader(config JournalReaderConfig) (*JournalReader, error) {
	r := &JournalReader{}

	var err error
	// Open the journal
	if r.journal, err = NewJournal(); err != nil {
		return nil, err
	}

	// Add any supplied matches
	for _, m := range config.Matches {
		r.journal.AddMatch(m.String())
	}

	// Set the start position based on options
	if config.Since != 0 {
		// Start based on a relative time
		start := time.Now().Add(config.Since)
		if err := r.journal.SeekRealtimeUsec(uint64(start.UnixNano() / 1000)); err != nil {
			return nil, err
		}
	} else if config.NumFromTail != 0 {
		// Start based on a number of lines before the tail
		if err := r.journal.SeekTail(); err != nil {
			return nil, err
		}

		// Move the read pointer into position near the tail. Go one further than
		// the option so that the initial cursor advancement positions us at the
		// correct starting point.
		if _, err := r.journal.PreviousSkip(config.NumFromTail + 1); err != nil {
			return nil, err
		}
	}

	return r, nil
}

func (r *JournalReader) Read(b []byte) (int, error) {
	var err error
	var c int

	// Advance the journal cursor
	c, err = r.journal.Next()

	// An unexpected error
	if err != nil {
		return 0, err
	}

	// EOF detection
	if c == 0 {
		return 0, io.EOF
	}

	// Build a message
	var msg string
	msg, err = r.buildMessage()

	if err != nil {
		return 0, err
	}

	// Copy and return the message
	copy(b, []byte(msg))

	return len(msg), nil
}

func (r *JournalReader) Close() error {
	return r.journal.Close()
}

// Follow synchronously follows the JournalReader, writing each new journal entry to writer. The
// follow will continue until a single time.Time is received on the until channel.
func (r *JournalReader) Follow(until <-chan time.Time, writer io.Writer) (err error) {

	// Process journal entries and events. Entries are flushed until the tail or
	// timeout is reached, and then we wait for new events or the timeout.
process:
	for {
		var msg = make([]byte, 64*1<<(10))

		c, err := r.Read(msg)
		if err != nil && err != io.EOF {
			break process
		}

		select {
		case <-until:
			return ErrExpired
		default:
			if c > 0 {
				writer.Write(msg)
				continue process
			}
		}

		// We're at the tail, so wait for new events or time out.
		// Holds journal events to process. Tightly bounded for now unless there's a
		// reason to unblock the journal watch routine more quickly.
		events := make(chan int, 1)
		pollDone := make(chan bool, 1)
		go func() {
			for {
				select {
				case <-pollDone:
					return
				default:
					events <- r.journal.Wait(time.Duration(1) * time.Second)
				}
			}
		}()

		select {
		case <-until:
			pollDone <- true
			return ErrExpired
		case e := <-events:
			pollDone <- true
			switch e {
			case SD_JOURNAL_NOP, SD_JOURNAL_APPEND, SD_JOURNAL_INVALIDATE:
				// TODO: need to account for any of these?
			default:
				log.Printf("Received unknown event: %d\n", e)
			}
			continue process
		}
	}

	return
}

// buildMessage returns a string representing the current journal entry in a simple format which
// includes the entry timestamp and MESSAGE field.
func (r *JournalReader) buildMessage() (string, error) {
	var msg string
	var usec uint64
	var err error

	if msg, err = r.journal.GetData("MESSAGE"); err != nil {
		return "", err
	}

	if usec, err = r.journal.GetRealtimeUsec(); err != nil {
		return "", err
	}

	timestamp := time.Unix(0, int64(usec)*int64(time.Microsecond))

	return fmt.Sprintf("%s %s\n", timestamp, msg), nil
}
<commit_msg>sdjournal: Add 'Path' in config to let NewJournalReader() select journal dir.<commit_after>// Copyright 2015 RedHat, Inc.
// Copyright 2015 CoreOS, Inc.
//
// 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 sdjournal

import (
	"errors"
	"fmt"
	"io"
	"log"
	"time"
)

var (
	ErrExpired = errors.New("Timeout expired")
)

// JournalReaderConfig represents options to drive the behavior of a JournalReader.
type JournalReaderConfig struct {
	// The Since and NumFromTail options are mutually exclusive and determine
	// where the reading begins within the journal.
	Since       time.Duration // start relative to a Duration from now
	NumFromTail uint64        // start relative to the tail

	// Show only journal entries whose fields match the supplied values. If
	// the array is empty, entries will not be filtered.
	Matches []Match

	// If not empty, the journal instance will point to a journal residing
	// in this directory. The supplied path may be relative or absolute.
	Path string
}

// JournalReader is an io.ReadCloser which provides a simple interface for iterating through the
// systemd journal.
type JournalReader struct {
	journal *Journal
}

// NewJournalReader creates a new JournalReader with configuration options that are similar to the
// systemd journalctl tool's iteration and filtering features.
func NewJournalReader(config JournalReaderConfig) (*JournalReader, error) {
	r := &JournalReader{}

	// Open the journal
	var err error
	if config.Path != "" {
		r.journal, err = NewJournalFromDir(config.Path)
	} else {
		r.journal, err = NewJournal()
	}
	if err != nil {
		return nil, err
	}

	// Add any supplied matches
	for _, m := range config.Matches {
		r.journal.AddMatch(m.String())
	}

	// Set the start position based on options
	if config.Since != 0 {
		// Start based on a relative time
		start := time.Now().Add(config.Since)
		if err := r.journal.SeekRealtimeUsec(uint64(start.UnixNano() / 1000)); err != nil {
			return nil, err
		}
	} else if config.NumFromTail != 0 {
		// Start based on a number of lines before the tail
		if err := r.journal.SeekTail(); err != nil {
			return nil, err
		}

		// Move the read pointer into position near the tail. Go one further than
		// the option so that the initial cursor advancement positions us at the
		// correct starting point.
		if _, err := r.journal.PreviousSkip(config.NumFromTail + 1); err != nil {
			return nil, err
		}
	}

	return r, nil
}

func (r *JournalReader) Read(b []byte) (int, error) {
	var err error
	var c int

	// Advance the journal cursor
	c, err = r.journal.Next()

	// An unexpected error
	if err != nil {
		return 0, err
	}

	// EOF detection
	if c == 0 {
		return 0, io.EOF
	}

	// Build a message
	var msg string
	msg, err = r.buildMessage()

	if err != nil {
		return 0, err
	}

	// Copy and return the message
	copy(b, []byte(msg))

	return len(msg), nil
}

func (r *JournalReader) Close() error {
	return r.journal.Close()
}

// Follow synchronously follows the JournalReader, writing each new journal entry to writer. The
// follow will continue until a single time.Time is received on the until channel.
func (r *JournalReader) Follow(until <-chan time.Time, writer io.Writer) (err error) {

	// Process journal entries and events. Entries are flushed until the tail or
	// timeout is reached, and then we wait for new events or the timeout.
process:
	for {
		var msg = make([]byte, 64*1<<(10))

		c, err := r.Read(msg)
		if err != nil && err != io.EOF {
			break process
		}

		select {
		case <-until:
			return ErrExpired
		default:
			if c > 0 {
				writer.Write(msg)
				continue process
			}
		}

		// We're at the tail, so wait for new events or time out.
		// Holds journal events to process. Tightly bounded for now unless there's a
		// reason to unblock the journal watch routine more quickly.
		events := make(chan int, 1)
		pollDone := make(chan bool, 1)
		go func() {
			for {
				select {
				case <-pollDone:
					return
				default:
					events <- r.journal.Wait(time.Duration(1) * time.Second)
				}
			}
		}()

		select {
		case <-until:
			pollDone <- true
			return ErrExpired
		case e := <-events:
			pollDone <- true
			switch e {
			case SD_JOURNAL_NOP, SD_JOURNAL_APPEND, SD_JOURNAL_INVALIDATE:
				// TODO: need to account for any of these?
			default:
				log.Printf("Received unknown event: %d\n", e)
			}
			continue process
		}
	}

	return
}

// buildMessage returns a string representing the current journal entry in a simple format which
// includes the entry timestamp and MESSAGE field.
func (r *JournalReader) buildMessage() (string, error) {
	var msg string
	var usec uint64
	var err error

	if msg, err = r.journal.GetData("MESSAGE"); err != nil {
		return "", err
	}

	if usec, err = r.journal.GetRealtimeUsec(); err != nil {
		return "", err
	}

	timestamp := time.Unix(0, int64(usec)*int64(time.Microsecond))

	return fmt.Sprintf("%s %s\n", timestamp, msg), nil
}
<|endoftext|>


<commit_before>// +build integration

/*
Copyright 2018 Google LLC

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 integration

import (
	"flag"
	"os"
	"os/exec"
	"testing"
	"time"

	kubernetesutil "github.com/GoogleCloudPlatform/skaffold/pkg/skaffold/kubernetes"
	"github.com/GoogleCloudPlatform/skaffold/pkg/skaffold/util"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"

	"github.com/sirupsen/logrus"
)

var gkeZone = flag.String("gke-zone", "us-central1-a", "gke zone")
var gkeClusterName = flag.String("gke-cluster-name", "integration-tests", "name of the integration test cluster")
var gcpProject = flag.String("gcp-project", "k8s-skaffold", "the gcp project where the integration test cluster lives")
var remote = flag.Bool("remote", false, "if true, run tests on a remote GKE cluster")

var client kubernetes.Interface

func TestMain(m *testing.M) {
	flag.Parse()
	if *remote {
		cmd := exec.Command("gcloud", "container", "clusters", "get-credentials", *gkeClusterName, "--zone", *gkeZone, "--project", *gcpProject)
		if stdout, stderr, err := util.RunCommand(cmd, nil); err != nil {
			logrus.Fatalf("Error authenticating to GKE cluster stdout: %s, stderr: %s, err: %s", stdout, stderr, err)
		}
	}

	var err error
	client, err = kubernetesutil.GetClientset()
	if err != nil {
		logrus.Fatalf("Test setup error: getting kubernetes client: %s", err)
	}

	os.Exit(m.Run())
}

func TestRun(t *testing.T) {
	type testObject struct {
		name      string
		namespace string
	}

	type testRunCase struct {
		description string
		dir         string
		extraArgs   []string
		deployments []testObject
		pods        []testObject

		remoteOnly bool
	}

	var testCases = []testRunCase{
		{
			description: "getting-started example",
			pods: []testObject{
				{
					name:      "getting-started",
					namespace: "default",
				},
			},
			dir: "../examples/getting-started",
		},
		{
			description: "no manifest example",
			deployments: []testObject{
				{
					name:      "skaffold",
					namespace: "default",
				},
			},
			dir: "../examples/no-manifest",
		},
		{
			description: "annotated getting-started example",
			pods: []testObject{
				{
					name:      "getting-started",
					namespace: "default",
				},
			},
			dir:       "../examples",
			extraArgs: []string{"-f", "annotated-skaffold.yaml"},
		},
		// // Don't run this test for now. It takes awhile to download all the
		// // dependencies
		// {
		// 	description: "repository root skaffold.yaml",
		// 	pods: []testObject{
		// 		{
		// 			name:      "skaffold",
		// 			namespace: "default",
		// 		},
		// 	},
		// 	dir: "../",
		// },
		{
			description: "gcb builder example",
			pods: []testObject{
				{
					name:      "getting-started",
					namespace: "default",
				},
			},
			dir:        "../examples/getting-started",
			extraArgs:  []string{"-f", "skaffold-gcb.yaml"},
			remoteOnly: true,
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.description, func(t *testing.T) {
			if !*remote && testCase.remoteOnly {
				t.Skip("skipping remote only test")
			}
			args := []string{"run"}
			args = append(args, testCase.extraArgs...)
			cmd := exec.Command("skaffold", args...)
			cmd.Dir = testCase.dir
			out, outerr, err := util.RunCommand(cmd, nil)
			if err != nil {
				t.Fatalf("skaffold run: \nstdout: %s\nstderr: %s\nerror: %s", out, outerr, err)
			}

			for _, p := range testCase.pods {
				if err := kubernetesutil.WaitForPodReady(client.CoreV1().Pods(p.namespace), p.name); err != nil {
					t.Fatalf("Timed out waiting for pod ready")
				}
				if err := client.CoreV1().Pods("default").Delete(p.name, nil); err != nil {
					t.Fatalf("Error deleting pod %s: %s", p, err)
				}
			}

			for _, d := range testCase.deployments {
				if err := kubernetesutil.WaitForDeploymentToStabilize(client, d.namespace, d.name, 10*time.Minute); err != nil {
					t.Fatalf("Timed out waiting for deployment to stabilize")
				}
				if err := client.AppsV1().Deployments(d.namespace).Delete(d.name, &meta_v1.DeleteOptions{}); err != nil {
					t.Fatalf("Error deleting deployment %s: %s", d, err)
				}
			}
		})
	}
}
<commit_msg>Don’t hardcode namespace<commit_after>// +build integration

/*
Copyright 2018 Google LLC

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 integration

import (
	"flag"
	"os"
	"os/exec"
	"testing"
	"time"

	kubernetesutil "github.com/GoogleCloudPlatform/skaffold/pkg/skaffold/kubernetes"
	"github.com/GoogleCloudPlatform/skaffold/pkg/skaffold/util"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"

	"github.com/sirupsen/logrus"
)

var gkeZone = flag.String("gke-zone", "us-central1-a", "gke zone")
var gkeClusterName = flag.String("gke-cluster-name", "integration-tests", "name of the integration test cluster")
var gcpProject = flag.String("gcp-project", "k8s-skaffold", "the gcp project where the integration test cluster lives")
var remote = flag.Bool("remote", false, "if true, run tests on a remote GKE cluster")

var client kubernetes.Interface

func TestMain(m *testing.M) {
	flag.Parse()
	if *remote {
		cmd := exec.Command("gcloud", "container", "clusters", "get-credentials", *gkeClusterName, "--zone", *gkeZone, "--project", *gcpProject)
		if stdout, stderr, err := util.RunCommand(cmd, nil); err != nil {
			logrus.Fatalf("Error authenticating to GKE cluster stdout: %s, stderr: %s, err: %s", stdout, stderr, err)
		}
	}

	var err error
	client, err = kubernetesutil.GetClientset()
	if err != nil {
		logrus.Fatalf("Test setup error: getting kubernetes client: %s", err)
	}

	os.Exit(m.Run())
}

func TestRun(t *testing.T) {
	type testObject struct {
		name      string
		namespace string
	}

	type testRunCase struct {
		description string
		dir         string
		extraArgs   []string
		deployments []testObject
		pods        []testObject

		remoteOnly bool
	}

	var testCases = []testRunCase{
		{
			description: "getting-started example",
			pods: []testObject{
				{
					name:      "getting-started",
					namespace: "default",
				},
			},
			dir: "../examples/getting-started",
		},
		{
			description: "no manifest example",
			deployments: []testObject{
				{
					name:      "skaffold",
					namespace: "default",
				},
			},
			dir: "../examples/no-manifest",
		},
		{
			description: "annotated getting-started example",
			pods: []testObject{
				{
					name:      "getting-started",
					namespace: "default",
				},
			},
			dir:       "../examples",
			extraArgs: []string{"-f", "annotated-skaffold.yaml"},
		},
		// // Don't run this test for now. It takes awhile to download all the
		// // dependencies
		// {
		// 	description: "repository root skaffold.yaml",
		// 	pods: []testObject{
		// 		{
		// 			name:      "skaffold",
		// 			namespace: "default",
		// 		},
		// 	},
		// 	dir: "../",
		// },
		{
			description: "gcb builder example",
			pods: []testObject{
				{
					name:      "getting-started",
					namespace: "default",
				},
			},
			dir:        "../examples/getting-started",
			extraArgs:  []string{"-f", "skaffold-gcb.yaml"},
			remoteOnly: true,
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.description, func(t *testing.T) {
			if !*remote && testCase.remoteOnly {
				t.Skip("skipping remote only test")
			}
			args := []string{"run"}
			args = append(args, testCase.extraArgs...)
			cmd := exec.Command("skaffold", args...)
			cmd.Dir = testCase.dir
			out, outerr, err := util.RunCommand(cmd, nil)
			if err != nil {
				t.Fatalf("skaffold run: \nstdout: %s\nstderr: %s\nerror: %s", out, outerr, err)
			}

			for _, p := range testCase.pods {
				if err := kubernetesutil.WaitForPodReady(client.CoreV1().Pods(p.namespace), p.name); err != nil {
					t.Fatalf("Timed out waiting for pod ready")
				}
				if err := client.CoreV1().Pods(p.namespace).Delete(p.name, nil); err != nil {
					t.Fatalf("Error deleting pod %s: %s", p, err)
				}
			}

			for _, d := range testCase.deployments {
				if err := kubernetesutil.WaitForDeploymentToStabilize(client, d.namespace, d.name, 10*time.Minute); err != nil {
					t.Fatalf("Timed out waiting for deployment to stabilize")
				}
				if err := client.AppsV1().Deployments(d.namespace).Delete(d.name, &meta_v1.DeleteOptions{}); err != nil {
					t.Fatalf("Error deleting deployment %s: %s", d, err)
				}
			}
		})
	}
}
<|endoftext|>


<commit_before>// Copyright (c) nano Author. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package nano

import (
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/lonnng/nano/session"
)

// VERSION returns current nano version
var VERSION = "0.0.1"

var (
	// app represents the current server process
	app = &struct {
		name    string    // current application name
		startAt time.Time // startup time
	}{}

	// env represents the environment of the current process, includes
	// work path and config path etc.
	env = &struct {
		wd          string                   // working path
		die         chan bool                // wait for end application
		heartbeat   time.Duration            // heartbeat internal
		checkOrigin func(*http.Request) bool // check origin when websocket enabled
		debug       bool                     // enable debug

		// session closed handlers
		muCallbacks sync.RWMutex           // protect callbacks
		callbacks   []SessionClosedHandler // callbacks that emitted on session closed
	}{}
)

type (
	// SessionClosedHandler represents a callback that will be called when a session
	// close or session low-level connection broken.
	SessionClosedHandler func(session *session.Session)
)

// init default configs
func init() {
	// application initialize
	app.name = strings.TrimLeft(filepath.Base(os.Args[0]), "/")
	app.startAt = time.Now()

	// environment initialize
	if wd, err := os.Getwd(); err != nil {
		panic(err)
	} else {
		env.wd, _ = filepath.Abs(wd)
	}

	env.die = make(chan bool)
	env.heartbeat = 30 * time.Second
	env.debug = false
	env.muCallbacks = sync.RWMutex{}
}
<commit_msg>ws: default origin checker<commit_after>// Copyright (c) nano Author. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package nano

import (
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/lonnng/nano/session"
)

// VERSION returns current nano version
var VERSION = "0.0.1"

var (
	// app represents the current server process
	app = &struct {
		name    string    // current application name
		startAt time.Time // startup time
	}{}

	// env represents the environment of the current process, includes
	// work path and config path etc.
	env = &struct {
		wd          string                   // working path
		die         chan bool                // wait for end application
		heartbeat   time.Duration            // heartbeat internal
		checkOrigin func(*http.Request) bool // check origin when websocket enabled
		debug       bool                     // enable debug

		// session closed handlers
		muCallbacks sync.RWMutex           // protect callbacks
		callbacks   []SessionClosedHandler // callbacks that emitted on session closed
	}{}
)

type (
	// SessionClosedHandler represents a callback that will be called when a session
	// close or session low-level connection broken.
	SessionClosedHandler func(session *session.Session)
)

// init default configs
func init() {
	// application initialize
	app.name = strings.TrimLeft(filepath.Base(os.Args[0]), "/")
	app.startAt = time.Now()

	// environment initialize
	if wd, err := os.Getwd(); err != nil {
		panic(err)
	} else {
		env.wd, _ = filepath.Abs(wd)
	}

	env.die = make(chan bool)
	env.heartbeat = 30 * time.Second
	env.debug = false
	env.muCallbacks = sync.RWMutex{}
	env.checkOrigin = func(_ *http.Request) bool { return true }
}
<|endoftext|>