dmac / fezzik

A light deployment system that takes care of the heavy lifting.
https://rubygems.org/gems/fezzik
MIT License
37 stars 6 forks source link

Allow specifying Fezzik env vars outside of destination blocks #27

Open philc opened 12 years ago

philc commented 12 years ago

Allow specifying Fezzik env vars outside of destination blocks

A pattern we've commonly seen is to create a big hash of env options, declare it outside of a Fezzik.destination block, and then call Fezzik.env on each of the key value pairs in that hash inside of a Fezzik.destination block:

options = {
  db_host: "localhost",
  unicorn_workers: 1,
  ...
}

Fezzik.destination :vagrant do
  set :domain, "..."
  options.each { |key, value| Fezzik.env key, value }
  ...
end

Fezzik.destination :staging do
  set :domain, "..."
  options.each { |key, value| Fezzik.env key, value }
  ...
end

What we really want to do is just declare these env vars using Fez and let each host override some of them:

Fez.env :db_host, "localhost"
Fez.env :unicorn_workers, 1
...

Fezzik.destination :vagrant do
  set :domain, "..."
  Fezzik.env :unicorn_workers, 2
end

This syntax is shorter (you don't need an "options.each" block in each destination) but more importantly, it's DRY built directly into Fezzik. As we've seen, people are lazy when defining configuration and by default copy and paste things, even when they could use Ruby to make it more DRY. When the tool itself promotes good behavior, it's more likely to be used.

If you try to use Fezzik.env outside of a destination block, Fez throws an exception:

Please specify the server domain via the :domain variable
    /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rake-remote_task-2.0.6/lib/rake/remote_task.rb:311:in `block in mandatory'
/Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rake-remote_task-2.0.6/lib/rake/remote_task.rb:268:in `call'
/Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rake-remote_task-2.0.6/lib/rake/remote_task.rb:268:in `block in fetch'
/Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rake-remote_task-2.0.6/lib/rake/remote_task.rb:321:in `block in protect_env'
<internal:prelude>:10:in `synchronize'
/Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rake-remote_task-2.0.6/lib/rake/remote_task.rb:320:in `protect_env'
/Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rake-remote_task-2.0.6/lib/rake/remote_task.rb:266:in `fetch'
/Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rake-remote_task-2.0.6/lib/rake/remote_task.rb:429:in `block in set'
/Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/fezzik-0.6.2/lib/fezzik/environment.rb:4:in `env'
dmac commented 12 years ago

Yep, this feature makes sense. I'll look at building it when I clean up the roles configuration, if I don't see a pull request before then.

philc commented 12 years ago

This and the role refactor will improve the user friendliness of Barkeep's deploy config significantly.

philc commented 11 years ago

Any new thoughts on this? I'm about to implement the good ol' env_hash and thought with all the new hotness this is the next biggest offender.

philc commented 11 years ago

Another idea is supporting multiple targets in Fezzik.destination:

Fezzik.destination :prod1, :prod2, :prod3 do
  Fezzik.env "var", "value"
end
philc commented 11 years ago

Woo ha ha!

dmac commented 11 years ago

Great suggestion, and supporting multiple arguments is much simpler from an implementation perspective. Feature is live in version 0.7.4!

philc commented 11 years ago

This is a little klunky in that you can't define this block full of environment vars before you've used Fezzik.destination with a domain variable:

Please specify the server domain via the :domain variable

Will this be fixable once we get off of rake remote task?

dmac commented 11 years ago

The main complexity comes from the ability to define environment variables per-host. What happens now is that if you declare an env without specifying hosts it gets added to every host in domain. If domain isn't defined we'd need to save these settings in a temporary envs_for_all_domains hash, then add them to each domain once it is defined. So more annoying than difficult, but something I'd rather avoid doing unless this continues to be a problem.

Is it very annoying to define your destinations like this:

destination :staging do
  # envs for staging
end

destination :prod do
  # envs for prod
end

destination :staging, :prod do
  # common envs
end

rather than the other way round?

philc commented 11 years ago

My natural expectation when reading code is to see the general params which apply to all hosts first, and then specialized configs further down the file. I wouldn't call it an annoyance; it just defied my up-front expectation and so I thought I'd mention it.

philc commented 11 years ago

On second thought, I'd say this is unintuitive enough that it's worth supporting the definition in the way I described. My mental model of these env vars is maps, and with the current implementation you can't define some general key/value pairs and change just one of them on a per-destination basis. We do that commonly in Ooyala's deploys. For example this scenario does not work:

destination :vagrant, :prod1, :prod2 do
  Fezzik.env "db_user", "ubuntu"
  Fezzik.env "rack_env", "production"
end

destination :vagrant do
  set domain, "my-vagrant"
  Fezzik.env "db_user", "vagrant"
end

...
dmac commented 11 years ago

Fair enough, although that's a somewhat orthogonal example, given that your example would work if you had this first:

destination :vagrant do
  domain "vagrant"
end
dmac commented 11 years ago

But I definitely see the advantage here. I'll reopen this ticket and think about it.

cespare commented 11 years ago

Is this hard to implement now?

dmac commented 11 years ago

No harder than it was before, I think.