Successfully reported this slideshow.
Your SlideShare is downloading. ×

fog or: How I Learned to Stop Worrying and Love the Cloud

fog or: How I Learned to Stop Worrying and Love the Cloud

Download to read offline

Learn how to easily get started on cloud computing with fog. If you can control your infrastructure choices, you’ll make better choices in development and get what you need in production. You'll get an overview of fog and concrete examples to give you a head start on your provisioning workflow.

Learn how to easily get started on cloud computing with fog. If you can control your infrastructure choices, you’ll make better choices in development and get what you need in production. You'll get an overview of fog and concrete examples to give you a head start on your provisioning workflow.

More Related Content

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

fog or: How I Learned to Stop Worrying and Love the Cloud

  1. 1. OR: fog HOW I LEARNED TO STOP WORRYING AND LOVE THE CLOUD
  2. 2. geemus (Wesley Beary) web: github.com/geemus twitter: @geemus
  3. 3. employer and sponsor web: engineyard.com twitter: @engineyard
  4. 4. API driven on demand services CLOUD core: compute, dns, storage also: kvs, load balance, ...
  5. 5. What?
  6. 6. What? on demand - only pay for what you actually use
  7. 7. What? on demand - only pay for what you actually use flexible - add and remove resources in minutes (instead of weeks)
  8. 8. What? on demand - only pay for what you actually use flexible - add and remove resources in minutes (instead of weeks) repeatable - code, test, repeat
  9. 9. What? on demand - only pay for what you actually use flexible - add and remove resources in minutes (instead of weeks) repeatable - code, test, repeat resilient - build better systems with transient resources
  10. 10. Why Worry?
  11. 11. Why Worry? option overload - which provider/service should I use
  12. 12. Why Worry? option overload - which provider/service should I use expertise - each service has yet another knowledge silo
  13. 13. Why Worry? option overload - which provider/service should I use expertise - each service has yet another knowledge silo tools - vastly different API, quality, maintenance, etc
  14. 14. Why Worry? option overload - which provider/service should I use expertise - each service has yet another knowledge silo tools - vastly different API, quality, maintenance, etc standards - slow progress and differing interpretations
  15. 15. Ruby cloud services web: github.com/geemus/fog twitter: @fog
  16. 16. Why?
  17. 17. Why? portable - AWS, Bluebox, Brightbox, Google, Rackspace, Slicehost, Terremark, ...
  18. 18. Why? portable - AWS, Bluebox, Brightbox, Google, Rackspace, Slicehost, Terremark, ... powerful - compute, dns, storage, collections, models, mocks, requests, ...
  19. 19. Why? portable - AWS, Bluebox, Brightbox, Google, Rackspace, Slicehost, Terremark, ... powerful - compute, dns, storage, collections, models, mocks, requests, ... established - 92k downloads, 1112 followers, 141 forks, 67 contributors, me, ...
  20. 20. Why? portable - AWS, Bluebox, Brightbox, Google, Rackspace, Slicehost, Terremark, ... powerful - compute, dns, storage, collections, models, mocks, requests, ... established - 92k downloads, 1112 followers, 141 forks, 67 contributors, me, ... Fog.mock! - faster, cheaper, simulated cloud behavior
  21. 21. Who?
  22. 22. Who? libraries - carrierwave, chef, deckard, gaff, gemcutter, ... products - DevStructure, Engine Yard, iSwifter, OpenFeint, RowFeeder, ...
  23. 23. Interactive Bit!
  24. 24. Interactive Bit! cloud
  25. 25. Interactive Bit! cloud fog
  26. 26. What?
  27. 27. What? That’s great and all but I don’t have a use case...
  28. 28. What? That’s great and all but I don’t have a use case... uptime - because who wants a busted web site?
  29. 29. Setup geymus ~ ⌘ gem install fog or geymus ~ ⌘ sudo gem install fog
  30. 30. Get Connected 7 # setup a connection to the service 8 compute = Fog::Compute.new(credentials)
  31. 31. Get Connected 1 credentials = { 2   :provider           => 'Rackspace', 3   :rackspace_api_key  => RACKSPACE_API_KEY, 4   :rackspace_username => RACKSPACE_USERNAME 5 } 6 7 # setup a connection to the service 8 compute = Fog::Compute.new(credentials)
  32. 32. Boot that Server  1 server_data = compute.create_server(  2   1,  3   49  4 ).body['server']  5
  33. 33. Boot that Server  1 server_data = compute.create_server(  2   1,  3   49  4 ).body['server']  5  6 until compute.get_server_details(  7   server_data['id']  8 ).body['server']['status'] == 'ACTIVE'  9 end 10
  34. 34. Boot that Server  1 server_data = compute.create_server(  2   1,  3   49  4 ).body['server']  5  6 until compute.get_server_details(  7   server_data['id']  8 ).body['server']['status'] == 'ACTIVE'  9 end 10 11 commands = [ 12   %{'mkdir .ssh'}, 13   %{'echo #{File.read('~/.ssh/id_rsa.pub')} >> ~/.ssh/authorized_keys'}, 14   %{passwd -l root}, 15 ] 16 17 Net::SSH.start( 18   server_data['addresses'].first, 19   'root', 20   :password => server_data['password'] 21 ) do |ssh| 22   commands.each do |command| 23     ssh.open_channel do |ssh_channel| 24       ssh_channel.request_pty 25       ssh_channel.exec(%{bash -lc '#{command}'}) 26       ssh.loop 27     end 28   end 29 end
  35. 35. Worry!
  36. 36. Worry! arguments - what goes where, what does it mean?
  37. 37. Worry! arguments - what goes where, what does it mean? portability - most of this will only work on Rackspace
  38. 38. Worry! arguments - what goes where, what does it mean? portability - most of this will only work on Rackspace disservice - back to square one, but with tools in hand
  39. 39. Bootstrap 7 # boot server and setup ssh keys 8 server = compute.servers.bootstrap(server_attributes)
  40. 40. Bootstrap 1 server_attributes = { 2   :image_id         => '49', 3   :private_key_path => PRIVATE_KEY_PATH, 4   :public_key_path  => PUBLIC_KEY_PATH 5 } 6 7 # boot server and setup ssh keys 8 server = compute.servers.bootstrap(server_attributes)
  41. 41. Servers?
  42. 42. Servers? 1 compute.servers # list servers, same as #all
  43. 43. Servers? 1 compute.servers # list servers, same as #all 2 3 compute.servers.get(1234567890) # server by id
  44. 44. Servers? 1 compute.servers # list servers, same as #all 2 3 compute.servers.get(1234567890) # server by id 4 5 compute.servers.reload # update to latest
  45. 45. Servers? 1 compute.servers # list servers, same as #all 2 3 compute.servers.get(1234567890) # server by id 4 5 compute.servers.reload # update to latest 6 7 compute.servers.new(attributes) # local model
  46. 46. Servers? 1 compute.servers # list servers, same as #all 2 3 compute.servers.get(1234567890) # server by id 4 5 compute.servers.reload # update to latest 6 7 compute.servers.new(attributes) # local model 8 9 compute.servers.create(attributes) # remote model
  47. 47. ping
  48. 48. ping 1 # ping target 10 times 2 ssh_results = server.ssh("ping -c 10 #{target}")
  49. 49. ping 1 # ping target 10 times 2 ssh_results = server.ssh("ping -c 10 #{target}") 3 stdout = ssh_results.first.stdout
  50. 50. ping 1 # ping target 10 times 2 ssh_results = server.ssh("ping -c 10 #{target}") 3 stdout = ssh_results.first.stdout 4 5 # parse result, last line is summary 6 # round-trip min/avg/max/stddev = A.A/B.B/C.C/D.D ms
  51. 51. ping 1 # ping target 10 times 2 ssh_results = server.ssh("ping -c 10 #{target}") 3 stdout = ssh_results.first.stdout 4 5 # parse result, last line is summary 6 # round-trip min/avg/max/stddev = A.A/B.B/C.C/D.D ms 7 stats = stdout.split("/n").last.split(' ')[-2] 8 min, avg, max, stddev = stats.split('/')
  52. 52. ping 1 # ping target 10 times 2 ssh_results = server.ssh("ping -c 10 #{target}") 3 stdout = ssh_results.first.stdout 4 5 # parse result, last line is summary 6 # round-trip min/avg/max/stddev = A.A/B.B/C.C/D.D ms 7 stats = stdout.split("/n").last.split(' ')[-2] 8 min, avg, max, stddev = stats.split('/') NOTE: most complex code was string parsing!?!
  53. 53. cleanup
  54. 54. cleanup  1 # shutdown the server  2 server.destroy
  55. 55. cleanup  1 # shutdown the server  2 server.destroy  3  4 # return the data as a hash  5 {  6   :min    => min,  7   :avg    => avg,  8   :max    => max,  9   :stddev => stddev 10 }
  56. 56. Next!
  57. 57. Next!  1 -server_data = compute.create_server(  2 +compute.import_key_pair(  3 +  'id_rsa.pub',  4 +  File.read('~/.ssh/id_rsa.pub')  5 +)  6 +  7 +compute.authorize_security_group_ingress(  8 +  'CidrIp'      => '0.0.0.0/0',  9 +  'FromPort'    => 22, 10 +  'IpProtocol'  => 'tcp', 11 +  'GroupName'   => 'default', 12 +  'ToPort'      => 22 13 +) 14 + 15 +server_data = compute.run_instances( 16 +  'ami-1a837773', 17    1, 18 -  49 19 -).body['server'] 20 +  1, 21 +  'InstanceType'  => 'm1.small', 22 +  'KeyName'       => 'id_rsa.pub', 23 +  'SecurityGroup' => 'default' 24 +).body['instancesSet'].first 25   26 -until compute.get_server_details( 27 -  server_data['id'] 28 -).body['server']['status'] == 'ACTIVE' 29 +until compute.describe_instances( 30 +  'instance-id' => server_data['instanceId'] 31 +).body['reservationSet'].first['instancesSet'].first['instanceState']['name'] == 'running' 32  end 33   34 +sleep(300) 35 + 36  Net::SSH.start( 37 -  server_data['addresses'].first, 38 -  'root', 39 -  :password => server_data['password'] 40 +  server_data['ipAddress'], 41 +  'ubuntu', 42 +  :key_data => [File.read('~/.ssh/id_rsa')] 43  ) do |ssh| 44    commands = [ 45      %{'mkdir .ssh'},
  58. 58. Next!  1 -server_data = compute.create_server(  2 +compute.import_key_pair(  3 +  'id_rsa.pub',  4 +  File.read('~/.ssh/id_rsa.pub')  5 +)  6 +  7 +compute.authorize_security_group_ingress(  8 +  'CidrIp'      => '0.0.0.0/0',  9 +  'FromPort'    => 22, 10 +  'IpProtocol'  => 'tcp', 11 +  'GroupName'   => 'default', 12 +  'ToPort'      => 22 13 +) 14 + 15 +server_data = compute.run_instances( 16 +  'ami-1a837773', 17    1, 18 -  49 19 -).body['server'] 20 +  1, 21 +  'InstanceType'  => 'm1.small', 22 +  'KeyName'       => 'id_rsa.pub', 23 +  'SecurityGroup' => 'default' 24 +).body['instancesSet'].first 25   26 -until compute.get_server_details( 27 -  server_data['id'] 28 -).body['server']['status'] == 'ACTIVE' 29 +until compute.describe_instances( 30 +  'instance-id' => server_data['instanceId'] 31 +).body['reservationSet'].first['instancesSet'].first['instanceState']['name'] == 'running' 32  end 33   34 +sleep(300) 35 + 36  Net::SSH.start( 37 -  server_data['addresses'].first, 38 -  'root', 39 -  :password => server_data['password'] 40 +  server_data['ipAddress'], 41 +  'ubuntu', 42 +  :key_data => [File.read('~/.ssh/id_rsa')] 43  ) do |ssh| 44    commands = [ 45      %{'mkdir .ssh'},
  59. 59. geopinging v1  1 # specify a different provider  2 credentials = {  3   :provider  => 'AWS',  4   :aws_access_key_id  => AWS_ACCESS_KEY_ID,  5   :aws_secret_access_key => AWS_SECRET_ACCESS_KEY  6 }  7  8 server_attributes = {  9   :image_id         => 'ami-1a837773', 10   :private_key_path => PRIVATE_KEY_PATH, 11   :public_key_path  => PUBLIC_KEY_PATH, 12   :username         => 'ubuntu' 13 }
  60. 60. geopinging v2 1 # specify a different aws region 2 # ['ap-southeast-1', 'eu-west-1', 'us-west-1] 3 credentials.merge!({ 4   :region => 'eu-west-1' 5 })
  61. 61. geopinging v... portable - AWS, Bluebox, Brightbox, Rackspace, Slicehost, Terremark, ...
  62. 62. geopinging v... portable - AWS, Bluebox, Brightbox, Rackspace, Slicehost, Terremark, ... lather, rinse, repeat
  63. 63. How? That is awesome, but how did you...
  64. 64. exploring geymus ~ ⌘ fog   To run as 'default', add the following to ~/.fog :default:   :aws_access_key_id:     INTENTIONALLY_LEFT_BLANK   :aws_secret_access_key: INTENTIONALLY_LEFT_BLANK   :public_key_path:       INTENTIONALLY_LEFT_BLANK   :private_key_path:      INTENTIONALLY_LEFT_BLANK   :rackspace_api_key:     INTENTIONALLY_LEFT_BLANK   :rackspace_username:    INTENTIONALLY_LEFT_BLANK ...
  65. 65. sign posts
  66. 66. sign posts geymus ~ ⌘ fog
  67. 67. sign posts geymus ~ ⌘ fog Welcome to fog interactive! :default credentials provide AWS and Rackspace
  68. 68. sign posts geymus ~ ⌘ fog Welcome to fog interactive! :default credentials provide AWS and Rackspace >> providers
  69. 69. sign posts geymus ~ ⌘ fog Welcome to fog interactive! :default credentials provide AWS and Rackspace >> providers [AWS, Rackspace]
  70. 70. sign posts geymus ~ ⌘ fog Welcome to fog interactive! :default credentials provide AWS and Rackspace >> providers [AWS, Rackspace] >> Rackspace.collections
  71. 71. sign posts geymus ~ ⌘ fog Welcome to fog interactive! :default credentials provide AWS and Rackspace >> providers [AWS, Rackspace] >> Rackspace.collections [:directories, :files, :flavors, :images, :servers]
  72. 72. sign posts geymus ~ ⌘ fog Welcome to fog interactive! :default credentials provide AWS and Rackspace >> providers [AWS, Rackspace] >> Rackspace.collections [:directories, :files, :flavors, :images, :servers] >> Rackspace[:compute]
  73. 73. sign posts geymus ~ ⌘ fog Welcome to fog interactive! :default credentials provide AWS and Rackspace >> providers [AWS, Rackspace] >> Rackspace.collections [:directories, :files, :flavors, :images, :servers] >> Rackspace[:compute] #<Fog::Rackspace::Compute ...>
  74. 74. sign posts geymus ~ ⌘ fog Welcome to fog interactive! :default credentials provide AWS and Rackspace >> providers [AWS, Rackspace] >> Rackspace.collections [:directories, :files, :flavors, :images, :servers] >> Rackspace[:compute] #<Fog::Rackspace::Compute ...> >> Rackspace[:compute].requests
  75. 75. sign posts geymus ~ ⌘ fog Welcome to fog interactive! :default credentials provide AWS and Rackspace >> providers [AWS, Rackspace] >> Rackspace.collections [:directories, :files, :flavors, :images, :servers] >> Rackspace[:compute] #<Fog::Rackspace::Compute ...> >> Rackspace[:compute].requests [:confirm_resized_server, ..., :update_server]
  76. 76. what are those?
  77. 77. what are those? provider => [AWS, Rackspace, Zerigo, ...]
  78. 78. what are those? provider => [AWS, Rackspace, Zerigo, ...] service => [Compute, DNS, Storage, ...]
  79. 79. what are those? provider => [AWS, Rackspace, Zerigo, ...] service => [Compute, DNS, Storage, ...] collection => [flavors, images, servers, ...]
  80. 80. what are those? provider => [AWS, Rackspace, Zerigo, ...] service => [Compute, DNS, Storage, ...] collection => [flavors, images, servers, ...] model => [flavor, image, server, ...]
  81. 81. what are those? provider => [AWS, Rackspace, Zerigo, ...] service => [Compute, DNS, Storage, ...] collection => [flavors, images, servers, ...] model => [flavor, image, server, ...] request => [describe_instances, run_instances, ...]
  82. 82. requests?
  83. 83. requests? >> Rackspace[:compute].list_servers
  84. 84. requests? >> Rackspace[:compute].list_servers #<Excon::Response:0x________
  85. 85. requests? >> Rackspace[:compute].list_servers #<Excon::Response:0x________ @body = { "servers" => [] },
  86. 86. requests? >> Rackspace[:compute].list_servers #<Excon::Response:0x________ @body = { "servers" => [] }, @headers = { "X-PURGE-KEY"=>"/______/servers", ..., "Connection"=>"keep-alive" },
  87. 87. requests? >> Rackspace[:compute].list_servers #<Excon::Response:0x________ @body = { "servers" => [] }, @headers = { "X-PURGE-KEY"=>"/______/servers", ..., "Connection"=>"keep-alive" }, @status=200>
  88. 88. sanity check
  89. 89. sanity check >> Rackspace.servers.select {|server| server.ready?}
  90. 90. sanity check >> Rackspace.servers.select {|server| server.ready?} <Fog::Rackspace::Compute::Servers filters={} [] >
  91. 91. sanity check >> Rackspace.servers.select {|server| server.ready?} <Fog::Rackspace::Compute::Servers filters={} [] > >> AWS.servers.select {|server| server.ready?}
  92. 92. sanity check >> Rackspace.servers.select {|server| server.ready?} <Fog::Rackspace::Compute::Servers filters={} [] > >> AWS.servers.select {|server| server.ready?} <Fog::AWS::Compute::Servers [] >
  93. 93. sanity check >> Rackspace.servers.select {|server| server.ready?} <Fog::Rackspace::Compute::Servers filters={} [] > >> AWS.servers.select {|server| server.ready?} <Fog::AWS::Compute::Servers [] > >> exit
  94. 94. finding images
  95. 95. finding images >> Rackspace.images.table([:id, :name])
  96. 96. finding images >> Rackspace.images.table([:id, :name]) +--------- +------------------------------------------+ | id | name | +--------- +------------------------------------------+ | 49 | Ubuntu 10.04 LTS (lucid) | +--------- +------------------------------------------+ ...
  97. 97. finding images >> Rackspace.images.table([:id, :name]) +--------- +------------------------------------------+ | id | name | +--------- +------------------------------------------+ | 49 | Ubuntu 10.04 LTS (lucid) | +--------- +------------------------------------------+ ... >> AWS.images # I use alestic.com listing
  98. 98. finding images >> Rackspace.images.table([:id, :name]) +--------- +------------------------------------------+ | id | name | +--------- +------------------------------------------+ | 49 | Ubuntu 10.04 LTS (lucid) | +--------- +------------------------------------------+ ... >> AWS.images # I use alestic.com listing ...
  99. 99. exploring...
  100. 100. exploring... It takes forever!
  101. 101. exploring... It takes forever! It’s so expensive!
  102. 102. exploring... It takes forever! It’s so expensive! A warm welcome for Fog.mock!
  103. 103. Mocks! geymus ~ ⌘ FOG_MOCK=true fog or require ‘fog’ Fog.mock!
  104. 104. simulation
  105. 105. simulation Most functions just work!
  106. 106. simulation Most functions just work! Unimplemented mocks? Errors keep you on track.
  107. 107. simulation Most functions just work! Unimplemented mocks? Errors keep you on track. Tests run against both, so it is either consistent or a bug.
  108. 108. Back to Business I have a bunch of data now what?
  109. 109. Back to Business I have a bunch of data now what? storage - aggregating cloud data
  110. 110. Get Connected 7 # setup a connection to the service 8 storage = Fog::Storage.new(credentials)
  111. 111. Get Connected 1 credentials = { 2   :provider  => 'AWS', 3   :aws_access_key_id  => AWS_ACCESS_KEY_ID, 4   :aws_secret_access_key => AWS_SECRET_ACCESS_KEY 5 } 6 7 # setup a connection to the service 8 storage = Fog::Storage.new(credentials)
  112. 112. directories 1 # create a directory 2 directory = storage.directories.create( 3   :key    => directory_name, 4   :public => true 5 )
  113. 113. files
  114. 114. files 1 # store the file 2 file = directory.files.create( 3   :body   => File.open(path), 4   :key    => name, 5   :public => true 6 )
  115. 115. files 1 # store the file 2 file = directory.files.create( 3   :body   => File.open(path), 4   :key    => name, 5   :public => true 6 ) 7 8 # return the public url for the file 9 file.public_url
  116. 116. geostorage  1 # specify a different provider  2 credentials = {  3   :provider           => 'Rackspace',  4   :rackspace_api_key  => RACKSPACE_API_KEY,  5   :rackspace_username => RACKSPACE_USERNAME  6 }
  117. 117. cleanup
  118. 118. cleanup geymus ~ ⌘ fog ...
  119. 119. cleanup geymus ~ ⌘ fog ... >> directory = AWS.directories.get(DIRECTORY_NAME) ...
  120. 120. cleanup geymus ~ ⌘ fog ... >> directory = AWS.directories.get(DIRECTORY_NAME) ... >> directory.files.each {|file| file.destroy} ...
  121. 121. cleanup geymus ~ ⌘ fog ... >> directory = AWS.directories.get(DIRECTORY_NAME) ... >> directory.files.each {|file| file.destroy} ... >> directory.destroy ...
  122. 122. cleanup geymus ~ ⌘ fog ... >> directory = AWS.directories.get(DIRECTORY_NAME) ... >> directory.files.each {|file| file.destroy} ... >> directory.destroy ... >> exit
  123. 123. geoaggregating portable - AWS, Google, Local, Rackspace
  124. 124. geoaggregating portable - AWS, Google, Local, Rackspace lather, rinse, repeat
  125. 125. Phase 3: Profit I’ve got the data, but how do I freemium?
  126. 126. Phase 3: Profit I’ve got the data, but how do I freemium? dns - make your cloud (premium) accessible
  127. 127. Get Connected 7 # setup a connection to the service 8 dns = Fog::DNS.new(credentials)
  128. 128. Get Connected 1 credentials = { 2   :provider  => 'Zerigo', 3   :zerigo_email => ZERIGO_EMAIL, 4   :zerigo_token => ZERIGO_TOKEN 5 } 6 7 # setup a connection to the service 8 dns = Fog::DNS.new(credentials)
  129. 129. zones 1 # create a zone 2 zone = dns.zone.create( 3   :domain => domain_name, 4   :email  => "admin@#{domain_name}" 5 )
  130. 130. records 1 # create a record 2 record = zones.records.create( 3   :ip   => '1.2.3.4', 4   :name => "#{customer_name}.#{domain_name}", 5   :type => 'A' 6 )
  131. 131. cleanup geymus ~ ⌘ fog ... >> zone = Zerigo.zones.get(ZONE_ID) ... >> zone.records.each {|record| record.destroy} ... >> zone.destroy ... >> exit
  132. 132. geofreemiuming portable - AWS, Linode, Slicehost, Zerigo
  133. 133. geofreemiuming portable - AWS, Linode, Slicehost, Zerigo lather, rinse, repeat
  134. 134. Congratulations!
  135. 135. Congratulations! todo - copy/paste, push, deploy!
  136. 136. Congratulations! todo - copy/paste, push, deploy! budgeting - find ways to spend your pile of money
  137. 137. Congratulations! todo - copy/paste, push, deploy! budgeting - find ways to spend your pile of money geemus - likes coffee, bourbon, games, etc
  138. 138. Congratulations! todo - copy/paste, push, deploy! budgeting - find ways to spend your pile of money geemus - likes coffee, bourbon, games, etc retire - at your earliest convenience
  139. 139. Love!
  140. 140. Love! knowledge - suffering encoded in ruby
  141. 141. Love! knowledge - expertise encoded in ruby
  142. 142. Love! knowledge - expertise encoded in ruby empowering - show the cloud who is boss
  143. 143. Love! knowledge - expertise encoded in ruby empowering - show the cloud who is boss exciting - this is some cutting edge stuff!
  144. 144. Homework: Easy
  145. 145. Homework: Easy follow @fog to hear about releases
  146. 146. Homework: Easy follow @fog to hear about releases follow github.com/geemus/fog to hear nitty gritty
  147. 147. Homework: Easy follow @fog to hear about releases follow github.com/geemus/fog to hear nitty gritty proudly display stickers wherever hackers are found
  148. 148. Homework: Easy follow @fog to hear about releases follow github.com/geemus/fog to hear nitty gritty proudly display stickers wherever hackers are found ask geemus your remaining questions
  149. 149. Homework: Easy follow @fog to hear about releases follow github.com/geemus/fog to hear nitty gritty proudly display stickers wherever hackers are found ask geemus your remaining questions play games with geemus
  150. 150. Homework: Normal
  151. 151. Homework: Normal report issues at github.com/geemus/fog/issues
  152. 152. Homework: Normal report issues at github.com/geemus/fog/issues irc #ruby-fog on freenode
  153. 153. Homework: Normal report issues at github.com/geemus/fog/issues irc #ruby-fog on freenode discuss groups.google.com/group/ruby-fog
  154. 154. Homework: Normal report issues at github.com/geemus/fog/issues irc #ruby-fog on freenode discuss groups.google.com/group/ruby-fog write blog posts
  155. 155. Homework: Normal report issues at github.com/geemus/fog/issues irc #ruby-fog on freenode discuss groups.google.com/group/ruby-fog write blog posts give lightning talks
  156. 156. Homework: Hard
  157. 157. Homework: Hard help make fog.io the cloud services resource for ruby
  158. 158. Homework: Hard help make fog.io the cloud services resource for ruby send pull requests fixing issues or adding features
  159. 159. Homework: Hard help make fog.io the cloud services resource for ruby send pull requests fixing issues or adding features proudly wear contributor-only grey shirt wherever hackers are found
  160. 160. Homework: Expert
  161. 161. Homework: Expert help maintain the cloud services you depend on
  162. 162. Homework: Expert help maintain the cloud services you depend on become a collaborator by keeping informed and involved
  163. 163. Homework: Expert help maintain the cloud services you depend on become a collaborator by keeping informed and involved proudly wear commit-only black shirt wherever hackers are found
  164. 164. Thanks! @geemus - questions, comments, suggestions
  165. 165. Thanks! Questions? (see also: README) examples - http://gist.github.com/729992 slides - http://slidesha.re/hR8sP9 repo - http://github.com/geemus/fog bugs - http://github.com/geemus/fog/issues @geemus - questions, comments, suggestions

Editor's Notes

  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n

×