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

Vendorless Knative #134

Open
11 of 17 tasks
chizhg opened this issue Mar 22, 2022 · 34 comments
Open
11 of 17 tasks

Vendorless Knative #134

chizhg opened this issue Mar 22, 2022 · 34 comments
Assignees
Labels
Epic Epics to group issues priority/important-soon Must be staffed and worked on either currently, or very soon, ideally in time for the next release. triage/accepted Issues which should be fixed (post-triage)

Comments

@chizhg
Copy link
Member

chizhg commented Mar 22, 2022

Currently, all the Knative projects are using go mod vendor, resulting in a huge number of files in the vendor directory.

Reasons for vendor dir removal

Maintenance burden for productivity

Having the vendor directory in every project creates a huge maintenance burden for productivity. Every single tool we use (various linters, compilers, formatters etc.) needs to be properly filtered, so it will give off false-positives. Here's a live example: knative-extensions/knobots#259 (there were multiple similar issues in the past)

Hard to count the contributions

Counting contributions is important for OSS projects, as it provides some insights into the health of the project. Having a vendor dir, impedes that, immensely. We need to filter out bots that contribute, and moreover, filter out the people contributions in vendor directory (or vendors in go.work is in place). Otherwise, the amount of the vendor code overwhelms any statistics.

Project looks dated

The enhancements in Go that made it possible to work without vendor directory safely has been made a long time ago - Go 1.13 released Sep 3, 2019 (over 3y ago). Since then, most of the code in Go has moved away from having the vendor directory. Sticking with it shows the project is old-fashioned, especially for newcomers. Remember when people attached external jar files to their Java projects, manually? That was before Maven was created to solve dependency management. The same was done for Go in 1.13+ releases.

Bad code habits

Having a vendor directory allows for relying on bad code habits, and hacks. Instead of using that just for Go code, we sometimes use it as a general tool for doing path traversal to some of the resources in the dependent projects. That's a smell, and may lead even to a vulnerability. Example of such smell can be seen in Knative-Serving:

$ find . -type l -printf '%p -> ' -exec readlink {} ';' | grep vendor
./config/core/configmaps/network.yaml -> ../../../vendor/knative.dev/networking/config/config-network.yaml
./config/core/300-imagecache.yaml -> ../../vendor/knative.dev/caching/config/image.yaml
./config/core/300-resources/certificate.yaml -> ../../../vendor/knative.dev/networking/config/certificate.yaml
./config/core/300-resources/domain-claim.yaml -> ../../../vendor/knative.dev/networking/config/domain-claim.yaml
./config/core/300-resources/ingress.yaml -> ../../../vendor/knative.dev/networking/config/ingress.yaml
./config/core/300-resources/serverlessservice.yaml -> ../../../vendor/knative.dev/networking/config/serverlessservice.yaml

(This is code smell, as symlinks are not cross-platform)

Status

