capistrano-harrow 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.gem
11
+ /integration.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in capistrano-harrow.gemspec
4
+ gemspec
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -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,9 @@
1
+ require "capistrano/harrow/config/git"
2
+
3
+ module Capistrano
4
+ module Harrow
5
+ module Config
6
+ class BackendError < StandardError; end
7
+ end
8
+ end
9
+ 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,10 @@
1
+ require "capistrano/harrow/ui/tty"
2
+
3
+ module Capistrano
4
+ module Harrow
5
+ module UI
6
+ class TimeoutError < StandardError; end
7
+
8
+ end
9
+ end
10
+ 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
@@ -0,0 +1,5 @@
1
+ module Capistrano
2
+ module Harrow
3
+ VERSION = "0.4.0"
4
+ end
5
+ 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: []