From 276f984db3d0fe8ec9235c08296cfd879c3e4e2a Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Fri, 20 Nov 2020 08:56:47 -0500 Subject: [PATCH] enable simple starlark scripts in core --- CHANGELOG.md | 5 + cmd/drone-server/config/config.go | 6 + cmd/drone-server/inject_plugin.go | 5 +- go.mod | 3 +- go.sum | 9 + plugin/converter/starlark.go | 50 ------ plugin/converter/starlark/args.go | 109 ++++++++++++ plugin/converter/starlark/starlark.go | 159 +++++++++++++++++ plugin/converter/starlark/starlark_test.go | 160 ++++++++++++++++++ plugin/converter/starlark/testdata/multi.star | 6 + .../starlark/testdata/multi.star.golden | 2 + .../converter/starlark/testdata/single.star | 11 ++ .../starlark/testdata/single.star.golden | 1 + plugin/converter/starlark/write.go | 99 +++++++++++ plugin/converter/starlark_oss.go | 27 --- 15 files changed, 573 insertions(+), 79 deletions(-) delete mode 100644 plugin/converter/starlark.go create mode 100644 plugin/converter/starlark/args.go create mode 100644 plugin/converter/starlark/starlark.go create mode 100644 plugin/converter/starlark/starlark_test.go create mode 100644 plugin/converter/starlark/testdata/multi.star create mode 100644 plugin/converter/starlark/testdata/multi.star.golden create mode 100644 plugin/converter/starlark/testdata/single.star create mode 100644 plugin/converter/starlark/testdata/single.star.golden create mode 100644 plugin/converter/starlark/write.go delete mode 100644 plugin/converter/starlark_oss.go diff --git a/CHANGELOG.md b/CHANGELOG.md index b34dd446..8c3d8dba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +### Added +- support for starlark scripts in core. +- support for executing pipelines in debug mode. + ## [1.9.2] ### Added - update go-scm dependency to fix diff --git a/cmd/drone-server/config/config.go b/cmd/drone-server/config/config.go index e9240ece..89514b9c 100644 --- a/cmd/drone-server/config/config.go +++ b/cmd/drone-server/config/config.go @@ -59,6 +59,7 @@ type ( Docker Docker HTTP HTTP Jsonnet Jsonnet + Starlark Starlark Logging Logging Prometheus Prometheus Proxy Proxy @@ -137,6 +138,11 @@ type ( Enabled bool `envconfig:"DRONE_JSONNET_ENABLED"` } + // Starlark configures the starlark plugin + Starlark struct { + Enabled bool `envconfig:"DRONE_STARLARK_ENABLED"` + } + // Kubernetes provides kubernetes configuration Kubernetes struct { Enabled bool `envconfig:"DRONE_KUBERNETES_ENABLED"` diff --git a/cmd/drone-server/inject_plugin.go b/cmd/drone-server/inject_plugin.go index 77cdfb15..ef2e7463 100644 --- a/cmd/drone-server/inject_plugin.go +++ b/cmd/drone-server/inject_plugin.go @@ -20,6 +20,7 @@ import ( "github.com/drone/drone/plugin/admission" "github.com/drone/drone/plugin/config" "github.com/drone/drone/plugin/converter" + "github.com/drone/drone/plugin/converter/starlark" "github.com/drone/drone/plugin/registry" "github.com/drone/drone/plugin/secret" "github.com/drone/drone/plugin/validator" @@ -79,7 +80,9 @@ func provideConfigPlugin(client *scm.Client, contents core.FileService, conf spe func provideConvertPlugin(client *scm.Client, conf spec.Config) core.ConvertService { return converter.Combine( converter.Legacy(false), - converter.Starlark(false), + starlark.New( + conf.Starlark.Enabled, + ), converter.Jsonnet( conf.Jsonnet.Enabled, ), diff --git a/go.mod b/go.mod index f97fee43..5cbaa771 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/drone/envsubst v1.0.3-0.20200709231038-aa43e1c1a629 github.com/drone/go-license v1.0.2 github.com/drone/go-login v1.0.4-0.20190311170324-2a4df4f242a2 - github.com/drone/go-scm v1.7.2-0.20201028160627-427b8a85897c + github.com/drone/go-scm v1.7.2-0.20201111225713-c0438b46084b github.com/drone/signal v1.0.0 github.com/dustin/go-humanize v1.0.0 github.com/go-chi/chi v3.3.3+incompatible @@ -62,6 +62,7 @@ require ( github.com/smartystreets/goconvey v1.6.4 // indirect github.com/ugorji/go v1.1.7 // indirect github.com/unrolled/secure v0.0.0-20181022170031-4b6b7cf51606 + go.starlark.net v0.0.0-20201118183435-e55f603d8c79 golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 golang.org/x/sync v0.0.0-20190423024810-112230192c58 gopkg.in/ini.v1 v1.57.0 // indirect diff --git a/go.sum b/go.sum index fbf9a23e..824d77d5 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,9 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2 github.com/buildkite/yaml v2.1.0+incompatible h1:xirI+ql5GzfikVNDmt+yeiXpf/v1Gt03qXTtT5WXdr8= github.com/buildkite/yaml v2.1.0+incompatible/go.mod h1:UoU8vbcwu1+vjZq01+KrpSeLBgQQIjL/H7Y6KwikUrI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -101,6 +104,8 @@ github.com/drone/go-scm v1.7.2-0.20201015134822-a014bb19b1df h1:CzwzLwv6h9HMJSph github.com/drone/go-scm v1.7.2-0.20201015134822-a014bb19b1df/go.mod h1:lXwfbyrIJwFFME5TpzavkwO2T5X8yBK6t6cve7g91x0= github.com/drone/go-scm v1.7.2-0.20201028160627-427b8a85897c h1:3Dv6guONE4nry6fvwEvFz/+pnC0tsMudkrz6CwRO3KM= github.com/drone/go-scm v1.7.2-0.20201028160627-427b8a85897c/go.mod h1:lXwfbyrIJwFFME5TpzavkwO2T5X8yBK6t6cve7g91x0= +github.com/drone/go-scm v1.7.2-0.20201111225713-c0438b46084b h1:ivLeFPmHN+9sLMVAF7HvgvEglU5tzoqlzePLY5zKPo8= +github.com/drone/go-scm v1.7.2-0.20201111225713-c0438b46084b/go.mod h1:lXwfbyrIJwFFME5TpzavkwO2T5X8yBK6t6cve7g91x0= github.com/drone/signal v1.0.0 h1:NrnM2M/4yAuU/tXs6RP1a1ZfxnaHwYkd0kJurA1p6uI= github.com/drone/signal v1.0.0/go.mod h1:S8t92eFT0g4WUgEc/LxG+LCuiskpMNsG0ajAMGnyZpc= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= @@ -374,6 +379,8 @@ github.com/unrolled/secure v0.0.0-20181022170031-4b6b7cf51606 h1:dU9yXzNi9rl6Mou github.com/unrolled/secure v0.0.0-20181022170031-4b6b7cf51606/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= github.com/vinzenz/yaml v0.0.0-20170920082545-91409cdd725d h1:3wDi6J5APMqaHBVPuVd7RmHD2gRTfqbdcVSpCNoUWtk= github.com/vinzenz/yaml v0.0.0-20170920082545-91409cdd725d/go.mod h1:mb5taDqMnJiZNRQ3+02W2IFG+oEz1+dTuCXkp4jpkfo= +go.starlark.net v0.0.0-20201118183435-e55f603d8c79 h1:JPjLPz44y2N9mkzh2N344kTk1Y4/V4yJAjTrXGmzv8I= +go.starlark.net v0.0.0-20201118183435-e55f603d8c79/go.mod h1:5YFcFnRptTN+41758c2bMPiqpGg4zBfYji1IQz8wNFk= golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -425,6 +432,8 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= diff --git a/plugin/converter/starlark.go b/plugin/converter/starlark.go deleted file mode 100644 index e350ba20..00000000 --- a/plugin/converter/starlark.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2019 Drone.IO Inc. All rights reserved. -// Use of this source code is governed by the Drone Non-Commercial License -// that can be found in the LICENSE file. - -// +build !oss - -package converter - -import ( - "bytes" - "context" - "strings" - - "github.com/drone/drone/core" -) - -// Starlark returns a conversion service that converts the -// starlark file to a yaml file. -func Starlark(enabled bool) core.ConvertService { - return &starlarkPlugin{ - enabled: enabled, - } -} - -type starlarkPlugin struct { - enabled bool -} - -func (p *starlarkPlugin) Convert(ctx context.Context, req *core.ConvertArgs) (*core.Config, error) { - if p.enabled == false { - return nil, nil - } - - // if the file extension is not jsonnet we can - // skip this plugin by returning zero values. - switch { - case strings.HasSuffix(req.Repo.Config, ".script"): - case strings.HasSuffix(req.Repo.Config, ".star"): - case strings.HasSuffix(req.Repo.Config, ".starlark"): - default: - return nil, nil - } - - // convert the starlark file to yaml - buf := new(bytes.Buffer) - - return &core.Config{ - Data: buf.String(), - }, nil -} diff --git a/plugin/converter/starlark/args.go b/plugin/converter/starlark/args.go new file mode 100644 index 00000000..3ea86bb7 --- /dev/null +++ b/plugin/converter/starlark/args.go @@ -0,0 +1,109 @@ +// 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 ( + "github.com/drone/drone/core" + + "go.starlark.net/starlark" + "go.starlark.net/starlarkstruct" +) + +// TODO(bradrydzewski) add repository id +// TODO(bradrydzewski) add repository timeout +// TODO(bradrydzewski) add repository counter +// TODO(bradrydzewski) add repository created +// TODO(bradrydzewski) add repository updated +// TODO(bradrydzewski) add repository synced +// TODO(bradrydzewski) add repository version + +// TODO(bradrydzewski) add build id, will always be zero value +// TODO(bradrydzewski) add build number, will always be zero value +// TODO(bradrydzewski) add build started, will always be zero value +// TODO(bradrydzewski) add build finished, will always be zero value +// TODO(bradrydzewski) add build created, will always be zero value +// TODO(bradrydzewski) add build updated, will always be zero value +// TODO(bradrydzewski) add build parent +// TODO(bradrydzewski) add build timestamp + +func createArgs(repo *core.Repository, build *core.Build) []starlark.Value { + return []starlark.Value{ + starlarkstruct.FromStringDict( + starlark.String("context"), + starlark.StringDict{ + "repo": starlarkstruct.FromStringDict(starlark.String("repo"), fromRepo(repo)), + "build": starlarkstruct.FromStringDict(starlark.String("build"), fromBuild(build)), + }, + ), + } +} + +func fromBuild(v *core.Build) starlark.StringDict { + return starlark.StringDict{ + "event": starlark.String(v.Event), + "action": starlark.String(v.Action), + "cron": starlark.String(v.Cron), + "environment": starlark.String(v.Deploy), + "link": starlark.String(v.Link), + "branch": starlark.String(v.Target), + "source": starlark.String(v.Source), + "before": starlark.String(v.Before), + "after": starlark.String(v.After), + "target": starlark.String(v.Target), + "ref": starlark.String(v.Ref), + "commit": starlark.String(v.After), + "title": starlark.String(v.Title), + "message": starlark.String(v.Message), + "source_repo": starlark.String(v.Fork), + "author_login": starlark.String(v.Author), + "author_name": starlark.String(v.AuthorName), + "author_email": starlark.String(v.AuthorEmail), + "author_avatar": starlark.String(v.AuthorAvatar), + "sender": starlark.String(v.Sender), + "params": fromMap(v.Params), + } +} + +func fromRepo(v *core.Repository) starlark.StringDict { + return starlark.StringDict{ + "uid": starlark.String(v.UID), + "name": starlark.String(v.Name), + "namespace": starlark.String(v.Namespace), + "slug": starlark.String(v.Slug), + "git_http_url": starlark.String(v.HTTPURL), + "git_ssh_url": starlark.String(v.SSHURL), + "link": starlark.String(v.Link), + "branch": starlark.String(v.Branch), + "config": starlark.String(v.Config), + "private": starlark.Bool(v.Private), + "visibility": starlark.String(v.Visibility), + "active": starlark.Bool(v.Active), + "trusted": starlark.Bool(v.Trusted), + "protected": starlark.Bool(v.Protected), + "ignore_forks": starlark.Bool(v.IgnoreForks), + "ignore_pull_requests": starlark.Bool(v.IgnorePulls), + } +} + +func fromMap(m map[string]string) *starlark.Dict { + dict := new(starlark.Dict) + for k, v := range m { + dict.SetKey( + starlark.String(k), + starlark.String(v), + ) + } + return dict +} diff --git a/plugin/converter/starlark/starlark.go b/plugin/converter/starlark/starlark.go new file mode 100644 index 00000000..119dd498 --- /dev/null +++ b/plugin/converter/starlark/starlark.go @@ -0,0 +1,159 @@ +// 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" + "context" + "errors" + "strings" + + "github.com/drone/drone/core" + "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") +) + +// New returns a conversion service that converts the +// starlark file to a yaml file. +func New(enabled bool) core.ConvertService { + return &starlarkPlugin{ + enabled: enabled, + } +} + +type starlarkPlugin struct { + enabled bool +} + +func (p *starlarkPlugin) Convert(ctx context.Context, req *core.ConvertArgs) (*core.Config, error) { + if p.enabled == false { + return nil, nil + } + + // if the file extension is not jsonnet we can + // skip this plugin by returning zero values. + switch { + case strings.HasSuffix(req.Repo.Config, ".script"): + case strings.HasSuffix(req.Repo.Config, ".star"): + case strings.HasSuffix(req.Repo.Config, ".starlark"): + default: + return nil, nil + } + + 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) + }, + } + globals, err := starlark.ExecFile(thread, req.Repo.Config, []byte(req.Config.Data), 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) + + // 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, ErrMaximumSize + } + + return &core.Config{ + Data: buf.String(), + }, nil +} + +func noLoad(_ *starlark.Thread, _ string) (starlark.StringDict, error) { + return nil, ErrCannotLoad +} diff --git a/plugin/converter/starlark/starlark_test.go b/plugin/converter/starlark/starlark_test.go new file mode 100644 index 00000000..8dba44a1 --- /dev/null +++ b/plugin/converter/starlark/starlark_test.go @@ -0,0 +1,160 @@ +// 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 ( + "context" + "io/ioutil" + "testing" + + "github.com/drone/drone/core" +) + +var noContext = context.Background() + +func TestConvert(t *testing.T) { + plugin := New(true) + + req := &core.ConvertArgs{ + Build: &core.Build{ + After: "3d21ec53a331a6f037a91c368710b99387d012c1", + }, + Repo: &core.Repository{ + Slug: "octocat/hello-world", + Config: ".drone.yml", + }, + Config: &core.Config{}, + } + + config, err := plugin.Convert(noContext, req) + if err != nil { + t.Error(err) + return + } + if config != nil { + t.Error("Want nil config when configuration is not starlark file") + return + } + + before, err := ioutil.ReadFile("testdata/single.star") + if err != nil { + t.Error(err) + return + } + after, err := ioutil.ReadFile("testdata/single.star.golden") + if err != nil { + t.Error(err) + return + } + + req.Repo.Config = "single.star" + req.Config.Data = string(before) + config, err = plugin.Convert(noContext, req) + if err != nil { + t.Error(err) + return + } + if config == nil { + t.Error("Want non-nil configuration") + return + } + + if want, got := config.Data, string(after); want != got { + t.Errorf("Want %q got %q", want, got) + } +} + +// this test verifies the starlark file can generate a multi-document +// yaml file that defines multiple pipelines. +func TestConvert_Multi(t *testing.T) { + before, err := ioutil.ReadFile("testdata/multi.star") + if err != nil { + t.Error(err) + return + } + after, err := ioutil.ReadFile("testdata/multi.star.golden") + if err != nil { + t.Error(err) + return + } + + req := &core.ConvertArgs{ + Build: &core.Build{ + After: "3d21ec53a331a6f037a91c368710b99387d012c1", + }, + Repo: &core.Repository{ + Slug: "octocat/hello-world", + Config: ".drone.star", + }, + Config: &core.Config{ + Data: string(before), + }, + } + + plugin := New(true) + config, err := plugin.Convert(noContext, req) + if err != nil { + t.Error(err) + return + } + + config, err = plugin.Convert(noContext, req) + if err != nil { + t.Error(err) + return + } + if config == nil { + t.Error("Want non-nil configuration") + return + } + + if want, got := config.Data, string(after); want != got { + t.Errorf("Want %q got %q", want, got) + } +} + +// this test verifies the plugin is skipped when it has +// not been explicitly enabled. +func TestConvert_Skip(t *testing.T) { + plugin := New(false) + config, err := plugin.Convert(noContext, nil) + if err != nil { + t.Error(err) + return + } + if config != nil { + t.Errorf("Expect nil config returned when plugin disabled") + } +} + +// this test verifies the plugin is skipped when the config +// file extension is not a starlark extension. +func TestConvert_SkipYaml(t *testing.T) { + req := &core.ConvertArgs{ + Repo: &core.Repository{ + Config: ".drone.yaml", + }, + } + + plugin := New(true) + config, err := plugin.Convert(noContext, req) + if err != nil { + t.Error(err) + return + } + if config != nil { + t.Errorf("Expect nil config returned for non-starlark files") + } +} diff --git a/plugin/converter/starlark/testdata/multi.star b/plugin/converter/starlark/testdata/multi.star new file mode 100644 index 00000000..0ea00f92 --- /dev/null +++ b/plugin/converter/starlark/testdata/multi.star @@ -0,0 +1,6 @@ +def main(ctx): + return [{ + 'kind': 'pipeline', + 'type': 'docker', + 'name': 'default' + }] \ No newline at end of file diff --git a/plugin/converter/starlark/testdata/multi.star.golden b/plugin/converter/starlark/testdata/multi.star.golden new file mode 100644 index 00000000..4c81db20 --- /dev/null +++ b/plugin/converter/starlark/testdata/multi.star.golden @@ -0,0 +1,2 @@ +--- +{"kind": "pipeline", "type": "docker", "name": "default"} diff --git a/plugin/converter/starlark/testdata/single.star b/plugin/converter/starlark/testdata/single.star new file mode 100644 index 00000000..52be3058 --- /dev/null +++ b/plugin/converter/starlark/testdata/single.star @@ -0,0 +1,11 @@ +def main(ctx): + print(ctx.build) + print(ctx.build.commit) + print(ctx.repo) + print(ctx.repo.namespace) + print(ctx.repo.name) + return { + 'kind': 'pipeline', + 'type': 'docker', + 'name': 'default' + } diff --git a/plugin/converter/starlark/testdata/single.star.golden b/plugin/converter/starlark/testdata/single.star.golden new file mode 100644 index 00000000..33a6f440 --- /dev/null +++ b/plugin/converter/starlark/testdata/single.star.golden @@ -0,0 +1 @@ +{"kind": "pipeline", "type": "docker", "name": "default"} \ No newline at end of file diff --git a/plugin/converter/starlark/write.go b/plugin/converter/starlark/write.go new file mode 100644 index 00000000..5b5f5f04 --- /dev/null +++ b/plugin/converter/starlark/write.go @@ -0,0 +1,99 @@ +// 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 ( + "encoding/json" + "fmt" + "io" + + "go.starlark.net/starlark" +) + +type writer interface { + io.Writer + io.ByteWriter + io.StringWriter +} + +func write(out writer, v starlark.Value) error { + if marshaler, ok := v.(json.Marshaler); ok { + jsonData, err := marshaler.MarshalJSON() + if err != nil { + return err + } + out.Write(jsonData) + return nil + } + + switch v := v.(type) { + case starlark.NoneType: + out.WriteString("null") + case starlark.Bool: + fmt.Fprintf(out, "%t", v) + case starlark.Int: + out.WriteString(v.String()) + case starlark.Float: + fmt.Fprintf(out, "%g", v) + case starlark.String: + s := string(v) + if isQuoteSafe(s) { + fmt.Fprintf(out, "%q", s) + } else { + data, _ := json.Marshal(s) + out.Write(data) + } + case starlark.Indexable: + out.WriteByte('[') + for i, n := 0, starlark.Len(v); i < n; i++ { + if i > 0 { + out.WriteString(", ") + } + if err := write(out, v.Index(i)); err != nil { + return err + } + } + out.WriteByte(']') + case *starlark.Dict: + out.WriteByte('{') + for i, itemPair := range v.Items() { + key := itemPair[0] + value := itemPair[1] + if i > 0 { + out.WriteString(", ") + } + if err := write(out, key); err != nil { + return err + } + out.WriteString(": ") + if err := write(out, value); err != nil { + return err + } + } + out.WriteByte('}') + default: + return fmt.Errorf("value %s (type `%s') can't be converted to JSON", v.String(), v.Type()) + } + return nil +} + +func isQuoteSafe(s string) bool { + for _, r := range s { + if r < 0x20 || r >= 0x10000 { + return false + } + } + return true +} diff --git a/plugin/converter/starlark_oss.go b/plugin/converter/starlark_oss.go deleted file mode 100644 index 51a04299..00000000 --- a/plugin/converter/starlark_oss.go +++ /dev/null @@ -1,27 +0,0 @@ -// 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. - -// +build oss - -package converter - -import ( - "github.com/drone/drone/core" -) - -// Starlark returns a conversion service that converts the -// starlark file to a yaml file. -func Starlark(enabled bool) core.ConvertService { - return new(noop) -}