zeta 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.markdown +2 -0
- data/Gemfile +2 -0
- data/Guardfile +10 -0
- data/README.md +209 -0
- data/Rakefile +2 -0
- data/bin/zeta +7 -0
- data/circle.yml +3 -0
- data/contracts/cache/service_1/consume.mson +1 -0
- data/contracts/cache/service_1/publish.mson +1 -0
- data/contracts/cache/service_2/consume.mson +1 -0
- data/contracts/cache/service_2/publish.mson +1 -0
- data/lib/zeta/instance.rb +145 -0
- data/lib/zeta/local_or_remote_file.rb +61 -0
- data/lib/zeta/runner.rb +83 -0
- data/lib/zeta/version.rb +3 -0
- data/lib/zeta.rb +23 -0
- data/todo.txt +4 -0
- data/zeta.gemspec +32 -0
- metadata +206 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 63a82870a90c693bb9e34ba0ce55aff5372166c6
|
4
|
+
data.tar.gz: a78e671775bc0677bdb678a3ddd4e60377471082
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7b08be9309a27352425d7fc8e1146746d538d18f019046d4585664d649fdeb7784e37432cd7f0d1615677227c656dc642b7a63790689465664a31c7b827b9925
|
7
|
+
data.tar.gz: 5c521252594b5344c7cd21b3649fe5f3ccafc487118e51ad515a571bac04c18a00ae97782d7ee617cfc482acafdf069b72701adb8e54b54dab8895fd8db677da
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.markdown
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
[![Circle CI](https://circleci.com/gh/moviepilot/zeta.svg?style=svg)](https://circleci.com/gh/moviepilot/zeta) [![Coverage Status](https://coveralls.io/repos/moviepilot/zeta/badge.svg?branch=master&service=github)](https://coveralls.io/github/moviepilot/zeta?branch=master) [![Code Climate](https://codeclimate.com/github/moviepilot/zeta/badges/gpa.svg)](https://codeclimate.com/github/moviepilot/zeta)
|
2
|
+
|
3
|
+
![](https://dl.dropboxusercontent.com/u/1953503/zeta.jpg)
|
4
|
+
|
5
|
+
# Old Maid
|
6
|
+
|
7
|
+
```
|
8
|
+
TLDR:
|
9
|
+
- each service defines which objects it publishes or consumes
|
10
|
+
- these contracts are formatted in human readable markdown
|
11
|
+
- you never have to know/care about other services or repositories
|
12
|
+
|
13
|
+
Old Maid will:
|
14
|
+
- know the rest of your infrastructure and fetches the contracts of all other services
|
15
|
+
- alert you if your change in service X breaks service Y
|
16
|
+
```
|
17
|
+
|
18
|
+
In an infrastructure where many services are talking with each other, it's sometimes hard to know **how changes in one service affect other services**, as each project often just knows about itself. Even if local tests pass, you can't know what other services might be affected when you make changes to a service.
|
19
|
+
|
20
|
+
*Old Maid* tackles this problem by allowing each service to define which objects it consumes, and which objects it publishes - in simple Markdown (specifically [MSON](https://github.com/apiaryio/mson)).It doesn't matter if these objected are transported via a HTTP, a message broker like RabbitMQ or any other mean.
|
21
|
+
|
22
|
+
|
23
|
+
## Walk this way
|
24
|
+
|
25
|
+
Let's imagine an imaginary chat app that is split up into three independent services that communicate via a message broker:
|
26
|
+
|
27
|
+
- **MessageService** keeps track of the state of to do items
|
28
|
+
- **SearchService** makes your chat history searchable
|
29
|
+
- **NotificationService**: sends an email when a private message is received
|
30
|
+
|
31
|
+
Each time a message is sent, MessageService publishes the full message to the message broker. While the SearchService is likely interested in the full message with all of its properties to index it probably, the NotificationService might just care about the sender and the receiver of the message, discarding all other properties.
|
32
|
+
|
33
|
+
An intern is asked to implement a feature that allows one message to be sent to multiple people at the same time. They go ahead and just change the numerical `recepient_id` property of a message to an `recipient_ids` array. They run their tests and all looks good.
|
34
|
+
|
35
|
+
😱But **THE INTERN JUST BROKE THE NOTIFICATION SERVICE** because it depends on the `recipient_id` property 😱
|
36
|
+
|
37
|
+
Wouldn't it be nice of some test local to the **MessageService** repository to tell the poorintern that removing the `recipient_id` property breaks the expectations other services have of the *MessageService* BEFORE they deploy?
|
38
|
+
|
39
|
+
|
40
|
+
## Yes, it would!
|
41
|
+
|
42
|
+
Each project have to contain two files in order for *Old Maid* to do its job:
|
43
|
+
|
44
|
+
1. `contracts/publish.mson`
|
45
|
+
2. `contracts/consume.mson`
|
46
|
+
|
47
|
+
These are simple markdown files in the wonderful [MSON](https://github.com/apiaryio/mson) format. Let's look at the contracts dir of **MessageService**, shall we?
|
48
|
+
|
49
|
+
### A publish specification:
|
50
|
+
```shell
|
51
|
+
/home/dev/message-service$ cat contracts/publish.mson
|
52
|
+
# Data Structures
|
53
|
+
This file defines what MessageService may publish.
|
54
|
+
|
55
|
+
# Message
|
56
|
+
- id: (number, required)
|
57
|
+
- sender_id: (number, required)
|
58
|
+
- recipients: (Array[number], required)
|
59
|
+
- text: (string, required)
|
60
|
+
- emoji: (string)
|
61
|
+
```
|
62
|
+
|
63
|
+
So far so good. This way *MessageService* can tell the world what exactly it means when a `Message` object is published. Much the same, the *NotificationService* could define which properties of a `Message` object from the `MessageService` it is actually interested in:
|
64
|
+
|
65
|
+
### A consume specification:
|
66
|
+
```shell
|
67
|
+
/home/dev/notification-service$ cat contracts/consume.mson
|
68
|
+
# Data Structures
|
69
|
+
We just consume one object type, and it comes from the MessageService. Check it out!
|
70
|
+
|
71
|
+
# MessageService:Message
|
72
|
+
- sender_id: (number, required)
|
73
|
+
- recipient_id: (number, required)
|
74
|
+
```
|
75
|
+
|
76
|
+
As you can see, this consumer expects the `recipient_id` property to be present when a `Message` object is received from `MessageService`. While a publish specification just defines objects, a consume specification prefixes the names of objects it consumes with the name of the service publishing the object. As in our example above:
|
77
|
+
|
78
|
+
```
|
79
|
+
# MessageService:Message
|
80
|
+
| `---------- object name
|
81
|
+
`------------------------- service name
|
82
|
+
|
83
|
+
```
|
84
|
+
|
85
|
+
## Getting started
|
86
|
+
|
87
|
+
### 1. Installation
|
88
|
+
First, add *Old Maid* to your `Gemfile` or install manually:
|
89
|
+
|
90
|
+
```shell
|
91
|
+
$ gem install zeta
|
92
|
+
```
|
93
|
+
|
94
|
+
### 2. Configuration
|
95
|
+
|
96
|
+
If you're using ruby on rails, *Old Maid* will automatically know your
|
97
|
+
environment and look for its configuration in `config/zeta.yml`, which could look like this:
|
98
|
+
|
99
|
+
```yaml
|
100
|
+
common: &common
|
101
|
+
# This name will be used by other services to identify objects they consume
|
102
|
+
# from the MessageService
|
103
|
+
service_name: MessageService
|
104
|
+
|
105
|
+
# Which directory contains the publish.mson and consume.mson
|
106
|
+
contracts_path: contracts
|
107
|
+
|
108
|
+
# Where to cache the contracts from all other projects that are part of the
|
109
|
+
# infrastructure. Old Maid will fetch these for you.
|
110
|
+
contracts_cache_path: contracts/.cache
|
111
|
+
|
112
|
+
|
113
|
+
development:
|
114
|
+
<<: *common
|
115
|
+
# The services file contains all services that are part of your infrastructure
|
116
|
+
# and tested with Old Maid. It's just another yaml file, but the nice thing is
|
117
|
+
# that it's outside of the service's repository and has to be maintained only
|
118
|
+
# in one place.
|
119
|
+
services_file:
|
120
|
+
|
121
|
+
# The file name to look for
|
122
|
+
file: 'development.yml'
|
123
|
+
|
124
|
+
# You can either host the file yourself via HTTP, but it's quite convenient
|
125
|
+
# to have it version controlled and hosted by a service like github. We'll
|
126
|
+
# call this repo zeta-config for this example:
|
127
|
+
github:
|
128
|
+
repo: jensmander/zeta-config
|
129
|
+
branch: master
|
130
|
+
path: infrastructure
|
131
|
+
|
132
|
+
# And here we can adjust things for each environment as needed
|
133
|
+
production:
|
134
|
+
<<: *common
|
135
|
+
...
|
136
|
+
|
137
|
+
```
|
138
|
+
|
139
|
+
You typically just create the above file once and then don't touch it anymore. If that file is in a private repository (that would be a good idea), make sure you `export GITHUB_USERNAME=youruser` and `GITHUB_TOKEN=yourtoken` and *Old Maid* will use that.
|
140
|
+
|
141
|
+
Here's how `github.com/jensmander/zeta-config/infrastructure/master.yml` might look in our example above:
|
142
|
+
|
143
|
+
```yaml
|
144
|
+
MessageService:
|
145
|
+
github:
|
146
|
+
repo: 'jensmander/messages'
|
147
|
+
branch: 'master'
|
148
|
+
path: 'contracts'
|
149
|
+
SearchService:
|
150
|
+
github:
|
151
|
+
repo: 'jensmander/search'
|
152
|
+
branch: 'master'
|
153
|
+
path: 'contracts'
|
154
|
+
NotificationService:
|
155
|
+
github:
|
156
|
+
repo: 'jensmander/notifications'
|
157
|
+
branch: 'master'
|
158
|
+
path: 'contracts'
|
159
|
+
```
|
160
|
+
|
161
|
+
Whenever you add a service to the infrastructure, you just add it to this central file and all existing services will automatically know about your new service.
|
162
|
+
|
163
|
+
|
164
|
+
### 3. Usage
|
165
|
+
|
166
|
+
```shell
|
167
|
+
Usage: zeta [options] full_check|fetch_remote_contracts|update_own_contracts|validate
|
168
|
+
|
169
|
+
Specific options:
|
170
|
+
-c, --config=CONFIG_FILE Config file (default: config/zeta.yml)
|
171
|
+
-e, --env=ENVIRONMENT Environment (default: RAILS_ENV, if it is set)
|
172
|
+
-s, --silent No output, just an appropriate return code
|
173
|
+
|
174
|
+
Common options:
|
175
|
+
-h, --help Show this message
|
176
|
+
-v, --version Show version
|
177
|
+
```
|
178
|
+
|
179
|
+
Example time. You can tell *Old Maid* to validate the whole infrastructure like this:
|
180
|
+
|
181
|
+
```shell
|
182
|
+
$ zeta -e development full_check
|
183
|
+
```
|
184
|
+
|
185
|
+
The above command performs the following three steps:
|
186
|
+
|
187
|
+
1. Fetch all contracts from remote repositories and put them into the cache directory configured above
|
188
|
+
2. Copy the current projects contracts (that you might have changed) into the cache directory
|
189
|
+
3. Validate all contracts (i.e. make sure that every publishing service satisfies its consumers)
|
190
|
+
|
191
|
+
The above commands can also be run in isolation:
|
192
|
+
|
193
|
+
```shell
|
194
|
+
$ zeta -e development fetch_contracts
|
195
|
+
```
|
196
|
+
|
197
|
+
This command will populate the `contracts/.cache` directory with the current version of all contracts and then copy over your local changes to your contract. You can then validate your infrastructure like this:
|
198
|
+
|
199
|
+
```shell
|
200
|
+
$ zeta -e development validate
|
201
|
+
```
|
202
|
+
|
203
|
+
If you just made changes to your local contracts, you can copy them over to the cache and validate your infrastructure like this:
|
204
|
+
|
205
|
+
```shell
|
206
|
+
$ zeta -e development update_own_contracts validate
|
207
|
+
```
|
208
|
+
|
209
|
+
Otherwise it will exit with an error and display any contract violations in JSON.
|
data/Rakefile
ADDED
data/bin/zeta
ADDED
data/circle.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Some MSON
|
@@ -0,0 +1 @@
|
|
1
|
+
Some MSON
|
@@ -0,0 +1 @@
|
|
1
|
+
Some MSON
|
@@ -0,0 +1 @@
|
|
1
|
+
Some MSON
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
require 'yaml'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'tmpdir'
|
5
|
+
require 'minimum-term'
|
6
|
+
|
7
|
+
require 'zeta/local_or_remote_file'
|
8
|
+
|
9
|
+
class Zeta
|
10
|
+
module Instance
|
11
|
+
attr_reader :config
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
@mutex = Mutex.new
|
15
|
+
@options = options
|
16
|
+
puts "Using config file #{config_file}" if verbose?
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_contracts
|
20
|
+
i = infrastructure
|
21
|
+
@mutex.synchronize do
|
22
|
+
puts "Updating #{cache_dir}" if verbose?
|
23
|
+
update_other_contracts
|
24
|
+
update_own_contracts
|
25
|
+
i.convert_all!
|
26
|
+
end
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_own_contracts
|
31
|
+
contract_files.each do |file|
|
32
|
+
source_file = File.join(config[:contracts_path], file)
|
33
|
+
target_file = File.join(cache_dir, config[:service_name], file)
|
34
|
+
puts "cp #{source_file} #{target_file}" if verbose?
|
35
|
+
FileUtils.rm_f(target_file)
|
36
|
+
FileUtils.cp(source_file, target_file) if File.exists?(source_file)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def errors
|
41
|
+
infrastructure.errors
|
42
|
+
end
|
43
|
+
|
44
|
+
def contracts_fulfilled?
|
45
|
+
infrastructure.contracts_fulfilled?
|
46
|
+
end
|
47
|
+
|
48
|
+
def infrastructure
|
49
|
+
@mutex.synchronize do
|
50
|
+
return @infrastructure if @infrastructure
|
51
|
+
@infrastructure = MinimumTerm::Infrastructure.new(data_dir: cache_dir, verbose: verbose?)
|
52
|
+
@infrastructure
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def config_file
|
57
|
+
return File.expand_path(@options[:config_file]) if @options[:config_file]
|
58
|
+
File.join(Dir.pwd, 'config', 'zeta.yml')
|
59
|
+
end
|
60
|
+
|
61
|
+
def env
|
62
|
+
return @options[:env].to_sym if @options[:env]
|
63
|
+
if Object.const_defined?('Rails')
|
64
|
+
Rails.env.to_sym
|
65
|
+
else
|
66
|
+
guessed = ENV['RAILS_ENV'] || ENV['RACK_ENV']
|
67
|
+
raise "No environment given" unless guessed
|
68
|
+
guessed
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def cache_dir
|
73
|
+
return @cache_dir if @cache_dir
|
74
|
+
full_path = File.expand_path(config[:contracts_cache_path])
|
75
|
+
FileUtils.mkdir_p(full_path)
|
76
|
+
@cache_dir = full_path
|
77
|
+
end
|
78
|
+
|
79
|
+
def config
|
80
|
+
return @config if @config
|
81
|
+
full_config = YAML.load_file(config_file).with_indifferent_access
|
82
|
+
env_config = full_config[env]
|
83
|
+
|
84
|
+
raise "No config for environment '#{env}' found in #{config_file}" unless env_config
|
85
|
+
|
86
|
+
# TODO validate it properly
|
87
|
+
[:service_name, :contracts_path, :contracts_cache_path].each do |k|
|
88
|
+
raise ":#{k} missing in #{full_config.to_json}" unless env_config[k]
|
89
|
+
end
|
90
|
+
|
91
|
+
@config = env_config
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def verbose?
|
97
|
+
@options[:verbose]
|
98
|
+
end
|
99
|
+
|
100
|
+
def fetch_service_contracts(service_name, config)
|
101
|
+
target_dir = File.join(cache_dir, service_name)
|
102
|
+
FileUtils.mkdir_p(target_dir)
|
103
|
+
|
104
|
+
contract_files.each do |contract|
|
105
|
+
file = File.join(target_dir, contract)
|
106
|
+
FileUtils.rm_f(file)
|
107
|
+
|
108
|
+
File.open(file, 'w') do |out|
|
109
|
+
contract = LocalOrRemoteFile.new(config.merge(file: contract, verbose: verbose?)).read
|
110
|
+
raise "Invalid contract:\n\n#{contract}\n#{'~'*80}" unless contract_looks_valid?(contract)
|
111
|
+
out.puts contract
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def contract_looks_valid?(contract)
|
117
|
+
true # TODO
|
118
|
+
end
|
119
|
+
|
120
|
+
def update_other_contracts
|
121
|
+
services.each do |service_name, config|
|
122
|
+
fetch_service_contracts(service_name, config)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def contract_files
|
127
|
+
['publish.mson', 'consume.mson']
|
128
|
+
end
|
129
|
+
|
130
|
+
def services
|
131
|
+
if config[:services]
|
132
|
+
return config[:services]
|
133
|
+
elsif config[:services_file]
|
134
|
+
file = LocalOrRemoteFile.new(config[:services_file].merge(debug: verbose?))
|
135
|
+
services = YAML.load(file.read)
|
136
|
+
begin
|
137
|
+
services.with_indifferent_access
|
138
|
+
rescue
|
139
|
+
raise "Could not load services from #{config[:services_file].to_json}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
require 'httparty'
|
3
|
+
require 'open-uri'
|
4
|
+
|
5
|
+
require 'pry'
|
6
|
+
|
7
|
+
class Zeta::LocalOrRemoteFile
|
8
|
+
def initialize(options)
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def read
|
13
|
+
if @options[:path]
|
14
|
+
read_local
|
15
|
+
elsif @options[:github]
|
16
|
+
read_from_github
|
17
|
+
else
|
18
|
+
raise "Unknown file location #{@options}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def read_local
|
25
|
+
open(File.join(@options[:path], @options[:file])).read
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_from_github
|
29
|
+
self.class.http_get(github_url, verbose?)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.http_get(url, verbose)
|
33
|
+
masked_url = ENV['GITHUB_TOKEN'].blank? ? url : url.sub(ENV['GITHUB_TOKEN'], '***')
|
34
|
+
print "GET #{masked_url}... " if verbose
|
35
|
+
result = HTTParty.get url
|
36
|
+
raise "Error #{result.code}" unless result.code == 200
|
37
|
+
print "OK\n".green if verbose
|
38
|
+
result.to_s
|
39
|
+
rescue
|
40
|
+
print "ERROR\n".blue if verbose
|
41
|
+
raise
|
42
|
+
end
|
43
|
+
|
44
|
+
def verbose?
|
45
|
+
!!@options[:verbose]
|
46
|
+
end
|
47
|
+
|
48
|
+
def github_url
|
49
|
+
repo = @options[:github][:repo]
|
50
|
+
branch = @options[:github][:branch]
|
51
|
+
path = @options[:github][:path]
|
52
|
+
file = @options[:file]
|
53
|
+
|
54
|
+
uri = [branch, path, file].compact.join('/')
|
55
|
+
if u = ENV['GITHUB_USER'] and t = ENV['GITHUB_TOKEN']
|
56
|
+
"https://#{u}:#{t}@raw.githubusercontent.com/#{repo}/#{uri}"
|
57
|
+
else
|
58
|
+
"https://raw.githubusercontent.com/#{repo}/#{uri}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/zeta/runner.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
class Zeta::Runner
|
5
|
+
COMMANDS = %w{full_check fetch_remote_contracts update_own_contracts validate}
|
6
|
+
|
7
|
+
def self.run
|
8
|
+
options = {}
|
9
|
+
parser = OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: zeta [options] #{COMMANDS.join('|')}"
|
11
|
+
|
12
|
+
opts.separator ""
|
13
|
+
opts.separator "Specific options:"
|
14
|
+
|
15
|
+
opts.on("-c CONFIG_FILE", "--config=CONFIG_FILE", "Config file (default: config/zeta.yml)") do |c|
|
16
|
+
options[:config_file] = c
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on("-e ENVIRONMENT", "--env=ENVIRONMENT", "Environment (default: RAILS_ENV, if it is set)") do |e|
|
20
|
+
options[:env] = e
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("-s", "--silent", "No output, just an appropriate return code") do |s|
|
24
|
+
options[:silent] = s
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.separator ""
|
28
|
+
opts.separator "Common options:"
|
29
|
+
|
30
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
31
|
+
puts opts
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
|
35
|
+
# Another typical switch to print the version.
|
36
|
+
opts.on_tail("-v", "--version", "Show version") do
|
37
|
+
puts Zeta::VERSION
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
end
|
41
|
+
parser.parse!
|
42
|
+
|
43
|
+
commands = ARGV
|
44
|
+
if commands.empty? or !(commands-COMMANDS).empty?
|
45
|
+
puts parser
|
46
|
+
exit(-1)
|
47
|
+
end
|
48
|
+
|
49
|
+
options[:verbose] = !options.delete(:silent)
|
50
|
+
zeta = Zeta.new(options)
|
51
|
+
|
52
|
+
begin
|
53
|
+
if commands.include?('fetch_remote_contracts') or commands.include?('full_check')
|
54
|
+
zeta.update_contracts
|
55
|
+
puts "\n" if options[:verbose]
|
56
|
+
end
|
57
|
+
|
58
|
+
if commands.include?('update_own_contracts')
|
59
|
+
puts "Copying #{zeta.config[:service_name].to_s.camelize} contracts..." if options[:verbose]
|
60
|
+
zeta.update_own_contracts
|
61
|
+
puts "\n" if options[:verbose]
|
62
|
+
end
|
63
|
+
|
64
|
+
if commands.include?('validate') or commands.include?('full_check')
|
65
|
+
puts "Validating your infrastructure with #{zeta.infrastructure.publishers.length} publishers and #{zeta.infrastructure.consumers.length} consumers..." if options[:verbose]
|
66
|
+
zeta.contracts_fulfilled?
|
67
|
+
unless zeta.errors.empty?
|
68
|
+
puts JSON.pretty_generate(zeta.errors)
|
69
|
+
puts "#{zeta.errors.length} invalid contracts".red
|
70
|
+
exit(-1)
|
71
|
+
end
|
72
|
+
puts "All contracts valid 🙌".green if options[:verbose]
|
73
|
+
end
|
74
|
+
rescue => e
|
75
|
+
if options[:verbose]
|
76
|
+
raise
|
77
|
+
else
|
78
|
+
puts "ERROR: ".red + e.message
|
79
|
+
end
|
80
|
+
exit(-1)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/zeta/version.rb
ADDED
data/lib/zeta.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'zeta/version'
|
3
|
+
require 'zeta/instance'
|
4
|
+
|
5
|
+
class Zeta
|
6
|
+
include Zeta::Instance
|
7
|
+
MUTEX = Mutex.new
|
8
|
+
|
9
|
+
# Not using the SingleForwardable module here so that, when
|
10
|
+
# somebody tries to figure out how Zeta works by looking at
|
11
|
+
# its methods, they don't get confused.
|
12
|
+
Zeta::Instance.instance_methods.each do |method|
|
13
|
+
define_singleton_method method do |*args|
|
14
|
+
send_args = [method, args].flatten.compact
|
15
|
+
MUTEX.synchronize do
|
16
|
+
@singleton ||= new
|
17
|
+
@singleton.send(*send_args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
data/todo.txt
ADDED
data/zeta.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'zeta/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'zeta'
|
8
|
+
spec.version = Zeta::VERSION
|
9
|
+
spec.authors = ['Jannis Hermanns']
|
10
|
+
spec.email = ['jannis@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'Collects and validates the publish/consume contracts of your infrastructure'
|
13
|
+
spec.description = 'Vlad'
|
14
|
+
spec.homepage = 'https://github.com/moviepilot/zeta'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = 'bin'
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_runtime_dependency 'rake', '~> 10.2'
|
22
|
+
spec.add_runtime_dependency 'minimum-term', '~> 0.2'
|
23
|
+
spec.add_runtime_dependency 'activesupport', '~> 4.2'
|
24
|
+
spec.add_runtime_dependency 'httparty', '~> 0.13'
|
25
|
+
spec.add_runtime_dependency 'colorize'
|
26
|
+
spec.add_runtime_dependency 'webmock'
|
27
|
+
|
28
|
+
spec.add_development_dependency 'bundler', '~> 1.9'
|
29
|
+
spec.add_development_dependency 'guard-bundler'
|
30
|
+
spec.add_development_dependency 'guard-rspec'
|
31
|
+
spec.add_development_dependency "coveralls", ["~> 0.8"]
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zeta
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jannis Hermanns
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '10.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minimum-term
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: httparty
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.13'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.13'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: colorize
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: webmock
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.9'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.9'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: guard-bundler
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: guard-rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: coveralls
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.8'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.8'
|
153
|
+
description: Vlad
|
154
|
+
email:
|
155
|
+
- jannis@gmail.com
|
156
|
+
executables:
|
157
|
+
- zeta
|
158
|
+
extensions: []
|
159
|
+
extra_rdoc_files: []
|
160
|
+
files:
|
161
|
+
- ".gitignore"
|
162
|
+
- ".rspec"
|
163
|
+
- ".travis.yml"
|
164
|
+
- CHANGELOG.markdown
|
165
|
+
- Gemfile
|
166
|
+
- Guardfile
|
167
|
+
- README.md
|
168
|
+
- Rakefile
|
169
|
+
- bin/zeta
|
170
|
+
- circle.yml
|
171
|
+
- contracts/cache/service_1/consume.mson
|
172
|
+
- contracts/cache/service_1/publish.mson
|
173
|
+
- contracts/cache/service_2/consume.mson
|
174
|
+
- contracts/cache/service_2/publish.mson
|
175
|
+
- lib/zeta.rb
|
176
|
+
- lib/zeta/instance.rb
|
177
|
+
- lib/zeta/local_or_remote_file.rb
|
178
|
+
- lib/zeta/runner.rb
|
179
|
+
- lib/zeta/version.rb
|
180
|
+
- todo.txt
|
181
|
+
- zeta.gemspec
|
182
|
+
homepage: https://github.com/moviepilot/zeta
|
183
|
+
licenses: []
|
184
|
+
metadata: {}
|
185
|
+
post_install_message:
|
186
|
+
rdoc_options: []
|
187
|
+
require_paths:
|
188
|
+
- lib
|
189
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0'
|
194
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
195
|
+
requirements:
|
196
|
+
- - ">="
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: '0'
|
199
|
+
requirements: []
|
200
|
+
rubyforge_project:
|
201
|
+
rubygems_version: 2.4.8
|
202
|
+
signing_key:
|
203
|
+
specification_version: 4
|
204
|
+
summary: Collects and validates the publish/consume contracts of your infrastructure
|
205
|
+
test_files: []
|
206
|
+
has_rdoc:
|