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

Add a PipelineResourceBase struct that implements some stub versions … #1188

Closed
wants to merge 1 commit into from

Conversation

dlorenc
Copy link
Contributor

@dlorenc dlorenc commented Aug 13, 2019

…of the interfaces.

Changes

This struct can be embedded to simplify and reduce boilerplate across the different resource
types, making further refactors easier.

Submitter Checklist

These are the criteria that every PR should meet, please check them off as you
review them:

See the contribution guide for more details.

@googlebot googlebot added the cla: yes Trying to make the CLA bot happy with ppl from different companies work on one commit label Aug 13, 2019
@tekton-robot
Copy link
Collaborator

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: dlorenc

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@tekton-robot tekton-robot added approved Indicates a PR has been approved by an approver from all required OWNERS files. size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Aug 13, 2019
@tekton-robot
Copy link
Collaborator

The following is the coverage report on pkg/.
Say /test pull-tekton-pipeline-go-coverage to re-run this coverage report

File Old Coverage New Coverage Delta
pkg/apis/pipeline/v1alpha1/cluster_resource.go 71.8% 77.8% 6.0
pkg/apis/pipeline/v1alpha1/git_resource.go 70.0% 82.4% 12.4
pkg/apis/pipeline/v1alpha1/image_resource.go 50.0% 64.3% 14.3
pkg/apis/pipeline/v1alpha1/pull_request_resource.go 77.3% 85.0% 7.7

@tekton-robot
Copy link
Collaborator

The following is the coverage report on pkg/.
Say /test pull-tekton-pipeline-go-coverage to re-run this coverage report

File Old Coverage New Coverage Delta
pkg/apis/pipeline/v1alpha1/cloud_event_resource.go 78.9% 86.7% 7.7
pkg/apis/pipeline/v1alpha1/cluster_resource.go 71.8% 77.8% 6.0
pkg/apis/pipeline/v1alpha1/git_resource.go 70.0% 82.4% 12.4
pkg/apis/pipeline/v1alpha1/image_resource.go 50.0% 64.3% 14.3
pkg/apis/pipeline/v1alpha1/pull_request_resource.go 77.3% 85.0% 7.7
pkg/apis/pipeline/v1alpha1/resource_types.go 0.0% 16.7% 16.7

…of the interfaces.

This struct can be embedded to simplify and reduce boilerplate across the different resource
types, making further refactors easier.
@tekton-robot
Copy link
Collaborator

The following is the coverage report on pkg/.
Say /test pull-tekton-pipeline-go-coverage to re-run this coverage report

File Old Coverage New Coverage Delta
pkg/apis/pipeline/v1alpha1/cloud_event_resource.go 78.9% 86.7% 7.7
pkg/apis/pipeline/v1alpha1/cluster_resource.go 71.1% 75.0% 3.9
pkg/apis/pipeline/v1alpha1/git_resource.go 70.0% 82.4% 12.4
pkg/apis/pipeline/v1alpha1/image_resource.go 50.0% 64.3% 14.3
pkg/apis/pipeline/v1alpha1/pull_request_resource.go 77.3% 85.0% 7.7
pkg/apis/pipeline/v1alpha1/resource_types.go 0.0% 16.7% 16.7

@tekton-robot
Copy link
Collaborator

tekton-robot commented Aug 14, 2019

@dlorenc: The following test failed, say /retest to rerun them all:

Test name Commit Details Rerun command
pull-tekton-pipeline-build-tests a585d6f link /test pull-tekton-pipeline-build-tests

Full PR test history. Your PR dashboard.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

@ghost
Copy link

ghost commented Aug 14, 2019

/lgtm

@tekton-robot tekton-robot assigned ghost Aug 14, 2019
@tekton-robot tekton-robot added the lgtm Indicates that a PR is ready to be merged. label Aug 14, 2019
Copy link
Collaborator

