grim/convey

Merge with replace-stage branch.
expand-list
2017-10-03, Eric Fritz
ac2ec4fef0a1
Parents b92ebe375621
Children 857d4e84d561
Merge with replace-stage branch.
--- a/REFERENCE.md Tue Oct 03 21:39:29 2017 -0500
+++ b/REFERENCE.md Tue Oct 03 21:41:11 2017 -0500
@@ -41,6 +41,11 @@
This example shows how to have one file extend another, inheriting all of the plans and tasks that are
not otherwise overwritten by the configuration file.
+## extends-merge.yml
+
+This example shows how to have one file extend another and specific stages of a plan can be replaced in
+the extending file.
+
## extends-slice.yml
This example shows how to expand an environment variable into a list in the context of a extended task.
@@ -490,6 +495,11 @@
| ----------- | -------- | ----------- |
| environment | | A list of environment variables to set. The should be specified in a `NAME` or `NAME=VALUE` format. If no value is provided, the value of the variable from the host will be provided if it is available. These environment variables will be applied on top of any that were set at the top-level. |
| stages | Yes | A list of `stages` to be run as part of this `plan` in the order that they should be run in. |
+| merge | | Whether or not to attempt to merge with a plan defined in the parent config file. |
+
+If a plan of the same name exists in a parent config file and merge is not set, it will replace the plan in its entirety. If merge is set, then
+the stages defined in this plan will overwrite stages of the parent plan with the same name. This behavior also applies to environments. It is an error
+to attempt to merge a stage that does not exist in the parent and new stages cannot be added as their order would not be well defined.
----
--- a/examples/base.yml Tue Oct 03 21:39:29 2017 -0500
+++ b/examples/base.yml Tue Oct 03 21:41:11 2017 -0500
@@ -21,3 +21,10 @@
plan2:
stages:
- tasks: [two]
+ plan3:
+ stages:
+ - name: pre
+ tasks: []
+ - tasks: [one, two]
+ - name: post
+ tasks: []
--- a/loaders/convey/convey.go Tue Oct 03 21:39:29 2017 -0500
+++ b/loaders/convey/convey.go Tue Oct 03 21:41:11 2017 -0500
@@ -125,8 +125,27 @@
baseConfig.Tasks[name] = task
}
+ // If the plan has the merge attribute set, try to overwrite
+ // stages of a plan that is already declared with the same name.
+ // If no such plan exists, raise an error (you're trying to
+ // overwrite a stage of nothing, which is almost certainly an error.
+
for name, plan := range cfg.Plans {
- baseConfig.Plans[name] = plan
+ if plan.Merge {
+ base, ok := baseConfig.Plans[name]
+ if !ok {
+ return nil, fmt.Errorf("cannot merge with unknown plan '%s'", name)
+ }
+
+ base, err := mergePlan(base, plan, name)
+ if err != nil {
+ return nil, err
+ }
+
+ baseConfig.Plans[name] = base
+ } else {
+ baseConfig.Plans[name] = plan
+ }
}
for name, metaPlan := range cfg.MetaPlans {
--- a/loaders/convey/extend_test.go Tue Oct 03 21:39:29 2017 -0500
+++ b/loaders/convey/extend_test.go Tue Oct 03 21:41:11 2017 -0500
@@ -180,3 +180,205 @@
Expect(cfg.Tasks["bar"].(*intrinsic.Extend).InnerTask).NotTo(BeIdenticalTo(cfg.Tasks["foo"]))
Expect(cfg.Tasks["bar"].(*intrinsic.Extend).InnerTask).To(BeEquivalentTo(cfg.Tasks["foo"]))
}
+
+func (s *conveySuite) TestReplaceStage(t sweet.T) {
+ data1 := `
+tasks:
+ pre:
+ image: pre
+ post:
+ image: post
+ foo:
+ image: foo
+plans:
+ plan1:
+ stages:
+ - name: pre
+ tasks:
+ - pre
+ - name: real-work
+ tasks:
+ - foo
+ - name: post
+ tasks:
+ - post
+ plan2:
+ stages:
+ - tasks:
+ - pre
+ - foo
+ - post
+`
+
+ data2 := `
+extends: base1.yaml
+tasks:
+ bar:
+ image: bar
+plans:
+ plan1:
+ merge: true
+ stages:
+ - name: real-work
+ tasks:
+ - bar
+ plan2:
+ stages:
+ - name: real-work
+ tasks:
+ - bar
+`
+
+ data3 := `
+extends: base2.yaml
+tasks:
+ baz:
+ image: baz
+plans:
+ plan1:
+ merge: true
+ stages:
+ - name: post
+ tasks:
+ - baz
+`
+
+ loader := &Loader{
+ fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
+ m := map[string]string{
+ "base1.yaml": data1,
+ "base2.yaml": data2,
+ }
+
+ return c.Load(".", name, []byte(m[name]))
+ },
+ }
+
+ cfg, err := loader.Load(".", ".", []byte(data3))
+ Expect(err).To(BeNil())
+ Expect(cfg.Plans).To(HaveLen(2))
+ Expect(cfg.Plans).To(HaveKey("plan1"))
+ Expect(cfg.Plans).To(HaveKey("plan2"))
+
+ Expect(cfg.Plans["plan1"].Stages).To(HaveLen(3))
+ Expect(cfg.Plans["plan1"].Stages[0].Name).To(Equal("pre"))
+ Expect(cfg.Plans["plan1"].Stages[1].Name).To(Equal("real-work"))
+ Expect(cfg.Plans["plan1"].Stages[2].Name).To(Equal("post"))
+ Expect(cfg.Plans["plan1"].Stages[0].Tasks).To(HaveLen(1))
+ Expect(cfg.Plans["plan1"].Stages[1].Tasks).To(HaveLen(1))
+ Expect(cfg.Plans["plan1"].Stages[2].Tasks).To(HaveLen(1))
+ Expect(cfg.Plans["plan1"].Stages[0].Tasks).To(ConsistOf("pre"))
+ Expect(cfg.Plans["plan1"].Stages[1].Tasks).To(ConsistOf("bar"))
+ Expect(cfg.Plans["plan1"].Stages[2].Tasks).To(ConsistOf("baz"))
+
+ Expect(cfg.Plans["plan2"].Stages).To(HaveLen(1))
+ Expect(cfg.Plans["plan2"].Stages[0].Tasks).To(HaveLen(1))
+ Expect(cfg.Plans["plan2"].Stages[0].Tasks).To(ConsistOf("bar"))
+}
+
+func (s *conveySuite) TestMergeMissingPlan(t sweet.T) {
+ data1 := `
+tasks:
+ foo:
+ image: foo
+plans:
+ plan1:
+ stages:
+ - tasks: [foo]
+`
+
+ data2 := `
+extends: base1.yaml
+tasks:
+ bar:
+ image: bar
+plans:
+ plan2:
+ merge: true
+ stages:
+ - tasks: [foo, bar]
+`
+
+ loader := &Loader{
+ fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
+ return c.Load(".", name, []byte(data1))
+ },
+ }
+
+ _, err := loader.Load(".", ".", []byte(data2))
+ Expect(err).To(MatchError("cannot merge with unknown plan 'plan2'"))
+}
+
+func (s *conveySuite) TestMergeMissingStage(t sweet.T) {
+ data1 := `
+tasks:
+ foo:
+ image: foo
+plans:
+ plan1:
+ stages:
+ - name: only
+ tasks:
+ - foo
+`
+
+ data2 := `
+extends: base1.yaml
+tasks:
+ bar:
+ image: bar
+plans:
+ plan1:
+ merge: true
+ stages:
+ - name: missing
+ tasks:
+ - bar
+`
+
+ loader := &Loader{
+ fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
+ return c.Load(".", name, []byte(data1))
+ },
+ }
+
+ _, err := loader.Load(".", ".", []byte(data2))
+ Expect(err).To(MatchError("cannot overwrite stage 'missing' in plan 'plan1' (no such stage in parent)"))
+}
+
+func (s *conveySuite) TestMergePlanEnvironment(t sweet.T) {
+ data1 := `
+tasks:
+ foo:
+ image: foo
+plans:
+ plan1:
+ stages:
+ - name: only
+ tasks:
+ - foo
+ environment:
+ - FOO=BAR
+ - BAR=BAZ
+`
+
+ data2 := `
+extends: base1.yaml
+plans:
+ plan1:
+ merge: true
+ environment:
+ - BAR=BONK
+`
+
+ loader := &Loader{
+ fileLoader: func(name string, c *Loader) (*cConfig.Config, error) {
+ return c.Load(".", name, []byte(data1))
+ },
+ }
+
+ cfg, err := loader.Load(".", ".", []byte(data2))
+ Expect(err).To(BeNil())
+ Expect(cfg.Plans["plan1"].Stages[0].Tasks).To(ConsistOf("foo"))
+ Expect(cfg.Plans["plan1"].Environment).To(ConsistOf("FOO=BAR", "BAR=BONK"))
+}
--- a/plans/plans.go Tue Oct 03 21:39:29 2017 -0500
+++ b/plans/plans.go Tue Oct 03 21:41:11 2017 -0500
@@ -34,6 +34,7 @@
type Plan struct {
Environment yaml.StringOrSlice `yaml:"environment"`
Stages []stages.Stage `yaml:"stages"`
+ Merge bool `yaml:"merge"`
}
func (p *Plan) setup(logger *gomol.LogAdapter, st *state.State) error {