mobilize-ssh 1.2 → 1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +65 -8
- data/lib/mobilize-ssh/handlers/git.rb +111 -0
- data/lib/mobilize-ssh/handlers/ssh.rb +43 -75
- data/lib/mobilize-ssh/helpers/ssh_helper.rb +37 -0
- data/lib/mobilize-ssh/version.rb +1 -1
- data/lib/mobilize-ssh.rb +1 -1
- data/mobilize-ssh.gemspec +1 -1
- data/test/code2.sh +1 -1
- data/test/mobilize-ssh_test.rb +5 -2
- data/test/ssh_job_rows.yml +13 -6
- metadata +7 -7
- data/lib/mobilize-ssh/extensions/string.rb +0 -5
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
|
-----
|
|
@@ -169,10 +215,21 @@ Start
|
|
|
169
215
|
### Create Job
|
|
170
216
|
|
|
171
217
|
* For mobilize-ssh, the following task is available:
|
|
172
|
-
* ssh.run `node: <node_alias>, cmd: <command>, user: user, sources:[*<source_paths>]`, which reads sources, copies them to a temporary folder on the selected node, and runs the command inside that folder.
|
|
173
|
-
* user, sources, and
|
|
218
|
+
* ssh.run `node: <node_alias>, cmd: <command>, user: user, sources:[*<source_paths>], params:[{<key,value pairs>}]`, which reads sources, copies them to a temporary folder on the selected node, and runs the command inside that folder.
|
|
219
|
+
* user, sources, node, and params 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.
|
|
226
|
+
* params are also optional for all of the below. They replace tokens in sources and the command.
|
|
227
|
+
* params are passed as a YML or JSON, as in:
|
|
228
|
+
* `ssh.run source:<source_path>, params:{'date':'2013-03-01', 'unit':'widgets'}`
|
|
229
|
+
* this example replaces all the keys, preceded by '@' in all source hqls with the value.
|
|
230
|
+
* The preceding '@' is used to keep from replacing instances
|
|
231
|
+
of "date" and "unit" in the command/source file; you should have `@date` and `@unit` in your actual HQL
|
|
232
|
+
if you'd like to replace those tokens.
|
|
176
233
|
* not specifying node will cause the command to be run on the default node.
|
|
177
234
|
* ssh sources can be specified with syntax
|
|
178
235
|
`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
|
-
|
|
4
|
-
|
|
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,15 +13,8 @@ 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
|
-
File.chmod(0600,key_path) unless File.stat(key_path).mode.to_s(8)[3..5] == "600"
|
|
57
|
-
return true
|
|
58
|
-
end
|
|
59
|
-
|
|
60
16
|
# converts a source path or target path to a dst in the context of handler and stage
|
|
61
|
-
def Ssh.path_to_dst(path,stage_path)
|
|
17
|
+
def Ssh.path_to_dst(path,stage_path,gdrive_slot)
|
|
62
18
|
has_handler = true if path.index("://")
|
|
63
19
|
red_path = path.split("://").last
|
|
64
20
|
#is user has a handler, their first path node is a node name,
|
|
@@ -69,7 +25,7 @@ module Mobilize
|
|
|
69
25
|
return Dataset.find_or_create_by_url(ssh_url)
|
|
70
26
|
end
|
|
71
27
|
#otherwise, use Gsheet
|
|
72
|
-
return Gsheet.path_to_dst(red_path,stage_path)
|
|
28
|
+
return Gsheet.path_to_dst(red_path,stage_path,gdrive_slot)
|
|
73
29
|
end
|
|
74
30
|
|
|
75
31
|
def Ssh.url_by_path(path,user_name)
|
|
@@ -97,7 +53,6 @@ module Mobilize
|
|
|
97
53
|
def Ssh.scp(node,from_path,to_path)
|
|
98
54
|
name,key,port,user = Ssh.host(node).ie{|h| ['name','key','port','user'].map{|k| h[k]}}
|
|
99
55
|
key_path = "#{Base.root}/#{key}"
|
|
100
|
-
Ssh.set_key_permissions(key_path)
|
|
101
56
|
opts = {:port=>(port || 22),:keys=>key_path}
|
|
102
57
|
if Ssh.needs_gateway?(node)
|
|
103
58
|
gname,gkey,gport,guser = Ssh.gateway(node).ie{|h| ['name','key','port','user'].map{|k| h[k]}}
|
|
@@ -112,25 +67,30 @@ module Mobilize
|
|
|
112
67
|
return true
|
|
113
68
|
end
|
|
114
69
|
|
|
115
|
-
def Ssh.run(node,command,
|
|
116
|
-
|
|
117
|
-
key_path = "#{Base.root}/#{key}"
|
|
118
|
-
Ssh.set_key_permissions(key_path)
|
|
70
|
+
def Ssh.run(node,command,user_name,file_hash={},run_params=nil)
|
|
71
|
+
default_user_name = Ssh.host(node)['user']
|
|
119
72
|
file_hash ||= {}
|
|
73
|
+
run_params ||={}
|
|
120
74
|
#make sure the dir for this command is clear
|
|
121
|
-
comm_md5 = [
|
|
122
|
-
comm_dir =
|
|
123
|
-
#
|
|
75
|
+
comm_md5 = [user_name,node,command,file_hash.keys.to_s,Time.now.to_f.to_s].join.to_md5
|
|
76
|
+
comm_dir = Dir.mktmpdir
|
|
77
|
+
#replace any params in the file_hash and command
|
|
78
|
+
run_params.each do |k,v|
|
|
79
|
+
command.gsub!("@#{k}",v)
|
|
80
|
+
file_hash.each do |name,data|
|
|
81
|
+
data.gsub!("@#{k}",v)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
#populate comm dir with any files
|
|
124
85
|
Ssh.pop_comm_dir(comm_dir,file_hash)
|
|
125
|
-
#move any files up to the node
|
|
126
|
-
rem_dir = nil
|
|
127
86
|
#make sure user starts in rem_dir
|
|
128
87
|
rem_dir = "#{comm_md5}/"
|
|
129
88
|
#make sure the rem_dir is gone
|
|
130
89
|
Ssh.fire!(node,"sudo rm -rf #{rem_dir}")
|
|
131
90
|
if File.exists?(comm_dir)
|
|
132
91
|
Ssh.scp(node,comm_dir,rem_dir)
|
|
133
|
-
|
|
92
|
+
#make sure comm_dir is removed
|
|
93
|
+
FileUtils.rm_r(comm_dir,:force=>true)
|
|
134
94
|
else
|
|
135
95
|
#create folder
|
|
136
96
|
mkdir_command = "mkdir #{rem_dir}"
|
|
@@ -142,9 +102,9 @@ module Mobilize
|
|
|
142
102
|
Ssh.write(node,command,cmd_path)
|
|
143
103
|
full_cmd = "(cd #{rem_dir} && sh #{cmd_file})"
|
|
144
104
|
#fire_cmd runs sh on cmd_path, optionally with sudo su
|
|
145
|
-
if
|
|
105
|
+
if user_name != default_user_name
|
|
146
106
|
#make sure user owns the folder and all files
|
|
147
|
-
fire_cmd = %{sudo chown -R #{
|
|
107
|
+
fire_cmd = %{sudo chown -R #{user_name} #{rem_dir}; sudo su #{user_name} -c "#{full_cmd}"}
|
|
148
108
|
rm_cmd = %{sudo rm -rf #{rem_dir}}
|
|
149
109
|
else
|
|
150
110
|
fire_cmd = full_cmd
|
|
@@ -158,7 +118,6 @@ module Mobilize
|
|
|
158
118
|
def Ssh.fire!(node,cmd)
|
|
159
119
|
name,key,port,user = Ssh.host(node).ie{|h| ['name','key','port','user'].map{|k| h[k]}}
|
|
160
120
|
key_path = "#{Base.root}/#{key}"
|
|
161
|
-
Ssh.set_key_permissions(key_path)
|
|
162
121
|
opts = {:port=>(port || 22),:keys=>key_path}
|
|
163
122
|
response = if Ssh.needs_gateway?(node)
|
|
164
123
|
gname,gkey,gport,guser = Ssh.gateway(node).ie{|h| ['name','key','port','user'].map{|k| h[k]}}
|
|
@@ -188,17 +147,15 @@ module Mobilize
|
|
|
188
147
|
def Ssh.write(node,fdata,to_path,binary=false)
|
|
189
148
|
from_path = Ssh.tmp_file(fdata,binary)
|
|
190
149
|
Ssh.scp(node,from_path,to_path)
|
|
191
|
-
|
|
150
|
+
#make sure local is removed
|
|
151
|
+
FileUtils.rm_r(from_path,:force=>true)
|
|
192
152
|
return true
|
|
193
153
|
end
|
|
194
154
|
|
|
195
155
|
def Ssh.tmp_file(fdata,binary=false,fpath=nil)
|
|
196
156
|
#creates a file under tmp/files with an md5 from the data
|
|
197
|
-
tmp_file_path = fpath || "#{
|
|
157
|
+
tmp_file_path = fpath || "#{Dir.mktmpdir}/#{(fdata + Time.now.utc.to_f.to_s).to_md5}"
|
|
198
158
|
write_mode = binary ? "wb" : "w"
|
|
199
|
-
#make sure folder is created
|
|
200
|
-
tmp_file_dir = tmp_file_path.split("/")[0..-2].join("/")
|
|
201
|
-
FileUtils.mkdir_p(tmp_file_dir)
|
|
202
159
|
#write data to path
|
|
203
160
|
File.open(tmp_file_path,write_mode) {|f| f.print(fdata)}
|
|
204
161
|
return tmp_file_path
|
|
@@ -208,7 +165,7 @@ module Mobilize
|
|
|
208
165
|
s = Stage.where(:path=>stage_path).first
|
|
209
166
|
u = s.job.runner.user
|
|
210
167
|
user_name = s.params['user']
|
|
211
|
-
node = s.params['node']
|
|
168
|
+
node = s.params['node']
|
|
212
169
|
node = Ssh.default_node unless Ssh.nodes.include?(node)
|
|
213
170
|
if user_name and !Ssh.sudoers(node).include?(u.name)
|
|
214
171
|
raise "#{u.name} does not have su permissions for this node"
|
|
@@ -218,23 +175,27 @@ module Mobilize
|
|
|
218
175
|
return user_name
|
|
219
176
|
end
|
|
220
177
|
|
|
221
|
-
def Ssh.file_hash_by_stage_path(stage_path)
|
|
178
|
+
def Ssh.file_hash_by_stage_path(stage_path,gdrive_slot)
|
|
222
179
|
file_hash = {}
|
|
223
180
|
s = Stage.where(:path=>stage_path).first
|
|
224
181
|
u = s.job.runner.user
|
|
225
182
|
user_name = Ssh.user_name_by_stage_path(stage_path)
|
|
226
|
-
s.sources.each do |sdst|
|
|
183
|
+
s.sources(gdrive_slot).each do |sdst|
|
|
227
184
|
split_path = sdst.path.split("/")
|
|
228
185
|
#if path is to stage output, name with stage name
|
|
229
|
-
file_name = if split_path.last == "out" and
|
|
230
|
-
|
|
186
|
+
file_name = if (split_path.last == "out" and (1..5).to_a.map{|n| "stage#{n.to_s}"}.include?(split_path[-2].to_s))
|
|
187
|
+
#<jobname>/stage1/out
|
|
231
188
|
"#{split_path[-2]}.out"
|
|
189
|
+
elsif (1..5).to_a.map{|n| "stage#{n.to_s}"}.include?(split_path.last[-6..-1])
|
|
190
|
+
#runner<jobname>stage1
|
|
191
|
+
"#{split_path.last[-6..-1]}.out"
|
|
232
192
|
else
|
|
233
193
|
split_path.last
|
|
234
194
|
end
|
|
235
195
|
if ["gsheet","gfile"].include?(sdst.handler)
|
|
236
196
|
#google drive sources are always read as the user
|
|
237
|
-
|
|
197
|
+
#with the apportioned slot
|
|
198
|
+
file_hash[file_name] = sdst.read(u.name,gdrive_slot)
|
|
238
199
|
else
|
|
239
200
|
#other sources should be read by su-user
|
|
240
201
|
file_hash[file_name] = sdst.read(user_name)
|
|
@@ -244,17 +205,24 @@ module Mobilize
|
|
|
244
205
|
end
|
|
245
206
|
|
|
246
207
|
def Ssh.run_by_stage_path(stage_path)
|
|
208
|
+
gdrive_slot = Gdrive.slot_worker_by_path(stage_path)
|
|
209
|
+
#return blank response if there are no slots available
|
|
210
|
+
return nil unless gdrive_slot
|
|
247
211
|
s = Stage.where(:path=>stage_path).first
|
|
248
212
|
params = s.params
|
|
249
213
|
node, command = [params['node'],params['cmd']]
|
|
250
214
|
node ||= Ssh.default_node
|
|
251
215
|
user_name = Ssh.user_name_by_stage_path(stage_path)
|
|
252
|
-
file_hash = Ssh.file_hash_by_stage_path(stage_path)
|
|
253
|
-
|
|
216
|
+
file_hash = Ssh.file_hash_by_stage_path(stage_path,gdrive_slot)
|
|
217
|
+
Gdrive.unslot_worker_by_path(stage_path)
|
|
218
|
+
run_params = params['params']
|
|
219
|
+
result = Ssh.run(node,command,user_name,file_hash,run_params)
|
|
254
220
|
#use Gridfs to cache result
|
|
255
221
|
response = {}
|
|
256
222
|
response['out_url'] = Dataset.write_by_url("gridfs://#{s.path}/out",result['stdout'].to_s,Gdrive.owner_name)
|
|
257
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
|
|
258
226
|
response['signal'] = result['exit_code']
|
|
259
227
|
response
|
|
260
228
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
end
|
|
37
|
+
end
|
data/lib/mobilize-ssh/version.rb
CHANGED
data/lib/mobilize-ssh.rb
CHANGED
|
@@ -6,10 +6,10 @@ require "net/scp"
|
|
|
6
6
|
require "mobilize-ssh/extensions/net-ssh-connection-session"
|
|
7
7
|
require "mobilize-ssh/extensions/net-ssh-gateway"
|
|
8
8
|
require "mobilize-ssh/extensions/socket"
|
|
9
|
-
require "mobilize-ssh/extensions/string"
|
|
10
9
|
|
|
11
10
|
module Mobilize
|
|
12
11
|
module Ssh
|
|
13
12
|
end
|
|
14
13
|
end
|
|
15
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.
|
|
19
|
+
gem.add_runtime_dependency "mobilize-base","1.3"
|
|
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"
|
data/test/code2.sh
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
tail
|
|
1
|
+
tail @file
|
data/test/mobilize-ssh_test.rb
CHANGED
|
@@ -14,8 +14,8 @@ describe "Mobilize" do
|
|
|
14
14
|
|
|
15
15
|
gdrive_slot = Mobilize::Gdrive.owner_email
|
|
16
16
|
puts "create user 'mobilize'"
|
|
17
|
-
|
|
18
|
-
u = Mobilize::User.where(:name=>
|
|
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
|
|
data/test/ssh_job_rows.yml
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
|
+
---
|
|
1
2
|
- name: test_ssh_1
|
|
2
3
|
active: true
|
|
3
4
|
trigger: once
|
|
4
5
|
status: ""
|
|
5
|
-
stage1:
|
|
6
|
-
stage2:
|
|
6
|
+
stage1: ssh.run node:"test_node", cmd:"ruby code.rb", user:"root", sources:["code.rb", "code.sh"]
|
|
7
|
+
stage2: gsheet.write source:"stage1", target:"test_ssh_1.out"
|
|
7
8
|
- name: test_ssh_2
|
|
8
9
|
active: true
|
|
9
10
|
trigger: "after test_ssh_1"
|
|
10
11
|
status: ""
|
|
11
|
-
stage1:
|
|
12
|
-
stage2:
|
|
12
|
+
stage1: ssh.run cmd:"sh code2.sh", user:"root", sources:["code2.sh","test_node/var/log/syslog"], params:{file:"syslog"}
|
|
13
|
+
stage2: gsheet.write source:"stage1", target:"test_ssh_2.out"
|
|
13
14
|
- name: test_ssh_3
|
|
14
15
|
active: true
|
|
15
16
|
trigger: "after test_ssh_2"
|
|
16
17
|
status: ""
|
|
17
|
-
stage1:
|
|
18
|
-
stage2:
|
|
18
|
+
stage1: ssh.run cmd:"echo '@test_param'", params:{test_param:"test param successful"}
|
|
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.
|
|
4
|
+
version: '1.3'
|
|
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-
|
|
12
|
+
date: 2013-04-18 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.
|
|
21
|
+
version: '1.3'
|
|
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.
|
|
29
|
+
version: '1.3'
|
|
30
30
|
- !ruby/object:Gem::Dependency
|
|
31
31
|
name: net-ssh
|
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -91,8 +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/
|
|
94
|
+
- lib/mobilize-ssh/handlers/git.rb
|
|
95
95
|
- lib/mobilize-ssh/handlers/ssh.rb
|
|
96
|
+
- lib/mobilize-ssh/helpers/ssh_helper.rb
|
|
96
97
|
- lib/mobilize-ssh/tasks.rb
|
|
97
98
|
- lib/mobilize-ssh/version.rb
|
|
98
99
|
- lib/samples/ssh.yml
|
|
@@ -124,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
124
125
|
version: '0'
|
|
125
126
|
requirements: []
|
|
126
127
|
rubyforge_project:
|
|
127
|
-
rubygems_version: 1.8.
|
|
128
|
+
rubygems_version: 1.8.25
|
|
128
129
|
signing_key:
|
|
129
130
|
specification_version: 3
|
|
130
131
|
summary: extend mobilize-base with the ability to run files across hosts
|
|
@@ -136,4 +137,3 @@ test_files:
|
|
|
136
137
|
- test/redis-test.conf
|
|
137
138
|
- test/ssh_job_rows.yml
|
|
138
139
|
- test/test_helper.rb
|
|
139
|
-
has_rdoc:
|