docker-rainbow 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 +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/README.md +156 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/docker-rainbow.gemspec +24 -0
- data/lib/docker/rainbow/version.rb +5 -0
- data/lib/docker/rainbow.rb +147 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 57676b6de93c1ed4dc502c7a83656ecb8eb4082d
|
4
|
+
data.tar.gz: 0d4a027e6676fb07393ba60d895287f2c9d4146d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: acc9adc6da0606edd8145ff414b05ec0373bc39801078e62da03091e77c5e810d7e1c6c7e5f8c51bdf4dc7e8a5aeecc987e1217b12690c8c70d5da394b1eeacd
|
7
|
+
data.tar.gz: dfdc49afe47b3c96ceac9bfdaab223a968ffef7d9a351950bd3653dfc7ea568f7cbd914dd1b6d40ecc59c5c04f98af9d2ca2aa3aeb9b19b3099a3f649c33e8cc
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
# Docker::Rainbow
|
2
|
+
|
3
|
+
This gem implements a scheme for choosing terse but meaningful names of Docker
|
4
|
+
containers. The names that it chooses convey information about when and how
|
5
|
+
containers were deployed, and how they relate to one another, without resorting
|
6
|
+
to unreadable random gobbledegook for names.
|
7
|
+
|
8
|
+
(If you want random gobbledegook, you should refer to containers by their IDs,
|
9
|
+
which are globally unique. Names are there to make the operator's life easier
|
10
|
+
by giving her a way to refer to containers when she types docker CLI commands.)
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'docker-rainbow'
|
18
|
+
```
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
$ gem install docker-rainbow
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
There is no command-line interface to the rainbow. Its API is minimal and
|
31
|
+
hopefully self-documenting.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'docker/rainbow'
|
35
|
+
|
36
|
+
rainbow = Docker::Rainbow.new()
|
37
|
+
|
38
|
+
# The color will be one of "blue," "green," "purple," etc. We try to find a
|
39
|
+
# color that isn't in use, but wrap around when necessary.
|
40
|
+
puts "Launching containers for the #{rainbow.color} epoch"
|
41
|
+
|
42
|
+
# This will return an Array like so:
|
43
|
+
# ['blue_cassandra_1', 'blue_cassandra_2', 'blue_cassandra_3']
|
44
|
+
names = rainbow.name_containers('spotify/cassandra:latest', count:3)
|
45
|
+
|
46
|
+
|
47
|
+
names.each do |name|
|
48
|
+
system("docker run -n #{name} spotify/cassandra:latest")
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
### Reusing names
|
53
|
+
|
54
|
+
By default, the rainbow raises an exception if it tries to choose any name
|
55
|
+
that is already in use by any container, running _or_ exited. Because rainbow's
|
56
|
+
palette has just six colors, this will typically happen after just a few
|
57
|
+
deploys.
|
58
|
+
|
59
|
+
In most cases, by the time we wrap back around to a previous color, the original
|
60
|
+
containers will have exited long ago; it's unlikely that you will have six
|
61
|
+
"vintages" of a given service running at the same time. For this reason the
|
62
|
+
rainbow will automatically remove exited containers with conflicting names,
|
63
|
+
unless you specify `gc:false` when you build your rainbow.
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
rainbow.name_containers('spotify/cassandra:latest', gc:false)
|
67
|
+
```
|
68
|
+
|
69
|
+
However: if any container with a conflicting name is *still running*, this
|
70
|
+
counts as a conflict and the rainbow will raise an exception because in all
|
71
|
+
likelihood, something is seriously wrong with the containers deployed to this
|
72
|
+
box. (How often would you have six different revisions of software running
|
73
|
+
on one box at once time?)
|
74
|
+
|
75
|
+
If you will handle naming conflicts yourself, you can ask the rainbow to ignore
|
76
|
+
them and/or opt out of the built-in garbage collection.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
rainbow.name_containers('spotify/cassandra:latest', reuse:true, gc:false)
|
80
|
+
```
|
81
|
+
|
82
|
+
### Accounting for entrypoints
|
83
|
+
|
84
|
+
For images that expose an `ENTRYPOINT` rather than a `CMD`, you might end up
|
85
|
+
with several long-lived containers launched from the same base image but with
|
86
|
+
conceptually different purposes. You can tell the rainbow the name of the
|
87
|
+
command you will be passing to `docker run` in order to choose unambiguous
|
88
|
+
names.
|
89
|
+
|
90
|
+
Note that you don't need to use the verbatim command; you could use a nickname
|
91
|
+
for the "role" of the container.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
rainbow.name_containers('docker/swarm', cmd: 'manager')
|
95
|
+
# => ["blue_swarm_manager"]
|
96
|
+
|
97
|
+
rainbow.name_containers('docker/swarm', cmd: 'node', count:2)
|
98
|
+
# => ["blue_swarm_node_1", "blue_swarm_node_2"]
|
99
|
+
```
|
100
|
+
|
101
|
+
We **strongly encourage** you to use a terse, meaningful description of the
|
102
|
+
cmd instead of passing the entire command word-for-word into the rainbow. You
|
103
|
+
might find it super interesting that you invoked busybox as `/bin/sh -c ls foo`,
|
104
|
+
but the people who look at `docker ps` output probably don't care to see a
|
105
|
+
container named `busybox_bin-sh-c-ls-foo`!
|
106
|
+
|
107
|
+
To encourage you to choose terse names, Rainbow will extract the _first_
|
108
|
+
alphanumeric word from the cmd and use it alone as a suffix for your container
|
109
|
+
named. If this is an issue, refer to "Customizing Container Names", below.
|
110
|
+
|
111
|
+
### Multi-tenancy
|
112
|
+
|
113
|
+
You might be deploying containers to a cluster that is in use by other tenants;
|
114
|
+
in this case, you want to choose a unique prefix to indicate which tenant owns
|
115
|
+
which container. Rainbow borrows from the nomenclature used by `docker-compose`
|
116
|
+
and supports an optional *project name* for all of your containers.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
rainbow = Docker::Rainbow.new(project: 'development')
|
120
|
+
|
121
|
+
rainbow.name_containers('foo') # => ["development_blue_foo]
|
122
|
+
```
|
123
|
+
|
124
|
+
## Customizing Container Names
|
125
|
+
|
126
|
+
If you absolutely insist on having more colors, you can pass a custom
|
127
|
+
palette.
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
Docker::Rainbow.new(palette: ['mauve', 'pink', 'chartreuse', ...])
|
131
|
+
```
|
132
|
+
|
133
|
+
You can also subclass Rainbow and override its private methods if you want
|
134
|
+
to be more opinionated about naming choices.
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
class PedanticRainbow < Docker::Rainbow
|
138
|
+
# Our full command is relevant to the container's personality; include
|
139
|
+
# every word, not just the first word! Also use periods to separate
|
140
|
+
# command words, not hyphens.
|
141
|
+
private def cmd_suffix(cmd)
|
142
|
+
cmd.split(/[^A-Za-z0-9]+/).join('.')
|
143
|
+
end
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
## Development
|
148
|
+
|
149
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
150
|
+
|
151
|
+
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`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
152
|
+
|
153
|
+
## Contributing
|
154
|
+
|
155
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/docker-rainbow. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
156
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "docker/rainbow"
|
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
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'docker/rainbow/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "docker-rainbow"
|
8
|
+
spec.version = Docker::Rainbow::VERSION
|
9
|
+
spec.authors = ["Tony Spataro"]
|
10
|
+
spec.email = ["tony@rightscale.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{An Ops-friendly container naming scheme for Docker.}
|
13
|
+
spec.description = %q{Chooses terse, meaningful, unique names for containers based on base image, existing containers, and other factors.}
|
14
|
+
spec.homepage = "https://github.com/xeger/docker-rainbow"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require 'docker/rainbow/version'
|
4
|
+
|
5
|
+
module Docker
|
6
|
+
class Rainbow
|
7
|
+
class Conflict < StandardError
|
8
|
+
attr_reader :names
|
9
|
+
|
10
|
+
def initialize(problem)
|
11
|
+
if problem.is_a?(String)
|
12
|
+
@names = []
|
13
|
+
super(problem)
|
14
|
+
elsif problem.respond_to?(:each) && problem.respond_to?(:join)
|
15
|
+
@names = problem
|
16
|
+
super("Names in use: #{problem.join(', ')}")
|
17
|
+
else
|
18
|
+
super("Unknown error: #{problem.inspect}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# All the colors of the Docker rainbow, in alphabetical order. You can
|
24
|
+
# override these if you need more deployment epochs, but remember: the
|
25
|
+
# goal is meaningful container names, not names that record the history
|
26
|
+
# of the world. (That's what `docker logs` is for...)
|
27
|
+
PALETTE = ['blue', 'green', 'orange', 'red', 'yellow', 'violet'].freeze
|
28
|
+
|
29
|
+
# Execute a shell command and return its output. If the command fails,
|
30
|
+
# raise RuntimeError with details of the command that failed.
|
31
|
+
# TODO use a pretty gem to do this, and/or a Ruby docker CLI wrapper
|
32
|
+
#
|
33
|
+
# @param [String] cmd
|
34
|
+
# @return [Array] of output lines
|
35
|
+
def self.shell(cmd)
|
36
|
+
output = `#{cmd}`
|
37
|
+
raise RuntimeError, "Command failed: #{cmd}" unless $?.success?
|
38
|
+
output.split("\n")
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param [String] project a prefix that comes before the colors, default
|
42
|
+
# is none
|
43
|
+
# @param [Array] colors a list of colors to choose from, default is the
|
44
|
+
# classic six colors of the rainbow, in alphabetical order.
|
45
|
+
def initialize(project:nil, palette:PALETTE)
|
46
|
+
@project = project
|
47
|
+
@palette = palette
|
48
|
+
@color = choose_color
|
49
|
+
end
|
50
|
+
|
51
|
+
# Choose names for one or more containers that will be launched from a
|
52
|
+
# given image. Optionally specify a command, a number of containers, and
|
53
|
+
# what to do about dead or running containers with overlapping names.
|
54
|
+
#
|
55
|
+
# By default, the rainbow will automatically run `docker rm` for any exited
|
56
|
+
# container whose name is about to be reused; you can specify `gc:false`
|
57
|
+
# to cause it to raise instead.
|
58
|
+
#
|
59
|
+
# The rainbow will _not_ rm containers that are running
|
60
|
+
#
|
61
|
+
# @param [String] image a Docker image label i.e. a `repository:tag` pair
|
62
|
+
# @param [Integer] count number of containers to name
|
63
|
+
# @param [Boolean] gc if true rm dead containers with conflicting names
|
64
|
+
# @param [Boolean] reuse if true, ignore conflicts with running containers
|
65
|
+
def name_containers(image, cmd:nil, count:1, gc:true, reuse:false)
|
66
|
+
prefix = "#{@color}_#{base_name(image)}"
|
67
|
+
prefix = "#{prefix}_#{cmd_suffix(cmd)}" if cmd
|
68
|
+
|
69
|
+
result = []
|
70
|
+
if count == 1
|
71
|
+
result << prefix
|
72
|
+
else
|
73
|
+
(1..count).each do |i|
|
74
|
+
result << "#{prefix}_#{i}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
remove_dead(result) if gc
|
79
|
+
|
80
|
+
unless reuse
|
81
|
+
in_use = find_in_use(result)
|
82
|
+
raise Conflict.new(in_use) if in_use.any?
|
83
|
+
end
|
84
|
+
|
85
|
+
result
|
86
|
+
end
|
87
|
+
|
88
|
+
# Find a color that is not being used by any running container in our
|
89
|
+
# project. If all colors are in use, return the first color in the palette.
|
90
|
+
private def choose_color
|
91
|
+
all = self.class.shell("docker ps --format={{.Names}}").compact
|
92
|
+
|
93
|
+
if @project
|
94
|
+
prefix = "#{project}_"
|
95
|
+
all.reject! { |n| !n.start_with?(prefix) }
|
96
|
+
all = all.map { |cn| cn[prefix.length..-1] }
|
97
|
+
end
|
98
|
+
|
99
|
+
# Find colors that are in use
|
100
|
+
in_use = Set.new(all.map { |cn| cn.split('_')[0] }) & @palette
|
101
|
+
|
102
|
+
# Find a color that isn't in use; default to the first color if
|
103
|
+
# everything is already used
|
104
|
+
@palette.detect { |c| !in_use.include?(c) } || @palette.first
|
105
|
+
end
|
106
|
+
|
107
|
+
# Given an image tag, derive a meaningful but terse "base name" for
|
108
|
+
# containers that will be launched from that image.
|
109
|
+
private def base_name(image)
|
110
|
+
# Remove registry namespace (anything before the final slash)
|
111
|
+
# as well as tag.
|
112
|
+
if image.include?("/")
|
113
|
+
base = image.split('/').last.split(':').first
|
114
|
+
else
|
115
|
+
base = image.split(':').first
|
116
|
+
end
|
117
|
+
|
118
|
+
# Strip noise-word suffixes from image name
|
119
|
+
base.sub!(/_service$/, '')
|
120
|
+
|
121
|
+
base
|
122
|
+
end
|
123
|
+
|
124
|
+
# Transform a cmd into a valid container name fragment. Split cmd into
|
125
|
+
# words and return ONLY the first word.
|
126
|
+
# This assumes that the first word of the command is sufficient to
|
127
|
+
# uniquely identify the container's role, and that any remaining words are
|
128
|
+
# noise.
|
129
|
+
private def cmd_suffix(cmd)
|
130
|
+
words = cmd.split(/[^A-Za-z0-9]+/)
|
131
|
+
words.first
|
132
|
+
end
|
133
|
+
|
134
|
+
def remove_dead(containers)
|
135
|
+
words = ['docker', 'ps', '--filter=status=exited', '--format={{.ID}}']
|
136
|
+
containers.each { |cn| words << "--filter=name=#{cn}" }
|
137
|
+
output = self.class.shell(words.join(' '))
|
138
|
+
output.each { |dead| shell("docker rm #{dead}") }
|
139
|
+
end
|
140
|
+
|
141
|
+
def find_in_use(containers)
|
142
|
+
words = ['docker', 'ps', '--format={{.Names}}']
|
143
|
+
containers.each { |cn| words << "--filter=name=#{cn}" }
|
144
|
+
self.class.shell(words.join(' '))
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: docker-rainbow
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tony Spataro
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Chooses terse, meaningful, unique names for containers based on base
|
56
|
+
image, existing containers, and other factors.
|
57
|
+
email:
|
58
|
+
- tony@rightscale.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- ".rspec"
|
65
|
+
- ".travis.yml"
|
66
|
+
- CODE_OF_CONDUCT.md
|
67
|
+
- Gemfile
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- bin/console
|
71
|
+
- bin/setup
|
72
|
+
- docker-rainbow.gemspec
|
73
|
+
- lib/docker/rainbow.rb
|
74
|
+
- lib/docker/rainbow/version.rb
|
75
|
+
homepage: https://github.com/xeger/docker-rainbow
|
76
|
+
licenses: []
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 2.4.5
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: An Ops-friendly container naming scheme for Docker.
|
98
|
+
test_files: []
|