capistrano-redmine-deployment 1.1.0 → 1.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6bcb935ad7aaa01c101fa9380a958b0dc57a3d6b1b39797fafb3eff6ae03a05
4
- data.tar.gz: adaff749719d3b3178b3f6f23e71d9605c8785ff37c773d2422eb2a76f568956
3
+ metadata.gz: 8e5b503dd2ed090f4e55888e0ff5f2d7cfa8379b042a7ad0a93f83753e49e05f
4
+ data.tar.gz: 9a3c275c8fcb7496c0558f836f4b59762dabdee0c563e03821af18f0f110be79
5
5
  SHA512:
6
- metadata.gz: c552022bdeac49a92283af11c3e99869efb1509be175ee68179759300c6c3fde2e502b1e096736157f20e4382e77bdd1a934ceb93c6a82a2d736bd544a3ba9c1
7
- data.tar.gz: b3d6ae5239ac13d3042f5228ba2a6175a3a0d1b115e186196bf26418d50db3518cb3ea4da397d7c5893f4203db3047ffa3b727ead08e30cddf69f5c25227ac2d
6
+ metadata.gz: 20c4e9147ffc279dc29f0037846e18cfab6604caa5d1792f6fe450247ad31335c5d528b7b7e000014b82e7870116089407756faa352ee0ecd0e1473660cd1166
7
+ data.tar.gz: 4000c73ce5970b91fc125f24fe3f83abec6b4ddf0cfc18569b0ace547cc849fe9bdf56d3efb33dc1e3f235cf70ef2c5666f012d56ab922aa26a8fd839f608e06
data/.gitignore CHANGED
File without changes
data/README.md CHANGED
@@ -88,12 +88,24 @@ Setup redmine `API-KEY` through rake-task:
88
88
  $ rake capistrano:redmine:deployment:setup
89
89
 
90
90
 
91
+ ## Verifying the setup
92
+
93
+ Once configured, verify that the credentials, host, project and repository resolve
94
+ to a reachable `redmine_deployment` endpoint:
95
+
96
+ $ cap <stage> redmine:verify
97
+
98
+ This receives a single deployment entry from redmine. A response with **no** entries
99
+ (i.e. no deployment has been logged yet) still counts as a success — it only fails
100
+ when the host is unreachable or the credentials / project / repository are wrong.
101
+
91
102
  ## Redmine requirements
92
103
 
93
104
  Install the plugin `redmine_deployment`.
94
105
 
95
106
  ## Features
96
107
  * logs (success/failed) deployment to associated redmine repository
108
+ * verifies redmine access / configuration (`redmine:verify`)
97
109
 
98
110
  -----
99
111
 
data/docs/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Capistrano::Redmine::Deployment - CHANGELOG
2
2
 
3
+ ## [1.2.1] - 2026-07-11
4
+ **[add]** `redmine:verify` task to validate deployment config using `deploy.rb` settings
5
+
6
+ ## [1.2.0] - 2026-07-10
7
+ * **[add]** `redmine:verify` capistrano task to verify config & access by receiving a single deployment
8
+ * **[add]** `Client#receive_deployment` (and `Client.receive_deployment`) - GETs a single deployment entry; an empty (no entries) 2xx response is still treated as valid
9
+ * **[add]** rspec test suite for `Client#receive_deployment`
10
+
3
11
  ## [1.1.0] - 2026-07-10
4
12
  * **[add]** ENV layer for config (`REDMINE_API_KEY`, `REDMINE_HOST`, `REDMINE_PROJECT`, `REDMINE_REPOSITORY`, `REDMINE_CA_FILE`)
5
13
  * **[add]** `:redmine_api_key` capistrano variable, with `:redmine_api_key_command` (command stdout, e.g. macOS keychain) as fallback
@@ -7,37 +7,105 @@ require 'openssl'
7
7
  module Capistrano
8
8
  module Redmine
9
9
  module Deployment
