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 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: []