strongspace 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/bin/strongspace +14 -0
- data/lib/strongspace/client.rb +136 -0
- data/lib/strongspace/command.rb +85 -0
- data/lib/strongspace/commands/auth.rb +143 -0
- data/lib/strongspace/commands/base.rb +16 -0
- data/lib/strongspace/commands/help.rb +84 -0
- data/lib/strongspace/commands/keys.rb +50 -0
- data/lib/strongspace/commands/spaces.rb +65 -0
- data/lib/strongspace/commands/version.rb +7 -0
- data/lib/strongspace/helpers.rb +86 -0
- data/lib/strongspace/version.rb +3 -0
- data/lib/strongspace.rb +3 -0
- metadata +171 -0
data/bin/strongspace
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'strongspace'
|
7
|
+
require 'strongspace/command'
|
8
|
+
|
9
|
+
args = ARGV.dup
|
10
|
+
ARGV.clear
|
11
|
+
command = args.shift.strip rescue 'help'
|
12
|
+
|
13
|
+
Strongspace::Command.run(command, args)
|
14
|
+
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'uri'
|
3
|
+
require 'strongspace/version'
|
4
|
+
require 'json/pure' unless {}.respond_to?(:to_json)
|
5
|
+
|
6
|
+
# A Ruby class to call the Strongspace REST API. You might use this if you want to
|
7
|
+
# manage your Strongspace apps from within a Ruby program, such as Capistrano.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# require 'strongspace'
|
12
|
+
# strongspace = Strongspace::Client.new('me@example.com', 'mypass')
|
13
|
+
#
|
14
|
+
class Strongspace::Client
|
15
|
+
def self.version
|
16
|
+
Strongspace::VERSION
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.gem_version_string
|
20
|
+
"strongspace-gem/#{version}"
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_accessor :host, :user, :password
|
24
|
+
|
25
|
+
def self.auth(user, password, host='https://www.strongspace.com')
|
26
|
+
client = new(user, password, host)
|
27
|
+
JSON.parse client.get('/api/v1/api_token', :username => user, :password => password).to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def username
|
31
|
+
self.user.split("/")[0]
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(user, password, host='https://www.strongspace.com')
|
35
|
+
@user = user
|
36
|
+
@password = password
|
37
|
+
@host = host
|
38
|
+
end
|
39
|
+
|
40
|
+
def spaces
|
41
|
+
doc = JSON.parse get('/api/v1/spaces')
|
42
|
+
end
|
43
|
+
|
44
|
+
def destroy_space(space_name)
|
45
|
+
doc = JSON.parse delete("/api/v1/spaces/#{escape(space_name)}").to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def create_space(name, type='normal')
|
49
|
+
doc = JSON.parse post("/api/v1/spaces", :name => name, :type => type)
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_space(space_name)
|
53
|
+
doc = JSON.parse get("/api/v1/spaces/#{escape(space_name)}")
|
54
|
+
end
|
55
|
+
|
56
|
+
def snapshots(space_name)
|
57
|
+
doc = JSON.parse get("/api/v1/spaces/#{escape(space_name)}/snapshots").to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
def destroy_snapshot(space_name, snapshot_name)
|
61
|
+
doc = JSON.parse delete("/api/v1/spaces/#{escape(space_name)}/snapshots/#{escape(snapshot_name)}").to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_snapshot(space_name, snapshot_name)
|
65
|
+
doc = JSON.parse post("/api/v1/spaces/#{escape(space_name)}/snapshots", :name => snapshot_name)
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
# Get the list of ssh public keys for the current user.
|
70
|
+
def keys
|
71
|
+
doc = JSON.parse get('/api/v1/ssh_keys')
|
72
|
+
end
|
73
|
+
|
74
|
+
# Add an ssh public key to the current user.
|
75
|
+
def add_key(key)
|
76
|
+
post("/api/v1/ssh_keys", :key => key).to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
# Remove an existing ssh public key from the current user.
|
80
|
+
def remove_key(key_id)
|
81
|
+
delete("/api/v1/ssh_keys/#{key_id}").to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
# Clear all keys on the current user.
|
85
|
+
def remove_all_keys
|
86
|
+
delete("/api/v1/ssh_keys").to_s
|
87
|
+
end
|
88
|
+
|
89
|
+
##################
|
90
|
+
|
91
|
+
def resource(uri)
|
92
|
+
RestClient.proxy = ENV['HTTP_PROXY'] || ENV['http_proxy']
|
93
|
+
if uri =~ /^https?/
|
94
|
+
RestClient::Resource.new(uri, user, password)
|
95
|
+
elsif host =~ /^https?/
|
96
|
+
RestClient::Resource.new(host, user, password)[uri]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def get(uri, extra_headers={}) # :nodoc:
|
101
|
+
process(:get, uri, extra_headers)
|
102
|
+
end
|
103
|
+
|
104
|
+
def post(uri, payload="", extra_headers={}) # :nodoc:
|
105
|
+
process(:post, uri, extra_headers, payload)
|
106
|
+
end
|
107
|
+
|
108
|
+
def put(uri, payload, extra_headers={}) # :nodoc:
|
109
|
+
process(:put, uri, extra_headers, payload)
|
110
|
+
end
|
111
|
+
|
112
|
+
def delete(uri, extra_headers={}) # :nodoc:
|
113
|
+
process(:delete, uri, extra_headers)
|
114
|
+
end
|
115
|
+
|
116
|
+
def process(method, uri, extra_headers={}, payload=nil)
|
117
|
+
headers = strongspace_headers.merge(extra_headers)
|
118
|
+
args = [method, payload, headers].compact
|
119
|
+
response = resource(uri).send(*args)
|
120
|
+
end
|
121
|
+
|
122
|
+
def escape(value) # :nodoc:
|
123
|
+
escaped = URI.escape(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
124
|
+
escaped.gsub('.', '%2E') # not covered by the previous URI.escape
|
125
|
+
end
|
126
|
+
|
127
|
+
def strongspace_headers # :nodoc:
|
128
|
+
{
|
129
|
+
'X-Strongspace-API-Version' => '1',
|
130
|
+
'User-Agent' => self.class.gem_version_string,
|
131
|
+
'X-Ruby-Version' => RUBY_VERSION,
|
132
|
+
'X-Ruby-Platform' => RUBY_PLATFORM
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'strongspace/helpers'
|
2
|
+
require 'strongspace/commands/base'
|
3
|
+
|
4
|
+
Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each { |c| require c }
|
5
|
+
|
6
|
+
module Strongspace
|
7
|
+
module Command
|
8
|
+
class InvalidCommand < RuntimeError; end
|
9
|
+
class CommandFailed < RuntimeError; end
|
10
|
+
|
11
|
+
extend Strongspace::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 'strongspace 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
|
+
rescue RestClient::ResourceNotFound => e
|
29
|
+
error extract_not_found(e.http_body)
|
30
|
+
rescue RestClient::RequestFailed => e
|
31
|
+
error extract_error(e.http_body) unless e.http_code == 402
|
32
|
+
rescue RestClient::RequestTimeout
|
33
|
+
error "API request timed out. Please try again, or contact support@strongspace.com if this issue persists."
|
34
|
+
rescue CommandFailed => e
|
35
|
+
error e.message
|
36
|
+
rescue Interrupt => e
|
37
|
+
error "\n[canceled]"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_internal(command, args, strongspace=nil)
|
42
|
+
klass, method = parse(command)
|
43
|
+
runner = klass.new(args, strongspace)
|
44
|
+
raise InvalidCommand unless runner.respond_to?(method)
|
45
|
+
runner.send(method)
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse(command)
|
49
|
+
parts = command.split(':')
|
50
|
+
case parts.size
|
51
|
+
when 1
|
52
|
+
begin
|
53
|
+
return eval("Strongspace::Command::#{command.capitalize}"), :index
|
54
|
+
rescue NameError, NoMethodError
|
55
|
+
return Strongspace::Command::App, command.to_sym
|
56
|
+
end
|
57
|
+
else
|
58
|
+
begin
|
59
|
+
const = Strongspace::Command
|
60
|
+
command = parts.pop
|
61
|
+
parts.each { |part| const = const.const_get(part.capitalize) }
|
62
|
+
return const, command.to_sym
|
63
|
+
rescue NameError
|
64
|
+
raise InvalidCommand
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def extract_not_found(body)
|
70
|
+
body =~ /^[\w\s]+ not found$/ ? body : "Resource not found"
|
71
|
+
end
|
72
|
+
|
73
|
+
def extract_error(body)
|
74
|
+
msg = parse_error_json(body) || 'Internal server error'
|
75
|
+
msg.split("\n").map { |line| ' ! ' + line }.join("\n")
|
76
|
+
end
|
77
|
+
|
78
|
+
def parse_error_json(body)
|
79
|
+
json = JSON.parse(body.to_s)
|
80
|
+
json['status']
|
81
|
+
rescue JSON::ParserError
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Strongspace::Command
|
2
|
+
class Auth < Base
|
3
|
+
attr_accessor :credentials
|
4
|
+
|
5
|
+
def client
|
6
|
+
@client ||= init_strongspace
|
7
|
+
end
|
8
|
+
|
9
|
+
def init_strongspace
|
10
|
+
client = Strongspace::Client.new(user, password, host)
|
11
|
+
client
|
12
|
+
end
|
13
|
+
|
14
|
+
# just a stub; will raise if not authenticated
|
15
|
+
def check
|
16
|
+
client.spaces
|
17
|
+
end
|
18
|
+
|
19
|
+
def host
|
20
|
+
ENV['STRONGSPACE_HOST'] || 'https://www.strongspace.com'
|
21
|
+
end
|
22
|
+
|
23
|
+
def reauthorize
|
24
|
+
@credentials = ask_for_credentials
|
25
|
+
write_credentials
|
26
|
+
end
|
27
|
+
|
28
|
+
def user # :nodoc:
|
29
|
+
get_credentials
|
30
|
+
@credentials[0]
|
31
|
+
end
|
32
|
+
|
33
|
+
def password # :nodoc:
|
34
|
+
get_credentials
|
35
|
+
@credentials[1]
|
36
|
+
end
|
37
|
+
|
38
|
+
def credentials_file
|
39
|
+
"#{home_directory}/.strongspace/credentials"
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_credentials # :nodoc:
|
43
|
+
return if @credentials
|
44
|
+
unless @credentials = read_credentials
|
45
|
+
@credentials = ask_for_credentials
|
46
|
+
save_credentials
|
47
|
+
end
|
48
|
+
@credentials
|
49
|
+
end
|
50
|
+
|
51
|
+
def read_credentials
|
52
|
+
File.exists?(credentials_file) and File.read(credentials_file).split("\n")
|
53
|
+
end
|
54
|
+
|
55
|
+
def echo_off
|
56
|
+
system "stty -echo"
|
57
|
+
end
|
58
|
+
|
59
|
+
def echo_on
|
60
|
+
system "stty echo"
|
61
|
+
end
|
62
|
+
|
63
|
+
def ask_for_credentials
|
64
|
+
puts "Enter your Strongspace credentials."
|
65
|
+
|
66
|
+
print "Username or Email: "
|
67
|
+
user = ask
|
68
|
+
|
69
|
+
print "Password: "
|
70
|
+
password = running_on_windows? ? ask_for_password_on_windows : ask_for_password
|
71
|
+
|
72
|
+
["#{user}/token", Strongspace::Client.auth(user, password, host)['api_token']]
|
73
|
+
end
|
74
|
+
|
75
|
+
def ask_for_password_on_windows
|
76
|
+
require "Win32API"
|
77
|
+
char = nil
|
78
|
+
password = ''
|
79
|
+
|
80
|
+
while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
|
81
|
+
break if char == 10 || char == 13 # received carriage return or newline
|
82
|
+
if char == 127 || char == 8 # backspace and delete
|
83
|
+
password.slice!(-1, 1)
|
84
|
+
else
|
85
|
+
# windows might throw a -1 at us so make sure to handle RangeError
|
86
|
+
(password << char.chr) rescue RangeError
|
87
|
+
end
|
88
|
+
end
|
89
|
+
puts
|
90
|
+
return password
|
91
|
+
end
|
92
|
+
|
93
|
+
def ask_for_password
|
94
|
+
echo_off
|
95
|
+
password = ask
|
96
|
+
puts
|
97
|
+
echo_on
|
98
|
+
return password
|
99
|
+
end
|
100
|
+
|
101
|
+
def save_credentials
|
102
|
+
begin
|
103
|
+
write_credentials
|
104
|
+
command = 'auth:check'
|
105
|
+
Strongspace::Command.run_internal(command, args)
|
106
|
+
rescue RestClient::Unauthorized => e
|
107
|
+
delete_credentials
|
108
|
+
raise e unless retry_login?
|
109
|
+
|
110
|
+
display "\nAuthentication failed"
|
111
|
+
@credentials = ask_for_credentials
|
112
|
+
@client = init_strongspace
|
113
|
+
retry
|
114
|
+
rescue Exception => e
|
115
|
+
delete_credentials
|
116
|
+
raise e
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def retry_login?
|
121
|
+
@login_attempts ||= 0
|
122
|
+
@login_attempts += 1
|
123
|
+
@login_attempts < 3
|
124
|
+
end
|
125
|
+
|
126
|
+
def write_credentials
|
127
|
+
FileUtils.mkdir_p(File.dirname(credentials_file))
|
128
|
+
File.open(credentials_file, 'w') do |f|
|
129
|
+
f.puts self.credentials
|
130
|
+
end
|
131
|
+
set_credentials_permissions
|
132
|
+
end
|
133
|
+
|
134
|
+
def set_credentials_permissions
|
135
|
+
FileUtils.chmod 0700, File.dirname(credentials_file)
|
136
|
+
FileUtils.chmod 0600, credentials_file
|
137
|
+
end
|
138
|
+
|
139
|
+
def delete_credentials
|
140
|
+
FileUtils.rm_f(credentials_file)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Strongspace::Command
|
2
|
+
class Base
|
3
|
+
include Strongspace::Helpers
|
4
|
+
attr_accessor :args
|
5
|
+
def initialize(args, strongspace=nil)
|
6
|
+
@args = args
|
7
|
+
@strongspace = strongspace
|
8
|
+
end
|
9
|
+
|
10
|
+
def strongspace
|
11
|
+
@strongspace ||= Strongspace::Command.run_internal('auth:client', args)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Strongspace::Command
|
2
|
+
class Help < Base
|
3
|
+
class HelpGroup < Array
|
4
|
+
attr_reader :title
|
5
|
+
|
6
|
+
def initialize(title)
|
7
|
+
@title = title
|
8
|
+
end
|
9
|
+
|
10
|
+
def command(name, description)
|
11
|
+
self << [name, description]
|
12
|
+
end
|
13
|
+
|
14
|
+
def space
|
15
|
+
self << ['', '']
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.groups
|
20
|
+
@groups ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.group(title, &block)
|
24
|
+
groups << begin
|
25
|
+
group = HelpGroup.new(title)
|
26
|
+
yield group
|
27
|
+
group
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.create_default_groups!
|
32
|
+
group 'General Commands' do |group|
|
33
|
+
group.command 'help', 'show this usage'
|
34
|
+
group.command 'version', 'show the gem version'
|
35
|
+
group.space
|
36
|
+
group.space
|
37
|
+
group.command 'keys', 'show your user\'s public keys'
|
38
|
+
group.command 'keys:add [<path to keyfile>]', 'add a public key'
|
39
|
+
group.command 'keys:remove <id> ', 'remove a key by id'
|
40
|
+
group.command 'keys:clear', 'remove all keys'
|
41
|
+
group.space
|
42
|
+
group.space
|
43
|
+
group.command 'spaces', 'show your user\'s spaces'
|
44
|
+
group.command 'spaces:create <space_name> [type]', 'add a new space. type => (normal,public,backup)'
|
45
|
+
group.command 'spaces:destroy <space_name> [type]', 'remove a space by and destroy its data'
|
46
|
+
group.command 'spaces:snapshots <space_name>', 'show a space\'s snapshots'
|
47
|
+
group.command 'spaces:create_snapshot <space_name@snapshot_name>', 'take a space of a space.'
|
48
|
+
group.command 'spaces:destroy_snapshot <space_name@snapshot_name>', 'remove a snapshot from a space'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def index
|
53
|
+
display usage
|
54
|
+
end
|
55
|
+
|
56
|
+
def version
|
57
|
+
display Strongspace::Client.version
|
58
|
+
end
|
59
|
+
|
60
|
+
def usage
|
61
|
+
longest_command_length = self.class.groups.map do |group|
|
62
|
+
group.map { |g| g.first.length }
|
63
|
+
end.flatten.max
|
64
|
+
|
65
|
+
self.class.groups.inject(StringIO.new) do |output, group|
|
66
|
+
output.puts "=== %s" % group.title
|
67
|
+
output.puts
|
68
|
+
|
69
|
+
group.each do |command, description|
|
70
|
+
if command.empty?
|
71
|
+
output.puts
|
72
|
+
else
|
73
|
+
output.puts "%-*s # %s" % [longest_command_length, command, description]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
output.puts
|
78
|
+
output
|
79
|
+
end.string
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
Strongspace::Command::Help.create_default_groups!
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Strongspace::Command
|
2
|
+
class Keys < Base
|
3
|
+
def list
|
4
|
+
long = args.any? { |a| a == '--long' }
|
5
|
+
keys = strongspace.keys["ssh_keys"]
|
6
|
+
if keys.empty?
|
7
|
+
display "No keys for #{strongspace.username}"
|
8
|
+
else
|
9
|
+
display "=== #{keys.size} key#{'s' if keys.size > 1} for #{strongspace.username}"
|
10
|
+
keys.each do |key|
|
11
|
+
display long ? key["key"].strip : format_key_for_display(key["key"]) + " key-id: #{key["id"]}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
alias :index :list
|
16
|
+
|
17
|
+
def add
|
18
|
+
keyfile = args.first || find_key
|
19
|
+
key = File.read(keyfile)
|
20
|
+
|
21
|
+
display "Uploading ssh public key #{keyfile}"
|
22
|
+
|
23
|
+
strongspace.add_key(key)
|
24
|
+
end
|
25
|
+
|
26
|
+
def remove
|
27
|
+
strongspace.remove_key(args.first)
|
28
|
+
display "Key #{args.first} removed."
|
29
|
+
end
|
30
|
+
|
31
|
+
def clear
|
32
|
+
strongspace.remove_all_keys
|
33
|
+
display "All keys removed."
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
def find_key
|
38
|
+
%w(rsa dsa).each do |key_type|
|
39
|
+
keyfile = "#{home_directory}/.ssh/id_#{key_type}.pub"
|
40
|
+
return keyfile if File.exists? keyfile
|
41
|
+
end
|
42
|
+
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."
|
43
|
+
end
|
44
|
+
|
45
|
+
def format_key_for_display(key)
|
46
|
+
type, hex, local = key.strip.split(/\s/)
|
47
|
+
[type, hex[0,10] + '...' + hex[-10,10], local].join(' ')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Strongspace::Command
|
2
|
+
class Spaces < Base
|
3
|
+
def list
|
4
|
+
long = args.any? { |a| a == '--long' }
|
5
|
+
spaces = strongspace.spaces["spaces"]
|
6
|
+
|
7
|
+
if spaces.empty?
|
8
|
+
display "#{strongspace.username} has no spaces"
|
9
|
+
else
|
10
|
+
display "=== #{strongspace.username} has #{spaces.size} space#{'s' if spaces.size > 1}"
|
11
|
+
spaces.each do |space|
|
12
|
+
space = space["space"]
|
13
|
+
display "#{space['name']} [type: #{space['type']}, snapshots: #{space['snapshots']}]"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
alias :index :list
|
18
|
+
|
19
|
+
def create
|
20
|
+
name = args[0]
|
21
|
+
type = args[1]
|
22
|
+
|
23
|
+
strongspace.create_space(name, type)
|
24
|
+
display "Create space #{name}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def destroy
|
28
|
+
strongspace.destroy_space(args.first)
|
29
|
+
display "Space #{args.first} removed."
|
30
|
+
end
|
31
|
+
|
32
|
+
def snapshots
|
33
|
+
if args.length == 0
|
34
|
+
display "No space specified."
|
35
|
+
return
|
36
|
+
end
|
37
|
+
snapshots = strongspace.snapshots(args.first)["snapshots"]
|
38
|
+
|
39
|
+
if snapshots.empty?
|
40
|
+
display "Space #{args.first} has no snapshots"
|
41
|
+
else
|
42
|
+
display "=== Space #{args.first} has #{snapshots.size} snapshot#{'s' if snapshots.size > 1}"
|
43
|
+
snapshots.each do |snapshot|
|
44
|
+
snapshot = snapshot["snapshot"]
|
45
|
+
display "#{args.first}@#{snapshot['name']} [created: #{snapshot['created_at']}]"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_snapshot
|
51
|
+
space_name, snapshot_name = args[0].split("@")
|
52
|
+
|
53
|
+
strongspace.create_snapshot(space_name, snapshot_name)
|
54
|
+
display "Created snapshot '#{args[0]}'"
|
55
|
+
end
|
56
|
+
|
57
|
+
def destroy_snapshot
|
58
|
+
space_name, snapshot_name = args[0].split("@")
|
59
|
+
|
60
|
+
strongspace.destroy_snapshot(space_name, snapshot_name)
|
61
|
+
display "Destroyed snapshot '#{args.first}'"
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Strongspace
|
2
|
+
module Helpers
|
3
|
+
def home_directory
|
4
|
+
running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
|
5
|
+
end
|
6
|
+
|
7
|
+
def running_on_windows?
|
8
|
+
RUBY_PLATFORM =~ /mswin32|mingw32/
|
9
|
+
end
|
10
|
+
|
11
|
+
def running_on_a_mac?
|
12
|
+
RUBY_PLATFORM =~ /-darwin\d/
|
13
|
+
end
|
14
|
+
|
15
|
+
def display(msg, newline=true)
|
16
|
+
if newline
|
17
|
+
puts(msg)
|
18
|
+
else
|
19
|
+
print(msg)
|
20
|
+
STDOUT.flush
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def redisplay(line, line_break = false)
|
25
|
+
display("\r\e[0K#{line}", line_break)
|
26
|
+
end
|
27
|
+
|
28
|
+
def error(msg)
|
29
|
+
STDERR.puts(msg)
|
30
|
+
exit 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def confirm(message="Are you sure you wish to continue? (y/n)?")
|
34
|
+
display("#{message} ", false)
|
35
|
+
ask.downcase == 'y'
|
36
|
+
end
|
37
|
+
|
38
|
+
def confirm_command(app = app)
|
39
|
+
if extract_option('--force')
|
40
|
+
display("Warning: The --force switch is deprecated, and will be removed in a future release. Use --confirm #{app} instead.")
|
41
|
+
return true
|
42
|
+
end
|
43
|
+
|
44
|
+
raise(Strongspace::Command::CommandFailed, "No app specified.\nRun this command from app folder or set it adding --app <app name>") unless app
|
45
|
+
|
46
|
+
confirmed_app = extract_option('--confirm', false)
|
47
|
+
if confirmed_app
|
48
|
+
unless confirmed_app == app
|
49
|
+
raise(Strongspace::Command::CommandFailed, "Confirmed app #{confirmed_app} did not match the selected app #{app}.")
|
50
|
+
end
|
51
|
+
return true
|
52
|
+
else
|
53
|
+
display "\n ! Potentially Destructive Action"
|
54
|
+
display " ! To proceed, type \"#{app}\" or re-run this command with --confirm #{@app}"
|
55
|
+
display "> ", false
|
56
|
+
if ask.downcase != app
|
57
|
+
display " ! Input did not match #{app}. Aborted."
|
58
|
+
false
|
59
|
+
else
|
60
|
+
true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def format_date(date)
|
66
|
+
date = Time.parse(date) if date.is_a?(String)
|
67
|
+
date.strftime("%Y-%m-%d %H:%M %Z")
|
68
|
+
end
|
69
|
+
|
70
|
+
def ask
|
71
|
+
gets.strip
|
72
|
+
end
|
73
|
+
|
74
|
+
def shell(cmd)
|
75
|
+
FileUtils.cd(Dir.pwd) {|d| return `#{cmd}`}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
unless String.method_defined?(:shellescape)
|
81
|
+
class String
|
82
|
+
def shellescape
|
83
|
+
empty? ? "''" : gsub(/([^A-Za-z0-9_\-.,:\/@\n])/n, '\\\\\\1').gsub(/\n/, "'\n'")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/strongspace.rb
ADDED
metadata
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: strongspace
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Strongspace
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-12-06 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rake
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 27
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 3
|
47
|
+
- 0
|
48
|
+
version: 1.3.0
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: taps
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ~>
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 5
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
- 3
|
63
|
+
- 11
|
64
|
+
version: 0.3.11
|
65
|
+
type: :development
|
66
|
+
version_requirements: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: webmock
|
69
|
+
prerelease: false
|
70
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 3
|
76
|
+
segments:
|
77
|
+
- 1
|
78
|
+
- 5
|
79
|
+
- 0
|
80
|
+
version: 1.5.0
|
81
|
+
type: :development
|
82
|
+
version_requirements: *id004
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rest-client
|
85
|
+
prerelease: false
|
86
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - <
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 11
|
92
|
+
segments:
|
93
|
+
- 1
|
94
|
+
- 7
|
95
|
+
- 0
|
96
|
+
version: 1.7.0
|
97
|
+
type: :runtime
|
98
|
+
version_requirements: *id005
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: json_pure
|
101
|
+
prerelease: false
|
102
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - <
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 3
|
108
|
+
segments:
|
109
|
+
- 1
|
110
|
+
- 5
|
111
|
+
- 0
|
112
|
+
version: 1.5.0
|
113
|
+
type: :runtime
|
114
|
+
version_requirements: *id006
|
115
|
+
description: Client library and command line tool for Strongspace.
|
116
|
+
email: support@strongspace.com
|
117
|
+
executables:
|
118
|
+
- strongspace
|
119
|
+
extensions: []
|
120
|
+
|
121
|
+
extra_rdoc_files: []
|
122
|
+
|
123
|
+
files:
|
124
|
+
- bin/strongspace
|
125
|
+
- lib/strongspace/client.rb
|
126
|
+
- lib/strongspace/command.rb
|
127
|
+
- lib/strongspace/commands/auth.rb
|
128
|
+
- lib/strongspace/commands/base.rb
|
129
|
+
- lib/strongspace/commands/help.rb
|
130
|
+
- lib/strongspace/commands/keys.rb
|
131
|
+
- lib/strongspace/commands/spaces.rb
|
132
|
+
- lib/strongspace/commands/version.rb
|
133
|
+
- lib/strongspace/helpers.rb
|
134
|
+
- lib/strongspace/version.rb
|
135
|
+
- lib/strongspace.rb
|
136
|
+
has_rdoc: true
|
137
|
+
homepage: http://github.com/expandrive/strongspace
|
138
|
+
licenses: []
|
139
|
+
|
140
|
+
post_install_message:
|
141
|
+
rdoc_options: []
|
142
|
+
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
none: false
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
hash: 3
|
151
|
+
segments:
|
152
|
+
- 0
|
153
|
+
version: "0"
|
154
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
|
+
none: false
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
hash: 3
|
160
|
+
segments:
|
161
|
+
- 0
|
162
|
+
version: "0"
|
163
|
+
requirements: []
|
164
|
+
|
165
|
+
rubyforge_project:
|
166
|
+
rubygems_version: 1.3.7
|
167
|
+
signing_key:
|
168
|
+
specification_version: 3
|
169
|
+
summary: Client library and CLI for Strongspace.
|
170
|
+
test_files: []
|
171
|
+
|