10
+ # HTTP layer that talks to the Redmine-side +redmine_deployment+ plugin.
11
+ #
12
+ # The client POSTs deployment receipts to, and GETs deployment entries
13
+ # from, +{host}/projects/{project}/deploy/{repository}.json+, authenticating
14
+ # with an +X-Redmine-API-Key+ header. A response carrying a +deployment+ key
15
+ # is treated as success; anything else (or an +errors+ array) is a failure.
16
+ #
17
+ # All connection details (host, project, repository, api_key, ca_file,
18
+ # host_verification) are read from the supplied {Config}. SSL is enabled
19
+ # automatically when the host URI uses the +https+ scheme. Console logging
20
+ # is on by default and can be turned off via {#silent!} or the +logging:+
21
+ # initializer flag.
22
+ #
23
+ # @example Log a successful deployment
24
+ # Client.deploy_success!(config, from_revision: 'abc', to_revision: 'def')
25
+ #
26
+ # @example Verify connectivity without writing anything
27
+ # Client.receive_deployment(config) # => {"id"=>7, ...}, nil, or false
10
28
  class Client
11
29
 
30
+ # @return [Config] the resolved configuration this client reads from.
12
31
  attr_reader :config
13
32
 
14
33
  class << self
34
+ # Logs a successful deployment.
35
+ #
36
+ # Tags the deployment as +'success'+ and POSTs it to Redmine via a
37
+ # fresh instance.
38
+ #
39
+ # @param config [Config] the resolved configuration.
40
+ # @param deployment [Hash] the deployment payload (mutated in place to
41
+ # set +:result+).
42
+ # @return [Boolean] +true+ when Redmine accepted the deployment.
15
43
  def deploy_success!(config, deployment)
16
44
  deployment[:result] = 'success'
17
45
 
18
46
  new(config).deploy!(deployment)
19
47
  end
20
48
 
49
+ # Logs a failed deployment.
50
+ #
51
+ # Tags the deployment as +'fail'+ and POSTs it to Redmine via a fresh
52
+ # instance.
53
+ #
54
+ # @param config [Config] the resolved configuration.
55
+ # @param deployment [Hash] the deployment payload (mutated in place to
56
+ # set +:result+).
57
+ # @return [Boolean] +true+ when Redmine accepted the deployment.
21
58
  def deploy_fail!(config, deployment)
22
59
  deployment[:result] = 'fail'
23
60
 
24
61
  new(config).deploy!(deployment)
25
62
  end
63
+
64
+ # Fetches a single deployment entry to verify connectivity.
65
+ #
66
+ # Convenience wrapper that builds a fresh instance and delegates to
67
+ # {#receive_deployment}.
68
+ #
69
+ # @param config [Config] the resolved configuration.
70
+ # @return [Hash, nil, false] the deployment hash, +nil+ when the
71
+ # endpoint is reachable but empty, or +false+ on a failed request.
72
+ def receive_deployment(config)
73
+ new(config).receive_deployment
74
+ end
26
75
  end
27
76
 
77
+ # @param config [Config] the resolved configuration to read connection
78
+ # details and credentials from.
79
+ # @param logging [Boolean] whether to print progress to STDOUT
80
+ # (default +true+). See {#silent!}.
28
81
  def initialize(config, logging: true)
29
82
  @config = config
30
83
  @logging = logging
31
84
  end
32
85
 
86
+ # Disables console logging for this client.
87
+ #
88
+ # @return [false]
33
89
  def silent!
34
90
  @logging = false
35
91
  end
36
92
 
93
+ # @return [Boolean] whether console logging is currently enabled.
37
94
  def log?
38
95
  @logging
39
96
  end
40
97
 
