singularity-cli 0.2.1 → 0.3.0
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.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/bin/singularity +14 -2
- data/lib/singularity.rb +154 -9
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f917e15b139df05230f76923b7c1296caa65d62
|
4
|
+
data.tar.gz: 230d2d431af444026ae30b1ece3e4fcdd65edca8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f294ca9b8811d3a3092475baa0f302ffdb10c14a40866d556bb2d96e57e8421ed111b7c41ba2b5320693121e09e24783a3afd734294a805dba2d31ddcec86b59
|
7
|
+
data.tar.gz: 79759aa9d74a1692304e4c5e738d69f0d13e26904dbc01b9665a74729d5b584786594182b1ac615e8657b5d455232c18742adb84954865ab12b8cf4f5afb1cea
|
data/README.md
CHANGED
data/bin/singularity
CHANGED
@@ -5,7 +5,7 @@ require 'singularity'
|
|
5
5
|
ARGV << '--help' if ARGV.empty?
|
6
6
|
|
7
7
|
def print_usage
|
8
|
-
puts "Usage:\n\tsingularity delete <uri> <file>\n\tsingularity deploy <uri> <file> <release>"
|
8
|
+
puts "Usage:\n\tsingularity delete <uri> <file.json>\n\tsingularity deploy <uri> <file.json> <release>\n\tsingularity run <uri> <file.json> <release> <script>"
|
9
9
|
exit
|
10
10
|
end
|
11
11
|
|
@@ -14,7 +14,6 @@ uri = ARGV[1]
|
|
14
14
|
file = ARGV[2]
|
15
15
|
|
16
16
|
case action
|
17
|
-
|
18
17
|
when "delete"
|
19
18
|
print_usage unless ARGV.size == 3
|
20
19
|
Singularity::Deleter.new(uri,file).delete
|
@@ -24,6 +23,19 @@ case action
|
|
24
23
|
release = ARGV[3]
|
25
24
|
Singularity::Deployer.new(uri,file,release).deploy
|
26
25
|
|
26
|
+
when "run"
|
27
|
+
# remove the word "run" from ARGV, pass the remainder to Runner
|
28
|
+
ARGV.shift
|
29
|
+
Singularity::Runner.new(ARGV).runner
|
30
|
+
|
31
|
+
when "runx"
|
32
|
+
# this option is to skip the use of /sbin/my_init
|
33
|
+
# (some commands won't run correctly when both are used)
|
34
|
+
Singularity::Runner.new(ARGV).runner
|
35
|
+
|
36
|
+
when "ssh"
|
37
|
+
Singularity::Runner.new("ssh").runner
|
38
|
+
|
27
39
|
else
|
28
40
|
print_usage
|
29
41
|
end
|
data/lib/singularity.rb
CHANGED
@@ -2,18 +2,17 @@ require 'erb'
|
|
2
2
|
require 'json'
|
3
3
|
require 'rest-client'
|
4
4
|
require 'colorize'
|
5
|
+
require 'yaml'
|
5
6
|
|
6
7
|
module Singularity
|
7
8
|
class Request
|
8
9
|
attr_accessor :release, :cpus, :mem, :envs, :schedule, :cmd, :arguments, :request_id, :repo, :release_string, :release_id_string
|
9
|
-
|
10
10
|
def get_binding
|
11
11
|
binding()
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
15
|
class Deployer
|
16
|
-
|
17
16
|
def initialize(uri, file, release)
|
18
17
|
@uri = uri
|
19
18
|
@file = file
|
@@ -25,7 +24,6 @@ module Singularity
|
|
25
24
|
print @data['id']
|
26
25
|
end
|
27
26
|
|
28
|
-
|
29
27
|
def is_paused
|
30
28
|
begin
|
31
29
|
resp = RestClient.get "#{@uri}/api/requests/request/#{@data['id']}"
|
@@ -45,7 +43,6 @@ module Singularity
|
|
45
43
|
# create or update the request
|
46
44
|
resp = RestClient.post "#{@uri}/api/requests", @data.to_json, :content_type => :json
|
47
45
|
end
|
48
|
-
|
49
46
|
# deploy the request
|
50
47
|
@data['requestId'] = @data['id']
|
51
48
|
@data['id'] = "#{@release}.#{Time.now.to_i}"
|
@@ -54,9 +51,7 @@ module Singularity
|
|
54
51
|
'user' => `whoami`.chomp,
|
55
52
|
'unpauseOnSuccessfulDeploy' => false
|
56
53
|
}
|
57
|
-
|
58
54
|
resp = RestClient.post "#{@uri}/api/deploys", deploy.to_json, :content_type => :json
|
59
|
-
|
60
55
|
puts " DEPLOYED".green
|
61
56
|
rescue Exception => e
|
62
57
|
puts " #{e.response}".red
|
@@ -65,12 +60,10 @@ module Singularity
|
|
65
60
|
end
|
66
61
|
|
67
62
|
class Deleter
|
68
|
-
|
69
63
|
def initialize(uri, file)
|
70
64
|
@uri = uri
|
71
65
|
@file = file
|
72
66
|
end
|
73
|
-
|
74
67
|
# Deleter.delete -- arguments are <uri>, <file>
|
75
68
|
def delete
|
76
69
|
begin
|
@@ -82,6 +75,158 @@ module Singularity
|
|
82
75
|
puts "#{task_id} #{$!.response}"
|
83
76
|
end
|
84
77
|
end
|
85
|
-
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Runner
|
81
|
+
def initialize(script)
|
82
|
+
#########################################################
|
83
|
+
# TODO
|
84
|
+
# check to see that .mescal.json and mesos-deploy.yml exist
|
85
|
+
#########################################################
|
86
|
+
@script = script
|
87
|
+
# read .mescal.json for ssh command, image, release number, cpus, mem
|
88
|
+
@configData = JSON.parse(ERB.new(open(File.join(Dir.pwd, ".mescal.json")).read).result(Request.new.get_binding))
|
89
|
+
@sshCmd = @configData['sshCmd']
|
90
|
+
@image = @configData['image'].split(':')[0]
|
91
|
+
@release = @configData['image'].split(':')[1]
|
92
|
+
|
93
|
+
# read mesos-deploy.yml for singularity url
|
94
|
+
@mesosDeployConfig = YAML.load_file(File.join(Dir.pwd, "mesos-deploy.yml"))
|
95
|
+
@uri = @mesosDeployConfig['singularity_url']
|
96
|
+
|
97
|
+
# create request/deploy json data
|
98
|
+
@data = {
|
99
|
+
'command' => "/sbin/my_init",
|
100
|
+
'resources' => {
|
101
|
+
'memoryMb' => @configData['mem'],
|
102
|
+
'cpus' => @configData['cpus'],
|
103
|
+
'numPorts' => 1
|
104
|
+
},
|
105
|
+
'env' => {
|
106
|
+
'APPLICATION_ENV' => "production"
|
107
|
+
},
|
108
|
+
'requestType' => "RUN_ONCE",
|
109
|
+
'containerInfo' => {
|
110
|
+
'type' => "DOCKER",
|
111
|
+
'docker' => {
|
112
|
+
'image' => @configData['image'],
|
113
|
+
'network' => "BRIDGE",
|
114
|
+
'portMappings' => [{
|
115
|
+
'containerPortType': "LITERAL",
|
116
|
+
'containerPort': 22,
|
117
|
+
'hostPortType': "FROM_OFFER",
|
118
|
+
'hostPort': 0
|
119
|
+
}]
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
# either we typed 'singularity ssh'
|
124
|
+
if @script == "ssh"
|
125
|
+
@data['id'] = Dir.pwd.split('/').last + "_SSH"
|
126
|
+
@data['command'] = "#{@sshCmd}"
|
127
|
+
# or we passed a script/commands to 'singularity run'
|
128
|
+
else
|
129
|
+
# if we passed "runx", then skip use of /sbin/my_init
|
130
|
+
if @script[0] == "runx"
|
131
|
+
@data['arguments'] = [] # don't use "--" as first argument
|
132
|
+
@data['command'] = @script[1] #remove "runx" from commands
|
133
|
+
@script.shift
|
134
|
+
@data['id'] = @script.join("-").tr('@/\*?% []#$', '_')
|
135
|
+
@data['id'][0] = ''
|
136
|
+
@script.shift
|
137
|
+
# else join /sbin/my_init with your commands
|
138
|
+
else
|
139
|
+
@data['arguments'] = ["--"]
|
140
|
+
@data['id'] = @script.join("-").tr('@/\*?% []#$', '_')
|
141
|
+
@data['id'][0] = ''
|
142
|
+
end
|
143
|
+
@script.each { |i| @data['arguments'].push i }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def is_paused
|
148
|
+
begin
|
149
|
+
resp = RestClient.get "#{@uri}/api/requests/request/#{@data['id']}"
|
150
|
+
JSON.parse(resp)['state'] == 'PAUSED'
|
151
|
+
rescue
|
152
|
+
print " Deploying request...".light_green
|
153
|
+
false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def runner
|
158
|
+
begin
|
159
|
+
if is_paused()
|
160
|
+
puts " PAUSED, SKIPPING.".yellow
|
161
|
+
return
|
162
|
+
else
|
163
|
+
# create or update the request
|
164
|
+
RestClient.post "#{@uri}/api/requests", @data.to_json, :content_type => :json
|
165
|
+
end
|
166
|
+
|
167
|
+
# deploy the request
|
168
|
+
@data['requestId'] = @data['id']
|
169
|
+
@data['id'] = "#{@release}.#{Time.now.to_i}"
|
170
|
+
@deploy = {
|
171
|
+
'deploy' => @data,
|
172
|
+
'user' => `whoami`.chomp,
|
173
|
+
'unpauseOnSuccessfulDeploy' => false
|
174
|
+
}
|
175
|
+
RestClient.post "#{@uri}/api/deploys", @deploy.to_json, :content_type => :json
|
176
|
+
puts " Deploy succeeded.".green
|
177
|
+
|
178
|
+
# get active tasks until ours shows up so we can get IP/PORT
|
179
|
+
begin
|
180
|
+
@thisTask = ''
|
181
|
+
@tasks = RestClient.get "#{@uri}/api/tasks/active", :content_type => :json
|
182
|
+
@tasks = JSON.parse(@tasks)
|
183
|
+
@tasks.each do |entry|
|
184
|
+
if entry['taskRequest']['request']['id'] == @data['requestId']
|
185
|
+
@thisTask = entry
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end until @thisTask != ''
|
189
|
+
@ip = @thisTask['offer']['url']['address']['ip']
|
190
|
+
@port = @thisTask['mesosTask']['container']['docker']['portMappings'][0]['hostPort']
|
191
|
+
|
192
|
+
# SSH into the machine
|
193
|
+
if @script == "ssh"
|
194
|
+
# uses "begin end until" because "system" will keep returning "false" unless the command exits with success
|
195
|
+
# this makes sure that the docker image has completely started and the SSH command succeeds
|
196
|
+
where = Dir.pwd.split('/').last
|
197
|
+
puts " Opening a shell to #{where}, please wait a moment...".light_blue
|
198
|
+
puts " STDOUT / STDERR will print to console when session is over.".light_blue
|
199
|
+
begin end until system "ssh -o LogLevel=quiet -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@#{@ip} -p #{@port}"
|
200
|
+
else
|
201
|
+
puts " Deployed and running #{@data['command']} #{@data['arguments']}".light_green
|
202
|
+
end
|
203
|
+
|
204
|
+
# need to wait for "task_finished" or "task_running & ssh" before we can ask for STDOUT/STDERR
|
205
|
+
begin
|
206
|
+
@taskState = RestClient.get "#{@uri}/api/history/task/#{@thisTask['taskId']['id']}"
|
207
|
+
@taskState = JSON.parse(@taskState)
|
208
|
+
@taskState["taskUpdates"].each do |update|
|
209
|
+
@taskState = update['taskState']
|
210
|
+
end
|
211
|
+
end until @taskState == "TASK_FINISHED" || (@taskState == "TASK_RUNNING" and @script == "ssh")
|
212
|
+
|
213
|
+
# output STDOUT / STDERR to shell
|
214
|
+
puts ""
|
215
|
+
stdout = RestClient.get "#{@uri}/api/sandbox/#{@thisTask['taskId']['id']}/read", {params: {path: "stdout", length: 30000, offset: 0}}
|
216
|
+
stdout = JSON.parse(stdout)
|
217
|
+
puts "stdout: ".cyan
|
218
|
+
puts stdout['data'].light_cyan
|
219
|
+
stderr = RestClient.get "#{@uri}/api/sandbox/#{@thisTask['taskId']['id']}/read", {params: {path: "stderr", length: 30000, offset: 0}}
|
220
|
+
stderr = JSON.parse(stderr)
|
221
|
+
puts "stderr: ".red
|
222
|
+
puts stderr['data'].light_magenta
|
86
223
|
|
224
|
+
# finally, delete the request (which also deletes the corresponding task)
|
225
|
+
RestClient.delete "#{@uri}/api/requests/request/#{@data['requestId']}"
|
226
|
+
|
227
|
+
rescue Exception => e
|
228
|
+
puts " #{e.response}".red
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
87
232
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: singularity-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Travis Webb
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-10-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rest-client
|