shelly 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/shelly +2 -2
- data/lib/shelly.rb +1 -0
- data/lib/shelly/app.rb +39 -0
- data/lib/shelly/base.rb +13 -0
- data/lib/shelly/cli/account.rb +56 -0
- data/lib/shelly/cli/apps.rb +65 -0
- data/lib/shelly/cli/main.rb +25 -0
- data/lib/shelly/client.rb +12 -6
- data/lib/shelly/templates/Cloudfile.erb +20 -0
- data/lib/shelly/user.rb +18 -5
- data/lib/shelly/version.rb +1 -1
- data/shelly.gemspec +1 -0
- data/spec/shelly/app_spec.rb +93 -0
- data/spec/shelly/{cli_spec.rb → cli/account_spec.rb} +38 -23
- data/spec/shelly/cli/apps_spec.rb +128 -0
- data/spec/shelly/cli/main_spec.rb +37 -0
- data/spec/shelly/client_spec.rb +14 -4
- data/spec/shelly/user_spec.rb +43 -5
- data/spec/spec_helper.rb +1 -1
- metadata +32 -7
- data/lib/shelly/cli.rb +0 -46
data/bin/shelly
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require "shelly/cli"
|
3
|
-
Shelly::CLI.start
|
2
|
+
require "shelly/cli/main"
|
3
|
+
Shelly::CLI::Main.start
|
data/lib/shelly.rb
CHANGED
data/lib/shelly/app.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'launchy'
|
3
|
+
|
4
|
+
module Shelly
|
5
|
+
class App < Base
|
6
|
+
DATABASE_KINDS = %w(postgresql mongodb redis none)
|
7
|
+
attr_accessor :purpose, :code_name, :databases
|
8
|
+
|
9
|
+
def add_git_remote
|
10
|
+
system("git remote add #{purpose} git@git.shellycloud.com:#{code_name}.git")
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate_cloudfile
|
14
|
+
@email = current_user.email
|
15
|
+
@databases = databases
|
16
|
+
template = File.read("lib/shelly/templates/Cloudfile.erb")
|
17
|
+
cloudfile = ERB.new(template, 0, "%<>-")
|
18
|
+
cloudfile.result(binding)
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_cloudfile
|
22
|
+
content = generate_cloudfile
|
23
|
+
File.open(cloudfile_path, "a+") { |f| f << content }
|
24
|
+
end
|
25
|
+
|
26
|
+
def cloudfile_path
|
27
|
+
File.join(Dir.pwd, "Cloudfile")
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.guess_code_name
|
31
|
+
File.basename(Dir.pwd)
|
32
|
+
end
|
33
|
+
|
34
|
+
def open_billing_page
|
35
|
+
url = "#{shelly.api_url}/apps/#{code_name}/edit_billing?api_key=#{current_user.token}"
|
36
|
+
Launchy.open(url)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/shelly/base.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require "shelly/user"
|
2
|
+
|
3
|
+
module Shelly
|
4
|
+
module CLI
|
5
|
+
class Account < Thor
|
6
|
+
namespace :account
|
7
|
+
include Helpers
|
8
|
+
|
9
|
+
desc "register", "Registers new user account on Shelly Cloud"
|
10
|
+
def register
|
11
|
+
email = ask_for_email
|
12
|
+
password = ask_for_password
|
13
|
+
|
14
|
+
# FIXME: ask user in loop, until he enters valid values
|
15
|
+
if email.blank? or password.blank?
|
16
|
+
say "Email and password can't be blank" and exit 1
|
17
|
+
end
|
18
|
+
|
19
|
+
user = User.new(email, password)
|
20
|
+
user.register
|
21
|
+
if user.ssh_key_exists?
|
22
|
+
say "Uploading your public SSH key from #{user.ssh_key_path}"
|
23
|
+
end
|
24
|
+
say "Successfully registered!"
|
25
|
+
say "Check you mailbox for email confirmation"
|
26
|
+
rescue Client::APIError => e
|
27
|
+
if e.message == "Validation Failed"
|
28
|
+
e.errors.each { |error| say "#{error.first} #{error.last}" }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Fix for bug with displaying help for subcommands
|
33
|
+
# http://stackoverflow.com/questions/5663519/namespacing-thor-commands-in-a-standalone-ruby-executable
|
34
|
+
def self.banner(task, namespace = true, subcommand = false)
|
35
|
+
"#{basename} #{task.formatted_usage(self, true, subcommand)}"
|
36
|
+
end
|
37
|
+
|
38
|
+
# FIXME: move to helpers
|
39
|
+
no_tasks do
|
40
|
+
def ask_for_email
|
41
|
+
email_question = User.guess_email.blank? ? "Email:" : "Email (#{User.guess_email} - default):"
|
42
|
+
email = ask(email_question)
|
43
|
+
email.blank? ? User.guess_email : email
|
44
|
+
end
|
45
|
+
|
46
|
+
def ask_for_password
|
47
|
+
say "Password: "
|
48
|
+
echo_off
|
49
|
+
password = $stdin.gets.strip
|
50
|
+
echo_on
|
51
|
+
password
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "shelly/app"
|
2
|
+
|
3
|
+
module Shelly
|
4
|
+
module CLI
|
5
|
+
class Apps < Thor
|
6
|
+
namespace :apps
|
7
|
+
|
8
|
+
desc "add", "Add new application to Shelly Cloud"
|
9
|
+
def add
|
10
|
+
@app = Shelly::App.new
|
11
|
+
@app.purpose = ask_for_purpose
|
12
|
+
@app.code_name = ask_for_code_name
|
13
|
+
@app.databases = ask_for_databases
|
14
|
+
@app.add_git_remote
|
15
|
+
@app.create_cloudfile
|
16
|
+
@app.open_billing_page
|
17
|
+
|
18
|
+
info_adding_cloudfile_to_repository
|
19
|
+
info_deploying_to_shellycloud
|
20
|
+
end
|
21
|
+
|
22
|
+
no_tasks do
|
23
|
+
def ask_for_purpose
|
24
|
+
purpose = ask("How will you use this system (production - default,staging):")
|
25
|
+
purpose.blank? ? "production" : purpose
|
26
|
+
end
|
27
|
+
|
28
|
+
def ask_for_code_name
|
29
|
+
default_code_name = "#{Shelly::App.guess_code_name}-#{@app.purpose}"
|
30
|
+
code_name = ask("Application code name (#{default_code_name} - default):")
|
31
|
+
code_name.blank? ? default_code_name : code_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def ask_for_databases
|
35
|
+
kinds = Shelly::App::DATABASE_KINDS
|
36
|
+
databases = ask("Which database do you want to use #{kinds.join(", ")} (postgresql - default):")
|
37
|
+
begin
|
38
|
+
databases = databases.split(/[\s,]/)
|
39
|
+
valid = databases.all? { |kind| kinds.include?(kind) }
|
40
|
+
break if valid
|
41
|
+
databases = ask("Unknown database kind. Supported are: #{kinds.join(", ")}:")
|
42
|
+
end while not valid
|
43
|
+
|
44
|
+
databases.empty? ? ["postgresql"] : databases
|
45
|
+
end
|
46
|
+
|
47
|
+
def info_adding_cloudfile_to_repository
|
48
|
+
say("Project is now configured for use with Shell Cloud:")
|
49
|
+
say("You can review changes using")
|
50
|
+
say(" git diff")
|
51
|
+
end
|
52
|
+
|
53
|
+
def info_deploying_to_shellycloud
|
54
|
+
say("When you make sure all settings are correct please issue following commands:")
|
55
|
+
say(" git add .")
|
56
|
+
say(' git commit -m "Application added to Shelly Cloud"')
|
57
|
+
say(" git push")
|
58
|
+
say("Deploy to #{@app.purpose} using:")
|
59
|
+
say(" git push #{@app.purpose} master")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "shelly"
|
2
|
+
require "thor/group"
|
3
|
+
require "shelly/cli/account"
|
4
|
+
require "shelly/cli/apps"
|
5
|
+
|
6
|
+
module Shelly
|
7
|
+
module CLI
|
8
|
+
class Main < Thor
|
9
|
+
include Thor::Actions
|
10
|
+
register(Account, "account", "account <command>", "Manages your account")
|
11
|
+
register(Apps, "apps", "apps <command>", "Manages your applications")
|
12
|
+
|
13
|
+
map %w(-v --version) => :version
|
14
|
+
desc "version", "Displays shelly version"
|
15
|
+
def version
|
16
|
+
say "shelly version #{Shelly::VERSION}"
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "register", "Registers new user account on Shelly Cloud"
|
20
|
+
def register
|
21
|
+
invoke 'account:register'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/shelly/client.rb
CHANGED
@@ -28,8 +28,12 @@ module Shelly
|
|
28
28
|
ENV["SHELLY_URL"] || "https://admin.winniecloud.com/apiv2"
|
29
29
|
end
|
30
30
|
|
31
|
-
def register_user(email, password)
|
32
|
-
post(
|
31
|
+
def register_user(email, password, ssh_key)
|
32
|
+
post("/users", :user => {:email => email, :password => password, :ssh_key => ssh_key})
|
33
|
+
end
|
34
|
+
|
35
|
+
def token
|
36
|
+
get("/token")
|
33
37
|
end
|
34
38
|
|
35
39
|
def post(path, params = {})
|
@@ -53,14 +57,16 @@ module Shelly
|
|
53
57
|
"shelly-version" => Shelly::VERSION}
|
54
58
|
end
|
55
59
|
|
60
|
+
def http_basic_auth_options
|
61
|
+
@email ? {:username => @email, :password => @password} : {}
|
62
|
+
end
|
63
|
+
|
56
64
|
def request_parameters(path, method, params = {})
|
57
|
-
unless @email.blank? or @password.blank?
|
58
|
-
params.merge!(:email => @email, :password => @password)
|
59
|
-
end
|
60
65
|
{:method => method,
|
61
66
|
:url => "#{api_url}#{path}",
|
62
67
|
:headers => headers,
|
63
|
-
:payload => params.to_json
|
68
|
+
:payload => params.to_json
|
69
|
+
}.merge(http_basic_auth_options)
|
64
70
|
end
|
65
71
|
|
66
72
|
def process_response(response)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<%= @code_name %>:
|
2
|
+
ruby: 1.9.2
|
3
|
+
monitoring_email:
|
4
|
+
- <%= @email %>
|
5
|
+
domains:
|
6
|
+
- <%= @code_name %>.winniecloud.com
|
7
|
+
servers:
|
8
|
+
app1:
|
9
|
+
size: large
|
10
|
+
web:
|
11
|
+
type: thin
|
12
|
+
count: 3
|
13
|
+
clock:
|
14
|
+
type: cron
|
15
|
+
<%- @databases.each_with_index do |kind, index| -%>
|
16
|
+
<%= "#{kind}#{index}" %>:
|
17
|
+
size: large
|
18
|
+
database:
|
19
|
+
type: <%= kind %>
|
20
|
+
<%- end -%>
|
data/lib/shelly/user.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Shelly
|
2
|
-
class User
|
2
|
+
class User < Base
|
3
3
|
attr_reader :email, :password
|
4
4
|
def initialize(email = nil, password = nil)
|
5
5
|
@email = email
|
@@ -7,16 +7,17 @@ module Shelly
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def register
|
10
|
-
|
11
|
-
|
10
|
+
ssh_key = File.read(ssh_key_path) if ssh_key_exists?
|
11
|
+
shelly.register_user(email, password, ssh_key)
|
12
12
|
save_credentials
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
|
15
|
+
def token
|
16
|
+
shelly.token["token"]
|
17
17
|
end
|
18
18
|
|
19
19
|
def load_credentials
|
20
|
+
return unless credentials_exists?
|
20
21
|
@email, @password = File.read(credentials_path).split("\n")
|
21
22
|
end
|
22
23
|
|
@@ -26,6 +27,18 @@ module Shelly
|
|
26
27
|
set_credentials_permissions
|
27
28
|
end
|
28
29
|
|
30
|
+
def ssh_key_exists?
|
31
|
+
File.exists?(ssh_key_path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def ssh_key_path
|
35
|
+
File.expand_path("~/.ssh/id_rsa.pub")
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.guess_email
|
39
|
+
@@guess_email ||= IO.popen("git config --get user.email").read.strip
|
40
|
+
end
|
41
|
+
|
29
42
|
protected
|
30
43
|
def config_dir
|
31
44
|
File.expand_path("~/.shelly")
|
data/lib/shelly/version.rb
CHANGED
data/shelly.gemspec
CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
|
|
24
24
|
s.add_runtime_dependency "thor"
|
25
25
|
s.add_runtime_dependency "rest-client"
|
26
26
|
s.add_runtime_dependency "json"
|
27
|
+
s.add_runtime_dependency "launchy"
|
27
28
|
|
28
29
|
s.files = `git ls-files`.split("\n")
|
29
30
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "shelly/app"
|
3
|
+
|
4
|
+
describe Shelly::App do
|
5
|
+
before do
|
6
|
+
FileUtils.mkdir_p("/projects/foo")
|
7
|
+
Dir.chdir("/projects/foo")
|
8
|
+
@app = Shelly::App.new
|
9
|
+
@app.purpose = "staging"
|
10
|
+
@app.code_name = "foo-staging"
|
11
|
+
end
|
12
|
+
|
13
|
+
describe ".guess_code_name" do
|
14
|
+
it "should return name of current working directory" do
|
15
|
+
Shelly::App.guess_code_name.should == "foo"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#add_git_remote" do
|
20
|
+
it "should add git remote with proper name and git repository" do
|
21
|
+
@app.should_receive(:system).with("git remote add staging git@git.shellycloud.com:foo-staging.git")
|
22
|
+
@app.add_git_remote
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#generate_cloudfile" do
|
27
|
+
it "should return generated cloudfile" do
|
28
|
+
user = mock(:email => "bob@example.com")
|
29
|
+
@app.stub(:current_user).and_return(user)
|
30
|
+
@app.databases = %w(postgresql mongodb)
|
31
|
+
FakeFS.deactivate!
|
32
|
+
expected = <<-config
|
33
|
+
foo-staging:
|
34
|
+
ruby: 1.9.2
|
35
|
+
monitoring_email:
|
36
|
+
- bob@example.com
|
37
|
+
domains:
|
38
|
+
- foo-staging.winniecloud.com
|
39
|
+
servers:
|
40
|
+
app1:
|
41
|
+
size: large
|
42
|
+
web:
|
43
|
+
type: thin
|
44
|
+
count: 3
|
45
|
+
clock:
|
46
|
+
type: cron
|
47
|
+
postgresql0:
|
48
|
+
size: large
|
49
|
+
database:
|
50
|
+
type: postgresql
|
51
|
+
mongodb1:
|
52
|
+
size: large
|
53
|
+
database:
|
54
|
+
type: mongodb
|
55
|
+
config
|
56
|
+
@app.generate_cloudfile.should == expected
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#create_cloudfile" do
|
61
|
+
before do
|
62
|
+
@app.stub(:generate_cloudfile).and_return("foo-staging:")
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should create file if Cloudfile doesn't exist" do
|
66
|
+
File.exists?("/projects/foo/Cloudfile").should be_false
|
67
|
+
@app.create_cloudfile
|
68
|
+
File.exists?("/projects/foo/Cloudfile").should be_true
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should append content if Cloudfile exists" do
|
72
|
+
File.open("/projects/foo/Cloudfile", "w") { |f| f << "foo-production:\n" }
|
73
|
+
@app.create_cloudfile
|
74
|
+
File.read("/projects/foo/Cloudfile").strip.should == "foo-production:\nfoo-staging:"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#cloudfile_path" do
|
79
|
+
it "should return path to Cloudfile" do
|
80
|
+
@app.cloudfile_path.should == "/projects/foo/Cloudfile"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "#open_billing_page" do
|
85
|
+
it "should open browser window" do
|
86
|
+
user = mock(:token => "abc", :email => nil, :password => nil)
|
87
|
+
@app.stub(:current_user).and_return(user)
|
88
|
+
url = "#{@app.shelly.api_url}/apps/foo-staging/edit_billing?api_key=abc"
|
89
|
+
Launchy.should_receive(:open).with(url)
|
90
|
+
@app.open_billing_page
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -1,50 +1,44 @@
|
|
1
1
|
require "spec_helper"
|
2
|
-
require "shelly/cli"
|
3
|
-
require "shelly/user"
|
2
|
+
require "shelly/cli/account"
|
4
3
|
|
5
|
-
describe Shelly::CLI do
|
4
|
+
describe Shelly::CLI::Account do
|
6
5
|
before do
|
6
|
+
FileUtils.stub(:chmod)
|
7
7
|
@client = mock
|
8
|
-
@
|
8
|
+
@account = Shelly::CLI::Account.new
|
9
9
|
Shelly::Client.stub(:new).and_return(@client)
|
10
10
|
$stdout.stub(:puts)
|
11
11
|
$stdout.stub(:print)
|
12
12
|
end
|
13
13
|
|
14
|
-
describe "#version" do
|
15
|
-
it "should return shelly's version" do
|
16
|
-
$stdout.should_receive(:puts).with("shelly version #{Shelly::VERSION}")
|
17
|
-
@cli.version
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
14
|
describe "#register" do
|
22
15
|
before do
|
23
16
|
Shelly::User.stub(:guess_email).and_return("")
|
24
17
|
@client.stub(:register_user)
|
18
|
+
@key_path = File.expand_path("~/.ssh/id_rsa.pub")
|
25
19
|
end
|
26
20
|
|
27
21
|
it "should ask for email and password" do
|
28
22
|
$stdout.should_receive(:print).with("Email: ")
|
29
23
|
$stdout.should_receive(:print).with("Password: ")
|
30
24
|
fake_stdin(["better@example.com", "secret"]) do
|
31
|
-
@
|
25
|
+
@account.register
|
32
26
|
end
|
33
27
|
end
|
34
28
|
|
35
29
|
it "should suggest email and use it if user enters blank email" do
|
36
30
|
Shelly::User.stub(:guess_email).and_return("kate@example.com")
|
37
|
-
$stdout.should_receive(:print).with("Email (
|
38
|
-
@client.should_receive(:register_user).with("kate@example.com", "secret")
|
31
|
+
$stdout.should_receive(:print).with("Email (kate@example.com - default): ")
|
32
|
+
@client.should_receive(:register_user).with("kate@example.com", "secret", nil)
|
39
33
|
fake_stdin(["", "secret"]) do
|
40
|
-
@
|
34
|
+
@account.register
|
41
35
|
end
|
42
36
|
end
|
43
37
|
|
44
38
|
it "should use email provided by user" do
|
45
|
-
@client.should_receive(:register_user).with("better@example.com", "secret")
|
39
|
+
@client.should_receive(:register_user).with("better@example.com", "secret", nil)
|
46
40
|
fake_stdin(["better@example.com", "secret"]) do
|
47
|
-
@
|
41
|
+
@account.register
|
48
42
|
end
|
49
43
|
end
|
50
44
|
|
@@ -53,7 +47,7 @@ describe Shelly::CLI do
|
|
53
47
|
$stdout.should_receive(:puts).with("Email and password can't be blank")
|
54
48
|
lambda do
|
55
49
|
fake_stdin(["", "only-pass"]) do
|
56
|
-
@
|
50
|
+
@account.register
|
57
51
|
end
|
58
52
|
end.should raise_error(SystemExit)
|
59
53
|
end
|
@@ -62,17 +56,38 @@ describe Shelly::CLI do
|
|
62
56
|
$stdout.should_receive(:puts).with("Email and password can't be blank")
|
63
57
|
lambda do
|
64
58
|
fake_stdin(["better@example.com", ""]) do
|
65
|
-
@
|
59
|
+
@account.register
|
66
60
|
end
|
67
61
|
end.should raise_error(SystemExit)
|
68
62
|
end
|
69
63
|
|
64
|
+
context "ssh key exists" do
|
65
|
+
it "should register with ssh-key" do
|
66
|
+
FileUtils.mkdir_p("~/.ssh")
|
67
|
+
File.open(@key_path, "w") { |f| f << "key" }
|
68
|
+
$stdout.should_receive(:puts).with("Uploading your public SSH key from #{@key_path}")
|
69
|
+
fake_stdin(["kate@example.com", "secret"]) do
|
70
|
+
@account.register
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "ssh key doesn't exist" do
|
76
|
+
it "should register user without the ssh key" do
|
77
|
+
$stdout.should_not_receive(:puts).with("Uploading your public SSH key from #{@key_path}")
|
78
|
+
fake_stdin(["kate@example.com", "secret"]) do
|
79
|
+
@account.register
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
70
84
|
context "on successful registration" do
|
71
85
|
it "should display message about registration and email confirmation" do
|
72
86
|
@client.stub(:register_user).and_return(true)
|
73
|
-
$stdout.should_receive(:puts).with("Successfully registered
|
87
|
+
$stdout.should_receive(:puts).with("Successfully registered!")
|
88
|
+
$stdout.should_receive(:puts).with("Check you mailbox for email confirmation")
|
74
89
|
fake_stdin(["kate@example.com", "pass"]) do
|
75
|
-
@
|
90
|
+
@account.register
|
76
91
|
end
|
77
92
|
end
|
78
93
|
end
|
@@ -84,9 +99,9 @@ describe Shelly::CLI do
|
|
84
99
|
@client.stub(:register_user).and_raise(exception)
|
85
100
|
$stdout.should_receive(:puts).with("email has been already taken")
|
86
101
|
fake_stdin(["kate@example.com", "pass"]) do
|
87
|
-
@
|
102
|
+
@account.register
|
88
103
|
end
|
89
104
|
end
|
90
105
|
end
|
91
106
|
end
|
92
|
-
end
|
107
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "shelly/cli/apps"
|
3
|
+
|
4
|
+
describe Shelly::CLI::Apps do
|
5
|
+
before do
|
6
|
+
@apps = Shelly::CLI::Apps.new
|
7
|
+
$stdout.stub(:print)
|
8
|
+
$stdout.stub(:puts)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#add" do
|
12
|
+
before do
|
13
|
+
FileUtils.mkdir_p("/projects/foo")
|
14
|
+
Dir.chdir("/projects/foo")
|
15
|
+
@app = Shelly::App.new
|
16
|
+
@app.stub(:add_git_remote)
|
17
|
+
@app.stub(:generate_cloudfile).and_return("Example Cloudfile")
|
18
|
+
@app.stub(:open_billing_page)
|
19
|
+
Shelly::App.stub(:new).and_return(@app)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should ask user how he will use application" do
|
23
|
+
$stdout.should_receive(:print).with("How will you use this system (production - default,staging): ")
|
24
|
+
@app.should_receive(:purpose=).with("staging")
|
25
|
+
fake_stdin(["staging", "", ""]) do
|
26
|
+
@apps.add
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when user provided empty purpose" do
|
31
|
+
it "should use 'production' as default" do
|
32
|
+
$stdout.should_receive(:print).with("How will you use this system (production - default,staging): ")
|
33
|
+
@app.should_receive(:purpose=).with("production")
|
34
|
+
fake_stdin(["", "", ""]) do
|
35
|
+
@apps.add
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should use code name provided by user" do
|
41
|
+
$stdout.should_receive(:print).with("How will you use this system (production - default,staging): ")
|
42
|
+
$stdout.should_receive(:print).with("Application code name (foo-staging - default): ")
|
43
|
+
@app.should_receive(:code_name=).with("mycodename")
|
44
|
+
fake_stdin(["staging", "mycodename", ""]) do
|
45
|
+
@apps.add
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when user provided empty code name" do
|
50
|
+
it "should use 'current_dirname-purpose' as default" do
|
51
|
+
$stdout.should_receive(:print).with("How will you use this system (production - default,staging): ")
|
52
|
+
$stdout.should_receive(:print).with("Application code name (foo-staging - default): ")
|
53
|
+
fake_stdin(["staging", "", ""]) do
|
54
|
+
@apps.add
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should use database provided by user (separated by comma or space)" do
|
60
|
+
$stdout.should_receive(:print).with("Which database do you want to use postgresql, mongodb, redis, none (postgresql - default): ")
|
61
|
+
@app.should_receive(:databases=).with(["postgresql", "mongodb", "redis"])
|
62
|
+
fake_stdin(["staging", "", "postgresql,mongodb redis"]) do
|
63
|
+
@apps.add
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should validate databases" do
|
68
|
+
$stdout.should_receive(:print).with("Which database do you want to use postgresql, mongodb, redis, none (postgresql - default): ")
|
69
|
+
$stdout.should_receive(:print).with("Unknown database kind. Supported are: postgresql, mongodb, redis, none: ")
|
70
|
+
fake_stdin(["staging", "", "postgresql,doesnt-exist", "none"]) do
|
71
|
+
@apps.add
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "when user provided empty database" do
|
76
|
+
it "should use 'postgresql' database as default" do
|
77
|
+
@app.should_receive(:databases=).with(["postgresql"])
|
78
|
+
fake_stdin(["staging", "", ""]) do
|
79
|
+
@apps.add
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should add git remote" do
|
85
|
+
@app.should_receive(:add_git_remote)
|
86
|
+
fake_stdin(["staging", "foooo", ""]) do
|
87
|
+
@apps.add
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should create Cloudfile" do
|
92
|
+
File.exists?("/projects/foo/Cloudfile").should be_false
|
93
|
+
fake_stdin(["staging", "foooo", ""]) do
|
94
|
+
@apps.add
|
95
|
+
end
|
96
|
+
File.read("/projects/foo/Cloudfile").should == "Example Cloudfile"
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should browser window with link to edit billing information" do
|
100
|
+
@app.should_receive(:open_billing_page)
|
101
|
+
fake_stdin(["staging", "foooo", ""]) do
|
102
|
+
@apps.add
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should display info about adding Cloudfile to repository" do
|
107
|
+
$stdout.should_receive(:puts).with("Project is now configured for use with Shell Cloud:")
|
108
|
+
$stdout.should_receive(:puts).with("You can review changes using")
|
109
|
+
$stdout.should_receive(:puts).with(" git diff")
|
110
|
+
fake_stdin(["staging", "foooo", "none"]) do
|
111
|
+
@apps.add
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should display info on how to deploy to ShellyCloud" do
|
116
|
+
$stdout.should_receive(:puts).with("When you make sure all settings are correct please issue following commands:")
|
117
|
+
$stdout.should_receive(:puts).with(" git add .")
|
118
|
+
$stdout.should_receive(:puts).with(' git commit -m "Application added to Shelly Cloud"')
|
119
|
+
$stdout.should_receive(:puts).with(" git push")
|
120
|
+
$stdout.should_receive(:puts).with("Deploy to staging using:")
|
121
|
+
$stdout.should_receive(:puts).with(" git push staging master")
|
122
|
+
fake_stdin(["staging", "foooo", "none"]) do
|
123
|
+
@apps.add
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "shelly/cli/main"
|
3
|
+
|
4
|
+
describe Shelly::CLI::Main do
|
5
|
+
before do
|
6
|
+
@main = Shelly::CLI::Main.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#version" do
|
10
|
+
it "should return shelly's version" do
|
11
|
+
$stdout.should_receive(:puts).with("shelly version #{Shelly::VERSION}")
|
12
|
+
@main.version
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#register" do
|
17
|
+
it "should invoke account:register command" do
|
18
|
+
@main.should_receive(:invoke).with('account:register')
|
19
|
+
@main.register
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#help" do
|
24
|
+
it "should display available commands" do
|
25
|
+
expected = <<-OUT
|
26
|
+
Tasks:
|
27
|
+
shelly account <command> # Manages your account
|
28
|
+
shelly apps <command> # Manages your applications
|
29
|
+
shelly help [TASK] # Describe available tasks or one specific task
|
30
|
+
shelly register # Registers new user account on Shelly Cloud
|
31
|
+
shelly version # Displays shelly version
|
32
|
+
OUT
|
33
|
+
out = IO.popen("bin/shelly").read.strip
|
34
|
+
out.should == expected.strip
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/shelly/client_spec.rb
CHANGED
@@ -25,8 +25,16 @@ describe Shelly::Client do
|
|
25
25
|
|
26
26
|
describe "#register_user" do
|
27
27
|
it "should send post request with login and password" do
|
28
|
-
@client.should_receive(:post).with("/users", {:user => {:email => "test@example.com",
|
29
|
-
|
28
|
+
@client.should_receive(:post).with("/users", {:user => {:email => "test@example.com",
|
29
|
+
:password => "secret", :ssh_key => "ssh-key Abb"}})
|
30
|
+
@client.register_user("test@example.com", "secret", "ssh-key Abb")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#token" do
|
35
|
+
it "should get authentication token" do
|
36
|
+
@client.should_receive(:get).with("/token")
|
37
|
+
@client.token
|
30
38
|
end
|
31
39
|
end
|
32
40
|
|
@@ -36,7 +44,9 @@ describe Shelly::Client do
|
|
36
44
|
:method => :post,
|
37
45
|
:url => "#{@client.api_url}/account",
|
38
46
|
:headers => @client.headers,
|
39
|
-
:payload => {:name => "bob"
|
47
|
+
:payload => {:name => "bob"}.to_json,
|
48
|
+
:username => "bob@example.com",
|
49
|
+
:password => "secret"
|
40
50
|
}
|
41
51
|
@client.request_parameters("/account", :post, :name => "bob").should == expected
|
42
52
|
end
|
@@ -49,7 +59,7 @@ describe Shelly::Client do
|
|
49
59
|
:headers => @client.headers,
|
50
60
|
:payload => {}.to_json
|
51
61
|
}
|
52
|
-
client.
|
62
|
+
client.request_parameters("/account", :get).should == expected
|
53
63
|
end
|
54
64
|
end
|
55
65
|
|
data/spec/shelly/user_spec.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
require "shelly/user"
|
2
1
|
require "spec_helper"
|
3
2
|
|
4
3
|
describe Shelly::User do
|
5
|
-
include FakeFS::SpecHelpers
|
6
|
-
|
7
4
|
before do
|
5
|
+
FileUtils.mkdir_p("~/.ssh")
|
6
|
+
File.open("~/.ssh/id_rsa.pub", "w") { |f| f << "ssh-key AAbbcc" }
|
8
7
|
@client = mock
|
9
8
|
Shelly::Client.stub(:new).and_return(@client)
|
10
9
|
@user = Shelly::User.new("bob@example.com", "secret")
|
@@ -25,7 +24,7 @@ describe Shelly::User do
|
|
25
24
|
end
|
26
25
|
|
27
26
|
it "should register user at Shelly Cloud" do
|
28
|
-
@client.should_receive(:register_user).with("bob@example.com", "secret")
|
27
|
+
@client.should_receive(:register_user).with("bob@example.com", "secret", "ssh-key AAbbcc")
|
29
28
|
@user.register
|
30
29
|
end
|
31
30
|
|
@@ -33,6 +32,21 @@ describe Shelly::User do
|
|
33
32
|
@user.should_receive(:save_credentials)
|
34
33
|
@user.register
|
35
34
|
end
|
35
|
+
|
36
|
+
context "when ssh key is not available" do
|
37
|
+
it "should register without it" do
|
38
|
+
FileUtils.rm_rf("~/.ssh/id_rsa.pub")
|
39
|
+
@client.should_receive(:register_user).with("bob@example.com", "secret", nil)
|
40
|
+
@user.register
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#token" do
|
46
|
+
it "should return token" do
|
47
|
+
@client.should_receive(:token).and_return({"token" => "abc"})
|
48
|
+
@user.token.should == "abc"
|
49
|
+
end
|
36
50
|
end
|
37
51
|
|
38
52
|
describe "#save_credentials" do
|
@@ -42,7 +56,7 @@ describe Shelly::User do
|
|
42
56
|
File.read("~/.shelly/credentials").should == "bob@example.com\nsecret"
|
43
57
|
end
|
44
58
|
|
45
|
-
it "should create config_dir if doesn't exist" do
|
59
|
+
it "should create config_dir if it doesn't exist" do
|
46
60
|
File.exists?("~/.shelly").should be_false
|
47
61
|
@user.save_credentials
|
48
62
|
File.exists?("~/.shelly").should be_true
|
@@ -61,10 +75,34 @@ describe Shelly::User do
|
|
61
75
|
config_dir = File.expand_path("~/.shelly")
|
62
76
|
FileUtils.mkdir_p(config_dir)
|
63
77
|
File.open(File.join(config_dir, "credentials"), "w") { |f| f << "superman@example.com\nkal-el" }
|
78
|
+
|
64
79
|
user = Shelly::User.new
|
65
80
|
user.load_credentials
|
66
81
|
user.email.should == "superman@example.com"
|
67
82
|
user.password.should == "kal-el"
|
68
83
|
end
|
84
|
+
|
85
|
+
context "credentials file doesn't exist" do
|
86
|
+
it "should return nil" do
|
87
|
+
user = Shelly::User.new
|
88
|
+
user.load_credentials.should be_nil
|
89
|
+
user.email.should be_nil
|
90
|
+
user.password.should be_nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#ssh_key_path" do
|
96
|
+
it "should return path to public ssh key file" do
|
97
|
+
@user.ssh_key_path.should == File.expand_path("~/.ssh/id_rsa.pub")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "#ssh_key_exists?" do
|
102
|
+
it "should return true if key exists, false otherwise" do
|
103
|
+
@user.should be_ssh_key_exists
|
104
|
+
FileUtils.rm_rf("~/.ssh/id_rsa.pub")
|
105
|
+
@user.should_not be_ssh_key_exists
|
106
|
+
end
|
69
107
|
end
|
70
108
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shelly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 5
|
10
|
+
version: 0.0.5
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Shelly Cloud team
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-09-
|
18
|
+
date: 2011-09-20 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -158,6 +158,20 @@ dependencies:
|
|
158
158
|
version: "0"
|
159
159
|
type: :runtime
|
160
160
|
version_requirements: *id010
|
161
|
+
- !ruby/object:Gem::Dependency
|
162
|
+
name: launchy
|
163
|
+
prerelease: false
|
164
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
165
|
+
none: false
|
166
|
+
requirements:
|
167
|
+
- - ">="
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
hash: 3
|
170
|
+
segments:
|
171
|
+
- 0
|
172
|
+
version: "0"
|
173
|
+
type: :runtime
|
174
|
+
version_requirements: *id011
|
161
175
|
description: Tool for managing applications and clouds at shellycloud.com
|
162
176
|
email:
|
163
177
|
- support@shellycloud.com
|
@@ -177,15 +191,23 @@ files:
|
|
177
191
|
- bin/shelly
|
178
192
|
- lib/core_ext/object.rb
|
179
193
|
- lib/shelly.rb
|
180
|
-
- lib/shelly/
|
194
|
+
- lib/shelly/app.rb
|
195
|
+
- lib/shelly/base.rb
|
196
|
+
- lib/shelly/cli/account.rb
|
197
|
+
- lib/shelly/cli/apps.rb
|
198
|
+
- lib/shelly/cli/main.rb
|
181
199
|
- lib/shelly/client.rb
|
182
200
|
- lib/shelly/helpers.rb
|
201
|
+
- lib/shelly/templates/Cloudfile.erb
|
183
202
|
- lib/shelly/user.rb
|
184
203
|
- lib/shelly/version.rb
|
185
204
|
- shelly.gemspec
|
186
205
|
- spec/helpers.rb
|
187
206
|
- spec/input_faker.rb
|
188
|
-
- spec/shelly/
|
207
|
+
- spec/shelly/app_spec.rb
|
208
|
+
- spec/shelly/cli/account_spec.rb
|
209
|
+
- spec/shelly/cli/apps_spec.rb
|
210
|
+
- spec/shelly/cli/main_spec.rb
|
189
211
|
- spec/shelly/client_spec.rb
|
190
212
|
- spec/shelly/user_spec.rb
|
191
213
|
- spec/spec_helper.rb
|
@@ -226,7 +248,10 @@ summary: Shelly Cloud command line tool
|
|
226
248
|
test_files:
|
227
249
|
- spec/helpers.rb
|
228
250
|
- spec/input_faker.rb
|
229
|
-
- spec/shelly/
|
251
|
+
- spec/shelly/app_spec.rb
|
252
|
+
- spec/shelly/cli/account_spec.rb
|
253
|
+
- spec/shelly/cli/apps_spec.rb
|
254
|
+
- spec/shelly/cli/main_spec.rb
|
230
255
|
- spec/shelly/client_spec.rb
|
231
256
|
- spec/shelly/user_spec.rb
|
232
257
|
- spec/spec_helper.rb
|
data/lib/shelly/cli.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
require "shelly"
|
2
|
-
require "shelly/user"
|
3
|
-
|
4
|
-
module Shelly
|
5
|
-
class CLI < Thor
|
6
|
-
include Helpers
|
7
|
-
include Thor::Actions
|
8
|
-
|
9
|
-
map %w(-v --version) => :version
|
10
|
-
desc "version", "Displays shelly version"
|
11
|
-
def version
|
12
|
-
say "shelly version #{Shelly::VERSION}"
|
13
|
-
end
|
14
|
-
|
15
|
-
desc "register", "Registers new user at Shelly Cloud"
|
16
|
-
def register
|
17
|
-
email_question = User.guess_email.blank? ? "Email:" : "Email (default #{User.guess_email}):"
|
18
|
-
email = ask(email_question)
|
19
|
-
email = User.guess_email if email.blank?
|
20
|
-
password = ask_for_password
|
21
|
-
|
22
|
-
if email.blank? or password.blank?
|
23
|
-
say "Email and password can't be blank" and exit 1
|
24
|
-
end
|
25
|
-
|
26
|
-
user = User.new(email, password)
|
27
|
-
if user.register
|
28
|
-
say "Successfully registered!\nCheck you mailbox for email confirmation"
|
29
|
-
end
|
30
|
-
rescue Client::APIError => e
|
31
|
-
if e.message == "Validation Failed"
|
32
|
-
e.errors.each { |error| say "#{error.first} #{error.last}" }
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
no_tasks do
|
37
|
-
def ask_for_password
|
38
|
-
say "Password: "
|
39
|
-
echo_off
|
40
|
-
password = $stdin.gets.strip
|
41
|
-
echo_on
|
42
|
-
password
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|