algo 0.1.0
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 +5 -0
- data/Gemfile +4 -0
- data/README.md +132 -0
- data/Rakefile +6 -0
- data/algo.gemspec +29 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/examples/awesome-cluster.rb +36 -0
- data/exe/algo +6 -0
- data/lib/algo/cli.rb +155 -0
- data/lib/algo/docker/base.rb +26 -0
- data/lib/algo/docker/connection.rb +98 -0
- data/lib/algo/docker/error.rb +44 -0
- data/lib/algo/docker/network.rb +43 -0
- data/lib/algo/docker/service.rb +84 -0
- data/lib/algo/docker/version.rb +5 -0
- data/lib/algo/docker.rb +63 -0
- data/lib/algo/dsl/cluster.rb +47 -0
- data/lib/algo/dsl/network.rb +57 -0
- data/lib/algo/dsl/service.rb +110 -0
- data/lib/algo/dsl.rb +34 -0
- data/lib/algo/version.rb +3 -0
- data/lib/algo.rb +13 -0
- metadata +168 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bb278f86fccf497cb5ece005a59c45ac56929fef
|
4
|
+
data.tar.gz: 8cf7c48ff992326edf08c6500cb48de73e8be902
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4b3a4d6561a8adda98ad72b85e5b6e22896e5b8fad5f719adc81b9702b5b7bbcd416b50d9c04e02516cc1bb5aad81b4493d0533d82927875a9babb2f797c646c
|
7
|
+
data.tar.gz: d54c9d0a2d304a8fbf8fa635f988241ba2f4b925b0c787034d93cbbc2413368287470041a2b6fcaf4a44aecd78b8006fe12aa3f1610947599860a166abbd379e
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# Algo
|
2
|
+
Docker container orchestration tool for swarm cluster.
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
|
6
|
+
Add this line to your application's Gemfile:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
gem 'algo'
|
10
|
+
```
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install algo
|
19
|
+
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Definition
|
24
|
+
|
25
|
+
```rb
|
26
|
+
cluster 'awesomecluster' do
|
27
|
+
|
28
|
+
# Define service/network prefix for cluster
|
29
|
+
prefix 'awsm'
|
30
|
+
|
31
|
+
# Define cluster wide available environment variable
|
32
|
+
env 'CLUSTER_ENV', 'PRODUCTION'
|
33
|
+
|
34
|
+
# Define cluster wide available label
|
35
|
+
label 'com.example.sample', 'clusterwidelabel'
|
36
|
+
|
37
|
+
# Define network
|
38
|
+
network 'net1'
|
39
|
+
|
40
|
+
# Define service
|
41
|
+
service 'name' do
|
42
|
+
image 'quay.io/yss44/curl'
|
43
|
+
replicas 3
|
44
|
+
command 'sh'
|
45
|
+
args '-ic', "while true; do curl -s awsm-nginx > /dev/null; echo $?; sleep 3; done"
|
46
|
+
|
47
|
+
update_parallelism 2
|
48
|
+
|
49
|
+
# Service related environment variable
|
50
|
+
env 'APP_DOMAIN', 'example.com'
|
51
|
+
|
52
|
+
network 'net1'
|
53
|
+
end
|
54
|
+
|
55
|
+
# Define another service
|
56
|
+
service 'nginx' do
|
57
|
+
image 'nginx:alpine'
|
58
|
+
replicas 2
|
59
|
+
network 'net1'
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
### Execution
|
66
|
+
|
67
|
+
```sh
|
68
|
+
# Prepare playground for algo
|
69
|
+
docker-machine create --driver virtualbox \
|
70
|
+
--virtualbox-boot2docker-url="https://github.com/boot2docker/boot2docker/releases/download/v1.12.0-rc4/boot2docker-experimental.iso" \
|
71
|
+
algo
|
72
|
+
eval $(docker-machine env algo)
|
73
|
+
|
74
|
+
# Create initial cluster
|
75
|
+
algo apply examples/awesomecluster.rb
|
76
|
+
# Applying to cluster awesomecluster...
|
77
|
+
# network: awsm-net1, status: created
|
78
|
+
# service: awsm-name, status: created
|
79
|
+
# service: awsm-nginx, status: created
|
80
|
+
# Complete applying for cluster awesomecluster!
|
81
|
+
|
82
|
+
# Change configuration
|
83
|
+
sed -i s/replicas 2/replicas 1/g examples/awesomecluster.rb
|
84
|
+
|
85
|
+
# Dry-run
|
86
|
+
algo apply examples/awesomecluster.rb --dry-run
|
87
|
+
# Running with dry-run mode...
|
88
|
+
# Applying to cluster awesomecluster...
|
89
|
+
# network: awsm-net1, status: ok
|
90
|
+
# service: awsm-name, status: ok
|
91
|
+
# service: awsm-nginx, status: changed
|
92
|
+
# Complete applying for cluster awesomecluster!
|
93
|
+
|
94
|
+
# Apply changes
|
95
|
+
algo apply examples/awesomecluster.rb --dry-run
|
96
|
+
# Applying to cluster awesomecluster...
|
97
|
+
# network: awsm-net1, status: ok
|
98
|
+
# service: awsm-name, status: ok
|
99
|
+
# service: awsm-nginx, status: changed
|
100
|
+
# Complete applying for cluster awesomecluster!
|
101
|
+
|
102
|
+
# Dry-run terminating cluster
|
103
|
+
algo apply examples/awesomecluster.rb --dry-run
|
104
|
+
# Running with dry-run mode...
|
105
|
+
# Terminating cluster awesomecluster...
|
106
|
+
# service: awsm-name, status: removed
|
107
|
+
# service: awsm-nginx, status: removed
|
108
|
+
# network: awsm-net1, status: removed
|
109
|
+
# Complete Termination for cluster awesomecluster...
|
110
|
+
|
111
|
+
# Terminate cluster
|
112
|
+
algo apply examples/awesomecluster.rb
|
113
|
+
# Terminating cluster awesomecluster...
|
114
|
+
# service: awsm-name, status: removed
|
115
|
+
# service: awsm-nginx, status: removed
|
116
|
+
# network: awsm-net1, status: removed
|
117
|
+
# Complete Termination for cluster awesomecluster...
|
118
|
+
```
|
119
|
+
|
120
|
+
## Development
|
121
|
+
|
122
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
123
|
+
|
124
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
125
|
+
|
126
|
+
## Contributing
|
127
|
+
|
128
|
+
1. Fork it ( https://github.com/yoshiso/algo/fork )
|
129
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
130
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
131
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
132
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/algo.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'algo/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "algo"
|
8
|
+
spec.version = Algo::VERSION
|
9
|
+
spec.authors = ["yoshiso"]
|
10
|
+
spec.email = ["nya060@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Docker container orchestration tool for swarm cluster.}
|
13
|
+
spec.description = %q{Docker container orchestration tool for swarm cluster.}
|
14
|
+
spec.homepage = "https://github.com/yoshiso/algo"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency 'activesupport', '~> 4.0'
|
23
|
+
spec.add_dependency 'excon', '0.51.0'
|
24
|
+
spec.add_dependency 'thor'
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
28
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
29
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "algo"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
require "pry"
|
11
|
+
Pry.start
|
data/bin/setup
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
cluster 'awesomecluster' do
|
2
|
+
|
3
|
+
# Define service/network prefix for cluster
|
4
|
+
prefix 'awsm'
|
5
|
+
|
6
|
+
# Define cluster wide available environment variable
|
7
|
+
env 'CLUSTER_ENV', 'PRODUCTION'
|
8
|
+
|
9
|
+
# Define cluster wide available label
|
10
|
+
label 'com.example.sample', 'clusterwidelabel'
|
11
|
+
|
12
|
+
# Define network
|
13
|
+
network 'net1'
|
14
|
+
|
15
|
+
# Define service
|
16
|
+
service 'name' do
|
17
|
+
image 'quay.io/yss44/curl'
|
18
|
+
replicas 3
|
19
|
+
command 'sh'
|
20
|
+
args '-ic', "while true; do curl -s awsm-nginx > /dev/null; echo $?; sleep 3; done"
|
21
|
+
|
22
|
+
update_parallelism 2
|
23
|
+
|
24
|
+
env 'APP_DOMAIN', 'example.com'
|
25
|
+
|
26
|
+
network 'net1'
|
27
|
+
end
|
28
|
+
|
29
|
+
# Define another service
|
30
|
+
service 'nginx' do
|
31
|
+
image 'nginx:alpine'
|
32
|
+
replicas 2
|
33
|
+
network 'net1'
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/exe/algo
ADDED
data/lib/algo/cli.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
module Algo
|
2
|
+
class Cli < Thor
|
3
|
+
class ValidationError < StandardError; end
|
4
|
+
|
5
|
+
class ServiceValidator
|
6
|
+
def initialize srv_spec
|
7
|
+
@srv_spec = srv_spec
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate
|
11
|
+
begin
|
12
|
+
@srv = Algo::Docker::Service.find(@srv_spec['Name'])
|
13
|
+
rescue Algo::Docker::Error::NotFoundError
|
14
|
+
@srv = nil
|
15
|
+
end
|
16
|
+
check_networks
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.validate srv_spec
|
20
|
+
new(srv_spec).validate
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def check_networks
|
26
|
+
return true if @srv.blank? or @srv.spec.networks.blank?
|
27
|
+
srv_networks = @srv.spec.networks.map { |n| { 'Target' => n.info['Name'] } }
|
28
|
+
unless srv_networks != @srv_spec['Networks']
|
29
|
+
@srv_spec['Networks'] = @srv.spec.networks.map { |n| { 'Target' => n.info['Id'] } }
|
30
|
+
return true
|
31
|
+
end
|
32
|
+
raise ValidationError, 'changing network in service is not supported'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class ServiceUpdator
|
37
|
+
|
38
|
+
def initialize srv_spec, options
|
39
|
+
@srv_spec = srv_spec
|
40
|
+
@options = options
|
41
|
+
end
|
42
|
+
|
43
|
+
def update
|
44
|
+
begin
|
45
|
+
srv = Algo::Docker::Service.find(@srv_spec['Name'])
|
46
|
+
if srv.raw_spec == @srv_spec
|
47
|
+
puts "service: #{@srv_spec['Name']}, status: ok"
|
48
|
+
return
|
49
|
+
end
|
50
|
+
srv.update @srv_spec unless dryrun?
|
51
|
+
puts "service: #{@srv_spec['Name']}, status: changed"
|
52
|
+
rescue Algo::Docker::Error::NotFoundError
|
53
|
+
Algo::Docker::Service.create(@srv_spec) unless dryrun?
|
54
|
+
puts "service: #{@srv_spec['Name']}, status: created"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.update srv_spec, dryrun=false
|
59
|
+
new(srv_spec, {dryrun: dryrun}).update
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def dryrun?
|
65
|
+
@options[:dryrun]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
desc 'apply [INVENTRY_FILE]', 'Apply configuration to clusters'
|
70
|
+
option :'dry-run', type: :boolean, default: false
|
71
|
+
option :'url', type: :string, desc: 'docker swarm url like tcp://localhost:2375'
|
72
|
+
option :'client_key', type: :string, desc: 'docker swarm client key path'
|
73
|
+
option :'client_sert', type: :string, desc: 'docker swarm client sert path'
|
74
|
+
option :'ssl_ca_file', type: :string, desc: 'docker swarm ssl ca file path'
|
75
|
+
option :'scheme', type: :string, desc: 'docker swarm connection scheme'
|
76
|
+
def apply inventry
|
77
|
+
Algo::Docker.url = options[:host] if options[:host]
|
78
|
+
Algo::Docker.options = docker_opts if docker_opts.present?
|
79
|
+
puts 'Running with dry-run mode...' if options[:'dry-run']
|
80
|
+
configuration = Algo::Dsl.load({}, inventry)
|
81
|
+
configuration.each do |cluster|
|
82
|
+
puts "Applying to cluster #{cluster['name']}..."
|
83
|
+
|
84
|
+
cluster['networks'].each do |net_spec|
|
85
|
+
begin
|
86
|
+
net = Algo::Docker::Network.find net_spec['Name']
|
87
|
+
puts "network: #{net_spec['Name']}, status: ok"
|
88
|
+
rescue Algo::Docker::Error::NotFoundError
|
89
|
+
Algo::Docker::Network.create net_spec unless options[:'dry-run']
|
90
|
+
puts "network: #{net_spec['Name']}, status: created"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
cluster['services'].each do |srv_spec|
|
95
|
+
ServiceValidator.validate srv_spec
|
96
|
+
end
|
97
|
+
cluster['services'].each do |srv_spec|
|
98
|
+
ServiceUpdator.update srv_spec, options[:'dry-run']
|
99
|
+
end
|
100
|
+
Algo::Docker::Service.all
|
101
|
+
.select { |srv| srv.spec.name.start_with?("#{cluster['prefix']}-") }
|
102
|
+
.select { |srv| ! srv.spec.name.in? cluster['services'].map { |spec| spec['Name'] } }
|
103
|
+
.map { |srv|
|
104
|
+
srv_name = srv.spec.name
|
105
|
+
srv.remove unless options[:'dry-run']
|
106
|
+
puts "service: #{srv_name}, status: removed"
|
107
|
+
}
|
108
|
+
Algo::Docker::Network.all(skip_default=true)
|
109
|
+
.select { |net| net.info['Name'].start_with?("#{cluster['prefix']}-") }
|
110
|
+
.select { |net| ! net.info['Name'].in? cluster['networks'].map { |net_spec| net_spec['Name'] } }
|
111
|
+
.map { |net|
|
112
|
+
net_name = net.info['Name']
|
113
|
+
net.remove unless options[:'dry-run']
|
114
|
+
puts "network: #{net_name}, status: removed"
|
115
|
+
}
|
116
|
+
puts "Complete applying for cluster #{cluster['name']}!"
|
117
|
+
end
|
118
|
+
rescue ValidationError => e
|
119
|
+
puts 'configuration validation failed because ' + e.message
|
120
|
+
end
|
121
|
+
|
122
|
+
desc 'rm [INVENTRY_FILE]', 'Terminate clusters'
|
123
|
+
option :'dry-run', type: :boolean, default: false
|
124
|
+
def rm inventry
|
125
|
+
puts 'Running with dry-run mode...' if options[:'dry-run']
|
126
|
+
configuration = Algo::Dsl.load({}, inventry)
|
127
|
+
configuration.each do |cluster|
|
128
|
+
puts "Terminating cluster #{cluster['name']}..."
|
129
|
+
Algo::Docker::Service.all
|
130
|
+
.select { |srv| srv.spec.name.start_with?("#{cluster['prefix']}-") }
|
131
|
+
.map { |srv|
|
132
|
+
srv_name = srv.spec.name
|
133
|
+
srv.remove unless options[:'dry-run']
|
134
|
+
puts "service: #{srv_name}, status: removed"
|
135
|
+
}
|
136
|
+
Algo::Docker::Network.all(skip_default=true)
|
137
|
+
.select { |net| net.info['Name'].start_with?("#{cluster['prefix']}-") }
|
138
|
+
.select { |net| ! net.info['Name'].in? cluster['networks'].map { |net_spec| "#{cluster['prefix']}-#{net_spec['Name']}" } }
|
139
|
+
.map { |net|
|
140
|
+
net_name = net.info['Name']
|
141
|
+
net.remove unless options[:'dry-run']
|
142
|
+
puts "network: #{net_name}, status: removed"
|
143
|
+
}
|
144
|
+
puts "Complete Termination for cluster #{cluster['name']}..."
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def docker_opts
|
151
|
+
options.slice(:client_key, :client_sert, :ssl_ca_file, :scheme)
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Algo
|
2
|
+
module Docker
|
3
|
+
class Base
|
4
|
+
include Docker::Error
|
5
|
+
|
6
|
+
attr_reader :id, :info
|
7
|
+
|
8
|
+
def initialize(connection, hash={})
|
9
|
+
unless connection.is_a?(Docker::Connection)
|
10
|
+
raise ArgumentError, "Expected a Docker::Connection, got: #{connection}."
|
11
|
+
end
|
12
|
+
normalize_hash(hash)
|
13
|
+
@connection, @info, @id = connection, hash, hash['Id']
|
14
|
+
raise ArgumentError, "Must have id, got: #{hash}" unless @id
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
attr_accessor :connection
|
19
|
+
|
20
|
+
def normalize_hash(hash)
|
21
|
+
hash["Id"] ||= hash.delete("ID")
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Algo
|
2
|
+
module Docker
|
3
|
+
class Connection
|
4
|
+
include Docker::Error
|
5
|
+
attr_reader :url, :options
|
6
|
+
|
7
|
+
# Create a new Connection. This method takes a url (String) and options
|
8
|
+
# (Hash). These are passed to Excon, so any options valid for `Excon.new`
|
9
|
+
# can be passed here.
|
10
|
+
def initialize(url, opts)
|
11
|
+
case
|
12
|
+
when !url.is_a?(String)
|
13
|
+
raise ArgumentError, "Expected a String, got: '#{url}'"
|
14
|
+
when !opts.is_a?(Hash)
|
15
|
+
raise ArgumentError, "Expected a Hash, got: '#{opts}'"
|
16
|
+
else
|
17
|
+
uri = URI.parse(url)
|
18
|
+
if uri.scheme == "unix"
|
19
|
+
@url, @options = 'unix:///', {:socket => uri.path}.merge(opts)
|
20
|
+
elsif uri.scheme =~ /^(https?|tcp)$/
|
21
|
+
@url, @options = url, opts
|
22
|
+
else
|
23
|
+
@url, @options = "http://#{uri}", opts
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# The actual client that sends HTTP methods to the Docker server. This value
|
29
|
+
# is not cached, since doing so may cause socket errors after bad requests.
|
30
|
+
def resource
|
31
|
+
Excon.new(url, options)
|
32
|
+
end
|
33
|
+
private :resource
|
34
|
+
|
35
|
+
# Send a request to the server with the `
|
36
|
+
def request(*args, &block)
|
37
|
+
request = compile_request_params(*args, &block)
|
38
|
+
# log_request(request)
|
39
|
+
response = resource.request(request).body
|
40
|
+
JSON.parse(response) unless response.blank?
|
41
|
+
rescue Excon::Errors::BadRequest => ex
|
42
|
+
raise ClientError, ex.response.body
|
43
|
+
rescue Excon::Errors::Unauthorized => ex
|
44
|
+
raise UnauthorizedError, ex.response.body
|
45
|
+
rescue Excon::Errors::NotFound => ex
|
46
|
+
raise NotFoundError, ex.response.body
|
47
|
+
rescue Excon::Errors::Conflict => ex
|
48
|
+
raise ConflictError, ex.response.body
|
49
|
+
rescue Excon::Errors::InternalServerError => ex
|
50
|
+
raise ServerError, ex.response.body
|
51
|
+
rescue Excon::Errors::Timeout => ex
|
52
|
+
raise TimeoutError, ex.message
|
53
|
+
end
|
54
|
+
|
55
|
+
# def log_request(request)
|
56
|
+
# if Docker.logger
|
57
|
+
# Docker.logger.debug(
|
58
|
+
# [request[:method], request[:path], request[:query], request[:body]]
|
59
|
+
# )
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
|
63
|
+
# Delegate all HTTP methods to the #request.
|
64
|
+
[:get, :put, :post, :delete].each do |method|
|
65
|
+
define_method(method) { |*args, &block| request(method, *args, &block) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
"Docker::Connection { :url => #{url}, :options => #{options} }"
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
# Given an HTTP method, path, optional query, extra options, and block,
|
74
|
+
# compiles a request.
|
75
|
+
def compile_request_params(http_method, path, query = nil, opts = nil, &block)
|
76
|
+
query ||= {}
|
77
|
+
opts ||= {}
|
78
|
+
headers = opts.delete(:headers) || {}
|
79
|
+
content_type = opts[:body].nil? ? 'text/plain' : 'application/json'
|
80
|
+
user_agent = "github.com:yoshiso/algo v#{Algo::VERSION}"
|
81
|
+
{
|
82
|
+
:method => http_method,
|
83
|
+
:path => "/v#{Docker::API_VERSION}#{path}",
|
84
|
+
:query => query,
|
85
|
+
:headers => { 'Content-Type' => content_type,
|
86
|
+
'User-Agent' => user_agent,
|
87
|
+
}.merge(headers),
|
88
|
+
:expects => (200..204).to_a << 304,
|
89
|
+
:idempotent => http_method == :get,
|
90
|
+
:request_block => block
|
91
|
+
}.merge(opts).reject { |_, v| v.nil? }
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# This module holds the Errors for the gem.
|
2
|
+
module Algo
|
3
|
+
module Docker
|
4
|
+
module Error
|
5
|
+
|
6
|
+
# The default error. It's never actually raised, but can be used to catch all
|
7
|
+
# gem-specific errors that are thrown as they all subclass from this.
|
8
|
+
class DockerError < StandardError; end
|
9
|
+
|
10
|
+
# Raised when invalid arguments are passed to a method.
|
11
|
+
class ArgumentError < DockerError; end
|
12
|
+
|
13
|
+
# Raised when a request returns a 400.
|
14
|
+
class ClientError < DockerError; end
|
15
|
+
|
16
|
+
# Raised when a request returns a 401.
|
17
|
+
class UnauthorizedError < DockerError; end
|
18
|
+
|
19
|
+
# Raised when a request returns a 404.
|
20
|
+
class NotFoundError < DockerError; end
|
21
|
+
|
22
|
+
# Raised when a request returns a 409.
|
23
|
+
class ConflictError < DockerError; end
|
24
|
+
|
25
|
+
# Raised when a request returns a 500.
|
26
|
+
class ServerError < DockerError; end
|
27
|
+
|
28
|
+
# Raised when there is an unexpected response code / body.
|
29
|
+
class UnexpectedResponseError < DockerError; end
|
30
|
+
|
31
|
+
# Raised when there is an incompatible version of Docker.
|
32
|
+
class VersionError < DockerError; end
|
33
|
+
|
34
|
+
# Raised when a request times out.
|
35
|
+
class TimeoutError < DockerError; end
|
36
|
+
|
37
|
+
# Raised when login fails.
|
38
|
+
class AuthenticationError < DockerError; end
|
39
|
+
|
40
|
+
# Raised when an IO action fails.
|
41
|
+
class IOError < DockerError; end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Algo
|
2
|
+
module Docker
|
3
|
+
class Network < Base
|
4
|
+
DEFAULT_NETWORKS = %w(ingress none host bridge docker_gwbridge)
|
5
|
+
|
6
|
+
def inspect
|
7
|
+
"<Algo::Docker::Network name=#{info['Name']} scope=#{info['Scope']}>"
|
8
|
+
end
|
9
|
+
|
10
|
+
def info
|
11
|
+
@info = self.class.find(@info["Id"]).info unless @info["Name"]
|
12
|
+
@info
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
@info
|
17
|
+
end
|
18
|
+
|
19
|
+
def remove
|
20
|
+
self.class.remove @info['Id']
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.find(id, conn=Docker.connection)
|
24
|
+
new(conn, conn.get("/networks/#{id}"))
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.remove(id_or_name, conn=Docker.connection)
|
28
|
+
conn.delete("/networks/#{id_or_name}")
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.create(init_spec, conn=Docker.connection)
|
32
|
+
new(conn, conn.post("/networks/create", nil, body: JSON.generate(init_spec)))
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.all(skip_default=false, conn=Docker.connection)
|
36
|
+
hashes = conn.get('/networks')
|
37
|
+
hashes.select { |h| !skip_default || !h['Name'].in?(DEFAULT_NETWORKS) }
|
38
|
+
.map{ |h| new(conn, h) }
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Algo
|
2
|
+
|
3
|
+
module Docker
|
4
|
+
|
5
|
+
class Service < Base
|
6
|
+
|
7
|
+
Spec = Struct.new('Spec', :name, :task_template, :mode, :update_config,
|
8
|
+
:networks, :endpoint_spec)
|
9
|
+
|
10
|
+
attr_reader :spec
|
11
|
+
|
12
|
+
def initialize conn, hash
|
13
|
+
super(conn, hash)
|
14
|
+
@spec = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def info
|
18
|
+
@info = Docker::Service.find(@info["Id"]).info if @info["Spec"].blank?
|
19
|
+
@info
|
20
|
+
end
|
21
|
+
|
22
|
+
def spec
|
23
|
+
if @spec.blank?
|
24
|
+
@spec = Spec.new(
|
25
|
+
info["Spec"]["Name"],
|
26
|
+
info["Spec"]["TaskTemplate"],
|
27
|
+
info["Spec"]["Mode"],
|
28
|
+
info["Spec"]["UpdateConfig"],
|
29
|
+
info["Spec"]["Networks"].tap { |network|
|
30
|
+
unless network.blank?
|
31
|
+
break network.map { |net| Network.new(@connection, {'Id' => net['Target']}) }
|
32
|
+
end
|
33
|
+
},
|
34
|
+
info["Spec"]["EndpointSpec"]
|
35
|
+
)
|
36
|
+
end
|
37
|
+
@spec
|
38
|
+
end
|
39
|
+
|
40
|
+
def raw_spec
|
41
|
+
info["Spec"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
"<Algo::Docker::Service name=#{spec.name}>"
|
46
|
+
end
|
47
|
+
|
48
|
+
def remove
|
49
|
+
self.class.remove info['Id']
|
50
|
+
end
|
51
|
+
|
52
|
+
def update next_spec
|
53
|
+
self.class.update info['Id'], info['Version']['Index'], next_spec
|
54
|
+
@info = self.class.find(@info["Id"]).info
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.find(id_or_name, conn=Docker.connection)
|
58
|
+
new(conn, conn.get("/services/#{id_or_name}"))
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.create(init_spec, conn=Docker.connection)
|
62
|
+
new(conn, conn.post('/services/create', nil, body: JSON.generate(init_spec)))
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.remove(id_or_name, conn=Docker.connection)
|
66
|
+
conn.delete("/services/#{id_or_name}")
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.update(id_or_name, version, next_spec, conn=Docker.connection)
|
70
|
+
conn.post("/services/#{id_or_name}/update",
|
71
|
+
{ version: version },
|
72
|
+
body: JSON.generate(next_spec))
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.all(conn=Docker.connection)
|
76
|
+
hashes = conn.get('/services')
|
77
|
+
hashes.map{ |h| new(conn, h) }
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
data/lib/algo/docker.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module Algo
|
2
|
+
module Docker
|
3
|
+
require 'algo/docker/version'
|
4
|
+
require 'algo/docker/error'
|
5
|
+
require 'algo/docker/base'
|
6
|
+
require 'algo/docker/connection'
|
7
|
+
|
8
|
+
# Entities
|
9
|
+
require 'algo/docker/network'
|
10
|
+
require 'algo/docker/service'
|
11
|
+
|
12
|
+
def connection
|
13
|
+
@connection ||= Connection.new(url, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def url
|
17
|
+
@url || env_url
|
18
|
+
end
|
19
|
+
|
20
|
+
def url=(new_url)
|
21
|
+
@url = new_url
|
22
|
+
end
|
23
|
+
|
24
|
+
def options
|
25
|
+
@options || env_options
|
26
|
+
end
|
27
|
+
|
28
|
+
def options=(new_options)
|
29
|
+
@options = env_options.merge(new_options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def env_url
|
33
|
+
ENV['DOCKER_URL'] || ENV['DOCKER_HOST']
|
34
|
+
end
|
35
|
+
|
36
|
+
def env_options
|
37
|
+
if cert_path = ENV['DOCKER_CERT_PATH']
|
38
|
+
{
|
39
|
+
client_cert: File.join(cert_path, 'cert.pem'),
|
40
|
+
client_key: File.join(cert_path, 'key.pem'),
|
41
|
+
ssl_ca_file: File.join(cert_path, 'ca.pem'),
|
42
|
+
scheme: 'https'
|
43
|
+
}.merge(ssl_options)
|
44
|
+
else
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def ssl_options
|
50
|
+
if ENV['DOCKER_SSL_VERIFY'] == 'false'
|
51
|
+
{
|
52
|
+
ssl_verify_peer: false
|
53
|
+
}
|
54
|
+
else
|
55
|
+
{}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module_function :env_options, :url, :url=, :options, :options=, :env_url, :ssl_options,
|
60
|
+
:connection
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Algo
|
2
|
+
class Dsl
|
3
|
+
module Cluster
|
4
|
+
|
5
|
+
class Context
|
6
|
+
include Dsl::Network
|
7
|
+
include Dsl::Service
|
8
|
+
|
9
|
+
attr_reader :context
|
10
|
+
|
11
|
+
def initialize name
|
12
|
+
@context = {
|
13
|
+
"services" => [],
|
14
|
+
"networks" => [],
|
15
|
+
"env" => [],
|
16
|
+
"labels" => {},
|
17
|
+
'name' => name,
|
18
|
+
'prefix' => name
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Assign cluster-wide used prefix.
|
23
|
+
# @param [String] pref_name
|
24
|
+
def prefix pref_name
|
25
|
+
@context['prefix'] = pref_name
|
26
|
+
end
|
27
|
+
|
28
|
+
def env key, val
|
29
|
+
@context['env'] << "#{key}=#{val}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def label key, val
|
33
|
+
@context['labels'][key] = val
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def cluster name, &block
|
39
|
+
ctx = Cluster::Context.new(name).tap do |ctx|
|
40
|
+
ctx.instance_eval(&block)
|
41
|
+
end
|
42
|
+
@clusters << ctx.context
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Algo
|
2
|
+
class Dsl
|
3
|
+
module Network
|
4
|
+
|
5
|
+
class Context
|
6
|
+
|
7
|
+
attr_reader :context
|
8
|
+
|
9
|
+
def initialize name, cluster
|
10
|
+
@cluster = cluster
|
11
|
+
@context = {
|
12
|
+
'Name' => "#{cluster_prefix}#{name}",
|
13
|
+
'Driver' => 'overlay',
|
14
|
+
'CheckDuplicate' => true,
|
15
|
+
'EnableIPv6' => false,
|
16
|
+
'IPAM' => {
|
17
|
+
'Config' => [],
|
18
|
+
'Driver' => 'default',
|
19
|
+
'Options' => {}
|
20
|
+
},
|
21
|
+
'Internal' => false,
|
22
|
+
'Labels' => cluster['labels'],
|
23
|
+
'Options' => {}
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def internal
|
28
|
+
@context['Internal'] = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def label key, val
|
32
|
+
@context['Labels'][key] = val
|
33
|
+
end
|
34
|
+
|
35
|
+
def ipv6
|
36
|
+
@context['EnableIPv6'] = true
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def cluster_prefix
|
42
|
+
"#{@cluster['prefix']}-" if @cluster['prefix']
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def network name, &block
|
48
|
+
raise 'should be called in cluster' unless @context
|
49
|
+
ctx = Network::Context.new(name, @context).tap do |ctx|
|
50
|
+
ctx.instance_eval(&block) if block_given?
|
51
|
+
end
|
52
|
+
@context['networks'] << ctx.context
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Algo
|
2
|
+
class Dsl
|
3
|
+
module Service
|
4
|
+
|
5
|
+
class Context
|
6
|
+
attr_reader :context
|
7
|
+
|
8
|
+
def initialize name, cluster
|
9
|
+
@cluster = cluster
|
10
|
+
@context = {
|
11
|
+
'Name' => "#{cluster_prefix}#{name}",
|
12
|
+
'TaskTemplate' => {
|
13
|
+
'ContainerSpec' => {
|
14
|
+
'Image' => nil
|
15
|
+
}
|
16
|
+
},
|
17
|
+
'Mode' => {
|
18
|
+
'Replicated' => {
|
19
|
+
'Replicas' => 1
|
20
|
+
}
|
21
|
+
},
|
22
|
+
'Labels' => @cluster['labels']
|
23
|
+
}
|
24
|
+
@context['TaskTemplate']['ContainerSpec']['Env'] = @cluster['env'] if @cluster['env'].present?
|
25
|
+
end
|
26
|
+
|
27
|
+
# ContainerSpec
|
28
|
+
|
29
|
+
def image image_name
|
30
|
+
@context['TaskTemplate']['ContainerSpec']['Image'] = image_name
|
31
|
+
end
|
32
|
+
|
33
|
+
def command *item
|
34
|
+
@context['TaskTemplate']['ContainerSpec']['Command'] = item
|
35
|
+
end
|
36
|
+
|
37
|
+
def args *items
|
38
|
+
@context['TaskTemplate']['ContainerSpec']['Args'] = items
|
39
|
+
end
|
40
|
+
|
41
|
+
def env key, val
|
42
|
+
@context['TaskTemplate']['ContainerSpec']['Env'] ||= []
|
43
|
+
@context['TaskTemplate']['ContainerSpec']['Env'] << "#{key}=#{val}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param [String] period period string like 30s, 1m, 4h
|
47
|
+
def stop_grace_period period
|
48
|
+
if period.end_with?('s')
|
49
|
+
period = period.chomp('s').to_i
|
50
|
+
elsif period.end_with?('m')
|
51
|
+
period = period.chomp('m').to_i * 60
|
52
|
+
elsif period.end_with?('h')
|
53
|
+
period = period.chomp('m').to_i * 60 * 60
|
54
|
+
else
|
55
|
+
raise
|
56
|
+
end
|
57
|
+
@context['TaskTemplate']['ContainerSpec']['StopGracePeriod'] = period * 1000000000
|
58
|
+
end
|
59
|
+
|
60
|
+
# Label
|
61
|
+
|
62
|
+
def label key, val
|
63
|
+
@context['Labels'] ||= {}
|
64
|
+
@context['Labels'][key] = val
|
65
|
+
end
|
66
|
+
|
67
|
+
# Mode
|
68
|
+
|
69
|
+
def replicas replica_size
|
70
|
+
@context['Mode']['Replicated']['Replicas']= replica_size
|
71
|
+
end
|
72
|
+
|
73
|
+
# UpdateConfig
|
74
|
+
|
75
|
+
def update_parallelism n
|
76
|
+
@context['UpdateConfig'] ||= {}
|
77
|
+
@context['UpdateConfig']['Parallelism']= n
|
78
|
+
end
|
79
|
+
|
80
|
+
def update_delay n
|
81
|
+
@context['UpdateConfig'] ||= {}
|
82
|
+
@context['UpdateConfig']['Delay']= n
|
83
|
+
end
|
84
|
+
|
85
|
+
# Networks
|
86
|
+
|
87
|
+
def network name
|
88
|
+
@context['Networks'] ||= []
|
89
|
+
@context['Networks'] << { 'Target' => "#{cluster_prefix}#{name}" }
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def cluster_prefix
|
95
|
+
"#{@cluster['prefix']}-" if @cluster['prefix']
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
def service name, &block
|
101
|
+
raise 'should be called in cluster' unless @context
|
102
|
+
ctx = Service::Context.new(name, @context).tap do |ctx|
|
103
|
+
ctx.instance_eval(&block)
|
104
|
+
end
|
105
|
+
@context['services'] << ctx.context
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/algo/dsl.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Algo
|
2
|
+
class Dsl
|
3
|
+
require 'algo/dsl/service'
|
4
|
+
require 'algo/dsl/network'
|
5
|
+
require 'algo/dsl/cluster'
|
6
|
+
|
7
|
+
include Dsl::Cluster
|
8
|
+
|
9
|
+
attr_reader :options
|
10
|
+
|
11
|
+
CLUSTER_DEFAULT = {}
|
12
|
+
|
13
|
+
def result
|
14
|
+
@clusters
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.load(options, path = nil)
|
18
|
+
dsl = new(options).tap do |dsl|
|
19
|
+
dsl._load_from(path)
|
20
|
+
end
|
21
|
+
dsl.result
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(options)
|
25
|
+
@options = CLUSTER_DEFAULT.dup
|
26
|
+
@options.merge!(options)
|
27
|
+
@clusters = []
|
28
|
+
end
|
29
|
+
|
30
|
+
def _load_from(path)
|
31
|
+
instance_eval(File.read(path), path) if path
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/algo/version.rb
ADDED
data/lib/algo.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'excon'
|
2
|
+
require 'thor'
|
3
|
+
require 'json'
|
4
|
+
require 'active_support'
|
5
|
+
require 'active_support/core_ext'
|
6
|
+
|
7
|
+
module Algo
|
8
|
+
require "algo/version"
|
9
|
+
require "algo/docker"
|
10
|
+
require "algo/dsl"
|
11
|
+
require "algo/cli"
|
12
|
+
# Your code goes here...
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: algo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- yoshiso
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: excon
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.51.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.51.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: thor
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.12'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.12'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.10'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.10'
|
111
|
+
description: Docker container orchestration tool for swarm cluster.
|
112
|
+
email:
|
113
|
+
- nya060@gmail.com
|
114
|
+
executables:
|
115
|
+
- algo
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- ".gitignore"
|
120
|
+
- ".rspec"
|
121
|
+
- ".travis.yml"
|
122
|
+
- Gemfile
|
123
|
+
- README.md
|
124
|
+
- Rakefile
|
125
|
+
- algo.gemspec
|
126
|
+
- bin/console
|
127
|
+
- bin/setup
|
128
|
+
- examples/awesome-cluster.rb
|
129
|
+
- exe/algo
|
130
|
+
- lib/algo.rb
|
131
|
+
- lib/algo/cli.rb
|
132
|
+
- lib/algo/docker.rb
|
133
|
+
- lib/algo/docker/base.rb
|
134
|
+
- lib/algo/docker/connection.rb
|
135
|
+
- lib/algo/docker/error.rb
|
136
|
+
- lib/algo/docker/network.rb
|
137
|
+
- lib/algo/docker/service.rb
|
138
|
+
- lib/algo/docker/version.rb
|
139
|
+
- lib/algo/dsl.rb
|
140
|
+
- lib/algo/dsl/cluster.rb
|
141
|
+
- lib/algo/dsl/network.rb
|
142
|
+
- lib/algo/dsl/service.rb
|
143
|
+
- lib/algo/version.rb
|
144
|
+
homepage: https://github.com/yoshiso/algo
|
145
|
+
licenses:
|
146
|
+
- MIT
|
147
|
+
metadata: {}
|
148
|
+
post_install_message:
|
149
|
+
rdoc_options: []
|
150
|
+
require_paths:
|
151
|
+
- lib
|
152
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
requirements: []
|
163
|
+
rubyforge_project:
|
164
|
+
rubygems_version: 2.5.1
|
165
|
+
signing_key:
|
166
|
+
specification_version: 4
|
167
|
+
summary: Docker container orchestration tool for swarm cluster.
|
168
|
+
test_files: []
|