devcontainers / features

A collection of Dev Container Features managed by Dev Container spec maintainers. See https://github.com/devcontainers/feature-starter to publish your own
https://containers.dev/features
MIT License
812 stars 313 forks source link

Ruby Feature: Which ruby version manager would you prefer? #757

Open samruddhikhandale opened 7 months ago

samruddhikhandale commented 7 months ago

Hello! πŸ‘‹

We are looking forward to get some feedback for refactoring the Ruby Feature.

Which Ruby version manager would you like the Feature to install by default? You can use the respective emojis for voting your preference!

  1. None - πŸ˜•
  2. rbenv - πŸŽ‰
  3. rvm - πŸš€
  4. Any other (feel free to drop a comment)

For the ruby Feature, we have heard issues with conflicting ruby managers (rbenv and rvm) which can be problematic for complex projects. We have a community member who is willing to contribute a PR with the necessary changes ✨ See https://github.com/devcontainers/features/issues/603#issuecomment-1806853742 ❀

(Draft) Proposal

Looking forward to everybody's feedback, thank you!

nevans commented 7 months ago

In the case of installing with apt, do we just accept whatever version comes from the OS? Or do we require that it matches the version that was specified for the container? Although I do like to use Debian/Ubuntu packages when I can, they will be constrained as to which version can be installed. For consistency, I think it's probably better to always "install from source" in the normal "none" case and simply ignore the OS distribution's packages (and maybe add a fourth "apt" option?).

ThomasOwens commented 7 months ago

@nevans

I was trying to not scope-creep this too much, but I think I see what you're getting at.

From what I can tell, the default none case does rely on whatever comes from the container. That isn't the best solution. It looks like Debian comes with Ruby and the common-utils feature runs an upgrade of all OS packages. If I'm understanding the scripts correctly, none always gets you the latest Ruby release (in the Debian repos, anyway). Not necessarily what people want. Plus, if Debian stopped distributing Ruby for some reason, we'd be in a tough spot.

I believe the solution is this:

  1. Create a new Feature option rubyVersionManager to define the Ruby version manager. It will be a string with enum: ["none", "rbenv", "rvm"]. The default value will be the poll results here.
  2. Create a new Feature option rubyVersion to define the version of Ruby to install. It will be a string with some proposals for supported Ruby versions. This should be freeform, though, and parsed out to download any version of Ruby.
  3. Based on the values of rubyVersionManager and rubyVersion, install the specified Ruby version. Install from source for none or using the version manager otherwise. When installing from source, set the path variables properly.

Would need to deal with the Ruby version from source. The URL for the source tarball for Ruby X.Y.Z would be https://cache.ruby-lang.org/pub/ruby/X.Y/ruby-X.Y.Z.tar.gz or https://github.com/ruby/ruby/archive/refs/tags/vX_Y_Z.tar.gz. That's not so bad. The preview versions, on the other hand, are of the format https://cache.ruby-lang.org/pub/ruby/X.Y/ruby-X.Y.Z-previewN.tar.gz or https://github.com/ruby/ruby/archive/refs/tags/v3_3_0_previewN.tar.gz. You need to not only know the major, minor, and patch version, but also the fact that it is a preview and which preview build it is. I'd have to think this through some more. I'd also need to understand how version managers specify preview builds. Although perhaps we don't need to worry about preview versions at all - anyone using one of them in a dev container can create custom onCreate or postCreate scripts to handle this specific case.

This does bring up an interesting question, though. Perhaps I was wrong in that no version manager should be installed. If you want true isolation between whatever Ruby comes with the OS and your app's Ruby version, the best way to do that is a version manager. I should revisit good practices for containerization of Ruby applications - perhaps it is insufficient to be containerized and use a system Ruby.

samruddhikhandale commented 7 months ago

In the case of installing with apt, do we require that it matches the version that was specified for the container?

Yes, this was the plan ^

As installing from apt is faster than installing from source. We thought it made sense to try adding ruby with apt.

For consistency, I think it's probably better to always "install from source" in the normal "none" case and simply ignore the OS distribution's packages (and maybe add a fourth "apt" option?). Although I do like to use Debian/Ubuntu packages when I can, they will be constrained as to which version can be installed.