98
+ # POSTs a deployment receipt to Redmine.
99
+ #
100
+ # Logs the outgoing deployment, sends it, and reports the outcome. A
101
+ # response containing a +deployment+ key counts as success; anything
102
+ # else is logged as an error.
103
+ #
104
+ # @param deployment [Hash] the deployment payload. Recognized keys
105
+ # include +:result+, +:from_revision+, +:to_revision+, +:environment+,
106
+ # +:branch+ and +:servers+.
107
+ # @return [Boolean] +true+ when Redmine created the deployment,
108
+ # +false+ otherwise.
41
109
  def deploy!(deployment)
42
110
  log_deploy(deployment) if log?
43
111
 
@@ -54,11 +122,98 @@ module Capistrano
54
122
  end
55
123
  end
56
124
 
125
+ # Receives a single deployment entry from the redmine repository.
126
+ #
127
+ # Used to verify that the configured credentials / host / project /
128
+ # repository actually resolve to a reachable redmine_deployment endpoint.
129
+ # A successful response with NO entries is still considered valid - it
130
+ # just means no deployment has been logged yet.
131
+ #
132
+ # @return [Hash] the single deployment entry when one exists.
133
+ # @return [nil] when the endpoint is reachable but has no entries yet.
134
+ # @return [false] when the request itself failed (non-2xx / errors).
135
+ def receive_deployment
136
+ response = fetch_deployments
137
+
138
+ return false unless response.is_a?(Hash)
139
+ return false if response['errors']
140
+
141
+ extract_deployment(response)
142
+ end
143
+
57
144
  private
58
145
 
146
+ # Builds and performs the POST request carrying the deployment payload.
147
+ #
148
+ # @param deployment [Hash] the deployment payload to serialize as JSON.
149
+ # @return [Hash, String] the parsed JSON response, or the raw body when
150
+ # it is not valid JSON.
59
151
  def send_deployment(deployment)
60
- uri = URI("#{config.host}/projects/#{config.project}/deploy/#{config.repository}.json")
152
+ uri = deploy_uri
153
+
154
+ request = Net::HTTP::Post.new(uri.request_uri)
155
+ request["Content-Type"] = "application/json"
156
+ request['X-Redmine-API-Key'] = config.api_key
157
+ request.body = { deployment: deployment }.to_json
158
+
159
+ perform(uri, request)
160
+ end
161
+
162
+ # Builds and performs the GET request that fetches deployment entries.
163
+ #
164
+ # Requests only a single entry (+limit=1+) since that is all that is
165
+ # needed to confirm access.
166
+ #
167
+ # @return [Hash, String] the parsed JSON response, or the raw body when
168
+ # it is not valid JSON.
169
+ def fetch_deployments
170
+ uri = deploy_uri
171
+ # only a single entry is needed to verify access
172
+ uri.query = 'limit=1'
173
+
174
+ request = Net::HTTP::Get.new(uri.request_uri)
175
+ request["Content-Type"] = "application/json"
176
+ request['X-Redmine-API-Key'] = config.api_key
61
177
 
178
+ perform(uri, request)
179
+ end
180
+
181
+ # Pulls a single deployment out of an index/show response. The
182
+ # redmine_deployment plugin may return either a single +deployment+
183
+ # object or a +deployments+ collection - an empty collection is fine.
184
+ #
185
+ # @param response [Object] the parsed response body.
186
+ # @return [Hash] the +deployment+ object, or the first entry of a
187
+ # +deployments+ collection.
188
+ # @return [nil] when +response+ is not a hash, or when a +deployments+
189
+ # collection is empty.
190
+ def extract_deployment(response)
191
+ return nil unless response.is_a?(Hash)
192
+
193
+ if response['deployment']
194
+ response['deployment']
195
+ elsif response['deployments']
196
+ response['deployments'].first
197
+ end
198
+ end
199
+
200
+ # @return [URI::Generic] the deploy endpoint URI for the configured
201
+ # host / project / repository.
202
+ def deploy_uri
203
+ URI("#{config.host}/projects/#{config.project}/deploy/#{config.repository}.json")
204
+ end
205
+
206
+ # Performs an HTTP request, applying SSL and host-verification settings.
207
+ #
208
+ # SSL is enabled when the URI scheme is +https+. A configured +ca_file+
209
+ # takes precedence; otherwise +host_verification == false+ disables peer
210
+ # verification (the LetsEncrypt/CRL workaround).
211
+ #
212
+ # @param uri [URI::Generic] the request target (supplies host/port/scheme).
213
+ # @param request [Net::HTTPRequest] the prepared request to send.
214
+ # @return [Hash, Array, String] the parsed JSON response, or the raw body
215
+ # when it is not valid JSON.
216
+ def perform(uri, request)
62
217
  http = Net::HTTP.new(uri.host, uri.port)
