switches 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -20,3 +20,4 @@ pkg
20
20
 
21
21
  ## PROJECT::SPECIFIC
22
22
  *.gem
23
+ README.html
data/README ADDED
@@ -0,0 +1 @@
1
+ Please see README.markdown or visit http://github.com/seamusabshere/switches
data/README.markdown ADDED
@@ -0,0 +1,145 @@
1
+ # switches #
2
+
3
+ Switches lets you turn on and off sections of your code with <tt>Switches.foobar?</tt> booleans.
4
+
5
+ It's an extraction from [http://brighterplanet.com](http://brighterplanet.com), where we use it as an emergency button to turn on/off API integration with Facebook, Campaign Monitor, etc.
6
+
7
+ ## Quick start ##
8
+
9
+ Add 2 lines to <tt>config/environment.rb</tt>:
10
+
11
+ require File.join(File.dirname(__FILE__), 'boot')
12
+ [...]
13
+ require 'switches' # AFTER boot, BEFORE initializer
14
+ [...]
15
+ Rails::Initializer.run do |config|
16
+ [...]
17
+ config.gem 'switches', :lib => false # INSIDE initializer
18
+
19
+ Now run this:
20
+
21
+ my-macbook:~/my_app $ ./script/runner 'Switches.setup'
22
+
23
+ Add your defaults to <tt>config/switches/defaults.yml</tt>:
24
+
25
+ ---
26
+ ssl: true # ssl support is on by default
27
+ campaign_monitor: true # campaign monitor integration is on by default
28
+
29
+ ## Tasks ##
30
+
31
+ <table>
32
+ <tr>
33
+ <th>Rake task</th>
34
+ <th>Cap task</th>
35
+ <th>Notes</th>
36
+ </tr>
37
+ <tr>
38
+ <td>rake s:c</td>
39
+ <td>cap TARGET s:c</td>
40
+ <td>show current switches</td>
41
+ </tr>
42
+ <tr>
43
+ <td>rake s:d</td>
44
+ <td>cap TARGET s:d</td>
45
+ <td>show difference between current and default switches</td>
46
+ </tr>
47
+ <tr>
48
+ <td>rake s:on[SWITCH]</td>
49
+ <td>cap TARGET s:on ARG=SWITCH</td>
50
+ <td>turn on SWITCH</td>
51
+ </tr>
52
+ <tr>
53
+ <td>rake s:off[SWITCH]</td>
54
+ <td>cap TARGET s:off ARG=SWITCH</td>
55
+ <td>turn off SWITCH</td>
56
+ </tr>
57
+ <tr>
58
+ <td>rake s:clear[SWITCH]</td>
59
+ <td>cap TARGET s:clear ARG=SWITCH</td>
60
+ <td>clear any switch for SWITCH</td>
61
+ </tr>
62
+ <tr>
63
+ <td>rake s:reset</td>
64
+ <td>cap TARGET s:reset</td>
65
+ <td>go back to defaults (deletes <tt>config/switches/current.yml</tt>)</td>
66
+ </tr>
67
+ <tr>
68
+ <td>rake s:backup</td>
69
+ <td>cap TARGET s:backup</td>
70
+ <td>backup current switches (copies to <tt>config/switches/backup.yml</tt>)</td>
71
+ </tr>
72
+ <tr>
73
+ <td>rake s:restore</td>
74
+ <td>cap TARGET s:restore</td>
75
+ <td>restore backed-up switches (copies <tt>backup.yml</tt> to <tt>current.yml</tt>)</td>
76
+ </tr>
77
+ <tr>
78
+ <td>rake s:default</td>
79
+ <td>cap TARGET s:default</td>
80
+ <td>list default settings</td>
81
+ </tr>
82
+ </table>
83
+
84
+ ## Throwing switches remotely with Capistrano ##
85
+
86
+ This is the minimum needed in the TARGET task:
87
+
88
+ task :production do
89
+ role :app, 'ec2-88-77-66-55.compute-1.amazonaws.com'
90
+ role :app, '177.133.33.144'
91
+
92
+ set :rails_env, 'production'
93
+ set :deploy_to, '/data/my_app'
94
+ set :gfs, false
95
+ end
96
+
97
+ The switches will get applied to any role that matches <tt>/app/</tt> (so :app_master, :app, etc.)
98
+
99
+ ## Usage ##
100
+
101
+ You can do stuff like (in <tt>app/models/user.rb</tt>):
102
+
103
+ after_create :subscribe_email if Switches.campaign_monitor?
104
+ def subscribe_email
105
+ CampaignMonitor.subscribe email
106
+ end
107
+
108
+ Uhh ohh! Campaign Monitor's API is down and you need to shut off those failing after_creates, like, NOW.
109
+
110
+ production-server-1:/var/www/apps/my_app $ rake s:off[campaign_monitor]
111
+ production-server-1:/var/www/apps/my_app $ sudo monit restart all -g my_app
112
+ [...]
113
+ production-server-2:/var/www/apps/my_app $ rake s:off[campaign_monitor]
114
+ production-server-2:/var/www/apps/my_app $ sudo monit restart all -g my_app
115
+
116
+ Or, even better, do it with cap:
117
+
118
+ my-macbook:~/my_app $ cap production s:off ARG=campaign_monitor
119
+ my-macbook:~/my_app $ cap production mongrel:restart
120
+
121
+ For another example, let's say you're a developer who doesn't have a self-signed certificate:
122
+
123
+ my-macbook:~/my_app $ rake s:off[ssl]
124
+
125
+ Those changes get persisted in <tt>config/switches/current.yml</tt>.
126
+
127
+ If you want to see your switches vis-a-vis the defaults:
128
+
129
+ my-macbook:~/my_app $ rake s:d
130
+ ---
131
+ ssl: true => false
132
+
133
+ And if you want to go back to the defaults:
134
+
135
+ my-macbook:~/my_app $ rake s:reset
136
+
137
+ Remember, you should version control <tt>config/switches/defaults.yml</tt> and ignore <tt>config/switches/current.yml</tt>.
138
+
139
+ ## Rationale ##
140
+
141
+ Sometimes you just need an easy way to "turn off" code.
142
+
143
+ ## Copyright ##
144
+
145
+ Copyright (c) 2009 Seamus Abshere. See LICENSE for details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
data/lib/switches.rb CHANGED
@@ -6,14 +6,17 @@ require 'activesupport'
6
6
 