We could also update the version option to accept os-provided, and it installs whichever version is available in apt feeds.

samruddhikhandale commented 7 months ago

Create a new Feature option rubyVersion to define the version of Ruby to install. It will be a string with some proposals for supported Ruby versions. This should be freeform, though, and parsed out to download any version of Ruby.

@nevans we already have version option, why not use that?

samruddhikhandale commented 7 months ago

Can we install ruby from the prebuilt binaries available in https://github.com/ruby/ruby-builder/releases/tag/toolcache ? This would make the Ruby Feature way faster to install. Also, we could still keep the install from source fall back if some version is not available in prebuilt binaries.

Update:

ThomasOwens commented 7 months ago

@samruddhikhandale

Thinking this through, I'm not sure using the OS-provided version will work. I need to test this specifically, but that version will get updated when the common-utils feature updates all the OS packages. If that is indeed the case, a devcontainer user has no control over the OS-provided Ruby version. Plus, if other OS-packages have a dependency or otherwise expect a more recent version of Ruby than what the application needs, other OS-packages could end up in a non-functional state.

I know I was one of the people who questioned even installing a Ruby version manager. And in very lightweight production Ruby container, that may make sense if the only thing the container is doing is running your application. However, in a development environment, you have other tools. And I'm not sure I'd want to be forcing the version of Ruby to something else, especially a much lower version than what is in the OS repositories.

I'm starting to lean toward requiring a Ruby version manager, even if it only points to the OS-installed Ruby version. It would still cause an issue if the OS ever stopped providing Ruby, but that could be a breaking change for a lot of people out there.

I can look at using ruby-builder's toolcache as a possibility. But the standard container is a Debian container. Not sure if the Ubuntu builds will work. Maybe worth a look, though.

Overall, it doesn't change the approach I'm taking. Just a few minor details to work out.

nevans commented 7 months ago

As installing from apt is faster than installing from source. We thought it made sense to try adding ruby with apt.

It does make sense! ...except that it probably won't match the requested version. πŸ˜‰

We could also update the version option to accept os-provided, and it installs whichever version is available in apt feeds.

That option makes sense to me. Are there other devcontainer features that go this route? If so, then we can follow their lead. Is os-provided a version name that is used by other devcontainer features? I think I've seen system used more commonly by various version managers, but consistency with other devcontainer features should take priority, IMO.

Thinking this through, I'm not sure using the OS-provided version will work

Honestly, I think that skipping the os-provided version is the simplest approach, at least for the first pass. Maybe iterate to allow that as an option in the future, after the most important case is handled: ensuring the user-specified version is installed.

I'm starting to lean toward requiring a Ruby version manager

Similarly, while it might be nice to provide a simple "no version manager" option, that doesn't need to be in the first updated release. We can iterate towards that. But it's not an option today, so it's not like the feature would be losing any functionality if it's left out of a first PR.

When installing from source, set the path variables properly.

Note that installing from source should be fine without setting any ENV variables, so long as ruby is installed to /usr/local/bin (which is the default for installing from source). In my opinion, it's preferred: rubygems uses smart dynamic defaults for GEM_HOME and GEM_PATH but only when they aren't set. Setting them to empty strings will prevent these defaults from being used, and docker makes it difficult to unset environment variables. Similarly, I think that rbenv should only need its own binaries and shims to be on the PATH. Setting any other env vars will override everything that rbenv does in its shims.

rvm might need GEM_HOME and GEM_PATH (I'm not sure). But you may want to keep them as-is even if it doesn't need them, simply for backward compatibility with the current feature.

The biggest problem with setting these vars is that the dynamic defaults will automatically adjust the directories based on binary compatibility with your current ruby version. When they env vars are set statically, you can wind up with compiled extensions that are incompatible with your currently installed ruby version... which kind of defeats the purpose of a version manager. πŸ˜‰ ENV variables are great for overrides, but lousy for dynamic defaults.

I can look at using ruby-builder's toolcache as a possibility. But the standard container is a Debian container. Not sure if the Ubuntu builds will work. Maybe worth a look, though.

Even if you were able to get it working with both Ubuntu and Debian, I'd still want to open an issue in that repo to make sure they're okay taking on a new dependency. It may be that they don't want to promise ongoing compatibility and stability for anything other than https://github.com/ruby/setup-ruby.


If you do create a default "install" script, I recommend that 1) we install to /usr/local and 2) we should lean heavily on one of the other existing tools. Some options:

And if the standard defaults don't work for some reason, rather than set the standard ENV vars, we should consider doing what the linux distros do, and add our own 'operating_system.rb' file to rubygems. See, for example:

samruddhikhandale commented 7 months ago

Thank you @ThomasOwens @nevans for your amazing thoughts, appreciate it! ✨

Is os-provided a version name that is used by other devcontainer features?

Yes, Python and Git Feature currently supports ie. See https://github.com/search?q=repo%3Adevcontainers%2Ffeatures%20os-provided&type=code

It does make sense! ...except that it probably won't match the requested version. πŸ˜‰ Honestly, I think that skipping the os-provided version is the simplest approach, at least for the first pass.

Apologies for not writing a detailed proposal, I was proposing to use apt only if the requested version matches with the one available in the feeds. However, if not, install from source.

If we could support os-provided as a value for version, then this is way better approach. The reason to have an option to install from apt is simply because that's faster. Installing from source takes quite a lot of time compared to installing from apt, and we could soon hear complaints about the Feature taking too long πŸ˜… So this is kind of like a precaution and a faster alternative.

I know I was one of the people who questioned even installing a Ruby version manager

Similarly, while it might be nice to provide a simple "no version manager" option, that doesn't need to be in the first updated release. We can iterate towards that. But it's not an option today, so it's not like the feature would be losing any functionality if it's left out of a first PR.

I agree having some ruby version manager is definitely better than having none. Then we have that question of which version manager should be installed by default? Should it be rvm, rbenv or something else?

According to the current polls, it's one vote for rbenv and three votes for none πŸ˜…

We definitely want to refactor the Ruby Feature as per how the community prefers, but it doesn't seem like we have enough votes right now.

If folks prefer to not have any ruby version manager installed, and if it's possible to install the requested version of ruby from source or ruby prebuilt binaries, then having none as default makes sense. This will most likely be used by learner development scenarios who doesn't have a preference on version managers and simply want some working version of ruby for their hobby projects.

In case of more complex scenarios, folks would have their own preference of ruby version managers which they could simply install with rubyVersionManager option.

Talking about the Ruby image, depending upon how this discussion evolves, we could decide a plan for it.

But you may want to keep them as-is even if it doesn't need them, simply for backward compatibility with the current feature.

This refactoring PR would most likely be a major version bump of the Feature. Major version bumps don't necessarily guarantee that it supports back compat, hence, if this might cause any other (future) issues, then we could avoid adding it.

Even if you were able to get it working with both Ubuntu and Debian, I'd still want to open an issue in that repo to make sure they're okay taking on a new dependency. It may be that they don't want to promise ongoing compatibility and stability for anything other than https://github.com/ruby/setup-ruby.

That sounds like a great idea, @nevans feel free to loop me in if necessary

ThomasOwens commented 7 months ago

@nevans and @samruddhikhandale

I've opened a draft/work-in-progress PR to start to show my work iteratively.

At this point, there should be parity with the Node Feature. I used that as a model since Node is frequently used to satisfy a dependency in Rails so I have some familiarity with setting up a Node development environment. I also noticed that they were using NVM, which is the Node equivalent to RVM. Although I prefer rbenv (and nodenv), this is a good first step. The initial issue of having multiple version managers has been resolved, which was my number one concern. At this point, I think it's iteration until it's good enough to merge.

Would need to check and images that use this Feature just to make sure requesting the image will be fine. I know it's used in the Ruby and the Ruby on Rails and Postgres devcontainers, but I'm not sure how to test building those while pointing at my modified feature.

samruddhikhandale commented 7 months ago

// cc @eljog and @joshspicer for visibility.

ThomasOwens commented 6 months ago

@samruddhikhandale @eljog @joshspicer

I realize it's getting close to holiday time in the US and you're all US-based from your profiles, so not expecting a fast response, but I wanted to check in on how to get this to a ready-to-merge state.

