docker-template 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +13 -0
  3. data/LICENSE +13 -0
  4. data/README.md +20 -0
  5. data/Rakefile +20 -0
  6. data/lib/docker/template.rb +86 -0
  7. data/lib/docker/template/alias.rb +28 -0
  8. data/lib/docker/template/ansi.rb +75 -0
  9. data/lib/docker/template/auth.rb +25 -0
  10. data/lib/docker/template/common.rb +126 -0
  11. data/lib/docker/template/config.rb +84 -0
  12. data/lib/docker/template/error.rb +20 -0
  13. data/lib/docker/template/error/bad_exit_status.rb +17 -0
  14. data/lib/docker/template/error/bad_repo_name.rb +15 -0
  15. data/lib/docker/template/error/invalid_repo_type.rb +16 -0
  16. data/lib/docker/template/error/invalid_targz_file.rb +15 -0
  17. data/lib/docker/template/error/no_rootfs_copy_dir.rb +15 -0
  18. data/lib/docker/template/error/no_rootfs_mkimg.rb +15 -0
  19. data/lib/docker/template/error/no_setup_context_found.rb +15 -0
  20. data/lib/docker/template/error/not_implemented.rb +15 -0
  21. data/lib/docker/template/error/repo_not_found.rb +16 -0
  22. data/lib/docker/template/interface.rb +119 -0
  23. data/lib/docker/template/metadata.rb +160 -0
  24. data/lib/docker/template/parser.rb +66 -0
  25. data/lib/docker/template/patches.rb +9 -0
  26. data/lib/docker/template/patches/array.rb +11 -0
  27. data/lib/docker/template/patches/hash.rb +76 -0
  28. data/lib/docker/template/patches/object.rb +9 -0
  29. data/lib/docker/template/patches/pathname.rb +46 -0
  30. data/lib/docker/template/patches/string.rb +9 -0
  31. data/lib/docker/template/repo.rb +180 -0
  32. data/lib/docker/template/rootfs.rb +75 -0
  33. data/lib/docker/template/routable.rb +28 -0
  34. data/lib/docker/template/scratch.rb +139 -0
  35. data/lib/docker/template/simple.rb +51 -0
  36. data/lib/docker/template/stream.rb +62 -0
  37. data/lib/docker/template/templates/rootfs.erb +8 -0
  38. data/lib/docker/template/templates/scratch.erb +7 -0
  39. data/lib/docker/template/util.rb +39 -0
  40. data/lib/docker/template/util/copy.rb +82 -0
  41. data/lib/docker/template/util/data.rb +26 -0
  42. data/lib/docker/template/version.rb +9 -0
  43. metadata +155 -0
