Skip to content

Commit

Permalink
Make PipelineRun parameterized support (#187)
Browse files Browse the repository at this point in the history
Fix wrong format of PipelineRun template

Fix unquoted error of parameter value

Refine parameter flag

Remove unused parameter type

Add some tests against PipelineRun template parsing

Remove unused method
  • Loading branch information
JohnNiang committed Sep 1, 2021
1 parent 25b55c3 commit aac855b
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 18 deletions.
58 changes: 40 additions & 18 deletions kubectl-plugin/pipeline/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ func newPipelineRunCmd(client dynamic.Interface) (cmd *cobra.Command) {
flags.StringVarP(&opt.namespace, "namespace", "n", "",
"The namespace of target Pipeline")
flags.BoolVarP(&opt.batch, "batch", "b", false, "Run pipeline as batch mode")
flags.StringToStringVarP(&opt.parameters, "parameters", "P", map[string]string{}, "The parameters that you want to pass, example of single parameter: name=value")
return
}

type pipelineRunOpt struct {
pipeline string
namespace string
batch bool
pipeline string
namespace string
batch bool
parameters map[string]string

// inner fields
client dynamic.Interface
Expand All @@ -56,24 +58,18 @@ func (o *pipelineRunOpt) preRunE(cmd *cobra.Command, args []string) (err error)
}

func (o *pipelineRunOpt) runE(_ *cobra.Command, _ []string) (err error) {
var tpl *template.Template
if tpl, err = template.New("pipelineRunTpl").Parse(pipelineRunTpl); err != nil {
err = fmt.Errorf("failed to parse template:'%s', error: %v", pipelineRunTpl, err)
return
}

var buf bytes.Buffer
if err = tpl.Execute(&buf, map[string]string{
"name": o.pipeline,
"namespace": o.namespace,
}); err != nil {
err = fmt.Errorf("failed render pipeline template, error: %v", err)
return
pipelineRunYaml, err := parsePipelineRunTpl(map[string]interface{}{
"name": o.pipeline,
"namespace": o.namespace,
"parameters": o.parameters,
})
if err != nil {
return err
}

var pipelineRunObj *unstructured.Unstructured
if pipelineRunObj, err = types.GetObjectFromYaml(buf.String()); err != nil {
err = fmt.Errorf("failed to unmarshal yaml to DevOpsProject object, %v", err)
if pipelineRunObj, err = types.GetObjectFromYaml(pipelineRunYaml); err != nil {
err = fmt.Errorf("failed to unmarshal yaml to Pipelinerun object, %v", err)
return
}

Expand Down Expand Up @@ -131,6 +127,25 @@ func (o *pipelineRunOpt) getPipelineNameList() (names []string, err error) {
return
}

func parsePipelineRunTpl(data map[string]interface{}) (pipelineRunYaml string, err error) {
var tpl *template.Template
if tpl, err = template.New("pipelineRunTpl").Parse(pipelineRunTpl); err != nil {
err = fmt.Errorf("failed to parse template:'%s', error: %v", pipelineRunTpl, err)
return
}

if err != nil {
return
}

var buf bytes.Buffer
if err = tpl.Execute(&buf, data); err != nil {
err = fmt.Errorf("failed to render pipeline template, error: %v", err)
return
}
return buf.String(), nil
}

var pipelineRunTpl = `
apiVersion: devops.kubesphere.io/v1alpha4
kind: PipelineRun
Expand All @@ -140,4 +155,11 @@ metadata:
spec:
pipelineRef:
name: {{.name}}
{{- if .parameters }}
parameters:
{{- range $name, $value := .parameters }}
- name: {{ $name | printf "%q" }}
value: {{ $value | printf "%q" }}
{{- end }}
{{- end }}
`
136 changes: 136 additions & 0 deletions kubectl-plugin/pipeline/run_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package pipeline

import (
"bytes"
"fmt"
"github.com/stretchr/testify/assert"
"html/template"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/yaml"
"testing"
)

func TestPipelineRunTplParse(t *testing.T) {
tpl := template.New("PipelineRunTpl")
tpl, err := tpl.Parse(pipelineRunTpl)
if err != nil {
t.Errorf("failed to parse PipelineRun template, err = %v", err)
}
var buf bytes.Buffer
err = tpl.Execute(&buf, map[string]interface{}{
"name": "fake_name",
"namespace": "fake_ns",
"parameters": nil,
})
if err != nil {
t.Errorf("failed to execute PipelineRun template, err = %v", err)
}
fmt.Println(buf.String())
}

// getNestedString comes from k8s.io/[email protected]/pkg/apis/meta/v1/unstructured/helpers.go:277
func getNestedString(obj map[string]interface{}, fields ...string) string {
val, found, err := unstructured.NestedString(obj, fields...)
if !found || err != nil {
return ""
}
return val
}

func getNestSlice(obj map[string]interface{}, fields ...string) []interface{} {
val, found, err := unstructured.NestedSlice(obj, fields...)
if !found || err != nil {
return nil
}
return val
}

func Test_parsePipelineRunTpl(t *testing.T) {
type args struct {
data map[string]interface{}
}
tests := []struct {
name string
args args
pipelineRunAssert func(obj *unstructured.Unstructured)
wantErr bool
}{{
name: "Without parameters",
args: args{
data: map[string]interface{}{
"name": "fake_name",
"namespace": "fake_namespace",
},
},
pipelineRunAssert: func(obj *unstructured.Unstructured) {
assert.Equal(t, "fake_name", obj.GetGenerateName())
assert.Equal(t, "fake_namespace", obj.GetNamespace())
assert.Equal(t, "fake_name", getNestedString(obj.Object, "spec", "pipelineRef", "name"))
assert.Equal(t, 0, len(getNestSlice(obj.Object, "spec", "parameters")))
},
}, {
name: "With nil parameters",
args: args{
data: map[string]interface{}{
"name": "fake_name",
"namespace": "fake_namespace",
"parameters": nil,
},
},
pipelineRunAssert: func(obj *unstructured.Unstructured) {
assert.Equal(t, "fake_name", obj.GetGenerateName())
assert.Equal(t, "fake_namespace", obj.GetNamespace())
assert.Equal(t, "fake_name", getNestedString(obj.Object, "spec", "pipelineRef", "name"))
assert.Equal(t, 0, len(getNestSlice(obj.Object, "spec", "parameters")))
},
}, {
name: "With empty parameters",
args: args{
data: map[string]interface{}{
"name": "fake_name",
"namespace": "fake_namespace",
"parameters": map[string]string{},
},
},
pipelineRunAssert: func(obj *unstructured.Unstructured) {
assert.Equal(t, "fake_name", obj.GetGenerateName())
assert.Equal(t, "fake_namespace", obj.GetNamespace())
assert.Equal(t, "fake_name", getNestedString(obj.Object, "spec", "pipelineRef", "name"))
assert.Equal(t, 0, len(getNestSlice(obj.Object, "spec", "parameters")))
},
}, {
name: "With one parameter",
args: args{
data: map[string]interface{}{
"name": "fake_name",
"namespace": "fake_namespace",
"parameters": map[string]string{
"a": "b",
},
},
},
pipelineRunAssert: func(obj *unstructured.Unstructured) {
assert.Equal(t, "fake_name", obj.GetGenerateName())
assert.Equal(t, "fake_namespace", obj.GetNamespace())
assert.Equal(t, "fake_name", getNestedString(obj.Object, "spec", "pipelineRef", "name"))
assert.Equal(t, 1, len(getNestSlice(obj.Object, "spec", "parameters")))
assert.Equal(t, map[string]interface{}{"name": "a", "value": "b"}, getNestSlice(obj.Object, "spec", "parameters")[0])
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotPipelineRunYaml, err := parsePipelineRunTpl(tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("parsePipelineRunTpl() error = %v, wantErr %v", err, tt.wantErr)
return
}
obj := unstructured.Unstructured{}
err = yaml.Unmarshal([]byte(gotPipelineRunYaml), &obj)
if (err != nil) != tt.wantErr {
t.Errorf("parsePipelineRunTpl() error = %v, wantErr %v", err, tt.wantErr)
return
}
tt.pipelineRunAssert(&obj)
})
}
}

0 comments on commit aac855b

Please sign in to comment.