strongspace 0.1.1 → 0.2.0
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/as_installed/ss +19 -0
- data/bin/as_installed/strongspace +20 -0
- data/lib/strongspace.rb +22 -1
- data/lib/strongspace/client.rb +16 -7
- data/lib/strongspace/command.rb +6 -8
- data/lib/strongspace/commands/auth.rb +40 -3
- data/lib/strongspace/commands/base.rb +0 -4
- data/lib/strongspace/commands/help.rb +2 -2
- data/lib/strongspace/commands/keys.rb +34 -1
- data/lib/strongspace/commands/spaces.rb +147 -8
- data/lib/strongspace/exceptions.rb +17 -0
- data/lib/strongspace/helpers.rb +53 -14
- data/lib/strongspace/plugin.rb +19 -3
- data/lib/strongspace/version.rb +1 -1
- data/spec/auth_spec.rb +19 -0
- data/spec/base.rb +1 -3
- data/spec/client_spec.rb +24 -5
- metadata +24 -22
data/bin/as_installed/ss
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
|
2
|
+
#
|
3
|
+
# This file was generated by RubyGems.
|
4
|
+
#
|
5
|
+
# The application 'strongspace' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
|
11
|
+
version = ">= 0"
|
12
|
+
|
13
|
+
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
|
14
|
+
version = $1
|
15
|
+
ARGV.shift
|
16
|
+
end
|
17
|
+
|
18
|
+
gem 'strongspace', version
|
19
|
+
load 'strongspace'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
|
2
|
+
#
|
3
|
+
# This file was generated by RubyGems.
|
4
|
+
#
|
5
|
+
# The application 'strongspace' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
|
11
|
+
version = ">= 0"
|
12
|
+
|
13
|
+
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
|
14
|
+
version = $1
|
15
|
+
ARGV.shift
|
16
|
+
end
|
17
|
+
|
18
|
+
gem 'strongspace', version
|
19
|
+
load 'strongspace'
|
20
|
+
|
data/lib/strongspace.rb
CHANGED
@@ -1,3 +1,24 @@
|
|
1
1
|
module Strongspace; end
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'rest_client'
|
4
|
+
require 'uri'
|
5
|
+
require 'json/pure' unless {}.respond_to?(:to_json)
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
STRONGSPACE_LIB_PATH = File.dirname(__FILE__) + "/strongspace/"
|
9
|
+
|
10
|
+
[
|
11
|
+
"version",
|
12
|
+
"exceptions",
|
13
|
+
"client",
|
14
|
+
"helpers",
|
15
|
+
"plugin_interface",
|
16
|
+
"plugin",
|
17
|
+
"command",
|
18
|
+
"commands/base"
|
19
|
+
].each do |library|
|
20
|
+
require STRONGSPACE_LIB_PATH + library
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
Dir["#{STRONGSPACE_LIB_PATH}/commands/*.rb"].each { |c| require c }
|
data/lib/strongspace/client.rb
CHANGED
@@ -1,8 +1,3 @@
|
|
1
|
-
require 'rest_client'
|
2
|
-
require 'uri'
|
3
|
-
require 'strongspace/version'
|
4
|
-
require 'json/pure' unless {}.respond_to?(:to_json)
|
5
|
-
|
6
1
|
# A Ruby class to call the Strongspace REST API. You might use this if you want to
|
7
2
|
# manage your Strongspace apps from within a Ruby program, such as Capistrano.
|
8
3
|
#
|
@@ -23,14 +18,26 @@ class Strongspace::Client
|
|
23
18
|
attr_accessor :host, :user, :password
|
24
19
|
|
25
20
|
def self.auth(user, password, host='https://www.strongspace.com')
|
26
|
-
|
27
|
-
|
21
|
+
begin
|
22
|
+
client = new(user, password, host)
|
23
|
+
return JSON.parse client.get('/api/v1/api_token', :username => user, :password => password).to_s
|
24
|
+
rescue RestClient::Request::Unauthorized => e
|
25
|
+
raise Strongspace::Exceptions::InvalidCredentials
|
26
|
+
rescue SocketError => e
|
27
|
+
raise Strongspace::Exceptions::NoConnection
|
28
|
+
end
|
28
29
|
end
|
29
30
|
|
30
31
|
def username
|
32
|
+
return nil if !user
|
33
|
+
|
31
34
|
self.user.split("/")[0]
|
32
35
|
end
|
33
36
|
|
37
|
+
def login_token
|
38
|
+
doc = JSON.parse get('/api/v1/login_token')
|
39
|
+
end
|
40
|
+
|
34
41
|
def initialize(user, password, host='https://www.strongspace.com')
|
35
42
|
@user = user
|
36
43
|
@password = password
|
@@ -73,6 +80,7 @@ class Strongspace::Client
|
|
73
80
|
|
74
81
|
def snapshots(space_name)
|
75
82
|
doc = JSON.parse get("/api/v1/spaces/#{escape(space_name)}/snapshots").to_s
|
83
|
+
doc["snapshots"]
|
76
84
|
end
|
77
85
|
|
78
86
|
def delete_snapshot(space_name, snapshot_name)
|
@@ -87,6 +95,7 @@ class Strongspace::Client
|
|
87
95
|
# Get the list of ssh public keys for the current user.
|
88
96
|
def keys
|
89
97
|
doc = JSON.parse get('/api/v1/ssh_keys')
|
98
|
+
doc["ssh_keys"]
|
90
99
|
end
|
91
100
|
|
92
101
|
# Add an ssh public key to the current user.
|
data/lib/strongspace/command.rb
CHANGED
@@ -1,9 +1,3 @@
|
|
1
|
-
require 'strongspace/helpers'
|
2
|
-
require 'strongspace/plugin'
|
3
|
-
require 'strongspace/commands/base'
|
4
|
-
|
5
|
-
Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each { |c| require c }
|
6
|
-
|
7
1
|
module Strongspace
|
8
2
|
module Command
|
9
3
|
class InvalidCommand < RuntimeError; end
|
@@ -21,7 +15,7 @@ module Strongspace
|
|
21
15
|
raise InvalidCommand
|
22
16
|
end
|
23
17
|
|
24
|
-
run_internal 'auth:
|
18
|
+
run_internal 'auth:reauthorize_interactve', args.dup if retries > 0
|
25
19
|
run_internal(command, args.dup)
|
26
20
|
rescue InvalidCommand
|
27
21
|
error "Unknown command. Run 'strongspace help' for usage information."
|
@@ -30,7 +24,7 @@ module Strongspace
|
|
30
24
|
STDERR.puts "Authentication failure"
|
31
25
|
run(command, args, retries+1)
|
32
26
|
else
|
33
|
-
error "Authentication failure"
|
27
|
+
error "! Authentication failure"
|
34
28
|
end
|
35
29
|
rescue RestClient::ResourceNotFound => e
|
36
30
|
error extract_not_found(e.http_body)
|
@@ -46,6 +40,9 @@ module Strongspace
|
|
46
40
|
end
|
47
41
|
|
48
42
|
def run_internal(command, args, strongspace=nil)
|
43
|
+
if command == "web:start"
|
44
|
+
require 'strongspace-web'
|
45
|
+
end
|
49
46
|
klass, method = parse(command)
|
50
47
|
runner = klass.new(args, strongspace)
|
51
48
|
raise InvalidCommand unless runner.respond_to?(method)
|
@@ -90,3 +87,4 @@ module Strongspace
|
|
90
87
|
end
|
91
88
|
end
|
92
89
|
end
|
90
|
+
|
@@ -20,11 +20,34 @@ module Strongspace::Command
|
|
20
20
|
ENV['STRONGSPACE_HOST'] || 'https://www.strongspace.com'
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
23
|
+
def reauthorize_interactve
|
24
24
|
@credentials = ask_for_credentials
|
25
25
|
write_credentials
|
26
26
|
end
|
27
27
|
|
28
|
+
def authorize!
|
29
|
+
@credentials = [args.first, args[1]]
|
30
|
+
r = Strongspace::Client.auth(@credentials[0], @credentials[1])
|
31
|
+
if r
|
32
|
+
@credentials[0] = "#{@credentials[0]}/token"
|
33
|
+
@credentials[1] = r['api_token']
|
34
|
+
write_credentials
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
|
41
|
+
def authenticated_login
|
42
|
+
if args.blank?
|
43
|
+
url = "#{host}/login/#{client.login_token['login_token']}"
|
44
|
+
else
|
45
|
+
to = URI.escape(args[0][0..1]) + URI.escape(URI.escape(args[0][2..-1])).gsub('&', '%26')
|
46
|
+
url = "#{host}/login/#{client.login_token['login_token']}?to=#{to}"
|
47
|
+
end
|
48
|
+
`open "#{url}"`
|
49
|
+
end
|
50
|
+
|
28
51
|
def user # :nodoc:
|
29
52
|
get_credentials
|
30
53
|
@credentials[0]
|
@@ -36,7 +59,7 @@ module Strongspace::Command
|
|
36
59
|
end
|
37
60
|
|
38
61
|
def credentials_file
|
39
|
-
"#{
|
62
|
+
"#{credentials_folder}/credentials"
|
40
63
|
end
|
41
64
|
|
42
65
|
def get_credentials # :nodoc:
|
@@ -72,6 +95,15 @@ module Strongspace::Command
|
|
72
95
|
["#{user}/token", Strongspace::Client.auth(user, password, host)['api_token']]
|
73
96
|
end
|
74
97
|
|
98
|
+
def valid_saved_credentials?
|
99
|
+
if File.exists?(credentials_file)
|
100
|
+
credentials = read_credentials
|
101
|
+
r = Strongspace::Client.auth(credentials[0], credentials[1])
|
102
|
+
return !r.blank?
|
103
|
+
end
|
104
|
+
return false
|
105
|
+
end
|
106
|
+
|
75
107
|
def ask_for_password_on_windows
|
76
108
|
require "Win32API"
|
77
109
|
char = nil
|
@@ -124,7 +156,12 @@ module Strongspace::Command
|
|
124
156
|
end
|
125
157
|
|
126
158
|
def write_credentials
|
127
|
-
|
159
|
+
begin
|
160
|
+
FileUtils.mkdir_p(credentials_folder)
|
161
|
+
rescue Errno::EEXIST => e
|
162
|
+
|
163
|
+
end
|
164
|
+
|
128
165
|
File.open(credentials_file, 'w') do |f|
|
129
166
|
f.puts self.credentials
|
130
167
|
end
|
@@ -58,8 +58,8 @@ module Strongspace::Command
|
|
58
58
|
group.command 'spaces:create <name> [type]', 'add a new space. type => (normal,public,backup)'
|
59
59
|
group.command 'spaces:delete <name> [type]', 'remove a space by and destroy its data'
|
60
60
|
group.command 'spaces:snapshots <name>', 'show a space\'s snapshots'
|
61
|
-
group.command 'spaces:create_snapshot <name
|
62
|
-
group.command 'spaces:delete_snapshot <name
|
61
|
+
group.command 'spaces:create_snapshot <name> [snapshot_name]', 'take a space of a space - snapshot_name defaults to current date/time.'
|
62
|
+
group.command 'spaces:delete_snapshot <name> <snapshot_name>', 'remove a snapshot from a space'
|
63
63
|
end
|
64
64
|
|
65
65
|
group 'Plugins' do |group|
|
@@ -2,7 +2,7 @@ module Strongspace::Command
|
|
2
2
|
class Keys < Base
|
3
3
|
def list
|
4
4
|
long = args.any? { |a| a == '--long' }
|
5
|
-
keys = strongspace.keys
|
5
|
+
keys = strongspace.keys
|
6
6
|
if keys.empty?
|
7
7
|
display "No keys for #{strongspace.username}"
|
8
8
|
else
|
@@ -11,6 +11,7 @@ module Strongspace::Command
|
|
11
11
|
display long ? key["key"].strip : format_key_for_display(key["key"]) + " key-id: #{key["id"]}"
|
12
12
|
end
|
13
13
|
end
|
14
|
+
keys
|
14
15
|
end
|
15
16
|
alias :index :list
|
16
17
|
|
@@ -19,6 +20,25 @@ module Strongspace::Command
|
|
19
20
|
return ($? == 0)
|
20
21
|
end
|
21
22
|
|
23
|
+
def generate_for_gui
|
24
|
+
return unless running_on_a_mac?
|
25
|
+
FileUtils.mkdir "#{credentials_folder}" unless File.exist? "#{credentials_folder}"
|
26
|
+
|
27
|
+
|
28
|
+
File.open("#{credentials_folder}/known_hosts", "w") do |f|
|
29
|
+
f.write "*.strongspace.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArXBYAoHZWVzLfHNMlgteAbq20AaCVcE1qALqVjYZerIpa3rBjNlv2i/2O8ul3OmSfcQwQGPTnABLqz9cozAbxF01eDfqUiSABUDT6m1/lY1a0V7RGS46Y/KJMVbOb4mVpxDZOVwBQh/DYTu7R55vFc93lXpE+tZboqnuq+LvJIZDqzoGTHIUprRs3sNY8Xegnz+m68P+tV6iLkXMRk8Gh8/IIavN4mXYhWPVbCv6Gqo2XhiYVMrCqLZFKLG0W6uwWY/xOhUjWxKDZMlqhyU/YUsMB5BZc9/x0t+Sc82OL+Eh3IB5EUmmCWnhm/LKxjMIn2UNe48BQqwaU/gozVtVPQ==\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
if !File.exist? "#{credentials_folder}/#{hostname}.rsa"
|
34
|
+
`/usr/bin/ssh-keygen -f #{credentials_folder}/#{hostname}.rsa -b 2048 -C \" Strongspace App - #{hostname}\" -q -N ""` unless File.exist? "#{credentials_folder}/#{hostname}.rsa"
|
35
|
+
args[0] = "#{credentials_folder}/#{hostname}.rsa.pub"
|
36
|
+
begin
|
37
|
+
add
|
38
|
+
rescue RestClient::Conflict => e # Swallow errors if the key already exists on Strongspace
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
22
42
|
|
23
43
|
def add
|
24
44
|
keyfile = args.first || find_key
|
@@ -39,6 +59,19 @@ module Strongspace::Command
|
|
39
59
|
display "All keys removed."
|
40
60
|
end
|
41
61
|
|
62
|
+
def valid_key_gui?
|
63
|
+
return unless running_on_a_mac? and File.exist? "#{support_directory}/ssh"
|
64
|
+
|
65
|
+
ret = `ssh -o PreferredAuthentications=publickey -i "#{support_directory}/ssh/#{hostname}.rsa" #{strongspace.username}@#{strongspace.username}.strongspace.com 2>&1`
|
66
|
+
|
67
|
+
if ret.include? "Strongspace"
|
68
|
+
display "Valid key installed"
|
69
|
+
return true
|
70
|
+
end
|
71
|
+
display "No valid key installed"
|
72
|
+
return false
|
73
|
+
end
|
74
|
+
|
42
75
|
protected
|
43
76
|
def find_key
|
44
77
|
%w(rsa dsa).each do |key_type|
|
@@ -2,14 +2,13 @@ module Strongspace::Command
|
|
2
2
|
class Spaces < Base
|
3
3
|
def list
|
4
4
|
long = args.any? { |a| a == '--long' }
|
5
|
-
spaces = strongspace.spaces
|
5
|
+
spaces = strongspace.spaces
|
6
6
|
|
7
7
|
if spaces.empty?
|
8
8
|
display "#{strongspace.username} has no spaces"
|
9
9
|
else
|
10
10
|
display "=== #{strongspace.username} has #{spaces.size} space#{'s' if spaces.size > 1}"
|
11
11
|
spaces.each do |space|
|
12
|
-
space = space["space"]
|
13
12
|
display "#{space['name']} [type: #{space['type']}, snapshots: #{space['snapshots']}]"
|
14
13
|
end
|
15
14
|
end
|
@@ -34,31 +33,171 @@ module Strongspace::Command
|
|
34
33
|
display "No space specified."
|
35
34
|
return
|
36
35
|
end
|
37
|
-
snapshots = strongspace.snapshots(args.first)
|
36
|
+
snapshots = strongspace.snapshots(args.first)
|
38
37
|
|
39
38
|
if snapshots.empty?
|
40
39
|
display "Space #{args.first} has no snapshots"
|
41
40
|
else
|
42
41
|
display "=== Space #{args.first} has #{snapshots.size} snapshot#{'s' if snapshots.size > 1}"
|
43
42
|
snapshots.each do |snapshot|
|
44
|
-
snapshot = snapshot["snapshot"]
|
45
43
|
display "#{args.first}@#{snapshot['name']} [created: #{snapshot['created_at']}]"
|
46
44
|
end
|
47
45
|
end
|
48
46
|
end
|
49
47
|
|
50
48
|
def create_snapshot
|
51
|
-
space_name, snapshot_name = args[0]
|
49
|
+
space_name, snapshot_name = args[0..1]
|
50
|
+
|
51
|
+
if snapshot_name.blank?
|
52
|
+
snapshot_name = Time.now.strftime("%Y-%m-%d-%H%M%S")
|
53
|
+
end
|
52
54
|
|
53
55
|
strongspace.create_snapshot(space_name, snapshot_name)
|
54
|
-
display "Created snapshot '#{
|
56
|
+
display "Created snapshot '#{space_name}@#{snapshot_name}'"
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_snapshot_and_thin
|
60
|
+
|
61
|
+
retries = 0
|
62
|
+
success = false
|
63
|
+
while (!success and (retries < 5)) do
|
64
|
+
begin
|
65
|
+
create_snapshot
|
66
|
+
thin_snapshots
|
67
|
+
rescue SocketError => e
|
68
|
+
sleep(10)
|
69
|
+
retries = retries + 1
|
70
|
+
next
|
71
|
+
end
|
72
|
+
success = true
|
73
|
+
end
|
74
|
+
|
55
75
|
end
|
56
76
|
|
57
77
|
def delete_snapshot
|
58
|
-
space_name, snapshot_name = args[0]
|
78
|
+
space_name, snapshot_name = args[0..1]
|
59
79
|
|
60
80
|
strongspace.delete_snapshot(space_name, snapshot_name)
|
61
|
-
display "Destroyed snapshot '#{
|
81
|
+
display "Destroyed snapshot '#{space_name}@#{snapshot_name}'"
|
82
|
+
end
|
83
|
+
|
84
|
+
def thin_snapshots
|
85
|
+
snapshots = strongspace.snapshots(args.first)
|
86
|
+
|
87
|
+
keeplist = []
|
88
|
+
|
89
|
+
if snapshots.count < 24
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
snapshots.each do |s|
|
94
|
+
|
95
|
+
if Time.parse(s['created_at']) > (Time.now - 3600*24)
|
96
|
+
keeplist << s
|
97
|
+
next
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
(snapshots - keeplist).each do |k|
|
103
|
+
puts "Drop: " + k['name']
|
104
|
+
strongspace.delete_snapshot(args.first, k['name'])
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
def schedule_snapshots
|
111
|
+
space_name = args[0]
|
112
|
+
|
113
|
+
if running_on_a_mac?
|
114
|
+
plist = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
115
|
+
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN
|
116
|
+
http://www.apple.com/DTDs/PropertyList-1.0.dtd >
|
117
|
+
<plist version=\"1.0\">
|
118
|
+
<dict>
|
119
|
+
<key>Label</key>
|
120
|
+
<string>com.strongspace.Snapshots.#{space_name}</string>
|
121
|
+
<key>Program</key>
|
122
|
+
<string>#{support_directory}/gems/bin/strongspace</string>
|
123
|
+
<key>ProgramArguments</key>
|
124
|
+
<array>
|
125
|
+
<string>strongspace</string>
|
126
|
+
<string>spaces:create_snapshot_and_thin</string>
|
127
|
+
<string>#{space_name}</string>
|
128
|
+
</array>
|
129
|
+
<key>KeepAlive</key>
|
130
|
+
<false/>
|
131
|
+
<key>StartCalendarInterval</key>
|
132
|
+
<dict>
|
133
|
+
<key>Minute</key>
|
134
|
+
<integer>0</integer>
|
135
|
+
</dict>
|
136
|
+
<key>RunAtLoad</key>
|
137
|
+
<true/>
|
138
|
+
<key>StandardOutPath</key>
|
139
|
+
<string>#{log_file}</string>
|
140
|
+
<key>StandardErrorPath</key>
|
141
|
+
<string>#{log_file}</string>
|
142
|
+
<key>EnvironmentVariables</key>
|
143
|
+
<dict>
|
144
|
+
<key>GEM_PATH</key>
|
145
|
+
<string>#{support_directory}/gems</string>
|
146
|
+
<key>GEM_HOME</key>
|
147
|
+
<string>#{support_directory}/gems</string>
|
148
|
+
<key>RACK_ENV</key>
|
149
|
+
<string>production</string>
|
150
|
+
</dict>
|
151
|
+
|
152
|
+
</dict>
|
153
|
+
</plist>"
|
154
|
+
|
155
|
+
file = File.new(launchd_plist_file(space_name), "w+")
|
156
|
+
file.puts plist
|
157
|
+
file.close
|
158
|
+
|
159
|
+
r = `launchctl load -S aqua '#{launchd_plist_file(space_name)}'`
|
160
|
+
if r.strip.ends_with?("Already loaded")
|
161
|
+
error "This task is aready scheduled, unload before scheduling again"
|
162
|
+
return
|
163
|
+
end
|
164
|
+
display "Scheduled Snapshots of #{space_name}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def unschedule_snapshots
|
169
|
+
space_name = args[0]
|
170
|
+
|
171
|
+
if space_name.blank?
|
172
|
+
display "Please supply the name of a space"
|
173
|
+
return false
|
174
|
+
end
|
175
|
+
|
176
|
+
if running_on_windows?
|
177
|
+
error "Scheduling currently isn't supported on Windows"
|
178
|
+
return
|
179
|
+
end
|
180
|
+
|
181
|
+
if running_on_a_mac?
|
182
|
+
if File.exist? launchd_plist_file(space_name)
|
183
|
+
`launchctl unload '#{launchd_plist_file(space_name)}'`
|
184
|
+
FileUtils.rm(launchd_plist_file(space_name))
|
185
|
+
end
|
186
|
+
else # Assume we're running on linux/unix
|
187
|
+
CronEdit::Crontab.Remove "strongspace-snapshots-#{space_name}"
|
188
|
+
end
|
189
|
+
|
190
|
+
display "Unscheduled snapshotting of #{space_name}"
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
private
|
195
|
+
def launchd_plist_file(space_name)
|
196
|
+
"#{launchd_agents_folder}/com.strongspace.Snapshots.#{space_name}.plist"
|
197
|
+
end
|
198
|
+
|
199
|
+
def log_file
|
200
|
+
"#{logs_folder}/Strongspace.log"
|
62
201
|
end
|
63
202
|
|
64
203
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Strongspace
|
2
|
+
module Exceptions
|
3
|
+
class StrongspaceError < StandardError; end
|
4
|
+
|
5
|
+
class InvalidCredentials < StrongspaceError
|
6
|
+
def message
|
7
|
+
"Invalid Strongspace Credentials"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class NoConnection < StrongspaceError
|
12
|
+
def message
|
13
|
+
"Could not connect to Strongspace"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/strongspace/helpers.rb
CHANGED
@@ -5,32 +5,77 @@ module Strongspace
|
|
5
5
|
self.class.name.split("::").last
|
6
6
|
end
|
7
7
|
|
8
|
-
def home_directory
|
8
|
+
def self.home_directory
|
9
9
|
running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
|
10
10
|
end
|
11
11
|
|
12
|
+
def home_directory
|
13
|
+
return Strongspace::Helpers.home_directory
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.support_directory
|
17
|
+
running_on_windows? ? "#{home_directory}/Strongspace" : "#{home_directory}/Library/Strongspace"
|
18
|
+
end
|
19
|
+
|
20
|
+
def support_directory
|
21
|
+
return Strongspace::Helpers.support_directory
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.running_on_windows?
|
25
|
+
RUBY_PLATFORM =~ /mswin32|mingw32/
|
26
|
+
end
|
27
|
+
|
12
28
|
def running_on_windows?
|
13
29
|
RUBY_PLATFORM =~ /mswin32|mingw32/
|
14
30
|
end
|
15
31
|
|
32
|
+
def self.running_on_a_mac?
|
33
|
+
RUBY_PLATFORM =~ /-darwin\d/
|
34
|
+
end
|
35
|
+
|
16
36
|
def running_on_a_mac?
|
17
37
|
RUBY_PLATFORM =~ /-darwin\d/
|
18
38
|
end
|
19
39
|
|
40
|
+
def gui_ssh_key
|
41
|
+
"#{credentials_folder}/#{hostname}.rsa"
|
42
|
+
end
|
43
|
+
|
44
|
+
def hostname
|
45
|
+
@hostname ||= `hostname`.strip
|
46
|
+
|
47
|
+
if @hostname.include?(".local")
|
48
|
+
@hostname = @hostname.split(".")[0]
|
49
|
+
end
|
50
|
+
return @hostname
|
51
|
+
end
|
52
|
+
|
53
|
+
def credentials_folder
|
54
|
+
"#{support_directory}/credentials"
|
55
|
+
end
|
56
|
+
|
20
57
|
def pids_folder
|
21
|
-
"#{
|
58
|
+
"#{support_directory}/pids"
|
22
59
|
end
|
23
60
|
|
24
61
|
def plugins_folder
|
25
62
|
Strongspace::Plugin.directory
|
26
63
|
end
|
27
64
|
|
65
|
+
def logs_folder
|
66
|
+
if running_on_a_mac?
|
67
|
+
"#{home_directory}/Library/Logs/Strongspace"
|
68
|
+
else
|
69
|
+
"#{support_directory}/logs"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
28
73
|
def bin_folder
|
29
|
-
"#{
|
74
|
+
"#{support_directory}/bin"
|
30
75
|
end
|
31
76
|
|
32
77
|
def launchd_agents_folder
|
33
|
-
"#{
|
78
|
+
"#{support_directory}/LaunchAgents"
|
34
79
|
end
|
35
80
|
|
36
81
|
def pid_file_path(name)
|
@@ -58,7 +103,8 @@ module Strongspace
|
|
58
103
|
end
|
59
104
|
|
60
105
|
begin
|
61
|
-
# This process is running
|
106
|
+
# This process is running, Kill 0 is a no-op that only works
|
107
|
+
# if the process exists
|
62
108
|
Process.kill(0, existing_pid)
|
63
109
|
return true
|
64
110
|
rescue Errno::EPERM
|
@@ -165,24 +211,17 @@ module Strongspace
|
|
165
211
|
end
|
166
212
|
|
167
213
|
def space_exist?(name)
|
168
|
-
strongspace.spaces
|
214
|
+
strongspace.spaces.each do |space|
|
169
215
|
# TODO: clean up the json returned by the strongspace API requests to simplify this iteration
|
170
|
-
space = space["space"]
|
171
216
|
return true if space["name"] == name
|
172
217
|
end
|
173
218
|
return false
|
174
219
|
end
|
175
220
|
|
176
|
-
def valid_space_name?(name)
|
177
|
-
# For now, just make sure the space name is all "word characters," i.e. [0-9A-Za-z_]
|
178
|
-
return false if name =~ /\W/
|
179
|
-
return true
|
180
|
-
end
|
181
221
|
|
182
222
|
def backup_space?(name)
|
183
223
|
space = nil
|
184
|
-
strongspace.spaces
|
185
|
-
s = s["space"]
|
224
|
+
strongspace.spaces.each do |s|
|
186
225
|
if s["name"] == name then
|
187
226
|
space = s
|
188
227
|
break
|
data/lib/strongspace/plugin.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# based on the Rails Plugin
|
2
|
-
|
3
1
|
module Strongspace
|
4
2
|
class Plugin
|
5
3
|
class << self
|
@@ -9,7 +7,7 @@ module Strongspace
|
|
9
7
|
attr_reader :name, :uri
|
10
8
|
|
11
9
|
def self.directory
|
12
|
-
File.expand_path("#{
|
10
|
+
File.expand_path("#{support_directory}/plugins")
|
13
11
|
end
|
14
12
|
|
15
13
|
def self.list
|
@@ -19,6 +17,7 @@ module Strongspace
|
|
19
17
|
end
|
20
18
|
|
21
19
|
def self.load!
|
20
|
+
self.update_support_directory!
|
22
21
|
list.each do |plugin|
|
23
22
|
begin
|
24
23
|
load_plugin(plugin)
|
@@ -26,6 +25,14 @@ module Strongspace
|
|
26
25
|
display "Unable to load plugin: #{plugin}: #{e.message}"
|
27
26
|
end
|
28
27
|
end
|
28
|
+
self.load_default_gem_plugins
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.load_default_gem_plugins
|
32
|
+
begin
|
33
|
+
require 'strongspace-rsync'
|
34
|
+
rescue Exception => e
|
35
|
+
end
|
29
36
|
end
|
30
37
|
|
31
38
|
def self.load_plugin(plugin)
|
@@ -38,6 +45,15 @@ module Strongspace
|
|
38
45
|
FileUtils.rm_rf("#{self.directory}/#{plugin}")
|
39
46
|
end
|
40
47
|
|
48
|
+
def self.update_support_directory!
|
49
|
+
if running_on_a_mac?
|
50
|
+
# if File.exist?("#{home_directory}/.strongspace") and !File.exist?("#{support_directory}")
|
51
|
+
# FileUtils.mv("#{home_directory}/.strongspace", "#{support_directory}")
|
52
|
+
# end
|
53
|
+
|
54
|
+
FileUtils.mkdir_p(launchd_agents_folder) unless File.exist? launchd_agents_folder
|
55
|
+
end
|
56
|
+
end
|
41
57
|
|
42
58
|
def initialize(uri)
|
43
59
|
@uri = uri
|
data/lib/strongspace/version.rb
CHANGED
data/spec/auth_spec.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path("./base", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require "strongspace"
|
4
|
+
|
5
|
+
def prepare_command(klass)
|
6
|
+
command = klass.new(['--app', 'myapp'])
|
7
|
+
command.stub!(:args).and_return([])
|
8
|
+
command.stub!(:display)
|
9
|
+
command
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
describe Strongspace::Command::Auth do
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
end
|
data/spec/base.rb
CHANGED
@@ -8,9 +8,7 @@ require 'fileutils'
|
|
8
8
|
require 'tmpdir'
|
9
9
|
require 'webmock/rspec'
|
10
10
|
|
11
|
-
require 'strongspace
|
12
|
-
require 'strongspace/commands/base'
|
13
|
-
Dir["#{File.dirname(__FILE__)}/../lib/strongspace/commands/*"].each { |c| require c }
|
11
|
+
require 'strongspace'
|
14
12
|
|
15
13
|
include WebMock::API
|
16
14
|
|
data/spec/client_spec.rb
CHANGED
@@ -1,23 +1,42 @@
|
|
1
1
|
require File.expand_path("./base", File.dirname(__FILE__))
|
2
2
|
require "cgi"
|
3
|
-
require "strongspace
|
3
|
+
require "strongspace"
|
4
4
|
|
5
5
|
describe Strongspace::Client do
|
6
6
|
before do
|
7
7
|
@client = Strongspace::Client.new(nil, nil)
|
8
8
|
end
|
9
9
|
|
10
|
-
it "
|
10
|
+
it "should return the current version" do
|
11
|
+
Strongspace::Client.version.should == Strongspace::VERSION
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return a gem version string" do
|
15
|
+
Strongspace::Client.gem_version_string.should == "strongspace-gem/#{Strongspace::VERSION}"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return an API key hash for auth" do
|
11
19
|
api_token = { "api_key" => "abc" }
|
12
20
|
stub_request(:get, "https://foo:bar@www.strongspace.com/api/v1/api_token").to_return(:body => api_token.to_json)
|
13
21
|
Strongspace::Client.auth("foo", "bar").should == api_token
|
14
22
|
end
|
15
23
|
|
16
|
-
it "
|
24
|
+
it "should fail auth gracefully with a bad password" do
|
25
|
+
api_token = { "api_key" => "abc" }
|
26
|
+
stub_request(:get, "https://foo:bar@www.strongspace.com/api/v1/api_token").to_return(:body => api_token.to_json)
|
27
|
+
lambda {Strongspace::Client.auth("foo", "ba3r")}.should raise_error(WebMock::NetConnectNotAllowedError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should return nil for username and password" do
|
31
|
+
@client.username.should == nil
|
32
|
+
@client.password.should == nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should return an array of spaces" do
|
17
36
|
stub_api_request(:get, "/spaces").to_return(:body => <<-EOJSON)
|
18
|
-
{"spaces":[{"space":{"name":"a space", "snapshots":0, "type":"normal"}}, {"space":{"name":"diskimages", "snapshots":0, "type":"normal"}}]}
|
37
|
+
{"spaces" : [{"space":{"name":"a space", "snapshots":0, "type":"normal"}}, {"space":{"name":"diskimages", "snapshots":0, "type":"normal"}}]}
|
19
38
|
EOJSON
|
20
|
-
@client.spaces.should ==
|
39
|
+
@client.spaces.should == [{"space"=>{"name"=>"a space", "snapshots"=>0, "type"=>"normal"}}, {"space"=>{"name"=>"diskimages", "snapshots"=>0, "type"=>"normal"}}]
|
21
40
|
end
|
22
41
|
|
23
42
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strongspace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Strongspace
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-01-
|
18
|
+
date: 2011-01-30 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -91,39 +91,37 @@ dependencies:
|
|
91
91
|
type: :development
|
92
92
|
version_requirements: *id005
|
93
93
|
- !ruby/object:Gem::Dependency
|
94
|
-
name:
|
94
|
+
name: webmock
|
95
95
|
prerelease: false
|
96
96
|
requirement: &id006 !ruby/object:Gem::Requirement
|
97
97
|
none: false
|
98
98
|
requirements:
|
99
99
|
- - ~>
|
100
100
|
- !ruby/object:Gem::Version
|
101
|
-
hash:
|
101
|
+
hash: 3
|
102
102
|
segments:
|
103
|
+
- 1
|
104
|
+
- 5
|
103
105
|
- 0
|
104
|
-
|
105
|
-
- 11
|
106
|
-
version: 0.3.11
|
106
|
+
version: 1.5.0
|
107
107
|
type: :development
|
108
108
|
version_requirements: *id006
|
109
109
|
- !ruby/object:Gem::Dependency
|
110
|
-
name:
|
110
|
+
name: ruby-fsevent
|
111
111
|
prerelease: false
|
112
112
|
requirement: &id007 !ruby/object:Gem::Requirement
|
113
113
|
none: false
|
114
114
|
requirements:
|
115
|
-
- -
|
115
|
+
- - ">="
|
116
116
|
- !ruby/object:Gem::Version
|
117
117
|
hash: 3
|
118
118
|
segments:
|
119
|
-
- 1
|
120
|
-
- 5
|
121
119
|
- 0
|
122
|
-
version:
|
120
|
+
version: "0"
|
123
121
|
type: :development
|
124
122
|
version_requirements: *id007
|
125
123
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
124
|
+
name: sinatra
|
127
125
|
prerelease: false
|
128
126
|
requirement: &id008 !ruby/object:Gem::Requirement
|
129
127
|
none: false
|
@@ -137,7 +135,7 @@ dependencies:
|
|
137
135
|
type: :development
|
138
136
|
version_requirements: *id008
|
139
137
|
- !ruby/object:Gem::Dependency
|
140
|
-
name:
|
138
|
+
name: sinatra-reloader
|
141
139
|
prerelease: false
|
142
140
|
requirement: &id009 !ruby/object:Gem::Requirement
|
143
141
|
none: false
|
@@ -184,14 +182,14 @@ dependencies:
|
|
184
182
|
requirement: &id012 !ruby/object:Gem::Requirement
|
185
183
|
none: false
|
186
184
|
requirements:
|
187
|
-
- -
|
185
|
+
- - "="
|
188
186
|
- !ruby/object:Gem::Version
|
189
|
-
hash:
|
187
|
+
hash: 13
|
190
188
|
segments:
|
191
189
|
- 1
|
192
|
-
-
|
193
|
-
-
|
194
|
-
version: 1.
|
190
|
+
- 6
|
191
|
+
- 1
|
192
|
+
version: 1.6.1
|
195
193
|
type: :runtime
|
196
194
|
version_requirements: *id012
|
197
195
|
- !ruby/object:Gem::Dependency
|
@@ -220,6 +218,8 @@ extensions: []
|
|
220
218
|
extra_rdoc_files: []
|
221
219
|
|
222
220
|
files:
|
221
|
+
- bin/as_installed/ss
|
222
|
+
- bin/as_installed/strongspace
|
223
223
|
- bin/ss
|
224
224
|
- bin/strongspace
|
225
225
|
- lib/strongspace/client.rb
|
@@ -232,12 +232,14 @@ files:
|
|
232
232
|
- lib/strongspace/commands/plugins.rb
|
233
233
|
- lib/strongspace/commands/spaces.rb
|
234
234
|
- lib/strongspace/commands/version.rb
|
235
|
+
- lib/strongspace/exceptions.rb
|
235
236
|
- lib/strongspace/helpers.rb
|
236
237
|
- lib/strongspace/plugin.rb
|
237
238
|
- lib/strongspace/plugin_interface.rb
|
238
239
|
- lib/strongspace/version.rb
|
239
240
|
- lib/strongspace.rb
|
240
241
|
- README.markdown
|
242
|
+
- spec/auth_spec.rb
|
241
243
|
- spec/base.rb
|
242
244
|
- spec/client_spec.rb
|
243
245
|
has_rdoc: true
|