@bobcatfish bobcatfish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually want to push back on this a bit: i think inheritance (go says it doesnt have inheritance but i effectively think that's what this is) makes it more difficult to reason about what code is doing - for example, if I want to look at ClusterResource now and understand what it's doing, I have to understand that it is embedding PipelineResourceBase and go look at that definition. Additionally if I'm implementing a new Resource type and I miss a function which I meant to implement, I might not realize it.

My personal feeling is that a slight reduction in the amount of code that needs to be written and faster refactoring are not worth introducing inheritance.

I wonder if there is another way we can tackle this - if we're finding that the interface our resources are implementing contains functions which frequently aren't needed for resources, maybe we should change the interface - maybe we should have a separate object for UploadVolume + DownloadVolume specs for example, with no implementation for types that don't need it.

btw if you are interested, this presentation from pycon 2013 is the one that really convinced me to avoid inheritance at all costs: https://pyvideo.org/pycon-us-2013/the-end-of-object-inheritance-the-beginning-of.html (The End Of Object Inheritance & The Beginning Of A New Modularity by Augie Fackler and Nathaniel Manista)

/hold

@tekton-robot tekton-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Aug 14, 2019
@dlorenc
Copy link
Contributor Author

dlorenc commented Aug 15, 2019

My personal feeling is that a slight reduction in the amount of code that needs to be written and faster refactoring are not worth introducing inheritance.

I don't think embedding in this fashion is really inheritance: https://medium.com/@simplyianm/why-gos-structs-are-superior-to-class-based-inheritance-b661ba897c67

but don't feel strongly enough about this to argue it. I think that criticism could basically apply to any form of struct or interface embedding, which we (and most Go programs) make heavy use of.

@dlorenc
Copy link
Contributor Author

dlorenc commented Aug 15, 2019

I wonder if there is another way we can tackle this - if we're finding that the interface our resources are implementing contains functions which frequently aren't needed for resources, maybe we should change the interface - maybe we should have a separate object for UploadVolume + DownloadVolume specs for example, with no implementation for types that don't need it.

One idea I was kicking around is to remove most of these methods and give resources one method with a signature something like:

func ModifyTaskSpec(spec TaskSpec) (TaskSpec, error)

which would be more flexible, but harder to unit test and provide less well-defined lifecycle hooks. Any thoughts on that approach?

@bobcatfish
Copy link
Collaborator

I don't think embedding in this fashion is really inheritance: https://medium.com/@simplyianm/why-gos-structs-are-superior-to-class-based-inheritance-b661ba897c67

Thanks for the link! Unfortunately I'm still not completely convinced, but I'd really like to be :( Like you said, struct embedding is used all over the place, and I find it makes reasoning about the code very difficult.

The article suggests that the main downside of inheritance is that "changing a base class can cause unwanted side effects all over a code base" and I don't see how that's any different when the "base" class is embedded vs. inherited.

type Foo struct {}
type Bar struct {
  Foo
}
type Baz struct {
  Bar
}

func (Foo) A() {
   fmt.Println("Foo A")
}

func (Foo) B() {
   fmt.Println("Foo B")
}

func (Bar) B() {
   fmt.Println("Bar B")
}

func (Baz) C() {
   fmt.Println("Baz C")
}


func main() {
	f := Baz{}
	f.A()
	f.B()
	f.C()
}

Output:

Foo A
Bar B
Baz C

Seems nearly as confusing as conventional inheritance to me!

I think that criticism could basically apply to any form of struct or interface embedding, which we (and most Go programs) make heavy use of.

I totally agree that this applies to all embedding, not just this particular case!

On a similar note - Python programs use inheritance and polymorphism like crazy, but I'd still maintain that it should be avoided as much as possible.

func ModifyTaskSpec(spec TaskSpec) (TaskSpec, error)

hmm interesting! this gave me some more ideas (im not super convinced by any of them either tho, and in fact I'm leaning toward going along with what you proposed in the meantime XD - i mention in idea 2 tho that im wondering how this will work with resource extensibility)

idea 1 - embedding & modifying TaskSpec

What do you think about this compromise, which does use struct embedding but I think limits the blast radius a bit more:

  • Each Resource type has two functions, e.g.:
    • func (s GitResource) GetTaskSpecInputUpdates() TaskSpecUpdates
    • func (s GitResource) GetTaskSpecOutputUpdates() TaskSpecUpdates
  • TaskSpecUpdates is an interface with functions for each type we know we want to update, in this case Volumes and Steps
  • We have an implementation TaskSpecNoUpdates for which Volumes and Steps return empty lists
  • Each Resource type can have its down Updater, e.g. GitInputUpdates which can embed TaskSpecNoUpdates if it wants, to default to updating nothing, or the function can simply return TaskSpecNoUpdates if there are none.

What I like about this:

  • TaskSpecNoUpdates is a bit more descriptive than PipelineResourceBase
  • We can expand TaskSpecUpdates with other parts of a TaskSpec that need to be updated (probably wouldnt ever happen tho??)
  • The existing types don't start inheriting from a base class; we limit this to only the functionality we want to control this way (the TaskSpec modification) vs. the entrity of each Resource type

idea 2 - pod spec

I was trying to think what other parts of the TaskSpec a PipelineResource might need to modify, and I realized it was totally news to me that they needed to add Volumes as well (thanks for #1139 btw!!)

The proposed contract in the resource extensibility proposal has beforecontainers and aftercontainers but nothing about volumes at all, however I think they are required. So a couple ways to handle that:

  • Add Volumes to the contract as well (which assumes we only ever need to update steps + volumes to make PipelineResources happen)
  • Make each of the beforecontainers and aftercontainers a pod spec which the Pipeline controller would have to introspect and somehow merge with the pod it wants to make (similar probably to how we merge stepTemplate)

@bobcatfish
Copy link
Collaborator

/joke

@tekton-robot
Copy link
Collaborator

@bobcatfish: Why did the scarecrow win an award? Because he was outstanding in his field.

In response to this:

/joke

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@bobcatfish
Copy link
Collaborator

wow

@dlorenc dlorenc closed this Aug 16, 2019
bobcatfish added a commit to bobcatfish/pipeline that referenced this pull request Sep 12, 2019
Now that we don't automatically copy the content of an input to an
output (when the same resource is used as both an input and an output),
this means that:
- Our fan-in / fan-out test will need to explicitly write to the output
  path, instead of writing to the input path and assuming it would get
  copied over (the very behaviour we're changing in tektoncd#1188)
- Data previously written to an output that is used as an input, and
  then an output later on, will be lost unless explicitly copied. In the
  update to the examples this was handled by symlinking the input to the
  output, I decided to instead update the test to no longer expect to
  see a file that was written by the first task in the graph and to not
  copy it explicitly.

Note that there is actually a race between the two tasks fanning out -
if the were writing the same file we would not be able to reliably
predict which would win.

Part of fixing tektoncd#1188
tekton-robot pushed a commit that referenced this pull request Sep 13, 2019
Now that we don't automatically copy the content of an input to an
output (when the same resource is used as both an input and an output),
this means that:
- Our fan-in / fan-out test will need to explicitly write to the output
  path, instead of writing to the input path and assuming it would get
  copied over (the very behaviour we're changing in #1188)
- Data previously written to an output that is used as an input, and
  then an output later on, will be lost unless explicitly copied. In the
  update to the examples this was handled by symlinking the input to the
  output, I decided to instead update the test to no longer expect to
  see a file that was written by the first task in the graph and to not
  copy it explicitly.

Note that there is actually a race between the two tasks fanning out -
if the were writing the same file we would not be able to reliably
predict which would win.

Part of fixing #1188
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cla: yes Trying to make the CLA bot happy with ppl from different companies work on one commit do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. lgtm Indicates that a PR is ready to be merged. size/M Denotes a PR that changes 30-99 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants