// NewEngine - Return a new Engine from the given config. func NewEngine(cfg *Config) (*Engine, error) { var err error if cfg == nil { return nil, errors.New("Fatal error, Cannot load Listless engine with empty configuration.") } E := new(Engine) E.Config = cfg E.Lua = lua.NewState() // Preload a few extra libs.. luajson.Preload(E.Lua) E.Lua.PreloadModule("url", gluaurl.Loader) // Disabled for security, right now: // E.Lua.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader) E.DB, err = NewDatabase(cfg.Database) if err != nil { return nil, err } E.Client = imapclient.NewClientTLS(cfg.IMAPHost, cfg.IMAPPort, cfg.IMAPUsername, cfg.IMAPPassword) E.Shutdown = make(chan struct{}) err = applyLuarWhitelists(E.Lua) if err != nil { log15.Error("Error setting method whitelists in lua runtime", log15.Ctx{"context": "lua", "error": err}) return nil, err } return E, nil }
func (lua *LuaExt) exec(reqLogger log.Logger, app *LuaApp, appID, reqId, script string, w http.ResponseWriter, r *http.Request) int { // FIXME(tsileo) a debug mode, with a defer/recover // also parse the Lu error and show the bugging line! start := time.Now() httpClient := &http.Client{} // Initialize internal Lua module written in Go logger := loggerModule.New(reqLogger.New("ctx", "Lua"), start, reqId) response := responseModule.New() request := requestModule.New(r, reqId, lua.authFunc) blobstore := blobstoreModule.New(lua.blobStore) kvstore := kvstoreModule.New(lua.kvStore) bewit := bewitModule.New(reqLogger.New("ctx", "Lua bewit module"), r) template := templateModule.New() // Initialize Lua state L := luamod.NewState() defer L.Close() setCustomGlobals(L) L.PreloadModule("request", request.Loader) L.PreloadModule("response", response.Loader) L.PreloadModule("logger", logger.Loader) L.PreloadModule("blobstore", blobstore.Loader) L.PreloadModule("kvstore", kvstore.Loader) L.PreloadModule("bewit", bewit.Loader) L.PreloadModule("template", template.Loader) // TODO(tsileo) docstore module // TODO(tsileo) cookies module // TODO(tsileo) lru module // TODO(tsileo) cache module => to cache response // TODO(tsileo) load module from github directly? // TODO(tsileo) ETag support // 3rd party module luajson.Preload(L) L.PreloadModule("http", gluahttp.NewHttpModule(httpClient).Loader) // Set some global variables L.SetGlobal("reqID", luamod.LString(reqId)) L.SetGlobal("appID", luamod.LString(appID)) // Execute the code if err := L.DoString(script); err != nil { // FIXME better error, with debug mode? panic(err) } // Apply the Response object to the actual response response.WriteTo(w) // TODO save the logRecords in the AppStats and find a way to serve them over Server-Sent Events // keep them in memory with the ability to dump them in bulk as blob for later query // logRecords := logger.Records() for _, logRecord := range logger.Records() { app.logs = append(app.logs, logRecord) } reqLogger.Info("Script executed", "response", response, "duration", time.Since(start)) return response.Status() }
func newLuaState(conf string) *lua.LState { L := lua.NewState() registerIRCChatClientType(L) registerSlackChatClientType(L) registerHipchatChatClientType(L) registerNullChatClientType(L) mod := L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{ "newbot": func(L *lua.LState) int { opt := L.OptTable(2, L.NewTable()) co := newCommonClientOption(conf) switch v := L.GetField(opt, "log").(type) { case *lua.LFunction: co.Logger = log.New(&luaLogger{L, v}, "", log.LstdFlags) case *lua.LTable: l, err := seelog.LoggerFromConfigAsString(luaToXml(v)) if err != nil { L.RaiseError(err.Error()) } co.Logger = log.New(&seelogLogger{l}, "", 0) } if s, ok := getStringField(L, opt, "http"); ok { co.HttpAddr = s } if tbl, ok := L.GetField(opt, "https").(*lua.LTable); ok { if s, ok := getStringField(L, tbl, "addr"); ok { co.Https.Addr = s } if s, ok := getStringField(L, tbl, "cert"); ok { co.Https.CertFile = s } if s, ok := getStringField(L, tbl, "key"); ok { co.Https.KeyFile = s } } if tbl, ok := L.GetField(opt, "crons").(*lua.LTable); ok { co.Crons = []CronEntry{} tbl.ForEach(func(key, value lua.LValue) { entry := value.(*lua.LTable) co.Crons = append(co.Crons, CronEntry{entry.RawGetInt(1).String(), entry.RawGetInt(2).String()}) }) } switch L.CheckString(1) { case "IRC": newIRCChatClient(L, co, opt) case "Slack": newSlackChatClient(L, co, opt) case "Hipchat": newHipchatChatClient(L, co, opt) case "Null": newNullChatClient(L, co, opt) default: L.RaiseError("unknown chat type: %s", L.ToString(1)) } return 1 }, "newlogger": func(L *lua.LState) int { logger, err := seelog.LoggerFromConfigAsString(luaToXml(L.CheckTable(1))) if err != nil { L.RaiseError(err.Error()) } L.Push(luar.New(L, log.New(&seelogLogger{logger}, "", 0))) return 1 }, }) L.SetField(mod, "cmain", lua.LChannel(luaMainChan)) L.SetField(mod, "cworker", lua.LChannel(luaWorkerChan)) proxyLuar(L, MessageEvent{}, nil) proxyLuar(L, log.Logger{}, nil) proxyLuar(L, url.Values{}, nil) proxyLuar(L, url.Userinfo{}, nil) proxyLuar(L, url.URL{}, nil) proxyLuar(L, http.Cookie{}, nil) proxyLuar(L, http.Header{}, nil) proxyLuar(L, http.Request{}, func(L *lua.LState, key string) bool { if key == "readbody" || key == "ReadBody" { L.Push(L.NewFunction(func(L *lua.LState) int { r := L.CheckUserData(1).Value.(*http.Request) b, err := ioutil.ReadAll(r.Body) defer r.Body.Close() if err != nil { pushN(L, lua.LNil, lua.LString(err.Error())) return 2 } pushN(L, lua.LString(b)) return 1 })) return true } return false }) L.PreloadModule("golbot", func(L *lua.LState) int { L.Push(mod) return 1 }) L.PreloadModule("charset", func(L *lua.LState) int { L.Push(L.SetFuncs(L.NewTable(), charsetMod)) return 1 }) L.PreloadModule("requests", func(L *lua.LState) int { L.Push(L.SetFuncs(L.NewTable(), requestsMod)) return 1 }) luajson.Preload(L) L.PreloadModule("re", gluare.Loader) L.PreloadModule("sh", gluash.Loader) L.PreloadModule("fs", gluafs.Loader) L.SetGlobal("goworker", L.NewFunction(func(L *lua.LState) int { go func() { L := newLuaState(conf) pushN(L, L.GetGlobal("worker"), <-luaWorkerChan) L.PCall(1, 0, nil) }() luaWorkerChan <- L.CheckAny(1) return 0 })) if err := L.DoString(` local golbot = require("golbot") local requests = require("requests") local json = require("json") notifymain = function(msg) golbot.cmain:send(msg) end requestmain = function(msg) msg._result = channel.make() golbot.cmain:send(msg) return msg._result:receive() end respond = function(msg, value) if msg and msg._result then msg._result:send(value) end end requests.json = function(opt) local headers = opt.headers or {} opt.headers = headers local found = false for i, v in ipairs(headers) do if i%2 == 0 and string.lower(v) == "content-type" then found = true break end end if not found then table.insert(headers, "Content-Type") table.insert(headers, "application/json") end jdata, e = json.encode(opt.json) if jdata == nil then return jdata, e end opt.data = jdata body, resp = requests.request(opt) if body == nil then return body, resp end return json.decode(body), resp end `); err != nil { panic(err) } if err := L.DoFile(conf); err != nil { panic(err) } return L }