gitmqueue 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +8 -0
- data/README.md +122 -16
- data/bin/gitmqueue-consume +3 -1
- data/gitmqueue.gemspec +1 -0
- data/lib/consumer.rb +22 -6
- data/lib/storage.rb +8 -27
- data/lib/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fef08ee9fcfc3c24da047d2c1a0aa5e760127bf47ab665e7962590c2be239744
|
4
|
+
data.tar.gz: c49766c95c5d7b3848c54d1a06cacbc594abaaaab88004129602a0824a70756b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b987755d7ff9dc3133c7a4ba8e67ede3fbfdd9967a44b955f60e775ba8ffeb2db4a5d6682c1296a28b233b7f9de80103cc3245476261fc04cedd6482e7334db
|
7
|
+
data.tar.gz: feb8ddc0b1f77d4ad37d61ba88e46acad0791f576d05fefb7bdd1a216214211989ad4f7735ad93f2ad4de54ec30ccabf6a3482d4c036f0aaf9c2e86e1d08b564
|
data/Gemfile.lock
CHANGED
@@ -2,13 +2,21 @@ PATH
|
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
4
|
gitmqueue (0.1.0)
|
5
|
+
listen (~> 3.2.0)
|
5
6
|
rugged (~> 0.28)
|
6
7
|
|
7
8
|
GEM
|
8
9
|
remote: https://rubygems.org/
|
9
10
|
specs:
|
10
11
|
diff-lcs (1.3)
|
12
|
+
ffi (1.11.3)
|
13
|
+
listen (3.2.0)
|
14
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
15
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
11
16
|
rake (10.5.0)
|
17
|
+
rb-fsevent (0.10.3)
|
18
|
+
rb-inotify (0.10.0)
|
19
|
+
ffi (~> 1.0)
|
12
20
|
rspec (3.9.0)
|
13
21
|
rspec-core (~> 3.9.0)
|
14
22
|
rspec-expectations (~> 3.9.0)
|
data/README.md
CHANGED
@@ -1,34 +1,38 @@
|
|
1
1
|
# GitMQueue : Git Message Queue
|
2
2
|
|
3
|
-
Hey,
|
3
|
+
Hey, Crazy idea bad pitch, lets use Git as a messaging queue! lets use github to
|
4
4
|
synchronize a cluster of them together.
|
5
5
|
|
6
|
-
|
7
|
-
track of our code changes, and we need a central point to pull and push these
|
8
|
-
changes (although it's designed to be decentralized), this is why we use
|
9
|
-
github/lab/bitbucket...etc.
|
6
|
+
![Screenshot-2019-11-24_22-41-55](https://user-images.githubusercontent.com/54403/69502136-09672400-0f0c-11ea-8707-df3164411c4e.png)
|
10
7
|
|
11
|
-
|
12
|
-
|
8
|
+
|
9
|
+
Lets get the obvious out of the way.
|
10
|
+
|
11
|
+
1. You already know that we use Git to keep track of our code changes, If you're a
|
12
|
+
developer you most probably use it every day, and you probably know how to share
|
13
|
+
your code changes with everybody else using some central repo server like Github
|
14
|
+
or Bitbucket.
|
15
|
+
|
16
|
+
1. Alright, now lets introduce another idea: **Git commits doesn't need to include
|
17
|
+
changes**, You don't have to create files then commit these files to
|
13
18
|
your git repo, you can create an empty commit with a command like this:
|
14
19
|
|
15
20
|
```
|
16
21
|
git commit --allow-empty -m "commit message"
|
17
22
|
```
|
18
23
|
|
19
|
-
OK, so the commit message can be any text right? that means you can have JSON
|
24
|
+
1. OK, so the commit message can be any text right? that means you can have JSON
|
20
25
|
messages or YAML or any other machine readable format as a commit message.
|
21
26
|
|
22
|
-
|
27
|
+
## Now imagine this use case:
|
23
28
|
|
24
|
-
1. Create a
|
29
|
+
1. Create a repository on github
|
25
30
|
1. Clone it to your server
|
26
31
|
1. Have your application commit a `message` to this repo every time you need to
|
27
32
|
1. Push the repo every period say 10 seconds
|
28
33
|
1. Clone same repo to another server
|
29
|
-
1. Have an application
|
30
|
-
|
31
|
-
them extracts the message and does the job
|
34
|
+
1. Have an application pull periodically and checks for new commits and for each
|
35
|
+
one of them extracts the message and process it
|
32
36
|
1. Put a git tag on the last processed message with that consumer identifier
|
33
37
|
1. Push that label to keep track of the last processed message (pull it when you
|
34
38
|
deploy that consumer again)
|
@@ -36,14 +40,14 @@ So now imagine this scenario:
|
|
36
40
|
Now you have a queue of messages that can't be altered, communicated between
|
37
41
|
servers and all data saved in a git repository.
|
38
42
|
|
39
|
-
This idea is flexible and can be
|
43
|
+
This idea is flexible and can be altered for several use cases:
|
40
44
|
|
41
|
-
1. the central server doesn't have to be
|
45
|
+
1. the central server doesn't have to be Github or a like, you can use your
|
42
46
|
own server as a git server.
|
43
47
|
1. You can have more than one queue for messages by creating many branches each
|
44
48
|
for a type of messages or a different messages purpose.
|
45
49
|
1. You can have the same branch consumed from different servers each message
|
46
|
-
processed multiple times in different ways like RabbitMQ
|
50
|
+
processed multiple times in different ways like RabbitMQ fanout strategy
|
47
51
|
1. You can keep browse your message queue with any git client UI or CLI
|
48
52
|
1. You can clean your servers git local repo so that it deletes old commit
|
49
53
|
history to save space.
|
@@ -66,3 +70,105 @@ This idea can as simple as invoking git commands from your application with a
|
|
66
70
|
system call, or use libgit to manipulate the repository for you, or even a small
|
67
71
|
server application that communicate over a socket, or a repo service that
|
68
72
|
communicate over even HTTP, there are so many ways to implement that concept.
|
73
|
+
|
74
|
+
The current repository is an implementation of this concept in Ruby.
|
75
|
+
|
76
|
+
# Installation
|
77
|
+
|
78
|
+
Install the gem with
|
79
|
+
```
|
80
|
+
gem install gitmqueue
|
81
|
+
```
|
82
|
+
|
83
|
+
or add gitmqueue to your `Gemfile`
|
84
|
+
```
|
85
|
+
gem 'gitmqueue', '~> 0.1'
|
86
|
+
```
|
87
|
+
|
88
|
+
|
89
|
+
# Producing messages
|
90
|
+
|
91
|
+
First create an instance of `GitMQueue::Storage`, to specify your storage place.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
storage = GitMQueue::Storage.new('/tmp/gitmqueue')
|
95
|
+
```
|
96
|
+
|
97
|
+
This is where your git repository will live, message producer will use this
|
98
|
+
storage instance to write messages to any branch you wish.
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
producer = GitMQueue::Producer.new(storage: storage, branch: 'master')
|
102
|
+
```
|
103
|
+
|
104
|
+
Now you can use `producer` to publish message to this branch.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
producer.publish('Hello, world!')
|
108
|
+
```
|
109
|
+
|
110
|
+
It will create a commit with `Hello, world!` as a message in the `master`
|
111
|
+
branch.
|
112
|
+
|
113
|
+
You can have as many producers as you wish for any number of branches, make sure
|
114
|
+
to have one producer per branch to prevent race conditions between producers.
|
115
|
+
|
116
|
+
# Consuming messages
|
117
|
+
|
118
|
+
You need to create storage instance as we did with the producer, then
|
119
|
+
instanciate your consumer
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
consumer = GitMQueue::Consumer.new(storage: storage, name: 'consumer', branch:
|
123
|
+
'master')
|
124
|
+
```
|
125
|
+
|
126
|
+
And start the consume process, this function will take a block of code that
|
127
|
+
accept one parameter which is your message, this function is non-blocking
|
128
|
+
it will execute the block of code each time a new commit appear in the branch
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
consumer.consume do |message|
|
132
|
+
puts message
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
After your block finish execution the consumer will tag this commit with a git
|
137
|
+
tag, the tag name will be the consumer name, if your consumer name is
|
138
|
+
`consumer1`, the tag will be `consumer1`, as git tags are global to all the
|
139
|
+
repository you need to make the consumer name unique not just for the branch but
|
140
|
+
for all the repository, so if you want to consume 2 branches from one consumer
|
141
|
+
you'll have to choose to different names so that tags are not overriden be every
|
142
|
+
consumer instance, you can adopt a naming scheme that involve the branch name,
|
143
|
+
while implementing this gem we had that name tag name as `branch.consumer` but
|
144
|
+
then thought it'll be better for the user to make this decision to allow for a
|
145
|
+
more flexible naming, the tag will be overritten after each time the consumer
|
146
|
+
process a message.
|
147
|
+
|
148
|
+
# Pushing and Pulling your data
|
149
|
+
|
150
|
+
As for now there is no way to push/pull from the gem, so this part is up to you,
|
151
|
+
until it's implemented in the gem.
|
152
|
+
|
153
|
+
# Gem executables
|
154
|
+
|
155
|
+
## gitmqueue-produce
|
156
|
+
|
157
|
+
Starts a process that waits you to enter any line and publish that as a message
|
158
|
+
|
159
|
+
Usage:
|
160
|
+
|
161
|
+
```
|
162
|
+
gitmqueue-produce /path/to/rpo branch-name
|
163
|
+
```
|
164
|
+
|
165
|
+
## gitmqueue-consume
|
166
|
+
|
167
|
+
Starts a process that waits for messages, each message will be written to the
|
168
|
+
terminal
|
169
|
+
|
170
|
+
Usage:
|
171
|
+
|
172
|
+
```
|
173
|
+
gitmqueue-consume /path/to/repo branch-name consumer-name
|
174
|
+
```
|
data/bin/gitmqueue-consume
CHANGED
data/gitmqueue.gemspec
CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.require_paths = ['lib']
|
29
29
|
|
30
30
|
spec.add_dependency 'rugged', '~> 0.28'
|
31
|
+
spec.add_dependency 'listen', '~> 3.2.0'
|
31
32
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
32
33
|
spec.add_development_dependency 'rake', '~> 10.0'
|
33
34
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
data/lib/consumer.rb
CHANGED
@@ -1,21 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'listen'
|
4
|
+
|
3
5
|
module GitMQueue
|
4
6
|
class Consumer
|
5
7
|
def initialize(storage:, name:, branch:)
|
6
8
|
@storage = storage
|
7
9
|
@branch = branch
|
8
10
|
@name = name
|
9
|
-
|
11
|
+
end
|
12
|
+
|
13
|
+
def branch_file
|
14
|
+
@branch_file ||= File.join(@storage.path, 'refs', 'heads')
|
10
15
|
end
|
11
16
|
|
12
17
|
def consume(&block)
|
13
|
-
@
|
18
|
+
@block = block
|
19
|
+
listener.start
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop
|
23
|
+
listener.stop
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
14
27
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
28
|
+
def listener
|
29
|
+
@listener ||= Listen.to(branch_file, only: /#{Regexp.escape(@branch)}/) do
|
30
|
+
commits = @storage.commits(@branch, @name)
|
31
|
+
commits.each do |commit|
|
32
|
+
@block.call commit.message
|
33
|
+
@storage.tag(@name, commit)
|
34
|
+
end
|
19
35
|
end
|
20
36
|
end
|
21
37
|
end
|
data/lib/storage.rb
CHANGED
@@ -5,10 +5,7 @@ require 'rugged'
|
|
5
5
|
|
6
6
|
module GitMQueue
|
7
7
|
class Storage
|
8
|
-
|
9
|
-
WAIT_FOR_COMMIT = 1 # seconds
|
10
|
-
|
11
|
-
attr_reader :repo
|
8
|
+
attr_reader :repo, :path
|
12
9
|
|
13
10
|
def initialize(path)
|
14
11
|
@path = path
|
@@ -27,34 +24,22 @@ module GitMQueue
|
|
27
24
|
Rugged::Tree::Builder.new(repo).write
|
28
25
|
end
|
29
26
|
|
30
|
-
def
|
31
|
-
wait_for_commit(branch, tag)
|
32
|
-
walker(branch, tag).first
|
33
|
-
end
|
34
|
-
|
35
|
-
def tag(label, commit)
|
36
|
-
repo.tags.create(label, commit, true)
|
37
|
-
end
|
38
|
-
|
39
|
-
def wait_branch(branch)
|
40
|
-
sleep WAIT_NON_EXISTING_BRANCH until repo.branches.exist?(branch)
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
attr_reader :path
|
46
|
-
|
47
|
-
def walker(branch, tag)
|
27
|
+
def commits(branch, tag)
|
48
28
|
walker = Rugged::Walker.new(repo)
|
49
29
|
walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
|
50
30
|
walker.push(branch(branch).target)
|
51
31
|
|
52
32
|
tag = repo.tags[tag]
|
53
33
|
walker.hide(tag.target) if tag
|
54
|
-
|
55
34
|
walker
|
56
35
|
end
|
57
36
|
|
37
|
+
def tag(label, commit)
|
38
|
+
repo.tags.create(label, commit, true)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
58
43
|
def read_or_create_repo
|
59
44
|
FileUtils.mkdir_p(path) unless File.exist?(path)
|
60
45
|
begin
|
@@ -63,9 +48,5 @@ module GitMQueue
|
|
63
48
|
Rugged::Repository.init_at(path, :bare)
|
64
49
|
end
|
65
50
|
end
|
66
|
-
|
67
|
-
def wait_for_commit(branch, tag)
|
68
|
-
sleep WAIT_FOR_COMMIT until branch(branch).target.oid != repo.tags[tag]&.target&.oid
|
69
|
-
end
|
70
51
|
end
|
71
52
|
end
|
data/lib/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitmqueue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Emad Elsaid
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-11-
|
11
|
+
date: 2019-11-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rugged
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.28'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: listen
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.2.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.2.0
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|