Skip to content

Commit

Permalink
PP-2369: Override StageTimeoutMs and Specify Options (#79)
Browse files Browse the repository at this point in the history
* Add the ability to override a deploy stage timeout

* add support for option type parameters

* dont need yaml bool
  • Loading branch information
petermcneely authored Mar 24, 2021
1 parent 252d9f2 commit a089088
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 26 deletions.
23 changes: 22 additions & 1 deletion pipeline/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,20 @@ func (b *Builder) Pipeline() (*types.SpinnakerPipeline, error) {
Default: param.Default,
Required: param.Required,
}

if len(param.Options) > 0 {
sp.Parameters[i].HasOptions = true
foundDefaultValue := param.Default == ""
for _, val := range param.Options {
foundDefaultValue = foundDefaultValue || param.Default == val.Value
sp.Parameters[i].Options = append(sp.Parameters[i].Options, types.Option{
Value: val.Value,
})
}
if !foundDefaultValue {
return sp, errors.New("builder: the specified default value is not one of the options")
}
}
}

var stageIndex = 0
Expand Down Expand Up @@ -442,7 +456,7 @@ func (b *Builder) defaultManifestStage(index int, s config.Stage) *types.Manifes
markUnstableAsSuccessful := setDefaultIfNil(s.DeployEmbeddedManifests.MarkUnstableAsSuccessful, false)
waitForCompletion := setDefaultIfNil(s.DeployEmbeddedManifests.WaitForCompletion, true)

return &types.ManifestStage{
stage := &types.ManifestStage{
StageMetadata: buildStageMetadata(s, "deployManifest", index, b.isLinear),
Account: s.Account,
CloudProvider: "kubernetes",
Expand All @@ -460,6 +474,13 @@ func (b *Builder) defaultManifestStage(index int, s config.Stage) *types.Manifes
MarkUnstableAsSuccessful: &markUnstableAsSuccessful,
WaitForCompletion: &waitForCompletion,
}

if s.DeployEmbeddedManifests.StageTimeoutMS > 0 {
stage.OverrideTimeout = true
stage.StageTimeoutMS = s.DeployEmbeddedManifests.StageTimeoutMS
}

return stage
}

func (b *Builder) buildV2RunJobStage(index int, s config.Stage) (*types.ManifestStage, error) {
Expand Down
202 changes: 184 additions & 18 deletions pipeline/builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,28 +148,173 @@ func TestBuilderPipelineStages(t *testing.T) {
})