7
7
  module Switches
8
8
  CONFIG_DIR = File.join RAILS_ROOT, 'config', 'switches'
9
- TASKS_DIR = File.join RAILS_ROOT, 'lib', 'tasks'
9
+ RAKE_PATH = File.join RAILS_ROOT, 'lib', 'tasks', 'switches.rake'
10
+ CAPISTRANO_PATH = File.join CONFIG_DIR, 'capistrano_tasks.rb'
11
+ CAPISTRANO_LOAD_PATH = CAPISTRANO_PATH.gsub "#{RAILS_ROOT}/", '' # => 'config/switches/capistrano_tasks.rb'
12
+ CAPFILE_PATH = File.join RAILS_ROOT, 'Capfile'
10
13
  CURRENT_PATH = File.join CONFIG_DIR, 'current.yml'
11
14
  DEFAULT_PATH = File.join CONFIG_DIR, 'default.yml'
12
15
  BACKUP_PATH = File.join CONFIG_DIR, 'backup.yml'
13
16
 
14
17
  class << self
15
18
  def say(str)
16
- $stderr.puts "[SWITCHES GEM] #{str}"
19
+ $stderr.puts "[SWITCHES GEM] #{str.gsub "#{RAILS_ROOT}/", ''}"
17
20
  end
18
21
 
19
22
  def setup
@@ -29,12 +32,33 @@ module Switches
29
32
  File.open(DEFAULT_PATH, 'w') { |f| f.write({ 'quick_brown' => true, 'fox_jumps' => false }.to_yaml) }
30
33
  end
31
34
 
