gitmq 0.1.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cd7432e882137f0309ae2c8ce3c086f04b048fc9effab91ce6188699ac019d99
4
+ data.tar.gz: 2963ec23a3681ec4499005f7de8ecf810517fb0731091b3d375109075fcb4571
5
+ SHA512:
6
+ metadata.gz: 62f9b7751214d6ccbff62987f761898dccfd922626e5d90863b04b463820ab4e95fdfdac94183492e99078d65ebf0d5fedf08f9c14530ea29f3efac5666e7a86
7
+ data.tar.gz: 71a7a5535992461d806aaec1a6e7da6dfff2a075f5734bf352d83830ea59896abba616ccaeca97aef5e44f3747c58abc2d2ba33fabcb35b286bad1de332d2cfd
@@ -0,0 +1,31 @@
1
+ name: Ruby Gem
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - master
7
+ push:
8
+ branches:
9
+ - master
10
+
11
+ jobs:
12
+ build:
13
+ name: Build + Publish
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@master
18
+ - name: Set up Ruby 2.6
19
+ uses: actions/setup-ruby@v1
20
+ with:
21
+ version: 2.6.x
22
+ - name: Publish to RubyGems
23
+ run: |
24
+ mkdir -p $HOME/.gem
25
+ touch $HOME/.gem/credentials
26
+ chmod 0600 $HOME/.gem/credentials
27
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
28
+ gem build *.gemspec
29
+ gem push *.gem
30
+ env:
31
+ GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
@@ -0,0 +1,3 @@
1
+ detectors:
2
+ IrresponsibleModule:
3
+ enabled: false
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ AllCops:
2
+ DefaultFormatter: simple
3
+ Style/Documentation:
4
+ Enabled: false
5
+ Naming/RescuedExceptionsVariableName:
6
+ Enabled: false
@@ -0,0 +1 @@
1
+ 2.6.3
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.3
7
+ before_install: gem install bundler -v 2.0.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in gitmq.gemspec
6
+ gemspec
@@ -0,0 +1,45 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ gitmq (0.1.2)
5
+ listen (~> 3.2.0)
6
+ rugged (~> 0.28)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
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)
16
+ rake (10.5.0)
17
+ rb-fsevent (0.10.3)
18
+ rb-inotify (0.10.0)
19
+ ffi (~> 1.0)
20
+ rspec (3.9.0)
21
+ rspec-core (~> 3.9.0)
22
+ rspec-expectations (~> 3.9.0)
23
+ rspec-mocks (~> 3.9.0)
24
+ rspec-core (3.9.0)
25
+ rspec-support (~> 3.9.0)
26
+ rspec-expectations (3.9.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.9.0)
29
+ rspec-mocks (3.9.0)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.9.0)
32
+ rspec-support (3.9.0)
33
+ rugged (0.28.3.1)
34
+
35
+ PLATFORMS
36
+ ruby
37
+
38
+ DEPENDENCIES
39
+ bundler (~> 2.0)
40
+ gitmq!
41
+ rake (~> 10.0)
42
+ rspec (~> 3.0)
43
+
44
+ BUNDLED WITH
45
+ 2.0.2
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Emad Elsaid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,174 @@
1
+ # GitMQ : Git Message Queue
2
+
3
+ Hey, Crazy idea bad pitch, lets use Git as a messaging queue! lets use github to
4
+ synchronize a cluster of them together.
5
+
6
+ ![Screenshot-2019-11-24_22-41-55](https://user-images.githubusercontent.com/54403/69502136-09672400-0f0c-11ea-8707-df3164411c4e.png)
7
+
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
18
+ your git repo, you can create an empty commit with a command like this:
19
+
20
+ ```
21
+ git commit --allow-empty -m "commit message"
22
+ ```
23
+
24
+ 1. OK, so the commit message can be any text right? that means you can have JSON
25
+ messages or YAML or any other machine readable format as a commit message.
26
+
27
+ ## Now imagine this use case:
28
+
29
+ 1. Create a repository on github
30
+ 1. Clone it to your server
31
+ 1. Have your application commit a `message` to this repo every time you need to
32
+ 1. Push the repo every period say 10 seconds
33
+ 1. Clone same repo to another server
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
36
+ 1. Put a git tag on the last processed message with that consumer identifier
37
+ 1. Push that label to keep track of the last processed message (pull it when you
38
+ deploy that consumer again)
39
+
40
+ Now you have a queue of messages that can't be altered, communicated between
41
+ servers and all data saved in a git repository.
42
+
43
+ This idea is flexible and can be altered for several use cases:
44
+
45
+ 1. the central server doesn't have to be Github or a like, you can use your
46
+ own server as a git server.
47
+ 1. You can have more than one queue for messages by creating many branches each
48
+ for a type of messages or a different messages purpose.
49
+ 1. You can have the same branch consumed from different servers each message
50
+ processed multiple times in different ways like RabbitMQ fanout strategy
51
+ 1. You can keep browse your message queue with any git client UI or CLI
52
+ 1. You can clean your servers git local repo so that it deletes old commit
53
+ history to save space.
54
+ 1. You can have many messages producers write to different branches in the same
55
+ repository
56
+ 1. In the future you can join 2 branches or split a branch and have your
57
+ consumers adapt to that
58
+
59
+
60
+ There are many advantages to use this approach, which is inherited from how Git
61
+ is designed
62
+
63
+ 1. Every message has an address (git commit hash)
64
+ 1. Every message can be traced to an author which is the application that
65
+ published it (you can use different names for each server or application
66
+ version)
67
+ 1. Every message has a creation date
68
+
69
+ This idea can as simple as invoking git commands from your application with a
70
+ system call, or use libgit to manipulate the repository for you, or even a small
71
+ server application that communicate over a socket, or a repo service that
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 gitmq
81
+ ```
82
+
83
+ or add gitmq to your `Gemfile`
84
+ ```
85
+ gem 'gitmq', '~> 0.1'
86
+ ```
87
+
88
+
89
+ # Producing messages
90
+
91
+ First create an instance of `GitMQ::Storage`, to specify your storage place.
92
+
93
+ ```ruby
94
+ storage = GitMQ::Storage.new('/tmp/gitmq')
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 = GitMQ::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 = GitMQ::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
+ ## gitmq-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
+ gitmq-produce /path/to/rpo branch-name
163
+ ```
164
+
165
+ ## gitmq-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
+ gitmq-consume /path/to/repo branch-name consumer-name
174
+ ```
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'gitmq'
5
+
6
+ path = ARGV.shift
7
+ branch = ARGV.shift
8
+ consumer = ARGV.shift
9
+
10
+ consumer = GitMQ::Consumer.new(
11
+ storage: GitMQ::Storage.new(path),
12
+ name: consumer,
13
+ branch: branch
14
+ )
15
+
16
+ consumer.consume { |m| puts m }
17
+
18
+ begin
19
+ sleep
20
+ rescue Interrupt
21
+ puts 'Bye.'
22
+ end
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'gitmq'
5
+
6
+ path = ARGV.shift
7
+ branch = ARGV.shift
8
+
9
+ producer = GitMQ::Producer.new(
10
+ storage: GitMQ::Storage.new(path),
11
+ branch: branch
12
+ )
13
+
14
+ loop do
15
+ producer.publish(gets.strip)
16
+ rescue Interrupt
17
+ puts 'Bye.'
18
+ exit
19
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'gitmq'
9
+ spec.version = GitMQ::VERSION
10
+ spec.authors = ['Emad Elsaid']
11
+ spec.email = ['emad.elsaid.hamed@gmail.com']
12
+
13
+ spec.summary = 'Git Message Queue'
14
+ spec.description = <<-DESC
15
+ An interface to use Git repositories as a message queue to communicate between services'
16
+ DESC
17
+ spec.homepage = 'https://www.github.com/emad-elsaid/gitmq'
18
+ spec.license = 'MIT'
19
+
20
+ spec.metadata['homepage_uri'] = spec.homepage
21
+ spec.metadata['source_code_uri'] = spec.homepage
22
+
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
25
+ end
26
+ spec.bindir = 'bin'
27
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+
30
+ spec.add_dependency 'rugged', '~> 0.28'
31
+ spec.add_dependency 'listen', '~> 3.2.0'
32
+ spec.add_development_dependency 'bundler', '~> 2.0'
33
+ spec.add_development_dependency 'rake', '~> 10.0'
34
+ spec.add_development_dependency 'rspec', '~> 3.0'
35
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'listen'
4
+
5
+ module GitMQ
6
+ class Consumer
7
+ def initialize(storage:, name:, branch:)
8
+ @storage = storage
9
+ @branch = branch
10
+ @name = name
11
+ end
12
+
13
+ def branch_file
14
+ @branch_file ||= File.join(@storage.path, 'refs', 'heads')
15
+ end
16
+
17
+ def consume(&block)
18
+ @block = block
19
+ consume_commits
20
+ listener.start
21
+ end
22
+
23
+ def stop
24
+ listener.stop
25
+ end
26
+
27
+ private
28
+
29
+ def listener
30
+ @listener ||= Listen.to(branch_file, only: /#{Regexp.escape(@branch)}/) do
31
+ consume_commits
32
+ end
33
+ end
34
+
35
+ def consume_commits
36
+ commits = @storage.commits(@branch, @name)
37
+ commits.each do |commit|
38
+ @block.call commit.message
39
+ @storage.tag(@name, commit)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './version'
4
+ require_relative './consumer'
5
+ require_relative './producer'
6
+ require_relative './storage'
7
+
8
+ module GitMQ
9
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitMQ
4
+ class Producer
5
+ def initialize(storage:, branch:)
6
+ @storage = storage
7
+ @branch = branch
8
+ end
9
+
10
+ def publish(event)
11
+ commit = Rugged::Commit.create(
12
+ @storage.repo,
13
+ tree: @storage.tree,
14
+ message: event.to_s,
15
+ parents: [@storage.branch(@branch)&.target].compact
16
+ )
17
+ @storage.branches.create(@branch, commit, force: true)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'rugged'
5
+
6
+ module GitMQ
7
+ class Storage
8
+ attr_reader :repo, :path
9
+
10
+ def initialize(path)
11
+ @path = path
12
+ @repo = read_or_create_repo
13
+ end
14
+
15
+ def branches
16
+ repo.branches
17
+ end
18
+
19
+ def branch(name)
20
+ branches[name]
21
+ end
22
+
23
+ def tree
24
+ Rugged::Tree::Builder.new(repo).write
25
+ end
26
+
27
+ def commits(branch, tag)
28
+ walker = Rugged::Walker.new(repo)
29
+ walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
30
+ walker.push(branch(branch).target)
31
+
32
+ tag = repo.tags[tag]
33
+ walker.hide(tag.target) if tag
34
+ walker
35
+ end
36
+
37
+ def tag(label, commit)
38
+ repo.tags.create(label, commit, true)
39
+ end
40
+
41
+ private
42
+
43
+ def read_or_create_repo
44
+ FileUtils.mkdir_p(path) unless File.exist?(path)
45
+ begin
46
+ Rugged::Repository.new(path)
47
+ rescue Rugged::RepositoryError
48
+ Rugged::Repository.init_at(path, :bare)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitMQ
4
+ VERSION = '0.1.2'
5
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gitmq
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Emad Elsaid
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-11-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rugged
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.28'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
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
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: " An interface to use Git repositories as a message queue to communicate
84
+ between services'\n"
85
+ email:
86
+ - emad.elsaid.hamed@gmail.com
87
+ executables:
88
+ - gitmq-consume
89
+ - gitmq-produce
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - ".github/workflows/gempush.yml"
94
+ - ".gitignore"
95
+ - ".reek.yml"
96
+ - ".rspec"
97
+ - ".rubocop.yml"
98
+ - ".ruby-version"
99
+ - ".travis.yml"
100
+ - Gemfile
101
+ - Gemfile.lock
102
+ - LICENSE.txt
103
+ - README.md
104
+ - Rakefile
105
+ - bin/gitmq-consume
106
+ - bin/gitmq-produce
107
+ - gitmq.gemspec
108
+ - lib/consumer.rb
109
+ - lib/gitmq.rb
110
+ - lib/producer.rb
111
+ - lib/storage.rb
112
+ - lib/version.rb
113
+ homepage: https://www.github.com/emad-elsaid/gitmq
114
+ licenses:
115
+ - MIT
116
+ metadata:
117
+ homepage_uri: https://www.github.com/emad-elsaid/gitmq
118
+ source_code_uri: https://www.github.com/emad-elsaid/gitmq
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubygems_version: 3.0.3
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Git Message Queue
138
+ test_files: []