Merge pull request #3085 from drone/feature/templates-jsonnet

add jsonnet support for build templates
This commit is contained in:
Brad Rydzewski 2021-06-04 10:36:01 -04:00 committed by GitHub
commit ef168cd2a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 265 additions and 35 deletions

View file

@ -7,13 +7,11 @@
package converter
import (
"bytes"
"context"
"strings"
"github.com/drone/drone/core"
"github.com/google/go-jsonnet"
"github.com/drone/drone/plugin/converter/jsonnet"
)
// TODO(bradrydzewski) handle jsonnet imports
@ -42,32 +40,12 @@ func (p *jsonnetPlugin) Convert(ctx context.Context, req *core.ConvertArgs) (*co
return nil, nil
}
// create the jsonnet vm
vm := jsonnet.MakeVM()
vm.MaxStack = 500
vm.StringOutput = false
vm.ErrorFormatter.SetMaxStackTraceSize(20)
file, err := jsonnet.Parse(req, nil, nil)
// convert the jsonnet file to yaml
buf := new(bytes.Buffer)
docs, err := vm.EvaluateSnippetStream(req.Repo.Config, req.Config.Data)
if err != nil {
doc, err2 := vm.EvaluateSnippet(req.Repo.Config, req.Config.Data)
if err2 != nil {
return nil, err
}
docs = append(docs, doc)
}
// the jsonnet vm returns a stream of yaml documents
// that need to be combined into a single yaml file.
for _, doc := range docs {
buf.WriteString("---")
buf.WriteString("\n")
buf.WriteString(doc)
}
return &core.Config{
Data: buf.String(),
Data: file,
}, nil
}

View file

@ -0,0 +1,56 @@
package jsonnet
import (
"bytes"
"fmt"
"github.com/drone/drone/core"
"github.com/google/go-jsonnet"
)
func Parse(req *core.ConvertArgs, template *core.Template, templateData map[string]interface{}) (string, error) {
// create the jsonnet vm
vm := jsonnet.MakeVM()
vm.MaxStack = 500
vm.StringOutput = false
vm.ErrorFormatter.SetMaxStackTraceSize(20)
var jsonnetFile string
var jsonentFileName string
if template != nil {
jsonnetFile = template.Data
jsonentFileName = template.Name
} else {
jsonnetFile = req.Config.Data
jsonentFileName = req.Repo.Config
}
// map external inputs
if len(templateData) != 0 {
for k, v := range templateData {
key := fmt.Sprintf("input." + k)
val := fmt.Sprint(v)
vm.ExtVar(key, val)
}
}
// convert the jsonnet file to yaml
buf := new(bytes.Buffer)
docs, err := vm.EvaluateSnippetStream(jsonentFileName, jsonnetFile)
if err != nil {
doc, err2 := vm.EvaluateSnippet(jsonentFileName, jsonnetFile)
if err2 != nil {
return "", err
}
docs = append(docs, doc)
}
// the jsonnet vm returns a stream of yaml documents
// that need to be combined into a single yaml file.
for _, doc := range docs {
buf.WriteString("---")
buf.WriteString("\n")
buf.WriteString(doc)
}
return buf.String(), nil
}

View file

@ -0,0 +1,94 @@
package jsonnet
import (
"io/ioutil"
"testing"
"github.com/drone/drone/core"
)
func TestParse(t *testing.T) {
before, err := ioutil.ReadFile("../testdata/input.jsonnet")
if err != nil {
t.Error(err)
return
}
after, err := ioutil.ReadFile("../testdata/input.jsonnet.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.yml",
},
Config: &core.Config{},
}
template := &core.Template{
Name: "my_template.jsonnet",
Data: string(before),
}
templateData := map[string]interface{}{
"stepName": "my_step",
"image": "my_image",
"commands": "my_command",
}
req.Config.Data = string(before)
parsedFile, err := Parse(req, template, templateData)
if err != nil {
t.Error(err)
return
}
if want, got := parsedFile, string(after); want != got {
t.Errorf("Want %q got %q", want, got)
}
}
func TestParseJsonnetNotTemplateFile(t *testing.T) {
before, err := ioutil.ReadFile("../testdata/single.jsonnet")
if err != nil {
t.Error(err)
return
}
after, err := ioutil.ReadFile("../testdata/input.jsonnet.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.jsonnet",
},
Config: &core.Config{},
}
req.Repo.Config = "plugin.jsonnet"
req.Config.Data = string(before)
parsedFile, err := Parse(req, nil, nil)
if err != nil {
t.Error(err)
return
}
if want, got := parsedFile, string(after); want != got {
t.Errorf("Want %q got %q", want, got)
}
}

