routemaster-drain 1.0.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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/.env.test +6 -0
  3. data/.gitignore +7 -0
  4. data/.gitmodules +3 -0
  5. data/.rspec +3 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +16 -0
  8. data/.yardopts +6 -0
  9. data/Gemfile +17 -0
  10. data/Gemfile.lock +122 -0
  11. data/Guardfile +10 -0
  12. data/LICENSE.txt +22 -0
  13. data/README.md +261 -0
  14. data/Rakefile +1 -0
  15. data/lib/routemaster/cache.rb +133 -0
  16. data/lib/routemaster/config.rb +57 -0
  17. data/lib/routemaster/dirty/filter.rb +49 -0
  18. data/lib/routemaster/dirty/map.rb +63 -0
  19. data/lib/routemaster/dirty/state.rb +33 -0
  20. data/lib/routemaster/drain.rb +5 -0
  21. data/lib/routemaster/drain/basic.rb +40 -0
  22. data/lib/routemaster/drain/caching.rb +43 -0
  23. data/lib/routemaster/drain/mapping.rb +43 -0
  24. data/lib/routemaster/drain/terminator.rb +31 -0
  25. data/lib/routemaster/fetcher.rb +63 -0
  26. data/lib/routemaster/jobs/cache.rb +12 -0
  27. data/lib/routemaster/jobs/cache_and_sweep.rb +16 -0
  28. data/lib/routemaster/middleware/authenticate.rb +59 -0
  29. data/lib/routemaster/middleware/cache.rb +29 -0
  30. data/lib/routemaster/middleware/dirty.rb +33 -0
  31. data/lib/routemaster/middleware/filter.rb +26 -0
  32. data/lib/routemaster/middleware/parse.rb +43 -0
  33. data/lib/routemaster/middleware/root_post_only.rb +18 -0
  34. data/lib/routemaster/redis_broker.rb +42 -0
  35. data/routemaster-drain.gemspec +28 -0
  36. data/spec/routemaster/cache_spec.rb +115 -0
  37. data/spec/routemaster/dirty/filter_spec.rb +77 -0
  38. data/spec/routemaster/dirty/map_spec.rb +122 -0
  39. data/spec/routemaster/dirty/state_spec.rb +41 -0
  40. data/spec/routemaster/drain/basic_spec.rb +37 -0
  41. data/spec/routemaster/drain/caching_spec.rb +47 -0
  42. data/spec/routemaster/drain/mapping_spec.rb +51 -0
  43. data/spec/routemaster/drain/terminator_spec.rb +61 -0
  44. data/spec/routemaster/fetcher_spec.rb +56 -0
  45. data/spec/routemaster/middleware/authenticate_spec.rb +59 -0
  46. data/spec/routemaster/middleware/cache_spec.rb +35 -0
  47. data/spec/routemaster/middleware/dirty_spec.rb +33 -0
  48. data/spec/routemaster/middleware/filter_spec.rb +35 -0
  49. data/spec/routemaster/middleware/parse_spec.rb +69 -0
  50. data/spec/routemaster/middleware/root_post_only_spec.rb +30 -0
  51. data/spec/spec_helper.rb +16 -0
  52. data/spec/support/events.rb +9 -0
  53. data/spec/support/rack_test.rb +23 -0
  54. data/spec/support/uses_dotenv.rb +11 -0
  55. data/spec/support/uses_redis.rb +15 -0
  56. data/spec/support/uses_webmock.rb +12 -0
  57. data/test.rb +17 -0
  58. metadata +247 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cc848f2c1b75d8d36a4807ac8d8f0d871f9fca29
