diff --git a/go.mod b/go.mod index 27ebeb81..15730110 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/drone/drone-ui v2.0.1+incompatible github.com/drone/drone-yaml v1.2.4-0.20200326192514-6f4d6dfb39e4 github.com/drone/envsubst v1.0.3-0.20200709231038-aa43e1c1a629 + github.com/drone/funcmap v0.0.0-20210823160631-9e9dec149056 // indirect 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.15.2 diff --git a/go.sum b/go.sum index 4557e2c8..60801fd5 100644 --- a/go.sum +++ b/go.sum @@ -86,6 +86,10 @@ github.com/drone/drone-yaml v1.2.4-0.20200326192514-6f4d6dfb39e4 h1:XsstoCeXC2t8 github.com/drone/drone-yaml v1.2.4-0.20200326192514-6f4d6dfb39e4/go.mod h1:QsqliFK8nG04AHFN9tTn9XJomRBQHD4wcejWW1uz/10= github.com/drone/envsubst v1.0.3-0.20200709231038-aa43e1c1a629 h1:rIaZZalMGGPb2cU/+ypuggZ8aMlpa17RUlJUtsMv8pw= github.com/drone/envsubst v1.0.3-0.20200709231038-aa43e1c1a629/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= +github.com/drone/funcmap v0.0.0-20190918184546-d4ef6e88376d h1:/IO7UVVu191Jc0DajV4cDVoO+91cuppvgxg2MZl+AXI= +github.com/drone/funcmap v0.0.0-20190918184546-d4ef6e88376d/go.mod h1:Hph0/pT6ZxbujnE1Z6/08p5I0XXuOsppqF6NQlGOK0E= +github.com/drone/funcmap v0.0.0-20210823160631-9e9dec149056 h1:SCJwMR0FMA0aKwAntCBh02YmtHEnU40zaDzeeCvIRug= +github.com/drone/funcmap v0.0.0-20210823160631-9e9dec149056/go.mod h1:Hph0/pT6ZxbujnE1Z6/08p5I0XXuOsppqF6NQlGOK0E= github.com/drone/go-license v1.0.2 h1:7OwndfYk+Lp/cGHkxe4HUn/Ysrrw3WYH2pnd99yrkok= github.com/drone/go-license v1.0.2/go.mod h1:fGRHf+F1cEaw3YVYiJ6js3G3dVhcxyS617RnNRUMsms= github.com/drone/go-login v1.0.4-0.20190311170324-2a4df4f242a2 h1:RGpgNkowJc5LAVn/ZONx70qmnaTA0z/3hHPzTBdAEO8= diff --git a/plugin/converter/template.go b/plugin/converter/template.go index b944f9fe..6ac01242 100644 --- a/plugin/converter/template.go +++ b/plugin/converter/template.go @@ -17,22 +17,26 @@ package converter import ( + "bytes" "context" "database/sql" "errors" + "path/filepath" "regexp" "strings" + templating "text/template" "github.com/drone/drone/core" "github.com/drone/drone/plugin/converter/jsonnet" "github.com/drone/drone/plugin/converter/starlark" + "github.com/drone/funcmap" "gopkg.in/yaml.v2" ) var ( - // templateFileRE regex to verifying kind is template. - templateFileRE = regexp.MustCompile("^kind:\\s+template+\\n") + // TemplateFileRE regex to verifying kind is template. + TemplateFileRE = regexp.MustCompile("^kind:\\s+template+\\n") ErrTemplateNotFound = errors.New("template converter: template name given not found") ErrTemplateSyntaxErrors = errors.New("template converter: there is a problem with the yaml file provided") ) @@ -52,8 +56,9 @@ func (p *templatePlugin) Convert(ctx context.Context, req *core.ConvertArgs) (*c if strings.HasSuffix(req.Repo.Config, ".yml") == false { return nil, nil } + // check kind is template - if templateFileRE.MatchString(req.Config.Data) == false { + if TemplateFileRE.MatchString(req.Config.Data) == false { return nil, nil } // map to templateArgs @@ -71,29 +76,55 @@ func (p *templatePlugin) Convert(ctx context.Context, req *core.ConvertArgs) (*c return nil, err } - // Check if file is of type Starlark - if strings.HasSuffix(templateArgs.Load, ".script") || - strings.HasSuffix(templateArgs.Load, ".star") || - strings.HasSuffix(templateArgs.Load, ".starlark") { - - file, err := starlark.Parse(req, template, templateArgs.Data) - if err != nil { - return nil, err - } - return &core.Config{ - 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 + switch filepath.Ext(templateArgs.Load) { + case ".yml", ".yaml": + return parseYaml(req, template, templateArgs) + case ".star", ".starlark", ".script": + return parseStarlark(req, template, templateArgs) + case ".jsonnet": + return parseJsonnet(req, template, templateArgs) + default: } return nil, nil } + +func parseYaml(req *core.ConvertArgs, template *core.Template, templateArgs core.TemplateArgs) (*core.Config, error) { + data := map[string]interface{}{ + "build": toBuild(req.Build), + "repo": toRepo(req.Repo), + "input": templateArgs.Data, + } + tmpl, err := templating.New(template.Name).Funcs(funcmap.SafeFuncs).Parse(template.Data) + if err != nil { + return nil, err + } + var out bytes.Buffer + err = tmpl.Execute(&out, data) + if err != nil { + return nil, err + } + return &core.Config{ + Data: out.String(), + }, nil +} + +func parseJsonnet(req *core.ConvertArgs, template *core.Template, templateArgs core.TemplateArgs) (*core.Config, error) { + file, err := jsonnet.Parse(req, template, templateArgs.Data) + if err != nil { + return nil, err + } + return &core.Config{ + Data: file, + }, nil +} + +func parseStarlark(req *core.ConvertArgs, template *core.Template, templateArgs core.TemplateArgs) (*core.Config, error) { + file, err := starlark.Parse(req, template, templateArgs.Data) + if err != nil { + return nil, err + } + return &core.Config{ + Data: file, + }, nil +} diff --git a/plugin/converter/template_test.go b/plugin/converter/template_test.go index ad2fa0a9..3b2a8f53 100644 --- a/plugin/converter/template_test.go +++ b/plugin/converter/template_test.go @@ -326,3 +326,65 @@ func TestTemplateNestedValuesPluginConvertStarlark(t *testing.T) { t.Errorf("Want %q got %q", want, got) } } + +func TestTemplatePluginConvertYaml(t *testing.T) { + templateArgs, err := ioutil.ReadFile("testdata/yaml.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/yaml.input.yml") + if err != nil { + t.Error(err) + return + } + + after, err := ioutil.ReadFile("testdata/yaml.input.golden") + if err != nil { + t.Error(err) + return + } + + template := &core.Template{ + Name: "plugin.yaml", + 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) + } +} diff --git a/plugin/converter/testdata/yaml.input.golden b/plugin/converter/testdata/yaml.input.golden new file mode 100644 index 00000000..20b7e9a5 --- /dev/null +++ b/plugin/converter/testdata/yaml.input.golden @@ -0,0 +1,8 @@ +kind: pipeline +type: docker +name: defaults +steps: + - name: MY_STEP + image: my_image + commands: + - my_command \ No newline at end of file diff --git a/plugin/converter/testdata/yaml.input.yml b/plugin/converter/testdata/yaml.input.yml new file mode 100644 index 00000000..954d25d3 --- /dev/null +++ b/plugin/converter/testdata/yaml.input.yml @@ -0,0 +1,8 @@ +kind: pipeline +type: docker +name: defaults +steps: + - name: {{ upper .input.stepName }} + image: {{ .input.image }} + commands: + - {{ .input.commands }} \ No newline at end of file diff --git a/plugin/converter/testdata/yaml.template.yml b/plugin/converter/testdata/yaml.template.yml new file mode 100644 index 00000000..eb2cbce0 --- /dev/null +++ b/plugin/converter/testdata/yaml.template.yml @@ -0,0 +1,6 @@ +kind: template +load: plugin.yaml +data: + stepName: my_step + image: my_image + commands: my_command