32
- say "Copying Rake tasks into #{TASKS_DIR}."
33
- FileUtils.cp File.join(File.dirname(__FILE__), 'tasks', 'switches.rake'), TASKS_DIR
35
+ say "Refreshing gem-related Rake tasks at #{RAKE_PATH}."
36
+ FileUtils.cp File.join(File.dirname(__FILE__), 'tasks', 'switches.rake'), RAKE_PATH
37
+
38
+ say "Refreshing gem-related Capistrano tasks at #{CAPISTRANO_PATH}."
39
+ FileUtils.cp File.join(File.dirname(__FILE__), 'tasks', 'capistrano_tasks.rb'), CAPISTRANO_PATH
40
+
41
+ needs_append = false
42
+ if not File.exists?(CAPFILE_PATH)
43
+ say "Creating a Capfile and including our tasks in it."
44
+ needs_append = true
45
+ FileUtils.touch CAPFILE_PATH
46
+ elsif old_capfile = IO.read(CAPFILE_PATH) and old_capfile.include?(CAPISTRANO_LOAD_PATH)
47
+ say "Found a Capfile that already includes our tasks. Great!"
48
+ else
49
+ say "I'm going to add a line to your existing Capfile. Sorry if I break anything!"
50
+ needs_append = true
51
+ end
52
+
53
+ File.open(CAPFILE_PATH, 'a') do |f|
54
+ say "Appending a line that loads our Capistrano tasks to your Capfile."
55
+ f.write "\n# Added by switches gem #{Time.now}\nload '#{CAPISTRANO_LOAD_PATH}'\n"
56
+ end if needs_append
34
57
 
35
58
  say "Don't forget to:"
36
59
  say "* git add #{DEFAULT_PATH}"
37
- say "* git add #{TASKS_DIR}/switches.rake"
60
+ say "* git add #{RAKE_PATH}"
61
+ say "* git add #{CAPISTRANO_PATH}"
38
62
  say "* put 'config/switches/current.yml' to your .gitignore"
39
63
  say "You can refresh the gem tasks with Switches.setup. It won't touch anything else."
40
64
  end
