robopigeon 0.1.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 +16 -0
- data/.gitlab-ci.yml +27 -0
- data/.rspec +3 -0
- data/.rubocop.yml +65 -0
- data/.ruby-version +1 -0
- data/.version +1 -0
- data/Gemfile +4 -0
- data/README.md +256 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/bin/setup +8 -0
- data/exe/robopigeon +22 -0
- data/lib/robopigeon/documentarian.rb +39 -0
- data/lib/robopigeon/dsl/helpers.rb +17 -0
- data/lib/robopigeon/dsl/initial_jobs.rb +36 -0
- data/lib/robopigeon/dsl/job.rb +34 -0
- data/lib/robopigeon/dsl.rb +46 -0
- data/lib/robopigeon/git/helper_dsl.rb +60 -0
- data/lib/robopigeon/git.rb +7 -0
- data/lib/robopigeon/gitlab/client.rb +42 -0
- data/lib/robopigeon/gitlab/dsl.rb +174 -0
- data/lib/robopigeon/gitlab/helper_dsl.rb +44 -0
- data/lib/robopigeon/gitlab/jira.rb +0 -0
- data/lib/robopigeon/gitlab/merge_request.rb +78 -0
- data/lib/robopigeon/gitlab.rb +29 -0
- data/lib/robopigeon/jira/client.rb +17 -0
- data/lib/robopigeon/jira/dsl.rb +81 -0
- data/lib/robopigeon/jira/helper_dsl.rb +24 -0
- data/lib/robopigeon/jira/ticket.rb +186 -0
- data/lib/robopigeon/jira/ticket_dsl.rb +155 -0
- data/lib/robopigeon/jira.rb +37 -0
- data/lib/robopigeon/markdown/helper_dsl.rb +73 -0
- data/lib/robopigeon/markdown.rb +11 -0
- data/lib/robopigeon/resources/initial_robopigeon.rb +156 -0
- data/lib/robopigeon/slack/attachments_dsl.rb +63 -0
- data/lib/robopigeon/slack/client.rb +46 -0
- data/lib/robopigeon/slack/dsl.rb +74 -0
- data/lib/robopigeon/slack/helper_dsl.rb +26 -0
- data/lib/robopigeon/slack/message.rb +35 -0
- data/lib/robopigeon/slack.rb +30 -0
- data/lib/robopigeon/version.rb +3 -0
- data/lib/robopigeon.rb +13 -0
- data/robopigeon +0 -0
- data/robopigeon.gemspec +47 -0
- data/robopigeon.rb +156 -0
- metadata +264 -0
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module RoboPigeon::Jira
|
4
|
+
class Ticket
|
5
|
+
MAX_TRANSITIONS = 10
|
6
|
+
ISSUE_PATH = '/rest/api/2/issue'.freeze
|
7
|
+
attr_accessor :ticket, :project, :issue_type, :summary, :fields,
|
8
|
+
:assignee, :reporter, :description
|
9
|
+
|
10
|
+
def initialize(ticket = nil)
|
11
|
+
self.ticket = ticket
|
12
|
+
self.fields = {}
|
13
|
+
update_from_server
|
14
|
+
self.project = ticket.split('-')[0] if ticket
|
15
|
+
end
|
16
|
+
|
17
|
+
def update_from_server
|
18
|
+
return unless ticket
|
19
|
+
|
20
|
+
get = jira_request.get("#{ISSUE_PATH}/#{ticket}/")
|
21
|
+
raise "Failed to look up issue #{ticket}" unless get.status < 400
|
22
|
+
|
23
|
+
raw = JSON.parse(get.body)
|
24
|
+
self.project = raw['fields']['project']['key']
|
25
|
+
self.issue_type = raw['fields']['issuetype']['name']
|
26
|
+
self.summary = raw['fields']['summary']
|
27
|
+
self.reporter = raw['fields']['reporter']['name'] if raw['fields']['reporter']
|
28
|
+
self.assignee = raw['fields']['assignee']['name'] if raw['fields']['assignee']
|
29
|
+
end
|
30
|
+
|
31
|
+
def jira_request
|
32
|
+
RoboPigeon::Jira::Client.jira_request
|
33
|
+
end
|
34
|
+
|
35
|
+
def create!
|
36
|
+
response = jira_request.post do |req|
|
37
|
+
req.url '/rest/api/2/issue'
|
38
|
+
req.body = {
|
39
|
+
fields: {
|
40
|
+
project: { key: project },
|
41
|
+
issuetype: { name: issue_type },
|
42
|
+
summary: summary,
|
43
|
+
description: description,
|
44
|
+
assignee: { name: reporter }
|
45
|
+
}.merge(fields)
|
46
|
+
}.to_json
|
47
|
+
end
|
48
|
+
|
49
|
+
begin
|
50
|
+
self.ticket = JSON.parse(response.body).fetch('key')
|
51
|
+
RoboPigeon::Jira::Client.last_created_ticket = ticket
|
52
|
+
rescue KeyError
|
53
|
+
raise RoboPigeon::Jira::RequiredFieldNotSet, "Response from JIRA did not contain new issue key: #{response.body}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def assign(user_email)
|
58
|
+
user_data = jira_request.get("/rest/api/2/user/search?username=#{user_email}")
|
59
|
+
user = JSON.parse(user_data.body).first
|
60
|
+
raise "Unable to find jira user with email #{user_email}" unless user
|
61
|
+
|
62
|
+
self.assignee = user['name']
|
63
|
+
self.reporter ||= user['name']
|
64
|
+
|
65
|
+
if ticket
|
66
|
+
jira_request.post do |req|
|
67
|
+
req.url "/rest/api/3/issue/#{ticket}/assignee"
|
68
|
+
req.body = { name: assignee }.to_json
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def set_reporter(user_email)
|
74
|
+
raise 'Cannot modify reporter' if ticket
|
75
|
+
|
76
|
+
user_data = jira_request.get("/rest/api/2/user/search?username=#{user_email}")
|
77
|
+
user = JSON.parse(user_data.body).first
|
78
|
+
raise "Unable to find jira user with email #{user_email}" unless user
|
79
|
+
|
80
|
+
self.reporter = user['name']
|
81
|
+
end
|
82
|
+
|
83
|
+
def description_from_template(_file)
|
84
|
+
self.description = 'Content from an erb template'
|
85
|
+
end
|
86
|
+
|
87
|
+
def set_field(name, value)
|
88
|
+
require_field(:issue_type)
|
89
|
+
request_path = "#{ISSUE_PATH}/createmeta?projectKeys=#{project}&expand=projects.issuetypes.fields"
|
90
|
+
response = jira_request.get(request_path)
|
91
|
+
raw = JSON.parse(response.body)
|
92
|
+
fields = raw['projects'].first['issuetypes'].select do |it|
|
93
|
+
it['name'] == issue_type
|
94
|
+
end.first['fields']
|
95
|
+
field = fields.keys.select do |key|
|
96
|
+
fields[key]['name'] == name
|
97
|
+
end.first
|
98
|
+
|
99
|
+
raise "Field #{name} was not found" unless field
|
100
|
+
|
101
|
+
self.fields[field] = if fields[field]['allowedValues'].nil?
|
102
|
+
value
|
103
|
+
else
|
104
|
+
{ value: value }
|
105
|
+
end
|
106
|
+
|
107
|
+
if ticket
|
108
|
+
post = jira_request.put do |req|
|
109
|
+
req.url "#{ISSUE_PATH}/#{ticket}/"
|
110
|
+
req.body = { fields: self.fields }.to_json
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
raise 'Failed to update field' unless post.nil? || post.status < 400
|
115
|
+
end
|
116
|
+
|
117
|
+
def set_issuetype(name)
|
118
|
+
require_field(:project)
|
119
|
+
request_path = "#{ISSUE_PATH}/createmeta?projectKeys=#{project}&expand=projects.issuetypes.fields"
|
120
|
+
response = jira_request.get(request_path)
|
121
|
+
raw = JSON.parse(response.body)
|
122
|
+
types = raw['projects'].first['issuetypes'].map { |type| type['name'] }
|
123
|
+
raise RoboPigeon::Jira::FieldDoesNotConform, "Issue type #{name} was not found in #{types.join(', ')}" unless types.include?(name)
|
124
|
+
|
125
|
+
self.issue_type = name
|
126
|
+
end
|
127
|
+
|
128
|
+
def perform_transition(transition)
|
129
|
+
require_ticket
|
130
|
+
get_fields = jira_request.get("#{ISSUE_PATH}/#{ticket}/transitions?expand=transitions.fields")
|
131
|
+
transition_details = JSON.parse(get_fields.body)['transitions'].find do |trans|
|
132
|
+
trans['name'].casecmp(transition).zero?
|
133
|
+
end
|
134
|
+
raise "Unable to find valid transition '#{transition}'" unless transition_details
|
135
|
+
|
136
|
+
jira_request.post do |req|
|
137
|
+
req.url "#{ISSUE_PATH}/#{ticket}/transitions"
|
138
|
+
req.body = { transition: { id: transition_details['id'] } }.to_json
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def current_state
|
143
|
+
require_ticket
|
144
|
+
get = jira_request.get do |req|
|
145
|
+
req.url "#{ISSUE_PATH}/#{ticket}/"
|
146
|
+
end
|
147
|
+
JSON.parse(get.body)['fields']['status']['name']
|
148
|
+
end
|
149
|
+
|
150
|
+
def wait_for_state!(state, timeout=10.minutes, time=Time.now, sleep_time=15)
|
151
|
+
require_ticket
|
152
|
+
waited = (Time.now - time).to_i
|
153
|
+
minutes = waited / 60
|
154
|
+
seconds = waited % 60
|
155
|
+
if current_state == state
|
156
|
+
puts "#{ticket} in #{state} after #{minutes} minutes #{seconds} seconds"
|
157
|
+
return
|
158
|
+
end
|
159
|
+
raise RoboPigeon::Jira::WaitTimeout, "Timed out waiting for #{ticket} to transition to #{state} after #{minutes} minutes #{seconds} seconds" if (timeout.to_i - waited) <= 0
|
160
|
+
|
161
|
+
puts "Still waiting for #{ticket} in #{state}... waited #{minutes} minutes #{seconds} seconds"
|
162
|
+
sleep sleep_time
|
163
|
+
wait_for_state!(state, timeout, time, sleep_time)
|
164
|
+
end
|
165
|
+
|
166
|
+
def add_comment(comment)
|
167
|
+
require_ticket
|
168
|
+
|
169
|
+
post = jira_request.post do |req|
|
170
|
+
req.url "#{ISSUE_PATH}/#{ticket}/comment"
|
171
|
+
req.body = { body: comment }.to_json
|
172
|
+
end
|
173
|
+
raise 'Failed to create comment' unless post.status == 201
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
def require_ticket
|
179
|
+
raise RoboPigeon::Jira::TicketNotFoundOrSet, 'Ticket was not set!' if ticket.nil?
|
180
|
+
end
|
181
|
+
|
182
|
+
def require_field(field)
|
183
|
+
raise RoboPigeon::Jira::RequiredFieldNotSet, "You must set a #{field}" if send(field).nil?
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module RoboPigeon::Dsl
|
2
|
+
class JiraTicket
|
3
|
+
include RoboPigeon::Dsl::Helpers
|
4
|
+
attr_accessor :ticket
|
5
|
+
|
6
|
+
def self.run(ticket_number = nil, &block)
|
7
|
+
ticket = RoboPigeon::Jira::Ticket.new(ticket_number)
|
8
|
+
jira = RoboPigeon::Dsl::JiraTicket.new
|
9
|
+
jira.ticket = ticket
|
10
|
+
jira.instance_eval(&block)
|
11
|
+
jira.ticket.create! unless jira.ticket.ticket
|
12
|
+
ticket
|
13
|
+
end
|
14
|
+
|
15
|
+
RoboPigeon::Documentarian.add_command(
|
16
|
+
'assign',
|
17
|
+
block: %w[job jira ticket],
|
18
|
+
params: [
|
19
|
+
{ name: 'email', type: 'String', desc: 'the email address for the person to assign the ticket to', example: 'someone@example.com' }
|
20
|
+
],
|
21
|
+
desc: 'assign the ticket to a person by email address'
|
22
|
+
)
|
23
|
+
def assign(email)
|
24
|
+
ticket.assign(email)
|
25
|
+
end
|
26
|
+
|
27
|
+
RoboPigeon::Documentarian.add_command(
|
28
|
+
'reporter',
|
29
|
+
block: %w[job jira ticket],
|
30
|
+
params: [
|
31
|
+
{ name: 'email', type: 'String', desc: 'the email address for the person to reporter the ticket to', example: 'someone@example.com' }
|
32
|
+
],
|
33
|
+
desc: 'reporter the ticket to a person by email address'
|
34
|
+
)
|
35
|
+
def reporter(email)
|
36
|
+
ticket.set_reporter(email)
|
37
|
+
end
|
38
|
+
|
39
|
+
RoboPigeon::Documentarian.add_command(
|
40
|
+
'comment',
|
41
|
+
block: %w[job jira ticket],
|
42
|
+
params: [
|
43
|
+
{ name: 'comment', type: 'String', desc: 'the comment to add to the ticket', example: 'A string comment' }
|
44
|
+
],
|
45
|
+
desc: 'add a comment to the ticket'
|
46
|
+
)
|
47
|
+
def comment(comment)
|
48
|
+
ticket.add_comment(comment)
|
49
|
+
end
|
50
|
+
|
51
|
+
RoboPigeon::Documentarian.add_command(
|
52
|
+
'project',
|
53
|
+
block: %w[job jira ticket],
|
54
|
+
params: [
|
55
|
+
{ name: 'project_key', type: 'String', desc: 'the key for the project you want to create the ticket in', example: 'TIC' }
|
56
|
+
],
|
57
|
+
desc: 'set which project you want to create the ticket in (only used when creating)'
|
58
|
+
)
|
59
|
+
def project(project_key)
|
60
|
+
ticket.project = project_key
|
61
|
+
end
|
62
|
+
|
63
|
+
RoboPigeon::Documentarian.add_command(
|
64
|
+
'summary',
|
65
|
+
block: %w[job jira ticket],
|
66
|
+
params: [
|
67
|
+
{ name: 'title', type: 'String', desc: 'title summary for your issue', example: 'A Bug has happend!' }
|
68
|
+
],
|
69
|
+
desc: 'set the issue summary (only used in creation, use the field funtion for updates)'
|
70
|
+
)
|
71
|
+
def summary(title)
|
72
|
+
ticket.summary = title
|
73
|
+
end
|
74
|
+
|
75
|
+
RoboPigeon::Documentarian.add_command(
|
76
|
+
'description',
|
77
|
+
block: %w[job jira ticket],
|
78
|
+
params: [
|
79
|
+
{ name: 'text', type: 'String', desc: 'the main body of a new issue', example: 'Lots of content in here' }
|
80
|
+
],
|
81
|
+
desc: 'Set the main body of the issue here, only used in creation, use the field function to update'
|
82
|
+
)
|
83
|
+
def description(text)
|
84
|
+
ticket.description = text
|
85
|
+
end
|
86
|
+
|
87
|
+
RoboPigeon::Documentarian.add_command(
|
88
|
+
'issuetype',
|
89
|
+
block: %w[job jira ticket],
|
90
|
+
params: [
|
91
|
+
{ name: 'type', type: 'String', desc: 'name of the issue type', example: 'Story' }
|
92
|
+
],
|
93
|
+
desc: 'sets the issue type to use when creating a ticket'
|
94
|
+
)
|
95
|
+
def issuetype(type)
|
96
|
+
ticket.set_issuetype type
|
97
|
+
end
|
98
|
+
|
99
|
+
RoboPigeon::Documentarian.add_command(
|
100
|
+
'create!',
|
101
|
+
block: %w[job jira ticket],
|
102
|
+
desc: 'creates the configured ticket and returns the id'
|
103
|
+
)
|
104
|
+
def create!
|
105
|
+
ticket.create!
|
106
|
+
ticket.ticket
|
107
|
+
end
|
108
|
+
|
109
|
+
RoboPigeon::Documentarian.add_command(
|
110
|
+
'field',
|
111
|
+
block: %w[job jira ticket],
|
112
|
+
params: [
|
113
|
+
{ name: 'field_name', type: 'String', desc: 'the name of the field you want to set', example: 'duedate' },
|
114
|
+
{ name: 'field_value', type: 'String', desc: 'what to set that field to', example: '2/10/2030' }
|
115
|
+
],
|
116
|
+
desc: 'set or update a field to a value'
|
117
|
+
)
|
118
|
+
def field(name, value)
|
119
|
+
ticket.set_field(name, value)
|
120
|
+
end
|
121
|
+
|
122
|
+
RoboPigeon::Documentarian.add_command(
|
123
|
+
'print_id',
|
124
|
+
block: %w[job jira ticket],
|
125
|
+
desc: 'print the ticket number to standard out (only for already created tickets)'
|
126
|
+
)
|
127
|
+
def print_id
|
128
|
+
puts ticket.ticket.to_s
|
129
|
+
end
|
130
|
+
|
131
|
+
RoboPigeon::Documentarian.add_command(
|
132
|
+
'transition',
|
133
|
+
block: %w[job jira ticket],
|
134
|
+
params: [
|
135
|
+
{ name: 'transition_name', type: 'String', desc: 'the name of the transition to run', example: 'Ready' }
|
136
|
+
],
|
137
|
+
desc: 'transition a ticket to a new state'
|
138
|
+
)
|
139
|
+
def transition(name)
|
140
|
+
ticket.perform_transition(name)
|
141
|
+
end
|
142
|
+
|
143
|
+
RoboPigeon::Documentarian.add_command(
|
144
|
+
'wait_for_state',
|
145
|
+
block: %w[job jira ticket],
|
146
|
+
params: [
|
147
|
+
{ name: 'state_name', type: 'String', desc: 'the name of the state to wait for', example: 'Approved' }
|
148
|
+
],
|
149
|
+
desc: 'block execution waiting for a state transition'
|
150
|
+
)
|
151
|
+
def wait_for_state(transition_state)
|
152
|
+
ticket.wait_for_state!(transition_state)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
RoboPigeon::Documentarian.add_block('jira', helpers: true, block: [], desc: 'a configuration block for jira')
|
2
|
+
RoboPigeon::Documentarian.add_block('jira', helpers: true, block: ['job'], desc: 'configure a jira ticket manipulation, sends on end, can have multiple')
|
3
|
+
|
4
|
+
require 'robopigeon/jira/client'
|
5
|
+
require 'robopigeon/jira/helper_dsl'
|
6
|
+
require 'robopigeon/jira/dsl'
|
7
|
+
require 'robopigeon/jira/ticket'
|
8
|
+
require 'robopigeon/jira/ticket_dsl'
|
9
|
+
|
10
|
+
module RoboPigeon::Jira
|
11
|
+
class TicketNotFoundOrSet < StandardError; end
|
12
|
+
class RequiredFieldNotSet < StandardError; end
|
13
|
+
class FieldDoesNotConform < StandardError; end
|
14
|
+
class WaitTimeout < StandardError; end
|
15
|
+
end
|
16
|
+
|
17
|
+
module RoboPigeon::Dsl
|
18
|
+
module Helpers
|
19
|
+
include RoboPigeon::Dsl::Helpers::Jira
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module RoboPigeon::Dsl
|
24
|
+
class Root
|
25
|
+
def jira(&block)
|
26
|
+
RoboPigeon::Dsl::JiraRoot.run(&block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module RoboPigeon::Dsl
|
32
|
+
class Job
|
33
|
+
def jira(&block)
|
34
|
+
RoboPigeon::Dsl::Jira.run(&block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'markdown2confluence'
|
3
|
+
require 'kramdown/converter/slack'
|
4
|
+
|
5
|
+
module RoboPigeon::Dsl
|
6
|
+
module Helpers
|
7
|
+
module Markdown
|
8
|
+
RoboPigeon::Documentarian.add_command(
|
9
|
+
'confluence_from_md',
|
10
|
+
params: [
|
11
|
+
{ name: 'file', type: 'String', desc: 'path to a markdown erb file', example: 'config/wiki-page-template.erb.md' }
|
12
|
+
],
|
13
|
+
block: ['helpers'],
|
14
|
+
desc: 'get jira/confluence markup from an erb markdown template'
|
15
|
+
)
|
16
|
+
def confluence_from_md(file)
|
17
|
+
from_md_template(file).to_confluence
|
18
|
+
end
|
19
|
+
|
20
|
+
RoboPigeon::Documentarian.add_command(
|
21
|
+
'jira_from_md',
|
22
|
+
params: [
|
23
|
+
{ name: 'file', type: 'String', desc: 'path to a markdown erb file', example: 'config/issue-template.erb.md' }
|
24
|
+
],
|
25
|
+
block: ['helpers'],
|
26
|
+
desc: 'get jira/confluence markup from an erb markdown template'
|
27
|
+
)
|
28
|
+
def jira_from_md(file)
|
29
|
+
from_md_template(file).to_confluence
|
30
|
+
end
|
31
|
+
|
32
|
+
RoboPigeon::Documentarian.add_command(
|
33
|
+
'html_from_md',
|
34
|
+
params: [
|
35
|
+
{ name: 'file', type: 'String', desc: 'path to a markdown erb file', example: 'config/results-template.erb.md' }
|
36
|
+
],
|
37
|
+
block: ['helpers'],
|
38
|
+
desc: 'get html markup from an erb markdown template'
|
39
|
+
)
|
40
|
+
def html_from_md(file)
|
41
|
+
from_md_template(file).to_html
|
42
|
+
end
|
43
|
+
|
44
|
+
RoboPigeon::Documentarian.add_command(
|
45
|
+
'slack_from_md',
|
46
|
+
params: [
|
47
|
+
{ name: 'file', type: 'String', desc: 'path to a markdown erb file', example: 'config/message-template.erb.md' }
|
48
|
+
],
|
49
|
+
block: ['helpers'],
|
50
|
+
desc: 'get slack markup from an erb markdown template'
|
51
|
+
)
|
52
|
+
def slack_from_md(file)
|
53
|
+
from_md_template(file).to_slack
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def from_md_template(file)
|
59
|
+
raise RoboPigeon::Markdown::Error, "template file #{file} not found" unless File.exist?(file)
|
60
|
+
|
61
|
+
raw = File.read(file)
|
62
|
+
markdown = ERB.new(raw).result(binding)
|
63
|
+
Kramdown::Document.new(markdown,
|
64
|
+
input: 'GFM',
|
65
|
+
syntax_highlighter: 'coderay',
|
66
|
+
syntax_highlighter_opts: {
|
67
|
+
css: 'style',
|
68
|
+
line_numbers: 'table'
|
69
|
+
})
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
slack do
|
2
|
+
# Slack is currently disabled, make sure you have a valid bot token, then remove
|
3
|
+
# the following line
|
4
|
+
enabled false
|
5
|
+
# This is where you configure your global slack preferences, like bot name and api key
|
6
|
+
# You should always keep the api key either encrypted with something like `encryptatron`
|
7
|
+
# or in a ci environment variable (which is probably better)
|
8
|
+
api_key ENV['SLACK_API_KEY']
|
9
|
+
name 'RoboPigeon'
|
10
|
+
end
|
11
|
+
|
12
|
+
gitlab do
|
13
|
+
# GitLab is currently disabled, make sure you have a valid api token with acces to
|
14
|
+
# to your repo, then remove the following line
|
15
|
+
enabled false
|
16
|
+
# This is where you configure your global gitlab settings, like api url and api key
|
17
|
+
# always keep your api key either in an environment variable or encrypted with something
|
18
|
+
# something like `encryptatron` or rails secrets.
|
19
|
+
api_url ENV['CI_API_V4_URL']
|
20
|
+
api_key ENV['GITLAB_API_KEY'] # Always use an envrionment variable, don't check in secrets.
|
21
|
+
end
|
22
|
+
|
23
|
+
jira do
|
24
|
+
# Jira is currently disabled, make sure you have a valid api token with acces to
|
25
|
+
# to your repo, then remove the following line
|
26
|
+
enabled false
|
27
|
+
# This is where you configure your global jira settings, like api url and api token
|
28
|
+
# always keep your api key either in an environment variable or encrypted with
|
29
|
+
# something like `encryptatron` or rails secrets.
|
30
|
+
api_url ENV['JIRA_API_URL']
|
31
|
+
api_key ENV['JIRA_API_TOKEN'] # Always use an envrionment variable, don't check in secrets.
|
32
|
+
end
|
33
|
+
|
34
|
+
job 'deploy_complete', 'Notify slack and comment in gitlab that a deploy is complete' do
|
35
|
+
# This configures a slack notification, at the end of the block it sends to
|
36
|
+
# the users and channels in the body. Un-comment the channels and fill in the
|
37
|
+
# appropriate ones to have them sent there.
|
38
|
+
slack do
|
39
|
+
# channel '#your-team-channel'
|
40
|
+
# channel '#your-release-notifications-channel'
|
41
|
+
user ENV['GITLAB_USER_EMAIL']
|
42
|
+
# `git_committer_email` looks at the commit log and returns the email address
|
43
|
+
# for the last person to make a non-merge commit
|
44
|
+
user git_committer_email
|
45
|
+
# `git_merger_email` looks at the commit log and returns the email address
|
46
|
+
# for the last person to make a merge commit
|
47
|
+
user git_merger_email
|
48
|
+
# This configures the overall message to send, it comes first, before attachments.
|
49
|
+
# If you want to send multiple messages, you can use attachments with pretext in them
|
50
|
+
# It's not actually required to that you set a message in order to send attachments
|
51
|
+
message "#{slack_user_for(ENV['GITLAB_USER_EMAIL'])} deployed #{ENV['CI_PROJECT_NAME']} #{ENV['CI_COMMIT_TAG']} to #{ENV['CI_ENVIRONMENT_NAME']}"
|
52
|
+
attachment do
|
53
|
+
# You have to set a fallback text for the actions
|
54
|
+
fallback "<#{ENV['CI_PIPELINE_URL']}|Pipeline>"
|
55
|
+
# Actions show up as little buttons under the message, this one will be
|
56
|
+
# a green button that links to the pipeline.
|
57
|
+
action 'button', 'Pipeline', ENV['CI_PIPELINE_URL'], 'primary'
|
58
|
+
action 'button', 'Rollback', "#{ENV['CI_PROJECT_URL']}/environments", 'danger'
|
59
|
+
color 'good'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# This configures an action taken in gitlab. In this case, we're saying to use
|
64
|
+
# the last branch merged into this one as the branch name, and then comment on
|
65
|
+
# each merge request with that branch that someone has started deploying
|
66
|
+
gitlab do
|
67
|
+
merge_request_comment "#{ENV['GITLAB_USER_EMAIL']} deployed branch", git_branch_merged_source
|
68
|
+
end
|
69
|
+
|
70
|
+
# This configures an action taken in jira, In this case, we're looping over all
|
71
|
+
# of the tickets since the last deployment, and then adding a comment to them
|
72
|
+
# with what version it was released in.
|
73
|
+
jira do
|
74
|
+
# This helper gets all of the tickets referenced in the git log since the last
|
75
|
+
# deployment to the referenced environment, that contain the provided key
|
76
|
+
tickets_in_log_since_deployment_to('production', 'TIC').each do |ticket_id|
|
77
|
+
# Then it comments on them with the project that was deployed and the version
|
78
|
+
ticket ticket_id do
|
79
|
+
# Then it comments on them with the project that was deployed and the version
|
80
|
+
comment "#{ENV['CI_PROJECT_NAME']} had changes related to this deployed to production in version #{ENV['CI_COMMIT_TAG']}"
|
81
|
+
# Runs a transition called 'Release'
|
82
|
+
transition 'Release'
|
83
|
+
# and sets the fix version to the tag of the deployment
|
84
|
+
field 'Fix Version', ENV['CI_COMMIT_TAG']
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# This example job creates a ticket, assignes it to whoever runs the pipeline, and
|
91
|
+
# then sends them a slack message with the id of the ticket
|
92
|
+
job 'create ticket' do
|
93
|
+
jira do
|
94
|
+
ticket do
|
95
|
+
# Set the project key
|
96
|
+
project 'PIG'
|
97
|
+
# Set the issue type
|
98
|
+
issuetype 'Bug'
|
99
|
+
# Set the summary of the ticket
|
100
|
+
summary 'Detecetd a defect! Fix it!'
|
101
|
+
# Assign the ticket to the user who is running the pipeline
|
102
|
+
assign ENV['GITLAB_USER_EMAIL']
|
103
|
+
# Set a description from a file template, that template can
|
104
|
+
# use any of the other dsl helpers like the git or deployment
|
105
|
+
# operations
|
106
|
+
description jira_from_md('path/to/file.erb.md')
|
107
|
+
# Create the ticket and store the id in a global variable
|
108
|
+
create!
|
109
|
+
# Print out the ticket number for logging/deubgging
|
110
|
+
puts 'Created ticket:'
|
111
|
+
print_id
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
slack do
|
116
|
+
user ENV['GITLAB_USER_EMAIL']
|
117
|
+
message "Created #{jira_last_created_ticket_slack_link}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Below are a number of pre-configured jobs that some people find useful!
|
122
|
+
job 'deploy_started', 'Notfiy slack and comment in gitlab that a deploy is starting' do
|
123
|
+
slack do
|
124
|
+
user ENV['GITLAB_USER_EMAIL']
|
125
|
+
user git_committer_email
|
126
|
+
user git_merger_email
|
127
|
+
message "#{slack_user_for(ENV['GITLAB_USER_EMAIL'])} deployed #{ENV['CI_PROJECT_NAME']} #{ENV['CI_COMMIT_TAG']} to #{ENV['CI_ENVIRONMENT_NAME']}"
|
128
|
+
attachment do
|
129
|
+
fallback "<#{ENV['CI_PIPELINE_URL']}|Pipeline>"
|
130
|
+
action 'button', 'Pipeline', ENV['CI_PIPELINE_URL'], 'primary'
|
131
|
+
color 'good'
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
gitlab do
|
136
|
+
merge_request_comment "#{ENV['GITLAB_USER_EMAIL']} has started deyploying this branch", git_branch_merged_source
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
job 'notify_failure', 'Notify slack and gitlab that a job has failed' do
|
141
|
+
slack do
|
142
|
+
# channel '#your-team-channel'
|
143
|
+
user ENV['GITLAB_USER_EMAIL']
|
144
|
+
user git_committer_email
|
145
|
+
attachment do
|
146
|
+
fallback "#{ENV['CI_PROJECT_NAME']} - #{ENV['CI_COMMIT_REF_NAME']} has a failure - <#{ENV['CI_PIPELINE_URL']}|Pipeline>"
|
147
|
+
title "#{ENV['CI_PROJECT_NAME']} - #{ENV['CI_COMMIT_REF_NAME']} has a failure"
|
148
|
+
action 'button', 'Pipeline', ENV['CI_PIPELINE_URL'], 'primary'
|
149
|
+
color 'danger'
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
gitlab do
|
154
|
+
merge_request_comment "The branch from this merge request has a failure - ENV['CI_PIPELINE_URL']", git_branch_merged_source
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module RoboPigeon::Dsl
|
2
|
+
class SlackAttachment
|
3
|
+
include RoboPigeon::Dsl::Helpers
|
4
|
+
attr_accessor :attachment
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
self.attachment = { actions: [] }
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.run(&block)
|
11
|
+
attachment = RoboPigeon::Dsl::SlackAttachment.new
|
12
|
+
attachment.instance_eval(&block)
|
13
|
+
attachment.attachment
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
RoboPigeon::Documentarian.add_command('fallback', block: %w[job slack attachment], params: [{ name: 'text', type: 'String', desc: 'a string message', example: 'This message is displayed when the client does not support attachments' }], desc: 'Add a fallback message for clients that do not support actions')
|
19
|
+
def fallback(text)
|
20
|
+
attachment[:fallback] = text
|
21
|
+
end
|
22
|
+
|
23
|
+
RoboPigeon::Documentarian.add_command('title', block: %w[job slack attachment], params: [{ name: 'title', type: 'String', desc: 'the title of the attachment', example: 'Job Failed!' }], desc: 'Add a title to your attachment')
|
24
|
+
def title(title)
|
25
|
+
attachment[:title] = title
|
26
|
+
end
|
27
|
+
|
28
|
+
RoboPigeon::Documentarian.add_command('title_linke', block: %w[job slack attachment], params: [{ name: 'link', type: 'String', desc: 'a url', example: 'https://gitlab.com/robopigeon/robopigeon/pipelines' }], desc: 'Add a link to the title so that users can click on it')
|
29
|
+
def title_link(title_link)
|
30
|
+
attachment[:title_link] = title_link
|
31
|
+
end
|
32
|
+
|
33
|
+
RoboPigeon::Documentarian.add_command('pretext', block: %w[job slack attachment], params: [{ name: 'text', type: 'String', desc: 'a string message', example: 'This message shows up before rest of the attachment' }], desc: 'Add a message that shows up just before your attachment')
|
34
|
+
def pretext(text)
|
35
|
+
attachment[:pretext] = text
|
36
|
+
end
|
37
|
+
|
38
|
+
RoboPigeon::Documentarian.add_command('color', block: %w[job slack attachment], params: [{ name: 'color', type: 'String', desc: 'a color, hex or something', example: 'danger' }], desc: 'Change the color of the bar next to your attachment')
|
39
|
+
def color(color)
|
40
|
+
attachment[:color] = color
|
41
|
+
end
|
42
|
+
|
43
|
+
RoboPigeon::Documentarian.add_command(
|
44
|
+
'action',
|
45
|
+
block: %w[job slack attachment],
|
46
|
+
params: [
|
47
|
+
{ name: 'type', type: 'String', desc: 'Currently only supports `button`', example: 'button' },
|
48
|
+
{ name: 'text', type: 'String', desc: 'Field label', example: 'Pipeline' },
|
49
|
+
{ name: 'url', type: 'String', desc: 'Url to link to', example: 'https://gitlab.com/robopigeon/robopigeon/pipelines' },
|
50
|
+
{ name: 'style', type: 'String', desc: 'button style, can be danger or primary', example: 'danger', defaut: 'nil' }
|
51
|
+
],
|
52
|
+
desc: 'Add a Url linked button to your bot'
|
53
|
+
)
|
54
|
+
def action(type, text, url, style=nil)
|
55
|
+
attachment[:actions].push(
|
56
|
+
type: type,
|
57
|
+
text: text,
|
58
|
+
url: url,
|
59
|
+
style: style
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|