63
218
  http.use_ssl = true if uri.scheme == 'https'
64
219
 
@@ -68,11 +223,6 @@ module Capistrano
68
223
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
69
224
  end
70
225
 
71
- request = Net::HTTP::Post.new(uri.request_uri)
72
- request["Content-Type"] = "application/json"
73
- request['X-Redmine-API-Key'] = config.api_key
74
- request.body = { deployment: deployment }.to_json
75
-
76
226
  response = http.request(request)
77
227
 
78
228
  begin
@@ -82,6 +232,10 @@ module Capistrano
82
232
  end
83
233
  end
84
234
 
235
+ # Prints the outgoing deployment details (and any SSL warnings) to STDOUT.
236
+ #
237
+ # @param deployment [Hash] the deployment payload being sent.
238
+ # @return [void]
85
239
  def log_deploy(deployment)
86
240
  puts "Sending deployment information to #{config.host} (project: '#{config.project}' | repo: '#{config.repository}')"
87
241
  puts "\e[33m Using custom CA file: #{config.ca_file}\e[0m" if config.ca_file
@@ -95,11 +249,20 @@ module Capistrano
95
249
  puts ""
96
250
  end
97
251
 
252
+ # Prints a success line naming the created deployment id.
253
+ #
254
+ # @param response [Hash] the parsed success response.
255
+ # @return [void]
98
256
  def log_deploy_done(response)
99
257
  puts "\e[32mSuccessfully created deployment ##{response['deployment']['id']}\e[0m"
100
258
  puts ""
101
259
  end
102
260
 
261
+ # Prints a failure line, listing +errors+ when present or the raw
262
+ # response otherwise.
263
+ #
264
+ # @param response [Object] the parsed failure response.
265
+ # @return [void]
103
266
  def log_deploy_errors(response)
104
267
  if response['errors']
105
268
  puts "\e[31mFailed to created deployment: #{response['errors'].join(', ')}\e[0m"
@@ -112,5 +275,3 @@ module Capistrano
112
275
  end
113
276
  end
114
277
  end
115
-
116
-
@@ -10,8 +10,8 @@ module Capistrano
10
10
 
11
11
  module VERSION
12
12
  MAJOR = 1
13
- MINOR = 1
14
- TINY = 0
13
+ MINOR = 2
14
+ TINY = 1
15
15
  PRE = nil
16
16
 
17
17
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -71,4 +71,30 @@ namespace :redmine do
71
71
  Capistrano::Redmine::Deployment::Client.deploy_fail!(config, deployment)
72
72
  end
73
73
  end
74
+
75
+ desc "Verifies your redmine deployment configuration & access (honors config/deploy.rb)"
76
+ task :verify do
77
+ run_locally do
78
+ # resolve redmine config - `capistrano: self` pulls the shared
79
+ # `set(:redmine_host, ...)` values from config/deploy.rb, then `.redmine`
80
+ # files and ENV win over them.
81
+ config = Capistrano::Redmine::Deployment::Config.resolve(capistrano: self)
82
+
83
+ unless config.valid?
84
+ puts "\e[31m Seems like your redmine configuration is missing or unfinished.\n Run rake task 'rake capistrano:redmine:deployment:setup' to start.\n Skipping redmine verification.\e[0m"
85
+ next
86
+ end
87
+
88
+ # An empty result (no deployments logged yet) is fine - `false` means the
89
+ # request itself failed (unreachable host, bad credentials, wrong project/repo).
90
+ result = Capistrano::Redmine::Deployment::Client.receive_deployment(config)
91
+
92
+ puts ""
93
+ if result == false
94
+ puts "\e[31mVerification FAILED. Check host, project, repository and api_key.\e[0m"
95
+ else
96
+ puts "\e[32mVerification succeeded.\e[0m"
97
+ end
98
+ end
99
+ end
74
100
  end
