dry-dock 0.1.5 → 0.1.6

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