Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TEP-0147: introducing matrix.strategy #7180

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions docs/pipeline-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1542,6 +1542,19 @@ IncludeParamsList
<p>Include is a list of IncludeParams which allows passing in specific combinations of Parameters into the Matrix.</p>
</td>
</tr>
<tr>
<td>
<code>strategy</code><br/>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Strategy is a JSON payload with a list of combinations
Strategy is an extension of Include to support dynamic combinations</p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1.OnErrorType">OnErrorType
Expand Down Expand Up @@ -4654,6 +4667,9 @@ More info: <a href="https://kubernetes.io/docs/tasks/configure-pod-container/sec
</p>
<div>
<p>TaskBreakpoints defines the breakpoint config for a particular Task</p>
<h3 id="tekton.dev/v1.Strategy">Strategy
</h3>
<div>
</div>
<table>
<thead>
Expand All @@ -4674,6 +4690,12 @@ string
<em>(Optional)</em>
<p>if enabled, pause TaskRun on failure of a step
failed step will not exit</p>
<code>include</code><br/>
<em>
[]string
</em>
</td>
<td>
</td>
</tr>
</tbody>
Expand Down Expand Up @@ -9520,6 +9542,19 @@ IncludeParamsList
<p>Include is a list of IncludeParams which allows passing in specific combinations of Parameters into the Matrix.</p>
</td>
</tr>
<tr>
<td>
<code>strategy</code><br/>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Strategy is a JSON payload with a list of combinations
Strategy is an extension of Include to support dynamic combinations</p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1beta1.OnErrorType">OnErrorType
Expand Down Expand Up @@ -13174,6 +13209,9 @@ Default is false.</p>
</p>
<div>
<p>TaskBreakpoints defines the breakpoint config for a particular Task</p>
<h3 id="tekton.dev/v1beta1.Strategy">Strategy
</h3>
<div>
</div>
<table>
<thead>
Expand All @@ -13194,6 +13232,12 @@ string
<em>(Optional)</em>
<p>if enabled, pause TaskRun on failure of a step
failed step will not exit</p>
<code>include</code><br/>
<em>
[]string
</em>
</td>
<td>
</td>
</tr>
</tbody>
Expand Down
81 changes: 80 additions & 1 deletion pkg/apis/pipeline/v1/matrix_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package v1

import (
"context"
"encoding/json"
"fmt"
"sort"

Expand All @@ -38,6 +39,12 @@ type Matrix struct {
// +optional
// +listType=atomic
Include IncludeParamsList `json:"include,omitempty"`

// Strategy is a JSON payload with a list of combinations
// Strategy is an extension of Include to support dynamic combinations
// +optional
// +listType=atomic
Strategy string `json:"strategy,omitempty"`
}

// IncludeParamsList is a list of IncludeParams which allows passing in specific combinations of Parameters into the Matrix.
Expand All @@ -54,14 +61,21 @@ type IncludeParams struct {
Params Params `json:"params,omitempty"`
}

type Strategy struct {
Include []map[string]string `json:"include,omitempty"`
}

// Combination is a map, mainly defined to hold a single combination from a Matrix with key as param.Name and value as param.Value
type Combination map[string]string

// Combinations is a Combination list
type Combinations []Combination

// FanOut returns an list of params that represent combinations
// FanOut returns a list of params that represent combinations
func (m *Matrix) FanOut() []Params {
if m.HasStrategy() {
return m.getStrategy().toParams()
}
var combinations, includeCombinations Combinations
includeCombinations = m.getIncludeCombinations()
if m.HasInclude() && !m.HasParams() {
Expand Down Expand Up @@ -177,6 +191,23 @@ func (m *Matrix) getIncludeCombinations() Combinations {
return combinations
}

// getStrategy generates combinations based on Matrix Strategy section
// Matrix Strategy allows "include" as a JSON payload
func (m *Matrix) getStrategy() Combinations {
var combinations Combinations
var s Strategy
if err := json.Unmarshal([]byte(m.Strategy), &s); err == nil {
for _, i := range s.Include {
newCombination := make(Combination)
for k, v := range i {
newCombination[k] = v
}
combinations = append(combinations, newCombination)
}
}
return combinations
}

// distribute generates a new Combination of Parameters by adding a new Parameter to an existing list of Combinations.
func (cs Combinations) distribute(param Param) Combinations {
var expandedCombinations Combinations
Expand Down Expand Up @@ -225,6 +256,9 @@ func (m *Matrix) CountCombinations() int {
// Add any additional Combinations generated from Matrix Include Parameters
count += m.countNewCombinationsFromInclude()

// Iterate over Matrix Strategy to count all combinations specified
count += m.countCombinationsFromStrategy()

return count
}

Expand Down Expand Up @@ -267,6 +301,11 @@ func (m *Matrix) countNewCombinationsFromInclude() int {
return count
}

// countCombinationsFromStrategy returns the count of Combinations specified in the strategy
func (m *Matrix) countCombinationsFromStrategy() int {
return len(m.getStrategy())
}

// HasInclude returns true if the Matrix has Include Parameters
func (m *Matrix) HasInclude() bool {
return m != nil && m.Include != nil && len(m.Include) > 0
Expand All @@ -277,6 +316,10 @@ func (m *Matrix) HasParams() bool {
return m != nil && m.Params != nil && len(m.Params) > 0
}

func (m *Matrix) HasStrategy() bool {
return m != nil && len(m.Strategy) > 0
}

// GetAllParams returns a list of all Matrix Parameters
func (m *Matrix) GetAllParams() Params {
var params Params
Expand All @@ -288,6 +331,12 @@ func (m *Matrix) GetAllParams() Params {
params = append(params, include.Params...)
}
}
if m.HasStrategy() {
ps := m.getStrategy().toParams()
for _, p := range ps {
params = append(params, p...)
}
}
return params
}

Expand Down Expand Up @@ -336,6 +385,18 @@ func (m *Matrix) validatePipelineParametersVariablesInMatrixParameters(prefix st
}
}
}
if m.HasStrategy() {
var s Strategy
if err := json.Unmarshal([]byte(m.Strategy), &s); err != nil {
for _, i := range s.Include {
for k, v := range i {
// Matrix Strategy Params must be of type string
errs = errs.Also(validateStringVariable(v, prefix, paramNames, arrayParamNames, objectParamNameKeys).ViaField(k).ViaField("matrix.strategy", k))

}
}
}
}
return errs
}

Expand All @@ -348,3 +409,21 @@ func (m *Matrix) validateParameterInOneOfMatrixOrParams(params []Param) (errs *a
}
return errs
}

