dockit 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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