t.Run("Parameter configuration is parsed correctly", func(t *testing.T) {
pipeline := &config.Pipeline{
Parameters: []config.Parameter{
{
Name: "param1",
Description: "parameter description",
Default: "default value",
Required: true,
t.Run("Without Options", func(t *testing.T) {
pipeline := &config.Pipeline{
Parameters: []config.Parameter{
{
Name: "param1",
Description: "parameter description",
Default: "default value",
Required: true,
},
},
},
}
}

b := builder.New(pipeline)
spinnaker, err := b.Pipeline()
require.NoError(t, err, "error generating pipeline json")

require.Len(t, spinnaker.Parameters, 1)

param := spinnaker.Parameters[0]
assert.Equal(t, true, param.Required)
assert.Equal(t, "parameter description", param.Description)
assert.Equal(t, "param1", param.Name)
assert.Equal(t, "default value", param.Default)
})

t.Run("With options", func(t *testing.T) {
pipeline := &config.Pipeline{
Parameters: []config.Parameter{
{
Name: "param1",
Description: "parameter description",
Required: true,
Options: []config.Option{
{
Value: "opt1",
},
{
Value: "opt2",
},
},
},
},
}

b := builder.New(pipeline)
spinnaker, err := b.Pipeline()
require.NoError(t, err, "error generating pipeline json")

b := builder.New(pipeline)
spinnaker, err := b.Pipeline()
require.NoError(t, err, "error generating pipeline json")
require.Len(t, spinnaker.Parameters, 1)

require.Len(t, spinnaker.Parameters, 1)
param := spinnaker.Parameters[0]
assert.Equal(t, true, param.Required)
assert.Equal(t, "parameter description", param.Description)
assert.Equal(t, "param1", param.Name)
assert.True(t, param.HasOptions)
assert.Equal(t, "opt1", param.Options[0].Value)
assert.Equal(t, "opt2", param.Options[1].Value)
})

t.Run("With options and a default value", func(t *testing.T) {
pipeline := &config.Pipeline{
Parameters: []config.Parameter{
{
Name: "param1",
Description: "parameter description",
Default: "opt1",
Required: true,
Options: []config.Option{
{
Value: "opt1",
},
{
Value: "opt2",
},
},
},
},
}

b := builder.New(pipeline)
spinnaker, err := b.Pipeline()
require.NoError(t, err, "error generating pipeline json")

require.Len(t, spinnaker.Parameters, 1)

param := spinnaker.Parameters[0]
assert.Equal(t, true, param.Required)
assert.Equal(t, "parameter description", param.Description)
assert.Equal(t, "param1", param.Name)
assert.Equal(t, "opt1", param.Default)
assert.True(t, param.HasOptions)
assert.Equal(t, "opt1", param.Options[0].Value)
assert.Equal(t, "opt2", param.Options[1].Value)
})

t.Run("With error handling for mismatched option and default values", func(t *testing.T) {
pipeline := &config.Pipeline{
Parameters: []config.Parameter{
{
Name: "param1",
Default: "optN",
Options: []config.Option{
{
Value: "opt1",
},
{
Value: "opt2",
},
},
},
},
}

param := spinnaker.Parameters[0]
assert.Equal(t, true, param.Required)
assert.Equal(t, "parameter description", param.Description)
assert.Equal(t, "param1", param.Name)
assert.Equal(t, "default value", param.Default)
b := builder.New(pipeline)
_, err := b.Pipeline()
require.Error(t, err, "builder: the specified default value is not one of the options")
})
})

t.Run("DeployEmbeddedManifests is parsed correctly", func(t *testing.T) {
t.Run("Picks up files to deploy", func(t *testing.T) {
pipeline := &config.Pipeline{
Stages: []config.Stage{
{
Name: "Test DeployEmbeddedManifests Stage",
DeployEmbeddedManifests: &config.DeployEmbeddedManifests{

Files: []config.ManifestFile{
{
File: file,
},
},
},
},
},
}
builder := builder.New(pipeline)
spinnaker, err := builder.Pipeline()
require.NoError(t, err, "error generating pipeline json")

assert.Equal(t, "Test DeployEmbeddedManifests Stage", spinnaker.Stages[0].(*types.ManifestStage).Name)
assert.NotNil(t, spinnaker.Stages[0].(*types.ManifestStage).Manifests[0])
})

t.Run("Overrides default timeout", func(t *testing.T) {
pipeline := &config.Pipeline{
Stages: []config.Stage{
{
Name: "Test DeployEmbeddedManifests Stage",
DeployEmbeddedManifests: &config.DeployEmbeddedManifests{
StageTimeoutMS: 360000,
Files: []config.ManifestFile{
{
File: file,
},
},
},
},
},
}
builder := builder.New(pipeline)
spinnaker, err := builder.Pipeline()
require.NoError(t, err, "error generating pipeline json")

assert.Equal(t, int64(360000), spinnaker.Stages[0].(*types.ManifestStage).StageTimeoutMS)
assert.Equal(t, true, spinnaker.Stages[0].(*types.ManifestStage).OverrideTimeout)
})
})

t.Run("Deploy stage is parsed correctly", func(t *testing.T) {
Expand Down Expand Up @@ -296,6 +441,27 @@ func TestBuilderPipelineStages(t *testing.T) {
})

t.Run("ManualJudgement stage is parsed correctly", func(t *testing.T) {
t.Run("Override timeout is assigned", func(t *testing.T) {
pipeline := &config.Pipeline{
Stages: []config.Stage{
{
Name: "Test ManualJudgementStage Timeout",
ManualJudgement: &config.ManualJudgementStage{
Timeout: 1,
},
},
},
}

builder := builder.New(pipeline)
spinnaker, err := builder.Pipeline()
require.NoError(t, err, "error generating pipeline json")

assert.Equal(t, "Test ManualJudgementStage Timeout", spinnaker.Stages[0].(*types.ManualJudgementStage).Name)
assert.True(t, spinnaker.Stages[0].(*types.ManualJudgementStage).OverrideTimeout)
assert.Equal(t, int64(3600000), spinnaker.Stages[0].(*types.ManualJudgementStage).StageTimeoutMS)
})

t.Run("Name is assigned", func(t *testing.T) {
pipeline := &config.Pipeline{
Stages: []config.Stage{
Expand Down
12 changes: 9 additions & 3 deletions pipeline/builder/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,13 @@ type Parameter struct {
Default string `json:"default"`
Required bool `json:"required"`

// TODO(bobbytables): Allow configuring parameter options
HasOptions bool `json:"hasOptions"`
Options []interface{} `json:"options"`
HasOptions bool `json:"hasOptions,omitempty"`
Options []Option `json:"options,omitempty"`
}

// Option contains the value of the option in a given pipeline parameter
type Option struct {
Value string `json:"value,omitempty"`
}

// Stage is an interface to represent a Stage struct such as RunJob or Deploy
Expand Down Expand Up @@ -75,6 +79,8 @@ type ManifestStage struct {
FailPipeline *bool `json:"failPipeline,omitempty"`
MarkUnstableAsSuccessful *bool `json:"markUnstableAsSuccessful,omitempty"`
WaitForCompletion *bool `json:"waitForCompletion,omitempty"`
OverrideTimeout bool `json:"overrideTimeout,omitempty"`
StageTimeoutMS int64 `json:"stageTimeoutMs,omitempty"`
}

func (ms ManifestStage) spinnakerStage() {}
Expand Down
15 changes: 11 additions & 4 deletions pipeline/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,16 @@ type Pipeline struct {

// Parameter defines a single parameter in a pipeline config
type Parameter struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Default string `yaml:"default"`
Required bool `yaml:"required"`
Name string `yaml:"name"`
Description string `yaml:"description"`
Default string `yaml:"default"`
Required bool `yaml:"required"`
Options []Option `yaml:"options"`
}

// Option contains the option value of a single parameter in a pipeline config
type Option struct {
Value string `yaml:"value"`
}

// ImageDescription contains the description of an image that can be referenced
Expand Down Expand Up @@ -250,6 +256,7 @@ type DeployEmbeddedManifests struct {
FailPipeline *bool `yaml:"failPipeline,omitempty"`
MarkUnstableAsSuccessful *bool `yaml:"markUnstableAsSuccessful,omitempty"`
WaitForCompletion *bool `yaml:"waitForCompletion,omitempty"`
StageTimeoutMS int64 `yaml:"stageTimeoutMs,omitempty"`
}

// DeleteEmbeddedManifest represents a single resource to be deleted
Expand Down

0 comments on commit a089088

Please sign in to comment.