dry-dock 0.1.5 → 0.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5f2e43155c83ad0b25eb7ee30a1da32697c0b270
4
- data.tar.gz: fe24886ba40a3d093989f2c03f0068026698dcdf
3
+ metadata.gz: 2627cbab1b9589543ec9a8997d95eb6071ecd17a
4
+ data.tar.gz: 6fe87f56623532901d4fb285a8cc07a584007cb0
5
5
  SHA512:
6
- metadata.gz: daf3a6b9aea72c10fb072740f97c2682df12f6378d88f768d0de43ffd16fd4f2f4bb92cb43190598e9d3e04d6c48bdd964c6edcd7ea8e8c1594e5c883a9968ba
7
- data.tar.gz: b300b19d4869daf770dd612f7524ce5a8cbf909d3812be2feb9661ee42a01f55ec639071e2f2515c4d6c6b7e4d23823321be501bc234457cb26597e8ad74dafe
6
+ metadata.gz: f4360c64a1b7d07b36ef1e6e79fc6fb65d73b14ecea226be78bc75819f0fe77b7fbe4f24b0572bfcb4d84900167f442f1709d332d6d5f71cee5b6d66660e5495
7
+ data.tar.gz: b28fa39b7acbe2e4044b7f75748d314b198f9f90b9da2098bb96552c5495da6548a9daf53080cc672984368464ac343d1124892443587cb5c879bcf975a7b16d
data/.rubocop.yml ADDED
@@ -0,0 +1,4 @@
1
+ Metrics/LineLength:
2
+ Max: 120
3
+ Style/FormatString:
4
+ EnforcedStyle: sprintf
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ source 'https://rubygems.org'
3
3
 
4
4
  group :development do
5
5
  gem 'rake', '~> 10.0'
6
+ gem 'rubocop', '~> 0.34'
6
7
  gem 'jeweler', '~> 2.0'
7
8
  gem 'pry', '~> 0.10'
8
9
 
@@ -17,5 +18,7 @@ group :test do
17
18
  gem 'fakefs', require: false
18
19
  end
19
20
 
21
+ gem 'attr_extras', '~> 4.4'
20
22
  gem 'docker-api', '~> 1.22', require: 'docker'
21
23
  gem 'excon', '~> 0.45'
24
+ gem 'memoist', '~> 0.12'
data/Gemfile.lock CHANGED
@@ -2,6 +2,10 @@ GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
4
  addressable (2.3.8)
5
+ ast (2.1.0)
6
+ astrolabe (1.3.1)
7
+ parser (~> 2.2)
8
+ attr_extras (4.4.0)
5
9
  builder (3.2.2)
6
10
  codeclimate-test-reporter (0.4.8)
7
11
  simplecov (>= 0.7.1, < 1.0.0)
@@ -39,6 +43,7 @@ GEM
39
43
  rdoc
40
44
  json (1.8.3)
41
45
  jwt (1.5.1)
46
+ memoist (0.12.0)
42
47
  method_source (0.8.2)
43
48
  mini_portile (0.6.2)
44
49
  multi_json (1.11.2)
@@ -52,11 +57,15 @@ GEM
52
57
  multi_json (~> 1.3)
53
58
  multi_xml (~> 0.5)
54
59
  rack (~> 1.2)
60
+ parser (2.2.2.6)
61
+ ast (>= 1.1, < 3.0)
62
+ powerpack (0.1.1)
55
63
  pry (0.10.1)
56
64
  coderay (~> 1.1.0)
57
65
  method_source (~> 0.8.1)
58
66
  slop (~> 3.4)
59
67
  rack (1.6.4)
68
+ rainbow (2.0.0)
60
69
  rake (10.4.2)
61
70
  rdoc (4.2.0)
62
71
  json (~> 1.4)
@@ -75,6 +84,13 @@ GEM
75
84
  diff-lcs (>= 1.2.0, < 2.0)
76
85
  rspec-support (~> 3.3.0)
77
86
  rspec-support (3.3.0)
87
+ rubocop (0.34.2)
88
+ astrolabe (~> 1.3)
89
+ parser (>= 2.2.2.5, < 3.0)
90
+ powerpack (~> 0.1)
91
+ rainbow (>= 1.99.1, < 3.0)
92
+ ruby-progressbar (~> 1.4)
93
+ ruby-progressbar (1.7.5)
78
94
  simplecov (0.10.0)
79
95
  docile (~> 1.1.0)
80
96
  json (~> 1.8)
@@ -89,15 +105,18 @@ PLATFORMS
89
105
  ruby
90
106
 
91
107
  DEPENDENCIES
108
+ attr_extras (~> 4.4)
92
109
  codeclimate-test-reporter
93
110
  docker-api (~> 1.22)
94
111
  excon (~> 0.45)
95
112
  fakefs
96
113
  jeweler (~> 2.0)
114
+ memoist (~> 0.12)
97
115
  pry (~> 0.10)
98
116
  rake (~> 10.0)
99
117
  rspec (~> 3.0)
100
118
  rspec-collection_matchers
119
+ rubocop (~> 0.34)
101
120
  simplecov (~> 0.9)
102
121
  simplecov-rcov (~> 0.2)
103
122
 
data/README.md CHANGED
@@ -3,11 +3,13 @@
3
3
  WORK IN PROGRESS — ALPHA-RELEASE SOFTWARE
