d2b09d854f
Implement a new error handling library based on pkg/errors. It provides stack saving on wrapping and exports some function to add stack saving also to external errors. It also implements custom zerolog error formatting without adding too much verbosity by just printing the chain error file:line without a full stack trace of every error. * Add a --detailed-errors options to print error with they full chain * Wrap all error returns. Use errors.WithStack to wrap without adding a new messsage and error.Wrap[f] to add a message. * Add golangci-lint wrapcheck to check that external packages errors are wrapped. This won't check that internal packages error are wrapped. But we want also to ensure this case so we'll have to find something else to check also these.
161 lines
4.7 KiB
Go
161 lines
4.7 KiB
Go
// Copyright 2020 Sorint.lab
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package config
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"agola.io/agola/internal/errors"
|
|
"go.starlark.net/starlark"
|
|
)
|
|
|
|
func starlarkArgs(cc *ConfigContext) (starlark.Tuple, error) {
|
|
d := &starlark.Dict{}
|
|
if err := d.SetKey(starlark.String("ref_type"), starlark.String(cc.RefType)); err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
if err := d.SetKey(starlark.String("ref"), starlark.String(cc.Ref)); err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
if err := d.SetKey(starlark.String("branch"), starlark.String(cc.Branch)); err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
if err := d.SetKey(starlark.String("tag"), starlark.String(cc.Tag)); err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
if err := d.SetKey(starlark.String("pull_request_id"), starlark.String(cc.PullRequestID)); err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
if err := d.SetKey(starlark.String("commit_sha"), starlark.String(cc.CommitSHA)); err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
return []starlark.Value{d}, nil
|
|
}
|
|
|
|
// based on (not existing anymore) function provided in
|
|
// https://github.com/google/starlark-go/blob/6fffce7528ee0fce17d72a4abe2919f464225968/starlarkstruct/struct.go#L325
|
|
// with changes to use go json marshalling functions
|
|
func starlarkJSON(out *bytes.Buffer, v starlark.Value) error {
|
|
switch v := v.(type) {
|
|
case starlark.NoneType:
|
|
out.WriteString("null")
|
|
case starlark.Bool:
|
|
fmt.Fprintf(out, "%t", v)
|
|
case starlark.Int:
|
|
data, err := json.Marshal(v.BigInt())
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
out.Write(data)
|
|
case starlark.Float:
|
|
data, err := json.Marshal(float64(v))
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
out.Write(data)
|
|
case starlark.String:
|
|
// we have to use a json Encoder to disable noisy html
|
|
// escaping. But the encoder appends a final \n so we
|
|
// also should remove it.
|
|
data := &bytes.Buffer{}
|
|
e := json.NewEncoder(data)
|
|
e.SetEscapeHTML(false)
|
|
if err := e.Encode(string(v)); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
// remove final \n introduced by the encoder
|
|
out.Write(bytes.TrimSuffix(data.Bytes(), []byte("\n")))
|
|
case starlark.Indexable: // Tuple, List
|
|
out.WriteByte('[')
|
|
for i, n := 0, starlark.Len(v); i < n; i++ {
|
|
if i > 0 {
|
|
out.WriteString(", ")
|
|
}
|
|
if err := starlarkJSON(out, v.Index(i)); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
}
|
|
out.WriteByte(']')
|
|
case *starlark.Dict:
|
|
out.WriteByte('{')
|
|
for i, item := range v.Items() {
|
|
if i > 0 {
|
|
out.WriteString(", ")
|
|
}
|
|
if _, ok := item[0].(starlark.String); !ok {
|
|
return errors.Errorf("cannot convert non-string dict key to JSON")
|
|
}
|
|
if err := starlarkJSON(out, item[0]); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
out.WriteString(": ")
|
|
if err := starlarkJSON(out, item[1]); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
}
|
|
out.WriteByte('}')
|
|
|
|
default:
|
|
return errors.Errorf("cannot convert starlark type %q to JSON", v.Type())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func execStarlark(configData []byte, configContext *ConfigContext) ([]byte, error) {
|
|
thread := &starlark.Thread{
|
|
Name: "agola-starlark",
|
|
// TODO(sgotti) redirect print to a logger?
|
|
Print: func(_ *starlark.Thread, msg string) {},
|
|
}
|
|
globals, err := starlark.ExecFile(thread, "config.star", configData, nil)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
// we require a main function that will be called wiht one
|
|
// arguments containing the config context
|
|
mainVal, ok := globals["main"]
|
|
if !ok {
|
|
return nil, errors.Errorf("no main function in starlark config")
|
|
}
|
|
main, ok := mainVal.(starlark.Callable)
|
|
if !ok {
|
|
return nil, errors.Errorf("main in starlark config is not a function")
|
|
}
|
|
args, err := starlarkArgs(configContext)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "cannot create startlark arguments")
|
|
}
|
|
mainVal, err = starlark.Call(thread, main, args, nil)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
switch v := mainVal.(type) {
|
|
case *starlark.Dict:
|
|
if err := starlarkJSON(buf, v); err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
default:
|
|
return nil, errors.Errorf("wrong starlark output, must be a dict")
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|