4
+ data.tar.gz: 5c627ab91e9497abb0acb81227484650f280853c
5
+ SHA512:
6
+ metadata.gz: fdcd9b0cb52cb767ac97d9b7df8b7c26c1ff92755487b7801748f488b3946c256079e441a8c9264768c858b3b12f70eed38b6a23fd93a2d48526a40642314445
7
+ data.tar.gz: 2cb2c4af8600340854423def3364f3e1d4f3d51627765d2e6f8a4579e70c25ba31ee38058acf7b37941be52b4f00e1ad3d472ed9607590f4f242043b2d65b232
data/.env.test ADDED
@@ -0,0 +1,6 @@
1
+ REDIS_TEST_URL=redis://localhost/13
2
+ ROUTEMASTER_DRAIN_REDIS=redis://localhost/13
3
+ ROUTEMASTER_CACHE_REDIS=redis://localhost/12
4
+ ROUTEMASTER_CACHE_AUTH=example.com:username:s3cr3t
5
+ ROUTEMASTER_DRAIN_TOKENS=d3m0
6
+
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ .bundle/
2
+ doc/
3
+ .yardoc/
4
+ pkg/
5
+ tmp
6
+ *.orig
7
+ tags
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "rebund"]
2
+ path = rebund
3
+ url = https://github.com/mezis/rebund.git
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ -I.
2
+ --color
3
+ --format progress
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.2
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ services:
3
+ - redis-server
4
+ rvm:
5
+ - 2.1.1
6
+ - 2.1.2
7
+ script:
8
+ - bundle exec rspec
9
+ install:
10
+ - "./rebund/run download"
11
+ - bundle install --path vendor/bundle
12
+ after_script:
13
+ - "./rebund/run upload"
14
+ env:
15
+ global:
16
+ secure: "kMxJcT8xyQ+QyCeKW5lDP44IU99Y8iN1AWZo9Z1BKN6HigGa8Ua8O/aD8AJjrTfRRSjnM402utmW6mk78RYVxJha3+69jJYr25I9EeyYhV2cuRu+5Ictxfcb9R9VhS0b6LDncz9h55xDS1UA35rvj+EOO4/M9moccw7T2qzPa2U="
data/.yardopts ADDED
@@ -0,0 +1,6 @@
1
+ --no-private
2
+ --protected
3
+ --markup-provider=redcarpet
4
+ --markup=markdown
5
+
6
+ lib/**/*.rb - README.md LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source ENV.fetch('GEM_SOURCE', 'https://rubygems.org')
2
+
3
+ # Specify your gem's dependencies in routemaster_client.gemspec
4
+ gemspec
5
+
6
+ # Just here to avoid a safety warning
7
+ gem 'psych', require: false
8
+
9
+ # Used in builds and tests
10
+ gem 'bundler', require: false
11
+ gem 'rake', require: false
12
+ gem 'guard-rspec', require: false
13
+ gem 'webmock', require: false
14
+ gem 'pry-nav', require: false
15
+ gem 'rack-test', require: false
16
+ gem 'dotenv', require: false
17
+
data/Gemfile.lock ADDED
@@ -0,0 +1,122 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ routemaster-drain (1.0.0)
5
+ faraday
6
+ faraday_middleware
7
+ hashie
8
+ net-http-persistent
9
+ rack
10
+ redis-namespace
11
+ resque
12
+ thread
13
+ wisper
14
+
15
+ GEM
16
+ remote: http://eu.yarp.io/
17
+ specs:
18
+ addressable (2.3.6)
19
+ celluloid (0.16.0)
20
+ timers (~> 4.0.0)
21
+ coderay (1.1.0)
22
+ crack (0.4.2)
23
+ safe_yaml (~> 1.0.0)
24
+ diff-lcs (1.2.5)
25
+ dotenv (0.11.1)
26
+ dotenv-deployment (~> 0.0.2)
27
+ dotenv-deployment (0.0.2)
28
+ faraday (0.9.0)
29
+ multipart-post (>= 1.2, < 3)
30
+ faraday_middleware (0.9.1)
31
+ faraday (>= 0.7.4, < 0.10)
32
+ ffi (1.9.3)
33
+ formatador (0.2.5)
34
+ guard (2.6.1)
35
+ formatador (>= 0.2.4)
36
+ listen (~> 2.7)
37
+ lumberjack (~> 1.0)
38
+ pry (>= 0.9.12)
39
+ thor (>= 0.18.1)
40
+ guard-rspec (4.3.1)
41
+ guard (~> 2.1)
42
+ rspec (>= 2.14, < 4.0)
43
+ hashie (3.3.1)
44
+ hitimes (1.2.2)
45
+ listen (2.7.9)
46
+ celluloid (>= 0.15.2)
47
+ rb-fsevent (>= 0.9.3)
48
+ rb-inotify (>= 0.9)
49
+ lumberjack (1.0.9)
50
+ method_source (0.8.2)
51
+ mono_logger (1.1.0)
52
+ multi_json (1.10.1)
53
+ multipart-post (2.0.0)
54
+ net-http-persistent (2.9.4)
55
+ pry (0.10.1)
56
+ coderay (~> 1.1.0)
57
+ method_source (~> 0.8.1)
58
+ slop (~> 3.4)
59
+ pry-nav (0.2.4)
60
+ pry (>= 0.9.10, < 0.11.0)
61
+ psych (2.0.5)
62
+ rack (1.5.2)
63
+ rack-protection (1.5.3)
64
+ rack
65
+ rack-test (0.6.2)
66
+ rack (>= 1.0)
67
+ rake (10.3.2)
68
+ rb-fsevent (0.9.4)
69
+ rb-inotify (0.9.5)
70
+ ffi (>= 0.5.0)
71
+ redis (3.1.0)
72
+ redis-namespace (1.5.1)
73
+ redis (~> 3.0, >= 3.0.4)
74
+ resque (1.25.2)
75
+ mono_logger (~> 1.0)
76
+ multi_json (~> 1.0)
77
+ redis-namespace (~> 1.3)
78
+ sinatra (>= 0.9.2)
79
+ vegas (~> 0.1.2)
80
+ rspec (3.1.0)
81
+ rspec-core (~> 3.1.0)
82
+ rspec-expectations (~> 3.1.0)
83
+ rspec-mocks (~> 3.1.0)
84
+ rspec-core (3.1.2)
85
+ rspec-support (~> 3.1.0)
86
+ rspec-expectations (3.1.0)
87
+ diff-lcs (>= 1.2.0, < 2.0)
88
+ rspec-support (~> 3.1.0)
89
+ rspec-mocks (3.1.0)
90
+ rspec-support (~> 3.1.0)
91
+ rspec-support (3.1.0)
92
+ safe_yaml (1.0.3)
93
+ sinatra (1.4.5)
94
+ rack (~> 1.4)
95
+ rack-protection (~> 1.4)
96
+ tilt (~> 1.3, >= 1.3.4)
97
+ slop (3.6.0)
98
+ thor (0.19.1)
99
+ thread (0.1.4)
100
+ tilt (1.4.1)
101
+ timers (4.0.0)
102
+ hitimes
103
+ vegas (0.1.11)
104
+ rack (>= 1.0.0)
105
+ webmock (1.18.0)
106
+ addressable (>= 2.3.6)
107
+ crack (>= 0.3.2)
108
+ wisper (1.4.0)
109
+
110
+ PLATFORMS
111
+ ruby
112
+
113
+ DEPENDENCIES
114
+ bundler
115
+ dotenv
116
+ guard-rspec
117
+ pry-nav
118
+ psych
119
+ rack-test
120
+ rake
121
+ routemaster-drain!
122
+ webmock
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec, cmd: 'bundle exec rspec' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
9
+ end
10
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 HouseTrip Ltd.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,261 @@
1
+ # routemaster-drain
2
+
3
+ A Rack-based event receiver for the
4
+ [Routemaster](https://github.com/HouseTrip/routemaster) event bus.
5
+
6
+ ![Version](https://badge.fury.io/rb/routemaster-drain.svg)
7
+ &nbsp;
8
+ ![Build](https://travis-ci.org/HouseTrip/routemaster-drain.svg?branch=master)
9
+ &nbsp;
10
+ [![](http://img.shields.io/badge/API%20docs-rubydoc.info-blue.svg)](http://rubydoc.info/github/HouseTrip/routemaster-drain/frames/file/README.md)
11
+
12
+ `routemaster-drain` is a collection of Rack middleware to receive and
13
+ parse Routemaster events, filter them, and preemptively cache the corresponding
14
+ resources.
15
+
16
+ It provides prebuilt middleware stacks (`Basic`, `Mapping`, and `Caching`) for
17
+ typical use cases, illustrated below, or you can easily roll your own by
18
+ combining middleware.
19
+
20
+
21
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
22
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
23
+
24
+ - [Installation](#installation)
25
+ - [Illustrated use cases](#illustrated-use-cases)
26
+ - [Simply receive events from Routemaster](#simply-receive-events-from-routemaster)
27
+ - [Receive change notifications without duplicates](#receive-change-notifications-without-duplicates)
28
+ - [Cache data for all notified resources](#cache-data-for-all-notified-resources)
29
+ - [Internals](#internals)
30
+ - [Dirty map](#dirty-map)
31
+ - [Filter](#filter)
32
+ - [Contributing](#contributing)
33
+
34
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
35
+
36
+
37
+ ## Installation
38
+
39
+ Add this line to your application's Gemfile:
40
+
41
+ gem 'routemaster-drain'
42
+
43
+ **Configuration**
44
+
45
+ This gem is configured through the environment, making 12factor compliance
46
+ easier.
47
+
48
+ - `ROUTEMASTER_DRAIN_TOKENS`: a comma-separated list of valid authentication
49
+ tokens, used by Routemaster to send you events.
50
+ - `ROUTEMASTER_DRAIN_REDIS`: the URL of the Redis instance used for filtering
51
+ and dirty mapping. Required if you use either feature, ignored otherwise.
52
+ A namespace can be specified.
53
+ Example: `redis://user:s3cr3t@myhost:1234/12/ns`.
54
+ - `ROUTEMASTER_CACHE_REDIS`: the URL of the Redis instance used for caching.
55
+ Required if you use the feature, ignored otherwise. Formatted like
56
+ `ROUTEMASTER_DRAIN_REDIS`.
57
+ - `ROUTEMASTER_CACHE_EXPIRY`: if using the cache, for how long to cache
58
+ entries, in seconds. Default 1 year (31,536,000).
59
+ - `ROUTEMASTER_CACHE_AUTH`: if using the cache, specifies what username/password
60
+ pairs to use to fetch resources. The format is a comma-separated list of
61
+ colon-separate lists of regexp, username, password values. Example:
62
+ `server1:user:p4ss,server2:user:p4ass`.
63
+ - `ROUTEMASTER_QUEUE_NAME`: if using the cache, on which Resque queue the cache
64
+ population jobs should be enqueued.
65
+
66
+
67
+
68
+ ## Illustrated use cases
69
+
70
+
71
+ ### Simply receive events from Routemaster
72
+
73
+ Provide a listener for events:
74
+
75
+ ```ruby
76
+ class Listener
77
+ def on_events_received(batch)
78
+ batch.each do |event|
79
+ puts event.url
80
+ end
81
+ end
82
+ end
83
+ ```
84
+
85
+ Each event is a `Hashie::Mash` and responds to `type` (one of `create`,
86
+ `update`, `delete`, or `noop`), `url` (the resource), and `t` (the event
87
+ timestamp, in milliseconds since the Epoch).
88
+
89
+ Create the app that will process events:
90
+
91
+ ```ruby
92
+ require 'routemaster/drain/basic'
93
+ $app = Routemaster::Drain::Basic.new
94
+ ```
95
+
96
+ Bind the app to your listener:
97
+
98
+ ```ruby
99
+ $app.subscribe(Listener.new)
100
+ ```
101
+
102
+ And finally, mount your app to your subscription path:
103
+
104
+ ```ruby
105
+ # typically in config.ru
106
+ map '/events' do
107
+ run $app
108
+ end
109
+ ```
110
+
111
+ This relies on the excellent event bus from the [wisper
112
+ gem](https://github.com/krisleech/wisper#wisper).
113
+
114
+
115
+ ### Receive change notifications without duplicates
116
+
117
+ When reacting to changes of some resource, it's common to want to avoid
118
+ receiving further change notifications until you've actually processed that
119
+ resource.
120
+
121
+ Possibly you'll want to process changes in batches at regular time intervals.
122
+
123
+ For this purpose, use `Routemaster::Drain::Mapping`:
124
+
125
+ ```ruby
126
+ require 'routemaster/drain/mapping'
127
+ $app = Routemaster::Drain::Mapping.new
128
+ ```
129
+
130
+ And mount it as usual:
131
+
132
+ ```ruby
133
+ # in config.ru
134
+ map('/events') { run $app }
135
+ ```
136
+
137
+ Instead of processing events, you'll check for changes in the dirty map:
138
+
139
+ ```ruby
140
+ require 'routemaster/dirty/map'
141
+ $map = Routemaster::Dirty::Map.new
142
+
143
+ every_5_minutes do
144
+ $map.sweep do |url|
145
+ # do something about this changed resource
146
+ true
147
+ end
148
+ end
149
+ ```
150
+
151
+ Until you've called `#sweep` and your block has returned `true`, you won't be
152
+ bugged again — the dirty map acts as a buffer of changes (see below for
153
+ internals).
154
+
155
+ Notes:
156
+ - You can limit the number of resources to be swept (`$map.sweep(123) { ... }`).
157
+ - You can count the number of resources to be swept with `$map.count`.
158
+ - You're not told _what_ is to be swept; entities won't be swept in the order of
159
+ events received (much like Routemaster does not guarantee ordering).
160
+ - If your sweeper fails, the dirty map will not be cleaned, so you can have
161
+ leftovers. It's good practice to regularly run `$map.sweep { ... }` and perform
162
+ cleanup regularly.
163
+ - The map won't tell you if the resources has been changed, created, or deleted.
164
+ You'll have to figure it out with an API call.
165
+ - You can still attach a listener to the app to get all events.
166
+
167
+
168
+ ### Cache data for all notified resources
169
+
170
+ Another common use case is that you'll actually need the representation of the
171
+ resources Routemaster tells you about.
172
+
173
+ The `Caching` prebuilt app can do that for you, using Resque to populate the
174
+ cache as events are received.
175
+
176
+ For this purpose, use `Routemaster::Drain::Caching`:
177
+
178
+ ```ruby
179
+ require 'routemaster/drain/machine'
180
+ $app = Routemaster::Drain:Caching.new
181
+ ```
182
+
183
+ And mount it as usual:
184
+
185
+ ```ruby
186
+ # in config.ru
187
+ map('/events') { run $app }
188
+ ```
189
+
190
+ You can still attach a listenenr if you want the incoming events. Typically,
191
+ what you'll want is the cache:
192
+
193
+ ```ruby
194
+ require 'routemaster/cache'
195
+ $cache = Routemaster::Cache.new
196
+
197
+ response = @cache.fget('https://example.com/widgets/123')
198
+ puts response.body.id
199
+ ```
200
+
201
+ In this example, is your app was notified by Routemaster about Widget #123, the
202
+ cache will be very likely to be hit; and it will be invalidated automatically
203
+ whenever the drain gets notified about a change on that widget.
204
+
205
+ Note that `Cache#fget` is a future, so you can efficiently query many resources
206
+ and have any `HTTP GET` requests (and cache queries) happen in parallel.
207
+
208
+ See
209
+ [rubydoc](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Cache)
210
+ for more details on `Cache`.
211
+
212
+
213
+ ## Internals
214
+
215
+ The more elaborate drains are built with two components which can also be used
216
+ independently,
217
+ [`Dirty::Map`](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Dirty/Map)
218
+ and
219
+ [`Dirty::Filter`](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Dirty/Filter).
220
+
221
+ ### Dirty map
222
+
223
+ A dirty map collects entities that have been created, updated, or deleted (or
224
+ rather, their URLs). It can be used to delay your service's reaction to events,
225
+ for instance combined with Resque.
226
+
227
+ A dirty map map gets _marked_ when an event about en entity gets processed that
228
+ indicates a state change, and _swept_ to process those changes.
229
+
230
+ Practically, instances of
231
+ [`Routemaster::Dirty::Map`](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Dirty/Map)
232
+ will emit a `:dirty_entity` event when a URL is marked as dirty, and can be
233
+ swept when an entity is "cleaned". If a URL is marked multiple times before
234
+ being swept (e.g. for very volatile entities), the event will only by broadcast
235
+ once.
236
+
237
+ To sweep the map, you can for instance listen to this event and call
238
+ [`#sweep_one`](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Dirty/Map#sweep_one-instance_method).
239
+
240
+ If you're not in a hurry and would rather run through batches you can call
241
+ [`#sweep`](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Dirty/Map#sweep-instance_method)
242
+ which will yield URLs until it runs out of dirty resources.
243
+
244
+ ### Filter
245
+
246
+ [`Routemaster::Dirty::Filter`](http://rubydoc.info/github/HouseTrip/routemaster-drain/Routemaster/Dirty/Filter) is a simple event filter
247
+ that performs reordering. It ignores events older than the latest known
248
+ information on an entity.
249
+
250
+ It stores transient state in Redis and will emit `:entity_changed` events
251
+ whenever an entity has changed. This event can usefully be fed into a dirty map,
252
+ as in `Receiver::Filter` for instance.
253
+
254
+
255
+ ## Contributing
256
+
257
+ 1. Fork it ( http://github.com/HouseTrip/routemaster-drain/fork )
258
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
259
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
260
+ 4. Push to the branch (`git push origin my-new-feature`)
261
+ 5. Create new Pull Request