mobilize-ssh 1.297 → 1.298

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -13,6 +13,7 @@ Table Of Contents
13
13
  * [Install Dirs and Files](#section_Install_Dirs_and_Files)
14
14
  * [Configure](#section_Configure)
15
15
  * [Ssh](#section_Configure_Ssh)
16
+ * [Git](#section_Configure_Git)
16
17
  * [Start](#section_Start)
17
18
  * [Create Job](#section_Start_Create_Job)
18
19
  * [Run Test](#section_Start_Run_Test)
@@ -79,9 +80,6 @@ Configure
79
80
  ### Configure Ssh
80
81
 
81
82
  The Ssh configuration consists of:
82
- * tmp_file_dir, which is where files will be stored before being scp'd
83
- over to the nodes. They will be deleted afterwards, unless the job
84
- fails in mid-copy. By default this is tmp/file/.
85
83
  * nodes, identified by aliases, such as `test_node`. This alias is what you should
86
84
  pass into the "node" param over in the ssh.run task.
87
85
  * if no node is specified, commands will default to the first node listed.
@@ -109,7 +107,6 @@ Sample ssh.yml:
109
107
  ``` yml
110
108
  ---
111
109
  development:
112
- tmp_file_dir: tmp/file/
113
110
  nodes:
114
111
  dev_node:
115
112
  sudoers:
@@ -126,7 +123,6 @@ development:
126
123
  port: 22
127
124
  user: gateway_user
128
125
  test:
129
- tmp_file_dir: tmp/file/
130
126
  nodes:
131
127
  test_node:
132
128
  sudoers:
@@ -143,7 +139,6 @@ test:
143
139
  port: 22
144
140
  user: gateway_user
145
141
  production:
146
- tmp_file_dir: tmp/file/
147
142
  nodes:
148
143
  prod_node:
149
144
  sudoers:
@@ -161,6 +156,57 @@ production:
161
156
  user: gateway_user
162
157
  ```
163
158
 
159
+ <a name='section_Configure_Git'></a>
160
+ ### Configure Git
161
+
162
+ Git configuration is not required but recommended, as it allows you to
163
+ pull files directly from public or private Git repositories.
164
+
165
+ The Git configuration consists of:
166
+ * domains, identified by aliases, such as `private` and `public`.
167
+ * domains are passed into the source parameters in the git.run task.
168
+ * if no domain is specified, commands will default to the first domain listed.
169
+
170
+ Each domain has:
171
+ * a host;
172
+ * a key (optional); If you don't need an ssh key to access the repo, remove that row from the configuration file.
173
+ * this is the relative path of the ssh key required to access the repository.
174
+ * a user, which is the user used for the git clone command.
175
+
176
+ Sample git.yml:
177
+
178
+ ``` yml
179
+ ---
180
+ development:
181
+ domains:
182
+ private:
183
+ host: github.<domain>.com
184
+ key: config/mobilize/ssh_private.key
185
+ user: git
186
+ public:
187
+ host: github.com
188
+ user: git
189
+ test:
190
+ domains:
191
+ private:
192
+ host: github.<domain>.com
193
+ key: config/mobilize/ssh_private.key
194
+ user: git
195
+ public:
196
+ host: github.com
197
+ user: git
198
+ production:
199
+ domains:
200
+ private:
201
+ host: github.<domain>.com
202
+ key: config/mobilize/ssh_private.key
203
+ user: git
204
+ public:
205
+ host: github.com
206
+ user: git
207
+ ```
208
+
209
+
164
210
  <a name='section_Start'></a>
165
211
  Start
166
212
  -----
@@ -173,6 +219,10 @@ Start
173
219
  * user, sources, and node are optional; cmd is required.
174
220
  * specifying user will cause the command to be prefixed with sudo su <user> -c.
175
221
  * non-google sources will also be read as the specified user.
222
+ * git sources can be specified with syntax `git://<domain>/<repo_owner>/<repo_name>/<file_path>`.
223
+ * Accessing private repos requires that you add the Mobilize public key to the repository as a deploy key.
224
+ * there is no user-level access control for git repositories at this time.
225
+ * domain defaults to the first one listed, if not included.
176
226
  * not specifying node will cause the command to be run on the default node.
177
227
  * ssh sources can be specified with syntax
178
228
  `ssh://<node><file_full_path>`. If node is omitted, default node will be used.
@@ -0,0 +1,111 @@
1
+ module Mobilize
2
+ module Git
3
+ def Git.config
4
+ Base.config('git')
5
+ end
6
+
7
+ def Git.host(domain)
8
+ Git.config['domains'][domain]['host']
9
+ end
10
+
11
+ def Git.domains
12
+ Git.config['domains'].keys
13
+ end
14
+
15
+ def Git.default_domain
16
+ Git.domains.first
17
+ end
18
+
19
+ # converts a source path or target path to a dst in the context of handler and stage
20
+ def Git.path_to_dst(path,stage_path,gdrive_slot)
21
+ red_path = path.split("://").last
22
+ git_url = Git.url_by_path(red_path)
23
+ return Dataset.find_or_create_by_url(git_url)
24
+ end
25
+
26
+ def Git.url_by_path(path)
27
+ path_nodes = path.split("/")
28
+ domain = path_nodes.first.to_s
29
+ revision = "HEAD"
30
+ if Git.domains.include?(domain)
31
+ repo = path_nodes[1..2].join("/")
32
+ file_path = path_nodes[3..-1].join("/")
33
+ else
34
+ domain = Git.default_domain
35
+ repo = path_nodes[0..1].join("/")
36
+ file_path = path_nodes[2..-1].join("/")
37
+ end
38
+ url = "git://#{domain}/#{repo}/#{revision}/#{file_path}"
39
+ return url
40
+ end
41
+
42
+ #return path to tar.gz of git repo
43
+ def Git.pack(domain,repo,revision="HEAD")
44
+ repo_dir = Git.pull(domain,repo,revision)
45
+ repo_name = repo.split("/").last
46
+ tar_gz_path = "#{repo_dir}/../#{repo_name}.tar.gz"
47
+ pack_cmd = "cd #{repo_dir} && git archive #{revision} --format=tar.gz > #{tar_gz_path}"
48
+ pack_cmd.bash(true)
49
+ FileUtils.rm_r(repo_dir,:force=>true)
50
+ return tar_gz_path
51
+ end
52
+
53
+ #confirm that git file exists
54
+ def Git.exists?(url)
55
+ domain,repo,revision,file_path=[]
56
+ url.split("/").ie do |url_nodes|
57
+ domain = url_nodes[2]
58
+ repo = url_nodes[3..4].join("/")
59
+ revision = url_nodes[5]
60
+ file_path = url_nodes[6..-1].join("/")
61
+ end
62
+ repo_dir = Git.pull(domain,repo,revision)
63
+ full_path = "#{repo_dir}/#{file_path}"
64
+ exists = File.exists?(full_path)
65
+ if exists
66
+ FileUtils.rm_r(repo_dir,:force=>true)
67
+ return exists
68
+ else
69
+ raise "Unable to find #{full_path}"
70
+ end
71
+ end
72
+
73
+ #pulls a git repo and sets it to the specified revision in the
74
+ #specified folder
75
+ def Git.pull(domain,repo,revision,run_dir=Dir.mktmpdir)
76
+ domain_properties = Git.config['domains'][domain]
77
+ user,host,key = ['user','host','key'].map{|k| domain_properties[k]}
78
+ #create folder for repo and command
79
+ run_file_path = run_dir + "/cmd.sh"
80
+ #put together command
81
+ git_prefix = key ? "ssh-add #{Base.root}/#{key};" : ""
82
+ git_suffix = (revision=="HEAD" ? " --depth=1" : "; git checkout -q #{revision}")
83
+ #add keys, clone repo, go to specific revision, execute command
84
+ full_cmd = "cd #{run_dir};#{git_prefix}git clone -q #{user}@#{host}:#{repo}.git#{git_suffix}"
85
+ #put command in file, run ssh-agent bash on it
86
+ File.open(run_file_path,"w") {|f| f.print(full_cmd)}
87
+ run_cmd = "ssh-agent bash #{run_file_path}"
88
+ #run the command, it will return an exception if there are issues
89
+ run_cmd.bash(true)
90
+ repo_name = repo.split("/").last
91
+ repo_dir = "#{run_dir}/#{repo_name}"
92
+ return repo_dir
93
+ end
94
+
95
+ def Git.read_by_dataset_path(dst_path,user_name,*args)
96
+ domain,repo,revision,file_path = []
97
+ dst_path.split("/").ie do |path_nodes|
98
+ domain = path_nodes[0]
99
+ repo = path_nodes[1..2].join("/")
100
+ revision = path_nodes[3]
101
+ file_path = path_nodes[4..-1].join("/")
102
+ end
103
+ #slash in front of path
104
+ repo_dir = Git.pull(domain,repo,revision)
105
+ full_path = "#{repo_dir}/#{file_path}"
106
+ result = "cat #{full_path}".bash(true)
107
+ FileUtils.rm_r(repo_dir,:force=>true)
108
+ return result
109
+ end
110
+ end
111
+ end
@@ -1,45 +1,8 @@
1
1
  module Mobilize
2
2
  module Ssh
3
- def Ssh.config
4
- Base.config('ssh')
5
- end
6
-
7
- def Ssh.tmp_file_dir
8
- Ssh.config['tmp_file_dir']
9
- end
10
-
11
- def Ssh.host(node)
12
- Ssh.config['nodes'][node]['host']
13
- end
14
-
15
- def Ssh.gateway(node)
16
- Ssh.config['nodes'][node]['gateway']
17
- end
18
-
19
- def Ssh.sudoers(node)
20
- Ssh.config['nodes'][node]['sudoers']
21
- end
22
-
23
- def Ssh.su_all_users(node)
24
- Ssh.config['nodes'][node]['su_all_users']
25
- end
26
-
27
- def Ssh.nodes
28
- Ssh.config['nodes'].keys
29
- end
30
-
31
- def Ssh.default_node
32
- Ssh.nodes.first
33
- end
34
-
35
- #determine if current machine is on host domain, needs gateway if one is provided and it is not
36
- def Ssh.needs_gateway?(node)
37
- host_domain_name = Ssh.host(node)['name'].split(".")[-2..-1].join(".")
38
- return true if Ssh.gateway(node) and Socket.domain_name != host_domain_name
39
- end
40
-
3
+ #adds convenience methods
4
+ require "#{File.dirname(__FILE__)}/../helpers/ssh_helper"
41
5
  def Ssh.pop_comm_dir(comm_dir,file_hash)
42
- FileUtils.rm_r comm_dir, :force=>true
43
6
  file_hash.each do |fname,fdata|
44
7
  fpath = "#{comm_dir}/#{fname}"
45
8
  #for now, only gz is binary
@@ -50,18 +13,6 @@ module Mobilize
50
13
  return true if file_hash.keys.length>0
51
14
  end
52
15
 
53
- def Ssh.set_key_permissions(key_path)
54
- #makes sure permissions are set as appropriate for ssh key
55
- raise "could not find ssh key at #{key_path}" unless File.exists?(key_path)
56
- #keys named with .pem are
57
- if key_path.ends_with?(".pem")
58
- File.chmod(0400,key_path) unless File.stat(key_path).mode.to_s(8)[3..5] == "400"
59
- else
60
- File.chmod(0600,key_path) unless File.stat(key_path).mode.to_s(8)[3..5] == "600"
61
- end
62
- return true
63
- end
64
-
65
16
  # converts a source path or target path to a dst in the context of handler and stage
66
17
  def Ssh.path_to_dst(path,stage_path,gdrive_slot)
67
18
  has_handler = true if path.index("://")
@@ -102,7 +53,6 @@ module Mobilize
102
53
  def Ssh.scp(node,from_path,to_path)
103
54
  name,key,port,user = Ssh.host(node).ie{|h| ['name','key','port','user'].map{|k| h[k]}}
104
55
  key_path = "#{Base.root}/#{key}"
105
- Ssh.set_key_permissions(key_path)
106
56
  opts = {:port=>(port || 22),:keys=>key_path}
107
57
  if Ssh.needs_gateway?(node)
108
58
  gname,gkey,gport,guser = Ssh.gateway(node).ie{|h| ['name','key','port','user'].map{|k| h[k]}}
@@ -117,25 +67,31 @@ module Mobilize
117
67
  return true
118
68
  end
119
69
 
120
- def Ssh.run(node,command,user,file_hash={})
121
- key,default_user = Ssh.host(node).ie{|h| ['key','user'].map{|k| h[k]}}
122
- key_path = "#{Base.root}/#{key}"
123
- Ssh.set_key_permissions(key_path)
70
+ def Ssh.run(node,command,user,file_hash={},params={})
71
+ default_user = Ssh.host(node)['user']
124
72
  file_hash ||= {}
125
73
  #make sure the dir for this command is clear
126
74
  comm_md5 = [user,node,command,file_hash.keys.to_s,Time.now.to_f.to_s].join.to_md5
127
- comm_dir = "#{Ssh.tmp_file_dir}#{comm_md5}"
75
+ comm_dir = Dir.mktmpdir
76
+ #add in default methods to the params
77
+ params.merge(Ssh.default_params)
78
+ #replace any params in the file_hash and command
79
+ params.each do |k,v|
80
+ command.gsub!(k,v)
81
+ file_hash.each do |name,data|
82
+ data.gsub!(k,v)
83
+ end
84
+ end
128
85
  #populate comm dir with any files
129
86
  Ssh.pop_comm_dir(comm_dir,file_hash)
130
- #move any files up to the node
131
- rem_dir = nil
132
87
  #make sure user starts in rem_dir
133
88
  rem_dir = "#{comm_md5}/"
134
89
  #make sure the rem_dir is gone
135
90
  Ssh.fire!(node,"sudo rm -rf #{rem_dir}")
136
91
  if File.exists?(comm_dir)
137
92
  Ssh.scp(node,comm_dir,rem_dir)
138
- FileUtils.rm_r comm_dir, :force=>true
93
+ #make sure comm_dir is removed
94
+ FileUtils.rm_r(comm_dir,:force=>true)
139
95
  else
140
96
  #create folder
141
97
  mkdir_command = "mkdir #{rem_dir}"
@@ -163,7 +119,6 @@ module Mobilize
163
119
  def Ssh.fire!(node,cmd)
164
120
  name,key,port,user = Ssh.host(node).ie{|h| ['name','key','port','user'].map{|k| h[k]}}
165
121
  key_path = "#{Base.root}/#{key}"
166
- Ssh.set_key_permissions(key_path)
167
122
  opts = {:port=>(port || 22),:keys=>key_path}
168
123
  response = if Ssh.needs_gateway?(node)
169
124
  gname,gkey,gport,guser = Ssh.gateway(node).ie{|h| ['name','key','port','user'].map{|k| h[k]}}
@@ -193,17 +148,15 @@ module Mobilize
193
148
  def Ssh.write(node,fdata,to_path,binary=false)
194
149
  from_path = Ssh.tmp_file(fdata,binary)
195
150
  Ssh.scp(node,from_path,to_path)
196
- FileUtils.rm from_path
151
+ #make sure local is removed
152
+ FileUtils.rm_r(from_path,:force=>true)
197
153
  return true
198
154
  end
199
155
 
200
156
  def Ssh.tmp_file(fdata,binary=false,fpath=nil)
201
157
  #creates a file under tmp/files with an md5 from the data
202
- tmp_file_path = fpath || "#{Ssh.tmp_file_dir}#{(fdata + Time.now.utc.to_f.to_s).to_md5}"
158
+ tmp_file_path = fpath || "#{Dir.mktmpdir}/#{(fdata + Time.now.utc.to_f.to_s).to_md5}"
203
159
  write_mode = binary ? "wb" : "w"
204
- #make sure folder is created
205
- tmp_file_dir = tmp_file_path.split("/")[0..-2].join("/")
206
- FileUtils.mkdir_p(tmp_file_dir)
207
160
  #write data to path
208
161
  File.open(tmp_file_path,write_mode) {|f| f.print(fdata)}
209
162
  return tmp_file_path
@@ -268,6 +221,8 @@ module Mobilize
268
221
  response = {}
269
222
  response['out_url'] = Dataset.write_by_url("gridfs://#{s.path}/out",result['stdout'].to_s,Gdrive.owner_name)
270
223
  response['err_url'] = Dataset.write_by_url("gridfs://#{s.path}/err",result['stderr'].to_s,Gdrive.owner_name) if result['stderr'].to_s.length>0
224
+ #is an error if there is no out and there is an err, regardless of signal
225
+ result['exit_code'] = 500 if result['stdout'].to_s.strip.length==0 and result['stderr'].to_s.strip.length>0
271
226
  response['signal'] = result['exit_code']
272
227
  response
273
228
  end
@@ -0,0 +1,45 @@
1
+ module Mobilize
2
+ module Ssh
3
+ def self.config
4
+ Base.config('ssh')
5
+ end
6
+
7
+ def self.host(node)
8
+ self.config['nodes'][node]['host']
9
+ end
10
+
11
+ def self.gateway(node)
12
+ self.config['nodes'][node]['gateway']
13
+ end
14
+
15
+ def self.sudoers(node)
16
+ self.config['nodes'][node]['sudoers']
17
+ end
18
+
19
+ def self.su_all_users(node)
20
+ self.config['nodes'][node]['su_all_users']
21
+ end
22
+
23
+ def self.nodes
24
+ self.config['nodes'].keys
25
+ end
26
+
27
+ def self.default_node
28
+ self.nodes.first
29
+ end
30
+
31
+ #determine if current machine is on host domain, needs gateway if one is provided and it is not
32
+ def self.needs_gateway?(node)
33
+ host_domain_name = self.host(node)['name'].split(".")[-2..-1].join(".")
34
+ return true if self.gateway(node) and Socket.domain_name != host_domain_name
35
+ end
36
+
37
+ def self.default_params
38
+ time = Time.now.utc
39
+ {
40
+ '$utc_date'=>time.strftime("%Y-%m-%d"),
41
+ '$utc_time'=>time.strftime("%H:%M"),
42
+ }
43
+ end
44
+ end
45
+ end
@@ -1,5 +1,5 @@
1
1
  module Mobilize
2
2
  module Ssh
3
- VERSION = "1.297"
3
+ VERSION = "1.298"
4
4
  end
5
5
  end
data/lib/mobilize-ssh.rb CHANGED
@@ -12,3 +12,4 @@ module Mobilize
12
12
  end
13
13
  end
14
14
  require "mobilize-ssh/handlers/ssh"
15
+ require "mobilize-ssh/handlers/git"
data/mobilize-ssh.gemspec CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
- gem.add_runtime_dependency "mobilize-base","1.297"
19
+ gem.add_runtime_dependency "mobilize-base","1.298"
20
20
  gem.add_runtime_dependency "net-ssh"
21
21
  gem.add_runtime_dependency "net-scp"
22
22
  gem.add_runtime_dependency "net-ssh-gateway"
@@ -14,8 +14,8 @@ describe "Mobilize" do
14
14
 
15
15
  gdrive_slot = Mobilize::Gdrive.owner_email
16
16
  puts "create user 'mobilize'"
17
- user = gdrive_slot.split("@").first
18
- u = Mobilize::User.where(:name=>user).first
17
+ user_name = gdrive_slot.split("@").first
18
+ u = Mobilize::User.where(:name=>user_name).first
19
19
  r = u.runner
20
20
 
21
21
  rb_code_sheet = Mobilize::Gsheet.find_by_path("#{r.path.split("/")[0..-2].join("/")}/code.rb",gdrive_slot)
@@ -42,6 +42,7 @@ describe "Mobilize" do
42
42
  ssh_target_sheet_1 = Mobilize::Gsheet.find_by_path("#{r.path.split("/")[0..-2].join("/")}/test_ssh_1.out",gdrive_slot)
43
43
  ssh_target_sheet_2 = Mobilize::Gsheet.find_by_path("#{r.path.split("/")[0..-2].join("/")}/test_ssh_2.out",gdrive_slot)
44
44
  ssh_target_sheet_3 = Mobilize::Gsheet.find_by_path("#{r.path.split("/")[0..-2].join("/")}/test_ssh_3.out",gdrive_slot)
45
+ ssh_target_sheet_4 = Mobilize::Gsheet.find_by_path("#{r.path.split("/")[0..-2].join("/")}/test_ssh_4.out",gdrive_slot)
45
46
  [ssh_target_sheet_1,ssh_target_sheet_2,ssh_target_sheet_3].each {|s| s.delete if s}
46
47
 
47
48
  ssh_job_rows = ::YAML.load_file("#{Mobilize::Base.root}/test/ssh_job_rows.yml")
@@ -59,10 +60,12 @@ describe "Mobilize" do
59
60
  ssh_target_sheet_1 = Mobilize::Gsheet.find_by_path("#{r.path.split("/")[0..-2].join("/")}/test_ssh_1.out",gdrive_slot)
60
61
  ssh_target_sheet_2 = Mobilize::Gsheet.find_by_path("#{r.path.split("/")[0..-2].join("/")}/test_ssh_2.out",gdrive_slot)
61
62
  ssh_target_sheet_3 = Mobilize::Gsheet.find_by_path("#{r.path.split("/")[0..-2].join("/")}/test_ssh_3.out",gdrive_slot)
63
+ ssh_target_sheet_4 = Mobilize::Gsheet.find_by_path("#{r.path.split("/")[0..-2].join("/")}/test_ssh_4.out",gdrive_slot)
62
64
 
63
65
  assert ssh_target_sheet_1.to_tsv.length > 100
64
66
  assert ssh_target_sheet_2.to_tsv.length > 100
65
67
  assert ssh_target_sheet_3.to_tsv.length > 3
68
+ assert ssh_target_sheet_4.to_tsv.length > 100
66
69
 
67
70
  end
68
71
 
@@ -1,3 +1,4 @@
1
+ ---
1
2
  - name: test_ssh_1
2
3
  active: true
3
4
  trigger: once
@@ -16,3 +17,9 @@
16
17
  status: ""
17
18
  stage1: 'ssh.run cmd:"whoami"'
18
19
  stage2: 'gsheet.write source:"stage1", target:"test_ssh_3.out"'
20
+ - name: test_ssh_4
21
+ active: true
22
+ trigger: "after test_ssh_3"
23
+ status: ""
24
+ stage1: 'ssh.run node:"test_node", user:root, sources:["git://DeNA/mobilize-ssh/test/code.rb","git://DeNA/mobilize-ssh/test/code.sh"], cmd:"ruby code.rb"'
25
+ stage2: 'gsheet.write source:stage1, target:"test_ssh_4.out"'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mobilize-ssh
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.297'
4
+ version: '1.298'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-06 00:00:00.000000000 Z
12
+ date: 2013-04-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mobilize-base
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - '='
20
20
  - !ruby/object:Gem::Version
21
- version: '1.297'
21
+ version: '1.298'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - '='
28
28
  - !ruby/object:Gem::Version
29
- version: '1.297'
29
+ version: '1.298'
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: net-ssh
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -91,7 +91,9 @@ files:
91
91
  - lib/mobilize-ssh/extensions/net-ssh-connection-session.rb
92
92
  - lib/mobilize-ssh/extensions/net-ssh-gateway.rb
93
93
  - lib/mobilize-ssh/extensions/socket.rb
94
+ - lib/mobilize-ssh/handlers/git.rb
94
95
  - lib/mobilize-ssh/handlers/ssh.rb
96
+ - lib/mobilize-ssh/helpers/ssh_helper.rb
95
97
  - lib/mobilize-ssh/tasks.rb
96
98
  - lib/mobilize-ssh/version.rb
97
99
  - lib/samples/ssh.yml
@@ -117,7 +119,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
117
119
  version: '0'
118
120
  segments:
119
121
  - 0
120
- hash: 1145721245595334784
122
+ hash: 2943604812785338750
121
123
  required_rubygems_version: !ruby/object:Gem::Requirement
122
124
  none: false
123
125
  requirements:
@@ -126,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
128
  version: '0'
127
129
  segments:
128
130
  - 0
129
- hash: 1145721245595334784
131
+ hash: 2943604812785338750
130
132
  requirements: []
131
133
  rubyforge_project:
132
134
  rubygems_version: 1.8.25