capistrano-redmine-deployment 1.1.0 → 1.2.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 +4 -4
- data/.gitignore +0 -0
- data/README.md +12 -0
- data/docs/CHANGELOG.md +5 -0
- data/lib/capistrano/redmine/deployment/client.rb +169 -8
- data/lib/capistrano/redmine/deployment/gem_version.rb +1 -1
- data/lib/capistrano/redmine/deployment/receipts.rb +0 -0
- data/lib/capistrano/redmine/deployment/tasks/verify.rake +57 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5586e101b5eda84c25017c252b6a01d26715c4fbab7ee71e609a84a20b7e47cc
|
|
4
|
+
data.tar.gz: 9e85e888ef2336c3bdc760188cd2d18f10586b1dbabab0367d631f07f4c729da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61c1a9c03f4f60a48f3e4c17f601020b1c6802bee6dcca09d26a1d4f79a777a145696c588dedf0927ddf7e58e665f6cd659653ba033c91ded060555d0e73756e
|
|
7
|
+
data.tar.gz: e18d575a41748df42b3cb263031fdf6b9152258a5a3bc42f31edf303c15a12f02bbd8cfcb50c5448f27c66a873e26a67c6981fc856a8046ad4aeba45765c2b73
|
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,10 @@
|
|
|
1
1
|
# Capistrano::Redmine::Deployment - CHANGELOG
|
|
2
2
|
|
|
3
|
+
## [1.2.0] - 2026-07-10
|
|
4
|
+
* **[add]** `redmine:verify` capistrano task to verify config & access by receiving a single deployment
|
|
5
|
+
* **[add]** `Client#receive_deployment` (and `Client.receive_deployment`) - GETs a single deployment entry; an empty (no entries) 2xx response is still treated as valid
|
|
6
|
+
* **[add]** rspec test suite for `Client#receive_deployment`
|
|
7
|
+
|
|
3
8
|
## [1.1.0] - 2026-07-10
|
|
4
9
|
* **[add]** ENV layer for config (`REDMINE_API_KEY`, `REDMINE_HOST`, `REDMINE_PROJECT`, `REDMINE_REPOSITORY`, `REDMINE_CA_FILE`)
|
|
5
10
|
* **[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 =
|
|
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
|
-
|
|
File without changes
|
|
@@ -0,0 +1,57 @@
|
|
|
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 "******************************************************************************************************"
|
|
26
|
+
puts ""
|
|
27
|
+
puts ""
|
|
28
|
+
|
|
29
|
+
# -- BEGIN over here ...
|
|
30
|
+
|
|
31
|
+
unless config.valid?
|
|
32
|
+
puts "\e[31mYour redmine configuration is missing or unfinished.\e[0m"
|
|
33
|
+
puts "Run 'rake capistrano:redmine:deployment:setup' to configure the credentials."
|
|
34
|
+
puts ""
|
|
35
|
+
puts "******************************************************************************************************"
|
|
36
|
+
puts ""
|
|
37
|
+
next
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# An empty result (no deployments logged yet) is fine - `false` means the
|
|
41
|
+
# request itself failed (unreachable host, bad credentials, wrong project/repo).
|
|
42
|
+
result = Capistrano::Redmine::Deployment::Client.receive_deployment(config)
|
|
43
|
+
|
|
44
|
+
puts ""
|
|
45
|
+
if result == false
|
|
46
|
+
puts "\e[31mVerification FAILED. Check host, project, repository and api_key.\e[0m"
|
|
47
|
+
else
|
|
48
|
+
puts "\e[32mVerification succeeded.\e[0m"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
puts ""
|
|
52
|
+
puts "******************************************************************************************************"
|
|
53
|
+
puts ""
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
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.
|
|
4
|
+
version: 1.2.0
|
|
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:
|