robopigeon 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|