Skip to content

Commit

Permalink
TEP-0118: Add validation for matrix combination count with include pa…
Browse files Browse the repository at this point in the history
…rams

[TEP-0090: Matrix] introduced `Matrix` to the `PipelineTask` specification such that the `PipelineTask` executes a list of `TaskRuns` or `Runs` in parallel with the specified list of inputs for a `Parameter` or with different combinations of the inputs for a set of `Parameters`.

To build on this, Tep-0018 introduced Matrix.Include, which allows passing in a specific combinations of `Parameters` into the `Matrix`

This commit validates that matrix combinations count including parameters in Matrix.Include does not exceed the max matrix combinations count.

Note: This is still in preview mode. Other forms of validation and implementation logic will be added in subsequent commits.
  • Loading branch information
EmmaMunley authored and tekton-robot committed Mar 7, 2023
1 parent 81876e6 commit c50c538
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 8 deletions.
2 changes: 2 additions & 0 deletions docs/matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ The default maximum count of `TaskRuns` or `Runs` from a given `Matrix` is **256
[config defaults](/config/config-defaults.yaml). When a `Matrix` in `PipelineTask` would generate more than the maximum
`TaskRuns` or `Runs`, the `Pipeline` validation would fail.

Note: The matrix combination count includes combinations generated from both `Matrix.Params` and `Matrix.Include.Params`.