@@ -0,0 +1,28 @@
1
+ # Frozen-string-literal: true
2
+ # Copyright: 2015 Jordon Bedwell - Apache v2.0 License
3
+ # Encoding: utf-8
4
+
5
+ module Docker
6
+ module Template
7
+ module Routable
8
+ def route_to_hash(methods, hash, alt_key = nil)
9
+ methods = [methods] unless methods.is_a?(Array)
10
+ methods.each do |method|
11
+ class_eval <<-STR, __FILE__, __LINE__
12
+ def #{method}
13
+ #{alt_key ? "#{hash}['#{alt_key}']" : "#{hash}['#{method}']"}
14
+ end
15
+ STR
16
+ end
17
+ end
18
+
19
+ def route_to_ivar(method, var, bool: false, revbool: false)
20
+ class_eval <<-STR, __FILE__, __LINE__
21
+ def #{method}#{"?" if bool || revbool}
22
+ #{revbool ? "!!!" : "!!" if bool || revbool}#{var}
23
+ end
24
+ STR
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,139 @@
1
+ # Frozen-string-literal: true
2
+ # Copyright: 2015 Jordon Bedwell - Apache v2.0 License
3
+ # Encoding: utf-8
4
+
5
+ module Docker
6
+ module Template
7
+ class Scratch < Common
8
+ attr_reader :rootfs, :img, :repo
9
+ def initialize(repo)
10
+ @repo = repo
11
+ end
12
+
13
+ #
14
+
15
+ def data
16
+ Template.get(:scratch, {
17
+ :maintainer => @repo.metadata["maintainer"],
18
+ :entrypoint => @repo.metadata["entry"].fallback,
19
+ :tar_gz => @tar_gz.basename
20
+ })
21
+ end
22
+
23
+ #
24
+
25
+ def unlink(img: false)
26
+ @copy.rmtree if @copy && @copy.directory?
27
+ @img.delete "force" => true if @img && img
28
+ @context.rmtree if @context && @context.directory?
29
+ @tar_gz.unlink if @tar_gz && @tar_gz.file?
30
+ end
31
+
32
+ #
33
+
34
+ private
35
+ def setup_context
36
+ @context = @repo.tmpdir
37
+ @tar_gz = @repo.tmpfile "archive", ".tar.gz", root: @context
38
+ @copy = @repo.tmpdir "copy"
39
+ copy_dockerfile
40
+ end
41
+
42
+ #
43
+
44
+ private
45
+ def build_rootfs
46
+ @rootfs ||= begin
47
+ self.class.rootfs_for(@repo)
48
+ end
49
+ end
50
+
51
+ # Caches and builds the master rootfs for repos, this is cached
52
+ # on the class because it could be used many times in a single build
53
+ # so we make sure to keep it around so you don't have tons of
54
+ # replication going about slowing down all of the builds.
55
+
56
+ def self.rootfs_for(repo)
57
+ (@rootfs ||= {})[repo.name] ||= begin
58
+ Rootfs.new(repo).tap(&:build)
59
+ end
60
+ end
61
+
62
+ #
63
+
64
+ private
65
+ def build_context
66
+ build_rootfs
67
+
68
+ output_given = false
69
+ img = Container.create(create_args)
70
+ img.start(start_args).attach do |type, str|
71
+ type == :stdout ? $stdout.print(str) : $stderr.print(Ansi.red(str))
72
+ output_given = true
73
+ end
74
+
75
+ # NOTE: Sometimes the instance exists too quickly for attach to even
76
+ # work, through the remote API, so we need to detect those situations
77
+ # and stream the logs after it's exited if we have given no output,
78
+ # we want you to always get the output that was given.
79
+
80
+ if !output_given
81
+ img.streaming_logs "stdout" => true, "stderr" => true do |type, str|
82
+ type == :stdout ? $stdout.print(str) : $stderr.print(Ansi.red(str))
83
+ end
84
+ end
85
+
86
+ if (status = img.json["State"]["ExitCode"]) != 0
87
+ raise Error::BadExitStatus, status
88
+ end
89
+ ensure
90
+ if img
91
+ img.stop rescue nil
92
+ img.delete
93
+ end
94
+ end
95
+
96
+ #
97
+
98
+ def verify_context
99
+ unless @tar_gz.size > 0
100
+ raise Error::InvalidTargzFile, @tar_gz
101
+ end
102
+ end
103
+
104
+ #
105
+
106
+ private
107
+ def copy_dockerfile
108
+ data = self.data % @tar_gz.basename
109
+ dockerfile = @context.join("Dockerfile")
110
+ dockerfile.write(data)
111
+ end
112
+
113
+ #
114
+
115
+ private
116
+ def create_args
117
+ {
118
+ "Env" => @repo.to_env_hash(tar_gz: @tar_gz, copy_dir: @copy).to_env_ary,
119
+ "Name" => ["rootfs", @repo.name, @repo.tag, "image"].join("-"),
120
+ "Image" => @rootfs.img.id,
121
+ "Volumes" => {
122
+ @tar_gz.to_s => {}, @copy.to_s => {}
123
+ }
124
+ }
125
+ end
126
+
127
+ #
128
+
129
+ private
130
+ def start_args
131
+ {
132
+ "Binds" => [
133
+ "#{@copy.to_s}:#{@copy.to_s}:ro", "#{@tar_gz.to_s}:#{@tar_gz.to_s}"
134
+ ]
135
+ }
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,51 @@
1
+ # Frozen-string-literal: true
2
+ # Copyright: 2015 Jordon Bedwell - Apache v2.0 License
3
+ # Encoding: utf-8
4
+
5
+ module Docker
6
+ module Template
7
+ class Simple < Common
8
+ attr_reader :repo
9
+ def initialize(repo)
10
+ @repo = repo
11
+ end
12
+
13
+ #
14
+
15
+ def sync
16
+ copy_build_and_verify unless @context
17
+ Util.create_dockerhub_context( \
18
+ self, @context)
19
+ end
20
+
21
+ #
22
+
23
+ def unlink(img: false, sync: true)
24
+ self.sync if sync && @repo.syncable?
25
+ @img.delete "force" => true if @img && img
26
+ @context.rmtree if @context && \
27
+ @context.directory?
28
+ end
29
+
30
+ #
31
+
32
+ def setup_context
33
+ @context = @repo.tmpdir
34
+ @copy = @context.join("copy")
35
+ copy_dockerfile
36
+ @copy.mkdir
37
+ end
38
+
39
+ #
40
+
41
+ private
42
+ def copy_dockerfile
43
+ dockerfile = @repo.root.join("Dockerfile").read
44
+ data = Util::Data.new(:metadata => @repo.metadata)
45
+ data = ERB.new(dockerfile).result(data._binding)
46
+ context = @context.join("Dockerfile")
47
+ context.write(data)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,62 @@
1
+ # Frozen-string-literal: true
2
+ # Copyright: 2015 Jordon Bedwell - Apache v2.0 License
3
+ # Encoding: utf-8
4
+
5
+ module Docker
6
+ module Template
7
+ class Stream
8
+ def initialize
9
+ @lines = {}
10
+ end
11
+
12
+ #
13
+
14
+ def log(part)
15
+ stream = JSON.parse(part)
16
+ return progress_bar(stream) if stream.any_keys?("progress", "progressDetail")
17
+ return $stdout.puts stream["status"] || stream["stream"] if stream.any_keys?("status", "stream")
18
+ return progress_error(stream) if stream.any_keys?("errorDetail", "error")
19
+
20
+ warn Ansi.red("Unhandled stream message")
21
+ $stderr.puts Ansi.red("Please file a bug ticket.")
22
+ $stdout.puts part
23
+ end
24
+
25
+ #
26
+
27
+ def progress_error(stream)
28
+ abort Ansi.red((stream["error"] || stream["errorDetail"]["message"]).capitalize)
29
+ end
30
+
31
+ #
32
+
33
+ private
34
+ def progress_bar(stream)
35
+ id = stream["id"]
36
+ unless id
37
+ return
38
+ end
39
+
40
+ before, diff = progress_diff(id)
41
+ $stdout.print before if before
42
+ str = stream["progress"] || stream["status"]
43
+ str = "%s: %s\r" % [id, str]
44
+
45
+ $stdout.print(Ansi.jump(str, both: diff))
46
+ end
47
+
48
+ #
49
+
50
+ private
51
+ def progress_diff(id)
52
+ if @lines.has_key?(id)
53
+ return nil, @lines.size - @lines[id]
54
+ end
55
+
56
+ @lines[id] = @lines.size
57
+ before = "\n" unless @lines.size == 1
58
+ return before, 0
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,8 @@
1
+ FROM <%= @rootfs_base_img %>
2
+ RUN apt-get update && apt-get install --no-install-recommends -yf \
3
+ xz-utils git ca-certificates wget
4
+
5
+ COPY copy /
6
+ ENTRYPOINT [ \
7
+ "/usr/local/bin/mkimg" \
8
+ ]
@@ -0,0 +1,7 @@
1
+ FROM scratch
2
+ MAINTAINER <%= @maintainer %>
3
+ ENV TERM=xterm
4
+ ADD <%= @tar_gz %> /
5
+ <% if @entrypoint %>
6
+ ENTRYPOINT ["<%= @entrypoint %>"]
7
+ <% end %>
@@ -0,0 +1,39 @@
1
+ # Frozen-string-literal: true
2
+ # Copyright: 2015 Jordon Bedwell - Apache v2.0 License
3
+ # Encoding: utf-8
4
+
5
+ module Docker
6
+ module Template
7
+ module Util module_function
8
+ autoload :Copy, "docker/template/util/copy"
9
+ autoload :Data, "docker/template/util/data"
10
+
11
+ def notify_alias(aliased)
12
+ repo = aliased.repo
13
+ parent_repo = aliased.parent_repo
14
+ $stdout.puts Ansi.green("Aliasing #{repo} -> #{parent_repo}")
15
+ end
16
+
17
+ #
18
+
19
+ def notify_build(repo, rootfs: false)
20
+ img = rootfs ? repo.to_rootfs_s : repo.to_s
21
+ $stdout.puts Ansi.green("Building: #{img}")
22
+ end
23
+
24
+ #
25
+
26
+ def create_dockerhub_context(builder, context)
27
+ tags = builder.repo.root.join("tags")
28
+ readme = builder.repo.root.children.select { |val| val.to_s =~ /readme/i }.first
29
+ context = tags.join(builder.repo.aliased) if builder.aliased?
30
+ dir = tags.join(builder.repo.tag)
31
+
32
+ FileUtils.mkdir_p dir
33
+ $stdout.puts Ansi.yellow("Storing a Docker context for #{builder.repo}")
34
+ Util::Copy.directory(context, dir)
35
+ Util::Copy.file(readme, dir)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,82 @@
1
+ # Frozen-string-literal: true
2
+ # Copyright: 2015 Jordon Bedwell - Apache v2.0 License
3
+ # Encoding: utf-8
4
+
5
+ module Docker
6
+ module Template
7
+ module Util
8
+ class Copy
9
+ def initialize(from, to)
10
+ @root = Template.root.realpath
11
+ @repos_root = Template.repos_root.realpath
12
+ @from = from.to_pathname
13
+ @to = to.to_pathname
14
+ end
15
+
16
+ #
17
+
18
+ def self.directory(from, to)
19
+ if from && to && File.exist?(from)
20
+ new(from, to).directory
21
+ end
22
+ end
23
+
24
+ # Copy a directory checking for symlinks and resolving them (only
25
+ # at the top level) if they are in the repos root, the root or the from
26
+ # path. The reason we check all three is because repos might be a
27
+ # symlink that resolves out of path so we need to allow symlinks
28
+ # from it. The same for the copy folder.
29
+
30
+ def directory
31
+ FileUtils.cp_r(@from.children, @to, :dereference_root => false)
32
+ @from.all_children.select(&:symlink?).each do |path|
33
+ resolved = path.realpath
34
+ pth = path.relative_path_from(@from)
35
+ pth = @to.join(path)
36
+
37
+ unless in_path?(resolved)
38
+ raise Errno::EPERM, "#{pth} not in #{@root}"
39
+ end
40
+
41
+ FileUtils.rm_r(pth)
42
+ FileUtils.cp_r(resolved, pth, {
43
+ :dereference_root => false
44
+ })
45
+ end
46
+ end
47
+
48
+ #
49
+
50
+ def self.file(from, to)
51
+ if to && from && File.exist?(from)
52
+ new(from, to).file
53
+ end
54
+ end
55
+
56
+ #
57
+
58
+ def file
59
+ if !@from.symlink?
60
+ return FileUtils.cp(@from, @to)
61
+ end
62
+
63
+ resolved = @from.realpath
64
+ allowed = resolved.in_path?(@root)
65
+ raise Errno::EPERM, "#{resolved} not in #{@root}." unless allowed
66
+ FileUtils.cp(resolved, @to)
67
+ end
68
+
69
+ # Check to see if the path falls within the users roots, while
70
+ # getting the real path of the from root just incase it, itself is
71
+ # a symlink, we don't want it to fail because of that.
72
+
73
+ private
74
+ def in_path?(resolved)
75
+ resolved.in_path?(@repos_root) || \
76
+ resolved.in_path?(@from.realpath) || \
77
+ resolved.in_path?(@root)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,26 @@
1
+ # Frozen-string-literal: true
2
+ # Copyright: 2015 Jordon Bedwell - Apache v2.0 License
3
+ # Encoding: utf-8
4
+
5
+ module Docker
6
+ module Template
7
+ module Util
8
+
9
+ # Provides a way to encapsulate data for ERB processing so that we
10
+ # don't get full unfettered access to the entire binding from within
11
+ # our ERB processing context. Nobody wants that.
12
+
13
+ class Data
14
+ def initialize(vars)
15
+ vars.each do |key, val|
16
+ instance_variable_set("@#{key}", val)
17
+ end
18
+ end
19
+
20
+ def _binding
21
+ return binding
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end