jiveapps 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/History.txt +4 -0
- data/Manifest.txt +30 -0
- data/PostInstall.txt +7 -0
- data/README.rdoc +48 -0
- data/Rakefile +68 -0
- data/app_generators/create/USAGE +5 -0
- data/app_generators/create/create_generator.rb +77 -0
- data/app_generators/create/templates/app.xml +17 -0
- data/app_generators/create/templates/canvas.html +4 -0
- data/app_generators/create/templates/hello.html +88 -0
- data/app_generators/create/templates/home.html +4 -0
- data/app_generators/create/templates/images/j-icon-jaf-48.png +0 -0
- data/app_generators/create/templates/javascripts/main.js +0 -0
- data/app_generators/create/templates/stylesheets/main.css +0 -0
- data/autotest/discover.rb +1 -0
- data/bin/jiveapps +14 -0
- data/lib/jiveapps.rb +7 -0
- data/lib/jiveapps/client.rb +179 -0
- data/lib/jiveapps/command.rb +68 -0
- data/lib/jiveapps/commands/app.rb +160 -0
- data/lib/jiveapps/commands/auth.rb +144 -0
- data/lib/jiveapps/commands/base.rb +19 -0
- data/lib/jiveapps/commands/help.rb +34 -0
- data/lib/jiveapps/commands/keys.rb +83 -0
- data/lib/jiveapps/helpers.rb +29 -0
- data/spec/client_spec.rb +121 -0
- data/spec/commands/app_spec.rb +59 -0
- data/spec/commands/keys_spec.rb +89 -0
- data/spec/spec_helper.rb +26 -0
- metadata +173 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'jiveapps/helpers'
|
2
|
+
require 'jiveapps/commands/base'
|
3
|
+
|
4
|
+
Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each { |c| require c }
|
5
|
+
|
6
|
+
module Jiveapps
|
7
|
+
module Command
|
8
|
+
class InvalidCommand < RuntimeError; end
|
9
|
+
class CommandFailed < RuntimeError; end
|
10
|
+
|
11
|
+
extend Jiveapps::Helpers
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def run(command, args, retries=0)
|
16
|
+
begin
|
17
|
+
run_internal 'auth:reauthorize', args.dup if retries > 0
|
18
|
+
run_internal(command, args.dup)
|
19
|
+
rescue InvalidCommand
|
20
|
+
error "Unknown command. Run 'jiveapps help' for usage information."
|
21
|
+
rescue RestClient::Unauthorized
|
22
|
+
if retries < 3
|
23
|
+
STDERR.puts "Authentication failure"
|
24
|
+
run(command, args, retries+1)
|
25
|
+
else
|
26
|
+
error "Authentication failure"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def run_internal(command, args)
|
32
|
+
klass, method = parse(command)
|
33
|
+
runner = klass.new(args)
|
34
|
+
raise InvalidCommand unless runner.respond_to?(method)
|
35
|
+
runner.send(method)
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse(command)
|
39
|
+
parts = command.split(':')
|
40
|
+
case parts.size
|
41
|
+
when 1
|
42
|
+
begin
|
43
|
+
return eval("Jiveapps::Command::#{command.capitalize}"), :index
|
44
|
+
rescue NameError, NoMethodError
|
45
|
+
return Jiveapps::Command::App, command
|
46
|
+
end
|
47
|
+
when 2
|
48
|
+
begin
|
49
|
+
return Jiveapps::Command.const_get(parts[0].capitalize), parts[1]
|
50
|
+
rescue NameError
|
51
|
+
raise InvalidCommand
|
52
|
+
end
|
53
|
+
else
|
54
|
+
raise InvalidCommand
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
### Helpers
|
60
|
+
|
61
|
+
def error(msg)
|
62
|
+
STDERR.puts(msg)
|
63
|
+
exit 1
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
module Jiveapps::Command
|
2
|
+
class App < Base
|
3
|
+
|
4
|
+
attr_reader :current_app
|
5
|
+
|
6
|
+
def list
|
7
|
+
formatted_list = jiveapps.list.map do |app|
|
8
|
+
" - " + app['name']
|
9
|
+
end
|
10
|
+
|
11
|
+
if formatted_list.size > 0
|
12
|
+
display "Your apps:"
|
13
|
+
display formatted_list
|
14
|
+
else
|
15
|
+
display "You have no apps."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def info
|
20
|
+
if app_name == nil
|
21
|
+
display "No app specified."
|
22
|
+
display "Run this command from app folder or set it by running: jiveapps info <app name>"
|
23
|
+
else
|
24
|
+
app = jiveapps.info(app_name)
|
25
|
+
if app == nil
|
26
|
+
display "App not found."
|
27
|
+
else
|
28
|
+
display "=== #{app['name']}"
|
29
|
+
display "Git URL: #{app['git_url']}"
|
30
|
+
display "App URL: #{app['app_url']}"
|
31
|
+
display "Sandbox Canvas URL: #{app['sandbox_canvas_url']}"
|
32
|
+
display "Sandbox Dashboard URL: #{app['sandbox_dashboard_url']}"
|
33
|
+
display "OAuth Consumer Key: #{app['oauth_consumer_key']}"
|
34
|
+
display "OAuth Consumer Secret: #{app['oauth_consumer_secret']}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def create
|
40
|
+
# check auth credentials and ssh key before generating app
|
41
|
+
Jiveapps::Command.run_internal('auth:check', [])
|
42
|
+
create_remote_app
|
43
|
+
generate_app
|
44
|
+
create_local_git_repo_and_push_to_remote
|
45
|
+
register_app
|
46
|
+
create_notify_user
|
47
|
+
end
|
48
|
+
|
49
|
+
def version
|
50
|
+
puts Jiveapps::Client.version
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def debug_mode?
|
56
|
+
return @debug_mode if @debug_mode.nil? == false
|
57
|
+
|
58
|
+
if args.include?('--debug')
|
59
|
+
args.delete('--debug')
|
60
|
+
@debug_mode = true
|
61
|
+
else
|
62
|
+
@debug_mode = false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_remote_app
|
67
|
+
debug "Creating remote app."
|
68
|
+
@current_app = jiveapps.create(app_name)
|
69
|
+
if @current_app["errors"]
|
70
|
+
if @current_app["errors"]["name"]
|
71
|
+
display "Error: Name #{@current_app["errors"]["name"]}"
|
72
|
+
end
|
73
|
+
@current_app = nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def generate_app
|
78
|
+
return unless current_app
|
79
|
+
debug "Generating local app."
|
80
|
+
|
81
|
+
require 'rubygems'
|
82
|
+
require 'rubigen'
|
83
|
+
require 'rubigen/scripts/generate'
|
84
|
+
RubiGen::Base.use_application_sources!
|
85
|
+
RubiGen::Scripts::Generate.new.run(@args, :generator => 'create')
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_local_git_repo_and_push_to_remote
|
89
|
+
return unless current_app
|
90
|
+
debug "Creating local git repo and pushing to remote."
|
91
|
+
|
92
|
+
Dir.chdir(File.join(Dir.pwd, app_name)) do
|
93
|
+
|
94
|
+
run("git init")
|
95
|
+
run("git add .")
|
96
|
+
run('git commit -m "initial commit"')
|
97
|
+
run("git remote add jiveapps #{current_app['git_url']}")
|
98
|
+
run("git push jiveapps master")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def mute_stdout
|
103
|
+
orig_stdout = $stdout
|
104
|
+
|
105
|
+
# redirect stdout to /dev/null
|
106
|
+
$stdout = File.new('/dev/null', 'w')
|
107
|
+
|
108
|
+
yield
|
109
|
+
|
110
|
+
# restore stdout
|
111
|
+
$stdout = orig_stdout
|
112
|
+
end
|
113
|
+
|
114
|
+
def register_app
|
115
|
+
return unless current_app
|
116
|
+
debug "Registering app."
|
117
|
+
|
118
|
+
@current_app = jiveapps.register(app_name)
|
119
|
+
end
|
120
|
+
|
121
|
+
def create_notify_user
|
122
|
+
return unless current_app
|
123
|
+
debug "Notifying user."
|
124
|
+
|
125
|
+
display ""
|
126
|
+
display ""
|
127
|
+
display ""
|
128
|
+
display "Congratulations, you have created a new Jive App!"
|
129
|
+
display "================================================="
|
130
|
+
display "Git URL: #{current_app['git_url']}"
|
131
|
+
display "App URL: #{current_app['app_url']}"
|
132
|
+
display "Sandbox Canvas URL: #{current_app['sandbox_canvas_url']}"
|
133
|
+
display "Sandbox Dashboard URL: #{current_app['sandbox_dashboard_url']}"
|
134
|
+
display "OAuth Consumer Key: #{current_app['oauth_consumer_key']}"
|
135
|
+
display "OAuth Consumer Secret: #{current_app['oauth_consumer_secret']}"
|
136
|
+
end
|
137
|
+
|
138
|
+
def app_name
|
139
|
+
args.first
|
140
|
+
end
|
141
|
+
|
142
|
+
def run(command)
|
143
|
+
if debug_mode?
|
144
|
+
puts "DEBUG: $ #{command}"
|
145
|
+
`#{command}`
|
146
|
+
elsif running_on_windows?
|
147
|
+
`#{command}` # TODO: figure out how to silence on Windows
|
148
|
+
else
|
149
|
+
`#{command} > /dev/null 2>&1` # silent
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def debug(msg)
|
154
|
+
if debug_mode?
|
155
|
+
puts "DEBUG: #{msg}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Jiveapps::Command
|
2
|
+
class Auth < Base
|
3
|
+
attr_accessor :credentials
|
4
|
+
|
5
|
+
def client
|
6
|
+
@client ||= init_jiveapps
|
7
|
+
end
|
8
|
+
|
9
|
+
def init_jiveapps
|
10
|
+
client = Jiveapps::Client.new(user, password, host)
|
11
|
+
client.on_warning { |msg| self.display("\n#{msg}\n\n") }
|
12
|
+
client
|
13
|
+
end
|
14
|
+
|
15
|
+
# just a stub; will raise if not authenticated
|
16
|
+
def check
|
17
|
+
client.list
|
18
|
+
end
|
19
|
+
|
20
|
+
def host
|
21
|
+
ENV['JIVEAPPS_HOST'] || Jiveapps::WEBHOST
|
22
|
+
end
|
23
|
+
|
24
|
+
def reauthorize
|
25
|
+
@credentials = ask_for_credentials
|
26
|
+
write_credentials
|
27
|
+
end
|
28
|
+
|
29
|
+
def user # :nodoc:
|
30
|
+
get_credentials
|
31
|
+
@credentials[0]
|
32
|
+
end
|
33
|
+
|
34
|
+
def password # :nodoc:
|
35
|
+
get_credentials
|
36
|
+
@credentials[1]
|
37
|
+
end
|
38
|
+
|
39
|
+
def credentials_file
|
40
|
+
"#{home_directory}/.jiveapps/credentials"
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_credentials # :nodoc:
|
44
|
+
return if @credentials
|
45
|
+
unless @credentials = read_credentials
|
46
|
+
@credentials = ask_for_credentials
|
47
|
+
save_credentials
|
48
|
+
end
|
49
|
+
@credentials
|
50
|
+
end
|
51
|
+
|
52
|
+
def read_credentials
|
53
|
+
File.exists?(credentials_file) and File.read(credentials_file).split("\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
def echo_off
|
57
|
+
system "stty -echo"
|
58
|
+
end
|
59
|
+
|
60
|
+
def echo_on
|
61
|
+
system "stty echo"
|
62
|
+
end
|
63
|
+
|
64
|
+
def ask_for_credentials
|
65
|
+
puts "Enter your Jiveapps credentials."
|
66
|
+
|
67
|
+
print "Username: "
|
68
|
+
user = ask
|
69
|
+
|
70
|
+
print "Password: "
|
71
|
+
password = running_on_windows? ? ask_for_password_on_windows : ask_for_password
|
72
|
+
|
73
|
+
[ user, password ]
|
74
|
+
end
|
75
|
+
|
76
|
+
def ask_for_password_on_windows
|
77
|
+
require "Win32API"
|
78
|
+
char = nil
|
79
|
+
password = ''
|
80
|
+
|
81
|
+
while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
|
82
|
+
break if char == 10 || char == 13 # received carriage return or newline
|
83
|
+
if char == 127 || char == 8 # backspace and delete
|
84
|
+
password.slice!(-1, 1)
|
85
|
+
else
|
86
|
+
# windows might throw a -1 at us so make sure to handle RangeError
|
87
|
+
(password << char.chr) rescue RangeError
|
88
|
+
end
|
89
|
+
end
|
90
|
+
puts
|
91
|
+
return password
|
92
|
+
end
|
93
|
+
|
94
|
+
def ask_for_password
|
95
|
+
echo_off
|
96
|
+
password = ask
|
97
|
+
puts
|
98
|
+
echo_on
|
99
|
+
return password
|
100
|
+
end
|
101
|
+
|
102
|
+
def save_credentials
|
103
|
+
begin
|
104
|
+
write_credentials
|
105
|
+
command = args.any? { |a| a == '--ignore-keys' } ? 'auth:check' : 'keys:add'
|
106
|
+
Jiveapps::Command.run_internal(command, args)
|
107
|
+
rescue RestClient::Unauthorized => e
|
108
|
+
delete_credentials
|
109
|
+
raise e unless retry_login?
|
110
|
+
|
111
|
+
display "\nAuthentication failed"
|
112
|
+
@credentials = ask_for_credentials
|
113
|
+
@client = init_jiveapps
|
114
|
+
retry
|
115
|
+
rescue Exception => e
|
116
|
+
delete_credentials
|
117
|
+
raise e
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def retry_login?
|
122
|
+
@login_attempts ||= 0
|
123
|
+
@login_attempts += 1
|
124
|
+
@login_attempts < 3
|
125
|
+
end
|
126
|
+
|
127
|
+
def write_credentials
|
128
|
+
FileUtils.mkdir_p(File.dirname(credentials_file))
|
129
|
+
File.open(credentials_file, 'w') do |f|
|
130
|
+
f.puts self.credentials
|
131
|
+
end
|
132
|
+
set_credentials_permissions
|
133
|
+
end
|
134
|
+
|
135
|
+
def set_credentials_permissions
|
136
|
+
FileUtils.chmod 0700, File.dirname(credentials_file)
|
137
|
+
FileUtils.chmod 0600, credentials_file
|
138
|
+
end
|
139
|
+
|
140
|
+
def delete_credentials
|
141
|
+
FileUtils.rm_f(credentials_file)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Jiveapps::Command
|
2
|
+
class Base
|
3
|
+
include Jiveapps::Helpers
|
4
|
+
|
5
|
+
attr_accessor :args
|
6
|
+
def initialize(args)
|
7
|
+
@args = args
|
8
|
+
end
|
9
|
+
|
10
|
+
def ask
|
11
|
+
gets.strip
|
12
|
+
end
|
13
|
+
|
14
|
+
def jiveapps
|
15
|
+
@jiveapps ||= Jiveapps::Command.run_internal('auth:client', args)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Jiveapps::Command
|
2
|
+
class Help < Base
|
3
|
+
|
4
|
+
def index
|
5
|
+
puts <<-eos
|
6
|
+
=== Summary
|
7
|
+
The "jiveapps" program is a command line tool for building and hosting Jive App front-ends.
|
8
|
+
|
9
|
+
=== General Commands
|
10
|
+
|
11
|
+
help # show this usage
|
12
|
+
|
13
|
+
list # list your apps
|
14
|
+
create <name> # create a new app
|
15
|
+
|
16
|
+
keys # show your user's public keys
|
17
|
+
keys:add [<path to keyfile>] # add a public key. optionally include path
|
18
|
+
keys:remove <keyname> # remove a key by name (user@host)
|
19
|
+
|
20
|
+
=== Simple Workflow Example:
|
21
|
+
|
22
|
+
$ jiveapps create myapp # create a new app named "myapp"
|
23
|
+
$ cd myapp # switch into app's directory
|
24
|
+
|
25
|
+
... develop your app ...
|
26
|
+
|
27
|
+
$ git add . # stage all files for commit
|
28
|
+
$ git commit -m "some updates to my app" # commit to your local git repository
|
29
|
+
$ git push jiveapps master # push updates to jive
|
30
|
+
eos
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Jiveapps::Command
|
2
|
+
class Keys < Base
|
3
|
+
|
4
|
+
# Lists uploaded SSH keys
|
5
|
+
def list
|
6
|
+
long = args.any? { |a| a == '--long' }
|
7
|
+
ssh_keys = jiveapps.keys
|
8
|
+
if ssh_keys.empty?
|
9
|
+
display "No keys for #{jiveapps.user}"
|
10
|
+
else
|
11
|
+
display "=== #{ssh_keys.size} key#{'s' if ssh_keys.size > 1} for #{jiveapps.user}"
|
12
|
+
ssh_keys.each do |ssh_key|
|
13
|
+
display long ? ssh_key['key'].strip : format_key_for_display(ssh_key['key'])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
alias :index :list
|
18
|
+
|
19
|
+
# Uploads an SSH Key
|
20
|
+
# - args.first can either be a path to a key file or be nil. if nil, looks in default paths
|
21
|
+
def add
|
22
|
+
keyfile = find_key(args.first)
|
23
|
+
key = File.read(keyfile)
|
24
|
+
|
25
|
+
display "Uploading ssh public key #{keyfile}"
|
26
|
+
jiveapps.add_key(key)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Remove an SSH key
|
30
|
+
# - args.first must be the name of the key
|
31
|
+
def remove
|
32
|
+
if args.first == nil
|
33
|
+
display "No key specified. Please specify key to remove, for example:\n$ jiveapps keys:remove name@host"
|
34
|
+
return
|
35
|
+
end
|
36
|
+
begin
|
37
|
+
jiveapps.remove_key(args.first)
|
38
|
+
display "Key #{args.first} removed."
|
39
|
+
rescue RestClient::ResourceNotFound
|
40
|
+
display "Key #{args.first} not found."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Check to see if this machine's SSH key (or the key passed in) has been registered with Jiveapps
|
45
|
+
def check
|
46
|
+
keyfile = find_key(args.first)
|
47
|
+
key = File.read(keyfile)
|
48
|
+
key_name = key.strip.split(/\s+/).last
|
49
|
+
|
50
|
+
uploaded_key_names = jiveapps.keys.map{|key| key['name']}
|
51
|
+
|
52
|
+
if uploaded_key_names.include?(key_name)
|
53
|
+
display "This machine's SSH key \"#{key_name}\" has been registered with Jive Apps."
|
54
|
+
else
|
55
|
+
display "This machine's SSH key \"#{key_name}\" has not been registered with Jive Apps."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
# Finds a key in the specified path or in the default locations (~/.ssh/id_(r|d)sa.pub)
|
61
|
+
def find_key(path=nil)
|
62
|
+
if !path.nil? && path.length > 0
|
63
|
+
return path if File.exists? path
|
64
|
+
raise CommandFailed, "No ssh public key found in #{path}."
|
65
|
+
else
|
66
|
+
%w(rsa dsa).each do |key_type|
|
67
|
+
keyfile = "#{home_directory}/.ssh/id_#{key_type}.pub"
|
68
|
+
return keyfile if File.exists? keyfile
|
69
|
+
end
|
70
|
+
raise CommandFailed, "No ssh public key found in #{home_directory}/.ssh/id_[rd]sa.pub. You may want to specify the full path to the keyfile or generate it with this command: ssh-keygen -t rsa"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Formats an SSH key for display by trimming out the middle
|
75
|
+
# Example Output:
|
76
|
+
# ssh-rsa AAAAB3NzaC...Fyoke4MQ== pablo@jive
|
77
|
+
def format_key_for_display(key)
|
78
|
+
type, hex, local = key.strip.split(/\s/)
|
79
|
+
[type, hex[0,10] + '...' + hex[-10,10], local].join(' ')
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|