ubalo 0.1 → 0.2
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/bin/ubalo +131 -215
- data/lib/ubalo/account.rb +93 -0
- data/lib/ubalo/pod.rb +99 -0
- data/lib/ubalo/pod_archive.rb +54 -0
- data/lib/ubalo/pod_dir.rb +81 -0
- data/lib/ubalo/task.rb +87 -0
- data/lib/ubalo/util.rb +189 -0
- data/lib/ubalo/version.rb +2 -2
- data/lib/ubalo.rb +9 -321
- metadata +24 -13
@@ -0,0 +1,54 @@
|
|
1
|
+
module Ubalo
|
2
|
+
class PodArchive
|
3
|
+
def self.create_with_json(account, json)
|
4
|
+
new(account, json.fetch('label'), json)
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :label, :put_url, :get_url
|
8
|
+
|
9
|
+
def initialize(account, label, attributes)
|
10
|
+
@account = account
|
11
|
+
@label = label
|
12
|
+
update_attributes(attributes)
|
13
|
+
end
|
14
|
+
|
15
|
+
def activate_from(pod_dir)
|
16
|
+
with_temp_archive do |archive_name|
|
17
|
+
pod_dir.make_archive(archive_name)
|
18
|
+
Util.put_targz(put_url, archive_name)
|
19
|
+
request(:put, "/activate")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def extract_to(pod_dir)
|
24
|
+
with_temp_archive do |archive_name|
|
25
|
+
Util.get_targz(get_url, archive_name)
|
26
|
+
pod_dir.extract_archive(archive_name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def inspect
|
31
|
+
"#<PodArchive #{label.inspect}>"
|
32
|
+
end
|
33
|
+
|
34
|
+
def ==(other)
|
35
|
+
label == other.label
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def update_attributes(attributes)
|
40
|
+
@get_url = attributes['get_url']
|
41
|
+
@put_url = attributes['put_url']
|
42
|
+
end
|
43
|
+
|
44
|
+
def request method, path = nil, options = {}
|
45
|
+
update_attributes(@account.request(method, "/archives/#{label}#{path}", options))
|
46
|
+
end
|
47
|
+
|
48
|
+
def with_temp_archive
|
49
|
+
Dir.mktmpdir do |dir|
|
50
|
+
yield File.join(dir, "ubalo-files.tar.gz")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'archive/tar/minitar'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module Ubalo
|
6
|
+
class PodDir
|
7
|
+
attr_reader :path
|
8
|
+
|
9
|
+
def initialize(path)
|
10
|
+
@path = path
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
in_path do
|
15
|
+
File.read("Ubalopod").strip
|
16
|
+
end
|
17
|
+
rescue Errno::ENOENT
|
18
|
+
raise Ubalo::Error, "Please run this command from a pod directory"
|
19
|
+
end
|
20
|
+
|
21
|
+
def write_name(name)
|
22
|
+
in_path do
|
23
|
+
File.open("Ubalopod", "w") do |f|
|
24
|
+
f.puts name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def manifest
|
30
|
+
in_path do
|
31
|
+
Dir.glob('{.**,**}').map do |path|
|
32
|
+
next if ignore_patterns.any? do |pattern|
|
33
|
+
File.fnmatch?(pattern, path, File::FNM_DOTMATCH)
|
34
|
+
end
|
35
|
+
path
|
36
|
+
end.compact
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def make_archive archive_name
|
41
|
+
in_path do
|
42
|
+
raise Ubalo::Error, "no files to push" if manifest.empty?
|
43
|
+
archive = Zlib::GzipWriter.new(File.open(archive_name, 'wb'))
|
44
|
+
Archive::Tar::Minitar.pack(manifest, archive)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def extract_archive archive_name
|
49
|
+
in_path do
|
50
|
+
archive = Zlib::GzipReader.new(File.open(archive_name, 'rb'))
|
51
|
+
Archive::Tar::Minitar.unpack(archive, ".")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def inspect
|
56
|
+
"#<PodDir path=#{path.inspect}>"
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def ignore_patterns
|
61
|
+
return @ignore_patterns if @ignore_patterns
|
62
|
+
|
63
|
+
ignore_filename = in_path{File.expand_path(".ubaloignore")}
|
64
|
+
if File.exist?(ignore_filename)
|
65
|
+
@ignore_patterns = File.read(ignore_filename).each_line.map(&:strip)
|
66
|
+
else
|
67
|
+
@ignore_patterns = []
|
68
|
+
end
|
69
|
+
|
70
|
+
@ignore_patterns += %w{. .. Ubalopod}
|
71
|
+
end
|
72
|
+
|
73
|
+
def in_path
|
74
|
+
epath = File.expand_path(path)
|
75
|
+
FileUtils.mkdir_p(epath) unless File.directory?(epath)
|
76
|
+
Dir.chdir(epath) do
|
77
|
+
yield
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/ubalo/task.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
module Ubalo
|
2
|
+
class Task
|
3
|
+
attr_reader :label, :state, :pod_name, :arg, :output, :submitted_at
|
4
|
+
|
5
|
+
def self.create_with_json(account, json)
|
6
|
+
new(account, json.fetch('label'), json)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(account, label, attributes)
|
10
|
+
@account = account
|
11
|
+
@label = label
|
12
|
+
update_attributes(attributes)
|
13
|
+
end
|
14
|
+
|
15
|
+
def refresh!
|
16
|
+
update_attributes(request(:get))
|
17
|
+
rescue RestClient::ResourceNotFound
|
18
|
+
raise Ubalo::Error, "Could not find task #{label.inspect}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def pending?
|
22
|
+
%w{waiting running}.include?(state)
|
23
|
+
end
|
24
|
+
|
25
|
+
def complete?
|
26
|
+
state == 'exited'
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
"#<Task #{label.inspect} state=#{state.inspect}>"
|
31
|
+
end
|
32
|
+
|
33
|
+
def ==(other)
|
34
|
+
label == other.label
|
35
|
+
end
|
36
|
+
|
37
|
+
def printable_result
|
38
|
+
s = ""
|
39
|
+
s << " label: #{label}\n"
|
40
|
+
s << " pod: #{pod_name}\n"
|
41
|
+
if arg
|
42
|
+
s << " arg:\n"
|
43
|
+
s << Util.indent(arg.pretty_inspect)
|
44
|
+
end
|
45
|
+
s << " state: #{state}\n"
|
46
|
+
|
47
|
+
if container_process
|
48
|
+
s << "status: #{container_process.fetch('exit_result')}\n"
|
49
|
+
|
50
|
+
if stdout = container_process.fetch('stdout') and stdout.length > 0
|
51
|
+
s << "stdout:\n"
|
52
|
+
s << Util.indent(stdout)
|
53
|
+
end
|
54
|
+
|
55
|
+
if stderr = container_process.fetch('stderr') and stderr.length > 0
|
56
|
+
s << "stderr:\n"
|
57
|
+
s << Util.indent(stderr)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
if output
|
62
|
+
s << "output:\n"
|
63
|
+
s << printable_task_content('output', output)
|
64
|
+
end
|
65
|
+
s
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
attr_reader :container_process
|
70
|
+
|
71
|
+
def update_attributes(attributes)
|
72
|
+
@state = attributes['state']
|
73
|
+
@arg = attributes['arg']
|
74
|
+
@pod_name = attributes['pod_name']
|
75
|
+
@container_process = attributes['container_process']
|
76
|
+
@submitted_at = attributes['submitted_at']
|
77
|
+
|
78
|
+
@arg = Util.decode_content(attributes['arg']) if attributes['arg']
|
79
|
+
@output = Util.decode_content(attributes['outputs']) if attributes['outputs']
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
def request method, path = nil, options = {}
|
84
|
+
@account.request(method, "/tasks/#{label}#{path}", options)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/ubalo/util.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Ubalo
|
4
|
+
module Util
|
5
|
+
class UbaloJSON
|
6
|
+
class << self
|
7
|
+
def content_type
|
8
|
+
"application/json; schema=ubalo-data"
|
9
|
+
end
|
10
|
+
|
11
|
+
def dump(obj)
|
12
|
+
JSON.dump('data' => obj)
|
13
|
+
end
|
14
|
+
|
15
|
+
def load(data)
|
16
|
+
JSON.load(data)['data']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Logger
|
22
|
+
def initialize
|
23
|
+
@incomplete_line = false
|
24
|
+
end
|
25
|
+
|
26
|
+
def print(str)
|
27
|
+
@incomplete_line = true
|
28
|
+
$stderr.print(str)
|
29
|
+
end
|
30
|
+
|
31
|
+
def puts(str)
|
32
|
+
@incomplete_line = false
|
33
|
+
$stderr.print(str + "\n")
|
34
|
+
end
|
35
|
+
|
36
|
+
def complete_line
|
37
|
+
$stderr.print("\n") if @incomplete_line
|
38
|
+
@incomplete_line = false
|
39
|
+
end
|
40
|
+
|
41
|
+
def poll_on(message)
|
42
|
+
self.print message
|
43
|
+
600.times do
|
44
|
+
if result = yield
|
45
|
+
self.puts " done."
|
46
|
+
return result
|
47
|
+
end
|
48
|
+
self.print '.'
|
49
|
+
sleep 0.5
|
50
|
+
end
|
51
|
+
|
52
|
+
self.puts
|
53
|
+
raise UbaloError, "timed-out polling on #{message}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class << self
|
58
|
+
def normalize_pod_name(default_username, pod_name)
|
59
|
+
if pod_name.include?("/")
|
60
|
+
username, pod_name = (pod_name or "").split '/', 2
|
61
|
+
else
|
62
|
+
username, pod_name = default_username, pod_name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def put_targz(url, archive_name)
|
67
|
+
response = RestClient.put(url, File.open(archive_name, 'rb'), :content_type => 'application/x-tar', :content_encoding => 'x-gzip')
|
68
|
+
|
69
|
+
unless response.code == 200
|
70
|
+
raise UbaloError, "could not upload pod files"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_targz(url, archive_name)
|
75
|
+
RestClient.get(url) do |response,*_|
|
76
|
+
if response.code == 200
|
77
|
+
File.open(archive_name, "wb") do |f|
|
78
|
+
f.write response
|
79
|
+
end
|
80
|
+
else
|
81
|
+
raise UbaloError, "failed to download pod files"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def http_request(method, url, options)
|
87
|
+
params = options.delete(:params) || {}
|
88
|
+
|
89
|
+
headers = {:accept => :json, 'X-Ubalo-Version' => Ubalo::VERSION}
|
90
|
+
|
91
|
+
if authorization = options.delete(:authorization)
|
92
|
+
headers.merge!(:authorization => authorization)
|
93
|
+
end
|
94
|
+
|
95
|
+
if debug_mode?
|
96
|
+
$stderr.puts "about to #{method.inspect} to #{url.inspect} with #{options.inspect}"
|
97
|
+
end
|
98
|
+
|
99
|
+
resource = RestClient::Resource.new url, :timeout => 60
|
100
|
+
case method
|
101
|
+
when :get
|
102
|
+
response = resource.get headers.merge(:params => params)
|
103
|
+
when :post
|
104
|
+
response = resource.post params, headers
|
105
|
+
when :put
|
106
|
+
response = resource.put params, headers
|
107
|
+
when :delete
|
108
|
+
response = resource.delete headers
|
109
|
+
else
|
110
|
+
raise "don't understand request method #{method.inspect}"
|
111
|
+
end
|
112
|
+
|
113
|
+
if options.delete(:parse) == false
|
114
|
+
response
|
115
|
+
else
|
116
|
+
JSON.load(response)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def pluralize count, word
|
121
|
+
if count == 1
|
122
|
+
"#{count} #{word}"
|
123
|
+
else
|
124
|
+
"#{count} #{word}s"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def time_ago_in_words time
|
129
|
+
seconds = Time.now - Time.parse(time)
|
130
|
+
case
|
131
|
+
when seconds < 60
|
132
|
+
"#{pluralize(seconds.floor, "second")} ago"
|
133
|
+
when seconds/60 < 60
|
134
|
+
"#{pluralize((seconds/60).floor, "minute")} ago"
|
135
|
+
when seconds/60/60 < 24
|
136
|
+
"#{pluralize((seconds/60/60).floor, "hour")} ago"
|
137
|
+
else
|
138
|
+
"#{pluralize((seconds/60/60/24).floor, "day")} ago"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def indent str, amount=2
|
143
|
+
if str
|
144
|
+
str.each_line.map do |l|
|
145
|
+
" "*amount + l
|
146
|
+
end.join
|
147
|
+
else
|
148
|
+
""
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def decode_content encoded
|
153
|
+
data = encoded.fetch('data')
|
154
|
+
content_type = encoded.fetch('content_type')
|
155
|
+
if content_type == UbaloJSON.content_type
|
156
|
+
s = UbaloJSON.load(data)
|
157
|
+
else
|
158
|
+
raise UbaloError, "cannot understand content of this type"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def read_config(filename)
|
163
|
+
if File.exists?(filename)
|
164
|
+
YAML.load_file(filename)
|
165
|
+
else
|
166
|
+
{}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def append_config(filename, key, key_config)
|
171
|
+
config = read_config(filename)
|
172
|
+
config[key] = key_config
|
173
|
+
write_config(filename, config)
|
174
|
+
end
|
175
|
+
|
176
|
+
def write_config(filename, config)
|
177
|
+
File.open(filename, 'w') do |f|
|
178
|
+
YAML.dump(config, f)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
def debug_mode?
|
184
|
+
ENV['UBALO_DEBUG'] == 'true'
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
data/lib/ubalo/version.rb
CHANGED