// validateStrategy validates syntax of Matrix Strategy section
// Matrix Strategy allows "include" as a JSON payload with a list of combinations
func (m *Matrix) validateStrategy() (errs *apis.FieldError) {
e := "matrix.strategy section does not have a valid JSON payload, " +
"matrix.strategy section only allows valid JSON payload in the form of " +
"{include: []map[string]string} e.g. {include: [{k1: v1, k2: v2}, {k1: v3, k2: v4}, {k3: v1}]"
var s Strategy
if m.HasStrategy() {
if !json.Valid([]byte(m.Strategy)) {
return apis.ErrGeneric(e)
}
if err := json.Unmarshal([]byte(m.Strategy), &s); err != nil {
return apis.ErrGeneric(e + ": " + err.Error())
}
}
return errs
}
34 changes: 34 additions & 0 deletions pkg/apis/pipeline/v1/matrix_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,40 @@ func TestMatrix_FanOut(t *testing.T) {
Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: "I-do-not-exist"},
},
}},
}, {
name: "Fan out explicit combinations using strategy",
matrix: v1.Matrix{
Strategy: "{\"include\": [" +
"{\"DOCKERFILE\": \"path/to/Dockerfile1\", \"IMAGE\": \"image-1\"}," +
"{\"DOCKERFILE\": \"path/to/Dockerfile2\", \"IMAGE\": \"image-2\"}," +
"{\"DOCKERFILE\": \"path/to/Dockerfile3\", \"IMAGE\": \"image-3\"}" +
"]}",
},
want: []v1.Params{{
{
Name: "DOCKERFILE",
Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: "path/to/Dockerfile1"},
}, {
Name: "IMAGE",
Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: "image-1"},
},
}, {
{
Name: "DOCKERFILE",
Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: "path/to/Dockerfile2"},
}, {
Name: "IMAGE",
Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: "image-2"},
},
}, {
{
Name: "DOCKERFILE",
Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: "path/to/Dockerfile3"},
}, {
Name: "IMAGE",
Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: "image-3"},
},
}},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
47 changes: 47 additions & 0 deletions pkg/apis/pipeline/v1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/apis/pipeline/v1/pipeline_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ func (et *EmbeddedTask) IsCustomTask() bool {

// IsMatrixed return whether pipeline task is matrixed
func (pt *PipelineTask) IsMatrixed() bool {
return pt.Matrix.HasParams() || pt.Matrix.HasInclude()
return pt.Matrix.HasParams() || pt.Matrix.HasInclude() || pt.Matrix.HasStrategy()
}

// TaskSpecMetadata returns the metadata of the PipelineTask's EmbeddedTask spec.
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/pipeline/v1/pipeline_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ func (pt *PipelineTask) validateMatrix(ctx context.Context) (errs *apis.FieldErr
errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "matrix", config.BetaAPIFields))
errs = errs.Also(pt.Matrix.validateCombinationsCount(ctx))
errs = errs.Also(pt.Matrix.validateUniqueParams())
errs = errs.Also(pt.Matrix.validateStrategy())
}
errs = errs.Also(pt.Matrix.validateParameterInOneOfMatrixOrParams(pt.Params))
return errs
Expand Down
Loading
Loading