The proposed roadmap has been presented at TOC (slides: https://docs.google.com/presentation/d/1eK7vhSRnahOq34WYkKJde8hvXMvb8eY3r76Wem5Ia9A) and was accepted.

Roadmap

Below is a list of issues that form this Epic.

  1. First phase (the ordering is relevant)
    1. Hacks needs to be sourced without the vendor directory
    2. Knative hack scripts need to work with repos with vendor and without
    3. Notify the Knative contributors they can remove vendor if not using code-gen, or having symlinks to vendor/
    4. Check is it possible to execute K8s's shell scripts in vendorless project
  2. Second phase (could be done in any order)
@dprotaso
Copy link
Member

dprotaso commented Mar 23, 2022

which can add a lot of noise when reviewing PRs that introduce extra dependencies.

If this is the sole reason I would split this into a separate commit and review the commits independently

Just as an FYI dropping this practice will be quite a lot of work since the majority reason we vendor deps is forpulling in non-go code artifacts

Some examples:

  1. code-gen scripts from https://github.com/kubernetes/code-generator.
  2. reconciler gen scripts in knative.dev/pkg that are used for generating injection clients.
  3. knative.dev/hack scripts being propagated everywhere

Another extreme case is carrying patches for dependencies while we wait for them to be merged upstream. We've done that before for some K8s issues.

I actually do like seeing the vendored dependencies in PRs since it gives reviewers the ability to sanity check the changes being pulled in. This makes it easier to debug at times. If we drop vendoring I would hope we put an effort to improve our tooling around visualizing the changes - ie. see this dependabot PR - ko-build/ko#663

@chizhg
Copy link
Member Author

chizhg commented Apr 1, 2022

If this is the sole reason I would split this into a separate commit and review the commits independently

That's true, but we don't have a way to enforce it and usually it's tedious (at least for me) to keep that in mind.

If we keep vendor/, I found the pull request file tree does help reduce the noise though.

@github-actions
Copy link

This issue is stale because it has been open for 90 days with no
activity. It will automatically close after 30 more days of
inactivity. Reopen the issue with /reopen. Mark the issue as
fresh by adding the comment /remove-lifecycle stale.

@github-actions github-actions bot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Oct 11, 2022
@cardil
Copy link
Contributor

cardil commented Oct 21, 2022

/remove-lifecycle stale
/triage accepted

@knative-prow knative-prow bot added triage/accepted Issues which should be fixed (post-triage) and removed lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. labels Oct 21, 2022
@cardil
Copy link
Contributor

cardil commented Mar 14, 2023

/assign

@cardil
Copy link
Contributor

cardil commented Mar 14, 2023

I will list here a couple of reasons why we should get rid of the vendor, and address some of the concerns people often raise.

Reasons for removal

Maintenance burden for productivity

Having the vendor directory in every project creates a huge maintenance burden for productivity. Every single tool we use (various linters, compilers, formatters etc.) needs to be properly filtered, so it will give off false-positives. Here's a live example: knative-extensions/knobots#259 (there were multiple similar issues in the past)

Hard to count the contributions

Counting contributions is important for OSS projects, as it provides some insights into the health of the project. Having a vendor dir, impedes that, immensely. We need to filter out bots that contribute, and moreover, filter out the people contributions in vendor directory (or vendors in go.work is in place). Otherwise, the amount of the vendor code overwhelms any statistics.

Project looks dated

The enhancements in Go that made it possible to work without vendor directory safely has been made a long time ago - Go 1.13 released Sep 3, 2019 (over 3y ago). Since then, most of the code in Go has moved away from having the vendor directory. Sticking with it shows the project is old-fashioned, especially for newcomers. Remember when people attached external jar files to their Java projects, manually? That was before Maven was created to solve dependency management. The same was done for Go in 1.13+ releases.

Bad code habits

Having a vendor directory allows for relying on bad code habits, and hacks. Instead of using that just for Go code, we sometimes use it as a general tool for doing path traversal to some of the resources in the dependent projects. That's a smell, and may lead even to a vulnerability. Example of such smell can be seen in Knative-Serving:

$ find . -type l -printf '%p -> ' -exec readlink {} ';' | grep vendor
./config/core/configmaps/network.yaml -> ../../../vendor/knative.dev/networking/config/config-network.yaml
./config/core/300-imagecache.yaml -> ../../vendor/knative.dev/caching/config/image.yaml
./config/core/300-resources/certificate.yaml -> ../../../vendor/knative.dev/networking/config/certificate.yaml
./config/core/300-resources/domain-claim.yaml -> ../../../vendor/knative.dev/networking/config/domain-claim.yaml
./config/core/300-resources/ingress.yaml -> ../../../vendor/knative.dev/networking/config/ingress.yaml
./config/core/300-resources/serverlessservice.yaml -> ../../../vendor/knative.dev/networking/config/serverlessservice.yaml

(This is code smell, as symlinks are not cross-platform)

Addressing some of the concerns

Vendor is needed for reproducible builds

When using go 1.13+ in standard configuration (with GOPROXY), the reproducible builds are already ensured. The go.sum file lists the exact sum of dependencies, and GOPROXY ensures those sources are present. In case of removal, the tag from a dependency sources, the GOPROXY will still return previously collected sources.

Patching vendor files before it's patched in a dependency

Patching the project dependencies isn't a thing to do for an OSS project, IMHO. The Knative product vendors (Red Hat, Google, VMware, etc.) are there to provide such service for their customers. It is 100% expected to have vendor directories, and other kinds of code mirrors in product vendor infrastructure. Having it also in upstream, diminishes the reasons why customers should choose one of the vendor's solutions, which in turn hurts the OSS project.

Sharing other no-go files

In the long term, we would like to have a hackless project (all hacks rewritten to tools, see knative/hack#254). We have a way to achieve that, see: knative/hack#222. Other things, like symlinks and artifacts for code generation still need some work to address them without relying on vendor dir, but I think either we could use a solution similar to knative/hack#222 or have a dedicated Go tool written (maybe on upstream K8s)

Some loose ends

Still, we have some loose ends for R&D:

@pierDipi
Copy link
Member

Another extreme case is carrying patches for dependencies while we wait for them to be merged upstream. We've done that before for some K8s issues.

In addition there is a big productivity boost for day to day development when trying to debug failing tests that are coming from a different repository within Knative itself.

Moreover, we still do this for some changes that didn't make into Knative pkg itself
https://github.com/knative-sandbox/eventing-kafka-broker/blob/main/hack/patches/defaulting-webhook-queue-name.patch

I actually do like seeing the vendored dependencies in PRs since it gives reviewers the ability to sanity check the changes being pulled in. This makes it easier to debug at times. If we drop vendoring I would hope we put an effort to improve our tooling around visualizing the changes - ie. see this dependabot PR - ko-build/ko#663

+1 to this.

Unless we have a solutions to the pain points listed in the various comments, if we really need to drop vendor I'd propose to at least give each project the final decision to chose to use or not use vendor.

@cardil
Copy link
Contributor

cardil commented Mar 15, 2023

@pierDipi your point about debugging the code isn't valid, or I don't understand it. When you run code or tests without vendor folder, the go code is still downloaded to your home's go env GOMODCACHE directory. So, the only thing that changes is the path of the file. All IDEs support that very well, and you are automatically navigated to a proper file and line. Same for logging. You just look at a different place.

Moreover, we still do this for some changes that didn't make into Knative pkg itself
https://github.com/knative-sandbox/eventing-kafka-broker/blob/main/hack/patches/defaulting-webhook-queue-name.patch

That's not good development practice to do so. It would be better to rely on some external interim package (fork of PKG maybe, or part of it), than patching the vendor. Anyone, person, or robot, or IDE, could easily restore the code just by calling go mod tidy. Also, other repos may consume your code as a dependency. They still need to watch out for those patches.

Relying on those patches for reasons other than vulnerability patches, is just asking for trouble 😵‍💫. And as I said above, vulnerability patching is a job for Knative product's vendors, not the OSS project.

@cardil
Copy link
Contributor

cardil commented Mar 15, 2023

@dprotaso:

I actually do like seeing the vendored dependencies in PRs since it gives reviewers the ability to sanity check the changes being pulled in. This makes it easier to debug at times.

Personally, I found double-checking the dependencies code an overkill. But maybe people need it... I think we can create such tooling, easily. Here's a recipe (pseudocode):

if !project.HaveVendor() {
  c := git.CurrentProject()
  c.CheckoutTarget()
  gomod.UpdateDeps()
  g := git.init(go.GOMODCACHE)
  g.CommitAll()
  c.CheckoutPR()
  gomod.UpdateDeps()
  d := g.Diff()
  artifacts.SaveAsArtifact(d, "dependency-changes.patch")
}

@pierDipi
Copy link
Member

@pierDipi your point about debugging the code isn't valid, or I don't understand it. When you run code or tests without vendor folder, the go code is still downloaded to your home's go env GOMODCACHE directory. So, the only thing that changes is the path of the file. All IDEs support that very well, and you are automatically navigated to a proper file and line. Same for logging. You just look at a different place.

When you need, for example, to improve the error message or try a quick fix while debugging you need to make changes to the dependency and rerun the test, I use that constantly, and no pushing to remove fork + go mod edit to poing to my fork is painful

@skonto
Copy link
Contributor

skonto commented Mar 15, 2023

At first glance:

The enhancements in Go that made it possible to work without vendor directory safely has been made a long time ago - Go 1.13 released Sep 3, 2019 (over 3y ago). Since then, most of the code in Go has moved away from having the vendor directory.

To help the discussion could you add some pointers to big projects with more deps than knative that have gone down that path? And why some others haven't eg. K8s?

When using go 1.13+ in standard configuration (with GOPROXY), the reproducible builds are already ensured.

Although I understand the benefits of goproxy, I have several questions. Which proxy do you suggest, private or public? Secondly, goproxy relies on its cache for fetching the same version what if there is an issue there or there is a ratelimit or what about limitations? Which popular OSS repos that do not commit vendor actually use GOPROXY or they just use some other mechanism?

It would be nice to list here also the drawbacks that are not addressed by the proposal.

@cardil
Copy link
Contributor

cardil commented Mar 15, 2023

@pierDipi:

When you need, for example, to improve the error message or try a quick fix while debugging you need to make changes to the dependency and rerun the test

Nothing stops you from changing the files in GOMODCACHE. It's the same as changing the vendor dir. Without the risk, you actually commit the vendor chenges.

@pierDipi
Copy link
Member

That's not good development practice to do so. It would be better to rely on some external interim package (fork of PKG maybe, or part of it), than patching the vendor. Anyone, person, or robot, or IDE, could easily restore the code just by calling go mod tidy. Also, other repos may consume your code as a dependency. They still need to watch out for those patches.

Relying on those patches for reasons other than vulnerability patches, is just asking for trouble face_with_spiral_eyes. And as I said above, vulnerability patching is a job for Knative product's vendors, not the OSS project.

what is a good practice is IMHO not in scope for this discussion because it really depends, I brought up a concrete case for using vendor, and, I don't think vulnerability patching is not in scope for Knative

@cardil
Copy link
Contributor

cardil commented Mar 15, 2023

@skonto: Go proxy has this value by default for go1.13+: GOPROXY="https://proxy.golang.org,direct"

Big companies probably should have their own goproxies, but they most likely should feed out to proxy.golang.org in the end.

@pierDipi
Copy link
Member

what I miss from this discussion is how much maintenance vendor brings to productivity tooling, do you have a concrete list of changes we have made in past 6 months to such "vendor handling tooling"?

@pierDipi
Copy link
Member

pierDipi commented Mar 15, 2023

@pierDipi:

When you need, for example, to improve the error message or try a quick fix while debugging you need to make changes to the dependency and rerun the test

Nothing stops you from changing the files in GOMODCACHE. It's the same as changing the vendor dir. Without the risk, you actually commit the vendor chenges.

That works locally, how do you do that in CI?

There is no risk of committing those changes since there are tests that check for vendor changes

@cardil
Copy link
Contributor

cardil commented Mar 15, 2023

That works locally, how do you do that in CI?

For CI, you shouldn't do it. Instead, commit an interim package - for example, your personal fork.

@pierDipi
Copy link
Member

That works locally, how do you do that in CI?

For CI, you shouldn't do it. Instead, commit an interim package - for example, your personal fork.

That is clearly a productivity regression since it's not as easy to do

@cardil
Copy link
Contributor

cardil commented Mar 15, 2023

what I miss from this discussion is how much maintenance vendor brings to productivity tooling, do you have a concrete list of changes we have made in past 6 months to such "vendor handling tooling"?

A rapid and rough search:

@pierDipi
Copy link
Member

You keep saying what I should or shouldn't do but the focus here should be on being productive with the day to day development for people developing Knative and I'm bringing up concrete cases on productivity boosts while using vendor that would be removed without vendor

@pierDipi
Copy link
Member

pierDipi commented Mar 15, 2023

what I miss from this discussion is how much maintenance vendor brings to productivity tooling, do you have a concrete list of changes we have made in past 6 months to such "vendor handling tooling"?

A rapid and rough search:

I don't see how those changes are related to "checking that vendor match go.mod" and even if there is how is that causing a lot of maintenance

@cardil
Copy link
Contributor

cardil commented Mar 15, 2023

@pierDipi:

You keep saying what I should or shouldn't do but the focus here should be on being productive with the day to day development for people developing Knative

I'm saying you shouldn't do it because by doing so, you are risking a huge productivity black hole for other people that will need to deal with unexpected changes in vendor directory. Both your fellow contributors, and other projects they may import your packages, may be hurt badly.

@pierDipi
Copy link
Member

pierDipi commented Mar 15, 2023

I'm saying you shouldn't do it because by doing so, you are risking a huge productivity black hole for other people that will need to deal with unexpected changes in vendor directory. Both your fellow contributors, and other projects they may import your packages, may be hurt badly.

those are quick changes to test stuff, not to be merged nor reviewed

@cardil
Copy link
Contributor

cardil commented Mar 15, 2023

those are quick changes to test stuff, not to be merged nor reviewed

Then you can just call go mod vendor and make your changes. If you just like to test something, also at CI. If that's not intended to be merged.

@cardil
Copy link
Contributor

cardil commented Mar 15, 2023

I don't see how those changes are related to "checking that vendor match go.mod" and even if there is how is that causing a lot of maintenance

That was a rough search. Some of those PRs were affected by the fact we have a vendor. The point here is that every single automation (linter, compiler, formatter, test) needs to be properly filtered to not take vendor into account (or multiple ones). Here's an example issue, that's still unsolved: knative-extensions/knobots#259

@cardil
Copy link
Contributor

cardil commented Mar 15, 2023

@skonto:

To help the discussion could you add some pointers to big projects with more deps than knative that have gone down that path?

A quick browse though GitHub trending projects:

Also, checkout this list: https://github.com/avelino/awesome-go (most of those don't have vendor).

BTW. Our friends at https://github.com/sigstore also don't use vendor (cc @mattmoor @n3wscott @vaikas)

The general answer would be: The big projects are slow to adapt to new ways of working

@skonto
Copy link
Contributor

skonto commented Mar 15, 2023

Cool I saw that too, this helps to have a better understanding of adoption of the proposed approach.

also don't use vendor

How builds are done is the next question and how about the GOPROXY limitations? It would be nice to get some insights from people who have put this in practice.

@cardil
Copy link
Contributor

cardil commented Mar 15, 2023

@skonto:

How builds are done is the next question

I don't understand your question: "How builds are done?". Exactly, the same: go build .... Nothing is changing here. In fact, we have used default config on CI (with GOPROXY) for more than 3 years now. What people have on the boxes is a different question? I have default settings.

and how about the GOPROXY limitations?

I presume you are referring to this article you mentioned earlier? If so, then that article is no longer true. Golang have GOPRIVATE env var for a long time now. See: https://stackoverflow.com/a/58306008/844449 or https://pkg.go.dev/cmd/go#hdr-Configuration_for_downloading_non_public_code

For example, this is how I install some of our private code, having the default Google's proxy set:

GOPRIVATE=example.redhat.com/* go install example.redhat.com/repo/thing@latest

Rate-limiting? I'm not aware of any at the default proxy. In fact, the builds are much faster with the proxy, than without it (GOPROXY=direct). And, you are safe from left-pad type issues when you are using GOPROXY. I don't see a reason anyone would need to use GOPROXY=direct, really.

@vaikas
Copy link
Contributor

vaikas commented Mar 15, 2023

First and foremost, things have indeed changed since the project started as was mentioned in one of the comments in the thread. Choices that were made many years ago should be revisited indeed to see if there are better ways to do things, so thanks for raising the point. The list of all the reasons why packages were originally vendored may not be the same as it was a while ago.

Some of the points here seem to be workflow preferences (seems to me, at least, so I will not really get into those. Not trying to dismiss them). This is mainly because I am no longer involved in the code day-to-day, and those that are should be able to decide what works best for them.

The one thing that was originally brought up that I do not see discussed that would be good to define the workflow for is how would one handle checking what the actual changes are (I do see the code snippet, but where would that live, and who would run it, etc.). I think this is what @dprotaso is referring to, which means that instead of seeing a go.mod package version change, you will actually also see the real code changes, and while I see your go snippet there, I think these kinds of things are what folks mean it's a lot of work, so I think having some understanding of the real work across all the repositories and who's going to tackle them might make it easier to make tradeoffs.

Other item that should be brought up is the tooling to handle the non-go files.

@cardil
Copy link
Contributor

cardil commented Mar 17, 2023

@vaikas:

I do see the code snippet, but where would that live, and who would run it, etc.

It could be one of the tools we will deliver. Executed manually go run ./tools/deps-changes, or make reports.deps-changes or other exaction way. That's open for discussion wrt knative/hack#254

Of course, I could also be executed and archived on CI.

I wonder how you deal with the issue of deps changes on sigstore project?

@vaikas
Copy link
Contributor

vaikas commented Mar 17, 2023

The deps management is mainly automated by dependabot and IMHO it makes it pretty easy. If you're manually bumping the deps, there's usually a reason, and therefore you know what you're pulling in. Here's one example (just pulled randomly) where you can easily see what's being changed. Others may have different and better ways, but I like doing this.

output

@cardil cardil transferred this issue from knative/test-infra Jul 20, 2023
@cardil
Copy link
Contributor

cardil commented Jul 20, 2023

I've been looking at loose ends that we identified to be able to remove the vendor:

Kubernetes code-gen

There's a rewrite of the K8s code generator scripts - see kubernetes/kubernetes#117262 (K8s 1.28+). With it, and our approach of embedding scripts (knative/hack#222) it should be possible to run the code gen without vendor.

I'll also propose to rewrite the K8s code-gen to Go native code, instead of scripts.

Until those came to our projects (K8s 1.28+), we could use a fork of the K8s code-gen.

Knative code-gen

Our code-gen scripts can be rewritten to Go files, and executed with go run pkg@ver --params construct.

Using the files across the repos

We sometimes have symlinks from one repo to another. Most notably in serving (see #134 (comment))

The use of symlinks isn't cool, as it's not platform neutral. We would be better with having those YAMLs extracted at build time (similarly to knative/hack#222).

@krsna-m
Copy link
Contributor

krsna-m commented Jul 20, 2023

I am glad you are taking the lead on this @cardil. My 2cents are that we can already start to work on the rewrite into golang. If some of the scripts are simple enough i would keep them as is. Another thing to think about is that rewriting would essentially get us a fork that we would have to maintain so either 1. we should upstream the changes or 2. use what they have with minimal changes to limit the amount of extra things we have to support.

I agree about the symlinks, but the reality is they are being used. This should be brought up in TOC so that we get more eyes on it and kindly ask people to get away from this pattern and suggest other solutions to solve the issue that led them to symlinks.

Hope to see this land sooner than later!

@cardil
Copy link
Contributor

cardil commented Sep 1, 2023

The proposed roadmap has been presented at TOC (slides: https://docs.google.com/presentation/d/1eK7vhSRnahOq34WYkKJde8hvXMvb8eY3r76Wem5Ia9A) and was accepted.

I'll update this issue description to include the issues to be tracked in this epic, which will serve as a roadmap tracker.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Epic Epics to group issues priority/important-soon Must be staffed and worked on either currently, or very soon, ideally in time for the next release. triage/accepted Issues which should be fixed (post-triage)
Projects
Status: In Progress
Development

No branches or pull requests

8 participants