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.
@@ -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
@@ -1,5 +1,5 @@
1
- class Ubalo
1
+ module Ubalo
2
2
  unless const_defined?('VERSION')
3
- VERSION = "0.1"
3
+ VERSION = "0.2"
4
4
  end
5
5
  end