4
4
  SOME FEATURES REQUIRE DOCKER 1.8.0 OR NEWER
5
5
 
6
- ![Automated Build Status](https://travis-ci.org/ripta/drydock.svg)
6
+ [![Automated Build Status](https://travis-ci.org/ripta/drydock.svg)](https://travis-ci.org/ripta/drydock)
7
+ [![Code Climate](https://codeclimate.com/github/ripta/drydock/badges/gpa.svg)](https://codeclimate.com/github/ripta/drydock)
7
8
 
8
9
  A ruby DSL to build your own docker images. Images are built based on instructions
9
10
  contained in your project's `Drydockfile`.
10
11
 
12
+
11
13
  ## Why not Dockerfile?
12
14
 
13
15
  [Dockerfiles](https://docs.docker.com/reference/builder/) are great to start out
@@ -27,6 +29,7 @@ it would be nice to import a configurably-older image, import the new Gemfile,
27
29
  and re-run the build. On the other hand, it would be important to be able to limit
28
30
  the age of the cache.
29
31
 
32
+
30
33
  ## Why Drydock?
31
34
 
32
35
  Drydock interfaces directly with the [Docker Remote API](https://docs.docker.com/reference/api/docker_remote_api/)
@@ -39,20 +42,24 @@ in your project being built by Drydock.
39
42
 
40
43
  Drydockfiles are written in ruby.
41
44
 
42
- ## Production Installation
45
+
46
+ ## Installation
43
47
 
44
48
  Either (a) `gem install dry-dock`, or (b) add "dry-dock" to your project's Gemfile,
45
- and run `bundle`. Sorry, but the gem name `drydock` was already taken by a defunct
46
- gem, and I'm too lazy to contact them; the binary and name of the project, however,
47
- are both `drydock`.
49
+ and run `bundle`.
50
+
51
+ Sorry, but the gem name `drydock` was already taken by a defunct gem, and I'm too
52
+ lazy to contact them; the binary and name of the project, however, are both `drydock`.
48
53
 
49
54
  In your project's root directory, you'll want to create a `Drydockfile` containing
50
- drydock functions. When you're ready, build an image using:
55
+ drydock functions. When you're ready, from your project's directory, build an image using:
51
56
 
52
57
  ```
53
- $ drydock
58
+ $ bundle exec drydock
54
59
  ```
55
60
 
61
+ or `drydock` directly if you're not using bundler.
62
+
56
63
  Alternatively, point drydock to a directory containing the `Drydockfile`, or to any
57
64
  file to treat it as the `Drydockfile`, e.g.:
58
65
 
@@ -61,22 +68,14 @@ $ drydock ~/source/miniproject # project directory expects a file named Drydockf
61
68
  $ drydock ~/source/miniproject/drydock-definition.rb # expects a drydock-definition.rb
62
69
  ```
63
70
 
64
- Example `Drydockfile`s may be seen in `examples/`.
65
-
66
- ## Development Installation
71
+ **Example `Drydockfile`s may be seen in the `examples/` directory of the source repo.**
67
72
 
68
- This is needed if you plan on hacking drydock:
69
-
70
- ```
71
- $ git clone git@github.com:ripta/drydock.git
72
- $ bundle
73
- ```
74
73
 
75
74
  ## Drydockfile Syntax
76
75
 
77
76
  As previously mentioned, Drydockfiles are ruby. The contents of Drydockfile are
78
- evaluated in the context of an instance of `Drydock::Project`; you can refer to
79
- the documentation for it for more in-depth information.
77
+ evaluated in the context of an instance of {Drydock::Project}; you can refer to
78
+ the documentation for it for more in-depth information on each instruction.
80
79
 
81
80
  Because Drydockfiles are ruby, most constructs should work as-is: you can declare
82
81
  constants and refer to them later; call `Kernel#abort` to exit the program and
@@ -93,7 +92,18 @@ methods of the {Drydock::Project} class or head to the
93
92
  [automatically-generated ruby docs](http://www.rubydoc.info/gems/dry-dock).
94
93
 
95
94
 
95
+ ## Contributing
96
+
97
+ If you plan on hacking or contributing to drydock, fork the project, create a new
98
+ branch, make your changes, commit, and open a pull request.
99
+
100
+ After cloning your repo, `bundle` should take care of it.
101
+
102
+
96
103
  ## Roadmap
97
104
 
98
- 1. Customizable caching subsystem.
99
- 4. Customizable caching rules.
105
+ 1. Customizable caching subsystem with pluggable caching strategies.
106
+ 2. Squashing layers together, with cache support.
107
+ 3. Unarchiving a file directly into a container.
108
+ 4. Proper `ONBUILD` implementation and expanded support for hooks.
109
+ 5. Drydock instructions corresponding to `LABEL`, `VOLUME`, `USER`, and `WORKDIR` Docker instructions.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.5
1
+ 0.1.6
data/dry-dock.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: dry-dock 0.1.5 ruby lib
5
+ # stub: dry-dock 0.1.6 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "dry-dock"
9
- s.version = "0.1.5"
9
+ s.version = "0.1.6"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Ripta Pasay"]
14
- s.date = "2015-10-07"
14
+ s.date = "2015-10-08"
15
15
  s.description = "A Dockerfile-replacement DSL for building complex images"
16
16
  s.email = "github@r8y.org"
17
17
  s.executables = ["drydock", "json-test-consumer.rb", "json-test-producer.rb", "test-tar-writer-digest.rb"]
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
24
24
  ".dockerignore",
25
25
  ".pryrc",
26
26
  ".rspec",
27
+ ".rubocop.yml",
27
28
  ".travis.yml",
28
29
  ".yardopts",
29
30
  "Dockerfile",
@@ -52,6 +53,8 @@ Gem::Specification.new do |s|
52
53
  "lib/drydock/formatters.rb",
53
54
  "lib/drydock/ignorefile_definition.rb",
54
55
  "lib/drydock/image_repository.rb",
56
+ "lib/drydock/instructions/base.rb",
57
+ "lib/drydock/instructions/copy.rb",
55
58
  "lib/drydock/logger.rb",
56
59
  "lib/drydock/object_caches/base.rb",
57
60
  "lib/drydock/object_caches/filesystem_cache.rb",
@@ -109,26 +112,35 @@ Gem::Specification.new do |s|
109
112
  s.specification_version = 4
110
113
 
111
114
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
115
+ s.add_runtime_dependency(%q<attr_extras>, ["~> 4.4"])
112
116
  s.add_runtime_dependency(%q<docker-api>, ["~> 1.22"])
113
117
  s.add_runtime_dependency(%q<excon>, ["~> 0.45"])
118
+ s.add_runtime_dependency(%q<memoist>, ["~> 0.12"])
114
119
  s.add_development_dependency(%q<rake>, ["~> 10.0"])
120
+ s.add_development_dependency(%q<rubocop>, ["~> 0.34"])
115
121
  s.add_development_dependency(%q<jeweler>, ["~> 2.0"])
116
122
  s.add_development_dependency(%q<pry>, ["~> 0.10"])
117
123
  s.add_development_dependency(%q<simplecov>, ["~> 0.9"])
118
124
  s.add_development_dependency(%q<simplecov-rcov>, ["~> 0.2"])
119
125
  else
126
+ s.add_dependency(%q<attr_extras>, ["~> 4.4"])
120
127
  s.add_dependency(%q<docker-api>, ["~> 1.22"])
121
128
  s.add_dependency(%q<excon>, ["~> 0.45"])
129
+ s.add_dependency(%q<memoist>, ["~> 0.12"])
122
130
  s.add_dependency(%q<rake>, ["~> 10.0"])
131
+ s.add_dependency(%q<rubocop>, ["~> 0.34"])
123
132
  s.add_dependency(%q<jeweler>, ["~> 2.0"])
124
133
  s.add_dependency(%q<pry>, ["~> 0.10"])
125
134
  s.add_dependency(%q<simplecov>, ["~> 0.9"])
126
135
  s.add_dependency(%q<simplecov-rcov>, ["~> 0.2"])
127
136
  end
128
137
  else
138
+ s.add_dependency(%q<attr_extras>, ["~> 4.4"])
129
139
  s.add_dependency(%q<docker-api>, ["~> 1.22"])
130
140
  s.add_dependency(%q<excon>, ["~> 0.45"])
141
+ s.add_dependency(%q<memoist>, ["~> 0.12"])
131
142
  s.add_dependency(%q<rake>, ["~> 10.0"])
143
+ s.add_dependency(%q<rubocop>, ["~> 0.34"])
132
144
  s.add_dependency(%q<jeweler>, ["~> 2.0"])
133
145
  s.add_dependency(%q<pry>, ["~> 0.10"])
134
146
  s.add_dependency(%q<simplecov>, ["~> 0.9"])
data/lib/drydock.rb CHANGED
@@ -1,7 +1,9 @@
1
1
 
2
+ require 'attr_extras/explicit'
2
3
  require 'docker'
3
4
  require 'excon'
4
5
  require 'fileutils'
6
+ require 'memoist'
5
7
 
6
8
  require_relative 'drydock/docker_api_patch'
7
9
 
@@ -26,6 +28,8 @@ require_relative 'drydock/project'
26
28
  require_relative 'drydock/stream_monitor'
27
29
  require_relative 'drydock/tar_writer'
28
30
 
31
+ require_relative 'drydock/instructions/copy'
32
+
29
33
  require_relative 'drydock/object_caches/filesystem_cache'
30
34
  require_relative 'drydock/object_caches/in_memory_cache'
31
35
  require_relative 'drydock/object_caches/no_cache'
@@ -8,17 +8,17 @@ module Docker
8
8
  log_request(request)
9
9
  resource.request(request)
10
10
  rescue Excon::Errors::BadRequest => ex
11
- raise ClientError, ex.response.body
11
+ fail ClientError, ex.response.body
12
12
  rescue Excon::Errors::Unauthorized => ex
13
- raise UnauthorizedError, ex.response.body
13
+ fail UnauthorizedError, ex.response.body
14
14
  rescue Excon::Errors::NotFound => ex
15
- raise NotFoundError, ex.response.body
15
+ fail NotFoundError, ex.response.body
16
16
  rescue Excon::Errors::Conflict => ex
17
- raise ConflictError, ex.response.body
17
+ fail ConflictError, ex.response.body
18
18
  rescue Excon::Errors::InternalServerError => ex
19
- raise ServerError, ex.response.body
19
+ fail ServerError, ex.response.body
20
20
  rescue Excon::Errors::Timeout => ex
21
- raise TimeoutError, ex.message
21
+ fail TimeoutError, ex.message
22
22
  end
23
23
 
24
24
  end
@@ -1,13 +1,29 @@
1
1
 
2
+ # Drydock is a command line program that provides a DSL for you to create your
3
+ # own build pipeline for your docker images. See {file:README.md} for more
4
+ # information and background on the design.
2
5
  module Drydock
3
6
 
7
+ # The application's banner.
8
+ #
9
+ # @return [String] the banner
4
10
  def self.banner
5
11
  "Drydock v#{Drydock.version}"
6
12
  end
7
13
 
8
- def self.build(opts = {}, &blk)
9
- Project.new(opts).tap do |project|
10
- dryfile, dryfilename = yield
14
+ # Create a new project, then run and finalize the build.
15
+ #
16
+ # @param (see Project#initialize)
17
+ # @option (see Project#initialize)
18
+ # @yield [project] A block that describes the logic on how to search for a
19
+ # Drydockfile.
20
+ # @yieldparam project [Project] A newly-instantiated project object.
21
+ # @yieldreturn [Array<String>] An array of exactly two elements: the contents
22
+ # of the Drydockfile, and the path to the Drydockfile. The directory of
23
+ # the path will be made as the working directory.
24
+ def self.build(build_opts = {}, &blk)
25
+ Project.new(build_opts).tap do |project|
26
+ dryfile, dryfilename = yield project
11
27
 
12
28
  Dir.chdir(File.dirname(dryfilename))
13
29
  Drydock.logger.info("Working directory set to #{Dir.pwd}")
@@ -47,10 +63,6 @@ module Drydock
47
63
  @logger = logger
48
64
  end
49
65
 
50
- def self.using(project)
51
- raise NotImplementedError, "TODO(rpasay)"
52
- end
53
-
54
66
  def self.version
55
67
  version_file = File.join(File.dirname(__FILE__), '..', '..', 'VERSION')
56
68
  File.exist?(version_file) ? File.read(version_file).chomp : ""
@@ -0,0 +1,13 @@
1
+
2
+ module Drydock
3
+ module Instructions
4
+ # Base class for instruction implementation.
5
+ # **Do not use this class directly.**
6
+ #
7
+ # @see Project
8
+ class Base
9
+ extend AttrExtras.mixin
10
+ extend Memoist
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,117 @@
1
+
2
+ require_relative 'base'
3
+
4
+ module Drydock
5
+ module Instructions
6
+ # The concrete implementation of the COPY instruction.
7
+ # **Do not use this class directly.**
8
+ #
9
+ # @see Project#copy
10
+ class Copy < Base
11
+
12
+ attr_accessor :chmod, :ignorefile, :no_cache, :recursive
13
+
14
+ attr_reader :chain, :source_path, :target_path
15
+ attr_initialize :chain, :source_path, :target_path do
16
+ @chmod = false
17
+ @ignorefile = '.dockerignore'
18
+ @no_cache = false
19
+ @recursive = true
20
+ end
21
+
22
+ # @raise [InvalidInstructionError] when the `source_path` does not exist
23
+ # @raise [InvalidInstructionError] when the `source_path` is an empty directory
24
+ # with nothing to copy
25
+ # @raise [InvalidInstructionError] when the `target_path` does not exist in the
26
+ # container
27
+ # @raise [InvalidInstructionError] when the `target_path` exists in the container,
28
+ # but is not actually a directory
29
+ def run!
30
+ if source_path.start_with?('/')
31
+ Drydock.logger.warn("#{source_path.inspect} is an absolute path; we recommend relative paths")
32
+ end
33
+
34
+ fail InvalidInstructionError, "#{source_path} does not exist" unless File.exist?(source_path)
35
+
36
+ buffer = build_tar_from_source!
37
+ digest = calculate_digest(buffer)
38
+ write_to_container(buffer, digest)
39
+ end
40
+
41
+ private
42
+
43
+ def build_tar_from_source!
44
+ buffer = StringIO.new
45
+ log_info("Processing #{source_files.size} files in tree")
46
+
47
+ TarWriter.new(buffer) do |tar|
48
+ source_files.each do |source_file|
49
+ File.open(source_file, 'r') do |input|
50
+ stat = input.stat
51
+ mode = chmod || stat.mode
52
+ tar.add_entry(source_file, mode: mode, mtime: stat.mtime) do |tar_file|
53
+ tar_file.write(input.read)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ buffer.rewind
60
+ buffer
61
+ end
62
+
63
+ def calculate_digest(buffer)
64
+ Digest::MD5.hexdigest(buffer.read).tap do |digest|
65
+ log_info("Tree digest is md5:#{digest}")
66
+ buffer.rewind
67
+ end
68
+ end
69
+
70
+ def log_info(msg, indent: 0)
71
+ Drydock.logger.info(indent: indent, message: msg)
72
+ end
73
+
74
+ # Retrieve all files inside {#source_path} not matching the {#ignorefile} rules.
75
+ def source_files
76
+ files =
77
+ if File.directory?(source_path)
78
+ FileManager.find(source_path, ignorefile, prepend_path: true, recursive: recursive).sort
79
+ else
80
+ [source_path]
81
+ end
82
+
83
+ fail InvalidInstructionError, "#{source_path} is empty or does not match a path" if files.empty?
84
+
85
+ files
86
+ end
87
+ memoize :source_files
88
+
89
+ # Create a new container on the `chain`, and then write the contents of
90
+ # `buffer`, whose digest is `digest`.
91
+ def write_to_container(buffer, digest)
92
+ label = "# COPY #{recursive ? 'dir' : 'file'}:md5:#{digest} TO #{target_path}"
93
+
94
+ chain.run(label, no_cache: no_cache) do |container|
95
+ target_stat = container.archive_head(target_path)
96
+
97
+ # TODO(rpasay): cannot autocreate the target, because `container` here is already dead
98
+ unless target_stat
99
+ fail InvalidInstructionError, "Target path #{target_path.inspect} does not exist"
100
+ end
101
+
102
+ unless target_stat.directory?
103
+ Drydock.logger.debug(target_stat)
104
+ fail InvalidInstructionError,
105
+ "Target path #{target_path.inspect} exists, " +
106
+ "but is not a directory in the container"
107
+ end
108
+
109
+ container.archive_put(target_path) do |output|
110
+ output.write(buffer.read)
111
+ end
112
+ end
113
+ end
114
+
115
+ end
116
+ end
117
+ end
@@ -4,19 +4,19 @@ module Drydock
4
4
  class Base
5
5
 
6
6
  def clear
7
- raise NotImplementedError, '#clear must be overridden in the subclass'
7
+ fail NotImplementedError, '#clear must be overridden in the subclass'
8
8
  end
9
9
 
10
10
  def fetch(key, &blk)
11
- raise NotImplementedError, '#fetch must be overridden in the subclass'
11
+ fail NotImplementedError, '#fetch must be overridden in the subclass'
12
12
  end
13
13
 
14
14
  def get(key, &blk)
15
- raise NotImplementedError, '#get must be overridden in the subclass'
15
+ fail NotImplementedError, '#get must be overridden in the subclass'
16
16
  end
17
17
 
18
18
  def set(key, value = nil, &blk)
19
- raise NotImplementedError, '#set must be overridden in the subclass'
19
+ fail NotImplementedError, '#set must be overridden in the subclass'
20
20
  end
21
21
 
22
22
  end
data/lib/drydock/phase.rb CHANGED
@@ -14,7 +14,9 @@ module Drydock
14
14
  def self.from(hsh)
15
15
  h = hsh.to_h
16
16
  extra_keys = h.keys - members
17
- raise ArgumentError, "unknown options: #{extra_keys.join(', ')}" unless extra_keys.empty?
17
+
18
+ fail ArgumentError, "unknown options: #{extra_keys.join(', ')}" unless extra_keys.empty?
19
+
18
20
  new(*h.values_at(*members))
19
21
  end
20
22
 
@@ -106,15 +106,15 @@ module Drydock
106
106
  results = c.wait(timeout)
107
107
 
108
108
  unless results
109
- raise InvalidCommandExecutionError, {container: c.id, message: "Container did not return anything (API BUG?)"}
109
+ fail InvalidCommandExecutionError, {container: c.id, message: "Container did not return anything (API BUG?)"}
110
110
  end
111
111
 
112
112
  unless results.key?('StatusCode')
113
- raise InvalidCommandExecutionError, {container: c.id, message: "Container did not return a status code (API BUG?)"}
113
+ fail InvalidCommandExecutionError, {container: c.id, message: "Container did not return a status code (API BUG?)"}
114
114
  end
115
115
 
116
116
  unless results['StatusCode'] == 0
117
- raise InvalidCommandExecutionError, {container: c.id, message: "Container exited with code #{results['StatusCode']}"}
117
+ fail InvalidCommandExecutionError, {container: c.id, message: "Container exited with code #{results['StatusCode']}"}
118
118
  end
119
119
  rescue
120
120
  # on error, kill the streaming logs and reraise the exception
@@ -6,23 +6,23 @@ module Drydock
6
6
  class PackageManager < Base
7
7
 
8
8
  def add(*pkgs)
9
- raise NotImplementedError, '#add must be overridde in the subclass'
9
+ fail NotImplementedError, '#add must be overridde in the subclass'
10
10
  end
11
11
 
12
12
  def clean
13
- raise NotImplementedError, '#clean must be overridde in the subclass'
13
+ fail NotImplementedError, '#clean must be overridde in the subclass'
14
14
  end
15
15
 
16
16
  def remove(*pkgs)
17
- raise NotImplementedError, '#remove must be overridde in the subclass'
17
+ fail NotImplementedError, '#remove must be overridde in the subclass'
18
18
  end
19
19
 
20
20
  def update
21
- raise NotImplementedError, '#update must be overridde in the subclass'
21
+ fail NotImplementedError, '#update must be overridde in the subclass'
22
22
  end
23
23
 
24
24
  def upgrade
25
- raise NotImplementedError, '#upgrade must be overridde in the subclass'
25
+ fail NotImplementedError, '#upgrade must be overridde in the subclass'
26
26
  end
27
27
 
28
28
  end
@@ -1,5 +1,10 @@
1
1
 
2
2
  module Drydock
3
+ # A project defines the methods available in a `Drydockfile`. When run using
4
+ # the binary `drydock`, this object will be instantiated automatically for you.
5
+ #
6
+ # The contents of a `Drydockfile` is automatically evaluated in the context
7
+ # of a project, so you don't need to instantiate the object manually.
3
8
  class Project
4
9
 
5
10
  DEFAULT_OPTIONS = {
@@ -17,11 +22,11 @@ module Drydock
17
22
  # @option build_opts [Boolean] :auto_remove Whether intermediate images
18
23
  # created during the build of this project should be automatically removed.
19
24
  # @option build_opts [String] :author The default author field when an
20
- # author is not provided explicitly with {#author}.
25
+ # author is not provided explicitly with {Project#author}.
21
26
  # @option build_opts [ObjectCaches::Base] :cache An object cache manager.
22
27
  # @option build_opts [#call] :event_handler A handler that responds to a
23
28
  # `#call` message with four arguments: `[event, is_new, serial_no, event_type]`
24
- # most useful to override logging or
29
+ # most useful to override logging.
25
30
  # @option build_opts [PhaseChain] :chain A phase chain manager.
26
31
  # @option build_opts [String] :ignorefile The name of the ignore-file to load.
27
32
  def initialize(build_opts = {})
@@ -52,7 +57,7 @@ module Drydock
52
57
  # @raise [InvalidInstructionArgumentError] when neither name nor email is provided
53
58
  def author(name: nil, email: nil)
54
59
  if (name.nil? || name.empty?) && (email.nil? || name.empty?)
55
- raise InvalidInstructionArgumentError, 'at least one of `name:` or `email:` must be provided'
60
+ fail InvalidInstructionArgumentError, 'at least one of `name:` or `email:` must be provided'
56
61
  end
57
62
 
58
63
  value = email ? "#{name} <#{email}>" : name.to_s
@@ -128,73 +133,24 @@ module Drydock
128
133
  # the mode provided (in integer octal form) will be used to override *all*
129
134
  # file and directory modes.
130
135
  # @param [Boolean] no_cache When `false` (the default), the hash digest of the
131
- # source pathtaking into account all its files, directories, and contentsis
136
+ # source path--taking into account all its files, directories, and contents--is
132
137
  # used as the cache key. When `true`, the image is rebuilt *every* time.
133
138
  # @param [Boolean] recursive When `true`, then `source_path` is expected to be
134
139
  # a directory, at which point all its contents would be recursively searched.
135
140
  # When `false`, then `source_path` is expected to be a file.
136
141
  #
137
- # @raise [InvalidInstructionError] when the `source_path` does not exist
138
- # @raise [InvalidInstructionError] when the `source_path` is an empty directory
139
- # with nothing to copy
140
- # @raise [InvalidInstructionError] when the `target_path` does not exist in the
141
- # container
142
- # @raise [InvalidInstructionError] when the `target_path` exists in the container,
143
- # but is not actually a directory
142
+ # @raise (see Instructions::Copy#run!)
144
143
  def copy(source_path, target_path, chmod: false, no_cache: false, recursive: true)
145
144
  requires_from!(:copy)
146
145
  log_step('copy', source_path, target_path, chmod: (chmod ? sprintf('%o', chmod) : false))
147
146
 
148
- if source_path.start_with?('/')
149
- Drydock.logger.warn("#{source_path.inspect} is an absolute path; we recommend relative paths")
150
- end
151
-
152
- raise InvalidInstructionError, "#{source_path} does not exist" unless File.exist?(source_path)
147
+ Instructions::Copy.new(chain, source_path, target_path).tap do |ins|
148
+ ins.chmod = chmod if chmod
149
+ ins.ignorefile = ignorefile
150
+ ins.no_cache = no_cache
151
+ ins.recursive = recursive
153
152
 
154
- source_files = if File.directory?(source_path)
155
- FileManager.find(source_path, ignorefile, prepend_path: true, recursive: recursive)
156
- else
157
- [source_path]
158
- end
159
- source_files.sort!
160
-
161
- raise InvalidInstructionError, "#{source_path} is empty or does not match a path" if source_files.empty?
162
-
163
- buffer = StringIO.new
164
- log_info("Processing #{source_files.size} files in tree")
165
- TarWriter.new(buffer) do |tar|
166
- source_files.each do |source_file|
167
- File.open(source_file, 'r') do |input|
168
- stat = input.stat
169
- mode = chmod || stat.mode
170
- tar.add_entry(source_file, mode: stat.mode, mtime: stat.mtime) do |tar_file|
171
- tar_file.write(input.read)
172
- end
173
- end
174
- end
175
- end
176
-
177
- buffer.rewind
178
- digest = Digest::MD5.hexdigest(buffer.read)
179
-
180
- log_info("Tree digest is md5:#{digest}")
181
- chain.run("# COPY #{source_path} #{target_path} DIGEST #{digest}", no_cache: no_cache) do |container|
182
- target_stat = container.archive_head(target_path)
183
-
184
- # TODO(rpasay): cannot autocreate the target, because `container` here is already dead
185
- unless target_stat
186
- raise InvalidInstructionError, "Target path #{target_path.inspect} does not exist"
187
- end
188
-
189
- unless target_stat.directory?
190
- Drydock.logger.debug(target_stat)
191
- raise InvalidInstructionError, "Target path #{target_path.inspect} exists, but is not a directory in the container"
192
- end
193
-
194
- container.archive_put(target_path) do |output|
195
- buffer.rewind
196
- output.write(buffer.read)
197
- end
153
+ ins.run!
198
154
  end
199
155
 
200
156
  self
@@ -225,7 +181,7 @@ module Drydock
225
181
 
226
182
  unless cache.key?(source_url)
227
183
  cache.set(source_url) do |obj|
228
- chunked = Proc.new do |chunk, remaining_bytes, total_bytes|
184
+ chunked = proc do |chunk, _remaining_bytes, _total_bytes|
229
185
  obj.write(chunk)
230
186
  end
231
187
  Excon.get(source_url, response_block: chunked)
@@ -236,7 +192,8 @@ module Drydock
236
192
 
237
193
  # TODO(rpasay): invalidate cache when the downloaded file changes,
238
194
  # and then force rebuild
239
- chain.run("# DOWNLOAD #{source_url} #{target_path}") do |container|
195
+ digest = Digest::MD5.hexdigest(source_url)
196
+ chain.run("# DOWNLOAD file:md5:#{digest} #{target_path}") do |container|
240
197
  container.archive_put do |output|
241
198
  TarWriter.new(output) do |tar|
242
199
  cache.get(source_url) do |input|
@@ -252,7 +209,7 @@ module Drydock
252
209
  end
253
210
 
254
211
  # **This instruction is *optional*, but if specified, must appear at the
255
- # beginning of the file.**
212
+ # beginning of the file.**
256
213
  #
257
214
  # This instruction is used to restrict the version of `drydock` required to
258
215
  # run the `Drydockfile`. When not specified, any version of `drydock` is
@@ -266,20 +223,20 @@ module Drydock
266
223
  # drydock '~> 0.5'
267
224
  # @param [String] version The version specification to use.
268
225
  def drydock(version = '>= 0')
269
- raise InvalidInstructionError, '`drydock` must be called before `from`' if chain
226
+ fail InvalidInstructionError, '`drydock` must be called before `from`' if chain
270
227
  log_step('drydock', version)
271
228
 
272
229
  requirement = Gem::Requirement.create(version)
273
230
  current = Gem::Version.create(Drydock.version)
274
231
 
275
232
  unless requirement.satisfied_by?(current)
276
- raise InsufficientVersionError, "build requires #{version.inspect}, but you're on #{Drydock.version.inspect}"
233
+ fail InsufficientVersionError, "build requires #{version.inspect}, but you're on #{Drydock.version.inspect}"
277
234
  end
278
235
 
279
236
  self
280
237
  end
281
238
 
282
- # Sets the entrypoint command for an image.
239
+ # Sets the entrypoint command for an image.
283
240
  #
284
241
  # {#entrypoint} corresponds to the `ENTRYPOINT` Dockerfile instruction. This
285
242
  # instruction does **not** run the command, but rather provides the default
@@ -303,7 +260,7 @@ module Drydock
303
260
 
304
261
  # Set an environment variable, which will be persisted in future images
305
262
  # (unless it is specifically overwritten) and derived projects.
306
- #
263
+ #
307
264
  # Subsequent commands can refer to the environment variable by preceeding
308
265
  # the variable with a `$` sign, e.g.:
309
266
  #
@@ -315,7 +272,7 @@ module Drydock
315
272
  #
316
273
  # Multiple calls to this instruction will build on top of one another.
317
274
  # That is, after the following two instructions:
318
- #
275
+ #
319
276
  # ```
320
277
  # env 'APP_ROOT', '/app'
321
278
  # env 'BUILD_ROOT', '/build'
@@ -419,13 +376,13 @@ module Drydock
419
376
  # the project,** although non-instructions may appear before this.
420
377
  #
421
378
  # If the `drydock` instruction is provided, `from` should come after it.
422
- #
379
+ #
423
380
  # @param [#to_s] repo The name of the repository, which may be any valid docker
424
381
  # repository name, and may optionally include the registry address, e.g.,
425
382
  # `johndoe/thing` or `quay.io/jane/app`. The name *must not* contain the tag name.
426
383
  # @param [#to_s] tag The tag to use.
427
384
  def from(repo, tag = 'latest')
428
- raise InvalidInstructionError, '`from` must only be called once per project' if chain
385
+ fail InvalidInstructionError, '`from` must only be called once per project' if chain
429
386
 
430
387
  repo = repo.to_s
431
388
  tag = tag.to_s
@@ -506,8 +463,10 @@ module Drydock
506
463
  # when `Gemfile` changes but `package.json` does not, only the first
507
464
  # derived project will be rebuilt (and following that, the third as well).
508
465
  #
466
+ # @param (see #initialize)
467
+ # @option (see #initialize)
509
468
  def derive(opts = {}, &blk)
510
- clean_opts = build_opts.delete_if { |k, v| v.nil? }
469
+ clean_opts = build_opts.delete_if { |_, v| v.nil? }
511
470
  derive_opts = clean_opts.merge(opts).merge(chain: chain)
512
471
 
513
472
  Project.new(derive_opts).tap do |project|
@@ -538,8 +497,8 @@ module Drydock
538
497
  mkdir(path)
539
498
 
540
499
  requires_from!(:import)
541
- raise InvalidInstructionError, 'cannot `import` from `/`' if path == '/' && !force
542
- raise InvalidInstructionError, '`import` requires a `from:` option' if from.nil?
500
+ fail InvalidInstructionError, 'cannot `import` from `/`' if path == '/' && !force
501
+ fail InvalidInstructionError, '`import` requires a `from:` option' if from.nil?
543
502
  log_step('import', path, from: from.last_image.id)
544
503
 
545
504
  total_size = 0
@@ -597,8 +556,12 @@ module Drydock
597
556
  end
598
557
  end
599
558
 
559
+ # **NOT SUPPORTED YET**
560
+ #
600
561
  # @todo on_build instructions should be deferred to the end.
601
- def on_build(instruction = nil, &blk)
562
+ def on_build(instruction = nil, &_blk)
563
+ fail NotImplementedError, "on_build is not yet supported"
564
+
602
565
  requires_from!(:on_build)
603
566
  log_step('on_build', instruction)
604
567
  chain.run("# ON_BUILD #{instruction}", on_build: instruction)
@@ -623,7 +586,7 @@ module Drydock
623
586
  # Additional `opts` are also recognized:
624
587
  #
625
588
  # * `author`, a string, preferably in the format of "Name <email@domain.com>".
626
- # If provided, this overrides
589
+ # If provided, this overrides the author name set with {#author}.
627
590
  # * `comment`, an arbitrary string used as a comment for the resulting image
628
591
  #
629
592
  # If `run` results in a container being created and `&blk` is provided, the
@@ -645,9 +608,9 @@ module Drydock
645
608
  # Set project options.
646
609
  def set(key, value = nil, &blk)
647
610
  key = key.to_sym
648
- raise ArgumentError, "unknown option #{key.inspect}" unless build_opts.key?(key)
649
- raise ArgumentError, "one of value or block is required" if value.nil? && blk.nil?
650
- raise ArgumentError, "only one of value or block may be provided" if value && blk
611
+ fail ArgumentError, "unknown option #{key.inspect}" unless build_opts.key?(key)
612
+ fail ArgumentError, "one of value or block is required" if value.nil? && blk.nil?
613
+ fail ArgumentError, "only one of value or block may be provided" if value && blk
651
614
 
652
615
  build_opts[key] = value || blk
653
616
  end
@@ -673,7 +636,7 @@ module Drydock
673
636
  # apk.update
674
637
  # end
675
638
  # ```
676
- #
639
+ #
677
640
  # In cases of single commands, the above is the same as:
678
641
  #
679
642
  # ```
@@ -681,11 +644,12 @@ module Drydock
681
644
  # ```
682
645
  def with(plugin, &blk)
683
646
  (@plugins[plugin] ||= plugin.new(self)).tap do |instance|
684
- yield instance if block_given?
647
+ blk.call(instance) if blk
685
648
  end
686
649
  end
687
650
 
688
651
  private
652
+
689
653
  attr_reader :chain, :build_opts, :stream_monitor
690
654
 
691
655
  def build_cmd(cmd)
@@ -718,7 +682,7 @@ module Drydock
718
682
  end
719
683
 
720
684
  def requires_from!(instruction)
721
- raise InvalidInstructionError, "`#{instruction}` cannot be called before `from`" unless chain
685
+ fail InvalidInstructionError, "`#{instruction}` cannot be called before `from`" unless chain
722
686
  end
723
687
 
724
688
  end
@@ -5,7 +5,7 @@ module Drydock
5
5
  def add_entry(name, mode: 0644, mtime: Time.now, uid: 0, gid: 0)
6
6
  check_closed
7
7
 
8
- raise Gem::Package::NonSeekableIO unless @io.respond_to?(:pos=)
8
+ fail Gem::Package::NonSeekableIO unless @io.respond_to?(:pos=)
9
9
 
10
10
  name, prefix = split_name(name)
11
11
 
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-dock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ripta Pasay
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-07 00:00:00.000000000 Z
11
+ date: 2015-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: attr_extras
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.4'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: docker-api
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +52,20 @@ dependencies:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
54
  version: '0.45'
55
+ - !ruby/object:Gem::Dependency
56
+ name: memoist
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.12'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.12'
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: rake
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +80,20 @@ dependencies:
52
80
  - - "~>"
53
81
  - !ruby/object:Gem::Version
54
82
  version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.34'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.34'
55
97
  - !ruby/object:Gem::Dependency
56
98
  name: jeweler
57
99
  requirement: !ruby/object:Gem::Requirement
@@ -124,6 +166,7 @@ files:
124
166
  - ".dockerignore"
125
167
  - ".pryrc"
126
168
  - ".rspec"
169
+ - ".rubocop.yml"
127
170
  - ".travis.yml"
128
171
  - ".yardopts"
129
172
  - Dockerfile
@@ -152,6 +195,8 @@ files:
152
195
  - lib/drydock/formatters.rb
153
196
  - lib/drydock/ignorefile_definition.rb
154
197
  - lib/drydock/image_repository.rb
198
+ - lib/drydock/instructions/base.rb
199
+ - lib/drydock/instructions/copy.rb
155
200
  - lib/drydock/logger.rb
156
201
  - lib/drydock/object_caches/base.rb
157
202
  - lib/drydock/object_caches/filesystem_cache.rb