View file

@ -17,13 +17,13 @@ package converter
import (
"context"
"errors"
"github.com/drone/drone/core"
"github.com/drone/drone/plugin/converter/starlark"
"regexp"
"strings"
"github.com/drone/drone/core"
"github.com/drone/drone/plugin/converter/jsonnet"
"github.com/drone/drone/plugin/converter/starlark"
"gopkg.in/yaml.v2"
)
@ -67,7 +67,7 @@ func (p *templatePlugin) Convert(ctx context.Context, req *core.ConvertArgs) (*c
if template == nil {
return nil, ErrTemplateNotFound
}
// Check if file is Starlark
// Check if file is of type Starlark
if strings.HasSuffix(templateArgs.Load, ".script") ||
strings.HasSuffix(templateArgs.Load, ".star") ||
strings.HasSuffix(templateArgs.Load, ".starlark") {
@ -80,5 +80,16 @@ func (p *templatePlugin) Convert(ctx context.Context, req *core.ConvertArgs) (*c
Data: file,
}, nil
}
// Check if the file is of type Jsonnet
if strings.HasSuffix(templateArgs.Load, ".jsonnet") {
file, err := jsonnet.Parse(req, template, templateArgs.Data)
if err != nil {
return nil, err
}
return &core.Config{
Data: file,
}, nil
}
return nil, nil
}

View file

@ -24,7 +24,7 @@ import (
"github.com/golang/mock/gomock"
)
func TestTemplatePluginConvert(t *testing.T) {
func TestTemplatePluginConvertStarlark(t *testing.T) {
templateArgs, err := ioutil.ReadFile("testdata/starlark.template.yml")
if err != nil {
t.Error(err)
@ -175,3 +175,65 @@ func TestTemplatePluginConvertTemplateNotFound(t *testing.T) {
t.Errorf("template converter: template name given not found")
}
}
func TestTemplatePluginConvertJsonnet(t *testing.T) {
templateArgs, err := ioutil.ReadFile("testdata/jsonnet.template.yml")
if err != nil {
t.Error(err)
return
}
req := &core.ConvertArgs{
Build: &core.Build{
After: "3d21ec53a331a6f037a91c368710b99387d012c1",
},
Repo: &core.Repository{
Slug: "octocat/hello-world",
Config: ".drone.yml",
Namespace: "octocat",
},
Config: &core.Config{
Data: string(templateArgs),
},
}
beforeInput, err := ioutil.ReadFile("testdata/input.jsonnet")
if err != nil {
t.Error(err)
return
}
after, err := ioutil.ReadFile("testdata/input.jsonnet.golden")
if err != nil {
t.Error(err)
return
}
template := &core.Template{
Name: "plugin.jsonnet",
Data: string(beforeInput),
Namespace: "octocat",
}
controller := gomock.NewController(t)
defer controller.Finish()
templates := mock.NewMockTemplateStore(controller)
templates.EXPECT().FindName(gomock.Any(), template.Name, req.Repo.Namespace).Return(template, nil)
plugin := Template(templates)
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)
}
}

View file

@ -1,6 +1,6 @@
local stepName = std.extVar("input.my_step");
local image = std.extVar("input.my_image");
local commands = std.extVar("input.my_command");
local stepName = std.extVar("input.stepName");
local image = std.extVar("input.image");
local commands = std.extVar("input.commands");
{
"kind": "pipeline",

View file

@ -0,0 +1,15 @@
---
{
"kind": "pipeline",
"name": "default",
"steps": [
{
"commands": [
"my_command"
],
"image": "my_image",
"name": "my_step"
}
],
"type": "docker"
}

View file

@ -0,0 +1,14 @@
{
"kind": "pipeline",
"name": "default",
"steps": [
{
"commands": [
"my_command"
],
"image": "my_image",
"name": "my_step"
}
],
"type": "docker"
}