@@ -0,0 +1,71 @@
1
+ # assumes you've got roles, deploy_to, rails_env
2
+
3
+ require 'pp'
4
+ require 'activesupport'
5
+ require 'zlib'
6
+
7
+ # basically { :a => 1, :b => 2 }.eql?({ :b => 2, :a => 1}) => true
8
+ class StrictComparableHash < Hash
9
+ def eql?(other)
10
+ self == other
11
+ end
12
+
13
+ # must return an integer or #eql? will be ignored by Comparable
14
+ # assumes hash elements are strings
15
+ # will break if a value is something else whose to_s varies randomly
16
+ def hash
17
+ Zlib.crc32(to_a.map(&:to_s).sort.to_s)
18
+ end
19
+ end
20
+
21
+ # Hash.from_xml(a.gsub(/\n+/, ''))['hash']
22
+
23
+ namespace :s do
24
+ %w{ c d on off clear reset backup restore default }.each do |cmd|
25
+ task cmd.to_sym, :roles => lambda { roles.keys.map(&:to_s).grep(/app/).map(&:to_sym) } do
26
+
27
+ # download switches xml from servers
28
+ raw_input = Hash.new
29
+ run "cd #{deploy_to}/current; rake --silent s:#{cmd}#{"[#{ENV['ARG']}]" if ENV['ARG'].present?} RAILS_ENV=#{rails_env}; true" do |channel, stream, data|
30
+ server = channel[:server]
31
+ server_identifier = gfs ? server.host : "#{server.host}:#{server.port}".chomp(':')
32
+ raw_input[server_identifier] ||= Array.new
33
+ raw_input[server_identifier] << data
34
+ end
35
+
36
+ grouped_by_server = raw_input.inject(Hash.new) do |memo, ary|
37
+ server_identifier, chunks = ary
38
+ server_hash = Hash.from_xml(chunks.join.gsub(/\n+/, ''))['hash']
39
+ memo[server_identifier] = server_hash.is_a?(Hash) ? StrictComparableHash[server_hash] : StrictComparableHash.new
40
+ memo
41
+ end
42
+
43
+ first_server_switches = grouped_by_server.values.first
44
+
45
+ # If GFS, all server switches will be the same---just output them
46
+ if gfs
47
+ pp first_server_switches
48
+ else
49
+ grouped_by_switches = grouped_by_server.inject(Hash.new) do |memo, ary|
50
+ server_identifier, comparable_switches = ary
51
+ memo[comparable_switches] ||= Array.new
52
+ memo[comparable_switches] << server_identifier
53
+ memo
54
+ end
55
+ if grouped_by_switches.keys.length > 1
56
+ puts "Servers are different"
57
+ pp grouped_by_switches.invert
58
+ if grouped_by_switches.length == 2
59
+ a, b = grouped_by_switches.invert.values
60
+ puts
61
+ puts "Difference is"
62
+ pp a.diff(b)
63
+ end
64
+ else
65
+ puts "Servers are the same"
66
+ pp first_server_switches
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,88 +1,54 @@
1
1
  require 'switches'
2
2
 
3
- task :switches do
4
- Rake::Task['switches:default'].execute
5
- end
6
-
7
3
  namespace :s do
8
- task :c => 'switches:list_current'
9
- task :d => 'switches:diff'
10
- task :on, :name do |t, args|
11
- Rake::Task['switches:turn_on'].execute args
12
- end
13
- task :off, :name do |t, args|
14
- Rake::Task['switches:turn_off'].execute args
4
+ desc "List current"
5
+ task :c do
6
+ puts Switches.current.to_xml
15
7
  end
16
- end
17
8
 
18
- namespace :switches do
19
- desc "List current and show instructions"
20
- task :default do
21
- puts <<-EOS
22
-
23
- Here's a bunch of ugly instructions:
24
-
25
- List current settings:
26
- rake s:c
27
- Show difference between current and default settings:
28
- rake s:d
29
- Clear with:
30
- rake switches:clear[FOOBAR]
31
-
32
- ... now listing current settings ...
33
- EOS
34
- Rake::Task['switches:list_current'].execute
35
- end
36
-
37
- # not called :default so it doesn't look like the default task
38
- desc "List default"
39
- task :list_default do
40
- puts Switches.default.to_yaml
41
- end
42
-
43
- desc "List current"
44
- task :list_current do
45
- puts Switches.current.to_yaml
9
+ desc "Diff current vs. default switches"
10
+ task :d do
11
+ puts Switches.diff.to_xml
46
12
  end
47
-
13
+
48
14
  desc "Turn on switch"
49
- task :turn_on, :name do |t, args|
15
+ task :on, :name do |t, args|
50
16
  Switches.turn_on args.name
51
- puts Switches.current.to_yaml
17
+ puts Switches.current.to_xml
52
18
  end
53
19
 
54
20
  desc "Turn off switch"
55
- task :turn_off, :name do |t, args|
21
+ task :off, :name do |t, args|
56
22
  Switches.turn_off args.name
57
- puts Switches.current.to_yaml
23
+ puts Switches.current.to_xml
58
24
  end
59
-
25
+
60
26
  desc "Clear switch"
61
27
  task :clear, :name do |t, args|
62
28
  Switches.clear args.name
63
- puts Switches.current.to_yaml
64
- end
65
-
66
- desc "Diff current vs. default switches"
67
- task :diff do
68
- puts Switches.diff.to_yaml
29
+ puts Switches.current.to_xml
69
30
  end
70
-
31
+
71
32
  desc "Reset all switches to defaults"
72
33
  task :reset do
73
34
  Switches.reset
74
- puts Switches.current.to_yaml
35
+ puts Switches.current.to_xml
75
36
  end
76
-
37
+
77
38
  desc "Backup all switches to defaults"
78
39
  task :backup do
79
40
  Switches.backup
80
- puts Switches.current.to_yaml
41
+ puts Switches.current.to_xml
81
42
  end
82
-
43
+
83
44
  desc "Restore all switches to defaults"
84
45
  task :restore do
85
46
  Switches.restore
86
- puts Switches.current.to_yaml
47
+ puts Switches.current.to_xml
48
+ end
49
+
50
+ desc "List default"
51
+ task :default do
52
+ puts Switches.default.to_xml
87
53
  end
88
54
  end
data/switches.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{switches}
8
- s.version = "0.1.0"
8
+ s.version = "0.1.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Seamus Abshere"]
12
- s.date = %q{2009-10-30}
12
+ s.date = %q{2009-11-02}
13
13
  s.description = %q{
14
14
  Switches lets you turn on and off parts of your code from the commandline. There's a defaults.yml and a current.yml in the background.
15
15
 
@@ -37,16 +37,20 @@ It's inspired by ActiveSupport's StringInquirer (e.g. Rails.development?) and tr
37
37
  s.email = %q{seamus@abshere.net}
38
38
  s.extra_rdoc_files = [
39
39
  "LICENSE",
40
- "README.rdoc"
40
+ "README",
41
+ "README.html",
42
+ "README.markdown"
41
43
  ]
42
44
  s.files = [
43
45
  ".document",
44
46
  ".gitignore",
45
47
  "LICENSE",
46
- "README.rdoc",
48
+ "README",
49
+ "README.markdown",
47
50
  "Rakefile",
48
51
  "VERSION",
49
52
  "lib/switches.rb",
53
+ "lib/tasks/capistrano_tasks.rb",
50
54
  "lib/tasks/switches.rake",
51
55
  "spec/spec.opts",
52
56
  "spec/spec_helper.rb",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: switches
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seamus Abshere
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-30 00:00:00 -04:00
12
+ date: 2009-11-02 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -55,20 +55,25 @@ extensions: []
55
55
 
56
56
  extra_rdoc_files:
57
57
  - LICENSE
58
- - README.rdoc
58
+ - README
59
+ - README.html
60
+ - README.markdown
59
61
  files:
60
62
  - .document
61
63
  - .gitignore
62
64
  - LICENSE
63
- - README.rdoc
65
+ - README
66
+ - README.markdown
64
67
  - Rakefile
65
68
  - VERSION
66
69
  - lib/switches.rb
70
+ - lib/tasks/capistrano_tasks.rb
67
71
  - lib/tasks/switches.rake
68
72
  - spec/spec.opts
69
73
  - spec/spec_helper.rb
70
74
  - spec/switches_spec.rb
71
75
  - switches.gemspec
76
+ - README.html
72
77
  has_rdoc: true
73
78
  homepage: http://github.com/seamusabshere/switches
74
79
  licenses: []
data/README.rdoc DELETED
@@ -1,81 +0,0 @@
1
- = switches
2
-
3
- Switches lets you turn on and off sections of your code with <tt>Switches.foobar?</tt> booleans.
4
-
5
- == Quick start
6
-
7
- Add 2 lines to <tt>config/environment.rb</tt>:
8
-
9
- require File.join(File.dirname(__FILE__), 'boot')
10
- [...]
11
- require 'switches' # AFTER boot, BEFORE initializer
12
- [...]
13
- Rails::Initializer.run do |config|
14
- [...]
15
- config.gem 'switches', :lib => false # INSIDE initializer
16
-
17
- Now run this:
18
-
19
- my-macbook:~/my_app $ ./script/runner 'Switches.setup'
20
-
21
- Add your defaults to <tt>config/switches/defaults.yml</tt>:
22
-
23
- ---
24
- ssl: true # ssl support is on by default
25
- campaign_monitor: true # campaign monitor integration is on by default
26
-
27
- == Rake tasks
28
-
29
- rake s:c # show current switches
30
- rake s:d # show difference between current and default switches
31
- rake s:on[foobar] # turn on "foobar"
32
- rake s:off[foobar] # turn off "foobar"
33
-
34
- rake switches:clear[foobar] # clear any switch for "foobar"
35
- rake switches:reset # go back to defaults
36
- rake switches:backup # backup current switches
37
- rake switches:restore # restore backed-up switches
38
-
39
- == Usage
40
-
41
- You can do stuff like (in <tt>app/models/user.rb</tt>):
42
-
43
- after_create :subscribe_email if Switches.campaign_monitor?
44
- def subscribe_email
45
- CampaignMonitor.subscribe email
46
- end
47
-
48
- Uhh ohh! Campaign Monitor's API is down and you need to shut off those failing after_creates, like, NOW.
49
-
50
- production-server:/var/www/apps/my_app $ rake s:off[campaign_monitor]
51
- production-server:/var/www/apps/my_app $ sudo monit restart all -g my_app
52
-
53
- Or let's say you're a developer who doesn't have a self-signed certificate:
54
-
55
- my-macbook:~/my_app $ rake s:off[ssl]
56
-
57
- Those changes get persisted in <tt>config/switches/current.yml</tt>.
58
-
59
- If you want to see your switches vis-a-vis the defaults:
60
-
61
- my-macbook:~/my_app $ rake s:d
62
- ---
63
- ssl: true => false
64
-
65
- And if you want to go back to the defaults:
66
-
67
- my-macbook:~/my_app $ rake switches:reset
68
-
69
- Remember, you should version control <tt>config/switches/defaults.yml</tt> and ignore <tt>config/switches/current.yml</tt>.
70
-
71
- == Rationale
72
-
73
- Sometimes you just need an easy way to "turn off" code.
74
-
75
- == Wishlist
76
-
77
- * capistrano task like "cap production s:off[campaign_monitor]"
78
-
79
- == Copyright
80
-
81
- Copyright (c) 2009 Seamus Abshere. See LICENSE for details.