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 +4 -4
- data/.rubocop.yml +4 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +19 -0
- data/README.md +30 -20
- data/VERSION +1 -1
- data/dry-dock.gemspec +15 -3
- data/lib/drydock.rb +4 -0
- data/lib/drydock/docker_api_patch.rb +6 -6
- data/lib/drydock/drydock.rb +19 -7
- data/lib/drydock/instructions/base.rb +13 -0
- data/lib/drydock/instructions/copy.rb +117 -0
- data/lib/drydock/object_caches/base.rb +4 -4
- data/lib/drydock/phase.rb +3 -1
- data/lib/drydock/phase_chain.rb +3 -3
- data/lib/drydock/plugins/package_manager.rb +5 -5
- data/lib/drydock/project.rb +45 -81
- data/lib/drydock/tar_writer.rb +1 -1
- metadata +47 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2627cbab1b9589543ec9a8997d95eb6071ecd17a
|
4
|
+
data.tar.gz: 6fe87f56623532901d4fb285a8cc07a584007cb0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4360c64a1b7d07b36ef1e6e79fc6fb65d73b14ecea226be78bc75819f0fe77b7fbe4f24b0572bfcb4d84900167f442f1709d332d6d5f71cee5b6d66660e5495
|
7
|
+
data.tar.gz: b28fa39b7acbe2e4044b7f75748d314b198f9f90b9da2098bb96552c5495da6548a9daf53080cc672984368464ac343d1124892443587cb5c879bcf975a7b16d
|
data/.rubocop.yml
ADDED
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
|
-

|
6
|
+
[](https://travis-ci.org/ripta/drydock)
|
7
|
+
[](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
|
-
|
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`.
|
46
|
-
|
47
|
-
|
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
|
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
|
-
|
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.
|
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
|
+
# 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.
|
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-
|
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
|
-
|
11
|
+
fail ClientError, ex.response.body
|
12
12
|
rescue Excon::Errors::Unauthorized => ex
|
13
|
-
|
13
|
+
fail UnauthorizedError, ex.response.body
|
14
14
|
rescue Excon::Errors::NotFound => ex
|
15
|
-
|
15
|
+
fail NotFoundError, ex.response.body
|
16
16
|
rescue Excon::Errors::Conflict => ex
|
17
|
-
|
17
|
+
fail ConflictError, ex.response.body
|
18
18
|
rescue Excon::Errors::InternalServerError => ex
|
19
|
-
|
19
|
+
fail ServerError, ex.response.body
|
20
20
|
rescue Excon::Errors::Timeout => ex
|
21
|
-
|
21
|
+
fail TimeoutError, ex.message
|
22
22
|
end
|
23
23
|
|
24
24
|
end
|
data/lib/drydock/drydock.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
10
|
-
|
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,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
|
-
|
7
|
+
fail NotImplementedError, '#clear must be overridden in the subclass'
|
8
8
|
end
|
9
9
|
|
10
10
|
def fetch(key, &blk)
|
11
|
-
|
11
|
+
fail NotImplementedError, '#fetch must be overridden in the subclass'
|
12
12
|
end
|
13
13
|
|
14
14
|
def get(key, &blk)
|
15
|
-
|
15
|
+
fail NotImplementedError, '#get must be overridden in the subclass'
|
16
16
|
end
|
17
17
|
|
18
18
|
def set(key, value = nil, &blk)
|
19
|
-
|
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
|
-
|
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
|
|
data/lib/drydock/phase_chain.rb
CHANGED
@@ -106,15 +106,15 @@ module Drydock
|
|
106
106
|
results = c.wait(timeout)
|
107
107
|
|
108
108
|
unless results
|
109
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
9
|
+
fail NotImplementedError, '#add must be overridde in the subclass'
|
10
10
|
end
|
11
11
|
|
12
12
|
def clean
|
13
|
-
|
13
|
+
fail NotImplementedError, '#clean must be overridde in the subclass'
|
14
14
|
end
|
15
15
|
|
16
16
|
def remove(*pkgs)
|
17
|
-
|
17
|
+
fail NotImplementedError, '#remove must be overridde in the subclass'
|
18
18
|
end
|
19
19
|
|
20
20
|
def update
|
21
|
-
|
21
|
+
fail NotImplementedError, '#update must be overridde in the subclass'
|
22
22
|
end
|
23
23
|
|
24
24
|
def upgrade
|
25
|
-
|
25
|
+
fail NotImplementedError, '#upgrade must be overridde in the subclass'
|
26
26
|
end
|
27
27
|
|
28
28
|
end
|
data/lib/drydock/project.rb
CHANGED
@@ -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
|
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
|
-
|
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 path
|
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
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 { |
|
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
|
-
|
542
|
-
|
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, &
|
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
|
-
|
649
|
-
|
650
|
-
|
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
|
-
|
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
|
-
|
685
|
+
fail InvalidInstructionError, "`#{instruction}` cannot be called before `from`" unless chain
|
722
686
|
end
|
723
687
|
|
724
688
|
end
|
data/lib/drydock/tar_writer.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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
|