Initial Commit
This commit is contained in:
		
							
								
								
									
										344
									
								
								internal/builtins/http.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										344
									
								
								internal/builtins/http.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,344 @@
 | 
			
		||||
package builtins
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"go.elara.ws/logger/log"
 | 
			
		||||
	"go.elara.ws/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")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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))
 | 
			
		||||
	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":    starlarkBody(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":        starlarkBody(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 starlarkBody(body io.ReadCloser) *starlarkstruct.Struct {
 | 
			
		||||
	return starlarkstruct.FromStringDict(starlarkstruct.Default, starlark.StringDict{
 | 
			
		||||
		"string": bodyAsString(body),
 | 
			
		||||
		"bytes":  bodyAsBytes(body),
 | 
			
		||||
		"close":  bodyClose(body),
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func bodyAsBytes(body io.ReadCloser) *starlark.Builtin {
 | 
			
		||||
	return starlark.NewBuiltin("http.response.body.bytes", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
 | 
			
		||||
		data, err := io.ReadAll(io.LimitReader(body, maxBodySize))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		return starlark.Bytes(data), nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func bodyAsString(body io.ReadCloser) *starlark.Builtin {
 | 
			
		||||
	return starlark.NewBuiltin("http.response.body.string", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
 | 
			
		||||
		data, err := io.ReadAll(io.LimitReader(body, maxBodySize))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		return starlark.String(data), nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func bodyClose(body io.ReadCloser) *starlark.Builtin {
 | 
			
		||||
	return starlark.NewBuiltin("http.response.body.close", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
 | 
			
		||||
		err := body.Close()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		return starlark.None, nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 func(res http.ResponseWriter, req *http.Request) {
 | 
			
		||||
		defer req.Body.Close()
 | 
			
		||||
 | 
			
		||||
		res.Header().Add("X-Updater-Plugin", pluginName)
 | 
			
		||||
 | 
			
		||||
		if secure {
 | 
			
		||||
			err := verifySecure(cfg.Webhook.PasswordHash, pluginName, req)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("Error verifying webhook").Err(err).Send()
 | 
			
		||||
				res.WriteHeader(http.StatusForbidden)
 | 
			
		||||
				_, _ = io.WriteString(res, err.Error())
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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 {
 | 
			
		||||
			log.Error("Error while executing webhook").Err(err).Stringer("pos", fn.Position()).Send()
 | 
			
		||||
			res.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
			_, _ = io.WriteString(res, err.Error())
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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 {
 | 
			
		||||
				log.Error("Error unpacking returned body").Err(err).Send()
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			_, err = io.Copy(res, body)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("Error writing body").Err(err).Send()
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		case *starlark.Dict:
 | 
			
		||||
			code := http.StatusOK
 | 
			
		||||
			codeVal, ok, _ := val.Get(starlark.String("code"))
 | 
			
		||||
			if ok {
 | 
			
		||||
				err = starlark.AsInt(codeVal, &code)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Error("Error decoding returned status code").Err(err).Send()
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				res.WriteHeader(code)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			body := newBodyReader()
 | 
			
		||||
			bodyVal, ok, _ := val.Get(starlark.String("body"))
 | 
			
		||||
			if ok {
 | 
			
		||||
				err = body.Unpack(bodyVal)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Error("Error unpacking returned body").Err(err).Send()
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				_, err = io.Copy(res, body)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Error("Error writing body").Err(err).Send()
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println(string(pwd))
 | 
			
		||||
 | 
			
		||||
	if err := bcrypt.CompareHashAndPassword([]byte(pwdHash), pwd); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										76
									
								
								internal/builtins/log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								internal/builtins/log.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										127
									
								
								internal/builtins/regex.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								internal/builtins/regex.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,127 @@
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								internal/builtins/register.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								internal/builtins/register.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
package builtins
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"go.elara.ws/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["register_webhook"] = registerWebhook(opts.Mux, opts.Config, opts.Name)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								internal/builtins/run_every.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								internal/builtins/run_every.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
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),
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										98
									
								
								internal/builtins/store.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								internal/builtins/store.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
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
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										181
									
								
								internal/builtins/updater.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								internal/builtins/updater.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,181 @@
 | 
			
		||||
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"
 | 
			
		||||
	"go.elara.ws/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
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								internal/config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								internal/config/config.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Git     Git     `toml:"git"`
 | 
			
		||||
	Webhook Webhook `toml:"webhook"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Git struct {
 | 
			
		||||
	RepoDir     string      `toml:"repoDir"`
 | 
			
		||||
	RepoURL     string      `toml:"repoURL"`
 | 
			
		||||
	Commit      Commit      `toml:"commit"`
 | 
			
		||||
	Credentials Credentials `toml:"credentials"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Credentials struct {
 | 
			
		||||
	Username string
 | 
			
		||||
	Password string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Commit struct {
 | 
			
		||||
	Name  string `toml:"name"`
 | 
			
		||||
	Email string `toml:"email"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Webhook struct {
 | 
			
		||||
	PasswordHash string `toml:"pwd_hash"`
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user