harness-drone/plugin/converter/starlark/starlark.go

138 lines
3.8 KiB
Go

// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package starlark
import (
"bytes"
"github.com/drone/drone/core"
"github.com/drone/drone/handler/api/errors"
"github.com/sirupsen/logrus"
"go.starlark.net/starlark"
)
const (
separator = "---"
newline = "\n"
)
// limits generated configuration file size.
const limit = 1000000
var (
// ErrMainMissing indicates the starlark script is missing
// the main method.
ErrMainMissing = errors.New("starlark: missing main function")
// ErrMainInvalid indicates the starlark script defines a
// global variable named main, however, it is not callable.
ErrMainInvalid = errors.New("starlark: main must be a function")
// ErrMainReturn indicates the starlark script's main method
// returns an invalid or unexpected type.
ErrMainReturn = errors.New("starlark: main returns an invalid type")
// ErrMaximumSize indicates the starlark script generated a
// file that exceeds the maximum allowed file size.
ErrMaximumSize = errors.New("starlark: maximum file size exceeded")
// ErrCannotLoad indicates the starlark script is attempting to
// load an external file which is currently restricted.
ErrCannotLoad = errors.New("starlark: cannot load external scripts")
)
func Parse(req *core.ConvertArgs, template *core.Template, templateData map[string]interface{}) (file *string, err error) {
thread := &starlark.Thread{
Name: "drone",
Load: noLoad,
Print: func(_ *starlark.Thread, msg string) {
logrus.WithFields(logrus.Fields{
"namespace": req.Repo.Namespace,
"name": req.Repo.Name,
}).Traceln(msg)
},
}
var starlarkFile string
var starlarkFileName string
if template != nil {
starlarkFile = template.Data
starlarkFileName = template.Name
} else {
starlarkFile = req.Config.Data
starlarkFileName = req.Repo.Config
}
globals, err := starlark.ExecFile(thread, starlarkFileName, starlarkFile, nil)
if err != nil {
return nil, err
}
// find the main method in the starlark script and
// cast to a callable type. If not callable the script
// is invalid.
mainVal, ok := globals["main"]
if !ok {
return nil, ErrMainMissing
}
main, ok := mainVal.(starlark.Callable)
if !ok {
return nil, ErrMainInvalid
}
// create the input args and invoke the main method
// using the input args.
args := createArgs(req.Repo, req.Build, templateData)
// set the maximum number of operations in the script. this
// mitigates long running scripts.
thread.SetMaxExecutionSteps(50000)
// execute the main method in the script.
mainVal, err = starlark.Call(thread, main, args, nil)
if err != nil {
return nil, err
}
buf := new(bytes.Buffer)
switch v := mainVal.(type) {
case *starlark.List:
for i := 0; i < v.Len(); i++ {
item := v.Index(i)
buf.WriteString(separator)
buf.WriteString(newline)
if err := write(buf, item); err != nil {
return nil, err
}
buf.WriteString(newline)
}
case *starlark.Dict:
if err := write(buf, v); err != nil {
return nil, err
}
default:
return nil, ErrMainReturn
}
// this is a temporary workaround until we
// implement a LimitWriter.
if b := buf.Bytes(); len(b) > limit {
return nil, nil
}
parsedFile := buf.String()
return &parsedFile, nil
}
func noLoad(_ *starlark.Thread, _ string) (starlark.StringDict, error) {
return nil, ErrCannotLoad
}