@@ -0,0 +1,62 @@
1
+ namespace :capistrano do
2
+ namespace :redmine do
3
+ namespace :deployment do
4
+ desc <<-END_DESC
5
+ Verify the redmine credentials & access for deployment.
6
+ END_DESC
7
+
8
+ task :verify do
9
+ require 'capistrano/redmine/deployment/config'
10
+ require 'capistrano/redmine/deployment/client'
11
+
12
+ # resolve configs (from ENV / .redmine file - no capistrano context here)
13
+ config = Capistrano::Redmine::Deployment::Config.resolve
14
+
15
+ puts "******************************************************************************************************"
16
+ puts "== Capistrano::Redmine::Deployment - verify =="
17
+ puts "******************************************************************************************************"
18
+ puts ""
19
+ puts " This task verifies your redmine deployment configuration & access."
20
+ puts " It receives a single deployment entry from the associated redmine repository."
21
+ puts ""
22
+ puts " Shared settings are resolved from ENV or the '.redmine' file (host, project,"
23
+ puts " repository, api_key). Run 'rake capistrano:redmine:deployment:setup' to configure them."
24
+ puts ""
25
+ puts " NOTE: this rake task has no Capistrano context, so 'set(:redmine_host, ...)'"
26
+ puts " values from config/deploy.rb are NOT seen here. To verify using deploy.rb"
27
+ puts " settings, run the stage-aware task instead: cap <stage> redmine:verify"
28
+ puts ""
29
+ puts "******************************************************************************************************"
30
+ puts ""
31
+ puts ""
32
+
33
+ # -- BEGIN over here ...
34
+
35
+ unless config.valid?
36
+ puts "\e[31mYour redmine configuration is missing or unfinished.\e[0m"
37
+ puts "Run 'cap <stage> redmine:verify' to verify the stage-aware config."
38
+ puts "Run 'rake capistrano:redmine:deployment:setup' to configure the credentials."
39
+ puts ""
40
+ puts "******************************************************************************************************"
41
+ puts ""
42
+ next
43
+ end
44
+
45
+ # An empty result (no deployments logged yet) is fine - `false` means the
46
+ # request itself failed (unreachable host, bad credentials, wrong project/repo).
47
+ result = Capistrano::Redmine::Deployment::Client.receive_deployment(config)
48
+
49
+ puts ""
50
+ if result == false
51
+ puts "\e[31mVerification FAILED. Check host, project, repository and api_key.\e[0m"
52
+ else
53
+ puts "\e[32mVerification succeeded.\e[0m"
54
+ end
55
+
56
+ puts ""
57
+ puts "******************************************************************************************************"
58
+ puts ""
59
+ end
60
+ end
61
+ end
62
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capistrano-redmine-deployment
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Gonsior
@@ -36,6 +36,7 @@ files:
36
36
  - lib/capistrano/redmine/deployment/railtie.rb
37
37
  - lib/capistrano/redmine/deployment/receipts.rb
38
38
  - lib/capistrano/redmine/deployment/tasks/setup.rake
39
+ - lib/capistrano/redmine/deployment/tasks/verify.rake
39
40
  - lib/capistrano/redmine/deployment/version.rb
40
41
  homepage: https://github.com/ruby-smart/capistrano-redmine-deployment
41
42
  licenses: