pi 0.1.13

Sign up to get free protection for your applications and to get access to all the features.
data/lib/cli/config.rb ADDED
@@ -0,0 +1,96 @@
1
+ require "yaml"
2
+ require 'fileutils'
3
+
4
+ require 'rubygems'
5
+ require 'json/pure'
6
+
7
+ module PI::Cli
8
+ class Config
9
+
10
+ DEFAULT_TARGET = 'api.staging.samsungcloud.org'
11
+ DEFAULT_SUGGEST = 'samsungcloud.org'
12
+
13
+ TARGET_FILE = '~/.pi_target'
14
+ TOKEN_FILE = '~/.pi_token'
15
+
16
+ class << self
17
+ attr_accessor :colorize
18
+ attr_accessor :output
19
+ attr_accessor :trace
20
+ attr_reader :suggest_url
21
+
22
+ def target_url
23
+ target_file = File.expand_path(TARGET_FILE)
24
+ if File.exists? target_file
25
+ @target_url = lock_and_read(target_file).strip!
26
+ ha = @target_url.split('//')
27
+ ha.shift
28
+ @suggest_url = ha.join('.')
29
+ @suggest_url = DEFAULT_SUGGEST if @suggest_url.empty?
30
+ else
31
+ @target_url = DEFAULT_TARGET
32
+ @suggest_url = DEFAULT_SUGGEST
33
+ end
34
+ @target_url = "http://#{@target_url}" unless /^https?/ =~ @target_url
35
+ @target_url = @target_url.gsub(/\/+$/, '')
36
+ @target_url
37
+ end
38
+
39
+ def store_target(target_host)
40
+ target_file = File.expand_path(TARGET_FILE)
41
+ lock_and_write(target_file, target_host)
42
+ end
43
+
44
+ def all_tokens
45
+ token_file = File.expand_path(TOKEN_FILE)
46
+ return nil unless File.exists? token_file
47
+ contents = lock_and_read(token_file).strip
48
+ JSON.parse(contents)
49
+ end
50
+
51
+ alias :targets :all_tokens
52
+
53
+ def auth_token
54
+ return @token if @token
55
+ tokens = all_tokens
56
+ @token = tokens[target_url] if tokens
57
+ end
58
+
59
+ def remove_token_file
60
+ FileUtils.rm_f(File.expand_path(TOKEN_FILE))
61
+ end
62
+
63
+ def store_token(token)
64
+ tokens = all_tokens || {}
65
+ tokens[target_url] = token
66
+ token_file = File.expand_path(TOKEN_FILE)
67
+ lock_and_write(token_file, tokens.to_json)
68
+ end
69
+
70
+ def lock_and_read(file)
71
+ File.open(file, "r") {|f|
72
+ f.flock(File::LOCK_EX)
73
+ contents = f.read
74
+ f.flock(File::LOCK_UN)
75
+ contents
76
+ }
77
+ end
78
+
79
+ def lock_and_write(file, contents)
80
+ File.open(file, File::RDWR | File::CREAT, 0600) {|f|
81
+ f.flock(File::LOCK_EX)
82
+ f.rewind
83
+ f.puts contents
84
+ f.flush
85
+ f.truncate(f.pos)
86
+ f.flock(File::LOCK_UN)
87
+ }
88
+ end
89
+ end
90
+
91
+ def initialize(work_dir = Dir.pwd)
92
+ @work_dir = work_dir
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,118 @@
1
+ module PIExtensions
2
+
3
+ def say(message)
4
+ PI::Cli::Config.output.puts(message) if PI::Cli::Config.output
5
+ end
6
+
7
+ def header(message, filler = '-')
8
+ say "\n"
9
+ say message
10
+ say filler.to_s * message.size
11
+ end
12
+
13
+ def banner(message)
14
+ say "\n"
15
+ say message
16
+ end
17
+
18
+ def display(message, nl=true)
19
+ if nl
20
+ say message
21
+ else
22
+ if PI::Cli::Config.output
23
+ PI::Cli::Config.output.print(message)
24
+ PI::Cli::Config.output.flush
25
+ end
26
+ end
27
+ end
28
+
29
+ def clear(size=80)
30
+ return unless PI::Cli::Config.output
31
+ PI::Cli::Config.output.print("\r")
32
+ PI::Cli::Config.output.print(" " * size)
33
+ PI::Cli::Config.output.print("\r")
34
+ end
35
+
36
+ def err(message, prefix='Error: ')
37
+ raise PI::Cli::CliExit, "#{prefix}#{message}"
38
+ end
39
+
40
+ def quit(message = nil)
41
+ raise PI::Cli::GracefulExit, message
42
+ end
43
+
44
+ def blank?
45
+ self.to_s.blank?
46
+ end
47
+
48
+ def uptime_string(delta)
49
+ num_seconds = delta.to_i
50
+ days = num_seconds / (60 * 60 * 24);
51
+ num_seconds -= days * (60 * 60 * 24);
52
+ hours = num_seconds / (60 * 60);
53
+ num_seconds -= hours * (60 * 60);
54
+ minutes = num_seconds / 60;
55
+ num_seconds -= minutes * 60;
56
+ "#{days}d:#{hours}h:#{minutes}m:#{num_seconds}s"
57
+ end
58
+
59
+ def pretty_size(size, prec=1)
60
+ return 'NA' unless size
61
+ return "#{size}B" if size < 1024
62
+ return sprintf("%.#{prec}fK", size/1024.0) if size < (1024*1024)
63
+ return sprintf("%.#{prec}fM", size/(1024.0*1024.0)) if size < (1024*1024*1024)
64
+ return sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))
65
+ end
66
+
67
+ end
68
+
69
+ module PIStringExtensions
70
+
71
+ def red
72
+ colorize("\e[0m\e[31m")
73
+ end
74
+
75
+ def green
76
+ colorize("\e[0m\e[32m")
77
+ end
78
+
79
+ def yellow
80
+ colorize("\e[0m\e[33m")
81
+ end
82
+
83
+ def bold
84
+ colorize("\e[0m\e[1m")
85
+ end
86
+
87
+ def colorize(color_code)
88
+ if PI::Cli::Config.colorize
89
+ "#{color_code}#{self}\e[0m"
90
+ else
91
+ self
92
+ end
93
+ end
94
+
95
+ def blank?
96
+ self =~ /^\s*$/
97
+ end
98
+
99
+ def truncate(limit = 30)
100
+ return "" if self.blank?
101
+ etc = "..."
102
+ stripped = self.strip[0..limit]
103
+ if stripped.length > limit
104
+ stripped.gsub(/\s+?(\S+)?$/, "") + etc
105
+ else
106
+ stripped
107
+ end
108
+ end
109
+
110
+ end
111
+
112
+ class Object
113
+ include PIExtensions
114
+ end
115
+
116
+ class String
117
+ include PIStringExtensions
118
+ end
data/lib/cli/errors.rb ADDED
@@ -0,0 +1,19 @@
1
+ module PI::Cli
2
+
3
+ class CliError < StandardError
4
+ def self.error_code(code = nil)
5
+ define_method(:error_code) { code }
6
+ end
7
+ end
8
+
9
+ class UnknownCommand < CliError; error_code(100); end
10
+ class TargetMissing < CliError; error_code(102); end
11
+ class TargetInaccessible < CliError; error_code(103); end
12
+
13
+ class TargetError < CliError; error_code(201); end
14
+ class AuthError < TargetError; error_code(202); end
15
+
16
+ class CliExit < CliError; error_code(400); end
17
+ class GracefulExit < CliExit; error_code(401); end
18
+
19
+ end
data/lib/cli/runner.rb ADDED
@@ -0,0 +1,269 @@
1
+ require 'optparse'
2
+
3
+ require File.dirname(__FILE__) + '/usage'
4
+
5
+ class PI::Cli::Runner
6
+
7
+ attr_reader :namespace
8
+ attr_reader :action
9
+ attr_reader :args
10
+ attr_reader :options
11
+
12
+ def self.run(args)
13
+ new(args).run
14
+ end
15
+
16
+ def initialize(args=[])
17
+ @args = args
18
+ @options = { :colorize => true }
19
+ @exit_status = true
20
+ end
21
+
22
+ # Collect all the available options for all commands
23
+ # Some duplicates exists to capture all scenarios
24
+ def parse_options!
25
+ opts_parser = OptionParser.new do |opts|
26
+ opts.banner = "\nAvailable options:\n\n"
27
+ # generic tracing and debugging
28
+ opts.on('-t [TKEY]') { |tkey| @options[:trace] = tkey || true }
29
+ opts.on('--trace [TKEY]') { |tkey| @options[:trace] = tkey || true }
30
+ opts.on('-q', '--quiet') { @options[:quiet] = true }
31
+ opts.on('--verbose') { @options[:verbose] = true }
32
+ opts.on('--json') { @options[:json] = true }
33
+ opts.on('-v', '--version') { set_cmd(:misc, :version) }
34
+ opts.on('-h', '--help') { puts "#{command_usage}\n"; exit }
35
+ opts.on_tail('--options') { puts "#{opts}\n"; exit }
36
+ end
37
+ instances_delta_arg = check_instances_delta!
38
+ @args = opts_parser.parse!(@args)
39
+ @args.concat instances_delta_arg
40
+ self
41
+ end
42
+
43
+ def check_instances_delta!
44
+ return unless @args
45
+ instance_args = @args.select { |arg| /^[-]\d+$/ =~ arg } || []
46
+ @args.delete_if { |arg| instance_args.include? arg}
47
+ instance_args
48
+ end
49
+
50
+ def display_help
51
+ puts command_usage
52
+ exit
53
+ end
54
+
55
+ def set_cmd(namespace, action, args_range=0)
56
+ return if @help_only
57
+ unless args_range == "*" || args_range.is_a?(Range)
58
+ args_range = (args_range.to_i..args_range.to_i)
59
+ end
60
+
61
+ if args_range == "*" || args_range.include?(@args.size)
62
+ @namespace = namespace
63
+ @action = action
64
+ else
65
+ @exit_status = false
66
+ if @args.size > args_range.last
67
+ usage_error("Too many arguments for [#{action}]: %s" % [ @args[args_range.last..-1].map{|a| "'#{a}'"}.join(', ') ])
68
+ else
69
+ usage_error("Not enough arguments for [#{action}]")
70
+ end
71
+ end
72
+ end
73
+
74
+ def parse_command!
75
+ # just return if already set, happends with -v, -h
76
+ return if @namespace && @action
77
+
78
+ verb = @args.shift
79
+ case verb
80
+
81
+ ###############################################################################
82
+ # Users
83
+ ###############################################################################
84
+
85
+ when 'login'
86
+ usage('pi login [url]')
87
+ set_cmd(:user, :login, @args.size == 1 ? 1 : 0)
88
+
89
+ when 'logout'
90
+ usage('pi logout')
91
+ set_cmd(:user, :logout)
92
+
93
+ when 'info'
94
+ usage ('pi info')
95
+ set_cmd(:user, :info)
96
+
97
+ when 'user'
98
+ usage ('pi user')
99
+ set_cmd(:user, :user)
100
+
101
+ when 'targets'
102
+ usage ('pi targets')
103
+ set_cmd(:user, :targets)
104
+
105
+ when 'password'
106
+ usage ('pi password [newpassword]')
107
+ set_cmd(:user, :password, @args.size == 1 ? 1 : 0)
108
+
109
+ when 'github'
110
+ usage ('pi github ')
111
+ set_cmd(:user, :github)
112
+
113
+ when 'runtimes'
114
+ usage('pi runtimes')
115
+ set_cmd(:user, :runtimes)
116
+
117
+ when 'frameworks'
118
+ usage('pi frameworks')
119
+ set_cmd(:user, :frameworks)
120
+
121
+ when 'version', 'v'
122
+ usage('pi version')
123
+ set_cmd(:misc, :version)
124
+
125
+ when 'help'
126
+ display_help if @args.size == 0
127
+ @help_only = true
128
+ parse_command!
129
+
130
+ when 'options'
131
+ @args = @args.unshift('--options')
132
+ parse_options!
133
+
134
+ ###############################################################################
135
+ # Projects
136
+ ###############################################################################
137
+
138
+ when 'projects'
139
+ usage('pi projects')
140
+ set_cmd(:projects, :projects)
141
+
142
+ when 'create-project'
143
+ usage('pi create-project [projectname]')
144
+ set_cmd(:projects, :create_project, @args.size == 1 ? 1 : 0)
145
+
146
+ when 'delete-project'
147
+ usage('pi delete-project [projectname]')
148
+ set_cmd(:projects, :delete_project, @args.size == 1 ? 1 : 0)
149
+
150
+ when 'upload'
151
+ usage('pi upload [projectname]')
152
+ set_cmd(:projects, :upload, @args.size == 1 ? 1 : 0)
153
+
154
+ when 'project-events'
155
+ usage('pi project-events [projectname]')
156
+ set_cmd(:projects, :project_events, @args.size == 1 ? 1 : 0)
157
+
158
+ when 'project-tags'
159
+ usage('pi project-tags [projectname]')
160
+ set_cmd(:projects, :project_tags, @args.size == 1 ? 1 : 0)
161
+
162
+ when 'project-commits'
163
+ usage('pi project-commits [projectname]')
164
+ set_cmd(:projects, :project_commits, @args.size == 1 ? 1 : 0)
165
+
166
+ when 'project-apps'
167
+ usage('pi project-apps [projectname]')
168
+ set_cmd(:projects, :project_apps, @args.size == 1 ? 1 : 0)
169
+
170
+ when 'usage'
171
+ display basic_usage
172
+ exit(true)
173
+
174
+ else
175
+ if verb
176
+ display "pi: Unknown command [#{verb}]"
177
+ display basic_usage
178
+ exit(false)
179
+ end
180
+ end
181
+ end
182
+
183
+ def usage(msg = nil)
184
+ @usage = msg if msg
185
+ @usage
186
+ end
187
+
188
+ def usage_error(msg = nil)
189
+ @usage_error = msg if msg
190
+ @usage_error
191
+ end
192
+
193
+ def run
194
+
195
+ trap('TERM') { print "\nTerminated\n"; exit(false)}
196
+
197
+ parse_options!
198
+
199
+ @options[:colorize] = false unless STDOUT.tty?
200
+
201
+ PI::Cli::Config.colorize = @options.delete(:colorize)
202
+ PI::Cli::Config.trace = @options.delete(:trace)
203
+ PI::Cli::Config.output ||= STDOUT unless @options[:quiet]
204
+
205
+ parse_command!
206
+ if @namespace && @action
207
+ eval("PI::Cli::Command::#{@namespace.to_s.capitalize}").new(@options).send(@action.to_sym, *@args)
208
+ elsif @help_only || @usage
209
+ display_usage
210
+ else
211
+ display basic_usage
212
+ exit(false)
213
+ end
214
+
215
+ rescue OptionParser::InvalidOption => e
216
+ rescue OptionParser::AmbiguousOption => e
217
+ puts(e.message.red)
218
+ puts("\n")
219
+ puts(basic_usage)
220
+ @exit_status = false
221
+ rescue PI::Client::AuthError => e
222
+ if PI::Cli::Config.auth_token.nil?
223
+ puts "Login Required".red
224
+ else
225
+ puts "Not Authorized".red
226
+ end
227
+ @exit_status = false
228
+ rescue PI::Client::TargetError, PI::Client::NotFound, PI::Client::BadTarget => e
229
+ puts e.message.red
230
+ @exit_status = false
231
+ rescue PI::Client::HTTPException => e
232
+ puts e.message.red
233
+ @exit_status = false
234
+ rescue PI::Cli::GracefulExit => e
235
+ # Redirected commands end up generating this exception (kind of goto)
236
+ rescue PI::Cli::CliExit => e
237
+ puts e.message.red
238
+ @exit_status = false
239
+ rescue PI::Cli::CliError => e
240
+ say("Error #{e.error_code}: #{e.message}".red)
241
+ @exit_status = false
242
+ rescue SystemExit => e
243
+ @exit_status = e.success?
244
+ rescue SyntaxError => e
245
+ puts e.message.red
246
+ puts e.backtrace
247
+ @exit_status = false
248
+ rescue Interrupt => e
249
+ say("\nInterrupted".red)
250
+ @exit_status = false
251
+ rescue => e
252
+ puts e.message.red
253
+ puts e.backtrace
254
+ @exit_status = false
255
+ ensure
256
+ say("\n")
257
+ @exit_status == true if @exit_status.nil?
258
+ if @options[:verbose]
259
+ if @exit_status
260
+ puts "[#{@namespace}:#{@action}] SUCCEEDED".green
261
+ else
262
+ puts "[#{@namespace}:#{@action}] FAILED".red
263
+ end
264
+ say("\n")
265
+ end
266
+ exit(@exit_status)
267
+ end
268
+
269
+ end