first commit
This commit is contained in:
216
internal/builtins/html.go
Normal file
216
internal/builtins/html.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"go.starlark.net/starlark"
|
||||
"go.starlark.net/starlarkstruct"
|
||||
)
|
||||
|
||||
var (
|
||||
_ starlark.Iterable = (*starlarkSelection)(nil)
|
||||
_ starlark.Sliceable = (*starlarkSelection)(nil)
|
||||
_ starlark.Sequence = (*starlarkSelection)(nil)
|
||||
_ starlark.Value = (*starlarkSelection)(nil)
|
||||
)
|
||||
|
||||
var htmlModule = &starlarkstruct.Module{
|
||||
Name: "html",
|
||||
Members: starlark.StringDict{
|
||||
"parse": starlark.NewBuiltin("html.parse", htmlParse),
|
||||
},
|
||||
}
|
||||
|
||||
func htmlParse(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var r readerValue
|
||||
err := starlark.UnpackArgs("html.selection.find", args, kwargs, "from", &r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newStarlarkSelection(doc.Selection), nil
|
||||
}
|
||||
|
||||
type starlarkSelection struct {
|
||||
sel *goquery.Selection
|
||||
*starlarkstruct.Struct
|
||||
}
|
||||
|
||||
func newStarlarkSelection(sel *goquery.Selection) starlarkSelection {
|
||||
ss := starlarkSelection{sel: sel}
|
||||
ss.Struct = starlarkstruct.FromStringDict(starlark.String("html.selection"), starlark.StringDict{
|
||||
"text": starlark.NewBuiltin("html.selection.text", ss.text),
|
||||
"html": starlark.NewBuiltin("html.selection.html", ss.html),
|
||||
"children": starlark.NewBuiltin("html.selection.children", ss.children),
|
||||
"parent": starlark.NewBuiltin("html.selection.parent", ss.parent),
|
||||
"find": starlark.NewBuiltin("html.selection.find", ss.find),
|
||||
"add": starlark.NewBuiltin("html.selection.add", ss.add),
|
||||
"attr": starlark.NewBuiltin("html.selection.attr", ss.attr),
|
||||
"has_class": starlark.NewBuiltin("html.selection.has_class", ss.hasClass),
|
||||
"index_selector": starlark.NewBuiltin("html.selection.index_selector", ss.indexSelector),
|
||||
"and_self": starlark.NewBuiltin("html.selection.and_self", ss.andSelf),
|
||||
"first": starlark.NewBuiltin("html.selection.first", ss.first),
|
||||
"last": starlark.NewBuiltin("html.selection.last", ss.last),
|
||||
"next": starlark.NewBuiltin("html.selection.last", ss.next),
|
||||
"next_all": starlark.NewBuiltin("html.selection.next_all", ss.nextAll),
|
||||
"next_until": starlark.NewBuiltin("html.selection.next_until", ss.nextUntil),
|
||||
"prev": starlark.NewBuiltin("html.selection.prev", ss.prev),
|
||||
"prev_all": starlark.NewBuiltin("html.selection.prev_all", ss.prevAll),
|
||||
"prev_until": starlark.NewBuiltin("html.selection.prev_until", ss.prevUntil),
|
||||
})
|
||||
return ss
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) Truth() starlark.Bool {
|
||||
return len(ss.sel.Nodes) > 0
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) Len() int {
|
||||
return ss.sel.Length()
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) Index(i int) starlark.Value {
|
||||
return newStarlarkSelection(ss.sel.Slice(i, i+1))
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) Slice(start, end, _ int) starlark.Value {
|
||||
return newStarlarkSelection(ss.sel.Slice(start, end))
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) Iterate() starlark.Iterator {
|
||||
return newSelectionIterator(ss.sel)
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) text(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return starlark.String(ss.sel.Text()), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) html(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
s, err := ss.sel.Html()
|
||||
return starlark.String(s), err
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) children(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return newStarlarkSelection(ss.sel.Children()), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) parent(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return newStarlarkSelection(ss.sel.Parent()), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) find(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var selector string
|
||||
err := starlark.UnpackArgs("html.selection.find", args, kwargs, "selector", &selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newStarlarkSelection(ss.sel.Find(selector)), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) add(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var selector string
|
||||
err := starlark.UnpackArgs("html.selection.add", args, kwargs, "selector", &selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newStarlarkSelection(ss.sel.Add(selector)), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) indexSelector(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var selector string
|
||||
err := starlark.UnpackArgs("html.selection.index_selector", args, kwargs, "selector", &selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return starlark.MakeInt(ss.sel.IndexSelector(selector)), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) attr(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var name, def string
|
||||
err := starlark.UnpackArgs("html.selection.find", args, kwargs, "name", &name, "default??", &def)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return starlark.String(ss.sel.AttrOr(name, def)), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) hasClass(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var name string
|
||||
err := starlark.UnpackArgs("html.selection.has_class", args, kwargs, "name", &name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return starlark.Bool(ss.sel.HasClass(name)), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) andSelf(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return newStarlarkSelection(ss.sel.AndSelf()), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) first(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return newStarlarkSelection(ss.sel.First()), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) last(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return newStarlarkSelection(ss.sel.Last()), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) next(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return newStarlarkSelection(ss.sel.Next()), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) nextAll(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return newStarlarkSelection(ss.sel.NextAll()), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) nextUntil(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var selector string
|
||||
err := starlark.UnpackArgs("html.selection.next_until", args, kwargs, "selector", &selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newStarlarkSelection(ss.sel.NextUntil(selector)), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) prev(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return newStarlarkSelection(ss.sel.Prev()), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) prevAll(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return newStarlarkSelection(ss.sel.PrevAll()), nil
|
||||
}
|
||||
|
||||
func (ss starlarkSelection) prevUntil(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var selector string
|
||||
err := starlark.UnpackArgs("html.selection.prev_until", args, kwargs, "selector", &selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newStarlarkSelection(ss.sel.PrevUntil(selector)), nil
|
||||
}
|
||||
|
||||
type starlarkSelectionIterator struct {
|
||||
sel *goquery.Selection
|
||||
index int
|
||||
}
|
||||
|
||||
func newSelectionIterator(sel *goquery.Selection) *starlarkSelectionIterator {
|
||||
return &starlarkSelectionIterator{sel: sel}
|
||||
}
|
||||
|
||||
func (ssi *starlarkSelectionIterator) Next(v *starlark.Value) bool {
|
||||
if ssi.index == ssi.sel.Length() {
|
||||
return false
|
||||
}
|
||||
*v = newStarlarkSelection(ssi.sel.Slice(ssi.index, ssi.index+1))
|
||||
ssi.index++
|
||||
return true
|
||||
}
|
||||
|
||||
func (ssi *starlarkSelectionIterator) Done() {}
|
361
internal/builtins/http.go
Normal file
361
internal/builtins/http.go
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* LURE Updater - Automated updater bot for LURE packages
|
||||
* Copyright (C) 2023 Elara Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go.elara.ws/logger/log"
|
||||
"lure.sh/lure-updater/internal/config"
|
||||
"go.starlark.net/starlark"
|
||||
"go.starlark.net/starlarkstruct"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const maxBodySize = 16384
|
||||
|
||||
var (
|
||||
ErrInvalidBodyType = errors.New("invalid body type")
|
||||
ErrInvalidHdrKeyType = errors.New("invalid header key type")
|
||||
ErrInvalidHdrVal = errors.New("invalid header value type")
|
||||
ErrInvalidType = errors.New("invalid type")
|
||||
ErrInsecureWebhook = errors.New("secure webhook missing authorization")
|
||||
ErrIncorrectPassword = errors.New("incorrect password")
|
||||
)
|
||||
|
||||
var httpModule = &starlarkstruct.Module{
|
||||
Name: "http",
|
||||
Members: starlark.StringDict{
|
||||
"get": starlark.NewBuiltin("http.get", httpGet),
|
||||
"post": starlark.NewBuiltin("http.post", httpPost),
|
||||
"put": starlark.NewBuiltin("http.put", httpPut),
|
||||
"head": starlark.NewBuiltin("http.head", httpHead),
|
||||
},
|
||||
}
|
||||
|
||||
func httpGet(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return makeRequest("http.get", http.MethodGet, args, kwargs, thread)
|
||||
}
|
||||
|
||||
func httpPost(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return makeRequest("http.post", http.MethodPost, args, kwargs, thread)
|
||||
}
|
||||
|
||||
func httpPut(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return makeRequest("http.put", http.MethodPut, args, kwargs, thread)
|
||||
}
|
||||
|
||||
func httpHead(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return makeRequest("http.head", http.MethodHead, args, kwargs, thread)
|
||||
}
|
||||
|
||||
type starlarkBodyReader struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
func (sbr *starlarkBodyReader) Unpack(v starlark.Value) error {
|
||||
switch v := v.(type) {
|
||||
case starlark.String:
|
||||
sbr.Reader = strings.NewReader(string(v))
|
||||
case starlark.Bytes:
|
||||
sbr.Reader = strings.NewReader(string(v))
|
||||
case starlarkReader:
|
||||
sbr.Reader = v
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", ErrInvalidBodyType, v.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newBodyReader() *starlarkBodyReader {
|
||||
return &starlarkBodyReader{
|
||||
Reader: bytes.NewReader(nil),
|
||||
}
|
||||
}
|
||||
|
||||
type starlarkHeaders struct {
|
||||
http.Header
|
||||
}
|
||||
|
||||
func (sh *starlarkHeaders) Unpack(v starlark.Value) error {
|
||||
dict, ok := v.(*starlark.Dict)
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: %s", ErrInvalidType, v.Type())
|
||||
}
|
||||
|
||||
sh.Header = make(http.Header, dict.Len())
|
||||
for _, key := range dict.Keys() {
|
||||
keyStr, ok := key.(starlark.String)
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: %s", ErrInvalidHdrKeyType, key.Type())
|
||||
}
|
||||
|
||||
val, _, _ := dict.Get(key)
|
||||
list, ok := val.(*starlark.List)
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: %s", ErrInvalidHdrVal, val.Type())
|
||||
}
|
||||
|
||||
hdrVals := make([]string, list.Len())
|
||||
for i := 0; i < list.Len(); i++ {
|
||||
hdrVal, ok := list.Index(i).(starlark.String)
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: %s", ErrInvalidHdrVal, list.Index(i).Type())
|
||||
}
|
||||
|
||||
hdrVals[i] = string(hdrVal)
|
||||
}
|
||||
|
||||
sh.Header[string(keyStr)] = hdrVals
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeRequest(name, method string, args starlark.Tuple, kwargs []starlark.Tuple, thread *starlark.Thread) (starlark.Value, error) {
|
||||
var (
|
||||
url string
|
||||
redirect = true
|
||||
headers = &starlarkHeaders{}
|
||||
body = newBodyReader()
|
||||
)
|
||||
err := starlark.UnpackArgs(name, args, kwargs, "url", &url, "redirect??", &redirect, "headers??", headers, "body??", body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header = headers.Header
|
||||
|
||||
client := http.DefaultClient
|
||||
if !redirect {
|
||||
client = &http.Client{
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("Making HTTP request").Str("url", url).Str("method", req.Method).Bool("redirect", redirect).Stringer("pos", thread.CallFrame(1).Pos).Send()
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("Got HTTP response").Str("host", res.Request.URL.Host).Int("code", res.StatusCode).Stringer("pos", thread.CallFrame(1).Pos).Send()
|
||||
|
||||
return starlarkResponse(res), nil
|
||||
}
|
||||
|
||||
func starlarkResponse(res *http.Response) *starlarkstruct.Struct {
|
||||
return starlarkstruct.FromStringDict(starlarkstruct.Default, starlark.StringDict{
|
||||
"code": starlark.MakeInt(res.StatusCode),
|
||||
"headers": starlarkStringSliceMap(res.Header),
|
||||
"body": newStarlarkReader(res.Body),
|
||||
})
|
||||
}
|
||||
|
||||
func starlarkRequest(req *http.Request) *starlarkstruct.Struct {
|
||||
return starlarkstruct.FromStringDict(starlarkstruct.Default, starlark.StringDict{
|
||||
"method": starlark.String(req.Method),
|
||||
"remote_addr": starlark.String(req.RemoteAddr),
|
||||
"headers": starlarkStringSliceMap(req.Header),
|
||||
"query": starlarkStringSliceMap(req.URL.Query()),
|
||||
"body": newStarlarkReader(req.Body),
|
||||
})
|
||||
}
|
||||
|
||||
func starlarkStringSliceMap(ssm map[string][]string) *starlark.Dict {
|
||||
dict := starlark.NewDict(len(ssm))
|
||||
for key, vals := range ssm {
|
||||
sVals := make([]starlark.Value, len(vals))
|
||||
for i, val := range vals {
|
||||
sVals[i] = starlark.String(val)
|
||||
}
|
||||
dict.SetKey(starlark.String(key), starlark.NewList(sVals))
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
func registerWebhook(mux *http.ServeMux, cfg *config.Config, pluginName string) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("register_webhook", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var fn *starlark.Function
|
||||
secure := true
|
||||
err := starlark.UnpackArgs("register_webhook", args, kwargs, "function", &fn, "secure??", &secure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !secure {
|
||||
log.Warn("Plugin is registering an insecure webhook").Str("plugin", pluginName).Send()
|
||||
}
|
||||
|
||||
path := "/webhook/" + pluginName + "/" + fn.Name()
|
||||
mux.HandleFunc(path, webhookHandler(pluginName, secure, cfg, thread, fn))
|
||||
log.Debug("Registered webhook").Str("path", path).Str("function", fn.Name()).Stringer("pos", thread.CallFrame(1).Pos).Send()
|
||||
return starlark.None, nil
|
||||
})
|
||||
}
|
||||
|
||||
func webhookHandler(pluginName string, secure bool, cfg *config.Config, thread *starlark.Thread, fn *starlark.Function) http.HandlerFunc {
|
||||
return handleError(func(res http.ResponseWriter, req *http.Request) *HTTPError {
|
||||
defer req.Body.Close()
|
||||
|
||||
res.Header().Add("X-Updater-Plugin", pluginName)
|
||||
|
||||
if secure {
|
||||
err := verifySecure(cfg.Webhook.PasswordHash, pluginName, req)
|
||||
if err != nil {
|
||||
return &HTTPError{
|
||||
Message: "Error verifying webhook",
|
||||
Code: http.StatusForbidden,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("Calling webhook function").Str("name", fn.Name()).Stringer("pos", fn.Position()).Send()
|
||||
val, err := starlark.Call(thread, fn, starlark.Tuple{starlarkRequest(req)}, nil)
|
||||
if err != nil {
|
||||
return &HTTPError{
|
||||
Message: "Error while executing webhook",
|
||||
Code: http.StatusInternalServerError,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
switch val := val.(type) {
|
||||
case starlark.NoneType:
|
||||
res.WriteHeader(http.StatusOK)
|
||||
case starlark.Int:
|
||||
var code int
|
||||
err = starlark.AsInt(val, &code)
|
||||
if err == nil {
|
||||
res.WriteHeader(code)
|
||||
} else {
|
||||
res.WriteHeader(http.StatusOK)
|
||||
}
|
||||
case starlark.String, starlark.Bytes:
|
||||
body := newBodyReader()
|
||||
err = body.Unpack(val)
|
||||
if err != nil {
|
||||
return &HTTPError{
|
||||
Message: "Error unpacking returned body",
|
||||
Code: http.StatusInternalServerError,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
_, err = io.Copy(res, body)
|
||||
if err != nil {
|
||||
log.Error("Error writing body").Err(err).Send()
|
||||
return &HTTPError{
|
||||
Message: "Error writing body",
|
||||
Code: http.StatusInternalServerError,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
case *starlark.Dict:
|
||||
code := http.StatusOK
|
||||
codeVal, ok, _ := val.Get(starlark.String("code"))
|
||||
if ok {
|
||||
err = starlark.AsInt(codeVal, &code)
|
||||
if err != nil {
|
||||
return &HTTPError{
|
||||
Message: "Error decoding returned status code",
|
||||
Code: http.StatusInternalServerError,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
res.WriteHeader(code)
|
||||
}
|
||||
|
||||
body := newBodyReader()
|
||||
bodyVal, ok, _ := val.Get(starlark.String("body"))
|
||||
if ok {
|
||||
err = body.Unpack(bodyVal)
|
||||
if err != nil {
|
||||
return &HTTPError{
|
||||
Message: "Error unpacking returned body",
|
||||
Code: http.StatusInternalServerError,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
_, err = io.Copy(res, body)
|
||||
if err != nil {
|
||||
return &HTTPError{
|
||||
Message: "Error writing body",
|
||||
Code: http.StatusInternalServerError,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
type HTTPError struct {
|
||||
Code int
|
||||
Message string
|
||||
Err error
|
||||
}
|
||||
|
||||
func handleError(h func(res http.ResponseWriter, req *http.Request) *HTTPError) http.HandlerFunc {
|
||||
return func(res http.ResponseWriter, req *http.Request) {
|
||||
httpErr := h(res, req)
|
||||
if httpErr != nil {
|
||||
log.Error(httpErr.Message).Err(httpErr.Err).Send()
|
||||
res.WriteHeader(httpErr.Code)
|
||||
fmt.Sprintf("%s: %s", httpErr.Message, httpErr.Err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifySecure(pwdHash, pluginName string, req *http.Request) error {
|
||||
var pwd []byte
|
||||
if _, pwdStr, ok := req.BasicAuth(); ok {
|
||||
pwdStr = strings.TrimSpace(pwdStr)
|
||||
pwd = []byte(pwdStr)
|
||||
} else if hdrStr := req.Header.Get("Authorization"); hdrStr != "" {
|
||||
hdrStr = strings.TrimPrefix(hdrStr, "Bearer")
|
||||
hdrStr = strings.TrimSpace(hdrStr)
|
||||
pwd = []byte(hdrStr)
|
||||
} else {
|
||||
log.Warn("Insecure webhook request").
|
||||
Str("from", req.RemoteAddr).
|
||||
Str("plugin", pluginName).
|
||||
Send()
|
||||
return ErrInsecureWebhook
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(pwdHash), pwd); err != nil {
|
||||
return ErrIncorrectPassword
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
94
internal/builtins/log.go
Normal file
94
internal/builtins/log.go
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* LURE Updater - Automated updater bot for LURE packages
|
||||
* Copyright (C) 2023 Elara Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"go.elara.ws/logger"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.starlark.net/starlark"
|
||||
"go.starlark.net/starlarkstruct"
|
||||
)
|
||||
|
||||
func logModule(name string) *starlarkstruct.Module {
|
||||
return &starlarkstruct.Module{
|
||||
Name: "log",
|
||||
Members: starlark.StringDict{
|
||||
"debug": logDebug(name),
|
||||
"info": logInfo(name),
|
||||
"warn": logWarn(name),
|
||||
"error": logError(name),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func logDebug(name string) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("log.debug", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return starlark.None, doLogEvent(name, "log.debug", log.Debug, thread, args, kwargs)
|
||||
})
|
||||
}
|
||||
|
||||
func logInfo(name string) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("log.info", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return starlark.None, doLogEvent(name, "log.info", log.Info, thread, args, kwargs)
|
||||
})
|
||||
}
|
||||
|
||||
func logWarn(name string) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("log.warn", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return starlark.None, doLogEvent(name, "log.warn", log.Warn, thread, args, kwargs)
|
||||
})
|
||||
}
|
||||
|
||||
func logError(name string) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("log.error", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
return starlark.None, doLogEvent(name, "log.error", log.Error, thread, args, kwargs)
|
||||
})
|
||||
}
|
||||
|
||||
func doLogEvent(pluginName, fnName string, eventFn func(msg string) logger.LogBuilder, thread *starlark.Thread, args starlark.Tuple, kwargs []starlark.Tuple) error {
|
||||
var msg string
|
||||
var fields *starlark.Dict
|
||||
err := starlark.UnpackArgs(fnName, args, kwargs, "msg", &msg, "fields??", &fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
evt := eventFn("[" + pluginName + "] " + msg)
|
||||
|
||||
if fields != nil {
|
||||
for _, key := range fields.Keys() {
|
||||
val, _, err := fields.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keyStr := strings.Trim(key.String(), `"`)
|
||||
evt = evt.Stringer(keyStr, val)
|
||||
}
|
||||
}
|
||||
|
||||
if fnName == "log.debug" {
|
||||
evt = evt.Stringer("pos", thread.CallFrame(1).Pos)
|
||||
}
|
||||
|
||||
evt.Send()
|
||||
|
||||
return nil
|
||||
}
|
238
internal/builtins/reader.go
Normal file
238
internal/builtins/reader.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
"lure.sh/lure-updater/internal/convert"
|
||||
"go.starlark.net/starlark"
|
||||
"go.starlark.net/starlarkstruct"
|
||||
)
|
||||
|
||||
type starlarkReader struct {
|
||||
closeFunc func() error
|
||||
br *bufio.Reader
|
||||
*starlarkstruct.Struct
|
||||
}
|
||||
|
||||
func newStarlarkReader(r io.Reader) starlarkReader {
|
||||
sr := starlarkReader{br: bufio.NewReader(r)}
|
||||
|
||||
if rc, ok := r.(io.ReadCloser); ok {
|
||||
sr.closeFunc = rc.Close
|
||||
}
|
||||
|
||||
sr.Struct = starlarkstruct.FromStringDict(starlark.String("regex"), starlark.StringDict{
|
||||
"read": starlark.NewBuiltin("reader.read", sr.read),
|
||||
"peek": starlark.NewBuiltin("reader.peek", sr.peek),
|
||||
"discard": starlark.NewBuiltin("reader.discard", sr.discard),
|
||||
"read_string": starlark.NewBuiltin("reader.read_string", sr.readString),
|
||||
"read_until": starlark.NewBuiltin("reader.read_until", sr.readUntil),
|
||||
"read_string_until": starlark.NewBuiltin("reader.read_string_until", sr.readStringUntil),
|
||||
"read_all": starlark.NewBuiltin("reader.read_all", sr.readAll),
|
||||
"read_all_string": starlark.NewBuiltin("reader.read_all_string", sr.readAllString),
|
||||
"read_json": starlark.NewBuiltin("reader.read_json", sr.readJSON),
|
||||
"read_msgpack": starlark.NewBuiltin("reader.read_msgpack", sr.readMsgpack),
|
||||
"close": starlark.NewBuiltin("reader.close", sr.closeReader),
|
||||
})
|
||||
|
||||
return sr
|
||||
}
|
||||
|
||||
func (sr starlarkReader) read(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var n int
|
||||
err := starlark.UnpackArgs("reader.read", args, kwargs, "n", &n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := make([]byte, n)
|
||||
_, err = io.ReadFull(sr.br, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return starlark.Bytes(buf), nil
|
||||
}
|
||||
|
||||
func (sr starlarkReader) readString(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var n int
|
||||
err := starlark.UnpackArgs("reader.read_string", args, kwargs, "n", &n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := make([]byte, n)
|
||||
_, err = io.ReadFull(sr.br, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return starlark.String(buf), nil
|
||||
}
|
||||
|
||||
func (sr starlarkReader) readUntil(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var delimiter string
|
||||
err := starlark.UnpackArgs("reader.read_until", args, kwargs, "delimiter", &delimiter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err := sr.br.ReadBytes(delimiter[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return starlark.Bytes(buf), nil
|
||||
}
|
||||
|
||||
func (sr starlarkReader) readStringUntil(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var delimiter string
|
||||
err := starlark.UnpackArgs("reader.read_string_until", args, kwargs, "delimiter", &delimiter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err := sr.br.ReadString(delimiter[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return starlark.String(buf), nil
|
||||
}
|
||||
|
||||
func (sr starlarkReader) peek(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var n int
|
||||
err := starlark.UnpackArgs("reader.peek", args, kwargs, "n", &n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err := sr.br.Peek(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return starlark.Bytes(buf), nil
|
||||
}
|
||||
|
||||
func (sr starlarkReader) discard(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var n int
|
||||
err := starlark.UnpackArgs("reader.discard", args, kwargs, "n", &n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dn, err := sr.br.Discard(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return starlark.MakeInt(dn), nil
|
||||
}
|
||||
|
||||
func (sr starlarkReader) readAll(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var limit int64 = 102400
|
||||
err := starlark.UnpackArgs("reader.read_all", args, kwargs, "limit??", &limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var r io.Reader = sr.br
|
||||
if limit > 0 {
|
||||
r = io.LimitReader(sr.br, limit)
|
||||
}
|
||||
|
||||
buf, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return starlark.Bytes(buf), nil
|
||||
}
|
||||
|
||||
func (sr starlarkReader) readAllString(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var limit int64 = 102400
|
||||
err := starlark.UnpackArgs("reader.read_all_string", args, kwargs, "limit??", &limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var r io.Reader = sr.br
|
||||
if limit > 0 {
|
||||
r = io.LimitReader(sr.br, limit)
|
||||
}
|
||||
|
||||
buf, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return starlark.String(buf), nil
|
||||
}
|
||||
|
||||
func (sr starlarkReader) readJSON(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var v any
|
||||
err := json.NewDecoder(sr.br).Decode(&v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return convert.Convert(v)
|
||||
}
|
||||
|
||||
func (sr starlarkReader) readMsgpack(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var v any
|
||||
err := msgpack.NewDecoder(sr.br).Decode(&v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return convert.Convert(v)
|
||||
}
|
||||
|
||||
func (sr starlarkReader) closeReader(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
if sr.closeFunc != nil {
|
||||
err := sr.closeFunc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return starlark.None, nil
|
||||
}
|
||||
|
||||
// Read implements the io.ReadCloser interface
|
||||
func (sr starlarkReader) Read(b []byte) (int, error) {
|
||||
return sr.br.Read(b)
|
||||
}
|
||||
|
||||
// Close implements the io.ReadCloser interface
|
||||
func (sr starlarkReader) Close() error {
|
||||
if sr.closeFunc != nil {
|
||||
return sr.closeFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type readerValue struct {
|
||||
io.ReadCloser
|
||||
}
|
||||
|
||||
func (rv *readerValue) Unpack(v starlark.Value) error {
|
||||
switch val := v.(type) {
|
||||
case starlark.String:
|
||||
rv.ReadCloser = io.NopCloser(strings.NewReader(string(val)))
|
||||
case starlark.Bytes:
|
||||
rv.ReadCloser = io.NopCloser(strings.NewReader(string(val)))
|
||||
case starlarkReader:
|
||||
rv.ReadCloser = val
|
||||
}
|
||||
|
||||
if rv.ReadCloser == nil {
|
||||
return errors.New("invalid type for reader")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
145
internal/builtins/regex.go
Normal file
145
internal/builtins/regex.go
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* LURE Updater - Automated updater bot for LURE packages
|
||||
* Copyright (C) 2023 Elara Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"go.elara.ws/pcre"
|
||||
"go.starlark.net/starlark"
|
||||
"go.starlark.net/starlarkstruct"
|
||||
)
|
||||
|
||||
var (
|
||||
cacheMtx = &sync.Mutex{}
|
||||
regexCache = map[string]*pcre.Regexp{}
|
||||
)
|
||||
|
||||
var regexModule = &starlarkstruct.Module{
|
||||
Name: "regex",
|
||||
Members: starlark.StringDict{
|
||||
"compile": starlark.NewBuiltin("regex.compile", regexCompile),
|
||||
"compile_glob": starlark.NewBuiltin("regex.compile_glob", regexCompileGlob),
|
||||
},
|
||||
}
|
||||
|
||||
func regexCompile(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var regexStr string
|
||||
err := starlark.UnpackArgs("regex.compile", args, kwargs, "regex", ®exStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cacheMtx.Lock()
|
||||
regex, ok := regexCache[regexStr]
|
||||
if !ok {
|
||||
regex, err = pcre.Compile(regexStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
regexCache[regexStr] = regex
|
||||
}
|
||||
cacheMtx.Unlock()
|
||||
|
||||
return starlarkRegex(regex), nil
|
||||
}
|
||||
|
||||
func regexCompileGlob(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var globStr string
|
||||
err := starlark.UnpackArgs("regex.compile_glob", args, kwargs, "glob", &globStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cacheMtx.Lock()
|
||||
regex, ok := regexCache[globStr]
|
||||
if !ok {
|
||||
regex, err = pcre.CompileGlob(globStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
regexCache[globStr] = regex
|
||||
}
|
||||
cacheMtx.Unlock()
|
||||
|
||||
return starlarkRegex(regex), nil
|
||||
}
|
||||
|
||||
func starlarkRegex(regex *pcre.Regexp) *starlarkstruct.Struct {
|
||||
return starlarkstruct.FromStringDict(starlarkstruct.Default, starlark.StringDict{
|
||||
"find_all": findAll(regex),
|
||||
"find_one": findOne(regex),
|
||||
"matches": matches(regex),
|
||||
})
|
||||
}
|
||||
|
||||
func findAll(regex *pcre.Regexp) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("regex.regexp.find_all", func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var in string
|
||||
err := starlark.UnpackArgs("regex.compile", args, kwargs, "in", &in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matches := regex.FindAllStringSubmatch(in, -1)
|
||||
return matchesToStarlark2D(matches), nil
|
||||
})
|
||||
}
|
||||
|
||||
func findOne(regex *pcre.Regexp) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("regex.regexp.find_one", func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var in string
|
||||
err := starlark.UnpackArgs("regex.compile", args, kwargs, "in", &in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
match := regex.FindStringSubmatch(in)
|
||||
return matchesToStarlark1D(match), nil
|
||||
})
|
||||
}
|
||||
|
||||
func matches(regex *pcre.Regexp) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("regex.regexp.find_one", func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var in string
|
||||
err := starlark.UnpackArgs("regex.compile", args, kwargs, "in", &in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
found := regex.MatchString(in)
|
||||
return starlark.Bool(found), nil
|
||||
})
|
||||
}
|
||||
|
||||
func matchesToStarlark2D(matches [][]string) *starlark.List {
|
||||
outer := make([]starlark.Value, len(matches))
|
||||
for i, match := range matches {
|
||||
outer[i] = matchesToStarlark1D(match)
|
||||
}
|
||||
return starlark.NewList(outer)
|
||||
}
|
||||
|
||||
func matchesToStarlark1D(match []string) *starlark.List {
|
||||
list := make([]starlark.Value, len(match))
|
||||
for j, val := range match {
|
||||
list[j] = starlark.String(val)
|
||||
}
|
||||
return starlark.NewList(list)
|
||||
}
|
49
internal/builtins/register.go
Normal file
49
internal/builtins/register.go
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* LURE Updater - Automated updater bot for LURE packages
|
||||
* Copyright (C) 2023 Elara Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"lure.sh/lure-updater/internal/config"
|
||||
"go.etcd.io/bbolt"
|
||||
"go.starlark.net/starlark"
|
||||
"go.starlark.net/starlarkjson"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Name string
|
||||
DB *bbolt.DB
|
||||
Config *config.Config
|
||||
Mux *http.ServeMux
|
||||
}
|
||||
|
||||
func Register(sd starlark.StringDict, opts *Options) {
|
||||
sd["run_every"] = starlark.NewBuiltin("run_every", runEvery)
|
||||
sd["sleep"] = starlark.NewBuiltin("sleep", sleep)
|
||||
sd["http"] = httpModule
|
||||
sd["regex"] = regexModule
|
||||
sd["store"] = storeModule(opts.DB, opts.Name)
|
||||
sd["updater"] = updaterModule(opts.Config)
|
||||
sd["log"] = logModule(opts.Name)
|
||||
sd["json"] = starlarkjson.Module
|
||||
sd["utils"] = utilsModule
|
||||
sd["html"] = htmlModule
|
||||
sd["register_webhook"] = registerWebhook(opts.Mux, opts.Config, opts.Name)
|
||||
}
|
102
internal/builtins/run_every.go
Normal file
102
internal/builtins/run_every.go
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* LURE Updater - Automated updater bot for LURE packages
|
||||
* Copyright (C) 2023 Elara Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.elara.ws/logger/log"
|
||||
"go.starlark.net/starlark"
|
||||
"go.starlark.net/starlarkstruct"
|
||||
)
|
||||
|
||||
var (
|
||||
tickerMtx = &sync.Mutex{}
|
||||
tickerCount = 0
|
||||
tickers = map[int]*time.Ticker{}
|
||||
)
|
||||
|
||||
func runEvery(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var every string
|
||||
var fn *starlark.Function
|
||||
err := starlark.UnpackArgs("run_every", args, kwargs, "every", &every, "function", &fn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d, err := time.ParseDuration(every)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tickerMtx.Lock()
|
||||
t := time.NewTicker(d)
|
||||
handle := tickerCount
|
||||
tickers[handle] = t
|
||||
tickerCount++
|
||||
tickerMtx.Unlock()
|
||||
log.Debug("Created new ticker").Int("handle", handle).Str("duration", every).Stringer("pos", thread.CallFrame(1).Pos).Send()
|
||||
|
||||
go func() {
|
||||
for range t.C {
|
||||
log.Debug("Calling scheduled function").Str("name", fn.Name()).Stringer("pos", fn.Position()).Send()
|
||||
_, err := starlark.Call(thread, fn, nil, nil)
|
||||
if err != nil {
|
||||
log.Warn("Error while executing scheduled function").Str("name", fn.Name()).Stringer("pos", fn.Position()).Err(err).Send()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return newTickerHandle(handle), nil
|
||||
}
|
||||
|
||||
func sleep(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var duration string
|
||||
err := starlark.UnpackArgs("sleep", args, kwargs, "duration", &duration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d, err := time.ParseDuration(duration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("Sleeping").Str("duration", duration).Stringer("pos", thread.CallFrame(1).Pos).Send()
|
||||
time.Sleep(d)
|
||||
return starlark.None, nil
|
||||
}
|
||||
|
||||
func stopTicker(handle int) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("stop", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
tickerMtx.Lock()
|
||||
tickers[handle].Stop()
|
||||
delete(tickers, handle)
|
||||
tickerMtx.Unlock()
|
||||
log.Debug("Stopped ticker").Int("handle", handle).Stringer("pos", thread.CallFrame(1).Pos).Send()
|
||||
return starlark.None, nil
|
||||
})
|
||||
}
|
||||
|
||||
func newTickerHandle(handle int) starlark.Value {
|
||||
return starlarkstruct.FromStringDict(starlarkstruct.Default, starlark.StringDict{
|
||||
"stop": stopTicker(handle),
|
||||
})
|
||||
}
|
116
internal/builtins/store.go
Normal file
116
internal/builtins/store.go
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* LURE Updater - Automated updater bot for LURE packages
|
||||
* Copyright (C) 2023 Elara Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"go.elara.ws/logger/log"
|
||||
"go.etcd.io/bbolt"
|
||||
"go.starlark.net/starlark"
|
||||
"go.starlark.net/starlarkstruct"
|
||||
)
|
||||
|
||||
func storeModule(db *bbolt.DB, bucketName string) *starlarkstruct.Module {
|
||||
return &starlarkstruct.Module{
|
||||
Name: "store",
|
||||
Members: starlark.StringDict{
|
||||
"set": storeSet(db, bucketName),
|
||||
"get": storeGet(db, bucketName),
|
||||
"delete": storeDelete(db, bucketName),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func storeSet(db *bbolt.DB, bucketName string) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("store.set", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var key, value string
|
||||
err := starlark.UnpackArgs("store.set", args, kwargs, "key", &key, "value", &value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.Update(func(tx *bbolt.Tx) error {
|
||||
bucket, err := tx.CreateBucketIfNotExists([]byte(bucketName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bucket.Put([]byte(key), []byte(value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("Set value").Str("bucket", bucketName).Str("key", key).Str("value", value).Stringer("pos", thread.CallFrame(1).Pos).Send()
|
||||
return starlark.None, nil
|
||||
})
|
||||
}
|
||||
|
||||
func storeGet(db *bbolt.DB, bucketName string) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("store.get", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var key string
|
||||
err := starlark.UnpackArgs("store.get", args, kwargs, "key", &key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var value string
|
||||
err = db.Update(func(tx *bbolt.Tx) error {
|
||||
bucket, err := tx.CreateBucketIfNotExists([]byte(bucketName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := bucket.Get([]byte(key))
|
||||
value = string(data)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("Retrieved value").Str("bucket", bucketName).Str("key", key).Str("value", value).Stringer("pos", thread.CallFrame(1).Pos).Send()
|
||||
return starlark.String(value), nil
|
||||
})
|
||||
}
|
||||
|
||||
func storeDelete(db *bbolt.DB, bucketName string) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("store.delete", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var key string
|
||||
err := starlark.UnpackArgs("store.delete", args, kwargs, "key", &key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.Update(func(tx *bbolt.Tx) error {
|
||||
bucket, err := tx.CreateBucketIfNotExists([]byte(bucketName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bucket.Delete([]byte(key))
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("Deleted value").Str("bucket", bucketName).Str("key", key).Stringer("pos", thread.CallFrame(1).Pos).Send()
|
||||
return starlark.None, nil
|
||||
})
|
||||
}
|
199
internal/builtins/updater.go
Normal file
199
internal/builtins/updater.go
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* LURE Updater - Automated updater bot for LURE packages
|
||||
* Copyright (C) 2023 Elara Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"go.elara.ws/logger/log"
|
||||
"lure.sh/lure-updater/internal/config"
|
||||
"go.starlark.net/starlark"
|
||||
"go.starlark.net/starlarkstruct"
|
||||
)
|
||||
|
||||
func updaterModule(cfg *config.Config) *starlarkstruct.Module {
|
||||
return &starlarkstruct.Module{
|
||||
Name: "updater",
|
||||
Members: starlark.StringDict{
|
||||
"repo_dir": starlark.String(cfg.Git.RepoDir),
|
||||
"pull": updaterPull(cfg),
|
||||
"push_changes": updaterPushChanges(cfg),
|
||||
"get_package_file": getPackageFile(cfg),
|
||||
"write_package_file": writePackageFile(cfg),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// repoMtx makes sure two starlark threads can
|
||||
// never access the repo at the same time
|
||||
var repoMtx = &sync.Mutex{}
|
||||
|
||||
func updaterPull(cfg *config.Config) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("updater.pull", func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
repoMtx.Lock()
|
||||
defer repoMtx.Unlock()
|
||||
|
||||
repo, err := git.PlainOpen(cfg.Git.RepoDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = w.Pull(&git.PullOptions{Progress: os.Stderr})
|
||||
if err != git.NoErrAlreadyUpToDate && err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return starlark.None, nil
|
||||
})
|
||||
}
|
||||
|
||||
func updaterPushChanges(cfg *config.Config) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("updater.push_changes", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var msg string
|
||||
err := starlark.UnpackArgs("updater.push_changes", args, kwargs, "msg", &msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repoMtx.Lock()
|
||||
defer repoMtx.Unlock()
|
||||
|
||||
repo, err := git.PlainOpen(cfg.Git.RepoDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status, err := w.Status()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if status.IsClean() {
|
||||
return starlark.None, nil
|
||||
}
|
||||
|
||||
err = w.Pull(&git.PullOptions{Progress: os.Stderr})
|
||||
if err != git.NoErrAlreadyUpToDate && err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = w.Add(".")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig := &object.Signature{
|
||||
Name: cfg.Git.Commit.Name,
|
||||
Email: cfg.Git.Commit.Email,
|
||||
When: time.Now(),
|
||||
}
|
||||
|
||||
h, err := w.Commit(msg, &git.CommitOptions{
|
||||
Author: sig,
|
||||
Committer: sig,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("Created new commit").Stringer("hash", h).Stringer("pos", thread.CallFrame(1).Pos).Send()
|
||||
|
||||
err = repo.Push(&git.PushOptions{
|
||||
Progress: os.Stderr,
|
||||
Auth: &http.BasicAuth{
|
||||
Username: cfg.Git.Credentials.Username,
|
||||
Password: cfg.Git.Credentials.Password,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("Successfully pushed to repo").Stringer("pos", thread.CallFrame(1).Pos).Send()
|
||||
|
||||
return starlark.None, nil
|
||||
})
|
||||
}
|
||||
|
||||
func getPackageFile(cfg *config.Config) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("updater.get_package_file", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var pkg, filename string
|
||||
err := starlark.UnpackArgs("updater.get_package_file", args, kwargs, "pkg", &pkg, "filename", &filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repoMtx.Lock()
|
||||
defer repoMtx.Unlock()
|
||||
|
||||
path := filepath.Join(cfg.Git.RepoDir, pkg, filename)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("Got package file").Str("package", pkg).Str("filename", filename).Stringer("pos", thread.CallFrame(1).Pos).Send()
|
||||
return starlark.String(data), nil
|
||||
})
|
||||
}
|
||||
|
||||
func writePackageFile(cfg *config.Config) *starlark.Builtin {
|
||||
return starlark.NewBuiltin("updater.write_package_file", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var pkg, filename, content string
|
||||
err := starlark.UnpackArgs("updater.write_package_file", args, kwargs, "pkg", &pkg, "filename", &filename, "content", &content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repoMtx.Lock()
|
||||
defer repoMtx.Unlock()
|
||||
|
||||
path := filepath.Join(cfg.Git.RepoDir, pkg, filename)
|
||||
fl, err := os.Create(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = io.Copy(fl, strings.NewReader(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("Wrote package file").Str("package", pkg).Str("filename", filename).Stringer("pos", thread.CallFrame(1).Pos).Send()
|
||||
return starlark.None, nil
|
||||
})
|
||||
}
|
23
internal/builtins/utils.go
Normal file
23
internal/builtins/utils.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"go.elara.ws/vercmp"
|
||||
"go.starlark.net/starlark"
|
||||
"go.starlark.net/starlarkstruct"
|
||||
)
|
||||
|
||||
var utilsModule = &starlarkstruct.Module{
|
||||
Name: "utils",
|
||||
Members: starlark.StringDict{
|
||||
"ver_cmp": starlark.NewBuiltin("utils.ver_cmp", utilsVerCmp),
|
||||
},
|
||||
}
|
||||
|
||||
func utilsVerCmp(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
var v1, v2 string
|
||||
err := starlark.UnpackArgs("utils.ver_cmp", args, kwargs, "v1", &v1, "v2", &v2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return starlark.MakeInt(vercmp.Compare(v1, v2)), nil
|
||||
}
|
45
internal/config/config.go
Normal file
45
internal/config/config.go
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* LURE Updater - Automated updater bot for LURE packages
|
||||
* Copyright (C) 2023 Elara Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
type Config struct {
|
||||
Git Git `toml:"git" envPrefix:"GIT_"`
|
||||
Webhook Webhook `toml:"webhook" envPrefix:"WEBHOOK_"`
|
||||
}
|
||||
|
||||
type Git struct {
|
||||
RepoDir string `toml:"repoDir" env:"REPO_DIR"`
|
||||
RepoURL string `toml:"repoURL" env:"REPO_URL"`
|
||||
Commit Commit `toml:"commit" envPrefix:"COMMIT_"`
|
||||
Credentials Credentials `toml:"credentials" envPrefix:"CREDENTIALS_"`
|
||||
}
|
||||
|
||||
type Credentials struct {
|
||||
Username string `toml:"username" env:"USERNAME"`
|
||||
Password string `toml:"password" env:"PASSWORD"`
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
Name string `toml:"name" env:"NAME"`
|
||||
Email string `toml:"email" env:"EMAIL"`
|
||||
}
|
||||
|
||||
type Webhook struct {
|
||||
PasswordHash string `toml:"pwd_hash" env:"PASSWORD_HASH"`
|
||||
}
|
87
internal/convert/convert.go
Normal file
87
internal/convert/convert.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"go.starlark.net/starlark"
|
||||
)
|
||||
|
||||
var ErrInvalidType = errors.New("unknown type")
|
||||
|
||||
func Convert(v any) (starlark.Value, error) {
|
||||
if v == nil {
|
||||
return starlark.None, nil
|
||||
}
|
||||
val := reflect.ValueOf(v)
|
||||
kind := val.Kind()
|
||||
for kind == reflect.Pointer || kind == reflect.Interface {
|
||||
val = val.Elem()
|
||||
}
|
||||
return convert(val)
|
||||
}
|
||||
|
||||
func convert(val reflect.Value) (starlark.Value, error) {
|
||||
switch val.Kind() {
|
||||
case reflect.Interface:
|
||||
return convert(val.Elem())
|
||||
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
|
||||
return starlark.MakeInt64(val.Int()), nil
|
||||
case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
|
||||
return starlark.MakeUint64(val.Uint()), nil
|
||||
case reflect.Float64, reflect.Float32:
|
||||
return starlark.Float(val.Float()), nil
|
||||
case reflect.Bool:
|
||||
return starlark.Bool(val.Bool()), nil
|
||||
case reflect.String:
|
||||
return starlark.String(val.String()), nil
|
||||
case reflect.Slice, reflect.Array:
|
||||
return convertSlice(val)
|
||||
case reflect.Map:
|
||||
return convertMap(val)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", ErrInvalidType, val.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func convertSlice(val reflect.Value) (starlark.Value, error) {
|
||||
// Detect byte slice
|
||||
if val.Type().Elem().Kind() == reflect.Uint8 {
|
||||
return starlark.Bytes(val.Bytes()), nil
|
||||
}
|
||||
|
||||
elems := make([]starlark.Value, val.Len())
|
||||
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
elem, err := convert(val.Index(i))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elems[i] = elem
|
||||
}
|
||||
|
||||
return starlark.NewList(elems), nil
|
||||
}
|
||||
|
||||
func convertMap(val reflect.Value) (starlark.Value, error) {
|
||||
dict := starlark.NewDict(val.Len())
|
||||
iter := val.MapRange()
|
||||
for iter.Next() {
|
||||
k, err := convert(iter.Key())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v, err := convert(iter.Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = dict.SetKey(k, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return dict, nil
|
||||
}
|
Reference in New Issue
Block a user