capistrano-harrow 0.4.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.
- 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: []
|