conjur-debify 3.0.0.pre.1118 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +1 -0
  3. data/.gitignore +22 -0
  4. data/.project +18 -0
  5. data/.rvmrc +60 -0
  6. data/CHANGELOG.md +246 -0
  7. data/CONTRIBUTING.md +16 -0
  8. data/Dockerfile +33 -0
  9. data/Gemfile +2 -0
  10. data/Jenkinsfile +116 -0
  11. data/LICENSE.txt +22 -0
  12. data/README.md +303 -0
  13. data/Rakefile +75 -0
  14. data/VERSION +1 -1
  15. data/bin/debify +5 -0
  16. data/build.sh +8 -0
  17. data/ci/test.sh +8 -0
  18. data/debify.gemspec +36 -0
  19. data/distrib/conjur_creds.rb +7 -0
  20. data/distrib/docker-debify +50 -0
  21. data/distrib/entrypoint.sh +19 -0
  22. data/distrib/script +1 -0
  23. data/distrib/secrets +1 -0
  24. data/distrib/secrets.yml +2 -0
  25. data/example/Gemfile +9 -0
  26. data/example/Gemfile.lock +32 -0
  27. data/example/debify.sh +3 -0
  28. data/example/distrib/postinstall.sh +8 -0
  29. data/example/docker-compose.yml +11 -0
  30. data/example/net-test.sh +7 -0
  31. data/example/test.sh +4 -0
  32. data/features/detect_version.feature +12 -0
  33. data/features/package.feature +23 -0
  34. data/features/sandbox.feature +23 -0
  35. data/features/step_definitions/debify_steps.rb +29 -0
  36. data/features/support/env.rb +12 -0
  37. data/features/support/hooks.rb +29 -0
  38. data/features/support/world.rb +10 -0
  39. data/features/test.feature +24 -0
  40. data/image-tags +23 -0
  41. data/lib/conjur/debify/Dockerfile.fpm +13 -0
  42. data/lib/conjur/debify/action/publish.rb +136 -0
  43. data/lib/conjur/debify/utils.rb +16 -0
  44. data/lib/conjur/debify/version.rb +5 -0
  45. data/lib/conjur/debify.rb +850 -0
  46. data/lib/conjur/fpm/Dockerfile +26 -0
  47. data/lib/conjur/fpm/debify_utils.sh +32 -0
  48. data/lib/conjur/fpm/package.sh +107 -0
  49. data/lib/conjur/publish/Dockerfile +5 -0
  50. data/publish-rubygem.sh +10 -0
  51. data/push-image.sh +6 -0
  52. data/secrets.yml +3 -0
  53. data/spec/action/publish_spec.rb +54 -0
  54. data/spec/data/Makefile +5 -0
  55. data/spec/data/test.tar +0 -0
  56. data/spec/debify_utils_spec.rb +55 -0
  57. data/spec/spec_helper.rb +1 -0
  58. data/spec/utils_spec.rb +22 -0
  59. data/tag-image.sh +6 -0
  60. data/test.sh +6 -0
  61. metadata +80 -7