I have two open questions:

Once the minimum feature set is implemented with appropriate test coverage, I can flip the PR out of its draft state.

nevans commented 6 months ago

@ThomasOwens NOTE: I edited this comment after first posting it.

I'll copy these comments over to the PR. To summarize my thoughts here:

As an incremental improvement, it's fine. But if we are going to bump the version number for backward incompatibility, I personally vote for delaying until we can make rvm fully optional. This is only a minor improvement over the current version: rbenv is unusable now, and (after the PR) rbenv will still be unusable without more backward incompatible changes.

I personally consider the minimum feature set to be:

My recommendation on how to satisfy my "minimum feature set":

I'm not sure if it should be part of the minimum feature set, but I think we should probably do one of the following, to aid with backward-incompatible upgrade pains:

None of the following are in my personal minimum feature set... but they would all be nice to have:

What do you think? (My apologies: I'll probably be too busy with other end-of-year stuff both at work and in my personal time, and I probably can't really offer any more than my criticism right now.)

ThomasOwens commented 6 months ago

@nevans

Thank you! Very much appreciated.

I agree that there are some minimum items open for a 1.3.0 incremental improvement:

I do think the overall question about how far to go is more of a strategic question for the core devcontainer team, though. Does it make sense to have a 1.3.0 that follows the patterns of the Node Feature (that is, the current PR plus the environment variable cleanup)? Or would it be better for a 2.0.0 that does a lot more of what you suggest?

I think your minimum suggestions for what a 2.0.0 look like are probably spot-on:

I don't agree with supporting os-provided, though. I think that if you're requesting the Ruby Feature in your devcontainer, there should be a guarantee that a Ruby version exists. I do think it's safe to check if an OS-provided Ruby exists and matches the requested version, but that requires requesting a specific version.

iwdt commented 5 months ago

It seems to me that Ruby feature should only install Ruby (via ruby-install or ruby-build and when finished, remove it). rbenv, rvm, chruby, asdf, etc. should be separate features that are installed separately if the user needs it

ruby-install looks like the easiest way to do this, since it takes into account installing all necessary dependencies (which may change from version to version) if they are missing, while ruby-build just downloads the archives and starts building.

samruddhikhandale commented 5 months ago

@ThomasOwens @nevans Apologies for the delay in response, was OOF.

would it be better for a 2.0.0 that does a lot more of what you suggest?

Going over the discussion, I feel bumping the major version makes sense to me. This gives us more flexibility for making changes, and we don't have to worry about backwards compatibility.

Always install ruby-build. It's small and, since it's an installer and not a version manager, shouldn't conflict with any other version manager.

An option to install rvm. This is the only working version manager today, so being able to request it should minimize the amount of people breaking with an update.

Install Rubies built by ruby-build to /usr/local. This is based on my understanding of what /opt and /usr/local are used.

They ^ make sense to me and sounds good. πŸ‘

Necessary versions are installed with your version manager. If you don't have a version manager, ruby-build takes care of installing them. If you install rvm, install Rubies with rvm. When rbenv is supported, install Ruby versions with rbenv and ruby-build.

Does this mean we are leaning towards not installing any manager?

By going over the reactions on this issue, looks like the community wants rbenv to be installed by default. As we were trying to shape the changes based on community feedback, it makes sense to me.

It seems to me that Ruby feature should only install Ruby (via ruby-install or ruby-build and when finished, remove it). rbenv, rvm, chruby, asdf, etc. should be separate features that are installed separately if the user needs it ruby-install looks like the easiest way to do this, since it takes into account installing all necessary dependencies (which may change from version to version) if they are missing, while ruby-build just downloads the archives and starts building.

Thanks @iwdt for pointing out. However, I am not too familiar with https://github.com/postmodern/ruby-install, and have questions on how well it's supported and maintained. Also, I am not confident if the the community will prefer to install ruby from third party binaries. I believe this will need an audit and security review before proceeding with this. // cc @Chuxel @bamurtaugh @craiglpeters wondering if you are familiar with https://github.com/postmodern/ruby-install ?

ThomasOwens commented 5 months ago

Does this mean we are leaning towards not installing any manager?

