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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.gitlab-ci.yml +27 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +65 -0
  6. data/.ruby-version +1 -0
  7. data/.version +1 -0
  8. data/Gemfile +4 -0
  9. data/README.md +256 -0
  10. data/Rakefile +11 -0
  11. data/bin/console +14 -0
  12. data/bin/rake +29 -0
  13. data/bin/rspec +29 -0
  14. data/bin/rubocop +29 -0
  15. data/bin/setup +8 -0
  16. data/exe/robopigeon +22 -0
  17. data/lib/robopigeon/documentarian.rb +39 -0
  18. data/lib/robopigeon/dsl/helpers.rb +17 -0
  19. data/lib/robopigeon/dsl/initial_jobs.rb +36 -0
  20. data/lib/robopigeon/dsl/job.rb +34 -0
  21. data/lib/robopigeon/dsl.rb +46 -0
  22. data/lib/robopigeon/git/helper_dsl.rb +60 -0
  23. data/lib/robopigeon/git.rb +7 -0
  24. data/lib/robopigeon/gitlab/client.rb +42 -0
  25. data/lib/robopigeon/gitlab/dsl.rb +174 -0
  26. data/lib/robopigeon/gitlab/helper_dsl.rb +44 -0
  27. data/lib/robopigeon/gitlab/jira.rb +0 -0
  28. data/lib/robopigeon/gitlab/merge_request.rb +78 -0
  29. data/lib/robopigeon/gitlab.rb +29 -0
  30. data/lib/robopigeon/jira/client.rb +17 -0
  31. data/lib/robopigeon/jira/dsl.rb +81 -0
  32. data/lib/robopigeon/jira/helper_dsl.rb +24 -0
  33. data/lib/robopigeon/jira/ticket.rb +186 -0
  34. data/lib/robopigeon/jira/ticket_dsl.rb +155 -0
  35. data/lib/robopigeon/jira.rb +37 -0
  36. data/lib/robopigeon/markdown/helper_dsl.rb +73 -0
  37. data/lib/robopigeon/markdown.rb +11 -0
  38. data/lib/robopigeon/resources/initial_robopigeon.rb +156 -0
  39. data/lib/robopigeon/slack/attachments_dsl.rb +63 -0
  40. data/lib/robopigeon/slack/client.rb +46 -0
  41. data/lib/robopigeon/slack/dsl.rb +74 -0
  42. data/lib/robopigeon/slack/helper_dsl.rb +26 -0
  43. data/lib/robopigeon/slack/message.rb +35 -0
  44. data/lib/robopigeon/slack.rb +30 -0
  45. data/lib/robopigeon/version.rb +3 -0
  46. data/lib/robopigeon.rb +13 -0
  47. data/robopigeon +0 -0
  48. data/robopigeon.gemspec +47 -0
  49. data/robopigeon.rb +156 -0
  50. 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,11 @@
1
+ require 'robopigeon/markdown/helper_dsl'
2
+
3
+ module RoboPigeon::Markdown
4
+ class Error < StandardError; end
5
+ end
6
+
7
+ module RoboPigeon::Dsl
8
+ module Helpers
9
+ include RoboPigeon::Dsl::Helpers::Markdown
10
+ end
11
+ 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