algo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|