jiveapps 0.0.3
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/.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
|