I don't think so, @samruddhikhandale. It seems like the community is in favor of rbenv, so that should be the default. Anything else would be an option. Minimally, none should also be an option. I'd like to include rvm as well.

I'd also tend to agree with not including ruby-install, at least now. It could easily be a 2.1 thing, if someone is interested in that. The idea of installing third-party binaries doesn't seem that appealing to me, even as an option.

I think the only open question from my end is the deal with the base image. It feels a bit strange to use a Ruby image and then install more Ruby stuff on top of it. I had put this in an earlier comment:

I see that the actual Ruby images use a Ruby image as their base. That seems both unexpected and wrong to me, but perhaps the tests should reflect the current reality rather than the ideal state.

samruddhikhandale commented 5 months ago

I don't think so, @samruddhikhandale. It seems like the community is in favor of rbenv, so that should be the default. Anything else would be an option. Minimally, none should also be an option. I'd like to include rvm as well.

Great, aligns with my thought!

I think the only open question from my end is the deal with the base image. It feels a bit strange to use a Ruby image and then install more Ruby stuff on top of it. I had put this in an earlier comment:

This has been historical, the ideology here is to use official images as base (so ruby in this case) and top it off with more utils to improve the dev container experience (using common-utils Feature, adding git, and other ruby pkgs/utils).

You will see the same behavior with majority of the devcontainer/images (See dotnet, python, anaconda)

bamurtaugh commented 5 months ago

However, I am not too familiar with https://github.com/postmodern/ruby-install, and have questions on how well it's supported and maintained. Also, I am not confident if the the community will prefer to install ruby from third party binaries. I believe this will need an audit and security review before proceeding with this. // cc @Chuxel @bamurtaugh @craiglpeters wondering if you are familiar with https://github.com/postmodern/ruby-install ?

Thanks for tagging me. On this point - I agree with your takeaway @samruddhikhandale πŸ‘ (I'm also not familiar with https://github.com/postmodern/ruby-install, so feels like it'd be safest not to include).

nevans commented 5 months ago

I am familiar with https://github.com/postmodern/ruby-install and it's great. I have great respect for the maintainer (and also for the rvm maintainers). And using either would simplify the install scripts. Nevertheless, I think both should be opt-in only.

So I'm in agreement with @samruddhikhandale, @ThomasOwens, and @bamurtaugh: The default should be ruby-build. It is the only tool officially supported by the ruby core team (as well as JRuby, TruffleRuby, etc).

samruddhikhandale commented 4 months ago

@ThomasOwens Wondering if you have started working on this, and let us know if you need any help. Thanks!

ThomasOwens commented 4 months ago

@samruddhikhandale Yep. Had to get some tools updated, but I've started refactoring the work in the draft PR and will start to work toward the decisions above. A bit busy this week/weekend, but looking to get more into this after.

samruddhikhandale commented 4 months ago

@samruddhikhandale Yep. Had to get some tools updated, but I've started refactoring the work in the draft PR and will start to work toward the decisions above. A bit busy this week/weekend, but looking to get more into this after.

That's a pretty exciting news, thank you so much! πŸŽ‰

koleskizzzzz commented 2 months ago

Hello! πŸ‘‹

We are looking forward to get some feedback for refactoring the Ruby Feature.

Which Ruby version manager would you like the Feature to install by default? You can use the respective emojis for voting your preference!

  1. None - πŸ˜•
  2. rbenv - πŸŽ‰
  3. rvm - πŸš€
  4. Any other (feel free to drop a comment)

For the ruby Feature, we have heard issues with conflicting ruby managers (rbenv and rvm) which can be problematic for complex projects. We have a community member who is willing to contribute a PR with the necessary changes ✨ See #603 (comment) ❀

(Draft) Proposal

  • Create a new Feature option which will define the ruby version manager to be used for installing ruby

    • Let’s say we call it rubyVersionManager
    • It will be string with enum: ["none", "rbenv", "rvm"])
    • Default value: "TBD: Poll result"
  • Based on the value provided by rubyVersionManager, the Feature will use that version manager to install ruby
  • In case of none, we will first attempt to install it with apt.

    • If it fails, then install from source.

Looking forward to everybody's feedback, thank you!