Skip to content

Commit

Permalink
Support rendering Template with parameters (#483)
Browse files Browse the repository at this point in the history
* Enhance render function with parameters

Signed-off-by: johnniang <johnniang@fastmail.com>

* Fix test error due to content type not set

Signed-off-by: johnniang <johnniang@fastmail.com>
  • Loading branch information
JohnNiang authored Mar 7, 2022
1 parent 43b271b commit e56f3b0
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 41 deletions.
7 changes: 4 additions & 3 deletions config/samples/devops_v1alpha1_clustertemplate.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
apiVersion: devops.kubesphere.io/v1alpha1
apiVersion: devops.kubesphere.io/v1alpha3
kind: ClusterTemplate
metadata:
name: clustertemplate-sample
Expand Down Expand Up @@ -36,10 +36,9 @@ spec:
stages {
stage('Checkout') {
steps {
checkout poll: false, scm: [$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'CloneOption', depth: 1, noTags: true, reference: '', shallow: true], [$class: 'SubmoduleOption', depth: 1, disableSubmodules: false, parentCredentials: false, recursiveSubmodules: true, reference: '', shallow: true, trackingSubmodules: false]], userRemoteConfigs: [[url: '{{ .params.gitCloneURL }}']]]
git branch: '{{.params.revision}}', url: '{{.params.cloneURL}}'
}
}
{{if not .buildOnly}}
stage('Gradle Check') {
steps {
container('gradle') {
Expand All @@ -54,10 +53,12 @@ spec:
}
}
}
{{if ne .params.buildOnly "true"}}
stage('Archive Assets') {
steps {
archiveArtifacts '**/build/libs/*.jar'
}
}
{{end}}
}
}
16 changes: 13 additions & 3 deletions pkg/kapis/devops/v1alpha3/template/cluster_template_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package template
import (
"context"
"github.com/emicklei/go-restful"
"io"
"k8s.io/apimachinery/pkg/runtime"
"kubesphere.io/devops/pkg/api"
"kubesphere.io/devops/pkg/api/devops/v1alpha3"
Expand All @@ -34,7 +35,15 @@ func (h *handler) handleQueryClusterTemplates(request *restful.Request, response

func (h *handler) handleRenderClusterTemplate(request *restful.Request, response *restful.Response) {
templateName := request.PathParameter(ClusterTemplatePathParameter.Data().Name)
kapis.ResponseWriter{Response: response}.WriteEntityOrError(h.renderClusterTemplate(templateName))

//var parameters []Parameter
var renderBody RenderBody
if err := request.ReadEntity(&renderBody); err != nil && err != io.EOF {
kapis.HandleError(request, response, err)
return
}

kapis.ResponseWriter{Response: response}.WriteEntityOrError(h.renderClusterTemplate(templateName, renderBody.Parameters))
}

func (h *handler) queryClusterTemplates(commonQuery *query.Query) (*api.ListResult, error) {
Expand All @@ -57,12 +66,13 @@ func (h *handler) getClusterTemplate(templateName string) (*v1alpha3.ClusterTemp
return template, nil
}

func (h *handler) renderClusterTemplate(templateName string) (v1alpha3.TemplateObject, error) {
func (h *handler) renderClusterTemplate(templateName string, parameters []Parameter) (v1alpha3.TemplateObject, error) {
template, err := h.getClusterTemplate(templateName)
if err != nil {
return nil, err
}
return render(template), nil

return render(template, parameters)
}

func clusterTemplatesToObjects(templates []v1alpha3.ClusterTemplate) []runtime.Object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ func Test_handler_handleRenderClusterTemplate(t *testing.T) {
}
createRequest := func(uri, templateName string) *restful.Request {
fakeRequest := httptest.NewRequest(http.MethodGet, uri, nil)
fakeRequest.Header.Add(restful.HEADER_ContentType, restful.MIME_JSON)
request := restful.NewRequest(fakeRequest)
request.PathParameters()[ClusterTemplatePathParameter.Data().Name] = templateName
return request
Expand Down
46 changes: 37 additions & 9 deletions pkg/kapis/devops/v1alpha3/template/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,47 @@
package template

import (
"bytes"
"kubesphere.io/devops/pkg/api/devops"
"kubesphere.io/devops/pkg/api/devops/v1alpha3"
tmpl "text/template"
)

func render(templateObjectCopy v1alpha3.TemplateObject) v1alpha3.TemplateObject {
templateObjectCopy = templateObjectCopy.DeepCopyObject().(v1alpha3.TemplateObject)
// TODO Render template using parameters
template := templateObjectCopy.TemplateSpec().Template
const parametersKey = "params"

// set template into annotations
if templateObjectCopy.GetAnnotations() == nil {
templateObjectCopy.SetAnnotations(map[string]string{})
// Parameter is a pair of name and value.
type Parameter struct {
Name string `json:"name"`
Value interface{} `json:"value"`
}

func render(templateObject v1alpha3.TemplateObject, parameters []Parameter) (v1alpha3.TemplateObject, error) {
templateObject = templateObject.DeepCopyObject().(v1alpha3.TemplateObject)
rawTemplate := templateObject.TemplateSpec().Template
template := tmpl.New("pipeline-template")
if _, err := template.Parse(rawTemplate); err != nil {
return nil, err
}

// TODO Verify required parameters and default parameters
// check the required parameters
parameterMap := map[string]interface{}{}
for _, parameter := range parameters {
parameterMap[parameter.Name] = parameter.Value
}

parametersData := map[string]map[string]interface{}{}
parametersData[parametersKey] = parameterMap

buffer := &bytes.Buffer{}
if err := template.Execute(buffer, parametersData); err != nil {
return nil, err
}
renderResult := buffer.String()

if templateObject.GetAnnotations() == nil {
templateObject.SetAnnotations(map[string]string{})
}
templateObjectCopy.GetAnnotations()[devops.GroupName+devops.RenderResultAnnoKey] = template
return templateObjectCopy
templateObject.GetAnnotations()[devops.GroupName+devops.RenderResultAnnoKey] = renderResult
return templateObject, nil
}
92 changes: 70 additions & 22 deletions pkg/kapis/devops/v1alpha3/template/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package template

import (
"fmt"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"kubesphere.io/devops/pkg/api/devops"
Expand All @@ -24,38 +25,85 @@ import (
)

func Test_render(t *testing.T) {
createTemplate := func(name, template string) v1alpha3.TemplateObject {
return &v1alpha3.Template{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1alpha3.TemplateSpec{
Template: template,
},
}
}
type args struct {
templateObject v1alpha3.TemplateObject
template v1alpha3.TemplateObject
parameters []Parameter
}
tests := []struct {
name string
args args
verify func(t *testing.T, object v1alpha3.TemplateObject)
name string
args args
wantErr assert.ErrorAssertionFunc
verify func(*testing.T, v1alpha3.TemplateObject)
}{{
name: "Should render template into annotations",
name: "Should render template without parameters",
args: args{
templateObject: &v1alpha3.Template{
ObjectMeta: metav1.ObjectMeta{
Name: "fake-template",
},
Spec: v1alpha3.TemplateSpec{
Template: "fake-template-content",
},
},
template: createTemplate("fake-name", "fake-template"),
parameters: nil,
},
verify: func(t *testing.T, object v1alpha3.TemplateObject) {
gotRenderResult := object.GetAnnotations()[devops.GroupName+devops.RenderResultAnnoKey]
wantRenderResult := object.TemplateSpec().Template
assert.Equal(t, wantRenderResult, gotRenderResult)
verify: func(t *testing.T, template v1alpha3.TemplateObject) {
got := template.GetAnnotations()[devops.GroupName+devops.RenderResultAnnoKey]
assert.Equal(t, "fake-template", got)
},
},
}
wantErr: assert.NoError,
}, {
name: "Should render template with parameters",
args: args{
template: createTemplate("fake-name", "The number should be {{ .params.number }}"),
parameters: []Parameter{{
Name: "number",
Value: "233",
}},
},
verify: func(t *testing.T, template v1alpha3.TemplateObject) {
got := template.GetAnnotations()[devops.GroupName+devops.RenderResultAnnoKey]
assert.Equal(t, "The number should be 233", got)
},
wantErr: assert.NoError,
}, {
name: "Should render incorrectly without corresponding parameter",
args: args{
template: createTemplate("fake-name", "The number should be: {{ .params.number }}"),
parameters: []Parameter{{
Name: "name",
Value: "fake-name",
}},
},
verify: func(t *testing.T, template v1alpha3.TemplateObject) {
got := template.GetAnnotations()[devops.GroupName+devops.RenderResultAnnoKey]
assert.Equal(t, "The number should be: <no value>", got)
},
wantErr: assert.NoError,
}, {
name: "Should return error if the template is invalid",
args: args{
template: createTemplate("fake-name", "{{}}"),
parameters: []Parameter{{
Name: "name",
Value: "fake-name",
}},
},
verify: func(t *testing.T, template v1alpha3.TemplateObject) {
assert.Nil(t, template)
},
wantErr: assert.Error,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
templateObject := render(tt.args.templateObject)
if tt.verify != nil {
tt.verify(t, templateObject)
template, err := render(tt.args.template, tt.args.parameters)
if !tt.wantErr(t, err, fmt.Sprintf("render(%v, %v)", tt.args.template, tt.args.parameters)) {
return
}
tt.verify(t, template)
})
}
}
7 changes: 7 additions & 0 deletions pkg/kapis/devops/v1alpha3/template/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ type PageResult struct {
Total int `json:"total"`
}

// RenderBody is the model of request body of render API.
type RenderBody struct {
Parameters []Parameter `json:"parameters"`
}

// RegisterRoutes is for registering template routes into WebService.
func RegisterRoutes(service *restful.WebService, options *common.Options) {
handler := newHandler(options)
Expand All @@ -63,6 +68,7 @@ func RegisterRoutes(service *restful.WebService, options *common.Options) {
To(handler.handleRenderTemplate).
Param(common.DevopsPathParameter).
Param(TemplatePathParameter).
Reads(RenderBody{}).
Doc(fmt.Sprintf("Render template and return render result into annotations (%s/%s) inside template", devops.GroupName, devops.RenderResultAnnoKey)).
Returns(http.StatusOK, api.StatusOK, v1alpha3.Template{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsTemplateTag}))
Expand All @@ -77,6 +83,7 @@ func RegisterRoutes(service *restful.WebService, options *common.Options) {
service.Route(service.POST("/clustertemplates/{clustertemplate}/render").
To(handler.handleRenderClusterTemplate).
Param(ClusterTemplatePathParameter).
Reads(RenderBody{}).
Doc("Render cluster template.").
Returns(http.StatusOK, api.StatusOK, v1alpha3.ClusterTemplate{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsClusterTemplateTag}))
Expand Down
4 changes: 3 additions & 1 deletion pkg/kapis/devops/v1alpha3/template/route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ func TestRegisterRoutes(t *testing.T) {
uri := fmt.Sprintf("/kapis/%s/%s%s",
v1alpha3.GroupVersion.Group, v1alpha3.GroupVersion.Version, tt.args.uri)
recorder := httptest.NewRecorder()
request, _ := http.NewRequest(tt.args.method, uri, nil)
request := httptest.NewRequest(tt.args.method, uri, nil)
request.Header.Add(restful.HEADER_ContentType, restful.MIME_JSON)

container.ServeHTTP(recorder, request)
if recorder.Code == 404 {
assert.NotContains(t, recorder.Body.String(), "Page Not Found")
Expand Down
12 changes: 9 additions & 3 deletions pkg/kapis/devops/v1alpha3/template/template_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package template
import (
"context"
"github.com/emicklei/go-restful"
"io"
"k8s.io/apimachinery/pkg/runtime"
"kubesphere.io/devops/pkg/api"
"kubesphere.io/devops/pkg/api/devops/v1alpha3"
Expand Down Expand Up @@ -66,16 +67,21 @@ func (h *handler) getTemplate(devopsName, templateName string) (*v1alpha3.Templa
func (h *handler) handleRenderTemplate(request *restful.Request, response *restful.Response) {
devopsName := request.PathParameter(common.DevopsPathParameter.Data().Name)
templateName := request.PathParameter(TemplatePathParameter.Data().Name)
var renderBody RenderBody
if err := request.ReadEntity(&renderBody); err != nil && err != io.EOF {
kapis.HandleError(request, response, err)
return
}

kapis.ResponseWriter{Response: response}.WriteEntityOrError(h.renderTemplate(devopsName, templateName))
kapis.ResponseWriter{Response: response}.WriteEntityOrError(h.renderTemplate(devopsName, templateName, renderBody.Parameters))
}

func (h *handler) renderTemplate(devopsName, templateName string) (v1alpha3.TemplateObject, error) {
func (h *handler) renderTemplate(devopsName, templateName string, parameters []Parameter) (v1alpha3.TemplateObject, error) {
tmpl, err := h.getTemplate(devopsName, templateName)
if err != nil {
return nil, err
}
return render(tmpl), nil
return render(tmpl, parameters)
}

func templatesToObjects(templates []v1alpha3.Template) []runtime.Object {
Expand Down
2 changes: 2 additions & 0 deletions pkg/kapis/devops/v1alpha3/template/template_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func Test_templatesToObjects(t *testing.T) {
})
}
}

func Test_handler_handleQueryTemplates(t *testing.T) {
createTemplate := func(name string) *v1alpha3.Template {
return &v1alpha3.Template{
Expand Down Expand Up @@ -251,6 +252,7 @@ func Test_handler_handleRenderTemplate(t *testing.T) {
}
createRequest := func(uri, devopsName, templateName string) *restful.Request {
fakeRequest := httptest.NewRequest(http.MethodGet, uri, nil)
fakeRequest.Header.Add(restful.HEADER_ContentType, restful.MIME_JSON)
request := restful.NewRequest(fakeRequest)
request.PathParameters()[common.DevopsPathParameter.Data().Name] = devopsName
request.PathParameters()[TemplatePathParameter.Data().Name] = templateName
Expand Down

0 comments on commit e56f3b0

Please sign in to comment.