```yaml
apiVersion: v1
kind: ConfigMap
Expand Down
47 changes: 43 additions & 4 deletions pkg/apis/pipeline/v1/matrix_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"github.com/tektoncd/pipeline/pkg/apis/config"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/utils/strings/slices"
"knative.dev/pkg/apis"
)

Expand Down Expand Up @@ -97,11 +98,49 @@ func createCombination(name string, value string, combination Params) Params {

// CountCombinations returns the count of combinations of Parameters generated from the Matrix in PipelineTask.
func (m *Matrix) CountCombinations() int {
// Iterate over matrix.params and compute count of all generated combinations
count := m.countGeneratedCombinationsFromParams()

// Add any additional combinations generated from matrix include params
count += m.countNewCombinationsFromInclude()

return count
}

// countGeneratedCombinationsFromParams returns the count of combinations of Parameters generated from the matrix
// parameters
func (m *Matrix) countGeneratedCombinationsFromParams() int {
if !m.hasParams() {
return 0
}
count := 1
for _, param := range m.Params {
count *= len(param.Value.ArrayVal)
}
return count
}

// countNewCombinationsFromInclude returns the count of combinations of Parameters generated from the matrix
// include parameters
func (m *Matrix) countNewCombinationsFromInclude() int {
if !m.hasInclude() {
return 0
}
if !m.hasParams() {
return len(m.Include)
}
count := 0
if m.hasParams() {
count = 1
for _, param := range m.Params {
count *= len(param.Value.ArrayVal)
matrixParamMap := m.Params.extractParamMapArrVals()
for _, include := range m.Include {
for _, param := range include.Params {
if val, exist := matrixParamMap[param.Name]; exist {
// If the matrix include param values does not exist, a new combination will be generated
if !slices.Contains(val, param.Value.StringVal) {
count++
} else {
break
}
}
}
}
return count
Expand Down
85 changes: 85 additions & 0 deletions pkg/apis/pipeline/v1/matrix_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,91 @@ func TestPipelineTask_CountCombinations(t *testing.T) {
Name: "xyzzy", Value: ParamValue{Type: ParamTypeArray, ArrayVal: []string{"x", "y", "z", "z", "y"}},
}}},
want: 135,
}, {
name: "explicit combinations in the matrix",
matrix: &Matrix{
Include: []MatrixInclude{{
Name: "build-1",
Params: []Param{{
Name: "IMAGE", Value: ParamValue{Type: ParamTypeString, StringVal: "image-1"},
}, {
Name: "DOCKERFILE", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/Dockerfile1"},
}},
}, {
Name: "build-2",
Params: []Param{{
Name: "IMAGE", Value: ParamValue{Type: ParamTypeString, StringVal: "image-2"},
}, {
Name: "DOCKERFILE", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/Dockerfile2"},
}},
}, {
Name: "build-3",
Params: []Param{{
Name: "IMAGE", Value: ParamValue{Type: ParamTypeString, StringVal: "image-3"},
}, {
Name: "DOCKERFILE", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/Dockerfile3"},
}},
}},
},
want: 3,
}, {
name: "params and include in matrix with overriding combinations params",
matrix: &Matrix{
Params: []Param{{
Name: "GOARCH", Value: ParamValue{ArrayVal: []string{"linux/amd64", "linux/ppc64le", "linux/s390x"}},
}, {
Name: "version", Value: ParamValue{ArrayVal: []string{"go1.17", "go1.18.1"}}},
},
Include: []MatrixInclude{{
Name: "common-package",
Params: []Param{{
Name: "package", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/common/package/"}}},
}, {
Name: "s390x-no-race",
Params: []Param{{
Name: "GOARCH", Value: ParamValue{Type: ParamTypeString, StringVal: "linux/s390x"},
}, {
Name: "flags", Value: ParamValue{Type: ParamTypeString, StringVal: "-cover -v"}}},
}, {
Name: "go117-context",
Params: []Param{{
Name: "version", Value: ParamValue{Type: ParamTypeString, StringVal: "go1.17"},
}, {
Name: "context", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/go117/context"}}},
}},
},
want: 6,
}, {
name: "params and include in matrix with overriding combinations params and one new combination",
matrix: &Matrix{
Params: []Param{{
Name: "GOARCH", Value: ParamValue{ArrayVal: []string{"linux/amd64", "linux/ppc64le", "linux/s390x"}},
}, {
Name: "version", Value: ParamValue{ArrayVal: []string{"go1.17", "go1.18.1"}}},
},
Include: []MatrixInclude{{
Name: "common-package",
Params: []Param{{
Name: "package", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/common/package/"}}},
}, {
Name: "s390x-no-race",
Params: []Param{{
Name: "GOARCH", Value: ParamValue{Type: ParamTypeString, StringVal: "linux/s390x"},
}, {
Name: "flags", Value: ParamValue{Type: ParamTypeString, StringVal: "-cover -v"}}},
}, {
Name: "go117-context",
Params: []Param{{
Name: "version", Value: ParamValue{Type: ParamTypeString, StringVal: "go1.17"},
}, {
Name: "context", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/go117/context"}}},
}, {
Name: "non-existent-arch",
Params: []Param{{
Name: "GOARCH", Value: ParamValue{Type: ParamTypeString, StringVal: "I-do-not-exist"}},
}},
}},
want: 7,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
10 changes: 10 additions & 0 deletions pkg/apis/pipeline/v1/param_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ func (ps Params) extractParamValuesFromParams() []string {
return pvs
}

// extractParamMapArrVals creates a param map with the key: param.Name and
// val: param.Value.ArrayVal
func (ps Params) extractParamMapArrVals() map[string][]string {
paramsMap := make(map[string][]string)
for _, p := range ps {
paramsMap[p.Name] = p.Value.ArrayVal
}
return paramsMap
}

// Params is a list of Param
type Params []Param

Expand Down
47 changes: 43 additions & 4 deletions pkg/apis/pipeline/v1beta1/matrix_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"github.com/tektoncd/pipeline/pkg/apis/config"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/utils/strings/slices"
"knative.dev/pkg/apis"
)

Expand Down Expand Up @@ -97,11 +98,49 @@ func createCombination(name string, value string, combination Params) Params {

// CountCombinations returns the count of combinations of Parameters generated from the Matrix in PipelineTask.
func (m *Matrix) CountCombinations() int {
// Iterate over matrix.params and compute count of all generated combinations
count := m.countGeneratedCombinationsFromParams()

// Add any additional combinations generated from matrix include params
count += m.countNewCombinationsFromInclude()

return count
}

// countGeneratedCombinationsFromParams returns the count of combinations of Parameters generated from the matrix
// parameters
func (m *Matrix) countGeneratedCombinationsFromParams() int {
if !m.hasParams() {
return 0
}
count := 1
for _, param := range m.Params {
count *= len(param.Value.ArrayVal)
}
return count
}

// countNewCombinationsFromInclude returns the count of combinations of Parameters generated from the matrix
// include parameters
func (m *Matrix) countNewCombinationsFromInclude() int {
if !m.hasInclude() {
return 0
}
if !m.hasParams() {
return len(m.Include)
}
count := 0
if m.hasParams() {
count = 1
for _, param := range m.Params {
count *= len(param.Value.ArrayVal)
matrixParamMap := m.Params.extractParamMapArrVals()
for _, include := range m.Include {
for _, param := range include.Params {
if val, exist := matrixParamMap[param.Name]; exist {
// If the matrix include param values does not exist, a new combination will be generated
if !slices.Contains(val, param.Value.StringVal) {
count++
} else {
break
}
}
}
}
return count
Expand Down
85 changes: 85 additions & 0 deletions pkg/apis/pipeline/v1beta1/matrix_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,91 @@ func TestPipelineTask_CountCombinations(t *testing.T) {
Name: "xyzzy", Value: ParamValue{Type: ParamTypeArray, ArrayVal: []string{"x", "y", "z", "z", "y"}},
}}},
want: 135,
}, {
name: "explicit combinations in the matrix",
matrix: &Matrix{
Include: []MatrixInclude{{
Name: "build-1",
Params: []Param{{
Name: "IMAGE", Value: ParamValue{Type: ParamTypeString, StringVal: "image-1"},
}, {
Name: "DOCKERFILE", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/Dockerfile1"},
}},
}, {
Name: "build-2",
Params: []Param{{
Name: "IMAGE", Value: ParamValue{Type: ParamTypeString, StringVal: "image-2"},
}, {
Name: "DOCKERFILE", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/Dockerfile2"},
}},
}, {
Name: "build-3",
Params: []Param{{
Name: "IMAGE", Value: ParamValue{Type: ParamTypeString, StringVal: "image-3"},
}, {
Name: "DOCKERFILE", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/Dockerfile3"},
}},
}},
},
want: 3,
}, {
name: "params and include in matrix with overriding combinations params",
matrix: &Matrix{
Params: []Param{{
Name: "GOARCH", Value: ParamValue{ArrayVal: []string{"linux/amd64", "linux/ppc64le", "linux/s390x"}},
}, {
Name: "version", Value: ParamValue{ArrayVal: []string{"go1.17", "go1.18.1"}}},
},
Include: []MatrixInclude{{
Name: "common-package",
Params: []Param{{
Name: "package", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/common/package/"}}},
}, {
Name: "s390x-no-race",
Params: []Param{{
Name: "GOARCH", Value: ParamValue{Type: ParamTypeString, StringVal: "linux/s390x"},
}, {
Name: "flags", Value: ParamValue{Type: ParamTypeString, StringVal: "-cover -v"}}},
}, {
Name: "go117-context",
Params: []Param{{
Name: "version", Value: ParamValue{Type: ParamTypeString, StringVal: "go1.17"},
}, {
Name: "context", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/go117/context"}}},
}},
},
want: 6,
}, {
name: "params and include in matrix with overriding combinations params and one new combination",
matrix: &Matrix{
Params: []Param{{
Name: "GOARCH", Value: ParamValue{ArrayVal: []string{"linux/amd64", "linux/ppc64le", "linux/s390x"}},
}, {
Name: "version", Value: ParamValue{ArrayVal: []string{"go1.17", "go1.18.1"}}},
},
Include: []MatrixInclude{{
Name: "common-package",
Params: []Param{{
Name: "package", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/common/package/"}}},
}, {
Name: "s390x-no-race",
Params: []Param{{
Name: "GOARCH", Value: ParamValue{Type: ParamTypeString, StringVal: "linux/s390x"},
}, {
Name: "flags", Value: ParamValue{Type: ParamTypeString, StringVal: "-cover -v"}}},
}, {
Name: "go117-context",
Params: []Param{{
Name: "version", Value: ParamValue{Type: ParamTypeString, StringVal: "go1.17"},
}, {
Name: "context", Value: ParamValue{Type: ParamTypeString, StringVal: "path/to/go117/context"}}},
}, {
Name: "non-existent-arch",
Params: []Param{{
Name: "GOARCH", Value: ParamValue{Type: ParamTypeString, StringVal: "I-do-not-exist"}},
}},
}},
want: 7,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
10 changes: 10 additions & 0 deletions pkg/apis/pipeline/v1beta1/param_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,16 @@ func (ps Params) extractParamValuesFromParams() []string {
return pvs
}

// extractParamMapArrVals creates a param map with the key: param.Name and
// val: param.Value.ArrayVal
func (ps Params) extractParamMapArrVals() map[string][]string {
paramsMap := make(map[string][]string)
for _, p := range ps {
paramsMap[p.Name] = p.Value.ArrayVal
}
return paramsMap
}

// extractParamArrayLengths extract and return the lengths of all array params
// Example of returned value: {"a-array-params": 2,"b-array-params": 2 }
func (ps Params) extractParamArrayLengths() map[string]int {
Expand Down

0 comments on commit c50c538

Please sign in to comment.