capistrano-harrow 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/Gemfile +4 -0
- data/README.md +49 -0
- data/Rakefile +21 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/capistrano-harrow.gemspec +30 -0
- data/integration-test-server.rb +30 -0
- data/integration-test.rb +19 -0
- data/lib/capistrano/harrow.rb +13 -0
- data/lib/capistrano/harrow/api.rb +92 -0
- data/lib/capistrano/harrow/banner.rb +41 -0
- data/lib/capistrano/harrow/config.rb +9 -0
- data/lib/capistrano/harrow/config/git.rb +87 -0
- data/lib/capistrano/harrow/http.rb +35 -0
- data/lib/capistrano/harrow/installer.rb +224 -0
- data/lib/capistrano/harrow/plugin.rb +34 -0
- data/lib/capistrano/harrow/ui.rb +10 -0
- data/lib/capistrano/harrow/ui/tty.rb +60 -0
- data/lib/capistrano/harrow/version.rb +5 -0
- metadata +177 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 65e8e793ccd1dc35faf097eab9b91efa4998814e
|
4
|
+
data.tar.gz: e8bff1060e533c53a5d48e349a439715d5f078bf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 085035cbc8f19ba9250483ca635b6d3637a93435c513d2f3b8bd79806731b0c9dbfdb9991d36e7cd17fa145c056d39fa82411dd23ad36bc90ff240b47d8f2031
|
7
|
+
data.tar.gz: f59340879926938d78723f55d68abf54525c881bf656514314849241d19fc8e1e319402dd5f3356288143e738c167757a242edee9061edb45af38d1d2cf5dae1
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Capistrano::Harrow
|
2
|
+
|
3
|
+
A plugin for tighter integration with Harrow.io whilst using Capistrano.
|
4
|
+
|
5
|
+
Harrow.io is a continuous integration and deployment solution for people
|
6
|
+
who like software that works like Capistrano does.
|
7
|
+
|
8
|
+
A webbased "CI" platform which has the concept of "stages" (environments) baked
|
9
|
+
in to allow reuse of scripts and tasks. Harrow.io also has powerful access
|
10
|
+
control to allow certain team members to work in certain envirnments, but not
|
11
|
+
in others.
|
12
|
+
|
13
|
+
Harrow.io is a commercial SaaS product founded by Lee Hambley, long-time
|
14
|
+
maintainer of Capistrano in an effort to make FOSS work sustainable and find a
|
15
|
+
model where a commercial entity can serve the community better.
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
This Gem should not be installed directly, it is a dependency of `capistrano`
|
20
|
+
so that Capistrano's main gem release cycle is not burdened by changes to this
|
21
|
+
Gem, or to Harrow's public API.
|
22
|
+
|
23
|
+
## Development
|
24
|
+
|
25
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
26
|
+
`rake test` to run the tests. You can also run `bin/console` for an interactive
|
27
|
+
prompt that will allow you to experiment.
|
28
|
+
|
29
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
30
|
+
release a new version, update the version number in `version.rb`, and then run
|
31
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
32
|
+
git commits and tags, and push the `.gem` file to
|
33
|
+
[rubygems.org](https://rubygems.org).
|
34
|
+
|
35
|
+
To run the integration test on your local machine, run `bundle exec
|
36
|
+
rake integration`. This starts a development server on port 12345 and
|
37
|
+
runs the installer against that server. If you need to use another
|
38
|
+
port, you can specify it with the `port` argument to `rake`: `bundle
|
39
|
+
exec rake integration port=3333`
|
40
|
+
|
41
|
+
## Opting Out
|
42
|
+
|
43
|
+
The `capistrano-harrow` gem reads it's configuration from Git, you can completely
|
44
|
+
disable the gem integration by setting `git config harrow.disabled true`.
|
45
|
+
|
46
|
+
## Contributing
|
47
|
+
|
48
|
+
Bug reports and pull requests are welcome on GitHub at
|
49
|
+
https://github.com/harrowio/capistrano-harrow.
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.libs << "test"
|
6
|
+
t.libs << "lib"
|
7
|
+
t.test_files = FileList['test/**/*_test.rb']
|
8
|
+
end
|
9
|
+
|
10
|
+
task :integration do
|
11
|
+
port = ENV.fetch('port', 12345).to_i
|
12
|
+
sh %Q{
|
13
|
+
bundle exec -- ruby integration-test-server.rb -p #{port} > integration.log 2>&1 &
|
14
|
+
sleep 5
|
15
|
+
git config --unset harrow.session.uuid
|
16
|
+
yes longpassword | ruby -Ilib integration-test.rb http://localhost:#{port} .git/config
|
17
|
+
kill %1
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => :spec
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "capistrano/harrow"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'capistrano/harrow/version'
|
5
|
+
require 'capistrano/harrow/banner'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "capistrano-harrow"
|
9
|
+
spec.version = Capistrano::Harrow::VERSION
|
10
|
+
spec.authors = ["Lee Hambley", "Dario Hamidi"]
|
11
|
+
spec.email = ["leehambley@harrow.io", "dariohamidi@harrow.io"]
|
12
|
+
|
13
|
+
spec.summary = %q{A plugin to improve the user experience for users of Capistrano and Harrow}
|
14
|
+
spec.description = %q{Hooks to allow people experiencing problems with Capistrano to register with a service to get help and have a smoother workflow.}
|
15
|
+
spec.homepage = "https://github.com/harrowio/capistrano-harrow"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
spec.post_install_message = Capistrano::Harrow::Banner.new.to_s
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
26
|
+
spec.add_development_dependency "pry", "0.10.3"
|
27
|
+
spec.add_development_dependency "json", "1.8.3"
|
28
|
+
spec.add_development_dependency "byebug", "8.2.4"
|
29
|
+
spec.add_development_dependency "sinatra", "1.4.7"
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
$pass = 0
|
5
|
+
|
6
|
+
get '/capistrano/participating' do
|
7
|
+
$pass = $pass + 1
|
8
|
+
{participating: true}.to_json
|
9
|
+
end
|
10
|
+
|
11
|
+
post '/capistrano/sign-up' do
|
12
|
+
if $pass % 2 == 0
|
13
|
+
return [422, {}, {reason: 'invalid', errors: {email: ['not_unique']}}.to_json]
|
14
|
+
end
|
15
|
+
request.body.rewind
|
16
|
+
data = JSON.parse(request.body.read, symbolize_names: true)
|
17
|
+
url = data.fetch(:repository_url, '')
|
18
|
+
|
19
|
+
parts = url[%r{[:/]([^/]*/.*)$}, 1].to_s
|
20
|
+
organization, project = parts.split("/")
|
21
|
+
project.gsub!(/.git$/, '')
|
22
|
+
|
23
|
+
{
|
24
|
+
session_uuid: "94b07854-91ea-4c96-b896-f1d79a4f07aa",
|
25
|
+
organization_uuid: "574f01af-1031-41de-8d4f-b4d905ba0c13",
|
26
|
+
project_uuid: "4e48b8ec-188a-44c9-be83-999d0737a8d5",
|
27
|
+
project_name: project,
|
28
|
+
organization_name: organization,
|
29
|
+
}.to_json
|
30
|
+
end
|
data/integration-test.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'capistrano/harrow'
|
2
|
+
|
3
|
+
if ARGV.length < 2
|
4
|
+
$stderr.puts "Usage: test.rb <test-server-url> <git-config-file>\n"
|
5
|
+
exit(1)
|
6
|
+
end
|
7
|
+
|
8
|
+
server = ARGV[0]
|
9
|
+
git_config = ARGV[1]
|
10
|
+
|
11
|
+
api = Capistrano::Harrow::API.new(
|
12
|
+
url: server,
|
13
|
+
client: Capistrano::Harrow::HTTP.new,
|
14
|
+
participation_url: ENV.fetch('_CAPISTRANO_PARTICIPATION_URL', "#{server}/capistrano/participating"),
|
15
|
+
)
|
16
|
+
config = Capistrano::Harrow::Config::Git.new(git_config)
|
17
|
+
ui = Capistrano::Harrow::UI::TTY.new(timeout: 10)
|
18
|
+
installer = Capistrano::Harrow::Installer.new(ui: ui, config: config, api: api)
|
19
|
+
installer.install!
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "capistrano/harrow/version"
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Harrow
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require "capistrano/harrow/installer"
|
9
|
+
require "capistrano/harrow/ui"
|
10
|
+
require "capistrano/harrow/http"
|
11
|
+
require "capistrano/harrow/api"
|
12
|
+
require "capistrano/harrow/config"
|
13
|
+
require "capistrano/harrow/banner"
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
module Capistrano
|
5
|
+
module Harrow
|
6
|
+
class API
|
7
|
+
PARTICIPATION_URL = 'http://harrow.capistranorb.com/participating'
|
8
|
+
|
9
|
+
class NetworkError < StandardError; end
|
10
|
+
class ProtocolError < StandardError; end
|
11
|
+
class FatalError < StandardError; end
|
12
|
+
|
13
|
+
def initialize(params={url: 'https://www.app.harrow.io/api/',
|
14
|
+
client: HTTP,
|
15
|
+
participation_url: PARTICIPATION_URL,
|
16
|
+
})
|
17
|
+
@url = URI(params.fetch(:url))
|
18
|
+
@client = params.fetch(:client)
|
19
|
+
@participation_url = URI(params.fetch(:participation_url, PARTICIPATION_URL))
|
20
|
+
end
|
21
|
+
|
22
|
+
def participating?(params={})
|
23
|
+
name = params.delete(:name)
|
24
|
+
email = params.delete(:email)
|
25
|
+
repository_url = params.delete(:repository_url)
|
26
|
+
|
27
|
+
params[:name_present] = !name.to_s.empty?
|
28
|
+
params[:email_present] = !email.to_s.empty?
|
29
|
+
params[:repository_id] = Digest::SHA1.new.hexdigest(repository_url.to_s)
|
30
|
+
response = @client.get(
|
31
|
+
@participation_url,
|
32
|
+
{'User-Agent' => user_agent},
|
33
|
+
params,
|
34
|
+
)
|
35
|
+
|
36
|
+
case response
|
37
|
+
when Net::HTTPSuccess
|
38
|
+
return ::JSON.parse(response.body, symbolize_names: true).fetch(:participating, false)
|
39
|
+
end
|
40
|
+
|
41
|
+
false
|
42
|
+
rescue
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
def sign_up(data)
|
47
|
+
begin
|
48
|
+
response = @client.post(
|
49
|
+
@url.merge(@url.path + '/capistrano/sign-up'),
|
50
|
+
{'Content-Type' => 'application/json',
|
51
|
+
'User-Agent' => user_agent,
|
52
|
+
},
|
53
|
+
data.to_json,
|
54
|
+
)
|
55
|
+
rescue StandardError => e
|
56
|
+
raise FatalError.new(e)
|
57
|
+
end
|
58
|
+
|
59
|
+
response_code = response.code.to_i
|
60
|
+
if response_code >= 200 && response_code < 300
|
61
|
+
JSON.parse(response.body, symbolize_names: true)
|
62
|
+
elsif response_code == 422
|
63
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
64
|
+
if data.fetch(:reason, 'ok') == 'invalid'
|
65
|
+
data
|
66
|
+
else
|
67
|
+
raise ProtocolError.new(response)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
raise ProtocolError.new(response)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def user_agent
|
77
|
+
begin
|
78
|
+
git_version = `git version`.split(' ').last
|
79
|
+
rescue Errno::ENOENT
|
80
|
+
git_version = 'none'
|
81
|
+
end
|
82
|
+
|
83
|
+
result = "capistrano-harrow=#{Capistrano::Harrow::VERSION}"
|
84
|
+
result << " capistrano=#{Capistrano::VERSION}" if defined? Capistrano::VERSION
|
85
|
+
result << " ruby=#{RUBY_VERSION}"
|
86
|
+
result << " git=#{git_version}"
|
87
|
+
result
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Capistrano
|
2
|
+
module Harrow
|
3
|
+
class Banner
|
4
|
+
|
5
|
+
def initialize(env = ENV)
|
6
|
+
@env = env
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
text_banner
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def text_banner
|
16
|
+
%q{
|
17
|
+
_ _ _
|
18
|
+
| | | | (_)
|
19
|
+
| |__| | __ _ _ __ _ __ _____ ___ ___
|
20
|
+
| __ |/ _` | '__| '__/ _ \ \ /\ / / |/ _ \
|
21
|
+
| | | | (_| | | | | | (_) \ V V /| | (_) |
|
22
|
+
|_| |_|\__,_|_| |_| \___/ \_/\_(_)_|\___/
|
23
|
+
|
24
|
+
Continuous Integration & Deployment
|
25
|
+
Built by the team behind Capistrano
|
26
|
+
|
27
|
+
Learn more at http://hrw.io/cap-for-teams
|
28
|
+
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Not used yet.
|
34
|
+
#
|
35
|
+
def iterm2_banner
|
36
|
+
%Q{\033]1337;File=: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}
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Harrow
|
5
|
+
module Config
|
6
|
+
class Git
|
7
|
+
def initialize(config_file)
|
8
|
+
@config_file = config_file
|
9
|
+
end
|
10
|
+
|
11
|
+
def disabled?
|
12
|
+
case get :'harrow.disabled'
|
13
|
+
when 'true' then true
|
14
|
+
when 'false' then false
|
15
|
+
else
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def installed?
|
21
|
+
not session_uuid.to_s.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def session_uuid=(value)
|
25
|
+
set :'harrow.session.uuid', value
|
26
|
+
end
|
27
|
+
|
28
|
+
def session_uuid
|
29
|
+
get :'harrow.session.uuid'
|
30
|
+
end
|
31
|
+
|
32
|
+
def organization_uuid=(value)
|
33
|
+
set :'harrow.organization.uuid', value
|
34
|
+
end
|
35
|
+
|
36
|
+
def organization_uuid
|
37
|
+
get :'harrow.organization.uuid'
|
38
|
+
end
|
39
|
+
|
40
|
+
def project_uuid=(value)
|
41
|
+
set :'harrow.project.uuid', value
|
42
|
+
end
|
43
|
+
|
44
|
+
def project_uuid
|
45
|
+
get :'harrow.organization.uuid'
|
46
|
+
end
|
47
|
+
|
48
|
+
def repository_url
|
49
|
+
get :'remote.origin.url'
|
50
|
+
end
|
51
|
+
|
52
|
+
def username
|
53
|
+
get :'user.name'
|
54
|
+
end
|
55
|
+
|
56
|
+
def email
|
57
|
+
get :'user.email'
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def get(key)
|
63
|
+
begin
|
64
|
+
value = `git config --file #{@config_file.shellescape} #{key.to_s.shellescape}`.chop
|
65
|
+
if value.empty?
|
66
|
+
value = `git config --file ~/.gitconfig #{key.to_s.shellescape}`.chop
|
67
|
+
end
|
68
|
+
|
69
|
+
value
|
70
|
+
rescue Errno::ENOENT
|
71
|
+
raise BackendError.new('git not installed')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def set(key, value)
|
76
|
+
return unless File.exists? @config_file
|
77
|
+
|
78
|
+
begin
|
79
|
+
`git config --file #{@config_file.shellescape} #{key.to_s.shellescape} #{value.to_s.shellescape}`
|
80
|
+
rescue Errno::ENOENT
|
81
|
+
raise BackendError.new('git not installed')
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Harrow
|
5
|
+
class HTTP
|
6
|
+
def get(url, headers, params)
|
7
|
+
params = URI.encode_www_form(params)
|
8
|
+
request = Net::HTTP::Get.new(url.merge('?'+params).to_s)
|
9
|
+
headers.each do |header, value|
|
10
|
+
request[header.to_s] = value
|
11
|
+
end
|
12
|
+
|
13
|
+
make_request URI(url), request
|
14
|
+
end
|
15
|
+
|
16
|
+
def post(url, headers, data)
|
17
|
+
request = Net::HTTP::Post.new(url.path)
|
18
|
+
headers.each do |header, value|
|
19
|
+
request[header.to_s] = value
|
20
|
+
end
|
21
|
+
request.body = data
|
22
|
+
|
23
|
+
make_request URI(url), request
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def make_request(url, request)
|
29
|
+
http = Net::HTTP.new(url.host, url.port)
|
30
|
+
http.use_ssl = url.scheme == 'https'
|
31
|
+
http.request(request)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Capistrano
|
3
|
+
module Harrow
|
4
|
+
class Installer
|
5
|
+
PROMPTS = {
|
6
|
+
want_install: %q{ Try it now? },
|
7
|
+
enter_password: "Enter a password for your Harrow.io account",
|
8
|
+
confirm_password: "Confirm your password",
|
9
|
+
retry_request: "Retry?",
|
10
|
+
enter_name: "Enter your name",
|
11
|
+
enter_email: "Enter your email",
|
12
|
+
}
|
13
|
+
|
14
|
+
MESSAGES = {
|
15
|
+
aborting: "Aborting%<reason>s...\n",
|
16
|
+
installation_successful: %q{
|
17
|
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
18
|
+
┃ Success! ┃
|
19
|
+
┃ ┃
|
20
|
+
┃ You have been registered on Harrow.io. ┃
|
21
|
+
┃ ┃
|
22
|
+
┃ Your organization is called: ┃
|
23
|
+
┃ ┃
|
24
|
+
┃ %-35<organization_name>s┃
|
25
|
+
┃ ┃
|
26
|
+
┃ Your project is called: ┃
|
27
|
+
┃ ┃
|
28
|
+
┃ %-35<project_name>s┃
|
29
|
+
┃ ┃
|
30
|
+
┃ Log in here or check your email ┃
|
31
|
+
┃ for an account confirmation link ┃
|
32
|
+
┃ ┃
|
33
|
+
┃ https://www.app.harrow.io ┃
|
34
|
+
┃ ┃
|
35
|
+
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
36
|
+
},
|
37
|
+
existing_account_found: %q{
|
38
|
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
39
|
+
┃ Great! ┃
|
40
|
+
┃ ┃
|
41
|
+
┃ You already have an account! ┃
|
42
|
+
┃ ┃
|
43
|
+
┃ Please log in at: ┃
|
44
|
+
┃ ┃
|
45
|
+
┃ https://www.app.harrow.io ┃
|
46
|
+
┃ ┃
|
47
|
+
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
48
|
+
},
|
49
|
+
password_mismatch: "Passwords don't match!",
|
50
|
+
api_network_error: "Looks like there was a problem with the network",
|
51
|
+
api_protocol_error: "Harrow API declined request",
|
52
|
+
api_fatal_error: "Something went wrong",
|
53
|
+
password_too_short: "Your password needs to be at least 10 characters long",
|
54
|
+
signup_data: %q{
|
55
|
+
|
56
|
+
We'll set-up your account for you with the following
|
57
|
+
details:
|
58
|
+
|
59
|
+
%<name>s <%<email>s>
|
60
|
+
|
61
|
+
},
|
62
|
+
repository: %{
|
63
|
+
For this repository with the URL:
|
64
|
+
|
65
|
+
%<repository_url>s
|
66
|
+
|
67
|
+
},
|
68
|
+
}
|
69
|
+
|
70
|
+
def self.preinstall_message
|
71
|
+
%q{
|
72
|
+
- Free for small projects!
|
73
|
+
|
74
|
+
- Test, deploy and collaborate online easily
|
75
|
+
using tools you already know and love!
|
76
|
+
|
77
|
+
- Trigger tasks automatically based on Git changes
|
78
|
+
and webhooks. Get notified by email, slack, etc.
|
79
|
+
|
80
|
+
- Works seamlessly for PHP, Node.js, Ansible, Python, Go,
|
81
|
+
Capistrano and more!
|
82
|
+
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.message(tag, format_data)
|
87
|
+
sprintf(MESSAGES.fetch(tag, tag.to_s), format_data)
|
88
|
+
end
|
89
|
+
|
90
|
+
def initialize(params={ui: UI::TTY, api: API, config: Config::Git})
|
91
|
+
@ui = params.fetch(:ui)
|
92
|
+
@config = params.fetch(:config)
|
93
|
+
@api = params.fetch(:api)
|
94
|
+
@quit = false
|
95
|
+
@password = nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def install!
|
99
|
+
return if @config.disabled?
|
100
|
+
return if @config.installed?
|
101
|
+
return unless @api.participating?(signup_data)
|
102
|
+
|
103
|
+
@ui.show Banner.new.to_s
|
104
|
+
@ui.show self.class.preinstall_message
|
105
|
+
|
106
|
+
begin
|
107
|
+
if @ui.prompt(PROMPTS[:want_install]).downcase == 'no'
|
108
|
+
quit!
|
109
|
+
return
|
110
|
+
end
|
111
|
+
rescue UI::TimeoutError
|
112
|
+
quit!("timeout")
|
113
|
+
return
|
114
|
+
end
|
115
|
+
|
116
|
+
data = signup_data
|
117
|
+
if data[:email].to_s.empty? or data[:name].to_s.empty?
|
118
|
+
begin
|
119
|
+
data[:name] = @ui.prompt(PROMPTS[:enter_name], [])
|
120
|
+
data[:email] = @ui.prompt(PROMPTS[:enter_email], [])
|
121
|
+
rescue UI::TimeoutError
|
122
|
+
quit!("timeout")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
@ui.show self.class.message(:signup_data, data)
|
127
|
+
unless data[:repository_url].to_s.empty?
|
128
|
+
@ui.show self.class.message(:repository, data)
|
129
|
+
end
|
130
|
+
|
131
|
+
@password = prompt_password!
|
132
|
+
unless @password
|
133
|
+
quit!("no password provided")
|
134
|
+
return
|
135
|
+
end
|
136
|
+
|
137
|
+
sign_up_user!
|
138
|
+
end
|
139
|
+
|
140
|
+
def signup_data
|
141
|
+
{
|
142
|
+
repository_url: @config.repository_url,
|
143
|
+
name: @config.username,
|
144
|
+
email: @config.email,
|
145
|
+
password: @password,
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
def quit!(reason="")
|
150
|
+
unless reason.empty?
|
151
|
+
reason = ": #{reason}"
|
152
|
+
end
|
153
|
+
|
154
|
+
@quit = true
|
155
|
+
@ui.show(self.class.message(:aborting,{reason: reason}))
|
156
|
+
end
|
157
|
+
|
158
|
+
def quit?
|
159
|
+
@quit
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
def sign_up_user!
|
164
|
+
begin
|
165
|
+
response_data = @api.sign_up(signup_data)
|
166
|
+
@config.session_uuid = response_data[:session_uuid]
|
167
|
+
@config.project_uuid = response_data[:project_uuid]
|
168
|
+
@config.organization_uuid = response_data[:organization_uuid]
|
169
|
+
if response_data.fetch(:reason, 'ok') == 'invalid'
|
170
|
+
if response_data.fetch(:errors, {}).fetch(:email, []).first == 'not_unique'
|
171
|
+
@ui.show self.class.message(:existing_account_found, {})
|
172
|
+
return :account_exists
|
173
|
+
else
|
174
|
+
@ui.show self.class.message(:api_fatal_error, {})
|
175
|
+
return response_data
|
176
|
+
end
|
177
|
+
else
|
178
|
+
@ui.show self.class.message(:installation_successful, response_data)
|
179
|
+
return :signed_up
|
180
|
+
end
|
181
|
+
rescue API::NetworkError
|
182
|
+
@ui.show self.class.message(:api_network_error, {})
|
183
|
+
should_retry = false
|
184
|
+
begin
|
185
|
+
if @ui.prompt(PROMPTS[:retry_request], [:no, :yes]) == :yes
|
186
|
+
should_retry = true
|
187
|
+
end
|
188
|
+
rescue UI::TimeoutError
|
189
|
+
quit!
|
190
|
+
return
|
191
|
+
end
|
192
|
+
|
193
|
+
retry if should_retry
|
194
|
+
rescue API::ProtocolError
|
195
|
+
quit! self.class.message(:api_protocol_error, {})
|
196
|
+
return
|
197
|
+
rescue API::FatalError
|
198
|
+
quit! self.class.message(:api_fatal_error, {})
|
199
|
+
return
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def prompt_password!
|
204
|
+
tries = 3
|
205
|
+
while tries > 0
|
206
|
+
password = @ui.prompt_password(PROMPTS[:enter_password])
|
207
|
+
confirmed_password = @ui.prompt_password(PROMPTS[:confirm_password])
|
208
|
+
|
209
|
+
|
210
|
+
if password == confirmed_password && password.to_s.length >= 10
|
211
|
+
return password
|
212
|
+
elsif password.to_s.length < 10
|
213
|
+
@ui.show self.class.message(:password_too_short, {})
|
214
|
+
else
|
215
|
+
@ui.show self.class.message(:password_mismatch, {})
|
216
|
+
end
|
217
|
+
|
218
|
+
tries = tries - 1
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "capistrano/plugin"
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Harrow
|
5
|
+
class Plugin < Capistrano::Plugin
|
6
|
+
def define_tasks
|
7
|
+
namespace :harrow do
|
8
|
+
task :install do
|
9
|
+
harrow_url = ENV.fetch('_CAPISTRANO_HARROW_URL', 'https://www.app.harrow.io/api/')
|
10
|
+
participation_url = ENV.fetch('_CAPISTRANO_PARTICIPATION_URL', API::PARTICIPATION_URL)
|
11
|
+
git_config = ENV.fetch('GIT_CONFIG', ENV.fetch('_CAPISTRANO_HARROW_CONFIG', '.git/config'))
|
12
|
+
timeout = ENV.fetch('_CAPISTRANO_HARROW_TIMEOUT', 30).to_i
|
13
|
+
|
14
|
+
api = Capistrano::Harrow::API.new(
|
15
|
+
url: harrow_url,
|
16
|
+
client: Capistrano::Harrow::HTTP.new,
|
17
|
+
participation_url: participation_url,
|
18
|
+
)
|
19
|
+
|
20
|
+
config = Capistrano::Harrow::Config::Git.new(git_config)
|
21
|
+
ui = Capistrano::Harrow::UI::TTY.new(timeout: timeout)
|
22
|
+
installer = Capistrano::Harrow::Installer.new(ui: ui, config: config, api: api)
|
23
|
+
|
24
|
+
installer.install!
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def register_hooks
|
30
|
+
after "install", "harrow:install"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Capistrano
|
2
|
+
module Harrow
|
3
|
+
module UI
|
4
|
+
class TTY
|
5
|
+
def initialize(params={input: $stdin, output: $stdout, timeout: 30})
|
6
|
+
@in = params.fetch(:input, $stdin)
|
7
|
+
@out = params.fetch(:output, $stdout)
|
8
|
+
@timeout = params.fetch(:timeout, 60)
|
9
|
+
end
|
10
|
+
|
11
|
+
def show(text)
|
12
|
+
@out.puts text
|
13
|
+
end
|
14
|
+
|
15
|
+
def prompt_password(prompt_str)
|
16
|
+
`stty -echo 2>/dev/null`
|
17
|
+
password = prompt(prompt_str, [])
|
18
|
+
show "\n"
|
19
|
+
password
|
20
|
+
ensure
|
21
|
+
`stty echo 2>/dev/null`
|
22
|
+
end
|
23
|
+
|
24
|
+
def prompt(prompt_str, answers=['yes', 'no'])
|
25
|
+
answers = Array(answers)
|
26
|
+
|
27
|
+
@out.write prompt_str
|
28
|
+
|
29
|
+
default_answer = answers.first
|
30
|
+
hints = answers_hint(answers)
|
31
|
+
|
32
|
+
unless hints.empty?
|
33
|
+
@out.write " "
|
34
|
+
@out.write hints
|
35
|
+
end
|
36
|
+
|
37
|
+
@out.write ": "
|
38
|
+
|
39
|
+
unless IO.select([@in], [], [], @timeout)
|
40
|
+
raise TimeoutError.new
|
41
|
+
else
|
42
|
+
answer = @in.gets.chop
|
43
|
+
end
|
44
|
+
|
45
|
+
return default_answer if answer.empty?
|
46
|
+
|
47
|
+
answer
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def answers_hint(answers)
|
52
|
+
return '' if Array(answers).length == 0
|
53
|
+
items = answers.map(&:to_s).map(&:strip)
|
54
|
+
items[0] = items.first[0].upcase + items.first[1..-1]
|
55
|
+
"(#{items.join("/")})"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capistrano-harrow
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lee Hambley
|
8
|
+
- Dario Hamidi
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-04-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.11'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.11'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '10.0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '10.0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: minitest
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '5.0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '5.0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: pry
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - '='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.10.3
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - '='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.10.3
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: json
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - '='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.8.3
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - '='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 1.8.3
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: byebug
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - '='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 8.2.4
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - '='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: 8.2.4
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: sinatra
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - '='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 1.4.7
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - '='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: 1.4.7
|
112
|
+
description: Hooks to allow people experiencing problems with Capistrano to register
|
113
|
+
with a service to get help and have a smoother workflow.
|
114
|
+
email:
|
115
|
+
- leehambley@harrow.io
|
116
|
+
- dariohamidi@harrow.io
|
117
|
+
executables: []
|
118
|
+
extensions: []
|
119
|
+
extra_rdoc_files: []
|
120
|
+
files:
|
121
|
+
- ".gitignore"
|
122
|
+
- Gemfile
|
123
|
+
- README.md
|
124
|
+
- Rakefile
|
125
|
+
- bin/console
|
126
|
+
- bin/setup
|
127
|
+
- capistrano-harrow.gemspec
|
128
|
+
- integration-test-server.rb
|
129
|
+
- integration-test.rb
|
130
|
+
- lib/capistrano/harrow.rb
|
131
|
+
- lib/capistrano/harrow/api.rb
|
132
|
+
- lib/capistrano/harrow/banner.rb
|
133
|
+
- lib/capistrano/harrow/config.rb
|
134
|
+
- lib/capistrano/harrow/config/git.rb
|
135
|
+
- lib/capistrano/harrow/http.rb
|
136
|
+
- lib/capistrano/harrow/installer.rb
|
137
|
+
- lib/capistrano/harrow/plugin.rb
|
138
|
+
- lib/capistrano/harrow/ui.rb
|
139
|
+
- lib/capistrano/harrow/ui/tty.rb
|
140
|
+
- lib/capistrano/harrow/version.rb
|
141
|
+
homepage: https://github.com/harrowio/capistrano-harrow
|
142
|
+
licenses: []
|
143
|
+
metadata: {}
|
144
|
+
post_install_message: |2+
|
145
|
+
|
146
|
+
_ _ _
|
147
|
+
| | | | (_)
|
148
|
+
| |__| | __ _ _ __ _ __ _____ ___ ___
|
149
|
+
| __ |/ _` | '__| '__/ _ \ \ /\ / / |/ _ \
|
150
|
+
| | | | (_| | | | | | (_) \ V V /| | (_) |
|
151
|
+
|_| |_|\__,_|_| |_| \___/ \_/\_(_)_|\___/
|
152
|
+
|
153
|
+
Continuous Integration & Deployment
|
154
|
+
Built by the team behind Capistrano
|
155
|
+
|
156
|
+
Learn more at http://hrw.io/cap-for-teams
|
157
|
+
|
158
|
+
rdoc_options: []
|
159
|
+
require_paths:
|
160
|
+
- lib
|
161
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
167
|
+
requirements:
|
168
|
+
- - ">="
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
version: '0'
|
171
|
+
requirements: []
|
172
|
+
rubyforge_project:
|
173
|
+
rubygems_version: 2.6.3
|
174
|
+
signing_key:
|
175
|
+
specification_version: 4
|
176
|
+
summary: A plugin to improve the user experience for users of Capistrano and Harrow
|
177
|
+
test_files: []
|