dockit 1.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e774509e3e1c4afab5a44bce1fc3eee91d7d25cf
4
+ data.tar.gz: daeda4ab4b09f3f9bc7b706fefb77271930ab4fb
5
+ SHA512:
6
+ metadata.gz: 1d80feb61549460b39bdaa99adc5617ba09bbd75aed6e9bbe63cd20b682215d178a792682a2d202638030251595dbaf5152405b11469523b1db6f0ab3df6e7e2
7
+ data.tar.gz: 0867cb20ca447aec30f45524d2af9962c8639021a1282e45b7ac94bbc5ed4f1f3fd915aded2498ae38849f96c330e7049fb95066124f98c96c9d3a2503d663cf
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,17 @@
1
+ FROM cybercode/alpine-ruby:2.2
2
+ ENV DOCKER_VERSION=1.8.2 BIN=/usr/local/bin/docker
3
+
4
+ RUN apk add -u curl ruby-json && curl -sSL -o $BIN \
5
+ https://get.docker.com/builds/Linux/x86_64/docker-$DOCKER_VERSION \
6
+ && chmod +x $BIN && apk del curl
7
+
8
+ WORKDIR /app
9
+ COPY . .
10
+ RUN apk --update add --virtual build_deps \
11
+ build-base ruby-dev libc-dev linux-headers git \
12
+ && gem build dockit.gemspec \
13
+ && gem install --no-rdoc --no-ri dockit*.gem \
14
+ && apk del build_deps && rm -rf /app
15
+
16
+ WORKDIR /
17
+ ENTRYPOINT ["dockit"]
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dockit.gemspec
4
+ gemspec
@@ -0,0 +1,124 @@
1
+ [docker-api]: https://github.com/swipely/docker-api
2
+
3
+ # Dockit
4
+
5
+ `Dockit` is an alternative composer for docker projects. Its (IMHO) advantage is that it is scriptable, and rather than a single yaml configuration file, each service has it's own configuration file (`Dockit.yaml`), as well as an optional `Dockit.rb` which can provide scriptable configuration (as `Thor` subcommands) for any phase of the build and deploy process.
6
+
7
+ `Dockit` is built on the [Thor](https://github.com/erikhuda/thor) cli and the [docker-api] libraries.
8
+
9
+ ## Installation
10
+
11
+ ```sh
12
+ $ gem install dockit
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ 1. Create a top level deployment directory
18
+ 2. Create a sub-directory for each service
19
+ 3. Create a `Dockit.yaml` for each service (and optionally a `Docit.rb`.)
20
+ 4. Optionally, create a top level `Dockit.rb` subcommand file to orchestrate the build and deployment of the services.
21
+ 5. Run `dockit` in the root directory for help.
22
+
23
+ ## `Dockit.yaml`
24
+
25
+ The sections of the config file map directly to the argument sent by the
26
+ [docker-api] to the corresponding api endpoints (see [docker api](http://docs.docker.com/reference/api/docker_remote_api_v1.9/).)
27
+
28
+ The top level sections are:
29
+
30
+ - `build`
31
+ - `create`
32
+ - `run`
33
+
34
+ At least one of the sections `build` or `create` are required. If their is no `build` section, the `create` section must specify an `Image` value. Note that most (all?) of the values specified in the `run` section can be specified in the `create: HostConfig:` instead.
35
+
36
+ ### Examples
37
+
38
+ #### Simple build
39
+
40
+ ```yaml
41
+ build:
42
+ t: my-image
43
+ ```
44
+
45
+ Executing `dockit build` in the directory containing the file above, will create an image from the `Dockerfile` in the same directory named my-image.
46
+
47
+ Then executing `dockit start` will create and run a container named `my-image`
48
+
49
+ [docker hub]: https://registry.hub.docker.com/search?q=library
50
+
51
+ #### Pre-generated (or [docker hub]) image
52
+ ```yaml
53
+ create:
54
+ Image: postgres
55
+ name: db
56
+ ```
57
+
58
+ Executing `dockit build` will do nothing. Executing `dockit start` will start run a container named `db` from the local (or [docker hub] postgresql image.
59
+
60
+ #### Using locals and environment variables
61
+
62
+ The yaml file is first processed by the `ERB` template library. The "bindings" passed to the template processor can be specified on the command line with the `--locals` (alias `-l`) option. Also, the command line option `--env` (alias `-e`) is passed as `-<env>` For example, given:
63
+
64
+ ```yaml
65
+ create:
66
+ Image: postgres
67
+ name: db<%= env %>
68
+ Env:
69
+ - MYVAR=<%= myval %>
70
+ ```
71
+
72
+ - `dockit start` will generate an error (myval not defined)
73
+ - `dockit start -l myval:foo` will start a container named `db` with the environment variable `MVAR` set to `foo`.
74
+ - `dockt start -l myval:foo -e test` will start a container named `db-test`
75
+
76
+ ## `Dockit.rb`
77
+
78
+ The `dockit.rb` file can be used to add new subcommands to the cli on a project-wide or per-service basis. For per-service subcommands, the defined class name should be the "classified" directory name, for the project-wide, it should be named `All`. If the class inherits from `SubCommand` instead of `Thor`, it will inherit two useful methods:
79
+
80
+ - `invoke_default(service)` will run the same-named (or specified) dockit command on the specified service.
81
+ - `invoke_service(service)` will run the same-named (or specifed) subcommand from the `Dockit.rb` for the specified service.
82
+ - `invoke_git(service, gem=false)` will run the same-name (or specifed) subcommand from the `Dockit.rb` for the specified service after checking out the git repository for the service as a gzip archive to `repos.tar.tz`. By default it used the `master` branch. If `gem` is true, it will checkout the gemfiles as a separate archive (`gemfiles.tar.gz`). Note that the `repos` key must be set in the services yaml file.
83
+
84
+ For example:
85
+
86
+ ```ruby
87
+ class All < SubCommand
88
+ desc 'build', 'build all images'
89
+ def build
90
+ invoke_service 'app'
91
+ invoke_default 'db'
92
+ end
93
+ end
94
+ ```
95
+
96
+ Would run the `build` method from the file `app/Dockit.rb` and then create a second image using the options from `db/Dockit.yaml`.
97
+
98
+ ## DigitalOcean integration
99
+
100
+ If the `droplet_kit` gem is found at runtime, the `do` service be implemented. see `dockit help do` for available commands.
101
+
102
+ ## Running as a docker image
103
+
104
+ If you don't want to install ruby, etc., the docker image is also available rom docker hub as `cybercode/dockit`. You can create your own version of the docker image from the source code with `docker build -t dockit .` (or `rake docker`). The `Dockerfile` has `dockit` as the entrypoint, so you can pass dockit arguments on the command-line. e.g.:
105
+
106
+ ```sh
107
+ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v $PWD:/app dockit help
108
+ ```
109
+
110
+ ## The Github boilerplate
111
+
112
+ ### Development
113
+
114
+ 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.
115
+
116
+ 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).
117
+
118
+ ### Contributing
119
+
120
+ 1. Fork it ( https://github.com/cybercode/dockit/fork )
121
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
122
+ 3. Commit your changes (`git commit -am [comment]`)
123
+ 4. Push to the branch (`git push origin my-new-feature`)
124
+ 5. Create a new Pull Request
@@ -0,0 +1,28 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+ require 'rake/version_task'
4
+ require 'rdoc/task'
5
+
6
+ Rake::VersionTask.new
7
+
8
+ desc 'Run all specs'
9
+ RSpec::Core::RakeTask.new(:spec)
10
+
11
+ task default: :spec
12
+ task test: :spec
13
+
14
+ Rake::RDocTask.new do |rdoc|
15
+ rdoc.rdoc_dir = 'doc'
16
+ rdoc.main = 'README'
17
+ rdoc.title = " dockit #{Version.current}"
18
+ rdoc.rdoc_files.include('README*')
19
+ rdoc.rdoc_files.include('lib/**/*.rb')
20
+ end
21
+
22
+ desc "Generate Documentation"
23
+ task doc: :rdoc
24
+
25
+ desc "Build docker image"
26
+ task :docker do
27
+ system 'docker build -t dockit .'
28
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dockit"
5
+
6
+ require "pry"
7
+ Pry.start
8
+
9
+ #require "irb"
10
+ #IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # Allow running w/o deploying
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'dockit/cli'
7
+
8
+ Default.start(ARGV)
@@ -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,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "dockit"
7
+ s.version = File.read("VERSION").chomp # file managed by version gem...
8
+ s.authors = ["Rick Frankel"]
9
+ s.email = ["dockitk@rickster.com"]
10
+
11
+ s.summary = %q{A configuration manager and builder for docker projects.}
12
+ s.description = %q{Dockit is a builder for complex docker projects. Think scriptable docker-composer.}
13
+ s.homepage = "https://github.com/cybercode/dockit"
14
+ s.licenses = ["MIT"]
15
+
16
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ s.test_files = `git ls-files -z -- spec/*`.split("\x0")
18
+ s.bindir = "bin"
19
+ s.executables = %w[dockit]
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_development_dependency "bundler" , "~> 1.9"
23
+ s.add_development_dependency "rake" , "~> 10.4"
24
+ s.add_development_dependency "rspec" , "~> 3.2"
25
+ s.add_development_dependency "simplecov" , "~> 0.10"
26
+ s.add_development_dependency "pry" , "~> 0.10"
27
+
28
+ s.add_dependency "thor", "~> 0.19"
29
+ s.add_dependency "dotenv", "~> 2.0"
30
+ s.add_dependency "docker-api", "~> 1.21"
31
+ s.add_dependency "version", "~> 1.0"
32
+ end
@@ -0,0 +1,84 @@
1
+ require 'docker'
2
+ require 'yaml'
3
+ require 'pathname'
4
+ require 'version'
5
+
6
+ require 'dockit/config'
7
+ require 'dockit/service'
8
+ require 'dockit/image'
9
+ require 'dockit/container'
10
+
11
+ module Dockit
12
+ is_versioned
13
+
14
+ class Log
15
+ def debug(msg)
16
+ $stderr.puts "DEBUG: " + msg.join(' ')
17
+ end
18
+ end
19
+
20
+ # This class encapsulates the environment used in the Dockit cli.
21
+ # The class has three main attributes:
22
+ #
23
+ # root ::
24
+ # The (cached) root of the project.
25
+ # modules ::
26
+ # The "modules", a map of +Dockit.rb+ files by directory name
27
+ # services ::
28
+ # The "services", a map of +Dockit.yaml+ files, by directory name
29
+ class Env
30
+ BASENAME = 'Dockit'.freeze
31
+ attr_reader :services
32
+ attr_reader :modules
33
+
34
+ ##
35
+ # Initialize services and modules in the project.
36
+ # depth [Integer] :: How deep to recurse looking for modules/services
37
+ # debug [Boolean] :: Log +docker-api+ calls.
38
+ def initialize(depth: 2, debug: false)
39
+ @root = nil
40
+ @modules = find_subcommands(depth)
41
+ @services = find_services(depth)
42
+
43
+ Docker.logger = Dockit::Log.new if debug
44
+ end
45
+
46
+ ##
47
+ # The (cached) root of the project
48
+ # Returns [String] :: The absolute path of the project root.
49
+ def root
50
+ return @root if @root
51
+ @root = dir = Dir.pwd
52
+ begin
53
+ dir = File.dirname(dir)
54
+ return @root = dir if File.exist?(File.join(dir, "#{BASENAME}.rb"))
55
+ end while dir != '/'
56
+
57
+ @root
58
+ end
59
+
60
+ private
61
+ def find_services(depth)
62
+ find_relative(depth, 'yaml')
63
+ end
64
+
65
+ def find_subcommands(depth)
66
+ find_relative(depth, 'rb')
67
+ end
68
+
69
+ def find_relative(depth, ext)
70
+ result = {}
71
+ (0..depth).each do |i|
72
+ pat = File.join(root, ['*'] * i, "#{BASENAME}.#{ext}")
73
+ Pathname.glob(pat).inject(result) do |memo, path|
74
+ name = path.dirname.relative_path_from(Pathname.new(root)).to_s
75
+ name = 'all' if name == '.'
76
+ memo[name] = path.to_s
77
+
78
+ memo
79
+ end
80
+ end
81
+ result
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,249 @@
1
+ require 'io/console'
2
+ require 'thor'
3
+ require 'dockit'
4
+
5
+ class SubCommand < Thor
6
+ no_commands do
7
+ # invoke command against the Dockit.yaml for the given service.
8
+ def invoke_default(service=nil, cmd: nil, opts: {})
9
+ service ||= self.class.to_s.downcase
10
+ cmd ||= current_command_chain[-1]
11
+ cmd = "default:#{cmd}"
12
+ invoke cmd, [service], options.merge(opts)
13
+ instance_variable_get('@_invocations')[Default].slice!(-1)
14
+ end
15
+
16
+ # invoke the method in the Dockit.rb for the given service.
17
+ def invoke_service(service, cmd: nil, opts: {})
18
+ cmd ||= current_command_chain[-1]
19
+ cmd = "#{service}:#{cmd}"
20
+
21
+ say "Invoking #{cmd}"
22
+ invoke cmd, [], options.merge(opts)
23
+ instance_variable_get('@_invocations')[Default].slice!(-1)
24
+ end
25
+
26
+ # export git repository before running default command
27
+ def invoke_git(service, gem=false)
28
+ invoke_default service, cmd: 'git-build', opts: { gem: gem || options.gem }
29
+ end
30
+ end
31
+ end
32
+
33
+ class Default < Thor
34
+ DOCKIT_FILE = './Dockit.yaml'.freeze
35
+ @@root_echoed = false
36
+
37
+ class_option :host, type: :string, desc: 'override DOCKER_HOST env variable',
38
+ default: ENV['DOCKER_HOST'], aliases: ['H']
39
+ class_option :debug, type: :boolean, desc: "Log docker-api calls"
40
+ class_option :verbose, type: :boolean, aliases: ['v']
41
+ class_option :env, type: :string, desc: 'e.g., "test", "staging"', aliases: ['e']
42
+ class_option :locals, type: :hash, aliases: ['l'],
43
+ banner: "key:value [key:value ...]",
44
+ desc: "variables to pass to yaml file."
45
+
46
+ def initialize(*args)
47
+ super
48
+ ENV['DOCKER_HOST'] = options.host
49
+ unless @@root_echoed
50
+ puts "Project root: #{dockit.root}"
51
+ @@root_echoed=true
52
+ end
53
+ end
54
+
55
+ no_commands do
56
+ def _list(what)
57
+ puts what.to_s.capitalize, dockit.send(what).keys.collect {|s| " #{s}"}
58
+ end
59
+ end
60
+
61
+ def help(*args)
62
+ super
63
+ if args.count < 1
64
+ say "Run 'dockit list' to see the complete list of SERVICEs."
65
+ say "Run 'dockit help COMMAND' to see command specific options."
66
+ end
67
+ end
68
+
69
+ desc "version", "Print version"
70
+ def version
71
+ say "Dockit version #{Dockit::VERSION}"
72
+ end
73
+ map %w[--version -V] => :version
74
+
75
+ desc "list", "List available services"
76
+ def list
77
+ _list :modules
78
+ _list :services
79
+ end
80
+
81
+ # "run" is a reserved word in thor...
82
+ desc 'start [SERVICE] [CMD]', 'run a service, optionally override "Cmd"'
83
+ option :transient, type: :boolean, desc: 'remove container after run'
84
+ def start(service=nil, *cmd)
85
+ opts = cmd.length > 0 ? options.merge(create: {Cmd: cmd}) : options
86
+ exec(service) do |s|
87
+ s.start(opts)
88
+ end
89
+ end
90
+
91
+ desc 'sh [SERVICE] [CMD]', 'run an interactive command (default "bash -l")'
92
+ def sh(service=nil, *cmd)
93
+ exec(service) do |s|
94
+ cmd = %w[bash -l] if cmd.length < 1
95
+
96
+ # in case image has an entrypoint, use the cmd as the entrypoint
97
+ (entrypoint, *cmd) = cmd
98
+ s.start(
99
+ transient: true,
100
+ verbose: options.verbose,
101
+ create: {
102
+ Entrypoint: [entrypoint],
103
+ Cmd: cmd,
104
+ name: 'sh',
105
+ Tty: true,
106
+ AttachStdin: true,
107
+ AttachStdout: true,
108
+ AttachStderr: true,
109
+ OpenStdin: true,
110
+ StdinOnce: true,
111
+ })
112
+ end
113
+ end
114
+
115
+ desc "cleanup", "Remove unused containers and images"
116
+ option :images , type: :boolean, default: true, desc: "remove danging images"
117
+ option :containers , type: :boolean, default: true, desc: "remove exited containers"
118
+ option :force, type: :boolean, default: false, desc: "stop and remove all"
119
+ def cleanup
120
+ if options[:containers]
121
+ Dockit::Container.clean(force: options[:force]) if ask_force('containers')
122
+ end
123
+ if options[:images]
124
+ Dockit::Image.clean(force: options[:force]) if ask_force('images')
125
+ end
126
+ end
127
+
128
+ desc 'config [SERVICE]', 'show parsed Dockit.yaml config file'
129
+ def config(service=nil)
130
+ exec(service) do |s|
131
+ say s.config.instance_variable_get('@config').to_yaml
132
+ end
133
+ end
134
+ desc 'push REGISTRY [SERVICE]', 'push image for SERVICE to REGSITRY'
135
+ option :force, type: :boolean, desc: 'overwrite current lastest version'
136
+ option :tag, desc: 'repos tag (defaults to "latest")', aliases: ['t']
137
+ def push(registry, service=nil)
138
+ exec(service) do |s|
139
+ s.push(registry, options[:tag], options[:force])
140
+ end
141
+ end
142
+
143
+ desc 'pull REGISTRY [SERVICE]', 'pull image for SERVICE from REGSITRY'
144
+ option :force, type: :boolean, desc: 'overwrite current tagged version'
145
+ option :tag, desc: 'repos tag (defaults to "latest")', aliases: ['t']
146
+ def pull(registry, service=nil)
147
+ exec(service) do |s|
148
+ s.pull(registry, options[:tag], options[:force])
149
+ end
150
+ end
151
+
152
+ desc 'build [SERVICE]', "Build image from current directory or service name"
153
+ def build(service=nil)
154
+ exec(service) do |s|
155
+ s.build()
156
+ end
157
+ end
158
+
159
+ desc 'git-build', 'build from git (gem) repository'
160
+ option :branch, desc: '<tree-ish> git reference', default: 'master'
161
+ option :gem, type: :boolean, desc: "update Gemfiles export"
162
+ def git_build(service=nil)
163
+ exec(service) do |s|
164
+ unless repos = s.config.get(:repos)
165
+ say "'repos' not defined in config file. Exiting",:red
166
+ exit 1
167
+ end
168
+
169
+ say "Exporting in #{Dir.pwd}", :green
170
+ say "<- #{repos} #{options.branch}", :green
171
+ if options.gem
172
+ # grab the Gemfiles separately for the bundler Dockerfile hack
173
+ say "-> Gemfiles", :green
174
+ export(repos, 'gemfile.tar.gz', 'Gemfile*')
175
+ end
176
+
177
+ say "-> repos.tar.gz", :green
178
+ export(repos, 'repos.tar.gz')
179
+
180
+ s.build
181
+ end
182
+ end
183
+
184
+ private
185
+ GIT_CMD = 'git archive -o %s --format tar.gz --remote %s %s %s'.freeze
186
+ def export(repos, archive, args='')
187
+ cmd = GIT_CMD % [archive, repos, options.branch, args]
188
+ say "#{cmd}\n", :blue if options.debug
189
+
190
+ unless system(cmd)
191
+ say "Export error", :red
192
+ exit 1
193
+ end
194
+ end
195
+
196
+ def ask_force(type)
197
+ force = options[:force]
198
+ say "Removing #{force ? 'ALL' : ''} #{type}...", force ? :red : nil
199
+
200
+ return true ### DISABLED# unless force
201
+ yes? '... Are you sure?'
202
+ end
203
+
204
+ def dockit
205
+ @@dockit ||= Dockit::Env.new(debug: options[:debug])
206
+ end
207
+
208
+ def exec(service)
209
+ file = _file(service)
210
+ if file != DOCKIT_FILE
211
+ say "Processing #{service}"
212
+ # problem w/ path length for docker build, so change to local directory
213
+ Dir.chdir(File.dirname(file))
214
+ end
215
+
216
+ locals = options[:locals]||{}
217
+ env = options[:env]
218
+ locals[:env] = env ? "-#{env}" : ""
219
+ yield Dockit::Service.new(locals: locals)
220
+ end
221
+
222
+ def _file(service)
223
+ return dockit.services[service] if service && dockit.services[service]
224
+ return DOCKIT_FILE if File.exist?(DOCKIT_FILE)
225
+ say "No config file in current directory", :red
226
+ help
227
+ exit
228
+ end
229
+ end
230
+
231
+ # Add digital ocean support if droplet_kit is installed
232
+ begin
233
+ require 'dockit/digitalocean'
234
+ Default.desc 'do', 'manage docker server'
235
+ Default.subcommand 'do', DO
236
+ rescue LoadError
237
+ end
238
+
239
+ # it would be nice to do this in the initialization method of the Default
240
+ # class above, but it hasn't been instantiated yet when invoking a subcommand.
241
+ Dockit::Env.new.modules.each do |k, v|
242
+ begin
243
+ require v
244
+ Default.desc k, "#{k} submodule, see 'help #{k}'"
245
+ Default.subcommand k, Module.const_get(k.capitalize)
246
+ rescue NameError => e
247
+ STDERR.puts "Can't load #{v}: #{e}"
248
+ end
249
+ end