@@ -0,0 +1,850 @@
1
+ require "conjur/debify/version"
2
+ require 'docker'
3
+ require 'fileutils'
4
+ require 'gli'
5
+ require 'json'
6
+ require 'base64'
7
+ require 'tmpdir'
8
+
9
+ require 'conjur/debify/utils'
10
+
11
+ require 'active_support'
12
+ require 'active_support/core_ext'
13
+
14
+ include GLI::App
15
+
16
+ DEFAULT_FILE_TYPE = "deb"
17
+
18
+ config_file '.debifyrc'
19
+
20
+ desc 'Set an environment variable (e.g. TERM=xterm) when starting a container'
21
+ flag [:env], :multiple => true
22
+
23
+ desc 'Mount local bundle to reuse gems from previous installation'
24
+ default_value true
25
+ switch [:'local-bundle']
26
+
27
+
28
+ Docker.options[:read_timeout] = 300
29
+
30
+ # This is used to turn on DEBUG notices.
31
+ module DebugMixin
32
+ DEBUG = ENV['DEBUG'].nil? ? true : ENV['DEBUG'].downcase == 'true'
33
+
34
+ def debug *a
35
+ DebugMixin.debug *a
36
+ end
37
+
38
+ def self.debug *a
39
+ $stderr.puts *a if DEBUG
40
+ end
41
+
42
+ def debug_write *a
43
+ DebugMixin.debug_write *a
44
+ end
45
+
46
+ def self.debug_write *a
47
+ $stderr.write *a if DEBUG
48
+ end
49
+
50
+ # you can give this to various docker methods to print output if debug is on
51
+ def self.docker_debug *a
52
+ if a.length == 2 && a[0].is_a?(Symbol)
53
+ debug a.last
54
+ else
55
+ a.each do |line|
56
+ begin
57
+ line = JSON.parse(line)
58
+ line.keys.each do |k|
59
+ debug line[k]
60
+ end
61
+ rescue JSON::ParserError
62
+ # Docker For Mac is spitting out invalid JSON, so just print
63
+ # out the line if parsing fails.
64
+ debug line
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ DOCKER = method :docker_debug
71
+ end
72
+
73
+ program_desc 'Utility commands for building and testing Conjur appliance Debian packages'
74
+
75
+ version Conjur::Debify::VERSION
76
+
77
+ subcommand_option_handling :normal
78
+ arguments :strict
79
+
80
+ def detect_version
81
+ if File.exists?("VERSION") && !(base_commit = `git log --pretty='%h' VERSION | head -n 1`.strip).empty?
82
+ base_version = File.read("VERSION").strip
83
+ commits_since = `git log #{base_commit}..HEAD --pretty='%h'`.split("\n").size
84
+ hash = `git rev-parse --short HEAD`.strip
85
+ [[base_version, commits_since].join('.'), hash].join("-")
86
+ else
87
+ `git describe --long --tags --abbrev=7 --match 'v*.*.*' | sed -e 's/^v//'`.strip.tap do |version|
88
+ raise "No Git version (tag) for project" if version.empty?
89
+ end
90
+ end
91
+ end
92
+
93
+ def git_files
94
+ files = (`git ls-files -z`.split("\x0") + ['Gemfile.lock', 'VERSION']).uniq
95
+ # Since submodule directories are listed, but are not files, we remove them.
96
+ # Currently, `conjur-project-config` is the only submodule in Conjur, and it
97
+ # can safely be removed because it's a developer-only tool. If we add another
98
+ # submodule in the future needed for production, we'll need to update this
99
+ # code. But YAGNI for now.
100
+ files.select { |f| File.file?(f) }
101
+ end
102
+
103
+ def login_to_registry(appliance_image_id)
104
+ config_file = File.expand_path('~/.docker/config.json')
105
+ if File.exist? config_file
106
+ json_config = JSON.parse(File.read(config_file))
107
+ registry = appliance_image_id.split('/')[0]
108
+
109
+ json_auth = json_config['auths'][registry]['auth']
110
+ if json_auth
111
+ username, password = Base64.decode64(json_auth).split(':')
112
+ Docker.authenticate! username: username, password: password, serveraddress: registry
113
+ end
114
+ end
115
+ end
116
+
117
+ desc "Clean current working directory of non-Git-managed files"
118
+ long_desc <<DESC
119
+ Reliable builds depend on having a clean working directory.
120
+
121
+ Because debify runs some commands in volume-mounted Docker containers,
122
+ it is capable of creating root-owned files.
123
+
124
+ This command will delete all files in the working directory that are not
125
+ git-managed. The command is designed to run in Jenkins. Therefore, it will
126
+ only perform file deletion if:
127
+
128
+ * The current user, as provided by Etc.getlogin, is 'jenkins'
129
+ * The BUILD_NUMBER environment variable is set
130
+
131
+ File deletion can be compelled using the "force" option.
132
+ DESC
133
+ arg_name "project-name -- <fpm-arguments>"
134
+ command "clean" do |c|
135
+ c.desc "Set the current working directory"
136
+ c.flag [:d, "dir"]
137
+
138
+ c.desc "Ignore (don't delete) a file or directory"
139
+ c.flag [:i, :ignore]
140
+
141
+ c.desc "Force file deletion even if if this doesn't look like a Jenkins environment"
142
+ c.switch [:force]
143
+
144
+ c.action do |global_options, cmd_options, args|
145
+ def looks_like_jenkins?
146
+ require 'etc'
147
+ Etc.getlogin == 'jenkins' && ENV['BUILD_NUMBER']
148
+ end
149
+
150
+ require 'set'
151
+ perform_deletion = cmd_options[:force] || looks_like_jenkins?
152
+ if !perform_deletion
153
+ $stderr.puts "No --force, and this doesn't look like Jenkins. I won't actually delete anything"
154
+ end
155
+ @ignore_list = Array(cmd_options[:ignore]) + ['.', '..', '.git']
156
+
157
+ def ignore_file? f
158
+ @ignore_list.find { |ignore| f.index(ignore) == 0 }
159
+ end
160
+
161
+ dir = cmd_options[:dir] || '.'
162
+ dir = File.expand_path(dir)
163
+ Dir.chdir dir do
164
+ require 'find'
165
+ find_files = []
166
+ Find.find('.').each do |p|
167
+ find_files.push p[2..-1]
168
+ end
169
+ find_files.compact!
170
+ delete_files = (find_files - git_files)
171
+ delete_files.delete_if { |file|
172
+ File.directory?(file) || ignore_file?(file)
173
+ }
174
+ if perform_deletion
175
+ image = Docker::Image.create 'fromImage' => "alpine:3.3"
176
+ options = {
177
+ 'Cmd' => ["sh", "-c", "while true; do sleep 1; done"],
178
+ 'Image' => image.id,
179
+ 'Binds' => [
180
+ [dir, "/src"].join(':'),
181
+ ]
182
+ }
183
+ options['Privileged'] = true if Docker.version['Version'] >= '1.10.0'
184
+ container = Docker::Container.create options
185
+ begin
186
+ container.start!
187
+ delete_files.each do |file|
188
+ puts file
189
+
190
+ file = "/src/#{file}"
191
+ cmd = ["rm", "-f", file]
192
+
193
+ stdout, stderr, status = container.exec cmd, &DebugMixin::DOCKER
194
+ $stderr.puts "Failed to delete #{file}" unless status == 0
195
+ end
196
+ ensure
197
+ container.delete force: true
198
+ end
199
+ else
200
+ delete_files.each do |file|
201
+ puts file
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ def copy_packages_from_container(container, package_name, dev_package_name)
209
+ Conjur::Debify::Utils.copy_from_container container, "/src/#{package_name}"
210
+ puts "#{package_name}"
211
+ begin
212
+ Conjur::Debify::Utils.copy_from_container container, "/dev-pkg/#{dev_package_name}"
213
+ puts "#{dev_package_name}"
214
+ rescue Docker::Error::NotFoundError
215
+ warn "#{dev_package_name} not found. The package might not have any development dependencies."
216
+ end
217
+ end
218
+
219
+ desc "Build a debian package for a project"
220
+ long_desc <<DESC
221
+ The package is built using fpm (https://github.com/jordansissel/fpm).
222
+
223
+ The project directory is required to contain:
224
+
225
+ * A Gemfile and Gemfile.lock
226
+ * A shell script called debify.sh
227
+
228
+ debify.sh is invoked by the package build process to create any custom
229
+ files, other than the project source tree. For example, config files can be
230
+ created in /opt/conjur/etc.
231
+
232
+ The distrib folder in the project source tree is intended to create scripts
233
+ for package pre-install, post-install etc. The distrib folder is not included
234
+ in the deb package, so its contents should be copied to the file system or
235
+ packaged using fpm arguments.
236
+
237
+ All arguments to this command which follow the double-dash are propagated to
238
+ the fpm command.
239
+ DESC
240
+ arg_name "project-name -- <fpm-arguments>"
241
+ command "package" do |c|
242
+ c.desc "Set the current working directory"
243
+ c.flag [:d, "dir"]
244
+
245
+ c.desc "Set the output file type of the fpm command (e.g rpm)"
246
+ c.flag [:o, :output]
247
+
248
+ c.desc "Specify the deb version; by default, it's read from the VERSION file"
249
+ c.flag [:v, :version]
250
+
251
+ c.desc "Specify a custom Dockerfile.fpm"
252
+ c.flag [:dockerfile]
253
+
254
+ c.desc "Specify files to add to the FPM image that are not included from the git repo"
255
+ c.flag [:'additional-files']
256
+
257
+ c.action do |global_options, cmd_options, args|
258
+ raise "project-name is required" unless project_name = args.shift
259
+
260
+ fpm_args = []
261
+ if (delimeter = args.shift) == '--'
262
+ fpm_args = args.dup
263
+ else
264
+ raise "Unexpected argument '#{delimeter}'"
265
+ end
266
+
267
+ dir = cmd_options[:dir] || '.'
268
+ pwd = File.dirname(__FILE__)
269
+
270
+ additional_files = []
271
+ if cmd_options[:'additional-files']
272
+ additional_files = cmd_options[:'additional-files'].split(',').map(&:strip)
273
+ end
274
+
275
+ begin
276
+ tries ||= 2
277
+ fpm_image = Docker::Image.build_from_dir File.expand_path('fpm', File.dirname(__FILE__)), tag: "debify-fpm", &DebugMixin::DOCKER
278
+ rescue
279
+ image_id = File.readlines(File.expand_path('fpm/Dockerfile', File.dirname(__FILE__)))
280
+ .find { | line | line =~ /^FROM/ }
281
+ .split(' ')
282
+ .last
283
+ login_to_registry image_id
284
+ retry unless (tries -= 1).zero?
285
+ end
286
+ DebugMixin.debug_write "Built base fpm image '#{fpm_image.id}'\n"
287
+ dir = File.expand_path(dir)
288
+
289
+ Dir.chdir dir do
290
+ version = cmd_options[:version] || detect_version
291
+
292
+ # move git files and Dockerfile to temp dir to make deb from
293
+ # we do this to avoid adding "non-git" files
294
+ # that aren't mentioned in the dockerignore to the deb
295
+ temp_dir = Dir.mktmpdir
296
+ DebugMixin.debug_write "Copying git files to tmp dir '#{temp_dir}'\n"
297
+ (git_files + additional_files).each do |fname|
298
+ original_file = File.join(dir, fname)
299
+ destination_path = File.join(temp_dir, fname)
300
+ FileUtils.mkdir_p(File.dirname(destination_path))
301
+ FileUtils.cp(original_file, destination_path)
302
+ end
303
+
304
+ # rename specified dockerfile to 'Dockerfile' during copy, incase name is different
305
+ dockerfile_path = cmd_options[:dockerfile] || File.expand_path("debify/Dockerfile.fpm", pwd)
306
+ temp_dockerfile = File.join(temp_dir, "Dockerfile")
307
+
308
+ # change image variable in specified Dockerfile
309
+ dockerfile = File.read(dockerfile_path)
310
+ replace_image = dockerfile.gsub("@@image@@", fpm_image.id)
311
+ File.open(temp_dockerfile, "w") { |file| file.puts replace_image }
312
+
313
+ # build image from project being debified dir
314
+ image = Docker::Image.build_from_dir temp_dir, &DebugMixin::DOCKER
315
+
316
+ DebugMixin.debug_write "Built fpm image '#{image.id}' for project #{project_name}\n"
317
+
318
+ container_cmd_options = [project_name, version]
319
+
320
+ # Set the output file type if present
321
+ file_type = cmd_options[:output] || DEFAULT_FILE_TYPE
322
+ container_cmd_options << "--file-type=#{file_type}"
323
+
324
+ options = {
325
+ 'Cmd' => container_cmd_options + fpm_args,
326
+ 'Image' => image.id
327
+ }
328
+ options['Privileged'] = true if Docker.version['Version'] >= '1.10.0'
329
+
330
+ container = Docker::Container.create options
331
+ begin
332
+ DebugMixin.debug_write "Packaging #{project_name} in container #{container.id}\n"
333
+ container.tap(&:start!).streaming_logs(follow: true, stdout: true, stderr: true) { |stream, chunk| $stderr.puts "#{chunk}" }
334
+ status = container.wait
335
+ raise "Failed to package #{project_name}" unless status['StatusCode'] == 0
336
+
337
+ if file_type == "deb"
338
+ # Copy deb packages
339
+ copy_packages_from_container(
340
+ container,
341
+ "conjur-#{project_name}_#{version}_amd64.deb",
342
+ "conjur-#{project_name}-dev_#{version}_amd64.deb"
343
+ )
344
+ elsif file_type == "rpm"
345
+ # Copy rpm packages
346
+ # The rpm builder replaces dashes with underscores in the version
347
+ rpm_version = version.tr('-', '_')
348
+ copy_packages_from_container(
349
+ container,
350
+ "conjur-#{project_name}-#{rpm_version}-1.x86_64.rpm",
351
+ "conjur-#{project_name}-dev-#{rpm_version}-1.x86_64.rpm"
352
+ )
353
+ end
354
+ ensure
355
+ container.delete(force: true)
356
+ end
357
+ end
358
+ end
359
+ end
360
+
361
+ def container_command container, *args
362
+ stdout, stderr, exitcode = container.exec args, &DebugMixin::DOCKER
363
+ exit_now! "Command failed : #{args.join(' ')}", exitcode unless exitcode == 0
364
+ stdout
365
+ end
366
+
367
+ def wait_for_conjur appliance_image, container
368
+ container_command container, '/opt/conjur/evoke/bin/wait_for_conjur'
369
+ rescue
370
+ $stderr.puts container.logs
371
+ raise
372
+ end
373
+
374
+ def network_options(cmd)
375
+ cmd.desc "Specify link for test container"
376
+ cmd.flag [:l, :link], :multiple => true
377
+
378
+ cmd.desc 'Attach to the specified network'
379
+ cmd.flag [:n, :net]
380
+ end
381
+
382
+ def short_id(id)
383
+ if id =~ /\A[0-9a-f]{64}\z/ # 64 hex digits, docker only allows lower case letters in ids
384
+ $stderr.puts "Warning: found full container id, using short id instead (#{id[0..11]} for #{id})"
385
+ id[0..11]
386
+ else
387
+ id
388
+ end
389
+ end
390
+
391
+ # If the source of the link is a full container id, use the short id
392
+ # instead. (Docker doesn't add full container ids as network aliases,
393
+ # only short ids).
394
+ def shorten_source_id(link)
395
+ src, dest = link.split(':')
396
+ src && dest ? "#{short_id(src)}:#{dest}" : link
397
+ end
398
+
399
+ def add_network_config(container_config, cmd_options)
400
+ host_config = container_config['HostConfig']
401
+ has_links = cmd_options[:link] && !cmd_options[:link].empty?
402
+ net_name = cmd_options[:net]
403
+ if net_name
404
+ host_config['NetworkMode'] = net_name
405
+ if has_links
406
+ container_config['NetworkingConfig'] ||= {}
407
+ container_config['NetworkingConfig'].deep_merge!(
408
+ 'EndpointsConfig' => {
409
+ net_name => {
410
+ 'Links' => cmd_options[:link].collect(&method(:shorten_source_id))
411
+ }
412
+ }
413
+ )
414
+ end
415
+ elsif has_links
416
+ # Don't shorten source ids here
417
+ host_config['Links'] = cmd_options[:link]
418
+ end
419
+ end
420
+
421
+ desc "Test a Conjur debian package in a Conjur appliance container"
422
+ long_desc <<DESC
423
+ First, a Conjur appliance container is created and started. By default, the
424
+ container image is registry.tld/conjur-appliance-cuke-master. An image tag
425
+ MUST be supplied. This image is configured with all the CONJUR_ environment
426
+ variables setup for the local environment (appliance URL, cert path, admin username and
427
+ password, etc). The project source tree is also mounted into the container, at
428
+ /src/<project-name>.
429
+
430
+ This command then waits for Conjur to initialize and be healthy. It proceeds by
431
+ installing the conjur-<project-name>_<version>_amd64.deb from the project working directory.
432
+
433
+ Then the evoke "test-install" command is used to install the test code in the
434
+ /src/<project-name>. Basically, the development bundle is installed and the database
435
+ configuration (if any) is setup.
436
+
437
+ Finally, a test script from the project source tree is run, again with the container
438
+ id as the program argument.
439
+
440
+ Then the Conjur container is deleted (use --keep to leave it running).
441
+ DESC
442
+ arg_name "project-name test-script"
443
+ command "test" do |c|
444
+ c.desc "Set the current working directory"
445
+ c.flag [:d, :dir]
446
+
447
+ c.desc "Keep the Conjur appliance container after the command finishes"
448
+ c.default_value false
449
+ c.switch [:k, :keep]
450
+
451
+ c.desc "Image name"
452
+ c.default_value "registry.tld/conjur-appliance-cuke-master"
453
+ c.flag [:i, :image]
454
+
455
+ c.desc "Image tag, e.g. 4.5-stable, 4.6-stable"
456
+ c.flag [:t, "image-tag"]
457
+
458
+ c.desc "'docker pull' the Conjur container image"
459
+ c.default_value true
460
+ c.switch [:pull]
461
+
462
+ c.desc "Specify the deb version; by default, it's read from the VERSION file"
463
+ c.flag [:v, :version]
464
+
465
+ c.desc "Specify volume for test container"
466
+ c.flag [:'volumes-from'], :multiple => true
467
+
468
+ network_options(c)
469
+
470
+ c.action do |global_options, cmd_options, args|
471
+ raise "project-name is required" unless project_name = args.shift
472
+ raise "test-script is required" unless test_script = args.shift
473
+ raise "Received extra command-line arguments" if args.shift
474
+
475
+ dir = cmd_options[:dir] || '.'
476
+ dir = File.expand_path(dir)
477
+
478
+ raise "Directory #{dir} does not exist or is not a directory" unless File.directory?(dir)
479
+ raise "Directory #{dir} does not contain a .deb file" unless Dir["#{dir}/*.deb"].length >= 1
480
+
481
+ Dir.chdir dir do
482
+ image_tag = cmd_options["image-tag"] or raise "image-tag is required"
483
+ appliance_image_id = [cmd_options[:image], image_tag].join(":")
484
+ version = cmd_options[:version] || detect_version
485
+ package_name = "conjur-#{project_name}_#{version}_amd64.deb"
486
+ dev_package_name = "conjur-#{project_name}-dev_#{version}_amd64.deb"
487
+
488
+ raise "#{test_script} does not exist or is not a file" unless File.file?(test_script)
489
+
490
+ begin
491
+ tries ||= 2
492
+ Docker::Image.create 'fromImage' => appliance_image_id, &DebugMixin::DOCKER if cmd_options[:pull]
493
+ rescue
494
+ login_to_registry appliance_image_id
495
+ retry unless (tries -= 1).zero?
496
+ end
497
+
498
+
499
+ def build_test_image(appliance_image_id, project_name, packages)
500
+ packages = packages.join " "
501
+ dockerfile = <<-DOCKERFILE
502
+ FROM #{appliance_image_id}
503
+
504
+ COPY #{packages} /tmp/
505
+
506
+ RUN if dpkg --list | grep conjur-#{project_name}; then dpkg --force all --purge conjur-#{project_name}; fi
507
+ RUN if [ -f /opt/conjur/etc/#{project_name}.conf ]; then rm /opt/conjur/etc/#{project_name}.conf; fi
508
+ RUN cd /tmp; dpkg --install #{packages}
509
+
510
+ RUN touch /etc/service/conjur/down
511
+ DOCKERFILE
512
+ Dir.mktmpdir do |tmpdir|
513
+ tmpfile = Tempfile.new('Dockerfile', tmpdir)
514
+ File.write(tmpfile, dockerfile)
515
+ dockerfile_name = File.basename(tmpfile.path)
516
+ tar_cmd = "tar -cvzh -C #{tmpdir} #{dockerfile_name} -C #{Dir.pwd} #{packages}"
517
+ tar = open("| #{tar_cmd}")
518
+ begin
519
+ Docker::Image.build_from_tar(tar, :dockerfile => dockerfile_name, &DebugMixin::DOCKER)
520
+ ensure
521
+ tar.close
522
+ end
523
+ end
524
+ end
525
+
526
+ packages = [package_name]
527
+ packages << dev_package_name if File.exist? dev_package_name
528
+
529
+ begin
530
+ tries ||= 2
531
+ appliance_image = build_test_image(appliance_image_id, project_name, packages)
532
+ rescue
533
+ login_to_registry appliance_image_id
534
+ retry unless (tries -= 1).zero?
535
+ end
536
+
537
+ vendor_dir = File.expand_path("tmp/debify/#{project_name}/vendor", ENV['HOME'])
538
+ dot_bundle_dir = File.expand_path("tmp/debify/#{project_name}/.bundle", ENV['HOME'])
539
+ FileUtils.mkdir_p vendor_dir
540
+ FileUtils.mkdir_p dot_bundle_dir
541
+ options = {
542
+ 'Image' => appliance_image.id,
543
+ 'Env' => [
544
+ "CONJUR_AUTHN_LOGIN=admin",
545
+ "CONJUR_ENV=appliance",
546
+ "CONJUR_AUTHN_API_KEY=SEcret12!!!!",
547
+ "CONJUR_ADMIN_PASSWORD=SEcret12!!!!",
548
+ ] + global_options[:env],
549
+ 'HostConfig' => {
550
+ 'Binds' => [
551
+ [dir, "/src/#{project_name}"].join(':')
552
+ ]
553
+ }
554
+ }
555
+ host_config = options['HostConfig']
556
+
557
+ host_config['Privileged'] = true if Docker.version['Version'] >= '1.10.0'
558
+ host_config['VolumesFrom'] = cmd_options[:'volumes-from'] if cmd_options[:'volumes-from'] && !cmd_options[:'volumes-from'].empty?
559
+
560
+ add_network_config(options, cmd_options)
561
+
562
+ if global_options[:'local-bundle']
563
+ host_config['Binds']
564
+ .push([vendor_dir, "/src/#{project_name}/vendor"].join(':'))
565
+ .push([dot_bundle_dir, "/src/#{project_name}/.bundle"].join(':'))
566
+ end
567
+
568
+ container = Docker::Container.create(options.tap { |o| DebugMixin.debug_write "creating container with options #{o.inspect}" })
569
+
570
+ begin
571
+ DebugMixin.debug_write "Testing #{project_name} in container #{container.id}\n"
572
+
573
+ spawn("docker logs -f #{container.id}", [:out, :err] => $stderr).tap do |pid|
574
+ Process.detach pid
575
+ end
576
+ container.start!
577
+
578
+ # Wait for pg/main so that migrations can run
579
+ 30.times do
580
+ stdout, stderr, exitcode = container.exec %w(sv status pg/main), &DebugMixin::DOCKER
581
+ status = stdout.join
582
+ break if exitcode == 0 && status =~ /^run\:/
583
+ sleep 1
584
+ end
585
+
586
+ # If we're not using shared gems, run dev-install instead of
587
+ # test-install. Even having to reinstall all the gems is
588
+ # better than dealing with Docker For Mac's current file
589
+ # sharing performance.
590
+ install_cmd = global_options[:'local-bundle'] ? 'test-install' : 'dev-install'
591
+ container_command container, "/opt/conjur/evoke/bin/#{install_cmd}", project_name
592
+
593
+ DebugMixin.debug_write "Starting conjur\n"
594
+
595
+ container_command container, "rm", "/etc/service/conjur/down"
596
+ container_command container, "sv", "start", "conjur"
597
+ wait_for_conjur appliance_image, container
598
+
599
+ system "./#{test_script} #{container.id}"
600
+ exit_now! "#{test_script} failed with exit code #{$?.exitstatus}", $?.exitstatus unless $?.exitstatus == 0
601
+ ensure
602
+ unless cmd_options[:keep] || ENV['KEEP_CONTAINERS']
603
+ DebugMixin.debug_write "deleting container"
604
+ container.delete(force: true)
605
+ end
606
+ end
607
+ end
608
+ end
609
+ end
610
+
611
+ desc "Setup a development sandbox for a Conjur debian package in a Conjur appliance container"
612
+ long_desc <<DESC
613
+ First, a Conjur appliance container is created and started. By default, the
614
+ container image is registry.tld/conjur-appliance-cuke-master. An image tag
615
+ MUST be supplied. This image is configured with all the CONJUR_ environment
616
+ variables setup for the local environment (appliance URL, cert path, admin username and
617
+ password, etc). The project source tree is also mounted into the container, at
618
+ /src/<project-name>, where <project-name> is taken from the name of the current working directory.
619
+
620
+ Once in the container, use "/opt/conjur/evoke/bin/dev-install" to install the development bundle of your project.
621
+ DESC
622
+ command "sandbox" do |c|
623
+ c.desc "Set the current working directory"
624
+ c.flag [:d, :dir]
625
+
626
+ c.desc "Image name"
627
+ c.default_value "registry.tld/conjur-appliance-cuke-master"
628
+ c.flag [:i, :image]
629
+
630
+ c.desc "Image tag, e.g. 4.5-stable, 4.6-stable"
631
+ c.flag [:t, "image-tag"]
632
+
633
+ c.desc "Bind another source directory into the container. Use <src>:<dest>, where both are full paths."
634
+ c.flag [:"bind"], :multiple => true
635
+
636
+ c.desc "'docker pull' the Conjur container image"
637
+ c.default_value false
638
+ c.switch [:pull]
639
+
640
+ network_options(c)
641
+
642
+ c.desc "Specify volume for container"
643
+ c.flag [:'volumes-from'], :multiple => true
644
+
645
+ c.desc "Expose a port from the container to host. Use <host>:<container>."
646
+ c.flag [:p, :port], :multiple => true
647
+
648
+ c.desc 'Run dev-install in /src/<project-name>'
649
+ c.default_value false
650
+ c.switch [:'dev-install']
651
+
652
+ c.desc 'Kill previous sandbox container'
653
+ c.default_value false
654
+ c.switch [:kill]
655
+
656
+ c.desc 'A command to run in the sandbox'
657
+ c.flag [:c, :command]
658
+
659
+ c.action do |global_options, cmd_options, args|
660
+ raise "Received extra command-line arguments" if args.shift
661
+
662
+ dir = cmd_options[:dir] || '.'
663
+ dir = File.expand_path(dir)
664
+
665
+ raise "Directory #{dir} does not exist or is not a directory" unless File.directory?(dir)
666
+
667
+ Dir.chdir dir do
668
+ image_tag = cmd_options["image-tag"] or raise "image-tag is required"
669
+ appliance_image_id = [cmd_options[:image], image_tag].join(":")
670
+
671
+ appliance_image = if cmd_options[:pull]
672
+ begin
673
+ tries ||= 2
674
+ Docker::Image.create 'fromImage' => appliance_image_id, &DebugMixin::DOCKER if cmd_options[:pull]
675
+ rescue
676
+ login_to_registry appliance_image_id
677
+ retry unless (tries -= 1).zero?
678
+ end
679
+ else
680
+ Docker::Image.get appliance_image_id
681
+ end
682
+
683
+ project_name = File.basename(Dir.getwd)
684
+ vendor_dir = File.expand_path("tmp/debify/#{project_name}/vendor", ENV['HOME'])
685
+ dot_bundle_dir = File.expand_path("tmp/debify/#{project_name}/.bundle", ENV['HOME'])
686
+ FileUtils.mkdir_p vendor_dir
687
+ FileUtils.mkdir_p dot_bundle_dir
688
+
689
+ options = {
690
+ 'name' => "#{project_name}-sandbox",
691
+ 'Image' => appliance_image.id,
692
+ 'WorkingDir' => "/src/#{project_name}",
693
+ 'Env' => [
694
+ "CONJUR_AUTHN_LOGIN=admin",
695
+ "CONJUR_ENV=appliance",
696
+ "CONJUR_AUTHN_API_KEY=SEcret12!!!!",
697
+ "CONJUR_ADMIN_PASSWORD=SEcret12!!!!",
698
+ ] + global_options[:env]
699
+ }
700
+
701
+ options['HostConfig'] = host_config = {}
702
+ host_config['Binds'] = [
703
+ [File.expand_path(".ssh/id_rsa", ENV['HOME']), "/root/.ssh/id_rsa", 'ro'].join(':'),
704
+ [dir, "/src/#{project_name}"].join(':'),
705
+ ] + Array(cmd_options[:bind])
706
+
707
+ if global_options[:'local-bundle']
708
+ host_config['Binds']
709
+ .push([vendor_dir, "/src/#{project_name}/vendor"].join(':'))
710
+ .push([dot_bundle_dir, "/src/#{project_name}/.bundle"].join(':'))
711
+ end
712
+
713
+ host_config['Privileged'] = true if Docker.version['Version'] >= '1.10.0'
714
+ host_config['VolumesFrom'] = cmd_options[:'volumes-from'] unless cmd_options[:'volumes-from'].empty?
715
+
716
+ add_network_config(options, cmd_options)
717
+
718
+ unless cmd_options[:port].empty?
719
+ port_bindings = Hash.new({})
720
+ cmd_options[:port].each do |mapping|
721
+ hport, cport = mapping.split(':')
722
+ port_bindings["#{cport}/tcp"] = [{'HostPort' => hport}]
723
+ end
724
+ host_config['PortBindings'] = port_bindings
725
+ end
726
+
727
+ if cmd_options[:kill]
728
+ previous = Docker::Container.get(options['name']) rescue nil
729
+ previous.delete(:force => true) if previous
730
+ end
731
+
732
+ container = Docker::Container.create(options.tap { |o| DebugMixin.debug_write "creating container with options #{o.inspect}" })
733
+ $stdout.puts container.id
734
+ container.start!
735
+
736
+ wait_for_conjur appliance_image, container
737
+
738
+ if cmd_options[:'dev-install']
739
+ container_command(container, "/opt/conjur/evoke/bin/dev-install", project_name)
740
+ container_command(container, 'sv', 'restart', "conjur/#{project_name}")
741
+ end
742
+
743
+ if cmd_options[:command]
744
+ container_command(container, '/bin/bash', '-c', cmd_options[:command])
745
+ end
746
+ end
747
+ end
748
+ end
749
+
750
+ desc "Publish a debian package to apt repository"
751
+ long_desc <<DESC
752
+ Publishes a deb created with `debify package` to our private apt repository.
753
+
754
+ "distribution" should match the major/minor version of the Conjur appliance you want to install to.
755
+
756
+ The package name is a required option. The package version can be specified as a CLI option, or it will
757
+ be auto-detected from Git.
758
+
759
+ --component should be 'stable' if run after package tests pass or 'testing' if the package is not yet ready for release.
760
+ If you don't specify the component, it will be set to 'testing' unless the current git branch is 'master' or 'origin/master'.
761
+ The git branch is first detected from the env var GIT_BRANCH or BRANCH_NAME, and then by checking `git rev-parse --abbrev-ref HEAD`
762
+ (which won't give you the answer you want when detached).
763
+
764
+ DESC
765
+ arg_name "distribution project-name"
766
+ command "publish" do |c|
767
+ c.desc "Set the current working directory"
768
+ c.flag [:d, :dir]
769
+
770
+ c.desc "Specify the deb package version; by default, it's computed automatically"
771
+ c.flag [:v, :version]
772
+
773
+ c.desc "Component to publish to, either 'stable' or the name of the git branch"
774
+ c.flag [:c, :component]
775
+
776
+ c.desc "Artifactory URL to publish to"
777
+ c.default_value "https://conjurinc.jfrog.io/conjurinc"
778
+ c.flag [:u, :url]
779
+
780
+ c.desc "Artifactory Debian repo to publish package to"
781
+ c.default_value "debian-private"
782
+ c.flag [:r, :repo]
783
+
784
+ c.desc "Artifactory RPM repo to publish package to"
785
+ c.default_value "redhat-private"
786
+ c.flag ['rpm-repo']
787
+
788
+ c.action do |global_options, cmd_options, args|
789
+ require 'conjur/debify/action/publish'
790
+ raise "distribution is required" unless distribution = args.shift
791
+ raise "project-name is required" unless project_name = args.shift
792
+ raise "Received extra command-line arguments" if args.shift
793
+
794
+ Conjur::Debify::Action::Publish.new(distribution, project_name, cmd_options).run
795
+ end
796
+ end
797
+
798
+ desc "Auto-detect and print the repository version"
799
+ command "detect-version" do |c|
800
+ c.desc "Set the current working directory"
801
+ c.flag [:d, :dir]
802
+ c.action do |global_options, cmd_options, args|
803
+ raise "Received extra command-line arguments" if args.shift
804
+
805
+ dir = cmd_options[:dir] || '.'
806
+ dir = File.expand_path(dir)
807
+
808
+ raise "Directory #{dir} does not exist or is not a directory" unless File.directory?(dir)
809
+
810
+ Dir.chdir dir do
811
+ puts detect_version
812
+ end
813
+ end
814
+ end
815
+
816
+ desc 'Show the given configuration'
817
+ arg_name 'configuration'
818
+ command 'config' do |c|
819
+ c.action do |_, _, args|
820
+ raise 'no configuration provided' unless config = args.shift
821
+ raise "Received extra command-line arguments" if args.shift
822
+
823
+ File.open(File.join('distrib', config)).each do |line|
824
+ puts line.gsub(/@@DEBIFY_VERSION@@/, Conjur::Debify::VERSION)
825
+ end
826
+ end
827
+ end
828
+
829
+
830
+ pre do |global, command, options, args|
831
+ # Pre logic here
832
+ # Return true to proceed; false to abort and not call the
833
+ # chosen command
834
+ # Use skips_pre before a command to skip this block
835
+ # on that command only
836
+ true
837
+ end
838
+
839
+ post do |global, command, options, args|
840
+ # Post logic here
841
+ # Use skips_post before a command to skip this
842
+ # block on that command only
843
+ end
844
+
845
+ on_error do |exception|
846
+ # Error logic here
847
+ # return false to skip default error handling
848
+ true
849
+ end
850
+