docker-rainbow 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ before_install: gem install bundler -v 1.10.6
@@ -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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in docker-rainbow.gemspec
4
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
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,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -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,5 @@
1
+ module Docker
2
+ class Rainbow
3
+ VERSION = "0.1.0"
4
+ end
5
+ 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: []